diff --git a/packages/syncfusion_flutter_barcodes/README.md b/packages/syncfusion_flutter_barcodes/README.md index 95bc7afd7..141e83ed8 100644 --- a/packages/syncfusion_flutter_barcodes/README.md +++ b/packages/syncfusion_flutter_barcodes/README.md @@ -1,12 +1,10 @@ ![syncfusion_flutter_barcode_banner](https://cdn.syncfusion.com/content/images/FTControl/Flutter/Barcode%20Banner.png) -# Syncfusion Flutter Barcodes +# Flutter Barcodes library -The Syncfusion Flutter Barcode Generator is a data visualization widget used to generate and display data in a machine-readable format. It provides a perfect approach to encoding input values using supported symbology types. +Flutter Barcode Generator package is a data visualization widget used to generate and display data in a machine-readable format. It provides a perfect approach to encoding input values using supported symbology types. -**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or Syncfusion Community License. For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. - -**Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. +**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. ## Table of contents - [Barcode Generator feature](#barcode-generator-feature) diff --git a/packages/syncfusion_flutter_barcodes/analysis_options.yaml b/packages/syncfusion_flutter_barcodes/analysis_options.yaml index 2a22b53a9..7b491de76 100644 --- a/packages/syncfusion_flutter_barcodes/analysis_options.yaml +++ b/packages/syncfusion_flutter_barcodes/analysis_options.yaml @@ -4,3 +4,4 @@ analyzer: errors: include_file_not_found: ignore lines_longer_than_80_chars: ignore + avoid_as: false diff --git a/packages/syncfusion_flutter_barcodes/example/pubspec.yaml b/packages/syncfusion_flutter_barcodes/example/pubspec.yaml index 1fb1b27c6..3dc82ab5b 100644 --- a/packages/syncfusion_flutter_barcodes/example/pubspec.yaml +++ b/packages/syncfusion_flutter_barcodes/example/pubspec.yaml @@ -14,7 +14,7 @@ description: A new Flutter project. version: 1.0.0+1 environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: @@ -24,7 +24,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^0.1.2 + cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/base/barcode_generator.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/base/barcode_generator.dart index a25af0cca..55e7d8692 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/base/barcode_generator.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/base/barcode_generator.dart @@ -19,11 +19,10 @@ import '../one_dimensional/upca_symbology.dart'; import '../one_dimensional/upce_symbology.dart'; import '../renderers/one_dimensional/codabar_renderer.dart'; - -import '../renderers/one_dimensional/code128A_renderer.dart'; -import '../renderers/one_dimensional/code128B_renderer.dart'; -import '../renderers/one_dimensional/code128C_renderer.dart'; import '../renderers/one_dimensional/code128_renderer.dart'; +import '../renderers/one_dimensional/code128a_renderer.dart'; +import '../renderers/one_dimensional/code128b_renderer.dart'; +import '../renderers/one_dimensional/code128c_renderer.dart'; import '../renderers/one_dimensional/code39_extended_renderer.dart'; import '../renderers/one_dimensional/code39_renderer.dart'; import '../renderers/one_dimensional/code93_renderer.dart'; @@ -87,9 +86,9 @@ class SfBarcodeGenerator extends StatefulWidget { /// /// Default symbology is [Code128]. SfBarcodeGenerator( - {Key key, - @required this.value, - Symbology symbology, + {Key? key, + required this.value, + Symbology? symbology, this.barColor, this.backgroundColor, this.showValue = false, @@ -115,7 +114,7 @@ class SfBarcodeGenerator extends StatefulWidget { /// child: SfBarcodeGenerator(value:'123456')); ///} /// ```dart - final String value; + final String? value; /// Define the barcode symbology that will be used to encode the input value /// to the visual barcode representation. @@ -160,7 +159,7 @@ class SfBarcodeGenerator extends StatefulWidget { /// barColor : Colors.red)); ///} /// ```dart - final Color barColor; + final Color? barColor; /// The background color to fill the background of the [SfBarcodeGenerator]. /// @@ -176,7 +175,7 @@ class SfBarcodeGenerator extends StatefulWidget { /// backgroundColor : Colors.red)); ///} /// ```dart - final Color backgroundColor; + final Color? backgroundColor; /// Whether to show a human readable text (input value) along with a barcode. /// @@ -261,12 +260,12 @@ class SfBarcodeGenerator extends StatefulWidget { /// Represents the barcode generator state class _SfBarcodeGeneratorState extends State { /// Specifies the theme data - SfBarcodeThemeData _barcodeTheme; + late SfBarcodeThemeData _barcodeTheme; /// Specifies the text size - Size _textSize; + Size? _textSize; - SymbologyRenderer _symbologyRenderer; + late SymbologyRenderer _symbologyRenderer; @override void didChangeDependencies() { @@ -332,12 +331,12 @@ class _SfBarcodeGeneratorState extends State { if (widget.showValue && _textSize == null) { _textSize = measureText(widget.value.toString(), widget.textStyle); } - _symbologyRenderer.getIsValidateInput(widget.value); + _symbologyRenderer.getIsValidateInput(widget.value!); _symbologyRenderer.textSize = _textSize; return Container( color: widget.backgroundColor ?? _barcodeTheme.backgroundColor, child: SfBarcodeGeneratorRenderObjectWidget( - value: widget.value, + value: widget.value!, symbology: widget.symbology, foregroundColor: widget.barColor ?? _barcodeTheme.barColor, showText: widget.showValue, diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/base/symbology_base.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/base/symbology_base.dart index 6f6e91b74..408e65e8d 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/base/symbology_base.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/base/symbology_base.dart @@ -44,5 +44,5 @@ abstract class Symbology { /// symbology: UPCE(module: 2))); ///} /// ```dart - final int module; + final int? module; } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/common/barcode_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/common/barcode_renderer.dart index d5e2e6957..1c1a2d796 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/common/barcode_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/common/barcode_renderer.dart @@ -8,7 +8,7 @@ import '../renderers/one_dimensional/symbology_base_renderer.dart'; class SfBarcodeGeneratorRenderObjectWidget extends LeafRenderObjectWidget { /// Creates the render object widget const SfBarcodeGeneratorRenderObjectWidget( - {Key key, + {Key? key, this.value, this.symbology, this.foregroundColor, @@ -21,59 +21,59 @@ class SfBarcodeGeneratorRenderObjectWidget extends LeafRenderObjectWidget { : super(key: key); ///Defines the value of the barcode to be rendered. - final String value; + final String? value; ///Define anyone of barcode symbology that will be used to ///convert input value into visual barcode representation - final Symbology symbology; + final Symbology? symbology; /// Define the color for barcode elements. - final Color foregroundColor; + final Color? foregroundColor; /// Specifies whether to show the value along with the barcode. - final bool showText; + final bool? showText; /// Specifies the spacing between the text and the barcode. - final double textSpacing; + final double? textSpacing; ///Defines the text alignment for the text to be rendered in the barcode. - final TextStyle textStyle; + final TextStyle? textStyle; /// Add style to customize the text. - final Size textSize; + final Size? textSize; /// Specifies the spacing between the text and the barcode. - final TextAlign textAlign; + final TextAlign? textAlign; /// Specifies the corresponding renderer class - final SymbologyRenderer symbologyRenderer; + final SymbologyRenderer? symbologyRenderer; @override RenderObject createRenderObject(BuildContext context) { return _RenderBarcode( - value: value, - symbology: symbology, - foregroundColor: foregroundColor, - showText: showText, - textSpacing: textSpacing, - symbologyRenderer: symbologyRenderer, - textStyle: textStyle, + value: value!, + symbology: symbology!, + foregroundColor: foregroundColor!, + showText: showText!, + textSpacing: textSpacing!, + symbologyRenderer: symbologyRenderer!, + textStyle: textStyle!, textSize: textSize, - textAlign: textAlign); + textAlign: textAlign!); } @override void updateRenderObject(BuildContext context, _RenderBarcode renderObject) { renderObject - ..value = value - ..symbology = symbology - ..symbologyRenderer = symbologyRenderer - ..foregroundColor = foregroundColor - ..showText = showText - ..textSpacing = textSpacing - ..textStyle = textStyle + ..value = value! + ..symbology = symbology! + ..symbologyRenderer = symbologyRenderer! + ..foregroundColor = foregroundColor! + ..showText = showText! + ..textSpacing = textSpacing! + ..textStyle = textStyle! ..textSize = textSize - ..textAlign = textAlign; + ..textAlign = textAlign!; } } @@ -81,15 +81,15 @@ class SfBarcodeGeneratorRenderObjectWidget extends LeafRenderObjectWidget { class _RenderBarcode extends RenderBox { /// Creates the RenderBarcode _RenderBarcode( - {@required String value, - Symbology symbology, - SymbologyRenderer symbologyRenderer, - Color foregroundColor, - bool showText, - double textSpacing, - TextStyle textStyle, - Size textSize, - TextAlign textAlign}) + {required String value, + required Symbology symbology, + required SymbologyRenderer symbologyRenderer, + required Color foregroundColor, + required bool showText, + required double textSpacing, + required TextStyle textStyle, + Size? textSize, + required TextAlign textAlign}) : _value = value, _symbology = symbology, _symbologyRenderer = symbologyRenderer, @@ -120,7 +120,7 @@ class _RenderBarcode extends RenderBox { TextStyle _textStyle; /// Add style to customize the text. - Size _textSize; + Size? _textSize; /// Specifies the spacing between the text and the barcode. TextAlign _textAlign; @@ -147,7 +147,7 @@ class _RenderBarcode extends RenderBox { TextStyle get textStyle => _textStyle; /// Returns the text size value - Size get textSize => _textSize; + Size get textSize => _textSize!; /// Returns the text align value TextAlign get textAlign => _textAlign; @@ -204,7 +204,7 @@ class _RenderBarcode extends RenderBox { } /// Sets the text size value - set textSize(Size value) { + set textSize(Size? value) { if (_textSize != value) { _textSize = value; markNeedsPaint(); @@ -252,7 +252,7 @@ class _RenderBarcode extends RenderBox { size.height - (showText ? (_textSpacing + - (_textSize != null ? _textSize.height : 0)) + (_textSize != null ? _textSize!.height : 0)) : 0)), offset, value, diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/codabar_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/codabar_symbology.dart index bbf5b53e8..f2115b817 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/codabar_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/codabar_symbology.dart @@ -7,5 +7,5 @@ class Codabar extends Symbology { /// /// The arguments [module] must be non-negative and greater than 0. /// - Codabar({int module}) : super(module: module); + Codabar({int? module}) : super(module: module); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128_symbology.dart index 1a2a4f0b0..729d578e3 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128_symbology.dart @@ -18,7 +18,7 @@ class Code128 extends Symbology { /// This is a very large method. This method could be /// refactor to a smaller methods, but it degrades the performance.Since it /// adds the character corresponding to this symbology is added in to the list - Code128({int module}) + Code128({int? module}) : super( module: module, ); diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128a_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128a_symbology.dart index 0cf5a17d4..5dfb5cf3f 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128a_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128a_symbology.dart @@ -9,5 +9,5 @@ class Code128A extends Code128 { /// /// The arguments [module] must be non-negative and greater than 0. /// - Code128A({int module}) : super(module: module); + Code128A({int? module}) : super(module: module); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128b_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128b_symbology.dart index e6253ac0c..4f1bbb3b0 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128b_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128b_symbology.dart @@ -9,5 +9,5 @@ class Code128B extends Code128 { /// /// The arguments [module] must be non-negative and greater than 0. /// - Code128B({int module}) : super(module: module); + Code128B({int? module}) : super(module: module); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128c_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128c_symbology.dart index 26e0064d3..090a018a9 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128c_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code128c_symbology.dart @@ -9,5 +9,5 @@ class Code128C extends Code128 { /// /// The arguments [module] must be non-negative and greater than 0. /// - Code128C({int module}) : super(module: module); + Code128C({int? module}) : super(module: module); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code39_extended_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code39_extended_symbology.dart index e3c9d4b0c..305fc0f65 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code39_extended_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code39_extended_symbology.dart @@ -17,6 +17,6 @@ class Code39Extended extends Code39 { /// This is a very large method. This method could be /// refactored to a smaller methods, but it degrades the performance.Since it /// adds character corresponding to this symbology is added in to the list - Code39Extended({int module, bool enableCheckSum}) + Code39Extended({int? module, bool? enableCheckSum}) : super(module: module, enableCheckSum: enableCheckSum ?? true); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code39_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code39_symbology.dart index cfd1d278e..b169405c7 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code39_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code39_symbology.dart @@ -23,7 +23,7 @@ class Code39 extends Symbology { /// a modulo 43 checksum can be added, /// if the [enableCheckSum] is true. /// - Code39({int module, this.enableCheckSum = true}) : super(module: module); + Code39({int? module, this.enableCheckSum = true}) : super(module: module); /// Whether to add a checksum on the far right side of the barcode. /// diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code93_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code93_symbology.dart index cbce95508..18bb965c4 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code93_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/code93_symbology.dart @@ -14,5 +14,5 @@ class Code93 extends Symbology { /// high level of accuracy. /// The checksum character is the modulo 47 remainder of the sum of the /// weighted value of the data characters. - Code93({int module}) : super(module: module); + Code93({int? module}) : super(module: module); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean13_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean13_symbology.dart index 4efa36b44..d1294f52f 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean13_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean13_symbology.dart @@ -23,5 +23,5 @@ class EAN13 extends Symbology { /// valid check digit otherwise remove it, since it has been calculated /// automatically. /// - EAN13({int module}) : super(module: module); + EAN13({int? module}) : super(module: module); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean8_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean8_symbology.dart index 26ec9341c..5bc9f56dc 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean8_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/ean8_symbology.dart @@ -20,5 +20,5 @@ class EAN8 extends Symbology { /// valid check digit otherwise remove it, since it has been calculated /// automatically. /// - EAN8({int module}) : super(module: module); + EAN8({int? module}) : super(module: module); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upca_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upca_symbology.dart index 30d08fb58..b0a820fc7 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upca_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upca_symbology.dart @@ -21,5 +21,5 @@ class UPCA extends Symbology { /// be valid check digit otherwise remove it, since it has been calculated /// automatically. /// - UPCA({int module}) : super(module: module); + UPCA({int? module}) : super(module: module); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upce_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upce_symbology.dart index f97dbc34d..fa3cf3e1d 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upce_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/one_dimensional/upce_symbology.dart @@ -16,5 +16,5 @@ class UPCE extends Symbology { /// By default, the number system(0) will add at the front and check digit /// at the end along with 6 digits of the input product code. /// - UPCE({int module}) : super(module: module); + UPCE({int? module}) : super(module: module); } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/codabar_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/codabar_renderer.dart index 5b1072cc7..61a096406 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/codabar_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/codabar_renderer.dart @@ -7,7 +7,7 @@ import 'symbology_base_renderer.dart'; /// Represents codabar renderer class CodabarRenderer extends SymbologyRenderer { /// Creates codabar renderer class - CodabarRenderer({Symbology symbology}) : super(symbology: symbology) { + CodabarRenderer({Symbology? symbology}) : super(symbology: symbology) { _codeBarMap = { '0': '101010011', '1': '101011001', @@ -33,7 +33,7 @@ class CodabarRenderer extends SymbologyRenderer { } /// Represents the supported symbol and its byte value - Map _codeBarMap; + late Map _codeBarMap; @override bool getIsValidateInput(String value) { @@ -61,17 +61,17 @@ class CodabarRenderer extends SymbologyRenderer { TextAlign textAlign, bool showValue) { final Paint paint = getBarPaint(foregroundColor); - final List code = _getCodeValues(value); + final List code = _getCodeValues(value); final int barTotalLength = _getTotalLength(code); - double left = symbology.module == null + double left = symbology!.module == null ? offset.dx : getLeftPosition( - barTotalLength, symbology.module, size.width, offset.dx); + barTotalLength, symbology!.module!, size.width, offset.dx); final Rect barCodeRect = Rect.fromLTRB( offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); double ratio = 0; - if (symbology.module != null) { - ratio = symbology.module.toDouble(); + if (symbology!.module != null) { + ratio = symbology!.module!.toDouble(); } else { // Calculates the bar length based on number of individual bar codes final int singleModule = (size.width ~/ barTotalLength).toInt(); @@ -81,12 +81,9 @@ class CodabarRenderer extends SymbologyRenderer { } left = left.roundToDouble(); for (int i = 0; i < code.length; i++) { - final String codeValue = code[i]; - const bool hasExtraHeight = false; - final double barHeight = hasExtraHeight - ? size.height + textSize.height + textSpacing - : size.height; - final int codeLength = codeValue.length; + final String? codeValue = code[i]; + final double barHeight = size.height; + final int codeLength = codeValue!.length; for (int j = 0; j < codeLength; j++) { final bool canDraw = codeValue[j] == '1' ? true : false; @@ -109,10 +106,10 @@ class CodabarRenderer extends SymbologyRenderer { } /// Calculate total bar length from give input value - int _getTotalLength(List code) { + int _getTotalLength(List code) { int count = 0; for (int i = 0; i < code.length; i++) { - final int numberOfDigits = code[i].length; + final int numberOfDigits = code[i]!.length; count += numberOfDigits; } count += code.length - 1; @@ -125,10 +122,10 @@ class CodabarRenderer extends SymbologyRenderer { } /// Returns the encoded value of the provided input value - List _getCodeValues(String value) { + List _getCodeValues(String value) { valueWithStartAndStopSymbol = _getValueWithStartAndStopSymbol(value); - final List codeBarValues = - List(valueWithStartAndStopSymbol.length); + final List codeBarValues = + List.filled(valueWithStartAndStopSymbol.length, null); for (int i = 0; i < valueWithStartAndStopSymbol.length; i++) { for (int j = 0; j < _codeBarMap.length; j++) { if (valueWithStartAndStopSymbol[i] == diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128_renderer.dart index a70e5adf9..760fdb384 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128_renderer.dart @@ -10,7 +10,7 @@ import 'symbology_base_renderer.dart'; /// Represents the code128 renderer class class Code128Renderer extends SymbologyRenderer { /// Creates the code128 renderer - Code128Renderer({Symbology symbology}) : super(symbology: symbology) { + Code128Renderer({Symbology? symbology}) : super(symbology: symbology) { code128ACharacterSets = []; code128ACharacterSets.add(' '); @@ -299,13 +299,13 @@ class Code128Renderer extends SymbologyRenderer { static const String _fnc4 = '\u00f4'; /// Represents the supported symbol character of code128A - List code128ACharacterSets; + late List code128ACharacterSets; /// Represents the supported symbol character of code128B - List code128BCharacterSets; + late List code128BCharacterSets; /// Represents the supported symbol character of code128C - List code128CCharacterSets; + late List code128CCharacterSets; /// Returns the byte value of supported symbol /// @@ -523,7 +523,7 @@ class Code128Renderer extends SymbologyRenderer { /// Method to validate the corresponding code set based on the input int _getValidatedCode(int start, int previousCodeSet, String value) { CodeType codeType = _getCodeType(start, value); - final int currentCodeType = + final int? currentCodeType = _getValidatedCodeTypes(start, previousCodeSet, value, codeType); if (currentCodeType != null) { return currentCodeType; @@ -565,7 +565,7 @@ class Code128Renderer extends SymbologyRenderer { } /// Method to get the validated types - int _getValidatedCodeTypes( + int? _getValidatedCodeTypes( int start, int previousCodeSet, String value, CodeType codeType) { if (codeType == CodeType.singleDigit) { if (previousCodeSet == _codeA) { @@ -638,13 +638,13 @@ class Code128Renderer extends SymbologyRenderer { final Paint paint = getBarPaint(foregroundColor); final List> encodedValue = _getEncodedValue(value); final int totalBarLength = _getTotalBarLength(encodedValue); - double left = symbology.module == null + double left = symbology?.module == null ? offset.dx : getLeftPosition( - totalBarLength, symbology.module, size.width, offset.dx); + totalBarLength, symbology?.module, size.width, offset.dx); double ratio = 0; - if (symbology.module != null) { - ratio = symbology.module.toDouble(); + if (symbology?.module != null) { + ratio = symbology!.module!.toDouble(); } else { // Calculates the bar length based on number of individual bar codes final int singleModule = (size.width ~/ totalBarLength).toInt(); diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128a_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128a_renderer.dart index 3fd66405d..062d4d2e2 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128a_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128a_renderer.dart @@ -4,7 +4,7 @@ import 'code128_renderer.dart'; /// Represents the code128A renderer class class Code128ARenderer extends Code128Renderer { /// Creates the code128A renderer - Code128ARenderer({Symbology symbology}) : super(symbology: symbology); + Code128ARenderer({Symbology? symbology}) : super(symbology: symbology); @override bool getIsValidateInput(String value) { diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128b_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128b_renderer.dart index 67e881b17..3eaa76395 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128b_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128b_renderer.dart @@ -4,7 +4,7 @@ import 'code128_renderer.dart'; /// Represents the code128B renderer class class Code128BRenderer extends Code128Renderer { /// Creates the code128B renderer - Code128BRenderer({Symbology symbology}) : super(symbology: symbology); + Code128BRenderer({Symbology? symbology}) : super(symbology: symbology); @override bool getIsValidateInput(String value) { diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128c_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128c_renderer.dart index 833078870..25010d5c7 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128c_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code128c_renderer.dart @@ -4,7 +4,7 @@ import 'code128_renderer.dart'; /// Represents the code128C renderer class class Code128CRenderer extends Code128Renderer { /// Creates the code128C renderer - Code128CRenderer({Symbology symbology}) : super(symbology: symbology); + Code128CRenderer({Symbology? symbology}) : super(symbology: symbology); @override bool getIsValidateInput(String value) { diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code39_extended_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code39_extended_renderer.dart index 8e03e09ac..8d8706b93 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code39_extended_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code39_extended_renderer.dart @@ -4,7 +4,7 @@ import 'code39_renderer.dart'; /// Represents code39 extended renderer class class Code39ExtendedRenderer extends Code39Renderer { /// Creates the code39 extended class - Code39ExtendedRenderer({Symbology symbology}) : super(symbology: symbology) { + Code39ExtendedRenderer({Symbology? symbology}) : super(symbology: symbology) { _code39ExtendedMap = { '0': '%U', '1': '\$A', @@ -138,7 +138,7 @@ class Code39ExtendedRenderer extends Code39Renderer { } /// Map to stores the input character and its index - Map _code39ExtendedMap; + late Map _code39ExtendedMap; /// To validate the provided input value @override diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code39_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code39_renderer.dart index f30f6b6bd..c2156c9c4 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code39_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code39_renderer.dart @@ -8,7 +8,7 @@ import 'symbology_base_renderer.dart'; /// Represents the code39 renderer class class Code39Renderer extends SymbologyRenderer { /// Creates codabar renderer class - Code39Renderer({Symbology symbology}) : super(symbology: symbology) { + Code39Renderer({Symbology? symbology}) : super(symbology: symbology) { _code39Symbology = [ '111221211', '211211112', @@ -60,10 +60,10 @@ class Code39Renderer extends SymbologyRenderer { } /// Represents the code39 symbology - List _code39Symbology; + late List _code39Symbology; /// Represnts the encoded input character - String _character; + late String _character; @override bool getIsValidateInput(String value) { @@ -116,15 +116,15 @@ class Code39Renderer extends SymbologyRenderer { final Paint paint = getBarPaint(foregroundColor); final List code = getCodeValues(value); final int barTotalLength = _getTotalLength(code); - double left = symbology.module == null + double left = symbology?.module == null ? offset.dx : getLeftPosition( - barTotalLength, symbology.module, size.width, offset.dx); + barTotalLength, symbology?.module, size.width, offset.dx); final Rect barCodeRect = Rect.fromLTRB( offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); double ratio = 0; - if (symbology.module != null) { - ratio = symbology.module.toDouble(); + if (symbology?.module != null) { + ratio = symbology!.module!.toDouble(); } else { // Calculates the bar length based on number of individual bar codes final int singleModule = (size.width ~/ barTotalLength).toInt(); @@ -135,10 +135,7 @@ class Code39Renderer extends SymbologyRenderer { left = left.roundToDouble(); for (int i = 0; i < code.length; i++) { final String codeValue = code[i]; - const bool hasExtraHeight = false; - final double barHeight = hasExtraHeight - ? size.height + textSize.height + textSpacing - : size.height; + final double barHeight = size.height; final int codeLength = codeValue.length; for (int j = 0; j < codeLength; j++) { // The current bar is drawn, if its value is divisible by 2 @@ -182,7 +179,8 @@ class Code39Renderer extends SymbologyRenderer { /// Represents the encoded value for provided input List getEncodedValue(String providedValue) { - final Code39 code39Symbology = symbology; + // ignore: avoid_as + final Code39 code39Symbology = symbology as Code39; if (code39Symbology.enableCheckSum) { final String checkSum = _getCheckSum(providedValue, _character); providedValue += checkSum; diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code93_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code93_renderer.dart index 50ec026da..23e6e0c3d 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code93_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/code93_renderer.dart @@ -7,12 +7,12 @@ import 'symbology_base_renderer.dart'; /// Represents the code93 renderer class class Code93Renderer extends SymbologyRenderer { /// Creates the code93 renderer - Code93Renderer({Symbology symbology}) : super(symbology: symbology) { + Code93Renderer({Symbology? symbology}) : super(symbology: symbology!) { _character = _getCode93Character(); } /// Represents the input value - String _character; + late String _character; @override bool getIsValidateInput(String value) { @@ -144,15 +144,15 @@ class Code93Renderer extends SymbologyRenderer { final Paint paint = getBarPaint(foregroundColor); final List code = _getCodeValues(value); final int barTotalLength = _getTotalLength(code); - double left = symbology.module == null + double left = symbology?.module == null ? offset.dx : getLeftPosition( - barTotalLength, symbology.module, size.width, offset.dx); + barTotalLength, symbology?.module, size.width, offset.dx); final Rect barCodeRect = Rect.fromLTRB( offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); double ratio = 0; - if (symbology.module != null) { - ratio = symbology.module.toDouble(); + if (symbology?.module != null) { + ratio = symbology!.module!.toDouble(); } else { //Calculates the bar length based on number of individual bar codes final int singleModule = (size.width ~/ barTotalLength).toInt(); @@ -163,10 +163,7 @@ class Code93Renderer extends SymbologyRenderer { left = left.roundToDouble(); for (int i = 0; i < code.length; i++) { final String codeValue = code[i]; - const bool hasExtraHeight = false; - final double barHeight = hasExtraHeight - ? size.height + textSize.height + textSpacing - : size.height; + final double barHeight = size.height; final int codeLength = codeValue.length; for (int j = 0; j < codeLength; j++) { //Draws the barcode when the corresponding bar value is one diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/ean13_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/ean13_renderer.dart index f1dcd1fa4..fc4237c51 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/ean13_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/ean13_renderer.dart @@ -8,10 +8,10 @@ import 'symbology_base_renderer.dart'; /// Represents the EAN13 renderer class class EAN13Renderer extends SymbologyRenderer { /// Creates the ean13 renderer - EAN13Renderer({Symbology symbology}) : super(symbology: symbology); + EAN13Renderer({Symbology? symbology}) : super(symbology: symbology); /// Represents the encoded input vale - String _encodedValue; + late String _encodedValue; @override bool getIsValidateInput(String value) { @@ -49,30 +49,30 @@ class EAN13Renderer extends SymbologyRenderer { bool showValue) { /// _singleDigitValues[0] specifies left value of start digit /// _singleDigitValues[1] specifies width of start digit - final List singleDigitValues = List(2); + final List singleDigitValues = List.filled(2, null); /// _positions[0] specifies end position of start bar /// _positions[1] specifies start position of middle bar /// _positions[2] specifies end position of middle bar /// _positions[3] specifies start position of end bar - final List positions = List(4); + final List positions = List.filled(4, null); final Paint paint = getBarPaint(foregroundColor); final List code = _getCodeValues(); final int barTotalLength = _getTotalLength(code); singleDigitValues[1] = showValue ? measureText(_encodedValue[0], textStyle).width : 0; const int additionalWidth = 2; - singleDigitValues[1] += additionalWidth; - final double width = size.width - singleDigitValues[1]; - double left = symbology.module == null - ? offset.dx + singleDigitValues[1] - : getLeftPosition(barTotalLength, symbology.module, width, - offset.dx + singleDigitValues[1]); + singleDigitValues[1] = singleDigitValues[1]! + additionalWidth; + final double width = size.width - singleDigitValues[1]!; + double left = symbology?.module == null + ? offset.dx + singleDigitValues[1]! + : getLeftPosition(barTotalLength, symbology?.module, width, + offset.dx + singleDigitValues[1]!); final Rect barCodeRect = Rect.fromLTRB( offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); double ratio = 0; - if (symbology.module != null) { - ratio = symbology.module.toDouble(); + if (symbology?.module != null) { + ratio = symbology!.module!.toDouble(); } else { // Calculates the bar length based on number of individual bar codes final int singleModule = (width ~/ barTotalLength).toInt(); @@ -81,7 +81,7 @@ class EAN13Renderer extends SymbologyRenderer { left += leftPadding; } left = left.roundToDouble(); - singleDigitValues[0] = left - singleDigitValues[1]; + singleDigitValues[0] = left - singleDigitValues[1]!; for (int i = 0; i < code.length; i++) { final String codeValue = code[i]; final bool hasExtraHeight = getHasExtraHeight(i, code); @@ -89,7 +89,7 @@ class EAN13Renderer extends SymbologyRenderer { final double barHeight = hasExtraHeight ? size.height + (showValue - ? (textSize.height * additionalHeight) + textSpacing + ? (textSize!.height * additionalHeight) + textSpacing : 0) : size.height; final int codeLength = codeValue.length; @@ -223,8 +223,8 @@ class EAN13Renderer extends SymbologyRenderer { /// Method to calculate the left value String _getLeftValue(bool isLeft, String structure, String leftString) { - String code; - List tempCodes; + late String code; + late List tempCodes; final Map> codes = _getBinaries(); for (int i = 0; i < leftString.length; i++) { if (structure[i] == 'L') { @@ -258,19 +258,19 @@ class EAN13Renderer extends SymbologyRenderer { TextStyle textStyle, double textSpacing, TextAlign textAlign, - List positions, - List singleDigitValues) { + List positions, + List singleDigitValues) { final String value1 = value[0]; final String value2 = value.substring(1, 7); final String value3 = value.substring(7, 13); - final double secondTextWidth = positions[1] - positions[0]; - final double thirdTextWidth = positions[3] - positions[2]; + final double secondTextWidth = positions[1]! - positions[0]!; + final double thirdTextWidth = positions[3]! - positions[2]!; // Renders the first digit of the input drawText( canvas, - Offset(singleDigitValues[0], offset.dy + size.height + textSpacing), - Size(singleDigitValues[1], size.height), + Offset(singleDigitValues[0]!, offset.dy + size.height + textSpacing), + Size(singleDigitValues[1]!, size.height), value1, textStyle, textSpacing, @@ -281,7 +281,7 @@ class EAN13Renderer extends SymbologyRenderer { // Renders the first six digits of encoded text drawText( canvas, - Offset(positions[0], offset.dy), + Offset(positions[0]!, offset.dy), Size(secondTextWidth, size.height), value2, textStyle, @@ -293,7 +293,7 @@ class EAN13Renderer extends SymbologyRenderer { // Renders the second six digits of encoded text drawText( canvas, - Offset(positions[2], offset.dy), + Offset(positions[2]!, offset.dy), Size(thirdTextWidth, size.height), value3, textStyle, diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/ean8_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/ean8_renderer.dart index 5277264dc..690b89b96 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/ean8_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/ean8_renderer.dart @@ -7,10 +7,10 @@ import 'symbology_base_renderer.dart'; /// Represents the EAN8 renderer class class EAN8Renderer extends SymbologyRenderer { /// Creates the ean8 renderer - EAN8Renderer({Symbology symbology}) : super(symbology: symbology); + EAN8Renderer({Symbology? symbology}) : super(symbology: symbology); /// Represents the encoded input value - String _encodedValue; + late String _encodedValue; @override bool getIsValidateInput(String value) { @@ -47,20 +47,20 @@ class EAN8Renderer extends SymbologyRenderer { /// _positions[1] specifies start position of middle bar /// _positions[2] specifies end position of middle bar /// _positions[3] specifies start position of end bar - final List positions = List(4); + final List positions = List.filled(4, null); final Paint paint = getBarPaint(foregroundColor); final List code = _getCodeValues(); final int barTotalLength = _getTotalLength(code); - double left = symbology.module == null + double left = symbology?.module == null ? offset.dx : getLeftPosition( - barTotalLength, symbology.module, size.width, offset.dx); + barTotalLength, symbology?.module, size.width, offset.dx); final Rect barCodeRect = Rect.fromLTRB( offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); double ratio = 0; - if (symbology.module != null) { - ratio = symbology.module.toDouble(); + if (symbology?.module != null) { + ratio = symbology!.module!.toDouble(); } else { // Calculates the bar length based on number of individual bar codes final int singleModule = (size.width ~/ barTotalLength).toInt(); @@ -76,7 +76,7 @@ class EAN8Renderer extends SymbologyRenderer { final double barHeight = hasExtraHeight ? size.height + (showValue - ? (textSize.height * additionalHeight) + textSpacing + ? (textSize!.height * additionalHeight) + textSpacing : 0) : size.height; final int codeLength = codeValue.length; @@ -211,16 +211,16 @@ class EAN8Renderer extends SymbologyRenderer { TextStyle textStyle, double textSpacing, TextAlign textAlign, - List positions) { + List positions) { final String value1 = value.substring(0, 4); final String value2 = value.substring(4, 8); - final double firstTextWidth = positions[1] - positions[0]; - final double secondTextWidth = positions[3] - positions[2]; + final double firstTextWidth = positions[1]! - positions[0]!; + final double secondTextWidth = positions[3]! - positions[2]!; // Renders the first four digits of input drawText( canvas, - Offset(positions[0], offset.dy), + Offset(positions[0]!, offset.dy), Size(firstTextWidth, size.height), value1, textStyle, @@ -232,7 +232,7 @@ class EAN8Renderer extends SymbologyRenderer { // Renders the last four digits of input drawText( canvas, - Offset(positions[2], offset.dy), + Offset(positions[2]!, offset.dy), Size(secondTextWidth, size.height), value2, textStyle, diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/symbology_base_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/symbology_base_renderer.dart index 6287e3676..0995067d1 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/symbology_base_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/symbology_base_renderer.dart @@ -15,13 +15,13 @@ abstract class SymbologyRenderer { SymbologyRenderer({this.symbology}); /// Specifies symbology corresponding to this renderer - final Symbology symbology; + final Symbology? symbology; /// Specifies the value with start and the stop symbol - String valueWithStartAndStopSymbol; + late String valueWithStartAndStopSymbol; /// Specifies the text size - Size textSize; + Size? textSize; /// Method to valid whether the provided input character is supported /// by corresponding symbology @@ -49,8 +49,8 @@ abstract class SymbologyRenderer { /// Calculates the left value of the initial bar code double getLeftPosition( - int barWidth, int module, double width, double offsetX) { - final int calculatedWidth = barWidth * module; + int barWidth, int? module, double width, double offsetX) { + final int calculatedWidth = barWidth * module!; // Calculates the left position of the barcode based on the provided // module value double diffInWidth = (width - calculatedWidth) / 2; @@ -61,7 +61,7 @@ abstract class SymbologyRenderer { /// Method to render the input value of the barcode void drawText(Canvas canvas, Offset offset, Size size, String value, TextStyle textStyle, double textSpacing, TextAlign textAlign, - [Offset actualOffset, Size actualSize]) { + [Offset? actualOffset, Size? actualSize]) { final TextSpan span = TextSpan(text: value, style: textStyle); final TextPainter textPainter = TextPainter( text: span, textDirection: TextDirection.ltr, textAlign: textAlign); @@ -106,8 +106,8 @@ abstract class SymbologyRenderer { this is EAN13Renderer) { // Checks whether the calculated x value is present inside the control // size - if (x >= actualOffset.dx && - x + textPainter.width <= actualOffset.dx + actualSize.width) { + if (x >= actualOffset!.dx && + x + textPainter.width <= actualOffset.dx + actualSize!.width) { textPainter.paint(canvas, Offset(x, y)); } } else { diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/upca_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/upca_renderer.dart index e71911811..08b539682 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/upca_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/upca_renderer.dart @@ -8,10 +8,10 @@ import 'symbology_base_renderer.dart'; /// Represents the UPCA renderer class class UPCARenderer extends SymbologyRenderer { /// Creates the upca renderer - UPCARenderer({Symbology symbology}) : super(symbology: symbology); + UPCARenderer({Symbology? symbology}) : super(symbology: symbology); /// Represents the encoded input value - String _encodedValue; + late String _encodedValue; @override bool getIsValidateInput(String value) { @@ -56,33 +56,33 @@ class UPCARenderer extends SymbologyRenderer { /// _singleDigitValues[1] specifies width of start digit /// _singleDigitValues[2] specifies left value of end digit /// _singleDigitValues[3] specifies width of end digit - final List singleDigitValues = List(4); + final List singleDigitValues = List.filled(4, null); /// _positions[0] specifies end position of start bar /// _positions[1] specifies start position of middle bar /// _positions[2] specifies end position of middle bar /// _positions[3] specifies start position of end bar - final List positions = List(4); + final List positions = List.filled(4, null); if (showValue) { singleDigitValues[1] = measureText(_encodedValue[0], textStyle).width; - singleDigitValues[1] += additionalWidth; + singleDigitValues[1] = singleDigitValues[1]! + additionalWidth; singleDigitValues[3] = measureText(_encodedValue[_encodedValue.length - 1], textStyle).width; - singleDigitValues[3] += additionalWidth; + singleDigitValues[3] = singleDigitValues[3]! + additionalWidth; } else { singleDigitValues[1] = singleDigitValues[3] = 0; } final double width = - size.width - (singleDigitValues[1] + singleDigitValues[3]); - double left = symbology.module == null - ? offset.dx + singleDigitValues[1] - : getLeftPosition(barTotalLength, symbology.module, width, - offset.dx + singleDigitValues[1]); + size.width - (singleDigitValues[1]! + singleDigitValues[3]!); + double left = symbology?.module == null + ? offset.dx + singleDigitValues[1]! + : getLeftPosition(barTotalLength, symbology?.module, width, + offset.dx + singleDigitValues[1]!); final Rect barCodeRect = Rect.fromLTRB( offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); double ratio = 0; - if (symbology.module != null) { - ratio = symbology.module.toDouble(); + if (symbology?.module != null) { + ratio = symbology!.module!.toDouble(); } else { //Calculates the bar length based on number of individual bar codes final int singleModule = (width ~/ barTotalLength).toInt(); @@ -100,7 +100,7 @@ class UPCARenderer extends SymbologyRenderer { final double barHeight = hasExtraHeight ? size.height + (showValue - ? (textSize.height * additionalHeight) + textSpacing + ? (textSize!.height * additionalHeight) + textSpacing : 0) : size.height; final int codeLength = codeValue.length; @@ -116,7 +116,7 @@ class UPCARenderer extends SymbologyRenderer { if (i == 0 && j == codeLength - 1) { // Calculates the left value for the first input digit - singleDigitValues[0] = left - singleDigitValues[1]; + singleDigitValues[0] = left - singleDigitValues[1]!; } else if (i == 1 && j == codeLength - 1) { // Finds the end position of first extra height bar positions[0] = left; @@ -201,7 +201,7 @@ class UPCARenderer extends SymbologyRenderer { /// Returns the encoded value of digits present at left side String _getLeftValue(bool isLeft, String structure, String leftString) { - String code; + late String code; List tempValue; final Map> codes = _getBinaries(); for (int i = 0; i < leftString.length; i++) { @@ -248,20 +248,20 @@ class UPCARenderer extends SymbologyRenderer { TextStyle textStyle, double textSpacing, TextAlign textAlign, - List positions, - List singleDigitValues) { + List positions, + List singleDigitValues) { final String value1 = value[0]; final String value2 = value.substring(1, 6); final String value3 = value.substring(6, 11); - final double secondTextWidth = positions[1] - positions[0]; - final double thirdTextWidth = positions[3] - positions[2]; + final double secondTextWidth = positions[1]! - positions[0]!; + final double thirdTextWidth = positions[3]! - positions[2]!; // Renders the first digit of encoded value drawText( canvas, - Offset(singleDigitValues[0], offset.dy + size.height + textSpacing), - Size(singleDigitValues[1], size.height), + Offset(singleDigitValues[0]!, offset.dy + size.height + textSpacing), + Size(singleDigitValues[1]!, size.height), value1, textStyle, textSpacing, @@ -272,7 +272,7 @@ class UPCARenderer extends SymbologyRenderer { // Renders the first five digits of encoded input value drawText( canvas, - Offset(positions[0], offset.dy), + Offset(positions[0]!, offset.dy), Size(secondTextWidth, size.height), value2, textStyle, @@ -284,7 +284,7 @@ class UPCARenderer extends SymbologyRenderer { // Renders the second five digits of encoded input value drawText( canvas, - Offset(positions[2], offset.dy), + Offset(positions[2]!, offset.dy), Size(thirdTextWidth, size.height), value3, textStyle, @@ -296,8 +296,8 @@ class UPCARenderer extends SymbologyRenderer { // Renders the last digit of the encoded input value drawText( canvas, - Offset(singleDigitValues[2], offset.dy + size.height + textSpacing), - Size(singleDigitValues[3], size.height), + Offset(singleDigitValues[2]!, offset.dy + size.height + textSpacing), + Size(singleDigitValues[3]!, size.height), value[value.length - 1], textStyle, textSpacing, diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/upce_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/upce_renderer.dart index a4b211858..d97a34479 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/upce_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/one_dimensional/upce_renderer.dart @@ -8,10 +8,10 @@ import 'symbology_base_renderer.dart'; /// Represents the UPCE renderer class class UPCERenderer extends SymbologyRenderer { /// Creates the upce renderer - UPCERenderer({Symbology symbology}) : super(symbology: symbology); + UPCERenderer({Symbology? symbology}) : super(symbology: symbology); /// Represents the encoded input value - String _encodedValue; + late String _encodedValue; @override bool getIsValidateInput(String value) { @@ -44,33 +44,33 @@ class UPCERenderer extends SymbologyRenderer { /// _singleDigitValues[1] specifies width of start digit /// _singleDigitValues[2] specifies left value of end digit /// _singleDigitValues[3] specifies width of end digit - final List singleDigitValues = List(4); + final List singleDigitValues = List.filled(4, null); /// _positions[0] specifies end position of start bar /// _positions[1] specifies start position of end bar - final List positions = List(2); + final List positions = List.filled(2, null); const int additionalWidth = 2; if (showValue) { singleDigitValues[1] = measureText('0', textStyle).width; - singleDigitValues[1] += additionalWidth; + singleDigitValues[1] = singleDigitValues[1]! + additionalWidth; singleDigitValues[3] = measureText(_encodedValue[_encodedValue.length - 1], textStyle).width; - singleDigitValues[3] += additionalWidth; + singleDigitValues[3] = singleDigitValues[3]! + additionalWidth; } else { singleDigitValues[1] = singleDigitValues[3] = 0; } final double width = - size.width - (singleDigitValues[1] + singleDigitValues[3]); - double left = symbology.module == null + size.width - (singleDigitValues[1]! + singleDigitValues[3]!); + double left = symbology?.module == null ? offset.dx - : getLeftPosition(barTotalLength, symbology.module, width, - offset.dx + singleDigitValues[1]); + : getLeftPosition(barTotalLength, symbology?.module, width, + offset.dx + singleDigitValues[1]!); final Rect barCodeRect = Rect.fromLTRB( offset.dx, offset.dy, offset.dx + size.width, offset.dy + size.height); double ratio = 0; - if (symbology.module != null) { - ratio = symbology.module.toDouble(); + if (symbology?.module != null) { + ratio = symbology!.module!.toDouble(); } else { //Calculates the bar length based on number of individual bar codes final int singleModule = (width ~/ barTotalLength).toInt(); @@ -84,7 +84,7 @@ class UPCERenderer extends SymbologyRenderer { final bool hasExtraHeight = getHasExtraHeight(i, code); final double barHeight = hasExtraHeight ? size.height + - (showValue ? (textSize.height * 0.5) + textSpacing : 0) + (showValue ? (textSize!.height * 0.5) + textSpacing : 0) : size.height; final int codeLength = codeValue.length; for (int j = 0; j < codeLength; j++) { @@ -100,7 +100,7 @@ class UPCERenderer extends SymbologyRenderer { // Calculates the left value for first digit if (i == 0 && j == codeLength - 1) { - singleDigitValues[0] = left - singleDigitValues[1]; + singleDigitValues[0] = left - singleDigitValues[1]!; } else if (i == 1 && j == codeLength - 1) { // Calculates the start position of intermediate bars positions[0] = left; @@ -128,18 +128,18 @@ class UPCERenderer extends SymbologyRenderer { TextStyle textStyle, double textSpacing, TextAlign textAlign, - List positions, - List singleDigitValues) { + List positions, + List singleDigitValues) { const String value1 = '0'; final String value2 = value.substring(1, 7); - final double secondTextWidth = positions[1] - positions[0]; + final double secondTextWidth = positions[1]! - positions[0]!; // Renders the first digit of encoded value drawText( canvas, - Offset(singleDigitValues[0], offset.dy + size.height + textSpacing), - Size(singleDigitValues[1], size.height), + Offset(singleDigitValues[0]!, offset.dy + size.height + textSpacing), + Size(singleDigitValues[1]!, size.height), value1, textStyle, textSpacing, @@ -149,7 +149,7 @@ class UPCERenderer extends SymbologyRenderer { //Renders the middle six digits of encoded value drawText( canvas, - Offset(positions[0], offset.dy), + Offset(positions[0]!, offset.dy), Size(secondTextWidth, size.height), value2, textStyle, @@ -160,8 +160,8 @@ class UPCERenderer extends SymbologyRenderer { // Renders the last digit of encoded value drawText( canvas, - Offset(singleDigitValues[2], offset.dy + size.height + textSpacing), - Size(singleDigitValues[3], size.height), + Offset(singleDigitValues[2]!, offset.dy + size.height + textSpacing), + Size(singleDigitValues[3]!, size.height), value[value.length - 1], textStyle, textSpacing, @@ -237,7 +237,7 @@ class UPCERenderer extends SymbologyRenderer { final String lastDigit = value[value.length - 1]; final String expansionValue = _getExpansion(lastDigit); String result = ''; - num index = 0; + int index = 0; for (int i = 0; i < expansionValue.length; i++) { final String actualValue = expansionValue[i]; if (actualValue == 'X') { diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/datamatrix_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/datamatrix_renderer.dart index 0f6d2ca18..ef8972be2 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/datamatrix_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/datamatrix_renderer.dart @@ -11,42 +11,43 @@ import '../one_dimensional/symbology_base_renderer.dart'; /// Represents the data matrix renderer class class DataMatrixRenderer extends SymbologyRenderer { /// Creates the data matrix renderer - DataMatrixRenderer({Symbology symbology}) : super(symbology: symbology) { - _dataMatrixSymbology = symbology; + DataMatrixRenderer({Symbology? symbology}) : super(symbology: symbology) { + // ignore: avoid_as + _dataMatrixSymbology = symbology as DataMatrix; _initialize(); } - DataMatrix _dataMatrixSymbology; + late DataMatrix _dataMatrixSymbology; /// Defines the list of symbol attributes - List<_DataMatrixSymbolAttribute> _symbolAttributes; + late List<_DataMatrixSymbolAttribute> _symbolAttributes; /// Defines the log list - List _log; + late List _log; /// Defines the aLog list - List _aLog; + late List _aLog; /// Defines the block length - int _blockLength; + late int _blockLength; /// Defines the polynomial collection - List _polynomial; + late List _polynomial; /// Defines the symbol attributes - _DataMatrixSymbolAttribute _symbolAttribute; + late _DataMatrixSymbolAttribute _symbolAttribute; /// Defines the actual encoding value - DataMatrixEncoding _encoding; + late DataMatrixEncoding _encoding; /// Defines the data matrix input value - String _inputValue; + late String _inputValue; /// Defines the list of data matrix - List> _dataMatrixList; + late List> _dataMatrixList; /// Defines the list of encoded code words - List _encodedCodeword; + late List? _encodedCodeword; /// Initializes the symbol attributes /// @@ -334,19 +335,19 @@ class DataMatrixRenderer extends SymbologyRenderer { /// Method to create the log list void _createLogList() { - _log = List(256); - _aLog = List(256); + _log = List.filled(256, null); + _aLog = List.filled(256, null); _log[0] = -255; _aLog[0] = 1; for (int i = 1; i <= 255; i++) { - _aLog[i] = _aLog[i - 1] * 2; + _aLog[i] = _aLog[i - 1]! * 2; - if (_aLog[i] >= 256) { - _aLog[i] = _aLog[i] ^ 301; + if (_aLog[i]! >= 256) { + _aLog[i] = _aLog[i]! ^ 301; } - _log[_aLog[i]] = i; + _log[_aLog[i]!] = i; } } @@ -356,19 +357,19 @@ class DataMatrixRenderer extends SymbologyRenderer { } /// Method used to create the data matrix - void _createMatrix(List codeword) { + void _createMatrix(List codeword) { int x, y, numOfRows, numOfColumns; - List places; - final int width = _symbolAttribute.symbolColumn; - final int height = _symbolAttribute.symbolRow; - final int fieldWidth = width ~/ _symbolAttribute.horizontalDataRegion; - final int fieldHeight = height ~/ _symbolAttribute.verticalDataRegion; + List places; + final int width = _symbolAttribute.symbolColumn!; + final int height = _symbolAttribute.symbolRow!; + final int fieldWidth = width ~/ _symbolAttribute.horizontalDataRegion!; + final int fieldHeight = height ~/ _symbolAttribute.verticalDataRegion!; numOfColumns = width - 2 * (width ~/ fieldWidth); numOfRows = height - 2 * (height ~/ fieldHeight); - places = List(numOfColumns * numOfRows); + places = List.filled(numOfColumns * numOfRows, null); _errorCorrectingCode200Placement(places, numOfRows, numOfColumns); - final List matrix = List(width * height); + final List matrix = List.filled(width * height, null); for (y = 0; y < height; y += fieldHeight) { for (x = 0; x < width; x++) { matrix[y * width + x] = 1; @@ -391,8 +392,9 @@ class DataMatrixRenderer extends SymbologyRenderer { for (y = 0; y < numOfRows; y++) { for (x = 0; x < numOfColumns; x++) { - final int v = places[(numOfRows - y - 1) * numOfColumns + x]; - if (v == 1 || v > 7 && (codeword[(v >> 3) - 1] & (1 << (v & 7))) != 0) { + final int v = places[(numOfRows - y - 1) * numOfColumns + x]!; + if (v == 1 || + v > 7 && (codeword[(v >> 3) - 1]! & (1 << (v & 7))) != 0) { matrix[(1 + y + 2 * (y ~/ (fieldHeight - 2))) * width + 1 + x + @@ -405,12 +407,12 @@ class DataMatrixRenderer extends SymbologyRenderer { } /// Methods to create array based on row and column - void _createArray(List matrix) { - final int symbolColumn = _symbolAttribute.symbolColumn, - symbolRow = _symbolAttribute.symbolRow; + void _createArray(List matrix) { + final int symbolColumn = _symbolAttribute.symbolColumn!, + symbolRow = _symbolAttribute.symbolRow!; - final List> tempArray = - List>.generate(symbolColumn, (int j) => List(symbolRow)); + final List> tempArray = List>.generate( + symbolColumn, (int j) => List.filled(symbolRow, null)); for (int m = 0; m < symbolColumn; m++) { for (int n = 0; n < symbolRow; n++) { @@ -418,8 +420,8 @@ class DataMatrixRenderer extends SymbologyRenderer { } } - final List> tempArray2 = - List>.generate(symbolRow, (int j) => List(symbolColumn)); + final List> tempArray2 = List>.generate( + symbolRow, (int j) => List.filled(symbolColumn, null)); for (int i = 0; i < symbolRow; i++) { for (int j = 0; j < symbolColumn; j++) { @@ -431,11 +433,12 @@ class DataMatrixRenderer extends SymbologyRenderer { } /// Method used to add the quiet zone - void _addQuietZone(List> tempArray) { + void _addQuietZone(List> tempArray) { const int quietZone = 1; - final int w = _symbolAttribute.symbolRow + (2 * quietZone); - final int h = _symbolAttribute.symbolColumn + (2 * quietZone); - _dataMatrixList = List>.generate(w, (int j) => List(h)); + final int w = _symbolAttribute.symbolRow! + (2 * quietZone); + final int h = _symbolAttribute.symbolColumn! + (2 * quietZone); + _dataMatrixList = + List>.generate(w, (int j) => List.filled(h, null)); // Top quietzone. for (int i = 0; i < h; i++) { _dataMatrixList[0][i] = 0; @@ -458,7 +461,7 @@ class DataMatrixRenderer extends SymbologyRenderer { } /// Method to encode the error correcting code word - void _errorCorrectingCode200PlacementBit(List array, int numOfRows, + void _errorCorrectingCode200PlacementBit(List array, int numOfRows, int numOfColumns, int row, int column, int place, String character) { if (row < 0) { row += numOfRows; @@ -475,7 +478,7 @@ class DataMatrixRenderer extends SymbologyRenderer { /// Method to encode the error correcting code word void _errorCorrectingCode200PlacementCornerA( - List array, int numOfRows, int numOfColumns, int place) { + List array, int numOfRows, int numOfColumns, int place) { _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, numOfRows - 1, 0, place, String.fromCharCode(7)); _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, @@ -496,7 +499,7 @@ class DataMatrixRenderer extends SymbologyRenderer { /// Method to encode the error correcting code word void _errorCorrectingCode200PlacementCornerB( - List array, int numOfRows, int numOfColumns, int place) { + List array, int numOfRows, int numOfColumns, int place) { _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, numOfRows - 3, 0, place, String.fromCharCode(7)); _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, @@ -517,7 +520,7 @@ class DataMatrixRenderer extends SymbologyRenderer { /// Method to encode the error correcting code word void _errorCorrectingCode200PlacementCornerC( - List array, int numOfRows, int numOfColumns, int place) { + List array, int numOfRows, int numOfColumns, int place) { _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, numOfRows - 3, 0, place, String.fromCharCode(7)); _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, @@ -538,7 +541,7 @@ class DataMatrixRenderer extends SymbologyRenderer { /// Method to encode the error correcting code word void _errorCorrectingCode200PlacementCornerD( - List array, int numOfRows, int numOfColumns, int place) { + List array, int numOfRows, int numOfColumns, int place) { _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, numOfRows - 1, 0, place, String.fromCharCode(7)); _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, @@ -558,7 +561,7 @@ class DataMatrixRenderer extends SymbologyRenderer { } /// Method to encode the error correcting code word - void _errorCorrectingCode200PlacementBlock(List array, int numOfRows, + void _errorCorrectingCode200PlacementBlock(List array, int numOfRows, int numOfColumns, int row, int column, int place) { _errorCorrectingCode200PlacementBit(array, numOfRows, numOfColumns, row - 2, column - 2, place, String.fromCharCode(7)); @@ -580,7 +583,7 @@ class DataMatrixRenderer extends SymbologyRenderer { /// Method to encode the error correcting code word void _errorCorrectingCode200Placement( - List array, int numOfRows, int numOfColumns) { + List array, int numOfRows, int numOfColumns) { int row, column, place; for (row = 0; row < numOfRows; row++) { for (column = 0; column < numOfColumns; column++) { @@ -658,7 +661,7 @@ class DataMatrixRenderer extends SymbologyRenderer { return i; } - return _aLog[(_log[i] + j) % 255]; + return _aLog[(_log[i]! + j) % 255]!; } /// Method used for error correction @@ -668,7 +671,7 @@ class DataMatrixRenderer extends SymbologyRenderer { } else if (i > 255 || j > 255) { return 0; } - return _aLog[(_log[i] + _log[j]) % 255]; + return _aLog[(_log[i]! + _log[j]!) % 255]!; } /// Method to perform the XOR operation for error correction @@ -677,11 +680,11 @@ class DataMatrixRenderer extends SymbologyRenderer { } /// Method to pad the code word - List _getPaddedCodewords(int dataCWLength, List temp) { + List _getPaddedCodewords(int dataCWLength, List temp) { final List codewords = []; int length = temp.length; for (int i = 0; i < length; i++) { - codewords.add(temp[i]); + codewords.add(temp[i]!); } if (length < dataCWLength) { @@ -703,13 +706,14 @@ class DataMatrixRenderer extends SymbologyRenderer { } /// Method used for base256 encoding - List _getDataMatrixBaseEncoder(List dataCodeword) { + List _getDataMatrixBaseEncoder(List dataCodeword) { int num = 1; if (dataCodeword.length > 249) { num++; } - final List result = List((1 + num) + dataCodeword.length); + final List result = + List.filled((1 + num) + dataCodeword.length, null); result[0] = 231; if (dataCodeword.length <= 249) { result[1] = dataCodeword.length; @@ -723,7 +727,7 @@ class DataMatrixRenderer extends SymbologyRenderer { } for (int i = 1; i < result.length; i++) { - result[i] = _getBase256Codeword(result[i], i); + result[i] = _getBase256Codeword(result[i]!, i); } return result; @@ -741,25 +745,26 @@ class DataMatrixRenderer extends SymbologyRenderer { } /// Method used for ASCII numeric encoding - List _getDataMatrixASCIINumericEncoder(List dataCodeword) { - List destinationArray = dataCodeword; + List _getDataMatrixASCIINumericEncoder(List dataCodeword) { + List destinationArray = dataCodeword; bool isEven = true; // if the input data length is odd, add 0 in front of the data. if ((destinationArray.length % 2) == 1) { isEven = false; - destinationArray = List(dataCodeword.length + 1); + destinationArray = List.filled(dataCodeword.length + 1, null); for (int i = 0; i < dataCodeword.length; i++) { destinationArray[i] = dataCodeword[i]; } } - final List result = List(destinationArray.length ~/ 2); + final List result = + List.filled(destinationArray.length ~/ 2, null); for (int i = 0; i < result.length; i++) { if (!isEven && i == result.length - 1) { - result[i] = destinationArray[2 * i] + 1; + result[i] = destinationArray[2 * i]! + 1; } else { - result[i] = (((destinationArray[2 * i] - 48) * 10) + - (destinationArray[(2 * i) + 1] - 48)) + + result[i] = (((destinationArray[2 * i]! - 48) * 10) + + (destinationArray[(2 * i) + 1]! - 48)) + 130; } } @@ -807,25 +812,25 @@ class DataMatrixRenderer extends SymbologyRenderer { } /// Method used for computing error correction - List _getComputedErrorCorrection(List codeword) { + List _getComputedErrorCorrection(List codeword) { _setSymbolAttribute(codeword); - final int numCorrectionCodeword = _symbolAttribute.correctionCodeWords; + final int numCorrectionCodeword = _symbolAttribute.correctionCodeWords!; // Create error correction array. - final List correctionCodeWordArray = - List(numCorrectionCodeword + _symbolAttribute.dataCodeWords); + final List correctionCodeWordArray = List.filled( + numCorrectionCodeword + _symbolAttribute.dataCodeWords!, null); for (int i = 0; i < correctionCodeWordArray.length; i++) { correctionCodeWordArray[i] = 0; } - final int step = _symbolAttribute.interLeavedBlock; - final int symbolDataWords = _symbolAttribute.dataCodeWords; - final int blockErrorWords = _symbolAttribute.correctionCodeWords ~/ step; + final int step = _symbolAttribute.interLeavedBlock!; + final int symbolDataWords = _symbolAttribute.dataCodeWords!; + final int blockErrorWords = _symbolAttribute.correctionCodeWords! ~/ step; final int total = symbolDataWords + blockErrorWords * step; // Updates m_polynomial based on the required number of correction bytes. _createPolynomial(step); //set block length (each block consists of length 68 ) _blockLength = 68; - final List blockByte = List(_blockLength); + final List blockByte = List.filled(_blockLength, null); for (int block = 0; block < step; block++) { for (int k = 0; k < blockByte.length; k++) { blockByte[k] = 0; @@ -833,26 +838,26 @@ class DataMatrixRenderer extends SymbologyRenderer { for (int i = block; i < symbolDataWords; i += step) { final int val = _getErrorCorrectingCodeSum( - blockByte[blockErrorWords - 1], _encodedCodeword[i]); + blockByte[blockErrorWords - 1]!, _encodedCodeword![i]!); for (int j = blockErrorWords - 1; j > 0; j--) { - blockByte[j] = _getErrorCorrectingCodeSum(blockByte[j - 1], - _getErrorCorrectingCodeProduct(_polynomial[j], val)); + blockByte[j] = _getErrorCorrectingCodeSum(blockByte[j - 1]!, + _getErrorCorrectingCodeProduct(_polynomial[j]!, val)); } - blockByte[0] = _getErrorCorrectingCodeProduct(_polynomial[0], val); + blockByte[0] = _getErrorCorrectingCodeProduct(_polynomial[0]!, val); } int blockDataWords = 0; if (block >= 8 && _dataMatrixSymbology.dataMatrixSize == DataMatrixSize.size144x144) { - blockDataWords = _symbolAttribute.dataCodeWords ~/ step; + blockDataWords = _symbolAttribute.dataCodeWords! ~/ step; } else { - blockDataWords = _symbolAttribute.interLeavedDataBlock; + blockDataWords = _symbolAttribute.interLeavedDataBlock!; int bIndex = blockErrorWords; for (int i = block + (step * blockDataWords); i < total; i += step) { - correctionCodeWordArray[i] = blockByte[--bIndex]; + correctionCodeWordArray[i] = blockByte[--bIndex]!; } if (bIndex != 0) { @@ -866,33 +871,33 @@ class DataMatrixRenderer extends SymbologyRenderer { } /// Method to get the correction code word - List _getCorrectionCodeWordArray( - List correctionCodeWordArray, int numCorrectionCodeword) { + List _getCorrectionCodeWordArray( + List correctionCodeWordArray, int numCorrectionCodeword) { if (correctionCodeWordArray.length > numCorrectionCodeword) { - final List tmp = correctionCodeWordArray; - correctionCodeWordArray = List(numCorrectionCodeword); + final List tmp = correctionCodeWordArray; + correctionCodeWordArray = List.filled(numCorrectionCodeword, null); for (int i = 0; i < correctionCodeWordArray.length; i++) { correctionCodeWordArray[i] = 0; } int z = 0; - for (int i = tmp.length - 1; i > _symbolAttribute.dataCodeWords; i--) { + for (int i = tmp.length - 1; i > _symbolAttribute.dataCodeWords!; i--) { correctionCodeWordArray[z++] = tmp[i]; } } - final List reversedList = correctionCodeWordArray.reversed.toList(); + final List reversedList = correctionCodeWordArray.reversed.toList(); return reversedList; } /// Method to set the symbol attribute - void _setSymbolAttribute(List codeword) { + void _setSymbolAttribute(List codeword) { int dataLength = codeword.length; _symbolAttribute = _DataMatrixSymbolAttribute(); if (_dataMatrixSymbology.dataMatrixSize == DataMatrixSize.auto) { for (int i = 0; i < _symbolAttributes.length; i++) { final _DataMatrixSymbolAttribute attribute = _symbolAttributes[i]; - if (attribute.dataCodeWords >= dataLength) { + if (attribute.dataCodeWords! >= dataLength) { _symbolAttribute = attribute; break; } @@ -905,13 +910,13 @@ class DataMatrixRenderer extends SymbologyRenderer { List temp; // Pad data codeword if the length is less than the selected symbol // attribute. - if (_symbolAttribute.dataCodeWords > dataLength) { - temp = _getPaddedCodewords(_symbolAttribute.dataCodeWords, codeword); + if (_symbolAttribute.dataCodeWords! > dataLength) { + temp = _getPaddedCodewords(_symbolAttribute.dataCodeWords!, codeword); _encodedCodeword = List.from(temp); dataLength = codeword.length; } else if (_symbolAttribute.dataCodeWords == 0) { throw 'Data cannot be encoded as barcode'; - } else if (_symbolAttribute.dataCodeWords < dataLength) { + } else if (_symbolAttribute.dataCodeWords! < dataLength) { final String symbolRow = _symbolAttribute.symbolRow.toString(); final String symbolColumn = _symbolAttribute.symbolColumn.toString(); throw 'Data too long for $symbolRow x $symbolColumn barcode.'; @@ -922,43 +927,43 @@ class DataMatrixRenderer extends SymbologyRenderer { void _createPolynomial(int step) { //Set block length for polynomial values _blockLength = 69; - _polynomial = List(_blockLength); - final int blockErrorWords = _symbolAttribute.correctionCodeWords ~/ step; + _polynomial = List.filled(_blockLength, null); + final int blockErrorWords = _symbolAttribute.correctionCodeWords! ~/ step; for (int i = 0; i < _polynomial.length; i++) { _polynomial[i] = 0x01; } for (int i = 1; i <= blockErrorWords; i++) { for (int j = i - 1; j >= 0; j--) { - _polynomial[j] = _getErrorCorrectingCodeDoublify(_polynomial[j], i); + _polynomial[j] = _getErrorCorrectingCodeDoublify(_polynomial[j]!, i); if (j > 0) { _polynomial[j] = - _getErrorCorrectingCodeSum(_polynomial[j], _polynomial[j - 1]); + _getErrorCorrectingCodeSum(_polynomial[j]!, _polynomial[j - 1]!); } } } } /// Method to get the code word - List _getCodeword(List dataCodeword) { + List _getCodeword(List dataCodeword) { _encodedCodeword = _getDataCodeword(dataCodeword); - final List correctCodeword = - _getComputedErrorCorrection(_encodedCodeword); - final List finalCodeword = - List(_encodedCodeword.length + correctCodeword.length); - for (int i = 0; i < _encodedCodeword.length; i++) { - finalCodeword[i] = _encodedCodeword[i]; + final List correctCodeword = + _getComputedErrorCorrection(_encodedCodeword!); + final List finalCodeword = List.filled( + _encodedCodeword!.length + correctCodeword.length, null); + for (int i = 0; i < _encodedCodeword!.length; i++) { + finalCodeword[i] = _encodedCodeword![i]; } for (int i = 0; i < correctCodeword.length; i++) { - finalCodeword[i + _encodedCodeword.length] = correctCodeword[i]; + finalCodeword[i + _encodedCodeword!.length] = correctCodeword[i]; } return finalCodeword; } /// Method used to prepare the code word - List _getDataCodeword(List dataCodeword) { + List? _getDataCodeword(List dataCodeword) { _encoding = _dataMatrixSymbology.encoding; if (_dataMatrixSymbology.encoding == DataMatrixEncoding.auto || _dataMatrixSymbology.encoding == DataMatrixEncoding.asciiNumeric) { @@ -1000,8 +1005,8 @@ class DataMatrixRenderer extends SymbologyRenderer { } /// Method to get the encoding type - List _getEncoding(List dataCodeword) { - List result; + List? _getEncoding(List dataCodeword) { + List? result; switch (_encoding) { case DataMatrixEncoding.ascii: result = _getDataMatrixASCIIEncoder(dataCodeword); @@ -1042,8 +1047,8 @@ class DataMatrixRenderer extends SymbologyRenderer { double x = 0; double y = 0; final Paint paint = getBarPaint(foregroundColor); - final int w = _symbolAttribute.symbolRow + (2 * quietZone); - final int h = _symbolAttribute.symbolColumn + (2 * quietZone); + final int w = _symbolAttribute.symbolRow! + (2 * quietZone); + final int h = _symbolAttribute.symbolColumn! + (2 * quietZone); final double minSize = size.width >= size.height ? size.height : size.width; final bool isSquareMatrix = getDataMatrixSize(_dataMatrixSymbology.dataMatrixSize) < 25; @@ -1090,7 +1095,7 @@ class DataMatrixRenderer extends SymbologyRenderer { @override void drawText(Canvas canvas, Offset offset, Size size, String value, TextStyle textStyle, double textSpacing, TextAlign textAlign, - [Offset actualOffset, Size actualSize]) { + [Offset? actualOffset, Size? actualSize]) { final TextSpan span = TextSpan(text: value, style: textStyle); final TextPainter textPainter = TextPainter( @@ -1129,7 +1134,7 @@ class DataMatrixRenderer extends SymbologyRenderer { } void _buildDataMatrix() { - final List codeword = _getCodeword(_getData()); + final List codeword = _getCodeword(_getData()); _createMatrix(codeword); } } @@ -1148,26 +1153,26 @@ class _DataMatrixSymbolAttribute { this.interLeavedDataBlock}); /// Defines the symbol row - final int symbolRow; + final int? symbolRow; /// Defines the symbol column - final int symbolColumn; + final int? symbolColumn; /// Defines the horizontal data region - final int horizontalDataRegion; + final int? horizontalDataRegion; /// Defines the vertical data region - final int verticalDataRegion; + final int? verticalDataRegion; /// Defines the data code words - final int dataCodeWords; + final int? dataCodeWords; /// Defines the error correction code words - final int correctionCodeWords; + final int? correctionCodeWords; /// Defines the inter leaved blocks - final int interLeavedBlock; + final int? interLeavedBlock; /// Defines the inter leaved data blocks - final int interLeavedDataBlock; + final int? interLeavedDataBlock; } diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/error_correction_codewords.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/error_correction_codewords.dart index d4c467b26..a2d8ee406 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/error_correction_codewords.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/error_correction_codewords.dart @@ -6,15 +6,15 @@ class ErrorCorrectionCodeWords { /// Creates the error correction code word ErrorCorrectionCodeWords({this.codeVersion, this.correctionLevel}) { _codeValue = QRCodeValue( - qrCodeVersion: codeVersion, errorCorrectionLevel: correctionLevel); + qrCodeVersion: codeVersion!, errorCorrectionLevel: correctionLevel!); eccw = _codeValue.noOfErrorCorrectionCodeWord; } /// Specifies the code version - final QRCodeVersion codeVersion; + final QRCodeVersion? codeVersion; /// Specifies the correction level - final ErrorCorrectionLevel correctionLevel; + final ErrorCorrectionLevel? correctionLevel; /// Specifies the alpha value static const List _alpha = [ @@ -276,22 +276,22 @@ class ErrorCorrectionCodeWords { ]; /// Specifies the error corrcetion code word - int eccw; + late int eccw; /// Specifies the data bits - int dataBits; + late int dataBits; /// Specifies the data code word - List dataCodeWords; + late List dataCodeWords; /// Specifies the list of integer value based on alpha value - List _gx; + late List _gx; /// Specifies the list of decimal value - List _decimalValue; + late List _decimalValue; /// Specifies the code value - QRCodeValue _codeValue; + late QRCodeValue _codeValue; /// Returns the error correction word /// @@ -1606,10 +1606,10 @@ class ErrorCorrectionCodeWords { } /// Converts binary to decimal value - void _binaryToDecimal(List inString) { + void _binaryToDecimal(List inString) { _decimalValue = []; for (int i = 0; i < inString.length; i++) { - _decimalValue.add(int.parse(inString[i], radix: 2)); + _decimalValue.add(int.parse(inString[i]!, radix: 2)); } } @@ -1672,7 +1672,7 @@ class ErrorCorrectionCodeWords { } else { final Map alphaNotation = _getAlphaNotation(leadTermSource); Map resPoly = _getGeneratorPolynomByLeadTerm(generatorPolynom, - alphaNotation[_getLargestExponent(alphaNotation)], i); + alphaNotation[_getLargestExponent(alphaNotation)]!, i); resPoly = _getDecimalNotation(resPoly); resPoly = _getXORPolynoms(leadTermSource, resPoly); leadTermSource = resPoly; @@ -1711,7 +1711,7 @@ class ErrorCorrectionCodeWords { messagePolyExponent - i, () => longPoly.entries.elementAt(i).value ^ - (shortPoly.length > i ? shortPoly[shortPolyExponent - i] : 0)); + (shortPoly.length > i ? shortPoly[shortPolyExponent - i]! : 0)); } final int resultPolyExponent = _getLargestExponent(resultPolynom); diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_renderer.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_renderer.dart index 3b5a23779..98df28c27 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_renderer.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_renderer.dart @@ -12,16 +12,18 @@ import 'qr_code_values.dart'; /// Represents the QRCode renderer class class QRCodeRenderer extends SymbologyRenderer { /// Creates the qr code renderer - QRCodeRenderer({Symbology symbology}) : super(symbology: symbology) { - _qrCodeSymbology = symbology; + QRCodeRenderer({Symbology? symbology}) : super(symbology: symbology) { + // ignore: avoid_as + _qrCodeSymbology = symbology as QRCode; if (_qrCodeSymbology.codeVersion != null && _qrCodeSymbology.codeVersion != QRCodeVersion.auto) { - _codeVersion = _qrCodeSymbology.codeVersion; + _codeVersion = _qrCodeSymbology.codeVersion!; _isUserMentionedVersion = true; } else { _isUserMentionedVersion = false; } + // ignore: unnecessary_null_comparison if (_qrCodeSymbology.errorCorrectionLevel != null) { _errorCorrectionLevel = _qrCodeSymbology.errorCorrectionLevel; _isUserMentionedErrorCorrectionLevel = true; @@ -29,6 +31,7 @@ class QRCodeRenderer extends SymbologyRenderer { _isUserMentionedErrorCorrectionLevel = false; } + // ignore: unnecessary_null_comparison if (_qrCodeSymbology.inputMode != null) { _inputMode = _qrCodeSymbology.inputMode; _isUserMentionedMode = true; @@ -38,7 +41,7 @@ class QRCodeRenderer extends SymbologyRenderer { } /// Specifies the qr code symbology - QRCode _qrCodeSymbology; + late QRCode _qrCodeSymbology; /// Specifies the CP437 character set static const List _cp437CharSet = [ @@ -317,7 +320,7 @@ class QRCodeRenderer extends SymbologyRenderer { ]; /// Specifies the QR code value - QRCodeValue _qrCodeValues; + late QRCodeValue _qrCodeValues; /// Specifies the QR code version QRCodeVersion _codeVersion = QRCodeVersion.version01; @@ -326,10 +329,10 @@ class QRCodeRenderer extends SymbologyRenderer { int _noOfModules = 21; /// Specifies the list of module value - List> _moduleValues; + late List> _moduleValues; /// Specifies the data alllocation value - List> _dataAllocationValues; + late List>? _dataAllocationValues; /// Specifies the actual input mode QRInputMode _inputMode = QRInputMode.numeric; @@ -341,7 +344,7 @@ class QRCodeRenderer extends SymbologyRenderer { int _dataBits = 0; /// Specifies the list of blocks - List _blocks; + late List? _blocks; /// Specifies the user mentioned level bool _isUserMentionedMode = false; @@ -359,10 +362,10 @@ class QRCodeRenderer extends SymbologyRenderer { int _eciAssignmentNumber = 3; /// Specifies the text value - String _encodedText; + late String _encodedText; /// Specifies the list of encode data - List _encodeDataCodeWords; + late List _encodeDataCodeWords; @override bool getIsValidateInput(String value) { @@ -543,7 +546,7 @@ class QRCodeRenderer extends SymbologyRenderer { final int version = getVersionNumber(_codeVersion); if (version > 6) { - final List versionInformation = _qrCodeValues.versionInformation; + final List? versionInformation = _qrCodeValues.versionInformation; int count = 0; for (int i = 0; i < 6; i++) { for (int j = 2; j >= 0; j--) { @@ -568,8 +571,8 @@ class QRCodeRenderer extends SymbologyRenderer { /// This is a very large method. This method could not be /// refactored to a smaller methods, since it has single switch condition and /// returns the alignment pattern based on the QR Code version - List _getAlignmentPatternCoordinates() { - List align; + List? _getAlignmentPatternCoordinates() { + List? align; final int versionNumber = getVersionNumber(_codeVersion); switch (versionNumber) { case 02: @@ -694,8 +697,8 @@ class QRCodeRenderer extends SymbologyRenderer { } /// Converts the string to the list of bool - List _getStringToBoolArray(String numberInString, int noOfBits) { - final List numberInBool = List(noOfBits); + List _getStringToBoolArray(String numberInString, int noOfBits) { + final List numberInBool = List.filled(noOfBits, null); int j = 0; for (int i = 0; i < numberInString.length; i++) { j = j * 10 + numberInString[i].codeUnitAt(0) - 48; @@ -708,8 +711,8 @@ class QRCodeRenderer extends SymbologyRenderer { } /// Converts the integer to bool array value - List _getIntToBoolArray(int number, int noOfBits) { - final List numberInBool = List(noOfBits); + List _getIntToBoolArray(int number, int noOfBits) { + final List numberInBool = List.filled(noOfBits, null); for (int i = 0; i < noOfBits; i++) { numberInBool[noOfBits - i - 1] = ((number >> i) & 1) == 1; } @@ -718,10 +721,11 @@ class QRCodeRenderer extends SymbologyRenderer { } ///Methods to creates the block value based on the encoded data - List> _getBlocks(List encodeData, int noOfBlocks) { - final List> encodedBlocks = List>.generate( + List> _getBlocks(List encodeData, int noOfBlocks) { + final List> encodedBlocks = List>.generate( noOfBlocks, - (int i) => List(encodeData.length ~/ 8 ~/ noOfBlocks)); + (int i) => + List.filled(encodeData.length ~/ 8 ~/ noOfBlocks, null)); String stringValue = ''; int j = 0; @@ -747,9 +751,9 @@ class QRCodeRenderer extends SymbologyRenderer { } /// Method to the split code word - List _splitCodeWord( - List> encodeData, int block, int count) { - final List encodeDataString = List(count); + List _splitCodeWord( + List> encodeData, int block, int count) { + final List encodeDataString = List.filled(count, null); for (int i = 0; i < count; i++) { encodeDataString[i] = encodeData[block][i]; } @@ -795,9 +799,7 @@ class QRCodeRenderer extends SymbologyRenderer { actualMode == QRInputMode.binary) && _qrCodeSymbology.inputMode == QRInputMode.numeric) || (actualMode == QRInputMode.binary && - _qrCodeSymbology.inputMode == QRInputMode.alphaNumeric)) { - // new BarcodeException("Mode Conflict"); - } + _qrCodeSymbology.inputMode == QRInputMode.alphaNumeric)) {} } } @@ -1093,15 +1095,15 @@ class QRCodeRenderer extends SymbologyRenderer { /// Method to draw the format information void _drawFormatInformation() { - final List formatInformation = _qrCodeValues.formatInformation; + final List? formatInformation = _qrCodeValues.formatInformation; int count = 0; for (int i = 0; i < 7; i++) { if (i == 6) { _moduleValues[i + 1][8].isBlack = - formatInformation[count] == 1 ? true : false; + formatInformation![count] == 1 ? true : false; } else { _moduleValues[i][8].isBlack = - formatInformation[count] == 1 ? true : false; + formatInformation![count] == 1 ? true : false; } _moduleValues[8][_noOfModules - i - 1].isBlack = @@ -1114,10 +1116,10 @@ class QRCodeRenderer extends SymbologyRenderer { //Draw format information from 0 to 6 if (i == 6) { _moduleValues[8][i + 1].isBlack = - formatInformation[count] == 1 ? true : false; + formatInformation![count] == 1 ? true : false; } else { _moduleValues[8][i].isBlack = - formatInformation[count] == 1 ? true : false; + formatInformation![count] == 1 ? true : false; } _moduleValues[_noOfModules - i - 1][8].isBlack = @@ -1125,7 +1127,7 @@ class QRCodeRenderer extends SymbologyRenderer { } //Draw format information 7 - _moduleValues[8][8].isBlack = formatInformation[7] == 1 ? true : false; + _moduleValues[8][8].isBlack = formatInformation![7] == 1 ? true : false; _moduleValues[8][_noOfModules - 8].isBlack = formatInformation[7] == 1 ? true : false; } @@ -1149,45 +1151,45 @@ class QRCodeRenderer extends SymbologyRenderer { _moduleValues[i - 1][j].isFilled)) { if (!_moduleValues[i][j].isFilled) { if (point + 1 < data.length) { - _dataAllocationValues[i][j].isBlack = data[point++]; + _dataAllocationValues![i][j].isBlack = data[point++]; } if ((i + j) % 3 == 0) { - if (_dataAllocationValues[i][j].isBlack) { - _dataAllocationValues[i][j].isBlack = true; + if (_dataAllocationValues![i][j].isBlack) { + _dataAllocationValues![i][j].isBlack = true; } else { - _dataAllocationValues[i][j].isBlack = false; + _dataAllocationValues![i][j].isBlack = false; } } else { - if (_dataAllocationValues[i][j].isBlack) { - _dataAllocationValues[i][j].isBlack = false; + if (_dataAllocationValues![i][j].isBlack) { + _dataAllocationValues![i][j].isBlack = false; } else { - _dataAllocationValues[i][j].isBlack = true; + _dataAllocationValues![i][j].isBlack = true; } } - _dataAllocationValues[i][j].isFilled = true; + _dataAllocationValues![i][j].isFilled = true; } if (!_moduleValues[i - 1][j].isFilled) { if (point + 1 < data.length) { - _dataAllocationValues[i - 1][j].isBlack = data[point++]; + _dataAllocationValues![i - 1][j].isBlack = data[point++]; } if ((i - 1 + j) % 3 == 0) { - if (_dataAllocationValues[i - 1][j].isBlack) { - _dataAllocationValues[i - 1][j].isBlack = true; + if (_dataAllocationValues![i - 1][j].isBlack) { + _dataAllocationValues![i - 1][j].isBlack = true; } else { - _dataAllocationValues[i - 1][j].isBlack = false; + _dataAllocationValues![i - 1][j].isBlack = false; } } else { - if (_dataAllocationValues[i - 1][j].isBlack) { - _dataAllocationValues[i - 1][j].isBlack = false; + if (_dataAllocationValues![i - 1][j].isBlack) { + _dataAllocationValues![i - 1][j].isBlack = false; } else { - _dataAllocationValues[i - 1][j].isBlack = true; + _dataAllocationValues![i - 1][j].isBlack = true; } } - _dataAllocationValues[i - 1][j].isFilled = true; + _dataAllocationValues![i - 1][j].isFilled = true; } } } @@ -1202,44 +1204,44 @@ class QRCodeRenderer extends SymbologyRenderer { _moduleValues[i - 1][j].isFilled)) { if (!_moduleValues[i][j].isFilled) { if (point + 1 < data.length) { - _dataAllocationValues[i][j].isBlack = data[point++]; + _dataAllocationValues![i][j].isBlack = data[point++]; } if ((i + j) % 3 == 0) { - if (_dataAllocationValues[i][j].isBlack) { - _dataAllocationValues[i][j].isBlack = true; + if (_dataAllocationValues![i][j].isBlack) { + _dataAllocationValues![i][j].isBlack = true; } else { - _dataAllocationValues[i][j].isBlack = false; + _dataAllocationValues![i][j].isBlack = false; } } else { - if (_dataAllocationValues[i][j].isBlack) { - _dataAllocationValues[i][j].isBlack = false; + if (_dataAllocationValues![i][j].isBlack) { + _dataAllocationValues![i][j].isBlack = false; } else { - _dataAllocationValues[i][j].isBlack = true; + _dataAllocationValues![i][j].isBlack = true; } } - _dataAllocationValues[i][j].isFilled = true; + _dataAllocationValues![i][j].isFilled = true; } if (!_moduleValues[i - 1][j].isFilled) { if (point + 1 < data.length) { - _dataAllocationValues[i - 1][j].isBlack = data[point++]; + _dataAllocationValues![i - 1][j].isBlack = data[point++]; } if ((i - 1 + j) % 3 == 0) { - if (_dataAllocationValues[i - 1][j].isBlack) { - _dataAllocationValues[i - 1][j].isBlack = true; + if (_dataAllocationValues![i - 1][j].isBlack) { + _dataAllocationValues![i - 1][j].isBlack = true; } else { - _dataAllocationValues[i - 1][j].isBlack = false; + _dataAllocationValues![i - 1][j].isBlack = false; } } else { - if (_dataAllocationValues[i - 1][j].isBlack) { - _dataAllocationValues[i - 1][j].isBlack = false; + if (_dataAllocationValues![i - 1][j].isBlack) { + _dataAllocationValues![i - 1][j].isBlack = false; } else { - _dataAllocationValues[i - 1][j].isBlack = true; + _dataAllocationValues![i - 1][j].isBlack = true; } } - _dataAllocationValues[i - 1][j].isFilled = true; + _dataAllocationValues![i - 1][j].isFilled = true; } } } @@ -1247,11 +1249,11 @@ class QRCodeRenderer extends SymbologyRenderer { for (int i = 0; i < _noOfModules; i++) { for (int j = 0; j < _noOfModules; j++) { if (!_moduleValues[i][j].isFilled) { - final bool flag = _dataAllocationValues[i][j].isBlack; + final bool flag = _dataAllocationValues![i][j].isBlack; if (flag) { - _dataAllocationValues[i][j].isBlack = false; + _dataAllocationValues![i][j].isBlack = false; } else { - _dataAllocationValues[i][j].isBlack = true; + _dataAllocationValues![i][j].isBlack = true; } } } @@ -1439,9 +1441,9 @@ class QRCodeRenderer extends SymbologyRenderer { _drawPDP(0, _noOfModules - 7); _drawTimingPattern(); if (_codeVersion != QRCodeVersion.version01) { - final List alignCoordinates = _getAlignmentPatternCoordinates(); + final List? alignCoordinates = _getAlignmentPatternCoordinates(); - for (int i = 0; i < alignCoordinates.length; i++) { + for (int i = 0; i < alignCoordinates!.length; i++) { for (int j = 0; j < alignCoordinates.length; j++) { if (!_moduleValues[alignCoordinates[i]][alignCoordinates[j]].isPDP) { _drawAlignmentPattern(alignCoordinates[i], alignCoordinates[j]); @@ -1482,10 +1484,10 @@ class QRCodeRenderer extends SymbologyRenderer { _encodeDataCodeWords.add(true); //Add ECI assignment number - final List numberInBool = + final List numberInBool = _getStringToBoolArray(_eciAssignmentNumber.toString(), 8); for (int i = 0; i < numberInBool.length; i++) { - _encodeDataCodeWords.add(numberInBool[i]); + _encodeDataCodeWords.add(numberInBool[i]!); } } _encodeDataCodeWords.add(false); @@ -1558,7 +1560,7 @@ class QRCodeRenderer extends SymbologyRenderer { void _encodeDataInNumericMode() { String number = ''; for (int i = 0; i < _encodedText.length; i++) { - List numberInBool; + List numberInBool; number += _encodedText[i]; if (i % 3 == 2 && i != 0 || i == _encodedText.length - 1) { @@ -1572,7 +1574,7 @@ class QRCodeRenderer extends SymbologyRenderer { number = ''; for (int i = 0; i < numberInBool.length; i++) { - _encodeDataCodeWords.add(numberInBool[i]); + _encodeDataCodeWords.add(numberInBool[i]!); } } } @@ -1580,33 +1582,34 @@ class QRCodeRenderer extends SymbologyRenderer { /// Method to encode the alpha numeric data void _encodeDataForAlphaNumeric() { - String numberInString = ''; + String? numberInString = ''; int number = 0; for (int i = 0; i < _encodedText.length; i++) { - List numberInBool; - numberInString += _encodedText[i]; + List numberInBool; + numberInString = numberInString! + _encodedText[i]; if (i % 2 == 0 && i + 1 != _encodedText.length) { - number = 45 * _qrCodeValues.getAlphaNumericValues(_encodedText[i]); + number = 45 * _qrCodeValues.getAlphaNumericValues(_encodedText[i])!; } if (i % 2 == 1 && i != 0) { - number += _qrCodeValues.getAlphaNumericValues(_encodedText[i]); + number += _qrCodeValues.getAlphaNumericValues(_encodedText[i])!; numberInBool = _getIntToBoolArray(number, 11); number = 0; for (int i = 0; i < numberInBool.length; i++) { - _encodeDataCodeWords.add(numberInBool[i]); + _encodeDataCodeWords.add(numberInBool[i]!); } numberInString = ''; } + // ignore: unnecessary_null_comparison if (i != 1 && numberInString != null) { if (i + 1 == _encodedText.length && numberInString.length == 1) { - number = _qrCodeValues.getAlphaNumericValues(_encodedText[i]); + number = _qrCodeValues.getAlphaNumericValues(_encodedText[i])!; numberInBool = _getIntToBoolArray(number, 6); number = 0; for (int i = 0; i < numberInBool.length; i++) { - _encodeDataCodeWords.add(numberInBool[i]); + _encodeDataCodeWords.add(numberInBool[i]!); } } } @@ -1637,9 +1640,9 @@ class QRCodeRenderer extends SymbologyRenderer { throw 'The provided input value contains non-convertible characters'; } - final List numberInBool = _getIntToBoolArray(number, 8); + final List numberInBool = _getIntToBoolArray(number, 8); for (int i = 0; i < numberInBool.length; i++) { - _encodeDataCodeWords.add(numberInBool[i]); + _encodeDataCodeWords.add(numberInBool[i]!); } } } @@ -1696,46 +1699,50 @@ class QRCodeRenderer extends SymbologyRenderer { _dataBits = _qrCodeValues.noOfDataCodeWord; _blocks = _qrCodeValues.noOfErrorCorrectionBlocks; - int totalBlockSize = _blocks[0]; - if (_blocks.length == 6) { - totalBlockSize = _blocks[0] + _blocks[3]; + int totalBlockSize = _blocks![0]; + if (_blocks!.length == 6) { + totalBlockSize = _blocks![0] + _blocks![3]; } - final List> ds1 = - List>.generate(totalBlockSize, (int i) => []); + final List> ds1 = + List>.generate(totalBlockSize, (int i) => []); List testEncodeData = _encodeDataCodeWords; - if (_blocks.length == 6) { - final int dataCodeWordLength = _blocks[0] * _blocks[2] * 8; + if (_blocks!.length == 6) { + final int dataCodeWordLength = _blocks![0] * _blocks![2] * 8; testEncodeData = []; for (int i = 0; i < dataCodeWordLength; i++) { testEncodeData.add(_encodeDataCodeWords[i]); } } - List> dsOne = List>.generate(_blocks[0], - (int i) => List(testEncodeData.length ~/ 8 ~/ _blocks[0])); - dsOne = _getBlocks(testEncodeData, _blocks[0]); + List> dsOne = List>.generate( + _blocks![0], + (int i) => List.filled( + testEncodeData.length ~/ 8 ~/ _blocks![0], null)); + dsOne = _getBlocks(testEncodeData, _blocks![0]); - for (int i = 0; i < _blocks[0]; i++) { + for (int i = 0; i < _blocks![0]; i++) { ds1[i] = - _splitCodeWord(dsOne, i, testEncodeData.length ~/ 8 ~/ _blocks[0]); + _splitCodeWord(dsOne, i, testEncodeData.length ~/ 8 ~/ _blocks![0]); } - if (_blocks.length == 6) { + if (_blocks!.length == 6) { testEncodeData = []; - for (int i = _blocks[0] * _blocks[2] * 8; + for (int i = _blocks![0] * _blocks![2] * 8; i < _encodeDataCodeWords.length; i++) { testEncodeData.add(_encodeDataCodeWords[i]); } - List> dsTwo = List>.generate(_blocks[0], - (int i) => List(testEncodeData.length ~/ 8 ~/ _blocks[3])); - dsTwo = _getBlocks(testEncodeData, _blocks[3]); + List> dsTwo = List>.generate( + _blocks![0], + (int i) => List.filled( + testEncodeData.length ~/ 8 ~/ _blocks![3], null)); + dsTwo = _getBlocks(testEncodeData, _blocks![3]); - for (int i = _blocks[0], count = 0; i < totalBlockSize; i++) { + for (int i = _blocks![0], count = 0; i < totalBlockSize; i++) { ds1[i] = _splitCodeWord( - dsTwo, count++, testEncodeData.length ~/ 8 ~/ _blocks[3]); + dsTwo, count++, testEncodeData.length ~/ 8 ~/ _blocks![3]); } } @@ -1744,7 +1751,7 @@ class QRCodeRenderer extends SymbologyRenderer { for (int k = 0; k < totalBlockSize; k++) { for (int j = 0; j < 8; j++) { if (i < ds1[k].length) { - _encodeDataCodeWords.add(ds1[k][i][j] == '1' ? true : false); + _encodeDataCodeWords.add(ds1[k][i]![j] == '1' ? true : false); } } } @@ -1770,12 +1777,12 @@ class QRCodeRenderer extends SymbologyRenderer { numberOfBitsInCharacterCountIndicator = _getIndicatorCount(); } - final List numberOfBitsInCharacterCountIndicatorInBool = + final List numberOfBitsInCharacterCountIndicatorInBool = _getIntToBoolArray( _encodedText.length, numberOfBitsInCharacterCountIndicator); for (int i = 0; i < numberOfBitsInCharacterCountIndicator; i++) { - _encodeDataCodeWords.add(numberOfBitsInCharacterCountIndicatorInBool[i]); + _encodeDataCodeWords.add(numberOfBitsInCharacterCountIndicatorInBool[i]!); } switch (_inputMode) { @@ -1796,7 +1803,7 @@ class QRCodeRenderer extends SymbologyRenderer { /// Method to calculate the error correcting code word void _calculateErrorCorrectingCodeWord( - int totalBlockSize, List> ds1) { + int totalBlockSize, List> ds1) { final ErrorCorrectionCodeWords errorCorrectionCodeWord = ErrorCorrectionCodeWords( codeVersion: _codeVersion, correctionLevel: _errorCorrectionLevel); @@ -1805,11 +1812,11 @@ class QRCodeRenderer extends SymbologyRenderer { final int eccw = _qrCodeValues.noOfErrorCorrectionCodeWord; _blocks = _qrCodeValues.noOfErrorCorrectionBlocks; - if (_blocks.length == 6) { + if (_blocks!.length == 6) { errorCorrectionCodeWord.dataBits = - (_dataBits - _blocks[3] * _blocks[5]) ~/ _blocks[0]; + (_dataBits - _blocks![3] * _blocks![5]) ~/ _blocks![0]; } else { - errorCorrectionCodeWord.dataBits = _dataBits ~/ _blocks[0]; + errorCorrectionCodeWord.dataBits = _dataBits ~/ _blocks![0]; } errorCorrectionCodeWord.eccw = eccw ~/ totalBlockSize; @@ -1818,23 +1825,23 @@ class QRCodeRenderer extends SymbologyRenderer { List>.generate(totalBlockSize, (int i) => []); int count = 0; - for (int i = 0; i < _blocks[0]; i++) { + for (int i = 0; i < _blocks![0]; i++) { errorCorrectionCodeWord.dataCodeWords = ds1[count]; polynomial[count++] = errorCorrectionCodeWord.getERCW(); } - if (_blocks.length == 6) { + if (_blocks!.length == 6) { errorCorrectionCodeWord.dataBits = - (_dataBits - _blocks[0] * _blocks[2]) ~/ _blocks[3]; + (_dataBits - _blocks![0] * _blocks![2]) ~/ _blocks![3]; - for (int i = 0; i < _blocks[3]; i++) { + for (int i = 0; i < _blocks![3]; i++) { errorCorrectionCodeWord.dataCodeWords = ds1[count]; polynomial[count++] = errorCorrectionCodeWord.getERCW(); } } - if (_blocks.length != 6) { + if (_blocks!.length != 6) { for (int i = 0; i < polynomial[0].length; i++) { - for (int k = 0; k < _blocks[0]; k++) { + for (int k = 0; k < _blocks![0]; k++) { for (int j = 0; j < 8; j++) { if (i < polynomial[k].length) { _encodeDataCodeWords @@ -1880,7 +1887,7 @@ class QRCodeRenderer extends SymbologyRenderer { final double minSize = size.width >= size.height ? size.height : size.width; int dimension = minSize ~/ _noOfModules; if (_qrCodeSymbology.module != null) { - dimension = _qrCodeSymbology.module; + dimension = _qrCodeSymbology.module!; } final double actualSize = (_noOfModules * dimension).toDouble(); final int xPosition = (offset.dx + (size.width - actualSize) / 2).toInt(); @@ -1896,8 +1903,8 @@ class QRCodeRenderer extends SymbologyRenderer { } if (_dataAllocationValues != null && - _dataAllocationValues[j][i].isFilled) { - if (_dataAllocationValues[j][i].isBlack) { + _dataAllocationValues![j][i].isFilled) { + if (_dataAllocationValues![j][i].isBlack) { paint.color = foregroundColor; } } @@ -1923,7 +1930,7 @@ class QRCodeRenderer extends SymbologyRenderer { @override void drawText(Canvas canvas, Offset offset, Size size, String value, TextStyle textStyle, double textSpacing, TextAlign textAlign, - [Offset actualOffset, Size actualSize]) { + [Offset? actualOffset, Size? actualSize]) { final TextSpan span = TextSpan(text: value, style: textStyle); final TextPainter textPainter = TextPainter( diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_values.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_values.dart index 133b998d9..f39ed6d87 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_values.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/renderers/two_dimensional/qr_code_values.dart @@ -11,13 +11,13 @@ class ModuleValue { } /// Specifies whether the the dot is black - bool isBlack; + late bool isBlack; /// Specifies whether the the dot is already filled - bool isFilled; + late bool isFilled; /// Specifies whether the the dot is PDP - bool isPDP; + late bool isPDP; } /// Represents the QR Code value @@ -32,10 +32,10 @@ class QRCodeValue { } /// Specifies the code version - final QRCodeVersion qrCodeVersion; + final QRCodeVersion? qrCodeVersion; /// Specifies the error correction level - final ErrorCorrectionLevel errorCorrectionLevel; + final ErrorCorrectionLevel? errorCorrectionLevel; /// Specifies the nuber of error correction code words static const List _noOfErrorCorrectionCodeWords = [ @@ -808,27 +808,27 @@ class QRCodeValue { } /// Specifies the number of data code word - int noOfDataCodeWord; + late int noOfDataCodeWord; /// Specifies the number of error correction code word - int noOfErrorCorrectionCodeWord; + late int noOfErrorCorrectionCodeWord; /// Specifies the number of error correction blocks - List noOfErrorCorrectionBlocks; + late List? noOfErrorCorrectionBlocks; /// Specifies the format information - List formatInformation; + late List? formatInformation; /// Specifies the version information - List versionInformation; + late List? versionInformation; /// Returns the alpha numeric value based on provided QR Version /// /// This is deliberately a very large method. This method could not be /// refactor to a smaller methods, since it has single switch condition and /// returns the alpha numeric value based on provided QR Version - int getAlphaNumericValues(String value) { - int valueInInt = 0; + int? getAlphaNumericValues(String value) { + int? valueInInt = 0; switch (value) { case '0': valueInInt = 0; @@ -1103,6 +1103,8 @@ class QRCodeValue { break; case QRCodeVersion.auto: break; + default: + break; } return countOfDataCodeWord; @@ -1124,6 +1126,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 9; break; + default: + break; } return countOfDataCodeWord; @@ -1145,6 +1149,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 16; break; + default: + break; } return countOfDataCodeWord; @@ -1166,6 +1172,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 26; break; + default: + break; } return countOfDataCodeWord; @@ -1187,6 +1195,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 36; break; + default: + break; } return countOfDataCodeWord; @@ -1208,6 +1218,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 46; break; + default: + break; } return countOfDataCodeWord; @@ -1229,6 +1241,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 60; break; + default: + break; } return countOfDataCodeWord; @@ -1250,6 +1264,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 66; break; + default: + break; } return countOfDataCodeWord; @@ -1271,6 +1287,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 86; break; + default: + break; } return countOfDataCodeWord; @@ -1292,6 +1310,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 100; break; + default: + break; } return countOfDataCodeWord; @@ -1313,6 +1333,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 122; break; + default: + break; } return countOfDataCodeWord; @@ -1334,6 +1356,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 140; break; + default: + break; } return countOfDataCodeWord; @@ -1355,6 +1379,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 158; break; + default: + break; } return countOfDataCodeWord; @@ -1376,6 +1402,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 180; break; + default: + break; } return countOfDataCodeWord; @@ -1397,6 +1425,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 197; break; + default: + break; } return countOfDataCodeWord; @@ -1418,6 +1448,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 223; break; + default: + break; } return countOfDataCodeWord; @@ -1439,6 +1471,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 253; break; + default: + break; } return countOfDataCodeWord; @@ -1460,6 +1494,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 283; break; + default: + break; } return countOfDataCodeWord; @@ -1481,6 +1517,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 313; break; + default: + break; } return countOfDataCodeWord; @@ -1502,6 +1540,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 341; break; + default: + break; } return countOfDataCodeWord; @@ -1523,6 +1563,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 385; break; + default: + break; } return countOfDataCodeWord; @@ -1544,6 +1586,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 406; break; + default: + break; } return countOfDataCodeWord; @@ -1565,6 +1609,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 442; break; + default: + break; } return countOfDataCodeWord; @@ -1586,6 +1632,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 464; break; + default: + break; } return countOfDataCodeWord; @@ -1607,6 +1655,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 514; break; + default: + break; } return countOfDataCodeWord; @@ -1628,6 +1678,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 538; break; + default: + break; } return countOfDataCodeWord; @@ -1649,6 +1701,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 596; break; + default: + break; } return countOfDataCodeWord; @@ -1670,6 +1724,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 628; break; + default: + break; } return countOfDataCodeWord; @@ -1691,6 +1747,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 661; break; + default: + break; } return countOfDataCodeWord; @@ -1712,6 +1770,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 701; break; + default: + break; } return countOfDataCodeWord; @@ -1733,6 +1793,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 745; break; + default: + break; } return countOfDataCodeWord; @@ -1754,6 +1816,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 793; break; + default: + break; } return countOfDataCodeWord; @@ -1775,6 +1839,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 845; break; + default: + break; } return countOfDataCodeWord; @@ -1796,6 +1862,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 901; break; + default: + break; } return countOfDataCodeWord; @@ -1817,6 +1885,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 961; break; + default: + break; } return countOfDataCodeWord; @@ -1838,6 +1908,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 986; break; + default: + break; } return countOfDataCodeWord; @@ -1859,6 +1931,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 1054; break; + default: + break; } return countOfDataCodeWord; @@ -1880,6 +1954,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 1096; break; + default: + break; } return countOfDataCodeWord; @@ -1901,6 +1977,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 1142; break; + default: + break; } return countOfDataCodeWord; @@ -1922,6 +2000,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 1222; break; + default: + break; } return countOfDataCodeWord; @@ -1943,6 +2023,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: countOfDataCodeWord = 1276; break; + default: + break; } return countOfDataCodeWord; @@ -1950,7 +2032,7 @@ class QRCodeValue { /// Calculates the number of error corrcetion code words int _getNumberOfErrorCorrectionCodeWords() { - int index = (getVersionNumber(qrCodeVersion) - 1) * 4; + int index = (getVersionNumber(qrCodeVersion!) - 1) * 4; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: index += 0; @@ -1964,6 +2046,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: index += 3; break; + default: + break; } return _noOfErrorCorrectionCodeWords[index]; @@ -1974,9 +2058,9 @@ class QRCodeValue { /// This is deliberately a very large method. This method could not be /// refactored to a smaller methods, since it has single switch condition and /// returns the error correction blocks based om the QR code version - List _getNumberOfErrorCorrectionBlocks() { - List numberOfErrorCorrectionBlocks; - final int version = getVersionNumber(qrCodeVersion); + List? _getNumberOfErrorCorrectionBlocks() { + List? numberOfErrorCorrectionBlocks; + final int version = getVersionNumber(qrCodeVersion!); switch (version) { case 1: case 2: @@ -2102,8 +2186,8 @@ class QRCodeValue { } /// Specifies the error correction blocks for version 3 - List _getErrorCorrectionBlocksForVersion3() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion3() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: case ErrorCorrectionLevel.medium: @@ -2113,14 +2197,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [2]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 4 - List _getErrorCorrectionBlocksForVersion4() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion4() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [1]; @@ -2132,14 +2218,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [4]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 5 - List _getErrorCorrectionBlocksForVersion5() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion5() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [1]; @@ -2153,14 +2241,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [2, 33, 11, 2, 34, 12]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 6 - List _getErrorCorrectionBlocksForVersion6() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion6() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [2]; @@ -2170,14 +2260,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [4]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 7 - List _getErrorCorrectionBlocksForVersion7() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion7() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [2]; @@ -2191,14 +2283,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [4, 39, 13, 1, 40, 14]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 8 - List _getErrorCorrectionBlocksForVersion8() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion8() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [2]; @@ -2212,14 +2306,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [4, 40, 14, 2, 41, 15]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 9 - List _getErrorCorrectionBlocksForVersion9() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion9() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [2]; @@ -2233,14 +2329,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [4, 36, 12, 4, 37, 13]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 10 - List _getErrorCorrectionBlocksForVersion10() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion10() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [2, 86, 68, 2, 87, 69]; @@ -2254,14 +2352,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [6, 43, 15, 2, 44, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version11 - List _getErrorCorrectionBlocksForVersion11() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion11() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [4]; @@ -2275,14 +2375,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [3, 36, 12, 8, 37, 13]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 12 - List _getErrorCorrectionBlocksForVersion12() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion12() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [2, 116, 92, 2, 117, 93]; @@ -2296,14 +2398,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [7, 42, 14, 4, 43, 15]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 13 - List _getErrorCorrectionBlocksForVersion13() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion13() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [4]; @@ -2317,14 +2421,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [12, 33, 11, 4, 34, 12]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 14 - List _getErrorCorrectionBlocksForVersion14() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion14() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [3, 145, 115, 1, 146, 116]; @@ -2338,14 +2444,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [11, 36, 12, 5, 37, 13]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 15 - List _getErrorCorrectionBlocksForVersion15() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion15() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [5, 109, 87, 1, 110, 88]; @@ -2359,14 +2467,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [11, 36, 12, 7, 37, 13]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 16 - List _getErrorCorrectionBlocksForVersion16() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion16() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [5, 112, 98, 1, 123, 99]; @@ -2380,14 +2490,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [3, 45, 15, 13, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 17 - List _getErrorCorrectionBlocksForVersion17() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion17() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [1, 135, 107, 5, 136, 108]; @@ -2401,14 +2513,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [2, 42, 14, 17, 43, 15]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 18 - List _getErrorCorrectionBlocksForVersion18() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion18() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [5, 150, 120, 1, 151, 121]; @@ -2422,14 +2536,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [2, 42, 14, 19, 43, 15]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 19 - List _getErrorCorrectionBlocksForVersion19() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion19() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [3, 141, 113, 4, 142, 114]; @@ -2443,14 +2559,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [9, 39, 13, 16, 40, 14]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } /// Specifies the error correction blocks for version 20 - List _getErrorCorrectionBlocksForVersion20() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion20() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [3, 135, 107, 5, 136, 108]; @@ -2464,14 +2582,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [15, 43, 15, 10, 44, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 21 - List _getErrorCorrectionBlocksForVersion21() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion21() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [4, 144, 116, 4, 145, 117]; @@ -2485,14 +2605,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [19, 46, 16, 6, 47, 17]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 22 - List _getErrorCorrectionBlocksForVersion22() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion22() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [2, 139, 111, 7, 140, 112]; @@ -2506,14 +2628,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [34]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 23 - List _getErrorCorrectionBlocksForVersion23() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion23() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [4, 151, 121, 5, 152, 122]; @@ -2527,14 +2651,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [16, 45, 15, 14, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 24 - List _getErrorCorrectionBlocksForVersion24() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion24() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [6, 147, 117, 4, 148, 118]; @@ -2548,14 +2674,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [30, 46, 16, 2, 47, 17]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 25 - List _getErrorCorrectionBlocksForVersion25() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion25() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [8, 132, 106, 4, 133, 107]; @@ -2569,14 +2697,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [22, 45, 15, 13, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 26 - List _getErrorCorrectionBlocksForVersion26() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion26() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [10, 142, 114, 2, 143, 115]; @@ -2590,14 +2720,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [33, 46, 16, 4, 47, 17]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 27 - List _getErrorCorrectionBlocksForVersion27() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion27() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [8, 152, 122, 4, 153, 123]; @@ -2611,14 +2743,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [12, 45, 15, 28, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 28 - List _getErrorCorrectionBlocksForVersion28() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion28() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [3, 147, 117, 10, 148, 118]; @@ -2632,14 +2766,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [11, 45, 15, 31, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 29 - List _getErrorCorrectionBlocksForVersion29() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion29() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [7, 146, 116, 7, 147, 117]; @@ -2653,14 +2789,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [19, 45, 15, 26, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 30 - List _getErrorCorrectionBlocksForVersion30() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion30() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [5, 145, 115, 10, 146, 116]; @@ -2674,14 +2812,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [23, 45, 15, 25, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 31 - List _getErrorCorrectionBlocksForVersion31() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion31() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [13, 145, 115, 3, 146, 116]; @@ -2695,14 +2835,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [23, 45, 15, 28, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 32 - List _getErrorCorrectionBlocksForVersion32() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion32() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [17]; @@ -2716,14 +2858,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [19, 45, 15, 35, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 33 - List _getErrorCorrectionBlocksForVersion33() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion33() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [17, 145, 115, 1, 146, 116]; @@ -2737,14 +2881,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [11, 45, 15, 46, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 34 - List _getErrorCorrectionBlocksForVersion34() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion34() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [13, 145, 115, 6, 146, 116]; @@ -2758,14 +2904,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [59, 46, 16, 1, 47, 17]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 35 - List _getErrorCorrectionBlocksForVersion35() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion35() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [12, 151, 121, 7, 152, 122]; @@ -2779,14 +2927,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [22, 45, 15, 41, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 36 - List _getErrorCorrectionBlocksForVersion36() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion36() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [6, 151, 121, 14, 152, 122]; @@ -2800,14 +2950,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [2, 45, 15, 64, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 37 - List _getErrorCorrectionBlocksForVersion37() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion37() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [17, 152, 122, 4, 153, 123]; @@ -2821,14 +2973,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [24, 45, 15, 46, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 38 - List _getErrorCorrectionBlocksForVersion38() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion38() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [4, 152, 122, 18, 153, 123]; @@ -2842,14 +2996,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [42, 45, 15, 32, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 39 - List _getErrorCorrectionBlocksForVersion39() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion39() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [20, 147, 117, 4, 148, 118]; @@ -2863,14 +3019,16 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [10, 45, 15, 67, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; } ///Specifies the error correction blocks for version 40 - List _getErrorCorrectionBlocksForVersion40() { - List numberOfErrorCorrectionBlocks; + List? _getErrorCorrectionBlocksForVersion40() { + List? numberOfErrorCorrectionBlocks; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: numberOfErrorCorrectionBlocks = [19, 148, 118, 6, 149, 119]; @@ -2884,6 +3042,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: numberOfErrorCorrectionBlocks = [20, 45, 15, 61, 46, 16]; break; + default: + break; } return numberOfErrorCorrectionBlocks; @@ -2894,9 +3054,9 @@ class QRCodeValue { /// This is deliberately a very large method. This method could not be /// refactored to a smaller methods, since it has single switch condition and /// returns the version information - List _obtainVersionInformation() { - List versionInformation; - final int version = getVersionNumber(qrCodeVersion); + List? _obtainVersionInformation() { + List? versionInformation; + final int version = getVersionNumber(qrCodeVersion!); switch (version) { case 7: versionInformation = [ @@ -3652,8 +3812,8 @@ class QRCodeValue { } /// Specifies the format information - List _obtainFormatInformation() { - List formatInformation; + List? _obtainFormatInformation() { + List? formatInformation; switch (errorCorrectionLevel) { case ErrorCorrectionLevel.low: formatInformation = [1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1]; @@ -3667,6 +3827,8 @@ class QRCodeValue { case ErrorCorrectionLevel.high: formatInformation = [0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0]; break; + default: + break; } return formatInformation; diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/datamatrix_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/datamatrix_symbology.dart index 94005aa01..ffa636fef 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/datamatrix_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/datamatrix_symbology.dart @@ -17,7 +17,7 @@ class DataMatrix extends Symbology { /// The arguments [module] must be non-negative and greater than 0. /// DataMatrix( - {int module, + {int? module, this.dataMatrixSize = DataMatrixSize.auto, this.encoding = DataMatrixEncoding.auto}) : super(module: module); diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/qr_code_symbology.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/qr_code_symbology.dart index a8b3ec484..66a97945a 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/qr_code_symbology.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/two_dimensional/qr_code_symbology.dart @@ -34,7 +34,7 @@ class QRCode extends Symbology { {this.codeVersion, this.errorCorrectionLevel = ErrorCorrectionLevel.high, this.inputMode = QRInputMode.binary, - int module}) + int? module}) : super(module: module); /// Define the version that is used to encode the amount of data. @@ -57,7 +57,7 @@ class QRCode extends Symbology { /// symbology: QRCode(codeVersion: 4))); ///} /// ```dart - final QRCodeVersion codeVersion; + final QRCodeVersion? codeVersion; /// Define the encode recovery capacity of the barcode. /// diff --git a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/utils/helper.dart b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/utils/helper.dart index e8cc7a40f..c7e118b4b 100644 --- a/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/utils/helper.dart +++ b/packages/syncfusion_flutter_barcodes/lib/src/barcode_generator/utils/helper.dart @@ -102,8 +102,6 @@ int getVersionNumber(QRCodeVersion qrCodeVersion) { case QRCodeVersion.auto: return 0; } - - return 0; } /// Returns the version based on number @@ -262,6 +260,4 @@ int getDataMatrixSize(DataMatrixSize dataMatrixSize) { case DataMatrixSize.auto: return 0; } - - return 0; } diff --git a/packages/syncfusion_flutter_barcodes/pubspec.yaml b/packages/syncfusion_flutter_barcodes/pubspec.yaml index e7629e4a6..fbafdb7c1 100644 --- a/packages/syncfusion_flutter_barcodes/pubspec.yaml +++ b/packages/syncfusion_flutter_barcodes/pubspec.yaml @@ -1,10 +1,10 @@ name: syncfusion_flutter_barcodes -description: Syncfusion Flutter barcodes widget is used to generate and display data in the machine-readable, industry-standard 1D and 2D barcodes. -version: 18.3.35 +description: Flutter Barcodes generator library is used to generate and display data in the machine-readable, industry-standard 1D and 2D barcodes. +version: 19.1.54 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_barcodes environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: @@ -13,3 +13,5 @@ dependencies: path: ../syncfusion_flutter_core flutter: + + diff --git a/packages/syncfusion_flutter_calendar/CHANGELOG.md b/packages/syncfusion_flutter_calendar/CHANGELOG.md index 653232cb1..01973bdff 100644 --- a/packages/syncfusion_flutter_calendar/CHANGELOG.md +++ b/packages/syncfusion_flutter_calendar/CHANGELOG.md @@ -1,4 +1,38 @@ -## [13.4.30] +## [Unreleased version] +** Bug fixes** +* Now, the localization is working properly for the spanned appointment count text in Flutter event calendar. + +**Features** +* Provided the LoadMore support for the event calendar. +* Provided the negative values support for BYSETPOS in recurrence to display the appointment in the last and second last week of a month. +* Provided the support for the month header format in the Flutter event calendar. +* Provided the support for Getting appointments between the start and end dates in the Flutter event calendar. +* Provided the current time indicator support for timeslot views. +* Provided the support for enabling and disabling the swiping interaction in the Flutter event calendar. +* Provided the support for the selected date changed callback in the Flutter event calendar. +* Improved the timeline views disabled slots appearance in the Flutter event calendar. + +**Breaking changes** +* The `startTime` and `endTime` properties of the `Appointment` class are now marked as required. +* The `startTime` property of the `RecurrenceProperties` class is now marked as required. +* The `startTime` and `endTime` properties of the `TimeRegion` class are now marked as required. + +## [18.4.34] - 01/12/2021 +**Bug fixes** +* Now, the date range picker layouting properly in the calendar header, when the `showDatePickerButton` property enabled. + +## [18.4.33] - 01/05/2021 +No changes. + +## [18.4.32] - 12/30/2020 +No changes. + +## [18.4.31] - 12/22/2020 +**Bug fixes** +* Now, the month view changes properly by date range picker when placing `SfCalendar` in `WillPopUp` widget. +* Now, the appointments are sorting properly in the month cells of `SfCalendar`. + +## [18.4.30] - 12/17/2020 **Features** * The custom builder support is provided for the time region and the appointment views in the calendar. * Provided the interaction support for the resource header. @@ -7,6 +41,8 @@ **Enhancements** * The animation for view switching, selection ripple effect, and header picker pop-up animation is improved. +**Breaking changes** +* Now, the display date that does not contain an appointment will show the text as `No events`. ## [18.3.50] - 11/17/2020 **Bug fixes** @@ -105,7 +141,7 @@ No changes. ## [18.1.55-beta] - 06/03/2020 **Bug fixes** -* Now, the month cell dates are aligned properly with the Flutter latest beta channel. +* Now, the month cell dates are aligned properly with the Flutter latest beta channel. ## [18.1.52-beta] - 05/14/2020 diff --git a/packages/syncfusion_flutter_calendar/README.md b/packages/syncfusion_flutter_calendar/README.md index 9b035de98..62958ca4e 100644 --- a/packages/syncfusion_flutter_calendar/README.md +++ b/packages/syncfusion_flutter_calendar/README.md @@ -1,12 +1,10 @@ -![syncfusion_flutter_calendar_banner](https://cdn.syncfusion.com/content/images/FTControl/Flutter/calender+banner+image.png) +![syncfusion_flutter_calendar_banner](https://cdn.syncfusion.com/content/images/FTControl/Flutter/calender+banner+image.png) -# Syncfusion Flutter Calendar +# Flutter Calendar -The Syncfusion Flutter Calendar widget has built-in configurable views that provide basic functionalities for scheduling and representing appointments/events efficiently. +The Flutter Calendar widget has built-in configurable views such as day, week, workweek, month, schedule, timeline day, timeline week, timeline workweek and timeline month that provide basic functionalities for scheduling and representing appointments/events efficiently. -**Disclaimer**: This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or the Syncfusion Community [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE). For more details, please check the LICENSE file. - -**Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. +**Disclaimer**: This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. ## Table of contents @@ -51,6 +49,10 @@ The Syncfusion Flutter Calendar widget has built-in configurable views that prov ![schedule_view](https://cdn.syncfusion.com/content/images/FTControl/Calendar/flutter-calendar-schedule-view.png) +* **Load more** - Load appointments on-demand whenever users switch from one view to another or when scrolling to the start or end position of the schedule view. + +![load_more](https://cdn.syncfusion.com/content/images/FTControl/Flutter/calendar/load_more_gif.gif) + * **Special time regions** - Disable interactions and selections for specific time ranges. This is useful when you want to block user interaction during holidays or another special events and to highlight those time slots. ![special_time_regions](https://cdn.syncfusion.com/content/images/FTControl/Flutter/flutter-calendar-special-time-regions.png) @@ -71,6 +73,10 @@ The Syncfusion Flutter Calendar widget has built-in configurable views that prov ![leading_trailing_dates](https://cdn.syncfusion.com/content/images/FTControl/Calendar/flutter-calendar-leading-trailing-dates.png) +* **Current time indicator** - The current time indicator displays in the current time slot of the Calendar. + +![current_time_indicator](https://cdn.syncfusion.com/content/images/FTControl/Flutter/calendar/current_time_indicator.png) + * **Custom start and end hours** - Display the event calendar timeslot views with specific time durations by hiding the unwanted hours. * **Month agenda view** - Display appointments in a list as shown in the following month view by clicking on a day. @@ -89,7 +95,7 @@ The Syncfusion Flutter Calendar widget has built-in configurable views that prov ![appearance_customization](https://cdn.syncfusion.com/content/images/FTControl/Flutter/Appearance+customization.png) -* **Localization and Gloablization** - Display the current date and time by following the globalized date and time formats, and localize all available static texts in calendar.. +* **Localization and Gloablization** - Display the current date and time by following the globalized date and time formats, and localize all available static texts in calendar. ![localization](https://cdn.syncfusion.com/content/images/FTControl/Flutter/calendar/localization.png) @@ -99,7 +105,7 @@ The Syncfusion Flutter Calendar widget has built-in configurable views that prov ![right_to_left](https://cdn.syncfusion.com/content/images/FTControl/Flutter/calendar/right_to_left.png) -* **Programmatic navigation** - Programmatic navigation to the previous/next views by using the calendar controller. +* **Navigation** - Programmatic navigation to the previous/next views by using the calendar controller. Also, support provided to enable or disable view navigation using swipe interaction. * **Minimum and maximum dates** - Restrict the date navigation for end users by using the minimum and maximum dates. @@ -303,4 +309,4 @@ class Meeting { Founded in 2001 and headquartered in Research Triangle Park, N.C., Syncfusion has more than 20,000 customers and more than 1 million users, including large financial institutions, Fortune 500 companies, and global IT consultancies. -Today we provide 1,000+ controls and frameworks for web ([ASP.NET Core](https://www.syncfusion.com/aspnet-core-ui-controls), [ASP.NET MVC](https://www.syncfusion.com/aspnet-mvc-ui-controls), [ASP.NET WebForms](https://www.syncfusion.com/jquery/aspnet-web-forms-ui-controls), [JavaScript](https://www.syncfusion.com/javascript-ui-controls), [Angular](https://www.syncfusion.com/angular-ui-components), [React](https://www.syncfusion.com/react-ui-components), [Vue](https://www.syncfusion.com/vue-ui-components), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [Flutter](https://www.syncfusion.com/flutter-widgets), [UWP](https://www.syncfusion.com/uwp-ui-controls), and [JavaScript](https://www.syncfusion.com/javascript-ui-controls)), and desktop development ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-controls)). We provide ready-to deploy enterprise software for dashboards, reports, data integration, and big data processing. Many customers have saved millions in licensing fees by deploying our software. \ No newline at end of file +Today we provide 1,000+ controls and frameworks for web ([ASP.NET Core](https://www.syncfusion.com/aspnet-core-ui-controls), [ASP.NET MVC](https://www.syncfusion.com/aspnet-mvc-ui-controls), [ASP.NET WebForms](https://www.syncfusion.com/jquery/aspnet-web-forms-ui-controls), [JavaScript](https://www.syncfusion.com/javascript-ui-controls), [Angular](https://www.syncfusion.com/angular-ui-components), [React](https://www.syncfusion.com/react-ui-components), [Vue](https://www.syncfusion.com/vue-ui-components), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [Flutter](https://www.syncfusion.com/flutter-widgets), [UWP](https://www.syncfusion.com/uwp-ui-controls), and [JavaScript](https://www.syncfusion.com/javascript-ui-controls)), and desktop development ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-controls)). We provide ready-to deploy enterprise software for dashboards, reports, data integration, and big data processing. Many customers have saved millions in licensing fees by deploying our software. diff --git a/packages/syncfusion_flutter_calendar/analysis_options.yaml b/packages/syncfusion_flutter_calendar/analysis_options.yaml index 2f547af5d..07fa312a6 100644 --- a/packages/syncfusion_flutter_calendar/analysis_options.yaml +++ b/packages/syncfusion_flutter_calendar/analysis_options.yaml @@ -2,4 +2,5 @@ include: package:syncfusion_flutter_core/analysis_options.yaml analyzer: errors: - include_file_not_found: ignore \ No newline at end of file + include_file_not_found: ignore + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_flutter_calendar/lib/calendar.dart b/packages/syncfusion_flutter_calendar/lib/calendar.dart index 9cbcb6497..574814340 100644 --- a/packages/syncfusion_flutter_calendar/lib/calendar.dart +++ b/packages/syncfusion_flutter_calendar/lib/calendar.dart @@ -9,64 +9,19 @@ /// * [Knowledge base](https://www.syncfusion.com/kb/flutter) library calendar; -import 'dart:math' as math; -import 'dart:ui'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/gestures.dart'; -import 'package:timezone/timezone.dart'; -import 'package:syncfusion_flutter_core/localizations.dart'; -import 'package:flutter/services.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; - -import 'package:intl/intl.dart' show DateFormat; -import 'package:intl/date_symbol_data_local.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:syncfusion_flutter_core/core.dart'; -import 'package:syncfusion_flutter_core/core_internal.dart'; -import 'package:syncfusion_flutter_datepicker/datepicker.dart'; - -part './src/calendar/sfcalendar.dart'; - -part './src/calendar/common/enums.dart'; -part './src/calendar/common/date_time_engine.dart'; -part './src/calendar/common/calendar_view_helper.dart'; -part './src/calendar/common/event_args.dart'; -part './src/calendar/common/calendar_controller.dart'; - -part 'src/calendar/scroll_view/custom_scroll_view.dart'; - -part 'src/calendar/views/calendar_view.dart'; -part 'src/calendar/views/header_view.dart'; -part 'src/calendar/views/view_header_view.dart'; -part 'src/calendar/views/day_view.dart'; -part 'src/calendar/views/month_view.dart'; -part 'src/calendar/views/timeline_view.dart'; -part 'src/calendar/views/selection_view.dart'; -part 'src/calendar/views/time_ruler_view.dart'; -part 'src/calendar/views/schedule_view.dart'; -part 'src/calendar/views/custom_calendar_button.dart'; -part 'src/calendar/views/multi_child_container.dart'; - -part './src/calendar/settings/time_slot_view_settings.dart'; -part './src/calendar/settings/month_view_settings.dart'; -part './src/calendar/settings/header_style.dart'; -part './src/calendar/settings/view_header_style.dart'; -part './src/calendar/settings/schedule_view_settings.dart'; -part './src/calendar/settings/time_region.dart'; -part './src/calendar/settings/resource_view_settings.dart'; - -part './src/calendar/appointment_engine/appointment.dart'; -part './src/calendar/appointment_engine/appointment_helper.dart'; -part './src/calendar/appointment_engine/recurrence_helper.dart'; -part './src/calendar/appointment_engine/recurrence_properties.dart'; -part './src/calendar/appointment_engine/month_appointment_helper.dart'; -part './src/calendar/appointment_engine/calendar_datasource.dart'; - -part 'src/calendar/resource_view/calendar_resource.dart'; -part 'src/calendar/resource_view/resource_view.dart'; - -part './src/calendar/appointment_layout/appointment_layout.dart'; -part './src/calendar/appointment_layout/agenda_view_layout.dart'; -part './src/calendar/appointment_layout/allday_appointment_layout.dart'; +export 'src/calendar/appointment_engine/appointment.dart'; +export 'src/calendar/appointment_engine/calendar_datasource.dart'; +export 'src/calendar/appointment_engine/recurrence_properties.dart'; +export 'src/calendar/common/calendar_controller.dart'; +export 'src/calendar/common/enums.dart'; +export 'src/calendar/common/event_args.dart'; +export 'src/calendar/resource_view/calendar_resource.dart'; +export 'src/calendar/settings/header_style.dart'; +export 'src/calendar/settings/month_view_settings.dart'; +export 'src/calendar/settings/resource_view_settings.dart'; +export 'src/calendar/settings/schedule_view_settings.dart'; +export 'src/calendar/settings/time_region.dart'; +export 'src/calendar/settings/time_slot_view_settings.dart'; +export 'src/calendar/settings/view_header_style.dart'; + +export 'src/calendar/sfcalendar.dart'; diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment.dart index 354fa0bcc..825df74b4 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment.dart @@ -1,4 +1,7 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_datepicker/datepicker.dart' + show IterableDiagnostics; /// Appointment data for calendar. /// @@ -41,7 +44,7 @@ part of calendar; /// return DataSource(appointments); /// } /// ``` -class Appointment { +class Appointment with Diagnosticable { /// Creates an appointment data for [SfCalendar]. /// /// An object that contains properties to hold the detailed information @@ -54,19 +57,12 @@ class Appointment { this.notes, this.location, this.resourceIds, - DateTime startTime, - DateTime endTime, - String subject, - Color color, - List recurrenceExceptionDates, - }) : startTime = startTime ?? DateTime.now(), - endTime = endTime ?? DateTime.now(), - subject = subject ?? '', - _actualStartTime = startTime, - _actualEndTime = endTime, - color = color ?? Colors.lightBlue, - recurrenceExceptionDates = recurrenceExceptionDates ?? [], - _isSpanned = false; + required this.startTime, + required this.endTime, + this.subject = '', + this.color = Colors.lightBlue, + this.recurrenceExceptionDates, + }); /// The start time for an [Appointment] in [SfCalendar]. /// @@ -320,7 +316,7 @@ class Appointment { /// return DataSource(appointments); /// } /// ``` - String startTimeZone; + String? startTimeZone; /// The end time zone for an [Appointment] in [SfCalendar]. /// @@ -366,7 +362,7 @@ class Appointment { /// return DataSource(appointments); /// } /// ``` - String endTimeZone; + String? endTimeZone; /// Recurs the [Appointment] on [SfCalendar]. /// @@ -422,7 +418,7 @@ class Appointment { /// } /// /// ``` - String recurrenceRule; + String? recurrenceRule; /// Delete the occurrence for an recurrence appointment. /// @@ -481,7 +477,7 @@ class Appointment { /// return DataSource(appointments); /// } /// ``` - List recurrenceExceptionDates; + List? recurrenceExceptionDates; /// Defines the notes for an [Appointment] in [SfCalendar]. /// @@ -536,7 +532,7 @@ class Appointment { /// return DataSource(appointments); /// } /// ``` - String notes; + String? notes; /// Defines the location for an [Appointment] in [SfCalendar]. /// @@ -591,7 +587,7 @@ class Appointment { /// return DataSource(appointments); /// } /// ``` - String location; + String? location; /// The ids of the [CalendarResource] that shares this [Appointment]. /// @@ -650,35 +646,7 @@ class Appointment { ///} /// /// ``` - List resourceIds; - - //Used for referring items in ItemsSource of Schedule. - Object _data; - - // ignore: prefer_final_fields - DateTime _actualStartTime; - - // ignore: prefer_final_fields - DateTime _actualEndTime; - - // ignore: prefer_final_fields - bool _isSpanned = false; - - /// For span appointments, we have split the appointment into multiple while - /// calculating the visible appointments, to render on the visible view, hence - /// it's not possible to get the exact start and end time for the spanning - /// appointment, hence to hold the exact start time of the appointment we have - /// used this variable, and stored the start time which calculated based on - /// the timezone, in the visible appointments calculation. - DateTime _exactStartTime; - - /// For span appointments, we have split the appointment into multiple while - /// calculating the visible appointments, to render on the visible view, hence - /// it's not possible to get the exact start and end time for the spanning - /// appointment, hence to hold the exact start time of the appointment we have - /// used this variable, and stored the end time which calculated based on - /// the timezone, in the visible appointments calculation. - DateTime _exactEndTime; + List? resourceIds; @override bool operator ==(dynamic other) { @@ -712,12 +680,31 @@ class Appointment { isAllDay = false, notes, location, - resourceIds, + hashList(resourceIds), startTime, endTime, subject, color, - recurrenceExceptionDates, + hashList(recurrenceExceptionDates), ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(StringProperty('startTimeZone', startTimeZone)); + properties.add(StringProperty('endTimeZone', endTimeZone)); + properties.add(StringProperty('recurrenceRule', recurrenceRule)); + properties.add(StringProperty('notes', notes)); + properties.add(StringProperty('location', location)); + properties.add(StringProperty('subject', subject)); + properties.add(ColorProperty('color', color)); + properties.add(DiagnosticsProperty('startTime', startTime)); + properties.add(DiagnosticsProperty('endTime', endTime)); + properties.add(IterableDiagnostics(recurrenceExceptionDates) + .toDiagnosticsNode(name: 'recurrenceExceptionDates')); + properties.add(IterableDiagnostics(resourceIds) + .toDiagnosticsNode(name: 'resourceIds')); + properties.add(DiagnosticsProperty('isAllDay', isAllDay)); + } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment_helper.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment_helper.dart index cdb79a4e4..afa72350c 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment_helper.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment_helper.dart @@ -1,1776 +1,1435 @@ -part of calendar; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:timezone/timezone.dart'; -DateTime _convertToStartTime(DateTime date) { - return DateTime(date.year, date.month, date.day, 0, 0, 0); -} - -DateTime _convertToEndTime(DateTime date) { - return DateTime(date.year, date.month, date.day, 23, 59, 59); -} - -/// This method is used to check the appointment needs all day appointment -/// view or not in agenda view, because the all day appointment view shown -/// as half of the normal appointment view in agenda view. -/// Agenda view used on month and schedule calendar view. -bool _isAllDayAppointmentView(Appointment appointment) { - return appointment.isAllDay || - appointment._isSpanned || - appointment._actualStartTime.day != appointment._actualEndTime.day; -} - -//// Check whether the data source has calendar appointment type or not. -bool _isCalendarAppointment(CalendarDataSource dataSource) { - if (dataSource.appointments == null || - dataSource.appointments.isEmpty || - dataSource.appointments[0] is Appointment) { - return true; - } - - return false; -} +import '../common/calendar_view_helper.dart'; +import '../common/enums.dart'; +import '../sfcalendar.dart'; +import 'appointment.dart'; +import 'calendar_datasource.dart'; +import 'recurrence_helper.dart'; -/// Calculate the maximum appointment date based on appointment collection -/// and schedule view settings. -DateTime _getMaxAppointmentDate( - List appointments, - String timeZone, - DateTime maxDate, - DateTime displayDate, - ScheduleViewSettings scheduleViewSettings, - bool useMobilePlatformUI) { - /// return default max date when [hideEmptyAgendaDays] as false - if (!scheduleViewSettings.hideEmptyScheduleWeek && useMobilePlatformUI) { - return maxDate; +/// Holds the static helper methods used for appointment rendering in calendar. +class AppointmentHelper { + /// Return the date with start time value for the date value. + static DateTime convertToStartTime(DateTime date) { + return DateTime(date.year, date.month, date.day, 0, 0, 0); } - DateTime currentMaxDate = displayDate; - if (appointments == null || appointments.isEmpty) { - return currentMaxDate; + /// Return the date with end time value for the date value. + static DateTime convertToEndTime(DateTime date) { + return DateTime(date.year, date.month, date.day, 23, 59, 59); } - /// Calculate the max appointment date based on appointments when - /// web view enabled or [hideEmptyAgendaDays] property as enabled. - for (int j = 0; j < appointments.length; j++) { - final Appointment appointment = appointments[j]; - appointment._actualEndTime = _convertTimeToAppointmentTimeZone( - appointment.endTime, appointment.endTimeZone, timeZone); - - if (appointment.recurrenceRule == null || - appointment.recurrenceRule == '') { - if (appointment._actualEndTime.isAfter(currentMaxDate)) { - currentMaxDate = appointment._actualEndTime; - } - - continue; - } - - /// Return specified ma date when recurrence rule does not have - /// count and until string. - if (!appointment.recurrenceRule.contains('COUNT') && - !appointment.recurrenceRule.contains('UNTIL')) { - currentMaxDate = maxDate; - return currentMaxDate; - } - - if (appointment.recurrenceRule.contains('UNTIL')) { - final List ruleSeparator = ['=', ';', ',']; - final List rRule = - _splitRule(appointment.recurrenceRule, ruleSeparator); - final String untilValue = rRule[rRule.indexOf('UNTIL') + 1]; - DateTime recurrenceEndDate = DateTime.parse(untilValue); - recurrenceEndDate = DateTime(recurrenceEndDate.year, - recurrenceEndDate.month, recurrenceEndDate.day, 23, 59, 59); - if (recurrenceEndDate.isAfter(currentMaxDate)) { - currentMaxDate = recurrenceEndDate; - continue; - } - } - - final List recursiveDates = _getRecurrenceDateTimeCollection( - appointment.recurrenceRule, - appointment._actualStartTime, - ); - - if (recursiveDates.isEmpty) { - continue; - } - - if (appointment.recurrenceExceptionDates == null || - appointment.recurrenceExceptionDates.isEmpty) { - final DateTime date = recursiveDates[recursiveDates.length - 1]; - if (date.isAfter(currentMaxDate)) { - currentMaxDate = date; - continue; - } - } - - for (int k = recursiveDates.length - 1; k >= 0; k--) { - final DateTime recurrenceDate = recursiveDates[k]; - bool isExceptionDate = false; - if (appointment.recurrenceExceptionDates != null) { - for (int i = 0; i < appointment.recurrenceExceptionDates.length; i++) { - final DateTime exceptionDate = - appointment.recurrenceExceptionDates[i]; - if (isSameDate(recurrenceDate, exceptionDate)) { - isExceptionDate = true; - } - } - } - - if (!isExceptionDate) { - final DateTime recurrenceEndDate = addDuration( - recurrenceDate, - appointment._actualEndTime - .difference(appointment._actualStartTime)); - if (recurrenceEndDate.isAfter(currentMaxDate)) { - currentMaxDate = recurrenceEndDate; - break; - } - } - } + /// Return the start date of the month specified in date. + static DateTime getMonthStartDate(DateTime date) { + return DateTime(date.year, date.month, 1); } - return currentMaxDate; -} - -/// Calculate the minimum appointment date based on appointment collection -/// and schedule view settings. -DateTime _getMinAppointmentDate( - List appointments, - String timeZone, - DateTime minDate, - DateTime displayDate, - ScheduleViewSettings scheduleViewSettings, - bool useMobilePlatformUI) { - /// return default min date when [hideEmptyAgendaDays] as false - if (!scheduleViewSettings.hideEmptyScheduleWeek && useMobilePlatformUI) { - return minDate; + /// Return the end date of the month specified in date. + static DateTime getMonthEndDate(DateTime date) { + return addDays(getNextMonthDate(date), -1); } - DateTime currentMinDate = displayDate; - if (appointments == null || appointments.isEmpty) { - return currentMinDate; + /// Return the date time value by adding the days in date. + static DateTime addDaysWithTime( + DateTime date, int days, int hour, int minute, int second) { + final DateTime newDate = addDays(date, days); + return DateTime( + newDate.year, newDate.month, newDate.day, hour, minute, second); } - /// Calculate the min appointment date based on appointments when - /// web view enabled or [hideEmptyAgendaDays] property as enabled. - for (int j = 0; j < appointments.length; j++) { - final Appointment appointment = appointments[j]; - appointment._actualStartTime = _convertTimeToAppointmentTimeZone( - appointment.startTime, appointment.startTimeZone, timeZone); - - if (appointment._actualStartTime.isBefore(currentMinDate)) { - currentMinDate = appointment._actualStartTime; + /// Check whether the data source has calendar appointment type or not. + static bool isCalendarAppointment(CalendarDataSource dataSource) { + if (dataSource.appointments == null || + dataSource.appointments!.isEmpty || + dataSource.appointments![0] is CalendarAppointment) { + return true; } - continue; - } - - return currentMinDate; -} - -/// Check any appointment in appointments collection in between -/// the start and end date. -bool _isAppointmentBetweenDates(List appointments, - DateTime startDate, DateTime endDate, String timeZone) { - startDate = _convertToStartTime(startDate); - endDate = _convertToEndTime(endDate); - if (appointments == null || appointments.isEmpty) { return false; } - for (int j = 0; j < appointments.length; j++) { - final Appointment appointment = appointments[j]; - appointment._actualStartTime = _convertTimeToAppointmentTimeZone( - appointment.startTime, appointment.startTimeZone, timeZone); - appointment._actualEndTime = _convertTimeToAppointmentTimeZone( - appointment.endTime, appointment.endTimeZone, timeZone); - - if (appointment.recurrenceRule == null || - appointment.recurrenceRule == '') { - if (_isAppointmentWithinVisibleDateRange( - appointment, startDate, endDate)) { - return true; - } - - continue; - } - - if (appointment.startTime.isAfter(endDate)) { - continue; - } - - String rule = appointment.recurrenceRule; - if (!rule.contains('COUNT') && !rule.contains('UNTIL')) { - final DateFormat formatter = DateFormat('yyyyMMdd'); - final String newSubString = ';UNTIL=' + formatter.format(endDate); - rule = rule + newSubString; - } - - final List ruleSeparator = ['=', ';', ',']; - final List rRule = _splitRule(rule, ruleSeparator); - if (rRule.contains('UNTIL')) { - final String untilValue = rRule[rRule.indexOf('UNTIL') + 1]; - DateTime recurrenceEndDate = DateTime.parse(untilValue); - recurrenceEndDate = DateTime(recurrenceEndDate.year, - recurrenceEndDate.month, recurrenceEndDate.day, 23, 59, 59); - if (recurrenceEndDate.isBefore(startDate)) { - continue; - } - } - - final List recursiveDates = _getRecurrenceDateTimeCollection( - rule, appointment._actualStartTime, - recurrenceDuration: - appointment._actualEndTime.difference(appointment._actualStartTime), - specificStartDate: startDate, - specificEndDate: endDate); - - if (recursiveDates.isEmpty) { - continue; - } - - if (appointment.recurrenceExceptionDates == null || - appointment.recurrenceExceptionDates.isEmpty) { + static bool _isSpanned(CalendarAppointment appointment) { + return !(appointment.actualEndTime.day == appointment.actualStartTime.day && + appointment.actualEndTime.month == + appointment.actualStartTime.month && + appointment.actualEndTime.year == + appointment.actualStartTime.year) && + appointment.actualEndTime + .difference(appointment.actualStartTime) + .inDays > + 0; + } + + /// Check and returns whether the span icon can be added for the spanning + /// appointment. + static bool canAddSpanIcon(List visibleDates, + CalendarAppointment appointment, CalendarView view, + {DateTime? visibleStartDate, + DateTime? visibleEndDate, + bool? showTrailingLeadingDates}) { + final DateTime viewStartDate = convertToStartTime(visibleDates[0]); + final DateTime viewEndDate = + convertToEndTime(visibleDates[visibleDates.length - 1]); + final DateTime appStartTime = appointment.exactStartTime; + final DateTime appEndTime = appointment.exactEndTime; + + if (appStartTime.isBefore(viewStartDate) || + appEndTime.isAfter(viewEndDate)) { return true; } - for (int i = 0; i < appointment.recurrenceExceptionDates.length; i++) { - final DateTime exceptionDate = appointment.recurrenceExceptionDates[i]; - for (int k = 0; k < recursiveDates.length; k++) { - final DateTime recurrenceDate = recursiveDates[k]; - if (!isSameDate(recurrenceDate, exceptionDate)) { - return true; + switch (view) { + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + case CalendarView.schedule: + break; + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + { + return appEndTime.difference(appStartTime).inDays <= 0 && + appStartTime.day != appEndTime.day; } - } - } - } - - return false; -} + case CalendarView.month: + { + if (showTrailingLeadingDates != null && + !showTrailingLeadingDates && + (appStartTime.isBefore(visibleStartDate!) || + appEndTime.isAfter(visibleEndDate!))) { + return true; + } -bool _isSpanned(Appointment appointment) { - return !(appointment._actualEndTime.day == appointment._actualStartTime.day && - appointment._actualEndTime.month == - appointment._actualStartTime.month && - appointment._actualEndTime.year == - appointment._actualStartTime.year) && - appointment._actualEndTime - .difference(appointment._actualStartTime) - .inDays > - 0; -} + if (appStartTime.isAfter(viewStartDate)) { + final int appointmentStartWeek = + appStartTime.difference(viewStartDate).inDays ~/ + DateTime.daysPerWeek; + final int appointmentEndWeek = + appEndTime.difference(viewStartDate).inDays ~/ + DateTime.daysPerWeek; + return appointmentStartWeek != appointmentEndWeek; + } + } + } -/// Check and returns whether the span icon can be added for the spanning -/// appointment. -bool _canAddSpanIcon( - List visibleDates, Appointment appointment, CalendarView view, - {DateTime visibleStartDate, - DateTime visibleEndDate, - bool showTrailingLeadingDates}) { - final DateTime viewStartDate = _convertToStartTime(visibleDates[0]); - final DateTime viewEndDate = - _convertToEndTime(visibleDates[visibleDates.length - 1]); - final DateTime appStartTime = appointment._exactStartTime; - final DateTime appEndTime = appointment._exactEndTime; - if (viewStartDate == null || - viewEndDate == null || - appStartTime == null || - appEndTime == null) { return false; } - if (appStartTime.isBefore(viewStartDate) || appEndTime.isAfter(viewEndDate)) { - return true; - } - - switch (view) { - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - case CalendarView.schedule: - break; - case CalendarView.day: - case CalendarView.week: - case CalendarView.workWeek: - { - return appEndTime.difference(appStartTime).inDays <= 0 && - appStartTime.day != appEndTime.day; - } - case CalendarView.month: - { - if (showTrailingLeadingDates != null && - !showTrailingLeadingDates && - (appStartTime.isBefore(visibleStartDate) || - appEndTime.isAfter(visibleEndDate))) { - return true; - } + /// Returns recurrence icon details for appointment view. + static TextSpan getRecurrenceIcon(Color color, double textSize) { + final IconData recurrenceIconData = Icons.autorenew; + return TextSpan( + text: String.fromCharCode(recurrenceIconData.codePoint), + style: TextStyle( + color: color, + fontSize: textSize, + fontFamily: recurrenceIconData.fontFamily, + )); + } + + /// Calculate and returns the centered y position for the span icon in the + /// spanning appointment UI. + static double getYPositionForSpanIcon( + TextSpan icon, TextPainter textPainter, RRect rect) { + /// There is a space around the font, hence to get the start position we + /// must calculate the icon start position, apart from the space, and the + /// value 1.5 used since the space on top and bottom of icon is not even, + /// hence to rectify this tha value 1.5 used, and tested with multiple + /// device. + final int iconStartPosition = (textPainter.height - + (icon.style!.fontSize! * textPainter.textScaleFactor)) ~/ + 1.5; + return rect.top - + ((textPainter.height - rect.height) / 2) - + iconStartPosition; + } + + /// Returns the appointment text which will be displayed on spanning + /// appointments on day, timeline day, schedule and month agenda view. + /// The text will display the current date, and total dates of the spanning + /// appointment + static String getSpanAppointmentText(CalendarAppointment appointment, + DateTime date, SfLocalizations localization) { + final String totalDays = (convertToEndTime(appointment.exactEndTime) + .difference(convertToStartTime(appointment.exactStartTime)) + .inDays + + 1) + .toString(); + final String currentDate = (convertToEndTime(date) + .difference(convertToStartTime(appointment.exactStartTime)) + .inDays + + 1) + .toString(); + + return appointment.subject + + ' (' + + localization.daySpanCountLabel + + ' ' + + currentDate + + ' / ' + + totalDays + + ')'; + } + + /// Returns recurrence icon details for appointment view. + static TextSpan getSpanIcon(Color color, double textSize, bool isForward) { + /// Text size multiplied by two, to align the icon, since the icon itself + /// covered by some space in the top and bottom side, hence size multiplied. + return TextSpan( + text: isForward ? '\u00BB' : '\u00AB', + style: TextStyle( + color: color, + fontSize: textSize * 2, + fontFamily: 'Roboto', + )); + } + + /// Check and returns whether the forward icon can be added for the spanning + /// appointment. + static bool canAddForwardSpanIcon(DateTime appStartTime, DateTime appEndTime, + DateTime viewStartDate, DateTime viewEndDate) { + return isSameOrAfterDate(viewStartDate, appStartTime) && + appEndTime.isAfter(viewEndDate); + } + + /// Check and returns whether the backward icon can be added for the spanning + /// appointment. + static bool canAddBackwardSpanIcon(DateTime appStartTime, DateTime appEndTime, + DateTime viewStartDate, DateTime viewEndDate) { + return appStartTime.isBefore(viewStartDate) && + isSameOrBeforeDate(viewEndDate, appEndTime); + } + + /// Return appointment collection based on the date. + static List getSelectedDateAppointments( + List? appointments, + String? timeZone, + DateTime? date) { + final List appointmentCollection = + []; + if (appointments == null || appointments.isEmpty || date == null) { + return appointmentCollection; + } - if (appStartTime.isAfter(viewStartDate)) { - final int appointmentStartWeek = - appStartTime.difference(viewStartDate).inDays ~/ - _kNumberOfDaysInWeek; - final int appointmentEndWeek = - appEndTime.difference(viewStartDate).inDays ~/ - _kNumberOfDaysInWeek; - return appointmentStartWeek != appointmentEndWeek; + final DateTime startDate = convertToStartTime(date); + final DateTime endDate = convertToEndTime(date); + final int count = appointments.length; + + for (int j = 0; j < count; j++) { + final CalendarAppointment appointment = appointments[j]; + appointment.actualStartTime = convertTimeToAppointmentTimeZone( + appointment.startTime, appointment.startTimeZone, timeZone); + appointment.actualEndTime = convertTimeToAppointmentTimeZone( + appointment.endTime, appointment.endTimeZone, timeZone); + appointment.exactStartTime = appointment.actualStartTime; + appointment.exactEndTime = appointment.actualEndTime; + + if (appointment.recurrenceRule == null || + appointment.recurrenceRule == '') { + if (isAppointmentWithinVisibleDateRange( + appointment, startDate, endDate)) { + appointmentCollection.add(appointment); } - } - } - return false; -} - -TextSpan _getRecurrenceIcon(Color color, double textSize) { - final IconData recurrenceIconData = Icons.autorenew; - return TextSpan( - text: String.fromCharCode(recurrenceIconData.codePoint), - style: TextStyle( - color: color, - fontSize: textSize, - fontFamily: recurrenceIconData.fontFamily, - )); -} - -/// Calculate and returns the centered y yposition for the span icon in the -/// spanning appointment UI. -double _getYPositionForSpanIcon( - TextSpan icon, TextPainter textPainter, RRect rect) { - /// There is a space around the font, hence to get the start position we - /// must calculate the icon start position, apart from the space, and the - /// value 1.5 used since the space on top and bottom of icon is not even, - /// hence to rectify this tha value 1.5 used, and tested with multiple - /// device. - final int iconStartPosition = (textPainter.height - - (icon.style.fontSize * textPainter.textScaleFactor)) ~/ - 1.5; - return rect.top - - ((textPainter.height - rect.height) / 2) - - iconStartPosition; -} - -/// Returns the appointment text which will be displayed on spanning -/// appointments on day, timeline day, schedule and month agenda view. -/// The text will display the current date, and total dates of the spanning -/// appointment -String _getSpanAppointmentText(Appointment appointment, DateTime date) { - final String totalDays = (_convertToEndTime(appointment._exactEndTime) - .difference(_convertToStartTime(appointment._exactStartTime)) - .inDays + - 1) - .toString(); - final String currentDate = (_convertToEndTime(date) - .difference(_convertToStartTime(appointment._exactStartTime)) - .inDays + - 1) - .toString(); - - return appointment.subject + ' (Day ' + currentDate + ' / ' + totalDays + ')'; -} - -TextSpan _getSpanIcon(Color color, double textSize, bool isForward) { - /// Text size multiplied by two, to align the icon, since the icon itself - /// covered by some space in the top and bottom side, hence size multiplied. - return TextSpan( - text: isForward ? '\u00BB' : '\u00AB', - style: TextStyle( - color: color, - fontSize: textSize * 2, - fontFamily: 'Roboto', - )); -} - -/// Check and returns whether the forward icon can be added for the spanning -/// appointment. -bool _canAddForwardSpanIcon(DateTime appStartTime, DateTime appEndTime, - DateTime viewStartDate, DateTime viewEndDate) { - return appStartTime.isAfter(viewStartDate) && appEndTime.isAfter(viewEndDate); -} - -/// Check and returns whether the backward icon can be added for the spanning -/// appointment. -bool _canAddBackwardSpanIcon(DateTime appStartTime, DateTime appEndTime, - DateTime viewStartDate, DateTime viewEndDate) { - return appStartTime.isBefore(viewStartDate) && - appEndTime.isBefore(viewEndDate); -} - -List _getSelectedDateAppointments( - List appointments, String timeZone, DateTime date) { - final List appointmentCollection = []; - if (appointments == null || appointments.isEmpty || date == null) { - return []; - } - - final DateTime startDate = _convertToStartTime(date); - final DateTime endDate = _convertToEndTime(date); - int count = 0; - if (appointments != null) { - count = appointments.length; - } - - for (int j = 0; j < count; j++) { - final Appointment appointment = appointments[j]; - appointment._actualStartTime = _convertTimeToAppointmentTimeZone( - appointment.startTime, appointment.startTimeZone, timeZone); - appointment._actualEndTime = _convertTimeToAppointmentTimeZone( - appointment.endTime, appointment.endTimeZone, timeZone); - appointment._exactStartTime = appointment._actualStartTime; - appointment._exactEndTime = appointment._actualEndTime; - - if (appointment.recurrenceRule == null || - appointment.recurrenceRule == '') { - if (_isAppointmentWithinVisibleDateRange( - appointment, startDate, endDate)) { - appointmentCollection.add(appointment); + continue; } - continue; + _getRecurrenceAppointments( + appointment, appointmentCollection, startDate, endDate, timeZone); } - _getRecurrenceAppointments( - appointment, appointmentCollection, startDate, endDate, timeZone); - } - - return appointmentCollection; -} - -Appointment _copy(Appointment appointment) { - final Appointment copyAppointment = Appointment(); - copyAppointment.startTime = appointment.startTime; - copyAppointment.endTime = appointment.endTime; - copyAppointment.isAllDay = appointment.isAllDay; - copyAppointment.subject = appointment.subject; - copyAppointment.color = appointment.color; - copyAppointment._actualStartTime = appointment._actualStartTime; - copyAppointment._actualEndTime = appointment._actualEndTime; - copyAppointment.startTimeZone = appointment.startTimeZone; - copyAppointment.endTimeZone = appointment.endTimeZone; - copyAppointment.recurrenceRule = appointment.recurrenceRule; - copyAppointment.recurrenceExceptionDates = - appointment.recurrenceExceptionDates; - copyAppointment.notes = appointment.notes; - copyAppointment.location = appointment.location; - copyAppointment._isSpanned = appointment._isSpanned; - copyAppointment._data = appointment._data; - copyAppointment._exactStartTime = appointment._exactStartTime; - copyAppointment._exactEndTime = appointment._exactEndTime; - copyAppointment.resourceIds = appointment.resourceIds; - return copyAppointment; -} - -/// Returns the specific date appointment collection by filtering the -/// appointments from passed visible appointment collection. -List _getSpecificDateVisibleAppointment( - SfCalendar calendar, DateTime date, List visibleAppointments) { - final List appointmentCollection = []; - if (date == null || visibleAppointments == null) { return appointmentCollection; } - final DateTime startDate = _convertToStartTime(date); - final DateTime endDate = _convertToEndTime(date); - - for (int j = 0; j < visibleAppointments.length; j++) { - final Appointment appointment = visibleAppointments[j]; - if (_isAppointmentWithinVisibleDateRange(appointment, startDate, endDate)) { - appointmentCollection.add(appointment); + static CalendarAppointment _copy(CalendarAppointment appointment) { + final CalendarAppointment copyAppointment = CalendarAppointment( + startTime: appointment.startTime, + endTime: appointment.endTime, + isAllDay: appointment.isAllDay, + subject: appointment.subject, + color: appointment.color, + startTimeZone: appointment.startTimeZone, + endTimeZone: appointment.endTimeZone, + recurrenceRule: appointment.recurrenceRule, + recurrenceExceptionDates: appointment.recurrenceExceptionDates, + notes: appointment.notes, + location: appointment.location, + isSpanned: appointment.isSpanned, + resourceIds: appointment.resourceIds); + copyAppointment.actualStartTime = appointment.actualStartTime; + copyAppointment.actualEndTime = appointment.actualEndTime; + copyAppointment.data = appointment.data; + copyAppointment.exactStartTime = appointment.exactStartTime; + copyAppointment.exactEndTime = appointment.exactEndTime; + return copyAppointment; + } + + /// Check the appointment in between the visible date range. + static bool isAppointmentWithinVisibleDateRange( + CalendarAppointment appointment, + DateTime visibleStart, + DateTime visibleEnd) { + return isDateRangeWithinVisibleDateRange(appointment.actualStartTime, + appointment.actualEndTime, visibleStart, visibleEnd); + } + + /// Check the date range in between the visible date range. + static bool isDateRangeWithinVisibleDateRange(DateTime startDate, + DateTime endDate, DateTime visibleStart, DateTime visibleEnd) { + if (startDate.isAfter(visibleStart)) { + if (startDate.isBefore(visibleEnd)) { + return true; + } + } else if (startDate.day == visibleStart.day && + startDate.month == visibleStart.month && + startDate.year == visibleStart.year) { + return true; + } else if (isSameOrAfterDate(visibleStart, endDate)) { + return true; } - } - - return appointmentCollection; -} - -/// Check the appointment in between the visible date range. -bool _isAppointmentWithinVisibleDateRange( - Appointment appointment, DateTime visibleStart, DateTime visibleEnd) { - return _isDateRangeWithinVisibleDateRange(appointment._actualStartTime, - appointment._actualEndTime, visibleStart, visibleEnd); -} -/// Check the date range in between the visible date range. -bool _isDateRangeWithinVisibleDateRange(DateTime startDate, DateTime endDate, - DateTime visibleStart, DateTime visibleEnd) { - if (startDate == null || - endDate == null || - visibleStart == null || - visibleEnd == null) { return false; } - if (startDate.isAfter(visibleStart)) { - if (startDate.isBefore(visibleEnd)) { + static bool _isAppointmentInVisibleDateRange(CalendarAppointment appointment, + DateTime visibleStart, DateTime visibleEnd) { + final DateTime appStartTime = appointment.actualStartTime; + final DateTime appEndTime = appointment.actualEndTime; + if ((appStartTime.isAfter(visibleStart) || + (appStartTime.day == visibleStart.day && + appStartTime.month == visibleStart.month && + appStartTime.year == visibleStart.year)) && + (appStartTime.isBefore(visibleEnd) || + (appStartTime.day == visibleEnd.day && + appStartTime.month == visibleEnd.month && + appStartTime.year == visibleEnd.year)) && + (appEndTime.isAfter(visibleStart) || + (appEndTime.day == visibleStart.day && + appEndTime.month == visibleStart.month && + appEndTime.year == visibleStart.year)) && + (appEndTime.isBefore(visibleEnd) || + (appEndTime.day == visibleEnd.day && + appEndTime.month == visibleEnd.month && + appEndTime.year == visibleEnd.year))) { return true; } - } else if (startDate.day == visibleStart.day && - startDate.month == visibleStart.month && - startDate.year == visibleStart.year) { - return true; - } else if (endDate.isAfter(visibleStart)) { - return true; - } - return false; -} - -bool _isAppointmentInVisibleDateRange( - Appointment appointment, DateTime visibleStart, DateTime visibleEnd) { - final DateTime appStartTime = appointment._actualStartTime; - final DateTime appEndTime = appointment._actualEndTime; - if ((appStartTime.isAfter(visibleStart) || - (appStartTime.day == visibleStart.day && - appStartTime.month == visibleStart.month && - appStartTime.year == visibleStart.year)) && - (appStartTime.isBefore(visibleEnd) || - (appStartTime.day == visibleEnd.day && - appStartTime.month == visibleEnd.month && - appStartTime.year == visibleEnd.year)) && - (appEndTime.isAfter(visibleStart) || - (appEndTime.day == visibleStart.day && - appEndTime.month == visibleStart.month && - appEndTime.year == visibleStart.year)) && - (appEndTime.isBefore(visibleEnd) || - (appEndTime.day == visibleEnd.day && - appEndTime.month == visibleEnd.month && - appEndTime.year == visibleEnd.year))) { - return true; + return false; } - return false; -} - -/// Check and returns whether the appointment's start or end date falls within -/// the visible dates passed to the method. -bool _isAppointmentDateWithinVisibleDateRange( - DateTime appointmentDate, DateTime visibleStart, DateTime visibleEnd) { - if (appointmentDate.isAfter(visibleStart)) { - if (appointmentDate.isBefore(visibleEnd)) { + /// Check and returns whether the appointment's start or end date falls within + /// the visible dates passed to the method. + static bool _isAppointmentDateWithinVisibleDateRange( + DateTime appointmentDate, DateTime visibleStart, DateTime visibleEnd) { + if (appointmentDate.isAfter(visibleStart)) { + if (appointmentDate.isBefore(visibleEnd)) { + return true; + } + } else if (isSameDate(appointmentDate, visibleStart)) { + return true; + } else if (isSameDate(appointmentDate, visibleEnd)) { return true; } - } else if (isSameDate(appointmentDate, visibleStart)) { - return true; - } else if (isSameDate(appointmentDate, visibleEnd)) { - return true; - } - return false; -} - -Location _timeZoneInfoToOlsonTimeZone(String windowsTimeZoneId) { - final Map olsonWindowsTimes = {}; - olsonWindowsTimes['AUS Central Standard Time'] = 'Australia/Darwin'; - olsonWindowsTimes['AUS Eastern Standard Time'] = 'Australia/Sydney'; - olsonWindowsTimes['Afghanistan Standard Time'] = 'Asia/Kabul'; - olsonWindowsTimes['Alaskan Standard Time'] = 'America/Anchorage'; - olsonWindowsTimes['Arab Standard Time'] = 'Asia/Riyadh'; - olsonWindowsTimes['Arabian Standard Time'] = 'Indian/Reunion'; - olsonWindowsTimes['Arabic Standard Time'] = 'Asia/Baghdad'; - olsonWindowsTimes['Argentina Standard Time'] = - 'America/Argentina/Buenos_Aires'; - olsonWindowsTimes['Atlantic Standard Time'] = 'America/Halifax'; - olsonWindowsTimes['Azerbaijan Standard Time'] = 'Asia/Baku'; - olsonWindowsTimes['Azores Standard Time'] = 'Atlantic/Azores'; - olsonWindowsTimes['Bahia Standard Time'] = 'America/Bahia'; - olsonWindowsTimes['Bangladesh Standard Time'] = 'Asia/Dhaka'; - olsonWindowsTimes['Belarus Standard Time'] = 'Europe/Minsk'; - olsonWindowsTimes['Canada Central Standard Time'] = 'America/Regina'; - olsonWindowsTimes['Cape Verde Standard Time'] = 'Atlantic/Cape_Verde'; - olsonWindowsTimes['Caucasus Standard Time'] = 'Asia/Yerevan'; - olsonWindowsTimes['Cen. Australia Standard Time'] = 'Australia/Adelaide'; - olsonWindowsTimes['Central America Standard Time'] = 'America/Guatemala'; - olsonWindowsTimes['Central Asia Standard Time'] = 'Asia/Almaty'; - olsonWindowsTimes['Central Brazilian Standard Time'] = 'America/Cuiaba'; - olsonWindowsTimes['Central Europe Standard Time'] = 'Europe/Budapest'; - olsonWindowsTimes['Central European Standard Time'] = 'Europe/Warsaw'; - olsonWindowsTimes['Central Pacific Standard Time'] = 'Pacific/Guadalcanal'; - olsonWindowsTimes['Central Standard Time'] = 'America/Chicago'; - olsonWindowsTimes['China Standard Time'] = 'Asia/Shanghai'; - olsonWindowsTimes['Dateline Standard Time'] = 'Etc/GMT+12'; - olsonWindowsTimes['E. Africa Standard Time'] = 'Africa/Nairobi'; - olsonWindowsTimes['E. Australia Standard Time'] = 'Australia/Brisbane'; - olsonWindowsTimes['E. South America Standard Time'] = 'America/Sao_Paulo'; - olsonWindowsTimes['Eastern Standard Time'] = 'America/New_York'; - olsonWindowsTimes['Egypt Standard Time'] = 'Africa/Cairo'; - olsonWindowsTimes['Ekaterinburg Standard Time'] = 'Asia/Yekaterinburg'; - olsonWindowsTimes['FLE Standard Time'] = 'Europe/Kiev'; - olsonWindowsTimes['Fiji Standard Time'] = 'Pacific/Fiji'; - olsonWindowsTimes['GMT Standard Time'] = 'Europe/London'; - olsonWindowsTimes['GTB Standard Time'] = 'Europe/Bucharest'; - olsonWindowsTimes['Georgian Standard Time'] = 'Asia/Tbilisi'; - olsonWindowsTimes['Greenland Standard Time'] = 'America/Godthab'; - olsonWindowsTimes['Greenwich Standard Time'] = 'Atlantic/Reykjavik'; - olsonWindowsTimes['Hawaiian Standard Time'] = 'Pacific/Honolulu'; - olsonWindowsTimes['India Standard Time'] = 'Asia/Kolkata'; - olsonWindowsTimes['Iran Standard Time'] = 'Asia/Tehran'; - olsonWindowsTimes['Israel Standard Time'] = 'Asia/Jerusalem'; - olsonWindowsTimes['Jordan Standard Time'] = 'Asia/Amman'; - olsonWindowsTimes['Kaliningrad Standard Time'] = 'Europe/Kaliningrad'; - olsonWindowsTimes['Korea Standard Time'] = 'Asia/Seoul'; - olsonWindowsTimes['Libya Standard Time'] = 'Africa/Tripoli'; - olsonWindowsTimes['Line Islands Standard Time'] = 'Pacific/Kiritimati'; - olsonWindowsTimes['Magadan Standard Time'] = 'Asia/Magadan'; - olsonWindowsTimes['Mauritius Standard Time'] = 'Indian/Mauritius'; - olsonWindowsTimes['Middle East Standard Time'] = 'Asia/Beirut'; - olsonWindowsTimes['Montevideo Standard Time'] = 'America/Montevideo'; - olsonWindowsTimes['Morocco Standard Time'] = 'Africa/Casablanca'; - olsonWindowsTimes['Mountain Standard Time'] = 'America/Denver'; - olsonWindowsTimes['Mountain Standard Time (Mexico)'] = 'America/Chihuahua'; - olsonWindowsTimes['Myanmar Standard Time'] = 'Asia/Rangoon'; - olsonWindowsTimes['N. Central Asia Standard Time'] = 'Asia/Novosibirsk'; - olsonWindowsTimes['Namibia Standard Time'] = 'Africa/Windhoek'; - olsonWindowsTimes['Nepal Standard Time'] = 'Asia/Kathmandu'; - olsonWindowsTimes['New Zealand Standard Time'] = 'Pacific/Auckland'; - olsonWindowsTimes['Newfoundland Standard Time'] = 'America/St_Johns'; - olsonWindowsTimes['North Asia East Standard Time'] = 'Asia/Irkutsk'; - olsonWindowsTimes['North Asia Standard Time'] = 'Asia/Krasnoyarsk'; - olsonWindowsTimes['Pacific SA Standard Time'] = 'America/Santiago'; - olsonWindowsTimes['Pacific Standard Time'] = 'America/Los_Angeles'; - olsonWindowsTimes['Pacific Standard Time (Mexico)'] = 'America/Santa_Isabel'; - olsonWindowsTimes['Pakistan Standard Time'] = 'Asia/Karachi'; - olsonWindowsTimes['Paraguay Standard Time'] = 'America/Asuncion'; - olsonWindowsTimes['Romance Standard Time'] = 'Europe/Paris'; - olsonWindowsTimes['Russia Time Zone 10'] = 'Asia/Srednekolymsk'; - olsonWindowsTimes['Russia Time Zone 11'] = 'Asia/Kamchatka'; - olsonWindowsTimes['Russia Time Zone 3'] = 'Europe/Samara'; - olsonWindowsTimes['Russian Standard Time'] = 'Europe/Moscow'; - olsonWindowsTimes['SA Eastern Standard Time'] = 'America/Cayenne'; - olsonWindowsTimes['SA Pacific Standard Time'] = 'America/Bogota'; - olsonWindowsTimes['SA Western Standard Time'] = 'America/La_Paz'; - olsonWindowsTimes['SE Asia Standard Time'] = 'Asia/Bangkok'; - olsonWindowsTimes['Samoa Standard Time'] = 'Pacific/Apia'; - olsonWindowsTimes['Singapore Standard Time'] = 'Asia/Singapore'; - olsonWindowsTimes['South Africa Standard Time'] = 'Africa/Johannesburg'; - olsonWindowsTimes['Sri Lanka Standard Time'] = 'Asia/Colombo'; - olsonWindowsTimes['Syria Standard Time'] = 'Asia/Damascus'; - olsonWindowsTimes['Taipei Standard Time'] = 'Asia/Taipei'; - olsonWindowsTimes['Tasmania Standard Time'] = 'Australia/Hobart'; - olsonWindowsTimes['Tokyo Standard Time'] = 'Asia/Tokyo'; - olsonWindowsTimes['Tonga Standard Time'] = 'Pacific/Tongatapu'; - olsonWindowsTimes['Turkey Standard Time'] = 'Europe/Istanbul'; - olsonWindowsTimes['US Eastern Standard Time'] = - 'America/Indiana/Indianapolis'; - olsonWindowsTimes['US Mountain Standard Time'] = 'America/Phoenix'; - olsonWindowsTimes['UTC'] = 'America/Danmarkshavn'; - olsonWindowsTimes['UTC+12'] = 'Pacific/Tarawa'; - olsonWindowsTimes['UTC-02'] = 'America/Noronha'; - olsonWindowsTimes['UTC-11'] = 'Pacific/Midway'; - olsonWindowsTimes['Ulaanbaatar Standard Time'] = 'Asia/Ulaanbaatar'; - olsonWindowsTimes['Venezuela Standard Time'] = 'America/Caracas'; - olsonWindowsTimes['Vladivostok Standard Time'] = 'Asia/Vladivostok'; - olsonWindowsTimes['W. Australia Standard Time'] = 'Australia/Perth'; - olsonWindowsTimes['W. Central Africa Standard Time'] = 'Africa/Lagos'; - olsonWindowsTimes['W. Europe Standard Time'] = 'Europe/Berlin'; - olsonWindowsTimes['West Asia Standard Time'] = 'Asia/Tashkent'; - olsonWindowsTimes['West Pacific Standard Time'] = 'Pacific/Port_Moresby'; - olsonWindowsTimes['Yakutsk Standard Time'] = 'Asia/Yakutsk'; - - if (olsonWindowsTimes.containsKey(windowsTimeZoneId)) { - final String timeZone = olsonWindowsTimes[windowsTimeZoneId]; - return getLocation(timeZone); - } else { - return getLocation(windowsTimeZoneId); + return false; } -} -bool _isDateTimeEqual(DateTime date1, DateTime date2) { - if (date1.year == date2.year && - date1.month == date2.month && - date1.day == date2.day && - date1.hour == date2.hour && - date1.minute == date2.minute) { - return true; + static Location _timeZoneInfoToOlsonTimeZone(String windowsTimeZoneId) { + final Map olsonWindowsTimes = {}; + olsonWindowsTimes['AUS Central Standard Time'] = 'Australia/Darwin'; + olsonWindowsTimes['AUS Eastern Standard Time'] = 'Australia/Sydney'; + olsonWindowsTimes['Afghanistan Standard Time'] = 'Asia/Kabul'; + olsonWindowsTimes['Alaskan Standard Time'] = 'America/Anchorage'; + olsonWindowsTimes['Arab Standard Time'] = 'Asia/Riyadh'; + olsonWindowsTimes['Arabian Standard Time'] = 'Indian/Reunion'; + olsonWindowsTimes['Arabic Standard Time'] = 'Asia/Baghdad'; + olsonWindowsTimes['Argentina Standard Time'] = + 'America/Argentina/Buenos_Aires'; + olsonWindowsTimes['Atlantic Standard Time'] = 'America/Halifax'; + olsonWindowsTimes['Azerbaijan Standard Time'] = 'Asia/Baku'; + olsonWindowsTimes['Azores Standard Time'] = 'Atlantic/Azores'; + olsonWindowsTimes['Bahia Standard Time'] = 'America/Bahia'; + olsonWindowsTimes['Bangladesh Standard Time'] = 'Asia/Dhaka'; + olsonWindowsTimes['Belarus Standard Time'] = 'Europe/Minsk'; + olsonWindowsTimes['Canada Central Standard Time'] = 'America/Regina'; + olsonWindowsTimes['Cape Verde Standard Time'] = 'Atlantic/Cape_Verde'; + olsonWindowsTimes['Caucasus Standard Time'] = 'Asia/Yerevan'; + olsonWindowsTimes['Cen. Australia Standard Time'] = 'Australia/Adelaide'; + olsonWindowsTimes['Central America Standard Time'] = 'America/Guatemala'; + olsonWindowsTimes['Central Asia Standard Time'] = 'Asia/Almaty'; + olsonWindowsTimes['Central Brazilian Standard Time'] = 'America/Cuiaba'; + olsonWindowsTimes['Central Europe Standard Time'] = 'Europe/Budapest'; + olsonWindowsTimes['Central European Standard Time'] = 'Europe/Warsaw'; + olsonWindowsTimes['Central Pacific Standard Time'] = 'Pacific/Guadalcanal'; + olsonWindowsTimes['Central Standard Time'] = 'America/Chicago'; + olsonWindowsTimes['China Standard Time'] = 'Asia/Shanghai'; + olsonWindowsTimes['Dateline Standard Time'] = 'Etc/GMT+12'; + olsonWindowsTimes['E. Africa Standard Time'] = 'Africa/Nairobi'; + olsonWindowsTimes['E. Australia Standard Time'] = 'Australia/Brisbane'; + olsonWindowsTimes['E. South America Standard Time'] = 'America/Sao_Paulo'; + olsonWindowsTimes['Eastern Standard Time'] = 'America/New_York'; + olsonWindowsTimes['Egypt Standard Time'] = 'Africa/Cairo'; + olsonWindowsTimes['Ekaterinburg Standard Time'] = 'Asia/Yekaterinburg'; + olsonWindowsTimes['FLE Standard Time'] = 'Europe/Kiev'; + olsonWindowsTimes['Fiji Standard Time'] = 'Pacific/Fiji'; + olsonWindowsTimes['GMT Standard Time'] = 'Europe/London'; + olsonWindowsTimes['GTB Standard Time'] = 'Europe/Bucharest'; + olsonWindowsTimes['Georgian Standard Time'] = 'Asia/Tbilisi'; + olsonWindowsTimes['Greenland Standard Time'] = 'America/Godthab'; + olsonWindowsTimes['Greenwich Standard Time'] = 'Atlantic/Reykjavik'; + olsonWindowsTimes['Hawaiian Standard Time'] = 'Pacific/Honolulu'; + olsonWindowsTimes['India Standard Time'] = 'Asia/Kolkata'; + olsonWindowsTimes['Iran Standard Time'] = 'Asia/Tehran'; + olsonWindowsTimes['Israel Standard Time'] = 'Asia/Jerusalem'; + olsonWindowsTimes['Jordan Standard Time'] = 'Asia/Amman'; + olsonWindowsTimes['Kaliningrad Standard Time'] = 'Europe/Kaliningrad'; + olsonWindowsTimes['Korea Standard Time'] = 'Asia/Seoul'; + olsonWindowsTimes['Libya Standard Time'] = 'Africa/Tripoli'; + olsonWindowsTimes['Line Islands Standard Time'] = 'Pacific/Kiritimati'; + olsonWindowsTimes['Magadan Standard Time'] = 'Asia/Magadan'; + olsonWindowsTimes['Mauritius Standard Time'] = 'Indian/Mauritius'; + olsonWindowsTimes['Middle East Standard Time'] = 'Asia/Beirut'; + olsonWindowsTimes['Montevideo Standard Time'] = 'America/Montevideo'; + olsonWindowsTimes['Morocco Standard Time'] = 'Africa/Casablanca'; + olsonWindowsTimes['Mountain Standard Time'] = 'America/Denver'; + olsonWindowsTimes['Mountain Standard Time (Mexico)'] = 'America/Chihuahua'; + olsonWindowsTimes['Myanmar Standard Time'] = 'Asia/Rangoon'; + olsonWindowsTimes['N. Central Asia Standard Time'] = 'Asia/Novosibirsk'; + olsonWindowsTimes['Namibia Standard Time'] = 'Africa/Windhoek'; + olsonWindowsTimes['Nepal Standard Time'] = 'Asia/Kathmandu'; + olsonWindowsTimes['New Zealand Standard Time'] = 'Pacific/Auckland'; + olsonWindowsTimes['Newfoundland Standard Time'] = 'America/St_Johns'; + olsonWindowsTimes['North Asia East Standard Time'] = 'Asia/Irkutsk'; + olsonWindowsTimes['North Asia Standard Time'] = 'Asia/Krasnoyarsk'; + olsonWindowsTimes['Pacific SA Standard Time'] = 'America/Santiago'; + olsonWindowsTimes['Pacific Standard Time'] = 'America/Los_Angeles'; + olsonWindowsTimes['Pacific Standard Time (Mexico)'] = + 'America/Santa_Isabel'; + olsonWindowsTimes['Pakistan Standard Time'] = 'Asia/Karachi'; + olsonWindowsTimes['Paraguay Standard Time'] = 'America/Asuncion'; + olsonWindowsTimes['Romance Standard Time'] = 'Europe/Paris'; + olsonWindowsTimes['Russia Time Zone 10'] = 'Asia/Srednekolymsk'; + olsonWindowsTimes['Russia Time Zone 11'] = 'Asia/Kamchatka'; + olsonWindowsTimes['Russia Time Zone 3'] = 'Europe/Samara'; + olsonWindowsTimes['Russian Standard Time'] = 'Europe/Moscow'; + olsonWindowsTimes['SA Eastern Standard Time'] = 'America/Cayenne'; + olsonWindowsTimes['SA Pacific Standard Time'] = 'America/Bogota'; + olsonWindowsTimes['SA Western Standard Time'] = 'America/La_Paz'; + olsonWindowsTimes['SE Asia Standard Time'] = 'Asia/Bangkok'; + olsonWindowsTimes['Samoa Standard Time'] = 'Pacific/Apia'; + olsonWindowsTimes['Singapore Standard Time'] = 'Asia/Singapore'; + olsonWindowsTimes['South Africa Standard Time'] = 'Africa/Johannesburg'; + olsonWindowsTimes['Sri Lanka Standard Time'] = 'Asia/Colombo'; + olsonWindowsTimes['Syria Standard Time'] = 'Asia/Damascus'; + olsonWindowsTimes['Taipei Standard Time'] = 'Asia/Taipei'; + olsonWindowsTimes['Tasmania Standard Time'] = 'Australia/Hobart'; + olsonWindowsTimes['Tokyo Standard Time'] = 'Asia/Tokyo'; + olsonWindowsTimes['Tonga Standard Time'] = 'Pacific/Tongatapu'; + olsonWindowsTimes['Turkey Standard Time'] = 'Europe/Istanbul'; + olsonWindowsTimes['US Eastern Standard Time'] = + 'America/Indiana/Indianapolis'; + olsonWindowsTimes['US Mountain Standard Time'] = 'America/Phoenix'; + olsonWindowsTimes['UTC'] = 'America/Danmarkshavn'; + olsonWindowsTimes['UTC+12'] = 'Pacific/Tarawa'; + olsonWindowsTimes['UTC-02'] = 'America/Noronha'; + olsonWindowsTimes['UTC-11'] = 'Pacific/Midway'; + olsonWindowsTimes['Ulaanbaatar Standard Time'] = 'Asia/Ulaanbaatar'; + olsonWindowsTimes['Venezuela Standard Time'] = 'America/Caracas'; + olsonWindowsTimes['Vladivostok Standard Time'] = 'Asia/Vladivostok'; + olsonWindowsTimes['W. Australia Standard Time'] = 'Australia/Perth'; + olsonWindowsTimes['W. Central Africa Standard Time'] = 'Africa/Lagos'; + olsonWindowsTimes['W. Europe Standard Time'] = 'Europe/Berlin'; + olsonWindowsTimes['West Asia Standard Time'] = 'Asia/Tashkent'; + olsonWindowsTimes['West Pacific Standard Time'] = 'Pacific/Port_Moresby'; + olsonWindowsTimes['Yakutsk Standard Time'] = 'Asia/Yakutsk'; + + final String? timeZone = olsonWindowsTimes[windowsTimeZoneId]; + if (timeZone != null) { + return getLocation(timeZone); + } else { + return getLocation(windowsTimeZoneId); + } } - return false; -} - -void _resetAppointmentView(List<_AppointmentView> _appointmentCollection) { - for (int i = 0; i < _appointmentCollection.length; i++) { - final _AppointmentView obj = _appointmentCollection[i]; - obj.canReuse = true; - obj.appointment = null; - obj.position = -1; - obj.startIndex = -1; - obj.endIndex = -1; - obj.maxPositions = -1; - obj.appointmentRect = null; + /// Resets the appointment views used on appointment layout rendering. + static void resetAppointmentView( + List _appointmentCollection) { + for (int i = 0; i < _appointmentCollection.length; i++) { + final AppointmentView obj = _appointmentCollection[i]; + obj.canReuse = true; + obj.appointment = null; + obj.position = -1; + obj.startIndex = -1; + obj.endIndex = -1; + obj.maxPositions = -1; + obj.appointmentRect = null; + } } -} -/// Returns the position for the time passed, based on the timeinterval height. -double _timeToPosition( - SfCalendar calendar, DateTime date, double timeIntervalHeight) { - final double singleIntervalHeightForAnHour = - ((60 / _getTimeInterval(calendar.timeSlotViewSettings)) * - timeIntervalHeight) - .toDouble(); - final int hour = date.hour; - final int minute = date.minute; - final int seconds = date.second; - double startHour = 0; - if (calendar.timeSlotViewSettings != null) { - startHour = calendar.timeSlotViewSettings.startHour; + /// Returns the position from time passed, based on the time interval height. + static double timeToPosition( + SfCalendar calendar, DateTime date, double timeIntervalHeight) { + final double singleIntervalHeightForAnHour = ((60 / + CalendarViewHelper.getTimeInterval( + calendar.timeSlotViewSettings)) * + timeIntervalHeight) + .toDouble(); + final int hour = date.hour; + final int minute = date.minute; + final int seconds = date.second; + final double startHour = calendar.timeSlotViewSettings.startHour; + + return ((hour + (minute / 60).toDouble() + (seconds / 3600).toDouble()) * + singleIntervalHeightForAnHour) - + (startHour * singleIntervalHeightForAnHour).toDouble(); + } + + /// Returns the appointment height from the duration passed. + static double getAppointmentHeightFromDuration(Duration? minimumDuration, + SfCalendar calendar, double timeIntervalHeight) { + if (minimumDuration == null || minimumDuration.inMinutes <= 0) { + return 0; + } + + final double hourHeight = ((60 / + CalendarViewHelper.getTimeInterval( + calendar.timeSlotViewSettings)) * + timeIntervalHeight) + .toDouble(); + return minimumDuration.inMinutes * (hourHeight / 60); + } + + /// Returns the minimum height for the appointment view passed, to render the + /// appointment view within this height. + static double _getAppointmentMinHeight( + SfCalendar calendar, AppointmentView appView, double timeIntervalHeight) { + double minHeight; + + // Appointment Default Bottom Position without considering MinHeight + final double defaultAppHeight = timeToPosition( + calendar, appView.appointment!.actualEndTime, timeIntervalHeight) - + timeToPosition( + calendar, appView.appointment!.actualStartTime, timeIntervalHeight); + + minHeight = getAppointmentHeightFromDuration( + calendar.timeSlotViewSettings.minimumAppointmentDuration, + calendar, + timeIntervalHeight); + + // Appointment Default Bottom Position - Default value as double.NaN + if (minHeight == 0) { + return defaultAppHeight; + } else if ((minHeight < defaultAppHeight) || + (timeIntervalHeight < defaultAppHeight)) { + // Appointment Minimum Height is smaller than default Appointment Height + // Appointment default Height is greater than TimeIntervalHeight + return defaultAppHeight; + } else if (minHeight > timeIntervalHeight) { + // Appointment Minimum Height is greater than Interval Height + return timeIntervalHeight; + } else { + // Appointment with proper MinHeight and within Interval + return minHeight; //appView.Appointment.MinHeight; + } } - return ((hour + (minute / 60).toDouble() + (seconds / 3600).toDouble()) * - singleIntervalHeightForAnHour) - - (startHour * singleIntervalHeightForAnHour).toDouble(); -} + static bool _isIntersectingAppointmentInDayView( + SfCalendar calendar, + CalendarView view, + CalendarAppointment currentApp, + AppointmentView appView, + CalendarAppointment appointment, + bool isAllDay, + double timeIntervalHeight) { + if (currentApp == appointment) { + return false; + } -/// Returns the appointment height from the duration passed. -double _getAppointmentHeightFromDuration( - Duration minimumDuration, SfCalendar calendar, double timeIntervalHeight) { - if (minimumDuration == null || minimumDuration.inMinutes <= 0) { - return 0; - } + if (currentApp.actualStartTime.isBefore(appointment.actualEndTime) && + currentApp.actualStartTime.isAfter(appointment.actualStartTime)) { + return true; + } - final double hourHeight = - ((60 / _getTimeInterval(calendar.timeSlotViewSettings)) * - timeIntervalHeight) - .toDouble(); - return minimumDuration.inMinutes * (hourHeight / 60); -} + if (currentApp.actualEndTime.isAfter(appointment.actualStartTime) && + currentApp.actualEndTime.isBefore(appointment.actualEndTime)) { + return true; + } -/// Returns the minimum height for the appointment view passed, to render the -/// appointment view within this height. -double _getAppointmentMinHeight( - SfCalendar calendar, _AppointmentView appView, double timeIntervalHeight) { - double minHeight; - - // Appointment Default Bottom Position without considering MinHeight - final double defaultAppHeight = _timeToPosition( - calendar, appView.appointment._actualEndTime, timeIntervalHeight) - - _timeToPosition( - calendar, appView.appointment._actualStartTime, timeIntervalHeight); - - minHeight = _getAppointmentHeightFromDuration( - calendar.timeSlotViewSettings.minimumAppointmentDuration, - calendar, - timeIntervalHeight); - - // Appointment Default Bottom Position - Default value as double.NaN - if (minHeight == 0) { - return defaultAppHeight; - } else if ((minHeight < defaultAppHeight) || - (timeIntervalHeight < defaultAppHeight)) { - // Appointment Minimum Height is smaller than default Appointment Height - // Appointment default Height is greater than TimeIntervalHeight - return defaultAppHeight; - } else if (minHeight > timeIntervalHeight) { - // Appointment Minimum Height is greater than Interval Height - return timeIntervalHeight; - } else { - // Appointment with proper MinHeight and within Interval - return minHeight; //appView.Appointment.MinHeight; - } -} + if (currentApp.actualEndTime.isAfter(appointment.actualEndTime) && + currentApp.actualStartTime.isBefore(appointment.actualStartTime)) { + return true; + } -bool _isIntersectingAppointmentInDayView( - SfCalendar calendar, - CalendarView view, - Appointment currentApp, - _AppointmentView appView, - Appointment appointment, - bool isAllDay, - double timeIntervalHeight) { - if (currentApp == appointment) { - return false; - } + if (CalendarViewHelper.isSameTimeSlot( + currentApp.actualStartTime, appointment.actualStartTime) || + CalendarViewHelper.isSameTimeSlot( + currentApp.actualEndTime, appointment.actualEndTime)) { + return true; + } - if (currentApp._actualStartTime.isBefore(appointment._actualEndTime) && - currentApp._actualStartTime.isAfter(appointment._actualStartTime)) { - return true; - } + if (isAllDay) { + return false; + } - if (currentApp._actualEndTime.isAfter(appointment._actualStartTime) && - currentApp._actualEndTime.isBefore(appointment._actualEndTime)) { - return true; - } + /// For timeline month view, the intercepting appointments muse be + /// calculated based on the date instead of the time, hence added this + /// condition and returned that it's a intercept appointment or not. + if (view == CalendarView.timelineMonth) { + return isSameDate( + currentApp.actualStartTime, appointment.actualStartTime) || + isSameDate(currentApp.actualEndTime, appointment.actualEndTime); + } - if (currentApp._actualEndTime.isAfter(appointment._actualEndTime) && - currentApp._actualStartTime.isBefore(appointment._actualStartTime)) { - return true; - } + // Intersecting appointments by comparing appointments MinHeight instead of + // Start and EndTime + if (calendar.timeSlotViewSettings.minimumAppointmentDuration != null && + calendar.timeSlotViewSettings.minimumAppointmentDuration!.inMinutes > + 0 && + view != CalendarView.timelineMonth) { + // Comparing appointments rendered in different dates + if (!isSameDate( + currentApp.actualStartTime, appointment.actualStartTime)) { + return false; + } - if (_isDateTimeEqual( - currentApp._actualStartTime, appointment._actualStartTime) || - _isDateTimeEqual(currentApp._actualEndTime, appointment._actualEndTime)) { - return true; - } + // Comparing appointments rendered in the same date + final double appTopPos = timeToPosition( + calendar, appointment.actualStartTime, timeIntervalHeight); + final double currentAppTopPos = timeToPosition( + calendar, currentApp.actualStartTime, timeIntervalHeight); + final double appHeight = + _getAppointmentMinHeight(calendar, appView, timeIntervalHeight); + // Height difference between previous and current appointment from top + // position + final double heightDiff = currentAppTopPos - appTopPos; + if (appTopPos != currentAppTopPos && appHeight > heightDiff) { + return true; + } + } - if (isAllDay) { return false; } - /// For timeline month view, the intercepting appointments muse be calculated - /// based on the date instead of the time, hence added this condition and - /// returned that it's a intercept appointment or not. - if (view == CalendarView.timelineMonth) { - return isSameDate( - currentApp._actualStartTime, appointment._actualStartTime) || - isSameDate(currentApp._actualEndTime, appointment._actualEndTime); - } + static bool _iterateAppointment( + CalendarAppointment app, bool isTimeline, bool isAllDay) { + if (isAllDay) { + if (!isTimeline && app.isAllDay) { + app.actualEndTime = convertToEndTime(app.actualEndTime); + app.actualStartTime = convertToStartTime(app.actualStartTime); + return true; + } else if (!isTimeline && _isSpanned(app)) { + return true; + } - // Intersecting appointments by comparing appointments MinHeight instead of - // Start and EndTime - if (calendar.timeSlotViewSettings.minimumAppointmentDuration != null && - calendar.timeSlotViewSettings.minimumAppointmentDuration.inMinutes > 0 && - view != CalendarView.timelineMonth) { - // Comparing appointments rendered in different dates - if (!isSameDate( - currentApp._actualStartTime, appointment._actualStartTime)) { return false; } - // Comparing appointments rendered in the same date - final double appTopPos = _timeToPosition( - calendar, appointment._actualStartTime, timeIntervalHeight); - final double currentAppTopPos = _timeToPosition( - calendar, currentApp._actualStartTime, timeIntervalHeight); - final double appHeight = - _getAppointmentMinHeight(calendar, appView, timeIntervalHeight); - // Height difference between previous and current appointment from top - // position - final double heightDiff = currentAppTopPos - appTopPos; - if (appTopPos != currentAppTopPos && appHeight > heightDiff) { - return true; + if ((app.isAllDay || _isSpanned(app)) && !isTimeline) { + return false; } - } - return false; -} + if (isTimeline && app.isAllDay) { + app.actualEndTime = convertToEndTime(app.actualEndTime); + app.actualStartTime = convertToStartTime(app.actualStartTime); + } -_AppointmentView _getAppointmentOnPosition( - _AppointmentView currentView, List<_AppointmentView> views) { - if (currentView == null || - currentView.appointment == null || - views == null || - views.isEmpty) { - return null; + return true; } - for (final _AppointmentView view in views) { - if (view.position == currentView.position && view != currentView) { - return view; + static int _orderAppointmentsDescending(bool value, bool value1) { + int boolValue1 = -1; + int boolValue2 = -1; + if (value) { + boolValue1 = 1; } - } - return null; -} - -bool _iterateAppointment(Appointment app, CalendarView view, bool isAllDay) { - final bool isTimeline = _isTimelineView(view); - if (isAllDay) { - if (!isTimeline && app.isAllDay) { - app._actualEndTime = _convertToEndTime(app._actualEndTime); - app._actualStartTime = _convertToStartTime(app._actualStartTime); - return true; - } else if (!isTimeline && _isSpanned(app)) { - return true; + if (value1) { + boolValue2 = 1; } - return false; - } - - if ((app.isAllDay || _isSpanned(app)) && !isTimeline) { - return false; - } - - if (isTimeline && app.isAllDay) { - app._actualEndTime = _convertToEndTime(app._actualEndTime); - app._actualStartTime = _convertToStartTime(app._actualStartTime); + return boolValue1.compareTo(boolValue2); } - return true; -} + /// Compare both boolean values used on appointment sorting. + static int orderAppointmentsAscending(bool value, bool value1) { + int boolValue1 = 1; + int boolValue2 = 1; + if (value) { + boolValue1 = -1; + } -int _orderAppointmentsDescending(bool value, bool value1) { - int boolValue1 = -1; - int boolValue2 = -1; - if (value) { - boolValue1 = 1; - } + if (value1) { + boolValue2 = -1; + } - if (value1) { - boolValue2 = 1; + return boolValue1.compareTo(boolValue2); } - return boolValue1.compareTo(boolValue2); -} - -int _orderAppointmentsAscending(bool value, bool value1) { - int boolValue1 = 1; - int boolValue2 = 1; - if (value) { - boolValue1 = -1; - } + static AppointmentView _getAppointmentView(CalendarAppointment appointment, + List appointmentCollection, int? resourceIndex) { + AppointmentView? appointmentRenderer; + for (int i = 0; i < appointmentCollection.length; i++) { + final AppointmentView view = appointmentCollection[i]; + if (view.appointment == null) { + appointmentRenderer = view; + break; + } + } - if (value1) { - boolValue2 = -1; - } + if (appointmentRenderer == null) { + appointmentRenderer = AppointmentView(); + appointmentRenderer.appointment = appointment; + appointmentRenderer.canReuse = false; + appointmentRenderer.resourceIndex = resourceIndex ?? -1; + appointmentCollection.add(appointmentRenderer); + } - return boolValue1.compareTo(boolValue2); -} + appointmentRenderer.appointment = appointment; + appointmentRenderer.canReuse = false; + appointmentRenderer.resourceIndex = resourceIndex ?? -1; + return appointmentRenderer; + } + + /// Update the appointment view collection position and its max position + /// details. + static void setAppointmentPositionAndMaxPosition( + List appointmentCollection, + SfCalendar calendar, + CalendarView view, + List visibleAppointments, + bool isAllDay, + double timeIntervalHeight, + [int? resourceIndex]) { + final bool isTimeline = CalendarViewHelper.isTimelineView(view); + final List normalAppointments = visibleAppointments + .where((CalendarAppointment app) => + _iterateAppointment(app, isTimeline, isAllDay)) + .toList(); + normalAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime)); + if (!isTimeline) { + normalAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + _orderAppointmentsDescending(app1.isSpanned, app2.isSpanned)); + normalAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + _orderAppointmentsDescending(app1.isAllDay, app2.isAllDay)); + } else { + normalAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + orderAppointmentsAscending(app1.isAllDay, app2.isAllDay)); + normalAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + orderAppointmentsAscending(app1.isSpanned, app2.isSpanned)); + } -void _setAppointmentPositionAndMaxPosition( - List<_AppointmentView> appointmentCollection, - SfCalendar calendar, - CalendarView view, - List visibleAppointments, - bool isAllDay, - double timeIntervalHeight, - [int resourceIndex]) { - if (visibleAppointments == null) { - return; - } + final Map> dict = >{}; + final List processedViews = []; + int maxColsCount = 1; + + for (int count = 0; count < normalAppointments.length; count++) { + final CalendarAppointment currentAppointment = normalAppointments[count]; + if ((view == CalendarView.workWeek || + view == CalendarView.timelineWorkWeek) && + calendar.timeSlotViewSettings.nonWorkingDays + .contains(currentAppointment.actualStartTime.weekday) && + calendar.timeSlotViewSettings.nonWorkingDays + .contains(currentAppointment.actualEndTime.weekday)) { + continue; + } - final List normalAppointments = visibleAppointments - .where((Appointment app) => _iterateAppointment(app, view, isAllDay)) - .toList(); - normalAppointments.sort((Appointment app1, Appointment app2) => - app1._actualStartTime.compareTo(app2._actualStartTime)); - if (!_isTimelineView(view)) { - normalAppointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsDescending(app1._isSpanned, app2._isSpanned)); - normalAppointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsDescending(app1.isAllDay, app2.isAllDay)); - } else { - normalAppointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1.isAllDay, app2.isAllDay)); - normalAppointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1._isSpanned, app2._isSpanned)); - } + List? intersectingApps; + final AppointmentView currentAppView = _getAppointmentView( + currentAppointment, appointmentCollection, resourceIndex); - final Map> dict = >{}; - final List<_AppointmentView> processedViews = <_AppointmentView>[]; - int maxColsCount = 1; - - for (int count = 0; count < normalAppointments.length; count++) { - final Appointment currentAppointment = normalAppointments[count]; - //Where this condition was not needed to iOS, because we have get the - // appointment for specific date. In Android we pass the visible date range. - if ((view == CalendarView.workWeek || - view == CalendarView.timelineWorkWeek) && - calendar.timeSlotViewSettings.nonWorkingDays - .contains(currentAppointment._actualStartTime.weekday) && - calendar.timeSlotViewSettings.nonWorkingDays - .contains(currentAppointment._actualEndTime.weekday)) { - continue; - } + for (int position = 0; position < maxColsCount; position++) { + bool isIntersecting = false; + for (int j = 0; j < processedViews.length; j++) { + final AppointmentView previousApp = processedViews[j]; - List<_AppointmentView> intersectingApps; - final _AppointmentView currentAppView = _getAppointmentView( - currentAppointment, appointmentCollection, resourceIndex); + if (previousApp.position != position) { + continue; + } - for (int position = 0; position < maxColsCount; position++) { - bool isIntersecting = false; - for (int j = 0; j < processedViews.length; j++) { - final _AppointmentView previousApp = processedViews[j]; + if (_isIntersectingAppointmentInDayView( + calendar, + view, + currentAppointment, + previousApp, + previousApp.appointment!, + isAllDay, + timeIntervalHeight)) { + isIntersecting = true; - if (previousApp.position != position) { - continue; - } + if (intersectingApps == null) { + final List keyList = dict.keys.toList(); + for (int keyCount = 0; keyCount < keyList.length; keyCount++) { + final int key = keyList[keyCount]; + if (dict[key]!.contains(previousApp)) { + intersectingApps = dict[key]; + break; + } + } - if (_isIntersectingAppointmentInDayView( - calendar, - view, - currentAppointment, - previousApp, - previousApp.appointment, - isAllDay, - timeIntervalHeight)) { - isIntersecting = true; - - if (intersectingApps == null) { - final List keyList = dict.keys.toList(); - for (int keyCount = 0; keyCount < keyList.length; keyCount++) { - final int key = keyList[keyCount]; - if (dict[key].contains(previousApp)) { - intersectingApps = dict[key]; - break; + if (intersectingApps == null) { + intersectingApps = []; + dict[dict.keys.length] = intersectingApps; } - } - if (intersectingApps == null) { - intersectingApps = <_AppointmentView>[]; - dict[dict.keys.length] = intersectingApps; + break; } - - break; } } - } - if (!isIntersecting && currentAppView.position == -1) { - currentAppView.position = position; + if (!isIntersecting && currentAppView.position == -1) { + currentAppView.position = position; + } } - } - processedViews.add(currentAppView); - if (currentAppView.position == -1) { - int position = 0; - if (intersectingApps == null) { - intersectingApps = <_AppointmentView>[]; - dict[dict.keys.length] = intersectingApps; - } else if (intersectingApps.isNotEmpty) { - position = intersectingApps - .reduce((_AppointmentView currentAppview, - _AppointmentView nextAppview) => - currentAppview.maxPositions > nextAppview.maxPositions - ? currentAppview - : nextAppview) - .maxPositions; - } + processedViews.add(currentAppView); + if (currentAppView.position == -1) { + int position = 0; + if (intersectingApps == null) { + intersectingApps = []; + dict[dict.keys.length] = intersectingApps; + } else if (intersectingApps.isNotEmpty) { + position = intersectingApps + .reduce((AppointmentView currentAppview, + AppointmentView nextAppview) => + currentAppview.maxPositions > nextAppview.maxPositions + ? currentAppview + : nextAppview) + .maxPositions; + } - intersectingApps.add(currentAppView); - for (int i = 0; i < intersectingApps.length; i++) { - intersectingApps[i].maxPositions = position + 1; - } + intersectingApps.add(currentAppView); + for (int i = 0; i < intersectingApps.length; i++) { + intersectingApps[i].maxPositions = position + 1; + } - currentAppView.position = position; - if (maxColsCount <= position) { - maxColsCount = position + 1; - } - } else { - int maxPosition = 1; - if (intersectingApps == null) { - intersectingApps = <_AppointmentView>[]; - dict[dict.keys.length] = intersectingApps; - } else if (intersectingApps.isNotEmpty) { - maxPosition = intersectingApps - .reduce((_AppointmentView currentAppview, - _AppointmentView nextAppview) => - currentAppview.maxPositions > nextAppview.maxPositions - ? currentAppview - : nextAppview) - .maxPositions; - - if (currentAppView.position == maxPosition) { - maxPosition++; + currentAppView.position = position; + if (maxColsCount <= position) { + maxColsCount = position + 1; + } + } else { + int maxPosition = 1; + if (intersectingApps == null) { + intersectingApps = []; + dict[dict.keys.length] = intersectingApps; + } else if (intersectingApps.isNotEmpty) { + maxPosition = intersectingApps + .reduce((AppointmentView currentAppview, + AppointmentView nextAppview) => + currentAppview.maxPositions > nextAppview.maxPositions + ? currentAppview + : nextAppview) + .maxPositions; + + if (currentAppView.position == maxPosition) { + maxPosition++; + } } - } - intersectingApps.add(currentAppView); - for (int i = 0; i < intersectingApps.length; i++) { - intersectingApps[i].maxPositions = maxPosition; - } + intersectingApps.add(currentAppView); + for (int i = 0; i < intersectingApps.length; i++) { + intersectingApps[i].maxPositions = maxPosition; + } - if (maxColsCount <= maxPosition) { - maxColsCount = maxPosition + 1; + if (maxColsCount <= maxPosition) { + maxColsCount = maxPosition + 1; + } } - } - - intersectingApps = null; - } - dict.clear(); -} + intersectingApps = null; + } -DateTime _convertTimeToAppointmentTimeZone( - DateTime date, String appTimeZoneId, String calendarTimeZoneId) { - if (((appTimeZoneId == null || appTimeZoneId == '') && - (calendarTimeZoneId == null || calendarTimeZoneId == '')) || - calendarTimeZoneId == appTimeZoneId) { - return date; + dict.clear(); } - DateTime convertedDate = date; - if (appTimeZoneId != null && appTimeZoneId != '') { - //// Convert the date to appointment time zone - if (appTimeZoneId == 'Dateline Standard Time') { - convertedDate = subtractDuration(date.toUtc(), const Duration(hours: 12)); - //// Above mentioned converted date hold the date value which is equal to original date, but the time zone value changed. - //// E.g., Nov 3- 9.00 AM IST equal to Nov 2- 10.30 PM EST - //// So convert the Appointment time zone date to current time zone date. - convertedDate = DateTime( - date.year - (convertedDate.year - date.year), - date.month - (convertedDate.month - date.month), - date.day - (convertedDate.day - date.day), - date.hour - (convertedDate.hour - date.hour), - date.minute - (convertedDate.minute - date.minute), - date.second); - } else { - /// Create the specified date on appointment time zone. - /// Eg., Appointment Time zone as Eastern time zone(-5.00) and it date is - /// Nov 1 10AM, create the date using location. - final DateTime timeZoneDate = TZDateTime( - _timeZoneInfoToOlsonTimeZone(appTimeZoneId), - date.year, - date.month, - date.day, - date.hour, - date.minute, - date.second); - - final Duration offset = DateTime.now().timeZoneOffset; - - /// Convert the appointment time zone date to local date. - /// Eg., Nov 1 10AM(EST) UTC value as Nov 1 3(UTC) add local - /// time zone offset(IST +5.30) return Nov 1 8PM. - final DateTime localTimeZoneDate = - addDuration(timeZoneDate.toUtc(), offset); - - /// Resulted date as Nov 1 8PM but its time zone as EST so create the - /// local time date based on resulted date. - /// We does not use from method in TZDateTime because we does not - /// know the local time zone location. - convertedDate = DateTime( - localTimeZoneDate.year, - localTimeZoneDate.month, - localTimeZoneDate.day, - localTimeZoneDate.hour, - localTimeZoneDate.minute, - localTimeZoneDate.second); + /// Convert the date time from appointment timezone value to calender timezone + /// value. If calendar timezone value as null or empty then it convert + /// it to local timezone value. + static DateTime convertTimeToAppointmentTimeZone( + DateTime date, String? appTimeZoneId, String? calendarTimeZoneId) { + if (((appTimeZoneId == null || appTimeZoneId == '') && + (calendarTimeZoneId == null || calendarTimeZoneId == '')) || + calendarTimeZoneId == appTimeZoneId) { + return date; } - } - if (calendarTimeZoneId != null && calendarTimeZoneId != '') { - convertedDate ??= date; - - DateTime actualConvertedDate; - //// Convert the converted date with calendar time zone - if (calendarTimeZoneId == 'Dateline Standard Time') { - actualConvertedDate = - subtractDuration(convertedDate.toUtc(), const Duration(hours: 12)); - //// Above mentioned actual converted date hold the date value which is equal to converted date, but the time zone value changed. - //// So convert the schedule time zone date to current time zone date for rendering the appointment. - return DateTime( - convertedDate.year + (actualConvertedDate.year - convertedDate.year), - convertedDate.month + - (actualConvertedDate.month - convertedDate.month), - convertedDate.day + (actualConvertedDate.day - convertedDate.day), - convertedDate.hour + (actualConvertedDate.hour - convertedDate.hour), - convertedDate.minute + - (actualConvertedDate.minute - convertedDate.minute), - convertedDate.second); - } else { - final Location location = - _timeZoneInfoToOlsonTimeZone(calendarTimeZoneId); - - /// Convert the local time to calendar time zone. - actualConvertedDate = TZDateTime.from(convertedDate, location); - - /// Return the calendar time zone value with local time zone. - return DateTime( - actualConvertedDate.year, - actualConvertedDate.month, - actualConvertedDate.day, - actualConvertedDate.hour, - actualConvertedDate.minute, - actualConvertedDate.second); + DateTime convertedDate = date; + if (appTimeZoneId != null && appTimeZoneId != '') { + //// Convert the date to appointment time zone + if (appTimeZoneId == 'Dateline Standard Time') { + convertedDate = date.toUtc().subtract(const Duration(hours: 12)); + //// Above mentioned converted date hold the date value which is equal to original date, but the time zone value changed. + //// E.g., Nov 3- 9.00 AM IST equal to Nov 2- 10.30 PM EST + //// So convert the Appointment time zone date to current time zone date. + convertedDate = DateTime( + date.year - (convertedDate.year - date.year), + date.month - (convertedDate.month - date.month), + date.day - (convertedDate.day - date.day), + date.hour - (convertedDate.hour - date.hour), + date.minute - (convertedDate.minute - date.minute), + date.second); + } else { + /// Create the specified date on appointment time zone. + /// Eg., Appointment Time zone as Eastern time zone(-5.00) and it + /// date is Nov 1 10AM, create the date using location. + final DateTime timeZoneDate = TZDateTime( + _timeZoneInfoToOlsonTimeZone(appTimeZoneId), + date.year, + date.month, + date.day, + date.hour, + date.minute, + date.second); + + final Duration offset = DateTime.now().timeZoneOffset; + + /// Convert the appointment time zone date to local date. + /// Eg., Nov 1 10AM(EST) UTC value as Nov 1 3(UTC) add local + /// time zone offset(IST +5.30) return Nov 1 8PM. + final DateTime localTimeZoneDate = timeZoneDate.toUtc().add(offset); + + /// Resulted date as Nov 1 8PM but its time zone as EST so create the + /// local time date based on resulted date. + /// We does not use from method in TZDateTime because we does not + /// know the local time zone location. + convertedDate = DateTime( + localTimeZoneDate.year, + localTimeZoneDate.month, + localTimeZoneDate.day, + localTimeZoneDate.hour, + localTimeZoneDate.minute, + localTimeZoneDate.second); + } } - } - - return convertedDate; -} - -/// Return the time regions in between the visible date range. -List _getVisibleRegions( - DateTime visibleStartDate, - DateTime visibleEndDate, - List regions, - String calendarTimeZone) { - final List regionCollection = []; - if (visibleStartDate == null || visibleEndDate == null || regions == null) { - return regionCollection; - } - final DateTime startDate = _convertToStartTime(visibleStartDate); - final DateTime endDate = _convertToEndTime(visibleEndDate); - for (int j = 0; j < regions.length; j++) { - final TimeRegion region = regions[j]; - region._actualStartTime = _convertTimeToAppointmentTimeZone( - region.startTime, region.timeZone, calendarTimeZone); - region._actualEndTime = _convertTimeToAppointmentTimeZone( - region.endTime, region.timeZone, calendarTimeZone); - - if (region.recurrenceRule == null || region.recurrenceRule == '') { - if (_isDateRangeWithinVisibleDateRange( - region._actualStartTime, region._actualEndTime, startDate, endDate)) { - regionCollection.add(region); + if (calendarTimeZoneId != null && calendarTimeZoneId != '') { + DateTime actualConvertedDate; + //// Convert the converted date with calendar time zone + if (calendarTimeZoneId == 'Dateline Standard Time') { + actualConvertedDate = + convertedDate.toUtc().subtract(const Duration(hours: 12)); + //// Above mentioned actual converted date hold the date value which is equal to converted date, but the time zone value changed. + //// So convert the schedule time zone date to current time zone date for rendering the appointment. + return DateTime( + convertedDate.year + + (actualConvertedDate.year - convertedDate.year), + convertedDate.month + + (actualConvertedDate.month - convertedDate.month), + convertedDate.day + (actualConvertedDate.day - convertedDate.day), + convertedDate.hour + + (actualConvertedDate.hour - convertedDate.hour), + convertedDate.minute + + (actualConvertedDate.minute - convertedDate.minute), + convertedDate.second); + } else { + final Location location = + _timeZoneInfoToOlsonTimeZone(calendarTimeZoneId); + + /// Convert the local time to calendar time zone. + actualConvertedDate = TZDateTime.from(convertedDate, location); + + /// Return the calendar time zone value with local time zone. + return DateTime( + actualConvertedDate.year, + actualConvertedDate.month, + actualConvertedDate.day, + actualConvertedDate.hour, + actualConvertedDate.minute, + actualConvertedDate.second); } - - continue; } - _getRecurrenceRegions( - region, regionCollection, startDate, endDate, calendarTimeZone); - } - - return regionCollection; -} - -List _getVisibleAppointments( - DateTime visibleStartDate, - DateTime visibleEndDate, - List appointments, - String calendarTimeZone, - bool isTimelineView, - {bool canCreateNewAppointment = true}) { - final List appointmentColl = []; - if (visibleStartDate == null || - visibleEndDate == null || - appointments == null) { - return appointmentColl; - } - - final DateTime startDate = _convertToStartTime(visibleStartDate); - final DateTime endDate = _convertToEndTime(visibleEndDate); - int count = 0; - if (appointments != null) { - count = appointments.length; - } - - for (int j = 0; j < count; j++) { - final Appointment appointment = appointments[j]; - appointment._actualStartTime = _convertTimeToAppointmentTimeZone( - appointment.startTime, appointment.startTimeZone, calendarTimeZone); - appointment._actualEndTime = _convertTimeToAppointmentTimeZone( - appointment.endTime, appointment.endTimeZone, calendarTimeZone); - - if (appointment.recurrenceRule == null || - appointment.recurrenceRule == '') { - if (_isAppointmentWithinVisibleDateRange( - appointment, startDate, endDate)) { - /// Stored the actual start time, to exact start time to use the value, - /// since, we split the span appointment into multiple instances and - /// change the actual start and end time based on the rendering, hence - /// to get the actual start and end time of the appointment we have - /// stored the value in the exact start and end time. - appointment._exactStartTime = appointment._actualStartTime; - appointment._exactEndTime = appointment._actualEndTime; - - /// can create new appointment boolean is used to skip the new - /// appointment creation while the appointment start and end date as - /// different and appointment duration is not more than 24 hours. - /// - /// The boolean value assigned to false when calendar view as schedule. - if (canCreateNewAppointment && - !(appointment._exactStartTime.day == - appointment._exactEndTime.day && - appointment._exactStartTime.year == - appointment._exactEndTime.year && - appointment._exactStartTime.month == - appointment._exactEndTime.month) && - appointment._exactStartTime.isBefore(appointment._exactEndTime) && - (appointment._exactEndTime.difference(appointment._exactStartTime)) - .inDays == - 0 && - !appointment.isAllDay && - !isTimelineView) { - for (int i = 0; i < 2; i++) { - final Appointment spannedAppointment = _copy(appointment); - if (i == 0) { - spannedAppointment._actualEndTime = DateTime( - appointment._exactStartTime.year, - appointment._exactStartTime.month, - appointment._exactStartTime.day, - 23, - 59, - 59); - } else { - spannedAppointment._actualStartTime = DateTime( - appointment._exactEndTime.year, - appointment._exactEndTime.month, - appointment._exactEndTime.day, - 0, - 0, - 0); - } - - spannedAppointment.startTime = spannedAppointment.isAllDay - ? appointment._actualStartTime - : _convertTimeToAppointmentTimeZone( - appointment._actualStartTime, - appointment.startTimeZone, - calendarTimeZone); - spannedAppointment.endTime = spannedAppointment.isAllDay - ? appointment._actualEndTime - : _convertTimeToAppointmentTimeZone(appointment._actualEndTime, - appointment.endTimeZone, calendarTimeZone); - - // Adding Spanned Appointment only when the Appointment range - // within the VisibleDate - if (_isAppointmentWithinVisibleDateRange( - spannedAppointment, startDate, endDate)) { - appointmentColl.add(spannedAppointment); - } - } - } else if (!(appointment._exactStartTime.day == - appointment._exactEndTime.day && - appointment._exactStartTime.year == - appointment._exactEndTime.year && - appointment._exactStartTime.month == - appointment._exactEndTime.month) && - appointment._exactStartTime.isBefore(appointment._exactEndTime) && - isTimelineView) { - //// Check the spanned appointment with in current visible dates. example visible date 21 to 27 and - //// the appointment start and end date as 23 and 25. - if (_isAppointmentInVisibleDateRange( - appointment, startDate, endDate)) { - appointment._isSpanned = _isSpanned(appointment); - appointmentColl.add(appointment); - } else if (_isAppointmentDateWithinVisibleDateRange( - appointment._actualStartTime, startDate, endDate)) { - //// Check the spanned appointment start date with in current visible dates. - //// example visible date 21 to 27 and the appointment start and end date as 23 and 28. + return convertedDate; + } + + /// Return the visible appointment collection based on visible start and + /// end date. + static List getVisibleAppointments( + DateTime visibleStartDate, + DateTime visibleEndDate, + List appointments, + String? calendarTimeZone, + bool isTimelineView, + {bool canCreateNewAppointment = true}) { + final List appointmentColl = []; + final DateTime startDate = convertToStartTime(visibleStartDate); + final DateTime endDate = convertToEndTime(visibleEndDate); + final int count = appointments.length; + + for (int j = 0; j < count; j++) { + final CalendarAppointment appointment = appointments[j]; + appointment.actualStartTime = convertTimeToAppointmentTimeZone( + appointment.startTime, appointment.startTimeZone, calendarTimeZone); + appointment.actualEndTime = convertTimeToAppointmentTimeZone( + appointment.endTime, appointment.endTimeZone, calendarTimeZone); + + if (appointment.recurrenceRule == null || + appointment.recurrenceRule == '') { + if (isAppointmentWithinVisibleDateRange( + appointment, startDate, endDate)) { + /// Stored the actual start time to exact start time to use the value, + /// since, we split the span appointment into multiple instances and + /// change the actual start and end time based on the rendering, hence + /// to get the actual start and end time of the appointment we have + /// stored the value in the exact start and end time. + appointment.exactStartTime = appointment.actualStartTime; + appointment.exactEndTime = appointment.actualEndTime; + + /// can create new appointment boolean is used to skip the new + /// appointment creation while the appointment start and end date as + /// different and appointment duration is not more than 24 hours. + /// + /// The bool value assigned to false when calendar view as schedule. + if (canCreateNewAppointment && + !(appointment.exactStartTime.day == + appointment.exactEndTime.day && + appointment.exactStartTime.year == + appointment.exactEndTime.year && + appointment.exactStartTime.month == + appointment.exactEndTime.month) && + appointment.exactStartTime.isBefore(appointment.exactEndTime) && + (appointment.exactEndTime.difference(appointment.exactStartTime)) + .inDays == + 0 && + !appointment.isAllDay && + !isTimelineView) { for (int i = 0; i < 2; i++) { - final Appointment spannedAppointment = _copy(appointment); + final CalendarAppointment spannedAppointment = _copy(appointment); if (i == 0) { - spannedAppointment._actualEndTime = DateTime( - endDate.year, endDate.month, endDate.day, 23, 59, 59); + spannedAppointment.actualEndTime = DateTime( + appointment.exactStartTime.year, + appointment.exactStartTime.month, + appointment.exactStartTime.day, + 23, + 59, + 59); } else { - spannedAppointment._actualStartTime = - DateTime(endDate.year, endDate.month, endDate.day, 0, 0, 0); + spannedAppointment.actualStartTime = DateTime( + appointment.exactEndTime.year, + appointment.exactEndTime.month, + appointment.exactEndTime.day, + 0, + 0, + 0); } spannedAppointment.startTime = spannedAppointment.isAllDay - ? appointment._actualStartTime - : _convertTimeToAppointmentTimeZone( - appointment._actualStartTime, + ? appointment.actualStartTime + : convertTimeToAppointmentTimeZone( + appointment.actualStartTime, appointment.startTimeZone, calendarTimeZone); spannedAppointment.endTime = spannedAppointment.isAllDay - ? appointment._actualEndTime - : _convertTimeToAppointmentTimeZone( - appointment._actualEndTime, - appointment.endTimeZone, - calendarTimeZone); + ? appointment.actualEndTime + : convertTimeToAppointmentTimeZone(appointment.actualEndTime, + appointment.endTimeZone, calendarTimeZone); // Adding Spanned Appointment only when the Appointment range // within the VisibleDate - if (_isAppointmentInVisibleDateRange( + if (isAppointmentWithinVisibleDateRange( spannedAppointment, startDate, endDate)) { - spannedAppointment._isSpanned = _isSpanned(spannedAppointment); appointmentColl.add(spannedAppointment); } } - } else if (_isAppointmentDateWithinVisibleDateRange( - appointment._actualEndTime, startDate, endDate)) { - //// Check the spanned appointment end date with in current visible dates. example visible date 21 to 27 and - //// the appointment start and end date as 18 and 24. - for (int i = 0; i < 2; i++) { - final Appointment spannedAppointment = _copy(appointment); - if (i == 0) { - spannedAppointment._actualStartTime = - appointment._actualStartTime; - final DateTime date = - addDuration(startDate, const Duration(days: -1)); - spannedAppointment._actualEndTime = - DateTime(date.year, date.month, date.day, 23, 59, 59); - } else { - spannedAppointment._actualStartTime = DateTime( - startDate.year, startDate.month, startDate.day, 0, 0, 0); + } else if (!(appointment.exactStartTime.day == + appointment.exactEndTime.day && + appointment.exactStartTime.year == + appointment.exactEndTime.year && + appointment.exactStartTime.month == + appointment.exactEndTime.month) && + appointment.exactStartTime.isBefore(appointment.exactEndTime) && + isTimelineView) { + //// Check the spanned appointment with in current visible dates. example visible date 21 to 27 and + //// the appointment start and end date as 23 and 25. + if (_isAppointmentInVisibleDateRange( + appointment, startDate, endDate)) { + appointment.isSpanned = _isSpanned(appointment); + appointmentColl.add(appointment); + } else if (_isAppointmentDateWithinVisibleDateRange( + appointment.actualStartTime, startDate, endDate)) { + //// Check the spanned appointment start date with in current visible dates. + //// example visible date 21 to 27 and the appointment start and end date as 23 and 28. + for (int i = 0; i < 2; i++) { + final CalendarAppointment spannedAppointment = + _copy(appointment); + if (i == 0) { + spannedAppointment.actualEndTime = DateTime( + endDate.year, endDate.month, endDate.day, 23, 59, 59); + } else { + spannedAppointment.actualStartTime = DateTime( + endDate.year, endDate.month, endDate.day, 0, 0, 0); + } + + spannedAppointment.startTime = spannedAppointment.isAllDay + ? appointment.actualStartTime + : convertTimeToAppointmentTimeZone( + appointment.actualStartTime, + appointment.startTimeZone, + calendarTimeZone); + spannedAppointment.endTime = spannedAppointment.isAllDay + ? appointment.actualEndTime + : convertTimeToAppointmentTimeZone( + appointment.actualEndTime, + appointment.endTimeZone, + calendarTimeZone); + + // Adding Spanned Appointment only when the Appointment range + // within the VisibleDate + if (_isAppointmentInVisibleDateRange( + spannedAppointment, startDate, endDate)) { + spannedAppointment.isSpanned = _isSpanned(spannedAppointment); + appointmentColl.add(spannedAppointment); + } } - - spannedAppointment.startTime = spannedAppointment.isAllDay - ? appointment._actualStartTime - : _convertTimeToAppointmentTimeZone( - appointment._actualStartTime, - appointment.startTimeZone, - calendarTimeZone); - spannedAppointment.endTime = spannedAppointment.isAllDay - ? appointment._actualEndTime - : _convertTimeToAppointmentTimeZone( - appointment._actualEndTime, - appointment.endTimeZone, - calendarTimeZone); - - // Adding Spanned Appointment only when the Appointment range - // within the VisibleDate - if (_isAppointmentInVisibleDateRange( - spannedAppointment, startDate, endDate)) { - spannedAppointment._isSpanned = _isSpanned(spannedAppointment); - appointmentColl.add(spannedAppointment); - } - } - } else if (!_isAppointmentDateWithinVisibleDateRange( - appointment._actualEndTime, startDate, endDate) && - !_isAppointmentDateWithinVisibleDateRange( - appointment._actualStartTime, startDate, endDate)) { - //// Check the spanned appointment start and end date not in current visible dates. example visible date 21 to 27 and - //// the appointment start and end date as 18 and 28. - for (int i = 0; i < 3; i++) { - final Appointment spannedAppointment = _copy(appointment); - if (i == 0) { - final DateTime date = - addDuration(startDate, const Duration(days: -1)); - spannedAppointment._actualEndTime = - DateTime(date.year, date.month, date.day, 23, 59, 59); - } else if (i == 1) { - spannedAppointment._actualStartTime = DateTime( - startDate.year, startDate.month, startDate.day, 0, 0, 0); - spannedAppointment._actualEndTime = DateTime( - endDate.year, endDate.month, endDate.day, 23, 59, 59); - } else { - final DateTime date = - addDuration(endDate, const Duration(days: 1)); - spannedAppointment._actualStartTime = - DateTime(date.year, date.month, date.day, 0, 0, 0); + } else if (_isAppointmentDateWithinVisibleDateRange( + appointment.actualEndTime, startDate, endDate)) { + //// Check the spanned appointment end date with in current visible dates. example visible date 21 to 27 and + //// the appointment start and end date as 18 and 24. + for (int i = 0; i < 2; i++) { + final CalendarAppointment spannedAppointment = + _copy(appointment); + if (i == 0) { + spannedAppointment.actualStartTime = + appointment.actualStartTime; + final DateTime date = addDays(startDate, -1); + spannedAppointment.actualEndTime = + DateTime(date.year, date.month, date.day, 23, 59, 59); + } else { + spannedAppointment.actualStartTime = DateTime( + startDate.year, startDate.month, startDate.day, 0, 0, 0); + } + + spannedAppointment.startTime = spannedAppointment.isAllDay + ? appointment.actualStartTime + : convertTimeToAppointmentTimeZone( + appointment.actualStartTime, + appointment.startTimeZone, + calendarTimeZone); + spannedAppointment.endTime = spannedAppointment.isAllDay + ? appointment.actualEndTime + : convertTimeToAppointmentTimeZone( + appointment.actualEndTime, + appointment.endTimeZone, + calendarTimeZone); + + // Adding Spanned Appointment only when the Appointment range + // within the VisibleDate + if (_isAppointmentInVisibleDateRange( + spannedAppointment, startDate, endDate)) { + spannedAppointment.isSpanned = _isSpanned(spannedAppointment); + appointmentColl.add(spannedAppointment); + } } - - spannedAppointment.startTime = spannedAppointment.isAllDay - ? appointment._actualStartTime - : _convertTimeToAppointmentTimeZone( - appointment._actualStartTime, - appointment.startTimeZone, - calendarTimeZone); - spannedAppointment.endTime = spannedAppointment.isAllDay - ? appointment._actualEndTime - : _convertTimeToAppointmentTimeZone( - appointment._actualEndTime, - appointment.endTimeZone, - calendarTimeZone); - - // Adding Spanned Appointment only when the Appointment range - // within the VisibleDate - if (_isAppointmentInVisibleDateRange( - spannedAppointment, startDate, endDate)) { - spannedAppointment._isSpanned = _isSpanned(spannedAppointment); - appointmentColl.add(spannedAppointment); + } else if (!_isAppointmentDateWithinVisibleDateRange( + appointment.actualEndTime, startDate, endDate) && + !_isAppointmentDateWithinVisibleDateRange( + appointment.actualStartTime, startDate, endDate)) { + //// Check the spanned appointment start and end date not in current visible dates. example visible date 21 to 27 and + //// the appointment start and end date as 18 and 28. + for (int i = 0; i < 3; i++) { + final CalendarAppointment spannedAppointment = + _copy(appointment); + if (i == 0) { + final DateTime date = addDays(startDate, -1); + spannedAppointment.actualEndTime = + DateTime(date.year, date.month, date.day, 23, 59, 59); + } else if (i == 1) { + spannedAppointment.actualStartTime = DateTime( + startDate.year, startDate.month, startDate.day, 0, 0, 0); + spannedAppointment.actualEndTime = DateTime( + endDate.year, endDate.month, endDate.day, 23, 59, 59); + } else { + final DateTime date = addDays(endDate, 1); + spannedAppointment.actualStartTime = + DateTime(date.year, date.month, date.day, 0, 0, 0); + } + + spannedAppointment.startTime = spannedAppointment.isAllDay + ? appointment.actualStartTime + : convertTimeToAppointmentTimeZone( + appointment.actualStartTime, + appointment.startTimeZone, + calendarTimeZone); + spannedAppointment.endTime = spannedAppointment.isAllDay + ? appointment.actualEndTime + : convertTimeToAppointmentTimeZone( + appointment.actualEndTime, + appointment.endTimeZone, + calendarTimeZone); + + // Adding Spanned Appointment only when the Appointment range + // within the VisibleDate + if (_isAppointmentInVisibleDateRange( + spannedAppointment, startDate, endDate)) { + spannedAppointment.isSpanned = _isSpanned(spannedAppointment); + appointmentColl.add(spannedAppointment); + } } + } else { + appointment.isSpanned = _isSpanned(appointment); + appointmentColl.add(appointment); } } else { - appointment._isSpanned = _isSpanned(appointment); appointmentColl.add(appointment); } - } else { - appointmentColl.add(appointment); } + + continue; } - continue; + /// Stored the actual start time to exact start time to use the value, + /// since, we split the span appointment into multiple instances and + /// change the actual start and end time based on the rendering, hence + /// to get the actual start and end time of the appointment we have + /// stored the value in the exact start and end time. + appointment.exactStartTime = appointment.actualStartTime; + appointment.exactEndTime = appointment.actualEndTime; + _getRecurrenceAppointments( + appointment, appointmentColl, startDate, endDate, calendarTimeZone); } - _getRecurrenceAppointments( - appointment, appointmentColl, startDate, endDate, calendarTimeZone); - } - - return appointmentColl; -} - -Appointment _cloneRecurrenceAppointment(Appointment appointment, - int recurrenceIndex, DateTime recursiveDate, String calendarTimeZone) { - final Appointment occurrenceAppointment = _copy(appointment); - occurrenceAppointment._actualStartTime = recursiveDate; - occurrenceAppointment.startTime = occurrenceAppointment.isAllDay - ? occurrenceAppointment._actualStartTime - : _convertTimeToAppointmentTimeZone( - occurrenceAppointment._actualStartTime, - occurrenceAppointment.startTimeZone, - calendarTimeZone); - - final int minutes = appointment._actualEndTime - .difference(appointment._actualStartTime) - .inMinutes; - occurrenceAppointment._actualEndTime = addDuration( - occurrenceAppointment._actualStartTime, Duration(minutes: minutes)); - occurrenceAppointment.endTime = occurrenceAppointment.isAllDay - ? occurrenceAppointment._actualEndTime - : _convertTimeToAppointmentTimeZone(occurrenceAppointment._actualEndTime, - occurrenceAppointment.endTimeZone, calendarTimeZone); - occurrenceAppointment._isSpanned = _isSpanned(occurrenceAppointment) && - (occurrenceAppointment.endTime - .difference(occurrenceAppointment.startTime) - .inDays > - 0); - occurrenceAppointment._exactStartTime = - occurrenceAppointment._actualStartTime; - occurrenceAppointment._exactEndTime = occurrenceAppointment._actualEndTime; - - return occurrenceAppointment; -} - -List _generateCalendarAppointments( - CalendarDataSource calendarData, SfCalendar calendar, - [List appointments]) { - if (calendarData == null) { - return null; - } - - final List dataSource = appointments ?? calendarData.appointments; - if (dataSource == null) { - return null; + return appointmentColl; } - final List calendarAppointmentCollection = []; - if (dataSource.isNotEmpty && dataSource[0] is Appointment) { - for (int i = 0; i < dataSource.length; i++) { - final Appointment item = dataSource[i]; - final DateTime appStartTime = item.startTime; - final DateTime appEndTime = item.endTime; - item._data = item; - item._actualStartTime = !item.isAllDay - ? _convertTimeToAppointmentTimeZone( - item.startTime, item.startTimeZone, calendar.timeZone) - : item.startTime; - item._actualEndTime = !item.isAllDay - ? _convertTimeToAppointmentTimeZone( - item.endTime, item.endTimeZone, calendar.timeZone) - : item.endTime; - _updateTimeForInvalidEndTime(item, calendar.timeZone); - calendarAppointmentCollection.add(item); - - item._isSpanned = - _isSpanned(item) && (appEndTime.difference(appStartTime).inDays > 0); - } - } else { - for (int i = 0; i < dataSource.length; i++) { - final dynamic item = dataSource[i]; - final Appointment app = _createAppointment(item, calendar); - - final DateTime appStartTime = app.startTime; - final DateTime appEndTime = app.endTime; - app._isSpanned = - _isSpanned(app) && (appEndTime.difference(appStartTime).inDays > 0); - calendarAppointmentCollection.add(app); + static CalendarAppointment _cloneRecurrenceAppointment( + CalendarAppointment appointment, + int recurrenceIndex, + DateTime recursiveDate, + String? calendarTimeZone) { + final CalendarAppointment occurrenceAppointment = _copy(appointment); + occurrenceAppointment.actualStartTime = recursiveDate; + occurrenceAppointment.startTime = occurrenceAppointment.isAllDay + ? occurrenceAppointment.actualStartTime + : convertTimeToAppointmentTimeZone( + occurrenceAppointment.actualStartTime, + occurrenceAppointment.startTimeZone, + calendarTimeZone); + + final int minutes = appointment.actualEndTime + .difference(appointment.actualStartTime) + .inMinutes; + occurrenceAppointment.actualEndTime = addDuration( + occurrenceAppointment.actualStartTime, Duration(minutes: minutes)); + occurrenceAppointment.endTime = occurrenceAppointment.isAllDay + ? occurrenceAppointment.actualEndTime + : convertTimeToAppointmentTimeZone(occurrenceAppointment.actualEndTime, + occurrenceAppointment.endTimeZone, calendarTimeZone); + occurrenceAppointment.isSpanned = _isSpanned(occurrenceAppointment) && + (occurrenceAppointment.endTime + .difference(occurrenceAppointment.startTime) + .inDays > + 0); + occurrenceAppointment.exactStartTime = + occurrenceAppointment.actualStartTime; + occurrenceAppointment.exactEndTime = occurrenceAppointment.actualEndTime; + + return occurrenceAppointment; + } + + /// Generate the calendar appointment collection from custom appointment + /// collection. + static List generateCalendarAppointments( + CalendarDataSource? calendarData, String? calendarTimeZone, + [List? appointments]) { + final List calendarAppointmentCollection = + []; + if (calendarData == null) { + return calendarAppointmentCollection; } - } - - return calendarAppointmentCollection; -} - -Appointment _createAppointment(Object appointmentObject, SfCalendar calendar) { - final Appointment app = Appointment(); - final int index = calendar.dataSource.appointments.indexOf(appointmentObject); - app.startTime = calendar.dataSource.getStartTime(index); - app.endTime = calendar.dataSource.getEndTime(index); - app.subject = calendar.dataSource.getSubject(index); - app.isAllDay = calendar.dataSource.isAllDay(index); - app.color = calendar.dataSource.getColor(index); - app.notes = calendar.dataSource.getNotes(index); - app.location = calendar.dataSource.getLocation(index); - app.startTimeZone = calendar.dataSource.getStartTimeZone(index); - app.endTimeZone = calendar.dataSource.getEndTimeZone(index); - app.recurrenceRule = calendar.dataSource.getRecurrenceRule(index); - app.recurrenceExceptionDates = - calendar.dataSource.getRecurrenceExceptionDates(index); - app.resourceIds = calendar.dataSource.getResourceIds(index); - app._data = appointmentObject; - app._actualStartTime = !app.isAllDay - ? _convertTimeToAppointmentTimeZone( - app.startTime, app.startTimeZone, calendar.timeZone) - : app.startTime; - app._actualEndTime = !app.isAllDay - ? _convertTimeToAppointmentTimeZone( - app.endTime, app.endTimeZone, calendar.timeZone) - : app.endTime; - _updateTimeForInvalidEndTime(app, calendar.timeZone); - return app; -} - -void _updateTimeForInvalidEndTime( - Appointment appointment, String scheduleTimeZone) { - if (appointment._actualEndTime.isBefore(appointment._actualStartTime) && - !appointment.isAllDay) { - appointment.endTime = _convertTimeToAppointmentTimeZone( - addDuration(appointment._actualStartTime, const Duration(minutes: 30)), - appointment.endTimeZone, - scheduleTimeZone); - appointment._actualEndTime = !appointment.isAllDay - ? _convertTimeToAppointmentTimeZone( - appointment.endTime, appointment.endTimeZone, scheduleTimeZone) - : appointment.endTime; - } -} - -void _getRecurrenceAppointments( - Appointment appointment, - List appointments, - DateTime visibleStartDate, - DateTime visibleEndDate, - String scheduleTimeZone) { - final DateTime appStartTime = appointment._actualStartTime; - int recurrenceIndex = 0; - if (appStartTime.isAfter(visibleEndDate)) { - return; - } - - String rule = appointment.recurrenceRule; - if (!rule.contains('COUNT') && !rule.contains('UNTIL')) { - final DateFormat formatter = DateFormat('yyyyMMdd'); - final String newSubString = ';UNTIL=' + formatter.format(visibleEndDate); - rule = rule + newSubString; - } - - List recursiveDates; - DateTime endDate; - final List ruleSeparator = ['=', ';', ',']; - final List rRule = - _splitRule(appointment.recurrenceRule, ruleSeparator); - if (appointment.recurrenceRule.contains('UNTIL')) { - final String untilValue = rRule[rRule.indexOf('UNTIL') + 1]; - endDate = DateTime.parse(untilValue); - endDate = addDuration(endDate, - appointment._actualEndTime.difference(appointment._actualStartTime)); - endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); - } else if (appointment.recurrenceRule.contains('COUNT')) { - recursiveDates = _getRecurrenceDateTimeCollection( - appointment.recurrenceRule, appointment._actualStartTime); - endDate = recursiveDates.last; - endDate = addDuration(endDate, - appointment._actualEndTime.difference(appointment._actualStartTime)); - endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); - } - if ((appointment.recurrenceRule.contains('UNTIL') || - appointment.recurrenceRule.contains('COUNT')) && - !(appStartTime.isBefore(visibleEndDate) && - visibleStartDate.isBefore(endDate))) { - return; - } + final List? dataSource = appointments ?? calendarData.appointments; + if (dataSource == null) { + return calendarAppointmentCollection; + } - recursiveDates = _getRecurrenceDateTimeCollection( - rule, appointment._actualStartTime, - recurrenceDuration: - appointment._actualEndTime.difference(appointment._actualStartTime), - specificStartDate: visibleStartDate, - specificEndDate: visibleEndDate); - - for (int j = 0; j < recursiveDates.length; j++) { - final DateTime recursiveDate = recursiveDates[j]; - if (appointment.recurrenceExceptionDates != null) { - bool isDateContains = false; - for (int i = 0; i < appointment.recurrenceExceptionDates.length; i++) { - final DateTime date = _convertTimeToAppointmentTimeZone( - appointment.recurrenceExceptionDates[i], '', scheduleTimeZone); - if (date.year == recursiveDate.year && - date.month == recursiveDate.month && - date.day == recursiveDate.day) { - isDateContains = true; - break; - } + if (dataSource.isNotEmpty && dataSource[0] is CalendarAppointment) { + for (int i = 0; i < dataSource.length; i++) { + final CalendarAppointment item = dataSource[i]; + final DateTime appStartTime = item.startTime; + final DateTime appEndTime = item.endTime; + item.data = item; + item.actualStartTime = !item.isAllDay + ? convertTimeToAppointmentTimeZone( + item.startTime, item.startTimeZone, calendarTimeZone) + : item.startTime; + item.actualEndTime = !item.isAllDay + ? convertTimeToAppointmentTimeZone( + item.endTime, item.endTimeZone, calendarTimeZone) + : item.endTime; + _updateTimeForInvalidEndTime(item, calendarTimeZone); + calendarAppointmentCollection.add(item); + + item.isSpanned = _isSpanned(item) && + (appEndTime.difference(appStartTime).inDays > 0); } - if (isDateContains) { - continue; + } else { + for (int i = 0; i < dataSource.length; i++) { + final dynamic item = dataSource[i]; + final CalendarAppointment app = + _createAppointment(item, calendarData, calendarTimeZone); + + final DateTime appStartTime = app.startTime; + final DateTime appEndTime = app.endTime; + app.isSpanned = + _isSpanned(app) && (appEndTime.difference(appStartTime).inDays > 0); + calendarAppointmentCollection.add(app); } } - final Appointment occurrenceAppointment = _cloneRecurrenceAppointment( - appointment, recurrenceIndex, recursiveDate, scheduleTimeZone); - recurrenceIndex++; - appointments.add(occurrenceAppointment); - } -} + return calendarAppointmentCollection; + } + + static CalendarAppointment _createAppointment(Object appointmentObject, + CalendarDataSource calendarData, String? calendarTimeZone) { + CalendarAppointment app; + if (appointmentObject is Appointment) { + app = CalendarAppointment( + startTime: appointmentObject.startTime, + endTime: appointmentObject.endTime, + subject: appointmentObject.subject, + isAllDay: appointmentObject.isAllDay, + color: appointmentObject.color, + notes: appointmentObject.notes, + location: appointmentObject.location, + startTimeZone: appointmentObject.startTimeZone, + endTimeZone: appointmentObject.endTimeZone, + recurrenceRule: appointmentObject.recurrenceRule, + recurrenceExceptionDates: appointmentObject.recurrenceExceptionDates, + resourceIds: appointmentObject.resourceIds); + } else { + final int index = calendarData.appointments!.indexOf(appointmentObject); + app = CalendarAppointment( + startTime: calendarData.getStartTime(index), + endTime: calendarData.getEndTime(index), + subject: calendarData.getSubject(index), + isAllDay: calendarData.isAllDay(index), + color: calendarData.getColor(index), + notes: calendarData.getNotes(index), + location: calendarData.getLocation(index), + startTimeZone: calendarData.getStartTimeZone(index), + endTimeZone: calendarData.getEndTimeZone(index), + recurrenceRule: calendarData.getRecurrenceRule(index), + recurrenceExceptionDates: + calendarData.getRecurrenceExceptionDates(index), + resourceIds: calendarData.getResourceIds(index)); + } -/// Get the recurrence time regions in between the visible date range. -void _getRecurrenceRegions( - TimeRegion region, - List regions, - DateTime visibleStartDate, - DateTime visibleEndDate, - String calendarTimeZone) { - final DateTime regionStartDate = region._actualStartTime; - if (regionStartDate.isAfter(visibleEndDate)) { - return; + app.data = appointmentObject; + app.actualStartTime = !app.isAllDay + ? convertTimeToAppointmentTimeZone( + app.startTime, app.startTimeZone, calendarTimeZone) + : app.startTime; + app.actualEndTime = !app.isAllDay + ? convertTimeToAppointmentTimeZone( + app.endTime, app.endTimeZone, calendarTimeZone) + : app.endTime; + _updateTimeForInvalidEndTime(app, calendarTimeZone); + return app; + } + + static void _updateTimeForInvalidEndTime( + CalendarAppointment appointment, String? scheduleTimeZone) { + if (appointment.actualEndTime.isBefore(appointment.actualStartTime) && + !appointment.isAllDay) { + appointment.endTime = convertTimeToAppointmentTimeZone( + addDuration(appointment.actualStartTime, const Duration(minutes: 30)), + appointment.endTimeZone, + scheduleTimeZone); + appointment.actualEndTime = !appointment.isAllDay + ? convertTimeToAppointmentTimeZone( + appointment.endTime, appointment.endTimeZone, scheduleTimeZone) + : appointment.endTime; + } } - String rule = region.recurrenceRule; - if (!rule.contains('COUNT') && !rule.contains('UNTIL')) { - final DateFormat formatter = DateFormat('yyyyMMdd'); - final String newSubString = ';UNTIL=' + formatter.format(visibleEndDate); - rule = rule + newSubString; - } + static void _getRecurrenceAppointments( + CalendarAppointment appointment, + List appointments, + DateTime visibleStartDate, + DateTime visibleEndDate, + String? scheduleTimeZone) { + final DateTime appStartTime = appointment.actualStartTime; + int recurrenceIndex = 0; + if (appStartTime.isAfter(visibleEndDate)) { + return; + } - List recursiveDates; - DateTime endDate; - final List ruleSeparator = ['=', ';', ',']; - final List rRule = _splitRule(region.recurrenceRule, ruleSeparator); - if (region.recurrenceRule.contains('UNTIL')) { - final String untilValue = rRule[rRule.indexOf('UNTIL') + 1]; - endDate = DateTime.parse(untilValue); - endDate = addDuration( - endDate, region._actualEndTime.difference(region._actualStartTime)); - endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); - } else if (region.recurrenceRule.contains('COUNT')) { - recursiveDates = _getRecurrenceDateTimeCollection( - region.recurrenceRule, region._actualStartTime); - endDate = recursiveDates.last; - endDate = addDuration( - endDate, region._actualEndTime.difference(region._actualStartTime)); - endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); - } + final String recurrenceRule = appointment.recurrenceRule ?? ''; + String rule = recurrenceRule; + if (!rule.contains('COUNT') && !rule.contains('UNTIL')) { + final DateFormat formatter = DateFormat('yyyyMMdd'); + final String newSubString = ';UNTIL=' + formatter.format(visibleEndDate); + rule = rule + newSubString; + } - if ((region.recurrenceRule.contains('UNTIL') || - region.recurrenceRule.contains('COUNT')) && - !(regionStartDate.isBefore(visibleEndDate) && + List recursiveDates; + final List ruleSeparator = ['=', ';', ',']; + final List rRule = + RecurrenceHelper.splitRule(recurrenceRule, ruleSeparator); + if (recurrenceRule.contains('UNTIL')) { + final String untilValue = rRule[rRule.indexOf('UNTIL') + 1]; + DateTime endDate = DateTime.parse(untilValue); + endDate = addDuration(endDate, + appointment.actualEndTime.difference(appointment.actualStartTime)); + endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); + if (!(appStartTime.isBefore(visibleEndDate) && visibleStartDate.isBefore(endDate))) { - return; - } - - recursiveDates = _getRecurrenceDateTimeCollection( - rule, region._actualStartTime, - recurrenceDuration: - region._actualEndTime.difference(region._actualStartTime), - specificStartDate: visibleStartDate, - specificEndDate: visibleEndDate); - - for (int j = 0; j < recursiveDates.length; j++) { - final DateTime recursiveDate = recursiveDates[j]; - if (region.recurrenceExceptionDates != null) { - bool isDateContains = false; - for (int i = 0; i < region.recurrenceExceptionDates.length; i++) { - final DateTime date = _convertTimeToAppointmentTimeZone( - region.recurrenceExceptionDates[i], '', calendarTimeZone); - if (date.year == recursiveDate.year && - date.month == recursiveDate.month && - date.day == recursiveDate.day) { - isDateContains = true; - break; - } + return; } - if (isDateContains) { - continue; + } else if (recurrenceRule.contains('COUNT')) { + recursiveDates = RecurrenceHelper.getRecurrenceDateTimeCollection( + recurrenceRule, appointment.actualStartTime); + DateTime endDate = recursiveDates.last; + endDate = addDuration(endDate, + appointment.actualEndTime.difference(appointment.actualStartTime)); + endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); + if (!(appStartTime.isBefore(visibleEndDate) && + visibleStartDate.isBefore(endDate))) { + return; } } - final TimeRegion occurrenceRegion = - _cloneRecurrenceRegion(region, recursiveDate, calendarTimeZone); - regions.add(occurrenceRegion); - } -} - -/// Returns the timeline appointment height based on the settings value. -double _getTimelineAppointmentHeight( - TimeSlotViewSettings settings, CalendarView view) { - if (settings.timelineAppointmentHeight != -1) { - return settings.timelineAppointmentHeight; - } - - if (view == CalendarView.timelineMonth) { - return 20; - } - - return 60; -} + recursiveDates = RecurrenceHelper.getRecurrenceDateTimeCollection( + rule, appointment.actualStartTime, + recurrenceDuration: + appointment.actualEndTime.difference(appointment.actualStartTime), + specificStartDate: visibleStartDate, + specificEndDate: visibleEndDate); -/// Used to clone the time region with new values. -TimeRegion _cloneRecurrenceRegion( - TimeRegion region, DateTime recursiveDate, String calendarTimeZone) { - final int minutes = - region._actualEndTime.difference(region._actualStartTime).inMinutes; - final DateTime actualEndTime = - addDuration(recursiveDate, Duration(minutes: minutes)); - final DateTime startDate = _convertTimeToAppointmentTimeZone( - recursiveDate, region.timeZone, calendarTimeZone); - - final DateTime endDate = _convertTimeToAppointmentTimeZone( - actualEndTime, region.timeZone, calendarTimeZone); - - final TimeRegion occurrenceRegion = - region.copyWith(startTime: startDate, endTime: endDate); - occurrenceRegion._actualStartTime = recursiveDate; - occurrenceRegion._actualEndTime = actualEndTime; - return occurrenceRegion; -} + for (int j = 0; j < recursiveDates.length; j++) { + final DateTime recursiveDate = recursiveDates[j]; + if (appointment.recurrenceExceptionDates != null) { + bool isDateContains = false; + for (int i = 0; i < appointment.recurrenceExceptionDates!.length; i++) { + final DateTime date = convertTimeToAppointmentTimeZone( + appointment.recurrenceExceptionDates![i], '', scheduleTimeZone); + if (date.year == recursiveDate.year && + date.month == recursiveDate.month && + date.day == recursiveDate.day) { + isDateContains = true; + break; + } + } + if (isDateContains) { + continue; + } + } -/// Returns the index of the passed id's resource from the passed resource -/// collection. -int _getResourceIndex(List resourceCollection, Object id) { - if (resourceCollection == null || resourceCollection.isEmpty) { - return -1; + final CalendarAppointment occurrenceAppointment = + _cloneRecurrenceAppointment( + appointment, recurrenceIndex, recursiveDate, scheduleTimeZone); + recurrenceIndex++; + appointments.add(occurrenceAppointment); + } } - - return resourceCollection.indexWhere((resource) => resource.id == id); } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/calendar_datasource.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/calendar_datasource.dart index 9a52e9f85..cb248280d 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/calendar_datasource.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/calendar_datasource.dart @@ -1,4 +1,13 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_calendar/src/calendar/appointment_engine/appointment_helper.dart'; +import 'package:syncfusion_flutter_calendar/src/calendar/common/calendar_view_helper.dart'; +import 'package:syncfusion_flutter_datepicker/datepicker.dart' + show IterableDiagnostics; + +import '../../../calendar.dart'; +import '../common/enums.dart'; +import '../resource_view/calendar_resource.dart'; /// An object that maintains the data source for [SfCalendar]. /// @@ -124,7 +133,105 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// } /// } ///``` - List appointments; + List? appointments; + + /// Returns the appointments in the specified date range. + /// + /// startDate - required - The starting date from which + /// to obtain the appointments. + /// + /// endDate - optional - The end date till which + /// to obtain the visible appointments. + /// + /// ```dart + /// + /// class MyAppState extends State { + /// + /// CalendarController _calendarController; + /// _AppointmentDataSource _dataSource; + /// + /// @override + /// initState() { + /// _calendarController = CalendarController(); + /// _dataSource = _getCalendarDataSource(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// controller: _calendarController, + /// dataSource: _dataSource, + /// onViewChanged: (ViewChangedDetails details) { + /// List dates = details.visibleDates; + /// String calendarTimeZone = ''; + /// List appointment = _dataSource.getVisibleAppointments( + /// dates[0], calendarTimeZone, + /// dates[(details.visibleDates.length) - 1]); + /// }, + /// ), + /// ), + /// ); + /// } + /// } + /// + /// _AppointmentDataSource _getCalendarDataSource() { + /// List appointments = []; + /// appointments.add(Appointment( + /// startTime: DateTime(2020, 11, 27, 9), + /// endTime: DateTime(2020, 11, 27, 9).add(Duration(hours: 2)), + /// subject: 'Meeting', + /// color: Colors.cyanAccent, + /// startTimeZone: '', + /// endTimeZone: '', + /// recurrenceRule: 'FREQ=DAILY;INTERVAL=2;COUNT=5', + /// )); + /// appointments.add(Appointment( + /// startTime: DateTime(2020, 11, 28, 5), + /// endTime: DateTime(2020, 11, 30, 7), + /// subject: 'Discussion', + /// color: Colors.orangeAccent, + /// startTimeZone: '', + /// endTimeZone: '', + /// isAllDay: true + /// )); + /// return _AppointmentDataSource(appointments); + /// }} + /// + /// class _AppointmentDataSource extends CalendarDataSource { + /// _AppointmentDataSource(List source) { + /// appointments = source; + /// } + /// } + /// ``` + List getVisibleAppointments( + DateTime startDate, String calendarTimeZone, + [DateTime? endDate]) { + endDate ??= startDate; + + /// Converts the given appointment type to calendar appointment, to handle + /// the internal operations like timezone converting. + /// Calendar appointment is an internal class to handle the appointment + /// rendering on view. + List calendarAppointments = + AppointmentHelper.generateCalendarAppointments(this, calendarTimeZone); + + calendarAppointments = AppointmentHelper.getVisibleAppointments( + startDate, endDate, calendarAppointments, calendarTimeZone, false, + canCreateNewAppointment: false); + + final List visibleAppointments = []; + + for (int i = 0; i < calendarAppointments.length; i++) { + visibleAppointments + .add(calendarAppointments[i].convertToCalendarAppointment()); + } + + return visibleAppointments; + } /// The collection of resource to be displayed in the timeline views of /// [SfCalendar]. @@ -142,7 +249,7 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// } /// /// ``` - List resources; + List? resources; /// Maps the custom appointments start time to the [Appointment]. /// @@ -163,8 +270,7 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// return appointments[index].from; /// } /// ``` - @protected - DateTime getStartTime(int index) => null; + DateTime getStartTime(int index) => DateTime.now(); /// Maps the custom appointments end time to the [Appointment]. /// @@ -185,8 +291,7 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// return appointments[index].to; /// } /// ``` - @protected - DateTime getEndTime(int index) => null; + DateTime getEndTime(int index) => DateTime.now(); /// Maps the custom appointments subject to the [Appointment]. /// @@ -204,7 +309,6 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// return appointments[index].title; /// } /// ``` - @protected String getSubject(int index) => ''; /// Maps the custom appointments isAllDay to the [Appointment]. @@ -223,7 +327,6 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// return appointments[index].isAllDay; /// } /// ``` - @protected bool isAllDay(int index) => false; /// Maps the custom appointments color to the [Appointment]. @@ -242,7 +345,6 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// return appointments[index].background; /// } /// ``` - @protected Color getColor(int index) => Colors.lightBlue; /// Maps the custom appointments notes to the [Appointment]. @@ -261,8 +363,7 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// return appointments[index].notes; /// } /// ``` - @protected - String getNotes(int index) => ''; + String? getNotes(int index) => null; /// Maps the custom appointments location to the [Appointment]. /// @@ -280,8 +381,7 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// return appointments[index].place; /// } /// ``` - @protected - String getLocation(int index) => ''; + String? getLocation(int index) => null; /// Maps the custom appointments start time zone to the [Appointment]. /// @@ -299,8 +399,7 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// return appointments[index].fromZone; /// } /// ``` - @protected - String getStartTimeZone(int index) => ''; + String? getStartTimeZone(int index) => null; /// Maps the custom appointments end time zone to the [Appointment]. /// @@ -318,8 +417,7 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// return appointments[index].toZone; /// } /// ``` - @protected - String getEndTimeZone(int index) => ''; + String? getEndTimeZone(int index) => null; /// Maps the custom appointments recurrence rule to the [Appointment]. /// @@ -337,8 +435,7 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// return appointments[index].recurrenceRule; /// } /// ``` - @protected - String getRecurrenceRule(int index) => ''; + String? getRecurrenceRule(int index) => null; /// Maps the custom appointments recurrenceExceptionDates to the [Appointment] /// @@ -356,8 +453,7 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// return appointments[index].exceptionDates; /// } /// ``` - @protected - List getRecurrenceExceptionDates(int index) => null; + List? getRecurrenceExceptionDates(int index) => null; /// Maps the custom appointment resource ids to the [Appointment] resource /// ids. @@ -376,8 +472,45 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// return appointments[index].resourceIds; /// } /// ``` + List? getResourceIds(int index) => null; + + /// Called when loadMoreAppointments function is called from the + /// loadMoreWidgetBuilder. + /// Call the [notifyListeners] to notify the calendar for data source changes. + /// + /// See also: [SfCalendar.loadMoreWidgetBuilder] + /// + /// ```dart + /// @override + /// void handleLoadMore(DateTime startDate, DateTime endDate){ + /// await Future.delayed(Duration(seconds: 5)); + /// List newColl = []; + /// for (DateTime date = startDate; + /// date.isBefore(endDate); + /// date = date.add(Duration(days: 1))) { + /// newColl.add(Appointment( + /// startTime: date, + /// endTime: date.add(Duration(hours: 2)), + /// subject: 'Meeting', + /// color: Colors.red, + /// )); + /// } + /// + /// appointments.addAll(newColl); + /// notifyListeners(CalendarDataSourceAction.add, newColl); + /// } + /// ``` @protected - List getResourceIds(int index) => null; + Future handleLoadMore(DateTime startDate, DateTime endDate) async {} + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(IterableDiagnostics(appointments) + .toDiagnosticsNode(name: 'appointments')); + properties.add(IterableDiagnostics(resources) + .toDiagnosticsNode(name: 'resources')); + } } /// Signature for callback that reports that a appointment collection set to the @@ -388,8 +521,8 @@ typedef CalendarDataSourceCallback = void Function( CalendarDataSourceAction, List); /// Notifier used to notify the action performed in the [CalendarDataSource] -class CalendarDataSourceChangeNotifier { - List _listeners; +class CalendarDataSourceChangeNotifier with Diagnosticable { + List? _listeners; /// Calls the listener every time the collection in the [CalendarDataSource] /// changed @@ -397,7 +530,7 @@ class CalendarDataSourceChangeNotifier { /// Listeners can be removed with [removeListener] void addListener(CalendarDataSourceCallback listener) { _listeners ??= []; - _listeners.add(listener); + _listeners!.add(listener); } /// remove the listener used for notify the data source changes. @@ -414,7 +547,7 @@ class CalendarDataSourceChangeNotifier { return; } - _listeners.remove(listener); + _listeners!.remove(listener); } /// Call all the registered listeners. @@ -437,10 +570,8 @@ class CalendarDataSourceChangeNotifier { return; } - for (final CalendarDataSourceCallback listener in _listeners) { - if (listener != null) { - listener(type, data); - } + for (final CalendarDataSourceCallback listener in _listeners!) { + listener(type, data); } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/month_appointment_helper.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/month_appointment_helper.dart index 2333fef39..5453f6efd 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/month_appointment_helper.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/month_appointment_helper.dart @@ -1,345 +1,368 @@ -part of calendar; - -_AppointmentView _getAppointmentView( - Appointment appointment, List<_AppointmentView> appointmentCollection, - [int resourceIndex]) { - _AppointmentView appointmentRenderer; - for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView view = appointmentCollection[i]; - if (view.appointment == null) { - appointmentRenderer = view; - break; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../common/calendar_view_helper.dart'; +import 'appointment_helper.dart'; + +/// Holds the static helper methods used for appointment rendering in calendar +/// month view. +class MonthAppointmentHelper { + static void _createVisibleAppointments( + List appointmentCollection, + List visibleAppointments, + List visibleDates, + int startIndex, + int endIndex) { + for (int i = 0; i < appointmentCollection.length; i++) { + final AppointmentView appointmentView = appointmentCollection[i]; + appointmentView.endIndex = -1; + appointmentView.startIndex = -1; + appointmentView.isSpanned = false; + appointmentView.position = -1; + appointmentView.maxPositions = 0; + appointmentView.canReuse = true; } - } - - if (appointmentRenderer == null) { - appointmentRenderer = _AppointmentView(); - appointmentRenderer.appointment = appointment; - appointmentRenderer.canReuse = false; - appointmentRenderer.resourceIndex = resourceIndex; - appointmentCollection.add(appointmentRenderer); - } - - appointmentRenderer.appointment = appointment; - appointmentRenderer.canReuse = false; - appointmentRenderer.resourceIndex = resourceIndex; - return appointmentRenderer; -} - -void _createVisibleAppointments( - List<_AppointmentView> appointmentCollection, - List visibleAppointments, - List visibleDates, - int startIndex, - int endIndex) { - for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; - appointmentView.endIndex = -1; - appointmentView.startIndex = -1; - appointmentView.isSpanned = false; - appointmentView.position = -1; - appointmentView.maxPositions = 0; - appointmentView.canReuse = true; - } - if (visibleAppointments == null) { - return; - } - - for (int i = 0; i < visibleAppointments.length; i++) { - final Appointment appointment = visibleAppointments[i]; - if (!appointment._isSpanned && - appointment._actualStartTime.day == appointment._actualEndTime.day && - appointment._actualStartTime.month == - appointment._actualEndTime.month) { - final _AppointmentView appointmentView = - _createAppointmentView(appointmentCollection); - appointmentView.appointment = appointment; - appointmentView.canReuse = false; - appointmentView.startIndex = - _getDateIndex(appointment._actualStartTime, visibleDates); - - /// Check the index value before the view start index then assign the - /// start index as visible start index - /// eg., if show trailing and leading dates as disabled and recurrence - /// appointment spanned from Aug 31 to Sep 2 then - /// In Aug month view, visible start and end index as 6, 36 but - /// appointment start and end index as 36, 38 - /// In Sep month view visible start and end index as 2, 31 but - /// appointment start and end index as 1, 3 - if (appointmentView.startIndex == -1 || - appointmentView.startIndex < startIndex) { - appointmentView.startIndex = startIndex; + for (int i = 0; i < visibleAppointments.length; i++) { + final CalendarAppointment appointment = visibleAppointments[i]; + if (!appointment.isSpanned && + appointment.actualStartTime.day == appointment.actualEndTime.day && + appointment.actualStartTime.month == + appointment.actualEndTime.month) { + final AppointmentView appointmentView = + _createAppointmentView(appointmentCollection); + appointmentView.appointment = appointment; + appointmentView.canReuse = false; + appointmentView.startIndex = + getDateIndex(appointment.actualStartTime, visibleDates); + + /// Check the index value before the view start index then assign the + /// start index as visible start index + /// eg., if show trailing and leading dates as disabled and recurrence + /// appointment spanned from Aug 31 to Sep 2 then + /// In Aug month view, visible start and end index as 6, 36 but + /// appointment start and end index as 36, 38 + /// In Sep month view visible start and end index as 2, 31 but + /// appointment start and end index as 1, 3 + if (appointmentView.startIndex == -1 || + appointmentView.startIndex < startIndex) { + appointmentView.startIndex = startIndex; + } + + appointmentView.endIndex = + getDateIndex(appointment.actualEndTime, visibleDates); + + /// Check the index value after the view end index then assign the + /// end index as visible end index + /// eg., if show trailing and leading dates as disabled and recurrence + /// appointment spanned from Aug 31 to Sep 2 then + /// In Aug month view, visible start and end index as 6, 36 but + /// appointment start and end index as 36, 38 + /// In Sep month view visible start and end index as 2, 31 but + /// appointment start and end index as 1, 3 + if (appointmentView.endIndex == -1 || + appointmentView.endIndex > endIndex) { + appointmentView.endIndex = endIndex; + } + + if (!appointmentCollection.contains(appointmentView)) { + appointmentCollection.add(appointmentView); + } + } else { + final AppointmentView appointmentView = + _createAppointmentView(appointmentCollection); + appointmentView.appointment = appointment; + appointmentView.canReuse = false; + appointmentView.startIndex = + getDateIndex(appointment.actualStartTime, visibleDates); + + /// Check the index value before the view start index then assign the + /// start index as visible start index + /// eg., if show trailing and leading dates as disabled and recurrence + /// appointment spanned from Aug 31 to Sep 2 then + /// In Aug month view, visible start and end index as 6, 36 but + /// appointment start and end index as 36, 38 + /// In Sep month view visible start and end index as 2, 31 but + /// appointment start and end index as 1, 3 + if (appointmentView.startIndex == -1 || + appointmentView.startIndex < startIndex) { + appointmentView.startIndex = startIndex; + } + + appointmentView.endIndex = + getDateIndex(appointment.actualEndTime, visibleDates); + + /// Check the index value after the view end index then assign the + /// end index as visible end index + /// eg., if show trailing and leading dates as disabled and recurrence + /// appointment spanned from Aug 31 to Sep 2 then + /// In Aug month view, visible start and end index as 6, 36 but + /// appointment start and end index as 36, 38 + /// In Sep month view visible start and end index as 2, 31 but + /// appointment start and end index as 1, 3 + if (appointmentView.endIndex == -1 || + appointmentView.endIndex > endIndex) { + appointmentView.endIndex = endIndex; + } + + _createAppointmentInfoForSpannedAppointment( + appointmentView, appointmentCollection); } + } + } + static void _createAppointmentInfoForSpannedAppointment( + AppointmentView appointmentView, + List appointmentCollection) { + if (appointmentView.startIndex ~/ DateTime.daysPerWeek != + appointmentView.endIndex ~/ DateTime.daysPerWeek) { + final int endIndex = appointmentView.endIndex; appointmentView.endIndex = - _getDateIndex(appointment._actualEndTime, visibleDates); - - /// Check the index value after the view end index then assign the - /// end index as visible end index - /// eg., if show trailing and leading dates as disabled and recurrence - /// appointment spanned from Aug 31 to Sep 2 then - /// In Aug month view, visible start and end index as 6, 36 but - /// appointment start and end index as 36, 38 - /// In Sep month view visible start and end index as 2, 31 but - /// appointment start and end index as 1, 3 - if (appointmentView.endIndex == -1 || - appointmentView.endIndex > endIndex) { - appointmentView.endIndex = endIndex; + ((((appointmentView.startIndex ~/ DateTime.daysPerWeek) + 1) * + DateTime.daysPerWeek) - + 1) + .toInt(); + appointmentView.isSpanned = true; + if (!appointmentCollection.contains(appointmentView)) { + appointmentCollection.add(appointmentView); } + final AppointmentView appointmentView1 = + _createAppointmentView(appointmentCollection); + appointmentView1.appointment = appointmentView.appointment; + appointmentView1.canReuse = false; + appointmentView1.startIndex = appointmentView.endIndex + 1; + appointmentView1.endIndex = endIndex; + _createAppointmentInfoForSpannedAppointment( + appointmentView1, appointmentCollection); + } else { + appointmentView.isSpanned = true; if (!appointmentCollection.contains(appointmentView)) { appointmentCollection.add(appointmentView); } - } else { - final _AppointmentView appointmentView = - _createAppointmentView(appointmentCollection); - appointmentView.appointment = appointment; - appointmentView.canReuse = false; - appointmentView.startIndex = - _getDateIndex(appointment._actualStartTime, visibleDates); - - /// Check the index value before the view start index then assign the - /// start index as visible start index - /// eg., if show trailing and leading dates as disabled and recurrence - /// appointment spanned from Aug 31 to Sep 2 then - /// In Aug month view, visible start and end index as 6, 36 but - /// appointment start and end index as 36, 38 - /// In Sep month view visible start and end index as 2, 31 but - /// appointment start and end index as 1, 3 - if (appointmentView.startIndex == -1 || - appointmentView.startIndex < startIndex) { - appointmentView.startIndex = startIndex; + } + } + + static void _setAppointmentPosition( + List appointmentViewCollection, + AppointmentView appointmentView, + int viewIndex) { + for (int j = 0; j < appointmentViewCollection.length; j++) { + //// Break when the collection reaches current appointment + if (j >= viewIndex) { + break; } - appointmentView.endIndex = - _getDateIndex(appointment._actualEndTime, visibleDates); - - /// Check the index value after the view end index then assign the - /// end index as visible end index - /// eg., if show trailing and leading dates as disabled and recurrence - /// appointment spanned from Aug 31 to Sep 2 then - /// In Aug month view, visible start and end index as 6, 36 but - /// appointment start and end index as 36, 38 - /// In Sep month view visible start and end index as 2, 31 but - /// appointment start and end index as 1, 3 - if (appointmentView.endIndex == -1 || - appointmentView.endIndex > endIndex) { - appointmentView.endIndex = endIndex; + final AppointmentView prevAppointmentView = appointmentViewCollection[j]; + if (!_isInterceptAppointments(appointmentView, prevAppointmentView)) { + continue; } - _createAppointmentInfoForSpannedAppointment( - appointmentView, appointmentCollection); + if (appointmentView.position == prevAppointmentView.position) { + appointmentView.position = appointmentView.position + 1; + appointmentView.maxPositions = appointmentView.position; + prevAppointmentView.maxPositions = appointmentView.position; + _setAppointmentPosition( + appointmentViewCollection, appointmentView, viewIndex); + break; + } } } -} -void _createAppointmentInfoForSpannedAppointment( - _AppointmentView appointmentView, - List<_AppointmentView> appointmentCollection) { - if (appointmentView.startIndex ~/ _kNumberOfDaysInWeek != - appointmentView.endIndex ~/ _kNumberOfDaysInWeek) { - final int endIndex = appointmentView.endIndex; - appointmentView.endIndex = - ((((appointmentView.startIndex ~/ _kNumberOfDaysInWeek) + 1) * - _kNumberOfDaysInWeek) - - 1) - .toInt(); - appointmentView.isSpanned = true; - if (appointmentCollection != null && - !appointmentCollection.contains(appointmentView)) { - appointmentCollection.add(appointmentView); + static bool _isInterceptAppointments( + AppointmentView appointmentView1, AppointmentView appointmentView2) { + if (appointmentView1.startIndex <= appointmentView2.startIndex && + appointmentView1.endIndex >= appointmentView2.startIndex || + appointmentView2.startIndex <= appointmentView1.startIndex && + appointmentView2.endIndex >= appointmentView1.startIndex) { + return true; } - final _AppointmentView appointmentView1 = - _createAppointmentView(appointmentCollection); - appointmentView1.appointment = appointmentView.appointment; - appointmentView1.canReuse = false; - appointmentView1.startIndex = appointmentView.endIndex + 1; - appointmentView1.endIndex = endIndex; - _createAppointmentInfoForSpannedAppointment( - appointmentView1, appointmentCollection); - } else { - appointmentView.isSpanned = true; - if (!appointmentCollection.contains(appointmentView)) { - appointmentCollection.add(appointmentView); + if (appointmentView1.startIndex <= appointmentView2.endIndex && + appointmentView1.endIndex >= appointmentView2.endIndex || + appointmentView2.startIndex <= appointmentView1.endIndex && + appointmentView2.endIndex >= appointmentView1.endIndex) { + return true; } - } -} -int _orderAppointmentViewBySpanned( - _AppointmentView appointmentView1, _AppointmentView appointmentView2) { - final int boolValue1 = appointmentView1.isSpanned ? -1 : 1; - final int boolValue2 = appointmentView2.isSpanned ? -1 : 1; - - if (boolValue1 == boolValue2 && - appointmentView2.startIndex == appointmentView1.startIndex) { - return (appointmentView2.endIndex - appointmentView2.startIndex) - .compareTo(appointmentView1.endIndex - appointmentView1.startIndex); + return false; } - return boolValue1.compareTo(boolValue2); -} - -void _setAppointmentPosition(List<_AppointmentView> appointmentViewCollection, - _AppointmentView appointmentView, int viewIndex) { - for (int j = 0; j < appointmentViewCollection.length; j++) { - //// Break when the collection reaches current appointment - if (j >= viewIndex) { - break; + /// Sort the appointment based on appointment start date, if both + /// the appointments have same start date then the appointment sorted based on + /// end date and its interval(difference between end time and start time). + static int _orderAppointmentViewBySpanned( + AppointmentView appointmentView1, AppointmentView appointmentView2) { + if (appointmentView1.appointment == null || + appointmentView2.appointment == null) { + return 0; } - final _AppointmentView prevAppointmentView = appointmentViewCollection[j]; - if (!_isInterceptAppointments(appointmentView, prevAppointmentView)) { - continue; + final CalendarAppointment appointment1 = appointmentView1.appointment!; + final CalendarAppointment appointment2 = appointmentView2.appointment!; + + /// Calculate the both appointment start time based on isAllDay property. + final DateTime startTime1 = appointment1.isAllDay + ? AppointmentHelper.convertToStartTime(appointment1.exactStartTime) + : appointment1.exactStartTime; + final DateTime startTime2 = appointment2.isAllDay + ? AppointmentHelper.convertToStartTime(appointment2.exactStartTime) + : appointment2.exactStartTime; + + /// Check if both the appointments does not starts with same date then + /// order the appointment based on its start time value. + /// Eg., app1 start with Nov3 and app2 start with Nov4 then compare both + /// the date value and it returns + /// a negative value if app1 start time before of app2 start time, + /// value 0 if app1 start time equal with app2 start time, and + /// a positive value otherwise (app1 start time after of app2 start time). + if (!isSameDate(startTime1, startTime2)) { + return startTime1.compareTo(startTime2); } - if (appointmentView.position == prevAppointmentView.position) { - appointmentView.position = appointmentView.position + 1; - appointmentView.maxPositions = appointmentView.position; - prevAppointmentView.maxPositions = appointmentView.position; - _setAppointmentPosition( - appointmentViewCollection, appointmentView, viewIndex); - break; - } - } -} + final DateTime endTime1 = appointment1.isAllDay + ? AppointmentHelper.convertToEndTime(appointment1.exactEndTime) + : appointment1.exactEndTime; + final DateTime endTime2 = appointment2.isAllDay + ? AppointmentHelper.convertToEndTime(appointment2.exactEndTime) + : appointment2.exactEndTime; + + /// Check both the appointments have same start and end time then sort the + /// appointments based on start time value. + /// Eg., app1 start with Nov3 10AM and ends with 11AM and app2 starts with + /// Nov3 9AM and ends with 11AM then swap the app2 before of app1. + if (isSameDate(endTime1, endTime2)) { + if (appointment1.isAllDay && appointment2.isAllDay) { + return 0; + } else if (appointment1.isAllDay && !appointment2.isAllDay) { + return -1; + } else if (appointment2.isAllDay && !appointment1.isAllDay) { + return 1; + } -bool _isInterceptAppointments( - _AppointmentView appointmentView1, _AppointmentView appointmentView2) { - if (appointmentView1.startIndex <= appointmentView2.startIndex && - appointmentView1.endIndex >= appointmentView2.startIndex || - appointmentView2.startIndex <= appointmentView1.startIndex && - appointmentView2.endIndex >= appointmentView1.startIndex) { - return true; - } + /// Check second appointment start time after the first appointment, then + /// swap list index value + return startTime1.compareTo(startTime2); + } - if (appointmentView1.startIndex <= appointmentView2.endIndex && - appointmentView1.endIndex >= appointmentView2.endIndex || - appointmentView2.startIndex <= appointmentView1.endIndex && - appointmentView2.endIndex >= appointmentView1.endIndex) { - return true; + /// Check second appointment occupy more cells than first appointment, then + /// swap list index value. + /// Eg., app1 start with Nov3 10AM and ends with Nov5 11AM and app2 starts + /// with Nov3 9AM and ends with Nov4 11AM then swap the app1 before of app2. + return (startTime2.difference(endTime2).inMinutes.abs()) + .compareTo(startTime1.difference(endTime1).inMinutes.abs()); } - return false; -} - -void _updateAppointmentPosition(List<_AppointmentView> appointmentCollection, - Map> indexAppointments) { - appointmentCollection.sort((_AppointmentView app1, _AppointmentView app2) { - if (app1.appointment?._actualStartTime != null && - app2.appointment?._actualStartTime != null) { - return app1.appointment._actualStartTime - .compareTo(app2.appointment._actualStartTime); - } + static void _updateAppointmentPosition( + List appointmentCollection, + Map> indexAppointments) { + appointmentCollection.sort(_orderAppointmentViewBySpanned); - return 0; - }); - appointmentCollection.sort((_AppointmentView app1, _AppointmentView app2) => - _orderAppointmentViewBySpanned(app1, app2)); - - for (int j = 0; j < appointmentCollection.length; j++) { - final _AppointmentView appointmentView = appointmentCollection[j]; - if (appointmentView.canReuse || appointmentView.appointment == null) { - continue; - } + for (int j = 0; j < appointmentCollection.length; j++) { + final AppointmentView appointmentView = appointmentCollection[j]; + if (appointmentView.canReuse || appointmentView.appointment == null) { + continue; + } - appointmentView.position = 1; - appointmentView.maxPositions = 1; - _setAppointmentPosition(appointmentCollection, appointmentView, j); - - /// Add the appointment views to index appointment based on start and end - /// index. It is used to get the visible index appointments. - for (int i = appointmentView.startIndex; - i <= appointmentView.endIndex; - i++) { - /// Check the index already have appointments, if exists then add the - /// current appointment to that collection, else create the index and - /// create new collection with current appointment. - if (indexAppointments.containsKey(i)) { - final List<_AppointmentView> existingAppointments = + appointmentView.position = 1; + appointmentView.maxPositions = 1; + _setAppointmentPosition(appointmentCollection, appointmentView, j); + + /// Add the appointment views to index appointment based on start and end + /// index. It is used to get the visible index appointments. + for (int i = appointmentView.startIndex; + i <= appointmentView.endIndex; + i++) { + /// Check the index already have appointments, if exists then add the + /// current appointment to that collection, else create the index and + /// create new collection with current appointment. + final List? existingAppointments = indexAppointments[i]; - existingAppointments.add(appointmentView); - indexAppointments[i] = existingAppointments; - } else { - indexAppointments[i] = <_AppointmentView>[appointmentView]; + if (existingAppointments != null) { + existingAppointments.add(appointmentView); + indexAppointments[i] = existingAppointments; + } else { + indexAppointments[i] = [appointmentView]; + } } } } -} -int _getDateIndex(DateTime date, List visibleDates) { - final int count = visibleDates.length; - DateTime dateTime = visibleDates[count - _kNumberOfDaysInWeek]; - int row = 0; - for (int i = count - _kNumberOfDaysInWeek; - i >= 0; - i -= _kNumberOfDaysInWeek) { - DateTime currentDate = visibleDates[i]; - currentDate = DateTime(currentDate.year, currentDate.month, currentDate.day, - currentDate.hour, currentDate.minute, currentDate.second); - if (currentDate.isBefore(date) || - (currentDate.day == date.day && - currentDate.month == date.month && - currentDate.year == date.year)) { - dateTime = currentDate; - row = i ~/ _kNumberOfDaysInWeek; - break; + /// Return the month cell dates index from visible dates. + static int getDateIndex(DateTime date, List visibleDates) { + final int count = visibleDates.length; + DateTime dateTime = visibleDates[count - DateTime.daysPerWeek]; + int row = 0; + for (int i = count - DateTime.daysPerWeek; + i >= 0; + i -= DateTime.daysPerWeek) { + DateTime currentDate = visibleDates[i]; + currentDate = DateTime( + currentDate.year, + currentDate.month, + currentDate.day, + currentDate.hour, + currentDate.minute, + currentDate.second); + if (currentDate.isBefore(date) || + (currentDate.day == date.day && + currentDate.month == date.month && + currentDate.year == date.year)) { + dateTime = currentDate; + row = i ~/ DateTime.daysPerWeek; + break; + } } - } - final DateTime endDateTime = addDuration(dateTime, const Duration(days: 6)); - int currentViewIndex = 0; - while (dateTime.isBefore(endDateTime) || - (dateTime.day == endDateTime.day && - dateTime.month == endDateTime.month && - dateTime.year == endDateTime.year)) { - if (dateTime.day == date.day && - dateTime.month == date.month && - dateTime.year == date.year) { - return ((row * _kNumberOfDaysInWeek) + currentViewIndex).toInt(); + final DateTime endDateTime = addDays(dateTime, 6); + int currentViewIndex = 0; + while ( + dateTime.isBefore(endDateTime) || isSameDate(dateTime, endDateTime)) { + if (isSameDate(dateTime, date)) { + return ((row * DateTime.daysPerWeek) + currentViewIndex).toInt(); + } + + currentViewIndex++; + dateTime = addDays(dateTime, 1); } - currentViewIndex++; - dateTime = addDuration(dateTime, const Duration(days: 1)); + return -1; } - return -1; -} - -_AppointmentView _createAppointmentView( - List<_AppointmentView> appointmentCollection) { - _AppointmentView appointmentView; - for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView view = appointmentCollection[i]; - if (view.canReuse) { - appointmentView = view; - break; + static AppointmentView _createAppointmentView( + List appointmentCollection) { + AppointmentView? appointmentView; + for (int i = 0; i < appointmentCollection.length; i++) { + final AppointmentView view = appointmentCollection[i]; + if (view.canReuse) { + appointmentView = view; + break; + } } - } - appointmentView = appointmentView ?? _AppointmentView(); - - appointmentView.endIndex = -1; - appointmentView.startIndex = -1; - appointmentView.position = -1; - appointmentView.maxPositions = 0; - appointmentView.isSpanned = false; - appointmentView.appointment = null; - appointmentView.canReuse = true; - return appointmentView; -} + appointmentView ??= AppointmentView(); + appointmentView.endIndex = -1; + appointmentView.startIndex = -1; + appointmentView.position = -1; + appointmentView.maxPositions = 0; + appointmentView.isSpanned = false; + appointmentView.appointment = null; + appointmentView.canReuse = true; + return appointmentView; + } -void _updateAppointment( - List visibleAppointments, - List<_AppointmentView> appointmentCollection, - List visibleDates, - Map> indexAppointments, - int startIndex, - int endIndex) { - _createVisibleAppointments(appointmentCollection, visibleAppointments, - visibleDates, startIndex, endIndex); - if (visibleAppointments != null && visibleAppointments.isNotEmpty) { - _updateAppointmentPosition(appointmentCollection, indexAppointments); + /// Update the appointment view details based on visible appointment and + /// visible dates. + static void updateAppointmentDetails( + List visibleAppointments, + List appointmentCollection, + List visibleDates, + Map> indexAppointments, + int startIndex, + int endIndex) { + _createVisibleAppointments(appointmentCollection, visibleAppointments, + visibleDates, startIndex, endIndex); + if (visibleAppointments.isNotEmpty) { + _updateAppointmentPosition(appointmentCollection, indexAppointments); + } } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_helper.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_helper.dart index 205cba4ac..aedb0436e 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_helper.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_helper.dart @@ -1,108 +1,102 @@ -part of calendar; - -/// Check the recurrence appointment in between the visible date range. -bool _isRecurrenceInBetweenSpecificRange(DateTime appointmentDate, - Duration duration, DateTime visibleStartDate, DateTime visibleEndTime) { - final DateTime appointmentEndDate = addDuration(appointmentDate, duration); - // ignore: lines_longer_than_80_chars - return isDateWithInDateRange(visibleStartDate, visibleEndTime, appointmentDate) || - isDateWithInDateRange( - visibleStartDate, visibleEndTime, appointmentEndDate) || - isDateWithInDateRange( - appointmentDate, appointmentEndDate, visibleStartDate); -} - -/// Returns the date time collection of recurring appointment. -List _getRecurrenceDateTimeCollection( - String rRule, DateTime recurrenceStartDate, - {Duration recurrenceDuration, - DateTime specificStartDate, - DateTime specificEndDate}) { - if (specificEndDate != null) { - specificEndDate = DateTime(specificEndDate.year, specificEndDate.month, - specificEndDate.day, 23, 59, 59); +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../common/enums.dart' show RecurrenceType, RecurrenceRange, WeekDays; +import 'appointment_helper.dart'; +import 'recurrence_properties.dart'; + +/// Holds the static helper methods used for handling recurrence in calendar. +class RecurrenceHelper { + /// Check the recurrence appointment in between the visible date range. + static bool _isRecurrenceInBetweenSpecificRange(DateTime appointmentDate, + Duration duration, DateTime visibleStartDate, DateTime visibleEndTime) { + final DateTime appointmentEndDate = addDuration(appointmentDate, duration); + + /// ignore: lines_longer_than_80_chars + return isDateWithInDateRange(visibleStartDate, visibleEndTime, appointmentDate) || + isDateWithInDateRange( + visibleStartDate, visibleEndTime, appointmentEndDate) || + isDateWithInDateRange( + appointmentDate, appointmentEndDate, visibleStartDate); } - recurrenceDuration ??= const Duration(); - final List recDateCollection = []; - final bool isSpecificDateRange = - specificStartDate != null && specificEndDate != null; - final List ruleSeparator = ['=', ';', ',']; - const String weeklySeparator = ';'; - - final List ruleArray = _splitRule(rRule, ruleSeparator); - int weeklyByDayPos; - int recCount = 0; - final List values = _findKeyIndex(ruleArray); - final String recurCount = values[0]; - final String daily = values[1]; - final String weekly = values[2]; - final String monthly = values[3]; - final String yearly = values[4]; - final String bySetPosCount = values[6]; - final String interval = values[7]; - final String intervalCount = values[8]; - final String untilValue = values[10]; - final String byDay = values[12]; - final String byDayValue = values[13]; - final String byMonthDay = values[14]; - final String byMonthDayCount = values[15]; - final String byMonthCount = values[17]; - final List weeklyRule = rRule.split(weeklySeparator); - final List weeklyRules = _findWeeklyRule(weeklyRule); - if (weeklyRules.isNotEmpty) { - weeklyByDayPos = int.parse(weeklyRules[1]); - } + /// Returns the date time collection of recurring appointment. + static List getRecurrenceDateTimeCollection( + String rRule, DateTime recurrenceStartDate, + {Duration? recurrenceDuration, + DateTime? specificStartDate, + DateTime? specificEndDate}) { + if (specificEndDate != null) { + specificEndDate = DateTime(specificEndDate.year, specificEndDate.month, + specificEndDate.day, 23, 59, 59); + } - if (ruleArray.isNotEmpty && rRule != null && rRule.isNotEmpty) { - DateTime addDate = recurrenceStartDate; - if (recurCount != null && recurCount.isNotEmpty) { - recCount = int.parse(recurCount); + recurrenceDuration ??= const Duration(); + final List recDateCollection = []; + final bool isSpecificDateRange = + specificStartDate != null && specificEndDate != null; + final List ruleSeparator = ['=', ';', ',']; + const String weeklySeparator = ';'; + + final List ruleArray = splitRule(rRule, ruleSeparator); + int weeklyByDayPos = -1; + int recCount = 0; + final List values = _findKeyIndex(ruleArray); + final String recurCount = values[0]; + final String daily = values[1]; + final String weekly = values[2]; + final String monthly = values[3]; + final String yearly = values[4]; + final String bySetPosCount = values[6]; + final String interval = values[7]; + final String intervalCount = values[8]; + final String untilValue = values[10]; + final String byDay = values[12]; + final String byDayValue = values[13]; + final String byMonthDay = values[14]; + final String byMonthDayCount = values[15]; + final String byMonthCount = values[17]; + final List weeklyRule = rRule.split(weeklySeparator); + final List weeklyRules = _findWeeklyRule(weeklyRule); + if (weeklyRules.isNotEmpty) { + weeklyByDayPos = int.parse(weeklyRules[1]); } - DateTime endDate; - if (rRule.contains('UNTIL')) { - endDate = DateTime.parse(untilValue); - endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 0); - if (isSpecificDateRange) { - endDate = - (endDate.isAfter(specificEndDate) || endDate == specificEndDate) - ? specificEndDate - : endDate; + if (ruleArray.isNotEmpty && rRule.isNotEmpty) { + final int recurrenceStartHour = recurrenceStartDate.hour; + final int recurrenceStartMinute = recurrenceStartDate.minute; + final int recurrenceStartSecond = recurrenceStartDate.second; + DateTime addDate = recurrenceStartDate; + if (recurCount.isNotEmpty) { + recCount = int.parse(recurCount); } - } -//// NoEndDate specified rule returns empty collection issue fix. - if (isSpecificDateRange && !rRule.contains('COUNT') && endDate == null) { - endDate = specificEndDate; - } + DateTime? endDate; + if (rRule.contains('UNTIL')) { + endDate = DateTime.parse(untilValue); + endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 0); + if (isSpecificDateRange) { + endDate = + (endDate.isAfter(specificEndDate) || endDate == specificEndDate) + ? specificEndDate + : endDate; + } + } - if (daily == 'DAILY') { - if (!rRule.contains('BYDAY')) { - final int dailyDayGap = - !rRule.contains('INTERVAL') ? 1 : int.parse(intervalCount); - int tempCount = 0; - while (tempCount < recCount || - (endDate != null && - (addDate.isBefore(endDate) || addDate == endDate))) { - if (isSpecificDateRange) { - if (_isRecurrenceInBetweenSpecificRange(addDate, recurrenceDuration, - specificStartDate, specificEndDate)) { - recDateCollection.add(addDate); - } - } else { - recDateCollection.add(addDate); - } + /// NoEndDate specified rule returns empty collection issue fix. + if (isSpecificDateRange && !rRule.contains('COUNT') && endDate == null) { + endDate = specificEndDate; + } - addDate = addDuration(addDate, Duration(days: dailyDayGap)); - tempCount++; - } - } else { - while (recDateCollection.length < recCount || - (endDate != null && - (addDate.isBefore(endDate) || addDate == endDate))) { - if (addDate.weekday != DateTime.sunday && - addDate.weekday != DateTime.saturday) { + if (daily == 'DAILY') { + if (!rRule.contains('BYDAY')) { + final int dailyDayGap = + !rRule.contains('INTERVAL') ? 1 : int.parse(intervalCount); + int tempCount = 0; + while (tempCount < recCount || + (endDate != null && + (addDate.isBefore(endDate) || + isSameDate(addDate, endDate)))) { if (isSpecificDateRange) { if (_isRecurrenceInBetweenSpecificRange(addDate, recurrenceDuration, specificStartDate, specificEndDate)) { @@ -111,335 +105,416 @@ List _getRecurrenceDateTimeCollection( } else { recDateCollection.add(addDate); } - } - - addDate = addDuration(addDate, const Duration(days: 1)); - } - } - } else if (weekly == 'WEEKLY') { - int tempCount = 0; - final int weeklyWeekGap = ruleArray.length > 4 && interval == 'INTERVAL' - ? int.parse(intervalCount) - : 1; - final bool isWeeklySelected = weeklyRule[weeklyByDayPos].length > 6; - - // Below code modified for fixing issue while setting rule as - // "FREQ=WEEKLY;COUNT=10;BYDAY=MO" along with specified start and end date - while ((tempCount < recCount && isWeeklySelected) || - (endDate != null && - (addDate.isBefore(endDate) || addDate == endDate))) { - if (isSpecificDateRange) { - if (_isRecurrenceInBetweenSpecificRange(addDate, recurrenceDuration, - specificStartDate, specificEndDate)) { - _setWeeklyRecurrenceDate( - addDate, weeklyRule, weeklyByDayPos, recDateCollection); - } - if (addDate.isAfter(specificEndDate)) { - break; + addDate = AppointmentHelper.addDaysWithTime( + addDate, + dailyDayGap, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + tempCount++; } } else { - _setWeeklyRecurrenceDate( - addDate, weeklyRule, weeklyByDayPos, recDateCollection); - } - - if (_isRecurrenceDate(addDate, weeklyRule, weeklyByDayPos)) { - tempCount++; - } + while (recDateCollection.length < recCount || + (endDate != null && + (addDate.isBefore(endDate) || addDate == endDate))) { + if (addDate.weekday != DateTime.sunday && + addDate.weekday != DateTime.saturday) { + if (isSpecificDateRange) { + if (_isRecurrenceInBetweenSpecificRange(addDate, + recurrenceDuration, specificStartDate, specificEndDate)) { + recDateCollection.add(addDate); + } + } else { + recDateCollection.add(addDate); + } + } - addDate = addDate.weekday == DateTime.saturday - ? addDuration( + addDate = AppointmentHelper.addDaysWithTime( addDate, - Duration( - days: ((weeklyWeekGap - 1) * _kNumberOfDaysInWeek) + 1)) - : addDuration(addDate, const Duration(days: 1)); - } - } else if (monthly == 'MONTHLY') { - final int monthlyMonthGap = ruleArray.length > 4 && interval == 'INTERVAL' - ? int.parse(intervalCount) - : 1; - if (byMonthDay == 'BYMONTHDAY') { - final int monthDate = int.parse(byMonthDayCount); - final int currDate = int.parse(recurrenceStartDate.day.toString()); - final DateTime temp = DateTime(addDate.year, addDate.month, monthDate, - addDate.hour, addDate.minute, addDate.second); - - /// Check the month date greater than recurrence start date and the - /// month have the date value. - /// Eg., Recurrence start date as Feb 28 and recurrence month day as - /// 30 then check the 30 greater than 28 and feb have 30 date. - if (monthDate >= currDate && temp.day == monthDate) { - addDate = temp; - } else { - /// Check the month date less than recurrence start date or the - /// month does not have the date - /// Eg., Recurrence start date as Feb 28 and recurrence month day as - /// 30 and feb 30 does not exist so move the recurrence to next month - /// and check next month have date 30 if exists then start the - /// recurrence from mar 30. - addDate = DateTime(addDate.year, addDate.month + 1, 1, addDate.hour, - addDate.minute, addDate.second); - final DateTime tempDate = DateTime(addDate.year, addDate.month, - monthDate, addDate.hour, addDate.minute, addDate.second); - if (tempDate.day == monthDate) { - addDate = tempDate; + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); } } - - final int yearValue = addDate.year; - final int hourValue = addDate.hour; - final int minuteValue = addDate.minute; - final int secondValue = addDate.second; - int monthValue = addDate.month; + } else if (weekly == 'WEEKLY') { int tempCount = 0; - while (tempCount < recCount || + final int weeklyWeekGap = ruleArray.length > 4 && interval == 'INTERVAL' + ? int.parse(intervalCount) + : 1; + assert(weeklyByDayPos != -1, 'Invalid weekly recurrence rule'); + final bool isWeeklySelected = weeklyRule[weeklyByDayPos].length > 6; + + /// Below code modified for fixing issue while setting rule as + /// "FREQ=WEEKLY;COUNT=10;BYDAY=MO" along with specified start and end + /// dates. + while ((tempCount < recCount && isWeeklySelected) || (endDate != null && (addDate.isBefore(endDate) || addDate == endDate))) { - if (addDate.day != monthDate) { - monthValue += monthlyMonthGap; - addDate = DateTime(yearValue, monthValue, monthDate, hourValue, - minuteValue, secondValue); - continue; - } - if (isSpecificDateRange) { if (_isRecurrenceInBetweenSpecificRange(addDate, recurrenceDuration, specificStartDate, specificEndDate)) { - recDateCollection.add(addDate); + _setWeeklyRecurrenceDate( + addDate, weeklyRule, weeklyByDayPos, recDateCollection); } if (addDate.isAfter(specificEndDate)) { break; } } else { - recDateCollection.add(addDate); + _setWeeklyRecurrenceDate( + addDate, weeklyRule, weeklyByDayPos, recDateCollection); } - monthValue += monthlyMonthGap; - addDate = DateTime(yearValue, monthValue, monthDate, hourValue, - minuteValue, secondValue); + if (_isRecurrenceDate(addDate, weeklyRule, weeklyByDayPos)) { + tempCount++; + } - tempCount++; + addDate = addDate.weekday == DateTime.saturday + ? AppointmentHelper.addDaysWithTime( + addDate, + ((weeklyWeekGap - 1) * DateTime.daysPerWeek) + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond) + : AppointmentHelper.addDaysWithTime( + addDate, + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); } - } else if (byDay == 'BYDAY') { - int tempRecDateCollectionCount = 0; - while (recDateCollection.length < recCount || - (endDate != null && - (addDate.isBefore(endDate) || addDate == endDate))) { - final DateTime monthStart = DateTime(addDate.year, addDate.month, 1, - addDate.hour, addDate.minute, addDate.second); - final DateTime weekStartDate = - addDuration(monthStart, Duration(days: -monthStart.weekday)); - final int monthStartWeekday = monthStart.weekday; - final int nthWeekDay = _getWeekDay(byDayValue); - int nthWeek; - if (monthStartWeekday <= nthWeekDay) { - nthWeek = int.parse(bySetPosCount) - 1; + } else if (monthly == 'MONTHLY') { + final int monthlyMonthGap = + ruleArray.length > 4 && interval == 'INTERVAL' + ? int.parse(intervalCount) + : 1; + if (byMonthDay == 'BYMONTHDAY') { + final int monthDate = int.parse(byMonthDayCount); + final int currDate = int.parse(recurrenceStartDate.day.toString()); + final DateTime temp = DateTime( + addDate.year, + addDate.month, + monthDate, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + + /// Check the month date greater than recurrence start date and the + /// month have the date value. + /// Eg., Recurrence start date as Feb 28 and recurrence month day as + /// 30 then check the 30 greater than 28 and feb have 30 date. + if (monthDate >= currDate && temp.day == monthDate) { + addDate = temp; } else { - nthWeek = int.parse(bySetPosCount); + /// Check the month date less than recurrence start date or the + /// month does not have the date + /// Eg., Recurrence start date as Feb 28 and recurrence month day as + /// 30 and feb 30 does not exist so move the recurrence to next + /// month and check next month have date 30 if exists then start the + /// recurrence from mar 30. + addDate = DateTime( + addDate.year, + addDate.month + 1, + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + final DateTime tempDate = DateTime( + addDate.year, + addDate.month, + monthDate, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + if (tempDate.day == monthDate) { + addDate = tempDate; + } } - addDate = addDuration( - weekStartDate, Duration(days: nthWeek * _kNumberOfDaysInWeek)); - addDate = addDuration(addDate, Duration(days: nthWeekDay)); - if (addDate.isBefore(recurrenceStartDate)) { - addDate = DateTime(addDate.year, addDate.month + 1, addDate.day, - addDate.hour, addDate.minute, addDate.second); - continue; - } + final int yearValue = addDate.year; + int monthValue = addDate.month; + int tempCount = 0; + while (tempCount < recCount || + (endDate != null && + (addDate.isBefore(endDate) || addDate == endDate))) { + if (addDate.day != monthDate) { + monthValue += monthlyMonthGap; + addDate = DateTime( + yearValue, + monthValue, + monthDate, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + continue; + } - if (isSpecificDateRange) { - if (_isRecurrenceInBetweenSpecificRange(addDate, recurrenceDuration, - specificStartDate, specificEndDate) && - (tempRecDateCollectionCount < recCount || - rRule.contains('UNTIL'))) { + if (isSpecificDateRange) { + if (_isRecurrenceInBetweenSpecificRange(addDate, + recurrenceDuration, specificStartDate, specificEndDate)) { + recDateCollection.add(addDate); + } + + if (addDate.isAfter(specificEndDate)) { + break; + } + } else { recDateCollection.add(addDate); } - if (addDate.isAfter(specificEndDate)) { - break; - } - } else { - recDateCollection.add(addDate); + monthValue += monthlyMonthGap; + addDate = DateTime( + yearValue, + monthValue, + monthDate, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + + tempCount++; } + } else if (byDay == 'BYDAY') { + int tempRecDateCollectionCount = 0; + while (recDateCollection.length < recCount || + (endDate != null && + (addDate.isBefore(endDate) || addDate == endDate))) { + final DateTime monthStart = DateTime( + addDate.year, + addDate.month, + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + final DateTime weekStartDate = AppointmentHelper.addDaysWithTime( + monthStart, + -monthStart.weekday, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + final int monthStartWeekday = monthStart.weekday; + final int nthWeekDay = _getWeekDay(byDayValue); + int nthWeek; + if (monthStartWeekday <= nthWeekDay) { + nthWeek = int.parse(bySetPosCount) - 1; + } else { + nthWeek = int.parse(bySetPosCount); + } - addDate = DateTime( - addDate.year, - addDate.month + monthlyMonthGap, - addDate.day - (addDate.day - 1), - addDate.hour, - addDate.minute, - addDate.second); - tempRecDateCollectionCount++; - } - } - } else if (yearly == 'YEARLY') { - final int yearlyYearGap = ruleArray.length > 4 && interval == 'INTERVAL' - ? int.parse(intervalCount) - : 1; - if (byMonthDay == 'BYMONTHDAY') { - final int monthIndex = int.parse(byMonthCount); - final int dayIndex = int.parse(byMonthDayCount); - if (monthIndex > 0 && monthIndex <= 12) { - final int bound = subtractDuration( - DateTime(addDate.year, addDate.month + 1, 1), - const Duration(days: 1)) - .day; - if (bound >= dayIndex) { - final DateTime specificDate = DateTime(addDate.year, monthIndex, - dayIndex, addDate.hour, addDate.minute, addDate.second); - if (specificDate.isBefore(addDate)) { - addDate = specificDate; - addDate = DateTime(addDate.year + 1, addDate.month, addDate.day, - addDate.hour, addDate.minute, addDate.second); - if (isSpecificDateRange) { - if (_isRecurrenceInBetweenSpecificRange(addDate, - recurrenceDuration, specificStartDate, specificEndDate)) { - recDateCollection.add(addDate); - } - } else { + final int bySetPosValue = int.parse(bySetPosCount); + if (bySetPosValue.isNegative) { + addDate = _getRecurrenceDateForNegativeValue( + bySetPosValue, monthStart, nthWeekDay); + } else { + addDate = AppointmentHelper.addDaysWithTime( + weekStartDate, + (nthWeek * DateTime.daysPerWeek) + nthWeekDay, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + } + + if (addDate.isBefore(recurrenceStartDate)) { + addDate = DateTime( + addDate.year, + addDate.month + 1, + addDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + continue; + } + + if (isSpecificDateRange) { + if (_isRecurrenceInBetweenSpecificRange(addDate, + recurrenceDuration, specificStartDate, specificEndDate) && + (tempRecDateCollectionCount < recCount || + rRule.contains('UNTIL'))) { recDateCollection.add(addDate); } + + if (addDate.isAfter(specificEndDate)) { + break; + } } else { - addDate = specificDate; + recDateCollection.add(addDate); } - int tempCount = 0; - while (tempCount < recCount || - (endDate != null && - (addDate.isBefore(endDate) || addDate == endDate))) { - if (!recDateCollection.contains(addDate)) { + addDate = DateTime( + addDate.year, + addDate.month + monthlyMonthGap, + addDate.day - (addDate.day - 1), + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + + tempRecDateCollectionCount++; + } + } + } else if (yearly == 'YEARLY') { + final int yearlyYearGap = ruleArray.length > 4 && interval == 'INTERVAL' + ? int.parse(intervalCount) + : 1; + if (byMonthDay == 'BYMONTHDAY') { + final int monthIndex = int.parse(byMonthCount); + final int dayIndex = int.parse(byMonthDayCount); + if (monthIndex > 0 && monthIndex <= 12) { + final int bound = + addDays(DateTime(addDate.year, addDate.month + 1, 1), -1).day; + if (bound >= dayIndex) { + final DateTime specificDate = DateTime( + addDate.year, + monthIndex, + dayIndex, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + if (specificDate.isBefore(addDate)) { + addDate = specificDate; + addDate = DateTime( + addDate.year + 1, + addDate.month, + addDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); if (isSpecificDateRange) { if (_isRecurrenceInBetweenSpecificRange(addDate, recurrenceDuration, specificStartDate, specificEndDate)) { recDateCollection.add(addDate); } - - if (addDate.isAfter(specificEndDate)) { - break; - } } else { recDateCollection.add(addDate); } + } else { + addDate = specificDate; } - addDate = DateTime(addDate.year + yearlyYearGap, addDate.month, - addDate.day, addDate.hour, addDate.minute, addDate.second); - tempCount++; + int tempCount = 0; + while (tempCount < recCount || + (endDate != null && + (addDate.isBefore(endDate) || addDate == endDate))) { + if (!recDateCollection.contains(addDate)) { + if (isSpecificDateRange) { + if (_isRecurrenceInBetweenSpecificRange( + addDate, + recurrenceDuration, + specificStartDate, + specificEndDate)) { + recDateCollection.add(addDate); + } + + if (addDate.isAfter(specificEndDate)) { + break; + } + } else { + recDateCollection.add(addDate); + } + } + + addDate = DateTime( + addDate.year + yearlyYearGap, + addDate.month, + addDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + tempCount++; + } } } - } - } else if (byDay == 'BYDAY') { - final int monthIndex = int.parse(byMonthCount); - DateTime monthStart = DateTime(addDate.year, monthIndex, 1, - addDate.hour, addDate.minute, addDate.second); - DateTime weekStartDate = - addDuration(monthStart, Duration(days: -monthStart.weekday)); - int monthStartWeekday = monthStart.weekday; - int nthWeekDay = _getWeekDay(byDayValue); - int nthWeek; - if (monthStartWeekday <= nthWeekDay) { - nthWeek = int.parse(bySetPosCount) - 1; - } else { - nthWeek = int.parse(bySetPosCount); - } - - monthStart = addDuration( - weekStartDate, Duration(days: nthWeek * _kNumberOfDaysInWeek)); - monthStart = addDuration(monthStart, Duration(days: nthWeekDay)); - - if ((monthStart.month != addDate.month && - monthStart.isBefore(addDate)) || - (monthStart.month == addDate.month && - (monthStart.isBefore(addDate) || - monthStart.isBefore(recurrenceStartDate)))) { - addDate = DateTime(addDate.year + 1, addDate.month, addDate.day, - addDate.hour, addDate.minute, addDate.second); - monthStart = DateTime(addDate.year, monthIndex, 1, addDate.hour, - addDate.minute, addDate.second); - weekStartDate = - addDuration(monthStart, Duration(days: -monthStart.weekday)); - monthStartWeekday = monthStart.weekday; - nthWeekDay = _getWeekDay(byDayValue); + } else if (byDay == 'BYDAY') { + final int monthIndex = int.parse(byMonthCount); + DateTime monthStart = DateTime( + addDate.year, + monthIndex, + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + DateTime weekStartDate = AppointmentHelper.addDaysWithTime( + monthStart, + -monthStart.weekday, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + int monthStartWeekday = monthStart.weekday; + int nthWeekDay = _getWeekDay(byDayValue); + int nthWeek; if (monthStartWeekday <= nthWeekDay) { nthWeek = int.parse(bySetPosCount) - 1; } else { nthWeek = int.parse(bySetPosCount); } - monthStart = addDuration( - weekStartDate, Duration(days: nthWeek * _kNumberOfDaysInWeek)); - monthStart = addDuration(monthStart, Duration(days: nthWeekDay)); - - addDate = monthStart; + final int bySetPosValue = int.parse(bySetPosCount); + if (bySetPosValue.isNegative) { + monthStart = _getRecurrenceDateForNegativeValue( + bySetPosValue, monthStart, nthWeekDay); + } else { + monthStart = AppointmentHelper.addDaysWithTime( + weekStartDate, + (nthWeek * DateTime.daysPerWeek) + nthWeekDay, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + } - if (!recDateCollection.contains(addDate)) { - if (isSpecificDateRange) { - if (_isRecurrenceInBetweenSpecificRange(addDate, - recurrenceDuration, specificStartDate, specificEndDate)) { - recDateCollection.add(addDate); - } + if ((monthStart.month != addDate.month && + monthStart.isBefore(addDate)) || + (monthStart.month == addDate.month && + (monthStart.isBefore(addDate) || + monthStart.isBefore(recurrenceStartDate)))) { + addDate = DateTime( + addDate.year + 1, + addDate.month, + addDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + monthStart = DateTime( + addDate.year, + monthIndex, + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + weekStartDate = AppointmentHelper.addDaysWithTime( + monthStart, + -monthStart.weekday, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + monthStartWeekday = monthStart.weekday; + nthWeekDay = _getWeekDay(byDayValue); + if (monthStartWeekday <= nthWeekDay) { + nthWeek = int.parse(bySetPosCount) - 1; } else { - recDateCollection.add(addDate); + nthWeek = int.parse(bySetPosCount); } - } - } else { - addDate = monthStart; - } - int tempCount = 0; - while (tempCount < recCount || - (endDate != null && - (addDate.isBefore(endDate) || addDate == endDate))) { - if (!recDateCollection.contains(addDate)) { - if (isSpecificDateRange) { - if (_isRecurrenceInBetweenSpecificRange(addDate, - recurrenceDuration, specificStartDate, specificEndDate)) { - recDateCollection.add(addDate); - } - - if (addDate.isAfter(specificEndDate)) { - break; - } + final int bySetPosValue = int.parse(bySetPosCount); + if (bySetPosValue.isNegative) { + monthStart = _getRecurrenceDateForNegativeValue( + bySetPosValue, monthStart, nthWeekDay); } else { - recDateCollection.add(addDate); + monthStart = AppointmentHelper.addDaysWithTime( + weekStartDate, + (nthWeek * DateTime.daysPerWeek) + nthWeekDay, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); } - } - - addDate = DateTime(addDate.year + yearlyYearGap, addDate.month, - addDate.day, addDate.hour, addDate.minute, addDate.second); - - monthStart = DateTime(addDate.year, monthIndex, 1, addDate.hour, - addDate.minute, addDate.second); - - weekStartDate = - addDuration(monthStart, Duration(days: -monthStart.weekday)); - monthStartWeekday = monthStart.weekday; - nthWeekDay = _getWeekDay(byDayValue); - if (monthStartWeekday <= nthWeekDay) { - nthWeek = int.parse(bySetPosCount) - 1; - } else { - nthWeek = int.parse(bySetPosCount); - } - monthStart = addDuration(weekStartDate, - Duration(days: (nthWeek * _kNumberOfDaysInWeek) + nthWeekDay)); - - if (monthStart.month != addDate.month && - monthStart.isBefore(addDate)) { addDate = monthStart; - addDate = DateTime(addDate.year + 1, addDate.month, addDate.day, - addDate.hour, addDate.minute, addDate.second); + if (!recDateCollection.contains(addDate)) { if (isSpecificDateRange) { if (_isRecurrenceInBetweenSpecificRange(addDate, recurrenceDuration, specificStartDate, specificEndDate)) { recDateCollection.add(addDate); } - - if (addDate.isAfter(specificEndDate)) { - break; - } } else { recDateCollection.add(addDate); } @@ -448,966 +523,1104 @@ List _getRecurrenceDateTimeCollection( addDate = monthStart; } - tempCount++; - } - } - } - } - - return recDateCollection; -} - -/// Returns the recurrence properties based on the given recurrence rule and -/// the recurrence start date. -RecurrenceProperties _parseRRule(String rRule, DateTime recStartDate) { - final DateTime recurrenceStartDate = recStartDate; - final RecurrenceProperties recProp = RecurrenceProperties(); - - recProp.startDate = recStartDate; - final List ruleSeparator = ['=', ';', ',']; - const String weeklySeparator = ';'; - final List ruleArray = _splitRule(rRule, ruleSeparator); - final List weeklyRule = rRule.split(weeklySeparator); - int weeklyByDayPos; - int recCount = 0; - final List resultList = _findKeyIndex(ruleArray); - final String recurCount = resultList[0]; - final String daily = resultList[1]; - final String weekly = resultList[2]; - final String monthly = resultList[3]; - final String yearly = resultList[4]; - final String bySetPosCount = resultList[6]; - final String intervalCount = resultList[8]; - final String untilValue = resultList[10]; - final String byDay = resultList[12]; - final String byDayValue = resultList[13]; - final String byMonthDay = resultList[14]; - final String byMonthDayCount = resultList[15]; - final String byMonthCount = resultList[17]; - final List weeklyRules = _findWeeklyRule(weeklyRule); - if (weeklyRules.isNotEmpty) { - weeklyByDayPos = int.parse(weeklyRules[1]); - } - - if (ruleArray.isNotEmpty && rRule != null && rRule.isNotEmpty) { - DateTime addDate = recurrenceStartDate; - if (recurCount != null && recurCount.isNotEmpty) { - recCount = int.parse(recurCount); - } - - if (!rRule.contains('COUNT') && !rRule.contains('UNTIL')) { - recProp.recurrenceRange = RecurrenceRange.noEndDate; - } else if (rRule.contains('COUNT')) { - recProp.recurrenceRange = RecurrenceRange.count; - recProp.recurrenceCount = recCount; - } else if (rRule.contains('UNTIL')) { - recProp.recurrenceRange = RecurrenceRange.endDate; - DateTime endDate = DateTime.parse(untilValue); - endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); - recProp.endDate = endDate; - } - - recProp.interval = intervalCount != null && intervalCount.isNotEmpty - ? int.parse(intervalCount) - : 1; - if (daily == 'DAILY') { - recProp.recurrenceType = RecurrenceType.daily; - - if (rRule.contains('BYDAY')) { - recProp.weekDays = [ - WeekDays.monday, - WeekDays.tuesday, - WeekDays.wednesday, - WeekDays.thursday, - WeekDays.friday - ]; - } - } else if (weekly == 'WEEKLY') { - recProp.recurrenceType = RecurrenceType.weekly; - int i = 0; - - while (i < _kNumberOfDaysInWeek && weeklyByDayPos != null) { - switch (addDate.weekday) { - case DateTime.sunday: - { - if (weeklyRule[weeklyByDayPos].contains('SU')) { - recProp.weekDays.add(WeekDays.sunday); - } - - break; - } + int tempCount = 0; + while (tempCount < recCount || + (endDate != null && + (addDate.isBefore(endDate) || addDate == endDate))) { + if (!recDateCollection.contains(addDate)) { + if (isSpecificDateRange) { + if (_isRecurrenceInBetweenSpecificRange(addDate, + recurrenceDuration, specificStartDate, specificEndDate)) { + recDateCollection.add(addDate); + } - case DateTime.monday: - { - if (weeklyRule[weeklyByDayPos].contains('MO')) { - recProp.weekDays.add(WeekDays.monday); + if (addDate.isAfter(specificEndDate)) { + break; + } + } else { + recDateCollection.add(addDate); } - - break; } - case DateTime.tuesday: - { - if (weeklyRule[weeklyByDayPos].contains('TU')) { - recProp.weekDays.add(WeekDays.tuesday); - } - - break; + addDate = DateTime( + addDate.year + yearlyYearGap, + addDate.month, + addDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + + monthStart = DateTime( + addDate.year, + monthIndex, + 1, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + + weekStartDate = AppointmentHelper.addDaysWithTime( + monthStart, + -monthStart.weekday, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + monthStartWeekday = monthStart.weekday; + nthWeekDay = _getWeekDay(byDayValue); + if (monthStartWeekday <= nthWeekDay) { + nthWeek = int.parse(bySetPosCount) - 1; + } else { + nthWeek = int.parse(bySetPosCount); } - case DateTime.wednesday: - { - if (weeklyRule[weeklyByDayPos].contains('WE')) { - recProp.weekDays.add(WeekDays.wednesday); - } - - break; + final int bySetPosValue = int.parse(bySetPosCount); + if (bySetPosValue.isNegative) { + monthStart = _getRecurrenceDateForNegativeValue( + bySetPosValue, monthStart, nthWeekDay); + } else { + monthStart = AppointmentHelper.addDaysWithTime( + weekStartDate, + (nthWeek * DateTime.daysPerWeek) + nthWeekDay, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); } - case DateTime.thursday: - { - if (weeklyRule[weeklyByDayPos].contains('TH')) { - recProp.weekDays.add(WeekDays.thursday); - } - - break; - } + if (monthStart.month != addDate.month && + monthStart.isBefore(addDate)) { + addDate = monthStart; + addDate = DateTime( + addDate.year + 1, + addDate.month, + addDate.day, + recurrenceStartHour, + recurrenceStartMinute, + recurrenceStartSecond); + if (!recDateCollection.contains(addDate)) { + if (isSpecificDateRange) { + if (_isRecurrenceInBetweenSpecificRange(addDate, + recurrenceDuration, specificStartDate, specificEndDate)) { + recDateCollection.add(addDate); + } - case DateTime.friday: - { - if (weeklyRule[weeklyByDayPos].contains('FR')) { - recProp.weekDays.add(WeekDays.friday); + if (addDate.isAfter(specificEndDate)) { + break; + } + } else { + recDateCollection.add(addDate); + } } - - break; + } else { + addDate = monthStart; } - case DateTime.saturday: - { - if (weeklyRule[weeklyByDayPos].contains('SA')) { - recProp.weekDays.add(WeekDays.saturday); - } - - break; - } + tempCount++; + } } - - addDate = addDate.weekday == 6 - ? addDuration( - addDate, - Duration( - days: ((recProp.interval - 1) * _kNumberOfDaysInWeek) + 1)) - : addDuration(addDate, const Duration(days: 1)); - i = i + 1; - } - } else if (monthly == 'MONTHLY') { - recProp.recurrenceType = RecurrenceType.monthly; - if (byMonthDay == 'BYMONTHDAY') { - recProp.week = -1; - recProp.dayOfMonth = - byMonthDayCount.isNotEmpty ? int.parse(byMonthDayCount) : 1; - } else if (byDay == 'BYDAY') { - recProp.week = bySetPosCount.isNotEmpty ? int.parse(bySetPosCount) : -1; - recProp.dayOfWeek = byDayValue.isNotEmpty ? _getWeekDay(byDayValue) : 1; - } - } else if (yearly == 'YEARLY') { - recProp.recurrenceType = RecurrenceType.yearly; - if (byMonthDay == 'BYMONTHDAY') { - recProp.month = byMonthCount.isNotEmpty ? int.parse(byMonthCount) : 1; - recProp.dayOfMonth = - byMonthDayCount.isNotEmpty ? int.parse(byMonthDayCount) : 1; - } else if (byDay == 'BYDAY') { - recProp.month = byMonthCount.isNotEmpty ? int.parse(byMonthCount) : 1; - recProp.week = bySetPosCount.isNotEmpty ? int.parse(bySetPosCount) : -1; - recProp.dayOfWeek = byDayValue.isNotEmpty ? _getWeekDay(byDayValue) : 1; } } - } - return recProp; -} + return recDateCollection; + } -String _getRRuleForDaily( - int recCount, - RecurrenceProperties recurrenceProperties, - DateTime startDate, - DateTime endDate, - bool isValidRecurrence, - Duration diffTimeSpan) { - String rRule = ''; - if ((recCount > 0 && - recurrenceProperties.recurrenceRange == RecurrenceRange.count) || - recurrenceProperties.recurrenceRange == RecurrenceRange.noEndDate || - ((startDate.isBefore(endDate) || startDate == endDate) && - recurrenceProperties.recurrenceRange == RecurrenceRange.endDate)) { - rRule = 'FREQ=DAILY'; - - if (recurrenceProperties.weekDays.contains(WeekDays.monday) && - recurrenceProperties.weekDays.contains(WeekDays.tuesday) && - recurrenceProperties.weekDays.contains(WeekDays.wednesday) && - recurrenceProperties.weekDays.contains(WeekDays.thursday) && - recurrenceProperties.weekDays.contains(WeekDays.friday)) { - if (diffTimeSpan.inHours > 24) { - isValidRecurrence = false; - } + /// Returns the recurrence properties based on the given recurrence rule and + /// the recurrence start date. + static RecurrenceProperties parseRRule(String rRule, DateTime recStartDate) { + final DateTime recurrenceStartDate = recStartDate; + final RecurrenceProperties recProp = + RecurrenceProperties(startDate: recStartDate); + final List ruleSeparator = ['=', ';', ',']; + const String weeklySeparator = ';'; + final List ruleArray = splitRule(rRule, ruleSeparator); + final List weeklyRule = rRule.split(weeklySeparator); + int weeklyByDayPos = -1; + int recCount = 0; + final List resultList = _findKeyIndex(ruleArray); + final String recurCount = resultList[0]; + final String daily = resultList[1]; + final String weekly = resultList[2]; + final String monthly = resultList[3]; + final String yearly = resultList[4]; + final String bySetPosCount = resultList[6]; + final String intervalCount = resultList[8]; + final String untilValue = resultList[10]; + final String byDay = resultList[12]; + final String byDayValue = resultList[13]; + final String byMonthDay = resultList[14]; + final String byMonthDayCount = resultList[15]; + final String byMonthCount = resultList[17]; + final List weeklyRules = _findWeeklyRule(weeklyRule); + if (weeklyRules.isNotEmpty) { + weeklyByDayPos = int.parse(weeklyRules[1]); + } - rRule = rRule + ';BYDAY=MO,TU,WE,TH,FR'; - } else { - if (diffTimeSpan.inHours >= recurrenceProperties.interval * 24) { - isValidRecurrence = false; + if (ruleArray.isNotEmpty && rRule.isNotEmpty) { + DateTime addDate = recurrenceStartDate; + if (recurCount.isNotEmpty) { + recCount = int.parse(recurCount); } - if (recurrenceProperties.interval > 0) { - rRule = rRule + ';INTERVAL=' + recurrenceProperties.interval.toString(); + if (!rRule.contains('COUNT') && !rRule.contains('UNTIL')) { + recProp.recurrenceRange = RecurrenceRange.noEndDate; + } else if (rRule.contains('COUNT')) { + recProp.recurrenceRange = RecurrenceRange.count; + recProp.recurrenceCount = recCount; + } else if (rRule.contains('UNTIL')) { + recProp.recurrenceRange = RecurrenceRange.endDate; + DateTime endDate = DateTime.parse(untilValue); + endDate = + DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); + recProp.endDate = endDate; } - } - - if (recurrenceProperties.recurrenceRange == RecurrenceRange.count) { - rRule = rRule + ';COUNT=' + recCount.toString(); - } else if (recurrenceProperties.recurrenceRange == - RecurrenceRange.endDate) { - final DateFormat format = DateFormat('yyyyMMdd'); - rRule = rRule + ';UNTIL=' + format.format(endDate); - } - } - if (!isValidRecurrence) { - rRule = ''; - } + recProp.interval = + intervalCount.isNotEmpty ? int.parse(intervalCount) : 1; + if (daily == 'DAILY') { + recProp.recurrenceType = RecurrenceType.daily; + + if (rRule.contains('BYDAY')) { + recProp.weekDays = [ + WeekDays.monday, + WeekDays.tuesday, + WeekDays.wednesday, + WeekDays.thursday, + WeekDays.friday + ]; + } + } else if (weekly == 'WEEKLY') { + recProp.recurrenceType = RecurrenceType.weekly; + int i = 0; + + while (i < DateTime.daysPerWeek && weeklyByDayPos != -1) { + switch (addDate.weekday) { + case DateTime.sunday: + { + if (weeklyRule[weeklyByDayPos].contains('SU')) { + recProp.weekDays.add(WeekDays.sunday); + } - return rRule; -} + break; + } -String _getRRuleForWeekly( - DateTime startDate, - DateTime endDate, - int recCount, - RecurrenceProperties recurrenceProperties, - bool isValidRecurrence, - Duration diffTimeSpan, - DateTime prevDate) { - String rRule = ''; - DateTime addDate = startDate; - if ((recCount > 0 && - recurrenceProperties.recurrenceRange == RecurrenceRange.count) || - recurrenceProperties.recurrenceRange == RecurrenceRange.noEndDate || - ((startDate.isBefore(endDate) || startDate == endDate) && - recurrenceProperties.recurrenceRange == RecurrenceRange.endDate)) { - rRule = 'FREQ=WEEKLY'; - String byDay = ''; - int su = 0, mo = 0, tu = 0, we = 0, th = 0, fr = 0, sa = 0; - String dayKey = ''; - int dayCount = 0; - rRule = rRule + ';BYDAY='; - int count = 0; - int i = 0; - while ((count < recCount && - isValidRecurrence && - recurrenceProperties.weekDays != null && - recurrenceProperties.weekDays.isNotEmpty) || - (addDate.isBefore(endDate) || addDate == endDate) || - (recurrenceProperties.recurrenceRange == RecurrenceRange.noEndDate && - i < _kNumberOfDaysInWeek)) { - switch (addDate.weekday) { - case DateTime.sunday: - { - if (recurrenceProperties.weekDays.contains(WeekDays.sunday)) { - dayKey = 'SU,'; - dayCount = su; - } + case DateTime.monday: + { + if (weeklyRule[weeklyByDayPos].contains('MO')) { + recProp.weekDays.add(WeekDays.monday); + } - break; - } + break; + } - case DateTime.monday: - { - if (recurrenceProperties.weekDays.contains(WeekDays.monday)) { - dayKey = 'MO,'; - dayCount = mo; - } + case DateTime.tuesday: + { + if (weeklyRule[weeklyByDayPos].contains('TU')) { + recProp.weekDays.add(WeekDays.tuesday); + } - break; - } + break; + } - case DateTime.tuesday: - { - if (recurrenceProperties.weekDays.contains(WeekDays.tuesday)) { - dayKey = 'TU,'; - dayCount = tu; - } + case DateTime.wednesday: + { + if (weeklyRule[weeklyByDayPos].contains('WE')) { + recProp.weekDays.add(WeekDays.wednesday); + } - break; - } + break; + } - case DateTime.wednesday: - { - if (recurrenceProperties.weekDays.contains(WeekDays.wednesday)) { - dayKey = 'WE,'; - dayCount = we; - } + case DateTime.thursday: + { + if (weeklyRule[weeklyByDayPos].contains('TH')) { + recProp.weekDays.add(WeekDays.thursday); + } - break; - } + break; + } - case DateTime.thursday: - { - if (recurrenceProperties.weekDays.contains(WeekDays.thursday)) { - dayKey = 'TH,'; - dayCount = th; - } + case DateTime.friday: + { + if (weeklyRule[weeklyByDayPos].contains('FR')) { + recProp.weekDays.add(WeekDays.friday); + } - break; - } + break; + } - case DateTime.friday: - { - if (recurrenceProperties.weekDays.contains(WeekDays.friday)) { - dayKey = 'FR,'; - dayCount = fr; - } + case DateTime.saturday: + { + if (weeklyRule[weeklyByDayPos].contains('SA')) { + recProp.weekDays.add(WeekDays.saturday); + } - break; + break; + } } - case DateTime.saturday: - { - if (recurrenceProperties.weekDays.contains(WeekDays.saturday)) { - dayKey = 'SA'; - dayCount = sa; - } - - break; - } + addDate = addDate.weekday == 6 + ? addDays( + addDate, ((recProp.interval - 1) * DateTime.daysPerWeek) + 1) + : addDays(addDate, 1); + i = i + 1; + } + } else if (monthly == 'MONTHLY') { + recProp.recurrenceType = RecurrenceType.monthly; + if (byMonthDay == 'BYMONTHDAY') { + recProp.week = 0; + recProp.dayOfMonth = + byMonthDayCount.isNotEmpty ? int.parse(byMonthDayCount) : 1; + } else if (byDay == 'BYDAY') { + recProp.week = + bySetPosCount.isNotEmpty ? int.parse(bySetPosCount) : 0; + recProp.dayOfWeek = + byDayValue.isNotEmpty ? _getWeekDay(byDayValue) : 1; + } + } else if (yearly == 'YEARLY') { + recProp.recurrenceType = RecurrenceType.yearly; + if (byMonthDay == 'BYMONTHDAY') { + recProp.month = byMonthCount.isNotEmpty ? int.parse(byMonthCount) : 1; + recProp.dayOfMonth = + byMonthDayCount.isNotEmpty ? int.parse(byMonthDayCount) : 1; + } else if (byDay == 'BYDAY') { + recProp.month = byMonthCount.isNotEmpty ? int.parse(byMonthCount) : 1; + recProp.week = + bySetPosCount.isNotEmpty ? int.parse(bySetPosCount) : 0; + recProp.dayOfWeek = + byDayValue.isNotEmpty ? _getWeekDay(byDayValue) : 1; + } } + } - if (dayKey != null && dayKey.isNotEmpty) { - if (count != 0) { - final Duration tempTimeSpan = addDate.difference(prevDate); - if (tempTimeSpan <= diffTimeSpan) { - isValidRecurrence = false; - } else { - prevDate = addDate; - if (dayCount == 1) { - break; - } + return recProp; + } - if (addDate.weekday != DateTime.saturday) { - byDay = - byDay.isNotEmpty && byDay.substring(byDay.length - 1) == 'A' - ? byDay + ',' + dayKey - : byDay + dayKey; - } else { - byDay = byDay + dayKey; - } + static String _getRRuleForDaily( + int recCount, + RecurrenceProperties recurrenceProperties, + DateTime startDate, + DateTime? endDate, + bool isValidRecurrence, + Duration diffTimeSpan) { + String rRule = ''; + if ((recCount > 0 && + recurrenceProperties.recurrenceRange == RecurrenceRange.count) || + recurrenceProperties.recurrenceRange == RecurrenceRange.noEndDate || + (recurrenceProperties.recurrenceRange == RecurrenceRange.endDate && + (startDate.isBefore(endDate!) || startDate == endDate))) { + rRule = 'FREQ=DAILY'; + + if (recurrenceProperties.weekDays.contains(WeekDays.monday) && + recurrenceProperties.weekDays.contains(WeekDays.tuesday) && + recurrenceProperties.weekDays.contains(WeekDays.wednesday) && + recurrenceProperties.weekDays.contains(WeekDays.thursday) && + recurrenceProperties.weekDays.contains(WeekDays.friday)) { + if (diffTimeSpan.inHours > 24) { + isValidRecurrence = false; + } - dayCount++; - } - } else { - prevDate = addDate; - count++; - byDay = byDay.isNotEmpty && byDay.substring(byDay.length - 1) == 'A' - ? byDay + ',' + dayKey - : byDay + dayKey; - dayCount++; + rRule = rRule + ';BYDAY=MO,TU,WE,TH,FR'; + } else { + if (diffTimeSpan.inHours >= recurrenceProperties.interval * 24) { + isValidRecurrence = false; } - if (recurrenceProperties.recurrenceRange == RecurrenceRange.noEndDate) { - recCount++; + if (recurrenceProperties.interval > 0) { + rRule = + rRule + ';INTERVAL=' + recurrenceProperties.interval.toString(); } + } + + if (recurrenceProperties.recurrenceRange == RecurrenceRange.count) { + rRule = rRule + ';COUNT=' + recCount.toString(); + } else if (recurrenceProperties.recurrenceRange == + RecurrenceRange.endDate) { + final DateFormat format = DateFormat('yyyyMMdd'); + rRule = rRule + ';UNTIL=' + format.format(endDate!); + } + } + + if (!isValidRecurrence) { + rRule = ''; + } + + return rRule; + } + static String _getRRuleForWeekly( + DateTime startDate, + DateTime? endDate, + int recCount, + RecurrenceProperties recurrenceProperties, + bool isValidRecurrence, + Duration diffTimeSpan, + DateTime prevDate) { + String rRule = ''; + DateTime addDate = startDate; + if ((recCount > 0 && + recurrenceProperties.recurrenceRange == RecurrenceRange.count) || + recurrenceProperties.recurrenceRange == RecurrenceRange.noEndDate || + (recurrenceProperties.recurrenceRange == RecurrenceRange.endDate && + (startDate.isBefore(endDate!) || startDate == endDate))) { + rRule = 'FREQ=WEEKLY'; + String byDay = ''; + int su = 0, mo = 0, tu = 0, we = 0, th = 0, fr = 0, sa = 0; + String dayKey = ''; + int dayCount = 0; + rRule = rRule + ';BYDAY='; + int count = 0; + int i = 0; + while ((count < recCount && + isValidRecurrence && + recurrenceProperties.weekDays.isNotEmpty) || + (recurrenceProperties.recurrenceRange == RecurrenceRange.endDate && + (addDate.isBefore(endDate!) || isSameDate(addDate, endDate))) || + (recurrenceProperties.recurrenceRange == RecurrenceRange.noEndDate && + i < DateTime.daysPerWeek)) { switch (addDate.weekday) { case DateTime.sunday: { - su = dayCount; + if (recurrenceProperties.weekDays.contains(WeekDays.sunday)) { + dayKey = 'SU,'; + dayCount = su; + } + break; } case DateTime.monday: { - mo = dayCount; + if (recurrenceProperties.weekDays.contains(WeekDays.monday)) { + dayKey = 'MO,'; + dayCount = mo; + } + break; } case DateTime.tuesday: { - tu = dayCount; + if (recurrenceProperties.weekDays.contains(WeekDays.tuesday)) { + dayKey = 'TU,'; + dayCount = tu; + } + break; } case DateTime.wednesday: { - we = dayCount; + if (recurrenceProperties.weekDays.contains(WeekDays.wednesday)) { + dayKey = 'WE,'; + dayCount = we; + } + break; } case DateTime.thursday: { - th = dayCount; + if (recurrenceProperties.weekDays.contains(WeekDays.thursday)) { + dayKey = 'TH,'; + dayCount = th; + } + break; } case DateTime.friday: { - fr = dayCount; + if (recurrenceProperties.weekDays.contains(WeekDays.friday)) { + dayKey = 'FR,'; + dayCount = fr; + } + break; } case DateTime.saturday: { - sa = dayCount; + if (recurrenceProperties.weekDays.contains(WeekDays.saturday)) { + dayKey = 'SA'; + dayCount = sa; + } + break; } } - dayCount = 0; - dayKey = ''; - } + if (dayKey.isNotEmpty) { + if (count != 0) { + final Duration tempTimeSpan = addDate.difference(prevDate); + if (tempTimeSpan <= diffTimeSpan) { + isValidRecurrence = false; + } else { + prevDate = addDate; + if (dayCount == 1) { + break; + } - addDate = addDate.weekday == DateTime.saturday - ? addDuration( - addDate, - Duration( - days: ((recurrenceProperties.interval - 1) * - _kNumberOfDaysInWeek) + - 1)) - : addDuration(addDate, const Duration(days: 1)); - i = i + 1; - } + if (addDate.weekday != DateTime.saturday) { + byDay = + byDay.isNotEmpty && byDay.substring(byDay.length - 1) == 'A' + ? byDay + ',' + dayKey + : byDay + dayKey; + } else { + byDay = byDay + dayKey; + } - byDay = _sortByDay(byDay); - rRule = rRule + byDay; + dayCount++; + } + } else { + prevDate = addDate; + count++; + byDay = byDay.isNotEmpty && byDay.substring(byDay.length - 1) == 'A' + ? byDay + ',' + dayKey + : byDay + dayKey; + dayCount++; + } - if (recurrenceProperties.interval > 0) { - rRule = rRule + ';INTERVAL=' + recurrenceProperties.interval.toString(); - } + if (recurrenceProperties.recurrenceRange == + RecurrenceRange.noEndDate) { + recCount++; + } - if (recurrenceProperties.recurrenceRange == RecurrenceRange.count) { - rRule = rRule + ';COUNT=' + recCount.toString(); - } else if (recurrenceProperties.recurrenceRange == - RecurrenceRange.endDate) { - final DateFormat format = DateFormat('yyyyMMdd'); - rRule = rRule + ';UNTIL=' + format.format(endDate); - } - } + switch (addDate.weekday) { + case DateTime.sunday: + { + su = dayCount; + break; + } - if (!isValidRecurrence) { - rRule = ''; - } + case DateTime.monday: + { + mo = dayCount; + break; + } - return rRule; -} + case DateTime.tuesday: + { + tu = dayCount; + break; + } -String _getRRuleForMonthly( - int recCount, - RecurrenceProperties recurrenceProperties, - DateTime startDate, - DateTime endDate, - Duration diffTimeSpan, - bool isValidRecurrence, -) { - String rRule = ''; - if ((recCount > 0 && - recurrenceProperties.recurrenceRange == RecurrenceRange.count) || - recurrenceProperties.recurrenceRange == RecurrenceRange.noEndDate || - ((startDate.isBefore(endDate) || startDate == endDate) && - recurrenceProperties.recurrenceRange == RecurrenceRange.endDate)) { - rRule = 'FREQ=MONTHLY'; - - if (recurrenceProperties.week == -1) { - rRule = - rRule + ';BYMONTHDAY=' + recurrenceProperties.dayOfMonth.toString(); - } else { - final DateTime firstDate = subtractDuration( - DateTime.now(), Duration(days: DateTime.now().weekday - 1)); - final List dayNames = - List.generate(_kNumberOfDaysInWeek, (int index) => index) - .map((int value) => DateFormat(DateFormat.ABBR_WEEKDAY) - .format(addDuration(firstDate, Duration(days: value)))) - .toList(); - final String byDayString = dayNames[recurrenceProperties.dayOfWeek - 1]; - //AbbreviatedDayNames will return three digit char , as per the standard - // we need to return only first two char for RRule so here have removed - // the last char. - rRule = rRule + - ';BYDAY=' + - byDayString.substring(0, byDayString.length - 1).toUpperCase() + - ';BYSETPOS=' + - recurrenceProperties.week.toString(); - } + case DateTime.wednesday: + { + we = dayCount; + break; + } - if (recurrenceProperties.interval > 0) { - rRule = rRule + ';INTERVAL=' + recurrenceProperties.interval.toString(); - } + case DateTime.thursday: + { + th = dayCount; + break; + } - if (recurrenceProperties.recurrenceRange == RecurrenceRange.count) { - rRule = rRule + ';COUNT=' + recCount.toString(); - } else if (recurrenceProperties.recurrenceRange == - RecurrenceRange.endDate) { - final DateFormat format = DateFormat('yyyyMMdd'); - rRule = rRule + ';UNTIL=' + format.format(endDate); - } + case DateTime.friday: + { + fr = dayCount; + break; + } + + case DateTime.saturday: + { + sa = dayCount; + break; + } + } + + dayCount = 0; + dayKey = ''; + } - if (DateTime( - startDate.year, - startDate.month + recurrenceProperties.interval, - startDate.day, + addDate = addDate.weekday == DateTime.saturday + ? AppointmentHelper.addDaysWithTime( + addDate, + ((recurrenceProperties.interval - 1) * DateTime.daysPerWeek) + + 1, startDate.hour, startDate.minute, startDate.second) - .difference(startDate) < - diffTimeSpan) { - isValidRecurrence = false; + : AppointmentHelper.addDaysWithTime( + addDate, 1, startDate.hour, startDate.minute, startDate.second); + i = i + 1; + } + + byDay = _sortByDay(byDay); + rRule = rRule + byDay; + + if (recurrenceProperties.interval > 0) { + rRule = rRule + ';INTERVAL=' + recurrenceProperties.interval.toString(); + } + + if (recurrenceProperties.recurrenceRange == RecurrenceRange.count) { + rRule = rRule + ';COUNT=' + recCount.toString(); + } else if (recurrenceProperties.recurrenceRange == + RecurrenceRange.endDate) { + final DateFormat format = DateFormat('yyyyMMdd'); + rRule = rRule + ';UNTIL=' + format.format(endDate!); + } } - } - if (!isValidRecurrence) { - rRule = ''; - } + if (!isValidRecurrence) { + rRule = ''; + } - return rRule; -} + return rRule; + } -String _getRRuleForYearly( + static String _getRRuleForMonthly( int recCount, RecurrenceProperties recurrenceProperties, DateTime startDate, - DateTime endDate, + DateTime? endDate, Duration diffTimeSpan, - bool isValidRecurrence) { - String rRule = ''; - if ((recCount > 0 && - recurrenceProperties.recurrenceRange == RecurrenceRange.count) || - recurrenceProperties.recurrenceRange == RecurrenceRange.noEndDate || - ((startDate.isBefore(endDate) || startDate == endDate) && - recurrenceProperties.recurrenceRange == RecurrenceRange.endDate)) { - rRule = 'FREQ=YEARLY'; - - if (recurrenceProperties.week == -1) { - rRule = rRule + - ';BYMONTHDAY=' + - recurrenceProperties.dayOfMonth.toString() + - ';BYMONTH=' + - recurrenceProperties.month.toString(); - } else { - final DateTime firstDate = subtractDuration( - DateTime.now(), Duration(days: DateTime.now().weekday - 1)); - final List dayNames = - List.generate(_kNumberOfDaysInWeek, (int index) => index) - .map((int value) => DateFormat(DateFormat.ABBR_WEEKDAY) - .format(addDuration(firstDate, Duration(days: value)))) - .toList(); - final String byDayString = dayNames[recurrenceProperties.dayOfWeek - 1]; - //AbbreviatedDayNames will return three digit char , as per the standard - // we need to return only first two char for RRule so here have removed - // the last char. - rRule = rRule + - ';BYDAY=' + - byDayString.substring(0, byDayString.length - 1).toUpperCase() + - ';BYMONTH=' + - recurrenceProperties.month.toString() + - ';BYSETPOS=' + - recurrenceProperties.week.toString(); - } + bool isValidRecurrence, + ) { + String rRule = ''; + if ((recCount > 0 && + recurrenceProperties.recurrenceRange == RecurrenceRange.count) || + recurrenceProperties.recurrenceRange == RecurrenceRange.noEndDate || + (recurrenceProperties.recurrenceRange == RecurrenceRange.endDate && + (startDate.isBefore(endDate!) || startDate == endDate))) { + rRule = 'FREQ=MONTHLY'; + + if (recurrenceProperties.week == 0) { + rRule = + rRule + ';BYMONTHDAY=' + recurrenceProperties.dayOfMonth.toString(); + } else { + final DateTime firstDate = + addDays(DateTime.now(), -(DateTime.now().weekday - 1)); + final List dayNames = + List.generate(DateTime.daysPerWeek, (int index) => index) + .map((int value) => DateFormat(DateFormat.ABBR_WEEKDAY) + .format(addDays(firstDate, value))) + .toList(); + final String byDayString = dayNames[recurrenceProperties.dayOfWeek - 1]; + + /// AbbreviatedDayNames will return three digit char, as per the + /// standard we need to return only first two char for RRule so here + /// have removed the last char. + rRule = rRule + + ';BYDAY=' + + byDayString.substring(0, byDayString.length - 1).toUpperCase() + + ';BYSETPOS=' + + recurrenceProperties.week.toString(); + } - if (recurrenceProperties.interval > 0) { - rRule = rRule + ';INTERVAL=' + recurrenceProperties.interval.toString(); - } + if (recurrenceProperties.interval > 0) { + rRule = rRule + ';INTERVAL=' + recurrenceProperties.interval.toString(); + } + + if (recurrenceProperties.recurrenceRange == RecurrenceRange.count) { + rRule = rRule + ';COUNT=' + recCount.toString(); + } else if (recurrenceProperties.recurrenceRange == + RecurrenceRange.endDate) { + final DateFormat format = DateFormat('yyyyMMdd'); + rRule = rRule + ';UNTIL=' + format.format(endDate!); + } - if (recurrenceProperties.recurrenceRange == RecurrenceRange.count) { - rRule = rRule + ';COUNT=' + recCount.toString(); - } else if (recurrenceProperties.recurrenceRange == - RecurrenceRange.endDate) { - final DateFormat format = DateFormat('yyyyMMdd'); - rRule = rRule + ';UNTIL=' + format.format(endDate); + if (DateTime( + startDate.year, + startDate.month + recurrenceProperties.interval, + startDate.day, + startDate.hour, + startDate.minute, + startDate.second) + .difference(startDate) < + diffTimeSpan) { + isValidRecurrence = false; + } } - if (DateTime( - startDate.year + recurrenceProperties.interval, - startDate.month, - startDate.day, - startDate.hour, - startDate.minute, - startDate.second) - .difference(startDate) < - diffTimeSpan) { - isValidRecurrence = false; + if (!isValidRecurrence) { + rRule = ''; } - } - if (!isValidRecurrence) { - rRule = ''; + return rRule; } - return rRule; -} + static String _getRRuleForYearly( + int recCount, + RecurrenceProperties recurrenceProperties, + DateTime startDate, + DateTime? endDate, + Duration diffTimeSpan, + bool isValidRecurrence) { + String rRule = ''; + if ((recCount > 0 && + recurrenceProperties.recurrenceRange == RecurrenceRange.count) || + recurrenceProperties.recurrenceRange == RecurrenceRange.noEndDate || + (recurrenceProperties.recurrenceRange == RecurrenceRange.endDate && + (startDate.isBefore(endDate!) || startDate == endDate))) { + rRule = 'FREQ=YEARLY'; + + if (recurrenceProperties.week == 0) { + rRule = rRule + + ';BYMONTHDAY=' + + recurrenceProperties.dayOfMonth.toString() + + ';BYMONTH=' + + recurrenceProperties.month.toString(); + } else { + final DateTime firstDate = + addDays(DateTime.now(), -(DateTime.now().weekday - 1)); + final List dayNames = + List.generate(DateTime.daysPerWeek, (int index) => index) + .map((int value) => DateFormat(DateFormat.ABBR_WEEKDAY) + .format(addDays(firstDate, value))) + .toList(); + final String byDayString = dayNames[recurrenceProperties.dayOfWeek - 1]; + + /// AbbreviatedDayNames will return three digit char, as per the + /// standard we need to return only first two char for RRule so here + /// have removed the last char. + rRule = rRule + + ';BYDAY=' + + byDayString.substring(0, byDayString.length - 1).toUpperCase() + + ';BYMONTH=' + + recurrenceProperties.month.toString() + + ';BYSETPOS=' + + recurrenceProperties.week.toString(); + } -/// Generates the recurrence rule based on the given recurrence properties and -/// the start date and end date of the recurrence appointment. -String _generateRRule(RecurrenceProperties recurrenceProperties, - DateTime appStartTime, DateTime appEndTime) { - final DateTime recPropStartDate = recurrenceProperties.startDate; - final DateTime recPropEndDate = recurrenceProperties.endDate; - final DateTime startDate = - appStartTime.isAfter(recPropStartDate) ? appStartTime : recPropStartDate; - final DateTime endDate = recPropEndDate; - final Duration diffTimeSpan = appEndTime.difference(appStartTime); - int recCount = 0; - final DateTime prevDate = DateTime.utc(1); - final bool isValidRecurrence = true; - - recCount = recurrenceProperties.recurrenceCount; - switch (recurrenceProperties.recurrenceType) { - case RecurrenceType.daily: - return _getRRuleForDaily(recCount, recurrenceProperties, startDate, - endDate, isValidRecurrence, diffTimeSpan); - case RecurrenceType.weekly: - return _getRRuleForWeekly(startDate, endDate, recCount, - recurrenceProperties, isValidRecurrence, diffTimeSpan, prevDate); - case RecurrenceType.monthly: - return _getRRuleForMonthly(recCount, recurrenceProperties, startDate, - endDate, diffTimeSpan, isValidRecurrence); - case RecurrenceType.yearly: - return _getRRuleForYearly(recCount, recurrenceProperties, startDate, - endDate, diffTimeSpan, isValidRecurrence); - } + if (recurrenceProperties.interval > 0) { + rRule = rRule + ';INTERVAL=' + recurrenceProperties.interval.toString(); + } - return ''; -} + if (recurrenceProperties.recurrenceRange == RecurrenceRange.count) { + rRule = rRule + ';COUNT=' + recCount.toString(); + } else if (recurrenceProperties.recurrenceRange == + RecurrenceRange.endDate) { + final DateFormat format = DateFormat('yyyyMMdd'); + rRule = rRule + ';UNTIL=' + format.format(endDate!); + } -List _findKeyIndex(List ruleArray) { - int byMonthDayPosition = 0; - int byDayPosition = 0; - String recurCount = ''; - String daily = ''; - String weekly = ''; - String monthly = ''; - String yearly = ''; - String bySetPos = ''; - String bySetPosCount = ''; - String interval = ''; - String intervalCount = ''; - String count = ''; - String byDay = ''; - String byDayValue = ''; - String byMonthDay = ''; - String byMonthDayCount = ''; - String byMonth = ''; - String byMonthCount = ''; - const String weeklyByDay = ''; - String until = ''; - String untilValue = ''; - - for (int i = 0; i < ruleArray.length; i++) { - if (ruleArray[i] == 'COUNT') { - count = ruleArray[i]; - recurCount = ruleArray[i + 1]; - continue; + if (DateTime( + startDate.year + recurrenceProperties.interval, + startDate.month, + startDate.day, + startDate.hour, + startDate.minute, + startDate.second) + .difference(startDate) < + diffTimeSpan) { + isValidRecurrence = false; + } } - if (ruleArray[i] == 'DAILY') { - daily = ruleArray[i]; - continue; + if (!isValidRecurrence) { + rRule = ''; } - if (ruleArray[i] == 'WEEKLY') { - weekly = ruleArray[i]; - continue; - } + return rRule; + } - if (ruleArray[i] == 'INTERVAL') { - interval = ruleArray[i]; - intervalCount = ruleArray[i + 1]; - continue; + /// Generates the recurrence rule based on the given recurrence properties and + /// the start date and end date of the recurrence appointment. + static String generateRRule(RecurrenceProperties recurrenceProperties, + DateTime appStartTime, DateTime appEndTime) { + final DateTime recPropStartDate = recurrenceProperties.startDate; + final DateTime? recPropEndDate = recurrenceProperties.endDate; + final DateTime startDate = appStartTime.isAfter(recPropStartDate) + ? appStartTime + : recPropStartDate; + final DateTime? endDate = recPropEndDate; + final Duration diffTimeSpan = appEndTime.difference(appStartTime); + int recCount = 0; + final DateTime prevDate = DateTime.utc(1); + final bool isValidRecurrence = true; + + recCount = recurrenceProperties.recurrenceCount; + switch (recurrenceProperties.recurrenceType) { + case RecurrenceType.daily: + return _getRRuleForDaily(recCount, recurrenceProperties, startDate, + endDate, isValidRecurrence, diffTimeSpan); + case RecurrenceType.weekly: + return _getRRuleForWeekly(startDate, endDate, recCount, + recurrenceProperties, isValidRecurrence, diffTimeSpan, prevDate); + case RecurrenceType.monthly: + return _getRRuleForMonthly(recCount, recurrenceProperties, startDate, + endDate, diffTimeSpan, isValidRecurrence); + case RecurrenceType.yearly: + return _getRRuleForYearly(recCount, recurrenceProperties, startDate, + endDate, diffTimeSpan, isValidRecurrence); } + } - if (ruleArray[i] == 'UNTIL') { - until = ruleArray[i]; - untilValue = ruleArray[i + 1]; - continue; - } + static List _findKeyIndex(List ruleArray) { + int byMonthDayPosition = 0; + int byDayPosition = 0; + String recurCount = ''; + String daily = ''; + String weekly = ''; + String monthly = ''; + String yearly = ''; + String bySetPos = ''; + String bySetPosCount = ''; + String interval = ''; + String intervalCount = ''; + String count = ''; + String byDay = ''; + String byDayValue = ''; + String byMonthDay = ''; + String byMonthDayCount = ''; + String byMonth = ''; + String byMonthCount = ''; + const String weeklyByDay = ''; + String until = ''; + String untilValue = ''; + + for (int i = 0; i < ruleArray.length; i++) { + if (ruleArray[i] == 'COUNT') { + count = ruleArray[i]; + recurCount = ruleArray[i + 1]; + continue; + } - if (ruleArray[i] == 'MONTHLY') { - monthly = ruleArray[i]; - continue; - } + if (ruleArray[i] == 'DAILY') { + daily = ruleArray[i]; + continue; + } - if (ruleArray[i] == 'YEARLY') { - yearly = ruleArray[i]; - continue; - } + if (ruleArray[i] == 'WEEKLY') { + weekly = ruleArray[i]; + continue; + } - if (ruleArray[i] == 'BYSETPOS') { - bySetPos = ruleArray[i]; - bySetPosCount = ruleArray[i + 1]; - continue; - } + if (ruleArray[i] == 'INTERVAL') { + interval = ruleArray[i]; + intervalCount = ruleArray[i + 1]; + continue; + } - if (ruleArray[i] == 'BYDAY') { - byDayPosition = i; - byDay = ruleArray[i]; - byDayValue = ruleArray[i + 1]; - continue; - } + if (ruleArray[i] == 'UNTIL') { + until = ruleArray[i]; + untilValue = ruleArray[i + 1]; + continue; + } - if (ruleArray[i] == 'BYMONTH') { - byMonth = ruleArray[i]; - byMonthCount = ruleArray[i + 1]; - continue; - } + if (ruleArray[i] == 'MONTHLY') { + monthly = ruleArray[i]; + continue; + } - if (ruleArray[i] == 'BYMONTHDAY') { - byMonthDayPosition = i; - byMonthDay = ruleArray[i]; - byMonthDayCount = ruleArray[i + 1]; - continue; - } - } + if (ruleArray[i] == 'YEARLY') { + yearly = ruleArray[i]; + continue; + } - return [ - recurCount, - daily, - weekly, - monthly, - yearly, - bySetPos, - bySetPosCount, - interval, - intervalCount, - until, - untilValue, - count, - byDay, - byDayValue, - byMonthDay, - byMonthDayCount, - byMonth, - byMonthCount, - weeklyByDay, - byMonthDayPosition.toString(), - byDayPosition.toString() - ]; -} + if (ruleArray[i] == 'BYSETPOS') { + bySetPos = ruleArray[i]; + bySetPosCount = ruleArray[i + 1]; + continue; + } -List _findWeeklyRule(List weeklyRule) { - final List result = []; - for (int i = 0; i < weeklyRule.length; i++) { - if (weeklyRule[i].contains('BYDAY')) { - result.add(weeklyRule[i]); - result.add(i.toString()); - break; - } - } + if (ruleArray[i] == 'BYDAY') { + byDayPosition = i; + byDay = ruleArray[i]; + byDayValue = ruleArray[i + 1]; + continue; + } - return result; -} + if (ruleArray[i] == 'BYMONTH') { + byMonth = ruleArray[i]; + byMonthCount = ruleArray[i + 1]; + continue; + } -int _getWeekDay(String weekDay) { - int index = 1; - final DateTime firstDate = subtractDuration( - DateTime.now(), Duration(days: DateTime.now().weekday - 1)); - final List dayNames = - List.generate(_kNumberOfDaysInWeek, (int index) => index) - .map((int value) => DateFormat(DateFormat.ABBR_WEEKDAY) - .format(addDuration(firstDate, Duration(days: value)))) - .toList(); - for (int i = 0; i < _kNumberOfDaysInWeek; i++) { - final String dayName = dayNames[i]; - // AbbreviatedDayNames will return three digit char , user may give 2 digit - // char also as per the standard so here have checked the char count. - if (dayName.toUpperCase() == weekDay || - weekDay.length == 2 && - dayName.substring(0, dayName.length - 1).toUpperCase() == weekDay) { - index = i; + if (ruleArray[i] == 'BYMONTHDAY') { + byMonthDayPosition = i; + byMonthDay = ruleArray[i]; + byMonthDayCount = ruleArray[i + 1]; + continue; + } } + + return [ + recurCount, + daily, + weekly, + monthly, + yearly, + bySetPos, + bySetPosCount, + interval, + intervalCount, + until, + untilValue, + count, + byDay, + byDayValue, + byMonthDay, + byMonthDayCount, + byMonth, + byMonthCount, + weeklyByDay, + byMonthDayPosition.toString(), + byDayPosition.toString() + ]; } - return index + 1; -} + static List _findWeeklyRule(List weeklyRule) { + final List result = []; + for (int i = 0; i < weeklyRule.length; i++) { + if (weeklyRule[i].contains('BYDAY')) { + result.add(weeklyRule[i]); + result.add(i.toString()); + break; + } + } -List _splitRule(String text, List patterns) { - final List result = []; - int startIndex = 0; - for (int i = 0; i < text.length; i++) { - final String charValue = text[i]; - for (int j = 0; j < patterns.length; j++) { - if (charValue == patterns[j]) { - result.add(text.substring(startIndex, i)); - startIndex = i + 1; + return result; + } + + static int _getWeekDay(String weekDay) { + int index = 1; + final DateTime firstDate = + addDays(DateTime.now(), -(DateTime.now().weekday - 1)); + final List dayNames = + List.generate(DateTime.daysPerWeek, (int index) => index) + .map((int value) => DateFormat(DateFormat.ABBR_WEEKDAY) + .format(addDays(firstDate, value))) + .toList(); + for (int i = 0; i < DateTime.daysPerWeek; i++) { + final String dayName = dayNames[i]; + + /// AbbreviatedDayNames will return three digit char, user may give 2 + /// digit char also as per the standard so here have checked the + /// char count. + if (dayName.toUpperCase() == weekDay || + weekDay.length == 2 && + dayName.substring(0, dayName.length - 1).toUpperCase() == + weekDay) { + index = i; } } + + return index + 1; } - if (startIndex != text.length) { - result.add(text.substring(startIndex, text.length)); + /// Split the recurrence rule based on patterns. + static List splitRule(String text, List patterns) { + final List result = []; + int startIndex = 0; + for (int i = 0; i < text.length; i++) { + final String charValue = text[i]; + for (int j = 0; j < patterns.length; j++) { + if (charValue == patterns[j]) { + result.add(text.substring(startIndex, i)); + startIndex = i + 1; + } + } + } + + if (startIndex != text.length) { + result.add(text.substring(startIndex, text.length)); + } + + return result; } - return result; -} + static bool _isRecurrenceDate( + DateTime addDate, List weeklyRule, int weeklyByDayPos) { + bool isRecurrenceDate = false; + switch (addDate.weekday) { + case DateTime.sunday: + { + if (weeklyRule[weeklyByDayPos].contains('SU')) { + isRecurrenceDate = true; + } -bool _isRecurrenceDate( - DateTime addDate, List weeklyRule, int weeklyByDayPos) { - bool isRecurrenceDate = false; - switch (addDate.weekday) { - case DateTime.sunday: - { - if (weeklyRule[weeklyByDayPos].contains('SU')) { - isRecurrenceDate = true; + break; } - break; - } + case DateTime.monday: + { + if (weeklyRule[weeklyByDayPos].contains('MO')) { + isRecurrenceDate = true; + } - case DateTime.monday: - { - if (weeklyRule[weeklyByDayPos].contains('MO')) { - isRecurrenceDate = true; + break; } - break; - } + case DateTime.tuesday: + { + if (weeklyRule[weeklyByDayPos].contains('TU')) { + isRecurrenceDate = true; + } - case DateTime.tuesday: - { - if (weeklyRule[weeklyByDayPos].contains('TU')) { - isRecurrenceDate = true; + break; } - break; - } + case DateTime.wednesday: + { + if (weeklyRule[weeklyByDayPos].contains('WE')) { + isRecurrenceDate = true; + } - case DateTime.wednesday: - { - if (weeklyRule[weeklyByDayPos].contains('WE')) { - isRecurrenceDate = true; + break; } - break; - } + case DateTime.thursday: + { + if (weeklyRule[weeklyByDayPos].contains('TH')) { + isRecurrenceDate = true; + } - case DateTime.thursday: - { - if (weeklyRule[weeklyByDayPos].contains('TH')) { - isRecurrenceDate = true; + break; } - break; - } + case DateTime.friday: + { + if (weeklyRule[weeklyByDayPos].contains('FR')) { + isRecurrenceDate = true; + } - case DateTime.friday: - { - if (weeklyRule[weeklyByDayPos].contains('FR')) { - isRecurrenceDate = true; + break; } - break; - } + case DateTime.saturday: + { + if (weeklyRule[weeklyByDayPos].contains('SA')) { + isRecurrenceDate = true; + } - case DateTime.saturday: - { - if (weeklyRule[weeklyByDayPos].contains('SA')) { - isRecurrenceDate = true; + break; } + } - break; - } + return isRecurrenceDate; } - return isRecurrenceDate; -} + static void _setWeeklyRecurrenceDate( + DateTime addDate, + List weeklyRule, + int weeklyByDayPos, + List recDateCollection) { + switch (addDate.weekday) { + case DateTime.sunday: + { + if (weeklyRule[weeklyByDayPos].contains('SU')) { + recDateCollection.add(addDate); + } -void _setWeeklyRecurrenceDate(DateTime addDate, List weeklyRule, - int weeklyByDayPos, List recDateCollection) { - switch (addDate.weekday) { - case DateTime.sunday: - { - if (weeklyRule[weeklyByDayPos].contains('SU')) { - recDateCollection.add(addDate); + break; } - break; - } + case DateTime.monday: + { + if (weeklyRule[weeklyByDayPos].contains('MO')) { + recDateCollection.add(addDate); + } - case DateTime.monday: - { - if (weeklyRule[weeklyByDayPos].contains('MO')) { - recDateCollection.add(addDate); + break; } - break; - } + case DateTime.tuesday: + { + if (weeklyRule[weeklyByDayPos].contains('TU')) { + recDateCollection.add(addDate); + } - case DateTime.tuesday: - { - if (weeklyRule[weeklyByDayPos].contains('TU')) { - recDateCollection.add(addDate); + break; } - break; - } + case DateTime.wednesday: + { + if (weeklyRule[weeklyByDayPos].contains('WE')) { + recDateCollection.add(addDate); + } - case DateTime.wednesday: - { - if (weeklyRule[weeklyByDayPos].contains('WE')) { - recDateCollection.add(addDate); + break; } - break; - } + case DateTime.thursday: + { + if (weeklyRule[weeklyByDayPos].contains('TH')) { + recDateCollection.add(addDate); + } - case DateTime.thursday: - { - if (weeklyRule[weeklyByDayPos].contains('TH')) { - recDateCollection.add(addDate); + break; } - break; - } + case DateTime.friday: + { + if (weeklyRule[weeklyByDayPos].contains('FR')) { + recDateCollection.add(addDate); + } - case DateTime.friday: - { - if (weeklyRule[weeklyByDayPos].contains('FR')) { - recDateCollection.add(addDate); + break; } - break; - } + case DateTime.saturday: + { + if (weeklyRule[weeklyByDayPos].contains('SA')) { + recDateCollection.add(addDate); + } - case DateTime.saturday: - { - if (weeklyRule[weeklyByDayPos].contains('SA')) { - recDateCollection.add(addDate); + break; } - - break; - } + } } -} -String _sortByDay(String byDay) { - final List sortedDays = [ - 'SU', - 'MO', - 'TU', - 'WE', - 'TH', - 'FR', - 'SA' - ]; - String weeklyDayString = ''; - int count = 0; - for (int i = 0; i < sortedDays.length; i++) { - if (!byDay.contains(sortedDays[i])) { - continue; + /// Returns the recurrence start date for negative recurrence value. + static DateTime _getRecurrenceDateForNegativeValue( + int bySetPosCount, DateTime date, int weekDay) { + DateTime? lastDate; + if (bySetPosCount == -1) { + lastDate = AppointmentHelper.getMonthEndDate(date); + } else if (bySetPosCount == -2) { + lastDate = AppointmentHelper.getMonthEndDate(date) + .subtract(Duration(days: DateTime.daysPerWeek)); } - count++; - String day = sortedDays[i]; - if (count > 1) { - day = ',' + day; + if (lastDate == null) { + assert(false, 'Invalid position count'); + return date; + } + + return _getLastWeekDay( + DateTime(lastDate.year, lastDate.month, lastDate.day, date.hour, + date.minute, date.second), + weekDay); + } + + /// Returns the last week date for the given weekday. + static DateTime _getLastWeekDay(DateTime date, int dayOfWeek) { + final DateTime currentDate = date; + final int currentDateWeek = currentDate.weekday; + int otherMonthCount = -currentDateWeek + (dayOfWeek - DateTime.daysPerWeek); + if (otherMonthCount.abs() >= DateTime.daysPerWeek) { + otherMonthCount += DateTime.daysPerWeek; } - weeklyDayString = weeklyDayString + day; + return currentDate.add(Duration(days: otherMonthCount)); } - return weeklyDayString; + static String _sortByDay(String byDay) { + final List sortedDays = [ + 'SU', + 'MO', + 'TU', + 'WE', + 'TH', + 'FR', + 'SA' + ]; + String weeklyDayString = ''; + int count = 0; + for (int i = 0; i < sortedDays.length; i++) { + if (!byDay.contains(sortedDays[i])) { + continue; + } + + count++; + String day = sortedDays[i]; + if (count > 1) { + day = ',' + day; + } + + weeklyDayString = weeklyDayString + day; + } + + return weeklyDayString; + } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_properties.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_properties.dart index b95b2ffd0..8a0271dae 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_properties.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_properties.dart @@ -1,4 +1,8 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_datepicker/datepicker.dart' + show IterableDiagnostics; +import '../common/enums.dart'; /// Recurrence properties allows to create recurrence rule for an [Appointment]. /// @@ -46,36 +50,25 @@ part of calendar; /// return DataSource(appointments); /// } /// ``` -class RecurrenceProperties { +class RecurrenceProperties with Diagnosticable { /// Creates an recurrence properties . /// /// An object contains properties that hold data for the creation of /// [Appointment.recurrenceRule] for [Appointment] using the /// [SfCalendar.generateRRule] method. RecurrenceProperties( - {RecurrenceType recurrenceType, - int recurrenceCount, - DateTime startDate, - DateTime endDate, - int interval, - RecurrenceRange recurrenceRange, - List weekDays, - int week, - int dayOfMonth, - int dayOfWeek, - int month}) - : recurrenceType = recurrenceType ?? RecurrenceType.daily, - recurrenceCount = recurrenceCount ?? 1, - startDate = startDate ?? DateTime.now(), - endDate = - endDate ?? addDuration(DateTime.now(), const Duration(days: 1)), - interval = interval ?? 1, - recurrenceRange = recurrenceRange ?? RecurrenceRange.noEndDate, - weekDays = weekDays ?? [], - week = week ?? -1, - dayOfMonth = dayOfMonth ?? 1, - dayOfWeek = dayOfWeek ?? 1, - month = month ?? 1; + {this.recurrenceType = RecurrenceType.daily, + this.recurrenceCount = 1, + required this.startDate, + this.endDate, + this.interval = 1, + this.recurrenceRange = RecurrenceRange.noEndDate, + List? weekDays, + this.week = 0, + this.dayOfMonth = 1, + this.dayOfWeek = 1, + this.month = 1}) + : this.weekDays = weekDays ?? []; /// Defines the recurrence type of an [Appointment]. /// @@ -182,8 +175,6 @@ class RecurrenceProperties { /// /// The [Appointment] starts to recur from the date set to this property. /// - /// Defaults to current date. - /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -277,7 +268,7 @@ class RecurrenceProperties { /// return DataSource(appointments); /// } /// ``` - DateTime endDate; + DateTime? endDate; /// Defines the recurrence interval between the [Appointment]. /// @@ -620,4 +611,62 @@ class RecurrenceProperties { /// } /// ``` int month; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + final RecurrenceProperties recurrenceProperties = other; + return recurrenceProperties.recurrenceType == recurrenceType && + recurrenceProperties.recurrenceCount == recurrenceCount && + recurrenceProperties.startDate == startDate && + recurrenceProperties.endDate == endDate && + recurrenceProperties.interval == interval && + recurrenceProperties.recurrenceRange == recurrenceRange && + recurrenceProperties.weekDays == weekDays && + recurrenceProperties.week == week && + recurrenceProperties.dayOfMonth == dayOfMonth && + recurrenceProperties.dayOfWeek == dayOfWeek && + recurrenceProperties.month == month; + } + + @override + int get hashCode { + return hashValues( + recurrenceType, + recurrenceCount, + startDate, + endDate, + interval, + recurrenceRange, + weekDays, + week, + dayOfMonth, + dayOfWeek, + month); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(IntProperty('recurrenceCount', recurrenceCount)); + properties.add(IntProperty('interval', interval)); + properties.add(IntProperty('week', week)); + properties.add(IntProperty('dayOfMonth', dayOfMonth)); + properties.add(IntProperty('dayOfWeek', dayOfWeek)); + properties.add(IntProperty('month', month)); + properties.add(DiagnosticsProperty('startTime', startDate)); + properties.add(DiagnosticsProperty('endTime', endDate)); + properties + .add(EnumProperty('recurrenceType', recurrenceType)); + properties + .add(EnumProperty('recurrenceRange', recurrenceRange)); + properties.add(IterableDiagnostics(weekDays) + .toDiagnosticsNode(name: 'weekDays')); + } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/agenda_view_layout.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/agenda_view_layout.dart index 9ddcd02f9..9a81ea512 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/agenda_view_layout.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/agenda_view_layout.dart @@ -1,7 +1,23 @@ -part of calendar; - -class _AgendaViewLayout extends StatefulWidget { - _AgendaViewLayout( +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../appointment_engine/appointment_helper.dart'; +import '../common/calendar_view_helper.dart'; +import '../common/event_args.dart'; +import '../settings/month_view_settings.dart'; +import '../settings/schedule_view_settings.dart'; + +/// Used to holds the agenda appointment views in calendar widgets. +class AgendaViewLayout extends StatefulWidget { + /// Constructor to create the agenda appointment layout that holds the agenda + /// appointment views in calendar widget. + AgendaViewLayout( this.monthViewSettings, this.scheduleViewSettings, this.selectedDate, @@ -19,45 +35,75 @@ class _AgendaViewLayout extends StatefulWidget { this.width, this.height); - final MonthViewSettings monthViewSettings; - final ScheduleViewSettings scheduleViewSettings; - final DateTime selectedDate; - final List appointments; + /// Defines the month view customization details. + final MonthViewSettings? monthViewSettings; + + /// Defines the schedule view customization details. + final ScheduleViewSettings? scheduleViewSettings; + + /// Holds the current selected date value of calendar. + final DateTime? selectedDate; + + /// Holds the selected date appointment collection. + final List? appointments; + + /// Defines the direction of the calendar widget is RTL or not. final bool isRTL; + + /// Defines the locale of the calendar widget final String locale; + + /// Holds the theme data of the calendar widget. final SfCalendarThemeData calendarTheme; - final ValueNotifier<_ScheduleViewHoveringDetails> agendaViewNotifier; + + /// Holds the hovering details of the agenda view widget. + final ValueNotifier agendaViewNotifier; + + /// Holds the localization data of the calendar widget. final SfLocalizations localizations; + + /// Defines the width of time label widget in calendar. final double timeLabelWidth; - final String appointmentTimeTextFormat; - final CalendarAppointmentBuilder appointmentBuilder; + + /// Defines the appointment time text format on appointment view. + final String? appointmentTimeTextFormat; + + /// Used to build the widget that replaces the appointment view in agenda + /// appointment widget. + final CalendarAppointmentBuilder? appointmentBuilder; + + /// Defines the scale factor of the calendar widget. final double textScaleFactor; + + /// Defines the current platform is mobile platform or not. final bool isMobilePlatform; + + /// Defines the width of the agenda appointment layout widget. final double width; + + /// Defines the height of the agenda appointment layout widget. final double height; @override _AgendaViewLayoutState createState() => _AgendaViewLayoutState(); } -class _AgendaViewLayoutState extends State<_AgendaViewLayout> { +class _AgendaViewLayoutState extends State { /// It holds the appointment views for the visible appointments. - List<_AppointmentView> _appointmentCollection; + List _appointmentCollection = []; - /// It holds the children of the widget, it holds null or empty when + /// It holds the children of the widget, it holds empty when /// appointment builder is null. - List _children; + List _children = []; @override void initState() { - _appointmentCollection = <_AppointmentView>[]; - _children = []; _updateAppointmentDetails(); super.initState(); } @override - void didUpdateWidget(_AgendaViewLayout oldWidget) { + void didUpdateWidget(AgendaViewLayout oldWidget) { if (widget.appointments != oldWidget.appointments || widget.selectedDate != oldWidget.selectedDate || widget.timeLabelWidth != oldWidget.timeLabelWidth || @@ -72,15 +118,11 @@ class _AgendaViewLayoutState extends State<_AgendaViewLayout> { @override Widget build(BuildContext context) { - _children ??= []; - /// Create the widgets when appointment builder is not null. - if (_children.isEmpty && - widget.appointmentBuilder != null && - _appointmentCollection != null) { + if (_children.isEmpty && widget.appointmentBuilder != null) { final int appointmentCount = _appointmentCollection.length; for (int i = 0; i < appointmentCount; i++) { - final _AppointmentView view = _appointmentCollection[i]; + final AppointmentView view = _appointmentCollection[i]; /// Check the appointment view have appointment, if not then the /// appointment view is not valid or it will be used for reusing view. @@ -88,14 +130,10 @@ class _AgendaViewLayoutState extends State<_AgendaViewLayout> { continue; } final CalendarAppointmentDetails details = CalendarAppointmentDetails( - appointments: - List.unmodifiable([view.appointment._data ?? view.appointment]), - date: widget.selectedDate, - bounds: view.appointmentRect.outerRect); - final Widget child = widget.appointmentBuilder(context, details); - - /// Throw exception when builder return widget is null. - assert(child != null, 'Widget must not be null'); + widget.selectedDate!, + List.unmodifiable([view.appointment!.data ?? view.appointment!]), + view.appointmentRect!.outerRect); + final Widget child = widget.appointmentBuilder!(context, details); _children.add(RepaintBoundary(child: child)); } } @@ -125,13 +163,13 @@ class _AgendaViewLayoutState extends State<_AgendaViewLayout> { const double padding = 5; final double totalAgendaViewWidth = widget.width + widget.timeLabelWidth; - final bool useMobilePlatformUI = - _isMobileLayoutUI(totalAgendaViewWidth, widget.isMobilePlatform); - _resetAppointmentView(_appointmentCollection); + final bool useMobilePlatformUI = CalendarViewHelper.isMobileLayoutUI( + totalAgendaViewWidth, widget.isMobilePlatform); + AppointmentHelper.resetAppointmentView(_appointmentCollection); _children.clear(); if (widget.selectedDate == null || widget.appointments == null || - widget.appointments.isEmpty) { + widget.appointments!.isEmpty) { return; } @@ -140,22 +178,29 @@ class _AgendaViewLayoutState extends State<_AgendaViewLayout> { ? false : true; - widget.appointments.sort((Appointment app1, Appointment app2) => - app1._actualStartTime.compareTo(app2._actualStartTime)); - widget.appointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1.isAllDay, app2.isAllDay)); - widget.appointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1._isSpanned, app2._isSpanned)); - final double agendaItemHeight = _getScheduleAppointmentHeight( - widget.monthViewSettings, widget.scheduleViewSettings); - final double agendaAllDayItemHeight = _getScheduleAllDayAppointmentHeight( - widget.monthViewSettings, widget.scheduleViewSettings); - - for (int i = 0; i < widget.appointments.length; i++) { - final Appointment appointment = widget.appointments[i]; + widget.appointments!.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime)); + widget.appointments!.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isAllDay, app2.isAllDay)); + widget.appointments!.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isSpanned, app2.isSpanned)); + final double agendaItemHeight = + CalendarViewHelper.getScheduleAppointmentHeight( + widget.monthViewSettings, widget.scheduleViewSettings); + final double agendaAllDayItemHeight = + CalendarViewHelper.getScheduleAllDayAppointmentHeight( + widget.monthViewSettings, widget.scheduleViewSettings); + + for (int i = 0; i < widget.appointments!.length; i++) { + final CalendarAppointment appointment = widget.appointments![i]; final bool isSpanned = - appointment._actualEndTime.day != appointment._actualStartTime.day || - appointment._isSpanned; + appointment.actualEndTime.day != appointment.actualStartTime.day || + appointment.isSpanned; final double appointmentHeight = (appointment.isAllDay || isSpanned) && !isLargerScheduleUI ? agendaAllDayItemHeight @@ -165,9 +210,9 @@ class _AgendaViewLayoutState extends State<_AgendaViewLayout> { final Radius cornerRadius = Radius.circular( (appointmentHeight * 0.1) > 5 ? 5 : (appointmentHeight * 0.1)); yPosition += appointmentHeight + padding; - _AppointmentView appointmentRenderer; + AppointmentView? appointmentRenderer; for (int i = 0; i < _appointmentCollection.length; i++) { - final _AppointmentView view = _appointmentCollection[i]; + final AppointmentView view = _appointmentCollection[i]; if (view.appointment == null) { appointmentRenderer = view; break; @@ -175,7 +220,7 @@ class _AgendaViewLayoutState extends State<_AgendaViewLayout> { } if (appointmentRenderer == null) { - appointmentRenderer = _AppointmentView(); + appointmentRenderer = AppointmentView(); appointmentRenderer.appointment = appointment; appointmentRenderer.canReuse = false; _appointmentCollection.add(appointmentRenderer); @@ -207,23 +252,23 @@ class _AgendaViewRenderWidget extends MultiChildRenderObjectWidget { this.appointmentCollection, this.width, this.height, - {List widgets}) + {List widgets = const []}) : super(children: widgets); - final MonthViewSettings monthViewSettings; - final ScheduleViewSettings scheduleViewSettings; - final DateTime selectedDate; - final List appointments; + final MonthViewSettings? monthViewSettings; + final ScheduleViewSettings? scheduleViewSettings; + final DateTime? selectedDate; + final List? appointments; final bool isRTL; final String locale; final SfCalendarThemeData calendarTheme; - final ValueNotifier<_ScheduleViewHoveringDetails> agendaViewNotifier; + final ValueNotifier agendaViewNotifier; final SfLocalizations localizations; final double timeLabelWidth; - final String appointmentTimeTextFormat; + final String? appointmentTimeTextFormat; final double textScaleFactor; final bool isMobilePlatform; - final List<_AppointmentView> appointmentCollection; + final List appointmentCollection; final double width; final double height; @@ -271,7 +316,7 @@ class _AgendaViewRenderWidget extends MultiChildRenderObjectWidget { } class _AgendaViewRenderObject extends RenderBox - with ContainerRenderObjectMixin { + with ContainerRenderObjectMixin { _AgendaViewRenderObject( this._monthViewSettings, this._scheduleViewSettings, @@ -331,11 +376,11 @@ class _AgendaViewRenderObject extends RenderBox markNeedsPaint(); } - MonthViewSettings _monthViewSettings; + MonthViewSettings? _monthViewSettings; - MonthViewSettings get monthViewSettings => _monthViewSettings; + MonthViewSettings? get monthViewSettings => _monthViewSettings; - set monthViewSettings(MonthViewSettings value) { + set monthViewSettings(MonthViewSettings? value) { if (_monthViewSettings == value) { return; } @@ -348,11 +393,11 @@ class _AgendaViewRenderObject extends RenderBox markNeedsPaint(); } - ScheduleViewSettings _scheduleViewSettings; + ScheduleViewSettings? _scheduleViewSettings; - ScheduleViewSettings get scheduleViewSettings => _scheduleViewSettings; + ScheduleViewSettings? get scheduleViewSettings => _scheduleViewSettings; - set scheduleViewSettings(ScheduleViewSettings value) { + set scheduleViewSettings(ScheduleViewSettings? value) { if (_scheduleViewSettings == value) { return; } @@ -399,11 +444,11 @@ class _AgendaViewRenderObject extends RenderBox markNeedsPaint(); } - String _appointmentTimeTextFormat; + String? _appointmentTimeTextFormat; - String get appointmentTimeTextFormat => _appointmentTimeTextFormat; + String? get appointmentTimeTextFormat => _appointmentTimeTextFormat; - set appointmentTimeTextFormat(String value) { + set appointmentTimeTextFormat(String? value) { if (_appointmentTimeTextFormat == value) { return; } @@ -433,11 +478,11 @@ class _AgendaViewRenderObject extends RenderBox markNeedsPaint(); } - DateTime _selectedDate; + DateTime? _selectedDate; - DateTime get selectedDate => _selectedDate; + DateTime? get selectedDate => _selectedDate; - set selectedDate(DateTime value) { + set selectedDate(DateTime? value) { if (_selectedDate == value) { return; } @@ -450,11 +495,11 @@ class _AgendaViewRenderObject extends RenderBox } } - List _appointments; + List? _appointments; - List get appointments => _appointments; + List? get appointments => _appointments; - set appointments(List value) { + set appointments(List? value) { if (_appointments == value) { return; } @@ -467,11 +512,11 @@ class _AgendaViewRenderObject extends RenderBox } } - List<_AppointmentView> _appointmentCollection; + List _appointmentCollection; - List<_AppointmentView> get appointmentCollection => _appointmentCollection; + List get appointmentCollection => _appointmentCollection; - set appointmentCollection(List<_AppointmentView> value) { + set appointmentCollection(List value) { if (_appointmentCollection == value) { return; } @@ -514,53 +559,65 @@ class _AgendaViewRenderObject extends RenderBox markNeedsPaint(); } - ValueNotifier<_ScheduleViewHoveringDetails> _agendaViewNotifier; + ValueNotifier _agendaViewNotifier; - ValueNotifier<_ScheduleViewHoveringDetails> get agendaViewNotifier => + ValueNotifier get agendaViewNotifier => _agendaViewNotifier; - set agendaViewNotifier(ValueNotifier<_ScheduleViewHoveringDetails> value) { + set agendaViewNotifier(ValueNotifier value) { if (_agendaViewNotifier == value) { return; } - _agendaViewNotifier?.removeListener(markNeedsPaint); + _agendaViewNotifier.removeListener(markNeedsPaint); _agendaViewNotifier = value; - _agendaViewNotifier?.addListener(markNeedsPaint); + _agendaViewNotifier.addListener(markNeedsPaint); } - Paint _rectPainter; - TextPainter _textPainter; + /// Caches [SemanticsNode]s created during [assembleSemanticsNode] so they + /// can be re-used when [assembleSemanticsNode] is called again. This ensures + /// stable ids for the [SemanticsNode]s of children across + /// [assembleSemanticsNode] invocations. + /// Ref: assembleSemanticsNode method in RenderParagraph class + /// (https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/paragraph.dart) + List? _cacheNodes; + Paint _rectPainter = Paint(); + TextPainter _textPainter = TextPainter(); /// attach will called when the render object rendered in view. @override void attach(PipelineOwner owner) { super.attach(owner); - _agendaViewNotifier?.addListener(markNeedsPaint); + _agendaViewNotifier.addListener(markNeedsPaint); } /// detach will called when the render object removed from view. @override void detach() { - _agendaViewNotifier?.removeListener(markNeedsPaint); + _agendaViewNotifier.removeListener(markNeedsPaint); super.detach(); } @override void setupParentData(RenderObject child) { - if (child.parentData is! _CalendarParentData) { - child.parentData = _CalendarParentData(); + if (child.parentData is! CalendarParentData) { + child.parentData = CalendarParentData(); } } + @override + bool hitTestSelf(Offset position) { + return true; + } + @override void performLayout() { final Size widgetSize = constraints.biggest; size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, widgetSize.height.isInfinite ? height : widgetSize.height); - RenderBox child = firstChild; + RenderBox? child = firstChild; for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; + final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.appointment == null || child == null || appointmentView.appointmentRect == null) { @@ -568,41 +625,39 @@ class _AgendaViewRenderObject extends RenderBox } child.layout(constraints.copyWith( - minHeight: appointmentView.appointmentRect.height, - maxHeight: appointmentView.appointmentRect.height, - minWidth: appointmentView.appointmentRect.width, - maxWidth: appointmentView.appointmentRect.width)); + minHeight: appointmentView.appointmentRect!.height, + maxHeight: appointmentView.appointmentRect!.height, + minWidth: appointmentView.appointmentRect!.width, + maxWidth: appointmentView.appointmentRect!.width)); child = childAfter(child); } } @override void paint(PaintingContext context, Offset offset) { - RenderBox child = firstChild; + RenderBox? child = firstChild; final bool _isNeedDefaultPaint = childCount == 0; - _rectPainter = _rectPainter ?? Paint(); final double totalAgendaViewWidth = size.width + _timeLabelWidth; - final bool useMobilePlatformUI = - _isMobileLayoutUI(totalAgendaViewWidth, isMobilePlatform); + final bool useMobilePlatformUI = CalendarViewHelper.isMobileLayoutUI( + totalAgendaViewWidth, isMobilePlatform); final bool isLargerScheduleUI = scheduleViewSettings == null || useMobilePlatformUI ? false : true; if (_isNeedDefaultPaint) { - _textPainter = _textPainter ?? TextPainter(); _drawDefaultUI(context.canvas, isLargerScheduleUI, offset); } else { const double padding = 5.0; for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; + final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.appointment == null || child == null || appointmentView.appointmentRect == null) { continue; } - final RRect rect = appointmentView.appointmentRect.shift(offset); + final RRect rect = appointmentView.appointmentRect!.shift(offset); child.paint(context, Offset(rect.left, rect.top)); if (agendaViewNotifier.value != null && - isSameDate(agendaViewNotifier.value.hoveringDate, selectedDate)) { + isSameDate(agendaViewNotifier.value!.hoveringDate, selectedDate)) { _addMouseHovering( context.canvas, size, rect, isLargerScheduleUI, padding); } @@ -624,18 +679,19 @@ class _AgendaViewRenderObject extends RenderBox SemanticsConfiguration config, Iterable children, ) { + _cacheNodes ??= []; final List semantics = _getSemanticsBuilder(size); final List semanticsNodes = []; for (int i = 0; i < semantics.length; i++) { final CustomPainterSemantics currentSemantics = semantics[i]; - final SemanticsNode newChild = SemanticsNode( - key: currentSemantics.key, - ); + final SemanticsNode newChild = _cacheNodes!.isNotEmpty + ? _cacheNodes!.removeAt(0) + : SemanticsNode(key: currentSemantics.key); final SemanticsProperties properties = currentSemantics.properties; final SemanticsConfiguration config = SemanticsConfiguration(); if (properties.label != null) { - config.label = properties.label; + config.label = properties.label!; } if (properties.textDirection != null) { config.textDirection = properties.textDirection; @@ -658,10 +714,16 @@ class _AgendaViewRenderObject extends RenderBox final List finalChildren = []; finalChildren.addAll(semanticsNodes); finalChildren.addAll(children); - + _cacheNodes = semanticsNodes; super.assembleSemanticsNode(node, config, finalChildren); } + @override + void clearSemantics() { + super.clearSemantics(); + _cacheNodes = null; + } + @override void visitChildrenForSemantics(RenderObjectVisitor visitor) { return; @@ -679,29 +741,28 @@ class _AgendaViewRenderObject extends RenderBox ), )); } else if (selectedDate != null && - (appointments == null || appointments.isEmpty)) { + (appointments == null || appointments!.isEmpty)) { semanticsBuilder.add(CustomPainterSemantics( rect: Offset.zero & size, properties: SemanticsProperties( - label: DateFormat('EEEEE').format(selectedDate).toString() + - DateFormat('dd/MMMM/yyyy').format(selectedDate).toString() + + label: DateFormat('EEEEE').format(selectedDate!).toString() + + DateFormat('dd/MMMM/yyyy').format(selectedDate!).toString() + ', ' 'No events', textDirection: TextDirection.ltr, ), )); - } else if (selectedDate != null && - appointmentCollection != null && - appointmentCollection.isNotEmpty) { + } else if (selectedDate != null) { for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; + final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.appointment == null) { continue; } semanticsBuilder.add(CustomPainterSemantics( - rect: appointmentView.appointmentRect.outerRect, + rect: appointmentView.appointmentRect!.outerRect, properties: SemanticsProperties( - label: _getAppointmentText(appointmentView.appointment), + label: CalendarViewHelper.getAppointmentSemanticsText( + appointmentView.appointment!), textDirection: TextDirection.ltr, ), )); @@ -712,21 +773,20 @@ class _AgendaViewRenderObject extends RenderBox } void _drawDefaultUI(Canvas canvas, bool isLargerScheduleUI, Offset offset) { - _rectPainter = _rectPainter ?? Paint(); _rectPainter.isAntiAlias = true; double yPosition = offset.dy + 5; double xPosition = offset.dx + 5; const double padding = 5; - if (selectedDate == null || appointments == null || appointments.isEmpty) { + if (selectedDate == null || appointments == null || appointments!.isEmpty) { _drawDefaultView(canvas, size, xPosition, yPosition, padding); return; } final TextStyle appointmentTextStyle = monthViewSettings != null - ? monthViewSettings.agendaStyle.appointmentTextStyle ?? + ? monthViewSettings!.agendaStyle.appointmentTextStyle ?? TextStyle(color: Colors.white, fontSize: 13, fontFamily: 'Roboto') - : scheduleViewSettings.appointmentTextStyle ?? + : scheduleViewSettings!.appointmentTextStyle ?? TextStyle( color: isLargerScheduleUI && calendarTheme.brightness == Brightness.light @@ -737,18 +797,18 @@ class _AgendaViewRenderObject extends RenderBox //// Draw Appointments for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; + final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.appointment == null) { continue; } - final Appointment appointment = appointmentView.appointment; + final CalendarAppointment appointment = appointmentView.appointment!; _rectPainter.color = appointment.color; final bool isSpanned = - appointment._actualEndTime.day != appointment._actualStartTime.day || - appointment._isSpanned; - final double appointmentHeight = appointmentView.appointmentRect.height; - final RRect rect = appointmentView.appointmentRect.shift(offset); + appointment.actualEndTime.day != appointment.actualStartTime.day || + appointment.isSpanned; + final double appointmentHeight = appointmentView.appointmentRect!.height; + final RRect rect = appointmentView.appointmentRect!.shift(offset); xPosition = rect.left; yPosition = rect.top; @@ -766,7 +826,7 @@ class _AgendaViewRenderObject extends RenderBox xPosition += timeWidth; final bool isRecurrenceAppointment = appointment.recurrenceRule != null && - appointment.recurrenceRule.isNotEmpty; + appointment.recurrenceRule!.isNotEmpty; final double textSize = _getTextSize(rect, appointmentTextStyle, isMobilePlatform); @@ -788,8 +848,8 @@ class _AgendaViewRenderObject extends RenderBox appointmentTextStyle, offset); if (isSpanned) { - final TextSpan icon = _getSpanIcon( - appointmentTextStyle.color, + final TextSpan icon = AppointmentHelper.getSpanIcon( + appointmentTextStyle.color!, isMobilePlatform ? textSize : textSize / 1.5, isRTL ? false : true); _drawIcon(canvas, size, textSize, rect, padding, isLargerScheduleUI, @@ -844,8 +904,8 @@ class _AgendaViewRenderObject extends RenderBox } if (isRecurrenceAppointment) { - final TextSpan icon = - _getRecurrenceIcon(appointmentTextStyle.color, textSize); + final TextSpan icon = AppointmentHelper.getRecurrenceIcon( + appointmentTextStyle.color!, textSize); _drawIcon( canvas, size, @@ -862,7 +922,7 @@ class _AgendaViewRenderObject extends RenderBox } if (agendaViewNotifier.value != null && - isSameDate(agendaViewNotifier.value.hoveringDate, selectedDate)) { + isSameDate(agendaViewNotifier.value!.hoveringDate, selectedDate)) { _addMouseHovering(canvas, size, rect, isLargerScheduleUI, padding); } } @@ -877,7 +937,7 @@ class _AgendaViewRenderObject extends RenderBox final double textSize = isMobilePlatform ? appointmentTextStyle.fontSize ?? defaultFontSize : appointmentTextStyle.fontSize != null - ? appointmentTextStyle.fontSize * 1.5 + ? appointmentTextStyle.fontSize! * 1.5 : defaultFontSize * 1.5; if (rect.width < textSize || rect.height < textSize) { return rect.width > rect.height ? rect.height : rect.width; @@ -929,9 +989,9 @@ class _AgendaViewRenderObject extends RenderBox /// value 2 used since the space on top and bottom of icon is not even, /// hence to rectify this tha value 2 used, and tested with multiple /// device. - iconStartPosition = - (_textPainter.height - (icon.style.fontSize * textScaleFactor) / 2) / - 2; + iconStartPosition = (_textPainter.height - + (icon.style!.fontSize! * textScaleFactor) / 2) / + 2; } // Value 8 added as a right side padding for the recurrence icon in the @@ -972,7 +1032,7 @@ class _AgendaViewRenderObject extends RenderBox double timeWidth, bool isRecurrence, double recurrenceTextSize, - Appointment appointment, + CalendarAppointment appointment, double appointmentHeight, TextStyle appointmentTextStyle) { _textPainter.textScaleFactor = textScaleFactor; @@ -993,13 +1053,13 @@ class _AgendaViewRenderObject extends RenderBox canvas, Offset(xPosition + padding, yPosition + topPadding)); final String format = appointmentTimeTextFormat ?? - (isSameDate(appointment._actualStartTime, appointment._actualEndTime) + (isSameDate(appointment.actualStartTime, appointment.actualEndTime) ? 'hh:mm a' : 'MMM dd, hh:mm a'); final TextSpan span = TextSpan( - text: DateFormat(format, locale).format(appointment._actualStartTime) + + text: DateFormat(format, locale).format(appointment.actualStartTime) + ' - ' + - DateFormat(format, locale).format(appointment._actualEndTime), + DateFormat(format, locale).format(appointment.actualEndTime), style: appointmentTextStyle); _textPainter.text = span; @@ -1021,7 +1081,7 @@ class _AgendaViewRenderObject extends RenderBox double xPosition, double yPosition, double padding, - Appointment appointment, + CalendarAppointment appointment, TextStyle appointmentTextStyle, double appointmentHeight, RRect rect, @@ -1029,14 +1089,15 @@ class _AgendaViewRenderObject extends RenderBox bool isLargerScheduleUI, Radius cornerRadius) { final TextSpan span = TextSpan( - text: _getSpanAppointmentText(appointment, selectedDate), + text: AppointmentHelper.getSpanAppointmentText( + appointment, selectedDate!, _localizations), style: appointmentTextStyle); _updateTextPainterProperties(span); _updatePainterLinesCount(appointmentHeight, isAllDay: false, isSpanned: true); final bool isNeedSpanIcon = - !isSameDate(appointment._exactEndTime, selectedDate); + !isSameDate(appointment.exactEndTime, selectedDate); final double textSize = _getTextSize(rect, appointmentTextStyle, isMobilePlatform); @@ -1057,8 +1118,10 @@ class _AgendaViewRenderObject extends RenderBox return topPadding; } - final TextSpan icon = _getSpanIcon(appointmentTextStyle.color, - isMobilePlatform ? textSize : textSize / 1.5, isRTL ? false : true); + final TextSpan icon = AppointmentHelper.getSpanIcon( + appointmentTextStyle.color!, + isMobilePlatform ? textSize : textSize / 1.5, + isRTL ? false : true); _drawIcon(canvas, size, textSize, rect, padding, isLargerScheduleUI, cornerRadius, icon, appointmentHeight, topPadding, true, false); return topPadding; @@ -1083,7 +1146,6 @@ class _AgendaViewRenderObject extends RenderBox } void _updateTextPainterProperties(TextSpan span) { - _textPainter ??= TextPainter(); _textPainter.text = span; _textPainter.maxLines = 1; _textPainter.textDirection = TextDirection.ltr; @@ -1102,7 +1164,7 @@ class _AgendaViewRenderObject extends RenderBox double appointmentHeight, bool isNeedIcon, double textSize, - Appointment appointment, + CalendarAppointment appointment, TextStyle appointmentTextStyle, Offset offset) { _textPainter.textScaleFactor = textScaleFactor; @@ -1142,11 +1204,11 @@ class _AgendaViewRenderObject extends RenderBox final DateFormat format = DateFormat(appointmentTimeTextFormat ?? 'hh:mm a', locale); final TextSpan span = TextSpan( - text: appointment.isAllDay || appointment._isSpanned + text: appointment.isAllDay || appointment.isSpanned ? 'All Day' - : format.format(appointment._actualStartTime) + + : format.format(appointment.actualStartTime) + ' - ' + - format.format(appointment._actualEndTime), + format.format(appointment.actualEndTime), style: appointmentTextStyle); _textPainter.text = span; @@ -1165,11 +1227,10 @@ class _AgendaViewRenderObject extends RenderBox void _addMouseHovering(Canvas canvas, Size size, RRect rect, bool isLargerScheduleUI, double padding) { - _rectPainter ??= Paint(); - if (rect.left < agendaViewNotifier.value.hoveringOffset.dx && - rect.right > agendaViewNotifier.value.hoveringOffset.dx && - rect.top < agendaViewNotifier.value.hoveringOffset.dy && - rect.bottom > agendaViewNotifier.value.hoveringOffset.dy) { + if (rect.left < agendaViewNotifier.value!.hoveringOffset.dx && + rect.right > agendaViewNotifier.value!.hoveringOffset.dx && + rect.top < agendaViewNotifier.value!.hoveringOffset.dy && + rect.bottom > agendaViewNotifier.value!.hoveringOffset.dy) { if (isLargerScheduleUI) { _rectPainter.color = Colors.grey.withOpacity(0.1); const double viewPadding = 2; @@ -1184,7 +1245,7 @@ class _AgendaViewRenderObject extends RenderBox _rectPainter); } else { _rectPainter.color = - calendarTheme.selectionBorderColor.withOpacity(0.4); + calendarTheme.selectionBorderColor!.withOpacity(0.4); _rectPainter.style = PaintingStyle.stroke; _rectPainter.strokeWidth = 2; canvas.drawRect(rect.outerRect, _rectPainter); @@ -1193,327 +1254,3 @@ class _AgendaViewRenderObject extends RenderBox } } } - -class _AgendaDateTimePainter extends CustomPainter { - _AgendaDateTimePainter( - this.selectedDate, - this.monthViewSettings, - this.scheduleViewSettings, - this.todayHighlightColor, - this.todayTextStyle, - this.locale, - this.calendarTheme, - this.agendaDateNotifier, - this.viewWidth, - this.isRTL, - this.textScaleFactor, - this.isMobilePlatform) - : super(repaint: agendaDateNotifier); - - final DateTime selectedDate; - final MonthViewSettings monthViewSettings; - final ScheduleViewSettings scheduleViewSettings; - final Color todayHighlightColor; - final TextStyle todayTextStyle; - final String locale; - final ValueNotifier<_ScheduleViewHoveringDetails> agendaDateNotifier; - final SfCalendarThemeData calendarTheme; - final double viewWidth; - final bool isRTL; - final double textScaleFactor; - final bool isMobilePlatform; - Paint _linePainter; - TextPainter _textPainter; - - @override - void paint(Canvas canvas, Size size) { - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - _linePainter = _linePainter ?? Paint(); - _linePainter.isAntiAlias = true; - const double padding = 5; - if (selectedDate == null) { - return; - } - - final bool useMobilePlatformUI = - _isMobileLayoutUI(viewWidth, isMobilePlatform); - final bool isToday = isSameDate(selectedDate, DateTime.now()); - TextStyle dateTextStyle, dayTextStyle; - if (monthViewSettings != null) { - dayTextStyle = monthViewSettings.agendaStyle.dayTextStyle ?? - calendarTheme.agendaDayTextStyle; - dateTextStyle = monthViewSettings.agendaStyle.dateTextStyle ?? - calendarTheme.agendaDateTextStyle; - } else { - dayTextStyle = scheduleViewSettings.dayHeaderSettings.dayTextStyle ?? - (useMobilePlatformUI - ? calendarTheme.agendaDayTextStyle - : TextStyle( - color: calendarTheme.agendaDayTextStyle.color, - fontSize: 9, - fontFamily: 'Roboto', - fontWeight: FontWeight.w500)); - dateTextStyle = scheduleViewSettings.dayHeaderSettings.dateTextStyle ?? - (useMobilePlatformUI - ? calendarTheme.agendaDateTextStyle - : TextStyle( - color: calendarTheme.agendaDateTextStyle.color, - fontSize: 18, - fontFamily: 'Roboto', - fontWeight: FontWeight.normal)); - } - - final Color selectedDayTextColor = isToday - ? todayHighlightColor ?? calendarTheme.todayHighlightColor - : dayTextStyle != null - ? dayTextStyle.color - : calendarTheme.agendaDayTextStyle.color; - final Color selectedDateTextColor = isToday - ? calendarTheme.todayTextStyle.color - : dateTextStyle != null - ? dateTextStyle.color - : calendarTheme.agendaDateTextStyle.color; - dayTextStyle = dayTextStyle.copyWith(color: selectedDayTextColor); - dateTextStyle = dateTextStyle.copyWith(color: selectedDateTextColor); - if (isToday) { - dayTextStyle = todayTextStyle != null - ? todayTextStyle.copyWith( - fontSize: dayTextStyle.fontSize, color: selectedDayTextColor) - : dayTextStyle; - dateTextStyle = todayTextStyle != null - ? todayTextStyle.copyWith( - fontSize: dateTextStyle.fontSize, color: selectedDateTextColor) - : dateTextStyle; - } - - /// Draw day label other than web schedule view. - if (scheduleViewSettings == null || useMobilePlatformUI) { - _addDayLabelForMobile(canvas, size, padding, dayTextStyle, dateTextStyle, - isToday, isMobilePlatform); - } else { - _addDayLabelForWeb( - canvas, size, padding, dayTextStyle, dateTextStyle, isToday); - } - } - - void _updateTextPainter(TextSpan span) { - _textPainter = _textPainter ?? TextPainter(); - _textPainter.text = span; - _textPainter.maxLines = 1; - _textPainter.textDirection = TextDirection.ltr; - _textPainter.textAlign = TextAlign.left; - _textPainter.textWidthBasis = TextWidthBasis.parent; - _textPainter.textScaleFactor = textScaleFactor; - } - - void _addDayLabelForMobile( - Canvas canvas, - Size size, - double padding, - TextStyle dayTextStyle, - TextStyle dateTextStyle, - bool isToday, - bool isMobile) { - //// Draw Weekday - final String dayTextFormat = scheduleViewSettings != null - ? scheduleViewSettings.dayHeaderSettings.dayFormat - : 'EEE'; - TextSpan span = TextSpan( - text: DateFormat(dayTextFormat, locale) - .format(selectedDate) - .toUpperCase() - .toString(), - style: dayTextStyle); - _updateTextPainter(span); - - _textPainter.layout(minWidth: 0, maxWidth: size.width); - _textPainter.paint( - canvas, - Offset( - padding + ((size.width - (2 * padding) - _textPainter.width) / 2), - padding)); - - final double weekDayHeight = padding + _textPainter.height; - //// Draw Date - span = TextSpan(text: selectedDate.day.toString(), style: dateTextStyle); - _updateTextPainter(span); - - _textPainter.layout(minWidth: 0, maxWidth: size.width); - - /// The padding value provides the space between the date and day text. - const int inBetweenPadding = 2; - final double xPosition = - padding + ((size.width - (2 * padding) - _textPainter.width) / 2); - double yPosition = weekDayHeight; - if (isToday) { - yPosition = weekDayHeight + padding + inBetweenPadding; - _linePainter.color = todayHighlightColor; - _drawTodayCircle(canvas, xPosition, yPosition, padding); - } - - /// padding added between date and day labels in web, to avoid the - /// hovering effect overlapping issue. - if (!isMobile && !isToday) { - yPosition = weekDayHeight + padding + inBetweenPadding; - } - if (agendaDateNotifier.value != null && - isSameDate(agendaDateNotifier.value.hoveringDate, selectedDate)) { - if (xPosition < agendaDateNotifier.value.hoveringOffset.dx && - xPosition + _textPainter.width > - agendaDateNotifier.value.hoveringOffset.dx && - yPosition < agendaDateNotifier.value.hoveringOffset.dy && - yPosition + _textPainter.height > - agendaDateNotifier.value.hoveringOffset.dy) { - _linePainter.color = isToday - ? Colors.black.withOpacity(0.1) - : (calendarTheme.brightness != null && - calendarTheme.brightness == Brightness.dark - ? Colors.white - : Colors.black87) - .withOpacity(0.04); - _drawTodayCircle(canvas, xPosition, yPosition, padding); - } - } - - _textPainter.paint(canvas, Offset(xPosition, yPosition)); - } - - void _addDayLabelForWeb(Canvas canvas, Size size, double padding, - TextStyle dayTextStyle, TextStyle dateTextStyle, bool isToday) { - /// Draw day label on web schedule view. - final String dateText = selectedDate.day.toString(); - - /// Calculate the date text maximum width value. - final String maxWidthDateText = '30'; - final String dayText = DateFormat( - isRTL - ? scheduleViewSettings.dayHeaderSettings.dayFormat + ', MMM' - : 'MMM, ' + scheduleViewSettings.dayHeaderSettings.dayFormat, - locale) - .format(selectedDate) - .toUpperCase() - .toString(); - - //// Draw Weekday - TextSpan span = TextSpan(text: maxWidthDateText, style: dateTextStyle); - _updateTextPainter(span); - _textPainter.layout(minWidth: 0, maxWidth: size.width); - - /// Calculate the start padding value for web schedule view date time label. - double startXPosition = size.width / 5; - startXPosition = isRTL ? size.width - startXPosition : startXPosition; - final double dateHeight = size.height; - final double yPosition = (dateHeight - _textPainter.height) / 2; - final double painterWidth = _textPainter.width; - span = TextSpan(text: dateText, style: dateTextStyle); - _textPainter.text = span; - _textPainter.layout(minWidth: 0, maxWidth: size.width); - double dateTextPadding = (painterWidth - _textPainter.width) / 2; - if (dateTextPadding < 0) { - dateTextPadding = 0; - } - - final double dateTextStartPosition = startXPosition + dateTextPadding; - if (isToday) { - _linePainter.color = todayHighlightColor; - _drawTodayCircle(canvas, dateTextStartPosition, yPosition, padding); - } - - if (agendaDateNotifier.value != null && - isSameDate(agendaDateNotifier.value.hoveringDate, selectedDate)) { - if (dateTextStartPosition < - (isRTL - ? size.width - agendaDateNotifier.value.hoveringOffset.dx - : agendaDateNotifier.value.hoveringOffset.dx) && - (dateTextStartPosition + _textPainter.width) > - (isRTL - ? size.width - agendaDateNotifier.value.hoveringOffset.dx - : agendaDateNotifier.value.hoveringOffset.dx) && - yPosition < agendaDateNotifier.value.hoveringOffset.dy && - (yPosition + _textPainter.height) > - agendaDateNotifier.value.hoveringOffset.dy) { - _linePainter.color = isToday - ? Colors.black.withOpacity(0.1) - : (calendarTheme.brightness != null && - calendarTheme.brightness == Brightness.dark - ? Colors.white - : Colors.black87) - .withOpacity(0.04); - _drawTodayCircle(canvas, dateTextStartPosition, yPosition, padding); - } - } - - _textPainter.paint(canvas, Offset(dateTextStartPosition, yPosition)); - - //// Draw Date - span = TextSpan(text: dayText, style: dayTextStyle); - _textPainter.text = span; - if (isRTL) { - _textPainter.layout(minWidth: 0, maxWidth: startXPosition); - startXPosition -= _textPainter.width + (3 * padding); - if (startXPosition > 0) { - _textPainter.paint(canvas, - Offset(startXPosition, (dateHeight - _textPainter.height) / 2)); - } - } else { - startXPosition += painterWidth + (3 * padding); - if (startXPosition > size.width) { - return; - } - _textPainter.layout(minWidth: 0, maxWidth: size.width - startXPosition); - _textPainter.paint(canvas, - Offset(startXPosition, (dateHeight - _textPainter.height) / 2)); - } - } - - void _drawTodayCircle( - Canvas canvas, double xPosition, double yPosition, double padding) { - canvas.drawCircle( - Offset(xPosition + (_textPainter.width / 2), - yPosition + (_textPainter.height / 2)), - _textPainter.width > _textPainter.height - ? (_textPainter.width / 2) + padding - : (_textPainter.height / 2) + padding, - _linePainter); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - return true; - } - - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilder(size); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - return true; - } - - List _getSemanticsBuilder(Size size) { - final List semanticsBuilder = - []; - if (selectedDate == null) { - return semanticsBuilder; - } else if (selectedDate != null) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Offset.zero & size, - properties: SemanticsProperties( - label: DateFormat('EEEEE').format(selectedDate).toString() + - DateFormat('dd/MMMM/yyyy').format(selectedDate).toString(), - textDirection: TextDirection.ltr, - ), - )); - } - - return semanticsBuilder; - } -} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/allday_appointment_layout.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/allday_appointment_layout.dart index 0478513ac..a66e9f480 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/allday_appointment_layout.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/allday_appointment_layout.dart @@ -1,18 +1,22 @@ -part of calendar; - -/// The class contains all day panel selection details. -/// if all day panel appointment selected then [appointmentView] holds -/// appointment details, else [selectedDate] holds selected region date value. -@immutable -class _SelectionDetails { - const _SelectionDetails(this.appointmentView, this.selectedDate); - - final _AppointmentView appointmentView; - final DateTime selectedDate; -} - -class _AllDayAppointmentLayout extends StatefulWidget { - _AllDayAppointmentLayout( +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../appointment_engine/appointment_helper.dart'; +import '../common/calendar_view_helper.dart'; +import '../common/date_time_engine.dart'; +import '../common/enums.dart'; +import '../common/event_args.dart'; +import '../sfcalendar.dart'; + +/// Used to holds the all day appointment views in calendar widgets. +class AllDayAppointmentLayout extends StatefulWidget { + /// Constructor to create the all day appointment layout that holds the + /// all day appointment views in calendar widget. + AllDayAppointmentLayout( this.calendar, this.view, this.visibleDates, @@ -29,63 +33,100 @@ class _AllDayAppointmentLayout extends StatefulWidget { this.isMobilePlatform, this.width, this.height, - {this.updateCalendarState}); + this.localizations, + this.updateCalendarState); + /// Holds the calendar instance used the get the properties of calendar. final SfCalendar calendar; + + /// Defines the current calendar view of the calendar widget. final CalendarView view; + + /// Holds the visible dates of the appointments view. final List visibleDates; - final List visibleAppointments; - final ValueNotifier<_SelectionDetails> repaintNotifier; - final _UpdateCalendarState updateCalendarState; + + /// Holds the visible appointment collection of the calendar widget. + final List? visibleAppointments; + + /// Holds the selection details and user to trigger repaint to draw the + /// selection. + final ValueNotifier repaintNotifier; + + /// Used to get the calendar state details. + final UpdateCalendarState updateCalendarState; + + /// Holds the time label width of calendar widget. final double timeLabelWidth; + + /// Used to holds the all painter height. Value is differ while + /// expanded and collapsed and the initial value does not depends on expander + /// initial page navigation animation. final double allDayPainterHeight; + + /// Defines the direction of the calendar widget is RTL or not. final bool isRTL; + + /// Holds the theme data of the calendar widget. final SfCalendarThemeData calendarTheme; - final ValueNotifier allDayHoverPosition; + + /// Used to hold the all day appointment hovering position. + final ValueNotifier allDayHoverPosition; + + /// Defines the scale factor of the calendar widget. final double textScaleFactor; + + /// Defines the current platform is mobile platform or not. final bool isMobilePlatform; + + /// Defines the height of the all day appointment layout widget. final double height; + + /// Defines the width of the all day appointment layout widget. final double width; - //// is expandable variable used to indicate whether the all day layout expandable or not. + /// Is expandable variable used to indicate whether the all day layout + /// expandable or not. final bool isExpandable; - //// is expanding variable used to identify the animation currently running or not. - //// It is used to restrict the expander icon show on initial animation. + /// Is expanding variable used to identify the animation currently running or + /// not. It is used to restrict the expander icon show on initial animation. final bool isExpanding; + /// Holds the localization data of the calendar widget. + final SfLocalizations localizations; + @override _AllDayAppointmentLayoutState createState() => _AllDayAppointmentLayoutState(); } -class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { - final _UpdateCalendarStateDetails _updateCalendarStateDetails = - _UpdateCalendarStateDetails(); +class _AllDayAppointmentLayoutState extends State { + final UpdateCalendarStateDetails _updateCalendarStateDetails = + UpdateCalendarStateDetails(); /// It holds the appointment list based on its visible index value. - Map> _indexAppointments; + Map> _indexAppointments = + >{}; /// It holds the more appointment index appointment counts based on its index. - Map _moreAppointmentIndex; + Map _moreAppointmentIndex = {}; /// It holds the appointment views for the visible appointments. - List<_AppointmentView> _appointmentCollection; + List _appointmentCollection = []; - /// It holds the children of the widget, it holds null or empty when + /// It holds the children of the widget, it holds empty when /// appointment builder is null. - List _children; + List _children = []; @override void initState() { - _children = []; widget.updateCalendarState(_updateCalendarStateDetails); _updateAppointmentDetails(); super.initState(); } @override - void didUpdateWidget(_AllDayAppointmentLayout oldWidget) { + void didUpdateWidget(AllDayAppointmentLayout oldWidget) { widget.updateCalendarState(_updateCalendarStateDetails); if (widget.visibleDates != oldWidget.visibleDates || widget.width != oldWidget.width || @@ -105,14 +146,10 @@ class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { @override Widget build(BuildContext context) { - _children ??= []; - /// Create the widgets when appointment builder is not null. - if (_children.isEmpty && - _appointmentCollection != null && - widget.calendar.appointmentBuilder != null) { + if (_children.isEmpty && widget.calendar.appointmentBuilder != null) { for (int i = 0; i < _appointmentCollection.length; i++) { - final _AppointmentView appointmentView = _appointmentCollection[i]; + final AppointmentView appointmentView = _appointmentCollection[i]; /// Check the appointment view have appointment, if not then the /// appointment view is not valid or it will be placed on in expandable @@ -123,26 +160,24 @@ class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { } final DateTime date = DateTime( - appointmentView.appointment._actualStartTime.year, - appointmentView.appointment._actualStartTime.month, - appointmentView.appointment._actualStartTime.day); - final Widget child = widget.calendar.appointmentBuilder( + appointmentView.appointment!.actualStartTime.year, + appointmentView.appointment!.actualStartTime.month, + appointmentView.appointment!.actualStartTime.day); + final Widget child = widget.calendar.appointmentBuilder!( context, CalendarAppointmentDetails( - date: date, - bounds: Rect.fromLTWH( - appointmentView.appointmentRect.left, - appointmentView.appointmentRect.top, - appointmentView.appointmentRect.width, - appointmentView.appointmentRect.right), - appointments: List.unmodifiable([ - appointmentView.appointment._data ?? - appointmentView.appointment + date, + List.unmodifiable([ + appointmentView.appointment!.data ?? + appointmentView.appointment! ]), + Rect.fromLTWH( + appointmentView.appointmentRect!.left, + appointmentView.appointmentRect!.top, + appointmentView.appointmentRect!.width, + appointmentView.appointmentRect!.right), isMoreAppointmentRegion: false)); - /// Throw exception when builder return widget is null. - assert(child != null, 'Widget must not be null'); _children.add(RepaintBoundary(child: child)); } @@ -162,33 +197,32 @@ class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { final int index = keys[i]; final DateTime date = widget.visibleDates[index]; final double xPosition = widget.timeLabelWidth + (index * cellWidth); - final List moreAppointments = []; - final List<_AppointmentView> moreAppointmentViews = - _indexAppointments[index]; + final List moreAppointments = + []; + final List moreAppointmentViews = + _indexAppointments[index]!; /// Get the appointments of the more appointment cell index from more /// appointment views. for (int j = 0; j < moreAppointmentViews.length; j++) { - final _AppointmentView currentAppointment = moreAppointmentViews[j]; - moreAppointments.add(currentAppointment.appointment); + final AppointmentView currentAppointment = moreAppointmentViews[j]; + moreAppointments.add(currentAppointment.appointment!); } - final Widget child = widget.calendar.appointmentBuilder( + final Widget child = widget.calendar.appointmentBuilder!( context, CalendarAppointmentDetails( - date: date, - bounds: Rect.fromLTWH( + date, + List.unmodifiable( + CalendarViewHelper.getCustomAppointments(moreAppointments)), + Rect.fromLTWH( widget.isRTL ? widget.width - xPosition - maxAppointmentWidth : xPosition, - widget.height - _kAllDayAppointmentHeight, + widget.height - kAllDayAppointmentHeight, maxAppointmentWidth, - _kAllDayAppointmentHeight - 1), - appointments: - List.unmodifiable(_getCustomAppointments(moreAppointments)), + kAllDayAppointmentHeight - 1), isMoreAppointmentRegion: true)); - /// Throw exception when builder return widget is null. - assert(child != null, 'Widget must not be null'); _children.add(RepaintBoundary(child: child)); } } @@ -210,6 +244,7 @@ class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { widget.isMobilePlatform, widget.width, widget.height, + widget.localizations, _appointmentCollection, _moreAppointmentIndex, widgets: _children, @@ -217,31 +252,29 @@ class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { } void _updateAppointmentDetails() { - _indexAppointments = >{}; + _indexAppointments = >{}; _moreAppointmentIndex = {}; - _appointmentCollection = <_AppointmentView>[]; + _appointmentCollection = []; /// Return when the widget as not placed on current visible calendar view. if (widget.visibleDates != - _updateCalendarStateDetails._currentViewVisibleDates) { + _updateCalendarStateDetails.currentViewVisibleDates) { return; } _appointmentCollection = - _updateCalendarStateDetails._allDayAppointmentViewCollection ?? - <_AppointmentView>[]; + _updateCalendarStateDetails.allDayAppointmentViewCollection; final double cellWidth = (widget.width - widget.timeLabelWidth) / widget.visibleDates.length; final double cellEndPadding = widget.calendar.cellEndPadding; - const double cornerRadius = (_kAllDayAppointmentHeight * 0.1) > 2 + const double cornerRadius = (kAllDayAppointmentHeight * 0.1) > 2 ? 2 - : _kAllDayAppointmentHeight * 0.1; + : kAllDayAppointmentHeight * 0.1; /// Calculate the maximum position of the appointment this widget can hold. - final int position = - widget.allDayPainterHeight ~/ _kAllDayAppointmentHeight; + final int position = widget.allDayPainterHeight ~/ kAllDayAppointmentHeight; for (int i = 0; i < _appointmentCollection.length; i++) { - final _AppointmentView appointmentView = _appointmentCollection[i]; + final AppointmentView appointmentView = _appointmentCollection[i]; if (appointmentView.canReuse) { continue; } @@ -253,12 +286,12 @@ class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { ((widget.visibleDates.length - appointmentView.endIndex) * cellWidth) + cellEndPadding, - (_kAllDayAppointmentHeight * appointmentView.position) + (kAllDayAppointmentHeight * appointmentView.position) .toDouble(), (widget.visibleDates.length - appointmentView.startIndex) * cellWidth, - ((_kAllDayAppointmentHeight * appointmentView.position) + - _kAllDayAppointmentHeight - + ((kAllDayAppointmentHeight * appointmentView.position) + + kAllDayAppointmentHeight - 1) .toDouble()), const Radius.circular(cornerRadius)); @@ -267,13 +300,13 @@ class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { Rect.fromLTRB( widget.timeLabelWidth + (appointmentView.startIndex * cellWidth), - (_kAllDayAppointmentHeight * appointmentView.position) + (kAllDayAppointmentHeight * appointmentView.position) .toDouble(), (appointmentView.endIndex * cellWidth) + widget.timeLabelWidth - cellEndPadding, - ((_kAllDayAppointmentHeight * appointmentView.position) + - _kAllDayAppointmentHeight - + ((kAllDayAppointmentHeight * appointmentView.position) + + kAllDayAppointmentHeight - 1) .toDouble()), const Radius.circular(cornerRadius)); @@ -282,30 +315,31 @@ class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { for (int j = appointmentView.startIndex; j < appointmentView.endIndex; j++) { - List<_AppointmentView> appointmentViews; + List appointmentViews; if (_indexAppointments.containsKey(j)) { - appointmentViews = _indexAppointments[j]; + appointmentViews = _indexAppointments[j]!; appointmentViews.add(appointmentView); } else { - appointmentViews = <_AppointmentView>[appointmentView]; + appointmentViews = [appointmentView]; } _indexAppointments[j] = appointmentViews; } /// Calculate the appointment bound for visible region appointments not /// all visible appointments of the widget. - if (!widget.isRTL && rect.left < widget.timeLabelWidth - 1 || - rect.right > widget.width + 1 || - (rect.bottom > - widget.allDayPainterHeight - _kAllDayAppointmentHeight && - appointmentView.maxPositions > position)) { + if (!widget.isRTL && + (rect.left < widget.timeLabelWidth - 1 || + rect.right > widget.width + 1 || + (rect.bottom > + widget.allDayPainterHeight - kAllDayAppointmentHeight && + appointmentView.maxPositions > position))) { continue; } else if (widget.isRTL && - rect.right > widget.width - widget.timeLabelWidth + 1 || - rect.left < 0 || - (rect.bottom > - widget.allDayPainterHeight - _kAllDayAppointmentHeight && - appointmentView.maxPositions > position)) { + (rect.right > widget.width - widget.timeLabelWidth + 1 || + rect.left < 0 || + (rect.bottom > + widget.allDayPainterHeight - kAllDayAppointmentHeight && + appointmentView.maxPositions > position))) { continue; } @@ -318,7 +352,7 @@ class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { /// views in the widget. maxPosition = _appointmentCollection .reduce( - (_AppointmentView currentAppView, _AppointmentView nextAppView) => + (AppointmentView currentAppView, AppointmentView nextAppView) => currentAppView.maxPositions > nextAppView.maxPositions ? currentAppView : nextAppView) @@ -336,13 +370,13 @@ class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { final int endIndexPosition = position - 1; for (int i = 0; i < keys.length; i++) { final int key = keys[i]; - final List<_AppointmentView> appointmentViews = _indexAppointments[key]; + final List appointmentViews = _indexAppointments[key]!; int count = 0; if (appointmentViews.isNotEmpty) { /// Calculate the current index appointments max position. maxPosition = appointmentViews - .reduce((_AppointmentView currentAppView, - _AppointmentView nextAppView) => + .reduce((AppointmentView currentAppView, + AppointmentView nextAppView) => currentAppView.maxPositions > nextAppView.maxPositions ? currentAppView : nextAppView) @@ -352,7 +386,7 @@ class _AllDayAppointmentLayoutState extends State<_AllDayAppointmentLayout> { continue; } - for (final _AppointmentView view in appointmentViews) { + for (final AppointmentView view in appointmentViews) { if (view.appointment == null) { continue; } @@ -397,28 +431,30 @@ class _AllDayAppointmentRenderWidget extends MultiChildRenderObjectWidget { this.isMobilePlatform, this.width, this.height, + this.localizations, this.appointmentCollection, this.moreAppointmentIndex, - {List widgets}) + {List widgets = const []}) : super(children: widgets); final SfCalendar calendar; final CalendarView view; final List visibleDates; - final List visibleAppointments; - final ValueNotifier<_SelectionDetails> repaintNotifier; + final List? visibleAppointments; + final ValueNotifier repaintNotifier; final double timeLabelWidth; final double allDayPainterHeight; final bool isRTL; final SfCalendarThemeData calendarTheme; - final ValueNotifier allDayHoverPosition; + final ValueNotifier allDayHoverPosition; final double textScaleFactor; final bool isMobilePlatform; final double height; final double width; final bool isExpandable; final bool isExpanding; + final SfLocalizations localizations; final Map moreAppointmentIndex; - final List<_AppointmentView> appointmentCollection; + final List appointmentCollection; @override _AllDayAppointmentRenderObject createRenderObject(BuildContext context) { @@ -439,6 +475,7 @@ class _AllDayAppointmentRenderWidget extends MultiChildRenderObjectWidget { isMobilePlatform, width, height, + localizations, appointmentCollection, moreAppointmentIndex); } @@ -463,13 +500,14 @@ class _AllDayAppointmentRenderWidget extends MultiChildRenderObjectWidget { ..allDayHoverPosition = allDayHoverPosition ..textScaleFactor = textScaleFactor ..isMobilePlatform = isMobilePlatform + ..localizations = localizations ..width = width ..height = height; } } class _AllDayAppointmentRenderObject extends RenderBox - with ContainerRenderObjectMixin { + with ContainerRenderObjectMixin { _AllDayAppointmentRenderObject( this.calendar, this._view, @@ -487,6 +525,7 @@ class _AllDayAppointmentRenderObject extends RenderBox this.isMobilePlatform, this._width, this._height, + this._localizations, this.appointmentCollection, this.moreAppointmentIndex); @@ -494,7 +533,7 @@ class _AllDayAppointmentRenderObject extends RenderBox bool isMobilePlatform; bool isExpanding; Map moreAppointmentIndex; - List<_AppointmentView> appointmentCollection; + List appointmentCollection; /// Width of the widget. double _width; @@ -577,6 +616,23 @@ class _AllDayAppointmentRenderObject extends RenderBox } } + SfLocalizations _localizations; + + SfLocalizations get localizations => _localizations; + + set localizations(SfLocalizations value) { + if (_localizations == value) { + return; + } + + _localizations = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + /// Used to check whether the widget is expandable or not. bool _isExpandable; @@ -608,11 +664,11 @@ class _AllDayAppointmentRenderObject extends RenderBox markNeedsPaint(); } - List _visibleAppointments; + List? _visibleAppointments; - List get visibleAppointments => _visibleAppointments; + List? get visibleAppointments => _visibleAppointments; - set visibleAppointments(List value) { + set visibleAppointments(List? value) { if (_visibleAppointments == value) { return; } @@ -672,46 +728,58 @@ class _AllDayAppointmentRenderObject extends RenderBox markNeedsPaint(); } - ValueNotifier _allDayHoverPosition; + ValueNotifier _allDayHoverPosition; - ValueNotifier get allDayHoverPosition => _allDayHoverPosition; + ValueNotifier get allDayHoverPosition => _allDayHoverPosition; - set allDayHoverPosition(ValueNotifier value) { + set allDayHoverPosition(ValueNotifier value) { if (_allDayHoverPosition == value) { return; } - _allDayHoverPosition?.removeListener(markNeedsPaint); + _allDayHoverPosition.removeListener(markNeedsPaint); _allDayHoverPosition = value; - _allDayHoverPosition?.addListener(markNeedsPaint); + _allDayHoverPosition.addListener(markNeedsPaint); } - ValueNotifier<_SelectionDetails> _selectionNotifier; + ValueNotifier _selectionNotifier; - ValueNotifier<_SelectionDetails> get selectionNotifier => _selectionNotifier; + ValueNotifier get selectionNotifier => _selectionNotifier; - set selectionNotifier(ValueNotifier<_SelectionDetails> value) { + set selectionNotifier(ValueNotifier value) { if (_selectionNotifier == value) { return; } - _selectionNotifier?.removeListener(markNeedsPaint); + _selectionNotifier.removeListener(markNeedsPaint); _selectionNotifier = value; - _selectionNotifier?.addListener(markNeedsPaint); + _selectionNotifier.addListener(markNeedsPaint); } - Paint _rectPainter; - TextPainter _textPainter; - TextPainter _expanderTextPainter; - BoxPainter _boxPainter; + /// Caches [SemanticsNode]s created during [assembleSemanticsNode] so they + /// can be re-used when [assembleSemanticsNode] is called again. This ensures + /// stable ids for the [SemanticsNode]s of children across + /// [assembleSemanticsNode] invocations. + /// Ref: assembleSemanticsNode method in RenderParagraph class + /// (https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/paragraph.dart) + List? _cacheNodes; + Paint _rectPainter = Paint(); + TextPainter _textPainter = TextPainter( + textDirection: TextDirection.ltr, + maxLines: 1, + textAlign: TextAlign.left, + textWidthBasis: TextWidthBasis.longestLine); + TextPainter _expanderTextPainter = TextPainter( + textDirection: TextDirection.ltr, textAlign: TextAlign.left, maxLines: 1); + late BoxPainter _boxPainter; bool _isHoveringAppointment = false; int _maxPosition = 0; double _cellWidth = 0; @override void setupParentData(RenderObject child) { - if (child.parentData is! _CalendarParentData) { - child.parentData = _CalendarParentData(); + if (child.parentData is! CalendarParentData) { + child.parentData = CalendarParentData(); } } @@ -719,15 +787,20 @@ class _AllDayAppointmentRenderObject extends RenderBox @override void attach(PipelineOwner owner) { super.attach(owner); - _allDayHoverPosition?.addListener(markNeedsPaint); - _selectionNotifier?.addListener(markNeedsPaint); + _allDayHoverPosition.addListener(markNeedsPaint); + _selectionNotifier.addListener(markNeedsPaint); + } + + @override + bool hitTestSelf(Offset position) { + return true; } /// detach will called when the render object removed from view. @override void detach() { - _allDayHoverPosition?.removeListener(markNeedsPaint); - _selectionNotifier?.removeListener(markNeedsPaint); + _allDayHoverPosition.removeListener(markNeedsPaint); + _selectionNotifier.removeListener(markNeedsPaint); super.detach(); } @@ -736,45 +809,45 @@ class _AllDayAppointmentRenderObject extends RenderBox final Size widgetSize = constraints.biggest; size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, widgetSize.height.isInfinite ? height : widgetSize.height); - RenderBox child = firstChild; - final int position = allDayPainterHeight ~/ _kAllDayAppointmentHeight; + RenderBox? child = firstChild; + final int position = allDayPainterHeight ~/ kAllDayAppointmentHeight; final double maximumBottomPosition = - allDayPainterHeight - _kAllDayAppointmentHeight; + allDayPainterHeight - kAllDayAppointmentHeight; for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView _appointmentView = appointmentCollection[i]; - if (_appointmentView.appointment == null || + final AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.appointment == null || child == null || - _appointmentView.appointmentRect == null) { + appointmentView.appointmentRect == null) { continue; } + final RRect appointmentRect = appointmentView.appointmentRect!; if (!isRTL && - _appointmentView.appointmentRect.left < timeLabelWidth - 1 || - _appointmentView.appointmentRect.right > size.width + 1 || - (_appointmentView.appointmentRect.bottom > maximumBottomPosition && - _appointmentView.maxPositions > position)) { + (appointmentRect.left < timeLabelWidth - 1 || + appointmentRect.right > size.width + 1 || + (appointmentRect.bottom > maximumBottomPosition && + appointmentView.maxPositions > position))) { child = childAfter(child); continue; } else if (isRTL && - _appointmentView.appointmentRect.right > - size.width - timeLabelWidth + 1 || - _appointmentView.appointmentRect.left < 0 || - (_appointmentView.appointmentRect.bottom > maximumBottomPosition && - _appointmentView.maxPositions > position)) { + (appointmentRect.right > size.width - timeLabelWidth + 1 || + appointmentRect.left < 0 || + (appointmentRect.bottom > maximumBottomPosition && + appointmentView.maxPositions > position))) { child = childAfter(child); continue; } child.layout(constraints.copyWith( - minHeight: _appointmentView.appointmentRect.height, - maxHeight: _appointmentView.appointmentRect.height, - minWidth: _appointmentView.appointmentRect.width, - maxWidth: _appointmentView.appointmentRect.width)); + minHeight: appointmentRect.height, + maxHeight: appointmentRect.height, + minWidth: appointmentRect.width, + maxWidth: appointmentRect.width)); child = childAfter(child); } _cellWidth = (size.width - timeLabelWidth) / visibleDates.length; - final double appointmentHeight = _kAllDayAppointmentHeight - 1; + final double appointmentHeight = kAllDayAppointmentHeight - 1; final double maxAppointmentWidth = _cellWidth - calendar.cellEndPadding; final List keys = moreAppointmentIndex.keys.toList(); for (int i = 0; i < keys.length; i++) { @@ -793,16 +866,14 @@ class _AllDayAppointmentRenderObject extends RenderBox @override void paint(PaintingContext context, Offset offset) { - _rectPainter ??= Paint(); - final Canvas canvas = context.canvas; + _textPainter.textScaleFactor = _textScaleFactor; if (view == CalendarView.day) { - _rectPainter ??= Paint(); _rectPainter.strokeWidth = 0.5; _rectPainter.color = calendar.cellBorderColor ?? calendarTheme.cellBorderColor; //// Decrease the x position by 0.5 because draw the end point of the view /// draws half of the line to current view and hides another half. - canvas.drawLine( + context.canvas.drawLine( Offset( isRTL ? size.width - timeLabelWidth + 0.5 : timeLabelWidth - 0.5, 0), @@ -820,7 +891,7 @@ class _AllDayAppointmentRenderObject extends RenderBox if (appointmentCollection.isNotEmpty) { _maxPosition = appointmentCollection .reduce( - (_AppointmentView currentAppView, _AppointmentView nextAppView) => + (AppointmentView currentAppView, AppointmentView nextAppView) => currentAppView.maxPositions > nextAppView.maxPositions ? currentAppView : nextAppView) @@ -832,29 +903,31 @@ class _AllDayAppointmentRenderObject extends RenderBox } _isHoveringAppointment = false; - final int position = allDayPainterHeight ~/ _kAllDayAppointmentHeight; - RenderBox child = firstChild; + final int position = allDayPainterHeight ~/ kAllDayAppointmentHeight; + RenderBox? child = firstChild; for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; + final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.canReuse || appointmentView.appointmentRect == null || appointmentView.appointment == null) { continue; } - final RRect rect = appointmentView.appointmentRect; - if (!isRTL && rect.left < timeLabelWidth - 1 || - rect.right > size.width + 1 || - (rect.bottom > allDayPainterHeight - _kAllDayAppointmentHeight && - appointmentView.maxPositions > position)) { + final RRect rect = appointmentView.appointmentRect!; + if (!isRTL && + (rect.left < timeLabelWidth - 1 || + rect.right > size.width + 1 || + (rect.bottom > allDayPainterHeight - kAllDayAppointmentHeight && + appointmentView.maxPositions > position))) { if (child != null) { child = childAfter(child); } continue; - } else if (isRTL && rect.right > size.width - timeLabelWidth + 1 || - rect.left < 0 || - (rect.bottom > allDayPainterHeight - _kAllDayAppointmentHeight && - appointmentView.maxPositions > position)) { + } else if (isRTL && + (rect.right > size.width - timeLabelWidth + 1 || + rect.left < 0 || + (rect.bottom > allDayPainterHeight - kAllDayAppointmentHeight && + appointmentView.maxPositions > position))) { if (child != null) { child = childAfter(child); } @@ -865,20 +938,14 @@ class _AllDayAppointmentRenderObject extends RenderBox child.paint(context, Offset(rect.left, rect.top)); child = childAfter(child); } else { - final Appointment appointment = appointmentView.appointment; + final CalendarAppointment appointment = appointmentView.appointment!; _rectPainter.color = appointment.color; - canvas.drawRRect(rect, _rectPainter); + context.canvas.drawRRect(rect, _rectPainter); final TextSpan span = TextSpan( text: _getAllDayAppointmentText(appointment), style: calendar.appointmentTextStyle, ); - _textPainter = _textPainter ?? - TextPainter( - textDirection: TextDirection.ltr, - maxLines: 1, - textAlign: TextAlign.left, - textScaleFactor: textScaleFactor, - textWidthBasis: TextWidthBasis.longestLine); + _textPainter.text = span; _textPainter.layout( minWidth: 0, @@ -889,7 +956,7 @@ class _AllDayAppointmentRenderObject extends RenderBox } final bool canAddSpanIcon = - _canAddSpanIcon(visibleDates, appointment, view); + AppointmentHelper.canAddSpanIcon(visibleDates, appointment, view); bool canAddForwardIcon = false; bool canAddBackwardIcon = false; @@ -898,20 +965,21 @@ class _AllDayAppointmentRenderObject extends RenderBox : rect.left + textPadding; if (canAddSpanIcon) { - final DateTime appStartTime = appointment._exactStartTime; - final DateTime appEndTime = appointment._exactEndTime; - final DateTime viewStartDate = _convertToStartTime(visibleDates[0]); - final DateTime viewEndDate = - _convertToEndTime(visibleDates[visibleDates.length - 1]); - double iconSize = _getTextSize( + final DateTime appStartTime = appointment.exactStartTime; + final DateTime appEndTime = appointment.exactEndTime; + final DateTime viewStartDate = + AppointmentHelper.convertToStartTime(visibleDates[0]); + final DateTime viewEndDate = AppointmentHelper.convertToEndTime( + visibleDates[visibleDates.length - 1]); + double? iconSize = _getTextSize( rect, - (calendar.appointmentTextStyle.fontSize * + (calendar.appointmentTextStyle.fontSize! * _textPainter.textScaleFactor)); - if (_canAddForwardSpanIcon( + if (AppointmentHelper.canAddForwardSpanIcon( appStartTime, appEndTime, viewStartDate, viewEndDate)) { canAddForwardIcon = true; iconSize = null; - } else if (_canAddBackwardSpanIcon( + } else if (AppointmentHelper.canAddBackwardSpanIcon( appStartTime, appEndTime, viewStartDate, viewEndDate)) { canAddBackwardIcon = true; } else { @@ -929,52 +997,50 @@ class _AllDayAppointmentRenderObject extends RenderBox } _textPainter.paint( - canvas, + context.canvas, Offset( xPosition, rect.top + (rect.height - _textPainter.height) / 2)); if (appointment.recurrenceRule != null && - appointment.recurrenceRule.isNotEmpty) { - _addRecurrenceIcon(canvas, rect, textPadding); + appointment.recurrenceRule!.isNotEmpty) { + _addRecurrenceIcon(context.canvas, rect, textPadding); } if (canAddSpanIcon) { if (canAddForwardIcon && canAddBackwardIcon) { _addForwardSpanIconForAllDay( - canvas, rect, textPadding, isMobilePlatform); + context.canvas, rect, textPadding, isMobilePlatform); _addBackwardSpanIconForAllDay( - canvas, rect, textPadding, isMobilePlatform); + context.canvas, rect, textPadding, isMobilePlatform); } else if (canAddBackwardIcon) { _addBackwardSpanIconForAllDay( - canvas, rect, textPadding, isMobilePlatform); + context.canvas, rect, textPadding, isMobilePlatform); } else { _addForwardSpanIconForAllDay( - canvas, rect, textPadding, isMobilePlatform); + context.canvas, rect, textPadding, isMobilePlatform); } } } - if (allDayHoverPosition != null) { - _addMouseHoveringForAppointment(canvas, rect); - } + _addMouseHoveringForAppointment(context.canvas, rect); if (selectionNotifier.value != null && - selectionNotifier.value.appointmentView != null && - selectionNotifier.value.appointmentView.appointment != null && - selectionNotifier.value.appointmentView.appointment == + selectionNotifier.value!.appointmentView != null && + selectionNotifier.value!.appointmentView!.appointment != null && + selectionNotifier.value!.appointmentView!.appointment == appointmentView.appointment) { - _addSelectionForAppointment(canvas, appointmentView); + _addSelectionForAppointment(context.canvas, appointmentView); } } if (selectionNotifier.value != null && - selectionNotifier.value.selectedDate != null) { - _addSelectionForAllDayPanel(canvas, size, cellEndPadding); + selectionNotifier.value!.selectedDate != null) { + _addSelectionForAllDayPanel(context.canvas, size, cellEndPadding); } if (isExpandable && _maxPosition > position && !isExpanding) { if (child != null) { final double endYPosition = - allDayPainterHeight - _kAllDayAppointmentHeight; + allDayPainterHeight - kAllDayAppointmentHeight; final List keys = moreAppointmentIndex.keys.toList(); for (final int index in keys) { if (child == null) { @@ -989,16 +1055,16 @@ class _AllDayAppointmentRenderObject extends RenderBox child = childAfter(child); } } else { - _addExpanderText(canvas, position, textPadding); + _addExpanderText(context.canvas, position, textPadding); } } if (isExpandable) { - _addExpandOrCollapseIcon(canvas, size, position); + _addExpandOrCollapseIcon(context.canvas, size, position); } - if (allDayHoverPosition != null && !_isHoveringAppointment) { - _addMouseHoveringForAllDayPanel(canvas, size); + if (!_isHoveringAppointment) { + _addMouseHoveringForAllDayPanel(context.canvas, size); } } @@ -1006,12 +1072,13 @@ class _AllDayAppointmentRenderObject extends RenderBox /// other views we just display the subject of the appointment and for day /// view we display the current date, and total dates of the spanning /// appointment. - String _getAllDayAppointmentText(Appointment appointment) { - if (view != CalendarView.day || !appointment._isSpanned) { + String _getAllDayAppointmentText(CalendarAppointment appointment) { + if (view != CalendarView.day || !appointment.isSpanned) { return appointment.subject; } - return _getSpanAppointmentText(appointment, visibleDates[0]); + return AppointmentHelper.getSpanAppointmentText( + appointment, visibleDates[0], _localizations); } double _getTextSize(RRect rect, double textSize) { @@ -1025,16 +1092,17 @@ class _AllDayAppointmentRenderObject extends RenderBox void _addForwardSpanIconForAllDay( Canvas canvas, RRect rect, double textPadding, bool isMobilePlatform) { final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize); - final TextSpan icon = _getSpanIcon( - calendar.appointmentTextStyle.color, textSize, isRTL ? false : true); + _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); + final TextSpan icon = AppointmentHelper.getSpanIcon( + calendar.appointmentTextStyle.color!, textSize, isRTL ? false : true); final double leftPadding = isMobilePlatform ? 1 : 2; _textPainter.text = icon; _textPainter.layout( minWidth: 0, maxWidth: rect.width - textPadding >= 0 ? rect.width - textPadding : 0); - final double yPosition = _getYPositionForSpanIcon(icon, _textPainter, rect); + final double yPosition = + AppointmentHelper.getYPositionForSpanIcon(icon, _textPainter, rect); canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromLTRB(isRTL ? rect.left : rect.right - textSize, rect.top, @@ -1055,16 +1123,17 @@ class _AllDayAppointmentRenderObject extends RenderBox void _addBackwardSpanIconForAllDay( Canvas canvas, RRect rect, double textPadding, bool isMobilePlatform) { final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize); - final TextSpan icon = _getSpanIcon( - calendar.appointmentTextStyle.color, textSize, isRTL ? true : false); + _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); + final TextSpan icon = AppointmentHelper.getSpanIcon( + calendar.appointmentTextStyle.color!, textSize, isRTL ? true : false); final double leftPadding = isMobilePlatform ? 1 : 2; _textPainter.text = icon; _textPainter.layout( minWidth: 0, maxWidth: rect.width - textPadding >= 0 ? rect.width - textPadding : 0); - final double yPosition = _getYPositionForSpanIcon(icon, _textPainter, rect); + final double yPosition = + AppointmentHelper.getYPositionForSpanIcon(icon, _textPainter, rect); canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromLTRB(isRTL ? rect.right - textSize : rect.left, rect.top, @@ -1083,17 +1152,9 @@ class _AllDayAppointmentRenderObject extends RenderBox } void _addExpanderText(Canvas canvas, int position, double textPadding) { - TextStyle textStyle = calendar.viewHeaderStyle.dayTextStyle; - textStyle ??= calendarTheme.viewHeaderDayTextStyle; - _textPainter = _textPainter ?? - TextPainter( - textDirection: TextDirection.ltr, - maxLines: 1, - textAlign: TextAlign.left, - textScaleFactor: textScaleFactor, - textWidthBasis: TextWidthBasis.longestLine); - - final double endYPosition = allDayPainterHeight - _kAllDayAppointmentHeight; + final TextStyle textStyle = calendar.viewHeaderStyle.dayTextStyle ?? + calendarTheme.viewHeaderDayTextStyle; + final double endYPosition = allDayPainterHeight - kAllDayAppointmentHeight; final List keys = moreAppointmentIndex.keys.toList(); for (final int index in keys) { final TextSpan span = TextSpan( @@ -1115,7 +1176,7 @@ class _AllDayAppointmentRenderObject extends RenderBox textPadding : timeLabelWidth + (index * _cellWidth) + textPadding, endYPosition + - ((_kAllDayAppointmentHeight - _textPainter.height) / 2))); + ((kAllDayAppointmentHeight - _textPainter.height) / 2))); } } @@ -1126,23 +1187,17 @@ class _AllDayAppointmentRenderObject extends RenderBox final TextSpan icon = TextSpan( text: String.fromCharCode(iconCodePoint), style: TextStyle( - color: calendar.viewHeaderStyle != null && - calendar.viewHeaderStyle.dayTextStyle != null && - calendar.viewHeaderStyle.dayTextStyle.color != null - ? calendar.viewHeaderStyle.dayTextStyle.color + color: calendar.viewHeaderStyle.dayTextStyle != null && + calendar.viewHeaderStyle.dayTextStyle!.color != null + ? calendar.viewHeaderStyle.dayTextStyle!.color : calendarTheme.viewHeaderDayTextStyle.color, - fontSize: calendar.viewHeaderStyle != null && - calendar.viewHeaderStyle.dayTextStyle != null && - calendar.viewHeaderStyle.dayTextStyle.fontSize != null - ? calendar.viewHeaderStyle.dayTextStyle.fontSize * 2 - : _kAllDayAppointmentHeight + 5, + fontSize: calendar.viewHeaderStyle.dayTextStyle != null && + calendar.viewHeaderStyle.dayTextStyle!.fontSize != null + ? calendar.viewHeaderStyle.dayTextStyle!.fontSize! * 2 + : kAllDayAppointmentHeight + 5, fontFamily: 'MaterialIcons', )); - _expanderTextPainter ??= TextPainter( - textDirection: TextDirection.ltr, - textAlign: TextAlign.left, - textScaleFactor: textScaleFactor, - maxLines: 1); + _expanderTextPainter.textScaleFactor = textScaleFactor; _expanderTextPainter.text = icon; _expanderTextPainter.layout(minWidth: 0, maxWidth: timeLabelWidth); _expanderTextPainter.paint( @@ -1153,16 +1208,16 @@ class _AllDayAppointmentRenderObject extends RenderBox ((timeLabelWidth - _expanderTextPainter.width) / 2) : (timeLabelWidth - _expanderTextPainter.width) / 2, allDayPainterHeight - - _kAllDayAppointmentHeight + - (_kAllDayAppointmentHeight - _expanderTextPainter.height) / 2)); + kAllDayAppointmentHeight + + (kAllDayAppointmentHeight - _expanderTextPainter.height) / 2)); } void _addMouseHoveringForAllDayPanel(Canvas canvas, Size size) { - if (allDayHoverPosition == null || allDayHoverPosition.value == null) { + if (allDayHoverPosition.value == null) { return; } final int rowIndex = - (allDayHoverPosition.value.dx - (isRTL ? 0 : timeLabelWidth)) ~/ + (allDayHoverPosition.value!.dx - (isRTL ? 0 : timeLabelWidth)) ~/ _cellWidth; final double leftPosition = (rowIndex * _cellWidth) + (isRTL ? 0 : timeLabelWidth); @@ -1173,25 +1228,24 @@ class _AllDayAppointmentRenderObject extends RenderBox void _addSelectionForAllDayPanel( Canvas canvas, Size size, double appointmentEndPadding) { - final int index = - _getIndex(visibleDates, selectionNotifier.value.selectedDate); - Decoration selectionDecoration = calendar.selectionDecoration; + final int index = DateTimeHelper.getIndex( + visibleDates, selectionNotifier.value!.selectedDate!); + Decoration? selectionDecoration = calendar.selectionDecoration; /// Set the default selection decoration background color with opacity /// value based on theme brightness when selected date hold all day /// appointment. for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; + final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.position == 0 && appointmentView.startIndex <= index && appointmentView.endIndex > index) { selectionDecoration ??= BoxDecoration( - color: calendarTheme.brightness == null || - calendarTheme.brightness == Brightness.light + color: calendarTheme.brightness == Brightness.light ? Colors.white.withOpacity(0.3) : Colors.black.withOpacity(0.4), border: - Border.all(color: calendarTheme.selectionBorderColor, width: 2), + Border.all(color: calendarTheme.selectionBorderColor!, width: 2), borderRadius: const BorderRadius.all(Radius.circular(2)), shape: BoxShape.rectangle, ); @@ -1204,7 +1258,7 @@ class _AllDayAppointmentRenderObject extends RenderBox /// when selected date does not hold all day appointment. selectionDecoration ??= BoxDecoration( color: Colors.transparent, - border: Border.all(color: calendarTheme.selectionBorderColor, width: 2), + border: Border.all(color: calendarTheme.selectionBorderColor!, width: 2), borderRadius: const BorderRadius.all(Radius.circular(2)), shape: BoxShape.rectangle, ); @@ -1214,13 +1268,13 @@ class _AllDayAppointmentRenderObject extends RenderBox if (isRTL) { xValue = size.width - xValue - _cellWidth; rect = Rect.fromLTRB(xValue + appointmentEndPadding, 0, - xValue + _cellWidth, _kAllDayAppointmentHeight - 1); + xValue + _cellWidth, kAllDayAppointmentHeight - 1); } else { rect = Rect.fromLTRB( xValue, 0, xValue + _cellWidth - appointmentEndPadding, - _kAllDayAppointmentHeight - 1); + kAllDayAppointmentHeight - 1); } _boxPainter = @@ -1234,22 +1288,22 @@ class _AllDayAppointmentRenderObject extends RenderBox /// Used to pass the argument of create box painter and it is called when /// decoration have asynchronous data like image. void _updateSelectionDecorationPainter() { - selectionNotifier.value = _SelectionDetails( - selectionNotifier.value.appointmentView, - selectionNotifier.value.selectedDate); + selectionNotifier.value = SelectionDetails( + selectionNotifier.value!.appointmentView, + selectionNotifier.value!.selectedDate); } void _addSelectionForAppointment( - Canvas canvas, _AppointmentView appointmentView) { - Decoration selectionDecoration = calendar.selectionDecoration; + Canvas canvas, AppointmentView appointmentView) { + Decoration? selectionDecoration = calendar.selectionDecoration; selectionDecoration ??= BoxDecoration( color: Colors.transparent, - border: Border.all(color: calendarTheme.selectionBorderColor, width: 2), + border: Border.all(color: calendarTheme.selectionBorderColor!, width: 2), borderRadius: const BorderRadius.all(Radius.circular(1)), shape: BoxShape.rectangle, ); - Rect rect = appointmentView.appointmentRect.outerRect; + Rect rect = appointmentView.appointmentRect!.outerRect; rect = Rect.fromLTRB(rect.left, rect.top, rect.right, rect.bottom); _boxPainter = selectionDecoration.createBoxPainter(_updateSelectionDecorationPainter); @@ -1258,16 +1312,15 @@ class _AllDayAppointmentRenderObject extends RenderBox } void _addMouseHoveringForAppointment(Canvas canvas, RRect rect) { - _rectPainter ??= Paint(); - if (allDayHoverPosition == null || allDayHoverPosition.value == null) { + if (allDayHoverPosition.value == null) { return; } - if (rect.left < allDayHoverPosition.value.dx && - rect.right > allDayHoverPosition.value.dx && - rect.top < allDayHoverPosition.value.dy && - rect.bottom > allDayHoverPosition.value.dy) { - _rectPainter.color = calendarTheme.selectionBorderColor.withOpacity(0.4); + if (rect.left < allDayHoverPosition.value!.dx && + rect.right > allDayHoverPosition.value!.dx && + rect.top < allDayHoverPosition.value!.dy && + rect.bottom > allDayHoverPosition.value!.dy) { + _rectPainter.color = calendarTheme.selectionBorderColor!.withOpacity(0.4); _rectPainter.strokeWidth = 2; _rectPainter.style = PaintingStyle.stroke; canvas.drawRect(rect.outerRect, _rectPainter); @@ -1278,9 +1331,9 @@ class _AllDayAppointmentRenderObject extends RenderBox void _addRecurrenceIcon(Canvas canvas, RRect rect, double textPadding) { final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize); - final TextSpan icon = - _getRecurrenceIcon(calendar.appointmentTextStyle.color, textSize); + _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); + final TextSpan icon = AppointmentHelper.getRecurrenceIcon( + calendar.appointmentTextStyle.color!, textSize); _textPainter.text = icon; _textPainter.layout( minWidth: 0, @@ -1309,18 +1362,19 @@ class _AllDayAppointmentRenderObject extends RenderBox SemanticsConfiguration config, Iterable children, ) { + _cacheNodes ??= []; final List semantics = _getSemanticsBuilder(size); final List semanticsNodes = []; for (int i = 0; i < semantics.length; i++) { final CustomPainterSemantics currentSemantics = semantics[i]; - final SemanticsNode newChild = SemanticsNode( - key: currentSemantics.key, - ); + final SemanticsNode newChild = _cacheNodes!.isNotEmpty + ? _cacheNodes!.removeAt(0) + : SemanticsNode(key: currentSemantics.key); final SemanticsProperties properties = currentSemantics.properties; final SemanticsConfiguration config = SemanticsConfiguration(); if (properties.label != null) { - config.label = properties.label; + config.label = properties.label!; } if (properties.textDirection != null) { config.textDirection = properties.textDirection; @@ -1343,10 +1397,16 @@ class _AllDayAppointmentRenderObject extends RenderBox final List finalChildren = []; finalChildren.addAll(semanticsNodes); finalChildren.addAll(children); - + _cacheNodes = semanticsNodes; super.assembleSemanticsNode(node, config, finalChildren); } + @override + void clearSemantics() { + super.clearSemantics(); + _cacheNodes = null; + } + @override void visitChildrenForSemantics(RenderObjectVisitor visitor) { return; @@ -1355,30 +1415,29 @@ class _AllDayAppointmentRenderObject extends RenderBox List _getSemanticsBuilder(Size size) { final List semanticsBuilder = []; - if (appointmentCollection == null || appointmentCollection.isEmpty) { + if (appointmentCollection.isEmpty) { return semanticsBuilder; } - final int position = allDayPainterHeight ~/ _kAllDayAppointmentHeight; - final double bottom = allDayPainterHeight - _kAllDayAppointmentHeight; + final int position = allDayPainterHeight ~/ kAllDayAppointmentHeight; + final double bottom = allDayPainterHeight - kAllDayAppointmentHeight; if (isExpandable) { final double left = isRTL ? size.width - timeLabelWidth : 0; - final double top = allDayPainterHeight - _kAllDayAppointmentHeight; + final double top = allDayPainterHeight - kAllDayAppointmentHeight; semanticsBuilder.add(CustomPainterSemantics( rect: Rect.fromLTWH(left, top, isRTL ? size.width : timeLabelWidth, _expanderTextPainter.height), properties: SemanticsProperties( - label: - _maxPosition <= allDayPainterHeight ~/ _kAllDayAppointmentHeight - ? 'Collapse all day section' - : 'Expand all day section', + label: _maxPosition <= allDayPainterHeight ~/ kAllDayAppointmentHeight + ? 'Collapse all day section' + : 'Expand all day section', textDirection: TextDirection.ltr, ), )); } if (isExpandable && - _maxPosition > (allDayPainterHeight ~/ _kAllDayAppointmentHeight) && + _maxPosition > (allDayPainterHeight ~/ kAllDayAppointmentHeight) && !isExpanding) { final List keys = moreAppointmentIndex.keys.toList(); for (final int index in keys) { @@ -1389,7 +1448,7 @@ class _AllDayAppointmentRenderObject extends RenderBox : timeLabelWidth + (index * _cellWidth), bottom, _cellWidth, - _kAllDayAppointmentHeight), + kAllDayAppointmentHeight), properties: SemanticsProperties( label: '+' + moreAppointmentIndex[index].toString(), textDirection: TextDirection.ltr, @@ -1399,19 +1458,20 @@ class _AllDayAppointmentRenderObject extends RenderBox } for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView view = appointmentCollection[i]; + final AppointmentView view = appointmentCollection[i]; if (view.appointment == null || view.appointmentRect == null || (view.appointmentRect != null && - view.appointmentRect.bottom > bottom && + view.appointmentRect!.bottom > bottom && view.maxPositions > position)) { continue; } semanticsBuilder.add(CustomPainterSemantics( - rect: view.appointmentRect?.outerRect, + rect: view.appointmentRect!.outerRect, properties: SemanticsProperties( - label: _getAppointmentText(view.appointment), + label: + CalendarViewHelper.getAppointmentSemanticsText(view.appointment!), textDirection: TextDirection.ltr, ), )); diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/appointment_layout.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/appointment_layout.dart index 8ca836f68..c3cb7de5b 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/appointment_layout.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/appointment_layout.dart @@ -1,31 +1,27 @@ -part of calendar; - -class _AppointmentView { - bool canReuse; - int startIndex = -1; - int endIndex = -1; - Appointment appointment; - int position = -1; - int maxPositions = -1; - bool isSpanned = false; - RRect appointmentRect; - int resourceIndex = -1; -} - -TextPainter _updateTextPainter(TextSpan span, TextPainter textPainter, - bool isRTL, double textScaleFactor) { - textPainter = textPainter ?? TextPainter(); - textPainter.text = span; - textPainter.maxLines = 1; - textPainter.textDirection = TextDirection.ltr; - textPainter.textAlign = isRTL ? TextAlign.right : TextAlign.left; - textPainter.textWidthBasis = TextWidthBasis.longestLine; - textPainter.textScaleFactor = textScaleFactor; - return textPainter; -} - -class _AppointmentLayout extends StatefulWidget { - _AppointmentLayout( +import 'dart:math' as math; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../appointment_engine/appointment_helper.dart'; +import '../appointment_engine/month_appointment_helper.dart'; +import '../common/calendar_view_helper.dart'; +import '../common/date_time_engine.dart'; +import '../common/enums.dart'; +import '../common/event_args.dart'; +import '../resource_view/calendar_resource.dart'; +import '../settings/time_slot_view_settings.dart'; +import '../sfcalendar.dart'; + +/// Used to holds the appointment views in calendar widgets. +class AppointmentLayout extends StatefulWidget { + /// Constructor to create the appointment layout that holds the appointment + /// views in calendar widget. + AppointmentLayout( this.calendar, this.view, this.visibleDates, @@ -40,70 +36,110 @@ class _AppointmentLayout extends StatefulWidget { this.isMobilePlatform, this.width, this.height, + this.localizations, this.updateCalendarState, - {Key key}) + {Key? key}) : super(key: key); + /// Holds the calendar instance used the get the properties of calendar. final SfCalendar calendar; + + /// Defines the current calendar view of the calendar widget. final CalendarView view; + + /// Holds the visible dates of the appointments view. final List visibleDates; + + /// Defines the time interval height of calendar and it used on day, week, + /// workweek and timeline calendar views. final double timeIntervalHeight; - final _UpdateCalendarState updateCalendarState; + + /// Used to get the calendar state details. + final UpdateCalendarState updateCalendarState; + + /// Defines the direction of the calendar widget is RTL or not. final bool isRTL; + + /// Holds the theme data of the calendar widget. final SfCalendarThemeData calendarTheme; - final ValueNotifier appointmentHoverPosition; - final List resourceCollection; - final double resourceItemHeight; + + /// Used to hold the appointment layout hovering position. + final ValueNotifier appointmentHoverPosition; + + /// Holds the resource details of the calendar widget. + final List? resourceCollection; + + /// Defines the resource item height of the calendar widget. + final double? resourceItemHeight; + + /// Defines the scale factor of the calendar widget. final double textScaleFactor; + + /// Defines the current platform is mobile platform or not. final bool isMobilePlatform; + + /// Defines the width of the appointment layout widget. final double width; + + /// Defines the height of the appointment layout widget. final double height; - final ValueNotifier> visibleAppointments; + + /// Holds the localization data of the calendar widget. + final SfLocalizations localizations; + + /// Holds the visible appointment collection of the calendar widget. + final ValueNotifier?> visibleAppointments; + + /// Return the appointment view based on x and y position. + AppointmentView? getAppointmentViewOnPoint(double x, double y) { + // ignore: avoid_as + final GlobalKey appointmentLayoutKey = key as GlobalKey; + final _AppointmentLayoutState state = + // ignore: avoid_as + appointmentLayoutKey.currentState as _AppointmentLayoutState; + return state._getAppointmentViewOnPoint(x, y); + } @override _AppointmentLayoutState createState() => _AppointmentLayoutState(); } -class _AppointmentLayoutState extends State<_AppointmentLayout> { +class _AppointmentLayoutState extends State { /// It holds the appointment views for the visible appointments. - List<_AppointmentView> _appointmentCollection; + List _appointmentCollection = []; /// It holds the appointment list based on its visible index value. - Map> _indexAppointments; + Map> _indexAppointments = + >{}; /// It holds the more appointment index appointment counts based on its index. - Map _monthAppointmentCountViews; + Map _monthAppointmentCountViews = {}; - /// It holds the children of the widget, it holds null or empty when + /// It holds the children of the widget, it holds empty when /// appointment builder is null. - List _children; + List _children = []; - final _UpdateCalendarStateDetails _updateCalendarStateDetails = - _UpdateCalendarStateDetails(); - TextPainter _textPainter; + final UpdateCalendarStateDetails _updateCalendarStateDetails = + UpdateCalendarStateDetails(); + TextPainter _textPainter = TextPainter(); @override void initState() { - _indexAppointments = >{}; - _appointmentCollection = <_AppointmentView>[]; - _monthAppointmentCountViews = {}; widget.updateCalendarState(_updateCalendarStateDetails); - _textPainter = TextPainter(); - _children = []; _updateAppointmentDetails(); - widget.visibleAppointments?.addListener(_updateVisibleAppointment); + widget.visibleAppointments.addListener(_updateVisibleAppointment); super.initState(); } @override - void didUpdateWidget(_AppointmentLayout oldWidget) { + void didUpdateWidget(AppointmentLayout oldWidget) { bool isAppointmentDetailsUpdated = false; if (widget.visibleDates != oldWidget.visibleDates || widget.timeIntervalHeight != oldWidget.timeIntervalHeight || widget.calendar != oldWidget.calendar || widget.width != oldWidget.width || widget.height != oldWidget.height || - (_isTimelineView(widget.view) && + (CalendarViewHelper.isTimelineView(widget.view) && (widget.resourceCollection != oldWidget.resourceCollection || widget.resourceItemHeight != oldWidget.resourceItemHeight))) { isAppointmentDetailsUpdated = true; @@ -111,9 +147,10 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { } if (widget.visibleAppointments != oldWidget.visibleAppointments) { - oldWidget.visibleAppointments?.removeListener(_updateVisibleAppointment); - widget.visibleAppointments?.addListener(_updateVisibleAppointment); - if (!_isCollectionEqual(widget.visibleAppointments.value, + oldWidget.visibleAppointments.removeListener(_updateVisibleAppointment); + widget.visibleAppointments.addListener(_updateVisibleAppointment); + if (!CalendarViewHelper.isCollectionEqual( + widget.visibleAppointments.value, oldWidget.visibleAppointments.value) && !isAppointmentDetailsUpdated) { _updateAppointmentDetails(); @@ -125,20 +162,16 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { @override void dispose() { - widget.visibleAppointments?.removeListener(_updateVisibleAppointment); + widget.visibleAppointments.removeListener(_updateVisibleAppointment); super.dispose(); } @override Widget build(BuildContext context) { - _children ??= []; - /// Create the widgets when appointment builder is not null. - if (_children.isEmpty && - _appointmentCollection != null && - widget.calendar.appointmentBuilder != null) { + if (_children.isEmpty && widget.calendar.appointmentBuilder != null) { for (int i = 0; i < _appointmentCollection.length; i++) { - final _AppointmentView appointmentView = _appointmentCollection[i]; + final AppointmentView appointmentView = _appointmentCollection[i]; /// Check the appointment view have appointment, if not then the /// appointment view is not valid or it will be used for reusing view. @@ -148,61 +181,59 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { } final DateTime date = DateTime( - appointmentView.appointment._actualStartTime.year, - appointmentView.appointment._actualStartTime.month, - appointmentView.appointment._actualStartTime.day); - final Widget child = widget.calendar.appointmentBuilder( + appointmentView.appointment!.actualStartTime.year, + appointmentView.appointment!.actualStartTime.month, + appointmentView.appointment!.actualStartTime.day); + final Widget child = widget.calendar.appointmentBuilder!( context, CalendarAppointmentDetails( - date: date, - bounds: Rect.fromLTWH( - appointmentView.appointmentRect.left, - appointmentView.appointmentRect.top, - appointmentView.appointmentRect.width, - appointmentView.appointmentRect.height), - appointments: List.unmodifiable([ - appointmentView.appointment._data ?? - appointmentView.appointment + date, + List.unmodifiable([ + appointmentView.appointment!.data ?? + appointmentView.appointment! ]), + Rect.fromLTWH( + appointmentView.appointmentRect!.left, + appointmentView.appointmentRect!.top, + appointmentView.appointmentRect!.width, + appointmentView.appointmentRect!.height), isMoreAppointmentRegion: false)); - /// Throw exception when builder return widget is null. - assert(child != null, 'Widget must not be null'); _children.add(RepaintBoundary(child: child)); } - if (_monthAppointmentCountViews != null) { + if (_monthAppointmentCountViews.isNotEmpty) { final List keys = _monthAppointmentCountViews.keys.toList(); /// Get the more appointment index(more appointment index map holds more /// appointment needed cell index and it bound) for (int i = 0; i < keys.length; i++) { final int index = keys[i]; - final List moreAppointments = []; - final List<_AppointmentView> moreAppointmentViews = - _indexAppointments[index]; + final List moreAppointments = + []; + final List moreAppointmentViews = + _indexAppointments[index]!; /// Get the appointments of the more appointment cell index from more /// appointment views. for (int j = 0; j < moreAppointmentViews.length; j++) { - final _AppointmentView currentAppointment = moreAppointmentViews[j]; - moreAppointments.add(currentAppointment.appointment); + final AppointmentView currentAppointment = moreAppointmentViews[j]; + moreAppointments.add(currentAppointment.appointment!); } final DateTime date = widget.visibleDates[index]; - final RRect moreRegionRect = _monthAppointmentCountViews[index]; - final Widget child = widget.calendar.appointmentBuilder( + final RRect moreRegionRect = _monthAppointmentCountViews[index]!; + final Widget child = widget.calendar.appointmentBuilder!( context, CalendarAppointmentDetails( - date: date, - bounds: Rect.fromLTWH(moreRegionRect.left, moreRegionRect.top, + date, + List.unmodifiable(CalendarViewHelper.getCustomAppointments( + moreAppointments)), + Rect.fromLTWH(moreRegionRect.left, moreRegionRect.top, moreRegionRect.width, moreRegionRect.height), - appointments: List.unmodifiable( - _getCustomAppointments(moreAppointments)), isMoreAppointmentRegion: true)); /// Throw exception when builder return widget is null. - assert(child != null, 'Widget must not be null'); _children.add(RepaintBoundary(child: child)); } } @@ -223,48 +254,46 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { widget.isMobilePlatform, widget.width, widget.height, + widget.localizations, _appointmentCollection, _indexAppointments, _monthAppointmentCountViews, widgets: _children); } - _AppointmentView _getAppointmentViewOnPoint(double x, double y) { - if (_appointmentCollection == null) { + AppointmentView? _getAppointmentViewOnPoint(double x, double y) { + if (_appointmentCollection.isEmpty) { return null; } - _AppointmentView selectedAppointmentView; + AppointmentView? selectedAppointmentView; for (int i = 0; i < _appointmentCollection.length; i++) { - final _AppointmentView appointmentView = _appointmentCollection[i]; + final AppointmentView appointmentView = _appointmentCollection[i]; if (appointmentView.appointment != null && appointmentView.appointmentRect != null && - appointmentView.appointmentRect.left <= x && - appointmentView.appointmentRect.right >= x && - appointmentView.appointmentRect.top <= y && - appointmentView.appointmentRect.bottom >= y) { + appointmentView.appointmentRect!.left <= x && + appointmentView.appointmentRect!.right >= x && + appointmentView.appointmentRect!.top <= y && + appointmentView.appointmentRect!.bottom >= y) { selectedAppointmentView = appointmentView; break; } } if (selectedAppointmentView == null && - _monthAppointmentCountViews != null && widget.view == CalendarView.month && widget.calendar.monthViewSettings.appointmentDisplayMode == MonthAppointmentDisplayMode.appointment) { final List keys = _monthAppointmentCountViews.keys.toList(); for (int i = 0; i < keys.length; i++) { - final RRect rect = _monthAppointmentCountViews[keys[i]]; + final RRect? rect = _monthAppointmentCountViews[keys[i]]!; if (rect != null && rect.left <= x && rect.right >= x && rect.top <= y && rect.bottom >= y) { - selectedAppointmentView = _AppointmentView() - ..appointment = Appointment() - ..appointmentRect = rect; + selectedAppointmentView = AppointmentView()..appointmentRect = rect; break; } } @@ -284,20 +313,92 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { }); } + /// Remove the appointments before the calendar time slot start date and + /// after the calendar time slot end date. + List _getValidAppointments( + List visibleAppointments) { + if (visibleAppointments.isEmpty || + widget.view == CalendarView.month || + widget.view == CalendarView.timelineMonth) { + return visibleAppointments; + } + + final List appointments = []; + final int viewStartHour = + widget.calendar.timeSlotViewSettings.startHour.toInt(); + final int viewStartMinutes = (viewStartHour * 60) + + ((widget.calendar.timeSlotViewSettings.startHour - viewStartHour) * 60) + .toInt(); + final int viewEndHour = + widget.calendar.timeSlotViewSettings.endHour.toInt(); + final int viewEndMinutes = (viewEndHour * 60) + + ((widget.calendar.timeSlotViewSettings.endHour - viewEndHour) * 60) + .toInt(); + + for (int i = 0; i < visibleAppointments.length; i++) { + final CalendarAppointment appointment = visibleAppointments[i]; + + /// Skip the span appointment because span appointment will placed + /// in between the valid hours(between time slot start and end hour). + if (!isSameDate(appointment.actualEndTime, appointment.actualStartTime)) { + /// Check the span appointment is start after time slot end hour and + /// end before time slot start hour then skip the rendering. + if (isSameDate(appointment.actualEndTime, + appointment.actualStartTime.add(Duration(days: 1)))) { + final int appointmentStartMinutes = + (appointment.actualStartTime.hour * 60) + + appointment.actualStartTime.minute; + final int appointmentEndMinutes = + (appointment.actualEndTime.hour * 60) + + appointment.actualEndTime.minute; + if (appointmentStartMinutes >= viewEndMinutes && + appointmentEndMinutes <= viewStartMinutes) { + continue; + } + } + + appointments.add(appointment); + continue; + } + final int appointmentStartMinutes = + (appointment.actualStartTime.hour * 60) + + appointment.actualStartTime.minute; + final int appointmentEndMinutes = (appointment.actualEndTime.hour * 60) + + appointment.actualEndTime.minute; + + /// Check the appointment before time slot start hour then skip the + /// appointment rendering. + if (appointmentStartMinutes < viewStartMinutes && + appointmentEndMinutes <= viewStartMinutes) { + continue; + } + + /// Check the appointment after time slot end hour then skip the + /// appointment rendering. + if (appointmentStartMinutes >= viewEndMinutes && + appointmentEndMinutes > viewEndMinutes) { + continue; + } + + appointments.add(appointment); + } + + return appointments; + } + void _updateAppointmentDetails() { _monthAppointmentCountViews = {}; - _indexAppointments = >{}; + _indexAppointments = >{}; widget.updateCalendarState(_updateCalendarStateDetails); - _appointmentCollection ??= <_AppointmentView>[]; - _resetAppointmentView(_appointmentCollection); + AppointmentHelper.resetAppointmentView(_appointmentCollection); _children.clear(); if (widget.visibleDates != - _updateCalendarStateDetails._currentViewVisibleDates) { + _updateCalendarStateDetails.currentViewVisibleDates) { return; } - final List visibleAppointments = - widget.visibleAppointments.value; + final List visibleAppointments = + _getValidAppointments(widget.visibleAppointments.value!); switch (widget.view) { case CalendarView.month: { @@ -314,18 +415,23 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { case CalendarView.timelineDay: case CalendarView.timelineWeek: case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: { _updateTimelineAppointmentDetails(visibleAppointments); } break; + case CalendarView.timelineMonth: + { + _updateTimelineMonthAppointmentDetails(visibleAppointments); + } + break; case CalendarView.schedule: return; } } - void _updateMonthAppointmentDetails(List visibleAppointments) { - final double cellWidth = widget.width / _kNumberOfDaysInWeek; + void _updateMonthAppointmentDetails( + List visibleAppointments) { + final double cellWidth = widget.width / DateTime.daysPerWeek; final double cellHeight = widget.height / widget.calendar.monthViewSettings.numberOfWeeksInView; if (widget.calendar.monthCellBuilder != null || @@ -337,26 +443,32 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { double xPosition = 0; double yPosition = 0; final int count = widget.visibleDates.length; - DateTime visibleStartDate = _convertToStartTime(widget.visibleDates[0]); - DateTime visibleEndDate = _convertToEndTime(widget.visibleDates[count - 1]); + DateTime visibleStartDate = + AppointmentHelper.convertToStartTime(widget.visibleDates[0]); + DateTime visibleEndDate = + AppointmentHelper.convertToEndTime(widget.visibleDates[count - 1]); int visibleStartIndex = 0; int visibleEndIndex = (widget.calendar.monthViewSettings.numberOfWeeksInView * - _kNumberOfDaysInWeek) - + DateTime.daysPerWeek) - 1; - final bool showTrailingLeadingDates = _isLeadingAndTrailingDatesVisible( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates); + final bool showTrailingLeadingDates = + CalendarViewHelper.isLeadingAndTrailingDatesVisible( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates); if (!showTrailingLeadingDates) { final DateTime currentMonthDate = widget.visibleDates[count ~/ 2]; - visibleStartDate = - _convertToStartTime(_getMonthStartDate(currentMonthDate)); - visibleEndDate = _convertToEndTime(_getMonthEndDate(currentMonthDate)); - visibleStartIndex = _getIndex(widget.visibleDates, visibleStartDate); - visibleEndIndex = _getIndex(widget.visibleDates, visibleEndDate); + visibleStartDate = AppointmentHelper.convertToStartTime( + AppointmentHelper.getMonthStartDate(currentMonthDate)); + visibleEndDate = AppointmentHelper.convertToEndTime( + AppointmentHelper.getMonthEndDate(currentMonthDate)); + visibleStartIndex = + DateTimeHelper.getIndex(widget.visibleDates, visibleStartDate); + visibleEndIndex = + DateTimeHelper.getIndex(widget.visibleDates, visibleEndDate); } - _updateAppointment( + MonthAppointmentHelper.updateAppointmentDetails( visibleAppointments, _appointmentCollection, widget.visibleDates, @@ -381,14 +493,14 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { final double startPosition = cellPadding + _textPainter.preferredLineHeight + todayCircleRadius; final int maximumDisplayCount = - widget.calendar.monthViewSettings.appointmentDisplayCount ?? 3; + widget.calendar.monthViewSettings.appointmentDisplayCount; final double appointmentHeight = (cellHeight - startPosition) / maximumDisplayCount; // right side padding used to add padding on appointment view right side // in month view final double cellEndPadding = widget.calendar.cellEndPadding; for (int i = 0; i < _appointmentCollection.length; i++) { - final _AppointmentView appointmentView = _appointmentCollection[i]; + final AppointmentView appointmentView = _appointmentCollection[i]; if (appointmentView.canReuse || appointmentView.appointment == null) { continue; } @@ -402,16 +514,16 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { if (widget.isRTL) { xPosition = - (6 - (appointmentView.startIndex % _kNumberOfDaysInWeek)) * + (6 - (appointmentView.startIndex % DateTime.daysPerWeek)) * cellWidth; xPosition -= appointmentWidth - cellWidth; } else { xPosition = - (appointmentView.startIndex % _kNumberOfDaysInWeek) * cellWidth; + (appointmentView.startIndex % DateTime.daysPerWeek) * cellWidth; } yPosition = - (appointmentView.startIndex ~/ _kNumberOfDaysInWeek) * cellHeight; + (appointmentView.startIndex ~/ DateTime.daysPerWeek) * cellHeight; if (appointmentView.position <= maximumDisplayCount) { yPosition = yPosition + startPosition + @@ -431,7 +543,7 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { appointmentWidth - cellEndPadding > 0 ? appointmentWidth - cellEndPadding : 0, - appointmentHeight - 1), + appointmentHeight > 1 ? appointmentHeight - 1 : 0), cornerRadius); appointmentView.appointmentRect = rect; @@ -441,9 +553,9 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { final List keys = _indexAppointments.keys.toList(); for (int i = 0; i < keys.length; i++) { final int index = keys[i]; - final int maxPosition = _indexAppointments[index] + final int maxPosition = _indexAppointments[index]! .reduce( - (_AppointmentView currentAppView, _AppointmentView nextAppView) => + (AppointmentView currentAppView, AppointmentView nextAppView) => currentAppView.maxPositions > nextAppView.maxPositions ? currentAppView : nextAppView) @@ -452,12 +564,12 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { continue; } if (widget.isRTL) { - xPosition = (6 - (index % _kNumberOfDaysInWeek)) * cellWidth; + xPosition = (6 - (index % DateTime.daysPerWeek)) * cellWidth; } else { - xPosition = (index % _kNumberOfDaysInWeek) * cellWidth; + xPosition = (index % DateTime.daysPerWeek) * cellWidth; } - yPosition = ((index ~/ _kNumberOfDaysInWeek) * cellHeight) + + yPosition = ((index ~/ DateTime.daysPerWeek) * cellHeight) + cellHeight - appointmentHeight; @@ -473,11 +585,12 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { } } - void _updateDayAppointmentDetails(List visibleAppointments) { - final double timeLabelWidth = _getTimeLabelWidth( + void _updateDayAppointmentDetails( + List visibleAppointments) { + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); final double width = widget.width - timeLabelWidth; - _setAppointmentPositionAndMaxPosition( + AppointmentHelper.setAppointmentPositionAndMaxPosition( _appointmentCollection, widget.calendar, widget.view, @@ -487,52 +600,43 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { final double cellWidth = width / widget.visibleDates.length; final double cellHeight = widget.timeIntervalHeight; double xPosition = timeLabelWidth; - double yPosition = 0; final double cellEndPadding = widget.calendar.cellEndPadding; - final int timeInterval = - _getTimeInterval(widget.calendar.timeSlotViewSettings); + final int timeInterval = CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings); + final int viewStartHour = + widget.calendar.timeSlotViewSettings.startHour.toInt(); + final double viewStartMinutes = + (widget.calendar.timeSlotViewSettings.startHour - viewStartHour) * 60; + for (int i = 0; i < _appointmentCollection.length; i++) { - final _AppointmentView appointmentView = _appointmentCollection[i]; + final AppointmentView appointmentView = _appointmentCollection[i]; if (appointmentView.canReuse || appointmentView.appointment == null) { continue; } - final Appointment appointment = appointmentView.appointment; + final CalendarAppointment appointment = appointmentView.appointment!; int column = -1; final int count = widget.visibleDates.length; - int datesCount = 0; for (int j = 0; j < count; j++) { final DateTime _date = widget.visibleDates[j]; - if (_date != null && - _date.day == appointment._actualStartTime.day && - _date.month == appointment._actualStartTime.month && - _date.year == appointment._actualStartTime.year) { - column = widget.isRTL - ? widget.visibleDates.length - 1 - datesCount - : datesCount; + if (isSameDate(_date, appointment.actualStartTime)) { + column = widget.isRTL ? widget.visibleDates.length - 1 - j : j; break; - } else if (_date != null) { - datesCount++; } } if (column == -1 || - appointment._isSpanned || + appointment.isSpanned || (appointment.endTime.difference(appointment.startTime).inDays > 0) || appointment.isAllDay) { continue; } - final int totalHours = appointment._actualStartTime.hour - - widget.calendar.timeSlotViewSettings.startHour.toInt(); - final double mins = appointment._actualStartTime.minute - - ((widget.calendar.timeSlotViewSettings.startHour - - widget.calendar.timeSlotViewSettings.startHour.toInt()) * - 60); - final int totalMins = (totalHours * 60 + mins).toInt(); - final int row = totalMins ~/ timeInterval; + final int totalHours = appointment.actualStartTime.hour - viewStartHour; + final double mins = appointment.actualStartTime.minute - viewStartMinutes; + final int totalMins = ((totalHours * 60) + mins).toInt(); final double appointmentWidth = (cellWidth - cellEndPadding) / appointmentView.maxPositions; @@ -546,29 +650,24 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { timeLabelWidth; } - yPosition = row * cellHeight; - Duration difference = - appointment._actualEndTime.difference(appointment._actualStartTime); + appointment.actualEndTime.difference(appointment.actualStartTime); final double minuteHeight = cellHeight / timeInterval; - yPosition += ((appointment._actualStartTime.hour * 60 + - appointment._actualStartTime.minute) % - timeInterval) * - minuteHeight; + double yPosition = totalMins * minuteHeight; double height = difference.inMinutes * minuteHeight; if (widget.calendar.timeSlotViewSettings.minimumAppointmentDuration != null && - widget.calendar.timeSlotViewSettings.minimumAppointmentDuration + widget.calendar.timeSlotViewSettings.minimumAppointmentDuration! .inMinutes > 0) { if (difference < - widget - .calendar.timeSlotViewSettings.minimumAppointmentDuration && + widget.calendar.timeSlotViewSettings + .minimumAppointmentDuration! && difference.inMinutes * minuteHeight < widget.calendar.timeSlotViewSettings.timeIntervalHeight) { difference = - widget.calendar.timeSlotViewSettings.minimumAppointmentDuration; + widget.calendar.timeSlotViewSettings.minimumAppointmentDuration!; height = difference.inMinutes * minuteHeight; //// Check the minimum appointment duration height does not greater than time interval height. if (height > @@ -578,36 +677,189 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { } } + if (yPosition + height <= 0) { + /// Skip the appointment rendering while the whole appointment placed + /// before calendar start time.(Eg., appointment start and end date as + /// 4 AM to 6 AM and the calendar start time is 8 AM then skip the + /// rendering). + continue; + } else if (yPosition > widget.height) { + /// Skip the appointment rendering while the whole appointment placed + /// after calendar end time.(Eg., appointment start and end date as + /// 8 PM to 9 PM and the calendar end time is 6 PM then skip the + /// rendering). + continue; + } else if (yPosition + height > widget.height) { + /// Change the height when appointment end time greater than calendar + /// time slot end time(Eg., calendar end time is 4 PM and appointment + /// end time is 6 PM then it takes more space and it hides span icon + /// when the appointment is spanned) + height = widget.height - yPosition; + } else if (yPosition < 0) { + /// Change the start position and height when appointment start time + /// before the calendar start time and appointment end time after the + /// calendar start time.(Eg., appointment start and end date as + /// 6 AM to 9 AM and the calendar start time is 8 AM then calculate the + /// new size from 8 AM to 9 AM, if we does not calculate the new size + /// then the appointment text drawn on hidden place). + height += yPosition; + yPosition = 0; + } + final Radius cornerRadius = Radius.circular((height * 0.1) > 2 ? 2 : (height * 0.1)); final RRect rect = RRect.fromRectAndRadius( - Rect.fromLTWH(xPosition, yPosition, appointmentWidth - 1, height - 1), + Rect.fromLTWH( + xPosition, + yPosition, + appointmentWidth > 1 ? appointmentWidth - 1 : 0, + height > 1 ? height - 1 : 0), + cornerRadius); + appointmentView.appointmentRect = rect; + } + } + + void _updateTimelineMonthAppointmentDetails( + List visibleAppointments) { + final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view); + + /// Filters the appointment for each resource from the visible appointment + /// collection, and assign appointment views for all the collections. + if (isResourceEnabled) { + for (int i = 0; i < widget.calendar.dataSource!.resources!.length; i++) { + final CalendarResource resource = + widget.calendar.dataSource!.resources![i]; + + /// Filters the appointment for each resource from the visible + /// appointment collection. + final List appointmentForEachResource = + visibleAppointments + .where((app) => + app.resourceIds != null && + app.resourceIds!.isNotEmpty && + app.resourceIds!.contains(resource.id)) + .toList(); + AppointmentHelper.setAppointmentPositionAndMaxPosition( + _appointmentCollection, + widget.calendar, + widget.view, + appointmentForEachResource, + false, + widget.timeIntervalHeight, + i); + } + } else { + AppointmentHelper.setAppointmentPositionAndMaxPosition( + _appointmentCollection, + widget.calendar, + widget.view, + visibleAppointments, + false, + widget.timeIntervalHeight); + } + + final double viewWidth = widget.width / widget.visibleDates.length; + final double cellWidth = widget.timeIntervalHeight; + double xPosition = 0; + double yPosition = 0; + final double cellEndPadding = widget.calendar.cellEndPadding; + final double slotHeight = + isResourceEnabled ? widget.resourceItemHeight! : widget.height; + for (int i = 0; i < _appointmentCollection.length; i++) { + final AppointmentView appointmentView = _appointmentCollection[i]; + if (appointmentView.canReuse || appointmentView.appointment == null) { + continue; + } + + final CalendarAppointment appointment = appointmentView.appointment!; + int column = -1; + + final DateTime startTime = appointment.actualStartTime; + int index = + DateTimeHelper.getVisibleDateIndex(widget.visibleDates, startTime); + if (index == -1 && startTime.isBefore(widget.visibleDates[0])) { + index = 0; + } + + column = widget.isRTL ? widget.visibleDates.length - 1 - index : index; + + /// For timeline day, week and work week view each column represents a + /// time slots for timeline month each column represent a day, and as + /// rendering wise the column here represents the day hence the `-1` + /// added in the above calculation not required for timeline month view, + /// hence to rectify this we have added +1. + if (widget.isRTL) { + column += 1; + } + + double appointmentHeight = _getTimelineAppointmentHeight( + widget.calendar.timeSlotViewSettings, widget.view); + if (appointmentHeight * appointmentView.maxPositions > slotHeight) { + appointmentHeight = slotHeight / appointmentView.maxPositions; + } + + xPosition = column * viewWidth; + yPosition = appointmentHeight * appointmentView.position; + if (isResourceEnabled && + appointment.resourceIds != null && + appointment.resourceIds!.isNotEmpty) { + /// To render the appointment on specific resource slot, we have got the + /// appointment's resource index and calculated y position based on + /// this. + yPosition += appointmentView.resourceIndex * widget.resourceItemHeight!; + } + + final DateTime endTime = appointment.actualEndTime; + final Duration difference = endTime.difference(startTime); + + /// The width for the appointment UI, calculated based on the date + /// difference between the start and end time of the appointment. + double width = (difference.inDays + 1) * cellWidth; + + /// For span appointment less than 23 hours the difference will fall + /// as 0 hence to render the appointment on the next day, added one + /// the width for next day. + if (difference.inDays == 0 && endTime.day != startTime.day) { + width += cellWidth; + } + + width = width - cellEndPadding; + final Radius cornerRadius = Radius.circular( + (appointmentHeight * 0.1) > 2 ? 2 : (appointmentHeight * 0.1)); + final RRect rect = RRect.fromRectAndRadius( + Rect.fromLTWH( + widget.isRTL ? xPosition - width : xPosition, + yPosition, + width > 0 ? width : 0, + appointmentHeight > 1 ? appointmentHeight - 1 : 0), cornerRadius); appointmentView.appointmentRect = rect; } } void _updateTimelineAppointmentDetails( - List visibleAppointments) { - final bool isResourceEnabled = - _isResourceEnabled(widget.calendar.dataSource, widget.view); + List visibleAppointments) { + final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view); /// Filters the appointment for each resource from the visible appointment /// collection, and assign appointment views for all the collections. - if (isResourceEnabled && visibleAppointments != null) { - for (int i = 0; i < widget.calendar.dataSource.resources.length; i++) { + if (isResourceEnabled) { + for (int i = 0; i < widget.calendar.dataSource!.resources!.length; i++) { final CalendarResource resource = - widget.calendar.dataSource.resources[i]; + widget.calendar.dataSource!.resources![i]; /// Filters the appointment for each resource from the visible /// appointment collection. - final List appointmentForEachResource = visibleAppointments - .where((app) => - app.resourceIds != null && - app.resourceIds.isNotEmpty && - app.resourceIds.contains(resource.id)) - .toList(); - _setAppointmentPositionAndMaxPosition( + final List appointmentForEachResource = + visibleAppointments + .where((app) => + app.resourceIds != null && + app.resourceIds!.isNotEmpty && + app.resourceIds!.contains(resource.id)) + .toList(); + AppointmentHelper.setAppointmentPositionAndMaxPosition( _appointmentCollection, widget.calendar, widget.view, @@ -617,7 +869,7 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { i); } } else { - _setAppointmentPositionAndMaxPosition( + AppointmentHelper.setAppointmentPositionAndMaxPosition( _appointmentCollection, widget.calendar, widget.view, @@ -631,215 +883,125 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { double xPosition = 0; double yPosition = 0; final int count = widget.visibleDates.length; - final int timeSlotCount = _getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view) - .toInt(); - final int timeInterval = - _getTimeInterval(widget.calendar.timeSlotViewSettings); + final int timeInterval = CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings); final double cellEndPadding = widget.calendar.cellEndPadding; + final int viewStartHour = + widget.calendar.timeSlotViewSettings.startHour.toInt(); + final double viewStartMinutes = + (widget.calendar.timeSlotViewSettings.startHour - viewStartHour) * 60; for (int i = 0; i < _appointmentCollection.length; i++) { - final _AppointmentView appointmentView = _appointmentCollection[i]; + final AppointmentView appointmentView = _appointmentCollection[i]; if (appointmentView.canReuse || appointmentView.appointment == null) { continue; } - final Appointment appointment = appointmentView.appointment; + final CalendarAppointment appointment = appointmentView.appointment!; int column = -1; - DateTime startTime = appointment._actualStartTime; - int datesCount = 0; + DateTime startTime = appointment.actualStartTime; for (int j = 0; j < count; j++) { final DateTime date = widget.visibleDates[j]; - if (date != null && - date.day == startTime.day && - date.month == startTime.month && - date.year == startTime.year) { - column = widget.isRTL - ? widget.visibleDates.length - 1 - datesCount - : datesCount; + if (isSameDate(date, startTime)) { + column = j; break; } else if (startTime.isBefore(date)) { - column = widget.isRTL - ? widget.visibleDates.length - 1 - datesCount - : datesCount; + column = j; startTime = DateTime(date.year, date.month, date.day, 0, 0, 0); break; - } else if (date != null) { - datesCount++; } } if (column == -1 && - appointment._actualStartTime.isBefore(widget.visibleDates[0])) { + appointment.actualStartTime.isBefore(widget.visibleDates[0])) { column = 0; } - /// For timeline day, week and work week view each column represents a - /// time slots for timeline month each column represent a day, and as - /// rendering wise the column here represents the day hence the `-1` - /// added in the above calculation not required for timeline month view, - /// hence to rectify this we have added +1. - if (widget.isRTL && widget.view == CalendarView.timelineMonth) { - column += 1; - } - - DateTime endTime = appointment._actualEndTime; - int endColumn = 0; - if (widget.view == CalendarView.timelineWorkWeek) { - endColumn = -1; - datesCount = 0; - for (int j = 0; j < count; j++) { - DateTime date = widget.visibleDates[j]; - if (date != null && - date.day == endTime.day && - date.month == endTime.month && - date.year == endTime.year) { - endColumn = widget.isRTL - ? widget.visibleDates.length - 1 - datesCount - : datesCount; - break; - } else if (endTime.isBefore(date)) { - endColumn = widget.isRTL - ? widget.visibleDates.length - 1 - datesCount - 1 - : datesCount - 1; - if (endColumn != -1) { - date = widget.visibleDates[endColumn]; - endTime = DateTime(date.year, date.month, date.day, 59, 59, 0); - } - break; - } else if (date != null) { - datesCount++; + DateTime endTime = appointment.actualEndTime; + int endColumn = -1; + for (int j = 0; j < count; j++) { + DateTime date = widget.visibleDates[j]; + if (isSameDate(date, endTime)) { + endColumn = j; + break; + } else if (endTime.isBefore(date)) { + endColumn = j - 1; + if (endColumn != -1) { + date = widget.visibleDates[endColumn]; + endTime = DateTime(date.year, date.month, date.day, 59, 59, 0); } + break; } + } - if (endColumn == -1 && - appointment._actualEndTime - .isAfter(widget.visibleDates[widget.visibleDates.length - 1])) { - endColumn = widget.isRTL ? 0 : widget.visibleDates.length - 1; - } + if (endColumn == -1 && + appointment.actualEndTime + .isAfter(widget.visibleDates[widget.visibleDates.length - 1])) { + endColumn = widget.visibleDates.length - 1; } if (column == -1 || endColumn == -1) { continue; } - int row = 0; - int totalHours = 0; - int totalMinutes = 0; - double minutes = 0; - if (widget.view != CalendarView.timelineMonth) { - totalHours = startTime.hour - - widget.calendar.timeSlotViewSettings.startHour.toInt(); - minutes = startTime.minute - - ((widget.calendar.timeSlotViewSettings.startHour - - widget.calendar.timeSlotViewSettings.startHour.toInt()) * - 60); - totalMinutes = (totalHours * 60 + minutes).toInt(); - row = totalMinutes ~/ timeInterval; - if (widget.isRTL) { - row = timeSlotCount - row; - } - } + int totalMinutes = (((startTime.hour - viewStartHour) * 60) + + (startTime.minute - viewStartMinutes)) + .toInt(); final double minuteHeight = cellWidth / timeInterval; double appointmentHeight = _getTimelineAppointmentHeight( widget.calendar.timeSlotViewSettings, widget.view); final double slotHeight = - isResourceEnabled ? widget.resourceItemHeight : widget.height; + isResourceEnabled ? widget.resourceItemHeight! : widget.height; if (appointmentHeight * appointmentView.maxPositions > slotHeight) { appointmentHeight = slotHeight / appointmentView.maxPositions; } - xPosition = (column * viewWidth) + (row * cellWidth); + xPosition = column * viewWidth; + double timePosition = totalMinutes * minuteHeight; + if (timePosition < 0) { + timePosition = 0; + } else if (timePosition > viewWidth) { + timePosition = viewWidth; + } + + xPosition += timePosition; yPosition = appointmentHeight * appointmentView.position; if (isResourceEnabled && appointment.resourceIds != null && - appointment.resourceIds.isNotEmpty) { + appointment.resourceIds!.isNotEmpty) { /// To render the appointment on specific resource slot, we have got the /// appointment's resource index and calculated y position based on /// this. - yPosition += appointmentView.resourceIndex * widget.resourceItemHeight; - } - if (widget.view != CalendarView.timelineMonth) { - /// Calculate the in between minute height - /// Eg., If start time as 12.07 PM and time interval as 60 minutes - /// then the height holds the value of 07 minutes height. - final double inBetweenMinuteHeight = - ((startTime.hour * 60 + startTime.minute) % timeInterval) * - minuteHeight; - if (widget.isRTL) { - /// If the view direction as RTL then we subtract the in between - /// minute height because the value used to calculate the start - /// position of the appointment. - xPosition -= inBetweenMinuteHeight; - } else { - xPosition += inBetweenMinuteHeight; - } + yPosition += appointmentView.resourceIndex * widget.resourceItemHeight!; } - double width = 0; - if (widget.view == CalendarView.timelineWorkWeek) { - totalHours = endTime.hour - - widget.calendar.timeSlotViewSettings.startHour.toInt(); - minutes = endTime.minute - - ((widget.calendar.timeSlotViewSettings.startHour - - widget.calendar.timeSlotViewSettings.startHour.toInt()) * - 60); - totalMinutes = (totalHours * 60 + minutes).toInt(); - row = totalMinutes ~/ timeInterval; - - /// Calculate the in between minute height - /// Eg., If end time as 12.07 PM and time interval as 60 minutes - /// then the height holds the value of 07 minutes height. - double inBetweenMinuteHeight = - ((endTime.hour * 60 + endTime.minute) % timeInterval) * - minuteHeight; - if (widget.isRTL) { - row = timeSlotCount - row; - - /// If the view direction as RTL then we subtract the in between - /// minute height because the value used to calculate the end - /// position of the appointment. - inBetweenMinuteHeight = -inBetweenMinuteHeight; - } - final double endXPosition = - (endColumn * viewWidth) + (row * cellWidth) + inBetweenMinuteHeight; - if (widget.isRTL) { - width = xPosition - endXPosition; - } else { - width = endXPosition - xPosition; - } - } else { - final Duration difference = endTime.difference(startTime); - if (widget.view != CalendarView.timelineMonth) { - /// The width for the appointment UI, calculated based on the minutes - /// difference between the start and end time of the appointment. - width = difference.inMinutes * minuteHeight; - } else { - /// The width for the appointment UI, calculated based on the date - /// difference between the start and end time of the appointment. - width = (difference.inDays + 1) * cellWidth; - - /// For span appointment less than 23 hours the difference will fall - /// as 0 hence to render the appointment on the next day, added one - /// the width for next day. - if (difference.inDays == 0 && endTime.day != startTime.day) { - width += cellWidth; - } - } + totalMinutes = (((endTime.hour - viewStartHour) * 60) + + (endTime.minute - viewStartMinutes)) + .toInt(); + double endXPosition = endColumn * viewWidth; + timePosition = totalMinutes * minuteHeight; + if (timePosition < 0) { + timePosition = 0; + } else if (timePosition > viewWidth) { + timePosition = viewWidth; } + endXPosition += timePosition; + double width = endXPosition - xPosition; + xPosition = widget.isRTL ? widget.width - xPosition : xPosition; + if (widget.calendar.timeSlotViewSettings.minimumAppointmentDuration != null && - widget.calendar.timeSlotViewSettings.minimumAppointmentDuration - .inMinutes > - 0 && - widget.view != CalendarView.timelineMonth) { - final double minWidth = _getAppointmentHeightFromDuration( - widget.calendar.timeSlotViewSettings.minimumAppointmentDuration, - widget.calendar, - widget.timeIntervalHeight); + widget.calendar.timeSlotViewSettings.minimumAppointmentDuration! > + appointment.actualEndTime + .difference(appointment.actualStartTime)) { + final double minWidth = + AppointmentHelper.getAppointmentHeightFromDuration( + widget.calendar.timeSlotViewSettings.minimumAppointmentDuration, + widget.calendar, + widget.timeIntervalHeight); width = width > minWidth ? width : minWidth; } @@ -847,12 +1009,29 @@ class _AppointmentLayoutState extends State<_AppointmentLayout> { final Radius cornerRadius = Radius.circular( (appointmentHeight * 0.1) > 2 ? 2 : (appointmentHeight * 0.1)); final RRect rect = RRect.fromRectAndRadius( - Rect.fromLTWH(widget.isRTL ? xPosition - width : xPosition, yPosition, - width, appointmentHeight - 1), + Rect.fromLTWH( + widget.isRTL ? xPosition - width : xPosition, + yPosition, + width > 0 ? width : 0, + appointmentHeight > 1 ? appointmentHeight - 1 : 0), cornerRadius); appointmentView.appointmentRect = rect; } } + + /// Returns the timeline appointment height based on the settings value. + double _getTimelineAppointmentHeight( + TimeSlotViewSettings settings, CalendarView view) { + if (settings.timelineAppointmentHeight != -1) { + return settings.timelineAppointmentHeight; + } + + if (view == CalendarView.timelineMonth) { + return 20; + } + + return 60; + } } class _AppointmentRenderWidget extends MultiChildRenderObjectWidget { @@ -871,10 +1050,11 @@ class _AppointmentRenderWidget extends MultiChildRenderObjectWidget { this.isMobilePlatform, this.width, this.height, + this.localizations, this.appointmentCollection, this.indexAppointments, this.monthAppointmentCountViews, - {List widgets}) + {List widgets = const []}) : super(children: widgets); final SfCalendar calendar; @@ -883,16 +1063,17 @@ class _AppointmentRenderWidget extends MultiChildRenderObjectWidget { final double timeIntervalHeight; final bool isRTL; final SfCalendarThemeData calendarTheme; - final ValueNotifier appointmentHoverPosition; - final List resourceCollection; - final double resourceItemHeight; + final ValueNotifier appointmentHoverPosition; + final List? resourceCollection; + final double? resourceItemHeight; final double textScaleFactor; final bool isMobilePlatform; final double width; final double height; - final List visibleAppointments; - final List<_AppointmentView> appointmentCollection; - final Map> indexAppointments; + final SfLocalizations localizations; + final List? visibleAppointments; + final List appointmentCollection; + final Map> indexAppointments; final Map monthAppointmentCountViews; @override @@ -912,6 +1093,7 @@ class _AppointmentRenderWidget extends MultiChildRenderObjectWidget { isMobilePlatform, width, height, + localizations, appointmentCollection, indexAppointments, monthAppointmentCountViews); @@ -935,31 +1117,14 @@ class _AppointmentRenderWidget extends MultiChildRenderObjectWidget { ..isMobilePlatform = isMobilePlatform ..width = width ..height = height + ..localizations = localizations ..appointmentCollection = appointmentCollection ..indexAppointments = indexAppointments ..monthAppointmentCountViews = monthAppointmentCountViews; } } -abstract class _CustomCalendarRenderObject extends RenderBox - with ContainerRenderObjectMixin { - @override - void setupParentData(RenderObject child) { - if (child.parentData is! _CalendarParentData) { - child.parentData = _CalendarParentData(); - } - } - - @override - void visitChildrenForSemantics(RenderObjectVisitor visitor) { - return; - } - - @protected - SemanticsBuilderCallback get semanticsBuilder; -} - -class _AppointmentRenderObject extends _CustomCalendarRenderObject { +class _AppointmentRenderObject extends CustomCalendarRenderObject { _AppointmentRenderObject( this._calendar, this._view, @@ -975,16 +1140,17 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { this.isMobilePlatform, this._width, this._height, + this._localizations, this.appointmentCollection, this.indexAppointments, this.monthAppointmentCountViews); - List _visibleAppointments; + List? _visibleAppointments; - List get visibleAppointments => _visibleAppointments; + List? get visibleAppointments => _visibleAppointments; - set visibleAppointments(List value) { - if (_isCollectionEqual(_visibleAppointments, value)) { + set visibleAppointments(List? value) { + if (CalendarViewHelper.isCollectionEqual(_visibleAppointments, value)) { return; } @@ -996,19 +1162,19 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { } } - ValueNotifier _appointmentHoverPosition; + ValueNotifier _appointmentHoverPosition; - ValueNotifier get appointmentHoverPosition => + ValueNotifier get appointmentHoverPosition => _appointmentHoverPosition; - set appointmentHoverPosition(ValueNotifier value) { + set appointmentHoverPosition(ValueNotifier value) { if (_appointmentHoverPosition == value) { return; } - _appointmentHoverPosition?.removeListener(markNeedsPaint); + _appointmentHoverPosition.removeListener(markNeedsPaint); _appointmentHoverPosition = value; - _appointmentHoverPosition?.addListener(markNeedsPaint); + _appointmentHoverPosition.addListener(markNeedsPaint); } double _timeIntervalHeight; @@ -1050,6 +1216,23 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { markNeedsLayout(); } + SfLocalizations _localizations; + + SfLocalizations get localizations => _localizations; + + set localizations(SfLocalizations value) { + if (_localizations == value) { + return; + } + + _localizations = value; + if (childCount != 0) { + return; + } + + markNeedsPaint(); + } + double _textScaleFactor; double get textScaleFactor => _textScaleFactor; @@ -1110,11 +1293,11 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { markNeedsPaint(); } - double _resourceItemHeight; + double? _resourceItemHeight; - double get resourceItemHeight => _resourceItemHeight; + double? get resourceItemHeight => _resourceItemHeight; - set resourceItemHeight(double value) { + set resourceItemHeight(double? value) { if (_resourceItemHeight == value) { return; } @@ -1123,11 +1306,11 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { markNeedsLayout(); } - List _resourceCollection; + List? _resourceCollection; - List get resourceCollection => _resourceCollection; + List? get resourceCollection => _resourceCollection; - set resourceCollection(List value) { + set resourceCollection(List? value) { if (_resourceCollection == value) { return; } @@ -1166,23 +1349,24 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { @override void attach(PipelineOwner owner) { super.attach(owner); - _appointmentHoverPosition?.addListener(markNeedsPaint); + _appointmentHoverPosition.addListener(markNeedsPaint); } /// detach will called when the render object removed from view. @override void detach() { - _appointmentHoverPosition?.removeListener(markNeedsPaint); + _appointmentHoverPosition.removeListener(markNeedsPaint); super.detach(); } bool isMobilePlatform; - List<_AppointmentView> appointmentCollection; - Map> indexAppointments; - Map monthAppointmentCountViews; + List appointmentCollection = []; + Map> indexAppointments = + >{}; + Map monthAppointmentCountViews = {}; - Paint _appointmentPainter; - TextPainter _textPainter; + Paint _appointmentPainter = Paint(); + TextPainter _textPainter = TextPainter(); @override bool get isRepaintBoundary => true; @@ -1194,21 +1378,27 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { List _getSemanticsBuilder(Size size) { final List semanticsBuilder = []; - if (appointmentCollection == null || appointmentCollection.isEmpty) { + if (appointmentCollection.isEmpty) { return semanticsBuilder; } for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; + final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.appointment == null || appointmentView.appointmentRect == null) { continue; } + final Rect rect = appointmentView.appointmentRect!.outerRect; + if (rect.height <= 0 || rect.width <= 0) { + continue; + } + semanticsBuilder.add(CustomPainterSemantics( - rect: appointmentView.appointmentRect?.outerRect, + rect: rect, properties: SemanticsProperties( - label: _getAppointmentText(appointmentView.appointment), + label: CalendarViewHelper.getAppointmentSemanticsText( + appointmentView.appointment!), textDirection: TextDirection.ltr, ), )); @@ -1222,9 +1412,14 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { final List keys = monthAppointmentCountViews.keys.toList(); for (int i = 0; i < keys.length; i++) { - final RRect moreRegionRect = monthAppointmentCountViews[keys[i]]; + final RRect moreRegionRect = monthAppointmentCountViews[keys[i]]!; + final Rect rect = moreRegionRect.outerRect; + if (rect.height <= 0 || rect.width <= 0) { + continue; + } + semanticsBuilder.add(CustomPainterSemantics( - rect: moreRegionRect.outerRect, + rect: rect, properties: SemanticsProperties( label: 'More', textDirection: TextDirection.ltr, @@ -1240,9 +1435,9 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { final Size widgetSize = constraints.biggest; size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, widgetSize.height.isInfinite ? height : widgetSize.height); - RenderBox child = firstChild; + RenderBox? child = firstChild; for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; + final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.appointment == null || child == null || appointmentView.appointmentRect == null) { @@ -1250,10 +1445,10 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { } child.layout(constraints.copyWith( - minHeight: appointmentView.appointmentRect.height, - maxHeight: appointmentView.appointmentRect.height, - minWidth: appointmentView.appointmentRect.width, - maxWidth: appointmentView.appointmentRect.width)); + minHeight: appointmentView.appointmentRect!.height, + maxHeight: appointmentView.appointmentRect!.height, + minWidth: appointmentView.appointmentRect!.width, + maxWidth: appointmentView.appointmentRect!.width)); child = childAfter(child); } @@ -1269,7 +1464,7 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { continue; } - final RRect moreRegionRect = monthAppointmentCountViews[keys[i]]; + final RRect moreRegionRect = monthAppointmentCountViews[keys[i]]!; child.layout(constraints.copyWith( minHeight: moreRegionRect.height, maxHeight: moreRegionRect.height, @@ -1281,15 +1476,13 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { @override void paint(PaintingContext context, Offset offset) { - RenderBox child = firstChild; + RenderBox? child = firstChild; final bool isNeedDefaultPaint = childCount == 0; if (isNeedDefaultPaint) { - _textPainter = _textPainter ?? TextPainter(); - _appointmentPainter = _appointmentPainter ?? Paint(); _drawCustomAppointmentView(context.canvas); } else { for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; + final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.appointment == null || child == null || appointmentView.appointmentRect == null) { @@ -1298,13 +1491,10 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { child.paint( context, - Offset(appointmentView.appointmentRect.left, - appointmentView.appointmentRect.top)); - if (appointmentHoverPosition != null) { - _appointmentPainter ??= Paint(); - _updateAppointmentHovering( - appointmentView.appointmentRect, context.canvas); - } + Offset(appointmentView.appointmentRect!.left, + appointmentView.appointmentRect!.top)); + _updateAppointmentHovering( + appointmentView.appointmentRect!, context.canvas); child = childAfter(child); } @@ -1321,12 +1511,9 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { continue; } - final RRect moreRegionRect = monthAppointmentCountViews[keys[i]]; + final RRect moreRegionRect = monthAppointmentCountViews[keys[i]]!; child.paint(context, Offset(moreRegionRect.left, moreRegionRect.top)); - if (appointmentHoverPosition != null) { - _appointmentPainter ??= Paint(); - _updateAppointmentHovering(moreRegionRect, context.canvas); - } + _updateAppointmentHovering(moreRegionRect, context.canvas); child = childAfter(child); } @@ -1335,7 +1522,6 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { void _drawCustomAppointmentView(Canvas canvas) { canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - _appointmentPainter = _appointmentPainter ?? Paint(); _appointmentPainter.isAntiAlias = true; switch (view) { case CalendarView.month: @@ -1364,7 +1550,7 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { } void _drawMonthAppointment(Canvas canvas, Size size, Paint paint) { - final double cellWidth = size.width / _kNumberOfDaysInWeek; + final double cellWidth = size.width / DateTime.daysPerWeek; final double cellHeight = size.height / calendar.monthViewSettings.numberOfWeeksInView; if (calendar.monthCellBuilder != null) { @@ -1385,40 +1571,44 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { void _drawMonthAppointmentView(Canvas canvas, Size size, double cellWidth, double cellHeight, Paint paint) { double xPosition = 0; - double yPosition = 0; final int count = visibleDates.length; - DateTime visibleStartDate = _convertToStartTime(visibleDates[0]); - DateTime visibleEndDate = _convertToEndTime(visibleDates[count - 1]); - final bool showTrailingLeadingDates = _isLeadingAndTrailingDatesVisible( - calendar.monthViewSettings.numberOfWeeksInView, - calendar.monthViewSettings.showTrailingAndLeadingDates); + DateTime visibleStartDate = + AppointmentHelper.convertToStartTime(visibleDates[0]); + DateTime visibleEndDate = + AppointmentHelper.convertToEndTime(visibleDates[count - 1]); + final bool showTrailingLeadingDates = + CalendarViewHelper.isLeadingAndTrailingDatesVisible( + calendar.monthViewSettings.numberOfWeeksInView, + calendar.monthViewSettings.showTrailingAndLeadingDates); if (!showTrailingLeadingDates) { final DateTime currentMonthDate = visibleDates[count ~/ 2]; - visibleStartDate = - _convertToStartTime(_getMonthStartDate(currentMonthDate)); - visibleEndDate = _convertToEndTime(_getMonthEndDate(currentMonthDate)); + visibleStartDate = AppointmentHelper.convertToStartTime( + AppointmentHelper.getMonthStartDate(currentMonthDate)); + visibleEndDate = AppointmentHelper.convertToEndTime( + AppointmentHelper.getMonthEndDate(currentMonthDate)); } final int maximumDisplayCount = - calendar.monthViewSettings.appointmentDisplayCount ?? 3; + calendar.monthViewSettings.appointmentDisplayCount; double textSize = -1; // right side padding used to add padding on appointment view right side // in month view final bool useMobilePlatformUI = - _isMobileLayoutUI(size.width, isMobilePlatform); + CalendarViewHelper.isMobileLayoutUI(size.width, isMobilePlatform); for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; + final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.canReuse || appointmentView.appointment == null || appointmentView.appointmentRect == null) { continue; } + final RRect appointmentRect = appointmentView.appointmentRect!; if (appointmentView.position < maximumDisplayCount || (appointmentView.position == maximumDisplayCount && appointmentView.maxPositions == maximumDisplayCount)) { - final Appointment appointment = appointmentView.appointment; - final bool canAddSpanIcon = _canAddSpanIcon( + final CalendarAppointment appointment = appointmentView.appointment!; + final bool canAddSpanIcon = AppointmentHelper.canAddSpanIcon( visibleDates, appointment, view, visibleStartDate: visibleStartDate, visibleEndDate: visibleEndDate, @@ -1432,11 +1622,11 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { if (textSize == -1) { //// left and right side padding value 2 subtracted in appointment width - double maxTextWidth = appointmentView.appointmentRect.width - 2; + double maxTextWidth = appointmentRect.width - 2; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; - for (double j = style.fontSize - 1; j > 0; j--) { + for (double j = style.fontSize! - 1; j > 0; j--) { _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); - if (_textPainter.height >= appointmentView.appointmentRect.height) { + if (_textPainter.height >= appointmentRect.height) { style = style.copyWith(fontSize: j); span = TextSpan(text: appointment.subject, style: style); _textPainter = _updateTextPainter( @@ -1454,33 +1644,28 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { _updateTextPainter(span, _textPainter, isRTL, _textScaleFactor); } - canvas.drawRRect(appointmentView.appointmentRect, paint); + canvas.drawRRect(appointmentRect, paint); final bool isRecurrenceAppointment = appointment.recurrenceRule != null && - appointment.recurrenceRule.isNotEmpty; + appointment.recurrenceRule!.isNotEmpty; /// left and right side padding value subtracted in appointment width /// Recurrence icon width also subtracted in appointment text width /// when it recurrence appointment. - final double textWidth = appointmentView.appointmentRect.width - - (isRecurrenceAppointment ? textSize : 1); + final double textWidth = + appointmentRect.width - (isRecurrenceAppointment ? textSize : 1); _textPainter.layout( minWidth: 0, maxWidth: textWidth > 0 ? textWidth : 0); - xPosition = appointmentView.appointmentRect.left; - yPosition = appointmentView.appointmentRect.top; - yPosition += ((appointmentView.appointmentRect.height - - 1 - - _textPainter.height) / - 2); + xPosition = appointmentRect.left; + final double yPosition = appointmentRect.top + + ((appointmentRect.height - _textPainter.height) / 2); if (isRTL && !canAddSpanIcon) { - xPosition += - appointmentView.appointmentRect.width - _textPainter.width - 2; + xPosition += appointmentRect.width - _textPainter.width - 2; } if (canAddSpanIcon) { - xPosition += - (appointmentView.appointmentRect.width - _textPainter.width) / 2; + xPosition += (appointmentRect.width - _textPainter.width) / 2; } _textPainter.paint(canvas, Offset(xPosition + 2, yPosition)); @@ -1491,18 +1676,17 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { size, style, textSize, - yPosition, - appointmentView.appointmentRect, - appointmentView.appointmentRect.tlRadius, + appointmentRect, + appointmentRect.tlRadius, paint, useMobilePlatformUI); } if (canAddSpanIcon) { - final int appStartIndex = - _getDateIndex(appointment._exactStartTime, visibleDates); - final int appEndIndex = - _getDateIndex(appointment._exactEndTime, visibleDates); + final int appStartIndex = MonthAppointmentHelper.getDateIndex( + appointment.exactStartTime, visibleDates); + final int appEndIndex = MonthAppointmentHelper.getDateIndex( + appointment.exactEndTime, visibleDates); if (appStartIndex == appointmentView.startIndex && appEndIndex == appointmentView.endIndex) { continue; @@ -1515,53 +1699,40 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { size, style, textSize, - appointmentView.appointmentRect, - appointmentView.appointmentRect.tlRadius, + appointmentRect, + appointmentRect.tlRadius, paint, useMobilePlatformUI); - _drawBackwardSpanIconForMonth( - canvas, - style, - textSize, - appointmentView.appointmentRect, - appointmentView.appointmentRect.tlRadius, - paint); + _drawBackwardSpanIconForMonth(canvas, style, textSize, + appointmentRect, appointmentRect.tlRadius, paint); } else if (appEndIndex != appointmentView.endIndex) { _drawForwardSpanIconForMonth( canvas, size, style, textSize, - appointmentView.appointmentRect, - appointmentView.appointmentRect.tlRadius, + appointmentRect, + appointmentRect.tlRadius, paint, useMobilePlatformUI); } else { - _drawBackwardSpanIconForMonth( - canvas, - style, - textSize, - appointmentView.appointmentRect, - appointmentView.appointmentRect.tlRadius, - paint); + _drawBackwardSpanIconForMonth(canvas, style, textSize, + appointmentRect, appointmentRect.tlRadius, paint); } } - if (appointmentHoverPosition != null) { - paint ??= Paint(); - _updateAppointmentHovering(appointmentView.appointmentRect, canvas); - } + _updateAppointmentHovering(appointmentRect, canvas); } } const double padding = 2; const double startPadding = 5; - double radius; + double? radius; final List keys = monthAppointmentCountViews.keys.toList(); for (int i = 0; i < keys.length; i++) { final int index = keys[i]; - final RRect moreRegionRect = monthAppointmentCountViews[index]; + final RRect moreRegionRect = monthAppointmentCountViews[index]!; if (radius == null) { radius = moreRegionRect.height * 0.12; if (radius > 3) { @@ -1571,7 +1742,7 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { double startXPosition = isRTL ? moreRegionRect.right - startPadding : moreRegionRect.left + startPadding; - paint.color = Colors.grey[600]; + paint.color = Colors.grey[600]!; for (int j = 0; j < 3; j++) { canvas.drawCircle( Offset(startXPosition, @@ -1585,10 +1756,7 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { } } - if (appointmentHoverPosition != null) { - paint ??= Paint(); - _updateAppointmentHovering(moreRegionRect, canvas); - } + _updateAppointmentHovering(moreRegionRect, canvas); } } @@ -1601,13 +1769,14 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { Radius cornerRadius, Paint paint, bool useMobilePlatformUI) { - final TextSpan icon = - _getSpanIcon(style.color, textSize, isRTL ? false : true); + final TextSpan icon = AppointmentHelper.getSpanIcon( + style.color!, textSize, isRTL ? false : true); _textPainter.text = icon; _textPainter.layout( minWidth: 0, maxWidth: rect.width + 1 > 0 ? rect.width + 1 : 0); - final double yPosition = _getYPositionForSpanIcon(icon, _textPainter, rect); + final double yPosition = + AppointmentHelper.getYPositionForSpanIcon(icon, _textPainter, rect); final double rightPadding = useMobilePlatformUI ? 0 : 2; final double xPosition = isRTL ? rect.left + rightPadding @@ -1623,13 +1792,14 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { void _drawBackwardSpanIconForMonth(Canvas canvas, TextStyle style, double textSize, RRect rect, Radius cornerRadius, Paint paint) { - final TextSpan icon = - _getSpanIcon(style.color, textSize, isRTL ? true : false); + final TextSpan icon = AppointmentHelper.getSpanIcon( + style.color!, textSize, isRTL ? true : false); _textPainter.text = icon; _textPainter.layout( minWidth: 0, maxWidth: rect.width + 1 > 0 ? rect.width + 1 : 0); - final double yPosition = _getYPositionForSpanIcon(icon, _textPainter, rect); + final double yPosition = + AppointmentHelper.getYPositionForSpanIcon(icon, _textPainter, rect); final double rightPadding = 2; final double xPosition = isRTL ? rect.right - textSize - rightPadding : rect.left + rightPadding; @@ -1647,16 +1817,17 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { Size size, TextStyle style, double textSize, - double yPosition, RRect rect, Radius cornerRadius, Paint paint, bool useMobilePlatformUI) { - final TextSpan icon = _getRecurrenceIcon(style.color, textSize); + final TextSpan icon = + AppointmentHelper.getRecurrenceIcon(style.color!, textSize); _textPainter.text = icon; _textPainter.layout( minWidth: 0, maxWidth: rect.width + 1 > 0 ? rect.width + 1 : 0); - yPosition = rect.top + ((rect.height - _textPainter.height) / 2); + final double yPosition = + rect.top + ((rect.height - _textPainter.height) / 2); final double rightPadding = useMobilePlatformUI ? 0 : 2; final double recurrenceStartPosition = isRTL ? rect.left + rightPadding @@ -1680,9 +1851,10 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { cellHeight * 0.2 < radius ? radius : cellHeight * 0.2; final int visibleDatesCount = visibleDates.length; final int currentMonth = visibleDates[visibleDatesCount ~/ 2].month; - final bool showTrailingLeadingDates = _isLeadingAndTrailingDatesVisible( - calendar.monthViewSettings.numberOfWeeksInView, - calendar.monthViewSettings.showTrailingAndLeadingDates); + final bool showTrailingLeadingDates = + CalendarViewHelper.isLeadingAndTrailingDatesVisible( + calendar.monthViewSettings.numberOfWeeksInView, + calendar.monthViewSettings.showTrailingAndLeadingDates); for (int i = 0; i < visibleDatesCount; i++) { final DateTime currentVisibleDate = visibleDates[i]; @@ -1691,15 +1863,19 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { continue; } - final List appointmentLists = - _getSpecificDateVisibleAppointment( - calendar, currentVisibleDate, visibleAppointments); - appointmentLists.sort((Appointment app1, Appointment app2) => - app1._actualStartTime.compareTo(app2._actualStartTime)); - appointmentLists.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1.isAllDay, app2.isAllDay)); - appointmentLists.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1._isSpanned, app2._isSpanned)); + final List appointmentLists = + _getSpecificDateVisibleAppointment(currentVisibleDate); + appointmentLists.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime)); + appointmentLists.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isAllDay, app2.isAllDay)); + appointmentLists.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isSpanned, app2.isSpanned)); final int count = appointmentLists.length <= calendar.monthViewSettings.appointmentDisplayCount ? appointmentLists.length @@ -1716,13 +1892,13 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { double startXPosition = 0; if (isRTL) { - startXPosition = (6 - (i % _kNumberOfDaysInWeek).toInt()) * cellWidth; + startXPosition = (6 - (i % DateTime.daysPerWeek).toInt()) * cellWidth; } else { - startXPosition = ((i % _kNumberOfDaysInWeek).toInt()) * cellWidth; + startXPosition = ((i % DateTime.daysPerWeek).toInt()) * cellWidth; } xPosition += startXPosition; - yPosition = (((i / _kNumberOfDaysInWeek) + 1).toInt()) * cellHeight; + yPosition = (((i / DateTime.daysPerWeek) + 1).toInt()) * cellHeight; for (int j = 0; j < count; j++) { paint.color = appointmentLists[j].color; canvas.drawCircle( @@ -1735,8 +1911,31 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { } } + /// Returns the specific date appointment collection by filtering the + /// appointments from passed visible appointment collection. + List _getSpecificDateVisibleAppointment(DateTime? date) { + final List appointmentCollection = + []; + if (date == null || visibleAppointments == null) { + return appointmentCollection; + } + + final DateTime startDate = AppointmentHelper.convertToStartTime(date); + final DateTime endDate = AppointmentHelper.convertToEndTime(date); + + for (int j = 0; j < visibleAppointments!.length; j++) { + final CalendarAppointment appointment = visibleAppointments![j]; + if (AppointmentHelper.isAppointmentWithinVisibleDateRange( + appointment, startDate, endDate)) { + appointmentCollection.add(appointment); + } + } + + return appointmentCollection; + } + void _updateAppointmentHovering(RRect rect, Canvas canvas) { - final Offset hoverPosition = appointmentHoverPosition.value; + final Offset? hoverPosition = appointmentHoverPosition.value; if (hoverPosition == null) { return; } @@ -1746,7 +1945,7 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { rect.top < hoverPosition.dy && rect.bottom > hoverPosition.dy) { _appointmentPainter.color = - calendarTheme.selectionBorderColor.withOpacity(0.4); + calendarTheme.selectionBorderColor!.withOpacity(0.4); _appointmentPainter.strokeWidth = 2; _appointmentPainter.style = PaintingStyle.stroke; canvas.drawRect(rect.outerRect, _appointmentPainter); @@ -1758,36 +1957,37 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { const int textStartPadding = 3; final bool useMobilePlatformUI = - _isMobileLayoutUI(size.width, isMobilePlatform); + CalendarViewHelper.isMobileLayoutUI(size.width, isMobilePlatform); for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; + final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.canReuse || appointmentView.appointmentRect == null || appointmentView.appointment == null) { continue; } - final Appointment appointment = appointmentView.appointment; + final CalendarAppointment appointment = appointmentView.appointment!; paint.color = appointment.color; - canvas.drawRRect(appointmentView.appointmentRect, paint); + final RRect appointmentRect = appointmentView.appointmentRect!; + canvas.drawRRect(appointmentRect, paint); - double xPosition = appointmentView.appointmentRect.left; - double yPosition = appointmentView.appointmentRect.top; + double xPosition = appointmentRect.left; + double yPosition = appointmentRect.top; final bool canAddSpanIcon = - _canAddSpanIcon(visibleDates, appointment, view); + AppointmentHelper.canAddSpanIcon(visibleDates, appointment, view); bool canAddForwardIcon = false; if (canAddSpanIcon) { - if (_isSameTimeSlot( - appointment._exactStartTime, appointment._actualStartTime) && - !_isSameTimeSlot( - appointment._exactEndTime, appointment._actualEndTime)) { + if (CalendarViewHelper.isSameTimeSlot( + appointment.exactStartTime, appointment.actualStartTime) && + !CalendarViewHelper.isSameTimeSlot( + appointment.exactEndTime, appointment.actualEndTime)) { canAddForwardIcon = true; - } else if (!_isSameTimeSlot( - appointment._exactStartTime, appointment._actualStartTime) && - _isSameTimeSlot( - appointment._exactEndTime, appointment._actualEndTime)) { - yPosition += _getTextSize(appointmentView.appointmentRect, - (calendar.appointmentTextStyle.fontSize * textScaleFactor)); + } else if (!CalendarViewHelper.isSameTimeSlot( + appointment.exactStartTime, appointment.actualStartTime) && + CalendarViewHelper.isSameTimeSlot( + appointment.exactEndTime, appointment.actualEndTime)) { + yPosition += _getTextSize(appointmentRect, + (calendar.appointmentTextStyle.fontSize! * textScaleFactor)); } } @@ -1799,13 +1999,11 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { _textPainter = _updateTextPainter(span, _textPainter, isRTL, _textScaleFactor); - final double totalHeight = - appointmentView.appointmentRect.height - textStartPadding; + final double totalHeight = appointmentRect.height - textStartPadding; _updatePainterMaxLines(totalHeight); //// left and right side padding value 2 subtracted in appointment width - double maxTextWidth = - appointmentView.appointmentRect.width - textStartPadding; + double maxTextWidth = appointmentRect.width - textStartPadding; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); @@ -1816,66 +2014,54 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { /// return second lines width. /// We are using the minIntrinsicWidth to restrict the text rendering /// when the appointment view bound does not hold single letter. - final double textWidth = - appointmentView.appointmentRect.width - textStartPadding; + final double textWidth = appointmentRect.width - textStartPadding; if (textWidth < _textPainter.minIntrinsicWidth && textWidth < _textPainter.width && textWidth < (calendar.appointmentTextStyle.fontSize ?? 15) * textScaleFactor) { - if (appointmentHoverPosition != null) { - paint ??= Paint(); - _updateAppointmentHovering(appointmentView.appointmentRect, canvas); - } + _updateAppointmentHovering(appointmentRect, canvas); continue; } if ((_textPainter.maxLines == 1 || _textPainter.maxLines == null) && _textPainter.height > totalHeight) { - if (appointmentHoverPosition != null) { - paint ??= Paint(); - _updateAppointmentHovering(appointmentView.appointmentRect, canvas); - } - + _updateAppointmentHovering(appointmentRect, canvas); continue; } if (isRTL) { - xPosition += appointmentView.appointmentRect.width - - textStartPadding - - _textPainter.width; + xPosition += + appointmentRect.width - textStartPadding - _textPainter.width; } _textPainter.paint(canvas, Offset(xPosition + textStartPadding, yPosition + textStartPadding)); if (appointment.recurrenceRule != null && - appointment.recurrenceRule.isNotEmpty) { + appointment.recurrenceRule!.isNotEmpty) { _addRecurrenceIconForDay( canvas, size, - appointmentView.appointmentRect, - appointmentView.appointmentRect.width, + appointmentRect, + appointmentRect.width, textStartPadding, paint, - appointmentView.appointmentRect.tlRadius, + appointmentRect.tlRadius, useMobilePlatformUI); } if (canAddSpanIcon) { if (canAddForwardIcon) { - _addForwardSpanIconForDay(canvas, appointmentView.appointmentRect, - size, appointmentView.appointmentRect.tlRadius, paint); + _addForwardSpanIconForDay( + canvas, appointmentRect, size, appointmentRect.tlRadius, paint); } else { - _addBackwardSpanIconForDay(canvas, appointmentView.appointmentRect, - size, appointmentView.appointmentRect.tlRadius, paint); + _addBackwardSpanIconForDay( + canvas, appointmentRect, size, appointmentRect.tlRadius, paint); } } - if (appointmentHoverPosition != null) { - paint ??= Paint(); - _updateAppointmentHovering(appointmentView.appointmentRect, canvas); - } + _updateAppointmentHovering(appointmentRect, canvas); } } @@ -1884,9 +2070,9 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { canvas.save(); const double bottomPadding = 2; final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize); - final TextSpan icon = - _getSpanIcon(calendar.appointmentTextStyle.color, textSize, false); + _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); + final TextSpan icon = AppointmentHelper.getSpanIcon( + calendar.appointmentTextStyle.color!, textSize, false); _textPainter = _updateTextPainter(icon, _textPainter, isRTL, _textScaleFactor); _textPainter.layout(minWidth: 0, maxWidth: rect.width); @@ -1914,7 +2100,7 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { /// hence to rectify this tha value 1.5 used, and tested with multiple /// device. final double iconStartPosition = - (_textPainter.height - (icon.style.fontSize * textScaleFactor)) / 1.5; + (_textPainter.height - (icon.style!.fontSize! * textScaleFactor)) / 1.5; return rect.left + (rect.width - _textPainter.height) / 2 + _textPainter.height + @@ -1926,9 +2112,9 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { canvas.save(); const double bottomPadding = 2; final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize); - final TextSpan icon = - _getSpanIcon(calendar.appointmentTextStyle.color, textSize, true); + _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); + final TextSpan icon = AppointmentHelper.getSpanIcon( + calendar.appointmentTextStyle.color!, textSize, true); _textPainter = _updateTextPainter(icon, _textPainter, isRTL, _textScaleFactor); _textPainter.layout(minWidth: 0, maxWidth: rect.width); @@ -1972,13 +2158,13 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { bool useMobilePlatformUI) { final double xPadding = useMobilePlatformUI ? 1 : 2; const double bottomPadding = 2; - double textSize = calendar.appointmentTextStyle.fontSize; + double textSize = calendar.appointmentTextStyle.fontSize!; if (rect.width < textSize || rect.height < textSize) { textSize = rect.width > rect.height ? rect.height : rect.width; } - final TextSpan icon = - _getRecurrenceIcon(calendar.appointmentTextStyle.color, textSize); + final TextSpan icon = AppointmentHelper.getRecurrenceIcon( + calendar.appointmentTextStyle.color!, textSize); _textPainter.text = icon; double maxTextWidth = appointmentWidth - textPadding - 2; maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; @@ -2003,44 +2189,43 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { void _drawTimelineAppointments(Canvas canvas, Size size, Paint paint) { const int textStartPadding = 3; final bool useMobilePlatformUI = - _isMobileLayoutUI(size.width, isMobilePlatform); + CalendarViewHelper.isMobileLayoutUI(size.width, isMobilePlatform); for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; + final AppointmentView appointmentView = appointmentCollection[i]; if (appointmentView.canReuse || appointmentView.appointmentRect == null || appointmentView.appointment == null) { continue; } - final Appointment appointment = appointmentView.appointment; + final CalendarAppointment appointment = appointmentView.appointment!; paint.color = appointment.color; - canvas.drawRRect(appointmentView.appointmentRect, paint); + final RRect appointmentRect = appointmentView.appointmentRect!; + canvas.drawRRect(appointmentRect, paint); final bool canAddSpanIcon = - _canAddSpanIcon(visibleDates, appointment, view); + AppointmentHelper.canAddSpanIcon(visibleDates, appointment, view); bool canAddForwardIcon = false; bool canAddBackwardIcon = false; - double xPosition = isRTL - ? appointmentView.appointmentRect.right - : appointmentView.appointmentRect.left; - double maxWidth = - appointmentView.appointmentRect.width - textStartPadding; + double xPosition = isRTL ? appointmentRect.right : appointmentRect.left; + double maxWidth = appointmentRect.width - textStartPadding; maxWidth = maxWidth > 0 ? maxWidth : 0; if (canAddSpanIcon) { - final DateTime appStartTime = appointment._exactStartTime; - final DateTime appEndTime = appointment._exactEndTime; - final DateTime viewStartDate = _convertToStartTime(visibleDates[0]); - final DateTime viewEndDate = - _convertToEndTime(visibleDates[visibleDates.length - 1]); - double iconSize = _getTextSize(appointmentView.appointmentRect, - (calendar.appointmentTextStyle.fontSize * textScaleFactor)) + + final DateTime appStartTime = appointment.exactStartTime; + final DateTime appEndTime = appointment.exactEndTime; + final DateTime viewStartDate = + AppointmentHelper.convertToStartTime(visibleDates[0]); + final DateTime viewEndDate = AppointmentHelper.convertToEndTime( + visibleDates[visibleDates.length - 1]); + double? iconSize = _getTextSize(appointmentRect, + (calendar.appointmentTextStyle.fontSize! * textScaleFactor)) + textStartPadding; - if (_canAddForwardSpanIcon( + if (AppointmentHelper.canAddForwardSpanIcon( appStartTime, appEndTime, viewStartDate, viewEndDate)) { canAddForwardIcon = true; iconSize = null; - } else if (_canAddBackwardSpanIcon( + } else if (AppointmentHelper.canAddBackwardSpanIcon( appStartTime, appEndTime, viewStartDate, viewEndDate)) { canAddBackwardIcon = true; } else { @@ -2064,8 +2249,7 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { _textPainter = _updateTextPainter(span, _textPainter, isRTL, _textScaleFactor); - final double totalHeight = - appointmentView.appointmentRect.height - textStartPadding - 2; + final double totalHeight = appointmentRect.height - textStartPadding - 2; _updatePainterMaxLines(totalHeight); /// In RTL, when the text wraps into multiple line the tine width is @@ -2081,10 +2265,7 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { _textPainter.layout(minWidth: 0, maxWidth: maxWidth); if ((_textPainter.maxLines == null || _textPainter.maxLines == 1) && _textPainter.height > totalHeight) { - if (appointmentHoverPosition != null) { - paint ??= Paint(); - _updateAppointmentHovering(appointmentView.appointmentRect, canvas); - } + _updateAppointmentHovering(appointmentRect, canvas); continue; } @@ -2099,62 +2280,29 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { _textPainter.paint( canvas, Offset(xPosition + textStartPadding, - appointmentView.appointmentRect.top + textStartPadding)); + appointmentRect.top + textStartPadding)); if (appointment.recurrenceRule != null && - appointment.recurrenceRule.isNotEmpty) { - _addRecurrenceIconForTimeline( - canvas, - size, - appointmentView.appointmentRect, - maxWidth, - appointmentView.appointmentRect.tlRadius, - paint, - useMobilePlatformUI); + appointment.recurrenceRule!.isNotEmpty) { + _addRecurrenceIconForTimeline(canvas, size, appointmentRect, maxWidth, + appointmentRect.tlRadius, paint, useMobilePlatformUI); } if (canAddSpanIcon) { if (canAddForwardIcon && canAddBackwardIcon) { - _addForwardSpanIconForTimeline( - canvas, - size, - appointmentView.appointmentRect, - maxWidth, - appointmentView.appointmentRect.tlRadius, - paint, - isMobilePlatform); - _addBackwardSpanIconForTimeline( - canvas, - size, - appointmentView.appointmentRect, - maxWidth, - appointmentView.appointmentRect.tlRadius, - paint, - isMobilePlatform); + _addForwardSpanIconForTimeline(canvas, size, appointmentRect, + maxWidth, appointmentRect.tlRadius, paint, isMobilePlatform); + _addBackwardSpanIconForTimeline(canvas, size, appointmentRect, + maxWidth, appointmentRect.tlRadius, paint, isMobilePlatform); } else if (canAddForwardIcon) { - _addForwardSpanIconForTimeline( - canvas, - size, - appointmentView.appointmentRect, - maxWidth, - appointmentView.appointmentRect.tlRadius, - paint, - isMobilePlatform); + _addForwardSpanIconForTimeline(canvas, size, appointmentRect, + maxWidth, appointmentRect.tlRadius, paint, isMobilePlatform); } else { - _addBackwardSpanIconForTimeline( - canvas, - size, - appointmentView.appointmentRect, - maxWidth, - appointmentView.appointmentRect.tlRadius, - paint, - isMobilePlatform); + _addBackwardSpanIconForTimeline(canvas, size, appointmentRect, + maxWidth, appointmentRect.tlRadius, paint, isMobilePlatform); } } - if (appointmentHoverPosition != null) { - paint ??= Paint(); - _updateAppointmentHovering(appointmentView.appointmentRect, canvas); - } + _updateAppointmentHovering(appointmentRect, canvas); } } @@ -2163,12 +2311,13 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { /// for timeline day view we display the current date, and total dates of the /// spanning appointment. String _getTimelineAppointmentText( - Appointment appointment, bool canAddSpanIcon) { + CalendarAppointment appointment, bool canAddSpanIcon) { if (view != CalendarView.timelineDay || !canAddSpanIcon) { return appointment.subject; } - return _getSpanAppointmentText(appointment, visibleDates[0]); + return AppointmentHelper.getSpanAppointmentText( + appointment, visibleDates[0], _localizations); } double _getTextSize(RRect rect, double textSize) { @@ -2187,7 +2336,8 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { /// hence to rectify this tha value 2 used, and tested with multiple /// device. final double iconStartPosition = - (_textPainter.height - (icon.style.fontSize * textScaleFactor) / 2) / 2; + (_textPainter.height - (icon.style!.fontSize! * textScaleFactor) / 2) / + 2; return rect.top - iconStartPosition + (isMobilePlatform ? 1 : xPadding); } @@ -2201,10 +2351,10 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { bool isMobilePlatform) { final double xPadding = 2; final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize); + _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); - final TextSpan icon = _getSpanIcon( - calendar.appointmentTextStyle.color, textSize, isRTL ? false : true); + final TextSpan icon = AppointmentHelper.getSpanIcon( + calendar.appointmentTextStyle.color!, textSize, isRTL ? false : true); _textPainter = _updateTextPainter(icon, _textPainter, isRTL, _textScaleFactor); _textPainter.layout(minWidth: 0, maxWidth: maxWidth); @@ -2233,10 +2383,10 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { bool isMobilePlatform) { final double xPadding = 2; final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize); + _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); - final TextSpan icon = _getSpanIcon( - calendar.appointmentTextStyle.color, textSize, isRTL ? true : false); + final TextSpan icon = AppointmentHelper.getSpanIcon( + calendar.appointmentTextStyle.color!, textSize, isRTL ? true : false); _textPainter = _updateTextPainter(icon, _textPainter, isRTL, _textScaleFactor); _textPainter.layout(minWidth: 0, maxWidth: maxWidth); @@ -2266,10 +2416,10 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { final double xPadding = useMobilePlatformUI ? 1 : 2; const double bottomPadding = 2; final double textSize = - _getTextSize(rect, calendar.appointmentTextStyle.fontSize); + _getTextSize(rect, calendar.appointmentTextStyle.fontSize!); - final TextSpan icon = - _getRecurrenceIcon(calendar.appointmentTextStyle.color, textSize); + final TextSpan icon = AppointmentHelper.getRecurrenceIcon( + calendar.appointmentTextStyle.color!, textSize); _textPainter.text = icon; _textPainter.layout(minWidth: 0, maxWidth: maxWidth); canvas.drawRRect( @@ -2287,3 +2437,14 @@ class _AppointmentRenderObject extends _CustomCalendarRenderObject { rect.bottom - bottomPadding - textSize)); } } + +TextPainter _updateTextPainter(TextSpan span, TextPainter textPainter, + bool isRTL, double textScaleFactor) { + textPainter.text = span; + textPainter.maxLines = 1; + textPainter.textDirection = TextDirection.ltr; + textPainter.textAlign = isRTL ? TextAlign.right : TextAlign.left; + textPainter.textWidthBasis = TextWidthBasis.longestLine; + textPainter.textScaleFactor = textScaleFactor; + return textPainter; +} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/calendar_controller.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/calendar_controller.dart index 47515437c..fd4e52e84 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/calendar_controller.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/calendar_controller.dart @@ -1,19 +1,23 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import 'calendar_view_helper.dart'; +import 'enums.dart'; /// Signature for callback that reports that the [CalendarController] properties /// changed. typedef CalendarValueChangedCallback = void Function(String); /// Notifier used to notify the when the objects properties changed. -class CalendarValueChangedNotifier { - List _listeners; +class CalendarValueChangedNotifier with Diagnosticable { + List? _listeners; /// Calls the listener every time the controller's property changed. /// /// Listeners can be removed with [removePropertyChangedListener]. void addPropertyChangedListener(CalendarValueChangedCallback listener) { _listeners ??= []; - _listeners.add(listener); + _listeners!.add(listener); } /// remove the listener used for notify the data source changes. @@ -29,7 +33,7 @@ class CalendarValueChangedNotifier { return; } - _listeners.remove(listener); + _listeners!.remove(listener); } /// Call all the registered listeners. @@ -49,10 +53,8 @@ class CalendarValueChangedNotifier { return; } - for (final CalendarValueChangedCallback listener in _listeners) { - if (listener != null) { - listener(property); - } + for (final CalendarValueChangedCallback listener in _listeners!) { + listener(property); } } @@ -125,12 +127,12 @@ class CalendarValueChangedNotifier { ///} /// ``` class CalendarController extends CalendarValueChangedNotifier { - DateTime _selectedDate; - DateTime _displayDate; - CalendarView _view; + DateTime? _selectedDate; + DateTime? _displayDate; + CalendarView? _view; /// The selected date in the [SfCalendar]. - DateTime get selectedDate => _selectedDate; + DateTime? get selectedDate => _selectedDate; /// Selects the given date programmatically in the [SfCalendar] by /// checking that the date falls in between the minimum and maximum date range @@ -163,8 +165,8 @@ class CalendarController extends CalendarValueChangedNotifier { /// } ///} /// ``` - set selectedDate(DateTime date) { - if (_isSameTimeSlot(_selectedDate, date)) { + set selectedDate(DateTime? date) { + if (CalendarViewHelper.isSameTimeSlot(_selectedDate, date)) { return; } @@ -182,7 +184,7 @@ class CalendarController extends CalendarValueChangedNotifier { /// If the [view] set as [CalendarView.month] and the /// [MonthViewSettings.numberOfWeeksInView] property set with value greater /// than 4, this will return the first date of the current visible month. - DateTime get displayDate => _displayDate; + DateTime? get displayDate => _displayDate; /// Navigates to the given date programmatically without any animation in the /// [SfCalendar] by checking that the date falls in between the @@ -216,8 +218,8 @@ class CalendarController extends CalendarValueChangedNotifier { /// } ///} /// ``` - set displayDate(DateTime date) { - if (isSameDate(_displayDate, date)) { + set displayDate(DateTime? date) { + if (date == null || isSameDate(_displayDate, date)) { return; } @@ -226,7 +228,7 @@ class CalendarController extends CalendarValueChangedNotifier { } /// The displayed view of the [SfCalendar]. - CalendarView get view => _view; + CalendarView? get view => _view; /// Change the calendar view programmatically in the [SfCalendar]. /// @@ -254,8 +256,8 @@ class CalendarController extends CalendarValueChangedNotifier { /// } ///} /// ``` - set view(CalendarView value) { - if (_view == value) { + set view(CalendarView? value) { + if (value == null || _view == value) { return; } @@ -263,8 +265,6 @@ class CalendarController extends CalendarValueChangedNotifier { notifyPropertyChangedListeners('calendarView'); } - VoidCallback _forward; - /// Moves to the next view programmatically with animation by checking that /// the next view dates falls between the minimum and maximum date range. /// @@ -313,15 +313,7 @@ class CalendarController extends CalendarValueChangedNotifier { /// } ///} /// ``` - void forward() { - if (_forward == null) { - return; - } - - _forward(); - } - - VoidCallback _backward; + VoidCallback? forward; /// Moves to the previous view programmatically with animation by checking /// that the previous view dates falls between the minimum and maximum date @@ -372,11 +364,13 @@ class CalendarController extends CalendarValueChangedNotifier { /// } ///} /// ``` - void backward() { - if (_backward == null) { - return; - } + VoidCallback? backward; - _backward(); + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('selectedDate', selectedDate)); + properties.add(DiagnosticsProperty('displayDate', displayDate)); + properties.add(EnumProperty('view', view)); } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/calendar_view_helper.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/calendar_view_helper.dart index 74761a5fb..4221a02fb 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/calendar_view_helper.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/calendar_view_helper.dart @@ -1,677 +1,1052 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../../../calendar.dart'; +import '../appointment_engine/calendar_datasource.dart'; +import '../resource_view/calendar_resource.dart'; +import '../settings/month_view_settings.dart'; +import '../settings/resource_view_settings.dart'; +import '../settings/schedule_view_settings.dart'; +import '../settings/time_region.dart'; +import '../settings/time_slot_view_settings.dart'; +import '../sfcalendar.dart'; +import 'enums.dart'; +import 'event_args.dart'; + +/// All day appointment height +const double kAllDayAppointmentHeight = 20; + +/// Signature for callback that used to get and update the calendar +/// state details. +typedef UpdateCalendarState = void Function( + UpdateCalendarStateDetails _updateCalendarStateDetails); + +//// Extra small devices (phones, 600px and down) +//// @media only screen and (max-width: 600px) {...} +//// +//// Small devices (portrait tablets and large phones, 600px and up) +//// @media only screen and (min-width: 600px) {...} +//// +//// Medium devices (landscape tablets, 768px and up) +//// media only screen and (min-width: 768px) {...} +//// +//// Large devices (laptops/desktops, 992px and up) +//// media only screen and (min-width: 992px) {...} +//// +//// Extra large devices (large laptops and desktops, 1200px and up) +//// media only screen and (min-width: 1200px) {...} +//// Default width to render the mobile UI in web, if the device width exceeds +//// the given width agenda view will render the web UI. +const double _kMobileViewWidth = 767; + +/// Holds the static helper methods used for calendar views rendering +/// in calendar. +class CalendarViewHelper { + /// Return the current context direction is RTL or not. + static bool isRTLLayout(BuildContext context) { + final TextDirection direction = Directionality.of(context); + return direction == TextDirection.rtl; + } -bool _isRTLLayout(BuildContext context) { - final TextDirection direction = Directionality.of(context); - return direction != null && direction == TextDirection.rtl; -} + /// Determine the current platform needs mobile platform UI. + /// The [_kMobileViewWidth] value is a breakpoint for mobile platform. + static bool isMobileLayoutUI(double width, bool isMobileLayout) { + return isMobileLayout || width <= _kMobileViewWidth; + } -/// Determine the current platform needs mobile platform UI. -/// The [_kMobileViewWidth] value is a breakpoint for mobile platform. -bool _isMobileLayoutUI(double width, bool isMobileLayout) { - return isMobileLayout || width <= _kMobileViewWidth; -} + /// Determine the current platform is mobile platform(android or iOS). + static bool isMobileLayout(TargetPlatform platform) { + if (kIsWeb) { + return false; + } -/// Determine the current platform is mobile platform(android or iOS). -bool _isMobileLayout(TargetPlatform platform) { - if (kIsWeb) { - return false; + return platform == TargetPlatform.android || platform == TargetPlatform.iOS; } - return platform == TargetPlatform.android || platform == TargetPlatform.iOS; -} + /// Check the list is empty or not. + static bool isEmptyList(List? value) { + if (value == null || value.isEmpty) { + return true; + } -/// Check the list is empty or not. -bool _isEmptyList(List value) { - if (value == null || value.isEmpty) { - return true; + return false; } - return false; -} + /// Check the date as current month date when the month leading and trailing + /// dates not shown and its row count as 6. + static bool isCurrentMonthDate(int weekRowCount, + bool showLeadingTrailingDates, int currentMonth, DateTime date) { + if (isLeadingAndTrailingDatesVisible( + weekRowCount, showLeadingTrailingDates)) { + return true; + } -/// Check the date as current month date when the month leading and trailing -/// dates not shown and its row count as 6. -bool _isCurrentMonthDate(int weekRowCount, bool showLeadingTrailingDates, - int currentMonth, DateTime date) { - if (_isLeadingAndTrailingDatesVisible( - weekRowCount, showLeadingTrailingDates)) { - return true; - } + if (date.month == currentMonth) { + return true; + } - if (date.month == currentMonth) { - return true; + return false; } - return false; -} + /// Check the leading and trailing dates visible or not. + static bool isLeadingAndTrailingDatesVisible( + int weekRowCount, bool showLeadingTrailingDates) { + return weekRowCount != 6 || showLeadingTrailingDates; + } -Size _getTextWidgetWidth( - String text, double height, double width, BuildContext context, - {TextStyle style}) { - /// Create new text with it style. - final RichText richTextWidget = Text( - text, - style: style, - maxLines: 1, - softWrap: false, - textDirection: TextDirection.ltr, - textAlign: TextAlign.left, - ).build(context); - - /// Create and layout the render object based on allocated width and height. - final renderObject = richTextWidget.createRenderObject(context); - renderObject.layout(BoxConstraints( - minWidth: width, - maxWidth: width, - minHeight: height, - maxHeight: height, - )); - - /// Get the size of text by using render object. - final List textBox = renderObject.getBoxesForSelection( - TextSelection(baseOffset: 0, extentOffset: text.length)); - double textWidth = 0; - double textHeight = 0; - for (final TextBox box in textBox) { - textWidth += box.right - box.left; - final double currentBoxHeight = box.bottom - box.top; - textHeight = textHeight > currentBoxHeight ? textHeight : currentBoxHeight; - } - - /// 10 padding added for text box(left and right side both as 5). - return Size(textWidth + 10, textHeight + 10); -} + /// Check both the dates collection dates are equal or not. + static bool isDateCollectionEqual( + List? originalDates, List? copyDates) { + if (originalDates == copyDates) { + return true; + } -Map _getCalendarViewsText(SfLocalizations localizations) { - final Map calendarViews = {}; - calendarViews[CalendarView.day] = localizations.allowedViewDayLabel; - calendarViews[CalendarView.week] = localizations.allowedViewWeekLabel; - calendarViews[CalendarView.workWeek] = localizations.allowedViewWorkWeekLabel; - calendarViews[CalendarView.timelineDay] = - localizations.allowedViewTimelineDayLabel; - calendarViews[CalendarView.timelineWeek] = - localizations.allowedViewTimelineWeekLabel; - calendarViews[CalendarView.timelineMonth] = - localizations.allowedViewTimelineMonthLabel; - calendarViews[CalendarView.timelineWorkWeek] = - localizations.allowedViewTimelineWorkWeekLabel; - calendarViews[CalendarView.month] = localizations.allowedViewMonthLabel; - calendarViews[CalendarView.schedule] = localizations.allowedViewScheduleLabel; - return calendarViews; -} + if (originalDates == null || copyDates == null) { + return false; + } -/// Check the leading and trailing dates visible or not. -bool _isLeadingAndTrailingDatesVisible( - int weekRowCount, bool showLeadingTrailingDates) { - return weekRowCount != 6 || showLeadingTrailingDates; -} + final int datesCount = originalDates.length; + if (datesCount != copyDates.length) { + return false; + } -/// Return the start date of the month specified in date. -DateTime _getMonthStartDate(DateTime date) { - return DateTime(date.year, date.month, 1); -} + for (int i = 0; i < datesCount; i++) { + if (!isSameDate(originalDates[i], copyDates[i])) { + return false; + } + } -/// Return the end date of the month specified in date. -DateTime _getMonthEndDate(DateTime date) { - return subtractDuration(getNextMonthDate(date), const Duration(days: 1)); -} + return true; + } -/// Return day label width based on schedule view setting. -double _getAgendaViewDayLabelWidth( - ScheduleViewSettings scheduleViewSettings, bool useMobilePlatformUI) { - if (scheduleViewSettings == null || - scheduleViewSettings.dayHeaderSettings == null || - scheduleViewSettings.dayHeaderSettings.width == -1) { - return useMobilePlatformUI ? 50 : 150; + /// Return the copy of list passed. + static List? cloneList(List? value) { + if (value == null || value.isEmpty) { + return null; + } + return value.sublist(0); } - return scheduleViewSettings.dayHeaderSettings.width; -} + /// Check both the collections are equal or not. + static bool isCollectionEqual(List? collection1, List? collection2) { + if (collection1 == collection2) { + return true; + } -/// Return date collection which falls between the visible date range. -List _getDatesWithInVisibleDateRange( - List dates, List visibleDates) { - final List visibleMonthDates = []; - if (visibleDates == null || dates == null) { - return visibleMonthDates; - } + if (isEmptyList(collection1) && isEmptyList(collection2)) { + return true; + } - final DateTime visibleStartDate = visibleDates[0]; - final DateTime visibleEndDate = visibleDates[visibleDates.length - 1]; - final int datesCount = dates.length; - for (int i = 0; i < datesCount; i++) { - final DateTime currentDate = dates[i]; - if (!isDateWithInDateRange(visibleStartDate, visibleEndDate, currentDate)) { - continue; + if (collection1 == null || collection2 == null) { + return false; } - visibleMonthDates.add(currentDate); - } + final int collectionCount = collection1.length; + if (collectionCount != collection2.length) { + return false; + } - return visibleMonthDates; -} + for (int i = 0; i < collectionCount; i++) { + if (collection1[i] != collection2[i]) { + return false; + } + } -/// Check both the dates collection dates are equal or not. -bool _isDateCollectionEqual( - List originalDates, List copyDates) { - if (originalDates == copyDates) { return true; } - if (originalDates == null || copyDates == null) { - return false; - } + /// Check both the resource collection resources are equal or not. + static bool isResourceCollectionEqual( + List? originalCollection, + List? copyCollection) { + if (originalCollection == copyCollection) { + return true; + } - final int datesCount = originalDates.length; - if (datesCount != copyDates.length) { - return false; - } + if (originalCollection == null || copyCollection == null) { + return false; + } - for (int i = 0; i < datesCount; i++) { - if (!isSameDate(originalDates[i], copyDates[i])) { + final int datesCount = originalCollection.length; + if (datesCount != copyCollection.length) { return false; } - } - return true; -} + for (int i = 0; i < datesCount; i++) { + if (originalCollection[i] != copyCollection[i]) { + return false; + } + } -/// Check both the collections are equal or not. -bool _isCollectionEqual(List collection1, List collection2) { - if (collection1 == collection2) { return true; } - if (_isEmptyList(collection1) && _isEmptyList(collection2)) { - return true; - } + /// Check whether the date collection contains the date value or not. + static bool isDateInDateCollection(List? dates, DateTime date) { + if (dates == null || dates.isEmpty) { + return false; + } + + for (final DateTime currentDate in dates) { + if (isSameDate(currentDate, date)) { + return true; + } + } - if (collection1 == null || collection2 == null) { return false; } - final int collectionCount = collection1.length; - if (collectionCount != collection2.length) { - return false; + /// Return schedule view appointment height and its value based on + /// schedule view settings and month view settings. + static double getScheduleAppointmentHeight( + MonthViewSettings? monthViewSettings, + ScheduleViewSettings? scheduleViewSettings) { + return monthViewSettings != null + ? (monthViewSettings.agendaItemHeight == -1 + ? 50 + : monthViewSettings.agendaItemHeight) + : (scheduleViewSettings == null || + scheduleViewSettings.appointmentItemHeight == -1 + ? 50 + : scheduleViewSettings.appointmentItemHeight); } - for (int i = 0; i < collectionCount; i++) { - if (collection1[i] != collection2[i]) { - return false; - } + /// Return schedule view all day appointment height and its value based on + /// schedule view settings and month view settings. + static double getScheduleAllDayAppointmentHeight( + MonthViewSettings? monthViewSettings, + ScheduleViewSettings? scheduleViewSettings) { + return monthViewSettings != null + ? (monthViewSettings.agendaItemHeight == -1 + ? 25 + : monthViewSettings.agendaItemHeight) + : (scheduleViewSettings == null || + scheduleViewSettings.appointmentItemHeight == -1 + ? 25 + : scheduleViewSettings.appointmentItemHeight); } - return true; -} + /// Returns the height for an resource item to render the resource within + /// it in the resource panel. + static double getResourceItemHeight( + double resourceViewSize, + double timelineViewHeight, + ResourceViewSettings resourceViewSettings, + int resourceCount) { + /// The combined padding value between the circle and the display name text + final double textPadding = resourceViewSettings.showAvatar ? 10 : 0; + + /// To calculate the resource item height based on visible resource count, + /// added this condition calculated the resource item height based on + /// visible resource count. + if (resourceViewSettings.visibleResourceCount > 0) { + return timelineViewHeight / resourceViewSettings.visibleResourceCount; + } -/// Check both the resource collection resources are equal or not. -bool _isResourceCollectionEqual(List originalCollection, - List copyCollection) { - if (originalCollection == copyCollection) { - return true; + double itemHeight = timelineViewHeight + textPadding; + + /// Added this condition to check if the visible resource count is `-1`, we + /// have calculated the resource item height based on the resource panel + /// width and the view height, the smallest of this will set as the + /// resource item height. + if (timelineViewHeight > resourceViewSize && + resourceViewSettings.visibleResourceCount < 0) { + itemHeight = resourceViewSize + textPadding; + } + + /// Modified the resource height if the visible resource count is `-1` on + /// this scenario if the resource count is less, to avoid the empty white + /// space on the screen height, we calculated the resource item height to + /// fill into the available screen height. + return resourceCount * itemHeight < timelineViewHeight + ? timelineViewHeight / resourceCount + : itemHeight; } - if (originalCollection == null || copyCollection == null) { - return false; + /// Check and returns whether the resource panel can be added or not in the + /// calendar. + static bool isResourceEnabled( + CalendarDataSource? dataSource, CalendarView view) { + return isTimelineView(view) && + dataSource != null && + dataSource.resources != null && + dataSource.resources!.isNotEmpty; } - final int datesCount = originalCollection.length; - if (datesCount != copyCollection.length) { - return false; + /// Return the appointment semantics text for the all the appointment + /// views(all day panel, time slot panel, agenda view). + static String getAppointmentSemanticsText(CalendarAppointment appointment) { + if (appointment.isAllDay) { + return appointment.subject + 'All day'; + } else if (appointment.isSpanned || + appointment.endTime.difference(appointment.startTime).inDays > 0) { + return appointment.subject + + DateFormat('hh mm a dd/MMMM/yyyy') + .format(appointment.startTime) + .toString() + + 'to' + + DateFormat('hh mm a dd/MMMM/yyyy') + .format(appointment.endTime) + .toString(); + } else { + return appointment.subject + + DateFormat('hh mm a').format(appointment.startTime).toString() + + '-' + + DateFormat('hh mm a dd/MMMM/yyyy') + .format(appointment.endTime) + .toString(); + } } - for (int i = 0; i < datesCount; i++) { - if (originalCollection[i] != copyCollection[i]) { - return false; + /// Get the exact the time from the position and the date time includes + /// minutes value. + static double getTimeToPosition(Duration duration, + TimeSlotViewSettings timeSlotViewSettings, double minuteHeight) { + final Duration startDuration = Duration( + hours: timeSlotViewSettings.startHour.toInt(), + minutes: ((timeSlotViewSettings.startHour - + timeSlotViewSettings.startHour.toInt()) * + 60) + .toInt()); + final Duration difference = duration - startDuration; + if (difference.isNegative) { + return 0; } + + return difference.inMinutes * minuteHeight; } - return true; -} + /// Returns the time interval value based on the given start time, end time + /// and time interval value of time slot view settings, the time interval will + /// be auto adjust if the given time interval doesn't cover the given start + /// and end time values, i.e: if the startHour set as 10 and endHour set as + /// 20 and the timeInterval value given as 180 means we cannot divide the 10 + /// hours into 3 hours, hence the time interval will be auto adjusted to 200 + /// based on the given properties. + static int getTimeInterval(TimeSlotViewSettings settings) { + double defaultLinesCount = 24; + double totalMinutes = 0; + + if (settings.startHour >= 0 && + settings.endHour >= settings.startHour && + settings.endHour <= 24) { + defaultLinesCount = settings.endHour - settings.startHour; + } -/// Check whether the date collection contains the date value or not. -bool _isDateInDateCollection(List dates, DateTime date) { - if (dates == null || dates.isEmpty) { - return false; + totalMinutes = defaultLinesCount * 60; + + if (settings.timeInterval.inMinutes >= 0 && + settings.timeInterval.inMinutes <= totalMinutes && + totalMinutes.round() % settings.timeInterval.inMinutes.round() == 0) { + return settings.timeInterval.inMinutes; + } else if (settings.timeInterval.inMinutes >= 0 && + settings.timeInterval.inMinutes <= totalMinutes) { + return _getNearestValue(settings.timeInterval.inMinutes, totalMinutes); + } else { + return 60; + } } - for (final DateTime currentDate in dates) { - if (isSameDate(currentDate, date)) { - return true; + /// Returns the horizontal lines count for a single day in day/week/workweek and time line view + static double getHorizontalLinesCount( + TimeSlotViewSettings settings, CalendarView view) { + if (view == CalendarView.timelineMonth) { + return 1; } + + double defaultLinesCount = 24; + double totalMinutes = 0; + final int timeInterval = getTimeInterval(settings); + + if (settings.startHour >= 0 && + settings.endHour >= settings.startHour && + settings.endHour <= 24) { + defaultLinesCount = settings.endHour - settings.startHour; + } + + totalMinutes = defaultLinesCount * 60; + + return totalMinutes / timeInterval; } - return false; -} + static int _getNearestValue(int timeInterval, double totalMinutes) { + timeInterval++; + if (totalMinutes.round() % timeInterval.round() == 0) { + return timeInterval; + } -double _getScheduleAppointmentHeight(MonthViewSettings monthViewSettings, - ScheduleViewSettings scheduleViewSettings) { - return monthViewSettings != null - ? (monthViewSettings.agendaItemHeight == -1 - ? 50 - : monthViewSettings.agendaItemHeight) - : (scheduleViewSettings.appointmentItemHeight == -1 - ? 50 - : scheduleViewSettings.appointmentItemHeight); -} + return _getNearestValue(timeInterval, totalMinutes); + } -double _getScheduleAllDayAppointmentHeight(MonthViewSettings monthViewSettings, - ScheduleViewSettings scheduleViewSettings) { - return monthViewSettings != null - ? (monthViewSettings.agendaItemHeight == -1 - ? 25 - : monthViewSettings.agendaItemHeight) - : (scheduleViewSettings.appointmentItemHeight == -1 - ? 25 - : scheduleViewSettings.appointmentItemHeight); -} + /// Check both the time slot date values are same or not. + static bool isSameTimeSlot(DateTime? date1, DateTime? date2) { + if (date1 == date2) { + return true; + } -/// Return the copy of list passed. -List _cloneList(List value) { - if (value == null || value.isEmpty) { - return null; + if (date1 == null || date2 == null) { + return false; + } + + return isSameDate(date1, date2) && + date1.hour == date2.hour && + date1.minute == date2.minute; } - return value.sublist(0); -} -/// Returns the height for an resource item to render the resource within it in -/// the resource panel. -double _getResourceItemHeight( - double resourceViewSize, - double timelineViewHeight, - ResourceViewSettings resourceViewSettings, - int resourceCount) { - /// The combined padding value between the circle and the display name text - final double textPadding = resourceViewSettings.showAvatar ? 10 : 0; - - /// To calculate the resource item height based on visible resource count, - /// added this condition calculated the resource item height based on - /// visible resource count. - if (resourceViewSettings.visibleResourceCount > 0) { - return timelineViewHeight / resourceViewSettings.visibleResourceCount; - } - - double itemHeight = timelineViewHeight + textPadding; - - /// Added this condition to check if the visible resource count is `-1`, we - /// have calculated the resource item height based on the resource panel width - /// and the view height, the smallest of this will set as the resource item - /// height. - if (timelineViewHeight > resourceViewSize && - resourceViewSettings.visibleResourceCount < 0) { - itemHeight = resourceViewSize + textPadding; - } - - /// Modified the resource height if the visible resource count is `-1` on this - /// scenario if the resource count is less, to avoid the empty white space on - /// the screen height, we calculated the resource item height to fill into the - /// available screen height. - return resourceCount * itemHeight < timelineViewHeight - ? timelineViewHeight / resourceCount - : itemHeight; -} + /// Return time label size based on calendar view of calendar widget. + static double getTimeLabelWidth( + double timeLabelViewWidth, CalendarView view) { + if (view == CalendarView.timelineMonth) { + return 0; + } -/// Check and returns whether the resource panel can be added or not in the -/// calendar. -bool _isResourceEnabled(CalendarDataSource dataSource, CalendarView view) { - return _isTimelineView(view) && - dataSource != null && - dataSource.resources != null && - dataSource.resources.isNotEmpty; -} + if (timeLabelViewWidth != -1) { + return timeLabelViewWidth; + } -String _getAppointmentText(Appointment appointment) { - if (appointment.isAllDay) { - return appointment.subject + 'All day'; - } else if (appointment._isSpanned || - appointment.endTime.difference(appointment.startTime).inDays > 0) { - return appointment.subject + - DateFormat('hh mm a dd/MMMM/yyyy') - .format(appointment.startTime) - .toString() + - 'to' + - DateFormat('hh mm a dd/MMMM/yyyy') - .format(appointment.endTime) - .toString(); - } else { - return appointment.subject + - DateFormat('hh mm a').format(appointment.startTime).toString() + - '-' + - DateFormat('hh mm a dd/MMMM/yyyy') - .format(appointment.endTime) - .toString(); + switch (view) { + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + return 30; + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + return 50; + case CalendarView.schedule: + case CalendarView.month: + case CalendarView.timelineMonth: + return 0; + } } -} -/// Get the exact the time from the position and the date time includes minutes -/// value. -double _getTimeToPosition(Duration duration, - TimeSlotViewSettings timeSlotViewSettings, double minuteHeight) { - final Duration startDuration = Duration( - hours: timeSlotViewSettings.startHour.toInt(), - minutes: ((timeSlotViewSettings.startHour - - timeSlotViewSettings.startHour.toInt()) * - 60) - .toInt()); - final Duration difference = duration - startDuration; - if (difference.isNegative) { - return 0; - } - - return difference.inMinutes * minuteHeight; -} + /// Return the view header height based on calendar view of calendar widget. + static double getViewHeaderHeight( + double viewHeaderHeight, CalendarView view) { + if (viewHeaderHeight != -1) { + return viewHeaderHeight; + } -/// Returns the time interval value based on the given start time, end time and -/// time interval value of time slot view settings, the time interval will be -/// auto adjust if the given time interval doesn't cover the given start and end -/// time values, i.e: if the startHour set as 10 and endHour set as 20 and the -/// timeInterval value given as 180 means we cannot divide the 10 hours into -/// 3 hours, hence the time interval will be auto adjusted to 200 -/// based on the given properties. -int _getTimeInterval(TimeSlotViewSettings settings) { - double defaultLinesCount = 24; - double totalMinutes = 0; - - if (settings.startHour >= 0 && - settings.endHour >= settings.startHour && - settings.endHour <= 24) { - defaultLinesCount = settings.endHour - settings.startHour; - } - - totalMinutes = defaultLinesCount * 60; - - if (settings.timeInterval.inMinutes >= 0 && - settings.timeInterval.inMinutes <= totalMinutes && - totalMinutes.round() % settings.timeInterval.inMinutes.round() == 0) { - return settings.timeInterval.inMinutes; - } else if (settings.timeInterval.inMinutes >= 0 && - settings.timeInterval.inMinutes <= totalMinutes) { - return _getNearestValue(settings.timeInterval.inMinutes, totalMinutes); - } else { - return 60; + switch (view) { + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + return 60; + case CalendarView.month: + return 25; + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + return 30; + case CalendarView.schedule: + return 0; + } } -} -/// Returns the horizontal lines count for a single day in day/week/workweek and time line view -double _getHorizontalLinesCount( - TimeSlotViewSettings settings, CalendarView view) { - if (view == CalendarView.timelineMonth) { - return 1; + /// method to check whether the view changed callback can triggered or not. + static bool shouldRaiseViewChangedCallback( + ViewChangedCallback? onViewChanged) { + return onViewChanged != null; } - double defaultLinesCount = 24; - double totalMinutes = 0; - final int timeInterval = _getTimeInterval(settings); + /// method to check whether the on tap callback can triggered or not. + static bool shouldRaiseCalendarTapCallback(CalendarTapCallback? onTap) { + return onTap != null; + } - if (settings.startHour >= 0 && - settings.endHour >= settings.startHour && - settings.endHour <= 24) { - defaultLinesCount = settings.endHour - settings.startHour; + /// method to check whether the long press callback can triggered or not. + static bool shouldRaiseCalendarLongPressCallback( + CalendarLongPressCallback? onLongPress) { + return onLongPress != null; } - totalMinutes = defaultLinesCount * 60; + //// method to check whether the selection changed callback can triggered or not. + static bool shouldRaiseCalendarSelectionChangedCallback( + CalendarSelectionChangedCallback? onSelectionChanged) { + return onSelectionChanged != null; + } - return totalMinutes / timeInterval; -} + /// method that raise the calendar tapped callback with the given parameters + static void raiseCalendarTapCallback( + SfCalendar calendar, + DateTime? date, + List? appointments, + CalendarElement element, + CalendarResource? resource) { + final CalendarTapDetails calendarTapDetails = + CalendarTapDetails(appointments, date, element, resource); + calendar.onTap!(calendarTapDetails); + } -int _getNearestValue(int timeInterval, double totalMinutes) { - timeInterval++; - if (totalMinutes.round() % timeInterval.round() == 0) { - return timeInterval; + /// Method that raise the calendar long press callback with given parameters. + static void raiseCalendarLongPressCallback( + SfCalendar calendar, + DateTime? date, + List? appointments, + CalendarElement element, + CalendarResource? resource) { + final CalendarLongPressDetails calendarLongPressDetails = + CalendarLongPressDetails(appointments, date, element, resource); + calendar.onLongPress!(calendarLongPressDetails); } - return _getNearestValue(timeInterval, totalMinutes); -} + /// method that raise the calendar selection changed callback + /// with the given parameters + static void raiseCalendarSelectionChangedCallback( + SfCalendar calendar, DateTime? date, CalendarResource? resource) { + final CalendarSelectionDetails calendarSelectionDetails = + CalendarSelectionDetails(date, resource); + calendar.onSelectionChanged!(calendarSelectionDetails); + } -bool _isSameTimeSlot(DateTime date1, DateTime date2) { - if (date1 == date2) { - return true; + /// method that raises the visible dates changed callback with the given + /// parameters + static void raiseViewChangedCallback( + SfCalendar calendar, List visibleDates) { + final ViewChangedDetails viewChangedDetails = + ViewChangedDetails(visibleDates); + calendar.onViewChanged!(viewChangedDetails); } - if (date1 == null || date2 == null) { - return false; + /// Check the calendar view is timeline view or not. + static bool isTimelineView(CalendarView view) { + switch (view) { + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + return true; + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + case CalendarView.month: + case CalendarView.schedule: + return false; + } } - return isSameDate(date1, date2) && - date1.hour == date2.hour && - date1.minute == date2.minute; -} + /// converts the given schedule appointment collection to their custom + /// appointment collection + static List getCustomAppointments( + List? appointments) { + final List customAppointments = []; + if (appointments != null) { + for (int i = 0; i < appointments.length; i++) { + final CalendarAppointment appointment = appointments[i]; + customAppointments.add(appointment.data ?? appointment); + } -// returns the single view width from the time line view for time line -double _getSingleViewWidthForTimeLineView(_CalendarViewState viewState) { - return (viewState._scrollController.position.maxScrollExtent + - viewState._scrollController.position.viewportDimension) / - viewState.widget.visibleDates.length; -} + return customAppointments; + } -double _getTimeLabelWidth(double timeLabelViewWidth, CalendarView view) { - if (view == CalendarView.timelineMonth) { - return 0; - } - if (timeLabelViewWidth != -1) { - return timeLabelViewWidth; - } - - switch (view) { - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - return 30; - case CalendarView.day: - case CalendarView.week: - case CalendarView.workWeek: - return 50; - case CalendarView.schedule: - case CalendarView.month: - case CalendarView.timelineMonth: - return 0; + return customAppointments; } - return 0; -} + /// Returns the index of the passed id's resource from the passed resource + /// collection. + static int getResourceIndex( + List? resourceCollection, Object id) { + if (resourceCollection == null || resourceCollection.isEmpty) { + return -1; + } -double _getViewHeaderHeight(double viewHeaderHeight, CalendarView view) { - if (viewHeaderHeight != -1) { - return viewHeaderHeight; + return resourceCollection.indexWhere((resource) => resource.id == id); } - switch (view) { - case CalendarView.day: - case CalendarView.week: - case CalendarView.workWeek: - return 60; - case CalendarView.month: - return 25; - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - return 30; - case CalendarView.schedule: - return 0; + /// Check the date in between first and last date + static bool isDateTimeWithInDateTimeRange( + DateTime startDate, DateTime endDate, DateTime date, int timeInterval) { + if (startDate.isAfter(endDate)) { + final dynamic temp = startDate; + startDate = endDate; + endDate = temp; + } + + if (isSameOrBeforeDateTime(endDate, date) && + isSameOrAfterDateTime(startDate, date)) { + return true; + } + + if (startDate.minute != 0) { + date = date.add(Duration(minutes: timeInterval)); + if (isSameOrBeforeDateTime(endDate, date) && + isSameOrAfterDateTime(startDate, date)) { + return true; + } + } + + return false; } - return viewHeaderHeight; -} + /// Check the date before/same of last date + static bool isSameOrBeforeDateTime(DateTime lastDate, DateTime date) { + return CalendarViewHelper.isSameTimeSlot(lastDate, date) || + lastDate.isAfter(date); + } -//// method to check whether the view changed callback can triggered or not. -bool _shouldRaiseViewChangedCallback(ViewChangedCallback onViewChanged) { - return onViewChanged != null; + /// Check the date after/same of first date + static bool isSameOrAfterDateTime(DateTime firstDate, DateTime date) { + return CalendarViewHelper.isSameTimeSlot(firstDate, date) || + firstDate.isBefore(date); + } } -//// method to check whether the on tap callback can triggered or not. -bool _shouldRaiseCalendarTapCallback(CalendarTapCallback onTap) { - return onTap != null; -} +/// Args to get and update the required properties from calendar state to it's +/// children's +class UpdateCalendarStateDetails { + /// Holds the current display date of calendar. + DateTime? currentDate; -//// method to check whether the long press callback can triggered or not. -bool _shouldRaiseCalendarLongPressCallback( - CalendarLongPressCallback onLongPress) { - return onLongPress != null; -} + /// Holds the current view visible dates collection of calendar. + List currentViewVisibleDates = []; -// method that raise the calendar tapped callback with the given parameters -void _raiseCalendarTapCallback(SfCalendar calendar, - {DateTime date, - List appointments, - CalendarElement element, - CalendarResource resource}) { - final CalendarTapDetails calendarTapDetails = - CalendarTapDetails(appointments, date, element, resource); - calendar.onTap(calendarTapDetails); -} + /// Holds the current visible appointment collections of calendar. + List visibleAppointments = []; + + /// Holds the current selected date of calendar. + DateTime? selectedDate; -// method that raise the calendar long press callback with the given parameters -void _raiseCalendarLongPressCallback(SfCalendar calendar, - {DateTime date, - List appointments, - CalendarElement element, - CalendarResource resource}) { - final CalendarLongPressDetails calendarLongPressDetails = - CalendarLongPressDetails(appointments, date, element, resource); - calendar.onLongPress(calendarLongPressDetails); + /// Holds the all day panel height of calendar. + double allDayPanelHeight = 0; + + /// Holds the all day panel appointment view collection. + List allDayAppointmentViewCollection = []; + + /// Holds the calendar appointments details. + List appointments = []; } -// method that raises the visible dates changed callback with the given -// parameters -void _raiseViewChangedCallback(SfCalendar calendar, - {List visibleDates}) { - final ViewChangedDetails viewChangedDetails = - ViewChangedDetails(visibleDates); - calendar.onViewChanged(viewChangedDetails); +/// Holds the time region view rendering details. +class TimeRegionView { + /// Constructor to create the time region view rendering details + TimeRegionView(this.visibleIndex, this.region, this.bound); + + /// Holds the time slot index of the calendar view. + int visibleIndex = -1; + + /// Holds the time region details. + CalendarTimeRegion region; + + /// Holds the time region view position and size. + Rect bound; } -bool _isAutoTimeIntervalHeight(SfCalendar calendar, CalendarView view) { - if (_isTimelineView(view)) { - return calendar.timeSlotViewSettings.timeIntervalWidth == -1; - } else { - return calendar.timeSlotViewSettings.timeIntervalHeight == -1; - } +/// Holds the appointment view rendering details. +class AppointmentView { + /// Decides the appointment view occupied or not. + bool canReuse = true; + + /// Holds the visible index of appointment start date. + int startIndex = -1; + + /// Holds the visible index of appointment end date. + int endIndex = -1; + + /// Holds the appointment details + CalendarAppointment? appointment; + + /// Defines the rendering position of the appointment view. + int position = -1; + + /// Defines the maximum rendering position of the appointment view. + int maxPositions = -1; + + /// Defines the appointment view holds spanned appointment or not. + bool isSpanned = false; + + /// Holds the appointment view position and size. + RRect? appointmentRect; + + /// Defines the resource view index of the appointment. + int resourceIndex = -1; } -/// Returns the default time interval width for timeline views. -double _getTimeIntervalWidth(double timeIntervalHeight, CalendarView view, - double width, bool isMobilePlatform) { - if (timeIntervalHeight >= 0) { - return timeIntervalHeight; +/// Appointment data for calendar. +/// +/// An object that contains properties to hold the detailed information about +/// the data, which will be rendered in [SfCalendar]. +class CalendarAppointment { + /// Constructor to creates an appointment data for [SfCalendar]. + CalendarAppointment({ + this.startTimeZone, + this.endTimeZone, + this.recurrenceRule, + this.isAllDay = false, + this.notes, + this.location, + this.resourceIds, + required this.startTime, + required this.endTime, + this.subject = '', + this.color = Colors.lightBlue, + this.isSpanned = false, + this.recurrenceExceptionDates, + }) : actualStartTime = startTime, + actualEndTime = endTime; + + /// The start time for an [CalendarAppointment] in [SfCalendar]. + /// + /// Defaults to `DateTime.now()`. + DateTime startTime; + + /// The end time for an [CalendarAppointment] in [SfCalendar]. + /// + /// Defaults to `DateTime.now()`. + DateTime endTime; + + /// Displays the [CalendarAppointment] on the all day panel area of time slot + /// views in [SfCalendar]. + /// + /// Defaults to `false`. + bool isAllDay = false; + + /// The subject for the [CalendarAppointment] in [SfCalendar]. + /// + /// Defaults to ` ` represents empty string. + String subject; + + /// The color that fills the background of the [CalendarAppointment] view in + /// [SfCalendar]. + /// + /// Defaults to `Colors.lightBlue`. + Color color; + + /// The start time zone for an [CalendarAppointment] in [SfCalendar]. + /// + /// If it is not [null] the appointment start time will be calculated based on + /// the time zone set to this property and [SfCalendar.timeZone] property. + /// + /// Defaults to null. + String? startTimeZone; + + /// The end time zone for an [CalendarAppointment] in [SfCalendar]. + /// + /// If it is not [null] the appointment end time will be calculated based on + /// the time zone set to this property and [SfCalendar.timeZone] property. + /// + /// Defaults to null. + String? endTimeZone; + + /// Recurs the [CalendarAppointment] on [SfCalendar]. + /// + /// Defaults to null. + String? recurrenceRule; + + /// Delete the occurrence for an recurrence appointment. + /// + /// Defaults to `null`. + List? recurrenceExceptionDates; + + /// Defines the notes for an [CalendarAppointment] in [SfCalendar]. + /// + /// Defaults to null. + String? notes; + + /// Defines the location for an [CalendarAppointment] in [SfCalendar]. + /// + /// Defaults to null. + String? location; + + /// The ids of the [CalendarResource] that shares this [CalendarAppointment]. + List? resourceIds; + + /// Holds the parent appointment details + Object? data; + + /// Store the appointment start date value based on start timezone value. + DateTime actualStartTime; + + /// Store the appointment end date value based on end timezone value. + DateTime actualEndTime; + + /// Defines the appointment is spanned appointment or not. + bool isSpanned = false; + + /// For span appointments, we have split the appointment into multiple while + /// calculating the visible appointments, to render on the visible view, hence + /// it's not possible to get the exact start and end time for the spanning + /// appointment, hence to hold the exact start time of the appointment we have + /// used this variable, and stored the start time which calculated based on + /// the timezone, in the visible appointments calculation. + late DateTime exactStartTime; + + /// For span appointments, we have split the appointment into multiple while + /// calculating the visible appointments, to render on the visible view, hence + /// it's not possible to get the exact start and end time for the spanning + /// appointment, hence to hold the exact start time of the appointment we have + /// used this variable, and stored the end time which calculated based on + /// the timezone, in the visible appointments calculation. + late DateTime exactEndTime; + + /// Returns an appointment object based on + /// the passed calendar appointment value + Appointment convertToCalendarAppointment() { + return Appointment( + startTime: startTime, + endTime: endTime, + subject: subject, + color: color, + recurrenceRule: recurrenceRule, + isAllDay: isAllDay, + resourceIds: resourceIds, + startTimeZone: startTimeZone, + endTimeZone: endTimeZone, + notes: notes, + location: location, + recurrenceExceptionDates: recurrenceExceptionDates); } - if (view == CalendarView.timelineMonth && - !_isMobileLayoutUI(width, isMobilePlatform)) { - return 160; + @override + bool operator ==(dynamic other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + final CalendarAppointment otherAppointment = other; + return CalendarViewHelper.isSameTimeSlot( + otherAppointment.startTime, startTime) && + CalendarViewHelper.isSameTimeSlot(otherAppointment.endTime, endTime) && + CalendarViewHelper.isSameTimeSlot( + otherAppointment.actualStartTime, actualStartTime) && + CalendarViewHelper.isSameTimeSlot( + otherAppointment.actualEndTime, actualEndTime) && + otherAppointment.isSpanned == isSpanned && + otherAppointment.startTimeZone == startTimeZone && + otherAppointment.endTimeZone == endTimeZone && + otherAppointment.isAllDay == isAllDay && + otherAppointment.notes == notes && + otherAppointment.location == location && + otherAppointment.resourceIds == resourceIds && + otherAppointment.subject == subject && + otherAppointment.color == color && + CalendarViewHelper.isDateCollectionEqual( + otherAppointment.recurrenceExceptionDates, + recurrenceExceptionDates); } - return 60; + @override + int get hashCode { + return hashValues( + startTimeZone, + endTimeZone, + recurrenceRule, + isAllDay = false, + notes, + location, + hashList(resourceIds), + startTime, + endTime, + subject, + color, + hashList(recurrenceExceptionDates), + ); + } } -/// Returns the time interval width based on property value, also arrange the -/// time slots into the view port size. -double _getTimeIntervalHeight( - SfCalendar calendar, - CalendarView view, - double width, - double height, - int visibleDatesCount, - double allDayHeight, - bool isMobilePlatform) { - final bool isTimelineView = _isTimelineView(view); - double timeIntervalHeight = isTimelineView - ? _getTimeIntervalWidth(calendar.timeSlotViewSettings.timeIntervalWidth, - view, width, isMobilePlatform) - : calendar.timeSlotViewSettings.timeIntervalHeight; - - if (!_isAutoTimeIntervalHeight(calendar, view)) { - return timeIntervalHeight; - } - - double viewHeaderHeight = - _getViewHeaderHeight(calendar.viewHeaderHeight, view); - - if (view == CalendarView.day) { - allDayHeight = _kAllDayLayoutHeight; - viewHeaderHeight = 0; - } else { - allDayHeight = allDayHeight > _kAllDayLayoutHeight - ? _kAllDayLayoutHeight - : allDayHeight; - } - - switch (view) { - case CalendarView.day: - case CalendarView.week: - case CalendarView.workWeek: - timeIntervalHeight = (height - allDayHeight - viewHeaderHeight) / - _getHorizontalLinesCount(calendar.timeSlotViewSettings, view); - break; - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - { - final double _horizontalLinesCount = - _getHorizontalLinesCount(calendar.timeSlotViewSettings, view); - timeIntervalHeight = - width / (_horizontalLinesCount * visibleDatesCount); - if (!_isValidWidth( - width, calendar, visibleDatesCount, _horizontalLinesCount)) { - // we have used 40 as a default time interval height for timeline view - // if the time interval height set for auto time interval height - timeIntervalHeight = 40; - } - } - break; - case CalendarView.schedule: - case CalendarView.month: - return 0; +/// It is used to highlight time slots on day, week, work week +/// and timeline views based on start and end time and +/// also used to restrict interaction on time slots. +/// +/// Note: If time region have both the [text] and [iconData] then the region +/// will draw icon only. +class CalendarTimeRegion { + /// Creates a Time region for timeslot views in calendar. + /// + /// The time region used to highlight and block the specific timeslots in + /// timeslots view of [SfCalendar]. + CalendarTimeRegion( + {required this.startTime, + required this.endTime, + this.text, + this.recurrenceRule, + this.color, + this.enablePointerInteraction = true, + this.recurrenceExceptionDates, + this.resourceIds, + this.timeZone, + this.iconData, + this.textStyle}) + : actualStartTime = startTime, + actualEndTime = endTime; + + /// Used to specify the start time of the [CalendarTimeRegion]. + final DateTime startTime; + + /// Used to specify the end time of the [CalendarTimeRegion]. + final DateTime endTime; + + /// Used to specify the text of [CalendarTimeRegion]. + final String? text; + + /// Used to specify the recurrence of [CalendarTimeRegion]. + final String? recurrenceRule; + + /// Used to specify the background color of [CalendarTimeRegion]. + final Color? color; + + /// Used to allow or restrict the interaction of [CalendarTimeRegion]. + final bool enablePointerInteraction; + + /// Used to specify the time zone of [CalendarTimeRegion] start and end time. + final String? timeZone; + + /// Used to specify the text style for [CalendarTimeRegion] text and icon. + final TextStyle? textStyle; + + /// Used to specify the icon of [CalendarTimeRegion]. + /// + /// Note: If time region have both the text and icon then it will draw icon + /// only. + final IconData? iconData; + + /// Used to restrict the occurrence for an recurrence region. + final List? recurrenceExceptionDates; + + /// The ids of the [CalendarResource] that shares this [CalendarTimeRegion]. + final List? resourceIds; + + /// Used to store the original time region details. + late TimeRegion data; + + /// Used to store the start date value with specified time zone. + late DateTime actualStartTime; + + /// Used to store the end date value with specified time zone. + late DateTime actualEndTime; + + /// Creates a copy of this [CalendarTimeRegion] but with the given fields + /// replaced with the new values. + CalendarTimeRegion copyWith( + {DateTime? startTime, + DateTime? endTime, + String? text, + String? recurrenceRule, + Color? color, + bool? enablePointerInteraction, + List? recurrenceExceptionDates, + String? timeZone, + IconData? iconData, + TextStyle? textStyle, + List? resourceIds}) { + return CalendarTimeRegion( + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + color: color ?? this.color, + recurrenceRule: recurrenceRule ?? this.recurrenceRule, + textStyle: textStyle ?? this.textStyle, + enablePointerInteraction: + enablePointerInteraction ?? this.enablePointerInteraction, + recurrenceExceptionDates: + recurrenceExceptionDates ?? this.recurrenceExceptionDates, + text: text ?? this.text, + iconData: iconData ?? this.iconData, + timeZone: timeZone ?? this.timeZone, + resourceIds: resourceIds ?? this.resourceIds); } - return timeIntervalHeight; -} + @override + bool operator ==(dynamic other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } -// checks whether the width can afford the line count or else creates a -// scrollable width -bool _isValidWidth(double screenWidth, SfCalendar calendar, - int visibleDatesCount, double horizontalLinesCount) { - const int offSetValue = 10; - final double tempWidth = - visibleDatesCount * offSetValue * horizontalLinesCount; + final CalendarTimeRegion region = other; + return region.textStyle == textStyle && + CalendarViewHelper.isSameTimeSlot(region.startTime, startTime) && + CalendarViewHelper.isSameTimeSlot(region.endTime, endTime) && + CalendarViewHelper.isSameTimeSlot( + region.actualStartTime, actualStartTime) && + CalendarViewHelper.isSameTimeSlot( + region.actualStartTime, actualStartTime) && + region.color == color && + region.recurrenceRule == recurrenceRule && + region.enablePointerInteraction == enablePointerInteraction && + CalendarViewHelper.isDateCollectionEqual( + region.recurrenceExceptionDates, recurrenceExceptionDates) && + region.iconData == iconData && + region.timeZone == timeZone && + region.resourceIds == resourceIds && + region.text == text; + } - if (tempWidth < screenWidth) { - return true; + @override + int get hashCode { + return hashValues( + startTime, + endTime, + color, + recurrenceRule, + textStyle, + enablePointerInteraction, + hashList(recurrenceExceptionDates), + hashList(resourceIds), + text, + iconData, + timeZone); } +} - return false; +/// Used to hold the schedule view hovering details +@immutable +class ScheduleViewHoveringDetails { + /// Constructor to create the schedule view hovering details. + const ScheduleViewHoveringDetails(this.hoveringDate, this.hoveringOffset); + + /// Holds the hovering position date time value. + final DateTime hoveringDate; + + /// Holds the hovering position value. + final Offset hoveringOffset; } -bool _isTimelineView(CalendarView view) { - switch (view) { - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - return true; - case CalendarView.day: - case CalendarView.week: - case CalendarView.workWeek: - case CalendarView.month: - case CalendarView.schedule: - return false; - } +/// The class contains all day panel selection details. +/// if all day panel appointment selected then [appointmentView] holds +/// appointment details, else [selectedDate] holds selected region date value. +@immutable +class SelectionDetails { + /// Constructor to create the selection details. + const SelectionDetails(this.appointmentView, this.selectedDate); + + /// Holds the selected appointment view details. + final AppointmentView? appointmentView; - return false; + /// Holds the selected date details. + final DateTime? selectedDate; } -// converts the given schedule appointment collection to their custom -// appointment collection -List _getCustomAppointments(List appointments) { - final List customAppointments = []; - if (appointments != null) { - for (int i = 0; i < appointments.length; i++) { - customAppointments.add(appointments[i]._data); +/// Parent data for use with calendar custom widget. +class CalendarParentData extends ContainerBoxParentData {} + +/// Custom render object used in calendar child widgets. +abstract class CustomCalendarRenderObject extends RenderBox + with ContainerRenderObjectMixin { + @override + void setupParentData(RenderObject child) { + if (child.parentData is! CalendarParentData) { + child.parentData = CalendarParentData(); } + } - return customAppointments; + @override + bool hitTestSelf(Offset position) { + return true; + } + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + return; } - return null; + /// Returns a function that builds semantic information for the render object. + SemanticsBuilderCallback? get semanticsBuilder => null; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/date_time_engine.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/date_time_engine.dart index b18bb73b4..13ffd9803 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/date_time_engine.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/date_time_engine.dart @@ -1,283 +1,283 @@ -part of calendar; +import 'package:syncfusion_flutter_core/core.dart'; +import 'enums.dart'; -//// Calculate the visible dates count based on calendar view -int _getViewDatesCount(CalendarView calendarView, int numberOfWeeks) { - if (calendarView == null) { - return 0; - } - - switch (calendarView) { - case CalendarView.month: - return _kNumberOfDaysInWeek * numberOfWeeks; - case CalendarView.week: - case CalendarView.workWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineWeek: - return _kNumberOfDaysInWeek; - case CalendarView.timelineDay: - case CalendarView.day: - case CalendarView.schedule: - return 1; - case CalendarView.timelineMonth: - - /// 6 represents the number of weeks in view, we have used this static, - /// since timeline month doesn't support the number of weeks in view. - return _kNumberOfDaysInWeek * 6; - } +/// Holds the static helper methods used for date calculation in calendar. +class DateTimeHelper { + /// Calculate the visible dates count based on calendar view + static int getViewDatesCount(CalendarView calendarView, int numberOfWeeks) { + switch (calendarView) { + case CalendarView.month: + return DateTime.daysPerWeek * numberOfWeeks; + case CalendarView.week: + case CalendarView.workWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineWeek: + return DateTime.daysPerWeek; + case CalendarView.timelineDay: + case CalendarView.day: + case CalendarView.schedule: + return 1; + case CalendarView.timelineMonth: - return 0; -} - -/// Returns the list of current month dates alone from the dates passed. -List _getCurrentMonthDates(List visibleDates) { - final int visibleDatesCount = visibleDates.length; - final int currentMonth = visibleDates[visibleDatesCount ~/ 2].month; - final List currentMonthDates = []; - for (int i = 0; i < visibleDatesCount; i++) { - final DateTime currentVisibleDate = visibleDates[i]; - if (currentVisibleDate.month != currentMonth) { - continue; + /// 6 represents the number of weeks in view, we have used this static, + /// since timeline month doesn't support the number of weeks in view. + return DateTime.daysPerWeek * 6; } - - currentMonthDates.add(currentVisibleDate); - } - - return currentMonthDates; -} - -//// Calculate the next view visible start date based on calendar view. -DateTime _getNextViewStartDate( - CalendarView calendarView, int numberOfWeeksInView, DateTime date) { - if (calendarView == null) { - return date; } - switch (calendarView) { - case CalendarView.month: - case CalendarView.timelineMonth: - { - /// The timeline month view renders the current month dates alone, hence - /// it doesn't support the numberOfWeekInView. - return numberOfWeeksInView == 6 || - calendarView == CalendarView.timelineMonth - ? getNextMonthDate(date) - : addDuration(date, - Duration(days: numberOfWeeksInView * _kNumberOfDaysInWeek)); + /// Returns the list of current month dates alone from the dates passed. + static List getCurrentMonthDates(List visibleDates) { + final int visibleDatesCount = visibleDates.length; + final int currentMonth = visibleDates[visibleDatesCount ~/ 2].month; + final List currentMonthDates = []; + for (int i = 0; i < visibleDatesCount; i++) { + final DateTime currentVisibleDate = visibleDates[i]; + if (currentVisibleDate.month != currentMonth) { + continue; } - case CalendarView.week: - case CalendarView.workWeek: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - return addDuration(date, const Duration(days: _kNumberOfDaysInWeek)); - case CalendarView.day: - case CalendarView.timelineDay: - case CalendarView.schedule: - return addDuration(date, const Duration(days: 1)); - } - return date; -} + currentMonthDates.add(currentVisibleDate); + } -//// Calculate the previous view visible start date based on calendar view. -DateTime _getPreviousViewStartDate( - CalendarView calendarView, int numberOfWeeksInView, DateTime date) { - if (calendarView == null) { - return date; + return currentMonthDates; } - switch (calendarView) { - case CalendarView.month: - case CalendarView.timelineMonth: - { - return numberOfWeeksInView == 6 || - calendarView == CalendarView.timelineMonth - ? getPreviousMonthDate(date) - : addDuration(date, - Duration(days: -numberOfWeeksInView * _kNumberOfDaysInWeek)); - } - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.week: - case CalendarView.workWeek: - return addDuration(date, const Duration(days: -_kNumberOfDaysInWeek)); - case CalendarView.day: - case CalendarView.timelineDay: - case CalendarView.schedule: - return addDuration(date, const Duration(days: -1)); + /// Calculate the next view visible start date based on calendar view. + static DateTime getNextViewStartDate( + CalendarView calendarView, int numberOfWeeksInView, DateTime date) { + switch (calendarView) { + case CalendarView.month: + case CalendarView.timelineMonth: + { + /// The timeline month view renders the current month dates alone, + /// hence it doesn't support the numberOfWeekInView. + return numberOfWeeksInView == 6 || + calendarView == CalendarView.timelineMonth + ? getNextMonthDate(date) + : addDays(date, numberOfWeeksInView * DateTime.daysPerWeek); + } + case CalendarView.week: + case CalendarView.workWeek: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + return addDays(date, DateTime.daysPerWeek); + case CalendarView.day: + case CalendarView.timelineDay: + case CalendarView.schedule: + return addDays(date, 1); + } } - return date; -} - -DateTime _getPreviousValidDate( - DateTime prevViewDate, List nonWorkingDays) { - DateTime previousDate = - subtractDuration(prevViewDate, const Duration(days: 1)); - while (nonWorkingDays.contains(previousDate.weekday)) { - previousDate = subtractDuration(previousDate, const Duration(days: 1)); + /// Calculate the previous view visible start date based on calendar view. + static DateTime getPreviousViewStartDate( + CalendarView calendarView, int numberOfWeeksInView, DateTime date) { + switch (calendarView) { + case CalendarView.month: + case CalendarView.timelineMonth: + { + return numberOfWeeksInView == 6 || + calendarView == CalendarView.timelineMonth + ? getPreviousMonthDate(date) + : addDays(date, -numberOfWeeksInView * DateTime.daysPerWeek); + } + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.week: + case CalendarView.workWeek: + return addDays(date, -DateTime.daysPerWeek); + case CalendarView.day: + case CalendarView.timelineDay: + case CalendarView.schedule: + return addDays(date, -1); + } } - return previousDate; -} -DateTime _getNextValidDate(DateTime nextDate, List nonWorkingDays) { - DateTime nextViewDate = addDuration(nextDate, const Duration(days: 1)); - while (nonWorkingDays.contains(nextViewDate.weekday)) { - nextViewDate = addDuration(nextViewDate, const Duration(days: 1)); + static DateTime _getPreviousValidDate( + DateTime prevViewDate, List nonWorkingDays) { + DateTime previousDate = addDays(prevViewDate, -1); + while (nonWorkingDays.contains(previousDate.weekday)) { + previousDate = addDays(previousDate, -1); + } + return previousDate; } - return nextViewDate; -} -int _getIndex(List dates, DateTime date) { - if (date.isBefore(dates[0])) { - return 0; + static DateTime _getNextValidDate( + DateTime nextDate, List nonWorkingDays) { + DateTime nextViewDate = addDays(nextDate, 1); + while (nonWorkingDays.contains(nextViewDate.weekday)) { + nextViewDate = addDays(nextViewDate, 1); + } + return nextViewDate; } - final int datesCount = dates.length; - if (date.isAfter(dates[datesCount - 1])) { - return datesCount - 1; - } + /// Return index value of the date in dates collection. + /// if the date in between the dates collection but dates collection does not + /// have a date value then it return next date index. + /// Eg., If the dates collection have Jan 4, Jan 5, Jan 7, Jan 8 and Jan 9, + /// and the date value as Jan 6 then it return index as 2(Jan 7). + static int getIndex(List dates, DateTime date) { + if (date.isBefore(dates[0])) { + return 0; + } - for (int i = 0; i < datesCount; i++) { - final DateTime visibleDate = dates[i]; - if (isSameOrBeforeDate(visibleDate, date)) { - return i; + final int datesCount = dates.length; + if (date.isAfter(dates[datesCount - 1])) { + return datesCount - 1; } - } - return -1; -} + for (int i = 0; i < datesCount; i++) { + final DateTime visibleDate = dates[i]; + if (isSameOrBeforeDate(visibleDate, date)) { + return i; + } + } -/// Get the exact visible date index for date, if the date collection -/// does not contains the date value then it return -1 value. -int _getVisibleDateIndex(List dates, DateTime date) { - if (!isDateWithInDateRange(dates[0], dates[dates.length - 1], date)) { return -1; } - for (int i = 0; i < dates.length; i++) { - final DateTime visibleDate = dates[i]; - if (isSameDate(visibleDate, date)) { - return i; + /// Get the exact visible date index for date, if the date collection + /// does not contains the date value then it return -1 value. + static int getVisibleDateIndex(List dates, DateTime date) { + final int count = dates.length; + if (!isDateWithInDateRange(dates[0], dates[count - 1], date)) { + return -1; } - } - return -1; -} + for (int i = 0; i < count; i++) { + if (isSameDate(dates[i], date)) { + return i; + } + } -bool _canMoveToPreviousView(CalendarView calendarView, int numberOfWeeksInView, - DateTime minDate, DateTime maxDate, List visibleDates, - [List nonWorkingDays, bool isRTL = false]) { - if (isRTL) { - return _canMoveToNextView(calendarView, numberOfWeeksInView, minDate, - maxDate, visibleDates, nonWorkingDays); + return -1; } - switch (calendarView) { - case CalendarView.month: - case CalendarView.timelineMonth: - { - if (numberOfWeeksInView != 6 || - calendarView == CalendarView.timelineMonth) { - final DateTime prevViewDate = - subtractDuration(visibleDates[0], const Duration(days: 1)); + /// Check the current calendar view is valid for move to previous view or not. + static bool canMoveToPreviousView( + CalendarView calendarView, + int numberOfWeeksInView, + DateTime minDate, + DateTime maxDate, + List visibleDates, + List nonWorkingDays, + [bool isRTL = false]) { + if (isRTL) { + return canMoveToNextView(calendarView, numberOfWeeksInView, minDate, + maxDate, visibleDates, nonWorkingDays); + } + + switch (calendarView) { + case CalendarView.month: + case CalendarView.timelineMonth: + { + if (numberOfWeeksInView != 6 || + calendarView == CalendarView.timelineMonth) { + final DateTime prevViewDate = addDays(visibleDates[0], -1); + if (!isSameOrAfterDate(minDate, prevViewDate)) { + return false; + } + } else { + final DateTime currentDate = visibleDates[visibleDates.length ~/ 2]; + final DateTime previousDate = getPreviousMonthDate(currentDate); + if ((previousDate.month < minDate.month && + previousDate.year == minDate.year) || + previousDate.year < minDate.year) { + return false; + } + } + } + break; + case CalendarView.day: + case CalendarView.week: + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + { + DateTime prevViewDate = visibleDates[0]; + prevViewDate = addDays(prevViewDate, -1); if (!isSameOrAfterDate(minDate, prevViewDate)) { return false; } - } else { - final DateTime currentDate = visibleDates[visibleDates.length ~/ 2]; - final DateTime previousDate = getPreviousMonthDate(currentDate); - if ((previousDate.month < minDate.month && - previousDate.year == minDate.year) || - previousDate.year < minDate.year) { + } + break; + case CalendarView.timelineWorkWeek: + case CalendarView.workWeek: + { + final DateTime previousDate = + _getPreviousValidDate(visibleDates[0], nonWorkingDays); + if (!isSameOrAfterDate(minDate, previousDate)) { return false; } } - } - break; - case CalendarView.day: - case CalendarView.week: - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - { - DateTime prevViewDate = visibleDates[0]; - prevViewDate = subtractDuration(prevViewDate, const Duration(days: 1)); - if (!isSameOrAfterDate(minDate, prevViewDate)) { - return false; - } - } - break; - case CalendarView.timelineWorkWeek: - case CalendarView.workWeek: - { - final DateTime previousDate = - _getPreviousValidDate(visibleDates[0], nonWorkingDays); - if (!isSameOrAfterDate(minDate, previousDate)) { - return false; - } - } - break; - case CalendarView.schedule: - return true; - } - - return true; -} + break; + case CalendarView.schedule: + return true; + } -bool _canMoveToNextView(CalendarView calendarView, int numberOfWeeksInView, - DateTime minDate, DateTime maxDate, List visibleDates, - [List nonWorkingDays, bool isRTL = false]) { - if (isRTL) { - return _canMoveToPreviousView(calendarView, numberOfWeeksInView, minDate, - maxDate, visibleDates, nonWorkingDays); + return true; } - switch (calendarView) { - case CalendarView.month: - case CalendarView.timelineMonth: - { - if (numberOfWeeksInView != 6 || - calendarView == CalendarView.timelineMonth) { - final DateTime nextViewDate = addDuration( - visibleDates[visibleDates.length - 1], const Duration(days: 1)); + /// Check the current calendar view is valid for move to next view or not. + static bool canMoveToNextView( + CalendarView calendarView, + int numberOfWeeksInView, + DateTime minDate, + DateTime maxDate, + List visibleDates, + List nonWorkingDays, + [bool isRTL = false]) { + if (isRTL) { + return canMoveToPreviousView(calendarView, numberOfWeeksInView, minDate, + maxDate, visibleDates, nonWorkingDays); + } + + switch (calendarView) { + case CalendarView.month: + case CalendarView.timelineMonth: + { + if (numberOfWeeksInView != 6 || + calendarView == CalendarView.timelineMonth) { + final DateTime nextViewDate = + addDays(visibleDates[visibleDates.length - 1], 1); + if (!isSameOrBeforeDate(maxDate, nextViewDate)) { + return false; + } + } else { + final DateTime currentDate = visibleDates[visibleDates.length ~/ 2]; + final DateTime nextDate = getNextMonthDate(currentDate); + if ((nextDate.month > maxDate.month && + nextDate.year == maxDate.year) || + nextDate.year > maxDate.year) { + return false; + } + } + } + break; + case CalendarView.day: + case CalendarView.week: + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + { + final DateTime nextViewDate = + addDays(visibleDates[visibleDates.length - 1], 1); if (!isSameOrBeforeDate(maxDate, nextViewDate)) { return false; } - } else { - final DateTime currentDate = visibleDates[visibleDates.length ~/ 2]; - final DateTime nextDate = getNextMonthDate(currentDate); - if ((nextDate.month > maxDate.month && - nextDate.year == maxDate.year) || - nextDate.year > maxDate.year) { + } + break; + case CalendarView.workWeek: + case CalendarView.timelineWorkWeek: + { + final DateTime nextDate = _getNextValidDate( + visibleDates[visibleDates.length - 1], nonWorkingDays); + if (!isSameOrBeforeDate(maxDate, nextDate)) { return false; } } - } - break; - case CalendarView.day: - case CalendarView.week: - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - { - final DateTime nextViewDate = addDuration( - visibleDates[visibleDates.length - 1], const Duration(days: 1)); - if (!isSameOrBeforeDate(maxDate, nextViewDate)) { - return false; - } - } - break; - case CalendarView.workWeek: - case CalendarView.timelineWorkWeek: - { - final DateTime nextDate = _getNextValidDate( - visibleDates[visibleDates.length - 1], nonWorkingDays); - if (!isSameOrBeforeDate(maxDate, nextDate)) { - return false; - } - } - break; - case CalendarView.schedule: - return true; - } + break; + case CalendarView.schedule: + return true; + } - return true; + return true; + } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/enums.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/enums.dart index dad92986f..74f816e13 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/enums.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/enums.dart @@ -1,5 +1,3 @@ -part of calendar; - /// A direction in which the [SfCalendar] month view navigates. enum MonthNavigationDirection { /// - MonthNavigationDirection.vertical, Navigates in top and bottom direction @@ -208,3 +206,18 @@ enum CalendarDataSourceAction { /// _Note:_ This is applicable only when the resource collection reset. resetResource } + +/// Available view navigation modes for [SfCalendar]. +enum ViewNavigationMode { + /// - ViewNavigationMode.snap, Allows to switching to previous/next views + /// through swipe interaction in SfCalendar. + snap, + + /// - ViewNavigationMode.none, Restrict the next or previous view dates + /// to be shown by swipe interaction in SfCalendar. + /// + /// It will not impact scrolling timeslot views, + /// [controller.forward], [controller.backward] + /// and [showNavigationArrow]. + none +} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/event_args.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/event_args.dart index b8a7d08b2..bc2ec5bee 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/event_args.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/event_args.dart @@ -1,4 +1,9 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../resource_view/calendar_resource.dart'; +import '../settings/time_region.dart'; +import 'enums.dart'; /// The dates that visible on the view changes in [SfCalendar]. /// @@ -22,8 +27,8 @@ class ViewChangedDetails { @immutable class CalendarTapDetails extends CalendarTouchDetails { /// Creates details for [CalendarTapCallback]. - const CalendarTapDetails(List appointments, DateTime date, - CalendarElement element, CalendarResource resource) + const CalendarTapDetails(List? appointments, DateTime? date, + CalendarElement element, CalendarResource? resource) : super(appointments, date, element, resource); } @@ -37,11 +42,31 @@ class CalendarTapDetails extends CalendarTouchDetails { @immutable class CalendarLongPressDetails extends CalendarTouchDetails { /// Creates details for [CalendarLongPressCallback] - const CalendarLongPressDetails(List appointments, DateTime date, - CalendarElement element, CalendarResource resource) + const CalendarLongPressDetails(List? appointments, DateTime? date, + CalendarElement element, CalendarResource? resource) : super(appointments, date, element, resource); } +/// The element that tapped on view in [SfCalendar] +/// +/// Details for [CalendarSelectionChangedCallback], +/// such as [date], and [resource]. +/// +/// See also: +/// [CalendarSelectionChangedCallback] +@immutable +class CalendarSelectionDetails { + /// Creates details for [CalendarSelectionChangedCallback]. + const CalendarSelectionDetails(this.date, this.resource); + + /// The date time value that represents selected calendar cell on + /// timeslot and month views. + final DateTime? date; + + /// The resource associated with the selected calendar cell in timeline views. + final CalendarResource? resource; +} + /// The element that tapped or long pressed on view in [SfCalendar]. /// /// Base class for [CalendarTapDetails] and [CalendarLongPressDetails]. @@ -59,16 +84,16 @@ class CalendarTouchDetails { /// The collection of appointments that tapped or falls inside the selected /// date. - final List appointments; + final List? appointments; /// The date cell that tapped on view. - final DateTime date; + final DateTime? date; /// The element that tapped on view. final CalendarElement targetElement; /// The resource associated with the tapped calendar cell in timeline views. - final CalendarResource resource; + final CalendarResource? resource; } /// Signature for a function that creates a widget based on month @@ -88,13 +113,20 @@ typedef CalendarAppointmentBuilder = Widget Function(BuildContext context, typedef TimeRegionBuilder = Widget Function( BuildContext context, TimeRegionDetails timeRegionDetails); -class _CalendarParentData extends ContainerBoxParentData {} +/// Signature for the function that create the widget based on load +/// more details. +typedef LoadMoreWidgetBuilder = Widget Function( + BuildContext context, LoadMoreCallback loadMoreAppointments); + +/// Signature for the function that have no arguments and return no data, but +/// that return a [Future] to indicate when their work is complete. +typedef LoadMoreCallback = Future Function(); /// Contains the details that needed on month cell builder. class MonthCellDetails { /// Default constructor to store the details needed in month cell builder const MonthCellDetails( - {this.date, this.appointments, this.visibleDates, this.bounds}); + this.date, this.appointments, this.visibleDates, this.bounds); /// The date value associated with the month cell widget. final DateTime date; @@ -112,7 +144,7 @@ class MonthCellDetails { /// Contains the details that needed on schedule view month header builder. class ScheduleViewMonthHeaderDetails { /// Default constructor to store the details needed in builder - const ScheduleViewMonthHeaderDetails({this.date, this.bounds}); + const ScheduleViewMonthHeaderDetails(this.date, this.bounds); /// The date value associated with the schedule view month header widget. final DateTime date; @@ -124,11 +156,8 @@ class ScheduleViewMonthHeaderDetails { /// Contains the details that needed on appointment view builder. class CalendarAppointmentDetails { /// Default constructor to store the details needed in appointment builder. - const CalendarAppointmentDetails( - {this.date, - this.appointments, - this.bounds, - this.isMoreAppointmentRegion = false}); + const CalendarAppointmentDetails(this.date, this.appointments, this.bounds, + {this.isMoreAppointmentRegion = false}); /// The date value associated with the appointment view widget. final DateTime date; @@ -150,7 +179,7 @@ class CalendarAppointmentDetails { /// Contains the details that needed on special region view builder. class TimeRegionDetails { /// Default constructor to store the details needed in time region builder. - TimeRegionDetails({this.region, this.date, this.bounds}); + TimeRegionDetails(this.region, this.date, this.bounds); /// Region detail associated with the time region view in day, week, /// workweek and timeline day, week, workweek views. @@ -163,14 +192,42 @@ class TimeRegionDetails { final Rect bounds; } -/// args to update the required properties from calendar state to it's -/// children's -class _UpdateCalendarStateDetails { - DateTime _currentDate; - List _currentViewVisibleDates; - List _visibleAppointments; - DateTime _selectedDate; - double _allDayPanelHeight; - List<_AppointmentView> _allDayAppointmentViewCollection; - List _appointments; -} +/// Signature for callback that reports that a current view or current visible +/// dates changes. +/// +/// The visible dates collection visible on view when the view changes available +/// in the [ViewChangedDetails]. +/// +/// Used by [SfCalendar.onViewChanged]. +typedef ViewChangedCallback = void Function( + ViewChangedDetails viewChangedDetails); + +/// Signature for callback that reports that a calendar element tapped on view. +/// +/// The tapped date, appointments, and element details when the tap action +/// performed on element available in the [CalendarTapDetails]. +/// +/// Used by[SfCalendar.onTap]. +typedef CalendarTapCallback = void Function( + CalendarTapDetails calendarTapDetails); + +/// Signature for callback that reports that a calendar element long pressed +/// on view. +/// +/// The tapped date, appointments, and element details when the long press +/// action performed on element available in the [CalendarLongPressDetails]. +/// +/// Used by[SfCalendar.onLongPress]. +typedef CalendarLongPressCallback = void Function( + CalendarLongPressDetails calendarLongPressDetails); + +/// Signature for callback that reports that +/// a calendar view selection changed on view. +/// +/// The selection changed date and resource details +/// when the selection changed action +/// performed on element available in the [CalendarSelectionDetails]. +/// +/// Used by[SfCalendar.onSelectionChanged]. +typedef CalendarSelectionChangedCallback = void Function( + CalendarSelectionDetails calendarSelectionDetails); diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/calendar_resource.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/calendar_resource.dart index c4d0ceaf1..44fbd65d9 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/calendar_resource.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/calendar_resource.dart @@ -1,4 +1,5 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; /// The resource data for calendar. /// @@ -61,14 +62,16 @@ part of calendar; ///} /// /// ``` -class CalendarResource { +class CalendarResource with Diagnosticable { /// Creates an resource data for [SfCalendar]. /// /// An object that contains properties to hold the detailed information /// about the data, which will be rendered in [SfCalendar]. CalendarResource( - {this.displayName, @required this.id, this.image, Color color}) - : color = color ?? Colors.lightBlue; + {this.displayName = '', + required this.id, + this.image, + this.color = Colors.lightBlue}); /// The name which displayed on the [CalendarResource] view of [SfCalendar]. /// @@ -215,5 +218,36 @@ class CalendarResource { ///} /// /// ``` - final ImageProvider image; + final ImageProvider? image; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + final CalendarResource resource = other; + return resource.displayName == displayName && + resource.id == id && + resource.image == image && + resource.color == color; + } + + @override + int get hashCode { + return hashValues(displayName, id, image, color); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + + properties.add(ColorProperty('color', color)); + properties.add(StringProperty('displayName', displayName)); + properties.add(DiagnosticsProperty('id', id)); + properties.add(DiagnosticsProperty('image', image)); + } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/resource_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/resource_view.dart index 6afc5088e..acc9ff68a 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/resource_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/resource_view.dart @@ -1,7 +1,15 @@ -part of calendar; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_calendar/src/calendar/common/calendar_view_helper.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; -class _ResourceContainer extends CustomPainter { - _ResourceContainer( +import '../settings/resource_view_settings.dart'; +import 'calendar_resource.dart'; + +/// Holds the resource views of the calendar. +class ResourceContainer extends CustomPainter { + /// Constructor to create the resource views of the calendar. + ResourceContainer( this.resources, this.resourceViewSettings, this.resourceItemHeight, @@ -10,22 +18,42 @@ class _ResourceContainer extends CustomPainter { this.notifier, this.isRTL, this.textScaleFactor, - this.mouseHoverPosition) + this.mouseHoverPosition, + this.imagePainterCollection) : super(repaint: notifier); - final List resources; + /// Holds the resources of the calendar. + final List? resources; + + /// Defines the customization of resource views in calendar widget. final ResourceViewSettings resourceViewSettings; + + /// Defines the item height of the resource view. final double resourceItemHeight; - final Color cellBorderColor; + + /// Defines the border color of the resource cell. + final Color? cellBorderColor; + + /// Hols the theme data of the calendar. final SfCalendarThemeData calendarTheme; + + /// Used to trigger repaint while resource decoration image loaded. final ValueNotifier notifier; + + /// Defines the direction of the calendar widget is RTL or not. final bool isRTL; + + /// Defines the scale factor for the calendar widget. final double textScaleFactor; - final Offset mouseHoverPosition; - Paint _circlePainter; - TextPainter _namePainter; + + /// Collection of images painter to paint the image. + final Map imagePainterCollection; + + /// Holds the mouse hovering position used to paint highlight. + final Offset? mouseHoverPosition; + Paint _circlePainter = Paint(); + TextPainter _namePainter = TextPainter(); final double _borderThickness = 5; - bool _isImageLoaded = false; @override void paint(Canvas canvas, Size size) { @@ -37,8 +65,6 @@ class _ResourceContainer extends CustomPainter { /// The circle height. final double actualItemHeight = resourceItemHeight * 0.80; double yPosition = 0; - _circlePainter ??= Paint(); - _namePainter ??= TextPainter(); _circlePainter.isAntiAlias = true; final double radius = actualItemHeight < actualItemWidth ? actualItemHeight / 2 @@ -49,11 +75,11 @@ class _ResourceContainer extends CustomPainter { final double lineXPosition = isRTL ? 0.5 : size.width - 0.5; canvas.drawLine(Offset(lineXPosition, 0), Offset(lineXPosition, size.height), _circlePainter); - final int count = resources.length; + final int count = resources!.length; if (resourceViewSettings.showAvatar) { for (int i = 0; i < count; i++) { canvas.save(); - final CalendarResource resource = resources[i]; + final CalendarResource resource = resources![i]; _drawResourceBorder( resource, canvas, size, actualItemHeight, yPosition, radius); _drawDisplayName( @@ -76,25 +102,22 @@ class _ResourceContainer extends CustomPainter { } } else { for (int i = 0; i < count; i++) { - final CalendarResource resource = resources[i]; + final CalendarResource resource = resources![i]; _drawResourceBackground(canvas, size, resource, yPosition); _drawDisplayName( resource, canvas, size, yPosition, actualItemHeight, radius); - if (mouseHoverPosition != null) { - _addHovering(canvas, size, yPosition); - } + _addHovering(canvas, size, yPosition); yPosition += resourceItemHeight; } } } void _addHovering(Canvas canvas, Size size, double yPosition) { - _circlePainter ??= Paint(); - if (mouseHoverPosition.dy > yPosition && - mouseHoverPosition.dy < (yPosition + resourceItemHeight)) { + if (mouseHoverPosition != null && + mouseHoverPosition!.dy > yPosition && + mouseHoverPosition!.dy < (yPosition + resourceItemHeight)) { _circlePainter.style = PaintingStyle.fill; - _circlePainter.color = (calendarTheme.brightness != null && - calendarTheme.brightness == Brightness.dark + _circlePainter.color = (calendarTheme.brightness == Brightness.dark ? Colors.white : Colors.black87) .withOpacity(0.04); @@ -118,7 +141,7 @@ class _ResourceContainer extends CustomPainter { /// Updates the text painter with the passed span. void _updateNamePainter(TextSpan span) { _namePainter.text = span; - _namePainter.textDirection = TextDirection.rtl; + _namePainter.textDirection = TextDirection.ltr; _namePainter.maxLines = 1; _namePainter.textWidthBasis = TextWidthBasis.longestLine; _namePainter.textScaleFactor = textScaleFactor; @@ -170,37 +193,49 @@ class _ResourceContainer extends CustomPainter { double innerCircleXPosition, double innerCircleWidth, double innerCircleHeight) { - final DecorationImage decorationImage = - DecorationImage(image: resource.image); final Offset offset = Offset(innerCircleXPosition, innerCircleYPosition); - final ImageConfiguration configuration = - ImageConfiguration(size: Size(innerCircleWidth, innerCircleHeight)); - final Rect rect = offset & configuration.size; + final Size size = Size(innerCircleWidth, innerCircleHeight); + final ImageConfiguration configuration = ImageConfiguration(size: size); + final Rect rect = offset & size; /// To render the image as circle. - final Path clipPath = Path()..addOval(rect); - final DecorationImagePainter imagePainter = - decorationImage.createPainter(() { - /// To draw an image we must use the onChanged callback, to repaint - /// the image, when drawing an image the image must be loaded before - /// the paint starts, if the image doesn't loaded we must repaint the - /// image, hence to handle this we have used this callback, and - /// repainted the image if the image doesn't load initially. - /// - /// Refer: [BoxPainter.onChanged]. - if (_isImageLoaded) { - return; - } - - _isImageLoaded = true; - notifier.value = !notifier.value; - }); + final Rect square = + Rect.fromCircle(center: rect.center, radius: rect.shortestSide / 2.0); + final Path clipPath = Path()..addOval(square); + final DecorationImagePainter? imagePainter = _getImagePainter(resource); + if (imagePainter == null) { + return; + } imagePainter.paint(canvas, rect, clipPath, configuration); + imagePainterCollection[resource.id] = imagePainter; + } + + DecorationImagePainter? _getImagePainter(CalendarResource resource) { + if (imagePainterCollection.isEmpty || + !imagePainterCollection.containsKey(resource.id)) { + return DecorationImage(image: resource.image!) + .createPainter(_onPainterChanged); + } else if (imagePainterCollection.containsKey(resource.id) && + !imagePainterCollection[resource.id] + .toString() + .contains(resource.image.toString())) { + imagePainterCollection[resource.id]!.dispose(); + return DecorationImage(image: resource.image!) + .createPainter(_onPainterChanged); + } + + return imagePainterCollection[resource.id]; + } - /// To ensured that the image is painter or not, if the image painted the - /// image property of [DecorationImage] must not be null, and since the - /// property is private, we have handled like this. - _isImageLoaded = !imagePainter.toString().contains('image: null'); + /// To draw an image we must use the onChanged callback, to repaint + /// the image, when drawing an image the image must be loaded before + /// the paint starts, if the image doesn't loaded we must repaint the + /// image, hence to handle this we have used this callback, and + /// repainted the image if the image doesn't load initially. + /// + /// Refer: [BoxPainter.onChanged]. + void _onPainterChanged() { + notifier.value = !notifier.value; } /// Draws the inner circle for the resource with the short term of the @@ -250,21 +285,24 @@ class _ResourceContainer extends CustomPainter { } @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _ResourceContainer oldWidget = oldDelegate; + bool shouldRepaint(ResourceContainer oldDelegate) { + final ResourceContainer oldWidget = oldDelegate; return oldWidget.resourceItemHeight != resourceItemHeight || - oldWidget.resources != resources || + !CalendarViewHelper.isResourceCollectionEqual( + oldWidget.resources, resources) || oldWidget.resourceViewSettings != resourceViewSettings || - oldWidget._isImageLoaded != _isImageLoaded || oldWidget.mouseHoverPosition != mouseHoverPosition; } List _getSemanticsBuilder(Size size) { final List semanticsBuilder = []; + if (resources == null) { + return semanticsBuilder; + } double top = 0; - for (int j = 0; j < resources.length; j++) { - final CalendarResource resource = resources[j]; + for (int j = 0; j < resources!.length; j++) { + final CalendarResource resource = resources![j]; semanticsBuilder.add(CustomPainterSemantics( rect: Rect.fromLTWH(0, top, size.width, resourceItemHeight), properties: SemanticsProperties( @@ -290,11 +328,10 @@ class _ResourceContainer extends CustomPainter { } @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _ResourceContainer oldWidget = oldDelegate; + bool shouldRebuildSemantics(ResourceContainer oldDelegate) { + final ResourceContainer oldWidget = oldDelegate; return oldWidget.resourceItemHeight != resourceItemHeight || oldWidget.resources != resources || - oldWidget.resourceViewSettings != resourceViewSettings || - oldWidget._isImageLoaded != _isImageLoaded; + oldWidget.resourceViewSettings != resourceViewSettings; } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/scroll_view/custom_scroll_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/scroll_view/custom_scroll_view.dart deleted file mode 100644 index 72cd14316..000000000 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/scroll_view/custom_scroll_view.dart +++ /dev/null @@ -1,2478 +0,0 @@ -part of calendar; - -@immutable -class _CustomScrollView extends StatefulWidget { - const _CustomScrollView( - this.calendar, - this.view, - this.width, - this.height, - this.agendaSelectedDate, - this.isRTL, - this.locale, - this.calendarTheme, - this.specialRegions, - this.blackoutDates, - this.controller, - this.removePicker, - this.resourcePanelScrollController, - this.textScaleFactor, - this.isMobilePlatform, - this.fadeInController, - {this.updateCalendarState, - this.getCalendarState}); - - final SfCalendar calendar; - final CalendarView view; - final double width; - final double height; - final bool isRTL; - final String locale; - final SfCalendarThemeData calendarTheme; - final CalendarController controller; - final _UpdateCalendarState updateCalendarState; - final VoidCallback removePicker; - final _UpdateCalendarState getCalendarState; - final ValueNotifier agendaSelectedDate; - final List specialRegions; - final ScrollController resourcePanelScrollController; - final double textScaleFactor; - final bool isMobilePlatform; - final List blackoutDates; - final AnimationController fadeInController; - - @override - _CustomScrollViewState createState() => _CustomScrollViewState(); -} - -class _CustomScrollViewState extends State<_CustomScrollView> - with TickerProviderStateMixin { - // three views to arrange the view in vertical/horizontal direction and handle the swiping - _CalendarView _currentView, _nextView, _previousView; - - // the three children which to be added into the layout - List<_CalendarView> _children; - - // holds the index of the current displaying view - int _currentChildIndex; - - // _scrollStartPosition contains the touch movement starting position - // _position contains distance that the view swiped - double _scrollStartPosition, _position; - - // animation controller to control the animation - AnimationController _animationController; - - // animation handled for the view swiping - Animation _animation; - - // tween animation to handle the animation - Tween _tween; - - // Three visible dates for the three views, the dates will updated based on - // the swiping in the swipe end currentViewVisibleDates which stores the - // visible dates of the current displaying view - List _visibleDates, - _previousViewVisibleDates, - _nextViewVisibleDates, - _currentViewVisibleDates; - - /// keys maintained to access the data and methods from the calendar view - /// class. - GlobalKey<_CalendarViewState> _previousViewKey, _currentViewKey, _nextViewKey; - - _UpdateCalendarStateDetails _updateCalendarStateDetails; - - /// Appointment view maintained to update the selection when an appointment - /// selected and navigated to previous/next view. - _AppointmentView _selectedAppointmentView; - - /// Collection used to store the special regions and - /// check the special regions manipulations. - List _timeRegions; - - /// Collection used to store the resource collection and check the collection - /// manipulations(add, remove, reset). - List _resourceCollection; - - /// The variable stores the timeline view scroll start position used to - /// decide the scroll as timeline scroll or scroll view on scroll update. - double _timelineScrollStartPosition = 0; - - /// The variable used to store the scroll start position to calculate the - /// scroll difference on scroll update. - double _timelineStartPosition = 0; - - /// Boolean value used to trigger the horizontal end animation when user - /// stops the scroll at middle. - bool _isNeedTimelineScrollEnd = false; - - /// Used to perform the drag or scroll in timeline view. - Drag _drag; - - FocusNode _focusNode; - - @override - void initState() { - _previousViewKey = GlobalKey<_CalendarViewState>(); - _currentViewKey = GlobalKey<_CalendarViewState>(); - _nextViewKey = GlobalKey<_CalendarViewState>(); - _focusNode = FocusNode(); - if (widget.controller != null) { - widget.controller._forward = widget.isRTL - ? _moveToPreviousViewWithAnimation - : _moveToNextViewWithAnimation; - widget.controller._backward = widget.isRTL - ? _moveToNextViewWithAnimation - : _moveToPreviousViewWithAnimation; - } - - _updateCalendarStateDetails = _UpdateCalendarStateDetails(); - _currentChildIndex = 1; - _updateVisibleDates(); - _animationController = AnimationController( - duration: const Duration(milliseconds: 250), - vsync: this, - animationBehavior: AnimationBehavior.normal); - _tween = Tween(begin: 0.0, end: 0.1); - _animation = _tween.animate(CurvedAnimation( - parent: _animationController, - curve: Curves.ease, - )) - ..addListener(animationListener); - - _timeRegions = _cloneList(widget.specialRegions); - _resourceCollection = _cloneList(widget.calendar.dataSource?.resources); - - super.initState(); - } - - @override - void didUpdateWidget(_CustomScrollView oldWidget) { - if (oldWidget.controller != widget.controller && - widget.controller != null) { - widget.controller._forward = widget.isRTL - ? _moveToPreviousViewWithAnimation - : _moveToNextViewWithAnimation; - widget.controller._backward = widget.isRTL - ? _moveToNextViewWithAnimation - : _moveToPreviousViewWithAnimation; - - if (!_isSameTimeSlot(oldWidget.controller.selectedDate, - widget.controller.selectedDate) || - !_isSameTimeSlot(_updateCalendarStateDetails._selectedDate, - widget.controller.selectedDate)) { - _selectResourceProgrammatically(); - } - } - - if (oldWidget.view != widget.view) { - _children.clear(); - - /// Switching timeline view from non timeline view or non timeline view - /// from timeline view creates the scroll layout as new because we handle - /// the scrolling touch for timeline view in this widget, so current - /// widget tree differ on timeline and non timeline views, so it creates - /// new widget tree. - if (_isTimelineView(widget.view) != _isTimelineView(oldWidget.view)) { - _currentChildIndex = 1; - } - - _updateVisibleDates(); - _position = 0; - } - - if ((widget.calendar.monthViewSettings.navigationDirection != - oldWidget.calendar.monthViewSettings.navigationDirection) || - widget.calendar.scheduleViewMonthHeaderBuilder != - oldWidget.calendar.scheduleViewMonthHeaderBuilder || - widget.calendar.monthCellBuilder != - oldWidget.calendar.monthCellBuilder || - widget.width != oldWidget.width || - widget.height != oldWidget.height || - widget.textScaleFactor != oldWidget.textScaleFactor) { - _position = null; - _children.clear(); - } - - if (!_isTimeRegionsEquals(widget.specialRegions, _timeRegions)) { - _timeRegions = _cloneList(widget.specialRegions); - _position = null; - _children.clear(); - } - - if ((widget.view == CalendarView.month || - widget.view == CalendarView.timelineMonth) && - widget.blackoutDates != oldWidget.blackoutDates) { - _children.clear(); - if (!_animationController.isAnimating) { - _position = 0; - } - } - - /// Check and re renders the views if the resource collection changed. - if (_isTimelineView(widget.view) && - !_isResourceCollectionEqual( - widget.calendar.dataSource?.resources, _resourceCollection)) { - _updateSelectedResourceIndex(); - _resourceCollection = _cloneList(widget.calendar.dataSource?.resources); - _position = 0; - _children.clear(); - } - - //// condition to check and update the view when the settings changed, it will check each and every property of settings - //// to avoid unwanted repainting - if (oldWidget.calendar.timeSlotViewSettings != - widget.calendar.timeSlotViewSettings || - oldWidget.calendar.monthViewSettings != - widget.calendar.monthViewSettings || - oldWidget.calendar.blackoutDatesTextStyle != - widget.calendar.blackoutDatesTextStyle || - oldWidget.calendar.resourceViewSettings != - widget.calendar.resourceViewSettings || - oldWidget.calendar.viewHeaderStyle != widget.calendar.viewHeaderStyle || - oldWidget.calendar.viewHeaderHeight != - widget.calendar.viewHeaderHeight || - oldWidget.calendar.todayHighlightColor != - widget.calendar.todayHighlightColor || - oldWidget.calendar.cellBorderColor != widget.calendar.cellBorderColor || - oldWidget.calendarTheme != widget.calendarTheme || - oldWidget.locale != widget.locale || - oldWidget.calendar.selectionDecoration != - widget.calendar.selectionDecoration) { - final bool isTimelineView = _isTimelineView(widget.view); - if (widget.view != CalendarView.month && - (oldWidget.calendar.timeSlotViewSettings.timeInterval != - widget.calendar.timeSlotViewSettings.timeInterval || - (!isTimelineView && - oldWidget.calendar.timeSlotViewSettings.timeIntervalHeight != - widget - .calendar.timeSlotViewSettings.timeIntervalHeight) || - (isTimelineView && - oldWidget.calendar.timeSlotViewSettings.timeIntervalWidth != - widget - .calendar.timeSlotViewSettings.timeIntervalWidth))) { - if (_currentChildIndex == 0) { - _previousViewKey.currentState._retainScrolledDateTime(); - } else if (_currentChildIndex == 1) { - _currentViewKey.currentState._retainScrolledDateTime(); - } else if (_currentChildIndex == 2) { - _nextViewKey.currentState._retainScrolledDateTime(); - } - } - _children.clear(); - _position = 0; - } - - if (widget.calendar.monthViewSettings.numberOfWeeksInView != - oldWidget.calendar.monthViewSettings.numberOfWeeksInView || - widget.calendar.timeSlotViewSettings.nonWorkingDays != - oldWidget.calendar.timeSlotViewSettings.nonWorkingDays || - widget.calendar.firstDayOfWeek != oldWidget.calendar.firstDayOfWeek || - widget.isRTL != oldWidget.isRTL) { - _updateVisibleDates(); - _position = 0; - } - - if ((widget.calendar.minDate != null && - !_isSameTimeSlot( - widget.calendar.minDate, oldWidget.calendar.minDate)) || - (widget.calendar.maxDate != null && - !_isSameTimeSlot( - widget.calendar.maxDate, oldWidget.calendar.maxDate))) { - _updateVisibleDates(); - _position = 0; - } - - if (_isTimelineView(widget.view) != _isTimelineView(oldWidget.view)) { - _children.clear(); - } - - /// position set as zero to maintain the existing scroll position in - /// timeline view - if (_isTimelineView(widget.view) && - (oldWidget.calendar.backgroundColor != - widget.calendar.backgroundColor || - oldWidget.calendar.headerStyle != widget.calendar.headerStyle) && - _position != null) { - _position = 0; - } - - if (widget.controller == oldWidget.controller && - widget.controller != null) { - if (oldWidget.controller.displayDate != widget.controller.displayDate || - !isSameDate(_updateCalendarStateDetails._currentDate, - widget.controller.displayDate)) { - _updateCalendarStateDetails._currentDate = - widget.controller.displayDate; - _updateVisibleDates(); - _updateMoveToDate(); - _position = 0; - } - - if (!_isSameTimeSlot(oldWidget.controller.selectedDate, - widget.controller.selectedDate) || - !_isSameTimeSlot(_updateCalendarStateDetails._selectedDate, - widget.controller.selectedDate)) { - _updateCalendarStateDetails._selectedDate = - widget.controller.selectedDate; - _selectResourceProgrammatically(); - _updateSelection(); - _position = 0; - } - } - - super.didUpdateWidget(oldWidget); - } - - @override - Widget build(BuildContext context) { - if (!_isTimelineView(widget.view) && widget.view != CalendarView.month) { - _updateScrollPosition(); - } - - double leftPosition, rightPosition, topPosition, bottomPosition; - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.horizontal || - widget.view != CalendarView.month) { - leftPosition = leftPosition ?? -widget.width; - rightPosition = rightPosition ?? -widget.width; - topPosition = 0; - bottomPosition = 0; - } else { - leftPosition = 0; - rightPosition = 0; - topPosition = topPosition ?? -widget.height; - bottomPosition = bottomPosition ?? -widget.height; - } - - final bool isTimelineView = _isTimelineView(widget.view); - final Widget customScrollWidget = GestureDetector( - child: CustomScrollViewerLayout( - _addViews(), - widget.view != CalendarView.month || - widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.horizontal - ? CustomScrollDirection.horizontal - : CustomScrollDirection.vertical, - _position, - _currentChildIndex), - onTapDown: (TapDownDetails details) { - if (!_focusNode.hasFocus) { - _focusNode.requestFocus(); - } - }, - onHorizontalDragStart: isTimelineView ? null : _onHorizontalStart, - onHorizontalDragUpdate: isTimelineView ? null : _onHorizontalUpdate, - onHorizontalDragEnd: isTimelineView ? null : _onHorizontalEnd, - onVerticalDragStart: widget.view == CalendarView.month && - widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical - ? _onVerticalStart - : null, - onVerticalDragUpdate: widget.view == CalendarView.month && - widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical - ? _onVerticalUpdate - : null, - onVerticalDragEnd: widget.view == CalendarView.month && - widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical - ? _onVerticalEnd - : null, - ); - - return Stack( - children: [ - Positioned( - left: leftPosition, - right: rightPosition, - bottom: bottomPosition, - top: topPosition, - child: RawKeyboardListener( - focusNode: _focusNode, - onKey: _onKeyDown, - child: isTimelineView - ? Listener( - onPointerSignal: _handlePointerSignal, - child: RawGestureDetector( - gestures: { - HorizontalDragGestureRecognizer: - GestureRecognizerFactoryWithHandlers< - HorizontalDragGestureRecognizer>( - () => HorizontalDragGestureRecognizer(), - (HorizontalDragGestureRecognizer instance) { - instance..onUpdate = _handleDragUpdate; - instance..onStart = _handleDragStart; - instance..onEnd = _handleDragEnd; - instance..onCancel = _handleDragCancel; - }, - ) - }, - behavior: HitTestBehavior.opaque, - child: customScrollWidget), - ) - : customScrollWidget, - )), - ], - ); - } - - @override - void dispose() { - if (_animationController != null) { - _animationController.dispose(); - _animationController = null; - } - - if (_animation != null) { - _animation.removeListener(animationListener); - } - - super.dispose(); - } - - /// Get the scroll layout current child view state based on its visible dates. - GlobalKey<_CalendarViewState> _getCurrentViewByVisibleDates() { - _CalendarView view; - for (int i = 0; i < _children.length; i++) { - final _CalendarView currentView = _children[i]; - if (currentView.visibleDates == _currentViewVisibleDates) { - view = currentView; - break; - } - } - - if (view == null) { - return null; - } - return view.key; - } - - /// Handle start of the scroll, set the scroll start position and check - /// the start position as start or end of timeline scroll controller. - /// If the timeline view scroll starts at min or max scroll position then - /// move the previous view to end of the scroll or move the next view to - /// start of the scroll and set the drag as timeline scroll controller drag. - void _handleDragStart(DragStartDetails details) { - if (!_isTimelineView(widget.view)) { - return; - } - final GlobalKey<_CalendarViewState> viewKey = - _getCurrentViewByVisibleDates(); - _timelineScrollStartPosition = - viewKey.currentState._scrollController.position.pixels; - _timelineStartPosition = details.globalPosition.dx; - _isNeedTimelineScrollEnd = false; - - /// If the timeline view scroll starts at min or max scroll position then - /// move the previous view to end of the scroll or move the next view to - /// start of the scroll - if (_timelineScrollStartPosition >= - viewKey.currentState._scrollController.position.maxScrollExtent) { - _positionTimelineView(); - } else if (_timelineScrollStartPosition <= - viewKey.currentState._scrollController.position.minScrollExtent) { - _positionTimelineView(); - } - - /// Set the drag as timeline scroll controller drag. - if (viewKey.currentState._scrollController.hasClients && - viewKey.currentState._scrollController.position != null) { - _drag = viewKey.currentState._scrollController.position - .drag(details, _disposeDrag); - } - } - - /// Handles the scroll update, if the scroll moves after the timeline max - /// scroll position or before the timeline min scroll position then check the - /// scroll start position if it is start or end of the timeline scroll view - /// then pass the touch to custom scroll view and set the timeline view - /// drag as null; - void _handleDragUpdate(DragUpdateDetails details) { - if (!_isTimelineView(widget.view)) { - return; - } - final GlobalKey<_CalendarViewState> viewKey = - _getCurrentViewByVisibleDates(); - - /// Calculate the scroll difference by current scroll position and start - /// scroll position. - final double difference = - details.globalPosition.dx - _timelineStartPosition; - if (_timelineScrollStartPosition >= - viewKey.currentState._scrollController.position.maxScrollExtent && - ((difference < 0 && !widget.isRTL) || - (difference > 0 && widget.isRTL))) { - /// Set the scroll position as timeline scroll start position and the - /// value used on horizontal update method. - _scrollStartPosition = _timelineStartPosition; - _drag?.cancel(); - - /// Move the touch(drag) to custom scroll view. - _onHorizontalUpdate(details); - - /// Enable boolean value used to trigger the horizontal end animation on - /// drag end. - _isNeedTimelineScrollEnd = true; - - /// Remove the timeline view drag or scroll. - _disposeDrag(); - return; - } else if (_timelineScrollStartPosition <= - viewKey.currentState._scrollController.position.minScrollExtent && - ((difference > 0 && !widget.isRTL) || - (difference < 0 && widget.isRTL))) { - /// Set the scroll position as timeline scroll start position and the - /// value used on horizontal update method. - _scrollStartPosition = _timelineStartPosition; - _drag?.cancel(); - - /// Move the touch(drag) to custom scroll view. - _onHorizontalUpdate(details); - - /// Enable boolean value used to trigger the horizontal end animation on - /// drag end. - _isNeedTimelineScrollEnd = true; - - /// Remove the timeline view drag or scroll. - _disposeDrag(); - return; - } - - _drag?.update(details); - } - - /// Handle the scroll end to update the timeline view scroll or custom scroll - /// view scroll based on [_isNeedTimelineScrollEnd] value - void _handleDragEnd(DragEndDetails details) { - if (_isNeedTimelineScrollEnd) { - _isNeedTimelineScrollEnd = false; - _onHorizontalEnd(details); - return; - } - - _isNeedTimelineScrollEnd = false; - _drag?.end(details); - } - - /// Handle drag cancel related operations. - void _handleDragCancel() { - _isNeedTimelineScrollEnd = false; - _drag?.cancel(); - } - - /// Remove the drag when the touch(drag) passed to custom scroll view. - void _disposeDrag() { - _drag = null; - } - - /// Handle the pointer scroll when a pointer signal occurs over this object. - /// eg., track pad scroll. - void _handlePointerSignal(PointerSignalEvent event) { - final GlobalKey<_CalendarViewState> viewKey = - _getCurrentViewByVisibleDates(); - if (event is PointerScrollEvent && - viewKey.currentState._scrollController.position != null) { - final double scrolledPosition = - widget.isRTL ? -event.scrollDelta.dx : event.scrollDelta.dx; - final double targetScrollOffset = math.min( - math.max( - viewKey.currentState._scrollController.position.pixels + - scrolledPosition, - viewKey.currentState._scrollController.position.minScrollExtent), - viewKey.currentState._scrollController.position.maxScrollExtent); - if (targetScrollOffset != - viewKey.currentState._scrollController.position.pixels) { - viewKey.currentState._scrollController.position - .jumpTo(targetScrollOffset); - } - } - } - - void _updateVisibleDates() { - widget.getCalendarState(_updateCalendarStateDetails); - final DateTime currentDate = DateTime( - _updateCalendarStateDetails._currentDate.year, - _updateCalendarStateDetails._currentDate.month, - _updateCalendarStateDetails._currentDate.day); - final DateTime prevDate = _getPreviousViewStartDate(widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, currentDate); - final DateTime nextDate = _getNextViewStartDate(widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, currentDate); - final List nonWorkingDays = (widget.view == CalendarView.workWeek || - widget.view == CalendarView.timelineWorkWeek) - ? widget.calendar.timeSlotViewSettings.nonWorkingDays - : null; - final int visibleDatesCount = _getViewDatesCount( - widget.view, widget.calendar.monthViewSettings.numberOfWeeksInView); - - _visibleDates = getVisibleDates(currentDate, nonWorkingDays, - widget.calendar.firstDayOfWeek, visibleDatesCount); - _previousViewVisibleDates = getVisibleDates( - widget.isRTL ? nextDate : prevDate, - nonWorkingDays, - widget.calendar.firstDayOfWeek, - visibleDatesCount); - _nextViewVisibleDates = getVisibleDates(widget.isRTL ? prevDate : nextDate, - nonWorkingDays, widget.calendar.firstDayOfWeek, visibleDatesCount); - if (widget.view == CalendarView.timelineMonth) { - _visibleDates = _getCurrentMonthDates(_visibleDates); - _previousViewVisibleDates = - _getCurrentMonthDates(_previousViewVisibleDates); - _nextViewVisibleDates = _getCurrentMonthDates(_nextViewVisibleDates); - } - - _currentViewVisibleDates = _visibleDates; - _updateCalendarStateDetails._currentViewVisibleDates = - _currentViewVisibleDates; - widget.updateCalendarState(_updateCalendarStateDetails); - - if (_currentChildIndex == 0) { - _visibleDates = _nextViewVisibleDates; - _nextViewVisibleDates = _previousViewVisibleDates; - _previousViewVisibleDates = _currentViewVisibleDates; - } else if (_currentChildIndex == 1) { - _visibleDates = _currentViewVisibleDates; - } else if (_currentChildIndex == 2) { - _visibleDates = _previousViewVisibleDates; - _previousViewVisibleDates = _nextViewVisibleDates; - _nextViewVisibleDates = _currentViewVisibleDates; - } - } - - void _updateNextViewVisibleDates() { - DateTime currentViewDate = _currentViewVisibleDates[0]; - if (widget.view == CalendarView.month && - widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { - currentViewDate = _currentViewVisibleDates[ - (_currentViewVisibleDates.length / 2).truncate()]; - } - - if (widget.isRTL) { - currentViewDate = _getPreviousViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentViewDate); - } else { - currentViewDate = _getNextViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentViewDate); - } - - List dates = getVisibleDates( - currentViewDate, - widget.view == CalendarView.workWeek || - widget.view == CalendarView.timelineWorkWeek - ? widget.calendar.timeSlotViewSettings.nonWorkingDays - : null, - widget.calendar.firstDayOfWeek, - _getViewDatesCount(widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView)); - - if (widget.view == CalendarView.timelineMonth) { - dates = _getCurrentMonthDates(dates); - } - - if (_currentChildIndex == 0) { - _nextViewVisibleDates = dates; - } else if (_currentChildIndex == 1) { - _previousViewVisibleDates = dates; - } else { - _visibleDates = dates; - } - } - - void _updatePreviousViewVisibleDates() { - DateTime currentViewDate = _currentViewVisibleDates[0]; - if (widget.view == CalendarView.month && - widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { - currentViewDate = _currentViewVisibleDates[ - (_currentViewVisibleDates.length / 2).truncate()]; - } - - if (widget.isRTL) { - currentViewDate = _getNextViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentViewDate); - } else { - currentViewDate = _getPreviousViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentViewDate); - } - - List dates = getVisibleDates( - currentViewDate, - widget.view == CalendarView.workWeek || - widget.view == CalendarView.timelineWorkWeek - ? widget.calendar.timeSlotViewSettings.nonWorkingDays - : null, - widget.calendar.firstDayOfWeek, - _getViewDatesCount(widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView)); - - if (widget.view == CalendarView.timelineMonth) { - dates = _getCurrentMonthDates(dates); - } - - if (_currentChildIndex == 0) { - _visibleDates = dates; - } else if (_currentChildIndex == 1) { - _nextViewVisibleDates = dates; - } else { - _previousViewVisibleDates = dates; - } - } - - void _getCalendarViewStateDetails(_UpdateCalendarStateDetails details) { - widget.getCalendarState(_updateCalendarStateDetails); - details._currentDate = _updateCalendarStateDetails._currentDate; - details._currentViewVisibleDates = - _updateCalendarStateDetails._currentViewVisibleDates; - details._selectedDate = _updateCalendarStateDetails._selectedDate; - details._allDayPanelHeight = _updateCalendarStateDetails._allDayPanelHeight; - details._allDayAppointmentViewCollection = - _updateCalendarStateDetails._allDayAppointmentViewCollection; - details._appointments = _updateCalendarStateDetails._appointments; - details._visibleAppointments = - _updateCalendarStateDetails._visibleAppointments; - } - - void _updateCalendarViewStateDetails(_UpdateCalendarStateDetails details) { - _updateCalendarStateDetails._selectedDate = details._selectedDate; - widget.updateCalendarState(_updateCalendarStateDetails); - } - - /// Return collection of time region, in between the visible dates. - List _getRegions(List visibleDates) { - return _getVisibleRegions( - visibleDates[0], - visibleDates[visibleDates.length - 1], - _timeRegions, - widget.calendar.timeZone); - } - - List _addViews() { - _children = _children ?? <_CalendarView>[]; - - if (_children != null && _children.isEmpty) { - _previousView = _CalendarView( - widget.calendar, - widget.view, - _previousViewVisibleDates, - widget.width, - widget.height, - widget.agendaSelectedDate, - widget.locale, - widget.calendarTheme, - _getRegions(_previousViewVisibleDates), - _getDatesWithInVisibleDateRange( - widget.blackoutDates, _previousViewVisibleDates), - _focusNode, - widget.removePicker, - widget.calendar.allowViewNavigation, - widget.controller, - widget.resourcePanelScrollController, - _resourceCollection, - widget.textScaleFactor, - widget.isMobilePlatform, - key: _previousViewKey, - updateCalendarState: (_UpdateCalendarStateDetails details) { - _updateCalendarViewStateDetails(details); - }, - getCalendarState: (_UpdateCalendarStateDetails details) { - _getCalendarViewStateDetails(details); - }, - ); - _currentView = _CalendarView( - widget.calendar, - widget.view, - _visibleDates, - widget.width, - widget.height, - widget.agendaSelectedDate, - widget.locale, - widget.calendarTheme, - _getRegions(_visibleDates), - _getDatesWithInVisibleDateRange(widget.blackoutDates, _visibleDates), - _focusNode, - widget.removePicker, - widget.calendar.allowViewNavigation, - widget.controller, - widget.resourcePanelScrollController, - _resourceCollection, - widget.textScaleFactor, - widget.isMobilePlatform, - key: _currentViewKey, - updateCalendarState: (_UpdateCalendarStateDetails details) { - _updateCalendarViewStateDetails(details); - }, - getCalendarState: (_UpdateCalendarStateDetails details) { - _getCalendarViewStateDetails(details); - }, - ); - _nextView = _CalendarView( - widget.calendar, - widget.view, - _nextViewVisibleDates, - widget.width, - widget.height, - widget.agendaSelectedDate, - widget.locale, - widget.calendarTheme, - _getRegions(_nextViewVisibleDates), - _getDatesWithInVisibleDateRange( - widget.blackoutDates, _nextViewVisibleDates), - _focusNode, - widget.removePicker, - widget.calendar.allowViewNavigation, - widget.controller, - widget.resourcePanelScrollController, - _resourceCollection, - widget.textScaleFactor, - widget.isMobilePlatform, - key: _nextViewKey, - updateCalendarState: (_UpdateCalendarStateDetails details) { - _updateCalendarViewStateDetails(details); - }, - getCalendarState: (_UpdateCalendarStateDetails details) { - _getCalendarViewStateDetails(details); - }, - ); - - _children.add(_previousView); - _children.add(_currentView); - _children.add(_nextView); - return _children; - } - - widget.getCalendarState(_updateCalendarStateDetails); - final _CalendarView previousView = _updateViews( - _previousView, _previousViewKey, _previousViewVisibleDates); - final _CalendarView currentView = - _updateViews(_currentView, _currentViewKey, _visibleDates); - final _CalendarView nextView = - _updateViews(_nextView, _nextViewKey, _nextViewVisibleDates); - - //// Update views while the all day view height differ from original height, - //// else repaint the appointment painter while current child visible appointment not equals calendar visible appointment - if (_previousView != previousView) { - _previousView = previousView; - } - if (_currentView != currentView) { - _currentView = currentView; - } - if (_nextView != nextView) { - _nextView = nextView; - } - - return _children; - } - - // method to check and update the views and appointments on the swiping end - _CalendarView _updateViews(_CalendarView view, - GlobalKey<_CalendarViewState> viewKey, List visibleDates) { - final int index = _children.indexOf(view); - - final _AppointmentLayout appointmentLayout = - viewKey.currentState._appointmentLayoutKey.currentWidget; - // update the view with the visible dates on swiping end. - if (view.visibleDates != visibleDates) { - view = _CalendarView( - widget.calendar, - widget.view, - visibleDates, - widget.width, - widget.height, - widget.agendaSelectedDate, - widget.locale, - widget.calendarTheme, - _getRegions(visibleDates), - _getDatesWithInVisibleDateRange(widget.blackoutDates, visibleDates), - _focusNode, - widget.removePicker, - widget.calendar.allowViewNavigation, - widget.controller, - widget.resourcePanelScrollController, - _resourceCollection, - widget.textScaleFactor, - widget.isMobilePlatform, - key: viewKey, - updateCalendarState: (_UpdateCalendarStateDetails details) { - _updateCalendarViewStateDetails(details); - }, - getCalendarState: (_UpdateCalendarStateDetails details) { - _getCalendarViewStateDetails(details); - }, - ); - - _children[index] = view; - } // check and update the visible appointments in the view - else if (!_isCollectionEqual(appointmentLayout.visibleAppointments.value, - _updateCalendarStateDetails._visibleAppointments)) { - if (widget.view != CalendarView.month && !_isTimelineView(widget.view)) { - view = _CalendarView( - widget.calendar, - widget.view, - visibleDates, - widget.width, - widget.height, - widget.agendaSelectedDate, - widget.locale, - widget.calendarTheme, - view.regions, - view.blackoutDates, - _focusNode, - widget.removePicker, - widget.calendar.allowViewNavigation, - widget.controller, - widget.resourcePanelScrollController, - _resourceCollection, - widget.textScaleFactor, - widget.isMobilePlatform, - key: viewKey, - updateCalendarState: (_UpdateCalendarStateDetails details) { - _updateCalendarViewStateDetails(details); - }, - getCalendarState: (_UpdateCalendarStateDetails details) { - _getCalendarViewStateDetails(details); - }, - ); - _children[index] = view; - } else if (view.visibleDates == _currentViewVisibleDates) { - appointmentLayout.visibleAppointments.value = - _updateCalendarStateDetails._visibleAppointments; - if (widget.view == CalendarView.month && - widget.calendar.monthCellBuilder != null) { - viewKey.currentState._monthView.visibleAppointmentNotifier.value = - _updateCalendarStateDetails._visibleAppointments; - } - } - } - // When calendar state changed the state doesn't pass to the child of - // custom scroll view, hence to update the calendar state to the child we - // have added this. - else if (view.calendar != widget.calendar) { - /// Update the calendar view when calendar properties like blackout dates - /// dynamically changed. - view = _CalendarView( - widget.calendar, - widget.view, - visibleDates, - widget.width, - widget.height, - widget.agendaSelectedDate, - widget.locale, - widget.calendarTheme, - view.regions, - view.blackoutDates, - _focusNode, - widget.removePicker, - widget.calendar.allowViewNavigation, - widget.controller, - widget.resourcePanelScrollController, - _resourceCollection, - widget.textScaleFactor, - widget.isMobilePlatform, - key: viewKey, - updateCalendarState: (_UpdateCalendarStateDetails details) { - _updateCalendarViewStateDetails(details); - }, - getCalendarState: (_UpdateCalendarStateDetails details) { - _getCalendarViewStateDetails(details); - }, - ); - - _children[index] = view; - } - - return view; - } - - void animationListener() { - setState(() { - _position = _animation.value; - }); - } - - /// Check both the region collection as equal or not. - bool _isTimeRegionsEquals( - List regions1, List regions2) { - /// Check both instance as equal - /// eg., if both are null then its equal. - if (regions1 == regions2) { - return true; - } - - /// Check the collections are not equal based on its length - if ((regions1 != null && regions2 == null) || - (regions1 == null && regions2 != null) || - (regions1.length != regions2.length)) { - return false; - } - - /// Check each of the region is equal to another or not. - for (int i = 0; i < regions1.length; i++) { - if (regions1[i] != regions2[i]) { - return false; - } - } - - return true; - } - - /// Updates the selected date programmatically, when resource enables, in - /// this scenario the first resource cell will be selected - void _selectResourceProgrammatically() { - if (!_isTimelineView(widget.view)) { - return; - } - - for (int i = 0; i < _children.length; i++) { - final GlobalKey<_CalendarViewState> viewKey = _children[i].key; - if (_isResourceEnabled(widget.calendar.dataSource, widget.view)) { - viewKey.currentState._selectedResourceIndex = 0; - viewKey.currentState._selectionPainter.selectedResourceIndex = 0; - } else { - viewKey.currentState._selectedResourceIndex = -1; - viewKey.currentState._selectionPainter.selectedResourceIndex = -1; - } - } - } - - /// Updates the selection, when the resource enabled and the resource - /// collection modified, moves or removes the selection based on the action - /// performed. - void _updateSelectedResourceIndex() { - for (int i = 0; i < _children.length; i++) { - final GlobalKey<_CalendarViewState> viewKey = _children[i].key; - final int selectedResourceIndex = - viewKey.currentState._selectedResourceIndex; - if (selectedResourceIndex != -1) { - final Object selectedResourceId = - _resourceCollection[selectedResourceIndex].id; - final int newIndex = _getResourceIndex( - widget.calendar.dataSource?.resources, selectedResourceId); - viewKey.currentState._selectedResourceIndex = newIndex; - } - } - } - - void _updateSelection() { - widget.getCalendarState(_updateCalendarStateDetails); - final _CalendarViewState previousViewState = _previousViewKey.currentState; - final _CalendarViewState currentViewState = _currentViewKey.currentState; - final _CalendarViewState nextViewState = _nextViewKey.currentState; - previousViewState._allDaySelectionNotifier?.value = null; - currentViewState._allDaySelectionNotifier?.value = null; - nextViewState._allDaySelectionNotifier?.value = null; - previousViewState._selectionPainter.selectedDate = - _updateCalendarStateDetails._selectedDate; - nextViewState._selectionPainter.selectedDate = - _updateCalendarStateDetails._selectedDate; - currentViewState._selectionPainter.selectedDate = - _updateCalendarStateDetails._selectedDate; - previousViewState._selectionNotifier.value = - !previousViewState._selectionNotifier.value; - currentViewState._selectionNotifier.value = - !currentViewState._selectionNotifier.value; - nextViewState._selectionNotifier.value = - !nextViewState._selectionNotifier.value; - if (previousViewState._selectionPainter._appointmentView != null && - previousViewState._selectionPainter._appointmentView.appointment != - null) { - _selectedAppointmentView = _cloneAppointmentView( - previousViewState._selectionPainter._appointmentView); - } else if (currentViewState._selectionPainter._appointmentView != null && - currentViewState._selectionPainter._appointmentView.appointment != - null) { - _selectedAppointmentView = _cloneAppointmentView( - currentViewState._selectionPainter._appointmentView); - } else if (nextViewState._selectionPainter._appointmentView != null && - nextViewState._selectionPainter._appointmentView.appointment != null) { - _selectedAppointmentView = _cloneAppointmentView( - nextViewState._selectionPainter._appointmentView); - } - - previousViewState._selectionPainter._appointmentView = - _selectedAppointmentView; - nextViewState._selectionPainter._appointmentView = _selectedAppointmentView; - currentViewState._selectionPainter._appointmentView = - _selectedAppointmentView; - } - - _AppointmentView _cloneAppointmentView(_AppointmentView view) { - final _AppointmentView clonedView = _AppointmentView(); - clonedView.appointment = view.appointment; - clonedView.appointmentRect = view.appointmentRect; - return clonedView; - } - - void _updateMoveToDate() { - if (widget.view == CalendarView.month) { - return; - } - - SchedulerBinding.instance.addPostFrameCallback((_) { - if (_currentChildIndex == 0) { - _previousViewKey.currentState._scrollToPosition(); - } else if (_currentChildIndex == 1) { - _currentViewKey.currentState._scrollToPosition(); - } else if (_currentChildIndex == 2) { - _nextViewKey.currentState._scrollToPosition(); - } - }); - } - - /// Updates the current view visible dates for calendar in the swiping end - void _updateCurrentViewVisibleDates({bool isNextView = false}) { - if (isNextView) { - if (_currentChildIndex == 0) { - _currentViewVisibleDates = _visibleDates; - } else if (_currentChildIndex == 1) { - _currentViewVisibleDates = _nextViewVisibleDates; - } else { - _currentViewVisibleDates = _previousViewVisibleDates; - } - } else { - if (_currentChildIndex == 0) { - _currentViewVisibleDates = _nextViewVisibleDates; - } else if (_currentChildIndex == 1) { - _currentViewVisibleDates = _previousViewVisibleDates; - } else { - _currentViewVisibleDates = _visibleDates; - } - } - - _updateCalendarStateDetails._currentViewVisibleDates = - _currentViewVisibleDates; - if (widget.view == CalendarView.month && - widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { - final DateTime currentMonthDate = - _currentViewVisibleDates[_currentViewVisibleDates.length ~/ 2]; - _updateCalendarStateDetails._currentDate = - DateTime(currentMonthDate.year, currentMonthDate.month, 01); - } else { - _updateCalendarStateDetails._currentDate = _currentViewVisibleDates[0]; - } - - widget.updateCalendarState(_updateCalendarStateDetails); - } - - void _updateNextView() { - if (!_animationController.isCompleted) { - return; - } - - _updateSelection(); - _updateNextViewVisibleDates(); - - /// Updates the all day panel of the view, when the all day panel expanded - /// and the view swiped with the expanded all day panel, and when we swipe - /// back to the view or swipes three times will render the all day panel as - /// expanded, to collapse the all day panel in day, week and work week view, - /// we have added this condition and called the method. - if (widget.view != CalendarView.month && !_isTimelineView(widget.view)) { - _updateAllDayPanel(); - } - - setState(() { - /// Update the custom scroll layout current child index when the - /// animation ends. - if (_currentChildIndex == 0) { - _currentChildIndex = 1; - } else if (_currentChildIndex == 1) { - _currentChildIndex = 2; - } else if (_currentChildIndex == 2) { - _currentChildIndex = 0; - } - }); - - _resetPosition(); - _updateAppointmentPainter(); - } - - void _updatePreviousView() { - if (!_animationController.isCompleted) { - return; - } - - _updateSelection(); - _updatePreviousViewVisibleDates(); - - /// Updates the all day panel of the view, when the all day panel expanded - /// and the view swiped with the expanded all day panel, and when we swipe - /// back to the view or swipes three times will render the all day panel as - /// expanded, to collapse the all day panel in day, week and work week view, - /// we have added this condition and called the method. - if (widget.view != CalendarView.month && !_isTimelineView(widget.view)) { - _updateAllDayPanel(); - } - - setState(() { - /// Update the custom scroll layout current child index when the - /// animation ends. - if (_currentChildIndex == 0) { - _currentChildIndex = 2; - } else if (_currentChildIndex == 1) { - _currentChildIndex = 0; - } else if (_currentChildIndex == 2) { - _currentChildIndex = 1; - } - }); - - _resetPosition(); - _updateAppointmentPainter(); - } - - void _moveToNextViewWithAnimation() { - if (!widget.isMobilePlatform) { - _moveToNextWebViewWithAnimation(); - return; - } - - if (!_canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - return; - } - - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted || _animationController.isDismissed) { - _animationController.reset(); - } else { - return; - } - - // Handled for time line view, to move the previous and next view to it's - // start and end position accordingly - if (_isTimelineView(widget.view)) { - _positionTimelineView(isScrolledToEnd: false); - } - - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical && - widget.view == CalendarView.month) { - // update the bottom to top swiping - _tween.begin = 0; - _tween.end = -widget.height; - } else { - // update the right to left swiping - _tween.begin = 0; - _tween.end = -widget.width; - } - - _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updateNextView()); - - /// updates the current view visible dates when the view swiped - _updateCurrentViewVisibleDates(isNextView: true); - } - - void _moveToPreviousViewWithAnimation() { - if (!widget.isMobilePlatform) { - _moveToPreviousWebViewWithAnimation(); - return; - } - - if (!_canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - return; - } - - // Resets the controller to backward it again, the animation will backward - // only from the dismissed state - if (_animationController.isCompleted || _animationController.isDismissed) { - _animationController.reset(); - } else { - return; - } - - // Handled for time line view, to move the previous and next view to it's - // start and end position accordingly - if (_isTimelineView(widget.view)) { - _positionTimelineView(isScrolledToEnd: false); - } - - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical && - widget.view == CalendarView.month) { - // update the top to bottom swiping - _tween.begin = 0; - _tween.end = widget.height; - } else { - // update the left to right swiping - _tween.begin = 0; - _tween.end = widget.width; - } - - _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); - - /// updates the current view visible dates when the view swiped. - _updateCurrentViewVisibleDates(); - } - - void _moveToPreviousWebViewWithAnimation() { - if (!_canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - return; - } - - // Resets the controller to backward it again, the animation will backward - // only from the dismissed state - if (widget.fadeInController.isCompleted || - widget.fadeInController.isDismissed) { - widget.fadeInController.reset(); - } else { - return; - } - - // Handled for time line view, to move the previous and next view to it's - // start and end position accordingly - if (_isTimelineView(widget.view)) { - _positionTimelineView(isScrolledToEnd: false); - } else if (!_isTimelineView(widget.view) && - widget.view != CalendarView.month) { - _updateDayViewScrollPosition(); - } - - /// updates the current view visible dates when the view swiped. - _updateCurrentViewVisibleDates(); - _position = 0; - widget.fadeInController.forward(); - _updateSelection(); - _updatePreviousViewVisibleDates(); - - /// Updates the all day panel of the view, when the all day panel expanded - /// and the view swiped with the expanded all day panel, and when we swipe - /// back to the view or swipes three times will render the all day panel as - /// expanded, to collapse the all day panel in day, week and work week view, - /// we have added this condition and called the method. - if (widget.view != CalendarView.month && !_isTimelineView(widget.view)) { - _updateAllDayPanel(); - } - - if (_currentChildIndex == 0) { - _currentChildIndex = 2; - } else if (_currentChildIndex == 1) { - _currentChildIndex = 0; - } else if (_currentChildIndex == 2) { - _currentChildIndex = 1; - } - - _updateAppointmentPainter(); - } - - void _moveToNextWebViewWithAnimation() { - if (!_canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - return; - } - - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (widget.fadeInController.isCompleted || - widget.fadeInController.isDismissed) { - widget.fadeInController.reset(); - } else { - return; - } - - // Handled for time line view, to move the previous and next view to it's - // start and end position accordingly - if (_isTimelineView(widget.view)) { - _positionTimelineView(isScrolledToEnd: false); - } else if (!_isTimelineView(widget.view) && - widget.view != CalendarView.month) { - _updateDayViewScrollPosition(); - } - - /// updates the current view visible dates when the view swiped - _updateCurrentViewVisibleDates(isNextView: true); - - _position = 0; - widget.fadeInController.forward(); - _updateSelection(); - _updateNextViewVisibleDates(); - - /// Updates the all day panel of the view, when the all day panel expanded - /// and the view swiped with the expanded all day panel, and when we swipe - /// back to the view or swipes three times will render the all day panel as - /// expanded, to collapse the all day panel in day, week and work week view, - /// we have added this condition and called the method. - if (widget.view != CalendarView.month && !_isTimelineView(widget.view)) { - _updateAllDayPanel(); - } - - if (_currentChildIndex == 0) { - _currentChildIndex = 1; - } else if (_currentChildIndex == 1) { - _currentChildIndex = 2; - } else if (_currentChildIndex == 2) { - _currentChildIndex = 0; - } - - _updateAppointmentPainter(); - } - - // resets position to zero on the swipe end to avoid the unwanted date updates - void _resetPosition() { - SchedulerBinding.instance.addPostFrameCallback((_) { - if (_position.abs() == widget.width || _position.abs() == widget.height) { - _position = 0; - } - }); - } - - void _updateScrollPosition() { - SchedulerBinding.instance.addPostFrameCallback((_) { - if (_previousView == null || - _currentView == null || - _nextView == null || - _previousViewKey.currentState == null || - _currentViewKey.currentState == null || - _nextViewKey.currentState == null || - _previousViewKey.currentState._scrollController == null || - _currentViewKey.currentState._scrollController == null || - _nextViewKey.currentState._scrollController == null || - !_previousViewKey.currentState._scrollController.hasClients || - !_currentViewKey.currentState._scrollController.hasClients || - !_nextViewKey.currentState._scrollController.hasClients) { - return; - } - - _updateDayViewScrollPosition(); - }); - } - - /// Update the current day view view scroll position to other views. - void _updateDayViewScrollPosition() { - double scrolledPosition = 0; - if (_currentChildIndex == 0) { - scrolledPosition = _previousViewKey.currentState._scrollController.offset; - } else if (_currentChildIndex == 1) { - scrolledPosition = _currentViewKey.currentState._scrollController.offset; - } else if (_currentChildIndex == 2) { - scrolledPosition = _nextViewKey.currentState._scrollController.offset; - } - - if (_previousViewKey.currentState._scrollController.offset != - scrolledPosition && - _previousViewKey - .currentState._scrollController.position.maxScrollExtent >= - scrolledPosition) { - _previousViewKey.currentState._scrollController.jumpTo(scrolledPosition); - } - - if (_currentViewKey.currentState._scrollController.offset != - scrolledPosition && - _currentViewKey - .currentState._scrollController.position.maxScrollExtent >= - scrolledPosition) { - _currentViewKey.currentState._scrollController.jumpTo(scrolledPosition); - } - - if (_nextViewKey.currentState._scrollController.offset != - scrolledPosition && - _nextViewKey.currentState._scrollController.position.maxScrollExtent >= - scrolledPosition) { - _nextViewKey.currentState._scrollController.jumpTo(scrolledPosition); - } - } - - int _getRowOfDate( - List visibleDates, _CalendarViewState currentViewState) { - for (int i = 0; i < visibleDates.length; i++) { - if (isSameDate( - currentViewState._selectionPainter.selectedDate, visibleDates[i])) { - switch (widget.view) { - case CalendarView.day: - case CalendarView.week: - case CalendarView.workWeek: - case CalendarView.schedule: - return null; - case CalendarView.month: - return i ~/ _kNumberOfDaysInWeek; - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - return i; - } - } - } - - return null; - } - - DateTime _updateSelectedDateForRightArrow( - _CalendarView currentView, _CalendarViewState currentViewState) { - DateTime selectedDate; - - /// Condition added to move the view to next view when the selection reaches - /// the last horizontal cell of the view in day, week, workweek, month and - /// timeline month. - if (!_isTimelineView(widget.view)) { - final int visibleDatesCount = currentView.visibleDates.length; - if (isSameDate(currentView.visibleDates[visibleDatesCount - 1], - currentViewState._selectionPainter.selectedDate)) { - _moveToNextViewWithAnimation(); - } - - selectedDate = addDuration( - currentViewState._selectionPainter.selectedDate, - const Duration(days: 1)); - - /// Move to next view when the new selected date as next month date. - if (widget.view == CalendarView.month && - !_isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentView.visibleDates[visibleDatesCount ~/ 2].month, - selectedDate)) { - _moveToNextViewWithAnimation(); - } else if (widget.view == CalendarView.workWeek) { - for (int i = 0; - i < - _kNumberOfDaysInWeek - - widget.calendar.timeSlotViewSettings.nonWorkingDays.length; - i++) { - if (widget.calendar.timeSlotViewSettings.nonWorkingDays - .contains(selectedDate.weekday)) { - selectedDate = addDuration(selectedDate, const Duration(days: 1)); - } else { - break; - } - } - } - } else { - final double xPosition = widget.view == CalendarView.timelineMonth - ? 0 - : _timeToPosition( - widget.calendar, - currentViewState._selectionPainter.selectedDate, - currentViewState._timeIntervalHeight); - final int rowIndex = - _getRowOfDate(currentView.visibleDates, currentViewState); - final double singleChildWidth = - _getSingleViewWidthForTimeLineView(currentViewState); - if ((rowIndex * singleChildWidth) + - xPosition + - currentViewState._timeIntervalHeight >= - currentViewState._scrollController.offset + widget.width) { - currentViewState._scrollController.jumpTo( - currentViewState._scrollController.offset + - currentViewState._timeIntervalHeight); - } - if (widget.view == CalendarView.timelineDay && - addDuration(currentViewState._selectionPainter.selectedDate, - widget.calendar.timeSlotViewSettings.timeInterval) - .day != - currentView - .visibleDates[currentView.visibleDates.length - 1].day) { - _moveToNextViewWithAnimation(); - } - - if ((rowIndex * singleChildWidth) + - xPosition + - currentViewState._timeIntervalHeight == - currentViewState._scrollController.position.maxScrollExtent + - currentViewState._scrollController.position.viewportDimension) { - _moveToNextViewWithAnimation(); - } - - /// For timeline month view each column represents a single day, and for - /// other timeline views each column represents a given time interval, - /// hence to update the selected date for timeline month we must add a day - /// and for other timeline views we must add the given time interval. - if (widget.view == CalendarView.timelineMonth) { - selectedDate = addDuration( - currentViewState._selectionPainter.selectedDate, - const Duration(days: 1)); - } else { - selectedDate = addDuration( - currentViewState._selectionPainter.selectedDate, - widget.calendar.timeSlotViewSettings.timeInterval); - } - if (widget.view == CalendarView.timelineWorkWeek) { - for (int i = 0; - i < - _kNumberOfDaysInWeek - - widget.calendar.timeSlotViewSettings.nonWorkingDays.length; - i++) { - if (widget.calendar.timeSlotViewSettings.nonWorkingDays - .contains(selectedDate.weekday)) { - selectedDate = addDuration(selectedDate, const Duration(days: 1)); - } else { - break; - } - } - } - } - - return selectedDate; - } - - DateTime _updateSelectedDateForLeftArrow( - _CalendarView currentView, _CalendarViewState currentViewState) { - DateTime selectedDate; - if (!_isTimelineView(widget.view)) { - if (isSameDate(currentViewState.widget.visibleDates[0], - currentViewState._selectionPainter.selectedDate)) { - _moveToPreviousViewWithAnimation(); - } - selectedDate = addDuration( - currentViewState._selectionPainter.selectedDate, - const Duration(days: -1)); - - /// Move to previous view when the selected date as previous month date. - if (widget.view == CalendarView.month && - !_isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentView - .visibleDates[currentView.visibleDates.length ~/ 2].month, - selectedDate)) { - _moveToPreviousViewWithAnimation(); - } else if (widget.view == CalendarView.workWeek) { - for (int i = 0; - i < - _kNumberOfDaysInWeek - - widget.calendar.timeSlotViewSettings.nonWorkingDays.length; - i++) { - if (widget.calendar.timeSlotViewSettings.nonWorkingDays - .contains(selectedDate.weekday)) { - selectedDate = addDuration(selectedDate, const Duration(days: -1)); - } else { - break; - } - } - } - } else { - final double xPosition = widget.view == CalendarView.timelineMonth - ? 0 - : _timeToPosition( - widget.calendar, - currentViewState._selectionPainter.selectedDate, - currentViewState._timeIntervalHeight); - final int rowIndex = - _getRowOfDate(currentView.visibleDates, currentViewState); - final double singleChildWidth = - _getSingleViewWidthForTimeLineView(currentViewState); - - if ((rowIndex * singleChildWidth) + xPosition == 0) { - _moveToPreviousViewWithAnimation(); - } - - if ((rowIndex * singleChildWidth) + xPosition <= - currentViewState._scrollController.offset) { - currentViewState._scrollController.jumpTo( - currentViewState._scrollController.offset - - currentViewState._timeIntervalHeight); - } - - /// For timeline month view each column represents a single day, and for - /// other timeline views each column represents a given time interval, - /// hence to update the selected date for timeline month we must subtract - /// a day and for other timeline views we must subtract the given time - /// interval. - if (widget.view == CalendarView.timelineMonth) { - selectedDate = addDuration( - currentViewState._selectionPainter.selectedDate, - const Duration(days: -1)); - } else { - selectedDate = subtractDuration( - currentViewState._selectionPainter.selectedDate, - widget.calendar.timeSlotViewSettings.timeInterval); - } - if (widget.view == CalendarView.timelineWorkWeek) { - for (int i = 0; - i < - _kNumberOfDaysInWeek - - widget.calendar.timeSlotViewSettings.nonWorkingDays.length; - i++) { - if (widget.calendar.timeSlotViewSettings.nonWorkingDays - .contains(selectedDate.weekday)) { - selectedDate = addDuration(selectedDate, const Duration(days: -1)); - } else { - break; - } - } - } - } - - return selectedDate; - } - - DateTime _updateSelectedDateForUpArrow( - _CalendarView currentView, _CalendarViewState currentViewState) { - if (widget.view == CalendarView.month) { - final int rowIndex = - _getRowOfDate(currentView.visibleDates, currentViewState); - if (rowIndex == 0) { - return currentViewState._selectionPainter.selectedDate; - } - - DateTime selectedDate = addDuration( - currentViewState._selectionPainter.selectedDate, - const Duration(days: -_kNumberOfDaysInWeek)); - - /// Move to month start date when the new selected date as - /// previous month date. - if (!_isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentView.visibleDates[currentView.visibleDates.length ~/ 2].month, - selectedDate)) { - selectedDate = - _getMonthStartDate(currentViewState._selectionPainter.selectedDate); - } - - return selectedDate; - } else if (!_isTimelineView(widget.view)) { - final double yPosition = _timeToPosition( - widget.calendar, - currentViewState._selectionPainter.selectedDate, - currentViewState._timeIntervalHeight); - if (yPosition == 0) { - return currentViewState._selectionPainter.selectedDate; - } - if (yPosition <= currentViewState._scrollController.offset) { - currentViewState._scrollController - .jumpTo(yPosition - currentViewState._timeIntervalHeight); - } - return subtractDuration(currentViewState._selectionPainter.selectedDate, - widget.calendar.timeSlotViewSettings.timeInterval); - } else if (_isResourceEnabled(widget.calendar.dataSource, widget.view)) { - final double resourceItemHeight = _getResourceItemHeight( - widget.calendar.resourceViewSettings.size, - widget.height, - widget.calendar.resourceViewSettings, - widget.calendar.dataSource.resources.length); - - if (currentViewState._selectedResourceIndex == 0 || - currentViewState._selectedResourceIndex == -1) { - currentViewState._selectedResourceIndex = 0; - } - - currentViewState._selectedResourceIndex -= 1; - - if (currentViewState._selectedResourceIndex * resourceItemHeight <= - currentViewState._timelineViewVerticalScrollController.offset) { - currentViewState._timelineViewVerticalScrollController.jumpTo( - currentViewState._timelineViewVerticalScrollController.offset - - resourceItemHeight); - } - - return currentViewState._selectionPainter.selectedDate; - } - - return null; - } - - DateTime _updateSelectedDateForDownArrow( - _CalendarView currentView, _CalendarViewState currentViewState) { - if (widget.view == CalendarView.month) { - final int rowIndex = - _getRowOfDate(currentView.visibleDates, currentViewState); - if (rowIndex == - widget.calendar.monthViewSettings.numberOfWeeksInView - 1) { - return currentViewState._selectionPainter.selectedDate; - } - - DateTime selectedDate = addDuration( - currentViewState._selectionPainter.selectedDate, - const Duration(days: _kNumberOfDaysInWeek)); - - /// Move to month end date when the new selected date as next month date. - if (!_isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentView.visibleDates[currentView.visibleDates.length ~/ 2].month, - selectedDate)) { - selectedDate = - _getMonthEndDate(currentViewState._selectionPainter.selectedDate); - } - return selectedDate; - } else if (!_isTimelineView(widget.view)) { - final double viewHeaderHeight = - _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); - final double yPosition = _timeToPosition( - widget.calendar, - currentViewState._selectionPainter.selectedDate, - currentViewState._timeIntervalHeight); - - if (addDuration(currentViewState._selectionPainter.selectedDate, - widget.calendar.timeSlotViewSettings.timeInterval) - .day != - currentViewState._selectionPainter.selectedDate.day) { - return currentViewState._selectionPainter.selectedDate; - } - - if (yPosition + - currentViewState._timeIntervalHeight + - widget.calendar.headerHeight + - viewHeaderHeight >= - currentViewState._scrollController.offset + widget.height && - currentViewState._scrollController.offset + - currentViewState - ._scrollController.position.viewportDimension != - currentViewState._scrollController.position.maxScrollExtent) { - currentViewState._scrollController.jumpTo( - currentViewState._scrollController.offset + - currentViewState._timeIntervalHeight); - } - return addDuration(currentViewState._selectionPainter.selectedDate, - widget.calendar.timeSlotViewSettings.timeInterval); - } else if (_isResourceEnabled(widget.calendar.dataSource, widget.view)) { - final double resourceItemHeight = _getResourceItemHeight( - widget.calendar.resourceViewSettings.size, - widget.height, - widget.calendar.resourceViewSettings, - widget.calendar.dataSource.resources.length); - if (currentViewState._selectedResourceIndex == - widget.calendar.dataSource.resources.length - 1 || - currentViewState._selectedResourceIndex == -1) { - currentViewState._selectedResourceIndex = - widget.calendar.dataSource.resources.length - 1; - } - - currentViewState._selectedResourceIndex += 1; - - if (currentViewState._selectedResourceIndex * resourceItemHeight >= - currentViewState._timelineViewVerticalScrollController.offset + - currentViewState._timelineViewVerticalScrollController.position - .viewportDimension) { - currentViewState._timelineViewVerticalScrollController.jumpTo( - currentViewState._timelineViewVerticalScrollController.offset + - resourceItemHeight); - } - - return currentViewState._selectionPainter.selectedDate; - } - - return null; - } - - DateTime _updateSelectedDate(RawKeyEvent event, - _CalendarViewState currentViewState, _CalendarView currentView) { - if (event.physicalKey == PhysicalKeyboardKey.arrowRight) { - return _updateSelectedDateForRightArrow(currentView, currentViewState); - } else if (event.physicalKey == PhysicalKeyboardKey.arrowLeft) { - return _updateSelectedDateForLeftArrow(currentView, currentViewState); - } else if (event.physicalKey == PhysicalKeyboardKey.arrowUp) { - return _updateSelectedDateForUpArrow(currentView, currentViewState); - } else if (event.physicalKey == PhysicalKeyboardKey.arrowDown) { - return _updateSelectedDateForDownArrow(currentView, currentViewState); - } - - return null; - } - - /// Checks the selected date is enabled or not. - bool _isSelectedDateEnabled(DateTime date) { - if (!isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, date)) { - return false; - } - - final List blackoutDates = []; - if (_currentView.blackoutDates != null) { - blackoutDates.addAll(_currentView.blackoutDates); - } - if (_previousView.blackoutDates != null) { - blackoutDates.addAll(_previousView.blackoutDates); - } - if (_nextView.blackoutDates != null) { - blackoutDates.addAll(_nextView.blackoutDates); - } - - final List regions = []; - if (_currentView.regions != null) { - regions.addAll(_currentView.regions); - } - if (_previousView.regions != null) { - regions.addAll(_previousView.regions); - } - if (_nextView.regions != null) { - regions.addAll(_nextView.regions); - } - - if ((widget.view == CalendarView.month || - widget.view == CalendarView.timelineMonth) && - _isDateInDateCollection(blackoutDates, date)) { - return false; - } else if (widget.view != CalendarView.month) { - for (int i = 0; i < regions.length; i++) { - final TimeRegion region = regions[i]; - if (region.enablePointerInteraction || - (region._actualStartTime.isAfter(date) && - !_isSameTimeSlot(region._actualStartTime, date)) || - region._actualEndTime.isBefore(date) || - _isSameTimeSlot(region._actualEndTime, date)) { - continue; - } - - return false; - } - } - - return true; - } - - void _onKeyDown(RawKeyEvent event) { - if (event.runtimeType != RawKeyDownEvent) { - return; - } - - widget.removePicker(); - _CalendarViewState currentVisibleViewState; - _CalendarView currentVisibleView; - if (_currentChildIndex == 0) { - currentVisibleViewState = _previousViewKey.currentState; - currentVisibleView = _previousView; - } else if (_currentChildIndex == 1) { - currentVisibleViewState = _currentViewKey.currentState; - currentVisibleView = _currentView; - } else if (_currentChildIndex == 2) { - currentVisibleViewState = _nextViewKey.currentState; - currentVisibleView = _nextView; - } - - if (currentVisibleViewState._selectionPainter.selectedDate != null && - isDateWithInDateRange( - currentVisibleViewState.widget.visibleDates[0], - currentVisibleViewState.widget.visibleDates[ - currentVisibleViewState.widget.visibleDates.length - 1], - currentVisibleViewState._selectionPainter.selectedDate)) { - final DateTime selectedDate = _updateSelectedDate( - event, currentVisibleViewState, currentVisibleView); - - if (selectedDate == null) { - return; - } - - if (!_isSelectedDateEnabled(selectedDate)) { - return; - } - - if (widget.view == CalendarView.month) { - widget.agendaSelectedDate.value = selectedDate; - } - - _updateCalendarStateDetails._selectedDate = selectedDate; - currentVisibleViewState._selectionPainter.selectedDate = selectedDate; - currentVisibleViewState._selectionPainter._appointmentView = null; - currentVisibleViewState._selectionPainter.selectedResourceIndex = - currentVisibleViewState._selectedResourceIndex; - currentVisibleViewState._selectionNotifier.value = - !currentVisibleViewState._selectionNotifier.value; - - widget.updateCalendarState(_updateCalendarStateDetails); - } - } - - void _positionTimelineView({bool isScrolledToEnd = true}) { - final _CalendarViewState previousViewState = _previousViewKey.currentState; - final _CalendarViewState currentViewState = _currentViewKey.currentState; - final _CalendarViewState nextViewState = _nextViewKey.currentState; - if (widget.isRTL) { - if (_currentChildIndex == 0) { - currentViewState._scrollController.jumpTo(isScrolledToEnd - ? currentViewState._scrollController.position.maxScrollExtent - : 0); - nextViewState._scrollController.jumpTo(0); - } else if (_currentChildIndex == 1) { - nextViewState._scrollController.jumpTo(isScrolledToEnd - ? nextViewState._scrollController.position.maxScrollExtent - : 0); - previousViewState._scrollController.jumpTo(0); - } else if (_currentChildIndex == 2) { - previousViewState._scrollController.jumpTo(isScrolledToEnd - ? previousViewState._scrollController.position.maxScrollExtent - : 0); - currentViewState._scrollController.jumpTo(0); - } - } else { - if (_currentChildIndex == 0) { - nextViewState._scrollController.jumpTo(isScrolledToEnd - ? nextViewState._scrollController.position.maxScrollExtent - : 0); - currentViewState._scrollController.jumpTo(0); - } else if (_currentChildIndex == 1) { - previousViewState._scrollController.jumpTo(isScrolledToEnd - ? previousViewState._scrollController.position.maxScrollExtent - : 0); - nextViewState._scrollController.jumpTo(0); - } else if (_currentChildIndex == 2) { - currentViewState._scrollController.jumpTo(isScrolledToEnd - ? currentViewState._scrollController.position.maxScrollExtent - : 0); - previousViewState._scrollController.jumpTo(0); - } - } - } - - void _onHorizontalStart(DragStartDetails dragStartDetails) { - widget.removePicker(); - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.horizontal || - widget.view != CalendarView.month) { - _scrollStartPosition = dragStartDetails.globalPosition.dx; - } - - // Handled for time line view, to move the previous and next view to it's - // start and end position accordingly - if (_isTimelineView(widget.view)) { - _positionTimelineView(); - } - } - - void _onHorizontalUpdate(DragUpdateDetails dragUpdateDetails) { - widget.removePicker(); - - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.horizontal || - widget.view != CalendarView.month) { - final double difference = - dragUpdateDetails.globalPosition.dx - _scrollStartPosition; - if (difference < 0 && - !_canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - _position = 0; - return; - } else if (difference > 0 && - !_canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - _position = 0; - return; - } - _position = difference; - _clearSelection(); - setState(() { - /* Updates the widget navigated distance and moves the widget - in the custom scroll view */ - }); - } - } - - void _onHorizontalEnd(DragEndDetails dragEndDetails) { - widget.removePicker(); - - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.horizontal || - widget.view != CalendarView.month) { - // condition to check and update the right to left swiping - if (-_position >= widget.width / 2) { - _tween.begin = _position; - _tween.end = -widget.width; - - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted && _position != _tween.end) { - _animationController.reset(); - } - - _animationController - .forward() - .then((dynamic value) => _updateNextView()); - - /// updates the current view visible dates when the view swiped in - /// right to left direction - _updateCurrentViewVisibleDates(isNextView: true); - } - // fling the view from right to left - else if (-dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { - if (!_canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - _position = 0; - setState(() { - /* Completes the swiping and rearrange the children position in the - custom scroll view */ - }); - return; - } - - _tween.begin = _position; - _tween.end = -widget.width; - - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted && _position != _tween.end) { - _animationController.reset(); - } - - _animationController - .fling(velocity: 5.0, animationBehavior: AnimationBehavior.normal) - .then((dynamic value) => _updateNextView()); - - /// updates the current view visible dates when fling the view in - /// right to left direction - _updateCurrentViewVisibleDates(isNextView: true); - } - // condition to check and update the left to right swiping - else if (_position >= widget.width / 2) { - _tween.begin = _position; - _tween.end = widget.width; - - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } - - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); - - /// updates the current view visible dates when the view swiped in - /// left to right direction - _updateCurrentViewVisibleDates(); - } - // fling the view from left to right - else if (dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { - if (!_canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - _position = 0; - setState(() { - /* Completes the swiping and rearrange the children position in the - custom scroll view */ - }); - return; - } - - _tween.begin = _position; - _tween.end = widget.width; - - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted && _position != _tween.end) { - _animationController.reset(); - } - - _animationController - .fling(velocity: 5.0, animationBehavior: AnimationBehavior.normal) - .then((dynamic value) => _updatePreviousView()); - - /// updates the current view visible dates when fling the view in - /// left to right direction - _updateCurrentViewVisibleDates(); - } - // condition to check and revert the right to left swiping - else if (_position.abs() <= widget.width / 2) { - _tween.begin = _position; - _tween.end = 0.0; - - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted && _position != _tween.end) { - _animationController.reset(); - } - - _animationController.forward(); - } - } - } - - void _onVerticalStart(DragStartDetails dragStartDetails) { - widget.removePicker(); - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical && - !_isTimelineView(widget.view)) { - _scrollStartPosition = dragStartDetails.globalPosition.dy; - } - } - - void _onVerticalUpdate(DragUpdateDetails dragUpdateDetails) { - widget.removePicker(); - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical && - !_isTimelineView(widget.view)) { - final double difference = - dragUpdateDetails.globalPosition.dy - _scrollStartPosition; - if (difference < 0 && - !_canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays)) { - _position = 0; - return; - } else if (difference > 0 && - !_canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays)) { - _position = 0; - return; - } - _position = difference; - setState(() { - /* Updates the widget navigated distance and moves the widget - in the custom scroll view */ - }); - } - } - - void _onVerticalEnd(DragEndDetails dragEndDetails) { - widget.removePicker(); - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical && - !_isTimelineView(widget.view)) { - // condition to check and update the bottom to top swiping - if (-_position >= widget.height / 2) { - _tween.begin = _position; - _tween.end = -widget.height; - - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } - - _animationController - .forward() - .then((dynamic value) => _updateNextView()); - - /// updates the current view visible dates when the view swiped in - /// bottom to top direction - _updateCurrentViewVisibleDates(isNextView: true); - } - // fling the view to bottom to top - else if (-dragEndDetails.velocity.pixelsPerSecond.dy > widget.height) { - if (!_canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays)) { - _position = 0; - setState(() { - /* Completes the swiping and rearrange the children position in the - custom scroll view */ - }); - return; - } - - _tween.begin = _position; - _tween.end = -widget.height; - - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } - - _animationController - .fling(velocity: 5.0, animationBehavior: AnimationBehavior.normal) - .then((dynamic value) => _updateNextView()); - - /// updates the current view visible dates when fling the view in - /// bottom to top direction - _updateCurrentViewVisibleDates(isNextView: true); - } - // condition to check and update the top to bottom swiping - else if (_position >= widget.height / 2) { - _tween.begin = _position; - _tween.end = widget.height; - - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } - - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); - - /// updates the current view visible dates when the view swiped in - /// top to bottom direction - _updateCurrentViewVisibleDates(); - } - // fling the view to top to bottom - else if (dragEndDetails.velocity.pixelsPerSecond.dy > widget.height) { - if (!_canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays)) { - _position = 0; - setState(() { - /* Completes the swiping and rearrange the children position in the - custom scroll view */ - }); - return; - } - - _tween.begin = _position; - _tween.end = widget.height; - - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } - - _animationController - .fling(velocity: 5.0, animationBehavior: AnimationBehavior.normal) - .then((dynamic value) => _updatePreviousView()); - - /// updates the current view visible dates when fling the view in - /// top to bottom direction - _updateCurrentViewVisibleDates(); - } - // condition to check and revert the bottom to top swiping - else if (_position.abs() <= widget.height / 2) { - _tween.begin = _position; - _tween.end = 0.0; - - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } - - _animationController.forward(); - } - } - } - - void _clearSelection() { - widget.getCalendarState(_updateCalendarStateDetails); - for (int i = 0; i < _children.length; i++) { - final GlobalKey<_CalendarViewState> viewKey = _children[i].key; - if (viewKey.currentState._selectionPainter.selectedDate != - _updateCalendarStateDetails._selectedDate) { - viewKey.currentState._selectionPainter.selectedDate = - _updateCalendarStateDetails._selectedDate; - viewKey.currentState._selectionNotifier.value = - !viewKey.currentState._selectionNotifier.value; - } - } - } - - /// Updates the all day panel of the view, when the all day panel expanded and - /// the view swiped to next or previous view with the expanded all day panel, - /// it will be collapsed. - void _updateAllDayPanel() { - GlobalKey<_CalendarViewState> viewKey; - if (_currentChildIndex == 0) { - viewKey = _previousViewKey; - } else if (_currentChildIndex == 1) { - viewKey = _currentViewKey; - } else if (_currentChildIndex == 2) { - viewKey = _nextViewKey; - } - if (viewKey.currentState._expanderAnimationController?.status == - AnimationStatus.completed) { - viewKey.currentState._expanderAnimationController?.reset(); - } - viewKey.currentState._isExpanded = false; - } - - /// Method to clear the appointments in the previous/next view - void _updateAppointmentPainter() { - for (int i = 0; i < _children.length; i++) { - final _CalendarView view = _children[i]; - final GlobalKey<_CalendarViewState> viewKey = view.key; - if (widget.view == CalendarView.month && - widget.calendar.monthCellBuilder != null) { - if (view.visibleDates == _currentViewVisibleDates) { - widget.getCalendarState(_updateCalendarStateDetails); - if (!_isCollectionEqual( - viewKey.currentState._monthView.visibleAppointmentNotifier.value, - _updateCalendarStateDetails._visibleAppointments)) { - viewKey.currentState._monthView.visibleAppointmentNotifier.value = - _updateCalendarStateDetails._visibleAppointments; - } - } else { - if (!_isEmptyList(viewKey - .currentState._monthView.visibleAppointmentNotifier.value)) { - viewKey.currentState._monthView.visibleAppointmentNotifier.value = - null; - } - } - } else { - final _AppointmentLayout appointmentLayout = - viewKey.currentState._appointmentLayoutKey.currentWidget; - if (view.visibleDates == _currentViewVisibleDates) { - widget.getCalendarState(_updateCalendarStateDetails); - if (!_isCollectionEqual(appointmentLayout.visibleAppointments.value, - _updateCalendarStateDetails._visibleAppointments)) { - appointmentLayout.visibleAppointments.value = - _updateCalendarStateDetails._visibleAppointments; - } - } else { - if (!_isEmptyList(appointmentLayout.visibleAppointments.value)) { - appointmentLayout.visibleAppointments.value = null; - } - } - } - } - } -} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/header_style.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/header_style.dart index 1c8e26567..0c2fbd02d 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/header_style.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/header_style.dart @@ -1,4 +1,5 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; /// Sets the style for customizing the [SfCalendar] header view. /// @@ -21,7 +22,7 @@ part of calendar; ///} /// ``` @immutable -class CalendarHeaderStyle { +class CalendarHeaderStyle with Diagnosticable { /// Creates a header style for calendar. /// /// The properties allows to customize the header view of [SfCalendar]. @@ -48,7 +49,7 @@ class CalendarHeaderStyle { /// ); ///} /// ``` - final TextStyle textStyle; + final TextStyle? textStyle; /// How the text should be aligned horizontally in [SfCalendar] header view. /// @@ -89,7 +90,7 @@ class CalendarHeaderStyle { /// ); ///} /// ``` - final Color backgroundColor; + final Color? backgroundColor; @override bool operator ==(dynamic other) { @@ -106,12 +107,16 @@ class CalendarHeaderStyle { otherStyle.backgroundColor == backgroundColor; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(EnumProperty('textAlign', textAlign)); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + properties.add(DiagnosticsProperty('textStyle', textStyle)); + } + @override int get hashCode { - return hashValues( - textStyle, - textAlign, - backgroundColor, - ); + return hashValues(textStyle, textAlign, backgroundColor); } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/month_view_settings.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/month_view_settings.dart index 3a938a902..31ef396a6 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/month_view_settings.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/month_view_settings.dart @@ -1,4 +1,6 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import '../common/enums.dart'; /// The settings have properties which allow to customize the month view of /// the [SfCalendar]. @@ -27,7 +29,7 @@ part of calendar; /// /// ``` @immutable -class MonthViewSettings { +class MonthViewSettings with Diagnosticable { /// Creates a Month view settings for calendar. /// /// The properties allows to customize the month view of [SfCalendar]. @@ -39,14 +41,10 @@ class MonthViewSettings { this.navigationDirection = MonthNavigationDirection.horizontal, this.dayFormat = 'EE', this.agendaItemHeight = -1, - bool showTrailingAndLeadingDates, - double agendaViewHeight, - MonthCellStyle monthCellStyle, - AgendaStyle agendaStyle}) - : monthCellStyle = monthCellStyle ?? const MonthCellStyle(), - showTrailingAndLeadingDates = showTrailingAndLeadingDates ?? true, - agendaStyle = agendaStyle ?? const AgendaStyle(), - agendaViewHeight = agendaViewHeight ?? -1; + this.showTrailingAndLeadingDates = true, + this.agendaViewHeight = -1, + this.monthCellStyle = const MonthCellStyle(), + this.agendaStyle = const AgendaStyle()}); /// Formats the text in the [SfCalendar] month view view header. /// @@ -482,6 +480,26 @@ class MonthViewSettings { otherSetting.navigationDirection == navigationDirection; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(monthCellStyle.toDiagnosticsNode(name: 'monthCellStyle')); + properties.add(agendaStyle.toDiagnosticsNode(name: 'agendaStyle')); + properties.add(StringProperty('dayFormat', dayFormat)); + properties.add(IntProperty('numberOfWeeksInView', numberOfWeeksInView)); + properties + .add(IntProperty('appointmentDisplayCount', appointmentDisplayCount)); + properties.add(EnumProperty( + 'appointmentDisplayMode', appointmentDisplayMode)); + properties.add(EnumProperty( + 'navigationDirection', navigationDirection)); + properties.add(DoubleProperty('agendaItemHeight', agendaItemHeight)); + properties.add(DoubleProperty('agendaViewHeight', agendaViewHeight)); + properties.add(DiagnosticsProperty('showAgenda', showAgenda)); + properties.add(DiagnosticsProperty( + 'showTrailingAndLeadingDates', showTrailingAndLeadingDates)); + } + @override int get hashCode { return hashValues( @@ -537,7 +555,7 @@ class MonthViewSettings { /// } /// ``` @immutable -class AgendaStyle { +class AgendaStyle with Diagnosticable { /// Creates a agenda style for month view in calendar. /// /// The properties allows to customize the agenda view in month view of @@ -587,7 +605,7 @@ class AgendaStyle { /// ); /// } /// ``` - final TextStyle appointmentTextStyle; + final TextStyle? appointmentTextStyle; /// The text style for the text in the day text of [SfCalendar] month agenda /// view. @@ -628,7 +646,7 @@ class AgendaStyle { /// ); /// } /// ``` - final TextStyle dayTextStyle; + final TextStyle? dayTextStyle; /// The text style for the text in the date view of [SfCalendar] month agenda /// view. @@ -669,7 +687,7 @@ class AgendaStyle { /// ); /// } /// ``` - final TextStyle dateTextStyle; + final TextStyle? dateTextStyle; /// The background color to fill the background of the [SfCalendar] month /// agenda view. @@ -710,7 +728,7 @@ class AgendaStyle { /// ); /// } ///``` - final Color backgroundColor; + final Color? backgroundColor; @override bool operator ==(dynamic other) { @@ -728,6 +746,18 @@ class AgendaStyle { otherStyle.backgroundColor == backgroundColor; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty( + 'appointmentTextStyle', appointmentTextStyle)); + properties + .add(DiagnosticsProperty('dateTextStyle', dateTextStyle)); + properties + .add(DiagnosticsProperty('dayTextStyle', dayTextStyle)); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + } + @override int get hashCode { return hashValues( @@ -800,7 +830,7 @@ class AgendaStyle { /// } /// @immutable -class MonthCellStyle { +class MonthCellStyle with Diagnosticable { /// Creates a month cell style for month view in calendar. /// /// The properties allows to customize the month cell in month view of @@ -857,7 +887,7 @@ class MonthCellStyle { /// ); /// } /// ``` - final TextStyle textStyle; + final TextStyle? textStyle; /// The text style for the text in the today cell of [SfCalendar] month view. /// @@ -899,7 +929,7 @@ class MonthCellStyle { /// ``` @Deprecated('Moved the same [todayTextStyle] to SfCalendar class, use ' '[todayTextStyle] property from SfCalendar class') - final TextStyle todayTextStyle; + final TextStyle? todayTextStyle; /// The text style for the text in the trailing dates cell of [SfCalendar] /// month view. @@ -940,7 +970,7 @@ class MonthCellStyle { /// ); /// } /// ``` - final TextStyle trailingDatesTextStyle; + final TextStyle? trailingDatesTextStyle; /// The text style for the text in the leading dates cell of [SfCalendar] /// month view. @@ -981,7 +1011,7 @@ class MonthCellStyle { /// ); /// } /// ``` - final TextStyle leadingDatesTextStyle; + final TextStyle? leadingDatesTextStyle; /// The background color to fill the background of the [SfCalendar] /// month cell. @@ -1022,7 +1052,7 @@ class MonthCellStyle { /// ); /// } /// ``` - final Color backgroundColor; + final Color? backgroundColor; /// The background color to fill the background of the [SfCalendar] today /// month cell. @@ -1063,7 +1093,7 @@ class MonthCellStyle { /// ); /// } /// ``` - final Color todayBackgroundColor; + final Color? todayBackgroundColor; /// The background color to fill the background of the [SfCalendar] trailing /// dates month cell. @@ -1104,7 +1134,7 @@ class MonthCellStyle { /// ); /// } /// ``` - final Color trailingDatesBackgroundColor; + final Color? trailingDatesBackgroundColor; /// The background color to fill the background of the [SfCalendar] leading /// dates month cell. @@ -1145,7 +1175,7 @@ class MonthCellStyle { /// ); /// } /// ``` - final Color leadingDatesBackgroundColor; + final Color? leadingDatesBackgroundColor; @override bool operator ==(dynamic other) { @@ -1167,6 +1197,22 @@ class MonthCellStyle { otherStyle.leadingDatesBackgroundColor == leadingDatesBackgroundColor; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('textStyle', textStyle)); + properties.add(DiagnosticsProperty( + 'trailingDatesTextStyle', trailingDatesTextStyle)); + properties.add(DiagnosticsProperty( + 'leadingDatesTextStyle', leadingDatesTextStyle)); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + properties.add(ColorProperty('todayBackgroundColor', todayBackgroundColor)); + properties.add(ColorProperty( + 'trailingDatesBackgroundColor', trailingDatesBackgroundColor)); + properties.add(ColorProperty( + 'leadingDatesBackgroundColor', leadingDatesBackgroundColor)); + } + @override int get hashCode { return hashValues( diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/resource_view_settings.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/resource_view_settings.dart index 2fbab7795..55bc3de7b 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/resource_view_settings.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/resource_view_settings.dart @@ -1,4 +1,5 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; /// The settings have properties which allow to customize the resource view of /// the [SfCalendar]. @@ -59,7 +60,7 @@ part of calendar; /// /// ``` @immutable -class ResourceViewSettings { +class ResourceViewSettings with Diagnosticable { /// Creates a resource view settings for calendar. /// /// The properties allows to customize the resource view of [SfCalendar]. @@ -130,7 +131,7 @@ class ResourceViewSettings { ///} /// /// ``` - final TextStyle displayNameTextStyle; + final TextStyle? displayNameTextStyle; /// The size of the resource view panel in timeline views of [SfCalendar]. /// @@ -199,4 +200,36 @@ class ResourceViewSettings { /// /// ``` final bool showAvatar; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + final ResourceViewSettings otherStyle = other; + return otherStyle.size == size && + otherStyle.visibleResourceCount == visibleResourceCount && + otherStyle.showAvatar == showAvatar && + otherStyle.displayNameTextStyle == displayNameTextStyle; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty( + 'displayNameTextStyle', displayNameTextStyle)); + properties.add(DoubleProperty('size', size)); + properties.add(DiagnosticsProperty('showAvatar', showAvatar)); + properties.add(IntProperty('visibleResourceCount', visibleResourceCount)); + } + + @override + int get hashCode { + return hashValues( + size, visibleResourceCount, showAvatar, displayNameTextStyle); + } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/schedule_view_settings.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/schedule_view_settings.dart index 3bee58388..b588e7d9c 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/schedule_view_settings.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/schedule_view_settings.dart @@ -1,4 +1,5 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; /// The settings have properties which allow to customize the schedule view of /// the [SfCalendar]. @@ -26,7 +27,7 @@ part of calendar; /// /// ``` @immutable -class ScheduleViewSettings { +class ScheduleViewSettings with Diagnosticable { /// Creates a schedule view settings for calendar. /// /// The properties allows to customize the schedule view of [SfCalendar]. @@ -34,13 +35,9 @@ class ScheduleViewSettings { {this.appointmentTextStyle, this.appointmentItemHeight = -1, this.hideEmptyScheduleWeek = false, - MonthHeaderSettings monthHeaderSettings, - WeekHeaderSettings weekHeaderSettings, - DayHeaderSettings dayHeaderSettings}) - : monthHeaderSettings = - monthHeaderSettings ?? const MonthHeaderSettings(), - weekHeaderSettings = weekHeaderSettings ?? const WeekHeaderSettings(), - dayHeaderSettings = dayHeaderSettings ?? const DayHeaderSettings(); + this.monthHeaderSettings = const MonthHeaderSettings(), + this.weekHeaderSettings = const WeekHeaderSettings(), + this.dayHeaderSettings = const DayHeaderSettings()}); /// Sets the style to customize month label in [SfCalendar] schedule view. /// @@ -168,7 +165,7 @@ class ScheduleViewSettings { /// } /// /// ``` - final TextStyle appointmentTextStyle; + final TextStyle? appointmentTextStyle; /// The height for each appointment view to layout within this in schedule /// view of [SfCalendar],. @@ -230,6 +227,23 @@ class ScheduleViewSettings { otherStyle.dayHeaderSettings == dayHeaderSettings; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add( + monthHeaderSettings.toDiagnosticsNode(name: 'monthHeaderSettings')); + properties + .add(weekHeaderSettings.toDiagnosticsNode(name: 'weekHeaderSettings')); + properties + .add(dayHeaderSettings.toDiagnosticsNode(name: 'dayHeaderSettings')); + properties.add(DiagnosticsProperty( + 'appointmentTextStyle', appointmentTextStyle)); + properties + .add(DoubleProperty('appointmentItemHeight', appointmentItemHeight)); + properties.add(DiagnosticsProperty( + 'hideEmptyScheduleWeek', hideEmptyScheduleWeek)); + } + @override int get hashCode { return hashValues( @@ -271,7 +285,7 @@ class ScheduleViewSettings { /// /// ``` @immutable -class MonthHeaderSettings { +class MonthHeaderSettings with Diagnosticable { /// Creates a month header settings for schedule view in calendar. /// /// The properties allows to customize the month header in schedule view of @@ -399,7 +413,7 @@ class MonthHeaderSettings { /// } /// /// ``` - final TextStyle monthTextStyle; + final TextStyle? monthTextStyle; @override bool operator ==(dynamic other) { @@ -418,6 +432,17 @@ class MonthHeaderSettings { otherStyle.monthTextStyle == monthTextStyle; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + .add(DiagnosticsProperty('monthTextStyle', monthTextStyle)); + properties.add(DoubleProperty('height', height)); + properties.add(EnumProperty('textAlign', textAlign)); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + properties.add(StringProperty('monthFormat', monthFormat)); + } + @override int get hashCode { return hashValues( @@ -456,7 +481,7 @@ class MonthHeaderSettings { /// /// ``` @immutable -class WeekHeaderSettings { +class WeekHeaderSettings with Diagnosticable { /// Creates a week header settings for schedule view in calendar. /// /// The properties allows to customize the week header in schedule view of @@ -490,7 +515,7 @@ class WeekHeaderSettings { /// } /// /// ``` - final String startDateFormat; + final String? startDateFormat; /// Formats the week end date text in the week label of [SfCalendar] /// schedule view. @@ -513,7 +538,7 @@ class WeekHeaderSettings { /// } /// /// ``` - final String endDateFormat; + final String? endDateFormat; /// The height for week label to layout within this in [SfCalendar] schedule /// view. @@ -609,7 +634,7 @@ class WeekHeaderSettings { /// } /// /// ``` - final TextStyle weekTextStyle; + final TextStyle? weekTextStyle; @override bool operator ==(dynamic other) { @@ -622,13 +647,25 @@ class WeekHeaderSettings { final WeekHeaderSettings otherStyle = other; return otherStyle.startDateFormat == startDateFormat && - otherStyle.height == height && - otherStyle.textAlign == textAlign && - otherStyle.backgroundColor == backgroundColor && - otherStyle.endDateFormat == endDateFormat || + otherStyle.height == height && + otherStyle.textAlign == textAlign && + otherStyle.backgroundColor == backgroundColor && + otherStyle.endDateFormat == endDateFormat && otherStyle.weekTextStyle == weekTextStyle; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + .add(DiagnosticsProperty('weekTextStyle', weekTextStyle)); + properties.add(DoubleProperty('height', height)); + properties.add(EnumProperty('textAlign', textAlign)); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + properties.add(StringProperty('startDateFormat', startDateFormat)); + properties.add(StringProperty('endDateFormat', endDateFormat)); + } + @override int get hashCode { return hashValues(startDateFormat, endDateFormat, height, textAlign, @@ -669,7 +706,7 @@ class WeekHeaderSettings { /// /// ``` @immutable -class DayHeaderSettings { +class DayHeaderSettings with Diagnosticable { /// Creates a day header settings for schedule view in calendar. /// /// The properties allows to customize the day header in schedule view of @@ -750,7 +787,7 @@ class DayHeaderSettings { /// } /// /// ``` - final TextStyle dayTextStyle; + final TextStyle? dayTextStyle; /// The text style for the date text in the day label of [SfCalendar] schedule /// view. @@ -780,7 +817,7 @@ class DayHeaderSettings { /// } /// /// ``` - final TextStyle dateTextStyle; + final TextStyle? dateTextStyle; @override bool operator ==(dynamic other) { @@ -798,6 +835,17 @@ class DayHeaderSettings { otherStyle.dateTextStyle == dateTextStyle; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + .add(DiagnosticsProperty('dayTextStyle', dayTextStyle)); + properties + .add(DiagnosticsProperty('dateTextStyle', dateTextStyle)); + properties.add(DoubleProperty('width', width)); + properties.add(StringProperty('dayFormat', dayFormat)); + } + @override int get hashCode { return hashValues(dayFormat, width, dayTextStyle, dateTextStyle); diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_region.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_region.dart index 8c11d697d..4e4066b11 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_region.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_region.dart @@ -1,4 +1,7 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_datepicker/datepicker.dart' + show IterableDiagnostics; /// It is used to highlight time slots on day, week, work week /// and timeline views based on start and end time and @@ -30,26 +33,23 @@ part of calendar; /// } /// /// ``` -class TimeRegion { +class TimeRegion with Diagnosticable { /// Creates a Time region for timeslot views in calendar. /// /// The time region used to highlight and block the specific timeslots in /// timeslots view of [SfCalendar]. TimeRegion( - {DateTime startTime, - DateTime endTime, + {required this.startTime, + required this.endTime, this.text, this.recurrenceRule, this.color, - bool enablePointerInteraction, + this.enablePointerInteraction = true, this.recurrenceExceptionDates, this.resourceIds, this.timeZone, this.iconData, - this.textStyle}) - : enablePointerInteraction = enablePointerInteraction ?? true, - startTime = startTime ?? DateTime.now(), - endTime = endTime ?? DateTime.now(); + this.textStyle}); /// Used to specify the start time of the [TimeRegion]. /// @@ -139,7 +139,7 @@ class TimeRegion { /// } /// /// ``` - final String text; + final String? text; /// Used to specify the recurrence of [TimeRegion]. /// It used to recur the [TimeRegion] and it value like @@ -173,7 +173,7 @@ class TimeRegion { /// } /// /// ``` - final String recurrenceRule; + final String? recurrenceRule; /// Used to specify the background color of [TimeRegion]. /// @@ -200,7 +200,7 @@ class TimeRegion { /// } /// /// ``` - final Color color; + final Color? color; /// Used to allow or restrict the interaction of [TimeRegion]. /// @@ -266,7 +266,7 @@ class TimeRegion { /// } /// /// ``` - final String timeZone; + final String? timeZone; /// Used to specify the text style for [TimeRegion] text and icon. /// @@ -295,7 +295,7 @@ class TimeRegion { /// } /// /// ``` - final TextStyle textStyle; + final TextStyle? textStyle; /// Used to specify the icon of [TimeRegion]. /// @@ -325,7 +325,7 @@ class TimeRegion { /// } /// /// ``` - final IconData iconData; + final IconData? iconData; /// Used to restrict the occurrence for an recurrence region. /// @@ -362,7 +362,7 @@ class TimeRegion { /// } /// /// ``` - final List recurrenceExceptionDates; + final List? recurrenceExceptionDates; /// The ids of the [CalendarResource] that shares this [TimeRegion]. /// @@ -433,28 +433,22 @@ class TimeRegion { ///} /// /// ``` - final List resourceIds; - - /// Used to store the start date value with specified time zone. - DateTime _actualStartTime; - - /// Used to store the end date value with specified time zone. - DateTime _actualEndTime; + final List? resourceIds; /// Creates a copy of this [TimeRegion] but with the given fields replaced /// with the new values. TimeRegion copyWith( - {DateTime startTime, - DateTime endTime, - String text, - String recurrenceRule, - Color color, - bool enablePointerInteraction, - List recurrenceExceptionDates, - String timeZone, - IconData iconData, - TextStyle textStyle, - List resourceIds}) { + {DateTime? startTime, + DateTime? endTime, + String? text, + String? recurrenceRule, + Color? color, + bool? enablePointerInteraction, + List? recurrenceExceptionDates, + String? timeZone, + IconData? iconData, + TextStyle? textStyle, + List? resourceIds}) { return TimeRegion( startTime: startTime ?? this.startTime, endTime: endTime ?? this.endTime, @@ -490,6 +484,7 @@ class TimeRegion { region.recurrenceExceptionDates == recurrenceExceptionDates && region.iconData == iconData && region.timeZone == timeZone && + region.resourceIds == resourceIds && region.text == text; } @@ -502,9 +497,29 @@ class TimeRegion { recurrenceRule, textStyle, enablePointerInteraction, - recurrenceExceptionDates, + hashList(recurrenceExceptionDates), + hashList(resourceIds), text, iconData, timeZone); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(IterableDiagnostics(recurrenceExceptionDates) + .toDiagnosticsNode(name: 'recurrenceExceptionDates')); + properties.add(IterableDiagnostics(resourceIds) + .toDiagnosticsNode(name: 'resourceIds')); + properties.add(StringProperty('timeZone', timeZone)); + properties.add(StringProperty('recurrenceRule', recurrenceRule)); + properties.add(StringProperty('text', text)); + properties.add(ColorProperty('color', color)); + properties.add(DiagnosticsProperty('startTime', startTime)); + properties.add(DiagnosticsProperty('endTime', endTime)); + properties.add(DiagnosticsProperty('textStyle', textStyle)); + properties.add(DiagnosticsProperty('iconData', iconData)); + properties.add(DiagnosticsProperty( + 'enablePointerInteraction', enablePointerInteraction)); + } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_slot_view_settings.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_slot_view_settings.dart index 421cc506c..c7a98e991 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_slot_view_settings.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_slot_view_settings.dart @@ -1,10 +1,5 @@ -part of calendar; - -/// All day appointment views default height -const double _kAllDayLayoutHeight = 60; - -/// All day appointment height -const double _kAllDayAppointmentHeight = 20; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; /// The settings have properties which allow to customize the time slot views /// of the [SfCalendar]. @@ -37,7 +32,7 @@ const double _kAllDayAppointmentHeight = 20; /// } /// ``` @immutable -class TimeSlotViewSettings { +class TimeSlotViewSettings with Diagnosticable { /// Creates a timeslot view settings for calendar. /// /// The properties allows to customize the timeslot views of [SfCalendar]. @@ -347,7 +342,7 @@ class TimeSlotViewSettings { /// ); /// } /// ``` - final Duration minimumAppointmentDuration; + final Duration? minimumAppointmentDuration; /// Formats the date text in the view header view of [SfCalendar] time slot /// views. @@ -473,7 +468,7 @@ class TimeSlotViewSettings { /// ); /// } /// ``` - final TextStyle timeTextStyle; + final TextStyle? timeTextStyle; @override bool operator ==(dynamic other) { @@ -500,12 +495,33 @@ class TimeSlotViewSettings { otherStyle.timeTextStyle == timeTextStyle; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + .add(DiagnosticsProperty('timeTextStyle', timeTextStyle)); + properties.add(DoubleProperty('startHour', startHour)); + properties.add(DoubleProperty('endHour', endHour)); + properties.add(IterableProperty('nonWorkingDays', nonWorkingDays)); + properties.add(DiagnosticsProperty('timeInterval', timeInterval)); + properties.add(DoubleProperty('timeIntervalHeight', timeIntervalHeight)); + properties.add(DoubleProperty('timeIntervalWidth', timeIntervalWidth)); + properties.add( + DoubleProperty('timelineAppointmentHeight', timelineAppointmentHeight)); + properties.add(DiagnosticsProperty( + 'minimumAppointmentDuration', minimumAppointmentDuration)); + properties.add(DoubleProperty('timeRulerSize', timeRulerSize)); + properties.add(StringProperty('timeFormat', timeFormat)); + properties.add(StringProperty('dateFormat', dateFormat)); + properties.add(StringProperty('dayFormat', dayFormat)); + } + @override int get hashCode { return hashValues( startHour, endHour, - nonWorkingDays, + hashList(nonWorkingDays), timeInterval, timeIntervalHeight, timeIntervalWidth, diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/view_header_style.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/view_header_style.dart index ea0740da7..b41bbb61c 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/view_header_style.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/view_header_style.dart @@ -1,4 +1,5 @@ -part of calendar; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; /// Sets the style to customize [SfCalendar] view header. /// @@ -23,7 +24,7 @@ part of calendar; /// /// ``` @immutable -class ViewHeaderStyle { +class ViewHeaderStyle with Diagnosticable { /// Creates a view header style for calendar. /// /// The properties allows to customize the view header view of [SfCalendar]. @@ -50,7 +51,7 @@ class ViewHeaderStyle { /// ); /// } /// ``` - final Color backgroundColor; + final Color? backgroundColor; /// The text style for the date text in the [SfCalendar] view header view. /// @@ -78,7 +79,7 @@ class ViewHeaderStyle { /// ); /// } /// ``` - final TextStyle dateTextStyle; + final TextStyle? dateTextStyle; /// The text style for the day text in the [SfCalendar] view header view. /// @@ -100,7 +101,7 @@ class ViewHeaderStyle { /// ); /// } /// ``` - final TextStyle dayTextStyle; + final TextStyle? dayTextStyle; @override bool operator ==(dynamic other) { @@ -117,6 +118,16 @@ class ViewHeaderStyle { otherStyle.dateTextStyle == dateTextStyle; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + .add(DiagnosticsProperty('dayTextStyle', dayTextStyle)); + properties + .add(DiagnosticsProperty('dateTextStyle', dateTextStyle)); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + } + @override int get hashCode { return hashValues( diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart index 3f8fbb4c3..96066d455 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart @@ -1,39 +1,50 @@ -part of calendar; - -/// The number of days in a week -const int _kNumberOfDaysInWeek = 7; - -/// Signature for callback that reports that a current view or current visible -/// dates changes. -/// -/// The visible dates collection visible on view when the view changes available -/// in the [ViewChangedDetails]. -/// -/// Used by [SfCalendar.onViewChanged]. -typedef ViewChangedCallback = void Function( - ViewChangedDetails viewChangedDetails); - -/// Signature for callback that reports that a calendar element tapped on view. -/// -/// The tapped date, appointments, and element details when the tap action -/// performed on element available in the [CalendarTapDetails]. -/// -/// Used by[SfCalendar.onTap]. -typedef CalendarTapCallback = void Function( - CalendarTapDetails calendarTapDetails); - -/// Signature for callback that reports that a calendar element long pressed -/// on view. -/// -/// The tapped date, appointments, and element details when the long press -/// action performed on element available in the [CalendarLongPressDetails]. -/// -/// Used by[SfCalendar.onLongPress]. -typedef CalendarLongPressCallback = void Function( - CalendarLongPressDetails calendarLongPressDetails); - -typedef _UpdateCalendarState = void Function( - _UpdateCalendarStateDetails _updateCalendarStateDetails); +import 'dart:math' as math; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; + +import 'package:intl/date_symbol_data_local.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_datepicker/datepicker.dart'; +import 'package:timezone/timezone.dart'; + +import 'appointment_engine/appointment.dart'; +import 'appointment_engine/appointment_helper.dart'; +import 'appointment_engine/calendar_datasource.dart'; +import 'appointment_engine/recurrence_helper.dart'; +import 'appointment_engine/recurrence_properties.dart'; +import 'appointment_layout/agenda_view_layout.dart'; +import 'common/calendar_controller.dart'; +import 'common/calendar_view_helper.dart'; +import 'common/date_time_engine.dart'; +import 'common/enums.dart'; +import 'common/event_args.dart'; +import 'resource_view/calendar_resource.dart'; +import 'resource_view/resource_view.dart'; +import 'settings/header_style.dart'; +import 'settings/month_view_settings.dart'; +import 'settings/resource_view_settings.dart'; +import 'settings/schedule_view_settings.dart'; +import 'settings/time_region.dart'; +import 'settings/time_slot_view_settings.dart'; +import 'settings/view_header_style.dart'; +import 'views/calendar_view.dart'; + +/// Specifies the unconfirmed ripple animation duration used on custom splash. +/// The duration was unconfirmed because the ripple animation duration changed +/// based on its radius value. +const Duration _kUnconfirmedRippleSplashDuration = Duration(seconds: 1); + +/// Specifies the fade animation duration used on custom splash. +const Duration _kSplashFadeDuration = Duration(milliseconds: 500); typedef _CalendarHeaderCallback = void Function(double width); @@ -141,8 +152,8 @@ class SfCalendar extends StatefulWidget { /// /// Use [DataSource] property to set the appointments to the scheduler. SfCalendar({ - Key key, - CalendarView view, + Key? key, + this.view = CalendarView.day, this.firstDayOfWeek = 7, this.headerHeight = 40, this.viewHeaderHeight = -1, @@ -156,6 +167,7 @@ class SfCalendar extends StatefulWidget { this.onViewChanged, this.onTap, this.onLongPress, + this.onSelectionChanged, this.controller, this.appointmentTimeTextFormat, this.blackoutDates, @@ -163,60 +175,93 @@ class SfCalendar extends StatefulWidget { this.monthCellBuilder, this.appointmentBuilder, this.timeRegionBuilder, - CalendarHeaderStyle headerStyle, - ViewHeaderStyle viewHeaderStyle, - TimeSlotViewSettings timeSlotViewSettings, - ResourceViewSettings resourceViewSettings, - MonthViewSettings monthViewSettings, - DateTime initialDisplayDate, - DateTime initialSelectedDate, - ScheduleViewSettings scheduleViewSettings, - DateTime minDate, - DateTime maxDate, - TextStyle appointmentTextStyle, - bool showNavigationArrow, - bool showDatePickerButton, - bool allowViewNavigation, - double cellEndPadding, + this.headerDateFormat, + this.headerStyle = const CalendarHeaderStyle(), + this.viewHeaderStyle = const ViewHeaderStyle(), + this.timeSlotViewSettings = const TimeSlotViewSettings(), + this.resourceViewSettings = const ResourceViewSettings(), + this.monthViewSettings = const MonthViewSettings(), + DateTime? initialDisplayDate, + this.initialSelectedDate, + this.scheduleViewSettings = const ScheduleViewSettings(), + DateTime? minDate, + DateTime? maxDate, + this.appointmentTextStyle = const TextStyle( + color: Colors.white, + fontSize: 10, + fontWeight: FontWeight.w500, + fontFamily: 'Roboto'), + this.showNavigationArrow = false, + this.showDatePickerButton = false, + this.allowViewNavigation = false, + this.showCurrentTimeIndicator = true, + this.cellEndPadding = 1, + this.viewNavigationMode = ViewNavigationMode.snap, this.allowedViews, this.specialRegions, + this.loadMoreWidgetBuilder, this.blackoutDatesTextStyle, - }) : appointmentTextStyle = appointmentTextStyle ?? - TextStyle( - color: Colors.white, - fontSize: 10, - fontWeight: FontWeight.w500, - fontFamily: 'Roboto'), - headerStyle = headerStyle ?? const CalendarHeaderStyle(), - viewHeaderStyle = viewHeaderStyle ?? const ViewHeaderStyle(), - timeSlotViewSettings = - timeSlotViewSettings ?? const TimeSlotViewSettings(), - scheduleViewSettings = - scheduleViewSettings ?? const ScheduleViewSettings(), - monthViewSettings = monthViewSettings ?? const MonthViewSettings(), - resourceViewSettings = - resourceViewSettings ?? const ResourceViewSettings(), - initialSelectedDate = - controller != null && controller.selectedDate != null - ? controller.selectedDate - : initialSelectedDate, - view = controller != null && controller.view != null - ? controller.view - : (view ?? CalendarView.day), - initialDisplayDate = - controller != null && controller.displayDate != null - ? controller.displayDate - : initialDisplayDate ?? - DateTime(DateTime.now().year, DateTime.now().month, - DateTime.now().day, 08, 45, 0), - minDate = minDate ?? DateTime(0001, 01, 01), + }) : initialDisplayDate = initialDisplayDate ?? + DateTime(DateTime.now().year, DateTime.now().month, + DateTime.now().day, 08, 45, 0), + minDate = minDate ?? DateTime(01, 01, 01), maxDate = maxDate ?? DateTime(9999, 12, 31), - showNavigationArrow = showNavigationArrow ?? false, - showDatePickerButton = showDatePickerButton ?? false, - allowViewNavigation = allowViewNavigation ?? false, - cellEndPadding = cellEndPadding ?? 1, super(key: key); + /// A builder that sets the widget to display on the calendar widget when + /// the appointments are being loaded. + /// + /// This callback will be called when a view or resource collection changed, + /// and when calendar reaches start or end scroll position in schedule view. + /// With this builder, you can set widget and then initiate the process of + /// loading more appointments by calling ‘loadMoreAppointments’ callback + /// which is passed as a parameter to this builder. The ‘loadMoreAppointments’ + /// will inturn call the [CalendarDataSource.handleLoadMore' method, where you + /// have to load the appointments. + /// + /// The widget returned from this builder will be rendered based on calendar + /// widget width and height. + /// + /// Note: This callback will be called after the onViewChanged callback. + /// The widget returned from this builder will be removed from [SfCalendar] + /// when [CalendarDataSource.notifyListeners] is called. + /// + /// See also: [CalendarDataSource.handleLoadMore] + /// + /// ``` dart + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfCalendar( + /// controller: _controller, + /// dataSource: _dataSource), + /// loadMoreWidgetBuilder: (BuildContext context, + /// CalendarLoadMoreCallback loadMoreAppointments) { + /// return FutureBuilder( + /// initialData: 'loading', + /// future: loadMoreAppointments(), + /// builder: (context, snapShot) { + /// return Container( + /// height: _controller.view == CalendarView.schedule + /// ? 50 + /// : double.infinity, + /// width: double.infinity, + /// color: Colors.white38, + /// alignment: Alignment.center, + /// child: CircularProgressIndicator( + /// valueColor: + /// AlwaysStoppedAnimation(Colors.deepPurple))); + /// }, + /// ); + /// }, + /// ), + /// ); + /// } + /// + /// ``` + final LoadMoreWidgetBuilder? loadMoreWidgetBuilder; + /// The list of [CalendarView]s that should be displayed in the header for /// quick navigation. /// @@ -239,7 +284,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final List allowedViews; + final List? allowedViews; /// Determines whether view switching is allowed among [CalendarView]s on /// interaction. @@ -283,6 +328,24 @@ class SfCalendar extends StatefulWidget { /// ``` final bool showDatePickerButton; + /// Displays an indicator that shows the current time in the time slot views + /// of [SfCalendar]. By default, the indicator color matches the + /// [todayHighlightColor]. + /// + /// Defaults to `true`. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCalendar( + /// view: CalendarView.day, + /// showCurrentTimeIndicator: true), + /// ); + /// } + /// + /// ``` + final bool showCurrentTimeIndicator; + /// Defines the view for the [SfCalendar]. /// /// Defaults to `CalendarView.day`. @@ -385,7 +448,7 @@ class SfCalendar extends StatefulWidget { /// ))); /// } /// ``` - final MonthCellBuilder monthCellBuilder; + final MonthCellBuilder? monthCellBuilder; /// A builder that builds a widget, replaces the appointment view in a day, /// week, workweek, month, schedule and timeline day, week, workweek, @@ -470,7 +533,7 @@ class SfCalendar extends StatefulWidget { /// ); /// } /// ``` - final CalendarAppointmentBuilder appointmentBuilder; + final CalendarAppointmentBuilder? appointmentBuilder; /// A builder that builds a widget that replaces the time region view in day, /// week, workweek, and timeline day, week, workweek views. @@ -520,7 +583,78 @@ class SfCalendar extends StatefulWidget { /// )); /// } /// ``` - final TimeRegionBuilder timeRegionBuilder; + final TimeRegionBuilder? timeRegionBuilder; + + /// Date format of the header date text of [SfCalendar] + /// + /// The provided format must match any one of our supported skeletons. + /// If it does not match, the provided string will be used as-is. + /// The supported sets of skeletons are as follows. + /// + /// ICU Name Skeleton + /// -------- -------- + /// DAY d + /// ABBR_WEEKDAY E + /// WEEKDAY EEEE + /// ABBR_STANDALONE_MONTH LLL + /// STANDALONE_MONTH LLLL + /// NUM_MONTH M + /// NUM_MONTH_DAY Md + /// NUM_MONTH_WEEKDAY_DAY MEd + /// ABBR_MONTH MMM + /// ABBR_MONTH_DAY MMMd + /// ABBR_MONTH_WEEKDAY_DAY MMMEd + /// MONTH MMMM + /// MONTH_DAY MMMMd + /// MONTH_WEEKDAY_DAY MMMMEEEEd + /// ABBR_QUARTER QQQ + /// QUARTER QQQQ + /// YEAR y + /// YEAR_NUM_MONTH yM + /// YEAR_NUM_MONTH_DAY yMd + /// YEAR_NUM_MONTH_WEEKDAY_DAY yMEd + /// YEAR_ABBR_MONTH yMMM + /// YEAR_ABBR_MONTH_DAY yMMMd + /// YEAR_ABBR_MONTH_WEEKDAY_DAY yMMMEd + /// YEAR_MONTH yMMMM + /// YEAR_MONTH_DAY yMMMMd + /// YEAR_MONTH_WEEKDAY_DAY yMMMMEEEEd + /// YEAR_ABBR_QUARTER yQQQ + /// YEAR_QUARTER yQQQQ + /// HOUR24 H + /// HOUR24_MINUTE Hm + /// HOUR24_MINUTE_SECOND Hms + /// HOUR j + /// HOUR_MINUTE jm + /// HOUR_MINUTE_SECOND jms + /// HOUR_MINUTE_GENERIC_TZ jmv + /// HOUR_MINUTE_TZ jmz + /// HOUR_GENERIC_TZ jv + /// HOUR_TZ jz + /// MINUTE m + /// MINUTE_SECOND ms + /// SECOND s + /// + /// Defaults to null. + /// + /// See also: + /// [onViewChanged]. + /// [DateFormat]. + /// + /// ``` dart + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// headerDateFormat: 'MMM,yyy', + /// ), + /// ), + /// ); + /// } + /// ``` + final String? headerDateFormat; /// A builder that builds a widget, replace the schedule month header /// widget in calendar schedule view. @@ -545,7 +679,7 @@ class SfCalendar extends StatefulWidget { /// ))); /// } /// ``` - final ScheduleViewMonthHeaderBuilder scheduleViewMonthHeaderBuilder; + final ScheduleViewMonthHeaderBuilder? scheduleViewMonthHeaderBuilder; /// The first day of the week in the [SfCalendar]. /// @@ -594,7 +728,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final String appointmentTimeTextFormat; + final String? appointmentTimeTextFormat; /// The color which fills the border of every calendar cells in [SfCalendar]. /// @@ -615,7 +749,7 @@ class SfCalendar extends StatefulWidget { /// } /// ///``` - final Color cellBorderColor; + final Color? cellBorderColor; /// The settings have properties which allow to customize the schedule view of /// the [SfCalendar]. @@ -814,7 +948,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final Color todayHighlightColor; + final Color? todayHighlightColor; /// The text style for the today text in [SfCalendar]. /// @@ -850,7 +984,7 @@ class SfCalendar extends StatefulWidget { /// ); /// } /// ``` - final TextStyle todayTextStyle; + final TextStyle? todayTextStyle; /// The background color to fill the background of the [SfCalendar]. /// @@ -871,7 +1005,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final Color backgroundColor; + final Color? backgroundColor; /// Displays the navigation arrows on the header view of [SfCalendar]. /// @@ -897,6 +1031,29 @@ class SfCalendar extends StatefulWidget { /// ``` final bool showNavigationArrow; + /// Specifies the view navigation for [SfCalendar] ] to + /// show dates for the next or previous views. + + /// Defaults to ViewNavigationMode.snap. + + /// Not applicable when the [view] set as [CalendarView.schedule]. + /// It will not impact scrolling timeslot views, + /// [controller.forward], [controller.backward] + /// and [showNavigationArrow]. + /// + /// ``` dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCalendar( + /// view: CalendarView.day, + /// viewNavigationMode: ViewNavigationMode.snap, + /// ), + /// ); + /// } + /// + /// ``` + final ViewNavigationMode viewNavigationMode; + /// The settings have properties which allow to customize the time slot views /// of the [SfCalendar]. /// @@ -1053,7 +1210,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final List blackoutDates; + final List? blackoutDates; /// Specifies the text style for the blackout dates text in [SfCalendar], /// that can’t be selected. @@ -1087,7 +1244,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final TextStyle blackoutDatesTextStyle; + final TextStyle? blackoutDatesTextStyle; /// The decoration for the selection cells in [SfCalendar]. /// @@ -1115,7 +1272,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final Decoration selectionDecoration; + final Decoration? selectionDecoration; /// The initial date to show on the [SfCalendar]. /// @@ -1168,7 +1325,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final String timeZone; + final String? timeZone; /// The date to initially select on the [SfCalendar]. /// @@ -1188,7 +1345,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final DateTime initialSelectedDate; + final DateTime? initialSelectedDate; /// Called when the current visible date changes in [SfCalendar]. /// @@ -1221,7 +1378,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final ViewChangedCallback onViewChanged; + final ViewChangedCallback? onViewChanged; /// Called whenever the [SfCalendar] elements tapped on view. /// @@ -1247,7 +1404,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final CalendarTapCallback onTap; + final CalendarTapCallback? onTap; /// Called whenever the [SfCalendar] elements long pressed on view. /// @@ -1274,7 +1431,31 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final CalendarLongPressCallback onLongPress; + final CalendarLongPressCallback? onLongPress; + + /// Called whenever a [SfCalendar] cell is selected. + /// + /// The callback details argument contains the selected date and + /// its resource details. + /// + /// see also: + /// [initialSelectedDate], and [controller.selectedDate]. + /// + /// ```dart + /// + ///return Container( + /// child: SfCalendar( + /// view: CalendarView.timelineDay, + /// onSelectionChanged: (CalendarSelectionDetails details){ + /// DateTime date = details.date; + /// CalendarResource resource = details.resource; + /// }, + /// ), + /// ); + /// } + /// + /// ``` + final CalendarSelectionChangedCallback? onSelectionChanged; /// Used to set the [Appointment] or custom event collection through the /// [CalendarDataSource] class. @@ -1327,7 +1508,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final CalendarDataSource dataSource; + final CalendarDataSource? dataSource; /// Defines the collection of special [TimeRegion] for [SfCalendar]. /// @@ -1359,7 +1540,7 @@ class SfCalendar extends StatefulWidget { /// } /// /// ``` - final List specialRegions; + final List? specialRegions; /// An object that used for programmatic date navigation and date selection /// in [SfCalendar]. @@ -1421,7 +1602,7 @@ class SfCalendar extends StatefulWidget { /// } ///} /// ``` - final CalendarController controller; + final CalendarController? controller; /// Returns the date time collection at which the recurrence appointment will /// recur @@ -1450,8 +1631,9 @@ class SfCalendar extends StatefulWidget { /// ``` static List getRecurrenceDateTimeCollection( String rRule, DateTime recurrenceStartDate, - {DateTime specificStartDate, DateTime specificEndDate}) { - return _getRecurrenceDateTimeCollection(rRule, recurrenceStartDate, + {DateTime? specificStartDate, DateTime? specificEndDate}) { + return RecurrenceHelper.getRecurrenceDateTimeCollection( + rRule, recurrenceStartDate, specificStartDate: specificStartDate, specificEndDate: specificEndDate); } @@ -1474,7 +1656,7 @@ class SfCalendar extends StatefulWidget { /// /// ``` static RecurrenceProperties parseRRule(String rRule, DateTime recStartDate) { - return _parseRRule(rRule, recStartDate); + return RecurrenceHelper.parseRRule(rRule, recStartDate); } /// Generates the recurrence rule based on the given recurrence properties and @@ -1508,104 +1690,215 @@ class SfCalendar extends StatefulWidget { /// ``` static String generateRRule(RecurrenceProperties recurrenceProperties, DateTime appStartTime, DateTime appEndTime) { - return _generateRRule(recurrenceProperties, appStartTime, appEndTime); + return RecurrenceHelper.generateRRule( + recurrenceProperties, appStartTime, appEndTime); } @override _SfCalendarState createState() => _SfCalendarState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(headerStyle.toDiagnosticsNode(name: 'headerStyle')); + properties.add(viewHeaderStyle.toDiagnosticsNode(name: 'viewHeaderStyle')); + properties.add( + timeSlotViewSettings.toDiagnosticsNode(name: 'timeSlotViewSettings')); + properties.add( + resourceViewSettings.toDiagnosticsNode(name: 'resourceViewSettings')); + properties + .add(monthViewSettings.toDiagnosticsNode(name: 'monthViewSettings')); + properties.add( + scheduleViewSettings.toDiagnosticsNode(name: 'scheduleViewSettings')); + if (dataSource != null) { + properties.add(dataSource!.toDiagnosticsNode(name: 'dataSource')); + } + properties + .add(DiagnosticsProperty('controller', controller)); + properties.add(DiagnosticsProperty( + 'appointmentTextStyle', appointmentTextStyle)); + properties.add(DiagnosticsProperty( + 'blackoutDatesTextStyle', blackoutDatesTextStyle)); + properties + .add(DiagnosticsProperty('todayTextStyle', todayTextStyle)); + properties.add(EnumProperty('view', view)); + properties.add( + DiagnosticsProperty('allowViewNavigation', allowViewNavigation)); + properties.add( + DiagnosticsProperty('showNavigationArrow', showNavigationArrow)); + properties.add(DiagnosticsProperty( + 'viewNavigationMode', viewNavigationMode)); + properties.add(DiagnosticsProperty( + 'showDatePickerButton', showDatePickerButton)); + properties.add(DiagnosticsProperty( + 'showCurrentTimeIndicator', showCurrentTimeIndicator)); + properties.add(IntProperty('firstDayOfWeek', firstDayOfWeek)); + properties.add(DoubleProperty('headerHeight', headerHeight)); + properties.add(DoubleProperty('viewHeaderHeight', viewHeaderHeight)); + properties.add(DoubleProperty('cellEndPadding', cellEndPadding)); + properties.add( + StringProperty('appointmentTimeTextFormat', appointmentTimeTextFormat)); + properties.add(DiagnosticsProperty( + 'initialDisplayDate', initialDisplayDate)); + properties.add(DiagnosticsProperty( + 'initialSelectedDate', initialSelectedDate)); + properties.add(DiagnosticsProperty('minDate', minDate)); + properties.add(DiagnosticsProperty('maxDate', maxDate)); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + properties.add(ColorProperty('todayHighlightColor', todayHighlightColor)); + properties.add(ColorProperty('cellBorderColor', cellBorderColor)); + properties.add(DiagnosticsProperty( + 'onViewChanged', onViewChanged)); + properties.add(DiagnosticsProperty('onTap', onTap)); + properties.add(DiagnosticsProperty( + 'onLongPress', onLongPress)); + properties.add(DiagnosticsProperty( + 'onSelectionChanged', onSelectionChanged)); + properties.add(DiagnosticsProperty( + 'scheduleViewMonthHeaderBuilder', scheduleViewMonthHeaderBuilder)); + properties.add(DiagnosticsProperty( + 'monthCellBuilder', monthCellBuilder)); + properties.add(DiagnosticsProperty( + 'appointmentBuilder', appointmentBuilder)); + properties.add(DiagnosticsProperty( + 'timeRegionBuilder', timeRegionBuilder)); + properties.add(DiagnosticsProperty( + 'loadMoreWidgetBuilder', loadMoreWidgetBuilder)); + properties.add(StringProperty('headerDateFormat', headerDateFormat)); + properties.add(DiagnosticsProperty( + 'selectionDecoration', selectionDecoration)); + properties.add(StringProperty('timeZone', timeZone)); + properties.add(IterableDiagnostics(blackoutDates) + .toDiagnosticsNode(name: 'blackoutDates')); + properties.add(IterableDiagnostics(allowedViews) + .toDiagnosticsNode(name: 'allowedViews')); + properties.add(IterableDiagnostics(specialRegions) + .toDiagnosticsNode(name: 'specialRegions')); + } } class _SfCalendarState extends State with SingleTickerProviderStateMixin { - List _currentViewVisibleDates; - DateTime _currentDate, _selectedDate; - List _visibleAppointments; - List<_AppointmentView> _allDayAppointmentViewCollection = - <_AppointmentView>[]; + late List _currentViewVisibleDates; + late DateTime _currentDate; + DateTime? _selectedDate; + List _visibleAppointments = []; + List _allDayAppointmentViewCollection = []; double _allDayPanelHeight = 0; /// Used to get the scrolled position to update the header value. - ScrollController _agendaScrollController, _resourcePanelScrollController; - - ValueNotifier _agendaSelectedDate, _headerUpdateNotifier; - String _locale; - SfLocalizations _localizations; - double _minWidth, _minHeight, _textScaleFactor; - SfCalendarThemeData _calendarTheme; - ValueNotifier _headerHoverNotifier, _resourceHoverNotifier; - ValueNotifier<_ScheduleViewHoveringDetails> _agendaDateNotifier, + ScrollController? _agendaScrollController, _resourcePanelScrollController; + + late ValueNotifier _agendaSelectedDate; + ValueNotifier _headerUpdateNotifier = + ValueNotifier(null); + late String _locale; + late SfLocalizations _localizations; + late double _minWidth, _minHeight, _textScaleFactor; + late SfCalendarThemeData _calendarTheme; + late ValueNotifier _headerHoverNotifier, _resourceHoverNotifier; + late ValueNotifier _agendaDateNotifier, _agendaViewNotifier; /// Notifier to repaint the resource view if the image doesn't loaded on /// initial load. - ValueNotifier _resourceImageNotifier; + late ValueNotifier _resourceImageNotifier; /// Used to assign the forward list as center of scroll view. - Key _scheduleViewKey; + Key _scheduleViewKey = UniqueKey(); /// Used to create the new scroll view for schedule calendar view. - Key _scrollKey; + late Key _scrollKey; /// Used to store the visible dates before the display date - List _previousDates; + late List _previousDates; /// Used to store the visible dates after the display date - List _nextDates; + late List _nextDates; /// Used to store the height of each views generated by next dates. - Map _forwardWidgetHeights; + late Map _forwardWidgetHeights; /// Used to store the height of each views generated by previous dates. - Map _backwardWidgetHeights; + late Map _backwardWidgetHeights; /// Used to store the max and min visible date. - DateTime _minDate, _maxDate; + DateTime? _minDate, _maxDate; /// Used to store the agenda date view width and the value used on agenda /// view generation, tap and long press callbacks and mouse hovering. - double _agendaDateViewWidth; + late double _agendaDateViewWidth; //// Used to notify the time zone data base loaded or not. //// Example, initially appointment added on visible date changed callback then //// data source changed listener perform operation but the time zone data base //// not initialized, so it makes error. - bool _timeZoneLoaded = false; - List _appointments; - CalendarController _controller; + late bool _timeZoneLoaded = false; + List _appointments = []; + late CalendarController _controller; /// Used to identify the schedule web view size changed and reformat the /// schedule view when the UI changed to mobile UI from web UI or web UI /// to mobile UI. - double _actualWidth; + double? _actualWidth; /// Collection used to store the blackout dates and check the collection /// manipulations(add, remove). - List _blackoutDates; + List? _blackoutDates; - bool _isRTL; - CalendarView _view; - bool _showHeader; - double _calendarViewWidth; - ValueNotifier _viewChangeNotifier; + late bool _isRTL; + late CalendarView _view; + late bool _showHeader; + late double _calendarViewWidth; + late ValueNotifier _viewChangeNotifier; /// Used for hold the schedule display date value used for show nothing /// planned text on schedule view. - DateTime _scheduleDisplayDate; + late DateTime _scheduleDisplayDate; /// Used to check the current platform as mobile platform(android or iOS) - bool _isMobilePlatform; + late bool _isMobilePlatform; /// Used to check the desktop platform needs mobile platform UI. - bool _useMobilePlatformUI; + late bool _useMobilePlatformUI; /// Fade animation controller to controls fade animation - AnimationController _fadeInController; + AnimationController? _fadeInController; /// Fade animation animated on view changing and web view navigation. - Animation _fadeIn; + Animation? _fadeIn; /// Opacity of widget handles by fade animation. - double _opacity; + ValueNotifier _opacity = ValueNotifier(1); + + /// Used to identify whether the load more function triggered or not. + bool _isLoadMoreLoaded = false; + + /// Used to check whether the load more widget needed or not. In schedule + /// calendar view it denotes the bottom end load more widget. + bool _isNeedLoadMore = false; + + /// Used to check whether the top end load more needed or not in schedule + /// calendar view. + bool _isScheduleStartLoadMore = false; + + /// Holds the schedule view loading min date value. It is used only load more + /// enabled. This value updated before load more triggered and this value + /// set to [_minDate] when the load more completed. + DateTime? _scheduleMinDate; + + /// Holds the schedule view loading max date value. It is used only load more + /// enabled. This value updated before load more triggered and this value + /// set to [_maxDate] when the load more completed. + DateTime? _scheduleMaxDate; + + /// Collection used to store the resource collection and check the collection + /// manipulations(add, remove, reset). + List? _resourceCollection; + + /// The image painter collection to paint the resource images in view. + Map _imagePainterCollection = + {}; @override void initState() { @@ -1614,56 +1907,61 @@ class _SfCalendarState extends State _calendarViewWidth = 0; initializeDateFormatting(); _loadDataBase().then((bool value) => _getAppointment()); - _agendaDateNotifier = ValueNotifier<_ScheduleViewHoveringDetails>(null); - _agendaViewNotifier = ValueNotifier<_ScheduleViewHoveringDetails>(null); + _agendaDateNotifier = ValueNotifier(null); + _agendaViewNotifier = ValueNotifier(null); _resourceImageNotifier = ValueNotifier(false); - _headerHoverNotifier = ValueNotifier(null) + _headerHoverNotifier = ValueNotifier(null) ..addListener(_updateViewHeaderHover); - _resourceHoverNotifier = ValueNotifier(null) + _resourceHoverNotifier = ValueNotifier(null) ..addListener(_updateViewHeaderHover); _controller = widget.controller ?? CalendarController(); - _controller.selectedDate = widget.initialSelectedDate; - _selectedDate = widget.initialSelectedDate; - _agendaSelectedDate = ValueNotifier(widget.initialSelectedDate); + _controller.selectedDate ??= widget.initialSelectedDate; + _selectedDate = _controller.selectedDate; + _agendaSelectedDate = ValueNotifier(_selectedDate); _agendaSelectedDate.addListener(_agendaSelectedDateListener); - _currentDate = - getValidDate(widget.minDate, widget.maxDate, widget.initialDisplayDate); + _currentDate = getValidDate(widget.minDate, widget.maxDate, + _controller.displayDate ?? widget.initialDisplayDate); _controller.displayDate = _currentDate; - _scheduleDisplayDate = _controller.displayDate; - _controller._view = widget.view; - _view = _controller.view; + _scheduleDisplayDate = _controller.displayDate!; + _controller.view ??= widget.view; + _view = _controller.view!; + if (_selectedDate != null) { + _updateSelectionChangedCallback(); + } _updateCurrentVisibleDates(); widget.dataSource?.addListener(_dataSourceChangedListener); + _resourceCollection = + CalendarViewHelper.cloneList(widget.dataSource?.resources); if (_view == CalendarView.month && widget.monthViewSettings.showAgenda) { _agendaScrollController = ScrollController(initialScrollOffset: 0, keepScrollOffset: true); } - if (_isResourceEnabled(widget.dataSource, _view)) { + if (CalendarViewHelper.isResourceEnabled(widget.dataSource, _view)) { _resourcePanelScrollController = ScrollController(initialScrollOffset: 0, keepScrollOffset: true); } _controller.addPropertyChangedListener(_calendarValueChangedListener); if (_view == CalendarView.schedule && - _shouldRaiseViewChangedCallback(widget.onViewChanged)) { - _raiseViewChangedCallback(widget, - visibleDates: []..add(_controller.displayDate)); + CalendarViewHelper.shouldRaiseViewChangedCallback( + widget.onViewChanged)) { + CalendarViewHelper.raiseViewChangedCallback( + widget, []..add(_controller.displayDate!)); } _initScheduleViewProperties(); - _blackoutDates = _cloneList(widget.blackoutDates); + _blackoutDates = CalendarViewHelper.cloneList(widget.blackoutDates); _viewChangeNotifier = ValueNotifier(false) ..addListener(_updateViewChangePopup); - _opacity = 1; - + _isLoadMoreLoaded = false; super.initState(); } @override void didChangeDependencies() { - _textScaleFactor = MediaQuery.of(context).textScaleFactor ?? 1.0; + _textScaleFactor = MediaQuery.of(context).textScaleFactor; // default width value will be device width when the widget placed inside a // infinity width widget _minWidth = MediaQuery.of(context).size.width; @@ -1694,13 +1992,15 @@ class _SfCalendarState extends State if (oldWidget.controller != widget.controller) { oldWidget.controller ?.removePropertyChangedListener(_calendarValueChangedListener); + _controller.removePropertyChangedListener(_calendarValueChangedListener); _controller = widget.controller ?? CalendarController(); if (widget.controller != null) { - _controller.selectedDate = widget.controller.selectedDate; - _controller.displayDate = widget.controller.displayDate ?? _currentDate; + _controller.selectedDate = widget.controller!.selectedDate; + _controller.displayDate = + widget.controller!.displayDate ?? _currentDate; _scheduleDisplayDate = - widget.controller.displayDate ?? _scheduleDisplayDate; - _controller.view = widget.controller.view ?? _view; + widget.controller!.displayDate ?? _scheduleDisplayDate; + _controller.view = widget.controller!.view ?? _view; } else { _controller.selectedDate = widget.initialSelectedDate; _currentDate = getValidDate( @@ -1708,27 +2008,29 @@ class _SfCalendarState extends State _controller.displayDate = _currentDate; _controller.view = widget.view; } - _selectedDate = widget.controller.selectedDate; - _view = _controller.view; + _selectedDate = _controller.selectedDate; + _view = _controller.view!; _controller.addPropertyChangedListener(_calendarValueChangedListener); } if (oldWidget.controller == widget.controller && widget.controller != null) { - if (oldWidget.controller.selectedDate != widget.controller.selectedDate) { + if (oldWidget.controller!.selectedDate != + widget.controller!.selectedDate) { _selectedDate = _controller.selectedDate; _agendaSelectedDate.value = _controller.selectedDate; - } else if (oldWidget.controller.view != widget.controller.view || - _view != widget.controller.view) { + } else if (oldWidget.controller!.view != widget.controller!.view || + _view != widget.controller!.view) { final CalendarView oldView = _view; _view = _controller.view ?? widget.view; _currentDate = getValidDate( widget.minDate, widget.maxDate, _updateCurrentDate(oldView)); _controller.displayDate = _currentDate; if (_view == CalendarView.schedule) { - if (_shouldRaiseViewChangedCallback(widget.onViewChanged)) { - _raiseViewChangedCallback(widget, - visibleDates: []..add(_controller.displayDate)); + if (CalendarViewHelper.shouldRaiseViewChangedCallback( + widget.onViewChanged)) { + CalendarViewHelper.raiseViewChangedCallback( + widget, []..add(_controller.displayDate!)); } _agendaScrollController?.removeListener(_handleScheduleViewScrolled); @@ -1743,28 +2045,48 @@ class _SfCalendarState extends State _nextDates.clear(); _backwardWidgetHeights.clear(); _forwardWidgetHeights.clear(); - WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { + WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { _handleScheduleViewScrolled(); }); } if (oldWidget.controller == widget.controller && widget.controller != null && - oldWidget.controller.displayDate != widget.controller.displayDate) { + oldWidget.controller!.displayDate != widget.controller!.displayDate) { if (_controller.displayDate != null) { _currentDate = getValidDate( widget.minDate, widget.maxDate, _controller.displayDate); } - _currentDate = _currentDate ?? - getValidDate( - widget.minDate, widget.maxDate, widget.initialDisplayDate); _controller.displayDate = _currentDate; - _scheduleDisplayDate = _controller.displayDate; + _scheduleDisplayDate = _currentDate; + } + + if ((widget.loadMoreWidgetBuilder == null && + oldWidget.loadMoreWidgetBuilder != null) || + (widget.loadMoreWidgetBuilder != null && + oldWidget.loadMoreWidgetBuilder == null)) { + _scrollKey = UniqueKey(); + _nextDates = []; + _previousDates = []; + if (_view == CalendarView.schedule) { + _headerUpdateNotifier = ValueNotifier(_scheduleDisplayDate); + } else if (widget.loadMoreWidgetBuilder != null && !_isNeedLoadMore) { + _isNeedLoadMore = true; + } + _forwardWidgetHeights = {}; + _backwardWidgetHeights = {}; + _agendaScrollController = ScrollController() + ..addListener(_handleScheduleViewScrolled); + _scheduleMaxDate = null; + _scheduleMinDate = null; + _minDate = null; + _maxDate = null; } - if (!_isDateCollectionEqual(widget.blackoutDates, _blackoutDates)) { - _blackoutDates = _cloneList(widget.blackoutDates); + if (!CalendarViewHelper.isDateCollectionEqual( + widget.blackoutDates, _blackoutDates)) { + _blackoutDates = CalendarViewHelper.cloneList(widget.blackoutDates); } if (_agendaSelectedDate.value != _selectedDate) { @@ -1781,26 +2103,26 @@ class _SfCalendarState extends State widget.minDate, widget.maxDate, _updateCurrentDate(_view)); _controller.displayDate = _currentDate; if (_view == CalendarView.schedule) { - if (_shouldRaiseViewChangedCallback(widget.onViewChanged)) { - _raiseViewChangedCallback(widget, - visibleDates: []..add(_controller.displayDate)); + if (CalendarViewHelper.shouldRaiseViewChangedCallback( + widget.onViewChanged)) { + CalendarViewHelper.raiseViewChangedCallback( + widget, []..add(_controller.displayDate!)); } _agendaScrollController?.removeListener(_handleScheduleViewScrolled); _initScheduleViewProperties(); } + } - if (_isResourceEnabled(widget.dataSource, _view) && - _resourcePanelScrollController == null) { - _resourcePanelScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); - } + if (CalendarViewHelper.isResourceEnabled(widget.dataSource, _view)) { + _resourcePanelScrollController ??= + ScrollController(initialScrollOffset: 0, keepScrollOffset: true); } if (_view == CalendarView.month && widget.monthViewSettings.showTrailingAndLeadingDates != oldWidget.monthViewSettings.showTrailingAndLeadingDates) { - _visibleAppointments = null; + _visibleAppointments = []; _updateVisibleAppointments(); } @@ -1809,19 +2131,32 @@ class _SfCalendarState extends State oldWidget.dataSource?.removeListener(_dataSourceChangedListener); widget.dataSource?.addListener(_dataSourceChangedListener); - if (_isResourceEnabled(widget.dataSource, _view)) { + if (CalendarViewHelper.isResourceEnabled(widget.dataSource, _view)) { _resourcePanelScrollController ??= ScrollController(initialScrollOffset: 0, keepScrollOffset: true); } } - if ((oldWidget.minDate != widget.minDate && widget.minDate != null) || - (oldWidget.maxDate != widget.maxDate && widget.maxDate != null)) { - _currentDate = getValidDate( - widget.minDate, widget.maxDate, widget.initialDisplayDate); + if (!CalendarViewHelper.isResourceCollectionEqual( + widget.dataSource?.resources, _resourceCollection)) { + _resourceCollection = + CalendarViewHelper.cloneList(widget.dataSource?.resources); + } + + if ((oldWidget.minDate != widget.minDate) || + (oldWidget.maxDate != widget.maxDate)) { + _currentDate = getValidDate(widget.minDate, widget.maxDate, _currentDate); if (_view == CalendarView.schedule) { _minDate = null; _maxDate = null; + if (widget.loadMoreWidgetBuilder != null && + _scheduleMinDate != null && + _scheduleMaxDate != null) { + _scheduleMinDate = + getValidDate(widget.minDate, widget.maxDate, _scheduleMinDate); + _scheduleMaxDate = + getValidDate(widget.minDate, widget.maxDate, _scheduleMaxDate); + } } } @@ -1844,7 +2179,7 @@ class _SfCalendarState extends State @override Widget build(BuildContext context) { double height; - _isRTL = _isRTLLayout(context); + _isRTL = CalendarViewHelper.isRTLLayout(context); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { _minWidth = constraints.maxWidth == double.infinity @@ -1854,22 +2189,20 @@ class _SfCalendarState extends State ? _minHeight : constraints.maxHeight; - _isMobilePlatform = _isMobileLayout(Theme.of(context).platform); - _useMobilePlatformUI = _isMobileLayoutUI(_minWidth, _isMobilePlatform); + _isMobilePlatform = + CalendarViewHelper.isMobileLayout(Theme.of(context).platform); + _useMobilePlatformUI = + CalendarViewHelper.isMobileLayoutUI(_minWidth, _isMobilePlatform); _fadeInController ??= AnimationController( duration: Duration(milliseconds: _isMobilePlatform ? 500 : 600), vsync: this) - ..addListener(() { - setState(() { - _opacity = _fadeIn.value; - }); - }); + ..addListener(_updateFadeAnimation); _fadeIn ??= Tween( begin: 0.1, end: 1, ).animate(CurvedAnimation( - parent: _fadeInController, + parent: _fadeInController!, curve: Curves.easeIn, )); @@ -1878,7 +2211,8 @@ class _SfCalendarState extends State if (_view == CalendarView.schedule && _actualWidth != null && _useMobilePlatformUI != - _isMobileLayoutUI(_actualWidth, _isMobilePlatform) && + CalendarViewHelper.isMobileLayoutUI( + _actualWidth!, _isMobilePlatform) && _nextDates.isNotEmpty) { _agendaScrollController?.removeListener(_handleScheduleViewScrolled); _initScheduleViewProperties(); @@ -1906,7 +2240,9 @@ class _SfCalendarState extends State height: _minHeight, color: widget.backgroundColor ?? _calendarTheme.backgroundColor, child: _view == CalendarView.schedule - ? addAgenda(height, _isRTL) + ? widget.loadMoreWidgetBuilder == null + ? addAgenda(height, _isRTL) + : addAgendaWithLoadMore(height, _isRTL) : _addChildren(agendaHeight, height, _minWidth, _isRTL), ), onTap: () { @@ -1919,34 +2255,50 @@ class _SfCalendarState extends State @override void dispose() { if (_agendaScrollController != null) { - _agendaScrollController.removeListener(_handleScheduleViewScrolled); - _agendaScrollController.dispose(); + _agendaScrollController!.removeListener(_handleScheduleViewScrolled); + _agendaScrollController!.dispose(); _agendaScrollController = null; } - if (_headerHoverNotifier != null) { - _headerHoverNotifier.removeListener(_updateViewHeaderHover); + if (_resourcePanelScrollController != null) { + _resourcePanelScrollController!.dispose(); + _resourcePanelScrollController = null; } - if (_agendaDateNotifier != null) { - _agendaDateNotifier.removeListener(_agendaSelectedDateListener); + _headerHoverNotifier.removeListener(_updateViewHeaderHover); + _agendaDateNotifier.removeListener(_agendaSelectedDateListener); + _resourceHoverNotifier.removeListener(_updateViewHeaderHover); + + _disposeResourceImagePainter(); + + if (widget.dataSource != null) { + widget.dataSource!.removeListener(_dataSourceChangedListener); } - if (_resourceHoverNotifier != null) { - _resourceHoverNotifier.removeListener(_updateViewHeaderHover); + if (_fadeInController != null) { + _fadeInController!.removeListener(_updateFadeAnimation); + _fadeInController!.dispose(); + _fadeInController = null; } - if (widget.dataSource != null) { - widget.dataSource.removeListener(_dataSourceChangedListener); + if (_fadeIn != null) { + _fadeIn = null; } _controller.removePropertyChangedListener(_calendarValueChangedListener); - _controller.dispose(); _viewChangeNotifier.removeListener(_updateViewChangePopup); _viewChangeNotifier.dispose(); super.dispose(); } + void _updateFadeAnimation() { + if (!mounted) { + return; + } + + _opacity.value = _fadeIn!.value; + } + /// loads the time zone data base to handle the time zone for calendar Future _loadDataBase() async { final ByteData byteData = @@ -1959,36 +2311,42 @@ class _SfCalendarState extends State /// Generates the calendar appointments from the given data source, and /// time zone details void _getAppointment() { - _appointments = _generateCalendarAppointments(widget.dataSource, widget); + _appointments = AppointmentHelper.generateCalendarAppointments( + widget.dataSource, widget.timeZone); _updateVisibleAppointments(); } /// Updates the visible appointments for the calendar // ignore: avoid_void_async void _updateVisibleAppointments() async { + if (!_timeZoneLoaded) { + return; + } if (_view != CalendarView.schedule) { final int visibleDatesCount = _currentViewVisibleDates.length; DateTime viewStartDate = _currentViewVisibleDates[0]; DateTime viewEndDate = _currentViewVisibleDates[visibleDatesCount - 1]; if (_view == CalendarView.month && - !_isLeadingAndTrailingDatesVisible( + !CalendarViewHelper.isLeadingAndTrailingDatesVisible( widget.monthViewSettings.numberOfWeeksInView, widget.monthViewSettings.showTrailingAndLeadingDates)) { final DateTime currentMonthDate = _currentViewVisibleDates[visibleDatesCount ~/ 2]; - viewStartDate = _getMonthStartDate(currentMonthDate); - viewEndDate = _getMonthEndDate(currentMonthDate); + viewStartDate = AppointmentHelper.getMonthStartDate(currentMonthDate); + viewEndDate = AppointmentHelper.getMonthEndDate(currentMonthDate); } - final List tempVisibleAppointment = + final List tempVisibleAppointment = // ignore: await_only_futures - await _getVisibleAppointments( + await AppointmentHelper.getVisibleAppointments( viewStartDate, viewEndDate, _appointments, widget.timeZone, - _view == CalendarView.month || _isTimelineView(_view)); - if (_isCollectionEqual(_visibleAppointments, tempVisibleAppointment)) { + _view == CalendarView.month || + CalendarViewHelper.isTimelineView(_view)); + if (CalendarViewHelper.isCollectionEqual( + _visibleAppointments, tempVisibleAppointment)) { if (mounted) { setState(() { // Updates the calendar widget because it trigger to change the @@ -2027,11 +2385,10 @@ class _SfCalendarState extends State } void _initScheduleViewProperties() { - _scheduleViewKey ??= UniqueKey(); _scrollKey = UniqueKey(); _nextDates = []; _previousDates = []; - _headerUpdateNotifier = ValueNotifier(_controller.displayDate); + _headerUpdateNotifier = ValueNotifier(_scheduleDisplayDate); _forwardWidgetHeights = {}; _backwardWidgetHeights = {}; @@ -2039,25 +2396,29 @@ class _SfCalendarState extends State /// Add listener for scroll view to handle the scroll view scroll position /// changes. - _agendaScrollController.addListener(_handleScheduleViewScrolled); + _agendaScrollController!.addListener(_handleScheduleViewScrolled); + _scheduleMaxDate = null; + _scheduleMinDate = null; + _minDate = null; + _maxDate = null; } /// Handle the scroll view scroll changes to update header date value. void _handleScheduleViewScrolled() { _removeDatePicker(); double widgetPosition = 0; - final double scrolledPosition = _agendaScrollController.position.pixels; + final double scrolledPosition = _agendaScrollController!.position.pixels; /// Scrolled position greater than zero then it moves to forward views. if (scrolledPosition >= 0) { for (int i = 0; i < _forwardWidgetHeights.length; i++) { - final _ScheduleViewDetails details = + final _ScheduleViewDetails? details = _forwardWidgetHeights.containsKey(i) ? _forwardWidgetHeights[i] : null; final double widgetHeight = details == null ? 0 : details._height; final double interSectionPoint = - details == null ? 0 : details._intersectPoint; + details == null ? -1 : details._intersectPoint; /// Check the scrolled position in between the view position if (scrolledPosition >= widgetPosition && @@ -2068,15 +2429,18 @@ class _SfCalendarState extends State /// tells the view does not have similar month dates. If it reaches /// the intersection point then it moves to another month date so /// update the header view date with latest date. - if (interSectionPoint != 0 && scrolledPosition > interSectionPoint) { - date = addDuration(date, const Duration(days: 6)); + if (interSectionPoint != -1 && + scrolledPosition >= interSectionPoint) { + date = addDays(date, 6); } - _currentDate = date; - if (date.month != _headerUpdateNotifier.value.month || - date.year != _headerUpdateNotifier.value.year) { - _controller.displayDate = date; - _headerUpdateNotifier.value = date; + final DateTime currentViewDate = + getValidDate(widget.minDate, widget.maxDate, date); + _currentDate = currentViewDate; + if (currentViewDate.month != _headerUpdateNotifier.value!.month || + currentViewDate.year != _headerUpdateNotifier.value!.year) { + _controller.displayDate = currentViewDate; + _headerUpdateNotifier.value = currentViewDate; } break; @@ -2087,13 +2451,13 @@ class _SfCalendarState extends State } else { /// Scrolled position less than zero then it moves to backward views. for (int i = 0; i < _backwardWidgetHeights.length; i++) { - final _ScheduleViewDetails details = + final _ScheduleViewDetails? details = _backwardWidgetHeights.containsKey(i) ? _backwardWidgetHeights[i] : null; final double widgetHeight = details == null ? 0 : details._height; final double interSectionPoint = - details == null ? 0 : details._intersectPoint; + details == null ? -1 : details._intersectPoint; /// Check the scrolled position in between the view position if (-scrolledPosition > widgetPosition && @@ -2104,16 +2468,18 @@ class _SfCalendarState extends State /// tells the view does not have similar month dates. If it reaches /// the intersection point then it moves to another month date so /// update the header view date with latest date. - if (interSectionPoint != 0 && + if (interSectionPoint != -1 && -scrolledPosition <= interSectionPoint) { - date = addDuration(date, const Duration(days: 6)); + date = addDays(date, 6); } - _currentDate = date; - if (date.month != _headerUpdateNotifier.value.month || - date.year != _headerUpdateNotifier.value.year) { - _controller.displayDate = date; - _headerUpdateNotifier.value = date; + final DateTime currentViewDate = + getValidDate(widget.minDate, widget.maxDate, date); + _currentDate = currentViewDate; + if (currentViewDate.month != _headerUpdateNotifier.value!.month || + currentViewDate.year != _headerUpdateNotifier.value!.year) { + _controller.displayDate = currentViewDate; + _headerUpdateNotifier.value = currentViewDate; } break; @@ -2122,15 +2488,70 @@ class _SfCalendarState extends State widgetPosition = widgetHeight; } } + + if (_agendaScrollController!.hasClients && + _agendaScrollController!.position.atEdge && + (_agendaScrollController!.position.minScrollExtent != 0 || + _agendaScrollController!.position.maxScrollExtent != 0) && + widget.loadMoreWidgetBuilder != null && + !_isNeedLoadMore && + !_isScheduleStartLoadMore) { + if (_agendaScrollController!.position.pixels == + _agendaScrollController!.position.minScrollExtent) { + DateTime date = AppointmentHelper.getMonthStartDate( + DateTime(_scheduleMinDate!.year, _scheduleMinDate!.month - 1)); + + if (!isSameOrAfterDate(widget.minDate, date)) { + date = widget.minDate; + } + + if (!isSameDate(_scheduleMinDate, date)) { + setState(() { + _isScheduleStartLoadMore = true; + _scheduleMinDate = date; + }); + } + } else { + DateTime date = AppointmentHelper.getMonthEndDate( + DateTime(_scheduleMaxDate!.year, _scheduleMaxDate!.month + 1)); + + if (!isSameOrBeforeDate(widget.maxDate, date)) { + date = widget.maxDate; + } + + if (!isSameDate(_scheduleMaxDate, date)) { + setState(() { + _isNeedLoadMore = true; + _scheduleMaxDate = date; + }); + } + } + } + } + + /// Method that raise the selection changed callback + /// when selected date changed programmatically. + void _updateSelectionChangedCallback() { + if (!CalendarViewHelper.shouldRaiseCalendarSelectionChangedCallback( + widget.onSelectionChanged)) { + return; + } + final bool isResourceEnabled = + CalendarViewHelper.isResourceEnabled(widget.dataSource, _view); + CalendarViewHelper.raiseCalendarSelectionChangedCallback( + widget, + _controller.selectedDate, + isResourceEnabled ? widget.dataSource!.resources![0] : null); } void _calendarValueChangedListener(String property) { _removeDatePicker(); if (property == 'selectedDate') { - if (_isSameTimeSlot(_selectedDate, _controller.selectedDate)) { + if (CalendarViewHelper.isSameTimeSlot( + _selectedDate, _controller.selectedDate)) { return; } - + _updateSelectionChangedCallback(); setState(() { _selectedDate = _controller.selectedDate; }); @@ -2143,47 +2564,63 @@ class _SfCalendarState extends State setState(() { final CalendarView oldView = _view; - _view = _controller.view; + _view = _controller.view!; _currentDate = getValidDate( widget.minDate, widget.maxDate, _updateCurrentDate(oldView)); if (!isSameDate(_currentDate, _controller.displayDate)) { _controller.displayDate = _currentDate; } - _fadeInController.reset(); - _fadeInController.forward(); + _fadeInController!.reset(); + _fadeInController!.forward(); _agendaScrollController = ScrollController(initialScrollOffset: 0); if (_view == CalendarView.schedule) { - _scheduleDisplayDate = _controller.displayDate; - if (_shouldRaiseViewChangedCallback(widget.onViewChanged)) { - _raiseViewChangedCallback(widget, - visibleDates: []..add(_controller.displayDate)); + _scheduleDisplayDate = _controller.displayDate!; + if (CalendarViewHelper.shouldRaiseViewChangedCallback( + widget.onViewChanged)) { + CalendarViewHelper.raiseViewChangedCallback( + widget, []..add(_controller.displayDate!)); } _agendaScrollController?.removeListener(_handleScheduleViewScrolled); _initScheduleViewProperties(); + } else if (CalendarViewHelper.isResourceEnabled( + widget.dataSource, _view)) { + _resourcePanelScrollController ??= + ScrollController(initialScrollOffset: 0, keepScrollOffset: true); } }); } } void _updateDisplayDate() { + if (!isSameOrAfterDate(widget.minDate, _controller.displayDate)) { + _controller.displayDate = widget.minDate; + return; + } + + if (!isSameOrBeforeDate(widget.maxDate, _controller.displayDate)) { + _controller.displayDate = widget.maxDate; + return; + } + switch (_view) { case CalendarView.schedule: { if (isSameDate(_currentDate, _controller.displayDate)) { - _currentDate = _controller.displayDate; + _currentDate = _controller.displayDate!; return; } - _fadeInController.reset(); - _fadeInController.forward(); + _fadeInController!.reset(); + _fadeInController!.forward(); setState(() { - _currentDate = _controller.displayDate; - _scheduleDisplayDate = _controller.displayDate; + _currentDate = _controller.displayDate!; + _scheduleDisplayDate = _currentDate; _updateCurrentVisibleDates(); _agendaScrollController ?.removeListener(_handleScheduleViewScrolled); + _agendaScrollController!.dispose(); _initScheduleViewProperties(); }); break; @@ -2198,18 +2635,18 @@ class _SfCalendarState extends State _controller.displayDate) && (widget.monthViewSettings.numberOfWeeksInView != 6 || (widget.monthViewSettings.numberOfWeeksInView == 6 && - _controller.displayDate.month == + _controller.displayDate!.month == _currentViewVisibleDates[ _currentViewVisibleDates.length ~/ 2] .month)))) { - _currentDate = _controller.displayDate; + _currentDate = _controller.displayDate!; return; } - _fadeInController.reset(); - _fadeInController.forward(); + _fadeInController!.reset(); + _fadeInController!.forward(); setState(() { - _currentDate = _controller.displayDate; + _currentDate = _controller.displayDate!; _updateCurrentVisibleDates(); }); break; @@ -2227,14 +2664,14 @@ class _SfCalendarState extends State _currentViewVisibleDates[0], _currentViewVisibleDates[_currentViewVisibleDates.length - 1], _controller.displayDate)) { - _currentDate = _controller.displayDate; + _currentDate = _controller.displayDate!; return; } - _fadeInController.reset(); - _fadeInController.forward(); + _fadeInController!.reset(); + _fadeInController!.forward(); setState(() { - _currentDate = _controller.displayDate; + _currentDate = _controller.displayDate!; _updateCurrentVisibleDates(); }); break; @@ -2243,19 +2680,20 @@ class _SfCalendarState extends State } void _updateCurrentVisibleDates() { - final List nonWorkingDays = (_view == CalendarView.workWeek || + final List? nonWorkingDays = (_view == CalendarView.workWeek || _view == CalendarView.timelineWorkWeek) ? widget.timeSlotViewSettings.nonWorkingDays : null; - final int visibleDatesCount = - _getViewDatesCount(_view, widget.monthViewSettings.numberOfWeeksInView); + final int visibleDatesCount = DateTimeHelper.getViewDatesCount( + _view, widget.monthViewSettings.numberOfWeeksInView); - _currentViewVisibleDates = getVisibleDates( - _currentDate, nonWorkingDays, widget.firstDayOfWeek, visibleDatesCount); + _currentViewVisibleDates = getVisibleDates(_currentDate, nonWorkingDays, + widget.firstDayOfWeek, visibleDatesCount) + .cast(); if (_view == CalendarView.timelineMonth) { _currentViewVisibleDates = - _getCurrentMonthDates(_currentViewVisibleDates); + DateTimeHelper.getCurrentMonthDates(_currentViewVisibleDates); } } @@ -2266,17 +2704,23 @@ class _SfCalendarState extends State return; } - final List visibleAppointmentCollection = []; + final List visibleAppointmentCollection = + []; //// Clone the visible appointments because if we add visible appointment directly then //// calendar view visible appointment also updated so it does not perform to paint, So //// clone the visible appointment and added newly added appointment and set the value. - if (_visibleAppointments != null) { - for (int i = 0; i < _visibleAppointments.length; i++) { - visibleAppointmentCollection.add(_visibleAppointments[i]); - } + for (int i = 0; i < _visibleAppointments.length; i++) { + visibleAppointmentCollection.add(_visibleAppointments[i]); } - _appointments ??= []; + if (_isNeedLoadMore || _isScheduleStartLoadMore) { + SchedulerBinding.instance?.addPostFrameCallback((timeStamp) { + setState(() { + _isNeedLoadMore = false; + _isScheduleStartLoadMore = false; + }); + }); + } switch (type) { case CalendarDataSourceAction.reset: @@ -2286,8 +2730,9 @@ class _SfCalendarState extends State break; case CalendarDataSourceAction.add: { - final List collection = - _generateCalendarAppointments(widget.dataSource, widget, data); + final List collection = + AppointmentHelper.generateCalendarAppointments( + widget.dataSource, widget.timeZone, data); if (_view != CalendarView.schedule) { final int visibleDatesCount = _currentViewVisibleDates.length; @@ -2295,21 +2740,24 @@ class _SfCalendarState extends State DateTime viewEndDate = _currentViewVisibleDates[visibleDatesCount - 1]; if (_view == CalendarView.month && - !_isLeadingAndTrailingDatesVisible( + !CalendarViewHelper.isLeadingAndTrailingDatesVisible( widget.monthViewSettings.numberOfWeeksInView, widget.monthViewSettings.showTrailingAndLeadingDates)) { final DateTime currentMonthDate = _currentViewVisibleDates[visibleDatesCount ~/ 2]; - viewStartDate = _getMonthStartDate(currentMonthDate); - viewEndDate = _getMonthEndDate(currentMonthDate); + viewStartDate = + AppointmentHelper.getMonthStartDate(currentMonthDate); + viewEndDate = AppointmentHelper.getMonthEndDate(currentMonthDate); } - visibleAppointmentCollection.addAll(_getVisibleAppointments( - viewStartDate, - viewEndDate, - collection, - widget.timeZone, - _view == CalendarView.month || _isTimelineView(_view))); + visibleAppointmentCollection.addAll( + AppointmentHelper.getVisibleAppointments( + viewStartDate, + viewEndDate, + collection, + widget.timeZone, + _view == CalendarView.month || + CalendarViewHelper.isTimelineView(_view))); } for (int i = 0; i < collection.length; i++) { @@ -2324,7 +2772,7 @@ class _SfCalendarState extends State for (int i = 0; i < data.length; i++) { final Object appointment = data[i]; for (int j = 0; j < _appointments.length; j++) { - if (_appointments[j]._data == appointment) { + if (_appointments[j].data == appointment) { _appointments.removeAt(j); j--; } @@ -2334,7 +2782,7 @@ class _SfCalendarState extends State for (int i = 0; i < data.length; i++) { final Object appointment = data[i]; for (int j = 0; j < visibleAppointmentCollection.length; j++) { - if (visibleAppointmentCollection[j]._data == appointment) { + if (visibleAppointmentCollection[j].data == appointment) { visibleAppointmentCollection.removeAt(j); j--; } @@ -2347,10 +2795,20 @@ class _SfCalendarState extends State case CalendarDataSourceAction.removeResource: case CalendarDataSourceAction.resetResource: { + if (data is! List) { + return; + } + final List resourceCollection = data; - if (resourceCollection != null) { + if (resourceCollection.isNotEmpty) { + _disposeResourceImagePainter(); setState(() { + _resourceCollection = + CalendarViewHelper.cloneList(widget.dataSource?.resources); /* To render the modified resource collection */ + if (CalendarViewHelper.isTimelineView(_view)) { + _isNeedLoadMore = true; + } }); } } @@ -2358,28 +2816,44 @@ class _SfCalendarState extends State } } + void _disposeResourceImagePainter() { + if (_imagePainterCollection.isNotEmpty) { + final List keys = _imagePainterCollection.keys.toList(); + for (int i = 0; i < keys.length; i++) { + _imagePainterCollection[keys[i]]!.dispose(); + } + + _imagePainterCollection.clear(); + } + } + /// Updates the visible appointments collection based on passed collection, /// the collection modified based on the data source's add and remove action. void _updateVisibleAppointmentCollection( - List visibleAppointmentCollection) { + List visibleAppointmentCollection) { if (_view == CalendarView.schedule) { - setState(() { - /// Update the view when the appointment collection changed. + SchedulerBinding.instance?.addPostFrameCallback((timeStamp) { + setState(() { + /// Update the view when the appointment collection changed. + }); }); return; } - if (_isCollectionEqual( + if (CalendarViewHelper.isCollectionEqual( _visibleAppointments, visibleAppointmentCollection)) { return; } - setState(() { - _visibleAppointments = visibleAppointmentCollection; + _visibleAppointments = visibleAppointmentCollection; - /// Update all day appointment related implementation in calendar, - /// because time label view needs the top position. - _updateAllDayAppointment(); + /// Update all day appointment related implementation in calendar, + /// because time label view needs the top position. + _updateAllDayAppointment(); + SchedulerBinding.instance?.addPostFrameCallback((timeStamp) { + setState(() { + /// Update the UI. + }); }); } @@ -2413,14 +2887,14 @@ class _SfCalendarState extends State _selectedDate)) { if (view == CalendarView.month || view == CalendarView.timelineMonth) { return DateTime( - _selectedDate.year, - _selectedDate.month, - _selectedDate.day, - _controller.displayDate.hour, - _controller.displayDate.minute, - _controller.displayDate.second); + _selectedDate!.year, + _selectedDate!.month, + _selectedDate!.day, + _controller.displayDate!.hour, + _controller.displayDate!.minute, + _controller.displayDate!.second); } else { - return _selectedDate; + return _selectedDate!; } } else if (isDateWithInDateRange( _currentViewVisibleDates[0], @@ -2431,9 +2905,9 @@ class _SfCalendarState extends State date.year, date.month, date.day, - _controller.displayDate.hour, - _controller.displayDate.minute, - _controller.displayDate.second); + _controller.displayDate!.hour, + _controller.displayDate!.minute, + _controller.displayDate!.second); } else { if (view == CalendarView.month || view == CalendarView.timelineMonth) { if (widget.monthViewSettings.numberOfWeeksInView > 0 && @@ -2444,29 +2918,29 @@ class _SfCalendarState extends State _currentDate.year, _currentDate.month, 1, - _controller.displayDate.hour, - _controller.displayDate.minute, - _controller.displayDate.second); + _controller.displayDate!.hour, + _controller.displayDate!.minute, + _controller.displayDate!.second); } else { final DateTime date = _currentViewVisibleDates[0]; return DateTime( date.year, date.month, date.day, - _controller.displayDate.hour, - _controller.displayDate.minute, - _controller.displayDate.second); + _controller.displayDate!.hour, + _controller.displayDate!.minute, + _controller.displayDate!.second); } } } - void _updateAppointmentView(List allDayAppointments) { + void _updateAppointmentView(List allDayAppointments) { for (int i = 0; i < allDayAppointments.length; i++) { - _AppointmentView appointmentView; + AppointmentView appointmentView; if (_allDayAppointmentViewCollection.length > i) { appointmentView = _allDayAppointmentViewCollection[i]; } else { - appointmentView = _AppointmentView(); + appointmentView = AppointmentView(); _allDayAppointmentViewCollection.add(appointmentView); } @@ -2476,16 +2950,16 @@ class _SfCalendarState extends State } void _updateAppointmentViewPosition() { - for (final _AppointmentView appointmentView + for (final AppointmentView appointmentView in _allDayAppointmentViewCollection) { if (appointmentView.appointment == null) { continue; } - final int startIndex = _getIndex(_currentViewVisibleDates, - appointmentView.appointment._actualStartTime); - final int endIndex = _getIndex(_currentViewVisibleDates, - appointmentView.appointment._actualEndTime) + + final int startIndex = DateTimeHelper.getIndex(_currentViewVisibleDates, + appointmentView.appointment!.actualStartTime); + final int endIndex = DateTimeHelper.getIndex(_currentViewVisibleDates, + appointmentView.appointment!.actualEndTime) + 1; if (startIndex == -1 && endIndex == 0) { appointmentView.appointment = null; @@ -2498,16 +2972,16 @@ class _SfCalendarState extends State } void _updateAppointmentPositionAndMaxPosition( - List> allDayAppointmentView) { + List> allDayAppointmentView) { for (int i = 0; i < allDayAppointmentView.length; i++) { - final List<_AppointmentView> intersectingAppointments = + final List intersectingAppointments = allDayAppointmentView[i]; for (int j = 0; j < intersectingAppointments.length; j++) { - final _AppointmentView currentView = intersectingAppointments[j]; + final AppointmentView currentView = intersectingAppointments[j]; if (currentView.position == -1) { currentView.position = 0; for (int k = 0; k < j; k++) { - final _AppointmentView intersectView = _getAppointmentOnPosition( + final AppointmentView? intersectView = _getAppointmentOnPosition( currentView, intersectingAppointments); if (intersectView != null) { currentView.position++; @@ -2520,8 +2994,8 @@ class _SfCalendarState extends State if (intersectingAppointments.isNotEmpty) { final int maxPosition = intersectingAppointments - .reduce((_AppointmentView currentAppView, - _AppointmentView nextAppView) => + .reduce((AppointmentView currentAppView, + AppointmentView nextAppView) => currentAppView.position > nextAppView.position ? currentAppView : nextAppView) @@ -2529,7 +3003,7 @@ class _SfCalendarState extends State 1; for (int j = 0; j < intersectingAppointments.length; j++) { - final _AppointmentView appointmentView = intersectingAppointments[j]; + final AppointmentView appointmentView = intersectingAppointments[j]; if (appointmentView.maxPositions != -1) { continue; } @@ -2539,14 +3013,31 @@ class _SfCalendarState extends State } } + AppointmentView? _getAppointmentOnPosition( + AppointmentView? currentView, List? views) { + if (currentView == null || + currentView.appointment == null || + views == null || + views.isEmpty) { + return null; + } + + for (final AppointmentView view in views) { + if (view.position == currentView.position && view != currentView) { + return view; + } + } + + return null; + } + void _updateIntersectAppointmentViewCollection( - List> allDayAppointmentView) { + List> allDayAppointmentView) { for (int i = 0; i < _currentViewVisibleDates.length; i++) { - final List<_AppointmentView> intersectingAppointments = - <_AppointmentView>[]; + final List intersectingAppointments = + []; for (int j = 0; j < _allDayAppointmentViewCollection.length; j++) { - final _AppointmentView currentView = - _allDayAppointmentViewCollection[j]; + final AppointmentView currentView = _allDayAppointmentViewCollection[j]; if (currentView.appointment == null) { continue; } @@ -2561,27 +3052,27 @@ class _SfCalendarState extends State } void _updateAllDayAppointment() { - if (_isTimelineView(_view) && _view == CalendarView.month) { + if (CalendarViewHelper.isTimelineView(_view) && + _view == CalendarView.month) { return; } _allDayPanelHeight = 0; - _allDayAppointmentViewCollection = - _allDayAppointmentViewCollection ?? <_AppointmentView>[]; //// Remove the existing appointment related details. - _resetAppointmentView(_allDayAppointmentViewCollection); + AppointmentHelper.resetAppointmentView(_allDayAppointmentViewCollection); - if (_visibleAppointments == null || _visibleAppointments.isEmpty) { + if (_visibleAppointments.isEmpty) { return; } //// Calculate the visible all day appointment collection. - final List allDayAppointments = []; - for (final Appointment appointment in _visibleAppointments) { + final List allDayAppointments = + []; + for (final CalendarAppointment appointment in _visibleAppointments) { if (appointment.isAllDay || - appointment._actualEndTime - .difference(appointment._actualStartTime) + appointment.actualEndTime + .difference(appointment.actualStartTime) .inDays > 0) { allDayAppointments.add(appointment); @@ -2596,12 +3087,12 @@ class _SfCalendarState extends State //// Sort the appointment view based on appointment view width. _allDayAppointmentViewCollection - .sort((_AppointmentView app1, _AppointmentView app2) { + .sort((AppointmentView app1, AppointmentView app2) { if (app1.appointment != null && app2.appointment != null) { - return (app2.appointment.endTime - .difference(app2.appointment.startTime)) > - (app1.appointment.endTime - .difference(app1.appointment.startTime)) + return (app2.appointment!.endTime + .difference(app2.appointment!.startTime)) > + (app1.appointment!.endTime + .difference(app1.appointment!.startTime)) ? 1 : 0; } @@ -2611,7 +3102,7 @@ class _SfCalendarState extends State //// Sort the appointment view based on appointment view start position. _allDayAppointmentViewCollection - .sort((_AppointmentView app1, _AppointmentView app2) { + .sort((AppointmentView app1, AppointmentView app2) { if (app1.appointment != null && app2.appointment != null) { return app1.startIndex.compareTo(app2.startIndex); } @@ -2619,8 +3110,8 @@ class _SfCalendarState extends State return 0; }); - final List> allDayAppointmentView = - >[]; + final List> allDayAppointmentView = + >[]; //// Calculate the intersecting appointment view collection. _updateIntersectAppointmentViewCollection(allDayAppointmentView); @@ -2632,11 +3123,10 @@ class _SfCalendarState extends State void _updateAllDayPanelHeight() { int maxPosition = 0; - if (_allDayAppointmentViewCollection != null && - _allDayAppointmentViewCollection.isNotEmpty) { + if (_allDayAppointmentViewCollection.isNotEmpty) { maxPosition = _allDayAppointmentViewCollection .reduce( - (_AppointmentView currentAppView, _AppointmentView nextAppView) => + (AppointmentView currentAppView, AppointmentView nextAppView) => currentAppView.maxPositions > nextAppView.maxPositions ? currentAppView : nextAppView) @@ -2647,12 +3137,11 @@ class _SfCalendarState extends State maxPosition = 0; } - _allDayPanelHeight = (maxPosition * _kAllDayAppointmentHeight).toDouble(); + _allDayPanelHeight = (maxPosition * kAllDayAppointmentHeight).toDouble(); } double _getMonthAgendaHeight() { - return (widget.monthViewSettings.agendaViewHeight == null || - widget.monthViewSettings.agendaViewHeight == -1) + return widget.monthViewSettings.agendaViewHeight == -1 ? _minHeight / 3 : widget.monthViewSettings.agendaViewHeight; } @@ -2674,17 +3163,19 @@ class _SfCalendarState extends State } void _updateMouseHoverPosition( - Offset globalPosition, bool isScheduleDisplayDate, - [bool isRTL, - DateTime currentDate, - double startPosition, - double padding = 0, + Offset globalPosition, + bool isScheduleDisplayDate, + bool isRTL, + DateTime? currentDate, + double? startPosition, + [double padding = 0, bool isResourceEnabled = false]) { if (_isMobilePlatform) { return; } - final RenderBox box = context.findRenderObject(); + // ignore: avoid_as + final RenderBox box = context.findRenderObject() as RenderBox; final Offset localPosition = box.globalToLocal(globalPosition); if (localPosition.dy < widget.headerHeight) { _updateMouseHoveringForHeader(localPosition); @@ -2695,9 +3186,10 @@ class _SfCalendarState extends State (_minWidth - widget.resourceViewSettings.size)) || (!isRTL && localPosition.dx < widget.resourceViewSettings.size)) && - localPosition.dy > startPosition && - (_shouldRaiseCalendarTapCallback(widget.onTap) || - _shouldRaiseCalendarLongPressCallback(widget.onLongPress))) { + localPosition.dy > startPosition! && + (CalendarViewHelper.shouldRaiseCalendarTapCallback(widget.onTap) || + CalendarViewHelper.shouldRaiseCalendarLongPressCallback( + widget.onLongPress))) { if (_headerHoverNotifier.value != null) { _headerHoverNotifier.value = null; } @@ -2715,7 +3207,7 @@ class _SfCalendarState extends State } final double yPosition = - (_resourcePanelScrollController.offset + localPosition.dy) - + (_resourcePanelScrollController!.offset + localPosition.dy) - startPosition; _resourceHoverNotifier.value = Offset(localPosition.dx, yPosition); @@ -2730,7 +3222,7 @@ class _SfCalendarState extends State double dateViewWidth = _getAgendaViewDayLabelWidth( widget.scheduleViewSettings, _useMobilePlatformUI); if (_view == CalendarView.month) { - currentDate = _selectedDate; + currentDate = _selectedDate!; final double agendaHeight = _getMonthAgendaHeight(); yPosition -= _minHeight - agendaHeight; dateViewWidth = _agendaDateViewWidth; @@ -2742,8 +3234,8 @@ class _SfCalendarState extends State dateViewWidth = 60; } } else { - yPosition = (_agendaScrollController.offset + localPosition.dy) - - startPosition - + yPosition = (_agendaScrollController!.offset + localPosition.dy) - + startPosition! - widget.headerHeight; } @@ -2769,8 +3261,8 @@ class _SfCalendarState extends State } xPosition = isRTL ? _minWidth - xPosition : xPosition; - _agendaDateNotifier.value = _ScheduleViewHoveringDetails( - currentDate, Offset(xPosition, yPosition)); + _agendaDateNotifier.value = ScheduleViewHoveringDetails( + currentDate!, Offset(xPosition, yPosition)); } else { /// padding value used to specify the view top padding on agenda view. /// padding value is assigned when the agenda view has top padding @@ -2778,7 +3270,7 @@ class _SfCalendarState extends State /// schedule view then it have top padding because the agenda view /// minimum panel height as appointment height specified in setting. if (_view == CalendarView.month) { - yPosition += _agendaScrollController?.offset; + yPosition += _agendaScrollController!.offset; xPosition -= isRTL ? 0 : dateViewWidth; } yPosition -= padding; @@ -2799,30 +3291,30 @@ class _SfCalendarState extends State _agendaViewNotifier.value = null; return; } - _agendaViewNotifier.value = _ScheduleViewHoveringDetails( - currentDate, Offset(xPosition, yPosition)); + _agendaViewNotifier.value = ScheduleViewHoveringDetails( + currentDate!, Offset(xPosition, yPosition)); } } } - void _pointerEnterEvent(PointerEnterEvent event, bool isScheduleDisplayDate, - [bool isRTL, - DateTime currentDate, - double startPosition, + void _pointerEnterEvent( + PointerEnterEvent event, bool isScheduleDisplayDate, bool isRTL, + [DateTime? currentDate, + double? startPosition, double padding = 0, - bool resourceEnabled]) { + bool resourceEnabled = false]) { _updateMouseHoverPosition(event.position, isScheduleDisplayDate, isRTL, - currentDate, startPosition, padding, resourceEnabled ?? false); + currentDate, startPosition, padding, resourceEnabled); } - void _pointerHoverEvent(PointerHoverEvent event, bool isScheduleDisplayDate, - [bool isRTL, - DateTime currentDate, - double startPosition, + void _pointerHoverEvent( + PointerHoverEvent event, bool isScheduleDisplayDate, bool isRTL, + [DateTime? currentDate, + double? startPosition, double padding = 0, - bool resourceEnabled]) { + bool resourceEnabled = false]) { _updateMouseHoverPosition(event.position, isScheduleDisplayDate, isRTL, - currentDate, startPosition, padding, resourceEnabled ?? false); + currentDate, startPosition, padding, resourceEnabled); } void _pointerExitEvent(PointerExitEvent event) { @@ -2832,188 +3324,433 @@ class _SfCalendarState extends State _resourceHoverNotifier.value = null; } - /// Return the all day appointment count from appointment collection. - int _getAllDayCount(List appointmentCollection) { - int allDayCount = 0; - for (int i = 0; i < appointmentCollection.length; i++) { - final Appointment appointment = appointmentCollection[i]; - if (_isAllDayAppointmentView(appointment)) { - allDayCount += 1; - } + /// Calculate the maximum appointment date based on appointment collection + /// and schedule view settings. + DateTime _getMaxAppointmentDate( + List appointments, + String? timeZone, + DateTime maxDate, + DateTime displayDate, + ScheduleViewSettings scheduleViewSettings, + bool useMobilePlatformUI) { + /// return default max date when [hideEmptyAgendaDays] as false + if (!scheduleViewSettings.hideEmptyScheduleWeek && useMobilePlatformUI) { + return maxDate; } - return allDayCount; - } + DateTime currentMaxDate = displayDate; + if (appointments.isEmpty) { + return currentMaxDate; + } - /// Return the collection of appointment collection listed by - /// start date of the appointment. - Map> _getAppointmentCollectionOnDateBasis( - List appointmentCollection, - DateTime startDate, - DateTime endDate) { - final Map> dateAppointments = - >{}; - while (startDate.isBefore(endDate) || isSameDate(endDate, startDate)) { - final List appointmentList = []; - for (int i = 0; i < appointmentCollection.length; i++) { - final Appointment appointment = appointmentCollection[i]; - if (!isDateWithInDateRange(appointment._actualStartTime, - appointment._actualEndTime, startDate)) { + /// Calculate the max appointment date based on appointments when + /// web view enabled or [hideEmptyAgendaDays] property as enabled. + for (int j = 0; j < appointments.length; j++) { + final CalendarAppointment appointment = appointments[j]; + appointment.actualEndTime = + AppointmentHelper.convertTimeToAppointmentTimeZone( + appointment.endTime, appointment.endTimeZone, timeZone); + + if (appointment.recurrenceRule == null || + appointment.recurrenceRule == '') { + if (appointment.actualEndTime.isAfter(currentMaxDate)) { + currentMaxDate = appointment.actualEndTime; + } + + continue; + } + + /// Return specified ma date when recurrence rule does not have + /// count and until string. + if (!appointment.recurrenceRule!.contains('COUNT') && + !appointment.recurrenceRule!.contains('UNTIL')) { + currentMaxDate = maxDate; + return currentMaxDate; + } + + if (appointment.recurrenceRule!.contains('UNTIL')) { + final List ruleSeparator = ['=', ';', ',']; + final List rRule = RecurrenceHelper.splitRule( + appointment.recurrenceRule!, ruleSeparator); + final String untilValue = rRule[rRule.indexOf('UNTIL') + 1]; + DateTime recurrenceEndDate = DateTime.parse(untilValue); + recurrenceEndDate = DateTime(recurrenceEndDate.year, + recurrenceEndDate.month, recurrenceEndDate.day, 23, 59, 59); + if (recurrenceEndDate.isAfter(currentMaxDate)) { + currentMaxDate = recurrenceEndDate; continue; } + } - appointmentList.add(appointment); + final List recursiveDates = + RecurrenceHelper.getRecurrenceDateTimeCollection( + appointment.recurrenceRule!, + appointment.actualStartTime, + ); + + if (recursiveDates.isEmpty) { + continue; } - if (appointmentList.isNotEmpty) { - dateAppointments[startDate] = appointmentList; + if (appointment.recurrenceExceptionDates == null || + appointment.recurrenceExceptionDates!.isEmpty) { + final DateTime date = recursiveDates[recursiveDates.length - 1]; + if (date.isAfter(currentMaxDate)) { + currentMaxDate = date; + continue; + } } - startDate = addDuration(startDate, const Duration(days: 1)); + for (int k = recursiveDates.length - 1; k >= 0; k--) { + final DateTime recurrenceDate = recursiveDates[k]; + bool isExceptionDate = false; + if (appointment.recurrenceExceptionDates != null) { + for (int i = 0; + i < appointment.recurrenceExceptionDates!.length; + i++) { + final DateTime exceptionDate = + appointment.recurrenceExceptionDates![i]; + if (isSameDate(recurrenceDate, exceptionDate)) { + isExceptionDate = true; + } + } + } + + if (!isExceptionDate) { + final DateTime recurrenceEndDate = addDuration( + recurrenceDate, + appointment.actualEndTime + .difference(appointment.actualStartTime)); + if (recurrenceEndDate.isAfter(currentMaxDate)) { + currentMaxDate = recurrenceEndDate; + break; + } + } + } } - return dateAppointments; + return currentMaxDate; } - /// Return the widget to scroll view based on index. - Widget _getItem(BuildContext context, int index, bool isRTL) { - /// Assign display date and today date, - /// schedule display date always hold the minimum date compared to - /// display date and today date. - /// schedule current date always hold the maximum date compared to - /// display date and today date - DateTime scheduleDisplayDate = _scheduleDisplayDate; - //getValidDate(widget.minDate, widget.maxDate, _controller.displayDate); - DateTime scheduleCurrentDate = DateTime.now(); - if (scheduleDisplayDate.isAfter(scheduleCurrentDate)) { - final DateTime tempDate = scheduleDisplayDate; - scheduleDisplayDate = scheduleCurrentDate; - scheduleCurrentDate = tempDate; + /// Calculate the minimum appointment date based on appointment collection + /// and schedule view settings. + DateTime _getMinAppointmentDate( + List appointments, + String? timeZone, + DateTime minDate, + DateTime displayDate, + ScheduleViewSettings scheduleViewSettings, + bool useMobilePlatformUI) { + /// return default min date when [hideEmptyAgendaDays] as false + if (!scheduleViewSettings.hideEmptyScheduleWeek && useMobilePlatformUI) { + return minDate; } - /// Get the minimum date of schedule view when it value as null - /// It return min date user assigned when the [hideEmptyScheduleWeek] - /// in [ScheduleViewSettings] disabled else it return min - /// start date of the appointment collection. - _minDate ??= _getMinAppointmentDate( - _appointments, - widget.timeZone, - widget.minDate, - scheduleDisplayDate, - widget.scheduleViewSettings, - _useMobilePlatformUI); + DateTime currentMinDate = displayDate; + if (appointments.isEmpty) { + return currentMinDate; + } - /// Assign minimum date value to schedule display date when the minimum - /// date is after of schedule display date - _minDate = - _minDate.isAfter(scheduleDisplayDate) ? scheduleDisplayDate : _minDate; - _minDate = _minDate.isBefore(widget.minDate) ? widget.minDate : _minDate; + /// Calculate the min appointment date based on appointments when + /// web view enabled or [hideEmptyAgendaDays] property as enabled. + for (int j = 0; j < appointments.length; j++) { + final CalendarAppointment appointment = appointments[j]; + appointment.actualStartTime = + AppointmentHelper.convertTimeToAppointmentTimeZone( + appointment.startTime, appointment.startTimeZone, timeZone); - final DateTime viewMinDate = - addDuration(_minDate, Duration(days: -_minDate.weekday)); + if (appointment.actualStartTime.isBefore(currentMinDate)) { + currentMinDate = appointment.actualStartTime; + } - /// Get the maximum date of schedule view when it value as null - /// It return max date user assigned when the [hideEmptyScheduleWeek] - /// in [ScheduleViewSettings] disabled else it return max - /// end date of the appointment collection. - _maxDate ??= _getMaxAppointmentDate( - _appointments, - widget.timeZone, - widget.maxDate, - scheduleCurrentDate, - widget.scheduleViewSettings, - _useMobilePlatformUI); + continue; + } - /// Assign maximum date value to schedule current date when the maximum - /// date is before of schedule current date - _maxDate = - _maxDate.isBefore(scheduleCurrentDate) ? scheduleCurrentDate : _maxDate; - _maxDate = _maxDate.isAfter(widget.maxDate) ? widget.maxDate : _maxDate; + return currentMinDate; + } - final bool hideEmptyAgendaDays = - widget.scheduleViewSettings.hideEmptyScheduleWeek || - !_useMobilePlatformUI; + /// Check any appointment in appointments collection in between + /// the start and end date. + bool _isAppointmentBetweenDates(List appointments, + DateTime startDate, DateTime endDate, String? timeZone) { + startDate = AppointmentHelper.convertToStartTime(startDate); + endDate = AppointmentHelper.convertToEndTime(endDate); + if (appointments.isEmpty) { + return false; + } - if (index > 0) { - /// Add next 100 dates to next dates collection when index - /// reaches next dates collection end. - if (_nextDates.isNotEmpty && index > _nextDates.length - 20) { - DateTime date = _nextDates[_nextDates.length - 1]; - int count = 0; - - /// Using while for calculate dates because if [hideEmptyAgendaDays] as - /// enabled, then it hides the weeks when it does not have appointments. - while (count < 20) { - for (int i = 1; i <= 100; i++) { - final DateTime updateDate = - addDuration(date, Duration(days: i * _kNumberOfDaysInWeek)); - - /// Skip the weeks after the max date. - if (!isSameOrBeforeDate(_maxDate, updateDate)) { - count = 20; - break; - } + for (int j = 0; j < appointments.length; j++) { + final CalendarAppointment appointment = appointments[j]; + appointment.actualStartTime = + AppointmentHelper.convertTimeToAppointmentTimeZone( + appointment.startTime, appointment.startTimeZone, timeZone); + appointment.actualEndTime = + AppointmentHelper.convertTimeToAppointmentTimeZone( + appointment.endTime, appointment.endTimeZone, timeZone); + + if (appointment.recurrenceRule == null || + appointment.recurrenceRule == '') { + if (AppointmentHelper.isAppointmentWithinVisibleDateRange( + appointment, startDate, endDate)) { + return true; + } - final DateTime weekEndDate = - addDuration(updateDate, const Duration(days: 6)); - - /// Skip the week date when it does not have appointments - /// when [hideEmptyAgendaDays] as enabled and display date and - /// current date not in between the week. - if (!hideEmptyAgendaDays || - _isAppointmentBetweenDates( - _appointments, updateDate, weekEndDate, widget.timeZone) || - isDateWithInDateRange( - updateDate, weekEndDate, scheduleDisplayDate) || - isDateWithInDateRange( - updateDate, weekEndDate, scheduleCurrentDate)) { - _nextDates.add(updateDate); - count++; - } + continue; + } + + if (appointment.startTime.isAfter(endDate)) { + continue; + } + + String rule = appointment.recurrenceRule!; + if (!rule.contains('COUNT') && !rule.contains('UNTIL')) { + final DateFormat formatter = DateFormat('yyyyMMdd'); + final String newSubString = ';UNTIL=' + formatter.format(endDate); + rule = rule + newSubString; + } + + final List ruleSeparator = ['=', ';', ',']; + final List rRule = + RecurrenceHelper.splitRule(rule, ruleSeparator); + if (rRule.contains('UNTIL')) { + final String untilValue = rRule[rRule.indexOf('UNTIL') + 1]; + DateTime recurrenceEndDate = DateTime.parse(untilValue); + recurrenceEndDate = DateTime(recurrenceEndDate.year, + recurrenceEndDate.month, recurrenceEndDate.day, 23, 59, 59); + if (recurrenceEndDate.isBefore(startDate)) { + continue; + } + } + + final List recursiveDates = + RecurrenceHelper.getRecurrenceDateTimeCollection( + rule, appointment.actualStartTime, + recurrenceDuration: appointment.actualEndTime + .difference(appointment.actualStartTime), + specificStartDate: startDate, + specificEndDate: endDate); + + if (recursiveDates.isEmpty) { + continue; + } + + if (appointment.recurrenceExceptionDates == null || + appointment.recurrenceExceptionDates!.isEmpty) { + return true; + } + + for (int i = 0; i < appointment.recurrenceExceptionDates!.length; i++) { + final DateTime exceptionDate = appointment.recurrenceExceptionDates![i]; + for (int k = 0; k < recursiveDates.length; k++) { + final DateTime recurrenceDate = recursiveDates[k]; + if (!isSameDate(recurrenceDate, exceptionDate)) { + return true; } + } + } + } + + return false; + } + + /// This method is used to check the appointment needs all day appointment + /// view or not in agenda view, because the all day appointment view shown + /// as half of the normal appointment view in agenda view. + /// Agenda view used on month and schedule calendar view. + bool _isAllDayAppointmentView(CalendarAppointment appointment) { + return appointment.isAllDay || + appointment.isSpanned || + appointment.actualStartTime.day != appointment.actualEndTime.day; + } + + /// Return the all day appointment count from appointment collection. + int _getAllDayCount(List appointmentCollection) { + int allDayCount = 0; + for (int i = 0; i < appointmentCollection.length; i++) { + final CalendarAppointment appointment = appointmentCollection[i]; + if (_isAllDayAppointmentView(appointment)) { + allDayCount += 1; + } + } + + return allDayCount; + } - date = addDuration(date, const Duration(days: 700)); + /// Return the collection of appointment collection listed by + /// start date of the appointment. + Map> _getAppointmentCollectionOnDateBasis( + List appointmentCollection, + DateTime startDate, + DateTime endDate) { + final Map> dateAppointments = + >{}; + while (startDate.isBefore(endDate) || isSameDate(endDate, startDate)) { + final List appointmentList = []; + for (int i = 0; i < appointmentCollection.length; i++) { + final CalendarAppointment appointment = appointmentCollection[i]; + if (!isDateWithInDateRange(appointment.actualStartTime, + appointment.actualEndTime, startDate)) { + continue; } + + appointmentList.add(appointment); + } + + if (appointmentList.isNotEmpty) { + dateAppointments[startDate] = appointmentList; } + + startDate = addDays(startDate, 1); + } + + return dateAppointments; + } + + /// Return the widget to scroll view based on index. + Widget? _getItem(BuildContext context, int index, bool isRTL) { + /// Assign display date and today date, + /// schedule display date always hold the minimum date compared to + /// display date and today date. + /// schedule current date always hold the maximum date compared to + /// display date and today date + DateTime scheduleDisplayDate = _scheduleDisplayDate; + DateTime scheduleCurrentDate = DateTime.now(); + if (scheduleDisplayDate.isAfter(scheduleCurrentDate)) { + final DateTime tempDate = scheduleDisplayDate; + scheduleDisplayDate = scheduleCurrentDate; + scheduleCurrentDate = tempDate; + } + + final bool isLoadMore = widget.loadMoreWidgetBuilder != null; + + if (isLoadMore) { + _minDate ??= _scheduleMinDate; + _maxDate ??= _scheduleMaxDate; } else { - /// Add previous 100 dates to previous dates collection when index - /// reaches previous dates collection end. - if (_previousDates.isNotEmpty && -index > _previousDates.length - 20) { - DateTime date = _previousDates[_previousDates.length - 1]; - int count = 0; - - /// Using while for calculate dates because if [hideEmptyAgendaDays] as - /// enabled, then it hides the weeks when it does not have appointments. - while (count < 20) { - for (int i = 1; i <= 100; i++) { - final DateTime updatedDate = - addDuration(date, Duration(days: -i * _kNumberOfDaysInWeek)); - - /// Skip the weeks before the min date. - if (!isSameOrAfterDate(viewMinDate, updatedDate)) { - count = 20; - break; - } + /// Get the minimum date of schedule view when it value as null + /// It return min date user assigned when the [hideEmptyScheduleWeek] + /// in [ScheduleViewSettings] disabled else it return min + /// start date of the appointment collection. + _minDate ??= _getMinAppointmentDate( + _appointments, + widget.timeZone, + widget.minDate, + scheduleDisplayDate, + widget.scheduleViewSettings, + _useMobilePlatformUI); + + /// Assign minimum date value to schedule display date when the minimum + /// date is after of schedule display date + _minDate = _minDate!.isAfter(scheduleDisplayDate) + ? scheduleDisplayDate + : _minDate; + _minDate = _minDate!.isBefore(widget.minDate) ? widget.minDate : _minDate; + + final DateTime viewMinDate = + addDays(_minDate, -(_minDate!.weekday % DateTime.daysPerWeek)); + + /// Get the maximum date of schedule view when it value as null + /// It return max date user assigned when the [hideEmptyScheduleWeek] + /// in [ScheduleViewSettings] disabled else it return max + /// end date of the appointment collection. + _maxDate ??= _getMaxAppointmentDate( + _appointments, + widget.timeZone, + widget.maxDate, + scheduleCurrentDate, + widget.scheduleViewSettings, + _useMobilePlatformUI); + + /// Assign maximum date value to schedule current date when the maximum + /// date is before of schedule current date + _maxDate = _maxDate!.isBefore(scheduleCurrentDate) + ? scheduleCurrentDate + : _maxDate; + _maxDate = _maxDate!.isAfter(widget.maxDate) ? widget.maxDate : _maxDate; + + final bool hideEmptyAgendaDays = + widget.scheduleViewSettings.hideEmptyScheduleWeek || + !_useMobilePlatformUI; + + if (index > 0) { + /// Add next 100 dates to next dates collection when index + /// reaches next dates collection end. + if (_nextDates.isNotEmpty && index > _nextDates.length - 20) { + DateTime date = _nextDates[_nextDates.length - 1]; + int count = 0; + + /// Using while for calculate dates, if [hideEmptyAgendaDays] is + /// enabled, then hide the week when it does not have appointments. + while (count < 20) { + for (int i = 1; i <= 100; i++) { + final DateTime updateDate = + addDays(date, i * DateTime.daysPerWeek); + + /// Skip the weeks after the max date. + if (!isSameOrBeforeDate(_maxDate, updateDate)) { + count = 20; + break; + } - final DateTime weekEndDate = - addDuration(updatedDate, const Duration(days: 6)); - - /// Skip the week date when it does not have appointments - /// when [hideEmptyAgendaDays] as enabled and display date and - /// current date not in between the week. - if (!hideEmptyAgendaDays || - _isAppointmentBetweenDates( - _appointments, updatedDate, weekEndDate, widget.timeZone) || - isDateWithInDateRange( - updatedDate, weekEndDate, scheduleDisplayDate) || - isDateWithInDateRange( - updatedDate, weekEndDate, scheduleCurrentDate)) { - _previousDates.add(updatedDate); - count++; + final DateTime weekEndDate = addDays(updateDate, 6); + + /// Skip the week date when it does not have appointments + /// when [hideEmptyAgendaDays] as enabled and display date and + /// current date not in between the week. + if (!hideEmptyAgendaDays || + _isAppointmentBetweenDates(_appointments, updateDate, + weekEndDate, widget.timeZone) || + isDateWithInDateRange( + updateDate, weekEndDate, scheduleDisplayDate) || + isDateWithInDateRange( + updateDate, weekEndDate, scheduleCurrentDate)) { + _nextDates.add(updateDate); + count++; + } } + + date = addDays(date, 700); } + } + } else { + /// Add previous 100 dates to previous dates collection when index + /// reaches previous dates collection end. + if (_previousDates.isNotEmpty && -index > _previousDates.length - 20) { + DateTime date = _previousDates[_previousDates.length - 1]; + int count = 0; + + /// Using while for calculate dates, if [hideEmptyAgendaDays] is + /// enabled, then hide the week when it does not have appointments. + while (count < 20) { + for (int i = 1; i <= 100; i++) { + final DateTime updatedDate = + addDays(date, -i * DateTime.daysPerWeek); + + /// Skip the weeks before the min date. + if (!isSameOrAfterDate(viewMinDate, updatedDate)) { + count = 20; + break; + } + + final DateTime weekEndDate = addDays(updatedDate, 6); + + /// Skip the week date when it does not have appointments + /// when [hideEmptyAgendaDays] as enabled and display date and + /// current date not in between the week. + if (!hideEmptyAgendaDays || + _isAppointmentBetweenDates(_appointments, updatedDate, + weekEndDate, widget.timeZone) || + isDateWithInDateRange( + updatedDate, weekEndDate, scheduleDisplayDate) || + isDateWithInDateRange( + updatedDate, weekEndDate, scheduleCurrentDate)) { + _previousDates.add(updatedDate); + count++; + } + } - date = addDuration(date, const Duration(days: -700)); + date = addDays(date, -700); + } } } } @@ -3036,32 +3773,32 @@ class _SfCalendarState extends State /// by subtract the 7 days to get previous date. final DateTime prevDate = index == 0 ? _previousDates.isEmpty - ? addDuration( - startDate, const Duration(days: -_kNumberOfDaysInWeek)) + ? addDays(startDate, -DateTime.daysPerWeek) : _previousDates[0] : (index > 0 ? _nextDates[index - 1] : -index >= _previousDates.length - 1 - ? addDuration( - startDate, const Duration(days: -_kNumberOfDaysInWeek)) + ? addDays(startDate, -DateTime.daysPerWeek) : _previousDates[-index]); - final DateTime prevEndDate = addDuration(prevDate, const Duration(days: 6)); - final DateTime endDate = addDuration(startDate, const Duration(days: 6)); + final DateTime prevEndDate = addDays(prevDate, 6); + final DateTime endDate = addDays(startDate, 6); /// Get the visible week appointment and split the appointments based on /// date. - final List appointmentCollection = _getVisibleAppointments( - isSameOrAfterDate(_minDate, startDate) ? startDate : _minDate, - isSameOrBeforeDate(_maxDate, endDate) ? endDate : _maxDate, - _appointments, - widget.timeZone, - false, - canCreateNewAppointment: false); - appointmentCollection.sort((Appointment app1, Appointment app2) => - app1._actualStartTime.compareTo(app2._actualStartTime)); + final List appointmentCollection = + AppointmentHelper.getVisibleAppointments( + isSameOrAfterDate(_minDate!, startDate) ? startDate : _minDate!, + isSameOrBeforeDate(_maxDate!, endDate) ? endDate : _maxDate!, + _appointments, + widget.timeZone, + false, + canCreateNewAppointment: false); + appointmentCollection.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime)); /// Get the collection of appointment collection listed by date. - final Map> dateAppointments = + final Map> dateAppointments = _getAppointmentCollectionOnDateBasis( appointmentCollection, startDate, endDate); final List dateAppointmentKeys = dateAppointments.keys.toList(); @@ -3127,9 +3864,11 @@ class _SfCalendarState extends State ? widget.scheduleViewSettings.monthHeaderSettings.height : 0; final double appointmentViewHeight = - _getScheduleAppointmentHeight(null, widget.scheduleViewSettings); + CalendarViewHelper.getScheduleAppointmentHeight( + null, widget.scheduleViewSettings); final double allDayAppointmentHeight = - _getScheduleAllDayAppointmentHeight(null, widget.scheduleViewSettings); + CalendarViewHelper.getScheduleAllDayAppointmentHeight( + null, widget.scheduleViewSettings); /// Calculate the divider height and color when it is web view. final double dividerHeight = _useMobilePlatformUI ? 0 : 1; @@ -3142,8 +3881,8 @@ class _SfCalendarState extends State /// Calculate the total height of appointment views of week. for (int i = 0; i < dateAppointmentKeys.length; i++) { - final List _currentDateAppointment = - dateAppointments[dateAppointmentKeys[i]]; + final List _currentDateAppointment = + dateAppointments[dateAppointmentKeys[i]]!; final int eventsCount = _currentDateAppointment.length; int allDayEventCount = 0; @@ -3177,7 +3916,7 @@ class _SfCalendarState extends State /// Create the generated view details to store the view height /// and its intersection point. final _ScheduleViewDetails scheduleViewDetails = _ScheduleViewDetails(); - scheduleViewDetails._intersectPoint = 0; + scheduleViewDetails._intersectPoint = -1; double previousHeight = 0; /// Get the previous view end position used to find the next view end @@ -3185,11 +3924,11 @@ class _SfCalendarState extends State if (currentIndex >= 0) { previousHeight = currentIndex == 0 ? 0 - : _forwardWidgetHeights[currentIndex - 1]._height; + : _forwardWidgetHeights[currentIndex - 1]!._height; } else { previousHeight = currentIndex == -1 ? 0 - : _backwardWidgetHeights[-currentIndex - 2]._height; + : _backwardWidgetHeights[-currentIndex - 2]!._height; } final List widgets = []; @@ -3226,16 +3965,16 @@ class _SfCalendarState extends State /// views. bool isNeedInBetweenMonthBuilder = _useMobilePlatformUI ? startDate.month != - (isSameOrBeforeDate(_maxDate, endDate) ? endDate : _maxDate).month + (isSameOrBeforeDate(_maxDate!, endDate) ? endDate : _maxDate!).month : false; /// Check the end date month have appointments or not. bool isNextMonthHasNoAppointment = false; if (isNeedInBetweenMonthBuilder) { - final DateTime lastAppointmentDate = dateAppointmentKeys.isNotEmpty + final DateTime? lastAppointmentDate = dateAppointmentKeys.isNotEmpty ? dateAppointmentKeys[dateAppointmentKeys.length - 1] : null; - final DateTime nextWeekDate = index == -1 + final DateTime? nextWeekDate = index == -1 ? _nextDates[0] : (index < 0 ? _previousDates[-index - 2] @@ -3252,15 +3991,13 @@ class _SfCalendarState extends State /// an appointments but [hideEmptyScheduleWeek] enabled so the next view /// date month as different with current week end date week. isNextMonthHasNoAppointment = lastAppointmentDate == null || - (lastAppointmentDate != null && - lastAppointmentDate.month != endDate.month && + (lastAppointmentDate.month != endDate.month && nextWeekDate != null && nextWeekDate.month == endDate.month && nextWeekDate.year == endDate.year); isNeedInBetweenMonthBuilder = isNextMonthHasNoAppointment || - (lastAppointmentDate != null && - lastAppointmentDate.month != startDate.month); + lastAppointmentDate.month != startDate.month; } /// Add the in between month label height to total height when @@ -3301,8 +4038,8 @@ class _SfCalendarState extends State /// Generate views on week days that have appointments. for (int i = 0; i < dateAppointmentKeys.length; i++) { final DateTime currentDate = dateAppointmentKeys[i]; - final List currentAppointments = - dateAppointments[currentDate]; + final List currentAppointments = + dateAppointments[currentDate]!; final int eventsCount = currentAppointments.length; int allDayEventCount = 0; @@ -3311,12 +4048,7 @@ class _SfCalendarState extends State allDayEventCount = _getAllDayCount(currentAppointments); } - /// Check if the view intersection point not set and the current week date - /// month differ from the week start date then assign the intersection - /// point. - if (scheduleViewDetails._intersectPoint == 0 && - (startDate.month != currentDate.month || - startDate.year != currentDate.year)) { + void _addMonthHeaderView() { /// Assign the intersection point based on previous view end position. scheduleViewDetails._intersectPoint = currentIndex >= 0 ? previousHeight + interSectPoint + viewTopPadding @@ -3332,16 +4064,13 @@ class _SfCalendarState extends State } } - /// Check the display date view not added in widget and appointment - /// date is after of display date then add the display date view. - if (!isDisplayDateHighlightAdded && - currentDate.isAfter(scheduleDisplayDate)) { + void _addDisplayOrCurrentDateView({bool isDisplayDate = true}) { final double highlightViewStartPosition = currentIndex >= 0 ? previousHeight + interSectPoint : -(previousHeight + height - interSectPoint); widgets.add(_getDisplayDateView( isRTL, - scheduleDisplayDate, + isDisplayDate ? scheduleDisplayDate : scheduleCurrentDate, highlightViewStartPosition, viewPadding, appointmentViewHeaderHeight, @@ -3360,38 +4089,62 @@ class _SfCalendarState extends State /// because display date view height as single appointment view height interSectPoint += appointmentViewHeaderHeight + dividerHeight; topHeight += appointmentViewHeaderHeight + dividerHeight; - isDisplayDateHighlightAdded = true; + if (isDisplayDate) { + isDisplayDateHighlightAdded = true; + } else { + isCurrentDateHighlightAdded = true; + } + } + + /// Check the display date view not added in widget and appointment + /// date is after of display date then add the display date view. + /// Checking the current date month and display date month is required + /// Eg., if week (Feb 28 - Mar 6), Feb 28 does not have appointments + /// and Feb 28 is display date and Mar 1 have appointments then the view + /// order is month header(march), display date(feb 28), So check whether + /// current date(Mar 1) month not equal then add the display date view + /// before month header. + if (!isDisplayDateHighlightAdded && + currentDate.isAfter(scheduleDisplayDate) && + currentDate.month != scheduleDisplayDate.month) { + _addDisplayOrCurrentDateView(isDisplayDate: true); } /// Check the current date view not added in widget and appointment /// date is after of current date then add the current date view. + /// Checking the current date month and today date month is required + /// Eg., if week (Feb 28 - Mar 6), Feb 28 does not have appointments + /// and Feb 28 is today date and Mar 1 have appointments then the view + /// order is month header(march), today date(feb 28), So check whether + /// current date(Mar 1) month not equal then add the today date view + /// before month header. if (!isCurrentDateHighlightAdded && - currentDate.isAfter(scheduleCurrentDate)) { - final double highlightViewStartPosition = currentIndex >= 0 - ? previousHeight + interSectPoint - : -(previousHeight + height - interSectPoint); - widgets.add(_getDisplayDateView( - isRTL, - scheduleCurrentDate, - highlightViewStartPosition, - viewPadding, - appointmentViewHeaderHeight, - padding)); + currentDate.isAfter(scheduleCurrentDate) && + currentDate.month != scheduleCurrentDate.month) { + _addDisplayOrCurrentDateView(isDisplayDate: false); + } - /// Add divider at end of each of the week days in web view. - if (!_useMobilePlatformUI) { - widgets.add(Divider( - height: dividerHeight, - thickness: 1, - color: dividerColor, - )); - } + /// Check if the view intersection point not set and the current week date + /// month differ from the week start date then assign the intersection + /// point. + if (scheduleViewDetails._intersectPoint == -1 && + (startDate.month != currentDate.month || + startDate.year != currentDate.year)) { + _addMonthHeaderView(); + } - /// Add intersect value with appointment height and divider height - /// because current date view height as single appointment view height - interSectPoint += appointmentViewHeaderHeight + dividerHeight; - topHeight += appointmentViewHeaderHeight + dividerHeight; - isCurrentDateHighlightAdded = true; + /// Check the display date view not added in widget and appointment + /// date is after of display date then add the display date view. + if (!isDisplayDateHighlightAdded && + currentDate.isAfter(scheduleDisplayDate)) { + _addDisplayOrCurrentDateView(isDisplayDate: true); + } + + /// Check the current date view not added in widget and appointment + /// date is after of current date then add the current date view. + if (!isCurrentDateHighlightAdded && + currentDate.isAfter(scheduleCurrentDate)) { + _addDisplayOrCurrentDateView(isDisplayDate: false); } final double totalPadding = (eventsCount + 1) * padding; @@ -3410,12 +4163,17 @@ class _SfCalendarState extends State : -(previousHeight + height - interSectPoint); interSectPoint += appointmentViewPadding; - currentAppointments.sort((Appointment app1, Appointment app2) => - app1._actualStartTime.compareTo(app2._actualStartTime)); - currentAppointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1.isAllDay, app2.isAllDay)); - currentAppointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1._isSpanned, app2._isSpanned)); + currentAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime)); + currentAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isAllDay, app2.isAllDay)); + currentAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isSpanned, app2.isSpanned)); /// Add appointment view to the current views collection. widgets.add(MouseRegion( @@ -3453,7 +4211,7 @@ class _SfCalendarState extends State appointmentViewTopPadding, isRTL ? viewPadding : 0, appointmentViewTopPadding), - child: _AgendaViewLayout( + child: AgendaViewLayout( null, widget.scheduleViewSettings, currentDate, @@ -3482,7 +4240,8 @@ class _SfCalendarState extends State _controller.displayDate = currentDate; } - if (!_shouldRaiseCalendarTapCallback(widget.onTap)) { + if (!CalendarViewHelper.shouldRaiseCalendarTapCallback( + widget.onTap)) { return; } @@ -3500,7 +4259,8 @@ class _SfCalendarState extends State _controller.displayDate = currentDate; } - if (!_shouldRaiseCalendarLongPressCallback(widget.onLongPress)) { + if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( + widget.onLongPress)) { return; } @@ -3671,17 +4431,17 @@ class _SfCalendarState extends State } Widget _getMonthOrWeekHeader( - DateTime startDate, DateTime endDate, bool isRTL, bool isMonthLabel, + DateTime startDate, DateTime? endDate, bool isRTL, bool isMonthLabel, {double viewPadding = 0, bool isNeedTopPadding = false}) { const double padding = 5; - Widget headerWidget; + Widget? headerWidget; if (isMonthLabel && widget.scheduleViewMonthHeaderBuilder != null) { final ScheduleViewMonthHeaderDetails details = ScheduleViewMonthHeaderDetails( - date: DateTime(startDate.year, startDate.month, 1), - bounds: Rect.fromLTWH(0, 0, _minWidth, + DateTime(startDate.year, startDate.month, 1), + Rect.fromLTWH(0, 0, _minWidth, widget.scheduleViewSettings.monthHeaderSettings.height)); - headerWidget = widget.scheduleViewMonthHeaderBuilder(context, details); + headerWidget = widget.scheduleViewMonthHeaderBuilder!(context, details); } return GestureDetector( @@ -3723,29 +4483,35 @@ class _SfCalendarState extends State ))), onTapUp: (TapUpDetails details) { _removeDatePicker(); - if (!_shouldRaiseCalendarTapCallback(widget.onTap)) { + if (!CalendarViewHelper.shouldRaiseCalendarTapCallback( + widget.onTap)) { return; } - _raiseCalendarTapCallback(widget, - date: DateTime(startDate.year, startDate.month, startDate.day), - appointments: null, - element: isMonthLabel + CalendarViewHelper.raiseCalendarTapCallback( + widget, + DateTime(startDate.year, startDate.month, startDate.day), + null, + isMonthLabel ? CalendarElement.header - : CalendarElement.viewHeader); + : CalendarElement.viewHeader, + null); }, onLongPressStart: (LongPressStartDetails details) { _removeDatePicker(); - if (!_shouldRaiseCalendarLongPressCallback(widget.onLongPress)) { + if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( + widget.onLongPress)) { return; } - _raiseCalendarLongPressCallback(widget, - date: DateTime(startDate.year, startDate.month, startDate.day), - appointments: null, - element: isMonthLabel + CalendarViewHelper.raiseCalendarLongPressCallback( + widget, + DateTime(startDate.year, startDate.month, startDate.day), + null, + isMonthLabel ? CalendarElement.header - : CalendarElement.viewHeader); + : CalendarElement.viewHeader, + null); }); } @@ -3816,14 +4582,15 @@ class _SfCalendarState extends State _controller.displayDate = currentDisplayDate; } - if (!_shouldRaiseCalendarTapCallback(widget.onTap)) { + if (!CalendarViewHelper.shouldRaiseCalendarTapCallback( + widget.onTap)) { return; } _raiseCallbackForScheduleView( currentDisplayDate, details.localPosition, - [], + [], viewHeaderWidth, padding, true, @@ -3840,14 +4607,15 @@ class _SfCalendarState extends State _controller.displayDate = currentDisplayDate; } - if (!_shouldRaiseCalendarLongPressCallback(widget.onLongPress)) { + if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( + widget.onLongPress)) { return; } _raiseCallbackForScheduleView( currentDisplayDate, details.localPosition, - [], + [], viewHeaderWidth, padding, false, @@ -3859,7 +4627,7 @@ class _SfCalendarState extends State void _raiseCallbackForScheduleView( DateTime currentDate, Offset offset, - List appointments, + List appointments, double viewHeaderWidth, double padding, bool isTapCallback, @@ -3867,58 +4635,64 @@ class _SfCalendarState extends State /// Check the touch position on day label if ((!_isRTL && viewHeaderWidth >= offset.dx) || (_isRTL && _minWidth - viewHeaderWidth < offset.dx)) { - final List currentAppointments = []; + final List currentAppointments = + []; for (int i = 0; i < appointments.length; i++) { - final Appointment appointment = appointments[i]; + final CalendarAppointment appointment = appointments[i]; currentAppointments.add(appointment); } if (isTapCallback) { - _raiseCalendarTapCallback(widget, - date: - DateTime(currentDate.year, currentDate.month, currentDate.day), - appointments: widget.dataSource != null && - !_isCalendarAppointment(widget.dataSource) - ? _getCustomAppointments(currentAppointments) + CalendarViewHelper.raiseCalendarTapCallback( + widget, + DateTime(currentDate.year, currentDate.month, currentDate.day), + widget.dataSource != null && + !AppointmentHelper.isCalendarAppointment(widget.dataSource!) + ? CalendarViewHelper.getCustomAppointments(currentAppointments) : currentAppointments, - element: CalendarElement.viewHeader); + CalendarElement.viewHeader, + null); } else { - _raiseCalendarLongPressCallback(widget, - date: - DateTime(currentDate.year, currentDate.month, currentDate.day), - appointments: widget.dataSource != null && - !_isCalendarAppointment(widget.dataSource) - ? _getCustomAppointments(currentAppointments) + CalendarViewHelper.raiseCalendarLongPressCallback( + widget, + DateTime(currentDate.year, currentDate.month, currentDate.day), + widget.dataSource != null && + !AppointmentHelper.isCalendarAppointment(widget.dataSource!) + ? CalendarViewHelper.getCustomAppointments(currentAppointments) : currentAppointments, - element: CalendarElement.viewHeader); + CalendarElement.viewHeader, + null); } } else { /// Calculate the touch position appointment from its collection. double currentYPosition = padding; - final double itemHeight = - _getScheduleAppointmentHeight(null, widget.scheduleViewSettings); - final double allDayItemHeight = _getScheduleAllDayAppointmentHeight( + final double itemHeight = CalendarViewHelper.getScheduleAppointmentHeight( null, widget.scheduleViewSettings); + final double allDayItemHeight = + CalendarViewHelper.getScheduleAllDayAppointmentHeight( + null, widget.scheduleViewSettings); if (isDisplayDate) { if (isTapCallback) { - _raiseCalendarTapCallback(widget, - date: DateTime( - currentDate.year, currentDate.month, currentDate.day), - appointments: null, - element: CalendarElement.calendarCell); + CalendarViewHelper.raiseCalendarTapCallback( + widget, + DateTime(currentDate.year, currentDate.month, currentDate.day), + null, + CalendarElement.calendarCell, + null); } else { - _raiseCalendarLongPressCallback(widget, - date: DateTime( - currentDate.year, currentDate.month, currentDate.day), - appointments: null, - element: CalendarElement.calendarCell); + CalendarViewHelper.raiseCalendarLongPressCallback( + widget, + DateTime(currentDate.year, currentDate.month, currentDate.day), + null, + CalendarElement.calendarCell, + null); } return; } for (int k = 0; k < appointments.length; k++) { - final Appointment appointment = appointments[k]; + final CalendarAppointment appointment = appointments[k]; final double currentAppointmentHeight = (_useMobilePlatformUI && _isAllDayAppointmentView(appointment) ? allDayItemHeight @@ -3926,26 +4700,32 @@ class _SfCalendarState extends State padding; if (currentYPosition <= offset.dy && currentYPosition + currentAppointmentHeight > offset.dy) { - final List selectedAppointment = [] - ..add(appointment); + final List selectedAppointment = + []..add(appointment); if (isTapCallback) { - _raiseCalendarTapCallback(widget, - date: DateTime( - currentDate.year, currentDate.month, currentDate.day), - appointments: widget.dataSource != null && - !_isCalendarAppointment(widget.dataSource) - ? _getCustomAppointments(selectedAppointment) + CalendarViewHelper.raiseCalendarTapCallback( + widget, + DateTime(currentDate.year, currentDate.month, currentDate.day), + widget.dataSource != null && + !AppointmentHelper.isCalendarAppointment( + widget.dataSource!) + ? CalendarViewHelper.getCustomAppointments( + selectedAppointment) : selectedAppointment, - element: CalendarElement.appointment); + CalendarElement.appointment, + null); } else { - _raiseCalendarLongPressCallback(widget, - date: DateTime( - currentDate.year, currentDate.month, currentDate.day), - appointments: widget.dataSource != null && - !_isCalendarAppointment(widget.dataSource) - ? _getCustomAppointments(selectedAppointment) + CalendarViewHelper.raiseCalendarLongPressCallback( + widget, + DateTime(currentDate.year, currentDate.month, currentDate.day), + widget.dataSource != null && + !AppointmentHelper.isCalendarAppointment( + widget.dataSource!) + ? CalendarViewHelper.getCustomAppointments( + selectedAppointment) : selectedAppointment, - element: CalendarElement.appointment); + CalendarElement.appointment, + null); } break; } @@ -3967,7 +4747,7 @@ class _SfCalendarState extends State } final DateTime scheduleDisplayDate = - getValidDate(widget.minDate, widget.maxDate, _controller.displayDate); + getValidDate(widget.minDate, widget.maxDate, _scheduleDisplayDate); final DateTime scheduleCurrentDate = DateTime.now(); final DateTime currentMaxDate = scheduleDisplayDate.isAfter(scheduleCurrentDate) @@ -3992,11 +4772,11 @@ class _SfCalendarState extends State /// Assign minimum date value to current minimum date when the minimum /// date is before of current minimum date - _minDate = _minDate.isAfter(currentMinDate) ? currentMinDate : _minDate; - _minDate = _minDate.isBefore(widget.minDate) ? widget.minDate : _minDate; + _minDate = _minDate!.isAfter(currentMinDate) ? currentMinDate : _minDate; + _minDate = _minDate!.isBefore(widget.minDate) ? widget.minDate : _minDate; final DateTime viewMinDate = - addDuration(_minDate, Duration(days: -_minDate.weekday)); + addDays(_minDate, -(_minDate!.weekday % DateTime.daysPerWeek)); /// Get the maximum date of schedule view when it value as null /// It return max date user assigned when the [hideEmptyAgendaDays] @@ -4012,21 +4792,23 @@ class _SfCalendarState extends State /// Assign maximum date value to current maximum date when the maximum /// date is before of current maximum date - _maxDate = _maxDate.isBefore(currentMaxDate) ? currentMaxDate : _maxDate; - _maxDate = _maxDate.isAfter(widget.maxDate) ? widget.maxDate : _maxDate; + _maxDate = _maxDate!.isBefore(currentMaxDate) ? currentMaxDate : _maxDate; + _maxDate = _maxDate!.isAfter(widget.maxDate) ? widget.maxDate : _maxDate; final double appointmentViewHeight = - _getScheduleAppointmentHeight(null, widget.scheduleViewSettings); + CalendarViewHelper.getScheduleAppointmentHeight( + null, widget.scheduleViewSettings); final double allDayAppointmentHeight = - _getScheduleAllDayAppointmentHeight(null, widget.scheduleViewSettings); + CalendarViewHelper.getScheduleAllDayAppointmentHeight( + null, widget.scheduleViewSettings); /// Get the view first date based on specified /// display date and first day of week. - int value = -scheduleDisplayDate.weekday + + int value = -(scheduleDisplayDate.weekday % DateTime.daysPerWeek) + widget.firstDayOfWeek - - _kNumberOfDaysInWeek; - if (value.abs() >= _kNumberOfDaysInWeek) { - value += _kNumberOfDaysInWeek; + DateTime.daysPerWeek; + if (value.abs() >= DateTime.daysPerWeek) { + value += DateTime.daysPerWeek; } if (_previousDates.isEmpty) { @@ -4034,15 +4816,14 @@ class _SfCalendarState extends State /// collection as empty. DateTime date = _nextDates.isNotEmpty ? _nextDates[0] - : addDuration(scheduleDisplayDate, Duration(days: value)); + : addDays(scheduleDisplayDate, value); int count = 0; /// Using while for calculate dates because if [hideEmptyAgendaDays] as /// enabled, then it hides the weeks when it does not have appointments. while (count < 50) { for (int i = 1; i <= 100; i++) { - final DateTime updatedDate = - addDuration(date, Duration(days: -i * _kNumberOfDaysInWeek)); + final DateTime updatedDate = addDays(date, -i * DateTime.daysPerWeek); /// Skip week dates before min date if (!isSameOrAfterDate(viewMinDate, updatedDate)) { @@ -4050,8 +4831,7 @@ class _SfCalendarState extends State break; } - final DateTime weekEndDate = - addDuration(updatedDate, const Duration(days: 6)); + final DateTime weekEndDate = addDays(updatedDate, 6); /// Skip the week date when it does not have appointments /// when [hideEmptyAgendaDays] as enabled. @@ -4075,7 +4855,7 @@ class _SfCalendarState extends State if (_previousDates.isEmpty) { for (int i = 0; i < _nextDates.length; i++) { final DateTime date = _nextDates[i]; - if (isSameDate(date, _currentDate)) { + if (isSameDate(date, updatedDate)) { isEqualDate = true; break; } @@ -4090,21 +4870,20 @@ class _SfCalendarState extends State count++; } - date = addDuration(date, const Duration(days: -700)); + date = addDays(date, -700); } } if (_nextDates.isEmpty) { /// Calculate the start date from display date - DateTime date = addDuration(scheduleDisplayDate, Duration(days: value)); + DateTime date = addDays(scheduleDisplayDate, value); int count = 0; /// Using while for calculate dates because if [hideEmptyAgendaDays] as /// enabled, then it hides the weeks when it does not have appointments. while (count < 50) { for (int i = 0; i < 100; i++) { - final DateTime updatedDate = - addDuration(date, Duration(days: i * _kNumberOfDaysInWeek)); + final DateTime updatedDate = addDays(date, i * DateTime.daysPerWeek); /// Skip week date after max date if (!isSameOrBeforeDate(_maxDate, updatedDate)) { @@ -4112,8 +4891,7 @@ class _SfCalendarState extends State break; } - final DateTime weekEndDate = - addDuration(updatedDate, const Duration(days: 6)); + final DateTime weekEndDate = addDays(updatedDate, 6); /// Skip the week date when it does not have appointments /// when [hideEmptyAgendaDays] as enabled. @@ -4131,7 +4909,7 @@ class _SfCalendarState extends State count++; } - date = addDuration(date, const Duration(days: 700)); + date = addDays(date, 700); } } @@ -4160,25 +4938,26 @@ class _SfCalendarState extends State /// previous view dates and calculate the same until the next view dates /// appointment fills the view port. DateTime viewStartDate = _nextDates[0]; - DateTime viewEndDate = addDuration( - _nextDates[_nextDates.length - 1], const Duration(days: 6)); - List appointmentCollection = _getVisibleAppointments( - viewStartDate, - isSameOrBeforeDate(_maxDate, viewEndDate) ? viewEndDate : _maxDate, - _appointments, - widget.timeZone, - false); + DateTime viewEndDate = addDays(_nextDates[_nextDates.length - 1], 6); + List appointmentCollection = + AppointmentHelper.getVisibleAppointments( + viewStartDate, + isSameOrBeforeDate(_maxDate!, viewEndDate) + ? viewEndDate + : _maxDate!, + _appointments, + widget.timeZone, + false); const double padding = 5; - Map> dateAppointments = + Map> dateAppointments = _getAppointmentCollectionOnDateBasis( appointmentCollection, viewStartDate, viewEndDate); List dateAppointmentKeys = dateAppointments.keys.toList(); double labelHeight = 0; if (_useMobilePlatformUI) { - DateTime previousDate = - addDuration(viewStartDate, const Duration(days: -1)); + DateTime previousDate = addDays(viewStartDate, -1); for (int i = 0; i < _nextDates.length; i++) { final DateTime nextDate = _nextDates[i]; if (previousDate.month != nextDate.month) { @@ -4195,8 +4974,8 @@ class _SfCalendarState extends State int allDayCount = 0; int numberOfEvents = 0; for (int i = 0; i < dateAppointmentKeys.length; i++) { - final List currentDateAppointment = - dateAppointments[dateAppointmentKeys[i]]; + final List currentDateAppointment = + dateAppointments[dateAppointmentKeys[i]]!; if (_useMobilePlatformUI) { allDayCount += _getAllDayCount(currentDateAppointment); } @@ -4219,15 +4998,15 @@ class _SfCalendarState extends State isNewDatesAdded = true; viewStartDate = currentDate; - viewEndDate = addDuration(currentDate, const Duration(days: 6)); + viewEndDate = addDays(currentDate, 6); /// Calculate the newly added date appointment height and add /// the height to existing appointments height. - appointmentCollection = _getVisibleAppointments( + appointmentCollection = AppointmentHelper.getVisibleAppointments( viewStartDate, - isSameOrBeforeDate(_maxDate, viewEndDate) + isSameOrBeforeDate(_maxDate!, viewEndDate) ? viewEndDate - : _maxDate, + : _maxDate!, _appointments, widget.timeZone, false); @@ -4248,8 +5027,8 @@ class _SfCalendarState extends State appointmentCollection, viewStartDate, viewEndDate); dateAppointmentKeys = dateAppointments.keys.toList(); for (int i = 0; i < dateAppointmentKeys.length; i++) { - final List currentDateAppointment = - dateAppointments[dateAppointmentKeys[i]]; + final List currentDateAppointment = + dateAppointments[dateAppointmentKeys[i]]!; if (_useMobilePlatformUI) { allDayCount += _getAllDayCount(currentDateAppointment); } @@ -4277,21 +5056,20 @@ class _SfCalendarState extends State /// in between space between the May 23 to May 28 and assign the value to /// scroll controller initial scroll position if (_nextDates.isNotEmpty && - _agendaScrollController.initialScrollOffset == 0 && - !_agendaScrollController.hasClients) { + _agendaScrollController!.initialScrollOffset == 0 && + !_agendaScrollController!.hasClients) { final DateTime viewStartDate = _nextDates[0]; - final DateTime viewEndDate = - addDuration(viewStartDate, const Duration(days: 6)); + final DateTime viewEndDate = addDays(viewStartDate, 6); if (viewStartDate.isBefore(scheduleDisplayDate) && !isSameDate(viewStartDate, scheduleDisplayDate) && isSameOrBeforeDate(viewEndDate, scheduleDisplayDate)) { - final DateTime viewEndDate = - addDuration(scheduleDisplayDate, const Duration(days: -1)); + final DateTime viewEndDate = addDays(scheduleDisplayDate, -1); /// Calculate the appointment between the week start date and /// previous date of display date to calculate the scrolling position. - final List appointmentCollection = _getVisibleAppointments( - viewStartDate, viewEndDate, _appointments, widget.timeZone, false); + final List appointmentCollection = + AppointmentHelper.getVisibleAppointments(viewStartDate, viewEndDate, + _appointments, widget.timeZone, false); const double padding = 5; @@ -4307,7 +5085,7 @@ class _SfCalendarState extends State /// Skip the scrolling when the previous week dates of display date /// does not have a appointment. if (appointmentCollection.isNotEmpty) { - final Map> dateAppointments = + final Map> dateAppointments = _getAppointmentCollectionOnDateBasis( appointmentCollection, viewStartDate, viewEndDate); final List dateAppointmentKeys = @@ -4315,8 +5093,8 @@ class _SfCalendarState extends State double totalAppointmentHeight = 0; for (int i = 0; i < dateAppointmentKeys.length; i++) { final DateTime currentDate = dateAppointmentKeys[i]; - final List currentDateAppointment = - dateAppointments[currentDate]; + final List currentDateAppointment = + dateAppointments[currentDate]!; final int eventsCount = currentDateAppointment.length; int allDayEventCount = 0; @@ -4352,21 +5130,21 @@ class _SfCalendarState extends State (!_useMobilePlatformUI ? 0 : widget.scheduleViewSettings.weekHeaderSettings.height) + - (viewStartDate.month == _controller.displayDate.month && + (viewStartDate.month == _scheduleDisplayDate.month && viewStartDate.day != 1 ? 0 : (!_useMobilePlatformUI ? 0 : widget.scheduleViewSettings.monthHeaderSettings.height + padding)); - _agendaScrollController.removeListener(_handleScheduleViewScrolled); + _agendaScrollController?.removeListener(_handleScheduleViewScrolled); _agendaScrollController = ScrollController(initialScrollOffset: scrolledPosition) ..addListener(_handleScheduleViewScrolled); - } else if ((viewStartDate.month != _controller.displayDate.month && + } else if ((viewStartDate.month != _scheduleDisplayDate.month && _useMobilePlatformUI) || todayNewEventHeight != 0) { - _agendaScrollController.removeListener(_handleScheduleViewScrolled); + _agendaScrollController?.removeListener(_handleScheduleViewScrolled); _agendaScrollController = ScrollController( initialScrollOffset: (!_useMobilePlatformUI ? 0 @@ -4389,35 +5167,38 @@ class _SfCalendarState extends State color: widget.headerStyle.backgroundColor ?? _calendarTheme.headerBackgroundColor, child: _CalendarHeaderView( - _currentViewVisibleDates, - widget.headerStyle, - null, - _view, - widget.monthViewSettings.numberOfWeeksInView, - _calendarTheme, - isRTL, - _locale, - widget.showNavigationArrow, - _controller, - widget.maxDate, - widget.minDate, - _minWidth, - widget.headerHeight, - widget.timeSlotViewSettings.nonWorkingDays, - widget.monthViewSettings.navigationDirection, - widget.showDatePickerButton, - _showHeader, - widget.allowedViews, - widget.allowViewNavigation, - _localizations, - _removeDatePicker, - _headerUpdateNotifier, - _viewChangeNotifier, - _handleOnTapForHeader, - _handleOnLongPressForHeader, - widget.todayHighlightColor, - _textScaleFactor, - _isMobilePlatform)), + _currentViewVisibleDates, + widget.headerStyle, + null, + _view, + widget.monthViewSettings.numberOfWeeksInView, + _calendarTheme, + isRTL, + _locale, + widget.showNavigationArrow, + _controller, + widget.maxDate, + widget.minDate, + _minWidth, + widget.headerHeight, + widget.timeSlotViewSettings.nonWorkingDays, + widget.monthViewSettings.navigationDirection, + widget.showDatePickerButton, + _showHeader, + widget.allowedViews, + widget.allowViewNavigation, + _localizations, + _removeDatePicker, + _headerUpdateNotifier, + _viewChangeNotifier, + _handleOnTapForHeader, + _handleOnLongPressForHeader, + widget.todayHighlightColor, + _textScaleFactor, + _isMobilePlatform, + widget.headerDateFormat, + true, + )), ), ), Positioned( @@ -4425,7 +5206,7 @@ class _SfCalendarState extends State left: 0, right: 0, height: height, - child: Opacity( + child: _OpacityWidget( opacity: _opacity, child: CustomScrollView( key: _scrollKey, @@ -4463,44 +5244,874 @@ class _SfCalendarState extends State ]); } - void _updateViewChangePopup() { - if (!mounted) { - return; + Widget addAgendaWithLoadMore(double height, bool isRTL) { + final bool hideEmptyAgendaDays = + widget.scheduleViewSettings.hideEmptyScheduleWeek || + !_useMobilePlatformUI; + + /// return empty view when [hideEmptyAgendaDays] enabled and + /// the appointments as empty. + if (!_timeZoneLoaded) { + return Container(); } - if (widget.showDatePickerButton && _showHeader) { - _showHeader = false; + final DateTime scheduleDisplayDate = + getValidDate(widget.minDate, widget.maxDate, _scheduleDisplayDate); + final DateTime scheduleCurrentDate = DateTime.now(); + + _scheduleMinDate ??= scheduleDisplayDate; + _scheduleMaxDate ??= scheduleDisplayDate; + _minDate ??= _scheduleMinDate; + _maxDate ??= _scheduleMaxDate; + if (!_isNeedLoadMore && !_isScheduleStartLoadMore) { + _minDate = _scheduleMinDate; + _maxDate = _scheduleMaxDate; } - setState(() {}); - } + final DateTime viewMinDate = + addDays(_minDate, -(_minDate!.weekday % DateTime.daysPerWeek)); - Widget _getCalendarViewPopup() { - if (widget.allowedViews == null || - widget.allowedViews.isEmpty || - !_viewChangeNotifier.value) { - return Container(); + final double appointmentViewHeight = + CalendarViewHelper.getScheduleAppointmentHeight( + null, widget.scheduleViewSettings); + final double allDayAppointmentHeight = + CalendarViewHelper.getScheduleAllDayAppointmentHeight( + null, widget.scheduleViewSettings); + + /// Get the view first date based on specified + /// display date and first day of week. + int value = -(scheduleDisplayDate.weekday % DateTime.daysPerWeek) + + widget.firstDayOfWeek - + DateTime.daysPerWeek; + if (value.abs() >= DateTime.daysPerWeek) { + value += DateTime.daysPerWeek; } - final double calendarViewTextHeight = 40; - final List children = []; - double width = 0; - Color headerTextColor = widget.headerStyle.textStyle != null - ? widget.headerStyle.textStyle.color - : (_calendarTheme.headerTextStyle.color); - headerTextColor ??= Colors.black87; - final TextStyle style = TextStyle(color: headerTextColor, fontSize: 12); - int selectedIndex = -1; - final Color todayColor = - widget.todayHighlightColor ?? _calendarTheme.todayHighlightColor; + if (_previousDates.isEmpty || + !isSameDate(_previousDates[_previousDates.length - 1], viewMinDate)) { + /// Calculate the start date from display date if next view dates + /// collection as empty. + DateTime date = _previousDates.isNotEmpty + ? _previousDates[_previousDates.length - 1] + : (_nextDates.isNotEmpty + ? _nextDates[0] + : addDays(scheduleDisplayDate, value)); + int count = 0; - final Map calendarViews = - _getCalendarViewsText(_localizations); + /// Using while for calculate dates because if [hideEmptyAgendaDays] as + /// enabled, then it hides the weeks when it does not have appointments. + while (count < 50) { + for (int i = 1; i <= 100; i++) { + final DateTime updatedDate = addDays(date, -i * DateTime.daysPerWeek); - /// Generate the calendar view pop up content views. - for (int i = 0; i < widget.allowedViews.length; i++) { - final CalendarView view = widget.allowedViews[i]; - final double textWidth = _getTextWidgetWidth( + /// Skip week dates before min date + if (!isSameOrAfterDate(viewMinDate, updatedDate)) { + count = 50; + break; + } + + final DateTime weekEndDate = addDays(updatedDate, 6); + + /// Skip the week date when it does not have appointments + /// when [hideEmptyAgendaDays] as enabled. + if (hideEmptyAgendaDays && + !_isAppointmentBetweenDates( + _appointments, updatedDate, weekEndDate, widget.timeZone) && + !isDateWithInDateRange( + updatedDate, weekEndDate, scheduleDisplayDate) && + !isDateWithInDateRange( + updatedDate, weekEndDate, scheduleCurrentDate)) { + continue; + } + + bool isEqualDate = false; + + /// Check the date placed in next dates collection, when + /// previous dates collection is empty. + /// Eg., if [hideEmptyAgendaDays] property enabled but after the + /// display date does not have a appointment then the previous + /// dates collection initial dates added to next dates. + if (_previousDates.isEmpty) { + for (int i = 0; i < _nextDates.length; i++) { + final DateTime date = _nextDates[i]; + if (isSameDate(date, updatedDate)) { + isEqualDate = true; + break; + } + } + } + + if (isEqualDate) { + continue; + } + + _previousDates.add(updatedDate); + count++; + } + + date = addDays(date, -700); + } + } + + final DateTime viewMaxDate = addDays(_maxDate, + (DateTime.daysPerWeek - _maxDate!.weekday) % DateTime.daysPerWeek); + if (_nextDates.isEmpty || + !isSameDate(_nextDates[_nextDates.length - 1], viewMaxDate)) { + /// Calculate the start date from display date + DateTime date = _nextDates.isEmpty + ? addDays(scheduleDisplayDate, value) + : addDays(_nextDates[_nextDates.length - 1], DateTime.daysPerWeek); + int count = 0; + + /// Using while for calculate dates because if [hideEmptyAgendaDays] as + /// enabled, then it hides the weeks when it does not have appointments. + while (count < 50) { + for (int i = 0; i < 100; i++) { + final DateTime updatedDate = addDays(date, i * DateTime.daysPerWeek); + + /// Skip week date after max date + if (!isSameOrBeforeDate(_maxDate, updatedDate)) { + count = 50; + break; + } + + final DateTime weekEndDate = addDays(updatedDate, 6); + + /// Skip the week date when it does not have appointments + /// when [hideEmptyAgendaDays] as enabled. + if (hideEmptyAgendaDays && + !_isAppointmentBetweenDates( + _appointments, updatedDate, weekEndDate, widget.timeZone) && + !isDateWithInDateRange( + updatedDate, weekEndDate, scheduleDisplayDate) && + !isDateWithInDateRange( + updatedDate, weekEndDate, scheduleCurrentDate)) { + continue; + } + + _nextDates.add(updatedDate); + count++; + } + + date = addDays(date, 700); + } + } + + /// Calculate the next views dates when [hideEmptyAgendaDays] property + /// enabled but after the display date does not have a appointment to the + /// viewport then the previous dates collection initial dates added to next + /// dates. + if (_nextDates.length < 10 && _previousDates.isNotEmpty) { + double totalHeight = 0; + + /// This boolean variable is used to check whether the previous dates + /// collection dates added to next dates collection or not. + bool isNewDatesAdded = false; + + /// Add the previous view dates start date to next dates collection and + /// remove the date from previous dates collection when next dates as + /// empty. + if (_nextDates.isEmpty) { + isNewDatesAdded = true; + _nextDates.add(_previousDates[0]); + _previousDates.removeAt(0); + } + + /// Calculate the next dates collection appointments height to check + /// the appointments fill the view port or not, if not then add another + /// previous view dates and calculate the same until the next view dates + /// appointment fills the view port. + DateTime viewStartDate = _nextDates[0]; + DateTime viewEndDate = addDays(_nextDates[_nextDates.length - 1], 6); + List appointmentCollection = + AppointmentHelper.getVisibleAppointments( + viewStartDate, + isSameOrBeforeDate(_maxDate!, viewEndDate) + ? viewEndDate + : _maxDate!, + _appointments, + widget.timeZone, + false); + + const double padding = 5; + Map> dateAppointments = + _getAppointmentCollectionOnDateBasis( + appointmentCollection, viewStartDate, viewEndDate); + List dateAppointmentKeys = dateAppointments.keys.toList(); + + double labelHeight = 0; + if (_useMobilePlatformUI) { + DateTime previousDate = addDays(viewStartDate, -1); + for (int i = 0; i < _nextDates.length; i++) { + final DateTime nextDate = _nextDates[i]; + if (previousDate.month != nextDate.month) { + labelHeight += + widget.scheduleViewSettings.monthHeaderSettings.height + + padding; + } + + previousDate = nextDate; + labelHeight += widget.scheduleViewSettings.weekHeaderSettings.height; + } + } + + int allDayCount = 0; + int numberOfEvents = 0; + for (int i = 0; i < dateAppointmentKeys.length; i++) { + final List currentDateAppointment = + dateAppointments[dateAppointmentKeys[i]]!; + if (_useMobilePlatformUI) { + allDayCount += _getAllDayCount(currentDateAppointment); + } + + numberOfEvents += currentDateAppointment.length; + } + + /// Check the next dates collection appointments height fills the view + /// port or not, if not then add another previous view dates and calculate + /// the same until the next view dates appointments fills the view port. + while (totalHeight < height && + (_previousDates.isNotEmpty || totalHeight == 0)) { + /// Initially appointment height as 0 and check the existing dates + /// appointment fills the view port or not. if not then add + /// another previous view dates + if (totalHeight != 0) { + final DateTime currentDate = _previousDates[0]; + _nextDates.insert(0, currentDate); + _previousDates.removeAt(0); + isNewDatesAdded = true; + + viewStartDate = currentDate; + viewEndDate = addDays(currentDate, 6); + + /// Calculate the newly added date appointment height and add + /// the height to existing appointments height. + appointmentCollection = AppointmentHelper.getVisibleAppointments( + viewStartDate, + isSameOrBeforeDate(_maxDate!, viewEndDate) + ? viewEndDate + : _maxDate!, + _appointments, + widget.timeZone, + false); + + if (_useMobilePlatformUI) { + final DateTime nextDate = _nextDates[1]; + if (nextDate.month != viewStartDate.month) { + labelHeight += + widget.scheduleViewSettings.monthHeaderSettings.height + + padding; + } + + labelHeight += + widget.scheduleViewSettings.weekHeaderSettings.height; + } + + dateAppointments = _getAppointmentCollectionOnDateBasis( + appointmentCollection, viewStartDate, viewEndDate); + dateAppointmentKeys = dateAppointments.keys.toList(); + for (int i = 0; i < dateAppointmentKeys.length; i++) { + final List currentDateAppointment = + dateAppointments[dateAppointmentKeys[i]]!; + if (_useMobilePlatformUI) { + allDayCount += _getAllDayCount(currentDateAppointment); + } + + numberOfEvents += currentDateAppointment.length; + } + } + + totalHeight = ((numberOfEvents + 1) * padding) + + ((numberOfEvents - allDayCount) * appointmentViewHeight) + + (allDayCount * allDayAppointmentHeight) + + labelHeight; + } + + /// Update the header date because the next dates insert the previous view + /// dates at initial position. + if (_nextDates.isNotEmpty && isNewDatesAdded) { + final DateTime date = _nextDates[0]; + _headerUpdateNotifier.value = _useMobilePlatformUI + ? date + : getValidDate(_minDate, _maxDate, date); + } + } + + /// Check whether the schedule view initially loading because initially + /// schedule display date and schedule loaded min date values are equal. + final bool isMinDisplayDate = + isSameDate(_scheduleMinDate, scheduleDisplayDate); + + /// Check whether the schedule view initially loading because initially + /// schedule display date and schedule loaded max date values are equal. + final bool isMaxDisplayDate = + isSameDate(_scheduleMaxDate, scheduleDisplayDate); + final bool isInitialLoadMore = isMinDisplayDate && + isMaxDisplayDate && + widget.loadMoreWidgetBuilder != null; + DateTime visibleMinDate = + AppointmentHelper.getMonthStartDate(scheduleDisplayDate); + DateTime visibleMaxDate = + AppointmentHelper.getMonthEndDate(scheduleDisplayDate); + + if (!isSameOrBeforeDate(widget.maxDate, visibleMaxDate)) { + visibleMaxDate = widget.maxDate; + } + + if (!isSameOrAfterDate(widget.minDate, visibleMinDate)) { + visibleMinDate = widget.minDate; + } + + /// The below codes used to scroll the view to current display date. + /// If display date as May 29, 2020 then its week day as friday but first + /// day of week as sunday then May 23, 2020 as shown, calculate the + /// in between space between the May 23 to May 28 and assign the value to + /// scroll controller initial scroll position + if (_nextDates.isNotEmpty && + _agendaScrollController!.initialScrollOffset == 0 && + !isInitialLoadMore && + !_isNeedLoadMore && + !_isScheduleStartLoadMore && + isSameDate(visibleMaxDate, _scheduleMaxDate) && + isSameDate(visibleMinDate, _scheduleMinDate)) { + DateTime viewStartDate = _nextDates[0]; + const double padding = 5; + final double appointmentViewHeight = + CalendarViewHelper.getScheduleAppointmentHeight( + null, widget.scheduleViewSettings); + final double allDayAppointmentHeight = + CalendarViewHelper.getScheduleAllDayAppointmentHeight( + null, widget.scheduleViewSettings); + + /// Calculate the day label(May, 25) height based on appointment height + /// and assign the label maximum height as 60. + double appointmentViewHeaderHeight = + appointmentViewHeight + (2 * padding); + if (_useMobilePlatformUI) { + appointmentViewHeaderHeight = + appointmentViewHeaderHeight > 60 ? 60 : appointmentViewHeaderHeight; + } + + /// Calculate the divider height and color when it is web view. + final double dividerHeight = _useMobilePlatformUI ? 0 : 1; + + /// Holds the height of 'No Events' label view. + final double displayEventHeight = _useMobilePlatformUI + ? appointmentViewHeaderHeight + : appointmentViewHeaderHeight + dividerHeight; + + /// Holds the heights of each weeks in month on initial loading. + /// Eg., holds Feb 1, 2021 to Feb 28, 2021 month weeks height. + final List heights = []; + + /// holds the total height of the month on initial loading. + /// /// Eg., holds Feb 1, 2021 to Feb 28, 2021 month total height. + double totalHeight = 0; + + /// While used to calculate the height of current month weeks on initial + /// loading. + while (isSameOrBeforeDate(_maxDate, viewStartDate)) { + final DateTime viewEndDate = + addDays(viewStartDate, DateTime.daysPerWeek - 1); + final DateTime appStartDate = + isSameOrAfterDate(_minDate!, viewStartDate) + ? viewStartDate + : _minDate!; + final DateTime appEndDate = isSameOrBeforeDate(_maxDate!, viewEndDate) + ? viewEndDate + : _maxDate!; + + /// Today date view height. + double todayNewEventHeight = isDateWithInDateRange( + viewStartDate, viewEndDate, scheduleCurrentDate) + ? displayEventHeight + : 0; + + /// Display date view height. + double displayNewEventHeight = isDateWithInDateRange( + viewStartDate, viewEndDate, scheduleDisplayDate) + ? displayEventHeight + : 0; + + /// Current week appointments heights. + final List appointmentCollection = + AppointmentHelper.getVisibleAppointments( + appStartDate, appEndDate, _appointments, widget.timeZone, false, + canCreateNewAppointment: false); + + /// Check the week date needs month header or not. + final bool isNeedMonthBuilder = _useMobilePlatformUI + ? (viewStartDate.month != appEndDate.month || + viewStartDate.year != appEndDate.year) || + viewStartDate.day == 1 + : false; + + /// Web view does not have month label. + double currentWeekHeight = isNeedMonthBuilder + ? widget.scheduleViewSettings.monthHeaderSettings.height + : 0; + + /// Add the week header height to the current view height. + /// web view does not have week label. + currentWeekHeight += _useMobilePlatformUI + ? widget.scheduleViewSettings.weekHeaderSettings.height + : 0; + + if (appointmentCollection.isNotEmpty) { + /// Get the collection of appointment collection listed by date. + final Map> dateAppointments = + _getAppointmentCollectionOnDateBasis( + appointmentCollection, appStartDate, appEndDate); + final List dateAppointmentKeys = + dateAppointments.keys.toList(); + + int numberOfEvents = 0; + + double appointmentHeight = 0; + + /// Calculate the total height of appointment views of week. + for (int i = 0; i < dateAppointmentKeys.length; i++) { + final DateTime currentDateKey = dateAppointmentKeys[i]; + final List _currentDateAppointment = + dateAppointments[currentDateKey]!; + + /// Assign today no event label height as 0 when today date have + /// appointments. + if (todayNewEventHeight != 0 && + isSameDate(scheduleCurrentDate, currentDateKey)) { + todayNewEventHeight = 0; + } + + /// Assign display date no event label height as 0 when display + /// date have appointments. + if (displayNewEventHeight != 0 && + isSameDate(scheduleDisplayDate, currentDateKey)) { + displayNewEventHeight = 0; + } + + final int eventsCount = _currentDateAppointment.length; + int allDayEventCount = 0; + + /// Web view does not differentiate all day and normal appointment. + if (_useMobilePlatformUI) { + allDayEventCount = _getAllDayCount(_currentDateAppointment); + } + + double panelHeight = + ((eventsCount - allDayEventCount) * appointmentViewHeight) + + (allDayEventCount * allDayAppointmentHeight); + panelHeight = panelHeight > appointmentViewHeight + ? panelHeight + : appointmentViewHeight; + appointmentHeight += panelHeight + dividerHeight; + numberOfEvents += eventsCount; + } + + /// Add the padding height to the appointment height + /// Each of the appointment view have top padding in agenda view and + /// end agenda view have end padding, so count as + /// (numberOfEvents + 1). + /// value 1 as padding between the agenda view and end appointment + /// view. each of the agenda view in the week have padding add the + /// existing value with date appointment keys length. + appointmentHeight += + (numberOfEvents + dateAppointmentKeys.length) * padding; + + /// Add appointment height and week view end padding to height. + currentWeekHeight += + appointmentHeight + (_useMobilePlatformUI ? padding : 0); + } + + currentWeekHeight += todayNewEventHeight; + currentWeekHeight += displayNewEventHeight; + totalHeight += currentWeekHeight; + heights.add(currentWeekHeight); + viewStartDate = addDays(viewStartDate, DateTime.daysPerWeek); + } + + /// Get the current display date week index from next dates collection. + int rangeIndex = -1; + for (int i = 0; i < _nextDates.length; i++) { + final DateTime visibleStartDate = _nextDates[i]; + final DateTime visibleEndDate = + addDays(visibleStartDate, DateTime.daysPerWeek); + if (!isDateWithInDateRange( + visibleStartDate, visibleEndDate, scheduleDisplayDate)) { + continue; + } + + rangeIndex = i; + } + + double initialScrolledPosition = 0; + for (int i = 0; i < rangeIndex; i++) { + /// Add display date's previous weeks height to the initial + /// scroll position. + initialScrolledPosition += heights[i]; + } + + viewStartDate = _nextDates[rangeIndex]; + + /// Calculate the scroll position with current display date week. + while (viewStartDate.isBefore(scheduleDisplayDate) && + !isSameDate(viewStartDate, scheduleDisplayDate)) { + final DateTime viewEndDate = addDays(viewStartDate, 6); + final DateTime appStartDate = + isSameOrAfterDate(_minDate!, viewStartDate) + ? viewStartDate + : _minDate!; + DateTime appEndDate = isSameOrBeforeDate(_maxDate!, viewEndDate) + ? viewEndDate + : _maxDate!; + if (appEndDate.isAfter(scheduleDisplayDate) || + isSameDate(appEndDate, scheduleDisplayDate)) { + appEndDate = addDays(scheduleDisplayDate, -1); + } + + /// Today date view height. + double todayNewEventHeight = + isDateWithInDateRange(appStartDate, appEndDate, scheduleCurrentDate) + ? displayEventHeight + : 0; + final List appointmentCollection = + AppointmentHelper.getVisibleAppointments( + appStartDate, appEndDate, _appointments, widget.timeZone, false, + canCreateNewAppointment: false); + + /// Check the week date needs month header or not. + final bool isNeedMonthBuilder = _useMobilePlatformUI + ? (viewStartDate.month != appEndDate.month || + viewStartDate.year != appEndDate.year) || + viewStartDate.day == 1 + : false; + + if (appointmentCollection.isNotEmpty) { + /// Get the collection of appointment collection listed by date. + final Map> dateAppointments = + _getAppointmentCollectionOnDateBasis( + appointmentCollection, appStartDate, appEndDate); + final List dateAppointmentKeys = + dateAppointments.keys.toList(); + + /// calculate the scroll position by adding week header height. + /// web view does not have week label. + initialScrolledPosition += _useMobilePlatformUI + ? widget.scheduleViewSettings.weekHeaderSettings.height + : 0; + + /// Web view does not have month label. + initialScrolledPosition += isNeedMonthBuilder + ? widget.scheduleViewSettings.monthHeaderSettings.height + : 0; + + int numberOfEvents = 0; + + double appointmentHeight = 0; + + /// Calculate the total height of appointment views of week. + for (int i = 0; i < dateAppointmentKeys.length; i++) { + final DateTime currentDateKey = dateAppointmentKeys[i]; + final List _currentDateAppointment = + dateAppointments[currentDateKey]!; + if (isSameDate(scheduleCurrentDate, currentDateKey)) { + todayNewEventHeight = 0; + } + + final int eventsCount = _currentDateAppointment.length; + int allDayEventCount = 0; + + /// Web view does not differentiate all day and normal appointment. + if (_useMobilePlatformUI) { + allDayEventCount = _getAllDayCount(_currentDateAppointment); + } + + double panelHeight = + ((eventsCount - allDayEventCount) * appointmentViewHeight) + + (allDayEventCount * allDayAppointmentHeight); + panelHeight = panelHeight > appointmentViewHeight + ? panelHeight + : appointmentViewHeight; + appointmentHeight += panelHeight + dividerHeight; + numberOfEvents += eventsCount; + } + + /// Add the padding height to the appointment height + /// Each of the appointment view have top padding in agenda view and + /// end agenda view have end padding, so count as + /// (numberOfEvents + 1). + /// value 1 as padding between the agenda view and end appointment + /// view. each of the agenda view in the week have padding add the + /// existing value with date appointment keys length. + appointmentHeight += + (numberOfEvents + dateAppointmentKeys.length) * padding; + + /// Add appointment height and week view end padding to scroll + /// position. + initialScrolledPosition += + appointmentHeight + (_useMobilePlatformUI ? padding : 0); + + initialScrolledPosition += todayNewEventHeight; + } else if (isNeedMonthBuilder || todayNewEventHeight != 0) { + initialScrolledPosition += (!_useMobilePlatformUI + ? 0 + : widget.scheduleViewSettings.weekHeaderSettings.height + + padding) + + todayNewEventHeight; + } + + viewStartDate = addDays(viewStartDate, DateTime.daysPerWeek); + } + + if (initialScrolledPosition != 0) { + final double belowSpace = totalHeight - initialScrolledPosition; + + /// Check the content height after the scroll position, if it lesser + /// than view port height then reduce the scroll position. + if (belowSpace < height) { + initialScrolledPosition -= (height - belowSpace); + initialScrolledPosition = + initialScrolledPosition > 0 ? initialScrolledPosition : 0; + } + + _agendaScrollController?.removeListener(_handleScheduleViewScrolled); + _agendaScrollController = + ScrollController(initialScrollOffset: initialScrolledPosition) + ..addListener(_handleScheduleViewScrolled); + _scrollKey = UniqueKey(); + } + } + + if (isInitialLoadMore) { + _isNeedLoadMore = true; + _scheduleMaxDate = AppointmentHelper.getMonthEndDate(_scheduleMaxDate!); + _scheduleMinDate = AppointmentHelper.getMonthStartDate(_scheduleMinDate!); + + if (!isSameOrBeforeDate(widget.maxDate, _scheduleMaxDate)) { + _scheduleMaxDate = widget.maxDate; + } + + if (!isSameOrAfterDate(widget.minDate, _scheduleMinDate)) { + _scheduleMinDate = widget.minDate; + } + } + + final List children = [ + Positioned( + top: 0, + right: 0, + left: 0, + height: widget.headerHeight, + child: GestureDetector( + child: Container( + color: widget.headerStyle.backgroundColor ?? + _calendarTheme.headerBackgroundColor, + child: _CalendarHeaderView( + _currentViewVisibleDates, + widget.headerStyle, + null, + _view, + widget.monthViewSettings.numberOfWeeksInView, + _calendarTheme, + isRTL, + _locale, + widget.showNavigationArrow, + _controller, + widget.maxDate, + widget.minDate, + _minWidth, + widget.headerHeight, + widget.timeSlotViewSettings.nonWorkingDays, + widget.monthViewSettings.navigationDirection, + widget.showDatePickerButton, + _showHeader, + widget.allowedViews, + widget.allowViewNavigation, + _localizations, + _removeDatePicker, + _headerUpdateNotifier, + _viewChangeNotifier, + _handleOnTapForHeader, + _handleOnLongPressForHeader, + widget.todayHighlightColor, + _textScaleFactor, + _isMobilePlatform, + widget.headerDateFormat, + !_isScheduleStartLoadMore && !_isNeedLoadMore, + )), + ), + ), + Positioned( + top: widget.headerHeight, + left: 0, + right: 0, + height: height, + child: _OpacityWidget( + opacity: _opacity, + child: NotificationListener( + onNotification: (OverscrollNotification notification) { + if (_isNeedLoadMore || + _isScheduleStartLoadMore || + widget.loadMoreWidgetBuilder == null) { + return true; + } + + if (notification.overscroll < 0 && + _agendaScrollController!.position.pixels <= + _agendaScrollController!.position.minScrollExtent) { + DateTime date = AppointmentHelper.getMonthStartDate( + DateTime(_scheduleMinDate!.year, + _scheduleMinDate!.month - 1)); + + if (!isSameOrAfterDate(widget.minDate, date)) { + date = widget.minDate; + } + + if (isSameDate(_scheduleMinDate, date)) { + return true; + } + + setState(() { + _isScheduleStartLoadMore = true; + _scheduleMinDate = date; + }); + } else if (_agendaScrollController!.position.pixels >= + _agendaScrollController!.position.maxScrollExtent) { + DateTime date = AppointmentHelper.getMonthEndDate( + DateTime(_scheduleMaxDate!.year, + _scheduleMaxDate!.month + 1)); + + if (!isSameOrBeforeDate(widget.maxDate, date)) { + date = widget.maxDate; + } + + if (isSameDate(_scheduleMaxDate, date)) { + return true; + } + + setState(() { + _isNeedLoadMore = true; + _scheduleMaxDate = date; + }); + } + return true; + }, + child: CustomScrollView( + key: _scrollKey, + physics: const AlwaysScrollableScrollPhysics( + parent: ClampingScrollPhysics( + parent: RangeMaintainingScrollPhysics())), + controller: _agendaScrollController, + center: _scheduleViewKey, + slivers: [ + SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + if (_previousDates.length <= index) { + return null; + } + + /// Send negative index value to differentiate the + /// backward view from forward view. + return _getItem(context, -(index + 1), isRTL); + }), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + if (_nextDates.length <= index) { + return null; + } + + return _getItem(context, index, isRTL); + }), + key: _scheduleViewKey, + ), + ], + )))), + _addDatePicker(widget.headerHeight, isRTL), + _getCalendarViewPopup(), + ]; + + if ((_isNeedLoadMore || _isScheduleStartLoadMore) && + widget.loadMoreWidgetBuilder != null) { + final Alignment loadMoreAlignment = _agendaScrollController!.hasClients && + _agendaScrollController!.position.pixels <= + _agendaScrollController!.position.minScrollExtent && + _isScheduleStartLoadMore + ? Alignment.topCenter + : Alignment.bottomCenter; + final DateTime visibleStartDate = _isNeedLoadMore + ? AppointmentHelper.getMonthStartDate(_scheduleMaxDate!) + : _scheduleMinDate!; + final DateTime visibleEndDate = _isNeedLoadMore + ? _scheduleMaxDate! + : AppointmentHelper.getMonthEndDate(_scheduleMinDate!); + children.add(Positioned( + top: widget.headerHeight, + left: 0, + right: 0, + height: height, + child: Container( + alignment: loadMoreAlignment, + color: Colors.transparent, + child: widget.loadMoreWidgetBuilder!(context, () async { + await loadMoreAppointments(visibleStartDate, visibleEndDate); + })))); + } + + return Stack(children: children); + } + + Future loadMoreAppointments( + DateTime visibleStartDate, DateTime visibleEndDate) async { + if (_isLoadMoreLoaded) { + return; + } + + _isLoadMoreLoaded = true; + // ignore: invalid_use_of_protected_member + await widget.dataSource!.handleLoadMore(visibleStartDate, visibleEndDate); + _isLoadMoreLoaded = false; + } + + void _updateViewChangePopup() { + if (!mounted) { + return; + } + + if (widget.showDatePickerButton && _showHeader) { + _showHeader = false; + } + + setState(() {}); + } + + Widget _getCalendarViewPopup() { + if (widget.allowedViews == null || + widget.allowedViews!.isEmpty || + !_viewChangeNotifier.value) { + return Container(); + } + + final double calendarViewTextHeight = 40; + final List children = []; + double width = 0; + Color? headerTextColor = widget.headerStyle.textStyle != null + ? widget.headerStyle.textStyle!.color + : (_calendarTheme.headerTextStyle.color); + headerTextColor ??= Colors.black87; + final TextStyle style = TextStyle(color: headerTextColor, fontSize: 12); + int selectedIndex = -1; + final Color? todayColor = + widget.todayHighlightColor ?? _calendarTheme.todayHighlightColor; + + final Map calendarViews = + _getCalendarViewsText(_localizations); + + /// Generate the calendar view pop up content views. + for (int i = 0; i < widget.allowedViews!.length; i++) { + final CalendarView view = widget.allowedViews![i]; + final double textWidth = _getTextWidgetWidth( calendarViews[view].toString(), calendarViewTextHeight, _minWidth, @@ -4513,197 +6124,904 @@ class _SfCalendarState extends State selectedIndex = i; } - children.add(InkWell( - onTap: () { - _viewChangeNotifier.value = false; - _controller.view = view; - }, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 0.0), - height: calendarViewTextHeight, - alignment: _isRTL ? Alignment.centerRight : Alignment.centerLeft, - child: Text( - calendarViews[view].toString(), - style: isSelected ? style.copyWith(color: todayColor) : style, - maxLines: 1, - ), - ), - )); + children.add(InkWell( + onTap: () { + _viewChangeNotifier.value = false; + _controller.view = view; + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 0.0), + height: calendarViewTextHeight, + alignment: _isRTL ? Alignment.centerRight : Alignment.centerLeft, + child: Text( + calendarViews[view].toString(), + style: isSelected ? style.copyWith(color: todayColor) : style, + maxLines: 1, + ), + ), + )); + } + + /// Restrict the pop up height with max height(200) + double height = widget.allowedViews!.length * calendarViewTextHeight; + height = height > 200 ? 200 : height; + + double arrowWidth = 0; + double iconWidth = _minWidth / 8; + iconWidth = iconWidth > 40 ? 40 : iconWidth; + const double padding = 5; + + /// Navigation arrow enabled when [showNavigationArrow] in [SfCalendar] is + /// enabled and calendar view as not schedule, because schedule view does + /// not have a support for navigation arrow. + final bool navigationArrowEnabled = + widget.showNavigationArrow && _view != CalendarView.schedule; + + /// Assign arrow width as icon width when the navigation arrow enabled. + if (navigationArrowEnabled) { + arrowWidth = iconWidth; + } + + double? headerIconTextWidth = widget.headerStyle.textStyle != null + ? widget.headerStyle.textStyle!.fontSize + : _calendarTheme.headerTextStyle.fontSize; + headerIconTextWidth ??= 14; + final double totalArrowWidth = 2 * arrowWidth; + final bool isCenterAlignment = !_isMobilePlatform && + (widget.headerStyle.textAlign == TextAlign.center || + widget.headerStyle.textAlign == TextAlign.justify); + + /// Calculate the calendar view button width that placed on header view + final double calendarViewWidth = _useMobilePlatformUI + ? iconWidth + : _getTextWidgetWidth(calendarViews[_view]!, widget.headerHeight, + _minWidth - totalArrowWidth, context, + style: style) + .width + + padding + + headerIconTextWidth; + double dividerWidth = 0; + double todayWidth = 0; + + /// Today button shown only the date picker enabled. + if (widget.showDatePickerButton) { + todayWidth = _useMobilePlatformUI + ? iconWidth + : _getTextWidgetWidth(_localizations.todayLabel, widget.headerHeight, + _minWidth - totalArrowWidth, context, + style: style) + .width + + padding; + + /// Divider shown when the view holds calendar views and today button. + dividerWidth = _useMobilePlatformUI ? 0 : 5; + } + double headerWidth = _minWidth - + totalArrowWidth - + calendarViewWidth - + todayWidth - + dividerWidth; + if (isCenterAlignment) { + headerWidth = headerWidth > 200 ? 200 : headerWidth; + } + + /// 20 as container left and right padding for the view. + width += 20; + double left = 0; + + /// Specifies the popup animation start position. + Alignment popupAlignment; + if (_isMobilePlatform) { + /// icon width specifies the today button width and calendar view width. + left = _isRTL + ? totalArrowWidth + : headerWidth + todayWidth + iconWidth - width; + popupAlignment = _isRTL ? Alignment.topLeft : Alignment.topRight; + if (widget.headerStyle.textAlign == TextAlign.right || + widget.headerStyle.textAlign == TextAlign.end) { + popupAlignment = _isRTL ? Alignment.topRight : Alignment.topLeft; + left = _isRTL + ? headerWidth + iconWidth + todayWidth - width + : totalArrowWidth; + } else if (widget.headerStyle.textAlign == TextAlign.center || + widget.headerStyle.textAlign == TextAlign.justify) { + popupAlignment = _isRTL ? Alignment.topLeft : Alignment.topRight; + left = _isRTL + ? arrowWidth + : headerWidth + arrowWidth + todayWidth + iconWidth - width; + } + } else { + left = _isRTL + ? calendarViewWidth - width + : headerWidth + totalArrowWidth + todayWidth + dividerWidth - 1; + popupAlignment = _isRTL ? Alignment.topLeft : Alignment.topRight; + if (widget.headerStyle.textAlign == TextAlign.right || + widget.headerStyle.textAlign == TextAlign.end) { + popupAlignment = _isRTL ? Alignment.topRight : Alignment.topLeft; + left = _isRTL + ? headerWidth + totalArrowWidth + todayWidth + dividerWidth - 1 + : calendarViewWidth - width; + } else if (widget.headerStyle.textAlign == TextAlign.center || + widget.headerStyle.textAlign == TextAlign.justify) { + popupAlignment = _isRTL ? Alignment.topRight : Alignment.topLeft; + + /// Calculate the left padding by calculate the total icon and header. + /// Calculate the menu icon position by adding the left padding, left + /// arrow and header label. + final double leftStartPosition = (_minWidth - + totalArrowWidth - + calendarViewWidth - + dividerWidth - + todayWidth - + headerWidth) / + 2; + left = _isRTL + ? leftStartPosition + calendarViewWidth - width + : leftStartPosition + + totalArrowWidth + + headerWidth + + todayWidth + + dividerWidth; + } + } + + if (left < 2) { + left = 2; + } else if (left + width + 2 > _minWidth) { + left = _minWidth - width - 2; + } + + double scrollPosition = 0; + if (selectedIndex != -1) { + scrollPosition = selectedIndex * calendarViewTextHeight; + final double maxScrollPosition = + widget.allowedViews!.length * calendarViewTextHeight; + scrollPosition = (maxScrollPosition - scrollPosition) > height + ? scrollPosition + : maxScrollPosition - height; + } + + return Positioned( + top: widget.headerHeight, + left: left, + height: height, + width: width, + child: _PopupWidget( + alignment: popupAlignment, + child: Container( + padding: EdgeInsets.all(0), + decoration: BoxDecoration( + color: _calendarTheme.brightness == Brightness.dark + ? Colors.grey[850] + : Colors.white, + boxShadow: kElevationToShadow[6], + borderRadius: BorderRadius.circular(2.0), + shape: BoxShape.rectangle, + ), + child: Material( + type: MaterialType.transparency, + child: ListView( + padding: EdgeInsets.all(0), + controller: + ScrollController(initialScrollOffset: scrollPosition), + children: children), + )))); + } + + /// Adds the resource panel on the left side of the view, if the resource + /// collection is not null. + Widget _addResourcePanel(bool isResourceEnabled, double resourceViewSize, + double height, bool isRTL) { + if (!isResourceEnabled) { + return Positioned( + left: 0, + right: 0, + top: 0, + bottom: 0, + child: Container(), + ); + } + + final double viewHeaderHeight = + CalendarViewHelper.getViewHeaderHeight(widget.viewHeaderHeight, _view); + final double timeLabelSize = CalendarViewHelper.getTimeLabelWidth( + widget.timeSlotViewSettings.timeRulerSize, _view); + final double top = viewHeaderHeight + timeLabelSize; + final double resourceItemHeight = CalendarViewHelper.getResourceItemHeight( + resourceViewSize, + height - top, + widget.resourceViewSettings, + _resourceCollection!.length); + final double panelHeight = resourceItemHeight * _resourceCollection!.length; + + final Widget verticalDivider = VerticalDivider( + width: 0.5, + thickness: 0.5, + color: widget.cellBorderColor ?? _calendarTheme.cellBorderColor, + ); + + return Positioned( + left: isRTL ? _minWidth - resourceViewSize : 0, + width: resourceViewSize, + top: 0, + bottom: 0, + child: Stack(children: [ + Positioned( + left: _isRTL ? 0.5 : resourceViewSize - 0.5, + width: 0.5, + top: _controller.view == CalendarView.timelineMonth + ? widget.headerHeight + : widget.headerHeight + viewHeaderHeight, + child: verticalDivider, + height: _controller.view == CalendarView.timelineMonth + ? viewHeaderHeight + : timeLabelSize, + ), + Positioned( + left: 0, + width: resourceViewSize, + top: widget.headerHeight + top, + bottom: 0, + child: MouseRegion( + onEnter: (PointerEnterEvent event) { + _pointerEnterEvent(event, false, isRTL, null, + top + widget.headerHeight, 0, isResourceEnabled); + }, + onExit: _pointerExitEvent, + onHover: (PointerHoverEvent event) { + _pointerHoverEvent(event, false, isRTL, null, + top + widget.headerHeight, 0, isResourceEnabled); + }, + child: GestureDetector( + child: ListView( + padding: const EdgeInsets.all(0.0), + physics: const ClampingScrollPhysics(), + controller: _resourcePanelScrollController, + scrollDirection: Axis.vertical, + children: [ + CustomPaint( + painter: ResourceContainer( + _resourceCollection, + widget.resourceViewSettings, + resourceItemHeight, + widget.cellBorderColor, + _calendarTheme, + _resourceImageNotifier, + isRTL, + _textScaleFactor, + _resourceHoverNotifier.value, + _imagePainterCollection), + size: Size(resourceViewSize, panelHeight), + ), + ]), + onTapUp: (TapUpDetails details) { + _handleOnTapForResourcePanel(details, resourceItemHeight); + }, + onLongPressStart: (LongPressStartDetails details) { + _handleOnLongPressForResourcePanel( + details, resourceItemHeight); + }, + ))) + ])); + } + + /// Handles and raises the [widget.onLongPress] callback, when the resource + /// panel is long pressed in [SfCalendar]. + void _handleOnLongPressForResourcePanel( + LongPressStartDetails details, double resourceItemHeight) { + if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( + widget.onLongPress)) { + return; + } + + final CalendarResource tappedResource = + _getTappedResource(details.localPosition.dy, resourceItemHeight); + final List resourceAppointments = + _getSelectedResourceAppointments(tappedResource); + CalendarViewHelper.raiseCalendarLongPressCallback(widget, null, + resourceAppointments, CalendarElement.resourceHeader, tappedResource); + } + + /// Handles and raises the [widget.onTap] callback, when the resource panel + /// is tapped in [SfCalendar]. + void _handleOnTapForResourcePanel( + TapUpDetails details, double resourceItemHeight) { + if (!CalendarViewHelper.shouldRaiseCalendarTapCallback(widget.onTap)) { + return; + } + + final CalendarResource tappedResource = + _getTappedResource(details.localPosition.dy, resourceItemHeight); + final List resourceAppointments = + _getSelectedResourceAppointments(tappedResource); + CalendarViewHelper.raiseCalendarTapCallback(widget, null, + resourceAppointments, CalendarElement.resourceHeader, tappedResource); + } + + /// Filter and returns the appointment collection for the given resource from + /// the visible appointments collection. + List _getSelectedResourceAppointments(CalendarResource resource) { + final List selectedResourceAppointments = []; + if (_visibleAppointments.isEmpty) { + return selectedResourceAppointments; + } + + for (int i = 0; i < _visibleAppointments.length; i++) { + final CalendarAppointment app = _visibleAppointments[i]; + if (app.resourceIds != null && + app.resourceIds!.isNotEmpty && + app.resourceIds!.contains(resource.id)) { + selectedResourceAppointments.add(app.data ?? app); + } + } + + return selectedResourceAppointments; + } + + /// Returns the tapped resource details, based on the tapped position. + CalendarResource _getTappedResource( + double tappedPosition, double resourceItemHeight) { + final int index = + (_resourcePanelScrollController!.offset + tappedPosition) ~/ + resourceItemHeight; + return _resourceCollection![index]; + } + + /// Adds the custom scroll view which used to produce the infinity scroll. + Widget _addCustomScrollView( + double top, + double resourceViewSize, + bool isRTL, + bool isResourceEnabled, + double width, + double height, + double agendaHeight) { + return Positioned( + top: top, + left: isResourceEnabled && !isRTL ? resourceViewSize : 0, + right: isResourceEnabled && isRTL ? resourceViewSize : 0, + height: height - agendaHeight, + child: _OpacityWidget( + opacity: _opacity, + child: CustomCalendarScrollView( + widget, + _view, + width - resourceViewSize, + height - agendaHeight, + _agendaSelectedDate, + isRTL, + _locale, + _calendarTheme, + _timeZoneLoaded ? widget.specialRegions : null, + _blackoutDates, + _controller, + _removeDatePicker, + _resourcePanelScrollController, + _resourceCollection, + _textScaleFactor, + _isMobilePlatform, + _fadeInController, + widget.minDate, + widget.maxDate, + _localizations, (UpdateCalendarStateDetails details) { + _updateCalendarState(details); + }, (UpdateCalendarStateDetails details) { + _getCalendarStateDetails(details); + })), + ); + } + + Widget _addChildren( + double agendaHeight, double height, double width, bool isRTL) { + final bool isResourceEnabled = + CalendarViewHelper.isResourceEnabled(widget.dataSource, _view); + final double resourceViewSize = + isResourceEnabled ? widget.resourceViewSettings.size : 0; + final DateTime currentViewDate = _currentViewVisibleDates[ + (_currentViewVisibleDates.length / 2).truncate()]; + + final List children = [ + Positioned( + top: 0, + right: 0, + left: 0, + height: widget.headerHeight, + child: Container( + color: widget.headerStyle.backgroundColor ?? + _calendarTheme.headerBackgroundColor, + child: _CalendarHeaderView( + _currentViewVisibleDates, + widget.headerStyle, + currentViewDate, + _view, + widget.monthViewSettings.numberOfWeeksInView, + _calendarTheme, + isRTL, + _locale, + widget.showNavigationArrow, + _controller, + widget.maxDate, + widget.minDate, + width, + widget.headerHeight, + widget.timeSlotViewSettings.nonWorkingDays, + widget.monthViewSettings.navigationDirection, + widget.showDatePickerButton, + _showHeader, + widget.allowedViews, + widget.allowViewNavigation, + _localizations, + _removeDatePicker, + _headerUpdateNotifier, + _viewChangeNotifier, + _handleOnTapForHeader, + _handleOnLongPressForHeader, + widget.todayHighlightColor, + _textScaleFactor, + _isMobilePlatform, + widget.headerDateFormat, + !_isNeedLoadMore, + )), + ), + _addResourcePanel(isResourceEnabled, resourceViewSize, height, isRTL), + _addCustomScrollView(widget.headerHeight, resourceViewSize, isRTL, + isResourceEnabled, width, height, agendaHeight), + _addAgendaView(agendaHeight, widget.headerHeight + height - agendaHeight, + width, isRTL), + _addDatePicker(widget.headerHeight, isRTL), + _getCalendarViewPopup(), + ]; + if (_isNeedLoadMore && widget.loadMoreWidgetBuilder != null) { + children.add(Container( + color: Colors.transparent, + child: widget.loadMoreWidgetBuilder!(context, () async { + await loadMoreAppointments(_currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - 1]); + }))); + } + return Stack(children: children); + } + + void _removeDatePicker() { + if (widget.showDatePickerButton && _showHeader) { + setState(() { + _showHeader = false; + }); + } + + _viewChangeNotifier.value = false; + } + + void _updateDatePicker() { + _viewChangeNotifier.value = false; + if (!widget.showDatePickerButton) { + return; + } + + setState(() { + _showHeader = !_showHeader; + }); + } + + Widget _addDatePicker(double top, bool isRTL) { + if (!widget.showDatePickerButton || !_showHeader) { + return Container(width: 0, height: 0); + } + + double maxHeight = _minHeight * 0.6; + double maxWidth = _minWidth * 0.5; + + double pickerWidth = 0; + double pickerHeight = 0; + + final TextStyle datePickerStyle = + widget.monthViewSettings.monthCellStyle.textStyle ?? + _calendarTheme.activeDatesTextStyle; + final Color? todayColor = + widget.todayHighlightColor ?? _calendarTheme.todayHighlightColor; + double left = 0; + if (_isMobilePlatform) { + pickerWidth = _minWidth; + pickerHeight = _minHeight * 0.5; + } else { + const double padding = 5; + double arrowWidth = 0; + double iconWidth = _minWidth / 8; + iconWidth = iconWidth > 40 ? 40 : iconWidth; + + /// Navigation arrow enabled when [showNavigationArrow] in [SfCalendar] is + /// enabled and calendar view as not schedule, because schedule view does + /// not have a support for navigation arrow. + final bool navigationArrowEnabled = + widget.showNavigationArrow && _view != CalendarView.schedule; + + /// Assign arrow width as icon width when the navigation arrow enabled. + if (navigationArrowEnabled) { + arrowWidth = iconWidth; + } + + final double totalArrowWidth = 2 * arrowWidth; + final double totalWidth = _minWidth - totalArrowWidth; + final double totalHeight = _minHeight - widget.headerHeight; + maxHeight = maxHeight < 250 + ? (totalHeight < 250 ? totalHeight - 10 : 250) + : maxHeight; + maxWidth = maxWidth < 250 + ? (totalWidth < 250 ? totalWidth - 10 : 250) + : maxWidth; + double containerSize = maxHeight > maxWidth ? maxWidth : maxHeight; + if (containerSize > 300) { + containerSize = 300; + } + + pickerWidth = containerSize; + pickerHeight = containerSize; + left = + isRTL ? _minWidth - containerSize - totalArrowWidth : totalArrowWidth; + if (widget.headerStyle.textAlign == TextAlign.right || + widget.headerStyle.textAlign == TextAlign.end) { + left = isRTL ? padding : _minWidth - containerSize - totalArrowWidth; + } else if (widget.headerStyle.textAlign == TextAlign.center || + widget.headerStyle.textAlign == TextAlign.justify) { + final double calendarViewWidth = _calendarViewWidth; + + double headerViewWidth = + _minWidth - calendarViewWidth - totalArrowWidth; + if (headerViewWidth == _minWidth) { + left = (_minWidth - containerSize) / 2; + } else { + headerViewWidth = headerViewWidth > 200 ? 200 : headerViewWidth; + final double leftPadding = (_minWidth - + headerViewWidth - + calendarViewWidth - + totalArrowWidth) / + 2; + double headerPadding = (headerViewWidth - containerSize) / 2; + headerPadding = headerPadding > 0 ? headerPadding : 0; + left = _isRTL + ? leftPadding + + arrowWidth + + calendarViewWidth + + headerViewWidth - + containerSize + : leftPadding + arrowWidth + headerPadding; + } + } } - /// Restrict the pop up height with max height(200) - double height = widget.allowedViews.length * calendarViewTextHeight; - height = height > 200 ? 200 : height; + return Positioned( + top: top, + left: left, + width: pickerWidth, + height: pickerHeight, + child: _PopupWidget( + child: Container( + margin: EdgeInsets.all(0), + padding: EdgeInsets.all(5), + decoration: _isMobilePlatform + ? BoxDecoration( + color: _calendarTheme.brightness == Brightness.dark + ? Colors.grey[850] + : Colors.white, + boxShadow: [ + BoxShadow( + offset: Offset(0.0, 3.0), + blurRadius: 2.0, + spreadRadius: 0.0, + color: Color(0x24000000)), + ], + shape: BoxShape.rectangle, + ) + : BoxDecoration( + color: _calendarTheme.brightness == Brightness.dark + ? Colors.grey[850] + : Colors.white, + boxShadow: kElevationToShadow[6], + borderRadius: BorderRadius.circular(2.0), + shape: BoxShape.rectangle, + ), + child: SfDateRangePicker( + showNavigationArrow: true, + initialSelectedDate: _currentDate, + initialDisplayDate: _currentDate, + todayHighlightColor: todayColor, + minDate: widget.minDate, + maxDate: widget.maxDate, + selectionColor: todayColor, + headerStyle: DateRangePickerHeaderStyle( + textAlign: + _isMobilePlatform ? TextAlign.center : TextAlign.left, + ), + monthViewSettings: DateRangePickerMonthViewSettings( + viewHeaderHeight: pickerHeight / 8, + firstDayOfWeek: widget.firstDayOfWeek, + ), + monthCellStyle: DateRangePickerMonthCellStyle( + textStyle: datePickerStyle, + todayTextStyle: + datePickerStyle.copyWith(color: todayColor)), + yearCellStyle: DateRangePickerYearCellStyle( + textStyle: datePickerStyle, + todayTextStyle: datePickerStyle.copyWith(color: todayColor), + leadingDatesTextStyle: widget.monthViewSettings + .monthCellStyle.leadingDatesTextStyle ?? + _calendarTheme.leadingDatesTextStyle, + ), + view: _view == CalendarView.month || + _view == CalendarView.timelineMonth + ? DateRangePickerView.year + : DateRangePickerView.month, + onViewChanged: (DateRangePickerViewChangedArgs details) { + if ((_view != CalendarView.month && + _view != CalendarView.timelineMonth) || + details.view != DateRangePickerView.month) { + return; + } - double arrowWidth = 0; - double iconWidth = _minWidth / 8; - iconWidth = iconWidth > 40 ? 40 : iconWidth; - const double padding = 5; + if (isSameDate(_currentDate, _controller.displayDate) || + isDateWithInDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[ + _currentViewVisibleDates.length - 1], + _controller.displayDate)) { + _removeDatePicker(); + } - /// Navigation arrow enabled when [showNavigationArrow] in [SfCalendar] is - /// enabled and calendar view as not schedule, because schedule view does - /// not have a support for navigation arrow. - final bool navigationArrowEnabled = - widget.showNavigationArrow && _view != CalendarView.schedule; + _showHeader = false; + final DateTime selectedDate = + details.visibleDateRange.startDate!; + _controller.displayDate = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + _controller.displayDate!.hour, + _controller.displayDate!.minute, + _controller.displayDate!.second); + }, + onSelectionChanged: + (DateRangePickerSelectionChangedArgs details) { + if (isSameDate(_currentDate, _controller.displayDate) || + isDateWithInDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[ + _currentViewVisibleDates.length - 1], + _controller.displayDate)) { + _removeDatePicker(); + } - /// Assign arrow width as icon width when the navigation arrow enabled. - if (navigationArrowEnabled) { - arrowWidth = iconWidth; + _showHeader = false; + _controller.displayDate = DateTime( + details.value.year, + details.value.month, + details.value.day, + _controller.displayDate!.hour, + _controller.displayDate!.minute, + _controller.displayDate!.second); + }, + )))); + } + + void _getCalendarStateDetails(UpdateCalendarStateDetails details) { + details.currentDate = _currentDate; + details.currentViewVisibleDates = _currentViewVisibleDates; + details.selectedDate = _selectedDate; + details.allDayPanelHeight = _allDayPanelHeight; + details.allDayAppointmentViewCollection = _allDayAppointmentViewCollection; + details.visibleAppointments = _visibleAppointments; + details.appointments = _appointments; + } + + void _updateCalendarState(UpdateCalendarStateDetails details) { + if (details.currentDate != null && + !isSameDate(details.currentDate, _currentDate)) { + _currentDate = + getValidDate(widget.minDate, widget.maxDate, details.currentDate); + _controller.displayDate = _currentDate; + details.currentDate = _currentDate; } - double headerIconTextWidth = widget.headerStyle.textStyle != null - ? widget.headerStyle.textStyle.fontSize - : _calendarTheme.headerTextStyle.fontSize; - headerIconTextWidth ??= 14; - final double totalArrowWidth = 2 * arrowWidth; - final bool isCenterAlignment = !_isMobilePlatform && - widget.headerStyle.textAlign != null && - (widget.headerStyle.textAlign == TextAlign.center || - widget.headerStyle.textAlign == TextAlign.justify); + if (_currentViewVisibleDates != details.currentViewVisibleDates) { + _currentViewVisibleDates = details.currentViewVisibleDates; + _allDayAppointmentViewCollection = []; + _visibleAppointments = []; + _allDayPanelHeight = 0; + _isNeedLoadMore = widget.loadMoreWidgetBuilder != null; + _updateVisibleAppointments(); + if (CalendarViewHelper.shouldRaiseViewChangedCallback( + widget.onViewChanged)) { + final bool showTrailingLeadingDates = + CalendarViewHelper.isLeadingAndTrailingDatesVisible( + widget.monthViewSettings.numberOfWeeksInView, + widget.monthViewSettings.showTrailingAndLeadingDates); + List visibleDates = _currentViewVisibleDates; + if (!showTrailingLeadingDates) { + visibleDates = DateTimeHelper.getCurrentMonthDates(visibleDates); + } + + CalendarViewHelper.raiseViewChangedCallback(widget, visibleDates); + } + } + + if (!CalendarViewHelper.isSameTimeSlot( + details.selectedDate, _selectedDate)) { + _selectedDate = details.selectedDate; + _controller.selectedDate = details.selectedDate; + } + } + + //// Handles the on tap callback for header + void _handleOnTapForHeader(double width) { + _calendarViewWidth = width; + _updateDatePicker(); + if (!CalendarViewHelper.shouldRaiseCalendarTapCallback(widget.onTap)) { + return; + } + + CalendarViewHelper.raiseCalendarTapCallback( + widget, _getTappedHeaderDate(), null, CalendarElement.header, null); + } + + //// Handles the on long press callback for header + void _handleOnLongPressForHeader(double width) { + _calendarViewWidth = width; + _updateDatePicker(); + if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( + widget.onLongPress)) { + return; + } + + CalendarViewHelper.raiseCalendarLongPressCallback( + widget, _getTappedHeaderDate(), null, CalendarElement.header, null); + } + + DateTime _getTappedHeaderDate() { + if (_view == CalendarView.month) { + return DateTime(_currentDate.year, _currentDate.month, 01, 0, 0, 0); + } else { + return DateTime( + _currentViewVisibleDates[0].year, + _currentViewVisibleDates[0].month, + _currentViewVisibleDates[0].day, + 0, + 0, + 0); + } + } + + //// Handles the onTap callback for agenda view. + void _handleTapForAgenda(TapUpDetails details, DateTime? selectedDate) { + _removeDatePicker(); + if (widget.allowViewNavigation && + ((!_isRTL && details.localPosition.dx < _agendaDateViewWidth) || + (_isRTL && + details.localPosition.dx > _minWidth - _agendaDateViewWidth))) { + _controller.view = CalendarView.day; + _controller.displayDate = selectedDate; + } + + if (!CalendarViewHelper.shouldRaiseCalendarTapCallback(widget.onTap)) { + return; + } + + final List selectedAppointments = + _getSelectedAppointments(details.localPosition, selectedDate); + + CalendarViewHelper.raiseCalendarTapCallback( + widget, + selectedDate, + selectedAppointments, + selectedAppointments.isNotEmpty + ? CalendarElement.appointment + : CalendarElement.agenda, + null); + } + + //// Handles the onLongPress callback for agenda view. + void _handleLongPressForAgenda( + LongPressStartDetails details, DateTime? selectedDate) { + _removeDatePicker(); + if (widget.allowViewNavigation && + ((!_isRTL && details.localPosition.dx < _agendaDateViewWidth) || + (_isRTL && + details.localPosition.dx > _minWidth - _agendaDateViewWidth))) { + _controller.view = CalendarView.day; + _controller.displayDate = selectedDate; + } + + if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( + widget.onLongPress)) { + return; + } - /// Calculate the calendar view button width that placed on header view - final double calendarViewWidth = _useMobilePlatformUI - ? iconWidth - : _getTextWidgetWidth(calendarViews[_view], widget.headerHeight, - _minWidth - totalArrowWidth, context, - style: style) - .width + - padding + - headerIconTextWidth; - double dividerWidth = 0; - double todayWidth = 0; + final List selectedAppointments = + _getSelectedAppointments(details.localPosition, selectedDate); - /// Today button shown only the date picker enabled. - if (widget.showDatePickerButton) { - todayWidth = _useMobilePlatformUI - ? iconWidth - : _getTextWidgetWidth(_localizations.todayLabel, widget.headerHeight, - _minWidth - totalArrowWidth, context, - style: style) - .width + - padding; + CalendarViewHelper.raiseCalendarLongPressCallback( + widget, + selectedDate, + selectedAppointments, + selectedAppointments.isNotEmpty + ? CalendarElement.appointment + : CalendarElement.agenda, + null); + } - /// Divider shown when the view holds calendar views and today button. - dividerWidth = _useMobilePlatformUI ? 0 : 5; + List _getSelectedAppointments( + Offset localPosition, DateTime? selectedDate) { + /// Return empty collection while tap the agenda view with no selected date. + if (selectedDate == null) { + return []; } - double headerWidth = _minWidth - - totalArrowWidth - - calendarViewWidth - - todayWidth - - dividerWidth; - if (isCenterAlignment) { - headerWidth = headerWidth > 200 ? 200 : headerWidth; + + /// Return empty collection while tap the agenda date view. + if ((!_isRTL && localPosition.dx < _agendaDateViewWidth) || + (_isRTL && localPosition.dx > _minWidth - _agendaDateViewWidth)) { + return []; } - /// 20 as container left and right padding for the view. - width += 20; - double left = 0; + List agendaAppointments = + AppointmentHelper.getSelectedDateAppointments( + _appointments, widget.timeZone, selectedDate); - /// Specifies the popup animation start position. - Alignment popupAlignment; - if (_isMobilePlatform) { - /// icon width specifies the today button width and calendar view width. - left = _isRTL - ? totalArrowWidth - : headerWidth + todayWidth + iconWidth - width; - popupAlignment = _isRTL ? Alignment.topLeft : Alignment.topRight; - if (widget.headerStyle.textAlign == TextAlign.right || - widget.headerStyle.textAlign == TextAlign.end) { - popupAlignment = _isRTL ? Alignment.topRight : Alignment.topLeft; - left = _isRTL - ? headerWidth + iconWidth + todayWidth - width - : totalArrowWidth; - } else if (widget.headerStyle.textAlign == TextAlign.center || - widget.headerStyle.textAlign == TextAlign.justify) { - popupAlignment = _isRTL ? Alignment.topLeft : Alignment.topRight; - left = _isRTL - ? arrowWidth - : headerWidth + arrowWidth + todayWidth + iconWidth - width; - } - } else { - left = _isRTL - ? calendarViewWidth - width - : headerWidth + totalArrowWidth + todayWidth + dividerWidth - 1; - popupAlignment = _isRTL ? Alignment.topLeft : Alignment.topRight; - if (widget.headerStyle.textAlign == TextAlign.right || - widget.headerStyle.textAlign == TextAlign.end) { - popupAlignment = _isRTL ? Alignment.topRight : Alignment.topLeft; - left = _isRTL - ? headerWidth + totalArrowWidth + todayWidth + dividerWidth - 1 - : calendarViewWidth - width; - } else if (widget.headerStyle.textAlign == TextAlign.center || - widget.headerStyle.textAlign == TextAlign.justify) { - popupAlignment = _isRTL ? Alignment.topRight : Alignment.topLeft; + /// Return empty collection while tap the agenda view does + /// not have appointments. + if (agendaAppointments.isEmpty) { + return []; + } - /// Calculate the left padding by calculate the total icon and header. - /// Calculate the menu icon position by adding the left padding, left - /// arrow and header label. - final double leftStartPosition = (_minWidth - - totalArrowWidth - - calendarViewWidth - - dividerWidth - - todayWidth - - headerWidth) / - 2; - left = _isRTL - ? leftStartPosition + calendarViewWidth - width - : leftStartPosition + - totalArrowWidth + - headerWidth + - todayWidth + - dividerWidth; + agendaAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime)); + agendaAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isAllDay, app2.isAllDay)); + + int index = -1; + //// Agenda appointment view top padding as 5. + const double padding = 5; + double xPosition = 0; + final double tappedYPosition = + _agendaScrollController!.offset + localPosition.dy; + final double actualAppointmentHeight = + CalendarViewHelper.getScheduleAppointmentHeight( + widget.monthViewSettings, null); + final double allDayAppointmentHeight = + CalendarViewHelper.getScheduleAllDayAppointmentHeight( + widget.monthViewSettings, null); + for (int i = 0; i < agendaAppointments.length; i++) { + final CalendarAppointment _appointment = agendaAppointments[i]; + final double appointmentHeight = _isAllDayAppointmentView(_appointment) + ? allDayAppointmentHeight + : actualAppointmentHeight; + if (tappedYPosition >= xPosition && + tappedYPosition < xPosition + appointmentHeight + padding) { + index = i; + break; } + + xPosition += appointmentHeight + padding; } - if (left < 2) { - left = 2; - } else if (left + width + 2 > _minWidth) { - left = _minWidth - width - 2; + /// Return empty collection while tap the agenda view and the tapped + /// position does not have appointment. + if (index > agendaAppointments.length || index == -1) { + return []; } - double scrollPosition = 0; - if (selectedIndex != -1) { - scrollPosition = selectedIndex * calendarViewTextHeight; - final double maxScrollPosition = - widget.allowedViews.length * calendarViewTextHeight; - scrollPosition = (maxScrollPosition - scrollPosition) > height - ? scrollPosition - : maxScrollPosition - height; + agendaAppointments = [agendaAppointments[index]]; + if (widget.dataSource != null && + !AppointmentHelper.isCalendarAppointment(widget.dataSource!)) { + return CalendarViewHelper.getCustomAppointments(agendaAppointments); } - return Positioned( - top: widget.headerHeight, - left: left, - height: height, - width: width, - child: _PopupWidget( - alignment: popupAlignment, - child: Container( - padding: EdgeInsets.all(0), - decoration: BoxDecoration( - color: _calendarTheme.brightness != null && - _calendarTheme.brightness == Brightness.dark - ? Colors.grey[850] - : Colors.white, - boxShadow: kElevationToShadow[6], - borderRadius: BorderRadius.circular(2.0), - shape: BoxShape.rectangle, - ), - child: Material( - type: MaterialType.transparency, - child: ListView( - padding: EdgeInsets.all(0), - controller: - ScrollController(initialScrollOffset: scrollPosition), - children: children), - )))); + return agendaAppointments; } - /// Adds the resource panel on the left side of the view, if the resource - /// collection is not null. - Widget _addResourcePanel(bool isResourceEnabled, double resourceViewSize, - double height, bool isRTL) { - if (!isResourceEnabled) { + // Returns the agenda view as a child for the calendar. + Widget _addAgendaView( + double height, double startPosition, double width, bool isRTL) { + if (_view != CalendarView.month || !widget.monthViewSettings.showAgenda) { return Positioned( left: 0, right: 0, @@ -4713,888 +7031,2335 @@ class _SfCalendarState extends State ); } - final double viewHeaderHeight = - _getViewHeaderHeight(widget.viewHeaderHeight, _view); - final double timeLabelSize = - _getTimeLabelWidth(widget.timeSlotViewSettings.timeRulerSize, _view); - final double top = viewHeaderHeight + timeLabelSize; - final double resourceItemHeight = _getResourceItemHeight( - resourceViewSize, - height - top, - widget.resourceViewSettings, - widget.dataSource.resources.length); - final double panelHeight = - resourceItemHeight * widget.dataSource.resources.length; - - final Widget verticalDivider = VerticalDivider( - width: 0.5, - thickness: 0.5, - color: widget.cellBorderColor ?? _calendarTheme.cellBorderColor, - ); + /// Show no selected date in agenda view when selected date is + /// disabled or black out date. + DateTime? currentSelectedDate; + if (_selectedDate != null) { + currentSelectedDate = isDateWithInDateRange( + widget.minDate, widget.maxDate, _selectedDate!) && + !CalendarViewHelper.isDateInDateCollection( + _blackoutDates, _selectedDate!) + ? _selectedDate + : null; + } - return Positioned( - left: isRTL ? _minWidth - resourceViewSize : 0, - width: resourceViewSize, - top: 0, - bottom: 0, - child: Stack(children: [ - Positioned( - left: _isRTL ? 0.5 : resourceViewSize - 0.5, - width: 0.5, - top: _controller.view == CalendarView.timelineMonth - ? widget.headerHeight - : widget.headerHeight + viewHeaderHeight, - child: verticalDivider, - height: _controller.view == CalendarView.timelineMonth - ? viewHeaderHeight - : timeLabelSize, - ), - Positioned( - left: 0, - width: resourceViewSize, - top: widget.headerHeight + top, - bottom: 0, - child: MouseRegion( - onEnter: (PointerEnterEvent event) { - _pointerEnterEvent(event, false, isRTL, null, - top + widget.headerHeight, 0, isResourceEnabled); - }, - onExit: _pointerExitEvent, - onHover: (PointerHoverEvent event) { - _pointerHoverEvent(event, false, isRTL, null, - top + widget.headerHeight, 0, isResourceEnabled); - }, + if (currentSelectedDate == null) { + return Positioned( + top: startPosition, + right: 0, + left: 0, + height: height, + child: _OpacityWidget( + opacity: _opacity, + child: Container( + color: widget.monthViewSettings.agendaStyle.backgroundColor ?? + _calendarTheme.agendaBackgroundColor, child: GestureDetector( - child: ListView( - padding: const EdgeInsets.all(0.0), - physics: const ClampingScrollPhysics(), - controller: _resourcePanelScrollController, - scrollDirection: Axis.vertical, - children: [ - CustomPaint( - painter: _ResourceContainer( - widget.dataSource.resources, - widget.resourceViewSettings, - resourceItemHeight, - widget.cellBorderColor, - _calendarTheme, - _resourceImageNotifier, - isRTL, - _textScaleFactor, - _resourceHoverNotifier.value), - size: Size(resourceViewSize, panelHeight), - ), - ]), + child: AgendaViewLayout( + widget.monthViewSettings, + null, + currentSelectedDate, + null, + isRTL, + _locale, + _localizations, + _calendarTheme, + _agendaViewNotifier, + widget.appointmentTimeTextFormat, + 0, + _textScaleFactor, + _isMobilePlatform, + widget.appointmentBuilder, + width, + height), onTapUp: (TapUpDetails details) { - _handleOnTapForResourcePanel(details, resourceItemHeight); + _handleTapForAgenda(details, null); }, onLongPressStart: (LongPressStartDetails details) { - _handleOnLongPressForResourcePanel( - details, resourceItemHeight); + _handleLongPressForAgenda(details, null); }, - ))) - ])); - } - - /// Handles and raises the [widget.onLongPress] callback, when the resource - /// panel is long pressed in [SfCalendar]. - void _handleOnLongPressForResourcePanel( - LongPressStartDetails details, double resourceItemHeight) { - if (!_shouldRaiseCalendarLongPressCallback(widget.onLongPress)) { - return; + )))); } - final CalendarResource tappedResource = - _getTappedResource(details.localPosition.dy, resourceItemHeight); - final List resourceAppointments = - _getSelectedResourceAppointments(tappedResource); - _raiseCalendarLongPressCallback(widget, - element: CalendarElement.resourceHeader, - resource: tappedResource, - appointments: resourceAppointments); - } + final List agendaAppointments = + AppointmentHelper.getSelectedDateAppointments( + _appointments, widget.timeZone, currentSelectedDate); + agendaAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + app1.actualStartTime.compareTo(app2.actualStartTime)); + agendaAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isAllDay, app2.isAllDay)); + agendaAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isSpanned, app2.isSpanned)); - /// Handles and raises the [widget.onTap] callback, when the resource panel - /// is tapped in [SfCalendar]. - void _handleOnTapForResourcePanel( - TapUpDetails details, double resourceItemHeight) { - if (!_shouldRaiseCalendarTapCallback(widget.onTap)) { - return; + /// Each appointment have top padding and it used to show the space + /// between two appointment views + const double topPadding = 5; + + /// Last appointment view have bottom padding and it show the space + /// between the last appointment and agenda view. + const double bottomPadding = 5; + final double appointmentHeight = + CalendarViewHelper.getScheduleAppointmentHeight( + widget.monthViewSettings, null); + final double allDayAppointmentHeight = + CalendarViewHelper.getScheduleAllDayAppointmentHeight( + widget.monthViewSettings, null); + double painterHeight = height; + if (agendaAppointments.isNotEmpty) { + final int count = _getAllDayCount(agendaAppointments); + painterHeight = (((count * (allDayAppointmentHeight + topPadding)) + + ((agendaAppointments.length - count) * + (appointmentHeight + topPadding))) + .toDouble()) + + bottomPadding; } - final CalendarResource tappedResource = - _getTappedResource(details.localPosition.dy, resourceItemHeight); - final List resourceAppointments = - _getSelectedResourceAppointments(tappedResource); - _raiseCalendarTapCallback(widget, - element: CalendarElement.resourceHeader, - resource: tappedResource, - appointments: resourceAppointments); + return Positioned( + top: startPosition, + right: 0, + left: 0, + height: height, + child: _OpacityWidget( + opacity: _opacity, + child: Container( + color: widget.monthViewSettings.agendaStyle.backgroundColor ?? + _calendarTheme.agendaBackgroundColor, + child: MouseRegion( + onEnter: (PointerEnterEvent event) { + _pointerEnterEvent(event, false, isRTL); + }, + onExit: _pointerExitEvent, + onHover: (PointerHoverEvent event) { + _pointerHoverEvent(event, false, isRTL); + }, + child: GestureDetector( + child: Stack(children: [ + CustomPaint( + painter: _AgendaDateTimePainter( + currentSelectedDate, + widget.monthViewSettings, + null, + widget.todayHighlightColor ?? + _calendarTheme.todayHighlightColor, + widget.todayTextStyle, + _locale, + _calendarTheme, + _agendaDateNotifier, + _minWidth, + isRTL, + _textScaleFactor, + _isMobilePlatform), + size: Size(_agendaDateViewWidth, height), + ), + Positioned( + top: 0, + left: isRTL ? 0 : _agendaDateViewWidth, + right: isRTL ? _agendaDateViewWidth : 0, + bottom: 0, + child: ListView( + padding: const EdgeInsets.all(0.0), + controller: _agendaScrollController, + children: [ + AgendaViewLayout( + widget.monthViewSettings, + null, + currentSelectedDate, + agendaAppointments, + isRTL, + _locale, + _localizations, + _calendarTheme, + _agendaViewNotifier, + widget.appointmentTimeTextFormat, + _agendaDateViewWidth, + _textScaleFactor, + _isMobilePlatform, + widget.appointmentBuilder, + width - _agendaDateViewWidth, + painterHeight), + ], + ), + ), + ]), + onTapUp: (TapUpDetails details) { + _handleTapForAgenda(details, _selectedDate!); + }, + onLongPressStart: (LongPressStartDetails details) { + _handleLongPressForAgenda(details, _selectedDate!); + }, + ))))); } +} - /// Filter and returns the appointment collection for the given resource from - /// the visible appointments collection. - List _getSelectedResourceAppointments(CalendarResource resource) { - final List selectedResourceAppointments = []; - if (_visibleAppointments == null || _visibleAppointments.isEmpty) { - return selectedResourceAppointments; - } +class _OpacityWidget extends StatefulWidget { + _OpacityWidget({required this.child, required this.opacity}); - for (int i = 0; i < _visibleAppointments.length; i++) { - final Appointment app = _visibleAppointments[i]; - if (app.resourceIds != null && - app.resourceIds.isNotEmpty && - app.resourceIds.contains(resource.id)) { - selectedResourceAppointments.add(app._data ?? app); - } + final Widget child; + + final ValueNotifier opacity; + + @override + State createState() => _OpacityWidgetState(); +} + +class _OpacityWidgetState extends State<_OpacityWidget> { + @override + void initState() { + widget.opacity.addListener(_update); + super.initState(); + } + + @override + void didUpdateWidget(covariant _OpacityWidget oldWidget) { + if (widget.opacity != oldWidget.opacity) { + oldWidget.opacity.removeListener(_update); + widget.opacity.addListener(_update); } + super.didUpdateWidget(oldWidget); + } - return selectedResourceAppointments; + void _update() { + setState(() { + /// Update the opacity widget with new opacity property value. + }); } - /// Returns the tapped resource details, based on the tapped position. - CalendarResource _getTappedResource( - double tappedPosition, double resourceItemHeight) { - final int index = - (_resourcePanelScrollController.offset + tappedPosition) ~/ - resourceItemHeight; - return widget.dataSource.resources[index]; + @override + void dispose() { + widget.opacity.removeListener(_update); + super.dispose(); } - /// Adds the custom scroll view which used to produce the infinity scroll. - Widget _addCustomScrollView( - double top, - double resourceViewSize, - bool isRTL, - bool isResourceEnabled, - double width, - double height, - double agendaHeight) { - return Positioned( - top: top, - left: isResourceEnabled && !isRTL ? resourceViewSize : 0, - right: isResourceEnabled && isRTL ? resourceViewSize : 0, - height: height - agendaHeight, - child: Opacity( - opacity: _opacity, - child: _CustomScrollView( - widget, - _view, - width - resourceViewSize, - height - agendaHeight, - _agendaSelectedDate, - isRTL, - _locale, - _calendarTheme, - _timeZoneLoaded ? widget.specialRegions : null, - _blackoutDates, - _controller, - _removeDatePicker, - _resourcePanelScrollController, - _textScaleFactor, - _isMobilePlatform, - _fadeInController, - updateCalendarState: (_UpdateCalendarStateDetails details) { - _updateCalendarState(details); - }, getCalendarState: (_UpdateCalendarStateDetails details) { - _getCalendarStateDetails(details); - })), - ); + @override + Widget build(BuildContext context) { + return Opacity(opacity: widget.opacity.value, child: widget.child); } +} - Widget _addChildren( - double agendaHeight, double height, double width, bool isRTL) { - final bool isResourceEnabled = _isResourceEnabled(widget.dataSource, _view); - final double resourceViewSize = - isResourceEnabled ? widget.resourceViewSettings.size : 0; - final DateTime currentViewDate = _currentViewVisibleDates[ - (_currentViewVisibleDates.length / 2).truncate()]; +/// Widget used to show the pop up animation to the child. +class _PopupWidget extends StatefulWidget { + _PopupWidget({required this.child, this.alignment = Alignment.topCenter}); - return Stack(children: [ - Positioned( - top: 0, - right: 0, - left: 0, - height: widget.headerHeight, - child: Container( - color: widget.headerStyle.backgroundColor ?? - _calendarTheme.headerBackgroundColor, - child: _CalendarHeaderView( - _currentViewVisibleDates, - widget.headerStyle, - currentViewDate, - _view, - widget.monthViewSettings.numberOfWeeksInView, - _calendarTheme, - isRTL, - _locale, - widget.showNavigationArrow, - _controller, - widget.maxDate, - widget.minDate, - width, - widget.headerHeight, - widget.timeSlotViewSettings.nonWorkingDays, - widget.monthViewSettings.navigationDirection, - widget.showDatePickerButton, - _showHeader, - widget.allowedViews, - widget.allowViewNavigation, - _localizations, - _removeDatePicker, - _headerUpdateNotifier, - _viewChangeNotifier, - _handleOnTapForHeader, - _handleOnLongPressForHeader, - widget.todayHighlightColor, - _textScaleFactor, - _isMobilePlatform)), - ), - _addResourcePanel(isResourceEnabled, resourceViewSize, height, isRTL), - _addCustomScrollView(widget.headerHeight, resourceViewSize, isRTL, - isResourceEnabled, width, height, agendaHeight), - _addAgendaView(agendaHeight, widget.headerHeight + height - agendaHeight, - width, isRTL), - _addDatePicker(widget.headerHeight, isRTL), - _getCalendarViewPopup(), - ]); + /// Widget that animated like popup. + final Widget child; + + /// Alignment defines the popup animation start position. + final Alignment alignment; + + @override + State createState() => _PopupWidgetState(); +} + +class _PopupWidgetState extends State<_PopupWidget> + with SingleTickerProviderStateMixin { + /// Controller used to handle the animation. + late AnimationController _animationController; + + /// Popup animation used to show the child like popup. + late Animation _animation; + + @override + void initState() { + _animationController = + AnimationController(vsync: this, duration: Duration(milliseconds: 200)); + _animation = + CurvedAnimation(parent: _animationController, curve: Curves.easeInOut); + super.initState(); } - void _removeDatePicker() { - if (widget.showDatePickerButton && _showHeader) { - setState(() { - _showHeader = false; - }); - } + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } - _viewChangeNotifier.value = false; + @override + Widget build(BuildContext context) { + /// Reset the existing animation. + _animationController.reset(); + + /// Start the animation. + _animationController.forward(); + return ScaleTransition( + alignment: widget.alignment, + scale: _animation, + child: FadeTransition(opacity: _animation, child: widget.child)); } +} - void _updateDatePicker() { - _viewChangeNotifier.value = false; - if (!widget.showDatePickerButton) { - return; - } +@immutable +class _CalendarHeaderView extends StatefulWidget { + const _CalendarHeaderView( + this.visibleDates, + this.headerStyle, + this.currentDate, + this.view, + this.numberOfWeeksInView, + this.calendarTheme, + this.isRTL, + this.locale, + this.showNavigationArrow, + this.controller, + this.maxDate, + this.minDate, + this.width, + this.height, + this.nonWorkingDays, + this.navigationDirection, + this.showDatePickerButton, + this.isPickerShown, + this.allowedViews, + this.allowViewNavigation, + this.localizations, + this.removePicker, + this.valueChangeNotifier, + this.viewChangeNotifier, + this.headerTapCallback, + this.headerLongPressCallback, + this.todayHighlightColor, + this.textScaleFactor, + this.isMobilePlatform, + this.headerDateFormat, + this.enableInteraction); + + final List visibleDates; + final CalendarHeaderStyle headerStyle; + final SfCalendarThemeData calendarTheme; + final DateTime? currentDate; + final CalendarView view; + final int numberOfWeeksInView; + final bool isRTL; + final String locale; + final bool showNavigationArrow; + final CalendarController controller; + final DateTime maxDate; + final DateTime minDate; + final double width; + final double height; + final List nonWorkingDays; + final List? allowedViews; + final bool allowViewNavigation; + final MonthNavigationDirection navigationDirection; + final VoidCallback removePicker; + final _CalendarHeaderCallback headerTapCallback; + final _CalendarHeaderCallback headerLongPressCallback; + final bool showDatePickerButton; + final SfLocalizations localizations; + final ValueNotifier valueChangeNotifier; + final ValueNotifier viewChangeNotifier; + final bool isPickerShown; + final double textScaleFactor; + final Color? todayHighlightColor; + final bool isMobilePlatform; + final String? headerDateFormat; + final bool enableInteraction; - setState(() { - _showHeader = !_showHeader; - }); + @override + _CalendarHeaderViewState createState() => _CalendarHeaderViewState(); +} + +class _CalendarHeaderViewState extends State<_CalendarHeaderView> { + late Map _calendarViews; + + @override + void initState() { + widget.valueChangeNotifier.addListener(_updateHeaderChanged); + _calendarViews = _getCalendarViewsText(widget.localizations); + super.initState(); } - Widget _addDatePicker(double top, bool isRTL) { - if (!widget.showDatePickerButton || !_showHeader) { - return Container(width: 0, height: 0); + @override + void didUpdateWidget(_CalendarHeaderView oldWidget) { + if (widget.valueChangeNotifier != oldWidget.valueChangeNotifier) { + oldWidget.valueChangeNotifier.removeListener(_updateHeaderChanged); + widget.valueChangeNotifier.addListener(_updateHeaderChanged); } - double maxHeight = _minHeight * 0.6; - double maxWidth = _minWidth * 0.5; + _calendarViews = _getCalendarViewsText(widget.localizations); + super.didUpdateWidget(oldWidget); + } - double pickerWidth = 0; - double pickerHeight = 0; + @override + Widget build(BuildContext context) { + final bool useMobilePlatformUI = CalendarViewHelper.isMobileLayoutUI( + widget.width, widget.isMobilePlatform); + double arrowWidth = 0; + double headerWidth = widget.width; - final TextStyle datePickerStyle = - widget.monthViewSettings.monthCellStyle.textStyle ?? - _calendarTheme.activeDatesTextStyle; - final Color todayColor = - widget.todayHighlightColor ?? _calendarTheme.todayHighlightColor; - double left = 0; - if (_isMobilePlatform) { - pickerWidth = _minWidth; - pickerHeight = _minHeight * 0.5; - } else { - const double padding = 5; - double arrowWidth = 0; - double iconWidth = _minWidth / 8; - iconWidth = iconWidth > 40 ? 40 : iconWidth; + /// Navigation arrow enabled when [showNavigationArrow] in [SfCalendar] is + /// enabled and calendar view as not schedule, because schedule view does + /// not have a support for navigation arrow. + final bool navigationArrowEnabled = + widget.showNavigationArrow && widget.view != CalendarView.schedule; + double iconWidth = widget.width / 8; + iconWidth = iconWidth > 40 ? 40 : iconWidth; + double calendarViewWidth = 0; - /// Navigation arrow enabled when [showNavigationArrow] in [SfCalendar] is - /// enabled and calendar view as not schedule, because schedule view does - /// not have a support for navigation arrow. - final bool navigationArrowEnabled = - widget.showNavigationArrow && _view != CalendarView.schedule; + /// Assign arrow width as icon width when the navigation arrow enabled. + if (navigationArrowEnabled) { + arrowWidth = iconWidth; + } - /// Assign arrow width as icon width when the navigation arrow enabled. - if (navigationArrowEnabled) { - arrowWidth = iconWidth; - } + final String headerString = _getHeaderText(); + final double totalArrowWidth = arrowWidth * 2; - final double totalArrowWidth = 2 * arrowWidth; - final double totalWidth = _minWidth - totalArrowWidth; - final double totalHeight = _minHeight - widget.headerHeight; - maxHeight = maxHeight < 250 - ? (totalHeight < 250 ? totalHeight - 10 : 250) - : maxHeight; - maxWidth = maxWidth < 250 - ? (totalWidth < 250 ? totalWidth - 10 : 250) - : maxWidth; - double containerSize = maxHeight > maxWidth ? maxWidth : maxHeight; - if (containerSize > 300) { - containerSize = 300; + /// Show calendar views on header when it is not empty. + final bool isNeedViewSwitchOption = + widget.allowedViews != null && widget.allowedViews!.isNotEmpty; + double todayIconWidth = 0; + double dividerWidth = 0; + final List children = []; + Color? headerTextColor = widget.headerStyle.textStyle != null + ? widget.headerStyle.textStyle!.color + : (widget.calendarTheme.headerTextStyle.color); + headerTextColor ??= Colors.black87; + final Color arrowColor = + headerTextColor.withOpacity(headerTextColor.opacity * 0.6); + Color prevArrowColor = arrowColor; + Color nextArrowColor = arrowColor; + final TextStyle style = TextStyle(color: arrowColor); + final double defaultCalendarViewTextSize = 12; + Widget calendarViewIcon = Container(width: 0, height: 0); + const double padding = 5; + double? headerIconTextWidth = widget.headerStyle.textStyle != null + ? widget.headerStyle.textStyle!.fontSize + : widget.calendarTheme.headerTextStyle.fontSize; + headerIconTextWidth ??= 14; + final String todayText = widget.localizations.todayLabel; + + double maxHeaderHeight = 0; + + /// Today icon shown when the date picker enabled on calendar. + if (widget.showDatePickerButton) { + todayIconWidth = iconWidth; + if (!useMobilePlatformUI) { + /// 5 as padding for around today text view. + final Size todayButtonSize = _getTextWidgetWidth( + todayText, widget.height, widget.width - totalArrowWidth, context, + style: TextStyle(fontSize: defaultCalendarViewTextSize)); + maxHeaderHeight = todayButtonSize.height; + todayIconWidth = todayButtonSize.width + padding; } + } - pickerWidth = containerSize; - pickerHeight = containerSize; - left = - isRTL ? _minWidth - containerSize - totalArrowWidth : totalArrowWidth; - if (widget.headerStyle.textAlign == TextAlign.right || - widget.headerStyle.textAlign == TextAlign.end) { - left = isRTL ? padding : _minWidth - containerSize - totalArrowWidth; - } else if (widget.headerStyle.textAlign == TextAlign.center || - widget.headerStyle.textAlign == TextAlign.justify) { - final double calendarViewWidth = _calendarViewWidth; + double headerTextWidth = 0; + if (!widget.isMobilePlatform) { + final Size headerTextSize = _getTextWidgetWidth( + headerString, + widget.height, + widget.width - totalArrowWidth - todayIconWidth - padding, + context, + style: widget.headerStyle.textStyle ?? + widget.calendarTheme.headerTextStyle); + headerTextWidth = headerTextSize.width + + padding + + (widget.showDatePickerButton ? headerIconTextWidth : 0); + maxHeaderHeight = maxHeaderHeight > headerTextSize.height + ? maxHeaderHeight + : headerTextSize.height; + } - double headerViewWidth = - _minWidth - calendarViewWidth - totalArrowWidth; - if (headerViewWidth == _minWidth) { - left = (_minWidth - containerSize) / 2; + if (isNeedViewSwitchOption) { + calendarViewWidth = iconWidth; + if (useMobilePlatformUI) { + maxHeaderHeight = + maxHeaderHeight != 0 && maxHeaderHeight <= widget.height + ? maxHeaderHeight + : widget.height; + + /// Render allowed views icon on mobile view. + calendarViewIcon = _getCalendarViewWidget( + useMobilePlatformUI, + false, + calendarViewWidth, + maxHeaderHeight, + style, + arrowColor, + headerTextColor, + widget.view, + widget.isMobilePlatform ? false : widget.viewChangeNotifier.value, + defaultCalendarViewTextSize, + semanticLabel: 'CalendarView'); + } else { + /// Assign divider width when today icon text shown. + dividerWidth = widget.showDatePickerButton ? 5 : 0; + + double totalWidth = + widget.width - totalArrowWidth - dividerWidth - todayIconWidth; + + totalWidth -= headerTextWidth; + final Map calendarViewsWidth = + {}; + double allowedViewsWidth = 0; + final int allowedViewsLength = widget.allowedViews!.length; + + double maxCalendarViewHeight = 0; + + /// Calculate the allowed views horizontal width. + for (int i = 0; i < allowedViewsLength; i++) { + final CalendarView currentView = widget.allowedViews![i]; + final Size calendarViewSize = _getTextWidgetWidth( + _calendarViews[currentView]!, widget.height, totalWidth, context, + style: TextStyle(fontSize: defaultCalendarViewTextSize)); + final double currentViewTextWidth = calendarViewSize.width + padding; + maxCalendarViewHeight = + maxCalendarViewHeight > calendarViewSize.height + ? maxCalendarViewHeight + : calendarViewSize.height; + calendarViewsWidth[currentView] = currentViewTextWidth; + allowedViewsWidth += currentViewTextWidth; + } + + /// Check the header view width enough for hold allowed views then + /// render the allowed views as children. + if (allowedViewsWidth < totalWidth) { + calendarViewWidth = allowedViewsWidth; + maxHeaderHeight = maxCalendarViewHeight > maxHeaderHeight + ? maxCalendarViewHeight + : maxHeaderHeight; + maxHeaderHeight = + maxHeaderHeight > widget.height ? widget.height : maxHeaderHeight; + for (int i = 0; i < allowedViewsLength; i++) { + final CalendarView currentView = widget.allowedViews![i]; + children.add(_getCalendarViewWidget( + useMobilePlatformUI, + false, + calendarViewsWidth[currentView]!, + maxHeaderHeight, + style, + arrowColor, + headerTextColor, + currentView, + widget.view == currentView, + defaultCalendarViewTextSize)); + } } else { - headerViewWidth = headerViewWidth > 200 ? 200 : headerViewWidth; - final double leftPadding = (_minWidth - - headerViewWidth - - calendarViewWidth - - totalArrowWidth) / - 2; - double headerPadding = (headerViewWidth - containerSize) / 2; - headerPadding = headerPadding > 0 ? headerPadding : 0; - left = _isRTL - ? leftPadding + - arrowWidth + - calendarViewWidth + - headerViewWidth - - containerSize - : leftPadding + arrowWidth + headerPadding; + /// Render allowed views drop down when header view does not have a + /// space to hold the allowed views. + final Size calendarViewSize = _getTextWidgetWidth( + _calendarViews[widget.view]!, + widget.height, + widget.width - totalArrowWidth, + context, + style: TextStyle(fontSize: defaultCalendarViewTextSize)); + maxCalendarViewHeight = calendarViewSize.height; + maxHeaderHeight = maxCalendarViewHeight > maxHeaderHeight + ? maxCalendarViewHeight + : maxHeaderHeight; + maxHeaderHeight = + maxHeaderHeight > widget.height ? widget.height : maxHeaderHeight; + calendarViewWidth = + calendarViewSize.width + padding + headerIconTextWidth; + children.add(_getCalendarViewWidget( + useMobilePlatformUI, + true, + calendarViewWidth, + maxHeaderHeight, + style, + arrowColor, + headerTextColor, + widget.view, + widget.viewChangeNotifier.value, + defaultCalendarViewTextSize, + semanticLabel: 'CalendarView')); } } } - return Positioned( - top: top, - left: left, - width: pickerWidth, - height: pickerHeight, - child: _PopupWidget( - child: Container( - margin: EdgeInsets.all(0), - padding: EdgeInsets.all(5), - decoration: _isMobilePlatform - ? BoxDecoration( - color: _calendarTheme.brightness != null && - _calendarTheme.brightness == Brightness.dark - ? Colors.grey[850] - : Colors.white, - boxShadow: [ - BoxShadow( - offset: Offset(0.0, 3.0), - blurRadius: 2.0, - spreadRadius: 0.0, - color: Color(0x24000000)), - ], - shape: BoxShape.rectangle, - ) - : BoxDecoration( - color: _calendarTheme.brightness != null && - _calendarTheme.brightness == Brightness.dark - ? Colors.grey[850] - : Colors.white, - boxShadow: kElevationToShadow[6], - borderRadius: BorderRadius.circular(2.0), - shape: BoxShape.rectangle, - ), - child: SfDateRangePicker( - showNavigationArrow: true, - initialSelectedDate: _currentDate, - initialDisplayDate: _currentDate, - todayHighlightColor: todayColor, - minDate: widget.minDate, - maxDate: widget.maxDate, - selectionColor: todayColor, - headerStyle: DateRangePickerHeaderStyle( - textAlign: - _isMobilePlatform ? TextAlign.center : TextAlign.left, - ), - monthViewSettings: DateRangePickerMonthViewSettings( - viewHeaderHeight: pickerHeight / 8, - firstDayOfWeek: widget.firstDayOfWeek, - ), - monthCellStyle: DateRangePickerMonthCellStyle( - textStyle: datePickerStyle, - todayTextStyle: - datePickerStyle.copyWith(color: todayColor)), - yearCellStyle: DateRangePickerYearCellStyle( - textStyle: datePickerStyle, - todayTextStyle: datePickerStyle.copyWith(color: todayColor), - leadingDatesTextStyle: widget.monthViewSettings - .monthCellStyle.leadingDatesTextStyle ?? - _calendarTheme.leadingDatesTextStyle, + headerWidth = widget.width - + calendarViewWidth - + todayIconWidth - + dividerWidth - + totalArrowWidth; + final double headerHeight = + maxHeaderHeight != 0 && maxHeaderHeight <= widget.height + ? maxHeaderHeight + : widget.height; + final List dates = widget.visibleDates; + if (!DateTimeHelper.canMoveToNextView( + widget.view, + widget.numberOfWeeksInView, + widget.minDate, + widget.maxDate, + dates, + widget.nonWorkingDays)) { + nextArrowColor = nextArrowColor.withOpacity(nextArrowColor.opacity * 0.5); + } + + if (!DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.numberOfWeeksInView, + widget.minDate, + widget.maxDate, + dates, + widget.nonWorkingDays)) { + prevArrowColor = prevArrowColor.withOpacity(prevArrowColor.opacity * 0.5); + } + + MainAxisAlignment _getAlignmentFromTextAlign() { + if (widget.headerStyle.textAlign == TextAlign.left || + widget.headerStyle.textAlign == TextAlign.start) { + return MainAxisAlignment.start; + } else if (widget.headerStyle.textAlign == TextAlign.right || + widget.headerStyle.textAlign == TextAlign.end) { + return MainAxisAlignment.end; + } + + return MainAxisAlignment.center; + } + + double arrowSize = + headerHeight == widget.height ? headerHeight * 0.6 : headerHeight * 0.8; + arrowSize = arrowSize > 25 ? 25 : arrowSize; + arrowSize = arrowSize * widget.textScaleFactor; + final bool isCenterAlignment = !widget.isMobilePlatform && + (navigationArrowEnabled || isNeedViewSwitchOption) && + (widget.headerStyle.textAlign == TextAlign.center || + widget.headerStyle.textAlign == TextAlign.justify); + + Alignment _getHeaderAlignment() { + if (widget.headerStyle.textAlign == TextAlign.left || + widget.headerStyle.textAlign == TextAlign.start) { + return widget.isRTL ? Alignment.centerRight : Alignment.centerLeft; + } else if (widget.headerStyle.textAlign == TextAlign.right || + widget.headerStyle.textAlign == TextAlign.end) { + return widget.isRTL ? Alignment.centerLeft : Alignment.centerRight; + } + + return Alignment.center; + } + + final Widget headerText = widget.isMobilePlatform + ? Container( + alignment: Alignment.center, + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + width: isCenterAlignment && headerWidth > 200 ? 200 : headerWidth, + height: headerHeight, + padding: const EdgeInsets.all(2), + child: Material( + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + child: InkWell( + //// set splash color as transparent when header does not have + // date piker. + splashColor: + !widget.showDatePickerButton || !widget.enableInteraction + ? Colors.transparent + : null, + highlightColor: + !widget.showDatePickerButton || !widget.enableInteraction + ? Colors.transparent + : null, + hoverColor: + !widget.showDatePickerButton || !widget.enableInteraction + ? Colors.transparent + : null, + splashFactory: _CustomSplashFactory(), + onTap: () { + if (!widget.enableInteraction) { + return; + } + widget.headerTapCallback( + calendarViewWidth + dividerWidth + todayIconWidth); + }, + onLongPress: () { + if (!widget.enableInteraction) { + return; + } + widget.headerLongPressCallback( + calendarViewWidth + dividerWidth + todayIconWidth); + }, + child: Semantics( + label: headerString, + child: Container( + width: isCenterAlignment && headerWidth > 200 + ? 200 + : headerWidth, + height: headerHeight, + alignment: Alignment.centerLeft, + padding: EdgeInsets.symmetric(horizontal: 5), + child: Row( + mainAxisAlignment: _getAlignmentFromTextAlign(), + children: widget.showDatePickerButton + ? [ + Flexible( + child: Text(headerString, + style: widget.headerStyle.textStyle ?? + widget.calendarTheme + .headerTextStyle, + maxLines: 1, + overflow: TextOverflow.clip, + softWrap: false, + textDirection: TextDirection.ltr)), + Icon( + widget.isPickerShown + ? Icons.arrow_drop_up + : Icons.arrow_drop_down, + color: arrowColor, + size: (widget.headerStyle.textStyle ?? + widget.calendarTheme + .headerTextStyle) + .fontSize ?? + 14, + ) + ] + : [ + Flexible( + child: Text(headerString, + style: widget.headerStyle.textStyle ?? + widget.calendarTheme + .headerTextStyle, + maxLines: 1, + overflow: TextOverflow.clip, + softWrap: false, + textDirection: TextDirection.ltr)) + ], + )), ), - view: _view == CalendarView.month || - _view == CalendarView.timelineMonth - ? DateRangePickerView.year - : DateRangePickerView.month, - onViewChanged: (DateRangePickerViewChangedArgs details) { - if ((_view != CalendarView.month && - _view != CalendarView.timelineMonth) || - details.view != DateRangePickerView.month) { + )), + ) + : Container( + alignment: _getHeaderAlignment(), + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + width: isCenterAlignment && headerWidth > 200 ? 200 : headerWidth, + height: headerHeight, + padding: const EdgeInsets.all(2), + child: Material( + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + child: InkWell( + //// set splash color as transparent when header does not have + // date piker. + splashColor: + !widget.showDatePickerButton || !widget.enableInteraction + ? Colors.transparent + : null, + highlightColor: + !widget.showDatePickerButton || !widget.enableInteraction + ? Colors.transparent + : null, + splashFactory: _CustomSplashFactory(), + onTap: () { + if (!widget.enableInteraction) { return; } - - if (isSameDate(_currentDate, _controller.displayDate) || - isDateWithInDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1], - _controller.displayDate)) { - _removeDatePicker(); - } - - _showHeader = false; - _controller.displayDate = DateTime( - details.visibleDateRange.startDate.year, - details.visibleDateRange.startDate.month, - details.visibleDateRange.startDate.day, - _controller.displayDate.hour, - _controller.displayDate.minute, - _controller.displayDate.second); + widget.headerTapCallback( + calendarViewWidth + dividerWidth + todayIconWidth); }, - onSelectionChanged: - (DateRangePickerSelectionChangedArgs details) { - if (isSameDate(_currentDate, _controller.displayDate) || - isDateWithInDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1], - _controller.displayDate)) { - _removeDatePicker(); + onLongPress: () { + if (!widget.enableInteraction) { + return; } - - _showHeader = false; - _controller.displayDate = DateTime( - details.value.year, - details.value.month, - details.value.day, - _controller.displayDate.hour, - _controller.displayDate.minute, - _controller.displayDate.second); + widget.headerLongPressCallback( + calendarViewWidth + dividerWidth + todayIconWidth); }, - )))); + child: Semantics( + label: headerString, + child: Container( + color: + widget.showDatePickerButton && widget.isPickerShown + ? Colors.grey.withOpacity(0.3) + : widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + width: isCenterAlignment && headerTextWidth > 200 + ? 200 + : headerTextWidth, + height: headerHeight, + alignment: Alignment.center, + padding: EdgeInsets.symmetric(horizontal: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: widget.showDatePickerButton + ? [ + Flexible( + child: Text(headerString, + style: widget.headerStyle.textStyle ?? + widget.calendarTheme + .headerTextStyle, + maxLines: 1, + overflow: TextOverflow.clip, + softWrap: false, + textDirection: TextDirection.ltr)), + Icon( + widget.isPickerShown + ? Icons.arrow_drop_up + : Icons.arrow_drop_down, + color: arrowColor, + size: (widget.headerStyle.textStyle ?? + widget.calendarTheme + .headerTextStyle) + .fontSize ?? + 14, + ) + ] + : [ + Flexible( + child: Text(headerString, + style: widget.headerStyle.textStyle ?? + widget.calendarTheme + .headerTextStyle, + maxLines: 1, + overflow: TextOverflow.clip, + softWrap: false, + textDirection: TextDirection.ltr)) + ], + )), + ), + )), + ); + + final Container leftArrow = Container( + alignment: Alignment.center, + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + width: arrowWidth, + height: headerHeight, + padding: const EdgeInsets.all(2), + child: Material( + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + child: InkWell( + //// set splash color as transparent when arrow reaches min date(disabled) + splashColor: + prevArrowColor != arrowColor || !widget.enableInteraction + ? Colors.transparent + : null, + highlightColor: + prevArrowColor != arrowColor || !widget.enableInteraction + ? Colors.transparent + : null, + hoverColor: + prevArrowColor != arrowColor || !widget.enableInteraction + ? Colors.transparent + : null, + splashFactory: _CustomSplashFactory(), + onTap: _backward, + child: Semantics( + label: 'Backward', + child: Container( + width: arrowWidth, + height: headerHeight, + alignment: Alignment.center, + child: Icon( + widget.navigationDirection == + MonthNavigationDirection.horizontal + ? Icons.chevron_left + : Icons.keyboard_arrow_up, + color: prevArrowColor, + size: arrowSize, + )), + ), + )), + ); + + final Container rightArrow = Container( + alignment: Alignment.center, + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + width: arrowWidth, + height: headerHeight, + padding: const EdgeInsets.all(2), + child: Material( + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + child: InkWell( + //// set splash color as transparent when arrow reaches max date(disabled) + splashColor: + nextArrowColor != arrowColor || !widget.enableInteraction + ? Colors.transparent + : null, + highlightColor: + nextArrowColor != arrowColor || !widget.enableInteraction + ? Colors.transparent + : null, + hoverColor: + nextArrowColor != arrowColor || !widget.enableInteraction + ? Colors.transparent + : null, + splashFactory: _CustomSplashFactory(), + onTap: _forward, + child: Semantics( + label: 'Forward', + child: Container( + width: arrowWidth, + height: headerHeight, + alignment: Alignment.center, + child: Icon( + widget.navigationDirection == + MonthNavigationDirection.horizontal + ? Icons.chevron_right + : Icons.keyboard_arrow_down, + color: nextArrowColor, + size: arrowSize, + )), + ), + )), + ); + + final Widget todayIcon = Container( + alignment: Alignment.center, + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + width: todayIconWidth, + height: headerHeight, + padding: const EdgeInsets.all(2), + child: Material( + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + child: InkWell( + splashColor: !widget.enableInteraction ? Colors.transparent : null, + highlightColor: + !widget.enableInteraction ? Colors.transparent : null, + hoverColor: !widget.enableInteraction ? Colors.transparent : null, + splashFactory: _CustomSplashFactory(), + onTap: () { + if (!widget.enableInteraction) { + return; + } + + widget.removePicker(); + widget.controller.displayDate = DateTime.now(); + }, + child: Semantics( + label: todayText, + child: useMobilePlatformUI + ? Container( + width: todayIconWidth, + height: headerHeight, + alignment: Alignment.center, + child: Icon( + Icons.today, + color: style.color, + size: style.fontSize, + )) + : Container( + width: todayIconWidth, + alignment: Alignment.center, + child: Text( + todayText, + style: TextStyle( + color: headerTextColor, + fontSize: defaultCalendarViewTextSize), + maxLines: 1, + textDirection: TextDirection.ltr, + )), + ), + )), + ); + + final Widget dividerWidget = widget.showDatePickerButton && + isNeedViewSwitchOption && + !useMobilePlatformUI + ? Container( + alignment: Alignment.center, + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + width: dividerWidth, + height: headerHeight, + padding: const EdgeInsets.symmetric(vertical: 5), + child: VerticalDivider( + color: Colors.grey, + thickness: 0.5, + )) + : Container( + width: 0, + height: 0, + ); + + List rowChildren = []; + if (widget.headerStyle.textAlign == TextAlign.left || + widget.headerStyle.textAlign == TextAlign.start) { + if (widget.isMobilePlatform) { + rowChildren = [ + headerText, + todayIcon, + calendarViewIcon, + leftArrow, + rightArrow, + ]; + } else { + rowChildren = [ + leftArrow, + rightArrow, + headerText, + todayIcon, + dividerWidget, + ]; + useMobilePlatformUI + ? rowChildren.add(calendarViewIcon) + : rowChildren.addAll(children); + } + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: rowChildren); + } else if (widget.headerStyle.textAlign == TextAlign.right || + widget.headerStyle.textAlign == TextAlign.end) { + if (widget.isMobilePlatform) { + rowChildren = [ + leftArrow, + rightArrow, + calendarViewIcon, + todayIcon, + headerText, + ]; + } else { + useMobilePlatformUI + ? rowChildren.add(calendarViewIcon) + : rowChildren.addAll(children); + + rowChildren.add(dividerWidget); + rowChildren.add(todayIcon); + rowChildren.add(headerText); + rowChildren.add(leftArrow); + rowChildren.add(rightArrow); + } + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: rowChildren); + } else { + if (widget.isMobilePlatform) { + rowChildren = [ + leftArrow, + headerText, + todayIcon, + dividerWidget, + calendarViewIcon, + rightArrow, + ]; + } else { + rowChildren = [ + leftArrow, + headerText, + rightArrow, + todayIcon, + dividerWidget, + ]; + useMobilePlatformUI + ? rowChildren.add(calendarViewIcon) + : rowChildren.addAll(children); + } + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: rowChildren); + } + } + + @override + void dispose() { + widget.valueChangeNotifier.removeListener(_updateHeaderChanged); + super.dispose(); + } + + void _updateHeaderChanged() { + setState(() {}); } - void _getCalendarStateDetails(_UpdateCalendarStateDetails details) { - details._currentDate = _currentDate; - details._currentViewVisibleDates = _currentViewVisibleDates; - details._selectedDate = _selectedDate; - details._allDayPanelHeight = _allDayPanelHeight; - details._allDayAppointmentViewCollection = _allDayAppointmentViewCollection; - details._visibleAppointments = _visibleAppointments; - details._appointments = _appointments; + void _backward() { + if (!widget.enableInteraction) { + return; + } + widget.removePicker(); + widget.controller.backward!(); } - void _updateCalendarState(_UpdateCalendarStateDetails details) { - if (details._currentDate != null && - !isSameDate(details._currentDate, _currentDate)) { - _currentDate = details._currentDate; - _controller.displayDate = details._currentDate; + void _forward() { + if (!widget.enableInteraction) { + return; } + widget.removePicker(); + widget.controller.forward!(); + } - if (details._currentViewVisibleDates != null && - _currentViewVisibleDates != details._currentViewVisibleDates) { - _currentViewVisibleDates = details._currentViewVisibleDates; - _allDayAppointmentViewCollection = null; - _visibleAppointments = null; - _allDayPanelHeight = 0; - _updateVisibleAppointments(); - if (_shouldRaiseViewChangedCallback(widget.onViewChanged)) { - final bool showTrailingLeadingDates = _isLeadingAndTrailingDatesVisible( - widget.monthViewSettings.numberOfWeeksInView, - widget.monthViewSettings.showTrailingAndLeadingDates); - List visibleDates = _currentViewVisibleDates; - if (!showTrailingLeadingDates) { - visibleDates = _getCurrentMonthDates(visibleDates); + Widget _getCalendarViewWidget( + bool useMobilePlatformUI, + bool isNeedIcon, + double width, + double height, + TextStyle style, + Color arrowColor, + Color headerTextColor, + CalendarView view, + bool isHighlighted, + double defaultCalendarViewTextSize, + {String? semanticLabel}) { + final String text = _calendarViews[view]!; + return Container( + alignment: Alignment.center, + color: widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + width: width, + height: height, + padding: EdgeInsets.all(2), + child: Material( + color: isHighlighted && (isNeedIcon || useMobilePlatformUI) + ? Colors.grey.withOpacity(0.3) + : widget.headerStyle.backgroundColor ?? + widget.calendarTheme.headerBackgroundColor, + child: InkWell( + splashColor: !widget.enableInteraction ? Colors.transparent : null, + highlightColor: + !widget.enableInteraction ? Colors.transparent : null, + hoverColor: !widget.enableInteraction ? Colors.transparent : null, + splashFactory: _CustomSplashFactory(), + onTap: () { + if (!widget.enableInteraction) { + return; + } + if (isNeedIcon || useMobilePlatformUI) { + widget.viewChangeNotifier.value = + !widget.viewChangeNotifier.value; + } else { + widget.controller.view = view; + } + }, + child: Semantics( + label: semanticLabel ?? text, + child: useMobilePlatformUI + ? Container( + width: width, + height: height, + alignment: Alignment.center, + child: Icon( + Icons.more_vert, + color: style.color, + size: style.fontSize, + )) + : (isNeedIcon + ? Container( + width: width, + height: height, + alignment: Alignment.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + text, + style: TextStyle( + color: headerTextColor, + fontSize: defaultCalendarViewTextSize), + maxLines: 1, + textDirection: TextDirection.ltr, + ), + Icon( + widget.viewChangeNotifier.value + ? Icons.arrow_drop_up + : Icons.arrow_drop_down, + color: arrowColor, + size: (widget.headerStyle.textStyle ?? + widget + .calendarTheme.headerTextStyle) + .fontSize ?? + 14, + ) + ], + )) + : Container( + width: width, + height: height, + alignment: Alignment.center, + child: Text( + text, + style: TextStyle( + color: isHighlighted + ? widget.todayHighlightColor ?? + widget.calendarTheme.todayHighlightColor + : headerTextColor, + fontSize: defaultCalendarViewTextSize), + maxLines: 1, + textDirection: TextDirection.ltr, + ))), + ), + )), + ); + } + + String _getHeaderText() { + String monthFormat = 'MMMM'; + final String? headerDateFormat = + widget.headerDateFormat != null && widget.headerDateFormat!.isNotEmpty + ? widget.headerDateFormat + : null; + switch (widget.view) { + case CalendarView.schedule: + { + if (headerDateFormat != null) { + return DateFormat(headerDateFormat, widget.locale) + .format(widget.valueChangeNotifier.value!) + .toString(); + } + return DateFormat(monthFormat, widget.locale) + .format(widget.valueChangeNotifier.value!) + .toString() + + ' ' + + widget.valueChangeNotifier.value!.year.toString(); + } + case CalendarView.month: + case CalendarView.timelineMonth: + { + final DateTime startDate = widget.visibleDates[0]; + final DateTime endDate = + widget.visibleDates[widget.visibleDates.length - 1]; + if (widget.numberOfWeeksInView != 6 && + startDate.month != endDate.month) { + if (headerDateFormat != null) { + return DateFormat(headerDateFormat, widget.locale) + .format(startDate) + .toString() + + ' - ' + + DateFormat(headerDateFormat, widget.locale) + .format(endDate) + .toString(); + } + monthFormat = 'MMM'; + return DateFormat(monthFormat, widget.locale) + .format(startDate) + .toString() + + ' ' + + startDate.year.toString() + + ' - ' + + DateFormat(monthFormat, widget.locale) + .format(endDate) + .toString() + + ' ' + + endDate.year.toString(); + } + + if (headerDateFormat != null) { + return DateFormat(headerDateFormat, widget.locale) + .format(widget.currentDate!) + .toString(); + } + return DateFormat(monthFormat, widget.locale) + .format(widget.currentDate!) + .toString() + + ' ' + + widget.currentDate!.year.toString(); + } + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + case CalendarView.timelineDay: + { + final DateTime headerDate = widget.visibleDates[0]; + if (headerDateFormat != null) { + return DateFormat(headerDateFormat, widget.locale) + .format(headerDate) + .toString(); + } + return DateFormat(monthFormat, widget.locale) + .format(headerDate) + .toString() + + ' ' + + headerDate.year.toString(); + } + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + { + final DateTime startDate = widget.visibleDates[0]; + final DateTime endDate = + widget.visibleDates[widget.visibleDates.length - 1]; + if (headerDateFormat != null) { + return DateFormat(headerDateFormat, widget.locale) + .format(startDate) + .toString() + + ' - ' + + DateFormat(headerDateFormat, widget.locale) + .format(endDate) + .toString(); + } + monthFormat = 'MMM'; + String startText = DateFormat(monthFormat, widget.locale) + .format(startDate) + .toString(); + startText = startDate.day.toString() + ' ' + startText + ' - '; + final String endText = endDate.day.toString() + + ' ' + + DateFormat(monthFormat, widget.locale) + .format(endDate) + .toString() + + ' ' + + endDate.year.toString(); + + return startText + endText; } + } + } +} + +/// It is used to generate the week and month label of schedule calendar view. +class _ScheduleLabelPainter extends CustomPainter { + _ScheduleLabelPainter( + this.startDate, + this.endDate, + this.scheduleViewSettings, + this.isMonthLabel, + this.isRTL, + this.locale, + this.useMobilePlatformUI, + this.agendaViewNotifier, + this.calendarTheme, + this._localizations, + this.textScaleFactor, + {this.isDisplayDate = false}) + : super(repaint: isDisplayDate ? agendaViewNotifier : null); + + final DateTime startDate; + final DateTime? endDate; + final bool isMonthLabel; + final bool isRTL; + final String locale; + final ScheduleViewSettings scheduleViewSettings; + final SfLocalizations _localizations; + final bool useMobilePlatformUI; + final ValueNotifier agendaViewNotifier; + final SfCalendarThemeData calendarTheme; + final bool isDisplayDate; + final double textScaleFactor; + TextPainter _textPainter = TextPainter(); + Paint _backgroundPainter = Paint(); - _raiseViewChangedCallback(widget, visibleDates: visibleDates); + @override + void paint(Canvas canvas, Size size) { + /// Draw the week label. + if (!isMonthLabel) { + if (isDisplayDate) { + _addDisplayDateLabel(canvas, size); + } else { + _addWeekLabel(canvas, size); } + } else { + /// Draw the month label + _addMonthLabel(canvas, size); + } + } + + void _addDisplayDateLabel(Canvas canvas, Size size) { + /// Add the localized add new appointment text for display date view. + final TextSpan span = TextSpan( + text: _localizations.noEventsCalendarLabel, + style: scheduleViewSettings.weekHeaderSettings.weekTextStyle ?? + const TextStyle( + color: Colors.grey, fontSize: 15, fontFamily: 'Roboto'), + ); + + double xPosition = 10; + _updateTextPainter(span); + + _textPainter.layout( + minWidth: 0, + maxWidth: size.width - xPosition > 0 ? size.width - xPosition : 0); + if (isRTL) { + xPosition = size.width - _textPainter.width - xPosition; } - if (!_isSameTimeSlot(details._selectedDate, _selectedDate)) { - _selectedDate = details._selectedDate; - _controller.selectedDate = details._selectedDate; + /// Draw display date view text + _textPainter.paint( + canvas, Offset(xPosition, (size.height - _textPainter.height) / 2)); + + /// Add hovering effect on display date view. + if (isDisplayDate && + agendaViewNotifier.value != null && + isSameDate(agendaViewNotifier.value!.hoveringDate, startDate)) { + const double padding = 5; + if (useMobilePlatformUI) { + final Rect rect = Rect.fromLTWH( + 0, padding, size.width - 2, size.height - (2 * padding)); + _backgroundPainter.color = + calendarTheme.selectionBorderColor!.withOpacity(0.4); + _backgroundPainter.style = PaintingStyle.stroke; + _backgroundPainter.strokeWidth = 2; + canvas.drawRect(rect, _backgroundPainter); + _backgroundPainter.style = PaintingStyle.fill; + } else { + const double viewPadding = 2; + final Rect rect = Rect.fromLTWH( + 0, + padding + viewPadding, + size.width - (isRTL ? viewPadding : padding), + size.height - (2 * (viewPadding + padding))); + _backgroundPainter.color = Colors.grey.withOpacity(0.1); + canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(4)), + _backgroundPainter); + } } } - //// Handles the on tap callback for header - void _handleOnTapForHeader(double width) { - _calendarViewWidth = width; - _updateDatePicker(); - if (!_shouldRaiseCalendarTapCallback(widget.onTap)) { - return; + void _addWeekLabel(Canvas canvas, Size size) { + double xPosition = 0; + const double yPosition = 0; + final String startDateFormat = + scheduleViewSettings.weekHeaderSettings.startDateFormat ?? 'MMM dd'; + String? endDateFormat = + scheduleViewSettings.weekHeaderSettings.endDateFormat; + if (startDate.month == endDate!.month && endDateFormat == null) { + endDateFormat = 'dd'; + } + + endDateFormat ??= 'MMM dd'; + final String firstDate = + DateFormat(startDateFormat, locale).format(startDate).toString(); + final String lastDate = + DateFormat(endDateFormat, locale).format(endDate!).toString(); + final TextSpan span = TextSpan( + text: firstDate + ' - ' + lastDate, + style: scheduleViewSettings.weekHeaderSettings.weekTextStyle ?? + const TextStyle( + color: Colors.grey, fontSize: 15, fontFamily: 'Roboto'), + ); + _backgroundPainter.color = + scheduleViewSettings.weekHeaderSettings.backgroundColor; + + /// Draw week label background. + canvas.drawRect( + Rect.fromLTWH(0, yPosition, size.width, + scheduleViewSettings.weekHeaderSettings.height), + _backgroundPainter); + _updateTextPainter(span); + + _textPainter.layout( + minWidth: 0, maxWidth: size.width - 10 > 0 ? size.width - 10 : 0); + + if (scheduleViewSettings.weekHeaderSettings.textAlign == TextAlign.right || + scheduleViewSettings.weekHeaderSettings.textAlign == TextAlign.end) { + xPosition = size.width - _textPainter.width; + } else if (scheduleViewSettings.weekHeaderSettings.textAlign == + TextAlign.center) { + xPosition = size.width / 2 - _textPainter.width / 2; + } + + if (isRTL) { + xPosition = size.width - _textPainter.width - xPosition; + if (scheduleViewSettings.weekHeaderSettings.textAlign == TextAlign.left || + scheduleViewSettings.weekHeaderSettings.textAlign == TextAlign.end) { + xPosition = 0; + } else if (scheduleViewSettings.weekHeaderSettings.textAlign == + TextAlign.center) { + xPosition = size.width / 2 - _textPainter.width / 2; + } } - _raiseCalendarTapCallback(widget, - date: _getTappedHeaderDate(), element: CalendarElement.header); + /// Draw week label text + _textPainter.paint( + canvas, + Offset( + xPosition, + yPosition + + (scheduleViewSettings.weekHeaderSettings.height / 2 - + _textPainter.height / 2))); } - //// Handles the on long press callback for header - void _handleOnLongPressForHeader(double width) { - _calendarViewWidth = width; - _updateDatePicker(); - if (!_shouldRaiseCalendarLongPressCallback(widget.onLongPress)) { - return; + void _addMonthLabel(Canvas canvas, Size size) { + double xPosition = 0; + const double yPosition = 0; + final String monthFormat = + scheduleViewSettings.monthHeaderSettings.monthFormat; + final TextSpan span = TextSpan( + text: DateFormat(monthFormat, locale).format(startDate).toString(), + style: scheduleViewSettings.monthHeaderSettings.monthTextStyle ?? + TextStyle(color: Colors.white, fontSize: 20, fontFamily: 'Roboto'), + ); + _backgroundPainter.shader = null; + _backgroundPainter.color = + scheduleViewSettings.monthHeaderSettings.backgroundColor; + final Rect rect = Rect.fromLTWH(0, yPosition, size.width, + scheduleViewSettings.monthHeaderSettings.height); + + /// Draw month label background. + canvas.drawRect(rect, _backgroundPainter); + _updateTextPainter(span); + + _textPainter.layout( + minWidth: 0, maxWidth: size.width - 10 > 0 ? size.width - 10 : 0); + + final double viewPadding = size.width * 0.15; + xPosition = viewPadding; + if (scheduleViewSettings.monthHeaderSettings.textAlign == TextAlign.right || + scheduleViewSettings.monthHeaderSettings.textAlign == TextAlign.end) { + xPosition = size.width - _textPainter.width; + } else if (scheduleViewSettings.monthHeaderSettings.textAlign == + TextAlign.center) { + xPosition = size.width / 2 - _textPainter.width / 2; } - _raiseCalendarLongPressCallback(widget, - date: _getTappedHeaderDate(), element: CalendarElement.header); + if (isRTL) { + xPosition = size.width - _textPainter.width - xPosition; + if (scheduleViewSettings.monthHeaderSettings.textAlign == + TextAlign.left || + scheduleViewSettings.monthHeaderSettings.textAlign == TextAlign.end) { + xPosition = 0; + } else if (scheduleViewSettings.monthHeaderSettings.textAlign == + TextAlign.center) { + xPosition = size.width / 2 - _textPainter.width / 2; + } + } + + /// Draw month label text. + _textPainter.paint(canvas, Offset(xPosition, _textPainter.height)); } - DateTime _getTappedHeaderDate() { - if (_view == CalendarView.month) { - return DateTime(_currentDate.year, _currentDate.month, 01, 0, 0, 0); + void _updateTextPainter(TextSpan span) { + _textPainter.text = span; + _textPainter.maxLines = 1; + _textPainter.textDirection = TextDirection.ltr; + _textPainter.textWidthBasis = TextWidthBasis.longestLine; + _textPainter.textScaleFactor = textScaleFactor; + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } + + List _getSemanticsBuilder(Size size) { + final List semanticsBuilder = + []; + double cellHeight; + const double top = 0; + const double left = 0; + cellHeight = 0; + String accessibilityText; + if (!isMonthLabel) { + if (!isDisplayDate) { + cellHeight = scheduleViewSettings.weekHeaderSettings.height; + accessibilityText = + DateFormat('dd', locale).format(startDate).toString() + + 'to' + + DateFormat('dd MMM', locale) + .format(endDate!.add(const Duration(days: 6))) + .toString(); + } else { + cellHeight = size.height; + accessibilityText = _localizations.noEventsCalendarLabel; + } } else { - return DateTime( - _currentViewVisibleDates[0].year, - _currentViewVisibleDates[0].month, - _currentViewVisibleDates[0].day, - 0, - 0, - 0); + cellHeight = scheduleViewSettings.monthHeaderSettings.height; + accessibilityText = + DateFormat('MMMM yyyy', locale).format(startDate).toString(); } + semanticsBuilder.add(CustomPainterSemantics( + rect: Rect.fromLTWH(left, top, size.width, cellHeight), + properties: SemanticsProperties( + label: accessibilityText, + textDirection: TextDirection.ltr, + ), + )); + + return semanticsBuilder; } - //// Handles the onTap callback for agenda view. - void _handleTapForAgenda(TapUpDetails details, DateTime selectedDate) { - _removeDatePicker(); - if (widget.allowViewNavigation && - ((!_isRTL && details.localPosition.dx < _agendaDateViewWidth) || - (_isRTL && - details.localPosition.dx > _minWidth - _agendaDateViewWidth))) { - _controller.view = CalendarView.day; - _controller.displayDate = selectedDate; + /// overrides this property to build the semantics information which uses to + /// return the required information for accessibility, need to return the list + /// of custom painter semantics which contains the rect area and the semantics + /// properties for accessibility + @override + SemanticsBuilderCallback get semanticsBuilder { + return (Size size) { + return _getSemanticsBuilder(size); + }; + } + + @override + bool shouldRebuildSemantics(CustomPainter oldDelegate) { + return true; + } +} + +/// Used to implement the sticky header in schedule calendar view +/// based on its header and content widget. +class _ScheduleAppointmentView extends Stack { + _ScheduleAppointmentView({ + required Widget content, + required Widget header, + AlignmentDirectional? alignment, + Key? key, + }) : super( + key: key, + children: [ + RepaintBoundary(child: content), + RepaintBoundary(child: header) + ], + alignment: alignment ?? AlignmentDirectional.topStart, + ); + + @override + RenderStack createRenderObject(BuildContext context) => + _AppointmentViewHeaderRenderObject( + scrollableState: Scrollable.of(context), + alignment: alignment, + textDirection: textDirection ?? Directionality.of(context), + fit: fit, + ); + + @override + @mustCallSuper + void updateRenderObject(BuildContext context, RenderStack renderObject) { + super.updateRenderObject(context, renderObject); + + if (renderObject is _AppointmentViewHeaderRenderObject) { + renderObject..scrollableState = Scrollable.of(context); } + } +} - if (!_shouldRaiseCalendarTapCallback(widget.onTap)) { - return; +/// Render object of the schedule calendar view item. +class _AppointmentViewHeaderRenderObject extends RenderStack { + _AppointmentViewHeaderRenderObject({ + ScrollableState? scrollableState, + AlignmentGeometry alignment = AlignmentDirectional.topStart, + TextDirection? textDirection, + StackFit fit = StackFit.loose, + }) : _scrollableState = scrollableState, + super( + alignment: alignment, + textDirection: textDirection, + fit: fit, + ); + + /// Used to update the child position when it scroll changed. + ScrollableState? _scrollableState; + + /// Current view port. + RenderAbstractViewport get _stackViewPort => RenderAbstractViewport.of(this)!; + + ScrollableState? get scrollableState => _scrollableState; + + set scrollableState(ScrollableState? newScrollable) { + final ScrollableState? oldScrollable = _scrollableState; + _scrollableState = newScrollable; + + markNeedsPaint(); + if (attached) { + oldScrollable!.position.removeListener(markNeedsPaint); + newScrollable!.position.addListener(markNeedsPaint); } + } - final List selectedAppointments = - _getSelectedAppointments(details.localPosition, selectedDate); + /// attach will called when the render object rendered in view. + @override + void attach(PipelineOwner owner) { + super.attach(owner); + scrollableState!.position.addListener(markNeedsPaint); + } - _raiseCalendarTapCallback(widget, - date: selectedDate, - appointments: selectedAppointments, - element: selectedAppointments != null && selectedAppointments.isNotEmpty - ? CalendarElement.appointment - : CalendarElement.agenda); + /// attach will called when the render object removed from view. + @override + void detach() { + scrollableState!.position.removeListener(markNeedsPaint); + super.detach(); } - //// Handles the onLongPress callback for agenda view. - void _handleLongPressForAgenda( - LongPressStartDetails details, DateTime selectedDate) { - _removeDatePicker(); - if (widget.allowViewNavigation && - ((!_isRTL && details.localPosition.dx < _agendaDateViewWidth) || - (_isRTL && - details.localPosition.dx > _minWidth - _agendaDateViewWidth))) { - _controller.view = CalendarView.day; - _controller.displayDate = selectedDate; + @override + void paint(PaintingContext context, Offset paintOffset) { + /// Update the child position. + updateHeaderOffset(); + paintStack(context, paintOffset); + } + + void updateHeaderOffset() { + /// Content widget height + final double contentSize = firstChild!.size.height; + final RenderBox headerView = lastChild!; + + /// Header view height + final double headerSize = headerView.size.height; + + /// Current view position on scroll view. + final double viewPosition = + _stackViewPort.getOffsetToReveal(this, 0).offset; + + /// Calculate the current view offset by view position on scroll view, + /// scrolled position and scroll view view port. + final double currentViewOffset = + viewPosition - _scrollableState!.position.pixels - _scrollableHeight; + + /// Check current header offset exits content size, if exist then place the + /// header at content size. + final double offset = _getCurrentOffset(currentViewOffset, contentSize); + final StackParentData headerParentData = + // ignore: avoid_as + headerView.parentData! as StackParentData; + final double headerYOffset = + _getHeaderOffset(contentSize, offset, headerSize); + + /// Update the header start y position. + if (headerYOffset != headerParentData.offset.dy) { + headerParentData.offset = + Offset(headerParentData.offset.dx, headerYOffset); + } + } + + /// Return the view port height. + double get _scrollableHeight { + final Object viewPort = _stackViewPort; + double viewPortHeight = 0; + + if (viewPort is RenderBox) { + viewPortHeight = viewPort.size.height; + } + + double anchor = 0; + if (viewPort is RenderViewport) { + anchor = viewPort.anchor; + } + + return -viewPortHeight * anchor; + } + + /// Check current header offset exits content size, if exist then place the + /// header at content size. + double _getCurrentOffset(double currentOffset, double contentSize) { + final double currentHeaderPosition = + -currentOffset > contentSize ? contentSize : -currentOffset; + return currentHeaderPosition > 0 ? currentHeaderPosition : 0; + } + + /// Return current offset value from header size and content size. + double _getHeaderOffset( + double contentSize, + double offset, + double headerSize, + ) { + return headerSize + offset < contentSize + ? offset + : contentSize - headerSize; + } +} + +/// Used to create the custom splash factory that shows the splash for inkwell +/// interaction. +class _CustomSplashFactory extends InteractiveInkFeatureFactory { + /// Called when the inkwell pressed and it return custom splash. + @override + InteractiveInkFeature create({ + required MaterialInkController controller, + required RenderBox referenceBox, + required Offset position, + required Color color, + required TextDirection textDirection, + bool containedInkWell = false, + RectCallback? rectCallback, + BorderRadius? borderRadius, + ShapeBorder? customBorder, + double? radius, + VoidCallback? onRemoved, + }) { + return _CustomSplash( + controller: controller, + referenceBox: referenceBox, + position: position, + color: color, + containedInkWell: containedInkWell, + borderRadius: borderRadius, + rectCallback: rectCallback, + onRemoved: onRemoved, + ); + } +} + +/// Custom ink splash used to animate the inkwell on intercation. +class _CustomSplash extends InteractiveInkFeature { + /// Begin a splash, centered at position relative to [referenceBox]. + /// + /// The [controller] argument is typically obtained via + /// `Material.of(context)`. + /// + /// If `containedInkWell` is true, then the splash will be sized to fit + /// the well rectangle, then clipped to it when drawn. The well + /// rectangle is the box returned by `rectCallback`, if provided, or + /// otherwise is the bounds of the [referenceBox]. + /// + /// If `containedInkWell` is false, then `rectCallback` should be null. + /// The ink splash is clipped only to the edges of the [Material]. + /// This is the default. + /// + /// When the splash is removed, `onRemoved` will be called. + _CustomSplash({ + required MaterialInkController controller, + required RenderBox referenceBox, + required Offset position, + required Color color, + bool containedInkWell = false, + RectCallback? rectCallback, + BorderRadius? borderRadius, + VoidCallback? onRemoved, + }) : _position = position, + _borderRadius = borderRadius ?? BorderRadius.zero, + _targetRadius = _getTargetRadius( + referenceBox, containedInkWell, rectCallback, position), + _clipCallback = + _getClipCallback(referenceBox, containedInkWell, rectCallback), + _repositionToReferenceBox = !containedInkWell, + super( + controller: controller, + referenceBox: referenceBox, + color: color, + onRemoved: onRemoved) { + _radiusController = AnimationController( + duration: _kUnconfirmedRippleSplashDuration, vsync: controller.vsync) + ..addListener(controller.markNeedsPaint) + ..forward(); + _radius = _radiusController.drive(Tween( + begin: 0.0, + end: _targetRadius, + )); + _alphaController = AnimationController( + duration: _kSplashFadeDuration, vsync: controller.vsync) + ..addListener(controller.markNeedsPaint) + ..addStatusListener(_handleAlphaStatusChanged); + _alpha = _alphaController!.drive(IntTween( + begin: color.alpha, + end: 0, + )); + + controller.addInkFeature(this); + } + + /// Position holds the input touch point. + final Offset _position; + + /// Specifies the border radius used on the inkwell + final BorderRadius _borderRadius; + + /// Radius of ink circle to be drawn on canvas based on its position. + final double _targetRadius; + + /// clipCallback is the callback used to obtain the rect used for clipping + /// the ink effect. If it is null, no clipping is performed on the ink circle. + final RectCallback? _clipCallback; + + /// Specifies the reference box repositioned or not. Its value depends on + /// contained inkwell property. + final bool _repositionToReferenceBox; + + /// Animation used to show a ripple. + late Animation _radius; + + /// Controller used to handle the ripple animation. + late AnimationController _radiusController; + + /// Animation used to handle a opacity. + late Animation _alpha; + + /// Controller used to handle the opacity animation. + late AnimationController? _alphaController; + + @override + void confirm() { + /// Calculate the ripple animation duration from its radius value and start + /// the animation. + Duration duration = Duration(milliseconds: (_targetRadius * 10).floor()); + duration = duration > _kUnconfirmedRippleSplashDuration + ? _kUnconfirmedRippleSplashDuration + : duration; + _radiusController + ..duration = duration + ..forward(); + _alphaController!.forward(); + } + + @override + void cancel() { + _alphaController?.forward(); + } + + void _handleAlphaStatusChanged(AnimationStatus status) { + /// Dispose inkwell animation when the animation completed. + if (status == AnimationStatus.completed) dispose(); + } + + @override + void dispose() { + _radiusController.dispose(); + _alphaController!.dispose(); + _alphaController = null; + super.dispose(); + } + + ///Draws an ink splash or ink ripple on the canvas. + @override + void paintFeature(Canvas canvas, Matrix4 transform) { + final Paint paint = Paint()..color = color.withAlpha(_alpha.value); + Offset? center = _position; + + /// If the reference box needs to reposition then its 'rectCallback' value + /// is null, so calculate the position based on reference box. + if (_repositionToReferenceBox) { + center = Offset.lerp(center, referenceBox.size.center(Offset.zero), + _radiusController.value); } - if (!_shouldRaiseCalendarLongPressCallback(widget.onLongPress)) { - return; + /// Get the offset needs to translate, if it not specified then it + /// returns null value. + final Offset? originOffset = MatrixUtils.getAsTranslation(transform); + canvas.save(); + + /// Translate the canvas based on offset value. + if (originOffset == null) { + canvas.transform(transform.storage); + } else { + canvas.translate(originOffset.dx, originOffset.dy); } - final List selectedAppointments = - _getSelectedAppointments(details.localPosition, selectedDate); + if (_clipCallback != null) { + /// Clip and draw the rect with fade animation value on canvas. + final Rect rect = _clipCallback!(); + if (_borderRadius != BorderRadius.zero) { + final RRect roundedRect = RRect.fromRectAndCorners( + rect, + topLeft: _borderRadius.topLeft, + topRight: _borderRadius.topRight, + bottomLeft: _borderRadius.bottomLeft, + bottomRight: _borderRadius.bottomRight, + ); + canvas.clipRRect(roundedRect); + canvas.drawRRect(roundedRect, paint); + } else { + canvas.clipRect(rect); + canvas.drawRect(rect, paint); + } + } - _raiseCalendarLongPressCallback(widget, - date: selectedDate, - appointments: selectedAppointments, - element: selectedAppointments != null && selectedAppointments.isNotEmpty - ? CalendarElement.appointment - : CalendarElement.agenda); + /// Draw the ripple on canvas. + canvas.drawCircle(center!, _radius.value, paint); + canvas.restore(); } +} - List _getSelectedAppointments( - Offset localPosition, DateTime selectedDate) { - /// Return empty collection while tap the agenda view with no selected date. +class _AgendaDateTimePainter extends CustomPainter { + _AgendaDateTimePainter( + this.selectedDate, + this.monthViewSettings, + this.scheduleViewSettings, + this.todayHighlightColor, + this.todayTextStyle, + this.locale, + this.calendarTheme, + this.agendaDateNotifier, + this.viewWidth, + this.isRTL, + this.textScaleFactor, + this.isMobilePlatform) + : super(repaint: agendaDateNotifier); + + final DateTime? selectedDate; + final MonthViewSettings? monthViewSettings; + final ScheduleViewSettings? scheduleViewSettings; + final Color? todayHighlightColor; + final TextStyle? todayTextStyle; + final String locale; + final ValueNotifier agendaDateNotifier; + final SfCalendarThemeData calendarTheme; + final double viewWidth; + final bool isRTL; + final double textScaleFactor; + final bool isMobilePlatform; + Paint _linePainter = Paint(); + TextPainter _textPainter = TextPainter(); + + @override + void paint(Canvas canvas, Size size) { + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + _linePainter.isAntiAlias = true; + const double padding = 5; if (selectedDate == null) { - return []; + return; } - /// Return empty collection while tap the agenda date view. - if ((!_isRTL && localPosition.dx < _agendaDateViewWidth) || - (_isRTL && localPosition.dx > _minWidth - _agendaDateViewWidth)) { - return []; + final bool useMobilePlatformUI = + CalendarViewHelper.isMobileLayoutUI(viewWidth, isMobilePlatform); + final bool isToday = isSameDate(selectedDate, DateTime.now()); + TextStyle? dateTextStyle, dayTextStyle; + if (monthViewSettings != null) { + dayTextStyle = monthViewSettings!.agendaStyle.dayTextStyle ?? + calendarTheme.agendaDayTextStyle; + dateTextStyle = monthViewSettings!.agendaStyle.dateTextStyle ?? + calendarTheme.agendaDateTextStyle; + } else { + dayTextStyle = scheduleViewSettings!.dayHeaderSettings.dayTextStyle ?? + (useMobilePlatformUI + ? calendarTheme.agendaDayTextStyle + : TextStyle( + color: calendarTheme.agendaDayTextStyle.color, + fontSize: 9, + fontFamily: 'Roboto', + fontWeight: FontWeight.w500)); + dateTextStyle = scheduleViewSettings!.dayHeaderSettings.dateTextStyle ?? + (useMobilePlatformUI + ? calendarTheme.agendaDateTextStyle + : TextStyle( + color: calendarTheme.agendaDateTextStyle.color, + fontSize: 18, + fontFamily: 'Roboto', + fontWeight: FontWeight.normal)); } - List agendaAppointments = _getSelectedDateAppointments( - _appointments, widget.timeZone, selectedDate); + final Color? selectedDayTextColor = isToday + ? todayHighlightColor + : dayTextStyle.color != null + ? dayTextStyle.color + : calendarTheme.agendaDayTextStyle.color; + final Color? selectedDateTextColor = isToday + ? calendarTheme.todayTextStyle.color + : dateTextStyle.color != null + ? dateTextStyle.color + : calendarTheme.agendaDateTextStyle.color; + dayTextStyle = dayTextStyle.copyWith(color: selectedDayTextColor); + dateTextStyle = dateTextStyle.copyWith(color: selectedDateTextColor); + if (isToday) { + dayTextStyle = todayTextStyle != null + ? todayTextStyle!.copyWith( + fontSize: dayTextStyle.fontSize, color: selectedDayTextColor) + : dayTextStyle; + dateTextStyle = todayTextStyle != null + ? todayTextStyle!.copyWith( + fontSize: dateTextStyle.fontSize, color: selectedDateTextColor) + : dateTextStyle; + } - /// Return empty collection while tap the agenda view does - /// not have appointments. - if (agendaAppointments == null || agendaAppointments.isEmpty) { - return []; + /// Draw day label other than web schedule view. + if (scheduleViewSettings == null || useMobilePlatformUI) { + _addDayLabelForMobile(canvas, size, padding, dayTextStyle, dateTextStyle, + isToday, isMobilePlatform); + } else { + _addDayLabelForWeb( + canvas, size, padding, dayTextStyle, dateTextStyle, isToday); } + } - agendaAppointments.sort((Appointment app1, Appointment app2) => - app1._actualStartTime.compareTo(app2._actualStartTime)); - agendaAppointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1.isAllDay, app2.isAllDay)); + void _updateTextPainter(TextSpan span) { + _textPainter.text = span; + _textPainter.maxLines = 1; + _textPainter.textDirection = TextDirection.ltr; + _textPainter.textAlign = TextAlign.left; + _textPainter.textWidthBasis = TextWidthBasis.parent; + _textPainter.textScaleFactor = textScaleFactor; + } - int index = -1; - //// Agenda appointment view top padding as 5. - const double padding = 5; - double xPosition = 0; - final double tappedYPosition = - _agendaScrollController.offset + localPosition.dy; - final double actualAppointmentHeight = - _getScheduleAppointmentHeight(widget.monthViewSettings, null); - final double allDayAppointmentHeight = - _getScheduleAllDayAppointmentHeight(widget.monthViewSettings, null); - for (int i = 0; i < agendaAppointments.length; i++) { - final Appointment _appointment = agendaAppointments[i]; - final double appointmentHeight = _isAllDayAppointmentView(_appointment) - ? allDayAppointmentHeight - : actualAppointmentHeight; - if (tappedYPosition >= xPosition && - tappedYPosition < xPosition + appointmentHeight + padding) { - index = i; - break; - } + void _addDayLabelForMobile( + Canvas canvas, + Size size, + double padding, + TextStyle dayTextStyle, + TextStyle dateTextStyle, + bool isToday, + bool isMobile) { + //// Draw Weekday + final String dayTextFormat = scheduleViewSettings != null + ? scheduleViewSettings!.dayHeaderSettings.dayFormat + : 'EEE'; + TextSpan span = TextSpan( + text: DateFormat(dayTextFormat, locale) + .format(selectedDate!) + .toUpperCase() + .toString(), + style: dayTextStyle); + _updateTextPainter(span); + + _textPainter.layout(minWidth: 0, maxWidth: size.width); + _textPainter.paint( + canvas, + Offset( + padding + ((size.width - (2 * padding) - _textPainter.width) / 2), + padding)); - xPosition += appointmentHeight + padding; + final double weekDayHeight = padding + _textPainter.height; + //// Draw Date + span = TextSpan(text: selectedDate!.day.toString(), style: dateTextStyle); + _updateTextPainter(span); + + _textPainter.layout(minWidth: 0, maxWidth: size.width); + + /// The padding value provides the space between the date and day text. + const int inBetweenPadding = 2; + final double xPosition = + padding + ((size.width - (2 * padding) - _textPainter.width) / 2); + double yPosition = weekDayHeight; + if (isToday) { + yPosition = weekDayHeight + padding + inBetweenPadding; + _linePainter.color = todayHighlightColor!; + _drawTodayCircle(canvas, xPosition, yPosition, padding); } - /// Return empty collection while tap the agenda view and the tapped - /// position does not have appointment. - if (index > agendaAppointments.length || index == -1) { - return []; + /// padding added between date and day labels in web, to avoid the + /// hovering effect overlapping issue. + if (!isMobile && !isToday) { + yPosition = weekDayHeight + padding + inBetweenPadding; } - - agendaAppointments = [agendaAppointments[index]]; - if (widget.dataSource != null && - !_isCalendarAppointment(widget.dataSource)) { - return _getCustomAppointments(agendaAppointments); + if (agendaDateNotifier.value != null && + isSameDate(agendaDateNotifier.value!.hoveringDate, selectedDate)) { + if (xPosition < agendaDateNotifier.value!.hoveringOffset.dx && + xPosition + _textPainter.width > + agendaDateNotifier.value!.hoveringOffset.dx && + yPosition < agendaDateNotifier.value!.hoveringOffset.dy && + yPosition + _textPainter.height > + agendaDateNotifier.value!.hoveringOffset.dy) { + _linePainter.color = isToday + ? Colors.black.withOpacity(0.1) + : (calendarTheme.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withOpacity(0.04); + _drawTodayCircle(canvas, xPosition, yPosition, padding); + } } - return agendaAppointments; + _textPainter.paint(canvas, Offset(xPosition, yPosition)); } - // Returns the agenda view as a child for the calendar. - Widget _addAgendaView( - double height, double startPosition, double width, bool isRTL) { - if (_view != CalendarView.month || !widget.monthViewSettings.showAgenda) { - return Positioned( - left: 0, - right: 0, - top: 0, - bottom: 0, - child: Container(), - ); + void _addDayLabelForWeb(Canvas canvas, Size size, double padding, + TextStyle dayTextStyle, TextStyle dateTextStyle, bool isToday) { + /// Draw day label on web schedule view. + final String dateText = selectedDate!.day.toString(); + + /// Calculate the date text maximum width value. + final String maxWidthDateText = '30'; + final String dayText = DateFormat( + isRTL + ? scheduleViewSettings!.dayHeaderSettings.dayFormat + ', MMM' + : 'MMM, ' + scheduleViewSettings!.dayHeaderSettings.dayFormat, + locale) + .format(selectedDate!) + .toUpperCase() + .toString(); + + //// Draw Weekday + TextSpan span = TextSpan(text: maxWidthDateText, style: dateTextStyle); + _updateTextPainter(span); + _textPainter.layout(minWidth: 0, maxWidth: size.width); + + /// Calculate the start padding value for web schedule view date time label. + double startXPosition = size.width / 5; + startXPosition = isRTL ? size.width - startXPosition : startXPosition; + final double dateHeight = size.height; + final double yPosition = (dateHeight - _textPainter.height) / 2; + final double painterWidth = _textPainter.width; + span = TextSpan(text: dateText, style: dateTextStyle); + _textPainter.text = span; + _textPainter.layout(minWidth: 0, maxWidth: size.width); + double dateTextPadding = (painterWidth - _textPainter.width) / 2; + if (dateTextPadding < 0) { + dateTextPadding = 0; } - /// Show no selected date in agenda view when selected date is - /// disabled or black out date. - DateTime currentSelectedDate; - if (_selectedDate != null) { - currentSelectedDate = isDateWithInDateRange( - widget.minDate, widget.maxDate, _selectedDate) && - !_isDateInDateCollection(_blackoutDates, _selectedDate) - ? _selectedDate - : null; + final double dateTextStartPosition = startXPosition + dateTextPadding; + if (isToday) { + _linePainter.color = todayHighlightColor!; + _drawTodayCircle(canvas, dateTextStartPosition, yPosition, padding); } - if (currentSelectedDate == null) { - return Positioned( - top: startPosition, - right: 0, - left: 0, - height: height, - child: Opacity( - opacity: _opacity, - child: Container( - color: widget.monthViewSettings.agendaStyle.backgroundColor ?? - _calendarTheme.agendaBackgroundColor, - child: GestureDetector( - child: _AgendaViewLayout( - widget.monthViewSettings, - null, - currentSelectedDate, - null, - isRTL, - _locale, - _localizations, - _calendarTheme, - _agendaViewNotifier, - widget.appointmentTimeTextFormat, - 0, - _textScaleFactor, - _isMobilePlatform, - widget.appointmentBuilder, - width, - height), - onTapUp: (TapUpDetails details) { - _handleTapForAgenda(details, null); - }, - onLongPressStart: (LongPressStartDetails details) { - _handleLongPressForAgenda(details, null); - }, - )))); + if (agendaDateNotifier.value != null && + isSameDate(agendaDateNotifier.value!.hoveringDate, selectedDate)) { + if (dateTextStartPosition < + (isRTL + ? size.width - agendaDateNotifier.value!.hoveringOffset.dx + : agendaDateNotifier.value!.hoveringOffset.dx) && + (dateTextStartPosition + _textPainter.width) > + (isRTL + ? size.width - agendaDateNotifier.value!.hoveringOffset.dx + : agendaDateNotifier.value!.hoveringOffset.dx) && + yPosition < agendaDateNotifier.value!.hoveringOffset.dy && + (yPosition + _textPainter.height) > + agendaDateNotifier.value!.hoveringOffset.dy) { + _linePainter.color = isToday + ? Colors.black.withOpacity(0.1) + : (calendarTheme.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withOpacity(0.04); + _drawTodayCircle(canvas, dateTextStartPosition, yPosition, padding); + } } - final List agendaAppointments = _getSelectedDateAppointments( - _appointments, widget.timeZone, currentSelectedDate); - agendaAppointments.sort((Appointment app1, Appointment app2) => - app1._actualStartTime.compareTo(app2._actualStartTime)); - agendaAppointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1.isAllDay, app2.isAllDay)); - agendaAppointments.sort((Appointment app1, Appointment app2) => - _orderAppointmentsAscending(app1._isSpanned, app2._isSpanned)); + _textPainter.paint(canvas, Offset(dateTextStartPosition, yPosition)); + + //// Draw Date + span = TextSpan(text: dayText, style: dayTextStyle); + _textPainter.text = span; + if (isRTL) { + _textPainter.layout(minWidth: 0, maxWidth: startXPosition); + startXPosition -= _textPainter.width + (3 * padding); + if (startXPosition > 0) { + _textPainter.paint(canvas, + Offset(startXPosition, (dateHeight - _textPainter.height) / 2)); + } + } else { + startXPosition += painterWidth + (3 * padding); + if (startXPosition > size.width) { + return; + } + _textPainter.layout(minWidth: 0, maxWidth: size.width - startXPosition); + _textPainter.paint(canvas, + Offset(startXPosition, (dateHeight - _textPainter.height) / 2)); + } + } - /// Each appointment have top padding and it used to show the space - /// between two appointment views - const double topPadding = 5; + void _drawTodayCircle( + Canvas canvas, double xPosition, double yPosition, double padding) { + canvas.drawCircle( + Offset(xPosition + (_textPainter.width / 2), + yPosition + (_textPainter.height / 2)), + _textPainter.width > _textPainter.height + ? (_textPainter.width / 2) + padding + : (_textPainter.height / 2) + padding, + _linePainter); + } - /// Last appointment view have bottom padding and it show the space - /// between the last appointment and agenda view. - const double bottomPadding = 5; - final double appointmentHeight = - _getScheduleAppointmentHeight(widget.monthViewSettings, null); - final double allDayAppointmentHeight = - _getScheduleAllDayAppointmentHeight(widget.monthViewSettings, null); - double painterHeight = height; - if (agendaAppointments != null && agendaAppointments.isNotEmpty) { - final int count = _getAllDayCount(agendaAppointments); - painterHeight = (((count * (allDayAppointmentHeight + topPadding)) + - ((agendaAppointments.length - count) * - (appointmentHeight + topPadding))) - .toDouble()) + - bottomPadding; - } + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } - return Positioned( - top: startPosition, - right: 0, - left: 0, - height: height, - child: Opacity( - opacity: _opacity, - child: Container( - color: widget.monthViewSettings.agendaStyle.backgroundColor ?? - _calendarTheme.agendaBackgroundColor, - child: MouseRegion( - onEnter: (PointerEnterEvent event) { - _pointerEnterEvent(event, false, isRTL); - }, - onExit: _pointerExitEvent, - onHover: (PointerHoverEvent event) { - _pointerHoverEvent(event, false, isRTL); - }, - child: GestureDetector( - child: Stack(children: [ - CustomPaint( - painter: _AgendaDateTimePainter( - currentSelectedDate, - widget.monthViewSettings, - null, - widget.todayHighlightColor ?? - _calendarTheme.todayHighlightColor, - widget.todayTextStyle, - _locale, - _calendarTheme, - _agendaDateNotifier, - _minWidth, - isRTL, - _textScaleFactor, - _isMobilePlatform), - size: Size(_agendaDateViewWidth, height), - ), - Positioned( - top: 0, - left: isRTL ? 0 : _agendaDateViewWidth, - right: isRTL ? _agendaDateViewWidth : 0, - bottom: 0, - child: ListView( - padding: const EdgeInsets.all(0.0), - controller: _agendaScrollController, - children: [ - _AgendaViewLayout( - widget.monthViewSettings, - null, - currentSelectedDate, - agendaAppointments, - isRTL, - _locale, - _localizations, - _calendarTheme, - _agendaViewNotifier, - widget.appointmentTimeTextFormat, - _agendaDateViewWidth, - _textScaleFactor, - _isMobilePlatform, - widget.appointmentBuilder, - width - _agendaDateViewWidth, - painterHeight), - ], - ), - ), - ]), - onTapUp: (TapUpDetails details) { - _handleTapForAgenda(details, _selectedDate); - }, - onLongPressStart: (LongPressStartDetails details) { - _handleLongPressForAgenda(details, _selectedDate); - }, - ))))); + /// overrides this property to build the semantics information which uses to + /// return the required information for accessibility, need to return the list + /// of custom painter semantics which contains the rect area and the semantics + /// properties for accessibility + @override + SemanticsBuilderCallback get semanticsBuilder { + return (Size size) { + return _getSemanticsBuilder(size); + }; } -} -/// Widget used to show the pop up animation to the child. -class _PopupWidget extends StatefulWidget { - _PopupWidget({this.child, this.alignment = Alignment.topCenter}); + @override + bool shouldRebuildSemantics(CustomPainter oldDelegate) { + return true; + } - /// Widget that animated like popup. - final Widget child; + List _getSemanticsBuilder(Size size) { + final List semanticsBuilder = + []; + if (selectedDate == null) { + return semanticsBuilder; + } else if (selectedDate != null) { + semanticsBuilder.add(CustomPainterSemantics( + rect: Offset.zero & size, + properties: SemanticsProperties( + label: DateFormat('EEEEE').format(selectedDate!).toString() + + DateFormat('dd/MMMM/yyyy').format(selectedDate!).toString(), + textDirection: TextDirection.ltr, + ), + )); + } - /// Alignment defines the popup animation start position. - final Alignment alignment; + return semanticsBuilder; + } +} - @override - State createState() => _PopupWidgetState(); +/// Used to store the height and intersection point of scroll view item. +/// intersection point used to identify the view does not have same month dates. +class _ScheduleViewDetails { + late double _height; + late double _intersectPoint; } -class _PopupWidgetState extends State<_PopupWidget> - with SingleTickerProviderStateMixin { - /// Controller used to handle the animation. - AnimationController _animationController; +/// Returns the maximum radius value calculated based on input touch position. +double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, + RectCallback? rectCallback, Offset position) { + /// If `containedInkWell` is false, then `rectCallback` should be null. + if (!containedInkWell) { + return Material.defaultSplashRadius; + } - /// Popup animation used to show the child like popup. - Animation _animation; + final Size size = + rectCallback != null ? rectCallback().size : referenceBox.size; + final double d1 = (position - size.topLeft(Offset.zero)).distance; + final double d2 = (position - size.topRight(Offset.zero)).distance; + final double d3 = (position - size.bottomLeft(Offset.zero)).distance; + final double d4 = (position - size.bottomRight(Offset.zero)).distance; + return math.max(math.max(d1, d2), math.max(d3, d4)).ceilToDouble(); +} - @override - void initState() { - _animationController = - AnimationController(vsync: this, duration: Duration(milliseconds: 200)); - _animation = - CurvedAnimation(parent: _animationController, curve: Curves.easeInOut); - super.initState(); +/// Return the rect callback value based on its argument value. +RectCallback? _getClipCallback( + RenderBox referenceBox, bool containedInkWell, RectCallback? rectCallback) { + if (rectCallback != null) { + /// If `containedInkWell` is false, then `rectCallback` should be null. + assert(containedInkWell); + return rectCallback; } + if (containedInkWell) return () => Offset.zero & referenceBox.size; + return null; +} - @override - void dispose() { - _animationController.dispose(); - super.dispose(); +Size _getTextWidgetWidth( + String text, double height, double width, BuildContext context, + {TextStyle? style}) { + /// Create new text with it style. + final Widget richTextWidget = Text( + text, + style: style, + maxLines: 1, + softWrap: false, + textDirection: TextDirection.ltr, + textAlign: TextAlign.left, + ).build(context); + + RenderParagraph? renderObject; + if (richTextWidget is! RichText) { + assert(richTextWidget is RichText); + } else { + /// Create and layout the render object based on allocated width and height. + renderObject = richTextWidget.createRenderObject(context); + } + renderObject!.layout(BoxConstraints( + minWidth: width, + maxWidth: width, + minHeight: height, + maxHeight: height, + )); + + /// Get the size of text by using render object. + final List textBox = renderObject.getBoxesForSelection( + TextSelection(baseOffset: 0, extentOffset: text.length)); + double textWidth = 0; + double textHeight = 0; + for (final TextBox box in textBox) { + textWidth += box.right - box.left; + final double currentBoxHeight = box.bottom - box.top; + textHeight = textHeight > currentBoxHeight ? textHeight : currentBoxHeight; } - @override - Widget build(BuildContext context) { - /// Reset the existing animation. - _animationController.reset(); + /// 10 padding added for text box(left and right side both as 5). + return Size(textWidth + 10, textHeight + 10); +} - /// Start the animation. - _animationController.forward(); - return ScaleTransition( - alignment: widget.alignment, - scale: _animation, - child: FadeTransition(opacity: _animation, child: widget.child)); +Map _getCalendarViewsText(SfLocalizations localizations) { + final Map calendarViews = {}; + calendarViews[CalendarView.day] = localizations.allowedViewDayLabel; + calendarViews[CalendarView.week] = localizations.allowedViewWeekLabel; + calendarViews[CalendarView.workWeek] = localizations.allowedViewWorkWeekLabel; + calendarViews[CalendarView.timelineDay] = + localizations.allowedViewTimelineDayLabel; + calendarViews[CalendarView.timelineWeek] = + localizations.allowedViewTimelineWeekLabel; + calendarViews[CalendarView.timelineMonth] = + localizations.allowedViewTimelineMonthLabel; + calendarViews[CalendarView.timelineWorkWeek] = + localizations.allowedViewTimelineWorkWeekLabel; + calendarViews[CalendarView.month] = localizations.allowedViewMonthLabel; + calendarViews[CalendarView.schedule] = localizations.allowedViewScheduleLabel; + return calendarViews; +} + +/// Return day label width based on schedule view setting. +double _getAgendaViewDayLabelWidth( + ScheduleViewSettings scheduleViewSettings, bool useMobilePlatformUI) { + if (scheduleViewSettings.dayHeaderSettings.width == -1) { + return useMobilePlatformUI ? 50 : 150; } + + return scheduleViewSettings.dayHeaderSettings.width; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/calendar_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/calendar_view.dart index bff11d49d..bb486d6d5 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/calendar_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/calendar_view.dart @@ -1,261 +1,413 @@ -part of calendar; - +import 'dart:async'; +import 'dart:math' as math; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/core_internal.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../appointment_engine/appointment_helper.dart'; +import '../appointment_engine/recurrence_helper.dart'; +import '../appointment_layout/allday_appointment_layout.dart'; +import '../appointment_layout/appointment_layout.dart'; +import '../common/calendar_controller.dart'; +import '../common/calendar_view_helper.dart'; +import '../common/date_time_engine.dart'; +import '../common/enums.dart'; +import '../resource_view/calendar_resource.dart'; +import '../settings/month_view_settings.dart'; +import '../settings/time_region.dart'; +import '../settings/time_slot_view_settings.dart'; +import '../settings/view_header_style.dart'; +import '../sfcalendar.dart'; +import '../views/day_view.dart'; +import '../views/month_view.dart'; +import '../views/timeline_view.dart'; + +/// All day appointment views default height +const double _kAllDayLayoutHeight = 60; + +/// Holds the looping widget for calendar view(time slot, month, timeline and +/// appointment views) widgets of calendar widget. @immutable -class _CalendarView extends StatefulWidget { - const _CalendarView( +class CustomCalendarScrollView extends StatefulWidget { + /// Constructor to create the calendar scroll view for holding calendar + /// view(time slot, month, timeline and appointment views) widgets of + /// calendar widget. + const CustomCalendarScrollView( this.calendar, this.view, - this.visibleDates, this.width, this.height, this.agendaSelectedDate, + this.isRTL, this.locale, this.calendarTheme, - this.regions, + this.specialRegions, this.blackoutDates, - this.focusNode, - this.removePicker, - this.allowViewNavigation, this.controller, + this.removePicker, this.resourcePanelScrollController, this.resourceCollection, this.textScaleFactor, this.isMobilePlatform, - {Key key, + this.fadeInController, + this.minDate, + this.maxDate, + this.localizations, this.updateCalendarState, - this.getCalendarState}) - : super(key: key); + this.getCalendarState); - final List visibleDates; - final List regions; - final List blackoutDates; + /// Holds the calendar instance used to get the calendar properties. final SfCalendar calendar; + + /// Holds the current calendar view of the calendar widget. final CalendarView view; + + /// Defines the width of the calendar scroll view widget. final double width; - final SfCalendarThemeData calendarTheme; + + /// Defines the height of the calendar scroll view widget. final double height; + + /// Defines the direction of calendar widget is RTL or not. + final bool isRTL; + + /// Defines the locale of the calendar. final String locale; - final ValueNotifier agendaSelectedDate; + + /// Holds the theme data value for calendar. + final SfCalendarThemeData calendarTheme; + + /// Holds the calendar controller for the calendar widget. final CalendarController controller; + + /// Used to update the calendar state details. + final UpdateCalendarState updateCalendarState; + + /// Used to get the calendar state details. + final UpdateCalendarState getCalendarState; + + /// Used to remove the calendar header picker. final VoidCallback removePicker; - final _UpdateCalendarState updateCalendarState; - final _UpdateCalendarState getCalendarState; - final bool allowViewNavigation; - final FocusNode focusNode; - final ScrollController resourcePanelScrollController; - final List resourceCollection; + + /// Holds the agenda selected date value and the value updated on month cell + /// selection and it set to null on month appointment selection. + final ValueNotifier agendaSelectedDate; + + /// Holds the special time region of calendar widget. + final List? specialRegions; + + /// Used to get the resource panel scroll position. + final ScrollController? resourcePanelScrollController; + + /// Collection used to store the resource collection and check the collection + /// manipulations(add, remove, reset). + final List? resourceCollection; + + /// Defines the scale factor for the calendar widget. final double textScaleFactor; + + /// Defines the current platform is mobile platform or not. final bool isMobilePlatform; + /// Holds the blackout dates collection of calendar. + final List? blackoutDates; + + /// Used to animate the calendar views while navigation and view switching. + final AnimationController? fadeInController; + + /// Defines the min date of the calendar. + final DateTime minDate; + + /// Defines the max date of the calendar. + final DateTime maxDate; + + /// Holds the localization data of the calendar widget. + final SfLocalizations localizations; + @override - _CalendarViewState createState() => _CalendarViewState(); + _CustomCalendarScrollViewState createState() => + _CustomCalendarScrollViewState(); } -class _CalendarViewState extends State<_CalendarView> +class _CustomCalendarScrollViewState extends State with TickerProviderStateMixin { - // line count is the total time slot lines to be drawn in the view - // line count per view is for time line view which contains the time slot - // count for per view - double _horizontalLinesCount; + // three views to arrange the view in vertical/horizontal direction and handle the swiping + late _CalendarView _currentView, _nextView, _previousView; - //// all day scroll controller is used to identify the scrollposition for draw - // all day selection. - ScrollController _scrollController, - _timelineViewHeaderScrollController, - _timelineViewVerticalScrollController, - _timelineRulerController; + // the three children which to be added into the layout + List<_CalendarView> _children = <_CalendarView>[]; - GlobalKey<_AppointmentLayoutState> _appointmentLayoutKey; - AnimationController _timelineViewAnimationController; - Animation _timelineViewAnimation; - Tween _timelineViewTween; + // holds the index of the current displaying view + int _currentChildIndex = 1; - //// timeline header is used to implement the sticky view header in horizontal calendar view mode. - _TimelineViewHeaderView _timelineViewHeader; - _SelectionPainter _selectionPainter; - double _allDayHeight = 0; - double _timeIntervalHeight; - _UpdateCalendarStateDetails _updateCalendarStateDetails; - ValueNotifier<_SelectionDetails> _allDaySelectionNotifier; - ValueNotifier _viewHeaderNotifier, - _calendarCellNotifier, - _allDayNotifier, - _appointmentHoverNotifier; - ValueNotifier _selectionNotifier, _timelineViewHeaderNotifier; - bool _isRTL; + // _scrollStartPosition contains the touch movement starting position + late double _scrollStartPosition; - bool _isExpanded = false; - DateTime _hoveringDate; + // _position contains distance that the view swiped + double _position = 0; - /// The property to hold the resource value associated with the selected - /// calendar cell. - int _selectedResourceIndex = -1; - AnimationController _animationController; - Animation _heightAnimation; - Animation _allDayExpanderAnimation; - AnimationController _expanderAnimationController; + // animation controller to control the animation + late AnimationController _animationController; - /// Store the month widget instance used to update the month view - /// when the visible appointment updated. - _MonthViewWidget _monthView; + // animation handled for the view swiping + late Animation _animation; + + // tween animation to handle the animation + Tween _tween = Tween(begin: 0.0, end: 0.1); + + // Three visible dates for the three views, the dates will updated based on + // the swiping in the swipe end currentViewVisibleDates which stores the + // visible dates of the current displaying view + late List _visibleDates, + _previousViewVisibleDates, + _nextViewVisibleDates, + _currentViewVisibleDates; + + /// keys maintained to access the data and methods from the calendar view + /// class. + GlobalKey<_CalendarViewState> _previousViewKey = + GlobalKey<_CalendarViewState>(), + _currentViewKey = GlobalKey<_CalendarViewState>(), + _nextViewKey = GlobalKey<_CalendarViewState>(); + + UpdateCalendarStateDetails _updateCalendarStateDetails = + UpdateCalendarStateDetails(); + + /// Collection used to store the special regions and + /// check the special regions manipulations. + List? _timeRegions; + + /// The variable stores the timeline view scroll start position used to + /// decide the scroll as timeline scroll or scroll view on scroll update. + double _timelineScrollStartPosition = 0; + + /// The variable used to store the scroll start position to calculate the + /// scroll difference on scroll update. + double _timelineStartPosition = 0; + + /// Boolean value used to trigger the horizontal end animation when user + /// stops the scroll at middle. + bool _isNeedTimelineScrollEnd = false; + + /// Used to perform the drag or scroll in timeline view. + Drag? _drag; + + FocusNode _focusNode = FocusNode(); @override void initState() { - _isExpanded = false; - _appointmentLayoutKey = GlobalKey<_AppointmentLayoutState>(); - _hoveringDate = DateTime.now(); - _selectionNotifier = ValueNotifier(false); - _timelineViewHeaderNotifier = ValueNotifier(false); - _viewHeaderNotifier = ValueNotifier(null) - ..addListener(_timelineViewHoveringUpdate); - _calendarCellNotifier = ValueNotifier(null); - _allDayNotifier = ValueNotifier(null); - _appointmentHoverNotifier = ValueNotifier(null); - _allDaySelectionNotifier = ValueNotifier<_SelectionDetails>(null); - if (!_isTimelineView(widget.view) && widget.view != CalendarView.month) { - _animationController = AnimationController( - duration: const Duration(milliseconds: 200), vsync: this); - _heightAnimation = - CurveTween(curve: Curves.easeIn).animate(_animationController) - ..addListener(() { - setState(() { - /* Animates the all day panel height when - expanding or collapsing */ - }); - }); + widget.controller.forward = widget.isRTL + ? _moveToPreviousViewWithAnimation + : _moveToNextViewWithAnimation; + widget.controller.backward = widget.isRTL + ? _moveToNextViewWithAnimation + : _moveToPreviousViewWithAnimation; + + _currentChildIndex = 1; + _updateVisibleDates(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 250), + vsync: this, + animationBehavior: AnimationBehavior.normal); + _animation = _tween.animate(CurvedAnimation( + parent: _animationController, + curve: Curves.ease, + )) + ..addListener(animationListener); + + _timeRegions = CalendarViewHelper.cloneList(widget.specialRegions); - _expanderAnimationController = AnimationController( - duration: const Duration(milliseconds: 100), vsync: this); - _allDayExpanderAnimation = - CurveTween(curve: Curves.easeIn).animate(_expanderAnimationController) - ..addListener(() { - setState(() { - /* Animates the all day panel height when - expanding or collapsing */ - }); - }); + super.initState(); + } + + @override + void didUpdateWidget(CustomCalendarScrollView oldWidget) { + if (oldWidget.controller != widget.controller) { + widget.controller.forward = widget.isRTL + ? _moveToPreviousViewWithAnimation + : _moveToNextViewWithAnimation; + widget.controller.backward = widget.isRTL + ? _moveToNextViewWithAnimation + : _moveToPreviousViewWithAnimation; + + if (!CalendarViewHelper.isSameTimeSlot(oldWidget.controller.selectedDate, + widget.controller.selectedDate) || + !CalendarViewHelper.isSameTimeSlot( + _updateCalendarStateDetails.selectedDate, + widget.controller.selectedDate)) { + _selectResourceProgrammatically(); + } } - _updateCalendarStateDetails = _UpdateCalendarStateDetails(); - _timeIntervalHeight = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); - if (widget.view != CalendarView.month) { - _horizontalLinesCount = _getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view); - _scrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_scrollListener); - if (_isTimelineView(widget.view)) { - _timelineRulerController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_timeRulerListener); - _timelineViewHeaderScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); - _timelineViewAnimationController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - animationBehavior: AnimationBehavior.normal); - _timelineViewTween = Tween(begin: 0.0, end: 0.1); - _timelineViewAnimation = _timelineViewTween - .animate(_timelineViewAnimationController) - ..addListener(_scrollAnimationListener); - _timelineViewVerticalScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); - _timelineViewVerticalScrollController - .addListener(_updateResourceScroll); - widget.resourcePanelScrollController - ?.addListener(_updateResourcePanelScroll); + if (oldWidget.view != widget.view) { + _children.clear(); + + /// Switching timeline view from non timeline view or non timeline view + /// from timeline view creates the scroll layout as new because we handle + /// the scrolling touch for timeline view in this widget, so current + /// widget tree differ on timeline and non timeline views, so it creates + /// new widget tree. + if (CalendarViewHelper.isTimelineView(widget.view) != + CalendarViewHelper.isTimelineView(oldWidget.view)) { + _currentChildIndex = 1; } - _scrollToPosition(); + _updateVisibleDates(); + _position = 0; } - super.initState(); - } + if ((widget.calendar.monthViewSettings.navigationDirection != + oldWidget.calendar.monthViewSettings.navigationDirection) || + widget.calendar.scheduleViewMonthHeaderBuilder != + oldWidget.calendar.scheduleViewMonthHeaderBuilder || + widget.calendar.monthCellBuilder != + oldWidget.calendar.monthCellBuilder || + widget.width != oldWidget.width || + widget.height != oldWidget.height || + widget.textScaleFactor != oldWidget.textScaleFactor) { + _position = 0; + _children.clear(); + } - @override - void didUpdateWidget(_CalendarView oldWidget) { - if (widget.view != CalendarView.month) { - _allDaySelectionNotifier ??= ValueNotifier<_SelectionDetails>(null); - if (!_isTimelineView(widget.view)) { - _updateTimeSlotView(oldWidget); + if (!_isTimeRegionsEquals(widget.specialRegions, _timeRegions)) { + _timeRegions = CalendarViewHelper.cloneList(widget.specialRegions); + _position = 0; + _children.clear(); + } + + if ((widget.view == CalendarView.month || + widget.view == CalendarView.timelineMonth) && + widget.blackoutDates != oldWidget.blackoutDates) { + _children.clear(); + if (!_animationController.isAnimating) { + _position = 0; } + } - _updateHorizontalLineCount(oldWidget); + /// Check and re renders the views if the resource collection changed. + if (CalendarViewHelper.isTimelineView(widget.view) && + !CalendarViewHelper.isResourceCollectionEqual( + oldWidget.resourceCollection, widget.resourceCollection)) { + _updateSelectedResourceIndex(); + _position = 0; + _children.clear(); + } - _scrollController = _scrollController ?? - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_scrollListener); + if (oldWidget.calendar.showCurrentTimeIndicator != + widget.calendar.showCurrentTimeIndicator) { + _position = 0; + _children.clear(); + } - if (_isTimelineView(widget.view)) { - _updateTimelineViews(oldWidget); + //// condition to check and update the view when the settings changed, it will check each and every property of settings + //// to avoid unwanted repainting + if (oldWidget.calendar.timeSlotViewSettings != + widget.calendar.timeSlotViewSettings || + oldWidget.calendar.monthViewSettings != + widget.calendar.monthViewSettings || + oldWidget.calendar.blackoutDatesTextStyle != + widget.calendar.blackoutDatesTextStyle || + oldWidget.calendar.resourceViewSettings != + widget.calendar.resourceViewSettings || + oldWidget.calendar.viewHeaderStyle != widget.calendar.viewHeaderStyle || + oldWidget.calendar.viewHeaderHeight != + widget.calendar.viewHeaderHeight || + oldWidget.calendar.todayHighlightColor != + widget.calendar.todayHighlightColor || + oldWidget.calendar.cellBorderColor != widget.calendar.cellBorderColor || + oldWidget.calendarTheme != widget.calendarTheme || + oldWidget.locale != widget.locale || + oldWidget.calendar.selectionDecoration != + widget.calendar.selectionDecoration) { + final bool isTimelineView = + CalendarViewHelper.isTimelineView(widget.view); + if (widget.view != CalendarView.month && + (oldWidget.calendar.timeSlotViewSettings.timeInterval != + widget.calendar.timeSlotViewSettings.timeInterval || + (!isTimelineView && + oldWidget.calendar.timeSlotViewSettings.timeIntervalHeight != + widget + .calendar.timeSlotViewSettings.timeIntervalHeight) || + (isTimelineView && + oldWidget.calendar.timeSlotViewSettings.timeIntervalWidth != + widget + .calendar.timeSlotViewSettings.timeIntervalWidth))) { + if (_currentChildIndex == 0) { + _previousViewKey.currentState!._retainScrolledDateTime(); + } else if (_currentChildIndex == 1) { + _currentViewKey.currentState!._retainScrolledDateTime(); + } else if (_currentChildIndex == 2) { + _nextViewKey.currentState!._retainScrolledDateTime(); + } } + _children.clear(); + _position = 0; } - /// Update the scroll position with following scenarios - /// 1. View changed from month or schedule view. - /// 2. View changed from timeline view(timeline day, timeline week, - /// timeline work week) to timeslot view(day, week, work week). - /// 3. View changed from timeslot view(day, week, work week) to - /// timeline view(timeline day, timeline week, timeline work week). - /// - /// This condition used to restrict the following scenarios - /// 1. View changed to month view. - /// 2. View changed with in the day, week, work week - /// (eg., view changed to week from day). - /// 3. View changed with in the timeline day, timeline week, timeline - /// work week(eg., view changed to timeline week from timeline day). - if ((oldWidget.view == CalendarView.month || - oldWidget.view == CalendarView.schedule || - (oldWidget.view != widget.view && _isTimelineView(widget.view)) || - (_isTimelineView(oldWidget.view) && - !_isTimelineView(widget.view))) && - widget.view != CalendarView.month) { - _scrollToPosition(); + if (widget.calendar.monthViewSettings.numberOfWeeksInView != + oldWidget.calendar.monthViewSettings.numberOfWeeksInView || + widget.calendar.timeSlotViewSettings.nonWorkingDays != + oldWidget.calendar.timeSlotViewSettings.nonWorkingDays || + widget.calendar.firstDayOfWeek != oldWidget.calendar.firstDayOfWeek || + widget.isRTL != oldWidget.isRTL) { + _updateVisibleDates(); + _position = 0; } - _timeIntervalHeight = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); - - /// Clear the all day panel selection when the calendar view changed - /// Eg., if select the all day panel and switch to month view and again - /// select the same month cell and move to day view then the view show - /// calendar cell selection and all day panel selection. - if (oldWidget.view != widget.view) { - _allDaySelectionNotifier = ValueNotifier<_SelectionDetails>(null); + if (!isSameDate(widget.calendar.minDate, oldWidget.calendar.minDate) || + !isSameDate(widget.calendar.maxDate, oldWidget.calendar.maxDate)) { + _updateVisibleDates(); + _position = 0; } - if ((oldWidget.view != widget.view || - oldWidget.width != widget.width || - oldWidget.height != widget.height) && - _selectionPainter._appointmentView != null) { - _selectionPainter._appointmentView = null; + if (CalendarViewHelper.isTimelineView(widget.view) != + CalendarViewHelper.isTimelineView(oldWidget.view)) { + _children.clear(); } - /// When view switched from any other view to timeline view, and resource - /// enabled the selection must render the first resource view. - widget.getCalendarState(_updateCalendarStateDetails); - if (!_isTimelineView(oldWidget.view) && - _updateCalendarStateDetails._selectedDate != null && - _isResourceEnabled(widget.calendar.dataSource, widget.view) && - _selectedResourceIndex == -1) { - _selectedResourceIndex = 0; + /// position set as zero to maintain the existing scroll position in + /// timeline view + if (CalendarViewHelper.isTimelineView(widget.view) && + (oldWidget.calendar.backgroundColor != + widget.calendar.backgroundColor || + oldWidget.calendar.headerStyle != widget.calendar.headerStyle)) { + _position = 0; } - if (!_isResourceEnabled(widget.calendar.dataSource, widget.view)) { - _selectedResourceIndex = -1; + if (widget.controller == oldWidget.controller) { + if (oldWidget.controller.displayDate != widget.controller.displayDate || + !isSameDate(_updateCalendarStateDetails.currentDate, + widget.controller.displayDate)) { + widget.getCalendarState(_updateCalendarStateDetails); + _updateCalendarStateDetails.currentDate = + widget.controller.displayDate!; + widget.updateCalendarState(_updateCalendarStateDetails); + _updateVisibleDates(); + _updateMoveToDate(); + _position = 0; + } + + if (!CalendarViewHelper.isSameTimeSlot(oldWidget.controller.selectedDate, + widget.controller.selectedDate) || + !CalendarViewHelper.isSameTimeSlot( + _updateCalendarStateDetails.selectedDate, + widget.controller.selectedDate)) { + widget.getCalendarState(_updateCalendarStateDetails); + _updateCalendarStateDetails.selectedDate = + widget.controller.selectedDate; + widget.updateCalendarState(_updateCalendarStateDetails); + _selectResourceProgrammatically(); + _updateSelection(); + _position = 0; + } } super.didUpdateWidget(oldWidget); @@ -263,640 +415,3583 @@ class _CalendarViewState extends State<_CalendarView> @override Widget build(BuildContext context) { - _isRTL = _isRTLLayout(context); - widget.getCalendarState(_updateCalendarStateDetails); - switch (widget.view) { - case CalendarView.schedule: - return null; - case CalendarView.month: - return _getMonthView(); - case CalendarView.day: - case CalendarView.week: - case CalendarView.workWeek: - return _getDayView(); - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - return _getTimelineView(); + if (!CalendarViewHelper.isTimelineView(widget.view) && + widget.view != CalendarView.month) { + _updateScrollPosition(); } - return null; + double leftPosition = 0, + rightPosition = 0, + topPosition = 0, + bottomPosition = 0; + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.horizontal || + widget.view != CalendarView.month) { + leftPosition = -widget.width; + rightPosition = -widget.width; + } else { + topPosition = -widget.height; + bottomPosition = -widget.height; + } + + final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); + final Widget customScrollWidget = GestureDetector( + child: CustomScrollViewerLayout( + _addViews(), + widget.view != CalendarView.month || + widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.horizontal + ? CustomScrollDirection.horizontal + : CustomScrollDirection.vertical, + _position, + _currentChildIndex), + onTapDown: (TapDownDetails details) { + if (!_focusNode.hasFocus) { + _focusNode.requestFocus(); + } + }, + onHorizontalDragStart: isTimelineView ? null : _onHorizontalStart, + onHorizontalDragUpdate: isTimelineView ? null : _onHorizontalUpdate, + onHorizontalDragEnd: isTimelineView ? null : _onHorizontalEnd, + onVerticalDragStart: widget.view == CalendarView.month && + widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical + ? _onVerticalStart + : null, + onVerticalDragUpdate: widget.view == CalendarView.month && + widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical + ? _onVerticalUpdate + : null, + onVerticalDragEnd: widget.view == CalendarView.month && + widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical + ? _onVerticalEnd + : null, + ); + + return Stack( + children: [ + Positioned( + left: leftPosition, + right: rightPosition, + bottom: bottomPosition, + top: topPosition, + child: RawKeyboardListener( + focusNode: _focusNode, + onKey: _onKeyDown, + child: isTimelineView + ? Listener( + onPointerSignal: _handlePointerSignal, + child: RawGestureDetector( + gestures: { + HorizontalDragGestureRecognizer: + GestureRecognizerFactoryWithHandlers< + HorizontalDragGestureRecognizer>( + () => HorizontalDragGestureRecognizer(), + (HorizontalDragGestureRecognizer instance) { + instance..onUpdate = _handleDragUpdate; + instance..onStart = _handleDragStart; + instance..onEnd = _handleDragEnd; + instance..onCancel = _handleDragCancel; + }, + ) + }, + behavior: HitTestBehavior.opaque, + child: customScrollWidget), + ) + : customScrollWidget, + )), + ], + ); } @override void dispose() { - if (_viewHeaderNotifier != null) { - _viewHeaderNotifier.removeListener(_timelineViewHoveringUpdate); - } + _animationController.dispose(); + _animation.removeListener(animationListener); + super.dispose(); + } - if (_calendarCellNotifier != null) { - _calendarCellNotifier.removeListener(_timelineViewHoveringUpdate); + /// Get the scroll layout current child view state based on its visible dates. + GlobalKey<_CalendarViewState>? _getCurrentViewByVisibleDates() { + _CalendarView? view; + for (int i = 0; i < _children.length; i++) { + final _CalendarView currentView = _children[i]; + if (currentView.visibleDates == _currentViewVisibleDates) { + view = currentView; + break; + } } - if (_timelineViewAnimation != null) { - _timelineViewAnimation.removeListener(_scrollAnimationListener); + if (view == null) { + return null; } + // ignore: avoid_as + return view.key as GlobalKey<_CalendarViewState>; + } - if (_isTimelineView(widget.view) && - _timelineViewAnimationController != null) { - _timelineViewAnimationController.dispose(); - _timelineViewAnimationController = null; - } - if (_scrollController != null) { - _scrollController.removeListener(_scrollListener); - _scrollController.dispose(); - _scrollController = null; - } - if (_timelineViewHeaderScrollController != null) { - _timelineViewHeaderScrollController.dispose(); - _timelineViewHeaderScrollController = null; - } - if (_animationController != null) { - _animationController.dispose(); - _animationController = null; + /// Handle start of the scroll, set the scroll start position and check + /// the start position as start or end of timeline scroll controller. + /// If the timeline view scroll starts at min or max scroll position then + /// move the previous view to end of the scroll or move the next view to + /// start of the scroll and set the drag as timeline scroll controller drag. + void _handleDragStart(DragStartDetails details) { + if (!CalendarViewHelper.isTimelineView(widget.view)) { + return; } - if (_timelineRulerController != null) { - _timelineRulerController.dispose(); - _timelineRulerController = null; + final GlobalKey<_CalendarViewState> viewKey = + _getCurrentViewByVisibleDates()!; + _timelineScrollStartPosition = + viewKey.currentState!._scrollController!.position.pixels; + _timelineStartPosition = details.globalPosition.dx; + _isNeedTimelineScrollEnd = false; + + /// If the timeline view scroll starts at min or max scroll position then + /// move the previous view to end of the scroll or move the next view to + /// start of the scroll + if (_timelineScrollStartPosition >= + viewKey.currentState!._scrollController!.position.maxScrollExtent) { + _positionTimelineView(); + } else if (_timelineScrollStartPosition <= + viewKey.currentState!._scrollController!.position.minScrollExtent) { + _positionTimelineView(); } - if (_expanderAnimationController != null) { - _expanderAnimationController.dispose(); - _expanderAnimationController = null; + /// Set the drag as timeline scroll controller drag. + if (viewKey.currentState!._scrollController!.hasClients) { + _drag = viewKey.currentState!._scrollController!.position + .drag(details, _disposeDrag); } - - super.dispose(); } - /// Updates the resource panel scroll based on timeline scroll in vertical - /// direction. - void _updateResourcePanelScroll() { - if (_updateCalendarStateDetails._currentViewVisibleDates == - widget.visibleDates) { - widget.removePicker(); + /// Handles the scroll update, if the scroll moves after the timeline max + /// scroll position or before the timeline min scroll position then check the + /// scroll start position if it is start or end of the timeline scroll view + /// then pass the touch to custom scroll view and set the timeline view + /// drag as null; + void _handleDragUpdate(DragUpdateDetails details) { + if (!CalendarViewHelper.isTimelineView(widget.view)) { + return; } - - if (widget.resourcePanelScrollController == null || - !_isResourceEnabled(widget.calendar.dataSource, widget.view)) { + final GlobalKey<_CalendarViewState> viewKey = + _getCurrentViewByVisibleDates()!; + + /// Calculate the scroll difference by current scroll position and start + /// scroll position. + final double difference = + details.globalPosition.dx - _timelineStartPosition; + if (_timelineScrollStartPosition >= + viewKey.currentState!._scrollController!.position.maxScrollExtent && + ((difference < 0 && !widget.isRTL) || + (difference > 0 && widget.isRTL))) { + /// Set the scroll position as timeline scroll start position and the + /// value used on horizontal update method. + _scrollStartPosition = _timelineStartPosition; + _drag?.cancel(); + + /// Move the touch(drag) to custom scroll view. + _onHorizontalUpdate(details); + + /// Enable boolean value used to trigger the horizontal end animation on + /// drag end. + _isNeedTimelineScrollEnd = true; + + /// Remove the timeline view drag or scroll. + _disposeDrag(); + return; + } else if (_timelineScrollStartPosition <= + viewKey.currentState!._scrollController!.position.minScrollExtent && + ((difference > 0 && !widget.isRTL) || + (difference < 0 && widget.isRTL))) { + /// Set the scroll position as timeline scroll start position and the + /// value used on horizontal update method. + _scrollStartPosition = _timelineStartPosition; + _drag?.cancel(); + + /// Move the touch(drag) to custom scroll view. + _onHorizontalUpdate(details); + + /// Enable boolean value used to trigger the horizontal end animation on + /// drag end. + _isNeedTimelineScrollEnd = true; + + /// Remove the timeline view drag or scroll. + _disposeDrag(); return; } - if (widget.resourcePanelScrollController.offset != - _timelineViewVerticalScrollController.offset) { - _timelineViewVerticalScrollController - .jumpTo(widget.resourcePanelScrollController.offset); - } + _drag?.update(details); } - /// Updates the timeline view scroll in vertical direction based on resource - /// panel scroll. - void _updateResourceScroll() { - if (_updateCalendarStateDetails._currentViewVisibleDates == - widget.visibleDates) { - widget.removePicker(); - } - - if (widget.resourcePanelScrollController == null || - !_isResourceEnabled(widget.calendar.dataSource, widget.view)) { + /// Handle the scroll end to update the timeline view scroll or custom scroll + /// view scroll based on [_isNeedTimelineScrollEnd] value + void _handleDragEnd(DragEndDetails details) { + if (_isNeedTimelineScrollEnd) { + _isNeedTimelineScrollEnd = false; + _onHorizontalEnd(details); return; } - if (widget.resourcePanelScrollController.offset != - _timelineViewVerticalScrollController.offset) { - widget.resourcePanelScrollController - .jumpTo(_timelineViewVerticalScrollController.offset); - } + _isNeedTimelineScrollEnd = false; + _drag?.end(details); } - Widget _getMonthView() { - return GestureDetector( - child: MouseRegion( - onEnter: _pointerEnterEvent, - onExit: _pointerExitEvent, - onHover: _pointerHoverEvent, - child: _addMonthView(_isRTL, widget.locale), - ), - onTapUp: (TapUpDetails details) { - _handleOnTapForMonth(details); - }, - onLongPressStart: (LongPressStartDetails details) { - _handleOnLongPressForMonth(details); - }, - ); + /// Handle drag cancel related operations. + void _handleDragCancel() { + _isNeedTimelineScrollEnd = false; + _drag?.cancel(); } - Widget _getDayView() { - _allDayHeight = 0; + /// Remove the drag when the touch(drag) passed to custom scroll view. + void _disposeDrag() { + _drag = null; + } - final bool isCurrentView = - _updateCalendarStateDetails._currentViewVisibleDates == - widget.visibleDates; - if (widget.view == CalendarView.day) { - final double viewHeaderHeight = - _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); - if (isCurrentView) { - _allDayHeight = _kAllDayLayoutHeight > viewHeaderHeight && - _updateCalendarStateDetails._allDayPanelHeight > - viewHeaderHeight - ? _updateCalendarStateDetails._allDayPanelHeight > - _kAllDayLayoutHeight - ? _kAllDayLayoutHeight - : _updateCalendarStateDetails._allDayPanelHeight - : viewHeaderHeight; - if (_allDayHeight < _updateCalendarStateDetails._allDayPanelHeight) { - _allDayHeight += _kAllDayAppointmentHeight; - } - } else { - _allDayHeight = viewHeaderHeight; + /// Handle the pointer scroll when a pointer signal occurs over this object. + /// eg., track pad scroll. + void _handlePointerSignal(PointerSignalEvent event) { + final GlobalKey<_CalendarViewState>? viewKey = + _getCurrentViewByVisibleDates(); + if (event is PointerScrollEvent && viewKey != null) { + final double scrolledPosition = + widget.isRTL ? -event.scrollDelta.dx : event.scrollDelta.dx; + final double targetScrollOffset = math.min( + math.max( + viewKey.currentState!._scrollController!.position.pixels + + scrolledPosition, + viewKey + .currentState!._scrollController!.position.minScrollExtent), + viewKey.currentState!._scrollController!.position.maxScrollExtent); + if (targetScrollOffset != + viewKey.currentState!._scrollController!.position.pixels) { + viewKey.currentState!._scrollController!.position + .jumpTo(targetScrollOffset); } - } else if (isCurrentView) { - _allDayHeight = - _updateCalendarStateDetails._allDayPanelHeight > _kAllDayLayoutHeight - ? _kAllDayLayoutHeight - : _updateCalendarStateDetails._allDayPanelHeight; - _allDayHeight = _allDayHeight * _heightAnimation.value; } - - return GestureDetector( - child: MouseRegion( - onEnter: _pointerEnterEvent, - onHover: _pointerHoverEvent, - onExit: _pointerExitEvent, - child: _addDayView( - widget.width, - _timeIntervalHeight * _horizontalLinesCount, - _isRTL, - widget.locale, - isCurrentView), - ), - onTapUp: (TapUpDetails details) { - _handleOnTapForDay(details); - }, - onLongPressStart: (LongPressStartDetails details) { - _handleOnLongPressForDay(details); - }, - ); } - Widget _getTimelineView() { - return GestureDetector( - child: MouseRegion( - onEnter: _pointerEnterEvent, - onHover: _pointerHoverEvent, - onExit: _pointerExitEvent, - child: _addTimelineView( - _timeIntervalHeight * - (_horizontalLinesCount * widget.visibleDates.length), - widget.height, - widget.locale), - ), - onTapUp: (TapUpDetails details) { - _handleOnTapForTimeline(details); - }, - onLongPressStart: (LongPressStartDetails details) { - _handleOnLongPressForTimeline(details); - }, - ); + void _updateVisibleDates() { + widget.getCalendarState(_updateCalendarStateDetails); + final DateTime currentDate = DateTime( + _updateCalendarStateDetails.currentDate!.year, + _updateCalendarStateDetails.currentDate!.month, + _updateCalendarStateDetails.currentDate!.day); + final DateTime prevDate = DateTimeHelper.getPreviousViewStartDate( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentDate); + final DateTime nextDate = DateTimeHelper.getNextViewStartDate(widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, currentDate); + final List? nonWorkingDays = (widget.view == CalendarView.workWeek || + widget.view == CalendarView.timelineWorkWeek) + ? widget.calendar.timeSlotViewSettings.nonWorkingDays + : null; + final int visibleDatesCount = DateTimeHelper.getViewDatesCount( + widget.view, widget.calendar.monthViewSettings.numberOfWeeksInView); + + _visibleDates = getVisibleDates(currentDate, nonWorkingDays, + widget.calendar.firstDayOfWeek, visibleDatesCount) + .cast(); + _previousViewVisibleDates = getVisibleDates( + widget.isRTL ? nextDate : prevDate, + nonWorkingDays, + widget.calendar.firstDayOfWeek, + visibleDatesCount) + .cast(); + _nextViewVisibleDates = getVisibleDates(widget.isRTL ? prevDate : nextDate, + nonWorkingDays, widget.calendar.firstDayOfWeek, visibleDatesCount) + .cast(); + if (widget.view == CalendarView.timelineMonth) { + _visibleDates = DateTimeHelper.getCurrentMonthDates(_visibleDates); + _previousViewVisibleDates = + DateTimeHelper.getCurrentMonthDates(_previousViewVisibleDates); + _nextViewVisibleDates = + DateTimeHelper.getCurrentMonthDates(_nextViewVisibleDates); + } + + _currentViewVisibleDates = _visibleDates; + _updateCalendarStateDetails.currentViewVisibleDates = + _currentViewVisibleDates; + widget.updateCalendarState(_updateCalendarStateDetails); + + if (_currentChildIndex == 0) { + _visibleDates = _nextViewVisibleDates; + _nextViewVisibleDates = _previousViewVisibleDates; + _previousViewVisibleDates = _currentViewVisibleDates; + } else if (_currentChildIndex == 1) { + _visibleDates = _currentViewVisibleDates; + } else if (_currentChildIndex == 2) { + _visibleDates = _previousViewVisibleDates; + _previousViewVisibleDates = _nextViewVisibleDates; + _nextViewVisibleDates = _currentViewVisibleDates; + } } - void _timelineViewHoveringUpdate() { - if (!_isTimelineView(widget.view) && mounted) { - return; + void _updateNextViewVisibleDates() { + DateTime currentViewDate = _currentViewVisibleDates[0]; + if (widget.view == CalendarView.month && + widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { + currentViewDate = _currentViewVisibleDates[ + (_currentViewVisibleDates.length / 2).truncate()]; } - // Updates the timeline views based on mouse hovering position. - _timelineViewHeaderNotifier.value = !_timelineViewHeaderNotifier.value; + if (widget.isRTL) { + currentViewDate = DateTimeHelper.getPreviousViewStartDate( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentViewDate); + } else { + currentViewDate = DateTimeHelper.getNextViewStartDate( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentViewDate); + } + + List dates = getVisibleDates( + currentViewDate, + widget.view == CalendarView.workWeek || + widget.view == CalendarView.timelineWorkWeek + ? widget.calendar.timeSlotViewSettings.nonWorkingDays + : null, + widget.calendar.firstDayOfWeek, + DateTimeHelper.getViewDatesCount(widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView)) + .cast(); + + if (widget.view == CalendarView.timelineMonth) { + dates = DateTimeHelper.getCurrentMonthDates(dates); + } + + if (_currentChildIndex == 0) { + _nextViewVisibleDates = dates; + } else if (_currentChildIndex == 1) { + _previousViewVisibleDates = dates; + } else { + _visibleDates = dates; + } } - void _scrollAnimationListener() { - _scrollController.jumpTo(_timelineViewAnimation.value); + void _updatePreviousViewVisibleDates() { + DateTime currentViewDate = _currentViewVisibleDates[0]; + if (widget.view == CalendarView.month && + widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { + currentViewDate = _currentViewVisibleDates[ + (_currentViewVisibleDates.length / 2).truncate()]; + } + + if (widget.isRTL) { + currentViewDate = DateTimeHelper.getNextViewStartDate( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentViewDate); + } else { + currentViewDate = DateTimeHelper.getPreviousViewStartDate( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentViewDate); + } + + List dates = getVisibleDates( + currentViewDate, + widget.view == CalendarView.workWeek || + widget.view == CalendarView.timelineWorkWeek + ? widget.calendar.timeSlotViewSettings.nonWorkingDays + : null, + widget.calendar.firstDayOfWeek, + DateTimeHelper.getViewDatesCount(widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView)) + .cast(); + + if (widget.view == CalendarView.timelineMonth) { + dates = DateTimeHelper.getCurrentMonthDates(dates); + } + + if (_currentChildIndex == 0) { + _visibleDates = dates; + } else if (_currentChildIndex == 1) { + _nextViewVisibleDates = dates; + } else { + _previousViewVisibleDates = dates; + } } - void _scrollToPosition() { - SchedulerBinding.instance.addPostFrameCallback((_) { - if (widget.view == CalendarView.month) { - return; - } + void _getCalendarViewStateDetails(UpdateCalendarStateDetails details) { + widget.getCalendarState(_updateCalendarStateDetails); + details.currentDate = _updateCalendarStateDetails.currentDate; + details.currentViewVisibleDates = + _updateCalendarStateDetails.currentViewVisibleDates; + details.selectedDate = _updateCalendarStateDetails.selectedDate; + details.allDayPanelHeight = _updateCalendarStateDetails.allDayPanelHeight; + details.allDayAppointmentViewCollection = + _updateCalendarStateDetails.allDayAppointmentViewCollection; + details.appointments = _updateCalendarStateDetails.appointments; + details.visibleAppointments = + _updateCalendarStateDetails.visibleAppointments; + } - widget.getCalendarState(_updateCalendarStateDetails); - final double scrollPosition = _getScrollPositionForCurrentDate( - _updateCalendarStateDetails._currentDate); - if (scrollPosition == -1) { - return; - } + void _updateCalendarViewStateDetails(UpdateCalendarStateDetails details) { + _updateCalendarStateDetails.selectedDate = details.selectedDate; + widget.updateCalendarState(_updateCalendarStateDetails); + } - _scrollController.jumpTo(scrollPosition); - }); + CalendarTimeRegion _getCalendarTimeRegionFromTimeRegion(TimeRegion region) { + return CalendarTimeRegion( + startTime: region.startTime, + endTime: region.endTime, + color: region.color, + text: region.text, + textStyle: region.textStyle, + recurrenceExceptionDates: region.recurrenceExceptionDates, + recurrenceRule: region.recurrenceRule, + resourceIds: region.resourceIds, + timeZone: region.timeZone, + enablePointerInteraction: region.enablePointerInteraction, + iconData: region.iconData, + ); } - double _getScrollPositionForCurrentDate(DateTime date) { - final int visibleDatesCount = widget.visibleDates.length; - if (!isDateWithInDateRange(widget.visibleDates[0], - widget.visibleDates[visibleDatesCount - 1], date)) { - return -1; + /// Return collection of time region, in between the visible dates. + List _getRegions(List visibleDates) { + final DateTime visibleStartDate = visibleDates[0]; + final DateTime visibleEndDate = visibleDates[visibleDates.length - 1]; + final List regionCollection = []; + if (_timeRegions == null) { + return regionCollection; } - double timeToPosition = 0; - if (!_isTimelineView(widget.view)) { - timeToPosition = - _timeToPosition(widget.calendar, date, _timeIntervalHeight); - } else { - for (int i = 0; i < visibleDatesCount; i++) { - if (!isSameDate(date, widget.visibleDates[i])) { - continue; + final DateTime startDate = + AppointmentHelper.convertToStartTime(visibleStartDate); + final DateTime endDate = AppointmentHelper.convertToEndTime(visibleEndDate); + for (int j = 0; j < _timeRegions!.length; j++) { + final TimeRegion timeRegion = _timeRegions![j]; + final CalendarTimeRegion region = + _getCalendarTimeRegionFromTimeRegion(timeRegion); + region.actualStartTime = + AppointmentHelper.convertTimeToAppointmentTimeZone( + region.startTime, region.timeZone, widget.calendar.timeZone); + region.actualEndTime = AppointmentHelper.convertTimeToAppointmentTimeZone( + region.endTime, region.timeZone, widget.calendar.timeZone); + region.data = timeRegion; + + if (region.recurrenceRule == null || region.recurrenceRule == '') { + if (AppointmentHelper.isDateRangeWithinVisibleDateRange( + region.actualStartTime, region.actualEndTime, startDate, endDate)) { + regionCollection.add(region); } - if (widget.view == CalendarView.timelineMonth) { - timeToPosition = _timeIntervalHeight * i; - } else { - timeToPosition = (_getSingleViewWidthForTimeLineView(this) * i) + - _timeToPosition(widget.calendar, date, _timeIntervalHeight); - } + continue; + } - break; + getRecurrenceRegions(region, regionCollection, startDate, endDate, + widget.calendar.timeZone); + } + + return regionCollection; + } + + /// Get the recurrence time regions in between the visible date range. + void getRecurrenceRegions( + CalendarTimeRegion region, + List regions, + DateTime visibleStartDate, + DateTime visibleEndDate, + String? calendarTimeZone) { + final DateTime regionStartDate = region.actualStartTime; + if (regionStartDate.isAfter(visibleEndDate)) { + return; + } + + String rule = region.recurrenceRule!; + if (!rule.contains('COUNT') && !rule.contains('UNTIL')) { + final DateFormat formatter = DateFormat('yyyyMMdd'); + final String newSubString = ';UNTIL=' + formatter.format(visibleEndDate); + rule = rule + newSubString; + } + + List recursiveDates; + DateTime endDate; + final List ruleSeparator = ['=', ';', ',']; + final List rRule = + RecurrenceHelper.splitRule(region.recurrenceRule!, ruleSeparator); + if (region.recurrenceRule!.contains('UNTIL')) { + final String untilValue = rRule[rRule.indexOf('UNTIL') + 1]; + endDate = DateTime.parse(untilValue); + endDate = addDuration( + endDate, region.actualEndTime.difference(region.actualStartTime)); + endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); + if (!(regionStartDate.isBefore(visibleEndDate) && + visibleStartDate.isBefore(endDate))) { + return; + } + } else if (region.recurrenceRule!.contains('COUNT')) { + recursiveDates = RecurrenceHelper.getRecurrenceDateTimeCollection( + region.recurrenceRule!, region.actualStartTime); + endDate = recursiveDates.last; + endDate = addDuration( + endDate, region.actualEndTime.difference(region.actualStartTime)); + endDate = DateTime(endDate.year, endDate.month, endDate.day, 23, 59, 59); + if (!(regionStartDate.isBefore(visibleEndDate) && + visibleStartDate.isBefore(endDate))) { + return; } } - if (_scrollController.hasClients) { - if (timeToPosition > _scrollController.position.maxScrollExtent) { - timeToPosition = _scrollController.position.maxScrollExtent; - } else if (timeToPosition < _scrollController.position.minScrollExtent) { - timeToPosition = _scrollController.position.minScrollExtent; + recursiveDates = RecurrenceHelper.getRecurrenceDateTimeCollection( + rule, region.actualStartTime, + recurrenceDuration: + region.actualEndTime.difference(region.actualStartTime), + specificStartDate: visibleStartDate, + specificEndDate: visibleEndDate); + + for (int j = 0; j < recursiveDates.length; j++) { + final DateTime recursiveDate = recursiveDates[j]; + if (region.recurrenceExceptionDates != null) { + bool isDateContains = false; + for (int i = 0; i < region.recurrenceExceptionDates!.length; i++) { + final DateTime date = + AppointmentHelper.convertTimeToAppointmentTimeZone( + region.recurrenceExceptionDates![i], '', calendarTimeZone); + if (date.year == recursiveDate.year && + date.month == recursiveDate.month && + date.day == recursiveDate.day) { + isDateContains = true; + break; + } + } + if (isDateContains) { + continue; + } } + + final CalendarTimeRegion occurrenceRegion = + cloneRecurrenceRegion(region, recursiveDate, calendarTimeZone); + regions.add(occurrenceRegion); } + } - return timeToPosition; + /// Used to clone the time region with new values. + CalendarTimeRegion cloneRecurrenceRegion(CalendarTimeRegion region, + DateTime recursiveDate, String? calendarTimeZone) { + final int minutes = + region.actualEndTime.difference(region.actualStartTime).inMinutes; + final DateTime actualEndTime = + addDuration(recursiveDate, Duration(minutes: minutes)); + final DateTime startDate = + AppointmentHelper.convertTimeToAppointmentTimeZone( + recursiveDate, region.timeZone, calendarTimeZone); + + final DateTime endDate = AppointmentHelper.convertTimeToAppointmentTimeZone( + actualEndTime, region.timeZone, calendarTimeZone); + + final TimeRegion occurrenceTimeRegion = + region.data.copyWith(startTime: startDate, endTime: endDate); + final CalendarTimeRegion occurrenceRegion = + _getCalendarTimeRegionFromTimeRegion(occurrenceTimeRegion); + occurrenceRegion.actualStartTime = recursiveDate; + occurrenceRegion.actualEndTime = actualEndTime; + occurrenceRegion.data = occurrenceTimeRegion; + return occurrenceRegion; } - /// Used to retain the scrolled date time. - void _retainScrolledDateTime() { - if (widget.view == CalendarView.month) { - return; + /// Return date collection which falls between the visible date range. + List _getDatesWithInVisibleDateRange( + List? dates, List visibleDates) { + final List visibleMonthDates = []; + if (dates == null) { + return visibleMonthDates; } - DateTime scrolledDate = widget.visibleDates[0]; - double scrolledPosition = 0; - if (_isTimelineView(widget.view)) { - final double singleViewWidth = _getSingleViewWidthForTimeLineView(this); + final DateTime visibleStartDate = visibleDates[0]; + final DateTime visibleEndDate = visibleDates[visibleDates.length - 1]; + final int datesCount = dates.length; + final Map dateCollection = {}; + for (int i = 0; i < datesCount; i++) { + final DateTime currentDate = dates[i]; + if (!isDateWithInDateRange( + visibleStartDate, visibleEndDate, currentDate)) { + continue; + } - /// Calculate the scrolled position date. - scrolledDate = widget - .visibleDates[_scrollController.position.pixels ~/ singleViewWidth]; + if (dateCollection.keys.contains( + currentDate.day.toString() + currentDate.month.toString())) { + continue; + } - /// Calculate the scrolled hour position without visible date position. - scrolledPosition = _scrollController.position.pixels % singleViewWidth; - } else { - /// Calculate the scrolled hour position. - scrolledPosition = _scrollController.position.pixels; + dateCollection[currentDate.day.toString() + + currentDate.month.toString()] = currentDate; + visibleMonthDates.add(currentDate); } - /// Calculate the current horizontal line based on time interval height. - final double columnIndex = scrolledPosition / _timeIntervalHeight; + return visibleMonthDates; + } - /// Calculate the time based on calculated horizontal position. - final double time = - ((_getTimeInterval(widget.calendar.timeSlotViewSettings) / 60) * - columnIndex) + - widget.calendar.timeSlotViewSettings.startHour; - final int hour = time.toInt(); - final int minute = ((time - hour) * 60).round(); - scrolledDate = DateTime( - scrolledDate.year, scrolledDate.month, scrolledDate.day, hour, minute); + List _addViews() { + if (_children.isEmpty) { + _previousView = _CalendarView( + widget.calendar, + widget.view, + _previousViewVisibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + _getRegions(_previousViewVisibleDates), + _getDatesWithInVisibleDateRange( + widget.blackoutDates, _previousViewVisibleDates), + _focusNode, + widget.removePicker, + widget.calendar.allowViewNavigation, + widget.controller, + widget.resourcePanelScrollController, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.minDate, + widget.maxDate, + widget.localizations, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: _previousViewKey, + ); + _currentView = _CalendarView( + widget.calendar, + widget.view, + _visibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + _getRegions(_visibleDates), + _getDatesWithInVisibleDateRange(widget.blackoutDates, _visibleDates), + _focusNode, + widget.removePicker, + widget.calendar.allowViewNavigation, + widget.controller, + widget.resourcePanelScrollController, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.minDate, + widget.maxDate, + widget.localizations, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: _currentViewKey, + ); + _nextView = _CalendarView( + widget.calendar, + widget.view, + _nextViewVisibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + _getRegions(_nextViewVisibleDates), + _getDatesWithInVisibleDateRange( + widget.blackoutDates, _nextViewVisibleDates), + _focusNode, + widget.removePicker, + widget.calendar.allowViewNavigation, + widget.controller, + widget.resourcePanelScrollController, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.minDate, + widget.maxDate, + widget.localizations, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: _nextViewKey, + ); + + _children.add(_previousView); + _children.add(_currentView); + _children.add(_nextView); + return _children; + } - /// Update the scrolled position after the widget generated. - SchedulerBinding.instance.addPostFrameCallback((_) { - _scrollController.jumpTo(_getPositionFromDate(scrolledDate)); - }); + widget.getCalendarState(_updateCalendarStateDetails); + final _CalendarView previousView = _updateViews( + _previousView, _previousViewKey, _previousViewVisibleDates); + final _CalendarView currentView = + _updateViews(_currentView, _currentViewKey, _visibleDates); + final _CalendarView nextView = + _updateViews(_nextView, _nextViewKey, _nextViewVisibleDates); + + //// Update views while the all day view height differ from original height, + //// else repaint the appointment painter while current child visible appointment not equals calendar visible appointment + if (_previousView != previousView) { + _previousView = previousView; + } + if (_currentView != currentView) { + _currentView = currentView; + } + if (_nextView != nextView) { + _nextView = nextView; + } + + return _children; } - /// Calculate the position from date. - double _getPositionFromDate(DateTime date) { - final int visibleDatesCount = widget.visibleDates.length; - _timeIntervalHeight = _getTimeIntervalHeight( + // method to check and update the views and appointments on the swiping end + _CalendarView _updateViews(_CalendarView view, + GlobalKey<_CalendarViewState> viewKey, List visibleDates) { + final int index = _children.indexOf(view); + + final AppointmentLayout appointmentLayout = + viewKey.currentState!._appointmentLayout; + // update the view with the visible dates on swiping end. + if (view.visibleDates != visibleDates) { + view = _CalendarView( widget.calendar, widget.view, + visibleDates, widget.width, widget.height, - visibleDatesCount, - _allDayHeight, - widget.isMobilePlatform); - double timeToPosition = 0; - final bool isTimelineView = _isTimelineView(widget.view); - if (!isTimelineView) { - timeToPosition = - _timeToPosition(widget.calendar, date, _timeIntervalHeight); - } else { - for (int i = 0; i < visibleDatesCount; i++) { - if (!isSameDate(date, widget.visibleDates[i])) { - continue; + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + _getRegions(visibleDates), + _getDatesWithInVisibleDateRange(widget.blackoutDates, visibleDates), + _focusNode, + widget.removePicker, + widget.calendar.allowViewNavigation, + widget.controller, + widget.resourcePanelScrollController, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.minDate, + widget.maxDate, + widget.localizations, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: viewKey, + ); + + _children[index] = view; + } // check and update the visible appointments in the view + else if (!CalendarViewHelper.isCollectionEqual( + appointmentLayout.visibleAppointments.value, + _updateCalendarStateDetails.visibleAppointments)) { + if (widget.view != CalendarView.month && + !CalendarViewHelper.isTimelineView(widget.view)) { + view = _CalendarView( + widget.calendar, + widget.view, + visibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + view.regions, + view.blackoutDates, + _focusNode, + widget.removePicker, + widget.calendar.allowViewNavigation, + widget.controller, + widget.resourcePanelScrollController, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.minDate, + widget.maxDate, + widget.localizations, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: viewKey, + ); + _children[index] = view; + } else if (view.visibleDates == _currentViewVisibleDates) { + /// Remove the appointment selection when the selected + /// appointment removed. + if (viewKey.currentState!._selectionPainter != null && + viewKey.currentState!._selectionPainter!.appointmentView != null && + (!_updateCalendarStateDetails.visibleAppointments.contains(viewKey + .currentState! + ._selectionPainter! + .appointmentView! + .appointment))) { + viewKey.currentState!._selectionPainter!.appointmentView = null; + viewKey.currentState!._selectionPainter!.repaintNotifier.value = + !viewKey.currentState!._selectionPainter!.repaintNotifier.value; } - if (widget.view == CalendarView.timelineMonth) { - timeToPosition = _timeIntervalHeight * i; - } else { - timeToPosition = (_getSingleViewWidthForTimeLineView(this) * i) + - _timeToPosition(widget.calendar, date, _timeIntervalHeight); + appointmentLayout.visibleAppointments.value = + _updateCalendarStateDetails.visibleAppointments; + if (widget.view == CalendarView.month && + widget.calendar.monthCellBuilder != null) { + viewKey.currentState!._monthView.visibleAppointmentNotifier.value = + _updateCalendarStateDetails.visibleAppointments; } - - break; } } - - double maxScrollPosition = 0; - if (!isTimelineView) { - final double scrollViewHeight = widget.height - - _allDayHeight - - _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); - final double scrollViewContentHeight = _getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view) * - _timeIntervalHeight; - maxScrollPosition = scrollViewContentHeight - scrollViewHeight; - } else { - final double scrollViewContentWidth = _getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view) * - _timeIntervalHeight * - visibleDatesCount; - maxScrollPosition = scrollViewContentWidth - widget.width; + // When calendar state changed the state doesn't pass to the child of + // custom scroll view, hence to update the calendar state to the child we + // have added this. + else if (view.calendar != widget.calendar) { + /// Update the calendar view when calendar properties like blackout dates + /// dynamically changed. + view = _CalendarView( + widget.calendar, + widget.view, + visibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + view.regions, + view.blackoutDates, + _focusNode, + widget.removePicker, + widget.calendar.allowViewNavigation, + widget.controller, + widget.resourcePanelScrollController, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.minDate, + widget.maxDate, + widget.localizations, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: viewKey, + ); + + _children[index] = view; } - return maxScrollPosition > timeToPosition - ? timeToPosition - : maxScrollPosition; + return view; } - void _expandOrCollapseAllDay() { - _isExpanded = !_isExpanded; - if (_isExpanded) { - _expanderAnimationController.forward(); - } else { - _expanderAnimationController.reverse(); - } + void animationListener() { + setState(() { + _position = _animation.value; + }); } - /// Update the time slot view scroll based on time ruler view scroll in - /// timeslot views. - void _timeRulerListener() { - if (!_isTimelineView(widget.view)) { - return; + /// Check both the region collection as equal or not. + bool _isTimeRegionsEquals( + List? regions1, List? regions2) { + /// Check both instance as equal + /// eg., if both are null then its equal. + if (regions1 == regions2) { + return true; } - if (_timelineRulerController.offset != _scrollController.offset) { - _scrollController.jumpTo(_timelineRulerController.offset); + /// Check the collections are not equal based on its length + if ((regions1 != null && regions2 == null) || + (regions1 == null && regions2 != null) || + (regions1!.length != regions2!.length)) { + return false; } - } - void _scrollListener() { - if (_updateCalendarStateDetails._currentViewVisibleDates == - widget.visibleDates) { - widget.removePicker(); + /// Check each of the region is equal to another or not. + for (int i = 0; i < regions1.length; i++) { + if (regions1[i] != regions2[i]) { + return false; + } } - if (_isTimelineView(widget.view)) { - widget.getCalendarState(_updateCalendarStateDetails); - if (_timelineViewHeader != null && - widget.view != CalendarView.timelineMonth) { - _timelineViewHeaderNotifier.value = !_timelineViewHeaderNotifier.value; + return true; + } + + /// Updates the selected date programmatically, when resource enables, in + /// this scenario the first resource cell will be selected + void _selectResourceProgrammatically() { + if (!CalendarViewHelper.isTimelineView(widget.view)) { + return; + } + + for (int i = 0; i < _children.length; i++) { + final GlobalKey<_CalendarViewState> viewKey = + // ignore: avoid_as + _children[i].key as GlobalKey<_CalendarViewState>; + if (CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + viewKey.currentState!._selectedResourceIndex = 0; + viewKey.currentState!._selectionPainter!.selectedResourceIndex = 0; + } else { + viewKey.currentState!._selectedResourceIndex = -1; + viewKey.currentState!._selectionPainter!.selectedResourceIndex = -1; } + } + } - if (_timelineRulerController.offset != _scrollController.offset) { - _timelineRulerController.jumpTo(_scrollController.offset); + /// Updates the selection, when the resource enabled and the resource + /// collection modified, moves or removes the selection based on the action + /// performed. + void _updateSelectedResourceIndex() { + for (int i = 0; i < _children.length; i++) { + final GlobalKey<_CalendarViewState> viewKey = + // ignore: avoid_as + _children[i].key as GlobalKey<_CalendarViewState>; + final int selectedResourceIndex = + viewKey.currentState!._selectedResourceIndex; + if (selectedResourceIndex != -1) { + final Object selectedResourceId = + widget.resourceCollection![selectedResourceIndex].id; + final int newIndex = CalendarViewHelper.getResourceIndex( + widget.calendar.dataSource?.resources, selectedResourceId); + viewKey.currentState!._selectedResourceIndex = newIndex; } + } + } - _timelineViewHeaderScrollController.jumpTo(_scrollController.offset); + void _updateSelection() { + widget.getCalendarState(_updateCalendarStateDetails); + final _CalendarViewState previousViewState = _previousViewKey.currentState!; + final _CalendarViewState currentViewState = _currentViewKey.currentState!; + final _CalendarViewState nextViewState = _nextViewKey.currentState!; + previousViewState._allDaySelectionNotifier.value = null; + currentViewState._allDaySelectionNotifier.value = null; + nextViewState._allDaySelectionNotifier.value = null; + previousViewState._selectionPainter!.selectedDate = + _updateCalendarStateDetails.selectedDate; + nextViewState._selectionPainter!.selectedDate = + _updateCalendarStateDetails.selectedDate; + currentViewState._selectionPainter!.selectedDate = + _updateCalendarStateDetails.selectedDate; + previousViewState._selectionPainter!.appointmentView = null; + nextViewState._selectionPainter!.appointmentView = null; + currentViewState._selectionPainter!.appointmentView = null; + previousViewState._selectionNotifier.value = + !previousViewState._selectionNotifier.value; + currentViewState._selectionNotifier.value = + !currentViewState._selectionNotifier.value; + nextViewState._selectionNotifier.value = + !nextViewState._selectionNotifier.value; + } + + void _updateMoveToDate() { + if (widget.view == CalendarView.month) { + return; } + + SchedulerBinding.instance!.addPostFrameCallback((_) { + if (_currentChildIndex == 0) { + _previousViewKey.currentState!._scrollToPosition(); + } else if (_currentChildIndex == 1) { + _currentViewKey.currentState!._scrollToPosition(); + } else if (_currentChildIndex == 2) { + _nextViewKey.currentState!._scrollToPosition(); + } + }); } - void _updateTimeSlotView(_CalendarView oldWidget) { - _animationController ??= AnimationController( - duration: const Duration(milliseconds: 200), vsync: this); - _heightAnimation ??= - CurveTween(curve: Curves.easeIn).animate(_animationController) - ..addListener(() { - setState(() { - /*Animates the all day panel when it's expanding or - collapsing*/ - }); - }); + /// Updates the current view visible dates for calendar in the swiping end + void _updateCurrentViewVisibleDates({bool isNextView = false}) { + if (isNextView) { + if (_currentChildIndex == 0) { + _currentViewVisibleDates = _visibleDates; + } else if (_currentChildIndex == 1) { + _currentViewVisibleDates = _nextViewVisibleDates; + } else { + _currentViewVisibleDates = _previousViewVisibleDates; + } + } else { + if (_currentChildIndex == 0) { + _currentViewVisibleDates = _nextViewVisibleDates; + } else if (_currentChildIndex == 1) { + _currentViewVisibleDates = _previousViewVisibleDates; + } else { + _currentViewVisibleDates = _visibleDates; + } + } - _updateCalendarStateDetails ??= _UpdateCalendarStateDetails(); - _expanderAnimationController ??= AnimationController( - duration: const Duration(milliseconds: 100), vsync: this); - _allDayExpanderAnimation ??= - CurveTween(curve: Curves.easeIn).animate(_expanderAnimationController) - ..addListener(() { - setState(() { - /*Animates the all day panel when it's expanding or - collapsing*/ - }); - }); + _updateCalendarStateDetails.currentViewVisibleDates = + _currentViewVisibleDates; + if (widget.view == CalendarView.month && + widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { + final DateTime currentMonthDate = + _currentViewVisibleDates[_currentViewVisibleDates.length ~/ 2]; + _updateCalendarStateDetails.currentDate = + DateTime(currentMonthDate.year, currentMonthDate.month, 01); + } else { + _updateCalendarStateDetails.currentDate = _currentViewVisibleDates[0]; + } - if (widget.view != CalendarView.day && _allDayHeight == 0) { - if (_animationController.status == AnimationStatus.completed) { - _animationController.reset(); + widget.updateCalendarState(_updateCalendarStateDetails); + } + + void _updateNextView() { + if (!_animationController.isCompleted) { + return; + } + + _updateSelection(); + _updateNextViewVisibleDates(); + + /// Updates the all day panel of the view, when the all day panel expanded + /// and the view swiped with the expanded all day panel, and when we swipe + /// back to the view or swipes three times will render the all day panel as + /// expanded, to collapse the all day panel in day, week and work week view, + /// we have added this condition and called the method. + if (widget.view != CalendarView.month && + !CalendarViewHelper.isTimelineView(widget.view)) { + _updateAllDayPanel(); + } + + setState(() { + /// Update the custom scroll layout current child index when the + /// animation ends. + if (_currentChildIndex == 0) { + _currentChildIndex = 1; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 0; } + }); + + _resetPosition(); + _updateAppointmentPainter(); + } - _animationController.forward(); + void _updatePreviousView() { + if (!_animationController.isCompleted) { + return; + } + + _updateSelection(); + _updatePreviousViewVisibleDates(); + + /// Updates the all day panel of the view, when the all day panel expanded + /// and the view swiped with the expanded all day panel, and when we swipe + /// back to the view or swipes three times will render the all day panel as + /// expanded, to collapse the all day panel in day, week and work week view, + /// we have added this condition and called the method. + if (widget.view != CalendarView.month && + !CalendarViewHelper.isTimelineView(widget.view)) { + _updateAllDayPanel(); } + + setState(() { + /// Update the custom scroll layout current child index when the + /// animation ends. + if (_currentChildIndex == 0) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 0; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 1; + } + }); + + _resetPosition(); + _updateAppointmentPainter(); } - void _updateHorizontalLineCount(_CalendarView oldWidget) { - if (widget.calendar.timeSlotViewSettings.startHour != - oldWidget.calendar.timeSlotViewSettings.startHour || - widget.calendar.timeSlotViewSettings.endHour != - oldWidget.calendar.timeSlotViewSettings.endHour || - _getTimeInterval(widget.calendar.timeSlotViewSettings) != - _getTimeInterval(oldWidget.calendar.timeSlotViewSettings) || - oldWidget.view == CalendarView.month || - oldWidget.view == CalendarView.timelineMonth || - oldWidget.view != CalendarView.timelineMonth && - widget.view == CalendarView.timelineMonth) { - _horizontalLinesCount = _getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view); + void _moveToNextViewWithAnimation() { + if (!widget.isMobilePlatform) { + _moveToNextWebViewWithAnimation(); + return; + } + + if (!DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + return; + } + + // Resets the controller to forward it again, the animation will forward + // only from the dismissed state + if (_animationController.isCompleted || _animationController.isDismissed) { + _animationController.reset(); } else { - _horizontalLinesCount = _horizontalLinesCount ?? - _getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view); + return; + } + + // Handled for time line view, to move the previous and next view to it's + // start and end position accordingly + if (CalendarViewHelper.isTimelineView(widget.view)) { + _positionTimelineView(isScrolledToEnd: false); + } + + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical && + widget.view == CalendarView.month) { + // update the bottom to top swiping + _tween.begin = 0; + _tween.end = -widget.height; + } else { + // update the right to left swiping + _tween.begin = 0; + _tween.end = -widget.width; } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController + .forward() + .then((dynamic value) => _updateNextView()); + + /// updates the current view visible dates when the view swiped + _updateCurrentViewVisibleDates(isNextView: true); } - void _updateTimelineViews(_CalendarView oldWidget) { - _timelineRulerController = _timelineRulerController ?? - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_timeRulerListener); + void _moveToPreviousViewWithAnimation() { + if (!widget.isMobilePlatform) { + _moveToPreviousWebViewWithAnimation(); + return; + } - _timelineViewAnimationController = _timelineViewAnimationController ?? - AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - animationBehavior: AnimationBehavior.normal); - _timelineViewTween = - _timelineViewTween ?? Tween(begin: 0.0, end: 0.1); + if (!DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + return; + } - _timelineViewAnimation = _timelineViewAnimation ?? - _timelineViewTween.animate(_timelineViewAnimationController) - ..addListener(_scrollAnimationListener); + // Resets the controller to backward it again, the animation will backward + // only from the dismissed state + if (_animationController.isCompleted || _animationController.isDismissed) { + _animationController.reset(); + } else { + return; + } - _timelineViewHeaderScrollController = _timelineViewHeaderScrollController ?? - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); - _timelineViewVerticalScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); - _timelineViewVerticalScrollController.addListener(_updateResourceScroll); - widget.resourcePanelScrollController - ?.addListener(_updateResourcePanelScroll); + // Handled for time line view, to move the previous and next view to it's + // start and end position accordingly + if (CalendarViewHelper.isTimelineView(widget.view)) { + _positionTimelineView(isScrolledToEnd: false); + } + + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical && + widget.view == CalendarView.month) { + // update the top to bottom swiping + _tween.begin = 0; + _tween.end = widget.height; + } else { + // update the left to right swiping + _tween.begin = 0; + _tween.end = widget.width; + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController + .forward() + .then((dynamic value) => _updatePreviousView()); + + /// updates the current view visible dates when the view swiped. + _updateCurrentViewVisibleDates(); } - void _getPainterProperties(_UpdateCalendarStateDetails details) { - widget.getCalendarState(_updateCalendarStateDetails); - details._allDayAppointmentViewCollection = - _updateCalendarStateDetails._allDayAppointmentViewCollection; - details._currentViewVisibleDates = - _updateCalendarStateDetails._currentViewVisibleDates; - details._visibleAppointments = - _updateCalendarStateDetails._visibleAppointments; - details._selectedDate = _updateCalendarStateDetails._selectedDate; + void _moveToPreviousWebViewWithAnimation() { + if (!DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + return; + } + + // Resets the controller to backward it again, the animation will backward + // only from the dismissed state + if (widget.fadeInController!.isCompleted || + widget.fadeInController!.isDismissed) { + widget.fadeInController!.reset(); + } else { + return; + } + + // Handled for time line view, to move the previous and next view to it's + // start and end position accordingly + if (CalendarViewHelper.isTimelineView(widget.view)) { + _positionTimelineView(isScrolledToEnd: false); + } else if (!CalendarViewHelper.isTimelineView(widget.view) && + widget.view != CalendarView.month) { + _updateDayViewScrollPosition(); + } + + /// updates the current view visible dates when the view swiped. + _updateCurrentViewVisibleDates(); + _position = 0; + widget.fadeInController!.forward(); + _updateSelection(); + _updatePreviousViewVisibleDates(); + + /// Updates the all day panel of the view, when the all day panel expanded + /// and the view swiped with the expanded all day panel, and when we swipe + /// back to the view or swipes three times will render the all day panel as + /// expanded, to collapse the all day panel in day, week and work week view, + /// we have added this condition and called the method. + if (widget.view != CalendarView.month && + !CalendarViewHelper.isTimelineView(widget.view)) { + _updateAllDayPanel(); + } + + if (_currentChildIndex == 0) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 0; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 1; + } + + _updateAppointmentPainter(); } - Widget _addAllDayAppointmentPanel( - SfCalendarThemeData calendarTheme, bool isCurrentView) { - final Color borderColor = - widget.calendar.cellBorderColor ?? calendarTheme.cellBorderColor; - final Widget shadowView = Divider( - height: 1, - thickness: 1, - color: borderColor.withOpacity(borderColor.opacity * 0.5), - ); + void _moveToNextWebViewWithAnimation() { + if (!DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + return; + } - final double timeLabelWidth = _getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); - double topPosition = - _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); - if (widget.view == CalendarView.day) { - topPosition = _allDayHeight; + // Resets the controller to forward it again, the animation will forward + // only from the dismissed state + if (widget.fadeInController!.isCompleted || + widget.fadeInController!.isDismissed) { + widget.fadeInController!.reset(); + } else { + return; } - if (_allDayHeight == 0 || - (widget.view != CalendarView.day && - widget.visibleDates != - _updateCalendarStateDetails._currentViewVisibleDates)) { - return Positioned( - left: 0, right: 0, top: topPosition, height: 1, child: shadowView); + // Handled for time line view, to move the previous and next view to it's + // start and end position accordingly + if (CalendarViewHelper.isTimelineView(widget.view)) { + _positionTimelineView(isScrolledToEnd: false); + } else if (!CalendarViewHelper.isTimelineView(widget.view) && + widget.view != CalendarView.month) { + _updateDayViewScrollPosition(); } - if (widget.view == CalendarView.day) { - //// Default minimum view header width in day view as 50,so set 50 - //// when view header width less than 50. - topPosition = 0; + /// updates the current view visible dates when the view swiped + _updateCurrentViewVisibleDates(isNextView: true); + + _position = 0; + widget.fadeInController!.forward(); + _updateSelection(); + _updateNextViewVisibleDates(); + + /// Updates the all day panel of the view, when the all day panel expanded + /// and the view swiped with the expanded all day panel, and when we swipe + /// back to the view or swipes three times will render the all day panel as + /// expanded, to collapse the all day panel in day, week and work week view, + /// we have added this condition and called the method. + if (widget.view != CalendarView.month && + !CalendarViewHelper.isTimelineView(widget.view)) { + _updateAllDayPanel(); } - double panelHeight = isCurrentView - ? _updateCalendarStateDetails._allDayPanelHeight - _allDayHeight - : 0; - if (panelHeight < 0) { - panelHeight = 0; + if (_currentChildIndex == 0) { + _currentChildIndex = 1; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 0; } - final double allDayExpanderHeight = - _allDayHeight + (panelHeight * _allDayExpanderAnimation.value); - return Positioned( - left: 0, - top: topPosition, - right: 0, - height: allDayExpanderHeight, - child: Stack( - children: [ - Positioned( - left: 0, - top: 0, - right: 0, - height: _isExpanded ? allDayExpanderHeight : _allDayHeight, - child: ListView( - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.all(0.0), - children: [ - _AllDayAppointmentLayout( - widget.calendar, - widget.view, - widget.visibleDates, - widget.visibleDates == - _updateCalendarStateDetails._currentViewVisibleDates - ? _updateCalendarStateDetails._visibleAppointments - : null, - timeLabelWidth, - allDayExpanderHeight, - panelHeight > 0 && - (_heightAnimation.value == 1 || - widget.view == CalendarView.day), - _allDayExpanderAnimation.value != 0.0 && - _allDayExpanderAnimation.value != 1, - _isRTL, - widget.calendarTheme, - _allDaySelectionNotifier, - _allDayNotifier, - widget.textScaleFactor, - widget.isMobilePlatform, - widget.width, - (widget.view == CalendarView.day && - _updateCalendarStateDetails._allDayPanelHeight < - _allDayHeight) || - !isCurrentView - ? _allDayHeight - : _updateCalendarStateDetails._allDayPanelHeight, - updateCalendarState: (_UpdateCalendarStateDetails details) { - _getPainterProperties(details); - }), - ], - ), - ), - Positioned( - left: 0, - top: allDayExpanderHeight - 1, - right: 0, - height: 1, - child: shadowView), - ], - ), - ); + _updateAppointmentPainter(); } - _AppointmentLayout _addAppointmentPainter(double width, double height, - [double resourceItemHeight]) { - final List visibleAppointments = widget.visibleDates == - _updateCalendarStateDetails._currentViewVisibleDates - ? _updateCalendarStateDetails._visibleAppointments - : null; - return _AppointmentLayout( - widget.calendar, - widget.view, - widget.visibleDates, - ValueNotifier>(visibleAppointments), - _timeIntervalHeight, - widget.calendarTheme, - _isRTL, - _appointmentHoverNotifier, - widget.resourceCollection, - resourceItemHeight, - widget.textScaleFactor, - widget.isMobilePlatform, - width, - height, - _getPainterProperties, - key: _appointmentLayoutKey, - ); + // resets position to zero on the swipe end to avoid the unwanted date updates + void _resetPosition() { + SchedulerBinding.instance!.addPostFrameCallback((_) { + if (_position.abs() == widget.width || _position.abs() == widget.height) { + _position = 0; + } + }); + } + + void _updateScrollPosition() { + SchedulerBinding.instance!.addPostFrameCallback((_) { + if (_previousViewKey.currentState == null || + _currentViewKey.currentState == null || + _nextViewKey.currentState == null || + _previousViewKey.currentState!._scrollController == null || + _currentViewKey.currentState!._scrollController == null || + _nextViewKey.currentState!._scrollController == null || + !_previousViewKey.currentState!._scrollController!.hasClients || + !_currentViewKey.currentState!._scrollController!.hasClients || + !_nextViewKey.currentState!._scrollController!.hasClients) { + return; + } + + _updateDayViewScrollPosition(); + }); + } + + /// Update the current day view view scroll position to other views. + void _updateDayViewScrollPosition() { + double scrolledPosition = 0; + if (_currentChildIndex == 0) { + scrolledPosition = + _previousViewKey.currentState!._scrollController!.offset; + } else if (_currentChildIndex == 1) { + scrolledPosition = + _currentViewKey.currentState!._scrollController!.offset; + } else if (_currentChildIndex == 2) { + scrolledPosition = _nextViewKey.currentState!._scrollController!.offset; + } + + if (_previousViewKey.currentState!._scrollController!.offset != + scrolledPosition && + _previousViewKey + .currentState!._scrollController!.position.maxScrollExtent >= + scrolledPosition) { + _previousViewKey.currentState!._scrollController! + .jumpTo(scrolledPosition); + } + + if (_currentViewKey.currentState!._scrollController!.offset != + scrolledPosition && + _currentViewKey + .currentState!._scrollController!.position.maxScrollExtent >= + scrolledPosition) { + _currentViewKey.currentState!._scrollController!.jumpTo(scrolledPosition); + } + + if (_nextViewKey.currentState!._scrollController!.offset != + scrolledPosition && + _nextViewKey + .currentState!._scrollController!.position.maxScrollExtent >= + scrolledPosition) { + _nextViewKey.currentState!._scrollController!.jumpTo(scrolledPosition); + } + } + + int _getRowOfDate( + List visibleDates, _CalendarViewState currentViewState) { + for (int i = 0; i < visibleDates.length; i++) { + if (isSameDate( + currentViewState._selectionPainter!.selectedDate, visibleDates[i])) { + switch (widget.view) { + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + case CalendarView.schedule: + return -1; + case CalendarView.month: + return i ~/ DateTime.daysPerWeek; + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + return i; + } + } + } + + return -1; + } + + DateTime _updateSelectedDateForRightArrow( + _CalendarView currentView, _CalendarViewState currentViewState) { + DateTime selectedDate; + + /// Condition added to move the view to next view when the selection reaches + /// the last horizontal cell of the view in day, week, workweek, month and + /// timeline month. + if (!CalendarViewHelper.isTimelineView(widget.view)) { + final int visibleDatesCount = currentView.visibleDates.length; + if (isSameDate(currentView.visibleDates[visibleDatesCount - 1], + currentViewState._selectionPainter!.selectedDate)) { + _moveToNextViewWithAnimation(); + } + + selectedDate = AppointmentHelper.addDaysWithTime( + currentViewState._selectionPainter!.selectedDate!, + 1, + currentViewState._selectionPainter!.selectedDate!.hour, + currentViewState._selectionPainter!.selectedDate!.minute, + currentViewState._selectionPainter!.selectedDate!.second); + + /// Move to next view when the new selected date as next month date. + if (widget.view == CalendarView.month && + !CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentView.visibleDates[visibleDatesCount ~/ 2].month, + selectedDate)) { + _moveToNextViewWithAnimation(); + } else if (widget.view == CalendarView.workWeek) { + for (int i = 0; + i < + DateTime.daysPerWeek - + widget.calendar.timeSlotViewSettings.nonWorkingDays.length; + i++) { + if (widget.calendar.timeSlotViewSettings.nonWorkingDays + .contains(selectedDate.weekday)) { + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, 1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + } else { + break; + } + } + } + } else { + final double xPosition = widget.view == CalendarView.timelineMonth + ? 0 + : AppointmentHelper.timeToPosition( + widget.calendar, + currentViewState._selectionPainter!.selectedDate!, + currentViewState._timeIntervalHeight); + final int rowIndex = + _getRowOfDate(currentView.visibleDates, currentViewState); + final double singleChildWidth = + _getSingleViewWidthForTimeLineView(currentViewState); + if ((rowIndex * singleChildWidth) + + xPosition + + currentViewState._timeIntervalHeight >= + currentViewState._scrollController!.offset + widget.width) { + currentViewState._scrollController!.jumpTo( + currentViewState._scrollController!.offset + + currentViewState._timeIntervalHeight); + } + if (widget.view == CalendarView.timelineDay && + currentViewState._selectionPainter!.selectedDate! + .add(widget.calendar.timeSlotViewSettings.timeInterval) + .day != + currentView + .visibleDates[currentView.visibleDates.length - 1].day) { + _moveToNextViewWithAnimation(); + } + + if ((rowIndex * singleChildWidth) + + xPosition + + currentViewState._timeIntervalHeight == + currentViewState._scrollController!.position.maxScrollExtent + + currentViewState._scrollController!.position.viewportDimension) { + _moveToNextViewWithAnimation(); + } + + /// For timeline month view each column represents a single day, and for + /// other timeline views each column represents a given time interval, + /// hence to update the selected date for timeline month we must add a day + /// and for other timeline views we must add the given time interval. + if (widget.view == CalendarView.timelineMonth) { + selectedDate = AppointmentHelper.addDaysWithTime( + currentViewState._selectionPainter!.selectedDate!, + 1, + currentViewState._selectionPainter!.selectedDate!.hour, + currentViewState._selectionPainter!.selectedDate!.minute, + currentViewState._selectionPainter!.selectedDate!.second); + } else { + selectedDate = currentViewState._selectionPainter!.selectedDate! + .add(widget.calendar.timeSlotViewSettings.timeInterval); + } + if (widget.view == CalendarView.timelineWorkWeek) { + for (int i = 0; + i < + DateTime.daysPerWeek - + widget.calendar.timeSlotViewSettings.nonWorkingDays.length; + i++) { + if (widget.calendar.timeSlotViewSettings.nonWorkingDays + .contains(selectedDate.weekday)) { + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, 1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + } else { + break; + } + } + } + } + + return selectedDate; + } + + DateTime _updateSelectedDateForLeftArrow( + _CalendarView currentView, _CalendarViewState currentViewState) { + DateTime selectedDate; + if (!CalendarViewHelper.isTimelineView(widget.view)) { + if (isSameDate(currentViewState.widget.visibleDates[0], + currentViewState._selectionPainter!.selectedDate)) { + _moveToPreviousViewWithAnimation(); + } + selectedDate = AppointmentHelper.addDaysWithTime( + currentViewState._selectionPainter!.selectedDate!, + -1, + currentViewState._selectionPainter!.selectedDate!.hour, + currentViewState._selectionPainter!.selectedDate!.minute, + currentViewState._selectionPainter!.selectedDate!.second); + + /// Move to previous view when the selected date as previous month date. + if (widget.view == CalendarView.month && + !CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentView + .visibleDates[currentView.visibleDates.length ~/ 2].month, + selectedDate)) { + _moveToPreviousViewWithAnimation(); + } else if (widget.view == CalendarView.workWeek) { + for (int i = 0; + i < + DateTime.daysPerWeek - + widget.calendar.timeSlotViewSettings.nonWorkingDays.length; + i++) { + if (widget.calendar.timeSlotViewSettings.nonWorkingDays + .contains(selectedDate.weekday)) { + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, -1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + } else { + break; + } + } + } + } else { + final double xPosition = widget.view == CalendarView.timelineMonth + ? 0 + : AppointmentHelper.timeToPosition( + widget.calendar, + currentViewState._selectionPainter!.selectedDate!, + currentViewState._timeIntervalHeight); + final int rowIndex = + _getRowOfDate(currentView.visibleDates, currentViewState); + final double singleChildWidth = + _getSingleViewWidthForTimeLineView(currentViewState); + + if ((rowIndex * singleChildWidth) + xPosition == 0) { + _moveToPreviousViewWithAnimation(); + } + + if ((rowIndex * singleChildWidth) + xPosition <= + currentViewState._scrollController!.offset) { + currentViewState._scrollController!.jumpTo( + currentViewState._scrollController!.offset - + currentViewState._timeIntervalHeight); + } + + /// For timeline month view each column represents a single day, and for + /// other timeline views each column represents a given time interval, + /// hence to update the selected date for timeline month we must subtract + /// a day and for other timeline views we must subtract the given time + /// interval. + if (widget.view == CalendarView.timelineMonth) { + selectedDate = AppointmentHelper.addDaysWithTime( + currentViewState._selectionPainter!.selectedDate!, + -1, + currentViewState._selectionPainter!.selectedDate!.hour, + currentViewState._selectionPainter!.selectedDate!.minute, + currentViewState._selectionPainter!.selectedDate!.second); + } else { + selectedDate = currentViewState._selectionPainter!.selectedDate! + .subtract(widget.calendar.timeSlotViewSettings.timeInterval); + } + if (widget.view == CalendarView.timelineWorkWeek) { + for (int i = 0; + i < + DateTime.daysPerWeek - + widget.calendar.timeSlotViewSettings.nonWorkingDays.length; + i++) { + if (widget.calendar.timeSlotViewSettings.nonWorkingDays + .contains(selectedDate.weekday)) { + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, -1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + } else { + break; + } + } + } + } + + return selectedDate; + } + + DateTime? _updateSelectedDateForUpArrow( + _CalendarView currentView, _CalendarViewState currentViewState) { + if (widget.view == CalendarView.month) { + final int rowIndex = + _getRowOfDate(currentView.visibleDates, currentViewState); + if (rowIndex == 0) { + return currentViewState._selectionPainter!.selectedDate; + } + + DateTime selectedDate = AppointmentHelper.addDaysWithTime( + currentViewState._selectionPainter!.selectedDate!, + -DateTime.daysPerWeek, + currentViewState._selectionPainter!.selectedDate!.hour, + currentViewState._selectionPainter!.selectedDate!.minute, + currentViewState._selectionPainter!.selectedDate!.second); + + /// Move to month start date when the new selected date as + /// previous month date. + if (!CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentView.visibleDates[currentView.visibleDates.length ~/ 2].month, + selectedDate)) { + selectedDate = AppointmentHelper.getMonthStartDate( + currentViewState._selectionPainter!.selectedDate!); + } + + return selectedDate; + } else if (!CalendarViewHelper.isTimelineView(widget.view)) { + final double yPosition = AppointmentHelper.timeToPosition( + widget.calendar, + currentViewState._selectionPainter!.selectedDate!, + currentViewState._timeIntervalHeight); + if (yPosition == 0) { + return currentViewState._selectionPainter!.selectedDate; + } + if (yPosition <= currentViewState._scrollController!.offset) { + currentViewState._scrollController! + .jumpTo(yPosition - currentViewState._timeIntervalHeight); + } + return currentViewState._selectionPainter!.selectedDate! + .subtract(widget.calendar.timeSlotViewSettings.timeInterval); + } else if (CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + final double resourceItemHeight = + CalendarViewHelper.getResourceItemHeight( + widget.calendar.resourceViewSettings.size, + widget.height, + widget.calendar.resourceViewSettings, + widget.calendar.dataSource!.resources!.length); + + currentViewState._selectedResourceIndex -= 1; + + if (currentViewState._selectedResourceIndex == -1) { + currentViewState._selectedResourceIndex = 0; + return currentViewState._selectionPainter!.selectedDate; + } + + if (currentViewState._selectedResourceIndex * resourceItemHeight < + currentViewState._timelineViewVerticalScrollController!.offset) { + double scrollPosition = + currentViewState._timelineViewVerticalScrollController!.offset - + resourceItemHeight; + scrollPosition = scrollPosition > 0 ? scrollPosition : 0; + currentViewState._timelineViewVerticalScrollController! + .jumpTo(scrollPosition); + } + + return currentViewState._selectionPainter!.selectedDate; + } + + return null; + } + + DateTime? _updateSelectedDateForDownArrow( + _CalendarView currentView, _CalendarViewState currentViewState) { + if (widget.view == CalendarView.month) { + final int rowIndex = + _getRowOfDate(currentView.visibleDates, currentViewState); + if (rowIndex == + widget.calendar.monthViewSettings.numberOfWeeksInView - 1) { + return currentViewState._selectionPainter!.selectedDate!; + } + + DateTime selectedDate = AppointmentHelper.addDaysWithTime( + currentViewState._selectionPainter!.selectedDate!, + DateTime.daysPerWeek, + currentViewState._selectionPainter!.selectedDate!.hour, + currentViewState._selectionPainter!.selectedDate!.minute, + currentViewState._selectionPainter!.selectedDate!.second); + + /// Move to month end date when the new selected date as next month date. + if (!CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentView.visibleDates[currentView.visibleDates.length ~/ 2].month, + selectedDate)) { + selectedDate = AppointmentHelper.getMonthEndDate( + currentViewState._selectionPainter!.selectedDate!); + } + return selectedDate; + } else if (!CalendarViewHelper.isTimelineView(widget.view)) { + final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + final double yPosition = AppointmentHelper.timeToPosition( + widget.calendar, + currentViewState._selectionPainter!.selectedDate!, + currentViewState._timeIntervalHeight); + + if (currentViewState._selectionPainter!.selectedDate! + .add(widget.calendar.timeSlotViewSettings.timeInterval) + .day != + currentViewState._selectionPainter!.selectedDate!.day) { + return currentViewState._selectionPainter!.selectedDate!; + } + + if (yPosition + + currentViewState._timeIntervalHeight + + widget.calendar.headerHeight + + viewHeaderHeight >= + currentViewState._scrollController!.offset + widget.height && + currentViewState._scrollController!.offset + + currentViewState + ._scrollController!.position.viewportDimension != + currentViewState._scrollController!.position.maxScrollExtent) { + currentViewState._scrollController!.jumpTo( + currentViewState._scrollController!.offset + + currentViewState._timeIntervalHeight); + } + return currentViewState._selectionPainter!.selectedDate! + .add(widget.calendar.timeSlotViewSettings.timeInterval); + } else if (CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + final double resourceItemHeight = + CalendarViewHelper.getResourceItemHeight( + widget.calendar.resourceViewSettings.size, + widget.height, + widget.calendar.resourceViewSettings, + widget.calendar.dataSource!.resources!.length); + if (currentViewState._selectedResourceIndex == + widget.calendar.dataSource!.resources!.length - 1 || + currentViewState._selectedResourceIndex == -1) { + return currentViewState._selectionPainter!.selectedDate!; + } + + currentViewState._selectedResourceIndex += 1; + + if (currentViewState._selectedResourceIndex * resourceItemHeight >= + currentViewState._timelineViewVerticalScrollController!.offset + + currentViewState._timelineViewVerticalScrollController!.position + .viewportDimension) { + double scrollPosition = + currentViewState._timelineViewVerticalScrollController!.offset + + resourceItemHeight; + scrollPosition = scrollPosition > + currentViewState._timelineViewVerticalScrollController!.position + .maxScrollExtent + ? currentViewState + ._timelineViewVerticalScrollController!.position.maxScrollExtent + : scrollPosition; + currentViewState._timelineViewVerticalScrollController! + .jumpTo(scrollPosition); + } + + return currentViewState._selectionPainter!.selectedDate!; + } + + return null; + } + + DateTime? _updateSelectedDate(RawKeyEvent event, + _CalendarViewState currentViewState, _CalendarView currentView) { + if (event.logicalKey == LogicalKeyboardKey.arrowRight) { + return _updateSelectedDateForRightArrow(currentView, currentViewState); + } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { + return _updateSelectedDateForLeftArrow(currentView, currentViewState); + } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + return _updateSelectedDateForUpArrow(currentView, currentViewState); + } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + return _updateSelectedDateForDownArrow(currentView, currentViewState); + } + + return null; + } + + /// Checks the selected date is enabled or not. + bool _isSelectedDateEnabled(DateTime date, int resourceIndex) { + final bool isMonthView = (widget.view == CalendarView.month || + widget.view == CalendarView.timelineMonth); + final int timeInterval = CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings); + if ((isMonthView && + !isDateWithInDateRange( + widget.calendar.minDate, widget.calendar.maxDate, date)) || + (!isMonthView && + !CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + date, + timeInterval))) { + return false; + } + + final List blackoutDates = []; + if (_currentView.blackoutDates != null) { + blackoutDates.addAll(_currentView.blackoutDates!); + } + if (_previousView.blackoutDates != null) { + blackoutDates.addAll(_previousView.blackoutDates!); + } + if (_nextView.blackoutDates != null) { + blackoutDates.addAll(_nextView.blackoutDates!); + } + + if (isMonthView && + CalendarViewHelper.isDateInDateCollection(blackoutDates, date)) { + return false; + } else if (!isMonthView) { + final List regions = []; + if (_currentView.regions != null) { + regions.addAll(_currentView.regions!); + } + if (_previousView.regions != null) { + regions.addAll(_previousView.regions!); + } + if (_nextView.regions != null) { + regions.addAll(_nextView.regions!); + } + + for (int i = 0; i < regions.length; i++) { + final CalendarTimeRegion region = regions[i]; + if (region.enablePointerInteraction || + (region.actualStartTime.isAfter(date) && + !CalendarViewHelper.isSameTimeSlot( + region.actualStartTime, date)) || + region.actualEndTime.isBefore(date) || + CalendarViewHelper.isSameTimeSlot(region.actualEndTime, date)) { + continue; + } + + /// Condition added ensure that the region is disabled only on the + /// specified resource slot, for other resources it must be enabled. + if (resourceIndex != -1 && + region.resourceIds != null && + region.resourceIds!.isNotEmpty && + !region.resourceIds! + .contains(widget.resourceCollection![resourceIndex].id)) { + continue; + } + + return false; + } + } + + return true; + } + + void _onKeyDown(RawKeyEvent event) { + if (event.runtimeType != RawKeyDownEvent) { + return; + } + + widget.removePicker(); + _CalendarViewState currentVisibleViewState; + _CalendarView currentVisibleView; + final bool isResourcesEnabled = CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view); + if (_currentChildIndex == 0) { + currentVisibleViewState = _previousViewKey.currentState!; + currentVisibleView = _previousView; + } else if (_currentChildIndex == 1) { + currentVisibleViewState = _currentViewKey.currentState!; + currentVisibleView = _currentView; + } else { + currentVisibleViewState = _nextViewKey.currentState!; + currentVisibleView = _nextView; + } + + final int previousResourceIndex = isResourcesEnabled + ? currentVisibleViewState._selectedResourceIndex + : -1; + if (currentVisibleViewState._selectionPainter!.selectedDate != null && + isDateWithInDateRange( + currentVisibleViewState.widget.visibleDates[0], + currentVisibleViewState.widget.visibleDates[ + currentVisibleViewState.widget.visibleDates.length - 1], + currentVisibleViewState._selectionPainter!.selectedDate)) { + final DateTime? selectedDate = _updateSelectedDate( + event, currentVisibleViewState, currentVisibleView); + + final int resourceIndex = isResourcesEnabled + ? currentVisibleViewState._selectedResourceIndex + : -1; + if (selectedDate == null) { + return; + } + + if (!_isSelectedDateEnabled(selectedDate, resourceIndex)) { + currentVisibleViewState._selectedResourceIndex = previousResourceIndex; + return; + } + + if (widget.view == CalendarView.month) { + widget.agendaSelectedDate.value = selectedDate; + } + + _updateCalendarStateDetails.selectedDate = selectedDate; + if (widget.calendar.onSelectionChanged != null && + (!CalendarViewHelper.isSameTimeSlot( + currentVisibleViewState._selectionPainter!.selectedDate, + selectedDate) || + (isResourcesEnabled && + currentVisibleViewState + ._selectionPainter!.selectedResourceIndex != + currentVisibleViewState._selectedResourceIndex))) { + CalendarViewHelper.raiseCalendarSelectionChangedCallback( + widget.calendar, + selectedDate, + isResourcesEnabled + ? widget.resourceCollection![ + currentVisibleViewState._selectedResourceIndex] + : null); + } + currentVisibleViewState._selectionPainter!.selectedDate = selectedDate; + currentVisibleViewState._selectionPainter!.appointmentView = null; + currentVisibleViewState._selectionPainter!.selectedResourceIndex = + currentVisibleViewState._selectedResourceIndex; + currentVisibleViewState._selectionNotifier.value = + !currentVisibleViewState._selectionNotifier.value; + + widget.updateCalendarState(_updateCalendarStateDetails); + } + } + + void _positionTimelineView({bool isScrolledToEnd = true}) { + final _CalendarViewState previousViewState = _previousViewKey.currentState!; + final _CalendarViewState currentViewState = _currentViewKey.currentState!; + final _CalendarViewState nextViewState = _nextViewKey.currentState!; + if (widget.isRTL) { + if (_currentChildIndex == 0) { + currentViewState._scrollController!.jumpTo(isScrolledToEnd + ? currentViewState._scrollController!.position.maxScrollExtent + : 0); + nextViewState._scrollController!.jumpTo(0); + } else if (_currentChildIndex == 1) { + nextViewState._scrollController!.jumpTo(isScrolledToEnd + ? nextViewState._scrollController!.position.maxScrollExtent + : 0); + previousViewState._scrollController!.jumpTo(0); + } else if (_currentChildIndex == 2) { + previousViewState._scrollController!.jumpTo(isScrolledToEnd + ? previousViewState._scrollController!.position.maxScrollExtent + : 0); + currentViewState._scrollController!.jumpTo(0); + } + } else { + if (_currentChildIndex == 0) { + nextViewState._scrollController!.jumpTo(isScrolledToEnd + ? nextViewState._scrollController!.position.maxScrollExtent + : 0); + currentViewState._scrollController!.jumpTo(0); + } else if (_currentChildIndex == 1) { + previousViewState._scrollController!.jumpTo(isScrolledToEnd + ? previousViewState._scrollController!.position.maxScrollExtent + : 0); + nextViewState._scrollController!.jumpTo(0); + } else if (_currentChildIndex == 2) { + currentViewState._scrollController!.jumpTo(isScrolledToEnd + ? currentViewState._scrollController!.position.maxScrollExtent + : 0); + previousViewState._scrollController!.jumpTo(0); + } + } + } + + void _onHorizontalStart(DragStartDetails dragStartDetails) { + switch (widget.calendar.viewNavigationMode) { + case ViewNavigationMode.none: + return; + case ViewNavigationMode.snap: + widget.removePicker(); + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.horizontal || + widget.view != CalendarView.month) { + _scrollStartPosition = dragStartDetails.globalPosition.dx; + } + + // Handled for time line view, to move the previous and + // next view to it's start and end position accordingly + if (CalendarViewHelper.isTimelineView(widget.view)) { + _positionTimelineView(); + } + } + } + + void _onHorizontalUpdate(DragUpdateDetails dragUpdateDetails) { + switch (widget.calendar.viewNavigationMode) { + case ViewNavigationMode.none: + return; + case ViewNavigationMode.snap: + widget.removePicker(); + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.horizontal || + widget.view != CalendarView.month) { + final double difference = + dragUpdateDetails.globalPosition.dx - _scrollStartPosition; + if (difference < 0 && + !DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + _position = 0; + return; + } else if (difference > 0 && + !DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + _position = 0; + return; + } + _position = difference; + _clearSelection(); + setState(() { + /* Updates the widget navigated distance and moves the widget + in the custom scroll view */ + }); + } + } + } + + void _onHorizontalEnd(DragEndDetails dragEndDetails) { + switch (widget.calendar.viewNavigationMode) { + case ViewNavigationMode.none: + return; + case ViewNavigationMode.snap: + widget.removePicker(); + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.horizontal || + widget.view != CalendarView.month) { + // condition to check and update the right to left swiping + if (-_position >= widget.width / 2) { + _tween.begin = _position; + _tween.end = -widget.width; + + // Resets the controller to forward it again, + // the animation will forward only from the dismissed state + if (_animationController.isCompleted && _position != _tween.end) { + _animationController.reset(); + } + + _animationController + .forward() + .then((dynamic value) => _updateNextView()); + + /// updates the current view visible dates when the view swiped in + /// right to left direction + _updateCurrentViewVisibleDates(isNextView: true); + } + // fling the view from right to left + else if (-dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { + if (!DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + _position = 0; + setState(() { + /* Completes the swiping and rearrange the children position + in the custom scroll view */ + }); + return; + } + + _tween.begin = _position; + _tween.end = -widget.width; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted && _position != _tween.end) { + _animationController.reset(); + } + + _animationController + .fling( + velocity: 5.0, animationBehavior: AnimationBehavior.normal) + .then((dynamic value) => _updateNextView()); + + /// updates the current view visible dates when fling the view in + /// right to left direction + _updateCurrentViewVisibleDates(isNextView: true); + } + // condition to check and update the left to right swiping + else if (_position >= widget.width / 2) { + _tween.begin = _position; + _tween.end = widget.width; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } + + _animationController + .forward() + .then((dynamic value) => _updatePreviousView()); + + /// updates the current view visible dates when the view swiped in + /// left to right direction + _updateCurrentViewVisibleDates(); + } + // fling the view from left to right + else if (dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { + if (!DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + _position = 0; + setState(() { + /* Completes the swiping and rearrange the children position + in the custom scroll view */ + }); + return; + } + + _tween.begin = _position; + _tween.end = widget.width; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted && _position != _tween.end) { + _animationController.reset(); + } + + _animationController + .fling( + velocity: 5.0, animationBehavior: AnimationBehavior.normal) + .then((dynamic value) => _updatePreviousView()); + + /// updates the current view visible dates when fling the view in + /// left to right direction + _updateCurrentViewVisibleDates(); + } + // condition to check and revert the right to left swiping + else if (_position.abs() <= widget.width / 2) { + _tween.begin = _position; + _tween.end = 0.0; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted && _position != _tween.end) { + _animationController.reset(); + } + + _animationController.forward(); + } + } + } + } + + void _onVerticalStart(DragStartDetails dragStartDetails) { + switch (widget.calendar.viewNavigationMode) { + case ViewNavigationMode.none: + return; + case ViewNavigationMode.snap: + widget.removePicker(); + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical && + !CalendarViewHelper.isTimelineView(widget.view)) { + _scrollStartPosition = dragStartDetails.globalPosition.dy; + } + } + } + + void _onVerticalUpdate(DragUpdateDetails dragUpdateDetails) { + switch (widget.calendar.viewNavigationMode) { + case ViewNavigationMode.none: + return; + case ViewNavigationMode.snap: + widget.removePicker(); + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical && + !CalendarViewHelper.isTimelineView(widget.view)) { + final double difference = + dragUpdateDetails.globalPosition.dy - _scrollStartPosition; + if (difference < 0 && + !DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays)) { + _position = 0; + return; + } else if (difference > 0 && + !DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays)) { + _position = 0; + return; + } + _position = difference; + setState(() { + /* Updates the widget navigated distance and moves the widget + in the custom scroll view */ + }); + } + } + } + + void _onVerticalEnd(DragEndDetails dragEndDetails) { + switch (widget.calendar.viewNavigationMode) { + case ViewNavigationMode.none: + return; + case ViewNavigationMode.snap: + widget.removePicker(); + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical && + !CalendarViewHelper.isTimelineView(widget.view)) { + // condition to check and update the bottom to top swiping + if (-_position >= widget.height / 2) { + _tween.begin = _position; + _tween.end = -widget.height; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } + + _animationController + .forward() + .then((dynamic value) => _updateNextView()); + + /// updates the current view visible dates when the view swiped in + /// bottom to top direction + _updateCurrentViewVisibleDates(isNextView: true); + } + // fling the view to bottom to top + else if (-dragEndDetails.velocity.pixelsPerSecond.dy > + widget.height) { + if (!DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays)) { + _position = 0; + setState(() { + /* Completes the swiping and rearrange the children position in + the custom scroll view */ + }); + return; + } + + _tween.begin = _position; + _tween.end = -widget.height; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } + + _animationController + .fling( + velocity: 5.0, animationBehavior: AnimationBehavior.normal) + .then((dynamic value) => _updateNextView()); + + /// updates the current view visible dates when fling the view in + /// bottom to top direction + _updateCurrentViewVisibleDates(isNextView: true); + } + // condition to check and update the top to bottom swiping + else if (_position >= widget.height / 2) { + _tween.begin = _position; + _tween.end = widget.height; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } + + _animationController + .forward() + .then((dynamic value) => _updatePreviousView()); + + /// updates the current view visible dates when the view swiped in + /// top to bottom direction + _updateCurrentViewVisibleDates(); + } + // fling the view to top to bottom + else if (dragEndDetails.velocity.pixelsPerSecond.dy > widget.height) { + if (!DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays)) { + _position = 0; + setState(() { + /* Completes the swiping and rearrange the children position in + the custom scroll view */ + }); + return; + } + + _tween.begin = _position; + _tween.end = widget.height; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } + + _animationController + .fling( + velocity: 5.0, animationBehavior: AnimationBehavior.normal) + .then((dynamic value) => _updatePreviousView()); + + /// updates the current view visible dates when fling the view in + /// top to bottom direction + _updateCurrentViewVisibleDates(); + } + // condition to check and revert the bottom to top swiping + else if (_position.abs() <= widget.height / 2) { + _tween.begin = _position; + _tween.end = 0.0; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } + + _animationController.forward(); + } + } + } + } + + void _clearSelection() { + widget.getCalendarState(_updateCalendarStateDetails); + for (int i = 0; i < _children.length; i++) { + final GlobalKey<_CalendarViewState> viewKey = + // ignore: avoid_as + _children[i].key as GlobalKey<_CalendarViewState>; + if (viewKey.currentState!._selectionPainter!.selectedDate != + _updateCalendarStateDetails.selectedDate) { + viewKey.currentState!._selectionPainter!.selectedDate = + _updateCalendarStateDetails.selectedDate; + viewKey.currentState!._selectionNotifier.value = + !viewKey.currentState!._selectionNotifier.value; + } + } + } + + /// Updates the all day panel of the view, when the all day panel expanded and + /// the view swiped to next or previous view with the expanded all day panel, + /// it will be collapsed. + void _updateAllDayPanel() { + GlobalKey<_CalendarViewState> viewKey; + if (_currentChildIndex == 0) { + viewKey = _previousViewKey; + } else if (_currentChildIndex == 1) { + viewKey = _currentViewKey; + } else { + viewKey = _nextViewKey; + } + if (viewKey.currentState!._expanderAnimationController?.status == + AnimationStatus.completed) { + viewKey.currentState!._expanderAnimationController?.reset(); + } + viewKey.currentState!._isExpanded = false; + } + + /// Method to clear the appointments in the previous/next view + void _updateAppointmentPainter() { + for (int i = 0; i < _children.length; i++) { + final _CalendarView view = _children[i]; + final GlobalKey<_CalendarViewState> viewKey = + // ignore: avoid_as + view.key as GlobalKey<_CalendarViewState>; + if (widget.view == CalendarView.month && + widget.calendar.monthCellBuilder != null) { + if (view.visibleDates == _currentViewVisibleDates) { + widget.getCalendarState(_updateCalendarStateDetails); + if (!CalendarViewHelper.isCollectionEqual( + viewKey.currentState!._monthView.visibleAppointmentNotifier.value, + _updateCalendarStateDetails.visibleAppointments)) { + viewKey.currentState!._monthView.visibleAppointmentNotifier.value = + _updateCalendarStateDetails.visibleAppointments; + } + } else { + if (!CalendarViewHelper.isEmptyList(viewKey + .currentState!._monthView.visibleAppointmentNotifier.value)) { + viewKey.currentState!._monthView.visibleAppointmentNotifier.value = + null; + } + } + } else { + final AppointmentLayout appointmentLayout = + viewKey.currentState!._appointmentLayout; + if (view.visibleDates == _currentViewVisibleDates) { + widget.getCalendarState(_updateCalendarStateDetails); + if (!CalendarViewHelper.isCollectionEqual( + appointmentLayout.visibleAppointments.value, + _updateCalendarStateDetails.visibleAppointments)) { + appointmentLayout.visibleAppointments.value = + _updateCalendarStateDetails.visibleAppointments; + } + } else { + if (!CalendarViewHelper.isEmptyList( + appointmentLayout.visibleAppointments.value)) { + appointmentLayout.visibleAppointments.value = null; + } + } + } + } + } +} + +@immutable +class _CalendarView extends StatefulWidget { + const _CalendarView( + this.calendar, + this.view, + this.visibleDates, + this.width, + this.height, + this.agendaSelectedDate, + this.locale, + this.calendarTheme, + this.regions, + this.blackoutDates, + this.focusNode, + this.removePicker, + this.allowViewNavigation, + this.controller, + this.resourcePanelScrollController, + this.resourceCollection, + this.textScaleFactor, + this.isMobilePlatform, + this.minDate, + this.maxDate, + this.localizations, + this.updateCalendarState, + this.getCalendarState, + {Key? key}) + : super(key: key); + + final List visibleDates; + final List? regions; + final List? blackoutDates; + final SfCalendar calendar; + final CalendarView view; + final double width; + final SfCalendarThemeData calendarTheme; + final double height; + final String locale; + final ValueNotifier agendaSelectedDate; + final CalendarController controller; + final VoidCallback removePicker; + final UpdateCalendarState updateCalendarState; + final UpdateCalendarState getCalendarState; + final bool allowViewNavigation; + final FocusNode focusNode; + final ScrollController? resourcePanelScrollController; + final List? resourceCollection; + final double textScaleFactor; + final bool isMobilePlatform; + final DateTime minDate; + final DateTime maxDate; + final SfLocalizations localizations; + + @override + _CalendarViewState createState() => _CalendarViewState(); +} + +class _CalendarViewState extends State<_CalendarView> + with TickerProviderStateMixin { + // line count is the total time slot lines to be drawn in the view + // line count per view is for time line view which contains the time slot + // count for per view + double? _horizontalLinesCount; + + // all day scroll controller is used to identify the scroll position for draw + // all day selection. + ScrollController? _scrollController; + ScrollController? _timelineViewHeaderScrollController, + _timelineViewVerticalScrollController, + _timelineRulerController; + + late AppointmentLayout _appointmentLayout; + AnimationController? _timelineViewAnimationController; + Animation? _timelineViewAnimation; + Tween _timelineViewTween = Tween(begin: 0.0, end: 0.1); + + //// timeline header is used to implement the sticky view header in horizontal calendar view mode. + late TimelineViewHeaderView _timelineViewHeader; + _SelectionPainter? _selectionPainter; + double _allDayHeight = 0; + late double _timeIntervalHeight; + UpdateCalendarStateDetails _updateCalendarStateDetails = + UpdateCalendarStateDetails(); + ValueNotifier _allDaySelectionNotifier = + ValueNotifier(null); + late ValueNotifier _viewHeaderNotifier; + ValueNotifier _calendarCellNotifier = ValueNotifier(null), + _allDayNotifier = ValueNotifier(null), + _appointmentHoverNotifier = ValueNotifier(null); + ValueNotifier _selectionNotifier = ValueNotifier(false), + _timelineViewHeaderNotifier = ValueNotifier(false); + late bool _isRTL; + + bool _isExpanded = false; + DateTime? _hoveringDate; + + /// The property to hold the resource value associated with the selected + /// calendar cell. + int _selectedResourceIndex = -1; + AnimationController? _animationController; + Animation? _heightAnimation; + Animation? _allDayExpanderAnimation; + AnimationController? _expanderAnimationController; + + /// Store the month widget instance used to update the month view + /// when the visible appointment updated. + late MonthViewWidget _monthView; + + /// Used to hold the global key for restrict the new appointment layout + /// creation. + /// if set the appointment layout key property as new Global key when create + /// the appointment layout then each of the time it creates new appointment + /// layout rather than update the existing appointment layout. + GlobalKey _appointmentLayoutKey = GlobalKey(); + + Timer? _timer; + late ValueNotifier _currentTimeNotifier; + + @override + void initState() { + _viewHeaderNotifier = ValueNotifier(null) + ..addListener(_timelineViewHoveringUpdate); + if (!CalendarViewHelper.isTimelineView(widget.view) && + widget.view != CalendarView.month) { + _animationController = AnimationController( + duration: const Duration(milliseconds: 200), vsync: this); + _heightAnimation = + CurveTween(curve: Curves.easeIn).animate(_animationController!) + ..addListener(() { + setState(() { + /* Animates the all day panel height when + expanding or collapsing */ + }); + }); + + _expanderAnimationController = AnimationController( + duration: const Duration(milliseconds: 100), vsync: this); + _allDayExpanderAnimation = CurveTween(curve: Curves.easeIn) + .animate(_expanderAnimationController!) + ..addListener(() { + setState(() { + /* Animates the all day panel height when + expanding or collapsing */ + }); + }); + } + + _timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + _allDayHeight, + widget.isMobilePlatform); + if (widget.view != CalendarView.month) { + _horizontalLinesCount = CalendarViewHelper.getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view); + _scrollController = + ScrollController(initialScrollOffset: 0, keepScrollOffset: true) + ..addListener(_scrollListener); + if (CalendarViewHelper.isTimelineView(widget.view)) { + _timelineRulerController = + ScrollController(initialScrollOffset: 0, keepScrollOffset: true) + ..addListener(_timeRulerListener); + _timelineViewHeaderScrollController = + ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + _timelineViewAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + animationBehavior: AnimationBehavior.normal); + _timelineViewAnimation = _timelineViewTween + .animate(_timelineViewAnimationController!) + ..addListener(_scrollAnimationListener); + _timelineViewVerticalScrollController = + ScrollController(initialScrollOffset: 0, keepScrollOffset: true) + ..addListener(_updateResourceScroll); + widget.resourcePanelScrollController + ?.addListener(_updateResourcePanelScroll); + } + + _scrollToPosition(); + } + + final DateTime today = DateTime.now(); + _currentTimeNotifier = ValueNotifier( + (today.day * 24 * 60) + (today.hour * 60) + today.minute); + _timer = _createTimer(); + super.initState(); + } + + @override + void didUpdateWidget(_CalendarView oldWidget) { + if (widget.view != CalendarView.month) { + if (!CalendarViewHelper.isTimelineView(widget.view)) { + _updateTimeSlotView(oldWidget); + } + + _updateHorizontalLineCount(oldWidget); + + _scrollController = _scrollController ?? + ScrollController(initialScrollOffset: 0, keepScrollOffset: true) + ..addListener(_scrollListener); + + if (CalendarViewHelper.isTimelineView(widget.view)) { + _updateTimelineViews(oldWidget); + } + } + + /// Update the scroll position with following scenarios + /// 1. View changed from month or schedule view. + /// 2. View changed from timeline view(timeline day, timeline week, + /// timeline work week) to timeslot view(day, week, work week). + /// 3. View changed from timeslot view(day, week, work week) to + /// timeline view(timeline day, timeline week, timeline work week). + /// + /// This condition used to restrict the following scenarios + /// 1. View changed to month view. + /// 2. View changed with in the day, week, work week + /// (eg., view changed to week from day). + /// 3. View changed with in the timeline day, timeline week, timeline + /// work week(eg., view changed to timeline week from timeline day). + if ((oldWidget.view == CalendarView.month || + oldWidget.view == CalendarView.schedule || + (oldWidget.view != widget.view && + CalendarViewHelper.isTimelineView(widget.view)) || + (CalendarViewHelper.isTimelineView(oldWidget.view) && + !CalendarViewHelper.isTimelineView(widget.view))) && + widget.view != CalendarView.month) { + _scrollToPosition(); + } + + _timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + _allDayHeight, + widget.isMobilePlatform); + + /// Clear the all day panel selection when the calendar view changed + /// Eg., if select the all day panel and switch to month view and again + /// select the same month cell and move to day view then the view show + /// calendar cell selection and all day panel selection. + if (oldWidget.view != widget.view) { + _allDaySelectionNotifier = ValueNotifier(null); + final DateTime today = DateTime.now(); + _currentTimeNotifier = ValueNotifier( + (today.day * 24 * 60) + (today.hour * 60) + today.minute); + _timer?.cancel(); + _timer = null; + } + + if (oldWidget.calendar.showCurrentTimeIndicator != + widget.calendar.showCurrentTimeIndicator) { + _timer?.cancel(); + _timer = _createTimer(); + } + + if ((oldWidget.view != widget.view || + oldWidget.width != widget.width || + oldWidget.height != widget.height) && + _selectionPainter!.appointmentView != null) { + _selectionPainter!.appointmentView = null; + } + + /// When view switched from any other view to timeline view, and resource + /// enabled the selection must render the first resource view. + widget.getCalendarState(_updateCalendarStateDetails); + if (!CalendarViewHelper.isTimelineView(oldWidget.view) && + _updateCalendarStateDetails.selectedDate != null && + CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view) && + _selectedResourceIndex == -1) { + _selectedResourceIndex = 0; + } + + if (!CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + _selectedResourceIndex = -1; + } + + _timer ??= _createTimer(); + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + _isRTL = CalendarViewHelper.isRTLLayout(context); + widget.getCalendarState(_updateCalendarStateDetails); + switch (widget.view) { + case CalendarView.schedule: + return Container(); + case CalendarView.month: + return _getMonthView(); + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + return _getDayView(); + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + return _getTimelineView(); + } + } + + @override + void dispose() { + _viewHeaderNotifier.removeListener(_timelineViewHoveringUpdate); + + _calendarCellNotifier.removeListener(_timelineViewHoveringUpdate); + + if (_timelineViewAnimation != null) { + _timelineViewAnimation!.removeListener(_scrollAnimationListener); + } + + if (widget.resourcePanelScrollController != null) { + widget.resourcePanelScrollController! + .removeListener(_updateResourcePanelScroll); + } + + if (CalendarViewHelper.isTimelineView(widget.view) && + _timelineViewAnimationController != null) { + _timelineViewAnimationController!.dispose(); + _timelineViewAnimationController = null; + } + if (_scrollController != null) { + _scrollController!.removeListener(_scrollListener); + _scrollController!.dispose(); + _scrollController = null; + } + if (_timelineViewHeaderScrollController != null) { + _timelineViewHeaderScrollController!.dispose(); + _timelineViewHeaderScrollController = null; + } + if (_animationController != null) { + _animationController!.dispose(); + _animationController = null; + } + if (_timelineRulerController != null) { + _timelineRulerController!.dispose(); + _timelineRulerController = null; + } + + if (_expanderAnimationController != null) { + _expanderAnimationController!.dispose(); + _expanderAnimationController = null; + } + + if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + + super.dispose(); + } + + Timer? _createTimer() { + return widget.calendar.showCurrentTimeIndicator && + widget.view != CalendarView.month && + widget.view != CalendarView.timelineMonth + ? Timer.periodic(Duration(seconds: 1), (Timer t) { + final DateTime today = DateTime.now(); + final DateTime viewEndDate = + widget.visibleDates[widget.visibleDates.length - 1]; + + /// Check the today date is in between visible date range and + /// today date hour and minute is 0(12 AM) because in day view + /// current time as Feb 16, 23.59 and changed to Feb 17 then view + /// will update both Feb 16 and 17 views. + if (!isDateWithInDateRange( + widget.visibleDates[0], viewEndDate, today) && + !(today.hour == 0 && + today.minute == 0 && + isSameDate(addDays(today, -1), viewEndDate))) { + return; + } + + _currentTimeNotifier.value = + (today.day * 24 * 60) + (today.hour * 60) + today.minute; + }) + : null; + } + + /// Updates the resource panel scroll based on timeline scroll in vertical + /// direction. + void _updateResourcePanelScroll() { + if (_updateCalendarStateDetails.currentViewVisibleDates == + widget.visibleDates) { + widget.removePicker(); + } + + if (widget.resourcePanelScrollController == null || + !CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + return; + } + + if (widget.resourcePanelScrollController!.offset != + _timelineViewVerticalScrollController!.offset) { + _timelineViewVerticalScrollController! + .jumpTo(widget.resourcePanelScrollController!.offset); + } + } + + /// Updates the timeline view scroll in vertical direction based on resource + /// panel scroll. + void _updateResourceScroll() { + if (_updateCalendarStateDetails.currentViewVisibleDates == + widget.visibleDates) { + widget.removePicker(); + } + + if (widget.resourcePanelScrollController == null || + !CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + return; + } + + if (widget.resourcePanelScrollController!.offset != + _timelineViewVerticalScrollController!.offset) { + widget.resourcePanelScrollController! + .jumpTo(_timelineViewVerticalScrollController!.offset); + } + } + + Widget _getMonthView() { + return GestureDetector( + child: MouseRegion( + onEnter: _pointerEnterEvent, + onExit: _pointerExitEvent, + onHover: _pointerHoverEvent, + child: Container( + width: widget.width, + height: widget.height, + child: _addMonthView(_isRTL, widget.locale)), + ), + onTapUp: (TapUpDetails details) { + _handleOnTapForMonth(details); + }, + onLongPressStart: (LongPressStartDetails details) { + _handleOnLongPressForMonth(details); + }, + ); + } + + Widget _getDayView() { + _allDayHeight = 0; + + final bool isCurrentView = + _updateCalendarStateDetails.currentViewVisibleDates == + widget.visibleDates; + if (widget.view == CalendarView.day) { + final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + if (isCurrentView) { + _allDayHeight = _kAllDayLayoutHeight > viewHeaderHeight && + _updateCalendarStateDetails.allDayPanelHeight > viewHeaderHeight + ? _updateCalendarStateDetails.allDayPanelHeight > + _kAllDayLayoutHeight + ? _kAllDayLayoutHeight + : _updateCalendarStateDetails.allDayPanelHeight + : viewHeaderHeight; + if (_allDayHeight < _updateCalendarStateDetails.allDayPanelHeight) { + _allDayHeight += kAllDayAppointmentHeight; + } + } else { + _allDayHeight = viewHeaderHeight; + } + } else if (isCurrentView) { + _allDayHeight = + _updateCalendarStateDetails.allDayPanelHeight > _kAllDayLayoutHeight + ? _kAllDayLayoutHeight + : _updateCalendarStateDetails.allDayPanelHeight; + _allDayHeight = _allDayHeight * _heightAnimation!.value; + } + + return GestureDetector( + child: MouseRegion( + onEnter: _pointerEnterEvent, + onHover: _pointerHoverEvent, + onExit: _pointerExitEvent, + child: Container( + height: widget.height, + width: widget.width, + child: _addDayView( + widget.width, + _timeIntervalHeight * _horizontalLinesCount!, + _isRTL, + widget.locale, + isCurrentView)), + ), + onTapUp: (TapUpDetails details) { + _handleOnTapForDay(details); + }, + onLongPressStart: (LongPressStartDetails details) { + _handleOnLongPressForDay(details); + }, + ); + } + + Widget _getTimelineView() { + return GestureDetector( + child: MouseRegion( + onEnter: _pointerEnterEvent, + onHover: _pointerHoverEvent, + onExit: _pointerExitEvent, + child: Container( + width: widget.width, + height: widget.height, + child: _addTimelineView( + _timeIntervalHeight * + (_horizontalLinesCount! * widget.visibleDates.length), + widget.height, + widget.locale), + )), + onTapUp: (TapUpDetails details) { + _handleOnTapForTimeline(details); + }, + onLongPressStart: (LongPressStartDetails details) { + _handleOnLongPressForTimeline(details); + }, + ); + } + + void _timelineViewHoveringUpdate() { + if (!CalendarViewHelper.isTimelineView(widget.view) && mounted) { + return; + } + + // Updates the timeline views based on mouse hovering position. + _timelineViewHeaderNotifier.value = !_timelineViewHeaderNotifier.value; + } + + void _scrollAnimationListener() { + _scrollController!.jumpTo(_timelineViewAnimation!.value); + } + + void _scrollToPosition() { + SchedulerBinding.instance!.addPostFrameCallback((_) { + if (widget.view == CalendarView.month) { + return; + } + + widget.getCalendarState(_updateCalendarStateDetails); + final double scrollPosition = _getScrollPositionForCurrentDate( + _updateCalendarStateDetails.currentDate!); + if (scrollPosition == -1) { + return; + } + + _scrollController!.jumpTo(scrollPosition); + }); + } + + double _getScrollPositionForCurrentDate(DateTime date) { + final int visibleDatesCount = widget.visibleDates.length; + if (!isDateWithInDateRange(widget.visibleDates[0], + widget.visibleDates[visibleDatesCount - 1], date)) { + return -1; + } + + double timeToPosition = 0; + if (!CalendarViewHelper.isTimelineView(widget.view)) { + timeToPosition = AppointmentHelper.timeToPosition( + widget.calendar, date, _timeIntervalHeight); + } else { + for (int i = 0; i < visibleDatesCount; i++) { + if (!isSameDate(date, widget.visibleDates[i])) { + continue; + } + + if (widget.view == CalendarView.timelineMonth) { + timeToPosition = _timeIntervalHeight * i; + } else { + timeToPosition = (_getSingleViewWidthForTimeLineView(this) * i) + + AppointmentHelper.timeToPosition( + widget.calendar, date, _timeIntervalHeight); + } + + break; + } + } + + if (_scrollController!.hasClients) { + if (timeToPosition > _scrollController!.position.maxScrollExtent) { + timeToPosition = _scrollController!.position.maxScrollExtent; + } else if (timeToPosition < _scrollController!.position.minScrollExtent) { + timeToPosition = _scrollController!.position.minScrollExtent; + } + } + + return timeToPosition; + } + + /// Used to retain the scrolled date time. + void _retainScrolledDateTime() { + if (widget.view == CalendarView.month) { + return; + } + + DateTime scrolledDate = widget.visibleDates[0]; + double scrolledPosition = 0; + if (CalendarViewHelper.isTimelineView(widget.view)) { + final double singleViewWidth = _getSingleViewWidthForTimeLineView(this); + + /// Calculate the scrolled position date. + scrolledDate = widget + .visibleDates[_scrollController!.position.pixels ~/ singleViewWidth]; + + /// Calculate the scrolled hour position without visible date position. + scrolledPosition = _scrollController!.position.pixels % singleViewWidth; + } else { + /// Calculate the scrolled hour position. + scrolledPosition = _scrollController!.position.pixels; + } + + /// Calculate the current horizontal line based on time interval height. + final double columnIndex = scrolledPosition / _timeIntervalHeight; + + /// Calculate the time based on calculated horizontal position. + final double time = ((CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings) / + 60) * + columnIndex) + + widget.calendar.timeSlotViewSettings.startHour; + final int hour = time.toInt(); + final int minute = ((time - hour) * 60).round(); + scrolledDate = DateTime( + scrolledDate.year, scrolledDate.month, scrolledDate.day, hour, minute); + + /// Update the scrolled position after the widget generated. + SchedulerBinding.instance!.addPostFrameCallback((_) { + _scrollController!.jumpTo(_getPositionFromDate(scrolledDate)); + }); + } + + /// Calculate the position from date. + double _getPositionFromDate(DateTime date) { + final int visibleDatesCount = widget.visibleDates.length; + _timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + visibleDatesCount, + _allDayHeight, + widget.isMobilePlatform); + double timeToPosition = 0; + final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); + if (!isTimelineView) { + timeToPosition = AppointmentHelper.timeToPosition( + widget.calendar, date, _timeIntervalHeight); + } else { + for (int i = 0; i < visibleDatesCount; i++) { + if (!isSameDate(date, widget.visibleDates[i])) { + continue; + } + + if (widget.view == CalendarView.timelineMonth) { + timeToPosition = _timeIntervalHeight * i; + } else { + timeToPosition = (_getSingleViewWidthForTimeLineView(this) * i) + + AppointmentHelper.timeToPosition( + widget.calendar, date, _timeIntervalHeight); + } + + break; + } + } + + double maxScrollPosition = 0; + if (!isTimelineView) { + final double scrollViewHeight = widget.height - + _allDayHeight - + CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + final double scrollViewContentHeight = + CalendarViewHelper.getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view) * + _timeIntervalHeight; + maxScrollPosition = scrollViewContentHeight - scrollViewHeight; + } else { + final double scrollViewContentWidth = + CalendarViewHelper.getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view) * + _timeIntervalHeight * + visibleDatesCount; + maxScrollPosition = scrollViewContentWidth - widget.width; + } + + return maxScrollPosition > timeToPosition + ? timeToPosition + : maxScrollPosition; + } + + void _expandOrCollapseAllDay() { + _isExpanded = !_isExpanded; + if (_isExpanded) { + _expanderAnimationController!.forward(); + } else { + _expanderAnimationController!.reverse(); + } + } + + /// Update the time slot view scroll based on time ruler view scroll in + /// timeslot views. + void _timeRulerListener() { + if (!CalendarViewHelper.isTimelineView(widget.view)) { + return; + } + + if (_timelineRulerController!.offset != _scrollController!.offset) { + _scrollController!.jumpTo(_timelineRulerController!.offset); + } + } + + void _scrollListener() { + if (_updateCalendarStateDetails.currentViewVisibleDates == + widget.visibleDates) { + widget.removePicker(); + } + + if (CalendarViewHelper.isTimelineView(widget.view)) { + widget.getCalendarState(_updateCalendarStateDetails); + if (widget.view != CalendarView.timelineMonth) { + _timelineViewHeaderNotifier.value = !_timelineViewHeaderNotifier.value; + } + + if (_timelineRulerController!.offset != _scrollController!.offset) { + _timelineRulerController!.jumpTo(_scrollController!.offset); + } + + _timelineViewHeaderScrollController!.jumpTo(_scrollController!.offset); + } + } + + void _updateTimeSlotView(_CalendarView oldWidget) { + _animationController ??= AnimationController( + duration: const Duration(milliseconds: 200), vsync: this); + _heightAnimation ??= + CurveTween(curve: Curves.easeIn).animate(_animationController!) + ..addListener(() { + setState(() { + /*Animates the all day panel when it's expanding or + collapsing*/ + }); + }); + + _expanderAnimationController ??= AnimationController( + duration: const Duration(milliseconds: 100), vsync: this); + _allDayExpanderAnimation ??= + CurveTween(curve: Curves.easeIn).animate(_expanderAnimationController!) + ..addListener(() { + setState(() { + /*Animates the all day panel when it's expanding or + collapsing*/ + }); + }); + + if (widget.view != CalendarView.day && _allDayHeight == 0) { + if (_animationController!.status == AnimationStatus.completed) { + _animationController!.reset(); + } + + _animationController!.forward(); + } + } + + void _updateHorizontalLineCount(_CalendarView oldWidget) { + if (widget.calendar.timeSlotViewSettings.startHour != + oldWidget.calendar.timeSlotViewSettings.startHour || + widget.calendar.timeSlotViewSettings.endHour != + oldWidget.calendar.timeSlotViewSettings.endHour || + CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings) != + CalendarViewHelper.getTimeInterval( + oldWidget.calendar.timeSlotViewSettings) || + oldWidget.view == CalendarView.month || + oldWidget.view == CalendarView.timelineMonth || + oldWidget.view != CalendarView.timelineMonth && + widget.view == CalendarView.timelineMonth) { + _horizontalLinesCount = CalendarViewHelper.getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view); + } else { + _horizontalLinesCount = _horizontalLinesCount ?? + CalendarViewHelper.getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view); + } + } + + void _updateTimelineViews(_CalendarView oldWidget) { + _timelineRulerController = _timelineRulerController ?? + ScrollController(initialScrollOffset: 0, keepScrollOffset: true) + ..addListener(_timeRulerListener); + + _timelineViewAnimationController = _timelineViewAnimationController ?? + AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + animationBehavior: AnimationBehavior.normal); + + _timelineViewAnimation = _timelineViewAnimation ?? + _timelineViewTween.animate(_timelineViewAnimationController!) + ..addListener(_scrollAnimationListener); + + _timelineViewHeaderScrollController = _timelineViewHeaderScrollController ?? + ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + _timelineViewVerticalScrollController = + ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + _timelineViewVerticalScrollController!.addListener(_updateResourceScroll); + widget.resourcePanelScrollController + ?.addListener(_updateResourcePanelScroll); + } + + void _getPainterProperties(UpdateCalendarStateDetails details) { + widget.getCalendarState(_updateCalendarStateDetails); + details.allDayAppointmentViewCollection = + _updateCalendarStateDetails.allDayAppointmentViewCollection; + details.currentViewVisibleDates = + _updateCalendarStateDetails.currentViewVisibleDates; + details.visibleAppointments = + _updateCalendarStateDetails.visibleAppointments; + details.selectedDate = _updateCalendarStateDetails.selectedDate; + } + + Widget _addAllDayAppointmentPanel( + SfCalendarThemeData calendarTheme, bool isCurrentView) { + final Color borderColor = + widget.calendar.cellBorderColor ?? calendarTheme.cellBorderColor; + final Widget shadowView = Divider( + height: 1, + thickness: 1, + color: borderColor.withOpacity(borderColor.opacity * 0.5), + ); + + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + double topPosition = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + if (widget.view == CalendarView.day) { + topPosition = _allDayHeight; + } + + if (_allDayHeight == 0 || + (widget.view != CalendarView.day && + widget.visibleDates != + _updateCalendarStateDetails.currentViewVisibleDates)) { + return Positioned( + left: 0, right: 0, top: topPosition, height: 1, child: shadowView); + } + + if (widget.view == CalendarView.day) { + //// Default minimum view header width in day view as 50,so set 50 + //// when view header width less than 50. + topPosition = 0; + } + + double panelHeight = isCurrentView + ? _updateCalendarStateDetails.allDayPanelHeight - _allDayHeight + : 0; + if (panelHeight < 0) { + panelHeight = 0; + } + + /// Remove the all day appointment selection when the selected all + /// day appointment removed. + if (_allDaySelectionNotifier.value != null && + _allDaySelectionNotifier.value!.appointmentView != null && + (!_updateCalendarStateDetails.visibleAppointments.contains( + _allDaySelectionNotifier.value!.appointmentView!.appointment))) { + _allDaySelectionNotifier.value = null; + } + + final double allDayExpanderHeight = + _allDayHeight + (panelHeight * _allDayExpanderAnimation!.value); + return Positioned( + left: 0, + top: topPosition, + right: 0, + height: allDayExpanderHeight, + child: Stack( + children: [ + Positioned( + left: 0, + top: 0, + right: 0, + height: _isExpanded ? allDayExpanderHeight : _allDayHeight, + child: ListView( + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(0.0), + children: [ + AllDayAppointmentLayout( + widget.calendar, + widget.view, + widget.visibleDates, + widget.visibleDates == + _updateCalendarStateDetails.currentViewVisibleDates + ? _updateCalendarStateDetails.visibleAppointments + : null, + timeLabelWidth, + allDayExpanderHeight, + panelHeight > 0 && + (_heightAnimation!.value == 1 || + widget.view == CalendarView.day), + _allDayExpanderAnimation!.value != 0.0 && + _allDayExpanderAnimation!.value != 1, + _isRTL, + widget.calendarTheme, + _allDaySelectionNotifier, + _allDayNotifier, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.width, + (widget.view == CalendarView.day && + _updateCalendarStateDetails.allDayPanelHeight < + _allDayHeight) || + !isCurrentView + ? _allDayHeight + : _updateCalendarStateDetails.allDayPanelHeight, + widget.localizations, (UpdateCalendarStateDetails details) { + _getPainterProperties(details); + }), + ], + ), + ), + Positioned( + left: 0, + top: allDayExpanderHeight - 1, + right: 0, + height: 1, + child: shadowView), + ], + ), + ); + } + + AppointmentLayout _addAppointmentPainter(double width, double height, + [double? resourceItemHeight]) { + final List? visibleAppointments = + widget.visibleDates == + _updateCalendarStateDetails.currentViewVisibleDates + ? _updateCalendarStateDetails.visibleAppointments + : null; + _appointmentLayout = AppointmentLayout( + widget.calendar, + widget.view, + widget.visibleDates, + ValueNotifier?>(visibleAppointments), + _timeIntervalHeight, + widget.calendarTheme, + _isRTL, + _appointmentHoverNotifier, + widget.resourceCollection, + resourceItemHeight, + widget.textScaleFactor, + widget.isMobilePlatform, + width, + height, + widget.localizations, + _getPainterProperties, + key: _appointmentLayoutKey, + ); + + return _appointmentLayout; } // Returns the month view as a child for the calendar view. Widget _addMonthView(bool isRTL, String locale) { - final double viewHeaderHeight = - _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); + final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); final double height = widget.height - viewHeaderHeight; return Stack( children: [ Positioned( - left: 0, + left: 0, + top: 0, + right: 0, + height: viewHeaderHeight, + child: Container( + color: widget.calendar.viewHeaderStyle.backgroundColor ?? + widget.calendarTheme.viewHeaderBackgroundColor, + child: RepaintBoundary( + child: CustomPaint( + painter: _ViewHeaderViewPainter( + widget.visibleDates, + widget.view, + widget.calendar.viewHeaderStyle, + widget.calendar.timeSlotViewSettings, + CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view), + CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view), + widget.calendar.monthViewSettings, + isRTL, + widget.locale, + widget.calendarTheme, + widget.calendar.todayHighlightColor ?? + widget.calendarTheme.todayHighlightColor, + widget.calendar.todayTextStyle, + widget.calendar.cellBorderColor, + widget.calendar.minDate, + widget.calendar.maxDate, + _viewHeaderNotifier, + widget.textScaleFactor), + ), + ), + ), + ), + Positioned( + left: 0, + top: viewHeaderHeight, + right: 0, + bottom: 0, + child: RepaintBoundary( + child: _CalendarMultiChildContainer( + width: widget.width, + height: height, + children: [ + RepaintBoundary(child: _getMonthWidget(isRTL, height)), + RepaintBoundary( + child: _addAppointmentPainter(widget.width, height)), + ], + )), + ), + Positioned( + left: 0, + top: viewHeaderHeight, + right: 0, + bottom: 0, + child: RepaintBoundary( + child: CustomPaint( + painter: _addSelectionView(), + size: Size(widget.width, height), + ), + ), + ), + ], + ); + } + + Widget _getMonthWidget(bool isRTL, double height) { + final List? visibleAppointments = + widget.visibleDates == + _updateCalendarStateDetails.currentViewVisibleDates + ? _updateCalendarStateDetails.visibleAppointments + : null; + _monthView = MonthViewWidget( + widget.visibleDates, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.monthCellStyle, + isRTL, + widget.calendar.todayHighlightColor ?? + widget.calendarTheme.todayHighlightColor, + widget.calendar.todayTextStyle, + widget.calendar.cellBorderColor, + widget.calendarTheme, + _calendarCellNotifier, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + widget.calendar.minDate, + widget.calendar.maxDate, + widget.calendar, + widget.blackoutDates, + widget.calendar.blackoutDatesTextStyle, + widget.textScaleFactor, + widget.calendar.monthCellBuilder, + widget.width, + height, + ValueNotifier?>(visibleAppointments)); + + return _monthView; + } + + // Returns the day view as a child for the calendar view. + Widget _addDayView(double width, double height, bool isRTL, String locale, + bool isCurrentView) { + double viewHeaderWidth = widget.width; + double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + if (widget.view == CalendarView.day) { + viewHeaderWidth = timeLabelWidth < 50 ? 50 : timeLabelWidth; + viewHeaderHeight = + _allDayHeight > viewHeaderHeight ? _allDayHeight : viewHeaderHeight; + } + + double panelHeight = isCurrentView + ? _updateCalendarStateDetails.allDayPanelHeight - _allDayHeight + : 0; + if (panelHeight < 0) { + panelHeight = 0; + } + + final double allDayExpanderHeight = + panelHeight * _allDayExpanderAnimation!.value; + return Stack( + children: [ + _addAllDayAppointmentPanel(widget.calendarTheme, isCurrentView), + Positioned( + left: isRTL ? widget.width - viewHeaderWidth : 0, top: 0, - right: 0, - height: viewHeaderHeight, + right: isRTL ? 0 : widget.width - viewHeaderWidth, + height: CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view), child: Container( color: widget.calendar.viewHeaderStyle.backgroundColor ?? widget.calendarTheme.viewHeaderBackgroundColor, @@ -907,10 +4002,10 @@ class _CalendarViewState extends State<_CalendarView> widget.view, widget.calendar.viewHeaderStyle, widget.calendar.timeSlotViewSettings, - _getTimeLabelWidth( + CalendarViewHelper.getTimeLabelWidth( widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view), - _getViewHeaderHeight( + CalendarViewHelper.getViewHeaderHeight( widget.calendar.viewHeaderHeight, widget.view), widget.calendar.monthViewSettings, isRTL, @@ -929,1928 +4024,3344 @@ class _CalendarViewState extends State<_CalendarView> ), ), Positioned( - left: 0, + top: (widget.view == CalendarView.day) + ? viewHeaderHeight + allDayExpanderHeight + : viewHeaderHeight + _allDayHeight + allDayExpanderHeight, + left: 0, + right: 0, + bottom: 0, + child: Container( + child: Scrollbar( + controller: _scrollController, + child: ListView( + padding: const EdgeInsets.all(0.0), + controller: _scrollController, + scrollDirection: Axis.vertical, + physics: const ClampingScrollPhysics(), + children: [ + Stack(children: [ + RepaintBoundary( + child: _CalendarMultiChildContainer( + width: width, + height: height, + children: [ + RepaintBoundary( + child: TimeSlotWidget( + widget.visibleDates, + _horizontalLinesCount!, + _timeIntervalHeight, + timeLabelWidth, + widget.calendar.cellBorderColor, + widget.calendarTheme, + widget.calendar.timeSlotViewSettings, + isRTL, + widget.regions, + _calendarCellNotifier, + widget.textScaleFactor, + widget.calendar.timeRegionBuilder, + width, + height, + widget.calendar.minDate, + widget.calendar.maxDate), + ), + RepaintBoundary( + child: _addAppointmentPainter(width, height)), + ])), + RepaintBoundary( + child: CustomPaint( + painter: _TimeRulerView( + _horizontalLinesCount!, + _timeIntervalHeight, + widget.calendar.timeSlotViewSettings, + widget.calendar.cellBorderColor, + isRTL, + widget.locale, + widget.calendarTheme, + CalendarViewHelper.isTimelineView(widget.view), + widget.visibleDates, + widget.textScaleFactor), + size: Size(timeLabelWidth, height), + ), + ), + RepaintBoundary( + child: CustomPaint( + painter: _addSelectionView(), + size: Size(width, height), + ), + ), + _getCurrentTimeIndicator( + timeLabelWidth, width, height, false), + ]) + ]), + ))), + ], + ); + } + + Widget _getCurrentTimeIndicator( + double timeLabelSize, double width, double height, bool isTimelineView) { + if (!widget.calendar.showCurrentTimeIndicator || + widget.view == CalendarView.timelineMonth) { + return Container( + width: 0, + height: 0, + ); + } + + return RepaintBoundary( + child: CustomPaint( + painter: _CurrentTimeIndicator( + _timeIntervalHeight, + timeLabelSize, + widget.calendar.timeSlotViewSettings, + isTimelineView, + widget.visibleDates, + widget.calendar.todayHighlightColor ?? + widget.calendarTheme.todayHighlightColor, + _isRTL, + _currentTimeNotifier, + ), + size: Size(width, height), + ), + ); + } + + /// Updates the cell selection when the initial display date property of + /// calendar has value, on this scenario the first resource cell must be + /// selected; + void _updateProgrammaticSelectedResourceIndex() { + if (_updateCalendarStateDetails.selectedDate != null && + _selectedResourceIndex == -1) { + if ((widget.view == CalendarView.timelineMonth && + (isSameDate(_updateCalendarStateDetails.selectedDate, + widget.calendar.initialSelectedDate))) || + (widget.view != CalendarView.timelineMonth && + (CalendarViewHelper.isSameTimeSlot( + _updateCalendarStateDetails.selectedDate, + widget.calendar.initialSelectedDate)))) { + _selectedResourceIndex = 0; + } + } + } + + // Returns the timeline view as a child for the calendar view. + Widget _addTimelineView(double width, double height, String locale) { + final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + final double timeLabelSize = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view); + double resourceItemHeight = 0; + height -= (viewHeaderHeight + timeLabelSize); + if (isResourceEnabled) { + _updateProgrammaticSelectedResourceIndex(); + final double resourceViewSize = widget.calendar.resourceViewSettings.size; + resourceItemHeight = CalendarViewHelper.getResourceItemHeight( + resourceViewSize, + (widget.height - viewHeaderHeight - timeLabelSize), + widget.calendar.resourceViewSettings, + widget.calendar.dataSource!.resources!.length); + height = resourceItemHeight * widget.resourceCollection!.length; + } + return Stack(children: [ + Positioned( + top: 0, + left: 0, + right: 0, + height: viewHeaderHeight, + child: Container( + color: widget.calendar.viewHeaderStyle.backgroundColor ?? + widget.calendarTheme.viewHeaderBackgroundColor, + child: _getTimelineViewHeader(width, viewHeaderHeight, widget.locale), + ), + ), + Positioned( top: viewHeaderHeight, + left: 0, right: 0, - bottom: 0, - child: RepaintBoundary( - child: _CalendarMultiChildContainer( - width: widget.width, - height: height, - children: [ - RepaintBoundary(child: _getMonthWidget(isRTL, height)), + height: timeLabelSize, + child: ListView( + padding: const EdgeInsets.all(0.0), + controller: _timelineRulerController, + scrollDirection: Axis.horizontal, + physics: _CustomNeverScrollableScrollPhysics(), + children: [ RepaintBoundary( - child: _addAppointmentPainter(widget.width, height)), + child: CustomPaint( + painter: _TimeRulerView( + _horizontalLinesCount!, + _timeIntervalHeight, + widget.calendar.timeSlotViewSettings, + widget.calendar.cellBorderColor, + _isRTL, + locale, + widget.calendarTheme, + CalendarViewHelper.isTimelineView(widget.view), + widget.visibleDates, + widget.textScaleFactor), + size: Size(width, timeLabelSize), + )), ], )), - ), - Positioned( + Positioned( + top: viewHeaderHeight + timeLabelSize, left: 0, - top: viewHeaderHeight, right: 0, bottom: 0, - child: RepaintBoundary( - child: CustomPaint( - painter: _addSelectionView(), - size: Size(widget.width, height), - ), - ), - ), - ], - ); + child: Scrollbar( + controller: _scrollController, + child: ListView( + padding: const EdgeInsets.all(0.0), + controller: _scrollController, + scrollDirection: Axis.horizontal, + physics: _CustomNeverScrollableScrollPhysics(), + children: [ + Container( + width: width, + child: Stack(children: [ + Scrollbar( + controller: _timelineViewVerticalScrollController, + child: ListView( + padding: const EdgeInsets.all(0.0), + scrollDirection: Axis.vertical, + controller: + _timelineViewVerticalScrollController, + physics: isResourceEnabled + ? const ClampingScrollPhysics() + : const NeverScrollableScrollPhysics(), + children: [ + Stack(children: [ + RepaintBoundary( + child: _CalendarMultiChildContainer( + width: width, + height: height, + children: [ + RepaintBoundary( + child: TimelineWidget( + _horizontalLinesCount!, + widget.visibleDates, + widget.calendar + .timeSlotViewSettings, + _timeIntervalHeight, + widget.calendar.cellBorderColor, + _isRTL, + widget.calendarTheme, + _calendarCellNotifier, + _scrollController!, + widget.regions, + resourceItemHeight, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget + .calendar.timeRegionBuilder, + width, + height, + widget.minDate, + widget.maxDate, + widget.blackoutDates)), + RepaintBoundary( + child: _addAppointmentPainter(width, + height, resourceItemHeight)), + ], + )), + RepaintBoundary( + child: CustomPaint( + painter: _addSelectionView( + resourceItemHeight), + size: Size(width, height), + ), + ), + _getCurrentTimeIndicator( + timeLabelSize, width, height, true), + ]), + ])), + ])), + ]), + )), + ]); + } + + //// Handles the onTap callback for month cells, and view header of month + void _handleOnTapForMonth(TapUpDetails details) { + _handleTouchOnMonthView(details, null); + } + + /// Handles the tap and long press related functions for month view. + void _handleTouchOnMonthView( + TapUpDetails? tapDetails, LongPressStartDetails? longPressDetails) { + widget.removePicker(); + final DateTime? previousSelectedDate = _selectionPainter!.selectedDate; + double xDetails = 0, yDetails = 0; + bool isTapCallback = false; + if (tapDetails != null) { + isTapCallback = true; + xDetails = tapDetails.localPosition.dx; + yDetails = tapDetails.localPosition.dy; + } else if (longPressDetails != null) { + xDetails = longPressDetails.localPosition.dx; + yDetails = longPressDetails.localPosition.dy; + } + + final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + if (yDetails < viewHeaderHeight) { + if (isTapCallback) { + _handleOnTapForViewHeader(tapDetails!, widget.width); + } else if (!isTapCallback) { + _handleOnLongPressForViewHeader(longPressDetails!, widget.width); + } + } else if (yDetails > viewHeaderHeight) { + if (!widget.focusNode.hasFocus) { + widget.focusNode.requestFocus(); + } + + AppointmentView? appointmentView; + bool isMoreTapped = false; + if (!widget.isMobilePlatform && + widget.calendar.monthViewSettings.appointmentDisplayMode == + MonthAppointmentDisplayMode.appointment) { + appointmentView = _appointmentLayout.getAppointmentViewOnPoint( + xDetails, yDetails - viewHeaderHeight); + isMoreTapped = appointmentView != null && + appointmentView.startIndex == -1 && + appointmentView.endIndex == -1 && + appointmentView.position == -1 && + appointmentView.maxPositions == -1; + } + + if (appointmentView == null) { + _drawSelection(xDetails, yDetails - viewHeaderHeight, 0); + } else { + _updateCalendarStateDetails.selectedDate = null; + widget.agendaSelectedDate.value = null; + _selectionPainter!.selectedDate = null; + _selectionPainter!.appointmentView = appointmentView; + _selectionNotifier.value = !_selectionNotifier.value; + } + + widget.updateCalendarState(_updateCalendarStateDetails); + final DateTime selectedDate = + _getDateFromPosition(xDetails, yDetails - viewHeaderHeight, 0)!; + if (appointmentView == null) { + if (!isDateWithInDateRange(widget.calendar.minDate, + widget.calendar.maxDate, selectedDate) || + CalendarViewHelper.isDateInDateCollection( + widget.blackoutDates, selectedDate)) { + return; + } + + final int currentMonth = + widget.visibleDates[widget.visibleDates.length ~/ 2].month; + + /// Check the selected cell date as trailing or leading date when + /// [SfCalendar] month not shown leading and trailing dates. + if (!CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + selectedDate)) { + return; + } + + _handleMonthCellTapNavigation(selectedDate); + } + + final bool canRaiseTap = + CalendarViewHelper.shouldRaiseCalendarTapCallback( + widget.calendar.onTap) && + isTapCallback; + final bool canRaiseLongPress = + CalendarViewHelper.shouldRaiseCalendarLongPressCallback( + widget.calendar.onLongPress) && + !isTapCallback; + final bool canRaiseSelectionChanged = + CalendarViewHelper.shouldRaiseCalendarSelectionChangedCallback( + widget.calendar.onSelectionChanged); + + if (canRaiseLongPress || canRaiseTap || canRaiseSelectionChanged) { + final List selectedAppointments = + appointmentView == null || isMoreTapped + ? _getSelectedAppointments(selectedDate) + : [ + appointmentView.appointment!.data ?? + appointmentView.appointment! + ]; + final CalendarElement selectedElement = appointmentView == null + ? CalendarElement.calendarCell + : isMoreTapped + ? CalendarElement.moreAppointmentRegion + : CalendarElement.appointment; + if (canRaiseTap) { + CalendarViewHelper.raiseCalendarTapCallback(widget.calendar, + selectedDate, selectedAppointments, selectedElement, null); + } else if (canRaiseLongPress) { + CalendarViewHelper.raiseCalendarLongPressCallback(widget.calendar, + selectedDate, selectedAppointments, selectedElement, null); + } + + _updatedSelectionChangedCallback( + canRaiseSelectionChanged, previousSelectedDate); + } + } + } + + /// Raise selection changed callback based on the arguments passed. + void _updatedSelectionChangedCallback( + bool canRaiseSelectionChanged, DateTime? previousSelectedDate, + [CalendarResource? selectedResource, + int? previousSelectedResourceIndex]) { + if (canRaiseSelectionChanged && + (((widget.view == CalendarView.month || + widget.view == CalendarView.timelineMonth) && + !isSameDate( + previousSelectedDate, _selectionPainter!.selectedDate)) || + ((widget.view != CalendarView.month && + widget.view != CalendarView.timelineMonth) && + !CalendarViewHelper.isSameTimeSlot( + previousSelectedDate, _selectionPainter!.selectedDate)) || + (CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view) && + _selectionPainter!.selectedResourceIndex != + previousSelectedResourceIndex))) { + CalendarViewHelper.raiseCalendarSelectionChangedCallback( + widget.calendar, _selectionPainter!.selectedDate, selectedResource); + } + } + + void _handleMonthCellTapNavigation(DateTime date) { + if (!widget.allowViewNavigation || + widget.view != CalendarView.month || + widget.calendar.monthViewSettings.showAgenda) { + return; + } + + widget.controller.view = CalendarView.day; + widget.controller.displayDate = date; + } + + //// Handles the onLongPress callback for month cells, and view header of month. + void _handleOnLongPressForMonth(LongPressStartDetails details) { + _handleTouchOnMonthView(null, details); + } + + //// Handles the onTap callback for timeline view cells, and view header of timeline. + void _handleOnTapForTimeline(TapUpDetails details) { + _handleTouchOnTimeline(details, null); + } + + /// Returns the index of resource value associated with the selected calendar + /// cell in timeline views. + int _getSelectedResourceIndex( + double yPosition, double viewHeaderHeight, double timeLabelSize) { + final int resourceCount = widget.calendar.dataSource != null && + widget.calendar.dataSource!.resources != null + ? widget.calendar.dataSource!.resources!.length + : 0; + final double resourceItemHeight = CalendarViewHelper.getResourceItemHeight( + widget.calendar.resourceViewSettings.size, + widget.height - viewHeaderHeight - timeLabelSize, + widget.calendar.resourceViewSettings, + resourceCount); + return (yPosition / resourceItemHeight).truncate(); + } + + /// Handles the tap and long press related functions for timeline view. + void _handleTouchOnTimeline( + TapUpDetails? tapDetails, LongPressStartDetails? longPressDetails) { + widget.removePicker(); + final DateTime? previousSelectedDate = _selectionPainter!.selectedDate; + double xDetails = 0, yDetails = 0; + bool isTapCallback = false; + if (tapDetails != null) { + isTapCallback = true; + xDetails = tapDetails.localPosition.dx; + yDetails = tapDetails.localPosition.dy; + } else if (longPressDetails != null) { + xDetails = longPressDetails.localPosition.dx; + yDetails = longPressDetails.localPosition.dy; + } + + final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + + if (yDetails < viewHeaderHeight) { + if (isTapCallback) { + _handleOnTapForViewHeader(tapDetails!, widget.width); + } else if (!isTapCallback) { + _handleOnLongPressForViewHeader(longPressDetails!, widget.width); + } + } else if (yDetails > viewHeaderHeight) { + if (!widget.focusNode.hasFocus) { + widget.focusNode.requestFocus(); + } + + widget.getCalendarState(_updateCalendarStateDetails); + DateTime? selectedDate = _updateCalendarStateDetails.selectedDate; + + double xPosition = _scrollController!.offset + xDetails; + double yPosition = yDetails - viewHeaderHeight; + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + + if (yPosition < timeLabelWidth) { + return; + } + + yPosition -= timeLabelWidth; + + CalendarResource? selectedResource; + + if (CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + yPosition += _timelineViewVerticalScrollController!.offset; + _selectedResourceIndex = _getSelectedResourceIndex( + yPosition, viewHeaderHeight, timeLabelWidth); + selectedResource = + widget.calendar.dataSource!.resources![_selectedResourceIndex]; + } + + final int previousSelectedResourceIndex = + _selectionPainter!.selectedResourceIndex; + _selectionPainter!.selectedResourceIndex = _selectedResourceIndex; + + if (_isRTL) { + xPosition = _scrollController!.offset + + (_scrollController!.position.viewportDimension - xDetails); + xPosition = (_scrollController!.position.viewportDimension + + _scrollController!.position.maxScrollExtent) - + xPosition; + } + + final AppointmentView? appointmentView = + _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); + if (appointmentView == null) { + _drawSelection(xDetails, yPosition, timeLabelWidth); + selectedDate = _selectionPainter!.selectedDate; + } else { + if (selectedDate != null) { + selectedDate = null; + _selectionPainter!.selectedDate = selectedDate; + _updateCalendarStateDetails.selectedDate = selectedDate; + } + + _selectionPainter!.appointmentView = appointmentView; + _selectionNotifier.value = !_selectionNotifier.value; + } + + widget.updateCalendarState(_updateCalendarStateDetails); + final bool canRaiseTap = + CalendarViewHelper.shouldRaiseCalendarTapCallback( + widget.calendar.onTap) && + isTapCallback; + final bool canRaiseLongPress = + CalendarViewHelper.shouldRaiseCalendarLongPressCallback( + widget.calendar.onLongPress) && + !isTapCallback; + final bool canRaiseSelectionChanged = + CalendarViewHelper.shouldRaiseCalendarSelectionChangedCallback( + widget.calendar.onSelectionChanged); + + if (canRaiseLongPress || canRaiseTap || canRaiseSelectionChanged) { + final DateTime selectedDate = + _getDateFromPosition(xDetails, yDetails - viewHeaderHeight, 0)!; + final int timeInterval = CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings); + if (appointmentView == null) { + if (!CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + selectedDate, + timeInterval) || + (widget.view == CalendarView.timelineMonth && + CalendarViewHelper.isDateInDateCollection( + widget.calendar.blackoutDates, selectedDate))) { + return; + } + + /// Restrict the callback, while selected region as disabled + /// [TimeRegion]. + if (!_isEnabledRegion( + xDetails, selectedDate, _selectedResourceIndex)) { + return; + } + + if (canRaiseTap) { + CalendarViewHelper.raiseCalendarTapCallback( + widget.calendar, + selectedDate, + null, + CalendarElement.calendarCell, + selectedResource); + } else if (canRaiseLongPress) { + CalendarViewHelper.raiseCalendarLongPressCallback( + widget.calendar, + selectedDate, + null, + CalendarElement.calendarCell, + selectedResource); + } + _updatedSelectionChangedCallback( + canRaiseSelectionChanged, + previousSelectedDate, + selectedResource, + previousSelectedResourceIndex); + } else { + if (canRaiseTap) { + CalendarViewHelper.raiseCalendarTapCallback( + widget.calendar, + selectedDate, + [ + appointmentView.appointment!.data ?? + appointmentView.appointment! + ], + CalendarElement.appointment, + selectedResource); + } else if (canRaiseLongPress) { + CalendarViewHelper.raiseCalendarLongPressCallback( + widget.calendar, + selectedDate, + [ + appointmentView.appointment!.data ?? + appointmentView.appointment! + ], + CalendarElement.appointment, + selectedResource); + } + _updatedSelectionChangedCallback( + canRaiseSelectionChanged, + previousSelectedDate, + selectedResource, + previousSelectedResourceIndex); + } + } + } + } + + //// Handles the onLongPress callback for timeline view cells, and view header + //// of timeline. + void _handleOnLongPressForTimeline(LongPressStartDetails details) { + _handleTouchOnTimeline(null, details); + } + + void _updateAllDaySelection(AppointmentView? view, DateTime? date) { + if (_allDaySelectionNotifier.value != null && + view == _allDaySelectionNotifier.value!.appointmentView && + isSameDate(date, _allDaySelectionNotifier.value!.selectedDate)) { + return; + } + + _allDaySelectionNotifier.value = SelectionDetails(view, date); + } + + //// Handles the onTap callback for day view cells, all day panel, and view + //// header of day. + void _handleOnTapForDay(TapUpDetails details) { + _handleTouchOnDayView(details, null); + } + + /// Handles the tap and long press related functions for day, week + /// work week views. + void _handleTouchOnDayView( + TapUpDetails? tapDetails, LongPressStartDetails? longPressDetails) { + widget.removePicker(); + final DateTime? previousSelectedDate = _selectionPainter!.selectedDate; + final int timeInterval = CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings); + double xDetails = 0, yDetails = 0; + bool isTappedCallback = false; + if (tapDetails != null) { + isTappedCallback = true; + xDetails = tapDetails.localPosition.dx; + yDetails = tapDetails.localPosition.dy; + } else if (longPressDetails != null) { + xDetails = longPressDetails.localPosition.dx; + yDetails = longPressDetails.localPosition.dy; + } + if (!widget.focusNode.hasFocus) { + widget.focusNode.requestFocus(); + } + + widget.getCalendarState(_updateCalendarStateDetails); + dynamic? selectedAppointment; + List? selectedAppointments; + CalendarElement targetElement = CalendarElement.viewHeader; + DateTime? selectedDate = _updateCalendarStateDetails.selectedDate; + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + + final double viewHeaderHeight = widget.view == CalendarView.day + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + final double allDayHeight = _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; + if (!_isRTL && + xDetails <= timeLabelWidth && + yDetails > viewHeaderHeight + allDayHeight) { + return; + } + + if (_isRTL && + xDetails >= widget.width - timeLabelWidth && + yDetails > viewHeaderHeight + allDayHeight) { + return; + } + + if (yDetails < viewHeaderHeight) { + /// Check the touch position in time ruler view + /// If RTL, time ruler placed at right side, + /// else time ruler placed at left side. + if ((!_isRTL && xDetails <= timeLabelWidth) || + (_isRTL && widget.width - xDetails <= timeLabelWidth)) { + return; + } + + if (isTappedCallback) { + _handleOnTapForViewHeader(tapDetails!, widget.width); + } else if (!isTappedCallback) { + _handleOnLongPressForViewHeader(longPressDetails!, widget.width); + } + + return; + } else if (yDetails < viewHeaderHeight + allDayHeight) { + /// Check the touch position in view header when [CalendarView] is day + /// If RTL, view header placed at right side, + /// else view header placed at left side. + if (widget.view == CalendarView.day && + ((!_isRTL && xDetails <= timeLabelWidth) || + (_isRTL && widget.width - xDetails <= timeLabelWidth)) && + yDetails < + CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view)) { + if (isTappedCallback) { + _handleOnTapForViewHeader(tapDetails!, widget.width); + } else if (!isTappedCallback) { + _handleOnLongPressForViewHeader(longPressDetails!, widget.width); + } + + return; + } else if ((!_isRTL && timeLabelWidth >= xDetails) || + (_isRTL && xDetails > widget.width - timeLabelWidth)) { + /// Perform expand or collapse when the touch position on + /// expander icon in all day panel. + _expandOrCollapseAllDay(); + return; + } + + final double yPosition = yDetails - viewHeaderHeight; + final AppointmentView? appointmentView = _getAllDayAppointmentOnPoint( + _updateCalendarStateDetails.allDayAppointmentViewCollection, + xDetails, + yPosition); + + if (appointmentView == null) { + targetElement = CalendarElement.allDayPanel; + if (isTappedCallback) { + selectedDate = + _getTappedViewHeaderDate(tapDetails!.localPosition, widget.width); + } else { + selectedDate = _getTappedViewHeaderDate( + longPressDetails!.localPosition, widget.width); + } + } + + /// Check the count position tapped or not + bool isTappedOnCount = appointmentView != null && + _updateCalendarStateDetails.allDayPanelHeight > allDayHeight && + yPosition > allDayHeight - kAllDayAppointmentHeight; + + /// Check the tap position inside the last appointment rendering position + /// when the panel as collapsed and it does not psoition does not have + /// appointment. + /// Eg., If July 8 have 3 all day appointments spanned to July 9 and + /// July 9 have 1 all day appointment spanned to July 10 then July 10 + /// appointment view does not shown and it only have count label. + /// If user tap on count label then the panel does not have appointment + /// view, because the view rendered after the end position, so calculate + /// the visible date cell appointment and it have appointments after + /// end position then perform expand operation. + if (appointmentView == null && + selectedDate != null && + _updateCalendarStateDetails.allDayPanelHeight > allDayHeight && + yPosition > allDayHeight - kAllDayAppointmentHeight) { + final int currentSelectedIndex = DateTimeHelper.getVisibleDateIndex( + widget.visibleDates, selectedDate); + if (currentSelectedIndex != -1) { + final List selectedIndexAppointment = + []; + for (int i = 0; + i < + _updateCalendarStateDetails + .allDayAppointmentViewCollection.length; + i++) { + final AppointmentView currentView = + _updateCalendarStateDetails.allDayAppointmentViewCollection[i]; + if (currentView.appointment == null) { + continue; + } + if (currentView.startIndex <= currentSelectedIndex && + currentView.endIndex > currentSelectedIndex) { + selectedIndexAppointment.add(currentView); + } + } + + int maxPosition = 0; + if (selectedIndexAppointment.isNotEmpty) { + maxPosition = selectedIndexAppointment + .reduce((AppointmentView currentAppView, + AppointmentView nextAppView) => + currentAppView.maxPositions > nextAppView.maxPositions + ? currentAppView + : nextAppView) + .maxPositions; + } + final int endAppointmentPosition = + allDayHeight ~/ kAllDayAppointmentHeight; + if (endAppointmentPosition < maxPosition) { + isTappedOnCount = true; + } + } + } + + if (appointmentView != null && + (yPosition < allDayHeight - kAllDayAppointmentHeight || + _updateCalendarStateDetails.allDayPanelHeight <= allDayHeight || + appointmentView.position + 1 >= appointmentView.maxPositions)) { + if (!CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + appointmentView.appointment!.actualStartTime, + timeInterval) || + !CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + appointmentView.appointment!.actualEndTime, + timeInterval)) { + return; + } + if (selectedDate != null) { + selectedDate = null; + _selectionPainter!.selectedDate = selectedDate; + _updateCalendarStateDetails.selectedDate = selectedDate; + } + + _selectionPainter!.appointmentView = null; + _selectionNotifier.value = !_selectionNotifier.value; + selectedAppointment = appointmentView.appointment; + selectedAppointments = null; + targetElement = CalendarElement.appointment; + _updateAllDaySelection(appointmentView, null); + } else if (isTappedOnCount) { + _expandOrCollapseAllDay(); + return; + } else if (appointmentView == null) { + _updateAllDaySelection(null, selectedDate); + _selectionPainter!.selectedDate = null; + _selectionPainter!.appointmentView = null; + _selectionNotifier.value = !_selectionNotifier.value; + _updateCalendarStateDetails.selectedDate = null; + } + } else { + final double yPosition = yDetails - + viewHeaderHeight - + allDayHeight + + _scrollController!.offset; + final AppointmentView? appointmentView = + _appointmentLayout.getAppointmentViewOnPoint(xDetails, yPosition); + _allDaySelectionNotifier.value = null; + if (appointmentView == null) { + if (_isRTL) { + _drawSelection(xDetails, yDetails - viewHeaderHeight - allDayHeight, + timeLabelWidth); + } else { + _drawSelection(xDetails - timeLabelWidth, + yDetails - viewHeaderHeight - allDayHeight, timeLabelWidth); + } + targetElement = CalendarElement.calendarCell; + } else { + if (selectedDate != null) { + selectedDate = null; + _selectionPainter!.selectedDate = selectedDate; + _updateCalendarStateDetails.selectedDate = selectedDate; + } + + _selectionPainter!.appointmentView = appointmentView; + _selectionNotifier.value = !_selectionNotifier.value; + selectedAppointment = appointmentView.appointment; + targetElement = CalendarElement.appointment; + } + } + + widget.updateCalendarState(_updateCalendarStateDetails); + final bool canRaiseTap = CalendarViewHelper.shouldRaiseCalendarTapCallback( + widget.calendar.onTap) && + isTappedCallback; + final bool canRaiseLongPress = + CalendarViewHelper.shouldRaiseCalendarLongPressCallback( + widget.calendar.onLongPress) && + !isTappedCallback; + final bool canRaiseSelectionChanged = + CalendarViewHelper.shouldRaiseCalendarSelectionChangedCallback( + widget.calendar.onSelectionChanged); + if (canRaiseLongPress || canRaiseTap || canRaiseSelectionChanged) { + final double yPosition = yDetails - viewHeaderHeight - allDayHeight; + if (_selectionPainter!.selectedDate != null && + targetElement != CalendarElement.allDayPanel) { + selectedAppointments = null; + + /// In LTR, remove the time ruler width value from the + /// touch x position while calculate the selected date value. + selectedDate = _getDateFromPosition( + !_isRTL ? xDetails - timeLabelWidth : xDetails, + yPosition, + timeLabelWidth); + + if (!CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + selectedDate!, + timeInterval)) { + return; + } + + /// Restrict the callback, while selected region as disabled + /// [TimeRegion]. + if (targetElement == CalendarElement.calendarCell && + !_isEnabledRegion( + yPosition, selectedDate, _selectedResourceIndex)) { + return; + } + + if (canRaiseTap) { + CalendarViewHelper.raiseCalendarTapCallback( + widget.calendar, + _selectionPainter!.selectedDate, + selectedAppointments, + targetElement, + null); + } else if (canRaiseLongPress) { + CalendarViewHelper.raiseCalendarLongPressCallback( + widget.calendar, + _selectionPainter!.selectedDate, + selectedAppointments, + targetElement, + null); + } + _updatedSelectionChangedCallback( + canRaiseSelectionChanged, previousSelectedDate); + } else if (selectedAppointment != null) { + selectedAppointments = [ + selectedAppointment.data ?? selectedAppointment + ]; + + /// In LTR, remove the time ruler width value from the + /// touch x position while calculate the selected date value. + selectedDate = _getDateFromPosition( + !_isRTL ? xDetails - timeLabelWidth : xDetails, + yPosition, + timeLabelWidth); + + if (canRaiseTap) { + CalendarViewHelper.raiseCalendarTapCallback( + widget.calendar, + selectedDate, + selectedAppointments, + CalendarElement.appointment, + null); + } else if (canRaiseLongPress) { + CalendarViewHelper.raiseCalendarLongPressCallback( + widget.calendar, + selectedDate, + selectedAppointments, + CalendarElement.appointment, + null); + } + _updatedSelectionChangedCallback( + canRaiseSelectionChanged, previousSelectedDate); + } else if (selectedDate != null && + targetElement == CalendarElement.allDayPanel) { + if (canRaiseTap) { + CalendarViewHelper.raiseCalendarTapCallback( + widget.calendar, selectedDate, null, targetElement, null); + } else if (canRaiseLongPress) { + CalendarViewHelper.raiseCalendarLongPressCallback( + widget.calendar, selectedDate, null, targetElement, null); + } + _updatedSelectionChangedCallback( + canRaiseSelectionChanged, previousSelectedDate); + } + } + } + + /// Check the selected date region as enabled time region or not. + bool _isEnabledRegion(double y, DateTime? selectedDate, int resourceIndex) { + if (widget.regions == null || + widget.regions!.isEmpty || + widget.view == CalendarView.timelineMonth || + selectedDate == null) { + return true; + } + + final double timeIntervalSize = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + _allDayHeight, + widget.isMobilePlatform); + + final double minuteHeight = timeIntervalSize / + CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings); + final Duration startDuration = Duration( + hours: widget.calendar.timeSlotViewSettings.startHour.toInt(), + minutes: ((widget.calendar.timeSlotViewSettings.startHour - + widget.calendar.timeSlotViewSettings.startHour.toInt()) * + 60) + .toInt()); + int minutes; + if (CalendarViewHelper.isTimelineView(widget.view)) { + final double viewWidth = _timeIntervalHeight * _horizontalLinesCount!; + if (_isRTL) { + minutes = ((_scrollController!.offset + + (_scrollController!.position.viewportDimension - y)) % + viewWidth) ~/ + minuteHeight; + } else { + minutes = ((_scrollController!.offset + y) % viewWidth) ~/ minuteHeight; + } + } else { + minutes = (_scrollController!.offset + y) ~/ minuteHeight; + } + + final DateTime date = DateTime(selectedDate.year, selectedDate.month, + selectedDate.day, 0, minutes + startDuration.inMinutes, 0); + bool isValidRegion = true; + final bool isResourcesEnabled = CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view); + for (int i = 0; i < widget.regions!.length; i++) { + final CalendarTimeRegion region = widget.regions![i]; + if (region.actualStartTime.isAfter(date) || + region.actualEndTime.isBefore(date)) { + continue; + } + + /// Condition added ensure that the region is disabled only on the + /// specified resource slot, for other resources it must be enabled. + if (isResourcesEnabled && + resourceIndex != -1 && + region.resourceIds != null && + region.resourceIds!.isNotEmpty && + !region.resourceIds! + .contains(widget.resourceCollection![resourceIndex].id)) { + continue; + } + + isValidRegion = region.enablePointerInteraction; + } + + return isValidRegion; + } + + bool _isAutoTimeIntervalHeight(SfCalendar calendar, CalendarView view) { + if (CalendarViewHelper.isTimelineView(view)) { + return calendar.timeSlotViewSettings.timeIntervalWidth == -1; + } else { + return calendar.timeSlotViewSettings.timeIntervalHeight == -1; + } + } + + /// Returns the default time interval width for timeline views. + double _getTimeIntervalWidth(double timeIntervalHeight, CalendarView view, + double width, bool isMobilePlatform) { + if (timeIntervalHeight >= 0) { + return timeIntervalHeight; + } + + if (view == CalendarView.timelineMonth && + !CalendarViewHelper.isMobileLayoutUI(width, isMobilePlatform)) { + return 160; + } + + return 60; + } + + /// Returns the time interval width based on property value, also arrange the + /// time slots into the view port size. + double _getTimeIntervalHeight( + SfCalendar calendar, + CalendarView view, + double width, + double height, + int visibleDatesCount, + double allDayHeight, + bool isMobilePlatform) { + final bool isTimelineView = CalendarViewHelper.isTimelineView(view); + double timeIntervalHeight = isTimelineView + ? _getTimeIntervalWidth(calendar.timeSlotViewSettings.timeIntervalWidth, + view, width, isMobilePlatform) + : calendar.timeSlotViewSettings.timeIntervalHeight; + + if (!_isAutoTimeIntervalHeight(calendar, view)) { + return timeIntervalHeight; + } + + double viewHeaderHeight = + CalendarViewHelper.getViewHeaderHeight(calendar.viewHeaderHeight, view); + + if (view == CalendarView.day) { + allDayHeight = _kAllDayLayoutHeight; + viewHeaderHeight = 0; + } else { + allDayHeight = allDayHeight > _kAllDayLayoutHeight + ? _kAllDayLayoutHeight + : allDayHeight; + } + + switch (view) { + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + timeIntervalHeight = (height - allDayHeight - viewHeaderHeight) / + CalendarViewHelper.getHorizontalLinesCount( + calendar.timeSlotViewSettings, view); + break; + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + { + final double _horizontalLinesCount = + CalendarViewHelper.getHorizontalLinesCount( + calendar.timeSlotViewSettings, view); + timeIntervalHeight = + width / (_horizontalLinesCount * visibleDatesCount); + if (!_isValidWidth( + width, calendar, visibleDatesCount, _horizontalLinesCount)) { + /// we have used 40 as a default time interval height for timeline + /// view when the time interval height set for auto time + /// interval height. + timeIntervalHeight = 40; + } + } + break; + case CalendarView.schedule: + case CalendarView.month: + return 0; + } + + return timeIntervalHeight; + } + + /// checks whether the width can afford the line count or else creates a + /// scrollable width + bool _isValidWidth(double screenWidth, SfCalendar calendar, + int visibleDatesCount, double horizontalLinesCount) { + const int offSetValue = 10; + final double tempWidth = + visibleDatesCount * offSetValue * horizontalLinesCount; + + if (tempWidth < screenWidth) { + return true; + } + + return false; + } + + //// Handles the onLongPress callback for day view cells, all day panel and + //// view header of day. + void _handleOnLongPressForDay(LongPressStartDetails details) { + _handleTouchOnDayView(null, details); + } + + //// Handles the on tap callback for view header + void _handleOnTapForViewHeader(TapUpDetails details, double width) { + final DateTime tappedDate = + _getTappedViewHeaderDate(details.localPosition, width)!; + _handleViewHeaderTapNavigation(tappedDate); + if (!CalendarViewHelper.shouldRaiseCalendarTapCallback( + widget.calendar.onTap)) { + return; + } + + CalendarViewHelper.raiseCalendarTapCallback( + widget.calendar, tappedDate, null, CalendarElement.viewHeader, null); + } + + //// Handles the on long press callback for view header + void _handleOnLongPressForViewHeader( + LongPressStartDetails details, double width) { + final DateTime tappedDate = + _getTappedViewHeaderDate(details.localPosition, width)!; + _handleViewHeaderTapNavigation(tappedDate); + if (!CalendarViewHelper.shouldRaiseCalendarLongPressCallback( + widget.calendar.onLongPress)) { + return; + } + + CalendarViewHelper.raiseCalendarLongPressCallback( + widget.calendar, tappedDate, null, CalendarElement.viewHeader, null); } - Widget _getMonthWidget(bool isRTL, double height) { - final List visibleAppointments = widget.visibleDates == - _updateCalendarStateDetails._currentViewVisibleDates - ? _updateCalendarStateDetails._visibleAppointments - : null; - _monthView = _MonthViewWidget( - widget.visibleDates, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.monthCellStyle, - isRTL, - widget.calendar.todayHighlightColor ?? - widget.calendarTheme.todayHighlightColor, - widget.calendar.todayTextStyle, - widget.calendar.cellBorderColor, - widget.calendarTheme, - _calendarCellNotifier, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - widget.calendar.minDate, - widget.calendar.maxDate, - widget.calendar, - widget.blackoutDates, - widget.calendar.blackoutDatesTextStyle, - widget.textScaleFactor, - widget.calendar.monthCellBuilder, - widget.width, - height, - ValueNotifier>(visibleAppointments)); + void _handleViewHeaderTapNavigation(DateTime date) { + if (!widget.allowViewNavigation || + widget.view == CalendarView.day || + widget.view == CalendarView.timelineDay || + widget.view == CalendarView.month) { + return; + } - return _monthView; + if (!isDateWithInDateRange( + widget.calendar.minDate, widget.calendar.maxDate, date) || + (widget.controller.view == CalendarView.timelineMonth && + CalendarViewHelper.isDateInDateCollection( + widget.blackoutDates, date))) { + return; + } + + if (widget.view == CalendarView.week || + widget.view == CalendarView.workWeek) { + widget.controller.view = CalendarView.day; + } else { + widget.controller.view = CalendarView.timelineDay; + } + + widget.controller.displayDate = date; } - // Returns the day view as a child for the calendar view. - Widget _addDayView(double width, double height, bool isRTL, String locale, - bool isCurrentView) { - double viewHeaderWidth = widget.width; - double viewHeaderHeight = - _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); - final double timeLabelWidth = _getTimeLabelWidth( + DateTime? _getTappedViewHeaderDate(Offset localPosition, double width) { + int index = 0; + final double timeLabelViewWidth = CalendarViewHelper.getTimeLabelWidth( widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); - if (widget.view == null || widget.view == CalendarView.day) { - viewHeaderWidth = timeLabelWidth < 50 ? 50 : timeLabelWidth; - viewHeaderHeight = - _allDayHeight > viewHeaderHeight ? _allDayHeight : viewHeaderHeight; + if (!CalendarViewHelper.isTimelineView(widget.view)) { + double cellWidth = 0; + if (widget.view != CalendarView.month) { + cellWidth = (width - timeLabelViewWidth) / widget.visibleDates.length; + + /// Set index value as 0 when calendar view as day because day view hold + /// single visible date. + if (widget.view == CalendarView.day) { + index = 0; + } else { + index = ((localPosition.dx - (_isRTL ? 0 : timeLabelViewWidth)) / + cellWidth) + .truncate(); + } + } else { + cellWidth = width / DateTime.daysPerWeek; + index = (localPosition.dx / cellWidth).truncate(); + } + + /// Calculate the RTL based value of index when the widget direction as + /// RTL. + if (_isRTL && widget.view != CalendarView.month) { + index = widget.visibleDates.length - index - 1; + } else if (_isRTL && widget.view == CalendarView.month) { + index = DateTime.daysPerWeek - index - 1; + } + + if (index < 0 || index >= widget.visibleDates.length) { + return null; + } + + return widget.visibleDates[index]; + } else { + index = ((_scrollController!.offset + + (_isRTL + ? _scrollController!.position.viewportDimension - + localPosition.dx + : localPosition.dx)) / + _getSingleViewWidthForTimeLineView(this)) + .truncate(); + + if (index < 0 || index >= widget.visibleDates.length) { + return null; + } + + return widget.visibleDates[index]; } + } - double panelHeight = isCurrentView - ? _updateCalendarStateDetails._allDayPanelHeight - _allDayHeight - : 0; - if (panelHeight < 0) { - panelHeight = 0; + void _updateHoveringForAppointment(double xPosition, double yPosition) { + if (_viewHeaderNotifier.value != null) { + _viewHeaderNotifier.value = null; } - final double allDayExpanderHeight = - panelHeight * _allDayExpanderAnimation.value; - return Stack( - children: [ - _addAllDayAppointmentPanel(widget.calendarTheme, isCurrentView), - Positioned( - left: isRTL ? widget.width - viewHeaderWidth : 0, - top: 0, - right: isRTL ? 0 : widget.width - viewHeaderWidth, - height: _getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view), - child: Container( - color: widget.calendar.viewHeaderStyle.backgroundColor ?? - widget.calendarTheme.viewHeaderBackgroundColor, - child: RepaintBoundary( - child: CustomPaint( - painter: _ViewHeaderViewPainter( - widget.visibleDates, - widget.view, - widget.calendar.viewHeaderStyle, - widget.calendar.timeSlotViewSettings, - _getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, - widget.view), - _getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view), - widget.calendar.monthViewSettings, - isRTL, - widget.locale, - widget.calendarTheme, - widget.calendar.todayHighlightColor ?? - widget.calendarTheme.todayHighlightColor, - widget.calendar.todayTextStyle, - widget.calendar.cellBorderColor, - widget.calendar.minDate, - widget.calendar.maxDate, - _viewHeaderNotifier, - widget.textScaleFactor), - ), - ), - ), - ), - Positioned( - top: (widget.view == CalendarView.day) - ? viewHeaderHeight + allDayExpanderHeight - : viewHeaderHeight + _allDayHeight + allDayExpanderHeight, - left: 0, - right: 0, - bottom: 0, - child: Container( - child: Scrollbar( - child: ListView( - padding: const EdgeInsets.all(0.0), - controller: _scrollController, - scrollDirection: Axis.vertical, - physics: const ClampingScrollPhysics(), - children: [ - Stack(children: [ - RepaintBoundary( - child: _CalendarMultiChildContainer( - width: width, - height: height, - children: [ - RepaintBoundary( - child: _TimeSlotWidget( - widget.visibleDates, - _horizontalLinesCount, - _timeIntervalHeight, - timeLabelWidth, - widget.calendar.cellBorderColor, - widget.calendarTheme, - widget.calendar.timeSlotViewSettings, - isRTL, - widget.regions, - _calendarCellNotifier, - widget.textScaleFactor, - widget.calendar.timeRegionBuilder, - width, - height), - ), - RepaintBoundary( - child: _addAppointmentPainter(width, height)), - ])), - RepaintBoundary( - child: CustomPaint( - painter: _TimeRulerView( - _horizontalLinesCount, - _timeIntervalHeight, - widget.calendar.timeSlotViewSettings, - widget.calendar.cellBorderColor, - isRTL, - widget.locale, - widget.calendarTheme, - _isTimelineView(widget.view), - widget.visibleDates, - widget.textScaleFactor), - size: Size(timeLabelWidth, height), - ), - ), - RepaintBoundary( - child: CustomPaint( - painter: _addSelectionView(), - size: Size(width, height), - ), - ), - ]) - ]), - ))), - ], - ); + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; + } + + if (_allDayNotifier.value != null) { + _allDayNotifier.value = null; + } + + if (_hoveringDate != null) { + _hoveringDate = null; + } + + _appointmentHoverNotifier.value = Offset(xPosition, yPosition); } - /// Updates the cell selection when the initial display date property of - /// calendar has value, on this scenario the first resource cell must be - /// selected; - void _updateProgrammaticSelectedResourceIndex() { - if (_updateCalendarStateDetails._selectedDate != null && - _selectedResourceIndex == -1) { - if ((widget.view == CalendarView.timelineMonth && - (isSameDate(_updateCalendarStateDetails._selectedDate, - widget.calendar.initialSelectedDate))) || - (widget.view != CalendarView.timelineMonth && - (_isSameTimeSlot(_updateCalendarStateDetails._selectedDate, - widget.calendar.initialSelectedDate)))) { - _selectedResourceIndex = 0; - } + void _updateHoveringForAllDayPanel(double xPosition, double yPosition) { + if (_viewHeaderNotifier.value != null) { + _viewHeaderNotifier.value = null; + } + + if (_calendarCellNotifier.value != null) { + _hoveringDate = null; + _calendarCellNotifier.value = null; + } + + if (_appointmentHoverNotifier.value != null) { + _appointmentHoverNotifier.value = null; + } + + if (_hoveringDate != null) { + _hoveringDate = null; + } + + _allDayNotifier.value = Offset(xPosition, yPosition); + } + + /// Removes the view header hovering in multiple occasions, when the pointer + /// hovering the disabled or blackout dates, and when the pointer moves out + /// of the view header. + void _removeViewHeaderHovering() { + if (_hoveringDate != null) { + _hoveringDate = null; + } + + if (_viewHeaderNotifier.value != null) { + _viewHeaderNotifier.value = null; } } - // Returns the timeline view as a child for the calendar view. - Widget _addTimelineView(double width, double height, String locale) { - final double viewHeaderHeight = - _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); - final double timeLabelSize = _getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); - final bool isResourceEnabled = - _isResourceEnabled(widget.calendar.dataSource, widget.view); - double resourceItemHeight = 0; - height -= (viewHeaderHeight + timeLabelSize); - if (isResourceEnabled) { - _updateProgrammaticSelectedResourceIndex(); - final double resourceViewSize = widget.calendar.resourceViewSettings.size; - resourceItemHeight = _getResourceItemHeight( - resourceViewSize, - (widget.height - viewHeaderHeight - timeLabelSize), - widget.calendar.resourceViewSettings, - widget.calendar.dataSource.resources.length); - height = resourceItemHeight * widget.resourceCollection.length; + void _removeAllWidgetHovering() { + if (_hoveringDate != null) { + _hoveringDate = null; } - return Stack(children: [ - Positioned( - top: 0, - left: 0, - right: 0, - height: viewHeaderHeight, - child: Container( - color: widget.calendar.viewHeaderStyle.backgroundColor ?? - widget.calendarTheme.viewHeaderBackgroundColor, - child: _getTimelineViewHeader(width, viewHeaderHeight, widget.locale), - ), - ), - Positioned( - top: viewHeaderHeight, - left: 0, - right: 0, - height: timeLabelSize, - child: ListView( - padding: const EdgeInsets.all(0.0), - controller: _timelineRulerController, - scrollDirection: Axis.horizontal, - physics: _CustomNeverScrollableScrollPhysics(), - children: [ - RepaintBoundary( - child: CustomPaint( - painter: _TimeRulerView( - _horizontalLinesCount, - _timeIntervalHeight, - widget.calendar.timeSlotViewSettings, - widget.calendar.cellBorderColor, - _isRTL, - locale, - widget.calendarTheme, - _isTimelineView(widget.view), - widget.visibleDates, - widget.textScaleFactor), - size: Size(width, timeLabelSize), - )), - ], - )), - Positioned( - top: viewHeaderHeight + timeLabelSize, - left: 0, - right: 0, - bottom: 0, - child: Scrollbar( - child: ListView( - padding: const EdgeInsets.all(0.0), - controller: _scrollController, - scrollDirection: Axis.horizontal, - physics: _CustomNeverScrollableScrollPhysics(), - children: [ - Container( - width: width, - child: Stack(children: [ - Scrollbar( - child: ListView( - padding: const EdgeInsets.all(0.0), - scrollDirection: Axis.vertical, - controller: - _timelineViewVerticalScrollController, - physics: isResourceEnabled - ? const ClampingScrollPhysics() - : const NeverScrollableScrollPhysics(), - children: [ - Stack(children: [ - RepaintBoundary( - child: _CalendarMultiChildContainer( - width: width, - height: height, - children: [ - RepaintBoundary( - child: _TimelineWidget( - _horizontalLinesCount, - widget.visibleDates, - widget - .calendar.timeSlotViewSettings, - _timeIntervalHeight, - widget.calendar.cellBorderColor, - _isRTL, - widget.calendarTheme, - _calendarCellNotifier, - _scrollController, - widget.regions, - resourceItemHeight, - widget.resourceCollection, - widget.textScaleFactor, - widget.isMobilePlatform, - widget.calendar.timeRegionBuilder, - width, - height)), - RepaintBoundary( - child: _addAppointmentPainter( - width, height, resourceItemHeight)) - ], - )), - RepaintBoundary( - child: CustomPaint( - painter: - _addSelectionView(resourceItemHeight), - size: Size(width, height), - ), - ), - ]), - ])), - ])), - ]), - )), - ]); - } - //// Handles the onTap callback for month cells, and view header of month - void _handleOnTapForMonth(TapUpDetails details) { - _handleTouchOnMonthView(details, null); + if (_viewHeaderNotifier.value != null) { + _viewHeaderNotifier.value = null; + } + + if (_calendarCellNotifier.value != null) { + _hoveringDate = null; + _calendarCellNotifier.value = null; + } + + if (_allDayNotifier.value != null) { + _allDayNotifier.value = null; + } + + if (_appointmentHoverNotifier.value != null) { + _appointmentHoverNotifier.value = null; + } } - /// Handles the tap and long press related functions for month view. - void _handleTouchOnMonthView( - TapUpDetails tapDetails, LongPressStartDetails longPressDetails) { - widget.removePicker(); - double xDetails, yDetails; - bool isTapCallback = false; - if (tapDetails != null) { - isTapCallback = true; - xDetails = tapDetails.localPosition.dx; - yDetails = tapDetails.localPosition.dy; - } else if (longPressDetails != null) { - xDetails = longPressDetails.localPosition.dx; - yDetails = longPressDetails.localPosition.dy; + void _updateHoveringForViewHeader(Offset localPosition, double xPosition, + double yPosition, double viewHeaderHeight) { + if (widget.calendar.onTap == null && widget.calendar.onLongPress == null) { + final bool isViewNavigationEnabled = + widget.calendar.allowViewNavigation && + widget.view != CalendarView.month && + widget.view != CalendarView.day && + widget.view != CalendarView.timelineDay; + if (!isViewNavigationEnabled) { + _removeAllWidgetHovering(); + return; + } } - final double viewHeaderHeight = - _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); - if (yDetails < viewHeaderHeight) { - if (isTapCallback) { - _handleOnTapForViewHeader(tapDetails, widget.width); - } else if (!isTapCallback) { - _handleOnLongPressForViewHeader(longPressDetails, widget.width); + if (yPosition < 0) { + if (_hoveringDate != null) { + _hoveringDate = null; } - } else if (yDetails > viewHeaderHeight) { - if (!widget.focusNode.hasFocus) { - widget.focusNode.requestFocus(); + + if (_viewHeaderNotifier.value != null) { + _viewHeaderNotifier.value = null; } - _AppointmentView appointmentView; - bool isMoreTapped = false; - if (!widget.isMobilePlatform && - widget.calendar.monthViewSettings.appointmentDisplayMode == - MonthAppointmentDisplayMode.appointment) { - appointmentView = _appointmentLayoutKey.currentState - ._getAppointmentViewOnPoint(xDetails, yDetails - viewHeaderHeight); - isMoreTapped = appointmentView != null && - appointmentView.startIndex == -1 && - appointmentView.endIndex == -1 && - appointmentView.position == -1 && - appointmentView.maxPositions == -1; + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; } - if (appointmentView == null) { - _drawSelection(xDetails, yDetails - viewHeaderHeight, 0); - } else { - _updateCalendarStateDetails._selectedDate = null; - widget.agendaSelectedDate.value = null; - _selectionPainter.selectedDate = null; - _selectionPainter._appointmentView = appointmentView; - _selectionNotifier.value = !_selectionNotifier.value; + if (_allDayNotifier.value != null) { + _allDayNotifier.value = null; } - widget.updateCalendarState(_updateCalendarStateDetails); - final DateTime selectedDate = - _getDateFromPosition(xDetails, yDetails - viewHeaderHeight, 0); - if (appointmentView == null) { - if (!isDateWithInDateRange(widget.calendar.minDate, - widget.calendar.maxDate, selectedDate) || - _isDateInDateCollection(widget.blackoutDates, selectedDate)) { - return; - } + if (_appointmentHoverNotifier.value != null) { + _appointmentHoverNotifier.value = null; + } + } - final int currentMonth = - widget.visibleDates[widget.visibleDates.length ~/ 2].month; + final DateTime? hoverDate = _getTappedViewHeaderDate( + Offset( + CalendarViewHelper.isTimelineView(widget.view) + ? localPosition.dx + : xPosition, + yPosition), + widget.width); - /// Check the selected cell date as trailing or leading date when - /// [SfCalendar] month not shown leading and trailing dates. - if (!_isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentMonth, - selectedDate)) { - return; - } + // Remove the hovering when the position not in cell regions. + if (hoverDate == null) { + _removeViewHeaderHovering(); - _handleMonthCellTapNavigation(selectedDate); - } + return; + } - if ((!isTapCallback && - _shouldRaiseCalendarLongPressCallback( - widget.calendar.onLongPress)) || - (isTapCallback && - _shouldRaiseCalendarTapCallback(widget.calendar.onTap))) { - final List selectedAppointments = appointmentView == null || - isMoreTapped - ? _getSelectedAppointments(selectedDate) - : [ - appointmentView.appointment._data ?? appointmentView.appointment - ]; - final CalendarElement selectedElement = appointmentView == null - ? CalendarElement.calendarCell - : isMoreTapped - ? CalendarElement.moreAppointmentRegion - : CalendarElement.appointment; - if (isTapCallback) { - _raiseCalendarTapCallback(widget.calendar, - date: selectedDate, - appointments: selectedAppointments, - element: selectedElement); - } else { - _raiseCalendarLongPressCallback(widget.calendar, - date: selectedDate, - appointments: selectedAppointments, - element: selectedElement); - } - } + if (!isDateWithInDateRange( + widget.calendar.minDate, widget.calendar.maxDate, hoverDate)) { + _removeViewHeaderHovering(); + + return; } - } - void _handleMonthCellTapNavigation(DateTime date) { - if (!widget.allowViewNavigation || - widget.view != CalendarView.month || - widget.calendar.monthViewSettings.showAgenda) { + if (widget.view == CalendarView.timelineMonth && + CalendarViewHelper.isDateInDateCollection( + widget.blackoutDates, hoverDate)) { + _removeViewHeaderHovering(); + return; } - widget.controller.view = CalendarView.day; - widget.controller.displayDate = date; - } + _hoveringDate = hoverDate; - //// Handles the onLongPress callback for month cells, and view header of month. - void _handleOnLongPressForMonth(LongPressStartDetails details) { - _handleTouchOnMonthView(null, details); - } + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; + } - //// Handles the onTap callback for timeline view cells, and view header of timeline. - void _handleOnTapForTimeline(TapUpDetails details) { - _handleTouchOnTimeline(details, null); - } + if (_allDayNotifier.value != null) { + _allDayNotifier.value = null; + } - /// Returns the index of resource value associated with the selected calendar - /// cell in timeline views. - int _getSelectedResourceIndex( - double yPosition, double viewHeaderHeight, double timeLabelSize) { - final int resourceCount = widget.calendar.dataSource != null && - widget.calendar.dataSource.resources != null - ? widget.calendar.dataSource.resources.length - : 0; - final double resourceItemHeight = _getResourceItemHeight( - widget.calendar.resourceViewSettings.size, - widget.height - viewHeaderHeight - timeLabelSize, - widget.calendar.resourceViewSettings, - resourceCount); - return (yPosition / resourceItemHeight).truncate(); + if (_appointmentHoverNotifier.value != null) { + _appointmentHoverNotifier.value = null; + } + + _viewHeaderNotifier.value = Offset(xPosition, yPosition); } - /// Handles the tap and long press related functions for timeline view. - void _handleTouchOnTimeline( - TapUpDetails tapDetails, LongPressStartDetails longPressDetails) { - widget.removePicker(); - double xDetails, yDetails; - bool isTapCallback = false; - if (tapDetails != null) { - isTapCallback = true; - xDetails = tapDetails.localPosition.dx; - yDetails = tapDetails.localPosition.dy; - } else if (longPressDetails != null) { - xDetails = longPressDetails.localPosition.dx; - yDetails = longPressDetails.localPosition.dy; + void _updatePointerHover(Offset globalPosition) { + if (widget.isMobilePlatform) { + return; } - final double viewHeaderHeight = - _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); + // ignore: avoid_as + final RenderBox box = context.findRenderObject() as RenderBox; + final Offset localPosition = box.globalToLocal(globalPosition); + double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + double allDayHeight = _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; - if (yDetails < viewHeaderHeight) { - if (isTapCallback) { - _handleOnTapForViewHeader(tapDetails, widget.width); - } else if (!isTapCallback) { - _handleOnLongPressForViewHeader(longPressDetails, widget.width); - } - } else if (yDetails > viewHeaderHeight) { - if (!widget.focusNode.hasFocus) { - widget.focusNode.requestFocus(); + /// All day panel and view header are arranged horizontally, + /// so get the maximum value from all day height and view header height and + /// use the value instead of adding of view header height and all day + /// height. + if (widget.view == CalendarView.day) { + if (allDayHeight > viewHeaderHeight) { + viewHeaderHeight = allDayHeight; } - widget.getCalendarState(_updateCalendarStateDetails); - DateTime selectedDate = _updateCalendarStateDetails._selectedDate; + allDayHeight = 0; + } - double xPosition = _scrollController.offset + xDetails; - double yPosition = yDetails - viewHeaderHeight; - final double timeLabelWidth = _getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + double xPosition; + double yPosition; + final bool isTimelineViews = CalendarViewHelper.isTimelineView(widget.view); + if (widget.view != CalendarView.month && !isTimelineViews) { + /// In LTR, remove the time ruler width value from the + /// touch x position while calculate the selected date from position. + xPosition = _isRTL ? localPosition.dx : localPosition.dx - timeLabelWidth; - if (yPosition < timeLabelWidth) { + if (localPosition.dy < viewHeaderHeight) { + if (widget.view == CalendarView.day) { + if ((_isRTL && localPosition.dx < widget.width - timeLabelWidth) || + (!_isRTL && localPosition.dx > timeLabelWidth)) { + _updateHoveringForAllDayPanel(localPosition.dx, localPosition.dy); + return; + } + + _updateHoveringForViewHeader( + localPosition, + _isRTL ? widget.width - localPosition.dx : localPosition.dx, + localPosition.dy, + viewHeaderHeight); + return; + } + + _updateHoveringForViewHeader(localPosition, localPosition.dx, + localPosition.dy, viewHeaderHeight); return; } - yPosition -= timeLabelWidth; + double panelHeight = + _updateCalendarStateDetails.allDayPanelHeight - _allDayHeight; + if (panelHeight < 0) { + panelHeight = 0; + } - CalendarResource selectedResource; + final double allDayExpanderHeight = + panelHeight * _allDayExpanderAnimation!.value; + final double allDayBottom = widget.view == CalendarView.day + ? viewHeaderHeight + : viewHeaderHeight + _allDayHeight + allDayExpanderHeight; + if (localPosition.dy > viewHeaderHeight && + localPosition.dy < allDayBottom) { + if ((_isRTL && localPosition.dx < widget.width - timeLabelWidth) || + (!_isRTL && localPosition.dx > timeLabelWidth)) { + _updateHoveringForAllDayPanel( + localPosition.dx, localPosition.dy - viewHeaderHeight); + } else { + _removeAllWidgetHovering(); + } - if (_isResourceEnabled(widget.calendar.dataSource, widget.view)) { - yPosition += _timelineViewVerticalScrollController.offset; - _selectedResourceIndex = _getSelectedResourceIndex( - yPosition, viewHeaderHeight, timeLabelWidth); - selectedResource = - widget.calendar.dataSource.resources[_selectedResourceIndex]; + return; } - _selectionPainter.selectedResourceIndex = _selectedResourceIndex; + yPosition = localPosition.dy - (viewHeaderHeight + allDayHeight); - if (_isRTL) { - xPosition = _scrollController.offset + - (_scrollController.position.viewportDimension - xDetails); - xPosition = (_scrollController.position.viewportDimension + - _scrollController.position.maxScrollExtent) - - xPosition; + final AppointmentView? appointment = + _appointmentLayout.getAppointmentViewOnPoint( + localPosition.dx, yPosition + _scrollController!.offset); + if (appointment != null) { + _updateHoveringForAppointment( + localPosition.dx, yPosition + _scrollController!.offset); + _hoveringDate = null; + return; } + } else { + xPosition = localPosition.dx; - final _AppointmentView appointmentView = _appointmentLayoutKey - .currentState - ._getAppointmentViewOnPoint(xPosition, yPosition); - if (appointmentView == null) { - _drawSelection(xDetails, yPosition, timeLabelWidth); - selectedDate = _selectionPainter.selectedDate; - } else { - if (selectedDate != null) { - selectedDate = null; - _selectionPainter.selectedDate = selectedDate; - _updateCalendarStateDetails._selectedDate = selectedDate; + /// Update the x position value with scroller offset and the value + /// assigned to mouse hover position. + /// mouse hover position value used for highlight the position + /// on all the calendar views. + if (isTimelineViews) { + if (_isRTL) { + xPosition = (_getSingleViewWidthForTimeLineView(this) * + widget.visibleDates.length) - + (_scrollController!.offset + + (_scrollController!.position.viewportDimension - + localPosition.dx)); + } else { + xPosition = localPosition.dx + _scrollController!.offset; } - - _selectionPainter._appointmentView = appointmentView; - _selectionNotifier.value = !_selectionNotifier.value; } - widget.updateCalendarState(_updateCalendarStateDetails); - - if ((!isTapCallback && - _shouldRaiseCalendarLongPressCallback( - widget.calendar.onLongPress)) || - (isTapCallback && - _shouldRaiseCalendarTapCallback(widget.calendar.onTap))) { - final DateTime selectedDate = - _getDateFromPosition(xDetails, yDetails - viewHeaderHeight, 0); - if (appointmentView == null) { - if (!isDateWithInDateRange(widget.calendar.minDate, - widget.calendar.maxDate, selectedDate) || - (widget.view == CalendarView.timelineMonth && - _isDateInDateCollection( - widget.calendar.blackoutDates, selectedDate))) { - return; - } + if (localPosition.dy < viewHeaderHeight) { + _updateHoveringForViewHeader( + localPosition, xPosition, localPosition.dy, viewHeaderHeight); + return; + } - /// Restrict the callback, while selected region as disabled - /// [TimeRegion]. - if (!_isEnabledRegion( - xDetails, selectedDate, _selectedResourceIndex)) { - return; - } + yPosition = localPosition.dy - viewHeaderHeight - timeLabelWidth; + if (CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + yPosition += _timelineViewVerticalScrollController!.offset; + } - if (isTapCallback) { - _raiseCalendarTapCallback(widget.calendar, - date: selectedDate, - appointments: null, - element: CalendarElement.calendarCell, - resource: selectedResource); - } else { - _raiseCalendarLongPressCallback(widget.calendar, - date: selectedDate, - appointments: null, - element: CalendarElement.calendarCell, - resource: selectedResource); - } - } else { - if (isTapCallback) { - _raiseCalendarTapCallback(widget.calendar, - date: selectedDate, - appointments: [ - appointmentView.appointment._data ?? - appointmentView.appointment - ], - element: CalendarElement.appointment, - resource: selectedResource); - } else { - _raiseCalendarLongPressCallback(widget.calendar, - date: selectedDate, - appointments: [ - appointmentView.appointment._data ?? - appointmentView.appointment - ], - element: CalendarElement.appointment, - resource: selectedResource); - } - } + final AppointmentView? appointment = + _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); + if (appointment != null) { + _updateHoveringForAppointment(xPosition, yPosition); + _hoveringDate = null; + return; } } - } - //// Handles the onLongPress callback for timeline view cells, and view header - //// of timeline. - void _handleOnLongPressForTimeline(LongPressStartDetails details) { - _handleTouchOnTimeline(null, details); - } + /// Remove the hovering when the position not in cell regions. + if (yPosition < 0) { + if (_hoveringDate != null) { + _hoveringDate = null; + } + + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; + } - void _updateAllDaySelection(_AppointmentView view, DateTime date) { - if (_allDaySelectionNotifier != null && - _allDaySelectionNotifier.value != null && - view == _allDaySelectionNotifier.value.appointmentView && - isSameDate(date, _allDaySelectionNotifier.value.selectedDate)) { return; } - _allDaySelectionNotifier?.value = _SelectionDetails(view, date); - } - - //// Handles the onTap callback for day view cells, all day panel, and view - //// header of day. - void _handleOnTapForDay(TapUpDetails details) { - _handleTouchOnDayView(details, null); - } + final DateTime? hoverDate = _getDateFromPosition( + isTimelineViews ? localPosition.dx : xPosition, + yPosition, + timeLabelWidth); - /// Handles the tap and long press related functions for day, week - /// work week views. - void _handleTouchOnDayView( - TapUpDetails tapDetails, LongPressStartDetails longPressDetails) { - widget.removePicker(); - double xDetails, yDetails; - bool isTappedCallback = false; - if (tapDetails != null) { - isTappedCallback = true; - xDetails = tapDetails.localPosition.dx; - yDetails = tapDetails.localPosition.dy; - } else if (longPressDetails != null) { - xDetails = longPressDetails.localPosition.dx; - yDetails = longPressDetails.localPosition.dy; - } - if (!widget.focusNode.hasFocus) { - widget.focusNode.requestFocus(); - } + /// Remove the hovering when the position not in cell regions or non active + /// cell regions. + final bool isMonthView = widget.view == CalendarView.month || + widget.view == CalendarView.timelineMonth; + final int timeInterval = CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings); + if (hoverDate == null || + (isMonthView && + !isDateWithInDateRange( + widget.calendar.minDate, widget.calendar.maxDate, hoverDate)) || + (!isMonthView && + !CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + hoverDate, + timeInterval))) { + if (_hoveringDate != null) { + _hoveringDate = null; + } - widget.getCalendarState(_updateCalendarStateDetails); - dynamic selectedAppointment; - List selectedAppointments; - CalendarElement targetElement; - DateTime selectedDate = _updateCalendarStateDetails._selectedDate; - final double timeLabelWidth = _getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; + } - final double viewHeaderHeight = widget.view == CalendarView.day - ? 0 - : _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); - final double allDayHeight = _isExpanded - ? _updateCalendarStateDetails._allDayPanelHeight - : _allDayHeight; - if (!_isRTL && - xDetails <= timeLabelWidth && - yDetails > viewHeaderHeight + allDayHeight) { return; } - if (_isRTL && - xDetails >= widget.width - timeLabelWidth && - yDetails > viewHeaderHeight + allDayHeight) { - return; - } + /// Check the hovering month cell date is blackout date. + if (isMonthView && + CalendarViewHelper.isDateInDateCollection( + widget.blackoutDates, hoverDate)) { + if (_hoveringDate != null) { + _hoveringDate = null; + } - if (yDetails < viewHeaderHeight) { - /// Check the touch position in time ruler view - /// If RTL, time ruler placed at right side, - /// else time ruler placed at left side. - if ((!_isRTL && xDetails <= timeLabelWidth) || - (_isRTL && widget.width - xDetails <= timeLabelWidth)) { - return; + /// Remove the existing cell hovering. + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; } - if (isTappedCallback) { - _handleOnTapForViewHeader(tapDetails, widget.width); - } else if (!isTappedCallback) { - _handleOnLongPressForViewHeader(longPressDetails, widget.width); + /// Remove the existing appointment hovering. + if (_appointmentHoverNotifier.value != null) { + _appointmentHoverNotifier.value = null; } return; - } else if (yDetails < viewHeaderHeight + allDayHeight) { - /// Check the touch position in view header when [CalendarView] is day - /// If RTL, view header placed at right side, - /// else view header placed at left side. - if (widget.view == CalendarView.day && - ((!_isRTL && xDetails <= timeLabelWidth) || - (_isRTL && widget.width - xDetails <= timeLabelWidth)) && - yDetails < - _getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view)) { - if (isTappedCallback) { - _handleOnTapForViewHeader(tapDetails, widget.width); - } else if (!isTappedCallback) { - _handleOnLongPressForViewHeader(longPressDetails, widget.width); - } + } - return; - } else if ((!_isRTL && timeLabelWidth >= xDetails) || - (_isRTL && xDetails > widget.width - timeLabelWidth)) { - /// Perform expand or collapse when the touch position on - /// expander icon in all day panel. - _expandOrCollapseAllDay(); - return; + final int hoveringResourceIndex = + _getSelectedResourceIndex(yPosition, viewHeaderHeight, timeLabelWidth); + + /// Restrict the hovering, while selected region as disabled [TimeRegion]. + if (((widget.view == CalendarView.day || + widget.view == CalendarView.week || + widget.view == CalendarView.workWeek) && + !_isEnabledRegion(yPosition, hoverDate, hoveringResourceIndex)) || + (isTimelineViews && + !_isEnabledRegion( + localPosition.dx, hoverDate, hoveringResourceIndex))) { + if (_hoveringDate != null) { + _hoveringDate = null; + } + + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; } + return; + } - final double yPosition = yDetails - viewHeaderHeight; - final _AppointmentView appointmentView = _getAllDayAppointmentOnPoint( - _updateCalendarStateDetails._allDayAppointmentViewCollection, - xDetails, - yPosition); + final int currentMonth = + widget.visibleDates[widget.visibleDates.length ~/ 2].month; - if (appointmentView == null) { - targetElement = CalendarElement.allDayPanel; - if (isTappedCallback) { - selectedDate = - _getTappedViewHeaderDate(tapDetails.localPosition, widget.width); - } else { - selectedDate = _getTappedViewHeaderDate( - longPressDetails.localPosition, widget.width); - } + /// Check the selected cell date as trailing or leading date when + /// [SfCalendar] month not shown leading and trailing dates. + if (!CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + hoverDate)) { + if (_hoveringDate != null) { + _hoveringDate = null; } - /// Check the count position tapped or not - bool isTappedOnCount = appointmentView != null && - _updateCalendarStateDetails._allDayPanelHeight > allDayHeight && - yPosition > allDayHeight - _kAllDayAppointmentHeight; - - /// Check the tap position inside the last appointment rendering position - /// when the panel as collapsed and it does not psoition does not have - /// appointment. - /// Eg., If July 8 have 3 all day appointments spanned to July 9 and - /// July 9 have 1 all day appointment spanned to July 10 then July 10 - /// appointment view does not shown and it only have count label. - /// If user tap on count label then the panel does not have appointment - /// view, because the view rendered after the end position, so calculate - /// the visible date cell appointment and it have appointments after - /// end position then perform expand operation. - if (appointmentView == null && - selectedDate != null && - _updateCalendarStateDetails._allDayPanelHeight > allDayHeight && - yPosition > allDayHeight - _kAllDayAppointmentHeight) { - final int currentSelectedIndex = - _getVisibleDateIndex(widget.visibleDates, selectedDate); - if (currentSelectedIndex != -1) { - final List<_AppointmentView> selectedIndexAppointment = - <_AppointmentView>[]; - for (int i = 0; - i < - _updateCalendarStateDetails - ._allDayAppointmentViewCollection.length; - i++) { - final _AppointmentView currentView = - _updateCalendarStateDetails._allDayAppointmentViewCollection[i]; - if (currentView.appointment == null) { - continue; - } - if (currentView.startIndex <= currentSelectedIndex && - currentView.endIndex > currentSelectedIndex) { - selectedIndexAppointment.add(currentView); - } - } + /// Remove the existing cell hovering. + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; + } - int maxPosition = 0; - if (selectedIndexAppointment.isNotEmpty) { - maxPosition = selectedIndexAppointment - .reduce((_AppointmentView currentAppView, - _AppointmentView nextAppView) => - currentAppView.maxPositions > nextAppView.maxPositions - ? currentAppView - : nextAppView) - .maxPositions; - } - final int endAppointmentPosition = - allDayHeight ~/ _kAllDayAppointmentHeight; - if (endAppointmentPosition < maxPosition) { - isTappedOnCount = true; - } - } + /// Remove the existing appointment hovering. + if (_appointmentHoverNotifier.value != null) { + _appointmentHoverNotifier.value = null; } - if (appointmentView != null && - (yPosition < allDayHeight - _kAllDayAppointmentHeight || - _updateCalendarStateDetails._allDayPanelHeight <= allDayHeight || - appointmentView.position + 1 >= appointmentView.maxPositions)) { - if (!isDateWithInDateRange( - widget.calendar.minDate, - widget.calendar.maxDate, - appointmentView.appointment._actualStartTime) || - !isDateWithInDateRange( - widget.calendar.minDate, - widget.calendar.maxDate, - appointmentView.appointment._actualEndTime)) { - return; - } - if (selectedDate != null) { - selectedDate = null; - _selectionPainter.selectedDate = selectedDate; - _updateCalendarStateDetails._selectedDate = selectedDate; - } + return; + } - _selectionPainter._appointmentView = null; - _selectionNotifier.value = !_selectionNotifier.value; - selectedAppointment = appointmentView.appointment; - selectedAppointments = null; - targetElement = CalendarElement.appointment; - _updateAllDaySelection(appointmentView, null); - } else if (isTappedOnCount) { - _expandOrCollapseAllDay(); + final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view); + + /// If resource enabled the selected date or time slot can be same but the + /// resource value differs hence to handle this scenario we are excluding + /// the following conditions, if resource enabled. + if (!isResourceEnabled) { + if ((widget.view == CalendarView.month && + isSameDate(_hoveringDate, hoverDate) && + _viewHeaderNotifier.value == null) || + (widget.view != CalendarView.month && + CalendarViewHelper.isSameTimeSlot(_hoveringDate, hoverDate) && + _viewHeaderNotifier.value == null)) { return; - } else if (appointmentView == null) { - _updateAllDaySelection(null, selectedDate); - _selectionPainter.selectedDate = null; - _selectionPainter._appointmentView = null; - _selectionNotifier.value = !_selectionNotifier.value; - _updateCalendarStateDetails._selectedDate = null; } - } else { - final double yPosition = - yDetails - viewHeaderHeight - allDayHeight + _scrollController.offset; - final _AppointmentView appointmentView = _appointmentLayoutKey - .currentState - ._getAppointmentViewOnPoint(xDetails, yPosition); - _allDaySelectionNotifier?.value = null; - if (appointmentView == null) { - if (_isRTL) { - _drawSelection(xDetails, yDetails - viewHeaderHeight - allDayHeight, - timeLabelWidth); - } else { - _drawSelection(xDetails - timeLabelWidth, - yDetails - viewHeaderHeight - allDayHeight, timeLabelWidth); - } - targetElement = CalendarElement.calendarCell; - } else { - if (selectedDate != null) { - selectedDate = null; - _selectionPainter.selectedDate = selectedDate; - _updateCalendarStateDetails._selectedDate = selectedDate; - } + } - _selectionPainter._appointmentView = appointmentView; - _selectionNotifier.value = !_selectionNotifier.value; - selectedAppointment = appointmentView.appointment; - targetElement = CalendarElement.appointment; - } + _hoveringDate = hoverDate; + + if (widget.view == CalendarView.month && + isSameDate(_selectionPainter!.selectedDate, _hoveringDate)) { + _calendarCellNotifier.value = null; + return; + } else if (widget.view != CalendarView.month && + CalendarViewHelper.isSameTimeSlot( + _selectionPainter!.selectedDate, _hoveringDate) && + hoveringResourceIndex == _selectedResourceIndex) { + _calendarCellNotifier.value = null; + return; } - widget.updateCalendarState(_updateCalendarStateDetails); - if ((!isTappedCallback && - _shouldRaiseCalendarLongPressCallback( - widget.calendar.onLongPress)) || - (isTappedCallback && - _shouldRaiseCalendarTapCallback(widget.calendar.onTap))) { - if (_selectionPainter.selectedDate != null && - targetElement != CalendarElement.allDayPanel) { - selectedAppointments = null; + if (widget.view != CalendarView.month && !isTimelineViews) { + yPosition += _scrollController!.offset; + } - final double yPosition = yDetails - viewHeaderHeight - allDayHeight; + if (_viewHeaderNotifier.value != null) { + _viewHeaderNotifier.value = null; + } - /// In LTR, remove the time ruler width value from the - /// touch x position while calculate the selected date value. - selectedDate = _getDateFromPosition( - !_isRTL ? xDetails - timeLabelWidth : xDetails, - yPosition, - timeLabelWidth); + if (_allDayNotifier.value != null) { + _allDayNotifier.value = null; + } - if (!isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, selectedDate)) { - return; - } + if (_appointmentHoverNotifier.value != null) { + _appointmentHoverNotifier.value = null; + } - /// Restrict the callback, while selected region as disabled - /// [TimeRegion]. - if (targetElement == CalendarElement.calendarCell && - !_isEnabledRegion( - yPosition, selectedDate, _selectedResourceIndex)) { - return; - } + _calendarCellNotifier.value = Offset(xPosition, yPosition); + } - if (isTappedCallback) { - _raiseCalendarTapCallback(widget.calendar, - date: _selectionPainter.selectedDate, - appointments: selectedAppointments, - element: targetElement); - } else { - _raiseCalendarLongPressCallback(widget.calendar, - date: _selectionPainter.selectedDate, - appointments: selectedAppointments, - element: targetElement); - } - } else if (selectedAppointment != null) { - selectedAppointments = [ - selectedAppointment._data ?? selectedAppointment - ]; + void _pointerEnterEvent(PointerEnterEvent event) { + _updatePointerHover(event.position); + } - /// In LTR, remove the time ruler width value from the - /// touch x position while calculate the selected date value. - selectedDate = _getDateFromPosition( - !_isRTL ? xDetails - timeLabelWidth : xDetails, 0, timeLabelWidth); + void _pointerHoverEvent(PointerHoverEvent event) { + _updatePointerHover(event.position); + } - if (isTappedCallback) { - _raiseCalendarTapCallback(widget.calendar, - date: selectedDate, - appointments: selectedAppointments, - element: CalendarElement.appointment); - } else { - _raiseCalendarLongPressCallback(widget.calendar, - date: selectedDate, - appointments: selectedAppointments, - element: CalendarElement.appointment); - } - } else if (selectedDate != null && - targetElement == CalendarElement.allDayPanel) { - if (isTappedCallback) { - _raiseCalendarTapCallback(widget.calendar, - date: selectedDate, appointments: null, element: targetElement); - } else { - _raiseCalendarLongPressCallback(widget.calendar, - date: selectedDate, appointments: null, element: targetElement); - } - } - } + void _pointerExitEvent(PointerExitEvent event) { + _hoveringDate = null; + _calendarCellNotifier.value = null; + _viewHeaderNotifier.value = null; + _appointmentHoverNotifier.value = null; + _allDayNotifier.value = null; } - /// Check the selected date region as enabled time region or not. - bool _isEnabledRegion(double y, DateTime selectedDate, int resourceIndex) { - if (widget.regions == null || - widget.regions.isEmpty || - widget.view == CalendarView.timelineMonth || - selectedDate == null) { - return true; + AppointmentView? _getAllDayAppointmentOnPoint( + List? appointmentCollection, double x, double y) { + if (appointmentCollection == null) { + return null; } - final double timeIntervalSize = _getTimeIntervalHeight( - widget.calendar, - widget.view, - widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); - - final double minuteHeight = timeIntervalSize / - _getTimeInterval(widget.calendar.timeSlotViewSettings); - final Duration startDuration = Duration( - hours: widget.calendar.timeSlotViewSettings.startHour.toInt(), - minutes: ((widget.calendar.timeSlotViewSettings.startHour - - widget.calendar.timeSlotViewSettings.startHour.toInt()) * - 60) - .toInt()); - int minutes; - if (_isTimelineView(widget.view)) { - final double viewWidth = _timeIntervalHeight * _horizontalLinesCount; - if (_isRTL) { - minutes = ((_scrollController.offset + - (_scrollController.position.viewportDimension - y)) % - viewWidth) ~/ - minuteHeight; - } else { - minutes = ((_scrollController.offset + y) % viewWidth) ~/ minuteHeight; + AppointmentView? selectedAppointmentView; + for (int i = 0; i < appointmentCollection.length; i++) { + final AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.appointment != null && + appointmentView.appointmentRect != null && + appointmentView.appointmentRect!.left <= x && + appointmentView.appointmentRect!.right >= x && + appointmentView.appointmentRect!.top <= y && + appointmentView.appointmentRect!.bottom >= y) { + selectedAppointmentView = appointmentView; + break; } - } else { - minutes = (_scrollController.offset + y) ~/ minuteHeight; } - final DateTime date = DateTime(selectedDate.year, selectedDate.month, - selectedDate.day, 0, minutes + startDuration.inMinutes, 0); - for (int i = 0; i < widget.regions.length; i++) { - final TimeRegion region = widget.regions[i]; - if (region.enablePointerInteraction || - region._actualStartTime.isAfter(date) || - region._actualEndTime.isBefore(date)) { - continue; - } + return selectedAppointmentView; + } - /// Condition added ensure that the region is disabled only on the - /// specified resource slot, for other resources it must be enabled. - if (_isResourceEnabled(widget.calendar.dataSource, widget.view) && - !region.enablePointerInteraction && - resourceIndex != -1 && - region.resourceIds != null && - region.resourceIds.isNotEmpty && - !region.resourceIds - .contains(widget.resourceCollection[resourceIndex].id)) { - continue; - } + List _getSelectedAppointments(DateTime selectedDate) { + return (widget.calendar.dataSource != null && + !AppointmentHelper.isCalendarAppointment( + widget.calendar.dataSource!)) + ? CalendarViewHelper.getCustomAppointments( + AppointmentHelper.getSelectedDateAppointments( + _updateCalendarStateDetails.appointments, + widget.calendar.timeZone, + selectedDate)) + : (AppointmentHelper.getSelectedDateAppointments( + _updateCalendarStateDetails.appointments, + widget.calendar.timeZone, + selectedDate)); + } - return false; + DateTime? _getDateFromPositionForMonth( + double cellWidth, double cellHeight, double x, double y) { + final int rowIndex = (x / cellWidth).truncate(); + final int columnIndex = (y / cellHeight).truncate(); + int index = 0; + if (_isRTL) { + index = (columnIndex * DateTime.daysPerWeek) + + (DateTime.daysPerWeek - rowIndex) - + 1; + } else { + index = (columnIndex * DateTime.daysPerWeek) + rowIndex; } - return true; + if (index < 0 || index >= widget.visibleDates.length) { + return null; + } + + return widget.visibleDates[index]; } - //// Handles the onLongPress callback for day view cells, all day panel and - //// view header of day. - void _handleOnLongPressForDay(LongPressStartDetails details) { - _handleTouchOnDayView(null, details); + DateTime _getDateFromPositionForDay( + double cellWidth, double cellHeight, double x, double y) { + final int columnIndex = + ((_scrollController!.offset + y) / cellHeight).truncate(); + final double time = ((CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings) / + 60) * + columnIndex) + + widget.calendar.timeSlotViewSettings.startHour; + final int hour = time.toInt(); + final int minute = ((time - hour) * 60).round(); + return DateTime(widget.visibleDates[0].year, widget.visibleDates[0].month, + widget.visibleDates[0].day, hour, minute); } - //// Handles the on tap callback for view header - void _handleOnTapForViewHeader(TapUpDetails details, double width) { - final DateTime tappedDate = - _getTappedViewHeaderDate(details.localPosition, width); - _handleViewHeaderTapNavigation(tappedDate); - if (!_shouldRaiseCalendarTapCallback(widget.calendar.onTap)) { - return; + DateTime? _getDateFromPositionForWeek( + double cellWidth, double cellHeight, double x, double y) { + final int columnIndex = + ((_scrollController!.offset + y) / cellHeight).truncate(); + final double time = ((CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings) / + 60) * + columnIndex) + + widget.calendar.timeSlotViewSettings.startHour; + final int hour = time.toInt(); + final int minute = ((time - hour) * 60).round(); + int rowIndex = (x / cellWidth).truncate(); + if (_isRTL) { + rowIndex = (widget.visibleDates.length - rowIndex) - 1; } - _raiseCalendarTapCallback(widget.calendar, - date: tappedDate, element: CalendarElement.viewHeader); - } - - //// Handles the on long press callback for view header - void _handleOnLongPressForViewHeader( - LongPressStartDetails details, double width) { - final DateTime tappedDate = - _getTappedViewHeaderDate(details.localPosition, width); - _handleViewHeaderTapNavigation(tappedDate); - if (!_shouldRaiseCalendarLongPressCallback(widget.calendar.onLongPress)) { - return; + if (rowIndex < 0 || rowIndex >= widget.visibleDates.length) { + return null; } - _raiseCalendarLongPressCallback(widget.calendar, - date: tappedDate, element: CalendarElement.viewHeader); + final DateTime date = widget.visibleDates[rowIndex]; + + return DateTime(date.year, date.month, date.day, hour, minute); } - void _handleViewHeaderTapNavigation(DateTime date) { - if (!widget.allowViewNavigation || - widget.view == CalendarView.day || - widget.view == CalendarView.timelineDay || - widget.view == CalendarView.month) { - return; + DateTime? _getDateFromPositionForTimeline( + double cellWidth, double cellHeight, double x, double y) { + int rowIndex, columnIndex; + if (_isRTL) { + rowIndex = (((_scrollController!.offset % + _getSingleViewWidthForTimeLineView(this)) + + (_scrollController!.position.viewportDimension - x)) / + cellWidth) + .truncate(); + } else { + rowIndex = (((_scrollController!.offset % + _getSingleViewWidthForTimeLineView(this)) + + x) / + cellWidth) + .truncate(); } - - if (!isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, date) || - (widget.controller.view == CalendarView.timelineMonth && - _isDateInDateCollection(widget.blackoutDates, date))) { - return; + columnIndex = + (_scrollController!.offset / _getSingleViewWidthForTimeLineView(this)) + .truncate(); + if (rowIndex >= _horizontalLinesCount!) { + columnIndex += rowIndex ~/ _horizontalLinesCount!; + rowIndex = (rowIndex % _horizontalLinesCount!).toInt(); + } + final double time = ((CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings) / + 60) * + rowIndex) + + widget.calendar.timeSlotViewSettings.startHour; + final int hour = time.toInt(); + final int minute = ((time - hour) * 60).round(); + if (columnIndex < 0) { + columnIndex = 0; + } else if (columnIndex > widget.visibleDates.length) { + columnIndex = widget.visibleDates.length - 1; } - if (widget.view == CalendarView.week || - widget.view == CalendarView.workWeek) { - widget.controller.view = CalendarView.day; - } else { - widget.controller.view = CalendarView.timelineDay; + if (columnIndex < 0 || columnIndex >= widget.visibleDates.length) { + return null; } - widget.controller.displayDate = date; + final DateTime date = widget.visibleDates[columnIndex]; + + return DateTime(date.year, date.month, date.day, hour, minute); } - DateTime _getTappedViewHeaderDate(Offset localPosition, double width) { - int index = 0; - final double timeLabelViewWidth = _getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); - if (!_isTimelineView(widget.view)) { - double cellWidth = 0; - if (widget.view != CalendarView.month) { - cellWidth = (width - timeLabelViewWidth) / widget.visibleDates.length; + DateTime? _getDateFromPosition(double x, double y, double timeLabelWidth) { + double cellWidth = 0; + double cellHeight = 0; + final double width = widget.width - timeLabelWidth; + switch (widget.view) { + case CalendarView.schedule: + return null; + case CalendarView.month: + { + if (x > width || x < 0) { + return null; + } - /// Set index value as 0 when calendar view as day because day view hold - /// single visible date. - if (widget.view == CalendarView.day) { - index = 0; - } else { - index = ((localPosition.dx - (_isRTL ? 0 : timeLabelViewWidth)) / - cellWidth) - .truncate(); + cellWidth = width / DateTime.daysPerWeek; + cellHeight = (widget.height - + CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view)) / + widget.calendar.monthViewSettings.numberOfWeeksInView; + return _getDateFromPositionForMonth(cellWidth, cellHeight, x, y); } - } else { - cellWidth = width / _kNumberOfDaysInWeek; - index = (localPosition.dx / cellWidth).truncate(); - } + case CalendarView.day: + { + if (y >= _timeIntervalHeight * _horizontalLinesCount! || + x > width || + x < 0) { + return null; + } + cellWidth = width; + cellHeight = _timeIntervalHeight; + return _getDateFromPositionForDay(cellWidth, cellHeight, x, y); + } + case CalendarView.week: + case CalendarView.workWeek: + { + if (y >= _timeIntervalHeight * _horizontalLinesCount! || + x > width || + x < 0) { + return null; + } + cellWidth = width / widget.visibleDates.length; + cellHeight = _timeIntervalHeight; + return _getDateFromPositionForWeek(cellWidth, cellHeight, x, y); + } + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + { + final double viewWidth = _timeIntervalHeight * + (_horizontalLinesCount! * widget.visibleDates.length); + if ((!_isRTL && x >= viewWidth) || + (_isRTL && x < (widget.width - viewWidth))) { + return null; + } + cellWidth = _timeIntervalHeight; + cellHeight = widget.height; + return _getDateFromPositionForTimeline(cellWidth, cellHeight, x, y); + } + } + } - /// Calculate the RTL based value of index when the widget direction as - /// RTL. - if (_isRTL && widget.view != CalendarView.month) { - index = widget.visibleDates.length - index - 1; - } else if (_isRTL && widget.view == CalendarView.month) { - index = _kNumberOfDaysInWeek - index - 1; - } + void _drawSelection(double x, double y, double timeLabelWidth) { + final DateTime? selectedDate = _getDateFromPosition(x, y, timeLabelWidth); + final bool isMonthView = widget.view == CalendarView.month || + widget.view == CalendarView.timelineMonth; + final int timeInterval = CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings); + if (selectedDate == null || + (isMonthView && + !isDateWithInDateRange(widget.calendar.minDate, + widget.calendar.maxDate, selectedDate)) || + (!isMonthView && + !CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + selectedDate, + timeInterval))) { + return; + } - if (index < 0 || index >= widget.visibleDates.length) { - return null; - } + /// Restrict the selection update, while selected region as disabled + /// [TimeRegion]. + if (((widget.view == CalendarView.day || + widget.view == CalendarView.week || + widget.view == CalendarView.workWeek) && + !_isEnabledRegion(y, selectedDate, _selectedResourceIndex)) || + (CalendarViewHelper.isTimelineView(widget.view) && + !_isEnabledRegion(x, selectedDate, _selectedResourceIndex))) { + return; + } - return widget.visibleDates[index]; - } else { - index = ((_scrollController.offset + - (_isRTL - ? _scrollController.position.viewportDimension - - localPosition.dx - : localPosition.dx)) / - _getSingleViewWidthForTimeLineView(this)) - .truncate(); + if ((widget.view == CalendarView.month || + widget.view == CalendarView.timelineMonth) && + CalendarViewHelper.isDateInDateCollection( + widget.blackoutDates, selectedDate)) { + return; + } - if (index < 0 || index >= widget.visibleDates.length) { - return null; + if (widget.view == CalendarView.month) { + final int currentMonth = + widget.visibleDates[widget.visibleDates.length ~/ 2].month; + + /// Check the selected cell date as trailing or leading date when + /// [SfCalendar] month not shown leading and trailing dates. + if (!CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + selectedDate)) { + return; } - return widget.visibleDates[index]; + widget.agendaSelectedDate.value = selectedDate; } - } - void _updateHoveringForAppointment(double xPosition, double yPosition) { - if (_viewHeaderNotifier.value != null) { - _viewHeaderNotifier.value = null; - } + _updateCalendarStateDetails.selectedDate = selectedDate; + _selectionPainter!.selectedDate = selectedDate; + _selectionPainter!.appointmentView = null; + _selectionNotifier.value = !_selectionNotifier.value; + } - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; + _SelectionPainter _addSelectionView([double? resourceItemHeight]) { + AppointmentView? appointmentView; + if (_selectionPainter?.appointmentView != null) { + appointmentView = _selectionPainter!.appointmentView; } - if (_allDayNotifier.value != null) { - _allDayNotifier.value = null; - } + _selectionPainter = _SelectionPainter( + widget.calendar, + widget.view, + widget.visibleDates, + _updateCalendarStateDetails.selectedDate, + widget.calendar.selectionDecoration, + _timeIntervalHeight, + widget.calendarTheme, + _selectionNotifier, + _isRTL, + _selectedResourceIndex, + resourceItemHeight, (UpdateCalendarStateDetails details) { + _getPainterProperties(details); + }); - if (_hoveringDate != null) { - _hoveringDate = null; + if (appointmentView != null && + _updateCalendarStateDetails.visibleAppointments + .contains(appointmentView.appointment)) { + _selectionPainter!.appointmentView = appointmentView; } - _appointmentHoverNotifier.value = Offset(xPosition, yPosition); + return _selectionPainter!; } - void _updateHoveringForAllDayPanel(double xPosition, double yPosition) { - if (_viewHeaderNotifier.value != null) { - _viewHeaderNotifier.value = null; - } + Widget _getTimelineViewHeader(double width, double height, String locale) { + _timelineViewHeader = TimelineViewHeaderView( + widget.visibleDates, + _timelineViewHeaderScrollController!, + _timelineViewHeaderNotifier, + widget.calendar.viewHeaderStyle, + widget.calendar.timeSlotViewSettings, + CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view), + _isRTL, + widget.calendar.todayHighlightColor ?? + widget.calendarTheme.todayHighlightColor, + widget.calendar.todayTextStyle, + widget.locale, + widget.calendarTheme, + widget.calendar.minDate, + widget.calendar.maxDate, + _viewHeaderNotifier, + widget.calendar.cellBorderColor, + widget.blackoutDates, + widget.calendar.blackoutDatesTextStyle, + widget.textScaleFactor); + return ListView( + padding: const EdgeInsets.all(0.0), + controller: _timelineViewHeaderScrollController, + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + children: [ + CustomPaint( + painter: _timelineViewHeader, + size: Size(width, height), + ) + ]); + } +} - if (_calendarCellNotifier.value != null) { - _hoveringDate = null; - _calendarCellNotifier.value = null; - } +class _ViewHeaderViewPainter extends CustomPainter { + _ViewHeaderViewPainter( + this.visibleDates, + this.view, + this.viewHeaderStyle, + this.timeSlotViewSettings, + this.timeLabelWidth, + this.viewHeaderHeight, + this.monthViewSettings, + this.isRTL, + this.locale, + this.calendarTheme, + this.todayHighlightColor, + this.todayTextStyle, + this.cellBorderColor, + this.minDate, + this.maxDate, + this.viewHeaderNotifier, + this.textScaleFactor) + : super(repaint: viewHeaderNotifier); - if (_appointmentHoverNotifier.value != null) { - _appointmentHoverNotifier.value = null; - } + final CalendarView view; + final ViewHeaderStyle viewHeaderStyle; + final TimeSlotViewSettings timeSlotViewSettings; + final MonthViewSettings monthViewSettings; + final List visibleDates; + final double timeLabelWidth; + final double viewHeaderHeight; + final SfCalendarThemeData calendarTheme; + final bool isRTL; + final String locale; + final Color? todayHighlightColor; + final TextStyle? todayTextStyle; + final Color? cellBorderColor; + final DateTime minDate; + final DateTime maxDate; + final ValueNotifier viewHeaderNotifier; + final double textScaleFactor; + Paint _circlePainter = Paint(); + TextPainter _dayTextPainter = TextPainter(), _dateTextPainter = TextPainter(); - if (_hoveringDate != null) { - _hoveringDate = null; + @override + void paint(Canvas canvas, Size size) { + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + double width = size.width; + width = _getViewHeaderWidth(width); + + /// Initializes the default text style for the texts in view header of + /// calendar. + final TextStyle viewHeaderDayStyle = + viewHeaderStyle.dayTextStyle ?? calendarTheme.viewHeaderDayTextStyle; + final TextStyle viewHeaderDateStyle = + viewHeaderStyle.dateTextStyle ?? calendarTheme.viewHeaderDateTextStyle; + + final DateTime today = DateTime.now(); + if (view != CalendarView.month) { + _addViewHeaderForTimeSlotViews( + canvas, size, viewHeaderDayStyle, viewHeaderDateStyle, width, today); + } else { + _addViewHeaderForMonthView( + canvas, size, viewHeaderDayStyle, width, today); } - - _allDayNotifier.value = Offset(xPosition, yPosition); } - /// Removes the view header hovering in multiple occasions, when the pointer - /// hovering the disabled or blackout dates, and when the pointer moves out - /// of the view header. - void _removeViewHeaderHovering() { - if (_hoveringDate != null) { - _hoveringDate = null; - } - - if (_viewHeaderNotifier.value != null) { - _viewHeaderNotifier.value = null; + void _addViewHeaderForMonthView(Canvas canvas, Size size, + TextStyle viewHeaderDayStyle, double width, DateTime today) { + TextStyle dayTextStyle = viewHeaderDayStyle; + double xPosition = 0; + double yPosition = 0; + if (isRTL) { + xPosition = size.width - width; } - } + bool hasToday = false; + for (int i = 0; i < DateTime.daysPerWeek; i++) { + final DateTime currentDate = visibleDates[i]; + String dayText = DateFormat(monthViewSettings.dayFormat, locale) + .format(currentDate) + .toString() + .toUpperCase(); + + dayText = _updateViewHeaderFormat(monthViewSettings.dayFormat, dayText); + + hasToday = monthViewSettings.numberOfWeeksInView > 0 && + monthViewSettings.numberOfWeeksInView < 6 + ? true + : visibleDates[visibleDates.length ~/ 2].month == today.month + ? true + : false; + + if (hasToday && + isDateWithInDateRange( + visibleDates[0], visibleDates[visibleDates.length - 1], today) && + currentDate.weekday == today.weekday) { + dayTextStyle = todayTextStyle != null + ? todayTextStyle!.copyWith( + fontSize: viewHeaderDayStyle.fontSize, + color: todayHighlightColor) + : viewHeaderDayStyle.copyWith(color: todayHighlightColor); + } else { + dayTextStyle = viewHeaderDayStyle; + } - void _removeAllWidgetHovering() { - if (_hoveringDate != null) { - _hoveringDate = null; - } + _updateDayTextPainter(dayTextStyle, width, dayText); - if (_viewHeaderNotifier.value != null) { - _viewHeaderNotifier.value = null; - } + if (yPosition == 0) { + yPosition = (viewHeaderHeight - _dayTextPainter.height) / 2; + } - if (_calendarCellNotifier.value != null) { - _hoveringDate = null; - _calendarCellNotifier.value = null; - } + if (viewHeaderNotifier.value != null) { + _addMouseHoverForMonth(canvas, size, xPosition, yPosition, width); + } - if (_allDayNotifier.value != null) { - _allDayNotifier.value = null; - } + _dayTextPainter.paint( + canvas, + Offset( + xPosition + (width / 2 - _dayTextPainter.width / 2), yPosition)); - if (_appointmentHoverNotifier.value != null) { - _appointmentHoverNotifier.value = null; + if (isRTL) { + xPosition -= width; + } else { + xPosition += width; + } } } - void _updateHoveringForViewHeader(Offset localPosition, double xPosition, - double yPosition, double viewHeaderHeight) { - if (widget.calendar.onTap == null && widget.calendar.onLongPress == null) { - final bool isViewNavigationEnabled = - widget.calendar.allowViewNavigation && - widget.view != CalendarView.month && - widget.view != CalendarView.day && - widget.view != CalendarView.timelineDay; - if (!isViewNavigationEnabled) { - _removeAllWidgetHovering(); - return; - } + void _addViewHeaderForTimeSlotViews( + Canvas canvas, + Size size, + TextStyle viewHeaderDayStyle, + TextStyle viewHeaderDateStyle, + double width, + DateTime today) { + double xPosition, yPosition; + final double labelWidth = + view == CalendarView.day && timeLabelWidth < 50 ? 50 : timeLabelWidth; + TextStyle dayTextStyle = viewHeaderDayStyle; + TextStyle dateTextStyle = viewHeaderDateStyle; + const double topPadding = 5; + if (view == CalendarView.day) { + width = labelWidth; } - if (yPosition < 0) { - if (_hoveringDate != null) { - _hoveringDate = null; - } - - if (_viewHeaderNotifier.value != null) { - _viewHeaderNotifier.value = null; - } - - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; + xPosition = view == CalendarView.day ? 0 : timeLabelWidth; + yPosition = 2; + final double cellWidth = width / visibleDates.length; + if (isRTL && view != CalendarView.day) { + xPosition = size.width - timeLabelWidth - cellWidth; + } + for (int i = 0; i < visibleDates.length; i++) { + final DateTime currentDate = visibleDates[i]; + String dayText = DateFormat(timeSlotViewSettings.dayFormat, locale) + .format(currentDate) + .toString() + .toUpperCase(); + + dayText = + _updateViewHeaderFormat(timeSlotViewSettings.dayFormat, dayText); + + final String dateText = DateFormat(timeSlotViewSettings.dateFormat) + .format(currentDate) + .toString(); + final bool isToday = isSameDate(currentDate, today); + if (isToday) { + dayTextStyle = todayTextStyle != null + ? todayTextStyle!.copyWith( + fontSize: viewHeaderDayStyle.fontSize, + color: todayHighlightColor) + : viewHeaderDayStyle.copyWith(color: todayHighlightColor); + dateTextStyle = todayTextStyle != null + ? todayTextStyle!.copyWith(fontSize: viewHeaderDateStyle.fontSize) + : viewHeaderDateStyle.copyWith( + color: calendarTheme.todayTextStyle.color); + } else { + dayTextStyle = viewHeaderDayStyle; + dateTextStyle = viewHeaderDateStyle; } - if (_allDayNotifier.value != null) { - _allDayNotifier.value = null; + if (!isDateWithInDateRange(minDate, maxDate, currentDate)) { + if (calendarTheme.brightness == Brightness.light) { + dayTextStyle = dayTextStyle.copyWith(color: Colors.black26); + dateTextStyle = dateTextStyle.copyWith(color: Colors.black26); + } else { + dayTextStyle = dayTextStyle.copyWith(color: Colors.white38); + dateTextStyle = dateTextStyle.copyWith(color: Colors.white38); + } } - if (_appointmentHoverNotifier.value != null) { - _appointmentHoverNotifier.value = null; - } - } + _updateDayTextPainter(dayTextStyle, width, dayText); - final DateTime hoverDate = _getTappedViewHeaderDate( - Offset(_isTimelineView(widget.view) ? localPosition.dx : xPosition, - yPosition), - widget.width); + final TextSpan dateTextSpan = TextSpan( + text: dateText, + style: dateTextStyle, + ); - // Remove the hovering when the position not in cell regions. - if (hoverDate == null) { - _removeViewHeaderHovering(); + _dateTextPainter.text = dateTextSpan; + _dateTextPainter.textDirection = TextDirection.ltr; + _dateTextPainter.textAlign = TextAlign.left; + _dateTextPainter.textWidthBasis = TextWidthBasis.longestLine; + _dateTextPainter.textScaleFactor = textScaleFactor; - return; - } + _dateTextPainter.layout(minWidth: 0, maxWidth: width); - if (!isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, hoverDate)) { - _removeViewHeaderHovering(); + /// To calculate the day start position by width and day painter + final double dayXPosition = (cellWidth - _dayTextPainter.width) / 2; - return; - } + /// To calculate the date start position by width and date painter + final double dateXPosition = (cellWidth - _dateTextPainter.width) / 2; - if (widget.view == CalendarView.timelineMonth && - _isDateInDateCollection(widget.blackoutDates, hoverDate)) { - _removeViewHeaderHovering(); + const int inBetweenPadding = 2; + yPosition = size.height / 2 - + (_dayTextPainter.height + + topPadding + + _dateTextPainter.height + + inBetweenPadding) / + 2; - return; - } + _dayTextPainter.paint( + canvas, Offset(xPosition + dayXPosition, yPosition)); - _hoveringDate = hoverDate; + if (isToday) { + _drawTodayCircle( + canvas, + xPosition + dateXPosition, + yPosition + topPadding + _dayTextPainter.height + inBetweenPadding, + _dateTextPainter); + } - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; - } + if (viewHeaderNotifier.value != null) { + _addMouseHoverForTimeSlotView(canvas, size, xPosition, yPosition, + dateXPosition, topPadding, isToday, inBetweenPadding); + } - if (_allDayNotifier.value != null) { - _allDayNotifier.value = null; + _dateTextPainter.paint( + canvas, + Offset( + xPosition + dateXPosition, + yPosition + + topPadding + + _dayTextPainter.height + + inBetweenPadding)); + + if (isRTL) { + xPosition -= cellWidth; + } else { + xPosition += cellWidth; + } } + } - if (_appointmentHoverNotifier.value != null) { - _appointmentHoverNotifier.value = null; + void _addMouseHoverForMonth(Canvas canvas, Size size, double xPosition, + double yPosition, double width) { + if (xPosition + (width / 2 - _dayTextPainter.width / 2) <= + viewHeaderNotifier.value!.dx && + xPosition + + (width / 2 - _dayTextPainter.width / 2) + + _dayTextPainter.width >= + viewHeaderNotifier.value!.dx && + yPosition - 5 <= viewHeaderNotifier.value!.dy && + (yPosition + size.height) - 5 >= viewHeaderNotifier.value!.dy) { + _drawTodayCircle( + canvas, + xPosition + (width / 2 - _dayTextPainter.width / 2), + yPosition, + _dayTextPainter, + hoveringColor: (calendarTheme.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withOpacity(0.04)); } - - _viewHeaderNotifier.value = Offset(xPosition, yPosition); } - void _updatePointerHover(Offset globalPosition) { - if (widget.isMobilePlatform) { - return; + void _addMouseHoverForTimeSlotView( + Canvas canvas, + Size size, + double xPosition, + double yPosition, + double dateXPosition, + double topPadding, + bool isToday, + int padding) { + if (xPosition + dateXPosition <= viewHeaderNotifier.value!.dx && + xPosition + dateXPosition + _dateTextPainter.width >= + viewHeaderNotifier.value!.dx) { + final Color hoveringColor = isToday + ? Colors.black.withOpacity(0.12) + : (calendarTheme.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withOpacity(0.04); + _drawTodayCircle( + canvas, + xPosition + dateXPosition, + yPosition + topPadding + _dayTextPainter.height + padding, + _dateTextPainter, + hoveringColor: hoveringColor); } + } - final RenderBox box = context.findRenderObject(); - final Offset localPosition = box.globalToLocal(globalPosition); - double viewHeaderHeight = - _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view); - final double timeLabelWidth = _getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); - double allDayHeight = _isExpanded - ? _updateCalendarStateDetails._allDayPanelHeight - : _allDayHeight; - - /// All day panel and view header are arranged horizontally, - /// so get the maximum value from all day height and view header height and - /// use the value instead of adding of view header height and all day - /// height. - if (widget.view == CalendarView.day) { - if (allDayHeight > viewHeaderHeight) { - viewHeaderHeight = allDayHeight; - } - - allDayHeight = 0; + String _updateViewHeaderFormat(String dayFormat, String dayText) { + switch (view) { + case CalendarView.day: + case CalendarView.schedule: + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + break; + case CalendarView.month: + case CalendarView.week: + case CalendarView.workWeek: + { + //// EE format value shows the week days as S, M, T, W, T, F, S. + if (dayFormat == 'EE' && (locale.contains('en'))) { + return dayText[0]; + } + } } - double xPosition; - double yPosition; - final bool isTimelineViews = _isTimelineView(widget.view); - if (widget.view != CalendarView.month && !isTimelineViews) { - /// In LTR, remove the time ruler width value from the - /// touch x position while calculate the selected date from position. - xPosition = _isRTL ? localPosition.dx : localPosition.dx - timeLabelWidth; - - if (localPosition.dy < viewHeaderHeight) { - if (widget.view == CalendarView.day) { - if ((_isRTL && localPosition.dx < widget.width - timeLabelWidth) || - (!_isRTL && localPosition.dx > timeLabelWidth)) { - _updateHoveringForAllDayPanel(localPosition.dx, localPosition.dy); - return; - } + return dayText; + } - _updateHoveringForViewHeader( - localPosition, - _isRTL ? widget.width - localPosition.dx : localPosition.dx, - localPosition.dy, - viewHeaderHeight); - return; - } + void _updateDayTextPainter( + TextStyle dayTextStyle, double width, String dayText) { + final TextSpan dayTextSpan = TextSpan( + text: dayText, + style: dayTextStyle, + ); - _updateHoveringForViewHeader(localPosition, localPosition.dx, - localPosition.dy, viewHeaderHeight); - return; - } + _dayTextPainter.text = dayTextSpan; + _dayTextPainter.textDirection = TextDirection.ltr; + _dayTextPainter.textAlign = TextAlign.left; + _dayTextPainter.textWidthBasis = TextWidthBasis.longestLine; + _dayTextPainter.textScaleFactor = textScaleFactor; - double panelHeight = - _updateCalendarStateDetails._allDayPanelHeight - _allDayHeight; - if (panelHeight < 0) { - panelHeight = 0; - } + _dayTextPainter.layout(minWidth: 0, maxWidth: width); + } - final double allDayExpanderHeight = - panelHeight * _allDayExpanderAnimation.value; - final double allDayBottom = widget.view == CalendarView.day - ? viewHeaderHeight - : viewHeaderHeight + _allDayHeight + allDayExpanderHeight; - if (localPosition.dy > viewHeaderHeight && - localPosition.dy < allDayBottom) { - if ((_isRTL && localPosition.dx < widget.width - timeLabelWidth) || - (!_isRTL && localPosition.dx > timeLabelWidth)) { - _updateHoveringForAllDayPanel( - localPosition.dx, localPosition.dy - viewHeaderHeight); - } else { - _removeAllWidgetHovering(); - } + double _getViewHeaderWidth(double width) { + switch (view) { + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + case CalendarView.schedule: + return 0; + case CalendarView.month: + return width / DateTime.daysPerWeek; + case CalendarView.day: + return timeLabelWidth; + case CalendarView.week: + case CalendarView.workWeek: + return width - timeLabelWidth; + } + } - return; - } + @override + bool shouldRepaint(_ViewHeaderViewPainter oldDelegate) { + final _ViewHeaderViewPainter oldWidget = oldDelegate; + return oldWidget.visibleDates != visibleDates || + oldWidget.viewHeaderStyle != viewHeaderStyle || + oldWidget.viewHeaderHeight != viewHeaderHeight || + oldWidget.todayHighlightColor != todayHighlightColor || + oldWidget.timeSlotViewSettings != timeSlotViewSettings || + oldWidget.monthViewSettings != monthViewSettings || + oldWidget.cellBorderColor != cellBorderColor || + oldWidget.calendarTheme != calendarTheme || + oldWidget.isRTL != isRTL || + oldWidget.locale != locale || + oldWidget.todayTextStyle != todayTextStyle || + oldWidget.textScaleFactor != textScaleFactor; + } - yPosition = localPosition.dy - (viewHeaderHeight + allDayHeight); + //// draw today highlight circle in view header. + void _drawTodayCircle( + Canvas canvas, double x, double y, TextPainter dateTextPainter, + {Color? hoveringColor}) { + _circlePainter.color = (hoveringColor ?? todayHighlightColor)!; + const double circlePadding = 5; + final double painterWidth = dateTextPainter.width / 2; + final double painterHeight = dateTextPainter.height / 2; + final double radius = + painterHeight > painterWidth ? painterHeight : painterWidth; + canvas.drawCircle(Offset(x + painterWidth, y + painterHeight), + radius + circlePadding, _circlePainter); + } - final _AppointmentView appointment = _appointmentLayoutKey.currentState - ._getAppointmentViewOnPoint( - localPosition.dx, yPosition + _scrollController.offset); - if (appointment != null) { - _updateHoveringForAppointment( - localPosition.dx, yPosition + _scrollController.offset); - _hoveringDate = null; - return; - } - } else { - xPosition = localPosition.dx; + /// overrides this property to build the semantics information which uses to + /// return the required information for accessibility, need to return the list + /// of custom painter semantics which contains the rect area and the semantics + /// properties for accessibility + @override + SemanticsBuilderCallback get semanticsBuilder { + return (Size size) { + return _getSemanticsBuilder(size); + }; + } - /// Update the x position value with scroller offset and the value - /// assigned to mouse hover position. - /// mouse hover position value used for highlight the position - /// on all the calendar views. - if (isTimelineViews) { - if (_isRTL) { - xPosition = (_getSingleViewWidthForTimeLineView(this) * - widget.visibleDates.length) - - (_scrollController.offset + - (_scrollController.position.viewportDimension - - localPosition.dx)); - } else { - xPosition = localPosition.dx + _scrollController.offset; - } - } + @override + bool shouldRebuildSemantics(_ViewHeaderViewPainter oldDelegate) { + final _ViewHeaderViewPainter oldWidget = oldDelegate; + return oldWidget.visibleDates != visibleDates; + } - if (localPosition.dy < viewHeaderHeight) { - _updateHoveringForViewHeader( - localPosition, xPosition, localPosition.dy, viewHeaderHeight); - return; - } + String _getAccessibilityText(DateTime date) { + if (!isDateWithInDateRange(minDate, maxDate, date)) { + return DateFormat('EEEEE').format(date).toString() + + DateFormat('dd/MMMM/yyyy').format(date).toString() + + ', Disabled date'; + } - yPosition = localPosition.dy - viewHeaderHeight - timeLabelWidth; - if (_isResourceEnabled(widget.calendar.dataSource, widget.view)) { - yPosition += _timelineViewVerticalScrollController.offset; - } + return DateFormat('EEEEE').format(date).toString() + + DateFormat('dd/MMMM/yyyy').format(date).toString(); + } - final _AppointmentView appointment = _appointmentLayoutKey.currentState - ._getAppointmentViewOnPoint(xPosition, yPosition); - if (appointment != null) { - _updateHoveringForAppointment(xPosition, yPosition); - _hoveringDate = null; - return; + List _getSemanticsForMonthViewHeader(Size size) { + final List semanticsBuilder = + []; + final double cellWidth = size.width / DateTime.daysPerWeek; + double left = isRTL ? size.width - cellWidth : 0; + const double top = 0; + for (int i = 0; i < DateTime.daysPerWeek; i++) { + semanticsBuilder.add(CustomPainterSemantics( + rect: Rect.fromLTWH(left, top, cellWidth, size.height), + properties: SemanticsProperties( + label: DateFormat('EEEEE') + .format(visibleDates[i]) + .toString() + .toUpperCase(), + textDirection: TextDirection.ltr, + ), + )); + if (isRTL) { + left -= cellWidth; + } else { + left += cellWidth; } } - /// Remove the hovering when the position not in cell regions. - if (yPosition < 0) { - if (_hoveringDate != null) { - _hoveringDate = null; - } + return semanticsBuilder; + } - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; + List _getSemanticsForDayHeader(Size size) { + final List semanticsBuilder = + []; + const double top = 0; + double left; + final double cellWidth = view == CalendarView.day + ? size.width + : (size.width - timeLabelWidth) / visibleDates.length; + if (isRTL) { + left = view == CalendarView.day + ? size.width - timeLabelWidth + : (size.width - timeLabelWidth) - cellWidth; + } else { + left = view == CalendarView.day ? 0 : timeLabelWidth; + } + for (int i = 0; i < visibleDates.length; i++) { + semanticsBuilder.add(CustomPainterSemantics( + rect: Rect.fromLTWH(left, top, cellWidth, size.height), + properties: SemanticsProperties( + label: _getAccessibilityText(visibleDates[i]), + textDirection: TextDirection.ltr, + ), + )); + if (isRTL) { + left -= cellWidth; + } else { + left += cellWidth; } + } - return; + return semanticsBuilder; + } + + List _getSemanticsBuilder(Size size) { + switch (view) { + case CalendarView.schedule: + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + return []; + case CalendarView.month: + return _getSemanticsForMonthViewHeader(size); + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + return _getSemanticsForDayHeader(size); } + } +} - final DateTime hoverDate = _getDateFromPosition( - isTimelineViews ? localPosition.dx : xPosition, - yPosition, - timeLabelWidth); +class _SelectionPainter extends CustomPainter { + _SelectionPainter( + this.calendar, + this.view, + this.visibleDates, + this.selectedDate, + this.selectionDecoration, + this.timeIntervalHeight, + this.calendarTheme, + this.repaintNotifier, + this.isRTL, + this.selectedResourceIndex, + this.resourceItemHeight, + this.getCalendarState) + : super(repaint: repaintNotifier); - /// Remove the hovering when the position not in cell regions or non active - /// cell regions. - if (hoverDate == null || - !isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, hoverDate)) { - if (_hoveringDate != null) { - _hoveringDate = null; - } + final SfCalendar calendar; + final CalendarView view; + final SfCalendarThemeData calendarTheme; + final List visibleDates; + Decoration? selectionDecoration; + DateTime? selectedDate; + final double timeIntervalHeight; + final bool isRTL; + final UpdateCalendarState getCalendarState; + int selectedResourceIndex; + final double? resourceItemHeight; + + BoxPainter? _boxPainter; + AppointmentView? appointmentView; + double _cellWidth = 0, _cellHeight = 0, _xPosition = 0, _yPosition = 0; + final ValueNotifier repaintNotifier; + final UpdateCalendarStateDetails _updateCalendarStateDetails = + UpdateCalendarStateDetails(); - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; - } + @override + void paint(Canvas canvas, Size size) { + selectionDecoration ??= BoxDecoration( + color: Colors.transparent, + border: Border.all(color: calendarTheme.selectionBorderColor!, width: 2), + borderRadius: const BorderRadius.all(Radius.circular(2)), + shape: BoxShape.rectangle, + ); + getCalendarState(_updateCalendarStateDetails); + selectedDate = _updateCalendarStateDetails.selectedDate; + final bool isMonthView = + (view == CalendarView.month || view == CalendarView.timelineMonth); + final int timeInterval = + CalendarViewHelper.getTimeInterval(calendar.timeSlotViewSettings); + if (selectedDate != null && + ((isMonthView && + !isDateWithInDateRange( + calendar.minDate, calendar.maxDate, selectedDate)) || + (!isMonthView && + !CalendarViewHelper.isDateTimeWithInDateTimeRange( + calendar.minDate, + calendar.maxDate, + selectedDate!, + timeInterval)))) { return; } + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + calendar.timeSlotViewSettings.timeRulerSize, view); + double width = size.width; + final bool isTimeline = CalendarViewHelper.isTimelineView(view); + if (view != CalendarView.month && !isTimeline) { + width -= timeLabelWidth; + } - /// Check the hovering month cell date is blackout date. - if ((widget.view == CalendarView.month || - widget.view == CalendarView.timelineMonth) && - _isDateInDateCollection(widget.blackoutDates, hoverDate)) { - if (_hoveringDate != null) { - _hoveringDate = null; - } + final bool isResourceEnabled = isTimeline && + CalendarViewHelper.isResourceEnabled(calendar.dataSource, view); + if ((selectedDate == null && appointmentView == null) || + visibleDates != _updateCalendarStateDetails.currentViewVisibleDates || + (isResourceEnabled && selectedResourceIndex == -1)) { + return; + } - /// Remove the existing cell hovering. - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; + if (!isTimeline) { + if (view == CalendarView.month) { + _cellWidth = width / DateTime.daysPerWeek; + _cellHeight = + size.height / calendar.monthViewSettings.numberOfWeeksInView; + } else { + _cellWidth = width / visibleDates.length; + _cellHeight = timeIntervalHeight; } + } else { + _cellWidth = timeIntervalHeight; + _cellHeight = size.height; - /// Remove the existing appointment hovering. - if (_appointmentHoverNotifier.value != null) { - _appointmentHoverNotifier.value = null; + /// The selection view must render on the resource area alone, when the + /// resource enabled. + if (isResourceEnabled && selectedResourceIndex >= 0) { + _cellHeight = resourceItemHeight!; } - - return; } - final int hoveringResourceIndex = - _getSelectedResourceIndex(yPosition, viewHeaderHeight, timeLabelWidth); + if (appointmentView != null && appointmentView!.appointment != null) { + _drawAppointmentSelection(canvas); + } - /// Restrict the hovering, while selected region as disabled [TimeRegion]. - if (((widget.view == CalendarView.day || - widget.view == CalendarView.week || - widget.view == CalendarView.workWeek) && - !_isEnabledRegion(yPosition, hoverDate, hoveringResourceIndex)) || - (isTimelineViews && - !_isEnabledRegion( - localPosition.dx, hoverDate, hoveringResourceIndex))) { - if (_hoveringDate != null) { - _hoveringDate = null; - } + switch (view) { + case CalendarView.schedule: + return; + case CalendarView.month: + { + if (selectedDate != null) { + _drawMonthSelection(canvas, size, width); + } + } + break; + case CalendarView.day: + { + if (selectedDate != null) { + _drawDaySelection(canvas, size, width, timeLabelWidth); + } + } + break; + case CalendarView.week: + case CalendarView.workWeek: + { + if (selectedDate != null) { + _drawWeekSelection(canvas, size, timeLabelWidth, width); + } + } + break; + case CalendarView.timelineDay: + { + if (selectedDate != null) { + _drawTimelineDaySelection(canvas, size, width); + } + } + break; + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + { + if (selectedDate != null) { + _drawTimelineWeekSelection(canvas, size, width); + } + } + break; + case CalendarView.timelineMonth: + { + if (selectedDate != null) { + _drawTimelineMonthSelection(canvas, size, width); + } + } + } + } - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; - } + void _drawMonthSelection(Canvas canvas, Size size, double width) { + if (!isDateWithInDateRange( + visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { return; } - final int currentMonth = - widget.visibleDates[widget.visibleDates.length ~/ 2].month; + final int currentMonth = visibleDates[visibleDates.length ~/ 2].month; /// Check the selected cell date as trailing or leading date when /// [SfCalendar] month not shown leading and trailing dates. - if (!_isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + if (!CalendarViewHelper.isCurrentMonthDate( + calendar.monthViewSettings.numberOfWeeksInView, + calendar.monthViewSettings.showTrailingAndLeadingDates, currentMonth, - hoverDate)) { - if (_hoveringDate != null) { - _hoveringDate = null; - } + selectedDate!)) { + return; + } - /// Remove the existing cell hovering. - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; + if (CalendarViewHelper.isDateInDateCollection( + calendar.blackoutDates, selectedDate!)) { + return; + } + + for (int i = 0; i < visibleDates.length; i++) { + if (isSameDate(visibleDates[i], selectedDate)) { + final int columnIndex = (i / DateTime.daysPerWeek).truncate(); + _yPosition = columnIndex * _cellHeight; + final int rowIndex = i % DateTime.daysPerWeek; + if (isRTL) { + _xPosition = (DateTime.daysPerWeek - 1 - rowIndex) * _cellWidth; + } else { + _xPosition = rowIndex * _cellWidth; + } + _drawSlotSelection(width, size.height, canvas); + break; } + } + } - /// Remove the existing appointment hovering. - if (_appointmentHoverNotifier.value != null) { - _appointmentHoverNotifier.value = null; + void _drawDaySelection( + Canvas canvas, Size size, double width, double timeLabelWidth) { + if (isSameDate(visibleDates[0], selectedDate)) { + if (isRTL) { + _xPosition = 0; + } else { + _xPosition = timeLabelWidth; } - return; + selectedDate = _updateSelectedDate(); + + _yPosition = AppointmentHelper.timeToPosition( + calendar, selectedDate!, timeIntervalHeight); + _drawSlotSelection(width + timeLabelWidth, size.height, canvas); } + } - final bool isResourceEnabled = - _isResourceEnabled(widget.calendar.dataSource, widget.view); + /// Method to update the selected date, when the selected date not fill the + /// exact time slot, and render the mid of time slot, on this scenario we + /// have updated the selected date to update the exact time slot. + /// + /// Eg: If the time interval is 60min, and the selected date is 12.45 PM the + /// selection renders on the center of 12 to 1 PM slot, to avoid this we have + /// modified the selected date to 1 PM so that the selection will render the + /// exact time slot. + DateTime _updateSelectedDate() { + final int timeInterval = + CalendarViewHelper.getTimeInterval(calendar.timeSlotViewSettings); + final int startHour = calendar.timeSlotViewSettings.startHour.toInt(); + final double startMinute = (calendar.timeSlotViewSettings.startHour - + calendar.timeSlotViewSettings.startHour.toInt()) * + 60; + final int selectedMinutes = ((selectedDate!.hour - startHour) * 60) + + (selectedDate!.minute - startMinute.toInt()); + if (selectedMinutes % timeInterval != 0) { + final int diff = selectedMinutes % timeInterval; + if (diff < (timeInterval / 2)) { + return selectedDate!.subtract(Duration(minutes: diff)); + } else { + return selectedDate!.add(Duration(minutes: timeInterval - diff)); + } + } - /// If resource enabled the selected date or time slot can be same but the - /// resource value differs hence to handle this scenario we are excluding - /// the following conditions, if resource enabled. - if (!isResourceEnabled) { - if ((widget.view == CalendarView.month && - isSameDate(_hoveringDate, hoverDate) && - _viewHeaderNotifier.value == null) || - (widget.view != CalendarView.month && - _isSameTimeSlot(_hoveringDate, hoverDate) && - _viewHeaderNotifier.value == null)) { - return; + return selectedDate!; + } + + void _drawWeekSelection( + Canvas canvas, Size size, double timeLabelWidth, double width) { + if (isDateWithInDateRange( + visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { + for (int i = 0; i < visibleDates.length; i++) { + if (isSameDate(selectedDate, visibleDates[i])) { + final int rowIndex = i; + if (isRTL) { + _xPosition = _cellWidth * (visibleDates.length - 1 - rowIndex); + } else { + _xPosition = timeLabelWidth + _cellWidth * rowIndex; + } + + selectedDate = _updateSelectedDate(); + + _yPosition = AppointmentHelper.timeToPosition( + calendar, selectedDate!, timeIntervalHeight); + _drawSlotSelection(width + timeLabelWidth, size.height, canvas); + break; + } } } + } - _hoveringDate = hoverDate; + /// Returns the yPosition for selection view based on resource associated with + /// the selected cell in timeline views when resource enabled. + double _getTimelineYPosition() { + if (selectedResourceIndex == -1) { + return 0; + } - if (widget.view == CalendarView.month && - isSameDate(_selectionPainter.selectedDate, _hoveringDate)) { - _calendarCellNotifier.value = null; - return; - } else if (widget.view != CalendarView.month && - _isSameTimeSlot(_selectionPainter.selectedDate, _hoveringDate) && - hoveringResourceIndex == _selectedResourceIndex) { - _calendarCellNotifier.value = null; - return; + return selectedResourceIndex * resourceItemHeight!; + } + + void _drawTimelineDaySelection(Canvas canvas, Size size, double width) { + if (isSameDate(visibleDates[0], selectedDate)) { + selectedDate = _updateSelectedDate(); + _xPosition = AppointmentHelper.timeToPosition( + calendar, selectedDate!, timeIntervalHeight); + _yPosition = _getTimelineYPosition(); + final double height = selectedResourceIndex == -1 + ? size.height + : _yPosition + resourceItemHeight!; + if (isRTL) { + _xPosition = size.width - _xPosition - _cellWidth; + } + _drawSlotSelection(width, height, canvas); } + } - if (widget.view != CalendarView.month && !isTimelineViews) { - yPosition += _scrollController.offset; + void _drawTimelineMonthSelection(Canvas canvas, Size size, double width) { + if (!isDateWithInDateRange( + visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { + return; } - if (_viewHeaderNotifier.value != null) { - _viewHeaderNotifier.value = null; + if (CalendarViewHelper.isDateInDateCollection( + calendar.blackoutDates, selectedDate!)) { + return; } - if (_allDayNotifier.value != null) { - _allDayNotifier.value = null; + for (int i = 0; i < visibleDates.length; i++) { + if (isSameDate(visibleDates[i], selectedDate)) { + _yPosition = _getTimelineYPosition(); + _xPosition = + isRTL ? size.width - ((i + 1) * _cellWidth) : i * _cellWidth; + final double height = selectedResourceIndex == -1 + ? size.height + : _yPosition + resourceItemHeight!; + _drawSlotSelection(width, height, canvas); + break; + } } + } - if (_appointmentHoverNotifier.value != null) { - _appointmentHoverNotifier.value = null; + void _drawTimelineWeekSelection(Canvas canvas, Size size, double width) { + if (isDateWithInDateRange( + visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { + selectedDate = _updateSelectedDate(); + for (int i = 0; i < visibleDates.length; i++) { + if (isSameDate(selectedDate, visibleDates[i])) { + final double singleViewWidth = width / visibleDates.length; + _xPosition = (i * singleViewWidth) + + AppointmentHelper.timeToPosition( + calendar, selectedDate!, timeIntervalHeight); + if (isRTL) { + _xPosition = size.width - _xPosition - _cellWidth; + } + _yPosition = _getTimelineYPosition(); + final double height = selectedResourceIndex == -1 + ? size.height + : _yPosition + resourceItemHeight!; + _drawSlotSelection(width, height, canvas); + break; + } + } } + } - _calendarCellNotifier.value = Offset(xPosition, yPosition); + void _drawAppointmentSelection(Canvas canvas) { + Rect rect = appointmentView!.appointmentRect!.outerRect; + rect = Rect.fromLTRB(rect.left, rect.top, rect.right, rect.bottom); + _boxPainter = selectionDecoration! + .createBoxPainter(_updateSelectionDecorationPainter); + _boxPainter!.paint(canvas, Offset(rect.left, rect.top), + ImageConfiguration(size: rect.size)); } - void _pointerEnterEvent(PointerEnterEvent event) { - _updatePointerHover(event.position); + /// Used to pass the argument of create box painter and it is called when + /// decoration have asynchronous data like image. + void _updateSelectionDecorationPainter() { + repaintNotifier.value = !repaintNotifier.value; } - void _pointerHoverEvent(PointerHoverEvent event) { - _updatePointerHover(event.position); + void _drawSlotSelection(double width, double height, Canvas canvas) { + //// padding used to avoid first, last row and column selection clipping. + const double padding = 0.5; + Rect rect; + rect = Rect.fromLTRB( + _xPosition == 0 ? _xPosition + padding : _xPosition, + _yPosition == 0 ? _yPosition + padding : _yPosition, + _xPosition + _cellWidth == width + ? _xPosition + _cellWidth - padding + : _xPosition + _cellWidth, + _yPosition + _cellHeight == height + ? _yPosition + _cellHeight - padding + : _yPosition + _cellHeight); + + _boxPainter = selectionDecoration! + .createBoxPainter(_updateSelectionDecorationPainter); + _boxPainter!.paint(canvas, Offset(rect.left, rect.top), + ImageConfiguration(size: rect.size, textDirection: TextDirection.ltr)); } - void _pointerExitEvent(PointerExitEvent event) { - _hoveringDate = null; - _calendarCellNotifier.value = null; - _viewHeaderNotifier.value = null; - _appointmentHoverNotifier.value = null; - _allDayNotifier.value = null; + @override + bool shouldRepaint(_SelectionPainter oldDelegate) { + final _SelectionPainter oldWidget = oldDelegate; + return oldWidget.selectionDecoration != selectionDecoration || + oldWidget.selectedDate != selectedDate || + oldWidget.view != view || + oldWidget.visibleDates != visibleDates || + oldWidget.selectedResourceIndex != selectedResourceIndex || + oldWidget.isRTL != isRTL; } +} - _AppointmentView _getAllDayAppointmentOnPoint( - List<_AppointmentView> appointmentCollection, double x, double y) { - if (appointmentCollection == null) { - return null; +class _TimeRulerView extends CustomPainter { + _TimeRulerView( + this.horizontalLinesCount, + this.timeIntervalHeight, + this.timeSlotViewSettings, + this.cellBorderColor, + this.isRTL, + this.locale, + this.calendarTheme, + this.isTimelineView, + this.visibleDates, + this.textScaleFactor); + + final double horizontalLinesCount; + final double timeIntervalHeight; + final TimeSlotViewSettings timeSlotViewSettings; + final bool isRTL; + final String locale; + final SfCalendarThemeData calendarTheme; + final Color? cellBorderColor; + final bool isTimelineView; + final List visibleDates; + final double textScaleFactor; + Paint _linePainter = Paint(); + TextPainter _textPainter = TextPainter(); + + @override + void paint(Canvas canvas, Size size) { + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + const double offset = 0.5; + double xPosition, yPosition; + final DateTime date = DateTime.now(); + xPosition = isRTL && isTimelineView ? size.width : 0; + yPosition = timeIntervalHeight; + _linePainter.strokeWidth = offset; + _linePainter.color = cellBorderColor ?? calendarTheme.cellBorderColor; + + if (!isTimelineView) { + final double lineXPosition = isRTL ? offset : size.width - offset; + // Draw vertical time label line + canvas.drawLine(Offset(lineXPosition, 0), + Offset(lineXPosition, size.height), _linePainter); } - _AppointmentView selectedAppointmentView; - for (int i = 0; i < appointmentCollection.length; i++) { - final _AppointmentView appointmentView = appointmentCollection[i]; - if (appointmentView.appointment != null && - appointmentView.appointmentRect != null && - appointmentView.appointmentRect.left <= x && - appointmentView.appointmentRect.right >= x && - appointmentView.appointmentRect.top <= y && - appointmentView.appointmentRect.bottom >= y) { - selectedAppointmentView = appointmentView; - break; + _textPainter.textDirection = TextDirection.ltr; + _textPainter.textWidthBasis = TextWidthBasis.longestLine; + _textPainter.textScaleFactor = textScaleFactor; + + final TextStyle timeTextStyle = + timeSlotViewSettings.timeTextStyle ?? calendarTheme.timeTextStyle; + + final double hour = (timeSlotViewSettings.startHour - + timeSlotViewSettings.startHour.toInt()) * + 60; + if (isTimelineView) { + canvas.drawLine(Offset(0, 0), Offset(size.width, 0), _linePainter); + final double timelineViewWidth = + timeIntervalHeight * horizontalLinesCount; + for (int i = 0; i < visibleDates.length; i++) { + _drawTimeLabels( + canvas, size, date, hour, xPosition, yPosition, timeTextStyle); + if (isRTL) { + xPosition -= timelineViewWidth; + } else { + xPosition += timelineViewWidth; + } + } + } else { + _drawTimeLabels( + canvas, size, date, hour, xPosition, yPosition, timeTextStyle); + } + } + + /// Draws the time labels in the time label view for timeslot views in + /// calendar. + void _drawTimeLabels(Canvas canvas, Size size, DateTime date, double hour, + double xPosition, double yPosition, TextStyle timeTextStyle) { + final int padding = 5; + + /// For timeline view we will draw 24 lines where as in day, week and work + /// week view we will draw 23 lines excluding the 12 AM, hence to rectify + /// this the i value handled accordingly. + for (int i = isTimelineView ? 0 : 1; + i <= (isTimelineView ? horizontalLinesCount - 1 : horizontalLinesCount); + i++) { + if (isTimelineView) { + canvas.save(); + canvas.clipRect( + Rect.fromLTWH(xPosition, 0, timeIntervalHeight, size.height)); + canvas.restore(); + canvas.drawLine( + Offset(xPosition, 0), Offset(xPosition, size.height), _linePainter); + } + + final double minute = + (i * CalendarViewHelper.getTimeInterval(timeSlotViewSettings)) + hour; + date = DateTime(date.day, date.month, date.year, + timeSlotViewSettings.startHour.toInt(), minute.toInt()); + final String time = DateFormat(timeSlotViewSettings.timeFormat, locale) + .format(date) + .toString(); + final TextSpan span = TextSpan( + text: time, + style: timeTextStyle, + ); + + final double cellWidth = isTimelineView ? timeIntervalHeight : size.width; + + _textPainter.text = span; + _textPainter.layout(minWidth: 0, maxWidth: cellWidth); + if (isTimelineView && _textPainter.height > size.height) { + return; + } + + double startXPosition = (cellWidth - _textPainter.width) / 2; + if (startXPosition < 0) { + startXPosition = 0; + } + + if (isTimelineView) { + startXPosition = isRTL ? xPosition - _textPainter.width : xPosition; + } + + double startYPosition = yPosition - (_textPainter.height / 2); + + if (isTimelineView) { + startYPosition = (size.height - _textPainter.height) / 2; + startXPosition = + isRTL ? startXPosition - padding : startXPosition + padding; + } + + _textPainter.paint(canvas, Offset(startXPosition, startYPosition)); + + if (!isTimelineView) { + final Offset start = + Offset(isRTL ? 0 : size.width - (startXPosition / 2), yPosition); + final Offset end = + Offset(isRTL ? startXPosition / 2 : size.width, yPosition); + canvas.drawLine(start, end, _linePainter); + yPosition += timeIntervalHeight; + if (yPosition.round() == size.height.round()) { + break; + } + } else { + if (isRTL) { + xPosition -= timeIntervalHeight; + } else { + xPosition += timeIntervalHeight; + } } } - - return selectedAppointmentView; } - List _getSelectedAppointments(DateTime selectedDate) { - return (widget.calendar.dataSource != null && - !_isCalendarAppointment(widget.calendar.dataSource)) - ? _getCustomAppointments(_getSelectedDateAppointments( - _updateCalendarStateDetails._appointments, - widget.calendar.timeZone, - selectedDate)) - : (_getSelectedDateAppointments( - _updateCalendarStateDetails._appointments, - widget.calendar.timeZone, - selectedDate)); + @override + bool shouldRepaint(_TimeRulerView oldDelegate) { + final _TimeRulerView oldWidget = oldDelegate; + return oldWidget.timeSlotViewSettings != timeSlotViewSettings || + oldWidget.cellBorderColor != cellBorderColor || + oldWidget.calendarTheme != calendarTheme || + oldWidget.isRTL != isRTL || + oldWidget.locale != locale || + oldWidget.visibleDates != visibleDates || + oldWidget.isTimelineView != isTimelineView || + oldWidget.textScaleFactor != textScaleFactor; } +} - DateTime _getDateFromPositionForMonth( - double cellWidth, double cellHeight, double x, double y) { - final int rowIndex = (x / cellWidth).truncate(); - final int columnIndex = (y / cellHeight).truncate(); - int index = 0; - if (_isRTL) { - index = (columnIndex * _kNumberOfDaysInWeek) + - (_kNumberOfDaysInWeek - rowIndex) - - 1; - } else { - index = (columnIndex * _kNumberOfDaysInWeek) + rowIndex; - } - - if (index < 0 || index >= widget.visibleDates.length) { - return null; - } +class _CalendarMultiChildContainer extends Stack { + _CalendarMultiChildContainer( + {this.painter, + List children = const [], + required this.width, + required this.height}) + : super(children: children); + final CustomPainter? painter; + final double width; + final double height; - return widget.visibleDates[index]; + @override + RenderStack createRenderObject(BuildContext context) { + final Directionality? widget = + context.dependOnInheritedWidgetOfExactType(); + return _MultiChildContainerRenderObject(width, height, + painter: painter, + direction: widget != null ? widget.textDirection : null); } - DateTime _getDateFromPositionForDay( - double cellWidth, double cellHeight, double x, double y) { - final int columnIndex = - ((_scrollController.offset + y) / cellHeight).truncate(); - final double time = - ((_getTimeInterval(widget.calendar.timeSlotViewSettings) / 60) * - columnIndex) + - widget.calendar.timeSlotViewSettings.startHour; - final int hour = time.toInt(); - final int minute = ((time - hour) * 60).round(); - return DateTime(widget.visibleDates[0].year, widget.visibleDates[0].month, - widget.visibleDates[0].day, hour, minute); + @override + void updateRenderObject(BuildContext context, RenderStack renderObject) { + super.updateRenderObject(context, renderObject); + if (renderObject is _MultiChildContainerRenderObject) { + final Directionality? widget = + context.dependOnInheritedWidgetOfExactType(); + renderObject + ..width = width + ..height = height + ..painter = painter + ..textDirection = widget != null ? widget.textDirection : null; + } } +} - DateTime _getDateFromPositionForWeek( - double cellWidth, double cellHeight, double x, double y) { - final int columnIndex = - ((_scrollController.offset + y) / cellHeight).truncate(); - final double time = - ((_getTimeInterval(widget.calendar.timeSlotViewSettings) / 60) * - columnIndex) + - widget.calendar.timeSlotViewSettings.startHour; - final int hour = time.toInt(); - final int minute = ((time - hour) * 60).round(); - int rowIndex = (x / cellWidth).truncate(); - if (_isRTL) { - rowIndex = (widget.visibleDates.length - rowIndex) - 1; +class _MultiChildContainerRenderObject extends RenderStack { + _MultiChildContainerRenderObject(this._width, this._height, + {CustomPainter? painter, TextDirection? direction}) + : _painter = painter, + super(textDirection: direction); + + CustomPainter? get painter => _painter; + CustomPainter? _painter; + + set painter(CustomPainter? value) { + if (_painter == value) { + return; } - if (rowIndex < 0 || rowIndex >= widget.visibleDates.length) { - return null; + final CustomPainter? oldPainter = _painter; + _painter = value; + _updatePainter(_painter, oldPainter); + if (attached) { + oldPainter?.removeListener(markNeedsPaint); + _painter?.addListener(markNeedsPaint); } + } - final DateTime date = widget.visibleDates[rowIndex]; + double get width => _width; - return DateTime(date.year, date.month, date.day, hour, minute); + set width(double value) { + if (_width == value) { + return; + } + + _width = value; + markNeedsLayout(); } - DateTime _getDateFromPositionForTimeline( - double cellWidth, double cellHeight, double x, double y) { - int rowIndex, columnIndex; - if (_isRTL) { - rowIndex = (((_scrollController.offset % - _getSingleViewWidthForTimeLineView(this)) + - (_scrollController.position.viewportDimension - x)) / - cellWidth) - .truncate(); - } else { - rowIndex = (((_scrollController.offset % - _getSingleViewWidthForTimeLineView(this)) + - x) / - cellWidth) - .truncate(); - } - columnIndex = - (_scrollController.offset / _getSingleViewWidthForTimeLineView(this)) - .truncate(); - if (rowIndex >= _horizontalLinesCount) { - columnIndex += rowIndex ~/ _horizontalLinesCount; - rowIndex = (rowIndex % _horizontalLinesCount).toInt(); - } - final double time = - ((_getTimeInterval(widget.calendar.timeSlotViewSettings) / 60) * - rowIndex) + - widget.calendar.timeSlotViewSettings.startHour; - final int hour = time.toInt(); - final int minute = ((time - hour) * 60).round(); - if (columnIndex < 0) { - columnIndex = 0; - } else if (columnIndex > widget.visibleDates.length) { - columnIndex = widget.visibleDates.length - 1; - } + double _width; + double _height; - if (columnIndex < 0 || columnIndex >= widget.visibleDates.length) { - return null; - } + double get height => _height; - final DateTime date = widget.visibleDates[columnIndex]; + set height(double value) { + if (_height == value) { + return; + } - return DateTime(date.year, date.month, date.day, hour, minute); + _height = value; + markNeedsLayout(); } - DateTime _getDateFromPosition(double x, double y, double timeLabelWidth) { - double cellWidth = 0; - double cellHeight = 0; - final double width = widget.width - timeLabelWidth; - switch (widget.view) { - case CalendarView.schedule: - return null; - case CalendarView.month: - { - if (x > width || x < 0) { - return null; - } + /// Caches [SemanticsNode]s created during [assembleSemanticsNode] so they + /// can be re-used when [assembleSemanticsNode] is called again. This ensures + /// stable ids for the [SemanticsNode]s of children across + /// [assembleSemanticsNode] invocations. + /// Ref: assembleSemanticsNode method in RenderParagraph class + /// (https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/paragraph.dart) + List? _cacheNodes; + + void _updatePainter(CustomPainter? newPainter, CustomPainter? oldPainter) { + if (newPainter == null) { + markNeedsPaint(); + } else if (oldPainter == null || + newPainter.runtimeType != oldPainter.runtimeType || + newPainter.shouldRepaint(oldPainter)) { + markNeedsPaint(); + } - cellWidth = width / _kNumberOfDaysInWeek; - cellHeight = (widget.height - - _getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view)) / - widget.calendar.monthViewSettings.numberOfWeeksInView; - return _getDateFromPositionForMonth(cellWidth, cellHeight, x, y); - } - case CalendarView.day: - { - if (y >= _timeIntervalHeight * _horizontalLinesCount || - x > width || - x < 0) { - return null; - } - cellWidth = width; - cellHeight = _timeIntervalHeight; - return _getDateFromPositionForDay(cellWidth, cellHeight, x, y); - } - case CalendarView.week: - case CalendarView.workWeek: - { - if (y >= _timeIntervalHeight * _horizontalLinesCount || - x > width || - x < 0) { - return null; - } - cellWidth = width / widget.visibleDates.length; - cellHeight = _timeIntervalHeight; - return _getDateFromPositionForWeek(cellWidth, cellHeight, x, y); - } - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - { - final double viewWidth = _timeIntervalHeight * - (_horizontalLinesCount * widget.visibleDates.length); - if ((!_isRTL && x >= viewWidth) || - (_isRTL && x < (widget.width - viewWidth))) { - return null; - } - cellWidth = _timeIntervalHeight; - cellHeight = widget.height; - return _getDateFromPositionForTimeline(cellWidth, cellHeight, x, y); - } + if (newPainter == null) { + if (attached) { + markNeedsSemanticsUpdate(); + } + } else if (oldPainter == null || + newPainter.runtimeType != oldPainter.runtimeType || + newPainter.shouldRebuildSemantics(oldPainter)) { + markNeedsSemanticsUpdate(); } + } - return null; + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _painter?.addListener(markNeedsPaint); } - void _drawSelection(double x, double y, double timeLabelWidth) { - final DateTime selectedDate = _getDateFromPosition(x, y, timeLabelWidth); - if (selectedDate == null || - !isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, selectedDate)) { - return; - } + @override + void detach() { + _painter?.removeListener(markNeedsPaint); + super.detach(); + } - /// Restrict the selection update, while selected region as disabled - /// [TimeRegion]. - if (((widget.view == CalendarView.day || - widget.view == CalendarView.week || - widget.view == CalendarView.workWeek) && - !_isEnabledRegion(y, selectedDate, _selectedResourceIndex)) || - (_isTimelineView(widget.view) && - !_isEnabledRegion(x, selectedDate, _selectedResourceIndex))) { - return; + @override + void performLayout() { + final Size widgetSize = constraints.biggest; + size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height); + for (var child = firstChild; child != null; child = childAfter(child)) { + child.layout(constraints); } + } - if ((widget.view == CalendarView.month || - widget.view == CalendarView.timelineMonth) && - _isDateInDateCollection(widget.blackoutDates, selectedDate)) { - return; + @override + void paint(PaintingContext context, Offset offset) { + if (_painter != null) { + _painter!.paint(context.canvas, size); } - if (widget.view == CalendarView.month) { - final int currentMonth = - widget.visibleDates[widget.visibleDates.length ~/ 2].month; + paintStack(context, offset); + } - /// Check the selected cell date as trailing or leading date when - /// [SfCalendar] month not shown leading and trailing dates. - if (!_isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentMonth, - selectedDate)) { - return; + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.isSemanticBoundary = true; + } + + @override + void assembleSemanticsNode( + SemanticsNode node, + SemanticsConfiguration config, + Iterable children, + ) { + _cacheNodes ??= []; + final List semantics = _semanticsBuilder(size); + final List semanticsNodes = []; + for (int i = 0; i < semantics.length; i++) { + final CustomPainterSemantics currentSemantics = semantics[i]; + final SemanticsNode newChild = _cacheNodes!.isNotEmpty + ? _cacheNodes!.removeAt(0) + : SemanticsNode(key: currentSemantics.key); + + final SemanticsProperties properties = currentSemantics.properties; + final SemanticsConfiguration config = SemanticsConfiguration(); + if (properties.label != null) { + config.label = properties.label!; + } + if (properties.textDirection != null) { + config.textDirection = properties.textDirection; } - widget.agendaSelectedDate.value = selectedDate; - } + newChild.updateWith( + config: config, + // As of now CustomPainter does not support multiple tree levels. + childrenInInversePaintOrder: const [], + ); - _updateCalendarStateDetails._selectedDate = selectedDate; - _selectionPainter.selectedDate = selectedDate; - _selectionPainter._appointmentView = null; - _selectionNotifier.value = !_selectionNotifier.value; - } + newChild + ..rect = currentSemantics.rect + ..transform = currentSemantics.transform + ..tags = currentSemantics.tags; - _SelectionPainter _addSelectionView([double resourceItemHeight]) { - _AppointmentView appointmentView; - if (_selectionPainter != null && - _selectionPainter._appointmentView != null) { - appointmentView = _selectionPainter._appointmentView; + semanticsNodes.add(newChild); } - _selectionPainter = _SelectionPainter( - widget.calendar, - widget.view, - widget.visibleDates, - _updateCalendarStateDetails._selectedDate, - widget.calendar.selectionDecoration, - _timeIntervalHeight, - widget.calendarTheme, - _selectionNotifier, - _isRTL, - _selectedResourceIndex, - resourceItemHeight, - updateCalendarState: (_UpdateCalendarStateDetails details) { - _getPainterProperties(details); - }); + final List finalChildren = []; + finalChildren.addAll(semanticsNodes); + finalChildren.addAll(children); + _cacheNodes = semanticsNodes; + super.assembleSemanticsNode(node, config, finalChildren); + } - if (appointmentView != null && - _updateCalendarStateDetails._visibleAppointments != null && - _updateCalendarStateDetails._visibleAppointments - .contains(appointmentView.appointment)) { - _selectionPainter._appointmentView = appointmentView; + @override + void clearSemantics() { + super.clearSemantics(); + _cacheNodes = null; + } + + SemanticsBuilderCallback get _semanticsBuilder { + final List semantics = []; + if (painter != null) { + semantics.addAll(painter!.semanticsBuilder!(size)); } + // ignore: avoid_as + for (RenderRepaintBoundary? child = firstChild as RenderRepaintBoundary; + child != null; + // ignore: avoid_as + child = childAfter(child) as RenderRepaintBoundary?) { + if (child.child is! CustomCalendarRenderObject) { + continue; + } - return _selectionPainter; - } + final CustomCalendarRenderObject appointmentRenderObject = + // ignore: avoid_as + child.child as CustomCalendarRenderObject; + semantics.addAll(appointmentRenderObject.semanticsBuilder!(size)); + } - Widget _getTimelineViewHeader(double width, double height, String locale) { - _timelineViewHeader = _TimelineViewHeaderView( - widget.visibleDates, - _timelineViewHeaderScrollController, - _timelineViewHeaderNotifier, - widget.calendar.viewHeaderStyle, - widget.calendar.timeSlotViewSettings, - _getViewHeaderHeight(widget.calendar.viewHeaderHeight, widget.view), - _isRTL, - widget.calendar.todayHighlightColor ?? - widget.calendarTheme.todayHighlightColor, - widget.calendar.todayTextStyle, - widget.locale, - widget.calendarTheme, - widget.calendar.minDate, - widget.calendar.maxDate, - _viewHeaderNotifier, - widget.calendar.cellBorderColor, - widget.blackoutDates, - widget.calendar.blackoutDatesTextStyle, - widget.textScaleFactor); - return ListView( - padding: const EdgeInsets.all(0.0), - controller: _timelineViewHeaderScrollController, - scrollDirection: Axis.horizontal, - physics: const NeverScrollableScrollPhysics(), - children: [ - CustomPaint( - painter: _timelineViewHeader, - size: Size(width, height), - ) - ]); + return (Size size) { + return semantics; + }; } } class _CustomNeverScrollableScrollPhysics extends NeverScrollableScrollPhysics { /// Creates scroll physics that does not let the user scroll. - const _CustomNeverScrollableScrollPhysics({ScrollPhysics parent}) + const _CustomNeverScrollableScrollPhysics({ScrollPhysics? parent}) : super(parent: parent); @override - _CustomNeverScrollableScrollPhysics applyTo(ScrollPhysics ancestor) { + _CustomNeverScrollableScrollPhysics applyTo(ScrollPhysics? ancestor) { /// Set the clamping scroll physics as default parent for never scroll /// physics, because flutter framework set different parent physics /// based on platform(iOS, Android, etc.,) @@ -2859,3 +7370,98 @@ class _CustomNeverScrollableScrollPhysics extends NeverScrollableScrollPhysics { ClampingScrollPhysics(parent: RangeMaintainingScrollPhysics()))); } } + +class _CurrentTimeIndicator extends CustomPainter { + _CurrentTimeIndicator( + this.timeIntervalSize, + this.timeRulerSize, + this.timeSlotViewSettings, + this.isTimelineView, + this.visibleDates, + this.todayHighlightColor, + this.isRTL, + ValueNotifier repaintNotifier) + : super(repaint: repaintNotifier); + final double timeIntervalSize; + final TimeSlotViewSettings timeSlotViewSettings; + final bool isTimelineView; + final List visibleDates; + final double timeRulerSize; + final Color? todayHighlightColor; + final bool isRTL; + + @override + void paint(Canvas canvas, Size size) { + final DateTime now = DateTime.now(); + final int hours = now.hour; + final int minutes = now.minute; + final int totalMinutes = (hours * 60) + minutes; + final int viewStartMinutes = (timeSlotViewSettings.startHour * 60).toInt(); + final int viewEndMinutes = (timeSlotViewSettings.endHour * 60).toInt(); + if (totalMinutes < viewStartMinutes || totalMinutes > viewEndMinutes) { + return; + } + + int index = -1; + for (int i = 0; i < visibleDates.length; i++) { + final DateTime date = visibleDates[i]; + if (isSameDate(date, now)) { + index = i; + break; + } + } + + if (index == -1) { + return; + } + + final double minuteHeight = timeIntervalSize / + CalendarViewHelper.getTimeInterval(timeSlotViewSettings); + final double currentTimePosition = CalendarViewHelper.getTimeToPosition( + Duration(hours: hours, minutes: minutes), + timeSlotViewSettings, + minuteHeight); + final Paint painter = Paint() + ..color = todayHighlightColor! + ..strokeWidth = 1 + ..isAntiAlias = true + ..style = PaintingStyle.fill; + if (isTimelineView) { + final double viewSize = size.width / visibleDates.length; + double startXPosition = (index * viewSize) + currentTimePosition; + if (isRTL) { + startXPosition = size.width - startXPosition; + } + canvas.drawCircle(Offset(startXPosition, 5), 5, painter); + canvas.drawLine(Offset(startXPosition, 0), + Offset(startXPosition, size.height), painter); + } else { + final double viewSize = + (size.width - timeRulerSize) / visibleDates.length; + final double startYPosition = currentTimePosition; + double viewStartPosition = (index * viewSize) + timeRulerSize; + double viewEndPosition = viewStartPosition + viewSize; + double startXPosition = viewStartPosition < 5 ? 5 : viewStartPosition; + if (isRTL) { + viewStartPosition = size.width - viewStartPosition; + viewEndPosition = size.width - viewEndPosition; + startXPosition = size.width - startXPosition; + } + canvas.drawCircle(Offset(startXPosition, startYPosition), 5, painter); + canvas.drawLine(Offset(viewStartPosition, startYPosition), + Offset(viewEndPosition, startYPosition), painter); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} + +/// Returns the single view width from the time line view for time line +double _getSingleViewWidthForTimeLineView(_CalendarViewState viewState) { + return (viewState._scrollController!.position.maxScrollExtent + + viewState._scrollController!.position.viewportDimension) / + viewState.widget.visibleDates.length; +} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/custom_calendar_button.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/custom_calendar_button.dart deleted file mode 100644 index e5cc216f8..000000000 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/custom_calendar_button.dart +++ /dev/null @@ -1,239 +0,0 @@ -part of calendar; - -/// Specifies the unconfirmed ripple animation duration used on custom splash. -/// The duration was unconfirmed because the ripple animation duration changed -/// based on its radius value. -const Duration _kUnconfirmedRippleSplashDuration = Duration(seconds: 1); - -/// Specifies the fade animation duration used on custom splash. -const Duration _kSplashFadeDuration = Duration(milliseconds: 500); - -/// Used to create the custom splash factory that shows the splash for inkwell -/// interaction. -class _CustomSplashFactory extends InteractiveInkFeatureFactory { - /// Called when the inkwell pressed and it return custom splash. - @override - InteractiveInkFeature create({ - @required MaterialInkController controller, - @required RenderBox referenceBox, - @required Offset position, - @required Color color, - @required TextDirection textDirection, - bool containedInkWell = false, - RectCallback rectCallback, - BorderRadius borderRadius, - ShapeBorder customBorder, - double radius, - VoidCallback onRemoved, - }) { - return _CustomSplash( - controller: controller, - referenceBox: referenceBox, - position: position, - color: color, - containedInkWell: containedInkWell, - borderRadius: borderRadius, - rectCallback: rectCallback, - onRemoved: onRemoved, - ); - } -} - -/// Custom ink splash used to animate the inkwell on intercation. -class _CustomSplash extends InteractiveInkFeature { - /// Begin a splash, centered at position relative to [referenceBox]. - /// - /// The [controller] argument is typically obtained via - /// `Material.of(context)`. - /// - /// If `containedInkWell` is true, then the splash will be sized to fit - /// the well rectangle, then clipped to it when drawn. The well - /// rectangle is the box returned by `rectCallback`, if provided, or - /// otherwise is the bounds of the [referenceBox]. - /// - /// If `containedInkWell` is false, then `rectCallback` should be null. - /// The ink splash is clipped only to the edges of the [Material]. - /// This is the default. - /// - /// When the splash is removed, `onRemoved` will be called. - _CustomSplash({ - @required MaterialInkController controller, - @required RenderBox referenceBox, - Offset position, - Color color, - bool containedInkWell = false, - RectCallback rectCallback, - BorderRadius borderRadius, - VoidCallback onRemoved, - }) : _position = position, - _borderRadius = borderRadius ?? BorderRadius.zero, - _targetRadius = _getTargetRadius( - referenceBox, containedInkWell, rectCallback, position), - _clipCallback = - _getClipCallback(referenceBox, containedInkWell, rectCallback), - _repositionToReferenceBox = !containedInkWell, - super( - controller: controller, - referenceBox: referenceBox, - color: color, - onRemoved: onRemoved) { - _radiusController = AnimationController( - duration: _kUnconfirmedRippleSplashDuration, vsync: controller.vsync) - ..addListener(controller.markNeedsPaint) - ..forward(); - _radius = _radiusController.drive(Tween( - begin: 0.0, - end: _targetRadius, - )); - _alphaController = AnimationController( - duration: _kSplashFadeDuration, vsync: controller.vsync) - ..addListener(controller.markNeedsPaint) - ..addStatusListener(_handleAlphaStatusChanged); - _alpha = _alphaController.drive(IntTween( - begin: color.alpha, - end: 0, - )); - - controller.addInkFeature(this); - } - - /// Position holds the input touch point. - final Offset _position; - - /// Specifies the border radius used on the inkwell - final BorderRadius _borderRadius; - - /// Radius of ink circle to be drawn on canvas based on its position. - final double _targetRadius; - - /// clipCallback is the callback used to obtain the rect used for clipping - /// the ink effect. If it is null, no clipping is performed on the ink circle. - final RectCallback _clipCallback; - - /// Specifies the reference box repositioned or not. Its value depends on - /// contained inkwell property. - final bool _repositionToReferenceBox; - - /// Animation used to show a ripple. - Animation _radius; - - /// Controller used to handle the ripple animation. - AnimationController _radiusController; - - /// Animation used to handle a opacity. - Animation _alpha; - - /// Controller used to handle the opacity animation. - AnimationController _alphaController; - - @override - void confirm() { - /// Calculate the ripple animation duration from its radius value and start - /// the animation. - Duration duration = Duration(milliseconds: (_targetRadius * 10).floor()); - duration = duration > _kUnconfirmedRippleSplashDuration - ? _kUnconfirmedRippleSplashDuration - : duration; - _radiusController - ..duration = duration - ..forward(); - _alphaController.forward(); - } - - @override - void cancel() { - _alphaController?.forward(); - } - - void _handleAlphaStatusChanged(AnimationStatus status) { - /// Dispose inkwell animation when the animation completed. - if (status == AnimationStatus.completed) dispose(); - } - - @override - void dispose() { - _radiusController.dispose(); - _alphaController.dispose(); - _radiusController = null; - _alphaController = null; - super.dispose(); - } - - ///Draws an ink splash or ink ripple on the canvas. - @override - void paintFeature(Canvas canvas, Matrix4 transform) { - final Paint paint = Paint()..color = color.withAlpha(_alpha.value); - Offset center = _position; - - /// If the reference box needs to reposition then its 'rectCallback' value - /// is null, so calculate the position based on reference box. - if (_repositionToReferenceBox) { - center = Offset.lerp(center, referenceBox.size.center(Offset.zero), - _radiusController.value); - } - - /// Get the offset needs to translate, if it not specified then it - /// returns null value. - final Offset originOffset = MatrixUtils.getAsTranslation(transform); - canvas.save(); - - /// Translate the canvas based on offset value. - if (originOffset == null) { - canvas.transform(transform.storage); - } else { - canvas.translate(originOffset.dx, originOffset.dy); - } - - if (_clipCallback != null) { - /// Clip and draw the rect with fade animation value on canvas. - final Rect rect = _clipCallback(); - if (_borderRadius != BorderRadius.zero) { - final RRect roundedRect = RRect.fromRectAndCorners( - rect, - topLeft: _borderRadius.topLeft, - topRight: _borderRadius.topRight, - bottomLeft: _borderRadius.bottomLeft, - bottomRight: _borderRadius.bottomRight, - ); - canvas.clipRRect(roundedRect); - canvas.drawRRect(roundedRect, paint); - } else { - canvas.clipRect(rect); - canvas.drawRect(rect, paint); - } - } - - /// Draw the ripple on canvas. - canvas.drawCircle(center, _radius.value, paint); - canvas.restore(); - } -} - -/// Returns the maximum radius value calculated based on input touch position. -double _getTargetRadius(RenderBox referenceBox, bool containedInkWell, - RectCallback rectCallback, Offset position) { - /// If `containedInkWell` is false, then `rectCallback` should be null. - if (!containedInkWell) { - return Material.defaultSplashRadius; - } - - final Size size = - rectCallback != null ? rectCallback().size : referenceBox.size; - final double d1 = (position - size.topLeft(Offset.zero)).distance; - final double d2 = (position - size.topRight(Offset.zero)).distance; - final double d3 = (position - size.bottomLeft(Offset.zero)).distance; - final double d4 = (position - size.bottomRight(Offset.zero)).distance; - return math.max(math.max(d1, d2), math.max(d3, d4)).ceilToDouble(); -} - -/// Return the rect callback value based on its argument value. -RectCallback _getClipCallback( - RenderBox referenceBox, bool containedInkWell, RectCallback rectCallback) { - if (rectCallback != null) { - /// If `containedInkWell` is false, then `rectCallback` should be null. - assert(containedInkWell); - return rectCallback; - } - if (containedInkWell) return () => Offset.zero & referenceBox.size; - return null; -} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/day_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/day_view.dart index 2adeab933..ad7fc70a9 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/day_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/day_view.dart @@ -1,15 +1,20 @@ -part of calendar; - -class _TimeRegionView { - _TimeRegionView({this.visibleIndex, this.region, this.bound}); - - int visibleIndex = -1; - TimeRegion region; - Rect bound; -} - -class _TimeSlotWidget extends StatefulWidget { - _TimeSlotWidget( +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../appointment_engine/appointment_helper.dart'; +import '../common/calendar_view_helper.dart'; +import '../common/date_time_engine.dart'; +import '../common/event_args.dart'; +import '../settings/time_slot_view_settings.dart'; + +/// Used to hold the time slots view on calendar day, week, workweek views. +class TimeSlotWidget extends StatefulWidget { + /// Constructor to create the time slot widget to holds time slots view for + /// day, week, workweek views. + TimeSlotWidget( this.visibleDates, this.horizontalLinesCount, this.timeIntervalHeight, @@ -23,40 +28,74 @@ class _TimeSlotWidget extends StatefulWidget { this.textScaleFactor, this.timeRegionBuilder, this.width, - this.height); + this.height, + this.minDate, + this.maxDate); + /// Holds the visible dates collection for current time slot view. final List visibleDates; + + /// Defines the total number of time slots needed in the view. final double horizontalLinesCount; + + /// Defines the height of single time slot view. final double timeIntervalHeight; + + /// Defines the width of time label view. final double timeLabelWidth; - final Color cellBorderColor; + + /// Defines the time slot border color. + final Color? cellBorderColor; + + /// Holds the theme data value for calendar. final SfCalendarThemeData calendarTheme; + + /// Defines the time slot setting used to customize the time slots. final TimeSlotViewSettings timeSlotViewSettings; + + /// Defines the direction of the calendar widget is RTL or not. final bool isRTL; - final ValueNotifier calendarCellNotifier; - final List specialRegion; + + /// Used to draw the hovering on time slot view. + final ValueNotifier calendarCellNotifier; + + /// Defines the special time region for the current time slot view. + final List? specialRegion; + + /// Defines the scale factor for the time slot time text. final double textScaleFactor; - final TimeRegionBuilder timeRegionBuilder; + + /// Used to build the widget that replaces the time regions in time slot view. + final TimeRegionBuilder? timeRegionBuilder; + + /// Holds the current time slot widget width. final double width; + + /// Holds the current time slot widget height. final double height; + /// Defines the min date of the calendar. + final DateTime minDate; + + /// Defines the max date of the calendar. + final DateTime maxDate; + @override _TimeSlotWidgetState createState() => _TimeSlotWidgetState(); } -class _TimeSlotWidgetState extends State<_TimeSlotWidget> { - List _children; - List<_TimeRegionView> _specialRegionViews; +class _TimeSlotWidgetState extends State { + List _children = []; + List _specialRegionViews = []; @override void initState() { - _children = []; _updateSpecialRegionDetails(); super.initState(); } @override - void didUpdateWidget(_TimeSlotWidget oldWidget) { + void didUpdateWidget(TimeSlotWidget oldWidget) { if (widget.visibleDates != oldWidget.visibleDates || widget.horizontalLinesCount != oldWidget.horizontalLinesCount || widget.timeIntervalHeight != oldWidget.timeIntervalHeight || @@ -66,7 +105,8 @@ class _TimeSlotWidgetState extends State<_TimeSlotWidget> { widget.width != oldWidget.width || widget.height != oldWidget.height || widget.timeRegionBuilder != oldWidget.timeRegionBuilder || - !_isCollectionEqual(widget.specialRegion, oldWidget.specialRegion)) { + !CalendarViewHelper.isCollectionEqual( + widget.specialRegion, oldWidget.specialRegion)) { _updateSpecialRegionDetails(); _children.clear(); } @@ -76,23 +116,17 @@ class _TimeSlotWidgetState extends State<_TimeSlotWidget> { @override Widget build(BuildContext context) { - _children ??= []; if (_children.isEmpty && widget.timeRegionBuilder != null && - _specialRegionViews != null && _specialRegionViews.isNotEmpty) { final int count = _specialRegionViews.length; for (int i = 0; i < count; i++) { - final _TimeRegionView view = _specialRegionViews[i]; - final Widget child = widget.timeRegionBuilder( + final TimeRegionView view = _specialRegionViews[i]; + final Widget child = widget.timeRegionBuilder!( context, - TimeRegionDetails( - region: view.region, - date: widget.visibleDates[view.visibleIndex], - bounds: view.bound)); + TimeRegionDetails(view.region.data, + widget.visibleDates[view.visibleIndex], view.bound)); - /// Throw exception when builder return widget is null. - assert(child != null, 'Widget must not be null'); _children.add(RepaintBoundary(child: child)); } } @@ -112,30 +146,33 @@ class _TimeSlotWidgetState extends State<_TimeSlotWidget> { widget.width, widget.height, _specialRegionViews, + widget.minDate, + widget.maxDate, widgets: _children, ); } void _updateSpecialRegionDetails() { - _specialRegionViews = <_TimeRegionView>[]; - if (widget.specialRegion == null || widget.specialRegion.isEmpty) { + _specialRegionViews = []; + if (widget.specialRegion == null || widget.specialRegion!.isEmpty) { return; } final double minuteHeight = widget.timeIntervalHeight / - _getTimeInterval(widget.timeSlotViewSettings); - final DateTime startDate = _convertToStartTime(widget.visibleDates[0]); - final DateTime endDate = - _convertToEndTime(widget.visibleDates[widget.visibleDates.length - 1]); + CalendarViewHelper.getTimeInterval(widget.timeSlotViewSettings); + final DateTime startDate = + AppointmentHelper.convertToStartTime(widget.visibleDates[0]); + final DateTime endDate = AppointmentHelper.convertToEndTime( + widget.visibleDates[widget.visibleDates.length - 1]); final double width = widget.width - widget.timeLabelWidth; final double cellWidth = width / widget.visibleDates.length; - for (int i = 0; i < widget.specialRegion.length; i++) { - final TimeRegion region = widget.specialRegion[i]; - final DateTime regionStartTime = region._actualStartTime; - final DateTime regionEndTime = region._actualEndTime; + for (int i = 0; i < widget.specialRegion!.length; i++) { + final CalendarTimeRegion region = widget.specialRegion![i]; + final DateTime regionStartTime = region.actualStartTime; + final DateTime regionEndTime = region.actualEndTime; /// Check the start date and end date as same. - if (_isSameTimeSlot(regionStartTime, regionEndTime)) { + if (CalendarViewHelper.isSameTimeSlot(regionStartTime, regionEndTime)) { continue; } @@ -149,11 +186,12 @@ class _TimeSlotWidgetState extends State<_TimeSlotWidget> { continue; } - int startIndex = - _getVisibleDateIndex(widget.visibleDates, regionStartTime); - int endIndex = _getVisibleDateIndex(widget.visibleDates, regionEndTime); + int startIndex = DateTimeHelper.getVisibleDateIndex( + widget.visibleDates, regionStartTime); + int endIndex = DateTimeHelper.getVisibleDateIndex( + widget.visibleDates, regionEndTime); - double startYPosition = _getTimeToPosition( + double startYPosition = CalendarViewHelper.getTimeToPosition( Duration( hours: regionStartTime.hour, minutes: regionStartTime.minute), widget.timeSlotViewSettings, @@ -185,7 +223,7 @@ class _TimeSlotWidgetState extends State<_TimeSlotWidget> { startYPosition = 0; } - double endYPosition = _getTimeToPosition( + double endYPosition = CalendarViewHelper.getTimeToPosition( Duration(hours: regionEndTime.hour, minutes: regionEndTime.minute), widget.timeSlotViewSettings, minuteHeight); @@ -235,8 +273,7 @@ class _TimeSlotWidgetState extends State<_TimeSlotWidget> { final Rect rect = Rect.fromLTRB(startXPosition, startPosition, startXPosition + cellWidth, endPosition); - _specialRegionViews - .add(_TimeRegionView(region: region, visibleIndex: j, bound: rect)); + _specialRegionViews.add(TimeRegionView(j, region, rect)); } } } @@ -258,23 +295,27 @@ class _TimeSlotRenderWidget extends MultiChildRenderObjectWidget { this.width, this.height, this.specialRegionBounds, - {List widgets}) + this.minDate, + this.maxDate, + {List widgets = const []}) : super(children: widgets); final List visibleDates; final double horizontalLinesCount; final double timeIntervalHeight; final double timeLabelWidth; - final Color cellBorderColor; + final Color? cellBorderColor; final SfCalendarThemeData calendarTheme; final TimeSlotViewSettings timeSlotViewSettings; final bool isRTL; - final ValueNotifier calendarCellNotifier; - final List specialRegion; + final ValueNotifier calendarCellNotifier; + final List? specialRegion; final double textScaleFactor; final double width; final double height; - final List<_TimeRegionView> specialRegionBounds; + final List specialRegionBounds; + final DateTime minDate; + final DateTime maxDate; @override _TimeSlotRenderObject createRenderObject(BuildContext context) { @@ -292,7 +333,9 @@ class _TimeSlotRenderWidget extends MultiChildRenderObjectWidget { textScaleFactor, width, height, - specialRegionBounds); + specialRegionBounds, + minDate, + maxDate); } @override @@ -312,11 +355,13 @@ class _TimeSlotRenderWidget extends MultiChildRenderObjectWidget { ..textScaleFactor = textScaleFactor ..width = width ..height = height + ..minDate = minDate + ..maxDate = maxDate ..specialRegionBounds = specialRegionBounds; } } -class _TimeSlotRenderObject extends _CustomCalendarRenderObject { +class _TimeSlotRenderObject extends CustomCalendarRenderObject { _TimeSlotRenderObject( this._visibleDates, this._horizontalLinesCount, @@ -331,7 +376,9 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { this._textScaleFactor, this._width, this._height, - this.specialRegionBounds); + this.specialRegionBounds, + this._minDate, + this._maxDate); List _visibleDates; @@ -397,11 +444,11 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { } } - Color _cellBorderColor; + Color? _cellBorderColor; - Color get cellBorderColor => _cellBorderColor; + Color? get cellBorderColor => _cellBorderColor; - set cellBorderColor(Color value) { + set cellBorderColor(Color? value) { if (_cellBorderColor == value) { return; } @@ -457,18 +504,18 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { markNeedsPaint(); } - ValueNotifier _calendarCellNotifier; + ValueNotifier _calendarCellNotifier; - ValueNotifier get calendarCellNotifier => _calendarCellNotifier; + ValueNotifier get calendarCellNotifier => _calendarCellNotifier; - set calendarCellNotifier(ValueNotifier value) { + set calendarCellNotifier(ValueNotifier value) { if (_calendarCellNotifier == value) { return; } - _calendarCellNotifier?.removeListener(markNeedsPaint); + _calendarCellNotifier.removeListener(markNeedsPaint); _calendarCellNotifier = value; - _calendarCellNotifier?.addListener(markNeedsPaint); + _calendarCellNotifier.addListener(markNeedsPaint); } double _width; @@ -497,6 +544,31 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { markNeedsLayout(); } + DateTime _minDate; + + DateTime get minDate => _minDate; + + set minDate(DateTime value) { + if (CalendarViewHelper.isSameTimeSlot(_minDate, value)) { + return; + } + + _minDate = value; + markNeedsPaint(); + } + + DateTime _maxDate; + + DateTime get maxDate => _maxDate; + + set maxDate(DateTime value) { + if (CalendarViewHelper.isSameTimeSlot(_maxDate, value)) { + return; + } + _maxDate = value; + markNeedsPaint(); + } + double _textScaleFactor; double get textScaleFactor => _textScaleFactor; @@ -510,12 +582,12 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { markNeedsPaint(); } - List _specialRegion; + List? _specialRegion; - List get specialRegion => _specialRegion; + List? get specialRegion => _specialRegion; - set specialRegion(List value) { - if (_isCollectionEqual(_specialRegion, value)) { + set specialRegion(List? value) { + if (CalendarViewHelper.isCollectionEqual(_specialRegion, value)) { return; } @@ -527,9 +599,9 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { } } - List<_TimeRegionView> specialRegionBounds; - double _cellWidth; - Paint _linePainter; + List specialRegionBounds; + late double _cellWidth; + Paint _linePainter = Paint(); @override bool get isRepaintBoundary => true; @@ -538,13 +610,13 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { @override void attach(PipelineOwner owner) { super.attach(owner); - _calendarCellNotifier?.addListener(markNeedsPaint); + _calendarCellNotifier.addListener(markNeedsPaint); } /// detach will called when the render object removed from view. @override void detach() { - _calendarCellNotifier?.removeListener(markNeedsPaint); + _calendarCellNotifier.removeListener(markNeedsPaint); super.detach(); } @@ -553,14 +625,14 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { final Size widgetSize = constraints.biggest; size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, widgetSize.height.isInfinite ? height : widgetSize.height); - RenderBox child = firstChild; - if (specialRegion == null || specialRegion.isEmpty) { + RenderBox? child = firstChild; + if (specialRegion == null || specialRegion!.isEmpty) { return; } final int count = specialRegionBounds.length; for (int i = 0; i < count; i++) { - final _TimeRegionView view = specialRegionBounds[i]; + final TimeRegionView view = specialRegionBounds[i]; if (child == null) { continue; } @@ -576,21 +648,21 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { @override void paint(PaintingContext context, Offset offset) { - RenderBox child = firstChild; + RenderBox? child = firstChild; final bool isNeedDefaultPaint = childCount == 0; final double width = size.width - timeLabelWidth; _cellWidth = width / visibleDates.length; - _linePainter = _linePainter ?? Paint(); + _minMaxExceeds(minDate, maxDate, context.canvas); if (isNeedDefaultPaint) { _addSpecialRegions(context.canvas); } else { - if (specialRegion == null || specialRegion.isEmpty) { + if (specialRegion == null || specialRegion!.isEmpty) { return; } final int count = specialRegionBounds.length; for (int i = 0; i < count; i++) { - final _TimeRegionView view = specialRegionBounds[i]; + final TimeRegionView view = specialRegionBounds[i]; if (child == null) { continue; } @@ -599,10 +671,67 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { child = childAfter(child); } } - _drawTimeSlots(context.canvas); } + void _minMaxExceeds(DateTime minDate, DateTime maxDate, Canvas canvas) { + final DateTime visibleStartDate = visibleDates[0]; + final DateTime visibleEndDate = visibleDates[visibleDates.length - 1]; + final DateTime maxEndDate = AppointmentHelper.convertToEndTime( + visibleDates[visibleDates.length - 1]); + if (isDateWithInDateRange(visibleStartDate, visibleEndDate, minDate)) { + _drawDisabledDate(visibleStartDate, minDate, canvas); + } + if (isDateWithInDateRange(visibleStartDate, visibleEndDate, maxDate)) { + _drawDisabledDate(maxDate, maxEndDate, canvas); + } + } + + void _drawDisabledDate( + DateTime disabledStartDate, DateTime disabledEndDate, canvas) { + final double minuteHeight = timeIntervalHeight / + CalendarViewHelper.getTimeInterval(timeSlotViewSettings); + final double viewWidth = width - timeLabelWidth; + final double cellWidth = viewWidth / visibleDates.length; + + final int startIndex = + DateTimeHelper.getVisibleDateIndex(visibleDates, disabledStartDate); + final int endIndex = + DateTimeHelper.getVisibleDateIndex(visibleDates, disabledEndDate); + final double startYPosition = CalendarViewHelper.getTimeToPosition( + Duration( + hours: disabledStartDate.hour, minutes: disabledStartDate.minute), + timeSlotViewSettings, + minuteHeight); + final double endYPosition = CalendarViewHelper.getTimeToPosition( + Duration(hours: disabledEndDate.hour, minutes: disabledEndDate.minute), + timeSlotViewSettings, + minuteHeight); + for (int i = startIndex; i <= endIndex; i++) { + final double topPosition = i == startIndex ? startYPosition : 0; + final double bottomPosition = i == endIndex ? endYPosition : height; + + if ((topPosition <= 0 && bottomPosition <= 0) || + (topPosition >= height && bottomPosition >= height) || + (topPosition == bottomPosition)) { + continue; + } + double leftPosition = timeLabelWidth + (i * cellWidth); + double rightPosition = leftPosition + cellWidth; + + Rect rect; + if (isRTL) { + leftPosition = width - leftPosition; + rightPosition = width - rightPosition; + } + rect = Rect.fromLTRB( + leftPosition, topPosition, rightPosition, bottomPosition); + _linePainter.style = PaintingStyle.fill; + _linePainter.color = Colors.grey.withOpacity(0.2); + canvas.drawRect(rect, _linePainter); + } + } + void _drawTimeSlots(Canvas canvas) { double x, y; y = timeIntervalHeight; @@ -643,12 +772,12 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { void _addMouseHoveringForTimeSlot(Canvas canvas, Size size) { final double padding = 0.5; - double left = (calendarCellNotifier.value.dx ~/ _cellWidth) * _cellWidth; - double top = (calendarCellNotifier.value.dy ~/ timeIntervalHeight) * + double left = (calendarCellNotifier.value!.dx ~/ _cellWidth) * _cellWidth; + double top = (calendarCellNotifier.value!.dy ~/ timeIntervalHeight) * timeIntervalHeight; _linePainter.style = PaintingStyle.stroke; _linePainter.strokeWidth = 2; - _linePainter.color = calendarTheme.selectionBorderColor.withOpacity(0.4); + _linePainter.color = calendarTheme.selectionBorderColor!.withOpacity(0.4); left += (isRTL ? 0 : timeLabelWidth); top = top == 0 ? top + padding : top; double height = timeIntervalHeight; @@ -666,7 +795,7 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { } void _addSpecialRegions(Canvas canvas) { - if (specialRegion == null || specialRegion.isEmpty) { + if (specialRegion == null || specialRegion!.isEmpty) { return; } @@ -680,19 +809,18 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { _linePainter.style = PaintingStyle.fill; final int count = specialRegionBounds.length; for (int i = 0; i < count; i++) { - final _TimeRegionView view = specialRegionBounds[i]; - final TimeRegion region = view.region; + final TimeRegionView view = specialRegionBounds[i]; + final CalendarTimeRegion region = view.region; _linePainter.color = region.color ?? Colors.grey.withOpacity(0.2); final TextStyle textStyle = region.textStyle ?? TextStyle( - color: calendarTheme.brightness != null && - calendarTheme.brightness == Brightness.dark + color: calendarTheme.brightness == Brightness.dark ? Colors.white54 : Colors.black45); final Rect rect = view.bound; canvas.drawRect(rect, _linePainter); - if ((region.text == null || region.text.isEmpty) && + if ((region.text == null || region.text!.isEmpty) && region.iconData == null) { continue; } @@ -702,8 +830,8 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { painter.ellipsis = '..'; } else { painter.text = TextSpan( - text: String.fromCharCode(region.iconData.codePoint), - style: textStyle.copyWith(fontFamily: region.iconData.fontFamily)); + text: String.fromCharCode(region.iconData!.codePoint), + style: textStyle.copyWith(fontFamily: region.iconData!.fontFamily)); } painter.layout(minWidth: 0, maxWidth: rect.width - 4); @@ -727,11 +855,12 @@ class _TimeSlotRenderObject extends _CustomCalendarRenderObject { final double hour = (timeSlotViewSettings.startHour - timeSlotViewSettings.startHour.toInt()) * 60; + final int timeInterval = + CalendarViewHelper.getTimeInterval(timeSlotViewSettings); for (int j = 0; j < visibleDates.length; j++) { DateTime date = visibleDates[j]; for (int i = 0; i < horizontalLinesCount; i++) { - final double minute = - (i * _getTimeInterval(timeSlotViewSettings)) + hour; + final double minute = (i * timeInterval) + hour; date = DateTime(date.year, date.month, date.day, timeSlotViewSettings.startHour.toInt(), minute.toInt()); semanticsBuilder.add(CustomPainterSemantics( diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/header_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/header_view.dart deleted file mode 100644 index 86ddf7992..000000000 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/header_view.dart +++ /dev/null @@ -1,913 +0,0 @@ -part of calendar; - -@immutable -class _CalendarHeaderView extends StatefulWidget { - const _CalendarHeaderView( - this.visibleDates, - this.headerStyle, - this.currentDate, - this.view, - this.numberOfWeeksInView, - this.calendarTheme, - this.isRTL, - this.locale, - this.showNavigationArrow, - this.controller, - this.maxDate, - this.minDate, - this.width, - this.height, - this.nonWorkingDays, - this.navigationDirection, - this.showDatePickerButton, - this.isPickerShown, - this.allowedViews, - this.allowViewNavigation, - this.localizations, - this.removePicker, - this.valueChangeNotifier, - this.viewChangeNotifier, - this.headerTapCallback, - this.headerLongPressCallback, - this.todayHighlightColor, - this.textScaleFactor, - this.isMobilePlatform); - - final List visibleDates; - final CalendarHeaderStyle headerStyle; - final SfCalendarThemeData calendarTheme; - final DateTime currentDate; - final CalendarView view; - final int numberOfWeeksInView; - final bool isRTL; - final String locale; - final bool showNavigationArrow; - final CalendarController controller; - final DateTime maxDate; - final DateTime minDate; - final double width; - final double height; - final List nonWorkingDays; - final List allowedViews; - final bool allowViewNavigation; - final MonthNavigationDirection navigationDirection; - final VoidCallback removePicker; - final _CalendarHeaderCallback headerTapCallback; - final _CalendarHeaderCallback headerLongPressCallback; - final bool showDatePickerButton; - final SfLocalizations localizations; - final ValueNotifier valueChangeNotifier; - final ValueNotifier viewChangeNotifier; - final bool isPickerShown; - final double textScaleFactor; - final Color todayHighlightColor; - final bool isMobilePlatform; - - @override - _CalendarHeaderViewState createState() => _CalendarHeaderViewState(); -} - -class _CalendarHeaderViewState extends State<_CalendarHeaderView> { - Map _calendarViews; - - @override - void initState() { - widget.valueChangeNotifier.addListener(_updateHeaderChanged); - _calendarViews = _getCalendarViewsText(widget.localizations); - super.initState(); - } - - @override - void didUpdateWidget(_CalendarHeaderView oldWidget) { - if (widget.valueChangeNotifier != oldWidget.valueChangeNotifier) { - oldWidget.valueChangeNotifier.removeListener(_updateHeaderChanged); - widget.valueChangeNotifier.addListener(_updateHeaderChanged); - } - - _calendarViews = _getCalendarViewsText(widget.localizations); - super.didUpdateWidget(oldWidget); - } - - @override - Widget build(BuildContext context) { - final bool useMobilePlatformUI = - _isMobileLayoutUI(widget.width, widget.isMobilePlatform); - double arrowWidth = 0; - double headerWidth = widget.width; - - /// Navigation arrow enabled when [showNavigationArrow] in [SfCalendar] is - /// enabled and calendar view as not schedule, because schedule view does - /// not have a support for navigation arrow. - final bool navigationArrowEnabled = - widget.showNavigationArrow && widget.view != CalendarView.schedule; - double iconWidth = widget.width / 8; - iconWidth = iconWidth > 40 ? 40 : iconWidth; - double calendarViewWidth = 0; - - /// Assign arrow width as icon width when the navigation arrow enabled. - if (navigationArrowEnabled) { - arrowWidth = iconWidth; - } - - final String headerString = _getHeaderText(); - final double totalArrowWidth = arrowWidth * 2; - - /// Show calendar views on header when it is not empty. - final bool isNeedViewSwitchOption = - widget.allowedViews != null && widget.allowedViews.isNotEmpty; - double todayIconWidth = 0; - double dividerWidth = 0; - final List children = []; - Color headerTextColor = widget.headerStyle.textStyle != null - ? widget.headerStyle.textStyle.color - : (widget.calendarTheme.headerTextStyle.color); - headerTextColor ??= Colors.black87; - final Color arrowColor = - headerTextColor.withOpacity(headerTextColor.opacity * 0.6); - Color prevArrowColor = arrowColor; - Color nextArrowColor = arrowColor; - final TextStyle style = TextStyle(color: arrowColor); - final double defaultCalendarViewTextSize = 12; - Widget calendarViewIcon = Container(width: 0, height: 0); - const double padding = 5; - double headerIconTextWidth = widget.headerStyle.textStyle != null - ? widget.headerStyle.textStyle.fontSize - : widget.calendarTheme.headerTextStyle.fontSize; - headerIconTextWidth ??= 14; - final String todayText = widget.localizations.todayLabel; - - double maxHeaderHeight = 0; - - /// Today icon shown when the date picker enabled on calendar. - if (widget.showDatePickerButton) { - todayIconWidth = iconWidth; - if (!useMobilePlatformUI) { - /// 5 as padding for around today text view. - final Size todayButtonSize = _getTextWidgetWidth( - todayText, widget.height, widget.width - totalArrowWidth, context, - style: TextStyle(fontSize: defaultCalendarViewTextSize)); - maxHeaderHeight = todayButtonSize.height; - todayIconWidth = todayButtonSize.width + padding; - } - } - - double headerTextWidth = 0; - if (!widget.isMobilePlatform) { - final Size headerTextSize = _getTextWidgetWidth( - headerString, - widget.height, - widget.width - totalArrowWidth - todayIconWidth - padding, - context, - style: widget.headerStyle.textStyle ?? - widget.calendarTheme.headerTextStyle); - headerTextWidth = headerTextSize.width + - padding + - (widget.showDatePickerButton ? headerIconTextWidth : 0); - maxHeaderHeight = maxHeaderHeight > headerTextSize.height - ? maxHeaderHeight - : headerTextSize.height; - } - - if (isNeedViewSwitchOption) { - calendarViewWidth = iconWidth; - if (useMobilePlatformUI) { - maxHeaderHeight = - maxHeaderHeight != 0 && maxHeaderHeight <= widget.height - ? maxHeaderHeight - : widget.height; - - /// Render allowed views icon on mobile view. - calendarViewIcon = _getCalendarViewWidget( - useMobilePlatformUI, - false, - calendarViewWidth, - maxHeaderHeight, - style, - arrowColor, - headerTextColor, - widget.view, - widget.isMobilePlatform ? false : widget.viewChangeNotifier.value, - defaultCalendarViewTextSize, - semanticLabel: 'CalendarView'); - } else { - /// Assign divider width when today icon text shown. - dividerWidth = widget.showDatePickerButton ? 5 : 0; - - double totalWidth = - widget.width - totalArrowWidth - dividerWidth - todayIconWidth; - - totalWidth -= headerTextWidth; - final Map calendarViewsWidth = - {}; - double allowedViewsWidth = 0; - final int allowedViewsLength = widget.allowedViews.length; - - double maxCalendarViewHeight = 0; - - /// Calculate the allowed views horizontal width. - for (int i = 0; i < allowedViewsLength; i++) { - final CalendarView currentView = widget.allowedViews[i]; - final Size calendarViewSize = _getTextWidgetWidth( - _calendarViews[currentView], widget.height, totalWidth, context, - style: TextStyle(fontSize: defaultCalendarViewTextSize)); - final double currentViewTextWidth = calendarViewSize.width + padding; - maxCalendarViewHeight = - maxCalendarViewHeight > calendarViewSize.height - ? maxCalendarViewHeight - : calendarViewSize.height; - calendarViewsWidth[currentView] = currentViewTextWidth; - allowedViewsWidth += currentViewTextWidth; - } - - /// Check the header view width enough for hold allowed views then - /// render the allowed views as children. - if (allowedViewsWidth < totalWidth) { - calendarViewWidth = allowedViewsWidth; - maxHeaderHeight = maxCalendarViewHeight > maxHeaderHeight - ? maxCalendarViewHeight - : maxHeaderHeight; - maxHeaderHeight = - maxHeaderHeight > widget.height ? widget.height : maxHeaderHeight; - for (int i = 0; i < allowedViewsLength; i++) { - final CalendarView currentView = widget.allowedViews[i]; - children.add(_getCalendarViewWidget( - useMobilePlatformUI, - false, - calendarViewsWidth[currentView], - maxHeaderHeight, - style, - arrowColor, - headerTextColor, - currentView, - widget.view == currentView, - defaultCalendarViewTextSize)); - } - } else { - /// Render allowed views drop down when header view does not have a - /// space to hold the allowed views. - final Size calendarViewSize = _getTextWidgetWidth( - _calendarViews[widget.view], - widget.height, - widget.width - totalArrowWidth, - context, - style: TextStyle(fontSize: defaultCalendarViewTextSize)); - maxCalendarViewHeight = calendarViewSize.height; - maxHeaderHeight = maxCalendarViewHeight > maxHeaderHeight - ? maxCalendarViewHeight - : maxHeaderHeight; - maxHeaderHeight = - maxHeaderHeight > widget.height ? widget.height : maxHeaderHeight; - calendarViewWidth = - calendarViewSize.width + padding + headerIconTextWidth; - children.add(_getCalendarViewWidget( - useMobilePlatformUI, - true, - calendarViewWidth, - maxHeaderHeight, - style, - arrowColor, - headerTextColor, - widget.view, - widget.viewChangeNotifier.value, - defaultCalendarViewTextSize, - semanticLabel: 'CalendarView')); - } - } - } - - headerWidth = widget.width - - calendarViewWidth - - todayIconWidth - - dividerWidth - - totalArrowWidth; - final double headerHeight = - maxHeaderHeight != 0 && maxHeaderHeight <= widget.height - ? maxHeaderHeight - : widget.height; - final List dates = widget.visibleDates; - if (!_canMoveToNextView(widget.view, widget.numberOfWeeksInView, - widget.minDate, widget.maxDate, dates, widget.nonWorkingDays)) { - nextArrowColor = nextArrowColor.withOpacity(nextArrowColor.opacity * 0.5); - } - - if (!_canMoveToPreviousView(widget.view, widget.numberOfWeeksInView, - widget.minDate, widget.maxDate, dates, widget.nonWorkingDays)) { - prevArrowColor = prevArrowColor.withOpacity(prevArrowColor.opacity * 0.5); - } - - MainAxisAlignment _getAlignmentFromTextAlign() { - if (widget.headerStyle.textAlign == null || - widget.headerStyle.textAlign == TextAlign.left || - widget.headerStyle.textAlign == TextAlign.start) { - return MainAxisAlignment.start; - } else if (widget.headerStyle.textAlign == TextAlign.right || - widget.headerStyle.textAlign == TextAlign.end) { - return MainAxisAlignment.end; - } - - return MainAxisAlignment.center; - } - - double arrowSize = - headerHeight == widget.height ? headerHeight * 0.6 : headerHeight * 0.8; - arrowSize = arrowSize > 25 ? 25 : arrowSize; - arrowSize = arrowSize * widget.textScaleFactor; - final bool isCenterAlignment = !widget.isMobilePlatform && - (navigationArrowEnabled || isNeedViewSwitchOption) && - widget.headerStyle.textAlign != null && - (widget.headerStyle.textAlign == TextAlign.center || - widget.headerStyle.textAlign == TextAlign.justify); - - Alignment _getHeaderAlignment() { - if (widget.headerStyle.textAlign == null || - widget.headerStyle.textAlign == TextAlign.left || - widget.headerStyle.textAlign == TextAlign.start) { - return widget.isRTL ? Alignment.centerRight : Alignment.centerLeft; - } else if (widget.headerStyle.textAlign == TextAlign.right || - widget.headerStyle.textAlign == TextAlign.end) { - return widget.isRTL ? Alignment.centerLeft : Alignment.centerRight; - } - - return Alignment.center; - } - - final Widget headerText = widget.isMobilePlatform - ? Container( - alignment: Alignment.center, - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - width: isCenterAlignment && headerWidth > 200 ? 200 : headerWidth, - height: headerHeight, - padding: const EdgeInsets.all(2), - child: Material( - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - child: InkWell( - //// set splash color as transparent when header does not have - // date piker. - splashColor: - !widget.showDatePickerButton ? Colors.transparent : null, - highlightColor: - !widget.showDatePickerButton ? Colors.transparent : null, - hoverColor: - !widget.showDatePickerButton ? Colors.transparent : null, - splashFactory: _CustomSplashFactory(), - onTap: () { - widget.headerTapCallback( - calendarViewWidth + dividerWidth + todayIconWidth); - }, - onLongPress: () { - widget.headerLongPressCallback( - calendarViewWidth + dividerWidth + todayIconWidth); - }, - child: Semantics( - label: headerString, - child: Container( - width: isCenterAlignment && headerWidth > 200 - ? 200 - : headerWidth, - height: headerHeight, - alignment: Alignment.centerLeft, - padding: EdgeInsets.symmetric(horizontal: 5), - child: Row( - mainAxisAlignment: _getAlignmentFromTextAlign(), - children: widget.showDatePickerButton - ? [ - Flexible( - child: Text(headerString, - style: widget.headerStyle.textStyle ?? - widget.calendarTheme - .headerTextStyle, - maxLines: 1, - overflow: TextOverflow.clip, - softWrap: false, - textDirection: TextDirection.ltr)), - Icon( - widget.isPickerShown - ? Icons.arrow_drop_up - : Icons.arrow_drop_down, - color: arrowColor, - size: (widget.headerStyle.textStyle ?? - widget.calendarTheme - .headerTextStyle) - .fontSize ?? - 14, - ) - ] - : [ - Flexible( - child: Text(headerString, - style: widget.headerStyle.textStyle ?? - widget.calendarTheme - .headerTextStyle, - maxLines: 1, - overflow: TextOverflow.clip, - softWrap: false, - textDirection: TextDirection.ltr)) - ], - )), - ), - )), - ) - : Container( - alignment: _getHeaderAlignment(), - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - width: isCenterAlignment && headerWidth > 200 ? 200 : headerWidth, - height: headerHeight, - padding: const EdgeInsets.all(2), - child: Material( - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - child: InkWell( - //// set splash color as transparent when header does not have - // date piker. - splashColor: - !widget.showDatePickerButton ? Colors.transparent : null, - highlightColor: - !widget.showDatePickerButton ? Colors.transparent : null, - splashFactory: _CustomSplashFactory(), - onTap: () { - widget.headerTapCallback( - calendarViewWidth + dividerWidth + todayIconWidth); - }, - onLongPress: () { - widget.headerLongPressCallback( - calendarViewWidth + dividerWidth + todayIconWidth); - }, - child: Semantics( - label: headerString, - child: Container( - color: - widget.showDatePickerButton && widget.isPickerShown - ? Colors.grey.withOpacity(0.3) - : widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - width: isCenterAlignment && headerTextWidth > 200 - ? 200 - : headerTextWidth, - height: headerHeight, - alignment: Alignment.center, - padding: EdgeInsets.symmetric(horizontal: 5), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: widget.showDatePickerButton - ? [ - Flexible( - child: Text(headerString, - style: widget.headerStyle.textStyle ?? - widget.calendarTheme - .headerTextStyle, - maxLines: 1, - overflow: TextOverflow.clip, - softWrap: false, - textDirection: TextDirection.ltr)), - Icon( - widget.isPickerShown - ? Icons.arrow_drop_up - : Icons.arrow_drop_down, - color: arrowColor, - size: (widget.headerStyle.textStyle ?? - widget.calendarTheme - .headerTextStyle) - .fontSize ?? - 14, - ) - ] - : [ - Flexible( - child: Text(headerString, - style: widget.headerStyle.textStyle ?? - widget.calendarTheme - .headerTextStyle, - maxLines: 1, - overflow: TextOverflow.clip, - softWrap: false, - textDirection: TextDirection.ltr)) - ], - )), - ), - )), - ); - - final Container leftArrow = Container( - alignment: Alignment.center, - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - width: arrowWidth, - height: headerHeight, - padding: const EdgeInsets.all(2), - child: Material( - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - child: InkWell( - //// set splash color as transparent when arrow reaches min date(disabled) - splashColor: - prevArrowColor != arrowColor ? Colors.transparent : null, - highlightColor: - prevArrowColor != arrowColor ? Colors.transparent : null, - hoverColor: - prevArrowColor != arrowColor ? Colors.transparent : null, - splashFactory: _CustomSplashFactory(), - onTap: _backward, - child: Semantics( - label: 'Backward', - child: Container( - width: arrowWidth, - height: headerHeight, - alignment: Alignment.center, - child: Icon( - widget.navigationDirection == - MonthNavigationDirection.horizontal - ? Icons.chevron_left - : Icons.keyboard_arrow_up, - color: prevArrowColor, - size: arrowSize, - )), - ), - )), - ); - - final Container rightArrow = Container( - alignment: Alignment.center, - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - width: arrowWidth, - height: headerHeight, - padding: const EdgeInsets.all(2), - child: Material( - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - child: InkWell( - //// set splash color as transparent when arrow reaches max date(disabled) - splashColor: - nextArrowColor != arrowColor ? Colors.transparent : null, - highlightColor: - nextArrowColor != arrowColor ? Colors.transparent : null, - hoverColor: - nextArrowColor != arrowColor ? Colors.transparent : null, - splashFactory: _CustomSplashFactory(), - onTap: _forward, - child: Semantics( - label: 'Forward', - child: Container( - width: arrowWidth, - height: headerHeight, - alignment: Alignment.center, - child: Icon( - widget.navigationDirection == - MonthNavigationDirection.horizontal - ? Icons.chevron_right - : Icons.keyboard_arrow_down, - color: nextArrowColor, - size: arrowSize, - )), - ), - )), - ); - - final Widget todayIcon = Container( - alignment: Alignment.center, - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - width: todayIconWidth, - height: headerHeight, - padding: const EdgeInsets.all(2), - child: Material( - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - child: InkWell( - splashFactory: _CustomSplashFactory(), - onTap: () { - widget.removePicker(); - widget.controller.displayDate = DateTime.now(); - }, - child: Semantics( - label: todayText, - child: useMobilePlatformUI - ? Container( - width: todayIconWidth, - height: headerHeight, - alignment: Alignment.center, - child: Icon( - Icons.today, - color: style.color, - size: style.fontSize, - )) - : Container( - width: todayIconWidth, - alignment: Alignment.center, - child: Text( - todayText, - style: TextStyle( - color: headerTextColor, - fontSize: defaultCalendarViewTextSize), - maxLines: 1, - textDirection: TextDirection.ltr, - )), - ), - )), - ); - - final Widget dividerWidget = widget.showDatePickerButton && - isNeedViewSwitchOption && - !useMobilePlatformUI - ? Container( - alignment: Alignment.center, - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - width: dividerWidth, - height: headerHeight, - padding: const EdgeInsets.symmetric(vertical: 5), - child: VerticalDivider( - color: Colors.grey, - thickness: 0.5, - )) - : Container( - width: 0, - height: 0, - ); - - List rowChildren = []; - if (widget.headerStyle.textAlign == null || - widget.headerStyle.textAlign == TextAlign.left || - widget.headerStyle.textAlign == TextAlign.start) { - if (widget.isMobilePlatform) { - rowChildren = [ - headerText, - todayIcon, - calendarViewIcon, - leftArrow, - rightArrow, - ]; - } else { - rowChildren = [ - leftArrow, - rightArrow, - headerText, - todayIcon, - dividerWidget, - ]; - useMobilePlatformUI - ? rowChildren.add(calendarViewIcon) - : rowChildren.addAll(children); - } - - return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: rowChildren); - } else if (widget.headerStyle.textAlign == TextAlign.right || - widget.headerStyle.textAlign == TextAlign.end) { - if (widget.isMobilePlatform) { - rowChildren = [ - leftArrow, - rightArrow, - calendarViewIcon, - todayIcon, - headerText, - ]; - } else { - useMobilePlatformUI - ? rowChildren.add(calendarViewIcon) - : rowChildren.addAll(children); - - rowChildren.add(dividerWidget); - rowChildren.add(todayIcon); - rowChildren.add(headerText); - rowChildren.add(leftArrow); - rowChildren.add(rightArrow); - } - - return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: rowChildren); - } else { - if (widget.isMobilePlatform) { - rowChildren = [ - leftArrow, - headerText, - todayIcon, - dividerWidget, - calendarViewIcon, - rightArrow, - ]; - } else { - rowChildren = [ - leftArrow, - headerText, - rightArrow, - todayIcon, - dividerWidget, - ]; - useMobilePlatformUI - ? rowChildren.add(calendarViewIcon) - : rowChildren.addAll(children); - } - - return Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: rowChildren); - } - } - - @override - void dispose() { - widget.valueChangeNotifier.removeListener(_updateHeaderChanged); - super.dispose(); - } - - void _updateHeaderChanged() { - setState(() {}); - } - - void _backward() { - widget.removePicker(); - widget.controller.backward(); - } - - void _forward() { - widget.removePicker(); - widget.controller.forward(); - } - - Widget _getCalendarViewWidget( - bool useMobilePlatformUI, - bool isNeedIcon, - double width, - double height, - TextStyle style, - Color arrowColor, - Color headerTextColor, - CalendarView view, - bool isHighlighted, - double defaultCalendarViewTextSize, - {String semanticLabel}) { - final String text = _calendarViews[view]; - return Container( - alignment: Alignment.center, - color: widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - width: width, - height: height, - padding: EdgeInsets.all(2), - child: Material( - color: isHighlighted && (isNeedIcon || useMobilePlatformUI) - ? Colors.grey.withOpacity(0.3) - : widget.headerStyle.backgroundColor ?? - widget.calendarTheme.headerBackgroundColor, - child: InkWell( - splashFactory: _CustomSplashFactory(), - onTap: () { - if (isNeedIcon || useMobilePlatformUI) { - widget.viewChangeNotifier.value = - !widget.viewChangeNotifier.value; - } else { - widget.controller.view = view; - } - }, - child: Semantics( - label: semanticLabel ?? text, - child: useMobilePlatformUI - ? Container( - width: width, - height: height, - alignment: Alignment.center, - child: Icon( - Icons.more_vert, - color: style.color, - size: style.fontSize, - )) - : (isNeedIcon - ? Container( - width: width, - height: height, - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - text, - style: TextStyle( - color: headerTextColor, - fontSize: defaultCalendarViewTextSize), - maxLines: 1, - textDirection: TextDirection.ltr, - ), - Icon( - widget.viewChangeNotifier.value - ? Icons.arrow_drop_up - : Icons.arrow_drop_down, - color: arrowColor, - size: (widget.headerStyle.textStyle ?? - widget - .calendarTheme.headerTextStyle) - .fontSize ?? - 14, - ) - ], - )) - : Container( - width: width, - height: height, - alignment: Alignment.center, - child: Text( - text, - style: TextStyle( - color: isHighlighted - ? widget.todayHighlightColor ?? - widget.calendarTheme.todayHighlightColor - : headerTextColor, - fontSize: defaultCalendarViewTextSize), - maxLines: 1, - textDirection: TextDirection.ltr, - ))), - ), - )), - ); - } - - String _getHeaderText() { - String format = 'MMM'; - switch (widget.view) { - case CalendarView.schedule: - { - format = 'MMMM'; - return DateFormat(format, widget.locale) - .format(widget.valueChangeNotifier.value) - .toString() + - ' ' + - widget.valueChangeNotifier.value.year.toString(); - } - case CalendarView.month: - case CalendarView.timelineMonth: - { - final DateTime startDate = widget.visibleDates[0]; - final DateTime endDate = - widget.visibleDates[widget.visibleDates.length - 1]; - if (widget.numberOfWeeksInView != 6 && - startDate.month != endDate.month) { - return DateFormat(format, widget.locale) - .format(startDate) - .toString() + - ' ' + - startDate.year.toString() + - ' - ' + - DateFormat(format, widget.locale).format(endDate).toString() + - ' ' + - endDate.year.toString(); - } - - format = 'MMMM'; - return DateFormat(format, widget.locale) - .format(widget.currentDate) - .toString() + - ' ' + - widget.currentDate.year.toString(); - } - case CalendarView.day: - case CalendarView.week: - case CalendarView.workWeek: - { - final DateTime headerDate = widget.visibleDates[0]; - format = 'MMMM'; - return DateFormat(format, widget.locale) - .format(headerDate) - .toString() + - ' ' + - headerDate.year.toString(); - } - case CalendarView.timelineDay: - { - format = 'MMMM'; - final DateTime headerDate = widget.visibleDates[0]; - return DateFormat(format, widget.locale) - .format(headerDate) - .toString() + - ' ' + - headerDate.year.toString(); - } - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - { - final DateTime startDate = widget.visibleDates[0]; - final DateTime endDate = - widget.visibleDates[widget.visibleDates.length - 1]; - String startText = - DateFormat(format, widget.locale).format(startDate).toString(); - startText = startDate.day.toString() + ' ' + startText + ' - '; - final String endText = endDate.day.toString() + - ' ' + - DateFormat(format, widget.locale).format(endDate).toString() + - ' ' + - endDate.year.toString(); - - return startText + endText; - } - } - - return null; - } -} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/month_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/month_view.dart index 8dff17281..b696f23eb 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/month_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/month_view.dart @@ -1,7 +1,21 @@ -part of calendar; - -class _MonthViewWidget extends StatefulWidget { - _MonthViewWidget( +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../appointment_engine/appointment_helper.dart'; +import '../common/calendar_view_helper.dart'; +import '../common/date_time_engine.dart'; +import '../common/event_args.dart'; +import '../settings/month_view_settings.dart'; +import '../sfcalendar.dart'; + +/// Used to hold the month cell views on calendar month view. +class MonthViewWidget extends StatefulWidget { + /// Constructor to create the month view widget to holds month cells for + /// calendar month view. + MonthViewWidget( this.visibleDates, this.rowCount, this.monthCellStyle, @@ -23,32 +37,72 @@ class _MonthViewWidget extends StatefulWidget { this.height, this.visibleAppointmentNotifier); + /// Defines the row count for the month view. final int rowCount; + + /// Defines the style for the month cells. final MonthCellStyle monthCellStyle; + + /// Holds the current month view widget dates. final List visibleDates; + + /// Defines the direction of calendar widget is RTL or not. final bool isRTL; - final Color todayHighlightColor; - final TextStyle todayTextStyle; - final Color cellBorderColor; + + /// Defines the today month cell highlight color. + final Color? todayHighlightColor; + + /// Defines the today month cell text style. + final TextStyle? todayTextStyle; + + /// Defines the month cell border color. + final Color? cellBorderColor; + + /// Holds the theme data details for calendar. final SfCalendarThemeData calendarTheme; - final ValueNotifier calendarCellNotifier; + + /// Holds the current hovering point used to paint the hovering. + final ValueNotifier calendarCellNotifier; + + /// Defines the min date of the calendar. final DateTime minDate; + + /// Defines the max date of the calendar. final DateTime maxDate; + + /// Holds the calendar instance used to get the calendar properties. final SfCalendar calendar; + + /// Decides the trailing and leading of month view will visible or not. final bool showTrailingAndLeadingDates; - final List blackoutDates; - final TextStyle blackoutDatesTextStyle; + + /// Holds the blackout dates collection of calendar. + final List? blackoutDates; + + /// Defines the text style of the blackout dates month cell. + final TextStyle? blackoutDatesTextStyle; + + /// Defines the scale factor for the month cell text. final double textScaleFactor; + + /// Defines the width of the month view widget. final double width; + + /// Defines the height of the month view widget. final double height; - final MonthCellBuilder builder; - final ValueNotifier> visibleAppointmentNotifier; + + /// Used to build the widget that replaces the month cell. + final MonthCellBuilder? builder; + + /// Holds the visible appointment collection used to trigger the builder + /// when its value changed. + final ValueNotifier?> visibleAppointmentNotifier; @override _MonthViewWidgetState createState() => _MonthViewWidgetState(); } -class _MonthViewWidgetState extends State<_MonthViewWidget> { +class _MonthViewWidgetState extends State { @override void initState() { widget.visibleAppointmentNotifier.addListener(_updateAppointment); @@ -56,11 +110,11 @@ class _MonthViewWidgetState extends State<_MonthViewWidget> { } @override - void didUpdateWidget(_MonthViewWidget oldWidget) { + void didUpdateWidget(MonthViewWidget oldWidget) { if (widget.visibleAppointmentNotifier != oldWidget.visibleAppointmentNotifier) { - oldWidget.visibleAppointmentNotifier?.removeListener(_updateAppointment); - widget.visibleAppointmentNotifier?.addListener(_updateAppointment); + oldWidget.visibleAppointmentNotifier.removeListener(_updateAppointment); + widget.visibleAppointmentNotifier.addListener(_updateAppointment); } super.didUpdateWidget(oldWidget); @@ -68,7 +122,7 @@ class _MonthViewWidgetState extends State<_MonthViewWidget> { @override void dispose() { - widget.visibleAppointmentNotifier?.removeListener(_updateAppointment); + widget.visibleAppointmentNotifier.removeListener(_updateAppointment); super.dispose(); } @@ -77,13 +131,14 @@ class _MonthViewWidgetState extends State<_MonthViewWidget> { final List children = []; if (widget.builder != null) { final int visibleDatesCount = widget.visibleDates.length; - final double cellWidth = widget.width / _kNumberOfDaysInWeek; + final double cellWidth = widget.width / DateTime.daysPerWeek; final double cellHeight = widget.height / widget.rowCount; double xPosition = 0, yPosition = 0; final int currentMonth = widget.visibleDates[visibleDatesCount ~/ 2].month; - final bool showTrailingLeadingDates = _isLeadingAndTrailingDatesVisible( - widget.rowCount, widget.showTrailingAndLeadingDates); + final bool showTrailingLeadingDates = + CalendarViewHelper.isLeadingAndTrailingDatesVisible( + widget.rowCount, widget.showTrailingAndLeadingDates); for (int i = 0; i < visibleDatesCount; i++) { final DateTime currentVisibleDate = widget.visibleDates[i]; if (!showTrailingLeadingDates && @@ -97,29 +152,33 @@ class _MonthViewWidgetState extends State<_MonthViewWidget> { continue; } - final List appointments = _getSelectedDateAppointments( - widget.visibleAppointmentNotifier.value, - widget.calendar.timeZone, - currentVisibleDate); + final List appointments = + AppointmentHelper.getSelectedDateAppointments( + widget.visibleAppointmentNotifier.value, + widget.calendar.timeZone, + currentVisibleDate); List monthCellAppointment = appointments; if (widget.calendar.dataSource != null && - !_isCalendarAppointment(widget.calendar.dataSource)) { - monthCellAppointment = _getCustomAppointments(appointments); + !AppointmentHelper.isCalendarAppointment( + widget.calendar.dataSource!)) { + monthCellAppointment = + CalendarViewHelper.getCustomAppointments(appointments); } - final MonthCellDetails details = MonthCellDetails( - date: currentVisibleDate, - visibleDates: List.unmodifiable(widget.visibleDates), - appointments: List.unmodifiable(monthCellAppointment), - bounds: Rect.fromLTWH( - widget.isRTL ? widget.width - xPosition - cellWidth : xPosition, - yPosition, - cellWidth, - cellHeight)); - final Widget child = widget.builder(context, details); - if (child != null) { - children.add(RepaintBoundary(child: child)); - } + final Widget child = widget.builder!( + context, + MonthCellDetails( + currentVisibleDate, + List.unmodifiable(monthCellAppointment), + List.unmodifiable(widget.visibleDates), + Rect.fromLTWH( + widget.isRTL + ? widget.width - xPosition - cellWidth + : xPosition, + yPosition, + cellWidth, + cellHeight))); + children.add(RepaintBoundary(child: child)); xPosition += cellWidth; if (xPosition + 1 >= widget.width) { @@ -179,23 +238,23 @@ class _MonthViewRenderObjectWidget extends MultiChildRenderObjectWidget { this.textScaleFactor, this.width, this.height, - {List children}) + {List children = const []}) : super(children: children); final int rowCount; final MonthCellStyle monthCellStyle; final List visibleDates; - final List visibleAppointments; + final List? visibleAppointments; final bool isRTL; - final Color todayHighlightColor; - final TextStyle todayTextStyle; - final Color cellBorderColor; + final Color? todayHighlightColor; + final TextStyle? todayTextStyle; + final Color? cellBorderColor; final SfCalendarThemeData calendarTheme; - final ValueNotifier calendarCellNotifier; + final ValueNotifier calendarCellNotifier; final DateTime minDate; final DateTime maxDate; - final List blackoutDates; - final TextStyle blackoutDatesTextStyle; + final List? blackoutDates; + final TextStyle? blackoutDatesTextStyle; final bool showTrailingAndLeadingDates; final double textScaleFactor; final double width; @@ -249,7 +308,7 @@ class _MonthViewRenderObjectWidget extends MultiChildRenderObjectWidget { } } -class _MonthViewRenderObject extends _CustomCalendarRenderObject { +class _MonthViewRenderObject extends CustomCalendarRenderObject { _MonthViewRenderObject( this._visibleDates, this._visibleAppointments, @@ -322,11 +381,11 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { markNeedsPaint(); } - Color _todayHighlightColor; + Color? _todayHighlightColor; - Color get todayHighlightColor => _todayHighlightColor; + Color? get todayHighlightColor => _todayHighlightColor; - set todayHighlightColor(Color value) { + set todayHighlightColor(Color? value) { if (_todayHighlightColor == value) { return; } @@ -339,11 +398,11 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { markNeedsPaint(); } - TextStyle _todayTextStyle; + TextStyle? _todayTextStyle; - TextStyle get todayTextStyle => _todayTextStyle; + TextStyle? get todayTextStyle => _todayTextStyle; - set todayTextStyle(TextStyle value) { + set todayTextStyle(TextStyle? value) { if (_todayTextStyle == value) { return; } @@ -356,11 +415,11 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { markNeedsPaint(); } - Color _cellBorderColor; + Color? _cellBorderColor; - Color get cellBorderColor => _cellBorderColor; + Color? get cellBorderColor => _cellBorderColor; - set cellBorderColor(Color value) { + set cellBorderColor(Color? value) { if (_cellBorderColor == value) { return; } @@ -480,11 +539,11 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { markNeedsPaint(); } - List _visibleAppointments; + List? _visibleAppointments; - List get visibleAppointments => _visibleAppointments; + List? get visibleAppointments => _visibleAppointments; - set visibleAppointments(List value) { + set visibleAppointments(List? value) { if (_visibleAppointments == value) { return; } @@ -497,18 +556,19 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { markNeedsPaint(); } - List _blackoutDates; + List? _blackoutDates; - List get blackoutDates => _blackoutDates; + List? get blackoutDates => _blackoutDates; - set blackoutDates(List value) { + set blackoutDates(List? value) { if (_blackoutDates == value) { return; } - final List oldDates = _blackoutDates; + final List? oldDates = _blackoutDates; _blackoutDates = value; - if (_isEmptyList(_blackoutDates) && _isEmptyList(oldDates)) { + if (CalendarViewHelper.isEmptyList(_blackoutDates) && + CalendarViewHelper.isEmptyList(oldDates)) { return; } @@ -516,11 +576,11 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { markNeedsPaint(); } - TextStyle _blackoutDatesTextStyle; + TextStyle? _blackoutDatesTextStyle; - TextStyle get blackoutDatesTextStyle => _blackoutDatesTextStyle; + TextStyle? get blackoutDatesTextStyle => _blackoutDatesTextStyle; - set blackoutDatesTextStyle(TextStyle value) { + set blackoutDatesTextStyle(TextStyle? value) { if (_blackoutDatesTextStyle == value) { return; } @@ -533,31 +593,31 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { markNeedsPaint(); } - ValueNotifier _calendarCellNotifier; + ValueNotifier _calendarCellNotifier; - ValueNotifier get calendarCellNotifier => _calendarCellNotifier; + ValueNotifier get calendarCellNotifier => _calendarCellNotifier; - set calendarCellNotifier(ValueNotifier value) { + set calendarCellNotifier(ValueNotifier value) { if (_calendarCellNotifier == value) { return; } - _calendarCellNotifier?.removeListener(markNeedsPaint); + _calendarCellNotifier.removeListener(markNeedsPaint); _calendarCellNotifier = value; - _calendarCellNotifier?.addListener(markNeedsPaint); + _calendarCellNotifier.addListener(markNeedsPaint); } /// attach will called when the render object rendered in view. @override void attach(PipelineOwner owner) { super.attach(owner); - _calendarCellNotifier?.addListener(markNeedsPaint); + _calendarCellNotifier.addListener(markNeedsPaint); } /// detach will called when the render object removed from view. @override void detach() { - _calendarCellNotifier?.removeListener(markNeedsPaint); + _calendarCellNotifier.removeListener(markNeedsPaint); super.detach(); } @@ -566,7 +626,7 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { final Size widgetSize = constraints.biggest; size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, widgetSize.height.isInfinite ? height : widgetSize.height); - final double cellWidth = size.width / _kNumberOfDaysInWeek; + final double cellWidth = size.width / DateTime.daysPerWeek; final double cellHeight = size.height / rowCount; for (var child = firstChild; child != null; child = childAfter(child)) { child.layout(constraints.copyWith( @@ -580,17 +640,18 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { @override void paint(PaintingContext context, Offset offset) { final bool _isNeedCustomPaint = childCount != 0; - final double cellWidth = size.width / _kNumberOfDaysInWeek; + final double cellWidth = size.width / DateTime.daysPerWeek; final double cellHeight = size.height / rowCount; if (!_isNeedCustomPaint) { _drawMonthCells(context.canvas, size); } else { double xPosition = 0, yPosition = 0; - RenderBox child = firstChild; + RenderBox? child = firstChild; final int visibleDatesCount = visibleDates.length; final int currentMonth = visibleDates[visibleDatesCount ~/ 2].month; - final bool showTrailingLeadingDates = _isLeadingAndTrailingDatesVisible( - rowCount, showTrailingAndLeadingDates); + final bool showTrailingLeadingDates = + CalendarViewHelper.isLeadingAndTrailingDatesVisible( + rowCount, showTrailingAndLeadingDates); for (int i = 0; i < visibleDatesCount; i++) { final DateTime currentVisibleDate = visibleDates[i]; if (!showTrailingLeadingDates && @@ -603,14 +664,15 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { continue; } - child.paint( + child!.paint( context, Offset(isRTL ? size.width - xPosition - cellWidth : xPosition, yPosition)); child = childAfter(child); if (calendarCellNotifier.value != null && - !_isDateInDateCollection(blackoutDates, currentVisibleDate)) { + !CalendarViewHelper.isDateInDateCollection( + blackoutDates, currentVisibleDate)) { _addMouseHovering(context.canvas, size, cellWidth, cellHeight, xPosition, yPosition); } @@ -624,18 +686,18 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { } } - Paint _linePainter; - TextPainter _textPainter; + Paint _linePainter = Paint(); + TextPainter _textPainter = TextPainter(); static const double linePadding = 0.5; - List _blackoutDatesIndex; + List _blackoutDatesIndex = []; void _updateBlackoutDatesIndex() { _blackoutDatesIndex = []; - final int count = blackoutDates == null ? 0 : blackoutDates.length; + final int count = blackoutDates == null ? 0 : blackoutDates!.length; for (int i = 0; i < count; i++) { - final DateTime blackoutDate = blackoutDates[i]; + final DateTime blackoutDate = blackoutDates![i]; final int blackoutDateIndex = - _getVisibleDateIndex(visibleDates, blackoutDate); + DateTimeHelper.getVisibleDateIndex(visibleDates, blackoutDate); if (blackoutDateIndex == -1) { continue; } @@ -645,18 +707,17 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { } void _drawMonthCells(Canvas canvas, Size size) { - if (_blackoutDatesIndex == null || _blackoutDatesIndex.isEmpty) { + if (_blackoutDatesIndex.isEmpty) { _updateBlackoutDatesIndex(); } double xPosition, yPosition; - final double cellWidth = size.width / _kNumberOfDaysInWeek; + final double cellWidth = size.width / DateTime.daysPerWeek; final double cellHeight = size.height / rowCount; xPosition = isRTL ? size.width - cellWidth : 0; const double viewPadding = 5; const double circlePadding = 4; yPosition = viewPadding; - _textPainter = _textPainter ?? TextPainter(); _textPainter.textDirection = TextDirection.ltr; _textPainter.textWidthBasis = TextWidthBasis.longestLine; _textPainter.textScaleFactor = textScaleFactor; @@ -669,7 +730,6 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { final DateTime today = DateTime.now(); bool isCurrentDate; - _linePainter = _linePainter ?? Paint(); _linePainter.isAntiAlias = true; final TextStyle todayStyle = todayTextStyle ?? calendarTheme.todayTextStyle; final TextStyle currentMonthTextStyle = @@ -679,11 +739,12 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { calendarTheme.trailingDatesTextStyle; final TextStyle nextMonthTextStyle = monthCellStyle.leadingDatesTextStyle ?? calendarTheme.leadingDatesTextStyle; - final TextStyle blackoutDatesStyle = + final TextStyle? blackoutDatesStyle = blackoutDatesTextStyle ?? calendarTheme.blackoutDatesTextStyle; - final bool showTrailingLeadingDates = _isLeadingAndTrailingDatesVisible( - rowCount, showTrailingAndLeadingDates); + final bool showTrailingLeadingDates = + CalendarViewHelper.isLeadingAndTrailingDatesVisible( + rowCount, showTrailingAndLeadingDates); for (int i = 0; i < visibleDatesCount; i++) { isCurrentDate = false; @@ -799,9 +860,8 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { } if (isCurrentDate) { - _linePainter ??= Paint(); _linePainter.style = PaintingStyle.fill; - _linePainter.color = todayHighlightColor; + _linePainter.color = todayHighlightColor!; _linePainter.isAntiAlias = true; final double textHeight = _textPainter.height / 2; @@ -841,14 +901,13 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { void _addMouseHovering(Canvas canvas, Size size, double cellWidth, double cellHeight, double xPosition, double yPosition) { - if (xPosition <= calendarCellNotifier.value.dx && - xPosition + cellWidth >= calendarCellNotifier.value.dx && - yPosition <= calendarCellNotifier.value.dy && - yPosition + cellHeight >= calendarCellNotifier.value.dy) { - _linePainter = _linePainter ?? Paint(); + if (xPosition <= calendarCellNotifier.value!.dx && + xPosition + cellWidth >= calendarCellNotifier.value!.dx && + yPosition <= calendarCellNotifier.value!.dy && + yPosition + cellHeight >= calendarCellNotifier.value!.dy) { _linePainter.style = PaintingStyle.stroke; _linePainter.strokeWidth = 2; - _linePainter.color = calendarTheme.selectionBorderColor.withOpacity(0.4); + _linePainter.color = calendarTheme.selectionBorderColor!.withOpacity(0.4); canvas.drawRect( Rect.fromLTWH( xPosition == 0 ? xPosition + linePadding : xPosition, @@ -891,7 +950,7 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { String _getAccessibilityText(DateTime date, int index) { final String accessibilityText = DateFormat('EEE, dd/MMMM/yyyy').format(date).toString(); - if (_blackoutDatesIndex != null && _blackoutDatesIndex.contains(index)) { + if (_blackoutDatesIndex.contains(index)) { return accessibilityText + ', Blackout date'; } @@ -906,10 +965,11 @@ class _MonthViewRenderObject extends _CustomCalendarRenderObject { final List semanticsBuilder = []; double left = 0, top = 0; - final double cellWidth = size.width / _kNumberOfDaysInWeek; + final double cellWidth = size.width / DateTime.daysPerWeek; final double cellHeight = size.height / rowCount; - final bool showTrailingLeadingDates = _isLeadingAndTrailingDatesVisible( - rowCount, showTrailingAndLeadingDates); + final bool showTrailingLeadingDates = + CalendarViewHelper.isLeadingAndTrailingDatesVisible( + rowCount, showTrailingAndLeadingDates); final int currentMonth = visibleDates[visibleDates.length ~/ 2].month; for (int i = 0; i < visibleDates.length; i++) { final DateTime currentVisibleDate = visibleDates[i]; diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/multi_child_container.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/multi_child_container.dart deleted file mode 100644 index 83a2da8af..000000000 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/multi_child_container.dart +++ /dev/null @@ -1,193 +0,0 @@ -part of calendar; - -class _CalendarMultiChildContainer extends Stack { - _CalendarMultiChildContainer( - {this.painter, List children, this.width, this.height}) - : super(children: children); - final CustomPainter painter; - final double width; - final double height; - - @override - RenderStack createRenderObject(BuildContext context) { - return _MultiChildContainerRenderObject(width, height, painter: painter); - } - - @override - void updateRenderObject(BuildContext context, RenderStack renderObject) { - super.updateRenderObject(context, renderObject); - if (renderObject is _MultiChildContainerRenderObject) { - renderObject - ..width = width - ..height = height - ..painter = painter; - } - } -} - -class _MultiChildContainerRenderObject extends RenderStack { - _MultiChildContainerRenderObject(this._width, this._height, - {CustomPainter painter}) - : _painter = painter, - super(); - - CustomPainter get painter => _painter; - CustomPainter _painter; - - set painter(CustomPainter value) { - if (_painter == value) { - return; - } - - final CustomPainter oldPainter = _painter; - _painter = value; - _updatePainter(_painter, oldPainter); - if (attached) { - oldPainter?.removeListener(markNeedsPaint); - _painter?.addListener(markNeedsPaint); - } - } - - double get width => _width; - - set width(double value) { - if (_width == value) { - return; - } - - _width = value; - markNeedsLayout(); - } - - double _width; - double _height; - - double get height => _height; - - set height(double value) { - if (_height == value) { - return; - } - - _height = value; - markNeedsLayout(); - } - - void _updatePainter(CustomPainter newPainter, CustomPainter oldPainter) { - if (newPainter == null) { - markNeedsPaint(); - } else if (oldPainter == null || - newPainter.runtimeType != oldPainter.runtimeType || - newPainter.shouldRepaint(oldPainter)) { - markNeedsPaint(); - } - - if (newPainter == null) { - if (attached) { - markNeedsSemanticsUpdate(); - } - } else if (oldPainter == null || - newPainter.runtimeType != oldPainter.runtimeType || - newPainter.shouldRebuildSemantics(oldPainter)) { - markNeedsSemanticsUpdate(); - } - } - - @override - void attach(PipelineOwner owner) { - super.attach(owner); - _painter?.addListener(markNeedsPaint); - } - - @override - void detach() { - _painter?.removeListener(markNeedsPaint); - super.detach(); - } - - @override - void performLayout() { - final Size widgetSize = constraints.biggest; - size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, - widgetSize.height.isInfinite ? height : widgetSize.height); - for (var child = firstChild; child != null; child = childAfter(child)) { - child.layout(constraints); - } - } - - @override - void paint(PaintingContext context, Offset offset) { - if (_painter != null) { - _painter.paint(context.canvas, size); - } - - paintStack(context, offset); - } - - @override - void describeSemanticsConfiguration(SemanticsConfiguration config) { - super.describeSemanticsConfiguration(config); - config.isSemanticBoundary = true; - } - - @override - void assembleSemanticsNode( - SemanticsNode node, - SemanticsConfiguration config, - Iterable children, - ) { - final List semantics = semanticsBuilder(size); - final List semanticsNodes = []; - for (int i = 0; i < semantics.length; i++) { - final CustomPainterSemantics currentSemantics = semantics[i]; - final SemanticsNode newChild = SemanticsNode( - key: currentSemantics.key, - ); - - final SemanticsProperties properties = currentSemantics.properties; - final SemanticsConfiguration config = SemanticsConfiguration(); - if (properties.label != null) { - config.label = properties.label; - } - if (properties.textDirection != null) { - config.textDirection = properties.textDirection; - } - - newChild.updateWith( - config: config, - // As of now CustomPainter does not support multiple tree levels. - childrenInInversePaintOrder: const [], - ); - - newChild - ..rect = currentSemantics.rect - ..transform = currentSemantics.transform - ..tags = currentSemantics.tags; - - semanticsNodes.add(newChild); - } - - final List finalChildren = []; - finalChildren.addAll(semanticsNodes); - finalChildren.addAll(children); - - super.assembleSemanticsNode(node, config, finalChildren); - } - - SemanticsBuilderCallback get semanticsBuilder { - final List semantics = []; - if (painter != null) { - semantics.addAll(painter.semanticsBuilder(size)); - } - for (RenderRepaintBoundary child = firstChild; - child != null; - child = childAfter(child)) { - final _CustomCalendarRenderObject appointmentRenderObject = child.child; - semantics.addAll(appointmentRenderObject.semanticsBuilder(size)); - } - - return (Size size) { - return semantics; - }; - } -} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/schedule_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/schedule_view.dart deleted file mode 100644 index 27e6641a8..000000000 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/schedule_view.dart +++ /dev/null @@ -1,475 +0,0 @@ -part of calendar; - -/// Used to store the height and intersection point of scroll view item. -/// intersection point used to identify the view does not have same month dates. -class _ScheduleViewDetails { - double _height; - double _intersectPoint; -} - -@immutable -class _ScheduleViewHoveringDetails { - const _ScheduleViewHoveringDetails(this.hoveringDate, this.hoveringOffset); - - final DateTime hoveringDate; - final Offset hoveringOffset; -} - -//// Extra small devices (phones, 600px and down) -//// @media only screen and (max-width: 600px) {...} -//// -//// Small devices (portrait tablets and large phones, 600px and up) -//// @media only screen and (min-width: 600px) {...} -//// -//// Medium devices (landscape tablets, 768px and up) -//// media only screen and (min-width: 768px) {...} -//// -//// Large devices (laptops/desktops, 992px and up) -//// media only screen and (min-width: 992px) {...} -//// -//// Extra large devices (large laptops and desktops, 1200px and up) -//// media only screen and (min-width: 1200px) {...} -//// Default width to render the mobile UI in web, if the device width exceeds -//// the given width agenda view will render the web UI. -const double _kMobileViewWidth = 767; - -/// It is used to generate the week and month label of schedule calendar view. -class _ScheduleLabelPainter extends CustomPainter { - _ScheduleLabelPainter( - this.startDate, - this.endDate, - this.scheduleViewSettings, - this.isMonthLabel, - this.isRTL, - this.locale, - this.useMobilePlatformUI, - this.agendaViewNotifier, - this.calendarTheme, - this._localizations, - this.textScaleFactor, - {this.isDisplayDate = false}) - : super(repaint: isDisplayDate ? agendaViewNotifier : null); - - final DateTime startDate; - final DateTime endDate; - final bool isMonthLabel; - final bool isRTL; - final String locale; - final ScheduleViewSettings scheduleViewSettings; - final SfLocalizations _localizations; - final bool useMobilePlatformUI; - final ValueNotifier<_ScheduleViewHoveringDetails> agendaViewNotifier; - final SfCalendarThemeData calendarTheme; - final bool isDisplayDate; - final double textScaleFactor; - TextPainter _textPainter; - Paint _backgroundPainter; - - @override - void paint(Canvas canvas, Size size) { - /// Draw the week label. - if (!isMonthLabel) { - if (isDisplayDate) { - _addDisplayDateLabel(canvas, size); - } else { - _addWeekLabel(canvas, size); - } - } else { - /// Draw the month label - _addMonthLabel(canvas, size); - } - } - - void _addDisplayDateLabel(Canvas canvas, Size size) { - /// Add the localized add new appointment text for display date view. - final TextSpan span = TextSpan( - text: _localizations.noEventsCalendarLabel, - style: scheduleViewSettings.weekHeaderSettings.weekTextStyle ?? - const TextStyle( - color: Colors.grey, fontSize: 15, fontFamily: 'Roboto'), - ); - - double xPosition = 10; - _updateTextPainter(span); - - _textPainter.layout( - minWidth: 0, - maxWidth: size.width - xPosition > 0 ? size.width - xPosition : 0); - if (isRTL) { - xPosition = size.width - _textPainter.width - xPosition; - } - - /// Draw display date view text - _textPainter.paint( - canvas, Offset(xPosition, (size.height - _textPainter.height) / 2)); - - /// Add hovering effect on display date view. - if (isDisplayDate && - agendaViewNotifier.value != null && - isSameDate(agendaViewNotifier.value.hoveringDate, startDate)) { - _backgroundPainter ??= Paint(); - const double padding = 5; - if (useMobilePlatformUI) { - final Rect rect = Rect.fromLTWH( - 0, padding, size.width - 2, size.height - (2 * padding)); - _backgroundPainter.color = - calendarTheme.selectionBorderColor.withOpacity(0.4); - _backgroundPainter.style = PaintingStyle.stroke; - _backgroundPainter.strokeWidth = 2; - canvas.drawRect(rect, _backgroundPainter); - _backgroundPainter.style = PaintingStyle.fill; - } else { - const double viewPadding = 2; - final Rect rect = Rect.fromLTWH( - 0, - padding + viewPadding, - size.width - (isRTL ? viewPadding : padding), - size.height - (2 * (viewPadding + padding))); - _backgroundPainter.color = Colors.grey.withOpacity(0.1); - canvas.drawRRect(RRect.fromRectAndRadius(rect, Radius.circular(4)), - _backgroundPainter); - } - } - } - - void _addWeekLabel(Canvas canvas, Size size) { - double xPosition = 0; - const double yPosition = 0; - final String startDateFormat = - scheduleViewSettings.weekHeaderSettings.startDateFormat ?? 'MMM dd'; - String endDateFormat = - scheduleViewSettings.weekHeaderSettings.endDateFormat; - if (startDate.month == endDate.month && endDateFormat == null) { - endDateFormat = 'dd'; - } - - endDateFormat ??= 'MMM dd'; - final String firstDate = - DateFormat(startDateFormat, locale).format(startDate).toString(); - final String lastDate = - DateFormat(endDateFormat, locale).format(endDate).toString(); - final TextSpan span = TextSpan( - text: firstDate + ' - ' + lastDate, - style: scheduleViewSettings.weekHeaderSettings.weekTextStyle ?? - const TextStyle( - color: Colors.grey, fontSize: 15, fontFamily: 'Roboto'), - ); - _backgroundPainter ??= Paint(); - _backgroundPainter.color = - scheduleViewSettings.weekHeaderSettings.backgroundColor; - - /// Draw week label background. - canvas.drawRect( - Rect.fromLTWH(0, yPosition, size.width, - scheduleViewSettings.weekHeaderSettings.height), - _backgroundPainter); - _updateTextPainter(span); - - _textPainter.layout( - minWidth: 0, maxWidth: size.width - 10 > 0 ? size.width - 10 : 0); - - if (scheduleViewSettings.weekHeaderSettings.textAlign == TextAlign.right || - scheduleViewSettings.weekHeaderSettings.textAlign == TextAlign.end) { - xPosition = size.width - _textPainter.width; - } else if (scheduleViewSettings.weekHeaderSettings.textAlign == - TextAlign.center) { - xPosition = size.width / 2 - _textPainter.width / 2; - } - - if (isRTL) { - xPosition = size.width - _textPainter.width - xPosition; - if (scheduleViewSettings.weekHeaderSettings.textAlign == TextAlign.left || - scheduleViewSettings.weekHeaderSettings.textAlign == TextAlign.end) { - xPosition = 0; - } else if (scheduleViewSettings.weekHeaderSettings.textAlign == - TextAlign.center) { - xPosition = size.width / 2 - _textPainter.width / 2; - } - } - - /// Draw week label text - _textPainter.paint( - canvas, - Offset( - xPosition, - yPosition + - (scheduleViewSettings.weekHeaderSettings.height / 2 - - _textPainter.height / 2))); - } - - void _addMonthLabel(Canvas canvas, Size size) { - double xPosition = 0; - const double yPosition = 0; - final String monthFormat = - scheduleViewSettings.monthHeaderSettings.monthFormat; - final TextSpan span = TextSpan( - text: DateFormat(monthFormat, locale).format(startDate).toString(), - style: scheduleViewSettings.monthHeaderSettings.monthTextStyle ?? - TextStyle(color: Colors.white, fontSize: 20, fontFamily: 'Roboto'), - ); - _backgroundPainter ??= Paint(); - _backgroundPainter.shader = null; - _backgroundPainter.color = - scheduleViewSettings.monthHeaderSettings.backgroundColor; - final Rect rect = Rect.fromLTWH(0, yPosition, size.width, - scheduleViewSettings.monthHeaderSettings.height); - - /// Draw month label background. - canvas.drawRect(rect, _backgroundPainter); - _updateTextPainter(span); - - _textPainter.layout( - minWidth: 0, maxWidth: size.width - 10 > 0 ? size.width - 10 : 0); - - final double viewPadding = size.width * 0.15; - xPosition = viewPadding; - if (scheduleViewSettings.monthHeaderSettings.textAlign == TextAlign.right || - scheduleViewSettings.monthHeaderSettings.textAlign == TextAlign.end) { - xPosition = size.width - _textPainter.width; - } else if (scheduleViewSettings.monthHeaderSettings.textAlign == - TextAlign.center) { - xPosition = size.width / 2 - _textPainter.width / 2; - } - - if (isRTL) { - xPosition = size.width - _textPainter.width - xPosition; - if (scheduleViewSettings.monthHeaderSettings.textAlign == - TextAlign.left || - scheduleViewSettings.monthHeaderSettings.textAlign == TextAlign.end) { - xPosition = 0; - } else if (scheduleViewSettings.monthHeaderSettings.textAlign == - TextAlign.center) { - xPosition = size.width / 2 - _textPainter.width / 2; - } - } - - /// Draw month label text. - _textPainter.paint(canvas, Offset(xPosition, _textPainter.height)); - } - - void _updateTextPainter(TextSpan span) { - _textPainter ??= TextPainter(); - _textPainter.text = span; - _textPainter.maxLines = 1; - _textPainter.textDirection = TextDirection.ltr; - _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - return true; - } - - List _getSemanticsBuilder(Size size) { - final List semanticsBuilder = - []; - double cellHeight; - const double top = 0; - const double left = 0; - cellHeight = 0; - String accessibilityText; - if (!isMonthLabel) { - if (!isDisplayDate) { - cellHeight = scheduleViewSettings.weekHeaderSettings.height; - accessibilityText = - DateFormat('dd', locale).format(startDate).toString() + - 'to' + - DateFormat('dd MMM', locale) - .format(endDate.add(const Duration(days: 6))) - .toString(); - } else { - cellHeight = size.height; - accessibilityText = _localizations.noEventsCalendarLabel; - } - } else { - cellHeight = scheduleViewSettings.monthHeaderSettings.height; - accessibilityText = - DateFormat('MMMM yyyy', locale).format(startDate).toString(); - } - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, size.width, cellHeight), - properties: SemanticsProperties( - label: accessibilityText, - textDirection: TextDirection.ltr, - ), - )); - - return semanticsBuilder; - } - - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilder(size); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - return true; - } -} - -/// Used to implement the sticky header in schedule calendar view -/// based on its header and content widget. -class _ScheduleAppointmentView extends Stack { - _ScheduleAppointmentView({ - Widget content, - Widget header, - AlignmentDirectional alignment, - Key key, - }) : super( - key: key, - children: [ - RepaintBoundary(child: content), - RepaintBoundary(child: header) - ], - alignment: alignment ?? AlignmentDirectional.topStart, - ); - - @override - RenderStack createRenderObject(BuildContext context) => - _AppointmentViewHeaderRenderObject( - scrollableState: Scrollable.of(context), - alignment: alignment, - textDirection: textDirection ?? Directionality.of(context), - fit: fit, - ); - - @override - @mustCallSuper - void updateRenderObject(BuildContext context, RenderStack renderObject) { - super.updateRenderObject(context, renderObject); - - if (renderObject is _AppointmentViewHeaderRenderObject) { - renderObject..scrollableState = Scrollable.of(context); - } - } -} - -/// Render object of the schedule calendar view item. -class _AppointmentViewHeaderRenderObject extends RenderStack { - _AppointmentViewHeaderRenderObject({ - ScrollableState scrollableState, - AlignmentGeometry alignment, - TextDirection textDirection, - StackFit fit, - }) : _scrollableState = scrollableState, - super( - alignment: alignment, - textDirection: textDirection, - fit: fit, - ); - - /// Used to update the child position when it scroll changed. - ScrollableState _scrollableState; - - /// Current view port. - RenderAbstractViewport get _stackViewPort => RenderAbstractViewport.of(this); - - ScrollableState get scrollableState => _scrollableState; - - set scrollableState(ScrollableState newScrollable) { - final ScrollableState oldScrollable = _scrollableState; - _scrollableState = newScrollable; - - markNeedsPaint(); - if (attached) { - oldScrollable.position.removeListener(markNeedsPaint); - newScrollable.position.addListener(markNeedsPaint); - } - } - - /// attach will called when the render object rendered in view. - @override - void attach(PipelineOwner owner) { - super.attach(owner); - scrollableState.position.addListener(markNeedsPaint); - } - - /// attach will called when the render object removed from view. - @override - void detach() { - scrollableState.position.removeListener(markNeedsPaint); - super.detach(); - } - - @override - void paint(PaintingContext context, Offset paintOffset) { - /// Update the child position. - updateHeaderOffset(); - paintStack(context, paintOffset); - } - - void updateHeaderOffset() { - /// Content widget height - final double contentSize = firstChild.size.height; - final RenderBox headerView = lastChild; - - /// Header view height - final double headerSize = headerView.size.height; - - /// Current view position on scroll view. - final double viewPosition = - _stackViewPort.getOffsetToReveal(this, 0).offset; - - /// Calculate the current view offset by view position on scroll view, - /// scrolled position and scroll view view port. - final double currentViewOffset = - viewPosition - _scrollableState.position.pixels - _scrollableHeight; - - /// Check current header offset exits content size, if exist then place the - /// header at content size. - final double offset = _getCurrentOffset(currentViewOffset, contentSize); - final StackParentData headerParentData = headerView.parentData; - final double headerYOffset = - _getHeaderOffset(contentSize, offset, headerSize); - - /// Update the header start y position. - if (headerYOffset != headerParentData.offset.dy) { - headerParentData.offset = - Offset(headerParentData.offset.dx, headerYOffset); - } - } - - /// Return the view port height. - double get _scrollableHeight { - final Object viewPort = _stackViewPort; - double viewPortHeight; - - if (viewPort is RenderBox) { - viewPortHeight = viewPort.size.height; - } - - double anchor = 0; - if (viewPort is RenderViewport) { - anchor = viewPort.anchor; - } - - return -viewPortHeight * anchor; - } - - /// Check current header offset exits content size, if exist then place the - /// header at content size. - double _getCurrentOffset(double currentOffset, double contentSize) { - final double currentHeaderPosition = - -currentOffset > contentSize ? contentSize : -currentOffset; - return currentHeaderPosition > 0 ? currentHeaderPosition : 0; - } - - /// Return current offset value from header size and content size. - double _getHeaderOffset( - double contentSize, - double offset, - double headerSize, - ) { - return headerSize + offset < contentSize - ? offset - : contentSize - headerSize; - } -} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/selection_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/selection_view.dart deleted file mode 100644 index 36086d313..000000000 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/selection_view.dart +++ /dev/null @@ -1,367 +0,0 @@ -part of calendar; - -class _SelectionPainter extends CustomPainter { - _SelectionPainter( - this.calendar, - this.view, - this.visibleDates, - this.selectedDate, - this.selectionDecoration, - this.timeIntervalHeight, - this.calendarTheme, - this.repaintNotifier, - this.isRTL, - this.selectedResourceIndex, - this.resourceItemHeight, - {this.updateCalendarState}) - : super(repaint: repaintNotifier); - - final SfCalendar calendar; - final CalendarView view; - final SfCalendarThemeData calendarTheme; - final List visibleDates; - Decoration selectionDecoration; - DateTime selectedDate; - final double timeIntervalHeight; - final bool isRTL; - final _UpdateCalendarState updateCalendarState; - int selectedResourceIndex; - final double resourceItemHeight; - - BoxPainter _boxPainter; - _AppointmentView _appointmentView; - int _rowIndex, _columnIndex; - double _cellWidth, _cellHeight, _xPosition, _yPosition; - final ValueNotifier repaintNotifier; - final _UpdateCalendarStateDetails _updateCalendarStateDetails = - _UpdateCalendarStateDetails(); - - @override - void paint(Canvas canvas, Size size) { - selectionDecoration ??= BoxDecoration( - color: Colors.transparent, - border: Border.all(color: calendarTheme.selectionBorderColor, width: 2), - borderRadius: const BorderRadius.all(Radius.circular(2)), - shape: BoxShape.rectangle, - ); - - _updateCalendarStateDetails._currentViewVisibleDates = null; - _updateCalendarStateDetails._selectedDate = null; - updateCalendarState(_updateCalendarStateDetails); - selectedDate = _updateCalendarStateDetails._selectedDate; - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - final double timeLabelWidth = - _getTimeLabelWidth(calendar.timeSlotViewSettings.timeRulerSize, view); - double width = size.width; - final bool isTimeline = _isTimelineView(view); - if (view != CalendarView.month && !isTimeline) { - width -= timeLabelWidth; - } - - final bool isResourceEnabled = - isTimeline && _isResourceEnabled(calendar.dataSource, view); - if ((selectedDate == null && _appointmentView == null) || - visibleDates != _updateCalendarStateDetails._currentViewVisibleDates || - (isResourceEnabled && selectedResourceIndex == -1)) { - return; - } - - if (!isTimeline) { - if (view == CalendarView.month) { - _cellWidth = width / _kNumberOfDaysInWeek; - _cellHeight = - size.height / calendar.monthViewSettings.numberOfWeeksInView; - } else { - _cellWidth = width / visibleDates.length; - _cellHeight = timeIntervalHeight; - } - } else { - _cellWidth = timeIntervalHeight; - _cellHeight = size.height; - - /// The selection view must render on the resource area alone, when the - /// resource enabled. - if (isResourceEnabled && selectedResourceIndex >= 0) { - _cellHeight = resourceItemHeight; - } - } - - if (_appointmentView != null && _appointmentView.appointment != null) { - _drawAppointmentSelection(canvas); - } - - switch (view) { - case CalendarView.schedule: - return; - case CalendarView.month: - { - if (selectedDate != null) { - _drawMonthSelection(canvas, size, width); - } - } - break; - case CalendarView.day: - { - if (selectedDate != null) { - _drawDaySelection(canvas, size, width, timeLabelWidth); - } - } - break; - case CalendarView.week: - case CalendarView.workWeek: - { - if (selectedDate != null) { - _drawWeekSelection(canvas, size, timeLabelWidth, width); - } - } - break; - case CalendarView.timelineDay: - { - if (selectedDate != null) { - _drawTimelineDaySelection(canvas, size, width); - } - } - break; - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - { - if (selectedDate != null) { - _drawTimelineWeekSelection(canvas, size, width); - } - } - break; - case CalendarView.timelineMonth: - { - if (selectedDate != null) { - _drawTimelineMonthSelection(canvas, size, width); - } - } - } - } - - void _drawMonthSelection(Canvas canvas, Size size, double width) { - if (!isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { - return; - } - - final int currentMonth = visibleDates[visibleDates.length ~/ 2].month; - - /// Check the selected cell date as trailing or leading date when - /// [SfCalendar] month not shown leading and trailing dates. - if (!_isCurrentMonthDate( - calendar.monthViewSettings.numberOfWeeksInView, - calendar.monthViewSettings.showTrailingAndLeadingDates, - currentMonth, - selectedDate)) { - return; - } - - if (_isDateInDateCollection(calendar.blackoutDates, selectedDate)) { - return; - } - - for (int i = 0; i < visibleDates.length; i++) { - if (isSameDate(visibleDates[i], selectedDate)) { - final int index = i; - _columnIndex = (index / _kNumberOfDaysInWeek).truncate(); - _yPosition = _columnIndex * _cellHeight; - _rowIndex = index % _kNumberOfDaysInWeek; - if (isRTL) { - _xPosition = (_kNumberOfDaysInWeek - 1 - _rowIndex) * _cellWidth; - } else { - _xPosition = _rowIndex * _cellWidth; - } - _drawSlotSelection(width, size.height, canvas); - break; - } - } - } - - void _drawDaySelection( - Canvas canvas, Size size, double width, double timeLabelWidth) { - if (isSameDate(visibleDates[0], selectedDate)) { - if (isRTL) { - _xPosition = 0; - } else { - _xPosition = timeLabelWidth; - } - - selectedDate = _updateSelectedDate(); - - _yPosition = _timeToPosition(calendar, selectedDate, timeIntervalHeight); - _drawSlotSelection(width + timeLabelWidth, size.height, canvas); - } - } - - /// Method to update the selected date, when the selected date not fill the - /// exact time slot, and render the mid of time slot, on this scenario we - /// have updated the selected date to update the exact time slot. - /// - /// Eg: If the time interval is 60min, and the selected date is 12.45 PM the - /// selection renders on the center of 12 to 1 PM slot, to avoid this we have - /// modified the selected date to 1 PM so that the selection will render the - /// exact time slot. - DateTime _updateSelectedDate() { - final int timeInterval = _getTimeInterval(calendar.timeSlotViewSettings); - final int startHour = calendar.timeSlotViewSettings.startHour.toInt(); - final double startMinute = (calendar.timeSlotViewSettings.startHour - - calendar.timeSlotViewSettings.startHour.toInt()) * - 60; - final int selectedMinutes = ((selectedDate.hour - startHour) * 60) + - (selectedDate.minute - startMinute.toInt()); - if (selectedMinutes % timeInterval != 0) { - final int diff = selectedMinutes % timeInterval; - if (diff < (timeInterval / 2)) { - return selectedDate.subtract(Duration(minutes: diff)); - } else { - return selectedDate.add(Duration(minutes: timeInterval - diff)); - } - } - - return selectedDate; - } - - void _drawWeekSelection( - Canvas canvas, Size size, double timeLabelWidth, double width) { - if (isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { - for (int i = 0; i < visibleDates.length; i++) { - if (isSameDate(selectedDate, visibleDates[i])) { - _rowIndex = i; - if (isRTL) { - _xPosition = _cellWidth * (visibleDates.length - 1 - _rowIndex); - } else { - _xPosition = timeLabelWidth + _cellWidth * _rowIndex; - } - - selectedDate = _updateSelectedDate(); - - _yPosition = - _timeToPosition(calendar, selectedDate, timeIntervalHeight); - _drawSlotSelection(width + timeLabelWidth, size.height, canvas); - break; - } - } - } - } - - /// Returns the yPosition for selection view based on resource associated with - /// the selected cell in timeline views when resource enabled. - double _getTimelineYPosition() { - if (selectedResourceIndex == -1) { - return 0; - } - - return selectedResourceIndex * resourceItemHeight; - } - - void _drawTimelineDaySelection(Canvas canvas, Size size, double width) { - if (isSameDate(visibleDates[0], selectedDate)) { - selectedDate = _updateSelectedDate(); - _xPosition = _timeToPosition(calendar, selectedDate, timeIntervalHeight); - _yPosition = _getTimelineYPosition(); - final double height = selectedResourceIndex == -1 - ? size.height - : _yPosition + resourceItemHeight; - if (isRTL) { - _xPosition = size.width - _xPosition - _cellWidth; - } - _drawSlotSelection(width, height, canvas); - } - } - - void _drawTimelineMonthSelection(Canvas canvas, Size size, double width) { - if (!isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { - return; - } - - if (_isDateInDateCollection(calendar.blackoutDates, selectedDate)) { - return; - } - - for (int i = 0; i < visibleDates.length; i++) { - if (isSameDate(visibleDates[i], selectedDate)) { - _yPosition = _getTimelineYPosition(); - _xPosition = - isRTL ? size.width - ((i + 1) * _cellWidth) : i * _cellWidth; - final double height = selectedResourceIndex == -1 - ? size.height - : _yPosition + resourceItemHeight; - _drawSlotSelection(width, height, canvas); - break; - } - } - } - - void _drawTimelineWeekSelection(Canvas canvas, Size size, double width) { - if (isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { - selectedDate = _updateSelectedDate(); - for (int i = 0; i < visibleDates.length; i++) { - if (isSameDate(selectedDate, visibleDates[i])) { - final double singleViewWidth = width / visibleDates.length; - _rowIndex = i; - _xPosition = (_rowIndex * singleViewWidth) + - _timeToPosition(calendar, selectedDate, timeIntervalHeight); - if (isRTL) { - _xPosition = size.width - _xPosition - _cellWidth; - } - _yPosition = _getTimelineYPosition(); - final double height = selectedResourceIndex == -1 - ? size.height - : _yPosition + resourceItemHeight; - _drawSlotSelection(width, height, canvas); - break; - } - } - } - } - - void _drawAppointmentSelection(Canvas canvas) { - Rect rect = _appointmentView.appointmentRect.outerRect; - rect = Rect.fromLTRB(rect.left, rect.top, rect.right, rect.bottom); - _boxPainter = - selectionDecoration.createBoxPainter(_updateSelectionDecorationPainter); - _boxPainter.paint(canvas, Offset(rect.left, rect.top), - ImageConfiguration(size: rect.size)); - } - - /// Used to pass the argument of create box painter and it is called when - /// decoration have asynchronous data like image. - void _updateSelectionDecorationPainter() { - repaintNotifier.value = !repaintNotifier.value; - } - - void _drawSlotSelection(double width, double height, Canvas canvas) { - //// padding used to avoid first, last row and column selection clipping. - const double padding = 0.5; - Rect rect; - rect = Rect.fromLTRB( - _xPosition == 0 ? _xPosition + padding : _xPosition, - _yPosition == 0 ? _yPosition + padding : _yPosition, - _xPosition + _cellWidth == width - ? _xPosition + _cellWidth - padding - : _xPosition + _cellWidth, - _yPosition + _cellHeight == height - ? _yPosition + _cellHeight - padding - : _yPosition + _cellHeight); - - _boxPainter = - selectionDecoration.createBoxPainter(_updateSelectionDecorationPainter); - _boxPainter.paint(canvas, Offset(rect.left, rect.top), - ImageConfiguration(size: rect.size, textDirection: TextDirection.ltr)); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _SelectionPainter oldWidget = oldDelegate; - return oldWidget.selectionDecoration != selectionDecoration || - oldWidget.selectedDate != selectedDate || - oldWidget.view != view || - oldWidget.visibleDates != visibleDates || - oldWidget.selectedResourceIndex != selectedResourceIndex || - oldWidget.isRTL != isRTL; - } -} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/time_ruler_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/time_ruler_view.dart deleted file mode 100644 index 2d2c4686c..000000000 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/time_ruler_view.dart +++ /dev/null @@ -1,169 +0,0 @@ -part of calendar; - -class _TimeRulerView extends CustomPainter { - _TimeRulerView( - this.horizontalLinesCount, - this.timeIntervalHeight, - this.timeSlotViewSettings, - this.cellBorderColor, - this.isRTL, - this.locale, - this.calendarTheme, - this.isTimelineView, - this.visibleDates, - this.textScaleFactor); - - final double horizontalLinesCount; - final double timeIntervalHeight; - final TimeSlotViewSettings timeSlotViewSettings; - final bool isRTL; - final String locale; - final SfCalendarThemeData calendarTheme; - final Color cellBorderColor; - final bool isTimelineView; - final List visibleDates; - final double textScaleFactor; - Paint _linePainter; - TextPainter _textPainter; - - @override - void paint(Canvas canvas, Size size) { - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - const double offset = 0.5; - double xPosition, yPosition; - final DateTime date = DateTime.now(); - xPosition = isRTL && isTimelineView ? size.width : 0; - yPosition = timeIntervalHeight; - _linePainter = _linePainter ?? Paint(); - _linePainter.strokeWidth = offset; - _linePainter.color = cellBorderColor ?? calendarTheme.cellBorderColor; - - if (!isTimelineView) { - final double lineXPosition = isRTL ? offset : size.width - offset; - // Draw vertical time label line - canvas.drawLine(Offset(lineXPosition, 0), - Offset(lineXPosition, size.height), _linePainter); - } - - _textPainter = _textPainter ?? TextPainter(); - _textPainter.textDirection = TextDirection.ltr; - _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; - - final TextStyle timeTextStyle = - timeSlotViewSettings.timeTextStyle ?? calendarTheme.timeTextStyle; - - final double hour = (timeSlotViewSettings.startHour - - timeSlotViewSettings.startHour.toInt()) * - 60; - if (isTimelineView) { - canvas.drawLine(Offset(0, 0), Offset(size.width, 0), _linePainter); - final double timelineViewWidth = - timeIntervalHeight * horizontalLinesCount; - for (int i = 0; i < visibleDates.length; i++) { - _drawTimeLabels( - canvas, size, date, hour, xPosition, yPosition, timeTextStyle); - if (isRTL) { - xPosition -= timelineViewWidth; - } else { - xPosition += timelineViewWidth; - } - } - } else { - _drawTimeLabels( - canvas, size, date, hour, xPosition, yPosition, timeTextStyle); - } - } - - /// Draws the time labeels in the time label view for timeslot views in - /// calendar. - void _drawTimeLabels(Canvas canvas, Size size, DateTime date, double hour, - double xPosition, double yPosition, TextStyle timeTextStyle) { - final int padding = 5; - - /// For timeline view we will draw 24 lines where as in day, week and work - /// week view we will draw 23 lines excluding the 12 AM, hence to rectify - /// this the i value handled accordingly. - for (int i = isTimelineView ? 0 : 1; - i <= (isTimelineView ? horizontalLinesCount - 1 : horizontalLinesCount); - i++) { - if (isTimelineView) { - canvas.save(); - canvas.clipRect( - Rect.fromLTWH(xPosition, 0, timeIntervalHeight, size.height)); - canvas.restore(); - canvas.drawLine( - Offset(xPosition, 0), Offset(xPosition, size.height), _linePainter); - } - - final double minute = (i * _getTimeInterval(timeSlotViewSettings)) + hour; - date = DateTime(date.day, date.month, date.year, - timeSlotViewSettings.startHour.toInt(), minute.toInt()); - final String time = DateFormat(timeSlotViewSettings.timeFormat, locale) - .format(date) - .toString(); - final TextSpan span = TextSpan( - text: time, - style: timeTextStyle, - ); - - final double cellWidth = isTimelineView ? timeIntervalHeight : size.width; - - _textPainter.text = span; - _textPainter.layout(minWidth: 0, maxWidth: cellWidth); - if (isTimelineView && _textPainter.height > size.height) { - return; - } - - double startXPosition = (cellWidth - _textPainter.width) / 2; - if (startXPosition < 0) { - startXPosition = 0; - } - - if (isTimelineView) { - startXPosition = isRTL ? xPosition - _textPainter.width : xPosition; - } - - double startYPosition = yPosition - (_textPainter.height / 2); - - if (isTimelineView) { - startYPosition = (size.height - _textPainter.height) / 2; - startXPosition = - isRTL ? startXPosition - padding : startXPosition + padding; - } - - _textPainter.paint(canvas, Offset(startXPosition, startYPosition)); - - if (!isTimelineView) { - final Offset start = - Offset(isRTL ? 0 : size.width - (startXPosition / 2), yPosition); - final Offset end = - Offset(isRTL ? startXPosition / 2 : size.width, yPosition); - canvas.drawLine(start, end, _linePainter); - yPosition += timeIntervalHeight; - if (yPosition.round() == size.height.round()) { - break; - } - } else { - if (isRTL) { - xPosition -= timeIntervalHeight; - } else { - xPosition += timeIntervalHeight; - } - } - } - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _TimeRulerView oldWidget = oldDelegate; - return oldWidget.timeSlotViewSettings != timeSlotViewSettings || - oldWidget.cellBorderColor != cellBorderColor || - oldWidget.calendarTheme != calendarTheme || - oldWidget.isRTL != isRTL || - oldWidget.locale != locale || - oldWidget.visibleDates != visibleDates || - oldWidget.isTimelineView != isTimelineView || - oldWidget.textScaleFactor != textScaleFactor; - } -} diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/timeline_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/timeline_view.dart index b16b64da3..81deeb05b 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/timeline_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/timeline_view.dart @@ -1,11 +1,28 @@ -part of calendar; - -class _TimelineWidget extends StatefulWidget { - _TimelineWidget( +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../appointment_engine/appointment_helper.dart'; +import '../common/calendar_view_helper.dart'; +import '../common/date_time_engine.dart'; +import '../common/event_args.dart'; +import '../resource_view/calendar_resource.dart'; +import '../settings/time_slot_view_settings.dart'; +import '../settings/view_header_style.dart'; + +/// Used to hold the time slots view on calendar timeline views. +class TimelineWidget extends StatefulWidget { + /// Constructor to create the timeline widget to holds time slots view for + /// timeline views. + TimelineWidget( this.horizontalLinesCountPerView, this.visibleDates, this.timeSlotViewSettings, - this.timeIntervalHeight, + this.timeIntervalWidth, this.cellBorderColor, this.isRTL, this.calendarTheme, @@ -18,45 +35,90 @@ class _TimelineWidget extends StatefulWidget { this.isMobilePlatform, this.timeRegionBuilder, this.width, - this.height); + this.height, + this.minDate, + this.maxDate, + this.blackoutDates); + /// Defines the total number of time slots needed in the view. final double horizontalLinesCountPerView; + + /// Holds the visible dates collection for current timeline view. final List visibleDates; + + /// Defines the timeline view slot setting used to customize the time slots. final TimeSlotViewSettings timeSlotViewSettings; - final double timeIntervalHeight; - final Color cellBorderColor; + + /// Defines the width of time slot view. + final double timeIntervalWidth; + + /// Defines the time slot border color. + final Color? cellBorderColor; + + /// Holds the theme data value for calendar. final SfCalendarThemeData calendarTheme; + + /// Defines the direction of the calendar widget is RTL or not. final bool isRTL; - final ValueNotifier calendarCellNotifier; + + /// Used to draw the hovering on timeline view. + final ValueNotifier calendarCellNotifier; + + /// Used to get the current scroll position of the timeline view. final ScrollController scrollController; - final List specialRegion; + + /// Defines the special time region for the current timeline view. + final List? specialRegion; + + /// Defines the resource view item height. final double resourceItemHeight; - final List resourceCollection; + + /// Holds the resource collection used to draw the time slots based on + /// resource value. + final List? resourceCollection; + + /// Defines the scale factor for the time slot time text. final double textScaleFactor; + + /// Defines the current platform is mobile platform or not. final bool isMobilePlatform; + + /// Holds the current timeline widget width. final double width; + + /// Holds the current timeline widget height. final double height; - final TimeRegionBuilder timeRegionBuilder; + + /// Used to build the widget that replaces the time regions in timeline view. + final TimeRegionBuilder? timeRegionBuilder; + + /// Defines the min date of the calendar. + final DateTime minDate; + + /// Defines the max date of the calendar. + final DateTime maxDate; + + /// Holds the blackout dates collection of calendar. + final List? blackoutDates; @override _TimelineWidgetState createState() => _TimelineWidgetState(); } -class _TimelineWidgetState extends State<_TimelineWidget> { - List _children; - List<_TimeRegionView> _specialRegionViews; +class _TimelineWidgetState extends State { + List _children = []; + List _specialRegionViews = []; @override void initState() { - _children = []; _updateSpecialRegionDetails(); super.initState(); } @override - void didUpdateWidget(_TimelineWidget oldWidget) { + void didUpdateWidget(TimelineWidget oldWidget) { if (widget.visibleDates != oldWidget.visibleDates || - widget.timeIntervalHeight != oldWidget.timeIntervalHeight || + widget.timeIntervalWidth != oldWidget.timeIntervalWidth || widget.timeSlotViewSettings != oldWidget.timeSlotViewSettings || widget.isRTL != oldWidget.isRTL || widget.resourceItemHeight != oldWidget.resourceItemHeight || @@ -64,7 +126,8 @@ class _TimelineWidgetState extends State<_TimelineWidget> { widget.width != oldWidget.width || widget.height != oldWidget.height || widget.timeRegionBuilder != oldWidget.timeRegionBuilder || - !_isCollectionEqual(widget.specialRegion, oldWidget.specialRegion)) { + !CalendarViewHelper.isCollectionEqual( + widget.specialRegion, oldWidget.specialRegion)) { _updateSpecialRegionDetails(); _children.clear(); } @@ -74,23 +137,17 @@ class _TimelineWidgetState extends State<_TimelineWidget> { @override Widget build(BuildContext context) { - _children ??= []; if (_children.isEmpty && widget.timeRegionBuilder != null && - _specialRegionViews != null && _specialRegionViews.isNotEmpty) { final int count = _specialRegionViews.length; for (int i = 0; i < count; i++) { - final _TimeRegionView view = _specialRegionViews[i]; - final Widget child = widget.timeRegionBuilder( + final TimeRegionView view = _specialRegionViews[i]; + final Widget child = widget.timeRegionBuilder!( context, - TimeRegionDetails( - region: view.region, - date: widget.visibleDates[view.visibleIndex], - bounds: view.bound)); + TimeRegionDetails(view.region.data, + widget.visibleDates[view.visibleIndex], view.bound)); - /// Throw exception when builder return widget is null. - assert(child != null, 'Widget must not be null'); _children.add(RepaintBoundary(child: child)); } } @@ -99,7 +156,7 @@ class _TimelineWidgetState extends State<_TimelineWidget> { widget.horizontalLinesCountPerView, widget.visibleDates, widget.timeSlotViewSettings, - widget.timeIntervalHeight, + widget.timeIntervalWidth, widget.cellBorderColor, widget.isRTL, widget.calendarTheme, @@ -113,33 +170,37 @@ class _TimelineWidgetState extends State<_TimelineWidget> { widget.width, widget.height, _specialRegionViews, + widget.minDate, + widget.maxDate, + widget.blackoutDates, widgets: _children, ); } void _updateSpecialRegionDetails() { - _specialRegionViews = <_TimeRegionView>[]; - if (widget.visibleDates.length > _kNumberOfDaysInWeek || + _specialRegionViews = []; + if (widget.visibleDates.length > DateTime.daysPerWeek || widget.specialRegion == null || - widget.specialRegion.isEmpty) { + widget.specialRegion!.isEmpty) { return; } - final double minuteHeight = widget.timeIntervalHeight / - _getTimeInterval(widget.timeSlotViewSettings); - final DateTime startDate = _convertToStartTime(widget.visibleDates[0]); - final DateTime endDate = - _convertToEndTime(widget.visibleDates[widget.visibleDates.length - 1]); + final double minuteHeight = widget.timeIntervalWidth / + CalendarViewHelper.getTimeInterval(widget.timeSlotViewSettings); + final DateTime startDate = + AppointmentHelper.convertToStartTime(widget.visibleDates[0]); + final DateTime endDate = AppointmentHelper.convertToEndTime( + widget.visibleDates[widget.visibleDates.length - 1]); final double viewWidth = widget.width / widget.visibleDates.length; final bool isResourceEnabled = widget.resourceCollection != null && - widget.resourceCollection.isNotEmpty; - for (int i = 0; i < widget.specialRegion.length; i++) { - final TimeRegion region = widget.specialRegion[i]; - final DateTime regionStartTime = region._actualStartTime; - final DateTime regionEndTime = region._actualEndTime; + widget.resourceCollection!.isNotEmpty; + for (int i = 0; i < widget.specialRegion!.length; i++) { + final CalendarTimeRegion region = widget.specialRegion![i]; + final DateTime regionStartTime = region.actualStartTime; + final DateTime regionEndTime = region.actualEndTime; /// Check the start date and end date as same. - if (_isSameTimeSlot(regionStartTime, regionEndTime)) { + if (CalendarViewHelper.isSameTimeSlot(regionStartTime, regionEndTime)) { continue; } @@ -153,11 +214,12 @@ class _TimelineWidgetState extends State<_TimelineWidget> { continue; } - int startIndex = - _getVisibleDateIndex(widget.visibleDates, regionStartTime); - int endIndex = _getVisibleDateIndex(widget.visibleDates, regionEndTime); + int startIndex = DateTimeHelper.getVisibleDateIndex( + widget.visibleDates, regionStartTime); + int endIndex = DateTimeHelper.getVisibleDateIndex( + widget.visibleDates, regionEndTime); - double startXPosition = _getTimeToPosition( + double startXPosition = CalendarViewHelper.getTimeToPosition( Duration( hours: regionStartTime.hour, minutes: regionStartTime.minute), widget.timeSlotViewSettings, @@ -189,7 +251,7 @@ class _TimelineWidgetState extends State<_TimelineWidget> { startXPosition = 0; } - double endXPosition = _getTimeToPosition( + double endXPosition = CalendarViewHelper.getTimeToPosition( Duration(hours: regionEndTime.hour, minutes: regionEndTime.minute), widget.timeSlotViewSettings, minuteHeight); @@ -240,10 +302,10 @@ class _TimelineWidgetState extends State<_TimelineWidget> { double bottomPosition = widget.height; if (isResourceEnabled && region.resourceIds != null && - region.resourceIds.isNotEmpty) { - for (int i = 0; i < region.resourceIds.length; i++) { - final int index = _getResourceIndex( - widget.resourceCollection, region.resourceIds[i]); + region.resourceIds!.isNotEmpty) { + for (int i = 0; i < region.resourceIds!.length; i++) { + final int index = CalendarViewHelper.getResourceIndex( + widget.resourceCollection, region.resourceIds![i]); topPosition = index * widget.resourceItemHeight; bottomPosition = topPosition + widget.resourceItemHeight; _updateSpecialRegionRect(region, startPosition, endPosition, @@ -257,7 +319,7 @@ class _TimelineWidgetState extends State<_TimelineWidget> { } void _updateSpecialRegionRect( - TimeRegion region, + CalendarTimeRegion region, double startPosition, double endPosition, double topPosition, @@ -272,8 +334,7 @@ class _TimelineWidgetState extends State<_TimelineWidget> { startPosition, topPosition, endPosition, bottomPosition); } - _specialRegionViews - .add(_TimeRegionView(region: region, visibleIndex: index, bound: rect)); + _specialRegionViews.add(TimeRegionView(index, region, rect)); } } @@ -282,7 +343,7 @@ class _TimelineRenderWidget extends MultiChildRenderObjectWidget { this.horizontalLinesCountPerView, this.visibleDates, this.timeSlotViewSettings, - this.timeIntervalHeight, + this.timeIntervalWidth, this.cellBorderColor, this.isRTL, this.calendarTheme, @@ -296,26 +357,32 @@ class _TimelineRenderWidget extends MultiChildRenderObjectWidget { this.width, this.height, this.specialRegionBounds, - {List widgets}) + this.minDate, + this.maxDate, + this.blackoutDates, + {List widgets = const []}) : super(children: widgets); final double horizontalLinesCountPerView; final List visibleDates; final TimeSlotViewSettings timeSlotViewSettings; - final double timeIntervalHeight; - final Color cellBorderColor; + final double timeIntervalWidth; + final Color? cellBorderColor; final SfCalendarThemeData calendarTheme; final bool isRTL; - final ValueNotifier calendarCellNotifier; + final ValueNotifier calendarCellNotifier; final ScrollController scrollController; - final List specialRegion; + final List? specialRegion; final double resourceItemHeight; - final List resourceCollection; + final List? resourceCollection; final double textScaleFactor; final bool isMobilePlatform; final double width; final double height; - final List<_TimeRegionView> specialRegionBounds; + final List specialRegionBounds; + final DateTime minDate; + final DateTime maxDate; + final List? blackoutDates; @override _TimelineRenderObject createRenderObject(BuildContext context) { @@ -323,7 +390,7 @@ class _TimelineRenderWidget extends MultiChildRenderObjectWidget { horizontalLinesCountPerView, visibleDates, timeSlotViewSettings, - timeIntervalHeight, + timeIntervalWidth, cellBorderColor, isRTL, calendarTheme, @@ -336,7 +403,10 @@ class _TimelineRenderWidget extends MultiChildRenderObjectWidget { isMobilePlatform, width, height, - specialRegionBounds); + specialRegionBounds, + minDate, + maxDate, + blackoutDates); } @override @@ -346,7 +416,7 @@ class _TimelineRenderWidget extends MultiChildRenderObjectWidget { ..horizontalLinesCountPerView = horizontalLinesCountPerView ..visibleDates = visibleDates ..timeSlotViewSettings = timeSlotViewSettings - ..timeIntervalHeight = timeIntervalHeight + ..timeIntervalWidth = timeIntervalWidth ..cellBorderColor = cellBorderColor ..isRTL = isRTL ..calendarTheme = calendarTheme @@ -359,16 +429,19 @@ class _TimelineRenderWidget extends MultiChildRenderObjectWidget { ..isMobilePlatform = isMobilePlatform ..width = width ..height = height + ..minDate = minDate + ..maxDate = maxDate + ..blackoutDates = blackoutDates ..specialRegionBounds = specialRegionBounds; } } -class _TimelineRenderObject extends _CustomCalendarRenderObject { +class _TimelineRenderObject extends CustomCalendarRenderObject { _TimelineRenderObject( this._horizontalLinesCountPerView, this._visibleDates, this._timeSlotViewSettings, - this._timeIntervalHeight, + this._timeIntervalWidth, this._cellBorderColor, this._isRTL, this._calendarTheme, @@ -381,7 +454,10 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { this.isMobilePlatform, this._width, this._height, - this.specialRegionBounds); + this.specialRegionBounds, + this._minDate, + this._maxDate, + this._blackoutDates); double _horizontalLinesCountPerView; @@ -430,16 +506,16 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { } } - double _timeIntervalHeight; + double _timeIntervalWidth; - double get timeIntervalHeight => _timeIntervalHeight; + double get timeIntervalWidth => _timeIntervalWidth; - set timeIntervalHeight(double value) { - if (_timeIntervalHeight == value) { + set timeIntervalWidth(double value) { + if (_timeIntervalWidth == value) { return; } - _timeIntervalHeight = value; + _timeIntervalWidth = value; if (childCount == 0) { markNeedsPaint(); } else { @@ -447,11 +523,11 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { } } - Color _cellBorderColor; + Color? _cellBorderColor; - Color get cellBorderColor => _cellBorderColor; + Color? get cellBorderColor => _cellBorderColor; - set cellBorderColor(Color value) { + set cellBorderColor(Color? value) { if (_cellBorderColor == value) { return; } @@ -494,7 +570,7 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { } } - List resourceCollection; + List? resourceCollection; bool _isRTL; @@ -509,18 +585,18 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { markNeedsPaint(); } - ValueNotifier _calendarCellNotifier; + ValueNotifier _calendarCellNotifier; - ValueNotifier get calendarCellNotifier => _calendarCellNotifier; + ValueNotifier get calendarCellNotifier => _calendarCellNotifier; - set calendarCellNotifier(ValueNotifier value) { + set calendarCellNotifier(ValueNotifier value) { if (_calendarCellNotifier == value) { return; } - _calendarCellNotifier?.removeListener(markNeedsPaint); + _calendarCellNotifier.removeListener(markNeedsPaint); _calendarCellNotifier = value; - _calendarCellNotifier?.addListener(markNeedsPaint); + _calendarCellNotifier.addListener(markNeedsPaint); } double _width; @@ -562,12 +638,12 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { markNeedsPaint(); } - List _specialRegion; + List? _specialRegion; - List get specialRegion => _specialRegion; + List? get specialRegion => _specialRegion; - set specialRegion(List value) { - if (_isCollectionEqual(_specialRegion, value)) { + set specialRegion(List? value) { + if (CalendarViewHelper.isCollectionEqual(_specialRegion, value)) { return; } @@ -579,10 +655,50 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { } } - List<_TimeRegionView> specialRegionBounds; - ScrollController scrollController; - bool isMobilePlatform; - Paint _linePainter; + List specialRegionBounds; + + DateTime _minDate; + + DateTime get minDate => _minDate; + + set minDate(DateTime value) { + if (CalendarViewHelper.isSameTimeSlot(_minDate, value)) { + return; + } + + _minDate = value; + markNeedsPaint(); + } + + DateTime _maxDate; + + DateTime get maxDate => _maxDate; + + set maxDate(DateTime value) { + if (CalendarViewHelper.isSameTimeSlot(_maxDate, value)) { + return; + } + + _maxDate = value; + markNeedsPaint(); + } + + List? _blackoutDates; + + List? get blackoutDates => _blackoutDates; + + set blackoutDates(List? value) { + if (CalendarViewHelper.isDateCollectionEqual(_blackoutDates, value)) { + return; + } + + _blackoutDates = value; + markNeedsPaint(); + } + + late ScrollController scrollController; + late bool isMobilePlatform; + Paint _linePainter = Paint(); @override bool get isRepaintBoundary => true; @@ -591,13 +707,13 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { @override void attach(PipelineOwner owner) { super.attach(owner); - _calendarCellNotifier?.addListener(markNeedsPaint); + _calendarCellNotifier.addListener(markNeedsPaint); } /// detach will called when the render object removed from view. @override void detach() { - _calendarCellNotifier?.removeListener(markNeedsPaint); + _calendarCellNotifier.removeListener(markNeedsPaint); super.detach(); } @@ -606,14 +722,14 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { final Size widgetSize = constraints.biggest; size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, widgetSize.height.isInfinite ? height : widgetSize.height); - RenderBox child = firstChild; - if (specialRegion == null || specialRegion.isEmpty) { + RenderBox? child = firstChild; + if (specialRegion == null || specialRegion!.isEmpty) { return; } final int count = specialRegionBounds.length; for (int i = 0; i < count; i++) { - final _TimeRegionView view = specialRegionBounds[i]; + final TimeRegionView view = specialRegionBounds[i]; if (child == null) { continue; } @@ -629,21 +745,21 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { @override void paint(PaintingContext context, Offset offset) { - RenderBox child = firstChild; + RenderBox? child = firstChild; final bool isNeedDefaultPaint = childCount == 0; - _linePainter = _linePainter ?? Paint(); final bool isResourceEnabled = - resourceCollection != null && resourceCollection.isNotEmpty; + resourceCollection != null && resourceCollection!.isNotEmpty; + _minMaxExceeds(minDate, maxDate, blackoutDates, context.canvas); if (isNeedDefaultPaint) { _addSpecialRegion(context.canvas, isResourceEnabled); } else { - if (specialRegion == null || specialRegion.isEmpty) { + if (specialRegion == null || specialRegion!.isEmpty) { return; } final int count = specialRegionBounds.length; for (int i = 0; i < count; i++) { - final _TimeRegionView view = specialRegionBounds[i]; + final TimeRegionView view = specialRegionBounds[i]; if (child == null) { continue; } @@ -656,18 +772,77 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { _drawTimeline(context.canvas, isResourceEnabled); } + void _minMaxExceeds( + DateTime minDate, DateTime maxDate, blackoutDates, Canvas canvas) { + final DateTime visibleStartDate = visibleDates[0]; + final DateTime visibleEndDate = visibleDates[visibleDates.length - 1]; + final bool isTimelineMonth = visibleDates.length > DateTime.daysPerWeek; + + if (isDateWithInDateRange(visibleStartDate, visibleEndDate, minDate)) { + _drawDisabledDate(minDate, false, false, canvas, isTimelineMonth); + } + if (isDateWithInDateRange(visibleStartDate, visibleEndDate, maxDate)) { + _drawDisabledDate(maxDate, true, false, canvas, isTimelineMonth); + } + if (blackoutDates != null && isTimelineMonth) { + final int count = blackoutDates.length; + for (int i = 0; i < count; i++) { + final DateTime blackoutDate = blackoutDates[i]; + if (isDateWithInDateRange( + visibleStartDate, visibleEndDate, blackoutDate)) { + _drawDisabledDate(blackoutDate, false, true, canvas, isTimelineMonth); + } + } + } + } + + void _drawDisabledDate(DateTime disabledDate, isMaxDate, isBlackOutDate, + canvas, bool isTimelineMonth) { + final double minuteHeight = timeIntervalWidth / + CalendarViewHelper.getTimeInterval(timeSlotViewSettings); + final double viewWidth = width / visibleDates.length; + final int dateIndex = + DateTimeHelper.getVisibleDateIndex(visibleDates, disabledDate); + double leftPosition = 0; + final double topPosition = 0; + + final double xPosition = isTimelineMonth + ? 0 + : CalendarViewHelper.getTimeToPosition( + Duration(hours: disabledDate.hour, minutes: disabledDate.minute), + timeSlotViewSettings, + minuteHeight); + double rightPosition = (dateIndex * viewWidth) + xPosition; + if (isMaxDate) { + leftPosition = + (dateIndex * viewWidth) + (isTimelineMonth ? viewWidth : xPosition); + rightPosition = size.width; + } + final double bottomPosition = topPosition + height; + if (isBlackOutDate) { + leftPosition = (dateIndex * timeIntervalWidth); + rightPosition = leftPosition + timeIntervalWidth; + } + Rect rect; + if (isRTL) { + leftPosition = width - leftPosition; + rightPosition = width - rightPosition; + } + rect = + Rect.fromLTRB(leftPosition, topPosition, rightPosition, bottomPosition); + _linePainter.style = PaintingStyle.fill; + _linePainter.color = Colors.grey.withOpacity(0.2); + canvas.drawRect(rect, _linePainter); + } + void _drawTimeline(Canvas canvas, bool isResourceEnabled) { - double startXPosition = 0; - double endXPosition = size.width; - double startYPosition = timeIntervalHeight; - double endYPosition = timeIntervalHeight; _linePainter.strokeWidth = 0.5; _linePainter.strokeCap = StrokeCap.round; _linePainter.color = cellBorderColor ?? calendarTheme.cellBorderColor; - startXPosition = 0; - endXPosition = size.width; - startYPosition = 0.5; - endYPosition = 0.5; + double startXPosition = 0; + double endXPosition = size.width; + double startYPosition = 0.5; + double endYPosition = 0.5; final Offset start = Offset(startXPosition, startYPosition); final Offset end = Offset(endXPosition, endYPosition); @@ -695,11 +870,11 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { } if (isRTL) { - startXPosition -= timeIntervalHeight; - endXPosition -= timeIntervalHeight; + startXPosition -= timeIntervalWidth; + endXPosition -= timeIntervalWidth; } else { - startXPosition += timeIntervalHeight; - endXPosition += timeIntervalHeight; + startXPosition += timeIntervalWidth; + endXPosition += timeIntervalWidth; } } @@ -712,7 +887,7 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { startXPosition = 0; endXPosition = size.width; startYPosition = resourceItemHeight; - for (int i = 0; i < resourceCollection.length; i++) { + for (int i = 0; i < resourceCollection!.length; i++) { canvas.drawLine(Offset(startXPosition, startYPosition), Offset(endXPosition, startYPosition), _linePainter); startYPosition += resourceItemHeight; @@ -725,13 +900,13 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { } void _addMouseHovering(Canvas canvas, Size size, bool isResourceEnabled) { - double left = (calendarCellNotifier.value.dx ~/ timeIntervalHeight) * - timeIntervalHeight; + double left = (calendarCellNotifier.value!.dx ~/ timeIntervalWidth) * + timeIntervalWidth; double top = 0; double height = size.height; if (isResourceEnabled) { final int index = - (calendarCellNotifier.value.dy / resourceItemHeight).truncate(); + (calendarCellNotifier.value!.dy / resourceItemHeight).truncate(); top = index * resourceItemHeight; height = resourceItemHeight; } @@ -742,7 +917,7 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { ? height - (padding * 2) : height - padding : height; - double width = timeIntervalHeight; + double width = timeIntervalWidth; double difference = 0; if (isRTL && (size.width - scrollController.offset) < @@ -752,13 +927,13 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { if ((size.width - scrollController.offset) < scrollController.position.viewportDimension && - (left + timeIntervalHeight).round() == size.width.round()) { + (left + timeIntervalWidth).round() == size.width.round()) { width -= padding; } _linePainter.style = PaintingStyle.stroke; _linePainter.strokeWidth = 2; - _linePainter.color = calendarTheme.selectionBorderColor.withOpacity(0.4); + _linePainter.color = calendarTheme.selectionBorderColor!.withOpacity(0.4); left = left == 0 ? left - difference + padding : left - difference; canvas.drawRect(Rect.fromLTWH(left, top, width, height), _linePainter); } @@ -769,9 +944,9 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { /// Condition added to check and add the special region for timeline day, /// timeline week and timeline work week view only, since the special region /// support not applicable for timeline month view. - if (visibleDates.length > _kNumberOfDaysInWeek || + if (visibleDates.length > DateTime.daysPerWeek || _specialRegion == null || - _specialRegion.isEmpty) { + _specialRegion!.isEmpty) { return; } @@ -785,20 +960,19 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { _linePainter.style = PaintingStyle.fill; final int count = specialRegionBounds.length; for (int i = 0; i < count; i++) { - final _TimeRegionView view = specialRegionBounds[i]; - final TimeRegion region = view.region; + final TimeRegionView view = specialRegionBounds[i]; + final CalendarTimeRegion region = view.region; _linePainter.color = region.color ?? Colors.grey.withOpacity(0.2); final TextStyle textStyle = region.textStyle ?? TextStyle( - color: calendarTheme.brightness != null && - calendarTheme.brightness == Brightness.dark + color: calendarTheme.brightness == Brightness.dark ? Colors.white54 : Colors.black45); final Rect rect = view.bound; canvas.drawRect(rect, _linePainter); - if ((region.text == null || region.text.isEmpty) && + if ((region.text == null || region.text!.isEmpty) && region.iconData == null) { - return; + continue; } if (region.iconData == null) { @@ -806,8 +980,8 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { painter.ellipsis = '..'; } else { painter.text = TextSpan( - text: String.fromCharCode(region.iconData.codePoint), - style: textStyle.copyWith(fontFamily: region.iconData.fontFamily)); + text: String.fromCharCode(region.iconData!.codePoint), + style: textStyle.copyWith(fontFamily: region.iconData!.fontFamily)); } painter.layout(minWidth: 0, maxWidth: rect.width - 4); @@ -822,13 +996,13 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { List _getSemanticsBuilder(Size size) { List semanticsBuilder = []; final bool isResourceEnabled = - resourceCollection != null && resourceCollection.isNotEmpty; + resourceCollection != null && resourceCollection!.isNotEmpty; final double height = isResourceEnabled ? resourceItemHeight : size.height; double top = 0; if (isResourceEnabled) { - for (int i = 0; i < resourceCollection.length; i++) { + for (int i = 0; i < resourceCollection!.length; i++) { semanticsBuilder = _getAccessibilityDates( - size, top, height, semanticsBuilder, resourceCollection[i]); + size, top, height, semanticsBuilder, resourceCollection![i]); top += height; } } else { @@ -842,8 +1016,8 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { /// Returns the custom painter semantics for visible dates collection. List _getAccessibilityDates(Size size, double top, double height, List semanticsBuilder, - [CalendarResource resource]) { - double left = isRTL ? size.width - timeIntervalHeight : 0; + [CalendarResource? resource]) { + double left = isRTL ? size.width - timeIntervalWidth : 0; for (int j = 0; j < visibleDates.length; j++) { DateTime date = visibleDates[j]; final double hour = (timeSlotViewSettings.startHour - @@ -851,20 +1025,21 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { 60; for (int i = 0; i < horizontalLinesCountPerView; i++) { final double minute = - (i * _getTimeInterval(timeSlotViewSettings)) + hour; + (i * CalendarViewHelper.getTimeInterval(timeSlotViewSettings)) + + hour; date = DateTime(date.year, date.month, date.day, timeSlotViewSettings.startHour.toInt(), minute.toInt()); semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, timeIntervalHeight, height), + rect: Rect.fromLTWH(left, top, timeIntervalWidth, height), properties: SemanticsProperties( label: _getAccessibilityText(date, resource), textDirection: TextDirection.ltr, ), )); if (isRTL) { - left -= timeIntervalHeight; + left -= timeIntervalWidth; } else { - left += timeIntervalHeight; + left += timeIntervalWidth; } } } @@ -872,9 +1047,9 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { return semanticsBuilder; } - String _getAccessibilityText(DateTime date, [CalendarResource resource]) { + String _getAccessibilityText(DateTime date, [CalendarResource? resource]) { String dateText; - if (visibleDates.length > _kNumberOfDaysInWeek) { + if (visibleDates.length > DateTime.daysPerWeek) { dateText = DateFormat('EEEEE, dd\MMMM\yyyy').format(date).toString(); } dateText = DateFormat('h a, dd/MMMM/yyyy').format(date).toString(); @@ -887,8 +1062,11 @@ class _TimelineRenderObject extends _CustomCalendarRenderObject { } } -class _TimelineViewHeaderView extends CustomPainter { - _TimelineViewHeaderView( +/// Used to hold the view header cells for timeline view. +class TimelineViewHeaderView extends CustomPainter { + /// Constructor to create the view header view to holds header cell for + /// timeline views. + TimelineViewHeaderView( this.visibleDates, this.timelineViewHeaderScrollController, this.repaintNotifier, @@ -909,33 +1087,72 @@ class _TimelineViewHeaderView extends CustomPainter { this.textScaleFactor) : super(repaint: repaintNotifier); + /// Holds the visible dates collection for current timeline view. final List visibleDates; + + /// Defines the view header cell style. final ViewHeaderStyle viewHeaderStyle; + + /// Defines the timeline view slot setting used to provide the day and + /// date format of the view header cell. final TimeSlotViewSettings timeSlotViewSettings; + + /// Defines the height of the view header. final double viewHeaderHeight; - final Color todayHighlightColor; - final TextStyle todayTextStyle; - final double _padding = 5; + + /// Defines the today view header cell text color. + final Color? todayHighlightColor; + + /// Defines the today view header cell text style. + final TextStyle? todayTextStyle; + + /// Used to repaint the current view based on the timeline scroll to + /// achieve the sticky view header. final ValueNotifier repaintNotifier; + + /// Used to holds the current timeline scroll position. final ScrollController timelineViewHeaderScrollController; + + /// Holds the theme data value for calendar. final SfCalendarThemeData calendarTheme; + + /// Defines the direction of the calendar widget is RTL or not. final bool isRTL; + + /// Defines the locale of the calendar. final String locale; + + /// Defines the min date of the calendar. final DateTime minDate; + + /// Defines the max date of the calendar. final DateTime maxDate; - final ValueNotifier viewHeaderNotifier; - final Color cellBorderColor; - final List blackoutDates; - final TextStyle blackoutDatesTextStyle; + + /// Holds the current view header hovering position used to paint hovering. + final ValueNotifier viewHeaderNotifier; + + /// Defines the hovering view header cell background color. + final Color? cellBorderColor; + + /// Holds the blackout dates collection and it only applicable on + /// timeline month view. + final List? blackoutDates; + + /// Defines the style of the blackout dates cell and it only applicable on + /// timeline month view. + final TextStyle? blackoutDatesTextStyle; + + /// Defines the scale factor for the view header cell text. final double textScaleFactor; + final double _padding = 5; double _xPosition = 0; - TextPainter dayTextPainter, dateTextPainter; - Paint _hoverPainter; + TextPainter _dayTextPainter = TextPainter(), _dateTextPainter = TextPainter(); + Paint _hoverPainter = Paint(); @override void paint(Canvas canvas, Size size) { canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - final bool isTimelineMonth = visibleDates.length > _kNumberOfDaysInWeek; + final bool isTimelineMonth = visibleDates.length > DateTime.daysPerWeek; final DateTime today = DateTime.now(); final double childWidth = size.width / visibleDates.length; final int index = isTimelineMonth @@ -988,14 +1205,13 @@ class _TimelineViewHeaderView extends CustomPainter { final TextStyle viewHeaderDayStyle = viewHeaderStyle.dayTextStyle ?? viewHeaderDayTextStyle; - final TextStyle blackoutDatesStyle = + final TextStyle? blackoutDatesStyle = blackoutDatesTextStyle ?? calendarTheme.blackoutDatesTextStyle; TextStyle dayTextStyle = viewHeaderDayStyle; TextStyle dateTextStyle = viewHeaderDateStyle; if (isTimelineMonth) { - _hoverPainter ??= Paint(); _hoverPainter.strokeWidth = 0.5; _hoverPainter.strokeCap = StrokeCap.round; _hoverPainter.color = cellBorderColor ?? calendarTheme.cellBorderColor; @@ -1021,16 +1237,16 @@ class _TimelineViewHeaderView extends CustomPainter { .toString(); final bool isBlackoutDate = - _isDateInDateCollection(blackoutDates, currentDate); + CalendarViewHelper.isDateInDateCollection(blackoutDates, currentDate); if (isSameDate(currentDate, today)) { dayTextStyle = todayTextStyle != null - ? todayTextStyle.copyWith( + ? todayTextStyle!.copyWith( fontSize: viewHeaderDayStyle.fontSize, color: todayHighlightColor) : viewHeaderDayStyle.copyWith(color: todayHighlightColor); dateTextStyle = todayTextStyle != null - ? todayTextStyle.copyWith( + ? todayTextStyle!.copyWith( fontSize: viewHeaderDateStyle.fontSize, color: todayHighlightColor) : viewHeaderDateStyle.copyWith(color: todayHighlightColor); @@ -1058,25 +1274,23 @@ class _TimelineViewHeaderView extends CustomPainter { final TextSpan dayTextSpan = TextSpan(text: dayText, style: dayTextStyle); - dayTextPainter = dayTextPainter ?? TextPainter(); - dayTextPainter.text = dayTextSpan; - dayTextPainter.textDirection = TextDirection.ltr; - dayTextPainter.textAlign = TextAlign.left; - dayTextPainter.textWidthBasis = TextWidthBasis.longestLine; - dayTextPainter.textScaleFactor = textScaleFactor; + _dayTextPainter.text = dayTextSpan; + _dayTextPainter.textDirection = TextDirection.ltr; + _dayTextPainter.textAlign = TextAlign.left; + _dayTextPainter.textWidthBasis = TextWidthBasis.longestLine; + _dayTextPainter.textScaleFactor = textScaleFactor; final TextSpan dateTextSpan = TextSpan(text: dateText, style: dateTextStyle); - dateTextPainter = dateTextPainter ?? TextPainter(); - dateTextPainter.text = dateTextSpan; - dateTextPainter.textDirection = TextDirection.ltr; - dateTextPainter.textAlign = TextAlign.left; - dateTextPainter.textWidthBasis = TextWidthBasis.longestLine; - dateTextPainter.textScaleFactor = textScaleFactor; + _dateTextPainter.text = dateTextSpan; + _dateTextPainter.textDirection = TextDirection.ltr; + _dateTextPainter.textAlign = TextAlign.left; + _dateTextPainter.textWidthBasis = TextWidthBasis.longestLine; + _dateTextPainter.textScaleFactor = textScaleFactor; - dayTextPainter.layout(minWidth: 0, maxWidth: childWidth); - dateTextPainter.layout(minWidth: 0, maxWidth: childWidth); + _dayTextPainter.layout(minWidth: 0, maxWidth: childWidth); + _dateTextPainter.layout(minWidth: 0, maxWidth: childWidth); if (isTimelineMonth) { canvas.save(); _drawTimelineMonthViewHeader(canvas, childWidth, size, isBlackoutDate); @@ -1088,13 +1302,13 @@ class _TimelineViewHeaderView extends CustomPainter { void _drawTimelineTimeSlotsViewHeader( Canvas canvas, Size size, double childWidth, int index, int i) { - if (dateTextPainter.width + + if (_dateTextPainter.width + _xPosition + (_padding * 2) + - dayTextPainter.width > + _dayTextPainter.width > (i + 1) * childWidth) { _xPosition = ((i + 1) * childWidth) - - (dateTextPainter.width + (_padding * 2) + dayTextPainter.width); + (_dateTextPainter.width + (_padding * 2) + _dayTextPainter.width); } if (viewHeaderNotifier.value != null) { @@ -1102,28 +1316,28 @@ class _TimelineViewHeaderView extends CustomPainter { } if (isRTL) { - dateTextPainter.paint( + _dateTextPainter.paint( canvas, Offset( size.width - _xPosition - (_padding * 2) - - dayTextPainter.width - - dateTextPainter.width, - viewHeaderHeight / 2 - dateTextPainter.height / 2)); - dayTextPainter.paint( + _dayTextPainter.width - + _dateTextPainter.width, + viewHeaderHeight / 2 - _dateTextPainter.height / 2)); + _dayTextPainter.paint( canvas, - Offset(size.width - _xPosition - _padding - dayTextPainter.width, - viewHeaderHeight / 2 - dayTextPainter.height / 2)); + Offset(size.width - _xPosition - _padding - _dayTextPainter.width, + viewHeaderHeight / 2 - _dayTextPainter.height / 2)); } else { - dateTextPainter.paint( + _dateTextPainter.paint( canvas, Offset(_padding + _xPosition, - viewHeaderHeight / 2 - dateTextPainter.height / 2)); - dayTextPainter.paint( + viewHeaderHeight / 2 - _dateTextPainter.height / 2)); + _dayTextPainter.paint( canvas, - Offset(dateTextPainter.width + _xPosition + (_padding * 2), - viewHeaderHeight / 2 - dayTextPainter.height / 2)); + Offset(_dateTextPainter.width + _xPosition + (_padding * 2), + viewHeaderHeight / 2 - _dayTextPainter.height / 2)); } if (index == i) { @@ -1139,20 +1353,22 @@ class _TimelineViewHeaderView extends CustomPainter { const double leftPadding = 2; final double startXPosition = _xPosition + (childWidth - - (dateTextPainter.width + leftPadding + dayTextPainter.width)) / + (_dateTextPainter.width + + leftPadding + + _dayTextPainter.width)) / 2; final double startYPosition = (size.height - - (dayTextPainter.height > dateTextPainter.height - ? dayTextPainter.height - : dateTextPainter.height)) / + (_dayTextPainter.height > _dateTextPainter.height + ? _dayTextPainter.height + : _dateTextPainter.height)) / 2; if (viewHeaderNotifier.value != null && !isBlackoutDate) { _addMouseHovering(canvas, size, childWidth); } - dateTextPainter.paint(canvas, Offset(startXPosition, startYPosition)); - dayTextPainter.paint( + _dateTextPainter.paint(canvas, Offset(startXPosition, startYPosition)); + _dayTextPainter.paint( canvas, - Offset(startXPosition + dateTextPainter.width + leftPadding, + Offset(startXPosition + _dateTextPainter.width + leftPadding, startYPosition)); if (isRTL) { _xPosition -= childWidth; @@ -1166,8 +1382,7 @@ class _TimelineViewHeaderView extends CustomPainter { Offset(_xPosition, 0), Offset(_xPosition, size.height), _hoverPainter); } - void _addMouseHovering(Canvas canvas, Size size, [double cellWidth]) { - _hoverPainter ??= Paint(); + void _addMouseHovering(Canvas canvas, Size size, [double? cellWidth]) { double difference = 0; if (isRTL && (size.width - timelineViewHeaderScrollController.offset) < @@ -1180,8 +1395,8 @@ class _TimelineViewHeaderView extends CustomPainter { ? size.width - _xPosition - (_padding * 2) - - dayTextPainter.width - - dateTextPainter.width - + _dayTextPainter.width - + _dateTextPainter.width - _padding : _xPosition; final double rightPosition = isRTL && cellWidth == null @@ -1189,14 +1404,13 @@ class _TimelineViewHeaderView extends CustomPainter { : cellWidth != null ? _xPosition + cellWidth - _padding : _xPosition + - dayTextPainter.width + - dateTextPainter.width + + _dayTextPainter.width + + _dateTextPainter.width + (2 * _padding); - if (leftPosition + difference <= viewHeaderNotifier.value.dx && - rightPosition + difference >= viewHeaderNotifier.value.dx && - (size.height) - _padding >= viewHeaderNotifier.value.dy) { - _hoverPainter.color = (calendarTheme.brightness != null && - calendarTheme.brightness == Brightness.dark + if (leftPosition + difference <= viewHeaderNotifier.value!.dx && + rightPosition + difference >= viewHeaderNotifier.value!.dx && + (size.height) - _padding >= viewHeaderNotifier.value!.dy) { + _hoverPainter.color = (calendarTheme.brightness == Brightness.dark ? Colors.white : Colors.black87) .withOpacity(0.04); @@ -1207,8 +1421,8 @@ class _TimelineViewHeaderView extends CustomPainter { } @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _TimelineViewHeaderView oldWidget = oldDelegate; + bool shouldRepaint(TimelineViewHeaderView oldDelegate) { + final TimelineViewHeaderView oldWidget = oldDelegate; return oldWidget.visibleDates != visibleDates || oldWidget._xPosition != _xPosition || oldWidget.viewHeaderStyle != viewHeaderStyle || @@ -1220,7 +1434,8 @@ class _TimelineViewHeaderView extends CustomPainter { oldWidget.viewHeaderNotifier.value != viewHeaderNotifier.value || oldWidget.todayTextStyle != todayTextStyle || oldWidget.textScaleFactor != textScaleFactor || - !_isDateCollectionEqual(oldWidget.blackoutDates, blackoutDates); + !CalendarViewHelper.isDateCollectionEqual( + oldWidget.blackoutDates, blackoutDates); } List _getSemanticsBuilder(Size size) { @@ -1254,7 +1469,7 @@ class _TimelineViewHeaderView extends CustomPainter { return textString + ', Disabled date'; } - if (_isDateInDateCollection(blackoutDates, date)) { + if (CalendarViewHelper.isDateInDateCollection(blackoutDates, date)) { return textString + ', Blackout date'; } @@ -1273,8 +1488,8 @@ class _TimelineViewHeaderView extends CustomPainter { } @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _TimelineViewHeaderView oldWidget = oldDelegate; + bool shouldRebuildSemantics(TimelineViewHeaderView oldDelegate) { + final TimelineViewHeaderView oldWidget = oldDelegate; return oldWidget.visibleDates != visibleDates || oldWidget.calendarTheme != calendarTheme; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/view_header_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/view_header_view.dart deleted file mode 100644 index b07ac0ef7..000000000 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/view_header_view.dart +++ /dev/null @@ -1,504 +0,0 @@ -part of calendar; - -class _ViewHeaderViewPainter extends CustomPainter { - _ViewHeaderViewPainter( - this.visibleDates, - this.view, - this.viewHeaderStyle, - this.timeSlotViewSettings, - this.timeLabelWidth, - this.viewHeaderHeight, - this.monthViewSettings, - this.isRTL, - this.locale, - this.calendarTheme, - this.todayHighlightColor, - this.todayTextStyle, - this.cellBorderColor, - this.minDate, - this.maxDate, - this.viewHeaderNotifier, - this.textScaleFactor) - : super(repaint: viewHeaderNotifier); - - final CalendarView view; - final ViewHeaderStyle viewHeaderStyle; - final TimeSlotViewSettings timeSlotViewSettings; - final MonthViewSettings monthViewSettings; - final List visibleDates; - final double timeLabelWidth; - final double viewHeaderHeight; - final SfCalendarThemeData calendarTheme; - final bool isRTL; - final String locale; - final Color todayHighlightColor; - final TextStyle todayTextStyle; - final Color cellBorderColor; - final DateTime minDate; - final DateTime maxDate; - final ValueNotifier viewHeaderNotifier; - final double textScaleFactor; - DateTime _currentDate; - String _dayText, _dateText; - Paint _circlePainter; - TextPainter _dayTextPainter, _dateTextPainter; - - @override - void paint(Canvas canvas, Size size) { - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - double width = size.width; - width = _getViewHeaderWidth(width); - - /// Initializes the default text style for the texts in view header of - /// calendar. - final TextStyle viewHeaderDayStyle = - viewHeaderStyle.dayTextStyle ?? calendarTheme.viewHeaderDayTextStyle; - final TextStyle viewHeaderDateStyle = - viewHeaderStyle.dateTextStyle ?? calendarTheme.viewHeaderDateTextStyle; - - final DateTime today = DateTime.now(); - if (view != CalendarView.month) { - _addViewHeaderForTimeSlotViews( - canvas, size, viewHeaderDayStyle, viewHeaderDateStyle, width, today); - } else { - _addViewHeaderForMonthView( - canvas, size, viewHeaderDayStyle, width, today); - } - } - - void _addViewHeaderForMonthView(Canvas canvas, Size size, - TextStyle viewHeaderDayStyle, double width, DateTime today) { - TextStyle dayTextStyle = viewHeaderDayStyle; - double xPosition = 0; - double yPosition = 0; - if (isRTL) { - xPosition = size.width - width; - } - bool hasToday = false; - for (int i = 0; i < _kNumberOfDaysInWeek; i++) { - _currentDate = visibleDates[i]; - _dayText = DateFormat(monthViewSettings.dayFormat, locale) - .format(_currentDate) - .toString() - .toUpperCase(); - - _dayText = _updateViewHeaderFormat(monthViewSettings.dayFormat); - - hasToday = monthViewSettings.numberOfWeeksInView > 0 && - monthViewSettings.numberOfWeeksInView < 6 - ? true - : visibleDates[visibleDates.length ~/ 2].month == today.month - ? true - : false; - - if (hasToday && - isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDates.length - 1], today) && - _currentDate.weekday == today.weekday) { - dayTextStyle = todayTextStyle != null - ? todayTextStyle.copyWith( - fontSize: viewHeaderDayStyle.fontSize, - color: todayHighlightColor) - : viewHeaderDayStyle.copyWith(color: todayHighlightColor); - } else { - dayTextStyle = viewHeaderDayStyle; - } - - _updateDayTextPainter(dayTextStyle, width); - - if (yPosition == 0) { - yPosition = (viewHeaderHeight - _dayTextPainter.height) / 2; - } - - if (viewHeaderNotifier.value != null) { - _addMouseHoverForMonth(canvas, size, xPosition, yPosition, width); - } - - _dayTextPainter.paint( - canvas, - Offset( - xPosition + (width / 2 - _dayTextPainter.width / 2), yPosition)); - - if (isRTL) { - xPosition -= width; - } else { - xPosition += width; - } - } - } - - void _addViewHeaderForTimeSlotViews( - Canvas canvas, - Size size, - TextStyle viewHeaderDayStyle, - TextStyle viewHeaderDateStyle, - double width, - DateTime today) { - double xPosition, yPosition; - final double labelWidth = - view == CalendarView.day && timeLabelWidth < 50 ? 50 : timeLabelWidth; - TextStyle dayTextStyle = viewHeaderDayStyle; - TextStyle dateTextStyle = viewHeaderDateStyle; - const double topPadding = 5; - if (view == CalendarView.day) { - width = labelWidth; - } - - xPosition = view == CalendarView.day ? 0 : timeLabelWidth; - yPosition = 2; - final double cellWidth = width / visibleDates.length; - if (isRTL && view != CalendarView.day) { - xPosition = size.width - timeLabelWidth - cellWidth; - } - for (int i = 0; i < visibleDates.length; i++) { - _currentDate = visibleDates[i]; - _dayText = DateFormat(timeSlotViewSettings.dayFormat, locale) - .format(_currentDate) - .toString() - .toUpperCase(); - - _dayText = _updateViewHeaderFormat(timeSlotViewSettings.dayFormat); - - _dateText = DateFormat(timeSlotViewSettings.dateFormat) - .format(_currentDate) - .toString(); - final bool isToday = isSameDate(_currentDate, today); - if (isToday) { - dayTextStyle = todayTextStyle != null - ? todayTextStyle.copyWith( - fontSize: viewHeaderDayStyle.fontSize, - color: todayHighlightColor) - : viewHeaderDayStyle.copyWith(color: todayHighlightColor); - dateTextStyle = todayTextStyle != null - ? todayTextStyle.copyWith(fontSize: viewHeaderDateStyle.fontSize) - : viewHeaderDateStyle.copyWith( - color: calendarTheme.todayTextStyle.color); - } else { - dayTextStyle = viewHeaderDayStyle; - dateTextStyle = viewHeaderDateStyle; - } - - if (!isDateWithInDateRange(minDate, maxDate, _currentDate)) { - if (calendarTheme.brightness == Brightness.light) { - dayTextStyle = dayTextStyle.copyWith(color: Colors.black26); - dateTextStyle = dateTextStyle.copyWith(color: Colors.black26); - } else { - dayTextStyle = dayTextStyle.copyWith(color: Colors.white38); - dateTextStyle = dateTextStyle.copyWith(color: Colors.white38); - } - } - - _updateDayTextPainter(dayTextStyle, width); - - final TextSpan dateTextSpan = TextSpan( - text: _dateText, - style: dateTextStyle, - ); - - _dateTextPainter = _dateTextPainter ?? TextPainter(); - _dateTextPainter.text = dateTextSpan; - _dateTextPainter.textDirection = TextDirection.ltr; - _dateTextPainter.textAlign = TextAlign.left; - _dateTextPainter.textWidthBasis = TextWidthBasis.longestLine; - _dateTextPainter.textScaleFactor = textScaleFactor; - - _dateTextPainter.layout(minWidth: 0, maxWidth: width); - - /// To calculate the day start position by width and day painter - final double dayXPosition = (cellWidth - _dayTextPainter.width) / 2; - - /// To calculate the date start position by width and date painter - final double dateXPosition = (cellWidth - _dateTextPainter.width) / 2; - - const int inBetweenPadding = 2; - yPosition = size.height / 2 - - (_dayTextPainter.height + - topPadding + - _dateTextPainter.height + - inBetweenPadding) / - 2; - - _dayTextPainter.paint( - canvas, Offset(xPosition + dayXPosition, yPosition)); - - if (isToday) { - _drawTodayCircle( - canvas, - xPosition + dateXPosition, - yPosition + topPadding + _dayTextPainter.height + inBetweenPadding, - _dateTextPainter); - } - - if (viewHeaderNotifier.value != null) { - _addMouseHoverForTimeSlotView(canvas, size, xPosition, yPosition, - dateXPosition, topPadding, isToday, inBetweenPadding); - } - - _dateTextPainter.paint( - canvas, - Offset( - xPosition + dateXPosition, - yPosition + - topPadding + - _dayTextPainter.height + - inBetweenPadding)); - - if (isRTL) { - xPosition -= cellWidth; - } else { - xPosition += cellWidth; - } - } - } - - void _addMouseHoverForMonth(Canvas canvas, Size size, double xPosition, - double yPosition, double width) { - _circlePainter ??= Paint(); - if (xPosition + (width / 2 - _dayTextPainter.width / 2) <= - viewHeaderNotifier.value.dx && - xPosition + - (width / 2 - _dayTextPainter.width / 2) + - _dayTextPainter.width >= - viewHeaderNotifier.value.dx && - yPosition - 5 <= viewHeaderNotifier.value.dy && - (yPosition + size.height) - 5 >= viewHeaderNotifier.value.dy) { - _drawTodayCircle( - canvas, - xPosition + (width / 2 - _dayTextPainter.width / 2), - yPosition, - _dayTextPainter, - hoveringColor: (calendarTheme.brightness != null && - calendarTheme.brightness == Brightness.dark - ? Colors.white - : Colors.black87) - .withOpacity(0.04)); - } - } - - void _addMouseHoverForTimeSlotView( - Canvas canvas, - Size size, - double xPosition, - double yPosition, - double dateXPosition, - double topPadding, - bool isToday, - int padding) { - _circlePainter ??= Paint(); - if (xPosition + dateXPosition <= viewHeaderNotifier.value.dx && - xPosition + dateXPosition + _dateTextPainter.width >= - viewHeaderNotifier.value.dx) { - final Color hoveringColor = isToday - ? Colors.black.withOpacity(0.12) - : (calendarTheme.brightness != null && - calendarTheme.brightness == Brightness.dark - ? Colors.white - : Colors.black87) - .withOpacity(0.04); - _drawTodayCircle( - canvas, - xPosition + dateXPosition, - yPosition + topPadding + _dayTextPainter.height + padding, - _dateTextPainter, - hoveringColor: hoveringColor); - } - } - - String _updateViewHeaderFormat(String dayFormat) { - switch (view) { - case CalendarView.day: - case CalendarView.schedule: - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - break; - case CalendarView.month: - case CalendarView.week: - case CalendarView.workWeek: - { - //// EE format value shows the week days as S, M, T, W, T, F, S. - if (dayFormat == 'EE' && (locale == null || locale.contains('en'))) { - return _dayText[0]; - } - } - } - - return _dayText; - } - - void _updateDayTextPainter(TextStyle dayTextStyle, double width) { - final TextSpan dayTextSpan = TextSpan( - text: _dayText, - style: dayTextStyle, - ); - - _dayTextPainter = _dayTextPainter ?? TextPainter(); - _dayTextPainter.text = dayTextSpan; - _dayTextPainter.textDirection = TextDirection.ltr; - _dayTextPainter.textAlign = TextAlign.left; - _dayTextPainter.textWidthBasis = TextWidthBasis.longestLine; - _dayTextPainter.textScaleFactor = textScaleFactor; - - _dayTextPainter.layout(minWidth: 0, maxWidth: width); - } - - double _getViewHeaderWidth(double width) { - switch (view) { - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - case CalendarView.schedule: - return null; - case CalendarView.month: - return width / _kNumberOfDaysInWeek; - case CalendarView.day: - return timeLabelWidth; - case CalendarView.week: - case CalendarView.workWeek: - return width - timeLabelWidth; - } - - return null; - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _ViewHeaderViewPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.viewHeaderStyle != viewHeaderStyle || - oldWidget.viewHeaderHeight != viewHeaderHeight || - oldWidget.todayHighlightColor != todayHighlightColor || - oldWidget.timeSlotViewSettings != timeSlotViewSettings || - oldWidget.monthViewSettings != monthViewSettings || - oldWidget.cellBorderColor != cellBorderColor || - oldWidget.calendarTheme != calendarTheme || - oldWidget.isRTL != isRTL || - oldWidget.locale != locale || - oldWidget.todayTextStyle != todayTextStyle || - oldWidget.textScaleFactor != textScaleFactor; - } - - //// draw today highlight circle in view header. - void _drawTodayCircle( - Canvas canvas, double x, double y, TextPainter dateTextPainter, - {Color hoveringColor}) { - _circlePainter = _circlePainter ?? Paint(); - _circlePainter.color = hoveringColor ?? todayHighlightColor; - const double circlePadding = 5; - final double painterWidth = dateTextPainter.width / 2; - final double painterHeight = dateTextPainter.height / 2; - final double radius = - painterHeight > painterWidth ? painterHeight : painterWidth; - canvas.drawCircle(Offset(x + painterWidth, y + painterHeight), - radius + circlePadding, _circlePainter); - } - - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility - @override - SemanticsBuilderCallback get semanticsBuilder { - return (Size size) { - return _getSemanticsBuilder(size); - }; - } - - @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _ViewHeaderViewPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; - } - - String _getAccessibilityText(DateTime date) { - if (!isDateWithInDateRange(minDate, maxDate, date)) { - return DateFormat('EEEEE').format(date).toString() + - DateFormat('dd/MMMM/yyyy').format(date).toString() + - ', Disabled date'; - } - - return DateFormat('EEEEE').format(date).toString() + - DateFormat('dd/MMMM/yyyy').format(date).toString(); - } - - List _getSemanticsForMonthViewHeader(Size size) { - final List semanticsBuilder = - []; - final double cellWidth = size.width / _kNumberOfDaysInWeek; - double left = isRTL ? size.width - cellWidth : 0; - const double top = 0; - for (int i = 0; i < _kNumberOfDaysInWeek; i++) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, cellWidth, size.height), - properties: SemanticsProperties( - label: DateFormat('EEEEE') - .format(visibleDates[i]) - .toString() - .toUpperCase(), - textDirection: TextDirection.ltr, - ), - )); - if (isRTL) { - left -= cellWidth; - } else { - left += cellWidth; - } - } - - return semanticsBuilder; - } - - List _getSemanticsForDayHeader(Size size) { - final List semanticsBuilder = - []; - const double top = 0; - double left; - final double cellWidth = view == CalendarView.day - ? size.width - : (size.width - timeLabelWidth) / visibleDates.length; - if (isRTL) { - left = view == CalendarView.day - ? size.width - timeLabelWidth - : (size.width - timeLabelWidth) - cellWidth; - } else { - left = view == CalendarView.day ? 0 : timeLabelWidth; - } - for (int i = 0; i < visibleDates.length; i++) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, cellWidth, size.height), - properties: SemanticsProperties( - label: _getAccessibilityText(visibleDates[i]), - textDirection: TextDirection.ltr, - ), - )); - if (isRTL) { - left -= cellWidth; - } else { - left += cellWidth; - } - } - - return semanticsBuilder; - } - - List _getSemanticsBuilder(Size size) { - switch (view) { - case CalendarView.schedule: - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - return null; - case CalendarView.month: - return _getSemanticsForMonthViewHeader(size); - case CalendarView.day: - case CalendarView.week: - case CalendarView.workWeek: - return _getSemanticsForDayHeader(size); - } - - return null; - } -} diff --git a/packages/syncfusion_flutter_calendar/pubspec.yaml b/packages/syncfusion_flutter_calendar/pubspec.yaml index 8d88d93f0..7fbc22060 100644 --- a/packages/syncfusion_flutter_calendar/pubspec.yaml +++ b/packages/syncfusion_flutter_calendar/pubspec.yaml @@ -1,22 +1,22 @@ name: syncfusion_flutter_calendar -description: The Syncfusion Flutter Calendar widget has built-in configurable views that provide basic functionalities for scheduling and representing appointments/events efficiently. -version: 18.3.35 -homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_calendar +description: The Flutter Calendar widget has nine built-in configurable views that provide basic functionalities for scheduling and representing appointments/events efficiently. +version: 19.1.54 +homepage: https://github.com/syncfusion/flutter-examples environment: - sdk: ">=2.1.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: sdk: flutter - timezone: 0.5.9 + timezone: 0.7.0-nullsafety.0 syncfusion_flutter_core: path: ../syncfusion_flutter_core syncfusion_flutter_datepicker: path: ../syncfusion_flutter_datepicker - intl: ">=0.15.0 <0.17.0" + intl: ">=0.15.0 <0.20.0" flutter: assets: diff --git a/packages/syncfusion_flutter_charts/CHANGELOG.md b/packages/syncfusion_flutter_charts/CHANGELOG.md index ffa56903a..67054834a 100644 --- a/packages/syncfusion_flutter_charts/CHANGELOG.md +++ b/packages/syncfusion_flutter_charts/CHANGELOG.md @@ -1,4 +1,52 @@ - ## Unreleased +## [18.4.44] - 02/23/2021 + +**Bugs** +* An exception will not be thrown while selecting the data points, after updating the data source + +## [18.4.43] - 02/16/2021 + +**Bugs** +* The zoomed charts can be panned properly after changing the visible minimum and maximum values. +* Now, on selecting a data point, no exception is thrown when selection is enabled. + +## [18.4.42] - 02/09/2021 + +**Bugs** +* Now, the trackball tooltip is rendering properly when its height is greater than the chart widget. +* The spline series is rendering properly with cardinal type and date time axis. + +## [18.4.41] - 02/02/2021 + +**Bugs** +* Line series will not throw any exceptions on showing the tooltip with a single point. +* Now, the axis ranges will be calculated properly even the axis visibility is set to false. +* The text changed using onTooltipRender event is working properly. + +## [18.4.35] - 01/19/2021 + +**Bugs** +* Now, the spline rage area series will fill properly with negative values. + +## [18.4.34] - 01/12/2021 + +**Bugs** +* Now, the stacked charts are rendering properly with multiple axes and animation. +* The circular chart will not throw any exception while using selectDataPoints method. +* Tooltip format with `point.cumulativeValue` will not throw any exception now. + +## [18.4.33] - 01/05/2021 + +**Bugs** + +* The `onSelectionChanged` event triggers properly on selecting point using `selectDataPoints` method. + +## [18.4.31] - 12/22/2020 + +**Bugs** + +* Now, you can disable the `enableAutoIntervalOnZooming` property for the numeric axis of the cartesian chart. + +## [18.4.30] - 12/17/2020 ### Chart diff --git a/packages/syncfusion_flutter_charts/README.md b/packages/syncfusion_flutter_charts/README.md index 09b8ad693..03320e01e 100644 --- a/packages/syncfusion_flutter_charts/README.md +++ b/packages/syncfusion_flutter_charts/README.md @@ -1,12 +1,12 @@ ![syncfusion_flutter_chart_banner](https://cdn.syncfusion.com/content/images/FTControl/Flutter-Charts-Graphs.png) -# Syncfusion Flutter Charts +# Flutter Charts library -Syncfusion Flutter Charts is a data visualization library written natively in Dart for creating beautiful and high-performance charts, which are used to craft high-quality mobile app user interfaces using Flutter. +Flutter Charts package is a data visualization library written natively in Dart for creating beautiful, animated and high-performance charts, which are used to craft high-quality mobile app user interfaces using Flutter. ## Overview -Create various types of cartesian or circular charts with seamless interaction, responsiveness, and smooth animation. It has a rich set of features, and it is completely customizable and extendable. +Create various types of cartesian, circular and spark charts with seamless interaction, responsiveness, and smooth animation. It has a rich set of features, and it is completely customizable and extendable. This [syncfusion_flutter_charts](https://pub.dev/packages/syncfusion_flutter_charts) package includes the following widgets @@ -19,9 +19,7 @@ This [syncfusion_flutter_charts](https://pub.dev/packages/syncfusion_flutter_cha * [SfSparkBarChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/sparkcharts/SfSparkBarChart-class.html) * [SfSparkWinLossChart](https://pub.dev/documentation/syncfusion_flutter_charts/latest/sparkcharts/SfSparkWinLossChart-class.html) -**Disclaimer:** This is a commercial package. To use this package, you need to have either Syncfusion Commercial License or Syncfusion Community license. For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. - -**Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. +**Disclaimer:** This is a commercial package. To use this package, you need to have either Syncfusion Commercial License or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. ## Table of contents - [Chart features](#chart-features) @@ -42,42 +40,42 @@ This [syncfusion_flutter_charts](https://pub.dev/packages/syncfusion_flutter_cha ## Chart features -* **Chart types** - Provides functionality for rendering 25+ chart types, namely line, spline, column, bar, area, bubble, scatter, step line, fast line, range column, range area, step area, spline area, stacked charts, 100% stacked charts, pie, doughnut, radial bar, pyramid, funnel, etc. Each chart type is easily configured and customized with built-in features for creating stunning visual effects. +* **Chart types** - Provides functionality for rendering 30+ chart types, namely [line](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/line-chart), [spline](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/spline-chart), [column](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/column-chart), [bar](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/bar-chart), [area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/area-chart), [bubble](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/bubble-chart), [box and whisker](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/box-and-whisker-chart), [scatter](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/scatter-chart), [step line](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/step-line-chart), [fast line](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/line-chart), [range column](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/range-column-chart), [range area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/range-area-chart), [candle](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/candle-chart), [hilo](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/hilo-chart), [ohlc](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/ohlc-chart), [histogram](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/histogram-chart), [step area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/step-area-chart), [spline area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/spline-area-chart), [spline range area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/spline-range-area-chart), [stacked area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-area-chart), [stacked bar](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-bar-chart), [stacked column](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-column-chart), [stacked line](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-line-chart), [100% stacked area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-area-100-chart), [100% stacked bar](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-bar-100-chart), [100% stacked column](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-column-100-chart), [100% stacked line](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-line-100-chart), [waterfall](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/waterfall-chart), [pie](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/pie-chart), [doughnut](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/doughnut-chart), [radial bar](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/radial-bar-chart), [pyramid](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/pyramid-chart), [funnel](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/funnel-chart). Each chart type is easily configured and customized with built-in features for creating stunning visual effects. ![flutter_chart_types](https://cdn.syncfusion.com/content/images/FTControl/Charts/chart_types.png) -* **Axis types** - Plot various types of data in a graph with the help of numeric, category, date-time and log axis types. The built-in axis features allow to customize an axis elements further to make the axis more readable. +* **Axis types** - Plot various types of data in a graph with the help of numeric, category, date-time, date-time category and log axis types. The built-in axis features allow to customize an axis elements further to make the axis more readable. ![flutter_chart_axis_types](https://cdn.syncfusion.com/content/images/FTControl/chart-axis-types.png) -* **User interaction** - The end-user experience is greatly enhanced by including the user interaction features such as zooming and panning, crosshair, trackball, events, selection, and tooltip in chart. +* **User interaction** - The end-user experience is greatly enhanced by including the user interaction features such as zooming and panning, crosshair, trackball, callbacks, selection, tooltip, and auto-scrolling in chart. ![flutter_chart_user_interactions](https://cdn.syncfusion.com/content/images/FTControl/chart-user-interaction.gif) * **Legends** - Display additional information about the chart series. The chart legend can also be used to collapse the series. The legends can be wrapped or scrolled if an item exceeds the available bounds. ![flutter_chart_legend](https://cdn.syncfusion.com/content/images/FTControl/Charts/legends.png) -* **Dynamic update** - Updates the chart dynamically with live data that changes over seconds or minutes like stock prices, temperature, speed, etc. +* **Dynamic update** - Updates the chart dynamically or lazily with live data that changes over seconds or minutes like stock prices, temperature, speed, etc. ![flutter_chart_user_interactions](https://cdn.syncfusion.com/content/images/FTControl/Charts/live_updates.gif) ## Spark Charts features -Spark charts (micro charts) are lightweight charts that fit in a very small area. They display the trend of the data and convey quick information to the user. +Spark charts (Sparkline charts) which is also known as micro charts are lightweight charts that fit in a very small area. They display the trend of the data and convey quick information to the user. -* **Chart types** - Support to render line, area, column and win-loss chart types. -![spark_chart_types](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_types.jpg) +* **Chart types** - Support to render line, area, bar and win-loss chart types. +![sparkline_chart_types](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_types.jpg) * **Axis types** - Spark charts provides support for numeric, category and date-time axes. -![spark_chart_axis_types](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_axis_types.jpg) +![sparkline_chart_axis_types](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_axis_types.jpg) * **Markers and data labels** - Support to render markers and data labels on high, low, first, last and all data points. -![spark_chart_markers_data_label](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_marker_data_label.jpg) +![sparkline_chart_markers_data_label](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_marker_data_label.jpg) * **Trackball** - Display additional information about data points on interaction with the chart. -![spark_chart_trackball](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_trackball.gif) +![sparkline_chart_trackball](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_trackball.gif) * **Plot band** - Highlight a particular vertical range using a specific color. -![spark_chart_plotband](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_plotband.jpg) +![sparkline_chart_plotband](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_plotband.jpg) -* **Live update** - Spark charts can be used in the live update. -![spark_chart_live](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_live_update.gif) +* **Live update** - Sparkline charts can be used in the live update. +![sparkline_chart_live](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_live_update.gif) ## Get the demo application @@ -181,6 +179,14 @@ class SalesData { Add the chart elements such as title, legend, data label, and tooltip to display additional information about the data plotted in the chart. ```dart +TooltipBehavior _tooltipBehavior; + +@override +void initState(){ + _tooltipBehavior = TooltipBehavior(enable: true); + super.initState(); +} + @override Widget build(BuildContext context) { return Scaffold( @@ -194,7 +200,7 @@ Widget build(BuildContext context) { // Enable legend legend: Legend(isVisible: true), // Enable tooltip - tooltipBehavior: TooltipBehavior(enable: true), + tooltipBehavior: _tooltipBehavior, series: >[ LineSeries( @@ -249,10 +255,10 @@ Widget build(BuildContext context) { **Note** -Use `SfSparkAreaChart`, `SfSparkColumnChart` and `SfSparkWinLossChart` widgets to render area, column and win-loss charts respectively. +Use `SfSparkAreaChart`, `SfSparkBarChart` and `SfSparkWinLossChart` widgets to render area, bar and win-loss charts respectively. ### Bind spark charts data source -Based on data and your requirement, initialize the series and bind the data to spark charts. +Based on data and your requirement, initialize the series and bind the data to sparkline charts. ```dart @override @@ -301,7 +307,7 @@ Widget build(BuildContext context) { ); } ``` -![spark_chart_default_line](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_marker_data_label.jpg) +![sparkline_chart_default_line](https://cdn.syncfusion.com/content/images/FTControl/spark_chart_marker_data_label.jpg) ## Support and Feedback diff --git a/packages/syncfusion_flutter_charts/analysis_options.yaml b/packages/syncfusion_flutter_charts/analysis_options.yaml index 2a22b53a9..67178baca 100644 --- a/packages/syncfusion_flutter_charts/analysis_options.yaml +++ b/packages/syncfusion_flutter_charts/analysis_options.yaml @@ -4,3 +4,5 @@ analyzer: errors: include_file_not_found: ignore lines_longer_than_80_chars: ignore + avoid_as: ignore + unnecessary_null_comparison: ignore diff --git a/packages/syncfusion_flutter_charts/example/lib/main.dart b/packages/syncfusion_flutter_charts/example/lib/main.dart index a3e853a5a..bc37090a3 100644 --- a/packages/syncfusion_flutter_charts/example/lib/main.dart +++ b/packages/syncfusion_flutter_charts/example/lib/main.dart @@ -18,7 +18,7 @@ class _ChartApp extends StatelessWidget { class _MyHomePage extends StatefulWidget { // ignore: prefer_const_constructors_in_immutables - _MyHomePage({Key key}) : super(key: key); + _MyHomePage({Key? key}) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); diff --git a/packages/syncfusion_flutter_charts/example/pubspec.yaml b/packages/syncfusion_flutter_charts/example/pubspec.yaml index b61fa680c..fbc060b3d 100644 --- a/packages/syncfusion_flutter_charts/example/pubspec.yaml +++ b/packages/syncfusion_flutter_charts/example/pubspec.yaml @@ -1,16 +1,17 @@ name: charts_example description: This project demonstrates how to use Syncfusion Flutter Charts widget? version: 1.0.0+1 +publish_to: 'none' environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter syncfusion_flutter_charts: - path: ../ - cupertino_icons: ^0.1.3 + path: ../syncfusion_flutter_charts + cupertino_icons: ^1.0.2 flutter: diff --git a/packages/syncfusion_flutter_charts/lib/charts.dart b/packages/syncfusion_flutter_charts/lib/charts.dart index 60aa9da25..70212f6d4 100644 --- a/packages/syncfusion_flutter_charts/lib/charts.dart +++ b/packages/syncfusion_flutter_charts/lib/charts.dart @@ -12,9 +12,11 @@ library charts; import 'dart:async'; +import 'dart:io' show Platform; import 'dart:math' as math_lib; import 'dart:math' as math; import 'dart:math'; +import 'dart:typed_data'; import 'dart:ui'; import 'dart:ui' as dart_ui; import 'package:flutter/material.dart'; @@ -22,14 +24,18 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:intl/intl.dart' show DateFormat; import 'package:intl/intl.dart' show NumberFormat; -import 'package:vector_math/vector_math.dart' as vector; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_core/tooltip_internal.dart'; import './src/common/handcursor/mobile.dart' if (dart.library.html) './src/common/handcursor/web.dart'; +export 'package:syncfusion_flutter_core/core.dart' + show DataMarkerType, TooltipAlignment; + // export circular library part './src/circular_chart/base/circular_base.dart'; part './src/circular_chart/base/series_base.dart'; @@ -62,6 +68,7 @@ part './src/chart/axis/axis.dart'; part './src/chart/axis/axis_panel.dart'; part './src/chart/axis/axis_renderer.dart'; part './src/chart/axis/category_axis.dart'; +part './src/chart/axis/datetime_category_axis.dart'; part './src/chart/axis/datetime_axis.dart'; part './src/chart/axis/logarithmic_axis.dart'; part './src/chart/axis/numeric_axis.dart'; @@ -181,8 +188,6 @@ part './src/chart/trendlines/trendlines_painter.dart'; part './src/chart/user_interaction/crosshair.dart'; part './src/chart/user_interaction/crosshair_painter.dart'; part './src/chart/user_interaction/selection_renderer.dart'; -part './src/chart/user_interaction/tooltip_painter.dart'; -part './src/chart/user_interaction/tooltip_template.dart'; part './src/chart/user_interaction/trackball.dart'; part './src/chart/user_interaction/trackball_painter.dart'; part './src/chart/user_interaction/trackball_template.dart'; diff --git a/packages/syncfusion_flutter_charts/lib/sparkcharts.dart b/packages/syncfusion_flutter_charts/lib/sparkcharts.dart index 6e8ffc109..d5e05fcad 100644 --- a/packages/syncfusion_flutter_charts/lib/sparkcharts.dart +++ b/packages/syncfusion_flutter_charts/lib/sparkcharts.dart @@ -1,3 +1,13 @@ +/// Syncfusion Flutter Spark/Micro charts are light weight chart, typically drawn without axes or coordinates. +/// It presents the general shape of data’s in a simple and highly condensed way. +/// +/// To use, import `package:syncfusion_flutter_charts/sparkcharts.dart`. +/// +/// See also: +/// * [Syncfusion Flutter Charts product page](https://www.syncfusion.com/flutter-widgets/flutter-spark-charts) +/// * [User guide documentation](https://help.syncfusion.com/flutter/sparkcharts/overview) +/// * [Knowledge base](https://www.syncfusion.com/kb/flutter/sfsparklinechart) + library sparkcharts; // export spark chart library diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/annotation/annotation_settings.dart b/packages/syncfusion_flutter_charts/lib/src/chart/annotation/annotation_settings.dart index 499d21ef4..331e5543c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/annotation/annotation_settings.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/annotation/annotation_settings.dart @@ -15,20 +15,14 @@ class CartesianChartAnnotation { /// Creating an argument constructor of CartesianChartAnnotation class. const CartesianChartAnnotation( {this.widget, - CoordinateUnit coordinateUnit, - AnnotationRegion region, - ChartAlignment horizontalAlignment, - ChartAlignment verticalAlignment, - dynamic x, - num y, + this.coordinateUnit = CoordinateUnit.logicalPixel, + this.region = AnnotationRegion.chart, + this.horizontalAlignment = ChartAlignment.center, + this.verticalAlignment = ChartAlignment.center, + this.x = 0, + this.y = 0, this.xAxisName, - this.yAxisName}) - : coordinateUnit = coordinateUnit ?? CoordinateUnit.logicalPixel, - region = region ?? AnnotationRegion.chart, - x = x ?? 0, - y = y ?? 0, - horizontalAlignment = horizontalAlignment ?? ChartAlignment.center, - verticalAlignment = verticalAlignment ?? ChartAlignment.center; + this.yAxisName}); ///Considers any widget as annotation. /// @@ -51,7 +45,7 @@ class CartesianChartAnnotation { /// )); ///} ///``` - final Widget widget; + final Widget? widget; ///Specifies the coordinate units for placing the annotation in either logicalPixel or point. /// @@ -174,7 +168,7 @@ class CartesianChartAnnotation { /// )); ///} ///``` - final String xAxisName; + final String? xAxisName; ///Specifies the y-axis name to the annotation that should be bound. /// @@ -203,7 +197,7 @@ class CartesianChartAnnotation { /// )); ///} ///``` - final String yAxisName; + final String? yAxisName; ///Aligns the annotations horizontally. /// diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart index 1dd26eb12..93aa044c3 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart @@ -11,40 +11,42 @@ abstract class ChartAxis { /// Creating an argument constructor of ChartAxis class. ChartAxis({ this.name, - double plotOffset, - bool isVisible, - bool anchorRangeToVisiblePoints, - AxisTitle title, - AxisLine axisLine, - ChartRangePadding rangePadding, - int labelRotation, - ChartDataLabelPosition labelPosition, - LabelAlignment labelAlignment, - TickPosition tickPosition, - MajorTickLines majorTickLines, - MinorTickLines minorTickLines, - TextStyle labelStyle, - AxisLabelIntersectAction labelIntersectAction, + double? plotOffset, + bool? isVisible, + bool? anchorRangeToVisiblePoints, + AxisTitle? title, + AxisLine? axisLine, + ChartRangePadding? rangePadding, + int? labelRotation, + ChartDataLabelPosition? labelPosition, + LabelAlignment? labelAlignment, + TickPosition? tickPosition, + MajorTickLines? majorTickLines, + MinorTickLines? minorTickLines, + TextStyle? labelStyle, + AxisLabelIntersectAction? labelIntersectAction, this.desiredIntervals, - MajorGridLines majorGridLines, - MinorGridLines minorGridLines, - int maximumLabels, - int minorTicksPerInterval, - bool isInversed, - bool opposedPosition, - EdgeLabelPlacement edgeLabelPlacement, - bool enableAutoIntervalOnZooming, - double zoomFactor, - double zoomPosition, - InteractiveTooltip interactiveTooltip, + MajorGridLines? majorGridLines, + MinorGridLines? minorGridLines, + int? maximumLabels, + int? minorTicksPerInterval, + bool? isInversed, + bool? opposedPosition, + EdgeLabelPlacement? edgeLabelPlacement, + bool? enableAutoIntervalOnZooming, + double? zoomFactor, + double? zoomPosition, + InteractiveTooltip? interactiveTooltip, this.interval, this.crossesAt, this.associatedAxisName, - bool placeLabelsNearAxisLine, - List plotBands, + bool? placeLabelsNearAxisLine, + List? plotBands, this.rangeController, - double maximumLabelWidth, - double labelsExtent, + double? maximumLabelWidth, + double? labelsExtent, + int? autoScrollingDelta, + AutoScrollingMode? autoScrollingMode, }) : isVisible = isVisible ?? true, anchorRangeToVisiblePoints = anchorRangeToVisiblePoints ?? true, interactiveTooltip = interactiveTooltip ?? InteractiveTooltip(), @@ -79,7 +81,9 @@ abstract class ChartAxis { enableAutoIntervalOnZooming = enableAutoIntervalOnZooming ?? true, plotBands = plotBands ?? [], maximumLabelWidth = maximumLabelWidth, - labelsExtent = labelsExtent; + labelsExtent = labelsExtent, + autoScrollingDelta = autoScrollingDelta, + autoScrollingMode = autoScrollingMode ?? AutoScrollingMode.end; ///Toggles the visibility of the axis. /// @@ -103,7 +107,7 @@ abstract class ChartAxis { /// ///By default, value axis range will be calculated automatically based on the visible data points on /// dynamic changes. The visible data points are changed on performing interactions like pinch - /// zooming, selection zooming, panning and also on specifying [visibleMinimum] and [visibleMaximum] values. + /// zooming, selection zooming, panning and also on specifying `visibleMinimum` and `visibleMaximum` values. /// ///To toggle this functionality, this property can be used. i.e. on setting false to this property, /// value axis range will be calculated based on all the data points in chart irrespective of @@ -256,7 +260,7 @@ abstract class ChartAxis { /// )); ///} ///``` - final int desiredIntervals; + final int? desiredIntervals; ///The maximum number of labels to be displayed in an axis in 100 logical pixels. /// @@ -447,7 +451,7 @@ abstract class ChartAxis { /// )); ///} ///``` - final double interval; + final double? interval; ///Padding for plot area. The axis is rendered in chart with padding. /// @@ -478,7 +482,7 @@ abstract class ChartAxis { /// )); ///} ///``` - final String name; + final String? name; ///Zoom factor of an axis. /// @@ -560,7 +564,7 @@ abstract class ChartAxis { /// )); ///} ///``` - final String associatedAxisName; + final String? associatedAxisName; ///Consider to place the axis label respect to near axis line. /// @@ -601,7 +605,7 @@ abstract class ChartAxis { /// )); ///} ///``` - final RangeController rangeController; + final RangeController? rangeController; /// Specifies maximum text width for axis labels. /// @@ -634,7 +638,7 @@ abstract class ChartAxis { /// ); ///} ///``` - final double maximumLabelWidth; + final double? maximumLabelWidth; ///Specifies the fixed width for the axis labels. This width represents the space between axis line and /// axis title. @@ -672,7 +676,55 @@ abstract class ChartAxis { /// ); ///} ///``` - final double labelsExtent; + final double? labelsExtent; + + ///The number of data points to be visible always in the chart. + /// + ///For example, if there are 10 data points and `autoScrollingDelta` value is 5 and [autoScrollingMode] + /// is `AutoScrollingMode.end`, the last 5 data points will be displayed in the chart and remaining + /// data points can be viewed by panning the chart from left to right direction. If the [autoScrollingMode] + /// is `AutoScrollingMode.start`, first 5 points will be displayed and remaining data points can be + /// viewed by panning the chart from right to left direction. + /// + ///If the data points are less than the specified `autoScrollingDelta` value, all those data points will + /// be displayed. + /// + ///It always shows the recently added data points and scrolling will be reset to the start or end of + /// the range, based on [autoScrollingMode] property's value, whenever a new point is added dynamically. + /// + ///Defaults to `null`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// autoScrollingDelta: 3, + /// ), + /// )); + ///} + ///``` + final int? autoScrollingDelta; + + ///Determines whether the axis should be scrolled from the start position or end position. + /// + ///For example, if there are 10 data points and [autoScrollingDelta] value is 5 and `AutoScrollingMode.end` + /// is specified to this property, last 5 points will be displayed in the chart. If `AutoScrollingMode.start` + /// is set to this property, first 5 points will be displayed. + /// + ///Defaults to `AutoScrollingMode.end`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// autoScrollingMode: AutoScrollingMode.start, + /// ), + /// )); + ///} + ///``` + final AutoScrollingMode autoScrollingMode; } /// Holds the axis label information. @@ -708,20 +760,20 @@ class AxisLabel { String text; /// Contains the trimmed text of the label. - String trimmedText; + String? trimmedText; /// Contains the label text to be rendered. - String renderText; + String? renderText; /// Holds the value of the visible range of the axis. num value; - List _labelCollection; + List? _labelCollection; int _index = 1; ///Stores the location of an label. - Rect _labelRegion; + Rect? _labelRegion; //ignore: prefer_final_fields bool _needRender = true; @@ -736,7 +788,7 @@ class AxisLabel { /// class MajorTickLines { /// Creating an argument constructor of MajorTickLines class. - MajorTickLines({this.size = 5, this.width = 1, this.color}); + const MajorTickLines({this.size = 5, this.width = 1, this.color}); ///Size of the major tick lines. /// @@ -792,7 +844,7 @@ class MajorTickLines { /// )); ///} ///``` - final Color color; + final Color? color; } /// This class has the properties of minor tick lines. @@ -804,7 +856,7 @@ class MajorTickLines { /// class MinorTickLines { /// Creating an argument constructor of MinorTickLines class. - MinorTickLines({this.size = 3, this.width = 0.7, this.color}); + const MinorTickLines({this.size = 3, this.width = 0.7, this.color}); ///Height of the minor tick lines. /// @@ -861,7 +913,7 @@ class MinorTickLines { /// )); ///} ///``` - final Color color; + final Color? color; } /// Customizes the major grid lines. @@ -875,7 +927,7 @@ class MinorTickLines { /// class MajorGridLines { /// Creating an argument constructor of MajorGridLines class. - MajorGridLines({this.width = 0.7, this.color, this.dashArray}); + const MajorGridLines({this.width = 0.7, this.color, this.dashArray}); ///Dashes of the major grid lines. /// @@ -892,7 +944,7 @@ class MajorGridLines { /// )); ///} ///``` - final List dashArray; + final List? dashArray; /// Width of the major grid lines. /// @@ -926,7 +978,7 @@ class MajorGridLines { /// )); ///} ///``` - final Color color; + final Color? color; } /// Customizes the minor grid lines. @@ -938,7 +990,7 @@ class MajorGridLines { /// class MinorGridLines { /// Creating an argument constructor of MinorGridLines class. - MinorGridLines({this.width = 0.5, this.color, this.dashArray}); + const MinorGridLines({this.width = 0.5, this.color, this.dashArray}); ///Dashes of minor grid lines. /// @@ -957,7 +1009,7 @@ class MinorGridLines { /// )); ///} ///``` - final List dashArray; + final List? dashArray; ///Width of the minor grid lines. /// @@ -995,7 +1047,7 @@ class MinorGridLines { /// )); ///} ///``` - final Color color; + final Color? color; } /// This class holds the property of the axis title. @@ -1008,7 +1060,7 @@ class MinorGridLines { class AxisTitle { /// Creating an argument constructor of AxisTitle class. AxisTitle( - {this.text, TextStyle textStyle, this.alignment = ChartAlignment.center}) + {this.text, TextStyle? textStyle, this.alignment = ChartAlignment.center}) : textStyle = _getTextStyle( textStyle: textStyle, fontFamily: 'Segoe UI', @@ -1031,7 +1083,7 @@ class AxisTitle { /// )); ///} ///``` - final String text; + final String? text; ///Customizes the appearance of text in axis title. /// @@ -1091,7 +1143,7 @@ class AxisTitle { /// class AxisLine { /// Creating an argument constructor of AxisLine class. - AxisLine({this.color, this.dashArray, this.width = 1}); + const AxisLine({this.color, this.dashArray, this.width = 1}); ///Width of the axis line. /// @@ -1125,7 +1177,7 @@ class AxisLine { /// )); ///} ///``` - final Color color; + final Color? color; ///Dashes of the axis line. Any number of values can be provided in the list. Odd value is ///considered as rendering size and even value is considered as gap. @@ -1144,7 +1196,7 @@ class AxisLine { /// )); ///} ///``` - final List dashArray; + final List? dashArray; } ///calculate visible range based on min, max values @@ -1169,7 +1221,7 @@ class _VisibleRange { dynamic interval; /// Specifies delta value for min-max - num delta; + late num delta; } /// Creates an axis renderer for chart axis. @@ -1185,71 +1237,74 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { _zoomPosition = _axis.zoomPosition; } //ignore: prefer_final_fields - ChartAxis _axis; - SfCartesianChartState _chartState; - SfCartesianChart _chart; + late ChartAxis _axis; + ChartAxis? _oldAxis; + late SfCartesianChartState _chartState; + late SfCartesianChart _chart; //ignore: prefer_final_fields bool _isStack100 = false; dynamic _rangeMinimum, _rangeMaximum; - List _visibleLabels; + late List _visibleLabels; /// Holds the size of larger label. Size _maximumLabelSize = const Size(0, 0); /// Specifies axis orientations such as vertical, horizontal. - AxisOrientation _orientation; + AxisOrientation? _orientation; /// Specifies the visible range based on min, max values. - _VisibleRange _visibleRange; + _VisibleRange? _visibleRange; /// Specifies the actual range based on min, max values. - _VisibleRange _actualRange; + _VisibleRange? _actualRange; /// Holds the chart series - List _seriesRenderers; + late List _seriesRenderers; // ignore: prefer_final_fields Rect _bounds = const Rect.fromLTWH(0, 0, 0, 0); - bool _isInsideTickPosition; + bool? _isInsideTickPosition; - double _totalSize; + late double _totalSize; ///Internal variable - double _previousZoomFactor, _previousZoomPosition; + double? _previousZoomFactor, _previousZoomPosition; ///Internal variables - String _name; + String? _name; - int _labelRotation; + late int _labelRotation; - double _zoomFactor, _zoomPosition; + late double _zoomFactor, _zoomPosition; ///Checking the axis label collision bool _isCollide = false; - num _min, _max, _lowMin, _lowMax, _highMin, _highMax; + num? _min, _max, _lowMin, _lowMax, _highMin, _highMax; - Size _axisSize; + late Size _axisSize; - ChartAxisRenderer _crossAxisRenderer; + late ChartAxisRenderer _crossAxisRenderer; - num _crossValue; + num? _crossValue; - _VisibleRange _crossRange; + _VisibleRange? _crossRange; - num _labelOffset; + num? _labelOffset; - Offset _xAxisStart, _xAxisEnd; + Offset? _xAxisStart, _xAxisEnd; + + num? _visibleMinimum, _visibleMaximum; @override - Color getAxisLineColor(ChartAxis axis) => axis.axisLine.color; + Color? getAxisLineColor(ChartAxis axis) => axis.axisLine.color; @override double getAxisLineWidth(ChartAxis axis) => axis.axisLine.width; @override - Color getAxisMajorTickColor(ChartAxis axis, int majorTickIndex) => + Color? getAxisMajorTickColor(ChartAxis axis, int majorTickIndex) => axis.majorTickLines.color; @override @@ -1257,7 +1312,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { axis.majorTickLines.width; @override - Color getAxisMinorTickColor( + Color? getAxisMinorTickColor( ChartAxis axis, int majorTickIndex, int minorTickIndex) => axis.minorTickLines.color; @@ -1267,7 +1322,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { axis.minorTickLines.width; @override - Color getAxisMajorGridColor(ChartAxis axis, int majorGridIndex) => + Color? getAxisMajorGridColor(ChartAxis axis, int majorGridIndex) => axis.majorGridLines.color; @override @@ -1275,7 +1330,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { axis.majorGridLines.width; @override - Color getAxisMinorGridColor( + Color? getAxisMinorGridColor( ChartAxis axis, int majorGridIndex, int minorGridIndex) => axis.minorGridLines.color; @override @@ -1349,40 +1404,43 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { @override void drawHorizontalAxesTickLines( Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String renderType, - double animationFactor, - ChartAxisRenderer oldAxisRenderer, - bool needAnimate]) { + [String? renderType, + double? animationFactor, + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate]) { final Rect axisBounds = axisRenderer._bounds; - final ChartAxis axis = axisRenderer._axis; + final dynamic axis = axisRenderer._axis; final List visibleLabels = axisRenderer._visibleLabels; - num tempInterval, pointX, pointY, length = visibleLabels.length; + late double tempInterval, pointX, pointY; + int length = visibleLabels.length; if (length > 0) { final MajorTickLines ticks = axis.majorTickLines; const num padding = 1; - final bool isBetweenTicks = axis is CategoryAxis && - axis.labelPlacement == LabelPlacement.betweenTicks; + final bool isBetweenTicks = + (axis is CategoryAxis || axis is DateTimeCategoryAxis) && + axis.labelPlacement == LabelPlacement.betweenTicks; final num tickBetweenLabel = isBetweenTicks ? 0.5 : 0; length += isBetweenTicks ? 1 : 0; for (int i = 0; i < length; i++) { - tempInterval = isBetweenTicks - ? i < length - 1 - ? visibleLabels[i].value - tickBetweenLabel - : (visibleLabels[i - 1].value + - axisRenderer._visibleRange.interval) - - tickBetweenLabel - : visibleLabels[i].value; + tempInterval = (isBetweenTicks + ? i < length - 1 + ? visibleLabels[i].value - tickBetweenLabel + : (visibleLabels[i - 1].value + + axisRenderer._visibleRange!.interval) - + tickBetweenLabel + : visibleLabels[i].value) + .toDouble(); pointX = ((_valueToCoefficient(tempInterval, axisRenderer) * axisBounds.width) + axisBounds.left) .roundToDouble(); pointY = axisBounds.top - padding + axis.axisLine.width / 2; - if (needAnimate) { - final double oldLocation = - _getPrevLocation(axisRenderer, oldAxisRenderer, tempInterval); + if (needAnimate!) { + final double? oldLocation = + _getPrevLocation(axisRenderer, oldAxisRenderer!, tempInterval); pointX = oldLocation != null - ? (oldLocation - (oldLocation - pointX) * animationFactor) + ? (oldLocation - (oldLocation - pointX) * animationFactor!) : pointX; } if (axisBounds.left.roundToDouble() <= pointX && @@ -1404,11 +1462,11 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { chart); } if (axis.minorGridLines.width > 0 || axis.minorTickLines.width > 0) { - num nextValue = isBetweenTicks - ? (tempInterval + axisRenderer._visibleRange.interval) + num? nextValue = isBetweenTicks + ? (tempInterval + axisRenderer._visibleRange!.interval) : i == length - 1 - ? axisRenderer._visibleRange.maximum - : visibleLabels[i + 1]?.value; + ? axisRenderer._visibleRange!.maximum + : visibleLabels[i + 1].value; if (nextValue != null) { nextValue = ((_valueToCoefficient(nextValue, axisRenderer) * axisBounds.width) + @@ -1434,10 +1492,10 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { Offset( pointX, !axis.opposedPosition - ? (axisRenderer._isInsideTickPosition + ? (axisRenderer._isInsideTickPosition! ? pointY - ticks.size : pointY + ticks.size) - : (axisRenderer._isInsideTickPosition + : (axisRenderer._isInsideTickPosition! ? pointY + ticks.size : pointY - ticks.size))); } @@ -1475,13 +1533,13 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { void drawHorizontalAxesMinorLines( Canvas canvas, ChartAxisRenderer axisRenderer, - num tempInterval, + double tempInterval, Rect rect, num nextValue, int index, SfCartesianChart chart, - [String renderType]) { - num position = tempInterval; + [String? renderType]) { + double position = tempInterval; final ChartAxis axis = axisRenderer._axis; final MinorTickLines ticks = axis.minorTickLines; final num interval = @@ -1489,7 +1547,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { for (int i = 0; i < axis.minorTicksPerInterval; i++) { position = axis.isInversed ? (position - interval) : (position + interval); - final num pointY = rect.top; + final double pointY = rect.top; if (axis.minorGridLines.width > 0 && renderType == 'outside' && (axisRenderer._bounds.left <= position && @@ -1526,10 +1584,10 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { Offset( position, !axis.opposedPosition - ? (axisRenderer._isInsideTickPosition + ? (axisRenderer._isInsideTickPosition! ? pointY - ticks.size : pointY + ticks.size) - : (axisRenderer._isInsideTickPosition + : (axisRenderer._isInsideTickPosition! ? pointY + ticks.size : pointY - ticks.size)), axis.minorGridLines.dashArray); @@ -1541,27 +1599,30 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { @override void drawVerticalAxesTickLines( Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String renderType, - double animationFactor, - ChartAxisRenderer oldAxisRenderer, - bool needAnimate]) { - final ChartAxis axis = axisRenderer._axis; + [String? renderType, + double? animationFactor, + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate]) { + final dynamic axis = axisRenderer._axis; final Rect axisBounds = axisRenderer._bounds; final List visibleLabels = axisRenderer._visibleLabels; - num tempInterval, pointX, pointY, length = visibleLabels.length; + double tempInterval, pointX, pointY; + int length = visibleLabels.length; const num padding = 1; - final bool isBetweenTicks = axis is CategoryAxis && - axis.labelPlacement == LabelPlacement.betweenTicks; + final bool isBetweenTicks = + (axis is CategoryAxis || axis is DateTimeCategoryAxis) && + axis.labelPlacement == LabelPlacement.betweenTicks; final num tickBetweenLabel = isBetweenTicks ? 0.5 : 0; length += isBetweenTicks ? 1 : 0; for (int i = 0; i < length; i++) { - tempInterval = isBetweenTicks - ? i < length - 1 - ? visibleLabels[i].value - tickBetweenLabel - : (visibleLabels[i - 1].value + - axisRenderer._visibleRange.interval) - - tickBetweenLabel - : visibleLabels[i].value; + tempInterval = (isBetweenTicks + ? i < length - 1 + ? visibleLabels[i].value - tickBetweenLabel + : (visibleLabels[i - 1].value + + axisRenderer._visibleRange!.interval) - + tickBetweenLabel + : visibleLabels[i].value) + .toDouble(); pointY = (_valueToCoefficient(tempInterval, axisRenderer) * axisBounds.height) + axisBounds.top; @@ -1569,11 +1630,11 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { (pointY - axisBounds.top).abs(); pointX = axisBounds.left + padding - axis.axisLine.width / 2; - if (needAnimate) { - final double oldLocation = - _getPrevLocation(axisRenderer, oldAxisRenderer, tempInterval); + if (needAnimate!) { + final double? oldLocation = + _getPrevLocation(axisRenderer, oldAxisRenderer!, tempInterval); pointY = oldLocation != null - ? (oldLocation - (oldLocation - pointY) * animationFactor) + ? (oldLocation - (oldLocation - pointY) * animationFactor!) : pointY; } if (pointY >= axisBounds.top && pointY <= axisBounds.bottom) { @@ -1597,7 +1658,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { } if (axis.minorGridLines.width > 0 || axis.minorTickLines.width > 0) { axisRenderer.drawVerticalAxesMinorTickLines(canvas, axisRenderer, - tempInterval, axisBounds, i, chart, renderType); + tempInterval, axisBounds, i, chart, renderType!); } if (axis.majorTickLines.width > 0 && renderType == axis.tickPosition.toString().split('.')[1]) { @@ -1611,10 +1672,10 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { Offset(pointX, pointY), Offset( !axis.opposedPosition - ? (axisRenderer._isInsideTickPosition + ? (axisRenderer._isInsideTickPosition! ? pointX + axis.majorTickLines.size : pointX - axis.majorTickLines.size) - : (axisRenderer._isInsideTickPosition + : (axisRenderer._isInsideTickPosition! ? pointX - axis.majorTickLines.size : pointX + axis.majorTickLines.size), pointY)); @@ -1665,10 +1726,11 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { Rect rect, int index, SfCartesianChart chart, - [String renderType]) { + [String? renderType]) { final ChartAxis axis = axisRenderer._axis; - num value = tempInterval, position = 0; - final _VisibleRange range = axisRenderer._visibleRange; + num value = tempInterval; + double position = 0; + final _VisibleRange range = axisRenderer._visibleRange!; final bool rendering = axis.minorTicksPerInterval > 0 && (axis.minorGridLines.width > 0 || axis.minorTickLines.width > 0); if (rendering) { @@ -1707,10 +1769,10 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { Offset(rect.left, position), Offset( !axis.opposedPosition - ? (axisRenderer._isInsideTickPosition + ? (axisRenderer._isInsideTickPosition! ? rect.left + axis.minorTickLines.size : rect.left - axis.minorTickLines.size) - : (axisRenderer._isInsideTickPosition + : (axisRenderer._isInsideTickPosition! ? rect.left - axis.minorTickLines.size : rect.left + axis.minorTickLines.size), position)); @@ -1724,28 +1786,28 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { @override void drawHorizontalAxesLabels( Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String renderType, - double animationFactor, - ChartAxisRenderer oldAxisRenderer, - bool needAnimate]) { + [String? renderType, + double? animationFactor, + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate]) { final ChartAxis axis = axisRenderer._axis; if (renderType == axis.labelPosition.toString().split('.')[1]) { final Rect axisBounds = axisRenderer._bounds; int angle; TextStyle textStyle; final List visibleLabels = axisRenderer._visibleLabels; - num tempInterval, pointX, pointY, previousLabelEnd; + late double tempInterval, pointX, pointY, previousLabelEnd; for (int i = 0; i < visibleLabels.length; i++) { final AxisLabel label = visibleLabels[i]; final String labelText = - axisRenderer.getAxisLabel(axis, label.renderText, i); + axisRenderer.getAxisLabel(axis, label.renderText!, i); textStyle = label.labelStyle; textStyle = _getTextStyle( textStyle: textStyle, fontColor: textStyle.color ?? _chartState._chartTheme.axisLabelColor); - tempInterval = label.value; + tempInterval = label.value.toDouble(); angle = axisRenderer.getAxisLabelAngle(axisRenderer, labelText, i); /// For negative angle calculations @@ -1753,8 +1815,8 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { angle = angle + 360; } axisRenderer._labelRotation = angle; - final Size textSize = _measureText(labelText, textStyle); - final Size rotatedTextSize = _measureText(labelText, textStyle, angle); + final Size textSize = measureText(labelText, textStyle); + final Size rotatedTextSize = measureText(labelText, textStyle, angle); pointX = ((_valueToCoefficient(tempInterval, axisRenderer) * axisBounds.width) + axisBounds.left) @@ -1804,11 +1866,11 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { previousLabelEnd = axis.isInversed ? pointX : pointX + textSize.width; - if (needAnimate) { - final double oldLocation = _getPrevLocation( - axisRenderer, oldAxisRenderer, tempInterval, textSize, angle); + if (needAnimate!) { + final double? oldLocation = _getPrevLocation( + axisRenderer, oldAxisRenderer!, tempInterval, textSize, angle); pointX = oldLocation != null - ? (oldLocation - (oldLocation - pointX) * animationFactor) + ? (oldLocation - (oldLocation - pointX) * animationFactor!) : pointX; } final Offset point = Offset(pointX, pointY); @@ -1817,10 +1879,10 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { _drawText(canvas, labelText, point, textStyle, angle); } if (label._labelCollection != null && - label._labelCollection.isNotEmpty && + label._labelCollection!.isNotEmpty && axis.labelIntersectAction == AxisLabelIntersectAction.wrap) { - for (int j = 1; j < label._labelCollection.length; j++) { - final String wrapTxt = label._labelCollection[j]; + for (int j = 1; j < label._labelCollection!.length; j++) { + final String wrapTxt = label._labelCollection![j]; _drawText( canvas, wrapTxt, @@ -1828,7 +1890,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { pointX, pointY + (j * - _measureText(wrapTxt, axis.labelStyle, angle) + measureText(wrapTxt, axis.labelStyle, angle) .height)), textStyle, angle); @@ -1842,19 +1904,19 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { @override void drawVerticalAxesLabels( Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String renderType, - double animationFactor, - ChartAxisRenderer oldAxisRenderer, - bool needAnimate]) { + [String? renderType, + double? animationFactor, + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate]) { final ChartAxis axis = axisRenderer._axis; if (axis.labelPosition.toString().split('.')[1] == renderType) { final Rect axisBounds = axisRenderer._bounds; final List visibleLabels = axisRenderer._visibleLabels; TextStyle textStyle; - num tempInterval, pointX, pointY, previousEnd; + late double tempInterval, pointX, pointY, previousEnd; for (int i = 0; i < visibleLabels.length; i++) { final String labelText = - axisRenderer.getAxisLabel(axis, visibleLabels[i].renderText, i); + axisRenderer.getAxisLabel(axis, visibleLabels[i].renderText!, i); final int angle = axisRenderer.getAxisLabelAngle(axisRenderer, labelText, i); assert(angle - angle.floor() == 0, @@ -1864,8 +1926,8 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { textStyle: textStyle, fontColor: textStyle.color ?? _chartState._chartTheme.axisLabelColor); - tempInterval = visibleLabels[i].value; - final Size textSize = _measureText(labelText, textStyle, 0); + tempInterval = visibleLabels[i].value.toDouble(); + final Size textSize = measureText(labelText, textStyle, 0); pointY = (_valueToCoefficient(tempInterval, axisRenderer) * axisBounds.height) + axisBounds.top; @@ -1934,11 +1996,11 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { axisRenderer._visibleLabels[i]._labelRegion = Rect.fromLTWH(pointX, pointY, textSize.width, textSize.height); - if (needAnimate) { - final double oldLocation = _getPrevLocation( - axisRenderer, oldAxisRenderer, tempInterval, textSize); + if (needAnimate!) { + final double? oldLocation = _getPrevLocation( + axisRenderer, oldAxisRenderer!, tempInterval, textSize); pointY = oldLocation != null - ? (oldLocation - (oldLocation - pointY) * animationFactor) + ? (oldLocation - (oldLocation - pointY) * animationFactor!) : pointY; } @@ -1953,14 +2015,14 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { } /// To get the previous location of an axis - double _getPrevLocation(ChartAxisRenderer axisRenderer, + double? _getPrevLocation(ChartAxisRenderer axisRenderer, ChartAxisRenderer oldAxisRenderer, num value, - [Size textSize, num angle]) { - double location; + [Size? textSize, num? angle]) { + double? location; final Rect bounds = axisRenderer._bounds; final ChartAxis axis = axisRenderer._axis; textSize ??= const Size(0, 0); - if (oldAxisRenderer._visibleRange.minimum > value) { + if (oldAxisRenderer._visibleRange!.minimum > value) { location = axisRenderer._orientation == AxisOrientation.vertical ? (axis.isInversed ? ((bounds.top + bounds.height) - @@ -1981,7 +2043,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { : ((_valueToCoefficient(value, oldAxisRenderer) * bounds.width) - bounds.left) .roundToDouble()); - } else if (oldAxisRenderer._visibleRange.maximum < value) { + } else if (oldAxisRenderer._visibleRange!.maximum < value) { location = axisRenderer._orientation == AxisOrientation.vertical ? (axis.isInversed ? (bounds.bottom - @@ -2027,38 +2089,39 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { /// Return the x point double _getPointX( ChartAxisRenderer axisRenderer, Size textSize, Rect axisBounds) { - num pointX; + late double pointX; const num innerPadding = 5; final ChartAxis axis = axisRenderer._axis; if (axis.labelPosition == ChartDataLabelPosition.inside) { pointX = (!axis.opposedPosition) ? (axisBounds.left + innerPadding + - (axisRenderer._isInsideTickPosition + (axisRenderer._isInsideTickPosition! ? axis.majorTickLines.size : 0)) : (axisBounds.left - axisRenderer._maximumLabelSize.width - innerPadding - - (axisRenderer._isInsideTickPosition + (axisRenderer._isInsideTickPosition! ? axis.majorTickLines.size : 0)); } else { - pointX = (!axis.opposedPosition) - ? axisRenderer._labelOffset != null - ? axisRenderer._labelOffset - textSize.width - : (axisBounds.left - - (axisRenderer._isInsideTickPosition - ? 0 - : axis.majorTickLines.size) - - textSize.width - - innerPadding) - : (axisRenderer._labelOffset ?? - (axisBounds.left + - (axisRenderer._isInsideTickPosition - ? 0 - : axis.majorTickLines.size) + - innerPadding)); + pointX = ((!axis.opposedPosition) + ? axisRenderer._labelOffset != null + ? axisRenderer._labelOffset! - textSize.width + : (axisBounds.left - + (axisRenderer._isInsideTickPosition! + ? 0 + : axis.majorTickLines.size) - + textSize.width - + innerPadding) + : (axisRenderer._labelOffset ?? + (axisBounds.left + + (axisRenderer._isInsideTickPosition! + ? 0 + : axis.majorTickLines.size) + + innerPadding))) + .toDouble(); } return pointX; } @@ -2076,39 +2139,40 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { (label._index > 1 ? axisRenderer._maximumLabelSize.height / 2 : axisRenderer._maximumLabelSize.height) - - (axisRenderer._isInsideTickPosition + (axisRenderer._isInsideTickPosition! ? axis.majorTickLines.size : 0) : axisBounds.top + - (axisRenderer._isInsideTickPosition + (axisRenderer._isInsideTickPosition! ? axis.majorTickLines.size : 0) + (label._index > 1 ? axisRenderer._maximumLabelSize.height / 2 : 0); } else { - pointY = !axis.opposedPosition - ? axisRenderer._labelOffset ?? - (axisBounds.top + - ((axisRenderer._isInsideTickPosition - ? 0 - : axis.majorTickLines.size) + - innerPadding) + - (label._index > 1 - ? axisRenderer._maximumLabelSize.height / 2 - : 0)) - : axisRenderer._labelOffset != null - ? axisRenderer._labelOffset - - axisRenderer._maximumLabelSize.height - : (axisBounds.top - - (((axisRenderer._isInsideTickPosition + pointY = (!axis.opposedPosition + ? axisRenderer._labelOffset ?? + (axisBounds.top + + ((axisRenderer._isInsideTickPosition! ? 0 : axis.majorTickLines.size) + - innerPadding) - + innerPadding) + (label._index > 1 ? axisRenderer._maximumLabelSize.height / 2 - : 0)) - - axisRenderer._maximumLabelSize.height); + : 0)) + : axisRenderer._labelOffset != null + ? axisRenderer._labelOffset! - + axisRenderer._maximumLabelSize.height + : (axisBounds.top - + (((axisRenderer._isInsideTickPosition! + ? 0 + : axis.majorTickLines.size) + + innerPadding) - + (label._index > 1 + ? axisRenderer._maximumLabelSize.height / 2 + : 0)) - + axisRenderer._maximumLabelSize.height)) + .toDouble(); } return pointY; } @@ -2166,9 +2230,9 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { axisRenderer._visibleLabels[i - 1]._labelRegion != null && (axisRenderer._axis.isInversed == false ? currentRegion.left < - axisRenderer._visibleLabels[i - 1]._labelRegion.right + axisRenderer._visibleLabels[i - 1]._labelRegion!.right : currentRegion.right > - axisRenderer._visibleLabels[i - 1]._labelRegion.left); + axisRenderer._visibleLabels[i - 1]._labelRegion!.left); axisRenderer._visibleLabels[i]._labelRegion = !isIntersect ? currentRegion : null; return pointX; @@ -2187,17 +2251,17 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { style = _getTextStyle( textStyle: style, fontColor: style.color ?? _chartState._chartTheme.axisTitleColor); - final Size textSize = _measureText(title, style); - num top; + final Size textSize = measureText(title, style); + double top; if (axis.labelPosition == ChartDataLabelPosition.inside) { top = !axis.opposedPosition ? axisBounds.top + - (axisRenderer._isInsideTickPosition + (axisRenderer._isInsideTickPosition! ? 0 : axis.majorTickLines.size) + (!kIsWeb ? innerPadding : innerPadding + textSize.height / 2) : axisBounds.top - - (axisRenderer._isInsideTickPosition + (axisRenderer._isInsideTickPosition! ? 0 : axis.majorTickLines.size) - innerPadding - @@ -2205,7 +2269,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { } else { top = !axis.opposedPosition ? axisBounds.top + - (axisRenderer._isInsideTickPosition + (axisRenderer._isInsideTickPosition! ? 0 : axis.majorTickLines.size) + innerPadding + @@ -2213,7 +2277,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { ? axisRenderer._maximumLabelSize.height : axisRenderer._maximumLabelSize.height + textSize.height / 2) : axisBounds.top - - (axisRenderer._isInsideTickPosition + (axisRenderer._isInsideTickPosition! ? 0 : axis.majorTickLines.size) - innerPadding - @@ -2250,32 +2314,32 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { style = _getTextStyle( textStyle: style, fontColor: style.color ?? _chartState._chartTheme.axisTitleColor); - final Size textSize = _measureText(title, style); - num left; + final Size textSize = measureText(title, style); + double left; if (axis.labelPosition == ChartDataLabelPosition.inside) { left = (!axis.opposedPosition) ? axisBounds.left - - (axisRenderer._isInsideTickPosition + (axisRenderer._isInsideTickPosition! ? 0 : axis.majorTickLines.size) - innerPadding - textSize.height : axisBounds.left + - (axisRenderer._isInsideTickPosition + (axisRenderer._isInsideTickPosition! ? 0 : axis.majorTickLines.size) + innerPadding * 2; } else { left = (!axis.opposedPosition) ? (axisBounds.left - - (axisRenderer._isInsideTickPosition + (axisRenderer._isInsideTickPosition! ? 0 : axis.majorTickLines.size) - innerPadding - axisRenderer._maximumLabelSize.width - textSize.height / 2) : axisBounds.left + - (axisRenderer._isInsideTickPosition + (axisRenderer._isInsideTickPosition! ? 0 : axis.majorTickLines.size) + innerPadding + @@ -2296,7 +2360,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { } /// returns the calculated interval for axis - num calculateInterval(_VisibleRange range, Size availableSize); + num? calculateInterval(_VisibleRange range, Size availableSize); /// to apply the range padding for the axis void applyRangePadding(_VisibleRange range, num interval); @@ -2315,7 +2379,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { CartesianSeriesRenderer seriesRenderer; double paddingInterval = 0; ChartAxisRenderer _xAxisRenderer, _yAxisRenderer; - num minimumX, maximumX, minimumY, maximumY; + num? minimumX, maximumX, minimumY, maximumY; String seriesType; seriesRenderers = _seriesRenderers; for (int i = 0; i < seriesRenderers.length; i++) { @@ -2325,17 +2389,16 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { minimumY = seriesRenderer._minimumY; maximumY = seriesRenderer._maximumY; seriesType = seriesRenderer._seriesType; - if (seriesRenderer._visible && + if (seriesRenderer._visible! && minimumX != null && maximumX != null && minimumY != null && maximumY != null) { paddingInterval = 0; - _xAxisRenderer = seriesRenderer._xAxisRenderer; - _yAxisRenderer = seriesRenderer._yAxisRenderer; + _xAxisRenderer = seriesRenderer._xAxisRenderer!; + _yAxisRenderer = seriesRenderer._yAxisRenderer!; if (((_xAxisRenderer is DateTimeAxisRenderer || _xAxisRenderer is NumericAxisRenderer) && - _xAxisRenderer._axis.rangePadding != null && _xAxisRenderer._axis.rangePadding == ChartRangePadding.auto) && (seriesType.contains('column') || seriesType.contains('bar') || @@ -2343,7 +2406,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { seriesRenderer._minDelta = seriesRenderer._minDelta ?? _calculateMinPointsDelta( _xAxisRenderer, seriesRenderers, _chartState); - paddingInterval = seriesRenderer._minDelta / 2; + paddingInterval = seriesRenderer._minDelta! / 2; } if (((_chartState._requireInvertedAxis ? _yAxisRenderer @@ -2372,10 +2435,10 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { /// Find min and max values void _findMinMax(num minVal, num maxVal) { - if (_min == null || _min > minVal) { + if (_min == null || _min! > minVal) { _min = minVal; } - if (_max == null || _max < maxVal) { + if (_max == null || _max! < maxVal) { _max = maxVal; } } @@ -2424,7 +2487,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { desiredIntervalCount = math.max(size * (desiredIntervalCount / 100), 1); return desiredIntervalCount; } else { - return axisRenderer._axis.desiredIntervals; + return axisRenderer._axis.desiredIntervals!; } } @@ -2432,12 +2495,12 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { ChartRangePadding _calculateRangePadding( ChartAxisRenderer axisRenderer, SfCartesianChart chart) { final ChartAxis axis = axisRenderer._axis; - ChartRangePadding padding; + ChartRangePadding padding = ChartRangePadding.auto; if (axis.rangePadding != ChartRangePadding.auto) { padding = axis.rangePadding; } else if (axis.rangePadding == ChartRangePadding.auto && axisRenderer._orientation != null) { - switch (axisRenderer._orientation) { + switch (axisRenderer._orientation!) { case AxisOrientation.horizontal: padding = _chartState._requireInvertedAxis ? (_isStack100 @@ -2496,20 +2559,31 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { void _updateActualRange( ChartAxisRenderer axisRenderer, num minimum, num maximum, num interval) { final dynamic axis = axisRenderer._axis; - axisRenderer._actualRange.minimum = axis.minimum ?? minimum; - axisRenderer._actualRange.maximum = axis.maximum ?? maximum; - axisRenderer._actualRange.delta = - axisRenderer._actualRange.maximum - axisRenderer._actualRange.minimum; - axisRenderer._actualRange.interval = axis.interval ?? interval; + axisRenderer._actualRange!.minimum = axis.minimum == null + ? minimum + : (axisRenderer is DateTimeCategoryAxisRenderer + ? (axisRenderer._getEffectiveRange(axis.minimum, true)! - + (axis.labelPlacement != LabelPlacement.onTicks ? 0 : 0.5)) + : axis.minimum); + axisRenderer._actualRange!.maximum = axis.maximum == null + ? maximum + : (axisRenderer is DateTimeCategoryAxisRenderer + ? (axisRenderer._getEffectiveRange(axis.maximum, true)! + + (axis.labelPlacement != LabelPlacement.onTicks ? 0 : 0.5)) + : axis.maximum); + axisRenderer._actualRange!.delta = + axisRenderer._actualRange!.maximum - axisRenderer._actualRange!.minimum; + axisRenderer._actualRange!.interval = axis.interval ?? interval; } /// Find the normal range void _findNormalRange( ChartAxisRenderer axisRenderer, num start, num end, num interval) { - final ChartAxis axis = axisRenderer._axis; + final dynamic axis = axisRenderer._axis; num remaining, minimum, maximum; num startValue = start; - if (axis is CategoryAxis && axis.labelPlacement == LabelPlacement.onTicks) { + if ((axis is CategoryAxis || axis is DateTimeCategoryAxis) && + axis.labelPlacement == LabelPlacement.onTicks) { minimum = start - 0.5; maximum = end + 0.5; } else { @@ -2545,7 +2619,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { interval = (axisRenderer is NumericAxisRenderer) ? _calculateNumericNiceInterval( axisRenderer, maximum - minimum, _axisSize) - : calculateInterval(_VisibleRange(minimum, maximum), _axisSize); + : calculateInterval(_VisibleRange(minimum, maximum), _axisSize)!; maximum = (maximum / interval).ceil() * interval; } @@ -2559,46 +2633,54 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { TextStyle fontStyle = _axis.labelStyle; final String actualText = labelText; - Size textSize = _measureText(labelText, _axis.labelStyle, 0); + Size textSize = measureText(labelText, _axis.labelStyle, 0); if (_axis.maximumLabelWidth != null || _axis.labelsExtent != null) { if (_axis.maximumLabelWidth != null) { - assert(_axis.maximumLabelWidth >= 0, + assert(_axis.maximumLabelWidth! >= 0, 'maximumLabelWidth must not be negative'); } if (_axis.labelsExtent != null) { - assert(_axis.labelsExtent >= 0, 'labelsExtent must not be negative'); + assert(_axis.labelsExtent! >= 0, 'labelsExtent must not be negative'); } if ((_axis.maximumLabelWidth != null && - textSize.width > _axis.maximumLabelWidth) || - (_axis.labelsExtent != null && textSize.width > _axis.labelsExtent)) { + textSize.width > _axis.maximumLabelWidth!) || + (_axis.labelsExtent != null && + textSize.width > _axis.labelsExtent!)) { labelText = _trimAxisLabelsText( labelText, - _axis.labelsExtent ?? _axis.maximumLabelWidth, + (_axis.labelsExtent ?? _axis.maximumLabelWidth)!, _axis.labelStyle, this); } - textSize = _measureText(labelText, _axis.labelStyle, 0); + textSize = measureText(labelText, _axis.labelStyle, 0); } - final String trimmedText = + final String? trimmedText = labelText.contains('...') || labelText.isEmpty ? labelText : null; String renderText = trimmedText ?? actualText; - if (_chart.onAxisLabelRender != null) { + final AxisLabelRenderDetails axisLabelDetails = AxisLabelRenderDetails( + labelValue, + trimmedText ?? actualText, + actualText, + fontStyle, + _axis, + _name, + _orientation!); + if (_chart.axisLabelFormatter != null) { + final ChartAxisLabel axisLabel = + _chart.axisLabelFormatter!(axisLabelDetails); + fontStyle = axisLabel.textStyle; + renderText = axisLabel.text; + } else if (_chart.onAxisLabelRender != null) { axisLabelArgs = AxisLabelRenderArgs(labelValue, _name, _orientation, _axis); axisLabelArgs.text = actualText; axisLabelArgs.textStyle = fontStyle; - // axisLabelArgs.trimmedText = trimmedText; - _chart.onAxisLabelRender(axisLabelArgs); + _chart.onAxisLabelRender!(axisLabelArgs); fontStyle = axisLabelArgs.textStyle; - // if (actualText != axisLabelArgs.text && trimmedText == null) { renderText = axisLabelArgs.text; - // } else if (trimmedText != axisLabelArgs.trimmedText && - // trimmedText != null) { - // renderText = axisLabelArgs.trimmedText; - // } } final Size labelSize = - _measureText(renderText, fontStyle, _axis.labelRotation); + measureText(renderText, fontStyle, _axis.labelRotation); _visibleLabels.add(AxisLabel( fontStyle, labelSize, actualText, labelValue, trimmedText, renderText)); } @@ -2609,7 +2691,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { AxisLabelIntersectAction action; AxisLabel label; final ChartAxis axis = axisRenderer._axis; - num maximumLabelHeight = 0.0, + double maximumLabelHeight = 0.0, maximumLabelWidth = 0.0, labelMaximumWidth, pointX; @@ -2680,7 +2762,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { /// Based on below options, perform label intersection if (_isCollide) { - final List _list = _performLabelIntersectAction( + final List _list = _performLabelIntersectAction( label, action, maximumLabelWidth, @@ -2700,17 +2782,17 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { } /// Return the height and width values of labelIntersectAction - List _performLabelIntersectAction( + List _performLabelIntersectAction( AxisLabel label, AxisLabelIntersectAction action, - num maximumLabelWidth, - num maximumLabelHeight, - num labelMaximumWidth, + double maximumLabelWidth, + double maximumLabelHeight, + double labelMaximumWidth, num pointX, int i, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - num height; + double height; int angle = axisRenderer._labelRotation; Size currentLabelSize; final ChartAxis axis = axisRenderer._axis; @@ -2727,7 +2809,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { case AxisLabelIntersectAction.rotate90: angle = action == AxisLabelIntersectAction.rotate45 ? -45 : -90; axisRenderer._labelRotation = angle; - currentLabelSize = _measureText(label.text, axis.labelStyle, angle); + currentLabelSize = measureText(label.text, axis.labelStyle, angle); if (currentLabelSize.height > maximumLabelHeight) { maximumLabelHeight = currentLabelSize.height; } @@ -2737,11 +2819,11 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { break; case AxisLabelIntersectAction.wrap: label._labelCollection = _gettingLabelCollection( - label.renderText, labelMaximumWidth, axisRenderer); - if (label._labelCollection.isNotEmpty) { - label.renderText = label._labelCollection[0]; + label.renderText!, labelMaximumWidth, axisRenderer); + if (label._labelCollection!.isNotEmpty) { + label.renderText = label._labelCollection![0]; } - height = label.labelSize.height * label._labelCollection.length; + height = label.labelSize.height * label._labelCollection!.length; if (height > maximumLabelHeight) { maximumLabelHeight = height; } @@ -2749,11 +2831,11 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { default: break; } - return [maximumLabelWidth, maximumLabelHeight]; + return [maximumLabelWidth, maximumLabelHeight]; } /// To find the height of the current label - num _findMultiRows(num length, num currentX, AxisLabel currentLabel, + double _findMultiRows(int length, num currentX, AxisLabel currentLabel, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { AxisLabel label; num pointX; @@ -2792,7 +2874,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { String text; for (int i = 0; i < textCollection.length; i++) { text = textCollection[i]; - (_measureText(text, axis.labelStyle, axisRenderer._labelRotation).width < + (measureText(text, axis.labelStyle, axisRenderer._labelRotation).width < labelsExtent) ? labelCollection.add(text) : labelCollection.add(_trimAxisLabelsText( @@ -2803,17 +2885,11 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { ///Below method is for changing range while zooming void _calculateZoomRange(ChartAxisRenderer axisRenderer, Size axisSize) { - ChartAxisRenderer oldAxisRenderer; + ChartAxisRenderer? oldAxisRenderer; final ChartAxis axis = axisRenderer._axis; - assert( - axis.zoomFactor != null - ? axis.zoomFactor >= 0 && axis.zoomFactor <= 1 - : true, + assert(axis.zoomFactor >= 0 && axis.zoomFactor <= 1, 'The zoom factor of the axis should be between 0 and 1.'); - assert( - axis.zoomPosition != null - ? axis.zoomPosition >= 0 && axis.zoomPosition <= 1 - : true, + assert(axis.zoomPosition >= 0 && axis.zoomPosition <= 1, 'The zoom position of the axis should be between 0 and 1.'); /// Restrict zoom factor and zoom position values between 0 to 1 @@ -2827,8 +2903,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { : axisRenderer._zoomPosition < 0 ? 0 : axisRenderer._zoomPosition; - if (_chartState._oldAxisRenderers != null && - _chartState._oldAxisRenderers.isNotEmpty) { + if (_chartState._oldAxisRenderers.isNotEmpty) { oldAxisRenderer = _getOldAxisRenderer(axisRenderer, _chartState._oldAxisRenderers); } @@ -2843,11 +2918,11 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { : axisRenderer._zoomPosition; } - final _VisibleRange baseRange = axisRenderer._visibleRange; + final _VisibleRange baseRange = axisRenderer._visibleRange!; num start, end; - start = axisRenderer._visibleRange.minimum + - axisRenderer._zoomPosition * axisRenderer._visibleRange.delta; - end = start + axisRenderer._zoomFactor * axisRenderer._visibleRange.delta; + start = axisRenderer._visibleRange!.minimum + + axisRenderer._zoomPosition * axisRenderer._visibleRange!.delta; + end = start + axisRenderer._zoomFactor * axisRenderer._visibleRange!.delta; if (start < baseRange.minimum) { end = end + (baseRange.minimum - start); @@ -2857,19 +2932,33 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { start = start - (end - baseRange.maximum); end = baseRange.maximum; } - axisRenderer._visibleRange.minimum = start; - axisRenderer._visibleRange.maximum = end; - axisRenderer._visibleRange.delta = end - start; + axisRenderer._visibleRange!.minimum = start; + axisRenderer._visibleRange!.maximum = end; + axisRenderer._visibleRange!.delta = end - start; } - /// To check zommed axis state - void _checkWithZoomState(ChartAxisRenderer axisRenderer, + /// To set the zoom factor and position of axis through dynamic update or from + void _setZoomFactorAndPosition(ChartAxisRenderer axisRenderer, List axisRendererStates) { - for (int i = 0; i < axisRendererStates.length; i++) { - final ChartAxisRenderer zoomedAxisRenderer = axisRendererStates[i]; - if (zoomedAxisRenderer._name == axisRenderer._name) { - axisRenderer._zoomFactor = zoomedAxisRenderer._zoomFactor; - axisRenderer._zoomPosition = zoomedAxisRenderer._zoomPosition; + bool didUpdateAxis; + if (_oldAxis != null && + (_oldAxis!.zoomPosition != _axis.zoomPosition || + _oldAxis!.zoomFactor != _axis.zoomFactor)) { + _zoomFactor = _axis.zoomFactor; + _zoomPosition = _axis.zoomPosition; + didUpdateAxis = true; + } else { + didUpdateAxis = false; + } + for (final zoomedAxisRenderer in _chartState._zoomedAxisRendererStates) { + if (zoomedAxisRenderer._name == _name) { + if (didUpdateAxis) { + zoomedAxisRenderer._zoomFactor = _zoomFactor; + zoomedAxisRenderer._zoomPosition = _zoomPosition; + } else { + axisRenderer._zoomFactor = zoomedAxisRenderer._zoomFactor; + axisRenderer._zoomPosition = zoomedAxisRenderer._zoomPosition; + } break; } } @@ -2878,13 +2967,13 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { /// To provide chart changes to range controller void _setRangeControllerValues(ChartAxisRenderer _axisRenderer) { if (_axisRenderer is DateTimeAxisRenderer) { - _axis.rangeController.start = DateTime.fromMillisecondsSinceEpoch( - _axisRenderer._visibleRange.minimum); - _axis.rangeController.end = DateTime.fromMillisecondsSinceEpoch( - _axisRenderer._visibleRange.maximum); + _axis.rangeController!.start = DateTime.fromMillisecondsSinceEpoch( + _axisRenderer._visibleRange!.minimum); + _axis.rangeController!.end = DateTime.fromMillisecondsSinceEpoch( + _axisRenderer._visibleRange!.maximum); } else { - _axis.rangeController.start = _axisRenderer._visibleRange.minimum; - _axis.rangeController.end = _axisRenderer._visibleRange.maximum; + _axis.rangeController!.start = _axisRenderer._visibleRange!.minimum; + _axis.rangeController!.end = _axisRenderer._visibleRange!.maximum; } } @@ -2892,12 +2981,37 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { void _updateRangeControllerValues(ChartAxisRenderer _axisRenderer) { if (_axisRenderer is DateTimeAxisRenderer) { _axisRenderer._rangeMinimum = - _axis.rangeController.start.millisecondsSinceEpoch; + _axis.rangeController!.start.millisecondsSinceEpoch; _axisRenderer._rangeMaximum = - _axis.rangeController.end.millisecondsSinceEpoch; + _axis.rangeController!.end.millisecondsSinceEpoch; } else { - _axisRenderer._rangeMinimum = _axis.rangeController.start; - _axisRenderer._rangeMaximum = _axis.rangeController.end; + _axisRenderer._rangeMinimum = _axis.rangeController!.start; + _axisRenderer._rangeMaximum = _axis.rangeController!.end; + } + } + + /// Auto scrolling feature + void _updateAutoScrollingDelta( + int scrollingDelta, ChartAxisRenderer _axisRenderer) { + switch (_axis.autoScrollingMode) { + case AutoScrollingMode.start: + final _VisibleRange autoScrollRange = _VisibleRange( + _axisRenderer._visibleRange!.minimum, + _axisRenderer._visibleRange!.minimum + scrollingDelta); + autoScrollRange.delta = + autoScrollRange.maximum - autoScrollRange.minimum; + _zoomFactor = autoScrollRange.delta / _actualRange!.delta; + _zoomPosition = 0; + break; + case AutoScrollingMode.end: + final _VisibleRange autoScrollRange = _VisibleRange( + _axisRenderer._visibleRange!.maximum - scrollingDelta, + _axisRenderer._visibleRange!.maximum); + autoScrollRange.delta = + autoScrollRange.maximum - autoScrollRange.minimum; + _zoomFactor = autoScrollRange.delta / _actualRange!.delta; + _zoomPosition = 1 - _zoomFactor; + break; } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart index 79e5c9c69..ecdc90825 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart @@ -13,29 +13,29 @@ class _ChartAxis { //Here, we are using get keyword inorder to get the proper & updated instance of chart widget //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. SfCartesianChart get _chartWidget => _chartState._chart; - ChartAxisRenderer _primaryXAxisRenderer, _primaryYAxisRenderer; + late ChartAxisRenderer _primaryXAxisRenderer, _primaryYAxisRenderer; List _leftAxisRenderers = []; List _rightAxisRenderers = []; List _topAxisRenderers = []; List _bottomAxisRenderers = []; - List<_AxisSize> _leftAxesCount; - List<_AxisSize> _bottomAxesCount; - List<_AxisSize> _topAxesCount; - List<_AxisSize> _rightAxesCount; + late List<_AxisSize> _leftAxesCount; + late List<_AxisSize> _bottomAxesCount; + late List<_AxisSize> _topAxesCount; + late List<_AxisSize> _rightAxesCount; double _bottomSize = 0; double _topSize = 0; double _leftSize = 0; double _rightSize = 0; - num _innerPadding = 0; - num _axisPadding = 0; - Rect _axisClipRect; + double _innerPadding = 0; + double _axisPadding = 0; + late Rect _axisClipRect; List _verticalAxisRenderers = []; List _horizontalAxisRenderers = []; //ignore: prefer_final_fields List _axisRenderersCollection = []; /// Whether to repaint axis or not - bool _needsRepaint; + late bool _needsRepaint; /// To get the crossAt values of a specific axis void _getAxisCrossingValue(ChartAxisRenderer axisRenderer) { @@ -83,8 +83,16 @@ class _ChartAxis { ? value.floor() : targetAxisRenderer._labels.indexOf(value); targetAxisRenderer._calculateRangeAndInterval(_chartState, 'AxisCross'); + } else if (targetAxisRenderer is DateTimeCategoryAxisRenderer) { + value = value is num + ? value.floor() + : (value is DateTime + ? targetAxisRenderer._labels + .indexOf('${value.microsecondsSinceEpoch}') + : null); + targetAxisRenderer._calculateRangeAndInterval(_chartState, 'AxisCross'); } else if (targetAxisRenderer is LogarithmicAxisRenderer) { - final LogarithmicAxis _axis = targetAxisRenderer._axis; + final LogarithmicAxis _axis = targetAxisRenderer._axis as LogarithmicAxis; value = _calculateLogBaseValue(value, _axis.logBase); targetAxisRenderer._calculateRangeAndInterval(_chartState, 'AxisCross'); } else if (targetAxisRenderer is NumericAxisRenderer) { @@ -92,7 +100,7 @@ class _ChartAxis { } if (!value.isNaN) { currentAxisRenderer._crossValue = - _updateCrossValue(value, targetAxisRenderer._visibleRange); + _updateCrossValue(value, targetAxisRenderer._visibleRange!); currentAxisRenderer._crossRange = targetAxisRenderer._visibleRange; } } @@ -153,8 +161,8 @@ class _ChartAxis { num titleSize = 0; axisRenderer._totalSize = 0; if (axis.isVisible) { - if (axis.title.text != null && axis.title.text.isNotEmpty) { - titleSize = _measureText(axis.title.text, axis.title.textStyle).height + + if (axis.title.text != null && axis.title.text!.isNotEmpty) { + titleSize = measureText(axis.title.text!, axis.title.textStyle).height + _axisPadding; } final Rect rect = _chartState._containerRect; @@ -175,21 +183,22 @@ class _ChartAxis { ? 0 : (axisRenderer._orientation == AxisOrientation.horizontal) ? axisRenderer._maximumLabelSize.height - : (axis.labelsExtent != null && axis.labelsExtent > 0) + : (axis.labelsExtent != null && axis.labelsExtent! > 0) ? axis.labelsExtent - : axisRenderer._maximumLabelSize.width) + + : axisRenderer._maximumLabelSize.width)! + _innerPadding; axisRenderer._totalSize = titleSize + tickSize + labelSize; if (axisRenderer._orientation == AxisOrientation.horizontal) { if (!axis.opposedPosition) { axisRenderer._totalSize += - _bottomAxisRenderers.isNotEmpty && axis.labelStyle.fontSize > 0 + _bottomAxisRenderers.isNotEmpty && axis.labelStyle.fontSize! > 0 ? _axisPadding.toDouble() : 0; if (axisRenderer._crossValue != null && axisRenderer._crossRange != null) { final num crosPosition = _valueToCoefficient( - axisRenderer._crossValue, axisRenderer._crossAxisRenderer) * + axisRenderer._crossValue!, + axisRenderer._crossAxisRenderer) * rect.height; axisRenderer._totalSize = crosPosition - axisRenderer._totalSize < 0 ? (crosPosition - axisRenderer._totalSize).abs() @@ -202,13 +211,14 @@ class _ChartAxis { .add(_AxisSize(axisRenderer, axisRenderer._totalSize)); } else { axisRenderer._totalSize += - _topAxisRenderers.isNotEmpty && axis.labelStyle.fontSize > 0 + _topAxisRenderers.isNotEmpty && axis.labelStyle.fontSize! > 0 ? _axisPadding.toDouble() : 0; if (axisRenderer._crossValue != null && axisRenderer._crossRange != null) { final num crosPosition = _valueToCoefficient( - axisRenderer._crossValue, axisRenderer._crossAxisRenderer) * + axisRenderer._crossValue!, + axisRenderer._crossAxisRenderer) * rect.height; axisRenderer._totalSize = crosPosition + axisRenderer._totalSize > rect.height @@ -223,13 +233,14 @@ class _ChartAxis { } else if (axisRenderer._orientation == AxisOrientation.vertical) { if (!axis.opposedPosition) { axisRenderer._totalSize += - _leftAxisRenderers.isNotEmpty && axis.labelStyle.fontSize > 0 + _leftAxisRenderers.isNotEmpty && axis.labelStyle.fontSize! > 0 ? _axisPadding.toDouble() : 0; if (axisRenderer._crossValue != null && axisRenderer._crossRange != null) { final num crosPosition = _valueToCoefficient( - axisRenderer._crossValue, axisRenderer._crossAxisRenderer) * + axisRenderer._crossValue!, + axisRenderer._crossAxisRenderer) * rect.width; axisRenderer._totalSize = crosPosition - axisRenderer._totalSize < 0 ? (crosPosition - axisRenderer._totalSize).abs() @@ -241,13 +252,14 @@ class _ChartAxis { _leftAxesCount.add(_AxisSize(axisRenderer, axisRenderer._totalSize)); } else { axisRenderer._totalSize += - _rightAxisRenderers.isNotEmpty && axis.labelStyle.fontSize > 0 + _rightAxisRenderers.isNotEmpty && axis.labelStyle.fontSize! > 0 ? _axisPadding.toDouble() : 0; if (axisRenderer._crossValue != null && axisRenderer._crossRange != null) { final num crosPosition = _valueToCoefficient( - axisRenderer._crossValue, axisRenderer._crossAxisRenderer) * + axisRenderer._crossValue!, + axisRenderer._crossAxisRenderer) * rect.width; axisRenderer._totalSize = crosPosition + axisRenderer._totalSize > rect.width @@ -283,6 +295,8 @@ class _ChartAxis { _rightAxisRenderers.add(axisRenderer); index = _rightAxisRenderers.length; } + } else { + index = 0; } return index - 1; } @@ -302,10 +316,7 @@ class _ChartAxis { /// Calculate series clip rect size void _calculateSeriesClipRect() { final Rect containerRect = _chartState._containerRect; - final num padding = - _chartWidget.title.text != null && _chartWidget.title.text.isNotEmpty - ? 10 - : 0; + final num padding = _chartWidget.title.text.isNotEmpty ? 10 : 0; _chartState._chartAxis._axisClipRect = Rect.fromLTWH( containerRect.left + _leftSize, containerRect.top + _topSize + padding, @@ -325,52 +336,54 @@ class _ChartAxis { } /// Return the axis offset value for x and y axis - num _getPrevAxisOffset( + num? _getPrevAxisOffset( List<_AxisSize> axesSize, Rect rect, int currentAxisIndex, String type) { - num prevAxisOffsetValue; + num? prevAxisOffsetValue; if (currentAxisIndex > 0) { for (int i = currentAxisIndex - 1; i >= 0; i--) { final ChartAxisRenderer axisRenderer = axesSize[i].axisRenderer; final Rect bounds = axisRenderer._bounds; if (type == 'Left' && ((axisRenderer._labelOffset != null - ? axisRenderer._labelOffset - + ? axisRenderer._labelOffset! - axisRenderer._maximumLabelSize.width : bounds.left - bounds.width) < rect.left)) { prevAxisOffsetValue = axisRenderer._labelOffset != null - ? axisRenderer._labelOffset - axisRenderer._maximumLabelSize.width + ? axisRenderer._labelOffset! - + axisRenderer._maximumLabelSize.width : bounds.left - bounds.width; break; } else if (type == 'Bottom' && ((axisRenderer._labelOffset != null - ? axisRenderer._labelOffset + + ? axisRenderer._labelOffset! + axisRenderer._maximumLabelSize.height : bounds.top + bounds.height) > rect.top + rect.height)) { prevAxisOffsetValue = axisRenderer._labelOffset != null - ? axisRenderer._labelOffset + + ? axisRenderer._labelOffset! + axisRenderer._maximumLabelSize.height : bounds.top + bounds.height; break; } else if (type == 'Right' && ((axisRenderer._labelOffset != null - ? axisRenderer._labelOffset + + ? axisRenderer._labelOffset! + axisRenderer._maximumLabelSize.width : bounds.left + bounds.width) > rect.left + rect.width)) { prevAxisOffsetValue = axisRenderer._labelOffset != null - ? axisRenderer._labelOffset + axisRenderer._maximumLabelSize.width + ? axisRenderer._labelOffset! + + axisRenderer._maximumLabelSize.width : bounds.left + bounds.width; break; } else if (type == 'Top' && ((axisRenderer._labelOffset != null - ? axisRenderer._labelOffset - + ? axisRenderer._labelOffset! - axisRenderer._maximumLabelSize.height : bounds.top - bounds.height) < rect.top)) { prevAxisOffsetValue = axisRenderer._labelOffset != null - ? axisRenderer._labelOffset - + ? axisRenderer._labelOffset! - axisRenderer._maximumLabelSize.height : bounds.top - bounds.height; break; @@ -407,7 +420,7 @@ class _ChartAxis { /// Calculate the left axes bounds void _calculateLeftAxesBounds() { - num axisSize, width; + double axisSize, width; final int axesLength = _leftAxesCount.length; final Rect rect = _chartState._chartAxis._axisClipRect; for (int axisIndex = 0; axisIndex < axesLength; axisIndex++) { @@ -415,18 +428,18 @@ class _ChartAxis { final ChartAxisRenderer axisRenderer = _leftAxesCount[axisIndex].axisRenderer; final ChartAxis axis = axisRenderer._axis; - assert(axis.plotOffset != null ? axis.plotOffset >= 0 : true, + assert(axis.plotOffset >= 0, 'The plot offset value of the axis must be greater than or equal to 0.'); if (axisRenderer._crossValue != null) { - axisSize = (_valueToCoefficient( - axisRenderer._crossValue, axisRenderer._crossAxisRenderer) * + axisSize = (_valueToCoefficient(axisRenderer._crossValue!, + axisRenderer._crossAxisRenderer) * rect.width) + rect.left; if (axisIndex == 0 && !axis.placeLabelsNearAxisLine) { axisRenderer._labelOffset = rect.left - 5; } } else { - final num prevAxisOffsetValue = + final num? prevAxisOffsetValue = _getPrevAxisOffset(_leftAxesCount, rect, axisIndex, 'Left'); axisSize = prevAxisOffsetValue == null ? rect.left @@ -450,7 +463,7 @@ class _ChartAxis { /// Calculate the bottom axes bounds void _calculateBottomAxesBounds() { - num axisSize, height; + double axisSize, height; final int axesLength = _bottomAxesCount.length; final Rect rect = _chartState._chartAxis._axisClipRect; for (int axisIndex = 0; axisIndex < axesLength; axisIndex++) { @@ -458,19 +471,19 @@ class _ChartAxis { final ChartAxisRenderer axisRenderer = _bottomAxesCount[axisIndex].axisRenderer; final ChartAxis axis = axisRenderer._axis; - assert(axis.plotOffset != null ? axis.plotOffset >= 0 : true, + assert(axis.plotOffset >= 0, 'The plot offset value of the axis must be greater than or equal to 0.'); if (axisRenderer._crossValue != null) { axisSize = rect.top + rect.height - - (_valueToCoefficient( - axisRenderer._crossValue, axisRenderer._crossAxisRenderer) * + (_valueToCoefficient(axisRenderer._crossValue!, + axisRenderer._crossAxisRenderer) * rect.height); if (axisIndex == 0 && !axis.placeLabelsNearAxisLine) { axisRenderer._labelOffset = rect.top + rect.height + 5; } } else { - final num prevAxisOffsetValue = + final num? prevAxisOffsetValue = _getPrevAxisOffset(_bottomAxesCount, rect, axisIndex, 'Bottom'); axisSize = (prevAxisOffsetValue == null) ? rect.top + rect.height @@ -494,26 +507,26 @@ class _ChartAxis { /// Calculate the right axes bounds void _calculateRightAxesBounds() { - num axisSize, width; + double axisSize, width; final int axesLength = _rightAxesCount.length; final Rect rect = _chartState._chartAxis._axisClipRect; for (int axisIndex = 0; axisIndex < axesLength; axisIndex++) { final ChartAxisRenderer axisRenderer = _rightAxesCount[axisIndex].axisRenderer; final ChartAxis axis = axisRenderer._axis; - assert(axis.plotOffset != null ? axis.plotOffset >= 0 : true, + assert(axis.plotOffset >= 0, 'The plot offset value of the axis must be greater than or equal to 0.'); width = _rightAxesCount[axisIndex].size; if (axisRenderer._crossValue != null) { axisSize = rect.left + - (_valueToCoefficient( - axisRenderer._crossValue, axisRenderer._crossAxisRenderer) * + (_valueToCoefficient(axisRenderer._crossValue!, + axisRenderer._crossAxisRenderer) * rect.width); if (axisIndex == 0 && !axis.placeLabelsNearAxisLine) { axisRenderer._labelOffset = rect.left + rect.width + 5; } } else { - final num prevAxisOffsetValue = + final num? prevAxisOffsetValue = _getPrevAxisOffset(_rightAxesCount, rect, axisIndex, 'Right'); axisSize = (prevAxisOffsetValue == null) ? rect.left + rect.width @@ -537,27 +550,27 @@ class _ChartAxis { /// Calculate the top axes bounds void _calculateTopAxesBounds() { - num axisSize, height; + double axisSize, height; final int axesLength = _topAxesCount.length; final Rect rect = _chartState._chartAxis._axisClipRect; for (int axisIndex = 0; axisIndex < axesLength; axisIndex++) { final ChartAxisRenderer axisRenderer = _topAxesCount[axisIndex].axisRenderer; final ChartAxis axis = axisRenderer._axis; - assert(axis.plotOffset != null ? axis.plotOffset >= 0 : true, + assert(axis.plotOffset >= 0, 'The plot offset value of the axis must be greater than or equal to 0.'); height = _topAxesCount[axisIndex].size; if (axisRenderer._crossValue != null) { axisSize = rect.top + rect.height - - (_valueToCoefficient( - axisRenderer._crossValue, axisRenderer._crossAxisRenderer) * + (_valueToCoefficient(axisRenderer._crossValue!, + axisRenderer._crossAxisRenderer) * rect.height); if (axisIndex == 0 && !axis.placeLabelsNearAxisLine) { axisRenderer._labelOffset = rect.top - 5; } } else { - final num prevAxisOffsetValue = + final num? prevAxisOffsetValue = _getPrevAxisOffset(_topAxesCount, rect, axisIndex, 'Top'); axisSize = (prevAxisOffsetValue == null) ? rect.top @@ -612,6 +625,8 @@ class _ChartAxis { : _getAxisRenderer(_axesCollection[axisIndex])); if (axisRenderer is CategoryAxisRenderer) { axisRenderer._labels = []; + } else if (axisRenderer is DateTimeCategoryAxisRenderer) { + axisRenderer._labels = []; } axisRenderer._seriesRenderers = []; axisRenderer._chartState = _chartState; @@ -620,7 +635,8 @@ class _ChartAxis { seriesIndex++) { final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderer[seriesIndex]; - final XyDataSeries series = seriesRenderer._series; + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; if ((axisRenderer._name != null && axisRenderer._name == series.xAxisName) || (series.xAxisName == null && @@ -673,6 +689,10 @@ class _ChartAxis { ? _verticalAxisRenderers.add(axisRenderer) : _horizontalAxisRenderers.add(axisRenderer); } + axisRenderer._oldAxis = _chartState._widgetNeedUpdate + ? _getOldAxisRenderer(axisRenderer, _chartState._oldAxisRenderers) + ?._axis + : null; _axisRenderersCollection.add(axisRenderer); } } else { @@ -694,15 +714,17 @@ class _ChartAxis { ChartAxisRenderer _getAxisRenderer(ChartAxis axis) { switch (axis.runtimeType) { case NumericAxis: - return NumericAxisRenderer(axis); + return NumericAxisRenderer(axis as NumericAxis); case LogarithmicAxis: - return LogarithmicAxisRenderer(axis); + return LogarithmicAxisRenderer(axis as LogarithmicAxis); case CategoryAxis: - return CategoryAxisRenderer(axis); + return CategoryAxisRenderer(axis as CategoryAxis); case DateTimeAxis: - return DateTimeAxisRenderer(axis); + return DateTimeAxisRenderer(axis as DateTimeAxis); + case DateTimeCategoryAxis: + return DateTimeCategoryAxisRenderer(axis as DateTimeCategoryAxis); default: - return null; + return NumericAxisRenderer(axis as NumericAxis); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_renderer.dart index 3694dd7a6..31886fade 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_renderer.dart @@ -2,20 +2,20 @@ part of charts; abstract class _CustomizeAxisElements { /// To get axis line color - Color getAxisLineColor(ChartAxis axis); + Color? getAxisLineColor(ChartAxis axis); ///To get axis line width - Color getAxisMajorTickColor(ChartAxis axis, int majorTickIndex); + Color? getAxisMajorTickColor(ChartAxis axis, int majorTickIndex); /// To get major tick color - Color getAxisMinorTickColor( + Color? getAxisMinorTickColor( ChartAxis axis, int majorTickIndex, int minorTickIndex); /// To get major grid color - Color getAxisMajorGridColor(ChartAxis axis, int majorGridIndex); + Color? getAxisMajorGridColor(ChartAxis axis, int majorGridIndex); /// To get minor grid color - Color getAxisMinorGridColor( + Color? getAxisMinorGridColor( ChartAxis axis, int majorGridIndex, int minorGridIndex); double getAxisLineWidth(ChartAxis axis); @@ -82,7 +82,7 @@ abstract class _CustomizeAxisElements { void drawHorizontalAxesMinorLines( Canvas canvas, ChartAxisRenderer axisRenderer, - num tempInterval, + double tempInterval, Rect rect, num nextValue, int index, @@ -117,13 +117,13 @@ abstract class _CustomizeAxisElements { // ignore: must_be_immutable class _CartesianAxisRenderer extends StatefulWidget { // ignore: prefer_const_constructors_in_immutables - _CartesianAxisRenderer({this.chartState, this.renderType}); + _CartesianAxisRenderer({required this.chartState, required this.renderType}); final SfCartesianChartState chartState; String renderType; - _CartesianAxisRendererState state; + late _CartesianAxisRendererState state; @override State createState() => _CartesianAxisRendererState(); @@ -131,13 +131,13 @@ class _CartesianAxisRenderer extends StatefulWidget { class _CartesianAxisRendererState extends State<_CartesianAxisRenderer> with SingleTickerProviderStateMixin { - List animationControllersList; + late List animationControllersList; /// Animation controller for axis - AnimationController animationController; + late AnimationController animationController; /// Repaint notifier for axis - ValueNotifier axisRepaintNotifier; + late ValueNotifier axisRepaintNotifier; @override void initState() { @@ -168,6 +168,107 @@ class _CartesianAxisRendererState extends State<_CartesianAxisRenderer> notifier: axisRepaintNotifier)))); } + void _animateAxis() { + final double animationFactor = animationController.value; + for (int axisIndex = 0; + axisIndex < + widget.chartState._chartAxis._axisRenderersCollection.length; + axisIndex++) { + ///visibleMinimum and visibleMaximum not defined in the chart axis class, + /// so dynamic datatype used here + final dynamic axisRenderer = + widget.chartState._chartAxis._axisRenderersCollection[axisIndex]; + + ///visibleMinimum and visibleMaximum not defined in the chart axis class, + /// so dynamic datatype used here + dynamic oldAxisRenderer; + bool needAnimate = false; + if ((widget.chartState._requireInvertedAxis + ? axisRenderer._orientation == AxisOrientation.vertical + : axisRenderer._orientation == AxisOrientation.horizontal) && + widget.chartState._oldAxisRenderers != null && + widget.chartState._oldAxisRenderers.isNotEmpty && + (axisRenderer._axis.visibleMinimum != null || + axisRenderer._axis.visibleMaximum != null)) { + oldAxisRenderer = _getOldAxisRenderer( + axisRenderer, widget.chartState._oldAxisRenderers); + if (oldAxisRenderer != null && + (oldAxisRenderer._axis.visibleMinimum != null || + oldAxisRenderer._axis.visibleMaximum != null)) { + needAnimate = + axisRenderer.runtimeType == oldAxisRenderer.runtimeType && + ((oldAxisRenderer._axis.visibleMinimum != null && + oldAxisRenderer._axis.visibleMinimum != + axisRenderer._axis.visibleMinimum) || + (oldAxisRenderer._axis.visibleMaximum != null && + oldAxisRenderer._axis.visibleMaximum != + axisRenderer._axis.visibleMaximum)) && + _checkSeriesAnimation(axisRenderer._seriesRenderers); + if (needAnimate) { + if (axisRenderer is DateTimeAxisRenderer || + axisRenderer is DateTimeCategoryAxisRenderer) { + axisRenderer._visibleMinimum = + (oldAxisRenderer._axis.visibleMinimum.millisecondsSinceEpoch - + (oldAxisRenderer._axis.visibleMinimum + .millisecondsSinceEpoch - + axisRenderer._axis.visibleMinimum + .millisecondsSinceEpoch) * + animationFactor) + .toInt(); + axisRenderer._visibleMaximum = + (oldAxisRenderer._axis.visibleMaximum.millisecondsSinceEpoch - + (oldAxisRenderer._axis.visibleMaximum + .millisecondsSinceEpoch - + axisRenderer._axis.visibleMaximum + .millisecondsSinceEpoch) * + animationFactor) + .toInt(); + } else { + axisRenderer._visibleMinimum = + oldAxisRenderer._axis.visibleMinimum - + (oldAxisRenderer._axis.visibleMinimum - + axisRenderer._axis.visibleMinimum) * + animationFactor; + axisRenderer._visibleMaximum = + oldAxisRenderer._axis.visibleMaximum - + (oldAxisRenderer._axis.visibleMaximum - + axisRenderer._axis.visibleMaximum) * + animationFactor; + } + if (axisRenderer is DateTimeCategoryAxisRenderer) { + axisRenderer._labels.clear(); + for (final CartesianSeriesRenderer seriesRenderer + in axisRenderer._seriesRenderers) { + widget.chartState._chartSeries + ._findSeriesMinMax(seriesRenderer); + } + } + axisRenderer._calculateRangeAndInterval(widget.chartState); + for (final CartesianSeriesRenderer seriesRenderer + in axisRenderer._seriesRenderers) { + seriesRenderer._calculateRegion = true; + seriesRenderer._repaintNotifier.value++; + if (seriesRenderer._series.dataLabelSettings.isVisible && + widget.chartState._renderDataLabel?.state != null) { + widget.chartState._renderDataLabel?.state! + .dataLabelRepaintNotifier.value++; + } + } + } + } + } + } + } + + bool _checkSeriesAnimation(List seriesRenderers) { + for (int i = 0; i < seriesRenderers.length; i++) { + if (seriesRenderers[i]._series.animationDuration <= 0) { + return false; + } + } + return true; + } + @override void dispose() { _disposeAnimationController(animationController, _repaintAxisElements); @@ -175,17 +276,18 @@ class _CartesianAxisRendererState extends State<_CartesianAxisRenderer> } void _repaintAxisElements() { + _animateAxis(); axisRepaintNotifier.value++; } } class _CartesianAxesPainter extends CustomPainter { _CartesianAxesPainter( - {this.chartState, - this.isRepaint, - ValueNotifier notifier, - this.renderType, - this.axisAnimation}) + {required this.chartState, + required this.isRepaint, + required ValueNotifier notifier, + required this.renderType, + required this.axisAnimation}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -208,7 +310,7 @@ class _CartesianAxesPainter extends CustomPainter { paintImage( canvas: canvas, rect: chartState._chartAxis._axisClipRect, - image: chartState._backgroundImage, + image: chartState._backgroundImage!, fit: BoxFit.fill); } } @@ -241,8 +343,8 @@ class _CartesianAxesPainter extends CustomPainter { Canvas canvas, ChartAxisRenderer axisRenderer, double animationFactor, - ChartAxisRenderer oldAxisRenderer, - bool needAnimate) { + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate) { final ChartAxis axis = axisRenderer._axis; if (axis.isVisible) { if (axis.axisLine.width > 0 && renderType == 'outside') { @@ -265,8 +367,8 @@ class _CartesianAxesPainter extends CustomPainter { Canvas canvas, ChartAxisRenderer axisRenderer, double animationFactor, - ChartAxisRenderer oldAxisRenderer, - bool needAnimate) { + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate) { final ChartAxis axis = axisRenderer._axis; if (axis.isVisible) { if (axis.axisLine.width > 0 && renderType == 'outside') { @@ -297,7 +399,7 @@ class _CartesianAxesPainter extends CustomPainter { final ChartAxis axis = axisRenderer._axis; axisRenderer._isInsideTickPosition = (axis.tickPosition == TickPosition.inside) ? true : false; - ChartAxisRenderer oldAxisRenderer; + ChartAxisRenderer? oldAxisRenderer; bool needAnimate = false; if (chartState._oldAxisRenderers != null && chartState._oldAxisRenderers.isNotEmpty && @@ -306,10 +408,10 @@ class _CartesianAxesPainter extends CustomPainter { _getOldAxisRenderer(axisRenderer, chartState._oldAxisRenderers); if (oldAxisRenderer != null && oldAxisRenderer._visibleRange != null) { needAnimate = chart.enableAxisAnimation && - (oldAxisRenderer._visibleRange.minimum != - axisRenderer._visibleRange.minimum || - oldAxisRenderer._visibleRange.maximum != - axisRenderer._visibleRange.maximum); + (oldAxisRenderer._visibleRange!.minimum != + axisRenderer._visibleRange!.minimum || + oldAxisRenderer._visibleRange!.maximum != + axisRenderer._visibleRange!.maximum); } } axisRenderer._orientation == AxisOrientation.horizontal diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart index 57f58633f..24a2f6731 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart @@ -9,83 +9,84 @@ part of charts; /// class CategoryAxis extends ChartAxis { /// Creating an argument constructor of CategoryAxis class. - CategoryAxis( - {String name, - bool isVisible, - AxisTitle title, - AxisLine axisLine, - bool arrangeByIndex, - ChartRangePadding rangePadding, - LabelPlacement labelPlacement, - EdgeLabelPlacement edgeLabelPlacement, - ChartDataLabelPosition labelPosition, - TickPosition tickPosition, - int labelRotation, - AxisLabelIntersectAction labelIntersectAction, - LabelAlignment labelAlignment, - bool isInversed, - bool opposedPosition, - int minorTicksPerInterval, - int maximumLabels, - MajorTickLines majorTickLines, - MinorTickLines minorTickLines, - MajorGridLines majorGridLines, - MinorGridLines minorGridLines, - TextStyle labelStyle, - double plotOffset, - double zoomFactor, - double zoomPosition, - InteractiveTooltip interactiveTooltip, - this.minimum, - this.maximum, - double interval, - this.visibleMinimum, - this.visibleMaximum, - dynamic crossesAt, - String associatedAxisName, - bool placeLabelsNearAxisLine, - List plotBands, - int desiredIntervals, - RangeController rangeController, - double maximumLabelWidth, - double labelsExtent}) - : arrangeByIndex = arrangeByIndex ?? false, - labelPlacement = labelPlacement ?? LabelPlacement.betweenTicks, - super( - name: name, - isVisible: isVisible, - isInversed: isInversed, - plotOffset: plotOffset, - rangePadding: rangePadding, - opposedPosition: opposedPosition, - edgeLabelPlacement: edgeLabelPlacement, - labelRotation: labelRotation, - labelPosition: labelPosition, - tickPosition: tickPosition, - labelIntersectAction: labelIntersectAction, - minorTicksPerInterval: minorTicksPerInterval, - maximumLabels: maximumLabels, - labelAlignment: labelAlignment, - labelStyle: labelStyle, - title: title, - axisLine: axisLine, - majorTickLines: majorTickLines, - minorTickLines: minorTickLines, - majorGridLines: majorGridLines, - minorGridLines: minorGridLines, - zoomFactor: zoomFactor, - zoomPosition: zoomPosition, - interactiveTooltip: interactiveTooltip, - interval: interval, - crossesAt: crossesAt, - associatedAxisName: associatedAxisName, - placeLabelsNearAxisLine: placeLabelsNearAxisLine, - plotBands: plotBands, - desiredIntervals: desiredIntervals, - rangeController: rangeController, - maximumLabelWidth: maximumLabelWidth, - labelsExtent: labelsExtent, - ); + CategoryAxis({ + String? name, + bool? isVisible, + AxisTitle? title, + AxisLine? axisLine, + this.arrangeByIndex = false, + ChartRangePadding? rangePadding, + this.labelPlacement = LabelPlacement.betweenTicks, + EdgeLabelPlacement? edgeLabelPlacement, + ChartDataLabelPosition? labelPosition, + TickPosition? tickPosition, + int? labelRotation, + AxisLabelIntersectAction? labelIntersectAction, + LabelAlignment? labelAlignment, + bool? isInversed, + bool? opposedPosition, + int? minorTicksPerInterval, + int? maximumLabels, + MajorTickLines? majorTickLines, + MinorTickLines? minorTickLines, + MajorGridLines? majorGridLines, + MinorGridLines? minorGridLines, + TextStyle? labelStyle, + double? plotOffset, + double? zoomFactor, + double? zoomPosition, + InteractiveTooltip? interactiveTooltip, + this.minimum, + this.maximum, + double? interval, + this.visibleMinimum, + this.visibleMaximum, + dynamic? crossesAt, + String? associatedAxisName, + bool? placeLabelsNearAxisLine, + List? plotBands, + int? desiredIntervals, + RangeController? rangeController, + double? maximumLabelWidth, + double? labelsExtent, + int? autoScrollingDelta, + AutoScrollingMode? autoScrollingMode, + }) : super( + name: name, + isVisible: isVisible, + isInversed: isInversed, + plotOffset: plotOffset, + rangePadding: rangePadding, + opposedPosition: opposedPosition, + edgeLabelPlacement: edgeLabelPlacement, + labelRotation: labelRotation, + labelPosition: labelPosition, + tickPosition: tickPosition, + labelIntersectAction: labelIntersectAction, + minorTicksPerInterval: minorTicksPerInterval, + maximumLabels: maximumLabels, + labelAlignment: labelAlignment, + labelStyle: labelStyle, + title: title, + axisLine: axisLine, + majorTickLines: majorTickLines, + minorTickLines: minorTickLines, + majorGridLines: majorGridLines, + minorGridLines: minorGridLines, + zoomFactor: zoomFactor, + zoomPosition: zoomPosition, + interactiveTooltip: interactiveTooltip, + interval: interval, + crossesAt: crossesAt, + associatedAxisName: associatedAxisName, + placeLabelsNearAxisLine: placeLabelsNearAxisLine, + plotBands: plotBands, + desiredIntervals: desiredIntervals, + rangeController: rangeController, + maximumLabelWidth: maximumLabelWidth, + labelsExtent: labelsExtent, + autoScrollingDelta: autoScrollingDelta, + autoScrollingMode: autoScrollingMode); ///Position of the category axis labels. /// @@ -136,7 +137,7 @@ class CategoryAxis extends ChartAxis { /// )); ///} ///``` - final double minimum; + final double? minimum; ///The maximum value of the axis. /// @@ -152,7 +153,7 @@ class CategoryAxis extends ChartAxis { /// )); ///} ///``` - final double maximum; + final double? maximum; ///The minimum visible value of the axis. The axis is rendered from this value ///initially. @@ -167,7 +168,7 @@ class CategoryAxis extends ChartAxis { /// )); ///} ///``` - final double visibleMinimum; + final double? visibleMinimum; ///The maximum visible value of the axis. /// @@ -183,7 +184,7 @@ class CategoryAxis extends ChartAxis { /// )); ///} ///``` - final double visibleMaximum; + final double? visibleMaximum; } /// Creates an axis renderer for Category axis @@ -193,15 +194,12 @@ class CategoryAxisRenderer extends ChartAxisRenderer { _labels = []; } dynamic _labels; - Rect _rect; + late Rect _rect; final CategoryAxis _categoryAxis; void _findAxisMinMaxValues(CartesianSeriesRenderer seriesRenderer, CartesianChartPoint point, int pointIndex, int dataLength, - [bool isXVisibleRange, bool isYVisibleRange]) { - final String seriesType = seriesRenderer._seriesType; - final bool _anchorRangeToVisiblePoints = - seriesRenderer._yAxisRenderer._axis.anchorRangeToVisiblePoints; + [bool? isXVisibleRange, bool? isYVisibleRange]) { if (_categoryAxis.arrangeByIndex) { pointIndex < _labels.length && _labels[pointIndex] != null ? _labels[pointIndex] += ', ' + point.x @@ -214,78 +212,8 @@ class CategoryAxisRenderer extends ChartAxisRenderer { point.xValue = _labels.indexOf(point.x.toString()); } point.yValue = point.y; - if (isYVisibleRange) { - seriesRenderer._minimumX ??= point.xValue; - seriesRenderer._maximumX ??= point.xValue; - } - if ((isXVisibleRange || !_anchorRangeToVisiblePoints) && - !seriesType.contains('range') && - !seriesType.contains('hilo') && - !seriesType.contains('candle') && - seriesType != 'boxandwhisker' && - seriesType != 'waterfall') { - seriesRenderer._minimumY ??= point.yValue; - seriesRenderer._maximumY ??= point.yValue; - } - if (isYVisibleRange && point.xValue != null) { - seriesRenderer._minimumX = - math.min(seriesRenderer._minimumX, point.xValue); - seriesRenderer._maximumX = - math.max(seriesRenderer._maximumX, point.xValue); - } - if (isXVisibleRange || !_anchorRangeToVisiblePoints) { - if (point.yValue != null && - (!seriesType.contains('range') && - !seriesType.contains('hilo') && - !seriesType.contains('candle') && - seriesType != 'boxandwhisker' && - seriesType != 'waterfall')) { - seriesRenderer._minimumY = - math.min(seriesRenderer._minimumY, point.yValue); - seriesRenderer._maximumY = - math.max(seriesRenderer._maximumY, point.yValue); - } - if (point.high != null) { - _highMin = _findMinValue(_highMin ?? point.high, point.high); - _highMax = _findMaxValue(_highMax ?? point.high, point.high); - } - if (point.low != null) { - _lowMin = _findMinValue(_lowMin ?? point.low, point.low); - _lowMax = _findMaxValue(_lowMax ?? point.low, point.low); - } - if (point.maximum != null) { - _highMin = _findMinValue(_highMin ?? point.maximum, point.maximum); - _highMax = _findMaxValue(_highMax ?? point.minimum, point.maximum); - } - if (point.minimum != null) { - _lowMin = _findMinValue(_lowMin ?? point.minimum, point.minimum); - _lowMax = _findMaxValue(_lowMax ?? point.minimum, point.minimum); - } - if (seriesType == 'waterfall') { - /// Empty point is not applicable for Waterfall series. - point.yValue ??= 0; - seriesRenderer._minimumY = _findMinValue( - seriesRenderer._minimumY ?? point.yValue, point.yValue); - seriesRenderer._maximumY = _findMaxValue( - seriesRenderer._maximumY ?? point.maxYValue, point.maxYValue); - } - } - - if (pointIndex >= dataLength - 1) { - if (seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType.contains('candle') || - seriesType == 'boxandwhisker') { - _lowMin ??= 0; - _lowMax ??= 5; - _highMin ??= 0; - _highMax ??= 5; - seriesRenderer._minimumY = math.min(_lowMin, _highMin); - seriesRenderer._maximumY = math.max(_lowMax, _highMax); - } - seriesRenderer._minimumY ??= 0; - seriesRenderer._maximumY ??= 5; - } + _setCategoryMinMaxValues(this, isXVisibleRange!, isYVisibleRange!, point, + pointIndex, dataLength, seriesRenderer); } /// Listener for range controller @@ -298,12 +226,12 @@ class CategoryAxisRenderer extends ChartAxisRenderer { /// Calculate the range and interval void _calculateRangeAndInterval(SfCartesianChartState chartState, - [String type]) { + [String? type]) { _chartState = chartState; _chart = chartState._chart; if (_axis.rangeController != null) { _chartState._rangeChangeBySlider = true; - _axis.rangeController.addListener(_controlListener); + _axis.rangeController!.addListener(_controlListener); } final Rect containerRect = _chartState._containerRect; _rect = Rect.fromLTWH(containerRect.left, containerRect.top, @@ -311,7 +239,7 @@ class CategoryAxisRenderer extends ChartAxisRenderer { calculateRange(this); _calculateActualRange(); if (_actualRange != null) { - applyRangePadding(_actualRange, _actualRange.interval); + applyRangePadding(_actualRange!, _actualRange!.interval); if (type == null && type != 'AxisCross' && _categoryAxis.isVisible) { generateVisibleLabels(); } @@ -324,18 +252,18 @@ class CategoryAxisRenderer extends ChartAxisRenderer { _actualRange = _VisibleRange( _chartState._rangeChangeBySlider && _categoryAxis.rangeController != null - ? _rangeMinimum ?? _categoryAxis.rangeController.start + ? _rangeMinimum ?? _categoryAxis.rangeController!.start : _categoryAxis.minimum ?? _min, _chartState._rangeChangeBySlider && _categoryAxis.rangeController != null - ? _rangeMaximum ?? _categoryAxis.rangeController.end + ? _rangeMaximum ?? _categoryAxis.rangeController!.end : _categoryAxis.maximum ?? _max); final List seriesRenderers = _seriesRenderers; CartesianSeriesRenderer seriesRenderer; for (int i = 0; i < seriesRenderers.length; i++) { seriesRenderer = seriesRenderers[i]; - if (_actualRange.maximum > seriesRenderer._dataPoints.length - 1) { - for (int i = _labels.length; i < _actualRange.maximum + 1; i++) { + if (_actualRange!.maximum > seriesRenderer._dataPoints.length - 1) { + for (int i = _labels.length; i < _actualRange!.maximum + 1; i++) { _labels.add(i.toString()); } } @@ -344,24 +272,33 @@ class CategoryAxisRenderer extends ChartAxisRenderer { _categoryAxis.minimum ?? _min, _categoryAxis.maximum ?? _max); ///Below condition is for checking the min, max value is equal - if ((_actualRange.minimum == _actualRange.maximum) && + if ((_actualRange!.minimum == _actualRange!.maximum) && (_categoryAxis.labelPlacement == LabelPlacement.onTicks)) { - _actualRange.maximum += 1; + _actualRange!.maximum += 1; } - _actualRange.delta = _actualRange.maximum - _actualRange.minimum; - _actualRange.interval = _categoryAxis.interval ?? - calculateInterval(_actualRange, Size(_rect.width, _rect.height)); - _actualRange.delta = _actualRange.maximum - _actualRange.minimum; + _actualRange!.delta = _actualRange!.maximum - _actualRange!.minimum; + _actualRange!.interval = _categoryAxis.interval ?? + calculateInterval(_actualRange!, Size(_rect.width, _rect.height)); + _actualRange!.delta = _actualRange!.maximum - _actualRange!.minimum; } } /// Calculates the visible range for an axis in chart. @override void calculateVisibleRange(Size availableSize) { - _visibleRange = _VisibleRange(_actualRange.minimum, _actualRange.maximum); - _visibleRange.delta = _actualRange.delta; - _visibleRange.interval = _actualRange.interval; - _checkWithZoomState(this, _chartState._zoomedAxisRendererStates); + _visibleRange = _VisibleRange(_actualRange!.minimum, _actualRange!.maximum); + _visibleRange!.delta = _actualRange!.delta; + _visibleRange!.interval = _actualRange!.interval; + bool canAutoScroll = false; + if (_categoryAxis.autoScrollingDelta != null && + _categoryAxis.autoScrollingDelta! > 0 && + !_chartState._isRedrawByZoomPan) { + canAutoScroll = true; + super._updateAutoScrollingDelta(_categoryAxis.autoScrollingDelta!, this); + } + if (!canAutoScroll) { + _setZoomFactorAndPosition(this, _chartState._zoomedAxisRendererStates); + } if (_zoomFactor < 1 || _zoomPosition > 0) { _chartState._zoomProgress = true; _calculateZoomRange(this, availableSize); @@ -374,7 +311,7 @@ class CategoryAxisRenderer extends ChartAxisRenderer { /// Applies range padding @override - void applyRangePadding(_VisibleRange range, num interval) { + void applyRangePadding(_VisibleRange range, num? interval) { ActualRangeChangedArgs rangeChangedArgs; if (_categoryAxis.labelPlacement == LabelPlacement.betweenTicks) { range.minimum -= 0.5; @@ -382,10 +319,9 @@ class CategoryAxisRenderer extends ChartAxisRenderer { range.delta = range.maximum - range.minimum; } - if (_categoryAxis.isVisible && - !(_categoryAxis.minimum != null && _categoryAxis.maximum != null)) { + if (!(_categoryAxis.minimum != null && _categoryAxis.maximum != null)) { ///Calculating range padding - _applyRangePadding(this, _chartState, range, interval); + _applyRangePadding(this, _chartState, range, interval!); } calculateVisibleRange(Size(_rect.width, _rect.height)); @@ -394,56 +330,58 @@ class CategoryAxisRenderer extends ChartAxisRenderer { if ((_categoryAxis.visibleMinimum != null || _categoryAxis.visibleMaximum != null) && (_categoryAxis.visibleMinimum != _categoryAxis.visibleMaximum) && - _chartState._zoomedAxisRendererStates != null && - _chartState._zoomedAxisRendererStates.isEmpty) { - _visibleRange.minimum = - _categoryAxis.visibleMinimum ?? _visibleRange.minimum; - _visibleRange.maximum = - _categoryAxis.visibleMaximum ?? _visibleRange.maximum; + (!_chartState._isRedrawByZoomPan)) { + _chartState._isRedrawByZoomPan = false; + _visibleRange!.minimum = _visibleMinimum ?? + _categoryAxis.visibleMinimum ?? + _visibleRange!.minimum; + _visibleRange!.maximum = _visibleMaximum ?? + _categoryAxis.visibleMaximum ?? + _visibleRange!.maximum; if (_categoryAxis.labelPlacement == LabelPlacement.betweenTicks) { - _visibleRange.minimum = _categoryAxis.visibleMinimum != null - ? _categoryAxis.visibleMinimum - 0.5 - : _visibleRange.minimum; - _visibleRange.maximum = _categoryAxis.visibleMaximum != null - ? _categoryAxis.visibleMaximum + 0.5 - : _visibleRange.maximum; + _visibleRange!.minimum = _categoryAxis.visibleMinimum != null + ? (_visibleMinimum ?? _categoryAxis.visibleMinimum!) - 0.5 + : _visibleRange!.minimum; + _visibleRange!.maximum = _categoryAxis.visibleMaximum != null + ? (_visibleMaximum ?? _categoryAxis.visibleMaximum!) + 0.5 + : _visibleRange!.maximum; } - _visibleRange.delta = _visibleRange.maximum - _visibleRange.minimum; - _visibleRange.interval = interval == null - ? calculateInterval(_visibleRange, _axisSize) - : _visibleRange.interval; - _zoomFactor = _visibleRange.delta / (range.delta); + _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; + _visibleRange!.interval = interval == null + ? calculateInterval(_visibleRange!, _axisSize) + : _visibleRange!.interval; + _zoomFactor = _visibleRange!.delta / (range.delta); _zoomPosition = - (_visibleRange.minimum - _actualRange.minimum) / range.delta; + (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; } - if (_categoryAxis.isVisible && _chart.onActualRangeChanged != null) { - rangeChangedArgs = ActualRangeChangedArgs(_name, _categoryAxis, - range.minimum, range.maximum, range.interval, _orientation); - rangeChangedArgs.visibleMin = _visibleRange.minimum; - rangeChangedArgs.visibleMax = _visibleRange.maximum; - rangeChangedArgs.visibleInterval = _visibleRange.interval; - _chart.onActualRangeChanged(rangeChangedArgs); - _visibleRange.minimum = rangeChangedArgs.visibleMin; - _visibleRange.maximum = rangeChangedArgs.visibleMax; - _visibleRange.delta = _visibleRange.maximum - _visibleRange.minimum; - _visibleRange.interval = rangeChangedArgs.visibleInterval; - _zoomFactor = _visibleRange.delta / (range.delta); + if (_chart.onActualRangeChanged != null) { + rangeChangedArgs = ActualRangeChangedArgs(_name!, _categoryAxis, + range.minimum, range.maximum, range.interval, _orientation!); + rangeChangedArgs.visibleMin = _visibleRange!.minimum; + rangeChangedArgs.visibleMax = _visibleRange!.maximum; + rangeChangedArgs.visibleInterval = _visibleRange!.interval; + _chart.onActualRangeChanged!(rangeChangedArgs); + _visibleRange!.minimum = rangeChangedArgs.visibleMin; + _visibleRange!.maximum = rangeChangedArgs.visibleMax; + _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; + _visibleRange!.interval = rangeChangedArgs.visibleInterval; + _zoomFactor = _visibleRange!.delta / (range.delta); _zoomPosition = - (_visibleRange.minimum - _actualRange.minimum) / range.delta; + (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; } } /// Generates the visible axis labels. @override void generateVisibleLabels() { - num tempInterval = _visibleRange.minimum.ceil(); + num tempInterval = _visibleRange!.minimum.ceil(); num position; String labelText; _visibleLabels = []; for (; - tempInterval <= _visibleRange.maximum; - tempInterval += _visibleRange.interval) { - if (_withInRange(tempInterval, _visibleRange)) { + tempInterval <= _visibleRange!.maximum; + tempInterval += _visibleRange!.interval) { + if (_withInRange(tempInterval, _visibleRange!)) { position = tempInterval.round(); if (position <= -1 || (_labels.isNotEmpty && position >= _labels.length)) { @@ -464,7 +402,7 @@ class CategoryAxisRenderer extends ChartAxisRenderer { num calculateInterval(_VisibleRange range, Size availableSize) => math .max( 1, - (_actualRange.delta / + (_actualRange!.delta / _calculateDesiredIntervalCount( Size(_rect.width, _rect.height), this)) .floor()) diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart index b266cf8e2..c9b396b8d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart @@ -11,84 +11,87 @@ part of charts; class DateTimeAxis extends ChartAxis { /// Creating an argument constructor of DateTimeAxis class. DateTimeAxis({ - String name, - bool isVisible, - AxisTitle title, - AxisLine axisLine, - ChartRangePadding rangePadding, - AxisLabelIntersectAction labelIntersectAction, - ChartDataLabelPosition labelPosition, - TickPosition tickPosition, - EdgeLabelPlacement edgeLabelPlacement, - double zoomFactor, - double zoomPosition, - bool enableAutoIntervalOnZooming, - int labelRotation, - bool isInversed, - bool opposedPosition, - int minorTicksPerInterval, - int maximumLabels, - double plotOffset, - MajorTickLines majorTickLines, - MinorTickLines minorTickLines, - MajorGridLines majorGridLines, - MinorGridLines minorGridLines, - TextStyle labelStyle, + String? name, + bool? isVisible, + AxisTitle? title, + AxisLine? axisLine, + ChartRangePadding? rangePadding, + AxisLabelIntersectAction? labelIntersectAction, + ChartDataLabelPosition? labelPosition, + TickPosition? tickPosition, + EdgeLabelPlacement? edgeLabelPlacement, + double? zoomFactor, + double? zoomPosition, + bool? enableAutoIntervalOnZooming, + int? labelRotation, + bool? isInversed, + bool? opposedPosition, + int? minorTicksPerInterval, + int? maximumLabels, + double? plotOffset, + MajorTickLines? majorTickLines, + MinorTickLines? minorTickLines, + MajorGridLines? majorGridLines, + MinorGridLines? minorGridLines, + TextStyle? labelStyle, this.dateFormat, - DateTimeIntervalType intervalType, - InteractiveTooltip interactiveTooltip, + this.intervalType = DateTimeIntervalType.auto, + InteractiveTooltip? interactiveTooltip, this.labelFormat, this.minimum, this.maximum, - LabelAlignment labelAlignment, - double interval, + LabelAlignment? labelAlignment, + double? interval, this.visibleMinimum, this.visibleMaximum, - dynamic crossesAt, - String associatedAxisName, - bool placeLabelsNearAxisLine, - List plotBands, - RangeController rangeController, - int desiredIntervals, - double maximumLabelWidth, - double labelsExtent, - }) : intervalType = intervalType ?? DateTimeIntervalType.auto, - super( - name: name, - isVisible: isVisible, - isInversed: isInversed, - opposedPosition: opposedPosition, - rangePadding: rangePadding, - plotOffset: plotOffset, - labelRotation: labelRotation, - labelIntersectAction: labelIntersectAction, - minorTicksPerInterval: minorTicksPerInterval, - maximumLabels: maximumLabels, - labelStyle: labelStyle, - title: title, - labelAlignment: labelAlignment, - axisLine: axisLine, - majorTickLines: majorTickLines, - minorTickLines: minorTickLines, - majorGridLines: majorGridLines, - minorGridLines: minorGridLines, - edgeLabelPlacement: edgeLabelPlacement, - labelPosition: labelPosition, - tickPosition: tickPosition, - zoomFactor: zoomFactor, - zoomPosition: zoomPosition, - enableAutoIntervalOnZooming: enableAutoIntervalOnZooming, - interactiveTooltip: interactiveTooltip, - interval: interval, - crossesAt: crossesAt, - associatedAxisName: associatedAxisName, - placeLabelsNearAxisLine: placeLabelsNearAxisLine, - plotBands: plotBands, - rangeController: rangeController, - desiredIntervals: desiredIntervals, - maximumLabelWidth: maximumLabelWidth, - labelsExtent: labelsExtent, - ); + dynamic? crossesAt, + String? associatedAxisName, + bool? placeLabelsNearAxisLine, + List? plotBands, + RangeController? rangeController, + int? desiredIntervals, + double? maximumLabelWidth, + double? labelsExtent, + this.autoScrollingDeltaType = DateTimeIntervalType.auto, + int? autoScrollingDelta, + AutoScrollingMode? autoScrollingMode, + }) : super( + name: name, + isVisible: isVisible, + isInversed: isInversed, + opposedPosition: opposedPosition, + rangePadding: rangePadding, + plotOffset: plotOffset, + labelRotation: labelRotation, + labelIntersectAction: labelIntersectAction, + minorTicksPerInterval: minorTicksPerInterval, + maximumLabels: maximumLabels, + labelStyle: labelStyle, + title: title, + labelAlignment: labelAlignment, + axisLine: axisLine, + majorTickLines: majorTickLines, + minorTickLines: minorTickLines, + majorGridLines: majorGridLines, + minorGridLines: minorGridLines, + edgeLabelPlacement: edgeLabelPlacement, + labelPosition: labelPosition, + tickPosition: tickPosition, + zoomFactor: zoomFactor, + zoomPosition: zoomPosition, + enableAutoIntervalOnZooming: enableAutoIntervalOnZooming, + interactiveTooltip: interactiveTooltip, + interval: interval, + crossesAt: crossesAt, + associatedAxisName: associatedAxisName, + placeLabelsNearAxisLine: placeLabelsNearAxisLine, + plotBands: plotBands, + rangeController: rangeController, + desiredIntervals: desiredIntervals, + maximumLabelWidth: maximumLabelWidth, + labelsExtent: labelsExtent, + autoScrollingDelta: autoScrollingDelta, + autoScrollingMode: autoScrollingMode); ///Formats the date-time axis labels. The default data-time axis label can be formatted ///with various built-in date formats. @@ -100,7 +103,7 @@ class DateTimeAxis extends ChartAxis { /// )); ///} ///``` - final DateFormat dateFormat; + final DateFormat? dateFormat; ///Formats the date time-axis labels. The labels can be customized by adding desired ///text to prefix or suffix. @@ -112,7 +115,7 @@ class DateTimeAxis extends ChartAxis { /// )); ///} ///``` - final String labelFormat; + final String? labelFormat; ///Customizes the date-time axis intervals. Intervals can be set to days, hours, ///milliseconds, minutes, months, seconds, years, and auto. If it is set to auto, @@ -142,7 +145,7 @@ class DateTimeAxis extends ChartAxis { /// )); ///} ///``` - final DateTime minimum; + final DateTime? minimum; ///Maximum value of the axis. The axis will end at this date. /// @@ -155,7 +158,7 @@ class DateTimeAxis extends ChartAxis { /// )); ///} ///``` - final DateTime maximum; + final DateTime? maximum; ///The minimum visible value of the axis. The axis will be rendered from this date initially. /// @@ -168,7 +171,7 @@ class DateTimeAxis extends ChartAxis { /// )); ///} ///``` - final DateTime visibleMinimum; + final DateTime? visibleMinimum; ///The maximum visible value of the axis. The axis will be rendered from this date initially. /// @@ -181,40 +184,58 @@ class DateTimeAxis extends ChartAxis { /// )); ///} ///``` - final DateTime visibleMaximum; + final DateTime? visibleMaximum; + + ///Defines the type of delta value in the DateTime axis. + /// + ///For example, if the [autoScrollingDelta] value is 5 and [autoScrollingDeltaType] is set to + /// `DateTimeIntervalType.days`, the data points with 5 days of values will be displayed. + /// + ///The value can be set to years, months, days, hours, minutes, seconds and auto. + /// + ///Defaults to `DateTimeIntervalType.auto` and the delta will be calculated automatically based on the data. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeAxis(autoScrollingDeltaType: DateTimeIntervalType.months), + /// )); + ///} + ///``` + final DateTimeIntervalType autoScrollingDeltaType; } /// Creates an axis renderer for Datetime axis class DateTimeAxisRenderer extends ChartAxisRenderer { /// Creating an argument constructor of DateTimeAxisRenderer class. DateTimeAxisRenderer(this._dateTimeAxis) : super(_dateTimeAxis); - DateTimeIntervalType _actualIntervalType; - int _dateTimeInterval; - @override - _VisibleRange _visibleRange; + late DateTimeIntervalType _actualIntervalType; + late int _dateTimeInterval; + @override - SfCartesianChart _chart; + late SfCartesianChart _chart; @override - Size _axisSize; + late Size _axisSize; final DateTimeAxis _dateTimeAxis; /// Find the series min and max values of an series void _findAxisMinMaxValues(CartesianSeriesRenderer seriesRenderer, CartesianChartPoint point, int pointIndex, int dataLength, - [bool isXVisibleRange, bool isYVisibleRange]) { + [bool? isXVisibleRange, bool? isYVisibleRange]) { if (point.x != null) { point.xValue = (point.x).millisecondsSinceEpoch; } final bool _anchorRangeToVisiblePoints = - seriesRenderer._yAxisRenderer._axis.anchorRangeToVisiblePoints; + seriesRenderer._yAxisRenderer!._axis.anchorRangeToVisiblePoints; final String seriesType = seriesRenderer._seriesType; point.yValue = point.y; - if (isYVisibleRange) { + if (isYVisibleRange!) { seriesRenderer._minimumX ??= point.xValue; seriesRenderer._maximumX ??= point.xValue; } - if ((isXVisibleRange || !_anchorRangeToVisiblePoints) && + if ((isXVisibleRange! || !_anchorRangeToVisiblePoints) && !seriesType.contains('range') && !seriesType.contains('hilo') && !seriesType.contains('candle') && @@ -225,9 +246,9 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { } if (isYVisibleRange && point.xValue != null) { seriesRenderer._minimumX = - math.min(seriesRenderer._minimumX, point.xValue); + math.min(seriesRenderer._minimumX!, point.xValue); seriesRenderer._maximumX = - math.max(seriesRenderer._maximumX, point.xValue); + math.max(seriesRenderer._maximumX!, point.xValue); } if (isXVisibleRange || !_anchorRangeToVisiblePoints) { if (point.yValue != null && @@ -237,9 +258,9 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { seriesType != 'boxandwhisker' && seriesType != 'waterfall')) { seriesRenderer._minimumY = - math.min(seriesRenderer._minimumY, point.yValue); + math.min(seriesRenderer._minimumY!, point.yValue); seriesRenderer._maximumY = - math.max(seriesRenderer._maximumY, point.yValue); + math.max(seriesRenderer._maximumY!, point.yValue); } if (point.high != null) { _highMin = math.min(_highMin ?? point.high, point.high); @@ -250,12 +271,12 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { _lowMax = math.max(_lowMax ?? point.low, point.low); } if (point.maximum != null) { - _highMin = _findMinValue(_highMin ?? point.maximum, point.maximum); - _highMax = _findMaxValue(_highMax ?? point.maximum, point.maximum); + _highMin = _findMinValue(_highMin ?? point.maximum!, point.maximum!); + _highMax = _findMaxValue(_highMax ?? point.maximum!, point.maximum!); } if (point.minimum != null) { - _lowMin = _findMinValue(_lowMin ?? point.minimum, point.minimum); - _lowMax = _findMaxValue(_lowMax ?? point.minimum, point.minimum); + _lowMin = _findMinValue(_lowMin ?? point.minimum!, point.minimum!); + _lowMax = _findMaxValue(_lowMax ?? point.minimum!, point.minimum!); } if (seriesType == 'waterfall') { /// Empty point is not applicable for Waterfall series. @@ -263,7 +284,7 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { seriesRenderer._minimumY = math.min(seriesRenderer._minimumY ?? point.yValue, point.yValue); seriesRenderer._maximumY = math.max( - seriesRenderer._maximumY ?? point.maxYValue, point.maxYValue); + (seriesRenderer._maximumY ?? point.maxYValue), point.maxYValue); } } if (pointIndex >= dataLength - 1) { @@ -275,8 +296,8 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { _lowMax ??= 5; _highMin ??= 0; _highMax ??= 5; - seriesRenderer._minimumY = math.min(_lowMin, _highMin); - seriesRenderer._maximumY = math.max(_lowMax, _highMax); + seriesRenderer._minimumY = math.min(_lowMin!, _highMin!); + seriesRenderer._maximumY = math.max(_lowMax!, _highMax!); } seriesRenderer._minimumX ??= 2717008000; seriesRenderer._maximumX ??= 13085008000; @@ -293,12 +314,12 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { /// Calculate axis range and interval void _calculateRangeAndInterval(SfCartesianChartState chartState, - [String type]) { + [String? type]) { _chartState = chartState; _chart = chartState._chart; if (_axis.rangeController != null) { _chartState._rangeChangeBySlider = true; - _axis.rangeController.addListener(_controlListener); + _axis.rangeController!.addListener(_controlListener); } final Rect containerRect = _chartState._containerRect; final Rect rect = Rect.fromLTWH(containerRect.left, containerRect.top, @@ -307,7 +328,7 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { calculateRange(this); _calculateActualRange(); if (_actualRange != null) { - applyRangePadding(_actualRange, _actualRange.interval); + applyRangePadding(_actualRange!, _actualRange!.interval); if (type == null && type != 'AxisCross' && _dateTimeAxis.isVisible) { generateVisibleLabels(); } @@ -323,56 +344,25 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { _chartState._rangeChangeBySlider && _dateTimeAxis.rangeController != null ? _rangeMinimum ?? - _dateTimeAxis.rangeController.start.millisecondsSinceEpoch + _dateTimeAxis.rangeController!.start.millisecondsSinceEpoch : _dateTimeAxis.minimum != null - ? _dateTimeAxis.minimum.millisecondsSinceEpoch + ? _dateTimeAxis.minimum!.millisecondsSinceEpoch : _min, _chartState._rangeChangeBySlider && _dateTimeAxis.rangeController != null ? _rangeMaximum ?? - _dateTimeAxis.rangeController.end.millisecondsSinceEpoch + _dateTimeAxis.rangeController!.end.millisecondsSinceEpoch : _dateTimeAxis.maximum != null - ? _dateTimeAxis.maximum.millisecondsSinceEpoch + ? _dateTimeAxis.maximum!.millisecondsSinceEpoch : _max); - if (_actualRange.minimum == _actualRange.maximum) { - _actualRange.minimum = _actualRange.minimum - 2592000000; - _actualRange.maximum = _actualRange.maximum + 2592000000; + if (_actualRange!.minimum == _actualRange!.maximum) { + _actualRange!.minimum = _actualRange!.minimum - 2592000000; + _actualRange!.maximum = _actualRange!.maximum + 2592000000; } _dateTimeInterval = - _calculateDateTimeNiceInterval(this, _axisSize, _actualRange).floor(); - _actualRange.interval = _dateTimeAxis.interval ?? _dateTimeInterval; - _actualRange.delta = _actualRange.maximum - _actualRange.minimum; - } - - /// To get the label format of the date-time axis - DateFormat _getLabelFormat(DateTimeAxisRenderer axisRenderer) { - DateFormat format; - final bool notDoubleInterval = (axisRenderer._axis.interval != null && - axisRenderer._axis.interval % 1 == 0) || - axisRenderer._axis.interval == null; - switch (axisRenderer._actualIntervalType) { - case DateTimeIntervalType.years: - format = notDoubleInterval ? DateFormat.yMMM() : DateFormat.MMMd(); - break; - case DateTimeIntervalType.months: - format = notDoubleInterval ? DateFormat.MMMd() : DateFormat.MMMd(); - break; - case DateTimeIntervalType.days: - format = notDoubleInterval ? DateFormat.MMMd() : DateFormat.Hm(); - break; - case DateTimeIntervalType.hours: - format = notDoubleInterval ? DateFormat.Hm() : DateFormat.ms(); - break; - case DateTimeIntervalType.minutes: - format = notDoubleInterval ? DateFormat.ms() : DateFormat.ms(); - break; - case DateTimeIntervalType.seconds: - format = notDoubleInterval ? DateFormat.ms() : DateFormat.ms(); - break; - case DateTimeIntervalType.auto: - break; - } - return format; + _calculateDateTimeNiceInterval(this, _axisSize, _actualRange!).floor(); + _actualRange!.interval = _dateTimeAxis.interval ?? _dateTimeInterval; + _actualRange!.delta = _actualRange!.maximum - _actualRange!.minimum; } /// Returns the range start values based on actual interval type @@ -419,12 +409,13 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { /// Increase the range interval based on actual interval type DateTime _increaseDateTimeInterval( - DateTimeAxisRenderer axisRenderer, int value, dynamic interval) { + DateTimeAxisRenderer axisRenderer, int value, dynamic dateInterval) { DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(value); - axisRenderer._visibleRange.interval = interval; - final bool isIntervalDecimal = interval % 1 == 0; + axisRenderer._visibleRange!.interval = dateInterval; + final bool isIntervalDecimal = dateInterval % 1 == 0; + final num interval = dateInterval; if (isIntervalDecimal) { - interval = interval.floor(); + final int interval = dateInterval.floor(); switch (axisRenderer._actualIntervalType) { case DateTimeIntervalType.years: dateTime = DateTime(dateTime.year + interval, dateTime.month, @@ -649,134 +640,19 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { } } - /// Calculate date time nice interval - int _calculateDateTimeNiceInterval( - DateTimeAxisRenderer axisRenderer, Size size, _VisibleRange range) { - final DateTimeAxis axis = axisRenderer._axis; - final bool notDoubleInterval = - (axis.visibleMinimum == null || axis.visibleMaximum == null) || - (axis.interval != null && axis.interval % 1 == 0) || - (axis.interval == null); - const int perDay = 24 * 60 * 60 * 1000; - final DateTime startDate = - DateTime.fromMillisecondsSinceEpoch(range.minimum.toInt()); - final DateTime endDate = - DateTime.fromMillisecondsSinceEpoch(range.maximum.toInt()); - num interval; - final num totalDays = - ((startDate.millisecondsSinceEpoch - endDate.millisecondsSinceEpoch) / - perDay) - .abs(); - axisRenderer._actualIntervalType = axis.intervalType; - switch (axis.intervalType) { - case DateTimeIntervalType.years: - interval = - _calculateNumericNiceInterval(axisRenderer, totalDays / 365, size); - break; - case DateTimeIntervalType.months: - interval = - _calculateNumericNiceInterval(axisRenderer, totalDays / 30, size); - break; - case DateTimeIntervalType.days: - interval = _calculateNumericNiceInterval(axisRenderer, totalDays, size); - break; - case DateTimeIntervalType.hours: - interval = - _calculateNumericNiceInterval(axisRenderer, totalDays * 24, size); - break; - case DateTimeIntervalType.minutes: - interval = _calculateNumericNiceInterval( - axisRenderer, totalDays * 24 * 60, size); - break; - case DateTimeIntervalType.seconds: - interval = _calculateNumericNiceInterval( - axisRenderer, totalDays * 24 * 60 * 60, size); - break; - case DateTimeIntervalType.auto: - - /// for years - interval = - _calculateNumericNiceInterval(axisRenderer, totalDays / 365, size); - if (interval >= 1) { - axisRenderer._actualIntervalType = DateTimeIntervalType.years; - return interval.floor(); - } - - /// for months - interval = - _calculateNumericNiceInterval(axisRenderer, totalDays / 30, size); - if (interval >= 1) { - axisRenderer._actualIntervalType = notDoubleInterval - ? DateTimeIntervalType.months - : DateTimeIntervalType.years; - return interval.floor(); - } - - /// for days - interval = _calculateNumericNiceInterval(axisRenderer, totalDays, size); - if (interval >= 1) { - axisRenderer._actualIntervalType = notDoubleInterval - ? DateTimeIntervalType.days - : DateTimeIntervalType.months; - return interval.floor(); - } - - /// for hours - interval = - _calculateNumericNiceInterval(axisRenderer, totalDays * 24, size); - if (interval >= 1) { - axisRenderer._actualIntervalType = notDoubleInterval - ? DateTimeIntervalType.hours - : DateTimeIntervalType.days; - return interval.floor(); - } - - /// for minutes - interval = _calculateNumericNiceInterval( - axisRenderer, totalDays * 24 * 60, size); - if (interval >= 1) { - axisRenderer._actualIntervalType = notDoubleInterval - ? DateTimeIntervalType.minutes - : DateTimeIntervalType.hours; - return interval.floor(); - } - - /// for seconds - interval = _calculateNumericNiceInterval( - axisRenderer, totalDays * 24 * 60 * 60, size); - axisRenderer._actualIntervalType = notDoubleInterval - ? DateTimeIntervalType.seconds - : DateTimeIntervalType.minutes; - return interval.floor(); - break; - } - return interval < 1 ? interval.ceil() : interval.floor(); - } - - /// Find min and max values - @override - void _findMinMax(num minimumValue, num maximumValue) { - if (_min == null || _min > minimumValue) { - _min = minimumValue; - } - if (_max == null || _max < maximumValue) { - _max = maximumValue; - } - } - /// Applies range padding to auto, normal, additional, round, and none types. @override - void applyRangePadding(_VisibleRange range, num interval) { + void applyRangePadding(_VisibleRange range, num? interval) { _min = range.minimum.toInt(); _max = range.maximum.toInt(); ActualRangeChangedArgs rangeChangedArgs; - if (_dateTimeAxis.isVisible && - _dateTimeAxis.minimum == null && - _dateTimeAxis.maximum == null) { + if (_dateTimeAxis.minimum == null && _dateTimeAxis.maximum == null) { final ChartRangePadding rangePadding = _calculateRangePadding(this, _chart); - final DateTime minimum = DateTime.fromMillisecondsSinceEpoch(_min); - final DateTime maximum = DateTime.fromMillisecondsSinceEpoch(_max); + final DateTime minimum = + DateTime.fromMillisecondsSinceEpoch(_min!.toInt()); + final DateTime maximum = + DateTime.fromMillisecondsSinceEpoch(_max!.toInt()); if (rangePadding == ChartRangePadding.none) { _min = minimum.millisecondsSinceEpoch; _max = maximum.millisecondsSinceEpoch; @@ -784,22 +660,22 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { rangePadding == ChartRangePadding.round) { switch (_actualIntervalType) { case DateTimeIntervalType.years: - _calculateYear(minimum, maximum, rangePadding, interval.toInt()); + _calculateYear(minimum, maximum, rangePadding, interval!.toInt()); break; case DateTimeIntervalType.months: - _calculateMonth(minimum, maximum, rangePadding, interval.toInt()); + _calculateMonth(minimum, maximum, rangePadding, interval!.toInt()); break; case DateTimeIntervalType.days: - _calculateDay(minimum, maximum, rangePadding, interval.toInt()); + _calculateDay(minimum, maximum, rangePadding, interval!.toInt()); break; case DateTimeIntervalType.hours: - _calculateHour(minimum, maximum, rangePadding, interval.toInt()); + _calculateHour(minimum, maximum, rangePadding, interval!.toInt()); break; case DateTimeIntervalType.minutes: - _calculateMinute(minimum, maximum, rangePadding, interval.toInt()); + _calculateMinute(minimum, maximum, rangePadding, interval!.toInt()); break; case DateTimeIntervalType.seconds: - _calculateSecond(minimum, maximum, rangePadding, interval.toInt()); + _calculateSecond(minimum, maximum, rangePadding, interval!.toInt()); break; case DateTimeIntervalType.auto: break; @@ -816,89 +692,73 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { if ((_dateTimeAxis.visibleMinimum != null || _dateTimeAxis.visibleMaximum != null) && (_dateTimeAxis.visibleMinimum != _dateTimeAxis.visibleMaximum) && - _chartState._zoomedAxisRendererStates != null && - _chartState._zoomedAxisRendererStates.isEmpty) { - _visibleRange.minimum = _dateTimeAxis.visibleMinimum != null - ? _dateTimeAxis.visibleMinimum.millisecondsSinceEpoch - : _visibleRange.minimum; - _visibleRange.maximum = _dateTimeAxis.visibleMaximum != null - ? _dateTimeAxis.visibleMaximum.millisecondsSinceEpoch - : _visibleRange.maximum; - _visibleRange.delta = _visibleRange.maximum - _visibleRange.minimum; - _visibleRange.interval = calculateInterval(_visibleRange, _axisSize); - _visibleRange.interval = interval != null && interval % 1 != 0 + (!_chartState._isRedrawByZoomPan)) { + _chartState._isRedrawByZoomPan = false; + _visibleRange!.minimum = _visibleMinimum ?? + (_dateTimeAxis.visibleMinimum != null + ? _dateTimeAxis.visibleMinimum!.millisecondsSinceEpoch + : _visibleRange!.minimum); + _visibleRange!.maximum = _visibleMaximum ?? + (_dateTimeAxis.visibleMaximum != null + ? _dateTimeAxis.visibleMaximum!.millisecondsSinceEpoch + : _visibleRange!.maximum); + _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; + _visibleRange!.interval = calculateInterval(_visibleRange!, _axisSize); + _visibleRange!.interval = interval != null && interval % 1 != 0 ? interval - : _visibleRange.interval; - _zoomFactor = _visibleRange.delta / (range.delta); + : _visibleRange!.interval; + _zoomFactor = _visibleRange!.delta / (range.delta); _zoomPosition = - (_visibleRange.minimum - _actualRange.minimum) / range.delta; + (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; } - if (_dateTimeAxis.isVisible && _chart.onActualRangeChanged != null) { - rangeChangedArgs = ActualRangeChangedArgs(_name, _dateTimeAxis, - range.minimum, range.maximum, range.interval, _orientation); - rangeChangedArgs.visibleMin = _visibleRange.minimum; - rangeChangedArgs.visibleMax = _visibleRange.maximum; - rangeChangedArgs.visibleInterval = _visibleRange.interval; - _chart.onActualRangeChanged(rangeChangedArgs); - _visibleRange.minimum = rangeChangedArgs.visibleMin is DateTime + if (_chart.onActualRangeChanged != null) { + rangeChangedArgs = ActualRangeChangedArgs(_name!, _dateTimeAxis, + range.minimum, range.maximum, range.interval, _orientation!); + rangeChangedArgs.visibleMin = _visibleRange!.minimum; + rangeChangedArgs.visibleMax = _visibleRange!.maximum; + rangeChangedArgs.visibleInterval = _visibleRange!.interval; + _chart.onActualRangeChanged!(rangeChangedArgs); + _visibleRange!.minimum = rangeChangedArgs.visibleMin is DateTime ? rangeChangedArgs.visibleMin.millisecondsSinceEpoch : rangeChangedArgs.visibleMin; - _visibleRange.maximum = rangeChangedArgs.visibleMax is DateTime + _visibleRange!.maximum = rangeChangedArgs.visibleMax is DateTime ? rangeChangedArgs.visibleMax.millisecondsSinceEpoch : rangeChangedArgs.visibleMax; - _visibleRange.delta = _visibleRange.maximum - _visibleRange.minimum; - _visibleRange.interval = rangeChangedArgs.visibleInterval; - _zoomFactor = _visibleRange.delta / (range.delta); + _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; + _visibleRange!.interval = rangeChangedArgs.visibleInterval; + _zoomFactor = _visibleRange!.delta / (range.delta); _zoomPosition = - (_visibleRange.minimum - _actualRange.minimum) / range.delta; + (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; } } /// Calculates the visible range for an axis in chart. @override void calculateVisibleRange(Size availableSize) { - _visibleRange = _VisibleRange(_actualRange.minimum, _actualRange.maximum); - _visibleRange.delta = _actualRange.delta; - _visibleRange.interval = _actualRange.interval; - - _checkWithZoomState(this, _chartState._zoomedAxisRendererStates); - if (_zoomFactor < 1 || _zoomPosition > 0) { - _chartState._zoomProgress = true; - _calculateZoomRange(this, availableSize); - _visibleRange.interval = - _axis.enableAutoIntervalOnZooming && _chartState._zoomProgress - ? calculateInterval(_visibleRange, availableSize) - : _visibleRange.interval; - _visibleRange.minimum = (_visibleRange.minimum).floor(); - _visibleRange.maximum = (_visibleRange.maximum).floor(); - if (_axis.rangeController != null) { - _chartState._rangeChangedByChart = true; - _setRangeControllerValues(this); - } - } + _calculateDateTimeVisibleRange(availableSize, this); } /// Generates the visible axis labels. @override void generateVisibleLabels() { _visibleLabels = []; - int interval = _visibleRange.minimum; - interval = _alignRangeStart(this, interval, _visibleRange.interval); - while (interval <= _visibleRange.maximum) { - if (_withInRange(interval, _visibleRange)) { + int interval = _visibleRange!.minimum; + interval = _alignRangeStart(this, interval, _visibleRange!.interval); + while (interval <= _visibleRange!.maximum) { + if (_withInRange(interval, _visibleRange!)) { final DateFormat format = - _dateTimeAxis.dateFormat ?? _getLabelFormat(this); + _dateTimeAxis.dateFormat ?? _getDateTimeLabelFormat(this); String labelText = format.format(DateTime.fromMillisecondsSinceEpoch(interval)); if (_dateTimeAxis.labelFormat != null && _dateTimeAxis.labelFormat != '') { - labelText = _dateTimeAxis.labelFormat + labelText = _dateTimeAxis.labelFormat! .replaceAll(RegExp('{value}'), labelText); } _triggerLabelRenderEvent(labelText, interval); } interval = - _increaseDateTimeInterval(this, interval, _visibleRange.interval) + _increaseDateTimeInterval(this, interval, _visibleRange!.interval) .millisecondsSinceEpoch; } _calculateMaximumLabelSize(this, _chartState); @@ -908,4 +768,66 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { @override num calculateInterval(_VisibleRange range, Size availableSize) => _calculateDateTimeNiceInterval(this, _axisSize, range).floor(); + + ///Auto Scrolling feature + void _updateScrollingDelta() { + if (_dateTimeAxis.autoScrollingDelta != null && + _dateTimeAxis.autoScrollingDelta! > 0) { + final DateTimeIntervalType intervalType = + _dateTimeAxis.autoScrollingDeltaType == DateTimeIntervalType.auto + ? _actualIntervalType + : _dateTimeAxis.autoScrollingDeltaType; + int scrollingDelta; + DateTime dateTime = + DateTime.fromMillisecondsSinceEpoch(_visibleRange!.maximum); + switch (intervalType) { + case DateTimeIntervalType.years: + dateTime = DateTime( + dateTime.year - _dateTimeAxis.autoScrollingDelta!, + dateTime.month, + dateTime.day, + dateTime.hour, + dateTime.minute, + dateTime.second, + dateTime.millisecond, + dateTime.microsecond); + scrollingDelta = + _visibleRange!.maximum - dateTime.millisecondsSinceEpoch; + break; + case DateTimeIntervalType.months: + dateTime = DateTime( + dateTime.year, + dateTime.month - _dateTimeAxis.autoScrollingDelta!, + dateTime.day, + dateTime.hour, + dateTime.minute, + dateTime.second, + dateTime.millisecond, + dateTime.microsecond); + scrollingDelta = + _visibleRange!.maximum - dateTime.millisecondsSinceEpoch; + break; + case DateTimeIntervalType.days: + scrollingDelta = + Duration(days: _dateTimeAxis.autoScrollingDelta!).inMilliseconds; + break; + case DateTimeIntervalType.hours: + scrollingDelta = + Duration(hours: _dateTimeAxis.autoScrollingDelta!).inMilliseconds; + break; + case DateTimeIntervalType.minutes: + scrollingDelta = Duration(minutes: _dateTimeAxis.autoScrollingDelta!) + .inMilliseconds; + break; + case DateTimeIntervalType.seconds: + scrollingDelta = Duration(seconds: _dateTimeAxis.autoScrollingDelta!) + .inMilliseconds; + break; + case DateTimeIntervalType.auto: + scrollingDelta = _dateTimeAxis.autoScrollingDelta!; + break; + } + super._updateAutoScrollingDelta(scrollingDelta, this); + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_category_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_category_axis.dart new file mode 100644 index 000000000..26d20dd35 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_category_axis.dart @@ -0,0 +1,528 @@ +part of charts; + +///An axis which is used to plot date-time values. It is similar to DateTimeAxis except that it +/// excludes missing dates. +/// +///This is a unique type of axis used mainly with financial series. Like [CategoryAxis], all the data +/// points are plotted with equal spaces by removing space for missing dates. Intervals and ranges +/// for the axis are calculated similar to [DateTimeAxis]. There will be no visual gaps between points +/// even when the difference between two points is more than a year. +/// +///A simple use case of this axis type is when the user wishes to visualize the working hours on an +/// employee for a month by excluding the weekends. +/// +///Provides options for label placement, interval, date format for customizing the appearance. +class DateTimeCategoryAxis extends ChartAxis { + /// Creating an argument constructor of DateTimeCategoryAxis class. + DateTimeCategoryAxis({ + String? name, + bool? isVisible, + AxisTitle? title, + AxisLine? axisLine, + ChartRangePadding? rangePadding, + EdgeLabelPlacement? edgeLabelPlacement, + ChartDataLabelPosition? labelPosition, + TickPosition? tickPosition, + int? labelRotation, + AxisLabelIntersectAction? labelIntersectAction, + LabelAlignment? labelAlignment, + bool? isInversed, + bool? opposedPosition, + int? minorTicksPerInterval, + int? maximumLabels, + MajorTickLines? majorTickLines, + MinorTickLines? minorTickLines, + MajorGridLines? majorGridLines, + MinorGridLines? minorGridLines, + TextStyle? labelStyle, + double? plotOffset, + double? zoomFactor, + double? zoomPosition, + InteractiveTooltip? interactiveTooltip, + this.minimum, + this.maximum, + double? interval, + this.visibleMinimum, + this.visibleMaximum, + dynamic? crossesAt, + String? associatedAxisName, + bool? placeLabelsNearAxisLine, + List? plotBands, + int? desiredIntervals, + RangeController? rangeController, + double? maximumLabelWidth, + double? labelsExtent, + this.labelPlacement = LabelPlacement.betweenTicks, + this.dateFormat, + this.intervalType = DateTimeIntervalType.auto, + this.autoScrollingDeltaType = DateTimeIntervalType.auto, + int? autoScrollingDelta, + AutoScrollingMode? autoScrollingMode, + }) : super( + name: name, + isVisible: isVisible, + isInversed: isInversed, + plotOffset: plotOffset, + rangePadding: rangePadding, + opposedPosition: opposedPosition, + edgeLabelPlacement: edgeLabelPlacement, + labelRotation: labelRotation, + labelPosition: labelPosition, + tickPosition: tickPosition, + labelIntersectAction: labelIntersectAction, + minorTicksPerInterval: minorTicksPerInterval, + maximumLabels: maximumLabels, + labelAlignment: labelAlignment, + labelStyle: labelStyle, + title: title, + axisLine: axisLine, + majorTickLines: majorTickLines, + minorTickLines: minorTickLines, + majorGridLines: majorGridLines, + minorGridLines: minorGridLines, + zoomFactor: zoomFactor, + zoomPosition: zoomPosition, + interactiveTooltip: interactiveTooltip, + interval: interval, + crossesAt: crossesAt, + associatedAxisName: associatedAxisName, + placeLabelsNearAxisLine: placeLabelsNearAxisLine, + plotBands: plotBands, + desiredIntervals: desiredIntervals, + rangeController: rangeController, + maximumLabelWidth: maximumLabelWidth, + labelsExtent: labelsExtent, + autoScrollingDelta: autoScrollingDelta, + autoScrollingMode: autoScrollingMode); + + ///Formats the date-time category axis labels. + /// + ///The axis label can be formatted with various built-in [date formats](https://pub.dev/documentation/intl/latest/intl/DateFormat-class.html). + /// + ///By default, date format will be applied to the axis labels based on the interval between the data points. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeCategoryAxis(dateFormat: DateFormat.y()), + /// )); + ///} + ///``` + final DateFormat? dateFormat; + + ///Position of the date-time category axis labels. + /// + ///The labels can be placed either between the ticks or at the major ticks. + /// + ///Defaults to `LabelPlacement.betweenTicks`. + /// + ///Also refer [LabelPlacement]. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeCategoryAxis(labelPlacement: LabelPlacement.onTicks), + /// )); + ///} + ///``` + final LabelPlacement labelPlacement; + + ///Customizes the date-time category axis interval. + /// + ///Intervals can be set to days, hours, minutes, months, seconds, years, and auto. If it is set to auto, + /// the interval type will be decided based on the data. + /// + ///Defaults to `DateTimeIntervalType.auto`. + /// + ///Also refer [DateTimeIntervalType]. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeCategoryAxis(intervalType: DateTimeIntervalType.years), + /// )); + ///} + ///``` + final DateTimeIntervalType intervalType; + + ///Minimum value of the axis. + /// + ///The axis will start from this date and data points below this value will not be rendered. + /// + ///Defaults to `null`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeCategoryAxis(minimum: DateTime(2000)), + /// )); + ///} + ///``` + final DateTime? minimum; + + ///Maximum value of the axis. + /// + ///The axis will end at this date and data points above this value will not be rendered. + /// + ///Defaults to `null`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeCategoryAxis(maximum: DateTime(2019)), + /// )); + ///} + ///``` + final DateTime? maximum; + + ///The minimum visible value of the axis. + /// + ///The axis will start from this date and data points below this value will not be rendered initially. + /// Further those data points can be viewed by panning from left to right direction. + /// + ///Defaults to `null`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeCategoryAxis(visibleMinimum: DateTime(2000)), + /// )); + ///} + ///``` + final DateTime? visibleMinimum; + + ///The maximum visible value of the axis. + /// + ///The axis will end at this date and data points above this value will not be rendered initially. + /// Further those data points can be viewed by panning from right to left direction. + /// + ///Defaults to `null`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeCategoryAxis(visibleMaximum: DateTime(2019)), + /// )); + ///} + ///``` + final DateTime? visibleMaximum; + + ///Defines the type of delta value in the DateTime axis. + /// + ///For example, if the [autoScrollingDelta] value is 5 and [autoScrollingDeltaType] is set to + /// `DateTimeIntervalType.days`, the data points with 5 days of values will be displayed. + /// + ///The value can be set to years, months, days, hours, minutes, seconds and auto. + /// + ///Defaults to `DateTimeIntervalType.auto` and the delta will be calculated automatically based on the data. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeCategoryAxis(autoScrollingDeltaType: DateTimeIntervalType.months), + /// )); + ///} + ///``` + final DateTimeIntervalType autoScrollingDeltaType; +} + +/// Creates an axis renderer for Category axis +class DateTimeCategoryAxisRenderer extends ChartAxisRenderer { + /// Creating an argument constructor of CategoryAxisRenderer class. + DateTimeCategoryAxisRenderer(this._dateTimeCategoryAxis) + : super(_dateTimeCategoryAxis) { + _labels = []; + } + dynamic _labels; + late Rect _rect; + final DateTimeCategoryAxis _dateTimeCategoryAxis; + late DateTimeIntervalType _actualIntervalType; + DateFormat? _dateTimeFormat; + DateFormat get _dateFormat => + _dateTimeFormat ?? _getDateTimeLabelFormat(this); + + /// Find the series min and max values of an series + void _findAxisMinMaxValues(CartesianSeriesRenderer seriesRenderer, + CartesianChartPoint point, int pointIndex, int dataLength, + [bool? isXVisibleRange, bool? isYVisibleRange]) { + final bool _anchorRangeToVisiblePoints = + seriesRenderer._yAxisRenderer!._axis.anchorRangeToVisiblePoints; + final String seriesType = seriesRenderer._seriesType; + + if (!_labels.contains('${point.x.microsecondsSinceEpoch}')) { + _labels.add('${point.x.microsecondsSinceEpoch}'); + } + point.xValue = _labels.indexOf('${point.x.microsecondsSinceEpoch}'); + point.yValue = point.y; + if (isYVisibleRange!) { + seriesRenderer._minimumX ??= point.xValue; + seriesRenderer._maximumX ??= point.xValue; + } + if ((isXVisibleRange! || !_anchorRangeToVisiblePoints) && + !seriesType.contains('range') && + !seriesType.contains('hilo') && + !seriesType.contains('candle') && + seriesType != 'boxandwhisker' && + seriesType != 'waterfall') { + seriesRenderer._minimumY ??= point.yValue; + seriesRenderer._maximumY ??= point.yValue; + } + _setCategoryMinMaxValues(this, isXVisibleRange, isYVisibleRange, point, + pointIndex, dataLength, seriesRenderer); + } + + /// Listener for range controller + void _controlListener() { + if (_axis.rangeController != null && !_chartState._rangeChangedByChart) { + _updateRangeControllerValues(this); + _chartState._redrawByRangeChange(); + } + } + + /// Calculate axis range and interval + void _calculateRangeAndInterval(SfCartesianChartState chartState, + [String? type]) { + _chartState = chartState; + _chart = chartState._chart; + if (_axis.rangeController != null) { + _chartState._rangeChangeBySlider = true; + _axis.rangeController!.addListener(_controlListener); + } + final Rect containerRect = _chartState._containerRect; + _rect = Rect.fromLTWH(containerRect.left, containerRect.top, + containerRect.width, containerRect.height); + _axisSize = Size(_rect.width, _rect.height); + calculateRange(this); + _calculateActualRange(); + if (_actualRange != null) { + applyRangePadding(_actualRange!, _actualRange!.interval); + if (type == null && + type != 'AxisCross' && + _dateTimeCategoryAxis.isVisible) { + generateVisibleLabels(); + } + } + } + + /// Calculate the required values of the actual range for the date-time axis + void _calculateActualRange() { + ///When chart series is empty, Rendering default chart with below min, max + _min ??= 0; + _max ??= 5; + _actualRange = _VisibleRange( + _chartState._rangeChangeBySlider && + _dateTimeCategoryAxis.rangeController != null + ? _rangeMinimum ?? + _dateTimeCategoryAxis + .rangeController!.start.millisecondsSinceEpoch + : _dateTimeCategoryAxis.minimum != null + ? _getEffectiveRange(_dateTimeCategoryAxis.minimum, true) + : _min, + _chartState._rangeChangeBySlider && + _dateTimeCategoryAxis.rangeController != null + ? _rangeMaximum ?? + _dateTimeCategoryAxis + .rangeController!.end.millisecondsSinceEpoch + : _dateTimeCategoryAxis.maximum != null + ? _getEffectiveRange(_dateTimeCategoryAxis.maximum, false) + : _max); + + ///Below condition is for checking the min, max value is equal + if ((_actualRange!.minimum == _actualRange!.maximum) && + (_dateTimeCategoryAxis.labelPlacement == LabelPlacement.onTicks)) { + _actualRange!.maximum += 1; + } + if (_labels.isNotEmpty) { + final List startAndEnd = _getStartAndEndDate(_labels); + _calculateDateTimeNiceInterval( + this, _axisSize, _actualRange!, startAndEnd[0], startAndEnd[1]) + .floor(); + } else { + _actualIntervalType = DateTimeIntervalType.days; + } + _actualRange!.delta = _actualRange!.maximum - _actualRange!.minimum; + _actualRange!.interval = _dateTimeCategoryAxis.interval ?? + calculateInterval(_actualRange!, Size(_rect.width, _rect.height)); + } + + /// Applies range padding to auto, normal, additional, round, and none types. + @override + void applyRangePadding(_VisibleRange range, num? interval) { + ActualRangeChangedArgs rangeChangedArgs; + if (_dateTimeCategoryAxis.labelPlacement == LabelPlacement.betweenTicks) { + range.minimum -= 0.5; + range.maximum += 0.5; + range.delta = range.maximum - range.minimum; + } + + if (_dateTimeCategoryAxis.isVisible && + !(_dateTimeCategoryAxis.minimum != null && + _dateTimeCategoryAxis.maximum != null)) { + ///Calculating range padding + _applyRangePadding(this, _chartState, range, interval!); + } + + calculateVisibleRange(Size(_rect.width, _rect.height)); + + /// Setting range as visible zoomRange + if ((_dateTimeCategoryAxis.visibleMinimum != null || + _dateTimeCategoryAxis.visibleMaximum != null) && + (_dateTimeCategoryAxis.visibleMinimum != + _dateTimeCategoryAxis.visibleMaximum) && + _chartState._zoomedAxisRendererStates.isEmpty) { + _visibleRange!.minimum = _visibleMinimum != null + ? _getEffectiveRange( + DateTime.fromMillisecondsSinceEpoch(_visibleMinimum!.toInt()), + true) + : _getEffectiveRange(_dateTimeCategoryAxis.visibleMinimum, true) ?? + _visibleRange!.minimum; + _visibleRange!.maximum = _visibleMaximum != null + ? _getEffectiveRange( + DateTime.fromMillisecondsSinceEpoch(_visibleMaximum!.toInt()), + false) + : _getEffectiveRange(_dateTimeCategoryAxis.visibleMaximum, false) ?? + _visibleRange!.maximum; + if (_dateTimeCategoryAxis.labelPlacement == LabelPlacement.betweenTicks) { + _visibleRange!.minimum = _visibleMinimum != null + ? _getEffectiveRange( + DateTime.fromMillisecondsSinceEpoch( + _visibleMinimum!.toInt()), + true)! - + 0.5 + : (_dateTimeCategoryAxis.visibleMinimum != null + ? _getEffectiveRange( + _dateTimeCategoryAxis.visibleMinimum, true)! - + 0.5 + : _visibleRange!.minimum); + _visibleRange!.maximum = _visibleMaximum != null + ? _getEffectiveRange( + DateTime.fromMillisecondsSinceEpoch( + _visibleMaximum!.toInt()), + false)! + + 0.5 + : (_dateTimeCategoryAxis.visibleMaximum != null + ? _getEffectiveRange( + _dateTimeCategoryAxis.visibleMaximum, false)! + + 0.5 + : _visibleRange!.maximum); + } + _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; + _visibleRange!.interval = interval == null + ? calculateInterval(_visibleRange!, _axisSize) + : _visibleRange!.interval; + _zoomFactor = _visibleRange!.delta / (range.delta); + _zoomPosition = + (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; + } + if (_chart.onActualRangeChanged != null) { + rangeChangedArgs = ActualRangeChangedArgs(_name!, _dateTimeCategoryAxis, + range.minimum, range.maximum, range.interval, _orientation!); + rangeChangedArgs.visibleMin = _visibleRange!.minimum; + rangeChangedArgs.visibleMax = _visibleRange!.maximum; + rangeChangedArgs.visibleInterval = _visibleRange!.interval; + _chart.onActualRangeChanged!(rangeChangedArgs); + _visibleRange!.minimum = rangeChangedArgs.visibleMin; + _visibleRange!.maximum = rangeChangedArgs.visibleMax; + _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; + _visibleRange!.interval = rangeChangedArgs.visibleInterval; + _zoomFactor = _visibleRange!.delta / (range.delta); + _zoomPosition = + (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; + } + } + + /// Calculates the visible range for an axis in chart. + @override + void calculateVisibleRange(Size availableSize) { + _calculateDateTimeVisibleRange(availableSize, this); + } + + /// Generates the visible axis labels. + @override + void generateVisibleLabels() { + num tempInterval = _visibleRange!.minimum.ceil(); + num position; + String labelText; + _visibleLabels = []; + _dateTimeFormat = + _dateTimeCategoryAxis.dateFormat ?? _getDateTimeLabelFormat(this); + for (; + tempInterval <= _visibleRange!.maximum; + tempInterval += _visibleRange!.interval) { + if (_withInRange(tempInterval, _visibleRange!)) { + position = tempInterval.round(); + if (position <= -1 || + (_labels.isNotEmpty && position >= _labels.length)) { + continue; + } else if (_labels.isNotEmpty && _labels[position] != null) { + labelText = _getFormattedLabel(_labels[position], _dateFormat); + _labels[position] = labelText; + } else { + continue; + } + _triggerLabelRenderEvent(labelText, tempInterval); + } + } + _calculateMaximumLabelSize(this, _chartState); + } + + String _getFormattedLabel(String label, DateFormat dateFormat) { + return dateFormat + .format(DateTime.fromMicrosecondsSinceEpoch(int.parse(label))); + } + + dynamic _getStartAndEndDate(List labels) { + final List values = [] + ..addAll(labels) + ..sort((String first, String second) { + return int.parse(first) < int.parse(second) ? -1 : 1; + }); + return [ + DateTime.fromMicrosecondsSinceEpoch(int.parse(values.first)), + DateTime.fromMicrosecondsSinceEpoch(int.parse(values.last)) + ]; + } + + num? _getEffectiveRange(DateTime? rangeDate, bool needMin) { + num index = 0; + if (rangeDate == null) { + return null; + } + for (final String label in _labels) { + final value = int.parse(label); + if (needMin) { + if (value > rangeDate.microsecondsSinceEpoch) { + if (!(_labels.first == label)) { + index++; + } + break; + } else if (value < rangeDate.microsecondsSinceEpoch) { + index = _labels.indexOf(label); + } else { + index = _labels.indexOf(label); + break; + } + } else { + if (value <= rangeDate.microsecondsSinceEpoch) { + index = _labels.indexOf(label); + } + if (value >= rangeDate.microsecondsSinceEpoch) { + break; + } + } + } + return index; + } + + /// Finds the interval of an axis. + @override + num calculateInterval(_VisibleRange range, Size availableSize) => math + .max( + 1, + (_actualRange!.delta / + _calculateDesiredIntervalCount( + Size(_rect.width, _rect.height), this)) + .floor()) + .toInt(); +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart index eb691f0f2..c6c5aea21 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart @@ -9,84 +9,86 @@ part of charts; class LogarithmicAxis extends ChartAxis { /// Creating an argument constructor of LogarithmicAxis class. LogarithmicAxis({ - String name, - bool isVisible, - bool anchorRangeToVisiblePoints, - AxisTitle title, - AxisLine axisLine, - AxisLabelIntersectAction labelIntersectAction, - int labelRotation, - ChartDataLabelPosition labelPosition, - TickPosition tickPosition, - bool isInversed, - bool opposedPosition, - int minorTicksPerInterval, - int maximumLabels, - MajorTickLines majorTickLines, - MinorTickLines minorTickLines, - MajorGridLines majorGridLines, - MinorGridLines minorGridLines, - EdgeLabelPlacement edgeLabelPlacement, - TextStyle labelStyle, - double plotOffset, - double zoomFactor, - double zoomPosition, - bool enableAutoIntervalOnZooming, - InteractiveTooltip interactiveTooltip, + String? name, + bool? isVisible, + bool? anchorRangeToVisiblePoints, + AxisTitle? title, + AxisLine? axisLine, + AxisLabelIntersectAction? labelIntersectAction, + int? labelRotation, + ChartDataLabelPosition? labelPosition, + TickPosition? tickPosition, + bool? isInversed, + bool? opposedPosition, + int? minorTicksPerInterval, + int? maximumLabels, + MajorTickLines? majorTickLines, + MinorTickLines? minorTickLines, + MajorGridLines? majorGridLines, + MinorGridLines? minorGridLines, + EdgeLabelPlacement? edgeLabelPlacement, + TextStyle? labelStyle, + double? plotOffset, + double? zoomFactor, + double? zoomPosition, + bool? enableAutoIntervalOnZooming, + InteractiveTooltip? interactiveTooltip, this.minimum, this.maximum, - double interval, - double logBase, + double? interval, + this.logBase = 10, this.labelFormat, this.numberFormat, this.visibleMinimum, this.visibleMaximum, - LabelAlignment labelAlignment, - dynamic crossesAt, - String associatedAxisName, - bool placeLabelsNearAxisLine, - List plotBands, - int desiredIntervals, - RangeController rangeController, - double maximumLabelWidth, - double labelsExtent, - }) : logBase = logBase ?? 10, - super( - name: name, - isVisible: isVisible, - anchorRangeToVisiblePoints: anchorRangeToVisiblePoints, - isInversed: isInversed, - opposedPosition: opposedPosition, - labelRotation: labelRotation, - labelIntersectAction: labelIntersectAction, - labelPosition: labelPosition, - tickPosition: tickPosition, - minorTicksPerInterval: minorTicksPerInterval, - maximumLabels: maximumLabels, - labelStyle: labelStyle, - title: title, - axisLine: axisLine, - edgeLabelPlacement: edgeLabelPlacement, - labelAlignment: labelAlignment, - majorTickLines: majorTickLines, - minorTickLines: minorTickLines, - majorGridLines: majorGridLines, - minorGridLines: minorGridLines, - plotOffset: plotOffset, - zoomFactor: zoomFactor, - zoomPosition: zoomPosition, - enableAutoIntervalOnZooming: enableAutoIntervalOnZooming, - interactiveTooltip: interactiveTooltip, - interval: interval, - crossesAt: crossesAt, - associatedAxisName: associatedAxisName, - placeLabelsNearAxisLine: placeLabelsNearAxisLine, - plotBands: plotBands, - desiredIntervals: desiredIntervals, - rangeController: rangeController, - maximumLabelWidth: maximumLabelWidth, - labelsExtent: labelsExtent, - ); + LabelAlignment? labelAlignment, + dynamic? crossesAt, + String? associatedAxisName, + bool? placeLabelsNearAxisLine, + List? plotBands, + int? desiredIntervals, + RangeController? rangeController, + double? maximumLabelWidth, + double? labelsExtent, + int? autoScrollingDelta, + AutoScrollingMode? autoScrollingMode, + }) : super( + name: name, + isVisible: isVisible, + anchorRangeToVisiblePoints: anchorRangeToVisiblePoints, + isInversed: isInversed, + opposedPosition: opposedPosition, + labelRotation: labelRotation, + labelIntersectAction: labelIntersectAction, + labelPosition: labelPosition, + tickPosition: tickPosition, + minorTicksPerInterval: minorTicksPerInterval, + maximumLabels: maximumLabels, + labelStyle: labelStyle, + title: title, + axisLine: axisLine, + edgeLabelPlacement: edgeLabelPlacement, + labelAlignment: labelAlignment, + majorTickLines: majorTickLines, + minorTickLines: minorTickLines, + majorGridLines: majorGridLines, + minorGridLines: minorGridLines, + plotOffset: plotOffset, + zoomFactor: zoomFactor, + zoomPosition: zoomPosition, + enableAutoIntervalOnZooming: enableAutoIntervalOnZooming, + interactiveTooltip: interactiveTooltip, + interval: interval, + crossesAt: crossesAt, + associatedAxisName: associatedAxisName, + placeLabelsNearAxisLine: placeLabelsNearAxisLine, + plotBands: plotBands, + desiredIntervals: desiredIntervals, + rangeController: rangeController, + maximumLabelWidth: maximumLabelWidth, + labelsExtent: labelsExtent, + autoScrollingDelta: autoScrollingDelta, + autoScrollingMode: autoScrollingMode); ///Formats the numeric axis labels. /// @@ -102,7 +104,7 @@ class LogarithmicAxis extends ChartAxis { /// )); ///} ///``` - final String labelFormat; + final String? labelFormat; ///Formats the logarithmic axis labels with globalized label formats. /// @@ -118,7 +120,7 @@ class LogarithmicAxis extends ChartAxis { /// )); ///} ///``` - final NumberFormat numberFormat; + final NumberFormat? numberFormat; ///The minimum value of the axis. /// @@ -134,7 +136,7 @@ class LogarithmicAxis extends ChartAxis { /// )); ///} ///``` - final double minimum; + final double? minimum; ///The maximum value of the axis. ///The axis will end at this value. @@ -149,7 +151,7 @@ class LogarithmicAxis extends ChartAxis { /// )); ///} ///``` - final double maximum; + final double? maximum; ///The base value for logarithmic axis. ///The axislabel will render this base value.i.e 10,100,1000 and so on. @@ -180,7 +182,7 @@ class LogarithmicAxis extends ChartAxis { /// )); ///} ///``` - final double visibleMinimum; + final double? visibleMinimum; ///The minimum visible value of the axis. /// @@ -196,7 +198,7 @@ class LogarithmicAxis extends ChartAxis { /// )); ///} ///``` - final double visibleMaximum; + final double? visibleMaximum; } /// Creates an axis renderer for Logarithmic axis @@ -209,7 +211,7 @@ class LogarithmicAxisRenderer extends ChartAxisRenderer { /// Find the series min and max values of an series void _findAxisMinMaxValues(CartesianSeriesRenderer seriesRenderer, CartesianChartPoint point, int pointIndex, int dataLength, - [bool isXVisibleRange, bool isYVisibleRange]) { + [bool? isXVisibleRange, bool? isYVisibleRange]) { final String seriesType = seriesRenderer._seriesType; point.xValue = point.x; point.yValue = point.y; @@ -227,26 +229,26 @@ class LogarithmicAxisRenderer extends ChartAxisRenderer { _highMax ??= point.high; if (point.xValue != null) { seriesRenderer._minimumX = - math.min(seriesRenderer._minimumX, point.xValue); + math.min(seriesRenderer._minimumX!, point.xValue); seriesRenderer._maximumX = - math.max(seriesRenderer._maximumX, point.xValue); + math.max(seriesRenderer._maximumX!, point.xValue); } if (point.yValue != null && (!seriesType.contains('range') && !seriesType.contains('hilo') && !seriesType.contains('candle'))) { seriesRenderer._minimumY = - math.min(seriesRenderer._minimumY, point.yValue); + math.min(seriesRenderer._minimumY!, point.yValue); seriesRenderer._maximumY = - math.max(seriesRenderer._maximumY, point.yValue); + math.max(seriesRenderer._maximumY!, point.yValue); } if (point.high != null) { - _highMin = math.min(_highMin, point.high); - _highMax = math.max(_highMax, point.high); + _highMin = math.min(_highMin!, point.high); + _highMax = math.max(_highMax!, point.high); } if (point.low != null) { - _lowMin = math.min(_lowMin, point.low); - _lowMax = math.max(_lowMax, point.low); + _lowMin = math.min(_lowMin!, point.low); + _lowMax = math.max(_lowMax!, point.low); } if (pointIndex >= dataLength - 1) { if (seriesType.contains('range') || @@ -256,8 +258,10 @@ class LogarithmicAxisRenderer extends ChartAxisRenderer { _lowMax ??= 5; _highMin ??= 0; _highMax ??= 5; - seriesRenderer._minimumY = math.min(_lowMin, _highMin); - seriesRenderer._maximumY = math.max(_lowMax, _highMax); + seriesRenderer._minimumY = + math.min(_lowMin!.toDouble(), _highMin!.toDouble()); + seriesRenderer._maximumY = + math.max(_lowMax!.toDouble(), _highMax!.toDouble()); } seriesRenderer._minimumX ??= 0; seriesRenderer._minimumY ??= 0; @@ -276,12 +280,12 @@ class LogarithmicAxisRenderer extends ChartAxisRenderer { /// Calculate the range and interval void _calculateRangeAndInterval(SfCartesianChartState chartState, - [String type]) { + [String? type]) { _chartState = chartState; _chart = chartState._chart; if (_logarithmicAxis.rangeController != null) { _chartState._rangeChangeBySlider = true; - _logarithmicAxis.rangeController.addListener(_controlListener); + _logarithmicAxis.rangeController!.addListener(_controlListener); } final Rect containerRect = _chartState._containerRect; final Rect rect = Rect.fromLTWH(containerRect.left, containerRect.top, @@ -295,18 +299,38 @@ class LogarithmicAxisRenderer extends ChartAxisRenderer { if ((_logarithmicAxis.visibleMinimum != null || _logarithmicAxis.visibleMaximum != null) && (_logarithmicAxis.visibleMinimum != _logarithmicAxis.visibleMaximum) && - _chartState._zoomedAxisRendererStates != null && - _chartState._zoomedAxisRendererStates.isEmpty) { - _visibleRange.minimum = _logarithmicAxis.visibleMinimum != null - ? (math.log(_logarithmicAxis.visibleMinimum) / (math.log(10))).round() - : _visibleRange.minimum; - _visibleRange.maximum = _logarithmicAxis.visibleMaximum != null - ? (math.log(_logarithmicAxis.visibleMaximum) / (math.log(10))).round() - : _visibleRange.maximum; - _visibleRange.delta = _visibleRange.maximum - _visibleRange.minimum; - _zoomFactor = _visibleRange.delta / (_actualRange.delta); + (!_chartState._isRedrawByZoomPan)) { + _chartState._isRedrawByZoomPan = false; + _visibleRange!.minimum = _logarithmicAxis.visibleMinimum != null + ? (math.log(_logarithmicAxis.visibleMinimum!) / (math.log(10))) + .round() + : _visibleRange!.minimum; + _visibleRange!.maximum = _logarithmicAxis.visibleMaximum != null + ? (math.log(_logarithmicAxis.visibleMaximum!) / (math.log(10))) + .round() + : _visibleRange!.maximum; + _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; + _zoomFactor = _visibleRange!.delta / (_actualRange!.delta); + _zoomPosition = (_visibleRange!.minimum - _actualRange!.minimum) / + _actualRange!.delta; + } + + ActualRangeChangedArgs rangeChangedArgs; + if (_chart.onActualRangeChanged != null) { + final _VisibleRange range = _actualRange!; + rangeChangedArgs = ActualRangeChangedArgs(_name!, _logarithmicAxis, + range.minimum, range.maximum, range.interval, _orientation!); + rangeChangedArgs.visibleMin = _visibleRange!.minimum; + rangeChangedArgs.visibleMax = _visibleRange!.maximum; + rangeChangedArgs.visibleInterval = _visibleRange!.interval; + _chart.onActualRangeChanged!(rangeChangedArgs); + _visibleRange!.minimum = rangeChangedArgs.visibleMin; + _visibleRange!.maximum = rangeChangedArgs.visibleMax; + _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; + _visibleRange!.interval = rangeChangedArgs.visibleInterval; + _zoomFactor = _visibleRange!.delta / range.delta; _zoomPosition = - (_visibleRange.minimum - _actualRange.minimum) / _actualRange.delta; + (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; } if (type == null && type != 'AxisCross' && _logarithmicAxis.isVisible) { generateVisibleLabels(); @@ -320,33 +344,33 @@ class LogarithmicAxisRenderer extends ChartAxisRenderer { _max ??= 5; _min = _chartState._rangeChangeBySlider && _logarithmicAxis.rangeController != null - ? _rangeMinimum ?? _logarithmicAxis.rangeController.start + ? _rangeMinimum ?? _logarithmicAxis.rangeController!.start : _logarithmicAxis.minimum ?? _min; _max = _chartState._rangeChangeBySlider && _logarithmicAxis.rangeController != null - ? _rangeMaximum ?? _logarithmicAxis.rangeController.end + ? _rangeMaximum ?? _logarithmicAxis.rangeController!.end : _logarithmicAxis.maximum ?? _max; if (_axis.anchorRangeToVisiblePoints && _needCalculateYrange(_logarithmicAxis.minimum, _logarithmicAxis.maximum, - _chartState, _orientation)) { - final _VisibleRange range = _calculateYRangeOnZoomX(_actualRange, this); + _chartState, _orientation!)) { + final _VisibleRange range = _calculateYRangeOnZoomX(_actualRange!, this); _min = range.minimum; _max = range.maximum; } - _min = _min < 0 ? 0 : _min; - logStart = _calculateLogBaseValue(_min, _logarithmicAxis.logBase); - logStart = logStart.isFinite ? logStart : _min; - logEnd = _calculateLogBaseValue(_max, _logarithmicAxis.logBase); - logEnd = logEnd.isFinite ? logEnd : _max; + _min = _min! < 0 ? 0 : _min; + logStart = _calculateLogBaseValue(_min!, _logarithmicAxis.logBase); + logStart = logStart.isFinite ? logStart : _min!; + logEnd = _calculateLogBaseValue(_max!, _logarithmicAxis.logBase); + logEnd = logEnd.isFinite ? logEnd : _max!; _min = (logStart / 1).floor(); _max = (logEnd / 1).ceil(); if (_min == _max) { - _max += 1; + _max = _max! + 1; } _actualRange = _VisibleRange(_min, _max); - _actualRange.delta = _actualRange.maximum - _actualRange.minimum; - _actualRange.interval = _logarithmicAxis.interval ?? - calculateLogNiceInterval(_actualRange.delta); + _actualRange!.delta = _actualRange!.maximum - _actualRange!.minimum; + _actualRange!.interval = _logarithmicAxis.interval ?? + calculateLogNiceInterval(_actualRange!.delta); } /// To get the axis interval for logarithmic axis @@ -371,21 +395,31 @@ class LogarithmicAxisRenderer extends ChartAxisRenderer { /// Calculates the visible range for an axis in chart. @override void calculateVisibleRange(Size availableSize) { - _visibleRange = _VisibleRange(_actualRange.minimum, _actualRange.maximum); - _visibleRange.delta = _actualRange.delta; - _visibleRange.interval = _actualRange.interval; - _checkWithZoomState(this, _chartState._zoomedAxisRendererStates); + _visibleRange = _VisibleRange(_actualRange!.minimum, _actualRange!.maximum); + _visibleRange!.delta = _actualRange!.delta; + _visibleRange!.interval = _actualRange!.interval; + bool canAutoScroll = false; + if (_logarithmicAxis.autoScrollingDelta != null && + _logarithmicAxis.autoScrollingDelta! > 0 && + !_chartState._isRedrawByZoomPan) { + canAutoScroll = true; + super._updateAutoScrollingDelta( + _logarithmicAxis.autoScrollingDelta!, this); + } + if (!canAutoScroll) { + _setZoomFactorAndPosition(this, _chartState._zoomedAxisRendererStates); + } if (_zoomFactor < 1 || _zoomPosition > 0) { _chartState._zoomProgress = true; _calculateZoomRange(this, availableSize); - _visibleRange.delta = _visibleRange.maximum - _visibleRange.minimum; - _visibleRange.interval = + _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; + _visibleRange!.interval = _axis.enableAutoIntervalOnZooming && _chartState._zoomProgress - ? calculateLogNiceInterval(_visibleRange.delta) - : _visibleRange.interval; - _visibleRange.interval = _visibleRange.interval.floor() == 0 + ? calculateLogNiceInterval(_visibleRange!.delta) + : _visibleRange!.interval; + _visibleRange!.interval = _visibleRange!.interval.floor() == 0 ? 1 - : _visibleRange.interval.floor(); + : _visibleRange!.interval.floor(); if (_axis.rangeController != null) { _chartState._rangeChangedByChart = true; _setRangeControllerValues(this); @@ -400,21 +434,21 @@ class LogarithmicAxisRenderer extends ChartAxisRenderer { /// Generates the visible axis labels. @override void generateVisibleLabels() { - num tempInterval = _visibleRange.minimum; + num tempInterval = _visibleRange!.minimum; String labelText; _visibleLabels = []; for (; - tempInterval <= _visibleRange.maximum; - tempInterval += _visibleRange.interval) { + tempInterval <= _visibleRange!.maximum; + tempInterval += _visibleRange!.interval) { labelText = pow(_logarithmicAxis.logBase, tempInterval).floor().toString(); if (_logarithmicAxis.numberFormat != null) { - labelText = _logarithmicAxis.numberFormat + labelText = _logarithmicAxis.numberFormat! .format(pow(_logarithmicAxis.logBase, tempInterval).floor()); } if (_logarithmicAxis.labelFormat != null && _logarithmicAxis.labelFormat != '') { - labelText = _logarithmicAxis.labelFormat + labelText = _logarithmicAxis.labelFormat! .replaceAll(RegExp('{value}'), labelText); } _triggerLabelRenderEvent(labelText, tempInterval); @@ -426,5 +460,5 @@ class LogarithmicAxisRenderer extends ChartAxisRenderer { /// Finds the interval of an axis. @override - num calculateInterval(_VisibleRange range, Size availableSize) => null; + num? calculateInterval(_VisibleRange range, Size availableSize) => null; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart index b19f0a535..a9a0f9359 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart @@ -11,84 +11,88 @@ part of charts; class NumericAxis extends ChartAxis { /// Creating an argument constructor of NumericAxis class. NumericAxis({ - String name, - bool isVisible, - bool anchorRangeToVisiblePoints, - AxisTitle title, - AxisLine axisLine, - ChartRangePadding rangePadding, - AxisLabelIntersectAction labelIntersectAction, - int labelRotation, + String? name, + bool? isVisible, + bool? anchorRangeToVisiblePoints, + AxisTitle? title, + AxisLine? axisLine, + ChartRangePadding? rangePadding, + AxisLabelIntersectAction? labelIntersectAction, + int? labelRotation, this.labelFormat, this.numberFormat, - LabelAlignment labelAlignment, - ChartDataLabelPosition labelPosition, - TickPosition tickPosition, - bool isInversed, - bool opposedPosition, - int minorTicksPerInterval, - int maximumLabels, - MajorTickLines majorTickLines, - MinorTickLines minorTickLines, - MajorGridLines majorGridLines, - MinorGridLines minorGridLines, - EdgeLabelPlacement edgeLabelPlacement, - TextStyle labelStyle, - double plotOffset, - double zoomFactor, - double zoomPosition, - InteractiveTooltip interactiveTooltip, + LabelAlignment? labelAlignment, + ChartDataLabelPosition? labelPosition, + TickPosition? tickPosition, + bool? isInversed, + bool? opposedPosition, + int? minorTicksPerInterval, + int? maximumLabels, + MajorTickLines? majorTickLines, + MinorTickLines? minorTickLines, + MajorGridLines? majorGridLines, + MinorGridLines? minorGridLines, + EdgeLabelPlacement? edgeLabelPlacement, + TextStyle? labelStyle, + double? plotOffset, + double? zoomFactor, + double? zoomPosition, + bool? enableAutoIntervalOnZooming, + InteractiveTooltip? interactiveTooltip, this.minimum, this.maximum, - double interval, + double? interval, this.visibleMinimum, this.visibleMaximum, - dynamic crossesAt, - String associatedAxisName, - bool placeLabelsNearAxisLine, - List plotBands, - int decimalPlaces, - int desiredIntervals, - RangeController rangeController, - double maximumLabelWidth, - double labelsExtent, - }) : decimalPlaces = decimalPlaces ?? 3, - super( - name: name, - isVisible: isVisible, - anchorRangeToVisiblePoints: anchorRangeToVisiblePoints, - isInversed: isInversed, - opposedPosition: opposedPosition, - rangePadding: rangePadding, - labelRotation: labelRotation, - labelIntersectAction: labelIntersectAction, - labelPosition: labelPosition, - tickPosition: tickPosition, - minorTicksPerInterval: minorTicksPerInterval, - maximumLabels: maximumLabels, - labelStyle: labelStyle, - title: title, - labelAlignment: labelAlignment, - axisLine: axisLine, - edgeLabelPlacement: edgeLabelPlacement, - majorTickLines: majorTickLines, - minorTickLines: minorTickLines, - majorGridLines: majorGridLines, - minorGridLines: minorGridLines, - plotOffset: plotOffset, - zoomFactor: zoomFactor, - zoomPosition: zoomPosition, - interactiveTooltip: interactiveTooltip, - interval: interval, - crossesAt: crossesAt, - associatedAxisName: associatedAxisName, - placeLabelsNearAxisLine: placeLabelsNearAxisLine, - plotBands: plotBands, - desiredIntervals: desiredIntervals, - rangeController: rangeController, - maximumLabelWidth: maximumLabelWidth, - labelsExtent: labelsExtent, - ); + dynamic? crossesAt, + String? associatedAxisName, + bool? placeLabelsNearAxisLine, + List? plotBands, + this.decimalPlaces = 3, + int? desiredIntervals, + RangeController? rangeController, + double? maximumLabelWidth, + double? labelsExtent, + int? autoScrollingDelta, + AutoScrollingMode? autoScrollingMode, + }) : super( + name: name, + isVisible: isVisible, + anchorRangeToVisiblePoints: anchorRangeToVisiblePoints, + isInversed: isInversed, + opposedPosition: opposedPosition, + rangePadding: rangePadding, + labelRotation: labelRotation, + labelIntersectAction: labelIntersectAction, + labelPosition: labelPosition, + tickPosition: tickPosition, + minorTicksPerInterval: minorTicksPerInterval, + maximumLabels: maximumLabels, + labelStyle: labelStyle, + title: title, + labelAlignment: labelAlignment, + axisLine: axisLine, + edgeLabelPlacement: edgeLabelPlacement, + majorTickLines: majorTickLines, + minorTickLines: minorTickLines, + majorGridLines: majorGridLines, + minorGridLines: minorGridLines, + plotOffset: plotOffset, + enableAutoIntervalOnZooming: enableAutoIntervalOnZooming, + zoomFactor: zoomFactor, + zoomPosition: zoomPosition, + interactiveTooltip: interactiveTooltip, + interval: interval, + crossesAt: crossesAt, + associatedAxisName: associatedAxisName, + placeLabelsNearAxisLine: placeLabelsNearAxisLine, + plotBands: plotBands, + desiredIntervals: desiredIntervals, + rangeController: rangeController, + maximumLabelWidth: maximumLabelWidth, + labelsExtent: labelsExtent, + autoScrollingDelta: autoScrollingDelta, + autoScrollingMode: autoScrollingMode); ///Formats the numeric axis labels. /// @@ -104,7 +108,7 @@ class NumericAxis extends ChartAxis { /// )); ///} ///``` - final String labelFormat; + final String? labelFormat; ///Formats the numeric axis labels with globalized label formats. /// @@ -118,7 +122,7 @@ class NumericAxis extends ChartAxis { /// )); ///} ///``` - final NumberFormat numberFormat; + final NumberFormat? numberFormat; ///The minimum value of the axis. /// @@ -134,7 +138,7 @@ class NumericAxis extends ChartAxis { /// )); ///} ///``` - final double minimum; + final double? minimum; ///The maximum value of the axis. /// @@ -150,7 +154,7 @@ class NumericAxis extends ChartAxis { /// )); ///} ///``` - final double maximum; + final double? maximum; ///The minimum visible value of the axis. /// @@ -166,7 +170,7 @@ class NumericAxis extends ChartAxis { /// )); ///} ///``` - final double visibleMinimum; + final double? visibleMinimum; ///The maximum visible value of the axis. /// @@ -182,7 +186,7 @@ class NumericAxis extends ChartAxis { /// )); ///} ///``` - final double visibleMaximum; + final double? visibleMaximum; ///The rounding decimal value of the label. /// @@ -209,24 +213,24 @@ class NumericAxisRenderer extends ChartAxisRenderer { final int _innerPadding = 5; @override - Size _axisSize; + late Size _axisSize; final NumericAxis _numericAxis; /// Find the series min and max values of an series void _findAxisMinMaxValues(CartesianSeriesRenderer seriesRenderer, CartesianChartPoint point, int pointIndex, int dataLength, - [bool isXVisibleRange, bool isYVisibleRange]) { + [bool? isXVisibleRange, bool? isYVisibleRange]) { final bool _anchorRangeToVisiblePoints = - seriesRenderer._yAxisRenderer._axis.anchorRangeToVisiblePoints; + seriesRenderer._yAxisRenderer!._axis.anchorRangeToVisiblePoints; final String seriesType = seriesRenderer._seriesType; point.xValue = point.x; point.yValue = point.y; - if (isYVisibleRange) { + if (isYVisibleRange!) { seriesRenderer._minimumX ??= point.xValue; seriesRenderer._maximumX ??= point.xValue; } - if ((isXVisibleRange || !_anchorRangeToVisiblePoints) && + if ((isXVisibleRange! || !_anchorRangeToVisiblePoints) && !seriesType.contains('range') && !seriesType.contains('hilo') && !seriesType.contains('candle') && @@ -238,9 +242,9 @@ class NumericAxisRenderer extends ChartAxisRenderer { if (isYVisibleRange && point.xValue != null) { seriesRenderer._minimumX = - math.min(seriesRenderer._minimumX, point.xValue); + math.min(seriesRenderer._minimumX!, point.xValue); seriesRenderer._maximumX = - math.max(seriesRenderer._maximumX, point.xValue); + math.max(seriesRenderer._maximumX!, point.xValue); } if (isXVisibleRange || !_anchorRangeToVisiblePoints) { if (point.yValue != null && @@ -250,9 +254,9 @@ class NumericAxisRenderer extends ChartAxisRenderer { seriesType != 'boxandwhisker' && seriesType != 'waterfall')) { seriesRenderer._minimumY = - math.min(seriesRenderer._minimumY, point.yValue); + math.min(seriesRenderer._minimumY!, point.yValue); seriesRenderer._maximumY = - math.max(seriesRenderer._maximumY, point.yValue); + math.max(seriesRenderer._maximumY!, point.yValue); } if (point.high != null) { _highMin = _findMinValue(_highMin ?? point.high, point.high); @@ -263,12 +267,12 @@ class NumericAxisRenderer extends ChartAxisRenderer { _lowMax = _findMaxValue(_lowMax ?? point.low, point.low); } if (point.maximum != null) { - _highMin = _findMinValue(_highMin ?? point.maximum, point.maximum); - _highMax = _findMaxValue(_highMax ?? point.maximum, point.maximum); + _highMin = _findMinValue(_highMin ?? point.maximum!, point.maximum!); + _highMax = _findMaxValue(_highMax ?? point.maximum!, point.maximum!); } if (point.minimum != null) { - _lowMin = _findMinValue(_lowMin ?? point.minimum, point.minimum); - _lowMax = _findMaxValue(_lowMax ?? point.minimum, point.minimum); + _lowMin = _findMinValue(_lowMin ?? point.minimum!, point.minimum!); + _lowMax = _findMaxValue(_lowMax ?? point.minimum!, point.minimum!); } if (seriesType == 'waterfall') { /// Empty point is not applicable for Waterfall series. @@ -288,8 +292,8 @@ class NumericAxisRenderer extends ChartAxisRenderer { _lowMax ??= 5; _highMin ??= 0; _highMax ??= 5; - seriesRenderer._minimumY = math.min(_lowMin, _highMin); - seriesRenderer._maximumY = math.max(_lowMax, _highMax); + seriesRenderer._minimumY = math.min(_lowMin!, _highMin!); + seriesRenderer._maximumY = math.max(_lowMax!, _highMax!); } seriesRenderer._minimumX ??= 0; @@ -309,12 +313,12 @@ class NumericAxisRenderer extends ChartAxisRenderer { /// Calculate the range and interval void _calculateRangeAndInterval(SfCartesianChartState chartState, - [String type]) { + [String? type]) { _chartState = chartState; _chart = chartState._chart; if (_axis.rangeController != null) { _chartState._rangeChangeBySlider = true; - _axis.rangeController.addListener(_controlListener); + _axis.rangeController!.addListener(_controlListener); } final Rect containerRect = _chartState._containerRect; final Rect rect = Rect.fromLTWH(containerRect.left, containerRect.top, @@ -323,7 +327,7 @@ class NumericAxisRenderer extends ChartAxisRenderer { calculateRange(this); _calculateActualRange(); if (_actualRange != null) { - applyRangePadding(_actualRange, _actualRange.interval); + applyRangePadding(_actualRange!, _actualRange!.interval); if (type == null && type != 'AxisCross' && _numericAxis.isVisible) { generateVisibleLabels(); } @@ -336,42 +340,41 @@ class NumericAxisRenderer extends ChartAxisRenderer { _max ??= 5; _actualRange = _VisibleRange( _chartState._rangeChangeBySlider && _numericAxis.rangeController != null - ? _rangeMinimum ?? _numericAxis.rangeController.start + ? _rangeMinimum ?? _numericAxis.rangeController!.start : _numericAxis.minimum ?? _min, _chartState._rangeChangeBySlider && _numericAxis.rangeController != null - ? _rangeMaximum ?? _numericAxis.rangeController.end + ? _rangeMaximum ?? _numericAxis.rangeController!.end : _numericAxis.maximum ?? _max); if (_axis.anchorRangeToVisiblePoints && _needCalculateYrange(_numericAxis.minimum, _numericAxis.maximum, - _chartState, _orientation)) { - _actualRange = _calculateYRangeOnZoomX(_actualRange, this); + _chartState, _orientation!)) { + _actualRange = _calculateYRangeOnZoomX(_actualRange!, this); } ///Below condition is for checking the min, max value is equal - if (_actualRange.minimum == _actualRange.maximum) { - _actualRange.maximum += 1; + if (_actualRange!.minimum == _actualRange!.maximum) { + _actualRange!.maximum += 1; } ///Below condition is for checking the axis min value is greater than max value, then swapping min max values - else if (_actualRange.minimum > _actualRange.maximum) { - _actualRange.minimum = _actualRange.minimum + _actualRange.maximum; - _actualRange.maximum = _actualRange.minimum - _actualRange.maximum; - _actualRange.minimum = _actualRange.minimum - _actualRange.maximum; + else if (_actualRange!.minimum > _actualRange!.maximum) { + _actualRange!.minimum = _actualRange!.minimum + _actualRange!.maximum; + _actualRange!.maximum = _actualRange!.minimum - _actualRange!.maximum; + _actualRange!.minimum = _actualRange!.minimum - _actualRange!.maximum; } - _actualRange.delta = _actualRange.maximum - _actualRange.minimum; + _actualRange!.delta = _actualRange!.maximum - _actualRange!.minimum; - _actualRange.interval = _numericAxis.interval ?? - _calculateNumericNiceInterval(this, _actualRange.delta, _axisSize); + _actualRange!.interval = _numericAxis.interval ?? + _calculateNumericNiceInterval(this, _actualRange!.delta, _axisSize); } /// Applies range padding to auto, normal, additional, round, and none types. @override - void applyRangePadding(_VisibleRange range, num interval) { + void applyRangePadding(_VisibleRange range, num? interval) { ActualRangeChangedArgs rangeChangedArgs; - if (_numericAxis.isVisible && - !(_numericAxis.minimum != null && _numericAxis.maximum != null)) { + if (!(_numericAxis.minimum != null && _numericAxis.maximum != null)) { ///Calculating range padding - _applyRangePadding(this, _chartState, range, interval); + _applyRangePadding(this, _chartState, range, interval!); } calculateVisibleRange(_axisSize); @@ -380,46 +383,48 @@ class NumericAxisRenderer extends ChartAxisRenderer { if ((_numericAxis.visibleMinimum != null || _numericAxis.visibleMaximum != null) && (_numericAxis.visibleMinimum != _numericAxis.visibleMaximum) && - _chartState._zoomedAxisRendererStates != null && - _chartState._zoomedAxisRendererStates.isEmpty) { - _visibleRange.minimum = - _numericAxis.visibleMinimum ?? _visibleRange.minimum; - _visibleRange.maximum = - _numericAxis.visibleMaximum ?? _visibleRange.maximum; - _visibleRange.delta = _visibleRange.maximum - _visibleRange.minimum; - _visibleRange.interval = interval == null + (!_chartState._isRedrawByZoomPan)) { + _chartState._isRedrawByZoomPan = false; + _visibleRange!.minimum = _visibleMinimum ?? + _numericAxis.visibleMinimum ?? + _visibleRange!.minimum; + _visibleRange!.maximum = _visibleMaximum ?? + _numericAxis.visibleMaximum ?? + _visibleRange!.maximum; + _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; + _visibleRange!.interval = interval == null ? _calculateNumericNiceInterval( - this, _visibleRange.maximum - _visibleRange.minimum, _axisSize) - : _visibleRange.interval; - _zoomFactor = _visibleRange.delta / range.delta; + this, _visibleRange!.maximum - _visibleRange!.minimum, _axisSize) + : _visibleRange!.interval; + _zoomFactor = _visibleRange!.delta / range.delta; _zoomPosition = - (_visibleRange.minimum - _actualRange.minimum) / range.delta; + (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; } - if (_numericAxis.isVisible && _chart.onActualRangeChanged != null) { - rangeChangedArgs = ActualRangeChangedArgs(_name, _numericAxis, - range.minimum, range.maximum, range.interval, _orientation); - rangeChangedArgs.visibleMin = _visibleRange.minimum; - rangeChangedArgs.visibleMax = _visibleRange.maximum; - rangeChangedArgs.visibleInterval = _visibleRange.interval; - _chart.onActualRangeChanged(rangeChangedArgs); - _visibleRange.minimum = rangeChangedArgs.visibleMin; - _visibleRange.maximum = rangeChangedArgs.visibleMax; - _visibleRange.delta = _visibleRange.maximum - _visibleRange.minimum; - _visibleRange.interval = rangeChangedArgs.visibleInterval; - _zoomFactor = _visibleRange.delta / range.delta; + if (_chart.onActualRangeChanged != null) { + rangeChangedArgs = ActualRangeChangedArgs(_name!, _numericAxis, + range.minimum, range.maximum, range.interval, _orientation!); + rangeChangedArgs.visibleMin = _visibleRange!.minimum; + rangeChangedArgs.visibleMax = _visibleRange!.maximum; + rangeChangedArgs.visibleInterval = _visibleRange!.interval; + _chart.onActualRangeChanged!(rangeChangedArgs); + _visibleRange!.minimum = rangeChangedArgs.visibleMin; + _visibleRange!.maximum = rangeChangedArgs.visibleMax; + _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; + _visibleRange!.interval = rangeChangedArgs.visibleInterval; + _zoomFactor = _visibleRange!.delta / range.delta; _zoomPosition = - (_visibleRange.minimum - _actualRange.minimum) / range.delta; + (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; } } /// Generates the visible axis labels. @override void generateVisibleLabels() { - num tempInterval = _visibleRange.minimum; + num tempInterval = _visibleRange!.minimum; String text; final String minimum = tempInterval.toString(); - final num maximumVisibleRange = _visibleRange.maximum; - num interval = _visibleRange.interval; + final num maximumVisibleRange = _visibleRange!.maximum; + num interval = _visibleRange!.interval; interval = interval.toString().split('.').length >= 2 ? interval.toString().split('.')[1].length == 1 && interval.toString().split('.')[1] == '0' @@ -430,7 +435,7 @@ class NumericAxisRenderer extends ChartAxisRenderer { for (; tempInterval <= maximumVisibleRange; tempInterval += interval) { num minimumVisibleRange = tempInterval; if (minimumVisibleRange <= maximumVisibleRange && - minimumVisibleRange >= _visibleRange.minimum) { + minimumVisibleRange >= _visibleRange!.minimum) { final int fractionDigits = (minimum.split('.').length >= 2) ? minimum.split('.')[1].toString().length : (minimumVisibleRange.toString().split('.').length >= 2) @@ -441,10 +446,10 @@ class NumericAxisRenderer extends ChartAxisRenderer { minimumVisibleRange = minimumVisibleRange.toString().contains('e') ? minimumVisibleRange : num.tryParse( - minimumVisibleRange.toStringAsFixed(fractionDigitValue)); + minimumVisibleRange.toStringAsFixed(fractionDigitValue))!; if (minimumVisibleRange.toString().split('.').length > 1) { final String str = minimumVisibleRange.toString(); - final List list = str.split('.'); + final List? list = str.split('.'); minimumVisibleRange = double.parse( minimumVisibleRange.toStringAsFixed(_numericAxis.decimalPlaces)); if (list != null && @@ -459,11 +464,11 @@ class NumericAxisRenderer extends ChartAxisRenderer { } text = minimumVisibleRange.toString(); if (_numericAxis.numberFormat != null) { - text = _numericAxis.numberFormat.format(minimumVisibleRange); + text = _numericAxis.numberFormat!.format(minimumVisibleRange); } if (_numericAxis.labelFormat != null && _numericAxis.labelFormat != '') { - text = _numericAxis.labelFormat.replaceAll(RegExp('{value}'), text); + text = _numericAxis.labelFormat!.replaceAll(RegExp('{value}'), text); } text = _chartState._chartAxis._primaryYAxisRenderer._isStack100 == true && @@ -481,17 +486,26 @@ class NumericAxisRenderer extends ChartAxisRenderer { /// Calculates the visible range for an axis in chart. @override void calculateVisibleRange(Size availableSize) { - _visibleRange = _VisibleRange(_actualRange.minimum, _actualRange.maximum); - _visibleRange.delta = _actualRange.delta; - _visibleRange.interval = _actualRange.interval; - _checkWithZoomState(this, _chartState._zoomedAxisRendererStates); + _visibleRange = _VisibleRange(_actualRange!.minimum, _actualRange!.maximum); + _visibleRange!.delta = _actualRange!.delta; + _visibleRange!.interval = _actualRange!.interval; + bool canAutoScroll = false; + if (_numericAxis.autoScrollingDelta != null && + _numericAxis.autoScrollingDelta! > 0 && + !_chartState._isRedrawByZoomPan) { + canAutoScroll = true; + super._updateAutoScrollingDelta(_numericAxis.autoScrollingDelta!, this); + } + if (!canAutoScroll) { + _setZoomFactorAndPosition(this, _chartState._zoomedAxisRendererStates); + } if (_zoomFactor < 1 || _zoomPosition > 0) { _chartState._zoomProgress = true; _calculateZoomRange(this, availableSize); - _visibleRange.interval = + _visibleRange!.interval = _axis.enableAutoIntervalOnZooming && _chartState._zoomProgress - ? calculateInterval(_visibleRange, _axisSize) - : _visibleRange.interval; + ? calculateInterval(_visibleRange!, _axisSize) + : _visibleRange!.interval; if (_axis.rangeController != null) { _chartState._rangeChangedByChart = true; _setRangeControllerValues(this); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/plotband.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/plotband.dart index ede0dc015..2353580df 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/plotband.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/plotband.dart @@ -15,48 +15,36 @@ part of charts; class PlotBand { /// Creating an argument constructor of PlotBand class. PlotBand( - {bool isVisible, + {this.isVisible = true, this.start, this.end, - Color color, - double opacity, - Color borderColor, - double borderWidth, + this.color = Colors.grey, + this.opacity = 1.0, + this.borderColor = Colors.transparent, + this.borderWidth = 0, this.text, - TextStyle textStyle, - bool isRepeatable, - dynamic repeatEvery, + TextStyle? textStyle, + this.isRepeatable = false, + this.repeatEvery = 1, this.verticalTextPadding, this.horizontalTextPadding, this.repeatUntil, this.textAngle, - bool shouldRenderAboveSeries, - DateTimeIntervalType sizeType, - List dashArray, + this.shouldRenderAboveSeries = false, + this.sizeType = DateTimeIntervalType.auto, + this.dashArray = const [0, 0], this.size, this.associatedAxisStart, this.associatedAxisEnd, - TextAnchor verticalTextAlignment, - TextAnchor horizontalTextAlignment, + this.verticalTextAlignment = TextAnchor.middle, + this.horizontalTextAlignment = TextAnchor.middle, this.gradient}) - : isVisible = isVisible ?? true, - opacity = opacity ?? 1.0, - color = color ?? Colors.grey, - borderColor = borderColor ?? Colors.transparent, - borderWidth = borderWidth ?? 0, - textStyle = textStyle ?? + : textStyle = textStyle ?? const TextStyle( fontFamily: 'Roboto', fontStyle: FontStyle.normal, fontWeight: FontWeight.normal, - fontSize: 12), - isRepeatable = isRepeatable ?? false, - repeatEvery = repeatEvery ?? 1, - shouldRenderAboveSeries = shouldRenderAboveSeries ?? false, - dashArray = dashArray ?? [0, 0], - sizeType = sizeType ?? DateTimeIntervalType.auto, - verticalTextAlignment = verticalTextAlignment ?? TextAnchor.middle, - horizontalTextAlignment = horizontalTextAlignment ?? TextAnchor.middle; + fontSize: 12); ///Toggles the visibility of the plot band. /// @@ -140,7 +128,7 @@ class PlotBand { /// )); ///} ///``` - final String text; + final String? text; ///Customizes the text style of plot band. /// @@ -321,7 +309,7 @@ class PlotBand { /// )); ///} ///``` - final double textAngle; + final double? textAngle; ///Specifies the whether plot band need to be rendered above the series. /// @@ -513,7 +501,7 @@ class PlotBand { /// )); ///} ///``` - final LinearGradient gradient; + final LinearGradient? gradient; ///To move the plot band text vertically. /// @@ -545,7 +533,7 @@ class PlotBand { /// ) /// )); ///} - final String verticalTextPadding; + final String? verticalTextPadding; ///To move the plot band text horizontally. /// @@ -575,11 +563,12 @@ class PlotBand { /// ) /// )); ///} - final String horizontalTextPadding; + final String? horizontalTextPadding; } class _PlotBandPainter extends CustomPainter { - _PlotBandPainter({this.chartState, this.shouldRenderAboveSeries}) + _PlotBandPainter( + {required this.chartState, required this.shouldRenderAboveSeries}) : chart = chartState._chart; final SfCartesianChartState chartState; @@ -647,11 +636,31 @@ class _PlotBandPainter extends CustomPainter { : endValue is num ? endValue : axisRenderer._labels.indexOf(endValue); + } + if (axisRenderer is DateTimeCategoryAxisRenderer) { + startValue = startValue is num + ? startValue + : (startValue is DateTime + ? axisRenderer._labels.indexOf(axisRenderer._axis.isVisible + ? axisRenderer._dateFormat.format(startValue) + : startValue.microsecondsSinceEpoch.toString()) + : axisRenderer._labels.indexOf(startValue)); + endValue = isNeedRepeat + ? plotBand.repeatUntil is num + ? plotBand.repeatUntil.floor() + : axisRenderer._labels.indexOf(plotBand.repeatUntil) + : endValue is num + ? endValue + : endValue is DateTime + ? axisRenderer._labels.indexOf(axisRenderer._axis.isVisible + ? axisRenderer._dateFormat.format(endValue) + : endValue.microsecondsSinceEpoch.toString()) + : axisRenderer._labels.indexOf(endValue); } else if (axisRenderer is LogarithmicAxisRenderer || axisRenderer is NumericAxisRenderer) { endValue = isNeedRepeat ? plotBand.repeatUntil : endValue; } - return _ChartLocation(startValue, endValue); + return _ChartLocation(startValue.toDouble(), endValue.toDouble()); } /// Render a method for plotband @@ -665,8 +674,8 @@ class _PlotBandPainter extends CustomPainter { final _ChartLocation startAndEndValues = _getStartAndEndValues( axisRenderer, - plotBand.start ?? axisRenderer._visibleRange.minimum, - plotBand.end ?? axisRenderer._visibleRange.maximum, + plotBand.start ?? axisRenderer._visibleRange!.minimum, + plotBand.end ?? axisRenderer._visibleRange!.maximum, plotBand, isNeedRepeat); startValue = startAndEndValues.x; @@ -692,13 +701,14 @@ class _PlotBandPainter extends CustomPainter { /// To get and return value for date time axis num _getPlotBandValue(ChartAxisRenderer axisRenderer, PlotBand plotBand, - num value, num addValue) { + num value, num increment) { + final int addValue = increment.toInt(); DateTimeIntervalType intervalType; if (axisRenderer is DateTimeAxisRenderer) { intervalType = (plotBand.sizeType == DateTimeIntervalType.auto) ? axisRenderer._actualIntervalType : plotBand.sizeType; - DateTime date = DateTime.fromMillisecondsSinceEpoch(value); + DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt()); switch (intervalType) { case DateTimeIntervalType.years: date = DateTime(date.year + addValue, date.month, date.day, date.hour, @@ -741,7 +751,7 @@ class _PlotBandPainter extends CustomPainter { final Rect axisRect = chartState._chartAxis._axisClipRect; Rect plotBandRect; int textAngle; - num left, top, bottom, right; + double? left, top, bottom, right; final ChartAxis axis = axisRenderer._axis; startValue = axis is LogarithmicAxis @@ -752,19 +762,19 @@ class _PlotBandPainter extends CustomPainter { : endValue; endValue < 0 - ? endValue <= axisRenderer._visibleRange.minimum - ? endValue = axisRenderer._visibleRange.minimum + ? endValue <= axisRenderer._visibleRange!.minimum + ? endValue = axisRenderer._visibleRange!.minimum : endValue = endValue - : endValue >= axisRenderer._visibleRange.maximum - ? endValue = axisRenderer._visibleRange.maximum + : endValue >= axisRenderer._visibleRange!.maximum + ? endValue = axisRenderer._visibleRange!.maximum : endValue = endValue; startValue < 0 - ? startValue <= axisRenderer._visibleRange.minimum - ? startValue = axisRenderer._visibleRange.minimum + ? startValue <= axisRenderer._visibleRange!.minimum + ? startValue = axisRenderer._visibleRange!.minimum : startValue = startValue - : startValue >= axisRenderer._visibleRange.maximum - ? startValue = axisRenderer._visibleRange.maximum + : startValue >= axisRenderer._visibleRange!.maximum + ? startValue = axisRenderer._visibleRange!.maximum : startValue = startValue; startPoint = _calculatePoint(startValue, startValue, axisRenderer, @@ -772,7 +782,7 @@ class _PlotBandPainter extends CustomPainter { endPoint = _calculatePoint(endValue, endValue, axisRenderer, axisRenderer, chartState._requireInvertedAxis, null, axisRect); - ChartAxisRenderer segmentAxisRenderer; + ChartAxisRenderer? segmentAxisRenderer; if (plotBand.associatedAxisStart != null || plotBand.associatedAxisEnd != null) { if (axis.associatedAxisName == null) { @@ -792,7 +802,7 @@ class _PlotBandPainter extends CustomPainter { } } final _ChartLocation startAndEndValues = _getStartAndEndValues( - segmentAxisRenderer, + segmentAxisRenderer!, plotBand.associatedAxisStart ?? startValue, plotBand.associatedAxisEnd ?? endValue, plotBand, @@ -848,11 +858,12 @@ class _PlotBandPainter extends CustomPainter { } if (axisRenderer._orientation == AxisOrientation.horizontal) { - textAngle = plotBand.textAngle != null ? plotBand.textAngle.toInt() : 270; + textAngle = + plotBand.textAngle != null ? plotBand.textAngle!.toInt() : 270; plotBandRect = Rect.fromLTRB(left ?? startPoint.x, top ?? axisRect.top, right ?? endPoint.x, bottom ?? axisRect.bottom); } else { - textAngle = plotBand.textAngle != null ? plotBand.textAngle.toInt() : 0; + textAngle = plotBand.textAngle != null ? plotBand.textAngle!.toInt() : 0; plotBandRect = Rect.fromLTRB(left ?? axisRect.left, top ?? endPoint.y, right ?? axisRect.right, bottom ?? startPoint.y); } @@ -865,7 +876,7 @@ class _PlotBandPainter extends CustomPainter { final List dashArray = plotBand.dashArray; bool needDashLine = true; if (plotBandRect != null && plotBand.color != null) { - Path path; + Path? path; for (int i = 1; i < dashArray.length; i = i + 2) { if (dashArray[i] == 0) { needDashLine = false; @@ -886,7 +897,7 @@ class _PlotBandPainter extends CustomPainter { paint.isAntiAlias = false; dashPath = !kIsWeb ? _dashPath(path, - dashArray: _CircularIntervalList(dashArray)) + dashArray: _CircularIntervalList(dashArray))! : path; } else { dashPath = path; @@ -895,7 +906,7 @@ class _PlotBandPainter extends CustomPainter { Paint fillPaint; if (plotBand.gradient != null) { fillPaint = Paint() - ..shader = plotBand.gradient.createShader(plotBandRect) + ..shader = plotBand.gradient!.createShader(plotBandRect) ..style = PaintingStyle.fill; } else { fillPaint = Paint() @@ -915,45 +926,45 @@ class _PlotBandPainter extends CustomPainter { } } } - if (plotBand.text != null && plotBand.text.isNotEmpty) { + if (plotBand.text != null && plotBand.text!.isNotEmpty) { final Size textSize = - _measureText(plotBand.text, plotBand.textStyle, textAngle); - num x = 0; - num y = 0; + measureText(plotBand.text!, plotBand.textStyle, textAngle); + num? x = 0; + num? y = 0; if (plotBand.horizontalTextPadding != null && plotBand.horizontalTextPadding != '') { - if (plotBand.horizontalTextPadding.contains('%')) { + if (plotBand.horizontalTextPadding!.contains('%')) { x = _percentageToValue( - plotBand.horizontalTextPadding, + plotBand.horizontalTextPadding!, chart.isTransposed ? chartState._chartAxis._axisClipRect.bottom : chartState._chartAxis._axisClipRect.right); - } else if (plotBand.verticalTextPadding.contains('px')) { - x = double.parse(plotBand.horizontalTextPadding - .substring(0, plotBand.horizontalTextPadding.length - 2)); + } else if (plotBand.verticalTextPadding!.contains('px')) { + x = double.parse(plotBand.horizontalTextPadding! + .substring(0, plotBand.horizontalTextPadding!.length - 2)); } else { - x = double.parse(plotBand.horizontalTextPadding); + x = double.parse(plotBand.horizontalTextPadding!); } } if (plotBand.verticalTextPadding != null && plotBand.verticalTextPadding != '') { - if (plotBand.verticalTextPadding.contains('%')) { + if (plotBand.verticalTextPadding!.contains('%')) { y = _percentageToValue( - plotBand.verticalTextPadding, + plotBand.verticalTextPadding!, chart.isTransposed ? chartState._chartAxis._axisClipRect.right : chartState._chartAxis._axisClipRect.bottom); - } else if (plotBand.verticalTextPadding.contains('px')) { - y = double.parse(plotBand.verticalTextPadding - .substring(0, plotBand.verticalTextPadding.length - 2)); + } else if (plotBand.verticalTextPadding!.contains('px')) { + y = double.parse(plotBand.verticalTextPadding! + .substring(0, plotBand.verticalTextPadding!.length - 2)); } else { - y = double.parse(plotBand.verticalTextPadding); + y = double.parse(plotBand.verticalTextPadding!); } } _drawText( canvas, - plotBand.text, + plotBand.text!, Offset( plotBand.horizontalTextAlignment == TextAnchor.middle ? (plotBandRect.left + @@ -965,11 +976,11 @@ class _PlotBandPainter extends CustomPainter { chartState._chartAxis._axisClipRect.left) ? textSize.width : textSize.width / 2) + - x) + + x!) + (textAngle != 0 ? textSize.width / 2 : 0) : plotBand.horizontalTextAlignment == TextAnchor.start - ? plotBandRect.left + x - : plotBandRect.right - textSize.width + x, + ? plotBandRect.left + x! + : plotBandRect.right - textSize.width + x!, plotBand.verticalTextAlignment == TextAnchor.middle ? (plotBandRect.top + plotBandRect.height / 2 - @@ -979,12 +990,12 @@ class _PlotBandPainter extends CustomPainter { : (plotBandRect.top == chartState ._chartAxis._axisClipRect.bottom) - ? 0 + y - : textSize.height + y)) + + ? 0 + y! + : textSize.height + y!)) + (textAngle != 0 ? textSize.height / 2 : 0) : plotBand.verticalTextAlignment == TextAnchor.start - ? (plotBandRect.top - y) - : plotBandRect.bottom - textSize.height - y), + ? (plotBandRect.top - y!) + : plotBandRect.bottom - textSize.height - y!), plotBand.textStyle, textAngle); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart b/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart index fb99faf73..5b1e49d29 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart @@ -7,10 +7,17 @@ typedef ChartTooltipCallback = void Function(TooltipArgs tooltipArgs); typedef ChartActualRangeChangedCallback = void Function( ActualRangeChangedArgs rangeChangedArgs); +@Deprecated('Use ChartLabelFormatterCallback instead.') + /// Returns the AxisLabelRenderArgs. typedef ChartAxisLabelRenderCallback = void Function( AxisLabelRenderArgs axisLabelRenderArgs); +///Signature for the [axisLabelFormatter] callback that returns [ChartAxisLabel] class value to +/// customize the axis label text and style. +typedef ChartLabelFormatterCallback = ChartAxisLabel Function( + AxisLabelRenderDetails axisLabelRenderArgs); + /// Returns the DataLabelRenderArgs. typedef ChartDataLabelRenderCallback = void Function( DataLabelRenderArgs dataLabelArgs); @@ -57,10 +64,19 @@ typedef ChartIndicatorRenderCallback = void Function( ///Returns the MarkerRenderArgs. typedef ChartMarkerRenderCallback = void Function(MarkerRenderArgs markerArgs); +///Returns a widget which can be used to load more data to chart. +///called on dragging and when the visible range reaches the end. +typedef LoadMoreViewBuilderCallback = Widget Function( + BuildContext context, ChartSwipeDirection direction); + +/// A callback which gets called on swiping over plot area +typedef ChartPlotAreaSwipeCallback = void Function( + ChartSwipeDirection direction); + ///Renders the cartesian type charts. /// ///Cartesian charts are generally charts with horizontal and vertical axes.[SfCartesianChart] provides options to cusomize -/// chart types using the [series] property. +/// chart types using the `series` property. /// ///```dart ///Widget build(BuildContext context) { @@ -124,7 +140,7 @@ typedef ChartMarkerRenderCallback = void Function(MarkerRenderArgs markerArgs); class SfCartesianChart extends StatefulWidget { /// Creating an argument constructor of SfCartesianChart class. SfCartesianChart( - {Key key, + {Key? key, this.backgroundColor, this.enableSideBySideSeriesPlacement = true, this.borderColor = Colors.transparent, @@ -135,7 +151,8 @@ class SfCartesianChart extends StatefulWidget { this.plotAreaBackgroundImage, this.onTooltipRender, this.onActualRangeChanged, - this.onAxisLabelRender, + //ignore: deprecated_member_use_from_same_package + @deprecated this.onAxisLabelRender, this.onDataLabelRender, this.onLegendItemRender, this.onTrackballPositionChanging, @@ -158,6 +175,8 @@ class SfCartesianChart extends StatefulWidget { this.isTransposed = false, this.enableAxisAnimation = false, this.annotations, + this.loadMoreIndicatorBuilder, + this.onPlotAreaSwipe, this.palette = const [ Color.fromRGBO(75, 135, 185, 1), Color.fromRGBO(192, 108, 132, 1), @@ -170,21 +189,22 @@ class SfCartesianChart extends StatefulWidget { Color.fromRGBO(255, 240, 219, 1), Color.fromRGBO(238, 238, 238, 1) ], - ChartAxis primaryXAxis, - ChartAxis primaryYAxis, - EdgeInsets margin, - TooltipBehavior tooltipBehavior, - ZoomPanBehavior zoomPanBehavior, - Legend legend, - SelectionType selectionType, - ActivationMode selectionGesture, - bool enableMultiSelection, - CrosshairBehavior crosshairBehavior, - TrackballBehavior trackballBehavior, - dynamic series, - ChartTitle title, - List axes, - List> indicators}) + this.axisLabelFormatter, + ChartAxis? primaryXAxis, + ChartAxis? primaryYAxis, + EdgeInsets? margin, + TooltipBehavior? tooltipBehavior, + ZoomPanBehavior? zoomPanBehavior, + Legend? legend, + SelectionType? selectionType, + ActivationMode? selectionGesture, + bool? enableMultiSelection, + CrosshairBehavior? crosshairBehavior, + TrackballBehavior? trackballBehavior, + dynamic? series, + ChartTitle? title, + List? axes, + List>? indicators}) : primaryXAxis = primaryXAxis ?? NumericAxis(), primaryYAxis = primaryYAxis ?? NumericAxis(), title = title ?? ChartTitle(), @@ -243,7 +263,7 @@ class SfCartesianChart extends StatefulWidget { /// )); ///} ///``` - final Color backgroundColor; + final Color? backgroundColor; ///Color of the chart border. /// @@ -286,7 +306,7 @@ class SfCartesianChart extends StatefulWidget { /// )); ///} ///``` - final Color plotAreaBackgroundColor; + final Color? plotAreaBackgroundColor; ///Border color of the plot area. /// @@ -300,7 +320,7 @@ class SfCartesianChart extends StatefulWidget { /// )); ///} ///``` - final Color plotAreaBorderColor; + final Color? plotAreaBorderColor; ///Border width of the plot area. /// @@ -398,7 +418,7 @@ class SfCartesianChart extends StatefulWidget { /// args.locationX = 30; ///} ///``` - final ChartTooltipCallback onTooltipRender; + final ChartTooltipCallback? onTooltipRender; /// Occurs when the visible range of an axis is changed, i.e. value changes for minimum, /// maximum, and interval. Here, you can get the actual and visible range of an axis. @@ -413,7 +433,7 @@ class SfCartesianChart extends StatefulWidget { /// print(args.visibleMin); ///} ///``` - final ChartActualRangeChangedCallback onActualRangeChanged; + final ChartActualRangeChangedCallback? onActualRangeChanged; /// Occurs while rendering the axis labels. Text and text styles such as color, font size, /// and font weight can be customized. @@ -428,7 +448,8 @@ class SfCartesianChart extends StatefulWidget { /// args.text = 'axis Label'; ///} ///``` - final ChartAxisLabelRenderCallback onAxisLabelRender; + //ignore: deprecated_member_use_from_same_package + final ChartAxisLabelRenderCallback? onAxisLabelRender; /// Occurs when tapping the axis label. Here, you can get the appropriate axis that is /// tapped and the axis label text. @@ -443,7 +464,7 @@ class SfCartesianChart extends StatefulWidget { /// args.text = 'data Label'; ///} ///``` - final ChartDataLabelRenderCallback onDataLabelRender; + final ChartDataLabelRenderCallback? onDataLabelRender; /// Occurs when the legend item is rendered. Here, you can get the legend’s text, /// shape, series index, and point index of circular series. @@ -459,7 +480,7 @@ class SfCartesianChart extends StatefulWidget { /// args.seriesIndex = 2; ///} ///``` - final ChartLegendRenderCallback onLegendItemRender; + final ChartLegendRenderCallback? onLegendItemRender; /// Occurs when the trendline is rendered. Here, you can get the legend’s text, /// shape, series index, and point index of circular series. @@ -474,7 +495,7 @@ class SfCartesianChart extends StatefulWidget { /// args.seriesIndex = 2; ///} ///``` - final ChartTrendlineRenderCallback onTrendlineRender; + final ChartTrendlineRenderCallback? onTrendlineRender; /// Occurs while the trackball position is changed. Here, you can customize the text of /// the trackball. @@ -490,7 +511,7 @@ class SfCartesianChart extends StatefulWidget { /// args.chartPointInfo = ChartPointInfo(); ///} ///``` - final ChartTrackballCallback onTrackballPositionChanging; + final ChartTrackballCallback? onTrackballPositionChanging; /// Occurs when tapping the axis label. Here, you can get the appropriate axis that is /// tapped and the axis label text. @@ -506,7 +527,7 @@ class SfCartesianChart extends StatefulWidget { /// args.text = 'crosshair'; ///} ///``` - final ChartCrosshairCallback onCrosshairPositionChanging; + final ChartCrosshairCallback? onCrosshairPositionChanging; /// Occurs when zooming action begins. You can customize the zoom factor and zoom /// position of an axis. Here, you can get the axis, current zoom factor, current @@ -523,7 +544,7 @@ class SfCartesianChart extends StatefulWidget { /// args.currentZoomFactor = 0.2; ///} ///``` - final ChartZoomingCallback onZoomStart; + final ChartZoomingCallback? onZoomStart; /// Occurs when the zooming action is completed. Here, you can get the axis, current /// zoom factor, current zoom position, previous zoom factor, and previous zoom position. @@ -539,7 +560,7 @@ class SfCartesianChart extends StatefulWidget { /// print(args.currentZoomPosition); ///} ///``` - final ChartZoomingCallback onZoomEnd; + final ChartZoomingCallback? onZoomEnd; /// Occurs when zoomed state is reset. Here, you can get the axis, current zoom factor, /// current zoom position, previous zoom factor, and previous zoom position. @@ -555,7 +576,7 @@ class SfCartesianChart extends StatefulWidget { /// print(args.currentZoomPosition); ///} ///``` - final ChartZoomingCallback onZoomReset; + final ChartZoomingCallback? onZoomReset; /// Occurs when Zoooming event is performed. Here, you can get the axis, current zoom factor, /// current zoom position, previous zoom factor, and previous zoom position. @@ -571,7 +592,7 @@ class SfCartesianChart extends StatefulWidget { /// print(args.currentZoomPosition); ///} ///``` - final ChartZoomingCallback onZooming; + final ChartZoomingCallback? onZooming; /// Occurs when tapping the series point. Here, you can get the series, series index /// and point index. @@ -587,7 +608,7 @@ class SfCartesianChart extends StatefulWidget { ///} ///``` - final ChartPointTapCallback onPointTapped; + final ChartPointTapCallback? onPointTapped; ///Called when the data label is tapped. /// @@ -609,7 +630,7 @@ class SfCartesianChart extends StatefulWidget { /// ///``` - final DataLabelTapCallback onDataLabelTapped; + final DataLabelTapCallback? onDataLabelTapped; ///Occurs when tapping the axis label. Here, you can get the appropriate axis that is /// tapped and the axis label text. @@ -624,7 +645,7 @@ class SfCartesianChart extends StatefulWidget { /// print(args.text); ///} ///``` - final ChartAxisLabelTapCallback onAxisLabelTapped; + final ChartAxisLabelTapCallback? onAxisLabelTapped; /// Occurs when the legend item is rendered. Here, you can get the legend’s text, /// shape, series index, and point index of circular series. @@ -639,7 +660,7 @@ class SfCartesianChart extends StatefulWidget { /// print(args.pointIndex); ///} ///``` - final ChartLegendTapCallback onLegendTapped; + final ChartLegendTapCallback? onLegendTapped; /// Occurs while selection changes. Here, you can get the series, selected color, /// unselected color, selected border color, unselected border color, selected border @@ -651,7 +672,7 @@ class SfCartesianChart extends StatefulWidget { /// onSelectionChanged: (SelectionArgs args) => print(args.selectedColor), /// )); ///} - final ChartSelectionCallback onSelectionChanged; + final ChartSelectionCallback? onSelectionChanged; /// Occurs when tapped on the chart area. ///```dart @@ -665,7 +686,7 @@ class SfCartesianChart extends StatefulWidget { /// )); ///} ///``` - final ChartTouchInteractionCallback onChartTouchInteractionUp; + final ChartTouchInteractionCallback? onChartTouchInteractionUp; /// Occurs when touched on the chart area. ///```dart @@ -679,7 +700,7 @@ class SfCartesianChart extends StatefulWidget { /// )); ///} ///``` - final ChartTouchInteractionCallback onChartTouchInteractionDown; + final ChartTouchInteractionCallback? onChartTouchInteractionDown; /// Occurs when touched and moved on the chart area. ///```dart @@ -693,7 +714,7 @@ class SfCartesianChart extends StatefulWidget { /// )); ///} ///``` - final ChartTouchInteractionCallback onChartTouchInteractionMove; + final ChartTouchInteractionCallback? onChartTouchInteractionMove; /// Occurs when the indicator is rendered. Here, you can get the indicatorname,, /// seriesname, indicator index, and width of indicators. @@ -713,7 +734,7 @@ class SfCartesianChart extends StatefulWidget { /// )); ///} ///``` - final ChartIndicatorRenderCallback onIndicatorRender; + final ChartIndicatorRenderCallback? onIndicatorRender; /// Occurs when the marker is rendered. Here, you can get the marker pointIndex /// shape, height and width of data markers. @@ -734,7 +755,7 @@ class SfCartesianChart extends StatefulWidget { ///} ///``` /// - final ChartMarkerRenderCallback onMarkerRender; + final ChartMarkerRenderCallback? onMarkerRender; ///Customizes the tooltip in chart. /// @@ -820,7 +841,7 @@ class SfCartesianChart extends StatefulWidget { /// )); ///} ///``` - final List annotations; + final List? annotations; ///Enables or disables the multiple data points or series selection. /// @@ -881,7 +902,7 @@ class SfCartesianChart extends StatefulWidget { /// )); ///} ///``` - final ImageProvider plotAreaBackgroundImage; + final ImageProvider? plotAreaBackgroundImage; ///Data points or series can be selected while performing interaction on the chart. ///It can also be selected at the initial rendering using this property. @@ -970,9 +991,160 @@ class SfCartesianChart extends StatefulWidget { ///``` final List palette; + ///Called while rendering each axis label in the chart. + /// + ///Provides label text, axis name, orientation of the axis, trimmed text and text styles such as color, + /// font size, and font weight to the user using the [AxisLabelRenderDetails] class. + /// + ///You can customize the text and text style using the [ChartAxisLabel] class and can return it. + final ChartLabelFormatterCallback? axisLabelFormatter; + ///Technical indicators for charts. final List> indicators; + ///A builder that builds the widget (ex., loading indicator or load more button) + ///to display at the top of the chart area when horizontal scrolling reaches + ///the start or end of the chart. + /// + ///This can be used to achieve the features like load more and infinite + ///scrolling in the chart. Also provides the swiping direction value to the user. + /// + ///If the chart is transposed, this will be called when the vertical scrolling + ///reaches the top or bottom of the chart. + /// + ///## Infinite scrolling + /// + ///The below example demonstrates the infinite scrolling by showing + ///the circular progress indicator until the data is loaded when horizontal + ///scrolling reaches the end of the chart. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// loadMoreIndicatorBuilder: + /// (BuildContext context, ChartSwipeDirection direction) => + /// getLoadMoreViewBuilder(context, direction), + /// series: >[ + /// AreaSeries( + /// dataSource: chartData, + /// ), + /// ], + /// )); + ///} + ///Widget getLoadMoreViewBuilder( + /// BuildContext context, ChartSwipeDirection direction) { + /// if (direction == ChartSwipeDirection.end) { + /// return FutureBuilder( + /// future: _updateData(), /// Adding data by updateDataSource method + /// builder: + /// (BuildContext futureContext, AsyncSnapshot snapShot) { + /// return snapShot.connectionState != ConnectionState.done + /// ? const CircularProgressIndicator() + /// : SizedBox.fromSize(size: Size.zero); + /// }, + /// ); + /// } else { + /// return SizedBox.fromSize(size: Size.zero); + /// } + /// } + ///``` + /// + ///## Load more + /// + ///The below example demonstrates how to show a button when horizontal + ///scrolling reaches the end of the chart. + ///On tapping the button circular indicator will be displayed and data will be + ///loaded to the chart. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// loadMoreIndicatorBuilder: + /// (BuildContext context, ChartSwipeDirection direction) => + /// _buildLoadMoreView(context, direction), + /// series: >[ + /// LineSeries( + /// dataSource: chartData, + /// ), + /// ], + /// )); + ///} + /// Widget _buildLoadMoreView( + /// BuildContext context, ChartSwipeDirection direction) { + /// _visible = true; + /// if (direction == ChartSwipeDirection.end) { + /// return StatefulBuilder( + /// builder: (BuildContext context, StateSetter stateSetter) { + /// return Visibility( + /// visible: _visible, + /// child: RaisedButton( + /// child: const Text('Load More'), + /// onPressed: () async{ + /// stateSetter(() { + /// _visible = false; + /// }); + /// await loadMore(); + /// }), + /// ); + /// }); + /// } else { + /// return null; + /// } + /// } + ///FutureBuilder loadMore() { + /// return FutureBuilder( + /// future: _updateData(), /// Adding data by updateDataSource method + /// builder: + /// (BuildContext futureContext, AsyncSnapshot snapShot) { + /// return snapShot.connectionState != ConnectionState.done + /// ? const CircularProgressIndicator() + /// : SizedBox.fromSize(size: Size.zero); + /// }, + /// ); + /// } + ///``` + final LoadMoreViewBuilderCallback? loadMoreIndicatorBuilder; + + ///Called while swiping on the plot area. + /// + ///Whenever the swiping happens on the plot area (the series rendering area), `onPlotAreaSwipe` callback + /// will be called. It provides options to get the direction of swiping. + /// + ///If the chart is swiped from left to right direction, the direction is `ChartSwipeDirection.start` and + /// if the swipe happens from right to left direction, the direction is `ChartSwipeDirection.end`. + /// + ///Using this callback, the user able to achieve pagination functionality (on swiping over chart area, + /// next set of data points can be loaded to the chart). + /// + ///Also refer [ChartSwipeDirection]. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// onPlotAreaSwipe: + /// (ChartSwipeDirection direction) => + /// performSwipe(direction), + /// series: >[ + /// AreaSeries( + /// dataSource: chartData, + /// ), + /// ], + /// )); + ///} + ///Widget performSwipe(ChartSwipeDirection direction) { + /// if (direction == ChartSwipeDirection.end) { + /// chartData.add(ChartSampleData( + /// x: chartData[chartData.length - 1].x + 1, + /// y: 10)); + /// seriesController.updateDataSource(addedDataIndex: chartData.length - 1); + /// } + /// } + ///``` + final ChartPlotAreaSwipeCallback? onPlotAreaSwipe; + @override State createState() => SfCartesianChartState(); } @@ -982,96 +1154,96 @@ class SfCartesianChart extends StatefulWidget { class SfCartesianChartState extends State with TickerProviderStateMixin { /// Holds the animation controller along with their listener for all series and trenddlines - Map _controllerList; + late Map _controllerList; /// Repaint notifier for zoom container - ValueNotifier _zoomRepaintNotifier; + late ValueNotifier _zoomRepaintNotifier; /// Repaint notifier for trendline container - ValueNotifier _trendlineRepaintNotifier; + late ValueNotifier _trendlineRepaintNotifier; /// Repaint notifier for trackball container - ValueNotifier _trackballRepaintNotifier; + late ValueNotifier _trackballRepaintNotifier; /// Repaint notifier for crosshair container - ValueNotifier _crosshairRepaintNotifier; + late ValueNotifier _crosshairRepaintNotifier; /// Repaint notifier for indicator //ignore: unused_field - ValueNotifier _indicatorRepaintNotifier; + late ValueNotifier _indicatorRepaintNotifier; /// To measure legend size and position - List<_MeasureWidgetContext> _legendWidgetContext; + late List<_MeasureWidgetContext> _legendWidgetContext; /// Chart Template info - List<_ChartTemplateInfo> _templates; - List _zoomedAxisRendererStates; + late List<_ChartTemplateInfo> _templates; + late List _zoomedAxisRendererStates; - List _oldAxisRenderers; + late List _oldAxisRenderers; /// Contains chart container size - Rect _containerRect; + late Rect _containerRect; /// Holds the information of chart theme arguments - SfChartThemeData _chartTheme; - bool _zoomProgress; - List<_ZoomAxisRange> _zoomAxes; - bool _initialRender; - List<_LegendRenderContext> _legendToggleStates; - List _selectedSegments; - List _unselectedSegments; - List<_MeasureWidgetContext> _legendToggleTemplateStates; - List _renderDatalabelRegions; - Orientation _deviceOrientation; - List _dataLabelTemplateRegions; - List _annotationRegions; - bool _animateCompleted; + late SfChartThemeData _chartTheme; + late bool _zoomProgress; + late List<_ZoomAxisRange> _zoomAxes; + bool? _initialRender; + late List<_LegendRenderContext> _legendToggleStates; + late List _selectedSegments; + late List _unselectedSegments; + late List<_MeasureWidgetContext> _legendToggleTemplateStates; + late List _renderDatalabelRegions; + late Orientation _deviceOrientation; + late List _dataLabelTemplateRegions; + late List _annotationRegions; + late bool _animateCompleted; bool _legendRefresh = false; - bool _widgetNeedUpdate; - _DataLabelRenderer _renderDataLabel; - _CartesianAxisRenderer _renderOutsideAxis; - _CartesianAxisRenderer _renderInsideAxis; - List _oldSeriesRenderers; - List> _oldSeriesKeys; - bool _isLegendToggled; - List _segments; - List _oldSeriesVisible; - bool _zoomedState; - List _touchStartPositions; - List _touchMovePositions; - bool _enableDoubleTap; - Orientation _oldDeviceOrientation; - bool _legendToggling; - dart_ui.Image _backgroundImage; - dart_ui.Image _legendIconImage; + late bool _widgetNeedUpdate; + _DataLabelRenderer? _renderDataLabel; + late _CartesianAxisRenderer _renderOutsideAxis; + late _CartesianAxisRenderer _renderInsideAxis; + late List _oldSeriesRenderers; + late List?> _oldSeriesKeys; + late bool _isLegendToggled; + late List _segments; + late List _oldSeriesVisible; + bool? _zoomedState; + late List _touchStartPositions; + late List _touchMovePositions; + late bool _enableDoubleTap; + Orientation? _oldDeviceOrientation; + bool _legendToggling = false; + dart_ui.Image? _backgroundImage; + dart_ui.Image? _legendIconImage; bool _isTrendlineToggled = false; - List<_PainterKey> _painterKeys; - bool _triggerLoaded; + late List<_PainterKey> _painterKeys; + late bool _triggerLoaded; //ignore: prefer_final_fields bool _rangeChangeBySlider = false; //ignore: prefer_final_fields bool _rangeChangedByChart = false; //ignore: prefer_final_fields bool _isRangeSelectionSlider = false; - bool _isSeriesLoaded; - bool _isNeedUpdate; - List _seriesRenderers; + bool? _isSeriesLoaded; + late bool _isNeedUpdate; + late List _seriesRenderers; /// Holds the information of AxisBase class - _ChartAxis _chartAxis; + late _ChartAxis _chartAxis; /// Holds the information of SeriesBase class - _ChartSeries _chartSeries; + late _ChartSeries _chartSeries; /// Holds the information of LegendBase class - _ChartLegend _chartLegend; + late _ChartLegend _chartLegend; /// Holds the information of _ContainerArea class /// ignore: unused_field - _ContainerArea _containerArea; + late _ContainerArea _containerArea; /// Whether to check chart axis is inverted or not - bool _requireInvertedAxis; + late bool _requireInvertedAxis; /// To check if axis trimmed text is tapped //ignore: prefer_final_fields @@ -1080,19 +1252,19 @@ class SfCartesianChartState extends State //ignore: prefer_final_fields List<_ChartPointInfo> _chartPointInfo = <_ChartPointInfo>[]; - ZoomPanBehaviorRenderer _zoomPanBehaviorRenderer; + late ZoomPanBehaviorRenderer _zoomPanBehaviorRenderer; - TooltipBehaviorRenderer _tooltipBehaviorRenderer; + late TooltipBehaviorRenderer _tooltipBehaviorRenderer; - TrackballBehaviorRenderer _trackballBehaviorRenderer; + late TrackballBehaviorRenderer _trackballBehaviorRenderer; - CrosshairBehaviorRenderer _crosshairBehaviorRenderer; + late CrosshairBehaviorRenderer _crosshairBehaviorRenderer; - LegendRenderer _legendRenderer; + late LegendRenderer _legendRenderer; - List _technicalIndicatorRenderer; + late List _technicalIndicatorRenderer; - TrackballMarkerSettingsRenderer _trackballMarkerSettingsRenderer; + late TrackballMarkerSettingsRenderer _trackballMarkerSettingsRenderer; //Here, we are using get keyword inorder to get the proper & updated instance of chart widget //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. @@ -1105,15 +1277,36 @@ class SfCartesianChartState extends State final double _trendlineDurationFactor = 0.85; //holds the count for total no of series that should be animated - int _totalAnimatingSeries; + late int _totalAnimatingSeries; //holds the no of animation completed series - int _animationCompleteCount; + late int _animationCompleteCount; - SelectionArgs _selectionArgs; + SelectionArgs? _selectionArgs; bool _isTouchUp = false; + late StateSetter _loadMoreViewStateSetter; + + late ChartSwipeDirection _swipeDirection; + + Offset? _startOffset, _currentPosition; + late bool _isRedrawByZoomPan; + + ///To check the load more widget is in progress or not + late bool _isLoadMoreIndicator; + + // ignore: unused_element + bool get _animationCompleted { + for (final seriesRenderer in _seriesRenderers) { + if (seriesRenderer._animationController.status == + AnimationStatus.forward) { + return false; + } + } + return true; + } + /// To intialize default values void _initializeDefaultValues() { _chartAxis = _ChartAxis(this); @@ -1144,15 +1337,17 @@ class SfCartesianChartState extends State _animateCompleted = false; _widgetNeedUpdate = false; _oldSeriesRenderers = []; - _oldSeriesKeys = >[]; + _oldSeriesKeys = ?>[]; _isLegendToggled = false; - _oldSeriesVisible = []; + _oldSeriesVisible = []; _touchStartPositions = []; _touchMovePositions = []; _enableDoubleTap = false; _legendToggling = false; _painterKeys = <_PainterKey>[]; _isNeedUpdate = true; + _isRedrawByZoomPan = false; + _isLoadMoreIndicator = false; _technicalIndicatorRenderer = []; _zoomPanBehaviorRenderer = ZoomPanBehaviorRenderer(this); _trackballBehaviorRenderer = TrackballBehaviorRenderer(this); @@ -1163,6 +1358,17 @@ class SfCartesianChartState extends State widget.trackballBehavior.markerSettings); } + /// Called when this object is inserted into the tree. + /// + /// The framework will call this method exactly once for each State object it creates. + /// + /// Override this method to perform initialization that depends on the location at + /// which this object was inserted into the tree or on the widget used to configure this object. + /// + /// * In [initState], subscribe to the object. + /// + /// Here it overrides to initialize the object that depends on rendering the [SfCartesianChart]. + @override void initState() { _initializeDefaultValues(); @@ -1171,14 +1377,39 @@ class SfCartesianChartState extends State super.initState(); } + /// Called when a dependency of this [State] object changes. + /// + /// For example, if the previous call to [build] referenced an [InheritedWidget] that later changed, + /// the framework would call this method to notify this object about the change. + /// + /// This method is also called immediately after [initState]. It is safe to call [BuildContext.dependOnInheritedWidgetOfExactType] from this method. + /// + /// Here it called for initializing the chart theme of [SfCartesianChart]. + @override void didChangeDependencies() { _chartTheme = SfChartTheme.of(context); super.didChangeDependencies(); } + /// Called whenever the widget configuration changes. + /// + /// If the parent widget rebuilds and request that this location in the tree update to display a new widget with the same [runtimeType] and [Widget.key], + /// the framework will update the widget property of this [State] object to refer to the new widget and then call this method with the previous widget as an argument. + /// + /// Override this method to respond when the widget changes. + /// + /// The framework always calls [build] after calling [didUpdateWidget], which means any calls to [setState] in [didUpdateWidget] are redundant. + /// + /// * In [didUpdateWidget] unsubscribe from the old object and subscribe to the new one if the updated widget configuration requires replacing the object. + /// + /// Here it called whenever the series collection gets updated in [SfCartesianChart]. + @override void didUpdateWidget(SfCartesianChart oldWidget) { + _isRedrawByZoomPan = false; + _isLoadMoreIndicator = false; + _zoomProgress = false; final List oldWidgetSeriesRenderers = //ignore: prefer_spread_collections []..addAll(_seriesRenderers); @@ -1196,6 +1427,14 @@ class SfCartesianChartState extends State if (_legendWidgetContext != null && _legendWidgetContext.isNotEmpty) { _legendWidgetContext.clear(); } + if (_seriesRenderers.isNotEmpty && + _seriesRenderers[0]._selectionBehaviorRenderer?._selectionRenderer != + null) { + _seriesRenderers[0] + ._selectionBehaviorRenderer + ?._selectionRenderer + ?._isInteraction = false; + } if (_isNeedUpdate) { _widgetNeedUpdate = true; _oldSeriesRenderers = oldWidgetSeriesRenderers; @@ -1207,6 +1446,17 @@ class SfCartesianChartState extends State super.didUpdateWidget(oldWidget); } + /// Describes the part of the user interface represented by this widget. + /// + /// The framework calls this method in a number of different situations. For example: + /// + /// * After calling [initState]. + /// * After calling [didUpdateWidget]. + /// * After receiving a call to [setState]. + /// * After a dependency of this [State] object changes. + /// + /// Here it is called whenever the user interaction is performed and it removes the old widget and updates a chart with a new widget in [SfCartesianChart]. + @override Widget build(BuildContext context) { _oldDeviceOrientation = _oldDeviceOrientation == null @@ -1235,6 +1485,18 @@ class SfCartesianChartState extends State ))); } + /// Called when this object is removed from the tree permanently. + /// + /// The framework calls this method when this [State] object will never build again. After the framework calls [dispose], + /// the [State] object is considered unmounted and the [mounted] property is false. It is an error to call [setState] at this + /// point. This stage of the lifecycle is terminal: there is no way to remount a [State] object that has been disposed. + /// + /// Subclasses should override this method to release any resources retained by this object. + /// + /// * In [dispose], unsubscribe from the object. + /// + /// Here it end the animation controller of the series in [SfCartesianChart]. + @override void dispose() { _controllerList.forEach(_disposeAnimationController); @@ -1245,7 +1507,7 @@ class SfCartesianChartState extends State /// /// As this method is in the widget’s state class, /// you have to use a global key to access the state to call this method. - /// Returns the [dart:ui.image] + /// Returns the `dart:ui.image` /// /// ```dart /// final GlobalKey _key = GlobalKey(); @@ -1298,8 +1560,8 @@ class SfCartesianChartState extends State ///``` Future toImage({double pixelRatio = 1.0}) async { - final RenderRepaintBoundary boundary = - context.findRenderObject(); //get the render object from context + final RenderRepaintBoundary boundary = context.findRenderObject() + as RenderRepaintBoundary; //get the render object from context final dart_ui.Image image = await boundary.toImage(pixelRatio: pixelRatio); // Convert @@ -1310,7 +1572,7 @@ class SfCartesianChartState extends State ///Storing old series key values void _getOldSeriesKeys(List oldSeriesRenderers) { - _oldSeriesKeys = >[]; + _oldSeriesKeys = ?>[]; for (int i = 0; i < oldSeriesRenderers.length; i++) { _oldSeriesKeys.add(oldSeriesRenderers[i]._series.key); } @@ -1318,24 +1580,24 @@ class SfCartesianChartState extends State // In this method, create and update the series renderer for each series // void _createAndUpdateSeriesRenderer( - [SfCartesianChart oldWidget, - List oldWidgetSeriesRenderers, - List oldWidgetOldSeriesRenderers]) { + [SfCartesianChart? oldWidget, + List? oldWidgetSeriesRenderers, + List? oldWidgetOldSeriesRenderers]) { if (widget.series != null && widget.series.isNotEmpty) { if (oldWidget != null) { _oldSeriesRenderers = []; - _oldSeriesRenderers.addAll(oldWidgetSeriesRenderers); + _oldSeriesRenderers.addAll(oldWidgetSeriesRenderers!); } _seriesRenderers = []; final int seriesLength = widget.series.length; dynamic series; - int index, oldSeriesIndex; + int? index, oldSeriesIndex; for (int i = 0; i < seriesLength; i++) { series = widget.series[i]; index = null; oldSeriesIndex = null; if (oldWidget != null) { - if (oldWidgetOldSeriesRenderers.isNotEmpty) { + if (oldWidgetOldSeriesRenderers!.isNotEmpty) { // Check the current series is already exist in oldwidget // index = i < oldWidgetOldSeriesRenderers.length && _isSameSeries( @@ -1343,7 +1605,7 @@ class SfCartesianChartState extends State ? i : _getExistingSeriesIndex(series, oldWidgetOldSeriesRenderers); } - if (oldWidgetSeriesRenderers.isNotEmpty) { + if (oldWidgetSeriesRenderers!.isNotEmpty) { oldSeriesIndex = i < oldWidgetSeriesRenderers.length && _isSameSeries(oldWidgetSeriesRenderers[i]._series, series) ? i @@ -1354,7 +1616,7 @@ class SfCartesianChartState extends State CartesianSeriesRenderer seriesRenderer; if (index != null && - index < oldWidgetOldSeriesRenderers.length && + index < oldWidgetOldSeriesRenderers!.length && oldWidgetOldSeriesRenderers[index] != null) { seriesRenderer = oldWidgetOldSeriesRenderers[index]; } else { @@ -1369,7 +1631,8 @@ class SfCartesianChartState extends State seriesRenderer._animationController .addStatusListener(seriesRenderer._animationStatusListener); } - seriesRenderer._controller ??= ChartSeriesController(seriesRenderer); + seriesRenderer._controller ??= + ChartSeriesController(seriesRenderer as XyDataSeriesRenderer); } if (series.onRendererCreated != null) { series.onRendererCreated(seriesRenderer._controller); @@ -1390,7 +1653,8 @@ class SfCartesianChartState extends State oldWidgetSeriesRenderers[oldSeriesIndex] is FastLineSeriesRenderer) { final FastLineSeriesRenderer fastlineSeriesRenderer = - oldWidgetSeriesRenderers[oldSeriesIndex]; + oldWidgetSeriesRenderers[oldSeriesIndex] + as FastLineSeriesRenderer; seriesRenderer._oldDataPoints = >[] //ignore: prefer_spread_collections ..addAll(fastlineSeriesRenderer._overallDataPoints); @@ -1420,7 +1684,7 @@ class SfCartesianChartState extends State } /// Check current series index is exist in another index - int _getExistingSeriesIndex(CartesianSeries currentSeries, + int? _getExistingSeriesIndex(CartesianSeries currentSeries, List oldSeriesRenderers) { if (currentSeries.key != null) { for (int index = 0; index < oldSeriesRenderers.length; index++) { @@ -1439,7 +1703,8 @@ class SfCartesianChartState extends State if (_legendWidgetContext.isNotEmpty) { for (int i = 0; i < _legendWidgetContext.length; i++) { final _MeasureWidgetContext templateContext = _legendWidgetContext[i]; - final RenderBox renderBox = templateContext.context.findRenderObject(); + final RenderBox renderBox = + templateContext.context!.findRenderObject() as RenderBox; templateContext.size = renderBox.size; } _legendRefresh = true; @@ -1452,16 +1717,14 @@ class SfCartesianChartState extends State /// Redraw method for chart axis void _redraw() { _oldAxisRenderers = _chartAxis._axisRenderersCollection; - if (_tooltipBehaviorRenderer?._painter?.timer != null) { - _tooltipBehaviorRenderer._painter.timer.cancel(); - } - if (_trackballBehaviorRenderer?._trackballPainter?.timer != null) { - _trackballBehaviorRenderer._trackballPainter.timer.cancel(); + _tooltipBehaviorRenderer._timer?.cancel(); + if (_trackballBehaviorRenderer._trackballPainter?.timer != null) { + _trackballBehaviorRenderer._trackballPainter?.timer!.cancel(); } if (_isLegendToggled) { _segments = []; _oldSeriesVisible = - List(_chartSeries.visibleSeriesRenderers.length); + List.filled(_chartSeries.visibleSeriesRenderers.length, null); for (int i = 0; i < _chartSeries.visibleSeriesRenderers.length; i++) { final CartesianSeriesRenderer seriesRenderer = _chartSeries.visibleSeriesRenderers[i]; @@ -1478,7 +1741,7 @@ class SfCartesianChartState extends State _zoomedState = false; for (final ChartAxisRenderer axisRenderer in _zoomedAxisRendererStates) { _zoomedState = axisRenderer._zoomFactor != 1; - if (_zoomedState) { + if (_zoomedState!) { break; } } @@ -1487,6 +1750,7 @@ class SfCartesianChartState extends State _widgetNeedUpdate = false; if (mounted) { + _isRedrawByZoomPan = true; setState(() { /// check the "mounted" property of this object and to ensure the object is still in the tree. /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. @@ -1572,7 +1836,8 @@ class SfCartesianChartState extends State border: Border.all( color: _chart.title.borderWidth == 0 ? Colors.transparent - : _chart.title.borderColor ?? _chartTheme.titleTextColor, + : _chart + .title.borderColor, // ?? _chartTheme.titleTextColor, width: _chart.title.borderWidth)), ), alignment: (_chart.title.alignment == ChartAlignment.near) @@ -1598,32 +1863,32 @@ class SfCartesianChartState extends State return Expanded( child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - Widget element; + Widget? element; final List legendTemplates = _bindCartesianLegendTemplateWidgets(); if (legendTemplates.isNotEmpty && _legendWidgetContext.isEmpty) { element = Container(child: Stack(children: legendTemplates)); - SchedulerBinding.instance.addPostFrameCallback((_) => _refresh()); + SchedulerBinding.instance?.addPostFrameCallback((_) => _refresh()); } else { _initialize(constraints); _chartLegend._calculateLegendBounds(_chartLegend.chartSize); element = _getElements(this, _ContainerArea(this), constraints); } - return element; + return element!; }), ); } /// To return the template widget List _bindCartesianLegendTemplateWidgets() { - Widget legendWidget; + Widget? legendWidget; final List templates = []; - if (widget.legend.isVisible && widget.legend.legendItemBuilder != null) { + if (widget.legend.isVisible! && widget.legend.legendItemBuilder != null) { for (int i = 0; i < _seriesRenderers.length; i++) { final CartesianSeriesRenderer seriesRenderer = _seriesRenderers[i]; if (seriesRenderer._series.isVisibleInLegend) { - legendWidget = widget.legend.legendItemBuilder( - seriesRenderer._seriesName, seriesRenderer._series, null, i); + legendWidget = widget.legend.legendItemBuilder!( + seriesRenderer._seriesName!, seriesRenderer._series, null, i); templates.add(_MeasureWidgetSize( chartState: this, seriesIndex: i, @@ -1676,7 +1941,7 @@ class SfCartesianChartState extends State if (cartesianSeries.trendlines != null) { _seriesRenderers[i]._trendlineRenderer = []; final List trendlineTypes = [0, 0, 0, 0, 0, 0]; - for (final Trendline trendline in cartesianSeries.trendlines) { + for (final Trendline trendline in cartesianSeries.trendlines!) { trendlineRenderer = TrendlineRenderer(trendline); _seriesRenderers[i]._trendlineRenderer.add(trendlineRenderer); trendlineRenderer._name ??= @@ -1688,26 +1953,26 @@ class SfCartesianChartState extends State for (final TrendlineRenderer trendlineRenderer in _seriesRenderers[i]._trendlineRenderer) { trendline = trendlineRenderer._trendline; - trendlineRenderer._name = trendlineRenderer._name[0].toUpperCase() + - trendlineRenderer._name.substring(1); + trendlineRenderer._name = trendlineRenderer._name![0].toUpperCase() + + trendlineRenderer._name!.substring(1); if (trendlineTypes[trendline.type.index] == 1 && - trendlineRenderer._name[trendlineRenderer._name.length - 1] == + trendlineRenderer._name![trendlineRenderer._name!.length - 1] == '0') { - trendlineRenderer._name = trendlineRenderer._name - .substring(0, trendlineRenderer._name.length - 2); + trendlineRenderer._name = trendlineRenderer._name! + .substring(0, trendlineRenderer._name!.length - 2); } } } - if (_initialRender || + if (_initialRender! || (_widgetNeedUpdate && !_legendToggling && (_oldDeviceOrientation == MediaQuery.of(context).orientation))) { if (_seriesRenderers[i]._oldSeries != null && - (_seriesRenderers[i]._oldSeries.isVisible == + (_seriesRenderers[i]._oldSeries!.isVisible == _seriesRenderers[i]._series.isVisible)) { legendCheck = true; } else { - _seriesRenderers[i]._visible = _initialRender + _seriesRenderers[i]._visible = _initialRender! ? _seriesRenderers[i]._series.isVisible : _seriesRenderers[i]._visible ?? _seriesRenderers[i]._series.isVisible; @@ -1721,7 +1986,7 @@ class SfCartesianChartState extends State (_seriesRenderers[0]._series.toString().contains('Bar') && (_seriesRenderers[i]._series.toString().contains('Bar')))) { visibleSeriesRenderers.add(_seriesRenderers[i]); - if (!_initialRender && + if (!_initialRender! && _oldSeriesVisible.isNotEmpty && i < visibleSeriesRenderers.length) { if (i < visibleSeriesRenderers.length && @@ -1731,19 +1996,20 @@ class SfCartesianChartState extends State } if (legendCheck) { final int index = visibleSeriesRenderers.length - 1; - final String legendItemText = + final String? legendItemText = visibleSeriesRenderers[index]._series.legendItemText; - final String legendText = _chart.legend.legendItemBuilder != null + final String? legendText = _chart.legend.legendItemBuilder != null ? visibleSeriesRenderers[index]._seriesName - : _chartSeries._chartState._chartLegend?.legendCollections != + : visibleSeriesRenderers[index]._series.isVisibleInLegend && + _chartSeries._chartState._chartLegend.legendCollections != null && - _chartSeries - ._chartState._chartLegend.legendCollections.isNotEmpty + _chartSeries._chartState._chartLegend.legendCollections! + .isNotEmpty ? _chartSeries - ._chartState._chartLegend?.legendCollections[index]?.text + ._chartState._chartLegend.legendCollections![index].text : null; - final String seriesName = visibleSeriesRenderers[index]._series.name; + final String? seriesName = visibleSeriesRenderers[index]._series.name; _chartSeries.visibleSeriesRenderers[visibleSeriesRenderers.length - 1] ._visible = _checkWithLegendToggleState( @@ -1755,26 +2021,28 @@ class SfCartesianChartState extends State seriesName ?? 'Series $index'); } - final CartesianSeriesRenderer cSeriesRenderer = _chartSeries + final CartesianSeriesRenderer? cSeriesRenderer = _chartSeries .visibleSeriesRenderers[visibleSeriesRenderers.length - 1] is CartesianSeriesRenderer ? _chartSeries .visibleSeriesRenderers[visibleSeriesRenderers.length - 1] : null; if (cSeriesRenderer?._series != null && - cSeriesRenderer._series.trendlines != null) { - Trendline trendline; + cSeriesRenderer?._series.trendlines != null) { + Trendline? trendline; TrendlineRenderer trendlineRenderer; - for (int j = 0; j < cSeriesRenderer._series.trendlines.length; j++) { - trendline = cSeriesRenderer._series.trendlines[j]; + for (int j = 0; + j < cSeriesRenderer!._series.trendlines!.length; + j++) { + trendline = cSeriesRenderer._series.trendlines![j]; trendlineRenderer = cSeriesRenderer._trendlineRenderer[j]; trendlineRenderer._visible = _checkWithTrendlineLegendToggleState( visibleSeriesRenderers.length - 1, cSeriesRenderer._series, j, trendline, - trendlineRenderer._name) && - cSeriesRenderer._visible; + trendlineRenderer._name!) && + cSeriesRenderer._visible!; } _isTrendlineToggled = false; } @@ -1791,7 +2059,7 @@ class SfCartesianChartState extends State technicalIndicatorRenderer = TechnicalIndicatorsRenderer(_chart.indicators[i]); _technicalIndicatorRenderer.add(technicalIndicatorRenderer); - if (_initialRender) { + if (_initialRender!) { technicalIndicatorRenderer._visible = _chart.indicators[i].isVisible; } else { technicalIndicatorRenderer._visible = @@ -1806,7 +2074,7 @@ class SfCartesianChartState extends State /// To check the legend toggle state bool _checkIndicatorLegendToggleState(int seriesIndex, bool seriesVisible) { - bool seriesRender; + bool? seriesRender; if (widget.legend.legendItemBuilder != null) { final List<_MeasureWidgetContext> legendToggles = _legendToggleTemplateStates; @@ -1841,7 +2109,7 @@ class SfCartesianChartState extends State int trendlineIndex, Trendline trendline, String text) { - bool seriesRender; + bool? seriesRender; if (_legendToggleStates.isNotEmpty) { for (int j = 0; j < _legendToggleStates.length; j++) { final _LegendRenderContext legendRenderContext = _legendToggleStates[j]; @@ -1860,7 +2128,7 @@ class SfCartesianChartState extends State /// To toggle series visiblity based on legend toggle states bool _checkWithLegendToggleState( int seriesIndex, ChartSeries series, String text) { - bool seriesRender; + bool? seriesRender; if (_chart.legend.legendItemBuilder != null) { final List<_MeasureWidgetContext> legendToggles = _legendToggleTemplateStates; @@ -1899,29 +2167,40 @@ class SfCartesianChartState extends State // ignore: must_be_immutable class _ContainerArea extends StatelessWidget { // ignore: prefer_const_constructors_in_immutables - _ContainerArea([this._chartState]); + _ContainerArea(this._chartState); final SfCartesianChartState _chartState; //Here, we are using get keyword inorder to get the proper & updated instance of chart widget //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. SfCartesianChart get chart => _chartState._chart; - List _chartWidgets; - RenderBox renderBox; - Offset _touchPosition; - Offset _tapDownDetails; - Offset _mousePointerDetails; - CartesianSeries _series; - XyDataSeriesRenderer _seriesRenderer; + List _chartWidgets = []; + late RenderBox renderBox; + Offset? _touchPosition; + Offset? _tapDownDetails; + Offset? _mousePointerDetails; + late CartesianSeries _series; + late XyDataSeriesRenderer _seriesRenderer; + Offset? _zoomStartPosition; + bool _enableMouseHover = + kIsWeb || Platform.isLinux || Platform.isMacOS || Platform.isWindows; @override Widget build(BuildContext context) { final bool isUserInteractionEnabled = chart.zoomPanBehavior.enableDoubleTapZooming || chart.zoomPanBehavior.enableMouseWheelZooming || - chart.zoomPanBehavior.enableSelectionZooming || + _chartState._zoomPanBehaviorRenderer._canPerformSelection || chart.zoomPanBehavior.enablePanning || chart.zoomPanBehavior.enablePinching || - chart.trackballBehavior.enable || - chart.crosshairBehavior.enable || - chart.onChartTouchInteractionMove != null; + (chart.trackballBehavior.enable && + chart.trackballBehavior.activationMode == + ActivationMode.singleTap) || + (chart.crosshairBehavior.enable && + chart.crosshairBehavior.activationMode == + ActivationMode.singleTap) || + _chartState._trackballBehaviorRenderer._isLongPressActivated || + _chartState._crosshairBehaviorRenderer._isLongPressActivated || + chart.onChartTouchInteractionMove != null || + chart.loadMoreIndicatorBuilder != null || + chart.onPlotAreaSwipe != null; return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Container( @@ -1929,7 +2208,10 @@ class _ContainerArea extends StatelessWidget { /// To get the mouse region of the chart child: MouseRegion( - onHover: (PointerEvent event) => _performMouseHover(event), + // Using the _enableMouseHover property, prevented mouse hover function in mobile platforms. The mouse hover event should not be triggered for mobile platforms and logged an issue regarding this to the Flutter team. + // Issue: https://github.com/flutter/flutter/issues/68690 + onHover: (PointerEvent event) => + _enableMouseHover ? _performMouseHover(event) : null, onExit: (PointerEvent event) => _performMouseExit(event), child: Listener( onPointerDown: (PointerDownEvent event) { @@ -1939,7 +2221,7 @@ class _ContainerArea extends StatelessWidget { touchArgs = ChartTouchInteractionArgs(); touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionDown(touchArgs); + chart.onChartTouchInteractionDown!(touchArgs); } }, onPointerMove: (PointerMoveEvent event) { @@ -1949,7 +2231,7 @@ class _ContainerArea extends StatelessWidget { touchArgs = ChartTouchInteractionArgs(); touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionMove(touchArgs); + chart.onChartTouchInteractionMove!(touchArgs); } }, onPointerUp: (PointerUpEvent event) { @@ -1961,7 +2243,7 @@ class _ContainerArea extends StatelessWidget { touchArgs = ChartTouchInteractionArgs(); touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionUp(touchArgs); + chart.onChartTouchInteractionUp!(touchArgs); } }, onPointerSignal: (PointerSignalEvent event) { @@ -2005,15 +2287,9 @@ class _ContainerArea extends StatelessWidget { onLongPressEnd: (LongPressEndDetails details) { _performLongPressEnd(); }, - // onPanUpdate: (DragUpdateDetails details) { - // _performPanUpdate(details); - // }, - // onPanEnd: (DragEndDetails details) { - // _performPanEnd(details); - // }, - // onPanDown: (DragDownDetails details) { - // _performPanDown(details); - // }, + onPanDown: (DragDownDetails details) { + _performPanDown(details); + }, onVerticalDragUpdate: isUserInteractionEnabled ? (DragUpdateDetails details) { _performPanUpdate(details); @@ -2024,11 +2300,6 @@ class _ContainerArea extends StatelessWidget { _performPanEnd(details); } : null, - onVerticalDragDown: isUserInteractionEnabled - ? (DragDownDetails details) { - _performPanDown(details); - } - : null, onHorizontalDragUpdate: isUserInteractionEnabled ? (DragUpdateDetails details) { _performPanUpdate(details); @@ -2039,11 +2310,6 @@ class _ContainerArea extends StatelessWidget { _performPanEnd(details); } : null, - onHorizontalDragDown: isUserInteractionEnabled - ? (DragDownDetails details) { - _performPanDown(details); - } - : null, child: Container( child: _initializeChart(constraints, context), height: constraints.maxHeight, @@ -2053,8 +2319,6 @@ class _ContainerArea extends StatelessWidget { }); } - Offset _zoomStartPosition; - /// To initialise chart Widget _initializeChart(BoxConstraints constraints, BuildContext context) { // chart._chartState._tooltipBehaviorRenderer = TooltipBehaviorRenderer(chart.tooltipBehavior); @@ -2075,8 +2339,8 @@ class _ContainerArea extends StatelessWidget { /// Calculate container bounds void _calculateBounds() { - _chartState._chartSeries?._processData(); - _chartState._chartAxis?._measureAxesBounds(); + _chartState._chartSeries._processData(); + _chartState._chartAxis._measureAxesBounds(); _chartState._rangeChangeBySlider = false; _chartState._rangeChangedByChart = false; } @@ -2086,7 +2350,7 @@ class _ContainerArea extends StatelessWidget { SfCartesianChartState _chartState, XyDataSeriesRenderer seriesRenderer) { if (seriesRenderer._series.trendlines != null) { TrendlineRenderer trendlineRenderer; - for (int i = 0; i < seriesRenderer._series.trendlines.length; i++) { + for (int i = 0; i < seriesRenderer._series.trendlines!.length; i++) { trendlineRenderer = seriesRenderer._trendlineRenderer[i]; if (trendlineRenderer._isNeedRender) { trendlineRenderer.calculateTrendlinePoints( @@ -2109,12 +2373,29 @@ class _ContainerArea extends StatelessWidget { _bindAxisWidgets('inside'); _renderTemplates(); _bindInteractionWidgets(constraints, context); - renderBox = context.findRenderObject(); + _bindLoadMoreIndicatorWidget(); + renderBox = context.findRenderObject()! as RenderBox; _chartState._containerArea = this; _chartState._legendRefresh = false; return Container(child: Stack(children: _chartWidgets)); } + void _bindLoadMoreIndicatorWidget() { + _chartWidgets.add(StatefulBuilder( + builder: (BuildContext context, StateSetter stateSetter) { + Widget renderWidget; + _chartState._loadMoreViewStateSetter = stateSetter; + if (_chartState._isLoadMoreIndicator) { + renderWidget = Center( + child: _chartState._chart.loadMoreIndicatorBuilder!( + context, _chartState._swipeDirection)); + } else { + renderWidget = Container(); + } + return renderWidget; + })); + } + void _bindPlotBandWidgets(bool shouldRenderAboveSeries) { _chartWidgets.add(RepaintBoundary( child: CustomPaint( @@ -2130,16 +2411,17 @@ class _ContainerArea extends StatelessWidget { for (int i = 0; i < _chartState._chartSeries.visibleSeriesRenderers.length; i++) { - _seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; + _seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i] + as XyDataSeriesRenderer; _series = _seriesRenderer._series; if (_seriesRenderer != null && - _seriesRenderer._visible && + _seriesRenderer._visible! && _series.trendlines != null) { isTrendline = true; for (int j = 0; j < _seriesRenderer._trendlineRenderer.length; j++) { final TrendlineRenderer trendlineRenderer = _seriesRenderer._trendlineRenderer[j]; - final Trendline trendline = _series.trendlines[j]; + final Trendline trendline = _series.trendlines![j]; if (trendline.animationDuration > 0 && (_chartState._oldDeviceOrientation == null || _chartState._oldDeviceOrientation == @@ -2173,7 +2455,7 @@ class _ContainerArea extends StatelessWidget { void _bindDataLabelWidgets() { _chartState._renderDataLabel = _DataLabelRenderer( cartesianChartState: _chartState, show: _chartState._animateCompleted); - _chartWidgets.add(_chartState._renderDataLabel); + _chartWidgets.add(_chartState._renderDataLabel!); } /// To render a template @@ -2181,15 +2463,15 @@ class _ContainerArea extends StatelessWidget { _chartState._annotationRegions = []; _chartState._templates = <_ChartTemplateInfo>[]; _renderDataLabelTemplates(); - if (chart.annotations != null && chart.annotations.isNotEmpty) { - for (int i = 0; i < chart.annotations.length; i++) { - final CartesianChartAnnotation annotation = chart.annotations[i]; + if (chart.annotations != null && chart.annotations!.isNotEmpty) { + for (int i = 0; i < chart.annotations!.length; i++) { + final CartesianChartAnnotation annotation = chart.annotations![i]; final _ChartLocation location = _getAnnotationLocation(annotation, _chartState); final _ChartTemplateInfo chartTemplateInfo = _ChartTemplateInfo( key: GlobalKey(), animationDuration: 200, - widget: annotation.widget, + widget: annotation.widget!, templateType: 'Annotation', needMeasure: true, pointIndex: i, @@ -2218,7 +2500,7 @@ class _ContainerArea extends StatelessWidget { /// To render data label template void _renderDataLabelTemplates() { - Widget labelWidget; + Widget? labelWidget; CartesianChartPoint point; _chartState._dataLabelTemplateRegions = []; for (int i = 0; @@ -2226,18 +2508,19 @@ class _ContainerArea extends StatelessWidget { i++) { final CartesianSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; - final XyDataSeries series = seriesRenderer._series; + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; num padding; - if (series.dataLabelSettings.isVisible && seriesRenderer._visible) { + if (series.dataLabelSettings.isVisible && seriesRenderer._visible!) { for (int j = 0; j < seriesRenderer._dataPoints.length; j++) { point = seriesRenderer._dataPoints[j]; if (point.isVisible && !point.isGap) { labelWidget = (series.dataLabelSettings.builder != null) - ? series.dataLabelSettings.builder( - series.dataSource[point.overallDataPointIndex], + ? series.dataLabelSettings.builder!( + series.dataSource[point.overallDataPointIndex!], point, series, - point.overallDataPointIndex, + point.overallDataPointIndex!, i) : null; if (labelWidget != null) { @@ -2251,7 +2534,7 @@ class _ContainerArea extends StatelessWidget { seriesType.contains('hiloopenclose')) ? [point.low, point.high, point.open, point.close] : seriesType.contains('box') - ? [point.minimum] + ? [point.minimum!] : [point.y]; for (int k = 0; k < dataLabelTemplateYValues.length; k++) { @@ -2263,8 +2546,8 @@ class _ContainerArea extends StatelessWidget { final _ChartLocation location = _calculatePoint( point.xValue, dataLabelTemplateYValues[k], - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, series, _chartState._chartAxis._axisClipRect); @@ -2297,11 +2580,24 @@ class _ContainerArea extends StatelessWidget { for (int i = 0; i < _chartState._chartSeries.visibleSeriesRenderers.length; i++) { - _seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; + _seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i] + as XyDataSeriesRenderer; _seriesRenderer._animationCompleted = false; _series = _seriesRenderer._series; final String _seriesType = _seriesRenderer._seriesType; - if (_seriesRenderer != null && _seriesRenderer._visible) { + if (_seriesRenderer._isIndicator) { + _seriesRenderer._repaintNotifier = ValueNotifier(0); + if (_seriesRenderer is XyDataSeriesRenderer) { + _seriesRenderer._animationController = + AnimationController(vsync: _chartState) + ..addListener(_seriesRenderer._repaintSeriesElement); + _chartState._controllerList[_seriesRenderer._animationController] = + _seriesRenderer._repaintSeriesElement; + _seriesRenderer._animationController + .addStatusListener(_seriesRenderer._animationStatusListener); + } + } + if (_seriesRenderer != null && _seriesRenderer._visible!) { _calculateTrendlineRegion(_chartState, _seriesRenderer); _series.selectionBehavior._chartState = _chartState; _series.selectionSettings._chartState = _chartState; @@ -2311,74 +2607,97 @@ class _ContainerArea extends StatelessWidget { final dynamic selectionBehavior = _seriesRenderer._selectionBehavior; _seriesRenderer._selectionBehaviorRenderer = SelectionBehaviorRenderer(selectionBehavior, chart, _chartState); - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = _seriesRenderer._selectionBehaviorRenderer; if (selectionBehaviorRenderer != null) { selectionBehaviorRenderer._selectionRenderer ??= _SelectionRenderer(); - selectionBehaviorRenderer._selectionRenderer.chart = chart; - selectionBehaviorRenderer._selectionRenderer._chartState = + selectionBehaviorRenderer._selectionRenderer!.chart = chart; + selectionBehaviorRenderer._selectionRenderer!._chartState = _chartState; - selectionBehaviorRenderer._selectionRenderer.seriesRenderer = + selectionBehaviorRenderer._selectionRenderer!.seriesRenderer = _seriesRenderer; _series = _seriesRenderer._series; if (selectionBehavior.selectionController != null) { selectionBehaviorRenderer._selectRange(); } - selectionBehaviorRenderer._selectionRenderer.selectedSegments = + selectionBehaviorRenderer._selectionRenderer!.selectedSegments = _chartState._selectedSegments; - selectionBehaviorRenderer._selectionRenderer.unselectedSegments = + selectionBehaviorRenderer._selectionRenderer!.unselectedSegments = _chartState._unselectedSegments; //To determine whether initialSelectedDataIndexes collection is updated dynamically bool isSelecetedIndexesUpdated = false; if (_series.initialSelectedDataIndexes != null && - _series.initialSelectedDataIndexes.isNotEmpty && + _series.initialSelectedDataIndexes!.isNotEmpty && _seriesRenderer._oldSelectedIndexes != null && - _seriesRenderer._oldSelectedIndexes.isNotEmpty && - _seriesRenderer._oldSelectedIndexes.length == - _series.initialSelectedDataIndexes.length) { - for (final int index in _series.initialSelectedDataIndexes) { + _seriesRenderer._oldSelectedIndexes!.isNotEmpty && + _seriesRenderer._oldSelectedIndexes!.length == + _series.initialSelectedDataIndexes!.length) { + for (final int index in _series.initialSelectedDataIndexes!) { isSelecetedIndexesUpdated = - !_seriesRenderer._oldSelectedIndexes.contains(index); + !_seriesRenderer._oldSelectedIndexes!.contains(index); if (isSelecetedIndexesUpdated) { break; } } } else { isSelecetedIndexesUpdated = - _series.initialSelectedDataIndexes.isNotEmpty; + _series.initialSelectedDataIndexes!.isNotEmpty; + } + int totalSelectedSegment = 0; + int? selectedSeriesIndex; + if (selectionBehavior.enable && + _chartState._selectedSegments.isNotEmpty && + _chartState._unselectedSegments.isNotEmpty) { + for (int j = 0; j < _chartState._selectedSegments.length; j++) { + if (_chartState._selectedSegments[j]._seriesIndex == i) { + totalSelectedSegment += 1; + selectedSeriesIndex = + _chartState._selectedSegments[j]._seriesIndex; + } + } + for (int k = 0; k < _chartState._unselectedSegments.length; k++) { + if (_chartState._unselectedSegments[k]._seriesIndex == i) { + totalSelectedSegment += 1; + } + } } if (_chartState._isRangeSelectionSlider == false && selectionBehavior.enable && - isSelecetedIndexesUpdated) { - for (int j = 0; j < _seriesRenderer._dataPoints.length; j++) { + (isSelecetedIndexesUpdated || + (!_chartState._initialRender! && + (totalSelectedSegment != 0 + ? (totalSelectedSegment < + _chartState + ._seriesRenderers[i]._dataPoints.length) + : false)))) { + int segmentLength = _seriesRenderer._dataPoints.length; + + if (_checkSeriesType(_seriesRenderer._seriesType) || + _seriesRenderer._seriesType.contains('boxandwhisker')) { + segmentLength = _seriesRenderer._dataPoints.length - 1; + } + + for (int j = 0; j < segmentLength; j++) { final ChartSegment segment = ColumnSegment(); segment.currentSegmentIndex = j; segment._seriesIndex = i; segment._currentPoint = _seriesRenderer._dataPoints[j]; - if (_series.initialSelectedDataIndexes - .contains(segment.currentSegmentIndex)) { - selectionBehaviorRenderer._selectionRenderer.selectedSegments + if ((_series.initialSelectedDataIndexes! + .contains(segment.currentSegmentIndex) && + isSelecetedIndexesUpdated) || + chart.selectionType == SelectionType.series && + selectedSeriesIndex == i) { + selectionBehaviorRenderer._selectionRenderer!.selectedSegments .add(segment); } else { - selectionBehaviorRenderer._selectionRenderer.unselectedSegments + selectionBehaviorRenderer + ._selectionRenderer!.unselectedSegments! .add(segment); } } _seriesRenderer._oldSelectedIndexes = [] //ignore: prefer_spread_collections - ..addAll(_series.initialSelectedDataIndexes); - } - } - if (_seriesRenderer._isIndicator) { - _seriesRenderer._repaintNotifier = ValueNotifier(0); - if (_seriesRenderer is XyDataSeriesRenderer) { - _seriesRenderer._animationController = - AnimationController(vsync: _chartState) - ..addListener(_seriesRenderer._repaintSeriesElement); - _chartState._controllerList[_seriesRenderer._animationController] = - _seriesRenderer._repaintSeriesElement; - _seriesRenderer._animationController - .addStatusListener(_seriesRenderer._animationStatusListener); + ..addAll(_series.initialSelectedDataIndexes!); } } @@ -2388,7 +2707,7 @@ class _ContainerArea extends StatelessWidget { _chartState._legendRefresh || _chartState._oldDeviceOrientation == _chartState._deviceOrientation) && - (_chartState._initialRender || + (_chartState._initialRender! || _chartState._legendRefresh || ((_seriesType == 'column' || _seriesType == 'bar') && _chartState._legendToggling) || @@ -2441,7 +2760,7 @@ class _ContainerArea extends StatelessWidget { if (_chartState._chartAxis._axisRenderersCollection != null && _chartState._chartAxis._axisRenderersCollection.isNotEmpty && _chartState._chartAxis._axisRenderersCollection.length > 1) { - final Widget axisWidget = _CartesianAxisRenderer( + final _CartesianAxisRenderer axisWidget = _CartesianAxisRenderer( chartState: _chartState, renderType: renderType); if (renderType == 'outside') { _chartState._renderOutsideAxis = axisWidget; @@ -2453,9 +2772,9 @@ class _ContainerArea extends StatelessWidget { } /// To find a series on selection event - CartesianSeriesRenderer _findSeries(Offset position) { - CartesianSeriesRenderer seriesRenderer; - SelectionBehaviorRenderer selectionBehaviorRenderer; + CartesianSeriesRenderer? _findSeries(Offset position) { + CartesianSeriesRenderer? seriesRenderer; + SelectionBehaviorRenderer? selectionBehaviorRenderer; outerLoop: for (int i = _chartState._chartSeries.visibleSeriesRenderers.length - 1; i >= 0; @@ -2475,7 +2794,7 @@ class _ContainerArea extends StatelessWidget { _seriesType == 'waterfall') { for (int j = 0; j < seriesRenderer._dataPoints.length; j++) { if (seriesRenderer._dataPoints[j].region != null && - seriesRenderer._dataPoints[j].region.contains(position)) { + seriesRenderer._dataPoints[j].region!.contains(position)) { seriesRenderer._isOuterRegion = false; break outerLoop; } else { @@ -2490,8 +2809,8 @@ class _ContainerArea extends StatelessWidget { k >= 0; k--) { isSelect = - seriesRenderer._isSelectionEnable && seriesRenderer._visible - ? selectionBehaviorRenderer._selectionRenderer + seriesRenderer._isSelectionEnable && seriesRenderer._visible! + ? selectionBehaviorRenderer!._selectionRenderer! ._isSeriesContainsPoint( _chartState._chartSeries.visibleSeriesRenderers[i], position) @@ -2510,7 +2829,7 @@ class _ContainerArea extends StatelessWidget { _chartState._tooltipBehaviorRenderer._isHovering = false; _tapDownDetails = event.position; if (chart.zoomPanBehavior.enablePinching == true) { - ZoomPanArgs zoomStartArgs; + ZoomPanArgs? zoomStartArgs; if (_chartState._touchStartPositions.length < 2) { _chartState._touchStartPositions.add(event); } @@ -2522,7 +2841,7 @@ class _ContainerArea extends StatelessWidget { _chartState._chartAxis._axisRenderersCollection[axisIndex]; if (chart.onZoomStart != null) { zoomStartArgs = _bindZoomEvent( - chart, axisRenderer, zoomStartArgs, chart.onZoomStart); + chart, axisRenderer, zoomStartArgs, chart.onZoomStart!); axisRenderer._zoomFactor = zoomStartArgs.currentZoomFactor; axisRenderer._zoomPosition = zoomStartArgs.currentZoomPosition; } @@ -2543,13 +2862,13 @@ class _ContainerArea extends StatelessWidget { chart.selectionGesture == ActivationMode.singleTap && _chartState._zoomPanBehaviorRenderer._isPinching != true) { final CartesianSeriesRenderer selectionSeriesRenderer = - _findSeries(position); + _findSeries(position)!; final SelectionBehaviorRenderer selectionBehaviorRenderer = - selectionSeriesRenderer._selectionBehaviorRenderer; + selectionSeriesRenderer._selectionBehaviorRenderer!; if (selectionSeriesRenderer._isSelectionEnable && selectionBehaviorRenderer._selectionRenderer != null && !selectionSeriesRenderer._isOuterRegion) { - selectionBehaviorRenderer._selectionRenderer.seriesRenderer = + selectionBehaviorRenderer._selectionRenderer!.seriesRenderer = selectionSeriesRenderer; selectionBehaviorRenderer.onTouchDown(position.dx, position.dy); } @@ -2608,7 +2927,7 @@ class _ContainerArea extends StatelessWidget { void _performPointerUp(PointerUpEvent event) { if (_chartState._touchStartPositions.length == 2 && _chartState._touchMovePositions.length == 2 && - _chartState._zoomPanBehaviorRenderer._isPinching) { + _chartState._zoomPanBehaviorRenderer._isPinching == true) { _calculatePinchZoomingArgs(); } _chartState._touchStartPositions = []; @@ -2648,7 +2967,7 @@ class _ContainerArea extends StatelessWidget { !chart.crosshairBehavior.shouldAlwaysShow))) { _chartState._crosshairBehaviorRenderer .onTouchUp(position.dx, position.dy); - _chartState._crosshairBehaviorRenderer._isLongPressActivated = true; + _chartState._crosshairBehaviorRenderer._isLongPressActivated = false; } if (chart.tooltipBehavior.enable && chart.tooltipBehavior.activationMode == ActivationMode.singleTap || @@ -2678,7 +2997,7 @@ class _ContainerArea extends StatelessWidget { /// To calculate the arguments of pinch zooming event void _calculatePinchZoomingArgs() { - ZoomPanArgs zoomEndArgs, zoomResetArgs; + ZoomPanArgs? zoomEndArgs, zoomResetArgs; bool resetFlag = false; int axisIndex; for (axisIndex = 0; @@ -2688,7 +3007,7 @@ class _ContainerArea extends StatelessWidget { _chartState._chartAxis._axisRenderersCollection[axisIndex]; if (chart.onZoomEnd != null) { zoomEndArgs = - _bindZoomEvent(chart, axisRenderer, zoomEndArgs, chart.onZoomEnd); + _bindZoomEvent(chart, axisRenderer, zoomEndArgs, chart.onZoomEnd!); axisRenderer._zoomFactor = zoomEndArgs.currentZoomFactor; axisRenderer._zoomPosition = zoomEndArgs.currentZoomPosition; } @@ -2712,14 +3031,14 @@ class _ContainerArea extends StatelessWidget { index++) { final ChartAxisRenderer axisRenderer = _chartState._chartAxis._axisRenderersCollection[index]; - _bindZoomEvent(chart, axisRenderer, zoomResetArgs, chart.onZoomReset); + _bindZoomEvent(chart, axisRenderer, zoomResetArgs, chart.onZoomReset!); } } } /// To perform long press move update void _performLongPressMoveUpdate(LongPressMoveUpdateDetails details) { - final Offset position = renderBox.globalToLocal(details.globalPosition); + final Offset? position = renderBox.globalToLocal(details.globalPosition); if (_chartState._zoomPanBehaviorRenderer._isPinching != true) { if (chart.zoomPanBehavior.enableSelectionZooming && position != null && @@ -2728,8 +3047,8 @@ class _ContainerArea extends StatelessWidget { _chartState._zoomPanBehaviorRenderer.onDrawSelectionZoomRect( position.dx, position.dy, - _zoomStartPosition.dx, - _zoomStartPosition.dy); + _zoomStartPosition!.dx, + _zoomStartPosition!.dy); } } if (chart.trackballBehavior != null && @@ -2793,6 +3112,7 @@ class _ContainerArea extends StatelessWidget { /// To perform pan down void _performPanDown(DragDownDetails details) { + _chartState._startOffset = renderBox.globalToLocal(details.globalPosition); if (_chartState._zoomPanBehaviorRenderer._isPinching != true) { _zoomStartPosition = renderBox.globalToLocal(details.globalPosition); if (chart.zoomPanBehavior.enablePanning == true) { @@ -2804,9 +3124,9 @@ class _ContainerArea extends StatelessWidget { /// To perform long press on chart void _performLongPress() { - Offset position; + Offset? position; if (_tapDownDetails != null) { - position = renderBox.globalToLocal(_tapDownDetails); + position = renderBox.globalToLocal(_tapDownDetails!); if (chart.tooltipBehavior.enable && chart.tooltipBehavior.activationMode == ActivationMode.longPress || @@ -2821,12 +3141,13 @@ class _ContainerArea extends StatelessWidget { } } if (_chartState._chartSeries.visibleSeriesRenderers != null && + position != null && chart.selectionGesture == ActivationMode.longPress) { final CartesianSeriesRenderer selectionSeriesRenderer = - _findSeries(position); + _findSeries(position)!; final SelectionBehaviorRenderer selectionBehaviorRenderer = - selectionSeriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer.seriesRenderer = + selectionSeriesRenderer._selectionBehaviorRenderer!; + selectionBehaviorRenderer._selectionRenderer!.seriesRenderer = selectionSeriesRenderer; selectionBehaviorRenderer.onLongPress(position.dx, position.dy); } @@ -2835,6 +3156,7 @@ class _ContainerArea extends StatelessWidget { chart.trackballBehavior.enable == true && chart.trackballBehavior.activationMode == ActivationMode.longPress) && + position != null && _chartState._zoomPanBehaviorRenderer._isPinching != true) { _chartState._trackballBehaviorRenderer._isLongPressActivated = true; if (chart.trackballBehavior.builder != null) { @@ -2860,7 +3182,7 @@ class _ContainerArea extends StatelessWidget { /// Method for double tap void _performDoubleTap() { if (_tapDownDetails != null) { - final Offset position = renderBox.globalToLocal(_tapDownDetails); + final Offset position = renderBox.globalToLocal(_tapDownDetails!); if (chart.trackballBehavior != null && chart.trackballBehavior.enable && chart.trackballBehavior.activationMode == ActivationMode.doubleTap) { @@ -2872,10 +3194,10 @@ class _ContainerArea extends StatelessWidget { .onDoubleTap(position.dx, position.dy); } _chartState._enableDoubleTap = true; - + _chartState._isTouchUp = true; _chartState._trackballBehaviorRenderer .onTouchUp(position.dx, position.dy); - + _chartState._isTouchUp = false; _chartState._enableDoubleTap = false; } if (chart.crosshairBehavior != null && @@ -2884,8 +3206,10 @@ class _ContainerArea extends StatelessWidget { _chartState._crosshairBehaviorRenderer .onDoubleTap(position.dx, position.dy); _chartState._enableDoubleTap = true; + _chartState._isTouchUp = true; _chartState._crosshairBehaviorRenderer .onTouchUp(position.dx, position.dy); + _chartState._isTouchUp = false; _chartState._enableDoubleTap = false; } if (chart.tooltipBehavior.enable && @@ -2903,18 +3227,18 @@ class _ContainerArea extends StatelessWidget { if (_chartState._chartSeries.visibleSeriesRenderers != null && chart.selectionGesture == ActivationMode.doubleTap) { final CartesianSeriesRenderer selectionSeriesRenderer = - _findSeries(position); + _findSeries(position)!; final SelectionBehaviorRenderer selectionBehaviorRenderer = - selectionSeriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer.seriesRenderer = + selectionSeriesRenderer._selectionBehaviorRenderer!; + selectionBehaviorRenderer._selectionRenderer!.seriesRenderer = selectionSeriesRenderer; selectionBehaviorRenderer.onDoubleTap(position.dx, position.dy); } } if (chart.zoomPanBehavior.enableDoubleTapZooming == true) { - final Offset doubleTapPosition = _touchPosition; - final Offset position = doubleTapPosition; + final Offset? doubleTapPosition = _touchPosition; + final Offset? position = doubleTapPosition; if (position != null) { _chartState._zoomPanBehaviorRenderer.onDoubleTap(position.dx, position.dy, _chartState._zoomPanBehaviorRenderer._zoomFactor); @@ -2924,12 +3248,15 @@ class _ContainerArea extends StatelessWidget { /// Update the details for pan void _performPanUpdate(DragUpdateDetails details) { - Offset position; + Offset? position; + _chartState._currentPosition = + renderBox.globalToLocal(details.globalPosition); if (_chartState._zoomPanBehaviorRenderer._isPinching != true) { position = renderBox.globalToLocal(details.globalPosition); if (_chartState._zoomPanBehaviorRenderer._isPanning == true && chart.zoomPanBehavior.enablePanning && - _chartState._zoomPanBehaviorRenderer._previousMovedPosition != null) { + _chartState._zoomPanBehaviorRenderer._previousMovedPosition != null && + !_chartState._isLoadMoreIndicator) { _chartState._zoomPanBehaviorRenderer.onPan(position.dx, position.dy); } _chartState._zoomPanBehaviorRenderer._previousMovedPosition = position; @@ -2990,16 +3317,121 @@ class _ContainerArea extends StatelessWidget { !chart.trackballBehavior.shouldAlwaysShow && chart.trackballBehavior.activationMode != ActivationMode.doubleTap && _touchPosition != null) { + _chartState._isTouchUp = true; _chartState._trackballBehaviorRenderer - .onTouchUp(_touchPosition.dx, _touchPosition.dy); + .onTouchUp(_touchPosition!.dx, _touchPosition!.dy); + _chartState._isTouchUp = false; _chartState._trackballBehaviorRenderer._isLongPressActivated = false; } if (chart.crosshairBehavior.enable && !chart.crosshairBehavior.shouldAlwaysShow && + _touchPosition != null && chart.crosshairBehavior.activationMode != ActivationMode.doubleTap) { + _chartState._isTouchUp = true; _chartState._crosshairBehaviorRenderer - .onTouchUp(_touchPosition?.dx, _touchPosition?.dy); + .onTouchUp(_touchPosition!.dx, _touchPosition!.dy); + _chartState._isTouchUp = false; + _chartState._crosshairBehaviorRenderer._isLongPressActivated = false; + } + + /// Pagination/Swiping feature + if (chart.onPlotAreaSwipe != null && + _chartState._zoomedState != true && + _chartState._startOffset != null && + _chartState._currentPosition != null && + _chartState._chartAxis._axisClipRect + .contains(_chartState._startOffset!) && + _chartState._chartAxis._axisClipRect + .contains(_chartState._currentPosition!)) { + //swipe configuration options + const double swipeMaxDistanceThreshold = 50.0; + final double swipeMinDisplacement = (_chartState._requireInvertedAxis + ? _chartState._chartAxis._axisClipRect.height + : _chartState._chartAxis._axisClipRect.width) * + 0.1; + const double swipeMinVelocity = 240.0; + ChartSwipeDirection swipeDirection; + + final double dx = + (_chartState._currentPosition!.dx - _chartState._startOffset!.dx) + .abs(); + final double dy = + (_chartState._currentPosition!.dy - _chartState._startOffset!.dy) + .abs(); + final double velocity = details.primaryVelocity!; + + if (_chartState._requireInvertedAxis && + dx <= swipeMaxDistanceThreshold && + dy >= swipeMinDisplacement && + velocity.abs() >= swipeMinVelocity) { + ///vertical + swipeDirection = + velocity < 0 ? ChartSwipeDirection.start : ChartSwipeDirection.end; + chart.onPlotAreaSwipe!(swipeDirection); + } else if (!_chartState._requireInvertedAxis && + dx >= swipeMinDisplacement && + dy <= swipeMaxDistanceThreshold && + velocity.abs() >= swipeMinVelocity) { + ///horizontal + swipeDirection = + velocity > 0 ? ChartSwipeDirection.start : ChartSwipeDirection.end; + chart.onPlotAreaSwipe!(swipeDirection); + } + } + + ///Load More feature + if (chart.loadMoreIndicatorBuilder != null && + _chartState._startOffset != null && + _chartState._currentPosition != null) { + final bool verticallyDragging = + ((_chartState._currentPosition!.dy - _chartState._startOffset!.dy) + .abs() > + (_chartState._currentPosition!.dx - _chartState._startOffset!.dx) + .abs()); + if ((!verticallyDragging && !_chartState._requireInvertedAxis) || + (verticallyDragging && _chartState._requireInvertedAxis)) { + bool loadMore = false; + final ChartSwipeDirection direction = _chartState._requireInvertedAxis + ? (_chartState._currentPosition!.dy > _chartState._startOffset!.dy + ? ChartSwipeDirection.end + : ChartSwipeDirection.start) + : (_chartState._currentPosition!.dx > _chartState._startOffset!.dx + ? ChartSwipeDirection.start + : ChartSwipeDirection.end); + for (int axisIndex = 0; + axisIndex < _chartState._chartAxis._axisRenderersCollection.length; + axisIndex++) { + final ChartAxisRenderer axisRenderer = + _chartState._chartAxis._axisRenderersCollection[axisIndex]; + if (((!verticallyDragging && + axisRenderer._orientation == + AxisOrientation.horizontal) || + (verticallyDragging && + axisRenderer._orientation == AxisOrientation.vertical)) && + axisRenderer._actualRange != null && + ((axisRenderer._actualRange!.minimum.round() == + axisRenderer._visibleRange!.minimum.round() && + direction == ChartSwipeDirection.start) || + (axisRenderer._actualRange!.maximum.round() == + axisRenderer._visibleRange!.maximum.round() && + direction == ChartSwipeDirection.end))) { + loadMore = true; + break; + } + } + + if (loadMore && !_chartState._isLoadMoreIndicator) { + _chartState._isLoadMoreIndicator = true; + _chartState._loadMoreViewStateSetter(() { + _chartState._swipeDirection = direction; + }); + } else { + _chartState._isLoadMoreIndicator = false; + } + } } + _chartState._startOffset = null; + _chartState._currentPosition = null; } /// To perform mouse hover event @@ -3064,7 +3496,7 @@ class _ContainerArea extends StatelessWidget { trackballBehavior: chart.trackballBehavior, chartState: _chartState); userInteractionWidgets - .add(_chartState._trackballBehaviorRenderer._trackballTemplate); + .add(_chartState._trackballBehaviorRenderer._trackballTemplate!); } else { trackballPainter = _TrackballPainter( chartState: _chartState, @@ -3090,23 +3522,35 @@ class _ContainerArea extends StatelessWidget { decoration: const BoxDecoration(color: Colors.transparent), child: CustomPaint(painter: crosshairPainter))); } + final tooltip = chart.tooltipBehavior; if (chart.tooltipBehavior.enable || _shouldShowAxisTooltip(_chartState)) { - if (chart.tooltipBehavior.builder != null) { - _chartState._tooltipBehaviorRenderer._tooltipTemplate = - _TooltipTemplate( - show: false, - clipRect: _chartState._chartAxis._axisClipRect, - duration: chart.tooltipBehavior.duration, - tooltipBehavior: chart.tooltipBehavior, - chartState: _chartState); - userInteractionWidgets - .add(_chartState._tooltipBehaviorRenderer._tooltipTemplate); - } else { - _chartState._tooltipBehaviorRenderer._chartTooltip = - _ChartTooltipRenderer(chartState: _chartState); - userInteractionWidgets - .add(_chartState._tooltipBehaviorRenderer._chartTooltip); - } + _chartState._tooltipBehaviorRenderer._prevTooltipValue = + _chartState._tooltipBehaviorRenderer._currentTooltipValue = null; + _chartState._tooltipBehaviorRenderer._chartTooltip = SfTooltip( + color: tooltip.color ?? _chartState._chartTheme.tooltipColor, + key: GlobalKey(), + textStyle: tooltip.textStyle, + animationDuration: tooltip.animationDuration, + enable: tooltip.enable, + opacity: tooltip.opacity, + borderColor: tooltip.borderColor, + borderWidth: tooltip.borderWidth, + duration: tooltip.duration, + shouldAlwaysShow: tooltip.shouldAlwaysShow, + elevation: tooltip.elevation, + canShowMarker: tooltip.canShowMarker, + textAlignment: tooltip.textAlignment, + decimalPlaces: tooltip.decimalPlaces, + labelColor: tooltip.textStyle.color ?? + _chartState._chartTheme.tooltipLabelColor, + header: tooltip.header, + format: tooltip.format, + builder: tooltip.builder, + shadowColor: tooltip.shadowColor, + onTooltipRender: chart.onTooltipRender != null + ? _chartState._tooltipBehaviorRenderer._tooltipRenderingEvent + : null); + _chartWidgets.add(_chartState._tooltipBehaviorRenderer._chartTooltip!); } final Widget uiWidget = IgnorePointer( ignoring: (chart.annotations != null), @@ -3122,7 +3566,7 @@ class _ContainerArea extends StatelessWidget { final CartesianSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; final String _seriesType = seriesRenderer._seriesType; - num pointIndex; + int? pointIndex; final double padding = (_seriesType == 'bubble') || (_seriesType == 'scatter') || (_seriesType == 'bar') || @@ -3135,7 +3579,7 @@ class _ContainerArea extends StatelessWidget { : 15; /// regional padding to detect smooth touch - seriesRenderer._regionalData + seriesRenderer._regionalData! .forEach((dynamic regionRect, dynamic values) { final Rect region = regionRect[0]; final double left = region.left - padding; @@ -3152,11 +3596,11 @@ class _ContainerArea extends StatelessWidget { PointTapArgs pointTapArgs; pointTapArgs = PointTapArgs( i, - pointIndex, + pointIndex!, seriesRenderer._dataPoints, seriesRenderer - ._visibleDataPoints[pointIndex].overallDataPointIndex); - chart.onPointTapped(pointTapArgs); + ._visibleDataPoints![pointIndex!].overallDataPointIndex); + chart.onPointTapped!(pointTapArgs); } } } @@ -3172,14 +3616,14 @@ class _ContainerArea extends StatelessWidget { if (_chartState ._chartAxis._axisRenderersCollection[i]._axis.isVisible && labels[k]._labelRegion != null && - labels[k]._labelRegion.contains(position)) { + labels[k]._labelRegion!.contains(position)) { AxisLabelTapArgs labelArgs; labelArgs = AxisLabelTapArgs( _chartState._chartAxis._axisRenderersCollection[i]._axis, - _chartState._chartAxis._axisRenderersCollection[i]._name); + _chartState._chartAxis._axisRenderersCollection[i]._name!); labelArgs.text = labels[k].text; labelArgs.value = labels[k].value; - chart.onAxisLabelTapped(labelArgs); + chart.onAxisLabelTapped!(labelArgs); } } } @@ -3188,7 +3632,7 @@ class _ContainerArea extends StatelessWidget { /// Getter method of the series painter CustomPainter _getSeriesPainter(int value, AnimationController controller, CartesianSeriesRenderer seriesRenderer) { - CustomPainter customPainter; + CustomPainter? customPainter; final _PainterKey painterKey = _PainterKey( index: value, name: 'series $value', isRenderCompleted: false); _chartState._painterKeys.add(painterKey); @@ -3196,7 +3640,7 @@ class _ContainerArea extends StatelessWidget { case 'line': customPainter = _LineChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as LineSeriesRenderer, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty : (_chartState._legendToggling @@ -3209,7 +3653,7 @@ class _ContainerArea extends StatelessWidget { case 'spline': customPainter = _SplineChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as SplineSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3222,7 +3666,7 @@ class _ContainerArea extends StatelessWidget { case 'column': customPainter = _ColumnChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as ColumnSeriesRenderer, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty : true, @@ -3234,7 +3678,7 @@ class _ContainerArea extends StatelessWidget { case 'scatter': customPainter = _ScatterChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as ScatterSeriesRenderer, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty : (_chartState._legendToggling @@ -3247,7 +3691,7 @@ class _ContainerArea extends StatelessWidget { case 'stepline': customPainter = _StepLineChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as StepLineSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3260,7 +3704,7 @@ class _ContainerArea extends StatelessWidget { case 'area': customPainter = _AreaChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as AreaSeriesRenderer, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty : (_chartState._legendToggling @@ -3273,7 +3717,7 @@ class _ContainerArea extends StatelessWidget { case 'bubble': customPainter = _BubbleChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as BubbleSeriesRenderer, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty : (_chartState._legendToggling @@ -3286,7 +3730,7 @@ class _ContainerArea extends StatelessWidget { case 'bar': customPainter = _BarChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as BarSeriesRenderer, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty : true, @@ -3297,7 +3741,7 @@ class _ContainerArea extends StatelessWidget { case 'fastline': customPainter = _FastLineChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as FastLineSeriesRenderer, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty : (_chartState._legendToggling @@ -3310,7 +3754,7 @@ class _ContainerArea extends StatelessWidget { case 'rangecolumn': customPainter = _RangeColumnChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as RangeColumnSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3323,7 +3767,7 @@ class _ContainerArea extends StatelessWidget { case 'rangearea': customPainter = _RangeAreaChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as RangeAreaSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3336,7 +3780,7 @@ class _ContainerArea extends StatelessWidget { case 'steparea': customPainter = _StepAreaChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as StepAreaSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3349,7 +3793,7 @@ class _ContainerArea extends StatelessWidget { case 'splinearea': customPainter = _SplineAreaChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as SplineAreaSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3362,7 +3806,7 @@ class _ContainerArea extends StatelessWidget { case 'splinerangearea': customPainter = _SplineRangeAreaChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as SplineRangeAreaSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3375,7 +3819,7 @@ class _ContainerArea extends StatelessWidget { case 'stackedarea': customPainter = _StackedAreaChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as StackedAreaSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3388,7 +3832,7 @@ class _ContainerArea extends StatelessWidget { case 'stackedbar': customPainter = _StackedBarChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as StackedBarSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3401,7 +3845,7 @@ class _ContainerArea extends StatelessWidget { case 'stackedcolumn': customPainter = _StackedColummnChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as StackedColumnSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3414,7 +3858,7 @@ class _ContainerArea extends StatelessWidget { case 'stackedline': customPainter = _StackedLineChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as StackedLineSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3427,7 +3871,7 @@ class _ContainerArea extends StatelessWidget { case 'stackedarea100': customPainter = _StackedArea100ChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as StackedArea100SeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3440,7 +3884,7 @@ class _ContainerArea extends StatelessWidget { case 'stackedbar100': customPainter = _StackedBar100ChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as StackedBar100SeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3453,7 +3897,7 @@ class _ContainerArea extends StatelessWidget { case 'stackedcolumn100': customPainter = _StackedColumn100ChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as StackedColumn100SeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3466,7 +3910,7 @@ class _ContainerArea extends StatelessWidget { case 'stackedline100': customPainter = _StackedLine100ChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as StackedLine100SeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3479,7 +3923,7 @@ class _ContainerArea extends StatelessWidget { case 'hilo': customPainter = _HiloPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as HiloSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3493,7 +3937,7 @@ class _ContainerArea extends StatelessWidget { case 'hiloopenclose': customPainter = _HiloOpenClosePainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as HiloOpenCloseSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3506,7 +3950,7 @@ class _ContainerArea extends StatelessWidget { case 'candle': customPainter = _CandlePainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as CandleSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3519,7 +3963,8 @@ class _ContainerArea extends StatelessWidget { case 'histogram': customPainter = _HistogramChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as HistogramSeriesRenderer, + chartSeries: _chartState._chartSeries, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3532,7 +3977,7 @@ class _ContainerArea extends StatelessWidget { case 'boxandwhisker': customPainter = _BoxAndWhiskerPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as BoxAndWhiskerSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3545,7 +3990,7 @@ class _ContainerArea extends StatelessWidget { case 'waterfall': customPainter = _WaterfallChartPainter( chartState: _chartState, - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRenderer as WaterfallSeriesRenderer, painterKey: painterKey, isRepaint: _chartState._zoomedState != null ? _chartState._zoomedAxisRendererStates.isNotEmpty @@ -3554,7 +3999,8 @@ class _ContainerArea extends StatelessWidget { : seriesRenderer._needsRepaint), animationController: controller, notifier: seriesRenderer._repaintNotifier); + break; } - return customPainter; + return customPainter!; } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart index 34f0e4bf4..239c9fa08 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart @@ -6,7 +6,7 @@ class _ChartSeries { //Here, we are using get keyword inorder to get the proper & updated instance of chart widget //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. SfCartesianChart get chart => _chartState._chart; - bool isStacked100; + bool isStacked100 = false; int paletteIndex = 0; num sumOfYvalues = 0; List yValues = []; @@ -14,7 +14,10 @@ class _ChartSeries { /// Contains the visible series for chart List visibleSeriesRenderers = []; - List<_ClusterStackedItemInfo> clusterStackedItemInfo; + List<_ClusterStackedItemInfo> clusterStackedItemInfo = + <_ClusterStackedItemInfo>[]; + + bool _needAxisRangeAnimation = false; /// To get data and process data for rendering chart void _processData() { @@ -26,24 +29,42 @@ class _ChartSeries { if (chart.indicators.isNotEmpty) { _populateDataPoints(seriesRendererList); _calculateIndicators(); - _chartState._chartAxis?._calculateVisibleAxes(); + _chartState._chartAxis._calculateVisibleAxes(); _findMinMax(seriesRendererList); _renderTrendline(); } else { - _chartState._chartAxis?._calculateVisibleAxes(); + _chartState._chartAxis._calculateVisibleAxes(); _populateDataPoints(seriesRendererList); } _calculateStackedValues(_findSeriesCollection(_chartState)); _renderTrendline(); } + ///check whether axis animation applicable or not + bool _needAxisAnimation(CartesianSeriesRenderer seriesRenderer, + CartesianSeriesRenderer oldSeriesRenderer) { + final dynamic oldAxis = oldSeriesRenderer._xAxisRenderer!._axis; + final dynamic axis = seriesRenderer._xAxisRenderer!._axis; + final bool needAnimation = seriesRenderer._series.animationDuration > 0 && + seriesRenderer._yAxisRenderer!.runtimeType == + oldSeriesRenderer._yAxisRenderer!.runtimeType && + seriesRenderer._xAxisRenderer!.runtimeType == + oldSeriesRenderer._xAxisRenderer!.runtimeType && + ((oldAxis.visibleMinimum != null && + oldAxis.visibleMinimum != axis.visibleMinimum) || + (oldAxis.visibleMaximum != null && + oldAxis.visibleMaximum != axis.visibleMaximum)); + _needAxisRangeAnimation = needAnimation; + return needAnimation; + } + /// Find the data points for each series void _populateDataPoints(List seriesRendererList) { _chartState._totalAnimatingSeries = 0; bool isSelectionRangeChangeByEvent = false; for (final CartesianSeriesRenderer seriesRenderer in seriesRendererList) { final CartesianSeries series = seriesRenderer._series; - final ChartIndexedValueMapper _bubbleSize = series.sizeValueMapper; + final ChartIndexedValueMapper? _bubbleSize = series.sizeValueMapper; seriesRenderer._minimumX = seriesRenderer._minimumY = seriesRenderer._minDelta = seriesRenderer._maximumX = seriesRenderer._maximumY = null; @@ -53,7 +74,7 @@ class _ChartSeries { seriesRenderer._needAnimateSeriesElements = false; seriesRenderer._needsAnimation = false; seriesRenderer._reAnimate = false; - CartesianChartPoint currentPoint; + CartesianChartPoint? currentPoint; yValues = []; sumOfYvalues = 0; seriesRenderer._dataPoints = >[]; @@ -64,24 +85,25 @@ class _ChartSeries { if (!isStacked100 && seriesRenderer._seriesType.contains('100')) { isStacked100 = true; } - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { _chartState._totalAnimatingSeries++; } if (seriesRenderer is HistogramSeriesRenderer) { - final HistogramSeries series = seriesRenderer._series; + final HistogramSeries series = + seriesRenderer._series as HistogramSeries; for (int pointIndex = 0; pointIndex < series.dataSource.length; pointIndex++) { - yValues.add(series.yValueMapper(pointIndex) ?? 0); - sumOfYvalues += yValues[pointIndex] ?? 0; + yValues.add(series.yValueMapper!(pointIndex) ?? 0); + sumOfYvalues += yValues[pointIndex]; } seriesRenderer._processData(series, yValues, sumOfYvalues); seriesRenderer._histogramValues.minValue = - seriesRenderer._histogramValues.yValues.reduce(min); + seriesRenderer._histogramValues.yValues!.reduce(min); seriesRenderer._histogramValues.binWidth = series.binInterval ?? - ((3.5 * seriesRenderer._histogramValues.sDValue) / + ((3.5 * seriesRenderer._histogramValues.sDValue!) / math.pow( - seriesRenderer._histogramValues.yValues.length, 1 / 3)) + seriesRenderer._histogramValues.yValues!.length, 1 / 3)) .round(); } final String seriesType = seriesRenderer._seriesType; @@ -90,7 +112,7 @@ class _ChartSeries { if (series.dataSource != null) { dynamic xVal; dynamic yVal; - num low, high; + num? low, high; num maxYValue = 0; for (int pointIndex = 0; pointIndex < series.dataSource.length;) { currentPoint = _getChartPoint( @@ -103,7 +125,7 @@ class _ChartSeries { if (seriesRenderer is WaterfallSeriesRenderer) { yVal ??= 0; maxYValue += yVal; - currentPoint.maxYValue = maxYValue; + currentPoint!.maxYValue = maxYValue; } if (xVal != null) { @@ -120,11 +142,13 @@ class _ChartSeries { if (xAxis is DateTimeAxis) { xMin = xMin != null ? xMin.millisecondsSinceEpoch : xMin; xMax = xMax != null ? xMax.millisecondsSinceEpoch : xMax; - xPointValue = xPointValue != null - ? xPointValue.millisecondsSinceEpoch - : xPointValue; + xPointValue = xPointValue?.millisecondsSinceEpoch; } else if (xAxis is CategoryAxis) { xPointValue = pointIndex; + } else if (xAxis is DateTimeCategoryAxis) { + xMin = xMin != null ? xMin.millisecondsSinceEpoch : xMin; + xMax = xMax != null ? xMax.millisecondsSinceEpoch : xMax; + xPointValue = xPointValue?.millisecondsSinceEpoch; } if (xMin != null || xMax != null) { _isXVisibleRange = false; @@ -133,8 +157,7 @@ class _ChartSeries { _isYVisibleRange = false; } - if (_chartState._chart.onSelectionChanged != null && - (xMin != null || + if ((xMin != null || xMax != null || yMin != null || yMax != null) && @@ -143,11 +166,13 @@ class _ChartSeries { final int seriesIndex = _chartState ._chartSeries.visibleSeriesRenderers .indexOf(seriesRenderer); - final CartesianSeriesRenderer _oldSeriesRenderer = + final CartesianSeriesRenderer? _oldSeriesRenderer = _chartState._oldSeriesRenderers.length - 1 >= seriesIndex ? _chartState._oldSeriesRenderers[seriesIndex] : null; - if (_oldSeriesRenderer != null) { + if (_oldSeriesRenderer != null && + (_chartState._chart.onSelectionChanged != null || + _needAxisAnimation(seriesRenderer, _oldSeriesRenderer))) { isSelectionRangeChangeByEvent = _oldSeriesRenderer._minimumX != xMin || _oldSeriesRenderer._maximumX != xMax || @@ -181,19 +206,19 @@ class _ChartSeries { : true) { _isXVisibleRange = true; _isYVisibleRange = true; - seriesRenderer._dataPoints.add(currentPoint); - seriesRenderer._xValues.add(xVal); + seriesRenderer._dataPoints.add(currentPoint!); + seriesRenderer._xValues!.add(xVal); if (seriesRenderer is BubbleSeriesRenderer) { bubbleSize = series.sizeValueMapper == null ? 4 - : _bubbleSize(pointIndex) ?? 4; + : _bubbleSize!(pointIndex) ?? 4; currentPoint.bubbleSize = bubbleSize.toDouble(); - seriesRenderer._maxSize ??= currentPoint.bubbleSize; - seriesRenderer._minSize ??= currentPoint.bubbleSize; - seriesRenderer._maxSize = - math.max(seriesRenderer._maxSize, currentPoint.bubbleSize); - seriesRenderer._minSize = - math.min(seriesRenderer._minSize, currentPoint.bubbleSize); + seriesRenderer._maxSize ??= currentPoint.bubbleSize!.toDouble(); + seriesRenderer._minSize ??= currentPoint.bubbleSize!.toDouble(); + seriesRenderer._maxSize = math.max(seriesRenderer._maxSize!, + currentPoint.bubbleSize!.toDouble()); + seriesRenderer._minSize = math.min(seriesRenderer._minSize!, + currentPoint.bubbleSize!.toDouble()); } if (seriesType.contains('range') || @@ -228,8 +253,8 @@ class _ChartSeries { seriesType == 'boxandwhisker') && currentPoint.isVisible) { if (seriesType == 'boxandwhisker') { - final num max = currentPoint.maximum; - final num min = currentPoint.minimum; + final num max = currentPoint.maximum!; + final num min = currentPoint.minimum!; currentPoint.maximum = math.max(max, min); currentPoint.minimum = math.min(max, min); } else { @@ -240,24 +265,25 @@ class _ChartSeries { } } //determines whether the data source has been changed in-order to perform dynamic animation - if (!seriesRenderer._needsAnimation) { + if (!seriesRenderer._needsAnimation && + _needAxisRangeAnimation != true) { if (seriesRenderer._oldSeries == null || - seriesRenderer._oldDataPoints.length < + seriesRenderer._oldDataPoints!.length < seriesRenderer._dataPoints.length) { seriesRenderer._needAnimateSeriesElements = true; - seriesRenderer._needsAnimation = seriesRenderer._visible; + seriesRenderer._needsAnimation = seriesRenderer._visible!; } else { if (seriesRenderer._dataPoints.length <= - seriesRenderer._oldDataPoints.length) { - seriesRenderer._needsAnimation = seriesRenderer._visible && + seriesRenderer._oldDataPoints!.length) { + seriesRenderer._needsAnimation = seriesRenderer._visible! && _findChangesInPoint( currentPoint, - seriesRenderer._oldDataPoints[ + seriesRenderer._oldDataPoints![ seriesRenderer._dataPoints.length - 1], seriesRenderer, ); } else { - seriesRenderer._needsAnimation = seriesRenderer._visible; + seriesRenderer._needsAnimation = seriesRenderer._visible!; } } } @@ -267,9 +293,9 @@ class _ChartSeries { !needSorting && chart.indicators.isEmpty) { _findMinMaxValue( - seriesRenderer._xAxisRenderer, + seriesRenderer._xAxisRenderer!, seriesRenderer, - currentPoint, + currentPoint!, pointIndex, series.dataSource.length, _isXVisibleRange, @@ -280,7 +306,7 @@ class _ChartSeries { seriesRenderer._xValueList.clear(); seriesRenderer._yValueList.clear(); } - if (!currentPoint.isDrop) { + if (!currentPoint!.isDrop) { seriesRenderer._xValueList.add(currentPoint.xValue); seriesRenderer._yValueList.add(currentPoint.yValue); } @@ -288,7 +314,10 @@ class _ChartSeries { } pointIndex = seriesRenderer._seriesType != 'histogram' ? pointIndex + 1 - : pointIndex + yVal; + : pointIndex + yVal as int; + } + if (seriesRenderer._xAxisRenderer is DateTimeCategoryAxisRenderer) { + _sortDateTimeCategoryDetails(seriesRenderer); } if (needSorting) { _sortDataSource(seriesRenderer); @@ -310,9 +339,9 @@ class _ChartSeries { CartesianChartPoint currentPoint, int pointIndex, int dataLength, - [bool isXVisibleRange, - bool isYVisibleRange]) { - if (seriesRenderer._visible) { + [bool? isXVisibleRange, + bool? isYVisibleRange]) { + if (seriesRenderer._visible!) { if (axisRenderer is NumericAxisRenderer) { axisRenderer._findAxisMinMaxValues(seriesRenderer, currentPoint, pointIndex, dataLength, isXVisibleRange, isYVisibleRange); @@ -325,14 +354,17 @@ class _ChartSeries { } else if (axisRenderer is LogarithmicAxisRenderer) { axisRenderer._findAxisMinMaxValues(seriesRenderer, currentPoint, pointIndex, dataLength, isXVisibleRange, isYVisibleRange); + } else if (axisRenderer is DateTimeCategoryAxisRenderer) { + axisRenderer._findAxisMinMaxValues(seriesRenderer, currentPoint, + pointIndex, dataLength, isXVisibleRange, isYVisibleRange); } } } /// To find minimum and maximum series values void _findSeriesMinMax(CartesianSeriesRenderer seriesRenderer) { - final ChartAxisRenderer axisRenderer = seriesRenderer._xAxisRenderer; - if (seriesRenderer._visible) { + final ChartAxisRenderer axisRenderer = seriesRenderer._xAxisRenderer!; + if (seriesRenderer._visible!) { if (seriesRenderer is SplineSeriesRenderer) { seriesRenderer._xValueList.clear(); seriesRenderer._yValueList.clear(); @@ -376,12 +408,12 @@ class _ChartSeries { if (seriesRenderer._series.trendlines != null) { TrendlineRenderer trendlineRenderer; Trendline trendline; - for (int i = 0; i < seriesRenderer._series.trendlines.length; i++) { - trendline = seriesRenderer._series.trendlines[i]; + for (int i = 0; i < seriesRenderer._series.trendlines!.length; i++) { + trendline = seriesRenderer._series.trendlines![i]; trendlineRenderer = seriesRenderer._trendlineRenderer[i]; trendlineRenderer._isNeedRender = trendlineRenderer._visible == true && - seriesRenderer._visible && + seriesRenderer._visible! && (trendline.type == TrendlineType.polynomial ? (trendline.polynomialOrder >= 2 && trendline.polynomialOrder <= 6) @@ -430,6 +462,8 @@ class _ChartSeries { .toLowerCase() .compareTo(firstPoint.sortValue.toLowerCase()) : secondPoint.sortValue.compareTo(firstPoint.sortValue))); + } else { + return 0; } }); } @@ -439,9 +473,9 @@ class _ChartSeries { List seriesRendererCollection) { _StackedItemInfo stackedItemInfo; _ClusterStackedItemInfo clusterStackedItemInfo; - String groupName = ' '; - List<_StackingInfo> positiveValues; - List<_StackingInfo> negativeValues; + String groupName = ''; + List<_StackingInfo>? positiveValues; + List<_StackingInfo>? negativeValues; CartesianSeriesRenderer seriesRenderer; if (isStacked100) { _calculateStackingPercentage(seriesRendererCollection); @@ -454,11 +488,13 @@ class _ChartSeries { if (seriesRenderer is _StackedSeriesRenderer && seriesRenderer._series is _StackedSeriesBase) { final _StackedSeriesBase stackedSeriesBase = - seriesRenderer._series; + seriesRenderer._series as _StackedSeriesBase; if (seriesRenderer._dataPoints.isNotEmpty) { groupName = (seriesRenderer._seriesType.contains('stackedarea')) ? 'stackedareagroup' - : (stackedSeriesBase.groupName ?? 'series ' + i.toString()); + : (stackedSeriesBase.groupName == null + ? ('series ' + i.toString()) + : stackedSeriesBase.groupName); stackedItemInfo = _StackedItemInfo(i, seriesRenderer); if (_chartState._chartSeries.clusterStackedItemInfo.isNotEmpty) { for (int k = 0; @@ -484,7 +520,7 @@ class _ChartSeries { } seriesRenderer._stackingValues = <_StackedValues>[]; - _StackingInfo currentPositiveStackInfo; + _StackingInfo? currentPositiveStackInfo; if (positiveValues == null || negativeValues == null) { positiveValues = <_StackingInfo>[]; @@ -506,11 +542,11 @@ class _ChartSeries { bool isStacked100, List<_StackingInfo> positiveValues, List<_StackingInfo> negativeValues, - _StackingInfo currentPositiveStackInfo, + _StackingInfo? currentPositiveStackInfo, String groupName) { num lastValue, value; CartesianChartPoint point; - _StackingInfo currentNegativeStackInfo; + _StackingInfo? currentNegativeStackInfo; final List startValues = []; final List endValues = []; for (int j = 0; j < seriesRenderer._dataPoints.length; j++) { @@ -538,31 +574,33 @@ class _ChartSeries { } } } - if (currentPositiveStackInfo._stackingValues != null) { - final int length = currentPositiveStackInfo._stackingValues.length; + if (currentPositiveStackInfo?._stackingValues != null) { + final int length = currentPositiveStackInfo!._stackingValues!.length; if (length == 0 || j > length - 1) { - currentPositiveStackInfo._stackingValues.add(0); + currentPositiveStackInfo._stackingValues!.add(0); } } - if (currentNegativeStackInfo._stackingValues != null) { - final int length = currentNegativeStackInfo._stackingValues.length; + if (currentNegativeStackInfo?._stackingValues != null) { + final int length = currentNegativeStackInfo!._stackingValues!.length; if (length == 0 || j > length - 1) { - currentNegativeStackInfo._stackingValues.add(0); + currentNegativeStackInfo._stackingValues!.add(0); } } if (isStacked100 && seriesRenderer is _StackedSeriesRenderer) { value = value / seriesRenderer._percentageValues[j] * 100; value = value.isNaN ? 0 : value; } - if (value >= 0) { - lastValue = currentPositiveStackInfo._stackingValues[j]; - currentPositiveStackInfo._stackingValues[j] = lastValue + value; + if (seriesRenderer._seriesType.contains('stackedarea') || value >= 0) { + lastValue = currentPositiveStackInfo!._stackingValues![j]; + currentPositiveStackInfo._stackingValues![j] = + (lastValue + value).toDouble(); } else { - lastValue = currentNegativeStackInfo._stackingValues[j]; - currentNegativeStackInfo._stackingValues[j] = lastValue + value; + lastValue = currentNegativeStackInfo!._stackingValues![j]; + currentNegativeStackInfo._stackingValues![j] = + (lastValue + value).toDouble(); } startValues.add(lastValue.toDouble()); - endValues.add(value + lastValue); + endValues.add((value + lastValue).toDouble()); if (isStacked100 && endValues[j] > 100) { endValues[j] = 100; } @@ -577,10 +615,10 @@ class _ChartSeries { seriesRenderer._minimumY = startValues.reduce(min); seriesRenderer._maximumY = endValues.reduce(max); - if (seriesRenderer._minimumY > endValues.reduce(min)) { + if (seriesRenderer._minimumY! > endValues.reduce(min)) { seriesRenderer._minimumY = isStacked100 ? -100 : endValues.reduce(min); } - if (seriesRenderer._maximumY < startValues.reduce(max)) { + if (seriesRenderer._maximumY! < startValues.reduce(max)) { seriesRenderer._maximumY = 0; } } @@ -588,24 +626,24 @@ class _ChartSeries { /// To find the percentage of stacked series void _calculateStackingPercentage( List seriesRendererCollection) { - List<_StackingInfo> percentageValues; + List<_StackingInfo>? percentageValues; CartesianSeriesRenderer seriesRenderer; String groupName; - _StackingInfo stackingInfo; + _StackingInfo? stackingInfo; int length; num lastValue, value; CartesianChartPoint point; for (int i = 0; i < seriesRendererCollection.length; i++) { seriesRenderer = seriesRendererCollection[i]; - seriesRenderer._yAxisRenderer._isStack100 = true; + seriesRenderer._yAxisRenderer!._isStack100 = true; if (seriesRenderer is _StackedSeriesRenderer && seriesRenderer._series is _StackedSeriesBase) { final _StackedSeriesBase stackedSeriesBase = - seriesRenderer._series; + seriesRenderer._series as _StackedSeriesBase; if (seriesRenderer._dataPoints.isNotEmpty) { groupName = (seriesRenderer._seriesType == 'stackedarea100') ? 'stackedareagroup' - : (stackedSeriesBase.groupName ?? 'series ' + i.toString()); + : stackedSeriesBase.groupName; if (percentageValues == null) { percentageValues = <_StackingInfo>[]; @@ -625,18 +663,19 @@ class _ChartSeries { } } } - if (stackingInfo._stackingValues != null) { - length = stackingInfo._stackingValues.length; + if (stackingInfo?._stackingValues != null) { + length = stackingInfo!._stackingValues!.length; if (length == 0 || j > length - 1) { - stackingInfo._stackingValues.add(0); + stackingInfo._stackingValues!.add(0); } } - if (value >= 0) { - lastValue = stackingInfo._stackingValues[j]; - stackingInfo._stackingValues[j] = lastValue + value; + if (seriesRenderer._seriesType.contains('stackedarea') || + value >= 0) { + lastValue = stackingInfo!._stackingValues![j]; + stackingInfo._stackingValues![j] = (lastValue + value).toDouble(); } else { - lastValue = stackingInfo._stackingValues[j]; - stackingInfo._stackingValues[j] = lastValue - value; + lastValue = stackingInfo!._stackingValues![j]; + stackingInfo._stackingValues![j] = (lastValue - value).toDouble(); } if (j == seriesRenderer._dataPoints.length - 1) { percentageValues.add(stackingInfo); @@ -647,12 +686,12 @@ class _ChartSeries { for (int i = 0; i < percentageValues.length; i++) { if (seriesRenderer._seriesType == 'stackedarea100') { seriesRenderer._percentageValues = - percentageValues[i]._stackingValues; + percentageValues[i]._stackingValues!; } else { if (stackedSeriesBase.groupName == percentageValues[i].groupName) { seriesRenderer._percentageValues = - percentageValues[i]._stackingValues; + percentageValues[i]._stackingValues!; } } } @@ -750,9 +789,9 @@ class _ChartSeries { if (chart.indicators != null && chart.indicators.isNotEmpty) { dynamic indicator; bool existField; - Map _map; + Map _map = Map(); TechnicalIndicatorsRenderer technicalIndicatorsRenderer; - if (!chart.legend.isVisible) { + if (!chart.legend.isVisible!) { final List textCollection = []; for (int i = 0; i < chart.indicators.length; i++) { final TechnicalIndicators indicator = @@ -766,7 +805,7 @@ class _ChartSeries { _map = Map(); //ignore: avoid_function_literals_in_foreach_calls textCollection.forEach((String str) => - _map[str] = !_map.containsKey(str) ? (1) : (_map[str] + 1)); + _map[str] = !_map.containsKey(str) ? (1) : (_map[str]! + 1)); } final List indicatorTextCollection = []; @@ -777,7 +816,7 @@ class _ChartSeries { technicalIndicatorsRenderer._dataPoints = >[]; technicalIndicatorsRenderer._index = i; - if (!chart.legend.isVisible) { + if (!chart.legend.isVisible!) { final int count = indicatorTextCollection .contains(technicalIndicatorsRenderer._indicatorType) ? _getIndicatorId(indicatorTextCollection, @@ -806,23 +845,23 @@ class _ChartSeries { pointIndex++) { if (indicator.xValueMapper != null) { final dynamic xVal = indicator.xValueMapper(pointIndex); - num highValue, lowValue, openValue, closeValue, volumeValue; - technicalIndicatorsRenderer._dataPoints + num? highValue, lowValue, openValue, closeValue, volumeValue; + technicalIndicatorsRenderer._dataPoints! .add(CartesianChartPoint(xVal, null)); - currentPoint = technicalIndicatorsRenderer._dataPoints[ - technicalIndicatorsRenderer._dataPoints.length - 1]; + currentPoint = technicalIndicatorsRenderer._dataPoints![ + technicalIndicatorsRenderer._dataPoints!.length - 1]; if (indicator.highValueMapper != null) { highValue = indicator.highValueMapper(pointIndex); technicalIndicatorsRenderer - ._dataPoints[ - technicalIndicatorsRenderer._dataPoints.length - 1] + ._dataPoints![ + technicalIndicatorsRenderer._dataPoints!.length - 1] .high = highValue; } if (indicator.lowValueMapper != null) { lowValue = indicator.lowValueMapper(pointIndex); technicalIndicatorsRenderer - ._dataPoints[ - technicalIndicatorsRenderer._dataPoints.length - 1] + ._dataPoints![ + technicalIndicatorsRenderer._dataPoints!.length - 1] .low = lowValue; } @@ -836,23 +875,23 @@ class _ChartSeries { if (indicator.openValueMapper != null) { openValue = indicator.openValueMapper(pointIndex); technicalIndicatorsRenderer - ._dataPoints[ - technicalIndicatorsRenderer._dataPoints.length - 1] + ._dataPoints![ + technicalIndicatorsRenderer._dataPoints!.length - 1] .open = openValue; } if (indicator.closeValueMapper != null) { closeValue = indicator.closeValueMapper(pointIndex); technicalIndicatorsRenderer - ._dataPoints[ - technicalIndicatorsRenderer._dataPoints.length - 1] + ._dataPoints![ + technicalIndicatorsRenderer._dataPoints!.length - 1] .close = closeValue; } if (indicator is AccumulationDistributionIndicator && indicator.volumeValueMapper != null) { - volumeValue = indicator.volumeValueMapper(pointIndex); + volumeValue = indicator.volumeValueMapper!(pointIndex); technicalIndicatorsRenderer - ._dataPoints[ - technicalIndicatorsRenderer._dataPoints.length - 1] + ._dataPoints![ + technicalIndicatorsRenderer._dataPoints!.length - 1] .volume = volumeValue; } @@ -881,13 +920,13 @@ class _ChartSeries { (openValue == null && valueField == 'open') || (volumeValue == null && technicalIndicatorsRenderer._indicatorType == 'AD')) { - technicalIndicatorsRenderer._dataPoints.removeAt( - technicalIndicatorsRenderer._dataPoints.length - 1); + technicalIndicatorsRenderer._dataPoints!.removeAt( + technicalIndicatorsRenderer._dataPoints!.length - 1); } } } } else if (indicator.seriesName != null) { - CartesianSeriesRenderer seriesRenderer; + CartesianSeriesRenderer? seriesRenderer; for (int i = 0; i < _chartState._chartSeries.visibleSeriesRenderers.length; i++) { @@ -907,7 +946,7 @@ class _ChartSeries { : null; } if (technicalIndicatorsRenderer._dataPoints != null && - technicalIndicatorsRenderer._dataPoints.isNotEmpty) { + technicalIndicatorsRenderer._dataPoints!.isNotEmpty) { indicator._initSeriesCollection( indicator, chart, technicalIndicatorsRenderer); indicator._initDataSource(indicator, technicalIndicatorsRenderer); @@ -923,7 +962,7 @@ class _ChartSeries { /// To get the field type of an indicator String _getFieldType(TechnicalIndicators indicator) { - String valueField; + String valueField = ''; if (indicator is EmaIndicator) { valueField = indicator.valueField; } else if (indicator is TmaIndicator) { @@ -970,4 +1009,22 @@ class _ChartSeries { technicalIndicatorsRenderer._indicatorType = 'TMA'; } } + + //this function sorts the details available based on the x which is of datetime type + void _sortDateTimeCategoryDetails(CartesianSeriesRenderer seriesRenderer) { + final DateTimeCategoryAxisRenderer axisRenderer = + seriesRenderer._xAxisRenderer as DateTimeCategoryAxisRenderer; + seriesRenderer._dataPoints.sort((CartesianChartPoint point1, + CartesianChartPoint point2) { + return point2.x.isAfter(point1.x) ? -1 : 1; + }); + axisRenderer._labels.sort((String first, String second) { + return int.parse(first) < int.parse(second) ? -1 : 1; + }); + seriesRenderer._xValues?.sort(); + for (final CartesianChartPoint point in seriesRenderer._dataPoints) { + point.xValue = axisRenderer._labels + .indexOf(point.x.microsecondsSinceEpoch.toString()); + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/area_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/area_segment.dart index 7af9be393..bf095f296 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/area_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/area_segment.dart @@ -5,11 +5,12 @@ part of charts; /// This generates the area series points and has the [calculateSegmentPoints] override method /// used to customize the area series segment point calculation. /// -/// It gets the path, stroke color and fill color from the [series] to render the segment. +/// It gets the path, stroke color and fill color from the `series` to render the segment. /// class AreaSegment extends ChartSegment { - Path _path, _strokePath; - Rect _pathRect; + late Path _path; + Path? _strokePath; + Rect? _pathRect; /// Gets the color of the series. @override @@ -17,27 +18,25 @@ class AreaSegment extends ChartSegment { fillPaint = Paint(); if (_series.gradient == null) { if (_color != null) { - fillPaint.color = _color; - fillPaint.style = PaintingStyle.fill; + fillPaint!.color = _color!; + fillPaint!.style = PaintingStyle.fill; } } else { fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient, _pathRect, - _seriesRenderer._chartState._requireInvertedAxis) + ? _getLinearGradientPaint(_series.gradient!, _pathRect!, + _seriesRenderer._chartState!._requireInvertedAxis) : fillPaint; } assert(_series.opacity >= 0, 'The opacity value of the area series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the area series should be less than or equal to 1.'); - if (fillPaint.color != null) { - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; - } + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -49,7 +48,7 @@ class AreaSegment extends ChartSegment { ..strokeWidth = _series.borderWidth; if (_series.borderGradient != null) { _strokePaint.shader = - _series.borderGradient.createShader(_strokePath.getBounds()); + _series.borderGradient!.createShader(_strokePath!.getBounds()); } else if (_strokeColor != null) { _strokePaint.color = _series.borderColor; } @@ -70,9 +69,9 @@ class AreaSegment extends ChartSegment { void onPaint(Canvas canvas) { _pathRect = _path.getBounds(); canvas.drawPath( - _path, (_series.gradient == null) ? fillPaint : getFillPaint()); - if (strokePaint.color != Colors.transparent && _strokePath != null) { - _drawDashedLine(canvas, _series.dashArray, strokePaint, _strokePath); + _path, (_series.gradient == null) ? fillPaint! : getFillPaint()); + if (strokePaint!.color != Colors.transparent && _strokePath != null) { + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _strokePath!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bar_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bar_segment.dart index 2ca849c1a..e4a641438 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bar_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bar_segment.dart @@ -5,43 +5,42 @@ part of charts; /// This generates the bar series points and has the [calculateSegmentPoints] override method /// used to customize the bar series segment point calculation. /// -/// It gets the path, stroke color and fill color from the [series] to render the bar segment. +/// It gets the path, stroke color and fill color from the `series` to render the bar segment. /// class BarSegment extends ChartSegment { - RRect _trackBarRect; - @override - CartesianChartPoint _currentPoint; - Paint _trackerFillPaint, _trackerStrokePaint; - Path _path; + late RRect _trackBarRect; + + Paint? _trackerFillPaint, _trackerStrokePaint; + late Path _path; /// Rectangle of the segment this could be used to render the segment while overriding this segment - RRect segmentRect; + late RRect segmentRect; /// Gets the color of the series. @override Paint getFillPaint() { if (_series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint.isEmpty == true + ..color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.color - : (_currentPoint.pointColorMapper ?? _color) + : (_currentPoint!.pointColorMapper ?? _color!) ..style = PaintingStyle.fill; } else { fillPaint = _getLinearGradientPaint( - _series.gradient, - _currentPoint.region, - _seriesRenderer._chartState._requireInvertedAxis); + _series.gradient!, + _currentPoint!.region!, + _seriesRenderer._chartState!._requireInvertedAxis); } assert(_series.opacity >= 0, 'The opacity value of the the bar series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the the bar series should be less than or equal to 1.'); - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -49,44 +48,46 @@ class BarSegment extends ChartSegment { Paint getStrokePaint() { strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint.isEmpty == true + ..strokeWidth = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderWidth - : _strokeWidth; + : _strokeWidth!; if (_series.borderGradient != null) { - strokePaint.shader = - _series.borderGradient.createShader(_currentPoint.region); + strokePaint!.shader = + _series.borderGradient!.createShader(_currentPoint!.region!); } else { - strokePaint.color = _currentPoint.isEmpty == true + strokePaint!.color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderColor - : _strokeColor; + : _strokeColor!; } _series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; + ? strokePaint!.color = Colors.transparent + : strokePaint!.color; _defaultStrokeColor = strokePaint; - return strokePaint; + return strokePaint!; } /// Method to get series tracker fill. Paint _getTrackerFillPaint() { - final BarSeries barSeries = _series; - _trackerFillPaint = Paint() - ..color = barSeries.trackColor - ..style = PaintingStyle.fill; - return _trackerFillPaint; + if (_series is BarSeries) { + final BarSeries barSeries = _series as BarSeries; + _trackerFillPaint = Paint() + ..color = barSeries.trackColor + ..style = PaintingStyle.fill; + } + return _trackerFillPaint!; } /// Method to get series tracker stroke color. Paint _getTrackerStrokePaint() { - final BarSeries barSeries = _series; + final BarSeries barSeries = _series as BarSeries; _trackerStrokePaint = Paint() ..color = barSeries.trackBorderColor ..strokeWidth = barSeries.trackBorderWidth ..style = PaintingStyle.stroke; barSeries.trackBorderWidth == 0 - ? _trackerStrokePaint.color = Colors.transparent - : _trackerStrokePaint.color; - return _trackerStrokePaint; + ? _trackerStrokePaint!.color = Colors.transparent + : _trackerStrokePaint!.color; + return _trackerStrokePaint!; } /// Calculates the rendering bounds of a segment. @@ -96,23 +97,23 @@ class BarSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - final BarSeries barSeries = _series; + final BarSeries barSeries = _series as BarSeries; if (_trackerFillPaint != null && barSeries.isTrackVisible) { - _drawSegmentRect(canvas, _trackBarRect, _trackerFillPaint); + _drawSegmentRect(canvas, _trackBarRect, _trackerFillPaint!); } if (_trackerStrokePaint != null && barSeries.isTrackVisible) { - _drawSegmentRect(canvas, _trackBarRect, _trackerStrokePaint); + _drawSegmentRect(canvas, _trackBarRect, _trackerStrokePaint!); } if (fillPaint != null) { - _drawSegmentRect(canvas, segmentRect, fillPaint); + _drawSegmentRect(canvas, segmentRect, fillPaint!); } if (strokePaint != null) { if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) { - _drawDashedLine(canvas, _series.dashArray, strokePaint, _path); + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); } else { - _drawSegmentRect(canvas, segmentRect, strokePaint); + _drawSegmentRect(canvas, segmentRect, strokePaint!); } } } @@ -125,9 +126,9 @@ class BarSegment extends ChartSegment { _seriesRenderer, paint, segmentRect, - _currentPoint.yValue, + _currentPoint!.yValue, animationFactor, - _oldPoint != null ? _oldPoint.region : _oldRegion, + _oldPoint?.region ?? _oldRegion, _oldPoint?.yValue, _oldSeriesVisible) : canvas.drawRRect(segmentRect, paint); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/box_and_whisker_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/box_and_whisker_segment.dart index b6b7715a5..7a7272116 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/box_and_whisker_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/box_and_whisker_segment.dart @@ -5,10 +5,10 @@ part of charts; /// Generates the box and whisker series points and has the [calculateSegmentPoints] override method /// used to customize the box and whisker series segment point calculation. /// -/// Gets the path and fill color from the [series] to render the box and whisker segment. +/// Gets the path and fill color from the `series` to render the box and whisker segment. /// class BoxAndWhiskerSegment extends ChartSegment { - num _x, + late double _x, _min, _max, _maxY, @@ -26,19 +26,16 @@ class BoxAndWhiskerSegment extends ChartSegment { _bottomLineY, _medianX, _medianY; - Path _path; - Paint _meanPaint; + late Path _path; + late Paint _meanPaint; // ignore: unused_field - Color _pointColorMapper; + Color? _pointColorMapper; - @override - CartesianChartPoint _currentPoint; - - bool _isTransposed; + late bool _isTransposed; - _ChartLocation _minPoint, _maxPoint, _centerMinPoint, _centerMaxPoint; + late _ChartLocation _minPoint, _maxPoint, _centerMinPoint, _centerMaxPoint; - BoxAndWhiskerSeries _boxAndWhiskerSeries; + late BoxAndWhiskerSeries _boxAndWhiskerSeries; /// Gets the color of the series. @override @@ -46,26 +43,26 @@ class BoxAndWhiskerSegment extends ChartSegment { /// Get and set the paint options for box and whisker series. if (_series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint.isEmpty == true + ..color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.color - : (_currentPoint.pointColorMapper ?? _color) + : (_currentPoint!.pointColorMapper ?? _color!) ..style = PaintingStyle.fill; } else { fillPaint = _getLinearGradientPaint( - _series.gradient, - _currentPoint.region, - _seriesRenderer._chartState._requireInvertedAxis); + _series.gradient!, + _currentPoint!.region!, + _seriesRenderer._chartState!._requireInvertedAxis); } assert(_series.opacity >= 0, 'The opacity value of the box plot series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the box plot series should be less than or equal to 1.'); - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -73,44 +70,44 @@ class BoxAndWhiskerSegment extends ChartSegment { Paint getStrokePaint() { strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint.isEmpty == true + ..strokeWidth = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderWidth - : _strokeWidth; + : _strokeWidth!; _meanPaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint.isEmpty == true + ..strokeWidth = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderWidth - : _strokeWidth; + : _strokeWidth!; if (_series.borderGradient != null) { - strokePaint.shader = - _series.borderGradient.createShader(_currentPoint.region); + strokePaint!.shader = + _series.borderGradient!.createShader(_currentPoint!.region!); _meanPaint.shader = - _series.borderGradient.createShader(_currentPoint.region); + _series.borderGradient!.createShader(_currentPoint!.region!); } else { - strokePaint.color = _currentPoint.isEmpty == true + strokePaint!.color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderColor - : _strokeColor; - _meanPaint.color = _currentPoint.isEmpty == true + : _strokeColor!; + _meanPaint.color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderColor - : _strokeColor; + : _strokeColor!; } _series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; + ? strokePaint!.color = Colors.transparent + : strokePaint!.color; _defaultStrokeColor = strokePaint; - return strokePaint; + return strokePaint!; } /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _boxAndWhiskerSeries = _series; + _boxAndWhiskerSeries = _series as BoxAndWhiskerSeries; _x = _max = double.nan; - _isTransposed = _seriesRenderer._chartState._requireInvertedAxis; - _minPoint = _currentPoint.minimumPoint; - _maxPoint = _currentPoint.maximumPoint; - _centerMinPoint = _currentPoint.centerMinimumPoint; - _centerMaxPoint = _currentPoint.centerMaximumPoint; + _isTransposed = _seriesRenderer._chartState!._requireInvertedAxis; + _minPoint = _currentPoint!.minimumPoint!; + _maxPoint = _currentPoint!.maximumPoint!; + _centerMinPoint = _currentPoint!.centerMinimumPoint!; + _centerMaxPoint = _currentPoint!.centerMaximumPoint!; _x = _minPoint.x; _min = _minPoint.y; _max = _maxPoint.y; @@ -118,12 +115,12 @@ class BoxAndWhiskerSegment extends ChartSegment { _maxY = _centerMaxPoint.y; _centerMin = _centerMinPoint.x; _minY = _centerMinPoint.y; - _lowerX = _currentPoint.lowerQuartilePoint.x; - _lowerY = _currentPoint.lowerQuartilePoint.y; - _upperX = _currentPoint.upperQuartilePoint.x; - _upperY = _currentPoint.upperQuartilePoint.y; - _medianX = _currentPoint.medianPoint.x; - _medianY = _currentPoint.medianPoint.y; + _lowerX = _currentPoint!.lowerQuartilePoint!.x; + _lowerY = _currentPoint!.lowerQuartilePoint!.y; + _upperX = _currentPoint!.upperQuartilePoint!.x; + _upperY = _currentPoint!.upperQuartilePoint!.y; + _medianX = _currentPoint!.medianPoint!.x; + _medianY = _currentPoint!.medianPoint!.y; if (_lowerY > _upperY) { _centersY = _upperY + ((_upperY - _lowerY).abs() / 2); @@ -152,15 +149,15 @@ class BoxAndWhiskerSegment extends ChartSegment { /// To draw line path of box and whisker segments void _drawLine(Canvas canvas) { canvas.drawLine( - Offset(_lowerX, _topLineY), Offset(_upperX, _topLineY), strokePaint); + Offset(_lowerX, _topLineY), Offset(_upperX, _topLineY), strokePaint!); canvas.drawLine(Offset(_centerMax, _topRectY), - Offset(_centerMax, _topLineY), strokePaint); + Offset(_centerMax, _topLineY), strokePaint!); canvas.drawLine( - Offset(_lowerX, _medianY), Offset(_upperX, _medianY), strokePaint); + Offset(_lowerX, _medianY), Offset(_upperX, _medianY), strokePaint!); canvas.drawLine(Offset(_centerMax, _bottomRectY), - Offset(_centerMax, _bottomLineY), strokePaint); + Offset(_centerMax, _bottomLineY), strokePaint!); canvas.drawLine(Offset(_lowerX, _bottomLineY), - Offset(_upperX, _bottomLineY), strokePaint); + Offset(_upperX, _bottomLineY), strokePaint!); } /// To draw mean line path of box and whisker segments @@ -169,10 +166,10 @@ class BoxAndWhiskerSegment extends ChartSegment { final x = !isTransposed ? position.dx : position.dy; final y = !isTransposed ? position.dy : position.dx; if (_series.animationDuration <= 0 || - animationFactor >= _seriesRenderer._chartState._seriesDurationFactor) { + animationFactor >= _seriesRenderer._chartState!._seriesDurationFactor) { /// `0.15` is animation duration of mean point value, as like marker. final double opacity = (animationFactor - - _seriesRenderer._chartState._seriesDurationFactor) * + _seriesRenderer._chartState!._seriesDurationFactor) * (1 / 0.15); _meanPaint.color = Color.fromRGBO(_meanPaint.color.red, _meanPaint.color.green, _meanPaint.color.blue, opacity); @@ -186,7 +183,7 @@ class BoxAndWhiskerSegment extends ChartSegment { /// To draw line path of box and whisker segments void _drawFillLine(Canvas canvas) { final bool isOpen = - _currentPoint.lowerQuartile > _currentPoint.upperQuartile; + _currentPoint!.lowerQuartile! > _currentPoint!.upperQuartile!; canvas.drawLine( Offset(_topRectY, _maxY), Offset( @@ -195,7 +192,7 @@ class BoxAndWhiskerSegment extends ChartSegment { .abs() * animationFactor), _maxY), - strokePaint); + strokePaint!); canvas.drawLine( Offset(_bottomRectY, _maxY), Offset( @@ -204,24 +201,25 @@ class BoxAndWhiskerSegment extends ChartSegment { .abs() * animationFactor), _maxY), - strokePaint); + strokePaint!); } /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { if (fillPaint != null && _seriesRenderer._reAnimate || - (!(_seriesRenderer._chartState._widgetNeedUpdate && - !_seriesRenderer._chartState._isLegendToggled))) { + (!(_seriesRenderer._chartState!._widgetNeedUpdate && + _oldSeriesRenderer != null && + !_seriesRenderer._chartState!._isLegendToggled))) { _path = Path(); if (!_isTransposed && - _currentPoint.lowerQuartile > _currentPoint.upperQuartile) { - final num temp = _upperY; + _currentPoint!.lowerQuartile! > _currentPoint!.upperQuartile!) { + final double temp = _upperY; _upperY = _lowerY; _lowerY = temp; } - if (_seriesRenderer._chartState._isLegendToggled) { + if (_seriesRenderer._chartState!._isLegendToggled) { animationFactor = 1; } if (_lowerY > _upperY) { @@ -292,19 +290,19 @@ class BoxAndWhiskerSegment extends ChartSegment { if (_boxAndWhiskerSeries.showMean) { _drawMeanLine( canvas, - Offset(_currentPoint.centerMeanPoint.y, - _currentPoint.centerMeanPoint.x), + Offset(_currentPoint!.centerMeanPoint!.y, + _currentPoint!.centerMeanPoint!.x), Size(_series.markerSettings.width, _series.markerSettings.height), _isTransposed); } _lowerX == _upperX ? canvas.drawLine( - Offset(_lowerX, _lowerY), Offset(_upperX, _upperY), fillPaint) + Offset(_lowerX, _lowerY), Offset(_upperX, _upperY), fillPaint!) : _drawRectPath(); } else { - if (_currentPoint.lowerQuartile > _currentPoint.upperQuartile) { - final num temp = _upperY; + if (_currentPoint!.lowerQuartile! > _currentPoint!.upperQuartile!) { + final double temp = _upperY; _upperY = _lowerY; _lowerY = temp; } @@ -312,36 +310,36 @@ class BoxAndWhiskerSegment extends ChartSegment { if (_boxAndWhiskerSeries.showMean) { _drawMeanLine( canvas, - Offset(_currentPoint.centerMeanPoint.x, - _currentPoint.centerMeanPoint.y), + Offset(_currentPoint!.centerMeanPoint!.x, + _currentPoint!.centerMeanPoint!.y), Size(_series.markerSettings.width, _series.markerSettings.height), _isTransposed); } _lowerY == _upperY ? canvas.drawLine( - Offset(_lowerX, _lowerY), Offset(_upperX, _upperY), fillPaint) + Offset(_lowerX, _lowerY), Offset(_upperX, _upperY), fillPaint!) : _drawRectPath(); } if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0 && _series.animationDuration <= 0) { - canvas.drawPath(_path, fillPaint); - _drawDashedLine(canvas, _series.dashArray, strokePaint, _path); + canvas.drawPath(_path, fillPaint!); + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); } else { - canvas.drawPath(_path, fillPaint); - canvas.drawPath(_path, strokePaint); + canvas.drawPath(_path, fillPaint!); + canvas.drawPath(_path, strokePaint!); } - if (fillPaint.style == PaintingStyle.fill) { + if (fillPaint!.style == PaintingStyle.fill) { if (_isTransposed) { - if (_currentPoint.lowerQuartile > _currentPoint.upperQuartile) { + if (_currentPoint!.lowerQuartile! > _currentPoint!.upperQuartile!) { _drawFillLine(canvas); } if (_boxAndWhiskerSeries.showMean) { _drawMeanLine( canvas, - Offset(_currentPoint.centerMeanPoint.y, - _currentPoint.centerMeanPoint.x), + Offset(_currentPoint!.centerMeanPoint!.y, + _currentPoint!.centerMeanPoint!.x), Size(_series.markerSettings.width, _series.markerSettings.height), _isTransposed); @@ -351,37 +349,37 @@ class BoxAndWhiskerSegment extends ChartSegment { if (_boxAndWhiskerSeries.showMean) { _drawMeanLine( canvas, - Offset(_currentPoint.centerMeanPoint.x, - _currentPoint.centerMeanPoint.y), + Offset(_currentPoint!.centerMeanPoint!.x, + _currentPoint!.centerMeanPoint!.y), Size(_series.markerSettings.width, _series.markerSettings.height), _isTransposed); } } } - } else if (!_seriesRenderer._chartState._isLegendToggled) { - final BoxAndWhiskerSegment currentSegment = - _seriesRenderer._segments[currentSegmentIndex]; - final BoxAndWhiskerSegment oldSegment = - (currentSegment._oldSeriesRenderer != null && - currentSegment._oldSeriesRenderer._segments.isNotEmpty && - currentSegment._oldSeriesRenderer._segments[0] - is BoxAndWhiskerSegment && - currentSegment._oldSeriesRenderer._segments.length - 1 >= - currentSegmentIndex) - ? currentSegment._oldSeriesRenderer._segments[currentSegmentIndex] - : null; + } else if (!_seriesRenderer._chartState!._isLegendToggled) { + final BoxAndWhiskerSegment currentSegment = _seriesRenderer + ._segments[currentSegmentIndex!] as BoxAndWhiskerSegment; + final BoxAndWhiskerSegment? oldSegment = (currentSegment + ._oldSeriesRenderer!._segments.isNotEmpty && + currentSegment._oldSeriesRenderer!._segments[0] + is BoxAndWhiskerSegment && + currentSegment._oldSeriesRenderer!._segments.length - 1 >= + currentSegmentIndex!) + ? currentSegment._oldSeriesRenderer!._segments[currentSegmentIndex!] + as BoxAndWhiskerSegment? + : null; _animateBoxSeries( _boxAndWhiskerSeries.showMean, - Offset( - _currentPoint.centerMeanPoint.x, _currentPoint.centerMeanPoint.y), - Offset( - _currentPoint.centerMeanPoint.y, _currentPoint.centerMeanPoint.x), + Offset(_currentPoint!.centerMeanPoint!.x, + _currentPoint!.centerMeanPoint!.y), + Offset(_currentPoint!.centerMeanPoint!.y, + _currentPoint!.centerMeanPoint!.x), Size(_series.markerSettings.width, _series.markerSettings.height), _max, _isTransposed, - _currentPoint.lowerQuartile, - _currentPoint.upperQuartile, + _currentPoint!.lowerQuartile!, + _currentPoint!.upperQuartile!, _minY, _maxY, oldSegment?._minY, @@ -401,8 +399,8 @@ class BoxAndWhiskerSegment extends ChartSegment { _medianX, _medianY, animationFactor, - fillPaint, - strokePaint, + fillPaint!, + strokePaint!, canvas, _seriesRenderer); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bubble_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bubble_segment.dart index 6553c679f..f4c479d03 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bubble_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bubble_segment.dart @@ -5,15 +5,12 @@ part of charts; /// Generates the bubble series points and has the [calculateSegmentPoints] override method /// used to customize the bubble series segment point calculation. /// -/// Gets the path, stroke color and fill color from the [series] to render the bubble series. +/// Gets the path, stroke color and fill color from the `series` to render the bubble series. /// class BubbleSegment extends ChartSegment { ///Center position of the bubble and size - num _centerX, _centerY, _radius, _size; - - @override - CartesianChartPoint _currentPoint; + late double _centerX, _centerY, _radius, _size; /// Gets the color of the series. @override @@ -22,31 +19,31 @@ class BubbleSegment extends ChartSegment { if (_series.gradient == null) { if (_color != null) { fillPaint = Paint() - ..color = _currentPoint.isEmpty == true + ..color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.color - : ((hasPointColor && _currentPoint.pointColorMapper != null) - ? _currentPoint.pointColorMapper - : _color) + : ((hasPointColor && _currentPoint!.pointColorMapper != null) + ? _currentPoint!.pointColorMapper! + : _color!) ..style = PaintingStyle.fill; } } else { fillPaint = _getLinearGradientPaint( - _series.gradient, - _currentPoint.region, - _seriesRenderer._chartState._requireInvertedAxis); + _series.gradient!, + _currentPoint!.region!, + _seriesRenderer._chartState!._requireInvertedAxis); } assert(_series.opacity >= 0, 'The opacity value of the bubble series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the bubble series should be less than or equal to 1.'); - if (fillPaint.color != null) { - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; + if (fillPaint?.color != null) { + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; } _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -54,16 +51,16 @@ class BubbleSegment extends ChartSegment { Paint getStrokePaint() { final Paint _strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint.isEmpty == true + ..strokeWidth = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderWidth - : _strokeWidth; + : _strokeWidth!; if (_series.borderGradient != null) { _strokePaint.shader = - _series.borderGradient.createShader(_currentPoint.region); + _series.borderGradient!.createShader(_currentPoint!.region!); } else { - _strokePaint.color = _currentPoint.isEmpty == true + _strokePaint.color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderColor - : _strokeColor; + : _strokeColor!; } _series.borderWidth == 0 ? _strokePaint.color = Colors.transparent @@ -77,36 +74,37 @@ class BubbleSegment extends ChartSegment { void calculateSegmentPoints() { _centerX = _centerY = double.nan; final Rect rect = _calculatePlotOffset( - _seriesRenderer._chartState._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer._axis.plotOffset, - _seriesRenderer._yAxisRenderer._axis.plotOffset)); + _seriesRenderer._chartState!._chartAxis._axisClipRect, + Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, + _seriesRenderer._yAxisRenderer!._axis.plotOffset)); final _ChartLocation localtion = _calculatePoint( - _currentPoint.xValue, - _currentPoint.yValue, - _seriesRenderer._xAxisRenderer, - _seriesRenderer._yAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _currentPoint!.xValue, + _currentPoint!.yValue, + _seriesRenderer._xAxisRenderer!, + _seriesRenderer._yAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); _centerX = localtion.x; _centerY = localtion.y; - _radius = _calculateBubbleRadius(_seriesRenderer); - _currentPoint.region = Rect.fromLTRB( + if (_seriesRenderer is BubbleSeriesRenderer) + _radius = _calculateBubbleRadius(_seriesRenderer as BubbleSeriesRenderer); + _currentPoint!.region = Rect.fromLTRB( localtion.x - 2 * _radius, localtion.y - 2 * _radius, localtion.x + 2 * _radius, localtion.y + 2 * _radius); - _size = _radius = _currentPoint.region.width / 2; + _size = _radius = _currentPoint!.region!.width / 2; } /// To calculate and return the bubble size - num _calculateBubbleRadius(BubbleSeriesRenderer _seriesRenderer) { - final BubbleSeries bubbleSeries = _series; + double _calculateBubbleRadius(BubbleSeriesRenderer _seriesRenderer) { + final BubbleSeries bubbleSeries = _series as BubbleSeries; num bubbleRadius, sizeRange, radiusRange, maxSize, minSize; - maxSize = _seriesRenderer._maxSize; - minSize = _seriesRenderer._minSize; + maxSize = _seriesRenderer._maxSize!; + minSize = _seriesRenderer._minSize!; sizeRange = maxSize - minSize; - final num bubbleSize = ((_currentPoint.bubbleSize) ?? 4).toDouble(); + final double bubbleSize = ((_currentPoint!.bubbleSize) ?? 4).toDouble(); assert(bubbleSeries.minimumRadius >= 0 && bubbleSeries.maximumRadius >= 0, 'The min radius and max radius of the bubble should be greater than or equal to 0.'); if (bubbleSeries.sizeValueMapper == null) { @@ -114,44 +112,41 @@ class BubbleSegment extends ChartSegment { ? bubbleRadius = bubbleSeries.minimumRadius : bubbleRadius = bubbleSeries.maximumRadius; } else { - if ((bubbleSeries.maximumRadius != null) && - (bubbleSeries.minimumRadius != null)) { - if (sizeRange == 0) { - bubbleRadius = bubbleSize == 0 - ? bubbleSeries.minimumRadius - : bubbleSeries.maximumRadius; - } else { - radiusRange = - (bubbleSeries.maximumRadius - bubbleSeries.minimumRadius) * 2; - bubbleRadius = - (((bubbleSize.abs() - minSize) * radiusRange) / sizeRange) + - bubbleSeries.minimumRadius; - } + if (sizeRange == 0) { + bubbleRadius = bubbleSize == 0 + ? bubbleSeries.minimumRadius + : bubbleSeries.maximumRadius; + } else { + radiusRange = + (bubbleSeries.maximumRadius - bubbleSeries.minimumRadius) * 2; + bubbleRadius = + (((bubbleSize.abs() - minSize) * radiusRange) / sizeRange) + + bubbleSeries.minimumRadius; } } - return bubbleRadius; + return bubbleRadius.toDouble(); } /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _segmentRect = RRect.fromRectAndRadius(_currentPoint.region, Radius.zero); - if (_seriesRenderer._chartState._widgetNeedUpdate && + _segmentRect = RRect.fromRectAndRadius(_currentPoint!.region!, Radius.zero); + if (_seriesRenderer._chartState!._widgetNeedUpdate && !_seriesRenderer._reAnimate && - !_seriesRenderer._chartState._isLegendToggled && - _seriesRenderer._chartState._oldSeriesRenderers != null && - _seriesRenderer._chartState._oldSeriesRenderers.isNotEmpty && + !_seriesRenderer._chartState!._isLegendToggled && + _seriesRenderer._chartState!._oldSeriesRenderers.isNotEmpty && _oldSeriesRenderer != null && - _oldSeriesRenderer._segments.isNotEmpty && - _oldSeriesRenderer._segments[0] is BubbleSegment && + _oldSeriesRenderer!._segments.isNotEmpty && + _oldSeriesRenderer!._segments[0] is BubbleSegment && _series.animationDuration > 0 && _oldPoint != null) { final BubbleSegment currentSegment = - _seriesRenderer._segments[currentSegmentIndex]; - final BubbleSegment oldSegment = - (currentSegment._oldSeriesRenderer._segments.length - 1 >= - currentSegmentIndex) - ? currentSegment._oldSeriesRenderer._segments[currentSegmentIndex] + _seriesRenderer._segments[currentSegmentIndex!] as BubbleSegment; + final BubbleSegment? oldSegment = + (currentSegment._oldSeriesRenderer!._segments.length - 1 >= + currentSegmentIndex!) + ? currentSegment._oldSeriesRenderer! + ._segments[currentSegmentIndex!] as BubbleSegment? : null; _animateBubbleSeries( canvas, @@ -162,14 +157,14 @@ class BubbleSegment extends ChartSegment { oldSegment?._size, animationFactor, _radius, - strokePaint, - fillPaint, + strokePaint!, + fillPaint!, _seriesRenderer); } else { canvas.drawCircle( - Offset(_centerX, _centerY), _radius * animationFactor, fillPaint); + Offset(_centerX, _centerY), _radius * animationFactor, fillPaint!); canvas.drawCircle( - Offset(_centerX, _centerY), _radius * animationFactor, strokePaint); + Offset(_centerX, _centerY), _radius * animationFactor, strokePaint!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/candle_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/candle_segment.dart index 96fc010c6..63d56b6b7 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/candle_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/candle_segment.dart @@ -5,11 +5,11 @@ part of charts; /// Generates the candle series points and has the [calculateSegmentPoints] override method /// used to customize the candle series segment point calculation. /// -/// Gets the path and fill color from the [series] to render the candle segment. +/// Gets the path and fill color from the `series` to render the candle segment. /// class CandleSegment extends ChartSegment { // ignore: unused_field - num _x, + late double _x, _low, _high, _highY, @@ -26,35 +26,36 @@ class CandleSegment extends ChartSegment { _bottomRectY, _bottomLineY; // ignore: unused_field - Path _path, _linePath; - Color _pointColorMapper; - @override - CartesianChartPoint _currentPoint; + late Path _path, _linePath; + Color? _pointColorMapper; + //ignore: prefer_final_fields - bool _isSolid = false, _isTransposed, _isBull = false, _showSameValue; - _ChartLocation _lowPoint, _highPoint, _centerLowPoint, _centerHighPoint; + late bool _isSolid = false, _isTransposed, _isBull = false, _showSameValue; + late _ChartLocation _lowPoint, _highPoint, _centerLowPoint, _centerHighPoint; - CandleSeries _candleSeries; + late CandleSeries _candleSeries; + late CandleSegment _currentSegment; + CandleSegment? _oldSegment; /// Gets the color of the series. @override Paint getFillPaint() { fillPaint = Paint() - ..color = _currentPoint.isEmpty != null && _currentPoint.isEmpty + ..color = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! ? _series.emptyPointSettings.color - : (_currentPoint.pointColorMapper ?? _color); + : (_currentPoint!.pointColorMapper ?? _color!); assert(_series.opacity >= 0, 'The opacity value of the candle series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the candle series should be less than or equal to 1.'); - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; - fillPaint.strokeWidth = _strokeWidth; - fillPaint.style = _isSolid ? PaintingStyle.fill : PaintingStyle.stroke; + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; + fillPaint!.strokeWidth = _strokeWidth!; + fillPaint!.style = _isSolid ? PaintingStyle.fill : PaintingStyle.stroke; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -62,13 +63,13 @@ class CandleSegment extends ChartSegment { Paint getStrokePaint() { final Paint _strokePaint = Paint(); if (_strokeColor != null) { - _strokePaint.color = _pointColorMapper ?? _strokeColor; + _strokePaint.color = _pointColorMapper ?? _strokeColor!; _strokePaint.color = (_series.opacity < 1 && _strokePaint.color != Colors.transparent) ? _strokePaint.color.withOpacity(_series.opacity) : _strokePaint.color; } - _strokePaint.strokeWidth = _strokeWidth; + _strokePaint.strokeWidth = _strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.round; _defaultStrokeColor = _strokePaint; @@ -78,14 +79,14 @@ class CandleSegment extends ChartSegment { /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _candleSeries = _series; - _isBull = _currentPoint.open < _currentPoint.close; + _candleSeries = _series as CandleSeries; + _isBull = _currentPoint!.open < _currentPoint!.close; _x = _high = _low = double.nan; - _isTransposed = _seriesRenderer._chartState._requireInvertedAxis; - _lowPoint = _currentPoint.lowPoint; - _highPoint = _currentPoint.highPoint; - _centerLowPoint = _currentPoint.centerLowPoint; - _centerHighPoint = _currentPoint.centerHighPoint; + _isTransposed = _seriesRenderer._chartState!._requireInvertedAxis; + _lowPoint = _currentPoint!.lowPoint!; + _highPoint = _currentPoint!.highPoint!; + _centerLowPoint = _currentPoint!.centerLowPoint!; + _centerHighPoint = _currentPoint!.centerHighPoint!; _x = _lowPoint.x; _low = _lowPoint.y; _high = _highPoint.y; @@ -93,13 +94,13 @@ class CandleSegment extends ChartSegment { _highY = _centerHighPoint.y; _centerLow = _centerLowPoint.x; _lowY = _centerLowPoint.y; - _openX = _currentPoint.openPoint.x; - _openY = _currentPoint.openPoint.y; - _closeX = _currentPoint.closePoint.x; - _closeY = _currentPoint.closePoint.y; + _openX = _currentPoint!.openPoint!.x; + _openY = _currentPoint!.openPoint!.y; + _closeX = _currentPoint!.closePoint!.x; + _closeY = _currentPoint!.closePoint!.y; _showSameValue = _candleSeries.showIndicationForSameValues && - (!_seriesRenderer._chartState._requireInvertedAxis + (!_seriesRenderer._chartState!._requireInvertedAxis ? _centerHighPoint.y == _centerLowPoint.y : _centerHighPoint.x == _centerLowPoint.x); @@ -145,13 +146,13 @@ class CandleSegment extends ChartSegment { void _drawLine(Canvas canvas) { canvas.drawLine(Offset(_centerHigh, _topRectY), - Offset(_centerHigh, _topLineY), fillPaint); + Offset(_centerHigh, _topLineY), fillPaint!); canvas.drawLine(Offset(_centerHigh, _bottomRectY), - Offset(_centerHigh, _bottomLineY), fillPaint); + Offset(_centerHigh, _bottomLineY), fillPaint!); } void _drawFillLine(Canvas canvas) { - final bool isOpen = _currentPoint.open > _currentPoint.close; + final bool isOpen = _currentPoint!.open > _currentPoint!.close; canvas.drawLine( Offset(_topRectY, _highY), Offset( @@ -160,7 +161,7 @@ class CandleSegment extends ChartSegment { .abs() * animationFactor), _highY), - fillPaint); + fillPaint!); canvas.drawLine( Offset(_bottomRectY, _highY), Offset( @@ -169,7 +170,13 @@ class CandleSegment extends ChartSegment { .abs() * animationFactor), _highY), - fillPaint); + fillPaint!); + } + + void _calculateCandlePositions(num openX, num closeX) { + _centersY = closeX + ((openX - closeX).abs() / 2); + _topRectY = _centersY + ((_centersY - openX).abs() * animationFactor); + _bottomRectY = _centersY - ((_centersY - closeX).abs() * animationFactor); } /// Draws segment in series bounds. @@ -177,17 +184,18 @@ class CandleSegment extends ChartSegment { void onPaint(Canvas canvas) { if (fillPaint != null && (_seriesRenderer._reAnimate || - !(_seriesRenderer._chartState._widgetNeedUpdate && - !_seriesRenderer._chartState._isLegendToggled))) { + !(_seriesRenderer._chartState!._widgetNeedUpdate && + !_seriesRenderer._chartState!._isLegendToggled))) { _path = Path(); _linePath = Path(); - if (!_isTransposed && _currentPoint.open > _currentPoint.close) { - final num temp = _closeY; + + if (!_isTransposed && _currentPoint!.open > _currentPoint!.close) { + final double temp = _closeY; _closeY = _openY; _openY = temp; } - if (_seriesRenderer._chartState._isLegendToggled) { + if (_seriesRenderer._chartState!._isLegendToggled) { animationFactor = 1; } _centersY = _closeY + ((_closeY - _openY).abs() / 2); @@ -206,22 +214,13 @@ class CandleSegment extends ChartSegment { : _topLineY; if (_isTransposed) { - if (_currentPoint.open > _currentPoint.close) { - _centersY = _closeX + ((_openX - _closeX).abs() / 2); - _topRectY = - _centersY + ((_centersY - _openX).abs() * animationFactor); - _bottomRectY = - _centersY - ((_centersY - _closeX).abs() * animationFactor); - } else { - _centersY = _openX + (_closeX - _openX).abs() / 2; - _topRectY = - _centersY + ((_centersY - _closeX).abs() * animationFactor); - _bottomRectY = - _centersY - ((_centersY - _openX).abs() * animationFactor); - } + _currentPoint!.open > _currentPoint!.close + ? _calculateCandlePositions(_openX, _closeX) + : _calculateCandlePositions(_closeX, _openX); + if (_showSameValue) { canvas.drawLine(Offset(_centerHighPoint.x, _centerHighPoint.y), - Offset(_centerLowPoint.x, _centerHighPoint.y), fillPaint); + Offset(_centerLowPoint.x, _centerHighPoint.y), fillPaint!); } else { _path.moveTo(_topRectY, _highY); _centerHigh < _closeX @@ -245,93 +244,80 @@ class CandleSegment extends ChartSegment { } _openX == _closeX ? canvas.drawLine( - Offset(_openX, _openY), Offset(_closeX, _closeY), fillPaint) + Offset(_openX, _openY), Offset(_closeX, _closeY), fillPaint!) : _drawRectPath(); } else { - if (_currentPoint.open > _currentPoint.close) { - final num temp = _closeY; - _closeY = _openY; - _openY = temp; - } _showSameValue ? canvas.drawLine(Offset(_centerHighPoint.x, _highPoint.y), - Offset(_centerHighPoint.x, _lowPoint.y), fillPaint) + Offset(_centerHighPoint.x, _lowPoint.y), fillPaint!) : _drawLine(canvas); _openY == _closeY ? canvas.drawLine( - Offset(_openX, _openY), Offset(_closeX, _closeY), fillPaint) + Offset(_openX, _openY), Offset(_closeX, _closeY), fillPaint!) : _drawRectPath(); } if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0 && - fillPaint.style != PaintingStyle.fill && + fillPaint!.style != PaintingStyle.fill && _series.animationDuration <= 0) { - _drawDashedLine(canvas, _series.dashArray, fillPaint, _path); + _drawDashedLine(canvas, _series.dashArray, fillPaint!, _path); } else { - canvas.drawPath(_path, fillPaint); - if (fillPaint.style == PaintingStyle.fill) { + canvas.drawPath(_path, fillPaint!); + if (fillPaint!.style == PaintingStyle.fill) { if (_isTransposed) { - if (_currentPoint.open > _currentPoint.close) { - _showSameValue - ? canvas.drawLine( - Offset(_centerHighPoint.x, _centerHighPoint.y), - Offset(_centerLowPoint.x, _centerHighPoint.y), - fillPaint) - : _drawFillLine(canvas); - } else { - _showSameValue - ? canvas.drawLine( - Offset(_centerHighPoint.x, _centerHighPoint.y), - Offset(_centerLowPoint.x, _centerHighPoint.y), - fillPaint) - : _drawFillLine(canvas); - } + _showSameValue + ? canvas.drawLine( + Offset(_centerHighPoint.x, _centerHighPoint.y), + Offset(_centerLowPoint.x, _centerHighPoint.y), + fillPaint!) + : _drawFillLine(canvas); } else { _showSameValue ? canvas.drawLine(Offset(_centerHighPoint.x, _highPoint.y), - Offset(_centerHighPoint.x, _lowPoint.y), fillPaint) + Offset(_centerHighPoint.x, _lowPoint.y), fillPaint!) : _drawLine(canvas); } } } - } else if (!_seriesRenderer._chartState._isLegendToggled) { - final CandleSegment currentSegment = - _seriesRenderer._segments[currentSegmentIndex]; - final CandleSegment oldSegment = !_seriesRenderer._reAnimate && - (currentSegment._oldSeriesRenderer != null && - currentSegment._oldSeriesRenderer._segments.isNotEmpty && - currentSegment._oldSeriesRenderer._segments[0] + } else if (!_seriesRenderer._chartState!._isLegendToggled) { + _currentSegment = + _seriesRenderer._segments[currentSegmentIndex!] as CandleSegment; + _oldSegment = !_seriesRenderer._reAnimate && + (_currentSegment._oldSeriesRenderer != null && + _currentSegment._oldSeriesRenderer!._segments.isNotEmpty && + _currentSegment._oldSeriesRenderer!._segments[0] is CandleSegment && - currentSegment._oldSeriesRenderer._segments.length - 1 >= - currentSegmentIndex) - ? currentSegment._oldSeriesRenderer._segments[currentSegmentIndex] + _currentSegment._oldSeriesRenderer!._segments.length - 1 >= + currentSegmentIndex!) + ? _currentSegment._oldSeriesRenderer!._segments[currentSegmentIndex!] + as CandleSegment? : null; _animateCandleSeries( _showSameValue, _high, _isTransposed, - _currentPoint.open, - _currentPoint.close, + _currentPoint!.open!.toDouble(), + _currentPoint!.close!.toDouble(), _lowY, _highY, - oldSegment?._lowY, - oldSegment?._highY, + _oldSegment?._lowY, + _oldSegment?._highY, _openX, _openY, _closeX, _closeY, _centerLow, _centerHigh, - oldSegment?._openX, - oldSegment?._openY, - oldSegment?._closeX, - oldSegment?._closeY, - oldSegment?._centerLow, - oldSegment?._centerHigh, + _oldSegment?._openX, + _oldSegment?._openY, + _oldSegment?._closeX, + _oldSegment?._closeY, + _oldSegment?._centerLow, + _oldSegment?._centerHigh, animationFactor, - fillPaint, + fillPaint!, canvas, _seriesRenderer); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/chart_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/chart_segment.dart index b5d40c325..16803500a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/chart_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/chart_segment.dart @@ -23,49 +23,52 @@ abstract class ChartSegment { void onPaint(Canvas canvas); ///Color of the segment - Color _color, _strokeColor; + Color? _color, _strokeColor; ///Border width of the segment - double _strokeWidth; + double? _strokeWidth; ///Fill paint of the segment - Paint fillPaint; + Paint? fillPaint; ///Stroke paint of the segment - Paint strokePaint; + Paint? strokePaint; ///Chart series - XyDataSeries _series, _oldSeries; + late XyDataSeries _series; + XyDataSeries? _oldSeries; ///Chart series renderer - CartesianSeriesRenderer _seriesRenderer, _oldSeriesRenderer; + late CartesianSeriesRenderer _seriesRenderer; + CartesianSeriesRenderer? _oldSeriesRenderer; ///Animation factor value - double animationFactor; + late double animationFactor; /// Rectangle of the segment - RRect _segmentRect; + RRect? _segmentRect; ///Current point offset value List points = []; /// Default fill color & stroke color - Paint _defaultFillColor, _defaultStrokeColor; + Paint? _defaultFillColor, _defaultStrokeColor; /// Current index value. - int currentSegmentIndex, _seriesIndex; + int? currentSegmentIndex, _oldSegmentIndex; + late int _seriesIndex; - CartesianChartPoint _currentPoint, _point, _oldPoint, _nextPoint; + CartesianChartPoint? _currentPoint, _point, _oldPoint, _nextPoint; /// Old series visibility property. - bool _oldSeriesVisible; + bool? _oldSeriesVisible; /// Old rect region. - Rect _oldRegion; + Rect? _oldRegion; /// Cartesian chart properties - SfCartesianChart _chart; + late SfCartesianChart _chart; /// Cartesian chart state properties - SfCartesianChartState _chartState; + late SfCartesianChartState _chartState; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/column_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/column_segment.dart index dd112c24b..33cf54119 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/column_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/column_segment.dart @@ -5,18 +5,18 @@ part of charts; /// This generates the column series points and has the [calculateSegmentPoints] override method /// used to customize the column series segment point calculation. /// -/// It gets the path, stroke color and fill color from the [series] to render the column segment. +/// It gets the path, stroke color and fill color from the `series` to render the column segment. /// class ColumnSegment extends ChartSegment { /// Render path. - Path _path; - RRect _trackRect; - @override - CartesianChartPoint _currentPoint; - Paint _trackerFillPaint, _trackerStrokePaint; + late Path _path; + late RRect _trackRect; + // @override + // CartesianChartPoint _currentPoint; + Paint? _trackerFillPaint, _trackerStrokePaint; /// Rectangle of the segment this could be used to render the segment while overriding this segment - RRect segmentRect; + late RRect segmentRect; /// Gets the color of the series. @override @@ -24,26 +24,26 @@ class ColumnSegment extends ChartSegment { /// Get and set the paint options for column series. if (_series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint.isEmpty == true + ..color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.color - : (_currentPoint.pointColorMapper ?? _color) + : (_currentPoint!.pointColorMapper ?? _color!) ..style = PaintingStyle.fill; } else { fillPaint = _getLinearGradientPaint( - _series.gradient, - _currentPoint.region, - _seriesRenderer._chartState._requireInvertedAxis); + _series.gradient!, + _currentPoint!.region!, + _seriesRenderer._chartState!._requireInvertedAxis); } assert(_series.opacity >= 0, 'The opacity value of the column series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the column series should be less than or equal to 1.'); - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -51,44 +51,44 @@ class ColumnSegment extends ChartSegment { Paint getStrokePaint() { strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint.isEmpty == true + ..strokeWidth = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderWidth - : _strokeWidth; + : _strokeWidth!; if (_series.borderGradient != null) { - strokePaint.shader = - _series.borderGradient.createShader(_currentPoint.region); + strokePaint!.shader = + _series.borderGradient!.createShader(_currentPoint!.region!); } else { - strokePaint.color = _currentPoint.isEmpty == true + strokePaint!.color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderColor - : _strokeColor; + : _strokeColor!; } _series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; + ? strokePaint!.color = Colors.transparent + : strokePaint!.color; _defaultStrokeColor = strokePaint; - return strokePaint; + return strokePaint!; } /// Method to get series tracker fill. Paint _getTrackerFillPaint() { - final ColumnSeries columnSeries = _series; + final ColumnSeries columnSeries = _series as ColumnSeries; _trackerFillPaint = Paint() ..color = columnSeries.trackColor ..style = PaintingStyle.fill; - return _trackerFillPaint; + return _trackerFillPaint!; } /// Method to getseries tracker stroke color. Paint _getTrackerStrokePaint() { - final ColumnSeries columnSeries = _series; + final ColumnSeries columnSeries = _series as ColumnSeries; _trackerStrokePaint = Paint() ..color = columnSeries.trackBorderColor ..strokeWidth = columnSeries.trackBorderWidth ..style = PaintingStyle.stroke; columnSeries.trackBorderWidth == 0 - ? _trackerStrokePaint.color = Colors.transparent - : _trackerStrokePaint.color; - return _trackerStrokePaint; + ? _trackerStrokePaint!.color = Colors.transparent + : _trackerStrokePaint!.color; + return _trackerStrokePaint!; } /// Calculates the rendering bounds of a segment. @@ -98,24 +98,24 @@ class ColumnSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - final ColumnSeries columnSeries = _series; + final ColumnSeries columnSeries = _series as ColumnSeries; if (_trackerFillPaint != null && columnSeries.isTrackVisible) { - _drawSegmentRect(canvas, _trackRect, _trackerFillPaint); + _drawSegmentRect(canvas, _trackRect, _trackerFillPaint!); } if (_trackerStrokePaint != null && columnSeries.isTrackVisible) { - _drawSegmentRect(canvas, _trackRect, _trackerStrokePaint); + _drawSegmentRect(canvas, _trackRect, _trackerStrokePaint!); } if (fillPaint != null) { - _drawSegmentRect(canvas, segmentRect, fillPaint); + _drawSegmentRect(canvas, segmentRect, fillPaint!); } if (strokePaint != null) { if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) { - _drawDashedLine(canvas, _series.dashArray, strokePaint, _path); + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); } else { - _drawSegmentRect(canvas, segmentRect, strokePaint); + _drawSegmentRect(canvas, segmentRect, strokePaint!); } } } @@ -128,9 +128,9 @@ class ColumnSegment extends ChartSegment { _seriesRenderer, paint, segmentRect, - _currentPoint.yValue, + _currentPoint!.yValue, animationFactor, - _oldPoint != null ? _oldPoint.region : _oldRegion, + _oldPoint != null ? _oldPoint!.region : _oldRegion, _oldPoint?.yValue, _oldSeriesVisible) : canvas.drawRRect(segmentRect, paint); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/fastline_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/fastline_segment.dart index 1c0bfaa8e..6f9d05585 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/fastline_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/fastline_segment.dart @@ -5,7 +5,7 @@ part of charts; /// This generates the fast line series points and has the [calculateSegmentPoints] method overrided to customize /// the fast line segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class FastLineSegment extends ChartSegment { /// Gets the color of the series. @override @@ -16,7 +16,7 @@ class FastLineSegment extends ChartSegment { assert(_series.opacity <= 1, 'The opacity value of the fast line series should be less than or equal to 1.'); if (_color != null) { - _fillPaint.color = _color.withOpacity(_series.opacity); + _fillPaint.color = _color!.withOpacity(_series.opacity); } _fillPaint.style = PaintingStyle.fill; _defaultFillColor = _fillPaint; @@ -35,14 +35,14 @@ class FastLineSegment extends ChartSegment { if (_strokeColor != null) { _strokePaint.color = (_series.opacity < 1 && _strokeColor != Colors.transparent) - ? _strokeColor.withOpacity(_series.opacity) - : _strokeColor; + ? _strokeColor!.withOpacity(_series.opacity) + : _strokeColor!; } } else { - _strokePaint.shader = _series.gradient - .createShader(_seriesRenderer._segmentPath.getBounds()); + _strokePaint.shader = _series.gradient! + .createShader(_seriesRenderer._segmentPath!.getBounds()); } - _strokePaint.strokeWidth = _strokeWidth; + _strokePaint.strokeWidth = _strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.round; _defaultStrokeColor = _strokePaint; @@ -53,9 +53,9 @@ class FastLineSegment extends ChartSegment { @override void onPaint(Canvas canvas) { _series.dashArray != null - ? _drawDashedLine(canvas, _series.dashArray, strokePaint, - _seriesRenderer._segmentPath) - : canvas.drawPath(_seriesRenderer._segmentPath, strokePaint); + ? _drawDashedLine(canvas, _series.dashArray, strokePaint!, + _seriesRenderer._segmentPath!) + : canvas.drawPath(_seriesRenderer._segmentPath!, strokePaint!); } /// Calculates the rendering bounds of a segment. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hilo_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hilo_segment.dart index b77d0f6e2..37037b01c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hilo_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hilo_segment.dart @@ -5,21 +5,22 @@ part of charts; /// Generates the Hilo series points and has the [calculateSegmentPoints] method overrided to customize /// the Hilo segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class HiloSegment extends ChartSegment { /// Current point X value //ignore: unused_field - num _x, _low, _high, _centerX, _highX, _lowX, _centerY, _highY, _lowY; - Color _pointColorMapper; + late double _x, _low, _high, _centerX, _highX, _lowX, _centerY, _highY, _lowY; + Color? _pointColorMapper; /// Stores _low point location - _ChartLocation _lowPoint, _highPoint; + late _ChartLocation _lowPoint, _highPoint; /// Render path. - Path _path; - bool _showSameValue, _isTransposed; - HiloSeries _hiloSeries; - HiloSegment _currentSegment, _oldSegment; + late Path _path; + late bool _showSameValue, _isTransposed; + late HiloSeries _hiloSeries; + late HiloSegment _currentSegment; + HiloSegment? _oldSegment; /// Gets the color of the series. @override @@ -31,9 +32,9 @@ class HiloSegment extends ChartSegment { 'The opacity value of the Hilo series must be less than or equal to 1.'); if (_color != null) { _fillPaint.color = - _pointColorMapper ?? _color.withOpacity(_series.opacity); + _pointColorMapper ?? _color!.withOpacity(_series.opacity); } - _fillPaint.strokeWidth = _strokeWidth; + _fillPaint.strokeWidth = _strokeWidth!; _fillPaint.style = PaintingStyle.fill; _defaultFillColor = _fillPaint; return _fillPaint; @@ -49,15 +50,15 @@ class HiloSegment extends ChartSegment { 'The opacity value of the Hilo series should be less than or equal to 1.'); if (_strokeColor != null) { _strokePaint.color = - _currentPoint.isEmpty != null && _currentPoint.isEmpty + _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! ? _series.emptyPointSettings.color - : _pointColorMapper ?? _strokeColor; + : _pointColorMapper ?? _strokeColor!; _strokePaint.color = (_series.opacity < 1 && _strokePaint.color != Colors.transparent) ? _strokePaint.color.withOpacity(_series.opacity) : _strokePaint.color; } - _strokePaint.strokeWidth = _strokeWidth; + _strokePaint.strokeWidth = _strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.round; _defaultStrokeColor = _strokePaint; @@ -67,12 +68,12 @@ class HiloSegment extends ChartSegment { /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _hiloSeries = _series; + _hiloSeries = _series as HiloSeries; _x = _high = _low = double.nan; - _lowPoint = _currentPoint.lowPoint; - _highPoint = _currentPoint.highPoint; + _lowPoint = _currentPoint!.lowPoint!; + _highPoint = _currentPoint!.highPoint!; - _isTransposed = _seriesRenderer._chartState._requireInvertedAxis; + _isTransposed = _seriesRenderer._chartState!._requireInvertedAxis; _x = _lowPoint.x; _low = _lowPoint.y; @@ -96,8 +97,8 @@ class HiloSegment extends ChartSegment { @override void onPaint(Canvas canvas) { if (_series.animationDuration > 0 && - !_seriesRenderer._chartState._isLegendToggled) { - if (!_seriesRenderer._chartState._widgetNeedUpdate || + !_seriesRenderer._chartState!._isLegendToggled) { + if (!_seriesRenderer._chartState!._widgetNeedUpdate || _seriesRenderer._reAnimate) { if (_isTransposed) { _lowX = _lowPoint.x; @@ -106,24 +107,26 @@ class HiloSegment extends ChartSegment { _highX = _centerX + ((_centerX - _highX).abs() * animationFactor); _lowX = _centerX - ((_lowX - _centerX).abs() * animationFactor); canvas.drawLine(Offset(_lowX, _lowPoint.y), - Offset(_highX, _highPoint.y), strokePaint); + Offset(_highX, _highPoint.y), strokePaint!); } else { _centerY = _high + ((_low - _high) / 2); _highY = _centerY - ((_centerY - _high) * animationFactor); _lowY = _centerY + ((_low - _centerY) * animationFactor); canvas.drawLine(Offset(_lowPoint.x, _highY), - Offset(_highPoint.x, _lowY), strokePaint); + Offset(_highPoint.x, _lowY), strokePaint!); } } else { - _currentSegment = _seriesRenderer._segments[currentSegmentIndex]; + _currentSegment = + _seriesRenderer._segments[currentSegmentIndex!] as HiloSegment; _oldSegment = !_seriesRenderer._reAnimate && (_currentSegment._oldSeriesRenderer != null && - _currentSegment._oldSeriesRenderer._segments.isNotEmpty && - _currentSegment._oldSeriesRenderer._segments[0] + _currentSegment._oldSeriesRenderer!._segments.isNotEmpty && + _currentSegment._oldSeriesRenderer!._segments[0] is HiloSegment && - _currentSegment._oldSeriesRenderer._segments.length - 1 >= - currentSegmentIndex) - ? _currentSegment._oldSeriesRenderer._segments[currentSegmentIndex] + _currentSegment._oldSeriesRenderer!._segments.length - 1 >= + currentSegmentIndex!) + ? _currentSegment._oldSeriesRenderer! + ._segments[currentSegmentIndex!] as HiloSegment? : null; _animateHiloSeries( _isTransposed, @@ -132,7 +135,7 @@ class HiloSegment extends ChartSegment { _oldSegment?._lowPoint, _oldSegment?._highPoint, animationFactor, - strokePaint, + strokePaint!, canvas, _seriesRenderer); } @@ -141,10 +144,10 @@ class HiloSegment extends ChartSegment { _path = Path(); _path.moveTo(_lowPoint.x, _high); _path.lineTo(_highPoint.x, _low); - _drawDashedLine(canvas, _series.dashArray, strokePaint, _path); + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); } else { canvas.drawLine(Offset(_lowPoint.x, _high), Offset(_highPoint.x, _low), - strokePaint); + strokePaint!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hiloopenclose_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hiloopenclose_segment.dart index ab7dd6988..e67237710 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hiloopenclose_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hiloopenclose_segment.dart @@ -5,10 +5,10 @@ part of charts; /// Generates the HiloOpenClose series points and has the [calculateSegmentPoints] method overrided to customize /// the HiloOpenClose segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class HiloOpenCloseSegment extends ChartSegment { //ignore: unused_field - num _x, + late double _x, _low, _high, _centerY, @@ -25,15 +25,16 @@ class HiloOpenCloseSegment extends ChartSegment { _lowY; ///Render path. - Path _path; + late Path _path; - Color _pointColorMapper; - @override - CartesianChartPoint _currentPoint; - _ChartLocation _centerLowPoint, _centerHighPoint, _lowPoint, _highPoint; - bool _showSameValue, _isTransposed, _isBull = false; - HiloOpenCloseSegment _currentSegment, _oldSegment; - HiloOpenCloseSeries _hiloOpenCloseSeries; + Color? _pointColorMapper; + // @override + // CartesianChartPoint _currentPoint; + late _ChartLocation _centerLowPoint, _centerHighPoint, _lowPoint, _highPoint; + late bool _showSameValue, _isTransposed, _isBull = false; + late HiloOpenCloseSegment _currentSegment; + HiloOpenCloseSegment? _oldSegment; + late HiloOpenCloseSeries _hiloOpenCloseSeries; /// Gets the color of the series. @override @@ -45,9 +46,9 @@ class HiloOpenCloseSegment extends ChartSegment { 'The opacity value of the Hilo open-close series should be less than or equal to 1.'); if (_color != null) { fillPaint.color = - _pointColorMapper ?? _color.withOpacity(_series.opacity); + _pointColorMapper ?? _color!.withOpacity(_series.opacity); } - fillPaint.strokeWidth = _strokeWidth; + fillPaint.strokeWidth = _strokeWidth!; fillPaint.style = PaintingStyle.fill; _defaultFillColor = fillPaint; return fillPaint; @@ -62,15 +63,16 @@ class HiloOpenCloseSegment extends ChartSegment { assert(_series.opacity <= 1, 'The opacity value of the Hilo open-close series should be less than or equal to 1.'); if (_strokeColor != null) { - strokePaint.color = _currentPoint.isEmpty != null && _currentPoint.isEmpty - ? _series.emptyPointSettings.color - : _pointColorMapper ?? _strokeColor; + strokePaint.color = + _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! + ? _series.emptyPointSettings.color + : _pointColorMapper ?? _strokeColor!; strokePaint.color = (_series.opacity < 1 && strokePaint.color != Colors.transparent) ? strokePaint.color.withOpacity(_series.opacity) : strokePaint.color; } - strokePaint.strokeWidth = _strokeWidth; + strokePaint.strokeWidth = _strokeWidth!; strokePaint.style = PaintingStyle.stroke; strokePaint.strokeCap = StrokeCap.round; _defaultStrokeColor = strokePaint; @@ -80,13 +82,13 @@ class HiloOpenCloseSegment extends ChartSegment { /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _hiloOpenCloseSeries = _series; - _isTransposed = _seriesRenderer._chartState._requireInvertedAxis; - _isBull = _currentPoint.open < _currentPoint.close; - _lowPoint = _currentPoint.lowPoint; - _highPoint = _currentPoint.highPoint; - _centerLowPoint = _currentPoint.centerLowPoint; - _centerHighPoint = _currentPoint.centerHighPoint; + _hiloOpenCloseSeries = _series as HiloOpenCloseSeries; + _isTransposed = _seriesRenderer._chartState!._requireInvertedAxis; + _isBull = _currentPoint!.open < _currentPoint!.close; + _lowPoint = _currentPoint!.lowPoint!; + _highPoint = _currentPoint!.highPoint!; + _centerLowPoint = _currentPoint!.centerLowPoint!; + _centerHighPoint = _currentPoint!.centerHighPoint!; _x = _lowX = _lowPoint.x; _low = _lowPoint.y; _high = _highPoint.y; @@ -95,10 +97,10 @@ class HiloOpenCloseSegment extends ChartSegment { _highY = _centerHighPoint.y; _centerLow = _centerLowPoint.x; _lowY = _centerLowPoint.y; - _openX = _currentPoint.openPoint.x; - _openY = _currentPoint.openPoint.y; - _closeX = _currentPoint.closePoint.x; - _closeY = _currentPoint.closePoint.y; + _openX = _currentPoint!.openPoint!.x; + _openY = _currentPoint!.openPoint!.y; + _closeX = _currentPoint!.closePoint!.x; + _closeY = _currentPoint!.closePoint!.y; _showSameValue = _hiloOpenCloseSeries.showIndicationForSameValues && (!_isTransposed @@ -123,17 +125,17 @@ class HiloOpenCloseSegment extends ChartSegment { /// Draws the _path between open and close values. void drawHiloOpenClosePath(Canvas canvas) { canvas.drawLine( - Offset(_centerHigh, _highY), Offset(_centerLow, _lowY), strokePaint); + Offset(_centerHigh, _highY), Offset(_centerLow, _lowY), strokePaint!); canvas.drawLine( Offset(_openX, _openY), Offset(_isTransposed ? _openX : _centerHigh, _isTransposed ? _highY : _openY), - strokePaint); + strokePaint!); canvas.drawLine( Offset(_closeX, _closeY), Offset(_isTransposed ? _closeX : _centerLow, _isTransposed ? _highY : _closeY), - strokePaint); + strokePaint!); } /// To draw dashed hilo open close path @@ -155,59 +157,62 @@ class HiloOpenCloseSegment extends ChartSegment { if (strokePaint != null) { _path = Path(); if (_series.animationDuration > 0 && - !_seriesRenderer._chartState._isLegendToggled) { - if (!_seriesRenderer._chartState._widgetNeedUpdate || + !_seriesRenderer._chartState!._isLegendToggled) { + if (!_seriesRenderer._chartState!._widgetNeedUpdate || _seriesRenderer._reAnimate) { if (_isTransposed) { _centerX = _highX + ((_lowX - _highX) / 2); _openX = _centerX - - ((_centerX - _currentPoint.openPoint.x) * animationFactor); + ((_centerX - _currentPoint!.openPoint!.x) * animationFactor); _closeX = _centerX + - ((_currentPoint.closePoint.x - _centerX) * animationFactor); + ((_currentPoint!.closePoint!.x - _centerX) * animationFactor); _highX = _centerX + ((_centerX - _highX).abs() * animationFactor); _lowX = _centerX - ((_lowX - _centerX).abs() * animationFactor); canvas.drawLine(Offset(_lowX, _centerLowPoint.y), - Offset(_highX, _centerHighPoint.y), strokePaint); + Offset(_highX, _centerHighPoint.y), strokePaint!); canvas.drawLine( - Offset(_openX, _openY), Offset(_openX, _highY), strokePaint); + Offset(_openX, _openY), Offset(_openX, _highY), strokePaint!); canvas.drawLine( - Offset(_closeX, _lowY), Offset(_closeX, _closeY), strokePaint); + Offset(_closeX, _lowY), Offset(_closeX, _closeY), strokePaint!); } else { _centerY = _high + ((_low - _high) / 2); _openY = _centerY - - ((_centerY - _currentPoint.openPoint.y) * animationFactor); + ((_centerY - _currentPoint!.openPoint!.y) * animationFactor); _closeY = _centerY + - ((_currentPoint.closePoint.y - _centerY) * animationFactor); + ((_currentPoint!.closePoint!.y - _centerY) * animationFactor); _highY = _centerY - ((_centerY - _high) * animationFactor); _lowY = _centerY + ((_low - _centerY) * animationFactor); canvas.drawLine(Offset(_centerHigh, _highY), - Offset(_centerLow, _lowY), strokePaint); + Offset(_centerLow, _lowY), strokePaint!); canvas.drawLine(Offset(_openX, _openY), Offset(_centerHigh, _openY), - strokePaint); + strokePaint!); canvas.drawLine(Offset(_centerLow, _closeY), - Offset(_closeX, _closeY), strokePaint); + Offset(_closeX, _closeY), strokePaint!); } } else { - _currentSegment = _seriesRenderer._segments[currentSegmentIndex]; + _currentSegment = _seriesRenderer._segments[currentSegmentIndex!] + as HiloOpenCloseSegment; _oldSegment = !_seriesRenderer._reAnimate && (_currentSegment._oldSeriesRenderer != null && - _currentSegment._oldSeriesRenderer._segments.isNotEmpty && - _currentSegment._oldSeriesRenderer._segments[0] + _currentSegment + ._oldSeriesRenderer!._segments.isNotEmpty && + _currentSegment._oldSeriesRenderer!._segments[0] is HiloOpenCloseSegment && - _currentSegment._oldSeriesRenderer._segments.length - 1 >= - currentSegmentIndex) - ? _currentSegment - ._oldSeriesRenderer._segments[currentSegmentIndex] + _currentSegment._oldSeriesRenderer!._segments.length - + 1 >= + currentSegmentIndex!) + ? _currentSegment._oldSeriesRenderer! + ._segments[currentSegmentIndex!] as HiloOpenCloseSegment? : null; _animateHiloOpenCloseSeries( _isTransposed, _isTransposed ? _lowPoint.x : _low, _isTransposed ? _highPoint.x : _high, _isTransposed - ? (_oldSegment != null ? _oldSegment._lowPoint.x : null) + ? (_oldSegment != null ? _oldSegment!._lowPoint.x : null) : _oldSegment?._low, _isTransposed - ? (_oldSegment != null ? _oldSegment._highPoint.x : null) + ? (_oldSegment != null ? _oldSegment!._highPoint.x : null) : _oldSegment?._high, _openX, _openY, @@ -220,21 +225,23 @@ class HiloOpenCloseSegment extends ChartSegment { _oldSegment?._closeX, _oldSegment?._closeY, _isTransposed - ? (_oldSegment != null ? _oldSegment._centerLowPoint.y : null) + ? (_oldSegment != null + ? _oldSegment!._centerLowPoint.y + : null) : _oldSegment?._centerLow, _isTransposed ? (_oldSegment != null - ? _oldSegment._centerHighPoint.y + ? _oldSegment!._centerHighPoint.y : null) : _oldSegment?._centerHigh, animationFactor, - strokePaint, + strokePaint!, canvas, _seriesRenderer); } } else { if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) { - _drawDashedLine(canvas, _series.dashArray, strokePaint, + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _drawDashedHiloOpenClosePath(canvas)); } else { drawHiloOpenClosePath(canvas); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/histogram_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/histogram_segment.dart index f308a2608..0a8991b60 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/histogram_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/histogram_segment.dart @@ -5,20 +5,20 @@ part of charts; /// This generates the column series points and has the [calculateSegmentPoints] override method /// used to customize the column series segment point calculation. /// -/// It gets the path, stroke color and fill color from the [series] to render the column segment. +/// It gets the path, stroke color and fill color from the `series` to render the column segment. /// class HistogramSegment extends ChartSegment { /// Render path. - Path _path; - RRect _trackRect; - @override - CartesianChartPoint _currentPoint; - Paint _trackerFillPaint, _trackerStrokePaint; + late Path _path; + late RRect _trackRect; + // @override + // CartesianChartPoint _currentPoint; + Paint? _trackerFillPaint, _trackerStrokePaint; //We are using `segmentRect` to draw the histogram segment in the series. //we can override this class and customize the column segment by getting `segmentRect`. /// Rectangle of the segment - RRect segmentRect; + late RRect segmentRect; /// Gets the color of the series. @override @@ -29,31 +29,29 @@ class HistogramSegment extends ChartSegment { if (_series.gradient == null) { if (_color != null) { fillPaint = Paint() - ..color = _currentPoint.isEmpty == true + ..color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.color - : ((_currentPoint.pointColorMapper != null) - ? _currentPoint.pointColorMapper - : _color) + : ((_currentPoint!.pointColorMapper != null) + ? _currentPoint!.pointColorMapper! + : _color!) ..style = PaintingStyle.fill; } } else { fillPaint = _getLinearGradientPaint( - _series.gradient, - _currentPoint.region, - _seriesRenderer._chartState._requireInvertedAxis); + _series.gradient!, + _currentPoint!.region!, + _seriesRenderer._chartState!._requireInvertedAxis); } assert(_series.opacity >= 0, 'The opacity value of the histogram series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the histogram series should be less than or equal to 1.'); - if (fillPaint.color != null) { - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; - } + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -61,46 +59,48 @@ class HistogramSegment extends ChartSegment { Paint getStrokePaint() { strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint.isEmpty == true + ..strokeWidth = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderWidth - : _strokeWidth; + : _strokeWidth!; if (_series.borderGradient != null) { - strokePaint.shader = - _series.borderGradient.createShader(_currentPoint.region); + strokePaint!.shader = + _series.borderGradient!.createShader(_currentPoint!.region!); } else if (_strokeColor != null) { - strokePaint.color = _currentPoint.isEmpty == true + strokePaint!.color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderColor - : _strokeColor; + : _strokeColor!; } _defaultStrokeColor = strokePaint; _series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; - return strokePaint; + ? strokePaint!.color = Colors.transparent + : strokePaint!.color; + return strokePaint!; } /// Method to get series tracker fill. Paint _getTrackerFillPaint() { - final HistogramSeries histogramSeries = _series; + final HistogramSeries histogramSeries = + _series as HistogramSeries; if (_color != null) { _trackerFillPaint = Paint() ..color = histogramSeries.trackColor ..style = PaintingStyle.fill; } - return _trackerFillPaint; + return _trackerFillPaint!; } /// Method to get series tracker stroke color. Paint _getTrackerStrokePaint() { - final HistogramSeries histogramSeries = _series; + final HistogramSeries histogramSeries = + _series as HistogramSeries; _trackerStrokePaint = Paint() ..color = histogramSeries.trackBorderColor ..strokeWidth = histogramSeries.trackBorderWidth ..style = PaintingStyle.stroke; histogramSeries.trackBorderWidth == 0 - ? _trackerStrokePaint.color = Colors.transparent - : _trackerStrokePaint.color; - return _trackerStrokePaint; + ? _trackerStrokePaint!.color = Colors.transparent + : _trackerStrokePaint!.color; + return _trackerStrokePaint!; } /// Calculates the rendering bounds of a segment. @@ -110,42 +110,44 @@ class HistogramSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - final HistogramSeries histogramSeries = _series; + final HistogramSeries histogramSeries = + _series as HistogramSeries; if (_trackerFillPaint != null && histogramSeries.isTrackVisible) { - _drawSegmentRect(_trackerFillPaint, canvas, _trackRect); + _drawSegmentRect(_trackerFillPaint!, canvas, _trackRect); } if (_trackerStrokePaint != null && histogramSeries.isTrackVisible) { - _drawSegmentRect(_trackerStrokePaint, canvas, _trackRect); + _drawSegmentRect(_trackerStrokePaint!, canvas, _trackRect); } if (fillPaint != null) { - _drawSegmentRect(fillPaint, canvas, segmentRect); + _drawSegmentRect(fillPaint!, canvas, segmentRect); } if (strokePaint != null) { if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) { - _drawDashedLine(canvas, _series.dashArray, strokePaint, _path); + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); } else { - _drawSegmentRect(strokePaint, canvas, segmentRect); + _drawSegmentRect(strokePaint!, canvas, segmentRect); } } } /// To draw the rect of a given segment void _drawSegmentRect(Paint getPaint, Canvas canvas, RRect getRect) { - ((_chartState._initialRender || + ((_chartState._initialRender! || _chartState._isLegendToggled || - !_chartState._oldSeriesKeys.contains(_series.key)) && + (_series.key != null && + _chartState._oldSeriesKeys.contains(_series.key))) && _series.animationDuration > 0) ? _animateRectSeries( canvas, _seriesRenderer, getPaint, getRect, - _currentPoint.yValue, + _currentPoint!.yValue, animationFactor, - _oldPoint != null ? _oldPoint.region : _oldRegion, + _oldPoint != null ? _oldPoint!.region : _oldRegion, _oldPoint?.yValue, _oldSeriesVisible) : canvas.drawRRect(getRect, getPaint); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart index 5d0a5aefa..01f29dd61 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart @@ -6,30 +6,33 @@ part of charts; /// Generates the line series points and has the [calculateSegmentPoints] override method /// used to customize the line series segment point calculation. /// -/// Gets the path, stroke color and fill color from the [series]. +/// Gets the path, stroke color and fill color from the `series`. /// /// _Note:_ This is only applicable for [SfCartesianChart]. class LineSegment extends ChartSegment { /// segment points & old segment points - num _x1, _y1, _x2, _y2, _oldX1, _oldY1, _oldX2, _oldY2; + late double _x1, _y1, _x2, _y2; + double? _oldX1, _oldY1, _oldX2, _oldY2; /// Render path - Path _path; + late Path _path; - Color _pointColorMapper; + Color? _pointColorMapper; - bool _needAnimate, _newlyAddedSegment = false; + late bool _needAnimate, _newlyAddedSegment = false; - Rect _axisClipRect; + late Rect _axisClipRect; - _ChartLocation _first, _second, _currentPointLocation, _nextPointLocation; + late _ChartLocation _first, + _second, + _currentPointLocation, + _nextPointLocation; - ChartAxisRenderer _xAxisRenderer, - _yAxisRenderer, - _oldXAxisRenderer, - _oldYAxisRenderer; + late ChartAxisRenderer _xAxisRenderer, _yAxisRenderer; + ChartAxisRenderer? _oldXAxisRenderer, _oldYAxisRenderer; - LineSegment _currentSegment, _oldSegment; + late LineSegment _currentSegment; + LineSegment? _oldSegment; /// Gets the color of the series. @override @@ -41,9 +44,9 @@ class LineSegment extends ChartSegment { 'The opacity value of the line series should be less than or equal to 1.'); if (_color != null) { _fillPaint.color = - _pointColorMapper ?? _color.withOpacity(_series.opacity); + _pointColorMapper ?? _color!.withOpacity(_series.opacity); } - _fillPaint.strokeWidth = _strokeWidth; + _fillPaint.strokeWidth = _strokeWidth!; _fillPaint.style = PaintingStyle.fill; _defaultFillColor = _fillPaint; return _fillPaint; @@ -58,13 +61,13 @@ class LineSegment extends ChartSegment { assert(_series.opacity <= 1, 'The opacity value of the line series should be less than or equal to 1.'); if (_strokeColor != null) { - strokePaint.color = _pointColorMapper ?? _strokeColor; + strokePaint.color = _pointColorMapper ?? _strokeColor!; strokePaint.color = (_series.opacity < 1 && strokePaint.color != Colors.transparent) ? strokePaint.color.withOpacity(_series.opacity) : strokePaint.color; } - strokePaint.strokeWidth = _strokeWidth; + strokePaint.strokeWidth = _strokeWidth!; strokePaint.style = PaintingStyle.stroke; strokePaint.strokeCap = StrokeCap.round; _defaultStrokeColor = strokePaint; @@ -74,15 +77,15 @@ class LineSegment extends ChartSegment { /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _xAxisRenderer = _seriesRenderer._xAxisRenderer; - _yAxisRenderer = _seriesRenderer._yAxisRenderer; + _xAxisRenderer = _seriesRenderer._xAxisRenderer!; + _yAxisRenderer = _seriesRenderer._yAxisRenderer!; _axisClipRect = _calculatePlotOffset( _chartState._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer._axis.plotOffset, - _seriesRenderer._yAxisRenderer._axis.plotOffset)); + Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, + _seriesRenderer._yAxisRenderer!._axis.plotOffset)); _currentPointLocation = _calculatePoint( - _currentPoint.xValue, - _currentPoint.yValue, + _currentPoint!.xValue, + _currentPoint!.yValue, _xAxisRenderer, _yAxisRenderer, _chartState._requireInvertedAxis, @@ -91,8 +94,8 @@ class LineSegment extends ChartSegment { _x1 = _currentPointLocation.x; _y1 = _currentPointLocation.y; _nextPointLocation = _calculatePoint( - _nextPoint.xValue, - _nextPoint.yValue, + _nextPoint!.xValue, + _nextPoint!.yValue, _xAxisRenderer, _yAxisRenderer, _chartState._requireInvertedAxis, @@ -105,33 +108,35 @@ class LineSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - num prevX, prevY; - LineSegment prevSegment; + double? prevX, prevY; + LineSegment? prevSegment; final Rect rect = _calculatePlotOffset( - _seriesRenderer._chartState._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer._axis.plotOffset, - _seriesRenderer._yAxisRenderer._axis.plotOffset)); + _seriesRenderer._chartState!._chartAxis._axisClipRect, + Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, + _seriesRenderer._yAxisRenderer!._axis.plotOffset)); _path = Path(); if (_series.animationDuration > 0 && !_seriesRenderer._reAnimate && _oldSeriesRenderer != null && - _oldSeriesRenderer._segments.isNotEmpty && - _oldSeriesRenderer._segments[0] is LineSegment && - _seriesRenderer._chartState._oldSeriesRenderers.length - 1 >= - _seriesRenderer._segments[currentSegmentIndex]._seriesIndex && - _seriesRenderer._segments[currentSegmentIndex]._oldSeriesRenderer + _oldSeriesRenderer!._segments.isNotEmpty && + _oldSeriesRenderer!._segments[0] is LineSegment && + _seriesRenderer._chartState!._oldSeriesRenderers.length - 1 >= + _seriesRenderer._segments[currentSegmentIndex!]._seriesIndex && + _seriesRenderer._segments[currentSegmentIndex!]._oldSeriesRenderer! ._segments.isNotEmpty) { - _currentSegment = _seriesRenderer._segments[currentSegmentIndex]; - _oldSegment = (_currentSegment._oldSeriesRenderer._segments.length - 1 >= - currentSegmentIndex) - ? _currentSegment._oldSeriesRenderer._segments[currentSegmentIndex] + _currentSegment = + _seriesRenderer._segments[currentSegmentIndex!] as LineSegment; + _oldSegment = (_currentSegment._oldSeriesRenderer!._segments.length - 1 >= + currentSegmentIndex!) + ? _currentSegment._oldSeriesRenderer!._segments[currentSegmentIndex!] + as LineSegment? : null; - if (currentSegmentIndex > 0) { + if (currentSegmentIndex! > 0) { prevSegment = - (_currentSegment._oldSeriesRenderer._segments.length - 1 >= - currentSegmentIndex - 1) - ? _currentSegment - ._oldSeriesRenderer._segments[currentSegmentIndex - 1] + (_currentSegment._oldSeriesRenderer!._segments.length - 1 >= + currentSegmentIndex! - 1) + ? _currentSegment._oldSeriesRenderer! + ._segments[currentSegmentIndex! - 1] as LineSegment? : null; } _oldX1 = _oldSegment?._x1; @@ -146,38 +151,37 @@ class LineSegment extends ChartSegment { _newlyAddedSegment = false; } if (_oldSegment != null && - (_oldX1.isNaN || _oldX2.isNaN) && - _seriesRenderer._chartState._oldAxisRenderers != null && - _seriesRenderer._chartState._oldAxisRenderers.isNotEmpty) { - _oldXAxisRenderer = _getOldAxisRenderer(_seriesRenderer._xAxisRenderer, - _seriesRenderer._chartState._oldAxisRenderers); - _oldYAxisRenderer = _getOldAxisRenderer(_seriesRenderer._yAxisRenderer, - _seriesRenderer._chartState._oldAxisRenderers); + (_oldX1!.isNaN || _oldX2!.isNaN) && + _seriesRenderer._chartState!._oldAxisRenderers.isNotEmpty) { + _oldXAxisRenderer = _getOldAxisRenderer(_seriesRenderer._xAxisRenderer!, + _seriesRenderer._chartState!._oldAxisRenderers); + _oldYAxisRenderer = _getOldAxisRenderer(_seriesRenderer._yAxisRenderer!, + _seriesRenderer._chartState!._oldAxisRenderers); if (_oldYAxisRenderer != null && _oldXAxisRenderer != null) { - _needAnimate = _oldYAxisRenderer._visibleRange.minimum != - _seriesRenderer._yAxisRenderer._visibleRange.minimum || - _oldYAxisRenderer._visibleRange.maximum != - _seriesRenderer._yAxisRenderer._visibleRange.maximum || - _oldXAxisRenderer._visibleRange.minimum != - _seriesRenderer._xAxisRenderer._visibleRange.minimum || - _oldXAxisRenderer._visibleRange.maximum != - _seriesRenderer._xAxisRenderer._visibleRange.maximum; + _needAnimate = _oldYAxisRenderer!._visibleRange!.minimum != + _seriesRenderer._yAxisRenderer!._visibleRange!.minimum || + _oldYAxisRenderer!._visibleRange!.maximum != + _seriesRenderer._yAxisRenderer!._visibleRange!.maximum || + _oldXAxisRenderer!._visibleRange!.minimum != + _seriesRenderer._xAxisRenderer!._visibleRange!.minimum || + _oldXAxisRenderer!._visibleRange!.maximum != + _seriesRenderer._xAxisRenderer!._visibleRange!.maximum; } if (_needAnimate) { _first = _calculatePoint( - _currentPoint.xValue, - _currentPoint.yValue, - _oldXAxisRenderer, - _oldYAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _currentPoint!.xValue, + _currentPoint!.yValue, + _oldXAxisRenderer!, + _oldYAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); _second = _calculatePoint( - _nextPoint.xValue, - _nextPoint.yValue, - _oldXAxisRenderer, - _oldYAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _nextPoint!.xValue, + _nextPoint!.yValue, + _oldXAxisRenderer!, + _oldYAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); _oldX1 = _first.x; @@ -190,7 +194,7 @@ class LineSegment extends ChartSegment { _animateToPoint( canvas, _seriesRenderer, - strokePaint, + strokePaint!, animationFactor, _currentSegment._x1, _currentSegment._y1, @@ -202,7 +206,7 @@ class LineSegment extends ChartSegment { _animateLineTypeSeries( canvas, _seriesRenderer, - strokePaint, + strokePaint!, animationFactor, _currentSegment._x1, _currentSegment._y1, @@ -218,9 +222,9 @@ class LineSegment extends ChartSegment { if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) { _path.moveTo(_x1, _y1); _path.lineTo(_x2, _y2); - _drawDashedLine(canvas, _series.dashArray, strokePaint, _path); + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); } else { - canvas.drawLine(Offset(_x1, _y1), Offset(_x2, _y2), strokePaint); + canvas.drawLine(Offset(_x1, _y1), Offset(_x2, _y2), strokePaint!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_area_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_area_segment.dart index 3050f2159..a84e055b3 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_area_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_area_segment.dart @@ -5,12 +5,13 @@ part of charts; /// Generates the range area series points and has the [calculateSegmentPoints] method overrided to customize /// the range area segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class RangeAreaSegment extends ChartSegment { - Path _path, _borderPath; + late Path _path; + Path? _borderPath; ///For storing the path in RangeAreaBorderMode.excludeSides mode - Rect _pathRect; + Rect? _pathRect; /// Gets the color of the series. @override @@ -18,27 +19,25 @@ class RangeAreaSegment extends ChartSegment { fillPaint = Paint(); if (_series.gradient == null) { if (_color != null) { - fillPaint.color = _color; - fillPaint.style = PaintingStyle.fill; + fillPaint!.color = _color!; + fillPaint!.style = PaintingStyle.fill; } } else { fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient, _pathRect, - _seriesRenderer._chartState._requireInvertedAxis) + ? _getLinearGradientPaint(_series.gradient!, _pathRect!, + _seriesRenderer._chartState!._requireInvertedAxis) : fillPaint; } assert(_series.opacity >= 0, 'The opacity value of the range area series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the range area series should be less than or equal to 1.'); - if (fillPaint.color != null) { - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; - } + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -50,7 +49,7 @@ class RangeAreaSegment extends ChartSegment { ..strokeWidth = _series.borderWidth; if (_series.borderGradient != null && _borderPath != null) { _strokePaint.shader = - _series.borderGradient.createShader(_borderPath.getBounds()); + _series.borderGradient!.createShader(_borderPath!.getBounds()); } else if (_strokeColor != null) { _strokePaint.color = _series.borderColor; } @@ -69,19 +68,20 @@ class RangeAreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - final RangeAreaSeries _series = this._series; + final RangeAreaSeries _series = + this._series as RangeAreaSeries; _pathRect = _path.getBounds(); canvas.drawPath( - _path, (_series.gradient == null) ? fillPaint : getFillPaint()); + _path, (_series.gradient == null) ? fillPaint! : getFillPaint()); strokePaint = getStrokePaint(); - if (strokePaint.color != Colors.transparent) { + if (strokePaint!.color != Colors.transparent) { _drawDashedLine( canvas, _series.dashArray, - strokePaint, + strokePaint!, _series.borderDrawMode == RangeAreaBorderMode.all ? _path - : _borderPath); + : _borderPath!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_column_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_column_segment.dart index 46e8f5214..4b8b49f34 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_column_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_column_segment.dart @@ -5,22 +5,22 @@ part of charts; /// Generates the range column series points and has the [calculateSegmentPoints] method overrided to customize /// the range column segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class RangeColumnSegment extends ChartSegment { //ignore: unused_field - num _x1, _low1, _high1; + late double _x1, _low1, _high1; ///Path of the series - Path _path; - RRect _trackRect; - @override - CartesianChartPoint _currentPoint; - Paint _trackerFillPaint, _trackerStrokePaint; + late Path _path; + late RRect _trackRect; + // @override + // CartesianChartPoint _currentPoint; + Paint? _trackerFillPaint, _trackerStrokePaint; //We are using `segmentRect` to draw the histogram segment in the series. //we can override this class and customize the column segment by getting `segmentRect`. /// Rectangle of the segment - RRect segmentRect; + late RRect segmentRect; /// Gets the color of the series. @override @@ -30,30 +30,28 @@ class RangeColumnSegment extends ChartSegment { /// Get and set the paint options for range column series. if (_series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint.isEmpty == true + ..color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.color - : ((hasPointColor && _currentPoint.pointColorMapper != null) - ? _currentPoint.pointColorMapper - : _color) + : ((hasPointColor && _currentPoint!.pointColorMapper != null) + ? _currentPoint!.pointColorMapper + : _color)! ..style = PaintingStyle.fill; } else { fillPaint = _getLinearGradientPaint( - _series.gradient, - _currentPoint.region, - _seriesRenderer._chartState._requireInvertedAxis); + _series.gradient!, + _currentPoint!.region!, + _seriesRenderer._chartState!._requireInvertedAxis); } assert(_series.opacity >= 0, 'The opacity value of the range column series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the range column series should be less than or equal to 1.'); - if (fillPaint.color != null) { - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; - } - _defaultFillColor = fillPaint; - return fillPaint; + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; + _defaultFillColor = fillPaint!; + return fillPaint!; } /// Gets the border color of the series. @@ -61,46 +59,48 @@ class RangeColumnSegment extends ChartSegment { Paint getStrokePaint() { strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint.isEmpty == true + ..strokeWidth = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderWidth - : _strokeWidth; + : _strokeWidth!; _defaultStrokeColor = strokePaint; if (_series.borderGradient != null) { - strokePaint.shader = - _series.borderGradient.createShader(_currentPoint.region); + strokePaint!.shader = + _series.borderGradient!.createShader(_currentPoint!.region!); } else { - strokePaint.color = _currentPoint.isEmpty == true + strokePaint!.color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderColor - : _strokeColor; + : _strokeColor!; } _series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; - return strokePaint; + ? strokePaint!.color = Colors.transparent + : strokePaint!.color; + return strokePaint!; } /// Method to get series tracker fill. Paint _getTrackerFillPaint() { - final RangeColumnSeries _series = this._series; + final RangeColumnSeries _series = + this._series as RangeColumnSeries; _trackerFillPaint = Paint() ..color = _series.trackColor ..style = PaintingStyle.fill; - return _trackerFillPaint; + return _trackerFillPaint!; } /// Method to get series tracker stroke color. Paint _getTrackerStrokePaint() { - final RangeColumnSeries _series = this._series; + final RangeColumnSeries _series = + this._series as RangeColumnSeries; _trackerStrokePaint = Paint() ..color = _series.trackBorderColor ..strokeWidth = _series.trackBorderWidth ..style = PaintingStyle.stroke; _series.trackBorderWidth == 0 - ? _trackerStrokePaint.color = Colors.transparent - : _trackerStrokePaint.color; - return _trackerStrokePaint; + ? _trackerStrokePaint!.color = Colors.transparent + : _trackerStrokePaint!.color; + return _trackerStrokePaint!; } /// Calculates the rendering bounds of a segment. @@ -110,42 +110,43 @@ class RangeColumnSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - final RangeColumnSeries _series = this._series; + final RangeColumnSeries _series = + this._series as RangeColumnSeries; if (_trackerFillPaint != null && _series.isTrackVisible) { - canvas.drawRRect(_trackRect, _trackerFillPaint); + canvas.drawRRect(_trackRect, _trackerFillPaint!); } if (_trackerStrokePaint != null && _series.isTrackVisible) { - canvas.drawRRect(_trackRect, _trackerStrokePaint); + canvas.drawRRect(_trackRect, _trackerStrokePaint!); } if (fillPaint != null) { (_series.animationDuration > 0 && - !_seriesRenderer._chartState._isLegendToggled) + !_seriesRenderer._chartState!._isLegendToggled) ? _animateRangeColumn( canvas, _seriesRenderer, - fillPaint, + fillPaint!, segmentRect, - _oldPoint != null ? _oldPoint.region : _oldRegion, + _oldPoint != null ? _oldPoint!.region : _oldRegion, animationFactor) - : canvas.drawRRect(segmentRect, fillPaint); + : canvas.drawRRect(segmentRect, fillPaint!); } if (strokePaint != null) { if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) { - _drawDashedLine(canvas, _series.dashArray, strokePaint, _path); + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); } else { (_series.animationDuration > 0 && - !_seriesRenderer._chartState._isLegendToggled) + !_seriesRenderer._chartState!._isLegendToggled) ? _animateRangeColumn( canvas, _seriesRenderer, - strokePaint, + strokePaint!, segmentRect, - _oldPoint != null ? _oldPoint.region : _oldRegion, + _oldPoint != null ? _oldPoint!.region : _oldRegion, animationFactor) - : canvas.drawRRect(segmentRect, strokePaint); + : canvas.drawRRect(segmentRect, strokePaint!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/scatter_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/scatter_segment.dart index a88fd6025..9f87cf945 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/scatter_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/scatter_segment.dart @@ -5,10 +5,10 @@ part of charts; /// Generates the scatter series points and has the [calculateSegmentPoints] method overrided to customize /// the scatter segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class ScatterSegment extends ChartSegment { - @override - CartesianChartPoint _currentPoint; + // @override + // CartesianChartPoint _currentPoint; /// Gets the color of the series. @override @@ -17,48 +17,47 @@ class ScatterSegment extends ChartSegment { if (_series.gradient == null) { if (_color != null) { fillPaint = Paint() - ..color = _currentPoint.isEmpty == true + ..color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.color - : ((hasPointColor && _currentPoint.pointColorMapper != null) - ? _currentPoint.pointColorMapper - : _color) + : ((hasPointColor && _currentPoint!.pointColorMapper != null) + ? _currentPoint!.pointColorMapper + : _color)! ..style = PaintingStyle.fill; } } else { fillPaint = _getLinearGradientPaint( - _series.gradient, - _currentPoint.region, - _seriesRenderer._chartState._requireInvertedAxis); + _series.gradient!, + _currentPoint!.region!, + _seriesRenderer._chartState!._requireInvertedAxis); } _defaultFillColor = fillPaint; assert(_series.opacity >= 0, 'The opacity value of the the scatter series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the the scatter series should be less than or equal to 1.'); - if (fillPaint.color != null) { - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; - } - return fillPaint; + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; + return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { - final ScatterSeriesRenderer _scatterRenderer = _seriesRenderer; + final ScatterSeriesRenderer _scatterRenderer = + _seriesRenderer as ScatterSeriesRenderer; final Paint strokePaint = Paint() - ..color = _currentPoint.isEmpty == true + ..color = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderColor : _series.markerSettings.isVisible ? _series.markerSettings.borderColor ?? - _seriesRenderer._seriesColor - : _strokeColor + _seriesRenderer._seriesColor! + : _strokeColor! ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint.isEmpty == true + ..strokeWidth = _currentPoint!.isEmpty == true ? _series.emptyPointSettings.borderWidth - : _strokeWidth; + : _strokeWidth!; (strokePaint.strokeWidth == 0 && !_scatterRenderer._isLineType) ? strokePaint.color = Colors.transparent : strokePaint.color; @@ -75,16 +74,16 @@ class ScatterSegment extends ChartSegment { void onPaint(Canvas canvas) { if (fillPaint != null) { _series.animationDuration > 0 && - !_seriesRenderer._chartState._isLegendToggled - ? _animateScatterSeries(_seriesRenderer, _point, _oldPoint, - animationFactor, canvas, fillPaint, strokePaint) + !_seriesRenderer._chartState!._isLegendToggled + ? _animateScatterSeries(_seriesRenderer, _point!, _oldPoint, + animationFactor, canvas, fillPaint!, strokePaint!) : _seriesRenderer.drawDataMarker( - currentSegmentIndex, + currentSegmentIndex!, canvas, - fillPaint, - strokePaint, - _point.markerPoint.x, - _point.markerPoint.y, + fillPaint!, + strokePaint!, + _point!.markerPoint!.x, + _point!.markerPoint!.y, _seriesRenderer); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_area_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_area_segment.dart index 845b27dab..a411d78ed 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_area_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_area_segment.dart @@ -5,10 +5,10 @@ part of charts; /// Generates the spline area series points and has the [calculateSegmentPoints] method overrided to customize /// the spline area segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class SplineAreaSegment extends ChartSegment { - Path _path, _strokePath; - Rect _pathRect; + late Path _path, _strokePath; + Rect? _pathRect; /// Gets the color of the series. @override @@ -16,27 +16,25 @@ class SplineAreaSegment extends ChartSegment { fillPaint = Paint(); if (_series.gradient == null) { if (_color != null) { - fillPaint.color = _color; - fillPaint.style = PaintingStyle.fill; + fillPaint!.color = _color!; + fillPaint!.style = PaintingStyle.fill; } } else { fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient, _pathRect, - _seriesRenderer._chartState._requireInvertedAxis) + ? _getLinearGradientPaint(_series.gradient!, _pathRect!, + _seriesRenderer._chartState!._requireInvertedAxis) : fillPaint; } assert(_series.opacity >= 0, 'The opacity value of the spline area series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the spline area series should be less than or equal to 1.'); - if (fillPaint.color != null) { - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; - } + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -48,7 +46,7 @@ class SplineAreaSegment extends ChartSegment { ..strokeWidth = _series.borderWidth; if (_series.borderGradient != null) { strokePaint.shader = - _series.borderGradient.createShader(_strokePath.getBounds()); + _series.borderGradient!.createShader(_strokePath.getBounds()); } else if (_strokeColor != null) { strokePaint.color = _series.borderColor; } @@ -69,7 +67,7 @@ class SplineAreaSegment extends ChartSegment { void onPaint(Canvas canvas) { _pathRect = _path.getBounds(); canvas.drawPath( - _path, (_series.gradient == null) ? fillPaint : getFillPaint()); - _drawDashedLine(canvas, _series.dashArray, strokePaint, _strokePath); + _path, (_series.gradient == null) ? fillPaint! : getFillPaint()); + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _strokePath); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_range_area_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_range_area_segment.dart index 25913453c..811e71bc6 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_range_area_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_range_area_segment.dart @@ -5,13 +5,13 @@ part of charts; /// Generates the spline area series points and has the [calculateSegmentPoints] method overrided to customize /// the spline area segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class SplineRangeAreaSegment extends ChartSegment { /// Path _borderPath; - Path _path, _strokePath; + late Path _path, _strokePath; ///For storing the path in RangeAreaBorderMode.excludeSides mode - Rect _pathRect; + Rect? _pathRect; /// Gets the color of the series. @override @@ -19,27 +19,25 @@ class SplineRangeAreaSegment extends ChartSegment { fillPaint = Paint(); if (_series.gradient == null) { if (_color != null) { - fillPaint.color = _color; - fillPaint.style = PaintingStyle.fill; + fillPaint!.color = _color!; + fillPaint!.style = PaintingStyle.fill; } } else { fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient, _pathRect, - _seriesRenderer._chartState._requireInvertedAxis) + ? _getLinearGradientPaint(_series.gradient!, _pathRect!, + _seriesRenderer._chartState!._requireInvertedAxis) : fillPaint; } assert(_series.opacity >= 0, 'The opacity value of the spline range area series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the spline range area series should be less than or equal to 1.'); - if (fillPaint.color != null) { - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; - } + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -51,7 +49,7 @@ class SplineRangeAreaSegment extends ChartSegment { ..strokeWidth = _series.borderWidth; if (_series.borderGradient != null) { strokePaint.shader = - _series.borderGradient.createShader(_strokePath.getBounds()); + _series.borderGradient!.createShader(_strokePath.getBounds()); } else if (_strokeColor != null) { strokePaint.color = _series.borderColor; } @@ -71,15 +69,15 @@ class SplineRangeAreaSegment extends ChartSegment { @override void onPaint(Canvas canvas) { final SplineRangeAreaSeries splineRangeAreaSeries = - _seriesRenderer._series; + _seriesRenderer._series as SplineRangeAreaSeries; _pathRect = _path.getBounds(); canvas.drawPath( - _path, (_series.gradient == null) ? fillPaint : getFillPaint()); - if (strokePaint.color != Colors.transparent) { + _path, (_series.gradient == null) ? fillPaint! : getFillPaint()); + if (strokePaint!.color != Colors.transparent) { _drawDashedLine( canvas, _series.dashArray, - strokePaint, + strokePaint!, splineRangeAreaSeries.borderDrawMode == RangeAreaBorderMode.all ? _path : _strokePath); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_segment.dart index 01974b0d4..8915c2607 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_segment.dart @@ -5,45 +5,34 @@ part of charts; /// Generates the spline series points and has the [calculateSegmentPoints] method overrided to customize /// the spline segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class SplineSegment extends ChartSegment { - num _x1, - _y1, - _x2, - _y2, - _oldX1, - _oldY1, - _oldX2, - _oldY2, - _oldX3, - _oldY3, - _oldX4, - _oldY4; + late double _x1, _y1, _x2, _y2; + double? _oldX1, _oldY1, _oldX2, _oldY2, _oldX3, _oldY3, _oldX4, _oldY4; /// Start point X value - double startControlX; + double? startControlX; /// Start point Y value - double startControlY; + double? startControlY; /// End point X value - double endControlX; + double? endControlX; /// End point Y value - double endControlY; + double? endControlY; - @override - CartesianChartPoint _currentPoint, _nextPoint; + // @override + // CartesianChartPoint _currentPoint, _nextPoint; - Color _pointColorMapper; - _ChartLocation _currentPointLocation, _nextPointLocation; - ChartAxisRenderer _xAxisRenderer, - _yAxisRenderer, - _oldXAxisRenderer, - _oldYAxisRenderer; - Rect _axisClipRect; - SplineSegment _currentSegment, _oldSegment; - bool _needAnimate; + Color? _pointColorMapper; + late _ChartLocation _currentPointLocation, _nextPointLocation; + late ChartAxisRenderer _xAxisRenderer, _yAxisRenderer; + ChartAxisRenderer? _oldXAxisRenderer, _oldYAxisRenderer; + late Rect _axisClipRect; + late SplineSegment _currentSegment; + SplineSegment? _oldSegment; + late bool _needAnimate; /// Gets the color of the series. @override @@ -54,9 +43,9 @@ class SplineSegment extends ChartSegment { assert(_series.opacity <= 1, 'The opacity value of the spline series should be less than or equal to 1.'); if (_strokeColor != null) { - _fillPaint.color = _strokeColor.withOpacity(_series.opacity); + _fillPaint.color = _strokeColor!.withOpacity(_series.opacity); } - _fillPaint.strokeWidth = _strokeWidth; + _fillPaint.strokeWidth = _strokeWidth!; _fillPaint.style = PaintingStyle.stroke; _defaultFillColor = _fillPaint; return _fillPaint; @@ -72,13 +61,13 @@ class SplineSegment extends ChartSegment { 'The opacity value of the spline series should be less than or equal to 1.'); if (_strokeColor != null) { _strokePaint.color = - _pointColorMapper ?? _strokeColor.withOpacity(_series.opacity); + _pointColorMapper ?? _strokeColor!.withOpacity(_series.opacity); _strokePaint.color = (_series.opacity < 1 && _strokePaint.color != Colors.transparent) ? _strokePaint.color.withOpacity(_series.opacity) : _strokePaint.color; } - _strokePaint.strokeWidth = _strokeWidth; + _strokePaint.strokeWidth = _strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.round; _defaultStrokeColor = _strokePaint; @@ -88,15 +77,15 @@ class SplineSegment extends ChartSegment { /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _xAxisRenderer = _seriesRenderer._xAxisRenderer; - _yAxisRenderer = _seriesRenderer._yAxisRenderer; + _xAxisRenderer = _seriesRenderer._xAxisRenderer!; + _yAxisRenderer = _seriesRenderer._yAxisRenderer!; _axisClipRect = _calculatePlotOffset( _chartState._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer._axis.plotOffset, - _seriesRenderer._yAxisRenderer._axis.plotOffset)); + Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, + _seriesRenderer._yAxisRenderer!._axis.plotOffset)); _currentPointLocation = _calculatePoint( - _currentPoint.xValue, - _currentPoint.yValue, + _currentPoint!.xValue, + _currentPoint!.yValue, _xAxisRenderer, _yAxisRenderer, _chartState._requireInvertedAxis, @@ -105,8 +94,8 @@ class SplineSegment extends ChartSegment { _x1 = _currentPointLocation.x; _y1 = _currentPointLocation.y; _nextPointLocation = _calculatePoint( - _nextPoint.xValue, - _nextPoint.yValue, + _nextPoint!.xValue, + _nextPoint!.yValue, _xAxisRenderer, _yAxisRenderer, _chartState._requireInvertedAxis, @@ -115,47 +104,48 @@ class SplineSegment extends ChartSegment { _x2 = _nextPointLocation.x; _y2 = _nextPointLocation.y; - startControlX = _currentPoint.startControl.x; - startControlY = _currentPoint.startControl.y; - endControlX = _currentPoint.endControl.x; - endControlY = _currentPoint.endControl.y; + startControlX = _currentPoint!.startControl!.x; + startControlY = _currentPoint!.startControl!.y; + endControlX = _currentPoint!.endControl!.x; + endControlY = _currentPoint!.endControl!.y; } /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { if (_seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = _seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _seriesRenderer._segments[currentSegmentIndex], + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _seriesRenderer._segments[currentSegmentIndex!], _seriesRenderer._chart); } final Rect rect = _calculatePlotOffset( - _seriesRenderer._chartState._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer._axis.plotOffset, - _seriesRenderer._yAxisRenderer._axis.plotOffset)); + _seriesRenderer._chartState!._chartAxis._axisClipRect, + Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, + _seriesRenderer._yAxisRenderer!._axis.plotOffset)); /// Draw spline series if (_series.animationDuration > 0 && !_seriesRenderer._reAnimate && - _seriesRenderer._chartState._widgetNeedUpdate && - !_seriesRenderer._chartState._isLegendToggled && - _seriesRenderer._chartState._oldSeriesRenderers != null && - _seriesRenderer._chartState._oldSeriesRenderers.isNotEmpty && + _seriesRenderer._chartState!._widgetNeedUpdate && + !_seriesRenderer._chartState!._isLegendToggled && + _seriesRenderer._chartState!._oldSeriesRenderers.isNotEmpty && _oldSeries != null && - _oldSeriesRenderer._segments.isNotEmpty && - _oldSeriesRenderer._segments[0] is SplineSegment && - _seriesRenderer._chartState._oldSeriesRenderers.length - 1 >= - _seriesRenderer._segments[currentSegmentIndex]._seriesIndex && - _seriesRenderer._segments[currentSegmentIndex]._oldSeriesRenderer + _oldSeriesRenderer!._segments.isNotEmpty && + _oldSeriesRenderer!._segments[0] is SplineSegment && + _seriesRenderer._chartState!._oldSeriesRenderers.length - 1 >= + _seriesRenderer._segments[currentSegmentIndex!]._seriesIndex && + _seriesRenderer._segments[currentSegmentIndex!]._oldSeriesRenderer! ._segments.isNotEmpty && - _currentPoint.isGap != true && - _nextPoint.isGap != true) { - _currentSegment = _seriesRenderer._segments[currentSegmentIndex]; - _oldSegment = (_currentSegment._oldSeriesRenderer._segments.length - 1 >= - currentSegmentIndex) - ? _currentSegment._oldSeriesRenderer._segments[currentSegmentIndex] + _currentPoint!.isGap != true && + _nextPoint!.isGap != true) { + _currentSegment = + _seriesRenderer._segments[currentSegmentIndex!] as SplineSegment; + _oldSegment = (_currentSegment._oldSeriesRenderer!._segments.length - 1 >= + currentSegmentIndex!) + ? _currentSegment._oldSeriesRenderer!._segments[currentSegmentIndex!] + as SplineSegment? : null; _oldX1 = _oldSegment?._x1; _oldY1 = _oldSegment?._y1; @@ -167,61 +157,60 @@ class SplineSegment extends ChartSegment { _oldY4 = _oldSegment?.endControlY; if (_oldSegment != null && - (_oldX1.isNaN || _oldX2.isNaN) && - _seriesRenderer._chartState._oldAxisRenderers != null && - _seriesRenderer._chartState._oldAxisRenderers.isNotEmpty) { + (_oldX1!.isNaN || _oldX2!.isNaN) && + _seriesRenderer._chartState!._oldAxisRenderers.isNotEmpty) { _ChartLocation _oldPoint; - _oldXAxisRenderer = _getOldAxisRenderer(_seriesRenderer._xAxisRenderer, - _seriesRenderer._chartState._oldAxisRenderers); - _oldYAxisRenderer = _getOldAxisRenderer(_seriesRenderer._yAxisRenderer, - _seriesRenderer._chartState._oldAxisRenderers); + _oldXAxisRenderer = _getOldAxisRenderer(_seriesRenderer._xAxisRenderer!, + _seriesRenderer._chartState!._oldAxisRenderers); + _oldYAxisRenderer = _getOldAxisRenderer(_seriesRenderer._yAxisRenderer!, + _seriesRenderer._chartState!._oldAxisRenderers); if (_oldYAxisRenderer != null && _oldXAxisRenderer != null) { - _needAnimate = _oldYAxisRenderer._visibleRange.minimum != - _seriesRenderer._yAxisRenderer._visibleRange.minimum || - _oldYAxisRenderer._visibleRange.maximum != - _seriesRenderer._yAxisRenderer._visibleRange.maximum || - _oldXAxisRenderer._visibleRange.minimum != - _seriesRenderer._xAxisRenderer._visibleRange.minimum || - _oldXAxisRenderer._visibleRange.maximum != - _seriesRenderer._xAxisRenderer._visibleRange.maximum; + _needAnimate = _oldYAxisRenderer!._visibleRange!.minimum != + _seriesRenderer._yAxisRenderer!._visibleRange!.minimum || + _oldYAxisRenderer!._visibleRange!.maximum != + _seriesRenderer._yAxisRenderer!._visibleRange!.maximum || + _oldXAxisRenderer!._visibleRange!.minimum != + _seriesRenderer._xAxisRenderer!._visibleRange!.minimum || + _oldXAxisRenderer!._visibleRange!.maximum != + _seriesRenderer._xAxisRenderer!._visibleRange!.maximum; } if (_needAnimate) { _oldPoint = _calculatePoint( - _currentPoint.xValue, - _currentPoint.yValue, - _oldXAxisRenderer, - _oldYAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _currentPoint!.xValue, + _currentPoint!.yValue, + _oldXAxisRenderer!, + _oldYAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); _oldX1 = _oldPoint.x; _oldY1 = _oldPoint.y; _oldPoint = _calculatePoint( - _nextPoint.xValue, - _nextPoint.xValue, - _oldXAxisRenderer, - _oldYAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _nextPoint!.xValue, + _nextPoint!.xValue, + _oldXAxisRenderer!, + _oldYAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); _oldX2 = _oldPoint.x; _oldY2 = _oldPoint.y; _oldPoint = _calculatePoint( - startControlX, - startControlY, - _oldXAxisRenderer, - _oldYAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + startControlX!, + startControlY!, + _oldXAxisRenderer!, + _oldYAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); _oldX3 = _oldPoint.x; _oldY3 = _oldPoint.y; _oldPoint = _calculatePoint( - endControlX, - endControlY, - _oldXAxisRenderer, - _oldYAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + endControlX!, + endControlY!, + _oldXAxisRenderer!, + _oldYAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); _oldX4 = _oldPoint.x; @@ -231,7 +220,7 @@ class SplineSegment extends ChartSegment { _animateLineTypeSeries( canvas, _seriesRenderer, - strokePaint, + strokePaint!, animationFactor, _currentSegment._x1, _currentSegment._y1, @@ -241,8 +230,8 @@ class SplineSegment extends ChartSegment { _oldY1, _oldX2, _oldY2, - _currentSegment?.startControlX, - _currentSegment?.startControlY, + _currentSegment.startControlX, + _currentSegment.startControlY, _oldX3, _oldY3, _currentSegment.endControlX, @@ -253,10 +242,10 @@ class SplineSegment extends ChartSegment { } else { final Path path = Path(); path.moveTo(_x1, _y1); - if (_currentPoint.isGap != true && _nextPoint.isGap != true) { - path.cubicTo( - startControlX, startControlY, endControlX, endControlY, _x2, _y2); - _drawDashedLine(canvas, _series.dashArray, strokePaint, path); + if (_currentPoint!.isGap != true && _nextPoint!.isGap != true) { + path.cubicTo(startControlX!, startControlY!, endControlX!, endControlY!, + _x2, _y2); + _drawDashedLine(canvas, _series.dashArray, strokePaint!, path); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_area_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_area_segment.dart index 8a6872062..61c4d3b71 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_area_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_area_segment.dart @@ -5,10 +5,11 @@ part of charts; /// Generates the stacked area series points and has the [calculateSegmentPoints] method overrided to customize /// the stacked area segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class StackedAreaSegment extends ChartSegment { - Rect _pathRect; - Path _path, _strokePath; + Rect? _pathRect; + late Path _path; + Path? _strokePath; /// Gets the color of the series. @override @@ -16,48 +17,46 @@ class StackedAreaSegment extends ChartSegment { fillPaint = Paint(); if (_series.gradient == null) { if (_color != null) { - fillPaint.color = _color; - fillPaint.style = PaintingStyle.fill; + fillPaint!.color = _color!; + fillPaint!.style = PaintingStyle.fill; } } else { fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient, _pathRect, - _seriesRenderer._chartState._requireInvertedAxis) + ? _getLinearGradientPaint(_series.gradient!, _pathRect!, + _seriesRenderer._chartState!._requireInvertedAxis) : fillPaint; } assert(_series.opacity >= 0, 'The opacity value of the stacked area series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the stacked area series should be less than or equal to 1.'); - if (fillPaint.color != null) { - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; - } + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { strokePaint = Paint(); - strokePaint + strokePaint! ..style = PaintingStyle.stroke ..strokeWidth = _series.borderWidth; if (_series.borderGradient != null && _strokePath != null) { - strokePaint.shader = - _series.borderGradient.createShader(_strokePath.getBounds()); + strokePaint!.shader = + _series.borderGradient!.createShader(_strokePath!.getBounds()); } else if (_strokeColor != null) { - strokePaint.color = _series.borderColor; + strokePaint!.color = _series.borderColor; } _series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; - strokePaint.strokeCap = StrokeCap.round; + ? strokePaint!.color = Colors.transparent + : strokePaint!.color; + strokePaint!.strokeCap = StrokeCap.round; _defaultStrokeColor = strokePaint; - return strokePaint; + return strokePaint!; } /// Calculates the rendering bounds of a segment. @@ -67,7 +66,7 @@ class StackedAreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _renderStackedAreaSeries(_seriesRenderer, fillPaint, strokePaint, canvas, - _seriesIndex, getFillPaint(), _path, _strokePath); + _renderStackedAreaSeries(_seriesRenderer, fillPaint!, strokePaint!, canvas, + _seriesIndex, getFillPaint(), _path, _strokePath!); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_bar_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_bar_segment.dart index 684b3e1d4..a4ee1549c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_bar_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_bar_segment.dart @@ -5,21 +5,21 @@ part of charts; /// Generates the stacked bar series points and has the [calculateSegmentPoints] method overrided to customize /// the stacked bar segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class StackedBarSegment extends ChartSegment { /// Stacked values - double stackValues; - @override - CartesianChartPoint _currentPoint; + late double stackValues; + // @override + // CartesianChartPoint _currentPoint; /// Render path - Path _path; - RRect _trackRect; - Paint _trackerFillPaint, _trackerStrokePaint; - StackedBarSeries _stackedBarSeries; + late Path _path; + late RRect _trackRect; + Paint? _trackerFillPaint, _trackerStrokePaint; + late StackedBarSeries _stackedBarSeries; /// Rectangle of the segment this could be used to render the segment while overriding this segment - RRect segmentRect; + late RRect segmentRect; /// Gets the color of the series. @override @@ -27,26 +27,26 @@ class StackedBarSegment extends ChartSegment { /// Get and set the paint options for column series. if (_series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint.isEmpty != null && _currentPoint.isEmpty + ..color = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! ? _series.emptyPointSettings.color - : (_currentPoint.pointColorMapper ?? _color) + : (_currentPoint!.pointColorMapper ?? _color!) ..style = PaintingStyle.fill; } else { fillPaint = _getLinearGradientPaint( - _series.gradient, - _currentPoint.region, - _seriesRenderer._chartState._requireInvertedAxis); + _series.gradient!, + _currentPoint!.region!, + _seriesRenderer._chartState!._requireInvertedAxis); } assert(_series.opacity >= 0, 'The opacity value of the stacked bar series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the stacked bar series should be less than or equal to 1.'); - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -54,46 +54,46 @@ class StackedBarSegment extends ChartSegment { Paint getStrokePaint() { strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint.isEmpty != null && _currentPoint.isEmpty + ..strokeWidth = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! ? _series.emptyPointSettings.borderWidth - : _strokeWidth; + : _strokeWidth!; if (_series.borderGradient != null) { - strokePaint.shader = - _series.borderGradient.createShader(_currentPoint.region); + strokePaint!.shader = + _series.borderGradient!.createShader(_currentPoint!.region!); } else if (_strokeColor != null) { - strokePaint.color = _currentPoint.isEmpty != null && _currentPoint.isEmpty - ? _series.emptyPointSettings.borderColor - : _strokeColor; + strokePaint!.color = + _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! + ? _series.emptyPointSettings.borderColor + : _strokeColor!; } _defaultStrokeColor = strokePaint; _series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; - return strokePaint; + ? strokePaint!.color = Colors.transparent + : strokePaint!.color; + return strokePaint!; } /// Method to get series tracker fill. Paint _getTrackerFillPaint() { - final StackedBarSeries columnSeries = _series; - if (columnSeries.trackColor != null) { - _trackerFillPaint = Paint() - ..color = columnSeries.trackColor - ..style = PaintingStyle.fill; - } - return _trackerFillPaint; + final StackedBarSeries columnSeries = + _series as StackedBarSeries; + _trackerFillPaint = Paint() + ..color = columnSeries.trackColor + ..style = PaintingStyle.fill; + return _trackerFillPaint!; } /// Method to get _series tracker stroke color. Paint _getTrackerStrokePaint() { - _stackedBarSeries = _series; + _stackedBarSeries = _series as StackedBarSeries; _trackerStrokePaint = Paint() ..color = _stackedBarSeries.trackBorderColor ..strokeWidth = _stackedBarSeries.trackBorderWidth ..style = PaintingStyle.stroke; _stackedBarSeries.trackBorderWidth == 0 - ? _trackerStrokePaint.color = Colors.transparent - : _trackerStrokePaint.color; - return _trackerStrokePaint; + ? _trackerStrokePaint!.color = Colors.transparent + : _trackerStrokePaint!.color; + return _trackerStrokePaint!; } /// Calculates the rendering bounds of a segment. @@ -103,13 +103,13 @@ class StackedBarSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _stackedBarSeries = _series; + _stackedBarSeries = _series as StackedBarSeries; if (_trackerFillPaint != null && _stackedBarSeries.isTrackVisible) { - canvas.drawRRect(_trackRect, _trackerFillPaint); + canvas.drawRRect(_trackRect, _trackerFillPaint!); } if (_trackerStrokePaint != null && _stackedBarSeries.isTrackVisible) { - canvas.drawRRect(_trackRect, _trackerStrokePaint); + canvas.drawRRect(_trackRect, _trackerStrokePaint!); } _renderStackingRectSeries( @@ -120,7 +120,7 @@ class StackedBarSegment extends ChartSegment { _seriesRenderer, canvas, segmentRect, - _currentPoint, - currentSegmentIndex); + _currentPoint!, + currentSegmentIndex!); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_column_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_column_segment.dart index dc462f65c..0c18eddfe 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_column_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_column_segment.dart @@ -5,23 +5,23 @@ part of charts; /// Generates the stacked column series points and has the [calculateSegmentPoints] method overrided to customize /// the stacked column segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class StackedColumnSegment extends ChartSegment { /// Stack values. - double stackValues; - @override - CartesianChartPoint _currentPoint; + late double stackValues; + // @override + // CartesianChartPoint _currentPoint; /// Rendering path. - Path _path; - RRect _trackRect; - Paint _trackerFillPaint, _trackerStrokePaint; - StackedColumnSeries _stackedColumnSeries; + late Path _path; + late RRect _trackRect; + Paint? _trackerFillPaint, _trackerStrokePaint; + late StackedColumnSeries _stackedColumnSeries; //We are using `segmentRect` to draw the histogram segment in the series. //we can override this class and customize the column segment by getting `segmentRect`. /// Rectangle of the segment - RRect segmentRect; + late RRect segmentRect; /// Gets the color of the series. @override @@ -29,26 +29,26 @@ class StackedColumnSegment extends ChartSegment { /// Get and set the paint options for column _series. if (_series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint.isEmpty != null && _currentPoint.isEmpty + ..color = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! ? _series.emptyPointSettings.color - : (_currentPoint.pointColorMapper ?? _color) + : (_currentPoint!.pointColorMapper ?? _color!) ..style = PaintingStyle.fill; } else { fillPaint = _getLinearGradientPaint( - _series.gradient, - _currentPoint.region, - _seriesRenderer._chartState._requireInvertedAxis); + _series.gradient!, + _currentPoint!.region!, + _seriesRenderer._chartState!._requireInvertedAxis); } assert(_series.opacity >= 0, 'The opacity value of the stacked column series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the stacked column series should be less than or equal to 1.'); - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -56,46 +56,45 @@ class StackedColumnSegment extends ChartSegment { Paint getStrokePaint() { strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint.isEmpty != null && _currentPoint.isEmpty + ..strokeWidth = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! ? _series.emptyPointSettings.borderWidth - : _strokeWidth; + : _strokeWidth!; if (_series.borderGradient != null) { - strokePaint.shader = - _series.borderGradient.createShader(_currentPoint.region); + strokePaint!.shader = + _series.borderGradient!.createShader(_currentPoint!.region!); } else if (_strokeColor != null) { - strokePaint.color = _currentPoint.isEmpty != null && _currentPoint.isEmpty - ? _series.emptyPointSettings.borderColor - : _strokeColor; + strokePaint!.color = + _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! + ? _series.emptyPointSettings.borderColor + : _strokeColor!; } _defaultStrokeColor = strokePaint; _series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; - return strokePaint; + ? strokePaint!.color = Colors.transparent + : strokePaint!.color; + return strokePaint!; } /// Method to get series tracker fill. Paint _getTrackerFillPaint() { - _stackedColumnSeries = _series; - if (_stackedColumnSeries.trackColor != null) { - _trackerFillPaint = Paint() - ..color = _stackedColumnSeries.trackColor - ..style = PaintingStyle.fill; - } - return _trackerFillPaint; + _stackedColumnSeries = _series as StackedColumnSeries; + _trackerFillPaint = Paint() + ..color = _stackedColumnSeries.trackColor + ..style = PaintingStyle.fill; + return _trackerFillPaint!; } /// Method to get series tracker stroke color. Paint _getTrackerStrokePaint() { - _stackedColumnSeries = _series; + _stackedColumnSeries = _series as StackedColumnSeries; _trackerStrokePaint = Paint() ..color = _stackedColumnSeries.trackBorderColor ..strokeWidth = _stackedColumnSeries.trackBorderWidth ..style = PaintingStyle.stroke; _stackedColumnSeries.trackBorderWidth == 0 - ? _trackerStrokePaint.color = Colors.transparent - : _trackerStrokePaint.color; - return _trackerStrokePaint; + ? _trackerStrokePaint!.color = Colors.transparent + : _trackerStrokePaint!.color; + return _trackerStrokePaint!; } /// Calculates the rendering bounds of a segment. @@ -105,12 +104,12 @@ class StackedColumnSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _stackedColumnSeries = _series; + _stackedColumnSeries = _series as StackedColumnSeries; if (_trackerFillPaint != null && _stackedColumnSeries.isTrackVisible) { - canvas.drawRRect(_trackRect, _trackerFillPaint); + canvas.drawRRect(_trackRect, _trackerFillPaint!); } if (_trackerStrokePaint != null && _stackedColumnSeries.isTrackVisible) { - canvas.drawRRect(_trackRect, _trackerStrokePaint); + canvas.drawRRect(_trackRect, _trackerStrokePaint!); } _renderStackingRectSeries( fillPaint, @@ -120,7 +119,7 @@ class StackedColumnSegment extends ChartSegment { _seriesRenderer, canvas, segmentRect, - _currentPoint, - currentSegmentIndex); + _currentPoint!, + currentSegmentIndex!); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_line_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_line_segment.dart index 9eec53c8e..5329e6f56 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_line_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_line_segment.dart @@ -5,9 +5,9 @@ part of charts; /// Generates the stacked line100 series points and has the [calculateSegmentPoints] method overrided to customize /// the stacked line100 segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class StackedLineSegment extends ChartSegment { - num _x1, + late double _x1, _y1, _x2, _y2, @@ -15,7 +15,7 @@ class StackedLineSegment extends ChartSegment { _nextCummulativePos, _currentCummulativeValue, _nextCummulativeValue; - Color _pointColorMapper; + Color? _pointColorMapper; /// Gets the color of the series. @override @@ -27,9 +27,9 @@ class StackedLineSegment extends ChartSegment { 'The opacity value of the stacked line series should be less than or equal to 1.'); if (_color != null) { _fillPaint.color = - _pointColorMapper ?? _color.withOpacity(_series.opacity); + _pointColorMapper ?? _color!.withOpacity(_series.opacity); } - _fillPaint.strokeWidth = _strokeWidth; + _fillPaint.strokeWidth = _strokeWidth!; _fillPaint.style = PaintingStyle.fill; _defaultFillColor = _fillPaint; return _fillPaint; @@ -44,13 +44,13 @@ class StackedLineSegment extends ChartSegment { assert(_series.opacity <= 1, 'The opacity value of the stacked line series should be less than or equal to 1.'); if (_strokeColor != null) { - _strokePaint.color = _pointColorMapper ?? _strokeColor; + _strokePaint.color = _pointColorMapper ?? _strokeColor!; _strokePaint.color = (_series.opacity < 1 && _strokePaint.color != Colors.transparent) ? _strokePaint.color.withOpacity(_series.opacity) : _strokePaint.color; } - _strokePaint.strokeWidth = _strokeWidth; + _strokePaint.strokeWidth = _strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.round; _defaultStrokeColor = _strokePaint; @@ -61,41 +61,41 @@ class StackedLineSegment extends ChartSegment { @override void calculateSegmentPoints() { final Rect rect = _calculatePlotOffset( - _seriesRenderer._chartState._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer._axis.plotOffset, - _seriesRenderer._yAxisRenderer._axis.plotOffset)); + _seriesRenderer._chartState!._chartAxis._axisClipRect, + Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, + _seriesRenderer._yAxisRenderer!._axis.plotOffset)); final _ChartLocation currentChartPoint = _calculatePoint( - _currentPoint.xValue, + _currentPoint!.xValue, _currentCummulativePos, - _seriesRenderer._xAxisRenderer, - _seriesRenderer._yAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._xAxisRenderer!, + _seriesRenderer._yAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); final _ChartLocation _nextLocation = _calculatePoint( - _nextPoint.xValue, + _nextPoint!.xValue, _nextCummulativePos, - _seriesRenderer._xAxisRenderer, - _seriesRenderer._yAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._xAxisRenderer!, + _seriesRenderer._yAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); final _ChartLocation currentCummulativePoint = _calculatePoint( - _currentPoint.xValue, + _currentPoint!.xValue, _currentCummulativePos, - _seriesRenderer._xAxisRenderer, - _seriesRenderer._yAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._xAxisRenderer!, + _seriesRenderer._yAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); final _ChartLocation nextCummulativePoint = _calculatePoint( - _nextPoint.xValue, + _nextPoint!.xValue, _nextCummulativePos, - _seriesRenderer._xAxisRenderer, - _seriesRenderer._yAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._xAxisRenderer!, + _seriesRenderer._yAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); @@ -110,6 +110,7 @@ class StackedLineSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _renderStackedLineSeries(_series, canvas, strokePaint, _x1, _y1, _x2, _y2); + _renderStackedLineSeries(_series as _StackedSeriesBase, canvas, + strokePaint!, _x1, _y1, _x2, _y2); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedarea100_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedarea100_segment.dart index 536e044a4..ba5e9c726 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedarea100_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedarea100_segment.dart @@ -5,10 +5,11 @@ part of charts; /// Generates the stacked area100 series points and has the [calculateSegmentPoints] method overrided to customize /// the stacked area100 segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class StackedArea100Segment extends ChartSegment { - Rect _pathRect; - Path _path, _strokePath; + Rect? _pathRect; + late Path _path; + Path? _strokePath; /// Gets the color of the series. @override @@ -16,27 +17,25 @@ class StackedArea100Segment extends ChartSegment { fillPaint = Paint(); if (_series.gradient == null) { if (_color != null) { - fillPaint.color = _color; - fillPaint.style = PaintingStyle.fill; + fillPaint!.color = _color!; + fillPaint!.style = PaintingStyle.fill; } } else { fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient, _pathRect, - _seriesRenderer._chartState._requireInvertedAxis) + ? _getLinearGradientPaint(_series.gradient!, _pathRect!, + _seriesRenderer._chartState!._requireInvertedAxis) : fillPaint; } assert(_series.opacity >= 0, 'The opacity value of the stacked area 100 series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the stacked area 100 series should be less than or equal to 1.'); - if (fillPaint.color != null) { - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; - } + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -48,7 +47,7 @@ class StackedArea100Segment extends ChartSegment { ..strokeWidth = _series.borderWidth; if (_series.borderGradient != null && _strokePath != null) { strokePaint.shader = - _series.borderGradient.createShader(_strokePath.getBounds()); + _series.borderGradient!.createShader(_strokePath!.getBounds()); } else if (_strokeColor != null) { strokePaint.color = _series.borderColor; } @@ -67,7 +66,7 @@ class StackedArea100Segment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _renderStackedAreaSeries(_seriesRenderer, fillPaint, strokePaint, canvas, - _seriesIndex, getFillPaint(), _path, _strokePath); + _renderStackedAreaSeries(_seriesRenderer, fillPaint!, strokePaint!, canvas, + _seriesIndex, getFillPaint(), _path, _strokePath!); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedbar100_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedbar100_segment.dart index 92b3ec2f7..70d132c80 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedbar100_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedbar100_segment.dart @@ -5,20 +5,20 @@ part of charts; /// Generates the stacked bar100 series points and has the [calculateSegmentPoints] method overrided to customize /// the stacked bar100 segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class StackedBar100Segment extends ChartSegment { /// staked value of the segment - double stackValues; - @override - CartesianChartPoint _currentPoint; + late double stackValues; + // @override + // CartesianChartPoint _currentPoint; ///Render path. - Path _path; + late Path _path; //We are using `segmentRect` to draw the histogram segment in the series. //we can override this class and customize the column segment by getting `segmentRect`. ///Rectangle of the segment - RRect segmentRect; + late RRect segmentRect; /// Gets the color of the series. @override @@ -26,26 +26,26 @@ class StackedBar100Segment extends ChartSegment { /// Get and set the paint options for column series. if (_series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint.isEmpty != null && _currentPoint.isEmpty + ..color = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! ? _series.emptyPointSettings.color - : (_currentPoint.pointColorMapper ?? _color) + : (_currentPoint!.pointColorMapper ?? _color!) ..style = PaintingStyle.fill; } else { fillPaint = _getLinearGradientPaint( - _series.gradient, - _currentPoint.region, - _seriesRenderer._chartState._requireInvertedAxis); + _series.gradient!, + _currentPoint!.region!, + _seriesRenderer._chartState!._requireInvertedAxis); } assert(_series.opacity >= 0, 'The opacity value of the stacked bar 100 series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the stacked bar 100 series should be less than or equal to 1.'); - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -53,22 +53,23 @@ class StackedBar100Segment extends ChartSegment { Paint getStrokePaint() { strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint.isEmpty != null && _currentPoint.isEmpty + ..strokeWidth = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! ? _series.emptyPointSettings.borderWidth - : _strokeWidth; + : _strokeWidth!; if (_series.borderGradient != null) { - strokePaint.shader = - _series.borderGradient.createShader(_currentPoint.region); + strokePaint!.shader = + _series.borderGradient!.createShader(_currentPoint!.region!); } else if (_strokeColor != null) { - strokePaint.color = _currentPoint.isEmpty != null && _currentPoint.isEmpty - ? _series.emptyPointSettings.borderColor - : _strokeColor; + strokePaint!.color = + _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! + ? _series.emptyPointSettings.borderColor + : _strokeColor!; } _defaultStrokeColor = strokePaint; _series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; - return strokePaint; + ? strokePaint!.color = Colors.transparent + : strokePaint!.color; + return strokePaint!; } /// Calculates the rendering bounds of a segment. @@ -86,7 +87,7 @@ class StackedBar100Segment extends ChartSegment { _seriesRenderer, canvas, segmentRect, - _currentPoint, - currentSegmentIndex); + _currentPoint!, + currentSegmentIndex!); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedcolumn100_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedcolumn100_segment.dart index 5f98db5e5..102a4f725 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedcolumn100_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedcolumn100_segment.dart @@ -5,20 +5,20 @@ part of charts; /// Generates the stacked column100 series points and has the [calculateSegmentPoints] method overrided to customize /// the stacked column100 segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class StackedColumn100Segment extends ChartSegment { /// Stacked value. - double stackValues; - @override - CartesianChartPoint _currentPoint; + late double stackValues; + // @override + // CartesianChartPoint _currentPoint; /// Rendering path. - Path _path; + late Path _path; //We are using `segmentRect` to draw the histogram segment in the series. //we can override this class and customize the column segment by getting `segmentRect`. /// Rectangle of the segment - RRect segmentRect; + late RRect segmentRect; /// Gets the color of the series. @override @@ -26,26 +26,26 @@ class StackedColumn100Segment extends ChartSegment { /// Get and set the paint options for stackedcolumn100 series. if (_series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint.isEmpty != null && _currentPoint.isEmpty + ..color = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! ? _series.emptyPointSettings.color - : (_currentPoint.pointColorMapper ?? _color) + : (_currentPoint!.pointColorMapper ?? _color!) ..style = PaintingStyle.fill; } else { fillPaint = _getLinearGradientPaint( - _series.gradient, - _currentPoint.region, - _seriesRenderer._chartState._requireInvertedAxis); + _series.gradient!, + _currentPoint!.region!, + _seriesRenderer._chartState!._requireInvertedAxis); } assert(_series.opacity >= 0, 'The opacity value of the stacked column100 series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the stacked column100 series should be less than or equal to 1.'); - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -53,22 +53,23 @@ class StackedColumn100Segment extends ChartSegment { Paint getStrokePaint() { strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint.isEmpty != null && _currentPoint.isEmpty + ..strokeWidth = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! ? _series.emptyPointSettings.borderWidth - : _strokeWidth; + : _strokeWidth!; if (_series.borderGradient != null) { - strokePaint.shader = - _series.borderGradient.createShader(_currentPoint.region); + strokePaint!.shader = + _series.borderGradient!.createShader(_currentPoint!.region!); } else if (_strokeColor != null) { - strokePaint.color = _currentPoint.isEmpty != null && _currentPoint.isEmpty - ? _series.emptyPointSettings.borderColor - : _strokeColor; + strokePaint!.color = + _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! + ? _series.emptyPointSettings.borderColor + : _strokeColor!; } _defaultStrokeColor = strokePaint; _series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; - return strokePaint; + ? strokePaint!.color = Colors.transparent + : strokePaint!.color; + return strokePaint!; } /// Calculates the rendering bounds of a segment. @@ -86,7 +87,7 @@ class StackedColumn100Segment extends ChartSegment { _seriesRenderer, canvas, segmentRect, - _currentPoint, - currentSegmentIndex); + _currentPoint!, + currentSegmentIndex!); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedline100_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedline100_segment.dart index 35e828fde..3ae0033b5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedline100_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedline100_segment.dart @@ -5,9 +5,9 @@ part of charts; /// Generates the stacked line100 series points and has the [calculateSegmentPoints] method overrided to customize /// the stacked line100 segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class StackedLine100Segment extends ChartSegment { - num _x1, + late double _x1, _y1, _x2, _y2, @@ -15,7 +15,7 @@ class StackedLine100Segment extends ChartSegment { _nextCummulativePos, _currentCummulativeValue, _nextCummulativeValue; - Color _pointColorMapper; + Color? _pointColorMapper; /// Gets the color of the series. @override @@ -27,9 +27,9 @@ class StackedLine100Segment extends ChartSegment { 'The opacity value of the stacked line100 series should be less than or equal to 1.'); if (_color != null) { _fillPaint.color = - _pointColorMapper ?? _color.withOpacity(_series.opacity); + _pointColorMapper ?? _color!.withOpacity(_series.opacity); } - _fillPaint.strokeWidth = _strokeWidth; + _fillPaint.strokeWidth = _strokeWidth!; _fillPaint.style = PaintingStyle.fill; _defaultFillColor = _fillPaint; return _fillPaint; @@ -44,13 +44,13 @@ class StackedLine100Segment extends ChartSegment { assert(_series.opacity <= 1, 'The opacity value of the stacked line100 series should be less than or equal to 1.'); if (_strokeColor != null) { - _strokePaint.color = _pointColorMapper ?? _strokeColor; + _strokePaint.color = _pointColorMapper ?? _strokeColor!; _strokePaint.color = (_series.opacity < 1 && _strokePaint.color != Colors.transparent) ? _strokePaint.color.withOpacity(_series.opacity) : _strokePaint.color; } - _strokePaint.strokeWidth = _strokeWidth; + _strokePaint.strokeWidth = _strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.round; _defaultStrokeColor = _strokePaint; @@ -61,41 +61,41 @@ class StackedLine100Segment extends ChartSegment { @override void calculateSegmentPoints() { final Rect rect = _calculatePlotOffset( - _seriesRenderer._chartState._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer._axis.plotOffset, - _seriesRenderer._yAxisRenderer._axis.plotOffset)); + _seriesRenderer._chartState!._chartAxis._axisClipRect, + Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, + _seriesRenderer._yAxisRenderer!._axis.plotOffset)); final _ChartLocation currentChartPoint = _calculatePoint( - _currentPoint.xValue, + _currentPoint!.xValue, _currentCummulativePos, - _seriesRenderer._xAxisRenderer, - _seriesRenderer._yAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._xAxisRenderer!, + _seriesRenderer._yAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); final _ChartLocation _nextLocation = _calculatePoint( - _nextPoint.xValue, + _nextPoint!.xValue, _nextCummulativePos, - _seriesRenderer._xAxisRenderer, - _seriesRenderer._yAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._xAxisRenderer!, + _seriesRenderer._yAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); final _ChartLocation currentCummulativePoint = _calculatePoint( - _currentPoint.xValue, + _currentPoint!.xValue, _currentCummulativePos, - _seriesRenderer._xAxisRenderer, - _seriesRenderer._yAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._xAxisRenderer!, + _seriesRenderer._yAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); final _ChartLocation nextCummulativePoint = _calculatePoint( - _nextPoint.xValue, + _nextPoint!.xValue, _nextCummulativePos, - _seriesRenderer._xAxisRenderer, - _seriesRenderer._yAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._xAxisRenderer!, + _seriesRenderer._yAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, rect); @@ -110,6 +110,7 @@ class StackedLine100Segment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _renderStackedLineSeries(_series, canvas, strokePaint, _x1, _y1, _x2, _y2); + _renderStackedLineSeries(_series as _StackedSeriesBase, canvas, + strokePaint!, _x1, _y1, _x2, _y2); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/step_area_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/step_area_segment.dart index fc4e94447..d14cc8995 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/step_area_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/step_area_segment.dart @@ -5,10 +5,10 @@ part of charts; /// Generates the step area series points and has the [calculateSegmentPoints] method overrided to customize /// the step area segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class StepAreaSegment extends ChartSegment { - Path _path, _strokePath; - Rect _pathRect; + late Path _path, _strokePath; + Rect? _pathRect; /// Gets the color of the series. @override @@ -16,27 +16,25 @@ class StepAreaSegment extends ChartSegment { fillPaint = Paint(); if (_series.gradient == null) { if (_color != null) { - fillPaint.color = _color; - fillPaint.style = PaintingStyle.fill; + fillPaint!.color = _color!; + fillPaint!.style = PaintingStyle.fill; } } else { fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient, _pathRect, - _seriesRenderer._chartState._requireInvertedAxis) + ? _getLinearGradientPaint(_series.gradient!, _pathRect!, + _seriesRenderer._chartState!._requireInvertedAxis) : fillPaint; } assert(_series.opacity >= 0, 'The opacity value of the the step area series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the the step area series should be less than or equal to 1.'); - if (fillPaint.color != null) { - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; - } + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Gets the border color of the series. @@ -66,10 +64,10 @@ class StepAreaSegment extends ChartSegment { void onPaint(Canvas canvas) { _pathRect = _path.getBounds(); canvas.drawPath( - _path, (_series.gradient == null) ? fillPaint : getFillPaint()); + _path, (_series.gradient == null) ? fillPaint! : getFillPaint()); - if (strokePaint.color != Colors.transparent) { - _drawDashedLine(canvas, _series.dashArray, strokePaint, _strokePath); + if (strokePaint!.color != Colors.transparent) { + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _strokePath); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart index 7c24c15fa..a90bdfb23 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart @@ -5,30 +5,24 @@ part of charts; /// Generates the step line series points and has the [calculateSegmentPoints] method overrided to customize /// the step line segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class StepLineSegment extends ChartSegment { - num _x1, _y1, _x2, _y2, _x3, _y3; + late double _x1, _y1, _x2, _y2, _x3, _y3; /// Render path - Path _path; + late Path _path; - num _x1Pos, - _y1Pos, - _x2Pos, - _y2Pos, - _midX, - _midY, - _oldX1, - _oldY1, - _oldX2, - _oldY2, - _oldX3, - _oldY3; - Color _pointColorMapper; - bool _needAnimate; - ChartAxisRenderer _oldXAxisRenderer, _oldYAxisRenderer; - _ChartLocation _currentLocation, _midLocation, _nextLocation, _oldLocation; - StepLineSegment _currentSegment, _oldSegment; + late double _x1Pos, _y1Pos, _x2Pos, _y2Pos; + late num _midX, _midY; + + double? _oldX1, _oldY1, _oldX2, _oldY2, _oldX3, _oldY3; + Color? _pointColorMapper; + late bool _needAnimate; + ChartAxisRenderer? _oldXAxisRenderer, _oldYAxisRenderer; + late _ChartLocation _currentLocation, _midLocation, _nextLocation; + _ChartLocation? _oldLocation; + late StepLineSegment _currentSegment; + StepLineSegment? _oldSegment; /// Gets the color of the series. @override @@ -39,9 +33,9 @@ class StepLineSegment extends ChartSegment { assert(_series.opacity <= 1, 'The opacity value of the step line series should be less than or equal to 1.'); if (_color != null) { - _fillPaint.color = _color.withOpacity(_series.opacity); + _fillPaint.color = _color!.withOpacity(_series.opacity); } - _fillPaint.strokeWidth = _strokeWidth; + _fillPaint.strokeWidth = _strokeWidth!; _fillPaint.style = PaintingStyle.stroke; _defaultFillColor = _fillPaint; return _fillPaint; @@ -52,13 +46,13 @@ class StepLineSegment extends ChartSegment { Paint getStrokePaint() { final Paint _strokePaint = Paint(); if (_strokeColor != null) { - _strokePaint.color = _pointColorMapper ?? _strokeColor; + _strokePaint.color = _pointColorMapper ?? _strokeColor!; _strokePaint.color = (_series.opacity < 1 && _strokePaint.color != Colors.transparent) ? _strokePaint.color.withOpacity(_series.opacity) : _strokePaint.color; } - _strokePaint.strokeWidth = _strokeWidth; + _strokePaint.strokeWidth = _strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.square; _defaultStrokeColor = _strokePaint; @@ -68,26 +62,26 @@ class StepLineSegment extends ChartSegment { /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - final ChartAxisRenderer _xAxisRenderer = _seriesRenderer._xAxisRenderer; - final ChartAxisRenderer _yAxisRenderer = _seriesRenderer._yAxisRenderer; + final ChartAxisRenderer _xAxisRenderer = _seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer _yAxisRenderer = _seriesRenderer._yAxisRenderer!; final Rect _axisClipRect = _calculatePlotOffset( _chartState._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer._axis.plotOffset, - _seriesRenderer._yAxisRenderer._axis.plotOffset)); + Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, + _seriesRenderer._yAxisRenderer!._axis.plotOffset)); _currentLocation = _calculatePoint( - _currentPoint.xValue, - _currentPoint.yValue, + _currentPoint!.xValue, + _currentPoint!.yValue, _xAxisRenderer, _yAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._chartState!._requireInvertedAxis, _seriesRenderer._series, _axisClipRect); _nextLocation = _calculatePoint( - _nextPoint.xValue, - _nextPoint.yValue, + _nextPoint!.xValue, + _nextPoint!.yValue, _xAxisRenderer, _yAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._chartState!._requireInvertedAxis, _seriesRenderer._series, _axisClipRect); _midLocation = _calculatePoint( @@ -95,7 +89,7 @@ class StepLineSegment extends ChartSegment { _midY, _xAxisRenderer, _yAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _seriesRenderer._chartState!._requireInvertedAxis, _seriesRenderer._series, _axisClipRect); _x1 = _currentLocation.x; @@ -110,27 +104,28 @@ class StepLineSegment extends ChartSegment { @override void onPaint(Canvas canvas) { final Rect _rect = _calculatePlotOffset( - _seriesRenderer._chartState._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer._axis.plotOffset, - _seriesRenderer._yAxisRenderer._axis.plotOffset)); + _seriesRenderer._chartState!._chartAxis._axisClipRect, + Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, + _seriesRenderer._yAxisRenderer!._axis.plotOffset)); _path = Path(); if (_series.animationDuration > 0 && !_seriesRenderer._reAnimate && - _seriesRenderer._chartState._widgetNeedUpdate && - !_seriesRenderer._chartState._isLegendToggled && - _seriesRenderer._chartState._oldSeriesRenderers != null && - _seriesRenderer._chartState._oldSeriesRenderers.isNotEmpty && + _seriesRenderer._chartState!._widgetNeedUpdate && + !_seriesRenderer._chartState!._isLegendToggled && + _seriesRenderer._chartState!._oldSeriesRenderers.isNotEmpty && _oldSeriesRenderer != null && - _oldSeriesRenderer._segments.isNotEmpty && - _oldSeriesRenderer._segments[0] is StepLineSegment && - _seriesRenderer._chartState._oldSeriesRenderers.length - 1 >= - _seriesRenderer._segments[currentSegmentIndex]._seriesIndex && - _seriesRenderer._segments[currentSegmentIndex]._oldSeriesRenderer + _oldSeriesRenderer!._segments.isNotEmpty && + _oldSeriesRenderer!._segments[0] is StepLineSegment && + _seriesRenderer._chartState!._oldSeriesRenderers.length - 1 >= + _seriesRenderer._segments[currentSegmentIndex!]._seriesIndex && + _seriesRenderer._segments[currentSegmentIndex!]._oldSeriesRenderer! ._segments.isNotEmpty) { - _currentSegment = _seriesRenderer._segments[currentSegmentIndex]; - _oldSegment = (_currentSegment._oldSeriesRenderer._segments.length - 1 >= - currentSegmentIndex) - ? _currentSegment._oldSeriesRenderer._segments[currentSegmentIndex] + _currentSegment = + _seriesRenderer._segments[currentSegmentIndex!] as StepLineSegment; + _oldSegment = (_currentSegment._oldSeriesRenderer!._segments.length - 1 >= + currentSegmentIndex!) + ? _currentSegment._oldSeriesRenderer!._segments[currentSegmentIndex!] + as StepLineSegment? : null; _oldX1 = _oldSegment?._x1; _oldY1 = _oldSegment?._y1; @@ -140,61 +135,60 @@ class StepLineSegment extends ChartSegment { _oldY3 = _oldSegment?._y3; if (_oldSegment != null && - (_oldX1.isNaN || _oldX2.isNaN) && - _seriesRenderer._chartState._oldAxisRenderers != null && - _seriesRenderer._chartState._oldAxisRenderers.isNotEmpty) { - _oldXAxisRenderer = _getOldAxisRenderer(_seriesRenderer._xAxisRenderer, - _seriesRenderer._chartState._oldAxisRenderers); - _oldYAxisRenderer = _getOldAxisRenderer(_seriesRenderer._yAxisRenderer, - _seriesRenderer._chartState._oldAxisRenderers); + (_oldX1!.isNaN || _oldX2!.isNaN) && + _seriesRenderer._chartState!._oldAxisRenderers.isNotEmpty) { + _oldXAxisRenderer = _getOldAxisRenderer(_seriesRenderer._xAxisRenderer!, + _seriesRenderer._chartState!._oldAxisRenderers); + _oldYAxisRenderer = _getOldAxisRenderer(_seriesRenderer._yAxisRenderer!, + _seriesRenderer._chartState!._oldAxisRenderers); if (_oldYAxisRenderer != null && _oldXAxisRenderer != null) { - _needAnimate = _oldYAxisRenderer._visibleRange.minimum != - _seriesRenderer._yAxisRenderer._visibleRange.minimum || - _oldYAxisRenderer._visibleRange.maximum != - _seriesRenderer._yAxisRenderer._visibleRange.maximum || - _oldXAxisRenderer._visibleRange.minimum != - _seriesRenderer._xAxisRenderer._visibleRange.minimum || - _oldXAxisRenderer._visibleRange.maximum != - _seriesRenderer._xAxisRenderer._visibleRange.maximum; + _needAnimate = _oldYAxisRenderer!._visibleRange!.minimum != + _seriesRenderer._yAxisRenderer!._visibleRange!.minimum || + _oldYAxisRenderer!._visibleRange!.maximum != + _seriesRenderer._yAxisRenderer!._visibleRange!.maximum || + _oldXAxisRenderer!._visibleRange!.minimum != + _seriesRenderer._xAxisRenderer!._visibleRange!.minimum || + _oldXAxisRenderer!._visibleRange!.maximum != + _seriesRenderer._xAxisRenderer!._visibleRange!.maximum; } if (_needAnimate) { _oldLocation = _calculatePoint( _x1Pos, _y1Pos, - _oldXAxisRenderer, - _oldYAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _oldXAxisRenderer!, + _oldYAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, _rect); - _oldX1 = _oldLocation.x; - _oldY1 = _oldLocation.y; + _oldX1 = _oldLocation!.x; + _oldY1 = _oldLocation!.y; _oldLocation = _calculatePoint( _x2Pos, _y2Pos, - _oldXAxisRenderer, - _oldYAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _oldXAxisRenderer!, + _oldYAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, _rect); - _oldX2 = _oldLocation.x; - _oldY2 = _oldLocation.y; + _oldX2 = _oldLocation!.x; + _oldY2 = _oldLocation!.y; _oldLocation = _calculatePoint( _midX, _midY, - _oldXAxisRenderer, - _oldYAxisRenderer, - _seriesRenderer._chartState._requireInvertedAxis, + _oldXAxisRenderer!, + _oldYAxisRenderer!, + _seriesRenderer._chartState!._requireInvertedAxis, _series, _rect); - _oldX3 = _oldLocation.x; - _oldY3 = _oldLocation.y; + _oldX3 = _oldLocation!.x; + _oldY3 = _oldLocation!.y; } } _animateLineTypeSeries( canvas, _seriesRenderer, - strokePaint, + strokePaint!, animationFactor, _currentSegment._x1, _currentSegment._y1, @@ -214,10 +208,10 @@ class StepLineSegment extends ChartSegment { _path.moveTo(_x1, _y1); _path.lineTo(_x3, _y3); _path.lineTo(_x2, _y2); - _drawDashedLine(canvas, _series.dashArray, strokePaint, _path); + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); } else { - canvas.drawLine(Offset(_x1, _y1), Offset(_x3, _y3), strokePaint); - canvas.drawLine(Offset(_x3, _y3), Offset(_x2, _y2), strokePaint); + canvas.drawLine(Offset(_x1, _y1), Offset(_x3, _y3), strokePaint!); + canvas.drawLine(Offset(_x3, _y3), Offset(_x2, _y2), strokePaint!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/waterfall_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/waterfall_segment.dart index 9e96a42f0..ab130b239 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/waterfall_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/waterfall_segment.dart @@ -5,26 +5,26 @@ part of charts; /// Generates the waterfall series points and has the [calculateSegmentPoints] method overrided to customize /// the waterfall segment point calculation. /// -/// Gets the path and color from the [series]. +/// Gets the path and color from the `series`. class WaterfallSegment extends ChartSegment { /// To find the x and y values of connector lines between each data point. - num _x1, _y1, _x2, _y2; + late double _x1, _y1, _x2, _y2; ///Path of the series - Path _path; + late Path _path; /// Get the connetor line paint - Paint connectorLineStrokePaint; + Paint? connectorLineStrokePaint; /// Colors of the negative point, intermediate point and total point. - Color _negativePointsColor, _intermediateSumColor, _totalSumColor; + Color? _negativePointsColor, _intermediateSumColor, _totalSumColor; - @override - CartesianChartPoint _currentPoint; + // @override + // CartesianChartPoint _currentPoint; /// We are using `segmentRect` to draw the bar segment in the series. /// we can override this class and customize the waterfall segment by getting `segmentRect`. - RRect segmentRect; + late RRect segmentRect; /// Gets the color of the series. @override @@ -34,45 +34,43 @@ class WaterfallSegment extends ChartSegment { /// Get and set the paint options for waterfall series. if (_series.gradient == null) { fillPaint = Paint() - ..color = ((hasPointColor && _currentPoint.pointColorMapper != null) - ? _currentPoint.pointColorMapper - : _currentPoint.isIntermediateSum - ? _intermediateSumColor ?? _color - : _currentPoint.isTotalSum - ? _totalSumColor ?? _color - : _currentPoint.yValue < 0 - ? _negativePointsColor ?? _color - : _color) + ..color = ((hasPointColor && _currentPoint!.pointColorMapper != null) + ? _currentPoint!.pointColorMapper + : _currentPoint!.isIntermediateSum! + ? _intermediateSumColor ?? _color! + : _currentPoint!.isTotalSum! + ? _totalSumColor ?? _color! + : _currentPoint!.yValue < 0 + ? _negativePointsColor ?? _color! + : _color!)! ..style = PaintingStyle.fill; } else { fillPaint = _getLinearGradientPaint( - _series.gradient, - _currentPoint.region, - _seriesRenderer._chartState._requireInvertedAxis); + _series.gradient!, + _currentPoint!.region!, + _seriesRenderer._chartState!._requireInvertedAxis); } assert(_series.opacity >= 0, 'The opacity value of the waterfall series should be greater than or equal to 0.'); assert(_series.opacity <= 1, 'The opacity value of the waterfall series should be less than or equal to 1.'); - if (fillPaint.color != null) { - fillPaint.color = - (_series.opacity < 1 && fillPaint.color != Colors.transparent) - ? fillPaint.color.withOpacity(_series.opacity) - : fillPaint.color; - } + fillPaint!.color = + (_series.opacity < 1 && fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_series.opacity) + : fillPaint!.color; _defaultFillColor = fillPaint; - return fillPaint; + return fillPaint!; } /// Get the color of connector lines. Paint _getConnectorLineStrokePaint() { - final WaterfallSeries series = _series; + final WaterfallSeries series = _series as WaterfallSeries; connectorLineStrokePaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = series.connectorLineSettings.width ..color = series.connectorLineSettings.color ?? _chartState._chartTheme.waterfallConnectorLineColor; - return connectorLineStrokePaint; + return connectorLineStrokePaint!; } /// Gets the border color of the series. @@ -80,18 +78,18 @@ class WaterfallSegment extends ChartSegment { Paint getStrokePaint() { strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _strokeWidth; + ..strokeWidth = _strokeWidth!; _defaultStrokeColor = strokePaint; if (_series.borderGradient != null) { - strokePaint.shader = - _series.borderGradient.createShader(_currentPoint.region); + strokePaint!.shader = + _series.borderGradient!.createShader(_currentPoint!.region!); } else { - strokePaint.color = _strokeColor; + strokePaint!.color = _strokeColor!; } _series.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color; - return strokePaint; + ? strokePaint!.color = Colors.transparent + : strokePaint!.color; + return strokePaint!; } /// Calculates the rendering bounds of a segment. @@ -101,63 +99,64 @@ class WaterfallSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - final WaterfallSeries _series = this._series; + final WaterfallSeries _series = + this._series as WaterfallSeries; CartesianChartPoint oldPaint; final Path linePath = Path(); if (fillPaint != null) { (_series.animationDuration > 0 && - !_seriesRenderer._chartState._isLegendToggled) + !_seriesRenderer._chartState!._isLegendToggled) ? _animateRangeColumn( canvas, _seriesRenderer, - fillPaint, + fillPaint!, segmentRect, - _oldPoint != null ? _oldPoint.region : _oldRegion, + _oldPoint != null ? _oldPoint!.region : _oldRegion, animationFactor) - : canvas.drawRRect(segmentRect, fillPaint); + : canvas.drawRRect(segmentRect, fillPaint!); } if (strokePaint != null) { if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) { - _drawDashedLine(canvas, _series.dashArray, strokePaint, _path); + _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); } else { (_series.animationDuration > 0 && - !_seriesRenderer._chartState._isLegendToggled) + !_seriesRenderer._chartState!._isLegendToggled) ? _animateRangeColumn( canvas, _seriesRenderer, - strokePaint, + strokePaint!, segmentRect, - _oldPoint != null ? _oldPoint.region : _oldRegion, + _oldPoint != null ? _oldPoint!.region : _oldRegion, animationFactor) - : canvas.drawRRect(segmentRect, strokePaint); + : canvas.drawRRect(segmentRect, strokePaint!); } } if (connectorLineStrokePaint != null && - _currentPoint.overallDataPointIndex > 0) { - oldPaint = - _seriesRenderer._dataPoints[_currentPoint.overallDataPointIndex - 1]; - _x1 = oldPaint.endValueRightPoint.x; - _y1 = oldPaint.endValueRightPoint.y; - if (_currentPoint.isTotalSum || _currentPoint.isIntermediateSum) { - _x2 = _currentPoint.endValueLeftPoint.x; - _y2 = _currentPoint.endValueLeftPoint.y; + _currentPoint!.overallDataPointIndex! > 0) { + oldPaint = _seriesRenderer + ._dataPoints[_currentPoint!.overallDataPointIndex! - 1]; + _x1 = oldPaint.endValueRightPoint!.x; + _y1 = oldPaint.endValueRightPoint!.y; + if (_currentPoint!.isTotalSum! || _currentPoint!.isIntermediateSum!) { + _x2 = _currentPoint!.endValueLeftPoint!.x; + _y2 = _currentPoint!.endValueLeftPoint!.y; } else { - _x2 = _currentPoint.originValueLeftPoint.x; - _y2 = _currentPoint.originValueLeftPoint.y; + _x2 = _currentPoint!.originValueLeftPoint!.x; + _y2 = _currentPoint!.originValueLeftPoint!.y; } if (_series.animationDuration <= 0 || animationFactor >= - _seriesRenderer._chartState._seriesDurationFactor) { - if (_series.connectorLineSettings.dashArray[0] != 0 && - _series.connectorLineSettings.dashArray[1] != 0) { + _seriesRenderer._chartState!._seriesDurationFactor) { + if (_series.connectorLineSettings.dashArray![0] != 0 && + _series.connectorLineSettings.dashArray![1] != 0) { linePath.moveTo(_x1, _y1); linePath.lineTo(_x2, _y2); - _drawDashedLine(canvas, _series.connectorLineSettings.dashArray, - connectorLineStrokePaint, linePath); + _drawDashedLine(canvas, _series.connectorLineSettings.dashArray!, + connectorLineStrokePaint!, linePath); } else { canvas.drawLine( - Offset(_x1, _y1), Offset(_x2, _y2), connectorLineStrokePaint); + Offset(_x1, _y1), Offset(_x2, _y2), connectorLineStrokePaint!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/area_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/area_series.dart index b4b752909..0283f84f5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/area_series.dart @@ -11,42 +11,41 @@ part of charts; class AreaSeries extends XyDataSeries { /// Creating an argument constructor of AreaSeries class. AreaSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String xAxisName, - String yAxisName, - String name, - Color color, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - List trendlines, - bool isVisible, - bool enableTooltip, - List dashArray, - double animationDuration, - Color borderColor, - double borderWidth, - LinearGradient gradient, - LinearGradient borderGradient, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper? xValueMapper, + required ChartValueMapper? yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + List? trendlines, + bool? isVisible, + bool? enableTooltip, + List? dashArray, + double? animationDuration, + Color? borderColor, + double? borderWidth, + LinearGradient? gradient, + LinearGradient? borderGradient, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - BorderDrawMode borderDrawMode, - SeriesRendererCreatedCallback onRendererCreated}) - : borderDrawMode = borderDrawMode ?? BorderDrawMode.top, - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + this.borderDrawMode = BorderDrawMode.top, + SeriesRendererCreatedCallback? onRendererCreated}) + : super( key: key, onRendererCreated: onRendererCreated, onCreateRenderer: onCreateRenderer, @@ -113,7 +112,7 @@ class AreaSeries extends XyDataSeries { AreaSeriesRenderer createRenderer(ChartSeries series) { AreaSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as AreaSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -129,30 +128,31 @@ class AreaSeriesRenderer extends XyDataSeriesRenderer { /// Creates a segment for a data point in the series. ChartSegment _createSegments( - Path path, Path strokePath, int seriesIndex, num animateFactor, - [List _points]) { + Path path, Path strokePath, int seriesIndex, double animateFactor, + [List? _points]) { final AreaSegment segment = createSegment(); final List oldSeriesRenderers = - _chartState._oldSeriesRenderers; - segment._series = _series; + _chartState!._oldSeriesRenderers; + segment._series = _series as XyDataSeries; segment.currentSegmentIndex = 0; - segment.points = _points; + if (_points != null) segment.points = _points; segment._seriesRenderer = this; segment._seriesIndex = seriesIndex; segment.animationFactor = animateFactor; segment._path = path; segment._strokePath = strokePath; - if (_chartState._widgetNeedUpdate && + if (_chartState!._widgetNeedUpdate && oldSeriesRenderers != null && oldSeriesRenderers.isNotEmpty && oldSeriesRenderers.length - 1 >= segment._seriesIndex && oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; + segment._oldSegmentIndex = 0; } customizeSegment(segment); segment._chart = _chart; - segment._chartState = _chartState; + segment._chartState = _chartState!; _segments.add(segment); return segment; } @@ -161,22 +161,22 @@ class AreaSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } /// To create area series segments @override - ChartSegment createSegment() => AreaSegment(); + AreaSegment createSegment() => AreaSegment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment segment) { - final AreaSegment areaSegment = segment; + final AreaSegment areaSegment = segment as AreaSegment; areaSegment._color = areaSegment._seriesRenderer._seriesColor; areaSegment._strokeColor = areaSegment._seriesRenderer._seriesColor; areaSegment._strokeWidth = areaSegment._series.width; @@ -188,9 +188,9 @@ class AreaSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bar_series.dart index 2eb74d4b8..54956e602 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bar_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bar_series.dart @@ -11,55 +11,49 @@ part of charts; class BarSeries extends XyDataSeries { /// Creating an argument constructor of BarSeries class. BarSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, this.isTrackVisible = false, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - double spacing, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - LinearGradient gradient, - LinearGradient borderGradient, - BorderRadius borderRadius, - bool enableTooltip, - double animationDuration, - Color trackColor, - Color trackBorderColor, - double trackBorderWidth, - double trackPadding, - List trendlines, - Color borderColor, - double borderWidth, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + this.spacing = 0, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + LinearGradient? gradient, + LinearGradient? borderGradient, + this.borderRadius = const BorderRadius.all(Radius.zero), + bool? enableTooltip, + double? animationDuration, + this.trackColor = Colors.grey, + this.trackBorderColor = Colors.transparent, + this.trackBorderWidth = 1, + this.trackPadding = 0, + List? trendlines, + Color? borderColor, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - List dashArray, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) - : trackColor = trackColor ?? Colors.grey, - trackBorderColor = trackBorderColor ?? Colors.transparent, - trackBorderWidth = trackBorderWidth ?? 1, - trackPadding = trackPadding ?? 0, - spacing = spacing ?? 0, - borderRadius = borderRadius ?? const BorderRadius.all(Radius.zero), - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + List? dashArray, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) + : super( key: key, onCreateRenderer: onCreateRenderer, name: name, @@ -250,7 +244,7 @@ class BarSeries extends XyDataSeries { BarSeriesRenderer createRenderer(ChartSeries series) { BarSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as BarSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -265,48 +259,49 @@ class BarSeriesRenderer extends XyDataSeriesRenderer { BarSeriesRenderer(); // Store the rect position // - num _rectPosition; + late num _rectPosition; // Store the rect count // - num _rectCount; + late num _rectCount; /// To add bar segments to chart segments ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { - final BarSeries _barSeries = _series; + int pointIndex, int seriesIndex, double animateFactor) { + final BarSeries _barSeries = _series as BarSeries; final BarSegment segment = createSegment(); final List oldSeriesRenderers = - _chartState._oldSeriesRenderers; + _chartState!._oldSeriesRenderers; segment._series = _barSeries; segment._chart = _chart; - segment._chartState = _chartState; + segment._chartState = _chartState!; segment._seriesRenderer = this; segment._seriesIndex = seriesIndex; segment.currentSegmentIndex = pointIndex; segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); + .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); segment.animationFactor = animateFactor; segment._currentPoint = currentPoint; - if (_chartState._widgetNeedUpdate && - !_chartState._isLegendToggled && + if (_chartState!._widgetNeedUpdate && + !_chartState!._isLegendToggled && oldSeriesRenderers != null && oldSeriesRenderers.isNotEmpty && oldSeriesRenderers.length - 1 >= segment._seriesIndex && oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldPoint = (segment._oldSeriesRenderer._segments.isNotEmpty && - segment._oldSeriesRenderer._segments[0] is BarSegment && - segment._oldSeriesRenderer._dataPoints.length - 1 >= pointIndex) - ? segment._oldSeriesRenderer._dataPoints[pointIndex] + segment._oldPoint = (segment._oldSeriesRenderer!._segments.isNotEmpty && + segment._oldSeriesRenderer!._segments[0] is BarSegment && + segment._oldSeriesRenderer!._dataPoints.length - 1 >= pointIndex) + ? segment._oldSeriesRenderer!._dataPoints[pointIndex] : null; - } else if (_chartState._isLegendToggled && - _chartState._segments != null && - _chartState._segments.isNotEmpty) { + segment._oldSegmentIndex = _getOldSegmentIndex(segment); + } else if (_chartState!._isLegendToggled && + _chartState!._segments != null && + _chartState!._segments.isNotEmpty) { segment._oldSeriesVisible = - _chartState._oldSeriesVisible[segment._seriesIndex]; - for (int i = 0; i < _chartState._segments.length; i++) { - final BarSegment oldSegment = _chartState._segments[i]; + _chartState!._oldSeriesVisible[segment._seriesIndex]; + for (int i = 0; i < _chartState!._segments.length; i++) { + final BarSegment oldSegment = _chartState!._segments[i] as BarSegment; if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && oldSegment._seriesIndex == segment._seriesIndex) { segment._oldRegion = oldSegment.segmentRect.outerRect; @@ -315,16 +310,12 @@ class BarSeriesRenderer extends XyDataSeriesRenderer { } segment._path = _findingRectSeriesDashedBorder(currentPoint, _barSeries.borderWidth); - if (_barSeries.borderRadius != null) { - segment.segmentRect = - _getRRectFromRect(currentPoint.region, _barSeries.borderRadius); - } + segment.segmentRect = + _getRRectFromRect(currentPoint.region!, _barSeries.borderRadius); //Tracker rect if (_barSeries.isTrackVisible) { - if (_barSeries.borderRadius != null) { - segment._trackBarRect = _getRRectFromRect( - currentPoint.trackerRectRegion, _barSeries.borderRadius); - } + segment._trackBarRect = _getRRectFromRect( + currentPoint.trackerRectRegion!, _barSeries.borderRadius); } segment._segmentRect = segment.segmentRect; customizeSegment(segment); @@ -336,22 +327,22 @@ class BarSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => BarSegment(); + BarSegment createSegment() => BarSegment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment segment) { - final BarSegment barSegment = segment; + final BarSegment barSegment = segment as BarSegment; barSegment._color = segment._seriesRenderer._seriesColor; barSegment._strokeColor = segment._series.borderColor; barSegment._strokeWidth = segment._series.borderWidth; @@ -365,9 +356,9 @@ class BarSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/box_and_whisker_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/box_and_whisker_series.dart index c7dee5662..9f4c97cfe 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/box_and_whisker_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/box_and_whisker_series.dart @@ -2,7 +2,7 @@ part of charts; /// This class holds the properties of the Box and Whisker series. /// -/// To render a Box and Whisker chart, create an instance of [BoxAndWhiskerSeries], and add it to the [series] collection property of [SfCartesianChart]. +/// To render a Box and Whisker chart, create an instance of [BoxAndWhiskerSeries], and add it to the `series` collection property of [SfCartesianChart]. /// The Box and Whisker chart represents the hollow rectangle with the lower quartile, upper quartile, maximum and minimum value in the given data. /// /// Provides options for color, opacity, border color, and border width @@ -11,46 +11,43 @@ part of charts; class BoxAndWhiskerSeries extends XyDataSeries { /// Creating an argument constructor of BoxAndWhiskerSeries class. BoxAndWhiskerSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - double spacing, - MarkerSettings markerSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - bool enableTooltip, - double animationDuration, - Color borderColor, - double borderWidth, - LinearGradient gradient, - LinearGradient borderGradient, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + this.spacing = 0, + MarkerSettings? markerSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + bool? enableTooltip, + double? animationDuration, + Color? borderColor, + double? borderWidth, + LinearGradient? gradient, + LinearGradient? borderGradient, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - List dashArray, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - List trendlines, - BoxPlotMode boxPlotMode, - bool showMean}) - : spacing = spacing ?? 0, - boxPlotMode = boxPlotMode ?? BoxPlotMode.normal, - showMean = showMean ?? true, - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + List? dashArray, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + List? trendlines, + this.boxPlotMode = BoxPlotMode.normal, + this.showMean = true}) + : super( key: key, onCreateRenderer: onCreateRenderer, name: name, @@ -169,7 +166,7 @@ class BoxAndWhiskerSeries extends XyDataSeries { BoxAndWhiskerSeriesRenderer createRenderer(ChartSeries series) { BoxAndWhiskerSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as BoxAndWhiskerSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -183,20 +180,20 @@ class _BoxPlotQuartileValues { {this.minimum, this.maximum, //ignore: unused_element - List outliers, + List? outliers, this.upperQuartile, this.lowerQuartile, this.average, this.median, this.mean}); - num minimum; - num maximum; - List outliers = []; - num upperQuartile; - num lowerQuartile; - num average; - num median; - num mean; + num? minimum; + num? maximum; + List? outliers = []; + double? upperQuartile; + double? lowerQuartile; + num? average; + num? median; + num? mean; } /// Creates series renderer for Box and Whisker series @@ -204,24 +201,24 @@ class BoxAndWhiskerSeriesRenderer extends XyDataSeriesRenderer { /// Calling the default constructor of BoxAndWhiskerSeriesRenderer class. BoxAndWhiskerSeriesRenderer(); - num _rectPosition; + late num _rectPosition; - num _rectCount; + late num _rectCount; - BoxAndWhiskerSegment _segment; + late BoxAndWhiskerSegment _segment; - List _oldSeriesRenderers; + List? _oldSeriesRenderers; - _BoxPlotQuartileValues _boxPlotQuartileValues; + late _BoxPlotQuartileValues _boxPlotQuartileValues; /// To find the minimum, maximum, quartile and median value /// of a box plot series. - void _findBoxPlotValues( - List yValues, CartesianChartPoint point, BoxPlotMode mode) { - final num yCount = yValues.length; + void _findBoxPlotValues(List yValues, + CartesianChartPoint point, BoxPlotMode mode) { + final int yCount = yValues.length; _boxPlotQuartileValues = _BoxPlotQuartileValues(); _boxPlotQuartileValues.average = - (yValues.fold(0, (x, y) => x + y)) / yCount; + (yValues.fold(0, (num x, y) => (x.toDouble()) + y!)) / yCount; if (mode == BoxPlotMode.exclusive) { _boxPlotQuartileValues.lowerQuartile = _getExclusiveQuartileValue(yValues, yCount, 0.25); @@ -251,49 +248,54 @@ class BoxAndWhiskerSeriesRenderer extends XyDataSeriesRenderer { } /// To find exclusive quartile values. - num _getExclusiveQuartileValue(List yValues, num count, num percentile) { + double _getExclusiveQuartileValue( + List yValues, int count, num percentile) { if (count == 0) { return 0; } else if (count == 1) { - return yValues[0]; + return yValues[0]!.toDouble(); } num value = 0; final num rank = percentile * (count + 1); - final num integerRank = (rank.abs()).floor(); + final int integerRank = (rank.abs()).floor(); final num fractionRank = rank - integerRank; if (integerRank == 0) { - value = yValues[0]; + value = yValues[0]!; } else if (integerRank > count - 1) { - value = yValues[count - 1]; + value = yValues[count - 1]!; } else { - value = fractionRank * (yValues[integerRank] - yValues[integerRank - 1]) + - yValues[integerRank - 1]; + value = + fractionRank * (yValues[integerRank]! - yValues[integerRank - 1]!) + + yValues[integerRank - 1]!; } - return value; + return value.toDouble(); } /// To find inclusive quartile values. - num _getInclusiveQuartileValue(List yValues, num count, num percentile) { + double _getInclusiveQuartileValue( + List yValues, int count, num percentile) { if (count == 0) { return 0; } else if (count == 1) { - return yValues[0]; + return yValues[0]!.toDouble(); } num value = 0; final num rank = percentile * (count - 1); - final num integerRank = (rank.abs()).floor(); + final int integerRank = (rank.abs()).floor(); final num fractionRank = rank - integerRank; - value = fractionRank * (yValues[integerRank + 1] - yValues[integerRank]) + - yValues[integerRank]; - return value; + value = + (fractionRank * (yValues[integerRank + 1]! - yValues[integerRank]!) + + yValues[integerRank]!); + return value.toDouble(); } /// To find a midian value of each box plot point. - num _getMedian(List values) { - final num half = (values.length / 2).floor(); - return values.length % 2 != 0 - ? values[half] - : ((values[half - 1] + values[half]) / 2.0); + double _getMedian(List values) { + final int half = (values.length / 2).floor(); + return (values.length % 2 != 0 + ? values[half]! + : ((values[half - 1]! + values[half]!) / 2.0)) + .toDouble(); } /// To get the quartile values. @@ -306,30 +308,30 @@ class BoxAndWhiskerSeriesRenderer extends XyDataSeriesRenderer { } final bool isEvenList = count % 2 == 0; final num halfLength = count ~/ 2; - final List lowerQuartileArray = yValues.sublist(0, halfLength); - final List upperQuartileArray = + final List lowerQuartileArray = yValues.sublist(0, halfLength); + final List upperQuartileArray = yValues.sublist(isEvenList ? halfLength : halfLength + 1, count); _boxPlotQuartileValues.lowerQuartile = _getMedian(lowerQuartileArray); _boxPlotQuartileValues.upperQuartile = _getMedian(upperQuartileArray); } /// To get the outliers values of box plot series. - void _getMinMaxOutlier(List yValues, num count, + void _getMinMaxOutlier(List yValues, int count, _BoxPlotQuartileValues _boxPlotQuartileValues) { - final num interquartile = _boxPlotQuartileValues.upperQuartile - - _boxPlotQuartileValues.lowerQuartile; + final double interquartile = _boxPlotQuartileValues.upperQuartile! - + _boxPlotQuartileValues.lowerQuartile!; final num rangeIQR = 1.5 * interquartile; for (int i = 0; i < count; i++) { - if (yValues[i] < _boxPlotQuartileValues.lowerQuartile - rangeIQR) { - _boxPlotQuartileValues.outliers.add(yValues[i]); + if (yValues[i]! < _boxPlotQuartileValues.lowerQuartile! - rangeIQR) { + _boxPlotQuartileValues.outliers!.add(yValues[i]!); } else { _boxPlotQuartileValues.minimum = yValues[i]; break; } } for (int i = count - 1; i >= 0; i--) { - if (yValues[i] > _boxPlotQuartileValues.upperQuartile + rangeIQR) { - _boxPlotQuartileValues.outliers.add(yValues[i]); + if (yValues[i]! > _boxPlotQuartileValues.upperQuartile! + rangeIQR) { + _boxPlotQuartileValues.outliers!.add(yValues[i]!); } else { _boxPlotQuartileValues.maximum = yValues[i]; break; @@ -339,53 +341,51 @@ class BoxAndWhiskerSeriesRenderer extends XyDataSeriesRenderer { /// Range box plot _segment is created here ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { + int pointIndex, int seriesIndex, double animateFactor) { _segment = createSegment(); - _oldSeriesRenderers = _chartState._oldSeriesRenderers; + _oldSeriesRenderers = _chartState!._oldSeriesRenderers; _isRectSeries = false; - if (_segment != null) { - _segment._seriesIndex = seriesIndex; - _segment.currentSegmentIndex = pointIndex; - _segment._seriesRenderer = this; - _segment._series = _series; - _segment.animationFactor = animateFactor; - _segment._pointColorMapper = currentPoint.pointColorMapper; - _segment._currentPoint = currentPoint; - if (_chartState._widgetNeedUpdate && - !_chartState._isLegendToggled && - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= _segment._seriesIndex && - _oldSeriesRenderers[_segment._seriesIndex]._seriesName == - _segment._seriesRenderer._seriesName) { - _segment._oldSeriesRenderer = - _oldSeriesRenderers[_segment._seriesIndex]; - } - _segment.calculateSegmentPoints(); - //stores the points for rendering box and whisker - high, low and rect points - _segment.points - ..add(Offset(currentPoint.markerPoint.x, _segment._maxPoint.y)) - ..add(Offset(currentPoint.markerPoint.x, _segment._minPoint.y)) - ..add(Offset(_segment._lowerX, _segment._topRectY)) - ..add(Offset(_segment._upperX, _segment._topRectY)) - ..add(Offset(_segment._upperX, _segment._bottomRectY)) - ..add(Offset(_segment._lowerX, _segment._bottomRectY)); - customizeSegment(_segment); - _segment.strokePaint = _segment.getStrokePaint(); - _segment.fillPaint = _segment.getFillPaint(); - _segments.add(_segment); + _segment._seriesIndex = seriesIndex; + _segment.currentSegmentIndex = pointIndex; + _segment._seriesRenderer = this; + _segment._series = _series as XyDataSeries; + _segment.animationFactor = animateFactor; + _segment._pointColorMapper = currentPoint.pointColorMapper; + _segment._currentPoint = currentPoint; + if (_chartState!._widgetNeedUpdate && + !_chartState!._isLegendToggled && + _oldSeriesRenderers != null && + _oldSeriesRenderers!.isNotEmpty && + _oldSeriesRenderers!.length - 1 >= _segment._seriesIndex && + _oldSeriesRenderers![_segment._seriesIndex]._seriesName == + _segment._seriesRenderer._seriesName) { + _segment._oldSeriesRenderer = _oldSeriesRenderers![_segment._seriesIndex]; + _segment._oldSegmentIndex = _getOldSegmentIndex(_segment); } + _segment.calculateSegmentPoints(); + //stores the points for rendering box and whisker - high, low and rect points + _segment.points + ..add(Offset(currentPoint.markerPoint!.x, _segment._maxPoint.y)) + ..add(Offset(currentPoint.markerPoint!.x, _segment._minPoint.y)) + ..add(Offset(_segment._lowerX, _segment._topRectY)) + ..add(Offset(_segment._upperX, _segment._topRectY)) + ..add(Offset(_segment._upperX, _segment._bottomRectY)) + ..add(Offset(_segment._lowerX, _segment._bottomRectY)); + customizeSegment(_segment); + _segment.strokePaint = _segment.getStrokePaint(); + _segment.fillPaint = _segment.getFillPaint(); + _segments.add(_segment); return _segment; } @override - ChartSegment createSegment() => BoxAndWhiskerSegment(); + BoxAndWhiskerSegment createSegment() => BoxAndWhiskerSegment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment _segment) { - final BoxAndWhiskerSegment boxSegment = _segment; - boxSegment._color = boxSegment._currentPoint.pointColorMapper ?? + final BoxAndWhiskerSegment boxSegment = _segment as BoxAndWhiskerSegment; + boxSegment._color = boxSegment._currentPoint!.pointColorMapper ?? _segment._seriesRenderer._seriesColor; boxSegment._strokeColor = _segment._series.borderColor; boxSegment._strokeWidth = _segment._series.borderWidth; @@ -397,10 +397,10 @@ class BoxAndWhiskerSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment _segment) { if (_segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = _segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[_segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[_segment.currentSegmentIndex!], _chart); } _segment.onPaint(canvas); } @@ -410,9 +410,9 @@ class BoxAndWhiskerSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bubble_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bubble_series.dart index ad0422e8c..225ae06a0 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bubble_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bubble_series.dart @@ -10,46 +10,44 @@ part of charts; class BubbleSeries extends XyDataSeries { /// Creating an argument constructor of BubbleSeries class. BubbleSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper sizeValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - String xAxisName, - String yAxisName, - Color color, - MarkerSettings markerSettings, - List trendlines, - num minimumRadius, - num maximumRadius, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - String name, - bool enableTooltip, - List dashArray, - double animationDuration, - Color borderColor, - double borderWidth, - LinearGradient gradient, - LinearGradient borderGradient, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? sizeValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + String? xAxisName, + String? yAxisName, + Color? color, + MarkerSettings? markerSettings, + List? trendlines, + this.minimumRadius = 3, + this.maximumRadius = 10, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + String? name, + bool? enableTooltip, + List? dashArray, + double? animationDuration, + Color? borderColor, + double? borderWidth, + LinearGradient? gradient, + LinearGradient? borderGradient, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - SortingOrder sortingOrder, - String legendItemText, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) - : minimumRadius = minimumRadius ?? 3, - maximumRadius = maximumRadius ?? 10, - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + SortingOrder? sortingOrder, + String? legendItemText, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) + : super( key: key, onCreateRenderer: onCreateRenderer, name: name, @@ -127,7 +125,7 @@ class BubbleSeries extends XyDataSeries { BubbleSeriesRenderer createRenderer(ChartSeries series) { BubbleSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as BubbleSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -142,46 +140,44 @@ class BubbleSeriesRenderer extends XyDataSeriesRenderer { BubbleSeriesRenderer(); // Store the maximum size // - num _maxSize; + double? _maxSize; // Store the minimum size // - num _minSize; + double? _minSize; /// To add bubble segments to segment list ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { - final BubbleSegment segment = createSegment(); + int pointIndex, int seriesIndex, double animateFactor) { + final BubbleSegment segment = this.createSegment(); final List oldSeriesRenderers = - _chartState._oldSeriesRenderers; + _chartState!._oldSeriesRenderers; _isRectSeries = false; - if (segment != null) { - segment._seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); - segment._seriesIndex = seriesIndex; - segment._series = _series; - segment.animationFactor = animateFactor; - segment._currentPoint = currentPoint; - segment._seriesRenderer = this; - if (_chartState._widgetNeedUpdate && - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segment._seriesIndex && - oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldPoint = - (segment._oldSeriesRenderer._dataPoints.length - 1 >= pointIndex) - ? segment._oldSeriesRenderer._dataPoints[pointIndex] - : null; - } - segment.calculateSegmentPoints(); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); + segment._seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segment.points + .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segment._seriesIndex = seriesIndex; + segment._series = _series as XyDataSeries; + segment.animationFactor = animateFactor; + segment._currentPoint = currentPoint; + segment._seriesRenderer = this; + if (_chartState!._widgetNeedUpdate && + oldSeriesRenderers.isNotEmpty && + oldSeriesRenderers.length - 1 >= segment._seriesIndex && + oldSeriesRenderers[segment._seriesIndex]._seriesName == + segment._seriesRenderer._seriesName) { + segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; + segment._oldPoint = + (segment._oldSeriesRenderer!._dataPoints.length - 1 >= pointIndex) + ? segment._oldSeriesRenderer!._dataPoints[pointIndex] + : null; + segment._oldSegmentIndex = _getOldSegmentIndex(segment); } + segment.calculateSegmentPoints(); + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + _segments.add(segment); return segment; } @@ -189,25 +185,25 @@ class BubbleSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => BubbleSegment(); + BubbleSegment createSegment() => BubbleSegment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment segment) { - final BubbleSegment bubbleSegment = segment; + final BubbleSegment bubbleSegment = segment as BubbleSegment; bubbleSegment._color = bubbleSegment._seriesRenderer._seriesColor; - bubbleSegment._strokeColor = bubbleSegment._series.borderColor ?? - bubbleSegment._seriesRenderer._seriesColor; + bubbleSegment._strokeColor = bubbleSegment._series.borderColor; // ?? + // bubbleSegment._seriesRenderer._seriesColor; bubbleSegment._strokeWidth = bubbleSegment._series.borderWidth; bubbleSegment.strokePaint = bubbleSegment.getStrokePaint(); bubbleSegment.fillPaint = bubbleSegment.getFillPaint(); @@ -217,9 +213,11 @@ class BubbleSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + if (seriesRenderer != null) { + canvas.drawPath(seriesRenderer._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); + } } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/candle_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/candle_series.dart index 1b0257bb7..842c5caf5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/candle_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/candle_series.dart @@ -2,7 +2,7 @@ part of charts; /// This class holds the properties of the candle series. /// -/// To render a candle chart, create an instance of [CandleSeries], and add it to the [series] collection property of [SfCartesianChart]. +/// To render a candle chart, create an instance of [CandleSeries], and add it to the `series` collection property of [SfCartesianChart]. /// The candle chart represents the hollow rectangle with the open, close, high and low value in the given data. /// /// It has the [bearColor] and [bullColor] properties to change the appearance of the candle series. @@ -13,43 +13,42 @@ part of charts; class CandleSeries extends _FinancialSeriesBase { /// Creating an argument constructor of CandleSeries class. CandleSeries({ - ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper lowValueMapper, - @required ChartValueMapper highValueMapper, - @required ChartValueMapper openValueMapper, - @required ChartValueMapper closeValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String xAxisName, - String yAxisName, - String name, - Color bearColor, - Color bullColor, - bool enableSolidCandles, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - bool enableTooltip, - double animationDuration, - // Color borderColor, - double borderWidth, + ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper lowValueMapper, + required ChartValueMapper highValueMapper, + required ChartValueMapper openValueMapper, + required ChartValueMapper closeValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? xAxisName, + String? yAxisName, + String? name, + Color? bearColor, + Color? bullColor, + bool? enableSolidCandles, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + bool? enableTooltip, + double? animationDuration, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - List dashArray, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes, - bool showIndicationForSameValues, - List trendlines, + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + List? dashArray, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes, + bool? showIndicationForSameValues, + List? trendlines, }) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -95,7 +94,7 @@ class CandleSeries extends _FinancialSeriesBase { CandleSeriesRenderer createRenderer(ChartSeries series) { CandleSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as CandleSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -105,53 +104,54 @@ class CandleSeries extends _FinancialSeriesBase { } /// Creates series renderer for Candle series -class CandleSeriesRenderer extends XyDataSeriesRenderer { +class CandleSeriesRenderer extends _FinancialSerieBaseRenderer { /// Calling the default constructor of CandleSeriesRenderer class. CandleSeriesRenderer(); // Store the rect position // - num _rectPosition; + late num _rectPosition; // Store the rect count // - num _rectCount; + late num _rectCount; - CandleSegment _candleSegment, _segment; + late CandleSegment _candleSegment, _segment; - CandleSeries _candleSeries; + late CandleSeries _candleSeries; - CandleSeriesRenderer _candelSereisRenderer; + late CandleSeriesRenderer _candelSereisRenderer; - List _oldSeriesRenderers; + List? _oldSeriesRenderers; /// Range column _segment is created here ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { + int pointIndex, int seriesIndex, double animateFactor) { _segment = createSegment(); - _oldSeriesRenderers = _chartState._oldSeriesRenderers; + _oldSeriesRenderers = _chartState!._oldSeriesRenderers; _isRectSeries = false; if (_segment != null) { _segment._seriesIndex = seriesIndex; _segment.currentSegmentIndex = pointIndex; _segment._seriesRenderer = this; - _segment._series = _series; + _segment._series = _series as XyDataSeries; _segment.animationFactor = animateFactor; _segment._pointColorMapper = currentPoint.pointColorMapper; _segment._currentPoint = currentPoint; - if (_chartState._widgetNeedUpdate && - !_chartState._isLegendToggled && + if (_chartState!._widgetNeedUpdate && + !_chartState!._isLegendToggled && _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= _segment._seriesIndex && - _oldSeriesRenderers[_segment._seriesIndex]._seriesName == + _oldSeriesRenderers!.isNotEmpty && + _oldSeriesRenderers!.length - 1 >= _segment._seriesIndex && + _oldSeriesRenderers![_segment._seriesIndex]._seriesName == _segment._seriesRenderer._seriesName) { _segment._oldSeriesRenderer = - _oldSeriesRenderers[_segment._seriesIndex]; + _oldSeriesRenderers![_segment._seriesIndex]; + _segment._oldSegmentIndex = _getOldSegmentIndex(_segment); } _segment.calculateSegmentPoints(); //stores the points for rendering candle - high, low and rect points _segment.points - ..add(Offset(currentPoint.markerPoint.x, _segment._highPoint.y)) - ..add(Offset(currentPoint.markerPoint.x, _segment._lowPoint.y)) + ..add(Offset(currentPoint.markerPoint!.x, _segment._highPoint.y)) + ..add(Offset(currentPoint.markerPoint!.x, _segment._lowPoint.y)) ..add(Offset(_segment._openX, _segment._topRectY)) ..add(Offset(_segment._closeX, _segment._topRectY)) ..add(Offset(_segment._closeX, _segment._bottomRectY)) @@ -166,29 +166,29 @@ class CandleSeriesRenderer extends XyDataSeriesRenderer { } @override - ChartSegment createSegment() => CandleSegment(); + CandleSegment createSegment() => CandleSegment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment _segment) { - _candleSeries = _series; - _candelSereisRenderer = _segment._seriesRenderer; + _candleSeries = _series as CandleSeries; + _candelSereisRenderer = _segment._seriesRenderer as CandleSeriesRenderer; _candleSegment = _candelSereisRenderer._candleSegment; - if (_candleSeries.enableSolidCandles) { + if (_candleSeries.enableSolidCandles!) { _candleSegment._isSolid = true; _candleSegment._color = _candleSegment._isBull ? _candleSeries.bullColor : _candleSeries.bearColor; } else { _candleSegment._isSolid = !_candleSegment._isBull ? true : false; - _candleSegment.currentSegmentIndex - 1 >= 0 && + _candleSegment.currentSegmentIndex! - 1 >= 0 && _candleSegment ._seriesRenderer - ._dataPoints[_candleSegment.currentSegmentIndex - 1] + ._dataPoints[_candleSegment.currentSegmentIndex! - 1] .close > _candleSegment._seriesRenderer - ._dataPoints[_candleSegment.currentSegmentIndex].close + ._dataPoints[_candleSegment.currentSegmentIndex!].close ? _candleSegment._color = _candleSeries.bearColor : _candleSegment._color = _candleSeries.bullColor; } @@ -199,10 +199,10 @@ class CandleSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment _segment) { if (_segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = _segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[_segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[_segment.currentSegmentIndex!], _chart); } _segment.onPaint(canvas); } @@ -211,7 +211,7 @@ class CandleSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) {} + [CartesianSeriesRenderer? seriesRenderer]) {} /// Draws data label text of the appropriate data point in a series. @override diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/column_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/column_series.dart index 2f72f935a..653116fad 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/column_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/column_series.dart @@ -11,55 +11,49 @@ part of charts; class ColumnSeries extends XyDataSeries { /// Creating an argument constructor of ColumnSeries class. ColumnSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, this.isTrackVisible = false, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - double spacing, - MarkerSettings markerSettings, - List trendlines, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - LinearGradient gradient, - LinearGradient borderGradient, - BorderRadius borderRadius, - bool enableTooltip, - double animationDuration, - Color trackColor, - Color trackBorderColor, - double trackBorderWidth, - double trackPadding, - Color borderColor, - double borderWidth, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + this.spacing = 0, + MarkerSettings? markerSettings, + List? trendlines, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + LinearGradient? gradient, + LinearGradient? borderGradient, + this.borderRadius = const BorderRadius.all(Radius.zero), + bool? enableTooltip, + double? animationDuration, + this.trackColor = Colors.grey, + this.trackBorderColor = Colors.transparent, + this.trackBorderWidth = 1, + this.trackPadding = 0, + Color? borderColor, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - List dashArray, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) - : trackColor = trackColor ?? Colors.grey, - trackBorderColor = trackBorderColor ?? Colors.transparent, - trackBorderWidth = trackBorderWidth ?? 1, - trackPadding = trackPadding ?? 0, - spacing = spacing ?? 0, - borderRadius = borderRadius ?? const BorderRadius.all(Radius.zero), - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + List? dashArray, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) + : super( key: key, onCreateRenderer: onCreateRenderer, name: name, @@ -245,7 +239,7 @@ class ColumnSeries extends XyDataSeries { ColumnSeriesRenderer createRenderer(ChartSeries series) { ColumnSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as ColumnSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -260,50 +254,52 @@ class ColumnSeriesRenderer extends XyDataSeriesRenderer { ColumnSeriesRenderer(); // Store the rect position // - num _rectPosition; + late num _rectPosition; // Store the rect count // - num _rectCount; + late num _rectCount; /// To add column segments in segments list ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { - final ColumnSegment segment = createSegment(); + int pointIndex, int seriesIndex, double animateFactor) { + final ColumnSegment segment = createSegment() as ColumnSegment; final List oldSeriesRenderers = - _chartState._oldSeriesRenderers; - final ColumnSeries _columnSeries = _series; + _chartState!._oldSeriesRenderers; + final ColumnSeries _columnSeries = + _series as ColumnSeries; segment._seriesRenderer = this; segment._series = _columnSeries; segment._chart = _chart; - segment._chartState = _chartState; + segment._chartState = _chartState!; segment._seriesIndex = seriesIndex; segment.currentSegmentIndex = pointIndex; segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); + .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); segment.animationFactor = animateFactor; segment._currentPoint = currentPoint; - if (_chartState._widgetNeedUpdate && - _chartState._zoomPanBehaviorRenderer._isPinching != true && - !_chartState._isLegendToggled && + if (_chartState!._widgetNeedUpdate && + _chartState!._zoomPanBehaviorRenderer._isPinching != true && + !_chartState!._isLegendToggled && oldSeriesRenderers != null && oldSeriesRenderers.isNotEmpty && oldSeriesRenderers.length - 1 >= segment._seriesIndex && oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldPoint = (segment._oldSeriesRenderer._segments.isNotEmpty && - segment._oldSeriesRenderer._segments[0] is ColumnSegment && - segment._oldSeriesRenderer._dataPoints.length - 1 >= pointIndex) - ? segment._oldSeriesRenderer._dataPoints[pointIndex] + segment._oldPoint = (segment._oldSeriesRenderer!._segments.isNotEmpty && + segment._oldSeriesRenderer!._segments[0] is ColumnSegment && + segment._oldSeriesRenderer!._dataPoints.length - 1 >= pointIndex) + ? segment._oldSeriesRenderer!._dataPoints[pointIndex] : null; - } else if (_chartState._isLegendToggled && - _chartState._segments != null && - _chartState._segments.isNotEmpty) { + segment._oldSegmentIndex = _getOldSegmentIndex(segment); + } else if (_chartState!._isLegendToggled && + _chartState!._segments != null && + _chartState!._segments.isNotEmpty) { segment._oldSeriesVisible = - _chartState._oldSeriesVisible[segment._seriesIndex]; + _chartState!._oldSeriesVisible[segment._seriesIndex]; ColumnSegment oldSegment; - for (int i = 0; i < _chartState._segments.length; i++) { - oldSegment = _chartState._segments[i]; + for (int i = 0; i < _chartState!._segments.length; i++) { + oldSegment = _chartState!._segments[i] as ColumnSegment; if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && oldSegment._seriesIndex == segment._seriesIndex) { segment._oldRegion = oldSegment.segmentRect.outerRect; @@ -315,12 +311,12 @@ class ColumnSeriesRenderer extends XyDataSeriesRenderer { if (_columnSeries.borderRadius != null) { segment.segmentRect = - _getRRectFromRect(currentPoint.region, _columnSeries.borderRadius); + _getRRectFromRect(currentPoint.region!, _columnSeries.borderRadius); //Tracker rect if (_columnSeries.isTrackVisible) { segment._trackRect = _getRRectFromRect( - currentPoint.trackerRectRegion, _columnSeries.borderRadius); + currentPoint.trackerRectRegion!, _columnSeries.borderRadius); } } segment._segmentRect = segment.segmentRect; @@ -333,10 +329,10 @@ class ColumnSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } @@ -348,8 +344,8 @@ class ColumnSeriesRenderer extends XyDataSeriesRenderer { /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment segment) { - final ColumnSegment columnSegment = segment; - columnSegment._color = columnSegment._currentPoint.pointColorMapper ?? + final ColumnSegment columnSegment = segment as ColumnSegment; + columnSegment._color = columnSegment._currentPoint!.pointColorMapper ?? segment._seriesRenderer._seriesColor; columnSegment._strokeColor = segment._series.borderColor; columnSegment._strokeWidth = segment._series.borderWidth; @@ -363,9 +359,9 @@ class ColumnSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart index 0b2b602e1..f23c870b0 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart @@ -15,36 +15,36 @@ part of charts; class FastLineSeries extends XyDataSeries { /// Creating an argument constructor of FastLineSeries class. FastLineSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper dataLabelMapper, - String xAxisName, - String yAxisName, - Color color, - double width, - List dashArray, - LinearGradient gradient, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - List trendlines, - DataLabelSettings dataLabelSettings, - SortingOrder sortingOrder, - bool isVisible, - String name, - bool enableTooltip, - double animationDuration, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? dataLabelMapper, + String? xAxisName, + String? yAxisName, + Color? color, + double? width, + List? dashArray, + LinearGradient? gradient, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + List? trendlines, + DataLabelSettings? dataLabelSettings, + SortingOrder? sortingOrder, + bool? isVisible, + String? name, + bool? enableTooltip, + double? animationDuration, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - SeriesRendererCreatedCallback onRendererCreated}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -80,7 +80,7 @@ class FastLineSeries extends XyDataSeries { FastLineSeriesRenderer createRenderer(ChartSeries series) { FastLineSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as FastLineSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -100,14 +100,15 @@ class FastLineSeriesRenderer extends XyDataSeriesRenderer { ///Adds the segment to the segments list ChartSegment _createSegments( - int seriesIndex, SfCartesianChart chart, num animateFactor, - [List _points]) { + int seriesIndex, SfCartesianChart chart, double animateFactor, + [List? _points]) { final FastLineSegment segment = createSegment(); - segment._series = _series; + segment._series = _series as XyDataSeries; segment._seriesIndex = seriesIndex; segment._seriesRenderer = this; segment.animationFactor = animateFactor; - segment.points = _points; + if (_points != null) segment.points = _points; + segment._oldSegmentIndex = 0; customizeSegment(segment); segment._chart = chart; _segments.add(segment); @@ -118,22 +119,22 @@ class FastLineSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer - ._checkWithSelectionState(_segments[0], _chart); + selectionBehaviorRenderer?._selectionRenderer + ?._checkWithSelectionState(_segments[0], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => FastLineSegment(); + FastLineSegment createSegment() => FastLineSegment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment segment) { - final FastLineSegment fastLineSegment = segment; + final FastLineSegment fastLineSegment = segment as FastLineSegment; fastLineSegment._color = fastLineSegment._seriesRenderer._seriesColor; fastLineSegment._strokeColor = fastLineSegment._seriesRenderer._seriesColor; fastLineSegment._strokeWidth = fastLineSegment._series.width; @@ -145,9 +146,9 @@ class FastLineSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/financial_series_base.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/financial_series_base.dart index 2829eb0cf..d928e30b5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/financial_series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/financial_series_base.dart @@ -2,48 +2,48 @@ part of charts; abstract class _FinancialSeriesBase extends XyDataSeries { _FinancialSeriesBase( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - ChartValueMapper lowValueMapper, - ChartValueMapper highValueMapper, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? highValueMapper, this.openValueMapper, this.closeValueMapper, this.volumeValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - List dashArray, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - double spacing, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - LinearGradient gradient, - bool enableTooltip, - double animationDuration, - double borderWidth, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + List? dashArray, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + double? spacing, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + LinearGradient? gradient, + bool? enableTooltip, + double? animationDuration, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - List initialSelectedDataIndexes, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + List? initialSelectedDataIndexes, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, this.enableSolidCandles, this.bearColor, this.bullColor, - double opacity, + double? opacity, this.showIndicationForSameValues = false, - List trendlines, - SeriesRendererCreatedCallback onRendererCreated}) + List? trendlines, + SeriesRendererCreatedCallback? onRendererCreated}) : dashArray = dashArray ?? [0, 0], spacing = spacing ?? 0, super( @@ -80,12 +80,12 @@ abstract class _FinancialSeriesBase extends XyDataSeries { onRendererCreated: onRendererCreated, trendlines: trendlines); - final ChartIndexedValueMapper volumeValueMapper; - final ChartIndexedValueMapper openValueMapper; - final ChartIndexedValueMapper closeValueMapper; - final Color bearColor; - final Color bullColor; - final bool enableSolidCandles; + final ChartIndexedValueMapper? volumeValueMapper; + final ChartIndexedValueMapper? openValueMapper; + final ChartIndexedValueMapper? closeValueMapper; + final Color? bearColor; + final Color? bullColor; + final bool? enableSolidCandles; @override final List dashArray; @@ -124,12 +124,11 @@ abstract class _FinancialSeriesBase extends XyDataSeries { final bool showIndicationForSameValues; } -//ignore: unused_element abstract class _FinancialSerieBaseRenderer extends XyDataSeriesRenderer { _FinancialSerieBaseRenderer(); // ignore:unused_field - num _rectPosition; + late num _rectPosition; // ignore:unused_field - num _rectCount; + late num _rectCount; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hilo_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hilo_series.dart index 2cb1b96b7..ff3d4381b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hilo_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hilo_series.dart @@ -8,40 +8,40 @@ part of charts; class HiloSeries extends _FinancialSeriesBase { /// Creating an argument constructor of HiloSeries class. HiloSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper lowValueMapper, - @required ChartValueMapper highValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String xAxisName, - String yAxisName, - String name, - Color color, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - bool enableTooltip, - double animationDuration, - double borderWidth, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper lowValueMapper, + required ChartValueMapper highValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + bool? enableTooltip, + double? animationDuration, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - List dashArray, - double opacity, - double spacing, - List initialSelectedDataIndexes, - bool showIndicationForSameValues, - List trendlines, - SeriesRendererCreatedCallback onRendererCreated}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + List? dashArray, + double? opacity, + double? spacing, + List? initialSelectedDataIndexes, + bool? showIndicationForSameValues, + List? trendlines, + SeriesRendererCreatedCallback? onRendererCreated}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -81,7 +81,7 @@ class HiloSeries extends _FinancialSeriesBase { HiloSeriesRenderer createRenderer(ChartSeries series) { HiloSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as HiloSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -91,43 +91,44 @@ class HiloSeries extends _FinancialSeriesBase { } /// Creates series renderer for Hilo series -class HiloSeriesRenderer extends XyDataSeriesRenderer { +class HiloSeriesRenderer extends _FinancialSerieBaseRenderer { /// Calling the default constructor of HiloSeriesRenderer class. HiloSeriesRenderer(); // Store the rect position // - num _rectPosition; + late num _rectPosition; // Store the rect count // - num _rectCount; + late num _rectCount; /// Hilo segment is created here ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { + int pointIndex, int seriesIndex, double animateFactor) { _isRectSeries = false; final HiloSegment segment = createSegment(); final List oldSeriesRenderers = - _chartState._oldSeriesRenderers; + _chartState!._oldSeriesRenderers; if (segment != null) { segment._seriesIndex = seriesIndex; segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); segment.points.add( - Offset(currentPoint.markerPoint2.x, currentPoint.markerPoint2.y)); - segment._series = _series; + Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segment.points.add( + Offset(currentPoint.markerPoint2!.x, currentPoint.markerPoint2!.y)); + segment._series = _series as XyDataSeries; segment._seriesRenderer = this; segment.animationFactor = animateFactor; segment._pointColorMapper = currentPoint.pointColorMapper; segment._currentPoint = currentPoint; - if (_chartState._widgetNeedUpdate && - !_chartState._isLegendToggled && + if (_chartState!._widgetNeedUpdate && + !_chartState!._isLegendToggled && oldSeriesRenderers != null && oldSeriesRenderers.isNotEmpty && oldSeriesRenderers.length - 1 >= segment._seriesIndex && oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; + segment._oldSegmentIndex = _getOldSegmentIndex(segment); } segment.calculateSegmentPoints(); customizeSegment(segment); @@ -142,16 +143,16 @@ class HiloSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } @override - ChartSegment createSegment() => HiloSegment(); + HiloSegment createSegment() => HiloSegment(); /// Changes the series color, border color, and border width. @override @@ -165,11 +166,11 @@ class HiloSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes2[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); - canvas.drawPath(seriesRenderer._markerShapes2[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes2[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); + canvas.drawPath(seriesRenderer._markerShapes2[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hiloopenclose_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hiloopenclose_series.dart index dd0bfa42d..ef998aa17 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hiloopenclose_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hiloopenclose_series.dart @@ -9,43 +9,43 @@ part of charts; class HiloOpenCloseSeries extends _FinancialSeriesBase { /// Creating an argument constructor of HiloOpenCloseSeries class. HiloOpenCloseSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper lowValueMapper, - @required ChartValueMapper highValueMapper, - @required ChartValueMapper openValueMapper, - @required ChartValueMapper closeValueMapper, - ChartValueMapper volumeValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String xAxisName, - String yAxisName, - String name, - Color bearColor, - Color bullColor, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - bool enableTooltip, - double animationDuration, - double borderWidth, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper lowValueMapper, + required ChartValueMapper highValueMapper, + required ChartValueMapper openValueMapper, + required ChartValueMapper closeValueMapper, + ChartValueMapper? volumeValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? xAxisName, + String? yAxisName, + String? name, + Color? bearColor, + Color? bullColor, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + bool? enableTooltip, + double? animationDuration, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - List dashArray, - double opacity, - double spacing, - List initialSelectedDataIndexes, - bool showIndicationForSameValues, - List trendlines, - SeriesRendererCreatedCallback onRendererCreated}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + List? dashArray, + double? opacity, + double? spacing, + List? initialSelectedDataIndexes, + bool? showIndicationForSameValues, + List? trendlines, + SeriesRendererCreatedCallback? onRendererCreated}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -94,7 +94,7 @@ class HiloOpenCloseSeries extends _FinancialSeriesBase { HiloOpenCloseSeriesRenderer createRenderer(ChartSeries series) { HiloOpenCloseSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as HiloOpenCloseSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -109,46 +109,47 @@ class HiloOpenCloseSeriesRenderer extends XyDataSeriesRenderer { HiloOpenCloseSeriesRenderer(); // Store the rect position // - num _rectPosition; + late num _rectPosition; // Store the rect count // - num _rectCount; + late num _rectCount; - HiloOpenCloseSeries _hiloOpenCloseSeries; + late HiloOpenCloseSeries _hiloOpenCloseSeries; - HiloOpenCloseSegment _segment; + late HiloOpenCloseSegment _segment; - List _oldSeriesRenderers; + List? _oldSeriesRenderers; /// HiloOpenClose _segment is created here ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { + int pointIndex, int seriesIndex, double animateFactor) { _segment = createSegment(); - _oldSeriesRenderers = _chartState._oldSeriesRenderers; + _oldSeriesRenderers = _chartState!._oldSeriesRenderers; _isRectSeries = false; if (_segment != null) { _segment._seriesIndex = seriesIndex; _segment.currentSegmentIndex = pointIndex; _segment._seriesRenderer = this; - _segment._series = _series; + _segment._series = _series as XyDataSeries; _segment.animationFactor = animateFactor; _segment._pointColorMapper = currentPoint.pointColorMapper; _segment._currentPoint = currentPoint; - if (_chartState._widgetNeedUpdate && - !_chartState._isLegendToggled && + if (_chartState!._widgetNeedUpdate && + !_chartState!._isLegendToggled && _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= _segment._seriesIndex && - _oldSeriesRenderers[_segment._seriesIndex]._seriesName == + _oldSeriesRenderers!.isNotEmpty && + _oldSeriesRenderers!.length - 1 >= _segment._seriesIndex && + _oldSeriesRenderers![_segment._seriesIndex]._seriesName == _segment._seriesRenderer._seriesName) { _segment._oldSeriesRenderer = - _oldSeriesRenderers[_segment._seriesIndex]; + _oldSeriesRenderers![_segment._seriesIndex]; + _segment._oldSegmentIndex = _getOldSegmentIndex(_segment); } _segment.calculateSegmentPoints(); //stores the points for rendering Hilo open close segment, High, low, open, close _segment.points - ..add(Offset(currentPoint.markerPoint.x, _segment._highPoint.y)) - ..add(Offset(currentPoint.markerPoint.x, _segment._lowPoint.y)) + ..add(Offset(currentPoint.markerPoint!.x, _segment._highPoint.y)) + ..add(Offset(currentPoint.markerPoint!.x, _segment._lowPoint.y)) ..add(Offset(_segment._openX, _segment._openY)) ..add(Offset(_segment._closeX, _segment._closeY)); customizeSegment(_segment); @@ -163,21 +164,21 @@ class HiloOpenCloseSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment _segment) { if (_segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = _segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[_segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[_segment.currentSegmentIndex!], _chart); } _segment.onPaint(canvas); } @override - ChartSegment createSegment() => HiloOpenCloseSegment(); + HiloOpenCloseSegment createSegment() => HiloOpenCloseSegment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment _segment) { - _hiloOpenCloseSeries = _series; + _hiloOpenCloseSeries = _series as HiloOpenCloseSeries; _segment._color = _segment._seriesRenderer._seriesColor; _segment._strokeColor = _segment is HiloOpenCloseSegment && _segment._isBull ? _hiloOpenCloseSeries.bullColor @@ -189,7 +190,7 @@ class HiloOpenCloseSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) {} + [CartesianSeriesRenderer? seriesRenderer]) {} /// Draws data label text of the appropriate data point in a series. @override diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/histogram_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/histogram_series.dart index c7690912a..84f578c7e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/histogram_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/histogram_series.dart @@ -11,60 +11,52 @@ part of charts; class HistogramSeries extends XyDataSeries { /// Creating an argument constructor of HistogramSeries class. HistogramSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, this.isTrackVisible = false, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - double spacing, - MarkerSettings markerSettings, - List trendlines, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - LinearGradient gradient, - LinearGradient borderGradient, - BorderRadius borderRadius, - bool enableTooltip, - double animationDuration, - Color trackColor, - Color trackBorderColor, - double trackBorderWidth, - double trackPadding, - Color borderColor, - double borderWidth, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + this.spacing = 0, + MarkerSettings? markerSettings, + List? trendlines, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + LinearGradient? gradient, + LinearGradient? borderGradient, + this.borderRadius = const BorderRadius.all(Radius.zero), + bool? enableTooltip, + double? animationDuration, + this.trackColor = Colors.grey, + this.trackBorderColor = Colors.transparent, + this.trackBorderWidth = 1, + this.trackPadding = 0, + Color? borderColor, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - List dashArray, + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + List? dashArray, this.binInterval, - bool showNormalDistributionCurve, + this.showNormalDistributionCurve = false, this.curveColor = Colors.blue, - double curveWidth, + this.curveWidth = 2, this.curveDashArray, - SeriesRendererCreatedCallback onRendererCreated}) - : trackColor = trackColor ?? Colors.grey, - trackBorderColor = trackBorderColor ?? Colors.transparent, - trackBorderWidth = trackBorderWidth ?? 1, - trackPadding = trackPadding ?? 0, - spacing = spacing ?? 0, - borderRadius = borderRadius ?? const BorderRadius.all(Radius.zero), - showNormalDistributionCurve = showNormalDistributionCurve ?? false, - curveWidth = curveWidth ?? 2, - super( + SeriesRendererCreatedCallback? onRendererCreated}) + : super( key: key, onCreateRenderer: onCreateRenderer, name: name, @@ -121,11 +113,11 @@ class HistogramSeries extends XyDataSeries { /// )); ///} ///``` - final int binInterval; + final int? binInterval; ///Renders a spline curve for the normal distribution, calculated based on the series data points. /// - ///This spline curve type can be changed using the [splineType] property. + ///This spline curve type can be changed using the `splineType` property. /// ///Defaults to `false` ///```dart @@ -195,7 +187,7 @@ class HistogramSeries extends XyDataSeries { /// )); ///} ///``` - final List curveDashArray; + final List? curveDashArray; ///Color of the track. /// @@ -345,7 +337,7 @@ class HistogramSeries extends XyDataSeries { HistogramSeriesRenderer createRenderer(ChartSeries series) { HistogramSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as HistogramSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -357,47 +349,47 @@ class HistogramSeries extends XyDataSeries { class _HistogramValues { _HistogramValues( {this.sDValue, this.mean, this.binWidth, this.yValues, this.minValue}); - num sDValue; - num mean; - num binWidth; - num minValue; - List yValues = []; + num? sDValue; + num? mean; + num? binWidth; + num? minValue; + List? yValues = []; } /// Creates series renderer for Histogram series class HistogramSeriesRenderer extends XyDataSeriesRenderer { - num _rectPosition; - num _rectCount; - _HistogramValues _histogramValues; - HistogramSegment _segment; - List _oldSeriesRenderers; - HistogramSeries _histogramSeries; - BorderRadius _borderRadius; + late num _rectPosition; + late num _rectCount; + late _HistogramValues _histogramValues; + late HistogramSegment _segment; + List? _oldSeriesRenderers; + late HistogramSeries _histogramSeries; + BorderRadius? _borderRadius; /// To get the proper data for histogram series void _processData(HistogramSeries series, List yValues, num yValuesCount) { _histogramValues = _HistogramValues(); _histogramValues.yValues = yValues; - final num mean = yValuesCount / _histogramValues.yValues.length; + final num mean = yValuesCount / _histogramValues.yValues!.length; _histogramValues.mean = mean; num sumValue = 0; num sDValue; - for (int value = 0; value < _histogramValues.yValues.length; value++) { - sumValue += (_histogramValues.yValues[value] - _histogramValues.mean) * - (_histogramValues.yValues[value] - _histogramValues.mean); + for (int value = 0; value < _histogramValues.yValues!.length; value++) { + sumValue += (_histogramValues.yValues![value] - _histogramValues.mean!) * + (_histogramValues.yValues![value] - _histogramValues.mean!); } - sDValue = math.sqrt(sumValue / _histogramValues.yValues.length - 1).isNaN + sDValue = math.sqrt(sumValue / _histogramValues.yValues!.length - 1).isNaN ? 0 - : (math.sqrt(sumValue / _histogramValues.yValues.length - 1)).round(); + : (math.sqrt(sumValue / _histogramValues.yValues!.length - 1)).round(); _histogramValues.sDValue = sDValue; } /// Find the path for distribution line in the histogram Path _findNormalDistributionPath( HistogramSeries series, SfCartesianChart chart) { - final num min = _xAxisRenderer._visibleRange.minimum; - final num max = _xAxisRenderer._visibleRange.maximum; + final num min = _xAxisRenderer!._visibleRange!.minimum; + final num max = _xAxisRenderer!._visibleRange!.maximum; num xValue, yValue; final Path path = Path(); _ChartLocation pointLocation; @@ -405,18 +397,20 @@ class HistogramSeriesRenderer extends XyDataSeriesRenderer { final num del = (max - min) / (pointsCount - 1); for (int i = 0; i < pointsCount; i++) { xValue = min + i * del; - yValue = math.exp(-(xValue - _histogramValues.mean) * - (xValue - _histogramValues.mean) / - (2 * _histogramValues.sDValue * _histogramValues.sDValue)) / - (_histogramValues.sDValue * math.sqrt(2 * math.pi)); + yValue = math.exp(-(xValue - _histogramValues.mean!) * + (xValue - _histogramValues.mean!) / + (2 * _histogramValues.sDValue! * _histogramValues.sDValue!)) / + (_histogramValues.sDValue! * math.sqrt(2 * math.pi)); pointLocation = _calculatePoint( xValue, - yValue * _histogramValues.binWidth * _histogramValues.yValues.length, - _xAxisRenderer, - _yAxisRenderer, - _chartState._requireInvertedAxis, + yValue * + _histogramValues.binWidth! * + _histogramValues.yValues!.length, + _xAxisRenderer!, + _yAxisRenderer!, + _chartState!._requireInvertedAxis, series, - _chartState._chartAxis._axisClipRect); + _chartState!._chartAxis._axisClipRect); i == 0 ? path.moveTo(pointLocation.x, pointLocation.y) : path.lineTo(pointLocation.x, pointLocation.y); @@ -430,49 +424,51 @@ class HistogramSeriesRenderer extends XyDataSeriesRenderer { int pointIndex, _VisibleRange sideBySideInfo, int seriesIndex, - num animateFactor) { + double animateFactor) { _segment = createSegment(); - _oldSeriesRenderers = _chartState._oldSeriesRenderers; - _histogramSeries = _series; + _oldSeriesRenderers = _chartState!._oldSeriesRenderers; + _histogramSeries = _series as HistogramSeries; _borderRadius = _histogramSeries.borderRadius; _segment._seriesRenderer = this; _segment._series = _histogramSeries; _segment._chart = _chart; - _segment._chartState = _chartState; + _segment._chartState = _chartState!; _segment._seriesIndex = seriesIndex; _segment.currentSegmentIndex = pointIndex; _segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); + .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); _segment.animationFactor = animateFactor; - final num origin = math.max(_yAxisRenderer._visibleRange.minimum, 0); + final num origin = math.max(_yAxisRenderer!._visibleRange!.minimum, 0); currentPoint.region = _calculateRectangle( currentPoint.xValue + sideBySideInfo.minimum, currentPoint.yValue, currentPoint.xValue + sideBySideInfo.maximum, - math.max(_yAxisRenderer._visibleRange.minimum, 0), + math.max(_yAxisRenderer!._visibleRange!.minimum, 0), this, - _chartState); + _chartState!); _segment._currentPoint = currentPoint; - if (_chartState._widgetNeedUpdate && - !_chartState._isLegendToggled && + if (_chartState!._widgetNeedUpdate && + !_chartState!._isLegendToggled && _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= _segment._seriesIndex && - _oldSeriesRenderers[_segment._seriesIndex]._seriesName == + _oldSeriesRenderers!.isNotEmpty && + _oldSeriesRenderers!.length - 1 >= _segment._seriesIndex && + _oldSeriesRenderers![_segment._seriesIndex]._seriesName == _segment._seriesRenderer._seriesName) { - _segment._oldSeriesRenderer = _oldSeriesRenderers[_segment._seriesIndex]; - _segment._oldPoint = (_segment._oldSeriesRenderer._segments.isNotEmpty && - _segment._oldSeriesRenderer._segments[0] is HistogramSegment && - _segment._oldSeriesRenderer._dataPoints.length - 1 >= pointIndex) - ? _segment._oldSeriesRenderer._dataPoints[pointIndex] + _segment._oldSeriesRenderer = _oldSeriesRenderers![_segment._seriesIndex]; + _segment._oldPoint = (_segment._oldSeriesRenderer!._segments.isNotEmpty && + _segment._oldSeriesRenderer!._segments[0] is HistogramSegment && + _segment._oldSeriesRenderer!._dataPoints.length - 1 >= pointIndex) + ? _segment._oldSeriesRenderer!._dataPoints[pointIndex] : null; - } else if (_chartState._isLegendToggled && - _chartState._segments != null && - _chartState._segments.isNotEmpty) { + _segment._oldSegmentIndex = _getOldSegmentIndex(_segment); + } else if (_chartState!._isLegendToggled && + _chartState!._segments != null && + _chartState!._segments.isNotEmpty) { _segment._oldSeriesVisible = - _chartState._oldSeriesVisible[_segment._seriesIndex]; - for (int i = 0; i < _chartState._segments.length; i++) { - final HistogramSegment oldSegment = _chartState._segments[i]; + _chartState!._oldSeriesVisible[_segment._seriesIndex]; + for (int i = 0; i < _chartState!._segments.length; i++) { + final HistogramSegment oldSegment = + _chartState!._segments[i] as HistogramSegment; if (oldSegment.currentSegmentIndex == _segment.currentSegmentIndex && oldSegment._seriesIndex == _segment._seriesIndex) { _segment._oldRegion = oldSegment.segmentRect.outerRect; @@ -483,7 +479,7 @@ class HistogramSeriesRenderer extends XyDataSeriesRenderer { currentPoint, _histogramSeries.borderWidth); if (_borderRadius != null) { _segment.segmentRect = - _getRRectFromRect(currentPoint.region, _borderRadius); + _getRRectFromRect(currentPoint.region!, _borderRadius!); } //Tracker rect if (_histogramSeries.isTrackVisible) { @@ -493,12 +489,12 @@ class HistogramSeriesRenderer extends XyDataSeriesRenderer { currentPoint.xValue + sideBySideInfo.maximum, origin, this, - _chartState, - Offset(_segment._seriesRenderer._xAxisRenderer._axis?.plotOffset, - _segment._seriesRenderer._yAxisRenderer._axis?.plotOffset)); + _chartState!, + Offset(_segment._seriesRenderer._xAxisRenderer!._axis.plotOffset, + _segment._seriesRenderer._yAxisRenderer!._axis.plotOffset)); if (_borderRadius != null) { _segment._trackRect = - _getRRectFromRect(currentPoint.trackerRectRegion, _borderRadius); + _getRRectFromRect(currentPoint.trackerRectRegion!, _borderRadius!); } } _segment._segmentRect = _segment.segmentRect; @@ -511,24 +507,25 @@ class HistogramSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (_segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = _segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => HistogramSegment(); + HistogramSegment createSegment() => HistogramSegment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment segment) { - final HistogramSegment histogramSegment = segment; - histogramSegment._color = histogramSegment._currentPoint.pointColorMapper ?? - segment._seriesRenderer._seriesColor; + final HistogramSegment histogramSegment = segment as HistogramSegment; + histogramSegment._color = + histogramSegment._currentPoint!.pointColorMapper ?? + segment._seriesRenderer._seriesColor; histogramSegment._strokeColor = segment._series.borderColor; histogramSegment._strokeWidth = segment._series.borderWidth; histogramSegment.strokePaint = histogramSegment.getStrokePaint(); @@ -543,9 +540,9 @@ class HistogramSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/line_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/line_series.dart index f02b0e70f..39251186f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/line_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/line_series.dart @@ -11,37 +11,37 @@ part of charts; class LineSeries extends XyDataSeries { /// Creating an argument constructor of LineSeries class. LineSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - String xAxisName, - String yAxisName, - Color color, - double width, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - List trendlines, - bool isVisible, - String name, - bool enableTooltip, - List dashArray, - double animationDuration, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + String? xAxisName, + String? yAxisName, + Color? color, + double? width, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + List? trendlines, + bool? isVisible, + String? name, + bool? enableTooltip, + List? dashArray, + double? animationDuration, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - SortingOrder sortingOrder, - String legendItemText, - double opacity, - List initialSelectedDataIndexes, - SeriesRendererCreatedCallback onRendererCreated}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + SortingOrder? sortingOrder, + String? legendItemText, + double? opacity, + List? initialSelectedDataIndexes, + SeriesRendererCreatedCallback? onRendererCreated}) : super( key: key, onRendererCreated: onRendererCreated, @@ -78,7 +78,7 @@ class LineSeries extends XyDataSeries { LineSeriesRenderer createRenderer(ChartSeries series) { LineSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as LineSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -92,9 +92,9 @@ class LineSeriesRenderer extends XyDataSeriesRenderer { /// Calling the default constructor of LineSeriesRenderer class. LineSeriesRenderer(); - LineSegment _lineSegment, _segment; + late LineSegment _lineSegment, _segment; - List _oldSeriesRenderers; + List? _oldSeriesRenderers; /// To add line segments to segments list ChartSegment _createSegments( @@ -102,26 +102,27 @@ class LineSeriesRenderer extends XyDataSeriesRenderer { CartesianChartPoint _nextPoint, int pointIndex, int seriesIndex, - num animateFactor) { + double animateFactor) { _segment = createSegment(); - _oldSeriesRenderers = _chartState._oldSeriesRenderers; - _segment._series = _series; + _oldSeriesRenderers = _chartState!._oldSeriesRenderers; + _segment._series = _series as XyDataSeries; _segment._seriesRenderer = this; _segment._seriesIndex = seriesIndex; _segment._currentPoint = currentPoint; _segment.currentSegmentIndex = pointIndex; _segment._nextPoint = _nextPoint; _segment._chart = _chart; - _segment._chartState = _chartState; + _segment._chartState = _chartState!; _segment.animationFactor = animateFactor; _segment._pointColorMapper = currentPoint.pointColorMapper; - if (_chartState._widgetNeedUpdate && + if (_chartState!._widgetNeedUpdate && _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= _segment._seriesIndex && - _oldSeriesRenderers[_segment._seriesIndex]._seriesName == + _oldSeriesRenderers!.isNotEmpty && + _oldSeriesRenderers!.length - 1 >= _segment._seriesIndex && + _oldSeriesRenderers![_segment._seriesIndex]._seriesName == _segment._seriesRenderer._seriesName) { - _segment._oldSeriesRenderer = _oldSeriesRenderers[_segment._seriesIndex]; + _segment._oldSeriesRenderer = _oldSeriesRenderers![_segment._seriesIndex]; + _segment._oldSegmentIndex = _getOldSegmentIndex(_segment); } _segment.calculateSegmentPoints(); _segment.points.add(Offset(_segment._x1, _segment._y1)); @@ -135,22 +136,22 @@ class LineSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment _segment) { if (_segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = _segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[_segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[_segment.currentSegmentIndex!], _chart); } _segment.onPaint(canvas); } /// Creates a _segment for a data point in the series. @override - ChartSegment createSegment() => LineSegment(); + LineSegment createSegment() => LineSegment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment _segment) { - _lineSegment = _segment; + _lineSegment = _segment as LineSegment; _lineSegment._color = _lineSegment._pointColorMapper ?? _lineSegment._series.color ?? _lineSegment._seriesRenderer._seriesColor; @@ -166,9 +167,9 @@ class LineSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_area_series.dart index a5c778f0b..cc949acb2 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_area_series.dart @@ -12,43 +12,42 @@ part of charts; class RangeAreaSeries extends XyDataSeries { /// Creating an argument constructor of RangeAreaSeries class. RangeAreaSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper highValueMapper, - @required ChartValueMapper lowValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String xAxisName, - List trendlines, - String yAxisName, - String name, - Color color, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - bool enableTooltip, - List dashArray, - double animationDuration, - Color borderColor, - double borderWidth, - LinearGradient gradient, - LinearGradient borderGradient, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper highValueMapper, + required ChartValueMapper lowValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? xAxisName, + List? trendlines, + String? yAxisName, + String? name, + Color? color, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + bool? enableTooltip, + List? dashArray, + double? animationDuration, + Color? borderColor, + double? borderWidth, + LinearGradient? gradient, + LinearGradient? borderGradient, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - RangeAreaBorderMode borderDrawMode, - SeriesRendererCreatedCallback onRendererCreated}) - : borderDrawMode = borderDrawMode ?? RangeAreaBorderMode.all, - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + this.borderDrawMode = RangeAreaBorderMode.all, + SeriesRendererCreatedCallback? onRendererCreated}) + : super( key: key, onCreateRenderer: onCreateRenderer, xValueMapper: xValueMapper, @@ -107,7 +106,7 @@ class RangeAreaSeries extends XyDataSeries { RangeAreaSeriesRenderer createRenderer(ChartSeries series) { RangeAreaSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as RangeAreaSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -123,20 +122,21 @@ class RangeAreaSeriesRenderer extends XyDataSeriesRenderer { /// Range Area segment is created here ChartSegment _createSegments( - int seriesIndex, SfCartesianChart chart, num animateFactor, - [List _points]) { + int seriesIndex, SfCartesianChart chart, double animateFactor, + [List? _points]) { final RangeAreaSegment segment = createSegment(); _isRectSeries = false; if (segment != null) { segment._seriesIndex = seriesIndex; segment.animationFactor = animateFactor; segment._seriesRenderer = this; - segment._series = _series; - segment.points = _points; + segment._series = _series as XyDataSeries; + if (_points != null) segment.points = _points; customizeSegment(segment); segment._chart = chart; segment.strokePaint = segment.getStrokePaint(); segment.fillPaint = segment.getFillPaint(); + segment._oldSegmentIndex = 0; _segments.add(segment); } return segment; @@ -146,17 +146,17 @@ class RangeAreaSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer - ._checkWithSelectionState(_segments[0], _chart); + selectionBehaviorRenderer?._selectionRenderer + ?._checkWithSelectionState(_segments[0], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => RangeAreaSegment(); + RangeAreaSegment createSegment() => RangeAreaSegment(); /// Changes the series color, border color, and border width. @override @@ -170,11 +170,11 @@ class RangeAreaSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes2[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); - canvas.drawPath(seriesRenderer._markerShapes2[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes2[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); + canvas.drawPath(seriesRenderer._markerShapes2[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart index ca708e946..b049b4ca7 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart @@ -13,56 +13,50 @@ part of charts; class RangeColumnSeries extends XyDataSeries { /// Creating an argument constructor of RangeColumnSeries class. RangeColumnSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper highValueMapper, - @required ChartValueMapper lowValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper highValueMapper, + required ChartValueMapper lowValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, this.isTrackVisible = false, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - double spacing, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - LinearGradient gradient, - LinearGradient borderGradient, - BorderRadius borderRadius, - bool enableTooltip, - double animationDuration, - Color trackColor, - Color trackBorderColor, - double trackBorderWidth, - double trackPadding, - Color borderColor, - List trendlines, - double borderWidth, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + this.spacing = 0, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + LinearGradient? gradient, + LinearGradient? borderGradient, + this.borderRadius = const BorderRadius.all(Radius.zero), + bool? enableTooltip, + double? animationDuration, + this.trackColor = Colors.grey, + this.trackBorderColor = Colors.transparent, + this.trackBorderWidth = 1, + this.trackPadding = 0, + Color? borderColor, + List? trendlines, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - List dashArray, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) - : trackColor = trackColor ?? Colors.grey, - trackBorderColor = trackBorderColor ?? Colors.transparent, - trackBorderWidth = trackBorderWidth ?? 1, - trackPadding = trackPadding ?? 0, - spacing = spacing ?? 0, - borderRadius = borderRadius ?? const BorderRadius.all(Radius.zero), - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + List? dashArray, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) + : super( key: key, onCreateRenderer: onCreateRenderer, name: name, @@ -245,7 +239,7 @@ class RangeColumnSeries extends XyDataSeries { RangeColumnSeriesRenderer createRenderer(ChartSeries series) { RangeColumnSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as RangeColumnSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -260,54 +254,56 @@ class RangeColumnSeriesRenderer extends XyDataSeriesRenderer { RangeColumnSeriesRenderer(); // Store the rect position // - num _rectPosition; + late num _rectPosition; // Store the rect count // - num _rectCount; + late num _rectCount; /// To add range column segments in segments list ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { + int pointIndex, int seriesIndex, double animateFactor) { _isRectSeries = true; final RangeColumnSegment segment = createSegment(); - final List oldSeriesRenderers = - _chartState._oldSeriesRenderers; - final RangeColumnSeries _rangeColumnSeries = _series; + final List? oldSeriesRenderers = + _chartState!._oldSeriesRenderers; + final RangeColumnSeries _rangeColumnSeries = + _series as RangeColumnSeries; final BorderRadius borderRadius = _rangeColumnSeries.borderRadius; segment._seriesIndex = seriesIndex; segment.currentSegmentIndex = pointIndex; segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); - segment.points - .add(Offset(currentPoint.markerPoint2.x, currentPoint.markerPoint2.y)); + .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segment.points.add( + Offset(currentPoint.markerPoint2!.x, currentPoint.markerPoint2!.y)); segment._seriesRenderer = this; segment._series = _rangeColumnSeries; segment._chart = _chart; - segment._chartState = _chartState; + segment._chartState = _chartState!; segment.animationFactor = animateFactor; segment._currentPoint = currentPoint; - if (_chartState._widgetNeedUpdate && - _chartState._zoomPanBehaviorRenderer._isPinching != true && - !_chartState._isLegendToggled && + if (_chartState!._widgetNeedUpdate && + _chartState!._zoomPanBehaviorRenderer._isPinching != true && + !_chartState!._isLegendToggled && oldSeriesRenderers != null && oldSeriesRenderers.isNotEmpty && oldSeriesRenderers.length - 1 >= segment._seriesIndex && oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldPoint = (segment._oldSeriesRenderer._segments.isNotEmpty && - segment._oldSeriesRenderer._segments[0] is RangeColumnSegment && - segment._oldSeriesRenderer._dataPoints.length - 1 >= pointIndex) - ? segment._oldSeriesRenderer._dataPoints[pointIndex] + segment._oldPoint = (segment._oldSeriesRenderer!._segments.isNotEmpty && + segment._oldSeriesRenderer!._segments[0] is RangeColumnSegment && + segment._oldSeriesRenderer!._dataPoints.length - 1 >= pointIndex) + ? segment._oldSeriesRenderer!._dataPoints[pointIndex] : null; - } else if (_chartState._isLegendToggled && - _chartState._segments != null && - _chartState._segments.isNotEmpty) { + segment._oldSegmentIndex = _getOldSegmentIndex(segment); + } else if (_chartState!._isLegendToggled && + _chartState!._segments != null && + _chartState!._segments.isNotEmpty) { segment._oldSeriesVisible = - _chartState._oldSeriesVisible[segment._seriesIndex]; + _chartState!._oldSeriesVisible[segment._seriesIndex]; RangeColumnSegment oldSegment; - for (int i = 0; i < _chartState._segments.length; i++) { - oldSegment = _chartState._segments[i]; + for (int i = 0; i < _chartState!._segments.length; i++) { + oldSegment = _chartState!._segments[i] as RangeColumnSegment; if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && oldSegment._seriesIndex == segment._seriesIndex) { segment._oldRegion = oldSegment.segmentRect.outerRect; @@ -318,12 +314,12 @@ class RangeColumnSeriesRenderer extends XyDataSeriesRenderer { currentPoint, _rangeColumnSeries.borderWidth); if (borderRadius != null) { segment.segmentRect = - _getRRectFromRect(currentPoint.region, borderRadius); + _getRRectFromRect(currentPoint.region!, borderRadius); //Tracker rect if (_rangeColumnSeries.isTrackVisible) { segment._trackRect = - _getRRectFromRect(currentPoint.trackerRectRegion, borderRadius); + _getRRectFromRect(currentPoint.trackerRectRegion!, borderRadius); } } segment._segmentRect = segment.segmentRect; @@ -336,22 +332,22 @@ class RangeColumnSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => RangeColumnSegment(); + RangeColumnSegment createSegment() => RangeColumnSegment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment segment) { - final RangeColumnSegment rangeColumnSegment = segment; + final RangeColumnSegment rangeColumnSegment = segment as RangeColumnSegment; rangeColumnSegment._color = segment._seriesRenderer._seriesColor; rangeColumnSegment._strokeColor = segment._series.borderColor; rangeColumnSegment._strokeWidth = segment._series.borderWidth; @@ -367,11 +363,11 @@ class RangeColumnSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes2[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); - canvas.drawPath(seriesRenderer._markerShapes2[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes2[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); + canvas.drawPath(seriesRenderer._markerShapes2[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart index a973c6314..8dec01094 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart @@ -9,39 +9,39 @@ part of charts; class ScatterSeries extends XyDataSeries { /// Creating an argument constructor of ScatterSeries class. ScatterSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - String xAxisName, - String yAxisName, - String name, - Color color, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - bool isVisible, - DataLabelSettings dataLabelSettings, - bool enableTooltip, - List trendlines, - double animationDuration, - Color borderColor, - double borderWidth, - LinearGradient gradient, - LinearGradient borderGradient, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + bool? isVisible, + DataLabelSettings? dataLabelSettings, + bool? enableTooltip, + List? trendlines, + double? animationDuration, + Color? borderColor, + double? borderWidth, + LinearGradient? gradient, + LinearGradient? borderGradient, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - SortingOrder sortingOrder, - String legendItemText, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + SortingOrder? sortingOrder, + String? legendItemText, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -80,7 +80,7 @@ class ScatterSeries extends XyDataSeries { ScatterSeriesRenderer createRenderer(ChartSeries series) { ScatterSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as ScatterSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -95,50 +95,52 @@ class ScatterSeriesRenderer extends XyDataSeriesRenderer { ScatterSeriesRenderer(); // ignore:unused_field - CartesianChartPoint _point; + CartesianChartPoint? _point; final bool _isLineType = false; ///Adds the points to the segments . ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { + int pointIndex, int seriesIndex, double animateFactor) { final ScatterSegment segment = createSegment(); final List oldSeriesRenderers = - _chartState._oldSeriesRenderers; + _chartState!._oldSeriesRenderers; _isRectSeries = false; if (segment != null) { segment._seriesIndex = seriesIndex; segment.currentSegmentIndex = pointIndex; segment._seriesRenderer = this; - segment._series = _series; + segment._series = _series as XyDataSeries; segment.animationFactor = animateFactor; segment._point = currentPoint; segment._currentPoint = currentPoint; - if (_chartState._widgetNeedUpdate && - !_chartState._isLegendToggled && + if (_chartState!._widgetNeedUpdate && + !_chartState!._isLegendToggled && oldSeriesRenderers != null && oldSeriesRenderers.isNotEmpty && oldSeriesRenderers.length - 1 >= segment._seriesIndex && oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldPoint = (segment._oldSeriesRenderer._segments.isNotEmpty && - segment._oldSeriesRenderer._segments[0] is ScatterSegment && - segment._oldSeriesRenderer._dataPoints.length - 1 >= pointIndex) - ? segment._oldSeriesRenderer._dataPoints[pointIndex] + segment._oldPoint = (segment._oldSeriesRenderer!._segments.isNotEmpty && + segment._oldSeriesRenderer!._segments[0] is ScatterSegment && + segment._oldSeriesRenderer!._dataPoints.length - 1 >= + pointIndex) + ? segment._oldSeriesRenderer!._dataPoints[pointIndex] : null; + segment._oldSegmentIndex = _getOldSegmentIndex(segment); } final _ChartLocation location = _calculatePoint( currentPoint.xValue, currentPoint.yValue, - _xAxisRenderer, - _yAxisRenderer, - _chartState._requireInvertedAxis, + _xAxisRenderer!, + _yAxisRenderer!, + _chartState!._requireInvertedAxis, _series, - _chartState._chartAxis._axisClipRect); + _chartState!._chartAxis._axisClipRect); segment.points.add(Offset(location.x, location.y)); segment._segmentRect = - RRect.fromRectAndRadius(currentPoint.region, Radius.zero); + RRect.fromRectAndRadius(currentPoint.region!, Radius.zero); customizeSegment(segment); _segments.add(segment); } @@ -149,25 +151,24 @@ class ScatterSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => ScatterSegment(); + ScatterSegment createSegment() => ScatterSegment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment segment) { - final ScatterSegment scatterSegment = segment; + final ScatterSegment scatterSegment = segment as ScatterSegment; scatterSegment._color = scatterSegment._seriesRenderer._seriesColor; - scatterSegment._strokeColor = scatterSegment._series.borderColor ?? - scatterSegment._seriesRenderer._seriesColor; + scatterSegment._strokeColor = scatterSegment._series.borderColor; scatterSegment._strokeWidth = ((scatterSegment._series.markerSettings.shape == DataMarkerType.verticalLine || @@ -184,11 +185,11 @@ class ScatterSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { + [CartesianSeriesRenderer? seriesRenderer]) { final Size size = Size(_series.markerSettings.width, _series.markerSettings.height); final Path markerPath = _getMarkerShapesPath(_series.markerSettings.shape, - Offset(pointX, pointY), size, seriesRenderer, index); + Offset(pointX, pointY), size, seriesRenderer!, index); canvas.drawPath(markerPath, fillPaint); canvas.drawPath(markerPath, strokePaint); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart index 041f8c762..802cbccf4 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart @@ -16,7 +16,7 @@ abstract class CartesianSeries extends ChartSeries { this.yValueMapper, this.dataLabelMapper, this.name, - this.dataSource, + required this.dataSource, this.xAxisName, this.yAxisName, this.sizeValueMapper, @@ -34,23 +34,23 @@ abstract class CartesianSeries extends ChartSeries { this.trendlines, this.onRendererCreated, this.onCreateRenderer, - MarkerSettings markerSettings, - bool isVisible, - bool enableTooltip, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - double animationDuration, - List dashArray, - List initialSelectedDataIndexes, - Color borderColor, - double borderWidth, + MarkerSettings? markerSettings, + bool? isVisible, + bool? enableTooltip, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + double? animationDuration, + List? dashArray, + List? initialSelectedDataIndexes, + Color? borderColor, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - double opacity, - SortingOrder sortingOrder}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + double? opacity, + SortingOrder? sortingOrder}) : isVisible = isVisible ?? true, markerSettings = markerSettings ?? MarkerSettings(), dataLabelSettings = dataLabelSettings ?? DataLabelSettings(), @@ -115,7 +115,7 @@ abstract class CartesianSeries extends ChartSeries { /// )); ///} ///``` - final ValueKey key; + final ValueKey? key; ///Used to create the renderer for custom series. /// @@ -148,7 +148,7 @@ abstract class CartesianSeries extends ChartSeries { /// // custom implementation here... /// } ///``` - final ChartSeriesRendererFactory onCreateRenderer; + final ChartSeriesRendererFactory? onCreateRenderer; ///Triggers when the series renderer is created. @@ -175,7 +175,7 @@ abstract class CartesianSeries extends ChartSeries { /// )); ///} ///``` - final SeriesRendererCreatedCallback onRendererCreated; + final SeriesRendererCreatedCallback? onRendererCreated; ///Data required for rendering the series. /// @@ -241,7 +241,7 @@ abstract class CartesianSeries extends ChartSeries { ///} ///``` @override - final ChartIndexedValueMapper xValueMapper; + final ChartIndexedValueMapper? xValueMapper; ///Field in the data source, which is considered as y-value. /// @@ -273,7 +273,7 @@ abstract class CartesianSeries extends ChartSeries { ///} ///``` @override - final ChartIndexedValueMapper yValueMapper; + final ChartIndexedValueMapper? yValueMapper; ///Field in the data source, which is considered as fill color for the data points. /// @@ -306,7 +306,7 @@ abstract class CartesianSeries extends ChartSeries { ///} ///``` @override - final ChartIndexedValueMapper pointColorMapper; + final ChartIndexedValueMapper? pointColorMapper; ///Field in the data source, which is considered as text for the data points. /// @@ -337,7 +337,7 @@ abstract class CartesianSeries extends ChartSeries { ///} ///``` @override - final ChartIndexedValueMapper dataLabelMapper; + final ChartIndexedValueMapper? dataLabelMapper; ///Field in the data source, which is considered as size of the bubble for ///all the data points. @@ -372,7 +372,7 @@ abstract class CartesianSeries extends ChartSeries { /// final Color pointColorMapper; ///} ///``` - final ChartIndexedValueMapper sizeValueMapper; + final ChartIndexedValueMapper? sizeValueMapper; ///Field in the data source, which is considered as high value for the data points. /// @@ -401,7 +401,7 @@ abstract class CartesianSeries extends ChartSeries { /// final num low; ///} ///``` - final ChartIndexedValueMapper highValueMapper; + final ChartIndexedValueMapper? highValueMapper; ///Field in the data source, which is considered as low value for the data points. /// @@ -430,7 +430,7 @@ abstract class CartesianSeries extends ChartSeries { /// final num low; ///} ///``` - final ChartIndexedValueMapper lowValueMapper; + final ChartIndexedValueMapper? lowValueMapper; ///A boolean value, based on which the data point will be considered as intermediate sum or not. /// @@ -468,7 +468,7 @@ abstract class CartesianSeries extends ChartSeries { /// final bool isIntermediate; ///} ///``` - final ChartIndexedValueMapper intermediateSumPredicate; + final ChartIndexedValueMapper? intermediateSumPredicate; ///A boolean value, based on which the data point will be considered as total sum or not. /// @@ -506,7 +506,7 @@ abstract class CartesianSeries extends ChartSeries { /// final bool isTotalSum; ///} ///``` - final ChartIndexedValueMapper totalSumPredicate; + final ChartIndexedValueMapper? totalSumPredicate; ///Name of the x-axis to bind the series. /// @@ -540,7 +540,7 @@ abstract class CartesianSeries extends ChartSeries { /// BubbleColors(99.4, 2.2, 0.197, const Color.fromRGBO(122, 100, 255, 1)), /// ]; ///``` - final String xAxisName; + final String? xAxisName; ///Name of the y-axis to bind the series. /// @@ -574,7 +574,7 @@ abstract class CartesianSeries extends ChartSeries { /// BubbleColors(99.4, 2.2, 0.197, const Color.fromRGBO(122, 100, 255, 1)), /// ]; ///``` - final String yAxisName; + final String? yAxisName; ///Color of the series. /// @@ -595,7 +595,7 @@ abstract class CartesianSeries extends ChartSeries { /// )); ///} ///``` - final Color color; + final Color? color; ///Width of the series. /// @@ -619,7 +619,7 @@ abstract class CartesianSeries extends ChartSeries { /// )); ///} ///``` - final double width; + final double? width; ///Indication of data points. /// @@ -694,7 +694,7 @@ abstract class CartesianSeries extends ChartSeries { /// )); ///} ///``` - final List trendlines; + final List? trendlines; ///Fills the chart series with gradient color. /// @@ -724,7 +724,7 @@ abstract class CartesianSeries extends ChartSeries { /// )); ///} ///``` - final LinearGradient gradient; + final LinearGradient? gradient; ///Fills the border of the chart series with gradient color. /// @@ -755,7 +755,7 @@ abstract class CartesianSeries extends ChartSeries { /// )); ///} ///``` - final LinearGradient borderGradient; + final LinearGradient? borderGradient; ///Name of the series. /// @@ -776,7 +776,7 @@ abstract class CartesianSeries extends ChartSeries { ///} ///``` @override - final String name; + final String? name; ///Enables or disables the tooltip for this series. /// @@ -947,7 +947,7 @@ abstract class CartesianSeries extends ChartSeries { ///} ///``` @override - final String legendItemText; + final String? legendItemText; ///Customizes the data points or series on selection. /// @@ -1046,7 +1046,7 @@ abstract class CartesianSeries extends ChartSeries { ///} ///``` @override - final ChartIndexedValueMapper sortFieldValueMapper; + final ChartIndexedValueMapper? sortFieldValueMapper; ///The data points in the series can be sorted in ascending or descending order. /// @@ -1105,22 +1105,23 @@ abstract class CartesianSeries extends ChartSeries { /// ) /// ); /// } - final List initialSelectedDataIndexes; + final List? initialSelectedDataIndexes; } /// Creates a series renderer for Chart series abstract class ChartSeriesRenderer { - String _seriesName; + String? _seriesName; //ignore: prefer_final_fields - bool _visible; + bool? _visible; //ignore: prefer_final_fields bool _needsRepaint = true; - SfCartesianChart _chart; + late SfCartesianChart _chart; } +/// Called when the series renderer is created typedef SeriesRendererCreatedCallback = void Function( ChartSeriesController controller); @@ -1206,12 +1207,12 @@ class ChartSeriesController { /// } ///``` void updateDataSource( - {List addedDataIndexes, - List removedDataIndexes, - List updatedDataIndexes, - int addedDataIndex, - int removedDataIndex, - int updatedDataIndex}) { + {List? addedDataIndexes, + List? removedDataIndexes, + List? updatedDataIndexes, + int? addedDataIndex, + int? removedDataIndex, + int? updatedDataIndex}) { bool _needUpdate = false; if (removedDataIndexes != null && removedDataIndexes.isNotEmpty) { _removeDataPointsList(removedDataIndexes); @@ -1248,18 +1249,27 @@ class ChartSeriesController { if (index >= 0 && series.dataSource.length > index && series.dataSource[index] != null) { - final _VisibleRange xRange = seriesRenderer._xAxisRenderer._visibleRange; - final _VisibleRange yRange = seriesRenderer._yAxisRenderer._visibleRange; + final _VisibleRange xRange = + seriesRenderer._xAxisRenderer!._visibleRange!; + final _VisibleRange yRange = + seriesRenderer._yAxisRenderer!._visibleRange!; final CartesianChartPoint currentPoint = - _getChartPoint(seriesRenderer, series.dataSource[index], index); + _getChartPoint(seriesRenderer, series.dataSource[index], index)!; final String seriesType = seriesRenderer._seriesType; dynamic x = currentPoint.x; - if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) { + if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer || + seriesRenderer._xAxisRenderer is DateTimeCategoryAxisRenderer) { x = x.millisecondsSinceEpoch; } else if (seriesRenderer._xAxisRenderer is LogarithmicAxisRenderer) { - final LogarithmicAxis axis = seriesRenderer._xAxisRenderer._axis; + final LogarithmicAxis axis = + seriesRenderer._xAxisRenderer!._axis as LogarithmicAxis; + currentPoint.xValue = currentPoint.x; x = _calculateLogBaseValue(x > 1 ? x : 1, axis.logBase); + } else if (seriesRenderer._xAxisRenderer is CategoryAxisRenderer) { + x = index; } + currentPoint.xValue ??= x; + currentPoint.yValue = currentPoint.y; if (!_needXRecalculation && (xRange.minimum >= x || xRange.maximum <= x)) { _needXRecalculation = true; @@ -1267,7 +1277,8 @@ class ChartSeriesController { num minYVal = currentPoint.y ?? currentPoint.low; num maxYVal = currentPoint.y ?? currentPoint.high; if (seriesRenderer._yAxisRenderer is LogarithmicAxisRenderer) { - final LogarithmicAxis axis = seriesRenderer._yAxisRenderer._axis; + final LogarithmicAxis axis = + seriesRenderer._yAxisRenderer!._axis as LogarithmicAxis; minYVal = _calculateLogBaseValue(minYVal > 1 ? minYVal : 1, axis.logBase); maxYVal = @@ -1354,19 +1365,19 @@ class ChartSeriesController { ///``` CartesianChartPoint pixelToPoint(Offset position) { - ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final ChartAxis xAxis = xAxisRenderer._axis; final ChartAxis yAxis = yAxisRenderer._axis; final CartesianSeries series = seriesRenderer._series; - final Rect rect = seriesRenderer._chartState._chartAxis._axisClipRect; + final Rect rect = seriesRenderer._chartState!._chartAxis._axisClipRect; if (series.xAxisName != null || series.yAxisName != null) { for (final ChartAxisRenderer axisRenderer - in seriesRenderer._chartState._chartAxis._axisRenderersCollection) { + in seriesRenderer._chartState!._chartAxis._axisRenderersCollection) { if (xAxis.name == series.xAxisName) { xAxisRenderer = axisRenderer; } else if (yAxis.name == series.yAxisName) { @@ -1379,26 +1390,26 @@ class ChartSeriesController { } num xValue = _pointToXValue( - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, xAxisRenderer, xAxisRenderer._bounds, position.dx - (rect.left + xAxis.plotOffset), position.dy - (rect.top + yAxis.plotOffset)); num yValue = _pointToYValue( - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, yAxisRenderer, yAxisRenderer._bounds, position.dx - (rect.left + xAxis.plotOffset), position.dy - (rect.top + yAxis.plotOffset)); if (xAxisRenderer is LogarithmicAxisRenderer) { - final LogarithmicAxis axis = xAxis; + final LogarithmicAxis axis = xAxis as LogarithmicAxis; xValue = math.pow(xValue, _calculateLogBaseValue(xValue, axis.logBase)); } else { xValue = xValue; } if (yAxisRenderer is LogarithmicAxisRenderer) { - final LogarithmicAxis axis = yAxis; + final LogarithmicAxis axis = yAxis as LogarithmicAxis; yValue = math.pow(yValue, _calculateLogBaseValue(yValue, axis.logBase)); } else { yValue = yValue; @@ -1444,10 +1455,10 @@ class ChartSeriesController { final num x = point.x; final num y = point.y; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final bool isInverted = seriesRenderer._chartState._requireInvertedAxis; + final bool isInverted = seriesRenderer._chartState!._requireInvertedAxis; final CartesianSeries series = seriesRenderer._series; final _ChartLocation location = _calculatePoint( @@ -1457,13 +1468,13 @@ class ChartSeriesController { yAxisRenderer, isInverted, series, - seriesRenderer._chartState._containerRect); + seriesRenderer._chartState!._containerRect); return Offset(location.x, location.y); } ///If you wish to perform initial animation again in the existing series, this method can be called. - /// On calling this method, this particular series will be animated again based on the [animationDuration] + /// On calling this method, this particular series will be animated again based on the `animationDuration` /// property's value in the series. If the value is 0, then the animation will not be performed. /// ///```dart @@ -1491,34 +1502,28 @@ class ChartSeriesController { /// } ///``` void animate() { - if (seriesRenderer._visible && + if (seriesRenderer._visible! && seriesRenderer._series.animationDuration > 0) { - final SfCartesianChartState chartState = seriesRenderer._chartState; + final SfCartesianChartState chartState = seriesRenderer._chartState!; final SfCartesianChart chart = chartState._chart; final TooltipBehavior tooltip = chart.tooltipBehavior; final TrackballBehavior trackball = chart.trackballBehavior; - final _TooltipPainter tooltipPainter = - chartState._tooltipBehaviorRenderer._painter; - final _TooltipTemplate tooltipTemplate = - chartState._tooltipBehaviorRenderer._tooltipTemplate; - final _TrackballPainter trackballPainter = + final _TrackballPainter? trackballPainter = chartState._trackballBehaviorRenderer._trackballPainter; final TrackballBehaviorRenderer trackballBehaviorRenderer = chartState._trackballBehaviorRenderer; //This hides the tooltip if rendered for this current series renderer - if (tooltip != null && - tooltip.enable && + if (tooltip.enable && (tooltip.builder != null - ? tooltipTemplate.state.seriesIndex == + ? chartState._tooltipBehaviorRenderer._seriesIndex == seriesRenderer._segments[0]._seriesIndex - : tooltipPainter.currentSeries == seriesRenderer)) { + : chartState._tooltipBehaviorRenderer._currentSeries == + seriesRenderer)) { tooltip.hide(); } //This hides the trackball if rendered for this current series renderer - if (trackball != null && - trackball.enable && - trackballBehaviorRenderer != null) { + if (trackball.enable) { for (final point in trackballBehaviorRenderer._chartPointInfo) { if (point.seriesRenderer == seriesRenderer) { if (trackballPainter != null) { @@ -1526,10 +1531,10 @@ class ChartSeriesController { trackballPainter.canResetPath = true; break; } else { - final GlobalKey key = - trackballBehaviorRenderer._trackballTemplate.key; + final GlobalKey key = trackballBehaviorRenderer + ._trackballTemplate!.key as GlobalKey; final _TrackballTemplateState trackballTemplateState = - key.currentState; + key.currentState! as _TrackballTemplateState; trackballTemplateState.hideTrackballTemplate(); break; } @@ -1557,7 +1562,7 @@ class ChartSeriesController { chartState._animationCompleteCount = 0; chartState._forwardAnimation(seriesRenderer); //This animates the trendlines of the animating series. - if (seriesRenderer._trendlineRenderer != null) { + if (seriesRenderer._trendlineRenderer.isNotEmpty) { for (final TrendlineRenderer trendlineRenderer in seriesRenderer._trendlineRenderer) { if (trendlineRenderer._visible) { @@ -1589,6 +1594,10 @@ class ChartSeriesController { index < seriesRenderer._dataPoints.length) { final CartesianChartPoint currentPoint = seriesRenderer._dataPoints[index]; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + if (xAxisRenderer is DateTimeCategoryAxisRenderer) { + _needXRecalculation = true; + } seriesRenderer._dataPoints.removeAt(index); if (currentPoint != null) { if (!_needXRecalculation && @@ -1623,10 +1632,15 @@ class ChartSeriesController { void _updateCartesianSeries( bool needXRecalculation, bool needYRecalculation, bool needUpdate) { final SfCartesianChartState chartState = - seriesRenderer._xAxisRenderer._chartState; + seriesRenderer._xAxisRenderer!._chartState; + chartState._isRedrawByZoomPan = false; if (needXRecalculation || needYRecalculation || needUpdate) { if (needXRecalculation) { seriesRenderer._minimumX = seriesRenderer._maximumX = null; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + if (xAxisRenderer is DateTimeCategoryAxisRenderer) { + xAxisRenderer._labels.clear(); + } } if (needYRecalculation) { seriesRenderer._minimumY = seriesRenderer._maximumY = null; @@ -1634,15 +1648,15 @@ class ChartSeriesController { chartState._chartSeries._findSeriesMinMax(seriesRenderer); if (seriesRenderer._seriesType.contains('stacked')) { chartState._chartSeries - ?._calculateStackedValues(_findSeriesCollection(chartState)); + ._calculateStackedValues(_findSeriesCollection(chartState)); } } if (needXRecalculation) { - final dynamic axisRenderer = seriesRenderer._xAxisRenderer; + final dynamic axisRenderer = seriesRenderer._xAxisRenderer!; axisRenderer._calculateRangeAndInterval(chartState); } if (needYRecalculation) { - final dynamic axisRenderer = seriesRenderer._yAxisRenderer; + final dynamic axisRenderer = seriesRenderer._yAxisRenderer!; axisRenderer._calculateRangeAndInterval(chartState); } if (needXRecalculation || needYRecalculation) { @@ -1655,13 +1669,14 @@ class ChartSeriesController { } else { _repaintSeries(chartState, seriesRenderer); } + chartState._isLoadMoreIndicator = false; //This makes the update data source method work with dynamic animation(scheduled for release) // seriesRenderer._needsAnimation = seriesRenderer._needAnimateSeriesElements = // chartState.widgetNeedUpdate = true; // chartState.initialRender = false; - // seriesRenderer._chartState._totalAnimatingSeries = 1; - // seriesRenderer._chartState._animationCompleteCount = 0; - // seriesRenderer._chartState._forwardAnimation( + // seriesRenderer._chartState!._totalAnimatingSeries = 1; + // seriesRenderer._chartState!._animationCompleteCount = 0; + // seriesRenderer._chartState!._forwardAnimation( // seriesRenderer, seriesRenderer._series.animationDuration); } @@ -1671,7 +1686,7 @@ class ChartSeriesController { seriesRenderer._calculateRegion = true; seriesRenderer._repaintNotifier.value++; if (seriesRenderer._series.dataLabelSettings.isVisible) { - chartState._renderDataLabel.state.dataLabelRepaintNotifier.value++; + chartState._renderDataLabel?.state!.dataLabelRepaintNotifier.value++; } } } @@ -1679,20 +1694,19 @@ class ChartSeriesController { /// Creates a series renderer for cartesian series abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { /// Stores the series type - String _seriesType; + late String _seriesType; /// Whether to check the series is rect series or not // ignore: prefer_final_fields bool _isRectSeries = false; - final List<_ListControlPoints> _drawControlPoints = <_ListControlPoints>[]; + final List> _drawControlPoints = >[]; - final List<_ListControlPoints> _drawLowControlPoints = <_ListControlPoints>[]; + final List> _drawLowControlPoints = >[]; - final List<_ListControlPoints> _drawHighControlPoints = - <_ListControlPoints>[]; + final List> _drawHighControlPoints = >[]; - Path _segmentPath; + Path? _segmentPath; /// Gets the Segments collection variable declarations. // ignore: prefer_final_fields @@ -1700,10 +1714,10 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { //Maintain the old series state. //ignore: unused_field - CartesianSeries _oldSeries; + CartesianSeries? _oldSeries; //Store the current series state - CartesianSeries _series; + late CartesianSeries _series; /// Holds the collection of cartesian data points // ignore: prefer_final_fields @@ -1711,53 +1725,50 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { >[]; /// Holds the collection of cartesian visible data points - List> _visibleDataPoints; + List>? _visibleDataPoints; /// Holds the collection of old data points - List> _oldDataPoints; + List>? _oldDataPoints; /// Holds the old series initial selected data indexes - List _oldSelectedIndexes; + List? _oldSelectedIndexes; /// Holds the information for x Axis - ChartAxisRenderer _xAxisRenderer; + ChartAxisRenderer? _xAxisRenderer; /// Holds the information for y Axis - ChartAxisRenderer _yAxisRenderer; + ChartAxisRenderer? _yAxisRenderer; /// Minimum x value for Series - num _minimumX; + num? _minimumX; /// Maximum x value for Series - num _maximumX; + num? _maximumX; /// Minimum y value for Series - num _minimumY; + num? _minimumY; /// Maximum y value for Series - num _maximumY; + num? _maximumY; /// Hold the data about point regions - Map _regionalData; + Map? _regionalData; /// Color for the series based on color palette - Color _seriesColor; - List _xValues; + Color? _seriesColor; + List? _xValues; /// Hold the information about chart class @override - SfCartesianChart _chart; + late SfCartesianChart _chart; /// Holds the information about chart state class - SfCartesianChartState _chartState; - - /// Side by side index for series - int _sideBySideIndex; + SfCartesianChartState? _chartState; /// Contains the collection of path for markers - List _markerShapes; + late List _markerShapes; - List _markerShapes2; + late List _markerShapes2; // ignore: prefer_final_fields bool _isOuterRegion = false; @@ -1767,10 +1778,10 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { bool _isIndicator = false; ///storing mindelta for rect series - num _minDelta; + num? _minDelta; /// Repaint notifier for series - ValueNotifier _repaintNotifier; + late ValueNotifier _repaintNotifier; // ignore: prefer_final_fields bool _needAnimateSeriesElements = false; @@ -1784,29 +1795,29 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { bool _calculateRegion = false; //ignore: prefer_final_fields - Animation _seriesAnimation; + Animation? _seriesAnimation; //ignore: prefer_final_fields - Animation _seriesElementAnimation; + late Animation _seriesElementAnimation; //controls the animation of the corresponding series - AnimationController _animationController; + late AnimationController _animationController; ///We can redraw the series with updating or creating new points by using this controller.If we need to access the redrawing methods ///in this before we must get the ChartSeriesController onRendererCreated event. - ChartSeriesController _controller; + ChartSeriesController? _controller; //ignore: prefer_final_fields List _trendlineRenderer = []; - DataLabelSettingsRenderer _dataLabelSettingsRenderer; + late DataLabelSettingsRenderer _dataLabelSettingsRenderer; - MarkerSettingsRenderer _markerSettingsRenderer; + MarkerSettingsRenderer? _markerSettingsRenderer; // ignore: prefer_final_fields bool _isSelectionEnable = false; - SelectionBehaviorRenderer _selectionBehaviorRenderer; + SelectionBehaviorRenderer? _selectionBehaviorRenderer; dynamic _selectionBehavior; @@ -1814,13 +1825,14 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { bool _isMarkerRenderEvent = false; // bool for animation status - bool _animationCompleted; + late bool _animationCompleted; // ignore: prefer_final_fields bool _hasDataLabelTemplate = false; // ignore: prefer_final_fields - _VisibleRange sideBySideInfo; + /// It specifies the side by side information of the visible range. + _VisibleRange? sideBySideInfo; /// To create segment for series ChartSegment createSegment(); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_area_series.dart index 6a295330f..ce68a1022 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_area_series.dart @@ -7,44 +7,42 @@ part of charts; class SplineAreaSeries extends XyDataSeries { /// Creating an argument constructor of SplineAreaSeries class. SplineAreaSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String xAxisName, - String yAxisName, - String name, - Color color, - MarkerSettings markerSettings, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + MarkerSettings? markerSettings, this.splineType, - List trendlines, - double cardinalSplineTension, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - bool enableTooltip, - List dashArray, - double animationDuration, - Color borderColor, - double borderWidth, - LinearGradient gradient, - LinearGradient borderGradient, + List? trendlines, + this.cardinalSplineTension = 0.5, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + bool? enableTooltip, + List? dashArray, + double? animationDuration, + Color? borderColor, + double? borderWidth, + LinearGradient? gradient, + LinearGradient? borderGradient, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - BorderDrawMode borderDrawMode}) - : borderDrawMode = borderDrawMode ?? BorderDrawMode.top, - cardinalSplineTension = cardinalSplineTension ?? 0.5, - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + this.borderDrawMode = BorderDrawMode.top}) + : super( key: key, onCreateRenderer: onCreateRenderer, xValueMapper: xValueMapper, @@ -117,7 +115,7 @@ class SplineAreaSeries extends XyDataSeries { /// )); ///} ///``` - final SplineType splineType; + final SplineType? splineType; ///Line tension of the cardinal spline. The value ranges from 0 to 1. /// @@ -143,7 +141,7 @@ class SplineAreaSeries extends XyDataSeries { SplineAreaSeriesRenderer createRenderer(ChartSeries series) { SplineAreaSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as SplineAreaSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -157,25 +155,23 @@ class SplineAreaSeriesRenderer extends XyDataSeriesRenderer { /// Calling the default constructor of SplineAreaSeriesRenderer class. SplineAreaSeriesRenderer(); - //ignore: prefer_final_fields - List<_ControlPoints> _drawPoints; - /// SplineArea segment is created here ChartSegment _createSegments(int seriesIndex, SfCartesianChart chart, - num animateFactor, Path path, Path strokePath, - [List _points]) { + double animateFactor, Path path, Path strokePath, + [List? _points]) { final SplineAreaSegment segment = createSegment(); _isRectSeries = false; if (segment != null) { segment._seriesIndex = seriesIndex; segment.animationFactor = animateFactor; - segment._series = _series; + segment._series = _series as XyDataSeries; segment._seriesRenderer = this; - segment.points = _points; + if (_points != null) segment.points = _points; segment._path = path; segment._strokePath = strokePath; segment._chart = chart; customizeSegment(segment); + segment._oldSegmentIndex = 0; segment.strokePaint = segment.getStrokePaint(); segment.fillPaint = segment.getFillPaint(); _segments.add(segment); @@ -187,17 +183,17 @@ class SplineAreaSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer - ._checkWithSelectionState(_segments[0], _chart); + selectionBehaviorRenderer?._selectionRenderer + ?._checkWithSelectionState(_segments[0], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => SplineAreaSegment(); + SplineAreaSegment createSegment() => SplineAreaSegment(); /// Changes the series color, border color, and border width. @override @@ -211,9 +207,9 @@ class SplineAreaSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_range_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_range_area_series.dart index 4c5e9e5d7..823a14e85 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_range_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_range_area_series.dart @@ -7,45 +7,43 @@ part of charts; class SplineRangeAreaSeries extends XyDataSeries { /// Creating an argument constructor of SplineRangeAreaSeries class. SplineRangeAreaSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper highValueMapper, - @required ChartValueMapper lowValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String xAxisName, - String yAxisName, - String name, - Color color, - MarkerSettings markerSettings, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper highValueMapper, + required ChartValueMapper lowValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + MarkerSettings? markerSettings, this.splineType, - List trendlines, - double cardinalSplineTension, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - bool enableTooltip, - List dashArray, - double animationDuration, - Color borderColor, - double borderWidth, - LinearGradient gradient, - LinearGradient borderGradient, + List? trendlines, + this.cardinalSplineTension = 0.5, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + bool? enableTooltip, + List? dashArray, + double? animationDuration, + Color? borderColor, + double? borderWidth, + LinearGradient? gradient, + LinearGradient? borderGradient, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - RangeAreaBorderMode borderDrawMode}) - : borderDrawMode = borderDrawMode ?? RangeAreaBorderMode.all, - cardinalSplineTension = cardinalSplineTension ?? 0.5, - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + this.borderDrawMode = RangeAreaBorderMode.all}) + : super( key: key, onCreateRenderer: onCreateRenderer, xValueMapper: xValueMapper, @@ -102,7 +100,7 @@ class SplineRangeAreaSeries extends XyDataSeries { /// ///Also refer [SplineType] - final SplineType splineType; + final SplineType? splineType; ///Line tension of the cardinal spline curve. /// @@ -114,7 +112,8 @@ class SplineRangeAreaSeries extends XyDataSeries { SplineRangeAreaSeriesRenderer createRenderer(ChartSeries series) { SplineRangeAreaSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = + onCreateRenderer!(series) as SplineRangeAreaSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -128,26 +127,22 @@ class SplineRangeAreaSeriesRenderer extends XyDataSeriesRenderer { /// Calling the default constructor of SplineRangeAreaSeriesRenderer class. SplineRangeAreaSeriesRenderer(); - //ignore: prefer_final_fields - List<_ControlPoints> _drawLowPoints; - //ignore: prefer_final_fields - List<_ControlPoints> _drawHighPoints; - /// SplineRangeArea segment is created here ChartSegment _createSegments(int seriesIndex, SfCartesianChart chart, - num animateFactor, Path path, Path strokePath, - [List _points]) { + double animateFactor, Path path, Path strokePath, + [List? _points]) { final SplineRangeAreaSegment segment = createSegment(); _isRectSeries = false; if (segment != null) { segment._seriesIndex = seriesIndex; segment.animationFactor = animateFactor; - segment._series = _series; + segment._series = _series as XyDataSeries; segment._seriesRenderer = this; - segment.points = _points; + if (_points != null) segment.points = _points; segment._chart = chart; segment._path = path; segment._strokePath = strokePath; + segment._oldSegmentIndex = 0; customizeSegment(segment); segment.strokePaint = segment.getStrokePaint(); segment.fillPaint = segment.getFillPaint(); @@ -160,16 +155,16 @@ class SplineRangeAreaSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer - ._checkWithSelectionState(_segments[0], _chart); + selectionBehaviorRenderer?._selectionRenderer + ?._checkWithSelectionState(_segments[0], _chart); } segment.onPaint(canvas); } @override - ChartSegment createSegment() => SplineRangeAreaSegment(); + SplineRangeAreaSegment createSegment() => SplineRangeAreaSegment(); /// Changes the series color, border color, and border width. @override @@ -183,11 +178,11 @@ class SplineRangeAreaSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes2[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); - canvas.drawPath(seriesRenderer._markerShapes2[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes2[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); + canvas.drawPath(seriesRenderer._markerShapes2[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_series.dart index ed3cf03ca..5cfbdf569 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_series.dart @@ -9,41 +9,40 @@ part of charts; class SplineSeries extends XyDataSeries { /// Creating an argument constructor of SplineSeries class. SplineSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - MarkerSettings markerSettings, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + MarkerSettings? markerSettings, this.splineType, - double cardinalSplineTension, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - List trendlines, - bool enableTooltip, - List dashArray, - double animationDuration, + this.cardinalSplineTension = 0.5, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + List? trendlines, + bool? enableTooltip, + List? dashArray, + double? animationDuration, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - SortingOrder sortingOrder, - String legendItemText, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) - : cardinalSplineTension = cardinalSplineTension ?? 0.5, - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + SortingOrder? sortingOrder, + String? legendItemText, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) + : super( key: key, onCreateRenderer: onCreateRenderer, xValueMapper: xValueMapper, @@ -95,7 +94,7 @@ class SplineSeries extends XyDataSeries { /// )); ///} ///``` - final SplineType splineType; + final SplineType? splineType; ///Line tension of the cardinal spline. The value ranges from 0 to 1. /// @@ -121,7 +120,7 @@ class SplineSeries extends XyDataSeries { SplineSeriesRenderer createRenderer(ChartSeries series) { SplineSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as SplineSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -135,11 +134,8 @@ class SplineSeriesRenderer extends XyDataSeriesRenderer { /// Calling the default constructor of SplineSeriesRenderer class. SplineSeriesRenderer(); - final List _xValueList = []; - final List _yValueList = []; - - //ignore: prefer_final_fields - List<_ControlPoints> _drawPoints; + final List _xValueList = []; + final List _yValueList = []; /// Spline segment is created here ChartSegment _createSegments( @@ -147,34 +143,35 @@ class SplineSeriesRenderer extends XyDataSeriesRenderer { CartesianChartPoint nextPoint, int pointIndex, int seriesIndex, - num animateFactor) { - final SplineSegment segment = createSegment(); + double animateFactor) { + final SplineSegment segment = createSegment() as SplineSegment; final List _oldSeriesRenderers = - _chartState._oldSeriesRenderers; + _chartState!._oldSeriesRenderers; _isRectSeries = false; if (segment != null) { segment._chart = _chart; - segment._chartState = _chartState; + segment._chartState = _chartState!; segment.animationFactor = animateFactor; segment._currentPoint = currentPoint; segment._nextPoint = nextPoint; segment._pointColorMapper = currentPoint.pointColorMapper; segment.currentSegmentIndex = pointIndex; segment._seriesIndex = seriesIndex; - segment._series = _series; + segment._series = _series as XyDataSeries; segment._seriesRenderer = this; - if (_chartState._widgetNeedUpdate && + if (_chartState!._widgetNeedUpdate && _oldSeriesRenderers != null && _oldSeriesRenderers.isNotEmpty && _oldSeriesRenderers.length - 1 >= segment._seriesIndex && _oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; - segment._oldSeries = segment._oldSeriesRenderer._series; + segment._oldSeries = + segment._oldSeriesRenderer!._series as XyDataSeries; } segment.calculateSegmentPoints(); - segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); + segment.points.add( + Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); segment.points.add(Offset(segment._x2, segment._y2)); customizeSegment(segment); segment.strokePaint = segment.getStrokePaint(); @@ -188,10 +185,10 @@ class SplineSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } @@ -212,9 +209,9 @@ class SplineSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_area_series.dart index 8ec84e708..c526b83fa 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_area_series.dart @@ -13,43 +13,42 @@ part of charts; class StackedAreaSeries extends _StackedSeriesBase { /// Creating an argument constructor of StackedAreaSeries class. StackedAreaSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String xAxisName, - String yAxisName, - String name, - String groupName, - List trendlines, - Color color, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - bool enableTooltip, - List dashArray, - double animationDuration, - Color borderColor, - double borderWidth, - LinearGradient gradient, - LinearGradient borderGradient, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? xAxisName, + String? yAxisName, + String? name, + String? groupName, + List? trendlines, + Color? color, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + bool? enableTooltip, + List? dashArray, + double? animationDuration, + Color? borderColor, + double? borderWidth, + LinearGradient? gradient, + LinearGradient? borderGradient, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - BorderDrawMode borderDrawMode}) - : borderDrawMode = borderDrawMode ?? BorderDrawMode.top, - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + this.borderDrawMode = BorderDrawMode.top}) + : super( key: key, onCreateRenderer: onCreateRenderer, xValueMapper: xValueMapper, @@ -108,7 +107,8 @@ class StackedAreaSeries extends _StackedSeriesBase { StackedAreaSeriesRenderer createRenderer(ChartSeries series) { StackedAreaSeriesRenderer stackedAreaSeriesRenderer; if (onCreateRenderer != null) { - stackedAreaSeriesRenderer = onCreateRenderer(series); + stackedAreaSeriesRenderer = + onCreateRenderer!(series) as StackedAreaSeriesRenderer; assert(stackedAreaSeriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return stackedAreaSeriesRenderer; @@ -125,27 +125,28 @@ class StackedAreaSeriesRenderer extends _StackedSeriesRenderer { /// Stacked area segment is created here. // ignore: unused_element ChartSegment _createSegments( - int seriesIndex, SfCartesianChart chart, num animateFactor, - [List _points]) { + int seriesIndex, SfCartesianChart chart, double animateFactor, + [List? _points]) { final StackedAreaSegment segment = createSegment(); final List _oldSeriesRenderers = - _chartState._oldSeriesRenderers; + _chartState!._oldSeriesRenderers; _isRectSeries = false; if (segment != null) { segment._seriesRenderer = this; - segment._series = _series; + segment._series = _series as XyDataSeries; segment._seriesIndex = seriesIndex; - segment.points = _points; + if (_points != null) segment.points = _points; segment.animationFactor = animateFactor; - if (_chartState._widgetNeedUpdate && - _xAxisRenderer._zoomFactor == 1 && - _yAxisRenderer._zoomFactor == 1 && + if (_chartState!._widgetNeedUpdate && + _xAxisRenderer!._zoomFactor == 1 && + _yAxisRenderer!._zoomFactor == 1 && _oldSeriesRenderers != null && _oldSeriesRenderers.isNotEmpty && _oldSeriesRenderers.length - 1 >= segment._seriesIndex && _oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; + segment._oldSegmentIndex = 0; } customizeSegment(segment); segment._chart = chart; @@ -160,17 +161,17 @@ class StackedAreaSeriesRenderer extends _StackedSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer - ._checkWithSelectionState(_segments[0], _chart); + selectionBehaviorRenderer?._selectionRenderer + ?._checkWithSelectionState(_segments[0], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => StackedAreaSegment(); + StackedAreaSegment createSegment() => StackedAreaSegment(); /// Changes the series color, border color, and border width. @override @@ -184,9 +185,9 @@ class StackedAreaSeriesRenderer extends _StackedSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_bar_series.dart index ca651ad7d..b4f5362f9 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_bar_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_bar_series.dart @@ -14,49 +14,49 @@ part of charts; class StackedBarSeries extends _StackedSeriesBase { /// Creating an argument constructor of StackedBarSeries class. StackedBarSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - bool isTrackVisible, - String groupName, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - double spacing, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - LinearGradient gradient, - LinearGradient borderGradient, - BorderRadius borderRadius, - bool enableTooltip, - double animationDuration, - Color trackColor, - Color trackBorderColor, - double trackBorderWidth, - double trackPadding, - List trendlines, - Color borderColor, - double borderWidth, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + bool? isTrackVisible, + String? groupName, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + double? spacing, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + LinearGradient? gradient, + LinearGradient? borderGradient, + BorderRadius? borderRadius, + bool? enableTooltip, + double? animationDuration, + Color? trackColor, + Color? trackBorderColor, + double? trackBorderWidth, + double? trackPadding, + List? trendlines, + Color? borderColor, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - List dashArray, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + List? dashArray, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -105,7 +105,8 @@ class StackedBarSeries extends _StackedSeriesBase { StackedBarSeriesRenderer createRenderer(ChartSeries series) { StackedBarSeriesRenderer stackedAreaSeriesRenderer; if (onCreateRenderer != null) { - stackedAreaSeriesRenderer = onCreateRenderer(series); + stackedAreaSeriesRenderer = + onCreateRenderer!(series) as StackedBarSeriesRenderer; assert(stackedAreaSeriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return stackedAreaSeriesRenderer; @@ -119,22 +120,23 @@ class StackedBarSeriesRenderer extends _StackedSeriesRenderer { /// Calling the default constructor of StackedBarSeriesRenderer class. StackedBarSeriesRenderer(); @override - num _rectPosition; + late num _rectPosition; @override - num _rectCount; + late num _rectCount; /// Stacked Bar segment is created here. // ignore: unused_element ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { + int pointIndex, int seriesIndex, double animateFactor) { final StackedBarSegment segment = createSegment(); - final StackedBarSeries _stackedBarSeries = _series; + final StackedBarSeries _stackedBarSeries = + _series as StackedBarSeries; _isRectSeries = true; if (segment != null) { segment._seriesIndex = seriesIndex; segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); + segment.points.add( + Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); segment._seriesRenderer = this; segment._series = _stackedBarSeries; segment._currentPoint = currentPoint; @@ -143,17 +145,18 @@ class StackedBarSeriesRenderer extends _StackedSeriesRenderer { currentPoint, _stackedBarSeries.borderWidth); if (_stackedBarSeries.borderRadius != null) { segment.segmentRect = _getRRectFromRect( - currentPoint.region, _stackedBarSeries.borderRadius); + currentPoint.region!, _stackedBarSeries.borderRadius); //Tracker rect if (_stackedBarSeries.isTrackVisible) { segment._trackRect = _getRRectFromRect( - currentPoint.trackerRectRegion, _stackedBarSeries.borderRadius); + currentPoint.trackerRectRegion!, _stackedBarSeries.borderRadius); segment._trackerFillPaint = segment._getTrackerFillPaint(); segment._trackerStrokePaint = segment._getTrackerStrokePaint(); } } segment._segmentRect = segment.segmentRect; + segment._oldSegmentIndex = _getOldSegmentIndex(segment); customizeSegment(segment); segment.strokePaint = segment.getStrokePaint(); segment.fillPaint = segment.getFillPaint(); @@ -166,16 +169,16 @@ class StackedBarSeriesRenderer extends _StackedSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } @override - ChartSegment createSegment() => StackedBarSegment(); + StackedBarSegment createSegment() => StackedBarSegment(); @override void customizeSegment(ChartSegment segment) { @@ -192,8 +195,8 @@ class StackedBarSeriesRenderer extends _StackedSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_column_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_column_series.dart index e1567decc..da21c77e2 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_column_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_column_series.dart @@ -12,49 +12,49 @@ part of charts; class StackedColumnSeries extends _StackedSeriesBase { /// Creating an argument constructor of StackedColumnSeries class. StackedColumnSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - bool isTrackVisible, - String groupName, - String xAxisName, - String yAxisName, - List trendlines, - String name, - Color color, - double width, - double spacing, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - LinearGradient gradient, - LinearGradient borderGradient, - BorderRadius borderRadius, - bool enableTooltip, - double animationDuration, - Color trackColor, - Color trackBorderColor, - double trackBorderWidth, - double trackPadding, - Color borderColor, - double borderWidth, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + bool? isTrackVisible, + String? groupName, + String? xAxisName, + String? yAxisName, + List? trendlines, + String? name, + Color? color, + double? width, + double? spacing, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + LinearGradient? gradient, + LinearGradient? borderGradient, + BorderRadius? borderRadius, + bool? enableTooltip, + double? animationDuration, + Color? trackColor, + Color? trackBorderColor, + double? trackBorderWidth, + double? trackPadding, + Color? borderColor, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - List dashArray, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + List? dashArray, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -103,7 +103,8 @@ class StackedColumnSeries extends _StackedSeriesBase { StackedColumnSeriesRenderer createRenderer(ChartSeries series) { StackedColumnSeriesRenderer stackedAreaSeriesRenderer; if (onCreateRenderer != null) { - stackedAreaSeriesRenderer = onCreateRenderer(series); + stackedAreaSeriesRenderer = + onCreateRenderer!(series) as StackedColumnSeriesRenderer; assert(stackedAreaSeriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return stackedAreaSeriesRenderer; @@ -117,22 +118,23 @@ class StackedColumnSeriesRenderer extends _StackedSeriesRenderer { /// Calling the default constructor of StackedColumnSeriesRenderer class. StackedColumnSeriesRenderer(); @override - num _rectPosition; + late num _rectPosition; @override - num _rectCount; + late num _rectCount; /// Stacked Bar segment is created here. // ignore: unused_element ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { + int pointIndex, int seriesIndex, double animateFactor) { final StackedColumnSegment segment = createSegment(); - final StackedColumnSeries _stackedColumnSeries = _series; + final StackedColumnSeries _stackedColumnSeries = + _series as StackedColumnSeries; _isRectSeries = true; if (segment != null) { segment._seriesIndex = seriesIndex; segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); + segment.points.add( + Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); segment._seriesRenderer = this; segment._series = _stackedColumnSeries; segment._currentPoint = currentPoint; @@ -140,16 +142,17 @@ class StackedColumnSeriesRenderer extends _StackedSeriesRenderer { segment._path = _findingRectSeriesDashedBorder( currentPoint, _stackedColumnSeries.borderWidth); segment.segmentRect = _getRRectFromRect( - currentPoint.region, _stackedColumnSeries.borderRadius); + currentPoint.region!, _stackedColumnSeries.borderRadius); //Tracker rect if (_stackedColumnSeries.isTrackVisible) { segment._trackRect = _getRRectFromRect( - currentPoint.trackerRectRegion, _stackedColumnSeries.borderRadius); + currentPoint.trackerRectRegion!, _stackedColumnSeries.borderRadius); segment._trackerFillPaint = segment._getTrackerFillPaint(); segment._trackerStrokePaint = segment._getTrackerStrokePaint(); } segment._segmentRect = segment.segmentRect; + segment._oldSegmentIndex = _getOldSegmentIndex(segment); customizeSegment(segment); segment.strokePaint = segment.getStrokePaint(); segment.fillPaint = segment.getFillPaint(); @@ -162,16 +165,16 @@ class StackedColumnSeriesRenderer extends _StackedSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } @override - ChartSegment createSegment() => StackedColumnSegment(); + StackedColumnSegment createSegment() => StackedColumnSegment(); @override void customizeSegment(ChartSegment segment) { @@ -188,8 +191,8 @@ class StackedColumnSeriesRenderer extends _StackedSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_line_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_line_series.dart index f67dac41b..30115acbb 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_line_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_line_series.dart @@ -11,38 +11,38 @@ part of charts; class StackedLineSeries extends _StackedSeriesBase { /// Creating an argument constructor of StackedLineSeries class. StackedLineSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - String xAxisName, - String yAxisName, - Color color, - double width, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - String name, - bool enableTooltip, - List dashArray, - double animationDuration, - String groupName, - List trendlines, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + String? xAxisName, + String? yAxisName, + Color? color, + double? width, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + String? name, + bool? enableTooltip, + List? dashArray, + double? animationDuration, + String? groupName, + List? trendlines, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - SortingOrder sortingOrder, - String legendItemText, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + SortingOrder? sortingOrder, + String? legendItemText, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -80,7 +80,8 @@ class StackedLineSeries extends _StackedSeriesBase { StackedLineSeriesRenderer createRenderer(ChartSeries series) { StackedLineSeriesRenderer stackedLineSeriesRenderer; if (onCreateRenderer != null) { - stackedLineSeriesRenderer = onCreateRenderer(series); + stackedLineSeriesRenderer = + onCreateRenderer!(series) as StackedLineSeriesRenderer; assert(stackedLineSeriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return stackedLineSeriesRenderer; @@ -105,31 +106,32 @@ class StackedLineSeriesRenderer extends _StackedSeriesRenderer { double currentCummulativePos, double nextCummulativePos) { final StackedLineSegment segment = createSegment(); - final List _oldSeriesRenderers = - _chartState._oldSeriesRenderers; + final List? _oldSeriesRenderers = + _chartState!._oldSeriesRenderers; _isRectSeries = false; if (segment != null) { segment._seriesRenderer = this; - segment._series = _series; + segment._series = _series as XyDataSeries; segment._seriesIndex = seriesIndex; segment._currentPoint = currentPoint; segment.currentSegmentIndex = pointIndex; segment._nextPoint = _nextPoint; segment._chart = _chart; - segment._chartState = _chartState; + segment._chartState = _chartState!; segment.animationFactor = animationFactor; segment._pointColorMapper = currentPoint.pointColorMapper; segment._currentCummulativePos = currentCummulativePos; segment._nextCummulativePos = nextCummulativePos; - if (_chartState._widgetNeedUpdate && - _xAxisRenderer._zoomFactor == 1 && - _yAxisRenderer._zoomFactor == 1 && + if (_chartState!._widgetNeedUpdate && + _xAxisRenderer!._zoomFactor == 1 && + _yAxisRenderer!._zoomFactor == 1 && _oldSeriesRenderers != null && _oldSeriesRenderers.isNotEmpty && _oldSeriesRenderers.length - 1 >= segment._seriesIndex && _oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; + segment._oldSegmentIndex = _getOldSegmentIndex(segment); } segment.calculateSegmentPoints(); segment.points.add(Offset(segment._x1, segment._y1)); @@ -146,17 +148,17 @@ class StackedLineSeriesRenderer extends _StackedSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => StackedLineSegment(); + StackedLineSegment createSegment() => StackedLineSegment(); /// Changes the series color, border color, and border width. @override @@ -170,9 +172,9 @@ class StackedLineSeriesRenderer extends _StackedSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_series_base.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_series_base.dart index 88d0083f0..2644d227d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_series_base.dart @@ -2,49 +2,49 @@ part of charts; abstract class _StackedSeriesBase extends XyDataSeries { _StackedSeriesBase( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - List dashArray, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - double spacing, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - LinearGradient gradient, - LinearGradient borderGradient, - BorderRadius borderRadius, - String groupName, - bool isTrackVisible, - List trendlines, - bool enableTooltip, - double animationDuration, - Color trackColor, - Color trackBorderColor, - double trackBorderWidth, - double trackPadding, - Color borderColor, - double borderWidth, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + List? dashArray, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + double? spacing, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + LinearGradient? gradient, + LinearGradient? borderGradient, + BorderRadius? borderRadius, + String? groupName, + bool? isTrackVisible, + List? trendlines, + bool? enableTooltip, + double? animationDuration, + Color? trackColor, + Color? trackBorderColor, + double? trackBorderWidth, + double? trackPadding, + Color? borderColor, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - List initialSelectedDataIndexes, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - SeriesRendererCreatedCallback onRendererCreated, - double opacity}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + List? initialSelectedDataIndexes, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + SeriesRendererCreatedCallback? onRendererCreated, + double? opacity}) : borderRadius = borderRadius ?? const BorderRadius.all(Radius.zero), trackColor = trackColor ?? Colors.grey, trackBorderColor = trackBorderColor ?? Colors.transparent, @@ -234,7 +234,7 @@ abstract class _StackedSeriesBase extends XyDataSeries { final String groupName; } -class _StackedSeriesRenderer extends XyDataSeriesRenderer { +abstract class _StackedSeriesRenderer extends XyDataSeriesRenderer { _StackedSeriesRenderer(); // Store the stacking values // @@ -243,18 +243,16 @@ class _StackedSeriesRenderer extends XyDataSeriesRenderer { // Store the percentage values // //ignore: prefer_final_fields - List _percentageValues = []; + List _percentageValues = []; // Store the rect position // - num _rectPosition; + late num _rectPosition; // Store the rect count // - num _rectCount; + late num _rectCount; @override - ChartSegment createSegment() { - return null; - } + ChartSegment createSegment(); /// Changes the series color, border color, and border width. @override @@ -270,5 +268,5 @@ class _StackedSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) {} + [CartesianSeriesRenderer? seriesRenderer]) {} } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedarea100_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedarea100_series.dart index c09ecad46..56e181980 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedarea100_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedarea100_series.dart @@ -15,43 +15,42 @@ part of charts; class StackedArea100Series extends _StackedSeriesBase { /// Creating an argument constructor of StackedArea100Series class. StackedArea100Series( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String xAxisName, - String yAxisName, - String name, - String groupName, - List trendlines, - Color color, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - bool enableTooltip, - List dashArray, - double animationDuration, - Color borderColor, - double borderWidth, - LinearGradient gradient, - LinearGradient borderGradient, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? xAxisName, + String? yAxisName, + String? name, + String? groupName, + List? trendlines, + Color? color, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + bool? enableTooltip, + List? dashArray, + double? animationDuration, + Color? borderColor, + double? borderWidth, + LinearGradient? gradient, + LinearGradient? borderGradient, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - BorderDrawMode borderDrawMode}) - : borderDrawMode = borderDrawMode ?? BorderDrawMode.top, - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + this.borderDrawMode = BorderDrawMode.top}) + : super( key: key, onCreateRenderer: onCreateRenderer, xValueMapper: xValueMapper, @@ -110,7 +109,8 @@ class StackedArea100Series extends _StackedSeriesBase { StackedArea100SeriesRenderer createRenderer(ChartSeries series) { StackedArea100SeriesRenderer stackedAreaSeriesRenderer; if (onCreateRenderer != null) { - stackedAreaSeriesRenderer = onCreateRenderer(series); + stackedAreaSeriesRenderer = + onCreateRenderer!(series) as StackedArea100SeriesRenderer; assert(stackedAreaSeriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return stackedAreaSeriesRenderer; @@ -127,28 +127,30 @@ class StackedArea100SeriesRenderer extends _StackedSeriesRenderer { /// Stacked Area segment is created here. // ignore: unused_element ChartSegment _createSegments( - int seriesIndex, SfCartesianChart chart, num animateFactor, - [List _points]) { + int seriesIndex, SfCartesianChart chart, double animateFactor, + [List? _points]) { final StackedArea100Segment segment = createSegment(); final List _oldSeriesRenderers = - _chartState._oldSeriesRenderers; + _chartState!._oldSeriesRenderers; _isRectSeries = false; if (segment != null) { segment._seriesRenderer = this; - segment._series = _series; + segment._series = _series as XyDataSeries; segment._seriesIndex = seriesIndex; - segment.points = _points; + if (_points != null) segment.points = _points; segment.animationFactor = animateFactor; - if (_chartState._widgetNeedUpdate && - _xAxisRenderer._zoomFactor == 1 && - _yAxisRenderer._zoomFactor == 1 && + if (_chartState!._widgetNeedUpdate && + _xAxisRenderer!._zoomFactor == 1 && + _yAxisRenderer!._zoomFactor == 1 && _oldSeriesRenderers != null && _oldSeriesRenderers.isNotEmpty && _oldSeriesRenderers.length - 1 >= segment._seriesIndex && _oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; - segment._oldSeries = segment._oldSeriesRenderer._series; + segment._oldSeries = + segment._oldSeriesRenderer!._series as XyDataSeries; + segment._oldSegmentIndex = 0; } customizeSegment(segment); segment._chart = chart; @@ -163,17 +165,17 @@ class StackedArea100SeriesRenderer extends _StackedSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer - ._checkWithSelectionState(_segments[0], _chart); + selectionBehaviorRenderer?._selectionRenderer + ?._checkWithSelectionState(_segments[0], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => StackedArea100Segment(); + StackedArea100Segment createSegment() => StackedArea100Segment(); /// Changes the series color, border color, and border width. @override @@ -187,9 +189,9 @@ class StackedArea100SeriesRenderer extends _StackedSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedbar100_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedbar100_series.dart index 46d22b386..866fe906e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedbar100_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedbar100_series.dart @@ -13,44 +13,44 @@ part of charts; class StackedBar100Series extends _StackedSeriesBase { /// Creating an argument constructor of StackedBar100Series class. StackedBar100Series( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String groupName, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - double spacing, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - LinearGradient gradient, - LinearGradient borderGradient, - BorderRadius borderRadius, - bool enableTooltip, - double animationDuration, - List trendlines, - Color borderColor, - double borderWidth, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? groupName, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + double? spacing, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + LinearGradient? gradient, + LinearGradient? borderGradient, + BorderRadius? borderRadius, + bool? enableTooltip, + double? animationDuration, + List? trendlines, + Color? borderColor, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - List dashArray, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + List? dashArray, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -94,7 +94,8 @@ class StackedBar100Series extends _StackedSeriesBase { StackedBar100SeriesRenderer createRenderer(ChartSeries series) { StackedBar100SeriesRenderer stackedBarSeriesRenderer; if (onCreateRenderer != null) { - stackedBarSeriesRenderer = onCreateRenderer(series); + stackedBarSeriesRenderer = + onCreateRenderer!(series) as StackedBar100SeriesRenderer; assert(stackedBarSeriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return stackedBarSeriesRenderer; @@ -109,22 +110,23 @@ class StackedBar100SeriesRenderer extends _StackedSeriesRenderer { StackedBar100SeriesRenderer(); @override - num _rectPosition; + late num _rectPosition; @override - num _rectCount; + late num _rectCount; /// Stacked Bar segment is created here // ignore: unused_element ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { + int pointIndex, int seriesIndex, double animateFactor) { final StackedBar100Segment segment = createSegment(); - final StackedBar100Series _stackedBar100Series = _series; + final StackedBar100Series _stackedBar100Series = + _series as StackedBar100Series; _isRectSeries = true; if (segment != null) { segment._seriesIndex = seriesIndex; segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); + segment.points.add( + Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); segment._seriesRenderer = this; segment._series = _stackedBar100Series; segment._currentPoint = currentPoint; @@ -132,8 +134,9 @@ class StackedBar100SeriesRenderer extends _StackedSeriesRenderer { segment._path = _findingRectSeriesDashedBorder( currentPoint, _stackedBar100Series.borderWidth); segment.segmentRect = _getRRectFromRect( - currentPoint.region, _stackedBar100Series.borderRadius); + currentPoint.region!, _stackedBar100Series.borderRadius); segment._segmentRect = segment.segmentRect; + segment._oldSegmentIndex = _getOldSegmentIndex(segment); customizeSegment(segment); segment.strokePaint = segment.getStrokePaint(); segment.fillPaint = segment.getFillPaint(); @@ -146,21 +149,21 @@ class StackedBar100SeriesRenderer extends _StackedSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } @override - ChartSegment createSegment() => StackedBar100Segment(); + StackedBar100Segment createSegment() => StackedBar100Segment(); @override void customizeSegment(ChartSegment segment) { - final StackedBar100Segment bar100Segment = segment; - bar100Segment._color = bar100Segment._currentPoint.pointColorMapper ?? + final StackedBar100Segment bar100Segment = segment as StackedBar100Segment; + bar100Segment._color = bar100Segment._currentPoint!.pointColorMapper ?? bar100Segment._seriesRenderer._seriesColor; bar100Segment._strokeColor = bar100Segment._series.borderColor; bar100Segment._strokeWidth = bar100Segment._series.borderWidth; @@ -174,8 +177,8 @@ class StackedBar100SeriesRenderer extends _StackedSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedcolumn100_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedcolumn100_series.dart index b142e83a3..e0cc6ba2c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedcolumn100_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedcolumn100_series.dart @@ -13,44 +13,44 @@ part of charts; class StackedColumn100Series extends _StackedSeriesBase { /// Creating an argument constructor of StackedColumn100Series class. StackedColumn100Series( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String groupName, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - double spacing, - MarkerSettings markerSettings, - List trendlines, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - LinearGradient gradient, - LinearGradient borderGradient, - BorderRadius borderRadius, - bool enableTooltip, - double animationDuration, - Color borderColor, - double borderWidth, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? groupName, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + double? spacing, + MarkerSettings? markerSettings, + List? trendlines, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + LinearGradient? gradient, + LinearGradient? borderGradient, + BorderRadius? borderRadius, + bool? enableTooltip, + double? animationDuration, + Color? borderColor, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - List dashArray, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + List? dashArray, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -94,7 +94,8 @@ class StackedColumn100Series extends _StackedSeriesBase { StackedColumn100SeriesRenderer createRenderer(ChartSeries series) { StackedColumn100SeriesRenderer stackedAreaSeriesRenderer; if (onCreateRenderer != null) { - stackedAreaSeriesRenderer = onCreateRenderer(series); + stackedAreaSeriesRenderer = + onCreateRenderer!(series) as StackedColumn100SeriesRenderer; assert(stackedAreaSeriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return stackedAreaSeriesRenderer; @@ -109,23 +110,23 @@ class StackedColumn100SeriesRenderer extends _StackedSeriesRenderer { StackedColumn100SeriesRenderer(); @override - num _rectPosition; + late num _rectPosition; @override - num _rectCount; + late num _rectCount; /// Stacked Column 100 segment is created here // ignore: unused_element ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { + int pointIndex, int seriesIndex, double animateFactor) { final StackedColumn100Segment segment = createSegment(); final StackedColumn100Series _stackedColumn100Series = - _series; + _series as StackedColumn100Series; _isRectSeries = true; if (segment != null) { segment._seriesIndex = seriesIndex; segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); + segment.points.add( + Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); segment._seriesRenderer = this; segment._series = _stackedColumn100Series; segment._currentPoint = currentPoint; @@ -133,8 +134,9 @@ class StackedColumn100SeriesRenderer extends _StackedSeriesRenderer { segment._path = _findingRectSeriesDashedBorder( currentPoint, _stackedColumn100Series.borderWidth); segment.segmentRect = _getRRectFromRect( - currentPoint.region, _stackedColumn100Series.borderRadius); + currentPoint.region!, _stackedColumn100Series.borderRadius); segment._segmentRect = segment.segmentRect; + segment._oldSegmentIndex = _getOldSegmentIndex(segment); customizeSegment(segment); segment.strokePaint = segment.getStrokePaint(); segment.fillPaint = segment.getFillPaint(); @@ -147,24 +149,26 @@ class StackedColumn100SeriesRenderer extends _StackedSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => StackedColumn100Segment(); + StackedColumn100Segment createSegment() => StackedColumn100Segment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment segment) { - final StackedColumn100Segment column100Segment = segment; - column100Segment._color = column100Segment._currentPoint.pointColorMapper ?? - column100Segment._seriesRenderer._seriesColor; + final StackedColumn100Segment column100Segment = + segment as StackedColumn100Segment; + column100Segment._color = + column100Segment._currentPoint!.pointColorMapper ?? + column100Segment._seriesRenderer._seriesColor; column100Segment._strokeColor = column100Segment._series.borderColor; column100Segment._strokeWidth = column100Segment._series.borderWidth; } @@ -179,8 +183,8 @@ class StackedColumn100SeriesRenderer extends _StackedSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedline100_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedline100_series.dart index 0c31c84aa..e4ade2fcb 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedline100_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedline100_series.dart @@ -10,38 +10,38 @@ part of charts; class StackedLine100Series extends _StackedSeriesBase { /// Creating an argument constructor of StackedLine100Series class. StackedLine100Series( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - String xAxisName, - String yAxisName, - Color color, - double width, - MarkerSettings markerSettings, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - String name, - bool enableTooltip, - List dashArray, - double animationDuration, - List trendlines, - String groupName, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + String? xAxisName, + String? yAxisName, + Color? color, + double? width, + MarkerSettings? markerSettings, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + String? name, + bool? enableTooltip, + List? dashArray, + double? animationDuration, + List? trendlines, + String? groupName, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - SortingOrder sortingOrder, - String legendItemText, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + SortingOrder? sortingOrder, + String? legendItemText, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -79,7 +79,8 @@ class StackedLine100Series extends _StackedSeriesBase { StackedLine100SeriesRenderer createRenderer(ChartSeries series) { StackedLine100SeriesRenderer stackedLine100SeriesRenderer; if (onCreateRenderer != null) { - stackedLine100SeriesRenderer = onCreateRenderer(series); + stackedLine100SeriesRenderer = + onCreateRenderer!(series) as StackedLine100SeriesRenderer; assert(stackedLine100SeriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return stackedLine100SeriesRenderer; @@ -105,30 +106,31 @@ class StackedLine100SeriesRenderer extends _StackedSeriesRenderer { double nextCummulativePos) { final StackedLine100Segment segment = createSegment(); final List _oldSeriesRenderers = - _chartState._oldSeriesRenderers; + _chartState!._oldSeriesRenderers; _isRectSeries = false; if (segment != null) { segment._seriesRenderer = this; - segment._series = _series; + segment._series = _series as XyDataSeries; segment._seriesIndex = seriesIndex; segment._currentPoint = currentPoint; segment.currentSegmentIndex = pointIndex; segment._nextPoint = _nextPoint; segment._chart = _chart; - segment._chartState = _chartState; + segment._chartState = _chartState!; segment.animationFactor = animationFactor; segment._pointColorMapper = currentPoint.pointColorMapper; segment._currentCummulativePos = currentCummulativePos; segment._nextCummulativePos = nextCummulativePos; - if (_chartState._widgetNeedUpdate && - _xAxisRenderer._zoomFactor == 1 && - _yAxisRenderer._zoomFactor == 1 && + if (_chartState!._widgetNeedUpdate && + _xAxisRenderer!._zoomFactor == 1 && + _yAxisRenderer!._zoomFactor == 1 && _oldSeriesRenderers != null && _oldSeriesRenderers.isNotEmpty && _oldSeriesRenderers.length - 1 >= segment._seriesIndex && _oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; + segment._oldSegmentIndex = _getOldSegmentIndex(segment); } segment.calculateSegmentPoints(); segment.points.add(Offset(segment._x1, segment._y1)); @@ -145,17 +147,17 @@ class StackedLine100SeriesRenderer extends _StackedSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => StackedLine100Segment(); + StackedLine100Segment createSegment() => StackedLine100Segment(); /// Changes the series color, border color, and border width. @override @@ -169,9 +171,9 @@ class StackedLine100SeriesRenderer extends _StackedSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/step_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/step_area_series.dart index 46aeec351..7439356f8 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/step_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/step_area_series.dart @@ -16,42 +16,41 @@ part of charts; class StepAreaSeries extends XyDataSeries { /// Creating an argument constructor of StepAreaSeries class. StepAreaSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - String xAxisName, - String yAxisName, - String name, - Color color, - MarkerSettings markerSettings, - List trendlines, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - bool enableTooltip, - List dashArray, - double animationDuration, - Color borderColor, - double borderWidth, - LinearGradient gradient, - LinearGradient borderGradient, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + MarkerSettings? markerSettings, + List? trendlines, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + bool? enableTooltip, + List? dashArray, + double? animationDuration, + Color? borderColor, + double? borderWidth, + LinearGradient? gradient, + LinearGradient? borderGradient, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - SeriesRendererCreatedCallback onRendererCreated, - BorderDrawMode borderDrawMode}) - : borderDrawMode = borderDrawMode ?? BorderDrawMode.top, - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + SeriesRendererCreatedCallback? onRendererCreated, + this.borderDrawMode = BorderDrawMode.top}) + : super( key: key, onCreateRenderer: onCreateRenderer, xValueMapper: xValueMapper, @@ -109,7 +108,7 @@ class StepAreaSeries extends XyDataSeries { StepAreaSeriesRenderer createRenderer(ChartSeries series) { StepAreaSeriesRenderer stepAreaRenderer; if (onCreateRenderer != null) { - stepAreaRenderer = onCreateRenderer(series); + stepAreaRenderer = onCreateRenderer!(series) as StepAreaSeriesRenderer; assert(stepAreaRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return stepAreaRenderer; @@ -125,20 +124,21 @@ class StepAreaSeriesRenderer extends XyDataSeriesRenderer { /// StepArea segment is created here ChartSegment _createSegments( - Path path, Path strokePath, int seriesIndex, num animateFactor, - [List _points]) { + Path path, Path strokePath, int seriesIndex, double animateFactor, + [List? _points]) { final StepAreaSegment segment = createSegment(); _isRectSeries = false; segment._path = path; segment._strokePath = strokePath; segment._seriesIndex = seriesIndex; segment._seriesRenderer = this; - segment._series = _series; - segment.points = _points; + segment._series = _series as XyDataSeries; + if (_points != null) segment.points = _points; segment._chart = _chart; - segment._chartState = _chartState; + segment._chartState = _chartState!; segment.animationFactor = animateFactor; segment.calculateSegmentPoints(); + segment._oldSegmentIndex = 0; customizeSegment(segment); segment.strokePaint = segment.getStrokePaint(); segment.fillPaint = segment.getFillPaint(); @@ -150,17 +150,17 @@ class StepAreaSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer - ._checkWithSelectionState(_segments[0], _chart); + selectionBehaviorRenderer?._selectionRenderer + ?._checkWithSelectionState(_segments[0], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => StepAreaSegment(); + StepAreaSegment createSegment() => StepAreaSegment(); /// Changes the series color, border color, and border width. @override @@ -174,9 +174,9 @@ class StepAreaSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart index e65203750..5ed9f9ddd 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart @@ -10,36 +10,36 @@ part of charts; class StepLineSeries extends XyDataSeries { /// Creating an argument constructor of StepLineSeries class. StepLineSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - MarkerSettings markerSettings, - List trendlines, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - bool enableTooltip, - List dashArray, - double animationDuration, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + MarkerSettings? markerSettings, + List? trendlines, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + bool? enableTooltip, + List? dashArray, + double? animationDuration, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - SortingOrder sortingOrder, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - SeriesRendererCreatedCallback onRendererCreated, - double opacity}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + SortingOrder? sortingOrder, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + SeriesRendererCreatedCallback? onRendererCreated, + double? opacity}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -75,7 +75,8 @@ class StepLineSeries extends XyDataSeries { StepLineSeriesRenderer createRenderer(ChartSeries series) { StepLineSeriesRenderer stepLineSeriesRenderer; if (onCreateRenderer != null) { - stepLineSeriesRenderer = onCreateRenderer(series); + stepLineSeriesRenderer = + onCreateRenderer!(series) as StepLineSeriesRenderer; assert(stepLineSeriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return stepLineSeriesRenderer; @@ -97,30 +98,31 @@ class StepLineSeriesRenderer extends XyDataSeriesRenderer { CartesianChartPoint _nextPoint, int pointIndex, int seriesIndex, - num animateFactor) { + double animateFactor) { final StepLineSegment segment = createSegment(); final List _oldSeriesRenderers = - _chartState._oldSeriesRenderers; + _chartState!._oldSeriesRenderers; _isRectSeries = false; segment.currentSegmentIndex = pointIndex; segment._seriesIndex = seriesIndex; segment._seriesRenderer = this; - segment._series = _series; + segment._series = _series as XyDataSeries; segment._currentPoint = currentPoint; segment._midX = midX; segment._midY = midY; segment._nextPoint = _nextPoint; segment._chart = _chart; - segment._chartState = _chartState; + segment._chartState = _chartState!; segment.animationFactor = animateFactor; segment._pointColorMapper = currentPoint.pointColorMapper; - if (_chartState._widgetNeedUpdate && + if (_chartState!._widgetNeedUpdate && _oldSeriesRenderers != null && _oldSeriesRenderers.isNotEmpty && _oldSeriesRenderers.length - 1 >= segment._seriesIndex && _oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; + segment._oldSegmentIndex = _getOldSegmentIndex(segment); } segment.calculateSegmentPoints(); segment.points.add(Offset(segment._x1, segment._y1)); @@ -136,17 +138,17 @@ class StepLineSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => StepLineSegment(); + StepLineSegment createSegment() => StepLineSegment(); /// Changes the series color, border color, and border width. @override @@ -160,9 +162,9 @@ class StepLineSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/waterfall_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/waterfall_series.dart index 0a469b9f5..10c861e27 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/waterfall_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/waterfall_series.dart @@ -10,56 +10,49 @@ part of charts; class WaterfallSeries extends XyDataSeries { /// Creating an argument constructor of WaterfallSeries class. WaterfallSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - @required List dataSource, - @required ChartValueMapper xValueMapper, - @required ChartValueMapper yValueMapper, - ChartValueMapper intermediateSumPredicate, - ChartValueMapper totalSumPredicate, - Color negativePointsColor, - Color intermediateSumColor, - Color totalSumColor, - ChartValueMapper sortFieldValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper dataLabelMapper, - SortingOrder sortingOrder, - WaterfallConnectorLineSettings connectorLineSettings, - String xAxisName, - String yAxisName, - String name, - Color color, - double width, - double spacing, - MarkerSettings markerSettings, - DataLabelSettings dataLabelSettings, - bool isVisible, - LinearGradient gradient, - LinearGradient borderGradient, - BorderRadius borderRadius, - bool enableTooltip, - double animationDuration, - Color borderColor, - List trendlines, - double borderWidth, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? intermediateSumPredicate, + ChartValueMapper? totalSumPredicate, + this.negativePointsColor, + this.intermediateSumColor, + this.totalSumColor, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? dataLabelMapper, + SortingOrder? sortingOrder, + this.connectorLineSettings = const WaterfallConnectorLineSettings(), + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + this.spacing = 0, + MarkerSettings? markerSettings, + DataLabelSettings? dataLabelSettings, + bool? isVisible, + LinearGradient? gradient, + LinearGradient? borderGradient, + this.borderRadius = const BorderRadius.all(Radius.zero), + bool? enableTooltip, + double? animationDuration, + Color? borderColor, + List? trendlines, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - double opacity, - List dashArray, - SeriesRendererCreatedCallback onRendererCreated, - List initialSelectedDataIndexes}) - : spacing = spacing ?? 0, - negativePointsColor = negativePointsColor, - intermediateSumColor = intermediateSumColor, - totalSumColor = totalSumColor, - connectorLineSettings = - connectorLineSettings ?? WaterfallConnectorLineSettings(), - borderRadius = borderRadius ?? const BorderRadius.all(Radius.zero), - super( + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + double? opacity, + List? dashArray, + SeriesRendererCreatedCallback? onRendererCreated, + List? initialSelectedDataIndexes}) + : super( key: key, onCreateRenderer: onCreateRenderer, name: name, @@ -116,7 +109,7 @@ class WaterfallSeries extends XyDataSeries { /// ); ///} ///``` - final Color negativePointsColor; + final Color? negativePointsColor; ///Color of the intermediate sum points in the series. /// @@ -138,7 +131,7 @@ class WaterfallSeries extends XyDataSeries { /// ); ///} ///``` - final Color intermediateSumColor; + final Color? intermediateSumColor; ///Color of the total sum points in the series. /// @@ -160,7 +153,7 @@ class WaterfallSeries extends XyDataSeries { /// ); ///} ///``` - final Color totalSumColor; + final Color? totalSumColor; ///Options to customize the waterfall chart connector line. /// @@ -236,7 +229,7 @@ class WaterfallSeries extends XyDataSeries { WaterfallSeriesRenderer createRenderer(ChartSeries series) { WaterfallSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as WaterfallSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -249,51 +242,52 @@ class WaterfallSeries extends XyDataSeries { class WaterfallSeriesRenderer extends XyDataSeriesRenderer { /// Calling the default constructor of WaterfallSeriesRenderer class. WaterfallSeriesRenderer(); - num _rectPosition; - num _rectCount; + late num _rectPosition; + late num _rectCount; - WaterfallSeries _waterfallSeries; + late WaterfallSeries _waterfallSeries; /// To add waterfall segments in segments list ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, num animateFactor) { + int pointIndex, int seriesIndex, double animateFactor) { final WaterfallSegment segment = createSegment(); - final List oldSeriesRenderers = - _chartState._oldSeriesRenderers; - _waterfallSeries = _series; + final List? oldSeriesRenderers = + _chartState!._oldSeriesRenderers; + _waterfallSeries = _series as WaterfallSeries; final BorderRadius borderRadius = _waterfallSeries.borderRadius; segment._seriesIndex = seriesIndex; segment.currentSegmentIndex = pointIndex; segment.points - .add(Offset(currentPoint.markerPoint.x, currentPoint.markerPoint.y)); + .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); segment._seriesRenderer = this; segment._series = _waterfallSeries; segment._chart = _chart; - segment._chartState = _chartState; + segment._chartState = _chartState!; segment.animationFactor = animateFactor; segment._currentPoint = currentPoint; - if (_chartState._widgetNeedUpdate && - _chartState._zoomPanBehaviorRenderer._isPinching != true && - !_chartState._isLegendToggled && + if (_chartState!._widgetNeedUpdate && + _chartState!._zoomPanBehaviorRenderer._isPinching != true && + !_chartState!._isLegendToggled && oldSeriesRenderers != null && oldSeriesRenderers.isNotEmpty && oldSeriesRenderers.length - 1 >= segment._seriesIndex && oldSeriesRenderers[segment._seriesIndex]._seriesName == segment._seriesRenderer._seriesName) { segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldPoint = (segment._oldSeriesRenderer._segments.isNotEmpty && - segment._oldSeriesRenderer._segments[0] is WaterfallSegment && - segment._oldSeriesRenderer._dataPoints.length - 1 >= pointIndex) - ? segment._oldSeriesRenderer._dataPoints[pointIndex] + segment._oldPoint = (segment._oldSeriesRenderer!._segments.isNotEmpty && + segment._oldSeriesRenderer!._segments[0] is WaterfallSegment && + segment._oldSeriesRenderer!._dataPoints.length - 1 >= pointIndex) + ? segment._oldSeriesRenderer!._dataPoints[pointIndex] : null; - } else if (_chartState._isLegendToggled && - _chartState._segments != null && - _chartState._segments.isNotEmpty) { + segment._oldSegmentIndex = _getOldSegmentIndex(segment); + } else if (_chartState!._isLegendToggled && + _chartState!._segments != null && + _chartState!._segments.isNotEmpty) { segment._oldSeriesVisible = - _chartState._oldSeriesVisible[segment._seriesIndex]; + _chartState!._oldSeriesVisible[segment._seriesIndex]; WaterfallSegment oldSegment; - for (int i = 0; i < _chartState._segments.length; i++) { - oldSegment = _chartState._segments[i]; + for (int i = 0; i < _chartState!._segments.length; i++) { + oldSegment = _chartState!._segments[i] as WaterfallSegment; if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && oldSegment._seriesIndex == segment._seriesIndex) { segment._oldRegion = oldSegment.segmentRect.outerRect; @@ -304,7 +298,7 @@ class WaterfallSeriesRenderer extends XyDataSeriesRenderer { currentPoint, _waterfallSeries.borderWidth); if (borderRadius != null) { segment.segmentRect = - _getRRectFromRect(currentPoint.region, borderRadius); + _getRRectFromRect(currentPoint.region!, borderRadius); } segment._segmentRect = segment.segmentRect; customizeSegment(segment); @@ -316,22 +310,22 @@ class WaterfallSeriesRenderer extends XyDataSeriesRenderer { //ignore: unused_element void _drawSegment(Canvas canvas, ChartSegment segment) { if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( - _segments[segment.currentSegmentIndex], _chart); + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( + _segments[segment.currentSegmentIndex!], _chart); } segment.onPaint(canvas); } /// Creates a segment for a data point in the series. @override - ChartSegment createSegment() => WaterfallSegment(); + WaterfallSegment createSegment() => WaterfallSegment(); /// Changes the series color, border color, and border width. @override void customizeSegment(ChartSegment segment) { - final WaterfallSegment waterfallSegment = segment; + final WaterfallSegment waterfallSegment = segment as WaterfallSegment; waterfallSegment._color = segment._seriesRenderer._seriesColor; waterfallSegment._negativePointsColor = _waterfallSeries.negativePointsColor; @@ -350,9 +344,9 @@ class WaterfallSeriesRenderer extends XyDataSeriesRenderer { @override void drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer seriesRenderer]) { - canvas.drawPath(seriesRenderer._markerShapes[index], fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index], strokePaint); + [CartesianSeriesRenderer? seriesRenderer]) { + canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); } /// Draws data label text of the appropriate data point in a series. @@ -372,10 +366,9 @@ class WaterfallSeriesRenderer extends XyDataSeriesRenderer { /// class WaterfallConnectorLineSettings extends ConnectorLineSettings { /// Creating an argument constructor of WaterfallConnectorLineSettings class. - WaterfallConnectorLineSettings( - {double width, Color color, List dashArray}) - : dashArray = dashArray ?? [0, 0], - super(color: color, width: width ?? 1); + const WaterfallConnectorLineSettings( + {double? width, Color? color, this.dashArray = const [0, 0]}) + : super(color: color, width: width ?? 1); ///Dashes of the waterfall chart connector line. /// @@ -396,5 +389,5 @@ class WaterfallConnectorLineSettings extends ConnectorLineSettings { /// )); ///} ///``` - final List dashArray; + final List? dashArray; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart index 7c0e3bc6c..0c7f949c1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart @@ -6,46 +6,46 @@ part of charts; abstract class XyDataSeries extends CartesianSeries { /// Creating an argument constructor of XyDataSeries class. XyDataSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - SeriesRendererCreatedCallback onRendererCreated, - ChartValueMapper xValueMapper, - ChartValueMapper yValueMapper, - ChartValueMapper dataLabelMapper, - String name, - @required List dataSource, - String xAxisName, - String yAxisName, - ChartValueMapper pointColorMapper, - String legendItemText, - ChartValueMapper sortFieldValueMapper, - LinearGradient gradient, - LinearGradient borderGradient, - ChartValueMapper sizeValueMapper, - ChartValueMapper highValueMapper, - ChartValueMapper lowValueMapper, - ChartValueMapper intermediateSumPredicate, - ChartValueMapper totalSumPredicate, - List trendlines, - double width, - MarkerSettings markerSettings, - bool isVisible, - bool enableTooltip, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - double animationDuration, - List dashArray, - Color borderColor, - double borderWidth, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + SeriesRendererCreatedCallback? onRendererCreated, + ChartValueMapper? xValueMapper, + ChartValueMapper? yValueMapper, + ChartValueMapper? dataLabelMapper, + String? name, + required List dataSource, + String? xAxisName, + String? yAxisName, + ChartValueMapper? pointColorMapper, + String? legendItemText, + ChartValueMapper? sortFieldValueMapper, + LinearGradient? gradient, + LinearGradient? borderGradient, + ChartValueMapper? sizeValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? intermediateSumPredicate, + ChartValueMapper? totalSumPredicate, + List? trendlines, + double? width, + MarkerSettings? markerSettings, + bool? isVisible, + bool? enableTooltip, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + double? animationDuration, + List? dashArray, + Color? borderColor, + double? borderWidth, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - bool isVisibleInLegend, - LegendIconType legendIconType, - double opacity, - Color color, - List initialSelectedDataIndexes, - SortingOrder sortingOrder}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + double? opacity, + Color? color, + List? initialSelectedDataIndexes, + SortingOrder? sortingOrder}) : super( key: key, onRendererCreated: onRendererCreated, @@ -62,8 +62,12 @@ abstract class XyDataSeries extends CartesianSeries { color: color, name: name, width: width, - xValueMapper: (int index) => xValueMapper(dataSource[index], index), - yValueMapper: (int index) => yValueMapper(dataSource[index], index), + xValueMapper: xValueMapper != null + ? (int index) => xValueMapper(dataSource[index], index) + : null, + yValueMapper: yValueMapper != null + ? (int index) => yValueMapper(dataSource[index], index) + : null, sortFieldValueMapper: sortFieldValueMapper != null ? (int index) => sortFieldValueMapper(dataSource[index], index) : null, @@ -133,7 +137,7 @@ class CartesianChartPoint { this.maximum, this.isIntermediateSum, this.isTotalSum, - this.maxYValue, + this.maxYValue = 0, this.outliers, this.upperQuartile, this.lowerQuartile, @@ -172,46 +176,46 @@ class CartesianChartPoint { } /// X value of the point. - D x; + D? x; /// Y value of the point - D y; + D? y; /// Stores the xValues of the point - D xValue; + D? xValue; /// Stores the yValues of the Point - D yValue; + D? yValue; /// Sort value of the point. - D sortValue; + D? sortValue; /// High value of the point. - D high; + D? high; /// Low value of the point. - D low; + D? low; /// Open value of the point. - D open; + D? open; /// Close value of the point - D close; + D? close; /// Volume value of the point - num volume; + num? volume; /// Marker point location. - _ChartLocation markerPoint; + _ChartLocation? markerPoint; /// second Marker point location. - _ChartLocation markerPoint2; + _ChartLocation? markerPoint2; /// Size of the bubble. - num bubbleSize; + num? bubbleSize; /// To set empty value - bool isEmpty; + bool? isEmpty; /// To set gap value bool isGap = false; @@ -223,58 +227,58 @@ class CartesianChartPoint { bool isVisible = true; /// Used to map the color value from data point. - Color pointColorMapper; + Color? pointColorMapper; /// Map the datalabel value from data point. - String dataLabelMapper; + String? dataLabelMapper; /// Store the region. - Rect region; + Rect? region; /// Store the region for box series rect. - Rect boxRectRegion; + Rect? boxRectRegion; /// Store the outliers region. - List outlierRegion; + List? outlierRegion; /// Store the outliers region. - List outlierRegionPosition; + List? outlierRegionPosition; /// Minimum value of box plot series. - num minimum; + num? minimum; /// Maximum value of box plot series. - num maximum; + num? maximum; /// Outlier values of box plot series. - List outliers = []; + List? outliers = []; /// Upper quartile values of box plot series. - num upperQuartile; + double? upperQuartile; /// Lower quartile values of box plot series. - num lowerQuartile; + double? lowerQuartile; /// Average value of the given data source in box plot series. - num mean; + num? mean; /// Median value of the given data source in box plot series. - num median; + num? median; /// The intermediate sum value of the waterfall series. - bool isIntermediateSum; + bool? isIntermediateSum; /// The total sum value of the waterfall series. - bool isTotalSum; + bool? isTotalSum; /// The end value of each data point in the waterfall series. - num endValue; + num? endValue; /// The Origin value of each data point in waterfall series. - num originValue; + num? originValue; /// To find the maximum Y value in the waterfall series. - num maxYValue = 0; + num maxYValue; /// To execute OnDataLabelRender event or not. // ignore: prefer_final_fields @@ -285,13 +289,19 @@ class CartesianChartPoint { bool isTooltipRenderEvent = false; //specifies the style of data label in the onDataLabelEvent - TextStyle _dataLabelStyle; + TextStyle? _dataLabelStyle; //specifies the color of the data label in onDataLabelEvent - Color _dataLabelColor; + Color? _dataLabelColor; + + /// Stores the tooltip label text. + String? _tooltipLabelText; + + /// Stores the tooltip header text. + String? _tooltipHeaderText; /// Stores the chart location. - _ChartLocation openPoint, + _ChartLocation? openPoint, closePoint, centerOpenPoint, centerClosePoint, @@ -328,73 +338,73 @@ class CartesianChartPoint { List<_ChartLocation> outliersPoint = <_ChartLocation>[]; /// control points for spline series. - List<_ControlPoints> controlPoint; + List? controlPoint; /// control points for spline range area series. - List<_ControlPoints> controlPointshigh; + List? controlPointshigh; /// control points for spline range area series. - List<_ControlPoints> controlPointslow; + List? controlPointslow; /// Store the List of region. - List regions; + List? regions; /// store the cumulative value. - double cumulativeValue; + double? cumulativeValue; /// Stores the tracker rect region - Rect trackerRectRegion; + Rect? trackerRectRegion; /// Stores the forth data label text - String label; + String? label; /// Stores the forth data label text - String label2; + String? label2; /// Stores the forth data label text - String label3; + String? label3; /// Stores the forth data label text - String label4; + String? label4; /// Stores the median data label text - String label5; + String? label5; /// Stores the outliers data label text List outliersLabel = []; /// Stores the forth data label Rect - RRect labelFillRect; + RRect? labelFillRect; /// Stores the forth data label Rect - RRect labelFillRect2; + RRect? labelFillRect2; /// Stores the forth data label Rect - RRect labelFillRect3; + RRect? labelFillRect3; /// Stores the forth data label Rect - RRect labelFillRect4; + RRect? labelFillRect4; /// Stores the median data label Rect - RRect labelFillRect5; + RRect? labelFillRect5; /// Stores the outliers data label Rect List outliersFillRect = []; /// Stores the data label location - _ChartLocation labelLocation; + _ChartLocation? labelLocation; /// Stores the second data label location - _ChartLocation labelLocation2; + _ChartLocation? labelLocation2; /// Stores the third data label location - _ChartLocation labelLocation3; + _ChartLocation? labelLocation3; /// Stores the forth data label location - _ChartLocation labelLocation4; + _ChartLocation? labelLocation4; /// Stores the median data label location - _ChartLocation labelLocation5; + _ChartLocation? labelLocation5; /// Stores the outliers data label location List<_ChartLocation> outliersLocation = <_ChartLocation>[]; @@ -403,46 +413,46 @@ class CartesianChartPoint { bool dataLabelSaturationRegionInside = false; /// Stores the data label region - Rect dataLabelRegion; + Rect? dataLabelRegion; /// Stores the second data label region - Rect dataLabelRegion2; + Rect? dataLabelRegion2; /// Stores the third data label region - Rect dataLabelRegion3; + Rect? dataLabelRegion3; /// Stores the forth data label region - Rect dataLabelRegion4; + Rect? dataLabelRegion4; /// Stores the median data label region - Rect dataLabelRegion5; + Rect? dataLabelRegion5; /// Stores the outliers data label region List outliersDataLabelRegion = []; /// Stores the data point index - int index; + int? index; /// Stores the data index - int overallDataPointIndex; + int? overallDataPointIndex; /// Store the region data of the data point. - List regionData; + List? regionData; /// Store the visible point index. - int visiblePointIndex; + int? visiblePointIndex; } class _ChartLocation { _ChartLocation(this.x, this.y); - num x; - num y; + double x; + double y; } /// To calculate dash array path for series -Path _dashPath( - Path source, { - @required _CircularIntervalList dashArray, +Path? _dashPath( + Path? source, { + required _CircularIntervalList dashArray, }) { if (source == null) { return null; @@ -484,8 +494,8 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { @override void calculateEmptyPointValue( int pointIndex, CartesianChartPoint currentPoint, - [CartesianSeriesRenderer seriesRenderer]) { - final int pointLength = seriesRenderer._dataPoints.length - 1; + [CartesianSeriesRenderer? seriesRenderer]) { + final int pointLength = seriesRenderer!._dataPoints.length - 1; final String _seriesType = seriesRenderer._seriesType; final CartesianChartPoint prevPoint = seriesRenderer._dataPoints[ seriesRenderer._dataPoints.length >= 2 ? pointLength - 1 : pointLength]; @@ -602,8 +612,8 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { /// To render a series of elements for all series void _renderSeriesElements(SfCartesianChart chart, Canvas canvas, Animation animationController) { - _markerShapes = []; - _markerShapes2 = []; + _markerShapes = []; + _markerShapes2 = []; assert( _series.markerSettings.height != null ? _series.markerSettings.height >= 0 @@ -619,8 +629,8 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { if ((_series.markerSettings.isVisible && !(this is BoxAndWhiskerSeriesRenderer)) || this is ScatterSeriesRenderer) { - final MarkerSettingsRenderer markerSettingsRenderer = - _markerSettingsRenderer; + final MarkerSettingsRenderer? markerSettingsRenderer = + _markerSettingsRenderer!; markerSettingsRenderer?.renderMarker( this, point, animationController, canvas, pointIndex); } @@ -635,10 +645,10 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { if (_chartState != null && status == AnimationStatus.completed) { _reAnimate = false; _animationCompleted = true; - _chartState?._animationCompleteCount++; + _chartState!._animationCompleteCount++; _setAnimationStatus(_chartState); } else if (_chartState != null && status == AnimationStatus.forward) { - _chartState?._animateCompleted = false; + _chartState!._animateCompleted = false; _animationCompleted = false; } } @@ -657,17 +667,19 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { _series.color ?? _chart.palette[index % _chart.palette.length]; // calculates the tooltip region for trenlines in this series - final List trendlines = _series.trendlines; + final List? trendlines = _series.trendlines; if (trendlines != null && _chart.tooltipBehavior != null && _chart.tooltipBehavior.enable) { for (int j = 0; j < trendlines.length; j++) { if (_trendlineRenderer[j]._isNeedRender) { if (_trendlineRenderer[j]._pointsData != null) { - for (int k = 0; k < _trendlineRenderer[j]._pointsData.length; k++) { + for (int k = 0; + k < _trendlineRenderer[j]._pointsData!.length; + k++) { final CartesianChartPoint trendlinePoint = - _trendlineRenderer[j]._pointsData[k]; - _calculateTooltipRegion(trendlinePoint, index, this, _chartState, + _trendlineRenderer[j]._pointsData![k]; + _calculateTooltipRegion(trendlinePoint, index, this, _chartState!, trendlines[j], _trendlineRenderer[j], j); } } @@ -683,22 +695,22 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { int seriesIndex, CartesianChartPoint point, int pointIndex, - [_VisibleRange sideBySideInfo, - CartesianChartPoint _nextPoint, - num midX, - num midY]) { + [_VisibleRange? sideBySideInfo, + CartesianChartPoint? _nextPoint, + num? midX, + num? midY]) { if (_withInRange(seriesRenderer._dataPoints[pointIndex].xValue, - seriesRenderer._xAxisRenderer._visibleRange)) { - seriesRenderer._visibleDataPoints + seriesRenderer._xAxisRenderer!._visibleRange!)) { + seriesRenderer._visibleDataPoints! .add(seriesRenderer._dataPoints[pointIndex]); - if (seriesRenderer._visibleDataPoints.isNotEmpty) { + if (seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._dataPoints[pointIndex].visiblePointIndex = - seriesRenderer._visibleDataPoints.length - 1; + seriesRenderer._visibleDataPoints!.length - 1; } } _chart = _chartState._chart; - final ChartAxis xAxis = _xAxisRenderer._axis; - final ChartAxis yAxis = _yAxisRenderer._axis; + final ChartAxis xAxis = _xAxisRenderer!._axis; + final ChartAxis yAxis = _yAxisRenderer!._axis; final Rect rect = _calculatePlotOffset(_chartState._chartAxis._axisClipRect, Offset(xAxis.plotOffset, yAxis.plotOffset)); _isRectSeries = _seriesType == 'column' || @@ -710,7 +722,7 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { _seriesType == 'waterfall'; CartesianChartPoint point; - final num markerHeight = _series.markerSettings.height, + final double markerHeight = _series.markerSettings.height, markerWidth = _series.markerSettings.width; final bool isPointSeries = _seriesType == 'scatter' || _seriesType == 'bubble'; @@ -720,10 +732,11 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { (_series.markerSettings.isVisible || _series.dataLabelSettings.isVisible || _series.enableTooltip))) && - _visible) { + _visible!) { point = _dataPoints[pointIndex]; if (point.region == null || seriesRenderer._calculateRegion || + _seriesType.contains('waterfall') || _seriesType.contains('stackedcolumn') || _seriesType.contains('stackedbar')) { if (seriesRenderer._calculateRegion && @@ -777,32 +790,38 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { !point.isGap && !point.isDrop) { final List regionData = []; - String date; + String? date; final List regionRect = []; final dynamic primaryAxisRenderer = _xAxisRenderer; if (primaryAxisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis _axis = primaryAxisRenderer._axis; - final DateFormat dateFormat = _axis.dateFormat ?? - primaryAxisRenderer._getLabelFormat(_xAxisRenderer); + final DateTimeAxis _axis = primaryAxisRenderer._axis as DateTimeAxis; + final DateFormat dateFormat = + _axis.dateFormat ?? _getDateTimeLabelFormat(_xAxisRenderer!); date = dateFormat .format(DateTime.fromMillisecondsSinceEpoch(point.xValue)); + } else if (primaryAxisRenderer is DateTimeCategoryAxisRenderer) { + date = primaryAxisRenderer._dateFormat + .format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor())); } _xAxisRenderer is CategoryAxisRenderer ? regionData.add(point.x.toString()) - : _xAxisRenderer is DateTimeAxisRenderer + : _xAxisRenderer is DateTimeAxisRenderer || + _xAxisRenderer is DateTimeCategoryAxisRenderer ? regionData.add(date.toString()) - : regionData.add(_getLabelValue(point.xValue, - _xAxisRenderer._axis, chart.tooltipBehavior.decimalPlaces) + : regionData.add(_getLabelValue( + point.xValue, + _xAxisRenderer!._axis, + chart.tooltipBehavior.decimalPlaces) .toString()); if (_seriesType.contains('range')) { - regionData.add(_getLabelValue(point.high, _yAxisRenderer._axis, + regionData.add(_getLabelValue(point.high, _yAxisRenderer!._axis, chart.tooltipBehavior.decimalPlaces) .toString()); - regionData.add(_getLabelValue(point.low, _yAxisRenderer._axis, + regionData.add(_getLabelValue(point.low, _yAxisRenderer!._axis, chart.tooltipBehavior.decimalPlaces) .toString()); } else { - regionData.add(_getLabelValue(point.yValue, _yAxisRenderer._axis, + regionData.add(_getLabelValue(point.yValue, _yAxisRenderer!._axis, chart.tooltipBehavior.decimalPlaces) .toString()); } @@ -811,19 +830,19 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { regionRect.add(_isRectSeries ? _seriesType == 'column' || _seriesType.contains('stackedcolumn') ? point.yValue > 0 - ? point.region.topCenter - : point.region.bottomCenter - : point.region.topCenter + ? point.region!.topCenter + : point.region!.bottomCenter + : point.region!.topCenter : (_seriesType == 'rangearea' - ? Offset(point.markerPoint.x, - (point.markerPoint.y + point.markerPoint2.y) / 2) - : point.region.center)); + ? Offset(point.markerPoint!.x, + (point.markerPoint!.y + point.markerPoint2!.y) / 2) + : point.region!.center)); regionRect.add(point.pointColorMapper); regionRect.add(point.bubbleSize); if (_seriesType.contains('stacked')) { regionData.add((point.cumulativeValue).toString()); } - _regionalData[regionRect] = regionData; + _regionalData![regionRect] = regionData; } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart index 3f7c1c275..ca59ae287 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart @@ -9,12 +9,15 @@ class _CustomPaintStyle { class _AxisSize { _AxisSize(this.axisRenderer, this.size); - num size; + double size; ChartAxisRenderer axisRenderer; } class _PainterKey { - _PainterKey({this.index, this.name, this.isRenderCompleted}); + _PainterKey( + {required this.index, + required this.name, + required this.isRenderCompleted}); final int index; final String name; bool isRenderCompleted; @@ -24,14 +27,14 @@ class _PainterKey { /// ///Shows the information about the segments.To enable the interactiveToolTip, set the property to true. /// -/// By using this,to customize the [color], [borderwidth], [borderRadius], +/// By using this,to customize the [color], [borderWidth], [borderRadius], /// [format] and so on. /// /// _Note:_ IntereactivetoolTip applicable for axis types and trackball. class InteractiveTooltip { /// Creating an argument constructor of InteractiveTooltip class. - InteractiveTooltip( + const InteractiveTooltip( {this.enable = true, this.color, this.borderColor, @@ -44,13 +47,12 @@ class InteractiveTooltip { this.connectorLineWidth = 1.5, this.connectorLineDashArray, this.decimalPlaces = 3, - TextStyle textStyle}) - : textStyle = textStyle ?? - const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12); + this.canShowMarker = true, + this.textStyle = const TextStyle( + fontFamily: 'Roboto', + fontStyle: FontStyle.normal, + fontWeight: FontWeight.normal, + fontSize: 12)}); ///Toggles the visibility of the interactive tooltip in an axis. /// @@ -71,7 +73,7 @@ class InteractiveTooltip { ///Color of the interactive tooltip. /// - ///Used to change the color of the tooltip text. + ///Used to change the [color] of the tooltip text. /// ///Defaults to `null`. ///```dart @@ -84,7 +86,7 @@ class InteractiveTooltip { /// )); ///} ///``` - final Color color; + final Color? color; ///Border color of the interactive tooltip. /// @@ -102,7 +104,7 @@ class InteractiveTooltip { /// )); ///} ///``` - final Color borderColor; + final Color? borderColor; ///Border width of the interactive tooltip. /// @@ -139,7 +141,7 @@ class InteractiveTooltip { ///Customizes the corners of the interactive tooltip. /// - ///Each corner can be customized with a desired value or with a single value. + ///Each corner can be customized with a desired value or a single value. /// ///Defaults to `Radius.zero`. ///```dart @@ -200,7 +202,7 @@ class InteractiveTooltip { /// )); ///} ///``` - final String format; + final String? format; ///Width of the selection zooming tooltip connector line. /// @@ -227,7 +229,7 @@ class InteractiveTooltip { /// )); ///} ///``` - final Color connectorLineColor; + final Color? connectorLineColor; ///Giving dashArray to the selection zooming tooltip connector line. /// @@ -240,7 +242,7 @@ class InteractiveTooltip { /// )); ///} ///``` - final List connectorLineDashArray; + final List? connectorLineDashArray; ///Rounding decimal places of the selection zooming tooltip or trackball tooltip label. /// @@ -254,6 +256,26 @@ class InteractiveTooltip { ///} ///``` final int decimalPlaces; + + ///Toggles the visibility of the marker in the trackball tooltip. + /// + ///Markers are rendered with the series color and placed near the value in trackball + ///tooltip to convey which value belongs to which series. + /// + ///Trackball tooltip marker uses the same shape specified for the series marker. But + ///trackball tooltip marker will render based on the value specified to this property + ///irrespective of considering the series marker's visibility. + /// + /// Defaults to `true` + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(interactiveTooltip: InteractiveTooltip(canSHowMarker:true)), + /// )); + ///} + ///``` + final bool canShowMarker; } class _StackedValues { @@ -276,82 +298,82 @@ class _StackedItemInfo { class _ChartPointInfo { /// Marker x position - double markerXPos; + double? markerXPos; /// Marker y position - double markerYPos; + double? markerYPos; /// label for trackball and cross hair - String label; + String? label; /// Data point index - int dataPointIndex; + int? dataPointIndex; /// Instance of chart series - CartesianSeries series; + CartesianSeries? series; /// Instance of cartesian seriesRenderer - CartesianSeriesRenderer seriesRenderer; + CartesianSeriesRenderer? seriesRenderer; /// Chart data point - CartesianChartPoint chartDataPoint; + CartesianChartPoint? chartDataPoint; /// X position of the label - double xPosition; + double? xPosition; /// Y position of the label - double yPosition; + double? yPosition; /// Color of the segment - Color color; + Color? color; /// header text - String header; + String? header; /// Low Y position of financial series - double lowYPosition; + double? lowYPosition; /// High X position of financial series - double highXPosition; + double? highXPosition; /// High Y position of financial series - double highYPosition; + double? highYPosition; /// Open y position of financial series - double openYPosition; + double? openYPosition; /// close y position of financial series - double closeYPosition; + double? closeYPosition; /// open x position of finanical series - double openXPosition; + double? openXPosition; /// close x position of finanical series - double closeXPosition; + double? closeXPosition; /// Minimum Y position of box plot series - double minYPosition; + double? minYPosition; /// Maximum Y position of box plot series - double maxYPosition; + double? maxYPosition; /// Lower y position of box plot series - double lowerXPosition; + double? lowerXPosition; /// Upper y position of box plot series - double upperXPosition; + double? upperXPosition; /// Lower x position of box plot series - double lowerYPosition; + double? lowerYPosition; /// Upper x position of box plot series - double upperYPosition; + double? upperYPosition; // Maximum x position forr vox plot series - double maxXPosition; + double? maxXPosition; /// series index value - num seriesIndex; + late int seriesIndex; } ///Options to customize the markers that are displayed when trackball is enabled. @@ -366,13 +388,13 @@ class TrackballMarkerSettings extends MarkerSettings { /// Creating an argument constructor of TrackballMarkerSettings class. TrackballMarkerSettings( {this.markerVisibility = TrackballVisibilityMode.auto, - double height, - double width, - Color color, - DataMarkerType shape, - double borderWidth, - Color borderColor, - ImageProvider image}) + double? height, + double? width, + Color? color, + DataMarkerType? shape, + double? borderWidth, + Color? borderColor, + ImageProvider? image}) : super( height: height, width: width, @@ -385,11 +407,11 @@ class TrackballMarkerSettings extends MarkerSettings { ///Whether marker should be visible or not when trackball is enabled. /// ///The below values are applicable for this: - ///* auto - If the [isVisible] property in the series [markerSettings] is set + ///* auto - If the [isVisible] property in the series `markerSettings` is set /// to true, then the trackball marker will also be displayed for that /// particular series, else it will not be displayed. ///* visible - Makes the trackball marker visible for all the series, - /// irrespective of considering the [isVisible] property's value in the [markerSettings]. + /// irrespective of considering the [isVisible] property's value in the `markerSettings`. ///* hidden - Hides the trackball marker for all the series. /// ///Defaults to `TrackballVisibilityMode.auto`. @@ -424,14 +446,24 @@ class TrackballDetails { this.pointIndex, this.seriesIndex, this.groupingModeInfo]); - final CartesianChartPoint point; - final CartesianSeries series; - final int pointIndex; - final int seriesIndex; - final TrackballGroupingModeInfo groupingModeInfo; + + /// It specifies the cartesian chart point. + final CartesianChartPoint? point; + + /// It specifies the cartesian series. + final CartesianSeries? series; + + /// It specifies the point index. + final int? pointIndex; + + /// It specifies the series index. + final int? seriesIndex; + + /// It specifies the trackball grouping mode info. + final TrackballGroupingModeInfo? groupingModeInfo; } -/// To get cartesian type data label saturation color +/// To get cartesian type data label saturation color. Color _getDataLabelSaturationColor( CartesianChartPoint currentPoint, CartesianSeriesRenderer seriesRenderer, @@ -463,7 +495,7 @@ Color _getDataLabelSaturationColor( : 'Default'; if (dataLabel.useSeriesColor || dataLabelSettingsRenderer._color != null) { color = dataLabelSettingsRenderer._color ?? - (currentPoint.pointColorMapper ?? seriesRenderer._seriesColor); + (currentPoint.pointColorMapper ?? seriesRenderer._seriesColor!); } else { switch (seriesType) { case 'Line': @@ -505,7 +537,7 @@ Color _getDataLabelSaturationColor( case 'area': color = (!currentPoint.dataLabelSaturationRegionInside && - currentPoint.labelLocation.y < currentPoint.markerPoint.y) + currentPoint.labelLocation!.y < currentPoint.markerPoint!.y) ? _getOuterDataLabelColor(dataLabelSettingsRenderer, chart.plotAreaBackgroundColor, _chartState._chartTheme) : _getInnerDataLabelColor( @@ -525,11 +557,11 @@ Color _getOpenCloseDataLabelColor(CartesianChartPoint currentPoint, Color color; color = seriesRenderer ._segments[seriesRenderer._dataPoints.indexOf(currentPoint)] - .fillPaint + .fillPaint! .style == PaintingStyle.fill ? seriesRenderer - ._segments[seriesRenderer._dataPoints.indexOf(currentPoint)]._color + ._segments[seriesRenderer._dataPoints.indexOf(currentPoint)]._color! : const Color.fromRGBO(255, 255, 255, 1); return _getSaturationColor(color); } @@ -537,13 +569,13 @@ Color _getOpenCloseDataLabelColor(CartesianChartPoint currentPoint, /// To get outer data label color Color _getOuterDataLabelColor( DataLabelSettingsRenderer dataLabelSettingsRenderer, - Color backgroundColor, + Color? backgroundColor, SfChartThemeData theme) => dataLabelSettingsRenderer._color ?? - backgroundColor ?? - theme.brightness == Brightness.light + backgroundColor ?? + (theme.brightness == Brightness.light ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black; + : Colors.black); ///To get inner data label Color _getInnerDataLabelColor(CartesianChartPoint currentPoint, @@ -551,24 +583,24 @@ Color _getInnerDataLabelColor(CartesianChartPoint currentPoint, final DataLabelSettingsRenderer dataLabelSettingsRenderer = seriesRenderer._dataLabelSettingsRenderer; Color innerColor; - Color seriesColor = seriesRenderer._series.color; + Color? seriesColor = seriesRenderer._series.color; if (seriesRenderer._seriesType == 'waterfall') { seriesColor = _getWaterfallSeriesColor( - seriesRenderer._series, currentPoint, seriesColor); + seriesRenderer._series as WaterfallSeries, currentPoint, seriesColor); } // ignore: prefer_if_null_operators innerColor = dataLabelSettingsRenderer._color != null - ? dataLabelSettingsRenderer._color + ? dataLabelSettingsRenderer._color! // ignore: prefer_if_null_operators : currentPoint.pointColorMapper != null // ignore: prefer_if_null_operators - ? currentPoint.pointColorMapper + ? currentPoint.pointColorMapper! // ignore: prefer_if_null_operators : seriesColor != null ? seriesColor // ignore: prefer_if_null_operators : seriesRenderer._seriesColor != null - ? seriesRenderer._seriesColor + ? seriesRenderer._seriesColor! : theme.brightness == Brightness.light ? const Color.fromRGBO(255, 255, 255, 1) : Colors.black; @@ -583,15 +615,15 @@ void _animateRectSeries( RRect segmentRect, num yPoint, double animationFactor, - Rect oldSegmentRect, - num oldYValue, - bool oldSeriesVisible) { - final bool comparePrev = seriesRenderer._chartState._widgetNeedUpdate && + Rect? oldSegmentRect, + num? oldYValue, + bool? oldSeriesVisible) { + final bool comparePrev = seriesRenderer._chartState!._widgetNeedUpdate && oldYValue != null && !seriesRenderer._reAnimate && oldSegmentRect != null; final bool isLargePrev = oldYValue != null ? oldYValue > yPoint : false; - final bool isSingleSeries = seriesRenderer._chartState._isLegendToggled + final bool isSingleSeries = seriesRenderer._chartState!._isLegendToggled ? _checkSingleSeries(seriesRenderer) : false; if ((seriesRenderer._seriesType == 'column' && @@ -611,7 +643,8 @@ void _animateRectSeries( oldSeriesVisible, comparePrev, isLargePrev, - isSingleSeries); + isSingleSeries, + oldYValue); } else { _animateNormalRectSeries( canvas, @@ -624,7 +657,8 @@ void _animateRectSeries( oldSeriesVisible, comparePrev, isLargePrev, - isSingleSeries); + isSingleSeries, + oldYValue); } } @@ -636,90 +670,122 @@ void _animateTransposedRectSeries( RRect segmentRect, num yPoint, double animationFactor, - Rect oldSegmentRect, - bool oldSeriesVisible, + Rect? oldSegmentRect, + bool? oldSeriesVisible, bool comparePrev, bool isLargePrev, - bool isSingleSeries) { + bool isSingleSeries, + num? oldYValue) { double width = segmentRect.width; final double height = segmentRect.height; final double top = segmentRect.top; double left = segmentRect.left, right = segmentRect.right; - Rect rect; + Rect? rect; + final num defaultCrossesAtValue = 0; final num crossesAt = - _getCrossesAtValue(seriesRenderer, seriesRenderer._chartState); + _getCrossesAtValue(seriesRenderer, seriesRenderer._chartState!) ?? + defaultCrossesAtValue; seriesRenderer._needAnimateSeriesElements = seriesRenderer._needAnimateSeriesElements || segmentRect.outerRect != oldSegmentRect; - if (!seriesRenderer._chartState._isLegendToggled || + if (!seriesRenderer._chartState!._isLegendToggled || seriesRenderer._reAnimate) { width = segmentRect.width * ((!comparePrev && !seriesRenderer._reAnimate && - !seriesRenderer._chartState._initialRender && + !seriesRenderer._chartState!._initialRender! && oldSegmentRect == null && - seriesRenderer._chartState._oldSeriesKeys + seriesRenderer._series.key != null && + seriesRenderer._chartState!._oldSeriesKeys .contains(seriesRenderer._series.key)) ? 1 : animationFactor); - if (!seriesRenderer._yAxisRenderer._axis.isInversed) { + if (!seriesRenderer._yAxisRenderer!._axis.isInversed) { if (comparePrev) { - if (yPoint < (crossesAt ?? 0)) { + if (yPoint < crossesAt) { left = isLargePrev - ? oldSegmentRect.left - + ? oldSegmentRect!.left - (animationFactor * (oldSegmentRect.left - segmentRect.left)) - : oldSegmentRect.left + + : oldSegmentRect!.left + (animationFactor * (segmentRect.left - oldSegmentRect.left)); right = segmentRect.right; width = right - left; + } else if (yPoint == crossesAt) { + if (oldYValue != crossesAt) { + if (oldYValue! < crossesAt) { + left = oldSegmentRect!.left + + (animationFactor * (segmentRect.left - oldSegmentRect.left)); + right = segmentRect.right; + width = right - left; + } else { + right = oldSegmentRect!.right + + (animationFactor * + (segmentRect.right - oldSegmentRect.right)); + width = right - segmentRect.left; + } + } } else { right = isLargePrev - ? oldSegmentRect.right + + ? oldSegmentRect!.right + (animationFactor * (segmentRect.right - oldSegmentRect.right)) - : oldSegmentRect.right - + : oldSegmentRect!.right - (animationFactor * (oldSegmentRect.right - segmentRect.right)); width = right - segmentRect.left; } } else { - right = yPoint < (crossesAt ?? 0) + right = yPoint < crossesAt ? segmentRect.right : (segmentRect.right - segmentRect.width) + width; } } else { if (comparePrev) { - if (yPoint < (crossesAt ?? 0)) { + if (yPoint < crossesAt) { right = isLargePrev - ? oldSegmentRect.right + + ? oldSegmentRect!.right + (animationFactor * (segmentRect.right - oldSegmentRect.right)) - : oldSegmentRect.right - + : oldSegmentRect!.right - (animationFactor * (oldSegmentRect.right - segmentRect.right)); width = right - segmentRect.left; + } else if (yPoint == crossesAt) { + if (oldYValue != crossesAt) { + if (oldYValue! < crossesAt) { + right = oldSegmentRect!.right - + (animationFactor * + (oldSegmentRect.right - segmentRect.right)); + width = right - segmentRect.left; + } else { + left = oldSegmentRect!.left - + (animationFactor * (oldSegmentRect.left - segmentRect.left)); + right = segmentRect.right; + width = right - left; + } + } } else { left = isLargePrev - ? oldSegmentRect.left - + ? oldSegmentRect!.left - (animationFactor * (oldSegmentRect.left - segmentRect.left)) - : oldSegmentRect.left + + : oldSegmentRect!.left + (animationFactor * (segmentRect.left - oldSegmentRect.left)); right = segmentRect.right; width = right - left; } } else { - right = yPoint < (crossesAt ?? 0) + right = yPoint < crossesAt ? (segmentRect.right - segmentRect.width) + width : segmentRect.right; } } rect = Rect.fromLTWH(right - width, top, width, height); - } else if (seriesRenderer._chartState._isLegendToggled && + } else if (seriesRenderer._chartState!._isLegendToggled && oldSegmentRect != null) { rect = _performTransposedLegendToggleAnimation(seriesRenderer, segmentRect, oldSegmentRect, oldSeriesVisible, isSingleSeries, animationFactor); } canvas.drawRRect( - RRect.fromRectAndCorners(rect ?? segmentRect?.middleRect, + RRect.fromRectAndCorners(rect ?? segmentRect.middleRect, bottomLeft: segmentRect.blRadius, bottomRight: segmentRect.brRadius, topLeft: segmentRect.tlRadius, @@ -731,8 +797,8 @@ void _animateTransposedRectSeries( Rect _performTransposedLegendToggleAnimation( CartesianSeriesRenderer seriesRenderer, RRect segmentRect, - Rect oldSegmentRect, - bool oldSeriesVisible, + Rect? oldSegmentRect, + bool? oldSeriesVisible, bool isSingleSeries, double animationFactor) { final CartesianSeries series = seriesRenderer._series; @@ -742,7 +808,7 @@ Rect _performTransposedLegendToggleAnimation( double right = segmentRect.right; final double width = segmentRect.width; if (oldSeriesVisible != null && oldSeriesVisible) { - bottom = oldSegmentRect.bottom > segmentRect.bottom + bottom = oldSegmentRect!.bottom > segmentRect.bottom ? oldSegmentRect.bottom + (animationFactor * (segmentRect.bottom - oldSegmentRect.bottom)) : oldSegmentRect.bottom - @@ -781,90 +847,120 @@ void _animateNormalRectSeries( RRect segmentRect, num yPoint, double animationFactor, - Rect oldSegmentRect, - bool oldSeriesVisible, + Rect? oldSegmentRect, + bool? oldSeriesVisible, bool comparePrev, bool isLargePrev, - bool isSingleSeries) { + bool isSingleSeries, + num? oldYValue) { double height = segmentRect.height; final double width = segmentRect.width; final double left = segmentRect.left; double top = segmentRect.top, bottom; - Rect rect; + Rect? rect; + final num defaultCrossesAtValue = 0; final num crossesAt = - _getCrossesAtValue(seriesRenderer, seriesRenderer._chartState); + _getCrossesAtValue(seriesRenderer, seriesRenderer._chartState!) ?? + defaultCrossesAtValue; seriesRenderer._needAnimateSeriesElements = seriesRenderer._needAnimateSeriesElements || segmentRect.outerRect != oldSegmentRect; - if (!seriesRenderer._chartState._isLegendToggled || + if (!seriesRenderer._chartState!._isLegendToggled || seriesRenderer._reAnimate) { height = segmentRect.height * ((!comparePrev && !seriesRenderer._reAnimate && - !seriesRenderer._chartState._initialRender && + !seriesRenderer._chartState!._initialRender! && oldSegmentRect == null && - seriesRenderer._chartState._oldSeriesKeys + seriesRenderer._series.key != null && + seriesRenderer._chartState!._oldSeriesKeys .contains(seriesRenderer._series.key)) ? 1 : animationFactor); - if (!seriesRenderer._yAxisRenderer._axis.isInversed) { + if (!seriesRenderer._yAxisRenderer!._axis.isInversed) { if (comparePrev) { - if (yPoint < (crossesAt ?? 0)) { + if (yPoint < crossesAt) { bottom = isLargePrev - ? oldSegmentRect.bottom - + ? oldSegmentRect!.bottom - (animationFactor * (oldSegmentRect.bottom - segmentRect.bottom)) - : oldSegmentRect.bottom + + : oldSegmentRect!.bottom + (animationFactor * (segmentRect.bottom - oldSegmentRect.bottom)); height = bottom - top; + } else if (yPoint == crossesAt) { + if (oldYValue! != crossesAt) { + if (oldYValue < crossesAt) { + bottom = oldSegmentRect!.bottom + + (animationFactor * + (segmentRect.bottom - oldSegmentRect.bottom)); + height = bottom - top; + } else { + top = oldSegmentRect!.top + + (animationFactor * (segmentRect.top - oldSegmentRect.top)); + height = segmentRect.bottom - top; + } + } } else { top = isLargePrev - ? oldSegmentRect.top + + ? oldSegmentRect!.top + (animationFactor * (segmentRect.top - oldSegmentRect.top)) - : oldSegmentRect.top - + : oldSegmentRect!.top - (animationFactor * (oldSegmentRect.top - segmentRect.top)); height = segmentRect.bottom - top; } } else { - top = yPoint < (crossesAt ?? 0) + top = yPoint < crossesAt ? segmentRect.top : (segmentRect.top + segmentRect.height) - height; } } else { if (comparePrev) { - if (yPoint < (crossesAt ?? 0)) { + if (yPoint < crossesAt) { top = isLargePrev - ? oldSegmentRect.top + + ? oldSegmentRect!.top + (animationFactor * (segmentRect.top - oldSegmentRect.top)) - : oldSegmentRect.top - + : oldSegmentRect!.top - (animationFactor * (oldSegmentRect.top - segmentRect.top)); height = segmentRect.bottom - top; + } else if (yPoint == crossesAt) { + if (oldYValue! != crossesAt) { + if (oldYValue < crossesAt) { + top = oldSegmentRect!.top - + (animationFactor * (oldSegmentRect.top - segmentRect.top)); + height = segmentRect.bottom - top; + } else { + bottom = oldSegmentRect!.bottom - + (animationFactor * + (oldSegmentRect.bottom - segmentRect.bottom)); + height = bottom - top; + } + } } else { bottom = isLargePrev - ? oldSegmentRect.bottom - + ? oldSegmentRect!.bottom - (animationFactor * (oldSegmentRect.bottom - segmentRect.bottom)) - : oldSegmentRect.bottom + + : oldSegmentRect!.bottom + (animationFactor * (segmentRect.bottom - oldSegmentRect.bottom)); height = bottom - top; } } else { - top = yPoint < (crossesAt ?? 0) + top = yPoint < crossesAt ? (segmentRect.top + segmentRect.height) - height : segmentRect.top; } } rect = Rect.fromLTWH(left, top, width, height); - } else if (seriesRenderer._chartState._isLegendToggled && + } else if (seriesRenderer._chartState!._isLegendToggled && oldSegmentRect != null && oldSeriesVisible != null) { rect = _performLegendToggleAnimation(seriesRenderer, segmentRect, oldSegmentRect, oldSeriesVisible, isSingleSeries, animationFactor); } canvas.drawRRect( - RRect.fromRectAndCorners(rect ?? segmentRect?.middleRect, + RRect.fromRectAndCorners(rect ?? segmentRect.middleRect, bottomLeft: segmentRect.blRadius, bottomRight: segmentRect.brRadius, topLeft: segmentRect.tlRadius, @@ -928,17 +1024,18 @@ void _animateStackedRectSeries( SfCartesianChartState _chartState) { int index, seriesIndex; List seriesCollection; - Rect prevRegion; - final _StackedSeriesBase series = seriesRenderer._series; + Rect? prevRegion; + final _StackedSeriesBase series = + seriesRenderer._series as _StackedSeriesBase; index = seriesRenderer._dataPoints.indexOf(currentPoint); seriesCollection = _findSeriesCollection(_chartState); seriesIndex = seriesCollection.indexOf(seriesRenderer); bool isStackLine = false; if (seriesIndex != 0) { - if (seriesRenderer._chartState._chartSeries + if (seriesRenderer._chartState!._chartSeries .visibleSeriesRenderers[seriesIndex - 1] is StackedLineSeriesRenderer || - seriesRenderer._chartState._chartSeries + seriesRenderer._chartState!._chartSeries .visibleSeriesRenderers[seriesIndex - 1] is StackedLine100SeriesRenderer) { isStackLine = true; @@ -961,25 +1058,8 @@ void _animateStackedRectSeries( index < clusterStackedItemInfo.stackedItemInfo[k - 1] .seriesRenderer._dataPoints.length) { - if ((currentPoint.yValue > 0 && - clusterStackedItemInfo.stackedItemInfo[k - 1] - .seriesRenderer._dataPoints[index].yValue > - 0) || - (currentPoint.yValue < 0 && - clusterStackedItemInfo.stackedItemInfo[k - 1] - .seriesRenderer._dataPoints[index].yValue < - 0)) { - prevRegion = clusterStackedItemInfo.stackedItemInfo[k - 1] - .seriesRenderer._dataPoints[index].region; - } else { - if (k > 1) { - prevRegion = clusterStackedItemInfo - .stackedItemInfo[(k - 1) - 1] - .seriesRenderer - ._dataPoints[index] - .region; - } - } + prevRegion = _getPrevRegion( + currentPoint.yValue, clusterStackedItemInfo, index, k); } } } @@ -1000,27 +1080,29 @@ void _drawAnimatedStackedRect( double animationFactor, CartesianChartPoint currentPoint, int index, - Rect prevRegion) { + Rect? prevRegion) { double top = segmentRect.top, height = segmentRect.height; double right = segmentRect.right, width = segmentRect.width; final double height1 = segmentRect.height, top1 = segmentRect.top; + final num defaultCrossesAtValue = 0; final crossesAt = - _getCrossesAtValue(seriesRenderer, seriesRenderer._chartState); + _getCrossesAtValue(seriesRenderer, seriesRenderer._chartState!) ?? + defaultCrossesAtValue; height = segmentRect.height * animationFactor; width = segmentRect.width * animationFactor; if ((seriesRenderer._seriesType.contains('stackedcolumn')) && seriesRenderer._chart.isTransposed != true || (seriesRenderer._seriesType.contains('stackedbar')) && seriesRenderer._chart.isTransposed == true) { - seriesRenderer._yAxisRenderer._axis.isInversed != true - ? seriesRenderer._dataPoints[index].y > (crossesAt ?? 0) + seriesRenderer._yAxisRenderer!._axis.isInversed != true + ? seriesRenderer._dataPoints[index].y > crossesAt ? prevRegion == null ? top = (segmentRect.top + segmentRect.height) - height : top = prevRegion.top - height : prevRegion == null ? top = segmentRect.top : top = prevRegion.bottom - : seriesRenderer._dataPoints[index].y > (crossesAt ?? 0) + : seriesRenderer._dataPoints[index].y > crossesAt ? prevRegion == null ? top = segmentRect.top : top = prevRegion.bottom @@ -1041,15 +1123,15 @@ void _drawAnimatedStackedRect( seriesRenderer._chart.isTransposed == true || (seriesRenderer._seriesType.contains('stackedbar')) && seriesRenderer._chart.isTransposed != true) { - seriesRenderer._yAxisRenderer._axis.isInversed != true - ? seriesRenderer._dataPoints[index].y > (crossesAt ?? 0) + seriesRenderer._yAxisRenderer!._axis.isInversed != true + ? seriesRenderer._dataPoints[index].y > crossesAt ? prevRegion == null ? right = (segmentRect.right - segmentRect.width) + width : right = prevRegion.right + width : prevRegion == null ? right = segmentRect.right : right = prevRegion.left - : seriesRenderer._dataPoints[index].y > (crossesAt ?? 0) + : seriesRenderer._dataPoints[index].y > crossesAt ? prevRegion == null ? right = segmentRect.right : right = prevRegion.left @@ -1069,12 +1151,34 @@ void _drawAnimatedStackedRect( } } +// This recursive function returns the previous region of the cluster stakced info for animation purposes +Rect? _getPrevRegion(dynamic yValue, + _ClusterStackedItemInfo clusterStackedItemInfo, int index, int k) { + Rect? prevRegion; + if ((yValue > 0 && + clusterStackedItemInfo.stackedItemInfo[k - 1].seriesRenderer + ._dataPoints[index].yValue > + 0) || + (yValue < 0 && + clusterStackedItemInfo.stackedItemInfo[k - 1].seriesRenderer + ._dataPoints[index].yValue < + 0)) { + prevRegion = clusterStackedItemInfo + .stackedItemInfo[k - 1].seriesRenderer._dataPoints[index].region; + } else { + if (k > 1) { + prevRegion = _getPrevRegion(yValue, clusterStackedItemInfo, index, k - 1); + } + } + return prevRegion; +} + /// To check the series count bool _checkSingleSeries(CartesianSeriesRenderer seriesRenderer) { int count = 0; for (final CartesianSeriesRenderer seriesRenderer - in seriesRenderer._chartState._chartSeries.visibleSeriesRenderers) { - if (seriesRenderer._visible && + in seriesRenderer._chartState!._chartSeries.visibleSeriesRenderers) { + if (seriesRenderer._visible! && (seriesRenderer._seriesType == 'column' || seriesRenderer._seriesType == 'bar')) { count++; @@ -1089,46 +1193,54 @@ void _animateLineTypeSeries( CartesianSeriesRenderer seriesRenderer, Paint strokePaint, double animationFactor, - num newX1, - num newY1, - num newX2, - num newY2, - num oldX1, - num oldY1, - num oldX2, - num oldY2, - [num newX3, - num newY3, - num oldX3, - num oldY3, - num newX4, - num newY4, - num oldX4, - num oldY4]) { - num x1 = newX1; - num y1 = newY1; - num x2 = newX2; - num y2 = newY2; - num x3 = newX3; - num y3 = newY3; - num x4 = newX4; - num y4 = newY4; + double newX1, + double newY1, + double newX2, + double newY2, + double? oldX1, + double? oldY1, + double? oldX2, + double? oldY2, + [double? newX3, + double? newY3, + double? oldX3, + double? oldY3, + double? newX4, + double? newY4, + double? oldX4, + double? oldY4]) { + double x1 = newX1; + double y1 = newY1; + double x2 = newX2; + double y2 = newY2; + double? x3 = newX3; + double? y3 = newY3; + double? x4 = newX4; + double? y4 = newY4; y1 = _getAnimateValue(animationFactor, y1, oldY1, newY1, seriesRenderer); y2 = _getAnimateValue(animationFactor, y2, oldY2, newY2, seriesRenderer); - y3 = _getAnimateValue(animationFactor, y3, oldY3, newY3, seriesRenderer); - y4 = _getAnimateValue(animationFactor, y4, oldY4, newY4, seriesRenderer); + y3 = y3 != null + ? _getAnimateValue(animationFactor, y3, oldY3, newY3, seriesRenderer) + : y3; + y4 = y4 != null + ? _getAnimateValue(animationFactor, y4, oldY4, newY4, seriesRenderer) + : y4; x1 = _getAnimateValue(animationFactor, x1, oldX1, newX1, seriesRenderer); x2 = _getAnimateValue(animationFactor, x2, oldX2, newX2, seriesRenderer); - x3 = _getAnimateValue(animationFactor, x3, oldX3, newX3, seriesRenderer); - x4 = _getAnimateValue(animationFactor, x4, oldX4, newX4, seriesRenderer); + x3 = x3 != null + ? _getAnimateValue(animationFactor, x3, oldX3, newX3, seriesRenderer) + : x3; + x4 = x4 != null + ? _getAnimateValue(animationFactor, x4, oldX4, newX4, seriesRenderer) + : x4; final Path path = Path(); path.moveTo(x1, y1); if (seriesRenderer._seriesType == 'stepline') { - path.lineTo(x3, y3); + path.lineTo(x3!, y3!); } seriesRenderer._seriesType == 'spline' - ? path.cubicTo(x3, y3, x4, y4, x2, y2) + ? path.cubicTo(x3!, y3!, x4!, y4!, x2, y2) : path.lineTo(x2, y2); _drawDashedLine(canvas, seriesRenderer._series.dashArray, strokePaint, path); } @@ -1138,17 +1250,17 @@ void _animateToPoint( CartesianSeriesRenderer seriesRenderer, Paint strokePaint, double animationFactor, - num x1, - num y1, - num x2, - num y2, - num prevX, - num prevY) { + double x1, + double y1, + double x2, + double y2, + double? prevX, + double? prevY) { final Path path = Path(); prevX ??= x1; prevY ??= y1; - final num newX1 = prevX + (x1 - prevX) * animationFactor; - final num newY1 = prevY + (y1 - prevY) * animationFactor; + final double newX1 = prevX + (x1 - prevX) * animationFactor; + final double newY1 = prevY + (y1 - prevY) * animationFactor; path.moveTo(newX1, newY1); path.lineTo(newX1 + (x2 - newX1) * animationFactor, newY1 + (y2 - newY1) * animationFactor); @@ -1156,43 +1268,43 @@ void _animateToPoint( } /// To get value of animation based on animation factor -num _getAnimateValue( - double animationFactor, double value, num oldvalue, num newValue, - [CartesianSeriesRenderer seriesRenderer]) { +double _getAnimateValue( + double animationFactor, double? value, double? oldvalue, double? newValue, + [CartesianSeriesRenderer? seriesRenderer]) { if (seriesRenderer != null) { seriesRenderer._needAnimateSeriesElements = seriesRenderer._needAnimateSeriesElements || oldvalue != newValue; } - return (oldvalue != null && newValue != null && !oldvalue.isNaN) + return ((oldvalue != null && newValue != null && !oldvalue.isNaN) ? ((oldvalue > newValue) ? oldvalue - ((oldvalue - newValue) * animationFactor) : oldvalue + ((newValue - oldvalue) * animationFactor)) - : value; + : value)!; } /// To animate scatter series void _animateScatterSeries( CartesianSeriesRenderer seriesRenderer, CartesianChartPoint point, - CartesianChartPoint _oldPoint, + CartesianChartPoint? _oldPoint, double animationFactor, Canvas canvas, Paint fillPaint, Paint strokePaint) { final CartesianSeries series = seriesRenderer._series; - final num width = series.markerSettings.width, + final double width = series.markerSettings.width, height = series.markerSettings.height; final DataMarkerType markerType = series.markerSettings.shape; - num x = point.markerPoint.x; - num y = point.markerPoint.y; - if (seriesRenderer._chartState._widgetNeedUpdate && + double x = point.markerPoint!.x; + double y = point.markerPoint!.y; + if (seriesRenderer._chartState!._widgetNeedUpdate && _oldPoint != null && !seriesRenderer._reAnimate && _oldPoint.markerPoint != null) { x = _getAnimateValue( - animationFactor, x, _oldPoint.markerPoint.x, x, seriesRenderer); + animationFactor, x, _oldPoint.markerPoint!.x, x, seriesRenderer); y = _getAnimateValue( - animationFactor, y, _oldPoint.markerPoint.y, y, seriesRenderer); + animationFactor, y, _oldPoint.markerPoint!.y, y, seriesRenderer); animationFactor = 1; } @@ -1200,38 +1312,38 @@ void _animateScatterSeries( { switch (markerType) { case DataMarkerType.circle: - _ChartShapeUtils._drawCircle( + ShapeMaker.drawCircle( path, x, y, animationFactor * width, animationFactor * height); break; case DataMarkerType.rectangle: - _ChartShapeUtils._drawRectangle( + ShapeMaker.drawRectangle( path, x, y, animationFactor * width, animationFactor * height); break; case DataMarkerType.image: _drawImageMarker(seriesRenderer, canvas, x, y); break; case DataMarkerType.pentagon: - _ChartShapeUtils._drawPentagon( + ShapeMaker.drawPentagon( path, x, y, animationFactor * width, animationFactor * height); break; case DataMarkerType.verticalLine: - _ChartShapeUtils._drawVerticalLine( + ShapeMaker.drawVerticalLine( path, x, y, animationFactor * width, animationFactor * height); break; case DataMarkerType.invertedTriangle: - _ChartShapeUtils._drawInvertedTriangle( + ShapeMaker.drawInvertedTriangle( path, x, y, animationFactor * width, animationFactor * height); break; case DataMarkerType.horizontalLine: - _ChartShapeUtils._drawHorizontalLine( + ShapeMaker.drawHorizontalLine( path, x, y, animationFactor * width, animationFactor * height); break; case DataMarkerType.diamond: - _ChartShapeUtils._drawDiamond( + ShapeMaker.drawDiamond( path, x, y, animationFactor * width, animationFactor * height); break; case DataMarkerType.triangle: - _ChartShapeUtils._drawTriangle( + ShapeMaker.drawTriangle( path, x, y, animationFactor * width, animationFactor * height); break; case DataMarkerType.none: @@ -1245,19 +1357,19 @@ void _animateScatterSeries( /// To animate bubble series void _animateBubbleSeries( Canvas canvas, - num newX, - num newY, - num oldX, - num oldY, - num oldSize, + double newX, + double newY, + double? oldX, + double? oldY, + double? oldSize, double animationFactor, - num radius, + double radius, Paint strokePaint, Paint fillPaint, CartesianSeriesRenderer seriesRenderer) { - num x = newX; - num y = newY; - num size = radius; + double x = newX; + double y = newY; + double size = radius; x = _getAnimateValue(animationFactor, x, oldX, newX, seriesRenderer); y = _getAnimateValue(animationFactor, y, oldY, newY, seriesRenderer); size = @@ -1272,7 +1384,7 @@ void _animateRangeColumn( CartesianSeriesRenderer seriesRenderer, Paint fillPaint, RRect segmentRect, - Rect oldSegmentRect, + Rect? oldSegmentRect, double animationFactor) { double height = segmentRect.height; double width = segmentRect.width; @@ -1347,16 +1459,16 @@ void _animateHiloSeries( bool transposed, _ChartLocation newLow, _ChartLocation newHigh, - _ChartLocation oldLow, - _ChartLocation oldHigh, + _ChartLocation? oldLow, + _ChartLocation? oldHigh, double animationFactor, Paint paint, Canvas canvas, CartesianSeriesRenderer seriesRenderer) { if (transposed) { - num lowX = newLow.x; - num highX = newHigh.x; - num y = newLow.y; + double lowX = newLow.x; + double highX = newHigh.x; + double y = newLow.y; y = _getAnimateValue( animationFactor, y, oldLow?.y, newLow.y, seriesRenderer); lowX = _getAnimateValue( @@ -1365,9 +1477,9 @@ void _animateHiloSeries( animationFactor, highX, oldHigh?.x, newHigh.x, seriesRenderer); canvas.drawLine(Offset(lowX, y), Offset(highX, y), paint); } else { - num low = newLow.y; - num high = newHigh.y; - num x = newLow.x; + double low = newLow.y; + double high = newHigh.y; + double x = newLow.x; x = _getAnimateValue( animationFactor, x, oldLow?.x, newLow.x, seriesRenderer); low = _getAnimateValue( @@ -1381,34 +1493,34 @@ void _animateHiloSeries( /// To animate the Hilo open-close series void _animateHiloOpenCloseSeries( bool transposed, - num newLow, - num newHigh, - num oldLow, - num oldHigh, - num newOpenX, - num newOpenY, - num newCloseX, - num newCloseY, - num newCenterLow, - num newCenterHigh, - num oldOpenX, - num oldOpenY, - num oldCloseX, - num oldCloseY, - num oldCenterLow, - num oldCenterHigh, + double newLow, + double newHigh, + double? oldLow, + double? oldHigh, + double newOpenX, + double newOpenY, + double newCloseX, + double newCloseY, + double newCenterLow, + double newCenterHigh, + double? oldOpenX, + double? oldOpenY, + double? oldCloseX, + double? oldCloseY, + double? oldCenterLow, + double? oldCenterHigh, double animationFactor, Paint paint, Canvas canvas, CartesianSeriesRenderer seriesRenderer) { - num low = newLow; - num high = newHigh; - num openX = newOpenX; - num openY = newOpenY; - num closeX = newCloseX; - num closeY = newCloseY; - num centerHigh = newCenterHigh; - num centerLow = newCenterLow; + double low = newLow; + double high = newHigh; + double openX = newOpenX; + double openY = newOpenY; + double closeX = newCloseX; + double closeY = newCloseY; + double centerHigh = newCenterHigh; + double centerLow = newCenterLow; low = _getAnimateValue(animationFactor, low, oldLow, newLow, seriesRenderer); high = _getAnimateValue(animationFactor, high, oldHigh, newHigh, seriesRenderer); @@ -1441,43 +1553,43 @@ void _animateBoxSeries( Offset meanPosition, Offset transposedMeanPosition, Size size, - num max, + double max, bool transposed, - num lowerQuartile1, - num upperQuartile1, - num newMin, - num newMax, - num oldMin, - num oldMax, - num newLowerQuartileX, - num newLowerQuartileY, - num newUpperQuartileX, - num newUpperQuartileY, - num newCenterMin, - num newCenterMax, - num oldLowerQuartileX, - num oldLowerQuartileY, - num oldUpperQuartileX, - num oldUpperQuartileY, - num oldCenterMin, - num oldCenterMax, - num medianX, - num medianY, + double lowerQuartile1, + double upperQuartile1, + double newMin, + double newMax, + double? oldMin, + double? oldMax, + double newLowerQuartileX, + double newLowerQuartileY, + double newUpperQuartileX, + double newUpperQuartileY, + double newCenterMin, + double newCenterMax, + double? oldLowerQuartileX, + double? oldLowerQuartileY, + double? oldUpperQuartileX, + double? oldUpperQuartileY, + double? oldCenterMin, + double? oldCenterMax, + double medianX, + double medianY, double animationFactor, Paint fillPaint, Paint strokePaint, Canvas canvas, CartesianSeriesRenderer seriesRenderer) { - final num lowerQuartile = lowerQuartile1; - final num upperQuartile = upperQuartile1; - num minY = newMin; - num maxY = newMax; - num lowerQuartileX = newLowerQuartileX; - num lowerQuartileY = newLowerQuartileY; - num upperQuartileX = newUpperQuartileX; - num upperQuartileY = newUpperQuartileY; - num centerMax = newCenterMax; - num centerMin = newCenterMin; + final double lowerQuartile = lowerQuartile1; + final double upperQuartile = upperQuartile1; + double minY = newMin; + double maxY = newMax; + double lowerQuartileX = newLowerQuartileX; + double lowerQuartileY = newLowerQuartileY; + double upperQuartileX = newUpperQuartileX; + double upperQuartileY = newUpperQuartileY; + double centerMax = newCenterMax; + double centerMin = newCenterMin; minY = _getAnimateValue(animationFactor, minY, oldMin, newMin, seriesRenderer); maxY = @@ -1496,7 +1608,7 @@ void _animateBoxSeries( animationFactor, centerMin, oldCenterMin, newCenterMin, seriesRenderer); final Path path = Path(); if (!transposed && lowerQuartile > upperQuartile) { - final num temp = upperQuartileY; + final double temp = upperQuartileY; upperQuartileY = lowerQuartileY; lowerQuartileY = temp; } @@ -1542,7 +1654,7 @@ void _animateBoxSeries( } } else { if (lowerQuartile > upperQuartile) { - final num temp = upperQuartileY; + final double temp = upperQuartileY; upperQuartileY = lowerQuartileY; lowerQuartileY = temp; } @@ -1633,7 +1745,7 @@ void _drawMeanValuePath( Paint strokePaint, Canvas canvas) { if (seriesRenderer._series.animationDuration <= 0 || - animationFactor >= seriesRenderer._chartState._seriesDurationFactor) { + animationFactor >= seriesRenderer._chartState!._seriesDurationFactor) { canvas.drawLine(Offset(x + size.width / 2, y - size.height / 2), Offset(x - size.width / 2, y + size.height / 2), strokePaint); canvas.drawLine(Offset(x + size.width / 2, y + size.height / 2), @@ -1644,40 +1756,40 @@ void _drawMeanValuePath( /// To animate the candle series void _animateCandleSeries( bool showSameValue, - num high, + double high, bool transposed, - num open1, - num close1, - num newLow, - num newHigh, - num oldLow, - num oldHigh, - num newOpenX, - num newOpenY, - num newCloseX, - num newCloseY, - num newCenterLow, - num newCenterHigh, - num oldOpenX, - num oldOpenY, - num oldCloseX, - num oldCloseY, - num oldCenterLow, - num oldCenterHigh, + double open1, + double close1, + double newLow, + double newHigh, + double? oldLow, + double? oldHigh, + double newOpenX, + double newOpenY, + double newCloseX, + double newCloseY, + double newCenterLow, + double newCenterHigh, + double? oldOpenX, + double? oldOpenY, + double? oldCloseX, + double? oldCloseY, + double? oldCenterLow, + double? oldCenterHigh, double animationFactor, Paint paint, Canvas canvas, CartesianSeriesRenderer seriesRenderer) { - final num open = open1; - final num close = close1; - num lowY = newLow; - num highY = newHigh; - num openX = newOpenX; - num openY = newOpenY; - num closeX = newCloseX; - num closeY = newCloseY; - num centerHigh = newCenterHigh; - num centerLow = newCenterLow; + final double open = open1; + final double close = close1; + double lowY = newLow; + double highY = newHigh; + double openX = newOpenX; + double openY = newOpenY; + double closeX = newCloseX; + double closeY = newCloseY; + double centerHigh = newCenterHigh; + double centerLow = newCenterLow; lowY = _getAnimateValue(animationFactor, lowY, oldLow, newLow, seriesRenderer); highY = _getAnimateValue( @@ -1696,10 +1808,11 @@ void _animateCandleSeries( animationFactor, centerLow, oldCenterLow, newCenterLow, seriesRenderer); final Path path = Path(); if (!transposed && open > close) { - final num temp = closeY; + final double temp = closeY; closeY = openY; openY = temp; } + if (transposed) { if (showSameValue) { canvas.drawLine( @@ -1730,7 +1843,7 @@ void _animateCandleSeries( } } else { if (open > close) { - final num temp = closeY; + final double temp = closeY; closeY = openY; openY = temp; } @@ -1753,6 +1866,7 @@ void _animateCandleSeries( path.close(); } } + if (seriesRenderer._series.dashArray[0] != 0 && seriesRenderer._series.dashArray[1] != 0 && paint.style != PaintingStyle.fill && @@ -1763,26 +1877,13 @@ void _animateCandleSeries( } if (paint.style == PaintingStyle.fill) { if (transposed) { - if (open > close) { - if (showSameValue) { - canvas.drawLine( - Offset(centerHigh, highY), Offset(centerLow, highY), paint); - } else { - canvas.drawLine( - Offset(centerHigh, highY), Offset(closeX, highY), paint); - canvas.drawLine( - Offset(centerLow, highY), Offset(openX, highY), paint); - } + if (showSameValue) { + canvas.drawLine( + Offset(centerHigh, highY), Offset(centerLow, highY), paint); } else { - if (showSameValue) { - canvas.drawLine( - Offset(centerHigh, highY), Offset(centerLow, highY), paint); - } else { - canvas.drawLine( - Offset(centerHigh, highY), Offset(closeX, highY), paint); - canvas.drawLine( - Offset(centerLow, highY), Offset(openX, highY), paint); - } + canvas.drawLine( + Offset(centerHigh, highY), Offset(closeX, highY), paint); + canvas.drawLine(Offset(centerLow, highY), Offset(openX, highY), paint); } } else { if (showSameValue) { @@ -1799,13 +1900,13 @@ void _animateCandleSeries( } // To get nearest chart points from the touch point -List> _getNearestChartPoints( +List>? _getNearestChartPoints( double pointX, double pointY, ChartAxisRenderer actualXAxisRenderer, ChartAxisRenderer actualYAxisRenderer, CartesianSeriesRenderer cartesianSeriesRenderer, - [List> firstNearestDataPoints]) { + [List>? firstNearestDataPoints]) { final List> dataPointList = >[]; List> dataList = @@ -1815,39 +1916,39 @@ List> _getNearestChartPoints( firstNearestDataPoints != null ? dataList = firstNearestDataPoints - : dataList = cartesianSeriesRenderer._dataPoints; + : dataList = cartesianSeriesRenderer._visibleDataPoints!; for (int i = 0; i < dataList.length; i++) { xValues.add(dataList[i].xValue); if (cartesianSeriesRenderer is BoxAndWhiskerSeriesRenderer) { - yValues.add((dataList[i].maximum + dataList[i].minimum) / 2); + yValues.add((dataList[i].maximum! + dataList[i].minimum!) / 2); } else { yValues .add(dataList[i].yValue ?? (dataList[i].high + dataList[i].low) / 2); } } - num nearPointX = actualXAxisRenderer._visibleRange.minimum; - num nearPointY = actualYAxisRenderer._visibleRange.minimum; + num nearPointX = dataList[0].xValue; + num nearPointY = actualYAxisRenderer._visibleRange!.minimum; final Rect rect = _calculatePlotOffset( - cartesianSeriesRenderer._chartState._chartAxis._axisClipRect, - Offset(cartesianSeriesRenderer._xAxisRenderer._axis.plotOffset, - cartesianSeriesRenderer._yAxisRenderer._axis.plotOffset)); + cartesianSeriesRenderer._chartState!._chartAxis._axisClipRect, + Offset(cartesianSeriesRenderer._xAxisRenderer!._axis.plotOffset, + cartesianSeriesRenderer._yAxisRenderer!._axis.plotOffset)); final num touchXValue = _pointToXValue( - cartesianSeriesRenderer._chartState._requireInvertedAxis, + cartesianSeriesRenderer._chartState!._requireInvertedAxis, actualXAxisRenderer, actualXAxisRenderer._axis.isVisible ? actualXAxisRenderer._bounds - : cartesianSeriesRenderer._chartState._chartAxis._axisClipRect, + : cartesianSeriesRenderer._chartState!._chartAxis._axisClipRect, pointX - rect.left, pointY - rect.top); final num touchYValue = _pointToYValue( - cartesianSeriesRenderer._chartState._requireInvertedAxis, + cartesianSeriesRenderer._chartState!._requireInvertedAxis, actualYAxisRenderer, actualYAxisRenderer._axis.isVisible ? actualYAxisRenderer._bounds - : cartesianSeriesRenderer._chartState._chartAxis._axisClipRect, + : cartesianSeriesRenderer._chartState!._chartAxis._axisClipRect, pointX - rect.left, pointY - rect.top); double delta = 0; @@ -1881,19 +1982,19 @@ List> _getNearestChartPoints( ZoomPanArgs _bindZoomEvent( SfCartesianChart chart, ChartAxisRenderer axisRenderer, - ZoomPanArgs zoomPanArgs, + ZoomPanArgs? zoomPanArgs, ChartZoomingCallback zoomEventType) { zoomPanArgs = ZoomPanArgs(axisRenderer._axis, axisRenderer._previousZoomPosition, axisRenderer._previousZoomFactor); zoomPanArgs.currentZoomFactor = axisRenderer._zoomFactor; zoomPanArgs.currentZoomPosition = axisRenderer._zoomPosition; zoomEventType == chart.onZoomStart - ? chart.onZoomStart(zoomPanArgs) + ? chart.onZoomStart!(zoomPanArgs) : zoomEventType == chart.onZoomEnd - ? chart.onZoomEnd(zoomPanArgs) + ? chart.onZoomEnd!(zoomPanArgs) : zoomEventType == chart.onZooming - ? chart.onZooming(zoomPanArgs) - : chart.onZoomReset(zoomPanArgs); + ? chart.onZooming!(zoomPanArgs) + : chart.onZoomReset!(zoomPanArgs); return zoomPanArgs; } @@ -1904,10 +2005,10 @@ Path _findingRectSeriesDashedBorder( Path path; Offset topLeft, topRight, bottomRight, bottomLeft; double width; - topLeft = currentPoint.region.topLeft; - topRight = currentPoint.region.topRight; - bottomLeft = currentPoint.region.bottomLeft; - bottomRight = currentPoint.region.bottomRight; + topLeft = currentPoint.region!.topLeft; + topRight = currentPoint.region!.topRight; + bottomLeft = currentPoint.region!.bottomLeft; + bottomRight = currentPoint.region!.bottomRight; width = borderWidth / 2; path = Path(); path.moveTo(topLeft.dx + width, topLeft.dy + width); @@ -1926,7 +2027,7 @@ Future _getImageInfo(ImageProvider imageProvider) async { .resolve(const ImageConfiguration()) .addListener(ImageStreamListener((ImageInfo info, bool _) { completer.complete(info); - return completer.future; + // return completer.future; })); final ImageInfo imageInfo = await completer.future; return imageInfo.image; @@ -1934,9 +2035,9 @@ Future _getImageInfo(ImageProvider imageProvider) async { //ignore: avoid_void_async void _calculateImage( - [dynamic chartState, - dynamic seriesRenderer, - TrackballBehavior trackballBehavior]) async { + [dynamic? chartState, + dynamic? seriesRenderer, + TrackballBehavior? trackballBehavior]) async { final dynamic chart = chartState?._chart; if (chart != null && seriesRenderer == null) { if (chart.plotAreaBackgroundImage != null) { @@ -1949,20 +2050,20 @@ void _calculateImage( chartState._chartLegend.legendRepaintNotifier.value++; } } else if (trackballBehavior != null && - trackballBehavior.markerSettings.image != null) { + trackballBehavior.markerSettings!.image != null) { chartState._trackballMarkerSettingsRenderer._image = - await _getImageInfo(trackballBehavior.markerSettings.image); + await _getImageInfo(trackballBehavior.markerSettings!.image!); chartState._trackballRepaintNotifier.value++; } else { final CartesianSeries series = seriesRenderer._series; // final MarkerSettingsRenderer markerSettingsRenderer = seriesRenderer._markerSettingsRenderer; if (series.markerSettings.image != null) { seriesRenderer._markerSettingsRenderer._image = - await _getImageInfo(series.markerSettings.image); + await _getImageInfo(series.markerSettings.image!); seriesRenderer._repaintNotifier.value++; if (seriesRenderer._seriesType == 'scatter' && seriesRenderer._chart.legend.isVisible) { - seriesRenderer._chartState._chartLegend.legendRepaintNotifier.value++; + seriesRenderer._chartState!._chartLegend.legendRepaintNotifier.value++; } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart index 171ad38a5..3da8138e1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart @@ -12,38 +12,27 @@ class DataLabelSettings { DataLabelSettings( {this.alignment = ChartAlignment.center, this.color, - TextStyle textStyle, - EdgeInsets margin, + this.textStyle = const TextStyle( + fontFamily: 'Roboto', + fontStyle: FontStyle.normal, + fontWeight: FontWeight.normal, + fontSize: 12), + this.margin = const EdgeInsets.fromLTRB(5, 5, 5, 5), this.opacity = 1, this.labelAlignment = ChartDataLabelAlignment.auto, this.borderRadius = 5, this.isVisible = false, this.angle = 0, this.builder, - bool useSeriesColor, + this.useSeriesColor = false, this.offset, this.showCumulativeValues = false, this.showZeroValue = true, - Color borderColor, - double borderWidth, - LabelIntersectAction labelIntersectAction, - ConnectorLineSettings connectorLineSettings, - ChartDataLabelPosition labelPosition}) - : borderColor = borderColor ?? Colors.transparent, - useSeriesColor = useSeriesColor ?? false, - labelPosition = labelPosition ?? ChartDataLabelPosition.inside, - borderWidth = borderWidth ?? 0, - margin = margin ?? const EdgeInsets.fromLTRB(5, 5, 5, 5), - connectorLineSettings = - connectorLineSettings ?? ConnectorLineSettings(), - labelIntersectAction = - labelIntersectAction ?? LabelIntersectAction.hide, - textStyle = textStyle ?? - const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12); + this.borderColor = Colors.transparent, + this.borderWidth = 0, + this.labelIntersectAction = LabelIntersectAction.hide, + this.connectorLineSettings = const ConnectorLineSettings(), + this.labelPosition = ChartDataLabelPosition.inside}); ///Alignment of the data label. /// @@ -79,7 +68,7 @@ class DataLabelSettings { /// )); ///} ///``` - final Color color; + final Color? color; ///Customizes the data label font. ///```dart @@ -364,7 +353,7 @@ class DataLabelSettings { /// )); ///} ///``` - final ChartWidgetBuilder builder; + final ChartWidgetBuilder? builder; /// To show the cummulative values in stacked type series charts. /// @@ -433,7 +422,7 @@ class DataLabelSettings { /// ///Defaults to `null`. - final Offset offset; + final Offset? offset; } ///Datalabel renderer class for mutable fields and methods @@ -442,19 +431,20 @@ class DataLabelSettingsRenderer { DataLabelSettingsRenderer(this._dataLabelSettings) { _angle = _dataLabelSettings.angle; _offset = _dataLabelSettings.offset; + _color = _dataLabelSettings.color; } final DataLabelSettings _dataLabelSettings; - Color _color; + Color? _color; - TextStyle _textStyle; + TextStyle? _textStyle; - TextStyle _originalStyle; + TextStyle? _originalStyle; - int _angle; + late int _angle; - Offset _offset; + Offset? _offset; /// To render charts with data labels void _renderDataLabel( diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart index 2067ef69e..27c79912b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart @@ -8,36 +8,41 @@ void _calculateDataLabelPosition( SfCartesianChartState _chartState, DataLabelSettingsRenderer dataLabelSettingsRenderer, Animation dataLabelAnimation, - [Size templateSize, - Offset templateLocation]) { + [Size? templateSize, + Offset? templateLocation]) { final SfCartesianChart chart = _chartState._chart; final CartesianSeries series = seriesRenderer._series; if (dataLabelSettingsRenderer._angle.isNegative) { - final num angle = dataLabelSettingsRenderer._angle + 360; + final int angle = dataLabelSettingsRenderer._angle + 360; dataLabelSettingsRenderer._angle = angle; } final DataLabelSettings dataLabel = series.dataLabelSettings; - Size textSize, textSize2, textSize3, textSize4, textSize5; - num value1, value2; + Size? textSize, textSize2, textSize3, textSize4, textSize5; + double? value1, value2; final int boxPlotPadding = 8; final Rect rect = _calculatePlotOffset( _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer._axis.plotOffset, - seriesRenderer._yAxisRenderer._axis.plotOffset)); - value1 = - (point.open != null && point.close != null && point.close < point.open) - ? point.close - : point.open; - value2 = - (point.open != null && point.close != null && point.close > point.open) - ? point.close - : point.open; + Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, + seriesRenderer._yAxisRenderer!._axis.plotOffset)); + if (seriesRenderer._seriesType.contains('hilo') || + seriesRenderer._seriesType.contains('candle')) { + value1 = + ((point.open != null && point.close != null && point.close < point.open) + ? point.close + : point.open) + ?.toDouble(); + value2 = + ((point.open != null && point.close != null && point.close > point.open) + ? point.close + : point.open) + ?.toDouble(); + } final bool transposed = _chartState._requireInvertedAxis; - final bool inversed = seriesRenderer._yAxisRenderer._axis.isInversed; + final bool inversed = seriesRenderer._yAxisRenderer!._axis.isInversed; final Rect clipRect = _calculatePlotOffset( _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer._axis.plotOffset, - seriesRenderer._yAxisRenderer._axis.plotOffset)); + Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, + seriesRenderer._yAxisRenderer!._axis.plotOffset)); final bool isRangeSeries = seriesRenderer._seriesType.contains('range') || seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType.contains('candle'); @@ -45,14 +50,14 @@ void _calculateDataLabelPosition( if (isBoxSeries) { value1 = (point.upperQuartile != null && point.lowerQuartile != null && - point.upperQuartile < point.lowerQuartile) - ? point.upperQuartile - : point.lowerQuartile; + point.upperQuartile! < point.lowerQuartile!) + ? point.upperQuartile! + : point.lowerQuartile!; value2 = (point.upperQuartile != null && point.lowerQuartile != null && - point.upperQuartile > point.lowerQuartile) - ? point.upperQuartile - : point.lowerQuartile; + point.upperQuartile! > point.lowerQuartile!) + ? point.upperQuartile! + : point.lowerQuartile!; } // ignore: prefer_final_fields String label = point.dataLabelMapper ?? @@ -76,70 +81,71 @@ void _calculateDataLabelPosition( !inversed ? point.minimum : point.maximum, seriesRenderer); } DataLabelRenderArgs dataLabelArgs; - TextStyle dataLabelStyle = dataLabelSettingsRenderer._textStyle; + TextStyle? dataLabelStyle = dataLabelSettingsRenderer._textStyle; //ignore: prefer_conditional_assignment if (dataLabelSettingsRenderer._originalStyle == null) { dataLabelSettingsRenderer._originalStyle = dataLabel.textStyle; } dataLabelStyle = dataLabelSettingsRenderer._originalStyle; if (chart.onDataLabelRender != null && - !seriesRenderer._visibleDataPoints[index].labelRenderEvent) { - seriesRenderer._visibleDataPoints[index].labelRenderEvent = true; + !seriesRenderer._visibleDataPoints![index].labelRenderEvent) { + seriesRenderer._visibleDataPoints![index].labelRenderEvent = true; dataLabelArgs = DataLabelRenderArgs( seriesRenderer._series, seriesRenderer._dataPoints, index, - seriesRenderer._visibleDataPoints[index].overallDataPointIndex); + seriesRenderer._visibleDataPoints![index].overallDataPointIndex); dataLabelArgs.text = label; - dataLabelArgs.textStyle = dataLabelStyle; - dataLabelArgs.color = dataLabelSettingsRenderer._color; - chart.onDataLabelRender(dataLabelArgs); + dataLabelArgs.textStyle = dataLabelStyle!; + dataLabelArgs.color = seriesRenderer._series.dataLabelSettings.color; + chart.onDataLabelRender!(dataLabelArgs); label = point.label = dataLabelArgs.text; - index = dataLabelArgs.pointIndex; + index = dataLabelArgs.pointIndex!; point._dataLabelStyle = dataLabelArgs.textStyle; point._dataLabelColor = dataLabelArgs.color; + dataLabelSettingsRenderer._offset = dataLabelArgs.offset; } dataLabelSettingsRenderer._textStyle = dataLabelStyle; if (chart.onDataLabelRender != null) { dataLabelSettingsRenderer._color = point._dataLabelColor; dataLabelSettingsRenderer._textStyle = point._dataLabelStyle; - dataLabelStyle = dataLabelSettingsRenderer._textStyle; + dataLabelStyle = dataLabelSettingsRenderer._textStyle!; } if (point != null && point.isVisible && point.isGap != true && (point.y != 0 || dataLabel.showZeroValue)) { - final num markerPointX = dataLabel.builder == null + final double markerPointX = dataLabel.builder == null ? seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType == 'candle' || isBoxSeries - ? seriesRenderer._chartState._requireInvertedAxis - ? point.region.centerRight.dx - : point.region.topCenter.dx - : point.markerPoint.x - : templateLocation.dx; - final num markerPointY = dataLabel.builder == null + ? seriesRenderer._chartState!._requireInvertedAxis + ? point.region!.centerRight.dx + : point.region!.topCenter.dx + : point.markerPoint!.x + : templateLocation!.dx; + final double markerPointY = dataLabel.builder == null ? seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType == 'candle' || isBoxSeries - ? seriesRenderer._chartState._requireInvertedAxis - ? point.region.centerRight.dy - : point.region.topCenter.dy - : point.markerPoint.y - : templateLocation.dy; + ? seriesRenderer._chartState!._requireInvertedAxis + ? point.region!.centerRight.dy + : point.region!.topCenter.dy + : point.markerPoint!.y + : templateLocation!.dy; final _ChartLocation markerPoint2 = _calculatePoint( point.xValue, - seriesRenderer._yAxisRenderer._axis.isInversed ? value2 : value1, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._yAxisRenderer!._axis.isInversed ? value2 : value1, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, series, rect); final _ChartLocation markerPoint3 = _calculatePoint( point.xValue, - seriesRenderer._yAxisRenderer._axis.isInversed ? value1 : value2, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._yAxisRenderer!._axis.isInversed ? value1 : value2, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, series, rect); @@ -149,42 +155,42 @@ void _calculateDataLabelPosition( fontStyle: FontStyle.normal, fontWeight: FontWeight.normal, fontSize: 12) - : dataLabelStyle; + : dataLabelStyle!; point.label = label; if (label.isNotEmpty) { - _ChartLocation chartLocation, + _ChartLocation? chartLocation, chartLocation2, chartLocation3, chartLocation4, chartLocation5; textSize = - dataLabel.builder == null ? _measureText(label, font) : templateSize; + dataLabel.builder == null ? measureText(label, font) : templateSize!; chartLocation = _ChartLocation(markerPointX, markerPointY); if (isRangeSeries || isBoxSeries) { textSize2 = dataLabel.builder == null - ? _measureText(point.label2, font) - : templateSize; + ? measureText(point.label2!, font) + : templateSize!; chartLocation2 = _ChartLocation( dataLabel.builder == null ? seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType == 'candle' || isBoxSeries - ? seriesRenderer._chartState._requireInvertedAxis - ? point.region.centerLeft.dx - : point.region.bottomCenter.dx - : point.markerPoint2.x - : templateLocation.dx, + ? seriesRenderer._chartState!._requireInvertedAxis + ? point.region!.centerLeft.dx + : point.region!.bottomCenter.dx + : point.markerPoint2!.x + : templateLocation!.dx, dataLabel.builder == null ? seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType == 'candle' || isBoxSeries - ? seriesRenderer._chartState._requireInvertedAxis - ? point.region.centerLeft.dy - : point.region.bottomCenter.dy - : point.markerPoint2.y - : templateLocation.dy); + ? seriesRenderer._chartState!._requireInvertedAxis + ? point.region!.centerLeft.dy + : point.region!.bottomCenter.dy + : point.markerPoint2!.y + : templateLocation!.dy); if (isBoxSeries) { - if (!seriesRenderer._chartState._requireInvertedAxis) { + if (!seriesRenderer._chartState!._requireInvertedAxis) { chartLocation.y = chartLocation.y - boxPlotPadding; chartLocation2.y = chartLocation2.y + boxPlotPadding; } else { @@ -193,7 +199,7 @@ void _calculateDataLabelPosition( } } } - final List<_ChartLocation> alignedLabelLocations = + final List<_ChartLocation?> alignedLabelLocations = _getAlignedLabelLocations(_chartState, seriesRenderer, point, dataLabel, chartLocation, chartLocation2, textSize); chartLocation = alignedLabelLocations[0]; @@ -206,7 +212,7 @@ void _calculateDataLabelPosition( !seriesRenderer._seriesType.contains('hilo') && !seriesRenderer._seriesType.contains('candle') && !isBoxSeries) { - chartLocation.y = _calculatePathPosition( + chartLocation!.y = _calculatePathPosition( chartLocation.y, dataLabel.labelAlignment, textSize, @@ -225,7 +231,7 @@ void _calculateDataLabelPosition( ? series.markerSettings.height / 2 : 0)); } else { - final List<_ChartLocation> _locations = _getLabelLocations( + final List<_ChartLocation?> _locations = _getLabelLocations( index, _chartState, seriesRenderer, @@ -265,7 +271,7 @@ void _calculateDataLabelPosition( } else { point.label3 = point.dataLabelMapper ?? _getLabelText( - point.lowerQuartile > point.upperQuartile + point.lowerQuartile! > point.upperQuartile! ? !inversed ? point.upperQuartile : point.lowerQuartile @@ -275,7 +281,7 @@ void _calculateDataLabelPosition( seriesRenderer); point.label4 = point.dataLabelMapper ?? _getLabelText( - point.lowerQuartile > point.upperQuartile + point.lowerQuartile! > point.upperQuartile! ? !inversed ? point.lowerQuartile : point.upperQuartile @@ -287,87 +293,91 @@ void _calculateDataLabelPosition( _getLabelText(point.median, seriesRenderer); } textSize3 = dataLabel.builder == null - ? _measureText(point.label3, font) + ? measureText(point.label3!, font) : templateSize; if (seriesRenderer._seriesType.contains('hilo')) { if (point.open > point.close) { chartLocation3 = _ChartLocation( - point.centerClosePoint.x + textSize3.width, point.closePoint.y); + point.centerClosePoint!.x + textSize3!.width, + point.closePoint!.y); } else { chartLocation3 = _ChartLocation( - point.centerOpenPoint.x - textSize3.width, point.openPoint.y); + point.centerOpenPoint!.x - textSize3!.width, + point.openPoint!.y); } } else if (seriesRenderer._seriesType == 'candle' && - seriesRenderer._chartState._requireInvertedAxis) { + seriesRenderer._chartState!._requireInvertedAxis) { if (point.open > point.close) { chartLocation3 = - _ChartLocation(point.closePoint.x, markerPoint2.y + 1); + _ChartLocation(point.closePoint!.x, markerPoint2.y + 1); } else { chartLocation3 = - _ChartLocation(point.openPoint.x, markerPoint2.y + 1); + _ChartLocation(point.openPoint!.x, markerPoint2.y + 1); } } else if (isBoxSeries) { - if (seriesRenderer._chartState._requireInvertedAxis) { + if (seriesRenderer._chartState!._requireInvertedAxis) { chartLocation3 = _ChartLocation( - point.lowerQuartilePoint.x + boxPlotPadding, + point.lowerQuartilePoint!.x + boxPlotPadding, markerPoint2.y + 1); } else { chartLocation3 = _ChartLocation( - point.region.topCenter.dx, markerPoint2.y - boxPlotPadding); + point.region!.topCenter.dx, markerPoint2.y - boxPlotPadding); } } else { chartLocation3 = - _ChartLocation(point.region.topCenter.dx, markerPoint2.y); + _ChartLocation(point.region!.topCenter.dx, markerPoint2.y); } textSize4 = dataLabel.builder == null - ? _measureText(point.label4, font) + ? measureText(point.label4!, font) : templateSize; if (seriesRenderer._seriesType.contains('hilo')) { if (point.open > point.close) { chartLocation4 = _ChartLocation( - point.centerOpenPoint.x - textSize4.width, point.openPoint.y); + point.centerOpenPoint!.x - textSize4!.width, + point.openPoint!.y); } else { chartLocation4 = _ChartLocation( - point.centerClosePoint.x + textSize4.width, point.closePoint.y); + point.centerClosePoint!.x + textSize4!.width, + point.closePoint!.y); } } else if (seriesRenderer._seriesType == 'candle' && - seriesRenderer._chartState._requireInvertedAxis) { + seriesRenderer._chartState!._requireInvertedAxis) { if (point.open > point.close) { chartLocation4 = - _ChartLocation(point.openPoint.x, markerPoint3.y + 1); + _ChartLocation(point.openPoint!.x, markerPoint3.y + 1); } else { chartLocation4 = - _ChartLocation(point.closePoint.x, markerPoint3.y + 1); + _ChartLocation(point.closePoint!.x, markerPoint3.y + 1); } } else if (isBoxSeries) { - if (seriesRenderer._chartState._requireInvertedAxis) { + if (seriesRenderer._chartState!._requireInvertedAxis) { chartLocation4 = _ChartLocation( - point.upperQuartilePoint.x - boxPlotPadding, + point.upperQuartilePoint!.x - boxPlotPadding, markerPoint3.y + 1); } else { chartLocation4 = _ChartLocation( - point.region.bottomCenter.dx, markerPoint3.y + boxPlotPadding); + point.region!.bottomCenter.dx, markerPoint3.y + boxPlotPadding); } } else { chartLocation4 = - _ChartLocation(point.region.bottomCenter.dx, markerPoint3.y + 1); + _ChartLocation(point.region!.bottomCenter.dx, markerPoint3.y + 1); } if (isBoxSeries) { - textSize5 = _measureText(point.label5, font); - if (!seriesRenderer._chartState._requireInvertedAxis) { + textSize5 = measureText(point.label5!, font); + if (!seriesRenderer._chartState!._requireInvertedAxis) { chartLocation5 = _ChartLocation( - point.centerMedianPoint.x, point.centerMedianPoint.y); + point.centerMedianPoint!.x, point.centerMedianPoint!.y); } else { chartLocation5 = _ChartLocation( - point.centerMedianPoint.x, point.centerMedianPoint.y); + point.centerMedianPoint!.x, point.centerMedianPoint!.y); } } - final List<_ChartLocation> alignedLabelLocations2 = + final List<_ChartLocation?> alignedLabelLocations2 = _getAlignedLabelLocations(_chartState, seriesRenderer, point, - dataLabel, chartLocation3, chartLocation4, textSize3); + dataLabel, chartLocation3, chartLocation4, textSize3!); chartLocation3 = alignedLabelLocations2[0]; chartLocation4 = alignedLabelLocations2[1]; - final List<_ChartLocation> _locations = _getLabelLocations( + final List<_ChartLocation?> _locations = _getLabelLocations( index, _chartState, seriesRenderer, @@ -376,7 +386,7 @@ void _calculateDataLabelPosition( chartLocation3, chartLocation4, textSize3, - textSize4); + textSize4!); chartLocation3 = _locations[0]; chartLocation4 = _locations[1]; } @@ -384,7 +394,7 @@ void _calculateDataLabelPosition( point, dataLabel, _chartState, - chartLocation, + chartLocation!, chartLocation2, isRangeSeries, clipRect, @@ -403,16 +413,17 @@ void _calculateDataLabelPosition( } ///Calculating the label location based on alignment value -List<_ChartLocation> _getAlignedLabelLocations( +List<_ChartLocation?> _getAlignedLabelLocations( SfCartesianChartState _chartState, CartesianSeriesRenderer seriesRenderer, CartesianChartPoint point, DataLabelSettings dataLabel, _ChartLocation chartLocation, - _ChartLocation chartLocation2, + _ChartLocation? chartLocation2, Size textSize) { final SfCartesianChart chart = _chartState._chart; - final XyDataSeries series = seriesRenderer._series; + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; final bool transposed = _chartState._requireInvertedAxis; final bool isRangeSeries = seriesRenderer._seriesType.contains('range') || seriesRenderer._seriesType.contains('hilo') || @@ -444,7 +455,7 @@ List<_ChartLocation> _getAlignedLabelLocations( 0, transposed); if (isRangeSeries || isBoxSeries) { - chartLocation2.x = + chartLocation2!.x = (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) ? chartLocation2.x : _calculateAlignment( @@ -474,7 +485,7 @@ List<_ChartLocation> _getAlignedLabelLocations( 0, transposed); if (isRangeSeries || isBoxSeries) { - chartLocation2.y = + chartLocation2!.y = (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) ? chartLocation2.y : _calculateAlignment( @@ -490,28 +501,28 @@ List<_ChartLocation> _getAlignedLabelLocations( transposed); } } - return <_ChartLocation>[chartLocation, chartLocation2]; + return <_ChartLocation?>[chartLocation, chartLocation2]; } ///calculating the label loaction based on dataLabel position value ///(for range and rect series only) -List<_ChartLocation> _getLabelLocations( +List<_ChartLocation?> _getLabelLocations( int index, SfCartesianChartState _chartState, CartesianSeriesRenderer seriesRenderer, CartesianChartPoint point, DataLabelSettings dataLabel, - _ChartLocation chartLocation, - _ChartLocation chartLocation2, + _ChartLocation? chartLocation, + _ChartLocation? chartLocation2, Size textSize, - Size textSize2) { + Size? textSize2) { final bool transposed = _chartState._requireInvertedAxis; final EdgeInsets margin = dataLabel.margin; final bool isRangeSeries = seriesRenderer._seriesType.contains('range') || seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType.contains('candle'); final bool isBoxSeries = seriesRenderer._seriesType.contains('boxandwhisker'); - final bool inversed = seriesRenderer._yAxisRenderer._axis.isInversed; + final bool inversed = seriesRenderer._yAxisRenderer!._axis.isInversed; final num value = isRangeSeries ? point.high : isBoxSeries @@ -519,10 +530,10 @@ List<_ChartLocation> _getLabelLocations( : point.yValue; final bool minus = (value < 0 && !inversed) || (!(value < 0) && inversed); if (!_chartState._requireInvertedAxis) { - chartLocation.y = !isBoxSeries + chartLocation!.y = !isBoxSeries ? _calculateRectPosition( chartLocation.y, - point.region, + point.region!, minus, isRangeSeries ? ((dataLabel.labelAlignment == ChartDataLabelAlignment.outer || @@ -540,10 +551,10 @@ List<_ChartLocation> _getLabelLocations( margin) : chartLocation.y; } else { - chartLocation.x = !isBoxSeries + chartLocation!.x = !isBoxSeries ? _calculateRectPosition( chartLocation.x, - point.region, + point.region!, minus, seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType.contains('candle') || @@ -569,9 +580,9 @@ List<_ChartLocation> _getLabelLocations( } chartLocation2 = isRangeSeries ? _getSecondLabelLocation(index, _chartState, seriesRenderer, point, - dataLabel, chartLocation, chartLocation2, textSize) + dataLabel, chartLocation, chartLocation2!, textSize) : chartLocation2; - return <_ChartLocation>[chartLocation, chartLocation2]; + return <_ChartLocation?>[chartLocation, chartLocation2]; } ///Finding range series second label location @@ -584,14 +595,14 @@ _ChartLocation _getSecondLabelLocation( _ChartLocation chartLocation, _ChartLocation chartLocation2, Size textSize) { - final bool inversed = seriesRenderer._yAxisRenderer._axis.isInversed; + final bool inversed = seriesRenderer._yAxisRenderer!._axis.isInversed; final bool transposed = _chartState._requireInvertedAxis; final EdgeInsets margin = dataLabel.margin; bool minus; if (seriesRenderer._seriesType == 'boxandwhisker') { - minus = - (point.minimum < 0 && !inversed) || (!(point.minimum < 0) && inversed); + minus = (point.minimum! < 0 && !inversed) || + (!(point.minimum! < 0) && inversed); } else { minus = (point.low < 0 && !inversed) || (!(point.low < 0) && inversed); } @@ -599,7 +610,7 @@ _ChartLocation _getSecondLabelLocation( if (!_chartState._requireInvertedAxis) { chartLocation2.y = _calculateRectPosition( chartLocation2.y, - point.region, + point.region!, minus, dataLabel.labelAlignment == ChartDataLabelAlignment.top ? ChartDataLabelAlignment.auto @@ -615,7 +626,7 @@ _ChartLocation _getSecondLabelLocation( } else { chartLocation2.x = _calculateRectPosition( chartLocation2.x, - point.region, + point.region!, minus, dataLabel.labelAlignment == ChartDataLabelAlignment.top ? ChartDataLabelAlignment.auto @@ -638,22 +649,22 @@ void _calculateDataLabelRegion( DataLabelSettings dataLabel, SfCartesianChartState _chartState, _ChartLocation chartLocation, - _ChartLocation chartLocation2, + _ChartLocation? chartLocation2, bool isRangeSeries, Rect clipRect, Size textSize, - Size textSize2, - _ChartLocation chartLocation3, - _ChartLocation chartLocation4, - _ChartLocation chartLocation5, - Size textSize3, - Size textSize4, - Size textSize5, + Size? textSize2, + _ChartLocation? chartLocation3, + _ChartLocation? chartLocation4, + _ChartLocation? chartLocation5, + Size? textSize3, + Size? textSize4, + Size? textSize5, CartesianSeriesRenderer seriesRenderer, int index) { final DataLabelSettingsRenderer dataLabelSettingsRenderer = seriesRenderer._dataLabelSettingsRenderer; - Rect rect, rect2, rect3, rect4, rect5; + Rect? rect, rect2, rect3, rect4, rect5; final EdgeInsets margin = dataLabel.margin; final bool isBoxSeries = seriesRenderer._seriesType.contains('boxandwhisker'); rect = _calculateLabelRect(chartLocation, textSize, margin, @@ -667,7 +678,7 @@ void _calculateDataLabelRegion( ? rect : _validateRect(rect, clipRect); if (isRangeSeries || isBoxSeries) { - rect2 = _calculateLabelRect(chartLocation2, textSize2, margin, + rect2 = _calculateLabelRect(chartLocation2!, textSize2!, margin, dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor); rect2 = _validateRect(rect2, clipRect); } @@ -677,16 +688,16 @@ void _calculateDataLabelRegion( (chartLocation3 != null || chartLocation4 != null || chartLocation5 != null)) { - rect3 = _calculateLabelRect(chartLocation3, textSize3, margin, + rect3 = _calculateLabelRect(chartLocation3!, textSize3!, margin, dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor); rect3 = _validateRect(rect3, clipRect); - rect4 = _calculateLabelRect(chartLocation4, textSize4, margin, + rect4 = _calculateLabelRect(chartLocation4!, textSize4!, margin, dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor); rect4 = _validateRect(rect4, clipRect); if (isBoxSeries) { - rect5 = _calculateLabelRect(chartLocation5, textSize5, margin, + rect5 = _calculateLabelRect(chartLocation5!, textSize5!, margin, dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor); rect5 = _validateRect(rect5, clipRect); } @@ -699,8 +710,8 @@ void _calculateDataLabelRegion( point.labelLocation = _ChartLocation( fillRect.center.dx - textSize.width / 2, fillRect.center.dy - textSize.height / 2); - point.dataLabelRegion = Rect.fromLTWH(point.labelLocation.x, - point.labelLocation.y, textSize.width, textSize.height); + point.dataLabelRegion = Rect.fromLTWH(point.labelLocation!.x, + point.labelLocation!.y, textSize.width, textSize.height); if (margin == const EdgeInsets.all(0)) { point.labelFillRect = fillRect; } else { @@ -713,7 +724,7 @@ void _calculateDataLabelRegion( rect.top + rect.height / 2 - textSize.height / 2); } else if (isBoxSeries && _chartState._requireInvertedAxis && - point.upperQuartile > point.maximum) { + point.upperQuartile! > point.maximum!) { point.labelLocation = _ChartLocation( rect.left - rect.width - textSize.width, rect.top + rect.height / 2 - textSize.height / 2); @@ -725,7 +736,7 @@ void _calculateDataLabelRegion( rect.top + rect.height + textSize.height); } else if (isBoxSeries && !_chartState._requireInvertedAxis && - point.upperQuartile > point.maximum) { + point.upperQuartile! > point.maximum!) { point.labelLocation = _ChartLocation( rect.left + rect.width / 2 - textSize.width / 2, rect.top + rect.height + textSize.height); @@ -734,18 +745,18 @@ void _calculateDataLabelRegion( rect.left + rect.width / 2 - textSize.width / 2, rect.top + rect.height / 2 - textSize.height / 2); } - point.dataLabelRegion = Rect.fromLTWH(point.labelLocation.x, - point.labelLocation.y, textSize.width, textSize.height); + point.dataLabelRegion = Rect.fromLTWH(point.labelLocation!.x, + point.labelLocation!.y, textSize.width, textSize.height); point.labelFillRect = _rectToRrect(rect, dataLabel.borderRadius); } if (isRangeSeries || isBoxSeries) { final RRect fillRect2 = - _calculatePaddedFillRect(rect2, dataLabel.borderRadius, margin); + _calculatePaddedFillRect(rect2!, dataLabel.borderRadius, margin); point.labelLocation2 = _ChartLocation( - fillRect2.center.dx - textSize2.width / 2, + fillRect2.center.dx - textSize2!.width / 2, fillRect2.center.dy - textSize2.height / 2); - point.dataLabelRegion2 = Rect.fromLTWH(point.labelLocation2.x, - point.labelLocation2.y, textSize2.width, textSize2.height); + point.dataLabelRegion2 = Rect.fromLTWH(point.labelLocation2!.x, + point.labelLocation2!.y, textSize2.width, textSize2.height); if (margin == const EdgeInsets.all(0)) { point.labelFillRect2 = fillRect2; } else { @@ -753,8 +764,8 @@ void _calculateDataLabelRegion( point.labelLocation2 = _ChartLocation( rect2.left + rect2.width / 2 - textSize2.width / 2, rect2.top + rect2.height / 2 - textSize2.height / 2); - point.dataLabelRegion2 = Rect.fromLTWH(point.labelLocation2.x, - point.labelLocation2.y, textSize2.width, textSize2.height); + point.dataLabelRegion2 = Rect.fromLTWH(point.labelLocation2!.x, + point.labelLocation2!.y, textSize2.width, textSize2.height); point.labelFillRect2 = _rectToRrect(rect2, dataLabel.borderRadius); } } @@ -762,12 +773,12 @@ void _calculateDataLabelRegion( seriesRenderer._seriesType.contains('hilo') || isBoxSeries && (rect3 != null || rect4 != null || rect5 != null)) { final RRect fillRect3 = - _calculatePaddedFillRect(rect3, dataLabel.borderRadius, margin); + _calculatePaddedFillRect(rect3!, dataLabel.borderRadius, margin); point.labelLocation3 = _ChartLocation( - fillRect3.center.dx - textSize3.width / 2, + fillRect3.center.dx - textSize3!.width / 2, fillRect3.center.dy - textSize3.height / 2); - point.dataLabelRegion3 = Rect.fromLTWH(point.labelLocation3.x, - point.labelLocation3.y, textSize3.width, textSize3.height); + point.dataLabelRegion3 = Rect.fromLTWH(point.labelLocation3!.x, + point.labelLocation3!.y, textSize3.width, textSize3.height); if (margin == const EdgeInsets.all(0)) { point.labelFillRect3 = fillRect3; } else { @@ -775,17 +786,17 @@ void _calculateDataLabelRegion( point.labelLocation3 = _ChartLocation( rect3.left + rect3.width / 2 - textSize3.width / 2, rect3.top + rect3.height / 2 - textSize3.height / 2); - point.dataLabelRegion3 = Rect.fromLTWH(point.labelLocation3.x, - point.labelLocation3.y, textSize3.width, textSize3.height); + point.dataLabelRegion3 = Rect.fromLTWH(point.labelLocation3!.x, + point.labelLocation3!.y, textSize3.width, textSize3.height); point.labelFillRect3 = _rectToRrect(rect3, dataLabel.borderRadius); } final RRect fillRect4 = - _calculatePaddedFillRect(rect4, dataLabel.borderRadius, margin); + _calculatePaddedFillRect(rect4!, dataLabel.borderRadius, margin); point.labelLocation4 = _ChartLocation( - fillRect4.center.dx - textSize4.width / 2, + fillRect4.center.dx - textSize4!.width / 2, fillRect4.center.dy - textSize4.height / 2); - point.dataLabelRegion4 = Rect.fromLTWH(point.labelLocation4.x, - point.labelLocation4.y, textSize4.width, textSize4.height); + point.dataLabelRegion4 = Rect.fromLTWH(point.labelLocation4!.x, + point.labelLocation4!.y, textSize4.width, textSize4.height); if (margin == const EdgeInsets.all(0)) { point.labelFillRect4 = fillRect4; } else { @@ -793,18 +804,18 @@ void _calculateDataLabelRegion( point.labelLocation4 = _ChartLocation( rect4.left + rect4.width / 2 - textSize4.width / 2, rect4.top + rect4.height / 2 - textSize4.height / 2); - point.dataLabelRegion4 = Rect.fromLTWH(point.labelLocation4.x, - point.labelLocation4.y, textSize4.width, textSize4.height); + point.dataLabelRegion4 = Rect.fromLTWH(point.labelLocation4!.x, + point.labelLocation4!.y, textSize4.width, textSize4.height); point.labelFillRect4 = _rectToRrect(rect4, dataLabel.borderRadius); } if (isBoxSeries) { final RRect fillRect5 = - _calculatePaddedFillRect(rect5, dataLabel.borderRadius, margin); + _calculatePaddedFillRect(rect5!, dataLabel.borderRadius, margin); point.labelLocation5 = _ChartLocation( - fillRect5.center.dx - textSize5.width / 2, + fillRect5.center.dx - textSize5!.width / 2, fillRect5.center.dy - textSize5.height / 2); - point.dataLabelRegion5 = Rect.fromLTWH(point.labelLocation5.x, - point.labelLocation5.y, textSize5.width, textSize5.height); + point.dataLabelRegion5 = Rect.fromLTWH(point.labelLocation5!.x, + point.labelLocation5!.y, textSize5.width, textSize5.height); if (margin == const EdgeInsets.all(0)) { point.labelFillRect5 = fillRect5; } else { @@ -812,8 +823,8 @@ void _calculateDataLabelRegion( point.labelLocation5 = _ChartLocation( rect5.left + rect5.width / 2 - textSize5.width / 2, rect5.top + rect5.height / 2 - textSize5.height / 2); - point.dataLabelRegion5 = Rect.fromLTWH(point.labelLocation5.x, - point.labelLocation5.y, textSize5.width, textSize5.height); + point.dataLabelRegion5 = Rect.fromLTWH(point.labelLocation5!.x, + point.labelLocation5!.y, textSize5.width, textSize5.height); point.labelFillRect5 = _rectToRrect(rect5, dataLabel.borderRadius); } } @@ -827,7 +838,7 @@ void _calculateDataLabelRegion( rect.top + rect.height / 2 - textSize.height / 2); } else if (isBoxSeries && _chartState._requireInvertedAxis && - point.upperQuartile > point.maximum) { + point.upperQuartile! > point.maximum!) { point.labelLocation = _ChartLocation( rect.left - rect.width - textSize.width - 2, rect.top + rect.height / 2 - textSize.height / 2); @@ -839,7 +850,7 @@ void _calculateDataLabelRegion( rect.top + rect.height + textSize.height / 2); } else if (isBoxSeries && !_chartState._requireInvertedAxis && - point.upperQuartile > point.maximum) { + point.upperQuartile! > point.maximum!) { point.labelLocation = _ChartLocation( rect.left + rect.width / 2 - textSize.width / 2, rect.top + rect.height + textSize.height / 2); @@ -848,61 +859,61 @@ void _calculateDataLabelRegion( rect.left + rect.width / 2 - textSize.width / 2, rect.top + rect.height / 2 - textSize.height / 2); } - point.dataLabelRegion = Rect.fromLTWH(point.labelLocation.x, - point.labelLocation.y, textSize.width, textSize.height); + point.dataLabelRegion = Rect.fromLTWH(point.labelLocation!.x, + point.labelLocation!.y, textSize.width, textSize.height); if (isRangeSeries || isBoxSeries) { if (seriesRenderer._seriesType == 'candle' && _chartState._requireInvertedAxis && point.close > point.high) { point.labelLocation2 = _ChartLocation( - rect2.left + rect2.width + textSize2.width + 2, + rect2!.left + rect2.width + textSize2!.width + 2, rect2.top + rect2.height / 2 - textSize2.height / 2); } else if (isBoxSeries && _chartState._requireInvertedAxis && - point.upperQuartile > point.maximum) { + point.upperQuartile! > point.maximum!) { point.labelLocation2 = _ChartLocation( - rect2.left + rect2.width + textSize2.width + 2, + rect2!.left + rect2.width + textSize2!.width + 2, rect2.top + rect2.height / 2 - textSize2.height / 2); } else if (seriesRenderer._seriesType == 'candle' && !_chartState._requireInvertedAxis && point.close > point.high) { point.labelLocation2 = _ChartLocation( - rect2.left + rect2.width / 2 - textSize2.width / 2, + rect2!.left + rect2.width / 2 - textSize2!.width / 2, rect2.top - rect2.height - textSize2.height); } else if (isBoxSeries && !_chartState._requireInvertedAxis && - point.upperQuartile > point.maximum) { + point.upperQuartile! > point.maximum!) { point.labelLocation2 = _ChartLocation( - rect2.left + rect2.width / 2 - textSize2.width / 2, + rect2!.left + rect2.width / 2 - textSize2!.width / 2, rect2.top - rect2.height - textSize2.height); } else { point.labelLocation2 = _ChartLocation( - rect2.left + rect2.width / 2 - textSize2.width / 2, + rect2!.left + rect2.width / 2 - textSize2!.width / 2, rect2.top + rect2.height / 2 - textSize2.height / 2); } - point.dataLabelRegion2 = Rect.fromLTWH(point.labelLocation2.x, - point.labelLocation2.y, textSize2.width, textSize2.height); + point.dataLabelRegion2 = Rect.fromLTWH(point.labelLocation2!.x, + point.labelLocation2!.y, textSize2.width, textSize2.height); } if ((seriesRenderer._seriesType.contains('candle') || seriesRenderer._seriesType.contains('hilo') || isBoxSeries) && (rect3 != null || rect4 != null)) { point.labelLocation3 = _ChartLocation( - rect3.left + rect3.width / 2 - textSize3.width / 2, + rect3!.left + rect3.width / 2 - textSize3!.width / 2, rect3.top + rect3.height / 2 - textSize3.height / 2); - point.dataLabelRegion3 = Rect.fromLTWH(point.labelLocation3.x, - point.labelLocation3.y, textSize3.width, textSize3.height); + point.dataLabelRegion3 = Rect.fromLTWH(point.labelLocation3!.x, + point.labelLocation3!.y, textSize3.width, textSize3.height); point.labelLocation4 = _ChartLocation( - rect4.left + rect4.width / 2 - textSize4.width / 2, + rect4!.left + rect4.width / 2 - textSize4!.width / 2, rect4.top + rect4.height / 2 - textSize4.height / 2); - point.dataLabelRegion4 = Rect.fromLTWH(point.labelLocation4.x, - point.labelLocation4.y, textSize4.width, textSize4.height); + point.dataLabelRegion4 = Rect.fromLTWH(point.labelLocation4!.x, + point.labelLocation4!.y, textSize4.width, textSize4.height); if (rect5 != null) { point.labelLocation5 = _ChartLocation( - rect5.left + rect5.width / 2 - textSize5.width / 2, + rect5.left + rect5.width / 2 - textSize5!.width / 2, rect5.top + rect5.height / 2 - textSize5.height / 2); - point.dataLabelRegion5 = Rect.fromLTWH(point.labelLocation5.x, - point.labelLocation5.y, textSize5.width, textSize5.height); + point.dataLabelRegion5 = Rect.fromLTWH(point.labelLocation5!.x, + point.labelLocation5!.y, textSize5.width, textSize5.height); } } } @@ -921,7 +932,8 @@ double _calculatePathPosition( SfCartesianChartState _chartState, CartesianChartPoint currentPoint, Size markerSize) { - final XyDataSeries series = seriesRenderer._series; + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; const double padding = 5; final bool needFill = series.dataLabelSettings.color != null || series.dataLabelSettings.color != Colors.transparent || @@ -929,7 +941,7 @@ double _calculatePathPosition( final num fillSpace = needFill ? padding : 0; if (seriesRenderer._seriesType.contains('area') && !seriesRenderer._seriesType.contains('rangearea') && - seriesRenderer._yAxisRenderer._axis.isInversed) { + seriesRenderer._yAxisRenderer!._axis.isInversed) { position = position == ChartDataLabelAlignment.top ? ChartDataLabelAlignment.bottom : (position == ChartDataLabelAlignment.bottom @@ -970,7 +982,7 @@ double _calculatePathPosition( point, _chartState, currentPoint, - seriesRenderer._yAxisRenderer._axis.isInversed); + seriesRenderer._yAxisRenderer!._axis.isInversed); break; case ChartDataLabelAlignment.middle: break; @@ -1010,15 +1022,16 @@ double _calculatePathActualPosition( SfCartesianChartState _chartState, CartesianChartPoint currentPoint, bool inversed) { - final XyDataSeries series = seriesRenderer._series; - double yLocation; + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; + late double yLocation; bool isBottom, isOverLap = true; Rect labelRect; int positionIndex; final ChartDataLabelAlignment position = _getActualPathDataLabelAlignment(seriesRenderer, index, inversed); isBottom = position == ChartDataLabelAlignment.bottom; - final List dataLabelPosition = List(5); + final List dataLabelPosition = List.filled(5, null); dataLabelPosition[0] = 'DataLabelPosition.Outer'; dataLabelPosition[1] = 'DataLabelPosition.Top'; dataLabelPosition[2] = 'DataLabelPosition.Bottom'; @@ -1060,35 +1073,36 @@ ChartDataLabelAlignment _getActualPathDataLabelAlignment( CartesianSeriesRenderer seriesRenderer, int index, bool inversed) { final List> points = seriesRenderer._dataPoints; final num yValue = points[index].yValue; - final CartesianChartPoint _nextPoint = + final CartesianChartPoint? _nextPoint = points.length - 1 > index ? points[index + 1] : null; - final CartesianChartPoint previousPoint = + final CartesianChartPoint? previousPoint = index > 0 ? points[index - 1] : null; ChartDataLabelAlignment position; if (seriesRenderer._seriesType == 'bubble' || index == points.length - 1) { position = ChartDataLabelAlignment.top; } else { if (index == 0) { - position = (!_nextPoint.isVisible || + position = (!_nextPoint!.isVisible || yValue > _nextPoint.yValue || (yValue < _nextPoint.yValue && inversed)) ? ChartDataLabelAlignment.top : ChartDataLabelAlignment.bottom; } else if (index == points.length - 1) { - position = (!previousPoint.isVisible || + position = (!previousPoint!.isVisible || yValue > previousPoint.yValue || (yValue < previousPoint.yValue && inversed)) ? ChartDataLabelAlignment.top : ChartDataLabelAlignment.bottom; } else { - if (!_nextPoint.isVisible && !previousPoint.isVisible) { + if (!_nextPoint!.isVisible && !previousPoint!.isVisible) { position = ChartDataLabelAlignment.top; } else if (!_nextPoint.isVisible) { - position = (_nextPoint.yValue > yValue || previousPoint.yValue > yValue) - ? ChartDataLabelAlignment.bottom - : ChartDataLabelAlignment.top; + position = + (_nextPoint.yValue > yValue || previousPoint!.yValue > yValue) + ? ChartDataLabelAlignment.bottom + : ChartDataLabelAlignment.top; } else { - final num slope = (_nextPoint.yValue - previousPoint.yValue) / 2; + final num slope = (_nextPoint.yValue - previousPoint!.yValue) / 2; final num intersectY = (slope * index) + (_nextPoint.yValue - (slope * (index + 1))); position = !inversed @@ -1106,7 +1120,7 @@ ChartDataLabelAlignment _getActualPathDataLabelAlignment( /// To get the data label position ChartDataLabelAlignment _getPosition(int position) { - ChartDataLabelAlignment dataLabelPosition; + late ChartDataLabelAlignment dataLabelPosition; switch (position) { case 0: dataLabelPosition = ChartDataLabelAlignment.outer; @@ -1153,15 +1167,15 @@ void _drawDataLabel( double x = 0; double y = 0; if (dataLabelSettingsRenderer._offset != null) { - x = dataLabelSettingsRenderer._offset.dx; - y = dataLabelSettingsRenderer._offset.dy; + x = dataLabelSettingsRenderer._offset!.dx; + y = dataLabelSettingsRenderer._offset!.dy; } final double opacity = seriesRenderer._needAnimateSeriesElements && dataLabelAnimation != null ? dataLabelAnimation.value : 1; - TextStyle dataLabelStyle; - final String label = point.label; + TextStyle? dataLabelStyle; + final String? label = point.label; dataLabelStyle = dataLabelSettingsRenderer._textStyle; if (label != null && label.isNotEmpty && @@ -1180,10 +1194,10 @@ void _drawDataLabel( _getDataLabelSaturationColor( point, seriesRenderer, _chartState, dataLabelSettingsRenderer); final Rect labelRect = (point.labelFillRect != null) - ? Rect.fromLTWH(point.labelFillRect.left, point.labelFillRect.top, - point.labelFillRect.width, point.labelFillRect.height) - : Rect.fromLTWH(point.labelLocation.x, point.labelLocation.y, - point.dataLabelRegion.width, point.dataLabelRegion.height); + ? Rect.fromLTWH(point.labelFillRect!.left, point.labelFillRect!.top, + point.labelFillRect!.width, point.labelFillRect!.height) + : Rect.fromLTWH(point.labelLocation!.x, point.labelLocation!.y, + point.dataLabelRegion!.width, point.dataLabelRegion!.height); final bool isDatalabelCollide = (_chartState._requireInvertedAxis || (dataLabelSettingsRenderer._angle / 90) % 2 != 1) && _findingCollision(labelRect, _chartState._renderDatalabelRegions); @@ -1227,15 +1241,15 @@ void _triggerDataLabelEvent(SfCartesianChart chart, seriesIndex++) { final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderer[seriesIndex]; - final List> dataPoints = + final List>? dataPoints = seriesRenderer._visibleDataPoints; - for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { + for (int pointIndex = 0; pointIndex < dataPoints!.length; pointIndex++) { if (seriesRenderer._series.dataLabelSettings.isVisible && dataPoints[pointIndex].dataLabelRegion != null && - dataPoints[pointIndex].dataLabelRegion.contains(position)) { + dataPoints[pointIndex].dataLabelRegion!.contains(position)) { final CartesianChartPoint point = dataPoints[pointIndex]; final Offset position = - Offset(point.labelLocation.x, point.labelLocation.y); + Offset(point.labelLocation!.x, point.labelLocation!.y); _dataLabelTapEvent(chart, seriesRenderer._series.dataLabelSettings, pointIndex, point, position, seriesIndex); break; @@ -1257,13 +1271,13 @@ void _drawDataLabelRectAndText( double x, double y, SfCartesianChartState _chartState, - [SfCartesianChart chart]) { + [SfCartesianChart? chart]) { final DataLabelSettingsRenderer dataLabelSettingsRenderer = seriesRenderer._dataLabelSettingsRenderer; - final String label2 = point.dataLabelMapper ?? point.label2; - final String label3 = point.dataLabelMapper ?? point.label3; - final String label4 = point.dataLabelMapper ?? point.label4; - final String label5 = point.dataLabelMapper ?? point.label5; + final String? label2 = point.dataLabelMapper ?? point.label2; + final String? label3 = point.dataLabelMapper ?? point.label3; + final String? label4 = point.dataLabelMapper ?? point.label4; + final String? label5 = point.dataLabelMapper ?? point.label5; final bool isRangeSeries = seriesRenderer._seriesType.contains('range') || seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType.contains('candle'); @@ -1271,43 +1285,43 @@ void _drawDataLabelRectAndText( double padding = 0.0; if (dataLabelSettingsRenderer._angle != null && dataLabelSettingsRenderer._angle > 0) { - final Rect rect = _rotatedTextSize( - Size(point.dataLabelRegion.width, point.dataLabelRegion.height), + final Rect rect = rotatedTextSize( + Size(point.dataLabelRegion!.width, point.dataLabelRegion!.height), dataLabelSettingsRenderer._angle); if (_chartState._chartAxis._axisClipRect.top > - point.dataLabelRegion.center.dy + rect.top) { - padding = (point.dataLabelRegion.center.dy + rect.top) - + point.dataLabelRegion!.center.dy + rect.top) { + padding = (point.dataLabelRegion!.center.dy + rect.top) - _chartState._chartAxis._axisClipRect.top; } else if (_chartState._chartAxis._axisClipRect.bottom < - point.dataLabelRegion.center.dy + rect.bottom) { - padding = (point.dataLabelRegion.center.dy + rect.bottom) - + point.dataLabelRegion!.center.dy + rect.bottom) { + padding = (point.dataLabelRegion!.center.dy + rect.bottom) - _chartState._chartAxis._axisClipRect.bottom; } } if (dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor || (dataLabel.borderColor != null && dataLabel.borderWidth > 0)) { - final RRect fillRect = point.labelFillRect; + final RRect fillRect = point.labelFillRect!; final Path path = Path(); path.addRRect(fillRect); - final RRect fillRect2 = point.labelFillRect2; + final RRect? fillRect2 = point.labelFillRect2; final Path path2 = Path(); if (isRangeSeries || isBoxSeries) { - path2.addRRect(fillRect2); + path2.addRRect(fillRect2!); } - final RRect fillRect3 = point.labelFillRect3; + final RRect? fillRect3 = point.labelFillRect3; final Path path3 = Path(); - final RRect fillRect4 = point.labelFillRect4; + final RRect? fillRect4 = point.labelFillRect4; final Path path4 = Path(); - final RRect fillRect5 = point.labelFillRect5; + final RRect? fillRect5 = point.labelFillRect5; final Path path5 = Path(); if (seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType.contains('candle') || isBoxSeries) { - path3.addRRect(fillRect3); - path4.addRRect(fillRect4); + path3.addRRect(fillRect3!); + path4.addRRect(fillRect4!); if (isBoxSeries) { - path5.addRRect(fillRect5); + path5.addRRect(fillRect5!); } } if (dataLabel.borderColor != null && dataLabel.borderWidth > 0) { @@ -1322,14 +1336,14 @@ void _drawDataLabelRectAndText( ? strokePaint.color = Colors.transparent : strokePaint.color = strokePaint.color; canvas.save(); - canvas.translate(point.dataLabelRegion.center.dx + x, - point.dataLabelRegion.center.dy - padding); + canvas.translate(point.dataLabelRegion!.center.dx + x, + point.dataLabelRegion!.center.dy - padding); if (dataLabelSettingsRenderer._angle != null && dataLabelSettingsRenderer._angle > 0) { canvas.rotate((dataLabelSettingsRenderer._angle * math.pi) / 180); } - canvas.translate(-point.dataLabelRegion.center.dx, - -point.dataLabelRegion.center.dy - y); + canvas.translate(-point.dataLabelRegion!.center.dx, + -point.dataLabelRegion!.center.dy - y); canvas.drawPath(path, strokePaint); canvas.restore(); if (isRangeSeries || isBoxSeries) { @@ -1342,27 +1356,27 @@ void _drawDataLabelRectAndText( } } if (dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor) { - Color seriesColor = seriesRenderer._seriesColor; + Color? seriesColor = seriesRenderer._seriesColor!; if (seriesRenderer._seriesType == 'waterfall') { seriesColor = _getWaterfallSeriesColor( - seriesRenderer._series, point, seriesColor); + seriesRenderer._series as WaterfallSeries, point, seriesColor); } final Paint paint = Paint() ..color = (dataLabelSettingsRenderer._color ?? - (point.pointColorMapper ?? seriesColor)) + (point.pointColorMapper ?? seriesColor!)) .withOpacity((opacity - (1 - dataLabel.opacity)) < 0 ? 0 : opacity - (1 - dataLabel.opacity)) ..style = PaintingStyle.fill; canvas.save(); - canvas.translate(point.dataLabelRegion.center.dx + x, - point.dataLabelRegion.center.dy - padding); + canvas.translate(point.dataLabelRegion!.center.dx + x, + point.dataLabelRegion!.center.dy - padding); if (dataLabelSettingsRenderer._angle != null && dataLabelSettingsRenderer._angle > 0) { canvas.rotate((dataLabelSettingsRenderer._angle * math.pi) / 180); } - canvas.translate(-point.dataLabelRegion.center.dx, - -point.dataLabelRegion.center.dy - y); + canvas.translate(-point.dataLabelRegion!.center.dx, + -point.dataLabelRegion!.center.dy - y); canvas.drawPath(path, paint); canvas.restore(); if (isRangeSeries || isBoxSeries) { @@ -1381,54 +1395,56 @@ void _drawDataLabelRectAndText( canvas, label, dataLabelSettingsRenderer._angle != 0 - ? point.dataLabelRegion.center.dx + x - : point.labelLocation.x + x, + ? point.dataLabelRegion!.center.dx + x + : point.labelLocation!.x + x, dataLabelSettingsRenderer._angle != 0 - ? point.dataLabelRegion.center.dy - y - padding - : point.labelLocation.y - y, + ? point.dataLabelRegion!.center.dy - y - padding + : point.labelLocation!.y - y, dataLabelSettingsRenderer._angle, _textStyle); if (isRangeSeries || isBoxSeries) { if (_withInRange(isBoxSeries ? point.minimum : point.low, - seriesRenderer._yAxisRenderer._visibleRange)) { + seriesRenderer._yAxisRenderer!._visibleRange!)) { seriesRenderer.drawDataLabel( index, canvas, - label2, - point.labelLocation2.x + x, - point.labelLocation2.y - y, + label2!, + point.labelLocation2!.x + x, + point.labelLocation2!.y - y, dataLabelSettingsRenderer._angle, _textStyle); } if (seriesRenderer._seriesType == 'hiloopenclose' && (label3 != null && label4 != null && - (point.labelLocation3.y - point.labelLocation4.y).round() >= + (point.labelLocation3!.y - point.labelLocation4!.y).round() >= 8 || - (point.labelLocation4.x - point.labelLocation3.x).round() >= 15)) { + (point.labelLocation4!.x - point.labelLocation3!.x).round() >= + 15)) { seriesRenderer.drawDataLabel( index, canvas, - label3, - point.labelLocation3.x + x, - point.labelLocation3.y + y, + label3!, + point.labelLocation3!.x + x, + point.labelLocation3!.y + y, dataLabelSettingsRenderer._angle, _textStyle); seriesRenderer.drawDataLabel( index, canvas, - label4, - point.labelLocation4.x + x, - point.labelLocation3.y + y, + label4!, + point.labelLocation4!.x + x, + point.labelLocation3!.y + y, dataLabelSettingsRenderer._angle, _textStyle); } else if (label3 != null && label4 != null && - ((point.labelLocation3.y - point.labelLocation4.y).round() >= 8 || - (point.labelLocation4.x - point.labelLocation3.x).round() >= 15)) { + ((point.labelLocation3!.y - point.labelLocation4!.y).round() >= 8 || + (point.labelLocation4!.x - point.labelLocation3!.x).round() >= + 15)) { final Color fontColor = - _getOpenCloseDataLabelColor(point, seriesRenderer, chart); + _getOpenCloseDataLabelColor(point, seriesRenderer, chart!); final TextStyle _textStyleOpenClose = TextStyle( color: fontColor.withOpacity(opacity), fontSize: _textStyle.fontSize, @@ -1452,25 +1468,25 @@ void _drawDataLabelRectAndText( decorationThickness: _textStyle.decorationThickness, debugLabel: _textStyle.debugLabel, fontFamilyFallback: _textStyle.fontFamilyFallback); - if ((point.labelLocation2.y - point.labelLocation3.y).abs() >= 8 || - (point.labelLocation2.x - point.labelLocation3.x).abs() >= 8) { + if ((point.labelLocation2!.y - point.labelLocation3!.y).abs() >= 8 || + (point.labelLocation2!.x - point.labelLocation3!.x).abs() >= 8) { seriesRenderer.drawDataLabel( index, canvas, label3, - point.labelLocation3.x + x, - point.labelLocation3.y + y, + point.labelLocation3!.x + x, + point.labelLocation3!.y + y, dataLabelSettingsRenderer._angle, _textStyleOpenClose); } - if ((point.labelLocation.y - point.labelLocation4.y).abs() >= 8 || - (point.labelLocation.x - point.labelLocation4.x).abs() >= 8) { + if ((point.labelLocation!.y - point.labelLocation4!.y).abs() >= 8 || + (point.labelLocation!.x - point.labelLocation4!.x).abs() >= 8) { seriesRenderer.drawDataLabel( index, canvas, label4, - point.labelLocation4.x + x, - point.labelLocation4.y + y, + point.labelLocation4!.x + x, + point.labelLocation4!.y + y, dataLabelSettingsRenderer._angle, _textStyleOpenClose); } @@ -1479,24 +1495,24 @@ void _drawDataLabelRectAndText( index, canvas, label5, - point.labelLocation5.x + x, - point.labelLocation5.y + y, + point.labelLocation5!.x + x, + point.labelLocation5!.y + y, dataLabelSettingsRenderer._angle, _textStyleOpenClose); } if (isBoxSeries) { - if (point.outliers.isNotEmpty) { + if (point.outliers!.isNotEmpty) { final List<_ChartLocation> outliersLocation = <_ChartLocation>[]; final List outliersTextSize = []; final List outliersRect = []; final int outlierPadding = 12; for (int outlierIndex = 0; - outlierIndex < point.outliers.length; + outlierIndex < point.outliers!.length; outlierIndex++) { point.outliersLabel.add(point.dataLabelMapper ?? - _getLabelText(point.outliers[outlierIndex], seriesRenderer)); - outliersTextSize.add(_measureText( + _getLabelText(point.outliers![outlierIndex], seriesRenderer)); + outliersTextSize.add(measureText( point.outliersLabel[outlierIndex], dataLabelSettingsRenderer._textStyle == null ? const TextStyle( @@ -1504,7 +1520,7 @@ void _drawDataLabelRectAndText( fontStyle: FontStyle.normal, fontWeight: FontWeight.normal, fontSize: 12) - : dataLabelSettingsRenderer._originalStyle)); + : dataLabelSettingsRenderer._originalStyle!)); outliersLocation.add(_ChartLocation( point.outliersPoint[outlierIndex].x, point.outliersPoint[outlierIndex].y + outlierPadding)); @@ -1519,8 +1535,8 @@ void _drawDataLabelRectAndText( outliersRect[outlierIndex], _calculatePlotOffset( _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer._axis.plotOffset, - seriesRenderer._yAxisRenderer._axis.plotOffset))); + Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, + seriesRenderer._yAxisRenderer!._axis.plotOffset))); } if (dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor || @@ -1556,7 +1572,7 @@ void _drawDataLabelRectAndText( final Paint paint = Paint() ..color = (dataLabelSettingsRenderer._color ?? (point.pointColorMapper ?? - seriesRenderer._seriesColor)) + seriesRenderer._seriesColor!)) .withOpacity((opacity - (1 - dataLabel.opacity)) < 0 ? 0 : opacity - (1 - dataLabel.opacity)) @@ -1623,7 +1639,7 @@ String _getLabelText( labelValue = labelValue.round(); } } - final dynamic yAxis = seriesRenderer._yAxisRenderer._axis; + final dynamic yAxis = seriesRenderer._yAxisRenderer!._axis; if (yAxis is NumericAxis || yAxis is LogarithmicAxis) { final dynamic value = yAxis?.numberFormat != null ? yAxis.numberFormat.format(labelValue) @@ -1651,7 +1667,8 @@ double _calculateRectPosition( SfCartesianChartState _chartState, bool inverted, EdgeInsets margin) { - final XyDataSeries series = seriesRenderer._series; + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; double padding; padding = seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType.contains('candle') || @@ -1737,11 +1754,12 @@ double _calculateRectActualPosition( double borderWidth, SfCartesianChartState _chartState, EdgeInsets margin) { - double location; + late double location; Rect labelRect; bool isOverLap = true; int position = 0; - final XyDataSeries series = seriesRenderer._series; + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; final int finalPosition = seriesRenderer._seriesType.contains('range') ? 2 : 4; while (isOverLap && position < finalPosition) { @@ -1807,7 +1825,8 @@ double _calculateTopAndOuterPosition( _ChartLocation point, bool inverted, double borderWidth) { - final XyDataSeries series = seriesRenderer._series; + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; final num markerHeight = series.markerSettings.isVisible ? series.markerSettings.height / 2 : 0; if (((isMinus && !seriesRenderer._seriesType.contains('range')) && @@ -1826,7 +1845,7 @@ double _calculateTopAndOuterPosition( } /// Add padding for fill rect (if datalabel fill color is given) -RRect _calculatePaddedFillRect(Rect rect, num radius, EdgeInsets margin) { +RRect _calculatePaddedFillRect(Rect rect, double radius, EdgeInsets margin) { rect = Rect.fromLTRB(rect.left - margin.left, rect.top - margin.top, rect.right + margin.right, rect.bottom + margin.bottom); @@ -1834,7 +1853,7 @@ RRect _calculatePaddedFillRect(Rect rect, num radius, EdgeInsets margin) { } /// Converting rect into rounded rect -RRect _rectToRrect(Rect rect, num radius) => RRect.fromRectAndCorners(rect, +RRect _rectToRrect(Rect rect, double radius) => RRect.fromRectAndCorners(rect, topLeft: Radius.elliptical(radius, radius), topRight: Radius.elliptical(radius, radius), bottomLeft: Radius.elliptical(radius, radius), @@ -1843,7 +1862,7 @@ RRect _rectToRrect(Rect rect, num radius) => RRect.fromRectAndCorners(rect, /// Checking the condition whether data Label has been exist in the clip rect Rect _validateRect(Rect rect, Rect clipRect) { /// please don't add padding here - num left, top; + double left, top; left = rect.left < clipRect.left ? clipRect.left : rect.left; top = double.parse(rect.top.toStringAsFixed(2)) < clipRect.top ? clipRect.top @@ -1867,32 +1886,32 @@ bool _isLabelWithinRange(CartesianSeriesRenderer seriesRenderer, final bool isBoxSeries = seriesRenderer._seriesType.contains('boxandwhisker'); if (!(seriesRenderer._yAxisRenderer is LogarithmicAxisRenderer)) { withInRange = _withInRange( - point.xValue, seriesRenderer._xAxisRenderer._visibleRange) && + point.xValue, seriesRenderer._xAxisRenderer!._visibleRange!) && (seriesRenderer._seriesType.contains('range') || seriesRenderer._seriesType == 'hilo' ? (_withInRange(isBoxSeries ? point.minimum : point.low, - seriesRenderer._yAxisRenderer._visibleRange) || + seriesRenderer._yAxisRenderer!._visibleRange!) || _withInRange(isBoxSeries ? point.maximum : point.high, - seriesRenderer._yAxisRenderer._visibleRange)) + seriesRenderer._yAxisRenderer!._visibleRange!)) : seriesRenderer._seriesType == 'hiloopenclose' || seriesRenderer._seriesType.contains('candle') || isBoxSeries ? (_withInRange(isBoxSeries ? point.minimum : point.low, - seriesRenderer._yAxisRenderer._visibleRange) && + seriesRenderer._yAxisRenderer!._visibleRange!) && _withInRange(isBoxSeries ? point.maximum : point.high, - seriesRenderer._yAxisRenderer._visibleRange) && + seriesRenderer._yAxisRenderer!._visibleRange!) && _withInRange(isBoxSeries ? point.lowerQuartile : point.open, - seriesRenderer._yAxisRenderer._visibleRange) && + seriesRenderer._yAxisRenderer!._visibleRange!) && _withInRange( isBoxSeries ? point.upperQuartile : point.close, - seriesRenderer._yAxisRenderer._visibleRange)) + seriesRenderer._yAxisRenderer!._visibleRange!)) : _withInRange( seriesRenderer._seriesType.contains('100') ? point.cumulativeValue : seriesRenderer._seriesType == 'waterfall' ? point.endValue ?? 0 : point.yValue, - seriesRenderer._yAxisRenderer._visibleRange)); + seriesRenderer._yAxisRenderer!._visibleRange!)); } return withInRange; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart index 147a278e1..cea5ae433 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart @@ -9,16 +9,17 @@ part of charts; /// class MarkerSettings { /// Creating an argument constructor of MarkerSettings class. - MarkerSettings( - {this.isVisible = false, - double height, - double width, + const MarkerSettings( + {bool? isVisible, + double? height = 8, + double? width = 8, this.color, - DataMarkerType shape, - double borderWidth, + DataMarkerType? shape, + double? borderWidth, this.borderColor, this.image}) - : height = height ?? 8, + : isVisible = isVisible ?? false, + height = height ?? 8, width = width ?? 8, shape = shape ?? DataMarkerType.circle, borderWidth = borderWidth ?? 2; @@ -96,7 +97,7 @@ class MarkerSettings { /// )); ///} ///``` - final Color color; + final Color? color; ///Shape of the marker. /// @@ -138,7 +139,7 @@ class MarkerSettings { /// )); ///} ///``` - final Color borderColor; + final Color? borderColor; ///Border width of the marker. /// @@ -178,7 +179,7 @@ class MarkerSettings { /// )); ///} ///``` - final ImageProvider image; + final ImageProvider? image; } /// Marker settings renderer class for mutable fields and methods @@ -195,26 +196,27 @@ class MarkerSettingsRenderer { final MarkerSettings _markerSettings; // ignore: prefer_final_fields - Color _color; + Color? _color; - Color _borderColor; + Color? _borderColor; - double _borderWidth; + late double _borderWidth; - dart_ui.Image _image; + dart_ui.Image? _image; /// To paint the marker here void renderMarker( CartesianSeriesRenderer seriesRenderer, CartesianChartPoint point, - Animation animationController, + Animation? animationController, Canvas canvas, int markerIndex, - [int outlierIndex]) { + [int? outlierIndex]) { final bool isDataPointVisible = _isLabelWithinRange( seriesRenderer, seriesRenderer._dataPoints[markerIndex]); Paint strokePaint, fillPaint; - final XyDataSeries series = seriesRenderer._series; + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; final Size size = Size(series.markerSettings.width, series.markerSettings.height); final DataMarkerType markerType = series.markerSettings.shape; @@ -223,15 +225,15 @@ class MarkerSettingsRenderer { final bool isBoxSeries = seriesRenderer._seriesType.contains('boxandwhisker'); final double opacity = (animationController != null && - (seriesRenderer._chartState._initialRender || + (seriesRenderer._chartState!._initialRender! || seriesRenderer._needAnimateSeriesElements)) ? animationController.value : 1; point = seriesRenderer._dataPoints[markerIndex]; - Color seriesColor = seriesRenderer._seriesColor; + Color? seriesColor = seriesRenderer._seriesColor; if (seriesRenderer._seriesType == 'waterfall') { - seriesColor = - _getWaterfallSeriesColor(seriesRenderer._series, point, seriesColor); + seriesColor = _getWaterfallSeriesColor( + seriesRenderer._series as WaterfallSeries, point, seriesColor); } _borderColor = series.markerSettings.borderColor ?? seriesColor; _color = series.markerSettings.color; @@ -240,7 +242,7 @@ class MarkerSettingsRenderer { seriesRenderer._markerShapes.add(isDataPointVisible ? _getMarkerShapesPath( markerType, - Offset(point.markerPoint.x, point.markerPoint.y), + Offset(point.markerPoint!.x, point.markerPoint!.y), size, seriesRenderer, markerIndex, @@ -251,7 +253,7 @@ class MarkerSettingsRenderer { seriesRenderer._markerShapes.add(isDataPointVisible ? _getMarkerShapesPath( markerType, - Offset(point.outliersPoint[outlierIndex].x, + Offset(point.outliersPoint[outlierIndex!].x, point.outliersPoint[outlierIndex].y), size, seriesRenderer, @@ -265,7 +267,7 @@ class MarkerSettingsRenderer { seriesRenderer._markerShapes2.add(isDataPointVisible ? _getMarkerShapesPath( markerType, - Offset(point.markerPoint2.x, point.markerPoint2.y), + Offset(point.markerPoint2!.x, point.markerPoint2!.y), size, seriesRenderer, markerIndex, @@ -281,8 +283,8 @@ class MarkerSettingsRenderer { : (series.markerSettings.borderWidth == 0 ? Colors.transparent : ((hasPointColor && point.pointColorMapper != null) - ? point.pointColorMapper.withOpacity(opacity) - : _borderColor.withOpacity(opacity))) + ? point.pointColorMapper!.withOpacity(opacity) + : _borderColor!.withOpacity(opacity))) ..style = PaintingStyle.stroke ..strokeWidth = point.isEmpty == true ? series.emptyPointSettings.borderWidth @@ -290,23 +292,23 @@ class MarkerSettingsRenderer { if (series.gradient != null && series.markerSettings.borderColor == null) { strokePaint = _getLinearGradientPaint( - series.gradient, + series.gradient!, _getMarkerShapesPath( markerType, Offset( isBoxSeries - ? point.outliersPoint[outlierIndex].x - : point.markerPoint.x, + ? point.outliersPoint[outlierIndex!].x + : point.markerPoint!.x, isBoxSeries - ? point.outliersPoint[outlierIndex].y - : point.markerPoint.y), + ? point.outliersPoint[outlierIndex!].y + : point.markerPoint!.y), size, seriesRenderer, null, null, animationController) .getBounds(), - seriesRenderer._chartState._requireInvertedAxis); + seriesRenderer._chartState!._requireInvertedAxis); strokePaint.style = PaintingStyle.stroke; strokePaint.strokeWidth = point.isEmpty == true ? series.emptyPointSettings.borderWidth @@ -318,54 +320,54 @@ class MarkerSettingsRenderer { ? series.emptyPointSettings.color : _color != Colors.transparent ? (_color ?? - (seriesRenderer._chartState._chartTheme.brightness == + (seriesRenderer._chartState!._chartTheme.brightness == Brightness.light ? Colors.white : Colors.black)) .withOpacity(opacity) - : _color + : _color! ..style = PaintingStyle.fill; final bool isScatter = seriesRenderer._seriesType == 'scatter'; final Rect axisClipRect = - seriesRenderer._chartState._chartAxis._axisClipRect; + seriesRenderer._chartState!._chartAxis._axisClipRect; /// Render marker points if ((series.markerSettings.isVisible || isScatter || isBoxSeries) && point.isVisible && _withInRect(seriesRenderer, point.markerPoint, axisClipRect) && (point.markerPoint != null || - point.outliersPoint[outlierIndex] != null) && + point.outliersPoint[outlierIndex!] != null) && point.isGap != true && (!isScatter || series.markerSettings.shape == DataMarkerType.image) && seriesRenderer - ._markerShapes[isBoxSeries ? outlierIndex : markerIndex] != + ._markerShapes[isBoxSeries ? outlierIndex! : markerIndex] != null) { seriesRenderer.drawDataMarker( - isBoxSeries ? outlierIndex : markerIndex, + isBoxSeries ? outlierIndex! : markerIndex, canvas, fillPaint, strokePaint, isBoxSeries - ? point.outliersPoint[outlierIndex].x - : point.markerPoint.x, + ? point.outliersPoint[outlierIndex!].x + : point.markerPoint!.x, isBoxSeries - ? point.outliersPoint[outlierIndex].y - : point.markerPoint.y, + ? point.outliersPoint[outlierIndex!].y + : point.markerPoint!.y, seriesRenderer); if (series.markerSettings.shape == DataMarkerType.image) { _drawImageMarker( seriesRenderer, canvas, isBoxSeries - ? point.outliersPoint[outlierIndex].x - : point.markerPoint.x, + ? point.outliersPoint[outlierIndex!].x + : point.markerPoint!.x, isBoxSeries - ? point.outliersPoint[outlierIndex].y - : point.markerPoint.y); + ? point.outliersPoint[outlierIndex!].y + : point.markerPoint!.y); if (seriesRenderer._seriesType.contains('range') || seriesRenderer._seriesType == 'hilo') { - _drawImageMarker(seriesRenderer, canvas, point.markerPoint2.x, - point.markerPoint2.y); + _drawImageMarker(seriesRenderer, canvas, point.markerPoint2!.x, + point.markerPoint2!.y); } } } @@ -373,10 +375,11 @@ class MarkerSettingsRenderer { /// To determine if the marker is within axis clip rect bool _withInRect(CartesianSeriesRenderer seriesRenderer, - _ChartLocation markerPoint, Rect axisClipRect) { + _ChartLocation? markerPoint, Rect axisClipRect) { bool withInRect = false; - withInRect = markerPoint.x >= axisClipRect.left && + withInRect = markerPoint != null && + markerPoint.x >= axisClipRect.left && markerPoint.x <= axisClipRect.right && markerPoint.y <= axisClipRect.bottom && markerPoint.y >= axisClipRect.top; @@ -388,14 +391,14 @@ class MarkerSettingsRenderer { void _drawImageMarker(CartesianSeriesRenderer seriesRenderer, Canvas canvas, double pointX, double pointY) { // final MarkerSettingsRenderer markerSettingsRenderer = seriesRenderer._markerSettingsRenderer; - if (seriesRenderer._markerSettingsRenderer._image != null) { + if (seriesRenderer._markerSettingsRenderer!._image != null) { final double imageWidth = 2 * seriesRenderer._series.markerSettings.width; final double imageHeight = 2 * seriesRenderer._series.markerSettings.height; final Rect positionRect = Rect.fromLTWH(pointX - imageWidth / 2, pointY - imageHeight / 2, imageWidth, imageHeight); paintImage( - canvas: canvas, rect: positionRect, image: _image, fit: BoxFit.fill); + canvas: canvas, rect: positionRect, image: _image!, fit: BoxFit.fill); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart index 6146418e4..bf8645460 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart @@ -3,27 +3,30 @@ part of charts; // ignore: must_be_immutable class _DataLabelRenderer extends StatefulWidget { // ignore: prefer_const_constructors_in_immutables - _DataLabelRenderer({this.cartesianChartState, this.show}); + _DataLabelRenderer({required this.cartesianChartState, required this.show}); final SfCartesianChartState cartesianChartState; bool show; - _DataLabelRendererState state; + _DataLabelRendererState? state; @override - State createState() => _DataLabelRendererState(); + State createState() { + state = _DataLabelRendererState(); + return state!; + } } class _DataLabelRendererState extends State<_DataLabelRenderer> with SingleTickerProviderStateMixin { - List animationControllersList; + // List animationControllersList; /// Animation controller for series - AnimationController animationController; + late AnimationController animationController; /// Repaint notifier for crosshair container - ValueNotifier dataLabelRepaintNotifier; + late ValueNotifier dataLabelRepaintNotifier; @override void initState() { @@ -37,7 +40,7 @@ class _DataLabelRendererState extends State<_DataLabelRenderer> Widget build(BuildContext context) { widget.state = this; animationController.duration = Duration( - milliseconds: widget.cartesianChartState._initialRender ? 500 : 0); + milliseconds: widget.cartesianChartState._initialRender! ? 500 : 0); final Animation dataLabelAnimation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, @@ -57,11 +60,11 @@ class _DataLabelRendererState extends State<_DataLabelRenderer> @override void dispose() { - if (animationController != null) { - animationController.removeListener(repaintDataLabelElements); - animationController.dispose(); - animationController = null; - } + // if (animationController != null) { + animationController.removeListener(repaintDataLabelElements); + animationController.dispose(); + // animationController = null; + // } super.dispose(); } @@ -79,11 +82,11 @@ class _DataLabelRendererState extends State<_DataLabelRenderer> class _DataLabelPainter extends CustomPainter { _DataLabelPainter( - {this.cartesianChartState, - this.state, - this.animationController, - this.animation, - ValueNotifier notifier}) + {required this.cartesianChartState, + required this.state, + required this.animationController, + required this.animation, + required ValueNotifier notifier}) : super(repaint: notifier); final SfCartesianChartState cartesianChartState; @@ -107,7 +110,7 @@ class _DataLabelPainter extends CustomPainter { if (seriesRenderer._series.dataLabelSettings.isVisible && (seriesRenderer._animationCompleted || seriesRenderer._series.animationDuration == 0 || - !cartesianChartState._initialRender) && + !cartesianChartState._initialRender!) && (!seriesRenderer._needAnimateSeriesElements || (cartesianChartState._seriesDurationFactor < seriesRenderer._animationController.value || @@ -118,16 +121,16 @@ class _DataLabelPainter extends CustomPainter { seriesRenderer._dataLabelSettingsRenderer = DataLabelSettingsRenderer(seriesRenderer._series.dataLabelSettings); if (seriesRenderer._visibleDataPoints != null && - seriesRenderer._visibleDataPoints.isNotEmpty) { - for (int j = 0; j < seriesRenderer._visibleDataPoints.length; j++) { - if (seriesRenderer._visible && + seriesRenderer._visibleDataPoints!.isNotEmpty) { + for (int j = 0; j < seriesRenderer._visibleDataPoints!.length; j++) { + if (seriesRenderer._visible! && seriesRenderer._series.dataLabelSettings != null) { dataLabelSettingsRenderer = seriesRenderer._dataLabelSettingsRenderer; - dataLabelSettingsRenderer?._renderDataLabel( + dataLabelSettingsRenderer._renderDataLabel( cartesianChartState, seriesRenderer, - seriesRenderer._visibleDataPoints[j], + seriesRenderer._visibleDataPoints![j], animation, canvas, j, @@ -152,15 +155,15 @@ void _calculateRectSeriesRegion( int pointIndex, CartesianSeriesRenderer seriesRenderer, SfCartesianChartState _chartState) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; - final num crossesAt = _getCrossesAtValue(seriesRenderer, _chartState); - final num sideBySideMinimumVal = seriesRenderer.sideBySideInfo.minimum; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; + final num? crossesAt = _getCrossesAtValue(seriesRenderer, _chartState); + final num sideBySideMinimumVal = seriesRenderer.sideBySideInfo!.minimum; - final num sideBySideMaximumVal = seriesRenderer.sideBySideInfo.maximum; + final num sideBySideMaximumVal = seriesRenderer.sideBySideInfo!.maximum; final num origin = - crossesAt ?? math.max(yAxisRenderer._visibleRange.minimum, 0); + crossesAt ?? math.max(yAxisRenderer._visibleRange!.minimum, 0); /// Get the rectangle based on points final Rect rect = (seriesRenderer._seriesType.contains('stackedcolumn') || @@ -296,8 +299,8 @@ void _calculatePointSeriesRegion( CartesianSeriesRenderer seriesRenderer, SfCartesianChartState _chartState, Rect rect) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final CartesianSeries series = seriesRenderer._series; final _ChartLocation currentPoint = _calculatePoint( point.xValue, @@ -315,10 +318,10 @@ void _calculatePointSeriesRegion( currentPoint.x + series.markerSettings.width, currentPoint.y + series.markerSettings.width); } else { - final BubbleSeries bubbleSeries = series; - num bubbleRadius, sizeRange, radiusRange, bubbleSize; + final BubbleSeries bubbleSeries = series as BubbleSeries; + num bubbleRadius = 0, sizeRange = 0, radiusRange, bubbleSize; if (seriesRenderer is BubbleSeriesRenderer) { - sizeRange = seriesRenderer._maxSize - seriesRenderer._minSize; + sizeRange = seriesRenderer._maxSize! - seriesRenderer._minSize!; } bubbleSize = ((point.bubbleSize) ?? 4).toDouble(); if (bubbleSeries.sizeValueMapper == null) { @@ -335,7 +338,7 @@ void _calculatePointSeriesRegion( (bubbleSeries.maximumRadius - bubbleSeries.minimumRadius) * 2; if (seriesRenderer is BubbleSeriesRenderer) { bubbleRadius = - (((bubbleSize.abs() - seriesRenderer._minSize) * radiusRange) / + (((bubbleSize.abs() - seriesRenderer._minSize!) * radiusRange) / sizeRange) + bubbleSeries.minimumRadius; } @@ -357,17 +360,17 @@ void _calculatePathSeriesRegion( CartesianSeriesRenderer seriesRenderer, SfCartesianChartState _chartState, Rect rect, - num markerHeight, - num markerWidth, - [_VisibleRange sideBySideInfo, - CartesianChartPoint _nextPoint, - num midX, - num midY]) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; - final num sideBySideMinimumVal = seriesRenderer?.sideBySideInfo?.minimum; - - final num sideBySideMaximumVal = seriesRenderer?.sideBySideInfo?.maximum; + double markerHeight, + double markerWidth, + [_VisibleRange? sideBySideInfo, + CartesianChartPoint? _nextPoint, + num? midX, + num? midY]) { + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; + final num? sideBySideMinimumVal = seriesRenderer.sideBySideInfo?.minimum; + + final num? sideBySideMaximumVal = seriesRenderer.sideBySideInfo?.maximum; if (seriesRenderer._seriesType != 'rangearea' && seriesRenderer._seriesType != 'splinerangearea' && (!seriesRenderer._seriesType.contains('hilo')) && @@ -375,19 +378,18 @@ void _calculatePathSeriesRegion( !seriesRenderer._seriesType.contains('boxandwhisker')) { if (seriesRenderer._seriesType == 'spline' && pointIndex <= seriesRenderer._dataPoints.length - 2) { - point.controlPoint = - seriesRenderer._drawControlPoints[pointIndex]._listControlPoints; + point.controlPoint = seriesRenderer._drawControlPoints[pointIndex]; point.startControl = _calculatePoint( - point.controlPoint[0].controlPoint1, - point.controlPoint[0].controlPoint2, + point.controlPoint![0].dx, + point.controlPoint![0].dy, xAxisRenderer, yAxisRenderer, _chartState._requireInvertedAxis, seriesRenderer._series, rect); point.endControl = _calculatePoint( - point.controlPoint[1].controlPoint1, - point.controlPoint[1].controlPoint2, + point.controlPoint![1].dx, + point.controlPoint![1].dy, xAxisRenderer, yAxisRenderer, _chartState._requireInvertedAxis, @@ -397,19 +399,18 @@ void _calculatePathSeriesRegion( if (seriesRenderer._seriesType == 'splinearea' && pointIndex != 0 && pointIndex <= seriesRenderer._dataPoints.length - 1) { - point.controlPoint = - seriesRenderer._drawControlPoints[pointIndex - 1]._listControlPoints; + point.controlPoint = seriesRenderer._drawControlPoints[pointIndex - 1]; point.startControl = _calculatePoint( - point.controlPoint[0].controlPoint1, - point.controlPoint[0].controlPoint2, + point.controlPoint![0].dx, + point.controlPoint![0].dy, xAxisRenderer, yAxisRenderer, _chartState._requireInvertedAxis, seriesRenderer._series, rect); point.endControl = _calculatePoint( - point.controlPoint[1].controlPoint1, - point.controlPoint[1].controlPoint2, + point.controlPoint![1].dx, + point.controlPoint![1].dy, xAxisRenderer, yAxisRenderer, _chartState._requireInvertedAxis, @@ -421,18 +422,18 @@ void _calculatePathSeriesRegion( point.currentPoint = _calculatePoint( point.xValue, point.yValue, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); if (_nextPoint != null) { point._nextPoint = _calculatePoint( _nextPoint.xValue, _nextPoint.yValue, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); } @@ -440,9 +441,9 @@ void _calculatePathSeriesRegion( point._midPoint = _calculatePoint( midX, midY, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); } @@ -474,7 +475,7 @@ void _calculatePathSeriesRegion( currentPoint.y - markerHeight, 2 * markerWidth, 2 * markerHeight); point.markerPoint = currentPoint; } else { - num value1, value2; + num? value1, value2; value1 = (point.low != null && point.high != null && point.low < point.high) ? point.high : point.low; @@ -484,12 +485,12 @@ void _calculatePathSeriesRegion( if (seriesRenderer._seriesType == 'boxandwhisker') { value1 = (point.minimum != null && point.maximum != null && - point.minimum < point.maximum) + point.minimum! < point.maximum!) ? point.maximum : point.minimum; value2 = (point.minimum != null && point.maximum != null && - point.minimum > point.maximum) + point.minimum! > point.maximum!) ? point.maximum : point.minimum; } @@ -512,13 +513,12 @@ void _calculatePathSeriesRegion( if (seriesRenderer._seriesType == 'splinerangearea' && pointIndex != 0 && pointIndex <= seriesRenderer._dataPoints.length - 1) { - point.controlPointshigh = seriesRenderer - ._drawHighControlPoints[seriesRenderer._dataPoints.indexOf(point) - 1] - ._listControlPoints; + point.controlPointshigh = seriesRenderer._drawHighControlPoints[ + seriesRenderer._dataPoints.indexOf(point) - 1]; point.highStartControl = _calculatePoint( - point.controlPointshigh[0].controlPoint1, - point.controlPointshigh[0].controlPoint2, + point.controlPointshigh![0].dx, + point.controlPointshigh![0].dy, xAxisRenderer, yAxisRenderer, _chartState._requireInvertedAxis, @@ -526,8 +526,8 @@ void _calculatePathSeriesRegion( rect); point.highEndControl = _calculatePoint( - point.controlPointshigh[1].controlPoint1, - point.controlPointshigh[1].controlPoint2, + point.controlPointshigh![1].dx, + point.controlPointshigh![1].dy, xAxisRenderer, yAxisRenderer, _chartState._requireInvertedAxis, @@ -538,24 +538,23 @@ void _calculatePathSeriesRegion( pointIndex >= 0 && pointIndex <= seriesRenderer._dataPoints.length - 2) { point.controlPointslow = seriesRenderer - ._drawLowControlPoints[seriesRenderer._dataPoints.indexOf(point)] - ._listControlPoints; + ._drawLowControlPoints[seriesRenderer._dataPoints.indexOf(point)]; point.lowStartControl = _calculatePoint( - point.controlPointslow[0].controlPoint1, - point.controlPointslow[0].controlPoint2, + point.controlPointslow![0].dx, + point.controlPointslow![0].dy, xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); point.lowEndControl = _calculatePoint( - point.controlPointslow[1].controlPoint1, - point.controlPointslow[1].controlPoint2, + point.controlPointslow![1].dx, + point.controlPointslow![1].dy, xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); } @@ -566,16 +565,16 @@ void _calculatePathSeriesRegion( point.lowPoint = _calculatePoint( point.xValue, point.low, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, rect); point.highPoint = _calculatePoint( point.xValue, point.high, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, rect); @@ -592,8 +591,8 @@ void _calculatePathSeriesRegion( point.openPoint = _calculatePoint( point.xValue + sideBySideMinimumVal, point.open, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, rect); @@ -601,8 +600,8 @@ void _calculatePathSeriesRegion( point.closePoint = _calculatePoint( point.xValue + sideBySideMaximumVal, point.close, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, rect); @@ -610,8 +609,8 @@ void _calculatePathSeriesRegion( point.centerOpenPoint = _calculatePoint( point.xValue, point.open, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, rect); @@ -619,8 +618,8 @@ void _calculatePathSeriesRegion( point.centerClosePoint = _calculatePoint( point.xValue, point.close, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, rect); @@ -629,8 +628,8 @@ void _calculatePathSeriesRegion( point.centerHighPoint = _calculatePoint( center, point.high, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, rect); @@ -638,8 +637,8 @@ void _calculatePathSeriesRegion( point.centerLowPoint = _calculatePoint( center, point.low, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, rect); @@ -647,16 +646,16 @@ void _calculatePathSeriesRegion( point.lowPoint = _calculatePoint( point.xValue + sideBySideMinimumVal, point.low, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, rect); point.highPoint = _calculatePoint( point.xValue + sideBySideMaximumVal, point.high, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, rect); @@ -675,81 +674,81 @@ void _calculatePathSeriesRegion( point.centerMeanPoint = _calculatePoint( center, point.mean, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); point.minimumPoint = _calculatePoint( point.xValue + sideBySideMinimumVal, point.minimum, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); point.maximumPoint = _calculatePoint( point.xValue + sideBySideMaximumVal, point.maximum, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); point.centerMinimumPoint = _calculatePoint( center, point.minimum, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); point.centerMaximumPoint = _calculatePoint( center, point.maximum, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); point.lowerQuartilePoint = _calculatePoint( point.xValue + sideBySideMinimumVal, point.lowerQuartile, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); point.upperQuartilePoint = _calculatePoint( point.xValue + sideBySideMaximumVal, point.upperQuartile, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); point.medianPoint = _calculatePoint( point.xValue + sideBySideMinimumVal, point.median, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); point.centerMedianPoint = _calculatePoint( center, point.median, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); } @@ -758,26 +757,26 @@ void _calculatePathSeriesRegion( seriesRenderer._seriesType.contains('boxandwhisker') ? !_chartState._requireInvertedAxis ? Rect.fromLTWH( - point.markerPoint.x, - point.markerPoint.y, + point.markerPoint!.x, + point.markerPoint!.y, seriesRenderer._series.borderWidth, - point.markerPoint2.y - point.markerPoint.y) + point.markerPoint2!.y - point.markerPoint!.y) : Rect.fromLTWH( - point.markerPoint2.x, - point.markerPoint2.y, - (point.markerPoint.x - point.markerPoint2.x).abs(), + point.markerPoint2!.x, + point.markerPoint2!.y, + (point.markerPoint!.x - point.markerPoint2!.x).abs(), seriesRenderer._series.borderWidth) : Rect.fromLTRB( - point.markerPoint.x - markerWidth, - point.markerPoint.y - markerHeight, - point.markerPoint.x + markerWidth, - point.markerPoint2.y); + point.markerPoint!.x - markerWidth, + point.markerPoint!.y - markerHeight, + point.markerPoint!.x + markerWidth, + point.markerPoint2!.y); if (seriesRenderer._seriesType.contains('boxandwhisker')) { point.boxRectRegion = _calculateRectangle( point.xValue + sideBySideMinimumVal, - point.upperQuartile, + point.upperQuartile!, point.xValue + sideBySideMaximumVal, - point.lowerQuartile, + point.lowerQuartile!, seriesRenderer, _chartState); } @@ -787,7 +786,7 @@ void _calculatePathSeriesRegion( /// Finding outliers region void _calculateOutlierRegion(CartesianChartPoint point, _ChartLocation outlierPosition, num outlierWidth) { - point.outlierRegion.add(Rect.fromLTRB( + point.outlierRegion!.add(Rect.fromLTRB( outlierPosition.x - outlierWidth, outlierPosition.y - outlierWidth, outlierPosition.x + outlierWidth, @@ -800,13 +799,13 @@ void _calculateTooltipRegion( int seriesIndex, CartesianSeriesRenderer seriesRenderer, SfCartesianChartState _chartState, - [Trendline trendline, - TrendlineRenderer trendlineRenderer, - int trendlineIndex]) { + [Trendline? trendline, + TrendlineRenderer? trendlineRenderer, + int? trendlineIndex]) { final SfCartesianChart chart = _chartState._chart; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; final CartesianSeries series = seriesRenderer._series; - final num crossesAt = _getCrossesAtValue(seriesRenderer, _chartState); + final num? crossesAt = _getCrossesAtValue(seriesRenderer, _chartState); if ((series.enableTooltip != null || seriesRenderer._chart.trackballBehavior != null || chart.onPointTapped != null) && @@ -823,22 +822,27 @@ void _calculateTooltipRegion( } final List regionData = []; num binWidth = 0; - String date; + String? date; final List regionRect = []; if (seriesRenderer is HistogramSeriesRenderer) { - binWidth = seriesRenderer._histogramValues.binWidth; + binWidth = seriesRenderer._histogramValues.binWidth!; } if (xAxisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis xAxis = xAxisRenderer._axis; + final DateTimeAxis xAxis = xAxisRenderer._axis as DateTimeAxis; final DateFormat dateFormat = - xAxis.dateFormat ?? xAxisRenderer._getLabelFormat(xAxisRenderer); + xAxis.dateFormat ?? _getDateTimeLabelFormat(xAxisRenderer); date = dateFormat .format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor())); + } else if (xAxisRenderer is DateTimeCategoryAxisRenderer) { + date = point.x is DateTime + ? xAxisRenderer._dateFormat.format(point.x) + : point.x.toString(); } xAxisRenderer is CategoryAxisRenderer ? regionData.add(point.x.toString()) - : xAxisRenderer is DateTimeAxisRenderer - ? regionData.add(date.toString()) + : (xAxisRenderer is DateTimeAxisRenderer || + xAxisRenderer is DateTimeCategoryAxisRenderer) + ? regionData.add(date!.toString()) : seriesRenderer._seriesType != 'histogram' ? regionData.add(_getLabelValue( point.xValue, @@ -883,7 +887,7 @@ void _calculateTooltipRegion( regionData.add(point.yValue.toString()); } regionData.add(isTrendline - ? trendlineRenderer._name + ? trendlineRenderer!._name ?? '' : series.name ?? 'series $seriesIndex'); regionRect.add(seriesRenderer._seriesType.contains('boxandwhisker') ? point.boxRectRegion @@ -896,20 +900,20 @@ void _calculateTooltipRegion( seriesRenderer._seriesType.contains('stackedcolumn') || seriesRenderer._seriesType == 'histogram' ? (point.yValue > (crossesAt ?? 0)) - ? point.region.topCenter - : point.region.bottomCenter + ? point.region!.topCenter + : point.region!.bottomCenter : seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType.contains('candle') || seriesRenderer._seriesType.contains('boxandwhisker') - ? point.region.topCenter - : point.region.topCenter + ? point.region!.topCenter + : point.region!.topCenter : (seriesRenderer._seriesType.contains('rangearea') ? (isTrendline - ? Offset(point.markerPoint.x, point.markerPoint.y) - : Offset(point.markerPoint.x, point.markerPoint.y)) - : point.region.center)); + ? Offset(point.markerPoint!.x, point.markerPoint!.y) + : Offset(point.markerPoint!.x, point.markerPoint!.y)) + : point.region!.center)); regionRect.add( - isTrendline ? trendlineRenderer._fillColor : point.pointColorMapper); + isTrendline ? trendlineRenderer!._fillColor : point.pointColorMapper); regionRect.add(point.bubbleSize); regionRect.add(point); regionRect.add(point.outlierRegion); @@ -921,20 +925,20 @@ void _calculateTooltipRegion( if (isTrendline) { regionRect.add(trendline); } - seriesRenderer._regionalData[regionRect] = regionData; + seriesRenderer._regionalData![regionRect] = regionData; point.regionData = regionData; } } /// Paint the image marker -void _drawImageMarker(CartesianSeriesRenderer seriesRenderer, Canvas canvas, +void _drawImageMarker(CartesianSeriesRenderer? seriesRenderer, Canvas canvas, double pointX, double pointY, - [TrackballMarkerSettings trackballMarkerSettings, - SfCartesianChartState chartState]) { - final MarkerSettingsRenderer markerSettingsRenderer = + [TrackballMarkerSettings? trackballMarkerSettings, + SfCartesianChartState? chartState]) { + final MarkerSettingsRenderer? markerSettingsRenderer = seriesRenderer?._markerSettingsRenderer; - if (seriesRenderer != null && markerSettingsRenderer._image != null) { + if (seriesRenderer != null && markerSettingsRenderer!._image != null) { final double imageWidth = 2 * seriesRenderer._series.markerSettings.width; final double imageHeight = 2 * seriesRenderer._series.markerSettings.height; final Rect positionRect = Rect.fromLTWH(pointX - imageWidth / 2, @@ -942,20 +946,19 @@ void _drawImageMarker(CartesianSeriesRenderer seriesRenderer, Canvas canvas, paintImage( canvas: canvas, rect: positionRect, - image: markerSettingsRenderer._image, + image: markerSettingsRenderer._image!, fit: BoxFit.fill); } - if (chartState?._trackballMarkerSettingsRenderer != null && - chartState?._trackballMarkerSettingsRenderer?._image != null) { - final double imageWidth = 2 * trackballMarkerSettings.width; + if (chartState?._trackballMarkerSettingsRenderer._image != null) { + final double imageWidth = 2 * trackballMarkerSettings!.width; final double imageHeight = 2 * trackballMarkerSettings.height; final Rect positionRect = Rect.fromLTWH(pointX - imageWidth / 2, pointY - imageHeight / 2, imageWidth, imageHeight); paintImage( canvas: canvas, rect: positionRect, - image: chartState._trackballMarkerSettingsRenderer._image, + image: chartState!._trackballMarkerSettingsRenderer._image!, fit: BoxFit.fill); } } @@ -975,7 +978,7 @@ void _drawDashedLine( _dashPath( path, dashArray: _CircularIntervalList(dashArray), - ), + )!, paint); } else { canvas.drawPath(path, paint); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart index bff18e920..b74ec4d3b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart @@ -2,12 +2,12 @@ part of charts; class _AreaChartPainter extends CustomPainter { _AreaChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -22,20 +22,21 @@ class _AreaChartPainter extends CustomPainter { void paint(Canvas canvas, Size size) { final int seriesIndex = painterKey.index; Rect clipRect; - final AreaSeries series = seriesRenderer._series; + final AreaSeries series = + seriesRenderer._series as AreaSeries; seriesRenderer._storeSeriesProperties(chartState, seriesIndex); double animationFactor; - CartesianChartPoint prevPoint, point, _point; - _ChartLocation currentPoint, originPoint, _oldPoint; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; - CartesianSeriesRenderer oldSeriesRenderer; + CartesianChartPoint? prevPoint, point, _point; + _ChartLocation? currentPoint, originPoint, _oldPoint; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; + CartesianSeriesRenderer? oldSeriesRenderer; final Path _path = Path(); final Path _strokePath = Path(); - final num crossesAt = _getCrossesAtValue(seriesRenderer, chartState); + final num? crossesAt = _getCrossesAtValue(seriesRenderer, chartState); final num origin = crossesAt ?? 0; final List _points = []; - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { assert( series.animationDuration != null ? series.animationDuration >= 0 @@ -47,10 +48,11 @@ class _AreaChartPainter extends CustomPainter { seriesRenderer._dataPoints; final bool widgetNeedUpdate = chartState._widgetNeedUpdate; final bool isLegendToggled = chartState._isLegendToggled; - final bool isTransposed = seriesRenderer._chartState._requireInvertedAxis; + final bool isTransposed = + seriesRenderer._chartState!._requireInvertedAxis; canvas.save(); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; final Rect axisClipRect = _calculatePlotOffset( chartState._chartAxis._axisClipRect, @@ -69,7 +71,7 @@ class _AreaChartPainter extends CustomPainter { chartState, xAxisRenderer._axis, canvas, animationFactor); } if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -83,8 +85,8 @@ class _AreaChartPainter extends CustomPainter { ? _calculatePoint( _point.xValue, _point.yValue, - oldSeriesRenderer._xAxisRenderer, - oldSeriesRenderer._yAxisRenderer, + oldSeriesRenderer!._xAxisRenderer!, + oldSeriesRenderer._yAxisRenderer!, isTransposed, oldSeriesRenderer._series, axisClipRect) @@ -93,14 +95,14 @@ class _AreaChartPainter extends CustomPainter { xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); originPoint = _calculatePoint( point.xValue, - math_lib.max(yAxisRenderer._visibleRange.minimum, origin), + math_lib.max(yAxisRenderer._visibleRange!.minimum, origin), xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); - num x = currentPoint.x; - num y = currentPoint.y; + double x = currentPoint.x; + double y = currentPoint.y; _points.add(Offset(x, y)); final bool closed = series.emptyPointSettings.mode == EmptyPointMode.drop @@ -180,7 +182,7 @@ class _AreaChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - (!chartState._initialRender && + (!chartState._initialRender! && !seriesRenderer._needAnimateSeriesElements) || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart index 1f38f6f2a..5c619e966 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart @@ -2,12 +2,12 @@ part of charts; class _BarChartPainter extends CustomPainter { _BarChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -20,12 +20,13 @@ class _BarChartPainter extends CustomPainter { /// Painter method for bar series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; - final BarSeries series = seriesRenderer._series; - if (seriesRenderer._visible) { + final BarSeries series = + seriesRenderer._series as BarSeries; + if (seriesRenderer._visible!) { assert( series.animationDuration != null ? series.animationDuration >= 0 @@ -43,11 +44,11 @@ class _BarChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; int segmentIndex = -1; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -75,7 +76,7 @@ class _BarChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - (!chartState._initialRender && + (!chartState._initialRender! && !seriesRenderer._needAnimateSeriesElements) || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart index d36e5f209..fc6361719 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart @@ -2,12 +2,12 @@ part of charts; class _BoxAndWhiskerPainter extends CustomPainter { _BoxAndWhiskerPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); @@ -22,17 +22,18 @@ class _BoxAndWhiskerPainter extends CustomPainter { /// Painter method for box and whisker series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; assert(dataPoints.isNotEmpty, 'The data points should be available to render the box and whisker series.'); Rect clipRect; double animationFactor; - final BoxAndWhiskerSeries series = seriesRenderer._series; - CartesianChartPoint point; - if (seriesRenderer._visible) { + final BoxAndWhiskerSeries series = + seriesRenderer._series as BoxAndWhiskerSeries; + CartesianChartPoint? point; + if (seriesRenderer._visible!) { canvas.save(); assert( series.animationDuration != null @@ -47,11 +48,11 @@ class _BoxAndWhiskerPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; int segmentIndex = -1; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -69,29 +70,29 @@ class _BoxAndWhiskerPainter extends CustomPainter { seriesRenderer._createSegments( point, segmentIndex += 1, painterKey.index, animationFactor)); } - if (point.outliers.isNotEmpty) { + if (point.outliers!.isNotEmpty) { final MarkerSettingsRenderer markerSettingsRenderer = MarkerSettingsRenderer(series.markerSettings); - seriesRenderer._markerShapes = []; + seriesRenderer._markerShapes = []; point.outlierRegion = []; point.outlierRegionPosition = []; for (int outlierIndex = 0; - outlierIndex < point.outliers.length; + outlierIndex < point.outliers!.length; outlierIndex++) { point.outliersPoint.add(_calculatePoint( point.xValue, - point.outliers[outlierIndex], - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + point.outliers![outlierIndex], + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, axisClipRect)); _calculateOutlierRegion(point, point.outliersPoint[outlierIndex], series.markerSettings.width); - point.outlierRegionPosition.add(Offset( + point.outlierRegionPosition!.add(Offset( point.outliersPoint[outlierIndex].x, point.outliersPoint[outlierIndex].y)); - markerSettingsRenderer?.renderMarker( + markerSettingsRenderer.renderMarker( seriesRenderer, point, seriesRenderer._seriesElementAnimation, @@ -128,7 +129,7 @@ class _BoxAndWhiskerPainter extends CustomPainter { seriesRenderer._renderSeriesElements( chart, canvas, seriesRenderer._seriesElementAnimation); } - if (seriesRenderer._visible && animationFactor >= 1) { + if (seriesRenderer._visible! && animationFactor >= 1) { chartState._setPainterKey(painterKey.index, painterKey.name, true); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart index 4583a01eb..859d94141 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart @@ -2,12 +2,12 @@ part of charts; class _BubbleChartPainter extends CustomPainter { _BubbleChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -20,13 +20,14 @@ class _BubbleChartPainter extends CustomPainter { /// Painter method for bubble series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; double animationFactor; - final BubbleSeries series = seriesRenderer._series; - if (seriesRenderer._visible) { + final BubbleSeries series = + seriesRenderer._series as BubbleSeries; + if (seriesRenderer._visible!) { canvas.save(); assert( series.animationDuration != null @@ -36,7 +37,7 @@ class _BubbleChartPainter extends CustomPainter { final int seriesIndex = painterKey.index; seriesRenderer._storeSeriesProperties(chartState, seriesIndex); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; final Rect axisClipRect = _calculatePlotOffset( chartState._chartAxis._axisClipRect, @@ -45,7 +46,7 @@ class _BubbleChartPainter extends CustomPainter { canvas.clipRect(axisClipRect); int segmentIndex = -1; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -62,7 +63,7 @@ class _BubbleChartPainter extends CustomPainter { } canvas.restore(); if ((series.animationDuration <= 0 || - (!chartState._initialRender && + (!chartState._initialRender! && !seriesRenderer._needAnimateSeriesElements) || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart index 9d6cceddd..4966bf59e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart @@ -2,12 +2,12 @@ part of charts; class _CandlePainter extends CustomPainter { _CandlePainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -21,15 +21,16 @@ class _CandlePainter extends CustomPainter { /// Painter method for candle series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; Rect clipRect; double animationFactor; - final CandleSeries series = seriesRenderer._series; + final CandleSeries series = + seriesRenderer._series as CandleSeries; CartesianChartPoint point; - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { canvas.save(); assert( series.animationDuration != null @@ -44,23 +45,32 @@ class _CandlePainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; int segmentIndex = -1; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } + for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData(chartState, seriesRenderer, - painterKey.index, point, pointIndex, seriesRenderer.sideBySideInfo); + if (_withInRange(seriesRenderer._dataPoints[pointIndex].xValue, + seriesRenderer._xAxisRenderer!._visibleRange!)) { + seriesRenderer._calculateRegionData( + chartState, + seriesRenderer, + painterKey.index, + point, + pointIndex, + seriesRenderer.sideBySideInfo); - if (point.isVisible && !point.isGap) { - seriesRenderer._drawSegment( - canvas, - seriesRenderer._createSegments( - point, segmentIndex += 1, painterKey.index, animationFactor)); + if (point.isVisible && !point.isGap) { + seriesRenderer._drawSegment( + canvas, + seriesRenderer._createSegments(point, segmentIndex += 1, + painterKey.index, animationFactor)); + } } } clipRect = _calculatePlotOffset( @@ -86,7 +96,7 @@ class _CandlePainter extends CustomPainter { seriesRenderer._renderSeriesElements( chart, canvas, seriesRenderer._seriesElementAnimation); } - if (seriesRenderer._visible && animationFactor >= 1) { + if (seriesRenderer._visible! && animationFactor >= 1) { chartState._setPainterKey(painterKey.index, painterKey.name, true); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart index bd0825b1e..050c10160 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart @@ -2,12 +2,12 @@ part of charts; class _ColumnChartPainter extends CustomPainter { _ColumnChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -21,12 +21,13 @@ class _ColumnChartPainter extends CustomPainter { /// Painter method for column series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; - final ColumnSeries series = seriesRenderer._series; - if (seriesRenderer._visible) { + final ColumnSeries series = + seriesRenderer._series as ColumnSeries; + if (seriesRenderer._visible!) { Rect axisClipRect, clipRect; double animationFactor; CartesianChartPoint point; @@ -44,11 +45,11 @@ class _ColumnChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; int segmentIndex = -1; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -76,7 +77,7 @@ class _ColumnChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - (!chartState._initialRender && + (!chartState._initialRender! && !seriesRenderer._needAnimateSeriesElements) || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart index d0b5ef152..1a8db705a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart @@ -2,12 +2,12 @@ part of charts; class _FastLineChartPainter extends CustomPainter { _FastLineChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -15,8 +15,6 @@ class _FastLineChartPainter extends CustomPainter { final bool isRepaint; final AnimationController animationController; final FastLineSeriesRenderer seriesRenderer; - Path path; - Paint pathPaint; final _PainterKey painterKey; /// Painter method for fast line series @@ -24,11 +22,12 @@ class _FastLineChartPainter extends CustomPainter { void paint(Canvas canvas, Size size) { Rect clipRect; double animationFactor; - final FastLineSeries series = seriesRenderer._series; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final FastLineSeries series = + seriesRenderer._series as FastLineSeries; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List _points = []; - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { canvas.save(); assert( series.animationDuration != null @@ -38,7 +37,7 @@ class _FastLineChartPainter extends CustomPainter { final int seriesIndex = painterKey.index; seriesRenderer._storeSeriesProperties(chartState, seriesIndex); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; final Rect axisClipRect = _calculatePlotOffset( chartState._chartAxis._axisClipRect, @@ -47,22 +46,22 @@ class _FastLineChartPainter extends CustomPainter { canvas.clipRect(axisClipRect); if (seriesRenderer._reAnimate || (series.animationDuration > 0 && - !seriesRenderer._chartState._isLegendToggled)) { + !seriesRenderer._chartState!._isLegendToggled)) { seriesRenderer._needAnimateSeriesElements = seriesRenderer._needsAnimation; _performLinearAnimation( chartState, xAxisRenderer._axis, canvas, animationFactor); } - CartesianChartPoint prevPoint, point; + CartesianChartPoint? prevPoint, point; _ChartLocation currentLocation; - final _VisibleRange xVisibleRange = xAxisRenderer._visibleRange; - final _VisibleRange yVisibleRange = yAxisRenderer._visibleRange; + final _VisibleRange xVisibleRange = xAxisRenderer._visibleRange!; + final _VisibleRange yVisibleRange = yAxisRenderer._visibleRange!; final List> seriesPoints = seriesRenderer._dataPoints; assert(seriesPoints.isNotEmpty, 'The data points should be available to render fast line series.'); final Rect areaBounds = - seriesRenderer._chartState._chartAxis._axisClipRect; + seriesRenderer._chartState!._chartAxis._axisClipRect; final num xTolerance = (xVisibleRange.delta / areaBounds.width).abs(); final num yTolerance = (yVisibleRange.delta / areaBounds.height).abs(); num prevXValue = (seriesPoints.isNotEmpty && @@ -84,7 +83,7 @@ class _FastLineChartPainter extends CustomPainter { ///Eliminating nearest points CartesianChartPoint currentPoint; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; @@ -105,25 +104,25 @@ class _FastLineChartPainter extends CustomPainter { yVal, xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, series, areaBounds); _points.add(Offset(currentLocation.x, currentLocation.y)); if (prevPoint == null) { - seriesRenderer._segmentPath + seriesRenderer._segmentPath! .moveTo(currentLocation.x, currentLocation.y); } else if (seriesRenderer._dataPoints[pointIndex - 1].isVisible == false && series.emptyPointSettings.mode == EmptyPointMode.gap) { - seriesRenderer._segmentPath + seriesRenderer._segmentPath! .moveTo(currentLocation.x, currentLocation.y); } else if (point.isGap != true && seriesRenderer._dataPoints[pointIndex - 1].isGap != true && seriesRenderer._dataPoints[pointIndex].isVisible == true) { - seriesRenderer._segmentPath + seriesRenderer._segmentPath! .lineTo(currentLocation.x, currentLocation.y); } else { - seriesRenderer._segmentPath + seriesRenderer._segmentPath! .moveTo(currentLocation.x, currentLocation.y); } prevPoint = point; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart index ee69d3170..04d1794b9 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart @@ -2,12 +2,12 @@ part of charts; class _HiloPainter extends CustomPainter { _HiloPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -21,15 +21,16 @@ class _HiloPainter extends CustomPainter { /// Painter method for Hilo series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; Rect clipRect; double animationFactor; - final HiloSeries series = seriesRenderer._series; + final HiloSeries series = + seriesRenderer._series as HiloSeries; CartesianChartPoint point; - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { canvas.save(); assert( series.animationDuration != null @@ -44,12 +45,12 @@ class _HiloPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; int segmentIndex = -1; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -90,7 +91,7 @@ class _HiloPainter extends CustomPainter { seriesRenderer._renderSeriesElements( chart, canvas, seriesRenderer._seriesElementAnimation); } - if (seriesRenderer._visible && animationFactor >= 1) { + if (seriesRenderer._visible! && animationFactor >= 1) { chartState._setPainterKey(painterKey.index, painterKey.name, true); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart index aca50a9ff..192338d0f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart @@ -2,12 +2,12 @@ part of charts; class _HiloOpenClosePainter extends CustomPainter { _HiloOpenClosePainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -24,12 +24,13 @@ class _HiloOpenClosePainter extends CustomPainter { Rect clipRect; double animationFactor; CartesianChartPoint point; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; - final HiloOpenCloseSeries series = seriesRenderer._series; - if (seriesRenderer._visible) { + final HiloOpenCloseSeries series = + seriesRenderer._series as HiloOpenCloseSeries; + if (seriesRenderer._visible!) { canvas.save(); assert( series.animationDuration != null @@ -44,12 +45,12 @@ class _HiloOpenClosePainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; int segmentIndex = -1; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -90,7 +91,7 @@ class _HiloOpenClosePainter extends CustomPainter { seriesRenderer._renderSeriesElements( chart, canvas, seriesRenderer._seriesElementAnimation); } - if (seriesRenderer._visible && animationFactor >= 1) { + if (seriesRenderer._visible! && animationFactor >= 1) { chartState._setPainterKey(painterKey.index, painterKey.name, true); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart index 220ce34de..d57d6e32d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart @@ -2,13 +2,13 @@ part of charts; class _HistogramChartPainter extends CustomPainter { _HistogramChartPainter( - {this.chartState, - this.seriesRenderer, - this.chartSeries, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.chartSeries, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -26,14 +26,15 @@ class _HistogramChartPainter extends CustomPainter { Rect axisClipRect, clipRect; double animationFactor; CartesianChartPoint point; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; /// Clip rect added - if (seriesRenderer._visible) { - final HistogramSeries series = seriesRenderer._series; + if (seriesRenderer._visible!) { + final HistogramSeries series = + seriesRenderer._series as HistogramSeries; canvas.save(); assert( series.animationDuration != null @@ -48,14 +49,14 @@ class _HistogramChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; /// side by side range calculated int segmentIndex = -1; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -68,7 +69,7 @@ class _HistogramChartPainter extends CustomPainter { seriesRenderer._createSegments( point, segmentIndex += 1, - seriesRenderer.sideBySideInfo, + seriesRenderer.sideBySideInfo!, painterKey.index, animationFactor)); } @@ -89,7 +90,7 @@ class _HistogramChartPainter extends CustomPainter { ..style = PaintingStyle.stroke; series.curveDashArray == null ? canvas.drawPath(_path, _paint) - : _drawDashedLine(canvas, series.curveDashArray, _paint, _path); + : _drawDashedLine(canvas, series.curveDashArray!, _paint, _path); } clipRect = _calculatePlotOffset( Rect.fromLTRB( @@ -105,7 +106,7 @@ class _HistogramChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - !chartState._initialRender || + !chartState._initialRender! || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart index fe399a5ad..be411fad7 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart @@ -2,12 +2,12 @@ part of charts; class _LineChartPainter extends CustomPainter { _LineChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -22,12 +22,13 @@ class _LineChartPainter extends CustomPainter { void paint(Canvas canvas, Size size) { double animationFactor; Rect clipRect; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; - final LineSeries series = seriesRenderer._series; - if (seriesRenderer._visible) { + final LineSeries series = + seriesRenderer._series as LineSeries; + if (seriesRenderer._visible!) { assert( series.animationDuration != null ? series.animationDuration >= 0 @@ -37,7 +38,7 @@ class _LineChartPainter extends CustomPainter { final int seriesIndex = painterKey.index; seriesRenderer._storeSeriesProperties(chartState, seriesIndex); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; final Rect axisClipRect = _calculatePlotOffset( chartState._chartAxis._axisClipRect, @@ -52,13 +53,13 @@ class _LineChartPainter extends CustomPainter { chartState, xAxisRenderer._axis, canvas, animationFactor); } int segmentIndex = -1; - CartesianChartPoint currentPoint, + CartesianChartPoint? currentPoint, _nextPoint, startPoint, endPoint; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -101,7 +102,7 @@ class _LineChartPainter extends CustomPainter { canvas.restore(); if ((series.animationDuration <= 0 || - (!chartState._initialRender && + (!chartState._initialRender! && !seriesRenderer._needAnimateSeriesElements) || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart index c749b0e1b..73264e05a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart @@ -2,12 +2,12 @@ part of charts; class _RangeAreaChartPainter extends CustomPainter { _RangeAreaChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - this.painterKey, - ValueNotifier notifier}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required this.painterKey, + required ValueNotifier notifier}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -20,22 +20,26 @@ class _RangeAreaChartPainter extends CustomPainter { /// Painter method for range area series @override void paint(Canvas canvas, Size size) { - final RangeAreaSeries series = seriesRenderer._series; + final RangeAreaSeries series = + seriesRenderer._series as RangeAreaSeries; Rect clipRect; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; - CartesianSeriesRenderer oldSeriesRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; + CartesianSeriesRenderer? oldSeriesRenderer; final List> dataPoints = seriesRenderer._dataPoints; - CartesianChartPoint point, prevPoint, oldPoint; + CartesianChartPoint? point, prevPoint, oldPoint; final Path _path = Path(); - _ChartLocation currentPointLow, currentPointHigh, oldPointLow, oldPointHigh; - num currentLowX, currentLowY, currentHighX, currentHighY; + _ChartLocation? currentPointLow, + currentPointHigh, + oldPointLow, + oldPointHigh; + double currentLowX, currentLowY, currentHighX, currentHighY; double animationFactor; final Path _borderPath = Path(); RangeAreaSegment rangeAreaSegment; final List _points = []; - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { assert( series.animationDuration != null ? series.animationDuration >= 0 @@ -57,7 +61,7 @@ class _RangeAreaChartPainter extends CustomPainter { chartState, seriesRenderer, seriesIndex, oldSeriesRenderers); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; if (seriesRenderer._reAnimate || ((!(chartState._widgetNeedUpdate || chartState._isLegendToggled) || @@ -67,7 +71,7 @@ class _RangeAreaChartPainter extends CustomPainter { chartState, xAxisRenderer._axis, canvas, animationFactor); } if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -87,16 +91,16 @@ class _RangeAreaChartPainter extends CustomPainter { oldPointLow = _calculatePoint( oldPoint.xValue, oldPoint.low, - oldSeriesRenderer._xAxisRenderer, - oldSeriesRenderer._yAxisRenderer, + oldSeriesRenderer!._xAxisRenderer!, + oldSeriesRenderer._yAxisRenderer!, isTransposed, oldSeriesRenderer._series, axisClipRect); oldPointHigh = _calculatePoint( oldPoint.xValue, oldPoint.high, - oldSeriesRenderer._xAxisRenderer, - oldSeriesRenderer._yAxisRenderer, + oldSeriesRenderer._xAxisRenderer!, + oldSeriesRenderer._yAxisRenderer!, isTransposed, oldSeriesRenderer._series, axisClipRect); @@ -110,10 +114,10 @@ class _RangeAreaChartPainter extends CustomPainter { _points.add(Offset(currentPointLow.x, currentPointLow.y)); _points.add(Offset(currentPointHigh.x, currentPointHigh.y)); - currentLowX = currentPointLow?.x; - currentLowY = currentPointLow?.y; - currentHighX = currentPointHigh?.x; - currentHighY = currentPointHigh?.y; + currentLowX = currentPointLow.x; + currentLowY = currentPointLow.y; + currentHighX = currentPointHigh.x; + currentHighY = currentPointHigh.y; if (oldPointLow != null) { if (chart.isTransposed) { currentLowX = _getAnimateValue(animationFactor, currentLowX, @@ -174,16 +178,16 @@ class _RangeAreaChartPainter extends CustomPainter { oldPointLow = _calculatePoint( oldPoint.xValue, oldPoint.low, - oldSeriesRenderer._xAxisRenderer, - oldSeriesRenderer._yAxisRenderer, + oldSeriesRenderer!._xAxisRenderer!, + oldSeriesRenderer._yAxisRenderer!, isTransposed, oldSeriesRenderer._series, axisClipRect); oldPointHigh = _calculatePoint( oldPoint.xValue, oldPoint.high, - oldSeriesRenderer._xAxisRenderer, - oldSeriesRenderer._yAxisRenderer, + oldSeriesRenderer._xAxisRenderer!, + oldSeriesRenderer._yAxisRenderer!, isTransposed, oldSeriesRenderer._series, axisClipRect); @@ -195,10 +199,10 @@ class _RangeAreaChartPainter extends CustomPainter { currentPointHigh = _calculatePoint(point.xValue, point.high, xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); - currentLowX = currentPointLow?.x; - currentLowY = currentPointLow?.y; - currentHighX = currentPointHigh?.x; - currentHighY = currentPointHigh?.y; + currentLowX = currentPointLow.x; + currentLowY = currentPointLow.y; + currentHighX = currentPointHigh.x; + currentHighY = currentPointHigh.y; if (oldPointLow != null) { if (chart.isTransposed) { @@ -238,7 +242,7 @@ class _RangeAreaChartPainter extends CustomPainter { if (_path != null && seriesRenderer._segments != null && seriesRenderer._segments.isNotEmpty) { - rangeAreaSegment = seriesRenderer._segments[0]; + rangeAreaSegment = seriesRenderer._segments[0] as RangeAreaSegment; seriesRenderer._drawSegment( canvas, rangeAreaSegment @@ -260,7 +264,7 @@ class _RangeAreaChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - !chartState._initialRender || + !chartState._initialRender! || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart index 58f416f79..23b8c2664 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart @@ -2,12 +2,12 @@ part of charts; class _RangeColumnChartPainter extends CustomPainter { _RangeColumnChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -21,12 +21,13 @@ class _RangeColumnChartPainter extends CustomPainter { /// Painter method for range column series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; - final RangeColumnSeries series = seriesRenderer._series; - if (seriesRenderer._visible) { + final RangeColumnSeries series = + seriesRenderer._series as RangeColumnSeries; + if (seriesRenderer._visible!) { assert( series.animationDuration != null ? series.animationDuration >= 0 @@ -44,12 +45,12 @@ class _RangeColumnChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; int segmentIndex = -1; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -77,7 +78,7 @@ class _RangeColumnChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - (!chartState._initialRender && + (!chartState._initialRender! && !seriesRenderer._needAnimateSeriesElements) || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart index 1736bba09..ad90d27ea 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart @@ -2,12 +2,12 @@ part of charts; class _ScatterChartPainter extends CustomPainter { _ScatterChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -22,19 +22,20 @@ class _ScatterChartPainter extends CustomPainter { void paint(Canvas canvas, Size size) { canvas.save(); double animationFactor; - final ScatterSeries series = seriesRenderer._series; - if (seriesRenderer._visible) { + final ScatterSeries series = + seriesRenderer._series as ScatterSeries; + if (seriesRenderer._visible!) { assert( series.animationDuration != null ? series.animationDuration >= 0 : true, 'The animation duration of the scatter series must be greater or equal to 0.'); - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; final Rect axisClipRect = _calculatePlotOffset( chartState._chartAxis._axisClipRect, @@ -45,7 +46,7 @@ class _ScatterChartPainter extends CustomPainter { seriesRenderer._storeSeriesProperties(chartState, seriesIndex); int segmentIndex = -1; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart index ffb3c6495..7bf4aa7c8 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart @@ -2,12 +2,12 @@ part of charts; class _SplineAreaChartPainter extends CustomPainter { _SplineAreaChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -21,21 +21,22 @@ class _SplineAreaChartPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { double animationFactor; - CartesianChartPoint prevPoint, point, oldChartPoint; - CartesianSeriesRenderer oldSeriesRenderer; - _ChartLocation currentPoint, originPoint, oldPointLocation; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + CartesianChartPoint? prevPoint, point, oldChartPoint; + CartesianSeriesRenderer? oldSeriesRenderer; + _ChartLocation? currentPoint, originPoint, oldPointLocation; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; Rect clipRect; - final SplineAreaSeries series = seriesRenderer._series; - seriesRenderer?._drawControlPoints?.clear(); + final SplineAreaSeries series = + seriesRenderer._series as SplineAreaSeries; + seriesRenderer._drawControlPoints.clear(); final Path _path = Path(); final Path _strokePath = Path(); final List _points = []; - final num crossesAt = _getCrossesAtValue(seriesRenderer, chartState); + final num? crossesAt = _getCrossesAtValue(seriesRenderer, chartState); final num origin = crossesAt ?? 0; - num startControlX, startControlY, endControlX, endControlY; - if (seriesRenderer._visible) { + double? startControlX, startControlY, endControlX, endControlY; + if (seriesRenderer._visible!) { assert( series.animationDuration != null ? series.animationDuration >= 0 @@ -48,14 +49,15 @@ class _SplineAreaChartPainter extends CustomPainter { canvas.save(); final int seriesIndex = painterKey.index; seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - final bool isTransposed = seriesRenderer._chartState._requireInvertedAxis; + final bool isTransposed = + seriesRenderer._chartState!._requireInvertedAxis; final Rect axisClipRect = _calculatePlotOffset( chartState._chartAxis._axisClipRect, Offset( xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; oldSeriesRenderer = _getOldSeriesRenderer( @@ -73,7 +75,7 @@ class _SplineAreaChartPainter extends CustomPainter { } if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -94,8 +96,8 @@ class _SplineAreaChartPainter extends CustomPainter { ? _calculatePoint( oldChartPoint.xValue, oldChartPoint.yValue, - oldSeriesRenderer._xAxisRenderer, - oldSeriesRenderer._yAxisRenderer, + oldSeriesRenderer!._xAxisRenderer!, + oldSeriesRenderer._yAxisRenderer!, isTransposed, oldSeriesRenderer._series, axisClipRect) @@ -104,14 +106,14 @@ class _SplineAreaChartPainter extends CustomPainter { xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); originPoint = _calculatePoint( point.xValue, - math_lib.max(yAxisRenderer._visibleRange.minimum, origin), + math_lib.max(yAxisRenderer._visibleRange!.minimum, origin), xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); - num x = currentPoint.x; - num y = currentPoint.y; + double x = currentPoint.x; + double y = currentPoint.y; startControlX = startControlY = endControlX = endControlY = null; _points.add(Offset(currentPoint.x, currentPoint.y)); final bool closed = @@ -132,38 +134,38 @@ class _SplineAreaChartPainter extends CustomPainter { startControlY = _getAnimateValue( animationFactor, startControlY, - oldChartPoint.startControl.y, - point.startControl.y, + oldChartPoint!.startControl!.y, + point.startControl!.y, seriesRenderer); startControlX = _getAnimateValue( animationFactor, startControlX, - oldChartPoint.startControl.x, - point.startControl.x, + oldChartPoint.startControl!.x, + point.startControl!.x, seriesRenderer); } if (point.endControl != null) { endControlX = _getAnimateValue( animationFactor, endControlX, - oldChartPoint.endControl.x, - point.endControl.x, + oldChartPoint!.endControl!.x, + point.endControl!.x, seriesRenderer); endControlY = _getAnimateValue( animationFactor, endControlY, - oldChartPoint.endControl.y, - point.endControl.y, + oldChartPoint.endControl!.y, + point.endControl!.y, seriesRenderer); } } else { if (point.startControl != null) { - startControlX = point.startControl.x; - startControlY = point.startControl.y; + startControlX = point.startControl!.x; + startControlY = point.startControl!.y; } if (point.endControl != null) { - endControlX = point.endControl.x; - endControlY = point.endControl.y; + endControlX = point.endControl!.x; + endControlY = point.endControl!.y; } } @@ -185,8 +187,8 @@ class _SplineAreaChartPainter extends CustomPainter { _path.lineTo(x, y); } else if (pointIndex == dataPoints.length - 1 || dataPoints[pointIndex + 1].isGap == true) { - _strokePath.cubicTo( - startControlX, startControlY, endControlX, endControlY, x, y); + _strokePath.cubicTo(startControlX!, startControlY!, endControlX!, + endControlY!, x, y); if (series.borderDrawMode == BorderDrawMode.excludeBottom) { _strokePath.lineTo(originPoint.x, originPoint.y); } else if (series.borderDrawMode == BorderDrawMode.all) { @@ -197,8 +199,8 @@ class _SplineAreaChartPainter extends CustomPainter { startControlX, startControlY, endControlX, endControlY, x, y); _path.lineTo(originPoint.x, originPoint.y); } else { - _strokePath.cubicTo( - startControlX, startControlY, endControlX, endControlY, x, y); + _strokePath.cubicTo(startControlX!, startControlY!, endControlX!, + endControlY!, x, y); _path.cubicTo( startControlX, startControlY, endControlX, endControlY, x, y); @@ -239,7 +241,7 @@ class _SplineAreaChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - !chartState._initialRender || + !chartState._initialRender! || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart index b82f05e01..5267ce463 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart @@ -2,12 +2,12 @@ part of charts; class _SplineChartPainter extends CustomPainter { _SplineChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -22,15 +22,16 @@ class _SplineChartPainter extends CustomPainter { void paint(Canvas canvas, Size size) { Rect clipRect; double animationFactor; - final SplineSeries series = seriesRenderer._series; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final SplineSeries series = + seriesRenderer._series as SplineSeries; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; - seriesRenderer?._drawControlPoints?.clear(); + seriesRenderer._drawControlPoints.clear(); /// Clip rect will be added for series. - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { assert( series.animationDuration != null ? series.animationDuration >= 0 @@ -40,7 +41,7 @@ class _SplineChartPainter extends CustomPainter { final int seriesIndex = painterKey.index; seriesRenderer._storeSeriesProperties(chartState, seriesIndex); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; final Rect axisClipRect = _calculatePlotOffset( chartState._chartAxis._axisClipRect, @@ -60,35 +61,47 @@ class _SplineChartPainter extends CustomPainter { int segmentIndex = -1; - CartesianChartPoint point, _nextPoint, startPoint, endPoint; + CartesianChartPoint? point, _nextPoint, startPoint, endPoint; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } ///Draw spline for spline series for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, painterKey.index, point, pointIndex); - if ((point.isVisible && !point.isGap) && startPoint == null) { - startPoint = point; - } - if (pointIndex + 1 < dataPoints.length) { - _nextPoint = dataPoints[pointIndex + 1]; - if (startPoint != null && _nextPoint.isVisible && _nextPoint.isGap) { - startPoint = null; - } else if (_nextPoint.isVisible && !_nextPoint.isGap) { - endPoint = _nextPoint; + if (_withInRange(seriesRenderer._dataPoints[pointIndex].xValue, + seriesRenderer._xAxisRenderer!._visibleRange!) || + (pointIndex < dataPoints.length - 1 + ? _withInRange( + seriesRenderer._dataPoints[pointIndex + 1].xValue, + seriesRenderer._xAxisRenderer!._visibleRange!) + : false)) { + seriesRenderer._calculateRegionData( + chartState, seriesRenderer, painterKey.index, point, pointIndex); + if ((point.isVisible && !point.isGap) && startPoint == null) { + startPoint = point; + } + if (pointIndex + 1 < dataPoints.length) { + _nextPoint = dataPoints[pointIndex + 1]; + if (startPoint != null && + _nextPoint.isVisible && + _nextPoint.isGap) { + startPoint = null; + } else if (_nextPoint.isVisible && !_nextPoint.isGap) { + endPoint = _nextPoint; + } + } + if (startPoint != null && + endPoint != null && + startPoint != endPoint) { + seriesRenderer._drawSegment( + canvas, + seriesRenderer._createSegments(startPoint, endPoint, + segmentIndex += 1, seriesIndex, animationFactor)); + endPoint = startPoint = null; } - } - if (startPoint != null && endPoint != null && startPoint != endPoint) { - seriesRenderer._drawSegment( - canvas, - seriesRenderer._createSegments(startPoint, endPoint, - segmentIndex += 1, seriesIndex, animationFactor)); - endPoint = startPoint = null; } } clipRect = _calculatePlotOffset( @@ -106,7 +119,7 @@ class _SplineChartPainter extends CustomPainter { canvas.restore(); if ((series.animationDuration <= 0 || - (!chartState._initialRender && + (!chartState._initialRender! && !seriesRenderer._needAnimateSeriesElements) || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart index aa1f7e7e5..4c8181600 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart @@ -2,12 +2,12 @@ part of charts; class _SplineRangeAreaChartPainter extends CustomPainter { _SplineRangeAreaChartPainter( - {this.chartState, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey, - this.seriesRenderer}) + {required this.chartState, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey, + required this.seriesRenderer}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -22,20 +22,23 @@ class _SplineRangeAreaChartPainter extends CustomPainter { void paint(Canvas canvas, Size size) { Rect clipRect; double animationFactor; - _ChartLocation currentPointLow, currentPointHigh, oldPointLow, oldPointHigh; + _ChartLocation? currentPointLow, + currentPointHigh, + oldPointLow, + oldPointHigh; final int pointsLength = seriesRenderer._dataPoints.length; - CartesianChartPoint prevPoint, point, oldChartPoint; + CartesianChartPoint? prevPoint, point, oldChartPoint; final Path _path = Path(); final Path _strokePath = Path(); final List> dataPoints = seriesRenderer._dataPoints; - CartesianSeriesRenderer oldSeriesRenderer; + CartesianSeriesRenderer? oldSeriesRenderer; final List _points = []; - final ChartAxisRenderer xAxisRenderer = seriesRenderer?._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer?._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List oldSeriesRenderers = chartState._oldSeriesRenderers; - num currentPointLowX, + double? currentPointLowX, currentPointLowY, currentPointHighX, currentPointHighY, @@ -43,11 +46,11 @@ class _SplineRangeAreaChartPainter extends CustomPainter { startControlY, endControlX, endControlY; - seriesRenderer?._drawHighControlPoints?.clear(); - seriesRenderer?._drawLowControlPoints?.clear(); + seriesRenderer._drawHighControlPoints.clear(); + seriesRenderer._drawLowControlPoints.clear(); /// Clip rect will be added for series. - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { canvas.save(); final int seriesIndex = painterKey.index; @@ -57,7 +60,7 @@ class _SplineRangeAreaChartPainter extends CustomPainter { seriesRenderer._storeSeriesProperties(chartState, seriesIndex); final bool isTransposed = chartState._requireInvertedAxis; final SplineRangeAreaSeries series = - seriesRenderer._series; + seriesRenderer._series as SplineRangeAreaSeries; assert( series.animationDuration != null ? series.animationDuration >= 0 @@ -70,7 +73,7 @@ class _SplineRangeAreaChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; if (seriesRenderer._reAnimate || ((!(chartState._widgetNeedUpdate || chartState._isLegendToggled) || @@ -84,7 +87,7 @@ class _SplineRangeAreaChartPainter extends CustomPainter { } if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < pointsLength; pointIndex++) { @@ -104,8 +107,8 @@ class _SplineRangeAreaChartPainter extends CustomPainter { oldPointHigh = _calculatePoint( oldChartPoint.xValue, oldChartPoint.high, - oldSeriesRenderer._xAxisRenderer, - oldSeriesRenderer._yAxisRenderer, + oldSeriesRenderer!._xAxisRenderer!, + oldSeriesRenderer._yAxisRenderer!, isTransposed, series, axisClipRect); @@ -144,14 +147,14 @@ class _SplineRangeAreaChartPainter extends CustomPainter { startControlX = _getAnimateValue( animationFactor, startControlX, - oldChartPoint.highStartControl.x, - point.highStartControl.x, + oldChartPoint!.highStartControl!.x, + point.highStartControl!.x, seriesRenderer); startControlY = _getAnimateValue( animationFactor, startControlY, - oldChartPoint.highStartControl.y, - point.highStartControl.y, + oldChartPoint.highStartControl!.y, + point.highStartControl!.y, seriesRenderer); } else { startControlX = startControlY = null; @@ -160,21 +163,21 @@ class _SplineRangeAreaChartPainter extends CustomPainter { endControlX = _getAnimateValue( animationFactor, endControlX, - oldChartPoint.highEndControl.x, - point.highEndControl.x, + oldChartPoint!.highEndControl!.x, + point.highEndControl!.x, seriesRenderer); endControlY = _getAnimateValue( animationFactor, endControlY, - oldChartPoint.highEndControl.y, - point.highEndControl.y, + oldChartPoint.highEndControl!.y, + point.highEndControl!.y, seriesRenderer); } else { endControlX = endControlY = null; } } else { - currentPointHighX = currentPointHigh?.x; - currentPointHighY = currentPointHigh?.y; + currentPointHighX = currentPointHigh.x; + currentPointHighY = currentPointHigh.y; startControlX = point.highStartControl?.x; startControlY = point.highStartControl?.y; endControlX = point.highEndControl?.x; @@ -191,8 +194,8 @@ class _SplineRangeAreaChartPainter extends CustomPainter { _strokePath.moveTo(currentPointHighX, currentPointHighY); } else if (pointIndex == dataPoints.length - 1 || dataPoints[pointIndex + 1].isGap == true) { - _path.cubicTo(startControlX, startControlY, endControlX, - endControlY, currentPointHighX, currentPointHighY); + _path.cubicTo(startControlX!, startControlY!, endControlX!, + endControlY!, currentPointHighX, currentPointHighY); _strokePath.cubicTo(startControlX, startControlY, endControlX, endControlY, currentPointHighX, currentPointHighY); @@ -202,8 +205,8 @@ class _SplineRangeAreaChartPainter extends CustomPainter { _strokePath.lineTo(currentPointHighX, currentPointHighY); _strokePath.moveTo(currentPointLowX, currentPointLowY); } else { - _path.cubicTo(startControlX, startControlY, endControlX, - endControlY, currentPointHighX, currentPointHighY); + _path.cubicTo(startControlX!, startControlY!, endControlX!, + endControlY!, currentPointHighX, currentPointHighY); _strokePath.cubicTo(startControlX, startControlY, endControlX, endControlY, currentPointHighX, currentPointHighY); @@ -234,8 +237,8 @@ class _SplineRangeAreaChartPainter extends CustomPainter { oldPointLow = _calculatePoint( oldChartPoint.xValue, oldChartPoint.low, - oldSeriesRenderer._xAxisRenderer, - oldSeriesRenderer._yAxisRenderer, + oldSeriesRenderer!._xAxisRenderer!, + oldSeriesRenderer._yAxisRenderer!, isTransposed, series, axisClipRect); @@ -269,14 +272,14 @@ class _SplineRangeAreaChartPainter extends CustomPainter { startControlX = _getAnimateValue( animationFactor, startControlX, - oldChartPoint.lowStartControl.x, - point.lowStartControl.x, + oldChartPoint!.lowStartControl!.x, + point.lowStartControl!.x, seriesRenderer); startControlY = _getAnimateValue( animationFactor, startControlY, - oldChartPoint.lowStartControl.y, - point.lowStartControl.y, + oldChartPoint.lowStartControl!.y, + point.lowStartControl!.y, seriesRenderer); } else { startControlX = startControlY = null; @@ -285,21 +288,21 @@ class _SplineRangeAreaChartPainter extends CustomPainter { endControlX = _getAnimateValue( animationFactor, endControlX, - oldChartPoint.lowEndControl.x, - point.lowEndControl.x, + oldChartPoint!.lowEndControl!.x, + point.lowEndControl!.x, seriesRenderer); endControlY = _getAnimateValue( animationFactor, endControlY, - oldChartPoint.lowEndControl.y, - point.lowEndControl.y, + oldChartPoint.lowEndControl!.y, + point.lowEndControl!.y, seriesRenderer); } else { endControlX = endControlY = null; } } else { - currentPointLowX = currentPointLow?.x; - currentPointLowY = currentPointLow?.y; + currentPointLowX = currentPointLow.x; + currentPointLowY = currentPointLow.y; startControlX = point.lowStartControl?.x; startControlY = point.lowStartControl?.y; endControlX = point.lowEndControl?.x; @@ -314,11 +317,11 @@ class _SplineRangeAreaChartPainter extends CustomPainter { dataPoints[pointIndex + 1].isDrop) { _strokePath.moveTo(currentPointLowX, currentPointLowY); } else { - _strokePath.cubicTo(endControlX, endControlY, startControlX, - startControlY, currentPointLowX, currentPointLowY); + _strokePath.cubicTo(endControlX!, endControlY!, startControlX!, + startControlY!, currentPointLowX, currentPointLowY); } - _path.cubicTo(endControlX, endControlY, startControlX, - startControlY, currentPointLowX, currentPointLowY); + _path.cubicTo(endControlX!, endControlY!, startControlX!, + startControlY!, currentPointLowX, currentPointLowY); } } } @@ -327,7 +330,8 @@ class _SplineRangeAreaChartPainter extends CustomPainter { if (_path != null && seriesRenderer._segments != null && seriesRenderer._segments.isNotEmpty) { - splineRangeAreaSegment = seriesRenderer._segments[0]; + splineRangeAreaSegment = + seriesRenderer._segments[0] as SplineRangeAreaSegment; seriesRenderer._drawSegment( canvas, splineRangeAreaSegment @@ -349,7 +353,7 @@ class _SplineRangeAreaChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - (!chartState._initialRender && + (!chartState._initialRender! && !seriesRenderer._needAnimateSeriesElements) || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_area_painter.dart index ee1671f2e..b6ae4e754 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_area_painter.dart @@ -2,12 +2,12 @@ part of charts; class _StackedAreaChartPainter extends CustomPainter { _StackedAreaChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - this.painterKey, - ValueNotifier notifier}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required this.painterKey, + required ValueNotifier notifier}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -24,7 +24,7 @@ class _StackedAreaChartPainter extends CustomPainter { canvas, seriesRenderer, chartState, - seriesRenderer._seriesAnimation, + seriesRenderer._seriesAnimation!, seriesRenderer._seriesElementAnimation, painterKey); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_bar_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_bar_painter.dart index cb8dd2da3..80c6b1d96 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_bar_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_bar_painter.dart @@ -2,25 +2,25 @@ part of charts; class _StackedBarChartPainter extends CustomPainter { _StackedBarChartPainter({ - this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - this.painterKey, - ValueNotifier notifier, - }) : chart = chartState._chart, + required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required this.painterKey, + required ValueNotifier notifier, + }) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; final SfCartesianChart chart; - CartesianChartPoint point; + CartesianChartPoint? point; final bool isRepaint; final AnimationController animationController; List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; final StackedBarSeriesRenderer seriesRenderer; final _PainterKey painterKey; - //ignore: unused_field - static double animation; + // ignore: unused_field + // static double animation; /// Painter method for stacked bar series @override diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_column_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_column_painter.dart index 7be891085..6e4912b0e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_column_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_column_painter.dart @@ -2,17 +2,17 @@ part of charts; class _StackedColummnChartPainter extends CustomPainter { _StackedColummnChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; final SfCartesianChart chart; - CartesianChartPoint point; + CartesianChartPoint? point; final bool isRepaint; final AnimationController animationController; List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_line_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_line_painter.dart index 74ddce764..1be7f8d78 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_line_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_line_painter.dart @@ -2,12 +2,12 @@ part of charts; class _StackedLineChartPainter extends CustomPainter { _StackedLineChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -20,8 +20,13 @@ class _StackedLineChartPainter extends CustomPainter { /// Painter method for stacked line series @override void paint(Canvas canvas, Size size) { - _stackedLinePainter(canvas, seriesRenderer, seriesRenderer._seriesAnimation, - chartState, seriesRenderer._seriesElementAnimation, painterKey); + _stackedLinePainter( + canvas, + seriesRenderer, + seriesRenderer._seriesAnimation!, + chartState, + seriesRenderer._seriesElementAnimation, + painterKey); } @override diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedarea100_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedarea100_painter.dart index 4fe927d2d..a8a21e725 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedarea100_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedarea100_painter.dart @@ -2,12 +2,12 @@ part of charts; class _StackedArea100ChartPainter extends CustomPainter { _StackedArea100ChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - this.painterKey, - ValueNotifier notifier}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required this.painterKey, + required ValueNotifier notifier}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -24,7 +24,7 @@ class _StackedArea100ChartPainter extends CustomPainter { canvas, seriesRenderer, chartState, - seriesRenderer._seriesAnimation, + seriesRenderer._seriesAnimation!, seriesRenderer._seriesElementAnimation, painterKey); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedbar100_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedbar100_painter.dart index 7ef263958..808185d22 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedbar100_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedbar100_painter.dart @@ -2,25 +2,25 @@ part of charts; class _StackedBar100ChartPainter extends CustomPainter { _StackedBar100ChartPainter({ - this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - this.painterKey, - ValueNotifier notifier, - }) : chart = chartState._chart, + required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required this.painterKey, + required ValueNotifier notifier, + }) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; final SfCartesianChart chart; - CartesianChartPoint point; + CartesianChartPoint? point; final bool isRepaint; final AnimationController animationController; List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; final StackedBar100SeriesRenderer seriesRenderer; final _PainterKey painterKey; //ignore: unused_field - static double animation; + // static double animation; /// Painter method for stacked bar 100 series @override diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedcolumn100_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedcolumn100_painter.dart index f32eb44d8..33394e810 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedcolumn100_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedcolumn100_painter.dart @@ -2,17 +2,17 @@ part of charts; class _StackedColumn100ChartPainter extends CustomPainter { _StackedColumn100ChartPainter({ - this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - this.painterKey, - ValueNotifier notifier, - }) : chart = chartState._chart, + required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required this.painterKey, + required ValueNotifier notifier, + }) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; final SfCartesianChart chart; - CartesianChartPoint point; + CartesianChartPoint? point; final bool isRepaint; final AnimationController animationController; List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedline100_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedline100_painter.dart index 0c3a0510c..e2b5ce39b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedline100_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedline100_painter.dart @@ -2,12 +2,12 @@ part of charts; class _StackedLine100ChartPainter extends CustomPainter { _StackedLine100ChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart index 9dfa51fef..78449456b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart @@ -2,12 +2,12 @@ part of charts; class _StepAreaChartPainter extends CustomPainter { _StepAreaChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -22,26 +22,27 @@ class _StepAreaChartPainter extends CustomPainter { void paint(Canvas canvas, Size size) { Rect clipRect; double animationFactor; - CartesianChartPoint prevPoint, point, oldChartPoint; - _ChartLocation currentPoint, + CartesianChartPoint? prevPoint, point, oldChartPoint; + _ChartLocation? currentPoint, originPoint, previousPoint, oldPoint, prevOldPoint; - CartesianSeriesRenderer oldSeriesRenderer; + CartesianSeriesRenderer? oldSeriesRenderer; final List oldSeriesRenderers = chartState._oldSeriesRenderers; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; - final StepAreaSeries _series = seriesRenderer._series; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; + final StepAreaSeries _series = + seriesRenderer._series as StepAreaSeries; final Path _path = Path(); final Path _strokePath = Path(); final List _points = []; - final num crossesAt = _getCrossesAtValue(seriesRenderer, chartState); + final num? crossesAt = _getCrossesAtValue(seriesRenderer, chartState); final num origin = crossesAt ?? 0; /// Clip rect will be added for series. - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { assert( _series.animationDuration != null ? _series.animationDuration >= 0 @@ -51,11 +52,12 @@ class _StepAreaChartPainter extends CustomPainter { final List> dataPoints = seriesRenderer._dataPoints; final int seriesIndex = painterKey.index; - final StepAreaSeries series = seriesRenderer._series; + final StepAreaSeries series = + seriesRenderer._series as StepAreaSeries; final Rect axisClipRect = _calculatePlotOffset( chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer._axis.plotOffset, - seriesRenderer._yAxisRenderer._axis.plotOffset)); + Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, + seriesRenderer._yAxisRenderer!._axis.plotOffset)); canvas.clipRect(axisClipRect); oldSeriesRenderer = _getOldSeriesRenderer( @@ -63,19 +65,19 @@ class _StepAreaChartPainter extends CustomPainter { seriesRenderer._storeSeriesProperties(chartState, seriesIndex); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; if (seriesRenderer._reAnimate || ((!(chartState._widgetNeedUpdate || chartState._isLegendToggled) || !chartState._oldSeriesKeys .contains(seriesRenderer._series.key)) && seriesRenderer._series.animationDuration > 0)) { - _performLinearAnimation(chartState, seriesRenderer._xAxisRenderer._axis, - canvas, animationFactor); + _performLinearAnimation(chartState, + seriesRenderer._xAxisRenderer!._axis, canvas, animationFactor); } if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -95,8 +97,8 @@ class _StepAreaChartPainter extends CustomPainter { ? _calculatePoint( oldChartPoint.xValue, oldChartPoint.yValue, - oldSeriesRenderer._xAxisRenderer, - oldSeriesRenderer._yAxisRenderer, + oldSeriesRenderer!._xAxisRenderer!, + oldSeriesRenderer._yAxisRenderer!, chart.isTransposed, oldSeriesRenderer._series, axisClipRect) @@ -106,25 +108,25 @@ class _StepAreaChartPainter extends CustomPainter { point.yValue, xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, series, axisClipRect); previousPoint = prevPoint != null ? _calculatePoint( - prevPoint?.xValue, - prevPoint?.yValue, + prevPoint.xValue, + prevPoint.yValue, xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, series, axisClipRect) - : prevPoint; + : null; originPoint = _calculatePoint( point.xValue, - math_lib.max(yAxisRenderer._visibleRange.minimum, origin), + math_lib.max(yAxisRenderer._visibleRange!.minimum, origin), xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, series, axisClipRect); _points.add(Offset(currentPoint.x, currentPoint.y)); @@ -162,11 +164,11 @@ class _StepAreaChartPainter extends CustomPainter { seriesRenderer._series.markerSettings.width, chartState._chartAxis._axisClipRect.bottom + seriesRenderer._series.markerSettings.height), - Offset(seriesRenderer._xAxisRenderer._axis.plotOffset, - seriesRenderer._yAxisRenderer._axis.plotOffset)); + Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, + seriesRenderer._yAxisRenderer!._axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - !chartState._initialRender || + !chartState._initialRender! || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { @@ -189,18 +191,18 @@ class _StepAreaChartPainter extends CustomPainter { void _drawStepAreaPath( Path _path, Path _strokePath, - CartesianChartPoint prevPoint, + CartesianChartPoint? prevPoint, _ChartLocation currentPoint, _ChartLocation originPoint, - _ChartLocation previousPoint, - _ChartLocation oldPoint, - _ChartLocation prevOldPoint, + _ChartLocation? previousPoint, + _ChartLocation? oldPoint, + _ChartLocation? prevOldPoint, int pointIndex, double animationFactor, StepAreaSeries stepAreaSeries) { - num x = currentPoint.x; - num y = currentPoint.y; - num previousPointY = previousPoint?.y; + double x = currentPoint.x; + double y = currentPoint.y; + double? previousPointY = previousPoint?.y; final bool closed = stepAreaSeries.emptyPointSettings.mode == EmptyPointMode.drop ? _getSeriesVisibility(seriesRenderer._dataPoints, pointIndex) @@ -212,8 +214,10 @@ class _StepAreaChartPainter extends CustomPainter { } else { y = _getAnimateValue( animationFactor, y, oldPoint.y, currentPoint.y, seriesRenderer); - previousPointY = _getAnimateValue(animationFactor, previousPointY, - prevOldPoint?.y, previousPoint?.y, seriesRenderer); + previousPointY = previousPointY != null + ? _getAnimateValue(animationFactor, previousPointY, prevOldPoint?.y, + previousPoint?.y, seriesRenderer) + : previousPointY; } } if (prevPoint == null || @@ -238,7 +242,7 @@ class _StepAreaChartPainter extends CustomPainter { _path.lineTo(x, y); } else if (pointIndex == seriesRenderer._dataPoints.length - 1 || seriesRenderer._dataPoints[pointIndex + 1].isGap == true) { - _strokePath.lineTo(x, previousPointY); + _strokePath.lineTo(x, previousPointY!); _strokePath.lineTo(x, y); if (stepAreaSeries.borderDrawMode == BorderDrawMode.excludeBottom) { _strokePath.lineTo(originPoint.x, originPoint.y); @@ -250,7 +254,7 @@ class _StepAreaChartPainter extends CustomPainter { _path.lineTo(x, y); _path.lineTo(originPoint.x, originPoint.y); } else { - _path.lineTo(x, previousPointY); + _path.lineTo(x, previousPointY!); _strokePath.lineTo(x, previousPointY); _strokePath.lineTo(x, y); _path.lineTo(x, y); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart index ae9e36154..1c9c14c78 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart @@ -2,12 +2,12 @@ part of charts; class _StepLineChartPainter extends CustomPainter { _StepLineChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -22,12 +22,13 @@ class _StepLineChartPainter extends CustomPainter { void paint(Canvas canvas, Size size) { double animationFactor; Rect clipRect; - final StepLineSeries series = seriesRenderer._series; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final StepLineSeries series = + seriesRenderer._series as StepLineSeries; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { canvas.save(); assert( series.animationDuration != null @@ -37,7 +38,7 @@ class _StepLineChartPainter extends CustomPainter { final int seriesIndex = painterKey.index; seriesRenderer._storeSeriesProperties(chartState, seriesIndex); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; final Rect axisClipRect = _calculatePlotOffset( chartState._chartAxis._axisClipRect, @@ -52,14 +53,14 @@ class _StepLineChartPainter extends CustomPainter { chartState, xAxisRenderer._axis, canvas, animationFactor); } int segmentIndex = -1; - CartesianChartPoint startPoint, + CartesianChartPoint? startPoint, endPoint, currentPoint, _nextPoint; - num midX, midY; + num? midX, midY; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } @@ -120,7 +121,7 @@ class _StepLineChartPainter extends CustomPainter { canvas.restore(); if ((series.animationDuration <= 0 || - (!chartState._initialRender && + (!chartState._initialRender! && !seriesRenderer._needAnimateSeriesElements) || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || @@ -131,16 +132,16 @@ class _StepLineChartPainter extends CustomPainter { seriesRenderer._renderSeriesElements( chart, canvas, seriesRenderer._seriesElementAnimation); } - if (seriesRenderer._visible && animationFactor >= 1) { + if (seriesRenderer._visible! && animationFactor >= 1) { chartState._setPainterKey(seriesIndex, painterKey.name, true); } } } /// To get point value in the drop mode - CartesianChartPoint getDropValue( + CartesianChartPoint? getDropValue( List> points, int pointIndex) { - CartesianChartPoint value; + CartesianChartPoint? value; for (int i = pointIndex; i < points.length - 1; i++) { if (!points[i + 1].isDrop) { value = points[i + 1]; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart index d1445add7..bc68574e6 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart @@ -2,12 +2,12 @@ part of charts; class _WaterfallChartPainter extends CustomPainter { _WaterfallChartPainter( - {this.chartState, - this.seriesRenderer, - this.isRepaint, - this.animationController, - ValueNotifier notifier, - this.painterKey}) + {required this.chartState, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) : chart = chartState._chart, super(repaint: notifier); final SfCartesianChartState chartState; @@ -21,17 +21,18 @@ class _WaterfallChartPainter extends CustomPainter { /// Painter method for waterfall series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; final List> dataPoints = seriesRenderer._dataPoints; - final WaterfallSeries series = seriesRenderer._series; - final num origin = math.max(yAxisRenderer._visibleRange.minimum, 0); + final WaterfallSeries series = + seriesRenderer._series as WaterfallSeries; + final num origin = math.max(yAxisRenderer._visibleRange!.minimum, 0); num currentEndValue = 0; num intermediateOrigin = 0; num prevEndValue = 0; num originValue = 0; - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { assert( series.animationDuration != null ? series.animationDuration >= 0 @@ -39,7 +40,7 @@ class _WaterfallChartPainter extends CustomPainter { 'The animation duration of the waterfall series must be greater or equal to 0.'); Rect axisClipRect, clipRect; double animationFactor; - CartesianChartPoint point; + CartesianChartPoint? point; canvas.save(); final int seriesIndex = painterKey.index; seriesRenderer._storeSeriesProperties(chartState, seriesIndex); @@ -49,24 +50,24 @@ class _WaterfallChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.clipRect(axisClipRect); animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation.value + ? seriesRenderer._seriesAnimation!.value : 1; int segmentIndex = -1; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; currentEndValue += - (point.isIntermediateSum || point.isTotalSum) ? 0 : point.yValue; + (point.isIntermediateSum! || point.isTotalSum!) ? 0 : point.yValue; point.yValue = - point.y = point.isTotalSum ? currentEndValue : point.yValue; + point.y = point.isTotalSum! ? currentEndValue : point.yValue; originValue = (point.isIntermediateSum == true ? intermediateOrigin : ((prevEndValue != null) ? prevEndValue : origin)); - originValue = point.isTotalSum ? 0 : originValue; - point.yValue = point.y = point.isIntermediateSum + originValue = point.isTotalSum! ? 0 : originValue; + point.yValue = point.y = point.isIntermediateSum! ? currentEndValue - originValue : point.yValue; point.endValue = currentEndValue; @@ -75,7 +76,7 @@ class _WaterfallChartPainter extends CustomPainter { chartState, seriesRenderer, painterKey.index, point, pointIndex); if (chartState._templates.isNotEmpty) { chartState._templates[pointIndex].location = - Offset(point.markerPoint.x, point.markerPoint.y); + Offset(point.markerPoint!.x, point.markerPoint!.y); } if (point.isVisible && !point.isGap) { seriesRenderer._drawSegment( @@ -83,7 +84,7 @@ class _WaterfallChartPainter extends CustomPainter { seriesRenderer._createSegments( point, segmentIndex += 1, painterKey.index, animationFactor)); } - if (point.isIntermediateSum) { + if (point.isIntermediateSum!) { intermediateOrigin = currentEndValue; } prevEndValue = currentEndValue; @@ -102,7 +103,7 @@ class _WaterfallChartPainter extends CustomPainter { xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - (!chartState._initialRender && + (!chartState._initialRender! && !seriesRenderer._needAnimateSeriesElements) || animationFactor >= chartState._seriesDurationFactor) && (series.markerSettings.isVisible || diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/accumulation_distribution_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/accumulation_distribution_indicator.dart index 6099399b7..034e0337d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/accumulation_distribution_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/accumulation_distribution_indicator.dart @@ -13,26 +13,26 @@ class AccumulationDistributionIndicator extends TechnicalIndicators { /// Creating an argument constructor of AccumulationDistributionIndicator class. AccumulationDistributionIndicator( - {bool isVisible, - String xAxisName, - String yAxisName, - String seriesName, - List dashArray, - double animationDuration, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper highValueMapper, - ChartValueMapper lowValueMapper, - ChartValueMapper closeValueMapper, - ChartValueMapper volumeValueMapper, - String name, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - Color signalLineColor, - double signalLineWidth}) + {bool? isVisible, + String? xAxisName, + String? yAxisName, + String? seriesName, + List? dashArray, + double? animationDuration, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? closeValueMapper, + ChartValueMapper? volumeValueMapper, + String? name, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + Color? signalLineColor, + double? signalLineWidth}) : volumeValueMapper = (volumeValueMapper != null) - ? ((int index) => volumeValueMapper(dataSource[index], index)) + ? ((int index) => volumeValueMapper(dataSource![index], index)) : null, super( isVisible: isVisible, @@ -77,7 +77,7 @@ class AccumulationDistributionIndicator /// } ///``` /// - final ChartIndexedValueMapper volumeValueMapper; + final ChartIndexedValueMapper? volumeValueMapper; /// To initialise indicators collections // ignore:unused_element @@ -87,8 +87,12 @@ class AccumulationDistributionIndicator TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { technicalIndicatorsRenderer._targetSeriesRenderers = []; - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'AD', - indicator.signalLineColor, indicator.signalLineWidth, chart); + technicalIndicatorsRenderer._setSeriesProperties( + indicator, + indicator.name ?? 'AD', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); } /// To initialise data source of technical indicators @@ -96,8 +100,9 @@ class AccumulationDistributionIndicator void _initDataSource(TechnicalIndicators indicator, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { final List> validData = - technicalIndicatorsRenderer._dataPoints; - if (validData.isNotEmpty) { + technicalIndicatorsRenderer._dataPoints!; + if (validData.isNotEmpty && + indicator is AccumulationDistributionIndicator) { _calculateADPoints(indicator, validData, technicalIndicatorsRenderer); } } @@ -114,17 +119,16 @@ class AccumulationDistributionIndicator final CartesianSeriesRenderer signalSeriesRenderer = technicalIndicatorsRenderer._targetSeriesRenderers[0]; num sum = 0; - num i = 0; num value = 0; num high = 0; num low = 0; num close = 0; - for (i = 0; i < validData.length; i++) { + for (int i = 0; i < validData.length; i++) { high = validData[i].high ?? 0; low = validData[i].low ?? 0; close = validData[i].close ?? 0; value = ((close - low) - (high - close)) / (high - low); - sum = sum + value * validData[i].volume; + sum = sum + value * validData[i].volume!; point = technicalIndicatorsRenderer._getDataPoint(validData[i].x, sum, validData[i], signalSeriesRenderer, points.length); points.add(point); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/atr_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/atr_indicator.dart index f63798688..70e88bb95 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/atr_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/atr_indicator.dart @@ -13,24 +13,24 @@ part of charts; class AtrIndicator extends TechnicalIndicators { /// Creating an argument constructor of AtrIndicator class. AtrIndicator( - {bool isVisible, - String xAxisName, - String yAxisName, - String seriesName, - List dashArray, - double animationDuration, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper highValueMapper, - ChartValueMapper lowValueMapper, - ChartValueMapper closeValueMapper, - String name, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - Color signalLineColor, - double signalLineWidth, - int period}) + {bool? isVisible, + String? xAxisName, + String? yAxisName, + String? seriesName, + List? dashArray, + double? animationDuration, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? closeValueMapper, + String? name, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + Color? signalLineColor, + double? signalLineWidth, + int? period}) : super( isVisible: isVisible, xAxisName: xAxisName, @@ -59,8 +59,12 @@ class AtrIndicator extends TechnicalIndicators { TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { technicalIndicatorsRenderer._targetSeriesRenderers = []; - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'ATR', - indicator.signalLineColor, indicator.signalLineWidth, chart); + technicalIndicatorsRenderer._setSeriesProperties( + indicator, + indicator.name ?? 'ATR', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); } /// To initialise data source of technical indicators @@ -68,8 +72,10 @@ class AtrIndicator extends TechnicalIndicators { void _initDataSource(TechnicalIndicators indicator, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { final List> validData = - technicalIndicatorsRenderer._dataPoints; - if (validData.isNotEmpty && validData.length > indicator.period) { + technicalIndicatorsRenderer._dataPoints!; + if (validData.isNotEmpty && + validData.length > indicator.period && + indicator is AtrIndicator) { _calculateATRPoints(indicator, validData, technicalIndicatorsRenderer); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/bollinger_bands_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/bollinger_bands_indicator.dart index 0d00101cc..faf1f0b9d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/bollinger_bands_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/bollinger_bands_indicator.dart @@ -11,35 +11,29 @@ part of charts; class BollingerBandIndicator extends TechnicalIndicators { /// Creating an argument constructor of BollingerBandIndicator class. BollingerBandIndicator( - {bool isVisible, - String xAxisName, - String yAxisName, - String seriesName, - List dashArray, - double animationDuration, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper closeValueMapper, - String name, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - Color signalLineColor, - double signalLineWidth, - int period, - int standardDeviation, - Color upperLineColor, - double upperLineWidth, - Color lowerLineColor, - double lowerLineWidth, - Color bandColor}) - : standardDeviation = standardDeviation ?? 2, - upperLineColor = upperLineColor ?? Colors.red, - upperLineWidth = upperLineWidth ?? 2, - lowerLineColor = lowerLineColor ?? Colors.green, - lowerLineWidth = lowerLineWidth ?? 2, - bandColor = bandColor ?? Colors.grey.withOpacity(0.25), - super( + {bool? isVisible, + String? xAxisName, + String? yAxisName, + String? seriesName, + List? dashArray, + double? animationDuration, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? closeValueMapper, + String? name, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + Color? signalLineColor, + double? signalLineWidth, + int? period, + this.standardDeviation = 2, + this.upperLineColor = Colors.red, + this.upperLineWidth = 2, + this.lowerLineColor = Colors.green, + this.lowerLineWidth = 2, + this.bandColor = const Color(0x409e9e9e)}) + : super( isVisible: isVisible, xAxisName: xAxisName, yAxisName: yAxisName, @@ -165,19 +159,28 @@ class BollingerBandIndicator extends TechnicalIndicators { BollingerBandIndicator indicator, SfCartesianChart chart, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { + // Decides the type of renderer class to be used + bool isLine, isRangeArea; technicalIndicatorsRenderer._targetSeriesRenderers = []; if (indicator.bandColor != Colors.transparent && indicator.bandColor != null) { - technicalIndicatorsRenderer._setSeriesProperties( - indicator, 'rangearea', indicator.bandColor, 0, chart); + isLine = false; + isRangeArea = true; + technicalIndicatorsRenderer._setSeriesProperties(indicator, 'rangearea', + indicator.bandColor, 0, chart, isLine, isRangeArea); } - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'BollingerBand', - indicator.signalLineColor, indicator.signalLineWidth, chart); + isLine = true; + technicalIndicatorsRenderer._setSeriesProperties( + indicator, + indicator.name ?? 'BollingerBand', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); technicalIndicatorsRenderer._setSeriesProperties(indicator, 'UpperLine', - indicator.upperLineColor, indicator.upperLineWidth, chart); + indicator.upperLineColor, indicator.upperLineWidth, chart, isLine); technicalIndicatorsRenderer._setSeriesProperties(indicator, 'LowerLine', - indicator.lowerLineColor, indicator.lowerLineWidth, chart); + indicator.lowerLineColor, indicator.lowerLineWidth, chart, isLine); } /// To initialise data source of technical indicators @@ -186,7 +189,7 @@ class BollingerBandIndicator extends TechnicalIndicators { TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { final bool enableBand = indicator.bandColor != Colors.transparent && indicator.bandColor != null; - final num start = enableBand ? 1 : 0; + final int start = enableBand ? 1 : 0; final List> signalCollection = >[]; final List> upperCollection = @@ -201,25 +204,29 @@ class BollingerBandIndicator extends TechnicalIndicators { technicalIndicatorsRenderer._targetSeriesRenderers[start + 2]; final CartesianSeriesRenderer signalSeriesRenderer = technicalIndicatorsRenderer._targetSeriesRenderers[start]; - final CartesianSeriesRenderer rangeAreaSeriesRenderer = enableBand + final CartesianSeriesRenderer? rangeAreaSeriesRenderer = enableBand ? technicalIndicatorsRenderer._targetSeriesRenderers[0] : null; final List xValues = []; //prepare data final List> validData = - technicalIndicatorsRenderer._dataPoints; + technicalIndicatorsRenderer._dataPoints!; if (validData.isNotEmpty && validData.length >= indicator.period && indicator.period > 0) { num sum = 0; num deviationSum = 0; final num multiplier = indicator.standardDeviation; - final num limit = validData.length; - final num length = indicator.period.round(); - final List smaPoints = List(limit); - final List deviations = List(limit); - final List<_BollingerData> bollingerPoints = List<_BollingerData>(limit); + final int limit = validData.length; + final int length = indicator.period.round(); + // This has been null before + final List smaPoints = List.filled(limit, -1); + final List deviations = List.filled(limit, -1); + final List<_BollingerData> bollingerPoints = List<_BollingerData>.filled( + limit, + _BollingerData( + x: -1, midBand: -1, lowBand: -1, upBand: -1, visible: false)); for (int i = 0; i < length; i++) { sum += validData[i].close ?? 0; @@ -296,7 +303,7 @@ class BollingerBandIndicator extends TechnicalIndicators { upperCollection[++i].y, lowerCollection[++j].y, validData[k], - rangeAreaSeriesRenderer._series, + rangeAreaSeriesRenderer?._series, bandCollection.length)); } } @@ -318,8 +325,12 @@ class BollingerBandIndicator extends TechnicalIndicators { class _BollingerData { _BollingerData( - {this.x, this.midBand, this.lowBand, this.upBand, this.visible}); - num x; + {this.x, + required this.midBand, + required this.lowBand, + required this.upBand, + required this.visible}); + num? x; num midBand; num lowBand; num upBand; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/ema_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/ema_indicator.dart index 16d803e3a..af9578fa5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/ema_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/ema_indicator.dart @@ -9,26 +9,26 @@ part of charts; class EmaIndicator extends TechnicalIndicators { /// Creating an argument constructor of EmaIndicator class. EmaIndicator({ - bool isVisible, - String xAxisName, - String yAxisName, - String seriesName, - List dashArray, - double animationDuration, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper highValueMapper, - ChartValueMapper lowValueMapper, - ChartValueMapper openValueMapper, - ChartValueMapper closeValueMapper, - String name, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - Color signalLineColor, - double signalLineWidth, - int period, - String valueField, + bool? isVisible, + String? xAxisName, + String? yAxisName, + String? seriesName, + List? dashArray, + double? animationDuration, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, + String? name, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + Color? signalLineColor, + double? signalLineWidth, + int? period, + String? valueField, }) : valueField = (valueField ?? 'close').toLowerCase(), super( isVisible: isVisible, @@ -78,8 +78,12 @@ class EmaIndicator extends TechnicalIndicators { TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { technicalIndicatorsRenderer._targetSeriesRenderers = []; - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'EMA', - indicator.signalLineColor, indicator.signalLineWidth, chart); + technicalIndicatorsRenderer._setSeriesProperties( + indicator, + indicator.name ?? 'EMA', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); } /// To initialise data source of technical indicators @@ -87,9 +91,10 @@ class EmaIndicator extends TechnicalIndicators { void _initDataSource(TechnicalIndicators indicator, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { final List> validData = - technicalIndicatorsRenderer._dataPoints; + technicalIndicatorsRenderer._dataPoints!; if (validData.isNotEmpty && validData.length > indicator.period && + indicator is EmaIndicator && indicator.period > 0) { _calculateEMAPoints(indicator, validData, technicalIndicatorsRenderer); } @@ -102,7 +107,7 @@ class EmaIndicator extends TechnicalIndicators { TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { final CartesianSeriesRenderer signalSeriesRenderer = technicalIndicatorsRenderer._targetSeriesRenderers[0]; - final num period = indicator.period; + final int period = indicator.period; final List> points = >[]; final List xValues = []; @@ -120,7 +125,7 @@ class EmaIndicator extends TechnicalIndicators { average, validData[period - 1], signalSeriesRenderer, points.length); points.add(point); xValues.add(point.x); - num index = period; + int index = period; while (index < validData.length) { if (validData[index].isVisible || validData[index].isGap == true || diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/macd_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/macd_indicator.dart index a5ae88bdb..7add88c91 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/macd_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/macd_indicator.dart @@ -13,37 +13,30 @@ part of charts; class MacdIndicator extends TechnicalIndicators { /// Creating an argument constructor of MacdIndicator class. MacdIndicator( - {bool isVisible, - String xAxisName, - String yAxisName, - String seriesName, - List dashArray, - double animationDuration, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper closeValueMapper, - String name, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - Color signalLineColor, - double signalLineWidth, - int period, - int shortPeriod, - int longPeriod, - Color macdLineColor, - double macdLineWidth, - MacdType macdType, - Color histogramPositiveColor, - Color histogramNegativeColor}) - : shortPeriod = shortPeriod ?? 12, - longPeriod = longPeriod ?? 26, - macdLineColor = macdLineColor ?? Colors.orange, - macdLineWidth = macdLineWidth ?? 2, - macdType = macdType ?? MacdType.both, - histogramPositiveColor = histogramPositiveColor ?? Colors.green, - histogramNegativeColor = histogramNegativeColor ?? Colors.red, - super( + {bool? isVisible, + String? xAxisName, + String? yAxisName, + String? seriesName, + List? dashArray, + double? animationDuration, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? closeValueMapper, + String? name, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + Color? signalLineColor, + double? signalLineWidth, + int? period, + this.shortPeriod = 12, + this.longPeriod = 26, + this.macdLineColor = Colors.orange, + this.macdLineWidth = 2, + this.macdType = MacdType.both, + this.histogramPositiveColor = Colors.green, + this.histogramNegativeColor = Colors.red}) + : super( isVisible: isVisible, xAxisName: xAxisName, yAxisName: yAxisName, @@ -186,24 +179,38 @@ class MacdIndicator extends TechnicalIndicators { MacdIndicator indicator, SfCartesianChart chart, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { + // To describe the type of series renderer to be assigned + bool isLine, isRangeArea, isHistogram; technicalIndicatorsRenderer._targetSeriesRenderers = []; - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'MACD', - indicator.signalLineColor, indicator.signalLineWidth, chart); + technicalIndicatorsRenderer._setSeriesProperties( + indicator, + indicator.name ?? 'MACD', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); if (indicator.macdType == MacdType.line || indicator.macdType == MacdType.both) { + // Decides the type of renderer class to be used + isLine = true; technicalIndicatorsRenderer._setSeriesProperties(indicator, 'MacdLine', - indicator.macdLineColor, indicator.macdLineWidth, chart); + indicator.macdLineColor, indicator.macdLineWidth, chart, isLine); } if (indicator.macdType == MacdType.histogram || indicator.macdType == MacdType.both) { + isLine = false; + isRangeArea = false; + isHistogram = true; technicalIndicatorsRenderer._setSeriesProperties( indicator, 'Histogram', indicator.histogramPositiveColor, indicator.signalLineWidth / 2, - chart); + chart, + isLine, + isRangeArea, + isHistogram); } } @@ -222,11 +229,15 @@ class MacdIndicator extends TechnicalIndicators { List> histogramCollection = >[]; final List> validData = - technicalIndicatorsRenderer._dataPoints; + technicalIndicatorsRenderer._dataPoints!; final CartesianSeriesRenderer signalSeriesRenderer = technicalIndicatorsRenderer._targetSeriesRenderers[0]; - List signalX, histogramX, macdX, collection; - CartesianSeriesRenderer histogramSeriesRenderer, macdLineSeriesRenderer; + List signalX = [], + histogramX = [], + macdX = [], + collection; + CartesianSeriesRenderer? histogramSeriesRenderer; + CartesianSeriesRenderer? macdLineSeriesRenderer; if (indicator.macdType == MacdType.histogram) { histogramSeriesRenderer = @@ -302,7 +313,7 @@ class MacdIndicator extends TechnicalIndicators { initialEMA = sum / period; emaValues.add(initialEMA); num emaAvg = initialEMA; - for (int j = period; j < validData.length; j++) { + for (int j = period.toInt(); j < validData.length; j++) { emaAvg = (technicalIndicatorsRenderer._getFieldValue( validData, j, valueField) - emaAvg) * @@ -323,8 +334,8 @@ class MacdIndicator extends TechnicalIndicators { final List> macdCollection = >[]; final List xValues = []; - num dataMACDIndex = indicator.longPeriod - 1; - num macdIndex = 0; + int dataMACDIndex = indicator.longPeriod - 1; + int macdIndex = 0; while (dataMACDIndex < validData.length) { macdCollection.add(technicalIndicatorsRenderer._getDataPoint( validData[dataMACDIndex].x, @@ -346,8 +357,8 @@ class MacdIndicator extends TechnicalIndicators { List> validData, CartesianSeriesRenderer seriesRenderer, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - num dataSignalIndex = indicator.longPeriod + indicator.period - 2; - num signalIndex = 0; + int dataSignalIndex = indicator.longPeriod + indicator.period - 2; + int signalIndex = 0; final List xValues = []; final List> signalCollection = >[]; @@ -369,7 +380,7 @@ class MacdIndicator extends TechnicalIndicators { List _getMACDVales(MacdIndicator indicator, List shortEma, List longEma) { final List macdPoints = []; - final num diff = indicator.longPeriod - indicator.shortPeriod; + final int diff = indicator.longPeriod - indicator.shortPeriod; for (int i = 0; i < longEma.length; i++) { macdPoints.add(shortEma[i + diff] - longEma[i]); } @@ -384,8 +395,8 @@ class MacdIndicator extends TechnicalIndicators { List> validData, CartesianSeriesRenderer seriesRenderer, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - num dataHistogramIndex = indicator.longPeriod + indicator.period - 2; - num histogramIndex = 0; + int dataHistogramIndex = indicator.longPeriod + indicator.period - 2; + int histogramIndex = 0; final List> histogramCollection = >[]; final List xValues = []; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/momentum_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/momentum_indicator.dart index dd118e039..c8c33b5ba 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/momentum_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/momentum_indicator.dart @@ -10,30 +10,28 @@ part of charts; class MomentumIndicator extends TechnicalIndicators { /// Creating an argument constructor of MomentumIndicator class. MomentumIndicator({ - bool isVisible, - String xAxisName, - String yAxisName, - String seriesName, - List dashArray, - double animationDuration, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper highValueMapper, - ChartValueMapper lowValueMapper, - ChartValueMapper openValueMapper, - ChartValueMapper closeValueMapper, - String name, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - Color signalLineColor, - double signalLineWidth, - int period, - final Color centerLineColor, - final double centerLineWidth, - }) : centerLineColor = centerLineColor ?? Colors.red, - centerLineWidth = centerLineWidth ?? 2, - super( + bool? isVisible, + String? xAxisName, + String? yAxisName, + String? seriesName, + List? dashArray, + double? animationDuration, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, + String? name, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + Color? signalLineColor, + double? signalLineWidth, + int? period, + this.centerLineColor = Colors.red, + this.centerLineWidth = 2, + }) : super( isVisible: isVisible, xAxisName: xAxisName, yAxisName: yAxisName, @@ -94,12 +92,18 @@ class MomentumIndicator extends TechnicalIndicators { MomentumIndicator indicator, SfCartesianChart chart, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { + // Decides the type of renderer class to be used + final bool isLine = true; technicalIndicatorsRenderer._targetSeriesRenderers = []; - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'Momentum', - indicator.signalLineColor, indicator.signalLineWidth, chart); + technicalIndicatorsRenderer._setSeriesProperties( + indicator, + indicator.name ?? 'Momentum', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); technicalIndicatorsRenderer._setSeriesProperties(indicator, 'CenterLine', - indicator.centerLineColor, indicator.centerLineWidth, chart); + indicator.centerLineColor, indicator.centerLineWidth, chart, isLine); } /// To initialise data source of technical indicators @@ -111,7 +115,7 @@ class MomentumIndicator extends TechnicalIndicators { final List> centerLineCollection = >[]; final List> validData = - technicalIndicatorsRenderer._dataPoints; + technicalIndicatorsRenderer._dataPoints!; final List centerXValues = []; final List xValues = []; num value; @@ -121,7 +125,7 @@ class MomentumIndicator extends TechnicalIndicators { technicalIndicatorsRenderer._targetSeriesRenderers[0]; final CartesianSeriesRenderer upperSeriesRenderer = technicalIndicatorsRenderer._targetSeriesRenderers[1]; - final num length = indicator.period; + final int length = indicator.period; if (validData.length >= indicator.period && indicator.period > 0) { for (int i = 0; i < validData.length; i++) { centerLineCollection.add(technicalIndicatorsRenderer._getDataPoint( diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/rsi_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/rsi_indicator.dart index 2380536f5..86e54ba60 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/rsi_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/rsi_indicator.dart @@ -12,39 +12,32 @@ part of charts; class RsiIndicator extends TechnicalIndicators { /// Creating an argument constructor of RsiIndicator class. RsiIndicator( - {bool isVisible, - String xAxisName, - String yAxisName, - String seriesName, - List dashArray, - double animationDuration, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper highValueMapper, - ChartValueMapper lowValueMapper, - ChartValueMapper closeValueMapper, - String name, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - Color signalLineColor, - double signalLineWidth, - int period, - bool showZones, - double overbought, - double oversold, - final Color upperLineColor, - final double upperLineWidth, - final Color lowerLineColor, - final double lowerLineWidth}) - : showZones = showZones ?? true, - overbought = overbought ?? 80, - oversold = oversold ?? 20, - upperLineColor = upperLineColor ?? Colors.red, - upperLineWidth = upperLineWidth ?? 2, - lowerLineColor = lowerLineColor ?? Colors.green, - lowerLineWidth = lowerLineWidth ?? 2, - super( + {bool? isVisible, + String? xAxisName, + String? yAxisName, + String? seriesName, + List? dashArray, + double? animationDuration, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? closeValueMapper, + String? name, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + Color? signalLineColor, + double? signalLineWidth, + int? period, + this.showZones = true, + this.overbought = 80, + this.oversold = 20, + this.upperLineColor = Colors.red, + this.upperLineWidth = 2, + this.lowerLineColor = Colors.green, + this.lowerLineWidth = 2}) + : super( isVisible: isVisible, xAxisName: xAxisName, yAxisName: yAxisName, @@ -189,15 +182,21 @@ class RsiIndicator extends TechnicalIndicators { RsiIndicator indicator, SfCartesianChart chart, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { + // Decides the type of renderer class to be used + final bool isLine = true; technicalIndicatorsRenderer._targetSeriesRenderers = []; - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'RSI', - indicator.signalLineColor, indicator.signalLineWidth, chart); + technicalIndicatorsRenderer._setSeriesProperties( + indicator, + indicator.name ?? 'RSI', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); if (indicator.showZones == true) { technicalIndicatorsRenderer._setSeriesProperties(indicator, 'UpperLine', - indicator.upperLineColor, indicator.upperLineWidth, chart); + indicator.upperLineColor, indicator.upperLineWidth, chart, isLine); technicalIndicatorsRenderer._setSeriesProperties(indicator, 'LowerLine', - indicator.lowerLineColor, indicator.lowerLineWidth, chart); + indicator.lowerLineColor, indicator.lowerLineWidth, chart, isLine); } } @@ -214,7 +213,7 @@ class RsiIndicator extends TechnicalIndicators { final CartesianSeriesRenderer signalSeriesRenderer = technicalIndicatorsRenderer._targetSeriesRenderers[0]; final List> validData = - technicalIndicatorsRenderer._dataPoints; + technicalIndicatorsRenderer._dataPoints!; final List xValues = []; final List signalXValues = []; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/sma_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/sma_indicator.dart index ead50780f..ce89a5935 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/sma_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/sma_indicator.dart @@ -9,26 +9,26 @@ part of charts; class SmaIndicator extends TechnicalIndicators { /// Creating an argument constructor of SmaIndicator class. SmaIndicator({ - bool isVisible, - String xAxisName, - String yAxisName, - String seriesName, - List dashArray, - double animationDuration, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper highValueMapper, - ChartValueMapper lowValueMapper, - ChartValueMapper openValueMapper, - ChartValueMapper closeValueMapper, - String name, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - Color signalLineColor, - double signalLineWidth, - int period, - String valueField, + bool? isVisible, + String? xAxisName, + String? yAxisName, + String? seriesName, + List? dashArray, + double? animationDuration, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, + String? name, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + Color? signalLineColor, + double? signalLineWidth, + int? period, + String? valueField, }) : valueField = (valueField ?? 'close').toLowerCase(), super( isVisible: isVisible, @@ -78,8 +78,12 @@ class SmaIndicator extends TechnicalIndicators { TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { technicalIndicatorsRenderer._targetSeriesRenderers = []; - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'SMA', - indicator.signalLineColor, indicator.signalLineWidth, chart); + technicalIndicatorsRenderer._setSeriesProperties( + indicator, + indicator.name ?? 'SMA', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); } /// To initialise data source of technical indicators @@ -89,7 +93,7 @@ class SmaIndicator extends TechnicalIndicators { final List> smaPoints = >[]; final List> points = - technicalIndicatorsRenderer._dataPoints; + technicalIndicatorsRenderer._dataPoints!; final List xValues = []; CartesianChartPoint point; if (points.isNotEmpty) { @@ -116,7 +120,7 @@ class SmaIndicator extends TechnicalIndicators { smaPoints.add(point); xValues.add(point.x); - num index = indicator.period; + int index = indicator.period; while (index < validData.length) { sum -= technicalIndicatorsRenderer._getFieldValue( validData, index - indicator.period, valueField); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/stochastic_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/stochastic_indicator.dart index 4a0504d1d..83dc245d9 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/stochastic_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/stochastic_indicator.dart @@ -10,48 +10,37 @@ part of charts; class StochasticIndicator extends TechnicalIndicators { /// Creating an argument constructor of StochasticIndicator class. StochasticIndicator( - {bool isVisible, - String xAxisName, - String yAxisName, - String seriesName, - List dashArray, - double animationDuration, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper highValueMapper, - ChartValueMapper lowValueMapper, - ChartValueMapper openValueMapper, - ChartValueMapper closeValueMapper, - String name, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - Color signalLineColor, - double signalLineWidth, - int period, - bool showZones, - double overbought, - double oversold, - final Color upperLineColor, - final double upperLineWidth, - final Color lowerLineColor, - final double lowerLineWidth, - final Color periodLineColor, - final double periodLineWidth, - num kPeriod, - num dPeriod}) - : showZones = showZones ?? true, - overbought = overbought ?? 80, - oversold = oversold ?? 20, - kPeriod = kPeriod ?? 3, - dPeriod = dPeriod ?? 5, - upperLineColor = upperLineColor ?? Colors.red, - upperLineWidth = upperLineWidth ?? 2, - lowerLineColor = lowerLineColor ?? Colors.green, - lowerLineWidth = lowerLineWidth ?? 2, - periodLineColor = periodLineColor ?? Colors.yellow, - periodLineWidth = periodLineWidth ?? 2, - super( + {bool? isVisible, + String? xAxisName, + String? yAxisName, + String? seriesName, + List? dashArray, + double? animationDuration, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, + String? name, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + Color? signalLineColor, + double? signalLineWidth, + int? period, + this.showZones = true, + this.overbought = 80, + this.oversold = 20, + this.upperLineColor = Colors.red, + this.upperLineWidth = 2, + this.lowerLineColor = Colors.green, + this.lowerLineWidth = 2, + this.periodLineColor = Colors.yellow, + this.periodLineWidth = 2, + this.kPeriod = 3, + this.dPeriod = 5}) + : super( isVisible: isVisible, xAxisName: xAxisName, yAxisName: yAxisName, @@ -265,17 +254,23 @@ class StochasticIndicator extends TechnicalIndicators { StochasticIndicator indicator, SfCartesianChart chart, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { + // Decides the type of renderer class to be used + final bool isLine = true; technicalIndicatorsRenderer._targetSeriesRenderers = []; - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'Stocastic', - indicator.signalLineColor, indicator.signalLineWidth, chart); + technicalIndicatorsRenderer._setSeriesProperties( + indicator, + indicator.name ?? 'Stocastic', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); technicalIndicatorsRenderer._setSeriesProperties(indicator, 'PeriodLine', - indicator.periodLineColor, indicator.periodLineWidth, chart); + indicator.periodLineColor, indicator.periodLineWidth, chart, isLine); if (showZones) { technicalIndicatorsRenderer._setSeriesProperties(indicator, 'UpperLine', - indicator.upperLineColor, indicator.upperLineWidth, chart); + indicator.upperLineColor, indicator.upperLineWidth, chart, isLine); technicalIndicatorsRenderer._setSeriesProperties(indicator, 'LowerLine', - indicator.lowerLineColor, indicator.lowerLineWidth, chart); + indicator.lowerLineColor, indicator.lowerLineWidth, chart, isLine); } } @@ -294,9 +289,9 @@ class StochasticIndicator extends TechnicalIndicators { List> periodCollection = >[]; final List> validData = - technicalIndicatorsRenderer._dataPoints; + technicalIndicatorsRenderer._dataPoints!; final List xValues = []; - List collection, signalX, periodX; + late List collection, signalX, periodX; if (validData.isNotEmpty && validData.length >= indicator.period && indicator.period > 0) { @@ -319,21 +314,21 @@ class StochasticIndicator extends TechnicalIndicators { } source = _calculatePeriod( indicator.period, - indicator.kPeriod, + indicator.kPeriod.toInt(), validData, technicalIndicatorsRenderer._targetSeriesRenderers[1], technicalIndicatorsRenderer); collection = _stochasticCalculation( indicator.period, - indicator.kPeriod, + indicator.kPeriod.toInt(), source, technicalIndicatorsRenderer._targetSeriesRenderers[1], technicalIndicatorsRenderer); periodCollection = collection[0]; periodX = collection[1]; collection = _stochasticCalculation( - indicator.period + indicator.kPeriod - 1, - indicator.dPeriod, + (indicator.period + indicator.kPeriod - 1).toInt(), + indicator.dPeriod.toInt(), source, technicalIndicatorsRenderer._targetSeriesRenderers[0], technicalIndicatorsRenderer); @@ -355,8 +350,8 @@ class StochasticIndicator extends TechnicalIndicators { /// To calculate the values of the stochastic indicator List _stochasticCalculation( - num period, - num kPeriod, + int period, + int kPeriod, List> data, CartesianSeriesRenderer sourceSeriesRenderer, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { @@ -364,7 +359,7 @@ class StochasticIndicator extends TechnicalIndicators { >[]; final List xValues = []; if (data.length >= period + kPeriod && kPeriod > 0) { - final num count = period + (kPeriod - 1); + final int count = period + (kPeriod - 1); final List temp = []; final List values = []; for (int i = 0; i < data.length; i++) { @@ -383,7 +378,7 @@ class StochasticIndicator extends TechnicalIndicators { temp.removeRange(0, 1); length = temp.length; } - final num len = count - 1; + final int len = count - 1; for (int i = 0; i < data.length; i++) { if (!(i < len)) { pointCollection.add(technicalIndicatorsRenderer._getDataPoint( @@ -403,14 +398,15 @@ class StochasticIndicator extends TechnicalIndicators { /// To return list of stochastic indicator points List> _calculatePeriod( - num period, - num kPeriod, + int period, + int kPeriod, List> data, CartesianSeriesRenderer seriesRenderer, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - final List lowValue = List(data.length); - final List highValue = List(data.length); - final List closeValue = List(data.length); + // This has been null before + final List lowValue = List.filled(data.length, -1); + final List highValue = List.filled(data.length, -1); + final List closeValue = List.filled(data.length, -1); final List> modifiedSource = >[]; @@ -428,7 +424,7 @@ class StochasticIndicator extends TechnicalIndicators { modifiedSource.add(technicalIndicatorsRenderer._getDataPoint(data[i].x, data[i].close, data[i], seriesRenderer, modifiedSource.length)); } - num min, max; + num? min, max; for (int i = period - 1; i < data.length; ++i) { for (int j = 0; j < period; ++j) { min ??= lowValue[i - j]; @@ -436,8 +432,8 @@ class StochasticIndicator extends TechnicalIndicators { min = math.min(min, lowValue[i - j]); max = math.max(max, highValue[i - j]); } - maxs.add(max); - mins.add(min); + maxs.add(max!); + mins.add(min!); min = null; max = null; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart index 8768bc69e..ad37191b2 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart @@ -11,25 +11,25 @@ part of charts; class TechnicalIndicators { /// Creating an argument constructor of TechnicalIndicators class. TechnicalIndicators( - {bool isVisible, + {bool? isVisible, this.xAxisName, this.yAxisName, this.seriesName, - List dashArray, - double animationDuration, + List? dashArray, + double? animationDuration, this.dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper lowValueMapper, - ChartValueMapper highValueMapper, - ChartValueMapper openValueMapper, - ChartValueMapper closeValueMapper, + ChartValueMapper? xValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, this.name, - bool isVisibleInLegend, - LegendIconType legendIconType, + bool? isVisibleInLegend, + LegendIconType? legendIconType, this.legendItemText, - Color signalLineColor, - double signalLineWidth, - int period}) + Color? signalLineColor, + double? signalLineWidth, + int? period}) : isVisible = isVisible ?? true, dashArray = dashArray ?? [0, 0], animationDuration = animationDuration ?? 1500, @@ -39,19 +39,19 @@ class TechnicalIndicators { signalLineWidth = signalLineWidth ?? 2, period = period ?? 14, xValueMapper = (xValueMapper != null) - ? ((int index) => xValueMapper(dataSource[index], index)) + ? ((int index) => xValueMapper(dataSource![index], index)) : null, lowValueMapper = (lowValueMapper != null) - ? ((int index) => lowValueMapper(dataSource[index], index)) + ? ((int index) => lowValueMapper(dataSource![index], index)) : null, highValueMapper = (highValueMapper != null) - ? ((int index) => highValueMapper(dataSource[index], index)) + ? ((int index) => highValueMapper(dataSource![index], index)) : null, openValueMapper = (openValueMapper != null) - ? ((int index) => openValueMapper(dataSource[index], index)) + ? ((int index) => openValueMapper(dataSource![index], index)) : null, closeValueMapper = (closeValueMapper != null) - ? ((int index) => closeValueMapper(dataSource[index], index)) + ? ((int index) => closeValueMapper(dataSource![index], index)) : null; /// Boolean property to change the visibility of the technical indicators. @@ -85,7 +85,7 @@ class TechnicalIndicators { /// )); ///} ///``` - final String xAxisName; + final String? xAxisName; /// Property to map the technical indicators with the axes. /// @@ -101,7 +101,7 @@ class TechnicalIndicators { /// )); ///} ///``` - final String yAxisName; + final String? yAxisName; /// Property to link indicators to a series based on names. /// @@ -117,7 +117,7 @@ class TechnicalIndicators { /// )); ///} ///``` - final String seriesName; + final String? seriesName; /// Property to provide DashArray for the technical indicators. /// @@ -167,7 +167,7 @@ class TechnicalIndicators { /// )); ///} ///``` - final List dataSource; + final List? dataSource; /// Value mapper to map the x values with technical indicators. /// @@ -183,7 +183,7 @@ class TechnicalIndicators { /// )); ///} ///``` - final ChartIndexedValueMapper xValueMapper; + final ChartIndexedValueMapper? xValueMapper; /// Value mapper to map the low values with technical indicators. /// @@ -199,7 +199,7 @@ class TechnicalIndicators { /// )); ///} ///``` - final ChartIndexedValueMapper lowValueMapper; + final ChartIndexedValueMapper? lowValueMapper; /// Value mapper to map the high values with technical indicators. /// @@ -215,7 +215,7 @@ class TechnicalIndicators { /// )); ///} ///``` - final ChartIndexedValueMapper highValueMapper; + final ChartIndexedValueMapper? highValueMapper; /// Value mapper to map the open values with technical indicators. /// @@ -231,7 +231,7 @@ class TechnicalIndicators { /// )); ///} ///``` - final ChartIndexedValueMapper openValueMapper; + final ChartIndexedValueMapper? openValueMapper; /// Value mapper to map the close values with technical indicators. /// @@ -247,7 +247,7 @@ class TechnicalIndicators { /// )); ///} ///``` - final ChartIndexedValueMapper closeValueMapper; + final ChartIndexedValueMapper? closeValueMapper; /// Property to provide name for the technical indicators. /// @@ -263,7 +263,7 @@ class TechnicalIndicators { /// )); ///} ///``` - final String name; + final String? name; /// Boolean property to determine the rendering of legends for the technical indicators. /// @@ -313,7 +313,7 @@ class TechnicalIndicators { /// )); ///} ///``` - final String legendItemText; + final String? legendItemText; /// Property to provide the color of the signal line in the technical indicators. /// @@ -374,19 +374,19 @@ class TechnicalIndicatorsRenderer { final TechnicalIndicators _technicalIndicatorRenderer; - String _name; - bool _visible; + late String _name; + bool? _visible; //ignore: prefer_final_fields bool _isIndicator = true; final String _seriesType = 'indicator'; - String _indicatorType; - int _index; + late String _indicatorType; + late int _index; //ignore: prefer_final_fields List _targetSeriesRenderers = []; //ignore: prefer_final_fields - List> _dataPoints = + List>? _dataPoints = >[]; ///used for test case @@ -400,8 +400,8 @@ class TechnicalIndicatorsRenderer { dynamic y, CartesianChartPoint sourcePoint, CartesianSeriesRenderer seriesRenderer, - num index, - [TechnicalIndicators indicator]) { + int index, + [TechnicalIndicators? indicator]) { final CartesianChartPoint point = CartesianChartPoint(x, y); point.xValue = sourcePoint.xValue; @@ -423,10 +423,10 @@ class TechnicalIndicatorsRenderer { num high, num low, CartesianChartPoint sourcePoint, - CartesianSeries series, + CartesianSeries? series, int index, //ignore: unused_element - [TechnicalIndicators indicator]) { + [TechnicalIndicators? indicator]) { final CartesianChartPoint point = CartesianChartPoint(x, null); point.high = high; @@ -439,25 +439,26 @@ class TechnicalIndicatorsRenderer { /// To set properties of technical indicators void _setSeriesProperties(TechnicalIndicators indicator, - String name, Color color, double width, SfCartesianChart chart) { - List _dashArray; + String name, Color color, double width, SfCartesianChart chart, + [isLine = false, isRangeArea = false, isHistogram = false]) { + List? _dashArray; if (chart.onIndicatorRender != null && - name != 'rangearea' && - name != 'Histogram' && - !name.contains('Line')) { + !isRangeArea && + !isHistogram && + !isLine) { final IndicatorRenderArgs indicatorArgs = IndicatorRenderArgs( indicator, _index, chart.indicators[_index].seriesName, _dataPoints); indicatorArgs.indicatorName = name; indicatorArgs.signalLineColor = color; indicatorArgs.signalLineWidth = width; indicatorArgs.lineDashArray = indicator.dashArray; - chart.onIndicatorRender(indicatorArgs); + chart.onIndicatorRender!(indicatorArgs); color = indicatorArgs.signalLineColor; width = indicatorArgs.signalLineWidth; name = indicatorArgs.indicatorName; _dashArray = indicatorArgs.lineDashArray; } - final CartesianSeries series = name == 'rangearea' + final CartesianSeries series = isRangeArea ? RangeAreaSeries( name: name, color: color, @@ -467,11 +468,11 @@ class TechnicalIndicatorsRenderer { animationDuration: indicator.animationDuration, yAxisName: indicator.yAxisName, enableTooltip: false, - xValueMapper: null, - highValueMapper: null, - lowValueMapper: null, - dataSource: null) - : (name == 'Histogram' + xValueMapper: (dynamic, _) => null, + highValueMapper: (dynamic, _) => null, + lowValueMapper: (dynamic, _) => null, + dataSource: []) + : (isHistogram ? ColumnSeries( name: name, color: color, @@ -479,9 +480,9 @@ class TechnicalIndicatorsRenderer { xAxisName: indicator.xAxisName, animationDuration: indicator.animationDuration, yAxisName: indicator.yAxisName, - xValueMapper: null, - yValueMapper: null, - dataSource: null) + xValueMapper: (dynamic, _) => null, + yValueMapper: (dynamic, _) => null, + dataSource: []) : LineSeries( name: name, color: color, @@ -490,18 +491,17 @@ class TechnicalIndicatorsRenderer { xAxisName: indicator.xAxisName, animationDuration: indicator.animationDuration, yAxisName: indicator.yAxisName, - xValueMapper: null, - yValueMapper: null, - dataSource: null)); - final CartesianSeriesRenderer seriesRenderer = name == 'rangearea' + xValueMapper: (dynamic, _) => null, + yValueMapper: (dynamic, _) => null, + dataSource: [])); + final CartesianSeriesRenderer seriesRenderer = isRangeArea ? RangeAreaSeriesRenderer() - : (name == 'Histogram' ? ColumnSeriesRenderer() : LineSeriesRenderer()); + : (isHistogram ? ColumnSeriesRenderer() : LineSeriesRenderer()); seriesRenderer._series = series; seriesRenderer._visible = _visible; seriesRenderer._chart = chart; - seriesRenderer._seriesType = name == 'rangearea' - ? 'rangearea' - : (name == 'Histogram' ? 'column' : 'line'); + seriesRenderer._seriesType = + isRangeArea ? 'rangearea' : (isHistogram ? 'column' : 'line'); seriesRenderer._isIndicator = true; seriesRenderer._seriesName = _name; _targetSeriesRenderers.add(seriesRenderer); @@ -510,7 +510,7 @@ class TechnicalIndicatorsRenderer { /// Set series range of technical indicators void _setSeriesRange(List> points, TechnicalIndicators indicator, List xValues, - [CartesianSeriesRenderer seriesRenderer]) { + [CartesianSeriesRenderer? seriesRenderer]) { if (seriesRenderer == null) { _targetSeriesRenderers[0]._dataPoints = points; _targetSeriesRenderers[0]._xValues = xValues; @@ -522,8 +522,8 @@ class TechnicalIndicatorsRenderer { /// To get the value field value of technical indicators num _getFieldValue( - List> point, int itr, String valueField) { - num val; + List?> point, int itr, String valueField) { + num? val; if (valueField == 'low') { val = point[itr]?.low; } else if (valueField == 'high') { diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/tma_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/tma_indicator.dart index 6a2b57073..44777f42f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/tma_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/tma_indicator.dart @@ -9,26 +9,26 @@ part of charts; class TmaIndicator extends TechnicalIndicators { /// Creating an argument constructor of TmaIndicator class. TmaIndicator( - {bool isVisible, - String xAxisName, - String yAxisName, - String seriesName, - List dashArray, - double animationDuration, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper highValueMapper, - ChartValueMapper lowValueMapper, - ChartValueMapper openValueMapper, - ChartValueMapper closeValueMapper, - String name, - bool isVisibleInLegend, - LegendIconType legendIconType, - String legendItemText, - Color signalLineColor, - double signalLineWidth, - int period, - String valueField}) + {bool? isVisible, + String? xAxisName, + String? yAxisName, + String? seriesName, + List? dashArray, + double? animationDuration, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? highValueMapper, + ChartValueMapper? lowValueMapper, + ChartValueMapper? openValueMapper, + ChartValueMapper? closeValueMapper, + String? name, + bool? isVisibleInLegend, + LegendIconType? legendIconType, + String? legendItemText, + Color? signalLineColor, + double? signalLineWidth, + int? period, + String? valueField}) : valueField = (valueField ?? 'close').toLowerCase(), super( isVisible: isVisible, @@ -78,8 +78,12 @@ class TmaIndicator extends TechnicalIndicators { TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { technicalIndicatorsRenderer._targetSeriesRenderers = []; - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'TMA', - indicator.signalLineColor, indicator.signalLineWidth, chart); + technicalIndicatorsRenderer._setSeriesProperties( + indicator, + indicator.name ?? 'TMA', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); } /// To initialise data source of technical indicators @@ -87,8 +91,10 @@ class TmaIndicator extends TechnicalIndicators { void _initDataSource(TechnicalIndicators indicator, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { final List> validData = - technicalIndicatorsRenderer._dataPoints; - if (validData.isNotEmpty && validData.length > indicator.period) { + technicalIndicatorsRenderer._dataPoints!; + if (validData.isNotEmpty && + validData.length > indicator.period && + indicator is TmaIndicator) { _calculateTMAPoints(indicator, validData, technicalIndicatorsRenderer); } } @@ -111,14 +117,14 @@ class TmaIndicator extends TechnicalIndicators { //prepare data if (validData.isNotEmpty && validData.length >= period) { num sum = 0; - num index = 0; + int index = 0; List smaValues = []; - num length = validData.length; + int length = validData.length; while (length >= period) { sum = 0; index = validData.length - length; - for (num j = index; j < index + period; j++) { + for (int j = index; j < index + period; j++) { sum += technicalIndicatorsRenderer._getFieldValue( validData, j, valueField); } @@ -127,9 +133,9 @@ class TmaIndicator extends TechnicalIndicators { length--; } //initial values - for (num k = 0; k < period - 1; k++) { + for (int k = 0; k < period - 1; k++) { sum = 0; - for (num j = 0; j < k + 1; j++) { + for (int j = 0; j < k + 1; j++) { sum += technicalIndicatorsRenderer._getFieldValue( validData, j, valueField); } @@ -140,7 +146,7 @@ class TmaIndicator extends TechnicalIndicators { index = indicator.period; while (index <= smaValues.length) { sum = 0; - for (num j = index - indicator.period; j < index; j++) { + for (int j = index - indicator.period; j < index; j++) { sum = sum + smaValues[j]; } sum = sum / indicator.period; @@ -163,8 +169,8 @@ class TmaIndicator extends TechnicalIndicators { /// To return list of spliced values List _splice(List list, int index, //ignore: unused_element - [num howMany, - num elements]) { + [num? howMany, + num? elements]) { if (elements != null) { list.insertAll(index, [elements]); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines.dart b/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines.dart index 83b82365a..865c4e251 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines.dart @@ -10,39 +10,24 @@ part of charts; class Trendline { /// Creating an argument constructor of Trendline class. Trendline( - {bool enableTooltip, + {this.enableTooltip = true, this.intercept, this.name, this.dashArray, - Color color, - TrendlineType type, - double backwardForecast, - double forwardForecast, - double opacity, - bool isVisible, - double width, - double animationDuration, - String valueField, - bool isVisibleInLegend, - LegendIconType legendIconType, - MarkerSettings markerSettings, - int polynomialOrder, - int period}) - : enableTooltip = enableTooltip ?? true, - type = type ?? TrendlineType.linear, - backwardForecast = backwardForecast ?? 0, - forwardForecast = forwardForecast ?? 0, - opacity = opacity ?? 1, - valueField = valueField ?? 'high', - width = width ?? 2, - isVisible = isVisible ?? true, - isVisibleInLegend = isVisibleInLegend ?? true, - animationDuration = animationDuration ?? 1500, - markerSettings = markerSettings ?? MarkerSettings(), - legendIconType = legendIconType ?? LegendIconType.horizontalLine, - polynomialOrder = polynomialOrder ?? 2, - period = period ?? 2, - color = color ?? Colors.blue; + this.color = Colors.blue, + this.type = TrendlineType.linear, + this.backwardForecast = 0, + this.forwardForecast = 0, + this.opacity = 1, + this.isVisible = true, + this.width = 2, + this.animationDuration = 1500, + this.valueField = 'high', + this.isVisibleInLegend = true, + this.legendIconType = LegendIconType.horizontalLine, + this.markerSettings = const MarkerSettings(), + this.polynomialOrder = 2, + this.period = 2}); ///Determines the animation time of trendline. /// @@ -156,7 +141,7 @@ class Trendline { /// )); ///} ///``` - final List dashArray; + final List? dashArray; ///Enables the tooltip for trendlines. /// @@ -213,7 +198,7 @@ class Trendline { /// )); ///} ///``` - final String name; + final String? name; ///Specifies the intercept value of the trendlines. /// @@ -232,7 +217,7 @@ class Trendline { /// )); ///} ///``` - final double intercept; + final double? intercept; ///Determines the visiblity of the trendline. /// @@ -395,9 +380,9 @@ class Trendline { class TrendlineRenderer { /// Creates an argument constructor for Trendline renderer class TrendlineRenderer(this._trendline) { - _opacity = _trendline.opacity ?? 1; + _opacity = _trendline.opacity; _dashArray = _trendline.dashArray; - _fillColor = _trendline.color ?? Colors.blue; + _fillColor = _trendline.color; _visible = _trendline.isVisible; _name = _trendline.name; } @@ -405,24 +390,24 @@ class TrendlineRenderer { final Trendline _trendline; /// Holds the collection of cartesian data points - List> _pointsData; - CartesianSeriesRenderer _seriesRenderer; - _SlopeIntercept _slopeIntercept; - List _polynomialSlopes; - List _markerShapes; - List _points; + List>? _pointsData; + late CartesianSeriesRenderer _seriesRenderer; + late _SlopeIntercept _slopeIntercept; + List? _polynomialSlopes; + late List _markerShapes; + late List _points; //ignore: prefer_final_fields - double _opacity; + late double _opacity; //ignore: prefer_final_fields - List _dashArray; + List? _dashArray; //ignore: prefer_final_fields - Color _fillColor; + late Color _fillColor; //ignore: prefer_final_fields - bool _visible; + late bool _visible; //ignore: prefer_final_fields - String _name; - bool _isNeedRender; - AnimationController _animationController; + String? _name; + late bool _isNeedRender; + late AnimationController _animationController; //ignore: prefer_final_fields bool _isTrendlineRenderEvent = false; @@ -445,13 +430,13 @@ class TrendlineRenderer { trendPoint.yValue = y; trendPoint.isVisible = true; seriesRenderer._minimumX = - math.min(seriesRenderer._minimumX, trendPoint.xValue); + math.min(seriesRenderer._minimumX!, trendPoint.xValue); seriesRenderer._minimumY = - math.min(seriesRenderer._minimumY, trendPoint.yValue); + math.min(seriesRenderer._minimumY!, trendPoint.yValue); seriesRenderer._maximumX = - math.max(seriesRenderer._maximumX, trendPoint.xValue); + math.max(seriesRenderer._maximumX!, trendPoint.xValue); seriesRenderer._maximumY = - math.max(seriesRenderer._maximumY, trendPoint.yValue); + math.max(seriesRenderer._maximumY!, trendPoint.yValue); return trendPoint; } @@ -466,10 +451,14 @@ class TrendlineRenderer { final List> pts = >[]; if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) { - x1Linear = _increaseDateTimeForecast(seriesRenderer._xAxisRenderer, - xValues[0], -(_trendline.backwardForecast)); - x2Linear = _increaseDateTimeForecast(seriesRenderer._xAxisRenderer, - xValues[xValues.length - 1], _trendline.forwardForecast); + x1Linear = _increaseDateTimeForecast( + seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + xValues[0], + -(_trendline.backwardForecast)); + x2Linear = _increaseDateTimeForecast( + seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + xValues[xValues.length - 1], + _trendline.forwardForecast); } else { x1Linear = xValues[0] - _trendline.backwardForecast; x2Linear = xValues[xValues.length - 1] + _trendline.forwardForecast; @@ -520,15 +509,19 @@ class TrendlineRenderer { CartesianSeriesRenderer seriesRenderer, _SlopeIntercept slopeInterceptExpo) { num x1, x2, x3; - final num midPoint = (points.length / 2).round(); + final int midPoint = (points.length / 2).round(); final List> ptsExpo = >[]; if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) { - x1 = _increaseDateTimeForecast(seriesRenderer._xAxisRenderer, xValues[0], + x1 = _increaseDateTimeForecast( + seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + xValues[0], -(_trendline.backwardForecast)); x2 = xValues[midPoint - 1]; - x3 = _increaseDateTimeForecast(seriesRenderer._xAxisRenderer, - xValues[xValues.length - 1], _trendline.forwardForecast); + x3 = _increaseDateTimeForecast( + seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + xValues[xValues.length - 1], + _trendline.forwardForecast); } else { x1 = xValues[0] - _trendline.backwardForecast; x2 = xValues[midPoint - 1]; @@ -586,15 +579,19 @@ class TrendlineRenderer { CartesianSeriesRenderer seriesRenderer, _SlopeIntercept slopeInterceptPow) { num x1, x2, x3; - final num midPoint = (points.length / 2).round(); + final int midPoint = (points.length / 2).round(); final List> ptsPow = >[]; if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) { - x1 = _increaseDateTimeForecast(seriesRenderer._xAxisRenderer, xValues[0], + x1 = _increaseDateTimeForecast( + seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + xValues[0], -(_trendline.backwardForecast)); x2 = xValues[midPoint - 1]; - x3 = _increaseDateTimeForecast(seriesRenderer._xAxisRenderer, - xValues[xValues.length - 1], _trendline.forwardForecast); + x3 = _increaseDateTimeForecast( + seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + xValues[xValues.length - 1], + _trendline.forwardForecast); } else { x1 = xValues[0] - _trendline.backwardForecast; x1 = x1 > -1 ? x1 : 0; @@ -626,12 +623,13 @@ class TrendlineRenderer { while (index < points.length) { final CartesianChartPoint point = points[index]; powerPoints.add(point.xValue ?? point.x); - final dynamic xVal = - point.xValue != null && (math.log(point.xValue)).isFinite - ? math.log(point.xValue) - : (point.x is String) - ? point.xValue - : point.x; + final dynamic xVal = point.xValue != null && + (math.log(point.xValue)).isFinite + ? math.log(point.xValue) + : (seriesRenderer._xAxisRenderer is CategoryAxisRenderer || + seriesRenderer._xAxisRenderer is DateTimeCategoryAxisRenderer) + ? point.xValue + : point.x; xValues.add(xVal); if (!(seriesRenderer._series is RangeAreaSeries || seriesRenderer._series is RangeColumnSeries || @@ -659,15 +657,19 @@ class TrendlineRenderer { CartesianSeriesRenderer seriesRenderer, _SlopeIntercept slopeInterceptLog) { num x1, x2, x3; - final num midPoint = (points.length / 2).round(); + final int midPoint = (points.length / 2).round(); final List> ptsLog = >[]; if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) { - x1 = _increaseDateTimeForecast(seriesRenderer._xAxisRenderer, xValues[0], + x1 = _increaseDateTimeForecast( + seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + xValues[0], -(_trendline.backwardForecast)); x2 = xValues[midPoint - 1]; - x3 = _increaseDateTimeForecast(seriesRenderer._xAxisRenderer, - xValues[xValues.length - 1], _trendline.forwardForecast); + x3 = _increaseDateTimeForecast( + seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + xValues[xValues.length - 1], + _trendline.forwardForecast); } else { x1 = xValues[0] - _trendline.backwardForecast; x2 = xValues[midPoint - 1]; @@ -700,12 +702,13 @@ class TrendlineRenderer { while (index < points.length) { final CartesianChartPoint point = points[index]; xPointsLgr.add(point.xValue ?? point.x); - final dynamic xVal = - (point.xValue != null && (math.log(point.xValue)).isFinite) - ? math.log(point.xValue) - : (point.x is String) - ? point.xValue - : point.x; + final dynamic xVal = (point.xValue != null && + (math.log(point.xValue)).isFinite) + ? math.log(point.xValue) + : (seriesRenderer._xAxisRenderer is CategoryAxisRenderer || + seriesRenderer._xAxisRenderer is DateTimeCategoryAxisRenderer) + ? point.xValue + : point.x; xLogValue.add(xVal); if (!(seriesRenderer._series is RangeAreaSeries || seriesRenderer._series is RangeColumnSeries || @@ -733,24 +736,26 @@ class TrendlineRenderer { CartesianSeriesRenderer seriesRenderer) { //ignore: unused_local_variable final int midPoint = (points.length / 2).round(); - List pts = []; - _polynomialSlopes = List(_trendline.polynomialOrder + 1); + List> pts = >[]; + _polynomialSlopes = + List.filled(_trendline.polynomialOrder + 1, null); for (int i = 0; i < xValues.length; i++) { final dynamic xVal = xValues[i]; final num yVal = yValues[i]; for (int j = 0; j <= _trendline.polynomialOrder; j++) { - _polynomialSlopes[j] ??= 0; - _polynomialSlopes[j] += pow(xVal.toDouble(), j) * yVal; + _polynomialSlopes![j] ??= 0; + _polynomialSlopes![j] += pow(xVal.toDouble(), j) * yVal; } } final List numArray = - List(2 * _trendline.polynomialOrder + 1); - final List matrix = List(_trendline.polynomialOrder + 1); + List.filled(2 * _trendline.polynomialOrder + 1, null); + final List matrix = + List.filled(_trendline.polynomialOrder + 1, null); for (int i = 0; i <= _trendline.polynomialOrder; i++) { - matrix[i] = List(_trendline.polynomialOrder + 1); + matrix[i] = List.filled(_trendline.polynomialOrder + 1, null); } num num1 = 0; @@ -769,7 +774,7 @@ class TrendlineRenderer { matrix[i][j] = numArray[i + j]; } } - if (!_gaussJordanElimination(matrix, _polynomialSlopes)) { + if (!_gaussJordanElimination(matrix, _polynomialSlopes!)) { _polynomialSlopes = null; } pts = _getPoints(points, xValues, yValues, seriesRenderer); @@ -810,7 +815,7 @@ class TrendlineRenderer { CartesianSeriesRenderer seriesRenderer) { //ignore: unused_local_variable final int midPoint = (points.length / 2).round(); - final List _polynomialSlopesList = _polynomialSlopes; + final List _polynomialSlopesList = _polynomialSlopes!; final List> pts = >[]; @@ -819,11 +824,11 @@ class TrendlineRenderer { num yVal; final dynamic _backwardForecast = seriesRenderer._xAxisRenderer is DateTimeAxisRenderer - ? _getForecastDate(seriesRenderer._xAxisRenderer, false) + ? _getForecastDate(seriesRenderer._xAxisRenderer!, false) : _trendline.backwardForecast; final dynamic _forwardForecast = seriesRenderer._xAxisRenderer is DateTimeAxisRenderer - ? _getForecastDate(seriesRenderer._xAxisRenderer, true) + ? _getForecastDate(seriesRenderer._xAxisRenderer!, true) : _trendline.forwardForecast; for (int index = 1; index <= _polynomialSlopesList.length; index++) { @@ -862,7 +867,7 @@ class TrendlineRenderer { List> getMovingAveragePoints( List> points, List xValues, - List yValues, + List yValues, CartesianSeriesRenderer seriesRenderer) { final List> pts = >[]; @@ -870,7 +875,7 @@ class TrendlineRenderer { ? points.length - 1 : _trendline.period; periods = max(2, periods); - num y; + int? y; dynamic x; int count; int nullCount; @@ -881,9 +886,9 @@ class TrendlineRenderer { if (j >= yValues.length || yValues[j] == null) { nullCount++; } - y += j >= yValues.length ? 0 : yValues[j]; + y = y! + (j >= yValues.length ? 0 : yValues[j]!).toInt(); } - y = periods - nullCount <= 0 ? null : y / (periods - nullCount); + y = ((periods - nullCount) <= 0) ? null : (y! ~/ (periods - nullCount)); if (y != null && !y.isNaN && index + periods < xValues.length + 1) { x = xValues[periods - 1 + index]; pts.add(getDataPoint( @@ -939,14 +944,13 @@ class TrendlineRenderer { yAvg += yValues[index]; xyAvg += xValues[index].toDouble() * yValues[index].toDouble(); xxAvg += xValues[index].toDouble() * xValues[index].toDouble(); - // yyAvg += yValues[index].toDouble() * yValues[index].toDouble(); index++; } if (_trendline.intercept != null && _trendline.intercept != 0 && (_trendline.type == TrendlineType.linear || _trendline.type == TrendlineType.exponential)) { - intercept = _trendline.intercept.toDouble(); + intercept = _trendline.intercept!.toDouble(); switch (_trendline.type) { case TrendlineType.linear: slope = (xyAvg - (intercept * xAvg)) / xxAvg; @@ -977,25 +981,25 @@ class TrendlineRenderer { /// To set initial data source for trendlines void _initDataSource( SfCartesianChart chart, CartesianSeriesRenderer seriesRenderer) { - if (_pointsData.isNotEmpty) { + if (_pointsData!.isNotEmpty) { switch (_trendline.type) { case TrendlineType.linear: - _setLinearRange(_pointsData, seriesRenderer); + _setLinearRange(_pointsData!, seriesRenderer); break; case TrendlineType.exponential: - _setExponentialRange(_pointsData, seriesRenderer); + _setExponentialRange(_pointsData!, seriesRenderer); break; case TrendlineType.power: - _setPowerRange(_pointsData, seriesRenderer); + _setPowerRange(_pointsData!, seriesRenderer); break; case TrendlineType.logarithmic: - _setLogarithmicRange(_pointsData, seriesRenderer); + _setLogarithmicRange(_pointsData!, seriesRenderer); break; case TrendlineType.polynomial: - _setPolynomialRange(_pointsData, seriesRenderer); + _setPolynomialRange(_pointsData!, seriesRenderer); break; case TrendlineType.movingAverage: - _setMovingAverageRange(_pointsData, seriesRenderer); + _setMovingAverageRange(_pointsData!, seriesRenderer); break; default: break; @@ -1008,25 +1012,25 @@ class TrendlineRenderer { SfCartesianChartState _chartState) { final Rect rect = _calculatePlotOffset( _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer._axis.plotOffset, - seriesRenderer._yAxisRenderer._axis.plotOffset)); + Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, + seriesRenderer._yAxisRenderer!._axis.plotOffset)); _points = []; if (seriesRenderer._series.trendlines != null && _pointsData != null) { - for (int i = 0; i < _pointsData.length; i++) { - if (_pointsData[i].x != null && _pointsData[i].y != null) { - final _ChartLocation currentChartPoint = _pointsData[i].markerPoint = + for (int i = 0; i < _pointsData!.length; i++) { + if (_pointsData![i].x != null && _pointsData![i].y != null) { + final _ChartLocation currentChartPoint = _pointsData![i].markerPoint = _calculatePoint( (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) - ? _pointsData[i].xValue - : _pointsData[i].x, - _pointsData[i].y, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + ? _pointsData![i].xValue + : _pointsData![i].x, + _pointsData![i].y, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, rect); _points.add(Offset(currentChartPoint.x, currentChartPoint.y)); - _pointsData[i].region = Rect.fromLTRB( + _pointsData![i].region = Rect.fromLTRB( _points[i].dx, _points[i].dy, _points[i].dx, _points[i].dy); } } @@ -1037,14 +1041,14 @@ class TrendlineRenderer { /// Calculate marker shapes for trendlines void _calculateMarkerShapesPoint(CartesianSeriesRenderer seriesRenderer) { _markerShapes = []; - for (int i = 0; i < _pointsData.length; i++) { - final CartesianChartPoint point = _pointsData[i]; + for (int i = 0; i < _pointsData!.length; i++) { + final CartesianChartPoint point = _pointsData![i]; final DataMarkerType markerType = _trendline.markerSettings.shape; final Size size = Size( _trendline.markerSettings.width, _trendline.markerSettings.height); _markerShapes.add(_getMarkerShapesPath( markerType, - Offset(point.markerPoint.x, point.markerPoint.y), + Offset(point.markerPoint!.x, point.markerPoint!.y), size, seriesRenderer)); } @@ -1052,14 +1056,14 @@ class TrendlineRenderer { /// To set data source for trendlines void _setDataSource( - CartesianSeriesRenderer seriesRenderer, SfCartesianChart chart) { + CartesianSeriesRenderer? seriesRenderer, SfCartesianChart chart) { if (seriesRenderer?._series != null) { - _seriesRenderer = seriesRenderer; + _seriesRenderer = seriesRenderer!; _pointsData = seriesRenderer._dataPoints; if (seriesRenderer is _StackedSeriesRenderer) { - for (int i = 0; i < _pointsData.length; i++) { - _pointsData[i].y = seriesRenderer._stackingValues[0].endValues[i]; - _pointsData[i].yValue = + for (int i = 0; i < _pointsData!.length; i++) { + _pointsData![i].y = seriesRenderer._stackingValues[0].endValues[i]; + _pointsData![i].yValue = seriesRenderer._stackingValues[0].endValues[i]; } } @@ -1068,8 +1072,9 @@ class TrendlineRenderer { } /// To obtain control points for type curve trendlines - List _getControlPoints(List _dataPoints, int index) { - List yCoef = []; + List _getControlPoints(List _dataPoints, int index) { + List yCoef = []; + final List controlPoints = []; final List xValues = []; final List yValues = []; for (int i = 0; i < _dataPoints.length; i++) { @@ -1078,14 +1083,14 @@ class TrendlineRenderer { } yCoef = _naturalSpline( xValues, yValues, yCoef, xValues.length, SplineType.natural); - return _calculateControlPoints(xValues, yValues, yCoef[index].toDouble(), - yCoef[index + 1].toDouble(), index); + return _calculateControlPoints(xValues, yValues, yCoef[index]!.toDouble(), + yCoef[index + 1]!.toDouble(), index, controlPoints); } /// It returns the date-time values of trendline series int _increaseDateTimeForecast( DateTimeAxisRenderer axisRenderer, int value, num interval) { - final DateTimeAxis axis = axisRenderer._axis; + final DateTimeAxis axis = axisRenderer._axis as DateTimeAxis; DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(value); switch (axis.intervalType) { case DateTimeIntervalType.years: @@ -1122,9 +1127,9 @@ class TrendlineRenderer { bool _gaussJordanElimination( List matrix, List _polynomialSlopesList) { final int length = matrix.length; - final List numArray1 = List(length); - final List numArray2 = List(length); - final List numArray3 = List(length); + final List numArray1 = List.filled(length, null); + final List numArray2 = List.filled(length, null); + final List numArray3 = List.filled(length, null); for (int index = 0; index < length; index++) { numArray3[index] = 0; @@ -1159,9 +1164,9 @@ class TrendlineRenderer { matrix[index3][index4_1] = num2; ++index4_1; } - final num num3 = _polynomialSlopes[index2]; - _polynomialSlopes[index2] = _polynomialSlopes[index3]; - _polynomialSlopes[index3] = num3; + final num num3 = _polynomialSlopes![index2]; + _polynomialSlopes![index2] = _polynomialSlopes![index3]; + _polynomialSlopes![index3] = num3; } numArray2[index1] = index2; numArray1[index1] = index3; @@ -1175,7 +1180,7 @@ class TrendlineRenderer { matrix[index3][iindex4] *= num4; ++iindex4; } - _polynomialSlopes[index3] *= num4; + _polynomialSlopes![index3] *= num4; int iandex4 = 0; while (iandex4 < length) { if (iandex4 != index3) { @@ -1186,7 +1191,7 @@ class TrendlineRenderer { matrix[iandex4][index5] -= matrix[index3][index5] * num2; ++index5; } - _polynomialSlopes[iandex4] -= _polynomialSlopes[index3] * num2; + _polynomialSlopes![iandex4] -= _polynomialSlopes![index3] * num2; } ++iandex4; } @@ -1220,12 +1225,12 @@ class TrendlineRenderer { for (dynamic x = start; polyPoints.length <= 100; x += (end - start) / 100) { - final dynamic y = _getPolynomialYValue(_polynomialSlopes, x); + final dynamic y = _getPolynomialYValue(_polynomialSlopes!, x); final _ChartLocation position = _calculatePoint( x, y, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, _chartState._chartAxis._axisClipRect); @@ -1291,6 +1296,6 @@ class TrendlineRenderer { } class _SlopeIntercept { - num slope; - num intercept; + late num slope; + late num intercept; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart index 1f785e4fa..75c262a31 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart @@ -2,7 +2,9 @@ part of charts; class _TrendlinePainter extends CustomPainter { _TrendlinePainter( - {this.chartState, this.trendlineAnimations, ValueNotifier notifier}) + {required this.chartState, + required this.trendlineAnimations, + required ValueNotifier notifier}) : super(repaint: notifier); final SfCartesianChartState chartState; final Map> trendlineAnimations; @@ -11,37 +13,36 @@ class _TrendlinePainter extends CustomPainter { void paint(Canvas canvas, Size size) { Rect clipRect; double animationFactor; - Animation trendlineAnimation; + Animation? trendlineAnimation; for (int i = 0; i < chartState._chartSeries.visibleSeriesRenderers.length; i++) { final CartesianSeriesRenderer seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[i]; - final XyDataSeries series = seriesRenderer._series; + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; TrendlineRenderer trendlineRenderer; Trendline trendline; + List controlPoints; if (series.trendlines != null) { - for (int j = 0; j < series.trendlines.length; j++) { - trendline = series.trendlines[j]; + for (int j = 0; j < series.trendlines!.length; j++) { + trendline = series.trendlines![j]; trendlineRenderer = seriesRenderer._trendlineRenderer[j]; - assert(trendline.width != null ? trendline.width >= 0 : true, + assert(trendline.width >= 0, 'The width of the trendlines must be greater or equal to 0.'); - assert( - trendline.animationDuration != null - ? trendline.animationDuration >= 0 - : true, + assert(trendline.animationDuration >= 0, 'The animation duration time for trendlines should be greater than or equal to 0.'); trendlineAnimation = trendlineAnimations['$i-$j']; if (trendlineRenderer._isNeedRender && trendline.isVisible) { canvas.save(); - animationFactor = (trendlineAnimation != null && - !seriesRenderer._chartState._isLegendToggled) - ? trendlineAnimation.value + animationFactor = (!seriesRenderer._chartState!._isLegendToggled && + (seriesRenderer._oldSeries == null)) + ? trendlineAnimation!.value : 1; final Rect axisClipRect = _calculatePlotOffset( chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer._axis.plotOffset, - seriesRenderer._yAxisRenderer._axis.plotOffset)); + Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, + seriesRenderer._yAxisRenderer!._axis.plotOffset)); canvas.clipRect(axisClipRect); final Path path = Path(); final Paint paint = Paint(); @@ -49,16 +50,19 @@ class _TrendlinePainter extends CustomPainter { if (seriesRenderer._reAnimate || (trendline.animationDuration > 0 && seriesRenderer._oldSeries == null)) { - _performLinearAnimation(chartState, - seriesRenderer._xAxisRenderer._axis, canvas, animationFactor); + _performLinearAnimation( + chartState, + seriesRenderer._xAxisRenderer!._axis, + canvas, + animationFactor); } renderTrendlineEvent( chartState._chart, trendline, - series.trendlines.indexOf(trendline), + series.trendlines!.indexOf(trendline), chartState._chartSeries.visibleSeriesRenderers .indexOf(seriesRenderer), - seriesRenderer._seriesName); + seriesRenderer._seriesName!); paint.color = trendlineRenderer._fillColor .withOpacity(trendlineRenderer._opacity); paint.style = PaintingStyle.stroke; @@ -73,20 +77,20 @@ class _TrendlinePainter extends CustomPainter { path.moveTo(trendlineRenderer._points[0].dx, trendlineRenderer._points[0].dy); for (int i = 0; i < trendlineRenderer._points.length - 1; i++) { - final List controlPoints = trendlineRenderer - ._getControlPoints(trendlineRenderer._points, i); + controlPoints = trendlineRenderer._getControlPoints( + trendlineRenderer._points, i); path.cubicTo( - controlPoints[0], - controlPoints[1], - controlPoints[2], - controlPoints[3], + controlPoints[0].dx, + controlPoints[0].dy, + controlPoints[1].dx, + controlPoints[1].dy, trendlineRenderer._points[i + 1].dx, trendlineRenderer._points[i + 1].dy); } } else if (trendline.type == TrendlineType.polynomial) { final List polynomialPoints = trendlineRenderer.getPolynomialCurve( - trendlineRenderer._pointsData, + trendlineRenderer._pointsData!, seriesRenderer, chartState); path.moveTo(polynomialPoints[0].dx, polynomialPoints[0].dy); @@ -103,7 +107,7 @@ class _TrendlinePainter extends CustomPainter { } if (trendlineRenderer._dashArray != null) { _drawDashedLine( - canvas, trendlineRenderer._dashArray, paint, path); + canvas, trendlineRenderer._dashArray!, paint, path); } else { canvas.drawPath(path, paint); } @@ -117,28 +121,26 @@ class _TrendlinePainter extends CustomPainter { trendline.markerSettings.width, chartState._chartAxis._axisClipRect.bottom + trendline.markerSettings.height), - Offset(seriesRenderer._xAxisRenderer._axis.plotOffset, - seriesRenderer._yAxisRenderer._axis.plotOffset)); + Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, + seriesRenderer._yAxisRenderer!._axis.plotOffset)); canvas.restore(); if (trendlineRenderer._visible && (animationFactor > chartState._trendlineDurationFactor)) { canvas.clipRect(clipRect); if (trendline.markerSettings.isVisible) { for (final CartesianChartPoint point - in trendlineRenderer._pointsData) { + in trendlineRenderer._pointsData!) { if (point.isVisible && point.isGap != true) { if (trendline.markerSettings.shape == DataMarkerType.image) { _drawImageMarker(seriesRenderer, canvas, - point.markerPoint.x, point.markerPoint.y); + point.markerPoint!.x, point.markerPoint!.y); } final Paint strokePaint = Paint() ..color = trendline.markerSettings.borderWidth == 0 ? Colors.transparent - : ((point.pointColorMapper != null) - ? point.pointColorMapper - : trendlineRenderer._fillColor ?? - trendline.markerSettings.borderColor) + : trendline.markerSettings.borderColor ?? + trendlineRenderer._fillColor ..strokeWidth = trendline.markerSettings.borderWidth ..style = PaintingStyle.stroke; @@ -149,7 +151,7 @@ class _TrendlinePainter extends CustomPainter { : Colors.black) ..style = PaintingStyle.fill; final int index = - trendlineRenderer._pointsData.indexOf(point); + trendlineRenderer._pointsData!.indexOf(point); canvas.drawPath( trendlineRenderer._markerShapes[index], strokePaint); canvas.drawPath( @@ -177,13 +179,13 @@ class _TrendlinePainter extends CustomPainter { trendline.intercept, trendlineIndex, seriesIndex, - trendlineRenderer._name, + trendlineRenderer._name!, seriesName, - trendlineRenderer._pointsData); + trendlineRenderer._pointsData!); args.color = trendlineRenderer._fillColor; args.opacity = trendlineRenderer._opacity; args.dashArray = trendlineRenderer._dashArray; - chart.onTrendlineRender(args); + chart.onTrendlineRender!(args); trendlineRenderer._fillColor = args.color; trendlineRenderer._opacity = args.opacity; trendlineRenderer._dashArray = args.dashArray; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart index 79047c7d9..4ce8cd15c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart @@ -11,17 +11,15 @@ part of charts; class CrosshairBehavior { /// Creating an argument constructor of CrosshairBehavior class. CrosshairBehavior({ - ActivationMode activationMode, - CrosshairLineType lineType, + this.activationMode = ActivationMode.longPress, + this.lineType = CrosshairLineType.both, this.lineDashArray, this.enable = false, this.lineColor, this.lineWidth = 1, this.shouldAlwaysShow = false, - double hideDelay, - }) : activationMode = activationMode ?? ActivationMode.longPress, - hideDelay = hideDelay ?? 0, - lineType = lineType ?? CrosshairLineType.both; + this.hideDelay = 0, + }); /// Toggles the visibility of the crosshair. /// @@ -65,7 +63,7 @@ class CrosshairBehavior { /// )); ///} ///``` - final Color lineColor; + final Color? lineColor; /// Dashes of the crosshair line. /// @@ -82,7 +80,7 @@ class CrosshairBehavior { /// )); ///} ///``` - final List lineDashArray; + final List? lineDashArray; /// Gesture for activating the crosshair. /// @@ -151,7 +149,7 @@ class CrosshairBehavior { ///``` final double hideDelay; - SfCartesianChartState _chartState; + SfCartesianChartState? _chartState; /// Displays the crosshair at the specified x and y-positions. /// @@ -160,39 +158,42 @@ class CrosshairBehavior { /// /// coordinateUnit - specify the type of x and y values given.'pixel' or 'point' for logica pixel and chart data point respectively. /// Defaults to `'point'`. - void show(dynamic x, double y, [String coordinateUnit]) { - final SfCartesianChartState chartState = _chartState; + void show(dynamic x, double y, [String coordinateUnit = 'point']) { + final SfCartesianChartState chartState = _chartState!; final CrosshairBehaviorRenderer crosshairBehaviorRenderer = chartState._crosshairBehaviorRenderer; if (coordinateUnit != 'pixel') { final CartesianSeriesRenderer seriesRenderer = crosshairBehaviorRenderer - ._crosshairPainter.chartState._chartSeries.visibleSeriesRenderers[0]; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; + ._crosshairPainter!.chartState._chartSeries.visibleSeriesRenderers[0]; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; final _ChartLocation location = _calculatePoint( - x is DateTime + (x is DateTime && !(xAxisRenderer is DateTimeCategoryAxisRenderer)) ? x.millisecondsSinceEpoch - : (x is String && xAxisRenderer is CategoryAxisRenderer) - ? xAxisRenderer._labels.indexOf(x) - : x, + : ((x is DateTime && + xAxisRenderer is DateTimeCategoryAxisRenderer) + ? xAxisRenderer._labels + .indexOf(xAxisRenderer._dateFormat.format(x)) + : ((x is String && xAxisRenderer is CategoryAxisRenderer) + ? xAxisRenderer._labels.indexOf(x) + : x)), y, xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, - seriesRenderer._chartState._chartAxis._axisClipRect); + seriesRenderer._chartState!._chartAxis._axisClipRect); x = location.x; - y = location.y; + y = location.y.truncateToDouble(); } if (crosshairBehaviorRenderer._crosshairPainter != null && activationMode != ActivationMode.none && - x != null && - y != null) { - crosshairBehaviorRenderer._crosshairPainter + x != null) { + crosshairBehaviorRenderer._crosshairPainter! ._generateAllPoints(Offset(x.toDouble(), y)); - crosshairBehaviorRenderer._crosshairPainter.canResetPath = false; + crosshairBehaviorRenderer._crosshairPainter!.canResetPath = false; crosshairBehaviorRenderer - ._crosshairPainter.chartState._crosshairRepaintNotifier.value++; + ._crosshairPainter!.chartState._crosshairRepaintNotifier.value++; } } @@ -201,55 +202,53 @@ class CrosshairBehavior { /// /// pointIndex - index of point at which the crosshair needs to be shown. void showByIndex(int pointIndex) { - final SfCartesianChartState chartState = _chartState; + final SfCartesianChartState chartState = _chartState!; final CrosshairBehaviorRenderer crosshairBehaviorRenderer = chartState._crosshairBehaviorRenderer; if (_validIndex( - pointIndex, 0, crosshairBehaviorRenderer._crosshairPainter.chart)) { + pointIndex, 0, crosshairBehaviorRenderer._crosshairPainter!.chart)) { if (crosshairBehaviorRenderer._crosshairPainter != null && activationMode != ActivationMode.none) { final List visibleSeriesRenderer = - crosshairBehaviorRenderer._crosshairPainter.chartState._chartSeries + crosshairBehaviorRenderer._crosshairPainter!.chartState._chartSeries .visibleSeriesRenderers; final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderer[0]; - crosshairBehaviorRenderer._crosshairPainter._generateAllPoints(Offset( - seriesRenderer._dataPoints[pointIndex].markerPoint.x, - seriesRenderer._dataPoints[pointIndex].markerPoint.y)); - crosshairBehaviorRenderer._crosshairPainter.canResetPath = false; + crosshairBehaviorRenderer._crosshairPainter!._generateAllPoints(Offset( + seriesRenderer._dataPoints[pointIndex].markerPoint!.x, + seriesRenderer._dataPoints[pointIndex].markerPoint!.y)); + crosshairBehaviorRenderer._crosshairPainter!.canResetPath = false; crosshairBehaviorRenderer - ._crosshairPainter.chartState._crosshairRepaintNotifier.value++; + ._crosshairPainter!.chartState._crosshairRepaintNotifier.value++; } } } /// Hides the crosshair if it is displayed. void hide() { - final SfCartesianChartState chartState = _chartState; + final SfCartesianChartState chartState = _chartState!; final CrosshairBehaviorRenderer crosshairBehaviorRenderer = chartState._crosshairBehaviorRenderer; if (crosshairBehaviorRenderer._crosshairPainter != null) { - crosshairBehaviorRenderer._crosshairPainter.canResetPath = false; + crosshairBehaviorRenderer._crosshairPainter!.canResetPath = false; ValueNotifier(crosshairBehaviorRenderer - ._crosshairPainter.chartState._crosshairRepaintNotifier.value++); - if (crosshairBehaviorRenderer._crosshairPainter.timer != null) { - crosshairBehaviorRenderer._crosshairPainter.timer.cancel(); - } - if (!_chartState._isTouchUp) { + ._crosshairPainter!.chartState._crosshairRepaintNotifier.value++); + crosshairBehaviorRenderer._crosshairPainter!.timer?.cancel(); + if (!chartState._isTouchUp) { crosshairBehaviorRenderer - ._crosshairPainter.chartState._trackballRepaintNotifier.value++; - crosshairBehaviorRenderer._crosshairPainter.canResetPath = true; + ._crosshairPainter!.chartState._trackballRepaintNotifier.value++; + crosshairBehaviorRenderer._crosshairPainter!.canResetPath = true; } else { if (!shouldAlwaysShow) { final double duration = (hideDelay == 0 && crosshairBehaviorRenderer - ._crosshairPainter.chartState._enableDoubleTap) + ._crosshairPainter!.chartState._enableDoubleTap) ? 200 : hideDelay; - crosshairBehaviorRenderer._crosshairPainter.timer = + crosshairBehaviorRenderer._crosshairPainter!.timer = Timer(Duration(milliseconds: duration.toInt()), () { - crosshairBehaviorRenderer - ._crosshairPainter.chartState._crosshairRepaintNotifier.value++; - crosshairBehaviorRenderer._crosshairPainter.canResetPath = true; + crosshairBehaviorRenderer._crosshairPainter!.chartState + ._crosshairRepaintNotifier.value++; + crosshairBehaviorRenderer._crosshairPainter!.canResetPath = true; }); } } @@ -269,10 +268,10 @@ class CrosshairBehaviorRenderer with ChartBehavior { CrosshairBehavior get _crosshairBehavior => _chart.crosshairBehavior; /// Touch position - Offset _position; + Offset? _position; /// Holds the instance of CrosshairPainter - _CrosshairPainter _crosshairPainter; + _CrosshairPainter? _crosshairPainter; /// Check whether long press activated or not. //ignore: prefer_final_fields @@ -315,18 +314,18 @@ class CrosshairBehaviorRenderer with ChartBehavior { @override void onPaint(Canvas canvas) { if (_crosshairPainter != null) { - _crosshairPainter._drawCrosshair(canvas); + _crosshairPainter!._drawCrosshair(canvas); } } /// To draw cross hair line - void _drawLine(Canvas canvas, Paint paint, int seriesIndex) { + void _drawLine(Canvas canvas, Paint? paint, int? seriesIndex) { assert(_crosshairBehavior.lineWidth >= 0, 'Line width value of crosshair should be greater than 0.'); - if (_crosshairPainter != null) { - _crosshairPainter._drawCrosshairLine(canvas, paint, seriesIndex); + if (_crosshairPainter != null && paint != null) { + _crosshairPainter!._drawCrosshairLine(canvas, paint, seriesIndex); } } - Paint _linePainter(Paint paint) => _crosshairPainter?._getLinePainter(paint); + Paint? _linePainter(Paint paint) => _crosshairPainter?._getLinePainter(paint); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart index be9a0cbf0..8b2919a65 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart @@ -1,40 +1,40 @@ part of charts; class _CrosshairPainter extends CustomPainter { - _CrosshairPainter({this.chartState, this.valueNotifier}) + _CrosshairPainter({required this.chartState, required this.valueNotifier}) : chart = chartState._chart, super(repaint: valueNotifier); final SfCartesianChartState chartState; final SfCartesianChart chart; - Timer timer; + Timer? timer; ValueNotifier valueNotifier; - double pointerLength; - double pointerWidth; + // double pointerLength; + // double pointerWidth; double nosePointY = 0; double nosePointX = 0; double totalWidth = 0; - double x; - double y; - double xPos; - double yPos; + // double x; + // double y; + // double xPos; + // double yPos; bool isTop = false; - double cornerRadius; + // double cornerRadius; Path backgroundPath = Path(); bool canResetPath = true; bool isLeft = false; bool isRight = false; - bool enable; + // bool enable; double padding = 0; List stringValue = []; Rect boundaryRect = const Rect.fromLTWH(0, 0, 0, 0); double leftPadding = 0; double topPadding = 0; bool isHorizontalOrientation = false; - TextStyle labelStyle; + // TextStyle labelStyle; @override void paint(Canvas canvas, Size size) { - if (canResetPath != null && !canResetPath) { + if (!canResetPath) { chartState._crosshairBehaviorRenderer.onPaint(canvas); } } @@ -44,39 +44,51 @@ class _CrosshairPainter extends CustomPainter { /// calculate trackball points void _generateAllPoints(Offset position) { - if (_isRectContains(chartState._chartAxis._axisClipRect, position)) { - chartState._crosshairBehaviorRenderer._position = position; - } + final Rect _axisClipRect = chartState._chartAxis._axisClipRect; + double dx, dy; + dx = position.dx > _axisClipRect.right + ? _axisClipRect.right + : position.dx < _axisClipRect.left + ? _axisClipRect.left + : position.dx; + dy = position.dy > _axisClipRect.bottom + ? _axisClipRect.bottom + : position.dy < _axisClipRect.top + ? _axisClipRect.top + : position.dy; + chartState._crosshairBehaviorRenderer._position = Offset(dx, dy); } /// Get line painter paint Paint _getLinePainter(Paint crosshairLinePaint) => crosshairLinePaint; /// Draw the path of the cross hair line - void _drawCrosshairLine(Canvas canvas, Paint paint, int index) { + void _drawCrosshairLine(Canvas canvas, Paint paint, int? index) { if (chartState._crosshairBehaviorRenderer._position != null) { final Path dashArrayPath = Path(); if ((chart.crosshairBehavior.lineType == CrosshairLineType.horizontal || chart.crosshairBehavior.lineType == CrosshairLineType.both) && chart.crosshairBehavior.lineWidth != 0) { dashArrayPath.moveTo(chartState._chartAxis._axisClipRect.left, - chartState._crosshairBehaviorRenderer._position.dy); + chartState._crosshairBehaviorRenderer._position!.dy); dashArrayPath.lineTo(chartState._chartAxis._axisClipRect.right, - chartState._crosshairBehaviorRenderer._position.dy); + chartState._crosshairBehaviorRenderer._position!.dy); chart.crosshairBehavior.lineDashArray != null - ? _drawDashedLine(canvas, chart.crosshairBehavior.lineDashArray, + ? _drawDashedLine(canvas, chart.crosshairBehavior.lineDashArray!, paint, dashArrayPath) : canvas.drawPath(dashArrayPath, paint); } if ((chart.crosshairBehavior.lineType == CrosshairLineType.vertical || chart.crosshairBehavior.lineType == CrosshairLineType.both) && chart.crosshairBehavior.lineWidth != 0) { - dashArrayPath.moveTo(chartState._crosshairBehaviorRenderer._position.dx, + dashArrayPath.moveTo( + chartState._crosshairBehaviorRenderer._position!.dx, chartState._chartAxis._axisClipRect.top); - dashArrayPath.lineTo(chartState._crosshairBehaviorRenderer._position.dx, + dashArrayPath.lineTo( + chartState._crosshairBehaviorRenderer._position!.dx, chartState._chartAxis._axisClipRect.bottom); chart.crosshairBehavior.lineDashArray != null - ? _drawDashedLine(canvas, chart.crosshairBehavior.lineDashArray, + ? _drawDashedLine(canvas, chart.crosshairBehavior.lineDashArray!, paint, dashArrayPath) : canvas.drawPath(dashArrayPath, paint); } @@ -102,7 +114,7 @@ class _CrosshairPainter extends CustomPainter { final Paint crosshairLinePaint = Paint(); if (chartState._crosshairBehaviorRenderer._position != null) { - final Offset position = chartState._crosshairBehaviorRenderer._position; + final Offset position = chartState._crosshairBehaviorRenderer._position!; crosshairLinePaint.color = chart.crosshairBehavior.lineColor ?? chartState._chartTheme.crosshairLineColor; @@ -112,7 +124,7 @@ class _CrosshairPainter extends CustomPainter { if (chart.onCrosshairPositionChanging != null) { crosshairEventArgs = CrosshairRenderArgs(); crosshairEventArgs.lineColor = crosshairLinePaint.color; - chart.onCrosshairPositionChanging(crosshairEventArgs); + chart.onCrosshairPositionChanging!(crosshairEventArgs); crosshairLinePaint.color = crosshairEventArgs.lineColor; } chartState._crosshairBehaviorRenderer._drawLine( @@ -136,7 +148,8 @@ class _CrosshairPainter extends CustomPainter { Size labelSize; Rect labelRect, validatedRect; RRect tooltipRect; - Color color; + //ignore: unused_local_variable + Color? color; const double padding = 10; CrosshairRenderArgs crosshairEventArgs; for (int index = 0; @@ -145,12 +158,10 @@ class _CrosshairPainter extends CustomPainter { axisRenderer = chartState._chartAxis._bottomAxesCount[index].axisRenderer; final ChartAxis axis = axisRenderer._axis; if (_needToAddTooltip(axisRenderer)) { - fillPaint.color = axis.interactiveTooltip.color != null - ? axis.interactiveTooltip?.color - : chartState._chartTheme.crosshairBackgroundColor; - strokePaint.color = axis.interactiveTooltip.borderColor != null - ? axis.interactiveTooltip?.borderColor - : chartState._chartTheme.crosshairBackgroundColor; + fillPaint.color = axis.interactiveTooltip.color ?? + chartState._chartTheme.crosshairBackgroundColor; + strokePaint.color = axis.interactiveTooltip.borderColor ?? + chartState._chartTheme.crosshairBackgroundColor; strokePaint.strokeWidth = axis.interactiveTooltip.borderWidth; value = _getXValue(axisRenderer, position); if (chart.onCrosshairPositionChanging != null) { @@ -159,13 +170,12 @@ class _CrosshairPainter extends CustomPainter { crosshairEventArgs.text = value.toString(); crosshairEventArgs.lineColor = chart.crosshairBehavior.lineColor ?? chartState._chartTheme.crosshairLineColor; - crosshairEventArgs.lineColor = color; - chart.onCrosshairPositionChanging(crosshairEventArgs); + chart.onCrosshairPositionChanging!(crosshairEventArgs); value = crosshairEventArgs.text; color = crosshairEventArgs.lineColor; } labelSize = - _measureText(value.toString(), axis.interactiveTooltip.textStyle); + measureText(value.toString(), axis.interactiveTooltip.textStyle); labelRect = Rect.fromLTWH( position.dx - (labelSize.width / 2 + padding / 2), axisRenderer._bounds.top + axis.interactiveTooltip.arrowLength, @@ -244,7 +254,8 @@ class _CrosshairPainter extends CustomPainter { Rect labelRect, validatedRect; RRect tooltipRect; const double padding = 10; - Color color; + //ignore: unused_local_variable + Color? color; CrosshairRenderArgs crosshairEventArgs; for (int index = 0; index < chartState._chartAxis._topAxesCount.length; @@ -252,12 +263,10 @@ class _CrosshairPainter extends CustomPainter { axisRenderer = chartState._chartAxis._topAxesCount[index].axisRenderer; axis = axisRenderer._axis; if (_needToAddTooltip(axisRenderer)) { - fillPaint.color = axis.interactiveTooltip.color != null - ? axis.interactiveTooltip?.color - : chartState._chartTheme.crosshairBackgroundColor; - strokePaint.color = axis.interactiveTooltip.borderColor != null - ? axis.interactiveTooltip?.borderColor - : chartState._chartTheme.crosshairBackgroundColor; + fillPaint.color = axis.interactiveTooltip.color ?? + chartState._chartTheme.crosshairBackgroundColor; + strokePaint.color = axis.interactiveTooltip.borderColor ?? + chartState._chartTheme.crosshairBackgroundColor; strokePaint.strokeWidth = axis.interactiveTooltip.borderWidth; value = _getXValue(axisRenderer, position); if (chart.onCrosshairPositionChanging != null) { @@ -266,13 +275,12 @@ class _CrosshairPainter extends CustomPainter { crosshairEventArgs.text = value.toString(); crosshairEventArgs.lineColor = chart.crosshairBehavior.lineColor ?? chartState._chartTheme.crosshairLineColor; - crosshairEventArgs.lineColor = color; - chart.onCrosshairPositionChanging(crosshairEventArgs); + chart.onCrosshairPositionChanging!(crosshairEventArgs); value = crosshairEventArgs.text; color = crosshairEventArgs.lineColor; } labelSize = - _measureText(value.toString(), axis.interactiveTooltip.textStyle); + measureText(value.toString(), axis.interactiveTooltip.textStyle); labelRect = Rect.fromLTWH( position.dx - (labelSize.width / 2 + padding / 2), axisRenderer._bounds.top - @@ -354,7 +362,8 @@ class _CrosshairPainter extends CustomPainter { Rect labelRect, validatedRect; RRect tooltipRect; const double padding = 10; - Color color; + //ignore: unused_local_variable + Color? color; CrosshairRenderArgs crosshairEventArgs; for (int index = 0; index < chartState._chartAxis._leftAxesCount.length; @@ -364,9 +373,8 @@ class _CrosshairPainter extends CustomPainter { if (_needToAddTooltip(axisRenderer)) { fillPaint.color = axis.interactiveTooltip.color ?? chartState._chartTheme.crosshairBackgroundColor; - strokePaint.color = axis.interactiveTooltip.borderColor != null - ? axis.interactiveTooltip?.borderColor - : chartState._chartTheme.crosshairBackgroundColor; + strokePaint.color = axis.interactiveTooltip.borderColor ?? + chartState._chartTheme.crosshairBackgroundColor; strokePaint.strokeWidth = axis.interactiveTooltip.borderWidth; value = _getYValue(axisRenderer, position); if (chart.onCrosshairPositionChanging != null) { @@ -375,13 +383,12 @@ class _CrosshairPainter extends CustomPainter { crosshairEventArgs.text = value.toString(); crosshairEventArgs.lineColor = chart.crosshairBehavior.lineColor ?? chartState._chartTheme.crosshairLineColor; - crosshairEventArgs.lineColor = color; - chart.onCrosshairPositionChanging(crosshairEventArgs); + chart.onCrosshairPositionChanging!(crosshairEventArgs); value = crosshairEventArgs.text; color = crosshairEventArgs.lineColor; } labelSize = - _measureText(value.toString(), axis.interactiveTooltip.textStyle); + measureText(value.toString(), axis.interactiveTooltip.textStyle); labelRect = Rect.fromLTWH( axisRenderer._bounds.left - (labelSize.width + padding) - @@ -465,7 +472,8 @@ class _CrosshairPainter extends CustomPainter { Rect labelRect, validatedRect; CrosshairRenderArgs crosshairEventArgs; RRect tooltipRect; - Color color; + //ignore: unused_local_variable + Color? color; const double padding = 10; for (int index = 0; index < chartState._chartAxis._rightAxesCount.length; @@ -475,9 +483,8 @@ class _CrosshairPainter extends CustomPainter { if (_needToAddTooltip(axisRenderer)) { fillPaint.color = axis.interactiveTooltip.color ?? chartState._chartTheme.crosshairBackgroundColor; - strokePaint.color = axis.interactiveTooltip.borderColor != null - ? axis.interactiveTooltip?.borderColor - : chartState._chartTheme.crosshairBackgroundColor; + strokePaint.color = axis.interactiveTooltip.borderColor ?? + chartState._chartTheme.crosshairBackgroundColor; strokePaint.strokeWidth = axis.interactiveTooltip.borderWidth; value = _getYValue(axisRenderer, position); if (chart.onCrosshairPositionChanging != null) { @@ -486,13 +493,12 @@ class _CrosshairPainter extends CustomPainter { crosshairEventArgs.text = value.toString(); crosshairEventArgs.lineColor = chart.crosshairBehavior.lineColor ?? chartState._chartTheme.crosshairLineColor; - crosshairEventArgs.lineColor = color; - chart.onCrosshairPositionChanging(crosshairEventArgs); + chart.onCrosshairPositionChanging!(crosshairEventArgs); value = crosshairEventArgs.text; color = crosshairEventArgs.lineColor; } labelSize = - _measureText(value.toString(), axis.interactiveTooltip.textStyle); + measureText(value.toString(), axis.interactiveTooltip.textStyle); labelRect = Rect.fromLTWH( axisRenderer._bounds.left + axis.interactiveTooltip.arrowLength, position.dy - (labelSize.height / 2 + padding / 2), @@ -565,8 +571,8 @@ class _CrosshairPainter extends CustomPainter { /// To find the x value of crosshair dynamic _getXValue(ChartAxisRenderer axisRenderer, Offset position) { - final dynamic value = _pointToXValue( - chartState._requireInvertedAxis, + final dynamic value = _pointToXVal( + chart, axisRenderer, axisRenderer._bounds, position.dx - @@ -578,7 +584,7 @@ class _CrosshairPainter extends CustomPainter { String resultantString = _getInteractiveTooltipLabel(value, axisRenderer).toString(); if (axisRenderer._axis.interactiveTooltip.format != null) { - final String stringValue = axisRenderer._axis.interactiveTooltip.format + final String stringValue = axisRenderer._axis.interactiveTooltip.format! .replaceAll('{value}', resultantString); resultantString = stringValue; } @@ -600,7 +606,7 @@ class _CrosshairPainter extends CustomPainter { String resultantString = _getInteractiveTooltipLabel(value, axisRenderer).toString(); if (axisRenderer._axis.interactiveTooltip.format != null) { - final String stringValue = axisRenderer._axis.interactiveTooltip.format + final String stringValue = axisRenderer._axis.interactiveTooltip.format! .replaceAll('{value}', resultantString); resultantString = stringValue; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart index c1466e340..fcd2b042a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart @@ -2,26 +2,27 @@ part of charts; class _SelectionRenderer { _SelectionRenderer(); - int pointIndex; - int seriesIndex; - int cartesianSeriesIndex; - int cartesianPointIndex; - bool isSelection; - ChartSegment selectedSegment, currentSegment; + int? pointIndex; + int? seriesIndex; + late int cartesianSeriesIndex; + int? cartesianPointIndex; + late bool isSelection; + ChartSegment? selectedSegment, currentSegment; final List defaultselectedSegments = []; final List defaultunselectedSegments = []; bool isSelected = false; - dynamic chart; - dynamic _chartState; + late dynamic chart; + late dynamic _chartState; dynamic seriesRenderer; - Color fillColor, strokeColor; - double fillOpacity, strokeOpacity, strokeWidth; - SelectionArgs selectionArgs; - List selectedSegments; - List unselectedSegments; - SelectionType selectionType; - int overallPointIndex; + late Color fillColor, strokeColor; + late double fillOpacity, strokeOpacity, strokeWidth; + SelectionArgs? selectionArgs; + late List selectedSegments; + List? unselectedSegments; + SelectionType? selectionType; + int? viewportIndex; bool selected = false; + bool _isInteraction = false; ////Selects or deselects the specified data point in the series. /// @@ -41,46 +42,45 @@ class _SelectionRenderer { ///_Note:_ - Even though, the [enable] property in [ChartSelectionBehavior] is set to false, this method /// will work. - void selectDataPoints(int pointIndex, [int seriesIndex]) { + void selectDataPoints(int? pointIndex, [int? seriesIndex]) { if (chart is SfCartesianChart) { if (_validIndex(pointIndex, seriesIndex, chart)) { bool select = false; final List seriesRenderList = _chartState._chartSeries.visibleSeriesRenderers; final CartesianSeriesRenderer seriesRender = - seriesRenderList[seriesIndex]; + seriesRenderList[seriesIndex!]; + selected = pointIndex != null; + viewportIndex = _getVisibleDataPointIndex(pointIndex, seriesRender); final String seriesType = seriesRenderer._seriesType; final SelectionBehaviorRenderer selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer; - if (seriesType == 'line' || - seriesType == 'spline' || - seriesType == 'stepline' || - seriesType == 'stackedline' || - seriesType == 'stackedline100') { + selectionBehaviorRenderer._selectionRenderer!._isInteraction = true; + if (_checkSeriesType(seriesType) || + seriesType.contains('hilo') || + seriesType == 'candle' || + seriesType.contains('boxandwhisker')) { if (seriesRenderer._isSelectionEnable) { - selectionBehaviorRenderer._selectionRenderer.cartesianPointIndex = + selectionBehaviorRenderer._selectionRenderer!.cartesianPointIndex = pointIndex; - selectionBehaviorRenderer._selectionRenderer.cartesianSeriesIndex = + selectionBehaviorRenderer._selectionRenderer!.cartesianSeriesIndex = seriesIndex; - select = selectionBehaviorRenderer._selectionRenderer + select = selectionBehaviorRenderer._selectionRenderer! .isCartesianSelection( chart, seriesRender, pointIndex, seriesIndex); - selected = pointIndex != null; - overallPointIndex = - seriesRender._dataPoints[pointIndex].visiblePointIndex; } } else { _chartState._renderDatalabelRegions = []; if (seriesType.contains('area') || seriesType == 'fastline') { - selectionBehaviorRenderer._selectionRenderer.seriesIndex = + selectionBehaviorRenderer._selectionRenderer!.seriesIndex = seriesIndex; } else { - selectionBehaviorRenderer._selectionRenderer.seriesIndex = + selectionBehaviorRenderer._selectionRenderer!.seriesIndex = seriesIndex; - selectionBehaviorRenderer._selectionRenderer.pointIndex = + selectionBehaviorRenderer._selectionRenderer!.pointIndex = pointIndex; } - select = selectionBehaviorRenderer._selectionRenderer + select = selectionBehaviorRenderer._selectionRenderer! .isCartesianSelection( chart, seriesRender, pointIndex, seriesIndex); } @@ -93,18 +93,18 @@ class _SelectionRenderer { selectionType = null; } } else if (chart is SfCircularChart) { - if (_validIndex(pointIndex, seriesIndex, chart)) { + if (_validIndex(pointIndex!, seriesIndex!, chart)) { _chartState._chartSeries._seriesPointSelection( null, chart.selectionGesture, pointIndex, seriesIndex); } } else if (chart is SfFunnelChart) { - if (pointIndex < chart.series.dataSource.length) { - seriesRenderer._chartState._chartSeries + if (pointIndex! < chart.series.dataSource.length) { + seriesRenderer._chartState!._chartSeries ._seriesPointSelection(pointIndex, chart.selectionGesture); } } else { - if (pointIndex < chart.series.dataSource.length) { - seriesRenderer._chartState._chartSeries + if (pointIndex! < chart.series.dataSource.length) { + seriesRenderer._chartState!._chartSeries ._seriesPointSelection(pointIndex, chart.selectionGesture); } } @@ -118,20 +118,18 @@ class _SelectionRenderer { if (chart.onSelectionChanged != null) {} for (int j = 0; j < seriesRenderer._segments.length; j++) { currentSegment = seriesRenderer._segments[j]; - currentSegment.currentSegmentIndex == selectedItem + currentSegment!.currentSegmentIndex == selectedItem ? selectedSegments.add(seriesRenderer._segments[j]) - : unselectedSegments.add(seriesRenderer._segments[j]); + : unselectedSegments!.add(seriesRenderer._segments[j]); } } _selectedSegmentsColors(selectedSegments); - _unselectedSegmentsColors(unselectedSegments); + _unselectedSegmentsColors(unselectedSegments!); } ///Paint method for default fill color settings - Paint getDefaultFillColor( - [List> points, - int point, - ChartSegment segment]) { + Paint getDefaultFillColor(List>? points, + int? point, ChartSegment segment) { final String seriesType = seriesRenderer._seriesType; final Paint selectedFillPaint = Paint(); if (seriesRenderer._series is CartesianSeries) { @@ -142,10 +140,10 @@ class _SelectionRenderer { seriesType == 'stackedline' || seriesType == 'stackedline100' || seriesType.contains('hilo') - ? selectedFillPaint.color = segment._defaultStrokeColor.color - : selectedFillPaint.color = segment._defaultFillColor.color; + ? selectedFillPaint.color = segment._defaultStrokeColor!.color + : selectedFillPaint.color = segment._defaultFillColor!.color; if (segment._defaultFillColor?.shader != null) { - selectedFillPaint.shader = segment._defaultFillColor.shader; + selectedFillPaint.shader = segment._defaultFillColor!.shader; } } @@ -164,21 +162,20 @@ class _SelectionRenderer { } ///Paint method for default stroke color settings - Paint getDefaultStrokeColor( - [List> points, - int point, - ChartSegment segment]) { + Paint getDefaultStrokeColor(List>? points, + int? point, ChartSegment segment) { final Paint selectedStrokePaint = Paint(); if (seriesRenderer._series is CartesianSeries) { - selectedStrokePaint.color = segment._defaultStrokeColor.color; - selectedStrokePaint.strokeWidth = segment._defaultStrokeColor.strokeWidth; + selectedStrokePaint.color = segment._defaultStrokeColor!.color; + selectedStrokePaint.strokeWidth = + segment._defaultStrokeColor!.strokeWidth; } selectedStrokePaint.style = PaintingStyle.stroke; return selectedStrokePaint; } /// Paint method with selected fill color values - Paint getFillColor(bool isSelection, [ChartSegment segment, dynamic point]) { + Paint getFillColor(bool isSelection, ChartSegment segment) { final dynamic selectionBehavior = seriesRenderer._selectionBehavior; final CartesianSeries series = seriesRenderer._series; assert( @@ -193,46 +190,47 @@ class _SelectionRenderer { selectionBehavior.unselectedOpacity <= 1 : true, 'The unselected opacity of selection settings should between 0 and 1.'); - final ChartSelectionCallback chartEventSelection = chart.onSelectionChanged; + final ChartSelectionCallback? chartEventSelection = + chart.onSelectionChanged; if (isSelection) { if (series is CartesianSeries) { fillColor = chartEventSelection != null && selectionArgs != null && - selectionArgs.selectedColor != null - ? selectionArgs.selectedColor + selectionArgs!.selectedColor != null + ? selectionArgs!.selectedColor : _chartState._selectionArgs != null && - _chartState._selectionArgs.selectedColor != null - ? _chartState._selectionArgs.selectedColor + _chartState._selectionArgs!.selectedColor != null + ? _chartState._selectionArgs!.selectedColor : selectionBehavior.selectedColor ?? - segment._defaultFillColor.color; + segment._defaultFillColor!.color; } fillOpacity = chartEventSelection != null && selectionArgs != null && - selectionArgs.selectedColor != null - ? selectionArgs.selectedColor.opacity + selectionArgs!.selectedColor != null + ? selectionArgs!.selectedColor!.opacity : _chartState._selectionArgs != null && - _chartState._selectionArgs.selectedColor != null - ? _chartState._selectionArgs.selectedColor.opacity + _chartState._selectionArgs!.selectedColor != null + ? _chartState._selectionArgs!.selectedColor.opacity : selectionBehavior.selectedOpacity ?? series.opacity; } else { if (series is CartesianSeries) { fillColor = chartEventSelection != null && selectionArgs != null && - selectionArgs.unselectedColor != null - ? selectionArgs.unselectedColor + selectionArgs!.unselectedColor != null + ? selectionArgs!.unselectedColor : _chartState._selectionArgs != null && - _chartState._selectionArgs.unselectedColor != null - ? _chartState._selectionArgs.unselectedColor + _chartState._selectionArgs!.unselectedColor != null + ? _chartState._selectionArgs!.unselectedColor : selectionBehavior.unselectedColor ?? - segment._defaultFillColor.color; + segment._defaultFillColor!.color; } fillOpacity = chartEventSelection != null && selectionArgs != null && - selectionArgs.unselectedColor != null - ? selectionArgs.unselectedColor.opacity + selectionArgs!.unselectedColor != null + ? selectionArgs!.unselectedColor!.opacity : _chartState._selectionArgs != null && - _chartState._selectionArgs.unselectedColor != null - ? _chartState._selectionArgs.unselectedColor.opacity + _chartState._selectionArgs!.unselectedColor != null + ? _chartState._selectionArgs!.unselectedColor.opacity : selectionBehavior.unselectedOpacity ?? series.opacity; } final Paint selectedFillPaint = Paint(); @@ -252,12 +250,13 @@ class _SelectionRenderer { } /// Paint method with selected stroke color values - Paint getStrokeColor(bool isSelection, - [ChartSegment segment, CartesianChartPoint point]) { + Paint getStrokeColor(bool isSelection, ChartSegment segment, + [CartesianChartPoint? point]) { final CartesianSeries series = seriesRenderer._series; final String seriesType = seriesRenderer._seriesType; final dynamic selectionBehavior = seriesRenderer._selectionBehavior; - final ChartSelectionCallback chartEventSelection = chart.onSelectionChanged; + final ChartSelectionCallback? chartEventSelection = + chart.onSelectionChanged; if (isSelection) { if (series is CartesianSeries) { seriesType == 'line' || @@ -271,40 +270,40 @@ class _SelectionRenderer { seriesType == 'boxandwhisker' ? strokeColor = chartEventSelection != null && selectionArgs != null && - selectionArgs.selectedColor != null - ? selectionArgs.selectedColor + selectionArgs!.selectedColor != null + ? selectionArgs!.selectedColor : _chartState._selectionArgs != null && - _chartState._selectionArgs.selectedColor != null - ? _chartState._selectionArgs.selectedColor + _chartState._selectionArgs!.selectedColor != null + ? _chartState._selectionArgs!.selectedColor : selectionBehavior.selectedColor ?? - segment._defaultFillColor.color + segment._defaultFillColor!.color : strokeColor = chartEventSelection != null && selectionArgs != null && - selectionArgs.selectedBorderColor != null - ? selectionArgs.selectedBorderColor + selectionArgs!.selectedBorderColor != null + ? selectionArgs!.selectedBorderColor : _chartState._selectionArgs != null && - _chartState._selectionArgs.selectedBorderColor != null - ? _chartState._selectionArgs.selectedBorderColor + _chartState._selectionArgs!.selectedBorderColor != null + ? _chartState._selectionArgs!.selectedBorderColor : selectionBehavior.selectedBorderColor ?? series.borderColor; } strokeOpacity = chartEventSelection != null && selectionArgs != null && - selectionArgs.selectedBorderColor != null - ? selectionArgs.selectedBorderColor.opacity + selectionArgs!.selectedBorderColor != null + ? selectionArgs!.selectedBorderColor!.opacity : _chartState._selectionArgs != null && - _chartState._selectionArgs.selectedBorderColor != null - ? _chartState._selectionArgs.selectedBorderColor.opacity + _chartState._selectionArgs!.selectedBorderColor != null + ? _chartState._selectionArgs!.selectedBorderColor.opacity : selectionBehavior.selectedOpacity ?? series.opacity; strokeWidth = chartEventSelection != null && selectionArgs != null && - selectionArgs.selectedBorderWidth != null - ? selectionArgs.selectedBorderWidth + selectionArgs!.selectedBorderWidth != null + ? selectionArgs!.selectedBorderWidth : _chartState._selectionArgs != null && - _chartState._selectionArgs.selectedBorderWidth != null - ? _chartState._selectionArgs.selectedBorderWidth + _chartState._selectionArgs!.selectedBorderWidth != null + ? _chartState._selectionArgs!.selectedBorderWidth : selectionBehavior.selectedBorderWidth ?? series.borderWidth; } else { if (series is CartesianSeries) { @@ -318,37 +317,38 @@ class _SelectionRenderer { segment is CandleSegment || segment is BoxAndWhiskerSegment ? strokeColor = chartEventSelection != null && selectionArgs != null - ? selectionArgs.unselectedColor + ? selectionArgs!.unselectedColor : _chartState._selectionArgs != null && - _chartState._selectionArgs.unselectedColor != null - ? _chartState._selectionArgs.unselectedColor + _chartState._selectionArgs!.unselectedColor != null + ? _chartState._selectionArgs!.unselectedColor : selectionBehavior.unselectedColor ?? - segment._defaultFillColor.color + segment._defaultFillColor!.color : strokeColor = chartEventSelection != null && selectionArgs != null && - selectionArgs.unselectedBorderColor != null - ? selectionArgs.unselectedBorderColor + selectionArgs!.unselectedBorderColor != null + ? selectionArgs!.unselectedBorderColor : _chartState._selectionArgs != null && - _chartState._selectionArgs.unselectedBorderColor != null - ? _chartState._selectionArgs.unselectedBorderColor + _chartState._selectionArgs!.unselectedBorderColor != + null + ? _chartState._selectionArgs!.unselectedBorderColor : selectionBehavior.unselectedBorderColor ?? series.borderColor; } strokeOpacity = chartEventSelection != null && selectionArgs != null && - selectionArgs.unselectedColor != null - ? selectionArgs.unselectedColor.opacity + selectionArgs!.unselectedColor != null + ? selectionArgs!.unselectedColor!.opacity : _chartState._selectionArgs != null && - _chartState._selectionArgs.unselectedColor != null - ? _chartState._selectionArgs.unselectedColor.opacity + _chartState._selectionArgs!.unselectedColor != null + ? _chartState._selectionArgs!.unselectedColor.opacity : selectionBehavior.unselectedOpacity ?? series.opacity; strokeWidth = chartEventSelection != null && selectionArgs != null && - selectionArgs.unselectedBorderWidth != null - ? selectionArgs.unselectedBorderWidth + selectionArgs!.unselectedBorderWidth != null + ? selectionArgs!.unselectedBorderWidth : _chartState._selectionArgs != null && - _chartState._selectionArgs.unselectedBorderWidth != null - ? _chartState._selectionArgs.unselectedBorderWidth + _chartState._selectionArgs!.unselectedBorderWidth != null + ? _chartState._selectionArgs!.unselectedBorderWidth : selectionBehavior.unselectedBorderWidth ?? series.borderWidth; } final Paint selectedStrokePaint = Paint(); @@ -448,13 +448,17 @@ class _SelectionRenderer { .visibleSeriesRenderers[unselectedSegments[k]._seriesIndex]; if (!seriesRenderer._seriesType.contains('area') && seriesRenderer._seriesType != 'fastline') { - final ChartSegment currentSegment = - seriesRenderer._segments[unselectedSegments[k].currentSegmentIndex]; - final Paint fillPaint = getDefaultFillColor(null, null, currentSegment); - currentSegment.fillPaint = fillPaint; - final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegment); - currentSegment.strokePaint = strokePaint; + if (unselectedSegments[k].currentSegmentIndex! < + seriesRenderer._segments.length) { + final ChartSegment currentSegment = seriesRenderer + ._segments[unselectedSegments[k].currentSegmentIndex]; + final Paint fillPaint = + getDefaultFillColor(null, null, currentSegment); + currentSegment.fillPaint = fillPaint; + final Paint strokePaint = + getDefaultStrokeColor(null, null, currentSegment); + currentSegment.strokePaint = strokePaint; + } unselectedSegments.remove(unselectedSegments[k]); k--; } else { @@ -479,29 +483,39 @@ class _SelectionRenderer { .visibleSeriesRenderers[selectedSegments[j]._seriesIndex]; if (!seriesRenderer._seriesType.contains('area') && seriesRenderer._seriesType != 'fastline') { - final ChartSegment currentSegment = - seriesRenderer._segments[selectedSegments[j].currentSegmentIndex]; - final Paint fillPaint = getDefaultFillColor(null, null, currentSegment); - currentSegment.fillPaint = fillPaint; - final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegment); - currentSegment.strokePaint = strokePaint; - if (seriesRenderer._seriesType == 'line' || - seriesRenderer._seriesType == 'spline' || - seriesRenderer._seriesType == 'stepline' || - seriesRenderer._seriesType == 'stackedline' || - seriesRenderer._seriesType == 'stackedline100' || - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType == 'candle' || - seriesRenderer._seriesType.contains('boxandwhisker')) { - if (selectedSegments[j].currentSegmentIndex == cartesianPointIndex && - selectedSegments[j]._seriesIndex == cartesianSeriesIndex) { - isSamePointSelect = true; - } - } else { - if (selectedSegments[j].currentSegmentIndex == pointIndex && - selectedSegments[j]._seriesIndex == seriesIndex) { - isSamePointSelect = true; + if (selectedSegments[j].currentSegmentIndex! < + seriesRenderer._segments.length) { + final ChartSegment currentSegment = + seriesRenderer._segments[selectedSegments[j].currentSegmentIndex]; + final Paint fillPaint = + getDefaultFillColor(null, null, currentSegment); + currentSegment.fillPaint = fillPaint; + final Paint strokePaint = + getDefaultStrokeColor(null, null, currentSegment); + currentSegment.strokePaint = strokePaint; + if (seriesRenderer._seriesType == 'line' || + seriesRenderer._seriesType == 'spline' || + seriesRenderer._seriesType == 'stepline' || + seriesRenderer._seriesType == 'stackedline' || + seriesRenderer._seriesType == 'stackedline100' || + seriesRenderer._seriesType.contains('hilo') || + seriesRenderer._seriesType == 'candle' || + seriesRenderer._seriesType.contains('boxandwhisker')) { + if (selectedSegments[j]._currentPoint!.overallDataPointIndex == + cartesianPointIndex && + selectedSegments[j]._seriesIndex == cartesianSeriesIndex) { + isSamePointSelect = true; + } + } else { + if ((currentSegment._currentPoint!.overallDataPointIndex == + pointIndex || + selectedSegments[j]._currentPoint!.overallDataPointIndex == + pointIndex) && + currentSegment._oldSegmentIndex == + selectedSegments[j]._oldSegmentIndex && + selectedSegments[j]._seriesIndex == seriesIndex) { + isSamePointSelect = true; + } } } selectedSegments.remove(selectedSegments[j]); @@ -523,7 +537,7 @@ class _SelectionRenderer { return isSamePointSelect; } - ChartSegment getTappedSegment() { + ChartSegment? getTappedSegment() { for (int i = 0; i < _chartState._chartSeries.visibleSeriesRenderers.length; i++) { @@ -541,19 +555,20 @@ class _SelectionRenderer { seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType == 'candle' || seriesRenderer._seriesType.contains('boxandwhisker')) { - if (currentSegment.currentSegmentIndex == cartesianPointIndex && - currentSegment._seriesIndex == cartesianSeriesIndex) { + if (currentSegment!.currentSegmentIndex == cartesianPointIndex && + currentSegment!._seriesIndex == cartesianSeriesIndex) { selectedSegment = seriesRenderer._segments[k]; } } else { - if (currentSegment.currentSegmentIndex == pointIndex && - currentSegment._seriesIndex == seriesIndex) { + if (currentSegment!._currentPoint!.overallDataPointIndex == + pointIndex && + currentSegment!._seriesIndex == seriesIndex) { selectedSegment = seriesRenderer._segments[k]; } } } else { currentSegment = seriesRenderer._segments[0]; - if (currentSegment._seriesIndex == seriesIndex) { + if (currentSegment!._seriesIndex == seriesIndex) { selectedSegment = seriesRenderer._segments[0]; break; } @@ -572,8 +587,9 @@ class _SelectionRenderer { _chartState._chartSeries.visibleSeriesRenderers[i]; for (int k = 0; k < seriesRenderer._segments.length; k++) { currentSegment = seriesRenderer._segments[k]; - if (currentSegment.currentSegmentIndex == pointIndex && - currentSegment._seriesIndex == seriesIndex) { + if ((currentSegment!._currentPoint!.overallDataPointIndex == + pointIndex) && + currentSegment!._seriesIndex == seriesIndex) { isSelected = true; break outerLoop; } else { @@ -586,13 +602,13 @@ class _SelectionRenderer { /// To ensure selection for cartesian chart type bool isCartesianSelection(SfCartesianChart chartAssign, - CartesianSeriesRenderer seriesAssign, int pointIndex, int seriesIndex) { + CartesianSeriesRenderer seriesAssign, int? pointIndex, int? seriesIndex) { chart = chartAssign; seriesRenderer = seriesAssign; if (chart.onSelectionChanged != null && selected) { chart.onSelectionChanged(getSelectionEventArgs(seriesRenderer._series, - seriesIndex, overallPointIndex, seriesRenderer)); + seriesIndex!, viewportIndex!, seriesRenderer)); selected = false; } @@ -607,7 +623,7 @@ class _SelectionRenderer { /// UnSelecting the last selected segment if (selectedSegments.length == 1) { - changeColorAndPopUnselectedSegments(unselectedSegments); + changeColorAndPopUnselectedSegments(unselectedSegments!); } /// Executes when multiSelection is enabled @@ -624,8 +640,9 @@ class _SelectionRenderer { /// To identify the tapped segment for (int k = 0; k < seriesRenderer._segments.length; k++) { currentSegment = seriesRenderer._segments[k]; - if (currentSegment.currentSegmentIndex == pointIndex && - currentSegment._seriesIndex == seriesIndex) { + if ((currentSegment!._currentPoint!.overallDataPointIndex == + pointIndex) && + currentSegment!._seriesIndex == seriesIndex) { selectedSegment = seriesRenderer._segments[k]; break; } @@ -635,9 +652,13 @@ class _SelectionRenderer { /// To identify that tapped segment in any one of the selected segment if (selectedSegment != null) { for (int k = 0; k < selectedSegments.length; k++) { - if (selectedSegment.currentSegmentIndex == - selectedSegments[k].currentSegmentIndex && - selectedSegment._seriesIndex == + if ((selectedSegment!.currentSegmentIndex == + selectedSegments[k].currentSegmentIndex || + selectedSegment!._currentPoint!.overallDataPointIndex == + selectedSegments[k] + ._currentPoint! + .overallDataPointIndex) && + selectedSegment!._seriesIndex == selectedSegments[k]._seriesIndex) { multiSelect = true; break; @@ -654,9 +675,14 @@ class _SelectionRenderer { ._segments[selectedSegments[j].currentSegmentIndex]; /// Applying default settings when last selected segment becomes unselected - if ((selectedSegment.currentSegmentIndex == - selectedSegments[j].currentSegmentIndex && - selectedSegment._seriesIndex == + if (((selectedSegment!.currentSegmentIndex == + selectedSegments[j].currentSegmentIndex || + selectedSegment! + ._currentPoint!.overallDataPointIndex == + selectedSegments[j] + ._currentPoint! + .overallDataPointIndex) && + selectedSegment!._seriesIndex == selectedSegments[j]._seriesIndex) && (selectedSegments.length == 1)) { final Paint fillPaint = @@ -666,7 +692,12 @@ class _SelectionRenderer { currentSegment.fillPaint = fillPaint; currentSegment.strokePaint = strokePaint; - if (selectedSegments[j].currentSegmentIndex == pointIndex && + if ((currentSegment._currentPoint!.overallDataPointIndex == + pointIndex || + selectedSegments[j] + ._currentPoint! + .overallDataPointIndex == + pointIndex) && selectedSegments[j]._seriesIndex == seriesIndex) { isSamePointSelect = true; } @@ -674,20 +705,27 @@ class _SelectionRenderer { } /// Applying unselected color for unselected segments in multiSelect option - else if (selectedSegment.currentSegmentIndex == - selectedSegments[j].currentSegmentIndex && - selectedSegment._seriesIndex == + else if ((selectedSegment!._currentPoint!.overallDataPointIndex == + selectedSegments[j] + ._currentPoint! + .overallDataPointIndex) && + selectedSegment!._seriesIndex == selectedSegments[j]._seriesIndex) { final Paint fillPaint = getFillColor(false, currentSegment); currentSegment.fillPaint = fillPaint; final Paint strokePaint = getStrokeColor(false, currentSegment); currentSegment.strokePaint = strokePaint; - if (selectedSegments[j].currentSegmentIndex == pointIndex && + if ((currentSegment._currentPoint!.overallDataPointIndex == + pointIndex || + selectedSegments[j] + ._currentPoint! + .overallDataPointIndex == + pointIndex) && selectedSegments[j]._seriesIndex == seriesIndex) { isSamePointSelect = true; } - unselectedSegments.add(selectedSegments[j]); + unselectedSegments!.add(selectedSegments[j]); selectedSegments.remove(selectedSegments[j]); } } @@ -712,6 +750,7 @@ class _SelectionRenderer { seriesRenderer._seriesType == 'waterfall' ? isSelected = checkPosition() : isSelected = true; + unselectedSegments?.clear(); for (int i = _chartState._chartSeries.visibleSeriesRenderers.length - 1; i >= 0; @@ -721,18 +760,22 @@ class _SelectionRenderer { if (isSelected) { for (int j = 0; j < seriesRenderer._segments.length; j++) { currentSegment = seriesRenderer._segments[j]; - if (currentSegment.currentSegmentIndex == null || + if (currentSegment!.currentSegmentIndex == null || pointIndex == null) { break; } - currentSegment.currentSegmentIndex == pointIndex && - currentSegment._seriesIndex == seriesIndex + (seriesRenderer._seriesType.contains('area') + ? currentSegment!.currentSegmentIndex == pointIndex + : currentSegment! + ._currentPoint!.overallDataPointIndex == + pointIndex) && + currentSegment!._seriesIndex == seriesIndex ? selectedSegments.add(seriesRenderer._segments[j]) - : unselectedSegments.add(seriesRenderer._segments[j]); + : unselectedSegments!.add(seriesRenderer._segments[j]); } /// Giving color to unselected segments - _unselectedSegmentsColors(unselectedSegments); + _unselectedSegmentsColors(unselectedSegments!); /// Giving Color to selected segments _selectedSegmentsColors(selectedSegments); @@ -756,9 +799,9 @@ class _SelectionRenderer { for (int k = 0; k < seriesRenderer._segments.length; k++) { currentSegment = seriesRenderer._segments[k]; final ChartSegment compareSegment = seriesRenderer._segments[k]; - if (currentSegment.currentSegmentIndex != + if (currentSegment!.currentSegmentIndex != compareSegment.currentSegmentIndex && - currentSegment._seriesIndex != compareSegment._seriesIndex) { + currentSegment!._seriesIndex != compareSegment._seriesIndex) { isSelected = false; } } @@ -766,7 +809,7 @@ class _SelectionRenderer { /// Executes only when final selected segment became unselected if (selectedSegments.length == seriesRenderer._segments.length) { - changeColorAndPopUnselectedSegments(unselectedSegments); + changeColorAndPopUnselectedSegments(unselectedSegments!); } /// Executes when multiSelect option is enabled @@ -803,7 +846,7 @@ class _SelectionRenderer { /// Applying series fill when all last selected segment becomes unselected if (!seriesRenderer._seriesType.contains('area') && seriesRenderer._seriesType != 'fastline') { - if ((selectedSegment._seriesIndex == + if ((selectedSegment!._seriesIndex == selectedSegments[j]._seriesIndex) && (selectedSegments.length <= seriesRenderer._segments.length)) { @@ -813,7 +856,10 @@ class _SelectionRenderer { getDefaultStrokeColor(null, null, currentSegment); currentSegment.fillPaint = fillPaint; currentSegment.strokePaint = strokePaint; - if (selectedSegments[j].currentSegmentIndex == pointIndex && + if (selectedSegments[j] + ._currentPoint! + .overallDataPointIndex == + pointIndex && selectedSegments[j]._seriesIndex == seriesIndex) { isSamePointSelect = true; } @@ -821,22 +867,25 @@ class _SelectionRenderer { } /// Applying unselected color for unselected segments in multiSelect option - else if (selectedSegment._seriesIndex == + else if (selectedSegment!._seriesIndex == selectedSegments[j]._seriesIndex) { final Paint fillPaint = getFillColor(false, currentSegment); final Paint strokePaint = getStrokeColor(false, currentSegment); currentSegment.fillPaint = fillPaint; currentSegment.strokePaint = strokePaint; - if (selectedSegments[j].currentSegmentIndex == pointIndex && + if (selectedSegments[j] + ._currentPoint! + .overallDataPointIndex == + pointIndex && selectedSegments[j]._seriesIndex == seriesIndex) { isSamePointSelect = true; } - unselectedSegments.add(selectedSegments[j]); + unselectedSegments!.add(selectedSegments[j]); selectedSegments.remove(selectedSegments[j]); } } else { - if ((selectedSegment._seriesIndex == + if ((selectedSegment!._seriesIndex == selectedSegments[j]._seriesIndex) && (selectedSegments.length <= seriesRenderer._segments.length)) { @@ -853,7 +902,7 @@ class _SelectionRenderer { } /// Applying unselected color for unselected segments in multiSelect option - else if (selectedSegment._seriesIndex == + else if (selectedSegment!._seriesIndex == selectedSegments[j]._seriesIndex) { final Paint fillPaint = getFillColor(false, currentSegment); final Paint strokePaint = @@ -863,7 +912,7 @@ class _SelectionRenderer { if (selectedSegments[j]._seriesIndex == seriesIndex) { isSamePointSelect = true; } - unselectedSegments.add(selectedSegments[j]); + unselectedSegments!.add(selectedSegments[j]); selectedSegments.remove(selectedSegments[j]); } } @@ -872,6 +921,7 @@ class _SelectionRenderer { } } else { ///Executes when multiSelect is not enable + unselectedSegments?.clear(); isSamePointSelect = changeColorAndPopSelectedSegments( selectedSegments, isSamePointSelect); } @@ -902,25 +952,27 @@ class _SelectionRenderer { if (seriesIndex != null) { for (int k = 0; k < seriesRenderer._segments.length; k++) { currentSegment = seriesRenderer._segments[k]; - currentSegment._seriesIndex == seriesIndex + currentSegment!._seriesIndex == seriesIndex ? selectedSegments.add(seriesRenderer._segments[k]) - : unselectedSegments.add(seriesRenderer._segments[k]); + : unselectedSegments!.add(seriesRenderer._segments[k]); } } } else { currentSegment = seriesRenderer._segments[0]; - currentSegment._seriesIndex == seriesIndex + currentSegment!._seriesIndex == seriesIndex ? selectedSegments.add(seriesRenderer._segments[0]) - : unselectedSegments.add(seriesRenderer._segments[0]); + : unselectedSegments!.add(seriesRenderer._segments[0]); } /// Give Color to the Unselected segment - _unselectedSegmentsColors(unselectedSegments); + _unselectedSegmentsColors(unselectedSegments!); /// Give Color to the Selected segment _selectedSegmentsColors(selectedSegments); } } + } else { + isSelected = true; } } } @@ -932,7 +984,7 @@ class _SelectionRenderer { /// Executes only when last selected segment became unselected if (selectedSegments.length == _chartState._chartSeries.visibleSeriesRenderers.length) { - changeColorAndPopUnselectedSegments(unselectedSegments); + changeColorAndPopUnselectedSegments(unselectedSegments!); } /// Executes when multiSelect option is enabled @@ -944,7 +996,7 @@ class _SelectionRenderer { /// To identify that tapped again in any one of the selected segment if (selectedSegment != null) { for (int k = 0; k < selectedSegments.length; k++) { - if (selectedSegment.currentSegmentIndex == + if (selectedSegment!.currentSegmentIndex == selectedSegments[k].currentSegmentIndex) { multiSelect = true; break; @@ -961,7 +1013,7 @@ class _SelectionRenderer { ._segments[selectedSegments[j].currentSegmentIndex]; /// Applying default settings when last selected segment becomes unselected - if ((selectedSegment.currentSegmentIndex == + if ((selectedSegment!.currentSegmentIndex == selectedSegments[j].currentSegmentIndex) && (selectedSegments.length <= _chartState._chartSeries.visibleSeriesRenderers.length)) { @@ -981,7 +1033,7 @@ class _SelectionRenderer { } /// Applying unselected color for unselected segment in multiSelect option - else if (selectedSegment.currentSegmentIndex == + else if (selectedSegment!.currentSegmentIndex == selectedSegments[j].currentSegmentIndex) { final Paint fillPaint = getFillColor(false, currentSegment); final Paint strokePaint = getStrokeColor(false, currentSegment); @@ -993,7 +1045,7 @@ class _SelectionRenderer { isSamePointSelect = true; } - unselectedSegments.add(selectedSegments[j]); + unselectedSegments!.add(selectedSegments[j]); selectedSegments.remove(selectedSegments[j]); } } @@ -1027,7 +1079,7 @@ class _SelectionRenderer { i++) { final CartesianSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; - if (currentSegment.currentSegmentIndex == null || + if (currentSegment!.currentSegmentIndex == null || pointIndex == null) { break; } @@ -1035,21 +1087,21 @@ class _SelectionRenderer { currentSegment = seriesRenderer._segments[k]; if (isSegmentSeries) { - currentSegment._currentPoint.xValue == - selectedSegment._currentPoint.xValue + currentSegment!._currentPoint!.xValue == + selectedSegment!._currentPoint!.xValue ? selectedSegments.add(seriesRenderer._segments[k]) - : unselectedSegments.add(seriesRenderer._segments[k]); + : unselectedSegments!.add(seriesRenderer._segments[k]); } else { - currentSegment.currentSegmentIndex == - selectedSegment.currentSegmentIndex + currentSegment!.currentSegmentIndex == + selectedSegment!.currentSegmentIndex ? selectedSegments.add(seriesRenderer._segments[k]) - : unselectedSegments.add(seriesRenderer._segments[k]); + : unselectedSegments!.add(seriesRenderer._segments[k]); } } } /// Giving color to unselected segments - _unselectedSegmentsColors(unselectedSegments); + _unselectedSegmentsColors(unselectedSegments!); /// Giving Color to selected segments _selectedSegmentsColors(selectedSegments); @@ -1065,24 +1117,28 @@ class _SelectionRenderer { // To get point index and series index void getPointAndSeriesIndex(SfCartesianChart chart, Offset position, CartesianSeriesRenderer seriesRenderer) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer; - ChartSegment currentSegment, selectedSegment; + if (selectionBehaviorRenderer == null) { + return; + } + ChartSegment currentSegment; + ChartSegment? selectedSegment; for (int k = 0; k < seriesRenderer._segments.length; k++) { currentSegment = seriesRenderer._segments[k]; - if (currentSegment._segmentRect.contains(position)) { + if (currentSegment._segmentRect!.contains(position)) { selected = true; selectedSegment = seriesRenderer._segments[k]; - overallPointIndex = selectedSegment?._currentPoint?.visiblePointIndex; + viewportIndex = selectedSegment._currentPoint?.visiblePointIndex; } } if (selectedSegment == null) { - selectionBehaviorRenderer._selectionRenderer.pointIndex = null; - selectionBehaviorRenderer._selectionRenderer.seriesIndex = null; + selectionBehaviorRenderer._selectionRenderer!.pointIndex = null; + selectionBehaviorRenderer._selectionRenderer!.seriesIndex = null; } else { - selectionBehaviorRenderer._selectionRenderer.pointIndex = - selectedSegment.currentSegmentIndex; - selectionBehaviorRenderer._selectionRenderer.seriesIndex = + selectionBehaviorRenderer._selectionRenderer!.pointIndex = + selectedSegment._currentPoint?.overallDataPointIndex; + selectionBehaviorRenderer._selectionRenderer!.seriesIndex = selectedSegment._seriesIndex; } } @@ -1124,21 +1180,23 @@ class _SelectionRenderer { /// To identify that series contains a given point bool _isSeriesContainsPoint( CartesianSeriesRenderer seriesRenderer, Offset position) { - int dataPointIndex; - ChartSegment startSegment; - ChartSegment endSegment; - final List> nearestDataPoints = + int? dataPointIndex; + ChartSegment? startSegment; + ChartSegment? endSegment; + final List>? nearestDataPoints = _getNearestChartPoints( position.dx, position.dy, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, seriesRenderer); if (nearestDataPoints == null) { return false; } for (final CartesianChartPoint dataPoint in nearestDataPoints) { - dataPointIndex = seriesRenderer._dataPoints.indexOf(dataPoint); + dataPointIndex = seriesRenderer + ._dataPoints[seriesRenderer._dataPoints.indexOf(dataPoint)] + .visiblePointIndex; } if (dataPointIndex != null && seriesRenderer._segments.isNotEmpty) { @@ -1146,7 +1204,6 @@ class _SelectionRenderer { seriesRenderer._seriesType == 'candle' || seriesRenderer._seriesType.contains('boxandwhisker')) { startSegment = seriesRenderer._segments[dataPointIndex]; - // endSegment = series.segments[dataPointIndex]; } else { if (dataPointIndex == 0) { startSegment = seriesRenderer._segments[dataPointIndex]; @@ -1159,11 +1216,11 @@ class _SelectionRenderer { } startSegment != null ? cartesianSeriesIndex = startSegment._seriesIndex - : cartesianSeriesIndex = endSegment._seriesIndex; + : cartesianSeriesIndex = endSegment!._seriesIndex; startSegment != null ? cartesianPointIndex = startSegment.currentSegmentIndex - : cartesianPointIndex = endSegment.currentSegmentIndex; + : cartesianPointIndex = endSegment!.currentSegmentIndex; if (startSegment != null) { if (_isSegmentIntersect(startSegment, position.dx, position.dy)) { @@ -1179,25 +1236,25 @@ class _SelectionRenderer { } /// To identify the cartesian point index - int getCartesianPointIndex(Offset position) { + int? getCartesianPointIndex(Offset position) { final List> firstNearestDataPoints = >[]; int previousIndex, nextIndex; - int dataPointIndex, + int? dataPointIndex, previousDataPointIndex, nextDataPointIndex, nearestDataPointIndex; - final List> nearestDataPoints = + final List>? nearestDataPoints = _getNearestChartPoints( position.dx, position.dy, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, seriesRenderer); - for (final CartesianChartPoint dataPoint in nearestDataPoints) { - dataPointIndex = seriesRenderer._dataPoints.indexOf(dataPoint); - overallPointIndex = dataPoint.visiblePointIndex; + for (final CartesianChartPoint dataPoint in nearestDataPoints!) { + dataPointIndex = dataPoint.overallDataPointIndex; + viewportIndex = dataPoint.visiblePointIndex; previousIndex = seriesRenderer._dataPoints.indexOf(dataPoint) - 1; previousIndex < 0 ? previousDataPointIndex = dataPointIndex @@ -1211,32 +1268,29 @@ class _SelectionRenderer { firstNearestDataPoints .add(seriesRenderer._dataPoints[previousDataPointIndex]); firstNearestDataPoints.add(seriesRenderer._dataPoints[nextDataPointIndex]); - final List> firstNearestPoints = + final List>? firstNearestPoints = _getNearestChartPoints( position.dx, position.dy, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, seriesRenderer, firstNearestDataPoints); - for (final CartesianChartPoint dataPoint in firstNearestPoints) { + for (final CartesianChartPoint dataPoint in firstNearestPoints!) { if (seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType == 'candle' || seriesRenderer._seriesType.contains('boxandwhisker')) { nearestDataPointIndex = dataPointIndex; } else { - if (dataPointIndex < seriesRenderer._dataPoints.indexOf(dataPoint)) { + if (dataPointIndex! < dataPoint.overallDataPointIndex!) { nearestDataPointIndex = dataPointIndex; - } else if (dataPointIndex == - seriesRenderer._dataPoints.indexOf(dataPoint)) { - seriesRenderer._dataPoints.indexOf(dataPoint) - 1 < 0 - ? nearestDataPointIndex = - seriesRenderer._dataPoints.indexOf(dataPoint) - : nearestDataPointIndex = - seriesRenderer._dataPoints.indexOf(dataPoint) - 1; + } else if (dataPointIndex == dataPoint.overallDataPointIndex) { + dataPoint.overallDataPointIndex! - 1 < 0 + ? nearestDataPointIndex = dataPoint.overallDataPointIndex + : nearestDataPointIndex = dataPoint.overallDataPointIndex! - 1; } else { - nearestDataPointIndex = seriesRenderer._dataPoints.indexOf(dataPoint); + nearestDataPointIndex = dataPoint.overallDataPointIndex; } } } @@ -1330,9 +1384,9 @@ class _SelectionRenderer { /// To get the index of the selected segment void getSelectedSeriesIndex(SfCartesianChart chart, Offset position, CartesianSeriesRenderer seriesRenderer) { - Rect currentSegment; - int seriesIndex; - SelectionBehaviorRenderer selectionBehaviorRenderer; + Rect? currentSegment; + int? seriesIndex; + SelectionBehaviorRenderer? selectionBehaviorRenderer; CartesianChartPoint point; outerLoop: for (int i = 0; @@ -1340,7 +1394,7 @@ class _SelectionRenderer { i++) { final CartesianSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; - selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer; + selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer!; for (int j = 0; j < seriesRenderer._dataPoints.length; j++) { point = seriesRenderer._dataPoints[j]; currentSegment = point.region; @@ -1350,14 +1404,16 @@ class _SelectionRenderer { } } } - selectionBehaviorRenderer._selectionRenderer.seriesIndex = seriesIndex; + if (selectionBehaviorRenderer == null) return; + selectionBehaviorRenderer._selectionRenderer!.seriesIndex = seriesIndex; } /// To do selection for cartesian type chart. void performSelection(Offset position) { bool select = false; bool isSelect = false; - int cartesianPointIndex; + _isInteraction = true; + int? cartesianPointIndex; if (seriesRenderer._seriesType == 'line' || seriesRenderer._seriesType == 'spline' || seriesRenderer._seriesType == 'stepline' || @@ -1403,8 +1459,16 @@ class _SelectionRenderer { if (selectedSegments.isNotEmpty) { for (int i = 0; i < selectedSegments.length; i++) { if (selectedSegments[i]._seriesIndex == currentSegment._seriesIndex && - selectedSegments[i].currentSegmentIndex == - currentSegment.currentSegmentIndex) { + (_isInteraction || currentSegment._oldSegmentIndex != -1) && + (seriesRenderer._seriesType.contains('area') + ? selectedSegments[i].currentSegmentIndex == + currentSegment.currentSegmentIndex + : selectedSegments[i]._currentPoint!.overallDataPointIndex == + (_isInteraction + ? currentSegment._currentPoint!.overallDataPointIndex + : (currentSegment._oldSegmentIndex ?? + currentSegment + ._currentPoint!.overallDataPointIndex)))) { _selectedSegmentsColors([currentSegment]); isSelected = true; break; @@ -1412,11 +1476,18 @@ class _SelectionRenderer { } } - if (!isSelected && unselectedSegments.isNotEmpty) { - for (int i = 0; i < unselectedSegments.length; i++) { - if (unselectedSegments[i]._seriesIndex == currentSegment._seriesIndex && - unselectedSegments[i].currentSegmentIndex == - currentSegment.currentSegmentIndex) { + if (!isSelected && unselectedSegments!.isNotEmpty) { + for (int i = 0; i < unselectedSegments!.length; i++) { + if (unselectedSegments![i]._seriesIndex == + currentSegment._seriesIndex && + (currentSegment._oldSegmentIndex == -1 || + currentSegment._oldSegmentIndex != + currentSegment.currentSegmentIndex || + seriesRenderer._seriesType.contains('area') + ? unselectedSegments![i].currentSegmentIndex == + currentSegment.currentSegmentIndex + : unselectedSegments![i]._currentPoint?.overallDataPointIndex == + currentSegment._currentPoint?.overallDataPointIndex)) { _unselectedSegmentsColors([currentSegment]); isSelected = true; break; @@ -1425,21 +1496,27 @@ class _SelectionRenderer { } } - SelectionArgs getSelectionEventArgs(dynamic series, num seriesIndex, - num pointIndex, CartesianSeriesRenderer seriesRender) { + SelectionArgs getSelectionEventArgs(dynamic series, int seriesIndex, + int pointIndex, CartesianSeriesRenderer seriesRender) { if (series != null) { - selectionArgs = SelectionArgs(seriesRenderer, seriesIndex, pointIndex, - seriesRender._visibleDataPoints[pointIndex].overallDataPointIndex); + selectionArgs = SelectionArgs( + seriesRenderer: seriesRenderer, + seriesIndex: seriesIndex, + viewportPointIndex: pointIndex, + pointIndex: seriesRender + ._visibleDataPoints![pointIndex].overallDataPointIndex!); final dynamic selectionBehavior = seriesRenderer._selectionBehavior; - selectionArgs.selectedBorderColor = selectionBehavior.selectedBorderColor; - selectionArgs.unselectedBorderColor = + selectionArgs!.selectedBorderColor = + selectionBehavior.selectedBorderColor; + selectionArgs!.unselectedBorderColor = selectionBehavior.unselectedBorderColor; - selectionArgs.selectedBorderWidth = selectionBehavior.selectedBorderWidth; - selectionArgs.unselectedBorderWidth = + selectionArgs!.selectedBorderWidth = + selectionBehavior.selectedBorderWidth; + selectionArgs!.unselectedBorderWidth = selectionBehavior.unselectedBorderWidth; - selectionArgs.selectedColor = selectionBehavior.selectedColor; - selectionArgs.unselectedColor = selectionBehavior.unselectedColor; + selectionArgs!.selectedColor = selectionBehavior.selectedColor; + selectionArgs!.unselectedColor = selectionBehavior.unselectedColor; } - return selectionArgs; + return selectionArgs!; } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_painter.dart deleted file mode 100644 index 5b5efd931..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_painter.dart +++ /dev/null @@ -1,1524 +0,0 @@ -part of charts; - -// ignore: must_be_immutable -class _ChartTooltipRenderer extends StatefulWidget { - // ignore: prefer_const_constructors_in_immutables - _ChartTooltipRenderer({this.chartState}) : chartWidget = chartState._chart; - - final dynamic chartWidget; - - final dynamic chartState; - - _ChartTooltipRendererState state; - - @override - State createState() { - return _ChartTooltipRendererState(); - } -} - -class _ChartTooltipRendererState extends State<_ChartTooltipRenderer> - with SingleTickerProviderStateMixin { - /// Animation controller for series - AnimationController animationController; - - /// Repaint notifier for crosshair container - ValueNotifier tooltipRepaintNotifier; - - bool show; - - //ignore: prefer_final_fields - bool _needMarker = true; - - @override - void initState() { - show = false; - tooltipRepaintNotifier = ValueNotifier(0); - animationController = AnimationController(vsync: this) - ..addListener(_repaintTooltipElements); - super.initState(); - } - - @override - Widget build(BuildContext context) { - widget.state = this; - animationController.duration = Duration( - milliseconds: widget.chartWidget.tooltipBehavior.animationDuration); - final Animation tooltipAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: animationController, - curve: const Interval(0.1, 0.8, curve: Curves.easeOutBack), - )); - animationController.forward(from: 0.0); - final _TooltipPainter tooltipPainter = _TooltipPainter( - tooltipAnimation: tooltipAnimation, - chartTooltipState: this, - notifier: tooltipRepaintNotifier, - animationController: animationController); - tooltipPainter._chart = widget.chartWidget; - tooltipPainter.tooltip = widget.chartWidget.tooltipBehavior; - tooltipPainter._chartState = widget.chartState; - tooltipPainter._chartState._tooltipBehaviorRenderer._painter = - tooltipPainter; - return Container( - child: RepaintBoundary(child: CustomPaint(painter: tooltipPainter))); - } - - @override - void dispose() { - _disposeAnimationController(animationController, _repaintTooltipElements); - super.dispose(); - } - - void _repaintTooltipElements() { - tooltipRepaintNotifier.value++; - } - - /// To show tooltip with position offsets - void _showTooltip(double x, double y) { - if (x != null && - y != null && - widget.chartState._tooltipBehaviorRenderer._painter != null) { - show = true; - widget.chartState._tooltipBehaviorRenderer._isHovering - ? widget.chartState._tooltipBehaviorRenderer._painter - .showMouseTooltip(x, y) - : widget.chartState._tooltipBehaviorRenderer._painter.show(x, y); - } - } -} - -/// Holds the tooltip series and point index -/// -/// This class is used to provide the [seriesIndex] and [pointIndex] for the Tooltip. -class TooltipValue { - /// Creating an argument constructor of TooltipValue class. - TooltipValue(this.seriesIndex, this.pointIndex, [this.outlierIndex]); - - ///Index of the series. - int seriesIndex; - - ///Index of data points. - int pointIndex; - - ///Index of outlier points. - int outlierIndex; -} - -class _TooltipPainter extends CustomPainter { - _TooltipPainter( - {this.chartTooltipState, - this.animationController, - this.tooltipAnimation, - ValueNotifier notifier}) - : super(repaint: notifier); - double pointerLength = 10; - double nosePointY = 0; - double nosePointX = 0; - double totalWidth = 0; - double x; - double y; - double xPos; - double yPos; - ValueNotifier valueNotifier; - bool isTop = false; - double borderRadius = 5; - Path arrowPath = Path(); - bool canResetPath = false; - Timer timer; - bool isLeft = false; - bool isRight = false; - Animation tooltipAnimation; - dynamic _chart; - bool enable; - num padding = 0; - String stringValue; - num markerPointY; - List textValues = []; - List seriesRendererCollection = - []; - String header; - Rect boundaryRect = const Rect.fromLTWH(0, 0, 0, 0); - Rect _tooltipRect; - dynamic tooltip; - dynamic _chartState; - dynamic currentSeries, dataPoint; - num pointIndex; - double markerSize; - Color markerColor; - CartesianSeriesRenderer seriesRenderer; - TooltipValue prevTooltipValue; - TooltipValue currentTooltipValue; - - final _ChartTooltipRendererState chartTooltipState; - - final AnimationController animationController; - - bool mouseTooltip = false; - - /// To render chart tooltip - // ignore:unused_element - void _renderTooltipView(Offset position) { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _chartState._tooltipBehaviorRenderer; - if (tooltipBehaviorRenderer._painter._chart is SfCartesianChart) { - _renderCartesianChartTooltip(position); - } else if (tooltipBehaviorRenderer._painter._chart is SfCircularChart) { - _renderCircularChartTooltip(position); - } else { - _renderTriangularChartTooltip(position); - } - } - - /// To render a chart tooltip for cartesian series - void _renderCartesianChartTooltip(Offset position) { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _chartState._tooltipBehaviorRenderer; - tooltipBehaviorRenderer._painter.boundaryRect = - _chartState._chartAxis._axisClipRect; - bool isContains = false; - if (_chartState._chartAxis._axisClipRect.contains(position)) { - Offset tooltipPosition; - double touchPadding; - Offset padding; - bool isTrendLine; - dynamic dataRect; - dynamic dataValues; - bool outlierTooltip = false; - int outlierTooltipIndex = -1; - for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; - i++) { - seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; - final CartesianSeries series = seriesRenderer._series; - if (seriesRenderer._visible && - series.enableTooltip && - seriesRenderer._regionalData != null) { - int count = 0; - seriesRenderer._regionalData - .forEach((dynamic regionRect, dynamic values) { - isTrendLine = values[values.length - 1].contains('true'); - touchPadding = ((seriesRenderer._seriesType == 'bubble' || - seriesRenderer._seriesType == 'scatter' || - seriesRenderer._seriesType.contains('column') || - seriesRenderer._seriesType.contains('bar') || - seriesRenderer._seriesType == 'histogram') && - !isTrendLine) - ? 0 - : _chartState._tooltipBehaviorRenderer._isHovering - ? 0 - : 15; // regional padding to detect smooth touch - final Rect region = regionRect[0]; - final List outlierRegion = regionRect[5]; - final double left = region.left - touchPadding; - final double right = region.right + touchPadding; - final double top = region.top - touchPadding; - final double bottom = region.bottom + touchPadding; - Rect paddedRegion = Rect.fromLTRB(left, top, right, bottom); - if (outlierRegion != null) { - for (int rectIndex = 0; - rectIndex < outlierRegion.length; - rectIndex++) { - if (outlierRegion[rectIndex].contains(position)) { - paddedRegion = outlierRegion[rectIndex]; - outlierTooltipIndex = rectIndex; - outlierTooltip = true; - } - } - } - if (paddedRegion.contains(position) && - (isTrendLine ? regionRect[4].isVisible : true)) { - if (seriesRenderer._seriesType != 'boxandwhisker' - ? !region.contains(position) - : (paddedRegion.contains(position) || - !region.contains(position))) { - tooltipBehaviorRenderer._painter.boundaryRect = - _chartState._containerRect; - } - tooltipBehaviorRenderer._painter.prevTooltipValue = - TooltipValue(i, count, outlierTooltipIndex); - currentSeries = seriesRenderer; - pointIndex = _chart is SfCartesianChart - ? regionRect[4].visiblePointIndex - : count; - dataPoint = regionRect[4]; - Color seriesColor = seriesRenderer._seriesColor; - if (seriesRenderer._seriesType == 'waterfall') { - seriesColor = _getWaterfallSeriesColor(seriesRenderer._series, - seriesRenderer._dataPoints[pointIndex], seriesColor); - } - markerColor = regionRect[2] ?? - seriesRenderer._series.markerSettings.borderColor ?? - seriesColor; - tooltipPosition = (outlierTooltipIndex >= 0) - ? regionRect[6][outlierTooltipIndex] - : regionRect[1]; - final List paddingData = !(seriesRenderer._isRectSeries && - tooltipBehaviorRenderer - ._tooltipBehavior.tooltipPosition != - TooltipPosition.auto) - ? _getTooltipPaddingData(seriesRenderer, isTrendLine, region, - paddedRegion, tooltipPosition) - : [const Offset(2, 2), tooltipPosition]; - padding = paddingData[0]; - tooltipPosition = paddingData[1]; - dataValues = values; - dataRect = regionRect; - isContains = mouseTooltip = true; - } - count++; - }); - } - } - if (isContains) { - seriesRenderer = currentSeries ?? seriesRenderer; - if (tooltipBehaviorRenderer._painter.prevTooltipValue != null && - tooltipBehaviorRenderer._painter.currentTooltipValue != null && - (tooltipBehaviorRenderer._painter.prevTooltipValue.pointIndex != - tooltipBehaviorRenderer - ._painter.currentTooltipValue.pointIndex || - tooltipBehaviorRenderer._painter.prevTooltipValue.seriesIndex != - tooltipBehaviorRenderer - ._painter.currentTooltipValue.seriesIndex)) { - tooltipBehaviorRenderer._painter.currentTooltipValue = null; - } else if (seriesRenderer._seriesType == 'boxandwhisker' && - tooltipBehaviorRenderer._painter.currentTooltipValue != null && - tooltipBehaviorRenderer._painter.prevTooltipValue.outlierIndex != - tooltipBehaviorRenderer - ._painter.currentTooltipValue.outlierIndex) { - tooltipBehaviorRenderer._painter.currentTooltipValue = null; - } - if (currentSeries._isRectSeries && - tooltip.tooltipPosition == TooltipPosition.pointer) { - tooltipPosition = position; - } - tooltipBehaviorRenderer._painter.padding = padding.dy; - String header = tooltip.header; - header = (header == null) - ? (tooltip.shared - ? dataValues[0] - : (isTrendLine - ? dataValues[dataValues.length - 2] - : currentSeries._series.name ?? currentSeries._seriesName)) - : header; - tooltipBehaviorRenderer._painter.header = header; - tooltipBehaviorRenderer._painter.stringValue = ''; - if (tooltip.shared) { - textValues = []; - seriesRendererCollection = []; - for (int j = 0; - j < _chartState._chartSeries.visibleSeriesRenderers.length; - j++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[j]; - if (seriesRenderer._visible && - seriesRenderer._series.enableTooltip) { - final int index = seriesRenderer._xValues.indexOf(dataRect[4].x); - if (index > -1) { - final String text = - (tooltipBehaviorRenderer._painter.stringValue != '' - ? '\n' - : '') + - _calculateCartesianTooltipText( - seriesRenderer, - seriesRenderer._dataPoints[index], - dataValues, - tooltipPosition, - outlierTooltip, - outlierTooltipIndex); - tooltipBehaviorRenderer._painter.stringValue += text; - textValues.add(text); - seriesRendererCollection.add(seriesRenderer); - } - } - } - } else { - tooltipBehaviorRenderer._painter.stringValue = - _calculateCartesianTooltipText( - currentSeries, - dataRect[4], - dataValues, - tooltipPosition, - outlierTooltip, - outlierTooltipIndex); - } - tooltipBehaviorRenderer._painter._calculateLocation(tooltipPosition); - } else { - if (!_chartState._tooltipBehaviorRenderer._isHovering) { - tooltipBehaviorRenderer._painter.prevTooltipValue = - tooltipBehaviorRenderer._painter.currentTooltipValue = null; - tooltip.hide(); - } else { - mouseTooltip = isContains; - } - } - } - } - - /// It returns the tooltip text of cartesian series - String _calculateCartesianTooltipText( - CartesianSeriesRenderer seriesRenderer, - CartesianChartPoint point, - dynamic values, - Offset tooltipPosition, - bool outlierTooltip, - int outlierTooltipIndex) { - final bool isTrendLine = values[values.length - 1].contains('true'); - String resultantString; - final ChartAxisRenderer axisRenderer = seriesRenderer._yAxisRenderer; - final int digits = seriesRenderer._chart.tooltipBehavior.decimalPlaces; - String minimumValue, - maximumValue, - lowerQuartileValue, - upperQuartileValue, - medianValue, - meanValue, - outlierValue, - highValue, - lowValue, - openValue, - closeValue, - cumulativeValue, - boxPlotString; - if (seriesRenderer._seriesType == 'boxandwhisker') { - minimumValue = _getLabelValue(point.minimum, axisRenderer._axis, digits); - maximumValue = _getLabelValue(point.maximum, axisRenderer._axis, digits); - lowerQuartileValue = - _getLabelValue(point.lowerQuartile, axisRenderer._axis, digits); - upperQuartileValue = - _getLabelValue(point.upperQuartile, axisRenderer._axis, digits); - medianValue = _getLabelValue(point.median, axisRenderer._axis, digits); - meanValue = _getLabelValue(point.mean, axisRenderer._axis, digits); - outlierValue = (point.outliers.isNotEmpty && outlierTooltipIndex >= 0) - ? _getLabelValue( - point.outliers[outlierTooltipIndex], axisRenderer._axis, digits) - : null; - boxPlotString = '\nMinimum : ' + - minimumValue + - '\nMaximum : ' + - maximumValue + - '\nMedian : ' + - medianValue + - '\nMean : ' + - meanValue + - '\nLQ : ' + - lowerQuartileValue + - '\nHQ : ' + - upperQuartileValue; - } else if (seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType == 'hilo' || - seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType == 'candle') { - highValue = _getLabelValue(point.high, axisRenderer._axis, digits); - lowValue = _getLabelValue(point.low, axisRenderer._axis, digits); - if (seriesRenderer._seriesType == 'candle' || - seriesRenderer._seriesType == 'hiloopenclose') { - openValue = _getLabelValue(point.open, axisRenderer._axis, digits); - closeValue = _getLabelValue(point.close, axisRenderer._axis, digits); - } - } else if (seriesRenderer._seriesType.contains('stacked')) { - cumulativeValue = - _getLabelValue(point.cumulativeValue, axisRenderer._axis, digits); - } - if (tooltip.format != null) { - resultantString = (seriesRenderer._seriesType.contains('range') || seriesRenderer._seriesType == 'hilo') && - !isTrendLine - ? (tooltip.format - .replaceAll('point.x', values[0]) - .replaceAll('point.high', highValue) - .replaceAll('point.low', lowValue) - .replaceAll('seriesRenderer._series.name', - seriesRenderer._series.name ?? seriesRenderer._seriesName)) - : (seriesRenderer._seriesType.contains('hiloopenclose') || seriesRenderer._seriesType.contains('candle')) && - !isTrendLine - ? (tooltip.format - .replaceAll('point.x', values[0]) - .replaceAll('point.high', highValue) - .replaceAll('point.low', lowValue) - .replaceAll('point.open', openValue) - .replaceAll('point.close', closeValue) - .replaceAll( - 'seriesRenderer._series.name', - seriesRenderer._series.name ?? - seriesRenderer._seriesName)) - : (seriesRenderer._seriesType.contains('boxandwhisker')) && - !isTrendLine - ? (tooltip.format - .replaceAll('point.x', values[0]) - .replaceAll('point.minimum', minimumValue) - .replaceAll('point.maximum', maximumValue) - .replaceAll('point.lowerQuartile', lowerQuartileValue) - .replaceAll('point.upperQuartile', upperQuartileValue) - .replaceAll('point.mean', meanValue) - .replaceAll('point.median', medianValue) - .replaceAll( - 'seriesRenderer._series.name', - seriesRenderer._series.name ?? - seriesRenderer._seriesName)) - : (tooltip.format - .replaceAll('point.x', values[0]) - .replaceAll('point.y', _getLabelValue(point.y, axisRenderer._axis, digits)) - .replaceAll('series.name', seriesRenderer._series.name ?? seriesRenderer._seriesName) - .replaceAll('point.size', _getLabelValue(point.bubbleSize, axisRenderer._axis, digits))); - if (seriesRenderer._seriesType.contains('stacked')) { - resultantString = resultantString.replaceAll( - 'point.cumulativeValue', cumulativeValue); - } - } else { - resultantString = (tooltip.shared - ? seriesRenderer._series.name ?? seriesRenderer._seriesName - : values[0]) + - (((seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType == 'hilo') && - !isTrendLine) - ? ('\nHigh : ' + highValue + '\nLow : ' + lowValue) - : (seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType == 'candle' - ? ('\nHigh : ' + - highValue + - '\nLow : ' + - lowValue + - '\nOpen : ' + - openValue + - '\nClose : ' + - closeValue) - : seriesRenderer._seriesType == 'boxandwhisker' - ? outlierValue != null - ? ('\nOutliers : ' + outlierValue) - : boxPlotString - : ' : ' + - _getLabelValue(point.y, axisRenderer._axis, digits))); - } - return resultantString; - } - - /// To render a chart tooltip for circular series - void _renderCircularChartTooltip(Offset position) { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _chartState._tooltipBehaviorRenderer; - final SfCircularChart chart = tooltipBehaviorRenderer._painter._chart; - tooltipBehaviorRenderer._painter.boundaryRect = - _chartState._chartContainerRect; - bool isContains = false; - final _Region pointRegion = _getCircularPointRegion( - chart, position, _chartState._chartSeries.visibleSeriesRenderers[0]); - if (pointRegion != null && - _chartState._chartSeries.visibleSeriesRenderers[pointRegion.seriesIndex] - ._series.enableTooltip) { - tooltipBehaviorRenderer._painter.prevTooltipValue = - TooltipValue(pointRegion.seriesIndex, pointRegion.pointIndex); - if (tooltipBehaviorRenderer._painter.prevTooltipValue != null && - tooltipBehaviorRenderer._painter.currentTooltipValue != null && - tooltipBehaviorRenderer._painter.prevTooltipValue.pointIndex != - tooltipBehaviorRenderer._painter.currentTooltipValue.pointIndex) { - tooltipBehaviorRenderer._painter.currentTooltipValue = null; - } - final ChartPoint chartPoint = _chartState - ._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - ._renderPoints[pointRegion.pointIndex]; - final Offset location = - chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer - ? position - : _degreeToPoint( - chartPoint.midAngle, - (chartPoint.innerRadius + chartPoint.outerRadius) / 2, - chartPoint.center); - currentSeries = pointRegion.seriesIndex; - pointIndex = pointRegion.pointIndex; - dataPoint = _chartState - ._chartSeries.visibleSeriesRenderers[0]._dataPoints[pointIndex]; - final int digits = chart.tooltipBehavior.decimalPlaces; - String header = chart.tooltipBehavior.header; - header = (header == null) - // ignore: prefer_if_null_operators - ? _chartState - ._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - ._series - .name != - null - ? _chartState._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex]._series.name - : null - : header; - _chartState._tooltipBehaviorRenderer._painter.header = header; - if (chart.tooltipBehavior.format != null) { - final String resultantString = chart.tooltipBehavior.format - .replaceAll('point.x', chartPoint.x.toString()) - .replaceAll('point.y', _getDecimalLabelValue(chartPoint.y, digits)) - .replaceAll( - 'series.name', - _chartState - ._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - ._series - .name ?? - 'series.name'); - _chartState._tooltipBehaviorRenderer._painter.stringValue = - resultantString; - _chartState._tooltipBehaviorRenderer._painter - ._calculateLocation(location); - } else { - _chartState._tooltipBehaviorRenderer._painter.stringValue = - (chartPoint.x.toString() + - ' : ' + - _getDecimalLabelValue(chartPoint.y, digits)); - _chartState._tooltipBehaviorRenderer._painter - ._calculateLocation(location); - } - isContains = true; - } else { - chart.tooltipBehavior.hide(); - isContains = false; - } - mouseTooltip = isContains; - if (!isContains) { - tooltipBehaviorRenderer._painter.prevTooltipValue = - tooltipBehaviorRenderer._painter.currentTooltipValue = null; - } - } - - /// To render a chart tooltip for triangular series - void _renderTriangularChartTooltip(Offset position) { - final dynamic chart = _chart; - final dynamic chartState = _chartState; - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - chartState._tooltipBehaviorRenderer; - chartState._tooltipBehaviorRenderer._painter.boundaryRect = - chartState._chartContainerRect; - bool isContains = false; - const num seriesIndex = 0; - pointIndex = - _chartState._tooltipPointIndex ?? _chartState._currentActive.pointIndex; - dataPoint = _chartState - ._chartSeries.visibleSeriesRenderers[0]._dataPoints[pointIndex]; - _chartState._tooltipPointIndex = null; - final int digits = chart.tooltipBehavior.decimalPlaces; - if (chart.tooltipBehavior.enable) { - tooltipBehaviorRenderer._painter.prevTooltipValue = - TooltipValue(seriesIndex, pointIndex); - if (tooltipBehaviorRenderer._painter.prevTooltipValue != null && - tooltipBehaviorRenderer._painter.currentTooltipValue != null && - tooltipBehaviorRenderer._painter.prevTooltipValue.pointIndex != - tooltipBehaviorRenderer._painter.currentTooltipValue.pointIndex) { - tooltipBehaviorRenderer._painter.currentTooltipValue = null; - } - final PointInfo chartPoint = _chartState._chartSeries - .visibleSeriesRenderers[seriesIndex]._renderPoints[pointIndex]; - final Offset location = chart.tooltipBehavior.tooltipPosition == - TooltipPosition.pointer && - _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._series.explode - ? chartPoint.symbolLocation - : chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer && - !_chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._series.explode - ? position - : chartPoint.symbolLocation; - currentSeries = seriesIndex; - pointIndex = pointIndex; - String header = chart.tooltipBehavior.header; - header = (header == null) - // ignore: prefer_if_null_operators - ? _chartState._chartSeries.visibleSeriesRenderers[seriesIndex]._series - .name != - null - ? _chartState - ._chartSeries.visibleSeriesRenderers[seriesIndex]._series.name - : null - : header; - _chartState._tooltipBehaviorRenderer._painter.header = header; - if (chart.tooltipBehavior.format != null) { - final String resultantString = chart.tooltipBehavior.format - .replaceAll('point.x', chartPoint.x.toString()) - .replaceAll('point.y', _getDecimalLabelValue(chartPoint.y, digits)) - .replaceAll( - 'series.name', - _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._series.name ?? - 'series.name'); - _chartState._tooltipBehaviorRenderer._painter.stringValue = - resultantString; - _chartState._tooltipBehaviorRenderer._painter - ._calculateLocation(location); - } else { - _chartState._tooltipBehaviorRenderer._painter.stringValue = - chartPoint.x.toString() + - ' : ' + - _getDecimalLabelValue(chartPoint.y, digits); - _chartState._tooltipBehaviorRenderer._painter - ._calculateLocation(location); - } - isContains = true; - } else { - chart.tooltipBehavior.hide(); - isContains = false; - } - mouseTooltip = isContains; - if (!isContains) { - chartState._tooltipBehaviorRenderer._painter._painter.prevTooltipValue = - chartState._tooltipBehaviorRenderer._painter._painter - .currentTooltipValue = null; - } - } - - /// To get the location of chart tooltip - void _calculateLocation(Offset position) { - x = position?.dx; - y = position?.dy; - } - - @override - void paint(Canvas canvas, Size size) { - if (chartTooltipState.show) { - if (_chart is SfCartesianChart && _chartState is SfCartesianChartState) { - _chartState._tooltipBehaviorRenderer.onPaint(canvas); - } else if (_chart is SfCircularChart && - _chartState is SfCircularChartState) { - _chartState._tooltipBehaviorRenderer.onPaint(canvas); - } else if (_chart is SfPyramidChart && - _chartState is SfPyramidChartState) { - _chartState._tooltipBehaviorRenderer.onPaint(canvas); - } else if (_chart is SfFunnelChart && _chartState is SfFunnelChartState) { - _chartState._tooltipBehaviorRenderer.onPaint(canvas); - } - } - } - - /// To render tooltip - void _renderTooltip(Canvas canvas) { - isLeft = false; - isRight = false; - double height = 0, width = 0, headerTextWidth = 0, headerTextHeight = 0; - TooltipArgs tooltipArgs; - Size textSize, headerSize; - markerSize = 0; - - if (x != null && - y != null && - stringValue != null && - (!(_chart is SfCartesianChart) || !_chartState._requireAxisTooltip)) { - final int seriesIndex = _chart is SfCartesianChart - ? currentSeries._segments[0]._seriesIndex - : currentSeries; - if (_chart.onTooltipRender != null && !dataPoint.isTooltipRenderEvent) { - dataPoint.isTooltipRenderEvent = true; - tooltipArgs = TooltipArgs( - _chart is SfCartesianChart - ? currentSeries._segments[0]._seriesIndex - : currentSeries, - _chartState - ._chartSeries.visibleSeriesRenderers[seriesIndex]._dataPoints, - pointIndex, - _chart is SfCartesianChart - ? _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._visibleDataPoints[pointIndex].overallDataPointIndex - : pointIndex); - tooltipArgs.text = stringValue; - tooltipArgs.header = header; - tooltipArgs.locationX = x; - tooltipArgs.locationY = y; - _chart.onTooltipRender(tooltipArgs); - stringValue = tooltipArgs.text; - header = tooltipArgs.header; - x = tooltipArgs.locationX; - y = tooltipArgs.locationY; - } - if (tooltipAnimation.status == AnimationStatus.completed) { - dataPoint?.isTooltipRenderEvent = false; - } - } - - totalWidth = boundaryRect.left.toDouble() + boundaryRect.width.toDouble(); - final TextStyle textStyle = - _getTooltiptextStyle(tooltip.textStyle, false, true); - textSize = _measureText(stringValue, textStyle); - width = textSize.width; - height = textSize.height; - if (header != null && header.isNotEmpty) { - final TextStyle headerTextStyle = - _getTooltiptextStyle(tooltip.textStyle, true, true); - headerSize = _measureText(header, headerTextStyle); - headerTextWidth = headerSize.width; - headerTextHeight = headerSize.height + 10; - width = width > headerTextWidth ? width : headerTextWidth; - } - - if (width < 10) { - width = 10; // minimum width for tooltip to render - borderRadius = borderRadius > 5 ? 5 : borderRadius; - } - if (borderRadius > 15) { - borderRadius = 15; - } - - if (x != null && - y != null && - padding != null && - (stringValue != '' && stringValue != null || - header != '' && header != null)) { - _calculateBackgroundRect(canvas, height, width, headerTextHeight); - } - } - - /// calculate tooltip rect and arrow head - void _calculateBackgroundRect( - Canvas canvas, double height, double width, double headerTextHeight) { - double widthPadding = 15; - Rect boundaryRect; - if (_chart is SfCartesianChart && - tooltip.canShowMarker != null && - tooltip.canShowMarker && - chartTooltipState._needMarker) { - markerSize = 5; - widthPadding = 17; - } - if (_chart is SfCartesianChart && - (!_chartState._requireAxisTooltip && - (currentTooltipValue != null && - currentTooltipValue.seriesIndex != null))) { - boundaryRect = _chartState._chartAxis._axisClipRect; - } else if ((_chart is SfCartesianChart) && - (_chartState._requireAxisTooltip || - (currentTooltipValue != null && - currentTooltipValue.seriesIndex == null))) { - boundaryRect = _chartState._containerRect; - } else { - if (_chart is SfCartesianChart) { - return; - } else { - boundaryRect = _chartState._chartAreaRect; - } - } - - Rect rect = Rect.fromLTWH(x, y, width + (2 * markerSize) + widthPadding, - height + headerTextHeight + 10); - final Rect newRect = Rect.fromLTWH(boundaryRect.left + 20, boundaryRect.top, - boundaryRect.width - 40, boundaryRect.height); - final Rect leftRect = Rect.fromLTWH( - boundaryRect.left - 5, - boundaryRect.top - 20, - newRect.left - (boundaryRect.left - 5), - boundaryRect.height + 40); - final Rect rightRect = Rect.fromLTWH(newRect.right, boundaryRect.top - 20, - (boundaryRect.right + 5) + newRect.right, boundaryRect.height + 40); - - if (leftRect.contains(Offset(x, y))) { - isLeft = true; - isRight = false; - } else if (rightRect.contains(Offset(x, y))) { - isLeft = false; - isRight = true; - } - - if (y > pointerLength + rect.height && y > boundaryRect.top) { - if (_chart is SfCartesianChart && !_chartState._requireAxisTooltip) { - if (currentSeries._seriesType == 'bubble') { - padding = 2; - } - } - isTop = true; - xPos = x - (rect.width / 2); - yPos = (y - rect.height) - padding; - nosePointY = rect.top - padding; - nosePointX = rect.left; - final double tooltipRightEnd = x + (rect.width / 2); - if (_chart is SfCartesianChart && _chartState._requireAxisTooltip) { - xPos = xPos < boundaryRect.left ? boundaryRect.left : xPos; - } else { - xPos = xPos < boundaryRect.left - ? boundaryRect.left - : tooltipRightEnd > totalWidth - ? totalWidth - rect.width - : xPos; - } - yPos = yPos - (pointerLength / 2); - } else { - isTop = false; - xPos = x - (rect.width / 2); - yPos = - ((y >= boundaryRect.top ? y : boundaryRect.top) + pointerLength / 2) + - padding; - nosePointX = rect.left; - nosePointY = (y >= boundaryRect.top ? y : boundaryRect.top) + padding; - final double tooltipRightEnd = x + (rect.width / 2); - if (_chart is SfCartesianChart && _chartState._requireAxisTooltip) { - xPos = xPos < boundaryRect.left ? boundaryRect.left : xPos; - } else { - xPos = xPos < boundaryRect.left - ? boundaryRect.left - : tooltipRightEnd > totalWidth - ? totalWidth - rect.width - : xPos; - } - } - if (xPos <= boundaryRect.left + 5) { - xPos = xPos + 5; - } else if (xPos + rect.width >= totalWidth - 5) { - xPos = xPos - 5; - } - rect = Rect.fromLTWH(xPos, yPos, rect.width, rect.height); - if (boundaryRect.right < rect.right && - _chart is SfCartesianChart && - _chartState._requireAxisTooltip) { - const padding = 5; - rect = Rect.fromLTRB(boundaryRect.right - width - padding, rect.top, - boundaryRect.right + padding, rect.bottom); - } - _drawTooltipBackground(canvas, rect, nosePointX, nosePointY, borderRadius, - isTop, arrowPath, isLeft, isRight, tooltipAnimation); - } - - /// To draw the tooltip background - void _drawTooltipBackground( - Canvas canvas, - Rect rectF, - double xPos, - double yPos, - double borderRadius, - bool isTop, - Path backgroundPath, - bool isLeft, - bool isRight, - Animation tooltipAnimation) { - final double startArrow = pointerLength / 2; - final double endArrow = pointerLength / 2; - if (isTop) { - _drawTooltip( - canvas, - isTop, - rectF, - xPos, - yPos, - xPos - startArrow, - yPos - startArrow, - xPos + endArrow, - yPos - endArrow, - borderRadius, - backgroundPath, - isLeft, - isRight, - tooltipAnimation); - } else { - _drawTooltip( - canvas, - isTop, - rectF, - xPos, - yPos, - xPos - startArrow, - yPos + startArrow, - xPos + endArrow, - yPos + endArrow, - borderRadius, - backgroundPath, - isLeft, - isRight, - tooltipAnimation); - } - } - - void _drawTooltip( - Canvas canvas, - bool isTop, - Rect rectF, - double xPos, - double yPos, - double startX, - double startY, - double endX, - double endY, - double borderRadius, - Path backgroundPath, - bool isLeft, - bool isRight, - Animation tooltipAnimation) { - double animationFactor = 0; - if (tooltipAnimation == null) { - animationFactor = 1; - } else { - animationFactor = tooltipAnimation.value; - } - backgroundPath.reset(); - if (!canResetPath) { - if (isLeft) { - startX = rectF.left + (2 * borderRadius); - endX = startX + pointerLength; - } else if (isRight) { - startX = endX - pointerLength; - endX = rectF.right - (2 * borderRadius); - } - - final Rect rect = Rect.fromLTWH( - rectF.width / 2 + (rectF.left - rectF.width / 2 * animationFactor), - rectF.height / 2 + (rectF.top - rectF.height / 2 * animationFactor), - rectF.width * animationFactor, - rectF.height * animationFactor); - - _tooltipRect = rect; - - final RRect tooltipRect = RRect.fromRectAndCorners( - rect, - bottomLeft: Radius.circular(borderRadius), - bottomRight: Radius.circular(borderRadius), - topLeft: Radius.circular(borderRadius), - topRight: Radius.circular(borderRadius), - ); - _drawTooltipPath(canvas, tooltipRect, rect, backgroundPath, isTop, isLeft, - isRight, startX, endX, animationFactor, xPos, yPos); - final TextStyle textStyle = _getTooltiptextStyle( - tooltip.textStyle, false, false, animationFactor); - final Size result = _measureText(stringValue, textStyle); - _drawTooltipText(canvas, tooltipRect, textStyle, result, animationFactor); - if (_chart is SfCartesianChart && - tooltip.canShowMarker && - chartTooltipState._needMarker) { - _drawTooltipMarker( - canvas, tooltipRect, textStyle, animationFactor, result); - } - xPos = null; - yPos = null; - } - } - - /// draw the tooltip rect path - void _drawTooltipPath( - Canvas canvas, - RRect tooltipRect, - Rect rect, - Path backgroundPath, - bool isTop, - bool isLeft, - bool isRight, - double startX, - double endX, - double animationFactor, - double xPos, - double yPos) { - double factor = 0; - assert(tooltip.elevation != null ? tooltip.elevation >= 0 : true, - 'The elevation of the tooltip for all series must not be less than 0.'); - if (isTop && isRight) { - factor = rect.bottom; - backgroundPath.moveTo(rect.right - 20, factor); - backgroundPath.lineTo(xPos, yPos); - backgroundPath.lineTo(rect.right, factor - borderRadius); - backgroundPath.arcToPoint(Offset(rect.right - borderRadius, factor), - radius: Radius.circular(borderRadius)); - backgroundPath.lineTo(rect.right - 20, factor); - } else if (!isTop && isRight) { - factor = rect.top; - backgroundPath.moveTo(rect.right - 20, factor); - backgroundPath.lineTo(xPos, yPos); - backgroundPath.lineTo(rect.right, factor + borderRadius); - backgroundPath.arcToPoint(Offset(rect.right - borderRadius, factor), - radius: Radius.circular(borderRadius), clockwise: false); - backgroundPath.lineTo(rect.right - 20, factor); - } else if (isTop && isLeft) { - factor = rect.bottom; - backgroundPath.moveTo(rect.left + 20, factor); - backgroundPath.lineTo(xPos, yPos); - backgroundPath.lineTo(rect.left, factor - borderRadius); - backgroundPath.arcToPoint(Offset(rect.left + borderRadius, factor), - radius: Radius.circular(borderRadius), clockwise: false); - backgroundPath.lineTo(rect.left + 20, factor); - } else if (!isTop && isLeft) { - factor = rect.top; - backgroundPath.moveTo(rect.left + 20, factor); - backgroundPath.lineTo(xPos, yPos); - backgroundPath.lineTo(rect.left, factor + borderRadius); - backgroundPath.arcToPoint(Offset(rect.left + borderRadius, factor), - radius: Radius.circular(borderRadius)); - backgroundPath.lineTo(rect.left + 20, factor); - } else { - if (isTop) { - factor = tooltipRect.bottom; - } else { - factor = tooltipRect.top; - } - backgroundPath.moveTo(startX - ((endX - startX) / 4), factor); - backgroundPath.lineTo(xPos, yPos); - backgroundPath.lineTo(endX + ((endX - startX) / 4), factor); - backgroundPath.lineTo(startX + ((endX - startX) / 4), factor); - } - final Paint fillPaint = Paint() - ..color = (tooltip.color ?? _chartState._chartTheme.tooltipColor) - .withOpacity(tooltip.opacity) - ..strokeCap = StrokeCap.round - ..style = PaintingStyle.fill; - - final Paint strokePaint = Paint() - ..color = tooltip.borderColor == Colors.transparent - ? Colors.transparent - : tooltip.borderColor.withOpacity(tooltip.opacity) - ..strokeCap = StrokeCap.butt - ..style = PaintingStyle.stroke - ..strokeWidth = tooltip.borderWidth; - tooltip.borderWidth == 0 - ? strokePaint.color = Colors.transparent - : strokePaint.color = strokePaint.color; - - final Path tooltipPath = Path(); - tooltipPath.addRRect(tooltipRect); - if (tooltip.elevation > 0) { - if (tooltipRect.width * animationFactor > tooltipRect.width * 0.85) { - canvas.drawShadow(arrowPath, tooltip.shadowColor ?? fillPaint.color, - tooltip.elevation, true); - } - canvas.drawShadow(tooltipPath, tooltip.shadowColor ?? fillPaint.color, - tooltip.elevation, true); - } - - if (tooltipRect.width * animationFactor > tooltipRect.width * 0.85) { - canvas.drawPath(arrowPath, fillPaint); - canvas.drawPath(arrowPath, strokePaint); - } - canvas.drawPath(tooltipPath, fillPaint); - canvas.drawPath(tooltipPath, strokePaint); - } - - /// draw marker inside the tooltip - void _drawTooltipMarker(Canvas canvas, RRect tooltipRect, TextStyle textStyle, - double animationFactor, Size tooltipMarkerResult) { - final Size tooltipStringResult = tooltipMarkerResult; - if (tooltip.shared) { - Size result1 = const Size(0, 0); - String str = ''; - for (int i = 0; i < textValues.length; i++) { - str += textValues[i]; - final Size result = _measureText(str, textStyle); - final Offset markerPoint = Offset( - tooltipRect.left + - tooltipRect.width / 2 - - tooltipStringResult.width / 2, - (markerPointY + result1.height) - markerSize); - result1 = result; - final CartesianSeriesRenderer _seriesRenderer = - seriesRendererCollection[i]; - _renderMarker(markerPoint, _seriesRenderer, animationFactor, canvas); - } - } else { - final Offset markerPoint = Offset( - tooltipRect.left + - tooltipRect.width / 2 - - tooltipStringResult.width / 2, - ((tooltipRect.top + tooltipRect.height) - - tooltipStringResult.height / 2) - - markerSize); - _renderMarker(markerPoint, seriesRenderer, animationFactor, canvas); - } - } - - /// To render marker for the chart tooltip - void _renderMarker( - Offset markerPoint, - CartesianSeriesRenderer _seriesRenderer, - double animationFactor, - Canvas canvas) { - _seriesRenderer._isMarkerRenderEvent = true; - final Path markerPath = _getMarkerShapesPath( - _seriesRenderer._series.markerSettings.shape, - markerPoint, - Size((2 * markerSize) * animationFactor, - (2 * markerSize) * animationFactor), - _seriesRenderer); - - if (_seriesRenderer._series.markerSettings.shape == DataMarkerType.image) { - _drawImageMarker(_seriesRenderer, canvas, markerPoint.dx, markerPoint.dy); - } - - Paint markerPaint = Paint(); - markerPaint.color = (!tooltip.shared - ? markerColor - : _seriesRenderer._series.markerSettings.borderColor ?? - _seriesRenderer._seriesColor ?? - _seriesRenderer._series.color) - .withOpacity(tooltip.opacity); - if (_seriesRenderer._series.gradient != null) { - markerPaint = _getLinearGradientPaint( - _seriesRenderer._series.gradient, - _getMarkerShapesPath( - _seriesRenderer._series.markerSettings.shape, - Offset(markerPoint.dx, markerPoint.dy), - Size((2 * markerSize) * animationFactor, - (2 * markerSize) * animationFactor), - _seriesRenderer) - .getBounds(), - _seriesRenderer._chartState._requireInvertedAxis); - } - canvas.drawPath(markerPath, markerPaint); - final Paint markerBorderPaint = Paint(); - markerBorderPaint.color = Colors.white.withOpacity(tooltip.opacity); - markerBorderPaint.strokeWidth = 1; - markerBorderPaint.style = PaintingStyle.stroke; - canvas.drawPath(markerPath, markerBorderPaint); - } - - /// draw tooltip header, divider,text - void _drawTooltipText(Canvas canvas, RRect tooltipRect, TextStyle textStyle, - Size result, double animationFactor) { - const double padding = 10; - final num _maxLinesOfTooltipContent = _getMaxLinesContent(stringValue); - if (header != null && header.isNotEmpty) { - final TextStyle headerTextStyle = - _getTooltiptextStyle(tooltip.textStyle, true, false, animationFactor); - final Size headerResult = _measureText(header, headerTextStyle); - final num _maxLinesOfHeader = _getMaxLinesContent(header); - _drawText( - tooltip, - canvas, - header, - Offset( - (tooltipRect.left + tooltipRect.width / 2) - - headerResult.width / 2, - tooltipRect.top + padding / 2), - headerTextStyle, - _maxLinesOfHeader); - - final Paint dividerPaint = Paint(); - dividerPaint.color = _chartState._chartTheme.tooltipLabelColor - .withOpacity(tooltip.opacity); - dividerPaint.strokeWidth = 0.5 * animationFactor; - dividerPaint.style = PaintingStyle.stroke; - num lineOffset = 0; - if (tooltip != null && - tooltip.format != null && - tooltip.format.isNotEmpty) { - if (tooltip.textAlignment == ChartAlignment.near) { - lineOffset = padding; - } else if (tooltip.textAlignment == ChartAlignment.far) { - lineOffset = -padding; - } - } - if (animationFactor > 0.5) { - canvas.drawLine( - Offset(tooltipRect.left + padding - lineOffset, - tooltipRect.top + headerResult.height + padding), - Offset(tooltipRect.right - padding - lineOffset, - tooltipRect.top + headerResult.height + padding), - dividerPaint); - } - markerPointY = tooltipRect.top + headerResult.height + (padding * 2) + 6; - _drawText( - tooltip, - canvas, - stringValue, - Offset( - (tooltipRect.left + 2 * markerSize + tooltipRect.width / 2) - - result.width / 2, - (tooltipRect.top + tooltipRect.height) - result.height - 5), - textStyle, - _maxLinesOfTooltipContent); - } else { - _drawText( - tooltip, - canvas, - stringValue, - Offset( - (tooltipRect.left + 2 * markerSize + tooltipRect.width / 2) - - result.width / 2, - (tooltipRect.top + tooltipRect.height / 2) - result.height / 2), - textStyle, - _maxLinesOfTooltipContent); - } - } - - /// Get the text style of tooltip. - TextStyle _getTooltiptextStyle( - TextStyle textStyle, bool isHeader, bool isMeasureText, - [double animationFactor]) { - /// Default size of tooltip text, if the user is not spcified the font size. - const int textSize = 12; - final TextStyle tooltipTextStyle = TextStyle( - color: textStyle.color?.withOpacity(tooltip.opacity) ?? - _chartState._chartTheme.tooltipLabelColor, - fontSize: isMeasureText - ? textStyle.fontSize - : (textStyle.fontSize ?? textSize) * animationFactor, - fontWeight: isHeader ? FontWeight.bold : textStyle.fontWeight, - fontFamily: textStyle.fontFamily, - fontStyle: textStyle.fontStyle, - inherit: textStyle.inherit, - backgroundColor: textStyle.backgroundColor, - letterSpacing: textStyle.letterSpacing, - wordSpacing: textStyle.wordSpacing, - textBaseline: textStyle.textBaseline, - height: textStyle.height, - locale: textStyle.locale, - foreground: textStyle.foreground, - background: textStyle.background, - shadows: textStyle.shadows, - fontFeatures: textStyle.fontFeatures, - decoration: textStyle.decoration, - decorationColor: textStyle.decorationColor, - decorationStyle: textStyle.decorationStyle, - decorationThickness: textStyle.decorationThickness, - debugLabel: textStyle.debugLabel, - fontFamilyFallback: textStyle.fontFamilyFallback); - return tooltipTextStyle; - } - - ///draw tooltip text - void _drawText(dynamic tooltip, Canvas canvas, String text, Offset point, - TextStyle style, - [int maxLines, int rotation]) { - TextAlign tooltipTextAlign = TextAlign.start; - num pointX = point.dx; - if (tooltip != null && - tooltip.format != null && - tooltip.format.isNotEmpty) { - if (tooltip.textAlignment == ChartAlignment.near) { - tooltipTextAlign = TextAlign.start; - pointX = - _chartState._tooltipBehaviorRenderer._painter._tooltipRect.left; - } else if (tooltip.textAlignment == ChartAlignment.far) { - tooltipTextAlign = TextAlign.end; - pointX = - _chartState._tooltipBehaviorRenderer._painter._tooltipRect.right - - _measureText(text, style).width; - } - } - - final TextSpan span = TextSpan(text: text, style: style); - - final TextPainter tp = TextPainter( - text: span, - textDirection: TextDirection.ltr, - textAlign: tooltipTextAlign, - maxLines: maxLines ?? 1); - tp.layout(); - canvas.save(); - canvas.translate(pointX, point.dy); - if (rotation != null && rotation > 0) { - canvas.rotate(_degreeToRadian(rotation)); - } - tp.paint(canvas, const Offset(0.0, 0.0)); - canvas.restore(); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => true; - - /// To show the chart tooltip - void show(double x, double y) { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _chartState._tooltipBehaviorRenderer; - if (tooltip.enable && - tooltipBehaviorRenderer._painter != null && - tooltipBehaviorRenderer._painter._chartState._animateCompleted) { - tooltipBehaviorRenderer._painter.canResetPath = false; - tooltipBehaviorRenderer._painter._renderTooltipView(Offset(x, y)); - final bool needAnimate = !(_chart is SfCartesianChart) && - (tooltipBehaviorRenderer._painter.currentTooltipValue?.pointIndex == - tooltipBehaviorRenderer._painter.prevTooltipValue?.pointIndex) && - _chartState._chartSeries.currentSeries.explode && - !(tooltip.tooltipPosition == TooltipPosition.pointer); - chartTooltipState.animationController.duration = Duration( - milliseconds: - tooltipBehaviorRenderer._tooltipBehavior.animationDuration); - if ((tooltipBehaviorRenderer._painter.prevTooltipValue != null && - tooltipBehaviorRenderer._painter.currentTooltipValue == null) || - needAnimate) { - chartTooltipState.animationController.forward(from: 0.0); - tooltipBehaviorRenderer._painter.currentTooltipValue = - tooltipBehaviorRenderer._painter.prevTooltipValue; - } else { - if (tooltip.tooltipPosition == TooltipPosition.pointer && - (!(_chart is SfCartesianChart) || currentSeries._isRectSeries)) { - chartTooltipState.animationController.forward(from: 0.0); - tooltipBehaviorRenderer._painter.currentTooltipValue = - tooltipBehaviorRenderer._painter.prevTooltipValue; - } - } - if (tooltipBehaviorRenderer._painter.timer != null) { - tooltipBehaviorRenderer._painter.timer.cancel(); - } - assert(tooltip.duration != null ? tooltip.duration >= 0 : true, - 'The duration time for the tooltip must not be less than 0.'); - if (!tooltip.shouldAlwaysShow) { - tooltipBehaviorRenderer._painter.timer = - Timer(Duration(milliseconds: tooltip.duration.toInt()), () { - chartTooltipState.show = false; - tooltipBehaviorRenderer._painter.currentTooltipValue = - tooltipBehaviorRenderer._painter.prevTooltipValue = null; - chartTooltipState.tooltipRepaintNotifier.value++; - tooltipBehaviorRenderer._painter.canResetPath = true; - }); - } - } - } - - /// This method shows the tooltip for any logical pixel outside point region - //ignore: unused_element - void _showChartAreaTooltip(Offset position, ChartAxisRenderer xAxisRenderer, - ChartAxisRenderer yAxisRenderer, dynamic chart) { - final ChartAxis xAxis = xAxisRenderer._axis, yAxis = yAxisRenderer._axis; - if (tooltip.enable && - _chartState._tooltipBehaviorRenderer._painter != null && - _chartState - ._tooltipBehaviorRenderer._painter._chartState._animateCompleted) { - chartTooltipState.animationController.duration = Duration( - milliseconds: _chartState - ._tooltipBehaviorRenderer._tooltipBehavior.animationDuration); - chartTooltipState.animationController.forward(from: 0.0); - _chartState._tooltipBehaviorRenderer._painter.canResetPath = false; - //render - _chartState._tooltipBehaviorRenderer._painter.boundaryRect = - _chartState._chartAxis._axisClipRect; - if (_chartState._chartAxis._axisClipRect.contains(position)) { - _chartState._tooltipBehaviorRenderer._painter.currentSeries = - _chartState._chartSeries.visibleSeriesRenderers[0]; - _chartState._tooltipBehaviorRenderer._painter.currentSeries = - _chartState._chartSeries.visibleSeriesRenderers[0]; - _chartState._tooltipBehaviorRenderer._painter.padding = 5; - _chartState._tooltipBehaviorRenderer._painter.header = null; - dynamic xValue = _pointToXValue( - _chartState._requireInvertedAxis, - xAxisRenderer, - xAxisRenderer._bounds, - position.dx - - (_chartState._chartAxis._axisClipRect.left + xAxis.plotOffset), - position.dy - - (_chartState._chartAxis._axisClipRect.top + xAxis.plotOffset)); - dynamic yValue = _pointToYValue( - _chartState._requireInvertedAxis, - yAxisRenderer, - yAxisRenderer._bounds, - position.dx - - (_chartState._chartAxis._axisClipRect.left + yAxis.plotOffset), - position.dy - - (_chartState._chartAxis._axisClipRect.top + yAxis.plotOffset)); - if (xAxisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis xAxis = xAxisRenderer._axis; - xValue = - (xAxis.dateFormat ?? xAxisRenderer._getLabelFormat(xAxisRenderer)) - .format(DateTime.fromMillisecondsSinceEpoch(xValue.floor())); - } else if (xAxisRenderer is CategoryAxisRenderer) { - xValue = xAxisRenderer._visibleLabels[xValue.toInt()].text; - } else if (xAxisRenderer is NumericAxisRenderer) { - xValue = xValue.toStringAsFixed(2).contains('.00') - ? xValue.floor() - : xValue.toStringAsFixed(2); - } - - if (yAxisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis yAxis = yAxisRenderer._axis; - yValue = - (yAxis.dateFormat ?? yAxisRenderer._getLabelFormat(yAxisRenderer)) - .format(DateTime.fromMillisecondsSinceEpoch(yValue.floor())); - } else if (yAxisRenderer is NumericAxisRenderer) { - yValue = yValue.toStringAsFixed(2).contains('.00') - ? yValue.floor() - : yValue.toStringAsFixed(2); - } - _chartState._tooltipBehaviorRenderer._painter.stringValue = - ' $xValue : $yValue '; - _chartState._tooltipBehaviorRenderer._painter - ._calculateLocation(position); - } - if (_chartState._tooltipBehaviorRenderer._painter.timer != null) { - _chartState._tooltipBehaviorRenderer._painter.timer.cancel(); - } - if (!tooltip.shouldAlwaysShow) { - _chartState._tooltipBehaviorRenderer._painter.timer = - Timer(Duration(milliseconds: tooltip.duration.toInt()), () { - chartTooltipState.show = false; - chartTooltipState.tooltipRepaintNotifier.value++; - _chartState._tooltipBehaviorRenderer._painter.canResetPath = true; - }); - } - } - } - - /// To show the axis label tooltip for trimmed axes label texts. - void _showAxisTooltip(Offset position, dynamic chart, String text) { - final _TooltipPainter painter = - _chartState._tooltipBehaviorRenderer._painter; - if (painter != null) { - painter.canResetPath = false; - painter.header = ''; - painter.stringValue = text; - painter._calculateLocation(position); - chartTooltipState._needMarker = false; - if ((painter.prevTooltipValue != null) && - (painter.prevTooltipValue.pointIndex == - painter.currentTooltipValue.pointIndex)) { - chartTooltipState.animationController.duration = - Duration(milliseconds: 0); - chartTooltipState.animationController.forward(from: 0.0); - } else { - chartTooltipState.animationController.duration = Duration( - milliseconds: _chartState - ._tooltipBehaviorRenderer._tooltipBehavior.animationDuration); - chartTooltipState.animationController.forward(from: 0.0); - } - - if (painter.timer != null) { - painter.timer.cancel(); - } - } - painter.timer = Timer(Duration(milliseconds: tooltip.duration.toInt()), () { - chartTooltipState.show = false; - chartTooltipState.tooltipRepaintNotifier.value++; - painter.canResetPath = true; - }); - } - - /// To hide the tooltip when the timer ends - void hide() { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _chartState._tooltipBehaviorRenderer; - if (tooltipBehaviorRenderer._painter.timer != null) { - tooltipBehaviorRenderer._painter.timer.cancel(); - } - - tooltipBehaviorRenderer._painter.timer = - Timer(Duration(milliseconds: tooltip.duration.toInt()), () { - chartTooltipState.show = false; - tooltipBehaviorRenderer._painter.currentTooltipValue = - tooltipBehaviorRenderer._painter.prevTooltipValue = null; - chartTooltipState.tooltipRepaintNotifier.value++; - tooltipBehaviorRenderer._painter.canResetPath = true; - }); - } - - /// To show tooltip on mouse pointer actions - void showMouseTooltip(double x, double y) { - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _chartState._tooltipBehaviorRenderer; - if (tooltip.enable && - tooltipBehaviorRenderer._painter != null && - tooltipBehaviorRenderer._painter._chartState._animateCompleted) { - tooltipBehaviorRenderer._painter.canResetPath = false; - tooltipBehaviorRenderer._painter._renderTooltipView(Offset(x, y)); - if (tooltipBehaviorRenderer._painter.timer != null) { - tooltipBehaviorRenderer._painter.timer.cancel(); - } - if (tooltipBehaviorRenderer._painter.prevTooltipValue != null && - tooltipBehaviorRenderer._painter.currentTooltipValue == null) { - chartTooltipState.animationController.duration = Duration( - milliseconds: - tooltipBehaviorRenderer._tooltipBehavior.animationDuration); - chartTooltipState.animationController.forward(from: 0.0); - tooltipBehaviorRenderer._painter.currentTooltipValue = - tooltipBehaviorRenderer._painter.prevTooltipValue; - } else if (tooltipBehaviorRenderer._painter.prevTooltipValue != null && - tooltipBehaviorRenderer._painter.currentTooltipValue != null && - tooltipBehaviorRenderer._tooltipBehavior.tooltipPosition != - TooltipPosition.auto && - (!(seriesRenderer is CartesianSeriesRenderer) || - seriesRenderer._isRectSeries) && - tooltipBehaviorRenderer._painter.currentTooltipValue.seriesIndex == - tooltipBehaviorRenderer._painter.prevTooltipValue.seriesIndex && - tooltipBehaviorRenderer._painter.currentTooltipValue.pointIndex == - tooltipBehaviorRenderer._painter.prevTooltipValue.pointIndex) { - chartTooltipState.animationController.duration = Duration(seconds: 0); - chartTooltipState.animationController.forward(from: 0.0); - } - if (!mouseTooltip) { - hide(); - } - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_template.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_template.dart deleted file mode 100644 index be4c57c0b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/tooltip_template.dart +++ /dev/null @@ -1,287 +0,0 @@ -part of charts; - -// ignore: must_be_immutable -class _TooltipTemplate extends StatefulWidget { - // ignore: prefer_const_constructors_in_immutables - _TooltipTemplate( - {this.rect, - this.template, - this.show, - this.clipRect, - this.duration = 3000, - this.tooltipBehavior, - this.chartState}); - - Rect rect; - - Widget template; - - bool show; - - bool _alwaysShow; - - Rect clipRect; - - _TooltipTemplateState state; - - double duration; - - TooltipBehavior tooltipBehavior; - - dynamic chartState; - - @override - State createState() { - return _TooltipTemplateState(); - } -} - -class _TooltipTemplateState extends State<_TooltipTemplate> - with SingleTickerProviderStateMixin { - BuildContext tooltipContext; - - bool needMeasure; - - Size tooltipSize = const Size(0, 0); - - AnimationController _controller; - Animation _animation; - - //properties hold the previous and current tooltip values when the interaction is done through mouse hovering - TooltipValue prevTooltipValue; - TooltipValue currentTooltipValue; - //This stores the tooltip value when the mode of interation is touch. - TooltipValue presentTooltipValue; - int seriesIndex; - - Timer tooltipTimer; - - bool isOutOfBoundInTop = false; - - @override - void initState() { - widget.state = this; - _controller = AnimationController( - vsync: this, duration: const Duration(milliseconds: 300)); - _animation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: _controller, - curve: const Interval(0.1, 0.8, curve: Curves.easeOutBack), - )); - super.initState(); - } - - @override - void didUpdateWidget(_TooltipTemplate oldWidget) { - widget.state = this; - super.didUpdateWidget(oldWidget); - } - - @override - Widget build(BuildContext context) { - Widget tooltipWidget; - isOutOfBoundInTop = false; - num paddingTop; - const double arrowHeight = 5.0, arrowWidth = 10.0; - if (widget.show) { - if (needMeasure) { - tooltipWidget = - Opacity(opacity: 0.0, child: Container(child: widget.template)); - tooltipContext = context; - SchedulerBinding.instance.addPostFrameCallback((_) => _loaded()); - } else { - _controller.forward(from: 0.0); - num top = widget.rect.top; - final Rect tooltipRect = Rect.fromLTWH( - widget.rect.left - tooltipSize.width / 2, - top - tooltipSize.height - arrowHeight, - tooltipSize.width, - tooltipSize.height); - final Offset tooltipLocation = - _getTooltipLocation(tooltipRect, widget.clipRect); - if (widget.rect.top < widget.clipRect.top) { - paddingTop = widget.clipRect.top + arrowHeight; - top = tooltipLocation.dy; - } - final Offset arrowLocation = Offset( - tooltipLocation.dx, isOutOfBoundInTop ? top : top - arrowHeight); - top = isOutOfBoundInTop ? top + arrowHeight : tooltipRect.top; - if (widget.rect.top >= widget.clipRect.top) { - paddingTop = top; - } - tooltipWidget = Stack(children: [ - CustomPaint( - size: Size(arrowHeight, arrowWidth), - painter: _ArrowPainter( - arrowLocation, - Size(tooltipSize.width.toDouble(), arrowHeight.toDouble()), - widget.tooltipBehavior, - widget.chartState, - isOutOfBoundInTop, - _animation, - tooltipSize)), - Container( - child: Padding( - padding: - EdgeInsets.fromLTRB(tooltipLocation.dx, paddingTop, 0, 0), - child: ScaleTransition( - scale: _animation, child: widget.template))) - ]); - } - } else { - tooltipWidget = Container(); - presentTooltipValue = null; - } - if (tooltipTimer != null) { - tooltipTimer.cancel(); - } - if (widget.show && - (prevTooltipValue == null && currentTooltipValue == null)) { - tooltipTimer = Timer( - Duration(milliseconds: widget.duration.toInt()), hideTooltipTemplate); - } - return tooltipWidget; - } - - @override - void dispose() { - _controller.dispose(); - tooltipTimer?.cancel(); - super.dispose(); - } - - /// To hide tooltip template with timer - void hideOnTimer() { - if (prevTooltipValue == null && currentTooltipValue == null) { - hideTooltipTemplate(); - } else { - if (tooltipTimer != null) { - tooltipTimer.cancel(); - } - tooltipTimer = Timer( - Duration(milliseconds: widget.duration.toInt()), hideTooltipTemplate); - } - } - - /// To hide tooltip templates - void hideTooltipTemplate() { - presentTooltipValue = null; - if (mounted && !(widget._alwaysShow ?? false)) { - setState(() { - widget.show = false; - }); - prevTooltipValue = null; - currentTooltipValue = null; - } - } - - /// To perform rendering of tooltip - void _performTooltip() { - //for mouse hover the tooltip is redrawn only when the current tooltip value differs from the previous one - if (widget.show && - mounted && - ((prevTooltipValue == null && currentTooltipValue == null) || - ((widget.chartState is SfCartesianChartState && - widget.chartState._chartSeries - .visibleSeriesRenderers[seriesIndex]._isRectSeries && - widget.tooltipBehavior.tooltipPosition != - TooltipPosition.auto)) || - (prevTooltipValue?.seriesIndex != - currentTooltipValue?.seriesIndex || - prevTooltipValue?.outlierIndex != - currentTooltipValue?.outlierIndex || - prevTooltipValue?.pointIndex != - currentTooltipValue?.pointIndex))) { - final bool reRender = (widget - .chartState._tooltipBehaviorRenderer._isHovering && - prevTooltipValue != null && - currentTooltipValue != null && - prevTooltipValue.seriesIndex == currentTooltipValue.seriesIndex && - prevTooltipValue.pointIndex == currentTooltipValue.pointIndex && - prevTooltipValue.outlierIndex == currentTooltipValue.outlierIndex); - needMeasure = !reRender; - _controller.duration = Duration(milliseconds: reRender ? 0 : 300); - tooltipSize = reRender ? tooltipSize : const Size(0, 0); - setState(() {}); - } - } - - /// For rebuilding this state after finding render object size. - void _loaded() { - final RenderBox renderBox = tooltipContext.findRenderObject(); - tooltipSize = renderBox.size; - needMeasure = false; - if (mounted) { - setState(() {}); - } - } - - /// It returns the offset values of tooltip location - Offset _getTooltipLocation(Rect tooltipRect, Rect bounds) { - double left = tooltipRect.left, top = tooltipRect.top; - if (tooltipRect.left < bounds.left) { - left = bounds.left; - } - if (tooltipRect.top < bounds.top) { - top = bounds.top; - isOutOfBoundInTop = true; - } - if (tooltipRect.left + tooltipRect.width > bounds.left + bounds.width) { - left = (bounds.left + bounds.width) - tooltipRect.width; - } - if (tooltipRect.top + tooltipRect.height > bounds.top + bounds.height) { - top = (bounds.top + bounds.height) - tooltipRect.height; - } - return Offset(left, top); - } -} - -class _ArrowPainter extends CustomPainter { - const _ArrowPainter( - this._location, - this._currentSize, - this._tooltipBehavior, - this._chartState, - this._isOutOfBoundInTop, - this._animation, - this._templateSize); - final Offset _location; - final Size _currentSize; - final Size _templateSize; - final TooltipBehavior _tooltipBehavior; - final dynamic _chartState; - final bool _isOutOfBoundInTop; - final Animation _animation; - @override - void paint(Canvas canvas, Size size) { - const num padding = 2; - final num templateHeight = _templateSize.height; - final num arrowHeight = (_currentSize.height + padding) * _animation.value; - final num centerTemplateY = _isOutOfBoundInTop - ? _location.dy + _currentSize.height + templateHeight / 2 + padding - : _location.dy - templateHeight / 2 - padding; - final num locationY = _isOutOfBoundInTop - ? centerTemplateY - - ((templateHeight / 2) * _animation.value) - - arrowHeight - : centerTemplateY + ((templateHeight / 2) * _animation.value); - final num centerX = _location.dx + _currentSize.width / 2; - final num arrowWidth = 8 * _animation.value; - final Path path = Path() - ..moveTo(centerX + (_isOutOfBoundInTop ? 0 : -arrowWidth), locationY) - ..lineTo(centerX + (_isOutOfBoundInTop ? -arrowWidth : arrowWidth), - (locationY) + (_isOutOfBoundInTop ? arrowHeight : 0)) - ..lineTo(centerX + (_isOutOfBoundInTop ? arrowWidth : 0), - locationY + arrowHeight) - ..close(); - canvas.drawPath( - path, - Paint() - ..color = - (_tooltipBehavior.color ?? _chartState._chartTheme.tooltipColor) - .withOpacity(_tooltipBehavior.opacity) - ..style = PaintingStyle.fill); - } - - @override - bool shouldRepaint(_ArrowPainter oldDelegate) => true; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart index 4174231be..463a10086 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart @@ -9,11 +9,11 @@ part of charts; class TrackballBehavior { /// Creating an argument constructor of TrackballBehavior class. TrackballBehavior({ - ActivationMode activationMode, - TrackballLineType lineType, - TrackballDisplayMode tooltipDisplayMode, - ChartAlignment tooltipAlignment, - InteractiveTooltip tooltipSettings, + this.activationMode = ActivationMode.longPress, + this.lineType = TrackballLineType.vertical, + this.tooltipDisplayMode = TrackballDisplayMode.floatAllPoints, + this.tooltipAlignment = ChartAlignment.center, + this.tooltipSettings = const InteractiveTooltip(), this.markerSettings, this.lineDashArray, this.enable = false, @@ -21,14 +21,8 @@ class TrackballBehavior { this.lineWidth = 1, this.shouldAlwaysShow = false, this.builder, - double hideDelay, - }) : activationMode = activationMode ?? ActivationMode.longPress, - tooltipAlignment = tooltipAlignment ?? ChartAlignment.center, - hideDelay = hideDelay ?? 0, - tooltipDisplayMode = - tooltipDisplayMode ?? TrackballDisplayMode.floatAllPoints, - tooltipSettings = tooltipSettings ?? InteractiveTooltip(), - lineType = lineType ?? TrackballLineType.vertical; + this.hideDelay = 0, + }); ///Toggles the visibility of the trackball. /// @@ -70,11 +64,11 @@ class TrackballBehavior { /// )); ///} ///``` - final Color lineColor; + final Color? lineColor; ///Dashes of the track line. /// - ///Defaults to `[0,0]`. + ///Defaults to `null`. /// ///```dart ///Widget build(BuildContext context) { @@ -83,7 +77,7 @@ class TrackballBehavior { /// trackballBehavior: TrackballBehavior(enable: true, lineDashArray: [10,10]), /// )); ///}`` - final List lineDashArray; + final List? lineDashArray; ///Gesture for activating the trackball. /// @@ -295,20 +289,20 @@ class TrackballBehavior { /// )); ///} ///``` - final ChartTrackballBuilder builder; + final ChartTrackballBuilder? builder; - SfCartesianChartState _chartState; + SfCartesianChartState? _chartState; ///Options to customize the markers that are displayed when trackball is enabled. /// ///Trackball markers are used to provide information about the exact point location, /// when the trackball is visible. You can add a shape to adorn each data point. - /// Trackball markers can be enabled by using the [markerVisibility] property + /// Trackball markers can be enabled by using the `markerVisibility` property /// in [TrackballMarkerSettings]. /// ///Provides the options like color, border width, border color and shape of the /// marker to customize the appearance. - final TrackballMarkerSettings markerSettings; + final TrackballMarkerSettings? markerSettings; /// Displays the trackball at the specified x and y-positions. /// @@ -319,8 +313,8 @@ class TrackballBehavior { /// 'pixel' or 'point' for logical pixel and chart data point respectively. /// /// Defaults to 'point'. - void show(dynamic x, double y, [String coordinateUnit]) { - final SfCartesianChartState chartState = _chartState; + void show(dynamic x, double y, [String coordinateUnit = 'point']) { + final SfCartesianChartState chartState = _chartState!; final TrackballBehaviorRenderer trackballBehaviorRenderer = chartState._trackballBehaviorRenderer; final List visibleSeriesRenderer = @@ -329,20 +323,24 @@ class TrackballBehavior { if ((trackballBehaviorRenderer._trackballPainter != null || builder != null) && activationMode != ActivationMode.none) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; if (coordinateUnit != 'pixel') { final _ChartLocation location = _calculatePoint( - x is DateTime + (x is DateTime && !(xAxisRenderer is DateTimeCategoryAxisRenderer)) ? x.millisecondsSinceEpoch - : (x is String && xAxisRenderer is CategoryAxisRenderer) - ? xAxisRenderer._labels.indexOf(x) - : x, + : ((x is DateTime && + xAxisRenderer is DateTimeCategoryAxisRenderer) + ? xAxisRenderer._labels + .indexOf(xAxisRenderer._dateFormat.format(x)) + : ((x is String && xAxisRenderer is CategoryAxisRenderer) + ? xAxisRenderer._labels.indexOf(x) + : x)), y, xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, - seriesRenderer._chartState._chartAxis._axisClipRect); + seriesRenderer._chartState!._chartAxis._axisClipRect); x = location.x; y = location.y; } @@ -356,7 +354,7 @@ class TrackballBehavior { } if (trackballBehaviorRenderer._trackballPainter != null) { - trackballBehaviorRenderer._trackballPainter.canResetPath = false; + trackballBehaviorRenderer._trackballPainter!.canResetPath = false; chartState._trackballRepaintNotifier.value++; } } @@ -365,7 +363,7 @@ class TrackballBehavior { /// /// * pointIndex - index of the point for which the trackball must be shown void showByIndex(int pointIndex) { - final SfCartesianChartState chartState = _chartState; + final SfCartesianChartState chartState = _chartState!; final TrackballBehaviorRenderer trackballBehaviorRenderer = chartState._trackballBehaviorRenderer; if ((trackballBehaviorRenderer._trackballPainter != null || @@ -378,16 +376,16 @@ class TrackballBehavior { trackballBehaviorRenderer); } if (trackballBehaviorRenderer._trackballPainter != null) { - trackballBehaviorRenderer._trackballPainter.canResetPath = false; + trackballBehaviorRenderer._trackballPainter!.canResetPath = false; trackballBehaviorRenderer - ._trackballPainter.chartState._trackballRepaintNotifier.value++; + ._trackballPainter!.chartState._trackballRepaintNotifier.value++; } } } /// Hides the trackball if it is displayed. void hide() { - final SfCartesianChartState chartState = _chartState; + final SfCartesianChartState chartState = _chartState!; final TrackballBehaviorRenderer trackballBehaviorRenderer = chartState._trackballBehaviorRenderer; if (trackballBehaviorRenderer._trackballPainter != null && @@ -395,39 +393,42 @@ class TrackballBehavior { activationMode != ActivationMode.none) { if (chartState._chart.trackballBehavior.activationMode == ActivationMode.doubleTap) { - trackballBehaviorRenderer._trackballPainter.canResetPath = false; + trackballBehaviorRenderer._trackballPainter!.canResetPath = false; ValueNotifier(trackballBehaviorRenderer - ._trackballPainter.chartState._trackballRepaintNotifier.value++); - if (trackballBehaviorRenderer._trackballPainter.timer != null) { - trackballBehaviorRenderer._trackballPainter.timer.cancel(); + ._trackballPainter!.chartState._trackballRepaintNotifier.value++); + if (trackballBehaviorRenderer._trackballPainter!.timer != null) { + trackballBehaviorRenderer._trackballPainter!.timer?.cancel(); } } - if (!_chartState._isTouchUp) { + if (!_chartState!._isTouchUp) { trackballBehaviorRenderer - ._trackballPainter.chartState._trackballRepaintNotifier.value++; - trackballBehaviorRenderer._trackballPainter.canResetPath = true; + ._trackballPainter!.chartState._trackballRepaintNotifier.value++; + trackballBehaviorRenderer._trackballPainter!.canResetPath = true; } else { final double duration = (hideDelay == 0 && chartState._enableDoubleTap) ? 200 : hideDelay; if (!shouldAlwaysShow) { - trackballBehaviorRenderer._trackballPainter.timer = + trackballBehaviorRenderer._trackballPainter!.timer = Timer(Duration(milliseconds: duration.toInt()), () { - trackballBehaviorRenderer - ._trackballPainter.chartState._trackballRepaintNotifier.value++; - trackballBehaviorRenderer._trackballPainter.canResetPath = true; + trackballBehaviorRenderer._trackballPainter!.chartState + ._trackballRepaintNotifier.value++; + trackballBehaviorRenderer._trackballPainter!.canResetPath = true; }); } } } else if (trackballBehaviorRenderer._trackballTemplate != null) { - final GlobalKey key = trackballBehaviorRenderer._trackballTemplate.key; - final _TrackballTemplateState trackballTemplateState = key.currentState; + final GlobalKey key = + trackballBehaviorRenderer._trackballTemplate!.key as GlobalKey; + final _TrackballTemplateState? trackballTemplateState = + key.currentState as _TrackballTemplateState; final double duration = shouldAlwaysShow || (hideDelay == 0 && chartState._enableDoubleTap) ? 200 : hideDelay; - trackballTemplateState?._trackballTimer = Timer( - Duration(milliseconds: duration.toInt()), - trackballTemplateState.hideTrackballTemplate); + trackballTemplateState?._trackballTimer = + Timer(Duration(milliseconds: duration.toInt()), () { + trackballTemplateState.hideTrackballTemplate(); + }); } } } @@ -441,73 +442,72 @@ class TrackballBehaviorRenderer with ChartBehavior { TrackballBehavior get _trackballBehavior => _chart.trackballBehavior; /// Check whether long press activated or not . - bool _isLongPressActivated; + bool _isLongPressActivated = false; /// check whether onPointerMove or not. /// ignore: prefer_final_fields bool _isMoving = false; /// Touch position - /// ignore: unused_field - Offset _position; + late Offset _tapPosition; - /// Holds the instance of trackballPainter. - _TrackballPainter _trackballPainter; - _TrackballTemplate _trackballTemplate; + /// Holds the instance of trackballPainter!. + _TrackballPainter? _trackballPainter; + _TrackballTemplate? _trackballTemplate; List _visibleLocation = []; final List _markerShapes = []; - Rect _axisClipRect; + late Rect _axisClipRect; //ignore: unused_field - double _xPos; + late double _xPos; //ignore: unused_field - double _yPos; + late double _yPos; List _points = []; List _currentPointIndices = []; List _visibleSeriesIndices = []; List _visibleSeriesList = []; - TrackballGroupingModeInfo _groupingModeInfo; + late TrackballGroupingModeInfo _groupingModeInfo; List<_ChartPointInfo> _chartPointInfo = <_ChartPointInfo>[]; List _tooltipTop = []; List _tooltipBottom = []; final List _xAxesInfo = []; final List _yAxesInfo = []; List<_ClosestPoints> _visiblePoints = <_ClosestPoints>[]; - _TooltipPositions _tooltipPosition; - num _tooltipPadding; - bool _isRangeSeries; - bool _isBoxSeries; + _TooltipPositions? _tooltipPosition; + late num _tooltipPadding; + bool _isRangeSeries = false; + bool _isBoxSeries = false; bool _isTrackballTemplate = false; /// To render the trackball marker for both tooltip and template void _trackballMarker(int index) { if (_trackballBehavior.markerSettings != null && - (_trackballBehavior.markerSettings.markerVisibility == + (_trackballBehavior.markerSettings!.markerVisibility == TrackballVisibilityMode.auto ? (_chartPointInfo[index] - .seriesRenderer + .seriesRenderer! ._series .markerSettings .isVisible) - : _trackballBehavior.markerSettings.markerVisibility == + : _trackballBehavior.markerSettings!.markerVisibility == TrackballVisibilityMode.visible)) { - final MarkerSettings markerSettings = _trackballBehavior.markerSettings; + final MarkerSettings markerSettings = _trackballBehavior.markerSettings!; final DataMarkerType markerType = markerSettings.shape; final Size size = Size(markerSettings.width, markerSettings.height); final String seriesType = - _chartPointInfo[index].seriesRenderer._seriesType; - _chartPointInfo[index].seriesRenderer._isMarkerRenderEvent = true; + _chartPointInfo[index].seriesRenderer!._seriesType; + _chartPointInfo[index].seriesRenderer!._isMarkerRenderEvent = true; _markerShapes.add(_getMarkerShapesPath( markerType, Offset( - _chartPointInfo[index].xPosition, + _chartPointInfo[index].xPosition!, seriesType.contains('range') || seriesType.contains('hilo') || seriesType == 'candle' - ? _chartPointInfo[index].highYPosition + ? _chartPointInfo[index].highYPosition! : seriesType == 'boxandwhisker' - ? _chartPointInfo[index].maxYPosition - : _chartPointInfo[index].yPosition), + ? _chartPointInfo[index].maxYPosition! + : _chartPointInfo[index].yPosition!), size, _chartPointInfo[index].seriesRenderer, null, @@ -520,7 +520,7 @@ class TrackballBehaviorRenderer with ChartBehavior { int pointIndex, TrackballBehaviorRenderer trackballBehaviorRenderer) { _ChartLocation position; final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderers[0]; - final Rect rect = seriesRenderer._chartState._chartAxis._axisClipRect; + final Rect rect = seriesRenderer._chartState!._chartAxis._axisClipRect; final List> dataPoints = >[]; for (int i = 0; i < seriesRenderer._dataPoints.length; i++) { @@ -542,13 +542,13 @@ class TrackballBehaviorRenderer with ChartBehavior { position = _calculatePoint( xValue, yValue, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, + seriesRenderer._chartState!._requireInvertedAxis, seriesRenderer._series, rect); if (trackballBehaviorRenderer._trackballPainter != null) { - seriesRenderer._chartState._trackballBehaviorRenderer + seriesRenderer._chartState!._trackballBehaviorRenderer ._generateAllPoints(Offset(position.x, position.y)); } else if (_trackballBehavior.builder != null) { trackballBehaviorRenderer @@ -558,8 +558,10 @@ class TrackballBehaviorRenderer with ChartBehavior { } void _showTemplateTrackball(Offset position) { - final GlobalKey key = _trackballTemplate.key; - final _TrackballTemplateState trackballTemplateState = key.currentState; + final GlobalKey key = _trackballTemplate!.key as GlobalKey; + final _TrackballTemplateState trackballTemplateState = + key.currentState as _TrackballTemplateState; + _tapPosition = position; trackballTemplateState._alwaysShow = _trackballBehavior.shouldAlwaysShow; trackballTemplateState._duration = _trackballBehavior.hideDelay == 0 ? 200 : _trackballBehavior.hideDelay; @@ -568,16 +570,16 @@ class TrackballBehaviorRenderer with ChartBehavior { CartesianChartPoint dataPoint; for (int index = 0; index < _chartPointInfo.length; index++) { dataPoint = _chartPointInfo[index] - .seriesRenderer - ._dataPoints[_chartPointInfo[index].dataPointIndex]; + .seriesRenderer! + ._dataPoints[_chartPointInfo[index].dataPointIndex!]; if (_trackballBehavior.tooltipDisplayMode == TrackballDisplayMode.groupAllPoints) { _points.add(dataPoint); - _currentPointIndices.add(_chartPointInfo[index].dataPointIndex); + _currentPointIndices.add(_chartPointInfo[index].dataPointIndex!); _visibleSeriesIndices.add(_chartState ._chartSeries.visibleSeriesRenderers - .indexOf(_chartPointInfo[index].seriesRenderer)); - _visibleSeriesList.add(_chartPointInfo[index].seriesRenderer._series); + .indexOf(_chartPointInfo[index].seriesRenderer!)); + _visibleSeriesList.add(_chartPointInfo[index].seriesRenderer!._series); } _trackballMarker(index); } @@ -611,308 +613,290 @@ class TrackballBehaviorRenderer with ChartBehavior { _tooltipPadding = _chartState._requireInvertedAxis ? 8 : 5; _chartPointInfo = <_ChartPointInfo>[]; _visiblePoints = <_ClosestPoints>[]; - _markerShapes?.clear(); + _markerShapes.clear(); _tooltipTop = []; _tooltipBottom = []; _trackballPainter?._tooltipTop = []; _trackballPainter?._tooltipBottom = []; _visibleLocation = []; final Rect seriesBounds = _axisClipRect; - if (position.dx >= seriesBounds.left && - position.dx <= seriesBounds.right && - position.dy >= seriesBounds.top && - position.dy <= seriesBounds.bottom) { - double xPos = 0, - yPos = 0, - leastX = 0, - openXPos, - openYPos, - closeXPos, - closeYPos, - highXPos, - cummulativePos, - lowerXPos, - lowerYPos, - upperXPos, - upperYPos, - lowYPos, - highYPos, - minYPos, - maxYPos, - maxXPos; - int seriesIndex = 0, index; - final List seriesAxisRenderers = []; - CartesianSeriesRenderer visibleSeriesRenderer, cartesianSeriesRenderer; - ChartAxisRenderer chartAxisRenderer, xAxisRenderer, yAxisRenderer; - CartesianChartPoint chartDataPoint; - _ChartAxis chartAxis; - String seriesType, labelValue, seriesName; - bool invertedAxis; - CartesianSeries series; - num xValue, - yValue, - minimumValue, - maximumValue, - lowerQuartileValue, - upperQuartileValue, - meanValue, - highValue, - lowValue, - openValue, - closeValue, - bubbleSize, - cumulativeValue; - Rect axisClipRect; - final TrackballDisplayMode tooltipDisplayMode = - _chartState._chart.trackballBehavior.tooltipDisplayMode; - _ChartLocation highLocation, maxLocation; - for (final CartesianSeriesRenderer seriesRenderer - in _chartState._seriesRenderers) { - visibleSeriesRenderer = seriesRenderer; - chartAxisRenderer = visibleSeriesRenderer._xAxisRenderer; - if (chartAxisRenderer == null) { - continue; - } - if (!seriesAxisRenderers.contains(chartAxisRenderer)) { - seriesAxisRenderers.add(chartAxisRenderer); - for (final CartesianSeriesRenderer axisSeriesRenderer - in chartAxisRenderer._seriesRenderers) { - cartesianSeriesRenderer = axisSeriesRenderer; - seriesType = cartesianSeriesRenderer._seriesType; - _isRangeSeries = seriesType.contains('range') || - seriesType.contains('hilo') || - seriesType == 'candle'; - _isBoxSeries = seriesType == 'boxandwhisker'; - if (axisSeriesRenderer._visible == false || - (axisSeriesRenderer._dataPoints.isEmpty && - !axisSeriesRenderer._isRectSeries)) { - continue; - } - if (cartesianSeriesRenderer._dataPoints.isNotEmpty) { - final List> nearestDataPoints = - _getNearestChartPoints( - position.dx, - position.dy, - chartAxisRenderer, - visibleSeriesRenderer._yAxisRenderer, - cartesianSeriesRenderer); - if (nearestDataPoints == null) { - continue; + _tapPosition = position; + double? xPos = 0, + yPos = 0, + leastX = 0, + openXPos, + openYPos, + closeXPos, + closeYPos, + highXPos, + cummulativePos, + lowerXPos, + lowerYPos, + upperXPos, + upperYPos, + lowYPos, + highYPos, + minYPos, + maxYPos, + maxXPos; + int seriesIndex = 0, index; + final List seriesAxisRenderers = []; + CartesianSeriesRenderer visibleSeriesRenderer, cartesianSeriesRenderer; + ChartAxisRenderer? chartAxisRenderer; + ChartAxisRenderer xAxisRenderer, yAxisRenderer; + CartesianChartPoint chartDataPoint; + _ChartAxis chartAxis; + String seriesType, labelValue, seriesName; + bool invertedAxis = _chartState._requireInvertedAxis; + CartesianSeries series; + num? xValue, + yValue, + minimumValue, + maximumValue, + lowerQuartileValue, + upperQuartileValue, + meanValue, + highValue, + lowValue, + openValue, + closeValue, + bubbleSize, + cumulativeValue; + Rect axisClipRect; + final TrackballDisplayMode tooltipDisplayMode = + _chartState._chart.trackballBehavior.tooltipDisplayMode; + _ChartLocation highLocation, maxLocation; + for (final CartesianSeriesRenderer seriesRenderer + in _chartState._seriesRenderers) { + visibleSeriesRenderer = seriesRenderer; + chartAxisRenderer = visibleSeriesRenderer._xAxisRenderer!; + if (chartAxisRenderer == null) { + continue; + } + if (!seriesAxisRenderers.contains(chartAxisRenderer)) { + seriesAxisRenderers.add(chartAxisRenderer); + for (final CartesianSeriesRenderer axisSeriesRenderer + in chartAxisRenderer._seriesRenderers) { + cartesianSeriesRenderer = axisSeriesRenderer; + seriesType = cartesianSeriesRenderer._seriesType; + _isRangeSeries = seriesType.contains('range') || + seriesType.contains('hilo') || + seriesType == 'candle'; + _isBoxSeries = seriesType == 'boxandwhisker'; + if (axisSeriesRenderer._visible == false || + (axisSeriesRenderer._dataPoints.isEmpty && + !axisSeriesRenderer._isRectSeries)) { + continue; + } + if (cartesianSeriesRenderer._dataPoints.isNotEmpty) { + final List>? nearestDataPoints = + _getNearestChartPoints( + position.dx, + position.dy, + chartAxisRenderer, + visibleSeriesRenderer._yAxisRenderer!, + cartesianSeriesRenderer); + for (final CartesianChartPoint dataPoint + in nearestDataPoints!) { + index = axisSeriesRenderer._dataPoints.indexOf(dataPoint); + chartDataPoint = cartesianSeriesRenderer._dataPoints[index]; + xAxisRenderer = cartesianSeriesRenderer._xAxisRenderer!; + yAxisRenderer = cartesianSeriesRenderer._yAxisRenderer!; + chartAxis = cartesianSeriesRenderer._chartState!._chartAxis; + invertedAxis = _chartState._requireInvertedAxis; + series = cartesianSeriesRenderer._series; + xValue = chartDataPoint.xValue; + if (seriesType != 'boxandwhisker') { + yValue = chartDataPoint.yValue; } - for (final CartesianChartPoint dataPoint - in nearestDataPoints) { - index = axisSeriesRenderer._dataPoints.indexOf(dataPoint); - chartDataPoint = cartesianSeriesRenderer._dataPoints[index]; - xAxisRenderer = cartesianSeriesRenderer._xAxisRenderer; - yAxisRenderer = cartesianSeriesRenderer._yAxisRenderer; - chartAxis = cartesianSeriesRenderer._chartState._chartAxis; - invertedAxis = _chartState._requireInvertedAxis; - series = cartesianSeriesRenderer._series; - xValue = chartDataPoint.xValue; - if (seriesType != 'boxandwhisker') { - yValue = chartDataPoint.yValue; - } - minimumValue = chartDataPoint.minimum; - maximumValue = chartDataPoint.maximum; - lowerQuartileValue = chartDataPoint.lowerQuartile; - upperQuartileValue = chartDataPoint.upperQuartile; - meanValue = chartDataPoint.mean; - highValue = chartDataPoint.high; - lowValue = chartDataPoint.low; - openValue = chartDataPoint.open; - closeValue = chartDataPoint.close; - seriesName = cartesianSeriesRenderer._series.name ?? - 'Series $seriesIndex'; - bubbleSize = chartDataPoint.bubbleSize; - cumulativeValue = chartDataPoint.cumulativeValue; - axisClipRect = _calculatePlotOffset( - chartAxis._axisClipRect, - Offset(xAxisRenderer._axis.plotOffset, - yAxisRenderer._axis.plotOffset)); - cummulativePos = _calculatePoint( - xValue, - cumulativeValue, - xAxisRenderer, - yAxisRenderer, - invertedAxis, - series, - axisClipRect) - .y; - xPos = _calculatePoint( - xValue, - seriesType.contains('stacked') - ? cumulativeValue - : yValue, - xAxisRenderer, - yAxisRenderer, - invertedAxis, - series, - axisClipRect) - .x; - if (!xPos.toDouble().isNaN) { - if (seriesIndex == 0 || - ((leastX - position.dx) > (leastX - xPos))) { - leastX = xPos; - } - labelValue = _getTrackballLabelText( - cartesianSeriesRenderer, - xValue, - yValue, - lowValue, - highValue, - openValue, - closeValue, - minimumValue, - maximumValue, - lowerQuartileValue, - upperQuartileValue, - meanValue, - seriesName, - bubbleSize, + minimumValue = chartDataPoint.minimum; + maximumValue = chartDataPoint.maximum; + lowerQuartileValue = chartDataPoint.lowerQuartile; + upperQuartileValue = chartDataPoint.upperQuartile; + meanValue = chartDataPoint.mean; + highValue = chartDataPoint.high; + lowValue = chartDataPoint.low; + openValue = chartDataPoint.open; + closeValue = chartDataPoint.close; + seriesName = + cartesianSeriesRenderer._series.name ?? 'Series $seriesIndex'; + bubbleSize = chartDataPoint.bubbleSize; + cumulativeValue = chartDataPoint.cumulativeValue; + axisClipRect = _calculatePlotOffset( + chartAxis._axisClipRect, + Offset(xAxisRenderer._axis.plotOffset, + yAxisRenderer._axis.plotOffset)); + cummulativePos = _calculatePoint( + xValue!, cumulativeValue, - dataPoint); - yPos = seriesType.contains('stacked') - ? cummulativePos - : _calculatePoint(xValue, yValue, xAxisRenderer, - yAxisRenderer, invertedAxis, series, axisClipRect) - .y; - if (_isRangeSeries) { - lowYPos = _calculatePoint(xValue, lowValue, xAxisRenderer, + xAxisRenderer, + yAxisRenderer, + invertedAxis, + series, + axisClipRect) + .y; + xPos = _calculatePoint( + xValue, + seriesType.contains('stacked') ? cumulativeValue : yValue, + xAxisRenderer, + yAxisRenderer, + invertedAxis, + series, + axisClipRect) + .x; + if (!xPos.toDouble().isNaN) { + if (seriesIndex == 0 || + ((leastX! - position.dx) > (leastX - xPos))) { + leastX = xPos; + } + labelValue = _getTrackballLabelText( + cartesianSeriesRenderer, + xValue, + yValue, + lowValue, + highValue, + openValue, + closeValue, + minimumValue, + maximumValue, + lowerQuartileValue, + upperQuartileValue, + meanValue, + seriesName, + bubbleSize, + cumulativeValue, + dataPoint); + yPos = seriesType.contains('stacked') + ? cummulativePos + : _calculatePoint(xValue, yValue, xAxisRenderer, yAxisRenderer, invertedAxis, series, axisClipRect) .y; - highLocation = _calculatePoint( - xValue, - highValue, - xAxisRenderer, - yAxisRenderer, - invertedAxis, - series, - axisClipRect); - highYPos = highLocation.y; - highXPos = highLocation.x; - if (seriesType == 'hiloopenclose' || - seriesType == 'candle') { - openXPos = dataPoint.openPoint.x; - openYPos = dataPoint.openPoint.y; - closeXPos = dataPoint.closePoint.x; - closeYPos = dataPoint.closePoint.y; - } - } else if (seriesType == 'boxandwhisker') { - minYPos = _calculatePoint( - xValue, - minimumValue, - xAxisRenderer, - yAxisRenderer, - invertedAxis, - series, - axisClipRect) - .y; - maxLocation = _calculatePoint( - xValue, - maximumValue, - xAxisRenderer, - yAxisRenderer, - invertedAxis, - series, - axisClipRect); - maxXPos = maxLocation.x; - maxYPos = maxLocation.y; - lowerXPos = dataPoint.lowerQuartilePoint.x; - lowerYPos = dataPoint.lowerQuartilePoint.y; - upperXPos = dataPoint.upperQuartilePoint.x; - upperYPos = dataPoint.upperQuartilePoint.y; + if (_isRangeSeries) { + lowYPos = _calculatePoint(xValue, lowValue, xAxisRenderer, + yAxisRenderer, invertedAxis, series, axisClipRect) + .y; + highLocation = _calculatePoint( + xValue, + highValue, + xAxisRenderer, + yAxisRenderer, + invertedAxis, + series, + axisClipRect); + highYPos = highLocation.y; + highXPos = highLocation.x; + if (seriesType == 'hiloopenclose' || seriesType == 'candle') { + openXPos = dataPoint.openPoint!.x; + openYPos = dataPoint.openPoint!.y; + closeXPos = dataPoint.closePoint!.x; + closeYPos = dataPoint.closePoint!.y; } - final Rect rect = seriesBounds.intersect(Rect.fromLTWH( - xPos - 1, - _isRangeSeries - ? highYPos - 1 - : _isBoxSeries - ? maxYPos - 1 - : yPos - 1, - 2, - 2)); - if (seriesBounds.contains(Offset( - xPos, - _isRangeSeries - ? highYPos - : _isBoxSeries - ? maxYPos - : yPos)) || - seriesBounds.overlaps(rect)) { - _visiblePoints.add(_ClosestPoints( - closestPointX: !_isRangeSeries - ? xPos - : _isBoxSeries - ? maxXPos - : highXPos, - closestPointY: _isRangeSeries - ? highYPos - : _isBoxSeries - ? maxYPos - : yPos)); - _addChartPointInfo( - cartesianSeriesRenderer, + } else if (seriesType == 'boxandwhisker') { + minYPos = _calculatePoint(xValue, minimumValue, xAxisRenderer, + yAxisRenderer, invertedAxis, series, axisClipRect) + .y; + maxLocation = _calculatePoint( + xValue, + maximumValue, + xAxisRenderer, + yAxisRenderer, + invertedAxis, + series, + axisClipRect); + maxXPos = maxLocation.x; + maxYPos = maxLocation.y; + lowerXPos = dataPoint.lowerQuartilePoint!.x; + lowerYPos = dataPoint.lowerQuartilePoint!.y; + upperXPos = dataPoint.upperQuartilePoint!.x; + upperYPos = dataPoint.upperQuartilePoint!.y; + } + final Rect rect = seriesBounds.intersect(Rect.fromLTWH( + xPos - 1, + _isRangeSeries + ? highYPos! - 1 + : _isBoxSeries + ? maxYPos! - 1 + : yPos - 1, + 2, + 2)); + if (seriesBounds.contains(Offset( xPos, - yPos, - index, - !_isTrackballTemplate ? labelValue : null, - seriesIndex, - lowYPos, - highXPos, - highYPos, - openXPos, - openYPos, - closeXPos, - closeYPos, - minYPos, - maxXPos, - maxYPos, - lowerXPos, - lowerYPos, - upperXPos, - upperYPos); - if (tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints && - leastX >= seriesBounds.left) { - invertedAxis ? yPos = leastX : xPos = leastX; - } + _isRangeSeries + ? highYPos! + : _isBoxSeries + ? maxYPos! + : yPos)) || + seriesBounds.overlaps(rect)) { + _visiblePoints.add(_ClosestPoints( + closestPointX: !_isRangeSeries + ? xPos + : _isBoxSeries + ? maxXPos! + : highXPos!, + closestPointY: _isRangeSeries + ? highYPos! + : _isBoxSeries + ? maxYPos! + : yPos)); + _addChartPointInfo( + cartesianSeriesRenderer, + xPos, + yPos, + index, + !_isTrackballTemplate ? labelValue : null, + seriesIndex, + lowYPos, + highXPos, + highYPos, + openXPos, + openYPos, + closeXPos, + closeYPos, + minYPos, + maxXPos, + maxYPos, + lowerXPos, + lowerYPos, + upperXPos, + upperYPos); + if (tooltipDisplayMode == + TrackballDisplayMode.groupAllPoints && + leastX >= seriesBounds.left) { + invertedAxis ? yPos = leastX : xPos = leastX; } } } - seriesIndex++; } - _validateNearestXValue( - leastX, cartesianSeriesRenderer, position.dx, position.dy); + seriesIndex++; } - if (_visiblePoints.isNotEmpty) { + _validateNearestXValue( + leastX!, cartesianSeriesRenderer, position.dx, position.dy); + } + if (_visiblePoints.isNotEmpty) { + invertedAxis + ? _visiblePoints.sort((_ClosestPoints a, _ClosestPoints b) => + a.closestPointX.compareTo(b.closestPointX)) + : _visiblePoints.sort((_ClosestPoints a, _ClosestPoints b) => + a.closestPointY.compareTo(b.closestPointY)); + } + if (_chartPointInfo.isNotEmpty) { + if (tooltipDisplayMode != TrackballDisplayMode.groupAllPoints) { invertedAxis - ? _visiblePoints.sort((_ClosestPoints a, _ClosestPoints b) => - a.closestPointX.compareTo(b.closestPointX)) - : _visiblePoints.sort((_ClosestPoints a, _ClosestPoints b) => - a.closestPointY.compareTo(b.closestPointY)); + ? _chartPointInfo.sort((_ChartPointInfo a, _ChartPointInfo b) => + a.xPosition!.compareTo(b.xPosition!)) + : _chartPointInfo.sort((_ChartPointInfo a, _ChartPointInfo b) => + a.yPosition!.compareTo(b.yPosition!)); } - if (_chartPointInfo.isNotEmpty) { - if (tooltipDisplayMode != TrackballDisplayMode.groupAllPoints) { - invertedAxis - ? _chartPointInfo.sort( - (_ChartPointInfo a, _ChartPointInfo b) => - a.xPosition.compareTo(b.xPosition)) - : _chartPointInfo.sort( - (_ChartPointInfo a, _ChartPointInfo b) => - a.yPosition.compareTo(b.yPosition)); - } - if (tooltipDisplayMode == TrackballDisplayMode.nearestPoint || - (seriesRenderer._isRectSeries && - tooltipDisplayMode != - TrackballDisplayMode.groupAllPoints)) { - _validateNearestPointForAllSeries( - leastX, _chartPointInfo, position.dx, position.dy); - } + if (tooltipDisplayMode == TrackballDisplayMode.nearestPoint || + (seriesRenderer._isRectSeries && + tooltipDisplayMode != TrackballDisplayMode.groupAllPoints)) { + _validateNearestPointForAllSeries( + leastX!, _chartPointInfo, position.dx, position.dy); } } } - _triggerTrackballRenderCallback(); } + _triggerTrackballRenderCallback(); _chartPointInfo = _getValidPoints(_chartPointInfo, position); } @@ -923,25 +907,27 @@ class TrackballBehaviorRenderer with ChartBehavior { for (final _ChartPointInfo point in points) { if (validPoints.isEmpty) { validPoints.add(point); - } else if (validPoints[0].seriesRenderer._xAxisRenderer == - point.seriesRenderer._xAxisRenderer) { - if (!point.seriesRenderer._chartState._requireInvertedAxis) { - if ((validPoints[0].xPosition - position.dx).abs() == - (point.xPosition - position.dx).abs()) { + } else if (validPoints[0].seriesRenderer!._xAxisRenderer == + point.seriesRenderer!._xAxisRenderer) { + if (!point.seriesRenderer!._chartState!._requireInvertedAxis) { + if ((validPoints[0].xPosition! - position.dx).abs() == + (point.xPosition! - position.dx).abs() || + validPoints[0].series != point.series) { validPoints.add(point); } - } else if ((validPoints[0].yPosition - position.dy).abs() == - (point.yPosition - position.dy).abs()) { + } else if ((validPoints[0].yPosition! - position.dy).abs() == + (point.yPosition! - position.dy).abs() || + validPoints[0].series != point.series) { validPoints.add(point); } - } else if ((validPoints[0].xPosition - position.dx).abs() > - (point.xPosition - position.dx).abs()) { + } else if ((validPoints[0].xPosition! - position.dx).abs() > + (point.xPosition! - position.dx).abs()) { validPoints.clear(); validPoints.add(point); - } else if ((validPoints[0].xPosition - position.dx).abs() == - (point.xPosition - position.dx).abs()) { - if ((validPoints[0].yPosition - position.dy).abs() > - (point.yPosition - position.dy).abs()) { + } else if ((validPoints[0].xPosition! - position.dx).abs() == + (point.xPosition! - position.dx).abs()) { + if ((validPoints[0].yPosition! - position.dy).abs() > + (point.yPosition! - position.dy).abs()) { validPoints.clear(); validPoints.add(point); } @@ -956,11 +942,13 @@ class TrackballBehaviorRenderer with ChartBehavior { _chartState._chartPointInfo = _chartState._trackballBehaviorRenderer._chartPointInfo; int index; - for (index = 0; index < _chartState._chartPointInfo.length; index++) { + for (index = _chartState._chartPointInfo.length - 1; + index >= 0; + index--) { TrackballArgs chartPoint; chartPoint = TrackballArgs(); chartPoint.chartPointInfo = _chartState._chartPointInfo[index]; - _chart.onTrackballPositionChanging(chartPoint); + _chart.onTrackballPositionChanging!(chartPoint); _chartState._chartPointInfo[index].label = chartPoint.chartPointInfo.label; _chartState._chartPointInfo[index].header = @@ -981,7 +969,8 @@ class TrackballBehaviorRenderer with ChartBehavior { final List<_ChartPointInfo> tempTrackballInfo = List<_ChartPointInfo>.from(trackballInfo); _ChartPointInfo pointInfo, nextPointInfo, previousPointInfo; - num yValue, xValue; + num? yValue; + num xValue; Rect axisClipRect; int pointInfoIndex; CartesianChartPoint dataPoint; @@ -990,15 +979,15 @@ class TrackballBehaviorRenderer with ChartBehavior { for (i = 0; i < tempTrackballInfo.length; i++) { pointInfo = tempTrackballInfo[i]; dataPoint = - pointInfo.seriesRenderer._dataPoints[pointInfo.dataPointIndex]; - xAxisRenderer = pointInfo.seriesRenderer._xAxisRenderer; - yAxisRenderer = pointInfo.seriesRenderer._yAxisRenderer; + pointInfo.seriesRenderer!._dataPoints[pointInfo.dataPointIndex!]; + xAxisRenderer = pointInfo.seriesRenderer!._xAxisRenderer!; + yAxisRenderer = pointInfo.seriesRenderer!._yAxisRenderer!; xValue = dataPoint.xValue; - if (pointInfo.seriesRenderer._seriesType != 'boxandwhisker') { + if (pointInfo.seriesRenderer!._seriesType != 'boxandwhisker') { yValue = dataPoint.yValue; } axisClipRect = _calculatePlotOffset( - pointInfo.seriesRenderer._chartState._chartAxis._axisClipRect, + pointInfo.seriesRenderer!._chartState!._chartAxis._axisClipRect, Offset( xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); xPos = _calculatePoint( @@ -1007,14 +996,12 @@ class TrackballBehaviorRenderer with ChartBehavior { xAxisRenderer, yAxisRenderer, _chartState._requireInvertedAxis, - pointInfo.seriesRenderer._series, + pointInfo.seriesRenderer!._series, axisClipRect) .x; - trackballInfo = _getRectSeriesPointInfo(trackballInfo, pointInfo, xValue, - yValue, touchXPos, leastX, axisClipRect); if (_chartState._chart.trackballBehavior.tooltipDisplayMode != TrackballDisplayMode.floatAllPoints && - (!pointInfo.seriesRenderer._chartState._requireInvertedAxis)) { + (!pointInfo.seriesRenderer!._chartState!._requireInvertedAxis)) { if (leastX != xPos) { trackballInfo.remove(pointInfo); } @@ -1022,18 +1009,18 @@ class TrackballBehaviorRenderer with ChartBehavior { yPos = touchYPos; if (pointInfoIndex < tempTrackballInfo.length - 1) { nextPointInfo = tempTrackballInfo[pointInfoIndex + 1]; - if ((pointInfo.yPosition > yPos && pointInfoIndex == 0)) { + if ((pointInfo.yPosition! > yPos && pointInfoIndex == 0)) { continue; } if (!(yPos < - (nextPointInfo.yPosition - - ((nextPointInfo.yPosition - pointInfo.yPosition) / 2)))) { + (nextPointInfo.yPosition! - + ((nextPointInfo.yPosition! - pointInfo.yPosition!) / 2)))) { trackballInfo.remove(pointInfo); } else if (pointInfoIndex != 0) { previousPointInfo = tempTrackballInfo[pointInfoIndex - 1]; if (yPos < - (pointInfo.yPosition - - ((pointInfo.yPosition - previousPointInfo.yPosition) / + (pointInfo.yPosition! - + ((pointInfo.yPosition! - previousPointInfo.yPosition!) / 2))) { trackballInfo.remove(pointInfo); } @@ -1042,12 +1029,12 @@ class TrackballBehaviorRenderer with ChartBehavior { if (pointInfoIndex != 0 && pointInfoIndex == tempTrackballInfo.length - 1) { previousPointInfo = tempTrackballInfo[pointInfoIndex - 1]; - if (yPos < previousPointInfo.yPosition) { + if (yPos < previousPointInfo.yPosition!) { trackballInfo.remove(pointInfo); } if (yPos < - (pointInfo.yPosition - - ((pointInfo.yPosition - previousPointInfo.yPosition) / + (pointInfo.yPosition! - + ((pointInfo.yPosition! - previousPointInfo.yPosition!) / 2))) { trackballInfo.remove(pointInfo); } @@ -1057,68 +1044,6 @@ class TrackballBehaviorRenderer with ChartBehavior { } } - /// It returns the trackball information for all series - List<_ChartPointInfo> _getRectSeriesPointInfo( - List<_ChartPointInfo> trackballInfo, - _ChartPointInfo pointInfo, - num xValue, - num yValue, - double touchXPos, - double leastX, - Rect axisClipRect) { - if (pointInfo.seriesRenderer._isRectSeries && - _chartState._chart.enableSideBySideSeriesPlacement && - _chartState._chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.groupAllPoints) { - final ChartAxisRenderer xAxisRenderer = - pointInfo.seriesRenderer._xAxisRenderer, - yAxisRenderer = pointInfo.seriesRenderer._yAxisRenderer; - final bool isXAxisInverse = _chartState._requireInvertedAxis; - final CartesianSeries series = - pointInfo.seriesRenderer._series; - final _VisibleRange sideBySideInfo = - _calculateSideBySideInfo(pointInfo.seriesRenderer, _chartState); - final double xStartValue = xValue + sideBySideInfo.minimum; - final double xEndValue = xValue + sideBySideInfo.maximum; - double xStart = _calculatePoint(xStartValue, yValue, xAxisRenderer, - yAxisRenderer, isXAxisInverse, series, axisClipRect) - .x; - double xEnd = _calculatePoint(xEndValue, yValue, xAxisRenderer, - yAxisRenderer, isXAxisInverse, series, axisClipRect) - .x; - bool isStartIndex = pointInfo.seriesRenderer._sideBySideIndex == 0; - bool isEndIndex = pointInfo.seriesRenderer._sideBySideIndex == - pointInfo.seriesRenderer._chartState._chartSeries - .visibleSeriesRenderers.length - - 1; - final double xPos = touchXPos; - if (isXAxisInverse) { - final double temp = xStart; - xStart = xEnd; - xEnd = temp; - final bool isTemp = isEndIndex; - isEndIndex = isStartIndex; - isStartIndex = isTemp; - } else if (pointInfo.seriesRenderer._chartState._zoomedState == true || - _chartState._chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.floatAllPoints) { - if (xPos < leastX && isStartIndex) { - if (!(xPos < xStart) && !(xPos < xEnd && xPos >= xStart)) { - trackballInfo.remove(pointInfo); - } - } else if (xPos > leastX && isEndIndex) { - if (!(xPos > xEnd && xPos > xStart) && - !(xPos < xEnd && xPos >= xStart)) { - trackballInfo.remove(pointInfo); - } - } else if (!(xPos < xEnd && xPos >= xStart)) { - trackballInfo.remove(pointInfo); - } - } - } - return trackballInfo; - } - /// To find the nearest x value to render a trackball void _validateNearestXValue( double leastX, @@ -1127,28 +1052,29 @@ class TrackballBehaviorRenderer with ChartBehavior { double touchYPos) { final List<_ChartPointInfo> leastPointInfo = <_ChartPointInfo>[]; final Rect axisClipRect = _calculatePlotOffset( - seriesRenderer._chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer._axis.plotOffset, - seriesRenderer._yAxisRenderer._axis.plotOffset)); - final bool invertedAxis = seriesRenderer._chartState._requireInvertedAxis; + seriesRenderer._chartState!._chartAxis._axisClipRect, + Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, + seriesRenderer._yAxisRenderer!._axis.plotOffset)); + final bool invertedAxis = seriesRenderer._chartState!._requireInvertedAxis; double nearPointX = invertedAxis ? axisClipRect.top : axisClipRect.left; final double touchXValue = invertedAxis ? touchYPos : touchXPos; double delta = 0, currX; - num xValue, yValue; + num xValue; + num? yValue; CartesianChartPoint dataPoint; CartesianSeries series; ChartAxisRenderer xAxisRenderer, yAxisRenderer; _ChartLocation currXLocation; for (final _ChartPointInfo pointInfo in _chartPointInfo) { - if (pointInfo.dataPointIndex < seriesRenderer._dataPoints.length) { - dataPoint = seriesRenderer._dataPoints[pointInfo.dataPointIndex]; - xAxisRenderer = pointInfo.seriesRenderer._xAxisRenderer; - yAxisRenderer = pointInfo.seriesRenderer._yAxisRenderer; + if (pointInfo.dataPointIndex! < seriesRenderer._dataPoints.length) { + dataPoint = seriesRenderer._dataPoints[pointInfo.dataPointIndex!]; + xAxisRenderer = pointInfo.seriesRenderer!._xAxisRenderer!; + yAxisRenderer = pointInfo.seriesRenderer!._yAxisRenderer!; xValue = dataPoint.xValue; if (seriesRenderer._seriesType != 'boxandwhisker') { yValue = dataPoint.yValue; } - series = pointInfo.seriesRenderer._series; + series = pointInfo.seriesRenderer!._series; currXLocation = _calculatePoint(xValue, yValue, xAxisRenderer, yAxisRenderer, invertedAxis, series, axisClipRect); currX = invertedAxis ? currXLocation.y : currXLocation.x; @@ -1164,13 +1090,13 @@ class TrackballBehaviorRenderer with ChartBehavior { } } if (_chartPointInfo.isNotEmpty) { - if (_chartPointInfo[0].dataPointIndex < + if (_chartPointInfo[0].dataPointIndex! < seriesRenderer._dataPoints.length) { leastX = _getLeastX(_chartPointInfo[0], seriesRenderer, axisClipRect); } } - if (pointInfo.seriesRenderer._seriesType.contains('bar') + if (pointInfo.seriesRenderer!._seriesType.contains('bar') ? invertedAxis : invertedAxis) { _yPos = leastX; @@ -1184,10 +1110,10 @@ class TrackballBehaviorRenderer with ChartBehavior { double _getLeastX(_ChartPointInfo pointInfo, CartesianSeriesRenderer seriesRenderer, Rect axisClipRect) { return _calculatePoint( - seriesRenderer._dataPoints[pointInfo.dataPointIndex].xValue, + seriesRenderer._dataPoints[pointInfo.dataPointIndex!].xValue, 0, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, _chartState._requireInvertedAxis, seriesRenderer._series, axisClipRect) @@ -1200,17 +1126,17 @@ class TrackballBehaviorRenderer with ChartBehavior { final CartesianChartPoint point = seriesRenderer._dataPoints[index]; final TrackballMarkerSettings markerSettings = - trackballBehavior.markerSettings; + trackballBehavior.markerSettings!; if (markerSettings.shape == DataMarkerType.image) { - _drawImageMarker(null, canvas, _chartPointInfo[index].markerXPos, - _chartPointInfo[index].markerYPos, markerSettings, _chartState); + _drawImageMarker(null, canvas, _chartPointInfo[index].markerXPos!, + _chartPointInfo[index].markerYPos!, markerSettings, _chartState); } final Paint strokePaint = Paint() - ..color = trackballBehavior.markerSettings.borderWidth == 0 + ..color = trackballBehavior.markerSettings!.borderWidth == 0 ? Colors.transparent : ((point.pointColorMapper != null) - ? point.pointColorMapper - : markerSettings.borderColor ?? seriesRenderer._seriesColor) + ? point.pointColorMapper! + : markerSettings.borderColor ?? seriesRenderer._seriesColor!) ..strokeWidth = markerSettings.borderWidth ..style = PaintingStyle.stroke; @@ -1226,21 +1152,21 @@ class TrackballBehaviorRenderer with ChartBehavior { /// To add chart point info void _addChartPointInfo(CartesianSeriesRenderer seriesRenderer, double xPos, - double yPos, int dataPointIndex, String label, num seriesIndex, - [double lowYPos, - double highXPos, - double highYPos, - double openXPos, - double openYPos, - double closeXPos, - double closeYPos, - double minYPos, - double maxXPos, - double maxYPos, - double lowerXPos, - double lowerYPos, - double upperXPos, - double upperYPos]) { + double yPos, int dataPointIndex, String? label, int seriesIndex, + [double? lowYPos, + double? highXPos, + double? highYPos, + double? openXPos, + double? openYPos, + double? closeXPos, + double? closeYPos, + double? minYPos, + double? maxXPos, + double? maxYPos, + double? lowerXPos, + double? lowerYPos, + double? upperXPos, + double? upperYPos]) { final _ChartPointInfo pointInfo = _ChartPointInfo(); pointInfo.seriesRenderer = seriesRenderer; @@ -1254,36 +1180,36 @@ class TrackballBehaviorRenderer with ChartBehavior { if (seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType.contains('range') || seriesRenderer._seriesType == 'candle') { - pointInfo.lowYPosition = lowYPos; - pointInfo.highXPosition = highXPos; - pointInfo.highYPosition = highYPos; + pointInfo.lowYPosition = lowYPos!; + pointInfo.highXPosition = highXPos!; + pointInfo.highYPosition = highYPos!; if (seriesRenderer._seriesType == 'hiloopenclose' || seriesRenderer._seriesType == 'candle') { - pointInfo.openXPosition = openXPos; - pointInfo.openYPosition = openYPos; - pointInfo.closeXPosition = closeXPos; - pointInfo.closeYPosition = closeYPos; + pointInfo.openXPosition = openXPos!; + pointInfo.openYPosition = openYPos!; + pointInfo.closeXPosition = closeXPos!; + pointInfo.closeYPosition = closeYPos!; } } else if (seriesRenderer._seriesType.contains('boxandwhisker')) { - pointInfo.minYPosition = minYPos; - pointInfo.maxYPosition = maxYPos; - pointInfo.maxXPosition = maxXPos; - pointInfo.lowerXPosition = lowerXPos; - pointInfo.lowerYPosition = lowerYPos; - pointInfo.upperXPosition = upperXPos; - pointInfo.upperYPosition = upperYPos; + pointInfo.minYPosition = minYPos!; + pointInfo.maxYPosition = maxYPos!; + pointInfo.maxXPosition = maxXPos!; + pointInfo.lowerXPosition = lowerXPos!; + pointInfo.lowerYPosition = lowerYPos!; + pointInfo.upperXPosition = upperXPos!; + pointInfo.upperYPosition = upperYPos!; } if (seriesRenderer._segments.length > dataPointIndex) { - pointInfo.color = seriesRenderer._segments[dataPointIndex]._color; + pointInfo.color = seriesRenderer._segments[dataPointIndex]._color!; } else if (seriesRenderer._segments.length > 1) { pointInfo.color = - seriesRenderer._segments[seriesRenderer._segments.length - 1]._color; + seriesRenderer._segments[seriesRenderer._segments.length - 1]._color!; } pointInfo.chartDataPoint = seriesRenderer._dataPoints[dataPointIndex]; pointInfo.dataPointIndex = dataPointIndex; if (!_isTrackballTemplate) { - pointInfo.label = label; + pointInfo.label = label!; pointInfo.header = _getHeaderText( seriesRenderer._dataPoints[dataPointIndex], seriesRenderer); } @@ -1298,24 +1224,27 @@ class TrackballBehaviorRenderer with ChartBehavior { List _yAxesInfo, List<_ChartPointInfo> chartPointInfo, bool requireInvertedAxis, - [bool isPainterTooltip]) { + [bool? isPainterTooltip]) { _tooltipPadding = _chartState._requireInvertedAxis ? 8 : 5; num tooltipWidth = 0; _TooltipPositions tooltipPosition; for (int i = 0; i < chartPointInfo.length; i++) { if (requireInvertedAxis) { - _visibleLocation.add(chartPointInfo[i].xPosition); + _visibleLocation.add(chartPointInfo[i].xPosition!); } else { _visibleLocation.add((chartPointInfo[i] - .seriesRenderer + .seriesRenderer! ._seriesType .contains('range') || - chartPointInfo[i].seriesRenderer._seriesType.contains('hilo') || - chartPointInfo[i].seriesRenderer._seriesType == 'candle') - ? chartPointInfo[i].highYPosition - : chartPointInfo[i].seriesRenderer._seriesType == 'boxandwhisker' - ? chartPointInfo[i].maxYPosition - : chartPointInfo[i].yPosition); + chartPointInfo[i] + .seriesRenderer! + ._seriesType + .contains('hilo') || + chartPointInfo[i].seriesRenderer!._seriesType == 'candle') + ? chartPointInfo[i].highYPosition! + : chartPointInfo[i].seriesRenderer!._seriesType == 'boxandwhisker' + ? chartPointInfo[i].maxYPosition! + : chartPointInfo[i].yPosition!); } tooltipWidth += tooltipBottom[i] - tooltipTop[i] + _tooltipPadding; @@ -1335,12 +1264,12 @@ class TrackballBehaviorRenderer with ChartBehavior { _TooltipPositions _verticalArrangements(_TooltipPositions tooltipPPosition, List _xAxesInfo, List _yAxesInfo) { final _TooltipPositions tooltipPosition = tooltipPPosition; - num chartHeight, startPos; + num? startPos, chartHeight; final bool isTransposed = _chartState._requireInvertedAxis; dynamic secWidth, width; - final num length = tooltipPosition.tooltipTop.length; + final int length = tooltipPosition.tooltipTop.length; ChartAxisRenderer yAxisRenderer; - final num axesLength = + final int axesLength = _chartState._chartAxis._axisRenderersCollection.length; for (int i = length - 1; i >= 0; i--) { yAxisRenderer = _yAxesInfo[i]; @@ -1367,7 +1296,7 @@ class TrackballBehaviorRenderer with ChartBehavior { tooltipPosition.tooltipBottom[j] - tooltipPosition.tooltipTop[j]; if (tooltipPosition.tooltipBottom[j] > tooltipPosition.tooltipTop[j + 1] && - (tooltipPosition.tooltipTop[j + 1] > startPos && + (tooltipPosition.tooltipTop[j + 1] > startPos! && tooltipPosition.tooltipBottom[j + 1] < chartHeight)) { tooltipPosition.tooltipBottom[j] = tooltipPosition.tooltipTop[j + 1] - _tooltipPadding; @@ -1403,7 +1332,7 @@ class TrackballBehaviorRenderer with ChartBehavior { if (tooltipPosition.tooltipTop[j] < tooltipPosition.tooltipBottom[j - 1] && (tooltipPosition.tooltipTop[j - 1] > startPos && - tooltipPosition.tooltipBottom[j - 1] < chartHeight)) { + tooltipPosition.tooltipBottom[j - 1] < chartHeight!)) { tooltipPosition.tooltipTop[j] = tooltipPosition.tooltipBottom[j - 1] + _tooltipPadding; tooltipPosition.tooltipBottom[j] = @@ -1421,12 +1350,11 @@ class TrackballBehaviorRenderer with ChartBehavior { num temp, count = 0, start = 0, - startPoint = 0, halfHeight, midPos, tempTooltipHeight, temp1TooltipHeight; - int i, j, k; + int startPoint = 0, i, j, k; final num endPoint = tooltipBottom.length - 1; num tooltipHeight = (tooltipBottom[0] - tooltipTop[0]) + _tooltipPadding; temp = tooltipTop[0] + tooltipHeight; @@ -1510,41 +1438,45 @@ class TrackballBehaviorRenderer with ChartBehavior { /// To get and return label text of the trackball String _getTrackballLabelText( CartesianSeriesRenderer cartesianSeriesRenderer, - num xValue, - num yValue, - num lowValue, - num highValue, - num openValue, - num closeValue, - num minValue, - num maxValue, - num lowerQuartileValue, - num upperQuartileValue, - num meanValue, + num? xValue, + num? yValue, + num? lowValue, + num? highValue, + num? openValue, + num? closeValue, + num? minValue, + num? maxValue, + num? lowerQuartileValue, + num? upperQuartileValue, + num? meanValue, String seriesName, - num bubbleSize, - num cumulativeValue, + num? bubbleSize, + num? cumulativeValue, CartesianChartPoint dataPoint) { String labelValue; final int digits = _trackballBehavior.tooltipSettings.decimalPlaces; if (_trackballBehavior.tooltipSettings.format != null) { dynamic x; final ChartAxisRenderer axisRenderer = - cartesianSeriesRenderer._xAxisRenderer; + cartesianSeriesRenderer._xAxisRenderer!; if (axisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis _axis = axisRenderer._axis; + final DateTimeAxis _axis = axisRenderer._axis as DateTimeAxis; final DateFormat dateFormat = - _axis.dateFormat ?? axisRenderer._getLabelFormat(axisRenderer); - x = dateFormat.format(DateTime.fromMillisecondsSinceEpoch(xValue)); + _axis.dateFormat ?? _getDateTimeLabelFormat(axisRenderer); + x = dateFormat + .format(DateTime.fromMillisecondsSinceEpoch(xValue! as int)); } else if (axisRenderer is CategoryAxisRenderer) { x = dataPoint.x; + } else if (axisRenderer is DateTimeCategoryAxisRenderer) { + x = axisRenderer._labels + .indexOf(axisRenderer._dateFormat.format(dataPoint.x)); } - labelValue = _trackballBehavior.tooltipSettings.format + labelValue = _trackballBehavior.tooltipSettings.format! .replaceAll('point.x', (x ?? xValue).toString()) .replaceAll( 'point.y', - _getLabelValue( - yValue, cartesianSeriesRenderer._yAxisRenderer._axis, digits)) + _getLabelValue(yValue, + cartesianSeriesRenderer._yAxisRenderer!._axis, digits)) .replaceAll('point.high', highValue.toString()) .replaceAll('point.low', lowValue.toString()) .replaceAll('point.open', openValue.toString()) @@ -1564,50 +1496,50 @@ class TrackballBehaviorRenderer with ChartBehavior { !cartesianSeriesRenderer._seriesType.contains('hilo') && !cartesianSeriesRenderer._seriesType.contains('boxandwhisker') ? _getLabelValue( - yValue, cartesianSeriesRenderer._yAxisRenderer._axis, digits) + yValue, cartesianSeriesRenderer._yAxisRenderer!._axis, digits) : cartesianSeriesRenderer._seriesType == 'hiloopenclose' || cartesianSeriesRenderer._seriesType.contains('candle') || cartesianSeriesRenderer._seriesType.contains('boxandwhisker') ? cartesianSeriesRenderer._seriesType.contains('boxandwhisker') ? 'Maximum : ' + - _getLabelValue(maxValue, cartesianSeriesRenderer._yAxisRenderer._axis) + _getLabelValue(maxValue, cartesianSeriesRenderer._yAxisRenderer!._axis) .toString() + '\n' + 'Minimum : ' + - _getLabelValue(minValue, cartesianSeriesRenderer._yAxisRenderer._axis) + _getLabelValue(minValue, cartesianSeriesRenderer._yAxisRenderer!._axis) .toString() + '\n' + 'LowerQuartile : ' + _getLabelValue(lowerQuartileValue, - cartesianSeriesRenderer._yAxisRenderer._axis) + cartesianSeriesRenderer._yAxisRenderer!._axis) .toString() + '\n' + 'UpperQuartile : ' + _getLabelValue(upperQuartileValue, - cartesianSeriesRenderer._yAxisRenderer._axis) + cartesianSeriesRenderer._yAxisRenderer!._axis) .toString() : 'High : ' + - _getLabelValue(highValue, cartesianSeriesRenderer._yAxisRenderer._axis) + _getLabelValue(highValue, cartesianSeriesRenderer._yAxisRenderer!._axis) .toString() + '\n' + 'Low : ' + - _getLabelValue(lowValue, cartesianSeriesRenderer._yAxisRenderer._axis) + _getLabelValue(lowValue, cartesianSeriesRenderer._yAxisRenderer!._axis) .toString() + '\n' + 'Open : ' + - _getLabelValue(openValue, cartesianSeriesRenderer._yAxisRenderer._axis) + _getLabelValue(openValue, cartesianSeriesRenderer._yAxisRenderer!._axis) .toString() + '\n' + 'Close : ' + _getLabelValue(closeValue, - cartesianSeriesRenderer._yAxisRenderer._axis) + cartesianSeriesRenderer._yAxisRenderer!._axis) .toString() : 'High : ' + - _getLabelValue(highValue, cartesianSeriesRenderer._yAxisRenderer._axis) + _getLabelValue(highValue, cartesianSeriesRenderer._yAxisRenderer!._axis) .toString() + '\n' + 'Low : ' + - _getLabelValue(lowValue, cartesianSeriesRenderer._yAxisRenderer._axis) + _getLabelValue(lowValue, cartesianSeriesRenderer._yAxisRenderer!._axis) .toString(); } return labelValue; @@ -1616,23 +1548,27 @@ class TrackballBehaviorRenderer with ChartBehavior { /// To get header text of trackball String _getHeaderText(CartesianChartPoint point, CartesianSeriesRenderer seriesRenderer) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; String headerText; - String date; + String? date; if (xAxisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis _xAxis = xAxisRenderer._axis; + final DateTimeAxis _xAxis = xAxisRenderer._axis as DateTimeAxis; final DateFormat dateFormat = - _xAxis.dateFormat ?? xAxisRenderer._getLabelFormat(xAxisRenderer); + _xAxis.dateFormat ?? _getDateTimeLabelFormat(xAxisRenderer); date = dateFormat .format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor())); } headerText = xAxisRenderer is CategoryAxisRenderer ? point.x.toString() : xAxisRenderer is DateTimeAxisRenderer - ? date.toString() - : _getLabelValue(point.xValue, xAxisRenderer._axis, - _chart.tooltipBehavior.decimalPlaces) - .toString(); + ? date!.toString() + : (xAxisRenderer is DateTimeCategoryAxisRenderer + ? xAxisRenderer._getFormattedLabel( + '${point.x.microsecondsSinceEpoch}', + xAxisRenderer._dateFormat) + : _getLabelValue(point.xValue, xAxisRenderer._axis, + _chart.tooltipBehavior.decimalPlaces) + .toString()); return headerText; } @@ -1708,21 +1644,21 @@ class TrackballBehaviorRenderer with ChartBehavior { /// * canvas - Canvas used to draw the Track line on the chart. @override void onPaint(Canvas canvas) { - if (_trackballPainter != null && !_trackballPainter.canResetPath) { - _trackballPainter._drawTrackball(canvas); + if (_trackballPainter != null && !_trackballPainter!.canResetPath) { + _trackballPainter!._drawTrackball(canvas); } } /// To draw trackball line - void _drawLine(Canvas canvas, Paint paint, int seriesIndex) { + void _drawLine(Canvas canvas, Paint? paint, int seriesIndex) { assert(_trackballBehavior.lineWidth >= 0, 'Line width value of trackball should be greater than 0.'); - if (_trackballPainter != null) { - _trackballPainter._drawTrackBallLine(canvas, paint, seriesIndex); + if (_trackballPainter != null && paint != null) { + _trackballPainter!._drawTrackBallLine(canvas, paint, seriesIndex); } } - Paint _linePainter(Paint paint) => _trackballPainter?._getLinePainter(paint); + Paint? _linePainter(Paint paint) => _trackballPainter?._getLinePainter(paint); } ///Trackball marker renderer class for mutable fields and methods @@ -1731,9 +1667,9 @@ class TrackballMarkerSettingsRenderer { TrackballMarkerSettingsRenderer(this._trackballMarkerSettings); ///ignore: unused_field - final TrackballMarkerSettings _trackballMarkerSettings; + final TrackballMarkerSettings? _trackballMarkerSettings; - dart_ui.Image _image; + dart_ui.Image? _image; } /// Class to store the group mode details of trackball template. @@ -1742,8 +1678,15 @@ class TrackballGroupingModeInfo { TrackballGroupingModeInfo(this.points, this.currentPointIndices, this.visibleSeriesIndices, this.visibleSeriesList); + /// It specifies the cartesian chart points. final List points; + + /// It specifies the current point indices. final List currentPointIndices; + + /// It specifies the visible series indices. final List visibleSeriesIndices; + + /// It specifies the cartesian visible series list. final List visibleSeriesList; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart index 39026e46e..867b997e7 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart @@ -1,52 +1,55 @@ part of charts; class _TrackballPainter extends CustomPainter { - _TrackballPainter({this.chartState, this.valueNotifier}) + _TrackballPainter({required this.chartState, required this.valueNotifier}) : chart = chartState._chart, super(repaint: valueNotifier); final SfCartesianChartState chartState; final SfCartesianChart chart; - Timer timer; + Timer? timer; ValueNotifier valueNotifier; - double pointerLength; - double pointerWidth; + late double pointerLength; + late double pointerWidth; double nosePointY = 0; double nosePointX = 0; double totalWidth = 0; - double x; - double y; - double xPos; - double yPos; + double? x; + double? y; + double? xPos; + double? yPos; bool isTop = false; - double borderRadius; + late double borderRadius; Path backgroundPath = Path(); bool canResetPath = true; bool isLeft = false; bool isRight = false; - bool enable; double groupAllPadding = 10; - List stringValue = []; + List<_TrackballElement> stringValue = <_TrackballElement>[]; Rect boundaryRect = const Rect.fromLTWH(0, 0, 0, 0); double leftPadding = 0; double topPadding = 0; bool isHorizontalOrientation = false; bool isRectSeries = false; - TextStyle labelStyle; + late TextStyle labelStyle; bool divider = true; - List _markerShapes; + List? _markerShapes; //ignore: prefer_final_fields List _tooltipTop = []; //ignore: prefer_final_fields List _tooltipBottom = []; final List _xAxesInfo = []; final List _yAxesInfo = []; - List<_ChartPointInfo> chartPointInfo; - List<_ClosestPoints> _visiblePoints; - _TooltipPositions _tooltipPosition; + late List<_ChartPointInfo> chartPointInfo; + late List<_ClosestPoints> _visiblePoints; + _TooltipPositions? _tooltipPosition; num _padding = 5; - num _tooltipPadding; - bool isRangeSeries; - bool isBoxSeries; + late num _tooltipPadding; + bool isRangeSeries = false; + bool isBoxSeries = false; + late Rect labelRect; + late num markerSize, markerPadding; + bool isGroupMode = false; + late double lastMarkerResultHeight; @override void paint(Canvas canvas, Size size) => @@ -56,99 +59,190 @@ class _TrackballPainter extends CustomPainter { /// To draw the trackball for all series void _drawTrackball(Canvas canvas) { - chartPointInfo = chartState._trackballBehaviorRenderer._chartPointInfo; - _markerShapes = chartState._trackballBehaviorRenderer._markerShapes; - _visiblePoints = chartState._trackballBehaviorRenderer._visiblePoints; - isRangeSeries = chartState._trackballBehaviorRenderer._isRangeSeries; - isBoxSeries = chartState._trackballBehaviorRenderer._isBoxSeries; - _tooltipPadding = chartState._requireInvertedAxis ? 8 : 5; - borderRadius = chart.trackballBehavior.tooltipSettings.borderRadius; - pointerLength = chart.trackballBehavior.tooltipSettings.arrowLength; - pointerWidth = chart.trackballBehavior.tooltipSettings.arrowWidth; - isLeft = false; - isRight = false; - double height = 0, width = 0; - boundaryRect = chartState._chartAxis._axisClipRect; - totalWidth = boundaryRect.left + boundaryRect.width; - labelStyle = TextStyle( - color: chart.trackballBehavior.tooltipSettings.textStyle.color ?? - chartState._chartTheme.crosshairLabelColor, - fontSize: chart.trackballBehavior.tooltipSettings.textStyle.fontSize, - fontFamily: - chart.trackballBehavior.tooltipSettings.textStyle.fontFamily, - fontStyle: chart.trackballBehavior.tooltipSettings.textStyle.fontStyle, - fontWeight: - chart.trackballBehavior.tooltipSettings.textStyle.fontWeight, - inherit: chart.trackballBehavior.tooltipSettings.textStyle.inherit, - backgroundColor: - chart.trackballBehavior.tooltipSettings.textStyle.backgroundColor, - letterSpacing: - chart.trackballBehavior.tooltipSettings.textStyle.letterSpacing, - wordSpacing: - chart.trackballBehavior.tooltipSettings.textStyle.wordSpacing, - textBaseline: - chart.trackballBehavior.tooltipSettings.textStyle.textBaseline, - height: chart.trackballBehavior.tooltipSettings.textStyle.height, - locale: chart.trackballBehavior.tooltipSettings.textStyle.locale, - foreground: - chart.trackballBehavior.tooltipSettings.textStyle.foreground, - background: - chart.trackballBehavior.tooltipSettings.textStyle.background, - shadows: chart.trackballBehavior.tooltipSettings.textStyle.shadows, - fontFeatures: - chart.trackballBehavior.tooltipSettings.textStyle.fontFeatures, - decoration: - chart.trackballBehavior.tooltipSettings.textStyle.decoration, - decorationColor: - chart.trackballBehavior.tooltipSettings.textStyle.decorationColor, - decorationStyle: - chart.trackballBehavior.tooltipSettings.textStyle.decorationStyle, - decorationThickness: chart - .trackballBehavior.tooltipSettings.textStyle.decorationThickness, - debugLabel: - chart.trackballBehavior.tooltipSettings.textStyle.debugLabel, - fontFamilyFallback: chart - .trackballBehavior.tooltipSettings.textStyle.fontFamilyFallback); - for (int index = 0; index < chartPointInfo.length; index++) { - if (((chartPointInfo[index] - .seriesRenderer - ._seriesType - .contains('column') || - chartPointInfo[index].seriesRenderer._seriesType == - 'candle' || - chartPointInfo[index] - .seriesRenderer - ._seriesType - .contains('boxandwhisker') || - chartPointInfo[index] - .seriesRenderer - ._seriesType - .contains('hilo')) && - !chartState._requireInvertedAxis) || - (chartPointInfo[index].seriesRenderer._seriesType.contains('bar') && - chartState._requireInvertedAxis)) { - isHorizontalOrientation = true; - } - isRectSeries = false; - if ((chartPointInfo[index] - .seriesRenderer - ._seriesType - .contains('column') || - chartPointInfo[index].seriesRenderer._seriesType == 'candle' || - chartPointInfo[index] - .seriesRenderer - ._seriesType - .contains('hilo') || - chartPointInfo[index] - .seriesRenderer - ._seriesType - .contains('boxandwhisker') && - chartState._requireInvertedAxis) || - (chartPointInfo[index].seriesRenderer._seriesType.contains('bar') && - !chartState._requireInvertedAxis)) { - isRectSeries = true; + if (!_isSeriesAnimating()) { + chartPointInfo = chartState._trackballBehaviorRenderer._chartPointInfo; + _markerShapes = chartState._trackballBehaviorRenderer._markerShapes; + _visiblePoints = chartState._trackballBehaviorRenderer._visiblePoints; + isRangeSeries = chartState._trackballBehaviorRenderer._isRangeSeries; + isBoxSeries = chartState._trackballBehaviorRenderer._isBoxSeries; + _tooltipPadding = chartState._requireInvertedAxis ? 8 : 5; + borderRadius = chart.trackballBehavior.tooltipSettings.borderRadius; + pointerLength = chart.trackballBehavior.tooltipSettings.arrowLength; + pointerWidth = chart.trackballBehavior.tooltipSettings.arrowWidth; + isGroupMode = (chart.trackballBehavior.tooltipDisplayMode == + TrackballDisplayMode.groupAllPoints) + ? true + : false; + + isLeft = false; + isRight = false; + double height = 0, width = 0; + boundaryRect = chartState._chartAxis._axisClipRect; + totalWidth = boundaryRect.left + boundaryRect.width; + labelStyle = TextStyle( + color: chart.trackballBehavior.tooltipSettings.textStyle.color ?? + chartState._chartTheme.crosshairLabelColor, + fontSize: chart.trackballBehavior.tooltipSettings.textStyle.fontSize, + fontFamily: + chart.trackballBehavior.tooltipSettings.textStyle.fontFamily, + fontStyle: + chart.trackballBehavior.tooltipSettings.textStyle.fontStyle, + fontWeight: + chart.trackballBehavior.tooltipSettings.textStyle.fontWeight, + inherit: chart.trackballBehavior.tooltipSettings.textStyle.inherit, + backgroundColor: + chart.trackballBehavior.tooltipSettings.textStyle.backgroundColor, + letterSpacing: + chart.trackballBehavior.tooltipSettings.textStyle.letterSpacing, + wordSpacing: + chart.trackballBehavior.tooltipSettings.textStyle.wordSpacing, + textBaseline: + chart.trackballBehavior.tooltipSettings.textStyle.textBaseline, + height: chart.trackballBehavior.tooltipSettings.textStyle.height, + locale: chart.trackballBehavior.tooltipSettings.textStyle.locale, + foreground: + chart.trackballBehavior.tooltipSettings.textStyle.foreground, + background: + chart.trackballBehavior.tooltipSettings.textStyle.background, + shadows: chart.trackballBehavior.tooltipSettings.textStyle.shadows, + fontFeatures: + chart.trackballBehavior.tooltipSettings.textStyle.fontFeatures, + decoration: + chart.trackballBehavior.tooltipSettings.textStyle.decoration, + decorationColor: + chart.trackballBehavior.tooltipSettings.textStyle.decorationColor, + decorationStyle: + chart.trackballBehavior.tooltipSettings.textStyle.decorationStyle, + decorationThickness: chart + .trackballBehavior.tooltipSettings.textStyle.decorationThickness, + debugLabel: + chart.trackballBehavior.tooltipSettings.textStyle.debugLabel, + fontFamilyFallback: chart + .trackballBehavior.tooltipSettings.textStyle.fontFamilyFallback); + _ChartPointInfo? trackLinePoint = + chartPointInfo.isNotEmpty ? chartPointInfo[0] : null; + for (int index = 0; index < chartPointInfo.length; index++) { + final _ChartPointInfo next = chartPointInfo[index]; + final _ChartPointInfo pres = trackLinePoint!; + final Offset pos = chartState._trackballBehaviorRenderer._tapPosition; + if (chartState._requireInvertedAxis + ? ((pos.dy - pres.yPosition!).abs() >= + (pos.dy - next.yPosition!).abs()) + : ((pos.dx - pres.xPosition!).abs() >= + (pos.dx - next.xPosition!).abs())) { + trackLinePoint = chartPointInfo[index]; + } + if (((chartPointInfo[index] + .seriesRenderer! + ._seriesType + .contains('column') || + chartPointInfo[index].seriesRenderer!._seriesType == + 'candle' || + chartPointInfo[index] + .seriesRenderer! + ._seriesType + .contains('boxandwhisker') || + chartPointInfo[index] + .seriesRenderer! + ._seriesType + .contains('hilo')) && + !chartState._requireInvertedAxis) || + (chartPointInfo[index] + .seriesRenderer! + ._seriesType + .contains('bar') && + chartState._requireInvertedAxis)) { + isHorizontalOrientation = true; + } + isRectSeries = false; + if ((chartPointInfo[index] + .seriesRenderer! + ._seriesType + .contains('column') || + chartPointInfo[index].seriesRenderer!._seriesType == 'candle' || + chartPointInfo[index] + .seriesRenderer! + ._seriesType + .contains('hilo') || + chartPointInfo[index] + .seriesRenderer! + ._seriesType + .contains('boxandwhisker') && + chartState._requireInvertedAxis) || + (chartPointInfo[index] + .seriesRenderer! + ._seriesType + .contains('bar') && + !chartState._requireInvertedAxis)) { + isRectSeries = true; + } + + final Size size = _getTooltipSize(height, width, index); + height = size.height; + width = size.width; + if (width < 10) { + width = 10; // minimum width for tooltip to render + borderRadius = borderRadius > 5 ? 5 : borderRadius; + } + borderRadius = borderRadius > 15 ? 15 : borderRadius; + // Padding added for avoid tooltip and the data point are too close and + // extra padding based on trackball marker and width + _padding = (chart.trackballBehavior.markerSettings != null && + chart.trackballBehavior.markerSettings!.markerVisibility == + TrackballVisibilityMode.auto + ? (chartPointInfo[index] + .seriesRenderer! + ._series + .markerSettings + .isVisible) + : chart.trackballBehavior.markerSettings != null && + chart.trackballBehavior.markerSettings!.markerVisibility == + TrackballVisibilityMode.visible) + ? (chart.trackballBehavior.markerSettings!.width / 2) + 5 + : _padding; + if (x != null && + y != null && + chart.trackballBehavior.tooltipSettings.enable) { + if (isGroupMode && + ((chartPointInfo[index].header != null && + chartPointInfo[index].header != '') || + (chartPointInfo[index].label != null && + chartPointInfo[index].label != ''))) { + _calculateTrackballRect( + canvas, width, height, index, chartPointInfo); + } else { + if (!canResetPath && + chartPointInfo[index].label != null && + chartPointInfo[index].label != '') { + _tooltipTop.add(chartState._requireInvertedAxis + ? _visiblePoints[index].closestPointX - + (_tooltipPadding) - + (width / 2) + : _visiblePoints[index].closestPointY - + _tooltipPadding - + height / 2); + _tooltipBottom.add(chartState._requireInvertedAxis + ? _visiblePoints[index].closestPointX + + (_tooltipPadding) + + (width / 2) + : _visiblePoints[index].closestPointY + + _tooltipPadding + + height / 2); + _xAxesInfo + .add(chartPointInfo[index].seriesRenderer!._xAxisRenderer!); + _yAxesInfo + .add(chartPointInfo[index].seriesRenderer!._yAxisRenderer!); + } + } + } + + if (isGroupMode) { + break; + } } + if (!canResetPath && + trackLinePoint != null && chart.trackballBehavior.lineType != TrackballLineType.none) { final Paint trackballLinePaint = Paint(); trackballLinePaint.color = chart.trackballBehavior.lineColor ?? @@ -162,135 +256,89 @@ class _TrackballPainter extends CustomPainter { canvas, chartState._trackballBehaviorRenderer ._linePainter(trackballLinePaint), - index); - } - final Size size = _getTooltipSize(height, width, index); - height = size.height; - width = size.width; - if (width < 10) { - width = 10; // minimum width for tooltip to render - borderRadius = borderRadius > 5 ? 5 : borderRadius; - } - borderRadius = borderRadius > 15 ? 15 : borderRadius; - // Padding added for avoid tooltip and the data point are too close and - // extra padding based on trackball marker and width - _padding = (chart.trackballBehavior.markerSettings != null && - chart.trackballBehavior.markerSettings.markerVisibility == - TrackballVisibilityMode.auto - ? (chartPointInfo[index] - .seriesRenderer - ._series - .markerSettings - .isVisible) - : chart.trackballBehavior.markerSettings != null && - chart.trackballBehavior.markerSettings.markerVisibility == - TrackballVisibilityMode.visible) - ? (chart.trackballBehavior.markerSettings.width / 2) + 5 - : _padding; - if (x != null && - y != null && - chart.trackballBehavior.tooltipSettings.enable) { - if (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints && - ((chartPointInfo[index].header != null && - chartPointInfo[index].header != '') || - (chartPointInfo[index].label != null && - chartPointInfo[index].label != ''))) { - _calculateTrackballRect(canvas, width, height, index, chartPointInfo); - } else { - if (!canResetPath && - chartPointInfo[index].label != null && - chartPointInfo[index].label != '') { - _tooltipTop.add(chartState._requireInvertedAxis - ? _visiblePoints[index].closestPointX - - (_tooltipPadding) - - (width / 2) - : _visiblePoints[index].closestPointY - - _tooltipPadding - - height / 2); - _tooltipBottom.add(chartState._requireInvertedAxis - ? _visiblePoints[index].closestPointX + - (_tooltipPadding) + - (width / 2) - : _visiblePoints[index].closestPointY + - _tooltipPadding + - height / 2); - _xAxesInfo.add(chartPointInfo[index].seriesRenderer._xAxisRenderer); - _yAxesInfo.add(chartPointInfo[index].seriesRenderer._yAxisRenderer); - } - } + chartPointInfo.indexOf(trackLinePoint)); } - if (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints) { - break; + if (_tooltipTop != null && _tooltipTop.isNotEmpty) { + _tooltipPosition = chartState._trackballBehaviorRenderer + ._smartTooltipPositions( + _tooltipTop, + _tooltipBottom, + _xAxesInfo, + _yAxesInfo, + chartPointInfo, + chartState._requireInvertedAxis, + true); } - } - if (_tooltipTop != null && _tooltipTop.isNotEmpty) { - _tooltipPosition = chartState._trackballBehaviorRenderer - ._smartTooltipPositions( - _tooltipTop, - _tooltipBottom, - _xAxesInfo, - _yAxesInfo, - chartPointInfo, - chartState._requireInvertedAxis, - true); - } + for (int index = 0; index < chartPointInfo.length; index++) { + chartState._trackballBehaviorRenderer._trackballMarker(index); - for (int index = 0; index < chartPointInfo.length; index++) { - chartState._trackballBehaviorRenderer._trackballMarker(index); + if (_markerShapes != null && + _markerShapes!.isNotEmpty && + _markerShapes!.length > index) { + chartState._trackballBehaviorRenderer._renderTrackballMarker( + chartPointInfo[index].seriesRenderer!, + canvas, + chart.trackballBehavior, + index); + } - if (_markerShapes != null && - _markerShapes.isNotEmpty && - _markerShapes.length > index) { - chartState._trackballBehaviorRenderer._renderTrackballMarker( - chartPointInfo[index].seriesRenderer, - canvas, - chart.trackballBehavior, - index); + // Padding added for avoid tooltip and the data point are too close and + // extra padding based on trackball marker and width + _padding = (chart.trackballBehavior.markerSettings != null && + chart.trackballBehavior.markerSettings!.markerVisibility == + TrackballVisibilityMode.auto + ? (chartPointInfo[index] + .seriesRenderer! + ._series + .markerSettings + .isVisible) + : chart.trackballBehavior.markerSettings != null && + chart.trackballBehavior.markerSettings!.markerVisibility == + TrackballVisibilityMode.visible) + ? (chart.trackballBehavior.markerSettings!.width / 2) + 5 + : _padding; + if (chart.trackballBehavior.tooltipSettings.enable && + !isGroupMode && + chartPointInfo[index].label != null && + chartPointInfo[index].label != '') { + final Size size = _getTooltipSize(height, width, index); + height = size.height; + width = size.width; + if (width < 10) { + width = 10; // minimum width for tooltip to render + borderRadius = borderRadius > 5 ? 5 : borderRadius; + } + _calculateTrackballRect( + canvas, width, height, index, chartPointInfo, _tooltipPosition!); + if (index == chartPointInfo.length - 1) { + _tooltipTop.clear(); + _tooltipBottom.clear(); + _tooltipPosition!.tooltipTop.clear(); + _tooltipPosition!.tooltipBottom.clear(); + _xAxesInfo.clear(); + _yAxesInfo.clear(); + } + } } + } + } - // Padding added for avoid tooltip and the data point are too close and - // extra padding based on trackball marker and width - _padding = (chart.trackballBehavior.markerSettings != null && - chart.trackballBehavior.markerSettings.markerVisibility == - TrackballVisibilityMode.auto - ? (chartPointInfo[index] - .seriesRenderer - ._series - .markerSettings - .isVisible) - : chart.trackballBehavior.markerSettings != null && - chart.trackballBehavior.markerSettings.markerVisibility == - TrackballVisibilityMode.visible) - ? (chart.trackballBehavior.markerSettings.width / 2) + 5 - : _padding; - if (chart.trackballBehavior.tooltipSettings.enable && - chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.groupAllPoints && - chartPointInfo[index].label != null && - chartPointInfo[index].label != '') { - final Size size = _getTooltipSize(height, width, index); - height = size.height; - width = size.width; - if (width < 10) { - width = 10; // minimum width for tooltip to render - borderRadius = borderRadius > 5 ? 5 : borderRadius; - } - _calculateTrackballRect( - canvas, width, height, index, chartPointInfo, _tooltipPosition); - if (index == chartPointInfo.length - 1) { - _tooltipTop.clear(); - _tooltipBottom.clear(); - _tooltipPosition.tooltipTop.clear(); - _tooltipPosition.tooltipBottom.clear(); - _xAxesInfo.clear(); - _yAxesInfo.clear(); - } + bool _isSeriesAnimating() { + for (int i = 0; + i < chartState._chartSeries.visibleSeriesRenderers.length; + i++) { + final CartesianSeriesRenderer seriesRenderer = + chartState._chartSeries.visibleSeriesRenderers[i]; + if (!(seriesRenderer._animationCompleted || + seriesRenderer._series.animationDuration == 0 || + !chartState._initialRender!) && + seriesRenderer._series.isVisible) { + return true; } } + return false; } bool headerText = false; @@ -300,10 +348,10 @@ class _TrackballPainter extends CustomPainter { /// To get tooltip size Size _getTooltipSize(double height, double width, int index) { final Offset position = Offset( - chartPointInfo[index].xPosition, chartPointInfo[index].yPosition); - stringValue = []; - final String format = chartPointInfo[index] - .seriesRenderer + chartPointInfo[index].xPosition!, chartPointInfo[index].yPosition!); + stringValue = <_TrackballElement>[]; + final String? format = chartPointInfo[index] + .seriesRenderer! ._chart .trackballBehavior .tooltipSettings @@ -321,81 +369,93 @@ class _TrackballPainter extends CustomPainter { } if (chartPointInfo[index].header != null && chartPointInfo[index].header != '') { - stringValue.add(chartPointInfo[index].header); + stringValue.add(_TrackballElement(chartPointInfo[index].header!, null)); } - if (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints) { + if (isGroupMode) { String str1 = ''; for (int i = 0; i < chartPointInfo.length; i++) { if (chartPointInfo[i].header != null && - chartPointInfo[i].header.contains(':')) { + chartPointInfo[i].header!.contains(':')) { headerText = true; } bool isHeader = chartPointInfo[i].header != null && chartPointInfo[i].header != ''; bool isLabel = chartPointInfo[i].label != null && chartPointInfo[i].label != ''; - if (chartPointInfo[i].seriesRenderer._isIndicator) { + if (chartPointInfo[i].seriesRenderer!._isIndicator) { isHeader = chartPointInfo[0].header != null && chartPointInfo[0].header != ''; isLabel = chartPointInfo[0].label != null && chartPointInfo[0].label != ''; } divider = isHeader && isLabel; - final String str = (i == 0 && isHeader) ? '\n' : ''; - final String seriesType = chartPointInfo[i].seriesRenderer._seriesType; - if (chartPointInfo[i].seriesRenderer._isIndicator && + final String seriesType = chartPointInfo[i].seriesRenderer!._seriesType; + if (chartPointInfo[i].seriesRenderer!._isIndicator && chartPointInfo[i] - .seriesRenderer + .seriesRenderer! ._series - .name + .name! .contains('rangearea')) { - str1 = i == 0 ? '\n' : ''; + if (i == 0) { + stringValue.add(_TrackballElement('', null)); + } else { + str1 = ''; + } continue; } else if ((seriesType.contains('hilo') || seriesType.contains('candle') || seriesType.contains('range') || seriesType == 'boxandwhisker') && chartPointInfo[i] - .seriesRenderer + .seriesRenderer! ._chart .trackballBehavior .tooltipSettings .format == null && isLabel) { - stringValue.add(((chartPointInfo[index].header == null || - chartPointInfo[index].header == '') - ? '' - : '\n') + - '${chartPointInfo[i].seriesRenderer._seriesName}\n${chartPointInfo[i].label}'); - } else if (chartPointInfo[i].seriesRenderer._series.name != null) { + stringValue.add(_TrackballElement( + ((chartPointInfo[index].header == null || + chartPointInfo[index].header == '') + ? '' + : i == 0 + ? '\n' + : '') + + '${chartPointInfo[i].seriesRenderer!._seriesName}\n${chartPointInfo[i].label}', + chartPointInfo[i].seriesRenderer!)); + } else if (chartPointInfo[i].seriesRenderer!._series.name != null) { if (chartPointInfo[i] - .seriesRenderer + .seriesRenderer! ._chart .trackballBehavior .tooltipSettings .format != null) { if (isHeader && isLabel && i == 0) { - stringValue.add(''); + stringValue.add(_TrackballElement('', null)); } if (isLabel) { - stringValue.add(chartPointInfo[i].label); + stringValue.add(_TrackballElement( + chartPointInfo[i].label!, chartPointInfo[i].seriesRenderer!)); } } else if (isLabel && - chartPointInfo[i].label.contains(':') && + chartPointInfo[i].label!.contains(':') && (chartPointInfo[i].header == null || chartPointInfo[i].header == '')) { - stringValue.add(chartPointInfo[i].label); + stringValue.add(_TrackballElement( + chartPointInfo[i].label!, chartPointInfo[i].seriesRenderer!)); divider = false; } else { + if (isHeader && isLabel && i == 0) { + stringValue.add(_TrackballElement('', null)); + } if (isLabel) { - stringValue.add(str1 + - str + - chartPointInfo[i].seriesRenderer._series.name + - ': ' + - chartPointInfo[i].label); + stringValue.add(_TrackballElement( + str1 + + chartPointInfo[i].seriesRenderer!._series.name! + + ': ' + + chartPointInfo[i].label!, + chartPointInfo[i].seriesRenderer!)); } divider = (chartPointInfo[0].header != null && chartPointInfo[0].header != '') && @@ -407,22 +467,23 @@ class _TrackballPainter extends CustomPainter { } else { if (isLabel) { if (isHeader && i == 0) { - stringValue.add(''); + stringValue.add(_TrackballElement('', null)); } - stringValue.add(chartPointInfo[i].label); + stringValue.add(_TrackballElement( + chartPointInfo[i].label!, chartPointInfo[i].seriesRenderer!)); } } } for (int i = 0; i < stringValue.length; i++) { - String measureString = stringValue[i]; + String measureString = stringValue[i].label; if (measureString.contains('') && measureString.contains('')) { measureString = measureString.replaceAll('', '').replaceAll('', ''); } - if (_measureText(measureString, labelStyle).width > width) { - width = _measureText(measureString, labelStyle).width; + if (measureText(measureString, labelStyle).width > width) { + width = measureText(measureString, labelStyle).width; } - height += _measureText(measureString, labelStyle).height; + height += measureText(measureString, labelStyle).height; } x = position.dx; if (chart.trackballBehavior.tooltipAlignment == ChartAlignment.center) { @@ -434,37 +495,43 @@ class _TrackballPainter extends CustomPainter { y = boundaryRect.bottom; } } else { - stringValue = []; + stringValue = <_TrackballElement>[]; if (chartPointInfo[index].label != null && chartPointInfo[index].label != '') { - stringValue.add(chartPointInfo[index].label); + stringValue.add(_TrackballElement(chartPointInfo[index].label!, + chartPointInfo[index].seriesRenderer!)); } - String measureString = stringValue.isNotEmpty ? stringValue[0] : null; + + String? measureString = + stringValue.isNotEmpty ? stringValue[0].label : null; if (measureString != null && measureString.contains('') && measureString.contains('')) { measureString = measureString.replaceAll('', '').replaceAll('', ''); } - final Size size = _measureText(measureString, labelStyle); + final Size size = measureText(measureString!, labelStyle); width = size.width; height = size.height; - if (chartPointInfo[index].seriesRenderer._seriesType.contains('column') || - chartPointInfo[index].seriesRenderer._seriesType.contains('bar') || - chartPointInfo[index].seriesRenderer._seriesType == 'candle' || + if (chartPointInfo[index] + .seriesRenderer! + ._seriesType + .contains('column') || + chartPointInfo[index].seriesRenderer!._seriesType.contains('bar') || + chartPointInfo[index].seriesRenderer!._seriesType == 'candle' || chartPointInfo[index] - .seriesRenderer + .seriesRenderer! ._seriesType .contains('boxandwhisker') || - chartPointInfo[index].seriesRenderer._seriesType.contains('hilo')) { + chartPointInfo[index].seriesRenderer!._seriesType.contains('hilo')) { x = position.dx; y = position.dy; - } else if (chartPointInfo[index].seriesRenderer._seriesType == + } else if (chartPointInfo[index].seriesRenderer!._seriesType == 'rangearea') { - x = chartPointInfo[index].chartDataPoint.markerPoint.x; - y = (chartPointInfo[index].chartDataPoint.markerPoint.y + - chartPointInfo[index].chartDataPoint.markerPoint2.y) / + x = chartPointInfo[index].chartDataPoint!.markerPoint!.x; + y = (chartPointInfo[index].chartDataPoint!.markerPoint!.y + + chartPointInfo[index].chartDataPoint!.markerPoint2!.y) / 2; } else { x = position.dx; @@ -477,101 +544,122 @@ class _TrackballPainter extends CustomPainter { /// To find the rect location of the trackball void _calculateTrackballRect( Canvas canvas, double width, double height, int index, - [List<_ChartPointInfo> chartPointInfo, - _TooltipPositions tooltipPosition]) { - Rect labelRect = Rect.fromLTWH(x, y, width + 15, height + 10); - - final Rect backgroundRect = Rect.fromLTWH(boundaryRect.left + 25, - boundaryRect.top, boundaryRect.width - 50, boundaryRect.height); - final Rect leftRect = Rect.fromLTWH(boundaryRect.left - 5, boundaryRect.top, - backgroundRect.left - (boundaryRect.left - 5), boundaryRect.height); - final Rect rightRect = Rect.fromLTWH(backgroundRect.right, boundaryRect.top, - (boundaryRect.right + 5) - backgroundRect.right, boundaryRect.height); + [List<_ChartPointInfo>? chartPointInfo, + _TooltipPositions? tooltipPosition]) { + final double widthPadding = 17; + markerSize = 10; + Rect leftRect, rightRect; + if (!chart.trackballBehavior.tooltipSettings.canShowMarker) { + labelRect = Rect.fromLTWH(x!, y!, width + 15, height + 10); + final Rect backgroundRect = Rect.fromLTWH(boundaryRect.left + 25, + boundaryRect.top, boundaryRect.width - 50, boundaryRect.height); + leftRect = Rect.fromLTWH(boundaryRect.left - 5, boundaryRect.top, + backgroundRect.left - (boundaryRect.left - 5), boundaryRect.height); + rightRect = Rect.fromLTWH(backgroundRect.right, boundaryRect.top, + (boundaryRect.right + 5) - backgroundRect.right, boundaryRect.height); + } else { + labelRect = Rect.fromLTWH( + x!, y!, width + (2 * markerSize) + widthPadding, height + 10); + final Rect backgroundRect = Rect.fromLTWH(boundaryRect.left + 20, + boundaryRect.top, boundaryRect.width - 40, boundaryRect.height); + leftRect = Rect.fromLTWH( + boundaryRect.left - 5, + boundaryRect.top - 20, + backgroundRect.left - (boundaryRect.left - 5), + boundaryRect.height + 40); + rightRect = Rect.fromLTWH( + backgroundRect.right, + boundaryRect.top - 20, + (boundaryRect.right + 5) + backgroundRect.right, + boundaryRect.height + 40); + } - if (leftRect.contains(Offset(x, y))) { + if (leftRect.contains(Offset(x!, y!))) { isLeft = true; isRight = false; - } else if (rightRect.contains(Offset(x, y))) { + } else if (rightRect.contains(Offset(x!, y!))) { isLeft = false; isRight = true; } - if (y > pointerLength + labelRect.height) { + if (y! > pointerLength + labelRect.height) { _calculateTooltipSize(labelRect, chartPointInfo, tooltipPosition, index); } else { isTop = false; - if (chartPointInfo[index].seriesRenderer._seriesType.contains('bar') + if (chartPointInfo![index].seriesRenderer!._seriesType.contains('bar') ? chartState._requireInvertedAxis : chartState._requireInvertedAxis) { - xPos = x - (labelRect.width / 2); - yPos = (y + pointerLength) + _padding; + xPos = x! - (labelRect.width / 2); + yPos = (y! + pointerLength) + _padding; nosePointX = labelRect.left; nosePointY = labelRect.top + _padding; - final double tooltipRightEnd = x + (labelRect.width / 2); - xPos = xPos < boundaryRect.left + final double tooltipRightEnd = x! + (labelRect.width / 2); + xPos = xPos! < boundaryRect.left ? boundaryRect.left : tooltipRightEnd > totalWidth ? totalWidth - labelRect.width : xPos; } else { - if (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints) { - xPos = x - labelRect.width / 2; + if (isGroupMode) { + xPos = x! - labelRect.width / 2; yPos = y; } else { - xPos = x + _padding + pointerLength; - yPos = (y + pointerLength / 2) + _padding; + xPos = x! + _padding + pointerLength; + yPos = (y! + pointerLength / 2) + _padding; } nosePointX = labelRect.left; nosePointY = labelRect.top; - final double tooltipRightEnd = x + labelRect.width; - if (xPos < boundaryRect.left) { - xPos = (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints) - ? boundaryRect.left + groupAllPadding + final double tooltipRightEnd = x! + labelRect.width; + if (xPos! < boundaryRect.left) { + xPos = (isGroupMode) + ? (x! < boundaryRect.left) + ? boundaryRect.left + groupAllPadding + : x! + groupAllPadding : boundaryRect.left; } else if (tooltipRightEnd > totalWidth) { - xPos = (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints) - ? (xPos - (labelRect.width / 2) - groupAllPadding) - : ((xPos - labelRect.width - (2 * _padding)) - 2 * pointerLength); + xPos = isGroupMode + ? (xPos! - (labelRect.width / 2) - groupAllPadding) + : ((xPos! - labelRect.width - (2 * _padding)) - + 2 * pointerLength); isRight = true; } else { - xPos = (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints && - chart.trackballBehavior.tooltipAlignment == - ChartAlignment.near) - ? x + _padding + pointerLength + xPos = isGroupMode + ? xPos! + (labelRect.width / 2) + groupAllPadding : xPos; } + + if (isGroupMode && (yPos! + labelRect.height) >= boundaryRect.bottom) { + yPos = boundaryRect.bottom - labelRect.height; + } + + if (isGroupMode && yPos! <= boundaryRect.top) { + yPos = boundaryRect.top; + } } } - if (chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.groupAllPoints) { - if (xPos <= boundaryRect.left + 5) { - xPos = xPos + 10; - } else if (xPos + labelRect.width >= totalWidth - 5) { - xPos = xPos - 10; + if (!isGroupMode) { + if (xPos! <= boundaryRect.left + 5) { + xPos = xPos! + 10; + } else if (xPos! + labelRect.width >= totalWidth - 5) { + xPos = xPos! - 10; } } - labelRect = chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints || + labelRect = isGroupMode || chart.trackballBehavior.tooltipDisplayMode == TrackballDisplayMode.nearestPoint || chart.axes.isNotEmpty - ? Rect.fromLTWH(xPos, yPos, labelRect.width, labelRect.height) + ? Rect.fromLTWH(xPos!, yPos!, labelRect.width, labelRect.height) : Rect.fromLTWH( chartState._requireInvertedAxis - ? tooltipPosition.tooltipTop[index] - : xPos, + ? tooltipPosition!.tooltipTop[index].toDouble() + : xPos!, !chartState._requireInvertedAxis - ? tooltipPosition.tooltipTop[index] - : yPos, + ? tooltipPosition!.tooltipTop[index].toDouble() + : yPos!, labelRect.width, labelRect.height); - if (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints) { + if (isGroupMode) { _drawTooltipBackground( canvas, labelRect, @@ -587,9 +675,9 @@ class _TrackballPainter extends CustomPainter { null); } else { if (chartState._requireInvertedAxis - ? tooltipPosition.tooltipTop[index] >= boundaryRect.left && + ? tooltipPosition!.tooltipTop[index] >= boundaryRect.left && tooltipPosition.tooltipBottom[index] <= boundaryRect.right - : tooltipPosition.tooltipTop[index] >= boundaryRect.top && + : tooltipPosition!.tooltipTop[index] >= boundaryRect.top && tooltipPosition.tooltipBottom[index] <= boundaryRect.bottom) { _drawTooltipBackground( canvas, @@ -603,10 +691,10 @@ class _TrackballPainter extends CustomPainter { isRight, index, isRangeSeries - ? chartPointInfo[index].highXPosition + ? chartPointInfo![index].highXPosition : isBoxSeries - ? chartPointInfo[index].maxXPosition - : chartPointInfo[index].xPosition, + ? chartPointInfo![index].maxXPosition + : chartPointInfo![index].xPosition, isRangeSeries ? chartPointInfo[index].highYPosition : isBoxSeries @@ -619,49 +707,53 @@ class _TrackballPainter extends CustomPainter { /// To find the trackball tooltip size void _calculateTooltipSize( Rect labelRect, - List<_ChartPointInfo> chartPointInfo, - _TooltipPositions tooltipPositions, + List<_ChartPointInfo>? chartPointInfo, + _TooltipPositions? tooltipPositions, int index) { isTop = true; - if (chartPointInfo[index].seriesRenderer._seriesType.contains('bar') + isRight = false; + if (chartPointInfo![index].seriesRenderer!._seriesType.contains('bar') ? chartState._requireInvertedAxis : chartState._requireInvertedAxis) { - xPos = x - (labelRect.width / 2); - yPos = (y - labelRect.height) - _padding; + xPos = x! - (labelRect.width / 2); + yPos = (y! - labelRect.height) - _padding; nosePointY = labelRect.top - _padding; nosePointX = labelRect.left; - final double tooltipRightEnd = x + (labelRect.width / 2); - xPos = xPos < boundaryRect.left + final double tooltipRightEnd = x! + (labelRect.width / 2); + xPos = xPos! < boundaryRect.left ? boundaryRect.left : tooltipRightEnd > totalWidth ? totalWidth - labelRect.width : xPos; - yPos = yPos - pointerLength; - if (yPos + labelRect.height >= boundaryRect.bottom) { + yPos = yPos! - pointerLength; + if (yPos! + labelRect.height >= boundaryRect.bottom) { yPos = boundaryRect.bottom - labelRect.height; } } else { - xPos = x + _padding + pointerLength; - yPos = (y - labelRect.height / 2); - nosePointY = yPos; + xPos = x! + _padding + pointerLength; + yPos = (y! - labelRect.height / 2); + nosePointY = yPos!; nosePointX = labelRect.left; - final double tooltipRightEnd = xPos + (labelRect.width); - if (xPos < boundaryRect.left) { - xPos = (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints && + final double tooltipRightEnd = xPos! + labelRect.width; + final double tooltipRightTotalWidth = tooltipRightEnd > totalWidth + ? tooltipRightEnd + : tooltipRightEnd + labelRect.left + pointerLength; + if (xPos! < boundaryRect.left) { + xPos = (isGroupMode && chart.trackballBehavior.tooltipAlignment == ChartAlignment.far) ? boundaryRect.left + groupAllPadding - : boundaryRect.left; - } else if (tooltipRightEnd > totalWidth) { - xPos = (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints && + : (x! < boundaryRect.left) + ? boundaryRect.left + : x!; + } else if (tooltipRightTotalWidth > totalWidth) { + xPos = (isGroupMode && chart.trackballBehavior.tooltipAlignment == ChartAlignment.far) - ? xPos - labelRect.width - (2 * _padding) - groupAllPadding - : ((xPos - labelRect.width - (2 * _padding)) - 2 * pointerLength); + ? xPos! - labelRect.width - (2 * _padding) - groupAllPadding + : ((xPos! - labelRect.width - (2 * _padding)) - 2 * pointerLength); isRight = true; } - if (yPos + labelRect.height >= boundaryRect.bottom) { + if (yPos! + labelRect.height >= boundaryRect.bottom) { yPos = boundaryRect.bottom - labelRect.height; } } @@ -670,19 +762,20 @@ class _TrackballPainter extends CustomPainter { /// To draw the line for the trackball void _drawTrackBallLine(Canvas canvas, Paint paint, int index) { final Path dashArrayPath = Path(); - if (chartPointInfo[index].seriesRenderer._seriesType.contains('bar') + if (chartPointInfo[index].seriesRenderer!._seriesType.contains('bar') ? chartState._requireInvertedAxis : chartState._requireInvertedAxis) { - dashArrayPath.moveTo(boundaryRect.left, chartPointInfo[index].yPosition); - dashArrayPath.lineTo(boundaryRect.right, chartPointInfo[index].yPosition); + dashArrayPath.moveTo(boundaryRect.left, chartPointInfo[index].yPosition!); + dashArrayPath.lineTo( + boundaryRect.right, chartPointInfo[index].yPosition!); } else { - dashArrayPath.moveTo(chartPointInfo[index].xPosition, boundaryRect.top); + dashArrayPath.moveTo(chartPointInfo[index].xPosition!, boundaryRect.top); dashArrayPath.lineTo( - chartPointInfo[index].xPosition, boundaryRect.bottom); + chartPointInfo[index].xPosition!, boundaryRect.bottom); } chart.trackballBehavior.lineDashArray != null - ? _drawDashedLine( - canvas, chart.trackballBehavior.lineDashArray, paint, dashArrayPath) + ? _drawDashedLine(canvas, chart.trackballBehavior.lineDashArray!, paint, + dashArrayPath) : canvas.drawPath(dashArrayPath, paint); } @@ -698,8 +791,8 @@ class _TrackballPainter extends CustomPainter { bool isLeft, bool isRight, int index, - num xPosition, - num yPosition) { + double? xPosition, + double? yPosition) { final double startArrow = pointerLength; final double endArrow = pointerLength; if (isTop) { @@ -743,8 +836,8 @@ class _TrackballPainter extends CustomPainter { void _drawTooltip( Canvas canvas, Rect rectF, - double xPos, - double yPos, + double? xPos, + double? yPos, double startX, double startY, double endX, @@ -754,14 +847,13 @@ class _TrackballPainter extends CustomPainter { bool isLeft, bool isRight, int index, - num xPosition, - num yPosition) { + double? xPosition, + double? yPosition) { backgroundPath.reset(); if (!canResetPath && chart.trackballBehavior.tooltipDisplayMode != TrackballDisplayMode.none) { - if (chart.trackballBehavior.tooltipDisplayMode != - TrackballDisplayMode.groupAllPoints) { + if (!isGroupMode && !(xPosition == null || yPosition == null)) { if (chartState._requireInvertedAxis) { if (isLeft) { startX = rectF.left + borderRadius; @@ -817,15 +909,13 @@ class _TrackballPainter extends CustomPainter { final Paint fillPaint = Paint() ..color = chart.trackballBehavior.tooltipSettings.color ?? - (chartPointInfo[index].seriesRenderer._series.color ?? - chartState._chartTheme.crosshairBackgroundColor) + chartState._chartTheme.crosshairBackgroundColor ..isAntiAlias = false ..style = PaintingStyle.fill; final Paint stokePaint = Paint() ..color = chart.trackballBehavior.tooltipSettings.borderColor ?? - (chartPointInfo[index].seriesRenderer._series.color ?? - chartState._chartTheme.crosshairBackgroundColor) + chartState._chartTheme.crosshairBackgroundColor ..strokeWidth = chart.trackballBehavior.tooltipSettings.borderWidth ..strokeCap = StrokeCap.butt ..isAntiAlias = false @@ -837,10 +927,8 @@ class _TrackballPainter extends CustomPainter { dividerPaint.color = chartState._chartTheme.tooltipSeparatorColor; dividerPaint.strokeWidth = 1; dividerPaint.style = PaintingStyle.stroke; - if (chart.trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints && - divider) { - final Size headerResult = _measureText(stringValue[0], labelStyle); + if (isGroupMode && divider) { + final Size headerResult = measureText(stringValue[0].label, labelStyle); canvas.drawLine( Offset(tooltipRect.left + padding, tooltipRect.top + headerResult.height + padding), @@ -853,7 +941,7 @@ class _TrackballPainter extends CustomPainter { double totalHeight = 0; for (int i = 0; i < stringValue.length; i++) { - labelSize = _measureText(stringValue[i], labelStyle); + labelSize = measureText(stringValue[i].label, labelStyle); totalHeight += labelSize.height; } @@ -861,6 +949,16 @@ class _TrackballPainter extends CustomPainter { (tooltipRect.top + tooltipRect.height / 2) - totalHeight / 2; for (int i = 0; i < stringValue.length; i++) { + markerPadding = 0; + if (chart.trackballBehavior.tooltipSettings.canShowMarker) { + if (isGroupMode && i == 0) { + markerPadding = 0; + } else { + markerPadding = 10 - markerSize + 5; + } + } + + final double animationFactor = 1; labelStyle = TextStyle( fontWeight: FontWeight.normal, color: labelStyle.color, @@ -884,11 +982,11 @@ class _TrackballPainter extends CustomPainter { decorationThickness: labelStyle.decorationThickness, debugLabel: labelStyle.debugLabel, fontFamilyFallback: labelStyle.fontFamilyFallback); - labelSize = _measureText(stringValue[i], labelStyle); + labelSize = measureText(stringValue[i].label, labelStyle); eachTextHeight += labelSize.height; - if (!stringValue[i].contains(':') && - !stringValue[i].contains('') && - !stringValue[i].contains('')) { + if (!stringValue[i].label.contains(':') && + !stringValue[i].label.contains('') && + !stringValue[i].label.contains('')) { labelStyle = TextStyle( fontWeight: FontWeight.bold, color: labelStyle.color, @@ -912,18 +1010,32 @@ class _TrackballPainter extends CustomPainter { decorationThickness: labelStyle.decorationThickness, debugLabel: labelStyle.debugLabel, fontFamilyFallback: labelStyle.fontFamilyFallback); + + _drawTooltipMarker( + stringValue[i].label, + canvas, + tooltipRect, + animationFactor, + labelSize, + chartPointInfo[index].seriesRenderer!, + i, + null, + null, + eachTextHeight, + index); _drawText( canvas, - stringValue[i], + stringValue[i].label, Offset( - (tooltipRect.left + tooltipRect.width / 2) - + markerPadding + + (tooltipRect.left + tooltipRect.width / 2) - labelSize.width / 2, eachTextHeight - labelSize.height), labelStyle, 0); } else { - if (stringValue[i] != null) { - final List str = stringValue[i].split('\n'); + if (stringValue[i].label != null) { + final List str = stringValue[i].label.split('\n'); double padding = 0; if (str.length > 1) { for (int j = 0; j < str.length; j++) { @@ -931,7 +1043,7 @@ class _TrackballPainter extends CustomPainter { if (str1.length > 1) { for (int k = 0; k < str1.length; k++) { final double width = - k > 0 ? _measureText(str1[k - 1], labelStyle).width : 0; + k > 0 ? measureText(str1[k - 1], labelStyle).width : 0; str1[k] = k == 1 ? ':' + str1[k] : str1[k]; labelStyle = TextStyle( fontWeight: k > 0 ? FontWeight.bold : FontWeight.normal, @@ -956,16 +1068,39 @@ class _TrackballPainter extends CustomPainter { decorationThickness: labelStyle.decorationThickness, debugLabel: labelStyle.debugLabel, fontFamilyFallback: labelStyle.fontFamilyFallback); + if (k == 0) { + _drawTooltipMarker( + str1[k], + canvas, + tooltipRect, + animationFactor, + labelSize, + chartPointInfo[index].seriesRenderer!, + i, + null, + width, + eachTextHeight, + index); + } _drawText( canvas, str1[k], - Offset((tooltipRect.left + 4) + width, + Offset( + ((((!isGroupMode && + chart.trackballBehavior + .tooltipSettings.canShowMarker) + ? (tooltipRect.left + + tooltipRect.width / 2 - + labelSize.width / 2) + : (tooltipRect.left + 4)) + + markerPadding) + + width), (eachTextHeight - labelSize.height) + padding), labelStyle, 0); padding = k > 0 ? padding + - (labelStyle.fontSize + (labelStyle.fontSize * 0.15)) + (labelStyle.fontSize! + (labelStyle.fontSize! * 0.15)) : padding; } } else { @@ -992,15 +1127,37 @@ class _TrackballPainter extends CustomPainter { decorationThickness: labelStyle.decorationThickness, debugLabel: labelStyle.debugLabel, fontFamilyFallback: labelStyle.fontFamilyFallback); + _drawTooltipMarker( + str1[str1.length - 1], + canvas, + tooltipRect, + animationFactor, + labelSize, + chartPointInfo[index].seriesRenderer!, + i, + null, + null, + eachTextHeight, + index, + measureText(str1[str1.length - 1], labelStyle)); + markerPadding = + chart.trackballBehavior.tooltipSettings.canShowMarker + ? markerPadding + + (j == 0 && !isGroupMode + ? 13 + : j == 0 && isGroupMode + ? 7 + : 0) + : 0; _drawText( canvas, str1[str1.length - 1], - Offset(tooltipRect.left + 4, + Offset(markerPadding + tooltipRect.left + 4, eachTextHeight - labelSize.height + padding), labelStyle, 0); padding = padding + - (labelStyle.fontSize + (labelStyle.fontSize * 0.15)); + (labelStyle.fontSize! + (labelStyle.fontSize! * 0.15)); } } } else { @@ -1033,7 +1190,7 @@ class _TrackballPainter extends CustomPainter { } } final double width = - j > 0 ? _measureText(str1[j - 1], labelStyle).width : 0; + j > 0 ? measureText(str1[j - 1], labelStyle).width : 0; previousWidth += width; final String colon = boldString.isNotEmpty ? '' @@ -1070,11 +1227,36 @@ class _TrackballPainter extends CustomPainter { decorationThickness: labelStyle.decorationThickness, debugLabel: labelStyle.debugLabel, fontFamilyFallback: labelStyle.fontFamilyFallback); + if (j == 0) { + _drawTooltipMarker( + str1[j], + canvas, + tooltipRect, + animationFactor, + labelSize, + chartPointInfo[index].seriesRenderer!, + i, + previousWidth, + width, + eachTextHeight, + index); + } + + markerPadding = + chart.trackballBehavior.tooltipSettings.canShowMarker + ? markerPadding + + (j == 0 && !isGroupMode + ? 13 + : j == 0 && isGroupMode + ? 7 + : 0) + : 0; _drawText( canvas, colon + str1[j], Offset( - (tooltipRect.left + 4) + + markerPadding + + (tooltipRect.left + 4) + (previousWidth > width ? previousWidth : width), eachTextHeight - labelSize.height), labelStyle, @@ -1087,6 +1269,170 @@ class _TrackballPainter extends CustomPainter { } } + /// draw marker inside the trackball tooltip + void _drawTooltipMarker( + String labelValue, + Canvas canvas, + RRect tooltipRect, + double animationFactor, + Size tooltipMarkerResult, + CartesianSeriesRenderer? seriesRenderer, + int i, + double? previousWidth, + double? width, + double eachTextHeight, + int index, + [Size? headerSize]) { + final Size tooltipStringResult = tooltipMarkerResult; + markerSize = 5; + Offset markerPoint; + if (chart.trackballBehavior.tooltipSettings.canShowMarker) { + if (!isGroupMode) { + if (seriesRenderer!._seriesType.contains('hilo') || + seriesRenderer._seriesType.contains('candle') || + seriesRenderer._seriesType.contains('boxandwhisker')) { + markerPoint = Offset( + ((tooltipRect.left + + tooltipRect.width / 2 - + tooltipStringResult.width / 2) - + markerSize), + (eachTextHeight - tooltipStringResult.height / 2) + 0.0); + _renderMarker( + markerPoint, seriesRenderer, animationFactor, canvas, index); + } else { + markerPoint = Offset( + (tooltipRect.left + + tooltipRect.width / 2 - + tooltipStringResult.width / 2) - + markerSize, + ((tooltipRect.top + tooltipRect.height) - + tooltipStringResult.height / 2) - + markerSize); + } + _renderMarker( + markerPoint, seriesRenderer, animationFactor, canvas, index); + } else { + if (i > 0 && labelValue != '') { + seriesRenderer = stringValue[i].seriesRenderer!; + if ((seriesRenderer != null && + seriesRenderer._series.name != null && + seriesRenderer._chart.trackballBehavior.tooltipSettings.format == + null)) { + if (previousWidth != null && width != null) { + markerPoint = Offset( + (tooltipRect.left + 10) + + (previousWidth > width ? previousWidth : width), + eachTextHeight - tooltipMarkerResult.height / 2); + _renderMarker( + markerPoint, seriesRenderer, animationFactor, canvas, index); + } else if (stringValue[i].needRender) { + markerPoint = Offset( + (tooltipRect.left + 10), + (headerSize!.height * 2 + + tooltipRect.top + + markerSize + + headerSize.height / 2) + + (i == 1 + ? 0 + : lastMarkerResultHeight - headerSize.height)); + lastMarkerResultHeight = tooltipMarkerResult.height; + stringValue[i].needRender = false; + _renderMarker( + markerPoint, seriesRenderer, animationFactor, canvas, index); + } + } else { + markerPoint = Offset( + ((tooltipRect.left + tooltipRect.width / 2) - + tooltipMarkerResult.width / 2) - + markerSize, + eachTextHeight - tooltipMarkerResult.height / 2); + _renderMarker( + markerPoint, seriesRenderer, animationFactor, canvas, index); + } + } + } + } + } + + // To render marker for the chart tooltip + void _renderMarker( + Offset markerPoint, + CartesianSeriesRenderer _seriesRenderer, + double animationFactor, + Canvas canvas, + int index) { + _seriesRenderer._isMarkerRenderEvent = true; + final MarkerSettings markerSettings = + chart.trackballBehavior.markerSettings == null + ? _seriesRenderer._series.markerSettings + : chart.trackballBehavior.markerSettings!; + final Path markerPath = _getMarkerShapesPath( + markerSettings.shape, + markerPoint, + Size((2 * markerSize) * animationFactor, + (2 * markerSize) * animationFactor), + _seriesRenderer); + + Color? _seriesColor; + if (_seriesRenderer._seriesType.contains('candle')) { + final CandleSeriesRenderer seriesRenderer = + _seriesRenderer as CandleSeriesRenderer; + _seriesColor = seriesRenderer._candleSegment._isBull + ? seriesRenderer._candleSeries.bullColor + : seriesRenderer._candleSeries.bearColor; + } else if (_seriesRenderer._seriesType.contains('hiloopenclose')) { + final HiloOpenCloseSeriesRenderer seriesRenderer = + _seriesRenderer as HiloOpenCloseSeriesRenderer; + _seriesColor = seriesRenderer._segment._isBull + ? seriesRenderer._hiloOpenCloseSeries.bullColor + : seriesRenderer._hiloOpenCloseSeries.bearColor; + } else { + _seriesColor = (chartPointInfo[index].dataPointIndex! < + _seriesRenderer._dataPoints.length + ? _seriesRenderer + ._dataPoints[chartPointInfo[index].dataPointIndex!] + .pointColorMapper + : null) ?? + _seriesRenderer._seriesColor; + } + + Paint markerPaint = Paint(); + markerPaint.color = markerSettings.color ?? _seriesColor ?? Colors.white; + if (_seriesRenderer._series.gradient != null) { + markerPaint = _getLinearGradientPaint( + _seriesRenderer._series.gradient!, + _getMarkerShapesPath( + markerSettings.shape, + Offset(markerPoint.dx, markerPoint.dy), + Size((2 * markerSize) * animationFactor, + (2 * markerSize) * animationFactor), + _seriesRenderer) + .getBounds(), + _seriesRenderer._chartState!._requireInvertedAxis); + } + canvas.drawPath(markerPath, markerPaint); + Paint markerBorderPaint = Paint(); + markerBorderPaint.color = markerSettings.borderColor ?? + _seriesColor ?? + _seriesRenderer._chartState!._chartTheme.tooltipLabelColor; + markerBorderPaint.strokeWidth = 1; + markerBorderPaint.style = PaintingStyle.stroke; + + if (_seriesRenderer._series.gradient != null) { + markerBorderPaint = _getLinearGradientPaint( + _seriesRenderer._series.gradient!, + _getMarkerShapesPath( + markerSettings.shape, + Offset(markerPoint.dx, markerPoint.dy), + Size((2 * markerSize) * animationFactor, + (2 * markerSize) * animationFactor), + _seriesRenderer) + .getBounds(), + _seriesRenderer._chartState!._requireInvertedAxis); + } + canvas.drawPath(markerPath, markerBorderPaint); + } + @override bool shouldRepaint(CustomPainter oldDelegate) => true; @@ -1094,10 +1440,11 @@ class _TrackballPainter extends CustomPainter { String getFormattedValue(num value) => value.toString(); } -/// Class to store the about the details of the losest points +/// Class to store the about the details of the closest points class _ClosestPoints { /// Creates the parameterized constructor for class _ClosestPoints - const _ClosestPoints({this.closestPointX, this.closestPointY}); + const _ClosestPoints( + {required this.closestPointX, required this.closestPointY}); final double closestPointX; @@ -1113,3 +1460,15 @@ class _TooltipPositions { final List tooltipBottom; } + +/// Class to store the string values with their corresponding series renderer +class _TrackballElement { + /// Creates the parameterized constructor for the class _TrackballElement + _TrackballElement(this.label, this.seriesRenderer); + + final String label; + + final CartesianSeriesRenderer? seriesRenderer; + + bool needRender = true; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_template.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_template.dart index 4b25021a7..80e2a6e06 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_template.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_template.dart @@ -2,7 +2,10 @@ part of charts; // Widget class which is used to display the trackball template class _TrackballTemplate extends StatefulWidget { - const _TrackballTemplate({Key key, this.chartState, this.trackballBehavior}) + const _TrackballTemplate( + {required Key key, + required this.chartState, + required this.trackballBehavior}) : super(key: key); final SfCartesianChartState chartState; @@ -18,17 +21,17 @@ class _TrackballTemplateState extends State<_TrackballTemplate> { bool _isRender = false; //ignore: unused_field - _TrackballTemplateState _state; - List<_ChartPointInfo> _chartPointInfo; - List _markerShapes; - TrackballGroupingModeInfo groupingModeInfo; - Widget _template; + late _TrackballTemplateState _state; + List<_ChartPointInfo>? _chartPointInfo; + List? _markerShapes; + late TrackballGroupingModeInfo groupingModeInfo; + late Widget _template; //ignore: unused_field - double _duration; + late double _duration; //ignore: unused_field - bool _alwaysShow; + late bool _alwaysShow; //ignore: unused_field - Timer _trackballTimer; + late Timer _trackballTimer; bool _isRangeSeries = false, _isBoxSeries = false; @override @@ -48,27 +51,30 @@ class _TrackballTemplateState extends State<_TrackballTemplate> { final List trackballWidgets = []; _state = this; String seriesType; - if (_isRender && _chartPointInfo != null && _chartPointInfo.isNotEmpty) { - for (int index = 0; index < _chartPointInfo.length; index++) { - seriesType = _chartPointInfo[index].seriesRenderer._seriesType; + if (_isRender && + widget.chartState._animationCompleted && + _chartPointInfo != null && + _chartPointInfo!.isNotEmpty) { + for (int index = 0; index < _chartPointInfo!.length; index++) { + seriesType = _chartPointInfo![index].seriesRenderer!._seriesType; _isRangeSeries = seriesType.contains('range') || seriesType.contains('hilo') || seriesType == 'candle'; _isBoxSeries = seriesType == 'boxandwhisker'; if (widget.trackballBehavior.tooltipDisplayMode == TrackballDisplayMode.groupAllPoints) { - _template = widget.trackballBehavior.builder(context, + _template = widget.trackballBehavior.builder!(context, TrackballDetails(null, null, null, null, groupingModeInfo)); trackballWidget = _TrackballRenderObject( child: _template, template: _template, chartState: widget.chartState, - xPos: _chartPointInfo[index].xPosition, + xPos: _chartPointInfo![index].xPosition!, yPos: _isRangeSeries - ? _chartPointInfo[index].highYPosition + ? _chartPointInfo![index].highYPosition! : _isBoxSeries - ? _chartPointInfo[index].maxYPosition - : _chartPointInfo[index].yPosition, + ? _chartPointInfo![index].maxYPosition! + : _chartPointInfo![index].yPosition!, trackballBehavior: widget.trackballBehavior); trackballWidgets.add(trackballWidget); @@ -76,29 +82,29 @@ class _TrackballTemplateState extends State<_TrackballTemplate> { break; } else if (widget.trackballBehavior.tooltipDisplayMode != TrackballDisplayMode.none) { - _template = widget.trackballBehavior.builder( + _template = widget.trackballBehavior.builder!( context, TrackballDetails( - _chartPointInfo[index] - .seriesRenderer - ._dataPoints[_chartPointInfo[index].dataPointIndex], - _chartPointInfo[index].seriesRenderer._series, - _chartPointInfo[index].dataPointIndex, - _chartPointInfo[index].seriesIndex, + _chartPointInfo![index] + .seriesRenderer! + ._dataPoints[_chartPointInfo![index].dataPointIndex!], + _chartPointInfo![index].seriesRenderer!._series, + _chartPointInfo![index].dataPointIndex, + _chartPointInfo![index].seriesIndex, null)); trackballWidget = _TrackballRenderObject( child: _template, template: _template, chartState: widget.chartState, - xPos: _chartPointInfo[index].xPosition, + xPos: _chartPointInfo![index].xPosition!, yPos: _isRangeSeries - ? _chartPointInfo[index].highYPosition + ? _chartPointInfo![index].highYPosition! : _isBoxSeries - ? _chartPointInfo[index].maxYPosition - : _chartPointInfo[index].yPosition, + ? _chartPointInfo![index].maxYPosition! + : _chartPointInfo![index].yPosition!, trackballBehavior: widget.trackballBehavior, - chartPointInfo: _chartPointInfo, + chartPointInfo: _chartPointInfo!, index: index); trackballWidgets.add(trackballWidget); @@ -139,21 +145,21 @@ class _TrackballTemplateState extends State<_TrackballTemplate> { class _TrackballRenderObject extends SingleChildRenderObjectWidget { _TrackballRenderObject( - {Key key, - Widget child, - this.template, - this.chartState, - this.xPos, - this.yPos, - this.trackballBehavior, + {Key? key, + required Widget child, + required this.template, + required this.chartState, + required this.xPos, + required this.yPos, + required this.trackballBehavior, this.chartPointInfo, this.index}) : super(key: key, child: child); final Widget template; - final int index; + final int? index; final SfCartesianChartState chartState; - final List<_ChartPointInfo> chartPointInfo; + final List<_ChartPointInfo>? chartPointInfo; final double xPos; final double yPos; final TrackballBehavior trackballBehavior; @@ -179,26 +185,26 @@ class _TrackballRenderObject extends SingleChildRenderObjectWidget { class _TrackballTemplateRenderBox extends RenderShiftedBox { _TrackballTemplateRenderBox( this._template, this._chartState, this.xPos, this.yPos, - [this.chartPointInfo, this.index, RenderBox child]) + [this.chartPointInfo, this.index, RenderBox? child]) : super(child); Widget _template; final SfCartesianChartState _chartState; double xPos, yPos; - List<_ChartPointInfo> chartPointInfo; - int index; - double pointerLength, pointerWidth; - Rect trackballTemplateRect; - Rect boundaryRect; + List<_ChartPointInfo>? chartPointInfo; + int? index; + late double pointerLength, pointerWidth; + Rect? trackballTemplateRect; + late Rect boundaryRect; num padding = 10; - TrackballBehavior trackballBehavior; - bool isGroupAllPoints, isNearestPoint; + late TrackballBehavior trackballBehavior; + bool isGroupAllPoints = false, isNearestPoint = false; Widget get template => _template; bool isRight = false, isBottom = false; bool isTemplateInBounds = true; - Offset arrowOffset; - _TooltipPositions tooltipPosition; - BoxParentData childParentData; + // Offset arrowOffset; + _TooltipPositions? tooltipPosition; + late BoxParentData childParentData; set template(Widget value) { if (_template != value) { _template = value; @@ -215,7 +221,7 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { TrackballDisplayMode.nearestPoint; final TrackballBehaviorRenderer trackballBehaviorRenderer = _chartState._trackballBehaviorRenderer; - final List tooltipTop = trackballBehaviorRenderer._tooltipTop; + final List? tooltipTop = trackballBehaviorRenderer._tooltipTop; final List tooltipBottom = trackballBehaviorRenderer._tooltipBottom; final List<_ClosestPoints> visiblePoints = trackballBehaviorRenderer._visiblePoints; @@ -231,12 +237,12 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { pointerLength = trackballBehavior.tooltipSettings.arrowLength; pointerWidth = trackballBehavior.tooltipSettings.arrowWidth; double left, top; - Offset offset; + Offset? offset; if (child != null) { - child.layout(constraints, parentUsesSize: true); - size = constraints.constrain(Size(child.size.width, child.size.height)); - if (child.parentData is BoxParentData) { - childParentData = child.parentData; + child!.layout(constraints, parentUsesSize: true); + size = constraints.constrain(Size(child!.size.width, child!.size.height)); + if (child!.parentData is BoxParentData) { + childParentData = child!.parentData as BoxParentData; if (isGroupAllPoints) { if (trackballBehavior.tooltipAlignment == ChartAlignment.center) { @@ -255,15 +261,17 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { } if (index == 0) { - for (int index = 0; index < chartPointInfo.length; index++) { - tooltipTop.add(_chartState._requireInvertedAxis + for (int index = 0; index < chartPointInfo!.length; index++) { + tooltipTop!.add(_chartState._requireInvertedAxis ? visiblePoints[index].closestPointX - (size.width / 2) : visiblePoints[index].closestPointY - size.height / 2); tooltipBottom.add(_chartState._requireInvertedAxis ? visiblePoints[index].closestPointX + (size.width / 2) : visiblePoints[index].closestPointY + size.height / 2); - xAxesInfo.add(chartPointInfo[index].seriesRenderer._xAxisRenderer); - yAxesInfo.add(chartPointInfo[index].seriesRenderer._yAxisRenderer); + xAxesInfo + .add(chartPointInfo![index].seriesRenderer!._xAxisRenderer!); + yAxesInfo + .add(chartPointInfo![index].seriesRenderer!._yAxisRenderer!); } } @@ -273,25 +281,27 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { tooltipBottom, xAxesInfo, yAxesInfo, - chartPointInfo, + chartPointInfo!, _chartState._requireInvertedAxis); } if (!isGroupAllPoints) { - left = _chartState._requireInvertedAxis - ? tooltipPosition.tooltipTop[index] - : xPos + - padding + - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings.width / 2 - : 0); - top = _chartState._requireInvertedAxis - ? yPos + - pointerLength + - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings.width / 2 - : 0) - : tooltipPosition.tooltipTop[index]; + left = (_chartState._requireInvertedAxis + ? tooltipPosition!.tooltipTop[index!] + : xPos + + padding + + (isTrackballMarkerEnabled + ? trackballBehavior.markerSettings!.width / 2 + : 0)) + .toDouble(); + top = (_chartState._requireInvertedAxis + ? yPos + + pointerLength + + (isTrackballMarkerEnabled + ? trackballBehavior.markerSettings!.width / 2 + : 0) + : tooltipPosition!.tooltipTop[index!]) + .toDouble(); if (isNearestPoint) { left = _chartState._requireInvertedAxis @@ -299,13 +309,13 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { : xPos + padding + (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings.width / 2 + ? trackballBehavior.markerSettings!.width / 2 : 0); top = _chartState._requireInvertedAxis ? yPos + padding + (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings.width / 2 + ? trackballBehavior.markerSettings!.width / 2 : 0) : yPos - size.height / 2; } @@ -317,7 +327,7 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { size.width - pointerLength - (isTrackballMarkerEnabled - ? (trackballBehavior.markerSettings.width / 2) + ? (trackballBehavior.markerSettings!.width / 2) : 0); } else { isRight = false; @@ -328,7 +338,7 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { top = yPos - size.height - (isTrackballMarkerEnabled - ? (trackballBehavior.markerSettings.height) + ? (trackballBehavior.markerSettings!.height) : 0); } else { isBottom = false; @@ -338,11 +348,11 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { Rect.fromLTWH(left, top, size.width, size.height); if (_isTemplateWithinBounds( - boundaryRect, trackballTemplateRect, offset)) { + boundaryRect, trackballTemplateRect!, offset)) { isTemplateInBounds = true; childParentData.offset = Offset(left, top); } else { - child.layout(constraints.copyWith(maxWidth: 0), + child!.layout(constraints.copyWith(maxWidth: 0), parentUsesSize: true); isTemplateInBounds = false; } @@ -352,7 +362,7 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { size.width - 2 * padding - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings.width / 2 + ? trackballBehavior.markerSettings!.width / 2 : 0); } @@ -360,17 +370,17 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { Rect.fromLTWH(xPos, yPos, size.width, size.height); if (_isTemplateWithinBounds( - boundaryRect, trackballTemplateRect, offset)) { + boundaryRect, trackballTemplateRect!, offset)) { isTemplateInBounds = true; childParentData.offset = Offset( xPos + padding + (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings.width / 2 + ? trackballBehavior.markerSettings!.width / 2 : 0), yPos); } else { - child.layout(constraints.copyWith(maxWidth: 0), + child!.layout(constraints.copyWith(maxWidth: 0), parentUsesSize: true); isTemplateInBounds = false; } @@ -379,8 +389,8 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { } else { size = Size.zero; } - if (!isGroupAllPoints && index == chartPointInfo.length - 1) { - tooltipTop.clear(); + if (!isGroupAllPoints && index == chartPointInfo!.length - 1) { + tooltipTop?.clear(); tooltipBottom.clear(); yAxesInfo.clear(); xAxesInfo.clear(); @@ -388,7 +398,7 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { } /// To check template is within bounds - bool _isTemplateWithinBounds(Rect bounds, Rect templateRect, Offset offset) { + bool _isTemplateWithinBounds(Rect bounds, Rect templateRect, Offset? offset) { final Rect rect = Rect.fromLTWH( padding + templateRect.left, (3 * padding) + templateRect.top, @@ -408,20 +418,20 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { if (!isGroupAllPoints) { final Rect templateRect = Rect.fromLTWH( - offset.dx + trackballTemplateRect.left, - offset.dy + trackballTemplateRect.top, - trackballTemplateRect.width, - trackballTemplateRect.height); + offset.dx + trackballTemplateRect!.left, + offset.dy + trackballTemplateRect!.top, + trackballTemplateRect!.width, + trackballTemplateRect!.height); final Paint fillPaint = Paint() ..color = trackballBehavior.tooltipSettings.color ?? - (chartPointInfo[index].seriesRenderer._series.color ?? + (chartPointInfo![index!].seriesRenderer!._series.color ?? _chartState._chartTheme.crosshairBackgroundColor) ..isAntiAlias = false ..style = PaintingStyle.fill; final Paint strokePaint = Paint() ..color = trackballBehavior.tooltipSettings.borderColor ?? - (chartPointInfo[index].seriesRenderer._series.color ?? + (chartPointInfo![index!].seriesRenderer!._series.color ?? _chartState._chartTheme.crosshairBackgroundColor) ..strokeWidth = trackballBehavior.tooltipSettings.borderWidth ..strokeCap = StrokeCap.butt @@ -477,8 +487,8 @@ class _TracklinePainter extends CustomPainter { TrackballBehavior trackballBehavior; SfCartesianChartState chartState; - List<_ChartPointInfo> chartPointInfo; - List markerShapes; + List<_ChartPointInfo>? chartPointInfo; + List? markerShapes; bool isTrackLineDrawn = false; @override @@ -494,32 +504,32 @@ class _TracklinePainter extends CustomPainter { : trackballLinePaint.color = trackballLinePaint.color; final Rect boundaryRect = chartState._chartAxis._axisClipRect; - if (chartPointInfo != null && chartPointInfo.isNotEmpty) { - for (int index = 0; index < chartPointInfo.length; index++) { + if (chartPointInfo != null && chartPointInfo!.isNotEmpty) { + for (int index = 0; index < chartPointInfo!.length; index++) { if (index == 0) { - if (chartPointInfo[index].seriesRenderer._seriesType.contains('bar') + if (chartPointInfo![index].seriesRenderer!._seriesType.contains('bar') ? chartState._requireInvertedAxis : chartState._requireInvertedAxis) { dashArrayPath.moveTo( - boundaryRect.left, chartPointInfo[index].yPosition); + boundaryRect.left, chartPointInfo![index].yPosition!); dashArrayPath.lineTo( - boundaryRect.right, chartPointInfo[index].yPosition); + boundaryRect.right, chartPointInfo![index].yPosition!); } else { dashArrayPath.moveTo( - chartPointInfo[index].xPosition, boundaryRect.top); + chartPointInfo![index].xPosition!, boundaryRect.top); dashArrayPath.lineTo( - chartPointInfo[index].xPosition, boundaryRect.bottom); + chartPointInfo![index].xPosition!, boundaryRect.bottom); } trackballBehavior.lineDashArray != null - ? _drawDashedLine(canvas, trackballBehavior.lineDashArray, + ? _drawDashedLine(canvas, trackballBehavior.lineDashArray!, trackballLinePaint, dashArrayPath) : canvas.drawPath(dashArrayPath, trackballLinePaint); } if (markerShapes != null && - markerShapes.isNotEmpty && - markerShapes.length > index) { + markerShapes!.isNotEmpty && + markerShapes!.length > index) { chartState._trackballBehaviorRenderer._renderTrackballMarker( - chartPointInfo[index].seriesRenderer, + chartPointInfo![index].seriesRenderer!, canvas, trackballBehavior, index); diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_painter.dart index dadf96f10..f6eff8328 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_painter.dart @@ -3,13 +3,15 @@ part of charts; ///Below class is for drawing zoomRct class _ZoomRectPainter extends CustomPainter { _ZoomRectPainter( - {this.isRepaint, this.chartState, ValueNotifier notifier}) + {this.isRepaint = true, + required this.chartState, + ValueNotifier? notifier}) : chart = chartState._chart, super(repaint: notifier); final bool isRepaint; final SfCartesianChart chart; SfCartesianChartState chartState; - Paint strokePaint, fillPaint; + late Paint strokePaint, fillPaint; @override void paint(Canvas canvas, Size size) => @@ -17,7 +19,7 @@ class _ZoomRectPainter extends CustomPainter { /// To draw Rect void drawRect(Canvas canvas) { - final Color fillColor = chart.zoomPanBehavior.selectionRectColor; + final Color? fillColor = chart.zoomPanBehavior.selectionRectColor; strokePaint = Paint() ..color = chart.zoomPanBehavior.selectionRectBorderColor ?? chartState._chartTheme.selectionRectBorderColor @@ -36,10 +38,10 @@ class _ZoomRectPainter extends CustomPainter { canvas.drawPath( !kIsWeb ? _dashPath( - chartState._zoomPanBehaviorRenderer._rectPath, + chartState._zoomPanBehaviorRenderer._rectPath!, dashArray: _CircularIntervalList([5, 5]), - ) - : chartState._zoomPanBehaviorRenderer._rectPath, + )! + : chartState._zoomPanBehaviorRenderer._rectPath!, strokePaint); canvas.drawRect( chartState._zoomPanBehaviorRenderer._zoomingRect, fillPaint); @@ -108,8 +110,8 @@ class _ZoomRectPainter extends CustomPainter { dynamic resultantString = _getInteractiveTooltipLabel(value, axisRenderer); if (axis.interactiveTooltip.format != null) { - final String stringValue = - axis.interactiveTooltip.format.replaceAll('{value}', resultantString); + final String stringValue = axis.interactiveTooltip.format! + .replaceAll('{value}', resultantString); resultantString = stringValue; } return resultantString; @@ -172,8 +174,8 @@ class _ZoomRectPainter extends CustomPainter { Offset endPosition, Canvas canvas, String axisPosition) { - RRect startTooltipRect; - RRect endTooltipRect; + RRect? startTooltipRect; + RRect? endTooltipRect; String startValue; String endValue; Size startLabelSize; @@ -209,9 +211,9 @@ class _ZoomRectPainter extends CustomPainter { startValue = _getValue(startPosition, axisRenderer, axisPosition); endValue = _getValue(endPosition, axisRenderer, axisPosition); startLabelSize = - _measureText(startValue.toString(), axis.interactiveTooltip.textStyle); + measureText(startValue.toString(), axis.interactiveTooltip.textStyle); endLabelSize = - _measureText(endValue.toString(), axis.interactiveTooltip.textStyle); + measureText(endValue.toString(), axis.interactiveTooltip.textStyle); startLabelRect = _calculateRect( axisRenderer, startPosition, startLabelSize, axisPosition); endLabelRect = @@ -288,7 +290,7 @@ class _ZoomRectPainter extends CustomPainter { !kIsWeb ? _dashPath(connectorPath, dashArray: _CircularIntervalList( - tooltip.connectorLineDashArray)) + tooltip.connectorLineDashArray!))! : connectorPath, connectorLinePaint) : canvas.drawPath(connectorPath, connectorLinePaint); @@ -302,17 +304,15 @@ class _ZoomRectPainter extends CustomPainter { Path path, Offset position, Rect labelRect, - RRect rect, + RRect? rect, dynamic value, Size labelSize, InteractiveTooltip tooltip, String axisPosition) { - fillPaint.color = tooltip.color != null - ? tooltip?.color - : chartState._chartTheme.crosshairBackgroundColor; - strokePaint.color = tooltip.borderColor != null - ? tooltip?.borderColor - : chartState._chartTheme.crosshairBackgroundColor; + fillPaint.color = + tooltip.color ?? chartState._chartTheme.crosshairBackgroundColor; + strokePaint.color = + tooltip.borderColor ?? chartState._chartTheme.crosshairBackgroundColor; strokePaint.strokeWidth = tooltip.borderWidth; final bool isHorizontal = axisPosition == 'bottom' || axisPosition == 'top'; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart index 896d52928..38993d76b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart @@ -2,7 +2,7 @@ part of charts; /// Customizes the zooming options. /// -/// Customize the various zooming actions such as doubletapZooming, selectionZooming, zoomPinch. +/// Customize the various zooming actions such.toDouble()tapZooming, selectionZooming, zoomPinch. /// In selection you can long press and drag to select a range on the chart to be zoomed in and also /// zooming you can customize the selection rectangle using Borderwidth,color and RectColor. /// @@ -141,7 +141,7 @@ class ZoomPanBehavior { /// )); ///} ///``` - final double maximumZoomLevel; + final double? maximumZoomLevel; ///Border width of the selection zooming rectangle. /// @@ -175,7 +175,7 @@ class ZoomPanBehavior { /// )); ///} ///``` - final Color selectionRectBorderColor; + final Color? selectionRectBorderColor; ///Color of the selection zooming rectangle. /// @@ -192,36 +192,39 @@ class ZoomPanBehavior { /// )); ///} ///``` - final Color selectionRectColor; + final Color? selectionRectColor; - SfCartesianChartState _chartState; + SfCartesianChartState? _chartState; /// Increases the magnification of the plot area. void zoomIn() { - final SfCartesianChartState chartState = _chartState; + final SfCartesianChartState chartState = _chartState!; final SfCartesianChart chart = chartState._chart; final ZoomPanBehaviorRenderer zoomPanBehaviorRenderer = chartState._zoomPanBehaviorRenderer; + zoomPanBehaviorRenderer._isZoomIn = true; + zoomPanBehaviorRenderer._isZoomOut = false; + final double? zoomFactor = zoomPanBehaviorRenderer._zoomFactor; chartState._zoomProgress = true; - bool needZoom; + ChartAxisRenderer axisRenderer; + bool? needZoom; for (int index = 0; index < chartState._chartAxis._axisRenderersCollection.length; index++) { - final ChartAxisRenderer axisRenderer = - chartState._chartAxis._axisRenderersCollection[index]; + axisRenderer = chartState._chartAxis._axisRenderersCollection[index]; if (axisRenderer._zoomFactor <= 1.0 && axisRenderer._zoomFactor > 0.0) { if (axisRenderer._zoomFactor - 0.1 < 0) { needZoom = false; break; } else { - axisRenderer._zoomFactor -= 0.1; - axisRenderer._zoomPosition = 0.2; + zoomPanBehaviorRenderer._setZoomFactorAndZoomPosition( + chartState, axisRenderer, zoomFactor); needZoom = true; } } if (chart.onZooming != null) { - ZoomPanArgs zoomingArgs; - _bindZoomEvent(chart, axisRenderer, zoomingArgs, chart.onZooming); + ZoomPanArgs? zoomingArgs; + _bindZoomEvent(chart, axisRenderer, zoomingArgs, chart.onZooming!); } } if (needZoom == true) { @@ -231,25 +234,28 @@ class ZoomPanBehavior { /// Decreases the magnification of the plot area. void zoomOut() { - final SfCartesianChartState chartState = _chartState; + final SfCartesianChartState chartState = _chartState!; final SfCartesianChart chart = chartState._chart; final ZoomPanBehaviorRenderer zoomPanBehaviorRenderer = chartState._zoomPanBehaviorRenderer; + zoomPanBehaviorRenderer._isZoomOut = true; + zoomPanBehaviorRenderer._isZoomIn = false; + final double? zoomFactor = zoomPanBehaviorRenderer._zoomFactor; + ChartAxisRenderer axisRenderer; for (int index = 0; index < chartState._chartAxis._axisRenderersCollection.length; index++) { - final ChartAxisRenderer axisRenderer = - chartState._chartAxis._axisRenderersCollection[index]; + axisRenderer = chartState._chartAxis._axisRenderersCollection[index]; if (axisRenderer._zoomFactor < 1.0 && axisRenderer._zoomFactor > 0.0) { - axisRenderer._zoomFactor += 0.1; - axisRenderer._zoomPosition = 0.2; + zoomPanBehaviorRenderer._setZoomFactorAndZoomPosition( + chartState, axisRenderer, zoomFactor); axisRenderer._zoomFactor = axisRenderer._zoomFactor > 1.0 ? 1.0 : (axisRenderer._zoomFactor < 0.0 ? 0.0 : axisRenderer._zoomFactor); } if (chart.onZooming != null) { - ZoomPanArgs zoomingArgs; - _bindZoomEvent(chart, axisRenderer, zoomingArgs, chart.onZooming); + ZoomPanArgs? zoomingArgs; + _bindZoomEvent(chart, axisRenderer, zoomingArgs, chart.onZooming!); } } zoomPanBehaviorRenderer._createZoomState(); @@ -259,19 +265,19 @@ class ZoomPanBehavior { /// /// Here, you can pass the zoom factor of an axis to magnify the plot area. The value ranges from 0 to 1. void zoomByFactor(double zoomFactor) { - final SfCartesianChartState chartState = _chartState; + final SfCartesianChartState chartState = _chartState!; final SfCartesianChart chart = chartState._chart; final ZoomPanBehaviorRenderer zoomPanBehaviorRenderer = chartState._zoomPanBehaviorRenderer; + ChartAxisRenderer axisRenderer; for (int index = 0; index < chartState._chartAxis._axisRenderersCollection.length; index++) { - final ChartAxisRenderer axisRenderer = - chartState._chartAxis._axisRenderersCollection[index]; + axisRenderer = chartState._chartAxis._axisRenderersCollection[index]; axisRenderer._zoomFactor = zoomFactor; if (chart.onZooming != null) { - ZoomPanArgs zoomingArgs; - _bindZoomEvent(chart, axisRenderer, zoomingArgs, chart.onZooming); + ZoomPanArgs? zoomingArgs; + _bindZoomEvent(chart, axisRenderer, zoomingArgs, chart.onZooming!); } zoomPanBehaviorRenderer._createZoomState(); } @@ -282,7 +288,7 @@ class ZoomPanBehavior { /// Here, you can pass the rectangle with the left, right, top, and bottom values, /// using which the selection zooming will be performed. void zoomByRect(Rect rect) { - final SfCartesianChartState chartState = _chartState; + final SfCartesianChartState chartState = _chartState!; chartState._zoomPanBehaviorRenderer._doSelectionZooming(rect); } @@ -291,10 +297,10 @@ class ZoomPanBehavior { /// Here, you need to pass axis, zoom factor, zoom position of the zoom level that needs to be modified. void zoomToSingleAxis( ChartAxis axis, double zoomPosition, double zoomFactor) { - final SfCartesianChartState chartState = _chartState; + final SfCartesianChartState chartState = _chartState!; final ZoomPanBehaviorRenderer zoomPanBehaviorRenderer = chartState._zoomPanBehaviorRenderer; - final ChartAxisRenderer axisRenderer = _findExistingAxisRenderer( + final ChartAxisRenderer? axisRenderer = _findExistingAxisRenderer( axis, chartState._chartAxis._axisRenderersCollection); if (axisRenderer != null) { axisRenderer._zoomFactor = zoomFactor; @@ -308,16 +314,16 @@ class ZoomPanBehavior { /// To perform /// this action, the plot area needs to be in zoomed state. void panToDirection(String direction) { - final SfCartesianChartState chartState = _chartState; + final SfCartesianChartState chartState = _chartState!; final SfCartesianChart chart = chartState._chart; final ZoomPanBehaviorRenderer zoomPanBehaviorRenderer = chartState._zoomPanBehaviorRenderer; + ChartAxisRenderer axisRenderer; direction = direction.toLowerCase(); for (int axisIndex = 0; axisIndex < chartState._chartAxis._axisRenderersCollection.length; axisIndex++) { - final ChartAxisRenderer axisRenderer = - chartState._chartAxis._axisRenderersCollection[axisIndex]; + axisRenderer = chartState._chartAxis._axisRenderersCollection[axisIndex]; if (axisRenderer._orientation == AxisOrientation.horizontal) { if (direction == 'left') { axisRenderer._zoomPosition = (axisRenderer._zoomPosition > 0 && @@ -358,8 +364,8 @@ class ZoomPanBehavior { } } if (chart.onZooming != null) { - ZoomPanArgs zoomingArgs; - _bindZoomEvent(chart, axisRenderer, zoomingArgs, chart.onZooming); + ZoomPanArgs? zoomingArgs; + _bindZoomEvent(chart, axisRenderer, zoomingArgs, chart.onZooming!); } } zoomPanBehaviorRenderer._createZoomState(); @@ -367,20 +373,20 @@ class ZoomPanBehavior { /// Returns the plot area back to its original position after zooming. void reset() { - final SfCartesianChartState chartState = _chartState; + final SfCartesianChartState chartState = _chartState!; final SfCartesianChart chart = chartState._chart; final ZoomPanBehaviorRenderer zoomPanBehaviorRenderer = chartState._zoomPanBehaviorRenderer; + ChartAxisRenderer axisRenderer; for (int index = 0; index < chartState._chartAxis._axisRenderersCollection.length; index++) { - final ChartAxisRenderer axisRenderer = - chartState._chartAxis._axisRenderersCollection[index]; + axisRenderer = chartState._chartAxis._axisRenderersCollection[index]; axisRenderer._zoomFactor = 1.0; axisRenderer._zoomPosition = 0.0; if (chart.onZoomReset != null) { - ZoomPanArgs zoomResetArgs; - _bindZoomEvent(chart, axisRenderer, zoomResetArgs, chart.onZoomReset); + ZoomPanArgs? zoomResetArgs; + _bindZoomEvent(chart, axisRenderer, zoomResetArgs, chart.onZoomReset!); } } zoomPanBehaviorRenderer._createZoomState(); @@ -395,24 +401,29 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { ZoomPanBehavior get _zoomPanBehavior => _chart.zoomPanBehavior; SfCartesianChart get _chart => _chartState._chart; final SfCartesianChartState _chartState; - _ZoomRectPainter _painter; - Offset _previousMovedPosition; - bool _isPanning, _canPerformSelection, _isPinching; + late _ZoomRectPainter _painter; + Offset? _previousMovedPosition; + bool? _isPanning, _isPinching; + bool _canPerformSelection = false; Rect _zoomingRect = const Rect.fromLTWH(0, 0, 0, 0); bool _delayRedraw = false; - double _zoomFactor, _zoomPosition; + double? _zoomFactor, _zoomPosition; + late bool _isZoomIn, _isZoomOut; + Path? _rectPath; /// Below method for Double tap Zooming - void _doubleTapZooming(double xPos, double yPos, double zoomFactor) { + void _doubleTapZooming(double xPos, double yPos, double? zoomFactor) { _chartState._zoomProgress = true; - ZoomPanArgs zoomStartArgs; + ZoomPanArgs? zoomStartArgs; + ChartAxisRenderer axisRenderer; + double cumulative, origin, maxZoomFactor; for (int axisIndex = 0; axisIndex < _chartState._chartAxis._axisRenderersCollection.length; axisIndex++) { - final ChartAxisRenderer axisRenderer = - _chartState._chartAxis._axisRenderersCollection[axisIndex]; + axisRenderer = _chartState._chartAxis._axisRenderersCollection[axisIndex]; if (_chart.onZoomStart != null) { - _bindZoomEvent(_chart, axisRenderer, zoomStartArgs, _chart.onZoomStart); + _bindZoomEvent( + _chart, axisRenderer, zoomStartArgs, _chart.onZoomStart!); } axisRenderer._previousZoomFactor = axisRenderer._zoomFactor; axisRenderer._previousZoomPosition = axisRenderer._zoomPosition; @@ -420,11 +431,11 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { _zoomPanBehavior.zoomMode != ZoomMode.x) || (axisRenderer._orientation == AxisOrientation.horizontal && _zoomPanBehavior.zoomMode != ZoomMode.y)) { - final num cumulative = math.max( + cumulative = math.max( math.max(1 / _minMax(axisRenderer._zoomFactor, 0, 1), 1) + (0.25), 1); if (cumulative >= 1) { - num origin = axisRenderer._orientation == AxisOrientation.horizontal + origin = axisRenderer._orientation == AxisOrientation.horizontal ? xPos / _chartState._chartAxis._axisClipRect.width : 1 - (yPos / _chartState._chartAxis._axisClipRect.height); origin = origin > 1 @@ -432,25 +443,27 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { : origin < 0 ? 0 : origin; - zoomFactor = (cumulative == 1) ? 1 : _minMax(1 / cumulative, 0, 1); + zoomFactor = ((cumulative == 1) + ? 1 + : _minMax(1 / cumulative, 0, 1).toDouble()); _zoomPosition = (cumulative == 1) ? 0 : axisRenderer._zoomPosition + ((axisRenderer._zoomFactor - zoomFactor) * origin); if (axisRenderer._zoomPosition != _zoomPosition || axisRenderer._zoomFactor != zoomFactor) { - zoomFactor = (_zoomPosition + zoomFactor) > 1 - ? (1 - _zoomPosition) + zoomFactor = (_zoomPosition! + zoomFactor) > 1 + ? (1 - _zoomPosition!) : zoomFactor; } - axisRenderer._zoomPosition = _zoomPosition; + axisRenderer._zoomPosition = _zoomPosition!; axisRenderer._zoomFactor = zoomFactor; axisRenderer._bounds = const Rect.fromLTWH(0, 0, 0, 0); axisRenderer._visibleLabels = []; } - final num maxZoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; - if (zoomFactor < maxZoomFactor) { + maxZoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; + if (zoomFactor! < maxZoomFactor) { axisRenderer._zoomFactor = maxZoomFactor; axisRenderer._zoomPosition = 0.0; zoomFactor = maxZoomFactor; @@ -458,8 +471,8 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { } if (_chart.onZoomEnd != null) { - ZoomPanArgs zoomEndArgs; - _bindZoomEvent(_chart, axisRenderer, zoomEndArgs, _chart.onZoomEnd); + ZoomPanArgs? zoomEndArgs; + _bindZoomEvent(_chart, axisRenderer, zoomEndArgs, _chart.onZoomEnd!); } } _createZoomState(); @@ -467,23 +480,23 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { /// Below method is for panning the zoomed chart void _doPan(double xPos, double yPos) { - num currentScale; - num value; + num currentScale, value; + ChartAxisRenderer axisRenderer; + double currentZoomPosition; for (int axisIndex = 0; axisIndex < _chartState._chartAxis._axisRenderersCollection.length; axisIndex++) { - final ChartAxisRenderer axisRenderer = - _chartState._chartAxis._axisRenderersCollection[axisIndex]; + axisRenderer = _chartState._chartAxis._axisRenderersCollection[axisIndex]; axisRenderer._previousZoomFactor = axisRenderer._zoomFactor; axisRenderer._previousZoomPosition = axisRenderer._zoomPosition; if ((axisRenderer._orientation == AxisOrientation.vertical && _zoomPanBehavior.zoomMode != ZoomMode.x) || (axisRenderer._orientation == AxisOrientation.horizontal && _zoomPanBehavior.zoomMode != ZoomMode.y)) { - double currentZoomPosition = axisRenderer._zoomPosition; + currentZoomPosition = axisRenderer._zoomPosition; currentScale = math.max(1 / _minMax(axisRenderer._zoomFactor, 0, 1), 1); if (axisRenderer._orientation == AxisOrientation.horizontal) { - value = (_previousMovedPosition.dx - xPos) / + value = (_previousMovedPosition!.dx - xPos) / _chartState._chartAxis._axisClipRect.width / currentScale; currentZoomPosition = _minMax( @@ -495,7 +508,7 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { .toDouble(); axisRenderer._zoomPosition = currentZoomPosition; } else { - value = (_previousMovedPosition.dy - yPos) / + value = (_previousMovedPosition!.dy - yPos) / _chartState._chartAxis._axisClipRect.height / currentScale; currentZoomPosition = _minMax( @@ -509,15 +522,13 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { } } if (_chart.onZooming != null) { - ZoomPanArgs zoomingArgs; - _bindZoomEvent(_chart, axisRenderer, zoomingArgs, _chart.onZooming); + ZoomPanArgs? zoomingArgs; + _bindZoomEvent(_chart, axisRenderer, zoomingArgs, _chart.onZooming!); } } _createZoomState(); } - Path _rectPath; - ///Below method for drawing selection rectangle void _drawSelectionZoomRect( double currentX, double currentY, double startX, double startY) { @@ -534,40 +545,41 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { : ((currentY < clipRect.top) ? clipRect.top : currentY)); _rectPath = Path(); if (_zoomPanBehavior.zoomMode == ZoomMode.x) { - _rectPath.moveTo(startPosition.dx, clipRect.top); - _rectPath.lineTo(startPosition.dx, clipRect.bottom); - _rectPath.lineTo(currentMousePosition.dx, clipRect.bottom); - _rectPath.lineTo(currentMousePosition.dx, clipRect.top); - _rectPath.close(); + _rectPath!.moveTo(startPosition.dx, clipRect.top); + _rectPath!.lineTo(startPosition.dx, clipRect.bottom); + _rectPath!.lineTo(currentMousePosition.dx, clipRect.bottom); + _rectPath!.lineTo(currentMousePosition.dx, clipRect.top); + _rectPath!.close(); } else if (_zoomPanBehavior.zoomMode == ZoomMode.y) { - _rectPath.moveTo(clipRect.left, startPosition.dy); - _rectPath.lineTo(clipRect.left, currentMousePosition.dy); - _rectPath.lineTo(clipRect.right, currentMousePosition.dy); - _rectPath.lineTo(clipRect.right, startPosition.dy); - _rectPath.close(); + _rectPath!.moveTo(clipRect.left, startPosition.dy); + _rectPath!.lineTo(clipRect.left, currentMousePosition.dy); + _rectPath!.lineTo(clipRect.right, currentMousePosition.dy); + _rectPath!.lineTo(clipRect.right, startPosition.dy); + _rectPath!.close(); } else { - _rectPath.moveTo(startPosition.dx, startPosition.dy); - _rectPath.lineTo(startPosition.dx, currentMousePosition.dy); - _rectPath.lineTo(currentMousePosition.dx, currentMousePosition.dy); - _rectPath.lineTo(currentMousePosition.dx, startPosition.dy); - _rectPath.close(); + _rectPath!.moveTo(startPosition.dx, startPosition.dy); + _rectPath!.lineTo(startPosition.dx, currentMousePosition.dy); + _rectPath!.lineTo(currentMousePosition.dx, currentMousePosition.dy); + _rectPath!.lineTo(currentMousePosition.dx, startPosition.dy); + _rectPath!.close(); } - _zoomingRect = _rectPath.getBounds(); + _zoomingRect = _rectPath!.getBounds(); _chartState._zoomRepaintNotifier.value++; } /// Below method for zooming selected portion void _doSelectionZooming(Rect zoomRect) { - ZoomPanArgs zoomEndArgs; + ZoomPanArgs? zoomEndArgs; final Rect rect = _chartState._chartAxis._axisClipRect; + ChartAxisRenderer axisRenderer; for (int axisIndex = 0; axisIndex < _chartState._chartAxis._axisRenderersCollection.length; axisIndex++) { - final ChartAxisRenderer axisRenderer = - _chartState._chartAxis._axisRenderersCollection[axisIndex]; - ZoomPanArgs zoomStartArgs; + axisRenderer = _chartState._chartAxis._axisRenderersCollection[axisIndex]; + ZoomPanArgs? zoomStartArgs; if (_chart.onZoomStart != null) { - _bindZoomEvent(_chart, axisRenderer, zoomStartArgs, _chart.onZoomStart); + _bindZoomEvent( + _chart, axisRenderer, zoomStartArgs, _chart.onZoomStart!); } axisRenderer._previousZoomFactor = axisRenderer._zoomFactor; axisRenderer._previousZoomPosition = axisRenderer._zoomPosition; @@ -579,9 +591,9 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { axisRenderer._zoomFactor *= zoomRect.width / rect.width; if (_zoomPanBehavior.maximumZoomLevel != null) { axisRenderer._zoomFactor = - axisRenderer._zoomFactor >= _zoomPanBehavior.maximumZoomLevel + axisRenderer._zoomFactor >= _zoomPanBehavior.maximumZoomLevel! ? axisRenderer._zoomFactor - : _zoomPanBehavior.maximumZoomLevel; + : _zoomPanBehavior.maximumZoomLevel!; } } } else { @@ -594,14 +606,14 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { axisRenderer._zoomFactor *= zoomRect.height / rect.height; if (_zoomPanBehavior.maximumZoomLevel != null) { axisRenderer._zoomFactor = - axisRenderer._zoomFactor >= _zoomPanBehavior.maximumZoomLevel + axisRenderer._zoomFactor >= _zoomPanBehavior.maximumZoomLevel! ? axisRenderer._zoomFactor - : _zoomPanBehavior.maximumZoomLevel; + : _zoomPanBehavior.maximumZoomLevel!; } } } if (_chart.onZoomEnd != null) { - _bindZoomEvent(_chart, axisRenderer, zoomEndArgs, _chart.onZoomEnd); + _bindZoomEvent(_chart, axisRenderer, zoomEndArgs, _chart.onZoomEnd!); } } @@ -634,7 +646,7 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { touch1StartY = touchStartList[1].position.dy - elementOffsetRect.top; touch1EndX = touchMoveList[1].position.dx - elementOffsetRect.left; touch1EndY = touchMoveList[1].position.dy - elementOffsetRect.top; - num scaleX, scaleY, clipX, clipY; + double scaleX, scaleY, clipX, clipY; Rect pinchRect; scaleX = (touch0EndX - touch1EndX).abs() / (touch0StartX - touch1StartX).abs(); @@ -672,15 +684,16 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { num axisTrans; double currentZoomPosition; double currentZoomFactor; - num currentFactor; - num currentPosition; + double currentFactor; + double currentPosition; final Rect offsetRect = _chartState._chartAxis._axisClipRect; final List<_ZoomAxisRange> _zoomAxes = _chartState._zoomAxes; + ChartAxisRenderer axisRenderer; + double maxZoomFactor; for (int axisIndex = 0; axisIndex < _chartState._chartAxis._axisRenderersCollection.length; axisIndex++) { - final ChartAxisRenderer axisRenderer = - _chartState._chartAxis._axisRenderersCollection[axisIndex]; + axisRenderer = _chartState._chartAxis._axisRenderersCollection[axisIndex]; axisRenderer._previousZoomFactor = axisRenderer._zoomFactor; axisRenderer._previousZoomPosition = axisRenderer._zoomPosition; if ((axisRenderer._orientation == AxisOrientation.horizontal && @@ -689,28 +702,28 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { mode != ZoomMode.x)) { if (axisRenderer._orientation == AxisOrientation.horizontal) { value = pinchRect.left - offsetRect.left; - axisTrans = offsetRect.width / _zoomAxes[axisIndex].delta; - rangeMin = value / axisTrans + _zoomAxes[axisIndex].min; + axisTrans = offsetRect.width / _zoomAxes[axisIndex].delta!; + rangeMin = value / axisTrans + _zoomAxes[axisIndex].min!; value = pinchRect.left + pinchRect.width - offsetRect.left; - rangeMax = value / axisTrans + _zoomAxes[axisIndex].min; + rangeMax = value / axisTrans + _zoomAxes[axisIndex].min!; } else { value = pinchRect.top - offsetRect.top; - axisTrans = offsetRect.height / _zoomAxes[axisIndex].delta; + axisTrans = offsetRect.height / _zoomAxes[axisIndex].delta!; rangeMin = (value * -1 + offsetRect.height) / axisTrans + - _zoomAxes[axisIndex].min; + _zoomAxes[axisIndex].min!; value = pinchRect.top + pinchRect.height - offsetRect.top; rangeMax = (value * -1 + offsetRect.height) / axisTrans + - _zoomAxes[axisIndex].min; + _zoomAxes[axisIndex].min!; } selectionMin = math.min(rangeMin, rangeMax); selectionMax = math.max(rangeMin, rangeMax); - currentPosition = (selectionMin - _zoomAxes[axisIndex].actualMin) / - _zoomAxes[axisIndex].actualDelta; + currentPosition = (selectionMin - _zoomAxes[axisIndex].actualMin!) / + _zoomAxes[axisIndex].actualDelta!; currentFactor = - (selectionMax - selectionMin) / _zoomAxes[axisIndex].actualDelta; + (selectionMax - selectionMin) / _zoomAxes[axisIndex].actualDelta!; currentZoomPosition = currentPosition < 0 ? 0 : currentPosition; currentZoomFactor = currentFactor > 1 ? 1 : currentFactor; - final num maxZoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; + maxZoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; if (currentZoomFactor < maxZoomFactor) { axisRenderer._zoomFactor = maxZoomFactor; axisRenderer._zoomPosition = 0.0; @@ -719,8 +732,8 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { onPinch(axisRenderer, currentZoomPosition, currentZoomFactor); } if (chart.onZooming != null) { - ZoomPanArgs zoomingArgs; - _bindZoomEvent(chart, axisRenderer, zoomingArgs, chart.onZooming); + ZoomPanArgs? zoomingArgs; + _bindZoomEvent(chart, axisRenderer, zoomingArgs, chart.onZooming!); } } } @@ -730,26 +743,27 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { /// Below method is for storing calculated zoom range void _calculateZoomAxesRange(SfCartesianChart chart) { + ChartAxisRenderer axisRenderer; + _ZoomAxisRange range; + _VisibleRange axisRange; for (int index = 0; index < _chartState._chartAxis._axisRenderersCollection.length; index++) { - final ChartAxisRenderer axisRenderer = - _chartState._chartAxis._axisRenderersCollection[index]; - final _ZoomAxisRange range = _ZoomAxisRange(); - final _VisibleRange axisRange = axisRenderer._visibleRange; - if (_chartState._zoomAxes != null && - _chartState._zoomAxes.isNotEmpty && + axisRenderer = _chartState._chartAxis._axisRenderersCollection[index]; + range = _ZoomAxisRange(); + axisRange = axisRenderer._visibleRange!; + if (_chartState._zoomAxes.isNotEmpty && index <= _chartState._zoomAxes.length - 1) { if (!_delayRedraw) { - _chartState._zoomAxes[index].min = axisRange.minimum; - _chartState._zoomAxes[index].delta = axisRange.delta; + _chartState._zoomAxes[index].min = axisRange.minimum.toDouble(); + _chartState._zoomAxes[index].delta = axisRange.delta.toDouble(); } } else { - _chartState._zoomAxes ??= <_ZoomAxisRange>[]; - range.actualMin = axisRenderer._actualRange.minimum; - range.actualDelta = axisRenderer._actualRange.delta; - range.min = axisRange.minimum; - range.delta = axisRange.delta; + // _chartState._zoomAxes ??= <_ZoomAxisRange>[]; + range.actualMin = axisRenderer._actualRange!.minimum.toDouble(); + range.actualDelta = axisRenderer._actualRange!.delta.toDouble(); + range.min = axisRange.minimum.toDouble(); + range.delta = axisRange.delta.toDouble(); _chartState._zoomAxes.add(range); } } @@ -758,20 +772,21 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { /// Below method is for mouseWheel Zooming void _performMouseWheelZooming( PointerScrollEvent event, double mouseX, double mouseY) { - final num direction = (event.scrollDelta.dy / 120) > 0 ? -1 : 1; - num origin = 0.5; - num cumulative, zoomFactor, zoomPosition; + final double direction = (event.scrollDelta.dy / 120) > 0 ? -1 : 1; + double origin = 0.5; + double cumulative, zoomFactor, zoomPosition, maxZoomFactor; _chartState._zoomProgress = true; _calculateZoomAxesRange(_chart); _isPanning = _chart.zoomPanBehavior.enablePanning; - ZoomPanArgs zoomStartArgs, zoomResetArgs; + ZoomPanArgs? zoomStartArgs, zoomResetArgs; + ChartAxisRenderer axisRenderer; for (int axisIndex = 0; axisIndex < _chartState._chartAxis._axisRenderersCollection.length; axisIndex++) { - final ChartAxisRenderer axisRenderer = - _chartState._chartAxis._axisRenderersCollection[axisIndex]; + axisRenderer = _chartState._chartAxis._axisRenderersCollection[axisIndex]; if (_chart.onZoomStart != null) { - _bindZoomEvent(_chart, axisRenderer, zoomStartArgs, _chart.onZoomStart); + _bindZoomEvent( + _chart, axisRenderer, zoomStartArgs, _chart.onZoomStart!); } axisRenderer._previousZoomFactor = axisRenderer._zoomFactor; axisRenderer._previousZoomPosition = axisRenderer._zoomPosition; @@ -792,7 +807,8 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { : origin < 0 ? 0 : origin; - zoomFactor = (cumulative == 1) ? 1 : _minMax(1 / cumulative, 0, 1); + zoomFactor = ((cumulative == 1) ? 1 : _minMax(1 / cumulative, 0, 1)) + .toDouble(); zoomPosition = (cumulative == 1) ? 0 : axisRenderer._zoomPosition + @@ -807,21 +823,22 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { axisRenderer._zoomFactor = zoomFactor; axisRenderer._bounds = const Rect.fromLTWH(0, 0, 0, 0); axisRenderer._visibleLabels = []; - final num maxZoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; + maxZoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; if (zoomFactor < maxZoomFactor) { axisRenderer._zoomFactor = maxZoomFactor; axisRenderer._zoomPosition = 0.0; zoomFactor = maxZoomFactor; } if (_chart.onZoomEnd != null) { - ZoomPanArgs zoomEndArgs; - _bindZoomEvent(_chart, axisRenderer, zoomEndArgs, _chart.onZoomEnd); + ZoomPanArgs? zoomEndArgs; + _bindZoomEvent( + _chart, axisRenderer, zoomEndArgs, _chart.onZoomEnd!); } if (axisRenderer._zoomFactor.toInt() == 1 && axisRenderer._zoomPosition.toInt() == 0 && _chart.onZoomReset != null) { _bindZoomEvent( - _chart, axisRenderer, zoomResetArgs, _chart.onZoomReset); + _chart, axisRenderer, zoomResetArgs, _chart.onZoomReset!); } } } @@ -829,13 +846,55 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { _createZoomState(); } + /// Below method is for zoomIn and zoomOut public methods + void _setZoomFactorAndZoomPosition(SfCartesianChartState chartState, + ChartAxisRenderer axisRenderer, double? zoomFactor) { + final Rect axisClipRect = chartState._chartAxis._axisClipRect; + final num direction = _isZoomIn + ? 1 + : _isZoomOut + ? -1 + : 1; + final num cumulative = math.max( + math.max(1 / _minMax(axisRenderer._zoomFactor, 0, 1), 1) + + (0.1 * direction), + 1); + if (cumulative >= 1) { + num origin = axisRenderer._orientation == AxisOrientation.horizontal + ? (axisClipRect.left + axisClipRect.width / 2) / axisClipRect.width + : 1 - + ((axisClipRect.top + axisClipRect.height / 2) / + axisClipRect.height); + origin = origin > 1 + ? 1 + : origin < 0 + ? 0 + : origin; + zoomFactor = + ((cumulative == 1) ? 1 : _minMax(1 / cumulative, 0, 1)).toDouble(); + _zoomPosition = (cumulative == 1) + ? 0 + : axisRenderer._zoomPosition + + ((axisRenderer._zoomFactor - zoomFactor) * origin); + if (axisRenderer._zoomPosition != _zoomPosition || + axisRenderer._zoomFactor != zoomFactor) { + zoomFactor = (_zoomPosition! + zoomFactor) > 1 + ? (1 - _zoomPosition!) + : zoomFactor; + } + + axisRenderer._zoomPosition = _zoomPosition!; + axisRenderer._zoomFactor = zoomFactor; + } + } + /// Performs panning action. @override void onPan(double xPos, double yPos) => _doPan(xPos, yPos); /// Performs the double-tap action. @override - void onDoubleTap(double xPos, double yPos, double zoomFactor) => + void onDoubleTap(double xPos, double yPos, double? zoomFactor) => _doubleTapZooming(xPos, yPos, zoomFactor); /// Draws selection zoomRect @@ -869,5 +928,5 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { class _ZoomAxisRange { _ZoomAxisRange({this.actualMin, this.actualDelta, this.min, this.delta}); - num actualMin, actualDelta, min, delta; + double? actualMin, actualDelta, min, delta; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/utils/enum.dart b/packages/syncfusion_flutter_charts/lib/src/chart/utils/enum.dart index e81109deb..494bb0add 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/utils/enum.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/utils/enum.dart @@ -94,42 +94,6 @@ enum ChartDataLabelPosition { outside } -/// Data marker shapes. -/// -/// Data marker supports the below shapes. -/// If the shape is DataMarkerType.image, specify the image path in the imageUrl property of markerSettings. -enum DataMarkerType { - ///- DataMarkerType.cicle, will render marker shape circle. - circle, - - ///- DataMarkerType.rectangle, will render marker shape rectangle. - rectangle, - - ///- DataMarkerType.image, will render marker image. - image, - - ///- DataMarkerType.pentagon, will render marker shape pentagon. - pentagon, - - ///- DataMarkerType.verticalLine, will render marker verticalLine. - verticalLine, - - ///- DataMarkerType.horizontalLine, will render marker horizontalLine. - horizontalLine, - - ///- DataMarkerType.diamond, will render marker shape diamond. - diamond, - - ///- DataMarkerType.triangle, will render marker shape triangle. - triangle, - - ///- DataMarkerType.invertedTriangle, will render marker shape invertedTriangle. - invertedTriangle, - - ///- DataMarkerType.none, will skip rendering marker. - none, -} - /// Renders a variety of splines /// /// Spline supports the following types. @@ -418,15 +382,43 @@ enum LabelAlignment { ///Whether marker should be visible or not when trackball is enabled. enum TrackballVisibilityMode { - ///* auto - If the [isVisible] property in the series [markerSettings] is set + ///* auto - If the `isVisible` property in the series `markerSettings` is set ///to true, then the trackball marker will also be displayed for that particular ///series, else it will not be displayed. auto, ///* visible - Makes the trackball marker visible for all the series, - ///irrespective of considering the [isVisible] property's value in the [markerSettings]. + ///irrespective of considering the `isVisible` property's value in the `markerSettings`. visible, ///* hidden - Hides the trackball marker for all the series. hidden } + +///The direction of swiping on the chart. +/// +///Provides the swiping direction information to the user. +enum ChartSwipeDirection { + ///If the chart is swiped from left to right direction, + /// the direction is `ChartSwipeDirection.start` + start, + + ///if the swipe happens from right to left direction, the + /// direction is `ChartSwipeDirection.end`. + end +} + +///Determines whether the axis should be scrolled from the start position or end position. +/// +///For example, if there are 10 data points and [autoScrollingDelta] value is 5 and `AutoScrollingMode.end` +/// is specified to this property, last 5 points will be displayed in the chart. If `AutoScrollingMode.start` +/// is set to this property, first 5 points will be displayed. +/// +///Defaults to `AutoScrollingMode.end`. +enum AutoScrollingMode { + ///`AutoScrollingMode.start`, If the chart is scrolled from left to right direction + start, + + ///`AutoScrollingMode.end`, If the chart is scrolled from right to left direction + end +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart index 82676ec37..6a3944d57 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart @@ -1,27 +1,10 @@ part of charts; -/// Measure the text and return the text size -Size _measureText(String textValue, TextStyle textStyle, [int angle]) { - Size size; - final TextPainter textPainter = TextPainter( - textAlign: TextAlign.center, - textDirection: TextDirection.ltr, - text: TextSpan(text: textValue, style: textStyle)); - textPainter.layout(); - if (angle != null) { - final Rect rect = _rotatedTextSize(textPainter.size, angle); - size = Size(rect.width, rect.height); - } else { - size = Size(textPainter.width, textPainter.height); - } - return size; -} - /// Return percentage to value -num _percentageToValue(String value, num size) { +num? _percentageToValue(String? value, num size) { if (value != null) { return value.contains('%') - ? (size / 100) * num.tryParse(value.replaceAll(RegExp('%'), '')) + ? (size / 100) * num.tryParse(value.replaceAll(RegExp('%'), ''))! : num.tryParse(value); } return null; @@ -29,8 +12,8 @@ num _percentageToValue(String value, num size) { /// Draw the text void _drawText(Canvas canvas, String text, Offset point, TextStyle style, - [int angle]) { - final num maxLines = _getMaxLinesContent(text); + [int? angle]) { + final int maxLines = getMaxLinesContent(text); final TextSpan span = TextSpan(text: text, style: style); final TextPainter tp = TextPainter( text: span, @@ -42,77 +25,17 @@ void _drawText(Canvas canvas, String text, Offset point, TextStyle style, canvas.translate(point.dx, point.dy); Offset labelOffset = const Offset(0.0, 0.0); if (angle != null && angle > 0) { - canvas.rotate(_degreeToRadian(angle)); + canvas.rotate(degreeToRadian(angle)); labelOffset = Offset(-tp.width / 2, -tp.height / 2); } tp.paint(canvas, labelOffset); canvas.restore(); } -/// Returns lines count in a string -num _getMaxLinesContent(String text) { - return text != null && text.isNotEmpty && text.contains('\n') - ? text.split('\n').length - : 1; -} - -vector.Vector2 _offsetToVector2(Offset offset) => - vector.Vector2(offset.dx, offset.dy); - -Offset _vector2ToOffset(vector.Vector2 vector) => Offset(vector.x, vector.y); - -Offset _transform( - vector.Matrix2 matrix, - Offset offset, -) { - return _vector2ToOffset(matrix * _offsetToVector2(offset)); -} - -/// To calculate rect according to rotated text size -Rect _rotatedTextSize(Size size, int angle) { - final Rect rect = Rect.fromLTWH(0, 0, size.width, size.height); - final vector.Matrix2 _rotatorMatrix = - vector.Matrix2.rotation(_degreeToRadian(angle)); - - final Rect movedToCenterAsOrigin = rect.shift(-rect.center); - - Offset _topLeft = movedToCenterAsOrigin.topLeft; - Offset _topRight = movedToCenterAsOrigin.topRight; - Offset _bottomLeft = movedToCenterAsOrigin.bottomLeft; - Offset _bottomRight = movedToCenterAsOrigin.bottomRight; - - _topLeft = _transform(_rotatorMatrix, _topLeft); - _topRight = _transform(_rotatorMatrix, _topRight); - _bottomLeft = _transform(_rotatorMatrix, _bottomLeft); - _bottomRight = _transform(_rotatorMatrix, _bottomRight); - - final List rotOffsets = [ - _topLeft, - _topRight, - _bottomLeft, - _bottomRight - ]; - - final double minX = - rotOffsets.map((Offset offset) => offset.dx).reduce(math.min); - final double maxX = - rotOffsets.map((Offset offset) => offset.dx).reduce(math.max); - final double minY = - rotOffsets.map((Offset offset) => offset.dy).reduce(math.min); - final double maxY = - rotOffsets.map((Offset offset) => offset.dy).reduce(math.max); - - final Rect rotateRect = Rect.fromPoints( - Offset(minX, minY), - Offset(maxX, maxY), - ); - return rotateRect; -} - /// Draw the path void _drawDashedPath(Canvas canvas, _CustomPaintStyle style, Offset moveToPoint, Offset lineToPoint, - [List dashArray]) { + [List? dashArray]) { bool even = false; final Path path = Path(); path.moveTo(moveToPoint.dx, moveToPoint.dy); @@ -133,7 +56,7 @@ void _drawDashedPath(Canvas canvas, _CustomPaintStyle style, Offset moveToPoint, _dashPath( path, dashArray: _CircularIntervalList(dashArray), - ), + )!, paint); } } else { @@ -141,14 +64,11 @@ void _drawDashedPath(Canvas canvas, _CustomPaintStyle style, Offset moveToPoint, } } -/// Convert degree to radian -num _degreeToRadian(int deg) => deg * (math.pi / 180); - /// find the position of point -num _valueToCoefficient(num value, ChartAxisRenderer axisRenderer) { +num _valueToCoefficient(num? value, ChartAxisRenderer axisRenderer) { num result = 0; if (axisRenderer._visibleRange != null && value != null) { - final _VisibleRange range = axisRenderer._visibleRange; + final _VisibleRange range = axisRenderer._visibleRange!; if (range != null) { result = (value - range.minimum) / (range.delta); result = axisRenderer._axis.isInversed ? (1 - result) : result; @@ -167,11 +87,11 @@ bool _withInRange(num value, _VisibleRange range) => /// To find the proper series color of each point in waterfall chart, /// which includes intermediate sum, total sum and negative point. -Color _getWaterfallSeriesColor(WaterfallSeries series, - CartesianChartPoint point, Color seriesColor) => - point.isIntermediateSum +Color? _getWaterfallSeriesColor(WaterfallSeries series, + CartesianChartPoint point, Color? seriesColor) => + point.isIntermediateSum! ? series.intermediateSumColor ?? seriesColor - : point.isTotalSum + : point.isTotalSum! ? series.totalSumColor ?? seriesColor : point.yValue < 0 ? series.negativePointsColor ?? seriesColor @@ -180,11 +100,11 @@ Color _getWaterfallSeriesColor(WaterfallSeries series, /// Get the location of point _ChartLocation _calculatePoint( num x, - num y, + num? y, ChartAxisRenderer xAxisRenderer, ChartAxisRenderer yAxisRenderer, bool isInverted, - CartesianSeries series, + CartesianSeries? series, Rect rect) { final ChartAxis xAxis = xAxisRenderer._axis, yAxis = yAxisRenderer._axis; x = xAxis is LogarithmicAxis @@ -199,9 +119,9 @@ _ChartLocation _calculatePoint( y = _valueToCoefficient(y, yAxisRenderer); final num xLength = isInverted ? rect.height : rect.width; final num yLength = isInverted ? rect.width : rect.height; - final num locationX = + final double locationX = rect.left + (isInverted ? (y * yLength) : (x * xLength)); - final num locationY = + final double locationY = rect.top + (isInverted ? (1 - x) * xLength : (1 - y) * yLength); return _ChartLocation(locationX, locationY); } @@ -211,15 +131,17 @@ num _calculateMinPointsDelta( ChartAxisRenderer axisRenderer, List seriesRenderers, SfCartesianChartState chartState) { - num minDelta = 1.7976931348623157e+308, minVal, seriesMin; + num minDelta = 1.7976931348623157e+308, minVal; + num? seriesMin; dynamic xValues; for (final CartesianSeriesRenderer seriesRenderer in seriesRenderers) { final CartesianSeries series = seriesRenderer._series; num value; xValues = []; - if (seriesRenderer._visible && + if (seriesRenderer._visible! && ((axisRenderer._name == series.xAxisName) || - (axisRenderer._name == 'primaryXAxis' && + (axisRenderer._name == + (chartState._chart.primaryXAxis.name ?? 'primaryXAxis') && series.xAxisName == null) || (axisRenderer._name == chartState._chartAxis._primaryXAxisRenderer._name && @@ -231,11 +153,20 @@ num _calculateMinPointsDelta( xValues.sort(); if (xValues.length == 1) { + DateTime? minDate; + num? minimumInSeconds; + if (axisRenderer is DateTimeAxisRenderer) { + minDate = DateTime.fromMillisecondsSinceEpoch( + seriesRenderer._minimumX! as int); + minDate = minDate.subtract(Duration(days: 1)); + minimumInSeconds = minDate.millisecondsSinceEpoch; + } seriesMin = (axisRenderer is DateTimeAxisRenderer && seriesRenderer._minimumX == seriesRenderer._maximumX) - ? (seriesRenderer._minimumX - 2592000000) + ? minimumInSeconds : seriesRenderer._minimumX; - minVal = xValues[0] - (seriesMin ?? axisRenderer._visibleRange.minimum); + minVal = + xValues[0] - (seriesMin ?? axisRenderer._visibleRange!.minimum); if (minVal != 0) { minDelta = math.min(minDelta, minVal); } @@ -259,88 +190,6 @@ num _calculateMinPointsDelta( return minDelta; } -/// Draw different marker shapes by using height and width -class _ChartShapeUtils { - /// Draw the circle shape marker - static void _drawCircle( - Path path, double x, double y, double width, double height) { - path.addArc( - Rect.fromLTRB( - x - width / 2, y - height / 2, x + width / 2, y + height / 2), - 0.0, - 2 * math.pi); - } - - /// Draw the Rectangle shape marker - static void _drawRectangle( - Path path, double x, double y, double width, double height) { - path.addRect(Rect.fromLTRB( - x - width / 2, y - height / 2, x + width / 2, y + height / 2)); - } - - ///Draw the Pentagon shape marker - static void _drawPentagon( - Path path, double x, double y, double width, double height) { - const int eq = 72; - double xValue; - double yValue; - for (int i = 0; i <= 5; i++) { - xValue = width / 2 * math.cos((math.pi / 180) * (i * eq)); - yValue = height / 2 * math.sin((math.pi / 180) * (i * eq)); - i == 0 - ? path.moveTo(x + xValue, y + yValue) - : path.lineTo(x + xValue, y + yValue); - } - path.close(); - } - - ///Draw the Vertical line shape marker - static void _drawVerticalLine( - Path path, double x, double y, double width, double height) { - path.moveTo(x, y + height / 2); - path.lineTo(x, y - height / 2); - } - - ///Draw the Inverted Triangle shape marker - static void _drawInvertedTriangle( - Path path, double x, double y, double width, double height) { - path.moveTo(x + width / 2, y - height / 2); - - path.lineTo(x, y + height / 2); - path.lineTo(x - width / 2, y - height / 2); - path.lineTo(x + width / 2, y - height / 2); - path.close(); - } - - ///Draw the Horizontal line shape marker - static void _drawHorizontalLine( - Path path, double x, double y, double width, double height) { - path.moveTo(x - width / 2, y); - path.lineTo(x + width / 2, y); - } - - ///Draw the Diamond shape marker - static void _drawDiamond( - Path path, double x, double y, double width, double height) { - path.moveTo(x - width / 2, y); - path.lineTo(x, y + height / 2); - path.lineTo(x + width / 2, y); - path.lineTo(x, y - height / 2); - path.lineTo(x - width / 2, y); - path.close(); - } - - ///Draw the Triangle shape marker - static void _drawTriangle( - Path path, double x, double y, double width, double height) { - path.moveTo(x - width / 2, y + height / 2); - path.lineTo(x + width / 2, y + height / 2); - path.lineTo(x, y - height / 2); - path.lineTo(x - width / 2, y + height / 2); - path.close(); - } -} - ///Draw Legend series type icon PaintingStyle _calculateLegendShapes(Path path, double x, double y, double width, double height, String seriesType) { @@ -534,16 +383,22 @@ void _calculateStepAreaIconPath( ///Calculate pie legend icon path void _calculatePieIconPath( Path path, double x, double y, double width, double height) { - final num r = math.min(height, width) / 2; + final double r = math.min(height, width) / 2; path.moveTo(x, y); path.lineTo(x + r, y); - path.arcTo(Rect.fromCircle(center: Offset(x, y), radius: r), - _degreesToRadians(0), _degreesToRadians(270), false); + path.arcTo( + Rect.fromCircle(center: Offset(x, y), radius: r), + _degreesToRadians(0).toDouble(), + _degreesToRadians(270).toDouble(), + false); path.close(); path.moveTo(x + width / 10, y - height / 10); path.lineTo(x + r, y - height / 10); - path.arcTo(Rect.fromCircle(center: Offset(x + 2, y - 2), radius: r), - _degreesToRadians(-5), _degreesToRadians(-80), false); + path.arcTo( + Rect.fromCircle(center: Offset(x + 2, y - 2), radius: r), + _degreesToRadians(-5).toDouble(), + _degreesToRadians(-80).toDouble(), + false); path.close(); } @@ -568,26 +423,26 @@ void _calculateFunnelIconPath( } /// Calculate the rect bounds for column series and Bar series. -Rect _calculateRectangle(num x1, num y1, num x2, num y2, +Rect _calculateRectangle(num x1, num? y1, num x2, num? y2, CartesianSeriesRenderer seriesRenderer, SfCartesianChartState _chartState) { final Rect rect = _calculatePlotOffset( _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer?._axis?.plotOffset, - seriesRenderer._yAxisRenderer?._axis?.plotOffset)); + Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, + seriesRenderer._yAxisRenderer!._axis.plotOffset)); final bool isInverted = _chartState._requireInvertedAxis; final _ChartLocation point1 = _calculatePoint( x1, y1, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, isInverted, seriesRenderer._series, rect); final _ChartLocation point2 = _calculatePoint( x2, y2, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, isInverted, seriesRenderer._series, rect); @@ -609,22 +464,22 @@ Rect _calculateShadowRectangle( Offset plotOffset) { final Rect rect = _calculatePlotOffset( _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer?._axis?.plotOffset, - seriesRenderer._yAxisRenderer?._axis?.plotOffset)); + Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, + seriesRenderer._yAxisRenderer!._axis.plotOffset)); final bool isInverted = _chartState._requireInvertedAxis; final _ChartLocation point1 = _calculatePoint( x1, y1, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, isInverted, seriesRenderer._series, rect); final _ChartLocation point2 = _calculatePoint( x2, y2, - seriesRenderer._xAxisRenderer, - seriesRenderer._yAxisRenderer, + seriesRenderer._xAxisRenderer!, + seriesRenderer._yAxisRenderer!, isInverted, seriesRenderer._series, rect); @@ -633,24 +488,24 @@ Rect _calculateShadowRectangle( final bool isStackedColumn = seriesRenderer._seriesType == 'stackedcolumn'; final bool isStackedBar = seriesRenderer._seriesType == 'stackedbar'; final bool isRangeColumn = seriesRenderer._seriesType == 'rangecolumn'; - ColumnSeries columnSeries; - BarSeries barSeries; - StackedColumnSeries stackedColumnSeries; - StackedBarSeries stackedBarSeries; - RangeColumnSeries rangeColumnSeries; - HistogramSeries histogramSeries; + ColumnSeries? columnSeries; + BarSeries? barSeries; + StackedColumnSeries? stackedColumnSeries; + StackedBarSeries? stackedBarSeries; + RangeColumnSeries? rangeColumnSeries; + HistogramSeries? histogramSeries; if (seriesRenderer._seriesType == 'column') { - columnSeries = seriesRenderer._series; + columnSeries = seriesRenderer._series as ColumnSeries; } else if (seriesRenderer._seriesType == 'bar') { - barSeries = seriesRenderer._series; + barSeries = seriesRenderer._series as BarSeries; } else if (seriesRenderer._seriesType == 'stackedcolumn') { - stackedColumnSeries = seriesRenderer._series; + stackedColumnSeries = seriesRenderer._series as StackedColumnSeries; } else if (seriesRenderer._seriesType == 'stackedbar') { - stackedBarSeries = seriesRenderer._series; + stackedBarSeries = seriesRenderer._series as StackedBarSeries; } else if (seriesRenderer._seriesType == 'rangecolumn') { - rangeColumnSeries = seriesRenderer._series; + rangeColumnSeries = seriesRenderer._series as RangeColumnSeries; } else if (seriesRenderer._seriesType == 'histogram') { - histogramSeries = seriesRenderer._series; + histogramSeries = seriesRenderer._series as HistogramSeries; } return !_chartState._chart.isTransposed ? _getNormalShadowRect( @@ -701,27 +556,27 @@ Rect _getNormalShadowRect( Offset plotOffset, _ChartLocation point1, _ChartLocation point2, - ColumnSeries columnSeries, - BarSeries barSeries, - StackedColumnSeries stackedColumnSeries, - StackedBarSeries stackedBarSeries, - RangeColumnSeries rangeColumnSeries, - HistogramSeries histogramSeries) { + ColumnSeries? columnSeries, + BarSeries? barSeries, + StackedColumnSeries? stackedColumnSeries, + StackedBarSeries? stackedBarSeries, + RangeColumnSeries? rangeColumnSeries, + HistogramSeries? histogramSeries) { return Rect.fromLTWH( isColumn ? math.min(point1.x, point2.x) + - (-columnSeries.trackBorderWidth - columnSeries.trackPadding) + (-columnSeries!.trackBorderWidth - columnSeries.trackPadding) : isHistogram ? math.min(point1.x, point2.x) + - (-histogramSeries.trackBorderWidth - + (-histogramSeries!.trackBorderWidth - histogramSeries.trackPadding) : isRangeColumn ? math.min(point1.x, point2.x) + - (-rangeColumnSeries.trackBorderWidth - + (-rangeColumnSeries!.trackBorderWidth - rangeColumnSeries.trackPadding) : isStackedColumn ? math.min(point1.x, point2.x) + - (-stackedColumnSeries.trackBorderWidth - + (-stackedColumnSeries!.trackBorderWidth - stackedColumnSeries.trackPadding) : isStackedBar ? _chartState._chartAxis._axisClipRect.left @@ -732,26 +587,26 @@ Rect _getNormalShadowRect( ? rect.top : isStackedBar ? (math.min(point1.y, point2.y) - - stackedBarSeries.trackBorderWidth - + stackedBarSeries!.trackBorderWidth - stackedBarSeries.trackPadding) : (math.min(point1.y, point2.y) - - barSeries.trackBorderWidth - + barSeries!.trackBorderWidth - barSeries.trackPadding), isColumn ? (point2.x - point1.x).abs() + - (columnSeries.trackBorderWidth * 2) + + (columnSeries!.trackBorderWidth * 2) + columnSeries.trackPadding * 2 : isHistogram ? (point2.x - point1.x).abs() + - (histogramSeries.trackBorderWidth * 2) + + (histogramSeries!.trackBorderWidth * 2) + histogramSeries.trackPadding * 2 : isRangeColumn ? (point2.x - point1.x).abs() + - (rangeColumnSeries.trackBorderWidth * 2) + + (rangeColumnSeries!.trackBorderWidth * 2) + rangeColumnSeries.trackPadding * 2 : isStackedColumn ? (point2.x - point1.x).abs() + - (stackedColumnSeries.trackBorderWidth * 2) + + (stackedColumnSeries!.trackBorderWidth * 2) + stackedColumnSeries.trackPadding * 2 : isStackedBar ? _chartState._chartAxis._axisClipRect.width @@ -763,10 +618,10 @@ Rect _getNormalShadowRect( 2 * plotOffset.dy) : isStackedBar ? (point2.y - point1.y).abs() + - stackedBarSeries.trackBorderWidth * 2 + + stackedBarSeries!.trackBorderWidth * 2 + stackedBarSeries.trackPadding * 2 : (point2.y - point1.y).abs() + - barSeries.trackBorderWidth * 2 + + barSeries!.trackBorderWidth * 2 + barSeries.trackPadding * 2); } @@ -782,12 +637,12 @@ Rect _getTransposedShadowRect( Offset plotOffset, _ChartLocation point1, _ChartLocation point2, - ColumnSeries columnSeries, - BarSeries barSeries, - StackedColumnSeries stackedColumnSeries, - StackedBarSeries stackedBarSeries, - RangeColumnSeries rangeColumnSeries, - HistogramSeries histogramSeries) { + ColumnSeries? columnSeries, + BarSeries? barSeries, + StackedColumnSeries? stackedColumnSeries, + StackedBarSeries? stackedBarSeries, + RangeColumnSeries? rangeColumnSeries, + HistogramSeries? histogramSeries) { return Rect.fromLTWH( isColumn || isRangeColumn || isHistogram ? _chartState._chartAxis._axisClipRect.left @@ -795,25 +650,25 @@ Rect _getTransposedShadowRect( ? _chartState._chartAxis._axisClipRect.left : isStackedBar ? math.min(point1.x, point2.x) + - (-stackedBarSeries.trackBorderWidth - + (-stackedBarSeries!.trackBorderWidth - stackedBarSeries.trackPadding) : math.min(point1.x, point2.x) + - (-barSeries.trackBorderWidth - barSeries.trackPadding), + (-barSeries!.trackBorderWidth - barSeries.trackPadding), isColumn ? (math.min(point1.y, point2.y) - - columnSeries.trackBorderWidth - + columnSeries!.trackBorderWidth - columnSeries.trackPadding) : isHistogram ? (math.min(point1.y, point2.y) - - histogramSeries.trackBorderWidth - + histogramSeries!.trackBorderWidth - histogramSeries.trackPadding) : isRangeColumn ? (math.min(point1.y, point2.y) - - rangeColumnSeries.trackBorderWidth - + rangeColumnSeries!.trackBorderWidth - rangeColumnSeries.trackPadding) : isStackedColumn ? (math.min(point1.y, point2.y) - - stackedColumnSeries.trackBorderWidth - + stackedColumnSeries!.trackBorderWidth - stackedColumnSeries.trackPadding) : isStackedBar ? rect.top @@ -824,26 +679,26 @@ Rect _getTransposedShadowRect( ? _chartState._chartAxis._axisClipRect.width : isStackedBar ? (point2.x - point1.x).abs() + - (stackedBarSeries.trackBorderWidth * 2) + + (stackedBarSeries!.trackBorderWidth * 2) + stackedBarSeries.trackPadding * 2 : (point2.x - point1.x).abs() + - (barSeries.trackBorderWidth * 2) + + (barSeries!.trackBorderWidth * 2) + barSeries.trackPadding * 2, isColumn ? ((point2.y - point1.y).abs() + - columnSeries.trackBorderWidth * 2 + + columnSeries!.trackBorderWidth * 2 + columnSeries.trackPadding * 2) : isHistogram ? ((point2.y - point1.y).abs() + - histogramSeries.trackBorderWidth * 2 + + histogramSeries!.trackBorderWidth * 2 + histogramSeries.trackPadding * 2) : isRangeColumn ? (point2.y - point1.y).abs() + - rangeColumnSeries.trackBorderWidth * 2 + + rangeColumnSeries!.trackBorderWidth * 2 + rangeColumnSeries.trackPadding * 2 : isStackedColumn ? (point2.y - point1.y).abs() + - stackedColumnSeries.trackBorderWidth * 2 + + stackedColumnSeries!.trackBorderWidth * 2 + stackedColumnSeries.trackPadding * 2 : isStackedBar ? (_chartState._chartAxis._axisClipRect.height - @@ -855,152 +710,166 @@ Rect _getTransposedShadowRect( /// Calculated the side by side range for Column and bar series _VisibleRange _calculateSideBySideInfo( CartesianSeriesRenderer seriesRenderer, SfCartesianChartState _chartState) { - num rectPosition; - num count; + num? rectPosition; + num? count; num seriesSpacing; - num pointSpacing; + num? pointSpacing; final SfCartesianChart chart = _chartState._chart; final CartesianSeries series = seriesRenderer._series; if (seriesRenderer._seriesType == 'column' && chart.enableSideBySideSeriesPlacement) { - final ColumnSeriesRenderer columnSeriesRenderer = seriesRenderer; + final ColumnSeriesRenderer columnSeriesRenderer = + seriesRenderer as ColumnSeriesRenderer; _calculateSideBySidePositions(columnSeriesRenderer, _chartState); rectPosition = columnSeriesRenderer._rectPosition; count = columnSeriesRenderer._rectCount; } else if (seriesRenderer._seriesType == 'histogram' && chart.enableSideBySideSeriesPlacement) { - final HistogramSeriesRenderer histogramSeriesRenderer = seriesRenderer; + final HistogramSeriesRenderer histogramSeriesRenderer = + seriesRenderer as HistogramSeriesRenderer; _calculateSideBySidePositions(histogramSeriesRenderer, _chartState); rectPosition = histogramSeriesRenderer._rectPosition; count = histogramSeriesRenderer._rectCount; } else if (seriesRenderer._seriesType == 'bar' && chart.enableSideBySideSeriesPlacement) { - final BarSeriesRenderer barSeriesRenderer = seriesRenderer; + final BarSeriesRenderer barSeriesRenderer = + seriesRenderer as BarSeriesRenderer; _calculateSideBySidePositions(barSeriesRenderer, _chartState); rectPosition = barSeriesRenderer._rectPosition; count = barSeriesRenderer._rectCount; } else if ((seriesRenderer._seriesType.contains('stackedcolumn') || seriesRenderer._seriesType.contains('stackedbar')) && chart.enableSideBySideSeriesPlacement) { - final _StackedSeriesRenderer stackedRectSeriesRenderer = seriesRenderer; + final _StackedSeriesRenderer stackedRectSeriesRenderer = + seriesRenderer as _StackedSeriesRenderer; _calculateSideBySidePositions(stackedRectSeriesRenderer, _chartState); rectPosition = stackedRectSeriesRenderer._rectPosition; count = stackedRectSeriesRenderer._rectCount; } else if (seriesRenderer._seriesType == 'rangecolumn' && chart.enableSideBySideSeriesPlacement) { - final RangeColumnSeriesRenderer rangeColumnSeriesRenderer = seriesRenderer; + final RangeColumnSeriesRenderer rangeColumnSeriesRenderer = + seriesRenderer as RangeColumnSeriesRenderer; _calculateSideBySidePositions(rangeColumnSeriesRenderer, _chartState); rectPosition = rangeColumnSeriesRenderer._rectPosition; count = rangeColumnSeriesRenderer._rectCount; } else if (seriesRenderer._seriesType == 'hilo' && chart.enableSideBySideSeriesPlacement) { - final HiloSeriesRenderer hiloSeriesRenderer = seriesRenderer; + final HiloSeriesRenderer hiloSeriesRenderer = + seriesRenderer as HiloSeriesRenderer; _calculateSideBySidePositions(hiloSeriesRenderer, _chartState); rectPosition = hiloSeriesRenderer._rectPosition; count = hiloSeriesRenderer._rectCount; } else if (seriesRenderer._seriesType == 'hiloopenclose' && chart.enableSideBySideSeriesPlacement) { final HiloOpenCloseSeriesRenderer hiloOpenCloseSeriesRenderer = - seriesRenderer; + seriesRenderer as HiloOpenCloseSeriesRenderer; _calculateSideBySidePositions(hiloOpenCloseSeriesRenderer, _chartState); rectPosition = hiloOpenCloseSeriesRenderer._rectPosition; count = hiloOpenCloseSeriesRenderer._rectCount; } else if (seriesRenderer._seriesType == 'candle' && chart.enableSideBySideSeriesPlacement) { - final CandleSeriesRenderer candleSeriesRenderer = seriesRenderer; + final CandleSeriesRenderer candleSeriesRenderer = + seriesRenderer as CandleSeriesRenderer; _calculateSideBySidePositions(candleSeriesRenderer, _chartState); rectPosition = candleSeriesRenderer._rectPosition; count = candleSeriesRenderer._rectCount; } else if (seriesRenderer._seriesType == 'boxandwhisker' && chart.enableSideBySideSeriesPlacement) { final BoxAndWhiskerSeriesRenderer boxAndWhiskerSeriesRenderer = - seriesRenderer; + seriesRenderer as BoxAndWhiskerSeriesRenderer; _calculateSideBySidePositions(boxAndWhiskerSeriesRenderer, _chartState); rectPosition = boxAndWhiskerSeriesRenderer._rectPosition; count = boxAndWhiskerSeriesRenderer._rectCount; } else if (seriesRenderer._seriesType == 'waterfall' && chart.enableSideBySideSeriesPlacement) { - final WaterfallSeriesRenderer waterfallSeriesRenderer = seriesRenderer; + final WaterfallSeriesRenderer waterfallSeriesRenderer = + seriesRenderer as WaterfallSeriesRenderer; _calculateSideBySidePositions(waterfallSeriesRenderer, _chartState); rectPosition = waterfallSeriesRenderer._rectPosition; count = waterfallSeriesRenderer._rectCount; } if (seriesRenderer._seriesType == 'column') { - final ColumnSeries columnSeries = series; + final ColumnSeries columnSeries = series as ColumnSeries; seriesSpacing = chart.enableSideBySideSeriesPlacement ? columnSeries.spacing : 0; - assert(columnSeries.width <= 1, + assert(columnSeries.width == null || columnSeries.width! <= 1, 'The width of the column series must be less than or equal 1.'); - pointSpacing = columnSeries.width; + pointSpacing = columnSeries.width!; } else if (seriesRenderer._seriesType == 'histogram') { - final HistogramSeries histogramSeries = series; + final HistogramSeries histogramSeries = + series as HistogramSeries; seriesSpacing = chart.enableSideBySideSeriesPlacement ? histogramSeries.spacing : 0; - assert(histogramSeries.width <= 1, + assert(histogramSeries.width! <= 1, 'The width of the histogram series must be less than or equal 1.'); - pointSpacing = histogramSeries.width; + pointSpacing = histogramSeries.width!; } else if (seriesRenderer._seriesType == 'stackedcolumn' || seriesRenderer._seriesType == 'stackedcolumn100' || seriesRenderer._seriesType == 'stackedbar' || seriesRenderer._seriesType == 'stackedbar100') { - final _StackedSeriesBase stackedRectSeries = series; + final _StackedSeriesBase stackedRectSeries = + series as _StackedSeriesBase; seriesSpacing = chart.enableSideBySideSeriesPlacement ? stackedRectSeries.spacing : 0; - pointSpacing = stackedRectSeries.width; + pointSpacing = stackedRectSeries.width!; } else if (seriesRenderer._seriesType == 'rangecolumn') { - final RangeColumnSeries rangeColumnSeries = series; + final RangeColumnSeries rangeColumnSeries = + series as RangeColumnSeries; seriesSpacing = chart.enableSideBySideSeriesPlacement ? rangeColumnSeries.spacing : 0; - assert(rangeColumnSeries.width <= 1, + assert(rangeColumnSeries.width == null || rangeColumnSeries.width! <= 1, 'The width of the range column series must be less than or equal 1.'); pointSpacing = rangeColumnSeries.width; } else if (seriesRenderer._seriesType == 'hilo') { - final HiloSeries hiloSeries = series; + final HiloSeries hiloSeries = series as HiloSeries; seriesSpacing = chart.enableSideBySideSeriesPlacement ? hiloSeries.spacing : 0; - pointSpacing = hiloSeries.width; + pointSpacing = hiloSeries.width!; } else if (seriesRenderer._seriesType == 'hiloopenclose') { - final HiloOpenCloseSeries hiloOpenCloseSeries = series; + final HiloOpenCloseSeries hiloOpenCloseSeries = + series as HiloOpenCloseSeries; seriesSpacing = chart.enableSideBySideSeriesPlacement ? hiloOpenCloseSeries.spacing : 0; - pointSpacing = hiloOpenCloseSeries.width; + pointSpacing = hiloOpenCloseSeries.width!; } else if (seriesRenderer._seriesType == 'candle') { - final CandleSeries candleSeries = series; + final CandleSeries candleSeries = series as CandleSeries; seriesSpacing = chart.enableSideBySideSeriesPlacement ? candleSeries.spacing : 0; - pointSpacing = candleSeries.width; + pointSpacing = candleSeries.width!; } else if (seriesRenderer._seriesType == 'boxandwhisker') { - final BoxAndWhiskerSeries boxAndWhiskerSeries = series; + final BoxAndWhiskerSeries boxAndWhiskerSeries = + series as BoxAndWhiskerSeries; seriesSpacing = chart.enableSideBySideSeriesPlacement ? boxAndWhiskerSeries.spacing : 0; - assert(boxAndWhiskerSeries.width <= 1, + assert(boxAndWhiskerSeries.width! <= 1, 'The width of the box plot series must be less than or equal to 1.'); - pointSpacing = boxAndWhiskerSeries.width; + pointSpacing = boxAndWhiskerSeries.width!; } else if (seriesRenderer._seriesType == 'waterfall') { - final WaterfallSeries waterfallSeries = series; + final WaterfallSeries waterfallSeries = + series as WaterfallSeries; seriesSpacing = chart.enableSideBySideSeriesPlacement ? waterfallSeries.spacing : 0; - assert(waterfallSeries.width <= 1, + assert(waterfallSeries.width! <= 1, 'The width of the waterfall series must be less than or equal to 1.'); - pointSpacing = waterfallSeries.width; + pointSpacing = waterfallSeries.width!; } else { - final BarSeries barSeries = series; + final BarSeries barSeries = series as BarSeries; seriesSpacing = chart.enableSideBySideSeriesPlacement ? barSeries.spacing : 0; - assert(barSeries.width <= 1, - 'The width of the bar series must be less than or equal to 1.'); + // assert(barSeries.width == null || barSeries.width! <= 1, + // 'The width of the bar series must be less than or equal to 1.'); pointSpacing = barSeries.width; } final num position = - !chart.enableSideBySideSeriesPlacement ? 0 : rectPosition; - final num rectCount = !chart.enableSideBySideSeriesPlacement ? 1 : count; + !chart.enableSideBySideSeriesPlacement ? 0 : rectPosition!; + final num rectCount = !chart.enableSideBySideSeriesPlacement ? 1 : count!; /// Gets the minimum point delta in series final num minPointsDelta = seriesRenderer._minDelta ?? - _calculateMinPointsDelta(seriesRenderer._xAxisRenderer, + _calculateMinPointsDelta(seriesRenderer._xAxisRenderer!, _chartState._seriesRenderers, _chartState); - final num width = minPointsDelta * pointSpacing; + final num width = minPointsDelta * pointSpacing!; final num location = position / rectCount - 0.5; _VisibleRange doubleRange = _VisibleRange(location, location + (1 / rectCount)); @@ -1022,8 +891,8 @@ _VisibleRange _calculateSideBySideInfo( _ChartLocation _getRotatedTextLocation(double pointX, double pointY, String labelText, TextStyle textStyle, int angle, ChartAxis axis) { if (angle > 0) { - final Size textSize = _measureText(labelText, textStyle); - final Size rotateTextSize = _measureText(labelText, textStyle, angle); + final Size textSize = measureText(labelText, textStyle); + final Size rotateTextSize = measureText(labelText, textStyle, angle); /// label rotation for 0 to 90 pointX += ((rotateTextSize.width - textSize.width).abs() / 2) + @@ -1083,10 +952,10 @@ void _calculateSideBySidePositions( CartesianSeriesRenderer seriesRenderer, SfCartesianChartState _chartState) { final List seriesCollection = _findRectSeriesCollection(_chartState); - num rectCount = 0; - num position; + int rectCount = 0; + num? position; final num seriesLength = seriesCollection.length; - List<_StackingGroup> stackingGroupPos; + List<_StackingGroup>? stackingGroupPos; for (final CartesianSeriesRenderer seriesRenderer in seriesCollection) { if (seriesRenderer is ColumnSeriesRenderer) { seriesRenderer._rectPosition = rectCount++; @@ -1119,13 +988,13 @@ void _calculateSideBySidePositions( } if (seriesRenderer is _StackedSeriesRenderer) { for (int i = 0; i < seriesCollection.length; i++) { - _StackedSeriesBase series; + _StackedSeriesBase? series; if (seriesCollection[i] is _StackedSeriesRenderer) { seriesRenderer = seriesCollection[i]; - series = seriesRenderer._series; + series = seriesRenderer._series as _StackedSeriesBase; } if (seriesRenderer != null && seriesRenderer is _StackedSeriesRenderer) { - final String groupName = series.groupName; + final String groupName = series!.groupName; if (groupName != null) { stackingGroupPos ??= <_StackingGroup>[]; if (stackingGroupPos.isEmpty) { @@ -1158,9 +1027,9 @@ void _calculateSideBySidePositions( if (seriesRenderer._seriesType.contains('stackedcolumn') || seriesRenderer._seriesType.contains('stackedbar')) { for (int i = 0; i < seriesCollection.length; i++) { - _StackedSeriesRenderer seriesRenderer; + _StackedSeriesRenderer? seriesRenderer; if (seriesCollection[i] is _StackedSeriesRenderer) { - seriesRenderer = seriesCollection[i]; + seriesRenderer = seriesCollection[i] as _StackedSeriesRenderer; } if (seriesRenderer != null) { seriesRenderer._rectCount = rectCount; @@ -1173,7 +1042,7 @@ void _calculateSideBySidePositions( List _findSeriesCollection( SfCartesianChartState _chartState, //ignore: unused_element - [bool isRect]) { + [bool? isRect]) { final List seriesRendererCollection = []; for (int xAxisIndex = 0; @@ -1205,8 +1074,10 @@ List _findSeriesCollection( xAxisSeriesRenderer._seriesType.contains('stackedline') || xAxisSeriesRenderer._seriesType == 'histogram' || xAxisSeriesRenderer._seriesType == 'boxandwhisker') && - xAxisSeriesRenderer._visible) { - seriesRendererCollection.add(yAxisSeriesRenderer); + xAxisSeriesRenderer._visible!) { + if (!seriesRendererCollection.contains(yAxisSeriesRenderer)) { + seriesRendererCollection.add(yAxisSeriesRenderer); + } } } } @@ -1259,7 +1130,7 @@ List _findRectSeriesCollection( xAxisSeriesRenderer._seriesType == 'candle' || xAxisSeriesRenderer._seriesType == 'histogram' || xAxisSeriesRenderer._seriesType == 'boxandwhisker') && - xAxisSeriesRenderer._visible) { + xAxisSeriesRenderer._visible!) { if (!seriesRenderCollection.contains(yAxisSeriesRenderer)) { seriesRenderCollection.add(yAxisSeriesRenderer); } @@ -1289,8 +1160,16 @@ Paint _getLinearGradientPaint( return gradientPaint; } +Paint _getShaderPaint(Shader shader) { + Paint shaderPaint; + shaderPaint = Paint() + ..shader = shader + ..style = PaintingStyle.fill; + return shaderPaint; +} + /// It returns the actual label value for tooltip and data label etc -dynamic _getLabelValue(dynamic value, dynamic axis, [int showDigits]) { +dynamic _getLabelValue(dynamic value, dynamic axis, [int? showDigits]) { if (value.toString().split('.').length > 1) { final String str = value.toString(); final List list = str.split('.'); @@ -1307,7 +1186,7 @@ dynamic _getLabelValue(dynamic value, dynamic axis, [int showDigits]) { : value; } final dynamic text = axis is NumericAxis && axis.numberFormat != null - ? axis.numberFormat.format(value) + ? axis.numberFormat!.format(value) : value; return (axis.labelFormat != null && axis.labelFormat != '') ? axis.labelFormat.replaceAll(RegExp('{value}'), text.toString()) @@ -1340,11 +1219,11 @@ double _pointToYValue(bool _requireInvertedAxis, ChartAxisRenderer axisRenderer, } /// To return coefficient-based value -num _coefficientToValue(double coefficient, ChartAxisRenderer axisRenderer) { +double _coefficientToValue(double coefficient, ChartAxisRenderer axisRenderer) { double result; coefficient = axisRenderer._axis.isInversed ? 1 - coefficient : coefficient; - result = axisRenderer._visibleRange.minimum + - (axisRenderer._visibleRange.delta * coefficient); + result = axisRenderer._visibleRange!.minimum + + (axisRenderer._visibleRange!.delta * coefficient); return result; } @@ -1391,7 +1270,7 @@ void _canRepaintChartSeries(SfCartesianChartState _chartState, final CartesianSeries oldWidgetSeries = oldWidgetSeriesRenderer._series; if (series.animationDuration != oldWidgetSeries.animationDuration || - oldWidgetSeriesRenderer._chartState._chartSeries != + oldWidgetSeriesRenderer._chartState!._chartSeries != _chartState._chartSeries || series.color?.value != oldWidgetSeries.color?.value || series.width != oldWidgetSeries.width || @@ -1399,62 +1278,62 @@ void _canRepaintChartSeries(SfCartesianChartState _chartState, series.enableTooltip != oldWidgetSeries.enableTooltip || series.name != oldWidgetSeries.name || series.gradient != oldWidgetSeries.gradient || - seriesRenderer._xAxisRenderer?._visibleRange?.delta != - oldWidgetSeriesRenderer._xAxisRenderer?._visibleRange?.delta || - seriesRenderer._xAxisRenderer?._visibleRange?.maximum != - oldWidgetSeriesRenderer._xAxisRenderer?._visibleRange?.maximum || - seriesRenderer._xAxisRenderer?._visibleRange?.minimum != - oldWidgetSeriesRenderer._xAxisRenderer?._visibleRange?.minimum || - seriesRenderer._xAxisRenderer?._visibleRange?.interval != - oldWidgetSeriesRenderer._xAxisRenderer?._visibleRange?.interval || - seriesRenderer._xAxisRenderer?._axis?.isVisible != - oldWidgetSeriesRenderer._xAxisRenderer?._axis?.isVisible || - seriesRenderer._xAxisRenderer?._bounds != - oldWidgetSeriesRenderer._xAxisRenderer?._bounds || - seriesRenderer._xAxisRenderer?._axis?.isInversed != - oldWidgetSeriesRenderer._xAxisRenderer?._axis?.isInversed || - seriesRenderer._xAxisRenderer?._axis?.desiredIntervals != - oldWidgetSeriesRenderer._xAxisRenderer?._axis?.desiredIntervals || - seriesRenderer._xAxisRenderer?._axis?.enableAutoIntervalOnZooming != + seriesRenderer._xAxisRenderer!._visibleRange?.delta != + oldWidgetSeriesRenderer._xAxisRenderer!._visibleRange?.delta || + seriesRenderer._xAxisRenderer!._visibleRange?.maximum != + oldWidgetSeriesRenderer._xAxisRenderer!._visibleRange?.maximum || + seriesRenderer._xAxisRenderer!._visibleRange?.minimum != + oldWidgetSeriesRenderer._xAxisRenderer!._visibleRange?.minimum || + seriesRenderer._xAxisRenderer!._visibleRange?.interval != + oldWidgetSeriesRenderer._xAxisRenderer!._visibleRange?.interval || + seriesRenderer._xAxisRenderer!._axis.isVisible != + oldWidgetSeriesRenderer._xAxisRenderer!._axis.isVisible || + seriesRenderer._xAxisRenderer!._bounds != + oldWidgetSeriesRenderer._xAxisRenderer!._bounds || + seriesRenderer._xAxisRenderer!._axis.isInversed != + oldWidgetSeriesRenderer._xAxisRenderer!._axis.isInversed || + seriesRenderer._xAxisRenderer!._axis.desiredIntervals != + oldWidgetSeriesRenderer._xAxisRenderer!._axis.desiredIntervals || + seriesRenderer._xAxisRenderer!._axis.enableAutoIntervalOnZooming != oldWidgetSeriesRenderer - ._xAxisRenderer?._axis?.enableAutoIntervalOnZooming || - seriesRenderer._xAxisRenderer?._axis?.opposedPosition != - oldWidgetSeriesRenderer._xAxisRenderer?._axis?.opposedPosition || - seriesRenderer._xAxisRenderer?._orientation != - oldWidgetSeriesRenderer._xAxisRenderer?._orientation || - seriesRenderer._xAxisRenderer?._axis?.plotOffset != - oldWidgetSeriesRenderer._xAxisRenderer?._axis?.plotOffset || - seriesRenderer._xAxisRenderer?._axis?.rangePadding != - oldWidgetSeriesRenderer._xAxisRenderer?._axis?.rangePadding || - seriesRenderer._dataPoints?.length != - oldWidgetSeriesRenderer._dataPoints?.length || - seriesRenderer._yAxisRenderer?._visibleRange?.delta != - oldWidgetSeriesRenderer._yAxisRenderer?._visibleRange?.delta || - seriesRenderer._yAxisRenderer?._visibleRange?.maximum != - oldWidgetSeriesRenderer._yAxisRenderer?._visibleRange?.maximum || - seriesRenderer._yAxisRenderer?._visibleRange?.minimum != - oldWidgetSeriesRenderer._yAxisRenderer?._visibleRange?.minimum || - seriesRenderer._yAxisRenderer?._visibleRange?.interval != - oldWidgetSeriesRenderer._yAxisRenderer?._visibleRange?.interval || - seriesRenderer._yAxisRenderer?._axis?.isVisible != - oldWidgetSeriesRenderer._yAxisRenderer?._axis?.isVisible || - seriesRenderer._yAxisRenderer?._bounds != - oldWidgetSeriesRenderer._yAxisRenderer?._bounds || - seriesRenderer._yAxisRenderer?._axis?.isInversed != - oldWidgetSeriesRenderer._yAxisRenderer?._axis?.isInversed || - seriesRenderer._yAxisRenderer?._axis?.desiredIntervals != - oldWidgetSeriesRenderer._yAxisRenderer?._axis?.desiredIntervals || - seriesRenderer._yAxisRenderer?._axis?.enableAutoIntervalOnZooming != + ._xAxisRenderer!._axis.enableAutoIntervalOnZooming || + seriesRenderer._xAxisRenderer!._axis.opposedPosition != + oldWidgetSeriesRenderer._xAxisRenderer!._axis.opposedPosition || + seriesRenderer._xAxisRenderer!._orientation != + oldWidgetSeriesRenderer._xAxisRenderer!._orientation || + seriesRenderer._xAxisRenderer!._axis.plotOffset != + oldWidgetSeriesRenderer._xAxisRenderer!._axis.plotOffset || + seriesRenderer._xAxisRenderer!._axis.rangePadding != + oldWidgetSeriesRenderer._xAxisRenderer!._axis.rangePadding || + seriesRenderer._dataPoints.length != + oldWidgetSeriesRenderer._dataPoints.length || + seriesRenderer._yAxisRenderer!._visibleRange?.delta != + oldWidgetSeriesRenderer._yAxisRenderer!._visibleRange?.delta || + seriesRenderer._yAxisRenderer!._visibleRange?.maximum != + oldWidgetSeriesRenderer._yAxisRenderer!._visibleRange?.maximum || + seriesRenderer._yAxisRenderer!._visibleRange?.minimum != + oldWidgetSeriesRenderer._yAxisRenderer!._visibleRange?.minimum || + seriesRenderer._yAxisRenderer!._visibleRange?.interval != + oldWidgetSeriesRenderer._yAxisRenderer!._visibleRange?.interval || + seriesRenderer._yAxisRenderer!._axis.isVisible != + oldWidgetSeriesRenderer._yAxisRenderer!._axis.isVisible || + seriesRenderer._yAxisRenderer!._bounds != + oldWidgetSeriesRenderer._yAxisRenderer!._bounds || + seriesRenderer._yAxisRenderer!._axis.isInversed != + oldWidgetSeriesRenderer._yAxisRenderer!._axis.isInversed || + seriesRenderer._yAxisRenderer!._axis.desiredIntervals != + oldWidgetSeriesRenderer._yAxisRenderer!._axis.desiredIntervals || + seriesRenderer._yAxisRenderer!._axis.enableAutoIntervalOnZooming != oldWidgetSeriesRenderer - ._yAxisRenderer?._axis?.enableAutoIntervalOnZooming || - seriesRenderer._yAxisRenderer?._axis?.opposedPosition != - oldWidgetSeriesRenderer._yAxisRenderer?._axis?.opposedPosition || - seriesRenderer._yAxisRenderer?._orientation != - oldWidgetSeriesRenderer._yAxisRenderer?._orientation || - seriesRenderer._yAxisRenderer?._axis?.plotOffset != - oldWidgetSeriesRenderer._yAxisRenderer?._axis?.plotOffset || - seriesRenderer._yAxisRenderer?._axis?.rangePadding != - oldWidgetSeriesRenderer._yAxisRenderer?._axis?.rangePadding || + ._yAxisRenderer!._axis.enableAutoIntervalOnZooming || + seriesRenderer._yAxisRenderer!._axis.opposedPosition != + oldWidgetSeriesRenderer._yAxisRenderer!._axis.opposedPosition || + seriesRenderer._yAxisRenderer!._orientation != + oldWidgetSeriesRenderer._yAxisRenderer!._orientation || + seriesRenderer._yAxisRenderer!._axis.plotOffset != + oldWidgetSeriesRenderer._yAxisRenderer!._axis.plotOffset || + seriesRenderer._yAxisRenderer!._axis.rangePadding != + oldWidgetSeriesRenderer._yAxisRenderer!._axis.rangePadding || series.animationDuration != oldWidgetSeries.animationDuration || series.borderColor != oldWidgetSeries.borderColor || series.borderWidth != oldWidgetSeries.borderWidth || @@ -1471,8 +1350,8 @@ void _canRepaintChartSeries(SfCartesianChartState _chartState, seriesRenderer._maximumY != oldWidgetSeriesRenderer._maximumY || seriesRenderer._minimumX != oldWidgetSeriesRenderer._minimumX || seriesRenderer._minimumY != oldWidgetSeriesRenderer._minimumY || - series.dashArray?.length != oldWidgetSeries.dashArray?.length || - series.dataSource?.length != oldWidgetSeries.dataSource?.length || + series.dashArray.length != oldWidgetSeries.dashArray.length || + series.dataSource.length != oldWidgetSeries.dataSource.length || series.markerSettings.width != oldWidgetSeries.markerSettings.width || series.markerSettings.color?.value != oldWidgetSeries.markerSettings.color?.value || @@ -1496,28 +1375,28 @@ void _canRepaintChartSeries(SfCartesianChartState _chartState, oldWidgetSeries.dataLabelSettings.alignment || series.dataLabelSettings.angle != oldWidgetSeries.dataLabelSettings.angle || - series.dataLabelSettings.textStyle?.color?.value != - oldWidgetSeries.dataLabelSettings.textStyle?.color?.value || - series.dataLabelSettings.textStyle?.fontStyle != - oldWidgetSeries.dataLabelSettings.textStyle?.fontStyle || - series.dataLabelSettings.textStyle?.fontFamily != - oldWidgetSeries.dataLabelSettings.textStyle?.fontFamily || - series.dataLabelSettings.textStyle?.fontSize != - oldWidgetSeries.dataLabelSettings.textStyle?.fontSize || - series.dataLabelSettings.textStyle?.fontWeight != - oldWidgetSeries.dataLabelSettings.textStyle?.fontWeight || - series.dataLabelSettings.borderColor?.value != - oldWidgetSeries.dataLabelSettings.borderColor?.value || + series.dataLabelSettings.textStyle.color?.value != + oldWidgetSeries.dataLabelSettings.textStyle.color?.value || + series.dataLabelSettings.textStyle.fontStyle != + oldWidgetSeries.dataLabelSettings.textStyle.fontStyle || + series.dataLabelSettings.textStyle.fontFamily != + oldWidgetSeries.dataLabelSettings.textStyle.fontFamily || + series.dataLabelSettings.textStyle.fontSize != + oldWidgetSeries.dataLabelSettings.textStyle.fontSize || + series.dataLabelSettings.textStyle.fontWeight != + oldWidgetSeries.dataLabelSettings.textStyle.fontWeight || + series.dataLabelSettings.borderColor.value != + oldWidgetSeries.dataLabelSettings.borderColor.value || series.dataLabelSettings.borderWidth != oldWidgetSeries.dataLabelSettings.borderWidth || - series.dataLabelSettings.margin?.right != - oldWidgetSeries.dataLabelSettings.margin?.right || - series.dataLabelSettings.margin?.bottom != - oldWidgetSeries.dataLabelSettings.margin?.bottom || - series.dataLabelSettings.margin?.top != - oldWidgetSeries.dataLabelSettings.margin?.top || - series.dataLabelSettings.margin?.left != - oldWidgetSeries.dataLabelSettings.margin?.left || + series.dataLabelSettings.margin.right != + oldWidgetSeries.dataLabelSettings.margin.right || + series.dataLabelSettings.margin.bottom != + oldWidgetSeries.dataLabelSettings.margin.bottom || + series.dataLabelSettings.margin.top != + oldWidgetSeries.dataLabelSettings.margin.top || + series.dataLabelSettings.margin.left != + oldWidgetSeries.dataLabelSettings.margin.left || series.dataLabelSettings.borderRadius != oldWidgetSeries.dataLabelSettings.borderRadius) { seriesRenderer._needsRepaint = true; @@ -1585,10 +1464,18 @@ dynamic _getInteractiveTooltipLabel( : value - 1) : value) .round()]; + } else if (axisRenderer is DateTimeCategoryAxisRenderer) { + value = value < 0 ? 0 : value; + value = axisRenderer._labels[(value.round() >= axisRenderer._labels.length + ? (value.round() > axisRenderer._labels.length + ? axisRenderer._labels.length - 1 + : value - 1) + : value) + .round()]; } else if (axisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis _dateTimeAxis = axisRenderer._axis; + final DateTimeAxis _dateTimeAxis = axisRenderer._axis as DateTimeAxis; final DateFormat dateFormat = - _dateTimeAxis.dateFormat ?? axisRenderer._getLabelFormat(axisRenderer); + _dateTimeAxis.dateFormat ?? _getDateTimeLabelFormat(axisRenderer); value = dateFormat.format(DateTime.fromMillisecondsSinceEpoch(value.toInt())); } else { @@ -1599,75 +1486,75 @@ dynamic _getInteractiveTooltipLabel( /// It returns the path of marker shapes Path _getMarkerShapesPath(DataMarkerType markerType, Offset position, Size size, - [CartesianSeriesRenderer seriesRenderer, - int index, - TrackballBehavior trackballBehavior, - Animation animationController]) { - if (seriesRenderer._chart?.onMarkerRender != null && - !seriesRenderer._isMarkerRenderEvent) { + [CartesianSeriesRenderer? seriesRenderer, + int? index, + TrackballBehavior? trackballBehavior, + Animation? animationController]) { + if (seriesRenderer?._chart.onMarkerRender != null && + !seriesRenderer!._isMarkerRenderEvent) { final MarkerRenderArgs event = _triggerMarkerRenderEvent( seriesRenderer, size, markerType, - seriesRenderer._dataPoints[index].visiblePointIndex, - animationController); - markerType = event?.shape; + seriesRenderer._dataPoints[index!].visiblePointIndex!, + animationController)!; + markerType = event.shape; size = Size(event.markerHeight, event.markerWidth); } final Path path = Path(); switch (markerType) { case DataMarkerType.circle: { - _ChartShapeUtils._drawCircle( + ShapeMaker.drawCircle( path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.rectangle: { - _ChartShapeUtils._drawRectangle( + ShapeMaker.drawRectangle( path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.image: { if (seriesRenderer?._series != null) { - _loadMarkerImage(seriesRenderer, trackballBehavior); + _loadMarkerImage(seriesRenderer!, trackballBehavior); } } break; case DataMarkerType.pentagon: { - _ChartShapeUtils._drawPentagon( + ShapeMaker.drawPentagon( path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.verticalLine: { - _ChartShapeUtils._drawVerticalLine( + ShapeMaker.drawVerticalLine( path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.invertedTriangle: { - _ChartShapeUtils._drawInvertedTriangle( + ShapeMaker.drawInvertedTriangle( path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.horizontalLine: { - _ChartShapeUtils._drawHorizontalLine( + ShapeMaker.drawHorizontalLine( path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.diamond: { - _ChartShapeUtils._drawDiamond( + ShapeMaker.drawDiamond( path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.triangle: { - _ChartShapeUtils._drawTriangle( + ShapeMaker.drawTriangle( path, position.dx, position.dy, size.width, size.height); } break; @@ -1681,7 +1568,7 @@ class _StackingInfo { _StackingInfo(this.groupName, this._stackingValues); String groupName; // ignore: prefer_final_fields - List _stackingValues; + List? _stackingValues; } class _StackingGroup { @@ -1694,12 +1581,13 @@ class _StackingGroup { /// To load marker image // ignore: avoid_void_async void _loadMarkerImage(CartesianSeriesRenderer seriesRenderer, - TrackballBehavior trackballBehavior) async { - final XyDataSeries series = seriesRenderer._series; + TrackballBehavior? trackballBehavior) async { + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; if ((trackballBehavior != null && trackballBehavior.markerSettings != null && - trackballBehavior.markerSettings.shape == DataMarkerType.image && - trackballBehavior.markerSettings.image != null) || + trackballBehavior.markerSettings!.shape == DataMarkerType.image && + trackballBehavior.markerSettings!.image != null) || (series.markerSettings != null && (series.markerSettings.isVisible || seriesRenderer._seriesType == 'scatter') && @@ -1713,15 +1601,15 @@ void _loadMarkerImage(CartesianSeriesRenderer seriesRenderer, /// It returns the chart location of the annotation _ChartLocation _getAnnotationLocation( CartesianChartAnnotation annotation, SfCartesianChartState _chartState) { - final String xAxisName = annotation.xAxisName; - final String yAxisName = annotation.yAxisName; - ChartAxisRenderer xAxisRenderer, yAxisRenderer; - num xValue; + final String? xAxisName = annotation.xAxisName; + final String? yAxisName = annotation.yAxisName; + ChartAxisRenderer? xAxisRenderer, yAxisRenderer; + num? xValue; Rect axisClipRect; - _ChartLocation location; + _ChartLocation? location; if (annotation.coordinateUnit == CoordinateUnit.logicalPixel) { location = annotation.region == AnnotationRegion.chart - ? _ChartLocation(annotation.x, annotation.y) + ? _ChartLocation(annotation.x.toDouble(), annotation.y.toDouble()) : _ChartLocation( _chartState._chartAxis._axisClipRect.left + annotation.x, _chartState._chartAxis._axisClipRect.top + annotation.y); @@ -1737,7 +1625,7 @@ _ChartLocation _getAnnotationLocation( if (xAxisRenderer is CategoryAxisRenderer) { if (annotation.x != null && num.tryParse(annotation.x.toString()) != null) { - xValue = num.tryParse(annotation.x.toString()); + xValue = num.tryParse(annotation.x.toString())!; } else if (xAxisRenderer._labels.length > 0) { xValue = xAxisRenderer._labels.indexOf(annotation.x); } @@ -1745,6 +1633,18 @@ _ChartLocation _getAnnotationLocation( xValue = annotation.x is DateTime ? (annotation.x).millisecondsSinceEpoch : annotation.x; + } else if (xAxisRenderer is DateTimeCategoryAxisRenderer) { + if (annotation.x != null && + num.tryParse(annotation.x.toString()) != null) { + xValue = num.tryParse(annotation.x.toString())!; + } else { + xValue = annotation.x is num + ? annotation.x + : (annotation.x is DateTime + ? xAxisRenderer._labels + .indexOf(xAxisRenderer._dateFormat.format(annotation.x)) + : xAxisRenderer._labels.indexOf(annotation.x)); + } } else { xValue = annotation.x; } @@ -1759,11 +1659,11 @@ _ChartLocation _getAnnotationLocation( _chartState._chartAxis._axisClipRect, Offset( xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); - location = _calculatePoint(xValue, annotation.y, xAxisRenderer, + location = _calculatePoint(xValue!, annotation.y, xAxisRenderer, yAxisRenderer, _chartState._requireInvertedAxis, null, axisClipRect); } } - return location; + return location!; } /// Draw tooltip arrow head @@ -1907,8 +1807,8 @@ Rect _validateRectBounds(Rect tooltipRect, Rect boundary) { /// To render a rect for stacked series void _renderStackingRectSeries( - Paint fillPaint, - Paint strokePaint, + Paint? fillPaint, + Paint? strokePaint, Path path, double animationFactor, CartesianSeriesRenderer seriesRenderer, @@ -1916,11 +1816,12 @@ void _renderStackingRectSeries( RRect segmentRect, CartesianChartPoint _currentPoint, int currentSegmentIndex) { - final _StackedSeriesBase series = seriesRenderer._series; + final _StackedSeriesBase series = + seriesRenderer._series as _StackedSeriesBase; if (seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( seriesRenderer._segments[currentSegmentIndex], seriesRenderer._chart); } if (fillPaint != null) { @@ -1932,12 +1833,13 @@ void _renderStackingRectSeries( seriesRenderer, animationFactor, _currentPoint, - seriesRenderer._chartState) + seriesRenderer._chartState!) : canvas.drawRRect(segmentRect, fillPaint); } if (strokePaint != null) { if (series.dashArray[0] != 0 && series.dashArray[1] != 0) { - final XyDataSeries _series = seriesRenderer._series; + final XyDataSeries _series = + seriesRenderer._series as XyDataSeries; _drawDashedLine(canvas, _series.dashArray, strokePaint, path); } else { series.animationDuration > 0 @@ -1948,7 +1850,7 @@ void _renderStackingRectSeries( seriesRenderer, animationFactor, _currentPoint, - seriesRenderer._chartState) + seriesRenderer._chartState!) : canvas.drawRRect(segmentRect, strokePaint); } } @@ -1979,13 +1881,14 @@ void _drawStackedAreaPath( Rect _pathRect; dynamic stackedAreaSegment; _pathRect = _path.getBounds(); - final XyDataSeries _series = seriesRenderer._series; + final XyDataSeries _series = + seriesRenderer._series as XyDataSeries; stackedAreaSegment = seriesRenderer._segments[0]; stackedAreaSegment._pathRect = _pathRect; if (seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer selectionBehaviorRenderer = + final SelectionBehaviorRenderer? selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer._checkWithSelectionState( + selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( seriesRenderer._segments[0], seriesRenderer._chart); } canvas.drawPath( @@ -2001,8 +1904,14 @@ void _drawStackedAreaPath( } /// Render stacked line series -void _renderStackedLineSeries(_StackedSeriesBase series, - Canvas canvas, Paint strokePaint, num x1, num y1, num x2, num y2) { +void _renderStackedLineSeries( + _StackedSeriesBase series, + Canvas canvas, + Paint strokePaint, + double x1, + double y1, + double x2, + double y2) { final Path path = Path(); path.moveTo(x1, y1); path.lineTo(x2, y2); @@ -2014,7 +1923,7 @@ void _stackedAreaPainter( Canvas canvas, dynamic seriesRenderer, SfCartesianChartState _chartState, - Animation seriesAnimation, + Animation? seriesAnimation, Animation chartElementAnimation, _PainterKey painterKey) { Rect clipRect, axisClipRect; @@ -2022,14 +1931,14 @@ void _stackedAreaPainter( final SfCartesianChart chart = _chartState._chart; seriesRenderer._storeSeriesProperties(_chartState, seriesIndex); double animationFactor; - final num crossesAt = _getCrossesAtValue(seriesRenderer, _chartState); + final num? crossesAt = _getCrossesAtValue(seriesRenderer, _chartState); /// Clip rect will be added for series. - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { final dynamic series = seriesRenderer._series; canvas.save(); axisClipRect = _calculatePlotOffset( - seriesRenderer._chartState._chartAxis._axisClipRect, + seriesRenderer._chartState!._chartAxis._axisClipRect, Offset(seriesRenderer._xAxisRenderer?._axis?.plotOffset, seriesRenderer._yAxisRenderer?._axis?.plotOffset)); canvas.clipRect(axisClipRect); @@ -2038,16 +1947,16 @@ void _stackedAreaPainter( ((!(_chartState._widgetNeedUpdate || _chartState._isLegendToggled) || !_chartState._oldSeriesKeys.contains(series.key)) && series.animationDuration > 0)) { - _performLinearAnimation(_chartState, seriesRenderer._xAxisRenderer._axis, + _performLinearAnimation(_chartState, seriesRenderer._xAxisRenderer!._axis, canvas, animationFactor); } final Path _path = Path(), _strokePath = Path(); - final Rect rect = seriesRenderer._chartState._chartAxis._axisClipRect; + final Rect rect = seriesRenderer._chartState!._chartAxis._axisClipRect; _ChartLocation point1, point2; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer, - yAxisRenderer = seriesRenderer._yAxisRenderer; - CartesianChartPoint point; + final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!, + yAxisRenderer = seriesRenderer._yAxisRenderer!; + CartesianChartPoint? point; final dynamic _series = seriesRenderer._series; final List _points = []; if (seriesRenderer._dataPoints.isNotEmpty) { @@ -2058,32 +1967,32 @@ void _stackedAreaPainter( seriesRendererCollection = _findSeriesCollection(_chartState); point1 = _calculatePoint( seriesRenderer._dataPoints[0].xValue, - math_lib.max(yAxisRenderer._visibleRange.minimum, + math_lib.max(yAxisRenderer._visibleRange!.minimum, crossesAt ?? stackedValues.startValues[0]), xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, _series, rect); _path.moveTo(point1.x, point1.y); _strokePath.moveTo(point1.x, point1.y); if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; pointIndex < seriesRenderer._dataPoints.length; pointIndex++) { - seriesRenderer._calculateRegionData( - _chartState, seriesRenderer, seriesIndex, point, pointIndex); point = seriesRenderer._dataPoints[pointIndex]; + seriesRenderer._calculateRegionData( + _chartState, seriesRenderer, seriesIndex, point!, pointIndex); if (point.isVisible) { point1 = _calculatePoint( seriesRenderer._dataPoints[pointIndex].xValue, stackedValues.endValues[pointIndex], xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, _series, rect); _points.add(Offset(point1.x, point1.y)); @@ -2097,7 +2006,7 @@ void _stackedAreaPainter( crossesAt ?? stackedValues.startValues[j], xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, _series, rect); _path.lineTo(point2.x, point2.y); @@ -2115,7 +2024,7 @@ void _stackedAreaPainter( crossesAt ?? stackedValues.startValues[pointIndex + 1], xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, _series, rect); _path.moveTo(point1.x, point1.y); @@ -2142,7 +2051,7 @@ void _stackedAreaPainter( crossesAt ?? stackedValues.startValues[j], xAxisRenderer, yAxisRenderer, - seriesRenderer._chartState._requireInvertedAxis, + seriesRenderer._chartState!._requireInvertedAxis, _series, rect); _path.lineTo(point2.x, point2.y); @@ -2167,19 +2076,19 @@ void _stackedAreaPainter( clipRect = _calculatePlotOffset( Rect.fromLTRB( - seriesRenderer._chartState._chartAxis._axisClipRect.left - + seriesRenderer._chartState!._chartAxis._axisClipRect.left - _series.markerSettings.width, - seriesRenderer._chartState._chartAxis._axisClipRect.top - + seriesRenderer._chartState!._chartAxis._axisClipRect.top - _series.markerSettings.height, - seriesRenderer._chartState._chartAxis._axisClipRect.right + + seriesRenderer._chartState!._chartAxis._axisClipRect.right + _series.markerSettings.width, - seriesRenderer._chartState._chartAxis._axisClipRect.bottom + + seriesRenderer._chartState!._chartAxis._axisClipRect.bottom + _series.markerSettings.height), Offset(seriesRenderer._xAxisRenderer?._axis?.plotOffset, seriesRenderer._yAxisRenderer?._axis?.plotOffset)); canvas.restore(); if ((_series.animationDuration <= 0 || - !_chartState._initialRender || + !_chartState._initialRender! || animationFactor >= _chartState._seriesDurationFactor) && (_series.markerSettings.isVisible || _series.dataLabelSettings.isVisible)) { @@ -2209,7 +2118,7 @@ CartesianSeriesRenderer _getPreviousSeriesRenderer( /// Rect painter for stacked series void _stackedRectPainter(Canvas canvas, dynamic seriesRenderer, SfCartesianChartState _chartState, _PainterKey painterKey) { - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { canvas.save(); Rect clipRect, axisClipRect; CartesianChartPoint point; @@ -2235,7 +2144,7 @@ void _stackedRectPainter(Canvas canvas, dynamic seriesRenderer, canvas.clipRect(axisClipRect); int segmentIndex = -1; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; @@ -2265,7 +2174,7 @@ void _stackedRectPainter(Canvas canvas, dynamic seriesRenderer, seriesRenderer._yAxisRenderer?._axis?.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - !_chartState._initialRender || + !_chartState._initialRender! || animationFactor >= _chartState._seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { @@ -2283,18 +2192,18 @@ void _stackedRectPainter(Canvas canvas, dynamic seriesRenderer, void _stackedLinePainter( Canvas canvas, dynamic seriesRenderer, - Animation seriesAnimation, + Animation? seriesAnimation, SfCartesianChartState _chartState, Animation chartElementAnimation, _PainterKey painterKey) { Rect clipRect; double animationFactor; - if (seriesRenderer._visible) { + if (seriesRenderer._visible!) { final XyDataSeries series = seriesRenderer._series; canvas.save(); animationFactor = seriesAnimation != null ? seriesAnimation.value : 1; final int seriesIndex = painterKey.index; - _StackedValues stackedValues; + _StackedValues? stackedValues; seriesRenderer._storeSeriesProperties(_chartState, seriesIndex); if (seriesRenderer is _StackedSeriesRenderer && seriesRenderer._stackingValues.isNotEmpty) { @@ -2303,7 +2212,7 @@ void _stackedLinePainter( /// Clip rect will be added for series. final Rect axisClipRect = _calculatePlotOffset( - seriesRenderer._chartState._chartAxis._axisClipRect, + seriesRenderer._chartState!._chartAxis._axisClipRect, Offset(seriesRenderer._xAxisRenderer?._axis?.plotOffset, seriesRenderer._yAxisRenderer?._axis?.plotOffset)); canvas.clipRect(axisClipRect); @@ -2311,15 +2220,18 @@ void _stackedLinePainter( ((!(_chartState._widgetNeedUpdate || _chartState._isLegendToggled) || !_chartState._oldSeriesKeys.contains(series.key)) && series.animationDuration > 0)) { - _performLinearAnimation(_chartState, seriesRenderer._xAxisRenderer._axis, + _performLinearAnimation(_chartState, seriesRenderer._xAxisRenderer!._axis, canvas, animationFactor); } int segmentIndex = -1; - double currentCummulativePos, nextCummulativePos; - CartesianChartPoint startPoint, endPoint, currentPoint, _nextPoint; + double? currentCummulativePos, nextCummulativePos; + CartesianChartPoint? startPoint, + endPoint, + currentPoint, + _nextPoint; if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.isNotEmpty) { + seriesRenderer._visibleDataPoints!.isNotEmpty) { seriesRenderer._visibleDataPoints = >[]; } for (int pointIndex = 0; @@ -2328,7 +2240,7 @@ void _stackedLinePainter( currentPoint = seriesRenderer._dataPoints[pointIndex]; seriesRenderer._calculateRegionData( _chartState, seriesRenderer, seriesIndex, currentPoint, pointIndex); - if ((currentPoint.isVisible && !currentPoint.isGap) && + if ((currentPoint!.isVisible && !currentPoint.isGap) && startPoint == null && stackedValues != null) { startPoint = currentPoint; @@ -2336,9 +2248,9 @@ void _stackedLinePainter( } if (pointIndex + 1 < seriesRenderer._dataPoints.length) { _nextPoint = seriesRenderer._dataPoints[pointIndex + 1]; - if (startPoint != null && _nextPoint.isGap) { + if (startPoint != null && _nextPoint!.isGap) { startPoint = null; - } else if (_nextPoint.isVisible && + } else if (_nextPoint!.isVisible && !_nextPoint.isGap && stackedValues != null) { endPoint = _nextPoint; @@ -2355,26 +2267,26 @@ void _stackedLinePainter( segmentIndex += 1, seriesIndex, animationFactor, - currentCummulativePos, - nextCummulativePos)); + currentCummulativePos!, + nextCummulativePos!)); endPoint = startPoint = null; } } clipRect = _calculatePlotOffset( Rect.fromLTRB( - seriesRenderer._chartState._chartAxis._axisClipRect.left - + seriesRenderer._chartState!._chartAxis._axisClipRect.left - series.markerSettings.width, - seriesRenderer._chartState._chartAxis._axisClipRect.top - + seriesRenderer._chartState!._chartAxis._axisClipRect.top - series.markerSettings.height, - seriesRenderer._chartState._chartAxis._axisClipRect.right + + seriesRenderer._chartState!._chartAxis._axisClipRect.right + series.markerSettings.width, - seriesRenderer._chartState._chartAxis._axisClipRect.bottom + + seriesRenderer._chartState!._chartAxis._axisClipRect.bottom + series.markerSettings.height), Offset(seriesRenderer._xAxisRenderer?._axis?.plotOffset, seriesRenderer._yAxisRenderer?._axis?.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - !_chartState._initialRender || + !_chartState._initialRender! || animationFactor >= _chartState._seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { @@ -2389,18 +2301,18 @@ void _stackedLinePainter( } /// To find MonotonicSpline -List _getMonotonicSpline(List xValues, List yValues, - List yCoef, int dataCount, List dx) { +List? _getMonotonicSpline(List xValues, List yValues, + List yCoef, int dataCount, List dx) { final int count = dataCount; int index = -1; - final List slope = List(count - 1); - final List coefficient = List(count); + final List slope = List.filled(count - 1, null); + final List coefficient = List.filled(count, null); for (int i = 0; i < count - 1; i++) { dx[i] = xValues[i + 1] - xValues[i]; - slope[i] = (yValues[i + 1] - yValues[i]) / dx[i]; - if (slope[i].toDouble() == double.infinity) { + slope[i] = (yValues[i + 1] - yValues[i]) / dx[i]!; + if (slope[i]!.toDouble() == double.infinity) { slope[i] = 0; } } @@ -2415,15 +2327,15 @@ List _getMonotonicSpline(List xValues, List yValues, for (int i = 0; i < dx.length - 1; i++) { if (slope.length > i + 1) { - final num m = slope[i], next = slope[i + 1]; + final num m = slope[i]!, next = slope[i + 1]!; if (m * next <= 0) { coefficient[++index] = 0; } else { if (dx[i] == 0) { coefficient[++index] = 0; } else { - final double firstPoint = dx[i].toDouble(), - _nextPoint = dx[i + 1].toDouble(); + final double firstPoint = dx[i]!.toDouble(), + _nextPoint = dx[i + 1]!.toDouble(); final double interPoint = firstPoint + _nextPoint; coefficient[++index] = 3 * interPoint / @@ -2443,8 +2355,8 @@ List _getMonotonicSpline(List xValues, List yValues, } /// To find CardinalSpline -List _getCardinalSpline(List xValues, List yValues, - List yCoef, int dataCount, double tension) { +List _getCardinalSpline(List xValues, List yValues, + List yCoef, int dataCount, double tension) { if (tension < 0.1) { tension = 0; } else if (tension > 1) { @@ -2453,7 +2365,7 @@ List _getCardinalSpline(List xValues, List yValues, final int count = dataCount; - final List tangentsX = List(count); + final List tangentsX = List.filled(count, null); for (int i = 0; i < count; i++) { if (i == 0 && xValues.length > 2) { @@ -2474,13 +2386,16 @@ List _getCardinalSpline(List xValues, List yValues, } /// To find NaturalSpline -List _naturalSpline(List xValues, List yValues, List yCoeff, - int dataCount, SplineType splineType) { +List _naturalSpline(List xValues, List yValues, + List yCoeff, int dataCount, SplineType? splineType) { const double a = 6; final int count = dataCount; - final List yCoef = List(count); + int i, k; + final List yCoef = List.filled(count, null); + double d1, d2, d3, dy1, dy2; + num p; - final List u = List(count); + final List u = List.filled(count, null); if (splineType == SplineType.clamped && xValues.length > 1) { u[0] = 0.5; final num d0 = (xValues[1] - xValues[0]) / (yValues[1] - yValues[0]); @@ -2504,7 +2419,7 @@ List _naturalSpline(List xValues, List yValues, List yCoeff, yCoef[count - 1] = 0; } - for (int i = 1; i < count - 1; i++) { + for (i = 1; i < count - 1; i++) { yCoef[i] = 0; if ((yValues[i + 1] != double.nan) && (yValues[i - 1] != double.nan) && @@ -2515,27 +2430,27 @@ List _naturalSpline(List xValues, List yValues, List yCoeff, xValues[i - 1] != null && xValues[i] != null && yValues[i] != null) { - final double d1 = xValues[i].toDouble() - xValues[i - 1].toDouble(); - final double d2 = xValues[i + 1].toDouble() - xValues[i - 1].toDouble(); - final double d3 = xValues[i + 1].toDouble() - xValues[i].toDouble(); - final double dy1 = yValues[i + 1].toDouble() - yValues[i].toDouble(); - final double dy2 = yValues[i].toDouble() - yValues[i - 1].toDouble(); + d1 = xValues[i].toDouble() - xValues[i - 1].toDouble(); + d2 = xValues[i + 1].toDouble() - xValues[i - 1].toDouble(); + d3 = xValues[i + 1].toDouble() - xValues[i].toDouble(); + dy1 = yValues[i + 1].toDouble() - yValues[i].toDouble(); + dy2 = yValues[i].toDouble() - yValues[i - 1].toDouble(); if (xValues[i] == xValues[i - 1] || xValues[i] == xValues[i + 1]) { yCoef[i] = 0; u[i] = 0; } else { - final num p = 1 / ((d1 * yCoef[i - 1]) + (2 * d2)); + p = 1 / ((d1 * yCoef[i - 1]!) + (2 * d2)); yCoef[i] = -p * d3; if (d1 != null && u[i - 1] != null) { - u[i] = p * ((a * ((dy1 / d3) - (dy2 / d1))) - (d1 * u[i - 1])); + u[i] = p * ((a * ((dy1 / d3) - (dy2 / d1))) - (d1 * u[i - 1]!)); } } } } - for (int k = count - 2; k >= 0; k--) { + for (k = count - 2; k >= 0; k--) { if (u[k] != null && yCoef[k] != null && yCoef[k + 1] != null) { - yCoef[k] = (yCoef[k] * yCoef[k + 1]) + u[k]; + yCoef[k] = (yCoef[k]! * yCoef[k + 1]!) + u[k]!; } } @@ -2544,62 +2459,107 @@ List _naturalSpline(List xValues, List yValues, List yCoeff, } /// To find Monotonic ControlPoints -List _calculateMonotonicControlPoints( +List _calculateMonotonicControlPoints( double pointX, double pointY, double pointX1, double pointY1, double coefficientY, double coefficientY1, - double dx) { + double dx, + List controlPoints) { final num value = dx / 3; - final List values = List(4); + final List values = List.filled(4, null); values[0] = pointX + value; values[1] = pointY + (coefficientY * value); values[2] = pointX1 - value; values[3] = pointY1 - (coefficientY1 * value); - return values; + controlPoints.add(Offset(values[0]!, values[1]!)); + controlPoints.add(Offset(values[2]!, values[3]!)); + return controlPoints; } /// To find Cardinal ControlPoints -List _calculateCardinalControlPoints(double pointX, double pointY, - double pointX1, double pointY1, double coefficientY, double coefficientY1) { - final List values = List(4); +List _calculateCardinalControlPoints( + double pointX, + double pointY, + double pointX1, + double pointY1, + double coefficientY, + double coefficientY1, + CartesianSeriesRenderer seriesRenderer, + List controlPoints) { + double yCoefficient = coefficientY; + double y1Coefficient = coefficientY1; + if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) { + yCoefficient = coefficientY / dateTimeInterval(seriesRenderer); + y1Coefficient = coefficientY1 / dateTimeInterval(seriesRenderer); + } + final List values = List.filled(4, null); values[0] = pointX + (coefficientY / 3); - values[1] = pointY + (coefficientY / 3); + values[1] = pointY + (yCoefficient / 3); values[2] = pointX1 - (coefficientY1 / 3); - values[3] = pointY1 - (coefficientY1 / 3); - return values; + values[3] = pointY1 - (y1Coefficient / 3); + controlPoints.add(Offset(values[0]!, values[1]!)); + controlPoints.add(Offset(values[2]!, values[3]!)); + return controlPoints; +} + +/// calculate the dateTime intervals for cardinal spline type +num dateTimeInterval(CartesianSeriesRenderer seriesRenderer) { + final DateTimeAxis xAxis = + seriesRenderer._xAxisRenderer!._axis as DateTimeAxis; + final DateTimeIntervalType _actualIntervalType = xAxis.intervalType; + num intervalInMilliseconds; + if (_actualIntervalType == DateTimeIntervalType.years) { + intervalInMilliseconds = 365 * 24 * 60 * 60 * 1000; + } else if (_actualIntervalType == DateTimeIntervalType.months) { + intervalInMilliseconds = 30 * 24 * 60 * 60 * 1000; + } else if (_actualIntervalType == DateTimeIntervalType.days) { + intervalInMilliseconds = 24 * 60 * 60 * 1000; + } else if (_actualIntervalType == DateTimeIntervalType.hours) { + intervalInMilliseconds = 60 * 60 * 1000; + } else if (_actualIntervalType == DateTimeIntervalType.minutes) { + intervalInMilliseconds = 60 * 1000; + } else if (_actualIntervalType == DateTimeIntervalType.seconds) { + intervalInMilliseconds = 1000; + } else { + intervalInMilliseconds = 30 * 24 * 60 * 60 * 1000; + } + return intervalInMilliseconds; } /// to trigger Marker event -MarkerRenderArgs _triggerMarkerRenderEvent( +MarkerRenderArgs? _triggerMarkerRenderEvent( CartesianSeriesRenderer seriesRenderer, Size size, DataMarkerType markerType, int pointIndex, - Animation animationController) { - MarkerRenderArgs markerargs; - final num seriesIndex = seriesRenderer - ._chartState._chartSeries.visibleSeriesRenderers + Animation? animationController) { + MarkerRenderArgs? markerargs; + final int seriesIndex = seriesRenderer + ._chartState!._chartSeries.visibleSeriesRenderers .indexOf(seriesRenderer); - final XyDataSeries series = seriesRenderer._series; + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; final MarkerSettingsRenderer markerSettingsRenderer = - seriesRenderer._markerSettingsRenderer; + seriesRenderer._markerSettingsRenderer!; markerSettingsRenderer._color = series.markerSettings.color; if (seriesRenderer._chart.onMarkerRender != null) { markerargs = MarkerRenderArgs(pointIndex, seriesIndex, - seriesRenderer._visibleDataPoints[pointIndex].overallDataPointIndex); + seriesRenderer._visibleDataPoints![pointIndex].overallDataPointIndex!); markerargs.markerHeight = size.height; markerargs.markerWidth = size.width; markerargs.shape = markerType; markerargs.color = markerSettingsRenderer._color; markerargs.borderColor = markerSettingsRenderer._borderColor; markerargs.borderWidth = markerSettingsRenderer._borderWidth; - if ((animationController?.value == 1.0 && - animationController?.status == AnimationStatus.completed) || - seriesRenderer._animationController.duration.inMilliseconds == 0) { - seriesRenderer._chart.onMarkerRender(markerargs); + if (animationController == null || + ((animationController.value == 1.0 && + animationController.status == AnimationStatus.completed) || + seriesRenderer._animationController.duration!.inMilliseconds == + 0)) { + seriesRenderer._chart.onMarkerRender!(markerargs); size = Size(markerargs.markerWidth, markerargs.markerHeight); markerType = markerargs.shape; markerSettingsRenderer._color = markerargs.color; @@ -2611,15 +2571,13 @@ MarkerRenderArgs _triggerMarkerRenderEvent( } /// To find Natural ControlPoints -List _calculateControlPoints(List xValues, List yValues, - double yCoef, double nextyCoef, int i) { - final List controlPoints = List(4); - final double yCoeff1 = yCoef; - final double yCoeff2 = nextyCoef; +List _calculateControlPoints(List xValues, List yValues, + double yCoef, double nextyCoef, int i, List controlPoints) { + final List values = List.filled(4, null); final double x = xValues[i].toDouble(); - final double y = yValues[i].toDouble(); + final double y = yValues[i]!.toDouble(); final double nextX = xValues[i + 1].toDouble(); - final double nextY = yValues[i + 1].toDouble(); + final double nextY = yValues[i + 1]!.toDouble(); const double oneThird = 1 / 3.0; double deltaX2 = nextX - x; deltaX2 = deltaX2 * deltaX2; @@ -2628,34 +2586,31 @@ List _calculateControlPoints(List xValues, List yValues, final double dy1 = (2 * y) + nextY; final double dy2 = y + (2 * nextY); final double y1 = - oneThird * (dy1 - (oneThird * deltaX2 * (yCoeff1 + (0.5 * yCoeff2)))); + oneThird * (dy1 - (oneThird * deltaX2 * (yCoef + (0.5 * nextyCoef)))); final double y2 = - oneThird * (dy2 - (oneThird * deltaX2 * ((0.5 * yCoeff1) + yCoeff2))); - final num startControlPointsX = dx1 * oneThird; - final num startControlPointsY = y1; - final num endControlPointsX = dx2 * oneThird; - final num endControlPointsY = y2; - controlPoints[0] = startControlPointsX; - controlPoints[1] = startControlPointsY; - controlPoints[2] = endControlPointsX; - controlPoints[3] = endControlPointsY; + oneThird * (dy2 - (oneThird * deltaX2 * ((0.5 * yCoef) + nextyCoef))); + values[0] = dx1 * oneThird; + values[1] = y1; + values[2] = dx2 * oneThird; + values[3] = y2; + controlPoints.add(Offset(values[0]!, values[1]!)); + controlPoints.add(Offset(values[2]!, values[3]!)); return controlPoints; } /// To calculate spline area control points void _calculateSplineAreaControlPoints(CartesianSeriesRenderer seriesRenderer) { final dynamic series = seriesRenderer._series; - List yCoef = []; - List lowCoef = []; - List highCoef = []; + List? yCoef = []; + List? lowCoef = []; + List? highCoef = []; final List xValues = []; final List yValues = []; final List highValues = []; final List lowValues = []; - SplineType splineType; - splineType = series.splineType; - - for (int i = 0; i < seriesRenderer._dataPoints.length; i++) { + final SplineType? splineType = series.splineType; + int i; + for (i = 0; i < seriesRenderer._dataPoints.length; i++) { xValues.add(seriesRenderer._dataPoints[i].xValue); if (seriesRenderer is SplineAreaSeriesRenderer || seriesRenderer is SplineSeriesRenderer) { @@ -2667,7 +2622,7 @@ void _calculateSplineAreaControlPoints(CartesianSeriesRenderer seriesRenderer) { } if (xValues.isNotEmpty) { - final List dx = List(xValues.length - 1); + final List dx = List.filled(xValues.length - 1, null); /// Check the type of spline if (splineType == SplineType.monotonic) { @@ -2708,8 +2663,15 @@ void _calculateSplineAreaControlPoints(CartesianSeriesRenderer seriesRenderer) { _updateSplineAreaControlPoints( seriesRenderer, splineType, xValues, yValues, yCoef, dx); } else { - _findSplineRangeAreaControlPoint(seriesRenderer, splineType, xValues, - lowValues, highValues, dx, lowCoef, highCoef); + _findSplineRangeAreaControlPoint( + seriesRenderer as SplineRangeAreaSeriesRenderer, + splineType, + xValues, + lowValues, + highValues, + dx, + lowCoef, + highCoef); } } } @@ -2717,59 +2679,55 @@ void _calculateSplineAreaControlPoints(CartesianSeriesRenderer seriesRenderer) { /// To update the dynamic points of the spline area void _updateSplineAreaControlPoints( dynamic seriesRenderer, - SplineType splineType, + SplineType? splineType, List xValues, List yValues, - List yCoef, - List dx) { - for (int pointIndex = 0; pointIndex < xValues.length - 1; pointIndex++) { - List controlPoints = []; - seriesRenderer._drawPoints = <_ControlPoints>[]; + List? yCoef, + List? dx) { + double x, y, nextX, nextY; + int pointIndex; + List controlPoints; + for (pointIndex = 0; pointIndex < xValues.length - 1; pointIndex++) { + controlPoints = []; if (xValues[pointIndex] != null && yValues[pointIndex] != null && xValues[pointIndex + 1] != null && yValues[pointIndex + 1] != null) { - final double x = xValues[pointIndex].toDouble(); - final double y = yValues[pointIndex].toDouble(); - final double nextX = xValues[pointIndex + 1].toDouble(); - final double nextY = yValues[pointIndex + 1].toDouble(); + x = xValues[pointIndex].toDouble(); + y = yValues[pointIndex].toDouble(); + nextX = xValues[pointIndex + 1].toDouble(); + nextY = yValues[pointIndex + 1].toDouble(); if (splineType == SplineType.monotonic) { controlPoints = _calculateMonotonicControlPoints( x, y, nextX, nextY, - yCoef[pointIndex].toDouble(), - yCoef[pointIndex + 1].toDouble(), - dx[pointIndex].toDouble()); - seriesRenderer._drawPoints - .add(_ControlPoints(controlPoints[0], controlPoints[1])); - seriesRenderer._drawPoints - .add(_ControlPoints(controlPoints[2], controlPoints[3])); - seriesRenderer._drawControlPoints - .add(_ListControlPoints(seriesRenderer._drawPoints)); + yCoef![pointIndex]!.toDouble(), + yCoef[pointIndex + 1]!.toDouble(), + dx![pointIndex]!.toDouble(), + controlPoints); + seriesRenderer._drawControlPoints.add(controlPoints); } else if (splineType == SplineType.cardinal) { - controlPoints = _calculateCardinalControlPoints(x, y, nextX, nextY, - yCoef[pointIndex].toDouble(), yCoef[pointIndex + 1].toDouble()); - seriesRenderer._drawPoints - .add(_ControlPoints(controlPoints[0], controlPoints[1])); - seriesRenderer._drawPoints - .add(_ControlPoints(controlPoints[2], controlPoints[3])); - seriesRenderer._drawControlPoints - .add(_ListControlPoints(seriesRenderer._drawPoints)); + controlPoints = _calculateCardinalControlPoints( + x, + y, + nextX, + nextY, + yCoef![pointIndex]!.toDouble(), + yCoef[pointIndex + 1]!.toDouble(), + seriesRenderer, + controlPoints); + seriesRenderer._drawControlPoints.add(controlPoints); } else { controlPoints = _calculateControlPoints( xValues, yValues, - yCoef[pointIndex].toDouble(), - yCoef[pointIndex + 1].toDouble(), - pointIndex); - seriesRenderer._drawPoints - .add(_ControlPoints(controlPoints[0], controlPoints[1])); - seriesRenderer._drawPoints - .add(_ControlPoints(controlPoints[2], controlPoints[3])); - seriesRenderer._drawControlPoints - .add(_ListControlPoints(seriesRenderer._drawPoints)); + yCoef![pointIndex]!.toDouble(), + yCoef[pointIndex + 1]!.toDouble(), + pointIndex, + controlPoints); + seriesRenderer._drawControlPoints.add(controlPoints); } } } @@ -2778,143 +2736,97 @@ void _updateSplineAreaControlPoints( /// calculate splinerangearea control point void _findSplineRangeAreaControlPoint( SplineRangeAreaSeriesRenderer seriesRenderer, - SplineType splineType, + SplineType? splineType, List xValues, List lowValues, List highValues, - List dx, - List lowCoef, - List highCoef) { - for (int pointIndex = 0; pointIndex < xValues.length - 1; pointIndex++) { - List controlPointslow = []; - List controlPointshigh = []; - seriesRenderer._drawLowPoints = <_ControlPoints>[]; - seriesRenderer._drawHighPoints = <_ControlPoints>[]; + List? dx, + List? lowCoef, + List? highCoef) { + List controlPointslow, controlPointshigh; + double x, low, high, nextX, nextlow, nexthigh; + int pointIndex; + for (pointIndex = 0; pointIndex < xValues.length - 1; pointIndex++) { + controlPointslow = []; + controlPointshigh = []; if (xValues[pointIndex] != null && seriesRenderer._dataPoints[pointIndex].low != null && seriesRenderer._dataPoints[pointIndex].high != null && xValues[pointIndex + 1] != null && seriesRenderer._dataPoints[pointIndex + 1].low != null && seriesRenderer._dataPoints[pointIndex + 1].high != null) { - final double x = xValues[pointIndex].toDouble(); - final double low = seriesRenderer._dataPoints[pointIndex].low.toDouble(); - final double high = - seriesRenderer._dataPoints[pointIndex].high.toDouble(); - final double nextX = xValues[pointIndex + 1].toDouble(); - final double nextlow = - seriesRenderer._dataPoints[pointIndex + 1].low.toDouble(); - final double nexthigh = - seriesRenderer._dataPoints[pointIndex + 1].high.toDouble(); + x = xValues[pointIndex].toDouble(); + low = seriesRenderer._dataPoints[pointIndex].low.toDouble(); + high = seriesRenderer._dataPoints[pointIndex].high.toDouble(); + nextX = xValues[pointIndex + 1].toDouble(); + nextlow = seriesRenderer._dataPoints[pointIndex + 1].low.toDouble(); + nexthigh = seriesRenderer._dataPoints[pointIndex + 1].high.toDouble(); if (splineType == SplineType.monotonic) { controlPointslow = _calculateMonotonicControlPoints( x, low, nextX, nextlow, - lowCoef[pointIndex].toDouble(), - lowCoef[pointIndex + 1].toDouble(), - dx[pointIndex].toDouble()); - seriesRenderer._drawLowPoints.add(_ControlPoints( - controlPointslow[0], - controlPointslow[1], - )); - seriesRenderer._drawLowPoints.add(_ControlPoints( - controlPointslow[2], - controlPointslow[3], - )); - seriesRenderer._drawLowControlPoints - .add(_ListControlPoints(seriesRenderer._drawLowPoints)); + lowCoef![pointIndex]!.toDouble(), + lowCoef[pointIndex + 1]!.toDouble(), + dx![pointIndex]!.toDouble(), + controlPointslow); + seriesRenderer._drawLowControlPoints.add(controlPointslow); controlPointshigh = _calculateMonotonicControlPoints( x, high, nextX, nexthigh, - highCoef[pointIndex].toDouble(), - highCoef[pointIndex + 1].toDouble(), - dx[pointIndex].toDouble()); - seriesRenderer._drawHighPoints.add(_ControlPoints( - controlPointshigh[0], - controlPointshigh[1], - )); - seriesRenderer._drawHighPoints.add(_ControlPoints( - controlPointshigh[2], - controlPointshigh[3], - )); - seriesRenderer._drawHighControlPoints - .add(_ListControlPoints(seriesRenderer._drawHighPoints)); + highCoef![pointIndex]!.toDouble(), + highCoef[pointIndex + 1]!.toDouble(), + dx[pointIndex]!.toDouble(), + controlPointshigh); + seriesRenderer._drawHighControlPoints.add(controlPointshigh); } else if (splineType == SplineType.cardinal) { controlPointslow = _calculateCardinalControlPoints( x, low, nextX, nextlow, - lowCoef[pointIndex].toDouble(), - lowCoef[pointIndex + 1].toDouble()); - seriesRenderer._drawLowPoints.add(_ControlPoints( - controlPointslow[0], - controlPointslow[1], - )); - seriesRenderer._drawLowPoints.add(_ControlPoints( - controlPointslow[2], - controlPointslow[3], - )); - seriesRenderer._drawLowControlPoints - .add(_ListControlPoints(seriesRenderer._drawLowPoints)); + lowCoef![pointIndex]!.toDouble(), + lowCoef[pointIndex + 1]!.toDouble(), + seriesRenderer, + controlPointslow); + seriesRenderer._drawLowControlPoints.add(controlPointslow); controlPointshigh = _calculateCardinalControlPoints( x, high, nextX, nexthigh, - highCoef[pointIndex].toDouble(), - highCoef[pointIndex + 1].toDouble()); - seriesRenderer._drawHighPoints.add(_ControlPoints( - controlPointshigh[0], - controlPointshigh[1], - )); - seriesRenderer._drawHighPoints.add(_ControlPoints( - controlPointshigh[2], - controlPointshigh[3], - )); - seriesRenderer._drawHighControlPoints - .add(_ListControlPoints(seriesRenderer._drawHighPoints)); + highCoef![pointIndex]!.toDouble(), + highCoef[pointIndex + 1]!.toDouble(), + seriesRenderer, + controlPointshigh); + seriesRenderer._drawHighControlPoints.add(controlPointshigh); } else { controlPointslow = _calculateControlPoints( xValues, lowValues, - lowCoef[pointIndex].toDouble(), - lowCoef[pointIndex + 1].toDouble(), - pointIndex); - seriesRenderer._drawLowPoints.add(_ControlPoints( - controlPointslow[0], - controlPointslow[1], - )); - seriesRenderer._drawLowPoints.add(_ControlPoints( - controlPointslow[2], - controlPointslow[3], - )); - seriesRenderer._drawLowControlPoints - .add(_ListControlPoints(seriesRenderer._drawLowPoints)); + lowCoef![pointIndex]!.toDouble(), + lowCoef[pointIndex + 1]!.toDouble(), + pointIndex, + controlPointslow); + seriesRenderer._drawLowControlPoints.add(controlPointslow); controlPointshigh = _calculateControlPoints( xValues, highValues, - highCoef[pointIndex].toDouble(), - highCoef[pointIndex + 1].toDouble(), - pointIndex); - seriesRenderer._drawHighPoints - .add(_ControlPoints(controlPointshigh[0], controlPointshigh[1])); - seriesRenderer._drawHighPoints.add(_ControlPoints( - controlPointshigh[2], - controlPointshigh[3], - )); - seriesRenderer._drawHighControlPoints - .add(_ListControlPoints(seriesRenderer._drawHighPoints)); + highCoef![pointIndex]!.toDouble(), + highCoef[pointIndex + 1]!.toDouble(), + pointIndex, + controlPointshigh); + seriesRenderer._drawHighControlPoints.add(controlPointshigh); } } } } ///get the old axis (for stock chart animation) -ChartAxisRenderer _getOldAxisRenderer(ChartAxisRenderer axisRenderer, +ChartAxisRenderer? _getOldAxisRenderer(ChartAxisRenderer axisRenderer, List oldAxisRendererList) { for (int i = 0; i < oldAxisRendererList.length; i++) { if (oldAxisRendererList[i]._name == axisRenderer._name) { @@ -2924,17 +2836,9 @@ ChartAxisRenderer _getOldAxisRenderer(ChartAxisRenderer axisRenderer, return null; } -/// To check if position is inside rect -bool _isRectContains(Rect rect, Offset pos) { - return pos.dx >= rect.left && - pos.dx <= rect.right && - pos.dy >= rect.top && - pos.dy <= rect.bottom; -} - /// To get chart point -CartesianChartPoint _getChartPoint( - XyDataSeriesRenderer seriesRenderer, dynamic data, int pointIndex) { +CartesianChartPoint? _getChartPoint( + CartesianSeriesRenderer seriesRenderer, dynamic data, int pointIndex) { dynamic xVal, yVal, highVal, @@ -2949,30 +2853,32 @@ CartesianChartPoint _getChartPoint( minVal, minimumVal, maximumVal; - bool isIntermediateSum, isTotalSum; - CartesianChartPoint currentPoint; + bool? isIntermediateSum, isTotalSum; + CartesianChartPoint? currentPoint; final dynamic series = seriesRenderer._series; - final ChartIndexedValueMapper _xMap = series.xValueMapper; - final ChartIndexedValueMapper _yMap = series.yValueMapper; - final ChartIndexedValueMapper _highMap = series.highValueMapper; - final ChartIndexedValueMapper _lowMap = series.lowValueMapper; - final ChartIndexedValueMapper _isIntermediateSumMap = + final ChartIndexedValueMapper? _xMap = series.xValueMapper; + final ChartIndexedValueMapper? _yMap = series.yValueMapper; + final ChartIndexedValueMapper? _highMap = series.highValueMapper; + final ChartIndexedValueMapper? _lowMap = series.lowValueMapper; + final ChartIndexedValueMapper? _isIntermediateSumMap = series.intermediateSumPredicate; - final ChartIndexedValueMapper _isTotalSumMap = series.totalSumPredicate; - final ChartIndexedValueMapper _sortFieldMap = + final ChartIndexedValueMapper? _isTotalSumMap = + series.totalSumPredicate; + final ChartIndexedValueMapper? _sortFieldMap = series.sortFieldValueMapper; - final ChartIndexedValueMapper _pointColorMap = series.pointColorMapper; + final ChartIndexedValueMapper? _pointColorMap = + series.pointColorMapper; final dynamic _sizeMap = series.sizeValueMapper; - final ChartIndexedValueMapper _pointTextMap = series.dataLabelMapper; + final ChartIndexedValueMapper? _pointTextMap = series.dataLabelMapper; if (seriesRenderer is HistogramSeriesRenderer) { minVal = seriesRenderer._histogramValues.minValue; - yVal = seriesRenderer._histogramValues.yValues + yVal = seriesRenderer._histogramValues.yValues! .where((dynamic x) => x >= minVal && x < (minVal + seriesRenderer._histogramValues.binWidth)) .length; - xVal = minVal + seriesRenderer._histogramValues.binWidth / 2; + xVal = minVal + seriesRenderer._histogramValues.binWidth! / 2; minVal += seriesRenderer._histogramValues.binWidth; seriesRenderer._histogramValues.minValue = minVal; } else { @@ -2998,8 +2904,11 @@ CartesianChartPoint _getChartPoint( yVal.runtimeType == num || yVal.runtimeType == double || yVal.runtimeType == int || + yVal.runtimeType.toString() == 'List' || yVal.runtimeType.toString() == 'List' || + yVal.runtimeType.toString() == 'List' || yVal.runtimeType.toString() == 'List' || + yVal.runtimeType.toString() == 'List' || yVal.runtimeType.toString() == 'List', 'The Y value will accept only number or list of numbers.'); } @@ -3019,12 +2928,12 @@ CartesianChartPoint _getChartPoint( if (series is _FinancialSeriesBase) { final _FinancialSeriesBase financialSeries = - seriesRenderer._series; - final ChartIndexedValueMapper _openMap = + seriesRenderer._series as _FinancialSeriesBase; + final ChartIndexedValueMapper? _openMap = financialSeries.openValueMapper; - final ChartIndexedValueMapper _closeMap = + final ChartIndexedValueMapper? _closeMap = financialSeries.closeValueMapper; - final ChartIndexedValueMapper _volumeMap = + final ChartIndexedValueMapper? _volumeMap = financialSeries.volumeValueMapper; if (_openMap != null) { @@ -3103,7 +3012,7 @@ bool _findChangesInPoint( CartesianChartPoint oldPoint, CartesianSeriesRenderer seriesRenderer) { if (seriesRenderer._series.sortingOrder == - seriesRenderer._oldSeries.sortingOrder) { + seriesRenderer._oldSeries?.sortingOrder) { if (seriesRenderer is CandleSeriesRenderer || seriesRenderer._seriesType.contains('range') || seriesRenderer._seriesType.contains('hilo')) { @@ -3148,20 +3057,21 @@ bool _findChangesInPoint( /// To calculate range Y on zoom mode X _VisibleRange _calculateYRangeOnZoomX( _VisibleRange _actualRange, dynamic axisRenderer) { - num _mini, _maxi; + num? _mini, _maxi; final dynamic axis = axisRenderer._axis; final List _seriesRenderers = axisRenderer._seriesRenderers; - final num minimum = axis.minimum, maximum = axis.maximum; + final num? minimum = axis.minimum, maximum = axis.maximum; for (int i = 0; i < _seriesRenderers.length && _seriesRenderers.isNotEmpty; i++) { final dynamic xAxisRenderer = _seriesRenderers[i]._xAxisRenderer; - xAxisRenderer._calculateRangeAndInterval(axisRenderer._chartState); + xAxisRenderer._calculateRangeAndInterval( + axisRenderer._chartState, 'AnchoringRange'); final _VisibleRange xRange = xAxisRenderer._visibleRange; if (_seriesRenderers[i]._yAxisRenderer == axisRenderer && xRange != null && - _seriesRenderers[i]._visible) { + _seriesRenderers[i]._visible!) { for (int j = 0; j < _seriesRenderers[i]._dataPoints.length; j++) { final CartesianChartPoint point = _seriesRenderers[i]._dataPoints[j]; @@ -3182,7 +3092,7 @@ _VisibleRange _calculateYRangeOnZoomX( } /// Bool to calculate need for Y range -bool _needCalculateYrange(num minimum, num maximum, +bool _needCalculateYrange(num? minimum, num? maximum, SfCartesianChartState _chartState, AxisOrientation _orientation) { final SfCartesianChart chart = _chartState._chart; return (_chartState._zoomedState == true || @@ -3197,7 +3107,7 @@ bool _needCalculateYrange(num minimum, num maximum, } //this method returns the axisRenderer for the given axis from given collection, if not found returns null. -ChartAxisRenderer _findExistingAxisRenderer( +ChartAxisRenderer? _findExistingAxisRenderer( ChartAxis axis, List axisRenderers) { for (final ChartAxisRenderer axisRenderer in axisRenderers) { if (identical(axis, axisRenderer._axis)) { @@ -3207,6 +3117,22 @@ ChartAxisRenderer _findExistingAxisRenderer( return null; } +int _getOldSegmentIndex(ChartSegment segment) { + if (segment._oldSeriesRenderer != null) { + for (final ChartSegment oldSegment + in segment._oldSeriesRenderer!._segments) { + if (segment.runtimeType == oldSegment.runtimeType && + (segment._seriesRenderer._xAxisRenderer is CategoryAxisRenderer + ? oldSegment._currentPoint!.x == segment._currentPoint!.x + : oldSegment._currentPoint!.xValue == + segment._currentPoint!.xValue)) { + return segment._oldSeriesRenderer!._segments.indexOf(oldSegment); + } + } + } + return -1; +} + //this method determines the whether all the series animations has been completed and renders the datalabel void _setAnimationStatus(dynamic chartState) { if (chartState._totalAnimatingSeries == chartState._animationCompleteCount) { @@ -3216,32 +3142,340 @@ void _setAnimationStatus(dynamic chartState) { chartState._animateCompleted = false; } if (chartState._renderDataLabel != null) { - chartState._renderDataLabel.state?.render(); + if (chartState._renderDataLabel.state.mounted) { + chartState._renderDataLabel.state?.render(); + } } } -class _ControlPoints { - _ControlPoints(this.controlPoint1, this.controlPoint2); - final double controlPoint1; - final double controlPoint2; +/// Calculate date time nice interval +int _calculateDateTimeNiceInterval( + ChartAxisRenderer axisRenderer, Size size, _VisibleRange range, + [DateTime? startDate, DateTime? endDate]) { + final ChartAxis axis = axisRenderer._axis; + DateTime? visibleMinimum, visibleMaximum; + DateTimeIntervalType? actualIntervalType; + if (axis is DateTimeAxis) { + visibleMinimum = axis.visibleMinimum; + visibleMaximum = axis.visibleMaximum; + } else if (axis is DateTimeCategoryAxis) { + visibleMinimum = axis.visibleMinimum; + visibleMaximum = axis.visibleMaximum; + } + final bool notDoubleInterval = + (visibleMinimum == null || visibleMaximum == null) || + (axis.interval != null && axis.interval! % 1 == 0) || + (axis.interval == null); + const int perDay = 24 * 60 * 60 * 1000; + startDate ??= DateTime.fromMillisecondsSinceEpoch(range.minimum.toInt()); + endDate ??= DateTime.fromMillisecondsSinceEpoch(range.maximum.toInt()); + num? interval; + final num totalDays = + ((startDate.millisecondsSinceEpoch - endDate.millisecondsSinceEpoch) / + perDay) + .abs(); + if (axis is DateTimeAxis) { + actualIntervalType = axis.intervalType; + } else if (axis is DateTimeCategoryAxis) { + actualIntervalType = axis.intervalType; + } + switch (actualIntervalType) { + case DateTimeIntervalType.years: + interval = axisRenderer._calculateNumericNiceInterval( + axisRenderer, totalDays / 365, size); + break; + case DateTimeIntervalType.months: + interval = axisRenderer._calculateNumericNiceInterval( + axisRenderer, totalDays / 30, size); + break; + case DateTimeIntervalType.days: + interval = axisRenderer._calculateNumericNiceInterval( + axisRenderer, totalDays, size); + break; + case DateTimeIntervalType.hours: + interval = axisRenderer._calculateNumericNiceInterval( + axisRenderer, totalDays * 24, size); + break; + case DateTimeIntervalType.minutes: + interval = axisRenderer._calculateNumericNiceInterval( + axisRenderer, totalDays * 24 * 60, size); + break; + case DateTimeIntervalType.seconds: + interval = axisRenderer._calculateNumericNiceInterval( + axisRenderer, totalDays * 24 * 60 * 60, size); + break; + case DateTimeIntervalType.auto: + + /// for years + interval = axisRenderer._calculateNumericNiceInterval( + axisRenderer, totalDays / 365, size); + if (interval >= 1) { + _setActualIntervalType(axisRenderer, DateTimeIntervalType.years); + return interval.floor(); + } + + /// for months + interval = axisRenderer._calculateNumericNiceInterval( + axisRenderer, totalDays / 30, size); + if (interval >= 1) { + _setActualIntervalType( + axisRenderer, + notDoubleInterval + ? DateTimeIntervalType.months + : DateTimeIntervalType.years); + return interval.floor(); + } + + /// for days + interval = axisRenderer._calculateNumericNiceInterval( + axisRenderer, totalDays, size); + if (interval >= 1) { + _setActualIntervalType( + axisRenderer, + notDoubleInterval + ? DateTimeIntervalType.days + : DateTimeIntervalType.months); + return interval.floor(); + } + + /// for hours + interval = axisRenderer._calculateNumericNiceInterval( + axisRenderer, totalDays * 24, size); + if (interval >= 1) { + _setActualIntervalType( + axisRenderer, + notDoubleInterval + ? DateTimeIntervalType.hours + : DateTimeIntervalType.days); + return interval.floor(); + } + + /// for minutes + interval = axisRenderer._calculateNumericNiceInterval( + axisRenderer, totalDays * 24 * 60, size); + if (interval >= 1) { + _setActualIntervalType( + axisRenderer, + notDoubleInterval + ? DateTimeIntervalType.minutes + : DateTimeIntervalType.hours); + return interval.floor(); + } + + /// for seconds + interval = axisRenderer._calculateNumericNiceInterval( + axisRenderer, totalDays * 24 * 60 * 60, size); + _setActualIntervalType( + axisRenderer, + notDoubleInterval + ? DateTimeIntervalType.seconds + : DateTimeIntervalType.minutes); + return interval.floor(); + default: + break; + } + _setActualIntervalType(axisRenderer, actualIntervalType!); + return interval! < 1 ? interval.ceil() : interval.floor(); +} + +void _setActualIntervalType( + ChartAxisRenderer axisRenderer, DateTimeIntervalType intervalType) { + if (axisRenderer is DateTimeAxisRenderer) { + axisRenderer._actualIntervalType = intervalType; + } else if (axisRenderer is DateTimeCategoryAxisRenderer) { + axisRenderer._actualIntervalType = intervalType; + } } -class _ListControlPoints { - _ListControlPoints(this._listControlPoints); - final List<_ControlPoints> _listControlPoints; +/// To get the label format of the date-time axis +DateFormat _getDateTimeLabelFormat(ChartAxisRenderer axisRenderer) { + DateFormat? format; + final bool notDoubleInterval = (axisRenderer._axis.interval != null && + axisRenderer._axis.interval! % 1 == 0) || + axisRenderer._axis.interval == null; + DateTimeIntervalType? actualIntervalType; + if (axisRenderer is DateTimeAxisRenderer) { + actualIntervalType = axisRenderer._actualIntervalType; + } else if (axisRenderer is DateTimeCategoryAxisRenderer) { + actualIntervalType = axisRenderer._actualIntervalType; + } + switch (actualIntervalType) { + case DateTimeIntervalType.years: + format = notDoubleInterval ? DateFormat.yMMM() : DateFormat.MMMd(); + break; + case DateTimeIntervalType.months: + format = notDoubleInterval ? DateFormat.MMMd() : DateFormat.MMMd(); + break; + case DateTimeIntervalType.days: + format = notDoubleInterval ? DateFormat.MMMd() : DateFormat.Hm(); + break; + case DateTimeIntervalType.hours: + format = notDoubleInterval ? DateFormat.Hm() : DateFormat.ms(); + break; + case DateTimeIntervalType.minutes: + format = notDoubleInterval ? DateFormat.ms() : DateFormat.ms(); + break; + case DateTimeIntervalType.seconds: + format = notDoubleInterval ? DateFormat.ms() : DateFormat.ms(); + break; + case DateTimeIntervalType.auto: + break; + default: + break; + } + return format!; +} + +void _setCategoryMinMaxValues( + ChartAxisRenderer axisRenderer, + bool isXVisibleRange, + bool isYVisibleRange, + CartesianChartPoint point, + int pointIndex, + int dataLength, + CartesianSeriesRenderer seriesRenderer) { + final String seriesType = seriesRenderer._seriesType; + final bool anchorRangeToVisiblePoints = + seriesRenderer._yAxisRenderer!._axis.anchorRangeToVisiblePoints; + if (isYVisibleRange) { + seriesRenderer._minimumX ??= point.xValue; + seriesRenderer._maximumX ??= point.xValue; + } + if ((isXVisibleRange || !anchorRangeToVisiblePoints) && + !seriesType.contains('range') && + !seriesType.contains('hilo') && + !seriesType.contains('candle') && + seriesType != 'boxandwhisker' && + seriesType != 'waterfall') { + seriesRenderer._minimumY ??= point.yValue; + seriesRenderer._maximumY ??= point.yValue; + } + if (isYVisibleRange && point.xValue != null) { + seriesRenderer._minimumX = + math.min(seriesRenderer._minimumX!, point.xValue); + seriesRenderer._maximumX = + math.max(seriesRenderer._maximumX!, point.xValue); + } + if (isXVisibleRange || !anchorRangeToVisiblePoints) { + if (point.yValue != null && + (!seriesType.contains('range') && + !seriesType.contains('hilo') && + !seriesType.contains('candle') && + seriesType != 'boxandwhisker' && + seriesType != 'waterfall')) { + seriesRenderer._minimumY = + math.min(seriesRenderer._minimumY!, point.yValue); + seriesRenderer._maximumY = + math.max(seriesRenderer._maximumY!, point.yValue); + } + if (point.high != null) { + axisRenderer._highMin = + _findMinValue(axisRenderer._highMin ?? point.high, point.high); + axisRenderer._highMax = + _findMaxValue(axisRenderer._highMax ?? point.high, point.high); + } + if (point.low != null) { + axisRenderer._lowMin = + _findMinValue(axisRenderer._lowMin ?? point.low, point.low); + axisRenderer._lowMax = + _findMaxValue(axisRenderer._lowMax ?? point.low, point.low); + } + if (point.maximum != null) { + axisRenderer._highMin = _findMinValue( + axisRenderer._highMin ?? point.maximum!, point.maximum!); + axisRenderer._highMax = _findMaxValue( + axisRenderer._highMax ?? point.minimum!, point.maximum!); + } + if (point.minimum != null) { + axisRenderer._lowMin = + _findMinValue(axisRenderer._lowMin ?? point.minimum!, point.minimum!); + axisRenderer._lowMax = + _findMaxValue(axisRenderer._lowMax ?? point.minimum!, point.minimum!); + } + if (seriesType == 'waterfall') { + /// Empty point is not applicable for Waterfall series. + point.yValue ??= 0; + seriesRenderer._minimumY = + _findMinValue(seriesRenderer._minimumY ?? point.yValue, point.yValue); + seriesRenderer._maximumY = _findMaxValue( + seriesRenderer._maximumY ?? point.maxYValue, point.maxYValue); + } + } + + if (pointIndex >= dataLength - 1) { + if (seriesType.contains('range') || + seriesType.contains('hilo') || + seriesType.contains('candle') || + seriesType == 'boxandwhisker') { + axisRenderer._lowMin ??= 0; + axisRenderer._lowMax ??= 5; + axisRenderer._highMin ??= 0; + axisRenderer._highMax ??= 5; + seriesRenderer._minimumY = + math.min(axisRenderer._lowMin!, axisRenderer._highMin!); + seriesRenderer._maximumY = + math.max(axisRenderer._lowMax!, axisRenderer._highMax!); + } + seriesRenderer._minimumY ??= 0; + seriesRenderer._maximumY ??= 5; + } +} + +void _calculateDateTimeVisibleRange( + Size availableSize, ChartAxisRenderer axisRenderer) { + final _VisibleRange actualRange = axisRenderer._actualRange!; + final SfCartesianChartState chartState = axisRenderer._chartState; + axisRenderer._visibleRange = + _VisibleRange(actualRange.minimum, actualRange.maximum); + final _VisibleRange visibleRange = axisRenderer._visibleRange!; + visibleRange.delta = actualRange.delta; + visibleRange.interval = actualRange.interval; + bool canAutoScroll = false; + if (axisRenderer._axis.autoScrollingDelta != null && + axisRenderer._axis.autoScrollingDelta! > 0 && + !chartState._isRedrawByZoomPan) { + canAutoScroll = true; + if (axisRenderer is DateTimeAxisRenderer) { + axisRenderer._updateScrollingDelta(); + } else { + axisRenderer._updateAutoScrollingDelta( + axisRenderer._axis.autoScrollingDelta!, axisRenderer); + } + } + if (!canAutoScroll) { + axisRenderer._setZoomFactorAndPosition( + axisRenderer, chartState._zoomedAxisRendererStates); + } + if (axisRenderer._zoomFactor < 1 || axisRenderer._zoomPosition > 0) { + chartState._zoomProgress = true; + axisRenderer._calculateZoomRange(axisRenderer, availableSize); + visibleRange.interval = axisRenderer._axis.enableAutoIntervalOnZooming && + chartState._zoomProgress && + axisRenderer is DateTimeAxisRenderer + ? axisRenderer.calculateInterval(visibleRange, availableSize) + : visibleRange.interval; + if (axisRenderer is DateTimeAxisRenderer) { + visibleRange.minimum = (visibleRange.minimum).floor(); + visibleRange.maximum = (visibleRange.maximum).floor(); + } + if (axisRenderer._axis.rangeController != null) { + chartState._rangeChangedByChart = true; + axisRenderer._setRangeControllerValues(axisRenderer); + } + } } // This method used to return the cross value of the axis. -num _getCrossesAtValue( +num? _getCrossesAtValue( CartesianSeriesRenderer seriesRenderer, SfCartesianChartState chart) { - num crossesAt; - final num seriesIndex = + num? crossesAt; + final int seriesIndex = chart._chartSeries.visibleSeriesRenderers.indexOf(seriesRenderer); final List axisCollection = chart._requireInvertedAxis ? chart._chartAxis._verticalAxisRenderers : chart._chartAxis._horizontalAxisRenderers; for (int i = 0; i < axisCollection.length; i++) { - if (chart._chartSeries.visibleSeriesRenderers[seriesIndex]._xAxisRenderer + if (chart._chartSeries.visibleSeriesRenderers[seriesIndex]._xAxisRenderer! ._name == axisCollection[i]._name) { crossesAt = axisCollection[i]._crossValue; @@ -3251,21 +3485,21 @@ num _getCrossesAtValue( return crossesAt; } -List _getTooltipPaddingData(CartesianSeriesRenderer seriesRenderer, - bool isTrendLine, Rect region, Rect paddedRegion, Offset tooltipPosition) { - Offset padding, position; +List _getTooltipPaddingData(CartesianSeriesRenderer seriesRenderer, + bool isTrendLine, Rect region, Rect paddedRegion, Offset? tooltipPosition) { + Offset? padding, position; if (seriesRenderer._seriesType == 'bubble' && !isTrendLine) { padding = Offset(region.center.dx - region.centerLeft.dx, 2 * (region.center.dy - region.topCenter.dy)); - position = Offset(tooltipPosition.dx, paddedRegion.top); + position = Offset(tooltipPosition!.dx, paddedRegion.top); } else if (seriesRenderer._seriesType == 'scatter') { padding = Offset(seriesRenderer._series.markerSettings.width, seriesRenderer._series.markerSettings.height / 2); - position = Offset(tooltipPosition.dx, tooltipPosition.dy); + position = Offset(tooltipPosition!.dx, tooltipPosition.dy); } else if (seriesRenderer._seriesType.contains('rangearea')) { padding = Offset(seriesRenderer._series.markerSettings.width, seriesRenderer._series.markerSettings.height / 2); - position = Offset(tooltipPosition.dx, tooltipPosition.dy); + position = Offset(tooltipPosition!.dx, tooltipPosition.dy); } else { padding = (seriesRenderer._series.markerSettings.isVisible) ? Offset( @@ -3274,18 +3508,18 @@ List _getTooltipPaddingData(CartesianSeriesRenderer seriesRenderer, seriesRenderer._series.markerSettings.borderWidth / 2) : const Offset(2, 2); } - return [padding, position ?? tooltipPosition]; + return [padding, position ?? tooltipPosition]; } //Returns the old series renderer instance for the given series renderer -CartesianSeriesRenderer _getOldSeriesRenderer( +CartesianSeriesRenderer? _getOldSeriesRenderer( SfCartesianChartState chartState, CartesianSeriesRenderer seriesRenderer, int seriesIndex, List oldSeriesRenderers) { if (chartState._widgetNeedUpdate && - seriesRenderer._xAxisRenderer._zoomFactor == 1 && - seriesRenderer._yAxisRenderer._zoomFactor == 1 && + seriesRenderer._xAxisRenderer!._zoomFactor == 1 && + seriesRenderer._yAxisRenderer!._zoomFactor == 1 && oldSeriesRenderers != null && oldSeriesRenderers.isNotEmpty && oldSeriesRenderers.length - 1 >= seriesIndex && @@ -3298,13 +3532,13 @@ CartesianSeriesRenderer _getOldSeriesRenderer( } //Returns the old chart point for the given point and series index if present. -CartesianChartPoint _getOldChartPoint( +CartesianChartPoint? _getOldChartPoint( SfCartesianChartState chartState, CartesianSeriesRenderer seriesRenderer, Type segmentType, int seriesIndex, int pointIndex, - CartesianSeriesRenderer oldSeriesRenderer, + CartesianSeriesRenderer? oldSeriesRenderer, List oldSeriesRenderers) { return !seriesRenderer._reAnimate && (seriesRenderer._series.animationDuration > 0 && @@ -3325,14 +3559,14 @@ CartesianChartPoint _getOldChartPoint( String _trimAxisLabelsText(String text, num labelsExtent, TextStyle labelStyle, ChartAxisRenderer axisRenderer) { String label = text; - num size = _measureText( + num size = measureText( text, axisRenderer._axis.labelStyle, axisRenderer._labelRotation) .width; if (size > labelsExtent) { final int textLength = text.length; for (int i = textLength - 1; i >= 0; --i) { label = text.substring(0, i) + '...'; - size = _measureText(label, labelStyle, axisRenderer._labelRotation).width; + size = measureText(label, labelStyle, axisRenderer._labelRotation).width; if (size <= labelsExtent) { return label == '...' ? '' : label; } @@ -3358,3 +3592,37 @@ bool _shouldShowAxisTooltip(SfCartesianChartState chartState) { } return requireAxisTooltip; } + +int? _getVisibleDataPointIndex( + int? pointIndex, CartesianSeriesRenderer seriesRender) { + int? index; + if (pointIndex != null) { + if (pointIndex < seriesRender._dataPoints[0].overallDataPointIndex! || + pointIndex > + seriesRender._dataPoints[seriesRender._dataPoints.length - 1] + .overallDataPointIndex!) { + index = null; + } else if (pointIndex > seriesRender._dataPoints.length - 1) { + for (int i = 0; i < seriesRender._dataPoints.length; i++) { + if (pointIndex == seriesRender._dataPoints[i].overallDataPointIndex) { + index = seriesRender._dataPoints[i].visiblePointIndex; + } + } + } else { + index = seriesRender._dataPoints[pointIndex].visiblePointIndex; + } + } + return index; +} + +bool _checkSeriesType(String seriesType) { + bool isLineSeriesType = false; + if (seriesType == 'line' || + seriesType == 'spline' || + seriesType == 'stepline' || + seriesType == 'stackedline' || + seriesType == 'stackedline100') { + isLineSeriesType = true; + } + return isLineSeriesType; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart index 432403aed..ca7cd398b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart @@ -21,6 +21,10 @@ typedef CircularSelectionCallback = void Function(SelectionArgs selectionArgs); typedef CircularTouchInteractionCallback = void Function( ChartTouchInteractionArgs tapArgs); +///Signature for the callback that returns the shader value to override the fill color of the data points. +typedef CircularShaderCallback = Shader Function( + ChartShaderDetails chartShaderDetails); + ///Renders the circular chart /// ///The SfCircularChart supports pie, doughnut and radial bar series that can be customized within the Circular Charts class. @@ -56,7 +60,7 @@ typedef CircularTouchInteractionCallback = void Function( class SfCircularChart extends StatefulWidget { /// Creating an argument constructor of SfCircularChart class. SfCircularChart( - {Key key, + {Key? key, this.backgroundColor, this.backgroundImage, this.annotations, @@ -72,6 +76,7 @@ class SfCircularChart extends StatefulWidget { this.onChartTouchInteractionUp, this.onChartTouchInteractionDown, this.onChartTouchInteractionMove, + this.onCreateShader, this.palette = const [ Color.fromRGBO(75, 135, 185, 1), Color.fromRGBO(192, 108, 132, 1), @@ -84,15 +89,15 @@ class SfCircularChart extends StatefulWidget { Color.fromRGBO(255, 240, 219, 1), Color.fromRGBO(238, 238, 238, 1) ], - ChartTitle title, - EdgeInsets margin, - List> series, - Legend legend, - String centerX, - String centerY, - TooltipBehavior tooltipBehavior, - ActivationMode selectionGesture, - bool enableMultiSelection}) + ChartTitle? title, + EdgeInsets? margin, + List>? series, + Legend? legend, + String? centerX, + String? centerY, + TooltipBehavior? tooltipBehavior, + ActivationMode? selectionGesture, + bool? enableMultiSelection}) : series = series = series ?? >[], title = title ?? ChartTitle(), legend = legend ?? Legend(), @@ -179,7 +184,7 @@ class SfCircularChart extends StatefulWidget { /// )); ///} ///``` - final Color backgroundColor; + final Color? backgroundColor; ///Customizes the annotations. Annotations are used to mark the specific area ///of interest in the plot area with texts, shapes, or images @@ -200,7 +205,7 @@ class SfCircularChart extends StatefulWidget { /// )); ///} ///``` - final List annotations; + final List? annotations; ///Border color of the chart /// @@ -261,7 +266,7 @@ class SfCircularChart extends StatefulWidget { /// )); ///} ///``` - final ImageProvider backgroundImage; + final ImageProvider? backgroundImage; ///X value for placing the chart. /// @@ -305,7 +310,7 @@ class SfCircularChart extends StatefulWidget { /// args.legendIconType = LegendIconType.diamond; ///} ///``` - final CircularLegendRenderCallback onLegendItemRender; + final CircularLegendRenderCallback? onLegendItemRender; /// Occurs while tooltip is rendered. You can customize the position and header. Here, /// you can get the text, header, point index, series, x and y-positions. @@ -321,7 +326,7 @@ class SfCircularChart extends StatefulWidget { /// args.locationX = 30; ///} ///``` - final CircularTooltipCallback onTooltipRender; + final CircularTooltipCallback? onTooltipRender; /// Occurs while rendering the data label. The data label and text styles such as color, /// font size, and font weight can be customized. You can get the series index, point @@ -342,7 +347,7 @@ class SfCircularChart extends StatefulWidget { /// args.text = 'dataLabel'; ///} ///``` - final CircularDatalabelRenderCallback onDataLabelRender; + final CircularDatalabelRenderCallback? onDataLabelRender; /// Occurs when tapping a series point. Here, you can get the series, series index /// and point index. @@ -357,7 +362,26 @@ class SfCircularChart extends StatefulWidget { /// print(args.seriesIndex); ///} ///``` - final CircularPointTapCallback onPointTapped; + final CircularPointTapCallback? onPointTapped; + + ///Fills the data points with the gradient and image shaders. + /// + ///The data points of pie, doughnut and radial bar charts can be filled with [gradient](https://api.flutter.dev/flutter/dart-ui/Gradient-class.html) + /// (linear, radial and sweep gradient) and [image shaders](https://api.flutter.dev/flutter/dart-ui/ImageShader-class.html). + /// + ///All the data points are together considered as a single segment and the shader is applied commonly. + /// + ///Defaults to `null`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// onCreateShader: (ChartShaderDetails chartShaderDetails) => shader, + /// )); + ///} + ///``` + final CircularShaderCallback? onCreateShader; //Called when the data label is tapped. /// @@ -379,7 +403,7 @@ class SfCircularChart extends StatefulWidget { /// ///``` - final DataLabelTapCallback onDataLabelTapped; + final DataLabelTapCallback? onDataLabelTapped; /// Occurs when the legend is tapped. Here, you can get the series, /// series index, and point index. @@ -394,7 +418,7 @@ class SfCircularChart extends StatefulWidget { /// print(args.pointIndex); ///} ///``` - final ChartLegendTapCallback onLegendTapped; + final ChartLegendTapCallback? onLegendTapped; /// Occurs while selection changes. Here, you can get the series, selected color, /// unselected color, selected border color, unselected border color, selected @@ -410,7 +434,7 @@ class SfCircularChart extends StatefulWidget { /// print(args.selectedBorderColor); ///} ///``` - final CircularSelectionCallback onSelectionChanged; + final CircularSelectionCallback? onSelectionChanged; /// Occurs when tapped on the chart area. ///```dart @@ -424,7 +448,7 @@ class SfCircularChart extends StatefulWidget { /// )); ///} ///``` - final CircularTouchInteractionCallback onChartTouchInteractionUp; + final CircularTouchInteractionCallback? onChartTouchInteractionUp; /// Occurs when touched on the chart area. ///```dart @@ -439,7 +463,7 @@ class SfCircularChart extends StatefulWidget { ///} ///``` /// - final CircularTouchInteractionCallback onChartTouchInteractionDown; + final CircularTouchInteractionCallback? onChartTouchInteractionDown; /// Occurs when touched and moved on the chart area. ///```dart @@ -453,7 +477,7 @@ class SfCircularChart extends StatefulWidget { /// )); ///} ///``` - final CircularTouchInteractionCallback onChartTouchInteractionMove; + final CircularTouchInteractionCallback? onChartTouchInteractionMove; ///Color palette for the data points in the chart series. If the series color is ///not specified, then the series will be rendered with appropriate palette color. @@ -526,86 +550,102 @@ class SfCircularChartState extends State with TickerProviderStateMixin { /// Holds the animation controller list for all series //ignore: unused_field - List _controllerList; + late List _controllerList; /// Animation controller for series - AnimationController _animationController; + late AnimationController _animationController; /// Animation controller for Annotations //ignore: unused_field - AnimationController _annotationController; + late AnimationController _annotationController; /// Repaint notifier for series container - ValueNotifier _seriesRepaintNotifier; + late ValueNotifier _seriesRepaintNotifier; /// To measure legend size and position - List<_MeasureWidgetContext> _legendWidgetContext; + late List<_MeasureWidgetContext> _legendWidgetContext; /// Chart Template info - List<_ChartTemplateInfo> _templates; + late List<_ChartTemplateInfo> _templates; /// List of container widgets for chart series - List _chartWidgets; + List? _chartWidgets; /// Holds the information of chart theme arguments - SfChartThemeData _chartTheme; - Offset _centerLocation; - Rect _chartContainerRect; - Rect _chartAreaRect; - bool _animateCompleted; - List _explodedPoints; - List<_LegendRenderContext> _legendToggleStates; - List<_MeasureWidgetContext> _legendToggleTemplateStates; - bool _initialRender; + late SfChartThemeData _chartTheme; + late Offset _centerLocation; + late Rect _chartContainerRect; + late Rect _chartAreaRect; + late bool _animateCompleted; + late List _explodedPoints; + late List<_LegendRenderContext> _legendToggleStates; + late List<_MeasureWidgetContext> _legendToggleTemplateStates; + bool? _initialRender; //ignore: unused_field - List> _selectedDataPoints; + late List> _selectedDataPoints; //ignore: unused_field - List> _unselectedDataPoints; + late List> _unselectedDataPoints; //ignore: unused_field - List<_Region> _selectedRegions; + late List<_Region> _selectedRegions; //ignore: unused_field - List<_Region> _unselectedRegions; - List _dataLabelTemplateRegions; - List _annotationRegions; - _ChartTemplate _chartTemplate; - _ChartInteraction _currentActive; - Offset _tapPosition; - _CircularDataLabelRenderer _renderDataLabel; - bool _widgetNeedUpdate; - bool _isLegendToggled; - CircularSeriesRenderer _prevSeriesRenderer; - List> _oldPoints; - List _selectionData; + late List<_Region> _unselectedRegions; + late List _dataLabelTemplateRegions; + late List _annotationRegions; + _ChartTemplate? _chartTemplate; + _ChartInteraction? _currentActive; + Offset? _tapPosition; + _CircularDataLabelRenderer? _renderDataLabel; + late bool _widgetNeedUpdate; + late bool _isLegendToggled; + CircularSeriesRenderer? _prevSeriesRenderer; + List?>? _oldPoints; + late List _selectionData; //ignore: unused_field - Animation _chartElementAnimation; - Orientation _oldDeviceOrientation; - Orientation _deviceOrientation; + late Animation _chartElementAnimation; + Orientation? _oldDeviceOrientation; + late Orientation _deviceOrientation; //ignore: unused_field - CircularSeriesRenderer _seriesRenderer; - Size _prevSize; + CircularSeriesRenderer? _seriesRenderer; + Size? _prevSize; bool _didSizeChange = false; //Here, we are using get keyword inorder to get the proper & updated instance of chart widget //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. SfCircularChart get _chart => widget; - _ChartLegend _chartLegend; + late _ChartLegend _chartLegend; /// Holds the information of SeriesBase class - _CircularSeries _chartSeries; + late _CircularSeries _chartSeries; //ignore: unused_field - _CircularArea _circularArea; + late _CircularArea _circularArea; - bool _needToMoveFromCenter; + late bool _needToMoveFromCenter; - bool _needExplodeAll; + late bool _needExplodeAll; - TooltipBehaviorRenderer _tooltipBehaviorRenderer; - LegendRenderer _legendRenderer; + late TooltipBehaviorRenderer _tooltipBehaviorRenderer; + late LegendRenderer _legendRenderer; //ignore: prefer_final_fields bool _isToggled = false; + // ignore: unused_element + bool get _animationCompleted { + return _animationController.status != AnimationStatus.forward; + } + + /// Called when this object is inserted into the tree. + /// + /// The framework will call this method exactly once for each State object it creates. + /// + /// Override this method to perform initialization that depends on the location at + /// which this object was inserted into the tree or on the widget used to configure this object. + /// + /// * In [initState], subscribe to the object. + /// + /// Here it overrides to initialize the object that depends on rendering the [SfCircularChart]. + @override void initState() { _initializeDefaultValues(); @@ -614,20 +654,41 @@ class SfCircularChartState extends State super.initState(); } + /// Called when a dependency of this [State] object changes. + /// + /// For example, if the previous call to [build] referenced an [InheritedWidget] that later changed, + /// the framework would call this method to notify this object about the change. + /// + /// This method is also called immediately after [initState]. It is safe to call [BuildContext.dependOnInheritedWidgetOfExactType] from this method. + /// + /// Here it called for initializing the chart theme of [SfCircularChart]. + @override void didChangeDependencies() { _chartTheme = SfChartTheme.of(context); super.didChangeDependencies(); } - /// To update chart widgets + /// Called whenever the widget configuration changes. + /// + /// If the parent widget rebuilds and request that this location in the tree update to display a new widget with the same [runtimeType] and [Widget.key], + /// the framework will update the widget property of this [State] object to refer to the new widget and then call this method with the previous widget as an argument. + /// + /// Override this method to respond when the widget changes. + /// + /// The framework always calls [build] after calling [didUpdateWidget], which means any calls to [setState] in [didUpdateWidget] are redundant. + /// + /// * In [didUpdateWidget] unsubscribe from the old object and subscribe to the new one if the updated widget configuration requires replacing the object. + /// + /// Here it called whenever the series collection gets updated in [SfCircularChart]. + @override void didUpdateWidget(SfCircularChart oldWidget) { //Update and maintain the series state, when we update the series in the series collection // _createAndUpdateSeriesRenderer(oldWidget); _needsRepaintCircularChart(_chartSeries.visibleSeriesRenderers, - []..add(_prevSeriesRenderer)); + []..add(_prevSeriesRenderer)); _needExplodeAll = widget.series.isNotEmpty && widget.series[0].explodeAll && @@ -638,9 +699,23 @@ class SfCircularChartState extends State if (_legendWidgetContext.isNotEmpty) { _legendWidgetContext.clear(); } + if (_tooltipBehaviorRenderer._chartTooltipState != null) { + _tooltipBehaviorRenderer._show = false; + } super.didUpdateWidget(oldWidget); } + /// Describes the part of the user interface represented by this widget. + /// + /// The framework calls this method in a number of different situations. For example: + /// + /// * After calling [initState]. + /// * After calling [didUpdateWidget]. + /// * After receiving a call to [setState]. + /// * After a dependency of this [State] object changes. + /// + /// Here it is called whenever the user interaction is performed and it removes the old widget and updates a chart with a new widget in [SfCircularChart]. + @override Widget build(BuildContext context) { _initialRender = (_widgetNeedUpdate && !_isLegendToggled) @@ -662,7 +737,7 @@ class SfCircularChartState extends State widget.backgroundColor ?? _chartTheme.plotAreaBackgroundColor, image: widget.backgroundImage != null ? DecorationImage( - image: widget.backgroundImage, fit: BoxFit.fill) + image: widget.backgroundImage!, fit: BoxFit.fill) : null, border: Border.all( color: widget.borderColor, width: widget.borderWidth)), @@ -673,18 +748,29 @@ class SfCircularChartState extends State )); } - /// To end animation controller + /// Called when this object is removed from the tree permanently. + /// + /// The framework calls this method when this [State] object will never build again. After the framework calls [dispose], + /// the [State] object is considered unmounted and the [mounted] property is false. It is an error to call [setState] at this + /// point. This stage of the lifecycle is terminal: there is no way to remount a [State] object that has been disposed. + /// + /// Subclasses should override this method to release any resources retained by this object. + /// + /// * In [dispose], unsubscribe from the object. + /// + /// Here it end the animation controller of the series in [SfCircularChart]. + @override void dispose() { _disposeAnimationController(_animationController, _repaintChartElements); super.dispose(); } - /// Method to convert the [SfCirculaChart] as an image. + /// Method to convert the [SfCircularChart] as an image. /// /// As this method is in the widget’s state class, /// you have to use a global key to access the state to call this method. - /// Returns the [dart:ui.image] + /// Returns the `dart:ui.image` /// /// ```dart /// final GlobalKey _key = GlobalKey(); @@ -737,8 +823,8 @@ class SfCircularChartState extends State ///``` Future toImage({double pixelRatio = 1.0}) async { - final RenderRepaintBoundary boundary = - context.findRenderObject(); //get the render object from context + final RenderRepaintBoundary boundary = context.findRenderObject() + as RenderRepaintBoundary; //get the render object from context final dart_ui.Image image = await boundary.toImage(pixelRatio: pixelRatio); // Convert @@ -777,9 +863,9 @@ class SfCircularChartState extends State } // In this method, create and update the series renderer for each series // - void _createAndUpdateSeriesRenderer([SfCircularChart oldWidget]) { - if (widget.series != null && widget.series.isNotEmpty) { - final CircularSeriesRenderer oldSeriesRenderer = + void _createAndUpdateSeriesRenderer([SfCircularChart? oldWidget]) { + if (widget.series.isNotEmpty) { + final CircularSeriesRenderer? oldSeriesRenderer = oldWidget != null && oldWidget.series.isNotEmpty ? _chartSeries.visibleSeriesRenderers[0] : null; @@ -789,24 +875,24 @@ class SfCircularChartState extends State CircularSeriesRenderer seriesRenderer; if (_prevSeriesRenderer != null && - !_prevSeriesRenderer._chartState._isToggled && - _isSameSeries(_prevSeriesRenderer._series, series)) { - seriesRenderer = _prevSeriesRenderer; + !_prevSeriesRenderer!._chartState._isToggled && + _isSameSeries(_prevSeriesRenderer!._series, series)) { + seriesRenderer = _prevSeriesRenderer!; } else { seriesRenderer = series.createRenderer(series); if (seriesRenderer._controller == null && series.onRendererCreated != null) { seriesRenderer._controller = CircularSeriesController(seriesRenderer); - series.onRendererCreated(seriesRenderer._controller); + series.onRendererCreated!(seriesRenderer._controller); } } if (oldWidget != null && oldWidget.series.isNotEmpty) { _prevSeriesRenderer = oldSeriesRenderer; - _prevSeriesRenderer._series = oldWidget.series[0]; - _prevSeriesRenderer._oldRenderPoints = >[] + _prevSeriesRenderer!._series = oldWidget.series[0]; + _prevSeriesRenderer!._oldRenderPoints = >[] //ignore: prefer_spread_collections - ..addAll(_prevSeriesRenderer._renderPoints ?? []); - _prevSeriesRenderer._renderPoints = >[]; + ..addAll(_prevSeriesRenderer!._renderPoints ?? []); + _prevSeriesRenderer!._renderPoints = >[]; } seriesRenderer._series = series; seriesRenderer._isSelectionEnable = @@ -829,12 +915,15 @@ class SfCircularChartState extends State if (_isLegendToggled) { _isToggled = true; _prevSeriesRenderer = _chartSeries.visibleSeriesRenderers[0]; - _oldPoints = - List>(_prevSeriesRenderer._renderPoints.length); - for (int i = 0; i < _prevSeriesRenderer._renderPoints.length; i++) { - _oldPoints[i] = _prevSeriesRenderer._renderPoints[i]; + _oldPoints = List?>.filled( + _prevSeriesRenderer!._renderPoints!.length, null); + for (int i = 0; i < _prevSeriesRenderer!._renderPoints!.length; i++) { + _oldPoints![i] = _prevSeriesRenderer!._renderPoints![i]; } } + if (_tooltipBehaviorRenderer._chartTooltipState != null) { + _tooltipBehaviorRenderer._show = false; + } setState(() { /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. }); @@ -845,7 +934,8 @@ class SfCircularChartState extends State if (legendContexts.isNotEmpty) { for (int i = 0; i < legendContexts.length; i++) { final _MeasureWidgetContext templateContext = legendContexts[i]; - final RenderBox renderBox = templateContext.context.findRenderObject(); + final RenderBox renderBox = + templateContext.context!.findRenderObject() as RenderBox; templateContext.size = renderBox.size; } setState(() { @@ -869,11 +959,11 @@ class SfCircularChartState extends State final List legendTemplates = _bindLegendTemplateWidgets(this); if (legendTemplates.isNotEmpty && _legendWidgetContext.isEmpty) { element = Container(child: Stack(children: legendTemplates)); - SchedulerBinding.instance.addPostFrameCallback((_) => _refresh()); + SchedulerBinding.instance!.addPostFrameCallback((_) => _refresh()); } else { _chartLegend._calculateLegendBounds(_chartLegend.chartSize); element = - _getElements(this, _CircularArea(chartState: this), constraints); + _getElements(this, _CircularArea(chartState: this), constraints)!; } return element; }), @@ -897,12 +987,18 @@ class SfCircularChartState extends State // ignore: must_be_immutable class _CircularArea extends StatelessWidget { // ignore: prefer_const_constructors_in_immutables - _CircularArea({this.chartState}); + _CircularArea({required this.chartState}); //Here, we are using get keyword inorder to get the proper & updated instance of chart widget //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. SfCircularChart get chart => chartState._chart; final SfCircularChartState chartState; - CircularSeries series; + CircularSeries? series; + late RenderBox renderBox; + _Region? pointRegion; + late TapDownDetails tapDownDetails; + late Offset? doubleTapPosition; + bool _enableMouseHover = + kIsWeb || Platform.isLinux || Platform.isMacOS || Platform.isWindows; @override Widget build(BuildContext context) { @@ -910,7 +1006,10 @@ class _CircularArea extends StatelessWidget { builder: (BuildContext context, BoxConstraints constraints) { return Container( child: MouseRegion( - onHover: (PointerEvent event) => _onHover(event), + // Using the _enableMouseHover property, prevented mouse hover function in mobile platforms. The mouse hover event should not be triggered for mobile platforms and logged an issue regarding this to the Flutter team. + // Issue: https://github.com/flutter/flutter/issues/68690 + onHover: (PointerEvent event) => + _enableMouseHover ? _onHover(event) : null, onExit: (PointerEvent event) { chartState._tooltipBehaviorRenderer._isHovering = false; }, @@ -921,6 +1020,11 @@ class _CircularArea extends StatelessWidget { _performPointerMove(event), child: GestureDetector( onLongPress: _onLongPress, + onTapUp: (TapUpDetails details) { + if (chart.onPointTapped != null && pointRegion != null) { + _calculatePointSeriesIndex(chart, pointRegion); + } + }, onDoubleTap: _onDoubleTap, child: Container( height: constraints.maxHeight, @@ -934,26 +1038,20 @@ class _CircularArea extends StatelessWidget { } /// Find point index for selection - void _calculatePointSeriesIndex(SfCircularChart chart, _Region pointRegion) { + void _calculatePointSeriesIndex(SfCircularChart chart, _Region? pointRegion) { PointTapArgs pointTapArgs; pointTapArgs = PointTapArgs( - pointRegion.seriesIndex, - pointRegion.pointIndex, + pointRegion?.seriesIndex, + pointRegion?.pointIndex, chartState._chartSeries.visibleSeriesRenderers[0]._dataPoints, - pointRegion.pointIndex); - chart.onPointTapped(pointTapArgs); + pointRegion?.pointIndex); + chart.onPointTapped!(pointTapArgs); } - RenderBox renderBox; - _Region pointRegion; - TapDownDetails tapDownDetails; - Offset doubleTapPosition; - /// To perform the pointer down event void _onTapDown(PointerDownEvent event) { ChartTouchInteractionArgs touchArgs; chartState._tooltipBehaviorRenderer._isHovering = false; - // renderBox = context.findRenderObject(); chartState._currentActive = null; chartState._tapPosition = renderBox.globalToLocal(event.position); pointRegion = _getCircularPointRegion(chart, chartState._tapPosition, @@ -961,25 +1059,24 @@ class _CircularArea extends StatelessWidget { doubleTapPosition = chartState._tapPosition; if (chartState._tapPosition != null && pointRegion != null) { chartState._currentActive = _ChartInteraction( - pointRegion.seriesIndex, - pointRegion.pointIndex, + pointRegion?.seriesIndex, + pointRegion?.pointIndex, chartState._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex]._series, + .visibleSeriesRenderers[pointRegion!.seriesIndex]._series, chartState ._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - ._renderPoints[pointRegion.pointIndex], + .visibleSeriesRenderers[pointRegion!.seriesIndex] + ._renderPoints![pointRegion!.pointIndex], pointRegion); } else { //hides the tooltip if the point of interaction is outside circular region of the chart - chartState._tooltipBehaviorRenderer?._tooltipTemplate?.show = false; - chartState._tooltipBehaviorRenderer?._tooltipTemplate?.state - ?.hideOnTimer(); + chartState._tooltipBehaviorRenderer._show = false; + chartState._tooltipBehaviorRenderer._hideOnTimer(); } if (chart.onChartTouchInteractionDown != null) { touchArgs = ChartTouchInteractionArgs(); touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionDown(touchArgs); + chart.onChartTouchInteractionDown!(touchArgs); } } @@ -990,7 +1087,7 @@ class _CircularArea extends StatelessWidget { if (chart.onChartTouchInteractionMove != null) { touchArgs = ChartTouchInteractionArgs(); touchArgs.position = position; - chart.onChartTouchInteractionMove(touchArgs); + chart.onChartTouchInteractionMove!(touchArgs); } } @@ -998,32 +1095,34 @@ class _CircularArea extends StatelessWidget { void _onDoubleTap() { if (doubleTapPosition != null && pointRegion != null) { chartState._currentActive = _ChartInteraction( - pointRegion.seriesIndex, - pointRegion.pointIndex, + pointRegion?.seriesIndex, + pointRegion?.pointIndex, chartState._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex]._series, + .visibleSeriesRenderers[pointRegion!.seriesIndex]._series, chartState ._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - ._renderPoints[pointRegion.pointIndex], + .visibleSeriesRenderers[pointRegion!.seriesIndex] + ._renderPoints![pointRegion!.pointIndex], pointRegion); if (chartState._currentActive != null) { - if (chartState._currentActive.series.explodeGesture == + if (chartState._currentActive?.series.explodeGesture == ActivationMode.doubleTap) { chartState._chartSeries - ._seriesPointExplosion(chartState._currentActive.region); + ._seriesPointExplosion(chartState._currentActive?.region); } } chartState._chartSeries ._seriesPointSelection(pointRegion, ActivationMode.doubleTap); if (chart.tooltipBehavior.enable && chartState._animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.doubleTap) { + chart.tooltipBehavior.activationMode == ActivationMode.doubleTap && + doubleTapPosition != null) { if (chart.tooltipBehavior.builder != null) { _showCircularTooltipTemplate(); } else { chartState._tooltipBehaviorRenderer.onDoubleTap( - doubleTapPosition.dx.toDouble(), doubleTapPosition.dy.toDouble()); + doubleTapPosition!.dx.toDouble(), + doubleTapPosition!.dy.toDouble()); } } } @@ -1033,33 +1132,34 @@ class _CircularArea extends StatelessWidget { void _onLongPress() { if (chartState._tapPosition != null && pointRegion != null) { chartState._currentActive = _ChartInteraction( - pointRegion.seriesIndex, - pointRegion.pointIndex, + pointRegion?.seriesIndex, + pointRegion?.pointIndex, chartState._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex]._series, + .visibleSeriesRenderers[pointRegion!.seriesIndex]._series, chartState ._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - ._renderPoints[pointRegion.pointIndex], + .visibleSeriesRenderers[pointRegion!.seriesIndex] + ._renderPoints![pointRegion!.pointIndex], pointRegion); chartState._chartSeries ._seriesPointSelection(pointRegion, ActivationMode.longPress); if (chartState._currentActive != null) { - if (chartState._currentActive.series.explodeGesture == + if (chartState._currentActive?.series.explodeGesture == ActivationMode.longPress) { chartState._chartSeries - ._seriesPointExplosion(chartState._currentActive.region); + ._seriesPointExplosion(chartState._currentActive?.region); } } if (chart.tooltipBehavior.enable && chartState._animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.longPress) { + chart.tooltipBehavior.activationMode == ActivationMode.longPress && + chartState._tapPosition != null) { if (chart.tooltipBehavior.builder != null) { _showCircularTooltipTemplate(); } else { chartState._tooltipBehaviorRenderer.onLongPress( - chartState._tapPosition.dx.toDouble(), - chartState._tapPosition.dy.toDouble()); + chartState._tapPosition!.dx.toDouble(), + chartState._tapPosition!.dy.toDouble()); } } } @@ -1071,36 +1171,32 @@ class _CircularArea extends StatelessWidget { ChartTouchInteractionArgs touchArgs; final CircularSeriesRenderer seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[0]; - if (chart.onPointTapped != null && pointRegion != null) { - _calculatePointSeriesIndex(chart, pointRegion); - } if (chart.onDataLabelTapped != null) { _triggerCircularDataLabelEvent( chart, seriesRenderer, chartState, chartState._tapPosition); } if (chartState._tapPosition != null) { if (chartState._currentActive != null && - chartState._currentActive.series != null && - chartState._currentActive.series.explodeGesture == + chartState._currentActive!.series != null && + chartState._currentActive!.series.explodeGesture == ActivationMode.singleTap) { chartState._chartSeries - ._seriesPointExplosion(chartState._currentActive.region); + ._seriesPointExplosion(chartState._currentActive!.region); } if (chartState._tapPosition != null && chartState._currentActive != null) { chartState._chartSeries._seriesPointSelection( - chartState._currentActive.region, ActivationMode.singleTap); + chartState._currentActive!.region, ActivationMode.singleTap); } if (chart.tooltipBehavior.enable && chartState._animateCompleted && chart.tooltipBehavior.activationMode == ActivationMode.singleTap && chartState._currentActive != null && - chartState._currentActive.series != null) { + chartState._currentActive!.series != null) { if (chart.tooltipBehavior.builder != null) { _showCircularTooltipTemplate(); } else { - // final RenderBox renderBox = context.findRenderObject(); final Offset position = renderBox.globalToLocal(event.position); chartState._tooltipBehaviorRenderer .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); @@ -1109,7 +1205,7 @@ class _CircularArea extends StatelessWidget { if (chart.onChartTouchInteractionUp != null) { touchArgs = ChartTouchInteractionArgs(); touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionUp(touchArgs); + chart.onChartTouchInteractionUp!(touchArgs); } } chartState._tapPosition = null; @@ -1129,28 +1225,26 @@ class _CircularArea extends StatelessWidget { } if (chartState._tapPosition != null && pointRegion != null) { chartState._currentActive = _ChartInteraction( - pointRegion.seriesIndex, - pointRegion.pointIndex, + pointRegion!.seriesIndex, + pointRegion!.pointIndex, chartState._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex]._series, + .visibleSeriesRenderers[pointRegion!.seriesIndex]._series, chartState ._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - ._renderPoints[pointRegion.pointIndex], + .visibleSeriesRenderers[pointRegion!.seriesIndex] + ._renderPoints![pointRegion!.pointIndex], pointRegion); - } else if (chart.tooltipBehavior?.builder != null) { + } else { //hides the tooltip when the mouse is hovering out of the circular region - chartState._tooltipBehaviorRenderer?._tooltipTemplate?.show = false; - chartState._tooltipBehaviorRenderer?._tooltipTemplate?.state - ?.hideOnTimer(); + chartState._tooltipBehaviorRenderer._hide(); } if (chartState._tapPosition != null) { if (chart.tooltipBehavior.enable && chartState._currentActive != null && - chartState._currentActive.series != null) { + chartState._currentActive!.series != null) { chartState._tooltipBehaviorRenderer._isHovering = true; - if (chart.tooltipBehavior.builder != null && - chartState._animateCompleted) { + chartState._tooltipBehaviorRenderer._timer?.cancel(); + if (chart.tooltipBehavior.builder != null) { _showCircularTooltipTemplate(); } else { final Offset position = renderBox.globalToLocal(event.position); @@ -1158,10 +1252,8 @@ class _CircularArea extends StatelessWidget { .onEnter(position.dx.toDouble(), position.dy.toDouble()); } } else { - chartState?._tooltipBehaviorRenderer?._painter?.prevTooltipValue = null; - chartState?._tooltipBehaviorRenderer?._painter?.currentTooltipValue = - null; - chartState?._tooltipBehaviorRenderer?._painter?.hide(); + chartState._tooltipBehaviorRenderer._prevTooltipValue = null; + chartState._tooltipBehaviorRenderer._currentTooltipValue = null; } } chartState._tapPosition = null; @@ -1169,44 +1261,49 @@ class _CircularArea extends StatelessWidget { /// This method gets executed for showing tooltip when builder is provided in behavior ///the optional parameters will take values once thee public method gets called - void _showCircularTooltipTemplate([int seriesIndex, int pointIndex]) { - final _TooltipTemplate tooltipTemplate = - chartState._tooltipBehaviorRenderer._tooltipTemplate; - chartState._tooltipBehaviorRenderer._tooltipTemplate?._alwaysShow = - chart.tooltipBehavior.shouldAlwaysShow; - if (!chartState._tooltipBehaviorRenderer._isHovering) { + void _showCircularTooltipTemplate([int? seriesIndex, int? pointIndex]) { + final tooltipBehaviorRenderer = chartState._tooltipBehaviorRenderer; + if (!tooltipBehaviorRenderer._isHovering) { //assingning null for the previous and current tooltip values in case of touch interaction - tooltipTemplate?.state?.prevTooltipValue = null; - tooltipTemplate?.state?.currentTooltipValue = null; + tooltipBehaviorRenderer._prevTooltipValue = null; + tooltipBehaviorRenderer._currentTooltipValue = null; } final CircularSeries chartSeries = - chartState?._currentActive?.series ?? chart.series[seriesIndex]; + chartState._currentActive?.series ?? chart.series[seriesIndex!]; final ChartPoint point = pointIndex == null - ? chartState._currentActive.point + ? chartState._currentActive?.point : chartState ._chartSeries.visibleSeriesRenderers[0]._dataPoints[pointIndex]; if (point.isVisible) { - final Offset location = _degreeToPoint(point.midAngle, - (point.innerRadius + point.outerRadius) / 2, point.center); - if (location != null && (chartSeries.enableTooltip ?? true)) { - tooltipTemplate.rect = Rect.fromLTWH(location.dx, location.dy, 0, 0); - tooltipTemplate.template = chart.tooltipBehavior.builder( - chartSeries - .dataSource[pointIndex ?? chartState._currentActive.pointIndex], - point, - chartSeries, - seriesIndex ?? chartState._currentActive.seriesIndex, - pointIndex ?? chartState._currentActive.pointIndex); - if (chartState._tooltipBehaviorRenderer._isHovering) { - //assigning values for previous and current tooltip values when the mouse is hovering - tooltipTemplate.state.prevTooltipValue = - tooltipTemplate.state.currentTooltipValue; - tooltipTemplate.state.currentTooltipValue = TooltipValue( - seriesIndex ?? chartState._currentActive.seriesIndex, - pointIndex ?? chartState._currentActive.pointIndex); + final Offset? location = _degreeToPoint(point.midAngle!, + (point.innerRadius! + point.outerRadius!) / 2, point.center!); + if (location != null && (chartSeries.enableTooltip)) { + tooltipBehaviorRenderer._showLocation = location; + tooltipBehaviorRenderer._renderBox!.boundaryRect = + chartState._chartContainerRect; + tooltipBehaviorRenderer._tooltipTemplate = + chart.tooltipBehavior.builder!( + chartSeries.dataSource![ + pointIndex ?? chartState._currentActive!.pointIndex!], + point, + chartSeries, + seriesIndex ?? chartState._currentActive!.seriesIndex!, + pointIndex ?? chartState._currentActive!.pointIndex!); + if (tooltipBehaviorRenderer._isHovering) { + // assigning values for previous and current tooltip values when the mouse is hovering + tooltipBehaviorRenderer._prevTooltipValue = + tooltipBehaviorRenderer._currentTooltipValue; + tooltipBehaviorRenderer._currentTooltipValue = TooltipValue( + seriesIndex ?? chartState._currentActive!.seriesIndex!, + pointIndex ?? chartState._currentActive!.pointIndex!); + } + if (!tooltipBehaviorRenderer._isHovering) { + tooltipBehaviorRenderer._timer = Timer( + Duration(milliseconds: chart.tooltipBehavior.duration.toInt()), + tooltipBehaviorRenderer._hideTooltipTemplate); } - tooltipTemplate.show = true; - tooltipTemplate?.state?._performTooltip(); + tooltipBehaviorRenderer._show = true; + tooltipBehaviorRenderer._performTooltip(); } } } @@ -1215,7 +1312,7 @@ class _CircularArea extends StatelessWidget { Widget _initializeChart(BoxConstraints constraints, BuildContext context) { _calculateContainerSize(constraints); if (chart.series.isNotEmpty) { - chartState._chartSeries?._calculateAngleAndCenterPositions( + chartState._chartSeries._calculateAngleAndCenterPositions( chartState._chartSeries.visibleSeriesRenderers[0]); } return Container( @@ -1227,7 +1324,8 @@ class _CircularArea extends StatelessWidget { void _calculateContainerSize(BoxConstraints constraints) { final num width = constraints.maxWidth; final num height = constraints.maxHeight; - chartState._chartContainerRect = Rect.fromLTWH(0, 0, width, height); + chartState._chartContainerRect = + Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); final EdgeInsets margin = chart.margin; chartState._chartAreaRect = Rect.fromLTWH( margin.left, @@ -1243,8 +1341,8 @@ class _CircularArea extends StatelessWidget { _renderTemplates(); _bindTooltipWidgets(constraints); chartState._circularArea = this; - renderBox = context.findRenderObject(); - return Container(child: Stack(children: chartState._chartWidgets)); + renderBox = context.findRenderObject() as RenderBox; + return Container(child: Stack(children: chartState._chartWidgets!)); } /// To add chart templates @@ -1266,23 +1364,23 @@ class _CircularArea extends StatelessWidget { series.dataLabelSettings.connectorLineSettings; if (series.dataLabelSettings.isVisible && series.dataLabelSettings.builder != null) { - for (int i = 0; i < seriesRenderer._renderPoints.length; i++) { - point = seriesRenderer._renderPoints[i]; + for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { + point = seriesRenderer._renderPoints![i]; ChartAlignment labelAlign; if (point.isVisible) { - labelWidget = series.dataLabelSettings - .builder(series.dataSource[i], point, series, i, k); + labelWidget = series.dataLabelSettings.builder!( + series.dataSource![i], point, series, i, k); if (series.dataLabelSettings.labelPosition == ChartDataLabelPosition.inside) { - labelLocation = _degreeToPoint(point.midAngle, - (point.innerRadius + point.outerRadius) / 2, point.center); + labelLocation = _degreeToPoint(point.midAngle!, + (point.innerRadius! + point.outerRadius!) / 2, point.center!); labelLocation = Offset(labelLocation.dx, labelLocation.dy); labelAlign = ChartAlignment.center; } else { - final num connectorLength = - _percentToValue(connector.length ?? '10%', point.outerRadius); - labelLocation = _degreeToPoint(point.midAngle, - point.outerRadius + connectorLength, point.center); + final num connectorLength = _percentToValue( + connector.length ?? '10%', point.outerRadius!)!; + labelLocation = _degreeToPoint(point.midAngle!, + point.outerRadius! + connectorLength, point.center!); labelLocation = Offset( point.dataLabelPosition == Position.right ? labelLocation.dx + lineLength + 5 @@ -1308,19 +1406,21 @@ class _CircularArea extends StatelessWidget { } } } - if (chart.annotations != null && chart.annotations.isNotEmpty) { - for (int i = 0; i < chart.annotations.length; i++) { - final CircularChartAnnotation annotation = chart.annotations[i]; + if (chart.annotations != null && chart.annotations!.isNotEmpty) { + for (int i = 0; i < chart.annotations!.length; i++) { + final CircularChartAnnotation annotation = chart.annotations![i]; if (annotation.widget != null) { final double radius = _percentToValue( - annotation.radius, chartState._chartSeries.size / 2) + annotation.radius, chartState._chartSeries.size / 2)! .toDouble(); final Offset point = _degreeToPoint( annotation.angle, radius, chartState._centerLocation); final double annotationHeight = _percentToValue( - annotation.height, chartState._chartSeries.size / 2); + annotation.height, chartState._chartSeries.size / 2)! + .toDouble(); final double annotationWidth = _percentToValue( - annotation.width, chartState._chartSeries.size / 2); + annotation.width, chartState._chartSeries.size / 2)! + .toDouble(); final _ChartTemplateInfo templateInfo = _ChartTemplateInfo( key: GlobalKey(), templateType: 'Annotation', @@ -1333,7 +1433,7 @@ class _CircularArea extends StatelessWidget { height: annotationHeight, width: annotationWidth, child: annotation.widget) - : annotation.widget, + : annotation.widget!, pointIndex: i, animationDuration: 500, location: point); @@ -1347,7 +1447,7 @@ class _CircularArea extends StatelessWidget { void _renderTemplates() { if (chartState._templates.isNotEmpty) { for (int i = 0; i < chartState._templates.length; i++) { - chartState._templates[i].animationDuration = !chartState._initialRender + chartState._templates[i].animationDuration = !chartState._initialRender! ? 0 : chartState._templates[i].animationDuration; } @@ -1355,39 +1455,54 @@ class _CircularArea extends StatelessWidget { templates: chartState._templates, render: chartState._animateCompleted, chartState: chartState); - chartState._chartWidgets.add(chartState._chartTemplate); + chartState._chartWidgets!.add(chartState._chartTemplate!); } } /// To add tooltip widgets to chart void _bindTooltipWidgets(BoxConstraints constraints) { chart.tooltipBehavior._chartState = chartState; + final SfChartThemeData _chartTheme = chartState._chartTheme; if (chart.tooltipBehavior.enable) { - final List tooltipWidgets = []; - if (chart.tooltipBehavior.builder != null) { - chartState._tooltipBehaviorRenderer._tooltipTemplate = _TooltipTemplate( - show: false, - clipRect: chartState._chartContainerRect, - tooltipBehavior: chart.tooltipBehavior, - duration: chart.tooltipBehavior.duration, - chartState: chartState); - tooltipWidgets - .add(chartState._tooltipBehaviorRenderer._tooltipTemplate); - } else { - chartState._tooltipBehaviorRenderer._chartTooltip = - _ChartTooltipRenderer(chartState: chartState); - tooltipWidgets.add(chartState._tooltipBehaviorRenderer._chartTooltip); - } - final Widget uiWidget = - IgnorePointer(ignoring: true, child: Stack(children: tooltipWidgets)); - chartState._chartWidgets.add(uiWidget); + final tooltip = chart.tooltipBehavior; + chartState._tooltipBehaviorRenderer._prevTooltipValue = + chartState._tooltipBehaviorRenderer._currentTooltipValue = null; + chartState._tooltipBehaviorRenderer._chartTooltip = SfTooltip( + color: tooltip.color ?? _chartTheme.tooltipColor, + key: GlobalKey(), + textStyle: tooltip.textStyle, + animationDuration: tooltip.animationDuration, + enable: tooltip.enable, + opacity: tooltip.opacity, + borderColor: tooltip.borderColor, + borderWidth: tooltip.borderWidth, + duration: tooltip.duration, + shouldAlwaysShow: tooltip.shouldAlwaysShow, + elevation: tooltip.elevation, + canShowMarker: tooltip.canShowMarker, + textAlignment: tooltip.textAlignment, + decimalPlaces: tooltip.decimalPlaces, + labelColor: tooltip.textStyle.color ?? _chartTheme.tooltipLabelColor, + header: tooltip.header, + format: tooltip.format, + builder: tooltip.builder, + shadowColor: tooltip.shadowColor, + onTooltipRender: chart.onTooltipRender != null + ? chartState._tooltipBehaviorRenderer._tooltipRenderingEvent + : null); + final Widget uiWidget = IgnorePointer( + ignoring: true, + child: Stack(children: [ + chartState._tooltipBehaviorRenderer._chartTooltip! + ])); + chartState._chartWidgets!.add(uiWidget); } } /// To add series widgets in chart void _bindSeriesWidgets(BuildContext context) { - CustomPainter seriesPainter; - Animation seriesAnimation; + late CustomPainter seriesPainter; + Animation? seriesAnimation; chartState._animateCompleted = false; chartState._chartWidgets ??= []; CircularSeries series; @@ -1407,9 +1522,9 @@ class _CircularArea extends StatelessWidget { selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer = SelectionBehaviorRenderer(selectionBehavior, chart, chartState); selectionBehaviorRenderer._selectionRenderer ??= _SelectionRenderer(); - selectionBehaviorRenderer._selectionRenderer.chart = chart; - selectionBehaviorRenderer._selectionRenderer._chartState = chartState; - selectionBehaviorRenderer._selectionRenderer.seriesRenderer = + selectionBehaviorRenderer._selectionRenderer!.chart = chart; + selectionBehaviorRenderer._selectionRenderer!._chartState = chartState; + selectionBehaviorRenderer._selectionRenderer!.seriesRenderer = seriesRenderer; if (series.initialSelectedDataIndexes.isNotEmpty) { for (int index = 0; @@ -1423,7 +1538,7 @@ class _CircularArea extends StatelessWidget { if (series.animationDuration > 0 && !chartState._didSizeChange && (chartState._oldDeviceOrientation == chartState._deviceOrientation) && - (chartState._initialRender || + (chartState._initialRender! || (chartState._widgetNeedUpdate && seriesRenderer._needsAnimation) || chartState._isLegendToggled)) { @@ -1437,11 +1552,10 @@ class _CircularArea extends StatelessWidget { if (status == AnimationStatus.completed) { chartState._animateCompleted = true; if (chartState._renderDataLabel != null) { - chartState._renderDataLabel.state.render(); + chartState._renderDataLabel!.state.render(); } - if (chartState._chartTemplate != null && - chartState._chartTemplate.state != null) { - chartState._chartTemplate.state.templateRender(); + if (chartState._chartTemplate != null) { + chartState._chartTemplate!.state.templateRender(); } } })); @@ -1480,11 +1594,11 @@ class _CircularArea extends StatelessWidget { seriesAnimation: seriesAnimation, notifier: chartState._seriesRepaintNotifier); } - chartState._chartWidgets + chartState._chartWidgets! .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); chartState._renderDataLabel = _CircularDataLabelRenderer( circularChartState: chartState, show: chartState._animateCompleted); - chartState._chartWidgets.add(chartState._renderDataLabel); + chartState._chartWidgets!.add(chartState._renderDataLabel!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart index 0cde9f029..ae8f75933 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart @@ -9,15 +9,15 @@ class _CircularSeries { //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this.. SfCircularChart get chart => _chartState._chart; - CircularSeries currentSeries; + late CircularSeries currentSeries; - num size; + late num size; - num sumOfGroup; + late num sumOfGroup; - _Region explodedRegion; + late _Region explodedRegion; - _Region selectRegion; + late _Region selectRegion; List visibleSeriesRenderers = []; @@ -31,14 +31,14 @@ class _CircularSeries { series = seriesRenderer._series = chart.series[0]; seriesRenderer._dataPoints = >[]; seriesRenderer._needsAnimation = false; - final List> _oldPoints = + final List>? _oldPoints = _chartState._prevSeriesRenderer?._oldRenderPoints; - final CircularSeries oldSeries = + final CircularSeries? oldSeries = _chartState._prevSeriesRenderer?._series; int oldPointIndex = 0; if (series.dataSource != null) { for (int pointIndex = 0; - pointIndex < series.dataSource.length; + pointIndex < series.dataSource!.length; pointIndex++) { final ChartPoint currentPoint = _getCircularPoint(seriesRenderer, pointIndex); @@ -119,11 +119,13 @@ class _CircularSeries { ? 1 : (secondPoint.sortValue == null ? -1 - : (firstPoint.sortValue is String - ? secondPoint.sortValue + : (firstPoint.sortValue! is String + ? secondPoint.sortValue! .toLowerCase() - .compareTo(firstPoint.sortValue.toLowerCase()) - : secondPoint.sortValue.compareTo(firstPoint.sortValue))); + .compareTo(firstPoint.sortValue!.toLowerCase()) + : secondPoint.sortValue!.compareTo(firstPoint.sortValue))); + } else { + return 0; } }); } @@ -133,10 +135,10 @@ class _CircularSeries { final List seriesRenderers = _chartState._chartSeries.visibleSeriesRenderers; final CircularSeriesRenderer seriesRenderer = seriesRenderers[0]; - final num groupValue = currentSeries.groupTo; - final CircularChartGroupMode mode = currentSeries.groupMode; - bool isYText; - final ChartIndexedValueMapper textMapping = + final double? groupValue = currentSeries.groupTo; + final CircularChartGroupMode? mode = currentSeries.groupMode; + late bool isYText; + final ChartIndexedValueMapper? textMapping = currentSeries.dataLabelMapper; ChartPoint point; sumOfGroup = 0; @@ -155,22 +157,22 @@ class _CircularSeries { if (mode == CircularChartGroupMode.point && groupValue != null && i >= groupValue) { - sumOfGroup += point.y.abs(); + sumOfGroup += point.y!.abs(); } else if (mode == CircularChartGroupMode.value && groupValue != null && - point.y <= groupValue) { - sumOfGroup += point.y.abs(); + point.y! <= groupValue) { + sumOfGroup += point.y!.abs(); } else { - seriesRenderer._renderPoints.add(point); + seriesRenderer._renderPoints!.add(point); } } } if (sumOfGroup > 0) { - seriesRenderer._renderPoints + seriesRenderer._renderPoints! .add(ChartPoint('Others', sumOfGroup)); seriesRenderer - ._renderPoints[seriesRenderer._renderPoints.length - 1].text = + ._renderPoints![seriesRenderer._renderPoints!.length - 1].text = isYText == true ? 'Others : ' + sumOfGroup.toString() : 'Others'; } _setPointStyle(seriesRenderer); @@ -181,7 +183,7 @@ class _CircularSeries { final EmptyPointSettings empty = currentSeries.emptyPointSettings; final List palette = chart.palette; int i = 0; - for (final ChartPoint point in seriesRenderer._renderPoints) { + for (final ChartPoint point in seriesRenderer._renderPoints!) { point.fill = point.isEmpty && empty.color != null ? empty.color : point.pointColor ?? palette[i % palette.length]; @@ -243,13 +245,13 @@ class _CircularSeries { seriesRenderer._seriesType == 'doughnut')) { final Rect areaRect = _chartState._chartAreaRect; bool needExecute = true; - double radius = seriesRenderer._currentRadius; + double radius = seriesRenderer._currentRadius.toDouble(); while (needExecute) { radius += 1; final Rect circularRect = _getArcPath( 0.0, radius, - seriesRenderer._center, + seriesRenderer._center!, seriesRenderer._start, seriesRenderer._end, seriesRenderer._totalAngle, @@ -270,14 +272,14 @@ class _CircularSeries { seriesRenderer._rect = _getArcPath( 0.0, seriesRenderer._currentRadius, - seriesRenderer._center, + seriesRenderer._center!, seriesRenderer._start, seriesRenderer._end, seriesRenderer._totalAngle, chart, true) .getBounds(); - for (final ChartPoint point in seriesRenderer._renderPoints) { + for (final ChartPoint point in seriesRenderer._renderPoints!) { point.outerRadius = seriesRenderer._currentRadius; } } @@ -289,16 +291,16 @@ class _CircularSeries { num pointEndAngle; num pointStartAngle = seriesRenderer._start; final num innerRadius = seriesRenderer._currentInnerRadius; - for (final ChartPoint point in seriesRenderer._renderPoints) { + for (final ChartPoint point in seriesRenderer._renderPoints!) { if (point.isVisible) { point.innerRadius = (seriesRenderer._seriesType == 'doughnut') ? innerRadius : 0.0; - point.degree = (point.y.abs() / + point.degree = (point.y!.abs() / (seriesRenderer._sumOfPoints != 0 ? seriesRenderer._sumOfPoints : 1)) * seriesRenderer._totalAngle; - pointEndAngle = pointStartAngle + point.degree; + pointEndAngle = pointStartAngle + point.degree!; point.startAngle = pointStartAngle; point.endAngle = pointEndAngle; point.midAngle = (pointStartAngle + pointEndAngle) / 2; @@ -306,7 +308,7 @@ class _CircularSeries { point.radius, point, seriesRenderer._currentRadius); point.center = _needExplode(pointIndex, currentSeries) ? _findExplodeCenter( - point.midAngle, seriesRenderer, point.outerRadius) + point.midAngle!, seriesRenderer, point.outerRadius!) : seriesRenderer._center; if (currentSeries.dataLabelSettings != null) { _findDataLabelPosition(point); @@ -322,12 +324,12 @@ class _CircularSeries { bool isNeedExplode = false; final SfCircularChartState chartState = _chartState; if (series.explode) { - if (chartState._initialRender) { + if (chartState._initialRender!) { if (pointIndex == series.explodeIndex || series.explodeAll) { chartState._explodedPoints.add(pointIndex); isNeedExplode = true; } - } else if (chartState._widgetNeedUpdate || chartState._isLegendToggled) { + } else if (!chartState._initialRender! || chartState._isLegendToggled) { isNeedExplode = chartState._explodedPoints.contains(pointIndex); } } @@ -337,9 +339,9 @@ class _CircularSeries { /// To find sum of points in the series void _findSumOfPoints(CircularSeriesRenderer seriesRenderer) { seriesRenderer._sumOfPoints = 0; - for (final ChartPoint point in seriesRenderer._renderPoints) { + for (final ChartPoint point in seriesRenderer._renderPoints!) { if (point.isVisible) { - seriesRenderer._sumOfPoints += point.y.abs(); + seriesRenderer._sumOfPoints += point.y!.abs(); } } } @@ -378,9 +380,9 @@ class _CircularSeries { final Rect chartAreaRect = chartState._chartAreaRect; size = min(chartAreaRect.width, chartAreaRect.height); seriesRenderer._currentRadius = - _percentToValue(currentSeries.radius, size / 2).toDouble(); + _percentToValue(currentSeries.radius, size / 2)!.toDouble(); seriesRenderer._currentInnerRadius = _percentToValue( - currentSeries.innerRadius, seriesRenderer._currentRadius); + currentSeries.innerRadius, seriesRenderer._currentRadius)!; } /// To calculate center location of chart @@ -389,54 +391,54 @@ class _CircularSeries { final Rect chartAreaRect = chartState._chartAreaRect; final Rect chartContainerRect = chartState._chartContainerRect; seriesRenderer._center = Offset( - _percentToValue(chart.centerX, chartAreaRect.width).toDouble(), - _percentToValue(chart.centerY, chartAreaRect.height).toDouble()); + _percentToValue(chart.centerX, chartAreaRect.width)!.toDouble(), + _percentToValue(chart.centerY, chartAreaRect.height)!.toDouble()); seriesRenderer._center = Offset( - seriesRenderer._center.dx + + seriesRenderer._center!.dx + (chartContainerRect.width - chartAreaRect.width).abs() / 2, - seriesRenderer._center.dy + + seriesRenderer._center!.dy + (chartContainerRect.height - chartAreaRect.height).abs() / 2); - chartState._centerLocation = seriesRenderer._center; + chartState._centerLocation = seriesRenderer._center!; } /// To find explode center position Offset _findExplodeCenter( num midAngle, CircularSeriesRenderer seriesRenderer, num currentRadius) { final num explodeCenter = - _percentToValue(seriesRenderer._series.explodeOffset, currentRadius); - return _degreeToPoint(midAngle, explodeCenter, seriesRenderer._center); + _percentToValue(seriesRenderer._series.explodeOffset, currentRadius)!; + return _degreeToPoint(midAngle, explodeCenter, seriesRenderer._center!); } /// To calculate and return point radius num _calculatePointRadius( dynamic value, ChartPoint point, num radius) { if (value != null) { - radius = value != null ? _percentToValue(value, size / 2) : radius; + radius = value != null ? _percentToValue(value, size / 2)! : radius; } return radius; } /// To add selection points to selection list - void _seriesPointSelection(_Region pointRegion, ActivationMode mode, - [int pointIndex, int seriesIndex]) { + void _seriesPointSelection(_Region? pointRegion, ActivationMode mode, + [int? pointIndex, int? seriesIndex]) { bool isPointAlreadySelected = false; pointIndex = pointRegion != null ? pointRegion.pointIndex : pointIndex; seriesIndex = pointRegion != null ? pointRegion.seriesIndex : seriesIndex; final SfCircularChartState chartState = _chartState; final CircularSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; - int currentSelectedIndex; + _chartState._chartSeries.visibleSeriesRenderers[seriesIndex!]; + int? currentSelectedIndex; if (seriesRenderer._isSelectionEnable && mode == chart.selectionGesture) { if (chartState._selectionData.isNotEmpty) { if (!chart.enableMultiSelection && chartState._selectionData.isNotEmpty && chartState._selectionData.length > 1) { if (chartState._selectionData.contains(pointIndex)) { - currentSelectedIndex = pointIndex; + currentSelectedIndex = pointIndex!; } chartState._selectionData.clear(); if (currentSelectedIndex != null) { - chartState._selectionData.add(pointIndex); + chartState._selectionData.add(pointIndex!); } } for (int i = 0; i < chartState._selectionData.length; i++) { @@ -455,19 +457,19 @@ class _CircularSeries { } } if (!isPointAlreadySelected) { - chartState._selectionData.add(pointIndex); + chartState._selectionData.add(pointIndex!); chartState._seriesRepaintNotifier.value++; } } } - void _seriesPointExplosion(_Region pointRegion) { + void _seriesPointExplosion(_Region? pointRegion) { bool existExplodedRegion = false; final SfCircularChartState chartState = _chartState; final CircularSeriesRenderer seriesRenderer = _chartState - ._chartSeries.visibleSeriesRenderers[pointRegion.seriesIndex]; + ._chartSeries.visibleSeriesRenderers[pointRegion!.seriesIndex]; final ChartPoint point = - seriesRenderer._renderPoints[pointRegion.pointIndex]; + seriesRenderer._renderPoints![pointRegion.pointIndex]; if (seriesRenderer._series.explode) { if (chartState._explodedPoints.isNotEmpty) { if (chartState._explodedPoints.length == 1 && @@ -478,33 +480,37 @@ class _CircularSeries { chartState._explodedPoints.indexOf(pointRegion.pointIndex); chartState._explodedPoints.removeAt(index); chartState._seriesRepaintNotifier.value++; + chartState._renderDataLabel!.state.dataLabelRepaintNotifier.value++; } else if (seriesRenderer._series.explodeAll && chartState._explodedPoints.length > 1 && chartState._explodedPoints.contains(pointRegion.pointIndex)) { for (int i = 0; i < chartState._explodedPoints.length; i++) { final int explodeIndex = chartState._explodedPoints[i]; - seriesRenderer._renderPoints[explodeIndex].center = + seriesRenderer._renderPoints![explodeIndex].center = seriesRenderer._center; chartState._explodedPoints.removeAt(i); i--; } existExplodedRegion = true; chartState._seriesRepaintNotifier.value++; + chartState._renderDataLabel!.state.dataLabelRepaintNotifier.value++; } else if (chartState._explodedPoints.length == 1) { for (int i = 0; i < chartState._explodedPoints.length; i++) { final int explodeIndex = chartState._explodedPoints[i]; - seriesRenderer._renderPoints[explodeIndex].center = + seriesRenderer._renderPoints![explodeIndex].center = seriesRenderer._center; chartState._explodedPoints.removeAt(i); chartState._seriesRepaintNotifier.value++; + chartState._renderDataLabel!.state.dataLabelRepaintNotifier.value++; } } } if (!existExplodedRegion) { point.center = _findExplodeCenter( - point.midAngle, seriesRenderer, point.outerRadius); + point.midAngle!, seriesRenderer, point.outerRadius!); chartState._explodedPoints.add(pointRegion.pointIndex); chartState._seriesRepaintNotifier.value++; + chartState._renderDataLabel!.state.dataLabelRepaintNotifier.value++; } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart index 75f9d418a..a81f79535 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart @@ -19,37 +19,39 @@ class CircularSeries extends ChartSeries this.xValueMapper, this.yValueMapper, this.pointColorMapper, + this.pointShaderMapper, this.pointRadiusMapper, this.dataLabelMapper, this.sortFieldValueMapper, - int startAngle, - int endAngle, - String radius, - String innerRadius, - bool explode, - bool explodeAll, + int? startAngle, + int? endAngle, + String? radius, + String? innerRadius, + bool? explode, + bool? explodeAll, this.explodeIndex, - ActivationMode explodeGesture, - String explodeOffset, + ActivationMode? explodeGesture, + String? explodeOffset, this.groupTo, this.groupMode, - String gap, - double opacity, - EmptyPointSettings emptyPointSettings, - Color borderColor, - double borderWidth, - DataLabelSettings dataLabelSettings, - bool enableTooltip, - bool enableSmartLabels, + this.pointRenderMode, + String? gap, + double? opacity, + EmptyPointSettings? emptyPointSettings, + Color? borderColor, + double? borderWidth, + DataLabelSettings? dataLabelSettings, + bool? enableTooltip, + bool? enableSmartLabels, this.name, - double animationDuration, + double? animationDuration, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - SortingOrder sortingOrder, - LegendIconType legendIconType, - CornerStyle cornerStyle, - List initialSelectedDataIndexes}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + SortingOrder? sortingOrder, + LegendIconType? legendIconType, + CornerStyle? cornerStyle, + List? initialSelectedDataIndexes}) : startAngle = startAngle ?? 0, animationDuration = animationDuration ?? 1500, endAngle = endAngle ?? 360, @@ -128,7 +130,7 @@ class CircularSeries extends ChartSeries @override final DataLabelSettings dataLabelSettings; - _ChartSeriesRender _renderer; + _ChartSeriesRender? _renderer; ///A collection of data required for rendering the series. /// @@ -155,7 +157,7 @@ class CircularSeries extends ChartSeries ///} ///``` @override - final List dataSource; + final List? dataSource; ///Maps the field name, which will be considered as x-values. /// @@ -186,7 +188,7 @@ class CircularSeries extends ChartSeries ///} ///``` @override - final ChartIndexedValueMapper xValueMapper; + final ChartIndexedValueMapper? xValueMapper; ///Maps the field name, which will be considered as y-values. /// @@ -217,7 +219,7 @@ class CircularSeries extends ChartSeries ///} ///``` @override - final ChartIndexedValueMapper yValueMapper; + final ChartIndexedValueMapper? yValueMapper; ///Maps the field name, which will be considered as x-values. /// @@ -248,7 +250,56 @@ class CircularSeries extends ChartSeries ///} ///``` @override - final ChartIndexedValueMapper pointColorMapper; + final ChartIndexedValueMapper? pointColorMapper; + + ///Returns the shaders to fill each data point. + /// + ///The data points of pie, doughnut and radial bar charts can be filled with [gradient](https://api.flutter.dev/flutter/dart-ui/Gradient-class.html) + /// (linear, radial and sweep gradient) and [image shaders](https://api.flutter.dev/flutter/dart-ui/ImageShader-class.html). + /// + ///A shader specified in a data source cell will be applied to that specific data point. Also, a data point may have gradient + /// and another data point may have image shader. + /// + ///The user can also get the data, index, color and rect values of the specific data point from [ChartShaderMapper] and + /// can use in this method, for creating shaders. + /// + ///Defaults to `null`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// dataSource: [ + /// ChartData('USA', 10, ui.Gradient.radial(Offset(112.4, 140.0), 90, [ + /// Colors.pink, + /// Colors.red, + /// ], [ + /// 0.25, + /// 0.5, + /// ]),), + /// ChartData('China', 11, ui.Gradient.sweep(Offset(112.4, 140.0),[ + /// Colors.pink, + /// Colors.red, + /// ], [ + /// 0.25, + /// 0.5, + /// ]),), + /// ], + /// pointShaderMapper: (ChartData data, _, Color color, Rect rect) => data.pointShader, + /// ), + /// ], + /// )); + ///} + ///class ChartData { + /// ChartData(this.xVal, this.yVal, [this.pointColor]); + /// final String xVal; + /// final int yVal; + /// final Shader pointShader; + ///} + ///``` + final ChartShaderMapper? pointShaderMapper; ///Maps the field name, which will be considered for calculating the radius of /// all the data points. @@ -279,7 +330,7 @@ class CircularSeries extends ChartSeries /// final String radius; ///} ///``` - final ChartIndexedValueMapper pointRadiusMapper; + final ChartIndexedValueMapper? pointRadiusMapper; ///Maps the field name, which will be considered as text for the data points. /// @@ -310,7 +361,7 @@ class CircularSeries extends ChartSeries ///} ///``` @override - final ChartIndexedValueMapper dataLabelMapper; + final ChartIndexedValueMapper? dataLabelMapper; ///Field in the data source for performing sorting. Sorting will be performed ///based on this field. @@ -342,7 +393,7 @@ class CircularSeries extends ChartSeries ///} ///``` @override - final ChartIndexedValueMapper sortFieldValueMapper; + final ChartIndexedValueMapper? sortFieldValueMapper; ///Data label placement without collision. /// @@ -650,7 +701,7 @@ class CircularSeries extends ChartSeries /// )); ///} ///``` - final int explodeIndex; + final int? explodeIndex; ///Offset of exploded slice. The value ranges from 0% to 100%. /// @@ -712,7 +763,7 @@ class CircularSeries extends ChartSeries /// )); ///} ///``` - final double groupTo; + final double? groupTo; ///Slice can also be grouped based on the data points value or based on index. /// @@ -733,7 +784,24 @@ class CircularSeries extends ChartSeries /// )); ///} ///``` - final CircularChartGroupMode groupMode; + final CircularChartGroupMode? groupMode; + + ///Defines the painting mode of the data points. + /// + ///The data points in pie and doughnut chart can be filled either with solid colors or with sweep gradient + /// by using this property. + /// + ///* If `PointRenderMode.segment` is specified, the data points are filled with solid colors from palette + /// or with the colors mentioned in [pointColorMapping] property. + ///* If `PointRenderMode.gradient` is specified, a sweep gradient is formed with the solid colors and fills + /// the data points. + /// + ///`Note:` This property is applicable only if the [onCreateShader] or [pointShaderMapper] is null. + /// + ///Also refer [PointRenderMode]. + /// + ///Defaults to `pointRenderMode.segment`. + final PointRenderMode? pointRenderMode; ///Specifies the gap between the radial bars in percentage. /// @@ -792,7 +860,7 @@ class CircularSeries extends ChartSeries ///} ///``` @override - final String name; + final String? name; ///Duration for animating the data points. /// @@ -859,7 +927,7 @@ class CircularSeries extends ChartSeries /// )); ///} ///``` - final ValueKey key; + final ValueKey? key; ///Used to create the renderer for custom series. /// @@ -892,7 +960,7 @@ class CircularSeries extends ChartSeries /// // custom implementation here... /// } ///``` - final ChartSeriesRendererFactory onCreateRenderer; + final ChartSeriesRendererFactory? onCreateRenderer; ///Triggers when the series renderer is created. @@ -919,16 +987,16 @@ class CircularSeries extends ChartSeries /// )); ///} ///``` - final CircularSeriesRendererCreatedCallback onRendererCreated; + final CircularSeriesRendererCreatedCallback? onRendererCreated; /// To calculate empty point values @override void calculateEmptyPointValue( int pointIndex, ChartPoint currentPoint, - [CircularSeriesRenderer seriesRenderer]) { + [CircularSeriesRenderer? seriesRenderer]) { final EmptyPointSettings empty = emptyPointSettings; - final List> dataPoints = seriesRenderer._dataPoints; - final int pointLength = dataPoints.length; + final List>? dataPoints = seriesRenderer?._dataPoints; + final int pointLength = dataPoints!.length; final ChartPoint point = dataPoints[pointIndex]; if (point.y == null) { switch (empty.mode) { @@ -958,17 +1026,17 @@ class CircularSeries extends ChartSeries } /// To get visible point index -int _getVisiblePointIndex( - List> points, String loc, int index) { +int? _getVisiblePointIndex( + List?> points, String loc, int index) { if (loc == 'before') { for (int i = index; i >= 0; i--) { - if (points[i - 1].isVisible) { + if (points[i - 1]!.isVisible) { return i - 1; } } } else { for (int i = index; i < points.length; i++) { - if (points[i + 1].isVisible) { + if (points[i + 1]!.isVisible) { return i + 1; } } @@ -978,7 +1046,7 @@ int _getVisiblePointIndex( abstract class _CircularChartSegment { /// To get point color of current point - Color getPointColor( + Color? getPointColor( CircularSeriesRenderer seriesRenderer, ChartPoint point, int pointIndex, @@ -989,7 +1057,7 @@ abstract class _CircularChartSegment { /// To get opacity of current point double getOpacity( CircularSeriesRenderer seriesRenderer, - ChartPoint point, + ChartPoint? point, int pointIndex, int seriesIndex, double opacity); @@ -997,18 +1065,18 @@ abstract class _CircularChartSegment { /// To get Stroke color of current point Color getPointStrokeColor( CircularSeriesRenderer seriesRenderer, - ChartPoint point, + ChartPoint? point, int pointIndex, int seriesIndex, Color strokeColor); /// To get Stroke width of current point - double getPointStrokeWidth( + num getPointStrokeWidth( CircularSeriesRenderer seriesRenderer, - ChartPoint point, + ChartPoint? point, int pointIndex, int seriesIndex, - double strokeWidth); + num strokeWidth); } abstract class _LabelSegment { @@ -1030,8 +1098,8 @@ abstract class _LabelSegment { SfCircularChartState _chartState); /// To get data label color - Color getDataLabelColor(CircularSeriesRenderer seriesRenderer, - ChartPoint point, int pointIndex, int seriesIndex, Color color); + Color? getDataLabelColor(CircularSeriesRenderer seriesRenderer, + ChartPoint point, int pointIndex, int seriesIndex, Color? color); /// To get the data label stroke color Color getDataLabelStrokeColor( @@ -1055,20 +1123,20 @@ class _ChartSeriesRender with _CircularChartSegment, _LabelSegment { /// To get point color @override - Color getPointColor( + Color? getPointColor( CircularSeriesRenderer seriesRenderer, ChartPoint point, int pointIndex, int seriesIndex, Color color, double opacity) => - color?.withOpacity(opacity); + color.withOpacity(opacity); /// To get point stroke color @override Color getPointStrokeColor( CircularSeriesRenderer seriesRenderer, - ChartPoint point, + ChartPoint? point, int pointIndex, int seriesIndex, Color strokeColor) => @@ -1076,12 +1144,12 @@ class _ChartSeriesRender with _CircularChartSegment, _LabelSegment { /// To get point stroke width @override - double getPointStrokeWidth( + num getPointStrokeWidth( CircularSeriesRenderer seriesRenderer, - ChartPoint point, + ChartPoint? point, int pointIndex, int seriesIndex, - double strokeWidth) => + num strokeWidth) => strokeWidth; /// To return label text @@ -1118,12 +1186,12 @@ class _ChartSeriesRender with _CircularChartSegment, _LabelSegment { /// To return label color @override - Color getDataLabelColor( + Color? getDataLabelColor( CircularSeriesRenderer seriesRenderer, ChartPoint point, int pointIndex, int seriesIndex, - Color color) => + Color? color) => color; /// To return label stroke color @@ -1133,7 +1201,7 @@ class _ChartSeriesRender with _CircularChartSegment, _LabelSegment { ChartPoint point, int pointIndex, int seriesIndex, - Color strokeColor) => + Color? strokeColor) => strokeColor ?? point.fill; /// To return label stroke width @@ -1150,7 +1218,7 @@ class _ChartSeriesRender with _CircularChartSegment, _LabelSegment { @override double getOpacity( CircularSeriesRenderer seriesRenderer, - ChartPoint point, + ChartPoint? point, int pointIndex, int seriesIndex, double opacity) => @@ -1159,52 +1227,59 @@ class _ChartSeriesRender with _CircularChartSegment, _LabelSegment { /// Creates a series renderer for Circular series class CircularSeriesRenderer extends ChartSeriesRenderer { - CircularSeries _series; + late CircularSeries _series; - String _seriesType; + late String _seriesType; - List> _dataPoints; + late List> _dataPoints; - List> _renderPoints; + List>? _renderPoints; - List> _oldRenderPoints; + List>? _oldRenderPoints; - num _sumOfPoints; + late num _sumOfPoints; - num _start; + late num _start; - num _end; + late num _end; - num _totalAngle; + late num _totalAngle; - num _currentRadius; + late num _currentRadius; - num _currentInnerRadius; + late num _currentInnerRadius; - Offset _center; + Offset? _center; - List<_Region> _pointRegions; + late List<_Region> _pointRegions; // ignore:unused_field - Rect _rect; + late Rect _rect; - SelectionArgs _selectionArgs; + // Path saved for radial bar series + List _renderPaths = []; + + List _renderList = []; + + num? _innerRadialradius; + + SelectionArgs? _selectionArgs; //Determines whether there is a need for animation - bool _needsAnimation; + late bool _needsAnimation; ///We can redraw the series with updating or creating new points by using this controller.If we need to access the redrawing methods ///in this before we must get the ChartSeriesController onRendererCreated event. - CircularSeriesController _controller; + CircularSeriesController? _controller; - SfCircularChartState _chartState; + late SfCircularChartState _chartState; /// Repaint notifier for series - ValueNotifier _repaintNotifier; + late ValueNotifier _repaintNotifier; - DataLabelSettingsRenderer _dataLabelSettingsRenderer; + late DataLabelSettingsRenderer _dataLabelSettingsRenderer; - SelectionBehaviorRenderer _selectionBehaviorRenderer; + late SelectionBehaviorRenderer _selectionBehaviorRenderer; dynamic _selectionBehavior; @@ -1212,49 +1287,49 @@ class CircularSeriesRenderer extends ChartSeriesRenderer { bool _isSelectionEnable = false; /// To set style properties for selected points - _StyleOptions _selectPoint( + _StyleOptions? _selectPoint( int currentPointIndex, CircularSeriesRenderer seriesRenderer, SfCircularChart chart, - ChartPoint point) { - _StyleOptions pointStyle; + ChartPoint? point) { + _StyleOptions? pointStyle; final dynamic selection = _series.selectionBehavior.enable ? _series.selectionBehavior : _series.selectionSettings; - const num seriesIndex = 0; + const int seriesIndex = 0; if (selection.enable) { if (_chartState._selectionData.isNotEmpty) { for (int i = 0; i < _chartState._selectionData.length; i++) { final int selectionIndex = _chartState._selectionData[i]; if (chart.onSelectionChanged != null) { - chart.onSelectionChanged(_getSelectionEventArgs( + chart.onSelectionChanged!(_getSelectionEventArgs( seriesRenderer, seriesIndex, selectionIndex)); } if (currentPointIndex == selectionIndex) { pointStyle = _StyleOptions( - seriesRenderer._selectionArgs != null - ? seriesRenderer._selectionArgs.selectedColor + fill: seriesRenderer._selectionArgs != null + ? seriesRenderer._selectionArgs!.selectedColor : selection.selectedColor, - seriesRenderer._selectionArgs != null - ? seriesRenderer._selectionArgs.selectedBorderWidth + strokeWidth: seriesRenderer._selectionArgs != null + ? seriesRenderer._selectionArgs!.selectedBorderWidth : selection.selectedBorderWidth, - seriesRenderer._selectionArgs != null - ? seriesRenderer._selectionArgs.selectedBorderColor + strokeColor: seriesRenderer._selectionArgs != null + ? seriesRenderer._selectionArgs!.selectedBorderColor : selection.selectedBorderColor, - selection.selectedOpacity); + opacity: selection.selectedOpacity); break; } else if (i == _chartState._selectionData.length - 1) { pointStyle = _StyleOptions( - seriesRenderer._selectionArgs != null - ? seriesRenderer._selectionArgs.unselectedColor + fill: seriesRenderer._selectionArgs != null + ? seriesRenderer._selectionArgs!.unselectedColor : selection.unselectedColor, - seriesRenderer._selectionArgs != null - ? _selectionArgs.unselectedBorderWidth + strokeWidth: seriesRenderer._selectionArgs != null + ? _selectionArgs!.unselectedBorderWidth : selection.unselectedBorderWidth, - seriesRenderer._selectionArgs != null - ? seriesRenderer._selectionArgs.unselectedBorderColor + strokeColor: seriesRenderer._selectionArgs != null + ? seriesRenderer._selectionArgs!.unselectedBorderColor : selection.unselectedBorderColor, - selection.unselectedOpacity); + opacity: selection.unselectedOpacity); } } } @@ -1264,11 +1339,14 @@ class CircularSeriesRenderer extends ChartSeriesRenderer { /// To perform slection event and return Selection Args SelectionArgs _getSelectionEventArgs( - dynamic seriesRenderer, num seriesIndex, num pointIndex) { + dynamic seriesRenderer, int seriesIndex, int pointIndex) { if (seriesRenderer != null) { final dynamic selectionBehavior = seriesRenderer._selectionBehavior; - final SelectionArgs args = - SelectionArgs(seriesRenderer, seriesIndex, pointIndex, pointIndex); + final SelectionArgs args = SelectionArgs( + seriesRenderer: seriesRenderer, + seriesIndex: seriesIndex, + viewportPointIndex: pointIndex, + pointIndex: pointIndex); args.selectedBorderColor = selectionBehavior.selectedBorderColor; args.selectedBorderWidth = selectionBehavior.selectedBorderWidth; args.selectedColor = selectionBehavior.selectedColor; @@ -1281,92 +1359,94 @@ class CircularSeriesRenderer extends ChartSeriesRenderer { } /// To calculate point start and end angle - num _circularRenderPoint( + num? _circularRenderPoint( SfCircularChart chart, CircularSeriesRenderer seriesRenderer, ChartPoint point, - num pointStartAngle, - num innerRadius, - num outerRadius, + num? pointStartAngle, + num? innerRadius, + num? outerRadius, Canvas canvas, - num seriesIndex, - num pointIndex, + int seriesIndex, + int pointIndex, num animationDegreeValue, num animationRadiusValue, bool isAnyPointSelect, - ChartPoint oldPoint, - [List> oldPointList]) { + ChartPoint? oldPoint, + List?>? oldPointList) { final bool isDynamicUpdate = oldPoint != null; - final num oldStartAngle = oldPoint?.startAngle; - final num oldEndAngle = oldPoint?.endAngle; - num pointEndAngle, degree; + final num? oldStartAngle = oldPoint?.startAngle; + final num? oldEndAngle = oldPoint?.endAngle; + num? degree, pointEndAngle; /// below lines for dynamic dataSource changes if (isDynamicUpdate) { if (!oldPoint.isVisible && point.isVisible) { final num val = point.startAngle == seriesRenderer._start ? seriesRenderer._start - : oldPointList[ - _getVisiblePointIndex(oldPointList, 'before', pointIndex)] - .endAngle; - pointStartAngle = val - (val - point.startAngle) * animationDegreeValue; - pointEndAngle = val + (point.endAngle - val) * animationDegreeValue; + : oldPointList![ + _getVisiblePointIndex(oldPointList, 'before', pointIndex)!]! + .endAngle!; + pointStartAngle = + val - (val - point.startAngle!) * animationDegreeValue; + pointEndAngle = val + (point.endAngle! - val) * animationDegreeValue; degree = pointEndAngle - pointStartAngle; } else if (oldPoint.isVisible && !point.isVisible) { - if (oldPoint.startAngle.round() == seriesRenderer._start && - (oldPoint.endAngle.round() == seriesRenderer._end || - oldPoint.endAngle.round() == 360 + seriesRenderer._end)) { - pointStartAngle = oldPoint.startAngle; - pointEndAngle = oldPoint.endAngle - - (oldPoint.endAngle - oldPoint.startAngle) * animationDegreeValue; + if (oldPoint.startAngle!.round() == seriesRenderer._start && + (oldPoint.endAngle!.round() == seriesRenderer._end || + oldPoint.endAngle!.round() == 360 + seriesRenderer._end)) { + pointStartAngle = oldPoint.startAngle!; + pointEndAngle = oldPoint.endAngle! - + (oldPoint.endAngle! - oldPoint.startAngle!) * + animationDegreeValue; } else if (oldPoint.startAngle == oldPoint.endAngle) { - pointStartAngle = pointEndAngle = oldPoint.startAngle; + pointStartAngle = pointEndAngle = oldPoint.startAngle!; } else { - pointStartAngle = oldPoint.startAngle - - (oldPoint.startAngle - + pointStartAngle = oldPoint.startAngle! - + (oldPoint.startAngle! - (oldPoint.startAngle == seriesRenderer._start ? seriesRenderer._start : seriesRenderer - ._renderPoints[_getVisiblePointIndex( - seriesRenderer._renderPoints, + ._renderPoints![_getVisiblePointIndex( + seriesRenderer._renderPoints!, 'before', - pointIndex)] - .endAngle)) * + pointIndex)!] + .endAngle!)) * animationDegreeValue; - pointEndAngle = oldPoint.endAngle - - (oldPoint.endAngle - - ((oldPoint.endAngle.round() == seriesRenderer._end || - oldPoint.endAngle.round() == + pointEndAngle = oldPoint.endAngle! - + (oldPoint.endAngle! - + ((oldPoint.endAngle!.round() == seriesRenderer._end || + oldPoint.endAngle!.round() == 360 + seriesRenderer._end) - ? oldPoint.endAngle + ? oldPoint.endAngle! : seriesRenderer - ._renderPoints[_getVisiblePointIndex( - seriesRenderer._renderPoints, + ._renderPoints![_getVisiblePointIndex( + seriesRenderer._renderPoints!, 'after', - pointIndex)] - .startAngle)) * + pointIndex)!] + .startAngle!)) * animationDegreeValue; } degree = pointEndAngle - pointStartAngle; } else if (point.isVisible && oldPoint.isVisible) { - pointStartAngle = (point.startAngle > oldStartAngle) + pointStartAngle = (point.startAngle! > oldStartAngle!) ? oldStartAngle + - ((point.startAngle - oldStartAngle) * animationDegreeValue) + ((point.startAngle! - oldStartAngle) * animationDegreeValue) : oldStartAngle - - ((oldStartAngle - point.startAngle) * animationDegreeValue); - pointEndAngle = (point.endAngle > oldEndAngle) + ((oldStartAngle - point.startAngle!) * animationDegreeValue); + pointEndAngle = (point.endAngle! > oldEndAngle!) ? oldEndAngle + - ((point.endAngle - oldEndAngle) * animationDegreeValue) + ((point.endAngle! - oldEndAngle) * animationDegreeValue) : oldEndAngle - - ((oldEndAngle - point.endAngle) * animationDegreeValue); + ((oldEndAngle - point.endAngle!) * animationDegreeValue); degree = pointEndAngle - pointStartAngle; } } else if (point.isVisible) { - degree = animationDegreeValue * point.degree; - pointEndAngle = pointStartAngle + degree; + degree = animationDegreeValue * point.degree!; + pointEndAngle = pointStartAngle! + degree; } - outerRadius = _chartState._initialRender - ? animationRadiusValue * outerRadius + outerRadius = _chartState._initialRender! + ? animationRadiusValue * outerRadius! : outerRadius; _calculatePath( pointIndex, @@ -1391,53 +1471,54 @@ class CircularSeriesRenderer extends ChartSeriesRenderer { int seriesIndex, SfCircularChart chart, CircularSeriesRenderer seriesRenderer, - ChartPoint point, - ChartPoint oldPoint, + ChartPoint? point, + ChartPoint? oldPoint, Canvas canvas, - num degree, - num innerRadius, - num outerRadius, - num pointStartAngle, - num pointEndAngle, + num? degree, + num? innerRadius, + num? outerRadius, + num? pointStartAngle, + num? pointEndAngle, bool isDynamicUpdate) { - Path renderPath; + Path? renderPath; final CornerStyle cornerStyle = _series.cornerStyle; - num actualStartAngle, actualEndAngle; + late num actualStartAngle, actualEndAngle; if (!isDynamicUpdate || (isDynamicUpdate && - ((oldPoint.isVisible && point.isVisible) || - (oldPoint.isVisible && !point.isVisible) || - (!oldPoint.isVisible && point.isVisible)))) { - innerRadius = innerRadius ?? oldPoint.innerRadius; - outerRadius = outerRadius ?? oldPoint.outerRadius; + ((oldPoint!.isVisible && point!.isVisible) || + (oldPoint.isVisible && !point!.isVisible) || + (!oldPoint.isVisible && point!.isVisible)))) { + innerRadius = innerRadius ?? oldPoint!.innerRadius; + outerRadius = outerRadius ?? oldPoint!.outerRadius; if (cornerStyle != CornerStyle.bothFlat) { final num angleDeviation = - _findAngleDeviation(innerRadius, outerRadius, 360); + _findAngleDeviation(innerRadius!, outerRadius!, 360); actualStartAngle = (cornerStyle == CornerStyle.startCurve || cornerStyle == CornerStyle.bothCurve) - ? (pointStartAngle + angleDeviation) - : pointStartAngle; + ? (pointStartAngle! + angleDeviation) + : pointStartAngle!; actualEndAngle = (cornerStyle == CornerStyle.endCurve || cornerStyle == CornerStyle.bothCurve) - ? (pointEndAngle - angleDeviation) - : pointEndAngle; + ? (pointEndAngle! - angleDeviation) + : pointEndAngle!; } renderPath = Path(); renderPath = (cornerStyle == CornerStyle.startCurve || cornerStyle == CornerStyle.endCurve || cornerStyle == CornerStyle.bothCurve) ? _getRoundedCornerArcPath( - innerRadius, - outerRadius, - point.center ?? oldPoint.center, + innerRadius!, + outerRadius!, + point!.center ?? oldPoint!.center, actualStartAngle, actualEndAngle, degree, - cornerStyle) + cornerStyle, + point) : _getArcPath( - innerRadius, - outerRadius, - point.center ?? oldPoint.center, + innerRadius!, + outerRadius!, + point!.center ?? oldPoint!.center!, pointStartAngle, pointEndAngle, degree, @@ -1454,66 +1535,161 @@ class CircularSeriesRenderer extends ChartSeriesRenderer { int seriesIndex, SfCircularChart chart, CircularSeriesRenderer seriesRenderer, - ChartPoint point, + ChartPoint? point, Canvas canvas, - Path renderPath, - num degree, - num innerRadius) { - if (point.isVisible) { + Path? renderPath, + num? degree, + num? innerRadius) { + if (point != null && point.isVisible) { final _Region pointRegion = _Region( - _degreesToRadians(point.startAngle), - _degreesToRadians(point.endAngle), - point.startAngle, - point.endAngle, + _degreesToRadians(point.startAngle!), + _degreesToRadians(point.endAngle!), + point.startAngle!, + point.endAngle!, seriesIndex, pointIndex, point.center, innerRadius, - point.outerRadius); + point.outerRadius!); seriesRenderer._pointRegions.add(pointRegion); } - final _StyleOptions style = + final _StyleOptions? style = _selectPoint(pointIndex, seriesRenderer, chart, point); - final Color fillColor = style != null && style.fill != null + + final Color? fillColor = style != null && style.fill != null ? style.fill - : (point.fill != Colors.transparent - ? seriesRenderer._series._renderer.getPointColor( + : (point != null && point.fill != Colors.transparent + ? seriesRenderer._series._renderer?.getPointColor( seriesRenderer, point, pointIndex, seriesIndex, point.fill, seriesRenderer._series.opacity) - : point.fill); + : point!.fill); - final Color strokeColor = style != null && style.strokeColor != null + final Color? strokeColor = style != null && style.strokeColor != null ? style.strokeColor - : seriesRenderer._series._renderer.getPointStrokeColor( - seriesRenderer, point, pointIndex, seriesIndex, point.strokeColor); + : seriesRenderer._series._renderer?.getPointStrokeColor( + seriesRenderer, point, pointIndex, seriesIndex, point!.strokeColor); - final double strokeWidth = style != null && style.strokeWidth != null + final num? strokeWidth = style != null && style.strokeWidth != null ? style.strokeWidth - : seriesRenderer._series._renderer.getPointStrokeWidth( - seriesRenderer, point, pointIndex, seriesIndex, point.strokeWidth); + : seriesRenderer._series._renderer?.getPointStrokeWidth( + seriesRenderer, point, pointIndex, seriesIndex, point!.strokeWidth); assert(seriesRenderer._series.opacity >= 0, 'The opacity value will not accept negative numbers.'); assert(seriesRenderer._series.opacity <= 1, 'The opacity value must be less than 1.'); - final double opacity = style != null && style.opacity != null + final double? opacity = style != null && style.opacity != null ? style.opacity - : _series._renderer.getOpacity(seriesRenderer, point, pointIndex, + : _series._renderer?.getOpacity(seriesRenderer, point, pointIndex, seriesIndex, seriesRenderer._series.opacity); - if (renderPath != null && degree > 0) { - _drawPath( - canvas, - _StyleOptions( - fillColor, - _chartState._animateCompleted ? strokeWidth : 0, - strokeColor, - opacity), - renderPath); + Shader? _renderModeShader; + + if (chart.series[0].pointRenderMode == PointRenderMode.gradient && + point?.shader == null) { + final List colorsList = []; + final List stopsList = []; + num initStops = 0; + for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { + point = seriesRenderer._renderPoints![i]; + if (point.isVisible) { + colorsList.add(point.fill); + if (stopsList.isEmpty) { + initStops = (point.y! / _sumOfPoints) / 4; + stopsList.add(point.y! / _sumOfPoints - initStops); + } else { + if (stopsList.length == 1) { + stopsList.add( + (point.y! / _sumOfPoints + stopsList.last) + initStops / 1.5); + } else { + stopsList.add(point.y! / _sumOfPoints + stopsList.last); + } + } + } + } + + _renderModeShader = dart_ui.Gradient.sweep( + _center!, + colorsList, + stopsList, + TileMode.clamp, + degreeToRadian(chart.series[0].startAngle), + degreeToRadian(chart.series[0].endAngle), + _resolveTransform( + Rect.fromCircle( + center: _center!, + radius: point!.outerRadius!.toDouble(), + ), + TextDirection.ltr)); + } + + if (renderPath != null && degree! > 0) { + if (seriesRenderer is DoughnutSeriesRenderer) { + seriesRenderer._innerRadialradius = + !(point!.isVisible) || (seriesRenderer._innerRadialradius == null) + ? innerRadius + : seriesRenderer._innerRadialradius; + } + if (point != null && point.isVisible) { + point._pathRect = Rect.fromCircle( + center: _center!, + radius: point.outerRadius!.toDouble(), + ); + } + seriesRenderer._renderPaths.add(renderPath); + if (chart.onCreateShader != null && + point != null && + point.isVisible && + point.shader == null) { + Rect? innerRect; + if (seriesRenderer is DoughnutSeriesRenderer && + seriesRenderer._innerRadialradius != null) { + innerRect = Rect.fromCircle( + center: _center!, + radius: seriesRenderer._innerRadialradius!.toDouble(), + ); + } else { + innerRect = null; + } + if (point.isVisible) { + _renderList.clear(); + seriesRenderer._renderList.add(_StyleOptions( + fill: fillColor!, + strokeWidth: _chartState._animateCompleted ? strokeWidth! : 0, + strokeColor: strokeColor!, + opacity: opacity)); + seriesRenderer._renderList.add(point._pathRect); + seriesRenderer._renderList.add(innerRect); + } + } else { + _drawPath( + canvas, + _StyleOptions( + fill: fillColor!, + strokeWidth: _chartState._animateCompleted ? strokeWidth! : 0, + strokeColor: strokeColor!, + opacity: opacity), + renderPath, + point!._pathRect, + point.shader ?? _renderModeShader); + if (point != null && + (_renderModeShader != null || point.shader != null)) { + if (strokeColor != null && + strokeWidth != null && + strokeWidth > 0 && + _chartState._animateCompleted) { + final Paint paint = Paint(); + paint.color = strokeColor; + paint.strokeWidth = strokeWidth.toDouble(); + paint.style = PaintingStyle.stroke; + canvas.drawPath(renderPath, paint); + } + } + } } } } @@ -1547,7 +1723,7 @@ class CircularSeriesController { /// )); ///} ///``` - final CircularSeriesRenderer seriesRenderer; + late final CircularSeriesRenderer seriesRenderer; ///Used to process only the newly added, updated and removed data points in a series, /// instead of processing all the data points. @@ -1600,12 +1776,12 @@ class CircularSeriesController { /// } ///``` void updateDataSource( - {List addedDataIndexes, - List removedDataIndexes, - List updatedDataIndexes, - int addedDataIndex, - int removedDataIndex, - int updatedDataIndex}) { + {List? addedDataIndexes, + List? removedDataIndexes, + List? updatedDataIndexes, + int? addedDataIndex, + int? removedDataIndex, + int? updatedDataIndex}) { if (removedDataIndexes != null && removedDataIndexes.isNotEmpty) { _removeDataPointsList(removedDataIndexes); } else if (removedDataIndex != null) { @@ -1636,8 +1812,8 @@ class CircularSeriesController { void _addOrUpdateDataPoint(int index, bool needUpdate) { final CircularSeries series = seriesRenderer._series; if (index >= 0 && - series.dataSource.length > index && - series.dataSource[index] != null) { + series.dataSource!.length > index && + series.dataSource![index] != null) { final ChartPoint _currentPoint = _getCircularPoint(seriesRenderer, index); if (_currentPoint.x != null) { @@ -1680,16 +1856,16 @@ class CircularSeriesController { void _updateCircularSeries() { final SfCircularChartState _chartState = seriesRenderer._chartState; _chartState._chartSeries._processDataPoints(seriesRenderer); - _chartState._chartSeries?._calculateAngleAndCenterPositions(seriesRenderer); + _chartState._chartSeries._calculateAngleAndCenterPositions(seriesRenderer); seriesRenderer._repaintNotifier.value++; if (seriesRenderer._series.dataLabelSettings.isVisible && _chartState._renderDataLabel != null) { - _chartState._renderDataLabel.state.render(); + _chartState._renderDataLabel!.state.render(); } if (seriesRenderer._series.dataLabelSettings.isVisible && _chartState._chartTemplate != null && - _chartState._chartTemplate.state != null) { - _chartState._chartTemplate.state.templateRender(); + _chartState._chartTemplate!.state != null) { + _chartState._chartTemplate!.state.templateRender(); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/common.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/common.dart index 202fd8afc..37ff7bb78 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/common.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/common.dart @@ -10,55 +10,55 @@ class ChartPoint { ChartPoint([this.x, this.y, this.radius, this.pointColor, this.sortValue]); /// X value of chart point - dynamic x; + dynamic? x; /// Y value of chart point - num y; + num? y; /// Degree of chart point - num degree; + num? degree; /// Start angle of chart point - num startAngle; + num? startAngle; /// End angle of chart point - num endAngle; + num? endAngle; /// Middle angle of chart point - num midAngle; + num? midAngle; /// Center position of chart point - Offset center; + Offset? center; /// Text value of chart point - String text; + String? text; /// Fill color of the chart point - Color fill; + late Color fill; /// Color of chart point - Color color; + late Color color; /// Stroke color of chart point - Color strokeColor; + late Color strokeColor; /// Sort value of chart point - D sortValue; + D? sortValue; /// Stroke width of chart point - num strokeWidth; + late num strokeWidth; /// Inner radius of chart point - num innerRadius; + num? innerRadius; /// Outer radius of chart point - num outerRadius; + num? outerRadius; /// To set the explode value of chart point - bool isExplode; + bool? isExplode; /// To set the shadow value of chart point - bool isShadow; + bool? isShadow; /// to set the empty value of chart point bool isEmpty = false; @@ -70,13 +70,13 @@ class ChartPoint { bool isSelected = false; /// Data label positin of chart point - Position dataLabelPosition; + late Position dataLabelPosition; /// Render position of chart point - ChartDataLabelPosition renderPosition; + ChartDataLabelPosition? renderPosition; /// Label rect of chart point. - Rect labelRect; + late Rect labelRect; /// Size of the Data label of chart point Size dataLabelSize = const Size(0, 0); @@ -85,16 +85,16 @@ class ChartPoint { bool saturationRegionOutside = false; /// Y ratio of chart point - num yRatio; + late num yRatio; /// Height Ratio of chart point - num heightRatio; + late num heightRatio; /// Radius of the chart point - String radius; + String? radius; /// Color property of the chart point - Color pointColor; + Color? pointColor; /// To execute onTooltipRender event or not. // ignore: prefer_final_fields @@ -103,6 +103,38 @@ class ChartPoint { /// To execute OnDataLabelRender event or not. // ignore: prefer_final_fields bool labelRenderEvent = false; + + /// Current point index. + late int index; + + // Data type + dynamic? _data; + + /// PointShader Mapper + ChartShaderMapper? _pointShaderMapper; + + /// Shader of chart point + Shader? get shader => + _pointShaderMapper != null && center != null && outerRadius != null + ? _pointShaderMapper!( + _data, + index, + fill, + Rect.fromCircle( + center: center!, + radius: outerRadius!.toDouble(), + ), + ) + : null; + + /// Path of circular Series + Rect? _pathRect; + + /// Stores the tooltip label text. + late String _tooltipLabelText; + + /// Stores the tooltip header text. + late String _tooltipHeaderText; } class _Region { @@ -122,17 +154,17 @@ class _Region { num start; num end; num endAngle; - Offset center; - num innerRadius; + Offset? center; + num? innerRadius; num outerRadius; } class _StyleOptions { - _StyleOptions(this.fill, this.strokeWidth, this.strokeColor, [this.opacity]); - Color fill; - Color strokeColor; - double opacity; - num strokeWidth; + _StyleOptions({this.fill, this.strokeWidth, this.strokeColor, this.opacity}); + Color? fill; + Color? strokeColor; + double? opacity; + num? strokeWidth; } /// This class holds the properties of the connector line. @@ -144,8 +176,8 @@ class _StyleOptions { /// class ConnectorLineSettings { /// Creating an argument constructor of ConnectorLineSettings class. - ConnectorLineSettings( - {this.length, double width, ConnectorType type, this.color}) + const ConnectorLineSettings( + {this.length, double? width, ConnectorType? type, this.color}) : width = width ?? 1.0, type = type ?? ConnectorType.line; @@ -165,7 +197,7 @@ class ConnectorLineSettings { /// )); ///} ///``` - final String length; + final String? length; ///Width of the connector line. /// @@ -201,7 +233,7 @@ class ConnectorLineSettings { /// )); ///} ///``` - final Color color; + final Color? color; ///Type of the connector line. /// @@ -225,11 +257,11 @@ class ConnectorLineSettings { class _ChartInteraction { _ChartInteraction(this.seriesIndex, this.pointIndex, this.series, this.point, [this.region]); - int seriesIndex; - int pointIndex; + int? seriesIndex; + int? pointIndex; dynamic series; dynamic point; - _Region region; + _Region? region; } /// Customizes the annotation of the circular chart. @@ -244,13 +276,13 @@ class _ChartInteraction { class CircularChartAnnotation { /// Creating an argument constructor of CircularChartAnnotation class. CircularChartAnnotation( - {int angle, - String radius, + {int? angle, + String? radius, this.widget, - String height, - String width, - ChartAlignment horizontalAlignment, - ChartAlignment verticalAlignment}) + String? height, + String? width, + ChartAlignment? horizontalAlignment, + ChartAlignment? verticalAlignment}) : angle = angle ?? 0, radius = radius ?? '0%', height = height ?? '0%', @@ -318,7 +350,7 @@ class CircularChartAnnotation { /// )); ///} ///``` - final Widget widget; + final Widget? widget; ///Height of the annotation. /// @@ -446,7 +478,7 @@ Color _getCircularDataLabelColor(ChartPoint currentPoint, break; case 'RadialBar': final RadialBarSeries radialBar = - seriesRenderer._series; + seriesRenderer._series as RadialBarSeries; color = radialBar.trackColor; break; default: @@ -457,7 +489,7 @@ Color _getCircularDataLabelColor(ChartPoint currentPoint, ///To get inner data label color Color _getInnerColor( - Color dataLabelColor, Color pointColor, SfChartThemeData theme) => + Color? dataLabelColor, Color? pointColor, SfChartThemeData theme) => // ignore: prefer_if_null_operators dataLabelColor != null ? dataLabelColor @@ -470,7 +502,7 @@ Color _getInnerColor( ///To get outer data label color Color _getOuterColor( - Color dataLabelColor, Color backgroundColor, SfChartThemeData theme) => + Color? dataLabelColor, Color backgroundColor, SfChartThemeData theme) => // ignore: prefer_if_null_operators dataLabelColor != null ? dataLabelColor @@ -482,13 +514,13 @@ Color _getOuterColor( /// To check whether any point is selected bool _checkIsAnyPointSelect(CircularSeriesRenderer seriesRenderer, - ChartPoint point, SfCircularChart chart) { + ChartPoint? point, SfCircularChart chart) { bool isAnyPointSelected = false; final CircularSeries series = seriesRenderer._series; if (series.initialSelectedDataIndexes.isNotEmpty) { for (int i = 0; i < series.initialSelectedDataIndexes.length; i++) { final int data = series.initialSelectedDataIndexes[i]; - for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { + for (int j = 0; j < seriesRenderer._renderPoints!.length; j++) { if (j == data) { isAnyPointSelected = true; break; diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart index e25e78d0e..765967b8b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart @@ -3,13 +3,14 @@ part of charts; // ignore: must_be_immutable class _CircularDataLabelRenderer extends StatefulWidget { // ignore: prefer_const_constructors_in_immutables - _CircularDataLabelRenderer({this.circularChartState, this.show}); + _CircularDataLabelRenderer( + {required this.circularChartState, required this.show}); final SfCircularChartState circularChartState; bool show; - _CircularDataLabelRendererState state; + late _CircularDataLabelRendererState state; @override State createState() { @@ -19,13 +20,13 @@ class _CircularDataLabelRenderer extends StatefulWidget { class _CircularDataLabelRendererState extends State<_CircularDataLabelRenderer> with SingleTickerProviderStateMixin { - List animationControllersList; + late List animationControllersList; /// Animation controller for series - AnimationController animationController; + late AnimationController animationController; /// Repaint notifier for crosshair container - ValueNotifier dataLabelRepaintNotifier; + late ValueNotifier dataLabelRepaintNotifier; @override void initState() { @@ -39,7 +40,7 @@ class _CircularDataLabelRendererState extends State<_CircularDataLabelRenderer> Widget build(BuildContext context) { widget.state = this; animationController.duration = Duration( - milliseconds: widget.circularChartState._initialRender ? 500 : 0); + milliseconds: widget.circularChartState._initialRender! ? 500 : 0); final Animation dataLabelAnimation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, @@ -78,11 +79,11 @@ class _CircularDataLabelRendererState extends State<_CircularDataLabelRenderer> class _CircularDataLabelPainter extends CustomPainter { _CircularDataLabelPainter( - {this.circularChartState, - this.state, - this.animationController, - this.animation, - ValueNotifier notifier}) + {required this.circularChartState, + required this.state, + required this.animationController, + required this.animation, + required ValueNotifier notifier}) : super(repaint: notifier); final SfCircularChartState circularChartState; @@ -123,7 +124,7 @@ void _renderCircularDataLabel( Canvas canvas, SfCircularChartState _chartState, int seriesIndex, - Animation animation) { + Animation? animation) { ChartPoint point; final SfCircularChart chart = _chartState._chart; final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; @@ -132,45 +133,45 @@ void _renderCircularDataLabel( final num angle = dataLabel.angle; Offset labelLocation; const int labelPadding = 2; - String label; + String? label; final double animateOpacity = animation != null ? animation.value : 1; DataLabelRenderArgs dataLabelArgs; TextStyle dataLabelStyle; final List renderDataLabelRegions = []; for (int pointIndex = 0; - pointIndex < seriesRenderer._renderPoints.length; + pointIndex < seriesRenderer._renderPoints!.length; pointIndex++) { - point = seriesRenderer._renderPoints[pointIndex]; + point = seriesRenderer._renderPoints![pointIndex]; if (point.isVisible && (point.y != 0 || dataLabel.showZeroValue)) { label = point.text; - label = seriesRenderer._series._renderer.getLabelContent( - seriesRenderer, point, pointIndex, seriesIndex, label); + label = seriesRenderer._series._renderer!.getLabelContent( + seriesRenderer, point, pointIndex, seriesIndex, label!); dataLabelStyle = dataLabel.textStyle; dataLabelSettingsRenderer._color = seriesRenderer._series.dataLabelSettings.color; if (chart.onDataLabelRender != null && - !seriesRenderer._renderPoints[pointIndex].labelRenderEvent) { + !seriesRenderer._renderPoints![pointIndex].labelRenderEvent) { dataLabelArgs = DataLabelRenderArgs(seriesRenderer, seriesRenderer._renderPoints, pointIndex, pointIndex); dataLabelArgs.text = label; dataLabelArgs.textStyle = dataLabelStyle; dataLabelArgs.color = dataLabelSettingsRenderer._color; - chart.onDataLabelRender(dataLabelArgs); + chart.onDataLabelRender!(dataLabelArgs); label = point.text = dataLabelArgs.text; dataLabelStyle = dataLabelArgs.textStyle; - pointIndex = dataLabelArgs.pointIndex; + pointIndex = dataLabelArgs.pointIndex!; dataLabelSettingsRenderer._color = dataLabelArgs.color; - if (animation.status == AnimationStatus.completed) { + if (animation!.status == AnimationStatus.completed) { seriesRenderer._dataPoints[pointIndex].labelRenderEvent = true; } } - final Size textSize = _measureText(label, dataLabelStyle); + final Size textSize = measureText(label, dataLabelStyle); /// condition check for labels after event. if (label != '') { if (seriesRenderer._seriesType == 'radialbar') { dataLabelStyle = chart.onDataLabelRender == null - ? seriesRenderer._series._renderer.getDataLabelStyle( + ? seriesRenderer._series._renderer!.getDataLabelStyle( seriesRenderer, point, pointIndex, @@ -178,8 +179,8 @@ void _renderCircularDataLabel( dataLabelStyle, _chartState) : dataLabelStyle; - labelLocation = _degreeToPoint(point.startAngle, - (point.innerRadius + point.outerRadius) / 2, point.center); + labelLocation = _degreeToPoint(point.startAngle!, + (point.innerRadius! + point.outerRadius!) / 2, point.center!); labelLocation = Offset( (labelLocation.dx - textSize.width - 5) + (angle == 0 ? 0 : textSize.width / 2), @@ -221,7 +222,7 @@ void _renderCircularDataLabel( } } dataLabelStyle = chart.onDataLabelRender == null - ? seriesRenderer._series._renderer.getDataLabelStyle(seriesRenderer, + ? seriesRenderer._series._renderer!.getDataLabelStyle(seriesRenderer, point, pointIndex, seriesIndex, dataLabelStyle, _chartState) : dataLabelStyle; } @@ -250,8 +251,8 @@ void _setLabelPosition( final bool smartLabel = seriesRenderer._series.enableSmartLabels; const int labelPadding = 2; if (dataLabel.labelPosition == ChartDataLabelPosition.inside) { - labelLocation = _degreeToPoint(point.midAngle, - (point.innerRadius + point.outerRadius) / 2, point.center); + labelLocation = _degreeToPoint(point.midAngle!, + (point.innerRadius! + point.outerRadius!) / 2, point.center!); labelLocation = Offset( labelLocation.dx - (textSize.width / 2) + @@ -279,7 +280,7 @@ void _setLabelPosition( fontStyle: dataLabelStyle.fontStyle ?? dataLabel.textStyle.fontStyle, fontWeight: dataLabelStyle.fontWeight ?? dataLabel.textStyle.fontWeight, - inherit: dataLabelStyle.inherit ?? dataLabel.textStyle.inherit, + inherit: dataLabelStyle.inherit, backgroundColor: dataLabelStyle.backgroundColor ?? dataLabel.textStyle.backgroundColor, letterSpacing: @@ -326,7 +327,7 @@ void _setLabelPosition( point.renderPosition = ChartDataLabelPosition.inside; dataLabelStyle = TextStyle( color: (chart.onDataLabelRender != null && - dataLabel.textStyle.color != null) + dataLabelSettingsRenderer._color != null) ? _getSaturationColor( dataLabelSettingsRenderer._color ?? point.fill) : ((dataLabelStyle.color ?? dataLabel.textStyle.color) ?? @@ -338,7 +339,7 @@ void _setLabelPosition( fontStyle: dataLabelStyle.fontStyle ?? dataLabel.textStyle.fontStyle, fontWeight: dataLabelStyle.fontWeight ?? dataLabel.textStyle.fontWeight, - inherit: dataLabelStyle.inherit ?? dataLabel.textStyle.inherit, + inherit: dataLabelStyle.inherit, backgroundColor: dataLabelStyle.backgroundColor ?? dataLabel.textStyle.backgroundColor, letterSpacing: @@ -398,7 +399,7 @@ void _setLabelPosition( fontFamily: dataLabelStyle.fontFamily ?? dataLabel.textStyle.fontFamily, fontStyle: dataLabelStyle.fontStyle ?? dataLabel.textStyle.fontStyle, fontWeight: dataLabelStyle.fontWeight ?? dataLabel.textStyle.fontWeight, - inherit: dataLabelStyle.inherit ?? dataLabel.textStyle.inherit, + inherit: dataLabelStyle.inherit, backgroundColor: dataLabelStyle.backgroundColor ?? dataLabel.textStyle.backgroundColor, letterSpacing: @@ -455,25 +456,25 @@ void _renderOutsideDataLabel( List renderDataLabelRegions, double animateOpacity) { Path connectorPath; - Rect rect; + Rect? rect; Offset labelLocation; final EdgeInsets margin = seriesRenderer._series.dataLabelSettings.margin; final ConnectorLineSettings connector = seriesRenderer._series.dataLabelSettings.connectorLineSettings; connectorPath = Path(); final num connectorLength = - _percentToValue(connector.length ?? '10%', point.outerRadius); + _percentToValue(connector.length ?? '10%', point.outerRadius!)!; final Offset startPoint = - _degreeToPoint(point.midAngle, point.outerRadius, point.center); + _degreeToPoint(point.midAngle!, point.outerRadius!, point.center!); final Offset endPoint = _degreeToPoint( - point.midAngle, point.outerRadius + connectorLength, point.center); + point.midAngle!, point.outerRadius! + connectorLength, point.center!); connectorPath.moveTo(startPoint.dx, startPoint.dy); if (connector.type == ConnectorType.line) { connectorPath.lineTo(endPoint.dx, endPoint.dy); } rect = _getDataLabelRect(point.dataLabelPosition, connector.type, margin, connectorPath, endPoint, textSize); - point.labelRect = rect; + point.labelRect = rect!; labelLocation = Offset(rect.left + margin.left, rect.top + rect.height / 2 - textSize.height / 2); final Rect containerRect = _chartState._chartAreaRect; @@ -534,7 +535,7 @@ void _drawLabel( Rect labelRect, Offset location, String label, - Path connectorPath, + Path? connectorPath, Canvas canvas, CircularSeriesRenderer seriesRenderer, ChartPoint point, @@ -561,19 +562,20 @@ void _drawLabel( } if (dataLabel.builder == null) { - final double strokeWidth = seriesRenderer._series._renderer + final double strokeWidth = seriesRenderer._series._renderer! .getDataLabelStrokeWidth(seriesRenderer, point, pointIndex, seriesIndex, dataLabel.borderWidth); - final Color labelFill = seriesRenderer._series._renderer.getDataLabelColor( - seriesRenderer, - point, - pointIndex, - seriesIndex, - dataLabelSettingsRenderer._color ?? - (dataLabel.useSeriesColor - ? point.fill - : dataLabelSettingsRenderer._color)); - final Color strokeColor = seriesRenderer._series._renderer + final Color? labelFill = seriesRenderer._series._renderer! + .getDataLabelColor( + seriesRenderer, + point, + pointIndex, + seriesIndex, + dataLabelSettingsRenderer._color ?? + (dataLabel.useSeriesColor + ? point.fill + : dataLabelSettingsRenderer._color)); + final Color strokeColor = seriesRenderer._series._renderer! .getDataLabelStrokeColor(seriesRenderer, point, pointIndex, seriesIndex, dataLabel.borderColor.withOpacity(dataLabel.opacity)); if (strokeWidth != null && strokeWidth > 0) { @@ -613,23 +615,25 @@ void _triggerCircularDataLabelEvent( SfCircularChart chart, CircularSeriesRenderer seriesRenderer, SfCircularChartState chartState, - Offset position) { + Offset? position) { final int seriesIndex = 0; final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; for (int index = 0; index < seriesRenderer._dataPoints.length; index++) { final ChartPoint point = seriesRenderer._dataPoints[index]; if (dataLabel.isVisible && seriesRenderer._dataPoints[index].labelRect != null && + position != null && seriesRenderer._dataPoints[index].labelRect.contains(position)) { if (dataLabel.labelPosition == ChartDataLabelPosition.inside) { - final Offset labelLocation = _degreeToPoint(point.midAngle, - (point.innerRadius + point.outerRadius) / 2, point.center); + final Offset labelLocation = _degreeToPoint(point.midAngle!, + (point.innerRadius! + point.outerRadius!) / 2, point.center!); position = Offset(labelLocation.dx, labelLocation.dy); } else { final num connectorLength = _percentToValue( - dataLabel.connectorLineSettings.length ?? '10%', point.outerRadius); - final Offset labelLocation = _degreeToPoint( - point.midAngle, point.outerRadius + connectorLength, point.center); + dataLabel.connectorLineSettings.length ?? '10%', + point.outerRadius!)!; + final Offset labelLocation = _degreeToPoint(point.midAngle!, + point.outerRadius! + connectorLength, point.center!); position = Offset(labelLocation.dx, labelLocation.dy); } if (chart.onDataLabelTapped != null) { @@ -649,11 +653,12 @@ void _drawLabelRect( /// To find data label position void _findDataLabelPosition(ChartPoint point) => - point.dataLabelPosition = ((point.midAngle >= -90 && point.midAngle < 0) || - (point.midAngle >= 0 && point.midAngle < 90) || - (point.midAngle >= 270)) - ? Position.right - : Position.left; + point.dataLabelPosition = + ((point.midAngle! >= -90 && point.midAngle! < 0) || + (point.midAngle! >= 0 && point.midAngle! < 90) || + (point.midAngle! >= 270)) + ? Position.right + : Position.left; /// Method for setting color to datalabel Color _findthemecolor(SfCircularChartState _chartState, diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/doughnut_series.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/doughnut_series.dart index 447c9ce53..10a0a858e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/doughnut_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/doughnut_series.dart @@ -10,61 +10,67 @@ part of charts; class DoughnutSeries extends CircularSeries { /// Creating an argument constructor of DoughnutSeries class. DoughnutSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - CircularSeriesRendererCreatedCallback onRendererCreated, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper yValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper pointRadiusMapper, - ChartValueMapper dataLabelMapper, - ChartValueMapper sortFieldValueMapper, - int startAngle, - int endAngle, - String radius, - String innerRadius, - bool explode, - bool explodeAll, - int explodeIndex, - String explodeOffset, - ActivationMode explodeGesture, - double groupTo, - CircularChartGroupMode groupMode, - EmptyPointSettings emptyPointSettings, - Color strokeColor, - double strokeWidth, - DataLabelSettings dataLabelSettings, - bool enableTooltip, - bool enableSmartLabels, - String name, - double opacity, - double animationDuration, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + CircularSeriesRendererCreatedCallback? onRendererCreated, + List? dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? pointColorMapper, + ChartShaderMapper? pointShaderMapper, + ChartValueMapper? pointRadiusMapper, + ChartValueMapper? dataLabelMapper, + ChartValueMapper? sortFieldValueMapper, + int? startAngle, + int? endAngle, + String? radius, + String? innerRadius, + bool? explode, + bool? explodeAll, + int? explodeIndex, + String? explodeOffset, + ActivationMode? explodeGesture, + double? groupTo, + CircularChartGroupMode? groupMode, + PointRenderMode? pointRenderMode, + EmptyPointSettings? emptyPointSettings, + Color? strokeColor, + double? strokeWidth, + DataLabelSettings? dataLabelSettings, + bool? enableTooltip, + bool? enableSmartLabels, + String? name, + double? opacity, + double? animationDuration, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - SortingOrder sortingOrder, - LegendIconType legendIconType, - CornerStyle cornerStyle, - List initialSelectedDataIndexes}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + SortingOrder? sortingOrder, + LegendIconType? legendIconType, + CornerStyle? cornerStyle, + List? initialSelectedDataIndexes}) : super( key: key, onCreateRenderer: onCreateRenderer, onRendererCreated: onRendererCreated, dataSource: dataSource, - xValueMapper: (int index) => xValueMapper(dataSource[index], index), - yValueMapper: (int index) => yValueMapper(dataSource[index], index), + xValueMapper: (int index) => xValueMapper(dataSource![index], index), + yValueMapper: (int index) => yValueMapper(dataSource![index], index), pointColorMapper: (int index) => pointColorMapper != null - ? pointColorMapper(dataSource[index], index) + ? pointColorMapper(dataSource![index], index) : null, pointRadiusMapper: pointRadiusMapper == null ? null - : (int index) => pointRadiusMapper(dataSource[index], index), + : (int index) => pointRadiusMapper(dataSource![index], index), + pointShaderMapper: pointShaderMapper != null + ? (dynamic data, int index, Color color, Rect rect) => + pointShaderMapper(dataSource![index], index, color, rect) + : null, dataLabelMapper: (int index) => dataLabelMapper != null - ? dataLabelMapper(dataSource[index], index) + ? dataLabelMapper(dataSource![index], index) : null, sortFieldValueMapper: sortFieldValueMapper != null - ? (int index) => sortFieldValueMapper(dataSource[index], index) + ? (int index) => sortFieldValueMapper(dataSource![index], index) : null, animationDuration: animationDuration, startAngle: startAngle, @@ -77,6 +83,7 @@ class DoughnutSeries extends CircularSeries { explodeIndex: explodeIndex, explodeOffset: explodeOffset, explodeGesture: explodeGesture, + pointRenderMode: pointRenderMode, groupMode: groupMode, groupTo: groupTo, emptyPointSettings: emptyPointSettings, @@ -95,10 +102,10 @@ class DoughnutSeries extends CircularSeries { ); /// Create the circular series renderer. - DoughnutSeriesRenderer createRenderer(CircularSeries series) { - DoughnutSeriesRenderer seriesRenderer; + DoughnutSeriesRenderer? createRenderer(CircularSeries series) { + DoughnutSeriesRenderer? seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as DoughnutSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -109,48 +116,51 @@ class DoughnutSeries extends CircularSeries { class _DoughnutChartPainter extends CustomPainter { _DoughnutChartPainter({ - this.chartState, - this.index, - this.isRepaint, + required this.chartState, + required this.index, + required this.isRepaint, this.animationController, this.seriesAnimation, - ValueNotifier notifier, + required ValueNotifier notifier, }) : super(repaint: notifier); final SfCircularChartState chartState; final int index; final bool isRepaint; - final AnimationController animationController; - final Animation seriesAnimation; + final AnimationController? animationController; + final Animation? seriesAnimation; - DoughnutSeriesRenderer seriesRenderer; + late DoughnutSeriesRenderer seriesRenderer; /// To paint series @override void paint(Canvas canvas, Size size) { final SfCircularChart chart = chartState._chart; - num pointStartAngle; - seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[index]; + num? pointStartAngle; + seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[index] + as DoughnutSeriesRenderer; pointStartAngle = seriesRenderer._start; seriesRenderer._innerRadius = seriesRenderer._currentInnerRadius; seriesRenderer._radius = seriesRenderer._currentRadius; ChartPoint point; seriesRenderer._pointRegions = <_Region>[]; - ChartPoint _oldPoint; - final DoughnutSeriesRenderer oldSeriesRenderer = + ChartPoint? _oldPoint; + final DoughnutSeriesRenderer? oldSeriesRenderer = (chartState._widgetNeedUpdate && !chartState._isLegendToggled && - chartState._prevSeriesRenderer._seriesType == 'doughnut') - ? chartState._prevSeriesRenderer + chartState._prevSeriesRenderer?._seriesType == 'doughnut') + ? chartState._prevSeriesRenderer as DoughnutSeriesRenderer : null; - for (int i = 0; i < seriesRenderer._renderPoints.length; i++) { - point = seriesRenderer._renderPoints[i]; + seriesRenderer._renderPaths.clear(); + seriesRenderer._renderList.clear(); + for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { + point = seriesRenderer._renderPoints![i]; _oldPoint = (oldSeriesRenderer != null && oldSeriesRenderer._oldRenderPoints != null && - (oldSeriesRenderer._oldRenderPoints.length - 1 >= i)) - ? oldSeriesRenderer._oldRenderPoints[i] + (oldSeriesRenderer._oldRenderPoints!.length - 1 >= i)) + ? oldSeriesRenderer._oldRenderPoints![i] : ((chartState._isLegendToggled && - chartState._prevSeriesRenderer._seriesType == 'doughnut') - ? chartState._oldPoints[i] + chartState._prevSeriesRenderer?._seriesType == 'doughnut') + ? chartState._oldPoints![i] : null); pointStartAngle = seriesRenderer._circularRenderPoint( chart, @@ -162,12 +172,41 @@ class _DoughnutChartPainter extends CustomPainter { canvas, index, i, - seriesAnimation != null ? seriesAnimation?.value : 1, + seriesAnimation?.value ?? 1, 1, _checkIsAnyPointSelect(seriesRenderer, point, chart), _oldPoint, chartState._oldPoints); } + + if (seriesRenderer._renderList.isNotEmpty) { + Shader? _chartShader; + if (chart.onCreateShader != null) { + ChartShaderDetails chartShaderDetails; + chartShaderDetails = ChartShaderDetails(seriesRenderer._renderList[1], + seriesRenderer._renderList[2], 'series'); + _chartShader = chart.onCreateShader!(chartShaderDetails); + } + for (int k = 0; k < seriesRenderer._renderPaths.length; k++) { + _drawPath( + canvas, + seriesRenderer._renderList[0], + seriesRenderer._renderPaths[k], + seriesRenderer._renderList[1], + _chartShader); + } + if (seriesRenderer._renderList[0].strokeColor != null && + seriesRenderer._renderList[0].strokeWidth != null && + seriesRenderer._renderList[0].strokeWidth > 0) { + final Paint paint = Paint(); + paint.color = seriesRenderer._renderList[0].strokeColor; + paint.strokeWidth = seriesRenderer._renderList[0].strokeWidth; + paint.style = PaintingStyle.stroke; + for (int k = 0; k < seriesRenderer._renderPaths.length; k++) { + canvas.drawPath(seriesRenderer._renderPaths[k], paint); + } + } + } } @override @@ -180,11 +219,11 @@ class DoughnutSeriesRenderer extends CircularSeriesRenderer { DoughnutSeriesRenderer(); /// stores the series of the corresponding series for renderer - CircularSeries series; + late CircularSeries series; //ignore: unused_field - num _innerRadius; + late num _innerRadius; //ignore: unused_field - num _radius; + late num _radius; } diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/pie_series.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/pie_series.dart index 91c6bdaed..efa65005b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/pie_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/pie_series.dart @@ -9,60 +9,68 @@ part of charts; class PieSeries extends CircularSeries { /// Creating an argument constructor of PieSeries class. PieSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - CircularSeriesRendererCreatedCallback onRendererCreated, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper yValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper pointRadiusMapper, - ChartValueMapper dataLabelMapper, - ChartValueMapper sortFieldValueMapper, - int startAngle, - int endAngle, - String radius, - bool explode, - bool explodeAll, - int explodeIndex, - ActivationMode explodeGesture, - String explodeOffset, - double groupTo, - CircularChartGroupMode groupMode, - EmptyPointSettings emptyPointSettings, - Color strokeColor, - double strokeWidth, - double opacity, - DataLabelSettings dataLabelSettings, - bool enableTooltip, - bool enableSmartLabels, - String name, - double animationDuration, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + CircularSeriesRendererCreatedCallback? onRendererCreated, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? yValueMapper, + ChartValueMapper? pointColorMapper, + ChartShaderMapper? pointShaderMapper, + ChartValueMapper? pointRadiusMapper, + ChartValueMapper? dataLabelMapper, + ChartValueMapper? sortFieldValueMapper, + int? startAngle, + int? endAngle, + String? radius, + bool? explode, + bool? explodeAll, + int? explodeIndex, + ActivationMode? explodeGesture, + String? explodeOffset, + double? groupTo, + CircularChartGroupMode? groupMode, + PointRenderMode? pointRenderMode, + EmptyPointSettings? emptyPointSettings, + Color? strokeColor, + double? strokeWidth, + double? opacity, + DataLabelSettings? dataLabelSettings, + bool? enableTooltip, + bool? enableSmartLabels, + String? name, + double? animationDuration, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - SortingOrder sortingOrder, - LegendIconType legendIconType, - List initialSelectedDataIndexes}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + SortingOrder? sortingOrder, + LegendIconType? legendIconType, + List? initialSelectedDataIndexes}) : super( key: key, onCreateRenderer: onCreateRenderer, onRendererCreated: onRendererCreated, animationDuration: animationDuration, dataSource: dataSource, - xValueMapper: (int index) => xValueMapper(dataSource[index], index), - yValueMapper: (int index) => yValueMapper(dataSource[index], index), + xValueMapper: (int index) => + xValueMapper!(dataSource![index], index), + yValueMapper: (int index) => + yValueMapper!(dataSource![index], index), pointColorMapper: (int index) => pointColorMapper != null - ? pointColorMapper(dataSource[index], index) + ? pointColorMapper(dataSource![index], index) : null, pointRadiusMapper: pointRadiusMapper == null ? null - : (int index) => pointRadiusMapper(dataSource[index], index), + : (int index) => pointRadiusMapper(dataSource![index], index), dataLabelMapper: (int index) => dataLabelMapper != null - ? dataLabelMapper(dataSource[index], index) + ? dataLabelMapper(dataSource![index], index) : null, sortFieldValueMapper: sortFieldValueMapper != null - ? (int index) => sortFieldValueMapper(dataSource[index], index) + ? (int index) => sortFieldValueMapper(dataSource![index], index) + : null, + pointShaderMapper: pointShaderMapper != null + ? (dynamic data, int index, Color color, Rect rect) => + pointShaderMapper(dataSource![index], index, color, rect) : null, startAngle: startAngle, endAngle: endAngle, @@ -74,6 +82,7 @@ class PieSeries extends CircularSeries { explodeGesture: explodeGesture, groupTo: groupTo, groupMode: groupMode, + pointRenderMode: pointRenderMode, emptyPointSettings: emptyPointSettings, initialSelectedDataIndexes: initialSelectedDataIndexes, borderColor: strokeColor, @@ -89,10 +98,10 @@ class PieSeries extends CircularSeries { enableSmartLabels: enableSmartLabels); /// Create the pie series renderer. - PieSeriesRenderer createRenderer(CircularSeries series) { - PieSeriesRenderer seriesRenderer; + PieSeriesRenderer? createRenderer(CircularSeries series) { + PieSeriesRenderer? seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as PieSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -103,52 +112,56 @@ class PieSeries extends CircularSeries { class _PieChartPainter extends CustomPainter { _PieChartPainter({ - this.chartState, - this.index, - this.isRepaint, + required this.chartState, + required this.index, + required this.isRepaint, this.animationController, this.seriesAnimation, - ValueNotifier notifier, - }) : chart = chartState._chart, + required ValueNotifier notifier, + }) : chart = chartState._chart, super(repaint: notifier); final SfCircularChartState chartState; final SfCircularChart chart; final int index; final bool isRepaint; - final AnimationController animationController; - final Animation seriesAnimation; + final AnimationController? animationController; + final Animation? seriesAnimation; - PieSeriesRenderer seriesRenderer; + late PieSeriesRenderer seriesRenderer; /// To paint series @override void paint(Canvas canvas, Size size) { - num pointStartAngle; - seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[index]; + num? pointStartAngle; + seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[index] + as PieSeriesRenderer; pointStartAngle = seriesRenderer._start; seriesRenderer._pointRegions = <_Region>[]; bool isAnyPointNeedSelect = false; - if (chartState._initialRender) { + if (chartState._initialRender!) { isAnyPointNeedSelect = _checkIsAnyPointSelect(seriesRenderer, seriesRenderer._point, chart); } - ChartPoint _oldPoint; - ChartPoint point = seriesRenderer._point; - final PieSeriesRenderer oldSeriesRenderer = (chartState._widgetNeedUpdate && - !chartState._isLegendToggled && - chartState._prevSeriesRenderer != null && - chartState._prevSeriesRenderer._seriesType == 'pie') - ? chartState._prevSeriesRenderer - : null; - for (int i = 0; i < seriesRenderer._renderPoints.length; i++) { - point = seriesRenderer._renderPoints[i]; + ChartPoint? _oldPoint; + ChartPoint? point = seriesRenderer._point; + final PieSeriesRenderer? oldSeriesRenderer = + (chartState._widgetNeedUpdate && + !chartState._isLegendToggled && + chartState._prevSeriesRenderer != null && + chartState._prevSeriesRenderer!._seriesType == 'pie') + ? chartState._prevSeriesRenderer! as PieSeriesRenderer + : null; + seriesRenderer._renderPaths.clear(); + seriesRenderer._renderList.clear(); + for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { + point = seriesRenderer._renderPoints![i]; _oldPoint = (oldSeriesRenderer != null && oldSeriesRenderer._oldRenderPoints != null && - (oldSeriesRenderer._oldRenderPoints.length - 1 >= i)) - ? oldSeriesRenderer._oldRenderPoints[i] + (oldSeriesRenderer._oldRenderPoints!.length - 1 >= i)) + ? oldSeriesRenderer._oldRenderPoints![i] : ((chartState._isLegendToggled && - chartState._prevSeriesRenderer._seriesType == 'pie') - ? chartState._oldPoints[i] + chartState._prevSeriesRenderer?._seriesType == 'pie') + ? chartState._oldPoints![i] : null); point.innerRadius = 0.0; pointStartAngle = seriesRenderer._circularRenderPoint( @@ -161,12 +174,40 @@ class _PieChartPainter extends CustomPainter { canvas, index, i, - seriesAnimation != null ? seriesAnimation?.value : 1, - seriesAnimation != null ? seriesAnimation?.value : 1, + seriesAnimation?.value ?? 1, + seriesAnimation?.value ?? 1, isAnyPointNeedSelect, _oldPoint, chartState._oldPoints); } + if (seriesRenderer._renderList.isNotEmpty) { + Shader? _chartShader; + if (chart.onCreateShader != null) { + ChartShaderDetails chartShaderDetails; + chartShaderDetails = + ChartShaderDetails(seriesRenderer._renderList[1], null, 'series'); + _chartShader = chart.onCreateShader!(chartShaderDetails); + } + for (int k = 0; k < seriesRenderer._renderPaths.length; k++) { + _drawPath( + canvas, + seriesRenderer._renderList[0], + seriesRenderer._renderPaths[k], + seriesRenderer._renderList[1], + _chartShader); + } + if (seriesRenderer._renderList[0].strokeColor != null && + seriesRenderer._renderList[0].strokeWidth != null && + seriesRenderer._renderList[0].strokeWidth > 0) { + final Paint paint = Paint(); + paint.color = seriesRenderer._renderList[0].strokeColor; + paint.strokeWidth = seriesRenderer._renderList[0].strokeWidth; + paint.style = PaintingStyle.stroke; + for (int k = 0; k < seriesRenderer._renderPaths.length; k++) { + canvas.drawPath(seriesRenderer._renderPaths[k], paint); + } + } + } } @override @@ -178,6 +219,6 @@ class PieSeriesRenderer extends CircularSeriesRenderer { /// Calling the default constructor of PieSeriesRenderer class. PieSeriesRenderer(); @override - CircularSeries _series; - ChartPoint _point; + late CircularSeries _series; + ChartPoint? _point; } diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/radial_bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/radial_bar_series.dart index c1e1904ef..8d98e8039 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/radial_bar_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/radial_bar_series.dart @@ -10,59 +10,64 @@ part of charts; class RadialBarSeries extends CircularSeries { /// Creating an argument constructor of RadialBarSeries class. RadialBarSeries( - {ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - CircularSeriesRendererCreatedCallback onRendererCreated, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper yValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper pointRadiusMapper, - ChartValueMapper dataLabelMapper, - ChartValueMapper sortFieldValueMapper, + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + CircularSeriesRendererCreatedCallback? onRendererCreated, + List? dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? pointColorMapper, + ChartShaderMapper? pointShaderMapper, + ChartValueMapper? pointRadiusMapper, + ChartValueMapper? dataLabelMapper, + ChartValueMapper? sortFieldValueMapper, this.trackColor = const Color.fromRGBO(234, 236, 239, 1.0), this.trackBorderWidth = 0.0, this.trackOpacity = 1, this.useSeriesColor = false, this.trackBorderColor = Colors.transparent, this.maximumValue, - DataLabelSettings dataLabelSettings, - String radius, - String innerRadius, - String gap, - double strokeWidth, - double opacity, - Color strokeColor, - bool enableTooltip, - bool enableSmartLabels, - String name, - double animationDuration, + DataLabelSettings? dataLabelSettings, + String? radius, + String? innerRadius, + String? gap, + double? strokeWidth, + double? opacity, + Color? strokeColor, + bool? enableTooltip, + bool? enableSmartLabels, + String? name, + double? animationDuration, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - SortingOrder sortingOrder, - LegendIconType legendIconType, - CornerStyle cornerStyle, - List initialSelectedDataIndexes}) + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + SortingOrder? sortingOrder, + LegendIconType? legendIconType, + CornerStyle? cornerStyle, + List? initialSelectedDataIndexes}) : super( key: key, onCreateRenderer: onCreateRenderer, onRendererCreated: onRendererCreated, dataSource: dataSource, animationDuration: animationDuration, - xValueMapper: (int index) => xValueMapper(dataSource[index], index), - yValueMapper: (int index) => yValueMapper(dataSource[index], index), + xValueMapper: (int index) => xValueMapper(dataSource![index], index), + yValueMapper: (int index) => yValueMapper(dataSource![index], index), pointColorMapper: (int index) => pointColorMapper != null - ? pointColorMapper(dataSource[index], index) + ? pointColorMapper(dataSource![index], index) : null, pointRadiusMapper: (int index) => pointRadiusMapper != null - ? pointRadiusMapper(dataSource[index], index) + ? pointRadiusMapper(dataSource![index], index) + : null, + pointShaderMapper: pointShaderMapper != null + ? (dynamic data, int index, Color color, Rect rect) => + pointShaderMapper(dataSource![index], index, color, rect) : null, dataLabelMapper: (int index) => dataLabelMapper != null - ? dataLabelMapper(dataSource[index], index) + ? dataLabelMapper(dataSource![index], index) : null, sortFieldValueMapper: sortFieldValueMapper != null - ? (int index) => sortFieldValueMapper(dataSource[index], index) + ? (int index) => sortFieldValueMapper(dataSource![index], index) : null, radius: radius, innerRadius: innerRadius, @@ -116,7 +121,7 @@ class RadialBarSeries extends CircularSeries { /// )); ///} ///``` - final double maximumValue; + final double? maximumValue; ///Border color of the track /// @@ -205,7 +210,7 @@ class RadialBarSeries extends CircularSeries { RadialBarSeriesRenderer createRenderer(CircularSeries series) { RadialBarSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as RadialBarSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -216,74 +221,77 @@ class RadialBarSeries extends CircularSeries { class _RadialBarPainter extends CustomPainter { _RadialBarPainter({ - this.chartState, - this.index, - this.isRepaint, + required this.chartState, + required this.index, + required this.isRepaint, this.animationController, this.seriesAnimation, - ValueNotifier notifier, + ValueNotifier? notifier, }) : super(repaint: notifier); final SfCircularChartState chartState; final int index; final bool isRepaint; - final AnimationController animationController; - final Animation seriesAnimation; - RadialBarSeriesRenderer seriesRenderer; + final AnimationController? animationController; + final Animation? seriesAnimation; + late RadialBarSeriesRenderer seriesRenderer; /// To paint radial bar series @override void paint(Canvas canvas, Size size) { - num pointStartAngle, pointEndAngle, degree, length = 0; - seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[index]; + num? pointStartAngle, pointEndAngle, degree; + num length = 0; + seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[index] + as RadialBarSeriesRenderer; seriesRenderer._pointRegions = <_Region>[]; final num sum = seriesRenderer._sumOfPoints, actualStartAngle = seriesRenderer._start; seriesRenderer._innerRadius = seriesRenderer._currentInnerRadius; seriesRenderer._radius = seriesRenderer._currentRadius; - ChartPoint point, _oldPoint; - seriesRenderer._center = seriesRenderer._center; + ChartPoint? _oldPoint; + late ChartPoint point; + seriesRenderer._center = seriesRenderer._center!; canvas.clipRect(chartState._chartAreaRect); /// finding visible points count - for (int i = 0; i < seriesRenderer._renderPoints.length; i++) { - length += seriesRenderer._renderPoints[i].isVisible ? 1 : 0; + for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { + length += seriesRenderer._renderPoints![i].isVisible ? 1 : 0; } /// finding first visible point - final int firstVisible = + final int? firstVisible = seriesRenderer._getFirstVisiblePointIndex(seriesRenderer); final num ringSize = (seriesRenderer._currentRadius - seriesRenderer._currentInnerRadius) .abs() / length; - final num gap = _percentToValue( + final num? gap = _percentToValue( seriesRenderer._series.gap, (seriesRenderer._currentRadius - seriesRenderer._currentInnerRadius) .abs()); - final num animationValue = - seriesAnimation != null ? seriesAnimation.value : 1; + final num animationValue = seriesAnimation?.value ?? 1; final bool isLegendToggle = chartState._isLegendToggled; - final RadialBarSeriesRenderer oldSeriesRenderer = + final RadialBarSeriesRenderer? oldSeriesRenderer = (chartState._widgetNeedUpdate && !chartState._isLegendToggled && - chartState._prevSeriesRenderer._seriesType == 'radialbar') - ? chartState._prevSeriesRenderer + chartState._prevSeriesRenderer!._seriesType == 'radialbar') + ? chartState._prevSeriesRenderer! as RadialBarSeriesRenderer : null; - for (int i = 0; i < seriesRenderer._renderPoints.length; i++) { - point = seriesRenderer._renderPoints[i]; - RadialBarSeries series; + seriesRenderer._renderPaths.clear(); + for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { + point = seriesRenderer._renderPoints![i]; + late RadialBarSeries series; if (seriesRenderer._series is RadialBarSeries) { - series = seriesRenderer._series; + series = seriesRenderer._series as RadialBarSeries; } _oldPoint = (oldSeriesRenderer != null && oldSeriesRenderer._oldRenderPoints != null && - (oldSeriesRenderer._oldRenderPoints.length - 1 >= i)) - ? oldSeriesRenderer._oldRenderPoints[i] - : (isLegendToggle ? chartState._oldPoints[i] : null); + (oldSeriesRenderer._oldRenderPoints!.length - 1 >= i)) + ? oldSeriesRenderer._oldRenderPoints![i] + : (isLegendToggle ? chartState._oldPoints![i] : null); pointStartAngle = actualStartAngle; final bool isDynamicUpdate = _oldPoint != null; bool hide = false; - num oldStart, oldEnd, oldRadius, oldInnerRadius, value; + num? oldStart, oldEnd, oldRadius, oldInnerRadius, value; if (!isDynamicUpdate || ((_oldPoint.isVisible && point.isVisible) || (_oldPoint.isVisible && !point.isVisible) || @@ -291,41 +299,42 @@ class _RadialBarPainter extends CustomPainter { if (point.isVisible) { hide = false; if (isDynamicUpdate && !isLegendToggle) { - value = (point.y > _oldPoint.y) - ? _oldPoint.y + (point.y - _oldPoint.y) * animationValue - : _oldPoint.y - (_oldPoint.y - point.y) * animationValue; + value = (point.y! > _oldPoint.y!) + ? _oldPoint.y! + (point.y! - _oldPoint.y!) * animationValue + : _oldPoint.y! - (_oldPoint.y! - point.y!) * animationValue; } - degree = (value ?? point.y).abs() / (series.maximumValue ?? sum); + degree = (value ?? point.y!).abs() / (series.maximumValue ?? sum); degree = (degree > 1 ? 1 : degree) * (360 - 0.001); degree = isDynamicUpdate ? degree : degree * animationValue; pointEndAngle = pointStartAngle + degree; point.midAngle = (pointStartAngle + pointEndAngle) / 2; point.startAngle = pointStartAngle; point.endAngle = pointEndAngle; - point.center = seriesRenderer._center; + point.center = seriesRenderer._center!; point.innerRadius = seriesRenderer._innerRadius = seriesRenderer._innerRadius + ((i == firstVisible) ? 0 : ringSize); - point.outerRadius = seriesRenderer._radius = - ringSize < gap ? 0 : seriesRenderer._innerRadius + ringSize - gap; + point.outerRadius = seriesRenderer._radius = ringSize < gap! + ? 0 + : seriesRenderer._innerRadius + ringSize - gap; if (isLegendToggle) { seriesRenderer._calculateVisiblePointLegendToggleAnimation( point, _oldPoint, i, animationValue); } } //animate on hiding - else if (isLegendToggle && !point.isVisible && _oldPoint.isVisible) { + else if (isLegendToggle && !point.isVisible && _oldPoint!.isVisible) { hide = true; oldEnd = _oldPoint.endAngle; oldStart = _oldPoint.startAngle; - degree = _oldPoint.y.abs() / (series.maximumValue ?? sum); + degree = _oldPoint.y!.abs() / (series.maximumValue ?? sum); degree = (degree > 1 ? 1 : degree) * (360 - 0.001); - oldInnerRadius = _oldPoint.innerRadius + - ((_oldPoint.outerRadius + _oldPoint.innerRadius) / 2 - - _oldPoint.innerRadius) * + oldInnerRadius = _oldPoint.innerRadius! + + ((_oldPoint.outerRadius! + _oldPoint.innerRadius!) / 2 - + _oldPoint.innerRadius!) * animationValue; - oldRadius = _oldPoint.outerRadius - - (_oldPoint.outerRadius - - (_oldPoint.outerRadius + _oldPoint.innerRadius) / 2) * + oldRadius = _oldPoint.outerRadius! - + (_oldPoint.outerRadius! - + (_oldPoint.outerRadius! + _oldPoint.innerRadius!) / 2) * animationValue; } if (seriesRenderer is RadialBarSeriesRenderer) { @@ -348,6 +357,34 @@ class _RadialBarPainter extends CustomPainter { } } } + if (seriesRenderer._renderList.isNotEmpty) { + Shader? _chartShader; + if (chartState._chart.onCreateShader != null) { + ChartShaderDetails chartShaderDetails; + chartShaderDetails = ChartShaderDetails(seriesRenderer._renderList[1], + seriesRenderer._renderList[2], 'series'); + _chartShader = chartState._chart.onCreateShader!(chartShaderDetails); + } + for (int k = 0; k < seriesRenderer._renderPaths.length; k++) { + _drawPath( + canvas, + seriesRenderer._renderList[0], + seriesRenderer._renderPaths[k], + seriesRenderer._renderList[1], + _chartShader!); + } + if (seriesRenderer._renderList[0].strokeColor != null && + seriesRenderer._renderList[0].strokeWidth != null && + seriesRenderer._renderList[0].strokeWidth > 0) { + final Paint paint = Paint(); + paint.color = seriesRenderer._renderList[0].strokeColor; + paint.strokeWidth = seriesRenderer._renderList[0].strokeWidth; + paint.style = PaintingStyle.stroke; + for (int k = 0; k < seriesRenderer._renderPaths.length; k++) { + canvas.drawPath(seriesRenderer._renderPaths[k], paint); + } + } + } } @override @@ -360,19 +397,19 @@ class RadialBarSeriesRenderer extends CircularSeriesRenderer { RadialBarSeriesRenderer(); @override - CircularSeries _series; + late CircularSeries _series; - num _innerRadius; + late num _innerRadius; - num _radius; + late num _radius; @override - Offset _center; + Offset? _center; /// finding first visible point - int _getFirstVisiblePointIndex(RadialBarSeriesRenderer seriesRenderer) { - for (int i = 0; i < seriesRenderer._renderPoints.length; i++) { - if (seriesRenderer._renderPoints[i].isVisible) { + int? _getFirstVisiblePointIndex(RadialBarSeriesRenderer seriesRenderer) { + for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { + if (seriesRenderer._renderPoints![i].isVisible) { return i; } } @@ -381,68 +418,68 @@ class RadialBarSeriesRenderer extends CircularSeriesRenderer { /// Method for calculating animation for visible points on legend toggle void _calculateVisiblePointLegendToggleAnimation(ChartPoint point, - ChartPoint _oldPoint, int i, num animationValue) { - if (!_oldPoint.isVisible && point.isVisible) { + ChartPoint? _oldPoint, int i, num animationValue) { + if (!_oldPoint!.isVisible && point.isVisible) { _radius = i == 0 - ? point.outerRadius - : (point.innerRadius + - (point.outerRadius - point.innerRadius) * animationValue); + ? point.outerRadius! + : (point.innerRadius! + + (point.outerRadius! - point.innerRadius!) * animationValue); _innerRadius = i == 0 - ? (point.outerRadius - - (point.outerRadius - point.innerRadius) * animationValue) + ? (point.outerRadius! - + (point.outerRadius! - point.innerRadius!) * animationValue) : _innerRadius; } else { - _radius = (point.outerRadius > _oldPoint.outerRadius) - ? _oldPoint.outerRadius + - (point.outerRadius - _oldPoint.outerRadius) * animationValue - : _oldPoint.outerRadius - - (_oldPoint.outerRadius - point.outerRadius) * animationValue; - _innerRadius = (point.innerRadius > _oldPoint.innerRadius) - ? _oldPoint.innerRadius + - (point.innerRadius - _oldPoint.innerRadius) * animationValue - : _oldPoint.innerRadius - - (_oldPoint.innerRadius - point.innerRadius) * animationValue; + _radius = (point.outerRadius! > _oldPoint.outerRadius!) + ? _oldPoint.outerRadius! + + (point.outerRadius! - _oldPoint.outerRadius!) * animationValue + : _oldPoint.outerRadius! - + (_oldPoint.outerRadius! - point.outerRadius!) * animationValue; + _innerRadius = (point.innerRadius! > _oldPoint.innerRadius!) + ? _oldPoint.innerRadius! + + (point.innerRadius! - _oldPoint.innerRadius!) * animationValue + : _oldPoint.innerRadius! - + (_oldPoint.innerRadius! - point.innerRadius!) * animationValue; } } /// To draw data points void _drawDataPoint( ChartPoint point, - num degree, + num? degree, num pointStartAngle, - num pointEndAngle, + num? pointEndAngle, RadialBarSeriesRenderer seriesRenderer, bool hide, - num oldRadius, - num oldInnerRadius, - ChartPoint _oldPoint, - num oldStart, - num oldEnd, + num? oldRadius, + num? oldInnerRadius, + ChartPoint? _oldPoint, + num? oldStart, + num? oldEnd, int i, Canvas canvas, int index, SfCircularChart chart) { - RadialBarSeries series; + late RadialBarSeries series; if (seriesRenderer._series is RadialBarSeries) { - series = seriesRenderer._series; + series = seriesRenderer._series as RadialBarSeries; } _drawPath( canvas, _StyleOptions( - series.useSeriesColor ? point.fill : series.trackColor, - series.trackBorderWidth, - series.trackBorderColor, - series.trackOpacity), + fill: series.useSeriesColor ? point.fill : series.trackColor, + strokeWidth: series.trackBorderWidth, + strokeColor: series.trackBorderColor, + opacity: series.trackOpacity), _getArcPath( - hide ? oldInnerRadius : _innerRadius, - hide ? oldRadius : _radius.toDouble(), - _center, + hide ? oldInnerRadius! : _innerRadius, + hide ? oldRadius! : _radius.toDouble(), + _center!, 0, 360 - 0.001, 360 - 0.001, chart, true)); - if (_radius > 0 && degree > 0) { + if (_radius > 0 && degree! > 0) { _renderRadialPoints( point, degree, @@ -465,87 +502,132 @@ class RadialBarSeriesRenderer extends CircularSeriesRenderer { /// To render radial data points void _renderRadialPoints( ChartPoint point, - num degree, + num? degree, num pointStartAngle, - num pointEndAngle, + num? pointEndAngle, RadialBarSeriesRenderer seriesRenderer, bool hide, - num oldRadius, - num oldInnerRadius, - ChartPoint _oldPoint, - num oldStart, - num oldEnd, + num? oldRadius, + num? oldInnerRadius, + ChartPoint? _oldPoint, + num? oldStart, + num? oldEnd, int i, Canvas canvas, int index, SfCircularChart chart) { if (point.isVisible) { final _Region pointRegion = _Region( - _degreesToRadians(point.startAngle), - _degreesToRadians(point.endAngle), - point.startAngle, - point.endAngle, + _degreesToRadians(point.startAngle!), + _degreesToRadians(point.endAngle!), + point.startAngle!, + point.endAngle!, index, i, point.center, _innerRadius, - point.outerRadius); + point.outerRadius!); seriesRenderer._pointRegions.add(pointRegion); } final num angleDeviation = _findAngleDeviation( - hide ? oldInnerRadius : _innerRadius, hide ? oldRadius : _radius, 360); + hide ? oldInnerRadius! : _innerRadius, + hide ? oldRadius! : _radius, + 360); final CornerStyle cornerStyle = _series.cornerStyle; if (cornerStyle == CornerStyle.bothCurve || cornerStyle == CornerStyle.startCurve) { hide - ? oldStart = _oldPoint.startAngle + angleDeviation + ? oldStart = _oldPoint!.startAngle! + angleDeviation : pointStartAngle += angleDeviation; } if (cornerStyle == CornerStyle.bothCurve || cornerStyle == CornerStyle.endCurve) { hide - ? oldEnd = _oldPoint.endAngle - angleDeviation - : pointEndAngle -= angleDeviation; + ? oldEnd = _oldPoint!.endAngle! - angleDeviation + : pointEndAngle = pointEndAngle! - angleDeviation; } - final _StyleOptions style = + final _StyleOptions? style = seriesRenderer._selectPoint(i, seriesRenderer, chart, point); final Color fillColor = style != null && style.fill != null - ? style.fill + ? style.fill! : (point.fill != Colors.transparent - ? _series._renderer.getPointColor( - seriesRenderer, point, i, index, point.fill, _series.opacity) + ? _series._renderer!.getPointColor( + seriesRenderer, point, i, index, point.fill, _series.opacity)! : point.fill); final Color strokeColor = style != null && style.strokeColor != null - ? style.strokeColor - : _series._renderer.getPointStrokeColor( + ? style.strokeColor! + : _series._renderer!.getPointStrokeColor( seriesRenderer, point, i, index, point.strokeColor); final double strokeWidth = style != null && style.strokeWidth != null - ? style.strokeWidth - : _series._renderer.getPointStrokeWidth( - seriesRenderer, point, i, index, point.strokeWidth); + ? style.strokeWidth!.toDouble() + : _series._renderer! + .getPointStrokeWidth( + seriesRenderer, point, i, index, point.strokeWidth) + .toDouble(); final double opacity = style != null && style.opacity != null - ? style.opacity - : _series._renderer + ? style.opacity!.toDouble() + : _series._renderer! .getOpacity(seriesRenderer, point, i, index, _series.opacity); - if (hide - ? (((oldEnd - oldStart) > 0) && (oldRadius != oldInnerRadius)) - : ((pointEndAngle - pointStartAngle) > 0)) { - _drawPath( - canvas, - _StyleOptions( - fillColor, - _chartState._animateCompleted ? strokeWidth : 0, - strokeColor, - opacity), - _getRoundedCornerArcPath( - hide ? oldInnerRadius : _innerRadius, - hide ? oldRadius : _radius, - _center, - hide ? oldStart : pointStartAngle, - hide ? oldEnd : pointEndAngle, - degree, - _series.cornerStyle)); + seriesRenderer._innerRadialradius = + !(point.isVisible) || (seriesRenderer._innerRadialradius == null) + ? _innerRadius + : seriesRenderer._innerRadialradius; + seriesRenderer._renderList.clear(); + final Path _renderPath = _getRoundedCornerArcPath( + hide ? oldInnerRadius! : _innerRadius, + hide ? oldRadius! : _radius, + _center!, + hide ? oldStart! : pointStartAngle, + hide ? oldEnd! : pointEndAngle!, + degree, + _series.cornerStyle, + point); + seriesRenderer._renderPaths.add(_renderPath); + if (chart.onCreateShader != null && point.shader == null) { + point._pathRect = Rect.fromCircle( + center: _center!, + radius: _radius.toDouble(), + ); + Rect innerRect; + innerRect = Rect.fromCircle( + center: _center!, + radius: seriesRenderer._innerRadialradius!.toDouble(), + ); + seriesRenderer._renderList.add(_StyleOptions( + fill: fillColor, + strokeWidth: _chartState._animateCompleted ? strokeWidth : 0, + strokeColor: strokeColor, + opacity: opacity)); + seriesRenderer._renderList.add(point._pathRect); + seriesRenderer._renderList.add(innerRect); + } else { + if (hide + ? (((oldEnd! - oldStart!) > 0) && (oldRadius != oldInnerRadius)) + : ((pointEndAngle! - pointStartAngle) > 0)) { + _drawPath( + canvas, + _StyleOptions( + fill: fillColor, + strokeWidth: _chartState._animateCompleted ? strokeWidth : 0, + strokeColor: strokeColor, + opacity: opacity), + _renderPath, + point._pathRect, + point.shader); + if (point.shader != null) { + if (strokeColor != null && + strokeWidth != null && + strokeWidth > 0 && + _chartState._animateCompleted) { + final Paint paint = Paint(); + paint.color = strokeColor; + paint.strokeWidth = strokeWidth; + paint.style = PaintingStyle.stroke; + canvas.drawPath(_renderPath, paint); + } + } + } } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/enum.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/enum.dart index c86f8f7d8..e59aff936 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/enum.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/enum.dart @@ -50,3 +50,12 @@ enum CornerStyle { /// - CornerStyle.endCurve, will render ending corner curly. endCurve } + +/// Point Render Mode for circular charts +enum PointRenderMode { + /// - PointRenderMode.segment, will render points in normal behavior. + segment, + + /// - PointRenderMode.gradient, will render points making a sweep gradient based on their values and fill. + gradient, +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart index d14fc8748..79f58b82a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart @@ -1,10 +1,11 @@ part of charts; /// To get equivalent value for the percentage -num _percentToValue(String value, num size) { +num? _percentToValue(String? value, num size) { if (value != null) { return value.contains('%') - ? (size / 100) * (num.tryParse(value.replaceAll(RegExp('%'), ''))).abs() + ? (size / 100) * + (num.tryParse(value.replaceAll(RegExp('%'), '')))!.abs() : (num.tryParse(value))?.abs(); } return null; @@ -14,12 +15,12 @@ num _percentToValue(String value, num size) { num _degreesToRadians(num deg) => deg * (pi / 180); /// To get arc path for circular chart render -Path _getArcPath(num innerRadius, num radius, Offset center, num startAngle, - num endAngle, num degree, SfCircularChart chart, bool isAnimate) { +Path _getArcPath(num innerRadius, num radius, Offset center, num? startAngle, + num? endAngle, num? degree, SfCircularChart chart, bool isAnimate) { final Path path = Path(); - startAngle = _degreesToRadians(startAngle); - endAngle = _degreesToRadians(endAngle); - degree = _degreesToRadians(degree); + startAngle = _degreesToRadians(startAngle!); + endAngle = _degreesToRadians(endAngle!); + degree = _degreesToRadians(degree!); final math.Point innerRadiusStartPoint = math.Point( innerRadius * cos(startAngle) + center.dx, @@ -41,32 +42,37 @@ Path _getArcPath(num innerRadius, num radius, Offset center, num startAngle, final num midpointAngle = (endAngle + startAngle) / 2; - path.lineTo(radiusStartPoint.x, radiusStartPoint.y); - if (isFullCircle) { - path.arcTo(Rect.fromCircle(center: center, radius: radius), startAngle, - midpointAngle - startAngle, true); - path.arcTo(Rect.fromCircle(center: center, radius: radius), midpointAngle, - endAngle - midpointAngle, true); + path.arcTo( + Rect.fromCircle(center: center, radius: radius.toDouble()), + startAngle.toDouble(), + midpointAngle.toDouble() - startAngle.toDouble(), + true); + path.arcTo( + Rect.fromCircle(center: center, radius: radius.toDouble()), + midpointAngle.toDouble(), + endAngle.toDouble() - midpointAngle.toDouble(), + true); } else { - path.arcTo(Rect.fromCircle(center: center, radius: radius), startAngle, - degree, true); + path.lineTo(radiusStartPoint.x, radiusStartPoint.y); + path.arcTo(Rect.fromCircle(center: center, radius: radius.toDouble()), + startAngle.toDouble(), degree.toDouble(), true); } - path.lineTo(innerRadiusEndPoint.x, innerRadiusEndPoint.y); - if (isFullCircle) { - path.arcTo(Rect.fromCircle(center: center, radius: innerRadius), endAngle, - midpointAngle - endAngle, true); - path.arcTo(Rect.fromCircle(center: center, radius: innerRadius), - midpointAngle, startAngle - midpointAngle, true); + path.arcTo( + Rect.fromCircle(center: center, radius: innerRadius.toDouble()), + endAngle.toDouble(), + midpointAngle.toDouble() - endAngle.toDouble(), + true); + path.arcTo(Rect.fromCircle(center: center, radius: innerRadius.toDouble()), + midpointAngle.toDouble(), startAngle - midpointAngle.toDouble(), true); } else { - path.arcTo(Rect.fromCircle(center: center, radius: innerRadius), endAngle, - startAngle - endAngle, true); + path.lineTo(innerRadiusEndPoint.x, innerRadiusEndPoint.y); + path.arcTo(Rect.fromCircle(center: center, radius: innerRadius.toDouble()), + endAngle.toDouble(), startAngle.toDouble() - endAngle.toDouble(), true); + path.lineTo(radiusStartPoint.x, radiusStartPoint.y); } - - path.lineTo(radiusStartPoint.x, radiusStartPoint.y); - return path; } @@ -74,14 +80,21 @@ Path _getArcPath(num innerRadius, num radius, Offset center, num startAngle, ChartPoint _getCircularPoint( CircularSeriesRenderer seriesRenderer, int pointIndex) { final CircularSeries series = seriesRenderer._series; - ChartPoint currentPoint; - final ChartIndexedValueMapper xMap = series.xValueMapper; - final ChartIndexedValueMapper yMap = series.yValueMapper; - final ChartIndexedValueMapper sortFieldMap = + late ChartPoint currentPoint; + final ChartIndexedValueMapper? xMap = series.xValueMapper; + final ChartIndexedValueMapper? yMap = series.yValueMapper; + final ChartIndexedValueMapper? sortFieldMap = series.sortFieldValueMapper; - final ChartIndexedValueMapper radiusMap = series.pointRadiusMapper; - final ChartIndexedValueMapper colorMap = series.pointColorMapper; - dynamic xVal, yVal, radiusVal, colorVal, sortVal; + final ChartIndexedValueMapper? radiusMap = series.pointRadiusMapper; + final ChartIndexedValueMapper? colorMap = series.pointColorMapper; + final ChartShaderMapper? shadeMap = series.pointShaderMapper; + + /// Can be either a string or num value + dynamic xVal; + num? yVal; + String? radiusVal; + Color? colorVal; + String? sortVal; if (xMap != null) { xVal = xMap(pointIndex); } @@ -98,7 +111,6 @@ ChartPoint _getCircularPoint( if (colorMap != null) { colorVal = colorMap(pointIndex); } - if (sortFieldMap != null) { sortVal = sortFieldMap(pointIndex); } @@ -106,13 +118,27 @@ ChartPoint _getCircularPoint( currentPoint = ChartPoint(xVal, yVal, radiusVal, colorVal, sortVal); } + currentPoint.index = pointIndex; + if (shadeMap != null) { + currentPoint._pointShaderMapper = shadeMap; + } else { + currentPoint._pointShaderMapper = null; + } + currentPoint.center = seriesRenderer._center; return currentPoint; } /// To get rounded corners Arc path -Path _getRoundedCornerArcPath(num innerRadius, num outerRadius, Offset center, - num startAngle, num endAngle, num degree, CornerStyle cornerStyle) { +Path _getRoundedCornerArcPath( + num innerRadius, + num outerRadius, + Offset? center, + num startAngle, + num endAngle, + num? degree, + CornerStyle cornerStyle, + ChartPoint point) { final Path path = Path(); Offset _midPoint; @@ -120,7 +146,7 @@ Path _getRoundedCornerArcPath(num innerRadius, num outerRadius, Offset center, if (cornerStyle == CornerStyle.startCurve || cornerStyle == CornerStyle.bothCurve) { _midPoint = - _degreeToPoint(startAngle, (innerRadius + outerRadius) / 2, center); + _degreeToPoint(startAngle, (innerRadius + outerRadius) / 2, center!); midStartAngle = _degreesToRadians(180); @@ -129,12 +155,14 @@ Path _getRoundedCornerArcPath(num innerRadius, num outerRadius, Offset center, path.addArc( Rect.fromCircle( center: _midPoint, radius: (innerRadius - outerRadius).abs() / 2), - midStartAngle, - midEndAngle); + midStartAngle.toDouble(), + midEndAngle.toDouble()); } - path.addArc(Rect.fromCircle(center: center, radius: outerRadius), - _degreesToRadians(startAngle), _degreesToRadians(endAngle - startAngle)); + path.addArc( + Rect.fromCircle(center: center!, radius: outerRadius.toDouble()), + _degreesToRadians(startAngle).toDouble(), + _degreesToRadians(endAngle - startAngle).toDouble()); if (cornerStyle == CornerStyle.endCurve || cornerStyle == CornerStyle.bothCurve) { @@ -148,32 +176,36 @@ Path _getRoundedCornerArcPath(num innerRadius, num outerRadius, Offset center, path.arcTo( Rect.fromCircle( center: _midPoint, radius: (innerRadius - outerRadius).abs() / 2), - midStartAngle, - midEndAngle, + midStartAngle.toDouble(), + midEndAngle.toDouble(), false); } path.arcTo( - Rect.fromCircle(center: center, radius: innerRadius), - _degreesToRadians(endAngle), - _degreesToRadians(startAngle) - _degreesToRadians(endAngle), + Rect.fromCircle(center: center, radius: innerRadius.toDouble()), + _degreesToRadians(endAngle.toDouble()).toDouble(), + (_degreesToRadians(startAngle.toDouble()) - + _degreesToRadians(endAngle.toDouble())) + .toDouble(), false); - return path; } /// To get point region -_Region _getCircularPointRegion(SfCircularChart chart, Offset position, +_Region? _getCircularPointRegion(SfCircularChart chart, Offset? position, CircularSeriesRenderer seriesRenderer) { - _Region pointRegion; + _Region? pointRegion; const num chartStartAngle = -.5 * pi; for (final _Region region in seriesRenderer._pointRegions) { - final num fromCenterX = position.dx - region.center.dx; - final num fromCenterY = position.dy - region.center.dy; + final num fromCenterX = position!.dx - region.center!.dx; + final num fromCenterY = position.dy - region.center!.dy; num tapAngle = (atan2(fromCenterY, fromCenterX) - chartStartAngle) % (2 * pi); num pointStartAngle = region.start - _degreesToRadians(-90); num pointEndAngle = region.end - _degreesToRadians(-90); + if (chart.onDataLabelRender != null) { + seriesRenderer._dataPoints[region.pointIndex].labelRenderEvent = false; + } if (((region.endAngle + 90) > 360) && (region.startAngle + 90) > 360) { pointEndAngle = _degreesToRadians((region.endAngle + 90) % 360); pointStartAngle = _degreesToRadians((region.startAngle + 90) % 360); @@ -184,7 +216,7 @@ _Region _getCircularPointRegion(SfCircularChart chart, Offset position, final num distanceFromCenter = sqrt(pow(fromCenterX.abs(), 2) + pow(fromCenterY.abs(), 2)); if (distanceFromCenter <= region.outerRadius && - distanceFromCenter >= region.innerRadius) { + distanceFromCenter >= region.innerRadius!) { pointRegion = region; } } @@ -194,20 +226,24 @@ _Region _getCircularPointRegion(SfCircularChart chart, Offset position, } /// Draw the path -void _drawPath(Canvas canvas, _StyleOptions style, Path path) { +void _drawPath(Canvas canvas, _StyleOptions style, Path path, + [Rect? rect, Shader? shader]) { final Paint paint = Paint(); + if (shader != null) { + paint..shader = shader; + } if (style.fill != null) { paint.color = style.fill == Colors.transparent - ? style.fill - : style.fill.withOpacity(style.opacity ?? 1); + ? style.fill! + : style.fill!.withOpacity(style.opacity ?? 1); paint.style = PaintingStyle.fill; canvas.drawPath(path, paint); } if (style.strokeColor != null && style.strokeWidth != null && - style.strokeWidth > 0) { - paint.color = style.strokeColor; - paint.strokeWidth = style.strokeWidth; + style.strokeWidth! > 0) { + paint.color = style.strokeColor!; + paint.strokeWidth = style.strokeWidth!.toDouble(); paint.style = PaintingStyle.stroke; canvas.drawPath(path, paint); } @@ -223,9 +259,9 @@ Offset _degreeToPoint(num degree, num radius, Offset center) { /// To repaint circular chart void _needsRepaintCircularChart( List currentSeriesRenderers, - List oldSeriesRenderers) { + List oldSeriesRenderers) { if (currentSeriesRenderers.length == oldSeriesRenderers.length && - currentSeriesRenderers[0]._series == oldSeriesRenderers[0]._series) { + currentSeriesRenderers[0]._series == oldSeriesRenderers[0]!._series) { for (int seriesIndex = 0; seriesIndex < oldSeriesRenderers.length; seriesIndex++) { @@ -241,10 +277,10 @@ void _needsRepaintCircularChart( /// To repaint series void _canRepaintSeries(List currentSeriesRenderers, - List oldSeriesRenderers, int seriesIndex) { + List oldSeriesRenderers, int seriesIndex) { final CircularSeriesRenderer seriesRenderer = currentSeriesRenderers[0]; final CircularSeriesRenderer oldWidgetSeriesRenderer = - oldSeriesRenderers[seriesIndex]; + oldSeriesRenderers[seriesIndex]!; final CircularSeries series = seriesRenderer._series; final CircularSeries oldWidgetSeries = oldWidgetSeriesRenderer._series; @@ -252,20 +288,20 @@ void _canRepaintSeries(List currentSeriesRenderers, seriesRenderer._center?.dx != oldWidgetSeriesRenderer._center?.dx || series.borderWidth != oldWidgetSeries.borderWidth || series.name != oldWidgetSeries.name || - series.borderColor?.value != oldWidgetSeries.borderColor?.value || + series.borderColor.value != oldWidgetSeries.borderColor.value || seriesRenderer._currentInnerRadius != oldWidgetSeriesRenderer._currentInnerRadius || seriesRenderer._currentRadius != oldWidgetSeriesRenderer._currentRadius || seriesRenderer._start != oldWidgetSeriesRenderer._start || seriesRenderer._totalAngle != oldWidgetSeriesRenderer._totalAngle || - seriesRenderer._dataPoints?.length != - oldWidgetSeriesRenderer._dataPoints?.length || + seriesRenderer._dataPoints.length != + oldWidgetSeriesRenderer._dataPoints.length || series.emptyPointSettings.borderWidth != oldWidgetSeries.emptyPointSettings.borderWidth || - series.emptyPointSettings.borderColor?.value != - oldWidgetSeries.emptyPointSettings.borderColor?.value || - series.emptyPointSettings.color?.value != - oldWidgetSeries.emptyPointSettings.color?.value || + series.emptyPointSettings.borderColor.value != + oldWidgetSeries.emptyPointSettings.borderColor.value || + series.emptyPointSettings.color.value != + oldWidgetSeries.emptyPointSettings.color.value || series.emptyPointSettings.mode != oldWidgetSeries.emptyPointSettings.mode || series.dataSource?.length != oldWidgetSeries.dataSource?.length || @@ -277,18 +313,18 @@ void _canRepaintSeries(List currentSeriesRenderers, oldWidgetSeries.dataLabelSettings.borderRadius || series.dataLabelSettings.borderWidth != oldWidgetSeries.dataLabelSettings.borderWidth || - series.dataLabelSettings.borderColor?.value != - oldWidgetSeries.dataLabelSettings.borderColor?.value || - series.dataLabelSettings.textStyle?.color?.value != - oldWidgetSeries.dataLabelSettings.textStyle?.color?.value || - series.dataLabelSettings.textStyle?.fontWeight != - oldWidgetSeries.dataLabelSettings.textStyle?.fontWeight || - series.dataLabelSettings.textStyle?.fontSize != - oldWidgetSeries.dataLabelSettings.textStyle?.fontSize || - series.dataLabelSettings.textStyle?.fontFamily != - oldWidgetSeries.dataLabelSettings.textStyle?.fontFamily || - series.dataLabelSettings.textStyle?.fontStyle != - oldWidgetSeries.dataLabelSettings.textStyle?.fontStyle || + series.dataLabelSettings.borderColor.value != + oldWidgetSeries.dataLabelSettings.borderColor.value || + series.dataLabelSettings.textStyle.color?.value != + oldWidgetSeries.dataLabelSettings.textStyle.color?.value || + series.dataLabelSettings.textStyle.fontWeight != + oldWidgetSeries.dataLabelSettings.textStyle.fontWeight || + series.dataLabelSettings.textStyle.fontSize != + oldWidgetSeries.dataLabelSettings.textStyle.fontSize || + series.dataLabelSettings.textStyle.fontFamily != + oldWidgetSeries.dataLabelSettings.textStyle.fontFamily || + series.dataLabelSettings.textStyle.fontStyle != + oldWidgetSeries.dataLabelSettings.textStyle.fontStyle || series.dataLabelSettings.labelIntersectAction != oldWidgetSeries.dataLabelSettings.labelIntersectAction || series.dataLabelSettings.labelPosition != @@ -325,7 +361,7 @@ num _findAngleDeviation(num innerRadius, num outerRadius, num totalAngle) { } /// It returns the actual label value for tooltip and data label etc -dynamic _getDecimalLabelValue(dynamic value, [int showDigits]) { +dynamic _getDecimalLabelValue(dynamic value, [int? showDigits]) { if (value.toString().split('.').length > 1) { final String str = value.toString(); final List list = str.split('.'); @@ -343,3 +379,9 @@ dynamic _getDecimalLabelValue(dynamic value, [int showDigits]) { final dynamic text = value; return text.toString(); } + +/// Method to rotate Sweep gradient +Float64List _resolveTransform(Rect bounds, TextDirection textDirection) { + final GradientTransform transform = GradientRotation(degreeToRadian(-90)); + return transform.transform(bounds, textDirection: textDirection)!.storage; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/common.dart b/packages/syncfusion_flutter_charts/lib/src/common/common.dart index 4ab087efe..564697e11 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/common.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/common.dart @@ -1,7 +1,7 @@ part of charts; class _ChartContainer extends SingleChildRenderObjectWidget { - const _ChartContainer({Widget child}) : super(child: child); + const _ChartContainer({required Widget child}) : super(child: child); @override RenderObject createRenderObject(BuildContext context) { @@ -24,7 +24,7 @@ class _ChartContainerBox extends RenderShiftedBox { if (width == double.infinity) { width = minWidth; } - child.layout( + child!.layout( BoxConstraints( minHeight: 0.0, maxHeight: height, @@ -54,7 +54,7 @@ class ChartTitle { /// Creating an argument constructor of ChartTitle class. ChartTitle( {this.text = '', - TextStyle textStyle, + TextStyle? textStyle, this.alignment = ChartAlignment.center, this.borderColor = Colors.transparent, this.borderWidth = 0, @@ -143,7 +143,7 @@ class ChartTitle { /// )); ///} ///``` - final Color backgroundColor; + final Color? backgroundColor; ///Border color of the chart title. /// @@ -215,7 +215,7 @@ class ChartTextStyle extends TextStyle { ///} ///``` @override - final Color color; + final Color? color; /// To set the font family to chart text /// @@ -288,35 +288,35 @@ class ChartTextStyle extends TextStyle { ///legend item and for [SfCircularChart] type chart by default values mapped with ///xValueMapper will be displayed. /// -///You can customized with [isVisible], [borderWidth], [alignment], [opacity], [borderColor], -///[padding] and so on. +///You can customized with `isVisible`, `borderWidth`, `alignment`, `opacity`, `borderColor`, +///`padding` and so on. /// ///_Note:_ This is common for [SfCartesianChart] and [SfCircularChart] class Legend { /// Creating an argument constructor of Legend class. Legend( - {bool isVisible, - LegendPosition position, - ChartAlignment alignment, + {bool? isVisible, + LegendPosition? position, + ChartAlignment? alignment, this.backgroundColor, - Color borderColor, - double borderWidth, - double opacity, + Color? borderColor, + double? borderWidth, + double? opacity, this.height, this.width, - double padding, - double iconHeight, - double iconWidth, - bool toggleSeriesVisibility, - TextStyle textStyle, - bool isResponsive, - LegendItemOrientation orientation, - LegendTitle title, - LegendItemOverflowMode overflowMode, + double? padding, + double? iconHeight, + double? iconWidth, + bool? toggleSeriesVisibility, + TextStyle? textStyle, + bool? isResponsive, + LegendItemOrientation? orientation, + LegendTitle? title, + LegendItemOverflowMode? overflowMode, this.legendItemBuilder, - Color iconBorderColor, - double iconBorderWidth, - double itemPadding, + Color? iconBorderColor, + double? iconBorderWidth, + double? itemPadding, this.image}) : isVisible = isVisible ?? false, position = position ?? LegendPosition.auto, @@ -353,7 +353,7 @@ class Legend { /// )); ///} ///``` - final bool isVisible; + final bool? isVisible; ///Position of the legend. /// @@ -412,7 +412,7 @@ class Legend { /// )); ///} ///``` - final Color backgroundColor; + final Color? backgroundColor; ///Border color of the legend. /// @@ -515,7 +515,7 @@ class Legend { /// )); ///} ///``` - final String height; + final String? height; ///The width of the legend. /// @@ -532,7 +532,7 @@ class Legend { /// )); ///} ///``` - final String width; + final String? width; ///Padding between the legend items. /// @@ -695,7 +695,7 @@ class Legend { /// )); ///} ///``` - final LegendItemBuilder legendItemBuilder; + final LegendItemBuilder? legendItemBuilder; ///Overflow legend items. /// @@ -750,32 +750,30 @@ class Legend { /// )); ///} ///``` - final ImageProvider image; + final ImageProvider? image; } /// Legend renderer class for mutable fields and methods class LegendRenderer { /// Creates an argument constructor for Leged renderer class - LegendRenderer(this._legend) { - _renderer = _LegendRenderer(); - } + LegendRenderer(this._legend); //ignore: unused_field - final Legend _legend; - _LegendRenderer _renderer; - LegendPosition _legendPosition; - LegendItemOrientation _orientation; + final Legend? _legend; + _LegendRenderer _renderer = _LegendRenderer(); + late LegendPosition _legendPosition; + late LegendItemOrientation _orientation; } class _MeasureWidgetContext { _MeasureWidgetContext( {this.context, this.key, this.widget, this.seriesIndex, this.pointIndex}); - BuildContext context; - int seriesIndex; - int pointIndex; - Key key; - Size size; - Widget widget; + BuildContext? context; + int? seriesIndex; + int? pointIndex; + Key? key; + Size? size; + Widget? widget; bool isRender = false; } @@ -787,7 +785,7 @@ class _MeasureWidgetContext { ///Provides Options to customize the [text], [textStyle] and [alignment] properties. class LegendTitle { /// Creating an argument constructor of LegendTitle class. - LegendTitle({this.text, TextStyle textStyle, ChartAlignment alignment}) + LegendTitle({this.text, TextStyle? textStyle, ChartAlignment? alignment}) : textStyle = _getTextStyle( textStyle: textStyle, fontSize: 12.0, @@ -812,7 +810,7 @@ class LegendTitle { /// )); ///} ///``` - final String text; + final String? text; /// Customize the legend title text. /// @@ -964,18 +962,35 @@ class EmptyPointSettings { } /// Maps the index value. -typedef ChartIndexedValueMapper = R Function(int index); +typedef ChartIndexedValueMapper = R? Function(int index); /// Maps the data from data source. -typedef ChartValueMapper = R Function(T datum, int index); +typedef ChartValueMapper = R? Function(T datum, int index); + +///Signature for the callback that returns the shader from the data source based on the index. +/// Can get the data, index, color and rect values. +/// +/// +///T - Data of the current data point +/// +/// +///index - Index of the current data point +/// +/// +///rect - Rect value of the current data point slice +/// +///color - Color of the current data point +typedef ChartShaderMapper = Shader Function( + T datum, int index, Color color, Rect rect); /// Returns the widget. typedef ChartWidgetBuilder = Widget Function(dynamic data, dynamic point, dynamic series, int pointIndex, int seriesIndex); +/// Returns the widget as a template of trackball typedef ChartTrackballBuilder = Widget Function( BuildContext context, TrackballDetails trackballDetails); -// Custom renderer for series +/// Custom renderer for series typedef ChartSeriesRendererFactory = ChartSeriesRenderer Function( ChartSeries series); diff --git a/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart b/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart index 5dbd72ec2..d31294f34 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart @@ -13,37 +13,38 @@ class TooltipArgs { this.pointIndex]); /// Get and set the tooltip text. - String text; + String? text; /// Get and set the header text of the tooltip. - String header; + String? header; /// Get and set the x location of the tooltip. - double locationX; + double? locationX; /// Get and set the y location of the tooltip. - double locationY; + double? locationY; /// Get the index of the current series. - final dynamic seriesIndex; + final dynamic? seriesIndex; /// Get the list of data points in the series. - final List dataPoints; + final List? dataPoints; /// Get the overall index value of the tooltip. - final num pointIndex; + final num? pointIndex; /// Get the viewport index value of the tooltip. - final num viewportPointIndex; + final num? viewportPointIndex; } -/// Holds the onActualRangeChanged event arguments. +/// Holds the [onActualRangeChanged] event arguments. /// -/// ActualRangeChangedArgs is the type argument for onActualRangeChanged event. Whenever the actual range is changed, the onActualRangeChanged event is +/// ActualRangeChangedArgs is the type argument for [onActualRangeChanged] event. Whenever the actual range is changed, the [onActualRangeChanged] event is /// triggered and provides options to set the visible minimum and maximum values. /// /// It has the public properties of axis name, axis type, actual minimum, and maximum, visible minimum and maximum and axis orientation. class ActualRangeChangedArgs { + //doubt /// Creating an argument constructor of ActualRangeChangedArgs class. ActualRangeChangedArgs( [this.axisName, @@ -54,10 +55,10 @@ class ActualRangeChangedArgs { this.orientation]); /// Get the name of the axis. - final String axisName; + final String? axisName; /// Get the axis type. - final ChartAxis axis; + final ChartAxis? axis; /// Get the actual minimum range for an axis. final dynamic actualMin; @@ -78,7 +79,7 @@ class ActualRangeChangedArgs { dynamic visibleInterval; /// Get the orientation for an axis. - final AxisOrientation orientation; + final AxisOrientation? orientation; } /// Holds the onAxisLabelRender event arguments. @@ -93,22 +94,19 @@ class AxisLabelRenderArgs { AxisLabelRenderArgs([this.value, this.axisName, this.orientation, this.axis]); /// Get and set the text value of the axis label. - String text; - - /// Trimmed text value of the axis label. - // String _trimmedText; + late String text; /// Get the value of the axis label. - final num value; + final num? value; /// Get the axis name. - final String axisName; + final String? axisName; /// Get the orientation for an axis. - final AxisOrientation orientation; + final AxisOrientation? orientation; /// Get the chart axis type and its properties. - final ChartAxis axis; + final ChartAxis? axis; /// Get and set the text style of an axis label. TextStyle textStyle = const TextStyle( @@ -118,6 +116,53 @@ class AxisLabelRenderArgs { fontSize: 12); } +/// Holds label text, axis name, orientation of the axis, trimmed text and text styles such as color, +/// font size, and font weight for label formatter evet +class AxisLabelRenderDetails { + /// Creating an argument constructor of AxisLabelRenderDetails class. + AxisLabelRenderDetails(this.value, this.text, this.actualText, this.textStyle, + this.axis, this.axisName, this.orientation); + + /// The rendered text value of the axis label. + final String text; + + /// Actual text value of the axis label. + final String actualText; + + /// Get the value of the axis label. + final num value; + + /// Get the axis name. + final String? axisName; + + /// Get the orientation for an axis. + final AxisOrientation orientation; + + /// Get the chart axis type and its properties. + final ChartAxis axis; + + /// Get the text style of an axis label. + final TextStyle textStyle; +} + +/// Holds the axis label text and style details. +class ChartAxisLabel { + /// Creating an argument constructor of ChartAxisLabel class. + ChartAxisLabel(this.text, TextStyle? textStyle) + : textStyle = textStyle ?? + const TextStyle( + fontFamily: 'Roboto', + fontStyle: FontStyle.normal, + fontWeight: FontWeight.normal, + fontSize: 12); + + ///Text which is to be rendered as an axis label. + final String text; + + ///Text style of the axis label. + final TextStyle textStyle; +} + /// Holds the onDataLabelRender event arguments. /// /// DataLabelRenderArgs is the type of argument for the onDataLabelRender event. Whenever the data label gets rendered, the onDataLabelRender event is @@ -134,7 +179,7 @@ class DataLabelRenderArgs { this.pointIndex]); /// Get and set the text value of a data label. - String text; + late String text; /// Get and set the style property of the data label text. TextStyle textStyle = const TextStyle( @@ -172,19 +217,19 @@ class DataLabelRenderArgs { final dynamic dataPoints; /// Get the overall index value of a data label. - final num pointIndex; + final int? pointIndex; /// Get and set the background color of a data label. - Color color; + Color? color; /// Get and set the horizontal/vertical position of the data label. /// /// The first argument sets the horizontal component to dx, while the second /// argument sets the vertical component to dy. - Offset offset; + Offset? offset; /// Get the viewport index value of a data label. - final num viewportPointIndex; + final int? viewportPointIndex; } /// Holds the onLegendItemRender event arguments. @@ -197,19 +242,19 @@ class LegendRenderArgs { LegendRenderArgs([this.seriesIndex, this.pointIndex]); /// Get and set the legend text. - String text; + String? text; /// Get and set the shape of a legend. - LegendIconType legendIconType; + LegendIconType? legendIconType; /// Get the current series index. - final int seriesIndex; + final int? seriesIndex; /// Get the current point index. - final int pointIndex; + final int? pointIndex; /// Get and set the color of the legend icon. - Color color; + Color? color; } ///Holds the arguments for the event onTrendlineRender. @@ -226,31 +271,31 @@ class TrendlineRenderArgs { this.data]); /// Get the intercept value. - final double intercept; + final double? intercept; /// Get the index of the trendline. - final int trendlineIndex; + final int? trendlineIndex; /// Get the index of the series. - final int seriesIndex; + final int? seriesIndex; /// Get the name of the trendline. - final String trendlineName; + final String? trendlineName; /// Get the name of the series. - final String seriesName; + final String? seriesName; /// Get and set the color of the trendline. - Color color; + late Color color; /// Get and set the opacity value. - double opacity; + late double opacity; /// Get and set the dash array value of a trendline. - List dashArray; + List? dashArray; /// Get the data points of the trendline. - final List> data; + final List>? data; } /// Holds arguments for onTrackballPositionChanging event. @@ -273,22 +318,22 @@ class CrosshairRenderArgs { CrosshairRenderArgs([this.axis, this.value, this.axisName, this.orientation]); /// Get the type of chart axis and its properties. - final ChartAxis axis; + final ChartAxis? axis; /// Get and set the crosshair tooltip text. - String text; + late String text; /// Get and set the color of the crosshair line. - Color lineColor; + late Color lineColor; /// Get the visible range value. - final dynamic value; + final dynamic? value; /// Get the name of the axis. - final String axisName; + final String? axisName; /// Get the axis orientation. - final AxisOrientation orientation; + final AxisOrientation? orientation; } /// Holds the chart TouchUp event arguments. @@ -298,7 +343,7 @@ class CrosshairRenderArgs { /// class ChartTouchInteractionArgs { /// Get the position of the touch interaction. - Offset position; + late Offset position; } /// Holds the zooming event arguments. @@ -313,24 +358,24 @@ class ZoomPanArgs { ZoomPanArgs([this.axis, this.previousZoomPosition, this.previousZoomFactor]); /// Get the chart axis types and properties. - final ChartAxis axis; + final ChartAxis? axis; /// Get and set the current zoom position. - double currentZoomPosition; + late double currentZoomPosition; /// Get and set the current zoom factor. - double currentZoomFactor; + late double currentZoomFactor; /// Get the previous zooom position. - final double previousZoomPosition; + final double? previousZoomPosition; /// Get the previous zoom factor. - final double previousZoomFactor; + final double? previousZoomFactor; } /// Holds the onPointTapped event arguments. /// -/// The PointTapArgs is the argument type of onPointTapped event, whenever the [onPointTapped] is triggered, gets the series index, current point index, and the data points. +/// The PointTapArgs is the argument type of onPointTapped event, whenever the `onPointTapped` is triggered, gets the series index, current point index, and the data points. /// class PointTapArgs { /// Creating an argument constructor of PointTapArgs class. @@ -341,16 +386,16 @@ class PointTapArgs { this.pointIndex]); /// Get the series index. - final int seriesIndex; + final int? seriesIndex; /// Get the overall index value. - final int pointIndex; + final int? pointIndex; /// Get the list of data points. - final List dataPoints; + final List? dataPoints; /// Get the viewport index value. - final num viewportPointIndex; + final num? viewportPointIndex; } /// Holds the onAxisLabelTapped event arguments. @@ -362,21 +407,21 @@ class AxisLabelTapArgs { AxisLabelTapArgs([this.axis, this.axisName]); /// Get the type of chart axis and its properties. - final ChartAxis axis; + final ChartAxis? axis; /// Get the text of the axis label at the tapped position. - String text; + late String text; /// Get the value holds the properties of the visible label. - num value; + late num value; /// Get the axis name. - final String axisName; + final String? axisName; } /// Holds the onLegendTapped event arguments. /// -/// When the legend is tapped, the onLegendTapped event is triggered and we can get the [series], [seriesIndex], and [pointIndex]. +/// When the legend is tapped, the onLegendTapped event is triggered and we can get the `series`, [seriesIndex], and [pointIndex]. /// class LegendTapArgs { /// Creating an argument constructor of LegendTapArgs class. @@ -408,10 +453,10 @@ class LegendTapArgs { final dynamic series; /// Get the current series index. - final int seriesIndex; + final int? seriesIndex; /// Get the current point index. - final int pointIndex; + final int? pointIndex; } /// Holds the onSelectionChanged event arguments. @@ -421,31 +466,31 @@ class LegendTapArgs { class SelectionArgs { /// Creating an argument constructor of SelectionArgs class. SelectionArgs( - [this.seriesRenderer, - this.seriesIndex, - this.viewportPointIndex, - this.pointIndex]); + {required this.seriesRenderer, + required this.seriesIndex, + required this.viewportPointIndex, + required this.pointIndex}); /// Get the selected series. final dynamic seriesRenderer; /// Get and set the color of the selected series or data points. - Color selectedColor; + Color? selectedColor; /// Get and set the color of unselected series or data points. - Color unselectedColor; + Color? unselectedColor; /// Get and set the border color of the selected series or data points. - Color selectedBorderColor; + Color? selectedBorderColor; /// Get and set the border width of the selected series or data points. - double selectedBorderWidth; + double? selectedBorderWidth; /// Get and set the border color of the unselected series or data points. - Color unselectedBorderColor; + Color? unselectedBorderColor; /// Get and set the border width of the unselected series or data points. - double unselectedBorderWidth; + double? unselectedBorderWidth; /// Get the series index. final int seriesIndex; @@ -468,28 +513,28 @@ class IndicatorRenderArgs { [this.indicator, this.index, this.seriesName, this.dataPoints]); /// Get the technical indicator information. - final TechnicalIndicators indicator; + final TechnicalIndicators? indicator; /// Get and set the indicator name. - String indicatorName; + late String indicatorName; /// Get the current index of the technical indicator. - final int index; + final int? index; /// Get and set the color of the signal line. - Color signalLineColor; + late Color signalLineColor; /// Get and set the width of the signal line. - double signalLineWidth; + late double signalLineWidth; /// Get and set the dash array size. - List lineDashArray; + late List lineDashArray; /// Get the series name. - final String seriesName; + final String? seriesName; /// Get the current data points. - final List dataPoints; + final List? dataPoints; } /// Holds the onMarkerRender event arguments. @@ -504,36 +549,36 @@ class MarkerRenderArgs { [this.viewportPointIndex, this.seriesIndex, this.pointIndex]); /// Get the overall index value of the marker. - final int pointIndex; + final int? pointIndex; /// Get the series index of the marker. - final int seriesIndex; + final int? seriesIndex; /// Get and set the shape of the marker. - DataMarkerType shape; + late DataMarkerType shape; /// Get and set the width of the marker. - double markerWidth; + late double markerWidth; /// Get and set the height of the marker. - double markerHeight; + late double markerHeight; /// Get and set the color of the marker. - Color color; + Color? color; /// Get and set the border color of the marker. - Color borderColor; + Color? borderColor; /// Get and set the border width of marker. - double borderWidth; + late double borderWidth; /// Get the viewport index value of the marker. - final num viewportPointIndex; + final num? viewportPointIndex; } ///Holds the onDataLabelTapped callback arguments. /// -///Whenever the data label is tapped, [onDataLabelTapped] callback will be called. Provides options to get the position of the data label, +///Whenever the data label is tapped, `onDataLabelTapped` callback will be called. Provides options to get the position of the data label, /// series index, point index and its text. class DataLabelTapDetails { @@ -542,7 +587,7 @@ class DataLabelTapDetails { this.dataLabelSettings, this.pointIndex); /// Get the position of the tapped data label in logical pixels. - Offset position; + late Offset position; /// Get the series index of the tapped data label. final int seriesIndex; @@ -559,3 +604,21 @@ class DataLabelTapDetails { /// Get the viewport index value of the tapped data label. final int viewportPointIndex; } + +///Holds the onCreateShader callback arguments. +/// +///This is the argument type of the onCreateShader callback. The onCreateShader callback is called once while rendering +///the data points and legend. This provides options to get the outer rect, inner rect, and render type (either series or legend). +class ChartShaderDetails { + /// Creating an argument constructor of ChartShaderDetails class. + ChartShaderDetails(this.outerRect, this.innerRect, this.renderType); + + ///Holds the pie, doughnut and radial bar chart's outer rect value. + final Rect outerRect; + + ///Conveys whether the current rendering element is 'series' or 'legend'. + final String renderType; + + ///Holds the doughnut and radial bar chart's inner rect value. + final Rect? innerRect; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/handcursor/mobile.dart b/packages/syncfusion_flutter_charts/lib/src/common/handcursor/mobile.dart index 89e72e55e..a7f7d3816 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/handcursor/mobile.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/handcursor/mobile.dart @@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart'; class HandCursor extends MouseRegion { /// Creating an argument constructor of class. //ignore: prefer_const_constructors_in_immutables - HandCursor({@required Widget child}) : super(child: child); + HandCursor({required Widget child}) : super(child: child); } /// changes the style of the cursor while entering|leaving the hover zone diff --git a/packages/syncfusion_flutter_charts/lib/src/common/handcursor/web.dart b/packages/syncfusion_flutter_charts/lib/src/common/handcursor/web.dart index caaafb3ef..a5bc14684 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/handcursor/web.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/handcursor/web.dart @@ -7,30 +7,30 @@ import 'package:flutter/widgets.dart'; class HandCursor extends MouseRegion { /// Creating an argument constructor of HandCursor class. //ignore: prefer_const_constructors_in_immutables - HandCursor({@required Widget child}) + HandCursor({required Widget child}) : super( child: child, onHover: _mouseHover, onExit: _mouseExit, ); - static final html.Element _appContainer = + static final html.Element? _appContainer = html.window.document.getElementById('app-container'); static void _mouseHover(PointerEvent event) { if (_appContainer != null) { - _appContainer.style.cursor = 'pointer'; + _appContainer!.style.cursor = 'pointer'; } } static void _mouseExit(PointerEvent event) { if (_appContainer != null) { - _appContainer.style.cursor = 'default'; + _appContainer!.style.cursor = 'default'; } } } /// sets the cursor style when leaving the hover zone void changeCursorStyleOnNavigation() { - HandCursor._appContainer.style.cursor = 'default'; + HandCursor._appContainer!.style.cursor = 'default'; } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart b/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart index d18d9c307..7f288c2db 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart @@ -4,15 +4,15 @@ class _ChartLegend { _ChartLegend(this._chartState); dynamic get chart => _chartState._chart; final dynamic _chartState; - Legend legend; - List<_LegendRenderContext> legendCollections; - int rowCount; - int columnCount; + Legend? legend; + List<_LegendRenderContext>? legendCollections; + late int rowCount; + late int columnCount; Size legendSize = const Size(0, 0); Size chartSize = const Size(0, 0); bool shouldRenderLegend = false; - ValueNotifier legendRepaintNotifier; - bool isNeedScrollable; + late ValueNotifier legendRepaintNotifier; + late bool isNeedScrollable; /// To calculate legend bounds void _calculateLegendBounds(Size size) { @@ -20,23 +20,23 @@ class _ChartLegend { final LegendRenderer legendRenderer = _chartState._legendRenderer; shouldRenderLegend = false; assert( - legend.width != null - ? !legend.width.contains(RegExp(r'[a-z]')) && - !legend.width.contains(RegExp(r'[A-Z]')) + legend != null && legend!.width != null + ? !legend!.width!.contains(RegExp(r'[a-z]')) && + !legend!.width!.contains(RegExp(r'[A-Z]')) : true, 'Legend width must be number or percentage value, it should not contain any alphabets in the string.'); assert( - legend.height != null - ? !legend.height.contains(RegExp(r'[a-z]')) && - !legend.height.contains(RegExp(r'[A-Z]')) + legend != null && legend!.height != null + ? !legend!.height!.contains(RegExp(r'[a-z]')) && + !legend!.height!.contains(RegExp(r'[A-Z]')) : true, 'Legend height must be number or percentage value, it should not contain any alphabets in the string.'); - if (legend.isVisible) { + if (legend != null && legend!.isVisible!) { legendCollections = <_LegendRenderContext>[]; _calculateSeriesLegends(); - assert(legend.itemPadding != null ? legend.itemPadding >= 0 : true, + assert(legend!.itemPadding != null ? legend!.itemPadding >= 0 : true, 'The padding between the legend and chart area should not be less than 0.'); - if (legendCollections.isNotEmpty || + if (legendCollections!.isNotEmpty || _chartState._legendWidgetContext.isNotEmpty) { num legendHeight = 0, legendWidth = 0, @@ -48,94 +48,90 @@ class _ChartLegend { maxLegendWidth = 0, maxLegendHeight = 0, currentWidth = 0, - currentHeight = 0, - maxRenderWidth, - maxRenderHeight; + currentHeight = 0; + num? maxRenderWidth, maxRenderHeight; Size titleSize; const num titleSpace = 10; - final num padding = legend.itemPadding; + final num padding = legend!.itemPadding; _chartState._chartLegend.isNeedScrollable = false; final bool isBottomOrTop = legendRenderer._legendPosition == LegendPosition.bottom || legendRenderer._legendPosition == LegendPosition.top; legendRenderer._orientation = - (legend.orientation == LegendItemOrientation.auto) + (legend!.orientation == LegendItemOrientation.auto) ? (isBottomOrTop ? LegendItemOrientation.horizontal : LegendItemOrientation.vertical) - : legend.orientation; + : legend!.orientation; - maxRenderHeight = legend.height != null - ? _percentageToValue(legend.height, size.height) + maxRenderHeight = legend!.height != null + ? _percentageToValue(legend!.height, size.height) : isBottomOrTop ? _percentageToValue('30%', size.height) : size.height; - maxRenderWidth = legend.width != null - ? _percentageToValue(legend.width, size.width) + maxRenderWidth = legend!.width != null + ? _percentageToValue(legend!.width, size.width) : isBottomOrTop ? size.width : _percentageToValue('30%', size.width); - if (legend.title.text != null && legend.title.text.isNotEmpty) { - titleSize = _measureText( - legend.title.text, - legend.title.textStyle ?? - TextStyle(color: _chartState._chartTheme.legendTitleColor)); + if (legend!.title.text != null && legend!.title.text!.isNotEmpty) { + titleSize = measureText(legend!.title.text!, legend!.title.textStyle); titleHeight = titleSize.height + titleSpace; } - final bool isTemplate = legend.legendItemBuilder != null; + final bool isTemplate = legend!.legendItemBuilder != null; final int length = isTemplate ? _chartState._legendWidgetContext.length - : legendCollections.length; - _MeasureWidgetContext legendContext; - _LegendRenderContext legendRenderContext; + : legendCollections!.length; + late _MeasureWidgetContext legendContext; + late _LegendRenderContext legendRenderContext; String legendText; Size textSize; - assert(legend.iconWidth != null ? legend.iconWidth >= 0 : true, + assert(legend!.iconWidth != null ? legend!.iconWidth >= 0 : true, 'The icon width of legend should not be less than 0.'); - assert(legend.iconHeight != null ? legend.iconHeight >= 0 : true, + assert(legend!.iconHeight != null ? legend!.iconHeight >= 0 : true, 'The icon height of legend should not be less than 0.'); - assert(legend.padding != null ? legend.padding >= 0 : true, + assert(legend!.padding != null ? legend!.padding >= 0 : true, 'The padding between legend text and legend icon should not be less than 0.'); for (int i = 0; i < length; i++) { if (isTemplate) { legendContext = _chartState._legendWidgetContext[i]; - currentWidth = legendContext.size.width + padding; - currentHeight = legendContext.size.height + padding; + currentWidth = legendContext.size!.width + padding; + currentHeight = legendContext.size!.height + padding; } else { - legendRenderContext = legendCollections[i]; + legendRenderContext = legendCollections![i]; legendText = legendRenderContext.text; - textSize = _measureText(legendText, - legend.textStyle ?? const TextStyle(color: Colors.black)); + textSize = measureText(legendText, legend!.textStyle); legendRenderContext.textSize = textSize; textHeight = textSize.height; textWidth = textSize.width; maxTextHeight = max(textHeight, maxTextHeight); maxTextWidth = max(textWidth, maxTextWidth); currentWidth = - padding + legend.iconWidth + legend.padding + textWidth; - currentHeight = padding + max(maxTextHeight, legend.iconHeight); - legendRenderContext.size = Size(currentWidth, currentHeight); + padding + legend!.iconWidth + legend!.padding + textWidth; + currentHeight = padding + max(maxTextHeight, legend!.iconHeight); + legendRenderContext.size = + Size(currentWidth.toDouble(), currentHeight.toDouble()); } if (i == 0) { - maxRenderWidth = legend.width == null && !isBottomOrTop - ? max(maxRenderWidth, currentWidth) + maxRenderWidth = legend!.width == null && !isBottomOrTop + ? max(maxRenderWidth!, currentWidth) : maxRenderWidth; maxRenderHeight = (titleHeight - - (legend.height == null && isBottomOrTop - ? max(maxRenderHeight, currentHeight) - : maxRenderHeight)) + (legend!.height == null && isBottomOrTop + ? max(maxRenderHeight!, currentHeight) + : maxRenderHeight!)) .abs(); } shouldRenderLegend = true; bool needRender = false; if (legendRenderer._orientation == LegendItemOrientation.horizontal) { - if (legend.overflowMode == LegendItemOverflowMode.wrap) { - if ((legendWidth + currentWidth) > maxRenderWidth) { + if (legend!.overflowMode == LegendItemOverflowMode.wrap) { + if ((legendWidth + currentWidth) > maxRenderWidth!) { legendWidth = currentWidth; - if (legendHeight + currentHeight > maxRenderHeight) { + if (legendHeight + currentHeight > maxRenderHeight!) { _chartState._chartLegend.isNeedScrollable = true; } else { legendHeight = legendHeight + currentHeight; @@ -145,11 +141,11 @@ class _ChartLegend { legendWidth += currentWidth; legendHeight = max(legendHeight, currentHeight); } - } else if (legend.overflowMode == LegendItemOverflowMode.scroll || - legend.overflowMode == LegendItemOverflowMode.none) { - if (maxLegendWidth + currentWidth <= maxRenderWidth) { + } else if (legend!.overflowMode == LegendItemOverflowMode.scroll || + legend!.overflowMode == LegendItemOverflowMode.none) { + if (maxLegendWidth + currentWidth <= maxRenderWidth!) { legendWidth += currentWidth; - legendHeight = currentHeight > maxRenderHeight + legendHeight = currentHeight > maxRenderHeight! ? maxRenderHeight : max(legendHeight, currentHeight); needRender = true; @@ -158,10 +154,10 @@ class _ChartLegend { } } } else { - if (legend.overflowMode == LegendItemOverflowMode.wrap) { - if ((legendHeight + currentHeight) > maxRenderHeight) { + if (legend!.overflowMode == LegendItemOverflowMode.wrap) { + if ((legendHeight + currentHeight) > maxRenderHeight!) { legendHeight = currentHeight; - if (legendWidth + currentWidth > maxRenderWidth) { + if (legendWidth + currentWidth > maxRenderWidth!) { _chartState._chartLegend.isNeedScrollable = true; } else { legendWidth = legendWidth + currentWidth; @@ -170,11 +166,11 @@ class _ChartLegend { legendHeight += currentHeight; legendWidth = max(legendWidth, currentWidth); } - } else if (legend.overflowMode == LegendItemOverflowMode.scroll || - legend.overflowMode == LegendItemOverflowMode.none) { - if (maxLegendHeight + currentHeight <= maxRenderHeight) { + } else if (legend!.overflowMode == LegendItemOverflowMode.scroll || + legend!.overflowMode == LegendItemOverflowMode.none) { + if (maxLegendHeight + currentHeight <= maxRenderHeight!) { legendHeight += currentHeight; - legendWidth = currentWidth > maxRenderWidth + legendWidth = currentWidth > maxRenderWidth! ? maxRenderWidth : max(legendWidth, currentWidth); needRender = true; @@ -191,8 +187,8 @@ class _ChartLegend { maxLegendWidth = max(maxLegendWidth, legendWidth); maxLegendHeight = max(maxLegendHeight, legendHeight); } - legendSize = Size( - maxLegendWidth + padding, maxLegendHeight + titleHeight.toDouble()); + legendSize = Size((maxLegendWidth + padding).toDouble(), + maxLegendHeight + titleHeight.toDouble()); } } } @@ -200,14 +196,14 @@ class _ChartLegend { /// To calculate legends in chart void _calculateLegends( SfCartesianChart chart, int index, CartesianSeriesRenderer seriesRenderer, - [Trendline trendline, int trendlineIndex]) { - LegendRenderArgs legendEventArgs; + [Trendline? trendline, int? trendlineIndex]) { + LegendRenderArgs? legendEventArgs; bool isTrendlineadded = false; - TrendlineRenderer trendlineRenderer; + TrendlineRenderer? trendlineRenderer; final CartesianSeries series = seriesRenderer._series; if (trendline != null) { isTrendlineadded = true; - trendlineRenderer = seriesRenderer._trendlineRenderer[trendlineIndex]; + trendlineRenderer = seriesRenderer._trendlineRenderer[trendlineIndex!]; } seriesRenderer._seriesName = seriesRenderer._seriesName ?? 'series $index'; if (series.isVisibleInLegend && @@ -216,13 +212,14 @@ class _ChartLegend { legendEventArgs = LegendRenderArgs(index); legendEventArgs.text = series.legendItemText ?? (isTrendlineadded - ? trendlineRenderer._name - : seriesRenderer._seriesName); - legendEventArgs.legendIconType = - isTrendlineadded ? trendline.legendIconType : series.legendIconType; + ? trendlineRenderer!._name! + : seriesRenderer._seriesName!); + legendEventArgs.legendIconType = isTrendlineadded + ? trendline!.legendIconType + : series.legendIconType; legendEventArgs.color = - isTrendlineadded ? trendline.color : series.color; - chart.onLegendItemRender(legendEventArgs); + isTrendlineadded ? trendline!.color : series.color; + chart.onLegendItemRender!(legendEventArgs); } final _LegendRenderContext legendRenderContext = _LegendRenderContext( seriesRenderer: seriesRenderer, @@ -230,25 +227,26 @@ class _ChartLegend { seriesIndex: index, trendlineIndex: isTrendlineadded ? trendlineIndex : null, isSelect: _chartState._isTrendlineToggled - ? (isTrendlineadded ? !trendlineRenderer._visible : true) + ? (isTrendlineadded ? !trendlineRenderer!._visible : true) : !series.isVisible, text: legendEventArgs?.text ?? series.legendItemText ?? (isTrendlineadded - ? trendlineRenderer._name - : seriesRenderer._seriesName), + ? trendlineRenderer!._name! + : seriesRenderer._seriesName!), iconColor: legendEventArgs?.color ?? - (isTrendlineadded ? trendline.color : series.color), + (isTrendlineadded ? trendline!.color : series.color), + isTrendline: isTrendlineadded, iconType: legendEventArgs?.legendIconType ?? (isTrendlineadded - ? trendline.legendIconType + ? trendline!.legendIconType : series.legendIconType)); - legendCollections.add(legendRenderContext); - if (!seriesRenderer._visible && + legendCollections!.add(legendRenderContext); + if (!seriesRenderer._visible! && series.isVisibleInLegend && (_chartState._widgetNeedUpdate || _chartState._initialRender) && (seriesRenderer._oldSeries == null || - (!series.isVisible && seriesRenderer._oldSeries.isVisible))) { + (!series.isVisible && seriesRenderer._oldSeries!.isVisible))) { legendRenderContext.isSelect = true; if (!_chartState._legendToggleStates.contains(legendRenderContext)) { _chartState._legendToggleStates.add(legendRenderContext); @@ -257,7 +255,7 @@ class _ChartLegend { (seriesRenderer._oldSeries != null && (series.isVisible && !_chartState._legendToggling && - seriesRenderer._visible))) { + seriesRenderer._visible!))) { final List visibleSeriesRenderers = _chartState._chartSeries.visibleSeriesRenderers; final String legendItemText = @@ -281,7 +279,7 @@ class _ChartLegend { /// To calculate series legends void _calculateSeriesLegends() { - LegendRenderArgs legendEventArgs; + LegendRenderArgs? legendEventArgs; if (chart.legend.legendItemBuilder == null) { if (chart is SfCartesianChart) { for (int i = 0; @@ -294,13 +292,13 @@ class _ChartLegend { } if (seriesRenderer is CartesianSeriesRenderer) { final CartesianSeriesRenderer xYSeriesRenderer = seriesRenderer; - if (xYSeriesRenderer?._series != null && + if (xYSeriesRenderer._series != null && xYSeriesRenderer._series.trendlines != null) { for (int j = 0; - j < xYSeriesRenderer._series.trendlines.length; + j < xYSeriesRenderer._series.trendlines!.length; j++) { final Trendline trendline = - xYSeriesRenderer._series.trendlines[j]; + xYSeriesRenderer._series.trendlines![j]; if (trendline.isVisibleInLegend) { _chartState._chartLegend._calculateLegends( chart, i, xYSeriesRenderer, trendline, j); @@ -326,7 +324,7 @@ class _ChartLegend { chart.onLegendItemRender(legendEventArgs); } - legendCollections.add(_LegendRenderContext( + legendCollections!.add(_LegendRenderContext( seriesRenderer: seriesRenderer, seriesIndex: j, isSelect: false, @@ -342,33 +340,33 @@ class _ChartLegend { /// To calculate indicator legends void _calculateIndicatorLegends() { - LegendRenderArgs legendEventArgs; + LegendRenderArgs? legendEventArgs; final List textCollection = []; - TechnicalIndicatorsRenderer technicalIndicatorsRenderer; + TechnicalIndicatorsRenderer? technicalIndicatorsRenderer; for (int i = 0; i < chart.indicators.length; i++) { final TechnicalIndicators indicator = chart.indicators[i]; technicalIndicatorsRenderer = _chartState._technicalIndicatorRenderer[i]; _chartState._chartSeries ?._setIndicatorType(indicator, technicalIndicatorsRenderer); - textCollection.add(technicalIndicatorsRenderer._indicatorType); + textCollection.add(technicalIndicatorsRenderer!._indicatorType); } //ignore: prefer_collection_literals final Map _map = Map(); //ignore: avoid_function_literals_in_foreach_calls textCollection.forEach((dynamic str) => - _map[str] = !_map.containsKey(str) ? (1) : (_map[str] + 1)); + _map[str] = !_map.containsKey(str) ? (1) : (_map[str]! + 1)); final List indicatorTextCollection = []; for (int i = 0; i < chart.indicators.length; i++) { final TechnicalIndicators indicator = chart.indicators[i]; final int count = indicatorTextCollection - .contains(technicalIndicatorsRenderer._indicatorType) + .contains(technicalIndicatorsRenderer?._indicatorType) ? _chartState._chartSeries?._getIndicatorId(indicatorTextCollection, - technicalIndicatorsRenderer._indicatorType) + technicalIndicatorsRenderer?._indicatorType) : 0; - indicatorTextCollection.add(technicalIndicatorsRenderer._indicatorType); + indicatorTextCollection.add(technicalIndicatorsRenderer!._indicatorType); technicalIndicatorsRenderer._name = indicator.name ?? (technicalIndicatorsRenderer._indicatorType + (_map[technicalIndicatorsRenderer._indicatorType] == 1 @@ -395,7 +393,7 @@ class _ChartLegend { iconColor: legendEventArgs?.color ?? indicator.signalLineColor, iconType: legendEventArgs?.legendIconType ?? indicator.legendIconType); - legendCollections.add(legendRenderContext); + legendCollections!.add(legendRenderContext); if (!indicator.isVisible && indicator.isVisibleInLegend && _chartState._initialRender) { @@ -430,8 +428,8 @@ class _LegendContainer extends StatelessWidget { : true) { legendWidgets.add(_RenderLegend( index: i, - template: legendRenderContext.widget, - size: legendRenderContext.size, + template: legendRenderContext.widget!, + size: legendRenderContext.size!, chartState: chartState)); } } @@ -443,22 +441,18 @@ class _LegendContainer extends StatelessWidget { : true) { legendWidgets.add(_RenderLegend( index: i, - size: legendRenderContext.size, + size: legendRenderContext.size!, chartState: chartState)); } } } final bool needLegendTitle = - legend.title.text != null && legend.title.text.isNotEmpty; + legend.title.text != null && legend.title.text!.isNotEmpty; if (needLegendTitle) { - titleHeight = _measureText( - legend.title.text, - legend.title.textStyle ?? - TextStyle(color: chartState._chartTheme.legendTitleColor)) - .height + - 10; + titleHeight = + measureText(legend.title.text!, legend.title.textStyle).height + 10; } return _getWidget(legendWidgets, needLegendTitle, titleHeight); } @@ -525,20 +519,20 @@ class _LegendContainer extends StatelessWidget { final double fontSize = chart.legend.title.textStyle.fontSize; final String fontFamily = chart.legend.title.textStyle.fontFamily; final FontStyle fontStyle = chart.legend.title.textStyle.fontStyle; - final FontWeight fontWeight = chart.legend.title.textStyle.fontWeight; + final FontWeight? fontWeight = chart.legend.title.textStyle.fontWeight; widget = Container( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Container( - height: titleHeight, + height: titleHeight.toDouble(), alignment: titleAlign == ChartAlignment.center ? Alignment.center : titleAlign == ChartAlignment.near ? Alignment.centerLeft : Alignment.centerRight, child: Container( - child: Text(legend.title.text, + child: Text(legend.title.text!, overflow: TextOverflow.ellipsis, style: TextStyle( color: color, diff --git a/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart b/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart index 38df64c09..f6307faea 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart @@ -55,11 +55,11 @@ class _LegendRenderer with _CustomizeLegend { final dynamic chart = chartState._chart; final String legendText = legendItem.text; final List palette = chartState._chart.palette; - TrendlineRenderer trendlineRenderer; + TrendlineRenderer? trendlineRenderer; Color color = legendItem.iconColor ?? palette[index % palette.length]; color = legendRenderer._renderer.getLegendIconColor(index, legendItem, color); - final Size textSize = legendItem.textSize; + final Size textSize = legendItem.textSize!; final Offset iconOffset = Offset(legend.itemPadding + legend.iconWidth / 2, size.height / 2); if (legendItem.trendline != null) { @@ -68,9 +68,9 @@ class _LegendRenderer with _CustomizeLegend { } legendItem.isSelect = chart is SfCartesianChart ? ((legendItem.trendline != null) - ? !trendlineRenderer._visible + ? !trendlineRenderer!._visible : legendItem.seriesRenderer is TechnicalIndicators - ? !legendItem.indicatorRenderer._visible + ? !legendItem.indicatorRenderer!._visible! : !legendItem.seriesRenderer._visible) : !legendItem.point.isVisible; final TextStyle textStyle = legendItem.isSelect @@ -103,7 +103,7 @@ class _LegendRenderer with _CustomizeLegend { /// To get legend icon shape LegendIconType _getIconType(DataMarkerType shape) { - LegendIconType iconType; + LegendIconType? iconType; switch (shape) { case DataMarkerType.circle: iconType = LegendIconType.circle; @@ -135,7 +135,7 @@ class _LegendRenderer with _CustomizeLegend { case DataMarkerType.none: break; } - return iconType; + return iconType!; } /// To draw legend icon shapes @@ -155,7 +155,7 @@ class _LegendRenderer with _CustomizeLegend { PaintingStyle style = PaintingStyle.fill; final String seriesType = legendRenderContext.seriesRenderer is TechnicalIndicators - ? legendRenderContext.indicatorRenderer._seriesType + ? legendRenderContext.indicatorRenderer!._seriesType : legendRenderContext.seriesRenderer._seriesType; iconType = _getLegendIconType(iconType, legendRenderContext); final double width = (legendRenderContext.series.legendIconType == @@ -172,6 +172,15 @@ class _LegendRenderer with _CustomizeLegend { seriesType.contains('stackedline'))) ? size.height / 1.5 : size.height; + Shader? _legendShader; + final Rect _pathRect = Rect.fromLTWH( + location.dx - width / 2, location.dy - height / 2, width, height); + if (legendRenderContext.series is CircularSeries && + chartState._chart.onCreateShader != null) { + ChartShaderDetails chartShaderDetails; + chartShaderDetails = ChartShaderDetails(_pathRect, null, 'legend'); + _legendShader = chartState._chart.onCreateShader(chartShaderDetails); + } style = _getPathAndStyle(iconType, style, path, location, width, height, legendRenderContext.seriesRenderer, chartState, canvas); assert(legend.iconBorderWidth != null ? legend.iconBorderWidth >= 0 : true, @@ -189,7 +198,7 @@ class _LegendRenderer with _CustomizeLegend { seriesType == 'candle' || seriesType == 'boxandwhisker' || (legendRenderContext.series is TechnicalIndicators && - legendRenderContext.indicatorRenderer._isIndicator)) + legendRenderContext.indicatorRenderer!._isIndicator)) ? 2 : 1 ..style = (iconType == LegendIconType.seriesType) @@ -199,14 +208,34 @@ class _LegendRenderer with _CustomizeLegend { ? PaintingStyle.stroke : PaintingStyle.fill); final String _seriesType = seriesType; + if (legendRenderContext.series is CartesianSeries && + legendRenderContext.series.gradient != null && + !legendRenderContext.isTrendline! && + (iconType == LegendIconType.horizontalLine || + iconType == LegendIconType.verticalLine) && + !legendRenderContext.isSelect) { + fillPaint.color = legendRenderContext.series.gradient.colors.first; + } if ((actualIconType == LegendIconType.seriesType && (_seriesType == 'line' || _seriesType == 'fastline' || _seriesType.contains('stackedline'))) || (iconType == LegendIconType.seriesType && (_seriesType == 'radialbar' || _seriesType == 'doughnut'))) { - _drawIcon(iconType, index, _seriesType, legendRenderContext, chartState, - width, height, location, size, canvas, fillPaint, path); + _drawIcon( + iconType, + index, + _seriesType, + legendRenderContext, + chartState, + width, + height, + location, + size, + canvas, + fillPaint, + path, + legendRenderContext.point?.shader ?? _legendShader); } else { (legendRenderContext.series.legendIconType == LegendIconType.seriesType && @@ -216,19 +245,29 @@ class _LegendRenderer with _CustomizeLegend { !kIsWeb ? _dashPath(path, dashArray: - _CircularIntervalList([3, 2])) + _CircularIntervalList([3, 2]))! : path, fillPaint) : canvas.drawPath( path, (legendRenderContext.series is CartesianSeries && !legendRenderContext.isSelect && - legendRenderContext.series.gradient != null) + legendRenderContext.series.gradient != null && + !legendRenderContext.isTrendline! && + !(iconType == LegendIconType.horizontalLine || + iconType == LegendIconType.verticalLine)) ? _getLinearGradientPaint( legendRenderContext.series.gradient, path.getBounds(), chartState._requireInvertedAxis) - : fillPaint); + : (legendRenderContext.series is CircularSeries && + !legendRenderContext.isSelect && + legendRenderContext.point?.center != null && + (legendRenderContext.point?.shader != null || + _legendShader != null) + ? _getShaderPaint( + legendRenderContext.point?.shader ?? _legendShader) + : fillPaint)); } } final double iconBorderWidth = legendRenderer._renderer @@ -252,7 +291,7 @@ class _LegendRenderer with _CustomizeLegend { LegendIconType _getLegendIconType( LegendIconType iconType, _LegendRenderContext legendRenderContext) { if (legendRenderContext.series is TechnicalIndicators && - legendRenderContext.indicatorRenderer._isIndicator) { + legendRenderContext.indicatorRenderer!._isIndicator) { return legendRenderContext.series.legendIconType == LegendIconType.seriesType ? LegendIconType.horizontalLine @@ -294,11 +333,11 @@ class _LegendRenderer with _CustomizeLegend { path, x, y, width, height, seriesRenderer._seriesType); break; case LegendIconType.circle: - _ChartShapeUtils._drawCircle(path, x, y, width, height); + ShapeMaker.drawCircle(path, x, y, width, height); break; case LegendIconType.rectangle: - _ChartShapeUtils._drawRectangle(path, x, y, width, height); + ShapeMaker.drawRectangle(path, x, y, width, height); break; case LegendIconType.image: { @@ -323,27 +362,27 @@ class _LegendRenderer with _CustomizeLegend { break; } case LegendIconType.pentagon: - _ChartShapeUtils._drawPentagon(path, x, y, width, height); + ShapeMaker.drawPentagon(path, x, y, width, height); break; case LegendIconType.verticalLine: - _ChartShapeUtils._drawVerticalLine(path, x, y, width, height); + ShapeMaker.drawVerticalLine(path, x, y, width, height); break; case LegendIconType.invertedTriangle: - _ChartShapeUtils._drawInvertedTriangle(path, x, y, width, height); + ShapeMaker.drawInvertedTriangle(path, x, y, width, height); break; case LegendIconType.horizontalLine: - _ChartShapeUtils._drawHorizontalLine(path, x, y, width, height); + ShapeMaker.drawHorizontalLine(path, x, y, width, height); break; case LegendIconType.diamond: - _ChartShapeUtils._drawDiamond(path, x, y, width, height); + ShapeMaker.drawDiamond(path, x, y, width, height); break; case LegendIconType.triangle: - _ChartShapeUtils._drawTriangle(path, x, y, width, height); + ShapeMaker.drawTriangle(path, x, y, width, height); break; } return style; @@ -362,7 +401,8 @@ class _LegendRenderer with _CustomizeLegend { Size size, Canvas canvas, Paint fillPaint, - Path path) { + Path path, + Shader? shader) { final dynamic chart = chartState._chart; if (seriesType.contains('line')) { if (iconType != LegendIconType.seriesType) { @@ -380,7 +420,7 @@ class _LegendRenderer with _CustomizeLegend { ? canvas.drawPath( !kIsWeb ? _dashPath(linePath, - dashArray: _CircularIntervalList([3, 2])) + dashArray: _CircularIntervalList([3, 2]))! : linePath, paint) : canvas.drawPath(linePath, paint); @@ -388,8 +428,10 @@ class _LegendRenderer with _CustomizeLegend { final num radius = (width + height) / 2; _drawPath( canvas, - _StyleOptions(Colors.grey[100], fillPaint.strokeWidth, - Colors.grey[300].withOpacity(0.5)), + _StyleOptions( + fill: Colors.grey[100]!, + strokeWidth: fillPaint.strokeWidth, + strokeColor: Colors.grey[300]!.withOpacity(0.5)), _getArcPath( (radius / 2) - 2, radius / 2, @@ -409,7 +451,9 @@ class _LegendRenderer with _CustomizeLegend { _drawPath( canvas, _StyleOptions( - fillPaint.color, fillPaint.strokeWidth, Colors.transparent), + fill: fillPaint.color, + strokeWidth: fillPaint.strokeWidth, + strokeColor: Colors.transparent), _getArcPath( (radius / 2) - 2, radius / 2, @@ -418,19 +462,27 @@ class _LegendRenderer with _CustomizeLegend { pointEndAngle, degree, chart, - true)); + true), + null, + !legendRenderContext.isSelect ? shader ?? null : null); } else { final num radius = (width + height) / 2; _drawPath( canvas, - _StyleOptions(fillPaint.color, fillPaint.strokeWidth, - Colors.grey[300].withOpacity(0.5)), + _StyleOptions( + fill: fillPaint.color, + strokeWidth: fillPaint.strokeWidth, + strokeColor: Colors.grey[300]!.withOpacity(0.5)), _getArcPath(radius / 4, radius / 2, Offset(location.dx, location.dy), - 0, 270, 270, chart, true)); + 0, 270, 270, chart, true), + null, + !legendRenderContext.isSelect ? shader ?? null : null); _drawPath( canvas, - _StyleOptions(fillPaint.color, fillPaint.strokeWidth, - Colors.grey[300].withOpacity(0.5)), + _StyleOptions( + fill: fillPaint.color, + strokeWidth: fillPaint.strokeWidth, + strokeColor: Colors.grey[300]!.withOpacity(0.5)), _getArcPath( radius / 4, radius / 2, @@ -439,13 +491,16 @@ class _LegendRenderer with _CustomizeLegend { -85, -85, chart, - true)); + true), + null, + !legendRenderContext.isSelect ? shader ?? null : null); } } } class _RenderLegend extends StatelessWidget { - _RenderLegend({this.index, this.size, this.chartState, this.template}) + _RenderLegend( + {required this.index, required this.size, this.chartState, this.template}) : chart = chartState._chart; final int index; @@ -456,11 +511,11 @@ class _RenderLegend extends StatelessWidget { final dynamic chart; - final Widget template; + final Widget? template; @override Widget build(BuildContext context) { - bool isSelect; + bool? isSelect; if (chart.legend.legendItemBuilder != null) { final _MeasureWidgetContext _measureWidgetContext = chartState._legendWidgetContext[index]; @@ -493,7 +548,7 @@ class _RenderLegend extends StatelessWidget { } }, child: template != null - ? !isSelect + ? !isSelect! ? Opacity(child: template, opacity: 0.5) : template : CustomPaint( @@ -543,7 +598,7 @@ class _RenderLegend extends StatelessWidget { legendTapArgs = LegendTapArgs( chartState._chartSeries .visibleSeriesRenderers[_measureWidgetContext.seriesIndex], - _measureWidgetContext.seriesIndex, + _measureWidgetContext.seriesIndex!, 0); } else { _legendRenderContext = chartState._chartLegend.legendCollections[index]; @@ -602,10 +657,10 @@ class _ChartLegendStylePainter extends CustomPainter { class _ChartLegendPainter extends CustomPainter { _ChartLegendPainter( - {this.chartState, - this.legendIndex, - this.isSelect, - ValueNotifier notifier}) + {required this.chartState, + required this.legendIndex, + required this.isSelect, + required ValueNotifier notifier}) : chart = chartState._chart, super(repaint: notifier); @@ -634,16 +689,17 @@ class _ChartLegendPainter extends CustomPainter { class _LegendRenderContext { _LegendRenderContext( {this.size, - this.text, + required this.text, this.textSize, - this.iconColor, - this.iconType, + required this.iconColor, + required this.iconType, this.point, - this.isSelect, + required this.isSelect, this.trendline, - this.seriesIndex, + required this.seriesIndex, this.trendlineIndex, this.seriesRenderer, + this.isTrendline, this.indicatorRenderer}) : series = seriesRenderer is TechnicalIndicators ? seriesRenderer @@ -651,31 +707,33 @@ class _LegendRenderContext { String text; - Color iconColor; + Color? iconColor; - Size textSize; + Size? textSize; LegendIconType iconType; - Size size; + Size? size; - Size templateSize; + Size? templateSize; dynamic series; dynamic seriesRenderer; - TechnicalIndicatorsRenderer indicatorRenderer; + TechnicalIndicatorsRenderer? indicatorRenderer; - Trendline trendline; + Trendline? trendline; dynamic point; int seriesIndex; - int trendlineIndex; + int? trendlineIndex; bool isSelect; bool isRender = false; + + bool? isTrendline = false; } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/series/chart_series.dart b/packages/syncfusion_flutter_charts/lib/src/common/series/chart_series.dart index a9fd988c5..464259fce 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/series/chart_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/series/chart_series.dart @@ -18,7 +18,7 @@ class ChartSeries { this.dataSource, this.pointColorMapper, this.sortFieldValueMapper, - this.enableTooltip, + bool? enableTooltip, this.emptyPointSettings, this.dataLabelSettings, this.animationDuration, @@ -30,7 +30,8 @@ class ChartSeries { this.legendIconType, this.opacity, this.sortingOrder, - this.isVisible}); + this.isVisible}) + : enableTooltip = enableTooltip ?? true; ///Data required for rendering the series. If no data source is specified, empty ///chart will be rendered without series. @@ -62,7 +63,7 @@ class ChartSeries { /// final double y; ///} ///``` - final List dataSource; + final List? dataSource; ///Field in the data source, which is considered as x-value. /// @@ -93,7 +94,7 @@ class ChartSeries { /// final double y; ///} ///``` - final ChartIndexedValueMapper xValueMapper; + final ChartIndexedValueMapper? xValueMapper; ///Field in the data source, which is considered as y-value. /// @@ -124,7 +125,7 @@ class ChartSeries { /// final double y; ///} ///``` - final ChartIndexedValueMapper yValueMapper; + final ChartIndexedValueMapper? yValueMapper; ///Field in the data source, which is considered as fill color for the data points. /// @@ -156,7 +157,7 @@ class ChartSeries { /// final Color pointColorMapper; ///} ///``` - final ChartIndexedValueMapper pointColorMapper; + final ChartIndexedValueMapper? pointColorMapper; ///Field in the data source, which is considered as text for the data points. /// @@ -186,7 +187,7 @@ class ChartSeries { /// final int sales1; ///} ///``` - final ChartIndexedValueMapper dataLabelMapper; + final ChartIndexedValueMapper? dataLabelMapper; ///Customizes the empty points, i.e. null data points in a series. /// @@ -202,7 +203,7 @@ class ChartSeries { /// )); ///} ///``` - final EmptyPointSettings emptyPointSettings; + final EmptyPointSettings? emptyPointSettings; ///Customizes the data labels in a series. Data label is a text, which displays ///the details about the data point. @@ -219,7 +220,7 @@ class ChartSeries { /// )); ///} ///``` - final DataLabelSettings dataLabelSettings; + final DataLabelSettings? dataLabelSettings; ///Name of the series. The name will be displayed in legend item by default. ///If name is not specified for the series, then the current series index with ‘series’ @@ -237,7 +238,7 @@ class ChartSeries { /// )); ///} ///``` - final String name; + final String? name; ///Enables or disables the tooltip for this series. Tooltip will display more details ///about data points when tapping the data point region. @@ -272,7 +273,7 @@ class ChartSeries { /// )); ///} ///``` - final double animationDuration; + final double? animationDuration; ///Border color of the series. /// @@ -292,7 +293,7 @@ class ChartSeries { /// )); ///} ///``` - final Color borderColor; + final Color? borderColor; ///Border width of the series. /// @@ -312,7 +313,7 @@ class ChartSeries { /// )); ///} ///``` - final double borderWidth; + final double? borderWidth; ///Text to be displayed in legend. By default, the series name will be displayed ///in the legend. You can change this by setting values to this property. @@ -331,7 +332,7 @@ class ChartSeries { /// )); ///} ///``` - final String legendItemText; + final String? legendItemText; ///Shape of the legend icon. Any shape in the LegendIconType can be applied ///to this property. By default, icon will be rendered based on the type of the series. @@ -352,7 +353,7 @@ class ChartSeries { /// )); ///} ///``` - final LegendIconType legendIconType; + final LegendIconType? legendIconType; ///Customizes the data points or series on selection. /// @@ -374,7 +375,7 @@ class ChartSeries { ///} ///``` // ignore: deprecated_member_use_from_same_package - final SelectionSettings selectionSettings; + final SelectionSettings? selectionSettings; ///Customizes the data points or series on selection. /// @@ -395,7 +396,7 @@ class ChartSeries { /// )); ///} ///``` - final SelectionBehavior selectionBehavior; + final SelectionBehavior? selectionBehavior; ///Opacity of the series. The value ranges from 0 to 1. /// @@ -413,7 +414,7 @@ class ChartSeries { /// )); ///} ///``` - final double opacity; + final double? opacity; ///Field in the data source, which is considered for sorting the data points. /// @@ -445,7 +446,7 @@ class ChartSeries { /// final double y; ///} ///``` - final ChartIndexedValueMapper sortFieldValueMapper; + final ChartIndexedValueMapper? sortFieldValueMapper; ///The data points in the series can be sorted in ascending or descending order. ///The data points will be rendered in the specified order if it is set to none. @@ -466,7 +467,7 @@ class ChartSeries { /// )); ///} ///``` - final SortingOrder sortingOrder; + final SortingOrder? sortingOrder; ///Visibility of the series /// @@ -486,7 +487,7 @@ class ChartSeries { /// )); ///} ///``` - final bool isVisible; + final bool? isVisible; } /// This class Provides method to calculate the Empty Point value. diff --git a/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart b/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart index db5117d19..50eb80298 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart @@ -4,17 +4,17 @@ part of charts; class _RenderTemplate extends StatefulWidget { // ignore: prefer_const_constructors_in_immutables _RenderTemplate( - {this.template, + {required this.template, this.needMeasure, - this.templateLength, - this.templateIndex, - this.chartState}); + required this.templateLength, + required this.templateIndex, + required this.chartState}); final _ChartTemplateInfo template; - bool needMeasure; + bool? needMeasure; final int templateLength; final int templateIndex; final dynamic chartState; - bool isAnnotation; + bool? isAnnotation; @override State createState() => _RenderTemplateState(); @@ -22,95 +22,61 @@ class _RenderTemplate extends StatefulWidget { class _RenderTemplateState extends State<_RenderTemplate> with TickerProviderStateMixin { - List templateControllerList; - AnimationController animationController; - Animation animation; + late List templateControllerList; + AnimationController? animationController; + late Animation animation; @override void initState() { templateControllerList = []; + animationController = AnimationController(vsync: this); super.initState(); } - /// To calculate need to measure template - void _templatesMeasureCompleted() { - final RenderBox renderBox = widget.template.context.findRenderObject(); - widget.template.size = renderBox.size; - if (mounted) { - setState(() { - widget.template.needMeasure = false; - }); - } - } - @override Widget build(BuildContext context) { - num locationX, locationY; final _ChartTemplateInfo templateInfo = widget.template; Widget currentWidget = Container(); Widget renderWidget; - if (templateInfo.needMeasure && templateInfo.templateType != 'DataLabel') { - currentWidget = Opacity(opacity: 0.0, child: widget.template.widget); - widget.template.context = context; - SchedulerBinding.instance - .addPostFrameCallback((_) => _templatesMeasureCompleted()); + if (templateInfo.templateType == 'DataLabel') { + renderWidget = _ChartTemplateRenderObject( + child: templateInfo.widget!, + templateInfo: templateInfo, + chartState: widget.chartState, + animationController: animationController); } else { - if (templateInfo.templateType == 'DataLabel') { - renderWidget = _ChartTemplateRenderObject( - child: templateInfo.widget, - templateInfo: templateInfo, - chartState: widget.chartState, - animationController: animationController); - } else { - locationX = templateInfo.location.dx - - (templateInfo.horizontalAlignment == ChartAlignment.near - ? 0 - : templateInfo.horizontalAlignment == ChartAlignment.center - ? templateInfo.size.width / 2 - : templateInfo.size.width); - locationY = templateInfo.location.dy - - (templateInfo.verticalAlignment == ChartAlignment.near - ? 0 - : templateInfo.verticalAlignment == ChartAlignment.center - ? templateInfo.size.height / 2 - : templateInfo.size.height); - renderWidget = Stack(children: [ - Positioned( - left: locationX, - top: locationY, - child: templateInfo.widget, - ) - ]); - } - if (templateInfo.animationDuration > 0) { - final dynamic seriesRenderer = - (templateInfo.templateType == 'DataLabel') - ? widget.chartState._chartSeries - .visibleSeriesRenderers[templateInfo.seriesIndex] - : null; - final bool needsAnimate = widget.chartState._oldDeviceOrientation == - MediaQuery.of(context).orientation && - ((seriesRenderer != null && - seriesRenderer is CartesianSeriesRenderer) - ? seriesRenderer._needAnimateSeriesElements - : true); - animationController = AnimationController( - duration: Duration(milliseconds: widget.template.animationDuration), - vsync: this); - animation = Tween(begin: 0.0, end: 1.0).animate( - CurvedAnimation(parent: animationController, curve: Curves.linear)); - templateInfo.animationController = animationController; - animationController.forward(from: 1.0); - templateControllerList.add(animationController); - currentWidget = AnimatedBuilder( - animation: animationController, - child: renderWidget, - builder: (BuildContext context, Widget _widget) { - final double value = needsAnimate ? animationController.value : 1; - return Opacity(opacity: value * 1.0, child: _widget); - }); - } else { - currentWidget = renderWidget; - } + renderWidget = _ChartTemplateRenderObject( + child: templateInfo.widget!, + templateInfo: templateInfo, + chartState: widget.chartState, + animationController: animationController); + } + if (templateInfo.animationDuration > 0) { + final dynamic seriesRenderer = (templateInfo.templateType == 'DataLabel') + ? widget.chartState._chartSeries + .visibleSeriesRenderers[templateInfo.seriesIndex] + : null; + final bool needsAnimate = widget.chartState._oldDeviceOrientation == + MediaQuery.of(context).orientation && + ((seriesRenderer != null && seriesRenderer is CartesianSeriesRenderer) + ? seriesRenderer._needAnimateSeriesElements + : true); + animationController = AnimationController( + duration: Duration(milliseconds: widget.template.animationDuration), + vsync: this); + animation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: animationController!, curve: Curves.linear)); + templateInfo.animationController = animationController!; + animationController!.forward(from: 1.0); + templateControllerList.add(animationController!); + currentWidget = AnimatedBuilder( + animation: animationController!, + child: renderWidget, + builder: (BuildContext context, Widget? _widget) { + final double value = needsAnimate ? animationController!.value : 1; + return Opacity(opacity: value * 1.0, child: _widget); + }); + } else { + currentWidget = renderWidget; } return currentWidget; } @@ -130,18 +96,18 @@ class _RenderTemplateState extends State<_RenderTemplate> /// Represents the render object for annotation widget. class _ChartTemplateRenderObject extends SingleChildRenderObjectWidget { const _ChartTemplateRenderObject( - {Key key, - Widget child, - this.templateInfo, - this.chartState, - this.animationController}) + {Key? key, + required Widget child, + required this.templateInfo, + required this.chartState, + required this.animationController}) : super(key: key, child: child); final _ChartTemplateInfo templateInfo; final dynamic chartState; - final AnimationController animationController; + final AnimationController? animationController; @override RenderObject createRenderObject(BuildContext context) { @@ -160,14 +126,14 @@ class _ChartTemplateRenderObject extends SingleChildRenderObjectWidget { class _ChartTemplateRenderBox extends RenderShiftedBox { _ChartTemplateRenderBox( this._templateInfo, this._chartState, this._animationController, - [RenderBox child]) + [RenderBox? child]) : super(child); _ChartTemplateInfo _templateInfo; final dynamic _chartState; - final AnimationController _animationController; + final AnimationController? _animationController; _ChartTemplateInfo get templateInfo => _templateInfo; @@ -189,24 +155,25 @@ class _ChartTemplateRenderBox extends RenderShiftedBox { /// Here we have added 5px padding to each templates const int padding = 5; - child.layout(constraints, parentUsesSize: true); - size = constraints.constrain(Size(child.size.width, child.size.height)); - if (child.parentData is BoxParentData) { - final BoxParentData childParentData = child.parentData; + child!.layout(constraints, parentUsesSize: true); + size = constraints.constrain(Size(child!.size.width, child!.size.height)); + if (child!.parentData is BoxParentData) { + final BoxParentData childParentData = + child!.parentData as BoxParentData; locationX = locationX - (_templateInfo.horizontalAlignment == ChartAlignment.near ? 0 : _templateInfo.horizontalAlignment == ChartAlignment.center - ? child.size.width / 2 - : child.size.width); + ? child!.size.width / 2 + : child!.size.width); locationY = locationY - (_templateInfo.verticalAlignment == ChartAlignment.near ? 0 : _templateInfo.verticalAlignment == ChartAlignment.center ? _templateInfo.templateType == 'DataLabel' - ? child.size.height + padding - : child.size.height / 2 - : child.size.height); + ? child!.size.height + padding + : child!.size.height / 2 + : child!.size.height); if (_templateInfo.templateType == 'DataLabel' && _chartState is SfCartesianChartState) { final dynamic seriesRenderer = _chartState @@ -214,7 +181,7 @@ class _ChartTemplateRenderBox extends RenderShiftedBox { seriesRenderer._chartState = _chartState; seriesRenderer._dataLabelSettingsRenderer = DataLabelSettingsRenderer( seriesRenderer._series.dataLabelSettings); - final CartesianChartPoint point = + final CartesianChartPoint? point = seriesRenderer._dataPoints != null ? (seriesRenderer._dataPoints.isNotEmpty) ? seriesRenderer._dataPoints[_templateInfo.pointIndex] @@ -232,8 +199,7 @@ class _ChartTemplateRenderBox extends RenderShiftedBox { _calculateSplineAreaControlPoints(seriesRenderer); } if (point != null && seriesRenderer._seriesType != 'boxandwhisker') { - if (point.region == null && - seriesRenderer._seriesType != 'waterfall') { + if (point.region == null) { if (seriesRenderer._visibleDataPoints == null || seriesRenderer._visibleDataPoints.length >= seriesRenderer._dataPoints.length) { @@ -246,14 +212,14 @@ class _ChartTemplateRenderBox extends RenderShiftedBox { _calculateDataLabelPosition( seriesRenderer, point, - _templateInfo.pointIndex, + _templateInfo.pointIndex!, _chartState, seriesRenderer._dataLabelSettingsRenderer, - _animationController, + _animationController!, size, _templateInfo.location); - locationX = point.labelLocation.x; - locationY = point.labelLocation.y; + locationX = point.labelLocation!.x; + locationY = point.labelLocation!.y; isLabelWithInRange = _isLabelWithinRange(seriesRenderer, point); } } @@ -270,7 +236,8 @@ class _ChartTemplateRenderBox extends RenderShiftedBox { : _chartState._annotationRegions.add(rect); childParentData.offset = Offset(locationX, locationY); } else { - child.layout(constraints.copyWith(maxWidth: 0), parentUsesSize: true); + child! + .layout(constraints.copyWith(maxWidth: 0), parentUsesSize: true); } } } else { @@ -289,7 +256,10 @@ class _ChartTemplateRenderBox extends RenderShiftedBox { // ignore: must_be_immutable class _ChartTemplate extends StatefulWidget { // ignore: prefer_const_constructors_in_immutables - _ChartTemplate({this.templates, this.render, this.chartState}); + _ChartTemplate( + {required this.templates, + required this.render, + required this.chartState}); List<_ChartTemplateInfo> templates; @@ -297,7 +267,7 @@ class _ChartTemplate extends StatefulWidget { dynamic chartState; - _ChartTemplateState state; + late _ChartTemplateState state; @override State createState() => _ChartTemplateState(); @@ -345,29 +315,29 @@ class _ChartTemplateState extends State<_ChartTemplate> { class _ChartTemplateInfo { _ChartTemplateInfo( - {this.key, - this.widget, - this.location, - this.animationDuration, + {required this.key, + required this.widget, + required this.location, + required this.animationDuration, this.seriesIndex, this.pointIndex, - this.templateType, - this.clipRect, - this.needMeasure, - ChartAlignment horizontalAlignment, - ChartAlignment verticalAlignment}) + required this.templateType, + required this.clipRect, + this.needMeasure = true, + ChartAlignment? horizontalAlignment, + ChartAlignment? verticalAlignment}) : horizontalAlignment = horizontalAlignment ?? ChartAlignment.center, verticalAlignment = verticalAlignment ?? ChartAlignment.center; - Key key; - Widget widget; - Size size; - dynamic point; + Key? key; + Widget? widget; + late Size size; + late dynamic point; Offset location; - BuildContext context; + late BuildContext context; int animationDuration; - AnimationController animationController; - int pointIndex; - int seriesIndex; + late AnimationController animationController; + int? pointIndex; + int? seriesIndex; Rect clipRect; String templateType; ChartAlignment horizontalAlignment; diff --git a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection.dart b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection.dart index 1b940bde5..35c3ab85c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection.dart @@ -9,15 +9,15 @@ part of charts; class SelectionSettings { /// Creating an argument constructor of SelectionSettings class. SelectionSettings( - {bool enable, + {bool? enable, this.selectedColor, this.selectedBorderColor, this.selectedBorderWidth, this.unselectedColor, this.unselectedBorderColor, this.unselectedBorderWidth, - double selectedOpacity, - double unselectedOpacity, + double? selectedOpacity, + double? unselectedOpacity, this.selectionController}) : enable = enable ?? false, selectedOpacity = selectedOpacity ?? 1.0, @@ -61,7 +61,7 @@ class SelectionSettings { /// )); ///} ///``` - final Color selectedColor; + final Color? selectedColor; ///Border color of the selected data points or series. ///```dart @@ -78,7 +78,7 @@ class SelectionSettings { /// )); ///} ///``` - final Color selectedBorderColor; + final Color? selectedBorderColor; ///Border width of the selected data points or series. /// @@ -97,7 +97,7 @@ class SelectionSettings { /// )); ///} ///``` - final double selectedBorderWidth; + final double? selectedBorderWidth; ///Color of the unselected data points or series. /// @@ -115,7 +115,7 @@ class SelectionSettings { /// )); ///} ///``` - final Color unselectedColor; + final Color? unselectedColor; ///Border color of the unselected data points or series. /// @@ -133,7 +133,7 @@ class SelectionSettings { /// )); ///} ///``` - final Color unselectedBorderColor; + final Color? unselectedBorderColor; ///Border width of the unselected data points or series. /// @@ -151,7 +151,7 @@ class SelectionSettings { /// )); ///} ///``` - final double unselectedBorderWidth; + final double? unselectedBorderWidth; ///Opacity of the selected series or data point. /// @@ -206,7 +206,7 @@ class SelectionSettings { /// )); ///} ///``` - final RangeController selectionController; + final RangeController? selectionController; dynamic _chartState; @@ -263,9 +263,9 @@ class SelectionSettings { seriesRenderer._selectionBehaviorRenderer; final List selectedPoints = []; selectedItems = - selectionBehaviorRenderer._selectionRenderer.selectedSegments; + selectionBehaviorRenderer._selectionRenderer!.selectedSegments; for (int i = 0; i < selectedItems.length; i++) { - selectedPoints.add(selectedItems[i].currentSegmentIndex); + selectedPoints.add(selectedItems[i].currentSegmentIndex!); } return selectedPoints; } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection_behavior.dart b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection_behavior.dart index 094ad7dc0..1ea703191 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection_behavior.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection_behavior.dart @@ -6,15 +6,15 @@ part of charts; class SelectionBehavior { /// Creating an argument constructor of SelectionBehaviorclass. SelectionBehavior( - {bool enable, + {bool? enable, this.selectedColor, this.selectedBorderColor, this.selectedBorderWidth, this.unselectedColor, this.unselectedBorderColor, this.unselectedBorderWidth, - double selectedOpacity, - double unselectedOpacity, + double? selectedOpacity, + double? unselectedOpacity, this.selectionController}) : enable = enable ?? false, selectedOpacity = selectedOpacity ?? 1.0, @@ -58,7 +58,7 @@ class SelectionBehavior { /// )); ///} ///``` - final Color selectedColor; + final Color? selectedColor; ///Border color of the selected data points or series. ///```dart @@ -75,7 +75,7 @@ class SelectionBehavior { /// )); ///} ///``` - final Color selectedBorderColor; + final Color? selectedBorderColor; ///Border width of the selected data points or series. /// @@ -94,7 +94,7 @@ class SelectionBehavior { /// )); ///} ///``` - final double selectedBorderWidth; + final double? selectedBorderWidth; ///Color of the unselected data points or series. /// @@ -112,7 +112,7 @@ class SelectionBehavior { /// )); ///} ///``` - final Color unselectedColor; + final Color? unselectedColor; ///Border color of the unselected data points or series. /// @@ -130,7 +130,7 @@ class SelectionBehavior { /// )); ///} ///``` - final Color unselectedBorderColor; + final Color? unselectedBorderColor; ///Border width of the unselected data points or series. /// @@ -148,7 +148,7 @@ class SelectionBehavior { /// )); ///} ///``` - final double unselectedBorderWidth; + final double? unselectedBorderWidth; ///Opacity of the selected series or data point. /// @@ -203,7 +203,7 @@ class SelectionBehavior { /// )); ///} ///``` - final RangeController selectionController; + final RangeController? selectionController; dynamic _chartState; @@ -245,6 +245,11 @@ class SelectionBehavior { void selectDataPoints(int pointIndex, [int seriesIndex = 0]) { final dynamic seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; + assert( + seriesRenderer._chartState! is SfCartesianChartState + ? _getVisibleDataPointIndex(pointIndex, seriesRenderer) != null + : true, + 'Provided point index is not in the visible range. Provide point index which is in the visible range.'); final SelectionBehaviorRenderer selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer; selectionBehaviorRenderer._selectionRenderer @@ -260,9 +265,9 @@ class SelectionBehavior { seriesRenderer._selectionBehaviorRenderer; final List selectedPoints = []; selectedItems = - selectionBehaviorRenderer._selectionRenderer.selectedSegments; + selectionBehaviorRenderer._selectionRenderer!.selectedSegments; for (int i = 0; i < selectedItems.length; i++) { - selectedPoints.add(selectedItems[i].currentSegmentIndex); + selectedPoints.add(selectedItems[i].currentSegmentIndex!); } return selectedPoints; } @@ -281,20 +286,20 @@ class SelectionBehaviorRenderer with ChartSelectionBehavior { // ignore: deprecated_member_use_from_same_package final dynamic _selectionBehavior; - _SelectionRenderer _selectionRenderer; + _SelectionRenderer? _selectionRenderer; // ignore: unused_element void _selectRange() { bool isSelect = false; final CartesianSeriesRenderer seriesRenderer = - _selectionRenderer.seriesRenderer; - final SfCartesianChartState chartState = _selectionRenderer._chartState; + _selectionRenderer!.seriesRenderer; + final SfCartesianChartState chartState = _selectionRenderer!._chartState; if (_selectionBehavior.enable && _selectionBehavior.selectionController != null) { _selectionBehavior.selectionController.addListener(() { chartState._isRangeSelectionSlider = true; - _selectionRenderer.selectedSegments.clear(); - _selectionRenderer.unselectedSegments.clear(); + _selectionRenderer!.selectedSegments.clear(); + _selectionRenderer!.unselectedSegments?.clear(); final dynamic start = _selectionBehavior.selectionController.start; final dynamic end = _selectionBehavior.selectionController.end; for (int i = 0; i < seriesRenderer._dataPoints.length; i++) { @@ -305,15 +310,15 @@ class SelectionBehaviorRenderer with ChartSelectionBehavior { : (xValue >= start && xValue <= end); isSelect - ? _selectionRenderer.selectedSegments + ? _selectionRenderer!.selectedSegments .add(seriesRenderer._segments[i]) - : _selectionRenderer.unselectedSegments - .add(seriesRenderer._segments[i]); + : _selectionRenderer!.unselectedSegments + ?.add(seriesRenderer._segments[i]); } - _selectionRenderer - ._selectedSegmentsColors(_selectionRenderer.selectedSegments); - _selectionRenderer - ._unselectedSegmentsColors(_selectionRenderer.unselectedSegments); + _selectionRenderer! + ._selectedSegmentsColors(_selectionRenderer!.selectedSegments); + _selectionRenderer! + ._unselectedSegmentsColors(_selectionRenderer!.unselectedSegments!); for (final CartesianSeriesRenderer _seriesRenderer in _sfChartState._chartSeries.visibleSeriesRenderers) { @@ -321,10 +326,10 @@ class SelectionBehaviorRenderer with ChartSelectionBehavior { } }); } - if (chartState._initialRender) { + if (chartState._initialRender!) { chartState._isRangeSelectionSlider = false; } - _selectionRenderer._chartState = chartState; + _selectionRenderer!._chartState = chartState; } /// Specifies the index of the data point that needs to be selected initially while @@ -385,15 +390,15 @@ class SelectionBehaviorRenderer with ChartSelectionBehavior { /// Performs the double-tap action on the chart. @override void onDoubleTap(double xPos, double yPos) => - _selectionRenderer.performSelection(Offset(xPos, yPos)); + _selectionRenderer?.performSelection(Offset(xPos, yPos)); /// Performs the long press action on the chart. @override void onLongPress(double xPos, double yPos) => - _selectionRenderer.performSelection(Offset(xPos, yPos)); + _selectionRenderer?.performSelection(Offset(xPos, yPos)); /// Performs the touch-down action on the chart. @override void onTouchDown(double xPos, double yPos) => - _selectionRenderer.performSelection(Offset(xPos, yPos)); + _selectionRenderer?.performSelection(Offset(xPos, yPos)); } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart index 89d55d6d1..165c08d23 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart @@ -6,21 +6,21 @@ part of charts; class TooltipBehavior { /// Creating an argument constructor of TooltipBehavior class. TooltipBehavior( - {TextStyle textStyle, - ActivationMode activationMode, - int animationDuration, - bool enable, - double opacity, - Color borderColor, - double borderWidth, - double duration, - bool shouldAlwaysShow, - double elevation, - bool canShowMarker, - ChartAlignment textAlignment, - int decimalPlaces, - TooltipPosition tooltipPosition, - bool shared, + {TextStyle? textStyle, + ActivationMode? activationMode, + int? animationDuration, + bool? enable, + double? opacity, + Color? borderColor, + double? borderWidth, + double? duration, + bool? shouldAlwaysShow, + double? elevation, + bool? canShowMarker, + ChartAlignment? textAlignment, + int? decimalPlaces, + TooltipPosition? tooltipPosition, + bool? shared, this.color, this.header, this.format, @@ -68,7 +68,7 @@ class TooltipBehavior { /// )); ///} ///``` - final Color color; + final Color? color; /// Header of the tooltip. By default, the series name will be displayed in the header. /// @@ -80,7 +80,7 @@ class TooltipBehavior { /// )); ///} ///``` - final String header; + final String? header; ///Opacity of the tooltip. /// @@ -142,7 +142,7 @@ class TooltipBehavior { /// )); ///} ///``` - final String format; + final String? format; ///Duration for animating the tooltip. /// @@ -251,7 +251,7 @@ class TooltipBehavior { /// )); ///} ///``` - final ChartWidgetBuilder builder; + final ChartWidgetBuilder? builder; ///Color of the tooltip shadow. /// @@ -265,7 +265,7 @@ class TooltipBehavior { /// )); ///} ///``` - final Color shadowColor; + final Color? shadowColor; ///Elevation of the tooltip. /// @@ -368,10 +368,10 @@ class TooltipBehavior { final dynamic chart = chartState._chart; final TooltipBehaviorRenderer tooltipBehaviorRenderer = _chartState._tooltipBehaviorRenderer; - bool isInsidePointRegion; + bool? isInsidePointRegion; String text = ''; String trimmedText = ''; - Offset axisLabelPosition; + Offset? axisLabelPosition; if (chart is SfCartesianChart) { _chartState._requireAxisTooltip = false; for (int i = 0; @@ -383,15 +383,15 @@ class TooltipBehavior { if (_chartState ._chartAxis._axisRenderersCollection[i]._axis.isVisible && labels[k]._labelRegion != null && - labels[k]._labelRegion.contains(Offset(x, y))) { + labels[k]._labelRegion!.contains(Offset(x, y))) { _chartState._requireAxisTooltip = true; text = labels[k].text; trimmedText = labels[k].renderText ?? ''; - tooltipBehaviorRenderer._painter?.prevTooltipValue = - tooltipBehaviorRenderer._painter?.currentTooltipValue; - axisLabelPosition = labels[k]._labelRegion.center; + tooltipBehaviorRenderer._prevTooltipValue = + tooltipBehaviorRenderer._currentTooltipValue; + axisLabelPosition = labels[k]._labelRegion!.center; // -3 to indicte axis tooltip - tooltipBehaviorRenderer._painter?.currentTooltipValue = + tooltipBehaviorRenderer._currentTooltipValue = TooltipValue(null, k, 0); } } @@ -403,7 +403,7 @@ class TooltipBehavior { i++) { final CartesianSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; - if (seriesRenderer._visible && + if (seriesRenderer._visible! && seriesRenderer._series.enableTooltip && seriesRenderer._regionalData != null) { final String seriesType = seriesRenderer._seriesType; @@ -415,7 +415,7 @@ class TooltipBehavior { : _chartState._tooltipBehaviorRenderer._isHovering ? 0 : 15; // regional padding to detect smooth touch - seriesRenderer._regionalData + seriesRenderer._regionalData! .forEach((dynamic regionRect, dynamic values) { final Rect region = regionRect[0]; final Rect paddedRegion = Rect.fromLTRB( @@ -425,7 +425,7 @@ class TooltipBehavior { region.bottom + padding); bool outlierTooltip = false; if (seriesRenderer._seriesType == 'boxandwhisker') { - final List outlierRegion = regionRect[5]; + final List? outlierRegion = regionRect[5]; if (outlierRegion != null) { for (int rectIndex = 0; rectIndex < outlierRegion.length; @@ -449,48 +449,54 @@ class TooltipBehavior { x != null && y != null && _chartState._requireAxisTooltip) { - final _ChartTooltipRendererState tooltipState = - tooltipBehaviorRenderer._chartTooltip?.state; + final SfTooltipState? tooltipState = + tooltipBehaviorRenderer._chartTooltipState; if (trimmedText.contains('...')) { - tooltipState?.show = true; - tooltipState?._needMarker = false; - tooltipBehaviorRenderer._painter - ._showAxisTooltip(axisLabelPosition, chart, text); + tooltipBehaviorRenderer._show = true; + tooltipState?.needMarker = false; + tooltipBehaviorRenderer._showAxisTooltip( + axisLabelPosition!, chart, text); } else { - tooltipState.show = false; + tooltipBehaviorRenderer._show = false; hide(); } } else if (tooltipBehaviorRenderer._chartTooltip != null && activationMode != ActivationMode.none && x != null && y != null) { - final _ChartTooltipRendererState tooltipState = - tooltipBehaviorRenderer._chartTooltip?.state; + final SfTooltipState? tooltipState = + tooltipBehaviorRenderer._chartTooltipState; if (!(chart is SfCartesianChart) || tooltipBehaviorRenderer._isInteraction || (isInsidePointRegion ?? false)) { - tooltipState?._needMarker = true; + tooltipState?.needMarker = chart is SfCartesianChart; if (isInsidePointRegion == true || _chartState._tooltipBehaviorRenderer._isHovering || !(chart is SfCartesianChart)) { - tooltipState?._showTooltip(x, y); + tooltipBehaviorRenderer._showTooltip(x, y); } else { - tooltipState.show = false; + tooltipBehaviorRenderer._show = false; hide(); } - } else if (tooltipBehaviorRenderer._painter != null) { - tooltipState?.show = true; - tooltipState?._needMarker = false; - tooltipBehaviorRenderer._painter._showChartAreaTooltip( - Offset(x, y), chart.primaryXAxis, chart.primaryYAxis, chart); + } else if (tooltipBehaviorRenderer._renderBox != null) { + tooltipBehaviorRenderer._show = true; + tooltipState?.needMarker = false; + tooltipBehaviorRenderer._showChartAreaTooltip( + Offset(x, y), + _chartState._chartAxis._primaryXAxisRenderer, + _chartState._chartAxis._primaryYAxisRenderer, + chart); } } if (chart is SfCartesianChart && - _chartState._tooltipBehaviorRenderer._tooltipTemplate != null && + chart.tooltipBehavior.builder != null && x != null && y != null) { tooltipBehaviorRenderer._showTemplateTooltip(Offset(x, y)); } + if (tooltipBehaviorRenderer != null) { + tooltipBehaviorRenderer._isInteraction = false; + } } /// Displays the tooltip at the specified x and y-values. @@ -504,13 +510,13 @@ class TooltipBehavior { /// * xAxisName - name of the x axis the given point must be bind to. /// /// * yAxisName - name of the y axis the given point must be bind to. - void show(dynamic x, double y, [String xAxisName, String yAxisName]) { + void show(dynamic x, double y, [String? xAxisName, String? yAxisName]) { if (_chartState._chart is SfCartesianChart) { final dynamic chart = _chartState._chart; final TooltipBehaviorRenderer tooltipBehaviorRenderer = _chartState._tooltipBehaviorRenderer; - bool isInsidePointRegion = false; - ChartAxisRenderer xAxisRenderer, yAxisRenderer; + bool? isInsidePointRegion = false; + ChartAxisRenderer? xAxisRenderer, yAxisRenderer; if (xAxisName != null && yAxisName != null) { for (final ChartAxisRenderer axisRenderer in _chartState._chartAxis._axisRenderersCollection) { @@ -525,14 +531,19 @@ class TooltipBehavior { yAxisRenderer = _chartState._chartAxis._primaryYAxisRenderer; } final _ChartLocation position = _calculatePoint( - x is DateTime + (x is DateTime && !(xAxisRenderer! is DateTimeCategoryAxisRenderer)) ? x.millisecondsSinceEpoch - : (x is String && xAxisRenderer is CategoryAxisRenderer) - ? xAxisRenderer._labels.indexOf(x) - : x, + : ((x is DateTime && + xAxisRenderer! is DateTimeCategoryAxisRenderer) + ? (xAxisRenderer as DateTimeCategoryAxisRenderer) + ._labels + .indexOf(xAxisRenderer._dateFormat.format(x)) + : ((x is String && xAxisRenderer is CategoryAxisRenderer) + ? xAxisRenderer._labels.indexOf(x) + : x)), y, - xAxisRenderer, - yAxisRenderer, + xAxisRenderer!, + yAxisRenderer!, _chartState._requireInvertedAxis, null, _chartState._chartAxis._axisClipRect); @@ -541,7 +552,7 @@ class TooltipBehavior { i++) { final CartesianSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; - if (seriesRenderer._visible && + if (seriesRenderer._visible! && seriesRenderer._series.enableTooltip && seriesRenderer._regionalData != null) { final double padding = (seriesRenderer._seriesType == 'bubble' || @@ -550,7 +561,7 @@ class TooltipBehavior { seriesRenderer._seriesType.contains('bar')) ? 0 : 15; // regional padding to detect smooth touch - seriesRenderer._regionalData + seriesRenderer._regionalData! .forEach((dynamic regionRect, dynamic values) { final Rect region = regionRect[0]; final Rect paddedRegion = Rect.fromLTRB( @@ -565,27 +576,23 @@ class TooltipBehavior { } } if (_chartState._tooltipBehaviorRenderer._tooltipTemplate == null) { - final _ChartTooltipRendererState tooltipState = - tooltipBehaviorRenderer._chartTooltip?.state; + final SfTooltipState? tooltipState = + tooltipBehaviorRenderer._chartTooltipState; if (isInsidePointRegion ?? false) { - tooltipState?._needMarker = true; - tooltipState?._showTooltip(position.x, position.y); + tooltipState?.needMarker = chart is SfCartesianChart; + tooltipBehaviorRenderer._showTooltip(position.x, position.y); } else { //to show tooltip when the position is out of point region - tooltipState?.show = true; - tooltipState?._needMarker = false; - _chartState._tooltipBehaviorRenderer._painter._showChartAreaTooltip( + tooltipBehaviorRenderer._show = true; + tooltipState?.needMarker = false; + _chartState._tooltipBehaviorRenderer._showChartAreaTooltip( Offset(position.x, position.y), xAxisRenderer, yAxisRenderer, chart); } - } else if (_chartState._tooltipBehaviorRenderer._tooltipTemplate != - null && - position != null) { - tooltipBehaviorRenderer._showTemplateTooltip( - Offset(position.x, position.y), x, y); } + tooltipBehaviorRenderer._isInteraction = false; } } @@ -604,16 +611,16 @@ class TooltipBehavior { if (_validIndex(pointIndex, seriesIndex, chart)) { final CartesianSeriesRenderer cSeriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; - if (cSeriesRenderer._visible) { - x = cSeriesRenderer._dataPoints[pointIndex].markerPoint.x; - y = cSeriesRenderer._dataPoints[pointIndex].markerPoint.y; + if (cSeriesRenderer._visible!) { + x = cSeriesRenderer._dataPoints[pointIndex].markerPoint!.x; + y = cSeriesRenderer._dataPoints[pointIndex].markerPoint!.y; } } if (x != null && y != null && chart.series[seriesIndex].enableTooltip) { if (chart.tooltipBehavior.builder != null) { tooltipBehaviorRenderer._showTemplateTooltip(Offset(x, y)); } else if (chart.series[seriesIndex].enableTooltip) { - tooltipBehaviorRenderer._chartTooltip?.state?._showTooltip(x, y); + tooltipBehaviorRenderer._showTooltip(x, y); } } } else if (chart is SfCircularChart) { @@ -627,7 +634,7 @@ class TooltipBehavior { _chartState._circularArea ._showCircularTooltipTemplate(seriesIndex, pointIndex); } else if (chart.tooltipBehavior.builder == null && - chartState._animateCompleted && + chartState._animationCompleted && pointIndex >= 0 && (pointIndex + 1 <= _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] @@ -636,12 +643,12 @@ class TooltipBehavior { .visibleSeriesRenderers[seriesIndex]._renderPoints[pointIndex]; if (chartPoint.isVisible) { final Offset position = _degreeToPoint( - chartPoint.midAngle, - (chartPoint.innerRadius + chartPoint.outerRadius) / 2, - chartPoint.center); + chartPoint.midAngle!, + (chartPoint.innerRadius! + chartPoint.outerRadius!) / 2, + chartPoint.center!); x = position.dx; y = position.dy; - tooltipBehaviorRenderer._chartTooltip?.state?._showTooltip(x, y); + tooltipBehaviorRenderer._showTooltip(x, y); } } } else if (pointIndex != null && @@ -651,38 +658,42 @@ class TooltipBehavior { //this shows the tooltip for triangular type of charts (funnerl and pyramid) if (chart.tooltipBehavior.builder == null) { _chartState._tooltipPointIndex = pointIndex; - final Offset position = _chartState._chartSeries + final Offset? position = _chartState._chartSeries .visibleSeriesRenderers[0]._dataPoints[pointIndex].region?.center; x = position?.dx; y = position?.dy; - tooltipBehaviorRenderer._chartTooltip?.state?._showTooltip(x, y); + tooltipBehaviorRenderer._showTooltip(x, y); } else { - if (chart is SfFunnelChart && chartState._animateCompleted) { + if (chart is SfFunnelChart && chartState._animationCompleted) { _chartState._funnelplotArea._showFunnelTooltipTemplate(pointIndex); - } else if (chart is SfPyramidChart && chartState._animateCompleted) { + } else if (chart is SfPyramidChart && chartState._animationCompleted) { _chartState._chartPlotArea._showPyramidTooltipTemplate(pointIndex); } } } + tooltipBehaviorRenderer._isInteraction = false; } /// Hides the tooltip if it is displayed. void hide() { final TooltipBehaviorRenderer tooltipBehaviorRenderer = _chartState._tooltipBehaviorRenderer; + if (tooltipBehaviorRenderer != null) { + tooltipBehaviorRenderer._showLocation = null; + tooltipBehaviorRenderer._show = false; + } if (builder != null) { - //hides tooltip template - tooltipBehaviorRenderer._tooltipTemplate?.show = false; - //ignore: invalid_use_of_protected_member - tooltipBehaviorRenderer._tooltipTemplate?.state?.setState(() {}); + // hides tooltip template + tooltipBehaviorRenderer._chartTooltipState?.widget.hide(); } else { //hides default tooltip - tooltipBehaviorRenderer._chartTooltip?.state?.show = false; - tooltipBehaviorRenderer._painter?.currentTooltipValue = - tooltipBehaviorRenderer._painter?.prevTooltipValue = null; - tooltipBehaviorRenderer - ._chartTooltip?.state?.tooltipRepaintNotifier?.value++; - tooltipBehaviorRenderer._painter?.canResetPath = true; + tooltipBehaviorRenderer._currentTooltipValue = + tooltipBehaviorRenderer._prevTooltipValue = null; + + tooltipBehaviorRenderer._chartTooltipState?.widget.hide(); + if (tooltipBehaviorRenderer._renderBox != null) { + tooltipBehaviorRenderer._renderBox!.canResetPath = true; + } } } } @@ -692,38 +703,306 @@ class TooltipBehaviorRenderer with ChartBehavior { /// Creates an argument constructor for Tooltip renderer class TooltipBehaviorRenderer(this._chartState); - dynamic get _chart => _chartState._chart; + dynamic get _chart => _chartState?._chart; final dynamic _chartState; - TooltipBehavior get _tooltipBehavior => _chart.tooltipBehavior; + TooltipBehavior get _tooltipBehavior => _chart?.tooltipBehavior; - _ChartTooltipRenderer _chartTooltip; + SfTooltip? _chartTooltip; //ignore: prefer_final_fields - bool _isInteraction = true; + bool _isInteraction = false; //ignore: prefer_final_fields - bool _isHovering = false; + bool _isHovering = false, _mouseTooltip = false; + + Widget? _tooltipTemplate; + + TooltipRenderBox? get _renderBox => _chartTooltipState?.renderBox; + + SfTooltipState? get _chartTooltipState { + if (_chartTooltip != null) { + final State? state = (_chartTooltip?.key as GlobalKey).currentState; + //ignore: avoid_as + return state != null ? state as SfTooltipState : null; + } + } + + List _textValues = []; + List _seriesRendererCollection = + []; + + TooltipValue? _prevTooltipValue; + TooltipValue? _presentTooltipValue; + TooltipValue? _currentTooltipValue; + + CartesianSeriesRenderer? _seriesRenderer; + dynamic? _currentSeries, _dataPoint; + int? _pointIndex; + late int _seriesIndex; + late Color _markerColor; + late DataMarkerType _markerType; + Timer? _timer; + bool _show = false; + Offset? _showLocation; + Rect? _tooltipBounds; + String? _stringVal; + set _stringValue(String? value) { + _stringVal = value; + } + + /// Hides the Mouse tooltip if it is displayed. + void _hideMouseTooltip() => _hide(); + + /// Draws tooltip + /// + /// * canvas -Canvas used to draw tooltip + @override + void onPaint(Canvas canvas) {} + + /// Performs the double-tap action of appropriate point. + /// + /// Hits while double tapping on the chart. + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onDoubleTap(double xPos, double yPos) => + _tooltipBehavior.showByPixel(xPos, yPos); + + /// Performs the double-tap action of appropriate point. + /// + /// Hits while a long tap on the chart. + /// + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onLongPress(double xPos, double yPos) => + _tooltipBehavior.showByPixel(xPos, yPos); + + /// Performs the touch-down action of appropriate point. + /// + /// Hits while tapping on the chart. + /// + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onTouchDown(double xPos, double yPos) => + _tooltipBehavior.showByPixel(xPos, yPos); - _TooltipTemplate _tooltipTemplate; + /// Performs the touch move action of chart. + /// + /// Hits while tap and moving on the chart. + /// + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onTouchMove(double xPos, double yPos) { + // Not valid for tooltip + } - _TooltipPainter _painter; + /// Performs the touch move action of chart. + /// + /// Hits while release tap on the chart. + /// + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onTouchUp(double xPos, double yPos) => + _tooltipBehavior.showByPixel(xPos, yPos); + + /// Performs the mouse hover action of chart. + /// + /// Hits while enter tap on the chart. + /// + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onEnter(double xPos, double yPos) => + _tooltipBehavior.showByPixel(xPos, yPos); + + /// Performs the mouse exit action of chart. + /// + /// Hits while exit tap on the chart. + /// + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onExit(double xPos, double yPos) { + if (_renderBox != null && _tooltipBehavior.builder != null) { + _hideMouseTooltip(); + } else if (_tooltipTemplate != null) { + //ignore: unused_local_variable + _timer?.cancel(); + _timer = Timer( + Duration(milliseconds: _tooltipBehavior.duration.toInt()), () {}); + } + } + + /// To render chart tooltip + // ignore:unused_element + void _renderTooltipView(Offset position) { + _chartTooltipState?.needMarker = _chart is SfCartesianChart; + if (_chart is SfCartesianChart) { + _renderCartesianChartTooltip(position); + } else if (_chart is SfCircularChart) { + _renderCircularChartTooltip(position); + } else { + _renderTriangularChartTooltip(position); + } + } + + /// To show tooltip with position offsets + void _showTooltip(double? x, double? y) { + if (x != null && + y != null && + _renderBox != null && + _chartTooltipState != null) { + _show = true; + _chartTooltipState?.needMarker = true; + _isHovering ? _showMouseTooltip(x, y) : _showTooltipView(x, y); + } + } + + /// To show the chart tooltip + void _showTooltipView(double x, double y) { + if (_tooltipBehavior.enable && + _renderBox != null && + _chartState._animationCompleted) { + _renderBox!.canResetPath = false; + _renderTooltipView(Offset(x, y)); + final bool needAnimate = !(_chart is SfCartesianChart) && + (_currentTooltipValue?.pointIndex == + _presentTooltipValue?.pointIndex) && + _chartState._chartSeries.currentSeries.explode && + !(_tooltipBehavior.tooltipPosition == TooltipPosition.pointer); + if ((_presentTooltipValue != null && _currentTooltipValue == null) || + needAnimate) { + _renderBox!.stringValue = _stringVal; + _renderBox!.boundaryRect = _tooltipBounds!; + _chartTooltipState?.widget + .show(_showLocation, _tooltipBehavior.animationDuration); + _currentTooltipValue = _presentTooltipValue; + } else { + if (_tooltipBehavior.tooltipPosition == TooltipPosition.pointer && + (!(_chart is SfCartesianChart) || _currentSeries._isRectSeries)) { + _renderBox!.stringValue = _stringVal; + _renderBox!.boundaryRect = _tooltipBounds!; + _chartTooltipState?.widget + .show(_showLocation, _tooltipBehavior.animationDuration); + _currentTooltipValue = _presentTooltipValue; + } + } + _timer?.cancel(); + assert( + _tooltipBehavior.duration != null + ? _tooltipBehavior.duration >= 0 + : true, + 'The duration time for the tooltip must not be less than 0.'); + if (!_tooltipBehavior.shouldAlwaysShow) { + _timer = Timer( + Duration(milliseconds: _tooltipBehavior.duration.toInt()), () { + _show = false; + _currentTooltipValue = _presentTooltipValue = null; + if (_chartTooltipState?.widget != null && _renderBox != null) { + _chartTooltipState?.widget.hide(); + _renderBox!.canResetPath = true; + } + }); + } + } + } + + /// This method shows the tooltip for any logical pixel outside point region + //ignore: unused_element + void _showChartAreaTooltip(Offset position, ChartAxisRenderer xAxisRenderer, + ChartAxisRenderer yAxisRenderer, dynamic chart) { + _showLocation = position; + final ChartAxis xAxis = xAxisRenderer._axis, yAxis = yAxisRenderer._axis; + if (_tooltipBehavior.enable && + _renderBox != null && + _chartState._animationCompleted) { + _renderBox!.stringValue = _stringVal; + _renderBox!.boundaryRect = _tooltipBounds!; + _chartTooltipState?.widget + .show(_showLocation, _tooltipBehavior.animationDuration); + _renderBox!.canResetPath = false; + //render + _tooltipBounds = _chartState._chartAxis._axisClipRect; + if (_chartState._chartAxis._axisClipRect.contains(position)) { + _currentSeries = _chartState._chartSeries.visibleSeriesRenderers[0]; + _currentSeries = _chartState._chartSeries.visibleSeriesRenderers[0]; + _renderBox!.normalPadding = 5; + _renderBox!.inversePadding = 5; + _renderBox!.header = null; + dynamic xValue = _pointToXValue( + _chartState._requireInvertedAxis, + xAxisRenderer, + xAxisRenderer._bounds, + position.dx - + (_chartState._chartAxis._axisClipRect.left + xAxis.plotOffset), + position.dy - + (_chartState._chartAxis._axisClipRect.top + xAxis.plotOffset)); + dynamic yValue = _pointToYValue( + _chartState._requireInvertedAxis, + yAxisRenderer, + yAxisRenderer._bounds, + position.dx - + (_chartState._chartAxis._axisClipRect.left + yAxis.plotOffset), + position.dy - + (_chartState._chartAxis._axisClipRect.top + yAxis.plotOffset)); + if (xAxisRenderer is DateTimeAxisRenderer) { + final DateTimeAxis xAxis = xAxisRenderer._axis as DateTimeAxis; + xValue = (xAxis.dateFormat ?? _getDateTimeLabelFormat(xAxisRenderer)) + .format(DateTime.fromMillisecondsSinceEpoch(xValue.floor())); + } else if (xAxisRenderer is DateTimeCategoryAxisRenderer) { + xValue = xAxisRenderer._dateFormat + .format(DateTime.fromMillisecondsSinceEpoch(xValue.floor())); + } else if (xAxisRenderer is CategoryAxisRenderer) { + xValue = xAxisRenderer._visibleLabels[xValue.toInt()].text; + } else if (xAxisRenderer is NumericAxisRenderer) { + xValue = xValue.toStringAsFixed(2).contains('.00') + ? xValue.floor() + : xValue.toStringAsFixed(2); + } + + if (yAxisRenderer is NumericAxisRenderer || + yAxisRenderer is LogarithmicAxisRenderer) { + yValue = yValue.toStringAsFixed(2).contains('.00') + ? yValue.floor() + : yValue.toStringAsFixed(2); + } + _stringValue = ' $xValue : $yValue '; + _showLocation = position; + } + _timer?.cancel(); + if (!_tooltipBehavior.shouldAlwaysShow) { + _timer = Timer( + Duration(milliseconds: _tooltipBehavior.duration.toInt()), () { + _show = false; + if (_chartTooltipState?.widget != null && _renderBox != null) { + _chartTooltipState?.widget.hide(); + _renderBox!.canResetPath = true; + } + }); + } + } + } - /// To show tooltip template void _showTemplateTooltip(Offset position, [dynamic xValue, dynamic yValue]) { final dynamic chart = _chartState._chart; - dynamic series, currentSeriesRender; - _tooltipTemplate?._alwaysShow = _tooltipBehavior.shouldAlwaysShow; + _tooltipBounds = _chartState._chartAxis._axisClipRect; + dynamic series; + double yPadding = 0; if (_chartState._chartAxis._axisClipRect.contains(position) && - _chartState._animateCompleted) { - int seriesIndex, pointIndex; + _chartState._animationCompleted) { + int? seriesIndex, pointIndex; int outlierIndex = -1; bool isTooltipRegion = false; if (!_isHovering) { //assingning null for the previous and current tooltip values in case of mouse not hovering - _tooltipTemplate?.state?.prevTooltipValue = null; - _tooltipTemplate?.state?.currentTooltipValue = null; + _prevTooltipValue = null; + _currentTooltipValue = null; } for (int i = 0; i < _chartState._chartSeries.visibleSeriesRenderers.length; @@ -733,24 +1012,29 @@ class TooltipBehaviorRenderer with ChartBehavior { series = seriesRenderer._series; int j = 0; - final double padding = (seriesRenderer._seriesType.contains('bar') || - seriesRenderer._seriesType.contains('column')) || - _isHovering - ? 0 - : 15; - if (seriesRenderer._visible && + if (seriesRenderer._visible! && series.enableTooltip && seriesRenderer._regionalData != null) { - seriesRenderer._regionalData + seriesRenderer._regionalData! .forEach((dynamic regionRect, dynamic values) { + final bool isTrendLine = values[values.length - 1].contains('true'); + final double padding = ((seriesRenderer._seriesType == 'bubble' || + seriesRenderer._seriesType == 'scatter' || + seriesRenderer._seriesType.contains('column') || + seriesRenderer._seriesType.contains('bar') || + seriesRenderer._seriesType == 'histogram') && + !isTrendLine) + ? 0 + : _isHovering + ? 0 + : 15; final Rect region = regionRect[0]; final double left = region.left - padding; final double right = region.right + padding; final double top = region.top - padding; final double bottom = region.bottom + padding; Rect paddedRegion = Rect.fromLTRB(left, top, right, bottom); - final List outlierRegion = regionRect[5]; - final bool isTrendLine = values[values.length - 1].contains('true'); + final List? outlierRegion = regionRect[5]; if (outlierRegion != null) { for (int rectIndex = 0; @@ -764,35 +1048,29 @@ class TooltipBehaviorRenderer with ChartBehavior { } if (paddedRegion.contains(position)) { - seriesIndex = i; - currentSeriesRender = seriesRenderer; - pointIndex = seriesRenderer._dataPoints.indexOf(regionRect[4]); - _tooltipTemplate.state.seriesIndex = seriesIndex; + _seriesIndex = seriesIndex = i; + _currentSeries = seriesRenderer; + _pointIndex = pointIndex = + seriesRenderer._dataPoints.indexOf(regionRect[4]); Offset tooltipPosition = !(seriesRenderer._isRectSeries && _tooltipBehavior.tooltipPosition != TooltipPosition.auto) ? ((outlierIndex >= 0) ? regionRect[6][outlierIndex] : regionRect[1]) : position; - final List paddingData = _getTooltipPaddingData( + final List paddingData = _getTooltipPaddingData( seriesRenderer, isTrendLine, region, paddedRegion, tooltipPosition); - + yPadding = paddingData[0]!.dy; tooltipPosition = paddingData[1] ?? tooltipPosition; - _tooltipTemplate.rect = Rect.fromLTWH( - tooltipPosition.dx, - tooltipPosition.dy - - (!(seriesRenderer._isRectSeries && - _tooltipBehavior.tooltipPosition != - TooltipPosition.auto) - ? paddingData[0].dy - : 0), - region.width, - region.height); - _tooltipTemplate.template = chart.tooltipBehavior.builder( + _showLocation = tooltipPosition; + _renderBox!.normalPadding = + _seriesRenderer is BubbleSeriesRenderer ? 0 : yPadding; + _renderBox!.inversePadding = yPadding; + _tooltipTemplate = chart.tooltipBehavior.builder( series.dataSource[j], regionRect[4], series, pointIndex, i); isTooltipRegion = true; } @@ -800,35 +1078,41 @@ class TooltipBehaviorRenderer with ChartBehavior { }); } } - if (_chartState._tooltipBehaviorRenderer._isHovering && isTooltipRegion) { - _tooltipTemplate.state.prevTooltipValue = - _tooltipTemplate.state.currentTooltipValue; - _tooltipTemplate.state.currentTooltipValue = - TooltipValue(seriesIndex, pointIndex, outlierIndex); + if (_isHovering && isTooltipRegion) { + _prevTooltipValue = _currentTooltipValue; + _currentTooltipValue = + TooltipValue(seriesIndex, pointIndex!, outlierIndex); } - _tooltipTemplate.show = isTooltipRegion; - final TooltipValue presentTooltip = - _tooltipTemplate.state.presentTooltipValue; + final TooltipValue? presentTooltip = _presentTooltipValue; if (presentTooltip == null || seriesIndex != presentTooltip.seriesIndex || pointIndex != presentTooltip.pointIndex || outlierIndex != presentTooltip.outlierIndex || - (currentSeriesRender != null && - currentSeriesRender._isRectSeries && + (_currentSeries != null && + _currentSeries._isRectSeries && _tooltipBehavior.tooltipPosition != TooltipPosition.auto)) { //Current point is different than previous one so tooltip re-renders if (seriesIndex != null && pointIndex != null) { - _tooltipTemplate.state.presentTooltipValue = - TooltipValue(seriesIndex, pointIndex, outlierIndex); + _presentTooltipValue = + TooltipValue(seriesIndex, pointIndex!, outlierIndex); + } + if (isTooltipRegion && _tooltipTemplate != null) { + _timer?.cancel(); + if (!_isHovering && _renderBox != null) { + _timer = Timer( + Duration(milliseconds: _tooltipBehavior.duration.toInt()), + _hideTooltipTemplate); + } + _show = isTooltipRegion; + _performTooltip(); } - _tooltipTemplate?.state?._performTooltip(); } else { //Current point is same as previous one so timer is reset and tooltip is not re-rendered if (!_isHovering) { - _tooltipTemplate?.state?.tooltipTimer?.cancel(); - _tooltipTemplate?.state?.tooltipTimer = Timer( - Duration(milliseconds: _tooltipTemplate.duration.toInt()), - _tooltipTemplate.state.hideTooltipTemplate); + _timer?.cancel(); + _timer = Timer( + Duration(milliseconds: _tooltipBehavior.duration.toInt()), + _hideTooltipTemplate); } } @@ -836,8 +1120,8 @@ class TooltipBehaviorRenderer with ChartBehavior { //to show tooltip temlate when the position resides outside point region final dynamic x = xValue ?? _pointToXValue( - chart, - chart.primaryXAxis, + _chartState._requireInvertedAxis, + _chartState._chartAxis._primaryXAxisRenderer, _chartState._chartAxis._primaryXAxisRenderer._bounds, position.dx - (_chartState._chartAxis._axisClipRect.left + @@ -847,8 +1131,8 @@ class TooltipBehaviorRenderer with ChartBehavior { chart.primaryXAxis.plotOffset)); final dynamic y = yValue ?? _pointToYValue( - chart, - chart.primaryYAxis, + _chartState._requireInvertedAxis, + _chartState._chartAxis._primaryYAxisRenderer, _chartState._chartAxis._primaryYAxisRenderer._bounds, position.dx - (_chartState._chartAxis._axisClipRect.left + @@ -856,109 +1140,769 @@ class TooltipBehaviorRenderer with ChartBehavior { position.dy - (_chartState._chartAxis._axisClipRect.top + chart.primaryYAxis.plotOffset)); - _chartState._tooltipBehaviorRenderer._tooltipTemplate.rect = - Rect.fromLTWH(position.dx, position.dy, 1, 1); - _tooltipTemplate.template = chart.tooltipBehavior.builder( + _renderBox!.normalPadding = 5; + _renderBox!.inversePadding = 5; + _showLocation = position; + _tooltipTemplate = chart.tooltipBehavior.builder( null, CartesianChartPoint(x, y), null, null, null); isTooltipRegion = true; - _tooltipTemplate.show = isTooltipRegion; - _tooltipTemplate?.state?._performTooltip(); + _show = isTooltipRegion; + _performTooltip(); } if (!isTooltipRegion) { - _tooltipTemplate?.state?.hideOnTimer(); + _hideOnTimer(); } } - _chartState._tooltipBehaviorRenderer._isInteraction = false; + _isInteraction = false; } - /// Hides the Mouse tooltip if it is displayed. - void _hideMouseTooltip() => _painter?.hide(); + /// To hide the tooltip when the timer ends + void _hide() { + _timer?.cancel(); + _timer = + Timer(Duration(milliseconds: _tooltipBehavior.duration.toInt()), () { + _show = false; + _currentTooltipValue = _presentTooltipValue = null; + if (_chartTooltipState?.widget != null && _renderBox != null) { + _chartTooltipState?.widget.hide(); + _renderBox!.canResetPath = true; + } + }); + } - /// Draws tooltip - /// - /// * canvas -Canvas used to draw tooltip - @override - void onPaint(Canvas canvas) { - if (_painter != null) { - _painter._renderTooltip(canvas); + /// To hide tooltip template with timer + void _hideOnTimer() { + if (_prevTooltipValue == null && _currentTooltipValue == null) { + _hideTooltipTemplate(); + } else { + _timer?.cancel(); + _timer = Timer(Duration(milliseconds: _tooltipBehavior.duration.toInt()), + _hideTooltipTemplate); } } - /// Performs the double-tap action of appropriate point. - /// - /// Hits while double tapping on the chart. - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onDoubleTap(double xPos, double yPos) => - _tooltipBehavior.showByPixel(xPos, yPos); + /// To hide tooltip templates + void _hideTooltipTemplate() { + _presentTooltipValue = null; + if (!(_tooltipBehavior.shouldAlwaysShow)) { + _show = false; + _chartTooltipState?.widget.hide(); + _prevTooltipValue = null; + _currentTooltipValue = null; + } + } - /// Performs the double-tap action of appropriate point. - /// - /// Hits while a long tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onLongPress(double xPos, double yPos) => - _tooltipBehavior.showByPixel(xPos, yPos); + /// To perform rendering of tooltip + void _performTooltip() { + //for mouse hover the tooltip is redrawn only when the current tooltip value differs from the previous one + if (_show && + ((_prevTooltipValue == null && _currentTooltipValue == null) || + ((_chartState is SfCartesianChartState && + (_currentSeries?._isRectSeries ?? false) && + _tooltipBehavior.tooltipPosition != TooltipPosition.auto)) || + (_prevTooltipValue?.seriesIndex != + _currentTooltipValue?.seriesIndex || + _prevTooltipValue?.outlierIndex != + _currentTooltipValue?.outlierIndex || + _prevTooltipValue?.pointIndex != + _currentTooltipValue?.pointIndex))) { + final bool reRender = (_isHovering && + _prevTooltipValue != null && + _currentTooltipValue != null && + _prevTooltipValue!.seriesIndex == _currentTooltipValue!.seriesIndex && + _prevTooltipValue!.pointIndex == _currentTooltipValue!.pointIndex && + _prevTooltipValue!.outlierIndex == + _currentTooltipValue!.outlierIndex); + if (_tooltipBehavior.builder != null && _tooltipBounds != null) { + _renderBox!.stringValue = _stringVal; + _renderBox!.boundaryRect = _tooltipBounds!; + _chartTooltipState?.widget.show( + _showLocation, + (!reRender) ? _tooltipBehavior.animationDuration.toInt() : 0, + _tooltipTemplate); + } + } + } - /// Performs the touch-down action of appropriate point. - /// - /// Hits while tapping on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onTouchDown(double xPos, double yPos) => - _tooltipBehavior.showByPixel(xPos, yPos); + /// To show tooltip on mouse pointer actions + void _showMouseTooltip(double x, double y) { + if (_tooltipBehavior.enable && + _renderBox != null && + _chartState._animationCompleted) { + _renderBox!.canResetPath = false; + _renderTooltipView(Offset(x, y)); + _timer?.cancel(); + if (!_mouseTooltip) { + _hide(); + } else { + if (_presentTooltipValue != null && _currentTooltipValue == null) { + _renderBox!.stringValue = _stringVal; + _renderBox!.boundaryRect = _tooltipBounds!; + _chartTooltipState?.widget + .show(_showLocation, _tooltipBehavior.animationDuration); + _currentTooltipValue = _presentTooltipValue; + } else if (_presentTooltipValue != null && + _currentTooltipValue != null && + _tooltipBehavior.tooltipPosition != TooltipPosition.auto && + (!(_seriesRenderer is CartesianSeriesRenderer) || + _seriesRenderer!._isRectSeries) && + _currentTooltipValue!.seriesIndex == + _presentTooltipValue!.seriesIndex && + _currentTooltipValue!.pointIndex == + _presentTooltipValue!.pointIndex) { + _renderBox!.stringValue = _stringVal; + _renderBox!.boundaryRect = _tooltipBounds!; + _chartTooltipState?.widget.show(_showLocation, 0); + } + } + } + } - /// Performs the touch move action of chart. - /// - /// Hits while tap and moving on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onTouchMove(double xPos, double yPos) { - // Not valid for tooltip + void _tooltipRenderingEvent(TooltipRenderArgs args) { + String? header = args.header; + String? stringValue = args.text; + double? x = args.location?.dx, y = args.location?.dy; + TooltipArgs tooltipArgs; + if (x != null && + y != null && + stringValue != null && + _currentSeries != null) { + final int seriesIndex = _chart is SfCartesianChart + ? _currentSeries._segments[0]._seriesIndex + : 0; + if ((_chart is SfCartesianChart && !_chartState._requireAxisTooltip) || + !(_chart is SfCartesianChart)) { + if (_chart.onTooltipRender != null && + _dataPoint != null && + !(_dataPoint.isTooltipRenderEvent ?? false)) { + _dataPoint.isTooltipRenderEvent = true; + tooltipArgs = TooltipArgs( + seriesIndex, + _chartState + ._chartSeries.visibleSeriesRenderers[seriesIndex]._dataPoints, + _pointIndex, + _chart is SfCartesianChart + ? _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] + ._visibleDataPoints[_pointIndex].overallDataPointIndex + : _pointIndex); + tooltipArgs.text = stringValue; + tooltipArgs.header = header; + _dataPoint._tooltipLabelText = stringValue; + _dataPoint._tooltipHeaderText = header; + tooltipArgs.locationX = x; + tooltipArgs.locationY = y; + _chart.onTooltipRender(tooltipArgs); + stringValue = tooltipArgs.text; + header = tooltipArgs.header; + x = tooltipArgs.locationX; + y = tooltipArgs.locationY; + _dataPoint._tooltipLabelText = tooltipArgs.text; + _dataPoint._tooltipHeaderText = tooltipArgs.header; + _dataPoint.isTooltipRenderEvent = false; + args.text = stringValue!; + args.header = header; + args.location = Offset(x!, y!); + } else if (_chart.onTooltipRender != null) { + //Fires the on tooltip render event when the tooltip is shown outside point region + tooltipArgs = TooltipArgs(null, null, null); + tooltipArgs.text = stringValue; + tooltipArgs.header = header; + tooltipArgs.locationX = x; + tooltipArgs.locationY = y; + _chart.onTooltipRender(tooltipArgs); + args.text = tooltipArgs.text; + args.header = tooltipArgs.header; + args.location = + Offset(tooltipArgs.locationX!, tooltipArgs.locationY!); + } + if (_chart.onTooltipRender != null && _dataPoint != null) { + stringValue = _dataPoint._tooltipLabelText; + header = _dataPoint._tooltipHeaderText; + } + } + } } - /// Performs the touch move action of chart. - /// - /// Hits while release tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onTouchUp(double xPos, double yPos) => - _tooltipBehavior.showByPixel(xPos, yPos); + /// To render a chart tooltip for circular series + void _renderCircularChartTooltip(Offset position) { + final SfCircularChart chart = _chartState._chart; + _tooltipBounds = _chartState._chartContainerRect; + bool isContains = false; + final _Region? pointRegion = _getCircularPointRegion( + chart, position, _chartState._chartSeries.visibleSeriesRenderers[0]); + if (pointRegion != null && + _chartState._chartSeries.visibleSeriesRenderers[pointRegion.seriesIndex] + ._series.enableTooltip) { + _prevTooltipValue = + TooltipValue(pointRegion.seriesIndex, pointRegion.pointIndex); + _presentTooltipValue = _prevTooltipValue; + if (_prevTooltipValue != null && + _currentTooltipValue != null && + _prevTooltipValue!.pointIndex != _currentTooltipValue!.pointIndex) { + _currentTooltipValue = null; + } + final ChartPoint chartPoint = _chartState + ._chartSeries + .visibleSeriesRenderers[pointRegion.seriesIndex] + ._renderPoints[pointRegion.pointIndex]; + final Offset location = + chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer + ? position + : _degreeToPoint( + chartPoint.midAngle!, + (chartPoint.innerRadius! + chartPoint.outerRadius!) / 2, + chartPoint.center!); + _currentSeries = pointRegion.seriesIndex; + _pointIndex = pointRegion.pointIndex; + _dataPoint = _chartState + ._chartSeries.visibleSeriesRenderers[0]._dataPoints[_pointIndex]; + final int digits = chart.tooltipBehavior.decimalPlaces; + String? header = chart.tooltipBehavior.header; + header = (header == null) + // ignore: prefer_if_null_operators + ? _chartState + ._chartSeries + .visibleSeriesRenderers[pointRegion.seriesIndex] + ._series + .name != + null + ? _chartState._chartSeries + .visibleSeriesRenderers[pointRegion.seriesIndex]._series.name + : null + : header; + _renderBox!.header = header ?? ''; + if (chart.tooltipBehavior.format != null) { + final String resultantString = chart.tooltipBehavior.format! + .replaceAll('point.x', chartPoint.x.toString()) + .replaceAll('point.y', _getDecimalLabelValue(chartPoint.y, digits)) + .replaceAll( + 'series.name', + _chartState + ._chartSeries + .visibleSeriesRenderers[pointRegion.seriesIndex] + ._series + .name ?? + 'series.name'); + _stringValue = resultantString; + _showLocation = location; + } else { + _stringValue = chartPoint.x.toString() + + ' : ' + + _getDecimalLabelValue(chartPoint.y, digits); + _showLocation = location; + } + isContains = true; + } else { + chart.tooltipBehavior.hide(); + isContains = false; + } + _mouseTooltip = isContains; + if (!isContains) { + _prevTooltipValue = _currentTooltipValue = null; + } + } - /// Performs the mouse hover action of chart. - /// - /// Hits while enter tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onEnter(double xPos, double yPos) => - _tooltipBehavior.showByPixel(xPos, yPos); + /// To render a chart tooltip for triangular series + void _renderTriangularChartTooltip(Offset position) { + final dynamic chart = _chart; + final dynamic chartState = _chartState; - /// Performs the mouse exit action of chart. - /// - /// Hits while exit tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onExit(double xPos, double yPos) { - if (_painter != null) { - _hideMouseTooltip(); - } else if (_tooltipTemplate != null) { - //ignore: unused_local_variable - final Timer t = Timer( - Duration(milliseconds: _tooltipBehavior.duration.toInt()), - _tooltipTemplate.state.hideTooltipTemplate); + _tooltipBounds = chartState._chartContainerRect; + bool isContains = false; + const int seriesIndex = 0; + _pointIndex = _chartState._tooltipPointIndex; + if (_pointIndex == null && _chartState._currentActive == null) { + int? _pointIndex; + bool isPoint; + final dynamic seriesRenderer = + chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; + for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { + if (seriesRenderer._renderPoints[j].isVisible) { + isPoint = _isPointInPolygon( + seriesRenderer._renderPoints[j].pathRegion, position); + if (isPoint) { + _pointIndex = j; + break; + } + } + } + chartState._currentActive = _ChartInteraction( + seriesIndex, + _pointIndex, + seriesRenderer._series, + seriesRenderer._renderPoints[_pointIndex], + ); + } + _pointIndex ??= chartState._currentActive.pointIndex; + _dataPoint = _chartState + ._chartSeries.visibleSeriesRenderers[0]._dataPoints[_pointIndex]; + _chartState._tooltipPointIndex = null; + final int digits = chart.tooltipBehavior.decimalPlaces; + if (chart.tooltipBehavior.enable) { + _prevTooltipValue = TooltipValue(seriesIndex, _pointIndex!); + _presentTooltipValue = _prevTooltipValue; + if (_prevTooltipValue != null && + _currentTooltipValue != null && + _prevTooltipValue!.pointIndex != _currentTooltipValue!.pointIndex) { + _currentTooltipValue = null; + } + final PointInfo chartPoint = _chartState._chartSeries + .visibleSeriesRenderers[seriesIndex]._renderPoints[_pointIndex]; + final Offset location = chart.tooltipBehavior.tooltipPosition == + TooltipPosition.pointer && + _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] + ._series.explode + ? chartPoint.symbolLocation + : chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer && + !_chartState._chartSeries.visibleSeriesRenderers[seriesIndex] + ._series.explode + ? position + : chartPoint.symbolLocation; + _currentSeries = seriesIndex; + String? header = chart.tooltipBehavior.header; + header = (header == null) + // ignore: prefer_if_null_operators + ? _chartState._chartSeries.visibleSeriesRenderers[seriesIndex]._series + .name != + null + ? _chartState + ._chartSeries.visibleSeriesRenderers[seriesIndex]._series.name + : null + : header; + _renderBox!.header = header ?? ''; + if (chart.tooltipBehavior.format != null) { + final String resultantString = chart.tooltipBehavior.format + .replaceAll('point.x', chartPoint.x.toString()) + .replaceAll('point.y', _getDecimalLabelValue(chartPoint.y, digits)) + .replaceAll( + 'series.name', + _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] + ._series.name ?? + 'series.name'); + _stringValue = resultantString; + _showLocation = location; + } else { + _stringValue = chartPoint.x.toString() + + ' : ' + + _getDecimalLabelValue(chartPoint.y, digits); + _showLocation = location; + } + isContains = true; + } else { + chart.tooltipBehavior.hide(); + isContains = false; + } + _mouseTooltip = isContains; + if (!isContains) { + _prevTooltipValue = _currentTooltipValue = null; + } + } + + /// To show the axis label tooltip for trimmed axes label texts. + void _showAxisTooltip(Offset position, dynamic chart, String text) { + if (_renderBox != null) { + _chartTooltipState?.needMarker = false; + _renderBox!.canResetPath = false; + _renderBox!.header = ''; + _stringValue = text; + _showLocation = position; + _tooltipBounds = _chartState._containerRect; + _renderBox!.inversePadding = 0; + if ((_prevTooltipValue != null) && + (_prevTooltipValue!.pointIndex == _currentTooltipValue!.pointIndex)) { + _renderBox!.stringValue = _stringVal; + _renderBox!.boundaryRect = _tooltipBounds!; + _chartTooltipState?.widget.show(_showLocation, 0); + } else { + _renderBox!.stringValue = _stringVal; + _renderBox!.boundaryRect = _tooltipBounds!; + _chartTooltipState?.widget + .show(_showLocation, _tooltipBehavior.animationDuration); + _timer?.cancel(); + } + _timer = + Timer(Duration(milliseconds: _tooltipBehavior.duration.toInt()), () { + _show = false; + if (_chartTooltipState?.widget != null && _renderBox != null) { + _chartTooltipState?.widget.hide(); + _renderBox!.canResetPath = true; + } + }); } } + + /// To render a chart tooltip for cartesian series + void _renderCartesianChartTooltip(Offset position) { + _tooltipBounds = _chartState._chartAxis._axisClipRect; + bool isContains = false; + if (_chartState._chartAxis._axisClipRect.contains(position)) { + Offset? tooltipPosition; + double touchPadding; + Offset? padding; + bool? isTrendLine; + dynamic dataRect; + dynamic dataValues; + bool outlierTooltip = false; + int outlierTooltipIndex = -1; + final List markerGradients = []; + final List markerPaints = []; + final List markerTypes = []; + final List markerImages = []; + for (int i = 0; + i < _chartState._chartSeries.visibleSeriesRenderers.length; + i++) { + _seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; + final CartesianSeries series = + _seriesRenderer!._series; + if (_seriesRenderer!._visible! && + series.enableTooltip && + _seriesRenderer?._regionalData != null) { + int count = 0; + _seriesRenderer!._regionalData! + .forEach((dynamic regionRect, dynamic values) { + isTrendLine = values[values.length - 1].contains('true'); + touchPadding = ((_seriesRenderer!._seriesType == 'bubble' || + _seriesRenderer!._seriesType == 'scatter' || + _seriesRenderer!._seriesType.contains('column') || + _seriesRenderer!._seriesType.contains('bar') || + _seriesRenderer!._seriesType == 'histogram') && + !isTrendLine!) + ? 0 + : _isHovering + ? 0 + : 15; // regional padding to detect smooth touch + final Rect region = regionRect[0]; + final List? outlierRegion = regionRect[5]; + final double left = region.left - touchPadding; + final double right = region.right + touchPadding; + final double top = region.top - touchPadding; + final double bottom = region.bottom + touchPadding; + Rect paddedRegion = Rect.fromLTRB(left, top, right, bottom); + if (outlierRegion != null) { + for (int rectIndex = 0; + rectIndex < outlierRegion.length; + rectIndex++) { + if (outlierRegion[rectIndex].contains(position)) { + paddedRegion = outlierRegion[rectIndex]; + outlierTooltipIndex = rectIndex; + outlierTooltip = true; + } + } + } + + if (paddedRegion.contains(position) && + (isTrendLine! ? regionRect[4].isVisible : true)) { + if (_seriesRenderer!._seriesType != 'boxandwhisker' + ? !region.contains(position) + : (paddedRegion.contains(position) || + !region.contains(position))) { + _tooltipBounds = _chartState._chartAxis._axisClipRect; + } + _presentTooltipValue = + TooltipValue(i, count, outlierTooltipIndex); + _currentSeries = _seriesRenderer; + _pointIndex = _chart is SfCartesianChart + ? regionRect[4].visiblePointIndex + : count; + _dataPoint = regionRect[4]; + _markerType = _seriesRenderer!._series.markerSettings.shape; + Color? seriesColor = _seriesRenderer!._seriesColor; + if (_seriesRenderer!._seriesType == 'waterfall') { + seriesColor = _getWaterfallSeriesColor( + _seriesRenderer!._series as WaterfallSeries, + _seriesRenderer!._dataPoints[_pointIndex!], + seriesColor)!; + } + _markerColor = regionRect[2] ?? + _seriesRenderer!._series.markerSettings.borderColor ?? + seriesColor!; + tooltipPosition = (outlierTooltipIndex >= 0) + ? regionRect[6][outlierTooltipIndex] + : regionRect[1]; + final Paint markerPaint = Paint(); + markerPaint.color = (!_tooltipBehavior.shared + ? _markerColor + : _seriesRenderer!._series.markerSettings.borderColor ?? + _seriesRenderer!._seriesColor ?? + _seriesRenderer!._series.color)! + .withOpacity(_tooltipBehavior.opacity); + if (!_tooltipBehavior.shared) { + markerGradients + ..clear() + ..add(_seriesRenderer!._series.gradient); + markerImages + ..clear() + ..add(_seriesRenderer!._markerSettingsRenderer?._image); + markerPaints + ..clear() + ..add(markerPaint); + markerTypes + ..clear() + ..add(_markerType); + } + final List paddingData = !(_seriesRenderer! + ._isRectSeries && + _tooltipBehavior.tooltipPosition != TooltipPosition.auto) + ? _getTooltipPaddingData(_seriesRenderer!, isTrendLine!, + region, paddedRegion, tooltipPosition) + : [const Offset(2, 2), tooltipPosition]; + padding = paddingData[0]; + tooltipPosition = paddingData[1]; + _showLocation = tooltipPosition; + dataValues = values; + dataRect = regionRect; + isContains = _mouseTooltip = true; + } + count++; + }); + if (_tooltipBehavior.shared) { + markerGradients.add(_seriesRenderer!._series.gradient); + markerImages.add(_seriesRenderer!._markerSettingsRenderer?._image); + markerTypes.add(_seriesRenderer!._series.markerSettings.shape); + final Paint markerPaint = Paint(); + markerPaint.color = + _seriesRenderer!._series.markerSettings.borderColor ?? + _seriesRenderer!._seriesColor ?? + _seriesRenderer!._series.color! + .withOpacity(_tooltipBehavior.opacity); + markerPaints.add(markerPaint); + } + } + } + if (isContains) { + _renderBox!.markerGradients = markerGradients; + _renderBox!.markerImages = markerImages; + _renderBox!.markerPaints = markerPaints; + _renderBox!.markerTypes = markerTypes; + _seriesRenderer = _currentSeries ?? _seriesRenderer; + if (_presentTooltipValue != null && + _currentTooltipValue != null && + (_presentTooltipValue!.pointIndex != + _currentTooltipValue!.pointIndex || + _presentTooltipValue!.seriesIndex != + _currentTooltipValue!.seriesIndex)) { + _currentTooltipValue = null; + } else if (_seriesRenderer!._seriesType == 'boxandwhisker' && + _currentTooltipValue != null && + _presentTooltipValue!.outlierIndex != + _currentTooltipValue!.outlierIndex) { + _currentTooltipValue = null; + } + if (_currentSeries._isRectSeries && + _tooltipBehavior.tooltipPosition == TooltipPosition.pointer) { + tooltipPosition = position; + _showLocation = tooltipPosition; + } + _renderBox!.normalPadding = + _seriesRenderer is BubbleSeriesRenderer ? 0 : padding!.dy; + _renderBox!.inversePadding = padding!.dy; + String? header = _tooltipBehavior.header; + header = (header == null) + ? (_tooltipBehavior.shared + ? dataValues[0] + : (isTrendLine! + ? dataValues[dataValues.length - 2] + : _currentSeries._series.name ?? + _currentSeries._seriesName)) + : header; + _renderBox!.header = header ?? ''; + _stringValue = ''; + if (_tooltipBehavior.shared) { + _textValues = []; + _seriesRendererCollection = []; + for (int j = 0; + j < _chartState._chartSeries.visibleSeriesRenderers.length; + j++) { + final CartesianSeriesRenderer seriesRenderer = + _chartState._chartSeries.visibleSeriesRenderers[j]; + if (seriesRenderer._visible! && + seriesRenderer._series.enableTooltip) { + final int index = seriesRenderer._xValues!.indexOf(dataRect[4].x); + if (index > -1) { + final String text = (_stringVal != '' ? '\n' : '') + + _calculateCartesianTooltipText( + seriesRenderer, + seriesRenderer._dataPoints[index], + dataValues, + tooltipPosition!, + outlierTooltip, + outlierTooltipIndex); + _stringValue = _stringVal! + text; + _textValues.add(text); + _seriesRendererCollection.add(seriesRenderer); + } + } + } + } else { + _stringValue = _calculateCartesianTooltipText( + _currentSeries, + dataRect[4], + dataValues, + tooltipPosition!, + outlierTooltip, + outlierTooltipIndex); + } + _showLocation = tooltipPosition; + } else { + _stringValue = null; + if (!_isHovering) { + _presentTooltipValue = _currentTooltipValue = null; + _tooltipBehavior.hide(); + } else { + _mouseTooltip = isContains; + } + } + } + } + + /// It returns the tooltip text of cartesian series + String _calculateCartesianTooltipText( + CartesianSeriesRenderer seriesRenderer, + CartesianChartPoint point, + dynamic values, + Offset tooltipPosition, + bool outlierTooltip, + int outlierTooltipIndex) { + final bool isTrendLine = values[values.length - 1].contains('true'); + String resultantString; + final ChartAxisRenderer axisRenderer = seriesRenderer._yAxisRenderer!; + final tooltip = _tooltipBehavior; + final int digits = seriesRenderer._chart.tooltipBehavior.decimalPlaces; + String? minimumValue, + maximumValue, + lowerQuartileValue, + upperQuartileValue, + medianValue, + meanValue, + outlierValue, + highValue, + lowValue, + openValue, + closeValue, + cumulativeValue, + boxPlotString; + if (seriesRenderer._seriesType == 'boxandwhisker') { + minimumValue = _getLabelValue(point.minimum, axisRenderer._axis, digits); + maximumValue = _getLabelValue(point.maximum, axisRenderer._axis, digits); + lowerQuartileValue = + _getLabelValue(point.lowerQuartile, axisRenderer._axis, digits); + upperQuartileValue = + _getLabelValue(point.upperQuartile, axisRenderer._axis, digits); + medianValue = _getLabelValue(point.median, axisRenderer._axis, digits); + meanValue = _getLabelValue(point.mean, axisRenderer._axis, digits); + outlierValue = (point.outliers!.isNotEmpty && outlierTooltipIndex >= 0) + ? _getLabelValue( + point.outliers![outlierTooltipIndex], axisRenderer._axis, digits) + : null; + boxPlotString = '\nMinimum : ' + + minimumValue! + + '\nMaximum : ' + + maximumValue! + + '\nMedian : ' + + medianValue! + + '\nMean : ' + + meanValue! + + '\nLQ : ' + + lowerQuartileValue! + + '\nHQ : ' + + upperQuartileValue!; + } else if (seriesRenderer._seriesType.contains('range') || + seriesRenderer._seriesType == 'hilo' || + seriesRenderer._seriesType == 'hiloopenclose' || + seriesRenderer._seriesType == 'candle') { + highValue = _getLabelValue(point.high, axisRenderer._axis, digits); + lowValue = _getLabelValue(point.low, axisRenderer._axis, digits); + if (seriesRenderer._seriesType == 'candle' || + seriesRenderer._seriesType == 'hiloopenclose') { + openValue = _getLabelValue(point.open, axisRenderer._axis, digits); + closeValue = _getLabelValue(point.close, axisRenderer._axis, digits); + } + } else if (seriesRenderer._seriesType.contains('stacked')) { + cumulativeValue = + _getLabelValue(point.cumulativeValue, axisRenderer._axis, digits); + } + if (_tooltipBehavior.format != null) { + resultantString = (seriesRenderer._seriesType.contains('range') || + seriesRenderer._seriesType == 'hilo') && + !isTrendLine + ? (tooltip.format! + .replaceAll('point.x', values[0]) + .replaceAll('point.high', highValue!) + .replaceAll('point.low', lowValue!) + .replaceAll('seriesRenderer._series.name', + seriesRenderer._series.name ?? seriesRenderer._seriesName!)) + : (seriesRenderer._seriesType.contains('hiloopenclose') || + seriesRenderer._seriesType.contains('candle')) && + !isTrendLine + ? (tooltip.format! + .replaceAll('point.x', values[0]) + .replaceAll('point.high', highValue!) + .replaceAll('point.low', lowValue!) + .replaceAll('point.open', openValue!) + .replaceAll('point.close', closeValue!) + .replaceAll( + 'seriesRenderer._series.name', + seriesRenderer._series.name ?? + seriesRenderer._seriesName!)) + : (seriesRenderer._seriesType.contains('boxandwhisker')) && + !isTrendLine + ? (tooltip.format! + .replaceAll('point.x', values[0]) + .replaceAll('point.minimum', minimumValue!) + .replaceAll('point.maximum', maximumValue!) + .replaceAll('point.lowerQuartile', lowerQuartileValue!) + .replaceAll('point.upperQuartile', upperQuartileValue!) + .replaceAll('point.mean', meanValue!) + .replaceAll('point.median', medianValue!) + .replaceAll('seriesRenderer._series.name', + seriesRenderer._series.name ?? seriesRenderer._seriesName!)) + : seriesRenderer._seriesType.contains('stacked') + ? tooltip.format!.replaceAll('point.cumulativeValue', cumulativeValue!) + : seriesRenderer._seriesType == 'bubble' + ? tooltip.format!.replaceAll('point.x', values[0]).replaceAll('point.y', _getLabelValue(point.y, axisRenderer._axis, digits)).replaceAll('series.name', seriesRenderer._series.name ?? seriesRenderer._seriesName!).replaceAll('point.size', _getLabelValue(point.bubbleSize, axisRenderer._axis, digits)) + : tooltip.format!.replaceAll('point.x', values[0]).replaceAll('point.y', _getLabelValue(point.y, axisRenderer._axis, digits)).replaceAll('series.name', seriesRenderer._series.name ?? seriesRenderer._seriesName!); + } else { + resultantString = (_tooltipBehavior.shared + ? seriesRenderer._series.name ?? seriesRenderer._seriesName + : values[0]) + + (((seriesRenderer._seriesType.contains('range') || + seriesRenderer._seriesType == 'hilo') && + !isTrendLine) + ? ('\nHigh : ' + highValue! + '\nLow : ' + lowValue!) + : (seriesRenderer._seriesType == 'hiloopenclose' || + seriesRenderer._seriesType == 'candle' + ? ('\nHigh : ' + + highValue! + + '\nLow : ' + + lowValue! + + '\nOpen : ' + + openValue! + + '\nClose : ' + + closeValue!) + : seriesRenderer._seriesType == 'boxandwhisker' + ? outlierValue != null + ? ('\nOutliers : ' + outlierValue) + : boxPlotString + : ' : ' + + _getLabelValue(point.y, axisRenderer._axis, digits))); + } + return resultantString; + } +} + +/// Holds the tooltip series and point index +/// +/// This class is used to provide the [seriesIndex] and [pointIndex] for the Tooltip. +class TooltipValue { + /// Creating an argument constructor of TooltipValue class. + TooltipValue(this.seriesIndex, this.pointIndex, [this.outlierIndex]); + + ///Index of the series. + final int? seriesIndex; + + ///Index of data points. + final int pointIndex; + + ///Index of outlier points. + final int? outlierIndex; } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart index 1b45c415d..7d62fdbb5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart @@ -2,10 +2,10 @@ part of charts; ///Signature for callback reporting that a data label is tapped. /// -///Also refer [onDataLabelTapped] event and [DataLabelTapDetails] class. +///Also refer `onDataLabelTapped` event and [DataLabelTapDetails] class. typedef DataLabelTapCallback = void Function(DataLabelTapDetails onTapArgs); -/// [onDataLabelTapped] event for all series. +/// `onDataLabelTapped` event for all series. void _dataLabelTapEvent(dynamic chart, DataLabelSettings dataLabelSettings, int pointIndex, dynamic point, Offset position, int seriesIndex) { DataLabelTapDetails datalabelArgs; @@ -32,33 +32,34 @@ Color _getSaturationColor(Color color) { /// To get point from data and return point data CartesianChartPoint _getPointFromData( CartesianSeriesRenderer seriesRenderer, int pointIndex) { - final XyDataSeries series = seriesRenderer._series; - final ChartIndexedValueMapper xValue = series.xValueMapper; - final ChartIndexedValueMapper yValue = series.yValueMapper; - final dynamic xVal = xValue(pointIndex); + final XyDataSeries series = + seriesRenderer._series as XyDataSeries; + final ChartIndexedValueMapper? xValue = series.xValueMapper; + final ChartIndexedValueMapper? yValue = series.yValueMapper; + final dynamic xVal = xValue!(pointIndex); final dynamic yVal = (seriesRenderer._seriesType.contains('range') || seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType == 'candle') ? null - : yValue(pointIndex); + : yValue!(pointIndex); final CartesianChartPoint point = CartesianChartPoint(xVal, yVal); if (seriesRenderer._seriesType.contains('range') || seriesRenderer._seriesType.contains('hilo') || seriesRenderer._seriesType == 'candle') { - final ChartIndexedValueMapper highValue = series.highValueMapper; - final ChartIndexedValueMapper lowValue = series.lowValueMapper; - point.high = highValue(pointIndex); - point.low = lowValue(pointIndex); + final ChartIndexedValueMapper? highValue = series.highValueMapper; + final ChartIndexedValueMapper? lowValue = series.lowValueMapper; + point.high = highValue!(pointIndex); + point.low = lowValue!(pointIndex); } if (series is _FinancialSeriesBase) { if (seriesRenderer._seriesType == 'hiloopenclose' || seriesRenderer._seriesType == 'candle') { - final ChartIndexedValueMapper openValue = series.openValueMapper; - final ChartIndexedValueMapper closeValue = series.closeValueMapper; - point.open = openValue(pointIndex); - point.close = closeValue(pointIndex); + final ChartIndexedValueMapper? openValue = series.openValueMapper; + final ChartIndexedValueMapper? closeValue = series.closeValueMapper; + point.open = openValue!(pointIndex); + point.close = closeValue!(pointIndex); } } return point; @@ -66,14 +67,14 @@ CartesianChartPoint _getPointFromData( /// To return textstyle TextStyle _getTextStyle( - {TextStyle textStyle, - Color fontColor, - double fontSize, - FontStyle fontStyle, - String fontFamily, - FontWeight fontWeight, - Paint background, - bool takeFontColorValue}) { + {TextStyle? textStyle, + Color? fontColor, + double? fontSize, + FontStyle? fontStyle, + String? fontFamily, + FontWeight? fontWeight, + Paint? background, + bool? takeFontColorValue}) { if (textStyle != null) { return TextStyle( color: textStyle.color != null && @@ -113,13 +114,13 @@ TextStyle _getTextStyle( } } -Widget _getElements( +Widget? _getElements( dynamic _chartState, Widget chartWidget, BoxConstraints constraints) { final dynamic chart = _chartState._chart; final LegendPosition legendPosition = _chartState._legendRenderer._legendPosition; double legendHeight, legendWidth, chartHeight, chartWidth; - Widget element; + Widget? element; if (_chartState._chartLegend.shouldRenderLegend && chart.legend.isResponsive) { chartHeight = @@ -178,7 +179,7 @@ Widget _getElements( break; } } - return element; + return element!; } Widget _getBottomAndTopLegend( @@ -359,12 +360,12 @@ class _MeasureWidgetSize extends StatelessWidget { this.pointIndex, this.type}); final dynamic chartState; - final Widget currentWidget; - final double opacityValue; - final Key currentKey; - final int seriesIndex; - final int pointIndex; - final String type; + final Widget? currentWidget; + final double? opacityValue; + final Key? currentKey; + final int? seriesIndex; + final int? pointIndex; + final String? type; @override Widget build(BuildContext context) { final List<_MeasureWidgetContext> templates = @@ -377,7 +378,7 @@ class _MeasureWidgetSize extends StatelessWidget { pointIndex: pointIndex)); return Container( key: currentKey, - child: Opacity(opacity: opacityValue, child: currentWidget)); + child: Opacity(opacity: opacityValue!, child: currentWidget)); } } @@ -460,7 +461,7 @@ void _cartesianLegendToggleState( } if (!needSelect) { if (!(currentItem.seriesRenderer is TechnicalIndicators - ? !currentItem.indicatorRenderer._visible + ? !currentItem.indicatorRenderer!._visible! : !currentItem.seriesRenderer._visible && !_chartState._isTrendlineToggled)) { needSelect = false; @@ -483,7 +484,7 @@ void _cartesianLegendToggleState( } /// For checking whether elements collide -bool _findingCollision(Rect rect, List regions, [Rect pathRect]) { +bool _findingCollision(Rect rect, List regions, [Rect? pathRect]) { bool isCollide = false; if (pathRect != null && (pathRect.left < rect.left && @@ -509,13 +510,12 @@ bool _findingCollision(Rect rect, List regions, [Rect pathRect]) { /// To get equivalent value for the percentage num _getValueByPercentage(num value1, num value2) { - num value; - value = value1.isNegative + return (value1.isNegative ? (num.tryParse('-' + - (num.tryParse(value1.toString().replaceAll(RegExp('-'), '')) % value2) - .toString())) - : (value1 % value2); - return value; + (num.tryParse(value1.toString().replaceAll(RegExp('-'), ''))! % + value2) + .toString()))! + : (value1 % value2)); } Widget _renderChartTitle(dynamic _chartState) { @@ -597,7 +597,7 @@ List _bindLegendTemplateWidgets(dynamic widgetState) { } /// To check whether indexes are valid -bool _validIndex(int _pointIndex, int _seriesIndex, dynamic chart) { +bool _validIndex(int? _pointIndex, int? _seriesIndex, dynamic chart) { return _seriesIndex != null && _pointIndex != null && _seriesIndex >= 0 && @@ -608,7 +608,7 @@ bool _validIndex(int _pointIndex, int _seriesIndex, dynamic chart) { //this method removes the given listener from the animation controller and then dsiposes it. void _disposeAnimationController( - AnimationController animationController, VoidCallback listener) { + AnimationController? animationController, VoidCallback listener) { if (animationController != null) { animationController.removeListener(listener); animationController.dispose(); diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart index 24e93ebe4..ad8114d74 100644 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart @@ -33,7 +33,7 @@ typedef FunnelTouchInteractionCallback = void Function( class SfFunnelChart extends StatefulWidget { /// Creating an argument constructor of SfFunnelChart class. SfFunnelChart({ - Key key, + Key? key, this.backgroundColor, this.backgroundImage, this.borderColor = Colors.transparent, @@ -48,10 +48,10 @@ class SfFunnelChart extends StatefulWidget { this.onChartTouchInteractionUp, this.onChartTouchInteractionDown, this.onChartTouchInteractionMove, - ChartTitle title, - FunnelSeries series, - EdgeInsets margin, - Legend legend, + ChartTitle? title, + FunnelSeries? series, + EdgeInsets? margin, + Legend? legend, this.palette = const [ Color.fromRGBO(75, 135, 185, 1), Color.fromRGBO(192, 108, 132, 1), @@ -64,10 +64,10 @@ class SfFunnelChart extends StatefulWidget { Color.fromRGBO(255, 240, 219, 1), Color.fromRGBO(238, 238, 238, 1) ], - TooltipBehavior tooltipBehavior, - SmartLabelMode smartLabelMode, - ActivationMode selectionGesture, - bool enableMultiSelection, + TooltipBehavior? tooltipBehavior, + SmartLabelMode? smartLabelMode, + ActivationMode? selectionGesture, + bool? enableMultiSelection, }) : title = title ?? ChartTitle(), series = series ?? FunnelSeries(), margin = margin ?? const EdgeInsets.fromLTRB(10, 10, 10, 10), @@ -100,7 +100,7 @@ class SfFunnelChart extends StatefulWidget { /// )); ///} ///``` - final Color backgroundColor; + final Color? backgroundColor; ///Background color of the chart /// @@ -208,20 +208,20 @@ class SfFunnelChart extends StatefulWidget { /// args.legendIconType = LegendIconType.diamond; ///} ///``` - final FunnelLegendRenderCallback onLegendItemRender; + final FunnelLegendRenderCallback? onLegendItemRender; /// Occurs while tooltip is rendered. /// /// Here, you can get the Tooltip render arguments and customize them. - final FunnelTooltipCallback onTooltipRender; + final FunnelTooltipCallback? onTooltipRender; /// Occurs when the data label is rendered /// /// Here we can get get the datalabel render arguments and customise the datalabel parameters. - final FunnelDataLabelRenderCallback onDataLabelRender; + final FunnelDataLabelRenderCallback? onDataLabelRender; /// Occurs when the legend is tapped ,using this event the legend tap arguments can be customized. - final ChartLegendTapCallback onLegendTapped; + final ChartLegendTapCallback? onLegendTapped; ///Overlapping of the labels can be avoided by using the smartLabelMode property. /// @@ -305,7 +305,7 @@ class SfFunnelChart extends StatefulWidget { /// )); ///} ///``` - final ImageProvider backgroundImage; + final ImageProvider? backgroundImage; /// Occurs while selection changes. Here, you can get the series, selected color, /// unselected color, selected border color, unselected border color, selected @@ -321,7 +321,7 @@ class SfFunnelChart extends StatefulWidget { /// print(args.selectedBorderColor); ///} ///``` - final FunnelSelectionCallback onSelectionChanged; + final FunnelSelectionCallback? onSelectionChanged; /// Occurs when tapped on the chart area. ///```dart @@ -335,7 +335,7 @@ class SfFunnelChart extends StatefulWidget { /// )); ///} ///``` - final FunnelTouchInteractionCallback onChartTouchInteractionUp; + final FunnelTouchInteractionCallback? onChartTouchInteractionUp; /// Occurs when touched on the chart area. ///```dart @@ -349,7 +349,7 @@ class SfFunnelChart extends StatefulWidget { /// )); ///} ///``` - final FunnelTouchInteractionCallback onChartTouchInteractionDown; + final FunnelTouchInteractionCallback? onChartTouchInteractionDown; /// Occurs when touched and moved on the chart area. ///```dart @@ -363,7 +363,7 @@ class SfFunnelChart extends StatefulWidget { /// )); ///} ///``` - final FunnelTouchInteractionCallback onChartTouchInteractionMove; + final FunnelTouchInteractionCallback? onChartTouchInteractionMove; /// Occurs when tapping a series point. Here, you can get the series, series index /// and point index. @@ -378,7 +378,7 @@ class SfFunnelChart extends StatefulWidget { /// print(args.pointIndex); ///} ///``` - final FunnelPointTapCallback onPointTapped; + final FunnelPointTapCallback? onPointTapped; //Called when the data label is tapped. /// @@ -400,7 +400,7 @@ class SfFunnelChart extends StatefulWidget { /// ///``` - final DataLabelTapCallback onDataLabelTapped; + final DataLabelTapCallback? onDataLabelTapped; @override State createState() => SfFunnelChartState(); @@ -412,56 +412,72 @@ class SfFunnelChartState extends State with TickerProviderStateMixin { // Holds the animation controller list for all series //ignore: unused_field - List _controllerList; - AnimationController + late List _controllerList; + late AnimationController _animationController; // Animation controller for Annotations //ignore: unused_field - AnimationController _annotationController; - ValueNotifier _seriesRepaintNotifier; - List<_MeasureWidgetContext> + late AnimationController _annotationController; + late ValueNotifier _seriesRepaintNotifier; + late List<_MeasureWidgetContext> _legendWidgetContext; // To measure legend size and position - List<_ChartTemplateInfo> _templates; // Chart Template info - List _chartWidgets; + late List<_ChartTemplateInfo> _templates; // Chart Template info + late List _chartWidgets; /// Holds the information of chart theme arguments - SfChartThemeData _chartTheme; + late SfChartThemeData _chartTheme; //Here, we are using get keyword inorder to get the proper & updated instance of chart widget //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. SfFunnelChart get _chart => widget; - Rect _chartContainerRect; - Rect _chartAreaRect; - _ChartTemplate _chartTemplate; - _ChartInteraction _currentActive; - bool _initialRender; - List<_LegendRenderContext> _legendToggleStates; - List<_MeasureWidgetContext> _legendToggleTemplateStates; - bool _isLegendToggled; - Offset _tapPosition; - bool _animateCompleted; + late Rect _chartContainerRect; + late Rect _chartAreaRect; + _ChartTemplate? _chartTemplate; + _ChartInteraction? _currentActive; + bool? _initialRender; + late List<_LegendRenderContext> _legendToggleStates; + late List<_MeasureWidgetContext> _legendToggleTemplateStates; + late bool _isLegendToggled; + Offset? _tapPosition; + late bool _animateCompleted; //ignore: unused_field - Animation _chartElementAnimation; - _FunnelDataLabelRenderer _renderDataLabel; - bool _widgetNeedUpdate; - List _explodedPoints; - List _dataLabelTemplateRegions; - List _selectionData; - int _tooltipPointIndex; + late Animation _chartElementAnimation; + _FunnelDataLabelRenderer? _renderDataLabel; + late bool _widgetNeedUpdate; + late List _explodedPoints; + late List _dataLabelTemplateRegions; + late List _selectionData; + int? _tooltipPointIndex; //ignore: unused_field - FunnelSeriesRenderer _seriesRenderer; - Orientation _oldDeviceOrientation; - Orientation _deviceOrientation; - Size _prevSize; + late FunnelSeriesRenderer _seriesRenderer; + Orientation? _oldDeviceOrientation; + late Orientation _deviceOrientation; + Size? _prevSize; bool _didSizeChange = false; //Internal variables - String _seriesType; - List> _dataPoints; - List> _renderPoints; - _FunnelSeries _chartSeries; - _ChartLegend _chartLegend; + late String _seriesType; + late List> _dataPoints; + late List> _renderPoints; + late _FunnelSeries _chartSeries; + late _ChartLegend _chartLegend; //ignore: unused_field - _FunnelPlotArea _funnelplotArea; - TooltipBehaviorRenderer _tooltipBehaviorRenderer; - LegendRenderer _legendRenderer; + late _FunnelPlotArea _funnelplotArea; + late TooltipBehaviorRenderer _tooltipBehaviorRenderer; + late LegendRenderer _legendRenderer; + + // ignore: unused_element + bool get _animationCompleted { + return _animationController.status != AnimationStatus.forward; + } + + /// Called when this object is inserted into the tree. + /// + /// The framework will call this method exactly once for each State object it creates. + /// + /// Override this method to perform initialization that depends on the location at + /// which this object was inserted into the tree or on the widget used to configure this object. + /// + /// * In [initState], subscribe to the object. + /// + /// Here it overrides to initialize the object that depends on rendering the [SfFunnelChart]. @override void initState() { @@ -471,6 +487,15 @@ class SfFunnelChartState extends State super.initState(); } + /// Called when a dependency of this [State] object changes. + /// + /// For example, if the previous call to [build] referenced an [InheritedWidget] that later changed, + /// the framework would call this method to notify this object about the change. + /// + /// This method is also called immediately after [initState]. It is safe to call [BuildContext.dependOnInheritedWidgetOfExactType] from this method. + /// + /// Here it called for initializing the chart theme of [SfFunnelChart]. + @override void didChangeDependencies() { _chartTheme = SfChartTheme.of(context); @@ -478,6 +503,19 @@ class SfFunnelChartState extends State super.didChangeDependencies(); } + /// Called whenever the widget configuration changes. + /// + /// If the parent widget rebuilds and request that this location in the tree update to display a new widget with the same [runtimeType] and [Widget.key], + /// the framework will update the widget property of this [State] object to refer to the new widget and then call this method with the previous widget as an argument. + /// + /// Override this method to respond when the widget changes. + /// + /// The framework always calls [build] after calling [didUpdateWidget], which means any calls to [setState] in [didUpdateWidget] are redundant. + /// + /// * In [didUpdateWidget] unsubscribe from the old object and subscribe to the new one if the updated widget configuration requires replacing the object. + /// + /// Here it called whenever the series collection gets updated in [SfFunnelChart]. + @override void didUpdateWidget(SfFunnelChart oldWidget) { //Update and maintain the series state, when we update the series in the series collection // @@ -485,10 +523,24 @@ class SfFunnelChartState extends State _initialRender = !widget.series.explode; super.didUpdateWidget(oldWidget); + if (_tooltipBehaviorRenderer._chartTooltipState != null) { + _tooltipBehaviorRenderer._show = false; + } _isLegendToggled = false; _widgetNeedUpdate = true; } + /// Describes the part of the user interface represented by this widget. + /// + /// The framework calls this method in a number of different situations. For example: + /// + /// * After calling [initState]. + /// * After calling [didUpdateWidget]. + /// * After receiving a call to [setState]. + /// * After a dependency of this [State] object changes. + /// + /// Here it is called whenever the user interaction is performed and it removes the old widget and updates a chart with a new widget in [SfFunnelChart]. + @override Widget build(BuildContext context) { _prevSize = _prevSize ?? MediaQuery.of(context).size; @@ -506,7 +558,8 @@ class SfFunnelChartState extends State color: widget.backgroundColor, image: widget.backgroundImage != null ? DecorationImage( - image: widget.backgroundImage, fit: BoxFit.fill) + image: widget.backgroundImage!, + fit: BoxFit.fill) : null, border: Border.all( color: widget.borderColor, @@ -519,6 +572,18 @@ class SfFunnelChartState extends State ))))); } + /// Called when this object is removed from the tree permanently. + /// + /// The framework calls this method when this [State] object will never build again. After the framework calls [dispose], + /// the [State] object is considered unmounted and the [mounted] property is false. It is an error to call [setState] at this + /// point. This stage of the lifecycle is terminal: there is no way to remount a [State] object that has been disposed. + /// + /// Subclasses should override this method to release any resources retained by this object. + /// + /// * In [dispose], unsubscribe from the object. + /// + /// Here it end the animation controller of the series in [SfFunnelChart]. + @override void dispose() { _disposeAnimationController(_animationController, _repaintChartElements); @@ -527,7 +592,7 @@ class SfFunnelChartState extends State /// Method to convert the [SfFunnelChart] as an image. /// - /// Returns the [dart:ui.image] + /// Returns the `dart:ui.image` /// /// As this method is in the widget’s state class, you have to use a global key /// to access the state to call this method. @@ -584,8 +649,8 @@ class SfFunnelChartState extends State ///``` Future toImage({double pixelRatio = 1.0}) async { - final RenderRepaintBoundary boundary = - context.findRenderObject(); //get the render object from context + final RenderRepaintBoundary boundary = context.findRenderObject() + as RenderRepaintBoundary; //get the render object from context final dart_ui.Image image = await boundary.toImage(pixelRatio: pixelRatio); // Convert // the repaint boundary as image @@ -617,9 +682,9 @@ class SfFunnelChartState extends State } // In this method, create and update the series renderer for each series // - void _createAndUpdateSeriesRenderer([SfFunnelChart oldWidget]) { + void _createAndUpdateSeriesRenderer([SfFunnelChart? oldWidget]) { if (widget.series != null) { - final FunnelSeriesRenderer oldSeriesRenderer = + final FunnelSeriesRenderer? oldSeriesRenderer = oldWidget != null && oldWidget.series != null ? _chartSeries.visibleSeriesRenderers[0] : null; @@ -631,7 +696,7 @@ class SfFunnelChartState extends State FunnelSeriesRenderer seriesRenderers; if (oldSeriesRenderer != null && - _isSameSeries(oldWidget.series, series)) { + _isSameSeries(oldWidget!.series, series)) { seriesRenderers = oldSeriesRenderer; } else { seriesRenderers = series.createRenderer(series); @@ -668,11 +733,11 @@ class SfFunnelChartState extends State final List legendTemplates = _bindLegendTemplateWidgets(this); if (legendTemplates.isNotEmpty && _legendWidgetContext.isEmpty) { element = Container(child: Stack(children: legendTemplates)); - SchedulerBinding.instance.addPostFrameCallback((_) => _refresh()); + SchedulerBinding.instance!.addPostFrameCallback((_) => _refresh()); } else { _chartLegend._calculateLegendBounds(_chartLegend.chartSize); element = _getElements( - this, _FunnelPlotArea(chartState: this), constraints); + this, _FunnelPlotArea(chartState: this), constraints)!; } } else { element = Container(); @@ -686,7 +751,8 @@ class SfFunnelChartState extends State if (_legendWidgetContext.isNotEmpty) { for (int i = 0; i < _legendWidgetContext.length; i++) { final _MeasureWidgetContext templateContext = _legendWidgetContext[i]; - final RenderBox renderBox = templateContext.context.findRenderObject(); + final RenderBox renderBox = + templateContext.context!.findRenderObject() as RenderBox; templateContext.size = renderBox.size; } setState(() { @@ -699,6 +765,9 @@ class SfFunnelChartState extends State // ignore:unused_element void _redraw() { _initialRender = false; + if (_tooltipBehaviorRenderer._chartTooltipState != null) { + _tooltipBehaviorRenderer._show = false; + } setState(() { /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. }); @@ -724,16 +793,18 @@ class SfFunnelChartState extends State // ignore: must_be_immutable class _FunnelPlotArea extends StatelessWidget { // ignore: prefer_const_constructors_in_immutables - _FunnelPlotArea({this.chartState}); + _FunnelPlotArea({required this.chartState}); final SfFunnelChartState chartState; //Here, we are using get keyword inorder to get the proper & updated instance of chart widget //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. SfFunnelChart get chart => chartState._chart; - FunnelSeriesRenderer seriesRenderer; - RenderBox renderBox; - _Region pointRegion; - TapDownDetails tapDownDetails; - Offset doubleTapPosition; + late FunnelSeriesRenderer seriesRenderer; + late RenderBox renderBox; + _Region? pointRegion; + late TapDownDetails tapDownDetails; + Offset? doubleTapPosition; + bool _enableMouseHover = + kIsWeb || Platform.isLinux || Platform.isMacOS || Platform.isWindows; @override Widget build(BuildContext context) { @@ -741,7 +812,10 @@ class _FunnelPlotArea extends StatelessWidget { builder: (BuildContext context, BoxConstraints constraints) { return Container( child: MouseRegion( - onHover: (PointerEvent event) => _onHover(event), + // Using the _enableMouseHover property, prevented mouse hover function in mobile platforms. The mouse hover event should not be triggered for mobile platforms and logged an issue regarding this to the Flutter team. + // Issue: https://github.com/flutter/flutter/issues/68690 + onHover: (PointerEvent event) => + _enableMouseHover ? _onHover(event) : null, onExit: (PointerEvent event) { chartState._tooltipBehaviorRenderer._isHovering = false; }, @@ -756,6 +830,15 @@ class _FunnelPlotArea extends StatelessWidget { child: GestureDetector( onLongPress: _onLongPress, onDoubleTap: _onDoubleTap, + onTapUp: (TapUpDetails details) { + chartState._tapPosition = + renderBox.globalToLocal(details.globalPosition); + if (chart.onPointTapped != null && + seriesRenderer != null) { + _calculatePointSeriesIndex(chart, seriesRenderer, + chartState._tapPosition!); + } + }, child: Container( decoration: const BoxDecoration(color: Colors.transparent), @@ -777,7 +860,8 @@ class _FunnelPlotArea extends StatelessWidget { void _calculateContainerSize(BoxConstraints constraints) { final num width = constraints.maxWidth; final num height = constraints.maxHeight; - chartState._chartContainerRect = Rect.fromLTWH(0, 0, width, height); + chartState._chartContainerRect = + Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); final EdgeInsets margin = chart.margin; chartState._chartAreaRect = Rect.fromLTWH( margin.left, @@ -792,7 +876,7 @@ class _FunnelPlotArea extends StatelessWidget { _findTemplates(chartState); _renderTemplates(chartState); _bindTooltipWidgets(constraints); - renderBox = context.findRenderObject(); + renderBox = context.findRenderObject() as RenderBox; chartState._funnelplotArea = this; return Container(child: Stack(children: chartState._chartWidgets)); } @@ -813,7 +897,7 @@ class _FunnelPlotArea extends StatelessWidget { /// To bind series widgets in chart void _bindSeriesWidgets() { CustomPainter seriesPainter; - Animation seriesAnimation; + Animation? seriesAnimation; FunnelSeries series; for (int i = 0; i < chartState._chartSeries.visibleSeriesRenderers.length; @@ -831,8 +915,8 @@ class _FunnelPlotArea extends StatelessWidget { selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer = SelectionBehaviorRenderer(selectionBehavior, chart, chartState); selectionBehaviorRenderer._selectionRenderer ??= _SelectionRenderer(); - selectionBehaviorRenderer._selectionRenderer.chart = chart; - selectionBehaviorRenderer._selectionRenderer.seriesRenderer = + selectionBehaviorRenderer._selectionRenderer?.chart = chart; + selectionBehaviorRenderer._selectionRenderer?.seriesRenderer = seriesRenderer; if (series.initialSelectedDataIndexes.isNotEmpty) { for (int index = 0; @@ -846,7 +930,7 @@ class _FunnelPlotArea extends StatelessWidget { if (series.animationDuration > 0 && !chartState._didSizeChange && (chartState._oldDeviceOrientation == chartState._deviceOrientation) && - ((!chartState._widgetNeedUpdate && chartState._initialRender) || + ((!chartState._widgetNeedUpdate && chartState._initialRender!) || chartState._isLegendToggled)) { chartState._animationController.duration = Duration(milliseconds: series.animationDuration.toInt()); @@ -858,11 +942,11 @@ class _FunnelPlotArea extends StatelessWidget { if (status == AnimationStatus.completed) { chartState._animateCompleted = true; if (chartState._renderDataLabel != null) { - chartState._renderDataLabel.state.render(); + chartState._renderDataLabel!.state!.render(); } if (chartState._chartTemplate != null && - chartState._chartTemplate.state != null) { - chartState._chartTemplate.state.templateRender(); + chartState._chartTemplate!.state != null) { + chartState._chartTemplate!.state.templateRender(); } } })); @@ -875,7 +959,7 @@ class _FunnelPlotArea extends StatelessWidget { } else { chartState._animateCompleted = true; if (chartState._renderDataLabel?.state != null) { - chartState._renderDataLabel.state.render(); + chartState._renderDataLabel?.state!.render(); } } seriesRenderer._repaintNotifier = chartState._seriesRepaintNotifier; @@ -889,51 +973,67 @@ class _FunnelPlotArea extends StatelessWidget { chartState._chartWidgets .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); chartState._renderDataLabel = _FunnelDataLabelRenderer( + key: GlobalKey(), chartState: chartState, show: !chartState._widgetNeedUpdate ? chartState._animationController.status == AnimationStatus.completed || chartState._animationController.duration == null : true); - chartState._chartWidgets.add(chartState._renderDataLabel); + chartState._chartWidgets.add(chartState._renderDataLabel!); } } /// To bind tooltip widgets to chart void _bindTooltipWidgets(BoxConstraints constraints) { chart.tooltipBehavior._chartState = chartState; + final SfChartThemeData _chartTheme = chartState._chartTheme; + final tooltip = chart.tooltipBehavior; if (chart.tooltipBehavior.enable) { - if (chart.tooltipBehavior.builder != null) { - chartState._tooltipBehaviorRenderer._tooltipTemplate = _TooltipTemplate( - show: false, - clipRect: chartState._chartContainerRect, - tooltipBehavior: chart.tooltipBehavior, - duration: chart.tooltipBehavior.duration, - chartState: chartState); - chartState._chartWidgets - .add(chartState._tooltipBehaviorRenderer._tooltipTemplate); - } else { - chartState._tooltipBehaviorRenderer._chartTooltip = - _ChartTooltipRenderer(chartState: chartState); - chartState._chartWidgets - .add(chartState._tooltipBehaviorRenderer._chartTooltip); - } + chartState._tooltipBehaviorRenderer._prevTooltipValue = + chartState._tooltipBehaviorRenderer._currentTooltipValue = null; + chartState._tooltipBehaviorRenderer._chartTooltip = SfTooltip( + color: tooltip.color ?? _chartTheme.tooltipColor, + key: GlobalKey(), + textStyle: tooltip.textStyle, + animationDuration: tooltip.animationDuration, + enable: tooltip.enable, + opacity: tooltip.opacity, + borderColor: tooltip.borderColor, + borderWidth: tooltip.borderWidth, + duration: tooltip.duration, + shouldAlwaysShow: tooltip.shouldAlwaysShow, + elevation: tooltip.elevation, + canShowMarker: tooltip.canShowMarker, + textAlignment: tooltip.textAlignment, + decimalPlaces: tooltip.decimalPlaces, + labelColor: tooltip.textStyle.color ?? _chartTheme.tooltipLabelColor, + header: tooltip.header, + format: tooltip.format, + builder: tooltip.builder, + shadowColor: tooltip.shadowColor, + onTooltipRender: chart.onTooltipRender != null + ? chartState._tooltipBehaviorRenderer._tooltipRenderingEvent + : null); + chartState._chartWidgets + .add(chartState._tooltipBehaviorRenderer._chartTooltip!); } } void _calculatePointSeriesIndex(SfFunnelChart chart, FunnelSeriesRenderer seriesRenderer, Offset touchPosition) { PointTapArgs pointTapArgs; - num index; + int? index; for (int i = 0; i < seriesRenderer._renderPoints.length; i++) { - if (seriesRenderer._renderPoints[i].region.contains(touchPosition)) { + if (seriesRenderer._renderPoints[i].region != null && + seriesRenderer._renderPoints[i].region!.contains(touchPosition)) { index = i; break; } } if (index != null) { pointTapArgs = PointTapArgs(0, index, seriesRenderer._dataPoints, index); - chart.onPointTapped(pointTapArgs); + chart.onPointTapped!(pointTapArgs); } } @@ -943,42 +1043,46 @@ class _FunnelPlotArea extends StatelessWidget { chartState._tooltipBehaviorRenderer._isHovering = false; chartState._currentActive = null; chartState._tapPosition = renderBox.globalToLocal(event.position); - bool isPoint; - const num seriesIndex = 0; - num pointIndex; + bool isPoint = false; + const int seriesIndex = 0; + int? pointIndex; final FunnelSeriesRenderer seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; ChartTouchInteractionArgs touchArgs; for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { - if (seriesRenderer._renderPoints[j].isVisible) { + if (chart.onDataLabelRender != null) { + seriesRenderer._dataPoints[j].labelRenderEvent = false; + } + if (seriesRenderer._renderPoints[j].isVisible && !isPoint) { isPoint = _isPointInPolygon(seriesRenderer._renderPoints[j].pathRegion, chartState._tapPosition); if (isPoint) { pointIndex = j; - break; + if (chart.onDataLabelRender == null) { + break; + } } } } - doubleTapPosition = chartState._tapPosition; + doubleTapPosition = chartState._tapPosition!; if (chartState._tapPosition != null && isPoint != null && isPoint) { chartState._currentActive = _ChartInteraction( seriesIndex, - pointIndex, + pointIndex!, seriesRenderer._series, seriesRenderer._renderPoints[pointIndex], ); } else { //hides the tooltip if the point of interaction is outside funnel region of the chart - if (chart.tooltipBehavior?.builder != null) { - chartState._tooltipBehaviorRenderer._tooltipTemplate.show = false; - chartState._tooltipBehaviorRenderer?._tooltipTemplate?.state - ?.hideOnTimer(); + if (chart.tooltipBehavior.builder != null) { + chartState._tooltipBehaviorRenderer._show = false; + chartState._tooltipBehaviorRenderer._hideOnTimer(); } } if (chart.onChartTouchInteractionDown != null) { touchArgs = ChartTouchInteractionArgs(); touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionDown(touchArgs); + chart.onChartTouchInteractionDown!(touchArgs); } } @@ -989,25 +1093,29 @@ class _FunnelPlotArea extends StatelessWidget { if (chart.onChartTouchInteractionMove != null) { touchArgs = ChartTouchInteractionArgs(); touchArgs.position = position; - chart.onChartTouchInteractionMove(touchArgs); + chart.onChartTouchInteractionMove!(touchArgs); } } /// To perform double tap touch interactions void _onDoubleTap() { - const num seriesIndex = 0; + const int seriesIndex = 0; if (doubleTapPosition != null && chartState._currentActive != null) { - final num pointIndex = chartState._currentActive.pointIndex; + final int? pointIndex = chartState._currentActive!.pointIndex; chartState._currentActive = _ChartInteraction( seriesIndex, pointIndex, chartState._chartSeries.visibleSeriesRenderers[seriesIndex]._series, chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._renderPoints[pointIndex]); + ._renderPoints[pointIndex!]); if (chartState._currentActive != null) { - if (chartState._currentActive.series.explodeGesture == + if (chartState._currentActive!.series.explodeGesture == ActivationMode.doubleTap) { chartState._chartSeries._pointExplode(pointIndex); + final GlobalKey key = chartState._renderDataLabel!.key as GlobalKey; + final _FunnelDataLabelRendererState _funnelDataLabelRendererState = + key.currentState as _FunnelDataLabelRendererState; + _funnelDataLabelRendererState.dataLabelRepaintNotifier.value++; } } chartState._chartSeries @@ -1019,7 +1127,8 @@ class _FunnelPlotArea extends StatelessWidget { _showFunnelTooltipTemplate(); } else { chartState._tooltipBehaviorRenderer.onDoubleTap( - doubleTapPosition.dx.toDouble(), doubleTapPosition.dy.toDouble()); + doubleTapPosition!.dx.toDouble(), + doubleTapPosition!.dy.toDouble()); } } } @@ -1027,9 +1136,9 @@ class _FunnelPlotArea extends StatelessWidget { /// To perform long press touch interactions void _onLongPress() { - const num seriesIndex = 0; + const int seriesIndex = 0; if (chartState._tapPosition != null && chartState._currentActive != null) { - final num pointIndex = chartState._currentActive.pointIndex; + final int pointIndex = chartState._currentActive!.pointIndex!; chartState._currentActive = _ChartInteraction( seriesIndex, pointIndex, @@ -1040,9 +1149,13 @@ class _FunnelPlotArea extends StatelessWidget { chartState._chartSeries ._seriesPointSelection(pointIndex, ActivationMode.longPress); if (chartState._currentActive != null) { - if (chartState._currentActive.series.explodeGesture == + if (chartState._currentActive!.series.explodeGesture == ActivationMode.longPress) { chartState._chartSeries._pointExplode(pointIndex); + final GlobalKey key = chartState._renderDataLabel!.key as GlobalKey; + final _FunnelDataLabelRendererState _funnelDataLabelRendererState = + key.currentState as _FunnelDataLabelRendererState; + _funnelDataLabelRendererState.dataLabelRepaintNotifier.value++; } } if (chart.tooltipBehavior.enable && @@ -1052,8 +1165,8 @@ class _FunnelPlotArea extends StatelessWidget { _showFunnelTooltipTemplate(); } else { chartState._tooltipBehaviorRenderer.onLongPress( - chartState._tapPosition.dx.toDouble(), - chartState._tapPosition.dy.toDouble()); + chartState._tapPosition!.dx.toDouble(), + chartState._tapPosition!.dy.toDouble()); } } } @@ -1064,32 +1177,32 @@ class _FunnelPlotArea extends StatelessWidget { chartState._tooltipBehaviorRenderer._isHovering = false; chartState._tapPosition = renderBox.globalToLocal(event.position); ChartTouchInteractionArgs touchArgs; - if (chart.onPointTapped != null && seriesRenderer != null) { - _calculatePointSeriesIndex( - chart, seriesRenderer, chartState._tapPosition); - } if (chart.onDataLabelTapped != null && seriesRenderer != null) { _triggerFunnelDataLabelEvent( - chart, seriesRenderer, chartState, chartState._tapPosition); + chart, seriesRenderer, chartState, chartState._tapPosition!); } if (chartState._tapPosition != null) { if (chartState._currentActive != null && - chartState._currentActive.series != null && - chartState._currentActive.series.explodeGesture == + chartState._currentActive!.series != null && + chartState._currentActive!.series.explodeGesture == ActivationMode.singleTap) { chartState._chartSeries - ._pointExplode(chartState._currentActive.pointIndex); + ._pointExplode(chartState._currentActive!.pointIndex!); + final GlobalKey key = chartState._renderDataLabel!.key as GlobalKey; + final _FunnelDataLabelRendererState _funnelDataLabelRendererState = + key.currentState as _FunnelDataLabelRendererState; + _funnelDataLabelRendererState.dataLabelRepaintNotifier.value++; } if (chartState._tapPosition != null && chartState._currentActive != null) { chartState._chartSeries._seriesPointSelection( - chartState._currentActive.pointIndex, ActivationMode.singleTap); + chartState._currentActive!.pointIndex!, ActivationMode.singleTap); } if (chart.tooltipBehavior.enable && chartState._animateCompleted && chart.tooltipBehavior.activationMode == ActivationMode.singleTap && chartState._currentActive != null && - chartState._currentActive.series != null) { + chartState._currentActive!.series != null) { if (chart.tooltipBehavior.builder != null) { _showFunnelTooltipTemplate(); } else { @@ -1101,7 +1214,7 @@ class _FunnelPlotArea extends StatelessWidget { if (chart.onChartTouchInteractionUp != null) { touchArgs = ChartTouchInteractionArgs(); touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionUp(touchArgs); + chart.onChartTouchInteractionUp!(touchArgs); } } chartState._tapPosition = null; @@ -1111,9 +1224,9 @@ class _FunnelPlotArea extends StatelessWidget { void _onHover(PointerEvent event) { chartState._currentActive = null; chartState._tapPosition = renderBox.globalToLocal(event.position); - bool isPoint; - const num seriesIndex = 0; - num pointIndex; + bool? isPoint; + const int seriesIndex = 0; + int? pointIndex; final FunnelSeriesRenderer seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { @@ -1126,25 +1239,23 @@ class _FunnelPlotArea extends StatelessWidget { } } } - if (chartState._tapPosition != null && isPoint) { + if (chartState._tapPosition != null && isPoint!) { chartState._currentActive = _ChartInteraction( seriesIndex, - pointIndex, + pointIndex!, chartState._chartSeries.visibleSeriesRenderers[seriesIndex]._series, chartState._chartSeries.visibleSeriesRenderers[seriesIndex] ._renderPoints[pointIndex], ); } else { //hides the tooltip if the point of interaction is outside funnel region of the chart - chartState._tooltipBehaviorRenderer?._tooltipTemplate?.show = false; - chartState._tooltipBehaviorRenderer?._tooltipTemplate?.state - ?.hideOnTimer(); + chartState._tooltipBehaviorRenderer._hide(); } if (chartState._tapPosition != null) { if (chart.tooltipBehavior.enable && chartState._animateCompleted && chartState._currentActive != null && - chartState._currentActive.series != null) { + chartState._currentActive!.series != null) { chartState._tooltipBehaviorRenderer._isHovering = true; if (chart.tooltipBehavior.builder != null) { _showFunnelTooltipTemplate(); @@ -1154,24 +1265,19 @@ class _FunnelPlotArea extends StatelessWidget { .onEnter(position.dx.toDouble(), position.dy.toDouble()); } } else { - chartState?._tooltipBehaviorRenderer?._painter?.prevTooltipValue = null; - chartState?._tooltipBehaviorRenderer?._painter?.currentTooltipValue = - null; - chartState?._tooltipBehaviorRenderer?._painter?.hide(); + chartState._tooltipBehaviorRenderer._prevTooltipValue = null; + chartState._tooltipBehaviorRenderer._currentTooltipValue = null; } } chartState._tapPosition = null; } /// This method gets executed for showing tooltip when builder is provided in behavior - void _showFunnelTooltipTemplate([int pointIndex]) { - final _TooltipTemplate tooltipTemplate = - chartState._tooltipBehaviorRenderer._tooltipTemplate; - tooltipTemplate?._alwaysShow = chart.tooltipBehavior.shouldAlwaysShow; + void _showFunnelTooltipTemplate([int? pointIndex]) { if (!chartState._tooltipBehaviorRenderer._isHovering) { //assingning null for the previous and current tooltip values in case of touch interaction - tooltipTemplate?.state?.prevTooltipValue = null; - tooltipTemplate?.state?.currentTooltipValue = null; + chartState._tooltipBehaviorRenderer._prevTooltipValue = null; + chartState._tooltipBehaviorRenderer._currentTooltipValue = null; } final FunnelSeries chartSeries = chartState._currentActive?.series ?? chart.series; @@ -1179,25 +1285,48 @@ class _FunnelPlotArea extends StatelessWidget { ? chartState._currentActive?.point : chartState ._chartSeries.visibleSeriesRenderers[0]._dataPoints[pointIndex]; - final Offset location = point.symbolLocation; - if (location != null && (chartSeries.enableTooltip ?? true)) { - tooltipTemplate.rect = Rect.fromLTWH(location.dx, location.dy, 0, 0); - tooltipTemplate.template = chart.tooltipBehavior.builder( - chartSeries - .dataSource[pointIndex ?? chartState._currentActive?.pointIndex], - point, - chartSeries, - 0, - pointIndex ?? chartState._currentActive?.pointIndex); + final Offset location = chart.tooltipBehavior.tooltipPosition == + TooltipPosition.pointer && + !chartState._chartSeries.visibleSeriesRenderers[0]._series.explode + ? chartState._tapPosition! + : point.symbolLocation; + bool isPoint = false; + for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { + if (seriesRenderer._renderPoints[j].isVisible) { + isPoint = _isPointInPolygon( + seriesRenderer._renderPoints[j].pathRegion, location); + if (isPoint) { + pointIndex = j; + break; + } + } + } + if (location != null && isPoint && (chartSeries.enableTooltip)) { + chartState._tooltipBehaviorRenderer._showLocation = location; + chartState._tooltipBehaviorRenderer._renderBox?.boundaryRect = + chartState._chartContainerRect; + // tooltipTemplate.rect = Rect.fromLTWH(location.dx, location.dy, 0, 0); + chartState._tooltipBehaviorRenderer._tooltipTemplate = + chart.tooltipBehavior.builder!( + chartSeries.dataSource![ + pointIndex ?? chartState._currentActive!.pointIndex!], + point, + chartSeries, + 0, + pointIndex ?? chartState._currentActive!.pointIndex!); if (chartState._tooltipBehaviorRenderer._isHovering) { //assingning values for the previous and current tooltip values on mouse hover - tooltipTemplate.state.prevTooltipValue = - tooltipTemplate.state.currentTooltipValue; - tooltipTemplate.state.currentTooltipValue = TooltipValue( - 0, pointIndex ?? chartState._currentActive?.pointIndex); + chartState._tooltipBehaviorRenderer._prevTooltipValue = + chartState._tooltipBehaviorRenderer._currentTooltipValue; + chartState._tooltipBehaviorRenderer._currentTooltipValue = TooltipValue( + 0, pointIndex ?? chartState._currentActive!.pointIndex!); + } else { + chartState._tooltipBehaviorRenderer._timer = Timer( + Duration(milliseconds: chart.tooltipBehavior.duration.toInt()), + chartState._tooltipBehaviorRenderer._hideTooltipTemplate); } - tooltipTemplate.show = true; - tooltipTemplate?.state?._performTooltip(); + chartState._tooltipBehaviorRenderer._show = true; + chartState._tooltipBehaviorRenderer.._performTooltip(); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart index 9ba6bed1e..a4cee8685 100644 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart @@ -5,10 +5,10 @@ class _FunnelSeries { final SfFunnelChartState _chartState; - _FunnelSeriesBase currentSeries; + late _FunnelSeriesBase currentSeries; List visibleSeriesRenderers = []; - SelectionArgs _selectionArgs; + SelectionArgs? _selectionArgs; /// To find the visible series void _findVisibleSeries() { @@ -21,15 +21,15 @@ class _FunnelSeries { currentSeries = seriesRenderer._series; //Setting series type seriesRenderer._seriesType = 'funnel'; - final ChartIndexedValueMapper xValue = currentSeries.xValueMapper; - final ChartIndexedValueMapper yValue = currentSeries.yValueMapper; + final ChartIndexedValueMapper? xValue = currentSeries.xValueMapper; + final ChartIndexedValueMapper? yValue = currentSeries.yValueMapper; for (int pointIndex = 0; - pointIndex < currentSeries.dataSource.length; + pointIndex < currentSeries.dataSource!.length; pointIndex++) { - if (xValue(pointIndex) != null) { + if (xValue!(pointIndex) != null) { seriesRenderer._dataPoints - .add(PointInfo(xValue(pointIndex), yValue(pointIndex))); + .add(PointInfo(xValue(pointIndex), yValue!(pointIndex))); } } visibleSeriesRenderers @@ -71,10 +71,10 @@ class _FunnelSeries { void _setPointStyle(FunnelSeriesRenderer seriesRenderer) { currentSeries = seriesRenderer._series; final List palette = _chartState._chart.palette; - final ChartIndexedValueMapper pointColor = + final ChartIndexedValueMapper? pointColor = currentSeries.pointColorMapper; final EmptyPointSettings empty = currentSeries.emptyPointSettings; - final ChartIndexedValueMapper textMapping = + final ChartIndexedValueMapper? textMapping = currentSeries.textFieldMapper; final List> points = seriesRenderer._renderPoints; for (int i = 0; i < points.length; i++) { @@ -82,7 +82,7 @@ class _FunnelSeries { currentPoint = points[i]; currentPoint.fill = currentPoint.isEmpty && empty.color != null ? empty.color - : pointColor(i) ?? palette[i % palette.length]; + : pointColor!(i) ?? palette[i % palette.length]; currentPoint.color = currentPoint.fill; currentPoint.borderColor = currentPoint.isEmpty && empty.borderColor != null @@ -98,8 +98,8 @@ class _FunnelSeries { currentPoint.text = currentPoint.text ?? (textMapping != null - ? textMapping(i) ?? currentPoint.y.toString() - : currentPoint.y.toString()); + ? textMapping(i) ?? currentPoint.y!.toString() + : currentPoint.y!.toString()); if (_chartState._chart.legend.legendItemBuilder != null) { final List<_MeasureWidgetContext> legendToggles = @@ -133,7 +133,7 @@ class _FunnelSeries { seriesRenderer._sumOfPoints = 0; for (final PointInfo point in seriesRenderer._renderPoints) { if (point.isVisible) { - seriesRenderer._sumOfPoints += point.y.abs(); + seriesRenderer._sumOfPoints += point.y!.abs(); } } } @@ -144,19 +144,19 @@ class _FunnelSeries { final FunnelSeries series = seriesRenderer._series; final bool reverse = seriesRenderer._seriesType == 'pyramid' ? true : false; seriesRenderer._triangleSize = Size( - _percentToValue(series.width, chartAreaRect.width).toDouble(), - _percentToValue(series.height, chartAreaRect.height).toDouble()); + _percentToValue(series.width, chartAreaRect.width)!.toDouble(), + _percentToValue(series.height, chartAreaRect.height)!.toDouble()); seriesRenderer._neckSize = Size( - _percentToValue(series.neckWidth, chartAreaRect.width).toDouble(), - _percentToValue(series.neckHeight, chartAreaRect.height).toDouble()); + _percentToValue(series.neckWidth, chartAreaRect.width)!.toDouble(), + _percentToValue(series.neckHeight, chartAreaRect.height)!.toDouble()); seriesRenderer._explodeDistance = - _percentToValue(series.explodeOffset, chartAreaRect.width); + _percentToValue(series.explodeOffset, chartAreaRect.width)!; _initializeSizeRatio(seriesRenderer, reverse); } /// To initialise size ratio for the funnel void _initializeSizeRatio(FunnelSeriesRenderer seriesRenderer, - [bool reverse]) { + [bool? reverse]) { final List> points = seriesRenderer._renderPoints; double y; assert( @@ -170,12 +170,12 @@ class _FunnelSeries { 1 / (seriesRenderer._sumOfPoints * (1 + gapRatio / (1 - gapRatio))); final double spacing = gapRatio / (points.length - 1); y = 0; - num index; + int index; num height; - for (num i = points.length - 1; i >= 0; i--) { - index = reverse ? points.length - 1 - i : i; + for (int i = points.length - 1; i >= 0; i--) { + index = reverse! ? points.length - 1 - i : i; if (points[index].isVisible) { - height = coEff * points[index].y; + height = coEff * points[index].y!; points[index].yRatio = y; points[index].heightRatio = height; y += height + spacing; @@ -186,18 +186,18 @@ class _FunnelSeries { /// To calculate the segment path void _calculatePathSegment(String seriesType, PointInfo point) { final List pathRegion = point.pathRegion; - final num bottom = + final int bottom = seriesType == 'funnel' ? pathRegion.length - 2 : pathRegion.length - 1; final num x = (pathRegion[0].dx + pathRegion[bottom].dx) / 2; final num right = (pathRegion[1].dx + pathRegion[bottom - 1].dx) / 2; - point.region = Rect.fromLTWH(x, pathRegion[0].dy, right - x, - pathRegion[bottom].dy - pathRegion[0].dy); - point.symbolLocation = Offset(point.region.left + point.region.width / 2, - point.region.top + point.region.height / 2); + point.region = Rect.fromLTWH(x.toDouble(), pathRegion[0].dy, + (right - x).toDouble(), pathRegion[bottom].dy - pathRegion[0].dy); + point.symbolLocation = Offset(point.region!.left + point.region!.width / 2, + point.region!.top + point.region!.height / 2); } /// To perform point explode - void _pointExplode(num pointIndex) { + void _pointExplode(int pointIndex) { bool existExplodedRegion = false; final FunnelSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[0]; @@ -227,17 +227,10 @@ class _FunnelSeries { /// To calculate Path for the segemnt regions void _calculateFunnelPathRegion( - num pointIndex, FunnelSeriesRenderer seriesRenderer) { - num lineWidth, - topRadius, - bottomRadius, - endTop, - endBottom, - minRadius, - endMin, - bottomY, - top, - bottom; + int pointIndex, FunnelSeriesRenderer seriesRenderer) { + num lineWidth, topRadius, bottomRadius, endTop, endBottom, top, bottom; + num? minRadius, bottomY; + late num endMin; final Size area = seriesRenderer._triangleSize; const num offset = 0; final PointInfo currentPoint = @@ -246,7 +239,7 @@ class _FunnelSeries { final Rect rect = _chartState._chartContainerRect; //ignore: prefer_if_null_operators final num extraSpace = (currentPoint.explodeDistance != null - ? currentPoint.explodeDistance + ? currentPoint.explodeDistance! : _isNeedExplode(pointIndex, currentSeries, _chartState) ? seriesRenderer._explodeDistance : 0) + @@ -285,7 +278,7 @@ class _FunnelSeries { top += seriesTop; bottom += seriesTop; bottomY = (bottomY != null) ? (bottomY + seriesTop) : null; - num line1X, + late num line1X, line1Y, line2X, line2Y, @@ -316,18 +309,18 @@ class _FunnelSeries { emptySpaceAtLeft + offset + ((minRadius != null) ? minRadius : 0); line6Y = bottomY; } - currentPoint.pathRegion.add(Offset(line1X, line1Y)); - currentPoint.pathRegion.add(Offset(line2X, line2Y)); - currentPoint.pathRegion.add(Offset(line3X, line3Y)); - currentPoint.pathRegion.add(Offset(line4X, line4Y)); - currentPoint.pathRegion.add(Offset(line5X, line5Y)); - currentPoint.pathRegion.add(Offset(line6X, line6Y)); + currentPoint.pathRegion.add(Offset(line1X.toDouble(), line1Y.toDouble())); + currentPoint.pathRegion.add(Offset(line2X.toDouble(), line2Y.toDouble())); + currentPoint.pathRegion.add(Offset(line3X.toDouble(), line3Y.toDouble())); + currentPoint.pathRegion.add(Offset(line4X.toDouble(), line4Y.toDouble())); + currentPoint.pathRegion.add(Offset(line5X.toDouble(), line5Y.toDouble())); + currentPoint.pathRegion.add(Offset(line6X.toDouble(), line6Y.toDouble())); _calculatePathSegment(seriesRenderer._seriesType, currentPoint); } /// To calculate the funnel segments and render path void _calculateFunnelSegments( - Canvas canvas, num pointIndex, FunnelSeriesRenderer seriesRenderer) { + Canvas canvas, int pointIndex, FunnelSeriesRenderer seriesRenderer) { _calculateFunnelPathRegion(pointIndex, seriesRenderer); final PointInfo currentPoint = seriesRenderer._renderPoints[pointIndex]; @@ -346,45 +339,45 @@ class _FunnelSeries { } /// To paint the funnel segments - void _segmentPaint(Canvas canvas, Path path, num pointIndex, + void _segmentPaint(Canvas canvas, Path path, int pointIndex, FunnelSeriesRenderer seriesRenderer) { final PointInfo point = seriesRenderer._renderPoints[pointIndex]; - final _StyleOptions style = + final _StyleOptions? style = _getPointStyle(pointIndex, seriesRenderer, _chartState, point); final Color fillColor = - style != null && style.fill != null ? style.fill : point.fill; + style != null && style.fill != null ? style.fill! : point.fill; final Color strokeColor = style != null && style.strokeColor != null - ? style.strokeColor + ? style.strokeColor! : point.borderColor; final double strokeWidth = style != null && style.strokeWidth != null - ? style.strokeWidth - : point.borderWidth; + ? style.strokeWidth!.toDouble() + : point.borderWidth.toDouble(); final double opacity = style != null && style.opacity != null - ? style.opacity - : currentSeries.opacity; + ? style.opacity!.toDouble() + : currentSeries.opacity.toDouble(); _drawPath( canvas, _StyleOptions( - fillColor, - _chartState._animateCompleted ? strokeWidth : 0, - strokeColor, - opacity), + fill: fillColor, + strokeWidth: _chartState._animateCompleted ? strokeWidth : 0, + strokeColor: strokeColor, + opacity: opacity), path); } /// To add selection points to selection list - void _seriesPointSelection(num pointIndex, ActivationMode mode) { + void _seriesPointSelection(int pointIndex, ActivationMode mode) { bool isPointAlreadySelected = false; final SfFunnelChart chart = _chartState._chart; final FunnelSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[0]; final List selectionData = _chartState._selectionData; - int currentSelectedIndex; + int? currentSelectedIndex; if (seriesRenderer._isSelectionEnable && mode == chart.selectionGesture) { if (selectionData.isNotEmpty) { if (!chart.enableMultiSelection && @@ -421,51 +414,51 @@ class _FunnelSeries { } /// To return style options for the point on selection - _StyleOptions _getPointStyle( + _StyleOptions? _getPointStyle( int currentPointIndex, FunnelSeriesRenderer seriesRenderer, SfFunnelChartState _chartState, PointInfo point) { - _StyleOptions pointStyle; + _StyleOptions? pointStyle; final SfFunnelChart chart = _chartState._chart; final dynamic selection = seriesRenderer._series.selectionBehavior.enable ? seriesRenderer._series.selectionBehavior : seriesRenderer._series.selectionSettings; - const num seriesIndex = 0; + const int seriesIndex = 0; final List selectionData = _chartState._selectionData; if (selection.enable) { if (selectionData.isNotEmpty) { for (int i = 0; i < selectionData.length; i++) { final int selectionIndex = selectionData[i]; if (chart.onSelectionChanged != null) { - chart.onSelectionChanged(_getSelectionEventArgs( + chart.onSelectionChanged!(_getSelectionEventArgs( seriesRenderer, seriesIndex, selectionIndex)); } if (currentPointIndex == selectionIndex) { pointStyle = _StyleOptions( - _selectionArgs != null - ? _selectionArgs.selectedColor + fill: _selectionArgs != null + ? _selectionArgs!.selectedColor : selection.selectedColor, - _selectionArgs != null - ? _selectionArgs.selectedBorderWidth + strokeWidth: _selectionArgs != null + ? _selectionArgs!.selectedBorderWidth : selection.selectedBorderWidth, - _selectionArgs != null - ? _selectionArgs.selectedBorderColor + strokeColor: _selectionArgs != null + ? _selectionArgs!.selectedBorderColor : selection.selectedBorderColor, - selection.selectedOpacity); + opacity: selection.selectedOpacity); break; } else if (i == selectionData.length - 1) { pointStyle = _StyleOptions( - _selectionArgs != null - ? _selectionArgs.unselectedColor + fill: _selectionArgs != null + ? _selectionArgs!.unselectedColor : selection.unselectedColor, - _selectionArgs != null - ? _selectionArgs.unselectedBorderWidth + strokeWidth: _selectionArgs != null + ? _selectionArgs!.unselectedBorderWidth : selection.unselectedBorderWidth, - _selectionArgs != null - ? _selectionArgs.unselectedBorderColor + strokeColor: _selectionArgs != null + ? _selectionArgs!.unselectedBorderColor : selection.unselectedBorderColor, - selection.unselectedOpacity); + opacity: selection.unselectedOpacity); } } } @@ -475,24 +468,26 @@ class _FunnelSeries { /// To perform selection event and return selectionArgs SelectionArgs _getSelectionEventArgs( - dynamic seriesRenderer, num seriesIndex, num pointIndex) { - final FunnelSeries series = seriesRenderer._series; - final SfFunnelChart chart = seriesRenderer._chartState._chart; - if (series != null && pointIndex < chart.series.dataSource.length) { + dynamic seriesRenderer, int seriesIndex, int pointIndex) { + final SfFunnelChart chart = seriesRenderer._chartState!._chart; + if (pointIndex < chart.series.dataSource!.length) { final dynamic selectionBehavior = seriesRenderer._selectionBehavior; - _selectionArgs = - SelectionArgs(seriesRenderer, seriesIndex, pointIndex, pointIndex); - _selectionArgs.selectedBorderColor = + _selectionArgs = SelectionArgs( + seriesRenderer: seriesRenderer, + seriesIndex: seriesIndex, + viewportPointIndex: pointIndex, + pointIndex: pointIndex); + _selectionArgs!.selectedBorderColor = selectionBehavior.selectedBorderColor; - _selectionArgs.selectedBorderWidth = + _selectionArgs!.selectedBorderWidth = selectionBehavior.selectedBorderWidth; - _selectionArgs.unselectedBorderColor = + _selectionArgs!.unselectedBorderColor = selectionBehavior.unselectedBorderColor; - _selectionArgs.unselectedBorderWidth = + _selectionArgs!.unselectedBorderWidth = selectionBehavior.unselectedBorderWidth; - _selectionArgs.selectedColor = selectionBehavior.selectedColor; - _selectionArgs.unselectedColor = selectionBehavior.unselectedColor; + _selectionArgs!.selectedColor = selectionBehavior.selectedColor; + _selectionArgs!.unselectedColor = selectionBehavior.unselectedColor; } - return _selectionArgs; + return _selectionArgs!; } } diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart index a0472cc5c..b37600f42 100644 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart @@ -3,13 +3,15 @@ part of charts; // ignore: must_be_immutable class _FunnelDataLabelRenderer extends StatefulWidget { // ignore: prefer_const_constructors_in_immutables - _FunnelDataLabelRenderer({this.chartState, this.show}); + _FunnelDataLabelRenderer( + {required Key key, required this.chartState, required this.show}) + : super(key: key); final SfFunnelChartState chartState; bool show; - _FunnelDataLabelRendererState state; + _FunnelDataLabelRendererState? state; @override State createState() => _FunnelDataLabelRendererState(); @@ -17,13 +19,13 @@ class _FunnelDataLabelRenderer extends StatefulWidget { class _FunnelDataLabelRendererState extends State<_FunnelDataLabelRenderer> with SingleTickerProviderStateMixin { - List animationControllersList; + late List animationControllersList; /// Animation controller for series - AnimationController animationController; + late AnimationController animationController; /// Repaint notifier for crosshair container - ValueNotifier dataLabelRepaintNotifier; + late ValueNotifier dataLabelRepaintNotifier; @override void initState() { @@ -37,7 +39,7 @@ class _FunnelDataLabelRendererState extends State<_FunnelDataLabelRenderer> Widget build(BuildContext context) { widget.state = this; animationController.duration = - Duration(milliseconds: widget.chartState._initialRender ? 500 : 0); + Duration(milliseconds: widget.chartState._initialRender! ? 500 : 0); final Animation dataLabelAnimation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, @@ -76,11 +78,11 @@ class _FunnelDataLabelRendererState extends State<_FunnelDataLabelRenderer> class _FunnelDataLabelPainter extends CustomPainter { _FunnelDataLabelPainter( - {this.chartState, - this.state, - this.animationController, - this.animation, - ValueNotifier notifier}) + {required this.chartState, + required this.state, + required this.animationController, + required this.animation, + required ValueNotifier notifier}) : super(repaint: notifier); final SfFunnelChartState chartState; @@ -89,7 +91,7 @@ class _FunnelDataLabelPainter extends CustomPainter { final AnimationController animationController; - final Animation animation; + final Animation? animation; /// To paint funnel data label @override @@ -115,12 +117,12 @@ void _renderFunnelDataLabel( FunnelSeriesRenderer seriesRenderer, Canvas canvas, SfFunnelChartState chartState, - Animation animation, + Animation? animation, FunnelSeries series) { PointInfo point; final SfFunnelChart chart = chartState._chart; final DataLabelSettings dataLabel = series.dataLabelSettings; - String label; + String? label; final double animateOpacity = animation != null ? animation.value : 1; DataLabelRenderArgs dataLabelArgs; TextStyle dataLabelStyle; @@ -140,15 +142,15 @@ void _renderFunnelDataLabel( !seriesRenderer._renderPoints[pointIndex].labelRenderEvent) { dataLabelArgs = DataLabelRenderArgs(seriesRenderer, seriesRenderer._renderPoints, pointIndex, pointIndex); - dataLabelArgs.text = label; + dataLabelArgs.text = label!; dataLabelArgs.textStyle = dataLabelStyle; dataLabelArgs.color = dataLabelSettingsRenderer._color; - chart.onDataLabelRender(dataLabelArgs); + chart.onDataLabelRender!(dataLabelArgs); label = point.text = dataLabelArgs.text; dataLabelStyle = dataLabelArgs.textStyle; - pointIndex = dataLabelArgs.pointIndex; + pointIndex = dataLabelArgs.pointIndex!; dataLabelSettingsRenderer._color = dataLabelArgs.color; - if (animation.status == AnimationStatus.completed) { + if (animation!.status == AnimationStatus.completed) { seriesRenderer._dataPoints[pointIndex].labelRenderEvent = true; } } @@ -156,7 +158,7 @@ void _renderFunnelDataLabel( ? _getDataLabelTextStyle( seriesRenderer, point, chartState, animateOpacity) : dataLabelStyle; - final Size textSize = _measureText(label, dataLabelStyle); + final Size textSize = measureText(label!, dataLabelStyle); ///Label check after event if (label != '') { @@ -175,6 +177,8 @@ void _renderFunnelDataLabel( dataLabelStyle); } else { point.renderPosition = ChartDataLabelPosition.outside; + dataLabelStyle = _getDataLabelTextStyle( + seriesRenderer, point, chartState, animateOpacity); _renderOutsideFunnelDataLabel( canvas, label, @@ -201,7 +205,7 @@ void _setFunnelInsideLabelPosition( Canvas canvas, List renderDataLabelRegions, int pointIndex, - String label, + String? label, FunnelSeriesRenderer seriesRenderer, double animateOpacity, TextStyle dataLabelStyle) { @@ -224,13 +228,15 @@ void _setFunnelInsideLabelPosition( textSize.width + (2 * labelPadding), textSize.height + (2 * labelPadding)); final bool isDataLabelCollide = - _findingCollision(point.labelRect, renderDataLabelRegions, point.region); + _findingCollision(point.labelRect!, renderDataLabelRegions, point.region); if (isDataLabelCollide && smartLabelMode == SmartLabelMode.shift) { point.saturationRegionOutside = true; point.renderPosition = ChartDataLabelPosition.outside; + dataLabelStyle = _getDataLabelTextStyle( + seriesRenderer, point, chartState, animateOpacity); _renderOutsideFunnelDataLabel( canvas, - label, + label!, point, textSize, pointIndex, @@ -244,9 +250,9 @@ void _setFunnelInsideLabelPosition( (!isDataLabelCollide && smartLabelMode == SmartLabelMode.hide)) { point.renderPosition = ChartDataLabelPosition.inside; _drawFunnelLabel( - point.labelRect, + point.labelRect!, labelLocation, - label, + label!, null, canvas, seriesRenderer, @@ -262,7 +268,7 @@ void _setFunnelInsideLabelPosition( /// To render outside position funnel data labels void _renderOutsideFunnelDataLabel( Canvas canvas, - String label, + String? label, PointInfo point, Size textSize, int pointIndex, @@ -272,92 +278,61 @@ void _renderOutsideFunnelDataLabel( List renderDataLabelRegions, double animateOpacity) { Path connectorPath; - Rect rect; + Rect? rect; Offset labelLocation; + // Maximum available space for rendering datalabel. + final int maximumAvailableWidth = 22; final EdgeInsets margin = seriesRenderer._series.dataLabelSettings.margin; final ConnectorLineSettings connector = seriesRenderer._series.dataLabelSettings.connectorLineSettings; const num regionPadding = 10; connectorPath = Path(); final num connectorLength = _percentToValue( - connector.length ?? '0%', _chartState._chartAreaRect.width / 2) + + connector.length ?? '0%', _chartState._chartAreaRect.width / 2)! + seriesRenderer._maximumDataLabelRegion.width / 2 - regionPadding; final List regions = seriesRenderer._renderPoints[pointIndex].pathRegion; final Offset startPoint = Offset( (regions[1].dx + regions[2].dx) / 2, (regions[1].dy + regions[2].dy) / 2); + if (textSize.width > maximumAvailableWidth) { + label = label!.substring(0, 2) + '..'; + textSize = measureText(label, textStyle); + } + final double dx = seriesRenderer._renderPoints[pointIndex].symbolLocation.dx + + connectorLength; final Offset endPoint = Offset( - seriesRenderer._renderPoints[pointIndex].symbolLocation.dx + - connectorLength, + (dx + textSize.width + margin.left + margin.right) > + _chartState._chartAreaRect.right + ? dx - + (_percentToValue(seriesRenderer._series.explodeOffset, + _chartState._chartAreaRect.width)!) + : dx, (regions[1].dy + regions[2].dy) / 2); connectorPath.moveTo(startPoint.dx, startPoint.dy); if (connector.type == ConnectorType.line) { connectorPath.lineTo(endPoint.dx, endPoint.dy); } point.dataLabelPosition = Position.right; - if (textSize.width > 22) { - label = label.substring(0, 2) + '..'; - textSize = _measureText(label, textStyle); - } - rect = _getDataLabelRect(point.dataLabelPosition, connector.type, margin, + rect = _getDataLabelRect(point.dataLabelPosition!, connector.type, margin, connectorPath, endPoint, textSize); - final Rect containerRect = _chartState._chartAreaRect; - point.labelRect = rect; - labelLocation = Offset(rect.left + margin.left, - rect.top + rect.height / 2 - textSize.height / 2); + if (rect != null) { + final Rect containerRect = _chartState._chartAreaRect; + point.labelRect = rect; + labelLocation = Offset(rect.left + margin.left, + rect.top + rect.height / 2 - textSize.height / 2); - if (seriesRenderer._series.dataLabelSettings.builder == null) { - Rect lastRenderedLabelRegion; - if (renderDataLabelRegions.isNotEmpty) { - lastRenderedLabelRegion = - renderDataLabelRegions[renderDataLabelRegions.length - 1]; - } - if (rect.left > containerRect.left && - rect.right <= containerRect.right && - rect.top > containerRect.top && - rect.bottom < containerRect.bottom) { - if (!_isFunnelLabelIntersect(rect, lastRenderedLabelRegion)) { - _drawFunnelLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - _chartState, - textStyle, - renderDataLabelRegions, - animateOpacity); - } else { - if (pointIndex != 0) { - const num connectorLinePadding = 15; - const num padding = 2; - final Rect previousRenderedRect = - renderDataLabelRegions[renderDataLabelRegions.length - 1]; - rect = Rect.fromLTWH( - rect.left, - previousRenderedRect.top - padding - rect.height, - rect.width, - rect.height); - labelLocation = Offset( - rect.left + margin.left, - previousRenderedRect.top - - padding - - rect.height + - rect.height / 2 - - textSize.height / 2); - connectorPath = Path(); - connectorPath.moveTo(startPoint.dx, startPoint.dy); - if (rect.left - connectorLinePadding >= startPoint.dx) { - connectorPath.lineTo( - rect.left - connectorLinePadding, startPoint.dy); - } - connectorPath.lineTo(rect.left, rect.top + rect.height / 2); - } - if (rect.top >= containerRect.top + regionPadding) { + if (seriesRenderer._series.dataLabelSettings.builder == null) { + Rect? lastRenderedLabelRegion; + if (renderDataLabelRegions.isNotEmpty) { + lastRenderedLabelRegion = + renderDataLabelRegions[renderDataLabelRegions.length - 1]; + } + if (rect.left > containerRect.left && + rect.right <= containerRect.right && + rect.top > containerRect.top && + rect.bottom < containerRect.bottom) { + if (!_isFunnelLabelIntersect(rect, lastRenderedLabelRegion)) { _drawFunnelLabel( rect, labelLocation, @@ -371,6 +346,47 @@ void _renderOutsideFunnelDataLabel( textStyle, renderDataLabelRegions, animateOpacity); + } else { + if (pointIndex != 0) { + const num connectorLinePadding = 15; + const num padding = 2; + final Rect previousRenderedRect = + renderDataLabelRegions[renderDataLabelRegions.length - 1]; + rect = Rect.fromLTWH( + rect.left, + previousRenderedRect.top - padding - rect.height, + rect.width, + rect.height); + labelLocation = Offset( + rect.left + margin.left, + previousRenderedRect.top - + padding - + rect.height + + rect.height / 2 - + textSize.height / 2); + connectorPath = Path(); + connectorPath.moveTo(startPoint.dx, startPoint.dy); + if (rect.left - connectorLinePadding >= startPoint.dx) { + connectorPath.lineTo( + rect.left - connectorLinePadding, startPoint.dy); + } + connectorPath.lineTo(rect.left, rect.top + rect.height / 2); + } + if (rect.top >= containerRect.top + regionPadding) { + _drawFunnelLabel( + rect, + labelLocation, + label, + connectorPath, + canvas, + seriesRenderer, + point, + pointIndex, + _chartState, + textStyle, + renderDataLabelRegions, + animateOpacity); + } } } } @@ -378,7 +394,7 @@ void _renderOutsideFunnelDataLabel( } /// To check whether labels intersect -bool _isFunnelLabelIntersect(Rect rect, Rect previousRect) { +bool _isFunnelLabelIntersect(Rect rect, Rect? previousRect) { bool isIntersect = false; const num padding = 2; if (previousRect != null && (rect.bottom + padding) > previousRect.top) { @@ -391,8 +407,8 @@ bool _isFunnelLabelIntersect(Rect rect, Rect previousRect) { void _drawFunnelLabel( Rect labelRect, Offset location, - String label, - Path connectorPath, + String? label, + Path? connectorPath, Canvas canvas, FunnelSeriesRenderer seriesRenderer, PointInfo point, @@ -422,15 +438,15 @@ void _drawFunnelLabel( if (dataLabel.builder == null) { final double strokeWidth = dataLabel.borderWidth; - final Color labelFill = dataLabelSettingsRenderer._color ?? + final Color? labelFill = dataLabelSettingsRenderer._color ?? (dataLabel.useSeriesColor ? point.fill : dataLabelSettingsRenderer._color); - final Color strokeColor = - dataLabel.borderColor.withOpacity(dataLabel.opacity) ?? point.fill; + final Color? strokeColor = + dataLabel.borderColor.withOpacity(dataLabel.opacity); if (strokeWidth != null && strokeWidth > 0) { rectPaint = Paint() - ..color = strokeColor.withOpacity( + ..color = strokeColor!.withOpacity( !_chartState._isLegendToggled ? animateOpacity : dataLabel.opacity) ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth; @@ -455,7 +471,7 @@ void _drawFunnelLabel( dataLabel.borderRadius, canvas); } - _drawText(canvas, label, location, textStyle, dataLabel.angle); + _drawText(canvas, label!, location, textStyle, dataLabel.angle); renderDataLabelRegions.add(labelRect); } } @@ -473,7 +489,7 @@ void _triggerFunnelDataLabelEvent( final Offset labelLocation = point.symbolLocation; if (dataLabel.isVisible && seriesRenderer._renderPoints[index].labelRect != null && - seriesRenderer._renderPoints[index].labelRect.contains(position)) { + seriesRenderer._renderPoints[index].labelRect!.contains(position)) { position = Offset(labelLocation.dx, labelLocation.dy); _dataLabelTapEvent(chart, seriesRenderer._series.dataLabelSettings, index, point, position, seriesIndex); diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_series.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_series.dart index 12decaa35..97ccd5de6 100644 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_series.dart @@ -13,25 +13,25 @@ class _FunnelSeriesBase extends ChartSeries this.textFieldMapper, this.name, this.explodeIndex, - String neckWidth, - String neckHeight, - String height, - String width, - double gapRatio, - EmptyPointSettings emptyPointSettings, - String explodeOffset, - bool explode, - ActivationMode explodeGesture, - Color borderColor, - double borderWidth, - LegendIconType legendIconType, - DataLabelSettings dataLabelSettings, - double animationDuration, - double opacity, + String? neckWidth, + String? neckHeight, + String? height, + String? width, + double? gapRatio, + EmptyPointSettings? emptyPointSettings, + String? explodeOffset, + bool? explode, + ActivationMode? explodeGesture, + Color? borderColor, + double? borderWidth, + LegendIconType? legendIconType, + DataLabelSettings? dataLabelSettings, + double? animationDuration, + double? opacity, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - List initialSelectedDataIndexes, + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + List? initialSelectedDataIndexes, }) : neckWidth = neckWidth ?? '20%', neckHeight = neckHeight ?? '20%', height = height ?? '80%', @@ -81,7 +81,7 @@ class _FunnelSeriesBase extends ChartSeries /// )); ///} @override - final List dataSource; + final List? dataSource; ///Maps the field name, which will be considered as x-values. /// @@ -109,7 +109,7 @@ class _FunnelSeriesBase extends ChartSeries ///} ///``` @override - final ChartIndexedValueMapper xValueMapper; + final ChartIndexedValueMapper? xValueMapper; ///Maps the field name, which will be considered as y-values. /// @@ -137,7 +137,7 @@ class _FunnelSeriesBase extends ChartSeries ///} ///``` @override - final ChartIndexedValueMapper yValueMapper; + final ChartIndexedValueMapper? yValueMapper; ///Name of the series. /// @@ -153,7 +153,7 @@ class _FunnelSeriesBase extends ChartSeries ///} ///``` @override - final String name; + final String? name; ///Neck height of funnel. /// @@ -405,7 +405,7 @@ class _FunnelSeriesBase extends ChartSeries ///} ///``` @override - final ChartIndexedValueMapper pointColorMapper; + final ChartIndexedValueMapper? pointColorMapper; ///Maps the field name, which will be considered as text for data label. /// @@ -427,7 +427,7 @@ class _FunnelSeriesBase extends ChartSeries /// final Color pointColor; ///} ///``` - final ChartIndexedValueMapper textFieldMapper; + final ChartIndexedValueMapper? textFieldMapper; ///Opacity of the series. /// @@ -500,7 +500,7 @@ class _FunnelSeriesBase extends ChartSeries /// )); ///} ///``` - final num explodeIndex; + final num? explodeIndex; /// List of data indexes initially selected /// @@ -547,7 +547,7 @@ class _FunnelSeriesBase extends ChartSeries /// )); ///} ///``` - final ValueKey key; + final ValueKey? key; ///Used to create the renderer for custom series. /// @@ -578,7 +578,7 @@ class _FunnelSeriesBase extends ChartSeries /// // custom implementation here... /// } ///``` - final ChartSeriesRendererFactory onCreateRenderer; + final ChartSeriesRendererFactory? onCreateRenderer; ///Triggers when the series renderer is created. @@ -603,7 +603,7 @@ class _FunnelSeriesBase extends ChartSeries /// )); ///} ///``` - final FunnelSeriesRendererCreatedCallback onRendererCreated; + final FunnelSeriesRendererCreatedCallback? onRendererCreated; /// To calculate empty point values if null values are provided @override @@ -649,47 +649,49 @@ class _FunnelSeriesBase extends ChartSeries class FunnelSeries extends _FunnelSeriesBase { /// Creating an argument constructor of FunnelSeries class. FunnelSeries({ - ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - FunnelSeriesRendererCreatedCallback onRendererCreated, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper yValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper textFieldMapper, - String name, - String neckWidth, - String neckHeight, - String height, - String width, - double gapRatio, - LegendIconType legendIconType, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - double animationDuration, - double opacity, - Color borderColor, - double borderWidth, - bool explode, - ActivationMode explodeGesture, - String explodeOffset, + ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + FunnelSeriesRendererCreatedCallback? onRendererCreated, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? yValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? textFieldMapper, + String? name, + String? neckWidth, + String? neckHeight, + String? height, + String? width, + double? gapRatio, + LegendIconType? legendIconType, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + double? animationDuration, + double? opacity, + Color? borderColor, + double? borderWidth, + bool? explode, + ActivationMode? explodeGesture, + String? explodeOffset, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - num explodeIndex, - List initialSelectedDataIndexes, + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + num? explodeIndex, + List? initialSelectedDataIndexes, }) : super( key: key, onCreateRenderer: onCreateRenderer, onRendererCreated: onRendererCreated, dataSource: dataSource, - xValueMapper: (int index) => xValueMapper(dataSource[index], index), - yValueMapper: (int index) => yValueMapper(dataSource[index], index), + xValueMapper: (int index) => + xValueMapper!(dataSource![index], index), + yValueMapper: (int index) => + yValueMapper!(dataSource![index], index), pointColorMapper: (int index) => pointColorMapper != null - ? pointColorMapper(dataSource[index], index) + ? pointColorMapper(dataSource![index], index) : null, textFieldMapper: (int index) => textFieldMapper != null - ? textFieldMapper(dataSource[index], index) + ? textFieldMapper(dataSource![index], index) : null, name: name, neckWidth: neckWidth, @@ -714,9 +716,9 @@ class FunnelSeries extends _FunnelSeriesBase { /// Create the pie series renderer. FunnelSeriesRenderer createRenderer(FunnelSeries series) { - FunnelSeriesRenderer seriesRenderer; + FunnelSeriesRenderer? seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as FunnelSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -727,21 +729,21 @@ class FunnelSeries extends _FunnelSeriesBase { class _FunnelChartPainter extends CustomPainter { _FunnelChartPainter({ - this.chartState, - this.seriesIndex, - this.isRepaint, + required this.chartState, + required this.seriesIndex, + required this.isRepaint, this.animationController, this.seriesAnimation, - ValueNotifier notifier, + required ValueNotifier notifier, }) : super(repaint: notifier); final SfFunnelChartState chartState; final int seriesIndex; final bool isRepaint; - final AnimationController animationController; - final Animation seriesAnimation; - FunnelSeriesRenderer seriesRenderer; + final AnimationController? animationController; + final Animation? seriesAnimation; + late FunnelSeriesRenderer seriesRenderer; //ignore: unused_field - static PointInfo point; + static late PointInfo point; @override void paint(Canvas canvas, Size size) { @@ -752,7 +754,7 @@ class _FunnelChartPainter extends CustomPainter { pointIndex++) { if (seriesRenderer._renderPoints[pointIndex].isVisible) { final double animationFactor = - seriesAnimation != null ? seriesAnimation.value : 1; + seriesAnimation != null ? seriesAnimation!.value : 1; if (seriesRenderer._series.animationDuration > 0 && !chartState._isLegendToggled) { final double factor = (chartState._chartAreaRect.top + @@ -787,26 +789,27 @@ class FunnelSeriesRenderer extends ChartSeriesRenderer { /// Calling the default constructor of FunnelSeriesRenderer class. FunnelSeriesRenderer(); - FunnelSeries _series; + late FunnelSeries _series; //Internal variables - String _seriesType; - List> _dataPoints; - List> _renderPoints; - num _sumOfPoints; - Size _triangleSize; - Size _neckSize; - num _explodeDistance; - Rect _maximumDataLabelRegion; - FunnelSeriesController _controller; - SfFunnelChartState _chartState; - ValueNotifier _repaintNotifier; - DataLabelSettingsRenderer _dataLabelSettingsRenderer; - SelectionBehaviorRenderer _selectionBehaviorRenderer; - dynamic _selectionBehavior; + late String _seriesType; + late List> _dataPoints; + late List> _renderPoints; + late num _sumOfPoints; + late Size _triangleSize; + late Size _neckSize; + late num _explodeDistance; + late Rect _maximumDataLabelRegion; + FunnelSeriesController? _controller; + late SfFunnelChartState _chartState; + late ValueNotifier _repaintNotifier; + late DataLabelSettingsRenderer _dataLabelSettingsRenderer; + late SelectionBehaviorRenderer _selectionBehaviorRenderer; + late dynamic _selectionBehavior; //ignore: prefer_final_fields bool _isSelectionEnable = false; } +/// Called when the renderer for the funnel series is created typedef FunnelSeriesRendererCreatedCallback = void Function( FunnelSeriesController controller); @@ -886,12 +889,12 @@ class FunnelSeriesController { /// } ///``` void updateDataSource( - {List addedDataIndexes, - List removedDataIndexes, - List updatedDataIndexes, - int addedDataIndex, - int removedDataIndex, - int updatedDataIndex}) { + {List? addedDataIndexes, + List? removedDataIndexes, + List? updatedDataIndexes, + int? addedDataIndex, + int? removedDataIndex, + int? updatedDataIndex}) { if (removedDataIndexes != null && removedDataIndexes.isNotEmpty) { _removeDataPointsList(removedDataIndexes); } else if (removedDataIndex != null) { @@ -922,12 +925,12 @@ class FunnelSeriesController { void _addOrUpdateDataPoint(int index, bool needUpdate) { final FunnelSeries series = seriesRenderer._series; if (index >= 0 && - series.dataSource.length > index && - series.dataSource[index] != null) { - final ChartIndexedValueMapper xValue = series.xValueMapper; - final ChartIndexedValueMapper yValue = series.yValueMapper; + series.dataSource!.length > index && + series.dataSource![index] != null) { + final ChartIndexedValueMapper? xValue = series.xValueMapper; + final ChartIndexedValueMapper? yValue = series.yValueMapper; final PointInfo currentPoint = - PointInfo(xValue(index), yValue(index)); + PointInfo(xValue!(index), yValue!(index)); if (currentPoint.x != null) { if (needUpdate) { if (seriesRenderer._dataPoints.length > index) { @@ -968,16 +971,16 @@ class FunnelSeriesController { void _updateFunnelSeries() { final SfFunnelChartState chartState = seriesRenderer._chartState; chartState._chartSeries._processDataPoints(seriesRenderer); - chartState._chartSeries?._initializeSeriesProperties(seriesRenderer); + chartState._chartSeries._initializeSeriesProperties(seriesRenderer); seriesRenderer._repaintNotifier.value++; if (seriesRenderer._series.dataLabelSettings.isVisible && chartState._renderDataLabel != null) { - chartState._renderDataLabel.state.render(); + chartState._renderDataLabel!.state!.render(); } if (seriesRenderer._series.dataLabelSettings.isVisible && chartState._chartTemplate != null && - chartState._chartTemplate.state != null) { - chartState._chartTemplate.state.templateRender(); + chartState._chartTemplate!.state != null) { + chartState._chartTemplate!.state.templateRender(); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart index e4ca52943..b2a56a13e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart @@ -31,7 +31,7 @@ typedef PyramidTouchInteractionCallback = void Function( class SfPyramidChart extends StatefulWidget { /// Creating an argument constructor of SfPyramidChart class. SfPyramidChart({ - Key key, + Key? key, this.backgroundColor, this.backgroundImage, this.borderColor = Colors.transparent, @@ -46,10 +46,10 @@ class SfPyramidChart extends StatefulWidget { this.onChartTouchInteractionUp, this.onChartTouchInteractionDown, this.onChartTouchInteractionMove, - ChartTitle title, - PyramidSeries series, - EdgeInsets margin, - Legend legend, + ChartTitle? title, + PyramidSeries? series, + EdgeInsets? margin, + Legend? legend, this.palette = const [ Color.fromRGBO(75, 135, 185, 1), Color.fromRGBO(192, 108, 132, 1), @@ -62,12 +62,12 @@ class SfPyramidChart extends StatefulWidget { Color.fromRGBO(255, 240, 219, 1), Color.fromRGBO(238, 238, 238, 1) ], - TooltipBehavior tooltipBehavior, - SmartLabelMode smartLabelMode, - ActivationMode selectionGesture, - bool enableMultiSelection, + TooltipBehavior? tooltipBehavior, + SmartLabelMode? smartLabelMode, + ActivationMode? selectionGesture, + bool? enableMultiSelection, }) : title = title ?? ChartTitle(), - series = series ?? series, + series = series ?? PyramidSeries(), margin = margin ?? const EdgeInsets.fromLTRB(10, 10, 10, 10), legend = legend ?? Legend(), tooltipBehavior = tooltipBehavior ?? TooltipBehavior(), @@ -98,7 +98,7 @@ class SfPyramidChart extends StatefulWidget { /// )); ///} ///``` - final Color backgroundColor; + final Color? backgroundColor; ///Background color of the chart /// @@ -205,18 +205,18 @@ class SfPyramidChart extends StatefulWidget { /// args.legendIconType = LegendIconType.diamond; ///} ///``` - final PyramidLegendRenderCallback onLegendItemRender; + final PyramidLegendRenderCallback? onLegendItemRender; /// Occurs when the tooltip is rendered. /// /// Here,you can get the tooltip arguments and customize the arguments. - final PyramidTooltipCallback onTooltipRender; + final PyramidTooltipCallback? onTooltipRender; /// Occurs when the datalabel is rendered,Here datalabel arguments can be customized. - final PyramidDataLabelRenderCallback onDataLabelRender; + final PyramidDataLabelRenderCallback? onDataLabelRender; /// Occurs when the legend is tapped,the arguments can be used to customize the legend arguments - final ChartLegendTapCallback onLegendTapped; + final ChartLegendTapCallback? onLegendTapped; /// Smart labelmode to avoid the overlapping of labels. final SmartLabelMode smartLabelMode; @@ -298,7 +298,7 @@ class SfPyramidChart extends StatefulWidget { /// )); ///} ///``` - final ImageProvider backgroundImage; + final ImageProvider? backgroundImage; /// Occurs while selection changes. Here, you can get the series, selected color, /// unselected color, selected border color, unselected border color, selected @@ -314,7 +314,7 @@ class SfPyramidChart extends StatefulWidget { /// print(args.selectedBorderColor); ///} ///``` - final PyramidSelectionCallback onSelectionChanged; + final PyramidSelectionCallback? onSelectionChanged; /// Occurs when tapping a series point. Here, you can get the series, series index /// and point index. @@ -329,7 +329,7 @@ class SfPyramidChart extends StatefulWidget { /// print(args.pointIndex); ///} ///``` - final PyramidPointTapCallback onPointTapped; + final PyramidPointTapCallback? onPointTapped; //Called when the data label is tapped. /// @@ -351,7 +351,7 @@ class SfPyramidChart extends StatefulWidget { ///} /// ///``` - final DataLabelTapCallback onDataLabelTapped; + final DataLabelTapCallback? onDataLabelTapped; /// Occurs when tapped on the chart area. ///```dart @@ -365,7 +365,7 @@ class SfPyramidChart extends StatefulWidget { /// )); ///} ///``` - final PyramidTouchInteractionCallback onChartTouchInteractionUp; + final PyramidTouchInteractionCallback? onChartTouchInteractionUp; /// Occurs when touched and moved on the chart area. ///```dart @@ -379,7 +379,7 @@ class SfPyramidChart extends StatefulWidget { /// )); ///} ///``` - final PyramidTouchInteractionCallback onChartTouchInteractionMove; + final PyramidTouchInteractionCallback? onChartTouchInteractionMove; /// Occurs when touched on the chart area. ///```dart @@ -393,7 +393,7 @@ class SfPyramidChart extends StatefulWidget { /// )); ///} ///``` - final PyramidTouchInteractionCallback onChartTouchInteractionDown; + final PyramidTouchInteractionCallback? onChartTouchInteractionDown; @override State createState() => SfPyramidChartState(); @@ -404,59 +404,76 @@ class SfPyramidChart extends StatefulWidget { class SfPyramidChartState extends State with TickerProviderStateMixin { //ignore: unused_field - List _controllerList; + late List _controllerList; //ignore: unused_field - AnimationController _animationController; // Animation controller for series + late AnimationController + _animationController; // Animation controller for series //ignore: unused_field - AnimationController _annotationController; // Controller for Annotations + late AnimationController _annotationController; // Controller for Annotations - ValueNotifier _seriesRepaintNotifier; - List<_MeasureWidgetContext> + late ValueNotifier _seriesRepaintNotifier; + late List<_MeasureWidgetContext> _legendWidgetContext; // To measure legend size and position - List<_ChartTemplateInfo> _templates; // Chart Template info - List _chartWidgets; + late List<_ChartTemplateInfo> _templates; // Chart Template info + late List _chartWidgets; //ignore: unused_field - PyramidSeriesRenderer _seriesRenderer; + late PyramidSeriesRenderer _seriesRenderer; /// Holds the information of chart theme arguments - SfChartThemeData _chartTheme; - Rect _chartContainerRect; - Rect _chartAreaRect; - _ChartTemplate _chartTemplate; - _ChartInteraction _currentActive; - bool _initialRender; - List<_LegendRenderContext> _legendToggleStates; - List<_MeasureWidgetContext> _legendToggleTemplateStates; - bool _isLegendToggled; - Offset _tapPosition; - bool _animateCompleted; + late SfChartThemeData _chartTheme; + late Rect _chartContainerRect; + late Rect _chartAreaRect; + _ChartTemplate? _chartTemplate; + _ChartInteraction? _currentActive; + bool? _initialRender; + late List<_LegendRenderContext> _legendToggleStates; + late List<_MeasureWidgetContext> _legendToggleTemplateStates; + late bool _isLegendToggled; + Offset? _tapPosition; + bool? _animateCompleted; //ignore: unused_field - Animation _chartElementAnimation; - _PyramidDataLabelRenderer _renderDataLabel; - bool _widgetNeedUpdate; - List _explodedPoints; - List _dataLabelTemplateRegions; - List _selectionData; - int _tooltipPointIndex; - Orientation _oldDeviceOrientation; - Orientation _deviceOrientation; - Size _prevSize; + late Animation _chartElementAnimation; + _PyramidDataLabelRenderer? _renderDataLabel; + late bool _widgetNeedUpdate; + late List _explodedPoints; + late List _dataLabelTemplateRegions; + late List _selectionData; + int? _tooltipPointIndex; + Orientation? _oldDeviceOrientation; + late Orientation _deviceOrientation; + Size? _prevSize; bool _didSizeChange = false; //Internal variables - String _seriesType; - List> _dataPoints; - List> _renderPoints; - _PyramidSeries _chartSeries; - _ChartLegend _chartLegend; + late String _seriesType; + late List> _dataPoints; + List>? _renderPoints; + late _PyramidSeries _chartSeries; + late _ChartLegend _chartLegend; //ignore: unused_field - _PyramidPlotArea _chartPlotArea; - TooltipBehaviorRenderer _tooltipBehaviorRenderer; - LegendRenderer _legendRenderer; + late _PyramidPlotArea _chartPlotArea; + late TooltipBehaviorRenderer _tooltipBehaviorRenderer; + late LegendRenderer _legendRenderer; //Here, we are using get keyword inorder to get the proper & updated instance of chart widget //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. SfPyramidChart get _chart => widget; + // ignore: unused_element + bool get _animationCompleted { + return _animationController.status != AnimationStatus.forward; + } + + /// Called when this object is inserted into the tree. + /// + /// The framework will call this method exactly once for each State object it creates. + /// + /// Override this method to perform initialization that depends on the location at + /// which this object was inserted into the tree or on the widget used to configure this object. + /// + /// * In [initState], subscribe to the object. + /// + /// Here it overrides to initialize the object that depends on rendering the [SfPyramidChart]. + @override void initState() { _initializeDefaultValues(); @@ -465,22 +482,58 @@ class SfPyramidChartState extends State super.initState(); } + /// Called when a dependency of this [State] object changes. + /// + /// For example, if the previous call to [build] referenced an [InheritedWidget] that later changed, + /// the framework would call this method to notify this object about the change. + /// + /// This method is also called immediately after [initState]. It is safe to call [BuildContext.dependOnInheritedWidgetOfExactType] from this method. + /// + /// Here it called for initializing the chart theme of [SfPyramidChart]. + @override void didChangeDependencies() { _chartTheme = SfChartTheme.of(context); super.didChangeDependencies(); } + /// Called whenever the widget configuration changes. + /// + /// If the parent widget rebuilds and request that this location in the tree update to display a new widget with the same [runtimeType] and [Widget.key], + /// the framework will update the widget property of this [State] object to refer to the new widget and then call this method with the previous widget as an argument. + /// + /// Override this method to respond when the widget changes. + /// + /// The framework always calls [build] after calling [didUpdateWidget], which means any calls to [setState] in [didUpdateWidget] are redundant. + /// + /// * In [didUpdateWidget] unsubscribe from the old object and subscribe to the new one if the updated widget configuration requires replacing the object. + /// + /// Here it called whenever the series collection gets updated in [SfPyramidChart]. + @override void didUpdateWidget(SfPyramidChart oldWidget) { //Update and maintain the series state, when we update the series in the series collection // _createAndUpdateSeriesRenderer(oldWidget); _initialRender = !widget.series.explode; + if (_tooltipBehaviorRenderer._chartTooltipState != null) { + _tooltipBehaviorRenderer._show = false; + } super.didUpdateWidget(oldWidget); _isLegendToggled = false; _widgetNeedUpdate = true; } + /// Describes the part of the user interface represented by this widget. + /// + /// The framework calls this method in a number of different situations. For example: + /// + /// * After calling [initState]. + /// * After calling [didUpdateWidget]. + /// * After receiving a call to [setState]. + /// * After a dependency of this [State] object changes. + /// + /// Here it is called whenever the user interaction is performed and it removes the old widget and updates a chart with a new widget in [SfPyramidChart]. + @override Widget build(BuildContext context) { _prevSize = _prevSize ?? MediaQuery.of(context).size; @@ -498,7 +551,8 @@ class SfPyramidChartState extends State color: widget.backgroundColor, image: widget.backgroundImage != null ? DecorationImage( - image: widget.backgroundImage, fit: BoxFit.fill) + image: widget.backgroundImage!, + fit: BoxFit.fill) : null, border: Border.all( color: widget.borderColor, @@ -511,6 +565,18 @@ class SfPyramidChartState extends State ))))); } + /// Called when this object is removed from the tree permanently. + /// + /// The framework calls this method when this [State] object will never build again. After the framework calls [dispose], + /// the [State] object is considered unmounted and the [mounted] property is false. It is an error to call [setState] at this + /// point. This stage of the lifecycle is terminal: there is no way to remount a [State] object that has been disposed. + /// + /// Subclasses should override this method to release any resources retained by this object. + /// + /// * In [dispose], unsubscribe from the object. + /// + /// Here it end the animation controller of the series in [SfPyramidChart]. + @override void dispose() { _disposeAnimationController(_animationController, _repaintChartElements); @@ -519,7 +585,7 @@ class SfPyramidChartState extends State /// Method to convert the [SfPyramidChart] as an image. /// - /// Returns the [dart:ui.image] + /// Returns the `dart:ui.image` /// /// As this method is in the widget’s state class, /// you have to use a global key to access the state to call this method. @@ -576,8 +642,8 @@ class SfPyramidChartState extends State ///``` Future toImage({double pixelRatio = 1.0}) async { - final RenderRepaintBoundary boundary = - context.findRenderObject(); //get the render object from context + final RenderRepaintBoundary boundary = context.findRenderObject() + as RenderRepaintBoundary; //get the render object from context final dart_ui.Image image = await boundary.toImage(pixelRatio: pixelRatio); // Convert // the repaint boundary as image @@ -609,9 +675,9 @@ class SfPyramidChartState extends State } // In this method, create and update the series renderer for each series // - void _createAndUpdateSeriesRenderer([SfPyramidChart oldWidget]) { + void _createAndUpdateSeriesRenderer([SfPyramidChart? oldWidget]) { if (widget.series != null) { - final PyramidSeriesRenderer oldSeriesRenderer = + final PyramidSeriesRenderer? oldSeriesRenderer = oldWidget != null && oldWidget.series != null ? _chartSeries.visibleSeriesRenderers[0] : null; @@ -622,7 +688,7 @@ class SfPyramidChartState extends State PyramidSeriesRenderer seriesRenderers; if (oldSeriesRenderer != null && - _isSameSeries(oldWidget.series, series)) { + _isSameSeries(oldWidget!.series, series)) { seriesRenderers = oldSeriesRenderer; } else { seriesRenderers = series.createRenderer(series); @@ -653,7 +719,7 @@ class SfPyramidChartState extends State return Expanded(child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { Widget element; - if (widget.series?.dataSource != null) { + if (widget.series.dataSource != null) { _initialize(constraints); _chartSeries._findVisibleSeries(); @@ -661,11 +727,11 @@ class SfPyramidChartState extends State final List legendTemplates = _bindLegendTemplateWidgets(this); if (legendTemplates.isNotEmpty && _legendWidgetContext.isEmpty) { element = Container(child: Stack(children: legendTemplates)); - SchedulerBinding.instance.addPostFrameCallback((_) => _refresh()); + SchedulerBinding.instance!.addPostFrameCallback((_) => _refresh()); } else { _chartLegend._calculateLegendBounds(_chartLegend.chartSize); element = _getElements( - this, _PyramidPlotArea(chartState: this), constraints); + this, _PyramidPlotArea(chartState: this), constraints)!; } } else { element = Container(); @@ -680,7 +746,8 @@ class SfPyramidChartState extends State if (legendWidgetContexts.isNotEmpty) { for (int i = 0; i < legendWidgetContexts.length; i++) { final _MeasureWidgetContext templateContext = legendWidgetContexts[i]; - final RenderBox renderBox = templateContext.context.findRenderObject(); + final RenderBox renderBox = + templateContext.context!.findRenderObject() as RenderBox; templateContext.size = renderBox.size; } setState(() { @@ -692,6 +759,9 @@ class SfPyramidChartState extends State // ignore:unused_element void _redraw() { _initialRender = false; + if (_tooltipBehaviorRenderer._chartTooltipState != null) { + _tooltipBehaviorRenderer._show = false; + } setState(() { /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. }); @@ -718,16 +788,18 @@ class SfPyramidChartState extends State // ignore: must_be_immutable class _PyramidPlotArea extends StatelessWidget { // ignore: prefer_const_constructors_in_immutables - _PyramidPlotArea({this.chartState}); + _PyramidPlotArea({required this.chartState}); final SfPyramidChartState chartState; //Here, we are using get keyword inorder to get the proper & updated instance of chart widget //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. SfPyramidChart get chart => chartState._chart; - PyramidSeriesRenderer seriesRenderer; - RenderBox renderBox; - _Region pointRegion; - TapDownDetails tapDownDetails; - Offset doubleTapPosition; + late PyramidSeriesRenderer seriesRenderer; + late RenderBox renderBox; + _Region? pointRegion; + late TapDownDetails tapDownDetails; + Offset? doubleTapPosition; + bool _enableMouseHover = + kIsWeb || Platform.isLinux || Platform.isMacOS || Platform.isWindows; @override Widget build(BuildContext context) { @@ -735,7 +807,10 @@ class _PyramidPlotArea extends StatelessWidget { builder: (BuildContext context, BoxConstraints constraints) { return Container( child: MouseRegion( - onHover: (PointerEvent event) => _onHover(event), + // Using the _enableMouseHover property, prevented mouse hover function in mobile platforms. The mouse hover event should not be triggered for mobile platforms and logged an issue regarding this to the Flutter team. + // Issue: https://github.com/flutter/flutter/issues/68690 + onHover: (PointerEvent event) => + _enableMouseHover ? _onHover(event) : null, onExit: (PointerEvent event) { chartState._tooltipBehaviorRenderer._isHovering = false; }, @@ -749,6 +824,15 @@ class _PyramidPlotArea extends StatelessWidget { child: GestureDetector( onLongPress: _onLongPress, onDoubleTap: _onDoubleTap, + onTapUp: (TapUpDetails details) { + chartState._tapPosition = + renderBox.globalToLocal(details.globalPosition); + if (chart.onPointTapped != null && + seriesRenderer != null) { + _calculatePointSeriesIndex( + chart, seriesRenderer, chartState._tapPosition!); + } + }, child: Container( height: constraints.maxHeight, width: constraints.maxWidth, @@ -773,7 +857,8 @@ class _PyramidPlotArea extends StatelessWidget { void _calculateContainerSize(BoxConstraints constraints) { final num width = constraints.maxWidth; final num height = constraints.maxHeight; - chartState._chartContainerRect = Rect.fromLTWH(0, 0, width, height); + chartState._chartContainerRect = + Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); final EdgeInsets margin = chart.margin; chartState._chartAreaRect = Rect.fromLTWH( margin.left, @@ -788,7 +873,7 @@ class _PyramidPlotArea extends StatelessWidget { _findTemplates(chartState); _renderTemplates(chartState); _bindTooltipWidgets(constraints); - renderBox = context.findRenderObject(); + renderBox = context.findRenderObject() as RenderBox; chartState._chartPlotArea = this; return Container(child: Stack(children: chartState._chartWidgets)); } @@ -799,8 +884,8 @@ class _PyramidPlotArea extends StatelessWidget { chartState._chartSeries.visibleSeriesRenderers; if (visibleSeriesRenderers.isNotEmpty) { seriesRenderer = visibleSeriesRenderers[0]; - for (int i = 0; i < seriesRenderer._renderPoints.length; i++) { - if (seriesRenderer._renderPoints[i].isVisible) { + for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { + if (seriesRenderer._renderPoints![i].isVisible) { chartState._chartSeries._calculatePathRegion(i, seriesRenderer); } } @@ -809,8 +894,8 @@ class _PyramidPlotArea extends StatelessWidget { /// To bind series widget together void _bindSeriesWidgets() { - CustomPainter seriesPainter; - Animation seriesAnimation; + late CustomPainter seriesPainter; + Animation? seriesAnimation; PyramidSeries series; final List visibleSeriesRenderers = chartState._chartSeries.visibleSeriesRenderers; @@ -829,8 +914,8 @@ class _PyramidPlotArea extends StatelessWidget { SelectionBehaviorRenderer(selectionBehavior, chart, chartState); selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer; selectionBehaviorRenderer._selectionRenderer ??= _SelectionRenderer(); - selectionBehaviorRenderer._selectionRenderer.chart = chart; - selectionBehaviorRenderer._selectionRenderer.seriesRenderer = + selectionBehaviorRenderer._selectionRenderer!.chart = chart; + selectionBehaviorRenderer._selectionRenderer!.seriesRenderer = seriesRenderer; if (series.initialSelectedDataIndexes.isNotEmpty) { for (int index = 0; @@ -843,7 +928,7 @@ class _PyramidPlotArea extends StatelessWidget { if (series.animationDuration > 0 && !chartState._didSizeChange && (chartState._deviceOrientation == chartState._oldDeviceOrientation) && - ((!chartState._widgetNeedUpdate && chartState._initialRender) || + ((!chartState._widgetNeedUpdate && chartState._initialRender!) || chartState._isLegendToggled)) { chartState._animationController.duration = Duration(milliseconds: series.animationDuration.toInt()); @@ -855,11 +940,11 @@ class _PyramidPlotArea extends StatelessWidget { if (status == AnimationStatus.completed) { chartState._animateCompleted = true; if (chartState._renderDataLabel != null) { - chartState._renderDataLabel.state.render(); + chartState._renderDataLabel!.state?.render(); } if (chartState._chartTemplate != null && - chartState._chartTemplate.state != null) { - chartState._chartTemplate.state.templateRender(); + chartState._chartTemplate!.state != null) { + chartState._chartTemplate!.state.templateRender(); } } })); @@ -872,7 +957,7 @@ class _PyramidPlotArea extends StatelessWidget { } else { chartState._animateCompleted = true; if (chartState._renderDataLabel != null) { - chartState._renderDataLabel.state.render(); + chartState._renderDataLabel!.state?.render(); } } seriesRenderer._repaintNotifier = chartState._seriesRepaintNotifier; @@ -888,13 +973,14 @@ class _PyramidPlotArea extends StatelessWidget { chartState._chartWidgets .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); chartState._renderDataLabel = _PyramidDataLabelRenderer( + key: GlobalKey(), chartState: chartState, show: !chartState._widgetNeedUpdate ? chartState._animationController.status == AnimationStatus.completed || chartState._animationController.duration == null : true); - chartState._chartWidgets.add(chartState._renderDataLabel); + chartState._chartWidgets.add(chartState._renderDataLabel!); } } @@ -905,19 +991,33 @@ class _PyramidPlotArea extends StatelessWidget { chartState._tooltipBehaviorRenderer; tooltip._chartState = chartState; if (tooltip.enable) { - if (tooltip.builder != null) { - tooltipBehaviorRenderer._tooltipTemplate = _TooltipTemplate( - show: false, - clipRect: chartState._chartContainerRect, - tooltipBehavior: chart.tooltipBehavior, - duration: tooltip.duration, - chartState: chartState); - chartState._chartWidgets.add(tooltipBehaviorRenderer._tooltipTemplate); - } else { - tooltipBehaviorRenderer._chartTooltip = - _ChartTooltipRenderer(chartState: chartState); - chartState._chartWidgets.add(tooltipBehaviorRenderer._chartTooltip); - } + final SfChartThemeData _chartTheme = chartState._chartTheme; + tooltipBehaviorRenderer._prevTooltipValue = + tooltipBehaviorRenderer._currentTooltipValue = null; + chartState._tooltipBehaviorRenderer._chartTooltip = SfTooltip( + color: tooltip.color ?? _chartTheme.tooltipColor, + key: GlobalKey(), + textStyle: tooltip.textStyle, + animationDuration: tooltip.animationDuration, + enable: tooltip.enable, + opacity: tooltip.opacity, + borderColor: tooltip.borderColor, + borderWidth: tooltip.borderWidth, + duration: tooltip.duration, + shouldAlwaysShow: tooltip.shouldAlwaysShow, + elevation: tooltip.elevation, + canShowMarker: tooltip.canShowMarker, + textAlignment: tooltip.textAlignment, + decimalPlaces: tooltip.decimalPlaces, + labelColor: tooltip.textStyle.color ?? _chartTheme.tooltipLabelColor, + header: tooltip.header, + format: tooltip.format, + builder: tooltip.builder, + shadowColor: tooltip.shadowColor, + onTooltipRender: chart.onTooltipRender != null + ? chartState._tooltipBehaviorRenderer._tooltipRenderingEvent + : null); + chartState._chartWidgets.add(tooltipBehaviorRenderer._chartTooltip!); } } @@ -925,16 +1025,17 @@ class _PyramidPlotArea extends StatelessWidget { void _calculatePointSeriesIndex(SfPyramidChart chart, PyramidSeriesRenderer seriesRenderer, Offset touchPosition) { PointTapArgs pointTapArgs; - num index; - for (int i = 0; i < seriesRenderer._renderPoints.length; i++) { - if (seriesRenderer._renderPoints[i].region.contains(touchPosition)) { + int? index; + for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { + if (seriesRenderer._renderPoints![i].region != null && + seriesRenderer._renderPoints![i].region!.contains(touchPosition)) { index = i; break; } } if (index != null) { pointTapArgs = PointTapArgs(0, index, seriesRenderer._dataPoints, index); - chart.onPointTapped(pointTapArgs); + chart.onPointTapped!(pointTapArgs); } } @@ -944,21 +1045,26 @@ class _PyramidPlotArea extends StatelessWidget { //renderBox = context.findRenderObject(); chartState._currentActive = null; chartState._tapPosition = renderBox.globalToLocal(event.position); - bool isPoint; - const num seriesIndex = 0; - num pointIndex; + bool isPoint = false; + const int seriesIndex = 0; + late int pointIndex; final List visibleSeriesRenderers = chartState._chartSeries.visibleSeriesRenderers; final PyramidSeriesRenderer seriesRenderer = visibleSeriesRenderers[seriesIndex]; ChartTouchInteractionArgs touchArgs; - for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { - if (seriesRenderer._renderPoints[j].isVisible) { - isPoint = _isPointInPolygon(seriesRenderer._renderPoints[j].pathRegion, + for (int j = 0; j < seriesRenderer._renderPoints!.length; j++) { + if (chart.onDataLabelRender != null) { + seriesRenderer._dataPoints[j].labelRenderEvent = false; + } + if (seriesRenderer._renderPoints![j].isVisible && !isPoint) { + isPoint = _isPointInPolygon(seriesRenderer._renderPoints![j].pathRegion, chartState._tapPosition); if (isPoint) { pointIndex = j; - break; + if (chart.onDataLabelRender == null) { + break; + } } } } @@ -968,18 +1074,17 @@ class _PyramidPlotArea extends StatelessWidget { seriesIndex, pointIndex, visibleSeriesRenderers[seriesIndex]._series, - visibleSeriesRenderers[seriesIndex]._renderPoints[pointIndex], + visibleSeriesRenderers[seriesIndex]._renderPoints![pointIndex], ); } else { //hides the tooltip if the point of interaction is outside pyramid region of the chart - chartState._tooltipBehaviorRenderer?._tooltipTemplate?.show = false; - chartState._tooltipBehaviorRenderer?._tooltipTemplate?.state - ?.hideOnTimer(); + chartState._tooltipBehaviorRenderer._show = false; + chartState._tooltipBehaviorRenderer._hideOnTimer(); } if (chart.onChartTouchInteractionDown != null) { touchArgs = ChartTouchInteractionArgs(); touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionDown(touchArgs); + chart.onChartTouchInteractionDown!(touchArgs); } } @@ -990,38 +1095,43 @@ class _PyramidPlotArea extends StatelessWidget { if (chart.onChartTouchInteractionMove != null) { touchArgs = ChartTouchInteractionArgs(); touchArgs.position = position; - chart.onChartTouchInteractionMove(touchArgs); + chart.onChartTouchInteractionMove!(touchArgs); } } /// To perform double tap touch interactions void _onDoubleTap() { - const num seriesIndex = 0; + const int seriesIndex = 0; if (doubleTapPosition != null && chartState._currentActive != null) { - final num pointIndex = chartState._currentActive.pointIndex; + final int pointIndex = chartState._currentActive!.pointIndex!; final List visibleSeriesRenderers = chartState._chartSeries.visibleSeriesRenderers; chartState._currentActive = _ChartInteraction( seriesIndex, pointIndex, visibleSeriesRenderers[seriesIndex]._series, - visibleSeriesRenderers[seriesIndex]._renderPoints[pointIndex]); + visibleSeriesRenderers[seriesIndex]._renderPoints![pointIndex]); if (chartState._currentActive != null) { - if (chartState._currentActive.series.explodeGesture == + if (chartState._currentActive!.series.explodeGesture == ActivationMode.doubleTap) { chartState._chartSeries._pointExplode(pointIndex); + final GlobalKey key = chartState._renderDataLabel!.key as GlobalKey; + final _PyramidDataLabelRendererState _pyramidDataLabelRendererState = + key.currentState as _PyramidDataLabelRendererState; + _pyramidDataLabelRendererState.dataLabelRepaintNotifier.value++; } } chartState._chartSeries ._seriesPointSelection(pointIndex, ActivationMode.doubleTap); if (chart.tooltipBehavior.enable && - chartState._animateCompleted && + chartState._animateCompleted! && chart.tooltipBehavior.activationMode == ActivationMode.doubleTap) { if (chart.tooltipBehavior.builder != null) { _showPyramidTooltipTemplate(); } else { chartState._tooltipBehaviorRenderer.onDoubleTap( - doubleTapPosition.dx.toDouble(), doubleTapPosition.dy.toDouble()); + doubleTapPosition!.dx.toDouble(), + doubleTapPosition!.dy.toDouble()); } } } @@ -1029,34 +1139,38 @@ class _PyramidPlotArea extends StatelessWidget { /// To perform long press touch interactions void _onLongPress() { - const num seriesIndex = 0; + const int seriesIndex = 0; if (chartState._tapPosition != null && chartState._currentActive != null) { final List visibleSeriesRenderers = chartState._chartSeries.visibleSeriesRenderers; - final num pointIndex = chartState._currentActive.pointIndex; + final int pointIndex = chartState._currentActive!.pointIndex!; chartState._currentActive = _ChartInteraction( seriesIndex, pointIndex, visibleSeriesRenderers[seriesIndex]._series, - visibleSeriesRenderers[seriesIndex]._renderPoints[pointIndex], + visibleSeriesRenderers[seriesIndex]._renderPoints![pointIndex], pointRegion); chartState._chartSeries ._seriesPointSelection(pointIndex, ActivationMode.longPress); if (chartState._currentActive != null) { - if (chartState._currentActive.series.explodeGesture == + if (chartState._currentActive!.series.explodeGesture == ActivationMode.longPress) { chartState._chartSeries._pointExplode(pointIndex); + final GlobalKey key = chartState._renderDataLabel!.key as GlobalKey; + final _PyramidDataLabelRendererState _pyramidDataLabelRendererState = + key.currentState as _PyramidDataLabelRendererState; + _pyramidDataLabelRendererState.dataLabelRepaintNotifier.value++; } } if (chart.tooltipBehavior.enable && - chartState._animateCompleted && + chartState._animateCompleted! && chart.tooltipBehavior.activationMode == ActivationMode.longPress) { if (chart.tooltipBehavior.builder != null) { _showPyramidTooltipTemplate(); } else { chartState._tooltipBehaviorRenderer.onLongPress( - chartState._tapPosition.dx.toDouble(), - chartState._tapPosition.dy.toDouble()); + chartState._tapPosition!.dx.toDouble(), + chartState._tapPosition!.dy.toDouble()); } } } @@ -1065,47 +1179,60 @@ class _PyramidPlotArea extends StatelessWidget { /// To perform pointer up event void _onTapUp(PointerUpEvent event) { chartState._tooltipBehaviorRenderer._isHovering = false; - final _ChartInteraction currentActive = chartState._currentActive; + bool isPoint = false; chartState._tapPosition = renderBox.globalToLocal(event.position); - ChartTouchInteractionArgs touchArgs; - if (chart.onPointTapped != null && seriesRenderer != null) { - _calculatePointSeriesIndex( - chart, seriesRenderer, chartState._tapPosition); - } - if (chart.onDataLabelTapped != null && seriesRenderer != null) { - _triggerPyramidDataLabelEvent( - chart, seriesRenderer, chartState, chartState._tapPosition); + for (int j = 0; j < seriesRenderer._renderPoints!.length; j++) { + if (seriesRenderer._renderPoints![j].isVisible) { + isPoint = _isPointInPolygon(seriesRenderer._renderPoints![j].pathRegion, + chartState._tapPosition); + if (isPoint) { + break; + } + } } - if (chartState._tapPosition != null && chartState._currentActive != null) { - if (currentActive.series != null && - currentActive.series.explodeGesture == ActivationMode.singleTap) { - chartState._chartSeries._pointExplode(currentActive.pointIndex); + final _ChartInteraction? currentActive = + isPoint ? chartState._currentActive! : null; + ChartTouchInteractionArgs touchArgs; + if (currentActive != null) { + if (chart.onDataLabelTapped != null && seriesRenderer != null) { + _triggerPyramidDataLabelEvent( + chart, seriesRenderer, chartState, chartState._tapPosition!); } + if (chartState._tapPosition != null && + chartState._currentActive != null) { + if (currentActive.series != null && + currentActive.series.explodeGesture == ActivationMode.singleTap) { + chartState._chartSeries._pointExplode(currentActive.pointIndex!); + final GlobalKey key = chartState._renderDataLabel!.key as GlobalKey; + final _PyramidDataLabelRendererState _pyramidDataLabelRendererState = + key.currentState as _PyramidDataLabelRendererState; + _pyramidDataLabelRendererState.dataLabelRepaintNotifier.value++; + } - if (chartState - ._chartSeries.visibleSeriesRenderers[0]._isSelectionEnable) { - chartState._chartSeries._seriesPointSelection( - currentActive.pointIndex, ActivationMode.singleTap); - } + if (chartState + ._chartSeries.visibleSeriesRenderers[0]._isSelectionEnable) { + chartState._chartSeries._seriesPointSelection( + currentActive.pointIndex!, ActivationMode.singleTap); + } - if (chart.tooltipBehavior.enable && - chartState._animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.singleTap && - currentActive.series != null) { - if (chart.tooltipBehavior.builder != null) { - _showPyramidTooltipTemplate(); - } else { - // final RenderBox renderBox = context.findRenderObject(); - final Offset position = renderBox.globalToLocal(event.position); - chartState._tooltipBehaviorRenderer - .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); + if (chart.tooltipBehavior.enable && + chartState._animateCompleted! && + chart.tooltipBehavior.activationMode == ActivationMode.singleTap && + currentActive.series != null) { + if (chart.tooltipBehavior.builder != null) { + _showPyramidTooltipTemplate(); + } else { + final Offset position = renderBox.globalToLocal(event.position); + chartState._tooltipBehaviorRenderer + .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); + } } } } if (chart.onChartTouchInteractionUp != null) { touchArgs = ChartTouchInteractionArgs(); touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionUp(touchArgs); + chart.onChartTouchInteractionUp!(touchArgs); } chartState._tapPosition = null; } @@ -1114,17 +1241,17 @@ class _PyramidPlotArea extends StatelessWidget { void _onHover(PointerEvent event) { chartState._currentActive = null; chartState._tapPosition = renderBox.globalToLocal(event.position); - bool isPoint; - const num seriesIndex = 0; - num pointIndex; + bool? isPoint; + const int seriesIndex = 0; + int? pointIndex; final PyramidSeriesRenderer seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; final TooltipBehavior tooltip = chart.tooltipBehavior; final TooltipBehaviorRenderer tooltipBehaviorRenderer = chartState._tooltipBehaviorRenderer; - for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { - if (seriesRenderer._renderPoints[j].isVisible) { - isPoint = _isPointInPolygon(seriesRenderer._renderPoints[j].pathRegion, + for (int j = 0; j < seriesRenderer._renderPoints!.length; j++) { + if (seriesRenderer._renderPoints![j].isVisible) { + isPoint = _isPointInPolygon(seriesRenderer._renderPoints![j].pathRegion, chartState._tapPosition); if (isPoint) { pointIndex = j; @@ -1132,24 +1259,23 @@ class _PyramidPlotArea extends StatelessWidget { } } } - if (chartState._tapPosition != null && isPoint) { + if (chartState._tapPosition != null && isPoint != null && isPoint) { chartState._currentActive = _ChartInteraction( seriesIndex, - pointIndex, + pointIndex!, chartState._chartSeries.visibleSeriesRenderers[seriesIndex]._series, chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._renderPoints[pointIndex], + ._renderPoints![pointIndex], ); - } else if (tooltip?.builder != null) { - tooltipBehaviorRenderer?._tooltipTemplate?.show = false; - tooltipBehaviorRenderer?._tooltipTemplate?.state?.hideOnTimer(); + } else if (tooltip.builder != null) { + tooltipBehaviorRenderer._hide(); } if (chartState._tapPosition != null) { if (tooltip.enable && chartState._currentActive != null && - chartState._currentActive.series != null) { + chartState._currentActive!.series != null) { tooltipBehaviorRenderer._isHovering = true; - if (tooltip.builder != null && chartState._animateCompleted) { + if (tooltip.builder != null && chartState._animateCompleted!) { _showPyramidTooltipTemplate(); } else { final Offset position = renderBox.globalToLocal(event.position); @@ -1157,26 +1283,24 @@ class _PyramidPlotArea extends StatelessWidget { position.dx.toDouble(), position.dy.toDouble()); } } else { - tooltipBehaviorRenderer?._painter?.prevTooltipValue = null; - tooltipBehaviorRenderer?._painter?.currentTooltipValue = null; - tooltipBehaviorRenderer?._painter?.hide(); + tooltipBehaviorRenderer._prevTooltipValue = null; + tooltipBehaviorRenderer._currentTooltipValue = null; + tooltipBehaviorRenderer._hide(); } } chartState._tapPosition = null; } /// This method gets executed for showing tooltip when builder is provided in behavior - void _showPyramidTooltipTemplate([int pointIndex]) { + void _showPyramidTooltipTemplate([int? pointIndex]) { final TooltipBehavior tooltip = chart.tooltipBehavior; final TooltipBehaviorRenderer tooltipBehaviorRenderer = chartState._tooltipBehaviorRenderer; - tooltipBehaviorRenderer._tooltipTemplate?._alwaysShow = - tooltip.shouldAlwaysShow; + if (!tooltipBehaviorRenderer._isHovering) { //assingning null for the previous and current tooltip values in case of touch interaction - tooltipBehaviorRenderer._tooltipTemplate?.state?.prevTooltipValue = null; - tooltipBehaviorRenderer._tooltipTemplate?.state?.currentTooltipValue = - null; + tooltipBehaviorRenderer._prevTooltipValue = null; + tooltipBehaviorRenderer._currentTooltipValue = null; } final PyramidSeries chartSeries = chartState._currentActive?.series ?? chart.series; @@ -1184,27 +1308,46 @@ class _PyramidPlotArea extends StatelessWidget { ? chartState._currentActive?.point : chartState ._chartSeries.visibleSeriesRenderers[0]._dataPoints[pointIndex]; - final Offset location = point.symbolLocation; - if (location != null && (chartSeries.enableTooltip ?? true)) { - tooltipBehaviorRenderer._tooltipTemplate.rect = - Rect.fromLTWH(location.dx, location.dy, 0, 0); - tooltipBehaviorRenderer._tooltipTemplate.template = tooltip.builder( - chartSeries - .dataSource[pointIndex ?? chartState._currentActive.pointIndex], + final Offset? location = chart.tooltipBehavior.tooltipPosition == + TooltipPosition.pointer && + !chartState._chartSeries.visibleSeriesRenderers[0]._series.explode + ? chartState._tapPosition! + : point.symbolLocation; + bool isPoint = false; + for (int j = 0; j < seriesRenderer._renderPoints!.length; j++) { + if (seriesRenderer._renderPoints![j].isVisible) { + isPoint = _isPointInPolygon( + seriesRenderer._renderPoints![j].pathRegion, location); + if (isPoint) { + pointIndex = j; + break; + } + } + } + if (location != null && isPoint && (chartSeries.enableTooltip)) { + tooltipBehaviorRenderer._showLocation = location; + chartState._tooltipBehaviorRenderer._renderBox!.boundaryRect = + chartState._chartContainerRect; + tooltipBehaviorRenderer._tooltipTemplate = tooltip.builder!( + chartSeries.dataSource![ + pointIndex ?? chartState._currentActive!.pointIndex!], point, chartSeries, chartState._currentActive?.seriesIndex ?? 0, - pointIndex ?? chartState._currentActive?.pointIndex); + pointIndex ?? chartState._currentActive!.pointIndex!); if (tooltipBehaviorRenderer._isHovering) { //assingning values for the previous and current tooltip values on mouse hover - tooltipBehaviorRenderer._tooltipTemplate.state.prevTooltipValue = - tooltipBehaviorRenderer._tooltipTemplate.state.currentTooltipValue; - tooltipBehaviorRenderer._tooltipTemplate.state.currentTooltipValue = - TooltipValue( - 0, pointIndex ?? chartState._currentActive?.pointIndex); + tooltipBehaviorRenderer._prevTooltipValue = + tooltipBehaviorRenderer._currentTooltipValue; + tooltipBehaviorRenderer._currentTooltipValue = TooltipValue( + 0, pointIndex ?? chartState._currentActive!.pointIndex!); + } else { + chartState._tooltipBehaviorRenderer._timer = Timer( + Duration(milliseconds: chart.tooltipBehavior.duration.toInt()), + chartState._tooltipBehaviorRenderer._hideTooltipTemplate); } - tooltipBehaviorRenderer._tooltipTemplate.show = true; - tooltipBehaviorRenderer._tooltipTemplate?.state?._performTooltip(); + tooltipBehaviorRenderer._show = true; + tooltipBehaviorRenderer._performTooltip(); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/series_base.dart index effee4fd3..d1f49bdfb 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/series_base.dart @@ -5,11 +5,11 @@ class _PyramidSeries { final SfPyramidChartState _chartState; - PyramidSeries currentSeries; + late PyramidSeries currentSeries; List visibleSeriesRenderers = []; - SelectionArgs _selectionArgs; + SelectionArgs? _selectionArgs; /// To find the visible series void _findVisibleSeries() { @@ -22,14 +22,14 @@ class _PyramidSeries { currentSeries = seriesRenderer._series; //Setting seriestype seriesRenderer._seriesType = 'pyramid'; - final ChartIndexedValueMapper xValue = currentSeries.xValueMapper; - final ChartIndexedValueMapper yValue = currentSeries.yValueMapper; + final ChartIndexedValueMapper? xValue = currentSeries.xValueMapper; + final ChartIndexedValueMapper? yValue = currentSeries.yValueMapper; for (int pointIndex = 0; - pointIndex < currentSeries.dataSource.length; + pointIndex < currentSeries.dataSource!.length; pointIndex++) { - if (xValue(pointIndex) != null) { + if (xValue!(pointIndex) != null) { seriesRenderer._dataPoints - .add(PointInfo(xValue(pointIndex), yValue(pointIndex))); + .add(PointInfo(xValue(pointIndex), yValue!(pointIndex))); } } visibleSeriesRenderers @@ -64,7 +64,7 @@ class _PyramidSeries { seriesRenderer._renderPoints = >[]; for (int i = 0; i < points.length; i++) { if (points[i].isVisible) { - seriesRenderer._renderPoints.add(points[i]); + seriesRenderer._renderPoints!.add(points[i]); } } } @@ -73,18 +73,18 @@ class _PyramidSeries { void _setPointStyle(PyramidSeriesRenderer seriesRenderer) { currentSeries = seriesRenderer._series; final List palette = _chartState._chart.palette; - final ChartIndexedValueMapper pointColor = + final ChartIndexedValueMapper? pointColor = currentSeries.pointColorMapper; final EmptyPointSettings empty = currentSeries.emptyPointSettings; - final ChartIndexedValueMapper textMapping = + final ChartIndexedValueMapper? textMapping = currentSeries.textFieldMapper; - final List> points = seriesRenderer._renderPoints; + final List> points = seriesRenderer._renderPoints!; for (int i = 0; i < points.length; i++) { PointInfo currentPoint; currentPoint = points[i]; currentPoint.fill = currentPoint.isEmpty && empty.color != null ? empty.color - : pointColor(i) ?? palette[i % palette.length]; + : pointColor!(i) ?? palette[i % palette.length]; currentPoint.color = currentPoint.fill; currentPoint.borderColor = currentPoint.isEmpty && empty.borderColor != null @@ -133,9 +133,9 @@ class _PyramidSeries { /// To find the sum of points void _findSumOfPoints(PyramidSeriesRenderer seriesRenderer) { seriesRenderer._sumOfPoints = 0; - for (final PointInfo point in seriesRenderer._renderPoints) { + for (final PointInfo point in seriesRenderer._renderPoints!) { if (point.isVisible) { - seriesRenderer._sumOfPoints += point.y.abs(); + seriesRenderer._sumOfPoints += point.y!.abs(); } } } @@ -146,10 +146,10 @@ class _PyramidSeries { final Rect chartAreaRect = _chartState._chartAreaRect; final bool reverse = seriesRenderer._seriesType == 'pyramid' ? true : false; seriesRenderer._triangleSize = Size( - _percentToValue(series.width, chartAreaRect.width).toDouble(), - _percentToValue(series.height, chartAreaRect.height).toDouble()); + _percentToValue(series.width, chartAreaRect.width)!.toDouble(), + _percentToValue(series.height, chartAreaRect.height)!.toDouble()); seriesRenderer._explodeDistance = - _percentToValue(series.explodeOffset, chartAreaRect.width); + _percentToValue(series.explodeOffset, chartAreaRect.width)!; if (series.pyramidMode == PyramidMode.linear) { _initializeSizeRatio(seriesRenderer, reverse); } else { @@ -159,7 +159,7 @@ class _PyramidSeries { /// To intialize the surface size ratio in chart void _initializeSurfaceSizeRatio(PyramidSeriesRenderer seriesRenderer) { - final num count = seriesRenderer._renderPoints.length; + final num count = seriesRenderer._renderPoints!.length; final num sumOfValues = seriesRenderer._sumOfPoints; List y; List height; @@ -170,17 +170,17 @@ class _PyramidSeries { final num preSum = _getSurfaceHeight(0, sumOfValues); num currY = 0; PointInfo point; - for (num i = 0; i < count; i++) { - point = seriesRenderer._renderPoints[i]; + for (int i = 0; i < count; i++) { + point = seriesRenderer._renderPoints![i]; if (point.isVisible) { y.add(currY); - height.add(_getSurfaceHeight(currY, point.y.abs())); + height.add(_getSurfaceHeight(currY, point.y!.abs())); currY += height[i] + gapHeight * preSum; } } final num coef = 1 / (currY - gapHeight * preSum); - for (num i = 0; i < count; i++) { - point = seriesRenderer._renderPoints[i]; + for (int i = 0; i < count; i++) { + point = seriesRenderer._renderPoints![i]; if (point.isVisible) { point.yRatio = coef * y[i]; point.heightRatio = coef * height[i]; @@ -208,8 +208,8 @@ class _PyramidSeries { /// To initialise size ratio for the pyramid void _initializeSizeRatio(PyramidSeriesRenderer seriesRenderer, - [bool reverse]) { - final List> points = seriesRenderer._renderPoints; + [bool? reverse]) { + final List> points = seriesRenderer._renderPoints!; double y; assert( seriesRenderer._series.gapRatio != null @@ -222,12 +222,12 @@ class _PyramidSeries { 1 / (seriesRenderer._sumOfPoints * (1 + gapRatio / (1 - gapRatio))); final double spacing = gapRatio / (points.length - 1); y = 0; - num index; + int index; num height; - for (num i = points.length - 1; i >= 0; i--) { - index = reverse ? points.length - 1 - i : i; + for (int i = points.length - 1; i >= 0; i--) { + index = reverse! ? points.length - 1 - i : i; if (points[index].isVisible) { - height = coEff * points[index].y; + height = coEff * points[index].y!; points[index].yRatio = y; points[index].heightRatio = height; y += height + spacing; @@ -236,17 +236,17 @@ class _PyramidSeries { } /// To explode current point index - void _pointExplode(num pointIndex) { + void _pointExplode(int pointIndex) { bool existExplodedRegion = false; final PyramidSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[0]; final SfPyramidChartState chartState = _chartState; - final PointInfo point = seriesRenderer._renderPoints[pointIndex]; + final PointInfo point = seriesRenderer._renderPoints![pointIndex]; if (seriesRenderer._series.explode) { if (chartState._explodedPoints.isNotEmpty) { existExplodedRegion = true; final int previousIndex = chartState._explodedPoints[0]; - seriesRenderer._renderPoints[previousIndex].explodeDistance = 0; + seriesRenderer._renderPoints![previousIndex].explodeDistance = 0; point.explodeDistance = previousIndex == pointIndex ? 0 : seriesRenderer._explodeDistance; chartState._explodedPoints[0] = pointIndex; @@ -266,9 +266,9 @@ class _PyramidSeries { /// To calculate region path for rendering chart void _calculatePathRegion( - num pointIndex, PyramidSeriesRenderer seriesRenderer) { + int pointIndex, PyramidSeriesRenderer seriesRenderer) { final PointInfo currentPoint = - seriesRenderer._renderPoints[pointIndex]; + seriesRenderer._renderPoints![pointIndex]; currentPoint.pathRegion = []; final SfPyramidChartState chartState = _chartState; final Size area = seriesRenderer._triangleSize; @@ -277,7 +277,7 @@ class _PyramidSeries { const num offset = 0; // ignore: prefer_if_null_operators final num extraSpace = (currentPoint.explodeDistance != null - ? currentPoint.explodeDistance + ? currentPoint.explodeDistance! : _isNeedExplode(pointIndex, currentSeries, _chartState) ? seriesRenderer._explodeDistance : 0) + @@ -298,19 +298,19 @@ class _PyramidSeries { line3Y = bottom * area.height; line4X = emptySpaceAtLeft + offset + bottomRadius * area.width; line4Y = bottom * area.height; - currentPoint.pathRegion.add(Offset(line1X, line1Y)); - currentPoint.pathRegion.add(Offset(line2X, line2Y)); - currentPoint.pathRegion.add(Offset(line3X, line3Y)); - currentPoint.pathRegion.add(Offset(line4X, line4Y)); + currentPoint.pathRegion.add(Offset(line1X.toDouble(), line1Y.toDouble())); + currentPoint.pathRegion.add(Offset(line2X.toDouble(), line2Y.toDouble())); + currentPoint.pathRegion.add(Offset(line3X.toDouble(), line3Y.toDouble())); + currentPoint.pathRegion.add(Offset(line4X.toDouble(), line4Y.toDouble())); _calculatePathSegment(seriesRenderer._seriesType, currentPoint); } /// To calculate pyramid segments void _calculatePyramidSegments( - Canvas canvas, num pointIndex, PyramidSeriesRenderer seriesRenderer) { + Canvas canvas, int pointIndex, PyramidSeriesRenderer seriesRenderer) { _calculatePathRegion(pointIndex, seriesRenderer); final PointInfo currentPoint = - seriesRenderer._renderPoints[pointIndex]; + seriesRenderer._renderPoints![pointIndex]; final Path path = Path(); path.moveTo(currentPoint.pathRegion[0].dx, currentPoint.pathRegion[0].dy); path.lineTo(currentPoint.pathRegion[1].dx, currentPoint.pathRegion[0].dy); @@ -318,66 +318,65 @@ class _PyramidSeries { path.lineTo(currentPoint.pathRegion[2].dx, currentPoint.pathRegion[2].dy); path.lineTo(currentPoint.pathRegion[3].dx, currentPoint.pathRegion[3].dy); path.close(); - if (pointIndex == seriesRenderer._renderPoints.length - 1) { + if (pointIndex == seriesRenderer._renderPoints!.length - 1) { seriesRenderer._maximumDataLabelRegion = path.getBounds(); } _segmentPaint(canvas, path, pointIndex, seriesRenderer); } /// To paint the funnel segments - void _segmentPaint(Canvas canvas, Path path, num pointIndex, + void _segmentPaint(Canvas canvas, Path path, int pointIndex, PyramidSeriesRenderer seriesRenderer) { - final PointInfo point = seriesRenderer._renderPoints[pointIndex]; - final _StyleOptions style = + final PointInfo point = seriesRenderer._renderPoints![pointIndex]; + final _StyleOptions? style = _getPointStyle(pointIndex, seriesRenderer, _chartState._chart, point); final Color fillColor = - style != null && style.fill != null ? style.fill : point.fill; + style != null && style.fill != null ? style.fill! : point.fill; final Color strokeColor = style != null && style.strokeColor != null - ? style.strokeColor + ? style.strokeColor! : point.borderColor; final double strokeWidth = style != null && style.strokeWidth != null - ? style.strokeWidth - : point.borderWidth; + ? style.strokeWidth!.toDouble() + : point.borderWidth.toDouble(); final double opacity = style != null && style.opacity != null - ? style.opacity + ? style.opacity! : currentSeries.opacity; _drawPath( canvas, _StyleOptions( - fillColor, - _chartState._animateCompleted ? strokeWidth : 0, - strokeColor, - opacity), + fill: fillColor, + strokeWidth: _chartState._animateCompleted! ? strokeWidth : 0, + strokeColor: strokeColor, + opacity: opacity), path); } /// To calculate the segment path void _calculatePathSegment(String seriesType, PointInfo point) { final List pathRegion = point.pathRegion; - final num bottom = + final int bottom = seriesType == 'funnel' ? pathRegion.length - 2 : pathRegion.length - 1; final num x = (pathRegion[0].dx + pathRegion[bottom].dx) / 2; final num right = (pathRegion[1].dx + pathRegion[bottom - 1].dx) / 2; - point.region = Rect.fromLTWH(x, pathRegion[0].dy, right - x, - pathRegion[bottom].dy - pathRegion[0].dy); - point.symbolLocation = Offset(point.region.left + point.region.width / 2, - point.region.top + point.region.height / 2); + point.region = Rect.fromLTWH(x.toDouble(), pathRegion[0].dy, + (right - x).toDouble(), pathRegion[bottom].dy - pathRegion[0].dy); + point.symbolLocation = Offset(point.region!.left + point.region!.width / 2, + point.region!.top + point.region!.height / 2); } /// To add selection points to selection list - void _seriesPointSelection(num pointIndex, ActivationMode mode) { + void _seriesPointSelection(int pointIndex, ActivationMode mode) { bool isPointAlreadySelected = false; final SfPyramidChart chart = _chartState._chart; final PyramidSeriesRenderer seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[0]; - // final PyramidSeries series = seriesRenderer._series; final SfPyramidChartState chartState = _chartState; - int currentSelectedIndex; + int? currentSelectedIndex; if (seriesRenderer._isSelectionEnable && mode == chart.selectionGesture) { if (chartState._selectionData.isNotEmpty) { if (!chart.enableMultiSelection && @@ -414,49 +413,49 @@ class _PyramidSeries { } /// To return style options for the point on selection - _StyleOptions _getPointStyle( + _StyleOptions? _getPointStyle( int currentPointIndex, PyramidSeriesRenderer seriesRenderer, SfPyramidChart chart, PointInfo point) { - _StyleOptions pointStyle; + _StyleOptions? pointStyle; final dynamic selection = seriesRenderer._series.selectionBehavior.enable ? seriesRenderer._series.selectionBehavior : seriesRenderer._series.selectionSettings; - const num seriesIndex = 0; + const int seriesIndex = 0; if (selection.enable) { if (_chartState._selectionData.isNotEmpty) { for (int i = 0; i < _chartState._selectionData.length; i++) { final int selectionIndex = _chartState._selectionData[i]; if (chart.onSelectionChanged != null) { - chart.onSelectionChanged(_getSelectionEventArgs( + chart.onSelectionChanged!(_getSelectionEventArgs( seriesRenderer, seriesIndex, selectionIndex)); } if (currentPointIndex == selectionIndex) { pointStyle = _StyleOptions( - _selectionArgs != null - ? _selectionArgs.selectedColor - : selection.selectedColor, - _selectionArgs != null - ? _selectionArgs.selectedBorderWidth - : selection.selectedBorderWidth, - _selectionArgs != null - ? _selectionArgs.selectedBorderColor - : selection.selectedBorderColor, - selection.selectedOpacity); + fill: _selectionArgs != null + ? _selectionArgs!.selectedColor + : selection!.selectedColor, + strokeWidth: _selectionArgs != null + ? _selectionArgs!.selectedBorderWidth + : selection!.selectedBorderWidth, + strokeColor: _selectionArgs != null + ? _selectionArgs!.selectedBorderColor + : selection!.selectedBorderColor, + opacity: selection.selectedOpacity); break; } else if (i == _chartState._selectionData.length - 1) { pointStyle = _StyleOptions( - _selectionArgs != null - ? _selectionArgs.unselectedColor + fill: _selectionArgs != null + ? _selectionArgs!.unselectedColor : selection.unselectedColor, - _selectionArgs != null - ? _selectionArgs.unselectedBorderWidth + strokeWidth: _selectionArgs != null + ? _selectionArgs!.unselectedBorderWidth : selection.unselectedBorderWidth, - _selectionArgs != null - ? _selectionArgs.unselectedBorderColor + strokeColor: _selectionArgs != null + ? _selectionArgs!.unselectedBorderColor : selection.unselectedBorderColor, - selection.unselectedOpacity); + opacity: selection.unselectedOpacity); } } } @@ -466,23 +465,28 @@ class _PyramidSeries { /// To perform selection event and return selectionArgs SelectionArgs _getSelectionEventArgs( - dynamic seriesRenderer, num seriesIndex, num pointIndex) { - final SfPyramidChart chart = seriesRenderer._chartState._chart; - if (seriesRenderer != null && pointIndex < chart.series.dataSource.length) { + dynamic seriesRenderer, int seriesIndex, int pointIndex) { + final SfPyramidChart chart = seriesRenderer._chartState!._chart; + if (seriesRenderer != null && + chart.series != null && + pointIndex < chart.series.dataSource!.length) { final dynamic selectionBehavior = seriesRenderer._selectionBehavior; - _selectionArgs = - SelectionArgs(seriesRenderer, seriesIndex, pointIndex, pointIndex); - _selectionArgs.selectedBorderColor = + _selectionArgs = SelectionArgs( + seriesRenderer: seriesRenderer, + seriesIndex: seriesIndex, + viewportPointIndex: pointIndex, + pointIndex: pointIndex); + _selectionArgs!.selectedBorderColor = selectionBehavior.selectedBorderColor; - _selectionArgs.selectedBorderWidth = + _selectionArgs!.selectedBorderWidth = selectionBehavior.selectedBorderWidth; - _selectionArgs.selectedColor = selectionBehavior.selectedColor; - _selectionArgs.unselectedBorderColor = + _selectionArgs!.selectedColor = selectionBehavior.selectedColor; + _selectionArgs!.unselectedBorderColor = selectionBehavior.unselectedBorderColor; - _selectionArgs.unselectedBorderWidth = + _selectionArgs!.unselectedBorderWidth = selectionBehavior.unselectedBorderWidth; - _selectionArgs.unselectedColor = selectionBehavior.unselectedColor; + _selectionArgs!.unselectedColor = selectionBehavior.unselectedColor; } - return _selectionArgs; + return _selectionArgs!; } } diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart index e7b90db60..59b20b769 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart @@ -3,13 +3,15 @@ part of charts; // ignore: must_be_immutable class _PyramidDataLabelRenderer extends StatefulWidget { // ignore: prefer_const_constructors_in_immutables - _PyramidDataLabelRenderer({this.chartState, this.show}); + _PyramidDataLabelRenderer( + {required Key key, required this.chartState, required this.show}) + : super(key: key); final SfPyramidChartState chartState; bool show; - _PyramidDataLabelRendererState state; + _PyramidDataLabelRendererState? state; @override State createState() { @@ -19,13 +21,13 @@ class _PyramidDataLabelRenderer extends StatefulWidget { class _PyramidDataLabelRendererState extends State<_PyramidDataLabelRenderer> with SingleTickerProviderStateMixin { - List animationControllersList; + late List animationControllersList; /// Animation controller for series - AnimationController animationController; + late AnimationController animationController; /// Repaint notifier for crosshair container - ValueNotifier dataLabelRepaintNotifier; + late ValueNotifier dataLabelRepaintNotifier; @override void initState() { @@ -39,7 +41,7 @@ class _PyramidDataLabelRendererState extends State<_PyramidDataLabelRenderer> Widget build(BuildContext context) { widget.state = this; animationController.duration = - Duration(milliseconds: widget.chartState._initialRender ? 500 : 0); + Duration(milliseconds: widget.chartState._initialRender! ? 500 : 0); final Animation dataLabelAnimation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, @@ -78,11 +80,11 @@ class _PyramidDataLabelRendererState extends State<_PyramidDataLabelRenderer> class _PyramidDataLabelPainter extends CustomPainter { _PyramidDataLabelPainter( - {this.chartState, - this.state, - this.animationController, - this.animation, - ValueNotifier notifier}) + {required this.chartState, + required this.state, + required this.animationController, + required this.animation, + required ValueNotifier notifier}) : super(repaint: notifier); final SfPyramidChartState chartState; @@ -120,31 +122,31 @@ void _renderPyramidDataLabel( final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; final DataLabelSettingsRenderer dataLabelSettingsRenderer = seriesRenderer._dataLabelSettingsRenderer; - String label; + String? label; final double animateOpacity = animation != null ? animation.value : 1; DataLabelRenderArgs dataLabelArgs; TextStyle dataLabelStyle; final List renderDataLabelRegions = []; for (int pointIndex = 0; - pointIndex < seriesRenderer._renderPoints.length; + pointIndex < seriesRenderer._renderPoints!.length; pointIndex++) { - point = seriesRenderer._renderPoints[pointIndex]; + point = seriesRenderer._renderPoints![pointIndex]; if (point.isVisible && (point.y != 0 || dataLabel.showZeroValue)) { label = point.text; dataLabelStyle = dataLabel.textStyle; dataLabelSettingsRenderer._color = seriesRenderer._series.dataLabelSettings.color; if (chart.onDataLabelRender != null && - !seriesRenderer._renderPoints[pointIndex].labelRenderEvent) { + !seriesRenderer._renderPoints![pointIndex].labelRenderEvent) { dataLabelArgs = DataLabelRenderArgs(seriesRenderer, seriesRenderer._renderPoints, pointIndex, pointIndex); - dataLabelArgs.text = label; + dataLabelArgs.text = label!; dataLabelArgs.textStyle = dataLabelStyle; dataLabelArgs.color = dataLabelSettingsRenderer._color; - chart.onDataLabelRender(dataLabelArgs); + chart.onDataLabelRender!(dataLabelArgs); label = point.text = dataLabelArgs.text; dataLabelStyle = dataLabelArgs.textStyle; - pointIndex = dataLabelArgs.pointIndex; + pointIndex = dataLabelArgs.pointIndex!; dataLabelSettingsRenderer._color = dataLabelArgs.color; if (animation.status == AnimationStatus.completed) { seriesRenderer._dataPoints[pointIndex].labelRenderEvent = true; @@ -154,7 +156,7 @@ void _renderPyramidDataLabel( ? _getDataLabelTextStyle( seriesRenderer, point, chartState, animateOpacity) : dataLabelStyle; - final Size textSize = _measureText(label, dataLabelStyle); + final Size textSize = measureText(label!, dataLabelStyle); ///Label check after event if (label != '') { @@ -173,6 +175,8 @@ void _renderPyramidDataLabel( dataLabelStyle); } else { point.renderPosition = ChartDataLabelPosition.outside; + dataLabelStyle = _getDataLabelTextStyle( + seriesRenderer, point, chartState, animateOpacity); _renderOutsidePyramidDataLabel( canvas, label, @@ -221,10 +225,12 @@ void _setPyramidInsideLabelPosition( textSize.width + (2 * labelPadding), textSize.height + (2 * labelPadding)); final bool isDataLabelCollide = - _findingCollision(point.labelRect, renderDataLabelRegions, point.region); + _findingCollision(point.labelRect!, renderDataLabelRegions, point.region); if (isDataLabelCollide && smartLabelMode == SmartLabelMode.shift) { point.saturationRegionOutside = true; point.renderPosition = ChartDataLabelPosition.outside; + dataLabelStyle = _getDataLabelTextStyle( + seriesRenderer, point, chartState, animateOpacity); _renderOutsidePyramidDataLabel( canvas, label, @@ -241,7 +247,7 @@ void _setPyramidInsideLabelPosition( (!isDataLabelCollide && smartLabelMode == SmartLabelMode.hide)) { point.renderPosition = ChartDataLabelPosition.inside; _drawPyramidLabel( - point.labelRect, + point.labelRect!, labelLocation, label, null, @@ -269,7 +275,7 @@ void _renderOutsidePyramidDataLabel( List renderDataLabelRegions, double animateOpacity) { Path connectorPath; - Rect rect; + Rect? rect; Offset labelLocation; final EdgeInsets margin = seriesRenderer._series.dataLabelSettings.margin; final ConnectorLineSettings connector = @@ -277,74 +283,48 @@ void _renderOutsidePyramidDataLabel( const num regionPadding = 12; connectorPath = Path(); final num connectorLength = _percentToValue( - connector.length ?? '0%', _chartState._chartAreaRect.width / 2) + + connector.length ?? '0%', _chartState._chartAreaRect.width / 2)! + seriesRenderer._maximumDataLabelRegion.width / 2 - regionPadding; final Offset startPoint = Offset( - seriesRenderer._renderPoints[pointIndex].region.right, - seriesRenderer._renderPoints[pointIndex].region.top + - seriesRenderer._renderPoints[pointIndex].region.height / 2); + seriesRenderer._renderPoints![pointIndex].region!.right, + seriesRenderer._renderPoints![pointIndex].region!.top + + seriesRenderer._renderPoints![pointIndex].region!.height / 2); + final double dx = + seriesRenderer._renderPoints![pointIndex].symbolLocation.dx + + connectorLength; final Offset endPoint = Offset( - seriesRenderer._renderPoints[pointIndex].symbolLocation.dx + - connectorLength, - seriesRenderer._renderPoints[pointIndex].symbolLocation.dy); + (dx + textSize.width + margin.left + margin.right > + _chartState._chartAreaRect.right) + ? dx - + (_percentToValue(seriesRenderer._series.explodeOffset, + _chartState._chartAreaRect.width)!) + : dx, + seriesRenderer._renderPoints![pointIndex].symbolLocation.dy); connectorPath.moveTo(startPoint.dx, startPoint.dy); if (connector.type == ConnectorType.line) { connectorPath.lineTo(endPoint.dx, endPoint.dy); } point.dataLabelPosition = Position.right; - rect = _getDataLabelRect(point.dataLabelPosition, connector.type, margin, + rect = _getDataLabelRect(point.dataLabelPosition!, connector.type, margin, connectorPath, endPoint, textSize); - point.labelRect = rect; - labelLocation = Offset(rect.left + margin.left, - rect.top + rect.height / 2 - textSize.height / 2); - final Rect containerRect = _chartState._chartAreaRect; - if (seriesRenderer._series.dataLabelSettings.builder == null) { - Rect lastRenderedLabelRegion; - if (renderDataLabelRegions.isNotEmpty) { - lastRenderedLabelRegion = - renderDataLabelRegions[renderDataLabelRegions.length - 1]; - } - if (!_isPyramidLabelIntersect(rect, lastRenderedLabelRegion) && - (rect.left > containerRect.left && - rect.left + rect.width < - containerRect.left + containerRect.width) && - rect.top > containerRect.top && - rect.top + rect.height < containerRect.top + containerRect.height) { - _drawPyramidLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - _chartState, - textStyle, - renderDataLabelRegions, - animateOpacity); - } else { - if (pointIndex != 0) { - const num connectorLinePadding = 15; - const num padding = 2; - final Rect previousRenderedRect = + if (rect != null) { + point.labelRect = rect; + labelLocation = Offset(rect.left + margin.left, + rect.top + rect.height / 2 - textSize.height / 2); + final Rect containerRect = _chartState._chartAreaRect; + if (seriesRenderer._series.dataLabelSettings.builder == null) { + Rect? lastRenderedLabelRegion; + if (renderDataLabelRegions.isNotEmpty) { + lastRenderedLabelRegion = renderDataLabelRegions[renderDataLabelRegions.length - 1]; - rect = Rect.fromLTWH(rect.left, previousRenderedRect.bottom + padding, - rect.width, rect.height); - labelLocation = Offset( - rect.left + margin.left, - previousRenderedRect.bottom + - padding + - rect.height / 2 - - textSize.height / 2); - connectorPath = Path(); - connectorPath.moveTo(startPoint.dx, startPoint.dy); - connectorPath.lineTo( - rect.left - connectorLinePadding, rect.top + rect.height / 2); - connectorPath.lineTo(rect.left, rect.top + rect.height / 2); } - if (rect.bottom < _chartState._chartAreaRect.bottom) { + if (!_isPyramidLabelIntersect(rect, lastRenderedLabelRegion) && + (rect.left > containerRect.left && + rect.left + rect.width < + containerRect.left + containerRect.width) && + rect.top > containerRect.top && + rect.top + rect.height < containerRect.top + containerRect.height) { _drawPyramidLabel( rect, labelLocation, @@ -358,13 +338,48 @@ void _renderOutsidePyramidDataLabel( textStyle, renderDataLabelRegions, animateOpacity); + } else { + if (pointIndex != 0) { + const num connectorLinePadding = 15; + const num padding = 2; + final Rect previousRenderedRect = + renderDataLabelRegions[renderDataLabelRegions.length - 1]; + rect = Rect.fromLTWH(rect.left, previousRenderedRect.bottom + padding, + rect.width, rect.height); + labelLocation = Offset( + rect.left + margin.left, + previousRenderedRect.bottom + + padding + + rect.height / 2 - + textSize.height / 2); + connectorPath = Path(); + connectorPath.moveTo(startPoint.dx, startPoint.dy); + connectorPath.lineTo( + rect.left - connectorLinePadding, rect.top + rect.height / 2); + connectorPath.lineTo(rect.left, rect.top + rect.height / 2); + } + if (rect.bottom < _chartState._chartAreaRect.bottom) { + _drawPyramidLabel( + rect, + labelLocation, + label, + connectorPath, + canvas, + seriesRenderer, + point, + pointIndex, + _chartState, + textStyle, + renderDataLabelRegions, + animateOpacity); + } } } } } /// To check whether labels intesect -bool _isPyramidLabelIntersect(Rect rect, Rect previousRect) { +bool _isPyramidLabelIntersect(Rect rect, Rect? previousRect) { bool isIntersect = false; const num padding = 2; if (previousRect != null && (rect.top - padding) < previousRect.bottom) { @@ -377,8 +392,8 @@ bool _isPyramidLabelIntersect(Rect rect, Rect previousRect) { void _drawPyramidLabel( Rect labelRect, Offset location, - String label, - Path connectorPath, + String? label, + Path? connectorPath, Canvas canvas, PyramidSeriesRenderer seriesRenderer, PointInfo point, @@ -408,15 +423,15 @@ void _drawPyramidLabel( if (dataLabel.builder == null) { final double strokeWidth = dataLabel.borderWidth; - final Color labelFill = dataLabelSettingsRenderer._color ?? + final Color? labelFill = dataLabelSettingsRenderer._color ?? (dataLabel.useSeriesColor ? point.fill : dataLabelSettingsRenderer._color); - final Color strokeColor = - dataLabel.borderColor.withOpacity(dataLabel.opacity) ?? point.fill; + final Color? strokeColor = + dataLabel.borderColor.withOpacity(dataLabel.opacity); if (strokeWidth != null && strokeWidth > 0) { rectPaint = Paint() - ..color = strokeColor.withOpacity( + ..color = strokeColor!.withOpacity( !chartState._isLegendToggled ? animateOpacity : dataLabel.opacity) ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth; @@ -441,7 +456,7 @@ void _drawPyramidLabel( dataLabel.borderRadius, canvas); } - _drawText(canvas, label, location, textStyle, dataLabel.angle); + _drawText(canvas, label!, location, textStyle, dataLabel.angle); renderDataLabelRegions.add(labelRect); } } @@ -453,15 +468,16 @@ void _triggerPyramidDataLabelEvent( Offset position) { final int seriesIndex = 0; for (int pointIndex = 0; - pointIndex < seriesRenderer._renderPoints.length; + pointIndex < seriesRenderer._renderPoints!.length; pointIndex++) { final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; - final PointInfo point = seriesRenderer._renderPoints[pointIndex]; + final PointInfo point = seriesRenderer._renderPoints![pointIndex]; final Offset labelLocation = point.symbolLocation; if (dataLabel.isVisible && - seriesRenderer._renderPoints[pointIndex].labelRect != null && - seriesRenderer._renderPoints[pointIndex].labelRect.contains(position)) { + seriesRenderer._renderPoints![pointIndex].labelRect != null && + seriesRenderer._renderPoints![pointIndex].labelRect! + .contains(position)) { position = Offset(labelLocation.dx, labelLocation.dy); _dataLabelTapEvent(chart, seriesRenderer._series.dataLabelSettings, pointIndex, point, position, seriesIndex); diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_series.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_series.dart index b351ef5d4..48036ff5a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_series.dart @@ -13,24 +13,24 @@ class _PyramidSeriesBase extends ChartSeries this.textFieldMapper, this.name, this.explodeIndex, - String height, - String width, - PyramidMode pyramidMode, - double gapRatio, - EmptyPointSettings emptyPointSettings, - String explodeOffset, - bool explode, - ActivationMode explodeGesture, - Color borderColor, - double borderWidth, - LegendIconType legendIconType, - DataLabelSettings dataLabelSettings, - double animationDuration, - double opacity, + String? height, + String? width, + PyramidMode? pyramidMode, + double? gapRatio, + EmptyPointSettings? emptyPointSettings, + String? explodeOffset, + bool? explode, + ActivationMode? explodeGesture, + Color? borderColor, + double? borderWidth, + LegendIconType? legendIconType, + DataLabelSettings? dataLabelSettings, + double? animationDuration, + double? opacity, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - List initialSelectedDataIndexes, + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + List? initialSelectedDataIndexes, }) : height = height ?? '80%', width = width ?? '80%', pyramidMode = pyramidMode ?? PyramidMode.linear, @@ -77,7 +77,7 @@ class _PyramidSeriesBase extends ChartSeries /// )); ///} @override - final List dataSource; + final List? dataSource; ///Maps the field name, which will be considered as x-values. /// @@ -105,7 +105,7 @@ class _PyramidSeriesBase extends ChartSeries ///} ///``` @override - final ChartIndexedValueMapper xValueMapper; + final ChartIndexedValueMapper? xValueMapper; ///Maps the field name, which will be considered as y-values. /// @@ -133,7 +133,7 @@ class _PyramidSeriesBase extends ChartSeries ///} ///``` @override - final ChartIndexedValueMapper yValueMapper; + final ChartIndexedValueMapper? yValueMapper; ///Name of the series. /// @@ -149,7 +149,7 @@ class _PyramidSeriesBase extends ChartSeries ///} ///``` @override - final String name; + final String? name; ///Height of the series. /// @@ -380,7 +380,7 @@ class _PyramidSeriesBase extends ChartSeries ///} ///``` @override - final ChartIndexedValueMapper pointColorMapper; + final ChartIndexedValueMapper? pointColorMapper; ///Maps the field name, which will be considered as text for data label. /// @@ -402,7 +402,7 @@ class _PyramidSeriesBase extends ChartSeries /// final Color pointColor; ///} ///``` - final ChartIndexedValueMapper textFieldMapper; + final ChartIndexedValueMapper? textFieldMapper; ///Opacity of the series. The value ranges from 0 to 1. /// @@ -473,7 +473,7 @@ class _PyramidSeriesBase extends ChartSeries /// )); ///} ///``` - final num explodeIndex; + final num? explodeIndex; /// List of data indexes initially selected /// @@ -521,7 +521,7 @@ class _PyramidSeriesBase extends ChartSeries /// )); ///} ///``` - final ValueKey key; + final ValueKey? key; ///Used to create the renderer for custom series. /// @@ -552,7 +552,7 @@ class _PyramidSeriesBase extends ChartSeries /// // custom implementation here... /// } ///``` - final ChartSeriesRendererFactory onCreateRenderer; + final ChartSeriesRendererFactory? onCreateRenderer; ///Triggers when the series renderer is created. @@ -577,7 +577,7 @@ class _PyramidSeriesBase extends ChartSeries /// )); ///} ///``` - final PyramidSeriesRendererCreatedCallback onRendererCreated; + final PyramidSeriesRendererCreatedCallback? onRendererCreated; @override void calculateEmptyPointValue( @@ -622,46 +622,46 @@ class _PyramidSeriesBase extends ChartSeries class PyramidSeries extends _PyramidSeriesBase { /// Creating an argument constructor of PyramidSeries class. PyramidSeries({ - ValueKey key, - ChartSeriesRendererFactory onCreateRenderer, - PyramidSeriesRendererCreatedCallback onRendererCreated, - List dataSource, - ChartValueMapper xValueMapper, - ChartValueMapper yValueMapper, - ChartValueMapper pointColorMapper, - ChartValueMapper textFieldMapper, - String name, - String height, - String width, - PyramidMode pyramidMode, - double gapRatio, - LegendIconType legendIconType, - EmptyPointSettings emptyPointSettings, - DataLabelSettings dataLabelSettings, - double animationDuration, - double opacity, - Color borderColor, - double borderWidth, - bool explode, - num explodeIndex, - ActivationMode explodeGesture, - String explodeOffset, + ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + PyramidSeriesRendererCreatedCallback? onRendererCreated, + List? dataSource, + ChartValueMapper? xValueMapper, + ChartValueMapper? yValueMapper, + ChartValueMapper? pointColorMapper, + ChartValueMapper? textFieldMapper, + String? name, + String? height, + String? width, + PyramidMode? pyramidMode, + double? gapRatio, + LegendIconType? legendIconType, + EmptyPointSettings? emptyPointSettings, + DataLabelSettings? dataLabelSettings, + double? animationDuration, + double? opacity, + Color? borderColor, + double? borderWidth, + bool? explode, + num? explodeIndex, + ActivationMode? explodeGesture, + String? explodeOffset, // ignore: deprecated_member_use_from_same_package - SelectionSettings selectionSettings, - SelectionBehavior selectionBehavior, - List initialSelectedDataIndexes, + SelectionSettings? selectionSettings, + SelectionBehavior? selectionBehavior, + List? initialSelectedDataIndexes, }) : super( key: key, onCreateRenderer: onCreateRenderer, onRendererCreated: onRendererCreated, dataSource: dataSource, - xValueMapper: (int index) => xValueMapper(dataSource[index], index), - yValueMapper: (int index) => yValueMapper(dataSource[index], index), + xValueMapper: (int index) => xValueMapper!(dataSource![index], index), + yValueMapper: (int index) => yValueMapper!(dataSource![index], index), pointColorMapper: (int index) => pointColorMapper != null - ? pointColorMapper(dataSource[index], index) + ? pointColorMapper(dataSource![index], index) : null, textFieldMapper: (int index) => textFieldMapper != null - ? textFieldMapper(dataSource[index], index) + ? textFieldMapper(dataSource![index], index) : null, name: name, height: height, @@ -688,7 +688,7 @@ class PyramidSeries extends _PyramidSeriesBase { PyramidSeriesRenderer createRenderer(PyramidSeries series) { PyramidSeriesRenderer seriesRenderer; if (onCreateRenderer != null) { - seriesRenderer = onCreateRenderer(series); + seriesRenderer = onCreateRenderer!(series) as PyramidSeriesRenderer; assert(seriesRenderer != null, 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; @@ -699,21 +699,21 @@ class PyramidSeries extends _PyramidSeriesBase { class _PyramidChartPainter extends CustomPainter { _PyramidChartPainter({ - this.chartState, - this.seriesIndex, - this.isRepaint, + required this.chartState, + required this.seriesIndex, + required this.isRepaint, this.animationController, this.seriesAnimation, - ValueNotifier notifier, + required ValueNotifier notifier, }) : super(repaint: notifier); final SfPyramidChartState chartState; final int seriesIndex; final bool isRepaint; - final AnimationController animationController; - final Animation seriesAnimation; - PyramidSeriesRenderer seriesRenderer; + final AnimationController? animationController; + final Animation? seriesAnimation; + late PyramidSeriesRenderer seriesRenderer; //ignore: unused_field - static PointInfo point; + static late PointInfo point; @override void paint(Canvas canvas, Size size) { @@ -721,11 +721,11 @@ class _PyramidChartPainter extends CustomPainter { chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; for (int pointIndex = 0; - pointIndex < seriesRenderer._renderPoints.length; + pointIndex < seriesRenderer._renderPoints!.length; pointIndex++) { - if (seriesRenderer._renderPoints[pointIndex].isVisible) { + if (seriesRenderer._renderPoints![pointIndex].isVisible) { final double animationFactor = - seriesAnimation != null ? seriesAnimation.value : 1; + seriesAnimation != null ? seriesAnimation!.value : 1; if (seriesRenderer._series.animationDuration > 0 && !chartState._isLegendToggled) { final double factor = (chartState._chartAreaRect.top + @@ -760,27 +760,28 @@ class PyramidSeriesRenderer extends ChartSeriesRenderer { /// Calling the default constructor of PyramidSeriesRenderer class. PyramidSeriesRenderer(); - PyramidSeries _series; + late PyramidSeries _series; //Internal variables - String _seriesType; - List> _dataPoints; - List> _renderPoints; - num _sumOfPoints; - Size _triangleSize; - num _explodeDistance; - Rect _maximumDataLabelRegion; - PyramidSeriesController _controller; - SfPyramidChartState _chartState; + late String _seriesType; + late List> _dataPoints; + List>? _renderPoints; + late num _sumOfPoints; + late Size _triangleSize; + late num _explodeDistance; + late Rect _maximumDataLabelRegion; + PyramidSeriesController? _controller; + late SfPyramidChartState _chartState; /// Repaint notifier for series - ValueNotifier _repaintNotifier; - DataLabelSettingsRenderer _dataLabelSettingsRenderer; - SelectionBehaviorRenderer _selectionBehaviorRenderer; - dynamic _selectionBehavior; + late ValueNotifier _repaintNotifier; + late DataLabelSettingsRenderer _dataLabelSettingsRenderer; + late SelectionBehaviorRenderer _selectionBehaviorRenderer; + late dynamic _selectionBehavior; // ignore: prefer_final_fields bool _isSelectionEnable = false; } +/// Called when the pyramid series is created typedef PyramidSeriesRendererCreatedCallback = void Function( PyramidSeriesController controller); @@ -860,12 +861,12 @@ class PyramidSeriesController { /// } ///``` void updateDataSource( - {List addedDataIndexes, - List removedDataIndexes, - List updatedDataIndexes, - int addedDataIndex, - int removedDataIndex, - int updatedDataIndex}) { + {List? addedDataIndexes, + List? removedDataIndexes, + List? updatedDataIndexes, + int? addedDataIndex, + int? removedDataIndex, + int? updatedDataIndex}) { if (removedDataIndexes != null && removedDataIndexes.isNotEmpty) { _removeDataPointsList(removedDataIndexes); } else if (removedDataIndex != null) { @@ -896,12 +897,12 @@ class PyramidSeriesController { void _addOrUpdateDataPoint(int index, bool needUpdate) { final PyramidSeries series = seriesRenderer._series; if (index >= 0 && - series.dataSource.length > index && - series.dataSource[index] != null) { - final ChartIndexedValueMapper xValue = series.xValueMapper; - final ChartIndexedValueMapper yValue = series.yValueMapper; + series.dataSource!.length > index && + series.dataSource![index] != null) { + final ChartIndexedValueMapper? xValue = series.xValueMapper; + final ChartIndexedValueMapper? yValue = series.yValueMapper; final PointInfo _currentPoint = - PointInfo(xValue(index), yValue(index)); + PointInfo(xValue!(index), yValue!(index)); if (_currentPoint.x != null) { if (needUpdate) { if (seriesRenderer._dataPoints.length > index) { @@ -942,16 +943,16 @@ class PyramidSeriesController { void _updatePyramidSeries() { final SfPyramidChartState _chartState = seriesRenderer._chartState; _chartState._chartSeries._processDataPoints(); - _chartState._chartSeries?._initializeSeriesProperties(seriesRenderer); + _chartState._chartSeries._initializeSeriesProperties(seriesRenderer); seriesRenderer._repaintNotifier.value++; if (seriesRenderer._series.dataLabelSettings.isVisible && _chartState._renderDataLabel != null) { - _chartState._renderDataLabel.state.render(); + _chartState._renderDataLabel!.state?.render(); } if (seriesRenderer._series.dataLabelSettings.isVisible && _chartState._chartTemplate != null && - _chartState._chartTemplate.state != null) { - _chartState._chartTemplate.state.templateRender(); + _chartState._chartTemplate!.state != null) { + _chartState._chartTemplate!.state.templateRender(); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/common.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/common.dart index 4fb1950b0..0af5119ab 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/common.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/common.dart @@ -9,31 +9,31 @@ class PointInfo { dynamic x; /// Y value of point info - num y; + num? y; /// Text value of point info - String text; + String? text; /// Fill color of point info. - Color fill; + late Color fill; /// Stores the point info color. - Color color; + late Color color; /// Stores the border color of point info. - Color borderColor; + late Color borderColor; /// Stores the sort value. - D sortValue; + D? sortValue; /// Stores the border width value. - num borderWidth; + late num borderWidth; /// To set the property of explode. - bool isExplode; + bool isExplode = false; /// To set the property of shadow. - bool isShadow; + bool isShadow = false; /// To set the property of empty. bool isEmpty = false; @@ -45,13 +45,13 @@ class PointInfo { bool isSelected = false; /// Stores the value of label position. - Position dataLabelPosition; + Position? dataLabelPosition; /// Stores the value of chart data label position. - ChartDataLabelPosition renderPosition; + ChartDataLabelPosition? renderPosition; /// Stores the value of label rect. - Rect labelRect; + Rect? labelRect; /// Stores the value data label size. Size dataLabelSize = const Size(0, 0); @@ -60,22 +60,22 @@ class PointInfo { bool saturationRegionOutside = false; /// Stores the value of Y ratio. - num yRatio; + late num yRatio; /// Stores the value of height ratio. - num heightRatio; + late num heightRatio; /// Stores the list value of path region. - List pathRegion; + late List pathRegion; /// Stores the value of region. - Rect region; + Rect? region; /// Stores the offset value of symbol location. - Offset symbolLocation; + late Offset symbolLocation; /// Stores the value of explode Distance. - num explodeDistance; + num? explodeDistance; /// To execute onTooltipRender event or not. // ignore: prefer_final_fields @@ -84,4 +84,10 @@ class PointInfo { /// To execute OnDataLabelRender event or not. // ignore: prefer_final_fields bool labelRenderEvent = false; + + /// Stores the tooltip label text. + String? _tooltipLabelText; + + /// Stores the tooltip header text. + String? _tooltipHeaderText; } diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/helper.dart index 4604221af..56a790e36 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/helper.dart @@ -3,9 +3,9 @@ part of charts; /// Method for checking if point is within polygon bool _isPointInPolygon(List polygon, dynamic point) { bool p = false; - num i = -1; - final num l = polygon.length; - num j; + int i = -1; + final int l = polygon.length; + int j; for (j = l - 1; ++i < l; j = i) { ((polygon[i].dy <= point.dy && point.dy < polygon[j].dy) || (polygon[j].dy <= point.dy && point.dy < polygon[i].dy)) && @@ -121,12 +121,12 @@ Color _getPyramidFunnelColor(PointInfo currentPoint, ///To get inner data label color Color _innerColor( - Color dataLabelColor, Color pointColor, SfChartThemeData theme) => + Color? dataLabelColor, Color? pointColor, SfChartThemeData theme) => dataLabelColor ?? pointColor ?? Colors.black; ///To get outer data label color -Color _outerColor( - Color dataLabelColor, Color backgroundColor, SfChartThemeData theme) => +Color _outerColor(Color? dataLabelColor, Color? backgroundColor, + SfChartThemeData theme) => // ignore: prefer_if_null_operators dataLabelColor != null ? dataLabelColor @@ -140,7 +140,7 @@ Color _outerColor( ///To get outer data label text style TextStyle _getDataLabelTextStyle( dynamic seriesRenderer, PointInfo point, dynamic chartState, - [double animateOpacity]) { + [double? animateOpacity]) { final dynamic series = seriesRenderer._series; final DataLabelSettings dataLabel = series.dataLabelSettings; final Color fontColor = dataLabel.textStyle.color ?? @@ -188,9 +188,9 @@ bool _isNeedExplode(int pointIndex, dynamic series, dynamic _chartState) { } /// To return data label rect calculation method based on position -Rect _getDataLabelRect(Position position, ConnectorType connectorType, +Rect? _getDataLabelRect(Position position, ConnectorType connectorType, EdgeInsets margin, Path connectorPath, Offset endPoint, Size textSize) { - Rect rect; + Rect? rect; const int lineLength = 10; switch (position) { case Position.right: @@ -219,6 +219,9 @@ Rect _getDataLabelRect(Position position, ConnectorType connectorType, textSize.width + margin.left + margin.right, textSize.height + margin.top + margin.bottom); break; + default: + rect = null; + break; } return rect; } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/marker.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/marker.dart index 899270bb3..a77b1054a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/marker.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/marker.dart @@ -3,9 +3,33 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'utils/enum.dart'; -/// Represents the marker settings of spark chart +/// Adds and customizes the markers. +/// +/// Markers are used to provide information about the exact point location. +/// You can add a shape to adorn each data point. Markers can be enabled by +/// using the [displayMode] property of [SparkChartMarker]. +/// +/// Provides the options of [color], [borderWidth], [borderColor] and [shape] +/// of the marker to customize the appearance. +/// class SparkChartMarker { - /// Creates the marker settings for spark chart + /// Creates an instance of spark chart marker to add and customizes the marker + /// in spark chart widget. To make, the marker visible, set `displayeMode` + /// property value as SparkChartMarkerDisplayMode.all. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// marker: SparkChartMarker( + /// size: 20, displayMode: SparkChartMarkerDisplayMode.all), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` SparkChartMarker( {this.displayMode = SparkChartMarkerDisplayMode.none, this.borderColor, @@ -14,22 +38,152 @@ class SparkChartMarker { this.size = 5, this.shape = SparkChartMarkerShape.circle}); - /// Toggles the visibility of the marker. + /// Enables the markers in different modes. + /// + /// * [SparkChartMarkerDisplayMode.none] does not allow to display a marker on + /// any data points. + /// * [SparkChartMarkerDisplayMode.all] allows to display a marker on all the + /// data points. + /// * [SparkChartMarkerDisplayMode.high] allows displaying marker only on the + /// highest data points in the spark chart widget. + /// * [SparkChartMarkerDisplayMode.low] allows displaying marker only on the + /// lowest data points + /// * [SparkChartMarkerDisplayMode.first] allows displaying marker only on the + /// first data points. + /// * [SparkChartMarkerDisplayMode.last] allows displaying marker only on the + /// last data points. + /// + /// Defaults to `SparkChartMarkerMode.none`. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// marker: SparkChartMarker( + /// size: 20, displayMode: SparkChartMarkerDisplayMode.all), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` final SparkChartMarkerDisplayMode displayMode; - /// Represents the border color of the marker. - final Color borderColor; + /// Customizes the border color of the marker. The color of the border gets + /// applied based on the current theme of the application if the border color + /// value is set to null + /// + /// Defaults to `null` + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// borderWidth: 2, + /// marker: SparkChartMarker( + /// borderWidth: 3, + /// borderColor: Colors.red, + /// displayMode: SparkChartMarkerDisplayMode.all), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color? borderColor; - /// Represents the border width of the marker + /// Customizes the border width of the marker. + /// + /// Defaults to `2`. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// borderWidth: 2, + /// marker: SparkChartMarker( + /// borderWidth: 3, + /// borderColor: Colors.red, + /// displayMode: SparkChartMarkerDisplayMode.all), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` final double borderWidth; - /// Represents the color of the marker - final Color color; + /// Customizes the color of the marker. The color is set based on the current + /// application theme, if its value is set to null. + /// + /// Defaults to `null`. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// marker: SparkChartMarker( + /// color: Colors.red, displayMode: SparkChartMarkerDisplayMode.all), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` + final Color? color; - /// Represents the size of the marker + /// Customizes the marker size. This value is applied for both the width and + /// height of the marker. + /// + /// Defaults to `5`. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// marker: SparkChartMarker( + /// size: 20, displayMode: SparkChartMarkerDisplayMode.all), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` final double size; - /// Represents the shape of the marker + /// Customizes the marker shape. The [SparkChartMarkerShape] has pre-defined + /// sets of marker shape. + /// + /// * [SparkChartMarkerShape.circle] displays the circular shape. + /// * [SparkChartMarkerShape.diamond] displays the diamond shape as a marker. + /// * [SparkChartMarkerShape.square] displays the square shape as a marker. + /// * [SparkChartMarkerShape.triangle] displays the triangular shape as a marker. + /// * [SparkChartMarkerShape.invertedTriangle] displays the inverted + /// triangular shape as a marker + /// + /// Also refer [SparkChartMarkerShape]. + /// + /// Defaults to [SparkChartMarkerShape.circle]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: SfSparkAreaChart( + /// marker: SparkChartMarker( + /// shape: SparkChartMarkerShape.square, + /// displayMode: SparkChartMarkerDisplayMode.all), + /// data: [18, 24, 30, 14, 28], + /// )), + /// ); + /// } + /// ``` final SparkChartMarkerShape shape; @override @@ -54,9 +208,9 @@ class SparkChartMarker { final List values = [ displayMode, shape, - color, + color!, size, - borderColor, + borderColor!, borderWidth, ]; return hashList(values); diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/plot_band.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/plot_band.dart index 31dc268a4..02b103a47 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/plot_band.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/plot_band.dart @@ -1,9 +1,21 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -/// Represents the plot band settings for spark chart +/// Renders plot band.. +/// +/// Plot band is also known as stripline, which is used to shade the different +/// ranges in plot area with different colors to improve the readability of the +/// chart. +/// +/// Plot bands are drawn based on the axis. +/// +/// Provides the property of [start], [end], [color], [borderColor], and +/// [borderWidth] to customize the appearance. +/// class SparkChartPlotBand { - /// Creates the plot band for spark chart + /// Creates an instance of spark chart plot band to add and customizes the + /// plot band in spark chart widget. To make, the plot band visible, define + /// the value to its [start] and [end] property. /// /// ```dart /// @override @@ -24,7 +36,11 @@ class SparkChartPlotBand { this.borderColor, this.borderWidth = 0}); - /// Defines the plotband color + /// Customizes the color of the plot band. Since the plot band is rendered + /// above the axis line, you can customize the color of the plot band for a + /// transparent view of the axis line. + /// + /// Defaults to ` Color.fromRGBO(191, 212, 252, 0.5)`. /// /// ```dart /// @override @@ -40,7 +56,11 @@ class SparkChartPlotBand { /// ``` final Color color; - /// Defines the start value of plot band + /// Customizes the start position. Define any value between the provided data + /// range as the start value. To make the plot band visible, need to set both + /// the start and [end] property. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -54,9 +74,13 @@ class SparkChartPlotBand { /// ); /// } /// ``` - final double start; + final double? start; - /// Defines the end value of plot band + /// Customizes the end position. Define any value between the provided data + /// range as the end value. To make the plot band visible, need to set both + /// the start and [end] property. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -70,9 +94,12 @@ class SparkChartPlotBand { /// ); /// } /// ``` - final double end; + final double? end; - /// Defines the border color of plot band + /// Customizes the border color of the plot band. To make border visible for + /// plot band, need to set both the border color and border width. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -87,9 +114,12 @@ class SparkChartPlotBand { /// ); /// } /// ``` - final Color borderColor; + final Color? borderColor; - /// Defines the border width of plot band + /// Customizes the border width of the plot band. To make border visible for + /// plot band, need to set both the border color and border width. + /// + /// Defaults to `0`. /// /// ```dart /// @override @@ -125,10 +155,10 @@ class SparkChartPlotBand { @override int get hashCode { final List values = [ - start, - end, + start!, + end!, color, - borderColor, + borderColor!, borderWidth, ]; return hashList(values); diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/renderer_base.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/renderer_base.dart index 7879585b6..35920e9a4 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/renderer_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/renderer_base.dart @@ -12,7 +12,7 @@ import '../utils/helper.dart'; abstract class SfSparkChartRenderObjectWidget extends LeafRenderObjectWidget { /// Creates the render object for spark chart SfSparkChartRenderObjectWidget( - {Key key, + {Key? key, this.data, this.dataCount, this.xValueMapper, @@ -36,91 +36,91 @@ abstract class SfSparkChartRenderObjectWidget extends LeafRenderObjectWidget { : super(key: key); /// Specifies the data source for the series - final List data; + final List? data; /// Field in the data source, which is considered as x-value. - final SparkChartIndexedValueMapper xValueMapper; + final SparkChartIndexedValueMapper? xValueMapper; /// Field in the data source, which is considered as y-value. - final SparkChartIndexedValueMapper yValueMapper; + final SparkChartIndexedValueMapper? yValueMapper; /// Specifies the data source count - final int dataCount; + final int? dataCount; /// Specifies whether to inverse the spark chart - final bool isInversed; + final bool? isInversed; /// Specifies the horizontal axis line position. - final double axisCrossesAt; + final double? axisCrossesAt; /// Specifies the horizontal axis line width - final double axisLineWidth; + final double? axisLineWidth; /// Specifies the axis line color - final Color axisLineColor; + final Color? axisLineColor; /// Specifies the axis line dash array - final List axisLineDashArray; + final List? axisLineDashArray; /// Specifies the high point color - final Color highPointColor; + final Color? highPointColor; /// Specifies the low point color - final Color lowPointColor; + final Color? lowPointColor; /// Specifies the negative point color - final Color negativePointColor; + final Color? negativePointColor; /// Specifies the first point color - final Color firstPointColor; + final Color? firstPointColor; /// Specifies the last point color - final Color lastPointColor; + final Color? lastPointColor; /// Specifies the spark chart color - final Color color; + final Color? color; /// Specifies the spark chart plot band - final SparkChartPlotBand plotBand; + final SparkChartPlotBand? plotBand; /// Specifies the spark chart data details - final SparkChartDataDetails sparkChartDataDetails; + final SparkChartDataDetails? sparkChartDataDetails; /// Specfies the theme of the spark chart - final ThemeData themeData; + final ThemeData? themeData; /// Specifies the series screen coordinate points - final List coordinatePoints; + final List? coordinatePoints; /// Specifies the series data points - final List dataPoints; + final List? dataPoints; } /// Represents the RenderSparkChart class abstract class RenderSparkChart extends RenderBox { /// Creates the render object widget RenderSparkChart( - {Widget child, - List data, - int dataCount, - SparkChartIndexedValueMapper xValueMapper, - SparkChartIndexedValueMapper yValueMapper, - bool isInversed, - double axisCrossesAt, - double axisLineWidth, - Color axisLineColor, - List axisLineDashArray, - Color color, - Color firstPointColor, - Color lastPointColor, - Color highPointColor, - Color lowPointColor, - Color negativePointColor, - SparkChartPlotBand plotBand, - SparkChartDataDetails sparkChartDataDetails, - ThemeData themeData, - List coordinatePoints, - List dataPoints}) + {Widget? child, + List? data, + int? dataCount, + SparkChartIndexedValueMapper? xValueMapper, + SparkChartIndexedValueMapper? yValueMapper, + bool? isInversed, + double? axisCrossesAt, + double? axisLineWidth, + Color? axisLineColor, + List? axisLineDashArray, + Color? color, + Color? firstPointColor, + Color? lastPointColor, + Color? highPointColor, + Color? lowPointColor, + Color? negativePointColor, + SparkChartPlotBand? plotBand, + SparkChartDataDetails? sparkChartDataDetails, + ThemeData? themeData, + List? coordinatePoints, + List? dataPoints}) : _data = data, _dataCount = dataCount, _xValueMapper = xValueMapper, @@ -142,20 +142,20 @@ abstract class RenderSparkChart extends RenderBox { _dataPoints = dataPoints, _coordinatePoints = coordinatePoints { processDataSource(); - if (isInversed) { + if (isInversed == true) { inverseDataPoints(); } } /// Defines the data source - List _data; + List? _data; /// Returns the data source value - List get data => _data; + List? get data => _data; /// Set the data source value - set data(List value) { - if (_data != value) { + set data(List? value) { + if (_data != null && _data != value) { _data = value; _refreshSparkChart(); markNeedsPaint(); @@ -163,14 +163,14 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the data count - int _dataCount; + int? _dataCount; /// Returns the data count value - int get dataCount => _dataCount; + int? get dataCount => _dataCount; /// Set the data source value - set dataCount(int value) { - if (_dataCount != value) { + set dataCount(int? value) { + if (_dataCount != null && _dataCount != value) { _dataCount = value; _refreshSparkChart(); markNeedsPaint(); @@ -178,14 +178,14 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the x-value in the data source. - SparkChartIndexedValueMapper _xValueMapper; + SparkChartIndexedValueMapper? _xValueMapper; /// Returns the xValueMapper value - SparkChartIndexedValueMapper get xValueMapper => _xValueMapper; + SparkChartIndexedValueMapper? get xValueMapper => _xValueMapper; /// Set the xValue Mapper value - set xValueMapper(SparkChartIndexedValueMapper value) { - if (_xValueMapper != value) { + set xValueMapper(SparkChartIndexedValueMapper? value) { + if (_xValueMapper != null && _xValueMapper != value) { _xValueMapper = value; _refreshSparkChart(); markNeedsPaint(); @@ -193,14 +193,14 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the y-value in the data source. - SparkChartIndexedValueMapper _yValueMapper; + SparkChartIndexedValueMapper? _yValueMapper; /// Returns the yValueMapper value - SparkChartIndexedValueMapper get yValueMapper => _yValueMapper; + SparkChartIndexedValueMapper? get yValueMapper => _yValueMapper; /// Set the yValue Mapper value - set yValueMapper(SparkChartIndexedValueMapper value) { - if (_yValueMapper != value) { + set yValueMapper(SparkChartIndexedValueMapper? value) { + if (_yValueMapper != null && _yValueMapper != value) { _yValueMapper = value; _refreshSparkChart(); markNeedsPaint(); @@ -208,13 +208,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines whether to inverse the spark chart - bool _isInversed; + bool? _isInversed; /// Returns the isInversed - bool get isInversed => _isInversed; + bool? get isInversed => _isInversed; /// Set the isInversed - set isInversed(bool value) { + set isInversed(bool? value) { if (_isInversed != value) { _isInversed = value; inverseDataPoints(); @@ -224,13 +224,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the horizontal axis line position. - double _axisCrossesAt; + double? _axisCrossesAt; /// Returns the axisCrossesAt value - double get axisCrossesAt => _axisCrossesAt; + double? get axisCrossesAt => _axisCrossesAt; /// Set the axisCrossesAt value - set axisCrossesAt(double value) { + set axisCrossesAt(double? value) { if (_axisCrossesAt != value) { _axisCrossesAt = value; axisHeight = getAxisHeight(); @@ -239,13 +239,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the axis line width - double _axisLineWidth; + double? _axisLineWidth; /// Returns the axis line width value - double get axisLineWidth => _axisLineWidth; + double? get axisLineWidth => _axisLineWidth; /// Set the axis line width value - set axisLineWidth(double value) { + set axisLineWidth(double? value) { if (_axisLineWidth != value) { _axisLineWidth = value; markNeedsPaint(); @@ -253,13 +253,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the axis line color - Color _axisLineColor; + Color? _axisLineColor; /// Returns the axis line color value - Color get axisLineColor => _axisLineColor; + Color? get axisLineColor => _axisLineColor; /// Set the axis line color value - set axisLineColor(Color value) { + set axisLineColor(Color? value) { if (_axisLineColor != value) { _axisLineColor = value; markNeedsPaint(); @@ -267,13 +267,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the spark chart theme - ThemeData _themeData; + ThemeData? _themeData; /// Returns the spark chart theme - ThemeData get themeData => _themeData; + ThemeData? get themeData => _themeData; /// Sets the spark chart theme - set themeData(ThemeData value) { + set themeData(ThemeData? value) { if (_themeData != value) { _themeData = value; markNeedsPaint(); @@ -281,39 +281,39 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the series coordinate points - List _coordinatePoints; + List? _coordinatePoints; /// Returns the series coordinate points - List get coordinatePoints => _coordinatePoints; + List? get coordinatePoints => _coordinatePoints; /// Sets the series coordinate points - set coordinatePoints(List value) { + set coordinatePoints(List? value) { if (_coordinatePoints != value) { _coordinatePoints = value; } } /// Defines the series data points - List _dataPoints; + List? _dataPoints; /// Returns the series data points - List get dataPoints => _dataPoints; + List? get dataPoints => _dataPoints; /// Sets the series coordinate points - set dataPoints(List value) { + set dataPoints(List? value) { if (_dataPoints != value) { _dataPoints = value; } } /// Defines the axis line dash array - List _axisLineDashArray; + List? _axisLineDashArray; /// Returns the axis line dash array value - List get axisLineDashArray => _axisLineDashArray; + List? get axisLineDashArray => _axisLineDashArray; /// Set the axis line dash array value - set axisLineDashArray(List value) { + set axisLineDashArray(List? value) { if (_axisLineDashArray != value) { _axisLineDashArray = value; markNeedsPaint(); @@ -321,13 +321,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the first point color - Color _firstPointColor; + Color? _firstPointColor; /// Returns the first point color value - Color get firstPointColor => _firstPointColor; + Color? get firstPointColor => _firstPointColor; /// Set the first point color value - set firstPointColor(Color value) { + set firstPointColor(Color? value) { if (_firstPointColor != value) { _firstPointColor = value; markNeedsPaint(); @@ -335,13 +335,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the last point color - Color _lastPointColor; + Color? _lastPointColor; /// Returns the last point color vlue - Color get lastPointColor => _lastPointColor; + Color? get lastPointColor => _lastPointColor; /// Set the last point color value - set lastPointColor(Color value) { + set lastPointColor(Color? value) { if (_lastPointColor != value) { _lastPointColor = value; markNeedsPaint(); @@ -349,13 +349,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the high point color - Color _highPointColor; + Color? _highPointColor; /// Returns the high point color value - Color get highPointColor => _highPointColor; + Color? get highPointColor => _highPointColor; /// Set the high point color value - set highPointColor(Color value) { + set highPointColor(Color? value) { if (_highPointColor != value) { _highPointColor = value; markNeedsPaint(); @@ -363,13 +363,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the low point color - Color _lowPointColor; + Color? _lowPointColor; /// Returns the low point color value - Color get lowPointColor => _lowPointColor; + Color? get lowPointColor => _lowPointColor; /// Set the low point color value - set lowPointColor(Color value) { + set lowPointColor(Color? value) { if (_lowPointColor != value) { _lowPointColor = value; markNeedsPaint(); @@ -377,13 +377,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the negative point color - Color _negativePointColor; + Color? _negativePointColor; /// Returns the negative point color value - Color get negativePointColor => _negativePointColor; + Color? get negativePointColor => _negativePointColor; /// Set the negative point color value - set negativePointColor(Color value) { + set negativePointColor(Color? value) { if (_negativePointColor != value) { _negativePointColor = value; markNeedsPaint(); @@ -391,13 +391,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the spark chart series color - Color _color; + Color? _color; /// Returns the spark chart color - Color get color => _color; + Color? get color => _color; /// Set the spark chart color value - set color(Color value) { + set color(Color? value) { if (_color != value) { _color = value; markNeedsPaint(); @@ -405,13 +405,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the spark chart plot band - SparkChartPlotBand _plotBand; + SparkChartPlotBand? _plotBand; /// Returns the spark chart plot band - SparkChartPlotBand get plotBand => _plotBand; + SparkChartPlotBand? get plotBand => _plotBand; /// Sets the spark chart plot band - set plotBand(SparkChartPlotBand value) { + set plotBand(SparkChartPlotBand? value) { if (_plotBand != value) { _plotBand = value; calculatePlotBandPosition(); @@ -420,70 +420,70 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the spark chart data details - SparkChartDataDetails _sparkChartDataDetails; + SparkChartDataDetails? _sparkChartDataDetails; /// Returns the spark chart data details - SparkChartDataDetails get sparkChartDataDetails => _sparkChartDataDetails; + SparkChartDataDetails? get sparkChartDataDetails => _sparkChartDataDetails; /// Sets the spark chart data details - set sparkChartDataDetails(SparkChartDataDetails value) { + set sparkChartDataDetails(SparkChartDataDetails? value) { if (_sparkChartDataDetails != value) { _sparkChartDataDetails = value; } } /// Defines the plot band start height - double plotBandStartHeight; + double? plotBandStartHeight; /// Defines the plot band end height - double plotBandEndHeight; + double? plotBandEndHeight; /// Specifies the minimum X value - double minX; + double? minX; /// Specifies the maximum X value - double maxX; + double? maxX; /// Specifies the minimum Y value - double minY; + double? minY; /// Specifies the maximum X value - double maxY; + double? maxY; /// Defines the Y difference - double diffY; + double? diffY; /// Defines the X difference - double diffX; + double? diffX; /// Specifies the axis height - double axisHeight; + double? axisHeight; /// Specifies the area size - Size areaSize; + Size? areaSize; /// specifies the data label values - List dataLabels; + List? dataLabels; /// specifies the data label values - List reversedDataLabels; + List? reversedDataLabels; /// Method to find the minX, maxX, minY, maxY void _calculateMinimumMaximumXY(SparkChartPoint currentPoint) { minX ??= currentPoint.x.toDouble(); maxX ??= currentPoint.x.toDouble(); - minX = math.min(minX, currentPoint.x.toDouble()); - maxX = math.max(maxX, currentPoint.x.toDouble()); + minX = math.min(minX!, currentPoint.x.toDouble()); + maxX = math.max(maxX!, currentPoint.x.toDouble()); minY ??= currentPoint.y.toDouble(); maxY ??= currentPoint.y.toDouble(); - minY = math.min(minY, currentPoint.y.toDouble()); - maxY = math.max(maxY, currentPoint.y.toDouble()); + minY = math.min(minY!, currentPoint.y.toDouble()); + maxY = math.max(maxY!, currentPoint.y.toDouble()); } /// Method to process the data source void processDataSource() { - if (dataPoints.isNotEmpty) { - dataPoints.clear(); + if (dataPoints!.isNotEmpty) { + dataPoints!.clear(); } dataLabels = []; @@ -491,28 +491,28 @@ abstract class RenderSparkChart extends RenderBox { minX = maxX = minY = maxY = null; SparkChartPoint currentPoint; String labelY; - if (data != null && data.isNotEmpty && data is List) { - for (int i = 0; i < data.length; i++) { - if (data[i] != null) { - currentPoint = SparkChartPoint(x: i, y: data[i]); - labelY = _getDataLabel(data[i]); + if (data != null && data!.isNotEmpty && data is List) { + for (int i = 0; i < data!.length; i++) { + if (data![i] != null) { + currentPoint = SparkChartPoint(x: i, y: data![i]); + labelY = _getDataLabel(data![i]); currentPoint.labelY = labelY; _calculateMinimumMaximumXY(currentPoint); - dataPoints.add(currentPoint); - dataLabels.add(_getDataLabel(data[i])); + dataPoints!.add(currentPoint); + dataLabels!.add(_getDataLabel(data![i])); } } } else { dynamic xValue; dynamic yValue; - String labelX; + late String labelX; dynamic actualX; if (xValueMapper != null && yValueMapper != null && dataCount != null && - dataCount > 0) { - for (int i = 0; i < dataCount; i++) { - xValue = xValueMapper(i); + dataCount! > 0) { + for (int i = 0; i < dataCount!; i++) { + xValue = xValueMapper!(i); actualX = xValue; if (xValue is String) { labelX = xValue.toString(); @@ -525,7 +525,7 @@ abstract class RenderSparkChart extends RenderBox { labelX = _getDataLabel(xValue); } - yValue = yValueMapper(i); + yValue = yValueMapper!(i); labelY = _getDataLabel(yValue); if (xValue != null && yValue != null) { currentPoint = SparkChartPoint(x: xValue, y: yValue); @@ -533,8 +533,8 @@ abstract class RenderSparkChart extends RenderBox { currentPoint.labelX = labelX; currentPoint.labelY = labelY; _calculateMinimumMaximumXY(currentPoint); - dataPoints.add(currentPoint); - dataLabels.add(_getDataLabel(currentPoint.y)); + dataPoints!.add(currentPoint); + dataLabels!.add(_getDataLabel(currentPoint.y)); } } } @@ -546,7 +546,7 @@ abstract class RenderSparkChart extends RenderBox { String dataLabel = value.toString(); if (value is double) { value = double.parse(value.toStringAsFixed(3)); - final List list = dataLabel.split('.'); + final List? list = dataLabel.split('.'); if (list != null && list.length > 1 && num.parse(list[1]) == 0) { value = value.round(); } @@ -556,31 +556,31 @@ abstract class RenderSparkChart extends RenderBox { } /// Method to calculate axis height - double getAxisHeight() { - final double value = axisCrossesAt; - double axisLineHeight = - areaSize.height - ((areaSize.height / diffY) * (-minY)); - axisLineHeight = (minY < 0 && maxY <= 0) + double? getAxisHeight() { + final double value = axisCrossesAt!; + double? axisLineHeight = + areaSize!.height - ((areaSize!.height / diffY!) * (-minY!)); + axisLineHeight = ((minY! < 0 && maxY! <= 0) ? 0 - : (minY < 0 && maxY > 0) + : (minY! < 0 && maxY! > 0) ? axisHeight - : areaSize.height; - if (value >= minY && value <= maxY) { - axisLineHeight = areaSize.height - - (areaSize.height * ((value - minY) / diffY)).roundToDouble(); + : areaSize!.height); + if (value >= minY! && value <= maxY!) { + axisLineHeight = areaSize!.height - + (areaSize!.height * ((value - minY!) / diffY!)).roundToDouble(); } return axisLineHeight; } /// Inverse the data Points void inverseDataPoints() { - final List temp = dataPoints.reversed.toList(); - reversedDataLabels = List.from(dataLabels.reversed); - dataLabels.clear(); - dataLabels.addAll(reversedDataLabels); - dataPoints.clear(); - dataPoints.addAll(temp); - final double tempX = minX; + final List temp = dataPoints!.reversed.toList(); + reversedDataLabels = List.from(dataLabels!.reversed); + dataLabels!.clear(); + dataLabels!.addAll(reversedDataLabels!); + dataPoints!.clear(); + dataPoints!.addAll(temp); + final double tempX = minX!; minX = maxX; maxX = tempX; } @@ -588,61 +588,61 @@ abstract class RenderSparkChart extends RenderBox { /// Methods to calculate the visible points void calculateRenderingPoints() { if (minX != null && maxX != null && minY != null && maxY != null) { - diffX = maxX - minX; - diffY = maxY - minY; + diffX = maxX! - minX!; + diffY = maxY! - minY!; diffX = diffX == 0 ? 1 : diffX; diffY = diffY == 0 ? 1 : diffY; axisHeight = getAxisHeight(); - if (coordinatePoints.isNotEmpty) { - coordinatePoints.clear(); + if (coordinatePoints!.isNotEmpty) { + coordinatePoints!.clear(); } double x; double y; Offset visiblePoint; - for (int i = 0; i < dataPoints.length; i++) { - x = dataPoints[i].x.toDouble(); - y = dataPoints[i].y.toDouble(); - visiblePoint = transformToCoordinatePoint(minX, maxX, minY, maxY, diffX, - diffY, areaSize, x, y, dataPoints.length); - coordinatePoints.add(visiblePoint); + for (int i = 0; i < dataPoints!.length; i++) { + x = dataPoints![i].x.toDouble(); + y = dataPoints![i].y.toDouble(); + visiblePoint = transformToCoordinatePoint(minX!, maxX!, minY!, maxY!, + diffX!, diffY!, areaSize, x, y, dataPoints!.length); + coordinatePoints!.add(visiblePoint); } - coordinatePoints = sortScreenCoordiantePoints(coordinatePoints); + coordinatePoints = sortScreenCoordiantePoints(coordinatePoints!); } } /// Method to calculate the plot band position void calculatePlotBandPosition() { - final double height = areaSize.height; - final double start = plotBand == null + final double height = areaSize!.height; + final double? start = plotBand == null ? 0 - : (plotBand.start ?? minY) < minY + : (plotBand!.start ?? minY!) < minY! ? minY - : (plotBand.start ?? minY); - final double end = plotBand == null + : (plotBand!.start ?? minY); + final double? end = plotBand == null ? 0 - : (plotBand.end ?? maxY) > maxY + : (plotBand!.end ?? maxY!) > maxY! ? maxY - : (plotBand.end ?? maxY); - plotBandStartHeight = (height - ((height / diffY) * (start - minY))); - plotBandEndHeight = (height - ((height / diffY) * (end - minY))); + : (plotBand!.end ?? maxY); + plotBandStartHeight = (height - ((height / diffY!) * (start! - minY!))); + plotBandEndHeight = (height - ((height / diffY!) * (end! - minY!))); } /// Method to render axis line void renderAxisline(Canvas canvas, Offset offset) { - if (axisLineWidth > 0 && axisHeight != null) { + if (axisLineWidth! > 0 && axisHeight != null) { final double x1 = offset.dx; - final double y1 = offset.dy + axisHeight; - final double x2 = offset.dx + areaSize.width; + final double y1 = offset.dy + axisHeight!; + final double x2 = offset.dx + areaSize!.width; final Offset point1 = Offset(x1, y1); final Offset point2 = Offset(x2, y1); final Paint paint = Paint() - ..strokeWidth = axisLineWidth + ..strokeWidth = axisLineWidth! ..style = PaintingStyle.stroke - ..color = axisLineColor; - if (axisLineDashArray != null && axisLineDashArray.isNotEmpty) { - drawDashedPath(canvas, paint, point1, point2, axisLineDashArray); + ..color = axisLineColor!; + if (axisLineDashArray != null && axisLineDashArray!.isNotEmpty) { + drawDashedPath(canvas, paint, point1, point2, axisLineDashArray!); } else { canvas.drawLine(point1, point2, paint); } @@ -652,29 +652,29 @@ abstract class RenderSparkChart extends RenderBox { /// Method to render plot band void renderPlotBand(Canvas canvas, Offset offset) { if (plotBandStartHeight != plotBandEndHeight) { - final Paint paint = Paint()..color = plotBand.color; + final Paint paint = Paint()..color = plotBand!.color; final Rect plotBandRect = Rect.fromLTRB( offset.dx, - offset.dy + plotBandStartHeight, - offset.dx + areaSize.width, - offset.dy + plotBandEndHeight); + offset.dy + plotBandStartHeight!, + offset.dx + areaSize!.width, + offset.dy + plotBandEndHeight!); canvas.drawRect(plotBandRect, paint); - if (plotBand.borderColor != Colors.transparent && - plotBand.borderWidth > 0) { + if (plotBand!.borderColor != Colors.transparent && + plotBand!.borderWidth > 0) { final Paint borderPaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = plotBand.borderWidth - ..color = plotBand.borderColor; + ..strokeWidth = plotBand!.borderWidth + ..color = plotBand!.borderColor!; canvas.drawRect(plotBandRect, borderPaint); } } else { final Paint paint = Paint() - ..color = plotBand.color + ..color = plotBand!.color ..style = PaintingStyle.stroke ..strokeWidth = 3; - final Offset point1 = Offset(offset.dx, offset.dy + plotBandStartHeight); + final Offset point1 = Offset(offset.dx, offset.dy + plotBandStartHeight!); final Offset point2 = - Offset(offset.dx + areaSize.width, offset.dy + plotBandStartHeight); + Offset(offset.dx + areaSize!.width, offset.dy + plotBandStartHeight!); canvas.drawLine(point1, point2, paint); } } @@ -682,7 +682,7 @@ abstract class RenderSparkChart extends RenderBox { /// Method to refresh the spark chart void _refreshSparkChart() { processDataSource(); - if (isInversed) { + if (isInversed!) { inverseDataPoints(); } calculateRenderingPoints(); diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_area_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_area_renderer.dart index d54f62603..0cbfeb3ec 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_area_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_area_renderer.dart @@ -13,32 +13,32 @@ class SfSparkAreaChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { /// Creates the render object for spark chart SfSparkAreaChartRenderObjectWidget({ - Key key, - double borderWidth, - Color borderColor, - List data, - int dataCount, - SparkChartIndexedValueMapper xValueMapper, - SparkChartIndexedValueMapper yValueMapper, - bool isInversed, - double axisCrossesAt, - Color axisLineColor, - double axisLineWidth, - List axisLineDashArray, - Color firstPointColor, - Color lowPointColor, - Color highPointColor, - Color lastPointColor, - Color negativePointColor, - Color color, - SparkChartPlotBand plotBand, - SparkChartMarker marker, - SparkChartLabelDisplayMode labelDisplayMode, - TextStyle labelStyle, - ThemeData themeData, - SparkChartDataDetails sparkChartDataDetails, - List coordinatePoints, - List dataPoints, + Key? key, + double? borderWidth, + Color? borderColor, + List? data, + int? dataCount, + SparkChartIndexedValueMapper? xValueMapper, + SparkChartIndexedValueMapper? yValueMapper, + bool? isInversed, + double? axisCrossesAt, + Color? axisLineColor, + double? axisLineWidth, + List? axisLineDashArray, + Color? firstPointColor, + Color? lowPointColor, + Color? highPointColor, + Color? lastPointColor, + Color? negativePointColor, + Color? color, + SparkChartPlotBand? plotBand, + SparkChartMarker? marker, + SparkChartLabelDisplayMode? labelDisplayMode, + TextStyle? labelStyle, + ThemeData? themeData, + SparkChartDataDetails? sparkChartDataDetails, + List? coordinatePoints, + List? dataPoints, }) : borderWidth = borderWidth, borderColor = borderColor, marker = marker, @@ -68,19 +68,19 @@ class SfSparkAreaChartRenderObjectWidget coordinatePoints: coordinatePoints); /// Specifies the area chart border width - final double borderWidth; + final double? borderWidth; /// Specifies the area chart border color - final Color borderColor; + final Color? borderColor; /// Specifies the area chart marker - final SparkChartMarker marker; + final SparkChartMarker? marker; /// Specifies the spark chart data label - final SparkChartLabelDisplayMode labelDisplayMode; + final SparkChartLabelDisplayMode? labelDisplayMode; /// Specifies the spark chart data label style - final TextStyle labelStyle; + final TextStyle? labelStyle; @override RenderObject createRenderObject(BuildContext context) { @@ -147,31 +147,31 @@ class SfSparkAreaChartRenderObjectWidget class _RenderSparkAreaChart extends RenderSparkChart { /// Creates the render object widget _RenderSparkAreaChart( - {List data, - int dataCount, - SparkChartIndexedValueMapper xValueMapper, - SparkChartIndexedValueMapper yValueMapper, - bool isInversed, - double axisCrossesAt, - double axisLineWidth, - Color axisLineColor, - List axisLineDashArray, - Color color, - Color firstPointColor, - Color lastPointColor, - Color highPointColor, - Color lowPointColor, - Color negativePointColor, - SparkChartPlotBand plotBand, - double borderWidth, - Color borderColor, - SparkChartMarker marker, - SparkChartLabelDisplayMode labelDisplayMode, - TextStyle labelStyle, - SparkChartDataDetails sparkChartDataDetails, - ThemeData themeData, - List coordinatePoints, - List dataPoints}) + {List? data, + int? dataCount, + SparkChartIndexedValueMapper? xValueMapper, + SparkChartIndexedValueMapper? yValueMapper, + bool? isInversed, + double? axisCrossesAt, + double? axisLineWidth, + Color? axisLineColor, + List? axisLineDashArray, + Color? color, + Color? firstPointColor, + Color? lastPointColor, + Color? highPointColor, + Color? lowPointColor, + Color? negativePointColor, + SparkChartPlotBand? plotBand, + double? borderWidth, + Color? borderColor, + SparkChartMarker? marker, + SparkChartLabelDisplayMode? labelDisplayMode, + TextStyle? labelStyle, + SparkChartDataDetails? sparkChartDataDetails, + ThemeData? themeData, + List? coordinatePoints, + List? dataPoints}) : _borderWidth = borderWidth, _borderColor = borderColor, _marker = marker, @@ -200,13 +200,13 @@ class _RenderSparkAreaChart extends RenderSparkChart { dataPoints: dataPoints); /// Defines the border width. - double _borderWidth; + double? _borderWidth; /// Returns the border width value - double get borderWidth => _borderWidth; + double? get borderWidth => _borderWidth; /// Set the border width value - set borderWidth(double value) { + set borderWidth(double? value) { if (_borderWidth != value) { _borderWidth = value; markNeedsPaint(); @@ -214,13 +214,13 @@ class _RenderSparkAreaChart extends RenderSparkChart { } /// Defines the dash array. - Color _borderColor; + Color? _borderColor; /// Returns the dash arry value - Color get borderColor => _borderColor; + Color? get borderColor => _borderColor; /// Set the line width value - set borderColor(Color value) { + set borderColor(Color? value) { if (_borderColor != value) { _borderColor = value; markNeedsPaint(); @@ -228,13 +228,13 @@ class _RenderSparkAreaChart extends RenderSparkChart { } /// Defines the marker for spark chart - SparkChartMarker _marker; + SparkChartMarker? _marker; /// Gets the marker for spark chart - SparkChartMarker get marker => _marker; + SparkChartMarker? get marker => _marker; /// Sets the marker for spark chart - set marker(SparkChartMarker value) { + set marker(SparkChartMarker? value) { if (_marker != value) { _marker = value; markNeedsPaint(); @@ -242,13 +242,13 @@ class _RenderSparkAreaChart extends RenderSparkChart { } /// Defines the spark chart data label mode - SparkChartLabelDisplayMode _labelDisplayMode; + SparkChartLabelDisplayMode? _labelDisplayMode; /// Returns the spark chart data label mode - SparkChartLabelDisplayMode get labelDisplayMode => _labelDisplayMode; + SparkChartLabelDisplayMode? get labelDisplayMode => _labelDisplayMode; /// Sets the spark chart data label mode - set labelDisplayMode(SparkChartLabelDisplayMode value) { + set labelDisplayMode(SparkChartLabelDisplayMode? value) { if (_labelDisplayMode != value) { _labelDisplayMode = value; markNeedsPaint(); @@ -256,13 +256,13 @@ class _RenderSparkAreaChart extends RenderSparkChart { } /// Defines the spark chart data label text style - TextStyle _labelStyle; + TextStyle? _labelStyle; /// Returns the spark chart data label text style - TextStyle get labelStyle => _labelStyle; + TextStyle? get labelStyle => _labelStyle; /// Sets the spark chart data label mode - set labelStyle(TextStyle value) { + set labelStyle(TextStyle? value) { if (_labelStyle != value) { _labelStyle = value; markNeedsPaint(); @@ -270,110 +270,113 @@ class _RenderSparkAreaChart extends RenderSparkChart { } /// Specifies the low point in series - num _lowPoint; + num? _lowPoint; /// Specifies the high point in series - num _highPoint; + num? _highPoint; @override void processDataSource() { super.processDataSource(); - if (dataPoints != null && dataPoints.isNotEmpty) { - final List temp = List.from(dataPoints); - final List tempDataLabels = List.from(dataLabels); - dataLabels.clear(); - dataPoints.clear(); + if (dataPoints != null && dataPoints!.isNotEmpty) { + final List temp = List.from(dataPoints!); + final List tempDataLabels = List.from(dataLabels!); + dataLabels!.clear(); + dataPoints!.clear(); final SparkChartPoint point1 = SparkChartPoint(x: temp[0].x, y: minY); point1.labelX = temp[0].labelX; point1.labelY = temp[0].labelY; - dataPoints.add(point1); - dataPoints.addAll(temp); + dataPoints!.add(point1); + dataPoints!.addAll(temp); final SparkChartPoint point2 = SparkChartPoint(x: temp[temp.length - 1].x, y: minY); point2.labelX = temp[temp.length - 1].labelX; point2.labelY = temp[temp.length - 1].labelY; - dataPoints.add(point2); - dataLabels.add('0'); - dataLabels.addAll(tempDataLabels); - dataLabels.add('0'); + dataPoints!.add(point2); + dataLabels!.add('0'); + dataLabels!.addAll(tempDataLabels); + dataLabels!.add('0'); } } /// Render area series void _renderAreaSeries(Canvas canvas, Offset offset) { final Paint paint = Paint() - ..color = color + ..color = color! ..style = PaintingStyle.fill; final Path path = Path(); Size size; - _highPoint = coordinatePoints[0].dy; - _lowPoint = coordinatePoints[0].dy; + _highPoint = coordinatePoints![0].dy; + _lowPoint = coordinatePoints![0].dy; - for (int i = 0; i < coordinatePoints.length; i++) { - if (_highPoint < coordinatePoints[i].dy) { - _highPoint = coordinatePoints[i].dy; + for (int i = 0; i < coordinatePoints!.length; i++) { + if (_highPoint! < coordinatePoints![i].dy) { + _highPoint = coordinatePoints![i].dy; } - if (_lowPoint > coordinatePoints[i].dy) { - _lowPoint = coordinatePoints[i].dy; + if (_lowPoint! > coordinatePoints![i].dy) { + _lowPoint = coordinatePoints![i].dy; } if (i == 0) { - path.moveTo(offset.dx + coordinatePoints[i].dx, - offset.dy + coordinatePoints[i].dy); + path.moveTo(offset.dx + coordinatePoints![i].dx, + offset.dy + coordinatePoints![i].dy); } - if (i < coordinatePoints.length - 1) { - path.lineTo(offset.dx + coordinatePoints[i + 1].dx, - offset.dy + coordinatePoints[i + 1].dy); + if (i < coordinatePoints!.length - 1) { + path.lineTo(offset.dx + coordinatePoints![i + 1].dx, + offset.dy + coordinatePoints![i + 1].dy); } if (i >= 1 && - i <= coordinatePoints.length - 2 && + i <= coordinatePoints!.length - 2 && labelDisplayMode != SparkChartLabelDisplayMode.none && labelStyle != null) { - size = getTextSize(dataLabels[i], labelStyle); - dataPoints[i].dataLabelOffset = Offset( - offset.dx + coordinatePoints[i].dx, + size = getTextSize(dataLabels![i], labelStyle!); + dataPoints![i].dataLabelOffset = Offset( + (offset.dx + coordinatePoints![i].dx) - size.width / 2, offset.dy + (marker != null && - marker.displayMode != SparkChartMarkerDisplayMode.none - ? (dataPoints[i].y > 0 - ? (coordinatePoints[i].dy - + marker!.displayMode != SparkChartMarkerDisplayMode.none + ? (dataPoints![i].y > 0 + ? (coordinatePoints![i].dy - size.height - - marker.size / 2) - : (coordinatePoints[i].dy + marker.size / 2)) - : dataPoints[i].y > 0 - ? (coordinatePoints[i].dy - size.height) - : (coordinatePoints[i].dy + size.height))); - if (dataPoints[i].dataLabelOffset.dx <= offset.dx) { - dataPoints[i].dataLabelOffset = - Offset((offset.dx), dataPoints[i].dataLabelOffset.dy); + marker!.size / 2) + : (coordinatePoints![i].dy + marker!.size / 2)) + : dataPoints![i].y > 0 + ? (coordinatePoints![i].dy - size.height) + : (coordinatePoints![i].dy + size.height))); + if (dataPoints![i].dataLabelOffset!.dx <= offset.dx) { + dataPoints![i].dataLabelOffset = + Offset((offset.dx), dataPoints![i].dataLabelOffset!.dy); } - if (dataPoints[i].dataLabelOffset.dx >= offset.dx + areaSize.width) { - dataPoints[i].dataLabelOffset = Offset( - ((offset.dx + areaSize.width) - size.width), - dataPoints[i].dataLabelOffset.dy); + if (dataPoints![i].dataLabelOffset!.dx >= offset.dx + areaSize!.width) { + dataPoints![i].dataLabelOffset = Offset( + ((offset.dx + areaSize!.width) - size.width), + dataPoints![i].dataLabelOffset!.dy); } - if (dataPoints[i].dataLabelOffset.dy <= offset.dy) { - dataPoints[i].dataLabelOffset = Offset( - dataPoints[i].dataLabelOffset.dx, + if (dataPoints![i].dataLabelOffset!.dy <= offset.dy) { + dataPoints![i].dataLabelOffset = Offset( + dataPoints![i].dataLabelOffset!.dx, (offset.dy + (marker != null && - marker.displayMode != SparkChartMarkerDisplayMode.none - ? marker.size / 2 + size.height + marker!.displayMode != + SparkChartMarkerDisplayMode.none + ? marker!.size / 2 + size.height : size.height))); } - if (dataPoints[i].dataLabelOffset.dy >= offset.dy + areaSize.height) { - dataPoints[i].dataLabelOffset = Offset( - dataPoints[i].dataLabelOffset.dx, - (offset.dy + areaSize.height) - + if (dataPoints![i].dataLabelOffset!.dy >= + offset.dy + areaSize!.height) { + dataPoints![i].dataLabelOffset = Offset( + dataPoints![i].dataLabelOffset!.dx, + (offset.dy + areaSize!.height) - (marker != null && - marker.displayMode != SparkChartMarkerDisplayMode.none - ? marker.size / 2 + size.height + marker!.displayMode != + SparkChartMarkerDisplayMode.none + ? marker!.size / 2 + size.height : size.height)); } } @@ -383,7 +386,7 @@ class _RenderSparkAreaChart extends RenderSparkChart { if (borderColor != null && borderColor != Colors.transparent && borderWidth != null && - borderWidth > 0) { + borderWidth! > 0) { _renderAreaSeriesBorder(canvas, offset); } } @@ -391,20 +394,20 @@ class _RenderSparkAreaChart extends RenderSparkChart { /// Method to render the area series border void _renderAreaSeriesBorder(Canvas canvas, Offset offset) { final Paint strokePaint = Paint() - ..color = borderColor - ..strokeWidth = borderWidth + ..color = borderColor! + ..strokeWidth = borderWidth! ..style = PaintingStyle.stroke; final Path strokePath = Path(); - for (int i = 1; i < coordinatePoints.length - 1; i++) { + for (int i = 1; i < coordinatePoints!.length - 1; i++) { if (i == 1) { - strokePath.moveTo(offset.dx + coordinatePoints[i].dx, - offset.dy + coordinatePoints[i].dy); + strokePath.moveTo(offset.dx + coordinatePoints![i].dx, + offset.dy + coordinatePoints![i].dy); } - if (i < coordinatePoints.length - 2) { - strokePath.lineTo(offset.dx + coordinatePoints[i + 1].dx, - offset.dy + coordinatePoints[i + 1].dy); + if (i < coordinatePoints!.length - 2) { + strokePath.lineTo(offset.dx + coordinatePoints![i + 1].dx, + offset.dy + coordinatePoints![i + 1].dy); } } @@ -415,43 +418,46 @@ class _RenderSparkAreaChart extends RenderSparkChart { void paint(PaintingContext context, Offset offset) { super.paint(context, offset); if (coordinatePoints != null && - coordinatePoints.isNotEmpty && + coordinatePoints!.isNotEmpty && dataPoints != null && - dataPoints.isNotEmpty) { + dataPoints!.isNotEmpty) { _renderAreaSeries(context.canvas, offset); if (marker != null && - marker.displayMode != SparkChartMarkerDisplayMode.none) { + marker!.displayMode != SparkChartMarkerDisplayMode.none && + marker!.borderWidth > 0) { renderMarker( context.canvas, offset, - marker, - coordinatePoints, - dataPoints, - color, + marker!, + coordinatePoints!, + dataPoints!, + color!, 'Area', - _highPoint, - _lowPoint, - themeData, - lowPointColor, - highPointColor, - negativePointColor, - firstPointColor, - lastPointColor); + _highPoint!, + _lowPoint!, + axisCrossesAt!, + themeData!, + lowPointColor!, + highPointColor!, + negativePointColor!, + firstPointColor!, + lastPointColor!); } - if (labelDisplayMode != SparkChartLabelDisplayMode.none) { + if (labelDisplayMode != null && + labelDisplayMode != SparkChartLabelDisplayMode.none) { renderDataLabel( context.canvas, - dataLabels, - dataPoints, - coordinatePoints, - labelStyle, - labelDisplayMode, + dataLabels!, + dataPoints!, + coordinatePoints!, + labelStyle!, + labelDisplayMode!, 'Area', - themeData, + themeData!, offset, - color, - _highPoint, - _lowPoint); + color!, + _highPoint!, + _lowPoint!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_bar_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_bar_renderer.dart index 314b7fd56..647b29674 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_bar_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_bar_renderer.dart @@ -11,31 +11,31 @@ import 'renderer_base.dart'; class SfSparkBarChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { /// Creates the render object for spark chart SfSparkBarChartRenderObjectWidget( - {Key key, - double borderWidth, - Color borderColor, - List data, - int dataCount, - SparkChartIndexedValueMapper xValueMapper, - SparkChartIndexedValueMapper yValueMapper, - bool isInversed, - double axisCrossesAt, - Color axisLineColor, - double axisLineWidth, - List axisLineDashArray, - Color firstPointColor, - Color lowPointColor, - Color highPointColor, - Color lastPointColor, - Color negativePointColor, - Color color, - SparkChartPlotBand plotBand, - SparkChartLabelDisplayMode labelDisplayMode, - TextStyle labelStyle, - ThemeData themeData, - SparkChartDataDetails sparkChartDataDetails, - List coordinatePoints, - List dataPoints}) + {Key? key, + double? borderWidth, + Color? borderColor, + List? data, + int? dataCount, + SparkChartIndexedValueMapper? xValueMapper, + SparkChartIndexedValueMapper? yValueMapper, + bool? isInversed, + double? axisCrossesAt, + Color? axisLineColor, + double? axisLineWidth, + List? axisLineDashArray, + Color? firstPointColor, + Color? lowPointColor, + Color? highPointColor, + Color? lastPointColor, + Color? negativePointColor, + Color? color, + SparkChartPlotBand? plotBand, + SparkChartLabelDisplayMode? labelDisplayMode, + TextStyle? labelStyle, + ThemeData? themeData, + SparkChartDataDetails? sparkChartDataDetails, + List? coordinatePoints, + List? dataPoints}) : borderWidth = borderWidth, borderColor = borderColor, labelDisplayMode = labelDisplayMode, @@ -64,16 +64,16 @@ class SfSparkBarChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { dataPoints: dataPoints); /// Specifies the bar chart border width - final double borderWidth; + final double? borderWidth; /// Specifies the bar chart border color - final Color borderColor; + final Color? borderColor; /// Specifies the spark chart data label - final SparkChartLabelDisplayMode labelDisplayMode; + final SparkChartLabelDisplayMode? labelDisplayMode; /// Specifies the spark chart data label style - final TextStyle labelStyle; + final TextStyle? labelStyle; @override RenderObject createRenderObject(BuildContext context) { @@ -109,7 +109,7 @@ class SfSparkBarChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { BuildContext context, _RenderSparkBarChart renderObject) { renderObject ..isInversed = isInversed - ..axisCrossesAt = axisCrossesAt + ..axisCrossesAt = axisCrossesAt! ..axisLineColor = axisLineColor ..axisLineWidth = axisLineWidth ..axisLineDashArray = axisLineDashArray @@ -138,30 +138,30 @@ class SfSparkBarChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { class _RenderSparkBarChart extends RenderSparkChart { /// Creates the render object widget _RenderSparkBarChart( - {List data, - int dataCount, - SparkChartIndexedValueMapper xValueMapper, - SparkChartIndexedValueMapper yValueMapper, - bool isInversed, - double axisCrossesAt, - double axisLineWidth, - Color axisLineColor, - List axisLineDashArray, - Color color, - Color firstPointColor, - Color lastPointColor, - Color highPointColor, - Color lowPointColor, - Color negativePointColor, - SparkChartPlotBand plotBand, - double borderWidth, - Color borderColor, - SparkChartLabelDisplayMode labelDisplayMode, - TextStyle labelStyle, - SparkChartDataDetails sparkChartDataDetails, - ThemeData themeData, - List coordinatePoints, - List dataPoints}) + {List? data, + int? dataCount, + SparkChartIndexedValueMapper? xValueMapper, + SparkChartIndexedValueMapper? yValueMapper, + bool? isInversed, + double? axisCrossesAt, + double? axisLineWidth, + Color? axisLineColor, + List? axisLineDashArray, + Color? color, + Color? firstPointColor, + Color? lastPointColor, + Color? highPointColor, + Color? lowPointColor, + Color? negativePointColor, + SparkChartPlotBand? plotBand, + double? borderWidth, + Color? borderColor, + SparkChartLabelDisplayMode? labelDisplayMode, + TextStyle? labelStyle, + SparkChartDataDetails? sparkChartDataDetails, + ThemeData? themeData, + List? coordinatePoints, + List? dataPoints}) : _borderWidth = borderWidth, _borderColor = borderColor, _labelDisplayMode = labelDisplayMode, @@ -190,13 +190,13 @@ class _RenderSparkBarChart extends RenderSparkChart { dataPoints: dataPoints); /// Defines the border width. - double _borderWidth; + double? _borderWidth; /// Returns the border width value - double get borderWidth => _borderWidth; + double? get borderWidth => _borderWidth; /// Set the border width value - set borderWidth(double value) { + set borderWidth(double? value) { if (_borderWidth != value) { _borderWidth = value; markNeedsPaint(); @@ -204,13 +204,13 @@ class _RenderSparkBarChart extends RenderSparkChart { } /// Defines the dash array. - Color _borderColor; + Color? _borderColor; /// Returns the dash arry value - Color get borderColor => _borderColor; + Color? get borderColor => _borderColor; /// Set the line width value - set borderColor(Color value) { + set borderColor(Color? value) { if (_borderColor != value) { _borderColor = value; markNeedsPaint(); @@ -218,13 +218,13 @@ class _RenderSparkBarChart extends RenderSparkChart { } /// Defines the spark chart data label mode - SparkChartLabelDisplayMode _labelDisplayMode; + SparkChartLabelDisplayMode? _labelDisplayMode; /// Returns the spark chart data label mode - SparkChartLabelDisplayMode get labelDisplayMode => _labelDisplayMode; + SparkChartLabelDisplayMode? get labelDisplayMode => _labelDisplayMode; /// Sets the spark chart data label mode - set labelDisplayMode(SparkChartLabelDisplayMode value) { + set labelDisplayMode(SparkChartLabelDisplayMode? value) { if (_labelDisplayMode != value) { _labelDisplayMode = value; markNeedsPaint(); @@ -232,13 +232,13 @@ class _RenderSparkBarChart extends RenderSparkChart { } /// Defines the spark chart data label text style - TextStyle _labelStyle; + TextStyle? _labelStyle; /// Returns the spark chart data label text style - TextStyle get labelStyle => _labelStyle; + TextStyle? get labelStyle => _labelStyle; /// Sets the spark chart data label mode - set labelStyle(TextStyle value) { + set labelStyle(TextStyle? value) { if (_labelStyle != value) { _labelStyle = value; markNeedsPaint(); @@ -246,81 +246,83 @@ class _RenderSparkBarChart extends RenderSparkChart { } /// Defines the horizontal axis line position. - double _axisCrossesAt; + double? _axisCrossesAt; /// Returns the axisCrossesAt value @override - double get axisCrossesAt => _axisCrossesAt; + double get axisCrossesAt => _axisCrossesAt!; /// Set the axisCrossesAt value @override - set axisCrossesAt(double value) { + set axisCrossesAt(double? value) { if (_axisCrossesAt != value) { - _axisCrossesAt = value; + _axisCrossesAt = value!; calculateRenderingPoints(); markNeedsPaint(); } } /// Specifies the win loss segments - List _segments; + List? _segments; /// Specifies the low point in series - num _lowPoint; + late num _lowPoint; /// Specifies the high point in series - num _highPoint; + late num _highPoint; @override void calculateRenderingPoints() { - diffX = maxX - minX; - diffY = maxY - minY; + diffX = maxX! - minX!; + diffY = maxY! - minY!; diffX = diffX == 0 ? 1 : diffX; diffY = diffY == 0 ? 1 : diffY; _segments = []; - final double xInterval = - dataPoints[1].x.toDouble() - dataPoints[0].x.toDouble(); + final double xInterval = dataPoints!.length > 1 + ? dataPoints![1].x.toDouble() - dataPoints![0].x.toDouble() + : dataPoints!.length.toDouble(); final double columnSpace = 0.5; // Default space for column and winloss final double space = columnSpace * 2; - final axisBaseValue = minY < 0 ? minY : 0; + final axisBaseValue = minY! < 0 ? minY : 0; double visibleXPoint; Rect rect; double top; double x, y, y2; double columnHeight; double currentColumnHeight; - double columnWidth = areaSize.width / (((maxX - minX) / xInterval) + 1); + double columnWidth = areaSize!.width / (((maxX! - minX!) / xInterval) + 1); columnWidth -= space; - diffY = maxY - axisBaseValue; + diffY = maxY! - axisBaseValue!; axisHeight = getAxisHeight(); - if (coordinatePoints.isNotEmpty) { - coordinatePoints.clear(); + if (coordinatePoints!.isNotEmpty) { + coordinatePoints!.clear(); } - for (int i = 0; i < dataPoints.length; i++) { - x = dataPoints[i].x.toDouble(); - y = dataPoints[i].y.toDouble(); + for (int i = 0; i < dataPoints!.length; i++) { + x = dataPoints![i].x.toDouble(); + y = dataPoints![i].y.toDouble(); visibleXPoint = - (((x - minX) / xInterval) * (columnWidth + space)) + (space / 2); - columnHeight = (areaSize.height / diffY) * (y - axisBaseValue); + (((x - minX!) / xInterval) * (columnWidth + space)) + (space / 2); + columnHeight = (areaSize!.height / diffY!) * (y - axisBaseValue); currentColumnHeight = (y == axisBaseValue && y > axisCrossesAt) - ? ((dataPoints.length != 1 && diffY != 1) - ? (areaSize.height / diffY) * axisBaseValue - : (columnHeight.toInt() | 1)) + ? ((dataPoints!.length != 1 && diffY != 1) + ? (areaSize!.height / diffY!) * axisBaseValue + : (columnHeight.toInt() | 1)) + .toDouble() : (y == maxY && y < axisCrossesAt && - dataPoints.length != 1 && + dataPoints!.length != 1 && diffY != 1) - ? (areaSize.height / diffY) * maxY + ? (areaSize!.height / diffY!) * maxY! : columnHeight; - y2 = (areaSize.height - currentColumnHeight).abs(); - top = (y2 > axisHeight) ? axisHeight : y2; + y2 = (areaSize!.height - currentColumnHeight).abs(); + top = (y2 > axisHeight!) ? axisHeight! : y2; rect = Rect.fromLTRB(visibleXPoint, top, visibleXPoint + columnWidth, - top + (y2 - axisHeight).abs()); - _segments.add(rect); + top + (y2 - axisHeight!).abs()); + _segments!.add(rect); final double yPoint = y >= axisCrossesAt ? rect.top : rect.bottom; - coordinatePoints.add(Offset(visibleXPoint + columnWidth / 2, yPoint)); + coordinatePoints!.add(Offset(visibleXPoint + columnWidth / 2, yPoint)); } } @@ -328,33 +330,33 @@ class _RenderSparkBarChart extends RenderSparkChart { @override double getAxisHeight() { final double value = axisCrossesAt; - final double minimumColumnValue = minY < 0 ? minY : 0; - double axisLineHeight = - areaSize.height - ((areaSize.height / diffY) * (-minY)); - axisLineHeight = (minY < 0 && maxY <= 0) + final double minimumColumnValue = minY! < 0 ? minY! : 0; + double? axisLineHeight = + areaSize!.height - ((areaSize!.height / diffY!) * (-minY!)); + axisLineHeight = (minY! < 0 && maxY! <= 0) ? 0 - : (minY < 0 && maxY > 0) + : (minY! < 0 && maxY! > 0) ? axisHeight - : areaSize.height; - if (value >= minimumColumnValue && value <= maxY) { - axisLineHeight = areaSize.height - - (areaSize.height * ((value - minimumColumnValue) / diffY)) + : areaSize!.height; + if (value >= minimumColumnValue && value <= maxY!) { + axisLineHeight = areaSize!.height - + (areaSize!.height * ((value - minimumColumnValue) / diffY!)) .roundToDouble(); } - return axisLineHeight; + return axisLineHeight!; } /// Method to calculate the plot band position @override void calculatePlotBandPosition() { - final double height = areaSize.height; + final double height = areaSize!.height; final double start = - (plotBand.start ?? minY) < minY ? minY : (plotBand.start ?? minY); + (plotBand!.start ?? minY!) < minY! ? minY! : (plotBand!.start ?? minY!); final double end = - (plotBand.end ?? maxY) > maxY ? maxY : (plotBand.end ?? maxY); - final double baseValue = minY < 0 ? minY : 0; - plotBandStartHeight = (height - ((height / diffY) * (start - baseValue))); - plotBandEndHeight = (height - ((height / diffY) * (end - baseValue))); + (plotBand!.end ?? maxY!) > maxY! ? maxY! : (plotBand!.end ?? maxY!); + final double baseValue = minY! < 0 ? minY! : 0; + plotBandStartHeight = (height - ((height / diffY!) * (start - baseValue))); + plotBandEndHeight = (height - ((height / diffY!) * (end - baseValue))); } /// Method to render bar series @@ -371,38 +373,38 @@ class _RenderSparkBarChart extends RenderSparkChart { final bool canDrawBorder = borderColor != null && borderColor != Colors.transparent && borderWidth != null && - borderWidth > 0; + borderWidth! > 0; Rect rect; - _highPoint = coordinatePoints[0].dy; - _lowPoint = coordinatePoints[0].dy; - for (int i = 0; i < _segments.length; i++) { - if (_highPoint < coordinatePoints[i].dy) { - _highPoint = coordinatePoints[i].dy; + _highPoint = coordinatePoints![0].dy; + _lowPoint = coordinatePoints![0].dy; + for (int i = 0; i < _segments!.length; i++) { + if (_highPoint < coordinatePoints![i].dy) { + _highPoint = coordinatePoints![i].dy; } - if (_lowPoint > coordinatePoints[i].dy) { - _lowPoint = coordinatePoints[i].dy; + if (_lowPoint > coordinatePoints![i].dy) { + _lowPoint = coordinatePoints![i].dy; } rect = Rect.fromLTRB( - _segments[i].left + offset.dx, - _segments[i].top + offset.dy, - _segments[i].right + offset.dx, - _segments[i].bottom + offset.dy); - if (dataPoints[i].y == maxY && highPointColor != null) { - currentColor = highPointColor; - } else if (dataPoints[i].y == minY && lowPointColor != null) { - currentColor = lowPointColor; + _segments![i].left + offset.dx, + _segments![i].top + offset.dy, + _segments![i].right + offset.dx, + _segments![i].bottom + offset.dy); + if (dataPoints![i].y == maxY && highPointColor != null) { + currentColor = highPointColor!; + } else if (dataPoints![i].y == minY && lowPointColor != null) { + currentColor = lowPointColor!; } else if (i == 0 && firstPointColor != null) { - currentColor = firstPointColor; - } else if (i == _segments.length - 1 && lastPointColor != null) { - currentColor = lastPointColor; - } else if (dataPoints[i].y < axisCrossesAt && + currentColor = firstPointColor!; + } else if (i == _segments!.length - 1 && lastPointColor != null) { + currentColor = lastPointColor!; + } else if (dataPoints![i].y < axisCrossesAt && negativePointColor != null) { - currentColor = negativePointColor; + currentColor = negativePointColor!; } else { - currentColor = color; + currentColor = color!; } - dataPoints[i].color = currentColor; + dataPoints![i].color = currentColor; paint = Paint()..color = currentColor; canvas.drawRect(rect, paint); if (canDrawBorder) { @@ -411,22 +413,23 @@ class _RenderSparkBarChart extends RenderSparkChart { if (labelDisplayMode != SparkChartLabelDisplayMode.none && labelStyle != null) { - size = getTextSize(dataLabels[i], labelStyle); - yPosition = (dataPoints[i].y > 0 - ? ((_segments[i].topCenter.dy + offset.dy) - size.height) - : ((_segments[i].bottomCenter.dy + offset.dy))); - dataPoints[i].dataLabelOffset = Offset( - (offset.dx + _segments[i].topCenter.dx) - size.width / 2, + size = getTextSize(dataLabels![i], labelStyle!); + yPosition = (dataPoints![i].y > 0 + ? ((_segments![i].topCenter.dy + offset.dy) - size.height) + : ((_segments![i].bottomCenter.dy + offset.dy))); + dataPoints![i].dataLabelOffset = Offset( + (offset.dx + _segments![i].topCenter.dx) - size.width / 2, yPosition); - if (dataPoints[i].dataLabelOffset.dy <= offset.dy) { - dataPoints[i].dataLabelOffset = Offset( - dataPoints[i].dataLabelOffset.dx, (offset.dy + size.height)); + if (dataPoints![i].dataLabelOffset!.dy <= offset.dy) { + dataPoints![i].dataLabelOffset = Offset( + dataPoints![i].dataLabelOffset!.dx, (offset.dy + size.height)); } - if (dataPoints[i].dataLabelOffset.dy >= offset.dy + areaSize.height) { - dataPoints[i].dataLabelOffset = Offset( - dataPoints[i].dataLabelOffset.dx, - (offset.dy + areaSize.height) - size.height); + if (dataPoints![i].dataLabelOffset!.dy >= + offset.dy + areaSize!.height) { + dataPoints![i].dataLabelOffset = Offset( + dataPoints![i].dataLabelOffset!.dx, + (offset.dy + areaSize!.height) - size.height); } } } @@ -436,25 +439,26 @@ class _RenderSparkBarChart extends RenderSparkChart { void paint(PaintingContext context, Offset offset) { super.paint(context, offset); if (coordinatePoints != null && - coordinatePoints.isNotEmpty && + coordinatePoints!.isNotEmpty && dataPoints != null && - dataPoints.isNotEmpty) { + dataPoints!.isNotEmpty) { _renderBarSeries(context.canvas, offset); - if (labelDisplayMode != SparkChartLabelDisplayMode.none) { + if (labelDisplayMode != null && + labelDisplayMode != SparkChartLabelDisplayMode.none) { renderDataLabel( context.canvas, - dataLabels, - dataPoints, - coordinatePoints, - labelStyle, - labelDisplayMode, + dataLabels!, + dataPoints!, + coordinatePoints!, + labelStyle!, + labelDisplayMode!, 'Bar', - themeData, + themeData!, offset, - color, + color!, _highPoint, _lowPoint, - _segments); + _segments!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_line_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_line_renderer.dart index 0e19760dd..bc127dcfc 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_line_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_line_renderer.dart @@ -13,38 +13,33 @@ class SfSparkLineChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { /// Creates the render object for spark chart SfSparkLineChartRenderObjectWidget( - {Key key, - double width, - List dashArray, - List data, - int dataCount, - SparkChartIndexedValueMapper xValueMapper, - SparkChartIndexedValueMapper yValueMapper, - bool isInversed, - double axisCrossesAt, - Color axisLineColor, - double axisLineWidth, - List axisLineDashArray, - Color firstPointColor, - Color lowPointColor, - Color highPointColor, - Color lastPointColor, - Color negativePointColor, - Color color, - SparkChartPlotBand plotBand, - SparkChartMarker marker, - SparkChartLabelDisplayMode labelDisplayMode, - TextStyle labelStyle, - ThemeData themeData, - SparkChartDataDetails sparkChartDataDetails, - List coordinatePoints, - List dataPoints}) - : width = width, - dashArray = dashArray, - marker = marker, - labelDisplayMode = labelDisplayMode, - labelStyle = labelStyle, - super( + {Key? key, + this.width, + this.dashArray, + List? data, + int? dataCount, + SparkChartIndexedValueMapper? xValueMapper, + SparkChartIndexedValueMapper? yValueMapper, + bool? isInversed, + double? axisCrossesAt, + Color? axisLineColor, + double? axisLineWidth, + List? axisLineDashArray, + Color? firstPointColor, + Color? lowPointColor, + Color? highPointColor, + Color? lastPointColor, + Color? negativePointColor, + Color? color, + SparkChartPlotBand? plotBand, + this.marker, + this.labelDisplayMode, + this.labelStyle, + ThemeData? themeData, + SparkChartDataDetails? sparkChartDataDetails, + List? coordinatePoints, + List? dataPoints}) + : super( key: key, data: data, dataCount: dataCount, @@ -68,19 +63,19 @@ class SfSparkLineChartRenderObjectWidget dataPoints: dataPoints); /// Specifies the line width - final double width; + final double? width; /// Specifies the dash array - final List dashArray; + final List? dashArray; /// Specifies the area chart marker - final SparkChartMarker marker; + final SparkChartMarker? marker; /// Specifies the spark chart data label - final SparkChartLabelDisplayMode labelDisplayMode; + final SparkChartLabelDisplayMode? labelDisplayMode; /// Specifies the spark chart data label style - final TextStyle labelStyle; + final TextStyle? labelStyle; @override RenderObject createRenderObject(BuildContext context) { @@ -106,7 +101,7 @@ class SfSparkLineChartRenderObjectWidget marker: marker, labelDisplayMode: labelDisplayMode, labelStyle: labelStyle, - themeData: themeData, + themeData: themeData!, sparkChartDataDetails: sparkChartDataDetails, coordinatePoints: coordinatePoints, dataPoints: dataPoints); @@ -147,31 +142,31 @@ class SfSparkLineChartRenderObjectWidget class _RenderSparkLineChart extends RenderSparkChart { /// Creates the render object widget _RenderSparkLineChart( - {List data, - int dataCount, - SparkChartIndexedValueMapper xValueMapper, - SparkChartIndexedValueMapper yValueMapper, - bool isInversed, - double axisCrossesAt, - double axisLineWidth, - Color axisLineColor, - List axisLineDashArray, - Color color, - Color firstPointColor, - Color lastPointColor, - Color highPointColor, - Color lowPointColor, - Color negativePointColor, - SparkChartPlotBand plotBand, - double width, - List dashArray, - SparkChartMarker marker, - SparkChartLabelDisplayMode labelDisplayMode, - TextStyle labelStyle, - SparkChartDataDetails sparkChartDataDetails, - ThemeData themeData, - List coordinatePoints, - List dataPoints}) + {List? data, + int? dataCount, + SparkChartIndexedValueMapper? xValueMapper, + SparkChartIndexedValueMapper? yValueMapper, + bool? isInversed, + double? axisCrossesAt, + double? axisLineWidth, + Color? axisLineColor, + List? axisLineDashArray, + Color? color, + Color? firstPointColor, + Color? lastPointColor, + Color? highPointColor, + Color? lowPointColor, + Color? negativePointColor, + SparkChartPlotBand? plotBand, + double? width, + List? dashArray, + SparkChartMarker? marker, + SparkChartLabelDisplayMode? labelDisplayMode, + TextStyle? labelStyle, + SparkChartDataDetails? sparkChartDataDetails, + ThemeData? themeData, + List? coordinatePoints, + List? dataPoints}) : _width = width, _dashArray = dashArray, _marker = marker, @@ -200,13 +195,13 @@ class _RenderSparkLineChart extends RenderSparkChart { dataPoints: dataPoints); /// Defines the line width. - double _width; + double? _width; /// Returns the line width value - double get width => _width; + double? get width => _width; /// Set the line width value - set width(double value) { + set width(double? value) { if (_width != value) { _width = value; markNeedsPaint(); @@ -214,13 +209,13 @@ class _RenderSparkLineChart extends RenderSparkChart { } /// Defines the dash array. - List _dashArray; + List? _dashArray; /// Returns the dash arry value - List get dashArray => _dashArray; + List? get dashArray => _dashArray; /// Set the line width value - set dashArray(List value) { + set dashArray(List? value) { if (_dashArray != value) { _dashArray = value; markNeedsPaint(); @@ -228,13 +223,13 @@ class _RenderSparkLineChart extends RenderSparkChart { } /// Defines the marker for spark chart - SparkChartMarker _marker; + SparkChartMarker? _marker; /// Gets the marker for spark chart - SparkChartMarker get marker => _marker; + SparkChartMarker? get marker => _marker; /// Sets the marker for spark chart - set marker(SparkChartMarker value) { + set marker(SparkChartMarker? value) { if (_marker != value) { _marker = value; markNeedsPaint(); @@ -242,13 +237,13 @@ class _RenderSparkLineChart extends RenderSparkChart { } /// Defines the spark chart data label mode - SparkChartLabelDisplayMode _labelDisplayMode; + SparkChartLabelDisplayMode? _labelDisplayMode; /// Returns the spark chart data label mode - SparkChartLabelDisplayMode get labelDisplayMode => _labelDisplayMode; + SparkChartLabelDisplayMode? get labelDisplayMode => _labelDisplayMode; /// Sets the spark chart data label mode - set labelDisplayMode(SparkChartLabelDisplayMode value) { + set labelDisplayMode(SparkChartLabelDisplayMode? value) { if (_labelDisplayMode != value) { _labelDisplayMode = value; markNeedsPaint(); @@ -256,13 +251,13 @@ class _RenderSparkLineChart extends RenderSparkChart { } /// Defines the spark chart data label text style - TextStyle _labelStyle; + TextStyle? _labelStyle; /// Returns the spark chart data label text style - TextStyle get labelStyle => _labelStyle; + TextStyle? get labelStyle => _labelStyle; /// Sets the spark chart data label mode - set labelStyle(TextStyle value) { + set labelStyle(TextStyle? value) { if (_labelStyle != value) { _labelStyle = value; markNeedsPaint(); @@ -270,94 +265,94 @@ class _RenderSparkLineChart extends RenderSparkChart { } /// Specifies the low point in series - num _lowPoint; + late num _lowPoint; /// Specifies the high point in series - num _highPoint; + late num _highPoint; /// Render line series void _renderLineSeries(Canvas canvas, Offset offset) { - if (width != null && width > 0) { + if (width != null && width! > 0) { final Paint paint = Paint() - ..strokeWidth = width + ..strokeWidth = width! ..style = PaintingStyle.stroke - ..color = color; + ..color = color!; Size size; double yPosition; - _highPoint = coordinatePoints[0].dy; - _lowPoint = coordinatePoints[0].dy; - if (dashArray != null && dashArray.isNotEmpty) { + _highPoint = coordinatePoints![0].dy; + _lowPoint = coordinatePoints![0].dy; + if (dashArray != null && dashArray!.isNotEmpty) { Offset point1, point2; - for (int i = 0; i < coordinatePoints.length; i++) { - if (_highPoint < coordinatePoints[i].dy) { - _highPoint = coordinatePoints[i].dy; + for (int i = 0; i < coordinatePoints!.length; i++) { + if (_highPoint < coordinatePoints![i].dy) { + _highPoint = coordinatePoints![i].dy; } - if (_lowPoint > coordinatePoints[i].dy) { - _lowPoint = coordinatePoints[i].dy; + if (_lowPoint > coordinatePoints![i].dy) { + _lowPoint = coordinatePoints![i].dy; } - if (i < coordinatePoints.length - 1) { - point1 = Offset(offset.dx + coordinatePoints[i].dx, - offset.dy + coordinatePoints[i].dy); - point2 = Offset(offset.dx + coordinatePoints[i + 1].dx, - offset.dy + coordinatePoints[i + 1].dy); + if (i < coordinatePoints!.length - 1) { + point1 = Offset(offset.dx + coordinatePoints![i].dx, + offset.dy + coordinatePoints![i].dy); + point2 = Offset(offset.dx + coordinatePoints![i + 1].dx, + offset.dy + coordinatePoints![i + 1].dy); drawDashedPath(canvas, paint, point1, point2, dashArray); } if (labelDisplayMode != SparkChartLabelDisplayMode.none && labelStyle != null) { - size = getTextSize(dataLabels[i], labelStyle); + size = getTextSize(dataLabels![i], labelStyle!); yPosition = (marker != null && - marker.displayMode != SparkChartMarkerDisplayMode.none - ? (dataPoints[i].y > 0 - ? (coordinatePoints[i].dy - size.height - marker.size / 2) - : (coordinatePoints[i].dy + marker.size / 2)) - : dataPoints[i].y > 0 - ? (coordinatePoints[i].dy - size.height) - : (coordinatePoints[i].dy)); - dataPoints[i].dataLabelOffset = Offset( - offset.dx + coordinatePoints[i].dx, offset.dy + yPosition); - - _positionDataLabels(dataPoints[i], size, offset); + marker!.displayMode != SparkChartMarkerDisplayMode.none + ? (dataPoints![i].y > 0 + ? (coordinatePoints![i].dy - size.height - marker!.size / 2) + : (coordinatePoints![i].dy + marker!.size / 2)) + : dataPoints![i].y > 0 + ? (coordinatePoints![i].dy - size.height) + : (coordinatePoints![i].dy)); + dataPoints![i].dataLabelOffset = Offset( + (offset.dx + coordinatePoints![i].dx) - size.width / 2, + offset.dy + yPosition); + _positionDataLabels(dataPoints![i], size, offset); } } } else { final Path path = Path(); - for (int i = 0; i < coordinatePoints.length; i++) { - if (_highPoint < coordinatePoints[i].dy) { - _highPoint = coordinatePoints[i].dy; + for (int i = 0; i < coordinatePoints!.length; i++) { + if (_highPoint < coordinatePoints![i].dy) { + _highPoint = coordinatePoints![i].dy; } - if (_lowPoint > coordinatePoints[i].dy) { - _lowPoint = coordinatePoints[i].dy; + if (_lowPoint > coordinatePoints![i].dy) { + _lowPoint = coordinatePoints![i].dy; } if (i == 0) { - path.moveTo(offset.dx + coordinatePoints[i].dx, - offset.dy + coordinatePoints[i].dy); + path.moveTo(offset.dx + coordinatePoints![i].dx, + offset.dy + coordinatePoints![i].dy); } - if (i < coordinatePoints.length - 1) { - path.lineTo(offset.dx + coordinatePoints[i + 1].dx, - offset.dy + coordinatePoints[i + 1].dy); + if (i < coordinatePoints!.length - 1) { + path.lineTo(offset.dx + coordinatePoints![i + 1].dx, + offset.dy + coordinatePoints![i + 1].dy); } if (labelDisplayMode != SparkChartLabelDisplayMode.none && labelStyle != null) { - size = getTextSize(dataLabels[i], labelStyle); + size = getTextSize(dataLabels![i], labelStyle!); yPosition = (marker != null && - marker.displayMode != SparkChartMarkerDisplayMode.none - ? (dataPoints[i].y > 0 - ? (coordinatePoints[i].dy - size.height - marker.size / 2) - : (coordinatePoints[i].dy + marker.size / 2)) - : dataPoints[i].y > 0 - ? (coordinatePoints[i].dy - size.height) - : (coordinatePoints[i].dy)); - dataPoints[i].dataLabelOffset = Offset( - offset.dx + coordinatePoints[i].dx, offset.dy + yPosition); - - _positionDataLabels(dataPoints[i], size, offset); + marker!.displayMode != SparkChartMarkerDisplayMode.none + ? (dataPoints![i].y > 0 + ? (coordinatePoints![i].dy - size.height - marker!.size / 2) + : (coordinatePoints![i].dy + marker!.size / 2)) + : dataPoints![i].y > 0 + ? (coordinatePoints![i].dy - size.height) + : (coordinatePoints![i].dy)); + dataPoints![i].dataLabelOffset = Offset( + (offset.dx + coordinatePoints![i].dx) - size.width / 2, + offset.dy + yPosition); + _positionDataLabels(dataPoints![i], size, offset); } } @@ -368,33 +363,33 @@ class _RenderSparkLineChart extends RenderSparkChart { void _positionDataLabels( SparkChartPoint dataPoint, Size size, Offset offset) { - if (dataPoint.dataLabelOffset.dx <= offset.dx) { + if (dataPoint.dataLabelOffset!.dx <= offset.dx) { dataPoint.dataLabelOffset = - Offset((offset.dx), dataPoint.dataLabelOffset.dy); + Offset((offset.dx), dataPoint.dataLabelOffset!.dy); } - if (dataPoint.dataLabelOffset.dx >= offset.dx + areaSize.width) { + if (dataPoint.dataLabelOffset!.dx >= offset.dx + areaSize!.width) { dataPoint.dataLabelOffset = Offset( - ((offset.dx + areaSize.width) - size.width), - dataPoint.dataLabelOffset.dy); + ((offset.dx + areaSize!.width) - size.width), + dataPoint.dataLabelOffset!.dy); } - if (dataPoint.dataLabelOffset.dy <= offset.dy) { + if (dataPoint.dataLabelOffset!.dy <= offset.dy) { dataPoint.dataLabelOffset = Offset( - dataPoint.dataLabelOffset.dx, + dataPoint.dataLabelOffset!.dx, (offset.dy + (marker != null && - marker.displayMode != SparkChartMarkerDisplayMode.none - ? marker.size / 2 + size.height + marker!.displayMode != SparkChartMarkerDisplayMode.none + ? marker!.size / 2 + size.height : size.height))); } - if (dataPoint.dataLabelOffset.dy >= offset.dy + areaSize.height) { + if (dataPoint.dataLabelOffset!.dy >= offset.dy + areaSize!.height) { dataPoint.dataLabelOffset = Offset( - dataPoint.dataLabelOffset.dx, - (offset.dy + areaSize.height) - + dataPoint.dataLabelOffset!.dx, + (offset.dy + areaSize!.height) - (marker != null && - marker.displayMode != SparkChartMarkerDisplayMode.none - ? marker.size / 2 + size.height + marker!.displayMode != SparkChartMarkerDisplayMode.none + ? marker!.size / 2 + size.height : size.height)); } } @@ -403,42 +398,45 @@ class _RenderSparkLineChart extends RenderSparkChart { void paint(PaintingContext context, Offset offset) { super.paint(context, offset); if (coordinatePoints != null && - coordinatePoints.isNotEmpty && + coordinatePoints!.isNotEmpty && dataPoints != null && - dataPoints.isNotEmpty) { + dataPoints!.isNotEmpty) { _renderLineSeries(context.canvas, offset); if (marker != null && - marker.displayMode != SparkChartMarkerDisplayMode.none) { + marker!.displayMode != SparkChartMarkerDisplayMode.none && + marker!.borderWidth > 0) { renderMarker( context.canvas, offset, - marker, - coordinatePoints, - dataPoints, - color, + marker!, + coordinatePoints!, + dataPoints!, + color!, 'Line', _highPoint, _lowPoint, - themeData, + axisCrossesAt!, + themeData!, lowPointColor, highPointColor, negativePointColor, firstPointColor, lastPointColor); } - if (labelDisplayMode != SparkChartLabelDisplayMode.none) { + if (labelDisplayMode != null && + labelDisplayMode != SparkChartLabelDisplayMode.none) { renderDataLabel( context.canvas, - dataLabels, - dataPoints, - coordinatePoints, - labelStyle, - labelDisplayMode, + dataLabels!, + dataPoints!, + coordinatePoints!, + labelStyle!, + labelDisplayMode!, 'Line', - themeData, + themeData!, offset, - color, + color!, _highPoint, _lowPoint); } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_win_loss_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_win_loss_renderer.dart index f95046034..6cd9a5c5c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_win_loss_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_win_loss_renderer.dart @@ -12,30 +12,30 @@ class SfSparkWinLossChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { /// Creates the render object for spark chart SfSparkWinLossChartRenderObjectWidget( - {Key key, - List data, - int dataCount, - SparkChartIndexedValueMapper xValueMapper, - SparkChartIndexedValueMapper yValueMapper, - Color color, - SparkChartPlotBand plotBand, - double borderWidth, - Color borderColor, - Color tiePointColor, - bool isInversed, - double axisCrossesAt, - Color axisLineColor, - double axisLineWidth, - List axisLineDashArray, - Color firstPointColor, - Color lowPointColor, - Color highPointColor, - Color lastPointColor, - Color negativePointColor, - SparkChartDataDetails sparkChartDataDetails, - ThemeData themeData, - List coordinatePoints, - List dataPoints}) + {Key? key, + List? data, + int? dataCount, + SparkChartIndexedValueMapper? xValueMapper, + SparkChartIndexedValueMapper? yValueMapper, + Color? color, + SparkChartPlotBand? plotBand, + double? borderWidth, + Color? borderColor, + Color? tiePointColor, + bool? isInversed, + double? axisCrossesAt, + Color? axisLineColor, + double? axisLineWidth, + List? axisLineDashArray, + Color? firstPointColor, + Color? lowPointColor, + Color? highPointColor, + Color? lastPointColor, + Color? negativePointColor, + SparkChartDataDetails? sparkChartDataDetails, + ThemeData? themeData, + List? coordinatePoints, + List? dataPoints}) : borderWidth = borderWidth, borderColor = borderColor, tiePointColor = tiePointColor, @@ -63,13 +63,13 @@ class SfSparkWinLossChartRenderObjectWidget dataPoints: dataPoints); /// Specifies the area chart border width - final double borderWidth; + final double? borderWidth; /// Specifies the area chart border color - final Color borderColor; + final Color? borderColor; /// Specifies the tie point color - final Color tiePointColor; + final Color? tiePointColor; @override RenderObject createRenderObject(BuildContext context) { @@ -132,29 +132,29 @@ class SfSparkWinLossChartRenderObjectWidget class _RenderSparkWinLossChart extends RenderSparkChart { /// Creates the render object widget _RenderSparkWinLossChart( - {List data, - int dataCount, - SparkChartIndexedValueMapper xValueMapper, - SparkChartIndexedValueMapper yValueMapper, - bool isInversed, - double axisCrossesAt, - double axisLineWidth, - Color axisLineColor, - List axisLineDashArray, - Color color, - Color firstPointColor, - Color lastPointColor, - Color highPointColor, - Color lowPointColor, - Color negativePointColor, - SparkChartPlotBand plotBand, - Color tiePointColor, - double borderWidth, - Color borderColor, - ThemeData themeData, - SparkChartDataDetails sparkChartDataDetails, - List coordinatePoints, - List dataPoints}) + {List? data, + int? dataCount, + SparkChartIndexedValueMapper? xValueMapper, + SparkChartIndexedValueMapper? yValueMapper, + bool? isInversed, + double? axisCrossesAt, + double? axisLineWidth, + Color? axisLineColor, + List? axisLineDashArray, + Color? color, + Color? firstPointColor, + Color? lastPointColor, + Color? highPointColor, + Color? lowPointColor, + Color? negativePointColor, + SparkChartPlotBand? plotBand, + Color? tiePointColor, + double? borderWidth, + Color? borderColor, + ThemeData? themeData, + SparkChartDataDetails? sparkChartDataDetails, + List? coordinatePoints, + List? dataPoints}) : _tiePointColor = tiePointColor, _borderWidth = borderWidth, _borderColor = borderColor, @@ -181,13 +181,13 @@ class _RenderSparkWinLossChart extends RenderSparkChart { dataPoints: dataPoints); /// Defines the border width. - double _borderWidth; + double? _borderWidth; /// Returns the border width value - double get borderWidth => _borderWidth; + double? get borderWidth => _borderWidth; /// Set the border width value - set borderWidth(double value) { + set borderWidth(double? value) { if (_borderWidth != value) { _borderWidth = value; markNeedsPaint(); @@ -195,13 +195,13 @@ class _RenderSparkWinLossChart extends RenderSparkChart { } /// Defines the border color. - Color _borderColor; + Color? _borderColor; /// Returns the border color - Color get borderColor => _borderColor; + Color? get borderColor => _borderColor; /// Set the border color value - set borderColor(Color value) { + set borderColor(Color? value) { if (_borderColor != value) { _borderColor = value; markNeedsPaint(); @@ -209,13 +209,13 @@ class _RenderSparkWinLossChart extends RenderSparkChart { } /// Defines the tie point color. - Color _tiePointColor; + Color? _tiePointColor; /// Returns the tie point color - Color get tiePointColor => _tiePointColor; + Color? get tiePointColor => _tiePointColor; /// Set the tie point color - set tiePointColor(Color value) { + set tiePointColor(Color? value) { if (_tiePointColor != value) { _tiePointColor = value; markNeedsPaint(); @@ -223,18 +223,19 @@ class _RenderSparkWinLossChart extends RenderSparkChart { } /// Specifies the win loss segments - List _segments; + late List _segments; @override void calculateRenderingPoints() { - diffX = maxX - minX; - diffY = maxY - minY; + diffX = maxX! - minX!; + diffY = maxY! - minY!; diffX = diffX == 0 ? 1 : diffX; diffY = diffY == 0 ? 1 : diffY; _segments = []; - final double xInterval = - dataPoints[1].x.toDouble() - dataPoints[0].x.toDouble(); + final double xInterval = dataPoints!.length > 1 + ? dataPoints![1].x.toDouble() - dataPoints![0].x.toDouble() + : dataPoints!.length.toDouble(); final double columnSpace = 0.5; // Default space for column and winloss final double space = columnSpace * 2; final double winLossFactor = 0.5; @@ -244,32 +245,32 @@ class _RenderSparkWinLossChart extends RenderSparkChart { double bottom; Rect rect; double x, y, y2; - double columnWidth = areaSize.width / (((maxX - minX) / xInterval) + 1); + double columnWidth = areaSize!.width / (((maxX! - minX!) / xInterval) + 1); columnWidth -= space; axisHeight = getAxisHeight(); - if (coordinatePoints.isNotEmpty) { - coordinatePoints.clear(); + if (coordinatePoints!.isNotEmpty) { + coordinatePoints!.clear(); } - for (int i = 0; i < dataPoints.length; i++) { - x = dataPoints[i].x.toDouble(); - y = dataPoints[i].y.toDouble(); + for (int i = 0; i < dataPoints!.length; i++) { + x = dataPoints![i].x.toDouble(); + y = dataPoints![i].y.toDouble(); visibleXPoint = - (((x - minX) / xInterval) * (columnWidth + space)) + (space / 2); - y2 = (y > axisCrossesAt) - ? (areaSize.height / 2) - : (y < axisCrossesAt) - ? areaSize.height * winLossFactor - : ((areaSize.height * winLossFactor) - - (areaSize.height / heightFactor)); + (((x - minX!) / xInterval) * (columnWidth + space)) + (space / 2); + y2 = (y > axisCrossesAt!) + ? (areaSize!.height / 2) + : (y < axisCrossesAt!) + ? areaSize!.height * winLossFactor + : ((areaSize!.height * winLossFactor) - + (areaSize!.height / heightFactor)); rectHeight = - (y != axisCrossesAt) ? (areaSize.height / 2) : areaSize.height / 20; - bottom = y > axisCrossesAt ? rectHeight - y2 : rectHeight + y2; + (y != axisCrossesAt) ? (areaSize!.height / 2) : areaSize!.height / 20; + bottom = y > axisCrossesAt! ? rectHeight - y2 : rectHeight + y2; rect = Rect.fromLTRB(visibleXPoint, y2, visibleXPoint + columnWidth, bottom); _segments.add(rect); - coordinatePoints.add(Offset(visibleXPoint + columnWidth / 2, y2)); + coordinatePoints!.add(Offset(visibleXPoint + columnWidth / 2, y2)); } } @@ -279,7 +280,7 @@ class _RenderSparkWinLossChart extends RenderSparkChart { ..color = tiePointColor ?? Colors.deepPurple; final Paint negativePointPaint = Paint() ..color = negativePointColor ?? Colors.red; - final Paint paint = Paint()..color = color; + final Paint paint = Paint()..color = color!; final Paint strokePaint = Paint() ..color = borderColor ?? Colors.transparent ..strokeWidth = borderWidth ?? 0 @@ -288,20 +289,33 @@ class _RenderSparkWinLossChart extends RenderSparkChart { final bool canDrawBorder = borderColor != null && borderColor != Colors.transparent && borderWidth != null && - borderWidth > 0; + borderWidth! > 0; Rect rect; - for (int i = 0; i < dataPoints.length; i++) { + for (int i = 0; i < dataPoints!.length; i++) { rect = Rect.fromLTRB( _segments[i].left + offset.dx, _segments[i].top + offset.dy, _segments[i].right + offset.dx, _segments[i].bottom + offset.dy); - if (dataPoints[i].y < axisCrossesAt) { + if (dataPoints![i].y < axisCrossesAt) { canvas.drawRect(rect, negativePointPaint); - } else if (dataPoints[i].y == axisCrossesAt) { + } else if (dataPoints![i].y == axisCrossesAt) { canvas.drawRect(rect, tiePointPaint); + } else if (dataPoints![i].y == maxY && highPointColor != null) { + paint.color = highPointColor!; + canvas.drawRect(rect, paint); + } else if (dataPoints![i].y == minY && lowPointColor != null) { + paint.color = lowPointColor!; + canvas.drawRect(rect, paint); + } else if (i == 0 && firstPointColor != null) { + paint.color = firstPointColor!; + canvas.drawRect(rect, paint); + } else if (i == _segments.length - 1 && lastPointColor != null) { + paint.color = lastPointColor!; + canvas.drawRect(rect, paint); } else { + paint.color = color!; canvas.drawRect(rect, paint); } @@ -314,9 +328,9 @@ class _RenderSparkWinLossChart extends RenderSparkChart { @override void paint(PaintingContext context, Offset offset) { if (coordinatePoints != null && - coordinatePoints.isNotEmpty && + coordinatePoints!.isNotEmpty && dataPoints != null && - dataPoints.isNotEmpty) { + dataPoints!.isNotEmpty) { if (plotBand != null) { renderPlotBand(context.canvas, offset); } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_area_base.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_area_base.dart index 0e6d96bb8..e75c32a78 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_area_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_area_base.dart @@ -9,9 +9,23 @@ import '../trackball/trackball_renderer.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; -/// Represents the spark area chart widget +/// This class renders an area spark chart. The [SfSparkAreaChart] is a +/// very small chart, typically drawn without axis ticks and labels. +/// It presents the general shape of data in a simple and highly condensed way. +/// +/// To render an area spark chart, create the instance of [SfSparkAreaChart]. +/// Set the value for `data` property which of type List. +/// Now, it shows the filled area to represent the provided data. +/// +/// It provides option to customize its appearance with the properties +/// such as [color], [borderWidth], [borderColor]. To highlight the provided +/// data, use either its [marker] property or its data label property. +/// To highlight the data point, which is tapped, use its [trackball] property. +/// To highlight the particular region along with the vertical value, +/// use its [plotBand] property. + class SfSparkAreaChart extends StatefulWidget { - /// Creates the spark area chart + /// Creates a spark area chart for the provided set of data with its default view. /// /// ```dart /// @override @@ -25,8 +39,8 @@ class SfSparkAreaChart extends StatefulWidget { /// } /// ``` SfSparkAreaChart( - {Key key, - List data, + {Key? key, + List? data, this.plotBand, this.borderWidth = 0, this.borderColor, @@ -49,10 +63,25 @@ class SfSparkAreaChart extends StatefulWidget { fontWeight: FontWeight.normal, fontSize: 12), this.trackball}) - : _sparkChartDataDetails = SparkChartDataDetails(data: data), + : _sparkChartDataDetails = SparkChartDataDetails(data: data!), super(key: key); - /// Create the spark area chart with custom data source + /// Creates the spark area chart for the provided set of data with its default view. + /// + /// The difference between the default constructor and this constructor is, + /// in the default constructor uses its data property to get the input data value. + /// The `data` property of the default constructor is of type List. + /// + /// The custom constructor uses its [dataCount], [xValueMapper] and + /// [yValueMapper] to get the input data. + /// + /// The [dataCount] property allows declaring the total data count going to be + /// displayed in the chart. + /// + /// The [xValueMapper[ returns the x- value of the corresponding data point. + /// The [xValueMapper] allows providing num, DateTime, or string as x-value. + /// + /// The [yValueMapper] returns the y-value of the corresponding data point. /// /// ```dart /// class SalesData { @@ -89,16 +118,16 @@ class SfSparkAreaChart extends StatefulWidget { /// ``` SfSparkAreaChart.custom( - {Key key, + {Key? key, /// Data count for the spark charts. - int dataCount, + int? dataCount, /// Specifies the x-value mapping field - SparkChartIndexedValueMapper xValueMapper, + SparkChartIndexedValueMapper? xValueMapper, /// Specifies the y-value maping field - SparkChartIndexedValueMapper yValueMapper, + SparkChartIndexedValueMapper? yValueMapper, this.plotBand, this.borderWidth = 2, this.borderColor, @@ -122,12 +151,18 @@ class SfSparkAreaChart extends StatefulWidget { fontSize: 12), this.trackball}) : _sparkChartDataDetails = SparkChartDataDetails( - dataCount: dataCount, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper), + dataCount: dataCount!, + xValueMapper: xValueMapper!, + yValueMapper: yValueMapper!), super(key: key); - /// Specifies whether to inverse the spark area chart rendering. + /// Inverts the axis from right to left. + /// + /// In the spark chart, the provided set of data are rendered from left to + /// right by default and can be inverted to render the data points + /// from right to left. + /// + /// Defaults to `false`. /// /// ```dart /// @override @@ -143,7 +178,10 @@ class SfSparkAreaChart extends StatefulWidget { /// ``` final bool isInversed; - /// Specifies the axis line's position. + /// Customize the axis position based on the provided y-value.The axis line is + /// rendered on the minimum y-value and can be repositioned to required y-value. + /// + /// Defaults to `zero`. /// /// ```dart /// @override @@ -159,7 +197,9 @@ class SfSparkAreaChart extends StatefulWidget { /// ``` final double axisCrossesAt; - /// Specifies the width of the axis line. + /// Customizes the width of the axis line. + /// + /// Defaults to `2`. /// /// ```dart /// @override @@ -175,7 +215,10 @@ class SfSparkAreaChart extends StatefulWidget { /// ``` final double axisLineWidth; - /// Specified the color of the axis line. + /// Customizes the color of the axis line. + /// Colors.transparent can be set to [axisLineColor] to hide the axis line. + /// + /// Defaults to `Colors.black`. /// /// ```dart /// @override @@ -191,7 +234,10 @@ class SfSparkAreaChart extends StatefulWidget { /// ``` final Color axisLineColor; - /// Specifies the dash array value of the axis line. + /// Dashes of the axis line. Any number of values can be provided on the list. + /// Odd value is considered as rendering size and even value is considered a gap. + /// + /// Defaults to `null` /// /// ```dart /// @override @@ -205,9 +251,14 @@ class SfSparkAreaChart extends StatefulWidget { /// ); /// } /// ``` - final List axisLineDashArray; + final List? axisLineDashArray; - /// Specifies the highest data point color. + /// Customizes the [marker] color of the highest data point. + /// + /// When the high data point is the first or last data point of the provided + /// data set, then either the first or last point color gets applied. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -215,79 +266,107 @@ class SfSparkAreaChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( + /// marker: SparkChartMarker(displayMode: SparkChartMarkerDisplayMode.all), /// highPointColor: Colors.red, /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color highPointColor; + final Color? highPointColor; - /// Specifies the lowest data point color. + /// Customizes the [marker] color of the lowest data point. + /// + /// When the lowest data point is the first or last data point of the + /// provided data set, then either the first or last point color gets applied. + /// + /// Defaults to `null`. /// /// ```dart /// @override - /// Widget build(BuildContext context) { + /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( + /// marker: SparkChartMarker(displayMode: SparkChartMarkerDisplayMode.all), /// lowPointColor: Colors.red, /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color lowPointColor; + final Color? lowPointColor; - /// Specifies the negative point color. + /// Customizes the marker color of negative data point and data point value + /// less than the [axisCrossesAt] value. + /// + /// If the negative data point is either the high or low, first or last data + /// point, then priority will be given to those colors. + /// + /// Defaults to `null`. /// /// ```dart /// @override - /// Widget build(BuildContext context) { + /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( + /// marker: SparkChartMarker(displayMode: SparkChartMarkerDisplayMode.all), /// negativePointColor: Colors.red, - /// data: [18, 24, 30, 14, 28], + /// data: [18, 24, 30, -14, 28], /// )), /// ); /// } /// ``` - final Color negativePointColor; + final Color? negativePointColor; - /// Specifies the first point color. + /// Customizes the [marker] color of the first data point. + /// + /// If the first data point is either the high data point or low data point, + /// then the priority will be given to firstPointColor property. + /// + /// Defaults to `null`. /// /// ```dart /// @override - /// Widget build(BuildContext context) { + /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( + /// marker: SparkChartMarker(displayMode: SparkChartMarkerDisplayMode.all), /// firstPointColor: Colors.red, /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color firstPointColor; + final Color? firstPointColor; - /// Specifies the last point color. + /// Customizes the [marker] color of the last data point. + /// + /// If the last data point is either the high data point or low data point, + /// then the priority will be given to lastPointColor property. + /// + /// Defaults to `null`. /// /// ```dart /// @override - /// Widget build(BuildContext context) { + /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( + /// marker: SparkChartMarker(displayMode: SparkChartMarkerDisplayMode.all), /// lastPointColor: Colors.red, /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color lastPointColor; + final Color? lastPointColor; - /// Specifies the color of the spark area chart. + /// Customizes the spark area chart color. + /// + /// Defaults to `blue`. /// /// ```dart /// @override @@ -303,7 +382,18 @@ class SfSparkAreaChart extends StatefulWidget { /// ``` final Color color; - /// Represents the plot band settings for spark area chart. + /// Render plot band. + /// + /// Plot band is also known as stripline, which is used to shade the different + /// ranges in plot area with different colors to improve the readability + /// of the chart. + /// + /// Plot bands are drawn based on the axis. + /// + /// Provides the property of `start`, `end`, [color], [borderColor], and + /// [borderWidth] to customize the appearance. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -317,9 +407,14 @@ class SfSparkAreaChart extends StatefulWidget { /// ); /// } /// ``` - final SparkChartPlotBand plotBand; + final SparkChartPlotBand? plotBand; - /// Specifies the Border width of the series. + /// Customizes the border width of the spark area chart. + /// The border will be rendered on the top of the spark area chart. + /// To render the border, both the border width and border color property + /// needs to be set. + /// + /// Defaults to `0`. /// /// ```dart /// @override @@ -327,7 +422,8 @@ class SfSparkAreaChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// boderWidth: 2, + /// borderWidth: 2, + /// borderColor: Colors.red, /// data: [18, 24, 30, 14, 28], /// )), /// ); @@ -335,7 +431,12 @@ class SfSparkAreaChart extends StatefulWidget { /// ``` final double borderWidth; - /// Specifies the Border color of the series. + /// Customizes the border color of the spark area chart. + /// The border will be rendered on the top of the spark area chart. + /// To render the border, both the [borderWidth] and borderColor property + /// needs to be set. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -343,16 +444,25 @@ class SfSparkAreaChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// boderWidth: 2, - /// borderColor: Colors.black, + /// borderWidth: 2, + /// borderColor: Colors.red, /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color borderColor; + final Color? borderColor; - /// Represents the marker settings of spark chart. + /// Enables and customizes the markers. + /// + /// Markers are used to provide information about the exact point location. + /// You can add a shape to adorn each data point. Markers can be enabled by + /// using the `displayMode` property of [SparkChartMarker]. + /// + /// Provides the options of [color], [borderWidth], [borderColor] and `shape` + /// of the marker to customize the appearance. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -360,18 +470,35 @@ class SfSparkAreaChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// boderWidth: 2, - /// marker: SparkChartMarker(borderWidth: 3, size: 20, - /// shape: MarkerShape.circle, displayMode: MarkerDisplayMode.all), + /// marker: SparkChartMarker(displayMode: SparkChartMarkerDisplayMode.all), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final SparkChartMarker marker; + final SparkChartMarker? marker; - /// Specifies the spark area data label + /// Enables the data labels. + /// + /// Data labels are used to provide information about the exact point location + /// and its value. + /// + /// * [SparkChartLabelDisplayMode.all] enables the data label for all the data + /// points. + /// * [SparkChartLabelDisplayMode.none] disables the data labels. + /// * [SparkChartLabelDisplayMode.high] displays the data label on highest + /// data point. + /// * [SparkChartLabelDisplayMode.low] displays the data label on lowest + /// data point. + /// * [SparkChartLabelDisplayMode.first] displays the data label on first + /// data point. + /// * [SparkChartLabelDisplayMode.last] displays the data label on first + /// data point. + /// + /// Also refer [SparkChartLabelDisplayMode]. + /// + /// Defaults to `SparkChartDislayMode.none`. /// /// ```dart /// @override @@ -379,16 +506,22 @@ class SfSparkAreaChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// boderWidth: 2, - /// labelDisplayMode: SparkChartLabelDisplayode.high, + /// labelDisplayMode: SparkChartLabelDisplayMode.high, /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final SparkChartLabelDisplayMode labelDisplayMode; + final SparkChartLabelDisplayMode? labelDisplayMode; - /// Specifies the spark area data label + /// Customizes the data label text style. + /// + /// Using the [TextStyle], add style data labels. + /// + /// Defaults to the [TextStyle] property with font size `12.0` and font + /// family `Roboto`. + /// + /// Also refer [TextStyle]. /// /// ```dart /// @override @@ -396,7 +529,7 @@ class SfSparkAreaChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// boderWidth: 2, + /// labelDisplayMode: SparkChartLabelDisplayMode.high, /// labelStyle: TextStyle(fontStyle: FontStyle.italic), /// data: [18, 24, 30, 14, 28], /// )), @@ -405,7 +538,16 @@ class SfSparkAreaChart extends StatefulWidget { /// ``` final TextStyle labelStyle; - /// Represents the track ball options of spark area chart. + /// Enables and customizes the trackball. + /// + /// Trackball feature displays the tooltip for the data points that are closer + /// to the point where you touch on the chart area. This feature can be + /// enabled by creating an instance of [SparkChartTrackball]. + /// + /// Provides option to customizes the `activationMode`, `width`, [color], + /// [labelStyle], `backgroundColor`, [borderColor], [borderWidth]. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -413,14 +555,14 @@ class SfSparkAreaChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// trackball:(borderWidth: 2, - /// borderColor: Colors.black, SparkChartActivationMode: ActivationMode.doubleTap), + /// trackball: + /// SparkChartTrackball(borderWidth: 2, borderColor: Colors.black), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final SparkChartTrackball trackball; + final SparkChartTrackball? trackball; /// Specifies the spark chart data details final SparkChartDataDetails _sparkChartDataDetails; @@ -434,13 +576,22 @@ class SfSparkAreaChart extends StatefulWidget { /// Represents the state class for spark area widget class _SfSparkAreaChartState extends State { /// specifies the theme of the chart - ThemeData _themeData; + late ThemeData _themeData; /// Specifies the series screen coordinate points - List _coordinatePoints; + late List _coordinatePoints; /// Specifies the series data points - List _dataPoints; + late List _dataPoints; + + /// Called when this object is inserted into the tree. + /// + /// The framework will call this method exactly once for each State object it creates. + /// + /// Override this method to perform initialization that depends on the location at + /// which this object was inserted into the tree or on the widget used to configure this object. + /// + /// * In [initState], subscribe to the object. @override void initState() { @@ -449,22 +600,49 @@ class _SfSparkAreaChartState extends State { super.initState(); } + /// Called whenever the widget configuration changes. + /// + /// If the parent widget rebuilds and request that this location in the tree update to display a new widget with the same [runtimeType] and [Widget.key], + /// the framework will update the widget property of this [State] object to refer to the new widget and then call this method with the previous widget as an argument. + /// + /// Override this method to respond when the widget changes. + /// + /// The framework always calls [build] after calling [didUpdateWidget], which means any calls to [setState] in [didUpdateWidget] are redundant. + /// + /// * In [didUpdateWidget] unsubscribe from the old object and subscribe to the new one if the updated widget configuration requires replacing the object. + @override void didUpdateWidget(SfSparkAreaChart oldWidget) { super.didUpdateWidget(oldWidget); } + /// Called when a dependency of this [State] object changes. + /// + /// For example, if the previous call to [build] referenced an [InheritedWidget] that later changed, + /// the framework would call this method to notify this object about the change. + /// + /// This method is also called immediately after [initState]. It is safe to call [BuildContext.dependOnInheritedWidgetOfExactType] from this method. + @override void didChangeDependencies() { _themeData = Theme.of(context); super.didChangeDependencies(); } + /// Describes the part of the user interface represented by this widget. + /// + /// The framework calls this method in a number of different situations. For example: + /// + /// * After calling [initState]. + /// * After calling [didUpdateWidget]. + /// * After receiving a call to [setState]. + /// * After a dependency of this [State] object changes. + @override Widget build(BuildContext context) { if (widget.marker != null && - widget.marker.displayMode != SparkChartMarkerDisplayMode.none) { - final double padding = widget.marker.size / 2; + widget.marker!.displayMode != SparkChartMarkerDisplayMode.none) { + final double padding = widget.marker!.size / 2; return RepaintBoundary( child: Padding( padding: EdgeInsets.all(padding), child: _getSparkAreaChart())); diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_bar_base.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_bar_base.dart index fb7642169..cde67d081 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_bar_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_bar_base.dart @@ -8,9 +8,22 @@ import '../trackball/trackball_renderer.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; -/// Represents the spark bar chart widget +/// This class renders a bar spark chart. The [SfSparkBarChart] is a very small +/// chart, typically drawn without axis ticks and labels. +/// It presents the general shape of data in a simple and highly condensed way. +/// +/// To render a bar spark chart, create the instance of [SfSparkBarChart]. +/// Set the value for `data` property which of type List. Now, it shows +/// the rectangular column to represent the provided data. +/// +/// It provides option to customize its appearance with the properties such as +/// [color], [borderWidth], [borderColor]. To highlight the provided data, +/// use its data label property. To highlight the data point, which is tapped, +/// use its [trackball] property. To highlight the particular region along with +/// the vertical value, use its [plotBand] property. +/// class SfSparkBarChart extends StatefulWidget { - /// Creates the spark bar chart + /// Creates a spark bar chart for the provided set of data with its default view. /// /// ```dart /// @override @@ -24,8 +37,8 @@ class SfSparkBarChart extends StatefulWidget { /// } /// ``` SfSparkBarChart( - {Key key, - List data, + {Key? key, + List? data, this.plotBand, this.borderWidth = 0, this.borderColor, @@ -50,7 +63,23 @@ class SfSparkBarChart extends StatefulWidget { : _sparkChartDataDetails = SparkChartDataDetails(data: data), super(key: key); - /// Create the spark bar chart with custom data source + /// Creates the spark bar chart for the provided set of data with its default view. + /// + /// The difference between the default constructor and this constructor is, + /// in the default constructor uses its data property to get the input + /// data value. The `data` property of the default constructor is + /// of type List. + /// + /// The custom constructor uses its [dataCount], [xValueMapper] and + /// [yValueMapper] to get the input data. + /// + /// The [dataCount] property allows declaring the total data count going to + /// be displayed in the chart. + /// + /// The [xValueMapper[ returns the x- value of the corresponding data point. + /// The [xValueMapper] allows providing num, DateTime, or string as x-value. + /// + /// The [yValueMapper] returns the y-value of the corresponding data point. /// /// ```dart /// class SalesData { @@ -86,16 +115,16 @@ class SfSparkBarChart extends StatefulWidget { /// } /// ``` SfSparkBarChart.custom( - {Key key, + {Key? key, /// Data count for the spark charts. - int dataCount, + int? dataCount, /// Specifies the x-value mapping field - SparkChartIndexedValueMapper xValueMapper, + SparkChartIndexedValueMapper? xValueMapper, /// Specifies the y-value maping field - SparkChartIndexedValueMapper yValueMapper, + SparkChartIndexedValueMapper? yValueMapper, this.plotBand, this.borderWidth = 2, this.borderColor, @@ -123,7 +152,13 @@ class SfSparkBarChart extends StatefulWidget { yValueMapper: yValueMapper), super(key: key); - /// Specifies whether to inverse the spark bar chart rendering. + /// Inverts the axis from right to left. + /// + /// In the spark chart, the provided set of data are rendered from left to + /// right by default and can be inverted to render the data points from right + /// to left. + /// + /// Defaults to `false`. /// /// ```dart /// @override @@ -139,7 +174,11 @@ class SfSparkBarChart extends StatefulWidget { /// ``` final bool isInversed; - /// Specifies the axis line's position. + /// Customize the axis position based on the provided y-value. + /// The axis line is rendered on the minimum y-value and can be repositioned + /// to required y-value. + /// + /// Defaults to `zero`. /// /// ```dart /// @override @@ -147,7 +186,7 @@ class SfSparkBarChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkBarChart( - /// axisCrossesAt: 14, + /// axisCrossesAt: 24, /// data: [18, 24, 30, 14, 28], /// )), /// ); @@ -155,7 +194,9 @@ class SfSparkBarChart extends StatefulWidget { /// ``` final double axisCrossesAt; - /// Specifies the width of the axis line. + /// Customizes the width of the axis line. + /// + /// Defaults to `2`. /// /// ```dart /// @override @@ -171,7 +212,10 @@ class SfSparkBarChart extends StatefulWidget { /// ``` final double axisLineWidth; - /// Specified the color of the axis line. + /// Customizes the color of the axis line. Colors.transparent can be set to + /// [axisLineColor] to hide the axis line. + /// + /// Defaults to `Colors.black`. /// /// ```dart /// @override @@ -187,7 +231,10 @@ class SfSparkBarChart extends StatefulWidget { /// ``` final Color axisLineColor; - /// Specifies the dash array value of the axis line. + /// Dashes of the axis line. Any number of values can be provided on the list. + /// Odd value is considered as rendering size and even value is considered a gap. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -201,10 +248,14 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final List axisLineDashArray; + final List? axisLineDashArray; - /// Specifies the highest data point color. + /// Customizes the color of the highest rectangular column segment. /// + /// When the high data point is the first or last data point of the provided + /// data set, then either the first or last point color gets applied. + /// + /// Defaults to `null`. /// ```dart /// @override /// Widget build(BuildContext context) { @@ -217,9 +268,14 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final Color highPointColor; + final Color? highPointColor; - /// Specifies the lowest data point color. + /// Customizes the color of the lowest rectangular column segment. + /// + /// When the lowest data point is the first or last data point of the provided + /// data set, then either the first or last point color gets applied. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -233,9 +289,15 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final Color lowPointColor; + final Color? lowPointColor; - /// Specifies the negative point color. + /// Customizes the color of negative data point and data point value less than + /// the [axisCrossesAt] value. + /// + /// If the negative data point is either the high or low, first or last data + /// point, then priority will be given to those colors. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -244,14 +306,19 @@ class SfSparkBarChart extends StatefulWidget { /// body: Center( /// child: SfSparkBarChart( /// negativePointColor: Colors.red, - /// data: [18, 24, 30, 14, 28], + /// data: [18, 24, -30, 14, 28], /// )), /// ); /// } /// ``` - final Color negativePointColor; + final Color? negativePointColor; - /// Specifies the first point color. + /// Customizes the color of the first rectangular column segment. + /// + /// If the first data point is either the high data point or low data point, + /// then the priority will be given to firstPointColor property. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -265,9 +332,14 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final Color firstPointColor; + final Color? firstPointColor; - /// Specifies the last point color. + /// Customizes the color of the last rectangular column segment. + /// + /// If the last data point is either the high data point or low data point, + /// then the priority will be given to lastPointColor property. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -281,9 +353,11 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final Color lastPointColor; + final Color? lastPointColor; - /// Specifies the color of the spark bar chart. + /// Customizes the spark bar chart color. + /// + /// Defaults to `blue`. /// /// ```dart /// @override @@ -299,7 +373,18 @@ class SfSparkBarChart extends StatefulWidget { /// ``` final Color color; - /// Represents the plot band settings for spark bar chart. + /// Render plot band. + /// + /// Plot band is also known as stripline, which is used to shade the different + /// ranges in plot area with different colors to improve the readability of + /// the chart. + /// + /// Plot bands are drawn based on the axis. + /// + /// Provides the property of `start`, `end`, [color], [borderColor], and + /// [borderWidth] to customize the appearance. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -313,9 +398,12 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final SparkChartPlotBand plotBand; + final SparkChartPlotBand? plotBand; - /// Specifies the Border width of the series. + /// Customizes the border width of each rectangular column segment. To render + /// the border, both the border width and border color property needs to be set. + /// + /// Defaults to `0`. /// /// ```dart /// @override @@ -331,7 +419,11 @@ class SfSparkBarChart extends StatefulWidget { /// ``` final double borderWidth; - /// Specifies the Border color of the series. + /// Customizes the border color of each rectangular column segment. The border + /// will be rendered on the top of the spark area chart. To render the + /// border, both the [borderWidth] and borderColor property needs to be set. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -345,35 +437,58 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final Color borderColor; + final Color? borderColor; - /// Specifies the spark area data label + /// Enables the data labels. + /// + /// Data labels are used to provide information about the exact point location + /// and its value. + /// + /// * [SparkChartLabelDisplayMode.all] enables the data label for all the + /// data points + /// * [SparkChartLabelDisplayMode.none] disables the data labels + /// * [SparkChartLabelDisplayMode.high] displays the data label on highest + /// data point + /// * [SparkChartLabelDisplayMode.low] displays the data label on lowest + /// data point + /// * [SparkChartLabelDisplayMode.first] displays the data label on first data + /// point + /// * [SparkChartLabelDisplayMode.last] displays the data label on first data + /// point + /// * Also refer [SparkChartLabelDisplayMode] + /// + /// Defaults to `SparkChartDislayMode.none`. /// /// ```dart /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( - /// child: SfSparkAreaChart( - /// boderWidth: 2, - /// labelDisplayMode: SparkChartLabelDisplayode.high, + /// child: SfSparkBarChart( + /// labelDisplayMode: SparkChartLabelDisplayMode.high, /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final SparkChartLabelDisplayMode labelDisplayMode; + final SparkChartLabelDisplayMode? labelDisplayMode; - /// Specifies the spark bar data label + /// Customizes the data label text style. + /// + /// Using the [TextStyle], add style data labels. + /// + /// Defaults to the [TextStyle] property with font size `12.0` + /// and font family `Roboto`. + /// + /// Also refer [TextStyle]. /// /// ```dart /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( - /// child: SfSparkAreaChart( - /// boderWidth: 2, - /// labelStyle: TextStyle(fontStyle: FontStyle.italic), + /// child: SfSparkBarChart(labelStyle: TextStyle(fontStyle: FontStyle.italic), + /// labelDisplayMode: SparkChartLabelDisplayMode.high, /// data: [18, 24, 30, 14, 28], /// )), /// ); @@ -381,7 +496,16 @@ class SfSparkBarChart extends StatefulWidget { /// ``` final TextStyle labelStyle; - /// Represents the track ball options of spark bar chart. + /// Enables and customizes the trackball. + /// + /// Trackball feature displays the tooltip for the data points that are closer + /// to the point where you touch on the chart area. This feature can be + /// enabled by creating an instance of [SparkChartTrackball]. + /// + /// Provides option to customizes the `activationMode`, `width`, [color], + /// [labelStyle], `backgroundColor`, [borderColor], [borderWidth]. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -389,14 +513,15 @@ class SfSparkBarChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkBarChart( - /// trackball:(borderWidth: 2, - /// borderColor: Colors.black, activationMode: SparkChartActivationMode.doubleTap), + /// trackball: SparkChartTrackball(borderWidth: 2, + /// borderColor: Colors.black, + /// activationMode: SparkChartActivationMode.doubleTap), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final SparkChartTrackball trackball; + final SparkChartTrackball? trackball; /// Specifies the spark chart data details final SparkChartDataDetails _sparkChartDataDetails; @@ -409,13 +534,22 @@ class SfSparkBarChart extends StatefulWidget { /// Represents the state class for spark bar widget class _SfSparkBarChartState extends State { /// specifies the theme of the chart - ThemeData _themeData; + late ThemeData _themeData; /// Specifies the series screen coordinate points - List _coordinatePoints; + late List _coordinatePoints; /// Specifies the series data points - List _dataPoints; + late List _dataPoints; + + /// Called when this object is inserted into the tree. + /// + /// The framework will call this method exactly once for each State object it creates. + /// + /// Override this method to perform initialization that depends on the location at + /// which this object was inserted into the tree or on the widget used to configure this object. + /// + /// * In [initState], subscribe to the object. @override void initState() { @@ -424,17 +558,44 @@ class _SfSparkBarChartState extends State { super.initState(); } + /// Called when a dependency of this [State] object changes. + /// + /// For example, if the previous call to [build] referenced an [InheritedWidget] that later changed, + /// the framework would call this method to notify this object about the change. + /// + /// This method is also called immediately after [initState]. It is safe to call [BuildContext.dependOnInheritedWidgetOfExactType] from this method. + @override void didChangeDependencies() { _themeData = Theme.of(context); super.didChangeDependencies(); } + /// Called whenever the widget configuration changes. + /// + /// If the parent widget rebuilds and request that this location in the tree update to display a new widget with the same [runtimeType] and [Widget.key], + /// the framework will update the widget property of this [State] object to refer to the new widget and then call this method with the previous widget as an argument. + /// + /// Override this method to respond when the widget changes. + /// + /// The framework always calls [build] after calling [didUpdateWidget], which means any calls to [setState] in [didUpdateWidget] are redundant. + /// + /// * In [didUpdateWidget] unsubscribe from the old object and subscribe to the new one if the updated widget configuration requires replacing the object. + @override void didUpdateWidget(SfSparkBarChart oldWidget) { super.didUpdateWidget(oldWidget); } + /// Describes the part of the user interface represented by this widget. + /// + /// The framework calls this method in a number of different situations. For example: + /// + /// * After calling [initState]. + /// * After calling [didUpdateWidget]. + /// * After receiving a call to [setState]. + /// * After a dependency of this [State] object changes. + @override Widget build(BuildContext context) { return RepaintBoundary( diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_line_base.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_line_base.dart index 2e059e7fd..6ea2d9012 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_line_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_line_base.dart @@ -9,9 +9,22 @@ import '../trackball/trackball_renderer.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; -/// Represents the spark line chart +/// This class renders a sparkline chart. The [SfSparkLineChart] is a very small +/// chart, typically drawn without axis ticks and labels. +/// It presents the general shape of data in a simple and highly condensed way. +/// +/// To render a sparkline chart, create the instance of [SfSparkLineChart]. +/// Set the value for `data` property which of type List. Now, it shows +/// the line to represent the provided data. +/// +/// It provides option to customize its appearance with the properties such as +/// [color], [width], [dashArray]. To highlight the provided data, use either +/// its [marker] property or its data label property. To highlight the data +/// point, which is tapped, use its [trackball] property. To highlight the +/// particular region along with the vertical value, use its [plotBand] property. +/// class SfSparkLineChart extends StatefulWidget { - /// Creates the spark line chart + /// Creates a sparkline chart for the provided set of data with its default view. /// /// ```dart /// @override @@ -25,8 +38,8 @@ class SfSparkLineChart extends StatefulWidget { /// } /// ``` SfSparkLineChart( - {Key key, - List data, + {Key? key, + List? data, this.plotBand, this.width = 2, this.dashArray, @@ -52,7 +65,22 @@ class SfSparkLineChart extends StatefulWidget { : _sparkChartDataDetails = SparkChartDataDetails(data: data), super(key: key); - /// Create the spark line chart with custom data source + /// Creates the sparkline chart for the provided set of data with its default view. + /// + /// The difference between the default constructor and this constructor is, + /// in the default constructor uses its data property to get the input data + /// value. The `data` property of the default constructor is of type List. + /// + /// The custom constructor uses its [dataCount], [xValueMapper] and + /// [yValueMapper] to get the input data. + /// + /// The [dataCount] property allows declaring the total data count going to be + /// displayed in the chart. + /// + /// The [xValueMapper[ returns the x value of the corresponding data point. + /// The [xValueMapper] allows providing num, DateTime, or string as x-value. + /// + /// The [yValueMapper] returns the y-value of the corresponding data point. /// /// ```dart /// class SalesData { @@ -89,16 +117,16 @@ class SfSparkLineChart extends StatefulWidget { /// ``` SfSparkLineChart.custom( - {Key key, + {Key? key, /// Data count for the spark charts. - int dataCount, + int? dataCount, /// Specifies the x-value mapping field - SparkChartIndexedValueMapper xValueMapper, + SparkChartIndexedValueMapper? xValueMapper, /// Specifies the y-value maping field - SparkChartIndexedValueMapper yValueMapper, + SparkChartIndexedValueMapper? yValueMapper, this.plotBand, this.width = 2, this.dashArray, @@ -127,7 +155,13 @@ class SfSparkLineChart extends StatefulWidget { yValueMapper: yValueMapper), super(key: key); - /// Specifies whether to inverse the spark line chart rendering. + /// Inverts the axis from right to left. + /// + /// In the spark chart, the provided set of data are rendered from left to + /// right by default and can be inverted to render the data points from right + /// to left. + /// + /// Defaults to `false`. /// /// ```dart /// @override @@ -143,7 +177,11 @@ class SfSparkLineChart extends StatefulWidget { /// ``` final bool isInversed; - /// Specifies the axis line's position. + /// Customize the axis position based on the provided y-value. + /// The axis line is rendered on the minimum y-value and + /// can be repositioned to required y-value. + /// + /// Defaults to `zero`. /// /// ```dart /// @override @@ -151,7 +189,7 @@ class SfSparkLineChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkLineChart( - /// axisCrossesAt: 14, + /// axisCrossesAt: 24, /// data: [18, 24, 30, 14, 28], /// )), /// ); @@ -159,7 +197,9 @@ class SfSparkLineChart extends StatefulWidget { /// ``` final double axisCrossesAt; - /// Specifies the width of the axis line. + /// Customizes the width of the axis line. + /// + /// Defaults to `2`. /// /// ```dart /// @override @@ -175,7 +215,10 @@ class SfSparkLineChart extends StatefulWidget { /// ``` final double axisLineWidth; - /// Specified the color of the axis line. + /// Customizes the color of the axis line. + /// Colors.transparent can be set to [axisLineColor] to hide the axis line. + /// + /// Defaults to `Colors.black`. /// /// ```dart /// @override @@ -191,7 +234,10 @@ class SfSparkLineChart extends StatefulWidget { /// ``` final Color axisLineColor; - /// Specifies the dash array value of the axis line. + /// Dashes of the axis line. Any number of values can be provided on the list. + /// Odd value is considered as rendering size and even value is considered a gap. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -205,9 +251,14 @@ class SfSparkLineChart extends StatefulWidget { /// ); /// } /// ``` - final List axisLineDashArray; + final List? axisLineDashArray; - /// Specifies the highest data point color. + /// Customizes the [marker] color of the highest data point. + /// + /// When the high data point is the first or last data point of the provided + /// data set, then either the first or last point color gets applied. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -216,14 +267,20 @@ class SfSparkLineChart extends StatefulWidget { /// body: Center( /// child: SfSparkLineChart( /// highPointColor: Colors.red, + /// marker: SparkChartMarker(displayMode: SparkChartMarkerDisplayMode.all), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color highPointColor; + final Color? highPointColor; - /// Specifies the lowest data point color. + /// Customizes the [marker] color of the lowest data point. + /// + /// When the lowest data point is the first or last data point of the provided + /// data set, then either the first or last point color gets applied. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -232,14 +289,21 @@ class SfSparkLineChart extends StatefulWidget { /// body: Center( /// child: SfSparkLineChart( /// lowPointColor: Colors.red, + /// marker: SparkChartMarker(displayMode: SparkChartMarkerDisplayMode.all), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color lowPointColor; + final Color? lowPointColor; - /// Specifies the negative point color. + /// Customizes the marker color of negative data point and data point value + /// less than the [axisCrossesAt] value. + /// + /// If the negative data point is either the high or low, first or last data + /// point, then priority will be given to those colors. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -248,14 +312,20 @@ class SfSparkLineChart extends StatefulWidget { /// body: Center( /// child: SfSparkLineChart( /// negativePointColor: Colors.red, - /// data: [18, 24, 30, 14, 28], + /// marker: SparkChartMarker(displayMode: SparkChartMarkerDisplayMode.all), + /// data: [18, 24, -30, 14, 28], /// )), /// ); /// } /// ``` - final Color negativePointColor; + final Color? negativePointColor; - /// Specifies the first point color. + /// Customizes the [marker] color of the first data point. + /// + /// If the first data point is either the high data point or low data point, + /// then the priority will be given to firstPointColor property + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -264,14 +334,20 @@ class SfSparkLineChart extends StatefulWidget { /// body: Center( /// child: SfSparkLineChart( /// firstPointColor: Colors.red, - /// data: [18, 24, 30, 14, 28], + /// marker: SparkChartMarker(displayMode: SparkChartMarkerDisplayMode.all), + /// data: [18, 24, -30, 14, 28], /// )), /// ); /// } /// ``` - final Color firstPointColor; + final Color? firstPointColor; - /// Specifies the last point color. + /// Customizes the [marker] color of the last data point. + /// + /// If the last data point is either the high data point or low data point, + /// then the priority will be given to [lastPointColor]. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -280,14 +356,17 @@ class SfSparkLineChart extends StatefulWidget { /// body: Center( /// child: SfSparkLineChart( /// lastPointColor: Colors.red, - /// data: [18, 24, 30, 14, 28], + /// marker: SparkChartMarker(displayMode: SparkChartMarkerDisplayMode.all), + /// data: [18, 24, -30, 14, 28], /// )), /// ); /// } /// ``` - final Color lastPointColor; + final Color? lastPointColor; - /// Specifies the color of the spark line chart. + /// Customizes the spark line chart color. + /// + /// Defaults to `blue`. /// /// ```dart /// @override @@ -303,7 +382,18 @@ class SfSparkLineChart extends StatefulWidget { /// ``` final Color color; - /// Represents the plot band settings for spark line chart. + /// Render plot band. + /// + /// Plot band is also known as strip line, which is used to shade the different + /// ranges in plot area with different colors to improve the readability + /// of the chart. + /// + /// Plot bands are drawn based on the axis. + /// + /// Provides the property of `start`, `end`, [color], `borderColor`, and + /// `borderWidth` to customize the appearance. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -317,9 +407,11 @@ class SfSparkLineChart extends StatefulWidget { /// ); /// } /// ``` - final SparkChartPlotBand plotBand; + final SparkChartPlotBand? plotBand; - /// Specifies the width of the series. + /// Customizes the line width of the sparkline chart. + /// + /// Defaults to `2`. /// /// ```dart /// @override @@ -335,7 +427,11 @@ class SfSparkLineChart extends StatefulWidget { /// ``` final double width; - /// Specifies the Dash array value of series. + /// Dashes of the line of a sparkline chart. Any number of values can be + /// provided on the list. Odd value is considered as rendering size and even + /// value is considered a gap. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -349,9 +445,18 @@ class SfSparkLineChart extends StatefulWidget { /// ); /// } /// ``` - final List dashArray; + final List? dashArray; - /// Represents the track ball options of spark line chart. + /// Enables and customizes the trackball. + /// + /// Trackball feature displays the tooltip for the data points that are closer + /// to the point where you touch on the chart area. This feature can be + /// enabled by creating an instance of [SparkChartTrackball]. + /// + /// Provides option to customizes the `activationMode`, [width], [color], + /// [labelStyle], `backgroundColor`, `borderColor`, `borderWidth`. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -359,60 +464,91 @@ class SfSparkLineChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkLineChart( - /// trackball:(borderWidth: 2, + /// trackball:SparkChartTrackball(borderWidth: 2, /// borderColor: Colors.black, activationMode: SparkChartActivationMode.doubleTap), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final SparkChartTrackball trackball; + final SparkChartTrackball? trackball; - /// Represents the marker settings of spark chart. + /// Enables and customizes the markers. + /// + /// Markers are used to provide information about the exact point location. + /// You can add a shape to adorn each data point. Markers can be enabled by + /// using the `displayMode` property of [SparkChartMarker]. + /// + /// Provides the options of [color], `borderWidth`, `borderColor` and `shape` + /// of the marker to customize the appearance. + /// + /// Defaults to `null`. /// /// ```dart /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( - /// child: SfSparkAreaChart( - /// boderWidth: 2, - /// marker: SparkChartMarker(borderWidth: 3, size: 20, - /// shape: MarkerShape.circle, displayMode: MarkerDisplayMode.all), + /// child: SfSparkLineChart( + /// marker: SparkChartMarker(displayMode: SparkChartMarkerDisplayMode.all), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` + final SparkChartMarker? marker; - final SparkChartMarker marker; - - /// Specifies the spark area data label + /// Enables the data labels. + /// + /// Data labels are used to provide information about the exact point location + /// and its value. + /// + /// * [SparkChartLabelDisplayMode.all] enables the data label for all the data + /// points. + /// * [SparkChartLabelDisplayMode.none] disables the data labels. + /// * [SparkChartLabelDisplayMode.high] displays the data label on highest + /// data point. + /// * [SparkChartLabelDisplayMode.low] displays the data label on lowest + /// data point. + /// * [SparkChartLabelDisplayMode.first] displays the data label on first + /// data point. + /// * [SparkChartLabelDisplayMode.last] displays the data label on last + /// data point. + /// + /// Also refer [SparkChartLabelDisplayMode]. + /// + /// Defaults to `SparkChartDislayMode.none`. /// /// ```dart /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( - /// child: SfSparkAreaChart( - /// boderWidth: 2, - /// labelDisplayMode: SparkChartLabelDisplayode.high, + /// child: SfSparkLineChart( + /// labelDisplayMode: SparkChartLabelDisplayMode.high, /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final SparkChartLabelDisplayMode labelDisplayMode; + final SparkChartLabelDisplayMode? labelDisplayMode; - /// Specifies the spark line data label + /// Customizes the data label text style. + /// + /// Using the [TextStyle], add style data labels. + /// + /// Defaults to the [TextStyle] property with font size `12.0` and font family + /// `Roboto`. + /// + /// Also refer [TextStyle]. /// /// ```dart /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( - /// child: SfSparkAreaChart( - /// boderWidth: 2, + /// child: SfSparkLineChart( + /// labelDisplayMode: SparkChartLabelDisplayMode.high, /// labelStyle: TextStyle(fontStyle: FontStyle.italic), /// data: [18, 24, 30, 14, 28], /// )), @@ -433,13 +569,22 @@ class SfSparkLineChart extends StatefulWidget { /// Represents the state class for spark line chart widget class _SfSparkLineChartState extends State { /// specifies the theme of the chart - ThemeData _themeData; + late ThemeData _themeData; /// Specifies the series screen coordinate points - List _coordinatePoints; + late List _coordinatePoints; /// Specifies the series data points - List _dataPoints; + late List _dataPoints; + + /// Called when this object is inserted into the tree. + /// + /// The framework will call this method exactly once for each State object it creates. + /// + /// Override this method to perform initialization that depends on the location at + /// which this object was inserted into the tree or on the widget used to configure this object. + /// + /// * In [initState], subscribe to the object. @override void initState() { @@ -448,22 +593,49 @@ class _SfSparkLineChartState extends State { super.initState(); } + /// Called when a dependency of this [State] object changes. + /// + /// For example, if the previous call to [build] referenced an [InheritedWidget] that later changed, + /// the framework would call this method to notify this object about the change. + /// + /// This method is also called immediately after [initState]. It is safe to call [BuildContext.dependOnInheritedWidgetOfExactType] from this method. + @override void didChangeDependencies() { _themeData = Theme.of(context); super.didChangeDependencies(); } + /// Called whenever the widget configuration changes. + /// + /// If the parent widget rebuilds and request that this location in the tree update to display a new widget with the same [runtimeType] and [Widget.key], + /// the framework will update the widget property of this [State] object to refer to the new widget and then call this method with the previous widget as an argument. + /// + /// Override this method to respond when the widget changes. + /// + /// The framework always calls [build] after calling [didUpdateWidget], which means any calls to [setState] in [didUpdateWidget] are redundant. + /// + /// * In [didUpdateWidget] unsubscribe from the old object and subscribe to the new one if the updated widget configuration requires replacing the object. + @override void didUpdateWidget(SfSparkLineChart oldWidget) { super.didUpdateWidget(oldWidget); } + /// Describes the part of the user interface represented by this widget. + /// + /// The framework calls this method in a number of different situations. For example: + /// + /// * After calling [initState]. + /// * After calling [didUpdateWidget]. + /// * After receiving a call to [setState]. + /// * After a dependency of this [State] object changes. + @override Widget build(BuildContext context) { if (widget.marker != null && - widget.marker.displayMode != SparkChartMarkerDisplayMode.none) { - final double padding = widget.marker.size / 2; + widget.marker!.displayMode != SparkChartMarkerDisplayMode.none) { + final double padding = widget.marker!.size / 2; return RepaintBoundary( child: Padding( padding: EdgeInsets.all(padding), child: _getSparkLineChart())); diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_win_loss_base.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_win_loss_base.dart index b9a5bcee8..af06662ca 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_win_loss_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_win_loss_base.dart @@ -8,9 +8,24 @@ import '../trackball/trackball_renderer.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; -/// Represents the spark win loss chart widget +/// This class renders a win loss spark chart. The [SfSparkWinLossChart] is a +/// very small chart, typically drawn without axis ticks and labels. It presents +/// the general shape of data in a simple and highly condensed way. It is used +/// to show whether each value is positive or negative visualizing a Win/Loss +/// scenario. +/// +/// To render a bar spark chart, create the instance of [SfSparkWinLossChart]. +/// Set the value for `data` property which of type List. Now, it shows the +/// rectangular column to represent the provided data. +/// +/// It provides option to customize its appearance with the properties such as +/// [color], [borderWidth], [borderColor]. To highlight the data point, which is +/// tapped, use its [trackball] property. To highlight the particular region +/// along with the vertical value, use its [plotBand] property. +/// class SfSparkWinLossChart extends StatefulWidget { - /// Creates the spark win loss chart + /// Creates a spark win loss chart for the provided set of data with its + /// default view. /// /// ```dart /// @override @@ -24,8 +39,8 @@ class SfSparkWinLossChart extends StatefulWidget { /// } /// ``` SfSparkWinLossChart( - {Key key, - List data, + {Key? key, + List? data, this.plotBand, this.borderWidth = 0, this.borderColor, @@ -45,7 +60,23 @@ class SfSparkWinLossChart extends StatefulWidget { : _sparkChartDataDetails = SparkChartDataDetails(data: data), super(key: key); - /// Create the spark win loss chart with custom data source + /// Creates the spark win loss chart for the provided set of data with its + /// default view. + /// + /// The difference between the default constructor and this constructor is, in + /// the default constructor uses its data property to get the input data value. + /// The `data` property of the default constructor is of type List. + /// + /// The custom constructor uses its [dataCount], [xValueMapper] and + /// [yValueMapper] to get the input data. + /// + /// The [dataCount] property allows declaring the total data count going to be + /// displayed in the chart. + /// + /// The [xValueMapper[ returns the x- value of the corresponding data point. + /// The [xValueMapper] allows providing num, DateTime, or string as x-value. + /// + /// The [yValueMapper] returns the y-value of the corresponding data point. /// /// ```dart /// class SalesData { @@ -81,16 +112,16 @@ class SfSparkWinLossChart extends StatefulWidget { /// } /// ``` SfSparkWinLossChart.custom( - {Key key, + {Key? key, /// Data count for the spark charts. - int dataCount, + int? dataCount, /// Specifies the x-value mapping field - SparkChartIndexedValueMapper xValueMapper, + SparkChartIndexedValueMapper? xValueMapper, /// Specifies the y-value maping field - SparkChartIndexedValueMapper yValueMapper, + SparkChartIndexedValueMapper? yValueMapper, this.plotBand, this.borderWidth = 2, this.borderColor, @@ -113,7 +144,13 @@ class SfSparkWinLossChart extends StatefulWidget { yValueMapper: yValueMapper), super(key: key); - /// Specifies whether to inverse the spark chart rendering. + /// Inverts the axis from right to left. + /// + /// In the spark chart, the provided set of data are rendered from left to + /// right by default and can be inverted to render the data points from + /// right to left. + /// + /// Defaults to `false`. /// /// ```dart /// @override @@ -129,7 +166,11 @@ class SfSparkWinLossChart extends StatefulWidget { /// ``` final bool isInversed; - /// Specifies the axis line's position. + /// Customize the axis position based on the provided y-value. + /// The axis line is rendered on the minimum y-value and can be repositioned + /// to required y-value . + /// + /// Defaults to `zero`. /// /// ```dart /// @override @@ -137,7 +178,7 @@ class SfSparkWinLossChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkWinLossChart( - /// axisCrossesAt: 14, + /// axisCrossesAt: 24, /// data: [18, 24, 30, 14, 28], /// )), /// ); @@ -145,7 +186,9 @@ class SfSparkWinLossChart extends StatefulWidget { /// ``` final double axisCrossesAt; - /// Specifies the width of the axis line. + /// Customizes the width of the axis line. + /// + /// Defaults to `2`. /// /// ```dart /// @override @@ -161,7 +204,10 @@ class SfSparkWinLossChart extends StatefulWidget { /// ``` final double axisLineWidth; - /// Specified the color of the axis line. + /// Customizes the color of the axis line. + /// Colors.transparent can be set to [axisLineColor] to hide the axis line. + /// + /// Defaults to `Colors.black. /// /// ```dart /// @override @@ -177,7 +223,10 @@ class SfSparkWinLossChart extends StatefulWidget { /// ``` final Color axisLineColor; - /// Specifies the dash array value of the axis line. + /// Dashes of the axis line. Any number of values can be provided on the list. + /// Odd value is considered as rendering size and even value is considered a gap. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -191,9 +240,14 @@ class SfSparkWinLossChart extends StatefulWidget { /// ); /// } /// ``` - final List axisLineDashArray; + final List? axisLineDashArray; - /// Specifies the highest data point color. + /// Customizes the color of the highest rectangular column segment. + /// + /// When the high data point is the first or last data point of the provided + /// data set, then either the first or last point color gets applied. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -207,9 +261,14 @@ class SfSparkWinLossChart extends StatefulWidget { /// ); /// } /// ``` - final Color highPointColor; + final Color? highPointColor; - /// Specifies the lowest data point color. + /// Customizes the color of the lowest rectangular column segment. + /// + /// When the lowest data point is the first or last data point of the provided + /// data set, then either the first or last point color gets applied. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -218,14 +277,20 @@ class SfSparkWinLossChart extends StatefulWidget { /// body: Center( /// child: SfSparkWinLossChart( /// lowPointColor: Colors.red, - /// data: [18, 24, 30, 14, 28], + /// data: [18, -24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color lowPointColor; + final Color? lowPointColor; - /// Specifies the negative point color. + /// Customizes the color of negative data point and data point value less than + /// the [axisCrossesAt] value. + /// + /// If the negative data point is either the high or low, first or last data + /// point, then priority will be given to those colors. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -239,9 +304,14 @@ class SfSparkWinLossChart extends StatefulWidget { /// ); /// } /// ``` - final Color negativePointColor; + final Color? negativePointColor; - /// Specifies the first point color. + /// Customizes the color of the first rectangular column segment. + /// + /// If the first data point is either the high data point or low data point, + /// then the priority will be given to firstPointColor property. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -255,9 +325,14 @@ class SfSparkWinLossChart extends StatefulWidget { /// ); /// } /// ``` - final Color firstPointColor; + final Color? firstPointColor; - /// Specifies the last point color. + /// Customizes the color of the last rectangular column segment. + /// + /// If the last data point is either the high data point or low data point, + /// then the priority will be given to lastPointColor propety. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -271,9 +346,11 @@ class SfSparkWinLossChart extends StatefulWidget { /// ); /// } /// ``` - final Color lastPointColor; + final Color? lastPointColor; - /// Specifies the color of the spark win loss chart. + /// Customizes the spark win loss chart color. + /// + /// Defaults to `blue`. /// /// ```dart /// @override @@ -289,7 +366,18 @@ class SfSparkWinLossChart extends StatefulWidget { /// ``` final Color color; - /// Represents the plot band settings for spark win loss chart. + /// Render plot band. + /// + /// Plot band is also known as stripline, which is used to shade the different + /// ranges in plot area with different colors to improve the readability of + /// the chart. + /// + /// Plot bands are drawn based on the axis. + /// + /// Provides the property of `start`, `end`, [color], [borderColor], and + /// [borderWidth] to customize the appearance. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -303,9 +391,13 @@ class SfSparkWinLossChart extends StatefulWidget { /// ); /// } /// ``` - final SparkChartPlotBand plotBand; + final SparkChartPlotBand? plotBand; - /// Specifies the Border width of the series. + /// Customizes the border width of each rectangular column segment. + /// To render the border, both the border width and border color property + /// needs to be set. + /// + /// Defaults to `0` /// /// ```dart /// @override @@ -313,7 +405,7 @@ class SfSparkWinLossChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkWinLossChart( - /// width: 4, + /// borderWidth: 4, borderColor: Colors.red, /// data: [18, 24, 30, 14, 28], /// )), /// ); @@ -321,7 +413,12 @@ class SfSparkWinLossChart extends StatefulWidget { /// ``` final double borderWidth; - /// Specifies the Border color of the series. + /// Customizes the border color of each rectangular column segment. + /// The border will be rendered on the top of the spark area chart. + /// To render the border, both the [borderWidth] and borderColor property + /// needs to be set. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -329,15 +426,22 @@ class SfSparkWinLossChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkWinLossChart( - /// dashArray: [2,2], + /// borderWidth: 4, borderColor: Colors.red, /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color borderColor; + final Color? borderColor; - /// Tie point color of Win loss series. + /// Customizes the color of tie data point rectangular segment and the data + /// point value is equal to the [axisCrossesAt] value, is considered as a + /// tie point. + /// + /// If the tie data point is either the high or low, first or last data point, + /// then priority will be given to tie point color. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -345,15 +449,24 @@ class SfSparkWinLossChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkWinLossChart( - /// dashArray: [2,2], + /// tiePointColor: Colors.blue, /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color tiePointColor; + final Color? tiePointColor; - /// Represents the track ball options of spark win loss chart + /// Enables and customizes the trackball. + /// + /// Trackball feature displays the tooltip for the data points that are closer + /// to the point where you touch on the chart area. This feature can be + /// enabled by creating an instance of [SparkChartTrackball]. + /// + /// Provides option to customizes the `activationMode`, `width`, [color], + /// `labelStyle`, `backgroundColor`, [borderColor], [borderWidth]. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -361,13 +474,13 @@ class SfSparkWinLossChart extends StatefulWidget { /// return Scaffold( /// body: Center( /// child: SfSparkWinLossChart( - /// trackball:(borderWidth: 2, + /// trackball: SparkChartTrackball(borderWidth: 2, /// borderColor: Colors.black, activationMode: SparkChartActivationMode.doubleTap), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } - final SparkChartTrackball trackball; + final SparkChartTrackball? trackball; /// Specifies the spark chart data details final SparkChartDataDetails _sparkChartDataDetails; @@ -381,10 +494,19 @@ class SfSparkWinLossChart extends StatefulWidget { /// Represents the state class for spark win loss chart widget class _SfSparkWinLossChartState extends State { /// Specifies the series screen coordinate points - List _coordinatePoints; + late List _coordinatePoints; /// Specifies the series data points - List _dataPoints; + late List _dataPoints; + + /// Called when this object is inserted into the tree. + /// + /// The framework will call this method exactly once for each State object it creates. + /// + /// Override this method to perform initialization that depends on the location at + /// which this object was inserted into the tree or on the widget used to configure this object. + /// + /// * In [initState], subscribe to the object. @override void initState() { @@ -393,11 +515,31 @@ class _SfSparkWinLossChartState extends State { super.initState(); } + /// Called whenever the widget configuration changes. + /// + /// If the parent widget rebuilds and request that this location in the tree update to display a new widget with the same [runtimeType] and [Widget.key], + /// the framework will update the widget property of this [State] object to refer to the new widget and then call this method with the previous widget as an argument. + /// + /// Override this method to respond when the widget changes. + /// + /// The framework always calls [build] after calling [didUpdateWidget], which means any calls to [setState] in [didUpdateWidget] are redundant. + /// + /// * In [didUpdateWidget] unsubscribe from the old object and subscribe to the new one if the updated widget configuration requires replacing the object. + @override void didUpdateWidget(SfSparkWinLossChart oldWidget) { super.didUpdateWidget(oldWidget); } + /// Describes the part of the user interface represented by this widget. + /// + /// The framework calls this method in a number of different situations. For example: + /// + /// * After calling [initState]. + /// * After calling [didUpdateWidget]. + /// * After receiving a call to [setState]. + /// * After a dependency of this [State] object changes. + @override Widget build(BuildContext context) { return RepaintBoundary( diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/spark_chart_trackball.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/spark_chart_trackball.dart index 9031306f2..c6b17824d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/spark_chart_trackball.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/spark_chart_trackball.dart @@ -4,9 +4,18 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import '../utils/enum.dart'; -/// Represents the track ball behavior of spark chart widget +/// Enables and customizes the trackball. +/// +/// Trackball feature displays the tooltip for the data points that are closer +/// to the point where you touch on the chart area. This feature can be enabled +/// by creating an instance of [SparkChartTrackball]. +/// +/// Provides option to customizes the [activationMode], [width], [color], +/// [labelStyle], [backgroundColor], [borderColor], [borderWidth]. +/// class SparkChartTrackball { - /// Creates the track ball behavior of spark chart widget + /// Creates an instance of spark chart trackball to enable the trackball + /// on the closest data point from the touch position. /// /// ```dart /// @override @@ -14,7 +23,7 @@ class SparkChartTrackball { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// trackball:(borderWidth: 2, + /// trackball: SparkChartTrackball(borderWidth: 2, /// borderColor: Colors.black, activationMode: SparkChartActivationMode.doubleTap), /// data: [18, 24, 30, 14, 28], /// )), @@ -39,7 +48,9 @@ class SparkChartTrackball { this.borderWidth = 0, this.borderRadius = const BorderRadius.all(Radius.circular(5))}); - /// Represents the width of track ball line. + /// Customizes the width of the trackball line. + /// + /// Defaults to `2`. /// /// ```dart /// @override @@ -47,7 +58,7 @@ class SparkChartTrackball { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// trackball:(width: 5, + /// trackball: SparkChartTrackball(width: 5, /// ), /// data: [18, 24, 30, 14, 28], /// )), @@ -56,7 +67,11 @@ class SparkChartTrackball { /// ``` final double width; - /// Represents the color of track ball line. + /// Customizes the color of the trackball line. + /// The color is set based on the current application theme, + /// if its value is set to null. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -64,16 +79,20 @@ class SparkChartTrackball { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// trackball:( + /// trackball: SparkChartTrackball( /// color: Colors.black,), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color color; + final Color? color; - /// Represents the dash array for track ball line. + /// Dashes of the trackball line. Any number of values can be provided on the + /// list. Odd value is considered as rendering size and even value is + /// considered a gap. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -81,16 +100,27 @@ class SparkChartTrackball { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// trackball:(dashArray: [2,2], + /// trackball: SparkChartTrackball(dashArray: [2,2], /// ), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final List dashArray; + final List? dashArray; - /// Represents the activation mode of track ball line. + /// Defines the gesture for activating the trackball to the closest data point. + /// + /// * [SparkChartActivationMode.tap] allows to display the trackball on tap + /// gesture. + /// * [SparkChartActivationMode.doubleTap] allows to display the trackball on + /// double tap gesture. + /// * [SparkChartActivationMode.longPress] allows to display the trackball on + /// long press gesture. + /// + /// Also refer [SparkChartActivationMode]. + /// + /// Defaults to ` SparkChartActivationMode.tap`. /// /// ```dart /// @override @@ -98,7 +128,7 @@ class SparkChartTrackball { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// trackball:(SparkChartActivationMode: ActivationMode.doubleTap), + /// trackball: SparkChartTrackball(activationMode: SparkChartActivationMode.doubleTap), /// data: [18, 24, 30, 14, 28], /// )), /// ); @@ -106,7 +136,14 @@ class SparkChartTrackball { /// ``` final SparkChartActivationMode activationMode; - /// Represents the label style of the track ball. + /// Customizes the data label text style. + /// + /// Using the [TextStyle], add style data labels. + /// + /// Defaults to the [TextStyle] property with font size `12.0` and + /// font family `Roboto`. + /// + /// Also refer [TextStyle]. /// /// ```dart /// @override @@ -114,7 +151,7 @@ class SparkChartTrackball { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// trackball:(labelStyle: TextStyle(fontSize: 15)), + /// trackball: SparkChartTrackball(labelStyle: TextStyle(fontSize: 15)), /// data: [18, 24, 30, 14, 28], /// )), /// ); @@ -122,7 +159,11 @@ class SparkChartTrackball { /// ``` final TextStyle labelStyle; - /// Represents the background color for track ball tooltip. + /// Customizes the background color of the trackball tooltip. + /// The color is set based on the current application theme, if its value is + /// set to null. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -130,16 +171,20 @@ class SparkChartTrackball { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// trackball:( + /// trackball:SparkChartTrackball( /// backgroundColor: Colors.black), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color backgroundColor; + final Color? backgroundColor; - /// Represents the background color for track ball tooltip. + /// Customizes the border color of the trackball tooltip. + /// To make border visible for plot band, need to set both the border + /// color and border width. + /// + /// Defaults to `null`. /// /// ```dart /// @override @@ -147,25 +192,28 @@ class SparkChartTrackball { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// trackball:(borderWidth: 2, + /// trackball: SparkChartTrackball(borderWidth: 2, /// borderColor: Colors.black,), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final Color borderColor; + final Color? borderColor; - /// Repreents the border width of trackball tooltip. + /// Customizes the border width of the plot band. To make border visible for + /// plot band, need to set both the border color and border width. + /// + /// Defaults to `0`. /// /// ```dart /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( - /// child: SfSparkAreaChart( - /// trackball:(boderWidth: 2, - /// borderColor: Colors.black, activationMode: SparkChartActivationMode.doubleTap), + /// child: SfSparkAreaChart( + /// trackball: + /// SparkChartTrackball(borderWidth: 2, borderColor: Colors.black), /// data: [18, 24, 30, 14, 28], /// )), /// ); @@ -173,7 +221,11 @@ class SparkChartTrackball { /// ``` final double borderWidth; - /// Repreents the border radius of trackball tooltip. + /// Customizes the border radius of trackball tooltip. + /// + /// Also refer [BorderRadius]. + /// + /// Defaults to `BorderRadius.all(Radius.circular(5))})`. /// /// ```dart /// @override @@ -181,7 +233,8 @@ class SparkChartTrackball { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// trackball:(BorderRadius.all(Radius.circular(3)), + /// trackball: SparkChartTrackball( + /// borderRadius: BorderRadius.all(Radius.circular(3))), /// data: [18, 24, 30, 14, 28], /// )), /// ); @@ -189,9 +242,12 @@ class SparkChartTrackball { /// ``` final BorderRadius borderRadius; - /// Shows or hides the trackball. + /// Shows or hides the trackball.. + /// + /// By default, the trackball will be hidden on touch. + /// To avoid this, set this property to true. /// - /// By default, the trackball will be hidden on touch. To avoid this, set this property to true. + /// Defaults to `false`. /// /// ```dart /// @override @@ -199,7 +255,7 @@ class SparkChartTrackball { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// trackball:(shouldAlwaysShow: true, + /// trackball: SparkChartTrackball(shouldAlwaysShow: true), /// data: [18, 24, 30, 14, 28], /// )), /// ); @@ -207,7 +263,12 @@ class SparkChartTrackball { /// ``` final bool shouldAlwaysShow; - /// The trackball disappears after this time interval. + /// Provides the time delay to disappear the trackball on touch. + /// The provided value will be considered as milliseconds. + /// When [`shouldAlwaysShow`] is set as false, the value provided to this + /// property will be considered as a delay. + /// + /// Defaults to `0`. /// /// ```dart /// @override @@ -215,8 +276,10 @@ class SparkChartTrackball { /// return Scaffold( /// body: Center( /// child: SfSparkAreaChart( - /// trackball:(shouldAlwaysShow: true, - /// hideDelay: 200, + /// trackball: SparkChartTrackball( + /// shouldAlwaysShow: true, + /// hideDelay: 200, + /// ), /// data: [18, 24, 30, 14, 28], /// )), /// ); @@ -224,7 +287,14 @@ class SparkChartTrackball { /// ``` final double hideDelay; - /// Callback for formatting tooltip text. + /// Callback that gets triggered when a trackball tooltip text is created. + /// + /// The [TooltipFormatterDetails] is passed as an argument and it provides + /// the closest data point x value, y value, and the tooltip text + /// + /// The string returned from this call back will be displayed as tooltip text. + /// + /// Defaults to `null`. /// /// String handleTooltipFormatter(TooltipFormatterDetails details) { /// return details.y.toStringAsFixed(0) + 'cm'; @@ -235,15 +305,15 @@ class SparkChartTrackball { /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( - /// child: SfSparkAreaChart( - /// trackball:( - /// tooltipFormatter: handleTooltipFormatter, + /// child: SfSparkAreaChart( + /// trackball: + /// SparkChartTrackball(tooltipFormatter: handleTooltipFormatter), /// data: [18, 24, 30, 14, 28], /// )), /// ); /// } /// ``` - final SparkChartTooltipCallback tooltipFormatter; + final SparkChartTooltipCallback? tooltipFormatter; @override bool operator ==(Object other) { @@ -272,17 +342,17 @@ class SparkChartTrackball { int get hashCode { final List values = [ width, - color, + color!, activationMode, labelStyle, - backgroundColor, - borderColor, + backgroundColor!, + borderColor!, borderWidth, - dashArray, + dashArray!, shouldAlwaysShow, hideDelay, borderRadius, - tooltipFormatter + tooltipFormatter! ]; return hashList(values); } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/trackball_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/trackball_renderer.dart index 4671d6c09..dd5b84dd4 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/trackball_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/trackball_renderer.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'dart:io' show Platform; import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -13,7 +15,7 @@ import 'spark_chart_trackball.dart'; class SparkChartTrackballRenderer extends StatefulWidget { /// Creates the trackball renderer SparkChartTrackballRenderer( - {Key key, + {Key? key, this.trackball, this.coordinatePoints, this.dataPoints, @@ -21,16 +23,16 @@ class SparkChartTrackballRenderer extends StatefulWidget { : super(key: key); /// Specifies the spark chart trackball - final SparkChartTrackball trackball; + final SparkChartTrackball? trackball; /// Specifies the coordinate points - final List coordinatePoints; + final List? coordinatePoints; /// Specifies the spark chart data points - final List dataPoints; + final List? dataPoints; /// Specifie the spark chart widget - final Widget sparkChart; + final Widget? sparkChart; @override State createState() { @@ -42,40 +44,43 @@ class SparkChartTrackballRenderer extends StatefulWidget { class _SparckChartTrackballRendererState extends State { /// Holds the trackball repaint notifier - ValueNotifier _trackballRepaintNotifier; + ValueNotifier? _trackballRepaintNotifier; /// Specifies whether the track ball is enabled bool _isTrackballEnabled = false; /// Specifies the current touch position - Offset _touchPosition; + Offset? _touchPosition; /// Specifies the spark area bounds - Rect _areaBounds; + Rect? _areaBounds; /// Specifies the local rect - Rect _localBounds; + Rect? _localBounds; /// Specifies the nearest point index - int _currentIndex; + int? _currentIndex; /// Specifies the global position - Offset _globalPosition; + Offset? _globalPosition; /// Specifies the theme of the chart - ThemeData _themeData; + ThemeData? _themeData; /// Specifies the current data point - SparkChartPoint _currentDataPoint; + SparkChartPoint? _currentDataPoint; /// Specifies the current coordinate point - Offset _currentCoordinatePoint; + Offset? _currentCoordinatePoint; /// Specifies the trackball timer - Timer _timer; + Timer? _timer; /// Specifies whether to render the trackball on top - bool _isTop; + bool? _isTop; + + bool _enableMouseHover = + kIsWeb || Platform.isLinux || Platform.isMacOS || Platform.isWindows; @override void initState() { @@ -97,7 +102,7 @@ class _SparckChartTrackballRendererState @override void dispose() { if (_timer != null) { - _timer.cancel(); + _timer!.cancel(); } super.dispose(); @@ -106,56 +111,59 @@ class _SparckChartTrackballRendererState @override Widget build(BuildContext context) { return MouseRegion( - onHover: (PointerHoverEvent event) => - _enableAndUpdateTrackball(context, event.position), + // Using the _enableMouseHover property, prevented mouse hover function in mobile platforms. The mouse hover event should not be triggered for mobile platforms and logged an issue regarding this to the Flutter team. + // Issue: https://github.com/flutter/flutter/issues/68690 + onHover: (PointerHoverEvent event) => _enableMouseHover + ? _enableAndUpdateTrackball(context, event.position) + : null, onExit: (event) => _hide(), child: Listener( onPointerUp: (event) => _hide(), child: GestureDetector( - onVerticalDragStart: (widget.trackball != null && widget.trackball.activationMode != SparkChartActivationMode.doubleTap) + onVerticalDragStart: (widget.trackball != null && widget.trackball!.activationMode != SparkChartActivationMode.doubleTap) ? (DragStartDetails details) => _updateDragValue(context, details.globalPosition) : null, - onVerticalDragUpdate: (widget.trackball != null && widget.trackball.activationMode != SparkChartActivationMode.doubleTap) + onVerticalDragUpdate: (widget.trackball != null && widget.trackball!.activationMode != SparkChartActivationMode.doubleTap) ? (DragUpdateDetails details) => _updateDragValue(context, details.globalPosition) : null, onHorizontalDragStart: - (widget.trackball != null && widget.trackball.activationMode != SparkChartActivationMode.doubleTap) + (widget.trackball != null && widget.trackball!.activationMode != SparkChartActivationMode.doubleTap) ? (DragStartDetails details) => _updateDragValue(context, details.globalPosition) : null, onHorizontalDragUpdate: - (widget.trackball != null && widget.trackball.activationMode != SparkChartActivationMode.doubleTap) + (widget.trackball != null && widget.trackball!.activationMode != SparkChartActivationMode.doubleTap) ? (DragUpdateDetails details) => _updateDragValue(context, details.globalPosition) : null, - onTapDown: (widget.trackball != null && widget.trackball.activationMode == SparkChartActivationMode.tap) + onTapDown: (widget.trackball != null && widget.trackball!.activationMode == SparkChartActivationMode.tap) ? (TapDownDetails details) => _enableAndUpdateTrackball(context, details.globalPosition) : null, - onLongPressStart: (widget.trackball != null && widget.trackball.activationMode == SparkChartActivationMode.longPress) + onLongPressStart: (widget.trackball != null && widget.trackball!.activationMode == SparkChartActivationMode.longPress) ? (LongPressStartDetails details) => _enableAndUpdateTrackball(context, details.globalPosition) : null, - onLongPressMoveUpdate: (widget.trackball != null && widget.trackball.activationMode == SparkChartActivationMode.longPress) ? (LongPressMoveUpdateDetails details) => _updateDragValue(context, details.globalPosition) : null, - onDoubleTapDown: (widget.trackball != null && widget.trackball.activationMode == SparkChartActivationMode.doubleTap) ? (TapDownDetails details) => _enableTrackballBehavior(context, details.globalPosition) : null, - onDoubleTap: (widget.trackball != null && widget.trackball.activationMode == SparkChartActivationMode.doubleTap) ? () => _updateDragValue(context, _globalPosition) : null, + onLongPressMoveUpdate: (widget.trackball != null && widget.trackball!.activationMode == SparkChartActivationMode.longPress) ? (LongPressMoveUpdateDetails details) => _updateDragValue(context, details.globalPosition) : null, + onDoubleTapDown: (widget.trackball != null && widget.trackball!.activationMode == SparkChartActivationMode.doubleTap) ? (TapDownDetails details) => _enableAndUpdateTrackball(context, details.globalPosition) : null, + onDoubleTap: (widget.trackball != null && widget.trackball!.activationMode == SparkChartActivationMode.doubleTap) ? () => _updateDragValue(context, _globalPosition!) : null, child: _addTrackballPainter()), )); } /// Method to hide the trackball void _hide() { - if (widget.trackball != null && !widget.trackball.shouldAlwaysShow) { + if (widget.trackball != null && !widget.trackball!.shouldAlwaysShow) { if (_timer != null) { - _timer.cancel(); + _timer!.cancel(); } - _trackballRepaintNotifier.value++; - _timer = - Timer(Duration(milliseconds: widget.trackball.hideDelay.toInt()), () { - _trackballRepaintNotifier.value++; + _trackballRepaintNotifier!.value++; + _timer = Timer( + Duration(milliseconds: widget.trackball!.hideDelay.toInt()), () { + _trackballRepaintNotifier!.value++; _endTrackballDragging(); }); } @@ -168,7 +176,7 @@ class _SparckChartTrackballRendererState return Container( child: RepaintBoundary( child: CustomPaint( - painter: TrackballPainter(_trackballRepaintNotifier, + painter: TrackballPainter(_trackballRepaintNotifier!, _isTrackballEnabled, widget.trackball, this), size: Size(constraints.maxWidth, constraints.maxHeight)), ), @@ -179,7 +187,7 @@ class _SparckChartTrackballRendererState /// Method to enable the trackball behavior void _enableTrackballBehavior(BuildContext context, Offset globalPosition) { - final RenderBox renderBox = context.findRenderObject(); + final RenderBox renderBox = context.findRenderObject() as RenderBox; final Size renderBoxSize = renderBox.size; final Offset renderBoxOffset = renderBox.localToGlobal(Offset.zero); _areaBounds = Rect.fromLTWH(renderBoxOffset.dx, renderBoxOffset.dy, @@ -188,7 +196,7 @@ class _SparckChartTrackballRendererState Rect.fromLTWH(0, 0, renderBoxSize.width, renderBoxSize.height); _globalPosition = globalPosition; _touchPosition = renderBox.globalToLocal(globalPosition); - if (_localBounds.contains(_touchPosition)) { + if (_localBounds!.contains(_touchPosition!)) { _isTrackballEnabled = true; } } @@ -210,16 +218,16 @@ class _SparckChartTrackballRendererState void _updateDragValue(BuildContext context, Offset globalPosition) { _currentIndex = null; _isTop = false; - num index; + int? index; if (_isTrackballEnabled) { - final RenderBox renderBox = context.findRenderObject(); + final RenderBox renderBox = context.findRenderObject() as RenderBox; _touchPosition = renderBox.globalToLocal(globalPosition); - final double currentXPoint = _touchPosition.dx; + final double currentXPoint = _touchPosition!.dx; double xPoint; - double temp; + double? temp; double diff; - for (int i = 0; i < widget.coordinatePoints.length; i++) { - xPoint = widget.coordinatePoints[i].dx; + for (int i = 0; i < widget.coordinatePoints!.length; i++) { + xPoint = widget.coordinatePoints![i].dx; diff = (currentXPoint - xPoint).abs(); if (temp == null || temp > diff) { temp = diff; @@ -233,9 +241,9 @@ class _SparckChartTrackballRendererState } else if (widget.sparkChart is SfSparkBarChart) { _isTop = true; } - _currentDataPoint = widget.dataPoints[index]; - _currentCoordinatePoint = widget.coordinatePoints[index]; - _trackballRepaintNotifier.value++; + _currentDataPoint = widget.dataPoints![index]; + _currentCoordinatePoint = widget.coordinatePoints![index]; + _trackballRepaintNotifier!.value++; } _currentIndex = index; @@ -260,29 +268,30 @@ class TrackballPainter extends CustomPainter { final bool _isRepaint; /// Specifies the trackball of spark chart - final SparkChartTrackball _trackball; + final SparkChartTrackball? _trackball; /// Specifies the trackball renderer state final _SparckChartTrackballRendererState _rendererState; @override void paint(Canvas canvas, Size size) { - final num index = _rendererState._currentIndex; - if (index != null) { - final Offset screenPoint = _rendererState._currentCoordinatePoint; - _drawTrackLine(canvas, _rendererState._areaBounds, screenPoint, size); - _renderTrackballTooltip(canvas, screenPoint, index); + final num? index = _rendererState._currentIndex; + if (index != null && _trackball != null) { + final Offset screenPoint = _rendererState._currentCoordinatePoint!; + _drawTrackLine(canvas, _rendererState._areaBounds!, screenPoint, size); + _renderTrackballTooltip(canvas, screenPoint, index, size); } } /// Method to render the trackball tooltip - void _renderTrackballTooltip(Canvas canvas, Offset screenPoint, num index) { - Offset labelOffset = screenPoint; + void _renderTrackballTooltip( + Canvas canvas, Offset? screenPoint, num index, Size size) { + Offset labelOffset = screenPoint!; final String dataLabel = _getTrackballLabel(); final TextStyle labelStyle = _getTrackballLabelStyle(); final Size textSize = getTextSize(dataLabel, labelStyle); - final Rect areaBounds = _rendererState._areaBounds; - BorderRadius borderRadius = _trackball.borderRadius; + final Rect areaBounds = _rendererState._areaBounds!; + BorderRadius borderRadius = _trackball!.borderRadius; double rectWidth = textSize.width; if (rectWidth < 10) { rectWidth = 10; @@ -298,7 +307,7 @@ class TrackballPainter extends CustomPainter { final double pointerLength = 7; final double totalWidth = areaBounds.right - areaBounds.left; final double totalHeight = areaBounds.bottom - areaBounds.top; - final bool isTop = _rendererState._isTop; + final bool isTop = _rendererState._isTop!; bool isRight = false; double xPosition; double yPosition; @@ -335,6 +344,13 @@ class TrackballPainter extends CustomPainter { yPosition = (screenPoint.dy > 0 ? screenPoint.dy : 0) + pointerLength + padding; + if ((yPosition + labelRect.height) > size.height) { + final double y = size.height - (yPosition + labelRect.height); + screenPoint = Offset(screenPoint.dx, y); + yPosition = (screenPoint.dy > 0 ? screenPoint.dy : 0) + + pointerLength + + padding; + } } xPosition = xPosition < 0 @@ -360,15 +376,15 @@ class TrackballPainter extends CustomPainter { /// Method returns the trackball label String _getTrackballLabel() { - final SparkChartPoint currentPoint = _rendererState._currentDataPoint; - String dataLabel = currentPoint.labelY; - final String labelX = currentPoint.labelX; + final SparkChartPoint currentPoint = _rendererState._currentDataPoint!; + String dataLabel = currentPoint.labelY!; + final String? labelX = currentPoint.labelX; dataLabel = labelX != null ? labelX + ' : ' + dataLabel : dataLabel; - if (_trackball.tooltipFormatter != null) { + if (_trackball!.tooltipFormatter != null) { final TooltipFormatterDetails tooltipFormatterDetails = TooltipFormatterDetails( x: currentPoint.actualX, y: currentPoint.y, label: dataLabel); - dataLabel = _trackball.tooltipFormatter(tooltipFormatterDetails); + dataLabel = _trackball!.tooltipFormatter!(tooltipFormatterDetails); } return dataLabel; @@ -376,9 +392,9 @@ class TrackballPainter extends CustomPainter { /// Method to return the trackball label style TextStyle _getTrackballLabelStyle() { - return _trackball.labelStyle.copyWith( - color: _trackball.labelStyle.color ?? - (_rendererState._themeData.brightness == Brightness.light + return _trackball!.labelStyle.copyWith( + color: _trackball!.labelStyle.color ?? + (_rendererState._themeData!.brightness == Brightness.light ? const Color.fromRGBO(229, 229, 229, 1) : const Color.fromRGBO(0, 0, 0, 1))); } @@ -413,11 +429,11 @@ class TrackballPainter extends CustomPainter { bool isTop, bool isBottom) { final Color backgroundColor = - (_rendererState._themeData.brightness == Brightness.light + (_rendererState._themeData!.brightness == Brightness.light ? const Color.fromRGBO(79, 79, 79, 1) : const Color.fromRGBO(255, 255, 255, 1)); final Paint paint = Paint() - ..color = _trackball.backgroundColor ?? backgroundColor; + ..color = _trackball!.backgroundColor ?? backgroundColor; final Path path = Path(); if (!isTop) { if (isRight) { @@ -452,12 +468,12 @@ class TrackballPainter extends CustomPainter { path.addRRect(roundedRect); canvas.drawPath(path, paint); - if (_trackball.borderColor != null && - _trackball.borderColor != Colors.transparent && - _trackball.borderWidth > 0) { + if (_trackball!.borderColor != null && + _trackball!.borderColor != Colors.transparent && + _trackball!.borderWidth > 0) { final Paint borderPaint = Paint() - ..color = _trackball.borderColor - ..strokeWidth = _trackball.borderWidth + ..color = _trackball!.borderColor! + ..strokeWidth = _trackball!.borderWidth ..style = PaintingStyle.stroke; canvas.drawPath(path, borderPaint); } @@ -467,16 +483,16 @@ class TrackballPainter extends CustomPainter { void _drawTrackLine( Canvas canvas, Rect areaBounds, Offset screenPoint, Size size) { final Paint paint = Paint() - ..color = _trackball.color ?? - (_rendererState._themeData.brightness == Brightness.light + ..color = _trackball!.color ?? + (_rendererState._themeData!.brightness == Brightness.light ? const Color.fromRGBO(79, 79, 79, 1) : const Color.fromRGBO(255, 255, 255, 1)) - ..strokeWidth = _trackball.width + ..strokeWidth = _trackball!.width ..style = PaintingStyle.stroke; final Offset point1 = Offset(screenPoint.dx, 0); final Offset point2 = Offset(screenPoint.dx, size.height); - if (_trackball.dashArray != null && _trackball.dashArray.isNotEmpty) { - drawDashedPath(canvas, paint, point1, point2, _trackball.dashArray); + if (_trackball!.dashArray != null && _trackball!.dashArray!.isNotEmpty) { + drawDashedPath(canvas, paint, point1, point2, _trackball!.dashArray); } else { canvas.drawLine(point1, point2, paint); } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/enum.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/enum.dart index d2e771bdf..fee04141f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/enum.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/enum.dart @@ -1,93 +1,116 @@ -/// Maps the index value. +/// Signature for the callback that returns the dynamic value from the +/// data source based on the index. +/// typedef SparkChartIndexedValueMapper = R Function(int index); -/// Maps the tooltip label value +/// Signature for the callback that returns the string value to override the +/// default text format of trackball tooltip label. +/// typedef SparkChartTooltipCallback = String Function( TooltipFormatterDetails details); -/// Represents the marker display mode +/// Displays the marker on the spark chart widget in different modes. enum SparkChartMarkerDisplayMode { - /// SparkChartMarkerDisplayMode.none does not allow to display markers on any side + /// SparkChartMarkerDisplayMode.none does not allow to display a marker on any + /// data points in the spark chart widget. none, - /// SparkChartMarkerDisplayMode.all allows to display marker on all side + /// SparkChartMarkerDisplayMode.all allows to display a marker on all the data points + /// in the spark chart widget. all, - /// SparkChartMarkerDisplayMode.high allows to display marker on high point + /// SparkChartMarkerDisplayMode.high allows displaying marker only on the highest + /// data points in the spark chart widget. high, - /// SparkChartMarkerDisplayMode.low allows to display marker on low point + /// SparkChartMarkerDisplayMode.low allows displaying marker only on the lowest data + /// points in the spark chart widget. low, - /// SparkChartMarkerDisplayMode.first allows to display marker on first point + /// SparkChartMarkerDisplayMode.first allows displaying marker only on the first data + /// points in the spark chart widget. first, - /// SparkChartMarkerDisplayMode.last allows to display marker on last point + /// SparkChartMarkerDisplayMode.last allows displaying marker only on the last data + /// points in the spark chart widget. last } -/// Represents the marker shape +/// Displays the marker in different types of shape. enum SparkChartMarkerShape { - /// SparkChartMarkerShape.circle displays marker in circular shape + /// SparkChartMarkerShape.circle displays the marker in a circular shape. circle, - /// SparkChartMarkerShape.diamond displays marker in diamond shape + /// SparkChartMarkerShape.diamond displays the marker in a diamond shape. diamond, - /// SparkChartMarkerShape.square displays marker in square shape + /// SparkChartMarkerShape.square displays the marker in a square shape. square, - /// SparkChartMarkerShape.triangle displays marker in triangle shape + /// SparkChartMarkerShape.triangle displays the marker in a triangle shape. triangle, - /// SparkChartMarkerShape.invertedTriangle displays marker in inverted triangle shape + /// SparkChartMarkerShape.invertedTriangle displays the marker in an inverted + /// triangle shape invertedTriangle } -/// Represents the trackball spark chart activation mode +/// Activates the trackball in different gestures. enum SparkChartActivationMode { - ///SparkChartActivationMode.tap allows to display the trackball on tap + /// SparkChartActivationMode.tap activates the trackball on tap gesture. tap, - /// SparkChartActivationMode.doubleTap allows to display the trackball on double tap + /// SparkChartActivationMode.doubleTap activates the trackball on double tap + /// gesture. doubleTap, - /// SparkChartActivationMode.longPress allows to display the trckball on long press + /// SparkChartActivationMode.longPress activates the trackball on long press + /// gesture. longPress, } -/// Represents the label display mode +/// Displays the data labels on the spark chart widget in different modes. enum SparkChartLabelDisplayMode { - /// SparkChartLabelDisplayMode.none does not allow to display data labels on any side + /// SparkChartLabelDisplayMode.none does not allow to display the data label on any + /// data points in the spark chart widget. none, - ///SparkChartLabelDisplayMode.all allows to display data labels on all points + /// SparkChartLabelDisplayMode.none allows to display data label on all the + /// data points in the spark chart widget. all, - /// SparkChartLabelDisplayMode.high allows to display data on high point + /// SparkChartLabelDisplayMode.high allows displaying data label only on the + /// highest data points in the spark chart widget. high, - /// SparkChartLabelDisplayMode.low allows to display marker on low point + /// SparkChartLabelDisplayMode.low allows displaying data label only on the + /// lowest data points in the spark chart widget. low, - /// SparkChartLabelDisplayMode.first allows to display marker on first point + /// SparkChartLabelDisplayMode.first allows displaying data label only on the + /// first data points in the spark chart widget. first, - /// SparkChartLabelDisplayMode.last allows to display marker on last point + /// SparkChartLabelDisplayMode.first allows displaying data label only on the + /// last data points in the spark chart widget. last } -/// Specifies the tooltip formatter details +/// Passes as the argument in the `tooltipFormatter` callback of trackball. +/// The callback gets triggered when the trackball tooltip label text is created. class TooltipFormatterDetails { - /// Creates the tooltip formatter details + /// Creates an instance of TooltipFormatterDetails, where the closest point’s + /// x value and y value and tooltip text is passed as its argument. + /// TooltipFormatterDetails({this.x, this.y, this.label}); - /// Specifies the x-value of data point + /// Provides the x value of the closest data point. final dynamic x; - /// Specifies the y-value of data point - final num y; + /// Provides the y value of the closest data point. + final num? y; - /// Specifies the tooltip text - final String label; + /// Provides the tooltip label value. By default, the label displays the + /// x and the y value in the format of x : y. + final String? label; } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart index 7ad512400..83ee20ebe 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart @@ -2,6 +2,7 @@ import 'dart:math' as math; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; import 'package:flutter/foundation.dart'; import '../marker.dart'; import 'enum.dart'; @@ -37,13 +38,13 @@ Size getTextSize(String textValue, TextStyle textStyle) { /// Draw the data label void drawText(Canvas canvas, String dataLabel, Offset point, TextStyle style) { - final num maxLines = _getMaxLinesContent(dataLabel); + final num maxLines = getMaxLinesContent(dataLabel); final TextSpan span = TextSpan(text: dataLabel, style: style); final TextPainter tp = TextPainter( text: span, textDirection: TextDirection.ltr, textAlign: TextAlign.center, - maxLines: maxLines); + maxLines: maxLines.toInt()); tp.layout(); canvas.save(); canvas.translate(point.dx, point.dy); @@ -52,17 +53,10 @@ void drawText(Canvas canvas, String dataLabel, Offset point, TextStyle style) { canvas.restore(); } -/// Returns lines count in a string -num _getMaxLinesContent(String text) { - return text != null && text.isNotEmpty && text.contains('\n') - ? text.split('\n').length - : 1; -} - /// Draw the dashed line void drawDashedPath( Canvas canvas, Paint paint, Offset moveToPoint, Offset lineToPoint, - [List dashArray]) { + [List? dashArray]) { bool even = false; final Path path = Path(); path.moveTo(moveToPoint.dx, moveToPoint.dy); @@ -79,7 +73,7 @@ void drawDashedPath( _dashPath( path, dashArray: DashArrayIntervalList(dashArray), - ), + )!, paint); } } else { @@ -88,9 +82,9 @@ void drawDashedPath( } /// To calculate dash array path for series -Path _dashPath( - Path source, { - @required DashArrayIntervalList dashArray, +Path? _dashPath( + Path? source, { + @required DashArrayIntervalList? dashArray, }) { if (source == null) { return null; @@ -101,7 +95,7 @@ Path _dashPath( double distance = intialValue; bool draw = true; while (distance < measurePath.length) { - final double length = dashArray.next; + final double length = dashArray!.next; if (draw) { path.addPath( measurePath.extractPath(distance, distance + length), Offset.zero); @@ -216,11 +210,13 @@ Offset transformToCoordinatePoint( } /// Calculates and return the spark chart layout size -Size getLayoutSize(BoxConstraints constraints) { +Size getLayoutSize(BoxConstraints constraints, BuildContext context) { final double minHeight = 270; final double minWidth = 480; double height = constraints.maxHeight; double width = constraints.maxWidth; + final double deviceWidth = MediaQuery.of(context).size.width; + final double deviceHeight = MediaQuery.of(context).size.height; if (height == double.infinity) { if (width != double.infinity) { height = (width / 16) * 9; @@ -237,6 +233,13 @@ Size getLayoutSize(BoxConstraints constraints) { } } + if (width > deviceWidth) { + width = deviceWidth; + } + + if (height > deviceHeight) { + height = deviceHeight; + } return Size(width, height); } @@ -266,26 +269,26 @@ class SparkChartPoint { SparkChartPoint({this.x, this.y}); /// Specifies the x point - dynamic x; + dynamic? x; /// Specifies the y point - dynamic y; + dynamic? y; /// Specifes the pixel location of data label - Offset dataLabelOffset; + Offset? dataLabelOffset; /// Specifies the x label - String labelX; + String? labelX; /// Specifies the y label - String labelY; + String? labelY; /// Specifies the actual x value - dynamic actualX; + dynamic? actualX; /// Specifies the color of that particular segment in bar series - Color color; + Color? color; } /// Represents the spark chart data details @@ -295,38 +298,40 @@ class SparkChartDataDetails { {this.data, this.dataCount, this.xValueMapper, this.yValueMapper}); /// Speficies the list of spark chart data - final List data; + final List? data; /// Specifies the spark chart data count - final int dataCount; + final int? dataCount; /// Specifies the x-value mapper - final SparkChartIndexedValueMapper xValueMapper; + final SparkChartIndexedValueMapper? xValueMapper; /// Specifies the y-value mapper - final SparkChartIndexedValueMapper yValueMapper; + final SparkChartIndexedValueMapper? yValueMapper; } /// Represents the spark chart container class SparkChartContainer extends SingleChildRenderObjectWidget { /// Creates the spark chart container - const SparkChartContainer({Widget child}) : super(child: child); + const SparkChartContainer({Widget? child}) : super(child: child); @override RenderObject createRenderObject(BuildContext context) { - return _SparKChartContainerBox(); + return _SparKChartContainerBox(context); } } /// Represents the spark chart container box class _SparKChartContainerBox extends RenderShiftedBox { - _SparKChartContainerBox() : super(null); + _SparKChartContainerBox(this.context) : super(null); + + final BuildContext context; @override void performLayout() { - size = getLayoutSize(constraints); + size = getLayoutSize(constraints, context); - child.layout( + child!.layout( BoxConstraints( minHeight: 0.0, maxHeight: size.height, @@ -396,12 +401,13 @@ void renderMarker( String type, num highPoint, num lowPoint, + double axisCrossesAt, ThemeData themeData, - Color lowPointColor, - Color highPointColor, - Color negativePointColor, - Color firstPointColor, - Color lastPointColor) { + Color? lowPointColor, + Color? highPointColor, + Color? negativePointColor, + Color? firstPointColor, + Color? lastPointColor) { final Paint fillPaint = Paint()..style = PaintingStyle.fill; final Paint strokePaint = Paint() ..color = marker.borderColor ?? color @@ -442,30 +448,23 @@ void renderMarker( firstPointColor != null ? fillPaint.color = firstPointColor : fillPaint.color = fillPaint.color; - } - - if (i == + } else if (i == (type == 'Line' ? coordinatePoints.length - 1 : coordinatePoints.length - 2)) { lastPointColor != null ? fillPaint.color = lastPointColor : fillPaint.color = fillPaint.color; - } - - if (highPoint == coordinatePoints[i].dy) { + } else if (highPoint == coordinatePoints[i].dy) { lowPointColor != null ? fillPaint.color = lowPointColor : fillPaint.color = fillPaint.color; - } - - if (lowPoint == coordinatePoints[i].dy) { + } else if (lowPoint == coordinatePoints[i].dy) { highPointColor != null ? fillPaint.color = highPointColor : fillPaint.color = fillPaint.color; - } - - if (negativePointColor != null && dataPoints[i].y < 0) { + } else if (negativePointColor != null && + dataPoints[i].y < axisCrossesAt) { fillPaint.color = negativePointColor; } @@ -589,8 +588,8 @@ Color _getDataLabelSaturationColor( Offset offset, Color seriesColor, String type, - [Rect segment, - num yValue]) { + [Rect? segment, + num? yValue]) { Color color; if (type == 'Area') { @@ -604,13 +603,13 @@ Color _getDataLabelSaturationColor( ? const Color.fromRGBO(255, 255, 255, 1) : Colors.black; } else { - yValue > 0 - ? dataLabelOffset.dy > (segment.top + offset.dy) + yValue! > 0 + ? dataLabelOffset.dy > (segment!.top + offset.dy) ? color = seriesColor : color = theme.brightness == Brightness.light ? const Color.fromRGBO(255, 255, 255, 1) : Colors.black - : dataLabelOffset.dy < (segment.top + offset.dy) + : dataLabelOffset.dy < (segment!.top + offset.dy) ? color = seriesColor : color = theme.brightness == Brightness.light ? const Color.fromRGBO(255, 255, 255, 1) @@ -630,8 +629,8 @@ TextStyle _getTextStyle( ThemeData theme, Color seriesColor, String type, - [Rect segment, - num yValue]) { + [Rect? segment, + num? yValue]) { final TextStyle font = labelStyle; final Color fontColor = font.color ?? _getDataLabelSaturationColor(dataLabelOffset, coordinateOffset, theme, @@ -678,7 +677,7 @@ void renderDataLabel( Color seriesColor, num highPoint, num lowPoint, - [List segments]) { + [List? segments]) { TextStyle _textStyle; switch (labelDisplayMode) { @@ -689,16 +688,16 @@ void renderDataLabel( i++) { _textStyle = _getTextStyle( labelStyle, - dataPoints[i].dataLabelOffset, + dataPoints[i].dataLabelOffset!, coordinatePoints[i], offset, theme, - type == 'Bar' ? dataPoints[i].color : seriesColor, + type == 'Bar' ? dataPoints[i].color! : seriesColor, type, - type == 'Bar' ? segments[i] : null, + type == 'Bar' ? segments![i] : null, dataPoints[i].y); - drawText( - canvas, dataLabels[i], dataPoints[i].dataLabelOffset, _textStyle); + drawText(canvas, dataLabels[i], dataPoints[i].dataLabelOffset!, + _textStyle); } } @@ -708,16 +707,16 @@ void renderDataLabel( { _textStyle = _getTextStyle( labelStyle, - dataPoints[type == 'Area' ? 1 : 0].dataLabelOffset, + dataPoints[type == 'Area' ? 1 : 0].dataLabelOffset!, coordinatePoints[type == 'Area' ? 1 : 0], offset, theme, - type == 'Bar' ? dataPoints[0].color : seriesColor, + type == 'Bar' ? dataPoints[0].color! : seriesColor, type, - type == 'Bar' ? segments[0] : null, + type == 'Bar' ? segments![0] : null, dataPoints[0].y); drawText(canvas, dataLabels[type == 'Area' ? 1 : 0], - dataPoints[type == 'Area' ? 1 : 0].dataLabelOffset, _textStyle); + dataPoints[type == 'Area' ? 1 : 0].dataLabelOffset!, _textStyle); } break; @@ -728,16 +727,16 @@ void renderDataLabel( dataPoints[type == 'Area' ? dataPoints.length - 2 : dataPoints.length - 1] - .dataLabelOffset, + .dataLabelOffset!, coordinatePoints[ type == 'Area' ? dataPoints.length - 2 : dataPoints.length - 1], offset, theme, type == 'Bar' - ? dataPoints[dataPoints.length - 1].color + ? dataPoints[dataPoints.length - 1].color! : seriesColor, type, - type == 'Bar' ? segments[dataPoints.length - 1] : null, + type == 'Bar' ? segments![dataPoints.length - 1] : null, dataPoints[dataPoints.length - 1].y); drawText( @@ -747,7 +746,7 @@ void renderDataLabel( dataPoints[type == 'Area' ? dataPoints.length - 2 : dataPoints.length - 1] - .dataLabelOffset, + .dataLabelOffset!, _textStyle); } @@ -763,15 +762,15 @@ void renderDataLabel( if (highPoint == coordinatePoints[j].dy) { _textStyle = _getTextStyle( labelStyle, - dataPoints[j].dataLabelOffset, + dataPoints[j].dataLabelOffset!, coordinatePoints[j], offset, theme, - type == 'Bar' ? dataPoints[j].color : seriesColor, + type == 'Bar' ? dataPoints[j].color! : seriesColor, type, - type == 'Bar' ? segments[j] : null, + type == 'Bar' ? segments![j] : null, dataPoints[j].y); - drawText(canvas, dataLabels[j], dataPoints[j].dataLabelOffset, + drawText(canvas, dataLabels[j], dataPoints[j].dataLabelOffset!, _textStyle); } } @@ -790,15 +789,15 @@ void renderDataLabel( if (lowPoint == coordinatePoints[j].dy) { _textStyle = _getTextStyle( labelStyle, - dataPoints[j].dataLabelOffset, + dataPoints[j].dataLabelOffset!, coordinatePoints[j], offset, theme, - type == 'Bar' ? dataPoints[j].color : seriesColor, + type == 'Bar' ? dataPoints[j].color! : seriesColor, type, - type == 'Bar' ? segments[j] : null, + type == 'Bar' ? segments![j] : null, dataPoints[j].y); - drawText(canvas, dataLabels[j], dataPoints[j].dataLabelOffset, + drawText(canvas, dataLabels[j], dataPoints[j].dataLabelOffset!, _textStyle); } } diff --git a/packages/syncfusion_flutter_charts/pubspec.yaml b/packages/syncfusion_flutter_charts/pubspec.yaml index fbcbe0a52..0b9938b76 100644 --- a/packages/syncfusion_flutter_charts/pubspec.yaml +++ b/packages/syncfusion_flutter_charts/pubspec.yaml @@ -1,16 +1,16 @@ name: syncfusion_flutter_charts -description: Syncfusion Flutter Charts is a data visualization library written natively in Dart for creating beautiful and high-performance cartesian and circular charts. -version: 18.3.40 +description: Syncfusion Flutter Charts library includes data visualization widgets such as cartesian and circular charts, to create real-time, interactive, high-performance, animated charts. +version: 19.1.54 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_charts environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter - intl: ">=0.15.0 <0.17.0" - vector_math: ^2.0.8 + intl: ^0.17.0 + vector_math: ^2.1.0 syncfusion_flutter_core: path: ../syncfusion_flutter_core diff --git a/packages/syncfusion_flutter_core/README.md b/packages/syncfusion_flutter_core/README.md index 5b351ed3b..1928004ba 100644 --- a/packages/syncfusion_flutter_core/README.md +++ b/packages/syncfusion_flutter_core/README.md @@ -18,9 +18,7 @@ Syncfusion Flutter Core is a dependent package for the following Syncfusion Flut * [syncfusion_flutter_officecore](https://pub.dev/packages/syncfusion_flutter_officecore) * [syncfusion_localizations](https://pub.dev/packages/syncfusion_localizations) -**Disclaimer:** This is a commercial package. To use this package, you need to have either Syncfusion Commercial License or Syncfusion Community license. For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. - -**Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. +**Disclaimer:** This is a commercial package. To use this package, you need to have either Syncfusion Commercial License or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. ## Table of contents - [Get the demo application](#get-the-demo-application) diff --git a/packages/syncfusion_flutter_core/example/lib/main.dart b/packages/syncfusion_flutter_core/example/lib/main.dart index 25f346977..c9626ad0d 100644 --- a/packages/syncfusion_flutter_core/example/lib/main.dart +++ b/packages/syncfusion_flutter_core/example/lib/main.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; +// import 'package:syncfusion_flutter_charts/charts.dart'; void main() { return runApp(ChartApp()); @@ -18,7 +18,7 @@ class ChartApp extends StatelessWidget { class _MyHomePage extends StatefulWidget { //ignore: prefer_const_constructors_in_immutables - _MyHomePage({Key key}) : super(key: key); + _MyHomePage({Key? key}) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); @@ -29,6 +29,7 @@ class _MyHomePageState extends State<_MyHomePage> { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Syncfusion Flutter Chart')), - body: SfCartesianChart()); + body: Container()); + // body: SfCartesianChart()) // Commented until the chart moves to null safety; } } diff --git a/packages/syncfusion_flutter_core/example/pubspec.yaml b/packages/syncfusion_flutter_core/example/pubspec.yaml index 233d20ef6..120187e69 100644 --- a/packages/syncfusion_flutter_core/example/pubspec.yaml +++ b/packages/syncfusion_flutter_core/example/pubspec.yaml @@ -3,7 +3,7 @@ description: This project holds information about registering a license key with version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: diff --git a/packages/syncfusion_flutter_core/lib/analysis_options.yaml b/packages/syncfusion_flutter_core/lib/analysis_options.yaml index 73ccdf160..9467b8e41 100644 --- a/packages/syncfusion_flutter_core/lib/analysis_options.yaml +++ b/packages/syncfusion_flutter_core/lib/analysis_options.yaml @@ -32,6 +32,7 @@ analyzer: missing_required_param: warning omit_local_variable_types: ignore include_file_not_found: ignore + invalid_dependency: ignore linter: rules: diff --git a/packages/syncfusion_flutter_core/lib/core.dart b/packages/syncfusion_flutter_core/lib/core.dart index 9f820a754..c0e8cb9d5 100644 --- a/packages/syncfusion_flutter_core/lib/core.dart +++ b/packages/syncfusion_flutter_core/lib/core.dart @@ -1,9 +1,12 @@ library core; +import 'dart:math' as math; import 'package:flutter/material.dart'; +import 'package:vector_math/vector_math.dart' as vector; export './src/slider_controller.dart'; part './src/license.dart'; part './src/calendar/calendar_helper.dart'; part './src/calendar/hijri_date_time.dart'; +part './src/utils/helper.dart'; diff --git a/packages/syncfusion_flutter_core/lib/src/calendar/calendar_helper.dart b/packages/syncfusion_flutter_core/lib/src/calendar/calendar_helper.dart index 75c85eaa0..f70f4ce5e 100644 --- a/packages/syncfusion_flutter_core/lib/src/calendar/calendar_helper.dart +++ b/packages/syncfusion_flutter_core/lib/src/calendar/calendar_helper.dart @@ -136,8 +136,8 @@ bool isSameOrAfterDate(dynamic firstDate, dynamic date) { } /// Get the visible dates based on the date value and visible dates count. -List getVisibleDates(dynamic date, List nonWorkingDays, int firstDayOfWeek, - int visibleDatesCount) { +List getVisibleDates(dynamic date, List? nonWorkingDays, + int firstDayOfWeek, int visibleDatesCount) { List datesCollection; if (date is HijriDateTime) { datesCollection = []; @@ -145,14 +145,11 @@ List getVisibleDates(dynamic date, List nonWorkingDays, int firstDayOfWeek, datesCollection = []; } - dynamic currentDate = date; - if (firstDayOfWeek != null) { - currentDate = - getFirstDayOfWeekDate(visibleDatesCount, date, firstDayOfWeek); - } + final dynamic currentDate = + getFirstDayOfWeekDate(visibleDatesCount, date, firstDayOfWeek); for (int i = 0; i < visibleDatesCount; i++) { - final dynamic visibleDate = addDuration(currentDate, Duration(days: i)); + final dynamic visibleDate = addDays(currentDate, i); if (nonWorkingDays != null && nonWorkingDays.contains(visibleDate.weekday)) { continue; @@ -164,6 +161,15 @@ List getVisibleDates(dynamic date, List nonWorkingDays, int firstDayOfWeek, return datesCollection; } +/// Return date value without hour and minutes consideration. +dynamic addDays(dynamic date, int days) { + if (date is HijriDateTime) { + return date.add(Duration(days: days)); + } + + return DateTime(date.year, date.month, date.day + days); +} + /// Calculate first day of week date value based original date with first day of /// week value. dynamic getFirstDayOfWeekDate( @@ -187,6 +193,6 @@ dynamic getFirstDayOfWeekDate( value += numberOfWeekDays; } - currentDate = addDuration(currentDate, Duration(days: value)); + currentDate = addDays(currentDate, value); return currentDate; } diff --git a/packages/syncfusion_flutter_core/lib/src/calendar/custom_looping_widget.dart b/packages/syncfusion_flutter_core/lib/src/calendar/custom_looping_widget.dart index c89ce3c23..d3ae3bf09 100644 --- a/packages/syncfusion_flutter_core/lib/src/calendar/custom_looping_widget.dart +++ b/packages/syncfusion_flutter_core/lib/src/calendar/custom_looping_widget.dart @@ -117,12 +117,12 @@ class _CustomScrollViewLayout extends RenderWrap { @override void performLayout() { - double currentChildXPos, + double currentChildXPos = 0, firstChildXPos = 0, - lastChildXPos, - currentChildYPos, + lastChildXPos = 0, + currentChildYPos = 0, firstChildYPos = 0, - lastChildYPos; + lastChildYPos = 0; //// Below mentioned temporary variables used to restrict the parent data manipulation on [_updateChild] method. WrapParentData currentChildParentData, firstChildParentData, @@ -134,18 +134,12 @@ class _CustomScrollViewLayout extends RenderWrap { final dynamic children = getChildrenAsList(); _firstChild = _firstChild ?? firstChild; _lastChild = _lastChild ?? lastChild; - _currentChild = _currentChild ?? childAfter(firstChild); + _currentChild = _currentChild ?? childAfter(firstChild!); if (_navigationDirection == CustomScrollDirection.horizontal) { width = width / 3; - firstChildXPos = 0; - currentChildYPos = 0; - lastChildYPos = 0; } else if (_navigationDirection == CustomScrollDirection.vertical) { height = height / 3; - firstChildYPos = 0; - currentChildXPos = 0; - lastChildXPos = 0; } // sets the position as zero to restrict the view update when the view refreshed without swiping the view @@ -165,34 +159,30 @@ class _CustomScrollViewLayout extends RenderWrap { if (_navigationDirection == CustomScrollDirection.horizontal) { currentChildXPos = width; lastChildXPos = width * 2; - if (_position != null) { - firstChildXPos += _position; - currentChildXPos += _position; - lastChildXPos += _position; - - if (firstChildXPos.round() == -width.round()) { - firstChildXPos = width * 2; - _updateChild(); - } else if (lastChildXPos.round() == (width * 3).round()) { - lastChildXPos = 0; - _updateChild(); - } + firstChildXPos += _position; + currentChildXPos += _position; + lastChildXPos += _position; + + if (firstChildXPos.round() == -width.round()) { + firstChildXPos = width * 2; + _updateChild(); + } else if (lastChildXPos.round() == (width * 3).round()) { + lastChildXPos = 0; + _updateChild(); } } else if (_navigationDirection == CustomScrollDirection.vertical) { currentChildYPos = height; lastChildYPos = height * 2; - if (_position != null) { - firstChildYPos += _position; - currentChildYPos += _position; - lastChildYPos += _position; - - if (firstChildYPos.round() == -height.round()) { - firstChildYPos = height * 2; - _updateChild(); - } else if (lastChildYPos.round() == (height * 3).round()) { - lastChildYPos = 0; - _updateChild(); - } + firstChildYPos += _position; + currentChildYPos += _position; + lastChildYPos += _position; + + if (firstChildYPos.round() == -height.round()) { + firstChildYPos = height * 2; + _updateChild(); + } else if (lastChildYPos.round() == (height * 3).round()) { + lastChildYPos = 0; + _updateChild(); } } diff --git a/packages/syncfusion_flutter_core/lib/src/calendar/hijri_date_time.dart b/packages/syncfusion_flutter_core/lib/src/calendar/hijri_date_time.dart index f70c3ab57..1d7736f02 100644 --- a/packages/syncfusion_flutter_core/lib/src/calendar/hijri_date_time.dart +++ b/packages/syncfusion_flutter_core/lib/src/calendar/hijri_date_time.dart @@ -1755,9 +1755,9 @@ const List _kDateCollection = [ /// ``` class HijriDateTime { /// Creates a instance for HijriDateTime instance with given data. - HijriDateTime(this.year, this.month, this.day) { - _date = convertToGregorianDate(null, year: year, month: month, day: day); - } + HijriDateTime(this.year, this.month, this.day) + : _date = + convertToGregorianDate(null, year: year, month: month, day: day); /// returns the hijri date value based on the current date /// @@ -1772,7 +1772,7 @@ class HijriDateTime { } /// The gregorian date value for [this] - DateTime _date; + final DateTime _date; /// returns the hijri date from the given date /// @@ -1874,7 +1874,13 @@ class HijriDateTime { HijriDateTime _getPreviousDate(HijriDateTime date, int day) { if (day <= 0) { date = getPreviousMonthDate(date); - final int monthLength = date.getNumberOfDatesInMonth(); + final int? monthLength = date.getNumberOfDatesInMonth(); + + /// Return same date when dates count in month not specified. + if (monthLength == null) { + return date; + } + day = monthLength + day; return _getPreviousDate(date, day); } @@ -1883,11 +1889,17 @@ class HijriDateTime { } /// Returns the next possible date from the given value. - HijriDateTime _getNextDate(int monthLength, HijriDateTime date, int day) { - if (day > monthLength) { + HijriDateTime _getNextDate(int? monthLength, HijriDateTime date, int day) { + if (monthLength != null && day > monthLength) { day -= monthLength; date = getNextMonthDate(date); monthLength = date.getNumberOfDatesInMonth(); + + /// Return same date when dates count in month not specified. + if (monthLength == null) { + return date; + } + return _getNextDate(monthLength, date, day); } @@ -1896,23 +1908,27 @@ class HijriDateTime { /// Returns new [HijriDateTime] instance by subtracting given [Duration]. HijriDateTime _add(Duration duration) { - final int lengthOfMonth = getNumberOfDatesInMonth(); - int newDay, newMonth, newYear; - if (duration.inDays != null) { - HijriDateTime addedDate; - newDay = duration.inDays + day; - if (newDay > lengthOfMonth) { - addedDate = _getNextDate(lengthOfMonth, this, newDay); - } else if (newDay <= 0) { - addedDate = _getPreviousDate(this, newDay); - } + final int? lengthOfMonth = getNumberOfDatesInMonth(); - if (addedDate != null) { - return addedDate; - } + /// Return same date when dates count in month not specified. + if (lengthOfMonth == null) { + return this; + } + + int? newDay; + HijriDateTime? addedDate; + newDay = duration.inDays + day; + if (newDay > lengthOfMonth) { + addedDate = _getNextDate(lengthOfMonth, this, newDay); + } else if (newDay <= 0) { + addedDate = _getPreviousDate(this, newDay); + } + + if (addedDate != null) { + return addedDate; } - return HijriDateTime(newYear ?? year, newMonth ?? month, newDay ?? day); + return HijriDateTime(year, month, newDay); } /// Returns a new [HijriDateTime] instance with [duration] subtracted to @@ -1922,11 +1938,18 @@ class HijriDateTime { } /// returns the number of dates in the month - int getNumberOfDatesInMonth() { + int? getNumberOfDatesInMonth() { final int totalYear = year - 1; final int totalMonths = (totalYear * 12) + 1 + (month - 1); final int i = totalMonths - 16260; - return _kDateCollection[i] - _kDateCollection[i - 1]; + if (_kDateCollection.length > i - 1 && i > 0) { + return _kDateCollection[i] - _kDateCollection[i - 1]; + } + + /// Return null when the date value is not valid. + /// Valid date should be between 1356 AH (14 March 1937 CE) to + /// 1500 AH (16 November 2077 CE). + return null; } @override @@ -2019,15 +2042,23 @@ HijriDateTime convertToHijriDate(DateTime date) { } /// Converts and returns the gregorian date from the given hijri date values. -DateTime convertToGregorianDate(HijriDateTime date, - {int year, int month, int day}) { - if (date != null && date._date != null) { +DateTime convertToGregorianDate(HijriDateTime? date, + {int year = 0, int month = 0, int day = 0}) { + if (date != null) { return date._date; } + assert(year != 0); + assert(month != 0); + assert(day != 0); + /// When the year exceeds the maximum year limit return the maximum value. if (year > 1500) { return DateTime(2077, 11, 16); + } else if (year < 1356) { + /// Return minimum hijri date equivalent gregorian date value when + /// hijri year value before the minimum year value. + return DateTime(1937, 03, 14); } final int iy = year; diff --git a/packages/syncfusion_flutter_core/lib/src/license.dart b/packages/syncfusion_flutter_core/lib/src/license.dart index 7a01fcb65..6dee83c21 100644 --- a/packages/syncfusion_flutter_core/lib/src/license.dart +++ b/packages/syncfusion_flutter_core/lib/src/license.dart @@ -9,7 +9,7 @@ class SyncfusionLicense { /// /// context // ignore: missing_return - static String validateLicense([BuildContext context]) {} + static String? validateLicense(BuildContext context) {} /// /// Method to register the license key. diff --git a/packages/syncfusion_flutter_core/lib/src/localizations/global_localizations.dart b/packages/syncfusion_flutter_core/lib/src/localizations/global_localizations.dart index 09fdd63a2..71cc26b25 100644 --- a/packages/syncfusion_flutter_core/lib/src/localizations/global_localizations.dart +++ b/packages/syncfusion_flutter_core/lib/src/localizations/global_localizations.dart @@ -18,6 +18,11 @@ abstract class SfLocalizations { /// displayed under agenda section in month view. String get noEventsCalendarLabel; + /// A label that is shown on a spanned appointment. This label will be + /// displayed on appointment views for all-day, month agenda view and + /// schedule view. + String get daySpanCountLabel; + /// Label that is displayed in the calendar header view when allowed views /// have calendar day view. String get allowedViewDayLabel; @@ -144,13 +149,6 @@ abstract class SfLocalizations { /// 1 of 2 pages. String get pagesDataPagerLabel; - /// Label that is displayed in the information panel of DataPager to represent - /// the number of items. - /// - /// For example, - /// 1 of 2 pages (77 items) - String get itemsDataPagerLabel; - /// Label that is displayed in the bookmark view header of PdfViewer. String get pdfBookmarksLabel; @@ -249,6 +247,9 @@ class _DefaultLocalizations implements SfLocalizations { @override String get noEventsCalendarLabel => 'No events'; + @override + String get daySpanCountLabel => 'Day'; + @override String get allowedViewDayLabel => 'DAY'; @@ -357,9 +358,6 @@ class _DefaultLocalizations implements SfLocalizations { @override String get pagesDataPagerLabel => 'pages'; - @override - String get itemsDataPagerLabel => 'items'; - @override String get pdfBookmarksLabel => 'Bookmarks'; diff --git a/packages/syncfusion_flutter_core/lib/src/test/tooltip_tests.dart b/packages/syncfusion_flutter_core/lib/src/test/tooltip_tests.dart new file mode 100644 index 000000000..cd54d6a19 --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/src/test/tooltip_tests.dart @@ -0,0 +1,248 @@ +part of tooltip_internal; + +///This method runs the test scenarios for sftooltip widget. +void sfTooltipTest() { + group('Tooltip position', () { + _TooltipContainer? tooltipContainer; + TooltipRenderBox? renderBox; + SfTooltipState tooltipState; + testWidgets('tooltip show position', (WidgetTester tester) async { + tooltipContainer = _TooltipContainer('category_default'); + await tester.pumpWidget(tooltipContainer!); + final SfTooltip tooltip = tooltipContainer!.tooltip; + tooltipState = + // ignore: avoid_as + (tooltip.key! as GlobalKey).currentState! as SfTooltipState; + tooltipState.renderBox?.stringValue = 'test'; + tooltipState.renderBox?.boundaryRect = Rect.fromLTWH(0, 0, 400, 300); + tooltip.show(Offset(0, 100), 100); + renderBox = tooltipState.renderBox; + await tester.pump(const Duration(seconds: 3)); + }); + + test('to test tooltip position', () { + expect(renderBox?._x, 0); + expect(renderBox?._y, 100); + }); + + test('to test tooltip direction', () { + expect(renderBox?._isLeft, true); + expect(renderBox?._isOutOfBoundInTop, false); + expect(renderBox?._isRight, false); + expect(renderBox?._isTop, true); + }); + + tooltipContainer = renderBox = null; + testWidgets('tooltip show position', (WidgetTester tester) async { + tooltipContainer = _TooltipContainer('category_default'); + await tester.pumpWidget(tooltipContainer!); + final SfTooltip tooltip = tooltipContainer!.tooltip; + tooltipState = + // ignore: avoid_as + (tooltip.key! as GlobalKey).currentState! as SfTooltipState; + tooltipState.renderBox?.stringValue = 'test'; + tooltipState.renderBox?.boundaryRect = Rect.fromLTWH(0, 0, 400, 300); + tooltip.show(Offset(100, 100), 100); + renderBox = tooltipState.renderBox; + await tester.pump(const Duration(seconds: 3)); + }); + + test('to test tooltip position', () { + expect(renderBox?._x, 100); + expect(renderBox?._y, 100); + }); + + test('to test tooltip direction', () { + expect(renderBox?._isLeft, false); + expect(renderBox?._isOutOfBoundInTop, false); + expect(renderBox?._isRight, false); + expect(renderBox?._isTop, true); + }); + + tooltipContainer = renderBox = null; + testWidgets('tooltip show position', (WidgetTester tester) async { + tooltipContainer = _TooltipContainer('category_default'); + await tester.pumpWidget(tooltipContainer!); + final SfTooltip tooltip = tooltipContainer!.tooltip; + tooltipState = + // ignore: avoid_as + (tooltip.key! as GlobalKey).currentState! as SfTooltipState; + tooltipState.renderBox?.stringValue = 'test'; + tooltipState.renderBox?.inversePadding = 0.0; + tooltipState.renderBox?.boundaryRect = Rect.fromLTWH(0, 0, 400, 300); + tooltip.show(Offset(100, 0), 100); + renderBox = tooltipState.renderBox; + await tester.pump(const Duration(seconds: 3)); + }); + + test('to test tooltip position', () { + expect(renderBox?._x, 100); + expect(renderBox?._y, 0); + }); + + test('to test tooltip direction', () { + expect(renderBox?._isLeft, false); + expect(renderBox?._isOutOfBoundInTop, false); + expect(renderBox?._isRight, false); + expect(renderBox?._isTop, false); + }); + + tooltipContainer = renderBox = null; + testWidgets('tooltip show position', (WidgetTester tester) async { + tooltipContainer = _TooltipContainer('category_default'); + await tester.pumpWidget(tooltipContainer!); + final SfTooltip tooltip = tooltipContainer!.tooltip; + tooltipState = + // ignore: avoid_as + (tooltip.key! as GlobalKey).currentState! as SfTooltipState; + tooltipState.renderBox?.stringValue = 'test'; + tooltipState.renderBox?.inversePadding = 0.0; + tooltipState.renderBox?.boundaryRect = Rect.fromLTWH(0, 0, 400, 300); + tooltip.show(Offset(0, 0), 100); + renderBox = tooltipState.renderBox; + await tester.pump(const Duration(seconds: 3)); + }); + + test('to test tooltip position', () { + expect(renderBox?._x, 0); + expect(renderBox?._y, 0); + }); + + test('to test tooltip direction', () { + expect(renderBox?._isLeft, true); + expect(renderBox?._isOutOfBoundInTop, false); + expect(renderBox?._isRight, false); + expect(renderBox?._isTop, false); + }); + tooltipContainer = renderBox = null; + testWidgets('tooltip show position', (WidgetTester tester) async { + tooltipContainer = _TooltipContainer('category_default'); + await tester.pumpWidget(tooltipContainer!); + final SfTooltip tooltip = tooltipContainer!.tooltip; + tooltipState = + // ignore: avoid_as + (tooltip.key! as GlobalKey).currentState! as SfTooltipState; + tooltipState.renderBox?.stringValue = 'test'; + tooltipState.renderBox?.inversePadding = 0.0; + tooltipState.renderBox?.boundaryRect = Rect.fromLTWH(0, 0, 400, 300); + tooltip.show(Offset(10, 0), 00); + renderBox = tooltipState.renderBox; + await tester.pump(const Duration(seconds: 3)); + }); + + test('to test tooltip position', () { + expect(renderBox?._x, 10); + expect(renderBox?._y, 0); + }); + + test('to test tooltip direction', () { + expect(renderBox?._isLeft, true); + expect(renderBox?._isOutOfBoundInTop, false); + expect(renderBox?._isRight, false); + expect(renderBox?._isTop, false); + }); + + tooltipContainer = renderBox = null; + testWidgets('tooltip show position', (WidgetTester tester) async { + tooltipContainer = _TooltipContainer('category_default'); + await tester.pumpWidget(tooltipContainer!); + final SfTooltip tooltip = tooltipContainer!.tooltip; + tooltipState = + // ignore: avoid_as + (tooltip.key! as GlobalKey).currentState! as SfTooltipState; + tooltipState.renderBox?.stringValue = 'test'; + tooltipState.renderBox?.header = 'test'; + tooltipState.renderBox?.inversePadding = 0.0; + tooltipState.renderBox?.boundaryRect = Rect.fromLTWH(0, 0, 400, 300); + tooltip.show(Offset(399, 100), 00); + renderBox = tooltipState.renderBox; + await tester.pump(const Duration(seconds: 3)); + }); + + test('to test tooltip position', () { + expect(renderBox?._x, 399); + expect(renderBox?._y, 100); + }); + + test('to test tooltip direction', () { + expect(renderBox?._isLeft, false); + expect(renderBox?._isOutOfBoundInTop, false); + expect(renderBox?._isRight, true); + expect(renderBox?._isTop, true); + }); + + tooltipContainer = renderBox = null; + testWidgets('tooltip show position with template', + (WidgetTester tester) async { + tooltipContainer = _TooltipContainer('template'); + await tester.pumpWidget(tooltipContainer!); + final SfTooltip tooltip = tooltipContainer!.tooltip; + tooltipState = + // ignore: avoid_as + (tooltip.key! as GlobalKey).currentState! as SfTooltipState; + final Widget template = Container( + height: 30, width: 50, color: Colors.red, child: Text('test')); + tooltipState.renderBox?.stringValue = 'test'; + tooltipState.renderBox?.inversePadding = 0.0; + tooltipState.renderBox?.boundaryRect = Rect.fromLTWH(0, 0, 400, 300); + tooltip.show(Offset(100, 100), 100, template); + renderBox = tooltipState.renderBox; + await tester.pump(const Duration(seconds: 3)); + }); + + test('to test tooltip position', () { + expect(renderBox?._x, 100); + expect(renderBox?._y, 100); + }); + + test('to test tooltip direction', () { + expect(renderBox?._isLeft, false); + expect(renderBox?._isOutOfBoundInTop, false); + expect(renderBox?._isRight, false); + expect(renderBox?._isTop, false); + }); + }); +} + +// ignore: must_be_immutable +class _TooltipContainer extends StatelessWidget { + // ignore: prefer_const_constructors_in_immutables + _TooltipContainer(String sampleName) { + tooltip = SfTooltip( + shouldAlwaysShow: true, + key: GlobalKey(), + builder: sampleName == 'template' + ? () { + print('building'); + } + : null, + ); + } + late SfTooltip tooltip; + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Tooltip test', + home: Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () {}, + ), + // appBar: AppBar( + // title: const Text('Test Chart Widget'), + // ), + body: Center( + child: Container( + // color: Colors.blue, + margin: const EdgeInsets.fromLTRB(0, 0, 0, 0), + // height: 300, + // width: 400, + child: Stack(children: [ + Container( + color: Colors.orange, + ), + tooltip + ]), + ))), + ); + } +} diff --git a/packages/syncfusion_flutter_core/lib/src/theme/barcodes_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/barcodes_theme.dart index ab71787ce..23532df19 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/barcodes_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/barcodes_theme.dart @@ -31,7 +31,7 @@ import '../../theme.dart'; /// class SfBarcodeTheme extends InheritedTheme { /// Initialize the class of SfBarcodeTheme - const SfBarcodeTheme({Key key, this.data, this.child}) + const SfBarcodeTheme({Key? key, required this.data, required this.child}) : super(key: key, child: child); /// Specifies the color and typography values for descendant barcode widgets. @@ -80,7 +80,7 @@ class SfBarcodeTheme extends InheritedTheme { /// Defaults to [SfBarcodeTheme.barcodeThemeData] /// if there is no [SfBarcodeTheme] in the given build context. static SfBarcodeThemeData of(BuildContext context) { - final SfBarcodeTheme sfBarcodeTheme = + final SfBarcodeTheme? sfBarcodeTheme = context.dependOnInheritedWidgetOfExactType(); return sfBarcodeTheme?.data ?? SfTheme.of(context).barcodeThemeData; } @@ -90,7 +90,7 @@ class SfBarcodeTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SfBarcodeTheme ancestorTheme = + final SfBarcodeTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); return identical(this, ancestorTheme) ? child @@ -128,10 +128,10 @@ class SfBarcodeTheme extends InheritedTheme { class SfBarcodeThemeData with Diagnosticable { /// Initialize the SfBarcode theme data factory SfBarcodeThemeData({ - Brightness brightness, - Color backgroundColor, - Color barColor, - Color textColor, + Brightness? brightness, + Color? backgroundColor, + Color? barColor, + Color? textColor, }) { brightness = brightness ?? Brightness.light; final bool isLight = brightness == Brightness.light; @@ -153,10 +153,10 @@ class SfBarcodeThemeData with Diagnosticable { /// create intermediate themes based on two themes created with the /// [SfBarcodeThemeData] constructor. const SfBarcodeThemeData.raw({ - @required this.brightness, - @required this.backgroundColor, - @required this.barColor, - @required this.textColor, + required this.brightness, + required this.backgroundColor, + required this.barColor, + required this.textColor, }); /// The brightness of the overall theme of the @@ -270,10 +270,10 @@ class SfBarcodeThemeData with Diagnosticable { /// Creates a copy of this barcode theme data object with the matching fields /// replaced with the non-null parameter values. SfBarcodeThemeData copyWith({ - Brightness brightness, - Color backgroundColor, - Color barColor, - Color textColor, + Brightness? brightness, + Color? backgroundColor, + Color? barColor, + Color? textColor, }) { return SfBarcodeThemeData.raw( brightness: brightness ?? this.brightness, @@ -284,14 +284,13 @@ class SfBarcodeThemeData with Diagnosticable { } /// Returns the barcode theme data - static SfBarcodeThemeData lerp( - SfBarcodeThemeData a, SfBarcodeThemeData b, double t) { - assert(t != null); + static SfBarcodeThemeData? lerp( + SfBarcodeThemeData? a, SfBarcodeThemeData? b, double t) { if (a == null && b == null) { return null; } return SfBarcodeThemeData( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), barColor: Color.lerp(a.barColor, b.barColor, t), textColor: Color.lerp(a.textColor, b.textColor, t), ); @@ -305,10 +304,11 @@ class SfBarcodeThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - final SfBarcodeThemeData typedOther = other; - return typedOther.backgroundColor == backgroundColor && - typedOther.barColor == barColor && - typedOther.textColor == textColor; + + return other is SfBarcodeThemeData && + other.backgroundColor == backgroundColor && + other.barColor == barColor && + other.textColor == textColor; } @override diff --git a/packages/syncfusion_flutter_core/lib/src/theme/calendar_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/calendar_theme.dart index b49cd4d07..f8fb947f8 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/calendar_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/calendar_theme.dart @@ -21,7 +21,7 @@ import '../../theme.dart'; class SfCalendarTheme extends InheritedTheme { /// Constructor for the calendar theme class, which applies a theme to /// descendant Syncfusion calendar widgets. - const SfCalendarTheme({Key key, this.data, this.child}) + const SfCalendarTheme({Key? key, required this.data, required this.child}) : super(key: key, child: child); /// Specifies the color and typography values for descendant calendar widgets. @@ -65,7 +65,7 @@ class SfCalendarTheme extends InheritedTheme { /// Defaults to [SfThemeData.calendarThemeData] /// if there is no [SfCalendarTheme] in the given build context. static SfCalendarThemeData of(BuildContext context) { - final SfCalendarTheme sfCalendarTheme = + final SfCalendarTheme? sfCalendarTheme = context.dependOnInheritedWidgetOfExactType(); return sfCalendarTheme?.data ?? SfTheme.of(context).calendarThemeData; } @@ -75,7 +75,7 @@ class SfCalendarTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SfCalendarTheme ancestorTheme = + final SfCalendarTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); return identical(this, ancestorTheme) ? child @@ -105,30 +105,30 @@ class SfCalendarThemeData with Diagnosticable { /// Create a [SfCalendarThemeData] that's used to configure a /// [SfCalendarTheme]. factory SfCalendarThemeData({ - Brightness brightness, - Color backgroundColor, - Color headerBackgroundColor, - Color agendaBackgroundColor, - Color cellBorderColor, - Color activeDatesBackgroundColor, - Color todayBackgroundColor, - Color trailingDatesBackgroundColor, - Color leadingDatesBackgroundColor, - Color selectionBorderColor, - Color todayHighlightColor, - Color viewHeaderBackgroundColor, - TextStyle todayTextStyle, - TextStyle agendaDayTextStyle, - TextStyle agendaDateTextStyle, - TextStyle headerTextStyle, - TextStyle viewHeaderDateTextStyle, - TextStyle viewHeaderDayTextStyle, - TextStyle timeTextStyle, - TextStyle activeDatesTextStyle, - TextStyle trailingDatesTextStyle, - TextStyle leadingDatesTextStyle, - TextStyle blackoutDatesTextStyle, - TextStyle displayNameTextStyle, + Brightness? brightness, + Color? backgroundColor, + Color? headerBackgroundColor, + Color? agendaBackgroundColor, + Color? cellBorderColor, + Color? activeDatesBackgroundColor, + Color? todayBackgroundColor, + Color? trailingDatesBackgroundColor, + Color? leadingDatesBackgroundColor, + Color? selectionBorderColor, + Color? todayHighlightColor, + Color? viewHeaderBackgroundColor, + TextStyle? todayTextStyle, + TextStyle? agendaDayTextStyle, + TextStyle? agendaDateTextStyle, + TextStyle? headerTextStyle, + TextStyle? viewHeaderDateTextStyle, + TextStyle? viewHeaderDayTextStyle, + TextStyle? timeTextStyle, + TextStyle? activeDatesTextStyle, + TextStyle? trailingDatesTextStyle, + TextStyle? leadingDatesTextStyle, + TextStyle? blackoutDatesTextStyle, + TextStyle? displayNameTextStyle, }) { brightness = brightness ?? Brightness.light; final bool isLight = brightness == Brightness.light; @@ -255,30 +255,30 @@ class SfCalendarThemeData with Diagnosticable { /// create intermediate themes based on two themes created with the /// [SfCalendarThemeData] constructor. const SfCalendarThemeData.raw({ - @required this.brightness, - @required this.backgroundColor, - @required this.headerTextStyle, - @required this.headerBackgroundColor, - @required this.agendaBackgroundColor, - @required this.cellBorderColor, - @required this.viewHeaderDateTextStyle, - @required this.viewHeaderDayTextStyle, - @required this.viewHeaderBackgroundColor, - @required this.agendaDayTextStyle, - @required this.agendaDateTextStyle, - @required this.timeTextStyle, - @required this.activeDatesTextStyle, - @required this.activeDatesBackgroundColor, - @required this.todayBackgroundColor, - @required this.trailingDatesBackgroundColor, - @required this.leadingDatesBackgroundColor, - @required this.trailingDatesTextStyle, - @required this.blackoutDatesTextStyle, - @required this.displayNameTextStyle, - @required this.leadingDatesTextStyle, - @required this.todayTextStyle, - @required this.todayHighlightColor, - @required this.selectionBorderColor, + required this.brightness, + required this.backgroundColor, + required this.headerTextStyle, + required this.headerBackgroundColor, + required this.agendaBackgroundColor, + required this.cellBorderColor, + required this.viewHeaderDateTextStyle, + required this.viewHeaderDayTextStyle, + required this.viewHeaderBackgroundColor, + required this.agendaDayTextStyle, + required this.agendaDateTextStyle, + required this.timeTextStyle, + required this.activeDatesTextStyle, + required this.activeDatesBackgroundColor, + required this.todayBackgroundColor, + required this.trailingDatesBackgroundColor, + required this.leadingDatesBackgroundColor, + required this.trailingDatesTextStyle, + required this.blackoutDatesTextStyle, + required this.displayNameTextStyle, + required this.leadingDatesTextStyle, + required this.todayTextStyle, + required this.todayHighlightColor, + required this.selectionBorderColor, }); /// The brightness of the overall theme of the @@ -412,7 +412,7 @@ class SfCalendarThemeData with Diagnosticable { /// ); ///} /// ``` - final Color selectionBorderColor; + final Color? selectionBorderColor; ///Specifies the agenda view background color. /// @@ -647,7 +647,7 @@ class SfCalendarThemeData with Diagnosticable { /// ); ///} /// ``` - final TextStyle blackoutDatesTextStyle; + final TextStyle? blackoutDatesTextStyle; /// Specifies the text style for the text in the resource view of calendar. /// @@ -713,7 +713,7 @@ class SfCalendarThemeData with Diagnosticable { /// ); ///} /// ``` - final Color todayHighlightColor; + final Color? todayHighlightColor; /// Specifies the date text style for the view header. /// @@ -805,30 +805,30 @@ class SfCalendarThemeData with Diagnosticable { /// Creates a copy of this theme but with the given /// fields replaced with the new values. SfCalendarThemeData copyWith({ - Brightness brightness, - Color backgroundColor, - TextStyle headerTextStyle, - Color headerBackgroundColor, - Color agendaBackgroundColor, - Color cellBorderColor, - TextStyle viewHeaderDateTextStyle, - TextStyle viewHeaderDayTextStyle, - TextStyle agendaDayTextStyle, - TextStyle agendaDateTextStyle, - TextStyle timeTextStyle, - TextStyle activeDatesTextStyle, - Color activeDatesBackgroundColor, - Color todayBackgroundColor, - Color trailingDatesBackgroundColor, - Color leadingDatesBackgroundColor, - TextStyle trailingDatesTextStyle, - TextStyle blackoutDatesTextStyle, - TextStyle displayNameTextStyle, - TextStyle leadingDatesTextStyle, - TextStyle todayTextStyle, - Color todayHighlightColor, - TextStyle viewHeaderBackgroundColor, - Color selectionBorderColor, + Brightness? brightness, + Color? backgroundColor, + TextStyle? headerTextStyle, + Color? headerBackgroundColor, + Color? agendaBackgroundColor, + Color? cellBorderColor, + TextStyle? viewHeaderDateTextStyle, + TextStyle? viewHeaderDayTextStyle, + TextStyle? agendaDayTextStyle, + TextStyle? agendaDateTextStyle, + TextStyle? timeTextStyle, + TextStyle? activeDatesTextStyle, + Color? activeDatesBackgroundColor, + Color? todayBackgroundColor, + Color? trailingDatesBackgroundColor, + Color? leadingDatesBackgroundColor, + TextStyle? trailingDatesTextStyle, + TextStyle? blackoutDatesTextStyle, + TextStyle? displayNameTextStyle, + TextStyle? leadingDatesTextStyle, + TextStyle? todayTextStyle, + Color? todayHighlightColor, + Color? viewHeaderBackgroundColor, + Color? selectionBorderColor, }) { return SfCalendarThemeData.raw( brightness: brightness ?? this.brightness, @@ -870,14 +870,13 @@ class SfCalendarThemeData with Diagnosticable { } /// Linearly interpolate between two themes. - static SfCalendarThemeData lerp( - SfCalendarThemeData a, SfCalendarThemeData b, double t) { - assert(t != null); + static SfCalendarThemeData? lerp( + SfCalendarThemeData? a, SfCalendarThemeData? b, double t) { if (a == null && b == null) { return null; } return SfCalendarThemeData( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), headerBackgroundColor: Color.lerp(a.headerBackgroundColor, b.headerBackgroundColor, t), agendaBackgroundColor: @@ -907,35 +906,35 @@ class SfCalendarThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - final SfCalendarThemeData typedOther = other; - return typedOther.backgroundColor == backgroundColor && - typedOther.headerTextStyle == headerTextStyle && - typedOther.headerBackgroundColor == headerBackgroundColor && - typedOther.agendaBackgroundColor == agendaBackgroundColor && - typedOther.cellBorderColor == cellBorderColor && - typedOther.viewHeaderDateTextStyle == viewHeaderDateTextStyle && - typedOther.viewHeaderDayTextStyle == viewHeaderDayTextStyle && - typedOther.agendaDayTextStyle == agendaDayTextStyle && - typedOther.agendaDateTextStyle == agendaDateTextStyle && - typedOther.timeTextStyle == timeTextStyle && - typedOther.activeDatesTextStyle == activeDatesTextStyle && - typedOther.activeDatesBackgroundColor == activeDatesBackgroundColor && - typedOther.todayBackgroundColor == todayBackgroundColor && - typedOther.trailingDatesBackgroundColor == - trailingDatesBackgroundColor && - typedOther.leadingDatesBackgroundColor == leadingDatesBackgroundColor && - typedOther.trailingDatesTextStyle == trailingDatesTextStyle && - typedOther.blackoutDatesTextStyle == blackoutDatesTextStyle && - typedOther.leadingDatesTextStyle == leadingDatesTextStyle && - typedOther.todayTextStyle == todayTextStyle && - typedOther.todayHighlightColor == todayHighlightColor && - typedOther.viewHeaderBackgroundColor == viewHeaderBackgroundColor && - typedOther.selectionBorderColor == selectionBorderColor; + + return other is SfCalendarThemeData && + other.backgroundColor == backgroundColor && + other.headerTextStyle == headerTextStyle && + other.headerBackgroundColor == headerBackgroundColor && + other.agendaBackgroundColor == agendaBackgroundColor && + other.cellBorderColor == cellBorderColor && + other.viewHeaderDateTextStyle == viewHeaderDateTextStyle && + other.viewHeaderDayTextStyle == viewHeaderDayTextStyle && + other.agendaDayTextStyle == agendaDayTextStyle && + other.agendaDateTextStyle == agendaDateTextStyle && + other.timeTextStyle == timeTextStyle && + other.activeDatesTextStyle == activeDatesTextStyle && + other.activeDatesBackgroundColor == activeDatesBackgroundColor && + other.todayBackgroundColor == todayBackgroundColor && + other.trailingDatesBackgroundColor == trailingDatesBackgroundColor && + other.leadingDatesBackgroundColor == leadingDatesBackgroundColor && + other.trailingDatesTextStyle == trailingDatesTextStyle && + other.blackoutDatesTextStyle == blackoutDatesTextStyle && + other.leadingDatesTextStyle == leadingDatesTextStyle && + other.todayTextStyle == todayTextStyle && + other.todayHighlightColor == todayHighlightColor && + other.viewHeaderBackgroundColor == viewHeaderBackgroundColor && + other.selectionBorderColor == selectionBorderColor; } @override int get hashCode { - final List values = [ + final List values = [ backgroundColor, headerTextStyle, headerBackgroundColor, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/charts_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/charts_theme.dart index 5bb2b5ad8..ba305c6b8 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/charts_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/charts_theme.dart @@ -29,7 +29,7 @@ import '../../theme.dart'; /// class SfChartTheme extends InheritedTheme { /// Creating an argument constructor of SfChartTheme class. - const SfChartTheme({Key key, this.data, this.child}) + const SfChartTheme({Key? key, required this.data, required this.child}) : super(key: key, child: child); /// Specifies the color and typography values for descendant chart widgets. @@ -74,7 +74,7 @@ class SfChartTheme extends InheritedTheme { /// in the given build context. /// static SfChartThemeData of(BuildContext context) { - final SfChartTheme sfChartTheme = + final SfChartTheme? sfChartTheme = context.dependOnInheritedWidgetOfExactType(); return sfChartTheme?.data ?? SfTheme.of(context).chartThemeData; } @@ -84,7 +84,7 @@ class SfChartTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SfChartTheme ancestorTheme = + final SfChartTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); return identical(this, ancestorTheme) ? child @@ -120,32 +120,32 @@ class SfChartTheme extends InheritedTheme { class SfChartThemeData with Diagnosticable { /// Creating an argument constructor of SfChartThemeData class. factory SfChartThemeData( - {Brightness brightness, - Color backgroundColor, - Color axisLabelColor, - Color axisTitleColor, - Color axisLineColor, - Color majorGridLineColor, - Color minorGridLineColor, - Color majorTickLineColor, - Color minorTickLineColor, - Color titleTextColor, - Color titleBackgroundColor, - Color legendTextColor, - Color legendTitleColor, - Color legendBackgroundColor, - Color plotAreaBackgroundColor, - Color plotAreaBorderColor, - Color crosshairLineColor, - Color crosshairBackgroundColor, - Color crosshairLabelColor, - Color tooltipColor, - Color tooltipLabelColor, - Color tooltipSeparatorColor, - Color selectionRectColor, - Color selectionRectBorderColor, - Color selectionTooltipConnectorLineColor, - Color waterfallConnectorLineColor}) { + {Brightness? brightness, + Color? backgroundColor, + Color? axisLabelColor, + Color? axisTitleColor, + Color? axisLineColor, + Color? majorGridLineColor, + Color? minorGridLineColor, + Color? majorTickLineColor, + Color? minorTickLineColor, + Color? titleTextColor, + Color? titleBackgroundColor, + Color? legendTextColor, + Color? legendTitleColor, + Color? legendBackgroundColor, + Color? plotAreaBackgroundColor, + Color? plotAreaBorderColor, + Color? crosshairLineColor, + Color? crosshairBackgroundColor, + Color? crosshairLabelColor, + Color? tooltipColor, + Color? tooltipLabelColor, + Color? tooltipSeparatorColor, + Color? selectionRectColor, + Color? selectionRectBorderColor, + Color? selectionTooltipConnectorLineColor, + Color? waterfallConnectorLineColor}) { brightness = brightness ?? Brightness.light; final bool isLight = brightness == Brightness.light; backgroundColor ??= Colors.transparent; @@ -255,32 +255,32 @@ class SfChartThemeData with Diagnosticable { /// [SfChartThemeData] constructor. /// const SfChartThemeData.raw( - {@required this.brightness, - @required this.axisLabelColor, - @required this.axisLineColor, - @required this.axisTitleColor, - @required this.backgroundColor, - @required this.titleTextColor, - @required this.crosshairBackgroundColor, - @required this.crosshairLabelColor, - @required this.crosshairLineColor, - @required this.legendBackgroundColor, - @required this.legendTextColor, - @required this.legendTitleColor, - @required this.majorGridLineColor, - @required this.majorTickLineColor, - @required this.minorGridLineColor, - @required this.minorTickLineColor, - @required this.plotAreaBackgroundColor, - @required this.plotAreaBorderColor, - @required this.selectionRectColor, - @required this.selectionRectBorderColor, - @required this.selectionTooltipConnectorLineColor, - @required this.titleBackgroundColor, - @required this.tooltipColor, - @required this.tooltipSeparatorColor, - @required this.tooltipLabelColor, - @required this.waterfallConnectorLineColor}); + {required this.brightness, + required this.axisLabelColor, + required this.axisLineColor, + required this.axisTitleColor, + required this.backgroundColor, + required this.titleTextColor, + required this.crosshairBackgroundColor, + required this.crosshairLabelColor, + required this.crosshairLineColor, + required this.legendBackgroundColor, + required this.legendTextColor, + required this.legendTitleColor, + required this.majorGridLineColor, + required this.majorTickLineColor, + required this.minorGridLineColor, + required this.minorTickLineColor, + required this.plotAreaBackgroundColor, + required this.plotAreaBorderColor, + required this.selectionRectColor, + required this.selectionRectBorderColor, + required this.selectionTooltipConnectorLineColor, + required this.titleBackgroundColor, + required this.tooltipColor, + required this.tooltipSeparatorColor, + required this.tooltipLabelColor, + required this.waterfallConnectorLineColor}); /// The brightness of the overall theme of the /// application for the chart widgets. @@ -975,32 +975,32 @@ class SfChartThemeData with Diagnosticable { /// Creates a copy of this chart theme data object with the matching fields /// replaced with the non-null parameter values. SfChartThemeData copyWith( - {Brightness brightness, - Color axisLabelColor, - Color axisLineColor, - Color axisTitleColor, - Color backgroundColor, - Color titleTextColor, - Color crosshairBackgroundColor, - Color crosshairLabelColor, - Color crosshairLineColor, - Color legendBackgroundColor, - Color legendTextColor, - Color legendTitleColor, - Color majorGridLineColor, - Color majorTickLineColor, - Color minorGridLineColor, - Color minorTickLineColor, - Color plotAreaBackgroundColor, - Color plotAreaBorderColor, - Color selectionRectColor, - Color selectionRectBorderColor, - Color selectionTooltipConnectorLineColor, - Color titleBackgroundColor, - Color tooltipColor, - Color tooltipSeparatorColor, - Color tooltipLabelColor, - Color waterfallConnectorLineColor}) { + {Brightness? brightness, + Color? axisLabelColor, + Color? axisLineColor, + Color? axisTitleColor, + Color? backgroundColor, + Color? titleTextColor, + Color? crosshairBackgroundColor, + Color? crosshairLabelColor, + Color? crosshairLineColor, + Color? legendBackgroundColor, + Color? legendTextColor, + Color? legendTitleColor, + Color? majorGridLineColor, + Color? majorTickLineColor, + Color? minorGridLineColor, + Color? minorTickLineColor, + Color? plotAreaBackgroundColor, + Color? plotAreaBorderColor, + Color? selectionRectColor, + Color? selectionRectBorderColor, + Color? selectionTooltipConnectorLineColor, + Color? titleBackgroundColor, + Color? tooltipColor, + Color? tooltipSeparatorColor, + Color? tooltipLabelColor, + Color? waterfallConnectorLineColor}) { return SfChartThemeData.raw( brightness: brightness ?? this.brightness, axisLabelColor: axisLabelColor ?? this.axisLabelColor, @@ -1040,14 +1040,13 @@ class SfChartThemeData with Diagnosticable { } /// Linearly interpolate between two themes. - static SfChartThemeData lerp( - SfChartThemeData a, SfChartThemeData b, double t) { - assert(t != null); + static SfChartThemeData? lerp( + SfChartThemeData? a, SfChartThemeData? b, double t) { if (a == null && b == null) { return null; } return SfChartThemeData( - axisLabelColor: Color.lerp(a.axisLabelColor, b.axisLabelColor, t), + axisLabelColor: Color.lerp(a!.axisLabelColor, b!.axisLabelColor, t), axisLineColor: Color.lerp(a.axisLineColor, b.axisLineColor, t), axisTitleColor: Color.lerp(a.axisTitleColor, b.axisTitleColor, t), backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), @@ -1102,33 +1101,34 @@ class SfChartThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - final SfChartThemeData typedOther = other; - return typedOther.axisLabelColor == axisLabelColor && - typedOther.axisLineColor == axisLineColor && - typedOther.axisTitleColor == axisTitleColor && - typedOther.backgroundColor == backgroundColor && - typedOther.titleTextColor == titleTextColor && - typedOther.crosshairBackgroundColor == crosshairBackgroundColor && - typedOther.crosshairLabelColor == crosshairLabelColor && - typedOther.crosshairLineColor == crosshairLineColor && - typedOther.legendBackgroundColor == legendBackgroundColor && - typedOther.legendTextColor == legendTextColor && - typedOther.legendTitleColor == legendTitleColor && - typedOther.majorGridLineColor == majorGridLineColor && - typedOther.majorTickLineColor == majorTickLineColor && - typedOther.minorGridLineColor == minorGridLineColor && - typedOther.minorTickLineColor == minorTickLineColor && - typedOther.plotAreaBackgroundColor == plotAreaBackgroundColor && - typedOther.plotAreaBorderColor == plotAreaBorderColor && - typedOther.selectionRectColor == selectionRectColor && - typedOther.selectionRectBorderColor == selectionRectBorderColor && - typedOther.selectionTooltipConnectorLineColor == + + return other is SfChartThemeData && + other.axisLabelColor == axisLabelColor && + other.axisLineColor == axisLineColor && + other.axisTitleColor == axisTitleColor && + other.backgroundColor == backgroundColor && + other.titleTextColor == titleTextColor && + other.crosshairBackgroundColor == crosshairBackgroundColor && + other.crosshairLabelColor == crosshairLabelColor && + other.crosshairLineColor == crosshairLineColor && + other.legendBackgroundColor == legendBackgroundColor && + other.legendTextColor == legendTextColor && + other.legendTitleColor == legendTitleColor && + other.majorGridLineColor == majorGridLineColor && + other.majorTickLineColor == majorTickLineColor && + other.minorGridLineColor == minorGridLineColor && + other.minorTickLineColor == minorTickLineColor && + other.plotAreaBackgroundColor == plotAreaBackgroundColor && + other.plotAreaBorderColor == plotAreaBorderColor && + other.selectionRectColor == selectionRectColor && + other.selectionRectBorderColor == selectionRectBorderColor && + other.selectionTooltipConnectorLineColor == selectionTooltipConnectorLineColor && - typedOther.titleBackgroundColor == titleBackgroundColor && - typedOther.tooltipColor == tooltipColor && - typedOther.tooltipSeparatorColor == tooltipSeparatorColor && - typedOther.tooltipLabelColor == tooltipLabelColor && - typedOther.waterfallConnectorLineColor == waterfallConnectorLineColor; + other.titleBackgroundColor == titleBackgroundColor && + other.tooltipColor == tooltipColor && + other.tooltipSeparatorColor == tooltipSeparatorColor && + other.tooltipLabelColor == tooltipLabelColor && + other.waterfallConnectorLineColor == waterfallConnectorLineColor; } @override diff --git a/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart index cac846162..8542026e3 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart @@ -20,7 +20,7 @@ import '../../theme.dart'; /// ``` class SfDataGridTheme extends InheritedTheme { /// Applies the given theme [data] to [child]. - const SfDataGridTheme({Key key, this.data, this.child}) + const SfDataGridTheme({Key? key, required this.data, required this.child}) : super(key: key, child: child); /// Specifies the color and typography values for descendant [SfDataGrid] @@ -66,7 +66,7 @@ class SfDataGridTheme extends InheritedTheme { /// is no [SfDataGridTheme] in the given build context. /// static SfDataGridThemeData of(BuildContext context) { - final SfDataGridTheme sfDataGridTheme = + final SfDataGridTheme? sfDataGridTheme = context.dependOnInheritedWidgetOfExactType(); return sfDataGridTheme?.data ?? SfTheme.of(context).dataGridThemeData; } @@ -76,7 +76,7 @@ class SfDataGridTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SfDataGridTheme ancestorTheme = + final SfDataGridTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); return identical(this, ancestorTheme) ? child @@ -107,62 +107,20 @@ class SfDataGridThemeData with Diagnosticable { /// Create a [SfDataGridThemeData] that's used to configure a /// [SfDataGridTheme]. factory SfDataGridThemeData({ - Brightness brightness, - DataGridHeaderCellStyle headerStyle, - DataGridCellStyle cellStyle, - Color gridLineColor, - double gridLineStrokeWidth, - DataGridCellStyle selectionStyle, - DataGridCurrentCellStyle currentCellStyle, - Color frozenPaneLineColor, - double frozenPaneLineWidth, + Brightness? brightness, + Color? gridLineColor, + double? gridLineStrokeWidth, + Color? selectionColor, + DataGridCurrentCellStyle? currentCellStyle, + Color? frozenPaneLineColor, + double? frozenPaneLineWidth, + Color? sortIconColor, + Color? headerHoverColor, + Color? headerColor, + double? frozenPaneElevation, }) { brightness = brightness ?? Brightness.light; final bool isLight = brightness == Brightness.light; - headerStyle ??= isLight - ? const DataGridHeaderCellStyle( - textStyle: TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: Colors.black87), - backgroundColor: Color.fromRGBO(255, 255, 255, 1), - sortIconColor: Colors.black54, - hoverColor: Color.fromRGBO(245, 245, 245, 1), - hoverTextStyle: TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: Colors.black87)) - : const DataGridHeaderCellStyle( - textStyle: TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: Color.fromRGBO(255, 255, 255, 1)), - backgroundColor: Color.fromRGBO(33, 33, 33, 1), - sortIconColor: Colors.white54, - hoverColor: Color.fromRGBO(66, 66, 66, 1), - hoverTextStyle: TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: Color.fromRGBO(255, 255, 255, 1))); - cellStyle ??= isLight - ? const DataGridCellStyle( - textStyle: TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: Colors.black87), - backgroundColor: Color.fromRGBO(255, 255, 255, 1)) - : const DataGridCellStyle( - textStyle: TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: Color.fromRGBO(255, 255, 255, 1)), - backgroundColor: Color.fromRGBO(33, 33, 33, 1)); gridLineColor ??= isLight ? const Color.fromRGBO(0, 0, 0, 0.26) @@ -170,21 +128,9 @@ class SfDataGridThemeData with Diagnosticable { gridLineStrokeWidth ??= 1; - selectionStyle ??= isLight - ? DataGridCellStyle( - textStyle: TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: const Color.fromRGBO(0, 0, 0, 0.87)), - backgroundColor: const Color.fromRGBO(238, 238, 238, 1)) - : DataGridCellStyle( - textStyle: TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: const Color.fromRGBO(255, 255, 255, 1)), - backgroundColor: const Color.fromRGBO(48, 48, 48, 1)); + selectionColor ??= isLight + ? const Color.fromRGBO(238, 238, 238, 1) + : const Color.fromRGBO(48, 48, 48, 1); currentCellStyle ??= isLight ? const DataGridCurrentCellStyle( @@ -198,16 +144,31 @@ class SfDataGridThemeData with Diagnosticable { frozenPaneLineWidth ??= 2; + headerHoverColor ??= isLight + ? Color.fromRGBO(245, 245, 245, 1) + : Color.fromRGBO(66, 66, 66, 1); + + sortIconColor ??= isLight ? Colors.black54 : Colors.white54; + + headerColor ??= isLight + ? Color.fromRGBO(255, 255, 255, 1) + : Color.fromRGBO(33, 33, 33, 1); + + frozenPaneElevation ??= 5.0; + return SfDataGridThemeData.raw( - brightness: brightness, - headerStyle: headerStyle, - cellStyle: cellStyle, - gridLineColor: gridLineColor, - gridLineStrokeWidth: gridLineStrokeWidth, - selectionStyle: selectionStyle, - currentCellStyle: currentCellStyle, - frozenPaneLineColor: frozenPaneLineColor, - frozenPaneLineWidth: frozenPaneLineWidth); + brightness: brightness, + gridLineColor: gridLineColor, + gridLineStrokeWidth: gridLineStrokeWidth, + selectionColor: selectionColor, + currentCellStyle: currentCellStyle, + frozenPaneLineColor: frozenPaneLineColor, + frozenPaneLineWidth: frozenPaneLineWidth, + headerHoverColor: headerHoverColor, + sortIconColor: sortIconColor, + headerColor: headerColor, + frozenPaneElevation: frozenPaneElevation, + ); } /// Create a [SfDataGridThemeData] given a set of exact values. @@ -217,16 +178,19 @@ class SfDataGridThemeData with Diagnosticable { /// create intermediate themes based on two themes created with the /// [SfDataGridThemeData] constructor. /// - const SfDataGridThemeData.raw( - {@required this.brightness, - @required this.headerStyle, - @required this.cellStyle, - @required this.gridLineColor, - @required this.gridLineStrokeWidth, - @required this.selectionStyle, - @required this.currentCellStyle, - @required this.frozenPaneLineColor, - @required this.frozenPaneLineWidth}); + const SfDataGridThemeData.raw({ + required this.brightness, + required this.gridLineColor, + required this.gridLineStrokeWidth, + required this.selectionColor, + required this.currentCellStyle, + required this.frozenPaneLineColor, + required this.frozenPaneLineWidth, + required this.sortIconColor, + required this.headerColor, + required this.headerHoverColor, + required this.frozenPaneElevation, + }); /// The brightness of the overall theme of the /// application for the [SfDataGrid] widgets. @@ -255,53 +219,6 @@ class SfDataGridThemeData with Diagnosticable { /// ``` final Brightness brightness; - /// Defines the default configuration of header cells in [SfDataGrid]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// dataGridThemeData: SfDataGridThemeData( - /// headerStyle: DataGridHeaderCellStyle( - /// backgroundColor: Colors.teal, - /// textStyle: TextStyle(color: Colors.white) - /// ) - /// ) - /// ), - /// child: SfDataGrid(), - /// ), - /// ) - /// ); - /// } - /// ``` - final DataGridHeaderCellStyle headerStyle; - - /// Defines the default configuration of cells except header cells in - /// [SfDataGrid]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// dataGridThemeData: SfDataGridThemeData( - /// cellStyle: DataGridCellStyle( - /// backgroundColor: const Color(0xFF2E2946), - /// textStyle: TextStyle(decoration: TextDecoration.underline) - /// ) - /// ) - /// ), - /// child: SfDataGrid(), - /// ), - /// ) - /// ); - /// } - /// ``` - final DataGridCellStyle cellStyle; - /// The color for grid line. /// /// ```dart @@ -363,7 +280,7 @@ class SfDataGridThemeData with Diagnosticable { /// ); /// } /// ``` - final DataGridCellStyle selectionStyle; + final Color selectionColor; /// Defines the default configuration of current cell in [SfDataGrid]. /// @@ -397,53 +314,81 @@ class SfDataGridThemeData with Diagnosticable { /// This is applicable for both the frozen column and row. final Color frozenPaneLineColor; + /// The color of the sort icon which indicates the ascending or descending + /// order. + final Color sortIconColor; + + /// The background color of header cells when a pointer is hovering over it + /// in [SfDataGrid]. + final Color headerHoverColor; + + /// The color for the header cells in the [SfDataGrid]. + final Color headerColor; + + /// The elevation of the frozen pane line. + /// + /// This controls the size of the shadow below the frozen pane line. + /// + /// This is applicable for both the frozen column and row. + /// + /// If you want to hide the shadow and show only line, you can set this + /// property as 0.0. + /// + /// Defaults to 5.0. The value is always non-negative. + final double frozenPaneElevation; + /// Creates a copy of this theme but with the given /// fields replaced with the new values. - SfDataGridThemeData copyWith( - {Brightness brightness, - DataGridHeaderCellStyle headerStyle, - DataGridCellStyle cellStyle, - Color gridLineColor, - double gridLineStrokeWidth, - DataGridCellStyle selectionStyle, - DataGridCurrentCellStyle currentCellStyle, - double frozenPaneLineWidth, - Color frozenPaneLineColor}) { + SfDataGridThemeData copyWith({ + Brightness? brightness, + Color? gridLineColor, + double? gridLineStrokeWidth, + Color? selectionColor, + DataGridCurrentCellStyle? currentCellStyle, + double? frozenPaneLineWidth, + Color? frozenPaneLineColor, + Color? sortIconColor, + Color? headerHoverColor, + Color? headerColor, + double? frozenPaneElevation, + }) { return SfDataGridThemeData.raw( brightness: brightness ?? this.brightness, - headerStyle: headerStyle ?? this.headerStyle, - cellStyle: cellStyle ?? this.cellStyle, gridLineColor: gridLineColor ?? this.gridLineColor, gridLineStrokeWidth: gridLineStrokeWidth ?? this.gridLineStrokeWidth, - selectionStyle: selectionStyle ?? this.selectionStyle, + selectionColor: selectionColor ?? this.selectionColor, currentCellStyle: currentCellStyle ?? this.currentCellStyle, frozenPaneLineColor: frozenPaneLineColor ?? this.frozenPaneLineColor, frozenPaneLineWidth: frozenPaneLineWidth ?? this.frozenPaneLineWidth, + sortIconColor: sortIconColor ?? this.sortIconColor, + headerColor: headerColor ?? this.headerColor, + headerHoverColor: headerHoverColor ?? this.headerHoverColor, + frozenPaneElevation: frozenPaneElevation ?? this.frozenPaneElevation, ); } /// Linearly interpolate between two themes. - static SfDataGridThemeData lerp( - SfDataGridThemeData a, SfDataGridThemeData b, double t) { - assert(t != null); + static SfDataGridThemeData? lerp( + SfDataGridThemeData? a, SfDataGridThemeData? b, double t) { if (a == null && b == null) { return null; } return SfDataGridThemeData( - headerStyle: - DataGridHeaderCellStyle.lerp(a.headerStyle, b.headerStyle, t), - cellStyle: DataGridCellStyle.lerp(a.cellStyle, b.cellStyle, t), - gridLineColor: Color.lerp(a.gridLineColor, b.gridLineColor, t), + gridLineColor: Color.lerp(a!.gridLineColor, b!.gridLineColor, t), gridLineStrokeWidth: lerpDouble(a.gridLineStrokeWidth, b.gridLineStrokeWidth, t), - selectionStyle: - DataGridCellStyle.lerp(a.selectionStyle, b.selectionStyle, t), + selectionColor: Color.lerp(a.selectionColor, b.selectionColor, t), currentCellStyle: DataGridCurrentCellStyle.lerp( a.currentCellStyle, b.currentCellStyle, t), frozenPaneLineColor: Color.lerp(a.frozenPaneLineColor, b.frozenPaneLineColor, t), frozenPaneLineWidth: - lerpDouble(a.frozenPaneLineWidth, b.frozenPaneLineWidth, t)); + lerpDouble(a.frozenPaneLineWidth, b.frozenPaneLineWidth, t), + sortIconColor: Color.lerp(a.sortIconColor, b.sortIconColor, t), + headerHoverColor: Color.lerp(a.headerHoverColor, b.headerHoverColor, t), + headerColor: Color.lerp(a.headerColor, b.headerColor, t), + frozenPaneElevation: + lerpDouble(a.frozenPaneElevation, b.frozenPaneElevation, t)); } @override @@ -454,29 +399,35 @@ class SfDataGridThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - final SfDataGridThemeData typedOther = other; - return typedOther.brightness == brightness && - typedOther.headerStyle == headerStyle && - typedOther.cellStyle == cellStyle && - typedOther.gridLineColor == gridLineColor && - typedOther.gridLineStrokeWidth == gridLineStrokeWidth && - typedOther.selectionStyle == selectionStyle && - typedOther.currentCellStyle == currentCellStyle && - typedOther.frozenPaneLineWidth == frozenPaneLineWidth && - typedOther.frozenPaneLineColor == frozenPaneLineColor; + + return other is SfDataGridThemeData && + other.brightness == brightness && + other.gridLineColor == gridLineColor && + other.gridLineStrokeWidth == gridLineStrokeWidth && + other.selectionColor == selectionColor && + other.currentCellStyle == currentCellStyle && + other.frozenPaneLineWidth == frozenPaneLineWidth && + other.frozenPaneLineColor == frozenPaneLineColor && + other.sortIconColor == sortIconColor && + other.headerHoverColor == headerHoverColor && + other.headerColor == headerColor && + other.frozenPaneElevation == frozenPaneElevation && + other.frozenPaneElevation == frozenPaneElevation; } @override int get hashCode { final List values = [ - headerStyle, - cellStyle, gridLineColor, gridLineStrokeWidth, - selectionStyle, + selectionColor, currentCellStyle, frozenPaneLineColor, - frozenPaneLineWidth + frozenPaneLineWidth, + sortIconColor, + headerHoverColor, + headerColor, + frozenPaneElevation, ]; return hashList(values); } @@ -487,19 +438,12 @@ class SfDataGridThemeData with Diagnosticable { final SfDataGridThemeData defaultData = SfDataGridThemeData(); properties.add(EnumProperty('brightness', brightness, defaultValue: defaultData.brightness)); - properties.add(DiagnosticsProperty( - 'headerStyle', headerStyle, - defaultValue: defaultData.headerStyle)); - properties.add(DiagnosticsProperty( - 'cellStyle', cellStyle, - defaultValue: defaultData.cellStyle)); properties.add(ColorProperty('gridLineColor', gridLineColor, defaultValue: defaultData.gridLineColor)); properties.add(DoubleProperty('gridLineStrokeWidth', gridLineStrokeWidth, defaultValue: defaultData.gridLineStrokeWidth)); - properties.add(DiagnosticsProperty( - 'selectionStyle', selectionStyle, - defaultValue: defaultData.selectionStyle)); + properties.add(ColorProperty('selectionColor', selectionColor, + defaultValue: defaultData.selectionColor)); properties.add(DiagnosticsProperty( 'currentCellStyle', currentCellStyle, defaultValue: defaultData.currentCellStyle)); @@ -507,122 +451,14 @@ class SfDataGridThemeData with Diagnosticable { defaultValue: defaultData.frozenPaneLineColor)); properties.add(DoubleProperty('frozenPaneLineWidth', frozenPaneLineWidth, defaultValue: defaultData.frozenPaneLineWidth)); - } -} - -/// Holds the color and typography values for the cells in the [SfDataGrid]. -class DataGridCellStyle { - /// Create a [DataGridCellStyle] that's used to configure a style for the - /// cells in [SfDataGrid]. - const DataGridCellStyle({this.backgroundColor, this.textStyle}); - - /// The background color of cells in [SfDataGrid]. - final Color backgroundColor; - - /// The style for text of cells in [SfDataGrid]. - final TextStyle textStyle; - - @override - int get hashCode { - final List values = [ - textStyle, - backgroundColor, - ]; - return hashList(values); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - return other is DataGridCellStyle && - other.backgroundColor == backgroundColor && - other.textStyle == textStyle; - } - - /// Linearly interpolate between two styles. - static DataGridCellStyle lerp( - DataGridCellStyle a, DataGridCellStyle b, double t) { - assert(t != null); - if (a == null && b == null) { - return null; - } - return DataGridCellStyle( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), - textStyle: TextStyle.lerp(a.textStyle, b.textStyle, t)); - } -} - -/// Holds the color and typography values for the header cells -/// in the [SfDataGrid]. -class DataGridHeaderCellStyle extends DataGridCellStyle { - /// Create a [DataGridHeaderCellStyle] that's used to configure - /// a style for the header cells in [SfDataGrid]. - const DataGridHeaderCellStyle( - {Color backgroundColor, - TextStyle textStyle, - this.sortIconColor, - this.hoverColor, - this.hoverTextStyle}) - : super(backgroundColor: backgroundColor, textStyle: textStyle); - - /// The color of the sort icon which indicates the ascending or descending - /// order. - final Color sortIconColor; - - /// The background color of header cells when a pointer is hovering over it - /// in [SfDataGrid]. - final Color hoverColor; - - /// The style for text of header cells when a pointer is hovering over it - /// in [SfDataGrid]. - final TextStyle hoverTextStyle; - - @override - int get hashCode { - final List values = [ - textStyle, - backgroundColor, - sortIconColor, - hoverColor, - hoverTextStyle - ]; - return hashList(values); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - return other is DataGridHeaderCellStyle && - other.backgroundColor == backgroundColor && - other.textStyle == textStyle && - other.sortIconColor == sortIconColor && - other.hoverColor == hoverColor && - other.hoverTextStyle == hoverTextStyle; - } - - /// Linearly interpolate between two styles. - static DataGridHeaderCellStyle lerp( - DataGridHeaderCellStyle a, DataGridHeaderCellStyle b, double t) { - assert(t != null); - if (a == null && b == null) { - return null; - } - return DataGridHeaderCellStyle( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), - textStyle: TextStyle.lerp(a.textStyle, b.textStyle, t), - sortIconColor: Color.lerp(a.sortIconColor, b.sortIconColor, t), - hoverColor: Color.lerp(a.hoverColor, b.hoverColor, t), - hoverTextStyle: TextStyle.lerp(a.hoverTextStyle, b.hoverTextStyle, t)); + properties.add(ColorProperty('sortIconColor', sortIconColor, + defaultValue: defaultData.sortIconColor)); + properties.add(ColorProperty('headerHoverColor', headerHoverColor, + defaultValue: defaultData.headerHoverColor)); + properties.add(ColorProperty('headerColor', headerColor, + defaultValue: defaultData.headerColor)); + properties.add(DoubleProperty('frozenPaneElevation', frozenPaneElevation, + defaultValue: defaultData.frozenPaneElevation)); } } @@ -630,7 +466,8 @@ class DataGridHeaderCellStyle extends DataGridCellStyle { class DataGridCurrentCellStyle { /// Create a [DataGridCurrentCellStyle] that's used to configure /// a style for the current cell in [SfDataGrid]. - const DataGridCurrentCellStyle({this.borderColor, this.borderWidth}); + const DataGridCurrentCellStyle( + {required this.borderColor, required this.borderWidth}); /// The color of the border in current cell. final Color borderColor; @@ -661,14 +498,13 @@ class DataGridCurrentCellStyle { } /// Linearly interpolate between two styles. - static DataGridCurrentCellStyle lerp( - DataGridCurrentCellStyle a, DataGridCurrentCellStyle b, double t) { - assert(t != null); + static DataGridCurrentCellStyle? lerp( + DataGridCurrentCellStyle? a, DataGridCurrentCellStyle? b, double t) { if (a == null && b == null) { return null; } return DataGridCurrentCellStyle( - borderColor: Color.lerp(a.borderColor, b.borderColor, t), - borderWidth: lerpDouble(a.borderWidth, b.borderWidth, t)); + borderColor: Color.lerp(a!.borderColor, b!.borderColor, t)!, + borderWidth: lerpDouble(a.borderWidth, b.borderWidth, t)!); } } diff --git a/packages/syncfusion_flutter_core/lib/src/theme/datapager_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/datapager_theme.dart index ec0b90f1c..074acdb80 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/datapager_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/datapager_theme.dart @@ -8,7 +8,7 @@ import '../../theme.dart'; /// Applies a theme to descendant [SfDataPager] widgets. class SfDataPagerTheme extends InheritedTheme { /// Applies the given theme [data] to [child]. - const SfDataPagerTheme({Key key, this.data, this.child}) + const SfDataPagerTheme({Key? key, required this.data, required this.child}) : super(key: key, child: child); /// Specifies the color and typography values for descendant [SfDataPager] @@ -24,8 +24,8 @@ class SfDataPagerTheme extends InheritedTheme { /// /// Defaults to [SfThemeData.dataPagerThemeData] if there /// is no [SfDataPagerTheme] in the given build context. - static SfDataPagerThemeData of(BuildContext context) { - final SfDataPagerTheme sfDataPagerTheme = + static SfDataPagerThemeData? of(BuildContext context) { + final SfDataPagerTheme? sfDataPagerTheme = context.dependOnInheritedWidgetOfExactType(); return sfDataPagerTheme?.data ?? SfTheme.of(context).dataPagerThemeData; } @@ -35,7 +35,7 @@ class SfDataPagerTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SfDataPagerTheme ancestorTheme = + final SfDataPagerTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); return identical(this, ancestorTheme) ? child @@ -51,17 +51,17 @@ class SfDataPagerThemeData with Diagnosticable { /// Create a [SfDataPagerThemeData] that's used to configure a /// [SfDataPagerTheme]. factory SfDataPagerThemeData( - {Brightness brightness, - Color backgroundColor, - Color itemColor, - TextStyle itemTextStyle, - Color selectedItemColor, - TextStyle selectedItemTextStyle, - Color disabledItemColor, - TextStyle disabledItemTextStyle, - Color itemBorderColor, - double itemBorderWidth, - BorderRadiusGeometry itemBorderRadius}) { + {Brightness? brightness, + Color? backgroundColor, + Color? itemColor, + TextStyle? itemTextStyle, + Color? selectedItemColor, + TextStyle? selectedItemTextStyle, + Color? disabledItemColor, + TextStyle? disabledItemTextStyle, + Color? itemBorderColor, + double? itemBorderWidth, + BorderRadiusGeometry? itemBorderRadius}) { brightness = brightness ?? Brightness.light; final bool isLight = brightness == Brightness.light; @@ -121,17 +121,17 @@ class SfDataPagerThemeData with Diagnosticable { /// create intermediate themes based on two themes created with the /// [SfDataPagerThemeData] constructor. const SfDataPagerThemeData.raw( - {@required this.brightness, - @required this.backgroundColor, - @required this.itemColor, - @required this.itemTextStyle, - @required this.selectedItemColor, - @required this.selectedItemTextStyle, - @required this.disabledItemColor, - @required this.disabledItemTextStyle, - @required this.itemBorderColor, - @required this.itemBorderWidth, - @required this.itemBorderRadius}); + {required this.brightness, + required this.backgroundColor, + required this.itemColor, + required this.itemTextStyle, + required this.selectedItemColor, + required this.selectedItemTextStyle, + required this.disabledItemColor, + required this.disabledItemTextStyle, + required this.itemBorderColor, + required this.itemBorderWidth, + required this.itemBorderRadius}); /// The brightness of the overall theme of the /// application for the [SfDataPager] widgets. @@ -166,7 +166,7 @@ class SfDataPagerThemeData with Diagnosticable { final Color itemBorderColor; /// The width of the border in page item. - final double itemBorderWidth; + final double? itemBorderWidth; ///If non null, the corners of the page item are rounded by this [ItemBorderRadius]. /// @@ -179,17 +179,17 @@ class SfDataPagerThemeData with Diagnosticable { /// Creates a copy of this theme but with the given /// fields replaced with the new values. SfDataPagerThemeData copyWith( - {Brightness brightness, - Color backgroundColor, - Color itemColor, - TextStyle itemTextStyle, - Color selectedItemColor, - TextStyle selectedItemTextStyle, - Color disabledItemColor, - TextStyle disabledItemTextStyle, - Color itemBorderColor, - double itemBorderWidth, - BorderRadiusGeometry itemBorderRadius}) { + {Brightness? brightness, + Color? backgroundColor, + Color? itemColor, + TextStyle? itemTextStyle, + Color? selectedItemColor, + TextStyle? selectedItemTextStyle, + Color? disabledItemColor, + TextStyle? disabledItemTextStyle, + Color? itemBorderColor, + double? itemBorderWidth, + BorderRadiusGeometry? itemBorderRadius}) { return SfDataPagerThemeData.raw( brightness: brightness ?? this.brightness, backgroundColor: backgroundColor ?? this.backgroundColor, @@ -207,14 +207,13 @@ class SfDataPagerThemeData with Diagnosticable { } /// Linearly interpolate between two themes. - static SfDataPagerThemeData lerp( - SfDataPagerThemeData a, SfDataPagerThemeData b, double t) { - assert(t != null); + static SfDataPagerThemeData? lerp( + SfDataPagerThemeData? a, SfDataPagerThemeData? b, double t) { if (a == null && b == null) { return null; } return SfDataPagerThemeData( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), itemColor: Color.lerp(a.itemColor, b.itemColor, t), itemTextStyle: TextStyle.lerp(a.itemTextStyle, b.itemTextStyle, t), selectedItemColor: @@ -239,23 +238,24 @@ class SfDataPagerThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - final SfDataPagerThemeData typedOther = other; - return typedOther.brightness == brightness && - typedOther.itemColor == itemColor && - typedOther.backgroundColor == backgroundColor && - typedOther.itemTextStyle == itemTextStyle && - typedOther.selectedItemColor == selectedItemColor && - typedOther.selectedItemTextStyle == selectedItemTextStyle && - typedOther.disabledItemColor == disabledItemColor && - typedOther.disabledItemTextStyle == disabledItemTextStyle && - typedOther.itemBorderColor == itemBorderColor && - typedOther.itemBorderWidth == itemBorderWidth && - typedOther.itemBorderRadius == itemBorderRadius; + + return other is SfDataPagerThemeData && + other.brightness == brightness && + other.itemColor == itemColor && + other.backgroundColor == backgroundColor && + other.itemTextStyle == itemTextStyle && + other.selectedItemColor == selectedItemColor && + other.selectedItemTextStyle == selectedItemTextStyle && + other.disabledItemColor == disabledItemColor && + other.disabledItemTextStyle == disabledItemTextStyle && + other.itemBorderColor == itemBorderColor && + other.itemBorderWidth == itemBorderWidth && + other.itemBorderRadius == itemBorderRadius; } @override int get hashCode { - final List values = [ + final List values = [ itemColor, backgroundColor, itemTextStyle, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/daterangepicker_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/daterangepicker_theme.dart index 7e28cae2e..b5338e2e4 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/daterangepicker_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/daterangepicker_theme.dart @@ -21,7 +21,8 @@ import '../../theme.dart'; class SfDateRangePickerTheme extends InheritedTheme { /// Constructor for teh calendar theme class, which applies a theme to /// descendant Syncfusion date range picker widgets. - const SfDateRangePickerTheme({Key key, this.data, this.child}) + const SfDateRangePickerTheme( + {Key? key, required this.data, required this.child}) : super(key: key, child: child); /// Specifies the color and typography values for descendant chart widgets. @@ -65,7 +66,7 @@ class SfDateRangePickerTheme extends InheritedTheme { /// Defaults to [SfThemeData.dateRangePickerTheme] if there is no /// [SfDateRangePickerTheme] in the given build context. static SfDateRangePickerThemeData of(BuildContext context) { - final SfDateRangePickerTheme sfDateRangePickerTheme = + final SfDateRangePickerTheme? sfDateRangePickerTheme = context.dependOnInheritedWidgetOfExactType(); return sfDateRangePickerTheme?.data ?? SfTheme.of(context).dateRangePickerThemeData; @@ -77,7 +78,7 @@ class SfDateRangePickerTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SfDateRangePickerTheme ancestorTheme = + final SfDateRangePickerTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); return identical(this, ancestorTheme) ? child @@ -107,31 +108,31 @@ class SfDateRangePickerThemeData with Diagnosticable { /// Create a [SfDateRangePickerThemeData] that's used to configure a /// [SfDateRangePickerTheme]. factory SfDateRangePickerThemeData({ - Brightness brightness, - Color backgroundColor, - Color startRangeSelectionColor, - Color endRangeSelectionColor, - Color headerBackgroundColor, - Color viewHeaderBackgroundColor, - Color todayHighlightColor, - Color selectionColor, - Color rangeSelectionColor, - TextStyle viewHeaderTextStyle, - TextStyle headerTextStyle, - TextStyle trailingDatesTextStyle, - TextStyle leadingCellTextStyle, - TextStyle activeDatesTextStyle, - TextStyle cellTextStyle, - TextStyle rangeSelectionTextStyle, - TextStyle leadingDatesTextStyle, - TextStyle disabledDatesTextStyle, - TextStyle disabledCellTextStyle, - TextStyle selectionTextStyle, - TextStyle blackoutDatesTextStyle, - TextStyle todayTextStyle, - TextStyle todayCellTextStyle, - TextStyle weekendDatesTextStyle, - TextStyle specialDatesTextStyle, + Brightness? brightness, + Color? backgroundColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? headerBackgroundColor, + Color? viewHeaderBackgroundColor, + Color? todayHighlightColor, + Color? selectionColor, + Color? rangeSelectionColor, + TextStyle? viewHeaderTextStyle, + TextStyle? headerTextStyle, + TextStyle? trailingDatesTextStyle, + TextStyle? leadingCellTextStyle, + TextStyle? activeDatesTextStyle, + TextStyle? cellTextStyle, + TextStyle? rangeSelectionTextStyle, + TextStyle? leadingDatesTextStyle, + TextStyle? disabledDatesTextStyle, + TextStyle? disabledCellTextStyle, + TextStyle? selectionTextStyle, + TextStyle? blackoutDatesTextStyle, + TextStyle? todayTextStyle, + TextStyle? todayCellTextStyle, + TextStyle? weekendDatesTextStyle, + TextStyle? specialDatesTextStyle, }) { brightness = brightness ?? Brightness.light; final bool isLight = brightness == Brightness.light; @@ -219,31 +220,31 @@ class SfDateRangePickerThemeData with Diagnosticable { /// create intermediate themes based on two themes created with the /// [SfDateRangePickerThemeData] constructor. const SfDateRangePickerThemeData.raw({ - @required this.brightness, - @required this.backgroundColor, - @required this.viewHeaderTextStyle, - @required this.headerTextStyle, - @required this.trailingDatesTextStyle, - @required this.leadingCellTextStyle, - @required this.activeDatesTextStyle, - @required this.cellTextStyle, - @required this.rangeSelectionTextStyle, - @required this.rangeSelectionColor, - @required this.leadingDatesTextStyle, - @required this.disabledDatesTextStyle, - @required this.disabledCellTextStyle, - @required this.selectionColor, - @required this.selectionTextStyle, - @required this.startRangeSelectionColor, - @required this.endRangeSelectionColor, - @required this.headerBackgroundColor, - @required this.viewHeaderBackgroundColor, - @required this.blackoutDatesTextStyle, - @required this.todayHighlightColor, - @required this.todayTextStyle, - @required this.todayCellTextStyle, - @required this.weekendDatesTextStyle, - @required this.specialDatesTextStyle, + required this.brightness, + required this.backgroundColor, + required this.viewHeaderTextStyle, + required this.headerTextStyle, + required this.trailingDatesTextStyle, + required this.leadingCellTextStyle, + required this.activeDatesTextStyle, + required this.cellTextStyle, + required this.rangeSelectionTextStyle, + required this.rangeSelectionColor, + required this.leadingDatesTextStyle, + required this.disabledDatesTextStyle, + required this.disabledCellTextStyle, + required this.selectionColor, + required this.selectionTextStyle, + required this.startRangeSelectionColor, + required this.endRangeSelectionColor, + required this.headerBackgroundColor, + required this.viewHeaderBackgroundColor, + required this.blackoutDatesTextStyle, + required this.todayHighlightColor, + required this.todayTextStyle, + required this.todayCellTextStyle, + required this.weekendDatesTextStyle, + required this.specialDatesTextStyle, }); /// The brightness of the overall theme of the @@ -528,7 +529,7 @@ class SfDateRangePickerThemeData with Diagnosticable { /// ); /// } /// ``` - final Color selectionColor; + final Color? selectionColor; /// Specify the date picker in-between selected range background color in month view when selection mode as range/multi-range selection. /// @@ -549,7 +550,7 @@ class SfDateRangePickerThemeData with Diagnosticable { /// ); /// } /// ``` - final Color rangeSelectionColor; + final Color? rangeSelectionColor; /// Specify the date picker selected cell text style for the single and /// multiple selection and also for the start and end range of single @@ -594,7 +595,7 @@ class SfDateRangePickerThemeData with Diagnosticable { /// ); /// } /// ``` - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; /// Specify the date picker end date of selected range background color in month view when selection mode as range/multi-range selection. /// @@ -615,7 +616,7 @@ class SfDateRangePickerThemeData with Diagnosticable { /// ); /// } /// ``` - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; /// Specify the date picker header background color. /// @@ -678,7 +679,7 @@ class SfDateRangePickerThemeData with Diagnosticable { /// ); /// } /// ``` - final TextStyle blackoutDatesTextStyle; + final TextStyle? blackoutDatesTextStyle; /// Specify the date picker today highlight color. /// @@ -699,7 +700,7 @@ class SfDateRangePickerThemeData with Diagnosticable { /// ); /// } /// ``` - final Color todayHighlightColor; + final Color? todayHighlightColor; /// Specify the date picker today date month cell text style. /// @@ -763,7 +764,7 @@ class SfDateRangePickerThemeData with Diagnosticable { /// ); /// } /// ``` - final TextStyle weekendDatesTextStyle; + final TextStyle? weekendDatesTextStyle; /// Specify the date picker special dates text style in month view /// @@ -789,31 +790,31 @@ class SfDateRangePickerThemeData with Diagnosticable { /// Creates a copy of this theme but with the given /// fields replaced with the new values. SfDateRangePickerThemeData copyWith({ - Brightness brightness, - TextStyle viewHeaderTextStyle, - Color backgroundColor, - TextStyle headerTextStyle, - TextStyle trailingDatesTextStyle, - TextStyle leadingCellTextStyle, - TextStyle activeDatesTextStyle, - TextStyle cellTextStyle, - TextStyle rangeSelectionTextStyle, - TextStyle leadingDatesTextStyle, - TextStyle disabledDatesTextStyle, - TextStyle disabledCellTextStyle, - Color selectionColor, - Color rangeSelectionColor, - TextStyle selectionTextStyle, - Color startRangeSelectionColor, - Color endRangeSelectionColor, - Color headerBackgroundColor, - Color viewHeaderBackgroundColor, - TextStyle blackoutDatesTextStyle, - Color todayHighlightColor, - TextStyle todayTextStyle, - TextStyle todayCellTextStyle, - TextStyle weekendDatesTextStyle, - TextStyle specialDatesTextStyle, + Brightness? brightness, + TextStyle? viewHeaderTextStyle, + Color? backgroundColor, + TextStyle? headerTextStyle, + TextStyle? trailingDatesTextStyle, + TextStyle? leadingCellTextStyle, + TextStyle? activeDatesTextStyle, + TextStyle? cellTextStyle, + TextStyle? rangeSelectionTextStyle, + TextStyle? leadingDatesTextStyle, + TextStyle? disabledDatesTextStyle, + TextStyle? disabledCellTextStyle, + Color? selectionColor, + Color? rangeSelectionColor, + TextStyle? selectionTextStyle, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? headerBackgroundColor, + Color? viewHeaderBackgroundColor, + TextStyle? blackoutDatesTextStyle, + Color? todayHighlightColor, + TextStyle? todayTextStyle, + TextStyle? todayCellTextStyle, + TextStyle? weekendDatesTextStyle, + TextStyle? specialDatesTextStyle, }) { return SfDateRangePickerThemeData.raw( brightness: brightness ?? this.brightness, @@ -857,14 +858,13 @@ class SfDateRangePickerThemeData with Diagnosticable { } /// Linearly interpolate between two themes. - static SfDateRangePickerThemeData lerp( - SfDateRangePickerThemeData a, SfDateRangePickerThemeData b, double t) { - assert(t != null); + static SfDateRangePickerThemeData? lerp( + SfDateRangePickerThemeData? a, SfDateRangePickerThemeData? b, double t) { if (a == null && b == null) { return null; } return SfDateRangePickerThemeData( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), rangeSelectionColor: Color.lerp(a.rangeSelectionColor, b.rangeSelectionColor, t), selectionColor: Color.lerp(a.selectionColor, b.selectionColor, t), @@ -889,36 +889,37 @@ class SfDateRangePickerThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - final SfDateRangePickerThemeData typedOther = other; - return typedOther.viewHeaderTextStyle == viewHeaderTextStyle && - typedOther.backgroundColor == backgroundColor && - typedOther.headerTextStyle == headerTextStyle && - typedOther.trailingDatesTextStyle == trailingDatesTextStyle && - typedOther.leadingCellTextStyle == leadingCellTextStyle && - typedOther.activeDatesTextStyle == activeDatesTextStyle && - typedOther.cellTextStyle == cellTextStyle && - typedOther.rangeSelectionTextStyle == rangeSelectionTextStyle && - typedOther.rangeSelectionColor == rangeSelectionColor && - typedOther.leadingDatesTextStyle == leadingDatesTextStyle && - typedOther.disabledDatesTextStyle == disabledDatesTextStyle && - typedOther.disabledCellTextStyle == disabledCellTextStyle && - typedOther.selectionColor == selectionColor && - typedOther.selectionTextStyle == selectionTextStyle && - typedOther.startRangeSelectionColor == startRangeSelectionColor && - typedOther.endRangeSelectionColor == endRangeSelectionColor && - typedOther.headerBackgroundColor == headerBackgroundColor && - typedOther.viewHeaderBackgroundColor == viewHeaderBackgroundColor && - typedOther.blackoutDatesTextStyle == blackoutDatesTextStyle && - typedOther.todayHighlightColor == todayHighlightColor && - typedOther.todayTextStyle == todayTextStyle && - typedOther.todayCellTextStyle == todayCellTextStyle && - typedOther.weekendDatesTextStyle == weekendDatesTextStyle && - typedOther.specialDatesTextStyle == specialDatesTextStyle; + + return other is SfDateRangePickerThemeData && + other.viewHeaderTextStyle == viewHeaderTextStyle && + other.backgroundColor == backgroundColor && + other.headerTextStyle == headerTextStyle && + other.trailingDatesTextStyle == trailingDatesTextStyle && + other.leadingCellTextStyle == leadingCellTextStyle && + other.activeDatesTextStyle == activeDatesTextStyle && + other.cellTextStyle == cellTextStyle && + other.rangeSelectionTextStyle == rangeSelectionTextStyle && + other.rangeSelectionColor == rangeSelectionColor && + other.leadingDatesTextStyle == leadingDatesTextStyle && + other.disabledDatesTextStyle == disabledDatesTextStyle && + other.disabledCellTextStyle == disabledCellTextStyle && + other.selectionColor == selectionColor && + other.selectionTextStyle == selectionTextStyle && + other.startRangeSelectionColor == startRangeSelectionColor && + other.endRangeSelectionColor == endRangeSelectionColor && + other.headerBackgroundColor == headerBackgroundColor && + other.viewHeaderBackgroundColor == viewHeaderBackgroundColor && + other.blackoutDatesTextStyle == blackoutDatesTextStyle && + other.todayHighlightColor == todayHighlightColor && + other.todayTextStyle == todayTextStyle && + other.todayCellTextStyle == todayCellTextStyle && + other.weekendDatesTextStyle == weekendDatesTextStyle && + other.specialDatesTextStyle == specialDatesTextStyle; } @override int get hashCode { - final List values = [ + final List values = [ viewHeaderTextStyle, backgroundColor, headerTextStyle, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/gauges_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/gauges_theme.dart index 0f5acd137..bb6782a01 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/gauges_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/gauges_theme.dart @@ -29,7 +29,7 @@ import '../../theme.dart'; /// class SfGaugeTheme extends InheritedTheme { /// Initialize the gauge theme - const SfGaugeTheme({Key key, this.data, this.child}) + const SfGaugeTheme({Key? key, required this.data, required this.child}) : super(key: key, child: child); /// Specifies the color and typography values for descendant gauges widgets. @@ -73,8 +73,8 @@ class SfGaugeTheme extends InheritedTheme { /// Defaults to [SfGaugeTheme.gaugeThemeData] /// if there is no [SfGaugeTheme] in the given /// build context. - static SfGaugeThemeData of(BuildContext context) { - final SfGaugeTheme sfGaugeTheme = + static SfGaugeThemeData? of(BuildContext context) { + final SfGaugeTheme? sfGaugeTheme = context.dependOnInheritedWidgetOfExactType(); return sfGaugeTheme?.data ?? SfTheme.of(context).gaugeThemeData; } @@ -84,7 +84,7 @@ class SfGaugeTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SfGaugeTheme ancestorTheme = + final SfGaugeTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); return identical(this, ancestorTheme) ? child @@ -120,24 +120,24 @@ class SfGaugeTheme extends InheritedTheme { class SfGaugeThemeData with Diagnosticable { /// Initialize the gauge theme data factory SfGaugeThemeData({ - Brightness brightness, - Color backgroundColor, - Color titleColor, - Color axisLabelColor, - Color axisLineColor, - Color majorTickColor, - Color minorTickColor, - Color markerColor, - Color markerBorderColor, - Color needleColor, - Color knobColor, - Color knobBorderColor, - Color tailColor, - Color tailBorderColor, - Color rangePointerColor, - Color rangeColor, - Color titleBorderColor, - Color titleBackgroundColor, + Brightness? brightness, + Color? backgroundColor, + Color? titleColor, + Color? axisLabelColor, + Color? axisLineColor, + Color? majorTickColor, + Color? minorTickColor, + Color? markerColor, + Color? markerBorderColor, + Color? needleColor, + Color? knobColor, + Color? knobBorderColor, + Color? tailColor, + Color? tailBorderColor, + Color? rangePointerColor, + Color? rangeColor, + Color? titleBorderColor, + Color? titleBackgroundColor, }) { brightness = brightness ?? Brightness.light; final bool isLight = brightness == Brightness.light; @@ -193,24 +193,24 @@ class SfGaugeThemeData with Diagnosticable { /// [SfGaugeThemeData] constructor. /// const SfGaugeThemeData.raw( - {@required this.brightness, - @required this.backgroundColor, - @required this.titleColor, - @required this.axisLabelColor, - @required this.axisLineColor, - @required this.majorTickColor, - @required this.minorTickColor, - @required this.markerColor, - @required this.markerBorderColor, - @required this.needleColor, - @required this.knobColor, - @required this.knobBorderColor, - @required this.tailColor, - @required this.tailBorderColor, - @required this.rangePointerColor, - @required this.rangeColor, - @required this.titleBorderColor, - @required this.titleBackgroundColor}); + {required this.brightness, + required this.backgroundColor, + required this.titleColor, + required this.axisLabelColor, + required this.axisLineColor, + required this.majorTickColor, + required this.minorTickColor, + required this.markerColor, + required this.markerBorderColor, + required this.needleColor, + required this.knobColor, + required this.knobBorderColor, + required this.tailColor, + required this.tailBorderColor, + required this.rangePointerColor, + required this.rangeColor, + required this.titleBorderColor, + required this.titleBackgroundColor}); /// The brightness of the overall theme of the /// application for the gauge widgets. @@ -732,24 +732,24 @@ class SfGaugeThemeData with Diagnosticable { /// Creates a copy of this gauge theme data object with the matching fields /// replaced with the non-null parameter values. SfGaugeThemeData copyWith({ - Brightness brightness, - Color backgroundColor, - Color titleColor, - Color axisLabelColor, - Color axisLineColor, - Color majorTickColor, - Color minorTickColor, - Color markerColor, - Color markerBorderColor, - Color needleColor, - Color knobColor, - Color knobBorderColor, - Color tailColor, - Color tailBorderColor, - Color rangePointerColor, - Color rangeColor, - Color titleBorderColor, - Color titleBackgroundColor, + Brightness? brightness, + Color? backgroundColor, + Color? titleColor, + Color? axisLabelColor, + Color? axisLineColor, + Color? majorTickColor, + Color? minorTickColor, + Color? markerColor, + Color? markerBorderColor, + Color? needleColor, + Color? knobColor, + Color? knobBorderColor, + Color? tailColor, + Color? tailBorderColor, + Color? rangePointerColor, + Color? rangeColor, + Color? titleBorderColor, + Color? titleBackgroundColor, }) { return SfGaugeThemeData.raw( brightness: brightness ?? this.brightness, @@ -774,14 +774,13 @@ class SfGaugeThemeData with Diagnosticable { } /// Returns the gauge theme data - static SfGaugeThemeData lerp( - SfGaugeThemeData a, SfGaugeThemeData b, double t) { - assert(t != null); + static SfGaugeThemeData? lerp( + SfGaugeThemeData? a, SfGaugeThemeData? b, double t) { if (a == null && b == null) { return null; } return SfGaugeThemeData( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), titleColor: Color.lerp(a.titleColor, b.titleColor, t), axisLabelColor: Color.lerp(a.axisLabelColor, b.axisLabelColor, t), axisLineColor: Color.lerp(a.axisLineColor, b.axisLineColor, t), @@ -812,24 +811,25 @@ class SfGaugeThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - final SfGaugeThemeData typedOther = other; - return typedOther.backgroundColor == backgroundColor && - typedOther.titleColor == titleColor && - typedOther.axisLabelColor == axisLabelColor && - typedOther.axisLineColor == axisLineColor && - typedOther.majorTickColor == majorTickColor && - typedOther.minorTickColor == minorTickColor && - typedOther.markerColor == markerColor && - typedOther.markerBorderColor == markerBorderColor && - typedOther.needleColor == needleColor && - typedOther.knobColor == knobColor && - typedOther.knobBorderColor == knobBorderColor && - typedOther.tailColor == tailColor && - typedOther.tailBorderColor == tailBorderColor && - typedOther.rangePointerColor == rangePointerColor && - typedOther.rangeColor == rangeColor && - typedOther.titleBorderColor == titleBorderColor && - typedOther.titleBackgroundColor == titleBackgroundColor; + + return other is SfGaugeThemeData && + other.backgroundColor == backgroundColor && + other.titleColor == titleColor && + other.axisLabelColor == axisLabelColor && + other.axisLineColor == axisLineColor && + other.majorTickColor == majorTickColor && + other.minorTickColor == minorTickColor && + other.markerColor == markerColor && + other.markerBorderColor == markerBorderColor && + other.needleColor == needleColor && + other.knobColor == knobColor && + other.knobBorderColor == knobBorderColor && + other.tailColor == tailColor && + other.tailBorderColor == tailBorderColor && + other.rangePointerColor == rangePointerColor && + other.rangeColor == rangeColor && + other.titleBorderColor == titleBorderColor && + other.titleBackgroundColor == titleBackgroundColor; } @override diff --git a/packages/syncfusion_flutter_core/lib/src/theme/maps_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/maps_theme.dart index cd273b024..bd4f1ddf4 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/maps_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/maps_theme.dart @@ -23,7 +23,7 @@ class SfMapsTheme extends InheritedTheme { /// Applies the given theme [data] to [child]. /// /// The [data] and [child] arguments must not be null. - const SfMapsTheme({Key key, this.data, this.child}) + const SfMapsTheme({Key? key, required this.data, required this.child}) : super(key: key, child: child); /// Specifies the color and typography values for descendant maps widgets. @@ -64,8 +64,8 @@ class SfMapsTheme extends InheritedTheme { /// /// Defaults to [SfThemeData.mapsThemeData] if there is no /// [SfMapsTheme] in the given build context. - static SfMapsThemeData of(BuildContext context) { - final SfMapsTheme mapsTheme = + static SfMapsThemeData? of(BuildContext context) { + final SfMapsTheme? mapsTheme = context.dependOnInheritedWidgetOfExactType(); return mapsTheme?.data ?? SfTheme.of(context).mapsThemeData; } @@ -75,7 +75,7 @@ class SfMapsTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SfMapsTheme ancestorTheme = + final SfMapsTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); return identical(this, ancestorTheme) ? child @@ -130,35 +130,34 @@ class SfMapsThemeData with Diagnosticable { /// /// If any of the values are null, the default values will be set. factory SfMapsThemeData({ - Brightness brightness, - TextStyle titleTextStyle, - Color layerColor, - Color layerStrokeColor, - double layerStrokeWidth, - Color shapeHoverColor, - Color shapeHoverStrokeColor, - double shapeHoverStrokeWidth, - TextStyle legendTextStyle, - Color markerIconColor, - Color markerIconStrokeColor, - double markerIconStrokeWidth, - TextStyle dataLabelTextStyle, - Color bubbleColor, - Color bubbleStrokeColor, - double bubbleStrokeWidth, - Color bubbleHoverColor, - Color bubbleHoverStrokeColor, - double bubbleHoverStrokeWidth, - Color selectionColor, - Color selectionStrokeColor, - double selectionStrokeWidth, - Color tooltipColor, - Color tooltipStrokeColor, - double tooltipStrokeWidth, - BorderRadiusGeometry tooltipBorderRadius, - Color toggledItemColor, - Color toggledItemStrokeColor, - double toggledItemStrokeWidth, + Brightness? brightness, + Color? layerColor, + Color? layerStrokeColor, + double? layerStrokeWidth, + Color? shapeHoverColor, + Color? shapeHoverStrokeColor, + double? shapeHoverStrokeWidth, + TextStyle? legendTextStyle, + Color? markerIconColor, + Color? markerIconStrokeColor, + double? markerIconStrokeWidth, + TextStyle? dataLabelTextStyle, + Color? bubbleColor, + Color? bubbleStrokeColor, + double? bubbleStrokeWidth, + Color? bubbleHoverColor, + Color? bubbleHoverStrokeColor, + double? bubbleHoverStrokeWidth, + Color? selectionColor, + Color? selectionStrokeColor, + double? selectionStrokeWidth, + Color? tooltipColor, + Color? tooltipStrokeColor, + double? tooltipStrokeWidth, + BorderRadiusGeometry? tooltipBorderRadius, + Color? toggledItemColor, + Color? toggledItemStrokeColor, + double? toggledItemStrokeWidth, }) { brightness = brightness ?? Brightness.light; final bool isLight = brightness == Brightness.light; @@ -199,7 +198,6 @@ class SfMapsThemeData with Diagnosticable { return SfMapsThemeData.raw( brightness: brightness, - titleTextStyle: titleTextStyle, layerColor: layerColor, layerStrokeColor: layerStrokeColor, shapeHoverColor: shapeHoverColor, @@ -237,35 +235,34 @@ class SfMapsThemeData with Diagnosticable { /// create intermediate themes based on two themes created with the /// [SfMapsThemeData] constructor. const SfMapsThemeData.raw({ - @required this.brightness, - @required this.titleTextStyle, - @required this.layerColor, - @required this.layerStrokeColor, - @required this.layerStrokeWidth, - @required this.shapeHoverColor, - @required this.shapeHoverStrokeColor, - @required this.shapeHoverStrokeWidth, - @required this.legendTextStyle, - @required this.markerIconColor, - @required this.markerIconStrokeColor, - @required this.markerIconStrokeWidth, - @required this.dataLabelTextStyle, - @required this.bubbleColor, - @required this.bubbleStrokeColor, - @required this.bubbleStrokeWidth, - @required this.bubbleHoverColor, - @required this.bubbleHoverStrokeColor, - @required this.bubbleHoverStrokeWidth, - @required this.selectionColor, - @required this.selectionStrokeColor, - @required this.selectionStrokeWidth, - @required this.tooltipColor, - @required this.tooltipStrokeColor, - @required this.tooltipStrokeWidth, - @required this.tooltipBorderRadius, - @required this.toggledItemColor, - @required this.toggledItemStrokeColor, - @required this.toggledItemStrokeWidth, + required this.brightness, + required this.layerColor, + required this.layerStrokeColor, + required this.layerStrokeWidth, + required this.shapeHoverColor, + required this.shapeHoverStrokeColor, + required this.shapeHoverStrokeWidth, + required this.legendTextStyle, + required this.markerIconColor, + required this.markerIconStrokeColor, + required this.markerIconStrokeWidth, + required this.dataLabelTextStyle, + required this.bubbleColor, + required this.bubbleStrokeColor, + required this.bubbleStrokeWidth, + required this.bubbleHoverColor, + required this.bubbleHoverStrokeColor, + required this.bubbleHoverStrokeWidth, + required this.selectionColor, + required this.selectionStrokeColor, + required this.selectionStrokeWidth, + required this.tooltipColor, + required this.tooltipStrokeColor, + required this.tooltipStrokeWidth, + required this.tooltipBorderRadius, + required this.toggledItemColor, + required this.toggledItemStrokeColor, + required this.toggledItemStrokeWidth, }); /// The brightness of the overall theme of the @@ -295,27 +292,6 @@ class SfMapsThemeData with Diagnosticable { /// ``` final Brightness brightness; - /// Specifies the text style of the title. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// mapsThemeData: SfMapsThemeData( - /// titleTextStyle: TextStyle( - /// decoration: TextDecoration.underline, fontSize: 18) - /// ) - /// ), - /// child: SfMaps(), - /// ), - /// ) - /// ); - /// } - /// ``` - final TextStyle titleTextStyle; - /// Specifies the fill color for maps layer. /// /// ```dart @@ -406,7 +382,7 @@ class SfMapsThemeData with Diagnosticable { /// See also: /// * [shapeHoverStrokeColor], [shapeHoverStrokeWidth], to set the stroke /// for the hovered shapes. - final Color shapeHoverColor; + final Color? shapeHoverColor; /// Specifies the color which should apply on the stroke of the shapes while /// hovering in the web platform. @@ -428,7 +404,7 @@ class SfMapsThemeData with Diagnosticable { /// ); /// } /// ``` - final Color shapeHoverStrokeColor; + final Color? shapeHoverStrokeColor; /// Specifies the stroke which should apply on the stroke of the shapes while /// hovering in the web platform. @@ -450,7 +426,7 @@ class SfMapsThemeData with Diagnosticable { /// ); /// } /// ``` - final double shapeHoverStrokeWidth; + final double? shapeHoverStrokeWidth; /// Specifies the text style of the legend. /// @@ -471,7 +447,7 @@ class SfMapsThemeData with Diagnosticable { /// ); /// } /// ``` - final TextStyle legendTextStyle; + final TextStyle? legendTextStyle; /// Specifies the fill color for marker icon. /// @@ -511,7 +487,7 @@ class SfMapsThemeData with Diagnosticable { /// ); /// } /// ``` - final Color markerIconStrokeColor; + final Color? markerIconStrokeColor; /// Specifies the stroke width for marker icon. /// @@ -551,7 +527,7 @@ class SfMapsThemeData with Diagnosticable { /// ); /// } /// ``` - final TextStyle dataLabelTextStyle; + final TextStyle? dataLabelTextStyle; /// Specifies the fill color for bubble. /// @@ -643,7 +619,7 @@ class SfMapsThemeData with Diagnosticable { /// See also: /// * [bubbleHoverStrokeColor], [bubbleHoverStrokeWidth], to set the stroke /// for the hovered shapes. - final Color bubbleHoverColor; + final Color? bubbleHoverColor; /// Specifies the color which should apply on the stroke of the bubbles while /// hovering in the web platform. @@ -665,7 +641,7 @@ class SfMapsThemeData with Diagnosticable { /// ); /// } /// ``` - final Color bubbleHoverStrokeColor; + final Color? bubbleHoverStrokeColor; /// Specifies the stroke which should apply on the stroke of the bubbles while /// hovering in the web platform. @@ -687,7 +663,7 @@ class SfMapsThemeData with Diagnosticable { /// ); /// } /// ``` - final double bubbleHoverStrokeWidth; + final double? bubbleHoverStrokeWidth; /// Specifies the fill color for selected shape. /// @@ -787,7 +763,7 @@ class SfMapsThemeData with Diagnosticable { /// ); /// } /// ``` - final Color tooltipStrokeColor; + final Color? tooltipStrokeColor; /// Specifies the stroke width for tooltip. /// @@ -905,44 +881,42 @@ class SfMapsThemeData with Diagnosticable { /// ); /// } /// ``` - final double toggledItemStrokeWidth; + final double? toggledItemStrokeWidth; /// Creates a copy of this theme but with the given /// fields replaced with the new values. SfMapsThemeData copyWith({ - Brightness brightness, - TextStyle titleTextStyle, - Color layerColor, - Color layerStrokeColor, - double layerStrokeWidth, - Color shapeHoverColor, - Color shapeHoverStrokeColor, - double shapeHoverStrokeWidth, - TextStyle legendTextStyle, - Color markerIconColor, - Color markerIconStrokeColor, - double markerIconStrokeWidth, - TextStyle dataLabelTextStyle, - Color bubbleColor, - Color bubbleStrokeColor, - double bubbleStrokeWidth, - Color bubbleHoverColor, - Color bubbleHoverStrokeColor, - double bubbleHoverStrokeWidth, - Color selectionColor, - Color selectionStrokeColor, - double selectionStrokeWidth, - Color tooltipColor, - Color tooltipStrokeColor, - double tooltipStrokeWidth, - BorderRadiusGeometry tooltipBorderRadius, - Color toggledItemColor, - Color toggledItemStrokeColor, - double toggledItemStrokeWidth, + Brightness? brightness, + Color? layerColor, + Color? layerStrokeColor, + double? layerStrokeWidth, + Color? shapeHoverColor, + Color? shapeHoverStrokeColor, + double? shapeHoverStrokeWidth, + TextStyle? legendTextStyle, + Color? markerIconColor, + Color? markerIconStrokeColor, + double? markerIconStrokeWidth, + TextStyle? dataLabelTextStyle, + Color? bubbleColor, + Color? bubbleStrokeColor, + double? bubbleStrokeWidth, + Color? bubbleHoverColor, + Color? bubbleHoverStrokeColor, + double? bubbleHoverStrokeWidth, + Color? selectionColor, + Color? selectionStrokeColor, + double? selectionStrokeWidth, + Color? tooltipColor, + Color? tooltipStrokeColor, + double? tooltipStrokeWidth, + BorderRadiusGeometry? tooltipBorderRadius, + Color? toggledItemColor, + Color? toggledItemStrokeColor, + double? toggledItemStrokeWidth, }) { return SfMapsThemeData.raw( brightness: brightness ?? this.brightness, - titleTextStyle: titleTextStyle ?? this.titleTextStyle, layerColor: layerColor ?? this.layerColor, layerStrokeColor: layerStrokeColor ?? this.layerStrokeColor, layerStrokeWidth: layerStrokeWidth ?? this.layerStrokeWidth, @@ -984,14 +958,13 @@ class SfMapsThemeData with Diagnosticable { /// Linearly interpolate between two themes. /// /// The arguments must not be null. - static SfMapsThemeData lerp(SfMapsThemeData a, SfMapsThemeData b, double t) { - assert(t != null); + static SfMapsThemeData? lerp( + SfMapsThemeData? a, SfMapsThemeData? b, double t) { if (a == null && b == null) { return null; } return SfMapsThemeData( - titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t), - layerColor: Color.lerp(a.layerColor, b.layerColor, t), + layerColor: Color.lerp(a!.layerColor, b!.layerColor, t), layerStrokeColor: Color.lerp(a.layerStrokeColor, b.layerStrokeColor, t), layerStrokeWidth: lerpDouble(a.layerStrokeWidth, b.layerStrokeWidth, t), shapeHoverColor: Color.lerp(a.shapeHoverColor, b.shapeHoverColor, t), @@ -1046,7 +1019,6 @@ class SfMapsThemeData with Diagnosticable { return false; } return other is SfMapsThemeData && - other.titleTextStyle == titleTextStyle && other.layerColor == layerColor && other.layerStrokeColor == layerStrokeColor && other.layerStrokeWidth == layerStrokeWidth && @@ -1078,8 +1050,7 @@ class SfMapsThemeData with Diagnosticable { @override int get hashCode { - final List values = [ - titleTextStyle, + final List values = [ layerColor, layerStrokeColor, layerStrokeWidth, @@ -1117,9 +1088,6 @@ class SfMapsThemeData with Diagnosticable { final SfMapsThemeData defaultData = SfMapsThemeData(); properties.add(EnumProperty('brightness', brightness, defaultValue: defaultData.brightness)); - properties.add(DiagnosticsProperty( - 'titleTextStyle', titleTextStyle, - defaultValue: defaultData.titleTextStyle)); properties.add(ColorProperty('layerColor', layerColor, defaultValue: defaultData.layerColor)); properties.add(ColorProperty('layerStrokeColor', layerStrokeColor, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/pdfviewer_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/pdfviewer_theme.dart index e1af0a22c..71d811644 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/pdfviewer_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/pdfviewer_theme.dart @@ -22,7 +22,7 @@ import '../../theme.dart'; /// ``` class SfPdfViewerTheme extends InheritedTheme { /// Creates an argument constructor of [SfPdfViewerTheme] class. - const SfPdfViewerTheme({Key key, this.data, this.child}) + const SfPdfViewerTheme({Key? key, required this.data, required this.child}) : super(key: key, child: child); /// Specifies the color and typography values for descendant [SfPdfViewer] widgets. @@ -68,8 +68,8 @@ class SfPdfViewerTheme extends InheritedTheme { /// Defaults to [SfThemeData.pdfViewerThemeData] if there is no [SfPdfViewerTheme] /// in the given build context. /// - static SfPdfViewerThemeData of(BuildContext context) { - final SfPdfViewerTheme pdfViewerTheme = + static SfPdfViewerThemeData? of(BuildContext context) { + final SfPdfViewerTheme? pdfViewerTheme = context.dependOnInheritedWidgetOfExactType(); return pdfViewerTheme?.data ?? SfTheme.of(context).pdfViewerThemeData; } @@ -79,7 +79,7 @@ class SfPdfViewerTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SfPdfViewerTheme ancestorTheme = + final SfPdfViewerTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); return identical(this, ancestorTheme) ? child @@ -109,13 +109,13 @@ class SfPdfViewerTheme extends InheritedTheme { class SfPdfViewerThemeData with Diagnosticable { /// Creating an argument constructor of SfPdfViewerThemeData class. factory SfPdfViewerThemeData( - {Brightness brightness, - Color backgroundColor, - Color progressBarColor, - PdfScrollStatusStyle scrollStatusStyle, - PdfScrollHeadStyle scrollHeadStyle, - PdfBookmarkViewStyle bookmarkViewStyle, - PdfPaginationDialogStyle paginationDialogStyle}) { + {Brightness? brightness, + Color? backgroundColor, + Color? progressBarColor, + PdfScrollStatusStyle? scrollStatusStyle, + PdfScrollHeadStyle? scrollHeadStyle, + PdfBookmarkViewStyle? bookmarkViewStyle, + PdfPaginationDialogStyle? paginationDialogStyle}) { brightness = brightness ?? Brightness.light; final bool isLight = brightness == Brightness.light; backgroundColor ??= isLight ? Color(0xFFD6D6D6) : Color(0xFF303030); @@ -246,13 +246,13 @@ class SfPdfViewerThemeData with Diagnosticable { /// [SfPdfViewerThemeData] constructor. /// const SfPdfViewerThemeData.raw({ - @required this.brightness, - @required this.backgroundColor, - @required this.progressBarColor, - @required this.scrollStatusStyle, - @required this.scrollHeadStyle, - @required this.bookmarkViewStyle, - @required this.paginationDialogStyle, + required this.brightness, + required this.backgroundColor, + required this.progressBarColor, + required this.scrollStatusStyle, + required this.scrollHeadStyle, + required this.bookmarkViewStyle, + required this.paginationDialogStyle, }); /// The brightness of the overall theme of the @@ -326,7 +326,7 @@ class SfPdfViewerThemeData with Diagnosticable { /// ); /// } ///``` - final Color progressBarColor; + final Color? progressBarColor; /// Specifies the scroll status style of [SfPdfViewer] widget. /// @@ -444,13 +444,13 @@ class SfPdfViewerThemeData with Diagnosticable { /// Creates a copy of this [SfPdfViewer] theme data object with the matching fields /// replaced with the non-null parameter values. SfPdfViewerThemeData copyWith( - {Brightness brightness, - Color backgroundColor, - Color progressBarColor, - PdfScrollStatusStyle scrollStatusStyle, - PdfScrollHeadStyle scrollHeadStyle, - PdfBookmarkViewStyle bookmarkViewStyle, - PdfPaginationDialogStyle paginationDialogStyle}) { + {Brightness? brightness, + Color? backgroundColor, + Color? progressBarColor, + PdfScrollStatusStyle? scrollStatusStyle, + PdfScrollHeadStyle? scrollHeadStyle, + PdfBookmarkViewStyle? bookmarkViewStyle, + PdfPaginationDialogStyle? paginationDialogStyle}) { return SfPdfViewerThemeData.raw( brightness: brightness ?? this.brightness, backgroundColor: backgroundColor ?? this.backgroundColor, @@ -463,14 +463,13 @@ class SfPdfViewerThemeData with Diagnosticable { } /// Linearly interpolate between two themes. - static SfPdfViewerThemeData lerp( - SfPdfViewerThemeData a, SfPdfViewerThemeData b, double t) { - assert(t != null); + static SfPdfViewerThemeData? lerp( + SfPdfViewerThemeData? a, SfPdfViewerThemeData? b, double t) { if (a == null && b == null) { return null; } return SfPdfViewerThemeData( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), progressBarColor: Color.lerp(a.progressBarColor, b.progressBarColor, t), scrollStatusStyle: PdfScrollStatusStyle.lerp( a.scrollStatusStyle, b.scrollStatusStyle, t), @@ -490,19 +489,20 @@ class SfPdfViewerThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - final SfPdfViewerThemeData typedOther = other; - return typedOther.brightness == brightness && - typedOther.backgroundColor == backgroundColor && - typedOther.progressBarColor == progressBarColor && - typedOther.scrollStatusStyle == scrollStatusStyle && - typedOther.scrollHeadStyle == scrollHeadStyle && - typedOther.bookmarkViewStyle == bookmarkViewStyle && - typedOther.paginationDialogStyle == paginationDialogStyle; + + return other is SfPdfViewerThemeData && + other.brightness == brightness && + other.backgroundColor == backgroundColor && + other.progressBarColor == progressBarColor && + other.scrollStatusStyle == scrollStatusStyle && + other.scrollHeadStyle == scrollHeadStyle && + other.bookmarkViewStyle == bookmarkViewStyle && + other.paginationDialogStyle == paginationDialogStyle; } @override int get hashCode { - final List values = [ + final List values = [ backgroundColor, progressBarColor, scrollStatusStyle, @@ -545,14 +545,14 @@ class PdfScrollStatusStyle { const PdfScrollStatusStyle({this.backgroundColor, this.pageInfoTextStyle}); /// The background color of scroll status in [SfPdfViewer]. - final Color backgroundColor; + final Color? backgroundColor; /// The style for the page information text of scroll status in [SfPdfViewer]. - final TextStyle pageInfoTextStyle; + final TextStyle? pageInfoTextStyle; @override int get hashCode { - final List values = [backgroundColor, pageInfoTextStyle]; + final List values = [backgroundColor, pageInfoTextStyle]; return hashList(values); } @@ -570,14 +570,13 @@ class PdfScrollStatusStyle { } /// Linearly interpolate between two styles. - static PdfScrollStatusStyle lerp( - PdfScrollStatusStyle a, PdfScrollStatusStyle b, double t) { - assert(t != null); + static PdfScrollStatusStyle? lerp( + PdfScrollStatusStyle? a, PdfScrollStatusStyle? b, double t) { if (a == null && b == null) { return null; } return PdfScrollStatusStyle( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), pageInfoTextStyle: TextStyle.lerp(a.pageInfoTextStyle, b.pageInfoTextStyle, t)); } @@ -590,14 +589,17 @@ class PdfScrollHeadStyle { const PdfScrollHeadStyle({this.backgroundColor, this.pageNumberTextStyle}); /// The background color of scroll head in [SfPdfViewer]. - final Color backgroundColor; + final Color? backgroundColor; /// The style for the page number text of scroll head in [SfPdfViewer]. - final TextStyle pageNumberTextStyle; + final TextStyle? pageNumberTextStyle; @override int get hashCode { - final List values = [backgroundColor, pageNumberTextStyle]; + final List values = [ + backgroundColor, + pageNumberTextStyle + ]; return hashList(values); } @@ -615,14 +617,13 @@ class PdfScrollHeadStyle { } /// Linearly interpolate between two styles. - static PdfScrollHeadStyle lerp( - PdfScrollHeadStyle a, PdfScrollHeadStyle b, double t) { - assert(t != null); + static PdfScrollHeadStyle? lerp( + PdfScrollHeadStyle? a, PdfScrollHeadStyle? b, double t) { if (a == null && b == null) { return null; } return PdfScrollHeadStyle( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), pageNumberTextStyle: TextStyle.lerp(a.pageNumberTextStyle, b.pageNumberTextStyle, t)); } @@ -644,35 +645,35 @@ class PdfBookmarkViewStyle { this.headerTextStyle}); /// The background color of bookmark view in [SfPdfViewer]. - final Color backgroundColor; + final Color? backgroundColor; /// The header bar color of bookmark view in [SfPdfViewer]. - final Color headerBarColor; + final Color? headerBarColor; /// The close icon color of bookmark view in [SfPdfViewer]. - final Color closeIconColor; + final Color? closeIconColor; /// The back icon color of bookmark view in [SfPdfViewer]. - final Color backIconColor; + final Color? backIconColor; /// The navigation icon color of bookmark view in [SfPdfViewer]. - final Color navigationIconColor; + final Color? navigationIconColor; /// The selection color of bookmark item in [SfPdfViewer]. - final Color selectionColor; + final Color? selectionColor; /// The separator color of bookmark item title in [SfPdfViewer]. - final Color titleSeparatorColor; + final Color? titleSeparatorColor; /// The style for the title text of bookmark items in [SfPdfViewer]. - final TextStyle titleTextStyle; + final TextStyle? titleTextStyle; /// The style for the header text of bookmark in [SfPdfViewer]. - final TextStyle headerTextStyle; + final TextStyle? headerTextStyle; @override int get hashCode { - final List values = [ + final List values = [ backgroundColor, headerBarColor, closeIconColor, @@ -707,14 +708,13 @@ class PdfBookmarkViewStyle { } /// Linearly interpolate between two styles. - static PdfBookmarkViewStyle lerp( - PdfBookmarkViewStyle a, PdfBookmarkViewStyle b, double t) { - assert(t != null); + static PdfBookmarkViewStyle? lerp( + PdfBookmarkViewStyle? a, PdfBookmarkViewStyle? b, double t) { if (a == null && b == null) { return null; } return PdfBookmarkViewStyle( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), headerBarColor: Color.lerp(a.headerBarColor, b.headerBarColor, t), closeIconColor: Color.lerp(a.closeIconColor, b.closeIconColor, t), backIconColor: Color.lerp(a.backIconColor, b.backIconColor, t), @@ -744,32 +744,32 @@ class PdfPaginationDialogStyle { this.cancelTextStyle}); /// The background color of pagination dialog in [SfPdfViewer]. - final Color backgroundColor; + final Color? backgroundColor; /// The style for the header text of pagination dialog in [SfPdfViewer]. - final TextStyle headerTextStyle; + final TextStyle? headerTextStyle; /// The style for the input text field of pagination dialog in [SfPdfViewer]. - final TextStyle inputFieldTextStyle; + final TextStyle? inputFieldTextStyle; /// The style for the hint text of pagination dialog text field in [SfPdfViewer]. - final TextStyle hintTextStyle; + final TextStyle? hintTextStyle; /// The style for the page information text of pagination dialog in [SfPdfViewer]. - final TextStyle pageInfoTextStyle; + final TextStyle? pageInfoTextStyle; /// The style for the validation text of pagination dialog in [SfPdfViewer]. - final TextStyle validationTextStyle; + final TextStyle? validationTextStyle; /// The style for the Ok button text of pagination dialog in [SfPdfViewer]. - final TextStyle okTextStyle; + final TextStyle? okTextStyle; /// The style for the Cancel button of pagination dialog in [SfPdfViewer]. - final TextStyle cancelTextStyle; + final TextStyle? cancelTextStyle; @override int get hashCode { - final List values = [ + final List values = [ backgroundColor, headerTextStyle, inputFieldTextStyle, @@ -802,14 +802,13 @@ class PdfPaginationDialogStyle { } /// Linearly interpolate between two styles. - static PdfPaginationDialogStyle lerp( - PdfPaginationDialogStyle a, PdfPaginationDialogStyle b, double t) { - assert(t != null); + static PdfPaginationDialogStyle? lerp( + PdfPaginationDialogStyle? a, PdfPaginationDialogStyle? b, double t) { if (a == null && b == null) { return null; } return PdfPaginationDialogStyle( - backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t), + backgroundColor: Color.lerp(a!.backgroundColor, b!.backgroundColor, t), headerTextStyle: TextStyle.lerp(a.headerTextStyle, b.headerTextStyle, t), inputFieldTextStyle: diff --git a/packages/syncfusion_flutter_core/lib/src/theme/range_selector_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/range_selector_theme.dart index 850378175..990841de8 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/range_selector_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/range_selector_theme.dart @@ -8,7 +8,8 @@ class SfRangeSelectorTheme extends InheritedTheme { /// Applies the given theme [data] to [child]. /// /// The [data] and [child] arguments must not be null. - const SfRangeSelectorTheme({Key key, this.data, this.child}) + const SfRangeSelectorTheme( + {Key? key, required this.data, required this.child}) : super(key: key, child: child); /// Specifies the color and typography values for descendant @@ -84,8 +85,8 @@ class SfRangeSelectorTheme extends InheritedTheme { /// /// Defaults to [SfThemeData.rangeSelectorThemeData] if there is no /// [SfRangeSelectorTheme] in the given build context. - static SfRangeSelectorThemeData of(BuildContext context) { - final SfRangeSelectorTheme rangeSelectorTheme = + static SfRangeSelectorThemeData? of(BuildContext context) { + final SfRangeSelectorTheme? rangeSelectorTheme = context.dependOnInheritedWidgetOfExactType(); return rangeSelectorTheme?.data ?? SfTheme.of(context).rangeSelectorThemeData; @@ -97,7 +98,7 @@ class SfRangeSelectorTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SfRangeSelectorTheme ancestorTheme = + final SfRangeSelectorTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); return identical(this, ancestorTheme) ? child @@ -151,51 +152,51 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { /// /// If any of the values are null, the default values will be set. factory SfRangeSelectorThemeData( - {Brightness brightness, - double activeTrackHeight, - double inactiveTrackHeight, - Size tickSize, - Size minorTickSize, - Offset tickOffset, - Offset labelOffset, - TextStyle inactiveLabelStyle, - TextStyle activeLabelStyle, - TextStyle tooltipTextStyle, - Color inactiveTrackColor, - Color activeTrackColor, - Color thumbColor, - Color activeTickColor, - Color inactiveTickColor, - Color disabledActiveTickColor, - Color disabledInactiveTickColor, - Color activeMinorTickColor, - Color inactiveMinorTickColor, - Color disabledActiveMinorTickColor, - Color disabledInactiveMinorTickColor, - Color overlayColor, - Color inactiveDivisorColor, - Color activeDivisorColor, - Color disabledActiveTrackColor, - Color disabledInactiveTrackColor, - Color disabledActiveDivisorColor, - Color disabledInactiveDivisorColor, - Color disabledThumbColor, - Color activeRegionColor, - Color inactiveRegionColor, - Color tooltipBackgroundColor, - Color overlappingTooltipStrokeColor, - Color thumbStrokeColor, - Color overlappingThumbStrokeColor, - Color activeDivisorStrokeColor, - Color inactiveDivisorStrokeColor, - double trackCornerRadius, - double overlayRadius, - double thumbRadius, - double activeDivisorRadius, - double inactiveDivisorRadius, - double thumbStrokeWidth, - double activeDivisorStrokeWidth, - double inactiveDivisorStrokeWidth}) { + {Brightness? brightness, + double? activeTrackHeight, + double? inactiveTrackHeight, + Size? tickSize, + Size? minorTickSize, + Offset? tickOffset, + Offset? labelOffset, + TextStyle? inactiveLabelStyle, + TextStyle? activeLabelStyle, + TextStyle? tooltipTextStyle, + Color? inactiveTrackColor, + Color? activeTrackColor, + Color? thumbColor, + Color? activeTickColor, + Color? inactiveTickColor, + Color? disabledActiveTickColor, + Color? disabledInactiveTickColor, + Color? activeMinorTickColor, + Color? inactiveMinorTickColor, + Color? disabledActiveMinorTickColor, + Color? disabledInactiveMinorTickColor, + Color? overlayColor, + Color? inactiveDivisorColor, + Color? activeDivisorColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDivisorColor, + Color? disabledInactiveDivisorColor, + Color? disabledThumbColor, + Color? activeRegionColor, + Color? inactiveRegionColor, + Color? tooltipBackgroundColor, + Color? overlappingTooltipStrokeColor, + Color? thumbStrokeColor, + Color? overlappingThumbStrokeColor, + Color? activeDivisorStrokeColor, + Color? inactiveDivisorStrokeColor, + double? trackCornerRadius, + double? overlayRadius, + double? thumbRadius, + double? activeDivisorRadius, + double? inactiveDivisorRadius, + double? thumbStrokeWidth, + double? activeDivisorStrokeWidth, + double? inactiveDivisorStrokeWidth}) { brightness = brightness ?? Brightness.light; final bool isLight = brightness == Brightness.light; activeTrackHeight ??= 6.0; @@ -275,51 +276,51 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { /// create intermediate themes based on two themes created with the /// [SfRangeSelectorThemeData] constructor. const SfRangeSelectorThemeData.raw({ - @required Brightness brightness, - @required double activeTrackHeight, - @required double inactiveTrackHeight, - @required Size tickSize, - @required Size minorTickSize, - @required Offset tickOffset, - @required Offset labelOffset, - @required TextStyle inactiveLabelStyle, - @required TextStyle activeLabelStyle, - @required TextStyle tooltipTextStyle, - @required Color inactiveTrackColor, - @required Color activeTrackColor, - @required Color thumbColor, - @required Color thumbStrokeColor, - @required Color overlappingThumbStrokeColor, - @required Color activeDivisorStrokeColor, - @required Color inactiveDivisorStrokeColor, - @required Color activeTickColor, - @required Color inactiveTickColor, - @required Color disabledActiveTickColor, - @required Color disabledInactiveTickColor, - @required Color activeMinorTickColor, - @required Color inactiveMinorTickColor, - @required Color disabledActiveMinorTickColor, - @required Color disabledInactiveMinorTickColor, - @required Color overlayColor, - @required Color inactiveDivisorColor, - @required Color activeDivisorColor, - @required Color disabledActiveTrackColor, - @required Color disabledInactiveTrackColor, - @required Color disabledActiveDivisorColor, - @required Color disabledInactiveDivisorColor, - @required Color disabledThumbColor, - @required this.activeRegionColor, - @required this.inactiveRegionColor, - @required Color tooltipBackgroundColor, - @required Color overlappingTooltipStrokeColor, - @required double trackCornerRadius, - @required double overlayRadius, - @required double thumbRadius, - @required double activeDivisorRadius, - @required double inactiveDivisorRadius, - @required double thumbStrokeWidth, - @required double activeDivisorStrokeWidth, - @required double inactiveDivisorStrokeWidth, + required Brightness brightness, + required double activeTrackHeight, + required double inactiveTrackHeight, + required Size? tickSize, + required Size? minorTickSize, + required Offset? tickOffset, + required Offset? labelOffset, + required TextStyle? inactiveLabelStyle, + required TextStyle? activeLabelStyle, + required TextStyle? tooltipTextStyle, + required Color? inactiveTrackColor, + required Color? activeTrackColor, + required Color? thumbColor, + required Color? thumbStrokeColor, + required Color? overlappingThumbStrokeColor, + required Color? activeDivisorStrokeColor, + required Color? inactiveDivisorStrokeColor, + required Color activeTickColor, + required Color inactiveTickColor, + required Color disabledActiveTickColor, + required Color disabledInactiveTickColor, + required Color activeMinorTickColor, + required Color inactiveMinorTickColor, + required Color disabledActiveMinorTickColor, + required Color disabledInactiveMinorTickColor, + required Color? overlayColor, + required Color? inactiveDivisorColor, + required Color? activeDivisorColor, + required Color? disabledActiveTrackColor, + required Color? disabledInactiveTrackColor, + required Color? disabledActiveDivisorColor, + required Color? disabledInactiveDivisorColor, + required Color disabledThumbColor, + required this.activeRegionColor, + required this.inactiveRegionColor, + required Color? tooltipBackgroundColor, + required Color? overlappingTooltipStrokeColor, + required double? trackCornerRadius, + required double overlayRadius, + required double thumbRadius, + required double? activeDivisorRadius, + required double? inactiveDivisorRadius, + required double? thumbStrokeWidth, + required double? activeDivisorStrokeWidth, + required double? inactiveDivisorStrokeWidth, }) : super.raw( brightness: brightness, activeTrackHeight: activeTrackHeight, @@ -445,51 +446,51 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { /// fields replaced with the new values. @override SfRangeSelectorThemeData copyWith({ - Brightness brightness, - double activeTrackHeight, - double inactiveTrackHeight, - Size tickSize, - Size minorTickSize, - Offset tickOffset, - Offset labelOffset, - TextStyle inactiveLabelStyle, - TextStyle activeLabelStyle, - TextStyle tooltipTextStyle, - Color inactiveTrackColor, - Color activeTrackColor, - Color thumbColor, - Color thumbStrokeColor, - Color overlappingThumbStrokeColor, - Color activeDivisorStrokeColor, - Color inactiveDivisorStrokeColor, - Color activeTickColor, - Color inactiveTickColor, - Color disabledActiveTickColor, - Color disabledInactiveTickColor, - Color activeMinorTickColor, - Color inactiveMinorTickColor, - Color disabledActiveMinorTickColor, - Color disabledInactiveMinorTickColor, - Color overlayColor, - Color inactiveDivisorColor, - Color activeDivisorColor, - Color disabledActiveTrackColor, - Color disabledInactiveTrackColor, - Color disabledActiveDivisorColor, - Color disabledInactiveDivisorColor, - Color disabledThumbColor, - Color activeRegionColor, - Color inactiveRegionColor, - Color tooltipBackgroundColor, - Color overlappingTooltipStrokeColor, - double trackCornerRadius, - double overlayRadius, - double thumbRadius, - double activeDivisorRadius, - double inactiveDivisorRadius, - double thumbStrokeWidth, - double activeDivisorStrokeWidth, - double inactiveDivisorStrokeWidth, + Brightness? brightness, + double? activeTrackHeight, + double? inactiveTrackHeight, + Size? tickSize, + Size? minorTickSize, + Offset? tickOffset, + Offset? labelOffset, + TextStyle? inactiveLabelStyle, + TextStyle? activeLabelStyle, + TextStyle? tooltipTextStyle, + Color? inactiveTrackColor, + Color? activeTrackColor, + Color? thumbColor, + Color? thumbStrokeColor, + Color? overlappingThumbStrokeColor, + Color? activeDivisorStrokeColor, + Color? inactiveDivisorStrokeColor, + Color? activeTickColor, + Color? inactiveTickColor, + Color? disabledActiveTickColor, + Color? disabledInactiveTickColor, + Color? activeMinorTickColor, + Color? inactiveMinorTickColor, + Color? disabledActiveMinorTickColor, + Color? disabledInactiveMinorTickColor, + Color? overlayColor, + Color? inactiveDivisorColor, + Color? activeDivisorColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDivisorColor, + Color? disabledInactiveDivisorColor, + Color? disabledThumbColor, + Color? activeRegionColor, + Color? inactiveRegionColor, + Color? tooltipBackgroundColor, + Color? overlappingTooltipStrokeColor, + double? trackCornerRadius, + double? overlayRadius, + double? thumbRadius, + double? activeDivisorRadius, + double? inactiveDivisorRadius, + double? thumbStrokeWidth, + double? activeDivisorStrokeWidth, + double? inactiveDivisorStrokeWidth, }) { return SfRangeSelectorThemeData.raw( brightness: brightness ?? this.brightness, @@ -560,15 +561,14 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { /// Linearly interpolate between two themes. /// /// The arguments must not be null. - static SfRangeSelectorThemeData lerp( - SfRangeSelectorThemeData a, SfRangeSelectorThemeData b, double t) { - assert(t != null); + static SfRangeSelectorThemeData? lerp( + SfRangeSelectorThemeData? a, SfRangeSelectorThemeData? b, double t) { if (a == null && b == null) { return null; } return SfRangeSelectorThemeData( activeTrackHeight: - lerpDouble(a.activeTrackHeight, b.activeTrackHeight, t), + lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t), inactiveTrackHeight: lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t), tickSize: Size.lerp(a.tickSize, b.tickSize, t), @@ -649,61 +649,59 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { if (other.runtimeType != runtimeType) { return false; } - final SfRangeSelectorThemeData otherData = other; - return otherData.brightness == brightness && - otherData.activeTrackHeight == activeTrackHeight && - otherData.inactiveTrackHeight == inactiveTrackHeight && - otherData.tickSize == tickSize && - otherData.minorTickSize == minorTickSize && - otherData.tickOffset == tickOffset && - otherData.labelOffset == labelOffset && - otherData.inactiveLabelStyle == inactiveLabelStyle && - otherData.activeLabelStyle == activeLabelStyle && - otherData.tooltipTextStyle == tooltipTextStyle && - otherData.inactiveTrackColor == inactiveTrackColor && - otherData.activeTrackColor == activeTrackColor && - otherData.thumbColor == thumbColor && - otherData.thumbStrokeColor == thumbStrokeColor && - otherData.overlappingThumbStrokeColor == overlappingThumbStrokeColor && - otherData.activeDivisorStrokeColor == activeDivisorStrokeColor && - otherData.inactiveDivisorStrokeColor == inactiveDivisorStrokeColor && - otherData.activeTickColor == activeTickColor && - otherData.inactiveTickColor == inactiveTickColor && - otherData.disabledActiveTickColor == disabledActiveTickColor && - otherData.disabledInactiveTickColor == disabledInactiveTickColor && - otherData.activeMinorTickColor == activeMinorTickColor && - otherData.inactiveMinorTickColor == inactiveMinorTickColor && - otherData.disabledActiveMinorTickColor == - disabledActiveMinorTickColor && - otherData.disabledInactiveMinorTickColor == + + return other is SfRangeSelectorThemeData && + other.brightness == brightness && + other.activeTrackHeight == activeTrackHeight && + other.inactiveTrackHeight == inactiveTrackHeight && + other.tickSize == tickSize && + other.minorTickSize == minorTickSize && + other.tickOffset == tickOffset && + other.labelOffset == labelOffset && + other.inactiveLabelStyle == inactiveLabelStyle && + other.activeLabelStyle == activeLabelStyle && + other.tooltipTextStyle == tooltipTextStyle && + other.inactiveTrackColor == inactiveTrackColor && + other.activeTrackColor == activeTrackColor && + other.thumbColor == thumbColor && + other.thumbStrokeColor == thumbStrokeColor && + other.overlappingThumbStrokeColor == overlappingThumbStrokeColor && + other.activeDivisorStrokeColor == activeDivisorStrokeColor && + other.inactiveDivisorStrokeColor == inactiveDivisorStrokeColor && + other.activeTickColor == activeTickColor && + other.inactiveTickColor == inactiveTickColor && + other.disabledActiveTickColor == disabledActiveTickColor && + other.disabledInactiveTickColor == disabledInactiveTickColor && + other.activeMinorTickColor == activeMinorTickColor && + other.inactiveMinorTickColor == inactiveMinorTickColor && + other.disabledActiveMinorTickColor == disabledActiveMinorTickColor && + other.disabledInactiveMinorTickColor == disabledInactiveMinorTickColor && - otherData.overlayColor == overlayColor && - otherData.inactiveDivisorColor == inactiveDivisorColor && - otherData.activeDivisorColor == activeDivisorColor && - otherData.disabledActiveTrackColor == disabledActiveTrackColor && - otherData.disabledInactiveTrackColor == disabledInactiveTrackColor && - otherData.disabledActiveDivisorColor == disabledActiveDivisorColor && - otherData.disabledInactiveDivisorColor == - disabledInactiveDivisorColor && - otherData.disabledThumbColor == disabledThumbColor && - otherData.activeRegionColor == activeRegionColor && - otherData.inactiveRegionColor == inactiveRegionColor && - otherData.tooltipBackgroundColor == tooltipBackgroundColor && - otherData.overlappingTooltipStrokeColor == - overlappingTooltipStrokeColor && - otherData.trackCornerRadius == trackCornerRadius && - otherData.overlayRadius == overlayRadius && - otherData.thumbRadius == thumbRadius && - otherData.activeDivisorRadius == activeDivisorRadius && - otherData.inactiveDivisorRadius == inactiveDivisorRadius && - otherData.thumbStrokeWidth == thumbStrokeWidth && - otherData.activeDivisorStrokeWidth == activeDivisorStrokeWidth && - otherData.inactiveDivisorStrokeWidth == inactiveDivisorStrokeWidth; + other.overlayColor == overlayColor && + other.inactiveDivisorColor == inactiveDivisorColor && + other.activeDivisorColor == activeDivisorColor && + other.disabledActiveTrackColor == disabledActiveTrackColor && + other.disabledInactiveTrackColor == disabledInactiveTrackColor && + other.disabledActiveDivisorColor == disabledActiveDivisorColor && + other.disabledInactiveDivisorColor == disabledInactiveDivisorColor && + other.disabledThumbColor == disabledThumbColor && + other.activeRegionColor == activeRegionColor && + other.inactiveRegionColor == inactiveRegionColor && + other.tooltipBackgroundColor == tooltipBackgroundColor && + other.overlappingTooltipStrokeColor == overlappingTooltipStrokeColor && + other.trackCornerRadius == trackCornerRadius && + other.overlayRadius == overlayRadius && + other.thumbRadius == thumbRadius && + other.activeDivisorRadius == activeDivisorRadius && + other.inactiveDivisorRadius == inactiveDivisorRadius && + other.thumbStrokeWidth == thumbStrokeWidth && + other.activeDivisorStrokeWidth == activeDivisorStrokeWidth && + other.inactiveDivisorStrokeWidth == inactiveDivisorStrokeWidth; } @override int get hashCode { - return hashList([ + return hashList([ brightness, activeTrackHeight, inactiveTrackHeight, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/range_slider_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/range_slider_theme.dart index b520e2116..c531e4e7a 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/range_slider_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/range_slider_theme.dart @@ -8,7 +8,7 @@ class SfRangeSliderTheme extends InheritedTheme { /// Applies the given theme [data] to [child]. /// /// The [data] and [child] arguments must not be null. - const SfRangeSliderTheme({Key key, this.data, this.child}) + const SfRangeSliderTheme({Key? key, required this.data, required this.child}) : super(key: key, child: child); /// Specifies the color and typography values for @@ -24,8 +24,8 @@ class SfRangeSliderTheme extends InheritedTheme { /// /// Defaults to [SfThemeData.rangeSliderThemeData] if there is no /// [SfRangeSliderTheme] in the given build context. - static SfRangeSliderThemeData of(BuildContext context) { - final SfRangeSliderTheme rangeSliderTheme = + static SfRangeSliderThemeData? of(BuildContext context) { + final SfRangeSliderTheme? rangeSliderTheme = context.dependOnInheritedWidgetOfExactType(); return rangeSliderTheme?.data ?? SfTheme.of(context).rangeSliderThemeData; } @@ -36,7 +36,7 @@ class SfRangeSliderTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SfRangeSliderTheme ancestorTheme = + final SfRangeSliderTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); return identical(this, ancestorTheme) ? child @@ -88,57 +88,55 @@ class SfRangeSliderThemeData extends SfSliderThemeData { /// /// If any of the values are null, the default values will be set. factory SfRangeSliderThemeData( - {Brightness brightness, - double activeTrackHeight, - double inactiveTrackHeight, - Size tickSize, - Size minorTickSize, - Offset tickOffset, - Offset labelOffset, - TextStyle inactiveLabelStyle, - TextStyle activeLabelStyle, - TextStyle tooltipTextStyle, - Color inactiveTrackColor, - Color activeTrackColor, - Color thumbColor, - Color activeTickColor, - Color inactiveTickColor, - Color disabledActiveTickColor, - Color disabledInactiveTickColor, - Color activeMinorTickColor, - Color inactiveMinorTickColor, - Color disabledActiveMinorTickColor, - Color disabledInactiveMinorTickColor, - Color overlayColor, - Color inactiveDivisorColor, - Color activeDivisorColor, - Color disabledActiveTrackColor, - Color disabledInactiveTrackColor, - Color disabledActiveDivisorColor, - Color disabledInactiveDivisorColor, - Color disabledThumbColor, - Color activeRegionColor, - Color inactiveRegionColor, - Color tooltipBackgroundColor, - Color overlappingTooltipStrokeColor, - Color thumbStrokeColor, - Color overlappingThumbStrokeColor, - Color activeDivisorStrokeColor, - Color inactiveDivisorStrokeColor, - double trackCornerRadius, - double overlayRadius, - double thumbRadius, - double activeDivisorRadius, - double inactiveDivisorRadius, - double thumbStrokeWidth, - double activeDivisorStrokeWidth, - double inactiveDivisorStrokeWidth}) { + {Brightness? brightness, + double? activeTrackHeight, + double? inactiveTrackHeight, + Size? tickSize, + Size? minorTickSize, + Offset? tickOffset, + Offset? labelOffset, + TextStyle? inactiveLabelStyle, + TextStyle? activeLabelStyle, + TextStyle? tooltipTextStyle, + Color? inactiveTrackColor, + Color? activeTrackColor, + Color? thumbColor, + Color? activeTickColor, + Color? inactiveTickColor, + Color? disabledActiveTickColor, + Color? disabledInactiveTickColor, + Color? activeMinorTickColor, + Color? inactiveMinorTickColor, + Color? disabledActiveMinorTickColor, + Color? disabledInactiveMinorTickColor, + Color? overlayColor, + Color? inactiveDivisorColor, + Color? activeDivisorColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDivisorColor, + Color? disabledInactiveDivisorColor, + Color? disabledThumbColor, + Color? activeRegionColor, + Color? inactiveRegionColor, + Color? tooltipBackgroundColor, + Color? overlappingTooltipStrokeColor, + Color? thumbStrokeColor, + Color? overlappingThumbStrokeColor, + Color? activeDivisorStrokeColor, + Color? inactiveDivisorStrokeColor, + double? trackCornerRadius, + double? overlayRadius, + double? thumbRadius, + double? activeDivisorRadius, + double? inactiveDivisorRadius, + double? thumbStrokeWidth, + double? activeDivisorStrokeWidth, + double? inactiveDivisorStrokeWidth}) { brightness = brightness ?? Brightness.light; final bool isLight = brightness == Brightness.light; activeTrackHeight ??= 6.0; inactiveTrackHeight ??= 4.0; - tickSize ??= const Size(1.0, 8.0); - minorTickSize ??= const Size(1.0, 5.0); overlayRadius ??= 24.0; thumbRadius ??= 10.0; activeTickColor ??= const Color.fromRGBO(158, 158, 158, 1); @@ -210,49 +208,49 @@ class SfRangeSliderThemeData extends SfSliderThemeData { /// create intermediate themes based on two themes created with the /// [SfRangeSliderThemeData] constructor. const SfRangeSliderThemeData.raw({ - @required Brightness brightness, - @required double activeTrackHeight, - @required double inactiveTrackHeight, - @required Size tickSize, - @required Size minorTickSize, - @required Offset tickOffset, - @required Offset labelOffset, - @required TextStyle inactiveLabelStyle, - @required TextStyle activeLabelStyle, - @required TextStyle tooltipTextStyle, - @required Color inactiveTrackColor, - @required Color activeTrackColor, - @required Color thumbColor, - @required Color thumbStrokeColor, - @required this.overlappingThumbStrokeColor, - @required Color activeDivisorStrokeColor, - @required Color inactiveDivisorStrokeColor, - @required Color activeTickColor, - @required Color inactiveTickColor, - @required Color disabledActiveTickColor, - @required Color disabledInactiveTickColor, - @required Color activeMinorTickColor, - @required Color inactiveMinorTickColor, - @required Color disabledActiveMinorTickColor, - @required Color disabledInactiveMinorTickColor, - @required Color overlayColor, - @required Color inactiveDivisorColor, - @required Color activeDivisorColor, - @required Color disabledActiveTrackColor, - @required Color disabledInactiveTrackColor, - @required Color disabledActiveDivisorColor, - @required Color disabledInactiveDivisorColor, - @required Color disabledThumbColor, - @required Color tooltipBackgroundColor, - @required this.overlappingTooltipStrokeColor, - @required double trackCornerRadius, - @required double overlayRadius, - @required double thumbRadius, - @required double activeDivisorRadius, - @required double inactiveDivisorRadius, - @required double thumbStrokeWidth, - @required double activeDivisorStrokeWidth, - @required double inactiveDivisorStrokeWidth, + required Brightness brightness, + required double activeTrackHeight, + required double inactiveTrackHeight, + required Size? tickSize, + required Size? minorTickSize, + required Offset? tickOffset, + required Offset? labelOffset, + required TextStyle? inactiveLabelStyle, + required TextStyle? activeLabelStyle, + required TextStyle? tooltipTextStyle, + required Color? inactiveTrackColor, + required Color? activeTrackColor, + required Color? thumbColor, + required Color? thumbStrokeColor, + required this.overlappingThumbStrokeColor, + required Color? activeDivisorStrokeColor, + required Color? inactiveDivisorStrokeColor, + required Color activeTickColor, + required Color inactiveTickColor, + required Color disabledActiveTickColor, + required Color disabledInactiveTickColor, + required Color activeMinorTickColor, + required Color inactiveMinorTickColor, + required Color disabledActiveMinorTickColor, + required Color disabledInactiveMinorTickColor, + required Color? overlayColor, + required Color? inactiveDivisorColor, + required Color? activeDivisorColor, + required Color? disabledActiveTrackColor, + required Color? disabledInactiveTrackColor, + required Color? disabledActiveDivisorColor, + required Color? disabledInactiveDivisorColor, + required Color disabledThumbColor, + required Color? tooltipBackgroundColor, + required this.overlappingTooltipStrokeColor, + required double? trackCornerRadius, + required double overlayRadius, + required double thumbRadius, + required double? activeDivisorRadius, + required double? inactiveDivisorRadius, + required double? thumbStrokeWidth, + required double? activeDivisorStrokeWidth, + required double? inactiveDivisorStrokeWidth, }) : super.raw( brightness: brightness, activeTrackHeight: activeTrackHeight, @@ -328,7 +326,7 @@ class SfRangeSliderThemeData extends SfSliderThemeData { /// See also: /// * [thumbStrokeColor] and [thumbStrokeWidth], for setting the default /// stroke for the range slider and range selector thumbs. - final Color overlappingThumbStrokeColor; + final Color? overlappingThumbStrokeColor; /// Specifies the stroke color for the tooltips when they overlap in the /// [SfRangeSlider], and [SfRangeSelector]. @@ -356,57 +354,57 @@ class SfRangeSliderThemeData extends SfSliderThemeData { /// ) /// ) /// ``` - final Color overlappingTooltipStrokeColor; + final Color? overlappingTooltipStrokeColor; /// Creates a copy of this theme but with the given /// fields replaced with the new values. @override SfRangeSliderThemeData copyWith({ - Brightness brightness, - double activeTrackHeight, - double inactiveTrackHeight, - Size tickSize, - Size minorTickSize, - Offset tickOffset, - Offset labelOffset, - TextStyle inactiveLabelStyle, - TextStyle activeLabelStyle, - TextStyle tooltipTextStyle, - Color inactiveTrackColor, - Color activeTrackColor, - Color thumbColor, - Color thumbStrokeColor, - Color overlappingThumbStrokeColor, - Color activeDivisorStrokeColor, - Color inactiveDivisorStrokeColor, - Color activeTickColor, - Color inactiveTickColor, - Color disabledActiveTickColor, - Color disabledInactiveTickColor, - Color activeMinorTickColor, - Color inactiveMinorTickColor, - Color disabledActiveMinorTickColor, - Color disabledInactiveMinorTickColor, - Color overlayColor, - Color inactiveDivisorColor, - Color activeDivisorColor, - Color disabledActiveTrackColor, - Color disabledInactiveTrackColor, - Color disabledActiveDivisorColor, - Color disabledInactiveDivisorColor, - Color disabledThumbColor, - Color activeRegionColor, - Color inactiveRegionColor, - Color tooltipBackgroundColor, - Color overlappingTooltipStrokeColor, - double trackCornerRadius, - double overlayRadius, - double thumbRadius, - double activeDivisorRadius, - double inactiveDivisorRadius, - double thumbStrokeWidth, - double activeDivisorStrokeWidth, - double inactiveDivisorStrokeWidth, + Brightness? brightness, + double? activeTrackHeight, + double? inactiveTrackHeight, + Size? tickSize, + Size? minorTickSize, + Offset? tickOffset, + Offset? labelOffset, + TextStyle? inactiveLabelStyle, + TextStyle? activeLabelStyle, + TextStyle? tooltipTextStyle, + Color? inactiveTrackColor, + Color? activeTrackColor, + Color? thumbColor, + Color? thumbStrokeColor, + Color? overlappingThumbStrokeColor, + Color? activeDivisorStrokeColor, + Color? inactiveDivisorStrokeColor, + Color? activeTickColor, + Color? inactiveTickColor, + Color? disabledActiveTickColor, + Color? disabledInactiveTickColor, + Color? activeMinorTickColor, + Color? inactiveMinorTickColor, + Color? disabledActiveMinorTickColor, + Color? disabledInactiveMinorTickColor, + Color? overlayColor, + Color? inactiveDivisorColor, + Color? activeDivisorColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDivisorColor, + Color? disabledInactiveDivisorColor, + Color? disabledThumbColor, + Color? activeRegionColor, + Color? inactiveRegionColor, + Color? tooltipBackgroundColor, + Color? overlappingTooltipStrokeColor, + double? trackCornerRadius, + double? overlayRadius, + double? thumbRadius, + double? activeDivisorRadius, + double? inactiveDivisorRadius, + double? thumbStrokeWidth, + double? activeDivisorStrokeWidth, + double? inactiveDivisorStrokeWidth, }) { return SfRangeSliderThemeData.raw( brightness: brightness ?? this.brightness, @@ -475,15 +473,14 @@ class SfRangeSliderThemeData extends SfSliderThemeData { /// Linearly interpolate between two themes. /// /// The arguments must not be null. - static SfRangeSliderThemeData lerp( - SfRangeSliderThemeData a, SfRangeSliderThemeData b, double t) { - assert(t != null); + static SfRangeSliderThemeData? lerp( + SfRangeSliderThemeData? a, SfRangeSliderThemeData? b, double t) { if (a == null && b == null) { return null; } return SfRangeSliderThemeData( activeTrackHeight: - lerpDouble(a.activeTrackHeight, b.activeTrackHeight, t), + lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t), inactiveTrackHeight: lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t), tickSize: Size.lerp(a.tickSize, b.tickSize, t), @@ -561,59 +558,57 @@ class SfRangeSliderThemeData extends SfSliderThemeData { if (other.runtimeType != runtimeType) { return false; } - final SfRangeSliderThemeData otherData = other; - return otherData.brightness == brightness && - otherData.activeTrackHeight == activeTrackHeight && - otherData.inactiveTrackHeight == inactiveTrackHeight && - otherData.tickSize == tickSize && - otherData.minorTickSize == minorTickSize && - otherData.tickOffset == tickOffset && - otherData.labelOffset == labelOffset && - otherData.inactiveLabelStyle == inactiveLabelStyle && - otherData.activeLabelStyle == activeLabelStyle && - otherData.tooltipTextStyle == tooltipTextStyle && - otherData.inactiveTrackColor == inactiveTrackColor && - otherData.activeTrackColor == activeTrackColor && - otherData.thumbColor == thumbColor && - otherData.thumbStrokeColor == thumbStrokeColor && - otherData.overlappingThumbStrokeColor == overlappingThumbStrokeColor && - otherData.activeDivisorStrokeColor == activeDivisorStrokeColor && - otherData.inactiveDivisorStrokeColor == inactiveDivisorStrokeColor && - otherData.activeTickColor == activeTickColor && - otherData.inactiveTickColor == inactiveTickColor && - otherData.disabledActiveTickColor == disabledActiveTickColor && - otherData.disabledInactiveTickColor == disabledInactiveTickColor && - otherData.activeMinorTickColor == activeMinorTickColor && - otherData.inactiveMinorTickColor == inactiveMinorTickColor && - otherData.disabledActiveMinorTickColor == - disabledActiveMinorTickColor && - otherData.disabledInactiveMinorTickColor == + + return other is SfRangeSliderThemeData && + other.brightness == brightness && + other.activeTrackHeight == activeTrackHeight && + other.inactiveTrackHeight == inactiveTrackHeight && + other.tickSize == tickSize && + other.minorTickSize == minorTickSize && + other.tickOffset == tickOffset && + other.labelOffset == labelOffset && + other.inactiveLabelStyle == inactiveLabelStyle && + other.activeLabelStyle == activeLabelStyle && + other.tooltipTextStyle == tooltipTextStyle && + other.inactiveTrackColor == inactiveTrackColor && + other.activeTrackColor == activeTrackColor && + other.thumbColor == thumbColor && + other.thumbStrokeColor == thumbStrokeColor && + other.overlappingThumbStrokeColor == overlappingThumbStrokeColor && + other.activeDivisorStrokeColor == activeDivisorStrokeColor && + other.inactiveDivisorStrokeColor == inactiveDivisorStrokeColor && + other.activeTickColor == activeTickColor && + other.inactiveTickColor == inactiveTickColor && + other.disabledActiveTickColor == disabledActiveTickColor && + other.disabledInactiveTickColor == disabledInactiveTickColor && + other.activeMinorTickColor == activeMinorTickColor && + other.inactiveMinorTickColor == inactiveMinorTickColor && + other.disabledActiveMinorTickColor == disabledActiveMinorTickColor && + other.disabledInactiveMinorTickColor == disabledInactiveMinorTickColor && - otherData.overlayColor == overlayColor && - otherData.inactiveDivisorColor == inactiveDivisorColor && - otherData.activeDivisorColor == activeDivisorColor && - otherData.disabledActiveTrackColor == disabledActiveTrackColor && - otherData.disabledInactiveTrackColor == disabledInactiveTrackColor && - otherData.disabledActiveDivisorColor == disabledActiveDivisorColor && - otherData.disabledInactiveDivisorColor == - disabledInactiveDivisorColor && - otherData.disabledThumbColor == disabledThumbColor && - otherData.tooltipBackgroundColor == tooltipBackgroundColor && - otherData.overlappingTooltipStrokeColor == - overlappingTooltipStrokeColor && - otherData.trackCornerRadius == trackCornerRadius && - otherData.overlayRadius == overlayRadius && - otherData.thumbRadius == thumbRadius && - otherData.activeDivisorRadius == activeDivisorRadius && - otherData.inactiveDivisorRadius == inactiveDivisorRadius && - otherData.thumbStrokeWidth == thumbStrokeWidth && - otherData.activeDivisorStrokeWidth == activeDivisorStrokeWidth && - otherData.inactiveDivisorStrokeWidth == inactiveDivisorStrokeWidth; + other.overlayColor == overlayColor && + other.inactiveDivisorColor == inactiveDivisorColor && + other.activeDivisorColor == activeDivisorColor && + other.disabledActiveTrackColor == disabledActiveTrackColor && + other.disabledInactiveTrackColor == disabledInactiveTrackColor && + other.disabledActiveDivisorColor == disabledActiveDivisorColor && + other.disabledInactiveDivisorColor == disabledInactiveDivisorColor && + other.disabledThumbColor == disabledThumbColor && + other.tooltipBackgroundColor == tooltipBackgroundColor && + other.overlappingTooltipStrokeColor == overlappingTooltipStrokeColor && + other.trackCornerRadius == trackCornerRadius && + other.overlayRadius == overlayRadius && + other.thumbRadius == thumbRadius && + other.activeDivisorRadius == activeDivisorRadius && + other.inactiveDivisorRadius == inactiveDivisorRadius && + other.thumbStrokeWidth == thumbStrokeWidth && + other.activeDivisorStrokeWidth == activeDivisorStrokeWidth && + other.inactiveDivisorStrokeWidth == inactiveDivisorStrokeWidth; } @override int get hashCode { - return hashList([ + return hashList([ brightness, activeTrackHeight, inactiveTrackHeight, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/slider_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/slider_theme.dart index dfb84ac34..327162c69 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/slider_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/slider_theme.dart @@ -8,7 +8,7 @@ class SfSliderTheme extends InheritedTheme { /// Applies the given theme [data] to [child]. /// /// The [data] and [child] arguments must not be null. - const SfSliderTheme({Key key, this.data, this.child}) + const SfSliderTheme({Key? key, required this.data, required this.child}) : super(key: key, child: child); /// Specifies the color and typography values for descendant slider widgets. @@ -24,7 +24,7 @@ class SfSliderTheme extends InheritedTheme { /// Defaults to [SfThemeData.sliderThemeData] if there is no /// [SfSliderTheme] in the given build context. static SfSliderThemeData of(BuildContext context) { - final SfSliderTheme sliderTheme = + final SfSliderTheme? sliderTheme = context.dependOnInheritedWidgetOfExactType(); return sliderTheme?.data ?? SfTheme.of(context).sliderThemeData; } @@ -34,7 +34,7 @@ class SfSliderTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final SfSliderTheme ancestorTheme = + final SfSliderTheme? ancestorTheme = context.findAncestorWidgetOfExactType(); return identical(this, ancestorTheme) ? child @@ -82,52 +82,50 @@ class SfSliderThemeData with Diagnosticable { /// /// If any of the values are null, the default values will be set. factory SfSliderThemeData( - {Brightness brightness, - double activeTrackHeight, - double inactiveTrackHeight, - Size tickSize, - Size minorTickSize, - Offset tickOffset, - Offset labelOffset, - TextStyle inactiveLabelStyle, - TextStyle activeLabelStyle, - TextStyle tooltipTextStyle, - Color inactiveTrackColor, - Color activeTrackColor, - Color thumbColor, - Color activeTickColor, - Color inactiveTickColor, - Color disabledActiveTickColor, - Color disabledInactiveTickColor, - Color activeMinorTickColor, - Color inactiveMinorTickColor, - Color disabledActiveMinorTickColor, - Color disabledInactiveMinorTickColor, - Color overlayColor, - Color inactiveDivisorColor, - Color activeDivisorColor, - Color disabledActiveTrackColor, - Color disabledInactiveTrackColor, - Color disabledActiveDivisorColor, - Color disabledInactiveDivisorColor, - Color disabledThumbColor, - Color tooltipBackgroundColor, - Color thumbStrokeColor, - Color activeDivisorStrokeColor, - Color inactiveDivisorStrokeColor, - double trackCornerRadius, - double overlayRadius, - double thumbRadius, - double activeDivisorRadius, - double inactiveDivisorRadius, - double thumbStrokeWidth, - double activeDivisorStrokeWidth, - double inactiveDivisorStrokeWidth}) { + {Brightness? brightness, + double? activeTrackHeight, + double? inactiveTrackHeight, + Size? tickSize, + Size? minorTickSize, + Offset? tickOffset, + Offset? labelOffset, + TextStyle? inactiveLabelStyle, + TextStyle? activeLabelStyle, + TextStyle? tooltipTextStyle, + Color? inactiveTrackColor, + Color? activeTrackColor, + Color? thumbColor, + Color? activeTickColor, + Color? inactiveTickColor, + Color? disabledActiveTickColor, + Color? disabledInactiveTickColor, + Color? activeMinorTickColor, + Color? inactiveMinorTickColor, + Color? disabledActiveMinorTickColor, + Color? disabledInactiveMinorTickColor, + Color? overlayColor, + Color? inactiveDivisorColor, + Color? activeDivisorColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDivisorColor, + Color? disabledInactiveDivisorColor, + Color? disabledThumbColor, + Color? tooltipBackgroundColor, + Color? thumbStrokeColor, + Color? activeDivisorStrokeColor, + Color? inactiveDivisorStrokeColor, + double? trackCornerRadius, + double? overlayRadius, + double? thumbRadius, + double? activeDivisorRadius, + double? inactiveDivisorRadius, + double? thumbStrokeWidth, + double? activeDivisorStrokeWidth, + double? inactiveDivisorStrokeWidth}) { brightness = brightness ?? Brightness.light; activeTrackHeight ??= 6.0; inactiveTrackHeight ??= 4.0; - tickSize ??= const Size(1.0, 8.0); - minorTickSize ??= const Size(1.0, 5.0); overlayRadius ??= 24.0; thumbRadius ??= 10.0; activeTickColor ??= const Color.fromRGBO(158, 158, 158, 1); @@ -191,95 +189,95 @@ class SfSliderThemeData with Diagnosticable { /// create intermediate themes based on two themes created with the /// [SfSliderThemeData] constructor. const SfSliderThemeData.raw({ - @required this.brightness, - @required this.activeTrackHeight, - @required this.inactiveTrackHeight, - @required this.tickSize, - @required this.minorTickSize, - @required this.tickOffset, - @required this.labelOffset, - @required this.inactiveLabelStyle, - @required this.activeLabelStyle, - @required this.tooltipTextStyle, - @required this.inactiveTrackColor, - @required this.activeTrackColor, - @required this.thumbColor, - @required this.thumbStrokeColor, - @required this.activeDivisorStrokeColor, - @required this.inactiveDivisorStrokeColor, - @required this.activeTickColor, - @required this.inactiveTickColor, - @required this.disabledActiveTickColor, - @required this.disabledInactiveTickColor, - @required this.activeMinorTickColor, - @required this.inactiveMinorTickColor, - @required this.disabledActiveMinorTickColor, - @required this.disabledInactiveMinorTickColor, - @required this.overlayColor, - @required this.inactiveDivisorColor, - @required this.activeDivisorColor, - @required this.disabledActiveTrackColor, - @required this.disabledInactiveTrackColor, - @required this.disabledActiveDivisorColor, - @required this.disabledInactiveDivisorColor, - @required this.disabledThumbColor, - @required this.tooltipBackgroundColor, - @required this.trackCornerRadius, - @required this.overlayRadius, - @required this.thumbRadius, - @required this.activeDivisorRadius, - @required this.inactiveDivisorRadius, - @required this.thumbStrokeWidth, - @required this.activeDivisorStrokeWidth, - @required this.inactiveDivisorStrokeWidth, + required this.brightness, + required this.activeTrackHeight, + required this.inactiveTrackHeight, + required this.tickSize, + required this.minorTickSize, + required this.tickOffset, + required this.labelOffset, + required this.inactiveLabelStyle, + required this.activeLabelStyle, + required this.tooltipTextStyle, + required this.inactiveTrackColor, + required this.activeTrackColor, + required this.thumbColor, + required this.thumbStrokeColor, + required this.activeDivisorStrokeColor, + required this.inactiveDivisorStrokeColor, + required this.activeTickColor, + required this.inactiveTickColor, + required this.disabledActiveTickColor, + required this.disabledInactiveTickColor, + required this.activeMinorTickColor, + required this.inactiveMinorTickColor, + required this.disabledActiveMinorTickColor, + required this.disabledInactiveMinorTickColor, + required this.overlayColor, + required this.inactiveDivisorColor, + required this.activeDivisorColor, + required this.disabledActiveTrackColor, + required this.disabledInactiveTrackColor, + required this.disabledActiveDivisorColor, + required this.disabledInactiveDivisorColor, + required this.disabledThumbColor, + required this.tooltipBackgroundColor, + required this.trackCornerRadius, + required this.overlayRadius, + required this.thumbRadius, + required this.activeDivisorRadius, + required this.inactiveDivisorRadius, + required this.thumbStrokeWidth, + required this.activeDivisorStrokeWidth, + required this.inactiveDivisorStrokeWidth, }); /// Creates a copy of this theme but with the given fields /// replaced with the new values. SfSliderThemeData copyWith({ - Brightness brightness, - double activeTrackHeight, - double inactiveTrackHeight, - Size tickSize, - Size minorTickSize, - Offset tickOffset, - Offset labelOffset, - TextStyle inactiveLabelStyle, - TextStyle activeLabelStyle, - TextStyle tooltipTextStyle, - Color inactiveTrackColor, - Color activeTrackColor, - Color thumbColor, - Color thumbStrokeColor, - Color activeDivisorStrokeColor, - Color inactiveDivisorStrokeColor, - Color activeTickColor, - Color inactiveTickColor, - Color disabledActiveTickColor, - Color disabledInactiveTickColor, - Color activeMinorTickColor, - Color inactiveMinorTickColor, - Color disabledActiveMinorTickColor, - Color disabledInactiveMinorTickColor, - Color overlayColor, - Color inactiveDivisorColor, - Color activeDivisorColor, - Color disabledActiveTrackColor, - Color disabledInactiveTrackColor, - Color disabledActiveDivisorColor, - Color disabledInactiveDivisorColor, - Color disabledThumbColor, - Color activeRegionColor, - Color inactiveRegionColor, - Color tooltipBackgroundColor, - double trackCornerRadius, - double overlayRadius, - double thumbRadius, - double activeDivisorRadius, - double inactiveDivisorRadius, - double thumbStrokeWidth, - double activeDivisorStrokeWidth, - double inactiveDivisorStrokeWidth, + Brightness? brightness, + double? activeTrackHeight, + double? inactiveTrackHeight, + Size? tickSize, + Size? minorTickSize, + Offset? tickOffset, + Offset? labelOffset, + TextStyle? inactiveLabelStyle, + TextStyle? activeLabelStyle, + TextStyle? tooltipTextStyle, + Color? inactiveTrackColor, + Color? activeTrackColor, + Color? thumbColor, + Color? thumbStrokeColor, + Color? activeDivisorStrokeColor, + Color? inactiveDivisorStrokeColor, + Color? activeTickColor, + Color? inactiveTickColor, + Color? disabledActiveTickColor, + Color? disabledInactiveTickColor, + Color? activeMinorTickColor, + Color? inactiveMinorTickColor, + Color? disabledActiveMinorTickColor, + Color? disabledInactiveMinorTickColor, + Color? overlayColor, + Color? inactiveDivisorColor, + Color? activeDivisorColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDivisorColor, + Color? disabledInactiveDivisorColor, + Color? disabledThumbColor, + Color? activeRegionColor, + Color? inactiveRegionColor, + Color? tooltipBackgroundColor, + double? trackCornerRadius, + double? overlayRadius, + double? thumbRadius, + double? activeDivisorRadius, + double? inactiveDivisorRadius, + double? thumbStrokeWidth, + double? activeDivisorStrokeWidth, + double? inactiveDivisorStrokeWidth, }) { return SfSliderThemeData.raw( brightness: brightness ?? this.brightness, @@ -344,15 +342,14 @@ class SfSliderThemeData with Diagnosticable { /// Linearly interpolate between two themes. /// /// The arguments must not be null. - static SfSliderThemeData lerp( - SfSliderThemeData a, SfSliderThemeData b, double t) { - assert(t != null); + static SfSliderThemeData? lerp( + SfSliderThemeData? a, SfSliderThemeData? b, double t) { if (a == null && b == null) { return null; } return SfSliderThemeData( activeTrackHeight: - lerpDouble(a.activeTrackHeight, b.activeTrackHeight, t), + lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t), inactiveTrackHeight: lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t), tickSize: Size.lerp(a.tickSize, b.tickSize, t), @@ -430,56 +427,55 @@ class SfSliderThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - final SfSliderThemeData otherData = other; - return otherData.brightness == brightness && - otherData.activeTrackHeight == activeTrackHeight && - otherData.inactiveTrackHeight == inactiveTrackHeight && - otherData.tickSize == tickSize && - otherData.minorTickSize == minorTickSize && - otherData.tickOffset == tickOffset && - otherData.labelOffset == labelOffset && - otherData.inactiveLabelStyle == inactiveLabelStyle && - otherData.activeLabelStyle == activeLabelStyle && - otherData.tooltipTextStyle == tooltipTextStyle && - otherData.inactiveTrackColor == inactiveTrackColor && - otherData.activeTrackColor == activeTrackColor && - otherData.thumbColor == thumbColor && - otherData.thumbStrokeColor == thumbStrokeColor && - otherData.activeDivisorStrokeColor == activeDivisorStrokeColor && - otherData.inactiveDivisorStrokeColor == inactiveDivisorStrokeColor && - otherData.activeTickColor == activeTickColor && - otherData.inactiveTickColor == inactiveTickColor && - otherData.disabledActiveTickColor == disabledActiveTickColor && - otherData.disabledInactiveTickColor == disabledInactiveTickColor && - otherData.activeMinorTickColor == activeMinorTickColor && - otherData.inactiveMinorTickColor == inactiveMinorTickColor && - otherData.disabledActiveMinorTickColor == - disabledActiveMinorTickColor && - otherData.disabledInactiveMinorTickColor == + + return other is SfSliderThemeData && + other.brightness == brightness && + other.activeTrackHeight == activeTrackHeight && + other.inactiveTrackHeight == inactiveTrackHeight && + other.tickSize == tickSize && + other.minorTickSize == minorTickSize && + other.tickOffset == tickOffset && + other.labelOffset == labelOffset && + other.inactiveLabelStyle == inactiveLabelStyle && + other.activeLabelStyle == activeLabelStyle && + other.tooltipTextStyle == tooltipTextStyle && + other.inactiveTrackColor == inactiveTrackColor && + other.activeTrackColor == activeTrackColor && + other.thumbColor == thumbColor && + other.thumbStrokeColor == thumbStrokeColor && + other.activeDivisorStrokeColor == activeDivisorStrokeColor && + other.inactiveDivisorStrokeColor == inactiveDivisorStrokeColor && + other.activeTickColor == activeTickColor && + other.inactiveTickColor == inactiveTickColor && + other.disabledActiveTickColor == disabledActiveTickColor && + other.disabledInactiveTickColor == disabledInactiveTickColor && + other.activeMinorTickColor == activeMinorTickColor && + other.inactiveMinorTickColor == inactiveMinorTickColor && + other.disabledActiveMinorTickColor == disabledActiveMinorTickColor && + other.disabledInactiveMinorTickColor == disabledInactiveMinorTickColor && - otherData.overlayColor == overlayColor && - otherData.inactiveDivisorColor == inactiveDivisorColor && - otherData.activeDivisorColor == activeDivisorColor && - otherData.disabledActiveTrackColor == disabledActiveTrackColor && - otherData.disabledInactiveTrackColor == disabledInactiveTrackColor && - otherData.disabledActiveDivisorColor == disabledActiveDivisorColor && - otherData.disabledInactiveDivisorColor == - disabledInactiveDivisorColor && - otherData.disabledThumbColor == disabledThumbColor && - otherData.tooltipBackgroundColor == tooltipBackgroundColor && - otherData.trackCornerRadius == trackCornerRadius && - otherData.overlayRadius == overlayRadius && - otherData.thumbRadius == thumbRadius && - otherData.activeDivisorRadius == activeDivisorRadius && - otherData.inactiveDivisorRadius == inactiveDivisorRadius && - otherData.thumbStrokeWidth == thumbStrokeWidth && - otherData.activeDivisorStrokeWidth == activeDivisorStrokeWidth && - otherData.inactiveDivisorStrokeWidth == inactiveDivisorStrokeWidth; + other.overlayColor == overlayColor && + other.inactiveDivisorColor == inactiveDivisorColor && + other.activeDivisorColor == activeDivisorColor && + other.disabledActiveTrackColor == disabledActiveTrackColor && + other.disabledInactiveTrackColor == disabledInactiveTrackColor && + other.disabledActiveDivisorColor == disabledActiveDivisorColor && + other.disabledInactiveDivisorColor == disabledInactiveDivisorColor && + other.disabledThumbColor == disabledThumbColor && + other.tooltipBackgroundColor == tooltipBackgroundColor && + other.trackCornerRadius == trackCornerRadius && + other.overlayRadius == overlayRadius && + other.thumbRadius == thumbRadius && + other.activeDivisorRadius == activeDivisorRadius && + other.inactiveDivisorRadius == inactiveDivisorRadius && + other.thumbStrokeWidth == thumbStrokeWidth && + other.activeDivisorStrokeWidth == activeDivisorStrokeWidth && + other.inactiveDivisorStrokeWidth == inactiveDivisorStrokeWidth; } @override int get hashCode { - return hashList([ + return hashList([ brightness, activeTrackHeight, inactiveTrackHeight, @@ -768,7 +764,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final double activeDivisorRadius; + final double? activeDivisorRadius; /// Specifies the radius for the inactive divisor in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -801,7 +797,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final double inactiveDivisorRadius; + final double? inactiveDivisorRadius; /// Specifies the stroke width for the thumb in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -835,7 +831,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final double thumbStrokeWidth; + final double? thumbStrokeWidth; /// Specifies the stroke width for the active divisor in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -869,7 +865,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final double activeDivisorStrokeWidth; + final double? activeDivisorStrokeWidth; /// Specifies the stroke width for the inactive divisor in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -903,7 +899,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final double inactiveDivisorStrokeWidth; + final double? inactiveDivisorStrokeWidth; /// Specifies the size for tick. /// Specifies the size for the major ticks in the [SfSlider], @@ -941,7 +937,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Size tickSize; + final Size? tickSize; /// Specifies the size for the minor ticks in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -978,7 +974,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Size minorTickSize; + final Size? minorTickSize; /// Adjust the space around ticks in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1013,7 +1009,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Offset tickOffset; + final Offset? tickOffset; /// Adjust the space around labels in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1052,7 +1048,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Offset labelOffset; + final Offset? labelOffset; /// Specifies the appearance for inactive label in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1096,7 +1092,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final TextStyle inactiveLabelStyle; + final TextStyle? inactiveLabelStyle; /// Specifies the appearance for active label in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1136,7 +1132,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final TextStyle activeLabelStyle; + final TextStyle? activeLabelStyle; /// Specifies the appearance for the tooltip in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1172,7 +1168,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final TextStyle tooltipTextStyle; + final TextStyle? tooltipTextStyle; /// Specifies the color for the inactive track in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1212,7 +1208,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color inactiveTrackColor; + final Color? inactiveTrackColor; /// Specifies the color for active track in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1248,7 +1244,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color activeTrackColor; + final Color? activeTrackColor; /// Specifies the color for the thumb in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1281,7 +1277,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color thumbColor; + final Color? thumbColor; /// Specifies the stroke color for the thumb in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1315,7 +1311,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color thumbStrokeColor; + final Color? thumbStrokeColor; /// Specifies the stroke color for the active divisor in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1354,7 +1350,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color activeDivisorStrokeColor; + final Color? activeDivisorStrokeColor; /// Specifies the stroke color for the inactive divisor in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1397,7 +1393,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color inactiveDivisorStrokeColor; + final Color? inactiveDivisorStrokeColor; /// Specifies the color for active tick. /// Specifies the color for the active major ticks in the [SfSlider], @@ -1739,7 +1735,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color overlayColor; + final Color? overlayColor; /// Specifies the color for the inactive divisors in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1783,7 +1779,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color inactiveDivisorColor; + final Color? inactiveDivisorColor; /// Specifies the color for the active divisors in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1822,7 +1818,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color activeDivisorColor; + final Color? activeDivisorColor; /// Specifies the color for the disabled active track in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1854,7 +1850,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color disabledActiveTrackColor; + final Color? disabledActiveTrackColor; /// Specifies the color for the disabled inactive track in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1891,7 +1887,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color disabledInactiveTrackColor; + final Color? disabledInactiveTrackColor; /// Specifies the color for the disabled active divisors in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1926,7 +1922,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color disabledActiveDivisorColor; + final Color? disabledActiveDivisorColor; /// Specifies the color for the disabled inactive divisors in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -1966,7 +1962,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color disabledInactiveDivisorColor; + final Color? disabledInactiveDivisorColor; /// Specifies the color for the disabled thumb in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -2026,7 +2022,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final Color tooltipBackgroundColor; + final Color? tooltipBackgroundColor; /// Specifies the radius for the track corners in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. @@ -2058,7 +2054,7 @@ class SfSliderThemeData with Diagnosticable { /// ) /// ) /// ``` - final double trackCornerRadius; + final double? trackCornerRadius; /// Specifies the radius for the overlay in the [SfSlider], [SfRangeSlider], /// and [SfRangeSelector]. diff --git a/packages/syncfusion_flutter_core/lib/src/theme/theme_widget.dart b/packages/syncfusion_flutter_core/lib/src/theme/theme_widget.dart index bb37b122d..58d4a7639 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/theme_widget.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/theme_widget.dart @@ -40,9 +40,9 @@ import 'range_selector_theme.dart'; class SfTheme extends StatelessWidget { /// Creating an argument constructor of SfTheme class. const SfTheme({ - Key key, + Key? key, this.data, - @required this.child, + required this.child, }) : super(key: key); /// Specifies a widget that can hold single child. @@ -87,7 +87,7 @@ class SfTheme extends StatelessWidget { /// ); /// } /// ``` - final SfThemeData data; + final SfThemeData? data; //ignore: unused_field static final SfThemeData _kFallbackTheme = SfThemeData.fallback(); @@ -99,7 +99,7 @@ class SfTheme extends StatelessWidget { /// build context. /// static SfThemeData of(BuildContext context) { - final _SfInheritedTheme inheritedTheme = + final _SfInheritedTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_SfInheritedTheme>(); return inheritedTheme?.data ?? (Theme.of(context).brightness == Brightness.light @@ -114,10 +114,10 @@ class SfTheme extends StatelessWidget { } class _SfInheritedTheme extends InheritedTheme { - const _SfInheritedTheme({Key key, this.data, Widget child}) + const _SfInheritedTheme({Key? key, this.data, required Widget child}) : super(key: key, child: child); - final SfThemeData data; + final SfThemeData? data; @override bool updateShouldNotify(_SfInheritedTheme oldWidget) => @@ -125,7 +125,7 @@ class _SfInheritedTheme extends InheritedTheme { @override Widget wrap(BuildContext context, Widget child) { - final _SfInheritedTheme ancestorTheme = + final _SfInheritedTheme? ancestorTheme = context.findAncestorWidgetOfExactType<_SfInheritedTheme>(); return identical(this, ancestorTheme) ? child @@ -159,19 +159,19 @@ class _SfInheritedTheme extends InheritedTheme { class SfThemeData with Diagnosticable { /// Creating an argument constructor of SfThemeData class. factory SfThemeData( - {Brightness brightness, - SfPdfViewerThemeData pdfViewerThemeData, - SfChartThemeData chartThemeData, - SfCalendarThemeData calendarThemeData, - SfDataGridThemeData dataGridThemeData, - SfDataPagerThemeData dataPagerThemeData, - SfDateRangePickerThemeData dateRangePickerThemeData, - SfBarcodeThemeData barcodeThemeData, - SfGaugeThemeData gaugeThemeData, - SfSliderThemeData sliderThemeData, - SfRangeSliderThemeData rangeSliderThemeData, - SfRangeSelectorThemeData rangeSelectorThemeData, - SfMapsThemeData mapsThemeData}) { + {Brightness? brightness, + SfPdfViewerThemeData? pdfViewerThemeData, + SfChartThemeData? chartThemeData, + SfCalendarThemeData? calendarThemeData, + SfDataGridThemeData? dataGridThemeData, + SfDataPagerThemeData? dataPagerThemeData, + SfDateRangePickerThemeData? dateRangePickerThemeData, + SfBarcodeThemeData? barcodeThemeData, + SfGaugeThemeData? gaugeThemeData, + SfSliderThemeData? sliderThemeData, + SfRangeSliderThemeData? rangeSliderThemeData, + SfRangeSelectorThemeData? rangeSelectorThemeData, + SfMapsThemeData? mapsThemeData}) { brightness ??= Brightness.light; pdfViewerThemeData = pdfViewerThemeData ?? SfPdfViewerThemeData(brightness: brightness); @@ -218,32 +218,19 @@ class SfThemeData with Diagnosticable { /// [SfThemeData] constructor. /// const SfThemeData.raw( - {@required this.brightness, - @required this.pdfViewerThemeData, - @required this.chartThemeData, - @required this.calendarThemeData, - @required this.dataGridThemeData, - @required this.dateRangePickerThemeData, - @required this.barcodeThemeData, - @required this.gaugeThemeData, - @required this.sliderThemeData, - @required this.rangeSelectorThemeData, - @required this.rangeSliderThemeData, - @required this.mapsThemeData, - @required this.dataPagerThemeData}) - : assert(brightness != null), - assert(pdfViewerThemeData != null), - assert(chartThemeData != null), - assert(calendarThemeData != null), - assert(dateRangePickerThemeData != null), - assert(barcodeThemeData != null), - assert(gaugeThemeData != null), - assert(sliderThemeData != null), - assert(rangeSelectorThemeData != null), - assert(rangeSliderThemeData != null), - assert(mapsThemeData != null), - assert(dataGridThemeData != null), - assert(dataPagerThemeData != null); + {required this.brightness, + required this.pdfViewerThemeData, + required this.chartThemeData, + required this.calendarThemeData, + required this.dataGridThemeData, + required this.dateRangePickerThemeData, + required this.barcodeThemeData, + required this.gaugeThemeData, + required this.sliderThemeData, + required this.rangeSelectorThemeData, + required this.rangeSliderThemeData, + required this.mapsThemeData, + required this.dataPagerThemeData}); /// This method returns the light theme when no theme has been specified. factory SfThemeData.light() => SfThemeData(brightness: Brightness.light); @@ -499,18 +486,19 @@ class SfThemeData with Diagnosticable { /// Creates a copy of this theme but with the given /// fields replaced with the new values. SfThemeData copyWith( - {Brightness brightness, - SfPdfViewerThemeData pdfViewerThemeData, - SfChartThemeData chartThemeData, - SfCalendarThemeData calendarThemeData, - SfDataGridThemeData dataGridThemeData, - SfDateRangePickerThemeData dateRangePickerThemeData, - SfBarcodeThemeData barcodeThemeData, - SfSliderThemeData sliderThemeData, - SfRangeSelectorThemeData rangeSelectorThemeData, - SfRangeSliderThemeData rangeSliderThemeData, - SfMapsThemeData mapsThemeData, - SfDataPagerThemeData dataPagerThemeData}) { + {Brightness? brightness, + SfPdfViewerThemeData? pdfViewerThemeData, + SfChartThemeData? chartThemeData, + SfCalendarThemeData? calendarThemeData, + SfDataGridThemeData? dataGridThemeData, + SfDateRangePickerThemeData? dateRangePickerThemeData, + SfBarcodeThemeData? barcodeThemeData, + SfGaugeThemeData? gaugeThemeData, + SfSliderThemeData? sliderThemeData, + SfRangeSelectorThemeData? rangeSelectorThemeData, + SfRangeSliderThemeData? rangeSliderThemeData, + SfMapsThemeData? mapsThemeData, + SfDataPagerThemeData? dataPagerThemeData}) { return SfThemeData.raw( brightness: brightness ?? this.brightness, pdfViewerThemeData: pdfViewerThemeData ?? this.pdfViewerThemeData, @@ -521,7 +509,7 @@ class SfThemeData with Diagnosticable { dateRangePickerThemeData: dateRangePickerThemeData ?? this.dateRangePickerThemeData, barcodeThemeData: barcodeThemeData ?? this.barcodeThemeData, - gaugeThemeData: gaugeThemeData ?? this.barcodeThemeData, + gaugeThemeData: gaugeThemeData ?? this.gaugeThemeData, sliderThemeData: sliderThemeData ?? this.sliderThemeData, rangeSelectorThemeData: rangeSelectorThemeData ?? this.rangeSelectorThemeData, @@ -530,36 +518,36 @@ class SfThemeData with Diagnosticable { } /// Linearly interpolate between two themes. - static SfThemeData lerp(SfThemeData a, SfThemeData b, double t) { + static SfThemeData lerp(SfThemeData? a, SfThemeData? b, double t) { assert(a != null); assert(b != null); - assert(t != null); + return SfThemeData.raw( - brightness: t < 0.5 ? a.brightness : b.brightness, + brightness: t < 0.5 ? a!.brightness : b!.brightness, pdfViewerThemeData: SfPdfViewerThemeData.lerp( - a.pdfViewerThemeData, b.pdfViewerThemeData, t), + a!.pdfViewerThemeData, b!.pdfViewerThemeData, t)!, chartThemeData: - SfChartThemeData.lerp(a.chartThemeData, b.chartThemeData, t), + SfChartThemeData.lerp(a.chartThemeData, b.chartThemeData, t)!, calendarThemeData: SfCalendarThemeData.lerp( - a.calendarThemeData, b.calendarThemeData, t), + a.calendarThemeData, b.calendarThemeData, t)!, dataGridThemeData: SfDataGridThemeData.lerp( - a.dataGridThemeData, b.dataGridThemeData, t), + a.dataGridThemeData, b.dataGridThemeData, t)!, dataPagerThemeData: SfDataPagerThemeData.lerp( - a.dataPagerThemeData, b.dataPagerThemeData, t), + a.dataPagerThemeData, b.dataPagerThemeData, t)!, dateRangePickerThemeData: SfDateRangePickerThemeData.lerp( - a.dateRangePickerThemeData, b.dateRangePickerThemeData, t), + a.dateRangePickerThemeData, b.dateRangePickerThemeData, t)!, barcodeThemeData: - SfBarcodeThemeData.lerp(a.barcodeThemeData, b.barcodeThemeData, t), + SfBarcodeThemeData.lerp(a.barcodeThemeData, b.barcodeThemeData, t)!, gaugeThemeData: - SfGaugeThemeData.lerp(a.gaugeThemeData, b.gaugeThemeData, t), + SfGaugeThemeData.lerp(a.gaugeThemeData, b.gaugeThemeData, t)!, sliderThemeData: - SfSliderThemeData.lerp(a.sliderThemeData, b.sliderThemeData, t), + SfSliderThemeData.lerp(a.sliderThemeData, b.sliderThemeData, t)!, rangeSelectorThemeData: SfRangeSelectorThemeData.lerp( - a.rangeSelectorThemeData, b.rangeSelectorThemeData, t), + a.rangeSelectorThemeData, b.rangeSelectorThemeData, t)!, rangeSliderThemeData: SfRangeSliderThemeData.lerp( - a.rangeSliderThemeData, b.rangeSliderThemeData, t), + a.rangeSliderThemeData, b.rangeSliderThemeData, t)!, mapsThemeData: - SfMapsThemeData.lerp(a.mapsThemeData, b.mapsThemeData, t)); + SfMapsThemeData.lerp(a.mapsThemeData, b.mapsThemeData, t)!); } @override @@ -567,20 +555,21 @@ class SfThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - final SfThemeData otherData = other; - return otherData.brightness == brightness && - otherData.pdfViewerThemeData == pdfViewerThemeData && - otherData.chartThemeData == chartThemeData && - otherData.calendarThemeData == calendarThemeData && - otherData.dataGridThemeData == dataGridThemeData && - otherData.dataPagerThemeData == dataPagerThemeData && - otherData.dateRangePickerThemeData == dateRangePickerThemeData && - otherData.barcodeThemeData == barcodeThemeData && - otherData.gaugeThemeData == gaugeThemeData && - otherData.sliderThemeData == sliderThemeData && - otherData.rangeSelectorThemeData == rangeSelectorThemeData && - otherData.rangeSliderThemeData == rangeSliderThemeData && - otherData.mapsThemeData == mapsThemeData; + + return other is SfThemeData && + other.brightness == brightness && + other.pdfViewerThemeData == pdfViewerThemeData && + other.chartThemeData == chartThemeData && + other.calendarThemeData == calendarThemeData && + other.dataGridThemeData == dataGridThemeData && + other.dataPagerThemeData == dataPagerThemeData && + other.dateRangePickerThemeData == dateRangePickerThemeData && + other.barcodeThemeData == barcodeThemeData && + other.gaugeThemeData == gaugeThemeData && + other.sliderThemeData == sliderThemeData && + other.rangeSelectorThemeData == rangeSelectorThemeData && + other.rangeSliderThemeData == rangeSliderThemeData && + other.mapsThemeData == mapsThemeData; } @override diff --git a/packages/syncfusion_flutter_core/lib/src/tooltip/tooltip.dart b/packages/syncfusion_flutter_core/lib/src/tooltip/tooltip.dart new file mode 100644 index 000000000..a3bc6840f --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/src/tooltip/tooltip.dart @@ -0,0 +1,1062 @@ +part of tooltip_internal; + +/// Renders the tooltip widget. +/// +/// This class provides options for customizing the properties of the tooltip. +class SfTooltip extends StatefulWidget { + /// Creating an argument constructor of SfTooltip class. + SfTooltip( + {this.textStyle = const TextStyle(), + this.animationDuration = 500, + this.enable = true, + this.opacity = 1, + this.borderColor = Colors.black, + this.borderWidth = 0, + this.duration = 3000, + this.shouldAlwaysShow = false, + this.elevation = 0, + this.canShowMarker = true, + this.textAlignment = TooltipAlignment.near, + this.decimalPlaces = 2, + this.color = Colors.black, + this.labelColor = Colors.white, + this.header, + this.format, + this.builder, + this.shadowColor, + Key? key, + this.onTooltipRender}) + : super(key: key); + + ///Toggles the visibility of the tooltip. + /// + ///Defaults to `true`. + final bool enable; + + ///Color of the tooltip. + /// + ///Defaults to `Colors.black`. + final Color color; + + ///Color of the tooltip label. + /// + ///Defaults to `Colors.white`. + final Color labelColor; + + ///Color of the tooltip border. + /// + ///Defaults to `Colors.black`. + final Color borderColor; + + ///Color of the tooltip shadow. + /// + ///Defaults to tooltip color. + final Color? shadowColor; + + /// Header of the tooltip. By default, there will be no header. + final String? header; + + ///Opacity of the tooltip. + /// + ///The value ranges from 0 to 1. + /// + ///Defaults to `1`. + final double opacity; + + ///Customizes the tooltip text style + final TextStyle textStyle; + + ///Specifies the number decimals to be displayed in tooltip text + /// + ///Defaults to `2`. + final int decimalPlaces; + + ///Formats the tooltip text. + /// + ///By default, the tooltip will be rendered with x and y-values. + /// + ///You can add prefix or suffix to x, y values in the + ///tooltip by formatting them. + final String? format; + + ///Duration for animating the tooltip. + /// + ///Defaults to `500`. + final int animationDuration; + + ///Toggles the visibility of the marker in the tooltip. + /// + ///Defaults to `true`. + final bool canShowMarker; + + ///Border width of the tooltip. + /// + ///Defaults to `0`. + final double borderWidth; + + ///Builder of the tooltip. + /// + ///Defaults to `null`. + final dynamic builder; + + ///Elevation of the tooltip. + /// + ///Defaults to `0`. + final double elevation; + + ///Shows or hides the tooltip. + /// + ///By default, the tooltip will be hidden on touch. To avoid this, set this property to true. + /// + ///Defaults to `false`. + final bool shouldAlwaysShow; + + ///Duration for displaying the tooltip. + /// + ///Defaults to `3000`. + final double duration; + + ///Alignment of the text in the tooltip + final dynamic textAlignment; + + /// Occurs while tooltip is rendered. You can customize the text, position and header. + /// Here, you can get the text, header, x and y-positions. + final void Function(TooltipRenderArgs tooltipRenderArgs)? onTooltipRender; + + /// Displays the tooltip at the position. + /// + /// + /// *position - the x and y position at which the tooltip needs to be shown. + /// *duration - the duration in milliseconds for which the tooltip animation needs to happen. + /// *template - the widget that will be + void show(Offset? position, int duration, [Widget? template]) { + if (position != null) { + // ignore: avoid_as + final GlobalKey tooltipKey = key as GlobalKey; + // ignore: avoid_as + final SfTooltipState state = tooltipKey.currentState as SfTooltipState; + state.animationController!.duration = Duration(milliseconds: duration); + state.renderBox?.calculateLocation(position); + state._show = true; + if (template == null) { + // ignore: invalid_use_of_protected_member + state.setState(() {}); + } else { + state._template = template; + // ignore: invalid_use_of_protected_member + state.setState(() {}); + } + } + } + + /// Hides the tooltip if it is currently displayed. + void hide([int? duration]) { + // ignore: avoid_as + final GlobalKey tooltipKey = key! as GlobalKey; + // ignore: avoid_as + final SfTooltipState state = tooltipKey.currentState as SfTooltipState; + state._show = false; + state.animationController!.duration = Duration(milliseconds: duration ?? 0); + state.animationController!.reverse(from: 1.0); + } + + @override + SfTooltipState createState() => SfTooltipState(); +} + +/// Represents the state class of [SfTooltip] widget +class SfTooltipState extends State + with SingleTickerProviderStateMixin { + /// Animation controller for tooltip + AnimationController? animationController; + + /// Determibes the visibility of the tooltip. + late bool _show; + + /// whether to render marker or not. + late bool needMarker; + + /// holds the instance of the tooltip renderbox associated with this state. + TooltipRenderBox? renderBox; + + Widget? _template; + + @override + void initState() { + _show = false; + needMarker = widget.canShowMarker; + animationController = AnimationController( + duration: Duration(milliseconds: widget.animationDuration), + vsync: this); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final Animation tooltipAnimation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: animationController!, + curve: const Interval(0.1, 0.8, curve: Curves.easeOutBack), + )); + if (_show) { + animationController!.forward(from: 0.0); + } + _template = widget.builder != null ? (_template ?? Container()) : null; + return AnimatedBuilder( + animation: animationController!, + builder: (BuildContext context, Widget? child) { + if (renderBox != null) { + renderBox!.animationFactor = animationController!.value; + } + return child!; + }, + child: TooltipRenderObject( + template: _template, + tooltipAnimation: tooltipAnimation, + tooltipState: this, + animationController: animationController!), + ); + } + + @override + void dispose() { + animationController!.dispose(); + animationController = null; + super.dispose(); + } +} + +/// Single child render object widget classfor rendering the tooltip +class TooltipRenderObject extends SingleChildRenderObjectWidget { + /// Creating an argument constructor of TooltipRenderObject class. + TooltipRenderObject( + {Widget? template, + required SfTooltipState tooltipState, + required Animation tooltipAnimation, + required AnimationController animationController}) + : _tooltipState = tooltipState, + _tooltipAnimation = tooltipAnimation, + _animationController = animationController, + super(child: template); + + final SfTooltipState _tooltipState; + final Animation _tooltipAnimation; + final AnimationController _animationController; + + @override + TooltipRenderBox createRenderObject(BuildContext context) { + _tooltipState.renderBox = TooltipRenderBox( + _tooltipState, _tooltipAnimation, _animationController); + return _tooltipState.renderBox!; + } + + @override + void updateRenderObject(BuildContext context, TooltipRenderBox renderObject) { + renderObject + ..tooltipAnimation = _tooltipAnimation + ..animationController = _animationController + ..tooltipState = _tooltipState; + } +} + +/// tooltip render box class. This class holds the properties needed to render the tooltip widget. +class TooltipRenderBox extends RenderShiftedBox { + /// Creating an argument constructor of TooltipRenderBox class. + TooltipRenderBox( + this._tooltipState, this._tooltipAnimation, this._animationController, + [RenderBox? child]) + : super(child); + SfTooltip get _tooltip => _tooltipState.widget; + SfTooltipState _tooltipState; + + ///Setter for tooltipState + set tooltipState(SfTooltipState value) { + if (_tooltipState == value) { + return; + } + _tooltipState = value; + } + + late Animation _tooltipAnimation; + + ///Setter for tooltip animation instance + set tooltipAnimation(Animation value) { + if (_tooltipAnimation == value) { + return; + } + _tooltipAnimation = value; + } + + late AnimationController _animationController; + + ///Setter for tooltip animation controller instance + set animationController(AnimationController value) { + if (_animationController == value) { + return; + } + _animationController = value; + } + + late double _animationFactor; + + ///Setter for tooltip animation factor + set animationFactor(double value) { + _animationFactor = value; + markNeedsLayout(); + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + + String? _stringValue = ''; + + ///Setter for tooltip content text + set stringValue(String? value) { + if (_stringValue == value) { + return; + } + _stringValue = value; + } + + String? _header = ''; + + ///Setter for tooltip header text + set header(String? value) { + if (_header == value) { + return; + } + _header = value; + } + + double? _normalPadding = 0; + + ///Setter for tooltip padding + set normalPadding(double value) { + if (_normalPadding == value) { + return; + } + _normalPadding = value; + } + + double? _inversePadding; + + ///Setter for the tooltip padding when the tooltip is rendered upside + set inversePadding(double value) { + if (_inversePadding == value) { + return; + } + _inversePadding = value; + } + + late double _markerSize; + + ///Getter for size of marker rendered in tooltip + double get markerSize => _markerSize; + + bool _canResetPath = false; + + ///Setter for the boolean determining the reset of tooltip path + set canResetPath(bool value) { + if (_canResetPath == value) { + return; + } + _canResetPath = value; + } + + Rect _boundaryRect = const Rect.fromLTWH(0, 0, 0, 0); + + ///Setter for the boundary rect within which the tooltip could be shown + set boundaryRect(Rect value) { + if (_boundaryRect == value) { + return; + } + _boundaryRect = value; + } + + List _markerTypes = []; + + ///Setter for the tooltip marker type + set markerTypes(List types) { + _markerTypes = types; + } + + List _markerPaints = []; + + ///Setter for tooltip marker paint + set markerPaints(List paints) { + _markerPaints = paints; + } + + List _markerImages = []; + + ///Setter for the marker image of tooltip when the data marker type is image + set markerImages(List images) { + _markerImages = images; + } + + List? _markerGradients; + + ///Setter for the tooltip marker gradient + set markerGradients(List values) { + _markerGradients = values; + } + + final double _pointerLength = 10; + double? _xPos, _yPos, _x, _y; + double _nosePointX = 0, _nosePointY = 0, _borderRadius = 5, _totalWidth = 0; + bool _isLeft = false, + _isRight = false, + _isTop = false, + _isOutOfBoundInTop = false; + late double _markerPointY; + Rect? _tooltipRect; + Path _arrowPath = Path(); + late Size _templateSize; + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + if (child == null || _tooltipRect == null) { + return false; + } else { + return child!.hitTest(result, position: position - _tooltipRect!.topLeft); + } + } + + @override + void performLayout() { + if (_tooltipState._show) { + if (child != null) { + _isOutOfBoundInTop = false; + size = Size.copy(_boundaryRect.size); + child!.layout(constraints, parentUsesSize: true); + size = Size.copy(child!.size); + } + } else { + size = Size.zero; + child?.layout(constraints); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + _isOutOfBoundInTop = false; + context.canvas.translate(offset.dx, offset.dy); + if (_tooltipState._show) { + if ((_animationFactor == 0 || _tooltip.animationDuration == 0) && + _tooltipState.widget.onTooltipRender != null) { + final TooltipRenderArgs tooltipRenderArgs = TooltipRenderArgs(_header, + _stringValue, _x != null && _y != null ? Offset(_x!, _y!) : null); + _tooltipState.widget.onTooltipRender!(tooltipRenderArgs); + _x = tooltipRenderArgs.location!.dx; + _y = tooltipRenderArgs.location!.dy; + stringValue = tooltipRenderArgs.text; + header = tooltipRenderArgs.header; + } + if (_tooltip.builder == null) { + _renderDefaultTooltipView(context.canvas); + } else { + _renderTemplateTooltipView(context, offset); + } + } + context.canvas.translate(-offset.dx, -offset.dy); + } + + void _renderTemplateTooltipView(PaintingContext context, Offset offset) { + const double arrowHeight = 5.0; + _templateSize = Size.copy(child!.size); + _tooltipRect = Rect.fromLTWH( + _x! - _templateSize.width / 2, + _y! - _templateSize.height - arrowHeight, + _templateSize.width, + _templateSize.height); + + double top = _y!; + double paddingTop = 0; + final Offset tooltipLocation = + _getTemplateLocation(_tooltipRect!, _boundaryRect); + final Offset arrowLocation = Offset(_x! - _templateSize.width / 2, + _isOutOfBoundInTop ? _y! : _y! - arrowHeight); + if (_y! < _boundaryRect.top) { + paddingTop = _boundaryRect.top + arrowHeight; + top = tooltipLocation.dy; + } + top = _isOutOfBoundInTop ? top + arrowHeight : _tooltipRect!.top; + if (_y! >= _boundaryRect.top) { + paddingTop = top; + } + context.pushTransform(true, Offset(_x!, _y!) + offset, + Matrix4.diagonal3Values(_animationFactor, _animationFactor, 1), + (tooltipTemplateContext, tooltipTemplateOffset) { + tooltipTemplateContext.paintChild( + child!, + (_isOutOfBoundInTop + ? Offset(tooltipLocation.dx, tooltipLocation.dy + paddingTop) + : tooltipLocation) + + offset); + _renderArrow(tooltipTemplateContext.canvas, arrowLocation + offset); + }); + } + + void _renderArrow(Canvas canvas, Offset location) { + const double currentHeight = 5.0; + const double arrowWidth = 8; + const double padding = 2; + final Size currentSize = Size(_templateSize.width, currentHeight); + final num templateHeight = _templateSize.height; + final num arrowHeight = currentSize.height + padding; + final num centerTemplateY = _isOutOfBoundInTop + ? location.dy + currentSize.height + templateHeight / 2 + padding + : location.dy - templateHeight / 2 - padding; + final double locationY = _isOutOfBoundInTop + ? centerTemplateY - (templateHeight / 2) - arrowHeight + : centerTemplateY + templateHeight / 2; + final num centerX = location.dx + currentSize.width / 2; + _arrowPath = Path() + ..moveTo(centerX + (_isOutOfBoundInTop ? 0 : -arrowWidth), locationY) + ..lineTo(centerX + (_isOutOfBoundInTop ? -arrowWidth : arrowWidth), + (locationY) + (_isOutOfBoundInTop ? arrowHeight : 0)) + ..lineTo(centerX + (_isOutOfBoundInTop ? arrowWidth : 0), + locationY + arrowHeight) + ..close(); + canvas.drawPath( + _arrowPath, + Paint() + ..color = (_tooltip.color).withOpacity(_tooltip.opacity) + ..style = PaintingStyle.fill); + } + + /// To get the location of chart tooltip + void calculateLocation(Offset? position) { + _x = position?.dx; + _y = position?.dy; + } + + void _renderDefaultTooltipView(Canvas canvas) { + _isLeft = false; + _isRight = false; + double height = 0, width = 0, headerTextWidth = 0, headerTextHeight = 0; + _markerSize = 0; + _totalWidth = + _boundaryRect.left.toDouble() + _boundaryRect.width.toDouble(); + //ignore: prefer_final_locals + TextStyle _textStyle = _tooltip.textStyle; + final TextStyle textStyle = + _textStyle.copyWith(color: _textStyle.color ?? _tooltip.labelColor); + width = measureText(_stringValue!, textStyle).width; + height = measureText(_stringValue!, textStyle).height; + if (_header!.isNotEmpty) { + final TextStyle headerTextStyle = _textStyle.copyWith( + color: _textStyle.color ?? _tooltip.labelColor, + fontWeight: FontWeight.bold); + headerTextWidth = measureText(_header!, headerTextStyle).width; + headerTextHeight = measureText(_header!, headerTextStyle).height + 10; + width = width > headerTextWidth ? width : headerTextWidth; + } + + if (width < 10) { + width = 10; // minimum width for tooltip to render + _borderRadius = _borderRadius > 5 ? 5 : _borderRadius; + } + if (_borderRadius > 15) { + _borderRadius = 15; + } + if (_x != null && + _y != null && + (_inversePadding != null || _normalPadding != null) && + (_stringValue != '' || _header != '')) { + final Rect backRect = + _calculateBackgroundRect(canvas, height, width, headerTextHeight); + final double startArrow = _pointerLength / 2; + final double endArrow = _pointerLength / 2; + final double xPosition = _nosePointX; + final double yPosition = _nosePointY; + _drawTooltipBackground( + canvas, + _isTop, + backRect, + xPosition, + yPosition, + xPosition - startArrow, + _isTop ? (yPosition - startArrow) : (yPosition + startArrow), + xPosition + endArrow, + _isTop ? (yPosition - endArrow) : (yPosition + endArrow), + _borderRadius, + _arrowPath, + _isLeft, + _isRight, + _tooltipAnimation); + } + } + + /// calculate tooltip rect and arrow head + Rect _calculateBackgroundRect( + Canvas canvas, double height, double width, double headerTextHeight) { + double widthPadding = 15; + if (_tooltip.canShowMarker && _tooltipState.needMarker) { + _markerSize = 5; + widthPadding = 17; + } + + final Rect rect = Rect.fromLTWH( + _x!, + _y!, + width + (2 * _markerSize) + widthPadding, + height + headerTextHeight + 10); + final Rect newRect = Rect.fromLTWH(_boundaryRect.left + 20, + _boundaryRect.top, _boundaryRect.width - 40, _boundaryRect.height); + final Rect leftRect = Rect.fromLTWH( + _boundaryRect.left - 5, + _boundaryRect.top - 20, + newRect.left - (_boundaryRect.left - 5), + _boundaryRect.height + 40); + final Rect rightRect = Rect.fromLTWH(newRect.right, _boundaryRect.top - 20, + (_boundaryRect.right + 5) + newRect.right, _boundaryRect.height + 40); + + if (leftRect.contains(Offset(_x!, _y!))) { + _isLeft = true; + _isRight = false; + } else if (rightRect.contains(Offset(_x!, _y!))) { + _isLeft = false; + _isRight = true; + } + + if (_y! > _pointerLength + rect.height && _y! > _boundaryRect.top) { + _isTop = true; + _xPos = _x! - (rect.width / 2); + _yPos = (_y! - rect.height) - (_normalPadding ?? 0); + _nosePointY = rect.top - (_normalPadding ?? 0); + _nosePointX = rect.left; + final double tooltipRightEnd = _x! + (rect.width / 2); + _xPos = _xPos! < _boundaryRect.left + ? _boundaryRect.left + : tooltipRightEnd > _totalWidth + ? _totalWidth - rect.width + : _xPos; + _yPos = _yPos! - (_pointerLength / 2); + } else { + _isTop = false; + _xPos = _x! - (rect.width / 2); + _yPos = ((_y! >= _boundaryRect.top ? _y! : _boundaryRect.top) + + _pointerLength / 2) + + (_inversePadding ?? 0); + _nosePointX = rect.left; + _nosePointY = (_y! >= _boundaryRect.top ? _y! : _boundaryRect.top) + + (_inversePadding ?? 0); + final double tooltipRightEnd = _x! + (rect.width / 2); + _xPos = _xPos! < _boundaryRect.left + ? _boundaryRect.left + : tooltipRightEnd > _totalWidth + ? _totalWidth - rect.width + : _xPos; + } + if (_xPos! <= _boundaryRect.left + 5) { + _xPos = _xPos! + 5; + } else if (_xPos! + rect.width >= _totalWidth - 5) { + _xPos = _xPos! - 5; + } + return Rect.fromLTWH(_xPos!, _yPos!, rect.width, rect.height); + } + + void _drawTooltipBackground( + Canvas canvas, + bool isTop, + Rect rectF, + double xPosition, + double yPosition, + double startX, + double startY, + double endX, + double endY, + double borderRadius, + Path backgroundPath, + bool isLeft, + bool isRight, + Animation? tooltipAnimation) { + final double animationFactor = + tooltipAnimation == null ? 1 : tooltipAnimation.value; + backgroundPath.reset(); + if (!_canResetPath) { + if (isLeft) { + startX = rectF.left + (2 * borderRadius); + endX = startX + _pointerLength; + } else if (isRight) { + startX = endX - _pointerLength; + endX = rectF.right - (2 * borderRadius); + } + + final Rect rect = Rect.fromLTWH( + rectF.width / 2 + (rectF.left - rectF.width / 2 * animationFactor), + rectF.height / 2 + (rectF.top - rectF.height / 2 * animationFactor), + rectF.width * animationFactor, + rectF.height * animationFactor); + + _tooltipRect = rect; + + final RRect tooltipRect = RRect.fromRectAndCorners( + rect, + bottomLeft: Radius.circular(borderRadius), + bottomRight: Radius.circular(borderRadius), + topLeft: Radius.circular(borderRadius), + topRight: Radius.circular(borderRadius), + ); + _drawTooltipPath(canvas, tooltipRect, rect, backgroundPath, isTop, isLeft, + isRight, startX, endX, animationFactor, xPosition, yPosition); + + final TextStyle textStyle = _tooltip.textStyle.copyWith( + color: _tooltip.textStyle.color?.withOpacity(_tooltip.opacity) ?? + _tooltip.labelColor, + fontSize: (_tooltip.textStyle.fontSize ?? 12.0) * animationFactor, + ); + final Size result = measureText(_stringValue!, textStyle); + _drawTooltipText(canvas, tooltipRect, textStyle, result, animationFactor); + + if (_tooltip.canShowMarker && + _tooltipState.needMarker && + _markerTypes.isNotEmpty) { + if (_markerTypes.length == 1) { + final Offset markerPoint = Offset( + tooltipRect.left + tooltipRect.width / 2 - result.width / 2, + ((tooltipRect.top + tooltipRect.height) - result.height / 2) - + markerSize); + _drawMarkers(markerPoint, canvas, animationFactor, 0); + } else { + Size textSize = const Size(0, 0); + final List textValues = _stringValue!.split('\n'); + for (int i = 0; + i < _markerTypes.length && i < textValues.length; + i++) { + String str = ''; + str += textValues[i]; + final Size result1 = measureText(str, textStyle); + final Offset markerPoint = Offset( + tooltipRect.left + tooltipRect.width / 2 - result1.width / 2, + (_markerPointY + textSize.height) - markerSize); + textSize = result1; + _drawMarkers(markerPoint, canvas, animationFactor, i); + } + } + } + } + _xPos = null; + _yPos = null; + } + + void _drawMarkers( + Offset markerPoint, Canvas canvas, double animationFactor, int i) { + if (_markerImages[i] == null) { + final Path markerPath = _getMarkerShapesPath( + _markerTypes[i]!, + markerPoint, + _markerImages[i], + Size((2 * _markerSize) * animationFactor, + (2 * _markerSize) * animationFactor), + ); + if (_markerGradients![i] != null) { + _markerPaints[i] = Paint() + ..shader = _markerGradients![i]!.createShader( + _getMarkerShapesPath( + _markerTypes[i]!, + Offset(markerPoint.dx, markerPoint.dy), + _markerImages[i], + Size((2 * _markerSize) * animationFactor, + (2 * _markerSize) * animationFactor)) + .getBounds(), + ) + ..style = PaintingStyle.fill; + } + canvas.drawPath(markerPath, _markerPaints[i]!); + final Paint markerBorderPaint = Paint(); + markerBorderPaint.color = Colors.white.withOpacity(_tooltip.opacity); + markerBorderPaint.strokeWidth = 1; + markerBorderPaint.style = PaintingStyle.stroke; + canvas.drawPath(markerPath, markerBorderPaint); + } else { + _markerSize *= 2 * animationFactor; + final Rect positionRect = Rect.fromLTWH(markerPoint.dx - _markerSize / 2, + markerPoint.dy - _markerSize / 2, _markerSize, _markerSize); + paintImage( + canvas: canvas, + image: _markerImages[i], + rect: positionRect, + fit: BoxFit.fill); + } + } + + /// draw the tooltip rect path + void _drawTooltipPath( + Canvas canvas, + RRect tooltipRect, + Rect rect, + Path backgroundPath, + bool isTop, + bool isLeft, + bool isRight, + double startX, + double endX, + double animationFactor, + double xPosition, + double yPosition) { + double factor = 0; + assert(_tooltip.elevation >= 0, + 'The elevation of the tooltip for all series must not be less than 0.'); + if (isRight) { + factor = isTop ? rect.bottom : rect.top; + backgroundPath.moveTo(rect.right - 20, factor); + backgroundPath.lineTo(xPosition, yPosition); + backgroundPath.lineTo(rect.right, + isTop ? (factor - _borderRadius) : (factor + _borderRadius)); + backgroundPath.arcToPoint(Offset(rect.right - _borderRadius, factor), + radius: Radius.circular(_borderRadius), clockwise: isTop); + backgroundPath.lineTo(rect.right - 20, factor); + } else if (isLeft) { + factor = isTop ? rect.bottom : rect.top; + backgroundPath.moveTo(rect.left + 20, factor); + backgroundPath.lineTo(xPosition, yPosition); + backgroundPath.lineTo(rect.left, + isTop ? (factor - _borderRadius) : (factor + _borderRadius)); + backgroundPath.arcToPoint(Offset(rect.left + _borderRadius, factor), + radius: Radius.circular(_borderRadius), clockwise: !isTop); + backgroundPath.lineTo(rect.left + 20, factor); + } else { + factor = isTop ? tooltipRect.bottom : tooltipRect.top; + backgroundPath.moveTo(startX - ((endX - startX) / 4), factor); + backgroundPath.lineTo(xPosition, yPosition); + backgroundPath.lineTo(endX + ((endX - startX) / 4), factor); + backgroundPath.lineTo(startX + ((endX - startX) / 4), factor); + } + final Paint fillPaint = Paint() + ..color = (_tooltip.color).withOpacity(_tooltip.opacity) + ..strokeCap = StrokeCap.round + ..style = PaintingStyle.fill; + + final Paint strokePaint = Paint() + ..color = _tooltip.borderColor == Colors.transparent + ? Colors.transparent + : _tooltip.borderColor.withOpacity(_tooltip.opacity) + ..strokeCap = StrokeCap.butt + ..style = PaintingStyle.stroke + ..strokeWidth = _tooltip.borderWidth; + _tooltip.borderWidth == 0 + ? strokePaint.color = Colors.transparent + : strokePaint.color = strokePaint.color; + + final Path tooltipPath = Path(); + tooltipPath.addRRect(tooltipRect); + if (_tooltip.elevation > 0) { + if (tooltipRect.width * animationFactor > tooltipRect.width * 0.85) { + canvas.drawShadow(_arrowPath, _tooltip.shadowColor ?? fillPaint.color, + _tooltip.elevation, true); + } + canvas.drawShadow(tooltipPath, _tooltip.shadowColor ?? fillPaint.color, + _tooltip.elevation, true); + } + + if (tooltipRect.width * animationFactor > tooltipRect.width * 0.85) { + canvas.drawPath(_arrowPath, fillPaint); + canvas.drawPath(_arrowPath, strokePaint); + } + canvas.drawPath(tooltipPath, fillPaint); + canvas.drawPath(tooltipPath, strokePaint); + } + + /// draw tooltip header, divider,text + void _drawTooltipText(Canvas canvas, RRect tooltipRect, TextStyle textStyle, + Size result, double animationFactor) { + const double padding = 10; + final int _maxLinesOfTooltipContent = getMaxLinesContent(_stringValue); + if (_header!.isNotEmpty) { + final TextStyle headerTextStyle = _tooltip.textStyle.copyWith( + color: textStyle.color?.withOpacity(_tooltip.opacity) ?? + _tooltip.labelColor, + fontSize: (textStyle.fontSize ?? 12) * animationFactor, + fontWeight: FontWeight.bold, + ); + final Size headerResult = measureText(_header!, headerTextStyle); + _markerPointY = tooltipRect.top + + ((_header!.isNotEmpty) + ? headerResult.height + (padding * 2) + 6 + : (padding * 1.7)); + final int maxLinesOfHeader = getMaxLinesContent(_header); + _drawText( + _tooltip, + canvas, + _header!, + Offset( + (tooltipRect.left + tooltipRect.width / 2) - + headerResult.width / 2, + tooltipRect.top + padding / 2), + headerTextStyle, + maxLinesOfHeader); + + final Paint dividerPaint = Paint(); + dividerPaint.color = _tooltip.labelColor.withOpacity(_tooltip.opacity); + dividerPaint.strokeWidth = 0.5 * animationFactor; + dividerPaint.style = PaintingStyle.stroke; + num lineOffset = 0; + if (_tooltip.format != null && _tooltip.format!.isNotEmpty) { + if (_tooltip.textAlignment == TooltipAlignment.near) { + lineOffset = padding; + } else if (_tooltip.textAlignment == TooltipAlignment.far) { + lineOffset = -padding; + } + } + if (animationFactor > 0.5) { + canvas.drawLine( + Offset(tooltipRect.left + padding - lineOffset, + tooltipRect.top + headerResult.height + padding), + Offset(tooltipRect.right - padding - lineOffset, + tooltipRect.top + headerResult.height + padding), + dividerPaint); + } + _drawText( + _tooltip, + canvas, + _stringValue!, + Offset( + (tooltipRect.left + 2 * _markerSize + tooltipRect.width / 2) - + result.width / 2, + (tooltipRect.top + tooltipRect.height) - result.height - 5), + textStyle, + _maxLinesOfTooltipContent); + } else { + _drawText( + _tooltip, + canvas, + _stringValue!, + Offset( + (tooltipRect.left + 2 * _markerSize + tooltipRect.width / 2) - + result.width / 2, + (tooltipRect.top + tooltipRect.height / 2) - result.height / 2), + textStyle, + _maxLinesOfTooltipContent); + } + } + + ///draw tooltip text + void _drawText(dynamic tooltip, Canvas canvas, String text, Offset point, + TextStyle style, + [int? maxLines, int? rotation]) { + TextAlign tooltipTextAlign = TextAlign.start; + double pointX = point.dx; + if (tooltip != null && + tooltip.format != null && + tooltip.format.isNotEmpty) { + if (tooltip.textAlignment == 'near') { + tooltipTextAlign = TextAlign.start; + pointX = _tooltipRect!.left; + } else if (tooltip.textAlignment == 'far') { + tooltipTextAlign = TextAlign.end; + pointX = _tooltipRect!.right - measureText(text, style).width; + } + } + if (kIsWeb) { + if (_animationFactor < 0.5) { + style = + style.copyWith(color: style.color!.withOpacity(_animationFactor)); + } else if (_animationFactor <= 1) { + style = + style.copyWith(color: style.color!.withOpacity(tooltip.opacity)); + } + } + final TextSpan span = TextSpan(text: text, style: style); + + final TextPainter tp = TextPainter( + text: span, + textDirection: TextDirection.ltr, + textAlign: tooltipTextAlign, + maxLines: maxLines ?? 1); + tp.layout(); + canvas.save(); + canvas.translate(pointX, point.dy); + if (rotation != null && rotation > 0) { + canvas.rotate(degreeToRadian(rotation)); + } + tp.paint(canvas, const Offset(0.0, 0.0)); + canvas.restore(); + } + + /// It returns the offset values of tooltip location + Offset _getTemplateLocation(Rect tooltipRect, Rect bounds) { + double left = tooltipRect.left, top = tooltipRect.top; + if (tooltipRect.left < bounds.left) { + left = bounds.left; + } + if (tooltipRect.top < bounds.top) { + top = bounds.top; + _isOutOfBoundInTop = true; + } + if (tooltipRect.left + tooltipRect.width > bounds.left + bounds.width) { + left = (bounds.left + bounds.width) - tooltipRect.width; + } + if (tooltipRect.top + tooltipRect.height > bounds.top + bounds.height) { + top = (bounds.top + bounds.height) - tooltipRect.height; + } + return Offset(left, top); + } +} + +/// It returns the path of marker shapes +Path _getMarkerShapesPath( + DataMarkerType markerType, Offset position, dynamic image, Size size) { + // [CartesianSeriesRenderer seriesRenderer, + // [int index, + // // TrackballBehavior trackballBehavior, + // Animation animationController]) { + + final Path path = Path(); + switch (markerType) { + case DataMarkerType.circle: + { + ShapeMaker.drawCircle( + path, position.dx, position.dy, size.width, size.height); + } + break; + case DataMarkerType.rectangle: + { + ShapeMaker.drawRectangle( + path, position.dx, position.dy, size.width, size.height); + } + break; + case DataMarkerType.image: + {} + break; + case DataMarkerType.pentagon: + { + ShapeMaker.drawPentagon( + path, position.dx, position.dy, size.width, size.height); + } + break; + case DataMarkerType.verticalLine: + { + ShapeMaker.drawVerticalLine( + path, position.dx, position.dy, size.width, size.height); + } + break; + case DataMarkerType.invertedTriangle: + { + ShapeMaker.drawInvertedTriangle( + path, position.dx, position.dy, size.width, size.height); + } + break; + case DataMarkerType.horizontalLine: + { + ShapeMaker.drawHorizontalLine( + path, position.dx, position.dy, size.width, size.height); + } + break; + case DataMarkerType.diamond: + { + ShapeMaker.drawDiamond( + path, position.dx, position.dy, size.width, size.height); + } + break; + case DataMarkerType.triangle: + { + ShapeMaker.drawTriangle( + path, position.dx, position.dy, size.width, size.height); + } + break; + case DataMarkerType.none: + break; + } + return path; +} diff --git a/packages/syncfusion_flutter_core/lib/src/utils/helper.dart b/packages/syncfusion_flutter_core/lib/src/utils/helper.dart new file mode 100644 index 000000000..e3e60b195 --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/src/utils/helper.dart @@ -0,0 +1,234 @@ +part of core; + +/// Holds the arguments for the event onTooltipRender. +/// +/// Event is triggered when the tooltip is rendered, which allows you to customize tooltip arguments. +class TooltipRenderArgs { + /// Creating an argument constructor of TooltipArgs class. + TooltipRenderArgs(this.header, this.text, this.location); + + /// Text for the tooltip main content. + String? text; + + /// Text for the tooltip header to be shown. + String? header; + + /// The location at which the tooltip is shown + Offset? location; +} + +/// Used to align the tootip content. +/// +/// Tooltip alignment supports the below alignments. +enum TooltipAlignment { + ///- TooltipAlignment.near, will align the tooltip content nearby to center. + near, + + ///- TooltipAlignment.center, will align the tooltip content at center. + center, + + ///- TooltipAlignment.far, will align the tooltip content far from center. + far +} + +/// Data marker shapes. +/// +/// Data marker supports the below shapes. +/// If the shape is DataMarkerType.image, specify the image path in the imageUrl property of markerSettings. +enum DataMarkerType { + ///- DataMarkerType.cicle, will render marker shape circle. + circle, + + ///- DataMarkerType.rectangle, will render marker shape rectangle. + rectangle, + + ///- DataMarkerType.image, will render marker image. + image, + + ///- DataMarkerType.pentagon, will render marker shape pentagon. + pentagon, + + ///- DataMarkerType.verticalLine, will render marker verticalLine. + verticalLine, + + ///- DataMarkerType.horizontalLine, will render marker horizontalLine. + horizontalLine, + + ///- DataMarkerType.diamond, will render marker shape diamond. + diamond, + + ///- DataMarkerType.triangle, will render marker shape triangle. + triangle, + + ///- DataMarkerType.invertedTriangle, will render marker shape invertedTriangle. + invertedTriangle, + + ///- DataMarkerType.none, will skip rendering marker. + none, +} + +/// Draw different marker shapes by using height and width +class ShapeMaker { + /// Draw the circle shape marker + static void drawCircle( + Path path, double x, double y, double width, double height) { + path.addArc( + Rect.fromLTRB( + x - width / 2, y - height / 2, x + width / 2, y + height / 2), + 0.0, + 2 * math.pi); + } + + /// Draw the Rectangle shape marker + static void drawRectangle( + Path path, double x, double y, double width, double height) { + path.addRect(Rect.fromLTRB( + x - width / 2, y - height / 2, x + width / 2, y + height / 2)); + } + + ///Draw the Pentagon shape marker + static void drawPentagon( + Path path, double x, double y, double width, double height) { + const int eq = 72; + double xValue; + double yValue; + for (int i = 0; i <= 5; i++) { + xValue = width / 2 * math.cos((math.pi / 180) * (i * eq)); + yValue = height / 2 * math.sin((math.pi / 180) * (i * eq)); + i == 0 + ? path.moveTo(x + xValue, y + yValue) + : path.lineTo(x + xValue, y + yValue); + } + path.close(); + } + + ///Draw the Vertical line shape marker + static void drawVerticalLine( + Path path, double x, double y, double width, double height) { + path.moveTo(x, y + height / 2); + path.lineTo(x, y - height / 2); + } + + ///Draw the Inverted Triangle shape marker + static void drawInvertedTriangle( + Path path, double x, double y, double width, double height) { + path.moveTo(x + width / 2, y - height / 2); + + path.lineTo(x, y + height / 2); + path.lineTo(x - width / 2, y - height / 2); + path.lineTo(x + width / 2, y - height / 2); + path.close(); + } + + ///Draw the Horizontal line shape marker + static void drawHorizontalLine( + Path path, double x, double y, double width, double height) { + path.moveTo(x - width / 2, y); + path.lineTo(x + width / 2, y); + } + + ///Draw the Diamond shape marker + static void drawDiamond( + Path path, double x, double y, double width, double height) { + path.moveTo(x - width / 2, y); + path.lineTo(x, y + height / 2); + path.lineTo(x + width / 2, y); + path.lineTo(x, y - height / 2); + path.lineTo(x - width / 2, y); + path.close(); + } + + ///Draw the Triangle shape marker + static void drawTriangle( + Path path, double x, double y, double width, double height) { + path.moveTo(x - width / 2, y + height / 2); + path.lineTo(x + width / 2, y + height / 2); + path.lineTo(x, y - height / 2); + path.lineTo(x - width / 2, y + height / 2); + path.close(); + } +} + +/// This method measures the size for given text and textstyle +Size measureText(String textValue, TextStyle textStyle, [int? angle]) { + Size size; + final TextPainter textPainter = TextPainter( + textAlign: TextAlign.center, + textDirection: TextDirection.ltr, + text: TextSpan(text: textValue, style: textStyle)); + textPainter.layout(); + + if (angle != null) { + final Rect rect = rotatedTextSize(textPainter.size, angle); + size = Size(rect.width, rect.height); + } else { + size = Size(textPainter.width, textPainter.height); + } + return size; +} + +/// This method returns the rect for given size and angle +Rect rotatedTextSize(Size size, int angle) { + final Rect rect = Rect.fromLTWH(0, 0, size.width, size.height); + final vector.Matrix2 _rotatorMatrix = + vector.Matrix2.rotation(degreeToRadian(angle)); + + final Rect movedToCenterAsOrigin = rect.shift(-rect.center); + + Offset _topLeft = movedToCenterAsOrigin.topLeft; + Offset _topRight = movedToCenterAsOrigin.topRight; + Offset _bottomLeft = movedToCenterAsOrigin.bottomLeft; + Offset _bottomRight = movedToCenterAsOrigin.bottomRight; + + _topLeft = transform(_rotatorMatrix, _topLeft); + _topRight = transform(_rotatorMatrix, _topRight); + _bottomLeft = transform(_rotatorMatrix, _bottomLeft); + _bottomRight = transform(_rotatorMatrix, _bottomRight); + + final List rotOffsets = [ + _topLeft, + _topRight, + _bottomLeft, + _bottomRight + ]; + + final double minX = + rotOffsets.map((Offset offset) => offset.dx).reduce(math.min); + final double maxX = + rotOffsets.map((Offset offset) => offset.dx).reduce(math.max); + final double minY = + rotOffsets.map((Offset offset) => offset.dy).reduce(math.min); + final double maxY = + rotOffsets.map((Offset offset) => offset.dy).reduce(math.max); + + final Rect rotateRect = Rect.fromPoints( + Offset(minX, minY), + Offset(maxX, maxY), + ); + return rotateRect; +} + +/// This method converts the corresponding degrees to radian +double degreeToRadian(int deg) => deg * (math.pi / 180); + +/// This method converts the corresponding Offset to Vector2 +vector.Vector2 offsetToVector2(Offset offset) => + vector.Vector2(offset.dx, offset.dy); + +/// This method converts the corresponding Vector2 to Offset +Offset vector2ToOffset(vector.Vector2 vector) => Offset(vector.x, vector.y); + +/// This method transforms the given offset with respect ot the given matrix +Offset transform( + vector.Matrix2 matrix, + Offset offset, +) { + return vector2ToOffset(matrix * offsetToVector2(offset)); +} + +/// This method returns the maximum lines in the given text content +int getMaxLinesContent(String? text) { + return text != null && text.isNotEmpty && text.contains('\n') + ? text.split('\n').length + : 1; +} diff --git a/packages/syncfusion_flutter_core/lib/tooltip_internal.dart b/packages/syncfusion_flutter_core/lib/tooltip_internal.dart new file mode 100644 index 000000000..b0ef76cdd --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/tooltip_internal.dart @@ -0,0 +1,10 @@ +library tooltip_internal; + +import 'dart:ui'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter/rendering.dart'; +import 'package:flutter/material.dart'; + +import 'core.dart'; + +part 'src/tooltip/tooltip.dart'; diff --git a/packages/syncfusion_flutter_core/pubspec.yaml b/packages/syncfusion_flutter_core/pubspec.yaml index 58a2f8f25..a50b905e8 100644 --- a/packages/syncfusion_flutter_core/pubspec.yaml +++ b/packages/syncfusion_flutter_core/pubspec.yaml @@ -1,14 +1,17 @@ name: syncfusion_flutter_core description: Syncfusion Flutter Core is a dependent package for all the Syncfusion Flutter widgets. -version: 18.3.35 +version: 19.1.54 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_core environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: + vector_math: ^2.1.0 flutter: sdk: flutter - pedantic: ">=1.9.0 <1.9.9" + +dev_dependencies: + pedantic: ">=1.9.0 <2.0.0" flutter: diff --git a/packages/syncfusion_flutter_datagrid/CHANGELOG.md b/packages/syncfusion_flutter_datagrid/CHANGELOG.md index 08daebe4a..c61f0606f 100644 --- a/packages/syncfusion_flutter_datagrid/CHANGELOG.md +++ b/packages/syncfusion_flutter_datagrid/CHANGELOG.md @@ -1,5 +1,43 @@ ## Unreleased +**Bugs** +* Now, in Flutter 2.0, text can be typed in TextField widget when you load it in cells in web platform. + +**Features** +* Provided the support to refresh the data when the datagrid is pulled down. +* Provided the support to swipe a row “right to left” or “left to right” for custom actions such as deleting, editing, and so on. When the user swipes a row, the row will be moved, and swipe view will be shown for custom actions. +* Provided the support to scroll to a specific row, column, or cell. Also, users can scroll a row or column based on offset values. + +**Breakings Changes** + +We have documented all the API breaking details in [this](https://www.syncfusion.com/downloads/support/directtrac/general/doc/API_Breaking_Changes_in_Flutter_DataGrid_in_2021_Volume_1-1910747829.docx) document. + +## [18.4.49-beta] - 03/23/2021 + +**Bugs** + +* Stack overflow exception is no longer thrown when comparing two DataGridSource class with hashCode. + +## [18.4.47-beta] - 03/09/2021 + +**Bugs** + +* Now, if the widget is loaded using the 'headerCellBuilder' function, the padding will not be considered for the column header. + +## [18.4.33-beta] - 01/05/2021 + +**Bugs** + +* Now, when moving from a page with fewer rows than the size of the view port, rows are not clipped to another page with more rows than the size of the view port. + +## [18.4.31-beta] - 12/22/2020 + +**Breaking Changes** + +* Now, the row index is started from 0 instead of 1 for first row in `onQueryRowStyle` and `onQueryCellStyle` callbacks. + +## [18.4.30-beta] - 12/17/2020 + **Features** * Provided the support to show stacked headers i.e. unbound header rows. Unbound header rows span stacked header columns across multiple rows and columns. diff --git a/packages/syncfusion_flutter_datagrid/README.md b/packages/syncfusion_flutter_datagrid/README.md index 449c9ef08..f94f126ba 100644 --- a/packages/syncfusion_flutter_datagrid/README.md +++ b/packages/syncfusion_flutter_datagrid/README.md @@ -1,10 +1,10 @@ ![syncfusion flutter datagrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/Flutter-DataGrid.png) -# Syncfusion Flutter DataGrid +# Flutter DataGrid (DataTable) library -The Syncfusion Flutter DataGrid is used to display and manipulate data in a tabular view. It is built from the ground up to achieve the best possible performance, even when loading large amounts data. +The Flutter DataTable or DataGrid is used to display and manipulate data in a tabular view. It is built from the ground up to achieve the best possible performance, even when loading large amounts data. -**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or Syncfusion Community License. For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. +**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. **Note:** Our packages are now compatible with Flutter for web. However, this will be in beta until Flutter for web becomes stable. @@ -23,29 +23,54 @@ The Syncfusion Flutter DataGrid is used to display and manipulate data in a tabu ## DataGrid features -* **Column types** - Show different data types (int, double, string, and date-time) in different types of columns. Also, load any widget in a column. +**Column types** - Support to load any widget in a each column. -* **Column sizing** - Set the width of columns with various sizing options. Columns can also be sized based on their content. +![Flutter DataGrid shows different column types](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-column-types.png) -* **Auto row height** - Set the height for rows based on the content of their cells. +**Column sizing** - Set the width of columns with various sizing options. -* **Sorting** - Sort one or more columns in the ascending or descending order. +**Row height** - Set the height for header and data rows. Also, set the different height for specific rows. -* **Selection** - Select one or more rows. Keyboard navigation is supported for web platforms. +![Flutter DataGrid shows rows in auto-fit](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-auto-row-height.png) -* **Styling** - Customize the appearance of cells and headers. Conditional styling is also supported. +**Sorting** - Sort one or more columns in the ascending or descending order. -* **Stacked headers** - Show unbound header rows. Unbound header rows span stacked header columns across multiple rows and columns. +![Columns are sorted in flutter datagrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-sorting.gif) -* **Load more** - Display an interactive view when the grid reaches its maximum offset while scrolling down. Tapping the interactive view triggers a callback to add more data from the data source of the grid at run time. +**Selection** - Select one or more rows. Keyboard navigation is supported for web platforms. -* **Paging** - Load data in segments. It is useful when loading huge amounts of data. +![Flutter DataGrid shows rows with selection](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-selection.png) -* **Theme** - Use a dark or light theme. +**Styling** - Customize the appearance of cells and headers. Conditional styling is also supported. -* **Accessibility** - The DataGrid can easily be accessed by screen readers. +![Styling in Flutter DataGrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-styling.png) +![Styling in Flutter DataGrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-conditional-styles.png) -* **Right to Left (RTL)** - Right-to-left direction support for users working in RTL languages like Hebrew and Arabic. +**Stacked headers** - Show unbound header rows. Unbound header rows span stacked header columns across multiple rows and columns. + +![Flutter datagrid shows multiple column headers](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-stacked-headers.png) + +**Load more** - Display an interactive view when the grid reaches its maximum offset while scrolling down. Tapping the interactive view triggers a callback to add more data from the data source of the grid at run time. + +![infinite scrolling in Flutter datagrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-load-more.gif) + +**Paging** - Load data in segments. It is useful when loading huge amounts of data. + +![Flutter DataGrid shows rows in page segments](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-paging.png) + +**Freeze Panes** - Freeze the rows and columns when scrolling the grid. + +![First row and column are frozen in flutter datagrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-freeze-panes.gif) + +**Swiping** - Swipe a row right to left or left to right for custom actions such as deleting, editing, and so on. When the user swipes a row, the row will be moved and the swipe view will show the custom actions. + +**Pull to refresh** - Allows users to refresh data when the DataGrid is pulled down. + +**Theme** - Use a dark or light theme. + +**Accessibility** - The DataGrid can easily be accessed by screen readers. + +**Right to Left (RTL)** - Right-to-left direction support for users working in RTL languages like Hebrew and Arabic. ## Coming soon @@ -101,24 +126,33 @@ class Employee { Create the collection of employee data with the required number of data objects. Here, the method used to populate the data objects is initialized in initState() +`DataGridSource` objects are expected to be long-lived, not re-created with each build. + ```dart +List employees = []; + +EmployeeDataSource employeeDataSource; + @override void initState() { super.initState(); - populateData(); + employees= getEmployees(); + employeeDataSource = EmployeeDataSource(employees: employees); } -void populateData() { - _employees.add(Employee(10001, 'James', 'Project Lead', 20000)); - _employees.add(Employee(10002, 'Kathryn', 'Manager', 30000)); - _employees.add(Employee(10003, 'Lara', 'Developer', 15000)); - _employees.add(Employee(10004, 'Michael', 'Designer', 15000)); - _employees.add(Employee(10005, 'Martin', 'Developer', 15000)); - _employees.add(Employee(10006, 'Newberry', 'Developer', 15000)); - _employees.add(Employee(10007, 'Balnc', 'Developer', 15000)); - _employees.add(Employee(10008, 'Perry', 'Developer', 15000)); - _employees.add(Employee(10009, 'Gable', 'Developer', 15000)); - _employees.add(Employee(10010, 'Grimes', 'Developer', 15000)); + List getEmployees() { + return[ + Employee(10001, 'James', 'Project Lead', 20000), + Employee(10002, 'Kathryn', 'Manager', 30000), + Employee(10003, 'Lara', 'Developer', 15000), + Employee(10004, 'Michael', 'Designer', 15000), + Employee(10005, 'Martin', 'Developer', 15000), + Employee(10006, 'Newberry', 'Developer', 15000), + Employee(10007, 'Balnc', 'Developer', 15000), + Employee(10008, 'Perry', 'Developer', 15000), + Employee(10009, 'Gable', 'Developer', 15000), + Employee(10010, 'Grimes', 'Developer', 15000) + ]; } ``` @@ -126,41 +160,43 @@ void populateData() { `DataGridSource` is used to obtain the row data for the `SfDataGrid`. So, create the data source from the DataGridSource and override the following APIs in it: -* `dataSource`: Fetches the number of rows available for data population. Also, it is used to fetch the corresponding data object to process the selection. - -* `getValue`: Fetches the value for each cell. +* `rows`: Fetches the rows available for data population. Also, it is used to fetch the corresponding data object to process the selection. This contains the collection of `DataGridRow` where each row contains the collection of `DataGridCell`. Each cell should have the cell value in `value` property. `value` is used to perform the sorting for columns. -`DataGridSource` objects are expected to be long-lived, not re-created with each build. +* `buildRow`: Fetches the widget for each cell with `DataGridRowAdapter`. ```dart -final List _employees = []; +class EmployeeDataSource extends DataGridSource { + EmployeeDataSource({List employees}) { + _employees = employees + .map((e) => DataGridRow(cells: [ + DataGridCell(columnName: 'id', value: e.id), + DataGridCell(columnName: 'name', value: e.name), + DataGridCell( + columnName: 'designation', value: e.designation), + DataGridCell(columnName: 'salary', value: e.salary), + ])) + .toList(); + } -final EmployeeDataSource _employeeDataSource = EmployeeDataSource(); + List _employees; -class EmployeeDataSource extends DataGridSource { @override - List get dataSource => _employees; + List get rows => _employees; @override - getValue(Employee employee, String columnName) { - switch (columnName) { - case 'id': - return employee.id; - break; - case 'name': - return employee.name; - break; - case 'salary': - return employee.salary; - break; - case 'designation': - return employee.designation; - break; - default: - return ' '; - break; - } + DataGridRowAdapter buildRow(DataGridRow row) { + return DataGridRowAdapter( + cells: row.getCells().map((dataGridCell) { + return Container( + alignment: (dataGridCell.columnName == 'id' || dataGridCell.columnName == 'salary') + ? Alignment.centerRight + : Alignment.centerLeft, + padding: EdgeInsets.all(16.0), + child: Text(dataGridCell.value.toString()), + ); + }).toList()); } +} ``` Create an instance of `DataGridSource` and set this object to the `source` property of `SfDataGrid`. @@ -184,32 +220,46 @@ Widget build(BuildContext context) { ### Defining columns -`SfDataGrid` supports showing different data types (int, double, String, and DateTime) in different types of columns. You can add the column collection to the `columns` property. - -You can also load any widget in a column using the `GridWidgetColumn` and `cellBuilder` properties in `SfDataGrid`. +`SfDataGrid` supports load any widget in columns. You can add the column collection to the `columns` property. -```dart -final EmployeeDataSource _employeeDataSource = EmployeeDataSource(); - +```dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Syncfusion DataGrid'), + title: const Text('Syncfusion Flutter DataGrid'), ), - body: Center( - child: Expanded( - child: SfDataGrid( - source: _employeeDataSource, - columns: [ - GridNumericColumn(mappingName: 'id', headerText: 'ID'), - GridTextColumn(mappingName: 'name', headerText: 'Name'), - GridTextColumn( - mappingName: 'designation', headerText: 'Designation'), - GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), - ], - ), - ), + body: SfDataGrid( + source: employeeDataSource, + columns: [ + GridTextColumn( + columnName: 'id', + label: Container( + padding: EdgeInsets.all(16.0), + alignment: Alignment.centerRight, + child: Text( + 'ID', + ))), + GridTextColumn( + columnName: 'name', + label: Container( + padding: EdgeInsets.all(16.0), + alignment: Alignment.centerLeft, + child: Text('Name'))), + GridTextColumn( + columnName: 'designation', + width: 120, + label: Container( + padding: EdgeInsets.all(16.0), + alignment: Alignment.centerLeft, + child: Text('Designation'))), + GridTextColumn( + columnName: 'salary', + label: Container( + padding: EdgeInsets.all(16.0), + alignment: Alignment.centerRight, + child: Text('Salary'))), + ], ), ); } diff --git a/packages/syncfusion_flutter_datagrid/analysis_options.yaml b/packages/syncfusion_flutter_datagrid/analysis_options.yaml index 78d73d4e9..18db5ebed 100644 --- a/packages/syncfusion_flutter_datagrid/analysis_options.yaml +++ b/packages/syncfusion_flutter_datagrid/analysis_options.yaml @@ -4,5 +4,7 @@ analyzer: errors: lines_longer_than_80_chars: ignore include_file_not_found: ignore + invalid_dependency: ignore + avoid_as: ignore diff --git a/packages/syncfusion_flutter_datagrid/example/analysis_options.yaml b/packages/syncfusion_flutter_datagrid/example/analysis_options.yaml new file mode 100644 index 000000000..b08414c80 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/example/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + errors: + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_flutter_datagrid/example/lib/main.dart b/packages/syncfusion_flutter_datagrid/example/lib/main.dart index fe1644765..3e45e7249 100644 --- a/packages/syncfusion_flutter_datagrid/example/lib/main.dart +++ b/packages/syncfusion_flutter_datagrid/example/lib/main.dart @@ -20,21 +20,21 @@ class MyApp extends StatelessWidget { /// The home page of the application which hosts the datagrid. class MyHomePage extends StatefulWidget { /// Creates the home page. - MyHomePage({Key key}) : super(key: key); + MyHomePage({Key? key}) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); } -final List _employees = []; - -final EmployeeDataSource _employeeDataSource = EmployeeDataSource(); - class _MyHomePageState extends State { + List employees = []; + late EmployeeDataSource employeeDataSource; + @override void initState() { super.initState(); - populateData(); + employees = getEmployeeData(); + employeeDataSource = EmployeeDataSource(employeeData: employees); } @override @@ -44,28 +44,56 @@ class _MyHomePageState extends State { title: const Text('Syncfusion Flutter DataGrid'), ), body: SfDataGrid( - source: _employeeDataSource, + source: employeeDataSource, + columnWidthMode: ColumnWidthMode.fill, columns: [ - GridNumericColumn(mappingName: 'id', headerText: 'ID'), - GridTextColumn(mappingName: 'name', headerText: 'Name'), - GridTextColumn(mappingName: 'designation', headerText: 'Designation'), - GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), + GridTextColumn( + columnName: 'id', + label: Container( + padding: EdgeInsets.all(16.0), + alignment: Alignment.center, + child: Text( + 'ID', + ))), + GridTextColumn( + columnName: 'name', + label: Container( + padding: EdgeInsets.all(8.0), + alignment: Alignment.center, + child: Text('Name'))), + GridTextColumn( + columnName: 'designation', + label: Container( + padding: EdgeInsets.all(8.0), + alignment: Alignment.center, + child: Text( + 'Designation', + overflow: TextOverflow.ellipsis, + ))), + GridTextColumn( + columnName: 'salary', + label: Container( + padding: EdgeInsets.all(8.0), + alignment: Alignment.center, + child: Text('Salary'))), ], ), ); } - void populateData() { - _employees.add(Employee(10001, 'James', 'Project Lead', 20000)); - _employees.add(Employee(10002, 'Kathryn', 'Manager', 30000)); - _employees.add(Employee(10003, 'Lara', 'Developer', 15000)); - _employees.add(Employee(10004, 'Michael', 'Designer', 15000)); - _employees.add(Employee(10005, 'Martin', 'Developer', 15000)); - _employees.add(Employee(10006, 'Newberry', 'Developer', 15000)); - _employees.add(Employee(10007, 'Balnc', 'Developer', 15000)); - _employees.add(Employee(10008, 'Perry', 'Developer', 15000)); - _employees.add(Employee(10009, 'Gable', 'Developer', 15000)); - _employees.add(Employee(10010, 'Grimes', 'Developer', 15000)); + List getEmployeeData() { + return [ + Employee(10001, 'James', 'Project Lead', 20000), + Employee(10002, 'Kathryn', 'Manager', 30000), + Employee(10003, 'Lara', 'Developer', 15000), + Employee(10004, 'Michael', 'Designer', 15000), + Employee(10005, 'Martin', 'Developer', 15000), + Employee(10006, 'Newberry', 'Developer', 15000), + Employee(10007, 'Balnc', 'Developer', 15000), + Employee(10008, 'Perry', 'Developer', 15000), + Employee(10009, 'Gable', 'Developer', 15000), + Employee(10010, 'Grimes', 'Developer', 15000) + ]; } } @@ -90,28 +118,34 @@ class Employee { /// An object to set the employee collection data source to the datagrid. This /// is used to map the employee data to the datagrid widget. -class EmployeeDataSource extends DataGridSource { +class EmployeeDataSource extends DataGridSource { + /// Creates the employee data source class with required details. + EmployeeDataSource({required List employeeData}) { + _employeeData = employeeData + .map((e) => DataGridRow(cells: [ + DataGridCell(columnName: 'id', value: e.id), + DataGridCell(columnName: 'name', value: e.name), + DataGridCell( + columnName: 'designation', value: e.designation), + DataGridCell(columnName: 'salary', value: e.salary), + ])) + .toList(); + } + + List _employeeData = []; + @override - List get dataSource => _employees; + List get rows => _employeeData; @override - Object getValue(Employee employee, String columnName) { - switch (columnName) { - case 'id': - return employee.id; - break; - case 'name': - return employee.name; - break; - case 'salary': - return employee.salary; - break; - case 'designation': - return employee.designation; - break; - default: - return ' '; - break; - } + DataGridRowAdapter buildRow(DataGridRow row) { + return DataGridRowAdapter( + cells: row.getCells().map((e) { + return Container( + alignment: Alignment.center, + padding: EdgeInsets.all(8.0), + child: Text(e.value.toString()), + ); + }).toList()); } } diff --git a/packages/syncfusion_flutter_datagrid/example/pubspec.yaml b/packages/syncfusion_flutter_datagrid/example/pubspec.yaml index 5c4ac82fa..e46c3711a 100644 --- a/packages/syncfusion_flutter_datagrid/example/pubspec.yaml +++ b/packages/syncfusion_flutter_datagrid/example/pubspec.yaml @@ -3,14 +3,14 @@ description: This project demonstrates how to use Syncfusion Flutter DataGrid wi version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter syncfusion_flutter_datagrid: - path: ../ - cupertino_icons: ^0.1.3 + path: ../../syncfusion_flutter_datagrid + cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: diff --git a/packages/syncfusion_flutter_datagrid/lib/datagrid.dart b/packages/syncfusion_flutter_datagrid/lib/datagrid.dart index 6084ff055..1836ded5c 100644 --- a/packages/syncfusion_flutter_datagrid/lib/datagrid.dart +++ b/packages/syncfusion_flutter_datagrid/lib/datagrid.dart @@ -1,3 +1,14 @@ +/// The Syncfusion Flutter DataGrid is used to display and manipulate data in a +/// tabular view. Its rich feature set includes different types of columns, +/// selections, column sizing, etc. +/// +/// To use, import `package:syncfusion_flutter_datagrid/datagrid.dart`. +/// +/// See also: +/// +/// * [Syncfusion Flutter DataGrid product page](https://www.syncfusion.com/flutter-widgets/flutter-datagrid) +/// * [User guide documentation](https://help.syncfusion.com/flutter/datagrid/overview) +/// * [Knowledge base](https://www.syncfusion.com/kb/flutter/sfdatagrid) library datagrid; import 'dart:collection'; @@ -8,8 +19,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:intl/intl.dart' as intl show DateFormat, NumberFormat; import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:collection/collection.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; @@ -57,9 +68,6 @@ part './src/cell_renderer/grid_cell_renderer_base.dart'; part './src/cell_renderer/grid_virtualizing_cell_renderer_base.dart'; part './src/cell_renderer/grid_cell_text_field_renderer.dart'; part './src/cell_renderer/grid_header_cell_renderer.dart'; -part './src/cell_renderer/grid_cell_numeric_field_renderer.dart'; -part './src/cell_renderer/grid_cell_widget_renderer.dart'; -part './src/cell_renderer/grid_cell_datetime_renderer.dart'; part './src/cell_renderer/grid_cell_stacked_header_renderer.dart'; //Selection Controller diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_datetime_renderer.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_datetime_renderer.dart deleted file mode 100644 index 5b4df92a9..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_datetime_renderer.dart +++ /dev/null @@ -1,44 +0,0 @@ -part of datagrid; - -/// A cell renderer which displays the [DateTime] value in the cell. -/// -/// This renderer is typically used for [GridDateTimeColumn]. -class GridCellDateTimeRenderer - extends GridVirtualizingCellRendererBase { - /// Creates the [GridCellDateTimeRenderer] for [SfDataGrid] widget. - GridCellDateTimeRenderer(_DataGridStateDetails dataGridStateDetails) { - _dataGridStateDetails = dataGridStateDetails; - } - - @override - void setCellStyle(DataCellBase dataCell) { - if (dataCell != null) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final currentRowIndex = _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, dataCell.rowIndex); - - // Need to skip if the rowIndex is < 0 - // It's applicable for when un-ensured row-columns comes for ensuring. - if (currentRowIndex < 0) { - return; - } - - final cellValue = dataGridSettings.source - .getCellValue(currentRowIndex, dataCell.gridColumn.mappingName); - final dateTimeValue = _getDateTimeValue(cellValue); - dataCell - ..cellValue = cellValue - .._displayText = dateTimeValue != null - ? dataCell.gridColumn.getFormattedValue(dateTimeValue) - : null; - super.setCellStyle(dataCell); - } - } - - DateTime _getDateTimeValue(Object cellValue) { - if (cellValue == null) { - return null; - } - return DateTime.tryParse(cellValue.toString()); - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_numeric_field_renderer.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_numeric_field_renderer.dart deleted file mode 100644 index 6a4487b95..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_numeric_field_renderer.dart +++ /dev/null @@ -1,44 +0,0 @@ -part of datagrid; - -/// A cell renderer which displays the numeric value in the cell. -/// -/// This renderer is typically used for [GridNumericColumn]. -class GridCellNumericTextFieldRenderer - extends GridVirtualizingCellRendererBase { - /// Creates the [GridCellNumericTextFieldRenderer] for [SfDataGrid] widget. - GridCellNumericTextFieldRenderer(_DataGridStateDetails dataGridStateDetails) { - _dataGridStateDetails = dataGridStateDetails; - } - - @override - void setCellStyle(DataCellBase dataCell) { - if (dataCell != null) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final currentRowIndex = _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, dataCell.rowIndex); - // Need to skip if the rowIndex is < 0 - // It's applicable for when un-ensured row-columns comes for ensuring. - if (currentRowIndex < 0) { - return; - } - - final cellValue = dataGridSettings.source - .getCellValue(currentRowIndex, dataCell.gridColumn.mappingName); - final numericValue = _getNumericValue(cellValue); - dataCell - ..cellValue = cellValue - .._displayText = numericValue != null && !numericValue.isNaN - ? dataCell.gridColumn.getFormattedValue(cellValue) - : null; - super.setCellStyle(dataCell); - } - } - - double _getNumericValue(Object cellValue) { - if (cellValue == null) { - return double.nan; - } - - return double.tryParse(cellValue.toString()); - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_renderer_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_renderer_base.dart index c287352fe..6340979a2 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_renderer_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_renderer_base.dart @@ -2,13 +2,10 @@ part of datagrid; /// The base class for the cell renderer that used to display the widget. abstract class GridCellRendererBase { - /// Creates the [GridCellRendererBase] for [SfDataGrid] widget. - GridCellRendererBase(); - - _DataGridStateDetails _dataGridStateDetails; + late _DataGridStateDetails _dataGridStateDetails; /// Called when the child widgets for the GridCell are prepared. - Widget onPrepareWidgets(DataCellBase dataColumn) { + Widget? onPrepareWidgets(DataCellBase dataColumn) { return null; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_stacked_header_renderer.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_stacked_header_renderer.dart index 762cb4cce..5ed20efee 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_stacked_header_renderer.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_stacked_header_renderer.dart @@ -10,37 +10,34 @@ class _GridStackedHeaderCellRenderer } @override - void onInitializeDisplayWidget(DataCellBase dataCell, Widget widget) { - if (dataCell != null) { - final dataGridSettings = _dataGridStateDetails(); - final isLight = - dataGridSettings.dataGridThemeData.brightness == Brightness.light; - var label = DefaultTextStyle( - style: isLight - ? TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: Colors.black87) - : TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: Color.fromRGBO(255, 255, 255, 1)), - child: dataCell._stackedHeaderCell.child); + void onInitializeDisplayWidget(DataCellBase dataCell) { + final dataGridSettings = _dataGridStateDetails(); + final isLight = + dataGridSettings.dataGridThemeData!.brightness == Brightness.light; + Widget? label = DefaultTextStyle( + style: isLight + ? TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Colors.black87) + : TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)), + child: dataCell._stackedHeaderCell!.child); - dataCell._columnElement = GridCell( - key: dataCell._key, - dataCell: dataCell, - padding: EdgeInsets.zero, - backgroundColor: isLight - ? Color.fromRGBO(255, 255, 255, 1) - : Color.fromRGBO(33, 33, 33, 1), - isDirty: dataGridSettings.container._isDirty || dataCell._isDirty, - child: ExcludeSemantics(child: label), - ); + dataCell._columnElement = GridCell( + key: dataCell._key!, + dataCell: dataCell, + backgroundColor: isLight + ? Color.fromRGBO(255, 255, 255, 1) + : Color.fromRGBO(33, 33, 33, 1), + isDirty: dataGridSettings.container._isDirty || dataCell._isDirty, + child: label, + ); - label = null; - } + label = null; } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_text_field_renderer.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_text_field_renderer.dart index 65736bac9..eb54e4ad7 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_text_field_renderer.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_text_field_renderer.dart @@ -11,23 +11,41 @@ class GridCellTextFieldRenderer } @override - void setCellStyle(DataCellBase dataCell) { + void setCellStyle(DataCellBase? dataCell) { if (dataCell != null) { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final currentRowIndex = _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, dataCell.rowIndex); - // Need to skip if the rowIndex is < 0 - // It's applicable for when un-ensured row-columns comes for ensuring. - if (currentRowIndex < 0) { - return; - } - - dataCell - .._displayText = dataGridSettings.source - .getCellValue(currentRowIndex, dataCell.gridColumn.mappingName) - ?.toString() - ..cellValue = dataCell._displayText; + dataCell._textStyle = _getCellTextStyle(dataGridSettings, dataCell); super.setCellStyle(dataCell); } } + + TextStyle _getCellTextStyle( + _DataGridSettings dataGridSettings, DataCellBase dataCell) { + final DataRowBase? dataRow = dataCell._dataRow; + if (dataRow != null && dataRow.isSelectedRow) { + return dataGridSettings.dataGridThemeData!.brightness == Brightness.light + ? TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: const Color.fromRGBO(0, 0, 0, 0.87)) + : TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: const Color.fromRGBO(255, 255, 255, 1)); + } else { + return dataGridSettings.dataGridThemeData!.brightness == Brightness.light + ? TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: Colors.black87) + : TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)); + } + } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_widget_renderer.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_widget_renderer.dart deleted file mode 100644 index 7592896e2..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_widget_renderer.dart +++ /dev/null @@ -1,25 +0,0 @@ -part of datagrid; - -/// A cell renderer which loads any widget to the cells. -/// -/// This renderer is typically used for [GridWidgetColumn]. -class GridCellWidgetRenderer - extends GridVirtualizingCellRendererBase { - /// Creates the [GridCellWidgetRenderer] for [SfDataGrid] widget. - GridCellWidgetRenderer(_DataGridStateDetails dataGridStateDetails) { - _dataGridStateDetails = dataGridStateDetails; - } - @override - void onInitializeDisplayWidget(DataCellBase dataCell, Widget widget) { - if (dataCell != null) { - dataCell._columnElement = GridWidgetCell( - key: dataCell._key, - dataCell: dataCell, - padding: dataCell.gridColumn.padding ?? EdgeInsets.zero, - backgroundColor: dataCell._cellStyle?.backgroundColor, - isDirty: - _dataGridStateDetails().container._isDirty || dataCell._isDirty, - ); - } - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_header_cell_renderer.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_header_cell_renderer.dart index cfc560536..c6afd6201 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_header_cell_renderer.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_header_cell_renderer.dart @@ -9,40 +9,27 @@ class GridHeaderCellRenderer } @override - void onInitializeDisplayWidget(DataCellBase dataCell, Container widget) { - if (dataCell != null) { - var label = Text( - dataCell.cellValue, - key: dataCell._key, - softWrap: dataCell.gridColumn.headerTextSoftWrap, - overflow: dataCell.gridColumn.headerTextOverflow, - style: dataCell._cellStyle?.textStyle ?? - _dataGridStateDetails().dataGridThemeData.headerStyle.textStyle, - ); - dataCell._columnElement = GridHeaderCell( - key: dataCell._key, - dataCell: dataCell, - alignment: dataCell.gridColumn.headerTextAlignment, - padding: dataCell.gridColumn.headerPadding, - backgroundColor: dataCell._cellStyle?.backgroundColor ?? - _dataGridStateDetails() - .dataGridThemeData - .headerStyle - .backgroundColor, - isDirty: - _dataGridStateDetails().container._isDirty || dataCell._isDirty, - child: ExcludeSemantics(child: label), - ); - - label = null; - } + void onInitializeDisplayWidget(DataCellBase dataCell) { + final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + final Widget child = dataCell.gridColumn!.label; + dataCell._columnElement = GridHeaderCell( + key: dataCell._key!, + dataCell: dataCell, + child: DefaultTextStyle( + key: dataCell._key, style: dataCell._textStyle!, child: child), + backgroundColor: Colors.transparent, + isDirty: dataGridSettings.container._isDirty || + dataCell._isDirty || + dataCell._dataRow!._isDirty, + ); } @override void setCellStyle(DataCellBase dataCell) { + final SfDataGridThemeData themeData = + _dataGridStateDetails().dataGridThemeData!; TextStyle getDefaultHeaderTextStyle() { - return _dataGridStateDetails().dataGridThemeData.brightness == - Brightness.light + return themeData.brightness == Brightness.light ? TextStyle( fontFamily: 'Roboto', fontWeight: FontWeight.w500, @@ -55,28 +42,6 @@ class GridHeaderCellRenderer color: Color.fromRGBO(255, 255, 255, 1)); } - if (dataCell != null) { - dataCell - ..cellValue = - dataCell.gridColumn.headerText ?? dataCell.gridColumn.mappingName - .._cellStyle = DataGridHeaderCellStyle( - backgroundColor: dataCell.gridColumn.headerStyle?.backgroundColor ?? - Colors.transparent, - // Uncomment the below code if the mentioned report has resolved from framework side - // https://github.com/flutter/flutter/issues/29702 - //this._dataGridStateDetails().dataGridThemeData?.headerStyle?.backgroundColor, - textStyle: dataCell._ishover - ? (dataCell.gridColumn.headerStyle?.hoverTextStyle ?? - _dataGridStateDetails() - .dataGridThemeData - .headerStyle - .hoverTextStyle) - : (dataCell.gridColumn.headerStyle?.textStyle ?? - _dataGridStateDetails() - .dataGridThemeData - ?.headerStyle - ?.textStyle ?? - getDefaultHeaderTextStyle())); - } + dataCell._textStyle = getDefaultHeaderTextStyle(); } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_virtualizing_cell_renderer_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_virtualizing_cell_renderer_base.dart index 07ba34065..9235a629d 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_virtualizing_cell_renderer_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_virtualizing_cell_renderer_base.dart @@ -9,175 +9,33 @@ abstract class GridVirtualizingCellRendererBase createState() => _GridCellState(); } class _GridCellState extends State { - PointerDeviceKind _kind; + late PointerDeviceKind _kind; void _handleOnTapDown(TapDownDetails details) { - _kind = details?.kind; + _kind = details.kind!; } Widget _wrapInsideGestureDetector() { final dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell?._dataRow?._dataGridStateDetails(); + final _DataGridSettings? dataGridSettings = + dataCell._dataRow!._dataGridStateDetails!(); return GestureDetector( onTapUp: (details) { _handleOnTapUp( tapUpDetails: details, - dataGridSettings: dataGridSettings, + dataGridSettings: dataGridSettings!, dataCell: dataCell, kind: _kind); }, onTapDown: _handleOnTapDown, - onDoubleTap: dataGridSettings.onCellDoubleTap != null + onDoubleTap: dataGridSettings!.onCellDoubleTap != null ? () { _handleOnDoubleTap( dataCell: dataCell, dataGridSettings: dataGridSettings); @@ -88,10 +80,8 @@ class _GridCellState extends State { child: _wrapInsideCellContainer( child: widget.child, dataCell: widget.dataCell, - key: widget.key, + key: widget.key!, backgroundColor: widget.backgroundColor, - alignment: widget.alignment, - padding: widget.padding, )); @override @@ -110,115 +100,12 @@ class _GridCellState extends State { } } -/// A widget which loads any widget in the cells. -/// -/// This widget is typically used for [GridWidgetColumn]. -class GridWidgetCell extends GridCell { - /// Creates the [GridWidgetCell] for [SfDataGrid] widget. - const GridWidgetCell({ - @required Key key, - DataCellBase dataCell, - EdgeInsets padding, - Color backgroundColor, - bool isDirty, - Widget child, - }) : super( - key: key, - dataCell: dataCell, - padding: padding, - backgroundColor: backgroundColor, - isDirty: isDirty, - child: child); - - @override - State createState() => _GridWidgetCellState(); -} - -class _GridWidgetCellState extends State { - PointerDeviceKind _kind; - - void _handleOnTapDown(TapDownDetails details) { - _kind = details?.kind; - } - - Widget _wrapInsideGestureDetector(BuildContext context) { - final dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell?._dataRow?._dataGridStateDetails(); - return GestureDetector( - onTapUp: (details) { - _handleOnTapUp( - tapUpDetails: details, - dataGridSettings: dataGridSettings, - dataCell: dataCell, - kind: _kind); - }, - onTapDown: _handleOnTapDown, - onDoubleTap: dataGridSettings.onCellDoubleTap != null - ? () { - _handleOnDoubleTap( - dataCell: dataCell, dataGridSettings: dataGridSettings); - } - : null, - onLongPressEnd: (details) { - _handleOnLongPressEnd( - longPressEndDetails: details, - dataGridSettings: dataGridSettings, - dataCell: dataCell); - }, - onSecondaryTapUp: (details) { - _handleOnSecondaryTapUp( - tapUpDetails: details, - dataGridSettings: dataGridSettings, - dataCell: dataCell, - kind: _kind); - }, - onSecondaryTapDown: _handleOnTapDown, - child: _wrapInsideContainer(context), - ); - } - - Widget _wrapInsideContainer(BuildContext context) { - final _DataGridSettings dataGridSettings = - widget.dataCell?._dataRow?._dataGridStateDetails(); - Widget child; - if (dataGridSettings != null && dataGridSettings.cellBuilder != null) { - final recordIndex = _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, widget.dataCell.rowIndex); - child = dataGridSettings.cellBuilder( - context, widget.dataCell.gridColumn, recordIndex); - } - - return Container( - key: widget.key, - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration(border: _getCellBorder(widget.dataCell)), - child: _wrapInsideCellContainer( - child: child, - dataCell: widget.dataCell, - key: widget.key, - backgroundColor: widget.backgroundColor, - alignment: widget.alignment, - padding: widget.padding, - )); - } - - @override - Widget build(BuildContext context) { - final Widget child = _wrapInsideGestureDetector(context); - - return _GridCellRenderObjectWidget( - key: widget.key, - dataCell: widget.dataCell, - isDirty: widget.isDirty, - child: child, - ); - } -} - class _GridCellRenderObjectWidget extends SingleChildRenderObjectWidget { _GridCellRenderObjectWidget( - {@required Key key, this.dataCell, this.isDirty, this.child}) + {required Key? key, + required this.dataCell, + required this.isDirty, + required this.child}) : super(key: key, child: RepaintBoundary.wrap(child, 0)); @override @@ -243,7 +130,8 @@ class _GridCellRenderObjectWidget extends SingleChildRenderObjectWidget { class _RenderGridCell extends RenderBox with RenderObjectWithChildMixin { - _RenderGridCell({RenderBox child, DataCellBase dataCell, bool isDirty}) + _RenderGridCell( + {RenderBox? child, required DataCellBase dataCell, required bool isDirty}) : _dataCell = dataCell, _isDirty = isDirty { this.child = child; @@ -272,28 +160,30 @@ class _RenderGridCell extends RenderBox markNeedsPaint(); } - dataCell?._isDirty = false; + dataCell._isDirty = false; } - Rect columnRect = Rect.zero; + Rect? columnRect = Rect.zero; - Rect cellClipRect; + Rect? cellClipRect; - Rect _measureColumnRect(double rowHeight) { - if (dataCell._dataRow.isVisible && dataCell.isVisible) { - final DataRow dataRow = dataCell._dataRow; + Rect? _measureColumnRect(double rowHeight) { + if (dataCell._dataRow != null && + dataCell._dataRow!.isVisible && + dataCell.isVisible) { + final DataRowBase dataRow = dataCell._dataRow!; final _DataGridSettings _dataGridSettings = - dataRow._dataGridStateDetails(); - final double lineWidth = dataCell._dataRow._getColumnWidth( + dataRow._dataGridStateDetails!(); + final double lineWidth = dataRow._getColumnWidth( dataCell.columnIndex, dataCell.columnIndex + dataCell._columnSpan); - final double lineHeight = dataCell._dataRow._getRowHeight( + final double lineHeight = dataRow._getRowHeight( dataCell.rowIndex - dataCell._rowSpan, dataCell.rowIndex); if (dataRow.rowType == RowType.stackedHeaderRow) { columnRect = _getStackedHeaderCellRect(_dataGridSettings, lineWidth, lineHeight); } else { - final _VisibleLineInfo lineInfo = + final _VisibleLineInfo? lineInfo = dataRow._getColumnVisibleLineInfo(dataCell.columnIndex); final double origin = lineInfo != null ? lineInfo.origin : 0.0; columnRect = _getCellRect( @@ -306,13 +196,13 @@ class _RenderGridCell extends RenderBox return columnRect; } - Rect _getCellRect( + Rect? _getCellRect( _DataGridSettings dataGridSettings, - _VisibleLineInfo lineInfo, + _VisibleLineInfo? lineInfo, double origin, double lineWidth, double lineHeight) { - final DataRow dataRow = dataCell._dataRow; + final DataRowBase dataRow = dataCell._dataRow!; final int rowIndex = dataCell.rowIndex; final int rowSpan = dataCell._rowSpan; @@ -323,7 +213,7 @@ class _RenderGridCell extends RenderBox if (dataGridSettings.textDirection == TextDirection.rtl && lineInfo != null && lineInfo.visibleIndex == - _SfDataGridHelper.getVisibleLines(dataRow._dataGridStateDetails()) + _SfDataGridHelper.getVisibleLines(dataRow._dataGridStateDetails!()) .firstBodyVisibleIndex) { origin += lineInfo.scrollOffset; } @@ -341,9 +231,9 @@ class _RenderGridCell extends RenderBox return columnRect; } - Rect _getStackedHeaderCellRect( + Rect? _getStackedHeaderCellRect( _DataGridSettings dataGridSettings, double lineWidth, double lineHeight) { - final DataRow dataRow = dataCell._dataRow; + final DataRowBase dataRow = dataCell._dataRow!; final int cellStartIndex = dataCell.columnIndex; final int columnSpan = dataCell._columnSpan; final int cellEndIndex = cellStartIndex + columnSpan; @@ -354,10 +244,11 @@ class _RenderGridCell extends RenderBox final int footerFrozenColumnsCount = dataGridSettings.footerFrozenColumnsCount; final int columnsLength = dataGridSettings.columns.length; - final scrollColumns = dataGridSettings.container.scrollColumns; - Rect columnRect = Rect.zero; - double origin; - _VisibleLineInfo lineInfo; + final _ScrollAxisBase scrollColumns = + dataGridSettings.container.scrollColumns; + Rect? columnRect = Rect.zero; + double? origin; + _VisibleLineInfo? lineInfo; if (frozenColumns > cellStartIndex && frozenColumns <= cellEndIndex) { if (dataGridSettings.textDirection == TextDirection.ltr) { @@ -366,9 +257,9 @@ class _RenderGridCell extends RenderBox index--) { lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); if (lineInfo != null) { - final startLineInfo = + final _VisibleLineInfo? startLineInfo = scrollColumns.getVisibleLineAtLineIndex(cellStartIndex); - origin = startLineInfo.origin; + origin = startLineInfo?.origin; lineWidth = _getClippedWidth( dataGridSettings, cellStartIndex, cellEndIndex); break; @@ -414,17 +305,20 @@ class _RenderGridCell extends RenderBox for (int index = cellStartIndex; index <= cellEndIndex; index++) { lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); if (lineInfo != null) { - final line = scrollColumns.getVisibleLineAtLineIndex(cellEndIndex); - if (index == columnsLength - footerFrozenColumnsCount) { - origin = line.origin; - lineWidth = - dataRow._getColumnWidth(cellStartIndex + span, cellEndIndex); - break; - } else { - origin = line.clippedOrigin - lineInfo.scrollOffset; - lineWidth = _getClippedWidth( - dataGridSettings, cellStartIndex, cellEndIndex); - break; + final _VisibleLineInfo? line = + scrollColumns.getVisibleLineAtLineIndex(cellEndIndex); + if (line != null) { + if (index == columnsLength - footerFrozenColumnsCount) { + origin = line.origin; + lineWidth = dataRow._getColumnWidth( + cellStartIndex + span, cellEndIndex); + break; + } else { + origin = line.clippedOrigin - lineInfo.scrollOffset; + lineWidth = _getClippedWidth( + dataGridSettings, cellStartIndex, cellEndIndex); + break; + } } } span += 1; @@ -465,7 +359,7 @@ class _RenderGridCell extends RenderBox if (lineInfo != null) { columnRect = _getCellRect( - dataGridSettings, lineInfo, origin, lineWidth, lineHeight); + dataGridSettings, lineInfo, origin!, lineWidth, lineHeight); } return columnRect; } @@ -474,7 +368,7 @@ class _RenderGridCell extends RenderBox _DataGridSettings dataGridSettings, int startIndex, int endIndex) { double clippedWidth = 0; for (int index = startIndex; index <= endIndex; index++) { - final newline = dataCell._dataRow._getColumnVisibleLineInfo(index); + final newline = dataCell._dataRow!._getColumnVisibleLineInfo(index); if (newline != null) { if (dataGridSettings.textDirection == TextDirection.ltr) { clippedWidth += @@ -492,17 +386,17 @@ class _RenderGridCell extends RenderBox } @override - bool hitTestChildren(BoxHitTestResult result, {Offset position}) { + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { if (child == null) { return false; } - final BoxParentData childParentData = child.parentData; + final BoxParentData childParentData = child!.parentData as BoxParentData; final bool isHit = result.addWithPaintOffset( offset: childParentData.offset, position: position, hitTest: (result, transformed) => - child.hitTest(result, position: transformed)); + child!.hitTest(result, position: transformed)); if (isHit) { return true; } else { @@ -522,7 +416,7 @@ class _RenderGridCell extends RenderBox .constrain(Size(constraints.maxWidth, constraints.maxHeight)); if (child != null) { - child.layout( + child!.layout( BoxConstraints.tightFor( width: constraints.maxWidth, height: constraints.maxHeight), parentUsesSize: true); @@ -532,7 +426,7 @@ class _RenderGridCell extends RenderBox @override void paint(PaintingContext context, Offset offset) { if (child != null) { - context.paintChild(child, offset); + context.paintChild(child!, offset); } super.paint(context, offset); @@ -541,9 +435,9 @@ class _RenderGridCell extends RenderBox BorderDirectional _getCellBorder(DataCellBase dataCell) { final _DataGridSettings dataGridSettings = - dataCell._dataRow._dataGridStateDetails(); - final Color borderColor = dataGridSettings.dataGridThemeData?.gridLineColor; - final borderWidth = dataGridSettings.dataGridThemeData?.gridLineStrokeWidth; + dataCell._dataRow!._dataGridStateDetails!(); + final Color borderColor = dataGridSettings.dataGridThemeData!.gridLineColor; + final borderWidth = dataGridSettings.dataGridThemeData!.gridLineStrokeWidth; final rowIndex = (dataCell._rowSpan > 0) ? dataCell.rowIndex - dataCell._rowSpan @@ -602,20 +496,26 @@ BorderDirectional _getCellBorder(DataCellBase dataCell) { _GridIndexResolver.getStartFooterFrozenColumnIndex(dataGridSettings) == columnIndex; + final bool isFrozenPaneElevationApplied = + (dataGridSettings.dataGridThemeData!.frozenPaneElevation > 0.0); + final Color frozenPaneLineColor = - dataGridSettings.dataGridThemeData?.frozenPaneLineColor; + dataGridSettings.dataGridThemeData!.frozenPaneLineColor; final double frozenPaneLineWidth = - dataGridSettings.dataGridThemeData?.frozenPaneLineWidth; + dataGridSettings.dataGridThemeData!.frozenPaneLineWidth; BorderSide _getLeftBorder() { if ((columnIndex == 0 && (canDrawVerticalBorder || canDrawHeaderVerticalBorder)) || canDrawLeftFrozenBorder) { - if (canDrawLeftFrozenBorder && !isStackedHeaderCell) { + if (canDrawLeftFrozenBorder && + !isStackedHeaderCell && + !isFrozenPaneElevationApplied) { return BorderSide( width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else if (canDrawVerticalBorder || canDrawHeaderVerticalBorder) { + } else if ((canDrawVerticalBorder || canDrawHeaderVerticalBorder) && + !canDrawLeftFrozenBorder) { return BorderSide(width: borderWidth, color: borderColor); } else { return BorderSide.none; @@ -629,11 +529,15 @@ BorderDirectional _getCellBorder(DataCellBase dataCell) { if ((rowIndex == 0 && (canDrawHorizontalBorder || canDrawHeaderHorizontalBorder)) || canDrawTopFrozenBorder) { - if (canDrawTopFrozenBorder && !isStackedHeaderCell) { + if (canDrawTopFrozenBorder && + !isStackedHeaderCell && + !isFrozenPaneElevationApplied) { return BorderSide( width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else { + } else if (!canDrawTopFrozenBorder) { return BorderSide(width: borderWidth, color: borderColor); + } else { + return BorderSide.none; } } else { return BorderSide.none; @@ -644,10 +548,13 @@ BorderDirectional _getCellBorder(DataCellBase dataCell) { if (canDrawVerticalBorder || canDrawHeaderVerticalBorder || canDrawRightFrozenBorder) { - if (canDrawRightFrozenBorder && !isStackedHeaderCell) { + if (canDrawRightFrozenBorder && + !isStackedHeaderCell && + !isFrozenPaneElevationApplied) { return BorderSide( width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else if (canDrawVerticalBorder || canDrawHeaderVerticalBorder) { + } else if ((canDrawVerticalBorder || canDrawHeaderVerticalBorder) && + !canDrawRightFrozenBorder) { return BorderSide(width: borderWidth, color: borderColor); } else { return BorderSide.none; @@ -661,11 +568,15 @@ BorderDirectional _getCellBorder(DataCellBase dataCell) { if (canDrawHorizontalBorder || canDrawHeaderHorizontalBorder || canDrawBottomFrozenBorder) { - if (canDrawBottomFrozenBorder && !isStackedHeaderCell) { + if (canDrawBottomFrozenBorder && + !isStackedHeaderCell && + !isFrozenPaneElevationApplied) { return BorderSide( width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else { + } else if (!canDrawBottomFrozenBorder) { return BorderSide(width: borderWidth, color: borderColor); + } else { + return BorderSide.none; } } else { return BorderSide.none; @@ -681,20 +592,20 @@ BorderDirectional _getCellBorder(DataCellBase dataCell) { } Widget _wrapInsideCellContainer( - {@required DataCell dataCell, - @required Key key, - @required EdgeInsets padding, - @required Alignment alignment, - @required Color backgroundColor, - @required Widget child}) { - final dataGridSettings = dataCell._dataRow._dataGridStateDetails(); + {required DataCellBase dataCell, + required Key key, + required Color backgroundColor, + required Widget child}) { + final _DataGridSettings? dataGridSettings = + dataCell._dataRow!._dataGridStateDetails!(); if (dataGridSettings == null) { return child; } - final color = dataGridSettings.dataGridThemeData.currentCellStyle.borderColor; + final color = + dataGridSettings.dataGridThemeData!.currentCellStyle.borderColor; final borderWidth = - dataGridSettings.dataGridThemeData.currentCellStyle.borderWidth ?? 1.0; + dataGridSettings.dataGridThemeData!.currentCellStyle.borderWidth; Border getBorder() { final isCurrentCell = dataCell.isCurrentCell; @@ -714,10 +625,10 @@ Widget _wrapInsideCellContainer( ); } - double getCellHeight(DataCell dataCell, double defaultHeight) { + double getCellHeight(DataCellBase dataCell, double defaultHeight) { double height; if (dataCell._rowSpan > 0) { - height = dataCell._dataRow._getRowHeight( + height = dataCell._dataRow!._getRowHeight( dataCell.rowIndex - dataCell._rowSpan, dataCell.rowIndex); } else { height = defaultHeight; @@ -725,10 +636,10 @@ Widget _wrapInsideCellContainer( return height; } - double getCellWidth(DataCell dataCell, double defaultWidth) { + double getCellWidth(DataCellBase dataCell, double defaultWidth) { double width; if (dataCell._columnSpan > 0) { - width = dataCell._dataRow._getColumnWidth( + width = dataCell._dataRow!._getColumnWidth( dataCell.columnIndex, dataCell.columnIndex + dataCell._columnSpan); } else { width = defaultWidth; @@ -736,43 +647,41 @@ Widget _wrapInsideCellContainer( return width; } - return LayoutBuilder( - builder: (context, constraint) => Container( - key: key, - width: getCellWidth(dataCell, constraint.maxWidth), - height: getCellHeight(dataCell, constraint.maxHeight), - padding: _getPadding(dataCell, padding), - alignment: alignment, - clipBehavior: Clip.antiAlias, - decoration: - BoxDecoration(color: backgroundColor, border: getBorder()), - child: child)); -} - -EdgeInsets _getPadding(DataCellBase dataCell, EdgeInsets padding) { - final dataGridSettings = dataCell._dataRow?._dataGridStateDetails(); - - if (dataGridSettings == null) { - return const EdgeInsets.all(0.0); + Widget getChild(BoxConstraints constraint) { + final double width = getCellWidth(dataCell, constraint.maxWidth); + final double height = getCellHeight(dataCell, constraint.maxHeight); + + if (dataCell.isCurrentCell) { + return Stack( + children: [ + Container(width: width, height: height, child: child), + Positioned( + left: 0, + top: 0, + width: width, + height: height, + child: IgnorePointer( + ignoring: true, + child: Container( + key: key, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: backgroundColor, border: getBorder())), + )), + ], + ); + } else { + return Container(width: width, height: height, child: child); + } } - final themeData = dataGridSettings.dataGridThemeData; - final currentCellBorderWidth = themeData.currentCellStyle.borderWidth ?? 1.0; - final visualDensityPadding = dataGridSettings.visualDensity.vertical * 2; - - padding ??= EdgeInsets.all(16) + - EdgeInsets.fromLTRB(0.0, visualDensityPadding, 0.0, visualDensityPadding); - - padding = padding != null - ? padding - - EdgeInsets.all(dataCell.isCurrentCell ? currentCellBorderWidth : 0.0) - : padding; - - return padding != null && padding.isNonNegative ? padding : EdgeInsets.zero; + return LayoutBuilder(builder: (context, constraint) { + return getChild(constraint); + }); } -Rect _getCellClipRect(_DataGridSettings _dataGridSettings, - _VisibleLineInfo lineInfo, double rowHeight) { +Rect? _getCellClipRect(_DataGridSettings _dataGridSettings, + _VisibleLineInfo? lineInfo, double rowHeight) { // FLUT-1971 Need to check whether the lineInfo is null or not. Because it // will be null when load empty to the columns collection. if (lineInfo == null) { @@ -812,19 +721,19 @@ Rect _getCellClipRect(_DataGridSettings _dataGridSettings, } } -Rect _getSpannedCellClipRect( +Rect? _getSpannedCellClipRect( _DataGridSettings dataGridSettings, - DataRow dataRow, + DataRowBase dataRow, DataCellBase dataCell, double cellHeight, double cellWidth) { - Rect clipRect; + Rect? clipRect; var firstVisibleStackedColumnIndex = dataCell.columnIndex; double lastCellClippedSize = 0.0; var isLastCellClippedCorner = false; var isLastCellClippedBody = false; - double getClippedWidth(DataCellBase dataCell, _SpannedDataRow dataRow, + double getClippedWidth(DataCellBase dataCell, DataRowBase dataRow, {bool columnsNotInViewWidth = false, bool allCellsClippedWidth = false}) { final startIndex = dataCell.columnIndex; final endIndex = dataCell.columnIndex + dataCell._columnSpan; @@ -866,32 +775,32 @@ Rect _getSpannedCellClipRect( return null; } - if (dataRow != null && dataCell._renderer != null) { + if (dataCell._renderer != null) { final columnsNotInViewWidth = getClippedWidth(dataCell, dataRow, columnsNotInViewWidth: true); final clippedWidth = getClippedWidth(dataCell, dataRow, allCellsClippedWidth: true); - final visiblelineInfo = + final visibleLineInfo = dataRow._getColumnVisibleLineInfo(firstVisibleStackedColumnIndex); - if (visiblelineInfo != null) { - if (visiblelineInfo.isClippedOrigin && visiblelineInfo.isClippedCorner) { + if (visibleLineInfo != null) { + if (visibleLineInfo.isClippedOrigin && visibleLineInfo.isClippedCorner) { final clippedOrigin = columnsNotInViewWidth + - visiblelineInfo.size - - (visiblelineInfo.clippedSize + visiblelineInfo.clippedCornerExtent); + visibleLineInfo.size - + (visibleLineInfo.clippedSize + visibleLineInfo.clippedCornerExtent); final double left = dataGridSettings.textDirection == TextDirection.ltr ? clippedOrigin - : visiblelineInfo.clippedSize; + : visibleLineInfo.clippedSize; final double right = dataGridSettings.textDirection == TextDirection.ltr ? clippedWidth - : visiblelineInfo.clippedCornerExtent; + : visibleLineInfo.clippedCornerExtent; clipRect = Rect.fromLTWH(left, 0.0, right, cellHeight); - } else if (visiblelineInfo.isClippedOrigin) { + } else if (visibleLineInfo.isClippedOrigin) { final clippedOriginLTR = columnsNotInViewWidth + - visiblelineInfo.size - - visiblelineInfo.clippedSize; + visibleLineInfo.size - + visibleLineInfo.clippedSize; final clippedOriginRTL = (isLastCellClippedCorner && isLastCellClippedBody) ? lastCellClippedSize @@ -903,7 +812,7 @@ Rect _getSpannedCellClipRect( final double right = dataGridSettings.textDirection == TextDirection.ltr ? clippedWidth : cellWidth - - (columnsNotInViewWidth + visiblelineInfo.scrollOffset); + (columnsNotInViewWidth + visibleLineInfo.scrollOffset); clipRect = Rect.fromLTWH(left, 0.0, right, cellHeight); } else if (isLastCellClippedCorner && isLastCellClippedBody) { @@ -943,78 +852,65 @@ Rect _getSpannedCellClipRect( // Gesture Events void _handleOnTapUp( - {TapUpDetails tapUpDetails, - DataCellBase dataCell, - _DataGridSettings dataGridSettings, - PointerDeviceKind kind}) { - if (dataGridSettings == null || dataCell == null) { - return; - } + {required TapUpDetails tapUpDetails, + required DataCellBase dataCell, + required _DataGridSettings dataGridSettings, + required PointerDeviceKind kind}) { if (dataGridSettings.onCellTap != null) { final DataGridCellTapDetails details = DataGridCellTapDetails( rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn, - globalPosition: tapUpDetails?.globalPosition, - localPosition: tapUpDetails?.localPosition, + column: dataCell.gridColumn!, + globalPosition: tapUpDetails.globalPosition, + localPosition: tapUpDetails.localPosition, kind: kind); - dataGridSettings.onCellTap(details); + dataGridSettings.onCellTap!(details); } - dataGridSettings.dataGridFocusNode.requestFocus(); + dataGridSettings.dataGridFocusNode?.requestFocus(); dataCell._onTouchUp(); } void _handleOnDoubleTap( - {DataCellBase dataCell, _DataGridSettings dataGridSettings}) { - if (dataGridSettings == null || dataCell == null) { - return; - } - + {required DataCellBase dataCell, + required _DataGridSettings dataGridSettings}) { if (dataGridSettings.onCellDoubleTap != null) { final DataGridCellDoubleTapDetails details = DataGridCellDoubleTapDetails( rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn); - dataGridSettings.onCellDoubleTap(details); + column: dataCell.gridColumn!); + dataGridSettings.onCellDoubleTap!(details); } - dataGridSettings.dataGridFocusNode.requestFocus(); + dataGridSettings.dataGridFocusNode?.requestFocus(); dataCell._onTouchUp(); } void _handleOnLongPressEnd( - {LongPressEndDetails longPressEndDetails, - DataCellBase dataCell, - _DataGridSettings dataGridSettings}) { - if (dataGridSettings == null || dataCell == null) { - return; - } - + {required LongPressEndDetails longPressEndDetails, + required DataCellBase dataCell, + required _DataGridSettings dataGridSettings}) { if (dataGridSettings.onCellLongPress != null) { final DataGridCellLongPressDetails details = DataGridCellLongPressDetails( rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn, - globalPosition: longPressEndDetails?.globalPosition, - localPosition: longPressEndDetails?.localPosition, - velocity: longPressEndDetails?.velocity); - dataGridSettings.onCellLongPress(details); + column: dataCell.gridColumn!, + globalPosition: longPressEndDetails.globalPosition, + localPosition: longPressEndDetails.localPosition, + velocity: longPressEndDetails.velocity); + dataGridSettings.onCellLongPress!(details); } } void _handleOnSecondaryTapUp( - {TapUpDetails tapUpDetails, - DataCellBase dataCell, - _DataGridSettings dataGridSettings, - PointerDeviceKind kind}) { - if (dataGridSettings == null || dataCell == null) { - return; - } + {required TapUpDetails tapUpDetails, + required DataCellBase dataCell, + required _DataGridSettings dataGridSettings, + required PointerDeviceKind kind}) { if (dataGridSettings.onCellSecondaryTap != null) { final DataGridCellTapDetails details = DataGridCellTapDetails( rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn, - globalPosition: tapUpDetails?.globalPosition, - localPosition: tapUpDetails?.localPosition, + column: dataCell.gridColumn!, + globalPosition: tapUpDetails.globalPosition, + localPosition: tapUpDetails.localPosition, kind: kind); - dataGridSettings.onCellSecondaryTap(details); + dataGridSettings.onCellSecondaryTap!(details); } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_header_cell_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_header_cell_widget.dart index 606f5baf3..608aa4860 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_header_cell_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_header_cell_widget.dart @@ -4,13 +4,11 @@ part of datagrid; class GridHeaderCell extends StatefulWidget { /// Creates the [GridHeaderCell] for [SfDataGrid] widget. const GridHeaderCell({ - @required Key key, - this.dataCell, - this.padding, - this.backgroundColor, - this.isDirty, - this.child, - this.alignment, + required Key key, + required this.dataCell, + required this.backgroundColor, + required this.isDirty, + required this.child, }) : super(key: key); /// Holds the information required to display the cell. @@ -19,9 +17,6 @@ class GridHeaderCell extends StatefulWidget { /// The [child] contained by the [GridCell]. final Widget child; - /// Empty space to inscribe inside the [GridCell]. - final EdgeInsets padding; - /// The color to paint behind the [child]. final Color backgroundColor; @@ -29,110 +24,122 @@ class GridHeaderCell extends StatefulWidget { /// rebuild. final bool isDirty; - /// Align the [child] within the GridCell. - final Alignment alignment; @override State createState() => _GridHeaderCellState(); } class _GridHeaderCellState extends State { - DataGridSortDirection _sortDirection; - Color _sortIconColor; - int _sortNumber; - Color _sortNumberBackgroundColor; - Color _sortNumberTextColor; - PointerDeviceKind _kind; + DataGridSortDirection? _sortDirection; + Color _sortIconColor = Colors.transparent; + int _sortNumber = -1; + Color _sortNumberBackgroundColor = Colors.transparent; + Color _sortNumberTextColor = Colors.transparent; + late PointerDeviceKind _kind; void _handleOnTapUp(TapUpDetails tapUpDetails) { - final dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell?._dataRow?._dataGridStateDetails(); - if (dataGridSettings == null || dataCell == null) { + final DataCellBase? dataCell = widget.dataCell; + if (dataCell == null) { + return; + } + final _DataGridSettings? dataGridSettings = + dataCell._dataRow!._dataGridStateDetails!(); + if (dataGridSettings == null) { return; } if (dataGridSettings.onCellTap != null) { final DataGridCellTapDetails details = DataGridCellTapDetails( rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn, - globalPosition: tapUpDetails?.globalPosition, - localPosition: tapUpDetails?.localPosition, + column: dataCell.gridColumn!, + globalPosition: tapUpDetails.globalPosition, + localPosition: tapUpDetails.localPosition, kind: _kind); - dataGridSettings.onCellTap(details); + dataGridSettings.onCellTap!(details); } - dataGridSettings.dataGridFocusNode.requestFocus(); + dataGridSettings.dataGridFocusNode?.requestFocus(); if (dataGridSettings.sortingGestureType == SortingGestureType.tap) { _sort(dataCell); } } void _handleOnDoubleTap() { - final dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell?._dataRow?._dataGridStateDetails(); - if (dataGridSettings == null || dataCell == null) { + final DataCellBase? dataCell = widget.dataCell; + if (dataCell == null) { + return; + } + final _DataGridSettings? dataGridSettings = + dataCell._dataRow!._dataGridStateDetails!(); + if (dataGridSettings == null) { return; } if (dataGridSettings.onCellDoubleTap != null) { final DataGridCellDoubleTapDetails details = DataGridCellDoubleTapDetails( rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn); - dataGridSettings.onCellDoubleTap(details); + column: dataCell.gridColumn!); + dataGridSettings.onCellDoubleTap!(details); } - dataGridSettings.dataGridFocusNode.requestFocus(); + dataGridSettings.dataGridFocusNode?.requestFocus(); if (dataGridSettings.sortingGestureType == SortingGestureType.doubleTap) { _sort(dataCell); } } void _handleOnLongPressEnd(LongPressEndDetails longPressEndDetails) { - final dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell?._dataRow?._dataGridStateDetails(); - if (dataGridSettings == null || dataCell == null) { + final DataCellBase? dataCell = widget.dataCell; + if (dataCell == null) { return; } + + final _DataGridSettings? dataGridSettings = + dataCell._dataRow!._dataGridStateDetails!(); + if (dataGridSettings == null) { + return; + } + if (dataGridSettings.onCellLongPress != null) { final DataGridCellLongPressDetails details = DataGridCellLongPressDetails( rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn, - globalPosition: longPressEndDetails?.globalPosition, - localPosition: longPressEndDetails?.localPosition, - velocity: longPressEndDetails?.velocity); - dataGridSettings.onCellLongPress(details); + column: dataCell.gridColumn!, + globalPosition: longPressEndDetails.globalPosition, + localPosition: longPressEndDetails.localPosition, + velocity: longPressEndDetails.velocity); + dataGridSettings.onCellLongPress!(details); } } void _handleOnSecondaryTapUp(TapUpDetails tapUpDetails) { - final dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell?._dataRow?._dataGridStateDetails(); - if (dataGridSettings == null || dataCell == null) { + final DataCellBase? dataCell = widget.dataCell; + if (dataCell == null) { + return; + } + final _DataGridSettings? dataGridSettings = + dataCell._dataRow?._dataGridStateDetails!(); + if (dataGridSettings == null) { return; } if (dataGridSettings.onCellSecondaryTap != null) { final DataGridCellTapDetails details = DataGridCellTapDetails( rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn, - globalPosition: tapUpDetails?.globalPosition, - localPosition: tapUpDetails?.localPosition, + column: dataCell.gridColumn!, + globalPosition: tapUpDetails.globalPosition, + localPosition: tapUpDetails.localPosition, kind: _kind); - dataGridSettings.onCellSecondaryTap(details); + dataGridSettings.onCellSecondaryTap!(details); } } void _handleOnTapDown(TapDownDetails details) { - _kind = details?.kind; + _kind = details.kind!; } Widget _wrapInsideGestureDetector() { final _DataGridSettings dataGridSettings = - widget.dataCell?._dataRow?._dataGridStateDetails(); + widget.dataCell._dataRow!._dataGridStateDetails!(); return GestureDetector( onTapUp: dataGridSettings.onCellTap != null || dataGridSettings.sortingGestureType == SortingGestureType.tap @@ -157,106 +164,67 @@ class _GridHeaderCellState extends State { Widget _wrapInsideContainer() { final _DataGridSettings dataGridSettings = - widget.dataCell?._dataRow?._dataGridStateDetails(); - final column = widget.dataCell?.gridColumn; - bool isSortIconLoaded = false; + widget.dataCell._dataRow!._dataGridStateDetails!(); + final column = widget.dataCell.gridColumn; Widget checkHeaderCellConstraints(Widget child) { - final iconWidth = dataGridSettings.columnSizer._getSortIconWidth(column); + final iconWidth = dataGridSettings.columnSizer._getSortIconWidth(column!); return LayoutBuilder(builder: (context, constraints) { if (_sortDirection == null || constraints.maxWidth < iconWidth) { return child; } else { - isSortIconLoaded = true; return _getCellWithSortIcon(child); } }); } - final Widget headerCellWidget = - (dataGridSettings != null && dataGridSettings.headerCellBuilder != null) - ? dataGridSettings.headerCellBuilder(context, column) - : null; - Widget child = headerCellWidget ?? widget.child; - _ensureSortIconVisiblity(column, dataGridSettings); - child = checkHeaderCellConstraints(child); - final alignment = !isSortIconLoaded ? widget.alignment : null; - - EdgeInsets _getPadding(DataCellBase dataCell, EdgeInsets padding) { - final dataGridSettings = dataCell._dataRow?._dataGridStateDetails(); - - if (dataGridSettings == null) { - return const EdgeInsets.all(0.0); - } - - final visualDensityPadding = dataGridSettings.visualDensity.vertical * 2; - - padding ??= EdgeInsets.all(16) + - EdgeInsets.fromLTRB( - 0.0, visualDensityPadding, 0.0, visualDensityPadding); - - return padding != null && padding.isNonNegative - ? padding - : EdgeInsets.zero; - } + _ensureSortIconVisiblity(column!, dataGridSettings); return Container( key: widget.key, clipBehavior: Clip.antiAlias, decoration: BoxDecoration(border: _getCellBorder(widget.dataCell)), - alignment: Alignment.center, child: _wrapInsideCellContainer( - child: child, + child: checkHeaderCellConstraints(widget.child), dataCell: widget.dataCell, - key: widget.key, + key: widget.key!, backgroundColor: widget.backgroundColor, - alignment: alignment, - padding: headerCellWidget == null - ? _getPadding(widget.dataCell, widget.padding) - : null, )); } - Color _getHoverBackgroundColor() { - final _DataGridSettings dataGridSettings = - widget.dataCell?._dataRow?._dataGridStateDetails(); + Color? _getHoverBackgroundColor() { + final _DataGridSettings? dataGridSettings = + widget.dataCell._dataRow!._dataGridStateDetails!(); if (dataGridSettings == null) { return null; } - final hoverColor = (widget.dataCell.gridColumn?.headerStyle?.hoverColor ?? - dataGridSettings.dataGridThemeData.headerStyle?.hoverColor) ?? - ((dataGridSettings.dataGridThemeData.brightness == Brightness.light) - ? Color.fromRGBO(245, 245, 245, 1) - : Color.fromRGBO(66, 66, 66, 1)); - return hoverColor; + return dataGridSettings.dataGridThemeData!.headerHoverColor; } void onMouseHover() { - final _DataGridSettings dataGridSettings = - widget.dataCell?._dataRow?._dataGridStateDetails(); + final _DataGridSettings? dataGridSettings = + widget.dataCell._dataRow!._dataGridStateDetails!(); - if (dataGridSettings != null && widget.dataCell != null) { + if (dataGridSettings != null) { widget.dataCell - .._ishover = true .._isDirty = true .._updateColumn(); - dataGridSettings.source - .notifyDataSourceListeners(propertyName: 'hoverOnHeaderCell'); + dataGridSettings.source._notifyDataGridPropertyChangeListeners( + propertyName: 'hoverOnHeaderCell'); } } void onMouseExit() { - final _DataGridSettings dataGridSettings = - widget.dataCell?._dataRow?._dataGridStateDetails(); + final _DataGridSettings? dataGridSettings = + widget.dataCell._dataRow?._dataGridStateDetails!(); - if (dataGridSettings != null && widget.dataCell != null) { + if (dataGridSettings != null) { widget.dataCell - .._ishover = false .._isDirty = true .._updateColumn(); - dataGridSettings.source - .notifyDataSourceListeners(propertyName: 'hoverOnHeaderCell'); + dataGridSettings.source._notifyDataGridPropertyChangeListeners( + propertyName: 'hoverOnHeaderCell'); } } @@ -292,22 +260,20 @@ class _GridHeaderCellState extends State { } void _ensureSortIconVisiblity( - GridColumn column, _DataGridSettings dataGridSettings) { + GridColumn column, _DataGridSettings? dataGridSettings) { if (dataGridSettings != null) { - final sortColumn = dataGridSettings.source.sortedColumns.firstWhere( - (sortColumn) => sortColumn.name == column.mappingName, - orElse: () => null); + final sortColumn = dataGridSettings.source.sortedColumns.firstWhereOrNull( + (sortColumn) => sortColumn.name == column.columnName); if (dataGridSettings.source.sortedColumns.isNotEmpty && sortColumn != null) { final sortNumber = dataGridSettings.source.sortedColumns.indexOf(sortColumn) + 1; final isLight = - dataGridSettings.dataGridThemeData.brightness == Brightness.light; + dataGridSettings.dataGridThemeData!.brightness == Brightness.light; _sortDirection = sortColumn.sortDirection; - _sortIconColor = column?.headerStyle?.sortIconColor ?? - dataGridSettings.dataGridThemeData.headerStyle?.sortIconColor; + _sortIconColor = dataGridSettings.dataGridThemeData!.sortIconColor; _sortNumberBackgroundColor = - isLight ? Colors.grey[350] : Colors.grey[700]; + isLight ? Colors.grey[350]! : Colors.grey[700]!; _sortNumberTextColor = isLight ? Colors.black87 : Colors.white54; if (dataGridSettings.source.sortedColumns.length > 1 && dataGridSettings.showSortNumbers) { @@ -326,7 +292,7 @@ class _GridHeaderCellState extends State { final List children = []; children.add(_SortIcon( - sortDirection: _sortDirection, + sortDirection: _sortDirection!, sortIconColor: _sortIconColor, )); @@ -338,10 +304,10 @@ class _GridHeaderCellState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ Flexible( - child: Container(child: child, alignment: widget.alignment), + child: Container(child: child), ), Container( - padding: EdgeInsets.only(left: 4.0), + padding: EdgeInsets.only(left: 4.0, right: 4.0), child: Center(child: Row(children: children)), ) ]); @@ -355,7 +321,7 @@ class _GridHeaderCellState extends State { color: _sortNumberBackgroundColor, ), child: Center( - child: Text(_sortNumber?.toString(), + child: Text(_sortNumber.toString(), style: TextStyle(fontSize: 12, color: _sortNumberTextColor)), ), ); @@ -363,25 +329,23 @@ class _GridHeaderCellState extends State { void _sort(DataCellBase dataCell) { final _DataGridSettings dataGridSettings = - widget.dataCell?._dataRow?._dataGridStateDetails(); - if (dataCell?._dataRow?.rowType == RowType.headerRow && - dataCell?._dataRow?.rowIndex == + widget.dataCell._dataRow!._dataGridStateDetails!(); + if (dataCell._dataRow?.rowType == RowType.headerRow && + dataCell._dataRow?.rowIndex == _GridIndexResolver.getHeaderIndex(dataGridSettings)) { _makeSort(dataCell); } } Future _makeSort(DataCellBase dataCell) async { - final GridColumn column = dataCell?.gridColumn; - final _DataGridSettings dataGridSettings = - widget.dataCell?._dataRow?._dataGridStateDetails(); - if (column.mappingName == null) { - return; - } + final GridColumn column = dataCell.gridColumn!; + final _DataGridSettings? dataGridSettings = + widget.dataCell._dataRow!._dataGridStateDetails!(); + if (dataGridSettings != null && column.allowSorting && dataGridSettings.allowSorting) { - final sortColumnName = column.mappingName; + final sortColumnName = column.columnName; final allowMultiSort = dataGridSettings._isDesktop ? (dataGridSettings.isControlKeyPressed && dataGridSettings.allowMultiColumnSorting) @@ -390,9 +354,8 @@ class _GridHeaderCellState extends State { final sortedColumns = source.sortedColumns; if (sortedColumns.isNotEmpty && allowMultiSort) { - var sortedColumn = sortedColumns.firstWhere( - (sortColumn) => sortColumn.name == sortColumnName, - orElse: () => null); + var sortedColumn = sortedColumns.firstWhereOrNull( + (sortColumn) => sortColumn.name == sortColumnName); if (sortedColumn == null) { final newSortColumn = SortColumnDetails( name: sortColumnName, @@ -404,9 +367,8 @@ class _GridHeaderCellState extends State { } else { if (sortedColumn.sortDirection == DataGridSortDirection.descending && dataGridSettings.allowTriStateSorting) { - final removedSortColumn = sortedColumns.firstWhere( - (sortColumn) => sortColumn.name == sortColumnName, - orElse: () => null); + final removedSortColumn = sortedColumns.firstWhereOrNull( + (sortColumn) => sortColumn.name == sortColumnName); sortedColumns.remove(removedSortColumn); await source._updateDataSource(); } else { @@ -416,9 +378,8 @@ class _GridHeaderCellState extends State { DataGridSortDirection.ascending ? DataGridSortDirection.descending : DataGridSortDirection.ascending); - final removedSortColumn = sortedColumns.firstWhere( - (sortColumn) => sortColumn.name == sortedColumn.name, - orElse: () => null); + final removedSortColumn = sortedColumns.firstWhereOrNull( + (sortColumn) => sortColumn.name == sortedColumn!.name); sortedColumns.remove(removedSortColumn); sortedColumns.add(sortedColumn); if (!await source._updateDataSource()) { @@ -427,9 +388,8 @@ class _GridHeaderCellState extends State { } } } else { - var currentSortColumn = sortedColumns.firstWhere( - (sortColumn) => sortColumn.name == sortColumnName, - orElse: () => null); + var currentSortColumn = sortedColumns.firstWhereOrNull( + (sortColumn) => sortColumn.name == sortColumnName); if (sortedColumns.isNotEmpty && currentSortColumn != null) { if (currentSortColumn.sortDirection == DataGridSortDirection.descending && @@ -468,13 +428,13 @@ class _GridHeaderCellState extends State { } } dataGridSettings.source - .notifyDataSourceListeners(propertyName: 'Sorting'); + ._notifyDataGridPropertyChangeListeners(propertyName: 'Sorting'); } } } class _SortIcon extends StatefulWidget { - _SortIcon({this.sortDirection, this.sortIconColor}); + _SortIcon({required this.sortDirection, required this.sortIconColor}); final DataGridSortDirection sortDirection; final Color sortIconColor; @override @@ -483,8 +443,8 @@ class _SortIcon extends StatefulWidget { class _SortIconState extends State<_SortIcon> with SingleTickerProviderStateMixin { - AnimationController _animationController; - Animation _sortingAnimation; + late AnimationController _animationController; + late Animation _sortingAnimation; @override void initState() { diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/virtualizing_cells_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/virtualizing_cells_widget.dart index 7d5f1167a..6036854ae 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/virtualizing_cells_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/virtualizing_cells_widget.dart @@ -2,7 +2,7 @@ part of datagrid; class _VirtualizingCellsWidget extends StatefulWidget { const _VirtualizingCellsWidget( - {@required Key key, @required this.dataRow, this.isDirty}) + {required Key key, required this.dataRow, required this.isDirty}) : super(key: key); final DataRowBase dataRow; @@ -16,11 +16,11 @@ class _VirtualizingCellsWidgetState extends State<_VirtualizingCellsWidget> { @override Widget build(BuildContext context) { final List children = widget.dataRow._visibleColumns - .map((dataCell) => dataCell._columnElement) + .map((dataCell) => dataCell._columnElement!) .toList(growable: false); return _VirtualizingCellsRenderObjectWidget( - key: widget.key, + key: widget.key!, dataRow: widget.dataRow, isDirty: widget.isDirty, children: List.from(children), @@ -31,7 +31,10 @@ class _VirtualizingCellsWidgetState extends State<_VirtualizingCellsWidget> { class _VirtualizingCellsRenderObjectWidget extends MultiChildRenderObjectWidget { _VirtualizingCellsRenderObjectWidget( - {@required Key key, this.dataRow, this.isDirty, this.children}) + {required Key key, + required this.dataRow, + required this.isDirty, + required this.children}) : super(key: key, children: RepaintBoundary.wrapAll(List.from(children))); @override @@ -57,9 +60,9 @@ class _VirtualizingCellWidgetParentData extends ContainerBoxParentData { _VirtualizingCellWidgetParentData(); - double width = 0; - double height = 0; - Rect cellClipRect; + double width = 0.0; + double height = 0.0; + Rect? cellClipRect; void reset() { width = 0.0; @@ -76,7 +79,9 @@ class _RenderVirtualizingCellsWidget extends RenderBox RenderBoxContainerDefaultsMixin { _RenderVirtualizingCellsWidget( - {List children, DataRowBase dataRow, bool isDirty}) + {List? children, + required DataRowBase dataRow, + bool isDirty = false}) : _dataRow = dataRow, _isDirty = isDirty { addAll(children); @@ -92,7 +97,7 @@ class _RenderVirtualizingCellsWidget extends RenderBox markNeedsPaint(); } - dataRow?._isDirty = false; + dataRow._isDirty = false; } DataRowBase get dataRow => _dataRow; @@ -110,15 +115,17 @@ class _RenderVirtualizingCellsWidget extends RenderBox Rect rowRect = Rect.zero; - Rect rowClipRect; + Rect? rowClipRect; + + DataGridRowSwipeDirection? swipeDirection; Rect _measureRowRect(double width) { if (dataRow.isVisible) { final _DataGridSettings dataGridSettings = - dataRow._dataGridStateDetails(); + dataRow._dataGridStateDetails!(); final _VisualContainerHelper container = dataGridSettings.container; - final _VisibleLineInfo lineInfo = + final _VisibleLineInfo? lineInfo = container.scrollRows.getVisibleLineAtLineIndex(dataRow.rowIndex); final double lineSize = lineInfo != null ? lineInfo.size : 0.0; @@ -140,7 +147,15 @@ class _RenderVirtualizingCellsWidget extends RenderBox (lineInfo.isClippedBody && lineInfo.isClippedOrigin)) { final double top = lineInfo.size - lineInfo.clippedSize - lineInfo.clippedCornerExtent; - rowClipRect = Rect.fromLTWH(0, top, width, lineSize); + if (dataGridSettings.allowSwiping && dataRow._isSwipingRow) { + rowClipRect = _getSwipingRowClipRect( + dataGridSettings: dataGridSettings, top: top, height: lineSize); + } else { + rowClipRect = Rect.fromLTWH(0, top, width, lineSize); + } + } else if (dataGridSettings.allowSwiping && dataRow._isSwipingRow) { + rowClipRect = _getSwipingRowClipRect( + dataGridSettings: dataGridSettings, top: 0.0, height: lineSize); } else { rowClipRect = null; } @@ -152,6 +167,31 @@ class _RenderVirtualizingCellsWidget extends RenderBox } } + Rect? _getSwipingRowClipRect( + {required _DataGridSettings dataGridSettings, + required double top, + required double height}) { + if (dataGridSettings.swipingAnimation == null) { + return null; + } + double leftPosition = 0.0; + final viewWidth = dataGridSettings.viewWidth; + final extentWidth = dataGridSettings.container.extentWidth; + final swipingDelta = dataGridSettings.swipingOffset >= 0 + ? dataGridSettings.swipingAnimation!.value + : -dataGridSettings.swipingAnimation!.value; + + if (dataGridSettings.textDirection == TextDirection.rtl && + viewWidth > extentWidth) { + leftPosition = dataGridSettings.swipingOffset >= 0 + ? viewWidth - extentWidth + : (viewWidth - extentWidth) + swipingDelta; + } else { + leftPosition = dataGridSettings.swipingOffset >= 0 ? 0 : swipingDelta; + } + return Rect.fromLTWH(leftPosition, top, extentWidth - swipingDelta, height); + } + Rect _getRowRect(_DataGridSettings dataGridSettings, Offset offset) { bool needToSetMaxConstraint() => dataGridSettings.container.extentWidth < dataGridSettings.viewWidth && @@ -175,18 +215,12 @@ class _RenderVirtualizingCellsWidget extends RenderBox } void _drawRowBackground(PaintingContext context, Offset offset) { - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails(); + final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); final rect = _getRowRect(dataGridSettings, offset); var backgroundColor = Colors.transparent; - Color getDefaultSelectionBackgroundColor() { - return dataGridSettings.dataGridThemeData.brightness == Brightness.light - ? Color.fromRGBO(238, 238, 238, 1) - : Color.fromRGBO(48, 48, 48, 1); - } - Color getDefaultHeaderBackgroundColor() { - return dataGridSettings.dataGridThemeData.brightness == Brightness.light + return dataGridSettings.dataGridThemeData!.brightness == Brightness.light ? Color.fromRGBO(255, 255, 255, 1) : Color.fromRGBO(33, 33, 33, 1); } @@ -196,10 +230,10 @@ class _RenderVirtualizingCellsWidget extends RenderBox dataRow._visibleColumns.any((dataCell) => dataCell._rowSpan > 0); if (isRowSpanned) { - _RenderGridCell child = lastChild; - while (child != null) { + RenderBox? child = lastChild; + while (child != null && child is _RenderGridCell) { final _VirtualizingCellWidgetParentData childParentData = - child.parentData; + child.parentData as _VirtualizingCellWidgetParentData; final dataCell = child.dataCell; final lineInfo = dataRow._getColumnVisibleLineInfo(dataCell.columnIndex); @@ -209,8 +243,8 @@ class _RenderVirtualizingCellsWidget extends RenderBox final height = dataRow._getRowHeight( dataCell.rowIndex - dataCell._rowSpan, dataCell.rowIndex); Rect cellRect = Rect.zero; - if (child.cellClipRect != null) { - double left = columnRect.left; + if (cellClipRect != null) { + double left = columnRect!.left; double width = cellClipRect.width; if (cellClipRect.left > 0 && columnRect.width <= width) { left += cellClipRect.left; @@ -222,62 +256,49 @@ class _RenderVirtualizingCellsWidget extends RenderBox cellRect = Rect.fromLTWH(left, columnRect.top, width, height); } else { cellRect = Rect.fromLTWH( - columnRect.left, columnRect.top, columnRect.width, height); + columnRect!.left, columnRect.top, columnRect.width, height); } dataGridSettings.gridPaint?.color = backgroundColor; - context.canvas.drawRect(cellRect, dataGridSettings.gridPaint); + context.canvas.drawRect(cellRect, dataGridSettings.gridPaint!); } child = childParentData.previousSibling; } } } - if (dataGridSettings != null && - dataGridSettings.gridPaint != null && - dataRow != null) { - dataGridSettings.gridPaint.style = PaintingStyle.fill; + if (dataGridSettings.gridPaint != null) { + dataGridSettings.gridPaint!.style = PaintingStyle.fill; if (dataRow.rowRegion == RowRegion.header && dataRow.rowType == RowType.headerRow) { - backgroundColor = - dataGridSettings.dataGridThemeData.headerStyle.backgroundColor ?? - getDefaultHeaderBackgroundColor(); + backgroundColor = dataGridSettings.dataGridThemeData!.headerColor; drawSpannedRowBackgroundColor(backgroundColor); } else if (dataRow.rowRegion == RowRegion.header && dataRow.rowType == RowType.stackedHeaderRow) { backgroundColor = getDefaultHeaderBackgroundColor(); drawSpannedRowBackgroundColor(backgroundColor); } else { + /// Need to check the rowStyle Please look the previous version and + /// selection preference backgroundColor = dataRow.isSelectedRow - ? dataRow.rowStyle != null && - dataRow._stylePreference == - StylePreference.styleAndSelection - ? DataGridCellStyle.lerp( - dataGridSettings.dataGridThemeData.selectionStyle, - dataRow.rowStyle, - 0.5) - .backgroundColor - : dataGridSettings - .dataGridThemeData.selectionStyle.backgroundColor ?? - getDefaultSelectionBackgroundColor() - : dataRow.rowStyle != null - ? dataRow.rowStyle.backgroundColor ?? - dataGridSettings.dataGridThemeData.cellStyle.backgroundColor - : dataGridSettings.dataGridThemeData.cellStyle.backgroundColor; + ? dataGridSettings.dataGridThemeData!.selectionColor + : dataRow._dataGridRowAdapter!.color; } // Default theme color are common for both the HeaderBackgroundColor and // CellBackgroundColor, so we have checked commonly at outside of the // condition - backgroundColor ??= getDefaultHeaderBackgroundColor(); + if (backgroundColor == Colors.transparent) { + backgroundColor = getDefaultHeaderBackgroundColor(); + } dataGridSettings.gridPaint?.color = backgroundColor; - context.canvas.drawRect(rect, dataGridSettings.gridPaint); + context.canvas.drawRect(rect, dataGridSettings.gridPaint!); } } void _drawCurrentRowBorder(PaintingContext context, Offset offset) { - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails(); + final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); if (dataGridSettings.boxPainter != null && dataGridSettings.selectionMode == SelectionMode.multiple && @@ -289,7 +310,7 @@ class _RenderVirtualizingCellsWidget extends RenderBox final double stokeWidth = 1; final origin = (stokeWidth / 2 + - dataGridSettings.dataGridThemeData.gridLineStrokeWidth) + dataGridSettings.dataGridThemeData!.gridLineStrokeWidth) .ceil(); final Rect rowRect = _getRowRect(dataGridSettings, offset); @@ -302,10 +323,10 @@ class _RenderVirtualizingCellsWidget extends RenderBox dataGridSettings.gridLinesVisibility == GridLinesVisibility.horizontal; - dataGridSettings.boxPainter.paint( + dataGridSettings.boxPainter!.paint( context.canvas, Offset(rowRect.left + origin, rowRect.top + (origin / 2)), - dataGridSettings.configuration.copyWith( + dataGridSettings.configuration!.copyWith( size: Size( maxWidth - (origin * 2), constraints.maxHeight - @@ -322,25 +343,49 @@ class _RenderVirtualizingCellsWidget extends RenderBox } } + void _handleSwipingListener() { + final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); + dataGridSettings.source + ._notifyDataGridPropertyChangeListeners(propertyName: 'Swiping'); + } + + @override + void attach(PipelineOwner owner) { + final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); + dataGridSettings.swipingAnimationController + ?.addListener(_handleSwipingListener); + super.attach(owner); + } + + @override + void detach() { + final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); + dataGridSettings.swipingAnimationController + ?.removeListener(_handleSwipingListener); + + super.detach(); + } + @override bool get isRepaintBoundary => true; @override - bool hitTestChildren(BoxHitTestResult result, {Offset position}) { - _RenderGridCell child = lastChild; + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + RenderBox? child = lastChild; while (child != null) { final _VirtualizingCellWidgetParentData childParentData = - child.parentData; + child.parentData as _VirtualizingCellWidgetParentData; final bool isHit = result.addWithPaintOffset( offset: childParentData.offset, position: position, hitTest: (BoxHitTestResult result, Offset transformed) { - if (child.cellClipRect != null && - !child.cellClipRect.contains(transformed)) { + if (child is _RenderGridCell && + child.cellClipRect != null && + !child.cellClipRect!.contains(transformed)) { return false; } - return child.hitTest(result, position: transformed); + return child!.hitTest(result, position: transformed); }, ); @@ -353,44 +398,226 @@ class _RenderVirtualizingCellsWidget extends RenderBox } @override - bool hitTest(BoxHitTestResult result, {Offset position}) { + bool hitTest(BoxHitTestResult result, {required Offset position}) { final bool isRowSpanned = dataRow._visibleColumns.any((dataCell) => dataCell._rowSpan > 0); if (isRowSpanned) { - _RenderGridCell child = lastChild; + RenderBox? child = lastChild; while (child != null) { final _VirtualizingCellWidgetParentData childParentData = - child.parentData; - if (child.columnRect != null && child.columnRect.contains(position)) { + child.parentData as _VirtualizingCellWidgetParentData; + if (child is _RenderGridCell && + child.columnRect != null && + child.columnRect!.contains(position)) { return super.hitTest(result, position: Offset(position.dx.abs(), position.dy.abs())); } child = childParentData.previousSibling; } } + return super.hitTest(result, position: position); } + void _updateSwipingAnimation(_DataGridSettings dataGridSettings) { + dataGridSettings.swipingAnimation = Tween( + begin: 0.0, + end: dataGridSettings.swipingOffset.sign >= 0 + ? dataGridSettings.swipeMaxOffset + : -dataGridSettings.swipeMaxOffset) + .animate(dataGridSettings.swipingAnimationController!); + } + + void _handleSwipeStart( + PointerDownEvent event, _DataGridSettings dataGridSettings) { + dataGridSettings.isSwipingApplied = + (dataGridSettings.swipingOffset.abs() > 0) ? true : false; + + final swipedRow = dataGridSettings.rowGenerator.items.firstWhereOrNull( + (row) => row._isSwipingRow && dataGridSettings.swipingOffset.abs() > 0); + + if (swipedRow != null && swipedRow.rowIndex != dataRow.rowIndex) { + swipedRow._isSwipingRow = false; + dataGridSettings.swipingOffset = 0; + } + + dataGridSettings.source + ._notifyDataGridPropertyChangeListeners(propertyName: 'Swiping'); + } + + void _handleSwipeUpdate( + PointerMoveEvent event, _DataGridSettings dataGridSettings) { + bool canStartSwiping = true; + bool canUpdateSwiping = true; + final oldSwipingDelta = dataGridSettings.swipingOffset; + final currentSwipingDelta = + dataGridSettings.swipingOffset + event.localDelta.dx; + final rowIndex = _GridIndexResolver.resolveToRecordIndex( + dataGridSettings, dataRow.rowIndex); + final rowSwipeDirection = _SfDataGridHelper.getSwipeDirection( + dataGridSettings, currentSwipingDelta); + + if (dataGridSettings.onSwipeStart != null) { + final swipeStartDetails = DataGridSwipeStartDetails( + rowIndex: rowIndex, swipeDirection: rowSwipeDirection); + canStartSwiping = dataGridSettings.onSwipeStart!(swipeStartDetails); + } + + swipeDirection = currentSwipingDelta < 0 + ? DataGridRowSwipeDirection.endToStart + : DataGridRowSwipeDirection.startToEnd; + + if (dataGridSettings.swipingOffset.abs() == 0 || + dataGridSettings.swipingOffset.abs() == + dataGridSettings.swipeMaxOffset) { + dataGridSettings.isSwipingApplied = false; + } else { + dataGridSettings.isSwipingApplied = true; + } + + if (canStartSwiping && + dataGridSettings.allowSwiping && + (event.localDelta.dx.abs() > event.localDelta.dy.abs()) && + _SfDataGridHelper.canSwipeRow( + dataGridSettings, swipeDirection!, event.localDelta.dx)) { + if (dataGridSettings.onSwipeUpdate != null) { + final swipeUpdateDetails = DataGridSwipeUpdateDetails( + rowIndex: rowIndex, + swipeDirection: rowSwipeDirection, + swipeOffset: currentSwipingDelta); + canUpdateSwiping = dataGridSettings.onSwipeUpdate!(swipeUpdateDetails); + } + + if (canUpdateSwiping) { + if (dataGridSettings.swipingAnimationController!.isAnimating) { + return; + } + if (currentSwipingDelta >= 0 && + swipeDirection == DataGridRowSwipeDirection.startToEnd && + currentSwipingDelta >= dataGridSettings.swipeMaxOffset) { + dataGridSettings.swipingOffset = dataGridSettings.swipeMaxOffset; + } else if (currentSwipingDelta < 0 && + swipeDirection == DataGridRowSwipeDirection.endToStart && + -currentSwipingDelta >= dataGridSettings.swipeMaxOffset) { + dataGridSettings.swipingOffset = -dataGridSettings.swipeMaxOffset; + } else { + if (rowSwipeDirection == DataGridRowSwipeDirection.startToEnd + ? dataGridSettings.startSwipeActionsBuilder != null + : dataGridSettings.endSwipeActionsBuilder != null) { + dataGridSettings.swipingOffset += event.localDelta.dx; + } + } + dataRow._isSwipingRow = true; + if (oldSwipingDelta.sign != currentSwipingDelta.sign) { + _updateSwipingAnimation(dataGridSettings); + } + if (!dataGridSettings.swipingAnimationController!.isAnimating) { + dataGridSettings.swipingAnimationController!.value = + dataGridSettings.swipingOffset.abs() / + dataGridSettings.swipeMaxOffset; + } + dataGridSettings.source + ._notifyDataGridPropertyChangeListeners(propertyName: 'Swiping'); + } + } + } + + void _handleSwipeEnd( + PointerUpEvent event, _DataGridSettings dataGridSettings) { + void _onSwipeEnd() { + if (dataGridSettings.onSwipeEnd != null) { + final rowSwipeDirection = _SfDataGridHelper.getSwipeDirection( + dataGridSettings, dataGridSettings.swipingOffset); + final rowIndex = _GridIndexResolver.resolveToRecordIndex( + dataGridSettings, dataRow.rowIndex); + final swipeEndDetails = DataGridSwipeEndDetails( + rowIndex: rowIndex, swipeDirection: rowSwipeDirection); + dataGridSettings.onSwipeEnd!(swipeEndDetails); + } + } + + if (dataRow._isSwipingRow) { + if (dataGridSettings.swipingAnimationController!.isAnimating) { + return; + } + if (dataGridSettings.swipingOffset >= 0 && + swipeDirection == DataGridRowSwipeDirection.startToEnd && + dataGridSettings.swipingOffset > + dataGridSettings.swipeMaxOffset / 2) { + dataGridSettings.swipingOffset = dataGridSettings.swipeMaxOffset; + dataGridSettings.swipingAnimationController! + .forward() + .then((value) => _onSwipeEnd()); + } else if (dataGridSettings.swipingOffset < 0 && + swipeDirection == DataGridRowSwipeDirection.endToStart && + -dataGridSettings.swipingOffset > + dataGridSettings.swipeMaxOffset / 2) { + dataGridSettings.swipingOffset = -dataGridSettings.swipeMaxOffset; + dataGridSettings.swipingAnimationController! + .forward() + .then((value) => _onSwipeEnd()); + } else { + dataGridSettings.swipingAnimationController!.reverse().then((value) { + dataGridSettings.swipingOffset = 0; + dataRow._isSwipingRow = false; + _onSwipeEnd(); + dataGridSettings.source + ._notifyDataGridPropertyChangeListeners(propertyName: 'Swiping'); + }); + } + + dataGridSettings.isSwipingApplied = false; + dataGridSettings.source + ._notifyDataGridPropertyChangeListeners(propertyName: 'Swiping'); + } + } + + void _handleSwiping(PointerEvent event) { + final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); + if (dataGridSettings.allowSwiping) { + if (event is PointerDownEvent) { + _handleSwipeStart(event, dataGridSettings); + } + if (event is PointerMoveEvent) { + _handleSwipeUpdate(event, dataGridSettings); + } + if (event is PointerUpEvent) { + _handleSwipeEnd(event, dataGridSettings); + } + } + } + + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + _handleSwiping(event); + super.handleEvent(event, entry); + } + @override void performLayout() { - void _layout({RenderBox child, double width, double height}) { + void _layout( + {required RenderBox child, + required double width, + required double height}) { child.layout(BoxConstraints.tightFor(width: width, height: height), parentUsesSize: true); } - RenderBox child = firstChild; + RenderBox? child = firstChild; while (child != null) { - final _VirtualizingCellWidgetParentData _parentData = child.parentData; - final _RenderGridCell gridCell = child; - if (dataRow.isVisible && gridCell.dataCell.isVisible) { + final _VirtualizingCellWidgetParentData _parentData = + child.parentData as _VirtualizingCellWidgetParentData; + if (dataRow.isVisible && + child is _RenderGridCell && + child.dataCell.isVisible) { final Rect columnRect = - gridCell._measureColumnRect(constraints.maxHeight); + child._measureColumnRect(constraints.maxHeight)!; size = constraints.constrain(Size(columnRect.width, columnRect.height)); _parentData ..width = columnRect.width ..height = columnRect.height - ..cellClipRect = gridCell.cellClipRect; + ..cellClipRect = child.cellClipRect; _layout( child: child, width: _parentData.width, height: _parentData.height); _parentData.offset = Offset(columnRect.left, columnRect.top); @@ -410,18 +637,18 @@ class _RenderVirtualizingCellsWidget extends RenderBox // https://github.com/flutter/flutter/issues/29702 _drawRowBackground(context, offset); - RenderBox child = firstChild; + RenderBox? child = firstChild; while (child != null) { final _VirtualizingCellWidgetParentData childParentData = - child.parentData; + child.parentData as _VirtualizingCellWidgetParentData; if (childParentData.width != 0.0 && childParentData.height != 0.0) { if (childParentData.cellClipRect != null) { context.pushClipRect( needsCompositing, childParentData.offset + offset, - childParentData.cellClipRect, + childParentData.cellClipRect!, (context, offset) { - context.paintChild(child, offset); + context.paintChild(child!, offset); }, clipBehavior: Clip.antiAlias, ); @@ -431,7 +658,7 @@ class _RenderVirtualizingCellsWidget extends RenderBox } child = childParentData.nextSibling; } - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails(); + final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); if (dataGridSettings._isDesktop) { _drawCurrentRowBorder(context, offset); } @@ -440,6 +667,6 @@ class _RenderVirtualizingCellsWidget extends RenderBox class _HeaderCellsWidget extends _VirtualizingCellsWidget { const _HeaderCellsWidget( - {@required key, @required DataRowBase dataRow, bool isDirty}) + {required Key key, required DataRowBase dataRow, bool isDirty = false}) : super(key: key, dataRow: dataRow, isDirty: isDirty); } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/datagrid_datasource.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/datagrid_datasource.dart index 9f82de53a..5a2acb2cd 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/datagrid_datasource.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/datagrid_datasource.dart @@ -1,80 +1,79 @@ part of datagrid; +/// Signature for the [DataGridSourceChangeNotifier] listener. typedef _DataGridSourceListener = void Function( - {RowColumnIndex rowColumnIndex, - String propertyName, + {RowColumnIndex? rowColumnIndex}); + +/// Signature for the [DataGridSourceChangeNotifier] listener. +typedef _DataGridPropertyChangeListener = void Function( + {RowColumnIndex? rowColumnIndex, + String? propertyName, bool recalculateRowHeight}); /// A datasource for obtaining the row data for the [SfDataGrid]. /// /// The following APIs are mandatory to process the data, -/// * [dataSource] - The number of rows in a datagrid and row selection depends -/// on the [dataSource]. So, set the collection required for datagrid in -/// [dataSource]. -/// * [getValue] - The data needed for the cells is obtained from -/// [getValue]. +/// * [rows] - The number of rows in a datagrid and row selection depends +/// on the [rows]. So, set the collection required for datagrid in +/// [rows]. +/// * [buildRow] - The data needed for the cells is obtained from +/// [buildRow]. /// /// Call the [notifyDataSourceListeners] when performing CRUD in the underlying -/// datasource. When updating data in a cell, pass the corresponding -/// [RowColumnIndex] to the [notifyDataSourceListeners]. +/// datasource. /// /// [DataGridSource] objects are expected to be long-lived, not recreated with /// each build. /// ``` dart /// final List _employees = []; /// -/// class EmployeeDataSource extends DataGridSource { +/// class EmployeeDataSource extends DataGridSource { /// @override -/// List get dataSource => _employees; +/// List get rows => _employees +/// .map((dataRow) => DataGridRow(cells: [ +/// DataGridCell(columnName: 'id', value: dataRow.id), +/// DataGridCell(columnName: 'name', value: dataRow.name), +/// DataGridCell( +/// columnName: 'designation', value: dataRow.designation), +/// DataGridCell(columnName: 'salary', value: dataRow.salary), +/// ])) +/// .toList(); /// /// @override -/// getValue(Employee employee, String columnName){ -/// switch (columnName) { -/// case 'id': -/// return employee.id; -/// break; -/// case 'name': -/// return employee.name; -/// break; -/// case 'salary': -/// return employee.salary; -/// break; -/// case 'designation': -/// return employee.designation; -/// break; -/// default: -/// return ' '; -/// break; -/// } +/// DataGridRowAdapter buildRow(DataGridRow row) { +/// return DataGridRowAdapter( +/// cells: row.getCells().map((dataCell) { +/// return Text(dataCell.value.toString()); +/// }).toList()); /// } +/// } /// ``` -abstract class DataGridSource - extends DataGridSourceChangeNotifier with DataPagerDelegate { - /// Creates the [DataGridSource] for [SfDataGrid] widget. - DataGridSource({List dataSource}) { - _dataSource = dataSource ?? []; - _sortedColumns = []; - _effectiveDataSource = []; - } - - /// An underlying datasource to populate the rows for [SfDataGrid]. +abstract class DataGridSource extends DataGridSourceChangeNotifier + with DataPagerDelegate { + /// The collection of rows to display in [SfDataGrid]. /// - /// This property should be set to process the selection operation. - List get dataSource => _dataSource; - List _dataSource; + /// This must be non-null, but may be empty. + List get rows => List.empty(); - List _effectiveDataSource; - - List _unSortedDataSource = []; + /// Called to obtain the widget for each cell of the row. + /// + /// This method will be called for every row that are visible in datagrid’s + /// view port from the collection which is assigned to [DataGridSource.rows] + /// property. + /// + /// Return the widgets in the order in which those should be displayed in + /// each column of a row in [DataGridRowAdapter.cells]. + /// + /// The number of widgets in the collection must be exactly as many cells + /// as [SfDataGrid.columns] in the [SfDataGrid]. + /// + /// This method will be called whenever you call the [notifyListeners] method. + DataGridRowAdapter? buildRow(DataGridRow row); - /// Called to obtain the data for the cells. - Object getCellValue(int rowIndex, String columnName) { - return getValue(_effectiveDataSource[rowIndex], columnName); - } + List _effectiveRows = []; - /// Called to obtain the data for the cells from the corresponding object. - Object getValue(T data, String columnName) => null; + List _unSortedRows = []; /// Called whenever you call [notifyListeners] or [notifyDataSourceListeners] /// in the DataGridSource class. If you want to recalculate all columns @@ -89,52 +88,38 @@ abstract class DataGridSource /// Note: Column widths will be recalculated automatically whenever a new /// instance of DataGridSource is assigned to SfDataGrid. /// ``` dart - /// class EmployeeDataSource extends DataGridSource { + /// class EmployeeDataSource extends DataGridSource { /// @override - /// List get dataSource => _employees; + /// List get rows => _employees + /// .map((dataRow) => DataGridRow(cells: [ + /// DataGridCell(columnName: 'id', value: dataRow.id), + /// DataGridCell(columnName: 'name', value: dataRow.name), + /// DataGridCell( + /// columnName: 'designation', value: dataRow.designation), + /// DataGridCell(columnName: 'salary', value: dataRow.salary), + /// ])) + /// .toList(); /// - /// @override + /// @override /// bool shouldRecalculateColumnWidths() { /// return true; /// } /// /// @override - /// getValue(Employee employee, String columnName){ - /// switch (columnName) { - /// case 'id': - /// return employee.id; - /// break; - /// case 'name': - /// return employee.name; - /// break; - /// case 'salary': - /// return employee.salary; - /// break; - /// case 'designation': - /// return employee.designation; - /// break; - /// default: - /// return ' '; - /// break; - /// } + /// DataGridRowAdapter buildRow(DataGridRow row) { + /// return DataGridRowAdapter( + /// cells: row.getCells().map((dataCell) { + /// return Text(dataCell.value.toString()); + /// }).toList()); /// } + /// } + /// /// ``` @protected bool shouldRecalculateColumnWidths() { return false; } - @override - bool operator ==(Object other) => - identical(this, other) || - other is DataGridSource && runtimeType == other.runtimeType; - - @override - int get hashCode { - final List _hashList = [this, dataSource]; - return hashList(_hashList); - } - /// The collection of [SortColumnDetails] objects to sort the columns in /// [SfDataGrid]. /// @@ -165,11 +150,10 @@ abstract class DataGridSource /// source: _employeeDataSource, /// allowSorting: true, /// columns: [ - /// GridNumericColumn(mappingName: 'id', headerText: 'ID'), - /// GridTextColumn(mappingName: 'name', headerText: 'Name'), - /// GridTextColumn( - /// mappingName: 'designation', headerText: 'Designation'), - /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary') + /// GridTextColumn(columnName: 'id', label = Text('ID')), + /// GridTextColumn(columnName: 'name', label = Text('Name')), + /// GridTextColumn(columnName: 'designation', label = Text('Designation')), + /// GridTextColumn(columnName: 'salary', label = Text('Salary')), /// ], /// ), /// ], @@ -186,12 +170,12 @@ abstract class DataGridSource /// * [DataGridSource.sort] - call this method when you are adding the /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. List get sortedColumns => _sortedColumns; - List _sortedColumns; + List _sortedColumns = []; /// Called when the sorting is applied to a column. /// /// [SfDataGrid] sorts the data by cloning the datasource assigned in - /// [dataSource] property and applying the sorting in that cloned datasource. + /// [rows] property and applying the sorting in that cloned datasource. /// So, users won’t need to apply the sorting for the columns in application /// level. /// @@ -205,14 +189,16 @@ abstract class DataGridSource if (sortedColumns.isEmpty) { return true; } - _effectiveDataSource.sort((a, b) { + + _effectiveRows.sort((a, b) { return _compareValues(sortedColumns, a, b); }); + return true; } int _compareValues( - List sortedColumns, Object a, Object b) { + List sortedColumns, DataGridRow a, DataGridRow b) { if (sortedColumns.length > 1) { for (int i = 0; i < sortedColumns.length; i++) { final SortColumnDetails sortColumn = sortedColumns[i]; @@ -250,47 +236,63 @@ abstract class DataGridSource /// insensitive in ascending or descending order. /// /// ```dart - /// class EmployeeDataSource extends DataGridSource { - /// @override - /// List get dataSource => _employees + /// class EmployeeDataSource extends DataGridSource { + /// @override + /// List get rows => _employees + /// .map((dataRow) => DataGridRow(cells: [ + /// DataGridCell(columnName: 'id', value: dataRow.id), + /// DataGridCell(columnName: 'name', value: dataRow.name), + /// DataGridCell( + /// columnName: 'designation', value: dataRow.designation), + /// DataGridCell(columnName: 'salary', value: dataRow.salary), + /// ])) + /// .toList(); /// - /// @override - /// getValue(Employee employee, String columnName) { - /// switch (columnName) { - /// case 'id': - /// return employee.id; - /// break; - /// case 'name': - /// return employee.name; - /// break; - /// case 'salary': - /// return employee.salary; - /// break; - /// case 'designation': - /// return employee.designation; - /// break; - /// default: - /// return ' '; - /// break; + /// @override + /// DataGridRowAdapter buildRow(DataGridRow row) { + /// return DataGridRowAdapter( + /// cells: row.getCells().map((dataCell) { + /// return Text(dataCell.value.toString()); + /// }).toList()); /// } - /// } /// - /// @override - /// int compare(Employee a, Employee b, SortColumnDetails sortColumn) { - /// if (sortColumn.name == 'name') { - /// if (sortColumn.sortDirection == DataGridSortDirection.ascending) - /// return a.name.toLowerCase().compareTo(b.name.toLowerCase()); - /// else - /// return b.name.toLowerCase().compareTo(a.name.toLowerCase()); + /// @override + /// int compare(DataGridRow a, DataGridRow b, SortColumnDetails sortColumn) { + /// if (sortColumn.name == 'name') { + /// final String valueA = a + /// .getCells() + /// .firstWhere((dataCell) => dataCell.columnName == 'name', + /// orElse: () => null) + /// ?.value; + /// final String valueB = b + /// .getCells() + /// .firstWhere((dataCell) => dataCell.columnName == 'name', + /// orElse: () => null) + /// ?.value; + /// if (sortColumn.sortDirection == DataGridSortDirection.ascending) { + /// return valueA.toLowerCase().compareTo(valueB.toLowerCase()); + /// } else { + /// return valueA.toLowerCase().compareTo(valueB.toLowerCase()); + /// } + /// } + /// + /// return super.compare(a, b, sortColumn); /// } - /// return super.compare(a, b, sortColumn); /// } + /// /// ``` @protected - int compare(T a, T b, SortColumnDetails sortColumn) { - final Object value1 = getValue(a, sortColumn.name); - final Object value2 = getValue(b, sortColumn.name); - return _compareTo(value1, value2, sortColumn.sortDirection); + int compare(DataGridRow? a, DataGridRow? b, SortColumnDetails sortColumn) { + Object? _getCellValue(List? cells, String columnName) { + return cells + ?.firstWhereOrNull((element) => element.columnName == columnName) + ?.value ?? + null; + } + + final valueA = _getCellValue(a?.getCells() ?? null, sortColumn.name); + final valueB = _getCellValue(b?.getCells() ?? null, sortColumn.name); + return _compareTo(valueA, valueB, sortColumn.sortDirection); } int _compareTo( @@ -314,11 +316,12 @@ abstract class DataGridSource Future _updateDataSource() { if (sortedColumns.isNotEmpty) { - _unSortedDataSource = dataSource.toList(); - _effectiveDataSource = _unSortedDataSource; + _unSortedRows = rows.toList(); + _effectiveRows = _unSortedRows; } else { - _effectiveDataSource = dataSource; + _effectiveRows = rows; } + return handleSort(); } @@ -346,11 +349,10 @@ abstract class DataGridSource /// source: _employeeDataSource, /// allowSorting: true, /// columns: [ - /// GridNumericColumn(mappingName: 'id', headerText: 'ID'), - /// GridTextColumn(mappingName: 'name', headerText: 'Name'), - /// GridTextColumn( - /// mappingName: 'designation', headerText: 'Designation'), - /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary') + /// GridTextColumn(columnName: 'id', label = Text('ID')), + /// GridTextColumn(columnName: 'name', label = Text('Name')), + /// GridTextColumn(columnName: 'designation', label = Text('Designation')), + /// GridTextColumn(columnName: 'salary', label = Text('Salary')), /// ], /// ), /// ], @@ -360,18 +362,15 @@ abstract class DataGridSource /// ``` void sort() { _updateDataSource(); - notifyDataSourceListeners(propertyName: 'Sorting'); + _notifyDataGridPropertyChangeListeners(propertyName: 'Sorting'); } /// An indexer to retrieve the data from the underlying datasource. If the /// sorting is applied, the data will be retrieved from the sorted datasource. - /// - /// You can use this indexer when you get the data for the widget that you - /// return in [SfDataGrid.cellBuilder] callback for [GridWidgetColumn]. - T operator [](int index) => _effectiveDataSource[index]; + DataGridRow operator [](int index) => _effectiveRows[index]; /// Called when [LoadMoreRows] function is called from the - /// [loadMoreViewBuilder]. + /// [SfDataGrid.loadMoreViewBuilder]. /// /// Call the [notifyListeners] to refresh the datagrid based on current /// available rows. @@ -383,6 +382,16 @@ abstract class DataGridSource /// vertical scrolling. @protected Future handleLoadMoreRows() async {} + + /// Called when the `swipe to refresh` is performed in [SfDataGrid]. + /// + /// This method will be called only if the + /// [SfDataGrid.allowPullToRefresh] property returns true. + /// + /// Call the [notifyListeners] to refresh the datagrid based on current + /// available rows. + @protected + Future handleRefresh() async {} } /// Controls a [SfDataGrid] widget. @@ -397,29 +406,31 @@ class DataGridController extends DataGridSourceChangeNotifier { /// Creates the [DataGridController] with the [selectedIndex], [selectedRow] /// and [selectedRows]. DataGridController( - {int selectedIndex, Object selectedRow, List selectedRows}) + {int selectedIndex = -1, + DataGridRow? selectedRow, + List selectedRows = const []}) : _selectedRow = selectedRow, _selectedIndex = selectedIndex, - _selectedRows = selectedRows ?? [] { + _selectedRows = selectedRows.toList() { _currentCell = RowColumnIndex(-1, -1); } - _DataGridStateDetails _dataGridStateDetails; + _DataGridStateDetails? _dataGridStateDetails; /// The collection of objects that contains object of corresponding /// to the selected rows in [SfDataGrid]. - List get selectedRows => _selectedRows; - List _selectedRows = []; + List get selectedRows => _selectedRows; + List _selectedRows = List.empty(); /// The collection of objects that contains object of corresponding /// to the selected rows in [SfDataGrid]. - set selectedRows(List newSelectedRows) { + set selectedRows(List newSelectedRows) { if (_selectedRows == newSelectedRows) { return; } _selectedRows = newSelectedRows; - notifyDataSourceListeners(propertyName: 'selectedRows'); + _notifyDataGridPropertyChangeListeners(propertyName: 'selectedRows'); } /// An index of the corresponding selected row. @@ -433,41 +444,49 @@ class DataGridController extends DataGridSourceChangeNotifier { } _selectedIndex = newSelectedIndex; - notifyDataSourceListeners(propertyName: 'selectedIndex'); + _notifyDataGridPropertyChangeListeners(propertyName: 'selectedIndex'); } /// An object of the corresponding selected row. /// /// The given object must be given from the underlying datasource of the /// [SfDataGrid]. - Object get selectedRow => _selectedRow; - Object _selectedRow; + DataGridRow? get selectedRow => _selectedRow; + DataGridRow? _selectedRow; /// An object of the corresponding selected row. /// /// The given object must be given from the underlying datasource of the /// [SfDataGrid]. - set selectedRow(Object newSelectedRow) { + set selectedRow(DataGridRow? newSelectedRow) { if (_selectedRow == newSelectedRow) { return; } _selectedRow = newSelectedRow; - notifyDataSourceListeners(propertyName: 'selectedRow'); + _notifyDataGridPropertyChangeListeners(propertyName: 'selectedRow'); } + /// The current scroll offset of the vertical scrollbar. + double get verticalOffset => _verticalOffset; + double _verticalOffset = 0.0; + + /// The current scroll offset of the horizontal scrollbar. + double get horizontalOffset => _horizontalOffset; + double _horizontalOffset = 0.0; + ///If the [rowIndex] alone is given, the entire row will be set as dirty. ///So, data which is displayed in a row will be refreshed. /// You can call this method when the data is updated in row in /// underlying datasource. /// - /// If the [canRecalculateRowHeight] is set as true along with the [rowIndex], + /// If the `recalculateRowHeight` is set as true along with the [rowIndex], /// [SfDataGrid.onQueryRowHeight] callback will be called for that row. /// So, the row height can be reset based on the modified data. /// This is useful when setting auto row height /// using [SfDataGrid.onQueryRowHeight] callback. void refreshRow(int rowIndex, {bool recalculateRowHeight = false}) { - notifyDataSourceListeners( + _notifyDataGridPropertyChangeListeners( rowColumnIndex: RowColumnIndex(rowIndex, -1), propertyName: 'refreshRow', recalculateRowHeight: recalculateRowHeight); @@ -478,68 +497,224 @@ class DataGridController extends DataGridSourceChangeNotifier { /// This is used to identify the currently active cell to process the /// key navigation. RowColumnIndex get currentCell => _currentCell; - RowColumnIndex _currentCell; + RowColumnIndex _currentCell = RowColumnIndex.empty; /// Moves the currentcell to the specified cell coordinates. void moveCurrentCellTo(RowColumnIndex rowColumnIndex) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (dataGridSettings != null && - rowColumnIndex != null && - rowColumnIndex != RowColumnIndex(-1, -1) && - dataGridSettings.selectionMode != SelectionMode.none && - dataGridSettings.navigationMode != GridNavigationMode.row) { - final rowIndex = _GridIndexResolver.resolveToRowIndex( - dataGridSettings, rowColumnIndex.rowIndex); - final columnIndex = _GridIndexResolver.resolveToGridVisibleColumnIndex( - dataGridSettings, rowColumnIndex.columnIndex); - - final rowSelectionController = dataGridSettings.rowSelectionManager; - if (rowSelectionController is RowSelectionManager) { - rowSelectionController._processSelectionAndCurrentCell( - dataGridSettings, RowColumnIndex(rowIndex, columnIndex), - isProgrammatic: true); + if (_dataGridStateDetails != null) { + final _DataGridSettings? dataGridSettings = _dataGridStateDetails!(); + if (dataGridSettings != null && + rowColumnIndex != RowColumnIndex(-1, -1) && + dataGridSettings.selectionMode != SelectionMode.none && + dataGridSettings.navigationMode != GridNavigationMode.row) { + final rowIndex = _GridIndexResolver.resolveToRowIndex( + dataGridSettings, rowColumnIndex.rowIndex); + final columnIndex = _GridIndexResolver.resolveToGridVisibleColumnIndex( + dataGridSettings, rowColumnIndex.columnIndex); + if (rowIndex < 0 || columnIndex < 0) { + return; + } + final rowSelectionController = dataGridSettings.rowSelectionManager; + if (rowSelectionController is RowSelectionManager) { + rowSelectionController._processSelectionAndCurrentCell( + dataGridSettings, RowColumnIndex(rowIndex, columnIndex), + isProgrammatic: true); + } } } } - @override - bool operator ==(Object other) => - identical(this, other) || - other is DataGridController && runtimeType == other.runtimeType; + /// Scrolls the [SfDataGrid] to the given row and column index. + /// + /// If you want animation on scrolling, you can pass true as canAnimate argument. + /// + /// Also, you can control the position of a row when it comes to view by + /// passing the [DataGridScrollPosition] as an argument for rowPosition where + /// as you can pass [DataGridScrollPosition] as an argument for columnPosition + /// to control the position of a column. + Future scrollToCell(double rowIndex, double columnIndex, + {bool canAnimate = false, + DataGridScrollPosition rowPosition = DataGridScrollPosition.start, + DataGridScrollPosition columnPosition = + DataGridScrollPosition.start}) async { + if (_dataGridStateDetails != null) { + final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); + final scrollRows = dataGridSettings.container.scrollRows; + final scrollColumns = dataGridSettings.container.scrollColumns; + + if (rowIndex > dataGridSettings.container.rowCount || + columnIndex > scrollColumns.lineCount) { + return; + } + final _rowIndex = _GridIndexResolver.resolveToRowIndex( + dataGridSettings, rowIndex.toInt()); + final _columnIndex = _GridIndexResolver.resolveToGridVisibleColumnIndex( + dataGridSettings, columnIndex.toInt()); + double verticalOffset = _rowIndex < 0 + ? dataGridSettings.container.verticalOffset + : _SelectionHelper.getVerticalCumulativeDistance( + dataGridSettings, _rowIndex); + double horizontalOffset = _columnIndex < 0 + ? dataGridSettings.container.horizontalOffset + : _SelectionHelper.getHorizontalCumulativeDistance( + dataGridSettings, _columnIndex); + if (dataGridSettings.textDirection == TextDirection.rtl && + columnIndex == -1) { + horizontalOffset = dataGridSettings.container.extentWidth - + dataGridSettings.viewWidth - + horizontalOffset > + 0 + ? dataGridSettings.container.extentWidth - + dataGridSettings.viewWidth - + horizontalOffset + : 0; + } + if (rowPosition == DataGridScrollPosition.center) { + verticalOffset = verticalOffset - + ((dataGridSettings.viewHeight - + scrollRows.footerExtent - + scrollRows.headerExtent) / + 2) + + (dataGridSettings.rowHeight / 2); + } else if (rowPosition == DataGridScrollPosition.end) { + verticalOffset = verticalOffset - + (dataGridSettings.viewHeight - + scrollRows.footerExtent - + scrollRows.headerExtent) + + dataGridSettings.rowHeight; + } else if (rowPosition == DataGridScrollPosition.makeVisible) { + final visibleRows = scrollRows.getVisibleLines(); + final startIndex = + visibleRows[visibleRows.firstBodyVisibleIndex].lineIndex; + final endIndex = + visibleRows[visibleRows.lastBodyVisibleIndex].lineIndex; + if (_rowIndex > startIndex && _rowIndex < endIndex) { + verticalOffset = dataGridSettings.container.verticalOffset; + } + if (dataGridSettings.container.verticalOffset - verticalOffset < 0) { + verticalOffset = verticalOffset - + (dataGridSettings.viewHeight - + scrollRows.footerExtent - + scrollRows.headerExtent) + + dataGridSettings.rowHeight; + } + } + if (columnPosition == DataGridScrollPosition.center) { + horizontalOffset = horizontalOffset - + ((dataGridSettings.viewWidth - + scrollColumns.footerExtent - + scrollColumns.headerExtent) / + 2) + + (dataGridSettings.defaultColumnWidth / 2); + } else if (columnPosition == DataGridScrollPosition.end) { + horizontalOffset = horizontalOffset - + (dataGridSettings.viewWidth - + scrollColumns.footerExtent - + scrollColumns.headerExtent) + + dataGridSettings.defaultColumnWidth; + } else if (columnPosition == DataGridScrollPosition.makeVisible) { + final visibleColumns = scrollColumns.getVisibleLines(); + final startIndex = + visibleColumns[visibleColumns.firstBodyVisibleIndex].lineIndex; + final endIndex = + visibleColumns[visibleColumns.lastBodyVisibleIndex].lineIndex; + if (_columnIndex > startIndex && _columnIndex < endIndex) { + horizontalOffset = dataGridSettings.container.horizontalOffset; + } + if (dataGridSettings.container.horizontalOffset - horizontalOffset < + 0) { + horizontalOffset = horizontalOffset - + (dataGridSettings.viewWidth - + scrollColumns.footerExtent - + scrollColumns.headerExtent) + + dataGridSettings.defaultColumnWidth; + } + } - @override - int get hashCode { - final List _hashList = [ - selectedRows, - selectedRow, - selectedIndex, - currentCell - ]; - return hashList(_hashList); + _SfDataGridHelper.scrollVertical( + dataGridSettings, verticalOffset, canAnimate); + // Need to add await for the horizontal scrolling alone, to avoid the delay time between vertical and horizontal scrolling. + await _SfDataGridHelper.scrollHorizontal( + dataGridSettings, horizontalOffset, canAnimate); + } + } + + /// Scrolls the [SfDataGrid] to the given index. + /// + /// If you want animation on scrolling, you can pass true as canAnimate argument. + /// + /// Also, you can control the position of a row when it comes to view by passing + /// the [DataGridScrollPosition] as an argument for position. + Future scrollToRow(double rowIndex, + {bool canAnimate = false, + DataGridScrollPosition position = DataGridScrollPosition.start}) async { + return scrollToCell(rowIndex, -1, + canAnimate: canAnimate, rowPosition: position); + } + + /// Scrolls the [SfDataGrid] to the given column index. + /// + /// If you want animation on scrolling, you can pass true as canAnimate argument. + /// + /// Also, you can control the position of a row when it comes to view by passing + /// the [DataGridScrollPosition] as an argument for position. + Future scrollToColumn(double columnIndex, + {bool canAnimate = false, + DataGridScrollPosition position = DataGridScrollPosition.start}) async { + return scrollToCell(-1, columnIndex, + canAnimate: canAnimate, columnPosition: position); + } + + /// Scroll the vertical scrollbar from current position to the given value. + /// + /// If you want animation on scrolling, you can pass true as canAnimate argument. + Future scrollToVerticalOffset(double offset, + {bool canAnimate = false}) async { + if (_dataGridStateDetails != null) { + final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); + return _SfDataGridHelper.scrollVertical( + dataGridSettings, offset, canAnimate); + } + } + + /// Scroll the horizontal scrollbar from current value to the given value. + /// + /// If you want animation on scrolling, you can pass true as canAnimate argument. + Future scrollToHorizontalOffset(double offset, + {bool canAnimate = false}) async { + if (_dataGridStateDetails != null) { + final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); + return _SfDataGridHelper.scrollHorizontal( + dataGridSettings, offset, canAnimate); + } } } /// ToDO class DataGridSourceChangeNotifier extends ChangeNotifier { - ObserverList<_DataGridSourceListener> _dataSourceListeners = + final ObserverList<_DataGridSourceListener> _dataGridSourceListeners = ObserverList<_DataGridSourceListener>(); - @override - void addListener(Object listener) { - if (listener is _DataGridSourceListener) { - _dataSourceListeners.add(listener); - } else { - super.addListener(listener); - } + void _addDataGridSourceListener(_DataGridSourceListener listener) { + _dataGridSourceListeners.add(listener); } - @override - void removeListener(Object listener) { - if (listener is _DataGridSourceListener) { - _dataSourceListeners.remove(listener); - } else { - super.removeListener(listener); - } + void _removeDataGridSourceListener(_DataGridSourceListener listener) { + _dataGridSourceListeners.remove(listener); + } + + final ObserverList<_DataGridPropertyChangeListener> + _dataGridPropertyChangeListeners = + ObserverList<_DataGridPropertyChangeListener>(); + + void _addDataGridPropertyChangeListener( + _DataGridPropertyChangeListener listener) { + _dataGridPropertyChangeListeners.add(listener); + } + + void _removeDataGridPropertyChangeListener( + _DataGridPropertyChangeListener listener) { + _dataGridPropertyChangeListeners.remove(listener); } @protected @@ -548,24 +723,25 @@ class DataGridSourceChangeNotifier extends ChangeNotifier { super.notifyListeners(); } - /// ToDO + /// Calls all the datagrid source listeners. + /// Call this method whenever the underlying data is added or removed. If the value of the specific cell is updated, call this method with RowColumnIndex argument where it refers the corresponding row and column index of the cell. @protected - void notifyDataSourceListeners( - {RowColumnIndex rowColumnIndex, - String propertyName, - bool recalculateRowHeight}) { - for (final listener in _dataSourceListeners) { + void notifyDataSourceListeners({RowColumnIndex? rowColumnIndex}) { + for (final listener in _dataGridSourceListeners) { + listener(rowColumnIndex: rowColumnIndex); + } + } + + /// Call this method whenever the rowColumnIndex, propertyName and recalculateRowHeight of the underlying data are updated internally. + void _notifyDataGridPropertyChangeListeners( + {RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false}) { + for (final listener in _dataGridPropertyChangeListeners) { listener( rowColumnIndex: rowColumnIndex, propertyName: propertyName, recalculateRowHeight: recalculateRowHeight); } } - - @mustCallSuper - @override - void dispose() { - super.dispose(); - _dataSourceListeners = null; - } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell.dart index 53d09dd0a..2b029d006 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell.dart @@ -6,10 +6,14 @@ class DataCell extends DataCellBase { DataCell(); @override - Widget _onInitializeColumnElement(bool isInEdit) { + Widget? _onInitializeColumnElement(bool isInEdit) { if (_cellType != CellType.indentCell) { - _renderer.setCellStyle(this); - return _renderer.onPrepareWidgets(this); + if (_renderer != null) { + _renderer!.setCellStyle(this); + return _renderer!.onPrepareWidgets(this); + } else { + return null; + } } else { return null; } @@ -20,7 +24,7 @@ class DataCell extends DataCellBase { if (_renderer == null) { return; } - _renderer + _renderer! ..setCellStyle(this) ..onPrepareWidgets(this); } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell_base.dart index b5d6eda58..4e5a83509 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell_base.dart @@ -2,37 +2,23 @@ part of datagrid; /// A base class which provides functionalities for [DataCell]. abstract class DataCellBase { - /// Creates the [DataCellBase] for [SfDataGrid] widget. - DataCellBase() { - _isVisible = true; - _isEnsured = false; - _isDirty = false; - _columnSpan = 0; - _rowSpan = 0; - _ishover = false; - } - - Widget _columnElement; - - Key _key; - - DataGridCellStyle _cellStyle; + Widget? _columnElement; - GridCellRendererBase _renderer; + Key? _key; - CellType _cellType; + TextStyle? _textStyle; - bool _isVisible; + GridCellRendererBase? _renderer; - bool _isEnsured; + CellType? _cellType; - DataRowBase _dataRow; + bool _isVisible = true; - bool _isDirty; + bool _isEnsured = false; - bool _ishover; + DataRowBase? _dataRow; - String _displayText; + bool _isDirty = false; /// The column index of the [DataCell]. int columnIndex = -1; @@ -41,31 +27,31 @@ abstract class DataCellBase { int rowIndex = -1; /// [GridColumn] which is associated with [DataCell]. - GridColumn gridColumn; + GridColumn? gridColumn; /// The cell value of the column element associated with the [DataCell]. - Object cellValue; + Object? cellValue; /// Decides whether the [DataCell] has the currentcell. bool isCurrentCell = false; - int _columnSpan; + int _columnSpan = 0; - int _rowSpan; + int _rowSpan = 0; - StackedHeaderCell _stackedHeaderCell; + StackedHeaderCell? _stackedHeaderCell; /// Decides whether the [DataCell] is visible. bool get isVisible => _isVisible; - Widget _onInitializeColumnElement(bool isInEdit) => null; + Widget? _onInitializeColumnElement(bool isInEdit) => null; void _updateColumn() {} void _onTouchUp() { if (_dataRow != null) { final _DataGridSettings dataGridSettings = - _dataRow._dataGridStateDetails(); + _dataRow!._dataGridStateDetails!(); if (rowIndex <= _GridIndexResolver.getHeaderIndex(dataGridSettings) || dataGridSettings.selectionMode == SelectionMode.none) { return; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row.dart index 0ee42f7af..670dcc9e0 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row.dart @@ -54,7 +54,7 @@ class DataRow extends DataRowBase { } for (int index = startColumnIndex; index <= endColumnIndex; index++) { - var dc = _createColumn(index); + DataCellBase? dc = _createColumn(index); _visibleColumns.add(dc); dc = null; } @@ -112,21 +112,19 @@ class DataRow extends DataRowBase { } for (int index = startColumnIndex; index <= endColumnIndex; index++) { - var dc = _indexer(index); + DataCellBase? dc = _indexer(index); if (dc == null) { - var dataCell = _reUseCell(startColumnIndex, endColumnIndex); + DataCellBase? dataCell = _reUseCell(startColumnIndex, endColumnIndex); - dataCell ??= _visibleColumns.firstWhere( - (col) => - col.columnIndex == -1 && col._cellType != CellType.indentCell, - orElse: () => null); + dataCell ??= _visibleColumns.firstWhereOrNull((col) => + col.columnIndex == -1 && col._cellType != CellType.indentCell); _updateColumn(dataCell, index); dataCell = null; } - dc ??= _visibleColumns.firstWhere((col) => col.columnIndex == index, - orElse: () => null); + dc ??= + _visibleColumns.firstWhereOrNull((col) => col.columnIndex == index); if (dc != null) { if (!dc._isVisible) { @@ -150,7 +148,7 @@ class DataRow extends DataRowBase { } DataCellBase _createColumn(int index) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); final canIncrementHeight = rowType == RowType.headerRow && dataGridSettings.stackedHeaderRows.isNotEmpty; final dc = DataCell() @@ -169,15 +167,13 @@ class DataRow extends DataRowBase { .._cellType = CellType.headerCell; } else { dc - .._renderer = dc.gridColumn._cellType.isNotEmpty - ? dataGridSettings.cellRenderers[dc.gridColumn._cellType] - : dataGridSettings.cellRenderers['TextField'] + .._renderer = dataGridSettings.cellRenderers[dc.gridColumn!._cellType] .._cellType = CellType.gridCell; } if (canIncrementHeight) { final rowSpan = _StackedHeaderHelper._getRowSpan( - dataGridSettings, dc._dataRow.rowIndex - 1, index, false, - mappingName: dc.gridColumn.mappingName); + dataGridSettings, dc._dataRow!.rowIndex - 1, index, false, + mappingName: dc.gridColumn!.columnName); dc._rowSpan = rowSpan; } @@ -185,7 +181,7 @@ class DataRow extends DataRowBase { return dc; } - DataCellBase _indexer(int index) { + DataCellBase? _indexer(int index) { for (final column in _visibleColumns) { if (column.columnIndex == index) { return column; @@ -195,18 +191,16 @@ class DataRow extends DataRowBase { return null; } - DataCellBase _reUseCell(int startColumnIndex, int endColumnIndex) => - _visibleColumns.firstWhere( - (cell) => - cell.gridColumn != null && - (cell.columnIndex < 0 || - cell.columnIndex < startColumnIndex || - cell.columnIndex > endColumnIndex) && - !cell._isEnsured, - orElse: () => null); - - void _updateColumn(DataCellBase dc, int index) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + DataCellBase? _reUseCell(int startColumnIndex, int endColumnIndex) => + _visibleColumns.firstWhereOrNull((cell) => + cell.gridColumn != null && + (cell.columnIndex < 0 || + cell.columnIndex < startColumnIndex || + cell.columnIndex > endColumnIndex) && + !cell._isEnsured); + + void _updateColumn(DataCellBase? dc, int index) { + final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); final canIncrementHeight = rowType == RowType.headerRow && dataGridSettings.stackedHeaderRows.isNotEmpty; if (dc != null) { @@ -225,8 +219,8 @@ class DataRow extends DataRowBase { _updateRenderer(dataGridSettings, dc, dc.gridColumn); if (canIncrementHeight) { final rowSpan = _StackedHeaderHelper._getRowSpan( - dataGridSettings, dc._dataRow.rowIndex - 1, index, false, - mappingName: dc.gridColumn.mappingName); + dataGridSettings, dc._dataRow!.rowIndex - 1, index, false, + mappingName: dc.gridColumn!.columnName); dc._rowSpan = rowSpan; } else { dc._rowSpan = 0; @@ -246,13 +240,13 @@ class DataRow extends DataRowBase { } void _updateRenderer(_DataGridSettings dataGridSettings, - DataCellBase dataColumn, GridColumn column) { - GridCellRendererBase newRenderer; + DataCellBase dataColumn, GridColumn? column) { + GridCellRendererBase? newRenderer; if (rowRegion == RowRegion.header && rowType == RowType.headerRow) { newRenderer = dataGridSettings.cellRenderers['ColumnHeader']; dataColumn._cellType = CellType.headerCell; } else { - newRenderer = dataGridSettings.cellRenderers[column._cellType]; + newRenderer = dataGridSettings.cellRenderers[column!._cellType]; dataColumn._cellType = CellType.gridCell; } @@ -260,7 +254,8 @@ class DataRow extends DataRowBase { newRenderer = null; } - void _checkForCurrentCell(_DataGridSettings dataGridSettings, DataCell dc) { + void _checkForCurrentCell( + _DataGridSettings dataGridSettings, DataCellBase dc) { if (dataGridSettings.navigationMode == GridNavigationMode.cell) { final _CurrentCellManager currentCellManager = dataGridSettings.currentCell; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row_base.dart index 601516ae5..25acd7273 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row_base.dart @@ -2,28 +2,20 @@ part of datagrid; /// A base class which provides functionalities for [DataRow]. abstract class DataRowBase { - /// Creates the [DataRowBase] for [SfDataGrid] widget. - DataRowBase() { - _isDirty = false; - _isEnsured = false; - _isVisible = true; - _visibleColumns = []; - _stylePreference = StylePreference.selection; - } - - _DataGridStateDetails _dataGridStateDetails; + _DataGridStateDetails? _dataGridStateDetails; - Key _key; + Key? _key; - bool _isDirty; + bool _isDirty = false; - bool _isEnsured; + bool _isEnsured = false; - bool _isVisible; + bool _isVisible = true; - List _visibleColumns; + List _visibleColumns = []; - StylePreference _stylePreference; + // This flag is used to indicating whether the row is swiped or not. + bool _isSwipingRow = false; /// The row index of the [DataRow]. int rowIndex = -1; @@ -37,12 +29,13 @@ abstract class DataRowBase { /// Decides whether the [DataRow] is visible. bool get isVisible => _isVisible; - /// The style of the row which is set through [SfDataGrid.onQueryRowStyle]. - DataGridCellStyle rowStyle; - /// Decides whether the [DataRow] is currently active bool isCurrentRow = false; + DataGridRow? _dataGridRow; + + DataGridRowAdapter? _dataGridRowAdapter; + void _rowIndexChanged() { if (rowIndex < 0) { return; @@ -64,20 +57,21 @@ abstract class DataRowBase { void _ensureColumns(_VisibleLinesCollection visibleColumnLines) {} - _VisibleLineInfo _getColumnVisibleLineInfo(int index) => - _dataGridStateDetails() + _VisibleLineInfo? _getColumnVisibleLineInfo(int index) => + _dataGridStateDetails!() .container .scrollColumns .getVisibleLineAtLineIndex(index); - _VisibleLineInfo _getRowVisibleLineInfo(int index) => _dataGridStateDetails() - .container - .scrollRows - .getVisibleLineAtLineIndex(index); + _VisibleLineInfo? _getRowVisibleLineInfo(int index) => + _dataGridStateDetails!() + .container + .scrollRows + .getVisibleLineAtLineIndex(index); double _getColumnWidth(int startIndex, int endIndex) { if (startIndex != endIndex) { - final currentPos = _dataGridStateDetails() + final currentPos = _dataGridStateDetails!() .container .scrollColumns .rangeToRegionPoints(startIndex, endIndex, true); @@ -94,7 +88,7 @@ abstract class DataRowBase { double _getRowHeight(int startIndex, int endIndex) { if (startIndex != endIndex) { - final currentPos = _dataGridStateDetails() + final currentPos = _dataGridStateDetails!() .container .scrollRows .rangeToRegionPoints(startIndex, endIndex, true); diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/row_generator.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/row_generator.dart index 0533d5177..594635c77 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/row_generator.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/row_generator.dart @@ -1,7 +1,7 @@ part of datagrid; class _RowGenerator { - _RowGenerator({_DataGridStateDetails dataGridStateDetails}) { + _RowGenerator({required _DataGridStateDetails dataGridStateDetails}) { _dataGridStateDetails = dataGridStateDetails; items = []; } @@ -9,20 +9,20 @@ class _RowGenerator { List items = []; _DataGridStateDetails get dataGridStateDetails => _dataGridStateDetails; - _DataGridStateDetails _dataGridStateDetails; + late _DataGridStateDetails _dataGridStateDetails; _VisualContainerHelper get container => dataGridStateDetails().container; - void _preGenerateRows(_VisibleLinesCollection visibleRows, - _VisibleLinesCollection visibleColumns) { + void _preGenerateRows(_VisibleLinesCollection? visibleRows, + _VisibleLinesCollection? visibleColumns) { if (items.isNotEmpty || dataGridStateDetails().container.rowCount <= 0) { return; } - if (visibleRows != null) { + if (visibleRows != null && visibleColumns != null) { for (int i = 0; i < visibleRows.length; i++) { - var line = visibleRows[i]; - DataRowBase dr; + _VisibleLineInfo? line = visibleRows[i]; + late DataRowBase? dr; switch (line.region) { case _ScrollAxisRegion.header: dr = _createHeaderRow(line.lineIndex, visibleColumns); @@ -35,10 +35,7 @@ class _RowGenerator { break; } - if (dr != null) { - items.add(dr); - } - + items.add(dr); dr = null; line = null; } @@ -47,13 +44,13 @@ class _RowGenerator { void _ensureRows(_VisibleLinesCollection visibleRows, _VisibleLinesCollection visibleColumns) { - var actualStartAndEndIndex = []; - var region = RowRegion.header; + List? actualStartAndEndIndex = []; + RowRegion? region = RowRegion.header; List reUseRows() => items .where((row) => (row.rowIndex < 0 || - row.rowIndex < actualStartAndEndIndex[0] || + row.rowIndex < actualStartAndEndIndex![0] || row.rowIndex > actualStartAndEndIndex[1]) && !row._isEnsured) .toList(growable: false); @@ -77,17 +74,16 @@ class _RowGenerator { for (int index = actualStartAndEndIndex[0]; index <= actualStartAndEndIndex[1]; index++) { - var dr = _indexer(index); + DataRowBase? dr = _indexer(index); if (dr == null) { - var rows = reUseRows(); - if (rows != null) { + List? rows = reUseRows(); + if (rows.isNotEmpty) { _updateRow(rows, index, region); rows = null; } } - dr ??= items.firstWhere((row) => row.rowIndex == index, - orElse: () => null); + dr ??= items.firstWhereOrNull((row) => row.rowIndex == index); if (dr != null) { if (!dr._isVisible) { @@ -102,10 +98,8 @@ class _RowGenerator { dr = _createDataRow(index, visibleColumns); } - if (dr != null) { - dr._isEnsured = true; - items.add(dr); - } + dr._isEnsured = true; + items.add(dr); } dr = null; @@ -131,13 +125,22 @@ class _RowGenerator { DataRowBase _createDataRow( int rowIndex, _VisibleLinesCollection visibleColumns, {RowRegion rowRegion = RowRegion.body}) { + final _DataGridSettings dataGridSettings = dataGridStateDetails(); final dr = DataRow() .._dataGridStateDetails = dataGridStateDetails ..rowIndex = rowIndex ..rowRegion = rowRegion ..rowType = RowType.dataRow; dr._key = ObjectKey(dr); - _checkQueryRowStyle(dr); + dr + .._dataGridRow = + _SfDataGridHelper.getDataGridRow(dataGridSettings, rowIndex) + .._dataGridRowAdapter = _SfDataGridHelper.getDataGridRowAdapter( + dataGridSettings, dr._dataGridRow!); + assert(_SfDataGridHelper.debugCheckTheLength( + dataGridSettings.columns.length, + dr._dataGridRowAdapter!.cells.length, + 'SfDataGrid.columns.length == DataGridRowAdapter.cells.length')); _checkForCurrentRow(dr); _checkForSelection(dr); dr._initializeDataRow(visibleColumns); @@ -158,8 +161,7 @@ class _RowGenerator { .._key = ObjectKey(dr) .._initializeDataRow(visibleColumns); return dr; - } else if (dataGridSettings.stackedHeaderRows != null && - rowIndex < dataGridSettings.stackedHeaderRows.length) { + } else if (rowIndex < dataGridSettings.stackedHeaderRows.length) { dr = _SpannedDataRow(); dr._key = ObjectKey(dr); dr.rowIndex = rowIndex; @@ -192,7 +194,7 @@ class _RowGenerator { rowRegion: RowRegion.footer); } - DataRowBase _indexer(int index) { + DataRowBase? _indexer(int index) { for (int i = 0; i < items.length; i++) { if (items[i].rowIndex == index) { return items[i]; @@ -203,12 +205,11 @@ class _RowGenerator { } void _updateRow(List rows, int index, RowRegion region) { + final _DataGridSettings dataGridSettings = dataGridStateDetails(); if (region == RowRegion.header) { - DataRowBase dr; - final _DataGridSettings dataGridSettings = dataGridStateDetails(); + DataRowBase? dr; if (index == _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - dr = rows.firstWhere((row) => row.rowType == RowType.headerRow, - orElse: () => null); + dr = rows.firstWhereOrNull((row) => row.rowType == RowType.headerRow); if (dr != null) { dr .._key = dr._key @@ -225,8 +226,8 @@ class _RowGenerator { dr = null; } } else if (index < dataGridSettings.stackedHeaderRows.length) { - dr = rows.firstWhere((r) => r.rowType == RowType.stackedHeaderRow, - orElse: () => null); + dr = + rows.firstWhereOrNull((r) => r.rowType == RowType.stackedHeaderRow); if (dr != null) { dr._key = dr._key; dr.rowIndex = index; @@ -245,17 +246,17 @@ class _RowGenerator { dr = null; } } else { - _updateDataRow(rows, index, region); + _updateDataRow(rows, index, region, dataGridSettings); } } else { - _updateDataRow(rows, index, region); + _updateDataRow(rows, index, region, dataGridSettings); } } - void _updateDataRow(List rows, int index, RowRegion region) { - var row = rows?.firstWhere( - (row) => row is DataRow && row.rowType == RowType.dataRow, - orElse: () => null); + void _updateDataRow(List rows, int index, RowRegion region, + _DataGridSettings dataGridSettings) { + DataRowBase? row = rows.firstWhereOrNull( + (row) => row is DataRow && row.rowType == RowType.dataRow); if (row != null && row is DataRow) { if (index < 0 || index >= container.scrollRows.lineCount) { @@ -264,15 +265,22 @@ class _RowGenerator { row .._key = row._key ..rowIndex = index - ..rowRegion = region; - _checkQueryRowStyle(row); + ..rowRegion = region + .._dataGridRow = + _SfDataGridHelper.getDataGridRow(dataGridSettings, index) + .._dataGridRowAdapter = _SfDataGridHelper.getDataGridRowAdapter( + dataGridSettings, row._dataGridRow!); + assert(_SfDataGridHelper.debugCheckTheLength( + dataGridSettings.columns.length, + row._dataGridRowAdapter!.cells.length, + 'SfDataGrid.columns.length == DataGridRowAdapter.cells.length')); _checkForCurrentRow(row); _checkForSelection(row); row._rowIndexChanged(); } row = null; } else { - var dr = _createDataRow( + DataRowBase? dr = _createDataRow( index, _SfDataGridHelper.getVisibleLines(dataGridStateDetails())); items.add(dr); dr = null; @@ -280,46 +288,29 @@ class _RowGenerator { } double _queryRowHeight(int rowIndex, double height) { - final height = dataGridStateDetails().container.rowHeights[rowIndex]; - final rowHeight = dataGridStateDetails() - .onQueryRowHeight(RowHeightDetails(rowIndex, height)); + final _DataGridSettings dataGridSettings = dataGridStateDetails(); + var rowHeight = height; + if (dataGridSettings.onQueryRowHeight != null) { + rowHeight = dataGridSettings + .onQueryRowHeight!(RowHeightDetails(rowIndex, height)); + } + return rowHeight; } void _checkForSelection(DataRowBase row) { final _DataGridSettings dataGridSettings = dataGridStateDetails(); if (dataGridSettings.selectionMode != SelectionMode.none) { - final RowSelectionManager rowSelectionManager = - dataGridSettings.rowSelectionManager; + final RowSelectionManager? rowSelectionManager = + dataGridSettings.rowSelectionManager as RowSelectionManager; final int recordIndex = _GridIndexResolver.resolveToRecordIndex( dataGridSettings, row.rowIndex); - final Object record = - dataGridSettings.source._effectiveDataSource[recordIndex]; + final DataGridRow? record = + dataGridSettings.source._effectiveRows[recordIndex]; if (record != null) { - row._isSelectedRow = rowSelectionManager._selectedRows.contains(record); - } - } - } - - void _checkQueryRowStyle(DataRowBase dataRow) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - - if (dataGridSettings.onQueryRowStyle != null) { - final queryRowStyleArgs = QueryRowStyleArgs(rowIndex: dataRow.rowIndex); - final rowStyle = dataGridSettings.onQueryRowStyle(queryRowStyleArgs); - if (rowStyle != null) { - dataRow - ..rowStyle = rowStyle - .._stylePreference = queryRowStyleArgs.stylePreference; - } else { - dataRow - ..rowStyle = null - .._stylePreference = StylePreference.selection; + row._isSelectedRow = + rowSelectionManager!._selectedRows.contains(record); } - } else { - dataRow - ..rowStyle = null - .._stylePreference = StylePreference.selection; } } @@ -329,15 +320,13 @@ class _RowGenerator { final _CurrentCellManager currentCellManager = dataGridSettings.currentCell; - DataCellBase getDataCell() { + DataCellBase? getDataCell() { if (dr._visibleColumns.isEmpty) { return null; } - final dc = dr._visibleColumns.firstWhere( - (dataCell) => - dataCell.columnIndex == currentCellManager.columnIndex, - orElse: () => null); + final dc = dr._visibleColumns.firstWhereOrNull((dataCell) => + dataCell.columnIndex == currentCellManager.columnIndex); return dc; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/spanned_data_row.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/spanned_data_row.dart index b35591d4c..ad43a7672 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/spanned_data_row.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/spanned_data_row.dart @@ -10,7 +10,7 @@ class _SpannedDataRow extends DataRow { _visibleColumns.clear(); _SpannedDataColumn dc; - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); if (rowType == RowType.stackedHeaderRow) { if (dataGridSettings.stackedHeaderRows.isNotEmpty) { final stackedColumns = @@ -33,7 +33,7 @@ class _SpannedDataRow extends DataRow { _SpannedDataColumn _createStackedHeaderColumn( int index, int columnSpan, StackedHeaderCell stackedHeaderCell) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); final gridColumn = dataGridSettings.columns[index]; final rowSpan = _StackedHeaderHelper._getRowSpan( dataGridSettings, rowIndex, index, true, @@ -64,7 +64,7 @@ class _SpannedDataRow extends DataRow { return; } - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); if (dataGridSettings.stackedHeaderRows.isNotEmpty) { final stackedHeaderRow = dataGridSettings.stackedHeaderRows[rowIndex]; final stackedColumns = stackedHeaderRow.cells; @@ -76,26 +76,22 @@ class _SpannedDataRow extends DataRow { column._childColumnIndexes); for (final columns in columnsSequence) { - final acutalColumnIndex = columns.reduce(min); - var dc = _indexer(acutalColumnIndex); + final actualColumnIndex = columns.reduce(min); + var dc = _indexer(actualColumnIndex); if (dc == null) { - var dataCell = _reUseCell( - acutalColumnIndex, acutalColumnIndex + columns.length - 1); - dataCell ??= _visibleColumns.firstWhere( - (col) => - col.columnIndex == -1 && - col._cellType != CellType.indentCell, - orElse: () => null); + DataCellBase? dataCell = _reUseCell( + actualColumnIndex, actualColumnIndex + columns.length - 1); + dataCell ??= _visibleColumns.firstWhereOrNull((col) => + col.columnIndex == -1 && col._cellType != CellType.indentCell); _updateStackedHeaderColumn( - dataCell, acutalColumnIndex, columns.length - 1, column); + dataCell, actualColumnIndex, columns.length - 1, column); dataCell = null; } - dc ??= _visibleColumns.firstWhere( - (col) => col.columnIndex == acutalColumnIndex, - orElse: () => null); + dc ??= _visibleColumns + .firstWhereOrNull((col) => col.columnIndex == actualColumnIndex); if (dc != null) { if (!dc._isVisible) { @@ -103,7 +99,7 @@ class _SpannedDataRow extends DataRow { } } else { dc = _createStackedHeaderColumn( - acutalColumnIndex, columns.toList().length - 1, column); + actualColumnIndex, columns.toList().length - 1, column); _visibleColumns.add(dc); } @@ -120,9 +116,9 @@ class _SpannedDataRow extends DataRow { } } - void _updateStackedHeaderColumn(DataCellBase dc, int index, int columnSpan, + void _updateStackedHeaderColumn(DataCellBase? dc, int index, int columnSpan, StackedHeaderCell stackedHeaderCell) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); if (dc != null) { if (index < 0 || index >= dataGridSettings.container.columnCount) { dc._isVisible = false; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/enums.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/helper/enums.dart index 2307207f9..b8fc3fa4f 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/enums.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/helper/enums.dart @@ -65,21 +65,10 @@ enum ColumnWidthMode { /// No sizing. Default column width or defined width set to column. none, - /// Calculates the width of column based on header content. - /// So that header content is not truncated. - header, - - /// Calculates the width of column based on header and cell contents. - /// So that header and cell content’s are not truncated. - auto, - - /// Calculates the width of column based on cell contents. - /// So that cell content’s are not truncated. - cells, - - /// Applies [ColumnWidthMode.cells] width to all the columns except last - /// column which is visible and the remaining width from total width of - /// [SfDataGrid] is set to last column. + /// Applies [SfDataGrid.defaultColumnWidth] or [GridColumn.width] to all the + /// columns except last column which is visible and the remaining width + /// from total width of [SfDataGrid] is set to last column if `width` of this + /// column is not set. lastColumnFill, /// Divides the total width equally for columns. @@ -166,3 +155,31 @@ enum SortingGestureType { /// Sorting is applied on double tap the header. doubleTap, } + +/// The direction in which a row in [SfDataGrid] is swiped. +enum DataGridRowSwipeDirection { + /// The row is swiped by dragging in the reading direction (e.g., from left to + /// right in left-to-right languages). + startToEnd, + + /// The row is swiped by dragging in the reverse of the reading (e.g., from + /// right to left in left-to-right languages). + endToStart, +} + +/// Decides how to scroll request to the corresponding position. +enum DataGridScrollPosition { + /// Scroll to the start of a [SfDataGrid]. + start, + + /// Scroll to the end of a [SfDataGrid]. + end, + + /// Scroll to make a specified item visible. + /// + /// If the given item is not visible and it is presented at top of the datagrid, that item will be visible at top of the [SfDataGrid]. Also, behaves same for bottom case. + makeVisible, + + /// Scroll to the center of a [SfDataGrid]. + center, +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/grid_index_resolver.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/helper/grid_index_resolver.dart index b0ad3e552..28b0e22d2 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/grid_index_resolver.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/helper/grid_index_resolver.dart @@ -3,29 +3,19 @@ part of datagrid; @protected class _GridIndexResolver { static int getHeaderIndex(_DataGridSettings dataGridSettings) { - if (dataGridSettings == null) { - return 0; - } - final headerIndex = dataGridSettings.headerLineCount - 1; return headerIndex < 0 ? 0 : headerIndex; } static int resolveToGridVisibleColumnIndex( _DataGridSettings dataGridSettings, int columnIndex) { - if (dataGridSettings == null) { - return -1; - } - final indentColumnCount = 0; return columnIndex - indentColumnCount; } static int resolveToRowIndex( _DataGridSettings dataGridSettings, int rowIndex) { - if (dataGridSettings == null || - dataGridSettings.container == null || - rowIndex < 0) { + if (rowIndex < 0) { return -1; } @@ -40,14 +30,12 @@ class _GridIndexResolver { static int resolveStartIndexBasedOnPosition( _DataGridSettings dataGridSettings) { - return dataGridSettings != null ? dataGridSettings.headerLineCount : 0; + return dataGridSettings.headerLineCount; } static int resolveToRecordIndex( _DataGridSettings dataGridSettings, int rowIndex) { - if (dataGridSettings == null || - dataGridSettings.container == null || - rowIndex < 0) { + if (rowIndex < 0) { return -1; } @@ -58,7 +46,7 @@ class _GridIndexResolver { rowIndex = rowIndex - _GridIndexResolver.resolveStartIndexBasedOnPosition(dataGridSettings); if (rowIndex >= 0 && - rowIndex <= dataGridSettings.source._effectiveDataSource.length - 1) { + rowIndex <= dataGridSettings.source._effectiveRows.length - 1) { return rowIndex; } else { return -1; @@ -127,16 +115,15 @@ class _GridIndexResolver { @protected class _StackedHeaderHelper { static List _getChildSequence(_DataGridSettings dataGridSettings, - StackedHeaderCell column, int rowIndex) { + StackedHeaderCell? column, int rowIndex) { final List childSequenceNo = []; - if (column != null && - (column.columnNames != null || column.columnNames.isNotEmpty)) { + if (column != null && column.columnNames.isNotEmpty) { final childColumns = column.columnNames; for (final child in childColumns) { final columns = dataGridSettings.columns; for (int i = 0; i < columns.length; ++i) { - if (columns[i].mappingName == child) { + if (columns[i].columnName == child) { childSequenceNo.add(i); break; } @@ -148,16 +135,15 @@ class _StackedHeaderHelper { static int _getRowSpan(_DataGridSettings dataGridSettings, int rowindex, int columnIndex, bool isStackedHeader, - {String mappingName, StackedHeaderCell stackedHeaderCell}) { + {String? mappingName, StackedHeaderCell? stackedHeaderCell}) { int rowSpan = 0; int startIndex = 0; int endIndex = 0; - if (isStackedHeader) { + if (isStackedHeader && stackedHeaderCell != null) { final List> spannedColumns = _getConsecutiveRanges(stackedHeaderCell._childColumnIndexes); - final List spannedColumn = spannedColumns.singleWhere( - (element) => element.first == columnIndex, - orElse: () => null); + final List? spannedColumn = spannedColumns + .singleWhereOrNull((element) => element.first == columnIndex); if (spannedColumn != null) { startIndex = spannedColumn.reduce(min); endIndex = startIndex + spannedColumn.length - 1; @@ -171,8 +157,8 @@ class _StackedHeaderHelper { while (rowindex >= 0) { final stackedHeaderRow = dataGridSettings.stackedHeaderRows[rowindex]; - for (final stackedColumn in stackedHeaderRow.cells) { - if (stackedColumn != null && isStackedHeader) { + for (final StackedHeaderCell stackedColumn in stackedHeaderRow.cells) { + if (isStackedHeader) { final List> columnsRange = _getConsecutiveRanges(stackedColumn._childColumnIndexes); for (final column in columnsRange) { @@ -181,9 +167,7 @@ class _StackedHeaderHelper { return rowSpan; } } - } else if (stackedColumn != null && - (stackedColumn.columnNames != null || - stackedColumn.columnNames.isNotEmpty)) { + } else if (stackedColumn.columnNames.isNotEmpty) { final children = stackedColumn.columnNames; for (int child = 0; child < children.length; child++) { if (children[child] == mappingName) { diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/sfdatagrid_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/helper/sfdatagrid_helper.dart index e1a33977a..379d78671 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/sfdatagrid_helper.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/helper/sfdatagrid_helper.dart @@ -12,9 +12,10 @@ class _SfDataGridHelper { .getVisibleLines(_dataGridSettings.textDirection == TextDirection.rtl); } - static void scrollVertical( - _DataGridSettings dataGridSettings, double verticalOffset) { - final ScrollController verticalController = + static Future scrollVertical( + _DataGridSettings dataGridSettings, double verticalOffset, + [bool canAnimate = false]) async { + final ScrollController? verticalController = dataGridSettings.verticalController; if (verticalController == null || !verticalController.hasClients) { @@ -28,13 +29,21 @@ class _SfDataGridHelper { verticalOffset = verticalOffset.isNegative ? verticalController.position.minScrollExtent : verticalOffset; - dataGridSettings.verticalController.jumpTo(verticalOffset); + + if (canAnimate) { + await dataGridSettings.verticalController!.animateTo(verticalOffset, + duration: const Duration(milliseconds: 1000), + curve: Curves.fastOutSlowIn); + } else { + dataGridSettings.verticalController!.jumpTo(verticalOffset); + } dataGridSettings.container.updateScrollBars(); } - static void scrollHorizontal( - _DataGridSettings dataGridSettings, double horizontalOffset) { - final ScrollController horizontalController = + static Future scrollHorizontal( + _DataGridSettings dataGridSettings, double horizontalOffset, + [bool canAnimate = false]) async { + final ScrollController? horizontalController = dataGridSettings.horizontalController; if (horizontalController == null || !horizontalController.hasClients) { @@ -48,7 +57,84 @@ class _SfDataGridHelper { horizontalOffset = horizontalOffset.isNegative ? horizontalController.position.minScrollExtent : horizontalOffset; - dataGridSettings.horizontalController.jumpTo(horizontalOffset); + + if (canAnimate) { + await dataGridSettings.horizontalController!.animateTo(horizontalOffset, + duration: const Duration(milliseconds: 1000), + curve: Curves.fastOutSlowIn); + } else { + dataGridSettings.horizontalController!.jumpTo(horizontalOffset); + } dataGridSettings.container.updateScrollBars(); } + + static bool canSwipeRow(_DataGridSettings dataGridSettings, + DataGridRowSwipeDirection swipeDirection, double swipeOffset) { + if (dataGridSettings.container.horizontalOffset == 0) { + if ((dataGridSettings.container.extentWidth > + dataGridSettings.viewWidth) && + swipeDirection == DataGridRowSwipeDirection.endToStart && + swipeOffset <= 0) { + return false; + } else { + return true; + } + } else if (dataGridSettings.container.horizontalOffset == + dataGridSettings.container.extentWidth - dataGridSettings.viewWidth) { + if ((dataGridSettings.container.extentWidth > + dataGridSettings.viewWidth) && + swipeDirection == DataGridRowSwipeDirection.startToEnd && + swipeOffset >= 0) { + return false; + } else { + return true; + } + } else { + return false; + } + } + + static DataGridRowSwipeDirection getSwipeDirection( + _DataGridSettings dataGridSettings, double swipingOffset) { + return swipingOffset >= 0 + ? dataGridSettings.textDirection == TextDirection.ltr + ? DataGridRowSwipeDirection.startToEnd + : DataGridRowSwipeDirection.endToStart + : dataGridSettings.textDirection == TextDirection.ltr + ? DataGridRowSwipeDirection.endToStart + : DataGridRowSwipeDirection.startToEnd; + } + + static DataGridRow getDataGridRow( + _DataGridSettings dataGridSettings, int rowIndex) { + final recordIndex = + _GridIndexResolver.resolveToRecordIndex(dataGridSettings, rowIndex); + return dataGridSettings.source._effectiveRows[recordIndex]; + } + + static DataGridRowAdapter? getDataGridRowAdapter( + _DataGridSettings dataGridSettings, DataGridRow dataGridRow) { + DataGridRowAdapter buildBlankRow(DataGridRow dataGridRow) { + return DataGridRowAdapter( + cells: dataGridSettings.columns + .map((dataCell) => SizedBox.fromSize(size: Size.zero)) + .toList()); + } + + return dataGridSettings.source.buildRow(dataGridRow) ?? + buildBlankRow(dataGridRow); + } + + static bool debugCheckTheLength( + int columnLength, int cellLength, String message) { + assert(() { + if (columnLength != cellLength) { + throw FlutterError.fromParts([ + ErrorSummary('$message: is not true'), + ]); + } + return true; + }()); + return true; + } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/column_sizer.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/column_sizer.dart index 1f5e02ade..ed5ac9237 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/column_sizer.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/column_sizer.dart @@ -1,71 +1,21 @@ part of datagrid; -/// Handles the sizing for all the columns in the [SfDataGrid]. -/// -/// You can override any available methods in this class to calculate the -/// column width based on your requirement and set the instance to the -/// [SfDataGrid.columnSizer]. -/// -/// ``` dart -/// class CustomGridColumnSizer extends ColumnSizer { -/// // Calculate width for column when ColumnWidthMode is auto. -/// @override -/// double calculateAllCellsWidth(GridColumn column, [bool isAuto = false]) { -/// return super.calculateAllCellsWidth(column, isAuto); -/// } -/// -/// // Calculate width for column when ColumnWidthMode is header. -/// @override -/// double calculateColumnHeaderWidth(GridColumn column, [bool setWidth = true]) { -/// return super.calculateColumnHeaderWidth(column, setWidth); -/// } -/// -/// // Calculate width for column when ColumnWidthMode is cells. -/// @override -/// double calculateAllCellsExceptHeaderWidth(GridColumn column, -/// [bool setWidth = true]) { -/// return super.calculateAllCellsExceptHeaderWidth(column, setWidth); -/// } -/// } -/// -/// final CustomGridColumnSizer _customGridColumnSizer = CustomGridColumnSizer(); -/// -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// body: SfDataGrid( -/// source: _employeeDataSource, -/// columnSizer: _customGridColumnSizer, -/// columnWidthMode: ColumnWidthMode.auto, -/// columns: [ -/// GridNumericColumn(mappingName: 'id', headerText: 'ID'), -/// GridTextColumn(mappingName: 'name', headerText: 'Name'), -/// GridTextColumn(mappingName: 'designation', headerText: 'Designation'), -/// GridNumericColumn(mappingName: 'salary', headerText: 'Salary') -/// ], -/// ), -/// ); -/// } -/// ``` -class ColumnSizer { - /// Creates the [ColumnSizer] for [SfDataGrid] widget. - ColumnSizer() { +class _ColumnSizer { + _ColumnSizer() { _isColumnSizerLoadedInitially = false; } - int _textLength = 0; - double _previousColumnWidth = 0.0; - GridColumn _autoFillColumn; - bool _isColumnSizerLoadedInitially; + GridColumn? _autoFillColumn; + bool _isColumnSizerLoadedInitially = false; static const double _sortIconWidth = 20.0; static const double _sortNumberWidth = 18.0; - _DataGridStateDetails _dataGridStateDetails; + late _DataGridStateDetails _dataGridStateDetails; void _initialRefresh(double availableWidth) { final _LineSizeCollection lineSizeCollection = - _dataGridStateDetails().container.columnWidths; + _dataGridStateDetails().container.columnWidths as _LineSizeCollection; lineSizeCollection.suspendUpdates(); _refresh(availableWidth); lineSizeCollection.resumeUpdates(); @@ -73,13 +23,47 @@ class ColumnSizer { void _refresh(double availableWidth) { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final sizerColumns = dataGridSettings.columns - .where((column) => column.columnWidthMode != ColumnWidthMode.none); + final hasAnySizerColumn = dataGridSettings.columns.any((column) => + (column.columnWidthMode != ColumnWidthMode.none) || + (column.width != double.nan) || + !column.visible); - if (dataGridSettings.columnWidthMode != null || sizerColumns.isNotEmpty) { + final paddedEditableLineSizeHostBase = + dataGridSettings.container.columnWidths; + final _LineSizeCollection? lineSizeCollection = + paddedEditableLineSizeHostBase is _LineSizeCollection + ? paddedEditableLineSizeHostBase + : null; + + if (lineSizeCollection == null) { + return; + } + + lineSizeCollection.suspendUpdates(); + _ensureColumnVisibility(); + + if (dataGridSettings.columnWidthMode != ColumnWidthMode.none || + hasAnySizerColumn) { _sizerColumnWidth(availableWidth); } dataGridSettings.container.updateScrollBars(); + lineSizeCollection.resumeUpdates(); + } + + void _ensureColumnVisibility() { + final dataGridSettings = _dataGridStateDetails(); + for (final column in dataGridSettings.columns) { + final index = _GridIndexResolver.resolveToScrollColumnIndex( + dataGridSettings, dataGridSettings.columns.indexOf(column)); + if (column.visible) { + dataGridSettings.container.columnWidths.setHidden(index, index, false); + } else { + dataGridSettings.container.columnWidths.setHidden(index, index, true); + } + } + // Columns will be auto sized only if Columns doesn't have explicit width + // defined. + _sizerColumnWidth(0.0); } void _sizerColumnWidth(double viewPortWidth) { @@ -108,68 +92,19 @@ class ColumnSizer { calculatedColumns.add(column); } - // Set width based on widget column - final widgetColumns = dataGridSettings.columns - .skipWhile((column) => - !column.visible || - !(column.columnWidthMode == ColumnWidthMode.auto || - column.columnWidthMode == ColumnWidthMode.cells)) - .where((column) => column._cellType == 'Widget' && column.width.isNaN); - for (final column in widgetColumns) { - totalColumnSize += - _setColumnWidth(column, dataGridSettings.defaultColumnWidth); - calculatedColumns.add(column); - } - - // Set width based on cells - final cellColumns = dataGridSettings.columns - .skipWhile((column) => !column.visible) - .where((column) => - column.columnWidthMode == ColumnWidthMode.cells && - column.width.isNaN && - !widgetColumns.contains(column)); - for (final column in cellColumns) { - if (column._autoWidth.isNaN) { - final columnWidth = - _getWidthBasedOnColumn(column, ColumnWidthMode.cells); - totalColumnSize += columnWidth; - _setAutoWidth(column, columnWidth); - } else { - totalColumnSize += _setColumnWidth(column, column._autoWidth); - } - calculatedColumns.add(column); - } - - // Set width based on header - final headerColumns = dataGridSettings.columns - .skipWhile((column) => !column.visible) - .where((column) => - column.columnWidthMode == ColumnWidthMode.header && - column.width.isNaN); - for (final column in headerColumns) { - totalColumnSize += _getWidthBasedOnColumn(column, ColumnWidthMode.header); - calculatedColumns.add(column); - } - - // Set width based on auto and lastColumnFill - List autoColumns = dataGridSettings.columns - .where((column) => - column.columnWidthMode == ColumnWidthMode.auto && - !(!column.visible || widgetColumns.contains(column)) && - (column.width).isNaN) - .toList(); final List lastColumnFill = dataGridSettings.columns .skipWhile((column) => calculatedColumns.contains(column)) .where((col) => (col.columnWidthMode == ColumnWidthMode.lastColumnFill) && !_isLastFillColum(col)) .toList(); - autoColumns = (autoColumns + lastColumnFill).toSet().toList(); + + final List autoColumns = lastColumnFill; for (final column in autoColumns) { if ((column._autoWidth).isNaN) { final columnWidth = - _getWidthBasedOnColumn(column, ColumnWidthMode.auto); + dataGridSettings.container.columnWidths.defaultLineSize; totalColumnSize += columnWidth; _setAutoWidth(column, columnWidth); } else { @@ -181,20 +116,18 @@ class ColumnSizer { _autoFillColumn = null; } - GridColumn _getColumnToFill() { + GridColumn? _getColumnToFill() { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final column = dataGridSettings.columns.lastWhere( - (c) => - c.visible && - (c.width).isNaN && - (c.columnWidthMode == ColumnWidthMode.lastColumnFill), - orElse: () => null); + final GridColumn? column = dataGridSettings.columns.lastWhereOrNull((c) => + c.visible && + (c.width).isNaN && + (c.columnWidthMode == ColumnWidthMode.lastColumnFill)); if (column != null) { return column; } else { if (dataGridSettings.columnWidthMode == ColumnWidthMode.lastColumnFill) { - final lastColumn = dataGridSettings.columns - .lastWhere((c) => c.visible && (c.width).isNaN, orElse: () => null); + final GridColumn? lastColumn = dataGridSettings.columns + .lastWhereOrNull((c) => c.visible && (c.width).isNaN); if (lastColumn == null) { return null; } @@ -215,49 +148,21 @@ class ColumnSizer { continue; } - if (column._cellType == 'Widget') { - if (dataGridSettings.columnWidthMode == ColumnWidthMode.auto || - dataGridSettings.columnWidthMode == ColumnWidthMode.cells) { - totalColumnSize += - _setColumnWidth(column, dataGridSettings.defaultColumnWidth); - calculatedColumns.add(column); - continue; - } - } - if (column.columnWidthMode == ColumnWidthMode.fill || _isLastFillColum(column)) { continue; } switch (dataGridSettings.columnWidthMode) { - case ColumnWidthMode.cells: - if (column._autoWidth.isNaN) { - final columnWidth = - _getWidthBasedOnColumn(column, ColumnWidthMode.cells); - totalColumnSize += columnWidth; - _setAutoWidth(column, columnWidth); - } else { - totalColumnSize += _setColumnWidth(column, column._autoWidth); - } - calculatedColumns.add(column); - break; - case ColumnWidthMode.header: - totalColumnSize += - _getWidthBasedOnColumn(column, ColumnWidthMode.header); - calculatedColumns.add(column); - break; - case ColumnWidthMode.auto: case ColumnWidthMode.lastColumnFill: - if ((column._autoWidth).isNaN) { - final columnWidth = - _getWidthBasedOnColumn(column, ColumnWidthMode.auto); - totalColumnSize += columnWidth; - _setAutoWidth(column, columnWidth); + if ((column.width).isNaN) { + totalColumnSize += _setColumnWidth(column, + dataGridSettings.container.columnWidths.defaultLineSize); + calculatedColumns.add(column); } else { - totalColumnSize += _setColumnWidth(column, column._autoWidth); + totalColumnSize += _setColumnWidth(column, column.width); + calculatedColumns.add(column); } - calculatedColumns.add(column); break; case ColumnWidthMode.none: if (column.visible) { @@ -300,7 +205,7 @@ class ColumnSizer { var totalRemainingFillValue = remainingColumnWidth; double removedWidth = 0; - GridColumn fillColumn; + GridColumn? fillColumn; bool isRemoved; while (columns.isNotEmpty) { isRemoved = false; @@ -342,7 +247,7 @@ class ColumnSizer { if (fillColumn != null) { double columnWidth = 0; if ((fillColumn._autoWidth).isNaN) { - columnWidth = _getWidthBasedOnColumn(fillColumn, ColumnWidthMode.auto); + columnWidth = 0.0; _setAutoWidth(fillColumn, columnWidth); } else { columnWidth = fillColumn._autoWidth; @@ -360,14 +265,8 @@ class ColumnSizer { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); for (final column in remainingColumns) { if (_isLastFillColum(column)) { - if (column.columnWidthMode == ColumnWidthMode.lastColumnFill || - (dataGridSettings.columnWidthMode == - ColumnWidthMode.lastColumnFill)) { - _getWidthBasedOnColumn(column, ColumnWidthMode.auto); - } else { - _setNoneWidth( - column, dataGridSettings.container.columnWidths.defaultLineSize); - } + _setNoneWidth( + column, dataGridSettings.container.columnWidths.defaultLineSize); } else if (!_isFillColumn(column)) { _setNoneWidth( column, dataGridSettings.container.columnWidths.defaultLineSize); @@ -392,7 +291,7 @@ class ColumnSizer { return false; } - void _setAutoWidth(GridColumn column, double width) { + void _setAutoWidth(GridColumn? column, double width) { if (column != null) { column._autoWidth = width; } @@ -405,56 +304,6 @@ class ColumnSizer { } } - double _getWidthBasedOnColumn( - GridColumn column, ColumnWidthMode columnWidthMode) { - double width; - switch (columnWidthMode) { - case ColumnWidthMode.cells: - width = calculateAllCellsExceptHeaderWidth(column); - return _setColumnWidth(column, width); - case ColumnWidthMode.header: - width = calculateColumnHeaderWidth(column); - return _setColumnWidth(column, width); - case ColumnWidthMode.auto: - width = calculateAllCellsWidth(column, isAuto: true); - return _setColumnWidth(column, width); - default: - break; - } - return 0.0; - } - - /// Calculates the width for the column to fit the content including the - /// header cell when [SfDataGrid.columnWidthMode] is [ColumnWidthMode.auto]. - double calculateAllCellsWidth(GridColumn column, {bool isAuto = false}) { - final double headerWidth = - calculateColumnHeaderWidth(column, setWidth: false); - final double cellWidth = - calculateAllCellsExceptHeaderWidth(column, setWidth: false); - double width; - if (cellWidth > headerWidth) { - width = cellWidth; - } else { - width = headerWidth; - } - - if (isAuto) { - return width; - } - return _getColumnWidth(column, width); - } - - /// Calculates the width for the column based on the header text when - /// [SfDataGrid.columnWidthMode] is [ColumnWidthMode.header]. - double calculateColumnHeaderWidth(GridColumn column, {bool setWidth = true}) { - double width = _getHeaderCellWidth(column).roundToDouble(); - if (setWidth) { - column._actualWidth = width; - } - width += _getSortIconWidth(column); - return width; - } - double _getSortIconWidth(GridColumn column) { final dataGridSettings = _dataGridStateDetails(); double width = 0.0; @@ -468,18 +317,6 @@ class ColumnSizer { return width; } - /// Calculates the width for the column to fit the content in cells except - /// header cell when [SfDataGrid.columnWidthMode] is [ColumnWidthMode.cells]. - double calculateAllCellsExceptHeaderWidth(GridColumn column, - {bool setWidth = true}) { - if (setWidth) { - column._actualWidth = _getCellWidth(column); - return column._actualWidth; - } else { - return _getCellWidth(column); - } - } - double _setColumnWidth(GridColumn column, double columnWidth) { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); final columnIndex = dataGridSettings.columns.indexOf(column); @@ -531,412 +368,4 @@ class ColumnSizer { } return columnWidth; } - - double _getCellWidth(GridColumn column) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - double resultWidth = 0; - final recordCount = dataGridSettings.source._effectiveDataSource.length; - if (recordCount == 0) { - return double.nan; - } - - _textLength = 0; - _previousColumnWidth = 0; - int stringLength = 0; - final isInDefaultMode = dataGridSettings.columnWidthCalculationMode == - ColumnWidthCalculationMode.textSize; - Object index; - int firstVisibleIndex, lastVisibleIndex; - - if (dataGridSettings.columnWidthCalculationRange == - ColumnWidthCalculationRange.visibleRows) { - final visibleLines = - dataGridSettings.container.scrollRows.getVisibleLines(); - firstVisibleIndex = - visibleLines.firstBodyVisibleIndex <= visibleLines.length - 1 - ? visibleLines[visibleLines.firstBodyVisibleIndex].lineIndex - : 0; - lastVisibleIndex = - dataGridSettings.container.scrollRows.lastBodyVisibleLineIndex; - } else { - firstVisibleIndex = 0; - lastVisibleIndex = - dataGridSettings.source._effectiveDataSource.length - 1; - } - - for (int rowIndex = firstVisibleIndex; - rowIndex <= lastVisibleIndex; - rowIndex++) { - if (isInDefaultMode) { - final textWidth = getCellWidth(column, rowIndex); - if (textWidth.toString().isEmpty) { - continue; - } - - if (resultWidth < textWidth) { - resultWidth = textWidth; - } - } else { - final currentRowIndex = (dataGridSettings.columnWidthCalculationRange == - ColumnWidthCalculationRange.visibleRows) - ? _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, rowIndex) - : rowIndex; - final String text = _getDisplayText(currentRowIndex, column); - if (text.length >= stringLength) { - stringLength = text.length; - index = rowIndex; - } - } - } - - if (!isInDefaultMode) { - final textWidth = getCellWidth(column, index); - resultWidth = textWidth; - } - - _textLength = 0; - _previousColumnWidth = 0; - return resultWidth; - } - - /// Gets the width of the cell to calculate the width of the specified column. - double getCellWidth(GridColumn column, int rowIndex) { - double textWidth = 0.0; - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final currentRowIndex = (dataGridSettings.columnWidthCalculationRange == - ColumnWidthCalculationRange.visibleRows) - ? _GridIndexResolver.resolveToRecordIndex(dataGridSettings, rowIndex) - : rowIndex; - final formattedText = _getDisplayText(currentRowIndex, column); - if (formattedText != null && formattedText.length >= _textLength || - _previousColumnWidth >= column._actualWidth) { - textWidth = _measureTextWidth(formattedText, column, rowIndex); - _textLength = formattedText.length; - _previousColumnWidth = column._actualWidth; - } - - return textWidth.roundToDouble(); - } - - /// Gets the height of the cell to calculate the width of the specified - /// column. - double getCellHeight(GridColumn column, int rowIndex) { - double textHeight = 0.0; - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (rowIndex == _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - final text = column.headerText ?? column.mappingName; - textHeight = _measureTextHeight(text, column, rowIndex); - return textHeight.roundToDouble(); - } else { - final currentRowIndex = - _GridIndexResolver.resolveToRecordIndex(dataGridSettings, rowIndex); - final formattedText = _getDisplayText(currentRowIndex, column); - if (formattedText != null && formattedText.length >= _textLength || - _previousColumnWidth >= column._actualWidth) { - textHeight = _measureTextHeight(formattedText, column, rowIndex); - _textLength = formattedText.length; - _previousColumnWidth = column._actualWidth; - } - - return textHeight.roundToDouble(); - } - } - - /// Gets the row height to fit the row based on the content. - /// - /// The following code snippet shows how to use [getAutoRowHeight] method, - /// - /// ``` dart - /// final ColumnSizer _columnSizer = ColumnSizer(); - /// - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfDataGrid( - /// source: _employeeDatasource, - /// columnSizer: _columnSizer, - /// onQueryRowHeight: (rowIndex) { - /// double height = _columnSizer.getAutoRowHeight(rowIndex); - /// return height; - /// }, - /// columns: [ - /// GridTextColumn(mappingName: 'id') - /// ..softWrap = true - /// ..overflow = TextOverflow.clip - /// ..headerText = 'ID', - /// GridTextColumn(mappingName: 'contactName') - /// ..softWrap = true - /// ..overflow = TextOverflow.clip - /// ..headerText = 'Contact Name', - /// GridTextColumn(mappingName: 'companyName') - /// ..softWrap = true - /// ..overflow = TextOverflow.clip - /// ..headerText = 'Company Name', - /// GridTextColumn(mappingName: 'city') - /// ..softWrap = true - /// ..overflow = TextOverflow.clip - /// ..headerText = 'City', - /// GridTextColumn(mappingName: 'address') - /// ..softWrap = true - /// ..overflow = TextOverflow.clip - /// ..headerText = 'Address', - /// GridTextColumn(mappingName: 'designation') - /// ..softWrap = true - /// ..overflow = TextOverflow.clip - /// ..headerText = 'Designation', - /// GridTextColumn(mappingName: 'country') - /// ..softWrap = true - /// ..overflow = TextOverflow.clip - /// ..headerText = 'Country', - /// ])); - /// } - /// ``` - double getAutoRowHeight(int rowIndex, - {bool canIncludeHiddenColumns = false, List excludedColumns}) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - double resultHeight = -1; - GridColumn gridColumn; - int stringLength = 0; - final isInDefaultMode = dataGridSettings.columnWidthCalculationMode == - ColumnWidthCalculationMode.textSize; - if (dataGridSettings.stackedHeaderRows.isNotEmpty && - rowIndex <= dataGridSettings.stackedHeaderRows.length - 1) { - return dataGridSettings.headerRowHeight; - } - - for (int columnIndex = 0; - columnIndex < dataGridSettings.columns.length; - columnIndex++) { - final column = dataGridSettings.columns[columnIndex]; - if (!column.visible && !canIncludeHiddenColumns) { - continue; - } - - if (excludedColumns != null) { - if (excludedColumns.contains(column.mappingName)) { - continue; - } - } - - final recordCount = dataGridSettings.source._effectiveDataSource.length; - if (recordCount == 0) { - return double.nan; - } - - if (isInDefaultMode) { - final textHeight = getCellHeight(column, rowIndex); - if (textHeight.toString().isEmpty) { - continue; - } - - if (resultHeight < textHeight) { - resultHeight = textHeight; - } - } else { - final text = _getDisplayText(rowIndex, column); - if (text != null && text.length >= stringLength) { - stringLength = text.length; - gridColumn = column; - } - } - } - - if (!isInDefaultMode) { - final textHeight = getCellHeight(gridColumn, rowIndex); - resultHeight = textHeight; - } - _textLength = 0; - _previousColumnWidth = 0; - return resultHeight; - } - - double _getHeaderCellWidth(GridColumn column) { - final rowCount = _dataGridStateDetails().headerLineCount; - double resultWidth = 0; - for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) { - final text = column.headerText ?? column.mappingName; - if (text.isEmpty || text == null) { - return 0; - } - - final textWidth = _measureTextWidth(text, column, rowIndex); - final width = textWidth; - if (resultWidth < width) { - resultWidth = width; - } - } - return resultWidth; - } - - String _getDisplayText(int rowIndex, GridColumn column) { - final text = _dataGridStateDetails() - .source - .getCellValue(rowIndex, column.mappingName); - final String formattedText = - text != null ? column.getFormattedValue(text) : null; - if (formattedText != null) { - return formattedText; - } - return ''; - } - - TextStyle _getCellTextStyle(int rowIndex, GridColumn column) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - TextStyle cellTextStyle; - if (rowIndex == _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - cellTextStyle = column.headerStyle?.textStyle ?? - dataGridSettings.dataGridThemeData.headerStyle?.textStyle; - } else { - cellTextStyle = column.cellStyle?.textStyle ?? - dataGridSettings.dataGridThemeData.cellStyle?.textStyle; - } - return cellTextStyle; - } - - EdgeInsetsGeometry _getPadding(int rowIndex, GridColumn column) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - - if (rowIndex == _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - if (column.headerPadding != null) { - return column.headerPadding; - } else { - final visualDensityPadding = - dataGridSettings.visualDensity.vertical * 2; - return EdgeInsets.all(16) + - EdgeInsets.fromLTRB( - 0.0, visualDensityPadding, 0.0, visualDensityPadding); - } - } else { - if (column.padding != null) { - return column.padding; - } else { - final visualDensityPadding = - dataGridSettings.visualDensity.vertical * 2; - return EdgeInsets.all(16) + - EdgeInsets.fromLTRB( - 0.0, visualDensityPadding, 0.0, visualDensityPadding); - } - } - } - - double _measureTextWidth( - String displayText, GridColumn column, int rowIndex) { - final cellTextStyle = _getCellTextStyle(rowIndex, column); - return _calculateTextSize( - column, displayText, cellTextStyle, double.infinity, rowIndex) - .width; - } - - double _measureTextHeight( - String displayText, GridColumn column, int rowIndex) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final cellTextStyle = _getCellTextStyle(rowIndex, column); - final columnIndex = dataGridSettings.columns.indexOf(column); - final int scrollColumnIndex = _GridIndexResolver.resolveToScrollColumnIndex( - dataGridSettings, columnIndex); - final double columnWidth = !(column.visible) || column.width == 0.0 - ? dataGridSettings.defaultColumnWidth - : dataGridSettings.container.columnWidths[scrollColumnIndex]; - final gridLinesVisibility = dataGridSettings.gridLinesVisibility; - final Size gridLineStrokeWidthSize = - _getGridLineStrokeWidthSize(gridLinesVisibility, rowIndex, columnIndex); - - final padding = _getPadding(rowIndex, column); - // Removing padding and stroke width values also to the column hight calculation - final double actualColumnWidth = columnWidth - - _getSortIconWidth(column) - - (padding.horizontal + gridLineStrokeWidthSize.width); - return _calculateTextSize( - column, displayText, cellTextStyle, actualColumnWidth, rowIndex) - .height; - } - - Size _getGridLineStrokeWidthSize( - GridLinesVisibility gridLinesVisibility, int rowIndex, int columnIndex) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - Size gridLineStrokeWidthSize = Size.zero; - double gridLineStrokeWidth = - dataGridSettings.dataGridThemeData?.gridLineStrokeWidth; - if (gridLinesVisibility == GridLinesVisibility.both) { - if (columnIndex == 0 && rowIndex == 0) { - // Adding border line stroke also to the column width calculation - gridLineStrokeWidth *= 2; - gridLineStrokeWidthSize = Size( - gridLineStrokeWidthSize.width + gridLineStrokeWidth, - gridLineStrokeWidthSize.height + gridLineStrokeWidth); - } - if (rowIndex == 0 && columnIndex > 0) { - gridLineStrokeWidthSize = Size( - gridLineStrokeWidthSize.width + gridLineStrokeWidth, - gridLineStrokeWidthSize.height + (2 * gridLineStrokeWidth)); - } - if (columnIndex == 0 && rowIndex > 0) { - gridLineStrokeWidthSize = Size( - gridLineStrokeWidthSize.width + (2 * gridLineStrokeWidth), - gridLineStrokeWidthSize.height + gridLineStrokeWidth); - } - if (columnIndex > 0 && rowIndex > 0) { - gridLineStrokeWidthSize = Size( - gridLineStrokeWidthSize.width + gridLineStrokeWidth, - gridLineStrokeWidthSize.height + gridLineStrokeWidth); - } - } else if (gridLinesVisibility == GridLinesVisibility.vertical) { - if (columnIndex == 0) { - // Adding border line stroke also to the column width calculation - gridLineStrokeWidth *= 2; - } - - gridLineStrokeWidthSize = Size( - gridLineStrokeWidthSize.width + gridLineStrokeWidth, - gridLineStrokeWidthSize.height); - } else if (gridLinesVisibility == GridLinesVisibility.horizontal) { - if (rowIndex == 0) { - // Adding border line stroke also to the column width calculation - gridLineStrokeWidth *= 2; - } - - gridLineStrokeWidthSize = Size(gridLineStrokeWidthSize.width, - gridLineStrokeWidthSize.height + gridLineStrokeWidth); - } - return gridLineStrokeWidthSize; - } - - Size _calculateTextSize(GridColumn column, String text, TextStyle style, - double width, int rowIndex) { - Size textSize = Size.zero; - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final columnIndex = dataGridSettings.columns.indexOf(column); - final gridLinesVisibility = dataGridSettings.gridLinesVisibility; - final gridLineStrokeWidthSize = - _getGridLineStrokeWidthSize(gridLinesVisibility, rowIndex, columnIndex); - final TextPainter textPainter = TextPainter( - text: TextSpan( - text: text, - style: style, - ), - maxLines: column.maxLines, - textScaleFactor: dataGridSettings.textScaleFactor, - textDirection: TextDirection.ltr) - ..layout(maxWidth: width); - textSize = textPainter.size; - final padding = _getPadding(rowIndex, column); - if (padding != null) { - textSize = Size(textSize.width + padding.horizontal, - textSize.height + padding.vertical); - } - return Size(textSize.width + gridLineStrokeWidthSize.width, - textSize.height + gridLineStrokeWidthSize.height); - } - - @override - bool operator ==(Object other) => - identical(this, other) || - other is ColumnSizer && runtimeType == other.runtimeType; - - @override - int get hashCode { - final List _hashList = [this]; - return hashList(_hashList); - } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/grid_column.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/grid_column.dart index 906626ee6..934861ba6 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/grid_column.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/grid_column.dart @@ -4,41 +4,31 @@ part of datagrid; class GridColumn { /// Creates the [GridColumn] for [SfDataGrid] widget. GridColumn( - {@required this.mappingName, - ColumnWidthMode columnWidthMode, - Alignment textAlignment, - Alignment headerTextAlignment, - bool softWrap, - TextOverflow headerTextOverflow, - bool headerTextSoftWrap, - bool visible, - bool allowSorting, - double minimumWidth, - double maximumWidth, - double width, - this.maxLines, - this.overflow, - this.headerText, - this.headerStyle, - this.cellStyle, - this.padding, - this.headerPadding}) - : columnWidthMode = columnWidthMode ?? ColumnWidthMode.none, - textAlignment = textAlignment ?? Alignment.centerLeft, - headerTextAlignment = headerTextAlignment ?? Alignment.center, - softWrap = softWrap ?? false, - headerTextOverflow = headerTextOverflow ?? TextOverflow.ellipsis, - headerTextSoftWrap = headerTextSoftWrap ?? false, - visible = visible ?? true, - allowSorting = allowSorting ?? true, - minimumWidth = minimumWidth ?? double.nan, - maximumWidth = maximumWidth ?? double.nan, - width = width ?? double.nan { + {required this.columnName, + required this.label, + this.columnWidthMode = ColumnWidthMode.none, + this.visible = true, + this.allowSorting = true, + this.minimumWidth = double.nan, + this.maximumWidth = double.nan, + this.width = double.nan}) { _actualWidth = double.nan; _autoWidth = double.nan; } - double _autoWidth; + late double _autoWidth; + + /// The label of column header. + /// + /// Typically, this will be [Text] widget. You can also set [Icon] + /// (Typically using size 18), or a [Row] with an icon and [Text]. + /// + /// If you want to take the entire space for widget, + /// e.g. when you want to use [Center], you can wrap it with an [Expanded]. + /// + /// The widget will be loaded in text area alone. When sorting is applied, + /// the default sort icon will be loaded along with the widget. + final Widget label; /// How the column widths are determined. /// @@ -49,54 +39,20 @@ class GridColumn { /// Also refer [ColumnWidthMode] final ColumnWidthMode columnWidthMode; - /// The name to map the data member in the underlying data object of - /// datasource. + /// The name of a column.The name should be unique. /// - /// Defaults to null - final String mappingName; + /// This must not be empty or null. + final String columnName; /// The cell type of the column which denotes renderer associated with column. - String get cellType => _cellType; - String _cellType; - - /// How the text in cells except header cell is aligned. - /// - /// Defaults to null - final Alignment textAlignment; - - /// How the text in header cell is aligned. - /// - /// Defaults to null - final Alignment headerTextAlignment; - - /// An optional maximum number of lines for the text to span, wrapping - /// if necessary in cells except header cell. - /// - /// Defaults to null - final int maxLines; - - /// How visual overflow should be handled in cells except header cell. - /// - /// Defaults to null - /// - /// Also refer [TextOverflow] - final TextOverflow overflow; - - /// Whether the text should break at soft line breaks. - /// - /// Defaults to false - final bool softWrap; + String? get cellType => _cellType; + String? _cellType; /// The actual display width of the column when auto fitted based on /// [SfDataGrid.columnWidthMode] or [columnWidthMode]. /// /// Defaults to [double.nan] - double _actualWidth; - - /// The text which displays in header cell. - /// - /// Defaults to null. - final String headerText; + late double _actualWidth; /// The minimum width of the column. /// @@ -114,18 +70,6 @@ class GridColumn { /// Defaults to [double.nan] final double maximumWidth; - /// Decides how visual overflow of header text should be handled. - /// - /// Defaults to Ellipsis - /// - /// Also refer [TextOverflow] - final TextOverflow headerTextOverflow; - - /// Decides Whether the header text should break at soft line breaks. - /// - /// Defaults to false - final bool headerTextSoftWrap; - /// The width of the column. /// /// If value is lesser than [minimumWidth], then [minimumWidth] @@ -136,74 +80,6 @@ class GridColumn { /// Defaults to [double.nan] final double width; - /// The amount of space between the contents of the cell and the cell's - /// border. - /// - /// This is applicable for grid cells alone. - /// - /// Defaults to 16 - final EdgeInsetsGeometry padding; - - /// The amount of space between the contents of the header cell and the - /// header cell's border. - /// - /// Defaults to 16 - final EdgeInsetsGeometry headerPadding; - - /// The style of the header cell in the column. - /// - /// Defaults to null - /// - /// ``` dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfDataGrid( - /// source: _employeeDataSource, - /// columns: [ - /// GridNumericColumn(mappingName: 'id', headerText: 'ID', - /// headerStyle : DataGridHeaderCellStyle( - /// textStyle: TextStyle( - /// color: Colors.red, - /// fontWeight: FontWeight.bold))), - /// GridTextColumn(mappingName: 'name', headerText: 'Name'), - /// GridTextColumn(mappingName: 'designation', - /// headerText: 'Designation'), - /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary') - /// ], - /// ), - /// ); - /// } - /// - /// ``` - final DataGridHeaderCellStyle headerStyle; - - /// The style of the cells in the column except header cell. - /// - /// Defaults to null - /// - /// ``` dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfDataGrid( - /// source: _employeeDataSource, - /// columns: [ - /// GridNumericColumn(mappingName: 'id', headerText: 'ID', - /// cellStyle : DataGridCellStyle( - /// textStyle: TextStyle(color: Colors.red))), - /// GridTextColumn(mappingName: 'name', headerText: 'Name'), - /// GridTextColumn(mappingName: 'designation', - /// headerText: 'Designation'), - /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary') - /// ], - /// ), - /// ); - /// } - /// - /// ``` - final DataGridCellStyle cellStyle; - /// Whether column should be hidden. /// /// Defaults to false. @@ -226,12 +102,6 @@ class GridColumn { /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. final bool allowSorting; - /// Gets the formatted value based on the given format. - /// - /// This is applicable for column such as [GridNumericColumn] - /// and [GridDateTimeColumn]. - String getFormattedValue(Object cellValue) => cellValue?.toString(); - /// Sets the cell type which indicates the renderer for the column. /// /// Call this method in constructor of the column when you write the custom @@ -248,322 +118,37 @@ class GridColumn { /// displays [Text] for all the cells. /// /// ``` dart -/// @override -/// Widget build(BuildContext context) { -/// return SfDataGrid( -/// source: employeeDataSource, -/// columns: [ -/// GridTextColumn(mappingName: 'name', headerText : 'Name'), -/// GridTextColumn(mappingName: 'designation', headerText : 'Designation'), -/// ], -/// ); -/// } +/// @override +/// Widget build(BuildContext context) { +/// return SfDataGrid( +/// source: employeeDataSource, +/// columns: [ +/// GridTextColumn(columnName: 'name', label: Text('Name')), +/// GridTextColumn(columnName: 'designation', label: Text('Designation')), +/// ], +/// ); +/// } /// ``` class GridTextColumn extends GridColumn { - /// Creates a String column using [mappingName] and [headerText]. - GridTextColumn( - {@required String mappingName, - ColumnWidthMode columnWidthMode, - Alignment textAlignment, - Alignment headerTextAlignment, - bool softWrap, - TextOverflow headerTextOverflow, - EdgeInsetsGeometry padding, - EdgeInsetsGeometry headerPadding, - bool headerTextSoftWrap, - bool visible, - bool allowSorting, - double minimumWidth, - double maximumWidth, - double width, - int maxLines, - TextOverflow overflow, - String headerText, - DataGridHeaderCellStyle headerStyle, - DataGridCellStyle cellStyle}) - : super( - mappingName: mappingName, - columnWidthMode: columnWidthMode, - textAlignment: textAlignment, - headerTextAlignment: headerTextAlignment ?? Alignment.centerLeft, - softWrap: softWrap, - headerTextOverflow: headerTextOverflow, - headerTextSoftWrap: headerTextSoftWrap, - visible: visible, - allowSorting: allowSorting, - minimumWidth: minimumWidth, - maximumWidth: maximumWidth, - width: width, - maxLines: maxLines, - overflow: overflow ?? TextOverflow.ellipsis, - headerText: headerText, - headerStyle: headerStyle, - cellStyle: cellStyle, - padding: padding, - headerPadding: headerPadding) { - _cellType = 'TextField'; - } -} - -/// A column used to display the numeric values in its cells (int, double). -/// -/// [GridNumericColumn] supports [numberFormat] for formatting numeric values. -/// -/// ``` dart -/// @override -/// Widget build(BuildContext context) { -/// return SfDataGrid( -/// source: employeeDataSource(), -/// columns: [ -/// GridNumericColumn(mappingName: 'id', headerText : 'ID'), -/// GridNumericColumn(mappingName: 'salary', headerText : 'Salary'), -/// ], -/// ); -/// } -/// ``` -class GridNumericColumn extends GridColumn { - /// Creates a numeric column using [mappingName] and [headerText]. - GridNumericColumn({ - @required String mappingName, - this.numberFormat, - ColumnWidthMode columnWidthMode, - Alignment textAlignment, - Alignment headerTextAlignment, - bool softWrap, - TextOverflow headerTextOverflow, - EdgeInsetsGeometry padding, - EdgeInsetsGeometry headerPadding, - bool headerTextSoftWrap, - bool visible, - bool allowSorting, - double minimumWidth, - double maximumWidth, - double width, - int maxLines, - TextOverflow overflow, - String headerText, - DataGridHeaderCellStyle headerStyle, - DataGridCellStyle cellStyle, + /// Creates a String column using [columnName] and [label]. + GridTextColumn({ + required String columnName, + required Widget label, + ColumnWidthMode columnWidthMode = ColumnWidthMode.none, + bool visible = true, + bool allowSorting = true, + double minimumWidth = double.nan, + double maximumWidth = double.nan, + double width = double.nan, }) : super( - mappingName: mappingName, + columnName: columnName, + label: label, columnWidthMode: columnWidthMode, - textAlignment: textAlignment ?? Alignment.centerRight, - headerTextAlignment: headerTextAlignment ?? Alignment.centerRight, - softWrap: softWrap, - headerTextOverflow: headerTextOverflow, - headerTextSoftWrap: headerTextSoftWrap, visible: visible, allowSorting: allowSorting, minimumWidth: minimumWidth, maximumWidth: maximumWidth, - width: width, - maxLines: maxLines, - overflow: overflow, - headerText: headerText, - headerStyle: headerStyle, - cellStyle: cellStyle, - padding: padding, - headerPadding: headerPadding) { - _cellType = 'Numeric'; - } - - /// [intl.NumberFormat] to format a number in a locale-specific way. - /// - /// Defaults to null. - /// - /// ``` dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfDataGrid( - /// source: _employeeDataSource, - /// columns: [ - /// GridNumericColumn(mappingName: 'id', headerText: 'ID'), - /// GridTextColumn(mappingName: 'name', headerText: 'Name'), - /// GridTextColumn(mappingName: 'designation', - /// headerText: 'Designation'), - /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary', - /// numberFormat : - /// NumberFormat.currency(locale: 'en_US', symbol: '\$')) - /// ], - /// ), - /// ); - /// } - /// ``` - final intl.NumberFormat numberFormat; - - @override - String getFormattedValue(Object cellValue) { - if (numberFormat != null) { - return numberFormat.format(cellValue); - } - return cellValue.toString(); - } -} - -/// A column that shows any custom widget inside its cells. -/// -/// [GridWidgetColumn] supports loading [Image], [Switch] and so on in cells -/// in column. -/// -/// [SfDataGrid.cellBuilder] allows you to set the widget in a column as needed. -/// ``` dart -/// @override -/// Widget build(BuildContext context) { -/// return SfDataGrid( -/// source: employeeDataSource(), -/// cellBuilder: (BuildContext context, GridColumn column, int rowIndex){ -/// return employees[rowIndex].image; -/// }, -/// columns: [ -/// GridWidgetColumn(mappingName: image, headerText : 'Image'), -/// ], -/// ); -/// } -/// ``` -class GridWidgetColumn extends GridColumn { - /// Creates a widget column using [mappingName] and [headerText]. - GridWidgetColumn( - {@required String mappingName, - ColumnWidthMode columnWidthMode, - Alignment textAlignment, - Alignment headerTextAlignment, - bool softWrap, - TextOverflow headerTextOverflow, - EdgeInsetsGeometry padding, - EdgeInsetsGeometry headerPadding, - bool headerTextSoftWrap, - bool visible, - bool allowSorting, - double minimumWidth, - double maximumWidth, - double width, - int maxLines, - TextOverflow overflow, - String headerText, - DataGridHeaderCellStyle headerStyle, - DataGridCellStyle cellStyle}) - : super( - mappingName: mappingName, - columnWidthMode: columnWidthMode, - textAlignment: textAlignment, - headerTextAlignment: headerTextAlignment, - softWrap: softWrap, - headerTextOverflow: headerTextOverflow, - headerTextSoftWrap: headerTextSoftWrap, - visible: visible, - allowSorting: allowSorting, - minimumWidth: minimumWidth, - maximumWidth: maximumWidth, - width: width, - maxLines: maxLines, - overflow: overflow, - headerText: headerText, - headerStyle: headerStyle, - cellStyle: cellStyle, - padding: padding, - headerPadding: headerPadding) { - _cellType = 'Widget'; - } -} - -/// A column which displays the values of DateTime in its cells. -/// -/// [GridDateTimeColumn] supports [dateFormat] for formatting DateTime values. -/// -/// ``` dart -/// @override -/// Widget build(BuildContext context) { -/// return SfDataGrid( -/// source: employeeDataSource(), -/// columns: [ -/// GridNumericColumn(mappingName: 'id', headerText : 'ID'), -/// GridDateTimeColumn(mappingName: 'dateofjoining', -/// headerText : 'Date of Joining'), -/// ], -/// ); -/// } -/// ``` -class GridDateTimeColumn extends GridColumn { - /// Creates a datetime column using [mappingName] and [headerText]. - GridDateTimeColumn( - {@required String mappingName, - this.dateFormat, - ColumnWidthMode columnWidthMode, - Alignment textAlignment, - Alignment headerTextAlignment, - bool softWrap, - TextOverflow headerTextOverflow, - EdgeInsetsGeometry padding, - EdgeInsetsGeometry headerPadding, - bool headerTextSoftWrap, - bool visible, - bool allowSorting, - double minimumWidth, - double maximumWidth, - double width, - int maxLines, - TextOverflow overflow, - String headerText, - DataGridHeaderCellStyle headerStyle, - DataGridCellStyle cellStyle}) - : super( - mappingName: mappingName, - columnWidthMode: columnWidthMode, - textAlignment: textAlignment ?? Alignment.centerRight, - headerTextAlignment: headerTextAlignment ?? Alignment.centerRight, - softWrap: softWrap, - headerTextOverflow: headerTextOverflow, - headerTextSoftWrap: headerTextSoftWrap, - visible: visible, - allowSorting: allowSorting, - minimumWidth: minimumWidth, - maximumWidth: maximumWidth, - width: width, - maxLines: maxLines, - overflow: overflow, - headerText: headerText, - headerStyle: headerStyle, - cellStyle: cellStyle, - padding: padding, - headerPadding: headerPadding) { - _cellType = 'DateTime'; - } - - /// [intl.DateFormat] to format the dates in a locale-sensitive manner. - /// - /// Defaults to null. - /// - /// ``` dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfDataGrid( - /// source: _employeeDataSource, - /// columns: [ - /// GridNumericColumn(mappingName: 'id', headerText: 'ID'), - /// GridTextColumn(mappingName: 'name', headerText: 'Name'), - /// GridDateTimeColumn( - /// mappingName: 'dateOfJoining', headerText: 'Date of Joining', - /// dateFormat : DateFormat('dd/MM/yyyy')), - /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), - /// ], - /// ), - /// ); - /// } - /// ``` - final intl.DateFormat dateFormat; - - final intl.DateFormat _defaultDateFormat = intl.DateFormat('dd-MM-yyyy'); - - /// Gets the formatted value based on the given format. - /// - /// This method is overridden to format the given numeric value to specified - /// [dateFormat]. - @override - String getFormattedValue(Object cellValue) { - if (dateFormat != null) { - return dateFormat.format(cellValue); - } - return _defaultDateFormat.format(cellValue); + width: width) { + _cellType = 'TextField'; } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/stacked_header.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/stacked_header.dart index baa22ab0e..c4b94d81c 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/stacked_header.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/stacked_header.dart @@ -1,7 +1,7 @@ part of datagrid; /// Row configuration for stacked header in [SfDataGrid]. The columns for this -/// stacked header row are provided in the [stackedHeaderCells] property of the +/// stacked header row are provided in the [StackedHeaderCell] property of the /// [StackedHeaderRow] object. /// /// See also: @@ -10,16 +10,7 @@ part of datagrid; /// header row. class StackedHeaderRow { /// Creates the [StackedHeaderRow] for [SfDataGrid] widget. - StackedHeaderRow({List cells}) { - this.cells = cells ?? []; - } - - /// The name of the stacked header row. - /// - /// This is used to identify the header rows uniquely in - /// [stackedHeaderCellBuilder]. Each stacked header row must be named - /// uniquely. - String name; + StackedHeaderRow({required this.cells}); /// The collection of [StackedHeaderCell] in stacked header row. List cells; @@ -32,9 +23,9 @@ class StackedHeaderRow { /// [StackedHeaderRow] – which provides configuration for stacked header row. class StackedHeaderCell { /// Creates the [StackedHeaderCell] for [StackedHeaderRow]. - StackedHeaderCell({@required this.columnNames, @required this.child}); + StackedHeaderCell({required this.columnNames, required this.child}); - /// The collection of string which is the [GridColumn.mappingName] of the + /// The collection of string which is the [GridColumn.columnName] of the /// columns defined in the [SfDataGrid]. /// /// The columns are spanned as a stacked header based on this collection. If @@ -49,5 +40,5 @@ class StackedHeaderCell { /// Typically, a [Text] widget. final Widget child; - List _childColumnIndexes; + List _childColumnIndexes = []; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/scrollview_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/scrollview_widget.dart index 9318ebf63..5955a31e4 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/scrollview_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/scrollview_widget.dart @@ -2,7 +2,9 @@ part of datagrid; class _ScrollViewWidget extends StatefulWidget { const _ScrollViewWidget( - {@required this.dataGridStateDetails, this.width, this.height}); + {required this.dataGridStateDetails, + required this.width, + required this.height}); final _DataGridStateDetails dataGridStateDetails; final double width; @@ -13,9 +15,9 @@ class _ScrollViewWidget extends StatefulWidget { } class _ScrollViewWidgetState extends State<_ScrollViewWidget> { - ScrollController _verticalController; - ScrollController _horizontalController; - FocusNode _dataGridFocusNode; + ScrollController? _verticalController; + ScrollController? _horizontalController; + FocusNode? _dataGridFocusNode; double _width = 0.0; double _height = 0.0; bool _isScrolling = false; @@ -36,16 +38,14 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { ..verticalController = _verticalController ..horizontalController = _horizontalController; - if (_dataGridSettings.rowSelectionManager != null) { - _dataGridSettings.rowSelectionManager - .addListener(_handleSelectionController); - } + _dataGridSettings.rowSelectionManager + .addListener(_handleSelectionController); if (_dataGridFocusNode == null) { _dataGridFocusNode = FocusNode(onKey: _handleFocusKeyOperation); - _dataGridSettings.dataGridFocusNode = _dataGridFocusNode; + _dataGridSettings.dataGridFocusNode = _dataGridFocusNode!; if (_dataGridSettings.source.sortedColumns.isNotEmpty) { - _dataGridFocusNode.requestFocus(); + _dataGridFocusNode!.requestFocus(); } } @@ -63,9 +63,10 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { void _verticalListener() { setState(() { - final newValue = _verticalController.offset; + final newValue = _verticalController!.offset; _container.verticalOffset = newValue; _container.setRowHeights(); + _container.resetSwipeOffset(); _isScrolling = true; _container._isDirty = true; _isLoadMoreViewLoaded = false; @@ -74,9 +75,10 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { void _horizontalListener() { setState(() { - final newValue = _horizontalController.offset; + final newValue = _horizontalController!.offset; _container.horizontalOffset = newValue; _dataGridSettings.columnSizer._refresh(widget.width); + _container.resetSwipeOffset(); _isScrolling = true; _container._isDirty = true; }); @@ -90,8 +92,9 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { void _setHorizontalOffset() { if (_container._needToSetHorizontalOffset) { - _container.horizontalOffset = - _horizontalController.hasClients ? _horizontalController.offset : 0.0; + _container.horizontalOffset = _horizontalController!.hasClients + ? _horizontalController!.offset + : 0.0; _container.scrollColumns.markDirty(); } @@ -100,19 +103,17 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { void _updateColumnSizer() { final columnSizer = _dataGridSettings.columnSizer; - if (columnSizer != null) { - if (columnSizer._isColumnSizerLoadedInitially) { - columnSizer - .._initialRefresh(widget.width) - .._isColumnSizerLoadedInitially = false; - } else { - columnSizer._refresh(widget.width); - } + if (columnSizer._isColumnSizerLoadedInitially) { + columnSizer + .._initialRefresh(widget.width) + .._isColumnSizerLoadedInitially = false; + } else { + columnSizer._refresh(widget.width); } } void _ensureWidgets() { - if (_dataGridSettings.source._effectiveDataSource == null) { + if (_dataGridSettings.source._effectiveRows.isEmpty) { return; } @@ -145,6 +146,57 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { } } + Widget _buildScrollView(double extentWidth, double scrollViewHeight, + double extentHeight, Size containerSize) { + Widget scrollView = Scrollbar( + isAlwaysShown: _dataGridSettings.isScrollbarAlwaysShown, + controller: _verticalController, + child: SingleChildScrollView( + controller: _verticalController, + physics: _dataGridSettings.isSwipingApplied + ? NeverScrollableScrollPhysics() + : _dataGridSettings.verticalScrollPhysics, + child: ConstrainedBox( + constraints: + BoxConstraints(minHeight: min(scrollViewHeight, extentHeight)), + child: Scrollbar( + isAlwaysShown: _dataGridSettings.isScrollbarAlwaysShown, + controller: _horizontalController, + child: SingleChildScrollView( + controller: _horizontalController, + scrollDirection: Axis.horizontal, + physics: _dataGridSettings.isSwipingApplied + ? NeverScrollableScrollPhysics() + : _dataGridSettings.horizontalScrollPhysics, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: min(_width, extentWidth)), + child: _VisualContainer( + key: ValueKey('SfDataGrid-VisualContainer'), + isDirty: _container._isDirty, + rowGenerator: _rowGenerator, + containerSize: containerSize, + dataGridSettings: _dataGridSettings, + ), + ), + ), + ), + ), + ), + ); + + if (_dataGridSettings.allowPullToRefresh) { + scrollView = RefreshIndicator( + child: scrollView, + key: _dataGridSettings.refreshIndicatorKey, + onRefresh: _dataGridSettings.source.handleRefresh, + strokeWidth: _dataGridSettings.refreshIndicatorStrokeWidth, + displacement: _dataGridSettings.refreshIndicatorDisplacement, + ); + } + + return scrollView; + } + void _addScrollView(List children) { final double extentWidth = _container.extentWidth; final double headerRowsHeight = _container.scrollRows @@ -163,34 +215,8 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { ? extentHeight : scrollViewHeight)); - final Widget scrollView = Scrollbar( - isAlwaysShown: _dataGridSettings.isScrollbarAlwaysShown, - controller: _horizontalController, - child: SingleChildScrollView( - controller: _horizontalController, - scrollDirection: Axis.horizontal, - physics: _dataGridSettings.horizontalScrollPhysics, - child: ConstrainedBox( - constraints: BoxConstraints( - minWidth: min(_width, extentWidth), - ), - child: Scrollbar( - isAlwaysShown: _dataGridSettings.isScrollbarAlwaysShown, - controller: _verticalController, - child: SingleChildScrollView( - controller: _verticalController, - physics: _dataGridSettings.verticalScrollPhysics, - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: min(scrollViewHeight, extentHeight), - ), - child: _VisualContainer( - isDirty: _container._isDirty, - rowGenerator: _rowGenerator, - containerSize: containerSize)), - ), - ))), - ); + final Widget scrollView = _buildScrollView( + extentWidth, scrollViewHeight, extentHeight, containerSize); final Positioned wrapScrollView = Positioned.fill( top: headerRowsHeight, @@ -214,7 +240,7 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { rows.rowRegion == RowRegion.header && rows.rowType == RowType.stackedHeaderRow) .map((dataRow) => _HeaderCellsWidget( - key: dataRow._key, + key: dataRow._key!, dataRow: dataRow, isDirty: _container._isDirty || dataRow._isDirty, )) @@ -225,7 +251,7 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { rows.rowRegion == RowRegion.header && rows.rowType == RowType.headerRow) .map((dataRow) => _HeaderCellsWidget( - key: dataRow._key, + key: dataRow._key!, dataRow: dataRow, isDirty: _container._isDirty || dataRow._isDirty, )) @@ -237,17 +263,17 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { if (_dataGridSettings.textDirection == TextDirection.ltr) { return -_container.horizontalOffset; } else { - if (!_horizontalController.hasClients || - _horizontalController.offset <= 0.0 || - _horizontalController.position.maxScrollExtent <= 0.0 || + if (!_horizontalController!.hasClients || + _horizontalController!.offset <= 0.0 || + _horizontalController!.position.maxScrollExtent <= 0.0 || _container.extentWidth <= _width) { return 0.0; - } else if (_horizontalController.position.maxScrollExtent == - _horizontalController.offset) { - return -_horizontalController.position.maxScrollExtent; + } else if (_horizontalController!.position.maxScrollExtent == + _horizontalController!.offset) { + return -_horizontalController!.position.maxScrollExtent; } - return -(_horizontalController.position.maxScrollExtent - + return -(_horizontalController!.position.maxScrollExtent - _container.horizontalOffset); } } @@ -277,13 +303,16 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { await _dataGridSettings.source.handleLoadMoreRows(); } - if (_verticalController.hasClients && + if (_verticalController!.hasClients && _dataGridSettings.loadMoreViewBuilder != null) { - if (_verticalController.offset >= - _verticalController.position.maxScrollExtent && + // FLUT-3038 Need to restrict load more view when rows exist within the + // view height. + if ((_verticalController!.position.maxScrollExtent > 0.0) && + (_verticalController!.offset >= + _verticalController!.position.maxScrollExtent) && !_isLoadMoreViewLoaded) { - final loadMoreView = - _dataGridSettings.loadMoreViewBuilder(context, loadMoreRows); + final Widget? loadMoreView = + _dataGridSettings.loadMoreViewBuilder!(context, loadMoreRows); if (loadMoreView != null) { final loadMoreAlignment = @@ -304,6 +333,157 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { } } + void _addFreezePaneLinesElevation(List children) { + final dataGridThemeData = _dataGridSettings.dataGridThemeData; + if (dataGridThemeData!.frozenPaneElevation <= 0.0 || + _dataGridSettings.columns.isEmpty || + _dataGridSettings.source._effectiveRows.isEmpty) { + return; + } + + void drawElevation({ + EdgeInsets? margin, + double? bottom, + double? start, + double? end, + double? top, + Axis? axis, + }) { + final elevationLine = ClipRect( + child: Container( + width: axis == Axis.vertical ? 1 : 0, + height: axis == Axis.horizontal ? 1 : 0, + margin: margin, + decoration: BoxDecoration(color: Color(0xFF000000), boxShadow: [ + BoxShadow( + color: dataGridThemeData.brightness == Brightness.light + ? Color(0x3D000000) + : Color(0x3DFFFFFF), + offset: Offset.zero, + spreadRadius: 3.0, + blurRadius: dataGridThemeData.frozenPaneElevation, + ) + ]))); + + children.add(Positioned.directional( + top: top, + end: end, + start: start, + bottom: bottom, + child: elevationLine, + textDirection: _dataGridSettings.textDirection, + )); + } + + double getTopPosition(DataRowBase columnHeaderRow, int columnIndex) { + double top = 0.0; + if (_dataGridSettings.stackedHeaderRows.isNotEmpty) { + top = columnHeaderRow._getRowHeight( + 0, _dataGridSettings.stackedHeaderRows.length - 1); + final DataCellBase? dataCell = columnHeaderRow._visibleColumns + .firstWhereOrNull((cell) => cell.columnIndex == columnIndex); + // Need to ignore header cell spanned height from the total stacked + // header rows height if it is spanned. + if (dataCell != null && dataCell._rowSpan > 0) { + top -= columnHeaderRow._getRowHeight( + dataCell.rowIndex - dataCell._rowSpan, dataCell.rowIndex - 1); + } + } + return top; + } + + // The field remainingViewPortHeight and remainingViewPortWidth are used to + // restrict the elevation height and width fill in the entire screen when + // extent width and height is smaller than the view size. + final remainingViewPortHeight = + (_dataGridSettings.container.extentHeight < _height) + ? _height - _dataGridSettings.container.extentHeight + : 0.0; + final remainingViewPortWidth = + (_dataGridSettings.container.extentWidth < _width) + ? _width - _dataGridSettings.container.extentWidth + : 0.0; + + final DataRowBase? columnHeaderRow = _dataGridSettings + .container.rowGenerator.items + .firstWhereOrNull((row) => row.rowType == RowType.headerRow); + + // Provided the margin to allow shadow only to the corresponding side. + // In 4.0 pixels, 1.0 pixel defines the size of the container and + // 3.0 pixels defines the amount of spreadRadius. + final double margin = dataGridThemeData.frozenPaneElevation + 4.0; + + final frozenColumnIndex = + _GridIndexResolver.getLastFrozenColumnIndex(_dataGridSettings); + final footerFrozenColumnIndex = + _GridIndexResolver.getStartFooterFrozenColumnIndex(_dataGridSettings); + final frozenRowIndex = + _GridIndexResolver.getLastFrozenRowIndex(_dataGridSettings); + final footerFrozenRowIndex = + _GridIndexResolver.getStartFooterFrozenRowIndex(_dataGridSettings); + + if (columnHeaderRow != null && + frozenColumnIndex >= 0 && + !_canDisableHorizontalScrolling(_dataGridSettings)) { + final top = getTopPosition(columnHeaderRow, frozenColumnIndex); + final left = columnHeaderRow._getColumnWidth( + 0, _dataGridSettings.frozenColumnsCount - 1); + + drawElevation( + top: top, + start: left, + bottom: remainingViewPortHeight, + axis: Axis.horizontal, + margin: _dataGridSettings.textDirection == TextDirection.rtl + ? EdgeInsets.only(left: margin) + : EdgeInsets.only(right: margin)); + } + + if (columnHeaderRow != null && + footerFrozenColumnIndex >= 0 && + !_canDisableHorizontalScrolling(_dataGridSettings)) { + final top = getTopPosition(columnHeaderRow, footerFrozenColumnIndex); + final right = columnHeaderRow._getColumnWidth( + footerFrozenColumnIndex, _dataGridSettings.container.columnCount); + + drawElevation( + top: top, + bottom: remainingViewPortHeight, + end: right + remainingViewPortWidth, + axis: Axis.horizontal, + margin: _dataGridSettings.textDirection == TextDirection.rtl + ? EdgeInsets.only(right: margin) + : EdgeInsets.only(left: margin)); + } + + if (columnHeaderRow != null && + frozenRowIndex >= 0 && + !_canDisableVerticalScrolling(_dataGridSettings)) { + final top = columnHeaderRow._getRowHeight(0, frozenRowIndex); + + drawElevation( + top: top, + start: 0.0, + end: remainingViewPortWidth, + axis: Axis.vertical, + margin: EdgeInsets.only(bottom: margin)); + } + + if (columnHeaderRow != null && + footerFrozenRowIndex >= 0 && + !_canDisableVerticalScrolling(_dataGridSettings)) { + final bottom = columnHeaderRow._getRowHeight( + footerFrozenRowIndex, _dataGridSettings.container.rowCount); + + drawElevation( + start: 0.0, + end: remainingViewPortWidth, + axis: Axis.vertical, + bottom: bottom + remainingViewPortHeight, + margin: EdgeInsets.only(top: margin)); + } + } + void _handleSelectionController() async { setState(() { /* Rebuild the DataGrid when the selection or currentcell is processed. */ @@ -319,7 +499,8 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { _dataGridSettings.currentCell.rowIndex == rowIndex && _dataGridSettings.currentCell.columnIndex == columnIndex) || (dataGridSettings.navigationMode == GridNavigationMode.row && - _dataGridSettings.currentCell.rowIndex == rowIndex); + _dataGridSettings.currentCell.rowIndex == rowIndex) || + !_dataGridFocusNode!.hasPrimaryFocus; if (e.isShiftPressed) { final firstRowIndex = @@ -349,21 +530,23 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { if (e.logicalKey == LogicalKeyboardKey.tab) { return needToMoveFocus(); } else { - return true; + return _dataGridFocusNode!.hasPrimaryFocus; } } void _handleKeyOperation(RawKeyEvent e) { - if (e.runtimeType == RawKeyDownEvent) { - _rowSelectionManager.handleKeyEvent(e); - if (e.isControlPressed) { - _dataGridSettings.isControlKeyPressed = true; + if (_dataGridFocusNode!.hasPrimaryFocus) { + if (e.runtimeType == RawKeyDownEvent) { + _rowSelectionManager.handleKeyEvent(e); + if (e.isControlPressed) { + _dataGridSettings.isControlKeyPressed = true; + } } - } - if (e.runtimeType == RawKeyUpEvent) { - if (e.logicalKey == LogicalKeyboardKey.controlLeft || - e.logicalKey == LogicalKeyboardKey.controlRight) { - _dataGridSettings.isControlKeyPressed = false; + if (e.runtimeType == RawKeyUpEvent) { + if (e.logicalKey == LogicalKeyboardKey.controlLeft || + e.logicalKey == LogicalKeyboardKey.controlRight) { + _dataGridSettings.isControlKeyPressed = false; + } } } } @@ -380,6 +563,10 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { _container .._needToSetHorizontalOffset = true .._isDirty = true; + if (oldWidget.width != widget.width || + oldWidget.height != widget.height) { + _container.resetSwipeOffset(); + } // FLUT-2047 Need to mark all visible rows height as dirty when DataGrid // size is changed if onQueryRowHeight is not null. if (oldWidget.width != widget.width && @@ -413,13 +600,15 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { _addScrollView(children); + _addFreezePaneLinesElevation(children); + _addLoadMoreView(children); _container._isDirty = false; _isScrolling = false; return RawKeyboardListener( - focusNode: _dataGridFocusNode, + focusNode: _dataGridFocusNode!, onKey: _handleKeyOperation, child: Container( height: _height, @@ -427,28 +616,21 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { decoration: const BoxDecoration( color: Colors.transparent, ), - // Remove this ClipRect widget and uncomment the below line. - // when the below mentioned issue resolved from framework side. - // https://github.com/flutter/flutter/issues/50508 - // clipBehavior: Clip.antiAlias, - child: ClipRect( - clipBehavior: Clip.antiAlias, - clipper: _DataGridClipper(), - child: Stack( - fit: StackFit.passthrough, - children: List.from(children))))); + clipBehavior: Clip.antiAlias, + child: Stack( + fit: StackFit.passthrough, children: List.from(children)))); } @override void dispose() { if (_verticalController != null) { - _verticalController + _verticalController! ..removeListener(_verticalListener) ..dispose(); } if (_horizontalController != null) { - _horizontalController + _horizontalController! ..removeListener(_horizontalListener) ..dispose(); } @@ -457,20 +639,9 @@ class _ScrollViewWidgetState extends State<_ScrollViewWidget> { } } -// Remove the below custom clipper class. -// when the below mentioned issue resolved from framework side. -// https://github.com/flutter/flutter/issues/50508 -class _DataGridClipper extends CustomClipper { - @override - Rect getClip(Size size) => Rect.fromLTWH(0.0, 0.0, size.width, size.height); - - @override - bool shouldReclip(_DataGridClipper oldClipper) => false; -} - bool _canDisableVerticalScrolling(_DataGridSettings dataGridSettings) { - final _VisualContainerHelper container = dataGridSettings.container; - if (container != null && container.scrollRows != null) { + final _VisualContainerHelper? container = dataGridSettings.container; + if (container != null) { return (container.scrollRows.headerExtent + container.scrollRows.footerExtent) > dataGridSettings.viewHeight; @@ -480,8 +651,8 @@ bool _canDisableVerticalScrolling(_DataGridSettings dataGridSettings) { } bool _canDisableHorizontalScrolling(_DataGridSettings dataGridSettings) { - final _VisualContainerHelper container = dataGridSettings.container; - if (container != null && container.scrollColumns != null) { + final _VisualContainerHelper? container = dataGridSettings.container; + if (container != null) { return (container.scrollColumns.headerExtent + container.scrollColumns.footerExtent) > dataGridSettings.viewWidth; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_helper.dart index 1eaa81cb7..ce110e0a5 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_helper.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_helper.dart @@ -1,36 +1,30 @@ part of datagrid; class _VisualContainerHelper { - _VisualContainerHelper({this.rowGenerator}) { - _isPreGenerator = false; - _needToRefreshColumn = false; - _isGridLoaded = false; - _headerLineCount = 1; - _isDirty = false; - _needToSetHorizontalOffset = false; + _VisualContainerHelper({required this.rowGenerator}) { rowHeightsProvider = onCreateRowHeights(); columnWidthsProvider = onCreateColumnWidths(); } - bool _isPreGenerator; - bool _needToRefreshColumn; - bool _isGridLoaded; - int _headerLineCount; - bool _isDirty; + bool _isPreGenerator = false; + bool _needToRefreshColumn = false; + bool _isGridLoaded = false; + int _headerLineCount = 1; + bool _isDirty = false; // Used to set the horizontal offset for LTR to RTL and vise versa, - bool _needToSetHorizontalOffset; + bool _needToSetHorizontalOffset = false; - _DataGridStateDetails get dataGridStateDetails => + _DataGridStateDetails? get dataGridStateDetails => rowGenerator.dataGridStateDetails; - _RowGenerator rowGenerator; + late _RowGenerator rowGenerator; - _RowHeightManager rowHeightManager = _RowHeightManager(); + late _PaddedEditableLineSizeHostBase rowHeightsProvider; - _PaddedEditableLineSizeHostBase rowHeightsProvider; + late _PaddedEditableLineSizeHostBase columnWidthsProvider; - _PaddedEditableLineSizeHostBase columnWidthsProvider; + _RowHeightManager rowHeightManager = _RowHeightManager(); _PaddedEditableLineSizeHostBase get rowHeights => rowHeightsProvider; @@ -39,33 +33,33 @@ class _VisualContainerHelper { _ScrollAxisBase get scrollRows { _scrollRows ??= createScrollAxis(true, verticalScrollBar, rowHeightsProvider); - _scrollRows.name = 'ScrollRows'; + _scrollRows!.name = 'ScrollRows'; - return _scrollRows; + return _scrollRows!; } - _ScrollAxisBase _scrollRows; + _ScrollAxisBase? _scrollRows; set scrollRows(_ScrollAxisBase newValue) => _scrollRows = newValue; _ScrollAxisBase get scrollColumns { _scrollColumns ??= createScrollAxis(true, horizontalScrollBar, columnWidthsProvider); - _scrollColumns.name = 'ScrollColumns'; - return _scrollColumns; + _scrollColumns!.name = 'ScrollColumns'; + return _scrollColumns!; } - _ScrollAxisBase _scrollColumns; + _ScrollAxisBase? _scrollColumns; set scrollColumns(_ScrollAxisBase newValue) => _scrollColumns = newValue; _ScrollBarBase get horizontalScrollBar => _horizontalScrollBar ?? (_horizontalScrollBar = _ScrollInfo()); - _ScrollBarBase _horizontalScrollBar; + _ScrollBarBase? _horizontalScrollBar; _ScrollBarBase get verticalScrollBar => _verticalScrollBar ?? (_verticalScrollBar = _ScrollInfo()); - _ScrollBarBase _verticalScrollBar; + _ScrollBarBase? _verticalScrollBar; int get rowCount => rowHeightsProvider.lineCount; @@ -131,13 +125,15 @@ class _VisualContainerHelper { horizontalScrollBar.value - horizontalScrollBar.minimum; set horizontalOffset(double newValue) { - if (dataGridStateDetails().textDirection == TextDirection.ltr) { + final _DataGridSettings dataGridSettings = dataGridStateDetails!(); + if (dataGridSettings.textDirection == TextDirection.ltr) { horizontalScrollBar.value = newValue + horizontalScrollBar.minimum; } else { horizontalScrollBar.value = max(horizontalScrollBar.minimum, horizontalScrollBar.maximum - horizontalScrollBar.largeChange) - newValue; } + dataGridSettings.controller._horizontalOffset = horizontalScrollBar.value; _needToRefreshColumn = true; } @@ -148,24 +144,23 @@ class _VisualContainerHelper { set verticalOffset(double newValue) { if (verticalScrollBar.value != (newValue + verticalScrollBar.minimum)) { verticalScrollBar.value = newValue + verticalScrollBar.minimum; + dataGridStateDetails!().controller._verticalOffset = + verticalScrollBar.value; } } double get extentWidth { - final _PixelScrollAxis _scrollColumns = scrollColumns; + final _PixelScrollAxis _scrollColumns = scrollColumns as _PixelScrollAxis; return _scrollColumns.totalExtent; } double get extentHeight { - final _PixelScrollAxis _scrollRows = scrollRows; + final _PixelScrollAxis _scrollRows = scrollRows as _PixelScrollAxis; return _scrollRows.totalExtent; } void setRowHeights() { - if (rowGenerator == null) { - return; - } - if (dataGridStateDetails().onQueryRowHeight == null) { + if (dataGridStateDetails!().onQueryRowHeight == null) { return; } @@ -173,7 +168,7 @@ class _VisualContainerHelper { int endIndex = 0; - if (visibleRows.length < visibleRows.firstBodyVisibleIndex) { + if (visibleRows.length <= visibleRows.firstBodyVisibleIndex) { return; } @@ -208,7 +203,8 @@ class _VisualContainerHelper { var current = bodyStart; var currentEnd = endIndex; - final _LineSizeCollection lineSizeCollection = rowHeights; + final _LineSizeCollection lineSizeCollection = + rowHeights as _LineSizeCollection; lineSizeCollection.suspendUpdates(); for (int index = bodyStartLineIndex; current <= bodyEnd && index < scrollRows.firstFooterLineIndex; @@ -217,7 +213,7 @@ class _VisualContainerHelper { if (!rowHeightManager.contains(index, RowRegion.body)) { final rowHeight = rowGenerator._queryRowHeight(index, height); - if (rowHeight != null) { + if (rowHeight != height) { height = rowHeight; rowHeights.setRange(index, index, height); } @@ -238,7 +234,7 @@ class _VisualContainerHelper { final height = rowHeights[index]; final rowHeight = rowGenerator._queryRowHeight(index, height); - if (rowHeight != null) { + if (rowHeight != height) { rowHeights.setRange(index, index, rowHeight); } } @@ -255,11 +251,12 @@ class _VisualContainerHelper { return; } - final _DataGridSettings dataGridSettings = dataGridStateDetails(); + final _DataGridSettings dataGridSettings = dataGridStateDetails!(); for (int index = startIndex; index <= endIndex; index++) { if (!rowHeightManager.contains(index, region)) { - final rowHeight = rowGenerator._queryRowHeight(index, 0); - if (rowHeight != null) { + final height = dataGridSettings.container.rowHeights[index]; + final rowHeight = rowGenerator._queryRowHeight(index, height); + if (rowHeight != height) { rowHeights.setRange(index, index, rowHeight); if (region == RowRegion.header && @@ -305,7 +302,8 @@ class _VisualContainerHelper { } void removeColumns(int removeAtColumnIndex, int count) { - final _LineSizeCollection lineSizeCollection = columnWidths; + final _LineSizeCollection lineSizeCollection = + columnWidths as _LineSizeCollection; lineSizeCollection.suspendUpdates(); columnWidthsProvider.removeLines(removeAtColumnIndex, count, null); lineSizeCollection.resumeUpdates(); @@ -327,7 +325,7 @@ class _VisualContainerHelper { final Object _lineSizes = lineSizes; if (lineSizes is _DistancesHostBase) { return _PixelScrollAxis.fromPixelScrollAxis( - scrollBar, lineSizes, _lineSizes); + scrollBar, lineSizes, _lineSizes as _DistancesHostBase); } else { return _PixelScrollAxis.fromPixelScrollAxis(scrollBar, lineSizes, null); } @@ -336,7 +334,7 @@ class _VisualContainerHelper { } } - _VisibleLineInfo getRowVisibleLineInfo(int index) => + _VisibleLineInfo? getRowVisibleLineInfo(int index) => scrollRows.getVisibleLineAtLineIndex(index); List _getStartEndIndex( @@ -350,9 +348,6 @@ class _VisualContainerHelper { endIndex = visibleLines[visibleLines.firstBodyVisibleIndex - 1].lineIndex; } - - return [startIndex, endIndex]; - break; case 1: if ((visibleLines.firstBodyVisibleIndex <= 0 && @@ -363,7 +358,6 @@ class _VisualContainerHelper { startIndex = visibleLines[visibleLines.firstBodyVisibleIndex].lineIndex; endIndex = visibleLines[visibleLines.lastBodyVisibleIndex].lineIndex; - return [startIndex, endIndex]; } break; case 2: @@ -372,24 +366,22 @@ class _VisualContainerHelper { visibleLines[visibleLines.firstFooterVisibleIndex].lineIndex; endIndex = visibleLines[visibleLines.length - 1].lineIndex; } - return [startIndex, endIndex]; - break; - default: - return [startIndex, endIndex]; break; } + + return [startIndex, endIndex]; } void _refreshDefaultLineSize() { - rowHeights.defaultLineSize = dataGridStateDetails().rowHeight; - columnWidths.defaultLineSize = dataGridStateDetails().defaultColumnWidth; + final _DataGridSettings dataGridSettings = dataGridStateDetails!(); + rowHeights.defaultLineSize = dataGridSettings.rowHeight; + columnWidths.defaultLineSize = dataGridSettings.defaultColumnWidth; } void _refreshHeaderLineCount() { - final dataGridSettings = dataGridStateDetails(); + final dataGridSettings = dataGridStateDetails!(); _headerLineCount = 1; - if (dataGridSettings.stackedHeaderRows != null && - dataGridSettings.stackedHeaderRows.isNotEmpty) { + if (dataGridSettings.stackedHeaderRows.isNotEmpty) { _headerLineCount += dataGridSettings.stackedHeaderRows.length; dataGridSettings.headerLineCount = _headerLineCount; } else { @@ -398,13 +390,10 @@ class _VisualContainerHelper { } void _updateRowAndColumnCount() { - if (this == null) { - return; - } - - final _LineSizeCollection lineSizeCollection = columnWidths; + final _LineSizeCollection lineSizeCollection = + columnWidths as _LineSizeCollection; lineSizeCollection.suspendUpdates(); - final _DataGridSettings dataGridSettings = dataGridStateDetails(); + final _DataGridSettings dataGridSettings = dataGridStateDetails!(); _updateColumnCount(dataGridSettings); _updateRowCount(dataGridSettings); if (rowCount > 0) { @@ -430,18 +419,19 @@ class _VisualContainerHelper { } void _updateRowCount(_DataGridSettings dataGridSettings) { - final _LineSizeCollection lineSizeCollection = rowHeights; + final _LineSizeCollection lineSizeCollection = + rowHeights as _LineSizeCollection; lineSizeCollection.suspendUpdates(); - int rowCount = 0; - rowCount = dataGridSettings.source._effectiveDataSource != null - ? dataGridSettings.source._effectiveDataSource.length + int _rowCount = 0; + _rowCount = dataGridSettings.source._effectiveRows.isNotEmpty + ? dataGridSettings.source._effectiveRows.length : 0; - rowCount += dataGridSettings.headerLineCount; - this.rowCount = rowCount; + _rowCount += dataGridSettings.headerLineCount; + this.rowCount = _rowCount; _updateFreezePaneRows(dataGridSettings); // FLUT-2047 Need to mark all visible rows height as dirty when // updating the row count if onQueryRowHeight is not null. - if (dataGridStateDetails().onQueryRowHeight != null) { + if (dataGridStateDetails!().onQueryRowHeight != null) { rowHeightManager.reset(); } //need to reset the hidden state @@ -508,6 +498,21 @@ class _VisualContainerHelper { .._visibleColumns.forEach(updateColumn); } } + + void resetSwipeOffset() { + final dataGridSettings = dataGridStateDetails!(); + if (!dataGridSettings.allowSwiping) { + return; + } + + final swipedRow = dataGridSettings.rowGenerator.items.firstWhereOrNull( + (row) => row._isSwipingRow && dataGridSettings.swipingOffset.abs() > 0, + ); + if (swipedRow != null) { + swipedRow._isSwipingRow = false; + dataGridSettings.swipingOffset = 0.0; + } + } } class _RowHeightManager { diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_widget.dart index e1c813002..3feb8f358 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_widget.dart @@ -2,52 +2,100 @@ part of datagrid; class _VisualContainer extends StatefulWidget { const _VisualContainer( - {Key key, this.rowGenerator, this.containerSize, this.isDirty}) + {required Key key, + required this.rowGenerator, + required this.containerSize, + required this.isDirty, + required this.dataGridSettings}) : super(key: key); final Size containerSize; final _RowGenerator rowGenerator; final bool isDirty; + final _DataGridSettings dataGridSettings; @override State createState() => _VisualContainerState(); } class _VisualContainerState extends State<_VisualContainer> { + void _addSwipeBackgroundWidget(List children) { + final dataGridSettings = widget.dataGridSettings; + if (dataGridSettings.allowSwiping && + dataGridSettings.swipingOffset.abs() > 0.0) { + final swipeRow = widget.rowGenerator.items + .where((row) => + row.rowRegion == RowRegion.body || row.rowType == RowType.dataRow) + .firstWhereOrNull((row) => row._isSwipingRow); + if (swipeRow != null) { + final swipeDirection = _SfDataGridHelper.getSwipeDirection( + dataGridSettings, dataGridSettings.swipingOffset); + + switch (swipeDirection) { + case DataGridRowSwipeDirection.startToEnd: + if (dataGridSettings.startSwipeActionsBuilder != null) { + final Widget? startSwipeWidget = dataGridSettings + .startSwipeActionsBuilder!(context, swipeRow._dataGridRow!); + children.add(startSwipeWidget ?? Container()); + } + break; + case DataGridRowSwipeDirection.endToStart: + if (dataGridSettings.endSwipeActionsBuilder != null) { + final Widget? endSwipeWidget = dataGridSettings + .endSwipeActionsBuilder!(context, swipeRow._dataGridRow!); + children.add(endSwipeWidget ?? Container()); + } + break; + } + } + } + } + @override Widget build(BuildContext context) { final List children = widget.rowGenerator.items .where((row) => row.rowRegion == RowRegion.body || row.rowType == RowType.dataRow) .map((dataRow) => _VirtualizingCellsWidget( - key: dataRow._key, + key: dataRow._key!, dataRow: dataRow, isDirty: widget.isDirty || dataRow._isDirty, )) - .toList(growable: false); + .toList(); + + _addSwipeBackgroundWidget(children); return _VisualContainerRenderObjectWidget( key: widget.key, containerSize: widget.containerSize, isDirty: widget.isDirty, children: children, + dataGridSettings: widget.dataGridSettings, ); } } class _VisualContainerRenderObjectWidget extends MultiChildRenderObjectWidget { _VisualContainerRenderObjectWidget( - {Key key, this.containerSize, this.isDirty, this.children}) + {required Key? key, + required this.containerSize, + required this.isDirty, + required this.children, + required this.dataGridSettings}) : super(key: key, children: RepaintBoundary.wrapAll(List.from(children))); @override final List children; final Size containerSize; final bool isDirty; + final _DataGridSettings dataGridSettings; @override _RenderVisualContainer createRenderObject(BuildContext context) => - _RenderVisualContainer(containerSize: containerSize, isDirty: isDirty); + _RenderVisualContainer( + containerSize: containerSize, + isDirty: isDirty, + dataGridSettings: dataGridSettings); @override void updateRenderObject( @@ -55,16 +103,17 @@ class _VisualContainerRenderObjectWidget extends MultiChildRenderObjectWidget { super.updateRenderObject(context, renderObject); renderObject ..containerSize = containerSize - ..isDirty = isDirty; + ..isDirty = isDirty + .._dataGridSettings = dataGridSettings; } } class _VisualContainerParentData extends ContainerBoxParentData { _VisualContainerParentData(); - double width; - double height; - Rect rowClipRect; + double width = 0.0; + double height = 0.0; + Rect? rowClipRect; void reset() { width = 0.0; @@ -79,8 +128,12 @@ class _RenderVisualContainer extends RenderBox ContainerRenderObjectMixin, RenderBoxContainerDefaultsMixin { _RenderVisualContainer( - {List children, Size containerSize, bool isDirty}) + {List? children, + Size containerSize = Size.zero, + bool isDirty = false, + _DataGridSettings? dataGridSettings}) : _containerSize = containerSize, + _dataGridSettings = dataGridSettings!, _isDirty = isDirty { addAll(children); } @@ -108,6 +161,10 @@ class _RenderVisualContainer extends RenderBox markNeedsPaint(); } + late _DataGridSettings _dataGridSettings; + + _RenderVirtualizingCellsWidget? _swipeWholeRowElement; + @override bool get isRepaintBoundary => true; @@ -120,20 +177,26 @@ class _RenderVisualContainer extends RenderBox } @override - bool hitTestChildren(BoxHitTestResult result, {Offset position}) { - _RenderVirtualizingCellsWidget child = lastChild; + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + RenderBox? child = lastChild; while (child != null) { - final _VisualContainerParentData childParentData = child.parentData; + final _VisualContainerParentData childParentData = + child.parentData as _VisualContainerParentData; final bool isHit = result.addWithPaintOffset( offset: childParentData.offset, position: position, hitTest: (BoxHitTestResult result, Offset transformed) { - if (child.rowClipRect != null && - !child.rowClipRect.contains(transformed)) { + // Need to ensure whether the child type is _RenderVirtualizingCellsWidget + // or not before accessing it. Because swiping widget's render object won't be + // _RenderVirtualizingCellsWidget. + final RenderBox? wholeRowElement = child; + if (wholeRowElement != null && + wholeRowElement is _RenderVirtualizingCellsWidget && + wholeRowElement.rowClipRect != null && + !wholeRowElement.rowClipRect!.contains(transformed)) { return false; } - - return child.hitTest(result, position: transformed); + return child!.hitTest(result, position: transformed); }, ); @@ -145,59 +208,137 @@ class _RenderVisualContainer extends RenderBox return false; } + Offset _getSwipingChildOffset(Rect swipeRowRect) { + double dxPosition = 0.0; + final viewWidth = _dataGridSettings.viewWidth; + final maxSwipeOffset = _dataGridSettings.swipeMaxOffset; + final extentWidth = _dataGridSettings.container.extentWidth; + + if (_dataGridSettings.textDirection == TextDirection.rtl && + viewWidth > extentWidth) { + dxPosition = (_dataGridSettings.swipingOffset >= 0) + ? viewWidth - extentWidth + : viewWidth - maxSwipeOffset; + } else { + dxPosition = (_dataGridSettings.swipingOffset >= 0) + ? 0.0 + : extentWidth - maxSwipeOffset; + } + + return Offset(dxPosition, swipeRowRect.top); + } + @override void performLayout() { size = constraints.constrain(Size(containerSize.width, containerSize.height)); - void _layout({RenderBox child, double width, double height}) { + void layout( + {required RenderBox child, + required double width, + required double height}) { child.layout(BoxConstraints.tightFor(width: width, height: height), parentUsesSize: true); } - RenderBox child = firstChild; + RenderBox? child = firstChild; while (child != null) { - final _VisualContainerParentData _parentData = child.parentData; - final _RenderVirtualizingCellsWidget wholeRowElement = child; - if (wholeRowElement.dataRow.isVisible) { - final Rect rowRect = wholeRowElement._measureRowRect(size.width); - _parentData - ..width = rowRect.width - ..height = rowRect.height - ..rowClipRect = wholeRowElement.rowClipRect; - _layout( - child: child, width: _parentData.width, height: _parentData.height); - _parentData.offset = Offset(rowRect.left, rowRect.top); + final _VisualContainerParentData parentData = + child.parentData as _VisualContainerParentData; + // Need to ensure whether the child type is _RenderVirtualizingCellsWidget + // or not before accessing it. Because swiping widget's render object won't be + // _RenderVirtualizingCellsWidget. + final RenderBox? wholeRowElement = child; + if (wholeRowElement != null && + wholeRowElement is _RenderVirtualizingCellsWidget) { + if (wholeRowElement.dataRow.isVisible) { + final Rect rowRect = wholeRowElement._measureRowRect(size.width); + + parentData + ..width = rowRect.width + ..height = rowRect.height + ..rowClipRect = wholeRowElement.rowClipRect + ..offset = Offset( + (wholeRowElement.dataRow._isSwipingRow && + _dataGridSettings.swipingOffset != 0.0 && + _dataGridSettings.swipingAnimation != null) + ? rowRect.left + _dataGridSettings.swipingAnimation!.value + : rowRect.left, + rowRect.top); + + if (wholeRowElement.dataRow._isSwipingRow && + _dataGridSettings.swipingOffset.abs() > 0.0) { + _swipeWholeRowElement = wholeRowElement; + } + + layout( + child: child, width: parentData.width, height: parentData.height); + } else { + child.layout(const BoxConstraints.tightFor(width: 0.0, height: 0.0)); + parentData.reset(); + } } else { - child.layout(const BoxConstraints.tightFor(width: 0.0, height: 0.0)); - _parentData.reset(); + // We added the swiping widget to the last position of chidren collection. + // So, we can get it diretly from lastChild property. + final RenderBox? swipingWidget = lastChild; + if (swipingWidget != null && _swipeWholeRowElement != null) { + final swipeRowRect = + _swipeWholeRowElement!._measureRowRect(size.width); + + parentData + ..width = _dataGridSettings.swipeMaxOffset + ..height = swipeRowRect.height + ..offset = _getSwipingChildOffset(swipeRowRect); + + layout( + child: swipingWidget, + width: parentData.width, + height: parentData.height); + } } - child = _parentData.nextSibling; + child = parentData.nextSibling; } } @override void paint(PaintingContext context, Offset offset) { - RenderBox child = firstChild; + RenderBox? child = firstChild; while (child != null) { - final _VisualContainerParentData childParentData = child.parentData; - if (childParentData.width != 0.0 && childParentData.height != 0.0) { - if (childParentData.rowClipRect != null) { - context.pushClipRect( - needsCompositing, - childParentData.offset + offset, - childParentData.rowClipRect, - (context, offset) { - context.paintChild(child, offset); - }, - clipBehavior: Clip.antiAlias, - ); - } else { - context.paintChild(child, childParentData.offset + offset); + final _VisualContainerParentData childParentData = + child.parentData as _VisualContainerParentData; + final RenderBox? wholeRowElement = child; + if (wholeRowElement != null && + wholeRowElement is _RenderVirtualizingCellsWidget) { + if (childParentData.width != 0.0 && childParentData.height != 0.0) { + if (childParentData.rowClipRect != null) { + if (wholeRowElement.dataRow._isSwipingRow && + _dataGridSettings.swipingOffset.abs() > 0.0) { + // We added the swiping widget to the last position of chidren collection. + // So, we can get it diretly from lastChild property. + final RenderBox? swipeWidget = lastChild; + if (swipeWidget != null) { + final _VisualContainerParentData childParentData = + swipeWidget.parentData as _VisualContainerParentData; + context.paintChild( + swipeWidget, childParentData.offset + offset); + } + } + + context.pushClipRect( + needsCompositing, + childParentData.offset + offset, + childParentData.rowClipRect!, + (context, offset) { + context.paintChild(child!, offset); + }, + clipBehavior: Clip.antiAlias, + ); + } else { + context.paintChild(child, childParentData.offset + offset); + } } } - child = childParentData.nextSibling; } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/current_cell_manager.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/current_cell_manager.dart index 9437843dc..b154abe73 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/current_cell_manager.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/current_cell_manager.dart @@ -1,11 +1,11 @@ part of datagrid; class _CurrentCellManager { - _CurrentCellManager(_DataGridStateDetails dataGridStateDetails) { + _CurrentCellManager({required _DataGridStateDetails dataGridStateDetails}) { _dataGridStateDetails = dataGridStateDetails; } - _DataGridStateDetails _dataGridStateDetails; + late _DataGridStateDetails _dataGridStateDetails; int rowIndex = -1; @@ -13,6 +13,9 @@ class _CurrentCellManager { bool _handlePointerOperation( _DataGridSettings dataGridSettings, RowColumnIndex rowColumnIndex) { + if (dataGridSettings.allowSwiping) { + dataGridSettings.container.resetSwipeOffset(); + } final previousRowColumnIndex = RowColumnIndex(rowIndex, columnIndex); if (rowColumnIndex != previousRowColumnIndex && dataGridSettings.navigationMode != GridNavigationMode.row) { @@ -52,7 +55,7 @@ class _CurrentCellManager { } } - dataGridSettings.controller?._currentCell = + dataGridSettings.controller._currentCell = _GridIndexResolver.resolveToRowColumnIndex( dataGridSettings, RowColumnIndex(rowIndex, columnIndex)); } @@ -73,27 +76,25 @@ class _CurrentCellManager { } _updateCurrentRowColumnIndex(-1, -1); - dataGridSettings.controller?._currentCell = RowColumnIndex(-1, -1); + dataGridSettings.controller._currentCell = RowColumnIndex(-1, -1); } - DataRowBase _getDataRow(_DataGridSettings dataGridSettings, int rowIndex) { + DataRowBase? _getDataRow(_DataGridSettings dataGridSettings, int rowIndex) { final dataRows = dataGridSettings.rowGenerator.items; if (dataRows.isEmpty) { return null; } - return dataRows.firstWhere((row) => row.rowIndex == rowIndex, - orElse: () => null); + return dataRows.firstWhereOrNull((row) => row.rowIndex == rowIndex); } - DataCellBase _getDataCell(DataRowBase dataRow, int columnIndex) { + DataCellBase? _getDataCell(DataRowBase dataRow, int columnIndex) { if (dataRow._visibleColumns.isEmpty) { return null; } - return dataRow._visibleColumns.firstWhere( - (dataCell) => dataCell.columnIndex == columnIndex, - orElse: () => null); + return dataRow._visibleColumns + .firstWhereOrNull((dataCell) => dataCell.columnIndex == columnIndex); } void _updateCurrentRowColumnIndex(int rowIndex, int columnIndex) { @@ -102,7 +103,7 @@ class _CurrentCellManager { } void _setCurrentCellDirty( - DataRowBase dataRow, DataCellBase dataCell, bool enableCurrentCell) { + DataRowBase? dataRow, DataCellBase? dataCell, bool enableCurrentCell) { dataCell?.isCurrentCell = enableCurrentCell; dataCell?._isDirty = true; dataRow?.isCurrentRow = enableCurrentCell; @@ -119,9 +120,8 @@ class _CurrentCellManager { dataGridSettings, rowColumnIndex); final oldRowColumnIndex = _GridIndexResolver.resolveToRowColumnIndex( dataGridSettings, RowColumnIndex(rowIndex, columnIndex)); - return dataGridSettings.onCurrentCellActivating( - newRowColumnIndex, oldRowColumnIndex) ?? - true; + return dataGridSettings.onCurrentCellActivating!( + newRowColumnIndex, oldRowColumnIndex); } void _raiseCurrentCellActivated(RowColumnIndex previousRowColumnIndex) { @@ -134,7 +134,7 @@ class _CurrentCellManager { dataGridSettings, RowColumnIndex(rowIndex, columnIndex)); final oldRowColumnIndex = _GridIndexResolver.resolveToRowColumnIndex( dataGridSettings, previousRowColumnIndex); - dataGridSettings.onCurrentCellActivated( + dataGridSettings.onCurrentCellActivated!( newRowColumnIndex, oldRowColumnIndex); } @@ -223,8 +223,8 @@ class _CurrentCellManager { } void _updateBorderForMultipleSelection(_DataGridSettings dataGridSettings, - {RowColumnIndex previousRowColumnIndex, - RowColumnIndex nextRowColumnIndex}) { + {RowColumnIndex? previousRowColumnIndex, + RowColumnIndex? nextRowColumnIndex}) { if (dataGridSettings._isDesktop && dataGridSettings.navigationMode == GridNavigationMode.row && dataGridSettings.selectionMode == SelectionMode.multiple) { @@ -234,25 +234,24 @@ class _CurrentCellManager { ?._isDirty = true; } - final firstVisibleColumnIndex = - _GridIndexResolver.resolveToStartColumnIndex(dataGridSettings); - _updateCurrentRowColumnIndex(nextRowColumnIndex?.rowIndex ?? rowIndex, - nextRowColumnIndex?.columnIndex ?? firstVisibleColumnIndex); - dataGridSettings.currentCell - ._getDataRow( - dataGridSettings, nextRowColumnIndex?.rowIndex ?? rowIndex) - ?._isDirty = true; + if (nextRowColumnIndex != null) { + final firstVisibleColumnIndex = + _GridIndexResolver.resolveToStartColumnIndex(dataGridSettings); + _updateCurrentRowColumnIndex( + nextRowColumnIndex.rowIndex >= 0 + ? nextRowColumnIndex.rowIndex + : rowIndex, + nextRowColumnIndex.columnIndex >= 0 + ? nextRowColumnIndex.columnIndex + : firstVisibleColumnIndex); + dataGridSettings.currentCell + ._getDataRow( + dataGridSettings, + nextRowColumnIndex.rowIndex >= 0 + ? nextRowColumnIndex.rowIndex + : rowIndex) + ?._isDirty = true; + } } } - - @override - bool operator ==(Object other) => - identical(this, other) || - other is _CurrentCellManager && runtimeType == other.runtimeType; - - @override - int get hashCode { - final List _hashList = [this]; - return hashList(_hashList); - } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/row_selection_manager.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/row_selection_manager.dart index 99eb45433..a5af5a77e 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/row_selection_manager.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/row_selection_manager.dart @@ -19,10 +19,10 @@ part of datagrid; /// body: SfDataGrid( /// source: _employeeDataSource, /// columns: [ -/// GridNumericColumn(mappingName: 'id', headerText: 'ID'), -/// GridTextColumn(mappingName: 'name', headerText: 'Name'), -/// GridTextColumn(mappingName: 'designation', headerText: 'Designation'), -/// GridNumericColumn(mappingName: 'salary', headerText: 'Salary') +/// GridTextColumn(columnName: 'id', label = Text('ID')), +/// GridTextColumn(columnName: 'name', label = Text('Name')), +/// GridTextColumn(columnName: 'designation', label = Text('Designation')), +/// GridTextColumn(columnName: 'salary', label = Text('Salary')), /// ], /// selectionMode: SelectionMode.multiple, /// navigationMode: GridNavigationMode.cell, @@ -45,23 +45,22 @@ part of datagrid; /// ``` class RowSelectionManager extends SelectionManagerBase { /// Creates the [RowSelectionManager] for [SfDataGrid] widget. - RowSelectionManager({_DataGridStateDetails dataGridStateDetails}) - : super(dataGridStateDetails: dataGridStateDetails); + RowSelectionManager() : super(); RowColumnIndex _pressedRowColumnIndex = RowColumnIndex(-1, -1); void _applySelection(RowColumnIndex rowColumnIndex) { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - var recordIndex = _GridIndexResolver.resolveToRecordIndex( + final recordIndex = _GridIndexResolver.resolveToRecordIndex( dataGridSettings, rowColumnIndex.rowIndex); var record = _SelectionHelper.getRecord(dataGridSettings, recordIndex); - final addedItems = []; - final removeItems = []; + final List addedItems = []; + final List removeItems = []; if (dataGridSettings.selectionMode == SelectionMode.single) { - if (_selectedRows.contains(record)) { + if (record == null || _selectedRows.contains(record)) { return; } @@ -92,14 +91,14 @@ class RowSelectionManager extends SelectionManagerBase { removeItems.add(_selectedRows.selectedRow.first); } - if (!_selectedRows.contains(record)) { + if (record != null && !_selectedRows.contains(record)) { addedItems.add(record); } } if (_raiseSelectionChanging( newItems: addedItems, oldItems: removeItems)) { - if (!_selectedRows.contains(record)) { + if (record != null && !_selectedRows.contains(record)) { _clearSelectedRow(dataGridSettings); _addSelection(record, dataGridSettings); } else { @@ -117,15 +116,18 @@ class RowSelectionManager extends SelectionManagerBase { } else if (dataGridSettings.selectionMode == SelectionMode.multiple) { if (dataGridSettings.onSelectionChanging != null || dataGridSettings.onSelectionChanged != null) { - if (!_selectedRows.contains(record)) { - addedItems.add(record); - } else { - removeItems.add(record); + if (record != null) { + if (!_selectedRows.contains(record)) { + addedItems.add(record); + } else { + removeItems.add(record); + } } } if (_raiseSelectionChanging( - newItems: addedItems, oldItems: removeItems)) { + newItems: addedItems, oldItems: removeItems) && + record != null) { if (!_selectedRows.contains(record)) { _addSelection(record, dataGridSettings); } else { @@ -143,32 +145,30 @@ class RowSelectionManager extends SelectionManagerBase { } record = null; - recordIndex = null; } - void _addSelection(Object record, _DataGridSettings dataGridSettings) { - if (!_selectedRows.contains(record)) { + void _addSelection(DataGridRow? record, _DataGridSettings dataGridSettings) { + if (record != null && !_selectedRows.contains(record)) { final rowIndex = _SelectionHelper.resolveToRowIndex(dataGridSettings, record); - _setRowSelection(rowIndex, dataGridSettings, true); - if (record != null && !_selectedRows.selectedRow.contains(record)) { + if (!_selectedRows.selectedRow.contains(record)) { _selectedRows.selectedRow.add(record); - dataGridSettings.controller?._selectedRows?.add(record); + dataGridSettings.controller._selectedRows.add(record); _refreshSelection(); - return; + _setRowSelection(rowIndex, dataGridSettings, true); } } } - void _removeSelection(Object record, _DataGridSettings dataGridSettings) { + void _removeSelection( + DataGridRow? record, _DataGridSettings dataGridSettings) { if (record != null && _selectedRows.contains(record)) { - var rowIndex = + final rowIndex = _SelectionHelper.resolveToRowIndex(dataGridSettings, record); - _setRowSelection(rowIndex, dataGridSettings, false); _selectedRows.selectedRow.remove(record); - dataGridSettings.controller?._selectedRows?.remove(record); + dataGridSettings.controller._selectedRows.remove(record); _refreshSelection(); - rowIndex = null; + _setRowSelection(rowIndex, dataGridSettings, false); } } @@ -197,10 +197,12 @@ class RowSelectionManager extends SelectionManagerBase { } var row = dataGridSettings.rowGenerator.items - .firstWhere((item) => item.rowIndex == rowIndex, orElse: () => null); + .firstWhereOrNull((item) => item.rowIndex == rowIndex); if (row != null && row.rowType == RowType.dataRow) { row .._isDirty = true + .._dataGridRowAdapter = _SfDataGridHelper.getDataGridRowAdapter( + dataGridSettings, row._dataGridRow!) ..isSelectedRow = isRowSelected; } @@ -209,9 +211,9 @@ class RowSelectionManager extends SelectionManagerBase { void _clearSelection(_DataGridSettings dataGridSettings) { _selectedRows.selectedRow.clear(); - dataGridSettings.controller?._selectedRows?.clear(); - dataGridSettings.controller?._selectedRow = null; - dataGridSettings.controller?._selectedIndex = -1; + dataGridSettings.controller._selectedRows.clear(); + dataGridSettings.controller._selectedRow = null; + dataGridSettings.controller._selectedIndex = -1; for (final dataRow in dataGridSettings.rowGenerator.items) { if (dataRow.isSelectedRow) { dataRow.isSelectedRow = false; @@ -227,16 +229,31 @@ class RowSelectionManager extends SelectionManagerBase { void _refreshSelection() { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final Object _selectedRow = _selectedRows.selectedRow.isNotEmpty + _removeUnWantedDataGridRows(dataGridSettings); + final DataGridRow? _selectedRow = _selectedRows.selectedRow.isNotEmpty ? _selectedRows.selectedRow.last : null; - final int _recordIndex = - dataGridSettings.source._effectiveDataSource.indexOf(_selectedRow); - dataGridSettings.controller?._selectedIndex = _recordIndex; - dataGridSettings.controller?._selectedRow = _selectedRow; + final int _recordIndex = _selectedRow == null + ? -1 + : dataGridSettings.source._effectiveRows.indexOf(_selectedRow); + dataGridSettings.controller._selectedIndex = _recordIndex; + dataGridSettings.controller._selectedRow = _selectedRow; } - void _addCurrentCell(Object record, _DataGridSettings dataGridSettings, + void _removeUnWantedDataGridRows(_DataGridSettings dataGridSettings) { + final List duplicateSelectedRows = + _selectedRows.selectedRow.toList(); + for (final selectedRow in duplicateSelectedRows) { + final int rowIndex = + dataGridSettings.source._effectiveRows.indexOf(selectedRow); + if (rowIndex.isNegative) { + _selectedRows.selectedRow.remove(selectedRow); + dataGridSettings.controller._selectedRows.remove(selectedRow); + } + } + } + + void _addCurrentCell(DataGridRow record, _DataGridSettings dataGridSettings, {bool isSelectionChanging = false}) { final rowIndex = _SelectionHelper.resolveToRowIndex(dataGridSettings, record); @@ -271,6 +288,11 @@ class RowSelectionManager extends SelectionManagerBase { } else { if (dataGridSettings.selectionMode != SelectionMode.none) { final lastRecord = dataGridSettings.controller.selectedRow; + + if (lastRecord == null) { + return; + } + final _currentRowColumnIndex = _getRowColumnIndexOnModeChanged(dataGridSettings, lastRecord); @@ -288,14 +310,13 @@ class RowSelectionManager extends SelectionManagerBase { } RowColumnIndex _getRowColumnIndexOnModeChanged( - _DataGridSettings dataGridSettings, Object lastRecord) { + _DataGridSettings dataGridSettings, DataGridRow? lastRecord) { final int rowIndex = lastRecord == null && _pressedRowColumnIndex.rowIndex > 0 ? _pressedRowColumnIndex.rowIndex - : _SelectionHelper.resolveToRowIndex(dataGridSettings, lastRecord); + : _SelectionHelper.resolveToRowIndex(dataGridSettings, lastRecord!); - final int columnIndex = _pressedRowColumnIndex.columnIndex != null && - _pressedRowColumnIndex.columnIndex != -1 + final int columnIndex = _pressedRowColumnIndex.columnIndex != -1 ? _pressedRowColumnIndex.columnIndex : _GridIndexResolver.resolveToStartColumnIndex(dataGridSettings); @@ -348,43 +369,20 @@ class RowSelectionManager extends SelectionManagerBase { } @override - void handleDataGridSourceChanges( - {RowColumnIndex rowColumnIndex, - String propertyName, - bool recalculateRowHeight}) { - switch (propertyName) { - case 'selectedIndex': - onSelectedIndexChanged(); - break; - case 'selectedRow': - onSelectedRowChanged(); - break; - case 'selectedRows': - onSelectedRowsChanged(); - break; - default: - break; - } + void handleDataGridSourceChanges() { + final _DataGridSettings dataGridSettings = _dataGridStateDetails(); + _clearSelectedRows(dataGridSettings); } @override void onSelectedRowChanged() { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final Object newValue = dataGridSettings.controller?.selectedRow; - if (dataGridSettings == null || - dataGridSettings.selectionMode == SelectionMode.none) { + if (dataGridSettings.selectionMode == SelectionMode.none) { return; } - final int recordIndex = - dataGridSettings.source._effectiveDataSource?.indexOf(newValue); - final int rowIndex = - _GridIndexResolver.resolveToRowIndex(dataGridSettings, recordIndex); - - if (rowIndex < _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - return; - } + final DataGridRow? newValue = dataGridSettings.controller.selectedRow; bool canClearSelections() => _selectedRows.selectedRow.isNotEmpty && @@ -399,6 +397,15 @@ class RowSelectionManager extends SelectionManagerBase { return; } + final int recordIndex = + dataGridSettings.source._effectiveRows.indexOf(newValue!); + final int rowIndex = + _GridIndexResolver.resolveToRowIndex(dataGridSettings, recordIndex); + + if (rowIndex < _GridIndexResolver.getHeaderIndex(dataGridSettings)) { + return; + } + if (!_selectedRows.contains(newValue)) { //In multiple case we shouldn't to clear the collection as // well source properties. @@ -425,15 +432,15 @@ class RowSelectionManager extends SelectionManagerBase { @override void onSelectedIndexChanged() { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final int newValue = dataGridSettings.controller.selectedIndex; - if (dataGridSettings == null || - dataGridSettings.selectionMode == SelectionMode.none) { + if (dataGridSettings.selectionMode == SelectionMode.none) { return; } - if (dataGridSettings.source._effectiveDataSource.isEmpty || - newValue > dataGridSettings.source._effectiveDataSource.length) { + final int newValue = dataGridSettings.controller.selectedIndex; + + if (dataGridSettings.source._effectiveRows.isEmpty || + newValue > dataGridSettings.source._effectiveRows.length) { return; } @@ -477,15 +484,15 @@ class RowSelectionManager extends SelectionManagerBase { @override void onSelectedRowsChanged() { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final List newValue = - dataGridSettings?.controller?.selectedRows?.toList(growable: false); - if (dataGridSettings == null || - dataGridSettings.selectionMode != SelectionMode.multiple || + if (dataGridSettings.selectionMode != SelectionMode.multiple || dataGridSettings.selectionMode == SelectionMode.none) { return; } + final List newValue = + dataGridSettings.controller.selectedRows.toList(growable: false); + if (newValue.isEmpty) { _clearSelectedRows(dataGridSettings); notifyListeners(); @@ -493,7 +500,7 @@ class RowSelectionManager extends SelectionManagerBase { } _clearSelectedRows(dataGridSettings); - for (final record in newValue) { + for (final DataGridRow record in newValue) { _addSelection(record, dataGridSettings); } @@ -508,7 +515,7 @@ class RowSelectionManager extends SelectionManagerBase { _SelectionHelper.resolveToRowIndex(dataGridSettings, lastRecord); dataGridSettings.currentCell._updateBorderForMultipleSelection( dataGridSettings, - nextRowColumnIndex: RowColumnIndex(rowIndex, null)); + nextRowColumnIndex: RowColumnIndex(rowIndex, -1)); } notifyListeners(); @@ -524,7 +531,8 @@ class RowSelectionManager extends SelectionManagerBase { dataGridSettings.selectionMode != SelectionMode.multiple) { var lastRecord = dataGridSettings.controller.selectedRow; _clearSelection(dataGridSettings); - if (dataGridSettings.navigationMode == GridNavigationMode.cell) { + if (dataGridSettings.navigationMode == GridNavigationMode.cell && + lastRecord != null) { final _currentRowColumnIndex = _getRowColumnIndexOnModeChanged(dataGridSettings, lastRecord); @@ -532,8 +540,7 @@ class RowSelectionManager extends SelectionManagerBase { return; } - lastRecord = lastRecord == null && - dataGridSettings.selectionMode == SelectionMode.single + lastRecord = dataGridSettings.selectionMode == SelectionMode.single ? _SelectionHelper.getRecord( dataGridSettings, _GridIndexResolver.resolveToRecordIndex( @@ -553,7 +560,7 @@ class RowSelectionManager extends SelectionManagerBase { } else if (dataGridSettings._isDesktop && dataGridSettings.selectionMode == SelectionMode.multiple) { final currentRowColumnIndex = - RowColumnIndex(dataGridSettings.currentCell.rowIndex, null); + RowColumnIndex(dataGridSettings.currentCell.rowIndex, -1); dataGridSettings.currentCell._updateBorderForMultipleSelection( dataGridSettings, nextRowColumnIndex: currentRowColumnIndex); @@ -571,8 +578,7 @@ class RowSelectionManager extends SelectionManagerBase { final rowIndex = _GridIndexResolver.resolveToRecordIndex( dataGridSettings, currentCell.rowIndex); - if (recordLength != null && - recordLength > 0 && + if (recordLength > 0 && rowIndex >= recordLength && currentCell.rowIndex != -1) { final startRowIndexIndex = @@ -586,7 +592,6 @@ class RowSelectionManager extends SelectionManagerBase { final columnIndex = _GridIndexResolver.resolveToGridVisibleColumnIndex( dataGridSettings, currentCell.columnIndex); if (columnLength > 0 && - columnLength != null && columnIndex >= columnLength && currentCell.columnIndex != -1) { final startColumnIndex = @@ -615,6 +620,26 @@ class RowSelectionManager extends SelectionManagerBase { } } + @override + void _handleSelectionPropertyChanged( + {RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false}) { + switch (propertyName) { + case 'selectedIndex': + onSelectedIndexChanged(); + break; + case 'selectedRow': + onSelectedRowChanged(); + break; + case 'selectedRows': + onSelectedRowsChanged(); + break; + default: + break; + } + } + //KeyNavigation @override void handleKeyEvent(RawKeyEvent keyEvent) { @@ -640,16 +665,16 @@ class RowSelectionManager extends SelectionManagerBase { _processPageDown(); } - if (keyEvent?.logicalKey == LogicalKeyboardKey.arrowUp) { + if (keyEvent.logicalKey == LogicalKeyboardKey.arrowUp) { _processKeyUp(keyEvent); } - if (keyEvent?.logicalKey == LogicalKeyboardKey.arrowDown || - keyEvent?.logicalKey == LogicalKeyboardKey.enter) { + if (keyEvent.logicalKey == LogicalKeyboardKey.arrowDown || + keyEvent.logicalKey == LogicalKeyboardKey.enter) { _processKeyDown(keyEvent); } - if (keyEvent?.logicalKey == LogicalKeyboardKey.arrowLeft) { + if (keyEvent.logicalKey == LogicalKeyboardKey.arrowLeft) { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); if (dataGridSettings.textDirection == TextDirection.rtl) { _processKeyRight(dataGridSettings, keyEvent); @@ -658,7 +683,7 @@ class RowSelectionManager extends SelectionManagerBase { } } - if (keyEvent?.logicalKey == LogicalKeyboardKey.arrowRight) { + if (keyEvent.logicalKey == LogicalKeyboardKey.arrowRight) { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); if (dataGridSettings.textDirection == TextDirection.rtl) { _processKeyLeft(dataGridSettings, keyEvent); @@ -673,7 +698,7 @@ class RowSelectionManager extends SelectionManagerBase { } } - if (keyEvent?.logicalKey == LogicalKeyboardKey.space) { + if (keyEvent.logicalKey == LogicalKeyboardKey.space) { _processSpaceKey(); } } @@ -937,15 +962,15 @@ class RowSelectionManager extends SelectionManagerBase { return; } - final addedItems = []; - final removeItems = []; + final List addedItems = []; + final List removeItems = []; if (dataGridSettings.onSelectionChanging != null || dataGridSettings.onSelectionChanged != null) { - addedItems.addAll(dataGridSettings.source._effectiveDataSource); + addedItems.addAll(dataGridSettings.source._effectiveRows); } if (_raiseSelectionChanging(oldItems: removeItems, newItems: addedItems)) { - for (final record in dataGridSettings.source._effectiveDataSource) { + for (final record in dataGridSettings.source._effectiveRows) { if (!_selectedRows.contains(record)) { final rowIndex = _SelectionHelper.resolveToRowIndex(dataGridSettings, record); @@ -961,7 +986,7 @@ class RowSelectionManager extends SelectionManagerBase { } dataGridSettings.controller.selectedRows - .addAll(dataGridSettings.source._effectiveDataSource); + .addAll(dataGridSettings.source._effectiveRows); _refreshSelection(); dataGridSettings.container._isDirty = true; notifyListeners(); diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selected_row_info.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selected_row_info.dart index cea597536..61e9a9b56 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selected_row_info.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selected_row_info.dart @@ -1,13 +1,13 @@ part of datagrid; class _SelectedRowCollection { - List selectedRow = []; + List selectedRow = []; - bool contains(Object rowData) => findRowData(rowData) != null; + bool contains(DataGridRow rowData) => findRowData(rowData) != null; - Object findRowData(Object rowData) { - final selectedItem = selectedRow.firstWhere((dataRow) => dataRow == rowData, - orElse: () => null); + DataGridRow? findRowData(Object rowData) { + final selectedItem = + selectedRow.firstWhereOrNull((dataRow) => dataRow == rowData); return selectedItem; } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_helper.dart index 93cda40ea..53c88bad5 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_helper.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_helper.dart @@ -2,27 +2,22 @@ part of datagrid; class _SelectionHelper { static int resolveToRowIndex( - _DataGridSettings dataGridSettings, Object record) { - if (dataGridSettings == null) { - return -1; - } - - var recordIndex = - dataGridSettings.source._effectiveDataSource.indexOf(record); + _DataGridSettings dataGridSettings, DataGridRow record) { + var recordIndex = dataGridSettings.source._effectiveRows.indexOf(record); recordIndex += _GridIndexResolver.resolveStartIndexBasedOnPosition(dataGridSettings); return recordIndex.isNegative ? -1 : recordIndex; } - static Object getRecord(_DataGridSettings dataGridSettings, int index) { + static DataGridRow? getRecord(_DataGridSettings dataGridSettings, int index) { final DataGridSource source = dataGridSettings.source; - if (source._effectiveDataSource.isEmpty || index < 0) { + if (source._effectiveRows.isEmpty || index < 0) { return null; } - final Object record = source._effectiveDataSource[index]; + final DataGridRow record = source._effectiveRows[index]; return record; } @@ -63,8 +58,8 @@ class _SelectionHelper { } static int getRecordsCount(_DataGridSettings dataGridSettings) { - if (dataGridSettings.source._effectiveDataSource.isNotEmpty) { - return dataGridSettings.source._effectiveDataSource.length; + if (dataGridSettings.source._effectiveRows.isNotEmpty) { + return dataGridSettings.source._effectiveRows.length; } else { return 0; } @@ -80,16 +75,22 @@ class _SelectionHelper { } static int getLastCellIndex(_DataGridSettings dataGridSettings) { - final lastColumn = dataGridSettings.columns - .lastWhere((col) => col.visible, orElse: () => null); - final lastIndex = dataGridSettings.columns.indexOf(lastColumn); + final lastColumn = + dataGridSettings.columns.lastWhereOrNull((col) => col.visible); + if (lastColumn == null) { + return -1; + } - return lastIndex; + return dataGridSettings.columns.indexOf(lastColumn); } static int getFirstCellIndex(_DataGridSettings dataGridSettings) { - final gridColumn = dataGridSettings.columns - .firstWhere((col) => col.visible, orElse: () => null); + final gridColumn = + dataGridSettings.columns.firstWhereOrNull((col) => col.visible); + if (gridColumn == null) { + return -1; + } + final firstIndex = dataGridSettings.columns.indexOf(gridColumn); if (firstIndex < 0) { return firstIndex; @@ -172,8 +173,16 @@ class _SelectionHelper { double verticalOffset = 0.0; final headerRowIndex = _GridIndexResolver.getHeaderIndex(dataGridSettings); rowIndex = rowIndex > headerRowIndex ? rowIndex : headerRowIndex + 1; - final _PixelScrollAxis _scrollRows = dataGridSettings.container.scrollRows; - verticalOffset = _scrollRows.distances.getCumulatedDistanceAt(rowIndex); + final _PixelScrollAxis _scrollRows = + dataGridSettings.container.scrollRows as _PixelScrollAxis; + verticalOffset = _scrollRows.distances!.getCumulatedDistanceAt(rowIndex); + final frozenRowsCount = headerRowIndex + dataGridSettings.frozenRowsCount; + if (frozenRowsCount > 0) { + for (int i = 0; i <= frozenRowsCount; i++) { + verticalOffset -= dataGridSettings.container.rowHeights[i]; + } + return verticalOffset; + } return verticalOffset -= dataGridSettings.container.rowHeights[0]; } @@ -186,9 +195,16 @@ class _SelectionHelper { ? firstVisibleColumnIndex : columnIndex; final _PixelScrollAxis _scrollColumns = - dataGridSettings.container.scrollColumns; + dataGridSettings.container.scrollColumns as _PixelScrollAxis; horizontalOffset = - _scrollColumns.distances.getCumulatedDistanceAt(columnIndex); + _scrollColumns.distances!.getCumulatedDistanceAt(columnIndex); + final frozenColumnsCount = dataGridSettings.frozenColumnsCount; + if (frozenColumnsCount > 0) { + for (int i = 0; i <= frozenColumnsCount; i++) { + horizontalOffset -= dataGridSettings.container.columnWidths[i]; + } + return horizontalOffset; + } return horizontalOffset; } @@ -246,190 +262,206 @@ class _SelectionHelper { } static void scrollInViewFromLeft(_DataGridSettings dataGridSettings, - {int nextCellIndex, bool needToScrollMaxExtent = false}) { - final horizontalController = dataGridSettings.horizontalController; - var measuredHorizontalOffset = 0.0; - - if (dataGridSettings.frozenColumnsCount > 0 && - _GridIndexResolver.getLastFrozenColumnIndex(dataGridSettings) + 1 == - nextCellIndex) { - measuredHorizontalOffset = - dataGridSettings.horizontalController.position.minScrollExtent; - } else if (needToScrollMaxExtent) { - measuredHorizontalOffset = horizontalController.position.maxScrollExtent; - } else { - if (dataGridSettings.currentCell.columnIndex != -1 && - nextCellIndex == dataGridSettings.currentCell.columnIndex + 1) { - final nextCellIndexHeight = dataGridSettings - .container.columnWidthsProvider - .getSize(nextCellIndex, 0); - final visibleInfoCollection = - _SfDataGridHelper.getVisibleLines(dataGridSettings); - final nextCellInfo = - visibleInfoCollection.getVisibleLineAtLineIndex(nextCellIndex); - measuredHorizontalOffset = nextCellInfo != null - ? dataGridSettings.textDirection == TextDirection.rtl - ? nextCellInfo.clippedSize - - (~nextCellInfo.clippedOrigin.toInt()) - : nextCellInfo.size - - (nextCellInfo.size - nextCellInfo.clippedCornerExtent) - : nextCellIndexHeight.first; + {int nextCellIndex = -1, bool needToScrollMaxExtent = false}) { + if (dataGridSettings.horizontalController != null) { + final horizontalController = dataGridSettings.horizontalController!; + var measuredHorizontalOffset = 0.0; + + if (dataGridSettings.frozenColumnsCount > 0 && + _GridIndexResolver.getLastFrozenColumnIndex(dataGridSettings) + 1 == + nextCellIndex) { measuredHorizontalOffset = - dataGridSettings.horizontalController.offset + - measuredHorizontalOffset; + horizontalController.position.minScrollExtent; + } else if (needToScrollMaxExtent) { + measuredHorizontalOffset = + horizontalController.position.maxScrollExtent; } else { - final visibleInfoCollection = - _SfDataGridHelper.getVisibleLines(dataGridSettings); - final firstBodyVisibleLineIndex = visibleInfoCollection - .firstBodyVisibleIndex < - visibleInfoCollection.length - ? visibleInfoCollection[visibleInfoCollection.firstBodyVisibleIndex] - .lineIndex - : 0; - if (nextCellIndex < firstBodyVisibleLineIndex) { - scrollInViewFromRight(dataGridSettings, - previousCellIndex: nextCellIndex, needToScrollToMinExtent: false); - } else { + if (dataGridSettings.currentCell.columnIndex != -1 && + nextCellIndex == dataGridSettings.currentCell.columnIndex + 1) { + final nextCellIndexHeight = dataGridSettings + .container.columnWidthsProvider + .getSize(nextCellIndex, 0); + final visibleInfoCollection = + _SfDataGridHelper.getVisibleLines(dataGridSettings); + final nextCellInfo = + visibleInfoCollection.getVisibleLineAtLineIndex(nextCellIndex); + measuredHorizontalOffset = nextCellInfo != null + ? dataGridSettings.textDirection == TextDirection.rtl + ? nextCellInfo.clippedSize - + (~nextCellInfo.clippedOrigin.toInt()) + : nextCellInfo.size - + (nextCellInfo.size - nextCellInfo.clippedCornerExtent) + : nextCellIndexHeight.first; measuredHorizontalOffset = - dataGridSettings.horizontalController.offset + - _SelectionHelper.getHorizontalCumulativeDistance( - dataGridSettings, nextCellIndex); + horizontalController.offset + measuredHorizontalOffset; + } else { + final visibleInfoCollection = + _SfDataGridHelper.getVisibleLines(dataGridSettings); + final firstBodyVisibleLineIndex = + visibleInfoCollection.firstBodyVisibleIndex < + visibleInfoCollection.length + ? visibleInfoCollection[ + visibleInfoCollection.firstBodyVisibleIndex] + .lineIndex + : 0; + if (nextCellIndex < firstBodyVisibleLineIndex) { + return scrollInViewFromRight(dataGridSettings, + previousCellIndex: nextCellIndex, + needToScrollToMinExtent: false); + } else { + measuredHorizontalOffset = horizontalController.offset + + _SelectionHelper.getHorizontalCumulativeDistance( + dataGridSettings, nextCellIndex); + } } } - } - _SfDataGridHelper.scrollHorizontal( - dataGridSettings, measuredHorizontalOffset); + _SfDataGridHelper.scrollHorizontal( + dataGridSettings, measuredHorizontalOffset); + } } static void scrollInViewFromRight(_DataGridSettings dataGridSettings, - {int previousCellIndex, bool needToScrollToMinExtent = false}) { + {int previousCellIndex = -1, bool needToScrollToMinExtent = false}) { var measuredHorizontalOffset = 0.0; - if (dataGridSettings.footerFrozenColumnsCount > 0 && - _GridIndexResolver.getStartFooterFrozenColumnIndex(dataGridSettings) - - 1 == - previousCellIndex) { - measuredHorizontalOffset = - dataGridSettings.horizontalController.position.maxScrollExtent; - } else if (needToScrollToMinExtent) { - measuredHorizontalOffset = - dataGridSettings.horizontalController.position.minScrollExtent; - } else { - if (dataGridSettings.currentCell.columnIndex != -1 && - previousCellIndex == dataGridSettings.currentCell.columnIndex - 1) { - final previousCellIndexWidth = dataGridSettings - .container.columnWidthsProvider - .getSize(previousCellIndex, 0); - final visibleInfoCollection = - _SfDataGridHelper.getVisibleLines(dataGridSettings); - final previousCellInfo = - visibleInfoCollection.getVisibleLineAtLineIndex(previousCellIndex); - measuredHorizontalOffset = previousCellInfo != null - ? previousCellInfo.size - - (previousCellInfo.clippedSize - - previousCellInfo.clippedCornerExtent) - : previousCellIndexWidth.first; + if (dataGridSettings.horizontalController != null) { + final ScrollController horizontalController = + dataGridSettings.horizontalController!; + + if (dataGridSettings.footerFrozenColumnsCount > 0 && + _GridIndexResolver.getStartFooterFrozenColumnIndex(dataGridSettings) - + 1 == + previousCellIndex) { measuredHorizontalOffset = - dataGridSettings.horizontalController.offset - - measuredHorizontalOffset; - } else { + horizontalController.position.maxScrollExtent; + } else if (needToScrollToMinExtent) { measuredHorizontalOffset = - dataGridSettings.horizontalController.offset - - (dataGridSettings.horizontalController.offset - - _SelectionHelper.getHorizontalCumulativeDistance( - dataGridSettings, previousCellIndex)); + horizontalController.position.minScrollExtent; + } else { + if (dataGridSettings.currentCell.columnIndex != -1 && + previousCellIndex == dataGridSettings.currentCell.columnIndex - 1) { + final previousCellIndexWidth = dataGridSettings + .container.columnWidthsProvider + .getSize(previousCellIndex, 0); + final visibleInfoCollection = + _SfDataGridHelper.getVisibleLines(dataGridSettings); + final previousCellInfo = visibleInfoCollection + .getVisibleLineAtLineIndex(previousCellIndex); + measuredHorizontalOffset = previousCellInfo != null + ? previousCellInfo.size - + (previousCellInfo.clippedSize - + previousCellInfo.clippedCornerExtent) + : previousCellIndexWidth.first; + measuredHorizontalOffset = + horizontalController.offset - measuredHorizontalOffset; + } else { + measuredHorizontalOffset = horizontalController.offset - + (horizontalController.offset - + _SelectionHelper.getHorizontalCumulativeDistance( + dataGridSettings, previousCellIndex)); + } } - } - _SfDataGridHelper.scrollHorizontal( - dataGridSettings, measuredHorizontalOffset); + _SfDataGridHelper.scrollHorizontal( + dataGridSettings, measuredHorizontalOffset); + } } static void scrollInViewFromTop(_DataGridSettings dataGridSettings, - {int nextRowIndex, bool needToScrollToMaxExtent = false}) { + {int nextRowIndex = -1, bool needToScrollToMaxExtent = false}) { var measuredVerticalOffset = 0.0; - if (dataGridSettings.frozenRowsCount > 0 && - _GridIndexResolver.getLastFrozenRowIndex(dataGridSettings) + 1 == - nextRowIndex) { - measuredVerticalOffset = - dataGridSettings.verticalController.position.minScrollExtent; - } else if (needToScrollToMaxExtent) { - measuredVerticalOffset = - dataGridSettings.verticalController.position.maxScrollExtent; - } else { - if (dataGridSettings.currentCell.rowIndex != -1 && - nextRowIndex == dataGridSettings.currentCell.rowIndex + 1) { - final verticalController = dataGridSettings.verticalController; - final nextRowIndexHeight = dataGridSettings.container.rowHeightsProvider - .getSize(nextRowIndex, 0); - final nextRowInfo = dataGridSettings.container.scrollRows - .getVisibleLineAtLineIndex(nextRowIndex); - measuredVerticalOffset = nextRowInfo != null - ? nextRowInfo.size - - (nextRowInfo.size - nextRowInfo.clippedCornerExtent) - : nextRowIndexHeight.first; - measuredVerticalOffset = - verticalController.offset + measuredVerticalOffset; + if (dataGridSettings.verticalController != null) { + final ScrollController verticalController = + dataGridSettings.verticalController!; + + if (dataGridSettings.frozenRowsCount > 0 && + _GridIndexResolver.getLastFrozenRowIndex(dataGridSettings) + 1 == + nextRowIndex) { + measuredVerticalOffset = verticalController.position.minScrollExtent; + } else if (needToScrollToMaxExtent) { + measuredVerticalOffset = verticalController.position.maxScrollExtent; } else { - final visibleInfoCollection = - dataGridSettings.container.scrollRows.getVisibleLines(); - final firstBodyVisibleLineIndex = visibleInfoCollection - .firstBodyVisibleIndex < - visibleInfoCollection.length - ? visibleInfoCollection[visibleInfoCollection.firstBodyVisibleIndex] - .lineIndex - : 0; - if (nextRowIndex < firstBodyVisibleLineIndex) { - scrollInViewFromDown(dataGridSettings, - previousRowIndex: nextRowIndex, needToScrollToMinExtent: false); - } else { + if (dataGridSettings.currentCell.rowIndex != -1 && + nextRowIndex == dataGridSettings.currentCell.rowIndex + 1) { + final nextRowIndexHeight = dataGridSettings + .container.rowHeightsProvider + .getSize(nextRowIndex, 0); + final nextRowInfo = dataGridSettings.container.scrollRows + .getVisibleLineAtLineIndex(nextRowIndex); + measuredVerticalOffset = nextRowInfo != null + ? nextRowInfo.size - + (nextRowInfo.size - nextRowInfo.clippedCornerExtent) + : nextRowIndexHeight.first; measuredVerticalOffset = - _SelectionHelper.getVerticalCumulativeDistance( - dataGridSettings, nextRowIndex); + verticalController.offset + measuredVerticalOffset; + } else { + final visibleInfoCollection = + dataGridSettings.container.scrollRows.getVisibleLines(); + final firstBodyVisibleLineIndex = + visibleInfoCollection.firstBodyVisibleIndex < + visibleInfoCollection.length + ? visibleInfoCollection[ + visibleInfoCollection.firstBodyVisibleIndex] + .lineIndex + : 0; + if (nextRowIndex < firstBodyVisibleLineIndex) { + return scrollInViewFromDown(dataGridSettings, + previousRowIndex: nextRowIndex, needToScrollToMinExtent: false); + } else { + measuredVerticalOffset = + _SelectionHelper.getVerticalCumulativeDistance( + dataGridSettings, nextRowIndex); + } } } - } - _SfDataGridHelper.scrollVertical(dataGridSettings, measuredVerticalOffset); + _SfDataGridHelper.scrollVertical( + dataGridSettings, measuredVerticalOffset); + } } static void scrollInViewFromDown(_DataGridSettings dataGridSettings, - {int previousRowIndex, bool needToScrollToMinExtent = false}) { + {int previousRowIndex = -1, bool needToScrollToMinExtent = false}) { var measuredVerticalOffset = 0.0; - if (dataGridSettings.footerFrozenRowsCount > 0 && - _GridIndexResolver.getStartFooterFrozenRowIndex(dataGridSettings) - 1 == - previousRowIndex) { - measuredVerticalOffset = - dataGridSettings.verticalController.position.maxScrollExtent; - } else if (needToScrollToMinExtent) { - measuredVerticalOffset = - dataGridSettings.verticalController.position.minScrollExtent; - } else { - if (dataGridSettings.currentCell.rowIndex != -1 && - previousRowIndex == dataGridSettings.currentCell.rowIndex - 1) { - final previousRowIndexHeight = dataGridSettings - .container.rowHeightsProvider - .getSize(previousRowIndex, 0); - final previousRowInfo = dataGridSettings.container.scrollRows - .getVisibleLineAtLineIndex(previousRowIndex); - measuredVerticalOffset = previousRowInfo != null - ? previousRowInfo.size - - (previousRowInfo.clippedSize - - previousRowInfo.clippedCornerExtent) - : previousRowIndexHeight.first; - measuredVerticalOffset = - dataGridSettings.verticalController.offset - measuredVerticalOffset; + if (dataGridSettings.verticalController != null) { + final ScrollController verticalController = + dataGridSettings.verticalController!; + + if (dataGridSettings.footerFrozenRowsCount > 0 && + _GridIndexResolver.getStartFooterFrozenRowIndex(dataGridSettings) - + 1 == + previousRowIndex) { + measuredVerticalOffset = verticalController.position.maxScrollExtent; + } else if (needToScrollToMinExtent) { + measuredVerticalOffset = verticalController.position.minScrollExtent; } else { - measuredVerticalOffset = dataGridSettings.verticalController.offset - - (dataGridSettings.verticalController.offset - - _SelectionHelper.getVerticalCumulativeDistance( - dataGridSettings, previousRowIndex)); + if (dataGridSettings.currentCell.rowIndex != -1 && + previousRowIndex == dataGridSettings.currentCell.rowIndex - 1) { + final previousRowIndexHeight = dataGridSettings + .container.rowHeightsProvider + .getSize(previousRowIndex, 0); + final previousRowInfo = dataGridSettings.container.scrollRows + .getVisibleLineAtLineIndex(previousRowIndex); + measuredVerticalOffset = previousRowInfo != null + ? previousRowInfo.size - + (previousRowInfo.clippedSize - + previousRowInfo.clippedCornerExtent) + : previousRowIndexHeight.first; + measuredVerticalOffset = + verticalController.offset - measuredVerticalOffset; + } else { + measuredVerticalOffset = verticalController.offset - + (verticalController.offset - + _SelectionHelper.getVerticalCumulativeDistance( + dataGridSettings, previousRowIndex)); + } } - } - _SfDataGridHelper.scrollVertical(dataGridSettings, measuredVerticalOffset); + _SfDataGridHelper.scrollVertical( + dataGridSettings, measuredVerticalOffset); + } } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_manager_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_manager_base.dart index b5299ff14..9f49c8494 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_manager_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_manager_base.dart @@ -3,14 +3,13 @@ part of datagrid; /// Provides the base functionalities to process the selection in [SfDataGrid]. class SelectionManagerBase extends ChangeNotifier { /// Creates the [SelectionManagerBase]. - SelectionManagerBase({_DataGridStateDetails dataGridStateDetails}) { - _dataGridStateDetails = dataGridStateDetails; + SelectionManagerBase() { _selectedRows = _SelectedRowCollection()..selectedRow = []; } - _SelectedRowCollection _selectedRows; + late _SelectedRowCollection _selectedRows; - _DataGridStateDetails _dataGridStateDetails; + late _DataGridStateDetails _dataGridStateDetails; /// Processes the selection operation when tap a cell. void handleTap(RowColumnIndex rowColumnIndex) {} @@ -40,27 +39,36 @@ class SelectionManagerBase extends ChangeNotifier { void _onRowColumnChanged(int recordLength, int columnLength) {} + void _handleSelectionPropertyChanged( + {RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false}) {} + void _updateSelectionController( {bool isSelectionModeChanged = false, bool isNavigationModeChanged = false, bool isDataSourceChanged = false}) {} - bool _raiseSelectionChanging({List oldItems, List newItems}) { + bool _raiseSelectionChanging( + {List oldItems = const [], + List newItems = const []}) { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); if (dataGridSettings.onSelectionChanging == null) { return true; } - return dataGridSettings.onSelectionChanging(newItems, oldItems) ?? true; + return dataGridSettings.onSelectionChanging!(newItems, oldItems); } - void _raiseSelectionChanged({List oldItems, List newItems}) { + void _raiseSelectionChanged( + {List oldItems = const [], + List newItems = const []}) { final _DataGridSettings dataGridSettings = _dataGridStateDetails(); if (dataGridSettings.onSelectionChanged == null) { return; } - dataGridSettings.onSelectionChanged(newItems, oldItems); + dataGridSettings.onSelectionChanged!(newItems, oldItems); } int _getPreviousColumnIndex( @@ -105,7 +113,7 @@ class SelectionManagerBase extends ChangeNotifier { return previousIndex; } - GridColumn _getNextGridColumn( + GridColumn? _getNextGridColumn( _DataGridSettings dataGridSettings, int columnIndex, bool moveToRight) { final resolvedIndex = _GridIndexResolver.resolveToGridVisibleColumnIndex( dataGridSettings, columnIndex); @@ -113,10 +121,8 @@ class SelectionManagerBase extends ChangeNotifier { return null; } - var gridColumn = dataGridSettings.columns[resolvedIndex]; - if (gridColumn == null || - !gridColumn.visible || - gridColumn._actualWidth == 0.0) { + GridColumn? gridColumn = dataGridSettings.columns[resolvedIndex]; + if (!gridColumn.visible || gridColumn._actualWidth == 0.0) { gridColumn = _getNextGridColumn(dataGridSettings, moveToRight ? columnIndex + 1 : columnIndex - 1, moveToRight); } @@ -172,15 +178,4 @@ class SelectionManagerBase extends ChangeNotifier { return nextIndex; } - - @override - bool operator ==(Object other) => - identical(this, other) || - other is SelectionManagerBase && runtimeType == other.runtimeType; - - @override - int get hashCode { - final List _hashList = [this]; - return hashList(_hashList); - } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart b/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart index 7e1f896cc..bdf0e4fcf 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart @@ -1,14 +1,15 @@ part of datagrid; /// Signature for the [SfDataPager.pageItemBuilder] callback. -typedef DataPagerItemBuilderCallback = Widget Function(String text); +typedef DataPagerItemBuilderCallback = Widget? Function(String text); +/// Signature for the `_DataPagerChangeNotifier` listener. typedef _DataPagerControlListener = void Function({String property}); -/// Signature for the builder callback used by [SfDataGrid.onPageNavigationStart]. +/// Signature for the builder callback used by [SfDataPager.onPageNavigationStart]. typedef PageNavigationStart = void Function(int pageIndex); -/// Signature for the [SfDataGrid.onPageNavigationEnd] callback. +/// Signature for the [SfDataPager.onPageNavigationEnd] callback. typedef PageNavigationEnd = void Function(int pageIndex); // Its used to suspend the data pager update more than once when using the @@ -25,8 +26,8 @@ bool _suspendDataPagerUpdate = false; /// final List paginatedDataTable = []; ///``` /// -/// The following code example shows how to  initialize the [SfDatapager] -/// with [SfDatagrid] +/// The following code example shows how to  initialize the [SfDataPager] +/// with [SfDataGrid] /// /// ```dart /// @@ -49,7 +50,7 @@ bool _suspendDataPagerUpdate = false; ///            ], ///          ), ///          SfDataPager( -///            rowsPerPage: 10, +///            pageCount: paginatedDataTable.length / rowsPerPage, ///            visibleItemsCount: 5, ///            delegate: dataGridSource, ///     controller:dataPagerController @@ -72,6 +73,10 @@ bool _suspendDataPagerUpdate = false; /// /// ```dart /// class  PaginatedDataGridSource extends DataGridSource { +/// final List_paginatedData= []; +/// +/// int StartRowIndex= 0, endRowIndex = 0, rowsPerPage = 20; +/// ///  @override ///  List get dataSource => _paginatedData; /// @@ -93,13 +98,11 @@ bool _suspendDataPagerUpdate = false; ///  } /// ///  @override -///  int get rowCount => dataSource.isEmpty ? 0 : _employeeData.length; -/// -///  @override -///  Future handlePageChange(int oldPageIndex, int newPageIndex, -///      int startRowIndex, int rowsPerPage) async { -///    _paginatedData = _employeeData -///        .getRange(startRowIndex, startRowIndex + rowsPerPage) +///  Future handlePageChange(int oldPageIndex, int newPageIndex) async { +/// startRowIndex = newPageIndex * rowsPerPage; +/// endRowIndex = startRowsIndex + rowsPerPage; +///    _paginatedData =  paginatedDataTable +///        .getRange(startRowIndex, endRowIndex) ///        .toList(growable: false); ///    notifyDataSourceListeners(); ///    return true; @@ -107,6 +110,7 @@ bool _suspendDataPagerUpdate = false; /// ///``` /// See also: +/// /// [SfDataPager] - which is the widget this object controls. class DataPagerController extends _DataPagerChangeNotifier { /// An index of the currently selected page. @@ -121,19 +125,6 @@ class DataPagerController extends _DataPagerChangeNotifier { _notifyDataPagerListeners('selectedPageIndex'); } - /// The total number of pages in the data pager. - int get pageCount => _pageCount; - int _pageCount; - - /// The total number of pages in the data pager. - set pageCount(int newPageCount) { - if (newPageCount == _pageCount) { - return; - } - _pageCount = newPageCount; - _notifyDataPagerListeners('pageCount'); - } - /// Moves the currently selected page to first page. void firstPage() { _notifyDataPagerListeners('first'); @@ -144,29 +135,15 @@ class DataPagerController extends _DataPagerChangeNotifier { _notifyDataPagerListeners('last'); } - /// Moves the currently selected page to last page. + /// Moves the currently selected page to next page. void nextPage() { _notifyDataPagerListeners('next'); } - /// Moves the currently selected page to last page. + /// Moves the currently selected page to previous page. void previousPage() { _notifyDataPagerListeners('previous'); } - - @override - bool operator ==(Object other) => - identical(this, other) || - other is DataPagerController && runtimeType == other.runtimeType; - - @override - int get hashCode { - final List _hashList = [ - selectedPageIndex, - pageCount, - ]; - return hashList(_hashList); - } } /// A delegate that provides the row count details and method to listen the @@ -182,14 +159,10 @@ class DataPagerController extends _DataPagerChangeNotifier { /// /// ```dart /// class  PaginatedDataGridSource extends DataPagerDelegate{ -/// @override -///  int get rowCount => 1000; -/// ///  @override -///  Future handlePageChange(int oldPageIndex, int newPageIndex, -///      int startRowIndex, int rowsPerPage) async { -///    _paginatedData = _employeeData -///        .getRange(startRowIndex, startRowIndex + rowsPerPage.toInt()) +///  Future handlePageChange(int oldPageIndex, int newPageIndex) async { +///    _paginatedData =  paginatedDataTable +///        .getRange(startRowIndex, endRowIndex ) ///        .toList(growable: false); ///    notifyListeners(); ///    return true; @@ -197,24 +170,18 @@ class DataPagerController extends _DataPagerChangeNotifier { /// } /// ``` class DataPagerDelegate { - /// The total number of rows. - int get rowCount => _rowCount; - int _rowCount; - /// Called when the page is navigated. /// /// Return true, if the navigation should be performed. Otherwise, return /// false to disable the navigation for specific page index. - Future handlePageChange(int oldPageIndex, int newPageIndex, - int startRowIndex, int rowsPerPage) async { + Future handlePageChange(int oldPageIndex, int newPageIndex) async { return true; } } /// A widget that provides the paging functionality. /// -/// A [SfDataPager] shows [rowsPerPage] rows of data per page and provides -/// controls for showing other pages. +/// A [SfDataPager] shows [pageCount] number of pages required to display. /// /// Data is read lazily from a [DataPagerDelegate]. The widget is presented as /// card. @@ -232,81 +199,78 @@ class DataPagerDelegate { /// with [SfDataGrid] /// /// ```dart -///  @override -///  Widget build(BuildContext context) { -///    return Scaffold( -///      appBar: AppBar( -///       title: const Text('Syncfusion Flutter DataGrid'), -///      ), -///      body: Column( -///        children: [ -///          SfDataGrid( -///            source: _ dataGridSource, -///            columns: [ -///              GridNumericColumn(mappingName: 'id', headerText: 'ID'), -///              GridTextColumn(mappingName: 'name', headerText: 'Name'), -///              GridTextColumn(mappingName: 'designation', headerText: 'Designation'), -///              GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), -///            ], -///          ), -///          SfDataPager( -///            rowsPerPage: 10, -///            visibleItemsCount: 5, -///            delegate: dataGridSource, -///          ), -///       ], -///      ), -///    ); -///  } +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar( +/// title: const Text('Syncfusion Flutter DataGrid'), +/// ), +/// body: Column( +/// children: [ +/// SfDataGrid(source: dataGridSource, columns: [ +/// GridTextColumn(columnName: 'id', label: Text('ID')), +/// GridTextColumn(columnName: 'name', label: Text('Name')), +/// GridTextColumn(columnName: 'designation', label: Text('Designation')), +/// GridTextColumn(columnName: 'salary', label: Text('Salary')), +/// ]), +/// SfDataPager( +/// pageCount: paginatedDataTable.length / rowsPerPage, +/// visibleItemsCount: 5, +/// delegate: dataGridSource, +/// ), +/// ], +/// ), +/// ); +/// } /// ``` /// /// The following code example shows how to initialize the [DataGridSource] /// to [SfDataGrid] /// /// ```dart -/// class  PaginatedDataGridSource extends DataGridSource { -///  @override -///  List get dataSource => _paginatedData; /// -///  @override -/// Object getValue(Employee employee, String columnName) { -///    switch (columnName) { -///      case 'designation': -///        return employee.designation; -///        break; -///      case 'salary': -///        return employee.salary; -///        break; -///      case 'employeeName': -///        return employee.employeeName; -///      default: -///        return ''; -///        break; -///    } -///  } +/// class PaginatedDataGridSource extends DataGridSource { +/// @override +/// List get rows => _paginatedData +/// .map((dataRow) => DataGridRow(cells: [ +/// DataGridCell(columnName: 'id', value: dataRow.id), +/// DataGridCell(columnName: 'name', value: dataRow.name), +/// DataGridCell( +/// columnName: 'designation', value: dataRow.designation), +/// DataGridCell(columnName: 'salary', value: dataRow.salary), +/// ])) +/// .toList(); /// -///  @override -///  int get rowCount => dataSource.isEmpty ? 0 : _employeeData.length; +/// @override +/// DataGridRowAdapter buildRow(DataGridRow row) { +/// return DataGridRowAdapter( +/// cells: row.getCells().map((dataCell) { +/// return Text(dataCell.value.toString()); +/// }).toList()); +/// } /// -///  @override -///  Future handlePageChange(int oldPageIndex, int newPageIndex, -///      int startRowIndex, int rowsPerPage) async { -///    _paginatedData = _employeeData -///        .getRange(startRowIndex, startRowIndex + rowsPerPage.toInt()) +/// @override +///  Future handlePageChange(int oldPageIndex, int newPageIndex) async { +/// startRowIndex = newPageIndex * rowsPerPage; +/// endRowIndex = startRowIndex + rowsPerPage; +///    _paginatedData =  paginatedDataTable +///        .getRange(startRowIndex, endRowIndex) ///        .toList(growable: false); ///    notifyDataSourceListeners(); ///    return true; ///  } +/// } +/// /// ``` class SfDataPager extends StatefulWidget { /// Creates a widget describing a datapager. /// - /// The [rowsPerPage] and [delegate] argument must be defined and must not + /// The [pageCount] and [delegate] argument must be defined and must not /// be null. const SfDataPager( - {@required this.rowsPerPage, - @required this.delegate, - Key key, + {required this.pageCount, + required this.delegate, + Key? key, this.direction = Axis.horizontal, this.visibleItemsCount = 5, this.initialPageIndex = 0, @@ -314,13 +278,14 @@ class SfDataPager extends StatefulWidget { this.onPageNavigationStart, this.onPageNavigationEnd, this.controller}) - : assert(rowsPerPage != null), - assert(delegate != null); + : assert(pageCount > 0); - /// The number of rows to show on each page. + /// The number of pages required to display in [SfDataPager]. + /// Calculate the number of pages by dividing the total number of items + /// available by number of items displayed in a page. /// /// This must be non null. - final int rowsPerPage; + final double pageCount; /// The maximum number of Items to show in view. final int visibleItemsCount; @@ -337,7 +302,7 @@ class SfDataPager extends StatefulWidget { /// /// If you write the delegate from [DataPagerDelegate] with [ChangeNotifier], /// the [SfDataPager] will automatically listen the listener when call the - /// [notifyListeners] and refresh the UI based on the current configuration. + /// `notifyListeners` and refresh the UI based on the current configuration. /// /// This must be non-null final DataPagerDelegate delegate; @@ -345,23 +310,23 @@ class SfDataPager extends StatefulWidget { /// The page to show when first creating the [SfDataPager]. /// /// You can navigate to specific page using - /// [DataGridController.selectedPageIndex] at run time. + /// [DataPagerController.selectedPageIndex] at run time. final int initialPageIndex; /// A builder callback that builds the widget for the specific page Item. - final DataPagerItemBuilderCallback pageItemBuilder; + final DataPagerItemBuilderCallback? pageItemBuilder; /// An object that can be used to control the position to which this page is /// scrolled. - final DataPagerController controller; + final DataPagerController? controller; /// Called when page is being navigated. /// Typically you can use this callback to call the setState() to display the loading indicator while retrieving the rows from services. - final PageNavigationStart onPageNavigationStart; + final PageNavigationStart? onPageNavigationStart; /// Called when page is successfully navigated. /// Typically you can use this callback to call the setState() to hide the loading indicator once data is successfully retrieved from services. - final PageNavigationEnd onPageNavigationEnd; + final PageNavigationEnd? onPageNavigationEnd; @override _SfDataPagerState createState() => _SfDataPagerState(); @@ -369,10 +334,10 @@ class SfDataPager extends StatefulWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add(IntProperty('rowsPerPage', rowsPerPage, - defaultValue: 0.0, showName: true)); properties.add(IntProperty('visibleItemsCount', visibleItemsCount, defaultValue: 5, showName: true)); + properties.add( + DoubleProperty('pageCount', pageCount, showName: true, ifNull: 'null')); properties.add(IntProperty('initialPageIndex', initialPageIndex, defaultValue: 0.0, showName: true)); properties.add(DiagnosticsProperty('direction', direction, @@ -382,7 +347,7 @@ class SfDataPager extends StatefulWidget { properties.add(ObjectFlagProperty( 'controller', controller, showName: true, ifNull: 'null')); - properties.add(ObjectFlagProperty>( + properties.add(ObjectFlagProperty( 'pageItemBuilder', pageItemBuilder, showName: true, ifNull: 'null')); } @@ -397,11 +362,12 @@ class _SfDataPagerState extends State { static const Size _defaultPagerLabelDimension = Size(200.0, 50.0); static const double _kMobileViewWidthOnWeb = 767.0; - _DataPagerItemGenerator _itemGenerator; - ScrollController _scrollController; - DataPagerController _controller; - SfDataPagerThemeData _dataPagerThemeData; - SfLocalizations _localization; + late _DataPagerItemGenerator _itemGenerator; + ScrollController? _scrollController; + DataPagerController? _controller; + SfDataPagerThemeData? _dataPagerThemeData; + ImageConfiguration? _imageConfiguration; + late SfLocalizations _localization; int _pageCount = 0; double _scrollViewPortSize = 0.0; @@ -413,13 +379,12 @@ class _SfDataPagerState extends State { double _width = 0.0; double _height = 0.0; TextDirection _textDirection = TextDirection.ltr; - ImageConfiguration _imageConfiguration; - Orientation _deviceOrientation; + Orientation _deviceOrientation = Orientation.landscape; bool _isDirty = false; bool _isOrientationChanged = false; bool _isPregenerateItems = false; - bool _isDesktop; + bool _isDesktop = false; bool get _isRTL => _textDirection == TextDirection.rtl; @@ -443,8 +408,8 @@ class _SfDataPagerState extends State { _isDirty = true; } - set dataPagerThemeData(SfDataPagerThemeData newThemeData) { - if (_dataPagerThemeData == newThemeData) { + set dataPagerThemeData(SfDataPagerThemeData? newThemeData) { + if (newThemeData == null || _dataPagerThemeData == newThemeData) { return; } _dataPagerThemeData = newThemeData; @@ -464,12 +429,12 @@ class _SfDataPagerState extends State { } void _onInitialDataPagerLoaded() { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + WidgetsBinding.instance?.addPostFrameCallback((timeStamp) { if (widget.initialPageIndex > 0) { _handleDataPagerControlPropertyChanged(property: 'initialPageIndex'); } else { - _currentPageIndex = widget.initialPageIndex ?? 0.0; - _controller._selectedPageIndex = _currentPageIndex; + _currentPageIndex = widget.initialPageIndex; + _controller!._selectedPageIndex = _currentPageIndex; _handlePageItemTapped(_currentPageIndex); } }); @@ -520,7 +485,8 @@ class _SfDataPagerState extends State { _suspendDataPagerUpdate = false; } - Future _handleDataPagerControlPropertyChanged({String property}) async { + Future _handleDataPagerControlPropertyChanged( + {String? property}) async { _suspendDataPagerUpdate = true; switch (property) { case 'first': @@ -530,7 +496,7 @@ class _SfDataPagerState extends State { final bool canChangePage = await _canChangePage(0); if (canChangePage) { - await _scrollTo(_scrollController.position.minScrollExtent); + await _scrollTo(_scrollController!.position.minScrollExtent); _setCurrentPageIndex(0); } _raisePageNavigationEnd(canChangePage ? 0 : _currentPageIndex); @@ -542,7 +508,7 @@ class _SfDataPagerState extends State { final bool canChangePage = await _canChangePage(_lastPageIndex); if (canChangePage) { - await _scrollTo(_scrollController.position.maxScrollExtent); + await _scrollTo(_scrollController!.position.maxScrollExtent); _setCurrentPageIndex(_lastPageIndex); } _raisePageNavigationEnd( @@ -580,8 +546,7 @@ class _SfDataPagerState extends State { canChangePage ? nextPageIndex : _currentPageIndex); break; case 'initialPageIndex': - if (widget.initialPageIndex != null && - widget.initialPageIndex.isNegative) { + if (widget.initialPageIndex.isNegative) { return; } @@ -600,7 +565,7 @@ class _SfDataPagerState extends State { _handleScrollPositionChanged(); break; case 'selectedPageIndex': - final selectedPageIndex = _controller.selectedPageIndex; + final selectedPageIndex = _controller!.selectedPageIndex; if (selectedPageIndex < 0 || selectedPageIndex > _lastPageIndex || selectedPageIndex == _currentPageIndex) { @@ -625,12 +590,8 @@ class _SfDataPagerState extends State { Future _canChangePage(int index) async { _raisePageNavigationStart(index); - final bool canHandle = await widget.delegate.handlePageChange( - _currentPageIndex, - index, - (index * widget.rowsPerPage), - widget.rowsPerPage) ?? - true; + final bool canHandle = + await widget.delegate.handlePageChange(_currentPageIndex, index); return canHandle; } @@ -640,7 +601,7 @@ class _SfDataPagerState extends State { return null; } - widget.onPageNavigationStart(pageIndex); + widget.onPageNavigationStart!(pageIndex); } void _raisePageNavigationEnd(int pageIndex) { @@ -648,7 +609,7 @@ class _SfDataPagerState extends State { return null; } - widget.onPageNavigationEnd(pageIndex); + widget.onPageNavigationEnd!(pageIndex); } // ScrollController helpers @@ -669,7 +630,7 @@ class _SfDataPagerState extends State { double getScrollOffset(int index) { final double origin = _getCumulativeSize(index); - final double scrollOffset = _scrollController.offset; + final double scrollOffset = _scrollController!.offset; final double corner = origin + _getButtonSize(); final double currentViewSize = scrollOffset + _scrollViewPortSize; double offset = 0; @@ -696,7 +657,7 @@ class _SfDataPagerState extends State { } Future _scrollTo(double offset, {bool canUpdate = false}) async { - if (offset == _scrollController.offset && !canUpdate) { + if (offset == _scrollController!.offset && !canUpdate) { setState(() { _isDirty = true; }); @@ -704,7 +665,7 @@ class _SfDataPagerState extends State { } Future.delayed(Duration.zero, () { - _scrollController.animateTo(offset, + _scrollController!.animateTo(offset, duration: const Duration(milliseconds: 250), curve: Curves.fastOutSlowIn); }); @@ -760,12 +721,12 @@ class _SfDataPagerState extends State { key: ValueKey(type), size: 20, color: visible - ? _dataPagerThemeData.brightness == Brightness.light - ? _dataPagerThemeData.disabledItemTextStyle.color - .withOpacity(0.54) - : _dataPagerThemeData.disabledItemTextStyle.color - .withOpacity(0.65) - : _dataPagerThemeData.disabledItemTextStyle.color); + ? _dataPagerThemeData!.brightness == Brightness.light + ? _dataPagerThemeData!.disabledItemTextStyle.color + ?.withOpacity(0.54) + : _dataPagerThemeData!.disabledItemTextStyle.color + ?.withOpacity(0.65) + : _dataPagerThemeData!.disabledItemTextStyle.color); } if (widget.direction == Axis.vertical) { @@ -825,7 +786,7 @@ class _SfDataPagerState extends State { void _setCurrentPageIndex(int index) { _currentPageIndex = index; - _controller._selectedPageIndex = index; + _controller!._selectedPageIndex = index; } bool isNavigatorItemVisible(String type) { @@ -862,19 +823,19 @@ class _SfDataPagerState extends State { } void _ensureItems() { - if (!_scrollController.hasClients) { + if (!_scrollController!.hasClients) { return; } final double buttonSize = _getButtonSize(); final int startIndex = - _scrollController.offset <= _scrollController.position.minScrollExtent + _scrollController!.offset <= _scrollController!.position.minScrollExtent ? 0 - : _scrollController.offset ~/ buttonSize; + : _scrollController!.offset ~/ buttonSize; final int endIndex = - _scrollController.offset >= _scrollController.position.maxScrollExtent + _scrollController!.offset >= _scrollController!.position.maxScrollExtent ? _lastPageIndex - : (_scrollController.offset + _scrollViewPortSize) ~/ buttonSize; + : (_scrollController!.offset + _scrollViewPortSize) ~/ buttonSize; _itemGenerator.ensureItems(startIndex, endIndex); } @@ -906,52 +867,52 @@ class _SfDataPagerState extends State { // Item builder Widget _buildDataPagerItem( - {_ScrollableDataPagerItem element, String type, IconData iconData}) { + {_ScrollableDataPagerItem? element, String? type, IconData? iconData}) { final ThemeData _flutterTheme = Theme.of(context); - Widget pagerItem; - Key pagerItemKey; - Color itemColor = _dataPagerThemeData.itemColor; + late Widget? pagerItem; + Key? pagerItemKey; + Color itemColor = _dataPagerThemeData!.itemColor; bool visible = true; - Border border; + late Border border; // DataPageItemBuilder callback - pagerItem = widget.pageItemBuilder?.call(type ?? element?.index.toString()); + pagerItem = widget.pageItemBuilder?.call(type ?? element!.index.toString()); void _setBorder() { - border = _dataPagerThemeData.itemBorderWidth != null && - _dataPagerThemeData.itemBorderWidth > 0.0 + border = _dataPagerThemeData!.itemBorderWidth != null && + _dataPagerThemeData!.itemBorderWidth! > 0.0 ? Border.all( - width: _dataPagerThemeData.itemBorderWidth, - color: _dataPagerThemeData.itemBorderColor) + width: _dataPagerThemeData!.itemBorderWidth!, + color: _dataPagerThemeData!.itemBorderColor) : Border.all(width: 0.0, color: Colors.transparent); } if (pagerItem == null) { if (element == null) { - visible = !isNavigatorItemVisible(type); + visible = !isNavigatorItemVisible(type!); itemColor = visible - ? _dataPagerThemeData.itemColor - : _dataPagerThemeData.disabledItemColor; + ? _dataPagerThemeData!.itemColor + : _dataPagerThemeData!.disabledItemColor; pagerItem = Semantics( label: '$type Page', - child: _getIcon(type, iconData, visible), + child: _getIcon(type, iconData!, visible), ); pagerItemKey = ObjectKey(type); } else { final bool isSelected = checkIsSelectedIndex(element.index); itemColor = isSelected - ? _dataPagerThemeData.selectedItemColor - : _dataPagerThemeData.itemColor; + ? _dataPagerThemeData!.selectedItemColor + : _dataPagerThemeData!.itemColor; final int index = _resolveToItemIndexInView(element.index); pagerItem = Text( index.toString(), key: element.key, style: isSelected - ? _dataPagerThemeData.selectedItemTextStyle - : _dataPagerThemeData.itemTextStyle, + ? _dataPagerThemeData!.selectedItemTextStyle + : _dataPagerThemeData!.itemTextStyle, ); pagerItemKey = element.key; } @@ -971,12 +932,12 @@ class _SfDataPagerState extends State { decoration: BoxDecoration( color: itemColor, border: border, - borderRadius: _dataPagerThemeData.itemBorderRadius), - imageConfig: _imageConfiguration), + borderRadius: _dataPagerThemeData!.itemBorderRadius), + imageConfig: _imageConfiguration!), child: Material( key: pagerItemKey, color: Colors.transparent, - borderRadius: _dataPagerThemeData.itemBorderRadius, + borderRadius: _dataPagerThemeData!.itemBorderRadius, clipBehavior: Clip.antiAlias, child: InkWell( key: pagerItemKey, @@ -1001,7 +962,7 @@ class _SfDataPagerState extends State { } _handleDataPagerControlPropertyChanged( - property: type.toLowerCase()); + property: type!.toLowerCase()); } }, child: Align( @@ -1016,7 +977,7 @@ class _SfDataPagerState extends State { } // Header - Widget _buildHeader() { + Widget? _buildHeader() { final List children = []; IconData getFirstIcon() { @@ -1049,7 +1010,7 @@ class _SfDataPagerState extends State { } // Footer - Widget _buildFooter() { + Widget? _buildFooter() { final List children = []; IconData getNextIcon() { @@ -1082,14 +1043,7 @@ class _SfDataPagerState extends State { // Body Widget _buildBody(BoxConstraints constraint) { - _pageCount = widget.delegate.rowCount != null - ? (widget.delegate.rowCount / widget.rowsPerPage).ceil() - : 0; - - _pageCount = - _controller.pageCount != null && !_controller.pageCount.isNegative - ? _controller.pageCount - : _pageCount; + _pageCount = widget.pageCount.toInt(); final buttonSize = _getButtonSize(); _scrollViewExtent = buttonSize * _pageCount; @@ -1169,10 +1123,10 @@ class _SfDataPagerState extends State { void _resetScrollOffset(double previousScrollViewPortSize) { if (_isPregenerateItems && _isOrientationChanged && - _scrollController.hasClients && - _scrollController.offset > 0.0 && + _scrollController!.hasClients && + _scrollController!.offset > 0.0 && _scrollViewPortSize > previousScrollViewPortSize) { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + WidgetsBinding.instance?.addPostFrameCallback((timeStamp) { _handleScrollPositionChanged(); }); } else { @@ -1183,17 +1137,17 @@ class _SfDataPagerState extends State { Widget _buildDataPager(BoxConstraints constraint) { final List children = []; - final Widget header = _buildHeader(); + final Widget? header = _buildHeader(); if (header != null) { children.add(header); } - final Widget footer = _buildFooter(); + final Widget? footer = _buildFooter(); if (footer != null) { children.add(footer); } - final Widget body = _buildBody(constraint); + final Widget? body = _buildBody(constraint); if (body != null) { children.insert(1, body); } @@ -1207,17 +1161,16 @@ class _SfDataPagerState extends State { final int index = _resolveToItemIndexInView(_currentPageIndex); final String _labelInfo = '$index ${_localization.ofDataPagerLabel} $_pageCount ' - '${_localization.pagesDataPagerLabel} (${widget.delegate.rowCount} ' - '${_localization.itemsDataPagerLabel})'; + '${_localization.pagesDataPagerLabel}'; final Text label = Text( _labelInfo, textDirection: _textDirection, style: TextStyle( - fontSize: _dataPagerThemeData.itemTextStyle.fontSize, - fontWeight: _dataPagerThemeData.itemTextStyle.fontWeight, - fontFamily: _dataPagerThemeData.itemTextStyle.fontFamily, - color: _dataPagerThemeData.brightness == Brightness.light + fontSize: _dataPagerThemeData!.itemTextStyle.fontSize, + fontWeight: _dataPagerThemeData!.itemTextStyle.fontWeight, + fontFamily: _dataPagerThemeData!.itemTextStyle.fontFamily, + color: _dataPagerThemeData!.brightness == Brightness.light ? Colors.black54 : Colors.white54), ); @@ -1283,7 +1236,8 @@ class _SfDataPagerState extends State { final ThemeData themeData = Theme.of(context); _isDesktop = kIsWeb || themeData.platform == TargetPlatform.macOS || - themeData.platform == TargetPlatform.windows; + themeData.platform == TargetPlatform.windows || + themeData.platform == TargetPlatform.linux; } @override @@ -1294,7 +1248,7 @@ class _SfDataPagerState extends State { final bool isDelegateChanged = oldWidget.delegate != widget.delegate; if (isDataPagerControllerChanged || isDelegateChanged || - oldWidget.rowsPerPage != widget.rowsPerPage || + oldWidget.pageCount != widget.pageCount || oldWidget.direction != widget.direction || oldWidget.visibleItemsCount != widget.visibleItemsCount || oldWidget.initialPageIndex != widget.initialPageIndex) { @@ -1303,21 +1257,14 @@ class _SfDataPagerState extends State { _addDelegateListener(); } - if (oldWidget.rowsPerPage != widget.rowsPerPage) { - _currentPageIndex = 0; - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - _handlePageItemTapped(_currentPageIndex); - }); - } - if (isDataPagerControllerChanged) { - if (_controller.pageCount != widget.controller?.pageCount) { + if (oldWidget.pageCount != widget.pageCount) { _currentPageIndex = 0; } - oldWidget.controller + oldWidget.controller! .removeListener(_handleDataPagerControlPropertyChanged); - _controller = widget.controller ?? _controller + _controller = widget.controller ?? _controller! ..addListener(_handleDataPagerControlPropertyChanged); } @@ -1329,7 +1276,7 @@ class _SfDataPagerState extends State { Widget build(BuildContext context) { return Card( elevation: 0.0, - color: _dataPagerThemeData.backgroundColor, + color: _dataPagerThemeData!.backgroundColor, child: LayoutBuilder( builder: (context, constraint) { _updateConstraintChanged(constraint); @@ -1360,12 +1307,18 @@ class _SfDataPagerState extends State { @override void dispose() { - _scrollController - ..removeListener(_handleScrollPositionChanged) - ..dispose(); - _controller - ..removeListener(_handleDataPagerControlPropertyChanged) - ..dispose(); + if (_scrollController != null) { + _scrollController! + ..removeListener(_handleScrollPositionChanged) + ..dispose(); + } + + if (_controller != null) { + _controller! + ..removeListener(_handleDataPagerControlPropertyChanged) + ..dispose(); + } + _controller = null; _imageConfiguration = null; _removeDelegateListener(widget); @@ -1393,18 +1346,17 @@ class _DataPagerItemGenerator { item.isEnsured = false; } - _ScrollableDataPagerItem indexer(int index) { - return _items.firstWhere((element) => element.index == index, - orElse: () => null); + _ScrollableDataPagerItem? indexer(int index) { + return _items.firstWhereOrNull((element) => element.index == index); } for (int index = startIndex; index <= endIndex; index++) { var _scrollableElement = indexer(index); if (_scrollableElement == null) { - final reUseScrollableElement = _items.firstWhere( - (element) => element.index == -1 || !element.isEnsured, - orElse: () => null); + final _ScrollableDataPagerItem? reUseScrollableElement = + _items.firstWhereOrNull( + (element) => element.index == -1 || !element.isEnsured); updateScrollableItem(reUseScrollableElement, index); } @@ -1430,7 +1382,7 @@ class _DataPagerItemGenerator { } } - void updateScrollableItem(_ScrollableDataPagerItem element, int index) { + void updateScrollableItem(_ScrollableDataPagerItem? element, int index) { if (element != null) { element ..index = index @@ -1450,7 +1402,7 @@ class _DataPagerItemGenerator { class _ScrollableDataPagerItem { int index = -1; - Key key; + Key? key; bool isEnsured = false; Rect elementRect = Rect.zero; bool visibility = false; @@ -1458,7 +1410,11 @@ class _ScrollableDataPagerItem { } class _DataPagerItemRenderObject extends SingleChildRenderObjectWidget { - _DataPagerItemRenderObject({Key key, this.isDirty, this.element, this.child}) + _DataPagerItemRenderObject( + {required Key? key, + required this.isDirty, + required this.element, + required this.child}) : super(key: key, child: RepaintBoundary.wrap(child, 0)); @override @@ -1487,7 +1443,8 @@ class _DataPagerItemRenderObject extends SingleChildRenderObjectWidget { } class _DataPagerItemRenderBox extends RenderProxyBox { - _DataPagerItemRenderBox({_ScrollableDataPagerItem element, bool isDirty}) + _DataPagerItemRenderBox( + {required _ScrollableDataPagerItem element, required bool isDirty}) : element = element, _isDirty = isDirty; @@ -1513,7 +1470,10 @@ class _DataPagerItemRenderBox extends RenderProxyBox { class _DataPagerItemPanelRenderObject extends MultiChildRenderObjectWidget { _DataPagerItemPanelRenderObject( - {Key key, this.isDirty, this.viewSize, this.children}) + {Key? key, + required this.isDirty, + required this.viewSize, + required this.children}) : super(key: key, children: RepaintBoundary.wrapAll(List.from(children))); @override @@ -1546,7 +1506,7 @@ class _DataPagerItemPanelRenderBox extends RenderBox RenderBoxContainerDefaultsMixin, DebugOverflowIndicatorMixin { - _DataPagerItemPanelRenderBox({Size viewSize, bool isDirty}) + _DataPagerItemPanelRenderBox({required Size viewSize, required bool isDirty}) : _isDirty = isDirty, _viewSize = viewSize; @@ -1575,7 +1535,7 @@ class _DataPagerItemPanelRenderBox extends RenderBox } @override - bool hitTestChildren(BoxHitTestResult result, {Offset position}) => + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) => defaultHitTestChildren(result, position: position); @override @@ -1584,7 +1544,7 @@ class _DataPagerItemPanelRenderBox extends RenderBox @override void setupParentData(RenderObject child) { super.setupParentData(child); - if (child != null && child.parentData is! _DataPagerItemPanelParentData) { + if (child.parentData is! _DataPagerItemPanelParentData) { child.parentData = _DataPagerItemPanelParentData(); } } @@ -1593,33 +1553,40 @@ class _DataPagerItemPanelRenderBox extends RenderBox void performLayout() { size = constraints.constrain(viewSize); - _DataPagerItemRenderBox child = firstChild; + RenderBox? child = firstChild; while (child != null) { - final _DataPagerItemPanelParentData parentData = child.parentData; - if (child != null && child.element.visible) { - final childRect = child.pageItemRect; - parentData.offset = Offset(childRect.left, childRect.top); - child.layout( - BoxConstraints.tightFor( - width: childRect.width, height: childRect.height), - parentUsesSize: true); - } else { - child.layout(BoxConstraints.tightFor(width: 0.0, height: 0.0), - parentUsesSize: false); + final _DataPagerItemPanelParentData childParentData = + child.parentData! as _DataPagerItemPanelParentData; + if (child is _DataPagerItemRenderBox) { + if (child.element.visible) { + final childRect = child.pageItemRect; + childParentData.offset = Offset(childRect.left, childRect.top); + child.layout( + BoxConstraints.tightFor( + width: childRect.width, height: childRect.height), + parentUsesSize: true); + } else { + child.layout(BoxConstraints.tightFor(width: 0.0, height: 0.0), + parentUsesSize: false); + } } - child = parentData.nextSibling; + child = childParentData.nextSibling; } } @override void paint(PaintingContext context, Offset offset) { - _DataPagerItemRenderBox child = firstChild; + RenderBox? child = firstChild; while (child != null) { - final _DataPagerItemPanelParentData parentData = child.parentData; - if (child != null && child.element.visible) { - context.paintChild(child, parentData.offset + offset); + final _DataPagerItemPanelParentData childParentData = + child.parentData! as _DataPagerItemPanelParentData; + if (child is _DataPagerItemRenderBox) { + if (child.element.visible) { + context.paintChild(child, childParentData.offset + offset); + } } - child = parentData.nextSibling; + + child = childParentData.nextSibling; } super.paint(context, offset); @@ -1627,7 +1594,8 @@ class _DataPagerItemPanelRenderBox extends RenderBox } class _DataPagerItemBoxPainter extends CustomPainter { - const _DataPagerItemBoxPainter({this.decoration, this.imageConfig}); + const _DataPagerItemBoxPainter( + {required this.decoration, required this.imageConfig}); final ImageConfiguration imageConfig; final BoxDecoration decoration; @@ -1649,7 +1617,7 @@ class _DataPagerItemBoxPainter extends CustomPainter { } class _DataPagerChangeNotifier { - ObserverList<_DataPagerControlListener> _listeners = + ObserverList<_DataPagerControlListener>? _listeners = ObserverList<_DataPagerControlListener>(); void addListener(_DataPagerControlListener listener) { @@ -1657,8 +1625,10 @@ class _DataPagerChangeNotifier { } void _notifyDataPagerListeners(String propertyName) { - for (final listener in _listeners) { - listener(property: propertyName); + if (_listeners != null) { + for (final listener in _listeners!) { + listener(property: propertyName); + } } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table.dart index b738ab26f..2ee85d2ea 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table.dart @@ -13,11 +13,11 @@ enum _TreeTableNodeColor { /// A branch or leaf in the tree. abstract class _TreeTableNodeBase { /// Gets the color to the branch. - _TreeTableNodeColor get color => _color; - _TreeTableNodeColor _color; + _TreeTableNodeColor? get color => _color; + _TreeTableNodeColor? _color; /// Sets the color to the branch. - set color(Object value) { + set color(_TreeTableNodeColor? value) { if (value == _color) { return; } @@ -26,11 +26,11 @@ abstract class _TreeTableNodeBase { } /// Gets the parent branch. - _TreeTableBranchBase get parent => _parent; - _TreeTableBranchBase _parent; + _TreeTableBranchBase? get parent => _parent; + _TreeTableBranchBase? _parent; /// Sets the parent color. - set parent(_TreeTableBranchBase value) { + set parent(_TreeTableBranchBase? value) { if (value == _parent) { return; } @@ -68,7 +68,7 @@ abstract class _TreeTableNodeBase { /// /// Returns the minimum value (of the leftmost leaf) of the branch in /// a sorted tree. - Object getMinimum(); + Object? getMinimum(); /// Gets the position in the tree. /// @@ -84,11 +84,11 @@ abstract class _TreeTableNodeBase { /// A branch with left and right leaves or branches. mixin _TreeTableBranchBase on _TreeTableNodeBase { /// Gets the left node. - _TreeTableNodeBase get left => _left; - _TreeTableNodeBase _left; + _TreeTableNodeBase? get left => _left; + _TreeTableNodeBase? _left; /// Sets the left node. - set left(_TreeTableNodeBase value) { + set left(_TreeTableNodeBase? value) { if (value == _left) { return; } @@ -97,11 +97,11 @@ mixin _TreeTableBranchBase on _TreeTableNodeBase { } /// Gets the right node. - _TreeTableNodeBase get right => _right; - _TreeTableNodeBase _right; + _TreeTableNodeBase? get right => _right; + _TreeTableNodeBase? _right; /// Sets the right node. - set right(_TreeTableNodeBase value) { + set right(_TreeTableNodeBase? value) { if (value == _right) { return; } @@ -128,12 +128,12 @@ mixin _TreeTableBranchBase on _TreeTableNodeBase { /// The left branch cast to _TreeTableBranchBase. /// /// Returns the left branch cast to _TreeTableBranchBase. - _TreeTableBranchBase getLeftBranch(); + _TreeTableBranchBase? getLeftBranch(); /// The right branch cast to _TreeTableBranchBase. /// /// Returns the right branch cast to _TreeTableBranchBase. - _TreeTableBranchBase getRightBranch(); + _TreeTableBranchBase? getRightBranch(); /// Returns the position in the tree table of the specified child node. /// @@ -151,7 +151,7 @@ mixin _TreeTableBranchBase on _TreeTableNodeBase { /// * value - _required_ - The new node. /// * inAddMode - _required_ - Indicates whether tree-table is in add-mode. /// * isSortedTree - _required_ - Indicates whether tree-table is sorted. - void setLeft(_TreeTableNodeBase value, bool inAddMode, bool isSortedTree); + void setLeft(_TreeTableNodeBase? value, bool inAddMode, bool isSortedTree); /// Sets the right node. /// @@ -161,17 +161,17 @@ mixin _TreeTableBranchBase on _TreeTableNodeBase { /// /// * value - _required_ - The new node. /// * inAddMode - _required_ - Specifies if tree-table is in add-mode. - void setRight(_TreeTableNodeBase value, bool inAddMode); + void setRight(_TreeTableNodeBase? value, bool inAddMode); } /// A leaf with value and optional sort key. mixin _TreeTableEntryBase on _TreeTableNodeBase { /// Gets the value attached to this leaf. - Object get value => _value; - Object _value; + Object? get value => _value; + Object? _value; /// Sets the value attached to this leaf - set value(Object value) { + set value(Object? value) { if (value == _value) { return; } @@ -185,12 +185,12 @@ mixin _TreeTableEntryBase on _TreeTableNodeBase { /// * tree - _required_ - Tree table instance /// /// Returns the instance of newly created branch - _TreeTableBranchBase createBranch(_TreeTable tree); + _TreeTableBranchBase? createBranch(_TreeTable tree); /// Gets the sort key of this leaf. /// /// Returns the sort key of this leaf. - Object getSortKey(); + Object? getSortKey(); } /// A branch or leaf in the tree. @@ -198,11 +198,11 @@ abstract class _TreeTableNode extends _TreeTableNodeBase { static Object emptyMin = Object(); /// Gets the tree this node belongs to. - _TreeTable get tree => _tree; - _TreeTable _tree; + _TreeTable? get tree => _tree; + _TreeTable? _tree; /// Sets the tree this node belongs to. - set tree(_TreeTable value) { + set tree(_TreeTable? value) { if (value == _tree) { return; } @@ -212,11 +212,11 @@ abstract class _TreeTableNode extends _TreeTableNodeBase { /// Gets the parent branch. @override - _TreeTableBranchBase get parent => _parent; + _TreeTableBranchBase? get parent => _parent; /// Sets the parent branch. @override - set parent(_TreeTableBranchBase value) { + set parent(_TreeTableBranchBase? value) { _parent = value; } @@ -250,7 +250,7 @@ abstract class _TreeTableNode extends _TreeTableNodeBase { /// Returns the minimum value (of the most-left leaf) of the branch /// in a sorted tree. @override - Object getMinimum() => emptyMin; + Object? getMinimum() => emptyMin; /// Gets the number of child nodes (+1 for the current node). /// @@ -265,7 +265,7 @@ abstract class _TreeTableNode extends _TreeTableNodeBase { int getLevel() { int level = 0; if (parent != null) { - level = parent.getLevel() + 1; + level = parent!.getLevel() + 1; } return level; @@ -277,7 +277,7 @@ abstract class _TreeTableNode extends _TreeTableNodeBase { String getNodeInfoBase() { String side = '_'; if (parent != null) { - side = _MathHelper.referenceEquals(parent.left, this) ? 'L' : 'R'; + side = _MathHelper.referenceEquals(parent!.left, this) ? 'L' : 'R'; } return '${getLevel()} $side this., ${getPosition()} , ${getCount()}'; @@ -292,7 +292,7 @@ abstract class _TreeTableNode extends _TreeTableNodeBase { return 0; } - return parent.getEntryPositionOfChild(this); + return parent!.getEntryPositionOfChild(this); } /// Gets the Debug / text information about the node. @@ -312,26 +312,26 @@ class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { } int entryCount = -1; - Object _minimum = _TreeTableNode.emptyMin; + Object? _minimum = _TreeTableNode.emptyMin; /// Gets the right tree or branch. @override - _TreeTableNodeBase get right => _right; + _TreeTableNodeBase? get right => _right; /// Sets the right tree or branch. @override - set right(_TreeTableNodeBase value) { + set right(_TreeTableNodeBase? value) { setRight(value, false); } /// Gets the left leaf or branch. @override - _TreeTableNodeBase get left => _left; + _TreeTableNodeBase? get left => _left; /// Sets the left leaf or branch. @override - set left(_TreeTableNodeBase value) { - setLeft(value, false, tree.sorted); + set left(_TreeTableNodeBase? value) { + setLeft(value, false, tree!.sorted); } /// Indicates whether this is a leaf. @@ -345,12 +345,12 @@ class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { @override void invalidateCountBottomUp() { entryCount = -1; - if (parent != null && parent.parent == parent) { + if (parent != null && parent!.parent == parent) { throw Exception(); } if (parent != null) { - parent.invalidateCountBottomUp(); + parent!.invalidateCountBottomUp(); } } @@ -359,12 +359,12 @@ class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { @override void invalidateCountTopDown() { entryCount = -1; - if (!left.isEntry()) { - getLeftBranch().invalidateCountTopDown(); + if (left != null && !left!.isEntry()) { + getLeftBranch()?.invalidateCountTopDown(); } - if (!right.isEntry()) { - getRightBranch().invalidateCountTopDown(); + if (right != null && !right!.isEntry()) { + getRightBranch()?.invalidateCountTopDown(); } } @@ -374,7 +374,7 @@ class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { void invalidateMinimumBottomUp() { _minimum = _TreeTableNode.emptyMin; if (parent != null) { - parent.invalidateMinimumBottomUp(); + parent!.invalidateMinimumBottomUp(); } } @@ -382,11 +382,11 @@ class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { /// through all child branches and marks their child node minimum dirty. @override void invalidateMinimumTopDown() { - if (!left.isEntry()) { - getLeftBranch().invalidateMinimumTopDown(); + if (left != null && !left!.isEntry()) { + getLeftBranch()?.invalidateMinimumTopDown(); } - if (!right.isEntry()) { - getRightBranch().invalidateMinimumTopDown(); + if (right != null && !right!.isEntry()) { + getRightBranch()?.invalidateMinimumTopDown(); } _minimum = _TreeTableNode.emptyMin; } @@ -396,8 +396,8 @@ class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { /// Returns the number of child nodes (+1 for the current node). @override int getCount() { - if (entryCount < 0) { - entryCount = _left.getCount() + _right.getCount(); + if (entryCount < 0 && _left != null && _right != null) { + entryCount = _left!.getCount() + _right!.getCount(); } return entryCount; @@ -412,7 +412,9 @@ class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { int getEntryPositionOfChild(_TreeTableNodeBase node) { int pos = getPosition(); if (_MathHelper.referenceEquals(node, right)) { - pos += left.getCount(); + if (left != null) { + pos += left!.getCount(); + } } else if (!_MathHelper.referenceEquals(node, left)) { //throw ArgumentError('must be a child node','node'); throw ArgumentError('must be a child node'); @@ -425,9 +427,9 @@ class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { /// /// Returns the left node cast to _TreeTableBranchBase. @override - _TreeTableBranchBase getLeftBranch() { + _TreeTableBranchBase? getLeftBranch() { if (_left is _TreeTableBranchBase) { - return _left; + return _left! as _TreeTableBranchBase; } else { return null; } @@ -439,9 +441,9 @@ class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { /// Returns the minimum value (of the most-left leaf) of the branch /// in a sorted tree. @override - Object getMinimum() { + Object? getMinimum() { if (_MathHelper.referenceEquals(_TreeTableNode.emptyMin, _minimum)) { - _minimum = _left.getMinimum(); + _minimum = _left!.getMinimum(); } return _minimum; } @@ -450,9 +452,9 @@ class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { /// /// Returns the right node cast to _TreeTableBranchBase. @override - _TreeTableBranchBase getRightBranch() { + _TreeTableBranchBase? getRightBranch() { if (_right is _TreeTableBranchBase) { - return _right; + return _right! as _TreeTableBranchBase; } else { return null; } @@ -468,28 +470,28 @@ class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { /// * inAddMode - _required_ - Indicates whether tree-table is in add-mode. /// * isSorted - _required_ - Indicates whether tree-table is sorted. @override - void setLeft(_TreeTableNodeBase value, bool inAddMode, bool isSorted) { + void setLeft(_TreeTableNodeBase? value, bool inAddMode, bool isSorted) { if (!_MathHelper.referenceEquals(left, value)) { if (inAddMode) { - if (_left != null && _left.parent == this) { - _left.parent = null; + if (_left != null && _left!.parent == this) { + _left!.parent = null; } _left = value; if (_left != null) { - _left.parent = this; + _left!.parent = this; } } else { - final int lc = (_left != null) ? _left.getCount() : 0; + final int lc = (_left != null) ? _left!.getCount() : 0; final int vc = (value != null) ? value.getCount() : 0; final int entryCountDelta = vc - lc; - if (_left != null && _left.parent == this) { - _left.parent = null; + if (_left != null && _left!.parent == this) { + _left!.parent = null; } _left = value; if (_left != null) { - _left.parent = this; + _left!.parent = this; } if (entryCountDelta != 0) { @@ -515,28 +517,28 @@ class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { /// * value - _required_ - The new node. /// * inAddMode - _required_ - Indicates whether tree-table is in add-mode. @override - void setRight(_TreeTableNodeBase value, bool inAddMode) { + void setRight(_TreeTableNodeBase? value, bool inAddMode) { if (!_MathHelper.referenceEquals(right, value)) { if (inAddMode) { - if (_right != null && _right.parent == this) { - _right.parent = null; + if (_right != null && _right!.parent == this) { + _right!.parent = null; } _right = value; if (_right != null) { - _right.parent = this; + _right!.parent = this; } } else { - final int lc = (_right != null) ? _right.getCount() : 0; + final int lc = (_right != null) ? _right!.getCount() : 0; final int vc = (value != null) ? value.getCount() : 0; final int entryCountDelta = vc - lc; - if (_right != null && _right.parent == this) { - _right.parent = null; + if (_right != null && _right!.parent == this) { + _right!.parent = null; } _right = value; if (_right != null) { - _right.parent = this; + _right!.parent = this; } if (entryCountDelta != 0) { @@ -572,7 +574,7 @@ class _TreeTableEntry extends _TreeTableNode with _TreeTableEntryBase { /// Returns the instance of newly created branch @override - _TreeTableBranchBase createBranch(_TreeTable tree) => _TreeTableBranch(tree); + _TreeTableBranchBase? createBranch(_TreeTable tree) => _TreeTableBranch(tree); /// Gets the number of child nodes (+1 for the current node). /// @@ -586,13 +588,13 @@ class _TreeTableEntry extends _TreeTableNode with _TreeTableEntryBase { /// Returns the minimum value (of the most-left leaf) of the branch /// in a sorted tree. @override - Object getMinimum() => getSortKey(); + Object? getMinimum() => getSortKey(); /// Gets the sort key of this leaf. /// /// Returns the sort key of this leaf. @override - Object getSortKey() => value; + Object? getSortKey() => value; } /// An empty node. @@ -619,17 +621,12 @@ class _TreeTableEmpty extends _TreeTableNode { /// Tree table interface definition. class _TreeTableBase extends _ListBase { - _TreeTableBase() { - _isInitializing = false; - _sorted = false; - } - /// Gets the comparer value used by sorted trees. - Comparable get comparer => _comparer; - Comparable _comparer; + Comparable? get comparer => _comparer; + Comparable? _comparer; /// Sets the comparer value used by sorted trees. - set comparer(Comparable value) { + set comparer(Comparable? value) { if (value == _comparer) { return; } @@ -637,18 +634,18 @@ class _TreeTableBase extends _ListBase { _comparer = value; } - _TreeTableNodeBase get root => _root; - _TreeTableNodeBase _root; + _TreeTableNodeBase? get root => _root; + _TreeTableNodeBase? _root; /// Gets a value indicating whether this is a sorted tree or not. bool get sorted => _sorted; - bool _sorted; + bool _sorted = false; /// Gets the root node. /// Gets a value indicating whether the tree was initialize or not. bool get isInitializing => _isInitializing; - bool _isInitializing; + bool _isInitializing = false; /// Optimizes insertion of many elements when tree is initialized /// for the first time. @@ -663,14 +660,14 @@ class _TreeTableBase extends _ListBase { /// * current - _required_ - Current item /// /// Returns next subsequent entry - _TreeTableEntryBase getNextEntry(_TreeTableEntryBase current); + _TreeTableEntryBase? getNextEntry(_TreeTableEntryBase current); /// Optimized access to a previous entry. /// /// * current - _required_ - Current item /// /// Returns previous entry - _TreeTableEntryBase getPreviousEntry(_TreeTableEntryBase current); + _TreeTableEntryBase? getPreviousEntry(_TreeTableEntryBase current); } /// A tree table. @@ -680,27 +677,26 @@ class _TreeTable extends _TreeTableBase { /// * sorted - _required_ - Boolean value _TreeTable(bool sorted) { _sorted = sorted; - _inAddMode = false; } bool _inAddMode = false; - _TreeTableBranchBase _lastAddBranch; - _TreeTableEntryBase _lastFoundEntry; - Object _lastFoundEntryKey; + _TreeTableBranchBase? _lastAddBranch; + _TreeTableEntryBase? _lastFoundEntry; + Object? _lastFoundEntryKey; bool _lastFoundEntryHighestSmallerValue = false; int lastIndex = -1; - Object tag; + Object? tag; /// Gets the last index leaf. /// /// Returns the last index leaf. - _TreeTableEntryBase get lastIndexLeaf => _lastIndexLeaf; - _TreeTableEntryBase _lastIndexLeaf; + _TreeTableEntryBase? get lastIndexLeaf => _lastIndexLeaf; + _TreeTableEntryBase? _lastIndexLeaf; /// Sets the last index leaf. /// /// Returns the last index leaf. - set lastIndexLeaf(_TreeTableEntryBase value) { + set lastIndexLeaf(_TreeTableEntryBase? value) { if (_lastIndexLeaf != value) { _lastIndexLeaf = value; } @@ -708,11 +704,11 @@ class _TreeTable extends _TreeTableBase { /// Gets the comparer used by sorted trees. @override - Comparable get comparer => _comparer; + Comparable? get comparer => _comparer; /// Sets the comparer used by sorted trees. @override - set comparer(Comparable value) { + set comparer(Comparable? value) { _comparer = value; _sorted = _comparer != null; } @@ -746,7 +742,7 @@ class _TreeTable extends _TreeTableBase { /// Gets the root node. @override - _TreeTableNodeBase get root => _root; + _TreeTableNodeBase? get root => _root; /// Gets an object that can be used to synchronize access to /// the `ICollection`. @@ -754,7 +750,7 @@ class _TreeTable extends _TreeTableBase { /// Returns - _required_ - an object that can be used to synchronize access /// to the `ICollection`. @override - Object get syncRoot => null; + Object? get syncRoot => null; /// Appends a node. /// @@ -775,20 +771,23 @@ class _TreeTable extends _TreeTableBase { return 0; } else { // add node to most right branch - _TreeTableBranchBase branch; - _TreeTableNodeBase current = _lastAddBranch ?? _root; + _TreeTableBranchBase? branch; + _TreeTableNodeBase? current = _lastAddBranch ?? _root; - while (!current.isEntry()) { - branch = current; + while (current != null && !current.isEntry()) { + branch = current as _TreeTableBranchBase; current = branch.right; } - final _TreeTableEntryBase leaf = current; + final _TreeTableEntryBase? leaf = current as _TreeTableEntryBase; - final _TreeTableBranchBase newBranch = leaf.createBranch(this) - ..setLeft(leaf, _inAddMode, sorted) - // will set leaf.Parent ... - ..setRight(value, _inAddMode); + final _TreeTableBranchBase? newBranch = leaf?.createBranch(this); + if (newBranch != null) { + newBranch + ..setLeft(leaf!, _inAddMode, sorted) + // will set leaf.Parent ... + ..setRight(value, _inAddMode); + } if (branch == null) { _root = newBranch; @@ -796,23 +795,25 @@ class _TreeTable extends _TreeTableBase { // swap out leafs parent with new node _replaceNode(branch, current, newBranch, _inAddMode); if (!(branch.parent == null || - branch.parent.parent == null || - branch.right != branch.parent.parent.right)) { + branch.parent?.parent == null || + branch.right != branch.parent?.parent?.right)) { throw Exception(); } - final Object _left = branch.parent?.left; + final Object? _left = branch.parent?.left; if (!(branch.parent == null || - branch.parent.left.isEntry() || + (branch.parent != null && + branch.parent!.left != null && + branch.parent!.left!.isEntry()) || (_left is _TreeTableBranch && _left.right != branch))) { throw Exception(); } } - insertFixup(newBranch, _inAddMode); + insertFixup(newBranch!, _inAddMode); - if (value.parent != null && value.parent.parent != null) { - if (value.parent.parent.right == value) { + if (value.parent != null && value.parent?.parent != null) { + if (value.parent!.parent?.right == value) { throw Exception(); } } @@ -822,7 +823,7 @@ class _TreeTable extends _TreeTableBase { if (_inAddMode) { return -1; } else { - return _root.getCount() - 1; + return _root!.getCount() - 1; } } } @@ -834,7 +835,7 @@ class _TreeTable extends _TreeTableBase { /// * value - _required_ - Node value to add. /// /// Returns the instance for the tree - _TreeTableEntryBase addIfNotExists(Object key, _TreeTableEntryBase value) { + _TreeTableEntryBase? addIfNotExists(Object? key, _TreeTableEntryBase? value) { if (!sorted) { throw Exception('This tree is not sorted.'); } @@ -847,30 +848,35 @@ class _TreeTable extends _TreeTableBase { return value; } else { // find node - _TreeTableBranchBase branch; - _TreeTableNodeBase current = _root; + _TreeTableBranchBase? branch; + _TreeTableNodeBase? current = _root as _TreeTableNodeBase; int cmp = 0; - final Comparable comparer = this.comparer; + final Comparable? comparer = this.comparer; final bool inAddMode = false; - Comparable comparableKey = key; - while (!current.isEntry()) { - branch = current; + Comparable? comparableKey = key as Comparable; + while (current != null && !current.isEntry()) { + branch = current as _TreeTableBranchBase; if (comparer != null) { - cmp = Comparable.compare(key, branch.right.getMinimum()); - } else if (comparableKey != null) { - cmp = comparableKey.compareTo(branch.right.getMinimum()); + final _TreeTableNodeBase? tableNodeBase = branch.right; + if (tableNodeBase != null) { + final value = tableNodeBase.getMinimum(); + cmp = key.compareTo(value); + } + } else if (comparableKey is Comparable) { + cmp = comparableKey.compareTo(branch.right?.getMinimum()); } else { throw Exception('No Comparer specified.'); } if (cmp == 0) { current = branch.right; - while (!current.isEntry()) { - final _TreeTableBranchBase _current = current; + while (current != null && !current.isEntry()) { + final _TreeTableBranchBase _current = + current as _TreeTableBranchBase; current = _current.left; } - return current; + return current as _TreeTableEntryBase; } else if (cmp < 0) { current = branch.left; } else { @@ -878,11 +884,11 @@ class _TreeTable extends _TreeTableBase { } } - final _TreeTableEntryBase leaf = current; + final _TreeTableEntryBase leaf = current as _TreeTableEntryBase; if (comparer != null) { - cmp = Comparable.compare(key, leaf.getSortKey()); - } else if (value.getMinimum() is Comparable) { + cmp = key.compareTo(leaf.getSortKey()); + } else if (value!.getMinimum() is Comparable) { cmp = comparableKey.compareTo(leaf.getSortKey()); } @@ -892,13 +898,13 @@ class _TreeTable extends _TreeTableBase { return leaf; } - final _TreeTableBranchBase newBranch = leaf.createBranch(this); + final _TreeTableBranchBase? newBranch = leaf.createBranch(this); - if (cmp < 0) { + if (newBranch != null && cmp < 0) { newBranch ..setLeft(value, false, sorted) // will set leaf.Parent ... ..right = leaf; - } else if (cmp > 0) { + } else if (newBranch != null && cmp > 0) { newBranch ..setLeft(leaf, false, sorted) // will set leaf.Parent ... ..right = value; @@ -937,23 +943,24 @@ class _TreeTable extends _TreeTableBase { return 0; } else { final bool inAddMode = false; - final Comparable comparer = this.comparer; + final Comparable? comparer = this.comparer; // find node - _TreeTableBranchBase branch; - _TreeTableNodeBase current = _root; + _TreeTableBranchBase? branch; + _TreeTableNodeBase? current = _root; int count = 0; - int cmp = 0; + int? cmp = 0; - while (!current.isEntry()) { - branch = current; + while (current != null && !current.isEntry()) { + branch = current as _TreeTableBranchBase; if (comparer != null) { - cmp = - Comparable.compare(value.getMinimum(), branch.right.getMinimum()); + final dynamic? minimum = value.getMinimum(); + final dynamic? right = branch.right!.getMinimum(); + cmp = Comparable.compare(minimum, right); } else if (value.getMinimum() is Comparable) { - final Object _minimum = value.getMinimum(); - if (_minimum is Comparable) { - cmp = _minimum.compareTo(branch.right.getMinimum()); + final Object? _minimum = value.getMinimum(); + if (_minimum != null && _minimum is Comparable) { + cmp = _minimum.compareTo(branch.right!.getMinimum()); } else { cmp = null; } @@ -961,38 +968,44 @@ class _TreeTable extends _TreeTableBase { throw Exception('No Comparer Specified'); } - if (cmp <= 0) { + if (cmp != null && cmp <= 0) { current = branch.left; } else { - count += branch.left.getCount(); + count += branch.left!.getCount(); current = branch.right; } } - final _TreeTableEntryBase leaf = current; + final _TreeTableEntryBase leaf = current! as _TreeTableEntryBase; if (leaf is _TreeTableEntryBase) {} - final _TreeTableBranchBase newBranch = leaf.createBranch(this); + final _TreeTableBranchBase? newBranch = leaf.createBranch(this); if (comparer != null) { - cmp = Comparable.compare(value.getMinimum(), leaf.getSortKey()); + final minimum = value.getMinimum(); + final sortKey = leaf.getSortKey(); + if (minimum is Comparable && sortKey is Comparable) { + cmp = Comparable.compare(minimum, sortKey); + } } else if (value.getMinimum() is Comparable) { - final Object _minimum = value.getMinimum(); - if (_minimum is Comparable) { + final Object? _minimum = value.getMinimum(); + if (_minimum != null && _minimum is Comparable) { cmp = _minimum.compareTo(leaf.getSortKey()); } else { cmp = null; } } - if (cmp <= 0) { + if (newBranch != null && cmp != null && cmp <= 0) { newBranch ..setLeft(value, false, sorted) // will set leaf.Parent ... ..right = leaf; } else { - newBranch.setLeft(leaf, false, sorted); // will set leaf.Parent ... - count++; - newBranch.right = value; + if (newBranch != null) { + newBranch.setLeft(leaf, false, sorted); // will set leaf.Parent ... + count++; + newBranch.right = value; + } } if (branch == null) { @@ -1008,8 +1021,8 @@ class _TreeTable extends _TreeTableBase { } } - _TreeTableEntryBase cacheLastFoundEntry( - _TreeTableEntryBase entry, Object key, bool highestSmallerValue) { + _TreeTableEntryBase? cacheLastFoundEntry( + _TreeTableEntryBase? entry, Object? key, bool highestSmallerValue) { lastIndex = -1; _lastFoundEntry = entry; _lastFoundEntryKey = key; @@ -1022,14 +1035,14 @@ class _TreeTable extends _TreeTableBase { /// * value - _required_ - Node value to search for. /// /// Returns true if node belongs to this tree; false otherwise. - bool containsBase(_TreeTableNodeBase value) { + bool containsBase(_TreeTableNodeBase? value) { if (value == null || _root == null) { return false; } // search root - while (value.parent != null) { - value = value.parent; + while (value!.parent != null) { + value = value.parent!; } return _MathHelper.referenceEquals(value, _root); @@ -1042,125 +1055,129 @@ class _TreeTable extends _TreeTableBase { void copyToBase(List<_TreeTableNodeBase> array, int index) { final int count = getCount(); for (int i = 0; i < count; i++) { - array[i + index] = this[i]; + array[i + index] = this[i]!; } } - void deleteFixup(_TreeTableBranchBase x, bool isLeft) { + void deleteFixup(_TreeTableBranchBase? x, bool isLeft) { final bool inAddMode = false; - while (!_MathHelper.referenceEquals(x, _root) && + while (x != null && + !_MathHelper.referenceEquals(x, _root) && x._color == _TreeTableNodeColor.black) { if (isLeft) { - _TreeTableBranchBase w = x.parent.right; + var w = x.parent?.right; if (w != null && w.color == _TreeTableNodeColor.red) { w.color = _TreeTableNodeColor.black; - x.parent.color = _TreeTableNodeColor.black; - leftRotate(x.parent, inAddMode); - w = x.parent.right; + x.parent?.color = _TreeTableNodeColor.black; + leftRotate(x.parent!, inAddMode); + w = x.parent?.right as _TreeTableBranchBase; } if (w == null) { return; } - if (w.color == _TreeTableNodeColor.black && - (w.left.isEntry() || - w.getLeftBranch().color == _TreeTableNodeColor.black) && - (w.right.isEntry() || - w.getRightBranch().color == _TreeTableNodeColor.black)) { + if (w is _TreeTableBranchBase && + w.color == _TreeTableNodeColor.black && + (w.left!.isEntry() || + w.getLeftBranch()!.color == _TreeTableNodeColor.black) && + (w.right!.isEntry() || + w.getRightBranch()!.color == _TreeTableNodeColor.black)) { w.color = _TreeTableNodeColor.red; if (x.color == _TreeTableNodeColor.red) { x.color = _TreeTableNodeColor.black; return; } else { - isLeft = x.parent.left == x; + isLeft = x.parent!.left == x; x = x.parent; } - } else if (w.color == _TreeTableNodeColor.black && - !w.right.isEntry() && - w.getRightBranch().color == _TreeTableNodeColor.red) { - leftRotate(x.parent, inAddMode); - final _TreeTableNodeColor t = w.color; - w.color = x.parent.color; - x.parent.color = t; + } else if (w is _TreeTableBranchBase && + w.color == _TreeTableNodeColor.black && + !w.right!.isEntry() && + w.getRightBranch()!.color == _TreeTableNodeColor.red) { + leftRotate(x.parent!, inAddMode); + w.color = x.parent!.color; + x.parent!.color = w.color; return; - } else if (w.color == _TreeTableNodeColor.black && - !w.left.isEntry() && - w.getLeftBranch().color == _TreeTableNodeColor.red && - (w.right.isEntry() || - w.getRightBranch().color == _TreeTableNodeColor.black)) { + } else if (w is _TreeTableBranchBase && + w.color == _TreeTableNodeColor.black && + !w.left!.isEntry() && + w.getLeftBranch()!.color == _TreeTableNodeColor.red && + (w.right!.isEntry() || + w.getRightBranch()!.color == _TreeTableNodeColor.black)) { rightRotate(w, inAddMode); - w.parent.color = _TreeTableNodeColor.black; + w.parent!.color = _TreeTableNodeColor.black; w.color = _TreeTableNodeColor.red; - leftRotate(x.parent, inAddMode); - final _TreeTableNodeColor t = w.color; - w.color = x.parent.color; - x.parent.color = t; + leftRotate(x.parent!, inAddMode); + w.color = x.parent!.color; + x.parent!.color = w.color; return; } else { return; } } else { - _TreeTableBranchBase w = x.parent.left; + var w = x.parent?.left; if (w != null && w.color == _TreeTableNodeColor.red) { w.color = _TreeTableNodeColor.black; - x.parent.color = _TreeTableNodeColor.red; + x.parent!.color = _TreeTableNodeColor.red; rightRotate(x.parent, inAddMode); - w = x.parent.left; + w = x.parent!.left; } if (w == null) { return; } - if (w.color == _TreeTableNodeColor.black && - (w.left.isEntry() || - w.getLeftBranch().color == _TreeTableNodeColor.black) && - (w.right.isEntry() || - w.getRightBranch().color == _TreeTableNodeColor.black)) { + if (w is _TreeTableBranchBase && + w.color == _TreeTableNodeColor.black && + (w.left!.isEntry() || + w.getLeftBranch()!.color == _TreeTableNodeColor.black) && + (w.right!.isEntry() || + w.getRightBranch()!.color == _TreeTableNodeColor.black)) { w.color = _TreeTableNodeColor.red; if (x.color == _TreeTableNodeColor.red) { x.color = _TreeTableNodeColor.black; return; } else if (x.parent != null) { - isLeft = x.parent.left == x; + isLeft = x.parent!.left == x; x = x.parent; } } else { - if (w.color == _TreeTableNodeColor.black && - !w.right.isEntry() && - w.getRightBranch().color == _TreeTableNodeColor.red) { - final _TreeTableBranchBase xParent = x.parent; + if (w is _TreeTableBranchBase && + w.color == _TreeTableNodeColor.black && + !w.right!.isEntry() && + w.getRightBranch()!.color == _TreeTableNodeColor.red) { + final _TreeTableBranchBase xParent = x.parent!; leftRotate(xParent, inAddMode); - final _TreeTableNodeColor t = w.color; + final _TreeTableNodeColor t = w.color!; w.color = xParent.color; xParent.color = t; return; - } else if (w.color == _TreeTableNodeColor.black && - !w.left.isEntry() && - w.getLeftBranch().color == _TreeTableNodeColor.red && - (w.right.isEntry() || - w.getRightBranch().color == _TreeTableNodeColor.black)) { - final _TreeTableBranchBase wParent = w.parent; - final _TreeTableBranchBase xParent = x.parent; + } else if (w is _TreeTableBranchBase && + w.color == _TreeTableNodeColor.black && + !w.left!.isEntry() && + w.getLeftBranch()!.color == _TreeTableNodeColor.red && + (w.right!.isEntry() || + w.getRightBranch()!.color == _TreeTableNodeColor.black)) { + final _TreeTableBranchBase wParent = w.parent!; + final _TreeTableBranchBase xParent = x.parent!; rightRotate(w, inAddMode); wParent.color = _TreeTableNodeColor.black; w.color = _TreeTableNodeColor.red; leftRotate(x.parent, inAddMode); - final _TreeTableNodeColor t = w.color; w.color = xParent.color; - xParent.color = t; + xParent.color = w.color; return; } } } } - x.color = _TreeTableNodeColor.black; + x!.color = _TreeTableNodeColor.black; } /// Finds the node in a sorted tree is just one entry ahead of the @@ -1172,7 +1189,7 @@ class _TreeTable extends _TreeTableBase { /// * key - _required_ - The key to search. /// /// Returns the node; `NULL` if not found. - _TreeTableEntryBase findHighestSmallerOrEqualKey(Object key) => + _TreeTableEntryBase? findHighestSmallerOrEqualKey(Object key) => _findKey(key, true); /// Finds a node in a sorted tree that matches the specified key. @@ -1180,19 +1197,19 @@ class _TreeTable extends _TreeTableBase { /// * key - _required_ - The key to search. /// /// Returns the node; `NULL` if not found. - _TreeTableEntryBase findKey(Object key) => _findKey(key, false); + _TreeTableEntryBase? findKey(Object key) => _findKey(key, false); - _TreeTableEntryBase _findKey(Object key, bool highestSmallerValue) { + _TreeTableEntryBase? _findKey(Object? key, bool highestSmallerValue) { if (!sorted) { throw Exception('This tree is not sorted.'); } - Comparable comparableKey = key; + var comparableKey = key; if (root == null) { // replace root return null; } else { - final Comparable comparer = this.comparer; + final Comparable? comparer = this.comparer; int cmp = 0; if (_lastFoundEntry != null && @@ -1200,9 +1217,12 @@ class _TreeTable extends _TreeTableBase { key != null && _lastFoundEntryHighestSmallerValue == highestSmallerValue) { if (comparer != null) { - cmp = Comparable.compare(key, _lastFoundEntry.getMinimum()); - } else if (comparableKey != null) { - cmp = comparableKey.compareTo(_lastFoundEntry.getMinimum()); + final lastFoundEntry = _lastFoundEntry!.getMinimum(); + if (key is Comparable && lastFoundEntry is Comparable) { + cmp = Comparable.compare(key, lastFoundEntry); + } + } else if (comparableKey != null && comparableKey is Comparable) { + cmp = comparableKey.compareTo(_lastFoundEntry!.getMinimum()); } if (cmp == 0) { @@ -1211,16 +1231,19 @@ class _TreeTable extends _TreeTableBase { } // find node - _TreeTableBranchBase branch; - _TreeTableNodeBase current = root; + var branch; + _TreeTableNodeBase current = root!; - _TreeTableNodeBase lastLeft; + _TreeTableNodeBase? lastLeft; while (!current.isEntry()) { branch = current; if (comparer != null) { - cmp = Comparable.compare(key, branch.right.getMinimum()); - } else if (comparableKey != null) { + final minimum = branch.right.getMinimum(); + if (key is Comparable && minimum is Comparable) { + cmp = Comparable.compare(key, minimum); + } + } else if (comparableKey != null && comparableKey is Comparable) { cmp = comparableKey.compareTo(branch.right.getMinimum()); } else { throw Exception('No Comparer specified.'); @@ -1229,11 +1252,13 @@ class _TreeTable extends _TreeTableBase { if (cmp == 0) { current = branch.right; while (!current.isEntry()) { - final _TreeTableBranchBase _current = current; - current = _current.left; + if (current is _TreeTableBranchBase) { + current = current.left as _TreeTableNodeBase; + } } - return cacheLastFoundEntry(current, key, highestSmallerValue); + return cacheLastFoundEntry( + current as _TreeTableEntryBase, key, highestSmallerValue); } else if (cmp < 0) { current = branch.left; lastLeft = branch.left; @@ -1242,11 +1267,14 @@ class _TreeTable extends _TreeTableBase { } } - final _TreeTableEntryBase leaf = current; + final _TreeTableEntryBase leaf = current as _TreeTableEntryBase; if (comparer != null) { - cmp = Comparable.compare(key, leaf.getSortKey()); - } else if (comparableKey != null) { + final sortKey = leaf.getSortKey(); + if (key is Comparable && sortKey is Comparable) { + cmp = Comparable.compare(key, sortKey); + } + } else if (comparableKey != null && comparableKey is Comparable) { cmp = comparableKey.compareTo(leaf.getSortKey()); } @@ -1261,11 +1289,13 @@ class _TreeTable extends _TreeTableBase { } else if (lastLeft != null) { current = lastLeft; while (!current.isEntry()) { - final _TreeTableBranchBase _current = current; - current = _current.right; + final _TreeTableBranchBase _current = + current as _TreeTableBranchBase; + current = _current.right!; } - return cacheLastFoundEntry(current, key, highestSmallerValue); + return cacheLastFoundEntry( + current as _TreeTableEntryBase, key, highestSmallerValue); } } @@ -1299,7 +1329,7 @@ class _TreeTable extends _TreeTableBase { // replace root _root = value; } else { - _TreeTableEntryBase leaf; + var leaf = null; if (lastIndex != -1) { if (index == lastIndex) { leaf = lastIndexLeaf; @@ -1309,7 +1339,7 @@ class _TreeTable extends _TreeTableBase { } leaf ??= _getEntryAt(index); - final _TreeTableBranchBase branch = leaf.parent; + final _TreeTableBranchBase? branch = leaf.parent; final _TreeTableBranchBase newBranch = leaf.createBranch(this) ..setLeft(value, false, sorted) // will set leaf.Parent ... ..right = leaf; @@ -1324,7 +1354,7 @@ class _TreeTable extends _TreeTableBase { insertFixup(newBranch, _inAddMode); if (value.isEntry()) { - _lastIndexLeaf = value; + _lastIndexLeaf = value as _TreeTableEntryBase; lastIndex = index; } else { _lastIndexLeaf = null; @@ -1352,7 +1382,7 @@ class _TreeTable extends _TreeTableBase { /// /// Returns the index of the key int indexOfKey(Object key) { - final _TreeTableEntryBase entry = findKey(key); + final _TreeTableEntryBase? entry = findKey(key); if (entry == null) { return -1; } @@ -1360,70 +1390,72 @@ class _TreeTable extends _TreeTableBase { return entry.getPosition(); } - void insertFixup(_TreeTableBranchBase x, bool inAddMode) { + void insertFixup(_TreeTableBranchBase? x, bool inAddMode) { // Check Red-Black properties - while (!_MathHelper.referenceEquals(x, _root) && - x.parent.color == _TreeTableNodeColor.red && - x.parent.parent != null) { + while (x != null && + x.parent != null && + !_MathHelper.referenceEquals(x, _root) && + x.parent!.color == _TreeTableNodeColor.red && + x.parent!.parent != null) { // We have a violation - if (x.parent == x.parent.parent.left) { - final _TreeTableBranchBase y = x.parent.parent.right; + if (x.parent == x.parent!.parent!.left) { + final y = x.parent!.parent?.right; if (y != null && y.color == _TreeTableNodeColor.red) { // uncle is red - x.parent.color = _TreeTableNodeColor.black; + x.parent!.color = _TreeTableNodeColor.black; y.color = _TreeTableNodeColor.black; - x.parent.parent.color = _TreeTableNodeColor.red; - x = x.parent.parent; + x.parent!.parent?.color = _TreeTableNodeColor.red; + x = x.parent!.parent; } else { // uncle is black - if (x == x.parent.right) { + if (x == x.parent!.right) { // Make x a left child x = x.parent; leftRotate(x, inAddMode); } // Recolor and rotate - x.parent.color = _TreeTableNodeColor.black; - x.parent.parent.color = _TreeTableNodeColor.red; - rightRotate(x.parent.parent, inAddMode); + x!.parent!.color = _TreeTableNodeColor.black; + x.parent!.parent!.color = _TreeTableNodeColor.red; + rightRotate(x.parent!.parent, inAddMode); } } else { // Mirror image of above code - final _TreeTableBranchBase y = x.parent.parent.left; + final y = x.parent!.parent?.left; if (y != null && y.color == _TreeTableNodeColor.red) { // uncle is red - x.parent.color = _TreeTableNodeColor.black; + x.parent!.color = _TreeTableNodeColor.black; y.color = _TreeTableNodeColor.black; - x.parent.parent.color = _TreeTableNodeColor.red; - x = x.parent.parent; + x.parent!.parent!.color = _TreeTableNodeColor.red; + x = x.parent!.parent; } else { // uncle is black - if (x == x.parent.left) { + if (x == x.parent!.left) { x = x.parent; rightRotate(x, inAddMode); } - x.parent.color = _TreeTableNodeColor.black; - x.parent.parent.color = _TreeTableNodeColor.red; - leftRotate(x.parent.parent, inAddMode); + x!.parent!.color = _TreeTableNodeColor.black; + x.parent!.parent!.color = _TreeTableNodeColor.red; + leftRotate(x.parent!.parent, inAddMode); } } } - root.color = _TreeTableNodeColor.black; + root!.color = _TreeTableNodeColor.black; } /// Gets the number of leaves. /// /// Returns the number of leaves. - int getCount() => _root == null ? 0 : _root.getCount(); + int getCount() => _root == null ? 0 : _root!.getCount(); /// Gets a [TreeTableEnumerator]. /// /// Returns a [TreeTableEnumerator]. _TreeTableEnumerator getEnumeratorBase() => _TreeTableEnumerator(this); - _TreeTableEntryBase _getEntryAt(int index) { + _TreeTableEntryBase? _getEntryAt(int index) { final int treeCount = getCount(); if (index < 0 || index >= treeCount) { throw ArgumentError( @@ -1432,7 +1464,7 @@ class _TreeTable extends _TreeTableBase { if (_root == null) { // replace root - return _root; + return null; } else { if (lastIndex != -1) { if (index == lastIndex) { @@ -1445,71 +1477,74 @@ class _TreeTable extends _TreeTableBase { // find node _TreeTableBranchBase branch; - _TreeTableNodeBase current = _root; + _TreeTableNodeBase? current = _root; int count = 0; - while (!current.isEntry()) { - branch = current; - final int leftCount = branch.left.getCount(); + while (current != null && !current.isEntry()) { + branch = current as _TreeTableBranchBase; + final int leftCount = branch.left!.getCount(); if (index < count + leftCount) { current = branch.left; } else { - count += branch.left.getCount(); + count += branch.left!.getCount(); current = branch.right; } } - lastIndexLeaf = current; + if (current is _TreeTableEntryBase) { + lastIndexLeaf = current; + } lastIndex = index; return _lastIndexLeaf; } } - _TreeTableEntryBase getMostLeftEntry(_TreeTableBranchBase parent) { - _TreeTableNodeBase next; + _TreeTableEntryBase? getMostLeftEntry(_TreeTableBranchBase? parent) { + _TreeTableNodeBase? next; if (parent == null) { next = null; return null; } else { next = parent.left; - while (!next.isEntry()) { - final _TreeTableBranchBase _next = next; + while (!next!.isEntry()) { + final _TreeTableBranchBase _next = next as _TreeTableBranchBase; next = _next.left; } } - return next; + return next as _TreeTableEntryBase; } _TreeTableNodeBase _getSisterNode( _TreeTableBranchBase leafsParent, _TreeTableNodeBase node) { - final _TreeTableNodeBase sisterNode = - _MathHelper.referenceEquals(leafsParent.left, node) - ? leafsParent.right - : leafsParent.left; - return sisterNode; + final sisterNode = _MathHelper.referenceEquals(leafsParent.left!, node) + ? leafsParent.right + : leafsParent.left; + + return sisterNode as _TreeTableNodeBase; } - void leftRotate(_TreeTableBranchBase x, bool inAddMode) { - final _TreeTableBranchBase y = x.right; + void leftRotate(_TreeTableBranchBase? x, bool inAddMode) { + final _TreeTableBranchBase? y = x?.right! as _TreeTableBranchBase; if (y == null) { return; } - final _TreeTableNodeBase yLeft = y.left; - y.setLeft(_TreeTableEmpty.empty, inAddMode, sorted); - x.setRight(yLeft, inAddMode); - if (x.parent != null) { - if (_MathHelper.referenceEquals(x, x.parent.left)) { - x.parent.setLeft(y, inAddMode, sorted); + if (y.left is _TreeTableNodeBase) { + y.setLeft(_TreeTableEmpty.empty, inAddMode, sorted); + x!.setRight(y.left, inAddMode); + if (x.parent != null) { + if (_MathHelper.referenceEquals(x, x.parent!.left)) { + x.parent!.setLeft(y, inAddMode, sorted); + } else { + x.parent!.setRight(y, inAddMode); + } } else { - x.parent.setRight(y, inAddMode); + _root = y; } - } else { - _root = y; + y.setLeft(x, inAddMode, sorted); } - y.setLeft(x, inAddMode, sorted); } /// Removes the specified node. @@ -1525,7 +1560,7 @@ class _TreeTable extends _TreeTableBase { /// * resetParent - _required_ - Boolean value /// /// Returns the boolean value - bool _remove(_TreeTableNodeBase value, bool resetParent) { + bool _remove(_TreeTableNodeBase? value, bool resetParent) { if (value == null) { return false; } @@ -1547,16 +1582,16 @@ class _TreeTable extends _TreeTableBase { value.parent = null; } } else { - final _TreeTableBranchBase leafsParent = value.parent; + final _TreeTableBranchBase? leafsParent = value.parent; // get the sister node - final _TreeTableNodeBase sisterNode = _getSisterNode(leafsParent, value); + final _TreeTableNodeBase sisterNode = _getSisterNode(leafsParent!, value); // swap out leaves parent with sister if (_MathHelper.referenceEquals(leafsParent, _root)) { _root = sisterNode..parent = null; } else { - final _TreeTableBranchBase leafsParentParent = leafsParent.parent; + final _TreeTableBranchBase leafsParentParent = leafsParent.parent!; final bool isLeft = leafsParentParent.left == leafsParent; _replaceNode(leafsParentParent, leafsParent, sisterNode, false); @@ -1581,31 +1616,31 @@ class _TreeTable extends _TreeTableBase { _lastIndexLeaf = null; } - void _replaceNode(_TreeTableBranchBase branch, _TreeTableNodeBase oldNode, - _TreeTableNodeBase newNode, bool inAddMode) { + void _replaceNode(_TreeTableBranchBase? branch, _TreeTableNodeBase? oldNode, + _TreeTableNodeBase? newNode, bool inAddMode) { // also updates node count. - if (_MathHelper.referenceEquals(branch.left, oldNode)) { - branch.setLeft(newNode, inAddMode, sorted); + if (_MathHelper.referenceEquals(branch?.left, oldNode)) { + branch?.setLeft(newNode, inAddMode, sorted); } else { - branch.setRight(newNode, inAddMode); + branch?.setRight(newNode, inAddMode); } } - void rightRotate(_TreeTableBranchBase x, bool inAddMode) { - final _TreeTableBranchBase y = x.left; + void rightRotate(_TreeTableBranchBase? x, bool inAddMode) { + final _TreeTableBranchBase? y = x?.left as _TreeTableBranchBase; if (y == null) { return; } - final _TreeTableNodeBase yRight = y.right; + final _TreeTableNodeBase yRight = y.right as _TreeTableNodeBase; y.setRight(_TreeTableEmpty.empty, inAddMode); // make sure Parent is not reset later - x.setLeft(yRight, inAddMode, sorted); + x!.setLeft(yRight, inAddMode, sorted); if (x.parent != null) { - if (x == x.parent.right) { - x.parent.setRight(y, inAddMode); + if (x == x.parent!.right) { + x.parent!.setRight(y, inAddMode); } else { - x.parent.setLeft(y, inAddMode, sorted); + x.parent!.setLeft(y, inAddMode, sorted); } } else { _root = y; @@ -1618,12 +1653,14 @@ class _TreeTable extends _TreeTableBase { /// * index - _required_ - Index value where the node is to be inserted. /// * value - _required_ - Value of the node that is to be inserted. void setNodeAt(int index, _TreeTableNodeBase value) { - final _TreeTableEntryBase leaf = _getEntryAt(index); + final _TreeTableEntryBase? leaf = _getEntryAt(index); if (_MathHelper.referenceEquals(leaf, _root)) { _root = value; } else { - final _TreeTableBranchBase branch = leaf.parent; - _replaceNode(branch, leaf, value, false); + if (leaf != null) { + final _TreeTableBranchBase branch = leaf.parent as _TreeTableBranchBase; + _replaceNode(branch, leaf, value, false); + } } lastIndex = -1; @@ -1681,7 +1718,9 @@ class _TreeTable extends _TreeTableBase { /// * index - _required_ - The starting index in the destination array. @override void copyTo(List array, int index) { - copyToBase(array, index); + if (array is List<_TreeTableNodeBase>) { + copyToBase(array, index); + } } /// Ends optimization of insertion of elements when tree is initialized @@ -1691,7 +1730,7 @@ class _TreeTable extends _TreeTableBase { _inAddMode = false; // Fixes issues when GetCount() was called while debugging ... - final Object branch = _root; + final Object? branch = _root; if (branch is _TreeTableBranch && branch.entryCount != -1) { branch.entryCount = -1; } @@ -1734,9 +1773,9 @@ class _TreeTable extends _TreeTableBase { /// /// Returns next subsequent entry @override - _TreeTableEntryBase getNextEntry(_TreeTableEntryBase current) { - _TreeTableBranchBase parent = current.parent; - _TreeTableNodeBase next; + _TreeTableEntryBase? getNextEntry(_TreeTableEntryBase? current) { + _TreeTableBranchBase? parent = current?.parent; + _TreeTableNodeBase? next; if (parent == null) { next = null; @@ -1745,11 +1784,11 @@ class _TreeTable extends _TreeTableBase { if (_MathHelper.referenceEquals(current, parent.left)) { next = parent.right; } else { - _TreeTableBranchBase parentParent = parent.parent; + _TreeTableBranchBase? parentParent = parent.parent; if (parentParent == null) { return null; } else { - while (_MathHelper.referenceEquals(parentParent.right, parent)) { + while (_MathHelper.referenceEquals(parentParent!.right, parent)) { parent = parentParent; parentParent = parentParent.parent; if (parentParent == null) { @@ -1761,13 +1800,18 @@ class _TreeTable extends _TreeTableBase { } } - while (!next.isEntry()) { - final _TreeTableBranchBase _next = next; - next = _next.left; + while (!next!.isEntry()) { + if (next is _TreeTableBranchBase) { + next = next.left; + } } } - return next; + if (next is _TreeTableEntryBase) { + return next; + } else { + return null; + } } /// Optimized access to the previous entry. @@ -1776,9 +1820,9 @@ class _TreeTable extends _TreeTableBase { /// /// Returns previous entry @override - _TreeTableEntryBase getPreviousEntry(_TreeTableEntryBase current) { - _TreeTableBranchBase parent = current.parent; - _TreeTableNodeBase prev; + _TreeTableEntryBase? getPreviousEntry(_TreeTableEntryBase current) { + _TreeTableBranchBase? parent = current.parent; + _TreeTableNodeBase? prev; if (parent == null) { prev = null; @@ -1787,11 +1831,11 @@ class _TreeTable extends _TreeTableBase { if (_MathHelper.referenceEquals(current, parent.right)) { prev = parent.left; } else { - _TreeTableBranchBase parentParent = parent.parent; + _TreeTableBranchBase? parentParent = parent.parent; if (parentParent == null) { return null; } else { - while (_MathHelper.referenceEquals(parentParent.left, parent)) { + while (_MathHelper.referenceEquals(parentParent!.left, parent)) { parent = parentParent; parentParent = parentParent.parent; if (parentParent == null) { @@ -1803,20 +1847,25 @@ class _TreeTable extends _TreeTableBase { } } - while (!prev.isEntry()) { - final _TreeTableBranchBase _prev = prev; - prev = _prev.right; + while (!prev!.isEntry()) { + if (prev is _TreeTableBranchBase) { + prev = prev.right; + } } } - return prev; + if (prev is _TreeTableEntryBase) { + return prev; + } else { + return null; + } } /// Removes the node with the specified value. /// /// * value - _required_ - Value needs to be remove @override - bool remove(Object value) { + bool remove(Object? value) { if (value is _TreeTableNodeBase) { return removeBase(value); } else { @@ -1838,12 +1887,14 @@ class _TreeTable extends _TreeTableBase { /// /// Returns the item at the specified index. @override - _TreeTableNodeBase operator [](int index) => _getEntryAt(index); + _TreeTableNodeBase? operator [](int index) => _getEntryAt(index); /// Sets an item at the specified index. @override void operator []=(int index, Object value) { - setNodeAt(index, value); + if (value is _TreeTableNodeBase) { + setNodeAt(index, value); + } } void lastIndexLeafDisposed() { @@ -1860,20 +1911,21 @@ class _TreeTableEnumerator implements _EnumeratorBase { _tree = tree; _cursor = null; if (tree.count > 0 && (tree[0] is _TreeTableNodeBase)) { - _next = tree[0]; + _next = tree[0] as _TreeTableNodeBase; } } - _TreeTableNodeBase _cursor, _next; - _TreeTableBase _tree; + _TreeTableNodeBase? _cursor; + _TreeTableNodeBase? _next; + _TreeTableBase? _tree; /// Gets the current enumerator. - Object get current => currentBase; + Object? get current => currentBase; /// Gets the current node. - _TreeTableEntryBase get currentBase { + _TreeTableEntryBase? get currentBase { if (_cursor is _TreeTableEntryBase) { - return _cursor; + return _cursor as _TreeTableEntryBase; } else { return null; } @@ -1890,7 +1942,7 @@ class _TreeTableEnumerator implements _EnumeratorBase { _cursor = _next; - _TreeTableBranchBase _parent = _cursor.parent; + _TreeTableBranchBase? _parent = _cursor!.parent; if (_parent == null) { _next = null; @@ -1899,12 +1951,12 @@ class _TreeTableEnumerator implements _EnumeratorBase { if (_MathHelper.referenceEquals(_cursor, _parent.left)) { _next = _parent.right; } else { - _TreeTableBranchBase parentParent = _parent.parent; + _TreeTableBranchBase? parentParent = _parent.parent; if (parentParent == null) { _next = null; return true; } else { - while (_MathHelper.referenceEquals(parentParent.right, _parent)) { + while (_MathHelper.referenceEquals(parentParent!.right, _parent)) { _parent = parentParent; parentParent = parentParent.parent; if (parentParent == null) { @@ -1917,8 +1969,8 @@ class _TreeTableEnumerator implements _EnumeratorBase { } } - while (!_next.isEntry()) { - final _TreeTableBranchBase next = _next; + while (!_next!.isEntry()) { + final _TreeTableBranchBase next = _next! as _TreeTableBranchBase; _next = next.left; } } @@ -1930,8 +1982,11 @@ class _TreeTableEnumerator implements _EnumeratorBase { @override void reset() { _cursor = null; - if (_tree.count > 0 && (_tree[0] is _TreeTableNodeBase)) { - _next = _tree[0]; + if (_tree != null && + _tree!.count > 0 && + _tree?[0] != null && + (_tree![0] is _TreeTableNodeBase)) { + _next = _tree![0] as _TreeTableNodeBase; } else { _next = null; } @@ -1941,11 +1996,11 @@ class _TreeTableEnumerator implements _EnumeratorBase { /// An object that holds an [_TreeTableEntryBase]. class _TreeTableEntryBaseSource { /// Gets a reference to the [_TreeTableEntryBase]. - _TreeTableEntryBase get entry => _entry; - _TreeTableEntryBase _entry; + _TreeTableEntryBase? get entry => _entry; + _TreeTableEntryBase? _entry; /// Sets a reference to the [_TreeTableEntryBase]. - set entry(_TreeTableEntryBase value) { + set entry(_TreeTableEntryBase? value) { if (value == _entry) { return; } @@ -1962,7 +2017,7 @@ class _TreeTableEntrySourceCollection extends _ListBase { inner = _TreeTable(false); } - _TreeTableBase inner; + late _TreeTableBase inner; /// Gets the number of objects in this collection. @override @@ -2005,23 +2060,23 @@ class _TreeTableEntrySourceCollection extends _ListBase { /// * value - _required_ - The value of the object. /// /// Returns `True` if object belongs to the collection. `false` otherwise. - bool containsBase(_TreeTableEntryBaseSource value) { - if (value == null) { + bool containsBase(_TreeTableEntryBaseSource? value) { + if (value == null || value.entry == null) { return false; } - return inner.contains(value.entry); + return inner.contains(value.entry!); } /// Copies the contents of the collection to an array. /// /// * array - _required_ - Destination array. /// * index - _required_ - Starting index of the destination array. - void copyToBase(List<_TreeTableEntryBaseSource> array, int index) { + void copyToBase(List<_TreeTableEntryBaseSource>? array, int index) { final int count = inner.count; for (int n = 0; n < count; n++) { final Object _n = [n]; - if (_n is _TreeTableEntryBaseSource) { + if (_n is _TreeTableEntryBaseSource && array != null) { array[index + n] = _n; } } @@ -2037,7 +2092,7 @@ class _TreeTableEntrySourceCollection extends _ListBase { /// /// * index - _required_ - Index value where the object is to be inserted. /// * value - _required_ - Value of the object to insert. - void insertBase(int index, _TreeTableEntryBaseSource value) { + void insertBase(int index, _TreeTableEntryBaseSource? value) { if (value == null) { return; } @@ -2050,8 +2105,8 @@ class _TreeTableEntrySourceCollection extends _ListBase { /// Returns the position of a object in the collection. /// * value - _required_ - The value of the object. /// Returns - _required_ - the position of the object. - int indexOfBase(_TreeTableEntryBaseSource value) => - inner.indexOf(value.entry); + int indexOfBase(_TreeTableEntryBaseSource? value) => + (value != null && value.entry != null) ? inner.indexOf(value.entry!) : -1; /// Removes a node at the specified index. /// @@ -2063,12 +2118,12 @@ class _TreeTableEntrySourceCollection extends _ListBase { /// Removes the object. /// /// * value - _required_ - The value of the object to remove. - void removeBase(_TreeTableEntryBaseSource value) { - if (value == null) { + void removeBase(_TreeTableEntryBaseSource? value) { + if (value == null || value.entry == null) { return; } - inner.remove(value.entry); + inner.remove(value.entry!); } /// Adds the specified object to the collection. @@ -2111,7 +2166,9 @@ class _TreeTableEntrySourceCollection extends _ListBase { /// * index - _required_ - Starting index of the destination array. @override void copyTo(List array, int index) { - copyToBase(array, index); + if (array is List<_TreeTableEntryBaseSource>) { + copyToBase(array, index); + } } /// Returns a strongly typed enumerator. @@ -2124,8 +2181,10 @@ class _TreeTableEntrySourceCollection extends _ListBase { /// * index - _required_ - Index value of the object to insert. /// * value - _required_ - Value of the object to insert. @override - void insert(int index, Object value) { - insertBase(index, value); + void insert(int index, Object? value) { + if (value is _TreeTableEntryBaseSource) { + insertBase(index, value); + } } /// Returns the index of the specified object. @@ -2147,7 +2206,9 @@ class _TreeTableEntrySourceCollection extends _ListBase { /// * value - _required_ - Value of the object to remove. @override void remove(Object value) { - removeBase(value); + if (value is _TreeTableEntryBaseSource) { + removeBase(value); + } } /// Sets an `_TreeTableEntryBaseSource` at a specific position. @@ -2170,10 +2231,10 @@ class _TreeTableEntrySourceCollection extends _ListBase { /// /// Returns the entry value for the specified position. @override - _TreeTableEntryBaseSource operator [](num index) { - final Object entry = inner[index]; + _TreeTableEntryBaseSource? operator [](num index) { + final Object? entry = inner[index.toInt()]; if (entry is _TreeTableEntryBase) { - return entry.value; + return entry.value as _TreeTableEntryBaseSource; } else { return null; } @@ -2191,15 +2252,16 @@ class _TreeTableEntrySourceCollectionEnumerator implements _EnumeratorBase { inner = _TreeTableEnumerator(collection.inner); } - _TreeTableEnumerator inner; + _TreeTableEnumerator? inner; /// Gets the current enumerator. - Object get current => currentBase; + Object? get current => currentBase; /// Gets the current `_TreeTableEntryBaseSource` object. - _TreeTableEntryBaseSource get currentBase { - if (inner.currentBase.value is _TreeTableEntryBaseSource) { - return inner.currentBase.value; + _TreeTableEntryBaseSource? get currentBase { + if (inner != null && + inner?.currentBase?.value is _TreeTableEntryBaseSource) { + return inner?.currentBase?.value as _TreeTableEntryBaseSource; } else { return null; } @@ -2210,11 +2272,11 @@ class _TreeTableEntrySourceCollectionEnumerator implements _EnumeratorBase { /// Returns the boolean value indicates whether to move to the next object /// in the collection. @override - bool moveNext() => inner.moveNext(); + bool moveNext() => inner?.moveNext() ?? false; /// Resets the enumerator. @override void reset() { - inner.reset(); + inner?.reset(); } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_counter.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_counter.dart index f2a318cbd..fcfbdd209 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_counter.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_counter.dart @@ -5,12 +5,12 @@ mixin _TreeTableCounterNodeBase on _TreeTableSummaryNodeBase { /// Gets the cumulative position of this node. /// /// Returns Returns the cumulative position of this node. - _TreeTableCounterBase get getCounterPosition; + _TreeTableCounterBase? get getCounterPosition; /// The total of this node's counter and child nodes. /// /// Returns the total of this node's counter and child nodes (cached). - _TreeTableCounterBase getCounterTotal(); + _TreeTableCounterBase? getCounterTotal(); /// Marks all counters dirty in this node and child nodes. /// @@ -41,7 +41,7 @@ abstract class _TreeTableCounterBase { /// /// Returns the kind. int get kind => _kind; - int _kind; + int _kind = -1; /// Combines this counter object with another counter and returns a /// new object. A cookie can specify a specific counter type. @@ -50,7 +50,7 @@ abstract class _TreeTableCounterBase { /// * cookie - _required_ - Cookie value. /// /// Returns the new object - _TreeTableCounterBase combine(_TreeTableCounterBase other, int cookie); + _TreeTableCounterBase combine(_TreeTableCounterBase? other, int cookie); /// Compares this counter with another counter. A cookie can specify /// a specific counter type. @@ -59,7 +59,7 @@ abstract class _TreeTableCounterBase { /// * cookie - _required_ - The cookie. /// /// Returns the compared value. - double compare(_TreeTableCounterBase other, int cookie); + double compare(_TreeTableCounterBase? other, int cookie); /// Indicates whether the counter object is empty. A cookie can specify /// a specific counter type. @@ -94,22 +94,22 @@ class _TreeTableWithCounterBranch extends _TreeTableWithSummaryBranch /// * tree - _required_ - Tree instance _TreeTableWithCounterBranch(_TreeTable tree) : super(tree); - _TreeTableCounterBase counter; + _TreeTableCounterBase? counter; /// Gets the parent branch. @override - _TreeTableWithCounterBranch get parent { + _TreeTableWithCounterBranch? get parent { if (super.parent is _TreeTableWithCounterBranch) { - return super.parent; + return super.parent as _TreeTableWithCounterBranch; } else { return null; } } /// Gets the tree this branch belongs to. - _TreeTableWithCounter get treeTableWithCounter { + _TreeTableWithCounter? get treeTableWithCounter { if (tree is _TreeTableWithCounter) { - return tree; + return tree as _TreeTableWithCounter; } else { return null; } @@ -127,7 +127,7 @@ class _TreeTableWithCounterBranch extends _TreeTableWithSummaryBranch if (_MathHelper.referenceEquals(node, right)) { return pos.combine( - getLeftNode().getCounterTotal(), _TreeTableCounterCookies.countAll); + getLeftNode()?.getCounterTotal(), _TreeTableCounterCookies.countAll); } else if (_MathHelper.referenceEquals(node, left)) { return pos; } @@ -141,10 +141,10 @@ class _TreeTableWithCounterBranch extends _TreeTableWithSummaryBranch @override _TreeTableCounterBase get getCounterPosition { if (parent == null) { - return treeTableWithCounter.getStartCounterPosition(); + return treeTableWithCounter!.getStartCounterPosition(); } - return parent.getCounterPositionOfChild(this); + return parent!.getCounterPositionOfChild(this); } /// Gets the total of this node's counter and child nodes (cached). @@ -152,12 +152,12 @@ class _TreeTableWithCounterBranch extends _TreeTableWithSummaryBranch /// Returns Returns the total of this node's counter and child /// nodes (cached). @override - _TreeTableCounterBase getCounterTotal() { - if (tree.isInitializing) { + _TreeTableCounterBase? getCounterTotal() { + if (tree!.isInitializing) { return null; } else if (counter == null) { - final _TreeTableCounterBase _left = getLeftNode().getCounterTotal(); - final _TreeTableCounterBase _right = getRightNode().getCounterTotal(); + final _TreeTableCounterBase? _left = getLeftNode()?.getCounterTotal(); + final _TreeTableCounterBase? _right = getRightNode()?.getCounterTotal(); if (_left != null && _right != null) { counter = _left.combine(_right, _TreeTableCounterCookies.countAll); } @@ -170,9 +170,9 @@ class _TreeTableWithCounterBranch extends _TreeTableWithSummaryBranch /// /// Returns the left branch node cast to ITreeTableCounterNode. @override - _TreeTableCounterNodeBase getLeftNode() { + _TreeTableCounterNodeBase? getLeftNode() { if (left is _TreeTableCounterNodeBase) { - return left; + return left as _TreeTableCounterNodeBase; } else { return null; } @@ -182,15 +182,15 @@ class _TreeTableWithCounterBranch extends _TreeTableWithSummaryBranch /// /// Returns the left branch node cast to ITreeTableCounterNode. @override - _TreeTableCounterNodeBase getLeftC() => getLeftNode(); + _TreeTableCounterNodeBase? getLeftC() => getLeftNode(); /// The right branch node cast to ITreeTableCounterNode. /// /// Returns the right branch node cast to ITreeTableCounterNode. @override - _TreeTableCounterNodeBase getRightNode() { + _TreeTableCounterNodeBase? getRightNode() { if (right is _TreeTableCounterNodeBase) { - return right; + return right as _TreeTableCounterNodeBase; } else { return null; } @@ -200,26 +200,26 @@ class _TreeTableWithCounterBranch extends _TreeTableWithSummaryBranch /// /// Returns the right branch node cast to ITreeTableCounterNode. @override - _TreeTableCounterNodeBase getRightC() => getRightNode(); + _TreeTableCounterNodeBase? getRightC() => getRightNode(); /// Invalidates the counter bottom up. /// /// * notifyCounterSource - _required_ - If set to true notify counter source. @override void invalidateCounterBottomUp(bool notifyCounterSource) { - if (tree.isInitializing) { + if (tree!.isInitializing) { return; } counter = null; if (parent != null) { - parent.invalidateCounterBottomUp(notifyCounterSource); + parent!.invalidateCounterBottomUp(notifyCounterSource); } else if (notifyCounterSource) { - final Object _tree = tree; + final Object _tree = tree!; if (_tree is _TreeTableWithCounter) { - _TreeTableCounterSourceBase tcs; + _TreeTableCounterSourceBase? tcs; if (_tree.tag is _TreeTableCounterSourceBase) { - tcs = _tree.tag; + tcs = _tree.tag as _TreeTableCounterSourceBase; } if (tcs != null) { @@ -239,25 +239,25 @@ class _TreeTableWithCounterBranch extends _TreeTableWithSummaryBranch /// * notifyCounterSource - _required_ - If set to true notify counter source. @override void invalidateCounterTopDown(bool notifyCounterSource) { - if (tree.isInitializing) { + if (tree!.isInitializing) { return; } counter = null; - getLeftNode().invalidateCounterTopDown(notifyCounterSource); - getRightNode().invalidateCounterTopDown(notifyCounterSource); + getLeftNode()?.invalidateCounterTopDown(notifyCounterSource); + getRightNode()?.invalidateCounterTopDown(notifyCounterSource); } } /// A tree leaf with value, sort key and counter information. class _TreeTableWithCounterEntry extends _TreeTableWithSummaryEntryBase with _TreeTableCounterNodeBase { - _TreeTableCounterBase _counter; + _TreeTableCounterBase? _counter; /// Gets the tree this leaf belongs to. - _TreeTableWithCounter get treeTableWithCounter { + _TreeTableWithCounter? get treeTableWithCounter { if (super.tree is _TreeTableWithCounter) { - return super.tree; + return super.tree as _TreeTableWithCounter; } else { return null; } @@ -265,9 +265,9 @@ class _TreeTableWithCounterEntry extends _TreeTableWithSummaryEntryBase /// Gets the parent branch. @override - _TreeTableWithCounterBranch get parent { + _TreeTableWithCounterBranch? get parent { if (super.parent is _TreeTableWithCounterBranch) { - return super.parent; + return super.parent as _TreeTableWithCounterBranch; } else { return null; } @@ -275,7 +275,7 @@ class _TreeTableWithCounterEntry extends _TreeTableWithSummaryEntryBase /// Sets the parent branch. @override - set parent(Object value) { + set parent(Object? value) { super.parent = value; } @@ -283,16 +283,16 @@ class _TreeTableWithCounterEntry extends _TreeTableWithSummaryEntryBase /// /// Returns Returns the cumulative position of this node. @override - _TreeTableCounterBase get getCounterPosition { + _TreeTableCounterBase? get getCounterPosition { if (parent == null) { if (treeTableWithCounter == null) { return null; } - return treeTableWithCounter.getStartCounterPosition(); + return treeTableWithCounter?.getStartCounterPosition(); } - return parent.getCounterPositionOfChild(this); + return parent?.getCounterPositionOfChild(this); } /// Indicates whether the counter was set dirty. @@ -313,9 +313,9 @@ class _TreeTableWithCounterEntry extends _TreeTableWithSummaryEntryBase /// Gets the value as `_TreeTableCounterSourceBase`. /// /// Returns the value as `_TreeTableCounterSourceBase`. - _TreeTableCounterSourceBase getCounterSource() { + _TreeTableCounterSourceBase? getCounterSource() { if (value is _TreeTableCounterSourceBase) { - return value; + return value as _TreeTableCounterSourceBase; } else { return null; } @@ -327,13 +327,13 @@ class _TreeTableWithCounterEntry extends _TreeTableWithSummaryEntryBase @override _TreeTableCounterBase getCounterTotal() { if (_counter == null) { - final _TreeTableCounterSourceBase source = getCounterSource(); + final _TreeTableCounterSourceBase? source = getCounterSource(); if (source != null) { _counter = source.getCounter(); } } - return _counter; + return _counter!; } /// Reset cached counter. @@ -348,14 +348,14 @@ class _TreeTableWithCounterEntry extends _TreeTableWithSummaryEntryBase void invalidateCounterBottomUp(bool notifyCounterSource) { _counter = null; if (parent != null) { - parent.invalidateCounterBottomUp(notifyCounterSource); + parent!.invalidateCounterBottomUp(notifyCounterSource); } else if (notifyCounterSource) { - final Object _tree = tree; + final Object _tree = tree!; if (_tree is _TreeTableWithCounter) { - _TreeTableCounterSourceBase tcs; + _TreeTableCounterSourceBase? tcs; if (_tree.tag is _TreeTableCounterSourceBase) { - tcs = _tree.tag; + tcs = _tree.tag as _TreeTableCounterSourceBase; } if (tcs != null) { @@ -377,7 +377,7 @@ class _TreeTableWithCounterEntry extends _TreeTableWithSummaryEntryBase void invalidateCounterTopDown(bool notifyCounterSource) { _counter = null; if (notifyCounterSource) { - final _TreeTableCounterSourceBase source = getCounterSource(); + final _TreeTableCounterSourceBase? source = getCounterSource(); if (notifyCounterSource && source != null) { source.invalidateCounterTopDown(notifyCounterSource); } @@ -396,16 +396,16 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { _startPos = startPosition; } - _TreeTableCounterBase _startPos; + late _TreeTableCounterBase _startPos; /// Gets an object that implements the /// [Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances] property. - _TreeTableCounterSourceBase get parentCounterSource => _parentCounterSource; - _TreeTableCounterSourceBase _parentCounterSource; + _TreeTableCounterSourceBase? get parentCounterSource => _parentCounterSource; + _TreeTableCounterSourceBase? _parentCounterSource; /// Sets an object that implements the /// [Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances] property. - set parentCounterSource(_TreeTableCounterSourceBase value) { + set parentCounterSource(_TreeTableCounterSourceBase? value) { if (value == _parentCounterSource) { return; } @@ -416,13 +416,13 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { /// Gets the total of all counters in this tree. /// /// Returns the total of all counters in this tree. - _TreeTableCounterBase getCounterTotal() { + _TreeTableCounterBase? getCounterTotal() { if (root == null) { return _startPos; } - final Object _root = root; - if (_root is _TreeTableCounterNodeBase) { + final Object? _root = root; + if (_root != null && _root is _TreeTableCounterNodeBase) { return _root.getCounterTotal(); } else { return null; @@ -436,7 +436,7 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { /// * cookie - _required_ - The cookie. /// /// Returns an entry at the specified counter position. - _TreeTableWithCounterEntry getEntryAtCounterPosition( + _TreeTableWithCounterEntry? getEntryAtCounterPosition( _TreeTableCounterBase searchPosition, int cookie) => getEntryAtCounterPositionWithForParameter( getStartCounterPosition(), searchPosition, cookie, false); @@ -450,7 +450,7 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { /// be returned if multiple tree elements have the same SearchPosition. /// /// Returns an entry at the specified counter position. - _TreeTableWithCounterEntry getEntryAtCounterPositionwithThreeParameter( + _TreeTableWithCounterEntry? getEntryAtCounterPositionwithThreeParameter( _TreeTableCounterBase searchPosition, int cookie, bool preferLeftMost) => @@ -465,7 +465,7 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { /// * preferLeftMost - _required_ - If set to true prefer left most. /// /// Returns an entry at the specified counter position. - _TreeTableWithCounterEntry getEntryAtCounterPositionWithForParameter( + _TreeTableWithCounterEntry? getEntryAtCounterPositionWithForParameter( _TreeTableCounterBase start, _TreeTableCounterBase searchPosition, int cookie, @@ -482,9 +482,9 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { return null; } else { // find node - final _TreeTableNodeBase currentNode = root; + final _TreeTableNodeBase? currentNode = root; final _TreeTableCounterBase currentNodePosition = start; - return getEntryAtCounterPositionWithSixParameter(currentNode, start, + return getEntryAtCounterPositionWithSixParameter(currentNode!, start, searchPosition, cookie, preferLeftMost, currentNodePosition); } } @@ -507,30 +507,33 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { _TreeTableCounterBase searchPosition, int cookie, bool preferLeftMost, - _TreeTableCounterBase currentNodePosition) { - _TreeTableWithCounterBranch savedBranch; + _TreeTableCounterBase? currentNodePosition) { + _TreeTableWithCounterBranch? savedBranch; currentNodePosition = start; while (!currentNode.isEntry()) { - final _TreeTableWithCounterBranch branch = currentNode; - final _TreeTableCounterNodeBase leftB = branch.left; + final _TreeTableWithCounterBranch branch = + currentNode as _TreeTableWithCounterBranch; + final _TreeTableCounterNodeBase leftB = + branch.left as _TreeTableCounterNodeBase; final _TreeTableCounterBase rightNodePosition = - currentNodePosition.combine(leftB.getCounterTotal(), cookie); + currentNodePosition!.combine(leftB.getCounterTotal(), cookie); if (searchPosition.compare(rightNodePosition, cookie) < 0) { - currentNode = branch.left; + currentNode = branch.left!; } else if (preferLeftMost && searchPosition.compare(currentNodePosition, cookie) == 0) { while (!currentNode.isEntry()) { - final _TreeTableWithCounterBranch branch = currentNode; - currentNode = branch.left; + final _TreeTableWithCounterBranch branch = + currentNode as _TreeTableWithCounterBranch; + currentNode = branch.left!; } } else { if (preferLeftMost && searchPosition.compare(rightNodePosition, cookie) == 0) { - _TreeTableCounterBase currentNode2Position; + _TreeTableCounterBase? currentNode2Position; final _TreeTableNodeBase currentNode2 = getEntryAtCounterPositionWithSixParameter( - branch.left, + branch.left!, currentNodePosition, searchPosition, cookie, @@ -541,17 +544,17 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { currentNodePosition = currentNode2Position; } else { currentNodePosition = rightNodePosition; - currentNode = branch.right; + currentNode = branch.right!; } } else { savedBranch ??= branch; currentNodePosition = rightNodePosition; - currentNode = branch.right; + currentNode = branch.right!; } } } - return currentNode; + return currentNode as _TreeTableWithCounterEntry; } /// Gets the subsequent entry in the collection for which the specific @@ -562,10 +565,10 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { /// /// Returns the subsequent entry in the collection for which the /// specific counter is not empty. - _TreeTableEntryBase getNextNotEmptyCounterEntry( + _TreeTableEntryBase? getNextNotEmptyCounterEntry( _TreeTableEntryBase current, int cookie) { - _TreeTableBranchBase parent = current.parent; - _TreeTableNodeBase next; + _TreeTableBranchBase? parent = current.parent; + _TreeTableNodeBase? next; if (parent == null) { next = null; @@ -574,14 +577,14 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { next = current; // walk up until we find a branch that has visible entries do { - if (_MathHelper.referenceEquals(next, parent.left)) { + if (_MathHelper.referenceEquals(next, parent!.left)) { next = parent.right; } else { - _TreeTableBranchBase parentParent = parent.parent; + _TreeTableBranchBase? parentParent = parent.parent; if (parentParent == null) { return null; } else { - while (_MathHelper.referenceEquals(parentParent.right, parent) + while (_MathHelper.referenceEquals(parentParent!.right, parent) // for something that most likely went wrong when // adding the node or when doing a rotation ... || @@ -602,19 +605,20 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { } } while (next != null && (next is _TreeTableCounterNodeBase && - next.getCounterTotal().isEmpty(cookie))); + next.getCounterTotal()!.isEmpty(cookie))); // walk down to most left leaf that has visible entries - while (!next.isEntry()) { - final _TreeTableBranchBase branch = next; - final _TreeTableCounterNodeBase _left = branch.left; - next = !_left.getCounterTotal().isEmpty(cookie) + while (!next!.isEntry()) { + final _TreeTableBranchBase branch = next as _TreeTableBranchBase; + final _TreeTableCounterNodeBase _left = + branch.left as _TreeTableCounterNodeBase; + next = !_left.getCounterTotal()!.isEmpty(cookie) ? branch.left : branch.right; } } - return next; + return next as _TreeTableEntryBase; } /// Returns the previous entry in the collection for which the specific @@ -625,10 +629,10 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { /// /// Returns the previous entry in the collection for which the specific /// counter is not empty. - _TreeTableEntryBase getPreviousNotEmptyCounterEntry( + _TreeTableEntryBase? getPreviousNotEmptyCounterEntry( _TreeTableEntryBase current, int cookie) { - _TreeTableBranchBase parent = current.parent; - _TreeTableNodeBase next; + _TreeTableBranchBase? parent = current.parent; + _TreeTableNodeBase? next; if (parent == null) { next = null; @@ -637,14 +641,14 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { next = current; // walk up until we find a branch that has visible entries do { - if (_MathHelper.referenceEquals(next, parent.right)) { + if (_MathHelper.referenceEquals(next, parent!.right)) { next = parent.left; } else { - _TreeTableBranchBase parentParent = parent.parent; + _TreeTableBranchBase? parentParent = parent.parent; if (parentParent == null) { return null; } else { - while (_MathHelper.referenceEquals(parentParent.left, parent) + while (_MathHelper.referenceEquals(parentParent!.left, parent) // for something that most likely went wrong when // adding the node or when doing a rotation ... || @@ -664,19 +668,20 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { } } while (next != null && (next is _TreeTableCounterNodeBase && - next.getCounterTotal().isEmpty(cookie))); + next.getCounterTotal()!.isEmpty(cookie))); // walk down to most left leaf that has visible entries - while (!next.isEntry()) { - final _TreeTableBranchBase branch = next; - final _TreeTableCounterNodeBase _right = branch.right; - next = !_right.getCounterTotal().isEmpty(cookie) + while (!next!.isEntry()) { + final _TreeTableBranchBase branch = next as _TreeTableBranchBase; + final _TreeTableCounterNodeBase _right = + branch.right as _TreeTableCounterNodeBase; + next = !_right.getCounterTotal()!.isEmpty(cookie) ? branch.right : branch.left; } } - return next; + return next as _TreeTableEntryBase; } /// Gets the next entry in the collection for which CountVisible counter @@ -686,10 +691,16 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { /// /// Returns the next entry in the collection for which CountVisible counter /// is not empty. - _TreeTableWithCounterEntry getNextVisibleEntry( - _TreeTableWithCounterEntry current) => - getNextNotEmptyCounterEntry( - current, _TreeTableCounterCookies.countVisible); + _TreeTableWithCounterEntry? getNextVisibleEntry( + _TreeTableWithCounterEntry current) { + final nextCounterEntry = getNextNotEmptyCounterEntry( + current, _TreeTableCounterCookies.countVisible); + if (nextCounterEntry != null) { + return nextCounterEntry as _TreeTableWithCounterEntry; + } + + return null; + } /// Gets the previous entry in the collection for which CountVisible counter /// is not empty. @@ -698,10 +709,16 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { /// /// Returns the previous entry in the collection for which CountVisible /// counter is not empty. - _TreeTableWithCounterEntry getPreviousVisibleEntry( - _TreeTableWithCounterEntry current) => - getPreviousNotEmptyCounterEntry( - current, _TreeTableCounterCookies.countVisible); + _TreeTableWithCounterEntry? getPreviousVisibleEntry( + _TreeTableWithCounterEntry current) { + final previousCounterEntry = getPreviousNotEmptyCounterEntry( + current, _TreeTableCounterCookies.countVisible); + if (previousCounterEntry != null) { + return previousCounterEntry as _TreeTableWithCounterEntry; + } + + return null; + } /// Gets the starting counter for this tree. /// @@ -713,7 +730,7 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { /// * notifyCounterSource - _required_ - Boolean value void invalidateCounterTopDown(bool notifyCounterSource) { if (root != null) { - final Object _root = root; + final Object _root = root!; if (_root is _TreeTableCounterNodeBase) { _root.invalidateCounterTopDown(notifyCounterSource); } @@ -734,7 +751,7 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { /// /// Returns `true` if tree contains the specified entry; otherwise, `false`. @override - bool contains(Object value) { + bool contains(Object? value) { if (value == null) { return false; } @@ -782,7 +799,7 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { /// Returns the collection after removing the specified item from the /// tree collection @override - bool remove(Object value) => super.remove(value); + bool remove(Object? value) => super.remove(value); /// Gets a _TreeTableWithCounterEntry. /// @@ -790,9 +807,9 @@ class _TreeTableWithCounter extends _TreeTableWithSummary { /// /// Returns a new instance for _TreeTableWithCounterEntry @override - _TreeTableWithCounterEntry operator [](int index) { + _TreeTableWithCounterEntry? operator [](int index) { if (super[index] is _TreeTableWithCounterEntry) { - return super[index]; + return super[index] as _TreeTableWithCounterEntry; } else { return null; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_summary.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_summary.dart index f78db3976..40742b927 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_summary.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_summary.dart @@ -21,7 +21,7 @@ mixin _TreeTableSummaryNodeBase on _TreeTableNodeBase { /// * emptySummaries - _required_ - The empty summaries. /// /// Returns an array of summary objects. - List<_TreeTableSummaryBase> getSummaries( + List<_TreeTableSummaryBase>? getSummaries( _TreeTableEmptySummaryArraySourceBase emptySummaries); /// Marks all summaries dirty in this node and child nodes. @@ -65,14 +65,15 @@ class _TreeTableWithSummaryBranch extends _TreeTableBranch with _TreeTableSummaryNodeBase { _TreeTableWithSummaryBranch(_TreeTable tree) : super(tree); - List<_TreeTableSummaryBase> _summaries; + List<_TreeTableSummaryBase>? _summaries; ///Initializes a new instance of the `_TreeTableWithSummaryBranch` class. /// /// * tree - _required_ - Tree instance /// /// Gets the tree this branch belongs to. - _TreeTableWithSummary get treeTableWithSummary => super.tree; + _TreeTableWithSummary get treeTableWithSummary => + super.tree as _TreeTableWithSummary; /// Gets a value indicating whether this node has summaries or not. @override @@ -80,31 +81,35 @@ class _TreeTableWithSummaryBranch extends _TreeTableBranch /// Gets the parent branch. @override - _TreeTableWithSummaryBranch get parent => super.parent; + _TreeTableWithSummaryBranch? get parent => + super.parent != null ? super.parent as _TreeTableWithSummaryBranch : null; /// Sets the parent branch. @override - set parent(Object value) { - super.parent = value; + set parent(Object? value) { + if (value != null) { + super.parent = value as _TreeTableBranchBase; + } } /// The left branch node cast to _TreeTableSummaryNodeBase. /// /// Returns the left branch node cast to _TreeTableSummaryNodeBase. - _TreeTableSummaryNodeBase getLeftC() => getLeftNode(); + _TreeTableSummaryNodeBase? getLeftC() => getLeftNode(); /// The left branch node cast to _TreeTableSummaryNodeBase. /// /// Returns the left branch node cast to _TreeTableSummaryNodeBase. - _TreeTableSummaryNodeBase getLeftNode() => left; + _TreeTableSummaryNodeBase? getLeftNode() => left as _TreeTableSummaryNodeBase; /// Gets the right branch node cast to _TreeTableSummaryNodeBase. /// /// Returns the left branch node cast to _TreeTableSummaryNodeBase. - _TreeTableSummaryNodeBase getRightC() => getRightNode(); + _TreeTableSummaryNodeBase? getRightC() => getRightNode(); /// Returns the left branch node cast to _TreeTableSummaryNodeBase. - _TreeTableSummaryNodeBase getRightNode() => right; + _TreeTableSummaryNodeBase? getRightNode() => + right as _TreeTableSummaryNodeBase; /// Gets an array of summary objects. /// @@ -112,35 +117,35 @@ class _TreeTableWithSummaryBranch extends _TreeTableBranch /// /// Returns an array of summary objects. @override - List<_TreeTableSummaryBase> getSummaries( + List<_TreeTableSummaryBase>? getSummaries( _TreeTableEmptySummaryArraySourceBase emptySummaries) { - if (tree.isInitializing) { + if (tree!.isInitializing) { return null; } else if (_summaries == null) { - final List<_TreeTableSummaryBase> left = - getLeftNode().getSummaries(emptySummaries); - final List<_TreeTableSummaryBase> right = - getRightNode().getSummaries(emptySummaries); + final List<_TreeTableSummaryBase>? left = + getLeftNode()?.getSummaries(emptySummaries); + final List<_TreeTableSummaryBase>? right = + getRightNode()?.getSummaries(emptySummaries); if (left != null && right != null) { int reuseLeft = 0; int reuseRight = 0; _summaries = []; - for (int i = 0; i < _summaries.length; i++) { - _summaries[i] = left[i].combine(right[i]); + for (int i = 0; i < _summaries!.length; i++) { + _summaries![i] = left[i].combine(right[i]); // preserve memory optimization if (reuseLeft == i || reuseRight == i) { - if (_MathHelper.referenceEquals(_summaries[i], left[i])) { + if (_MathHelper.referenceEquals(_summaries![i], left[i])) { reuseLeft++; - } else if (_MathHelper.referenceEquals(_summaries[i], right[i])) { + } else if (_MathHelper.referenceEquals(_summaries![i], right[i])) { reuseRight++; } } } // preserve memory optimization - if (reuseLeft == _summaries.length) { + if (reuseLeft == _summaries!.length) { _summaries = left; - } else if (reuseRight == _summaries.length) { + } else if (reuseRight == _summaries!.length) { _summaries = right; } } @@ -154,16 +159,17 @@ class _TreeTableWithSummaryBranch extends _TreeTableBranch /// * notifyParentRecordSource - _required_ - Boolean value @override void invalidateSummariesBottomUp(bool notifyParentRecordSource) { - if (tree.isInitializing) { + if (tree!.isInitializing) { return; } _summaries = null; if (parent != null) { - parent.invalidateSummariesBottomUp(notifyParentRecordSource); + parent!.invalidateSummariesBottomUp(notifyParentRecordSource); } else if (notifyParentRecordSource) { - if (tree != null && tree.tag is _TreeTableSummaryArraySourceBase) { - final _TreeTableSummaryArraySourceBase _treeTag = tree.tag; + if (tree != null && tree!.tag is _TreeTableSummaryArraySourceBase) { + final _TreeTableSummaryArraySourceBase _treeTag = + tree!.tag as _TreeTableSummaryArraySourceBase; _treeTag.invalidateSummariesBottomUp(); } } @@ -174,13 +180,13 @@ class _TreeTableWithSummaryBranch extends _TreeTableBranch /// * notifyCounterSource - _required_ - If set to true notify counter source. @override void invalidateSummariesTopDown(bool notifyCounterSource) { - if (tree.isInitializing) { + if (tree!.isInitializing) { return; } _summaries = null; - getLeftNode().invalidateSummariesTopDown(notifyCounterSource); - getRightNode().invalidateSummariesTopDown(notifyCounterSource); + getLeftNode()?.invalidateSummariesTopDown(notifyCounterSource); + getRightNode()?.invalidateSummariesTopDown(notifyCounterSource); } } @@ -188,10 +194,11 @@ class _TreeTableWithSummaryBranch extends _TreeTableBranch class _TreeTableWithSummaryEntryBase extends _TreeTableEntry with _TreeTableSummaryNodeBase { static List<_TreeTableSummaryBase> emptySummaryArray = []; - List<_TreeTableSummaryBase> _summaries; + List<_TreeTableSummaryBase>? _summaries; /// Gets the tree this leaf belongs to. - _TreeTableWithSummary get treeTableWithSummary => tree; + _TreeTableWithSummary get treeTableWithSummary => + tree as _TreeTableWithSummary; /// Gets a value indicating whether the node has summaries or not. @override @@ -199,9 +206,9 @@ class _TreeTableWithSummaryEntryBase extends _TreeTableEntry /// Gets the parent branch. @override - _TreeTableWithSummaryBranch get parent { + _TreeTableWithSummaryBranch? get parent { if (super.parent is _TreeTableWithSummaryBranch) { - return super.parent; + return super.parent as _TreeTableWithSummaryBranch; } else { return null; } @@ -209,16 +216,18 @@ class _TreeTableWithSummaryEntryBase extends _TreeTableEntry /// Sets the parent branch. @override - set parent(Object value) { - super.parent = value; + set parent(Object? value) { + if (value != null) { + super.parent = value as _TreeTableBranchBase; + } } /// Gets the value as `_TreeTableSummaryArraySourceBase`. /// /// Returns the value as `_TreeTableSummaryArraySourceBase`. - _TreeTableSummaryArraySourceBase getSummaryArraySource() { + _TreeTableSummaryArraySourceBase? getSummaryArraySource() { if (value is _TreeTableSummaryArraySourceBase) { - return value; + return value as _TreeTableSummaryArraySourceBase; } else { return null; } @@ -230,10 +239,10 @@ class _TreeTableWithSummaryEntryBase extends _TreeTableEntry /// * emptySummaries - _required_ - The empty summaries. /// /// Returns an array of summary objects. - List<_TreeTableSummaryBase> onGetSummaries( + List<_TreeTableSummaryBase>? onGetSummaries( _TreeTableEmptySummaryArraySourceBase emptySummaries) { - List<_TreeTableSummaryBase> summaries; - final _TreeTableSummaryArraySourceBase summaryArraySource = + List<_TreeTableSummaryBase>? summaries; + final _TreeTableSummaryArraySourceBase? summaryArraySource = getSummaryArraySource(); if (summaryArraySource != null) { final bool summaryChanged = false; @@ -251,7 +260,7 @@ class _TreeTableWithSummaryEntryBase extends _TreeTableEntry /// /// Returns an instance for newly created TreeTable @override - _TreeTableBranchBase createBranch(_TreeTable tree) { + _TreeTableBranchBase? createBranch(_TreeTable tree) { final Object _tree = tree; if (_tree is _TreeTableWithSummaryBranch) { return _tree; @@ -277,15 +286,17 @@ class _TreeTableWithSummaryEntryBase extends _TreeTableEntry void invalidateSummariesBottomUp(bool notifyParentRecordSource) { _summaries = null; if (value is _TreeTableSummaryArraySourceBase && tree != null) { - final _TreeTableSummaryArraySourceBase _tree = tree.tag; + final _TreeTableSummaryArraySourceBase _tree = + tree!.tag as _TreeTableSummaryArraySourceBase; _tree.invalidateSummary(); } if (parent != null) { - parent.invalidateSummariesBottomUp(notifyParentRecordSource); + parent!.invalidateSummariesBottomUp(notifyParentRecordSource); } else if (notifyParentRecordSource) { - if (tree != null && tree.tag is _TreeTableSummaryArraySourceBase) { - final _TreeTableSummaryArraySourceBase _tree = tree.tag; + if (tree != null && tree!.tag is _TreeTableSummaryArraySourceBase) { + final _TreeTableSummaryArraySourceBase _tree = + tree!.tag as _TreeTableSummaryArraySourceBase; _tree.invalidateSummariesBottomUp(); } } @@ -299,7 +310,7 @@ class _TreeTableWithSummaryEntryBase extends _TreeTableEntry void invalidateSummariesTopDown(bool notifySummaryArraySource) { _summaries = null; if (notifySummaryArraySource) { - final _TreeTableSummaryArraySourceBase summaryArraySource = + final _TreeTableSummaryArraySourceBase? summaryArraySource = getSummaryArraySource(); if (summaryArraySource != null) { summaryArraySource.invalidateSummariesTopDown(); @@ -323,7 +334,7 @@ class _TreeTableWithSummary extends _TreeTable { return false; } - final Object _root = root; + final Object _root = root!; if (_root is _TreeTableSummaryNodeBase) { return _root.hasSummaries; } else { @@ -336,13 +347,13 @@ class _TreeTableWithSummary extends _TreeTable { /// * emptySummaries - _required_ - summary value /// /// Returns an array of summary objects. - List<_TreeTableSummaryBase> getSummaries( + List<_TreeTableSummaryBase>? getSummaries( _TreeTableEmptySummaryArraySourceBase emptySummaries) { if (root == null) { return emptySummaries.getEmptySummaries(); } - final Object _root = root; + final Object _root = root!; if (_root is _TreeTableSummaryNodeBase) { return _root.getSummaries(emptySummaries); } else { @@ -356,7 +367,7 @@ class _TreeTableWithSummary extends _TreeTable { /// summaries source. void invalidateSummariesTopDown(bool notifySummariesSource) { if (root != null) { - final Object _root = root; + final Object _root = root!; if (_root is _TreeTableSummaryNodeBase) { _root.invalidateSummariesTopDown(notifySummariesSource); } @@ -378,7 +389,7 @@ class _TreeTableWithSummary extends _TreeTable { /// /// Returns a boolean value indicates whether an object belongs to the tree. @override - bool contains(Object value) { + bool contains(Object? value) { if (value == null) { return false; } @@ -425,7 +436,7 @@ class _TreeTableWithSummary extends _TreeTable { /// /// Returns the removed value. @override - bool remove(Object value) => super.remove(value); + bool remove(Object? value) => super.remove(value); /// Gets a _TreeTableWithSummaryEntryBase. /// @@ -433,9 +444,9 @@ class _TreeTableWithSummary extends _TreeTable { /// /// Returns the new instance for _TreeTableWithSummaryEntryBase @override - _TreeTableWithSummaryEntryBase operator [](int index) { + _TreeTableWithSummaryEntryBase? operator [](int index) { if (super[index] is _TreeTableWithSummaryEntryBase) { - return super[index]; + return super[index] as _TreeTableWithSummaryEntryBase; } else { return null; } @@ -458,9 +469,9 @@ class _TreeTableWithSummaryEnumerator extends _TreeTableEnumerator { /// Gets the current `TreeTableWithSummary` object. @override - _TreeTableWithSummaryEntryBase get current { + _TreeTableWithSummaryEntryBase? get current { if (super.current is _TreeTableWithSummaryEntryBase) { - return super.current; + return super.current as _TreeTableWithSummaryEntryBase; } else { return null; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_base.dart index facc7cac3..1e16f7534 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_base.dart @@ -5,17 +5,12 @@ part of datagrid; /// Creates a list of the given length. /// The created list is fixed-length if [length] is provided. class _ListBase implements _CollectionBase, _EnumerableBase { - _ListBase() { - _isFixedSize = false; - _isReadOnly = false; - } - /// Gets the fixed size value bool get isFixedSize => _isFixedSize; - bool _isFixedSize; + bool _isFixedSize = false; bool get isReadOnly => _isReadOnly; - bool _isReadOnly; + bool _isReadOnly = false; /// Add an new element to the list collection /// @@ -54,7 +49,7 @@ class _ListBase implements _CollectionBase, _EnumerableBase { /// Gets the index value /// /// Returns the index value - Object operator [](int index) => this[index]; + Object? operator [](int index) => this[index]; /// Sets the index value void operator []=(int index, Object value) => this[index] = value; @@ -71,21 +66,21 @@ class _CollectionBase extends _EnumerableBase { /// Gets the count of the collection. int get count => _count; - int _count; + int _count = 0; /// Gets the synchronized. bool get isSynchronized => _isSynchronized; - bool _isSynchronized; + bool _isSynchronized = false; /// Gets the syncroot - Object get syncRoot => _syncRoot; - Object _syncRoot; + Object? get syncRoot => _syncRoot; + Object? _syncRoot; /// Copy an element based on index. /// /// * list - _required_ - List of element /// * index - _required_ - Index position - void copyTo(List list, int index) {} + void copyTo(List list, int index) {} @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_generic_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_generic_base.dart index ee9f55cda..c28884ddd 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_generic_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_generic_base.dart @@ -11,6 +11,6 @@ abstract class _EnumerableGenericBase with IterableMixin { /// Generic Enumerator class _EnumeratorGenericBase { /// Gets the current item from an list - T get currentGeneric => _current; - T _current; + T? get currentGeneric => _current; + T? _current; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/math_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/math_helper.dart index 54f8413b1..32cc1302f 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/math_helper.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/math_helper.dart @@ -13,11 +13,11 @@ class _MathHelper { /// /// * sortedList - _required_ - The sortedList /// * value - _required_ - Represented to a value - static int binarySearch>( + static int binarySearch>( List sortedList, T value) { int min = 0; int max = sortedList.length; - int index; + late int index; while (min < max) { final int mid = min + ((max - min) >> 1); final T element = sortedList[mid]; @@ -32,12 +32,12 @@ class _MathHelper { max = mid; } } - return index ?? -1; + return index; } /// Determines whether the specified Object instances are the same instance. /// /// * objA - _required_ - Instance of a class /// * objB - _required_ - Instance of a class - static bool referenceEquals(Object objA, Object objB) => objA == objB; + static bool referenceEquals(Object? objA, Object? objB) => objA == objB; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_collection_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_collection_base.dart index fbecd23c0..25ac4a893 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_collection_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_collection_base.dart @@ -79,7 +79,7 @@ abstract class _DistanceCounterCollectionBase { /// /// * index - _required_ - The index. /// Returns - _required_ - the nested entities at a given index or null. - _DistanceCounterCollectionBase getNestedDistances(int index); + _DistanceCounterCollectionBase? getNestedDistances(int index); /// Gets the distance position of the next entity after a given point. /// @@ -160,7 +160,7 @@ abstract class _DistanceCounterCollectionBase { /// * index - _required_ - The index. /// * nestedCollection - _required_ - The nested collection. void setNestedDistances( - int index, _DistanceCounterCollectionBase nestedCollection); + int index, _DistanceCounterCollectionBase? nestedCollection); /// Hides a specified range of entities (lines, rows or columns). /// diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_subset.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_subset.dart index d9dc3d137..6785a40a2 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_subset.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_subset.dart @@ -22,7 +22,7 @@ class _DistanceCounterSubset extends _DistanceCounterCollectionBase _count = 0; } - _DistanceCounterCollectionBase _trackDCC; + late _DistanceCounterCollectionBase _trackDCC; int start = 0; /// Gets an object that implements the `Distances` property. @@ -137,7 +137,7 @@ class _DistanceCounterSubset extends _DistanceCounterCollectionBase /// /// Returns the nested entities at a given index or null. @override - _DistanceCounterCollectionBase getNestedDistances(int index) => + _DistanceCounterCollectionBase? getNestedDistances(int index) => _trackDCC.getNestedDistances(index + start); /// Gets the next visible index. @@ -282,7 +282,7 @@ class _DistanceCounterSubset extends _DistanceCounterCollectionBase /// * nestedCollection - _required_ - The nested collection. @override void setNestedDistances( - int index, _DistanceCounterCollectionBase nestedCollection) { + int index, _DistanceCounterCollectionBase? nestedCollection) { _trackDCC.setNestedDistances(index + start, nestedCollection); } @@ -296,7 +296,7 @@ class _DistanceCounterSubset extends _DistanceCounterCollectionBase /// Sets the distance for an entity from the given index. @override - void operator []=(int index, Object value) { + void operator []=(int index, double value) { _trackDCC[index + start] = value; } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_range_counter_collection.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_range_counter_collection.dart index f2bd5e049..a43ea3530 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_range_counter_collection.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_range_counter_collection.dart @@ -35,7 +35,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { _rbTree = _DistanceLineCounterTree(startPos, false); } - _DistanceLineCounterTree _rbTree; + late _DistanceLineCounterTree _rbTree; /// Gets the padding distance of the counter collection. double paddingDistance = 0; @@ -45,7 +45,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return 0; } - final _DistanceLineCounter counter = _rbTree.getCounterTotal(); + final _DistanceLineCounter counter = _rbTree.getCounterTotal()!; return counter.lineCount; } @@ -61,7 +61,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return paddingDistance; } - final _DistanceLineCounter counter = _rbTree.getCounterTotal(); + final _DistanceLineCounter counter = _rbTree.getCounterTotal()!; return counter.distance + paddingDistance; } @@ -128,14 +128,14 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { } if (index >= count) { final int delta = index - count; - final _DistanceLineCounter counter = _rbTree.getCounterTotal(); + final _DistanceLineCounter counter = _rbTree.getCounterTotal()!; return counter.distance + (delta * defaultDistance); } else { final _LineIndexEntryAt e = initDistanceLine(index, false); - e.rbEntryPosition = e.rbEntry.getCounterPosition; - return e.rbEntryPosition.distance + - ((index - e.rbEntryPosition.lineCount) * - e.rbValue.singleLineDistance); + e.rbEntryPosition = e.rbEntry!.getCounterPosition as _DistanceLineCounter; + return e.rbEntryPosition!.distance + + ((index - e.rbEntryPosition!.lineCount) * + e.rbValue!.singleLineDistance); } } @@ -157,18 +157,22 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { } final searchPosition = _DistanceLineCounter(cumulatedDistance, 0); - final _DistanceLineCounterEntry rbEntry = + final _DistanceLineCounterEntry? rbEntry = _rbTree.getEntryAtCounterPositionWithThreeArgs( searchPosition, _DistanceLineCounterKind.distance, false); - final _DistanceLineCounterSource rbValue = rbEntry.value; - final _DistanceLineCounter rbEntryPosition = rbEntry.getCounterPosition; - if (rbValue.singleLineDistance > 0) { + final _DistanceLineCounterSource? rbValue = + rbEntry?.value as _DistanceLineCounterSource; + final _DistanceLineCounter? rbEntryPosition = + rbEntry?.getCounterPosition as _DistanceLineCounter; + if (rbValue != null && + rbEntryPosition != null && + rbValue.singleLineDistance > 0) { final totalDistance = (cumulatedDistance - rbEntryPosition.distance) / rbValue.singleLineDistance; delta = totalDistance.isFinite ? totalDistance.floor() : 0; } - return rbEntryPosition.lineCount + delta; + return (rbEntryPosition?.lineCount ?? 0) + delta; } _LineIndexEntryAt initDistanceLine( @@ -176,12 +180,12 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { final e = _LineIndexEntryAt() ..searchPosition = _DistanceLineCounter(0, lineIndex); e.rbEntry = _rbTree.getEntryAtCounterPositionWithThreeArgs( - e.searchPosition, _DistanceLineCounterKind.lines, false); - e.rbValue = e.rbEntry.value; + e.searchPosition!, _DistanceLineCounterKind.lines, false); + e.rbValue = e.rbEntry!.value as _DistanceLineCounterSource; e.rbEntryPosition = null; if (determineEntryPosition) { - e.rbEntryPosition = e.rbValue.lineCount > 1 - ? e.rbEntry.getCounterPosition + e.rbEntryPosition = e.rbValue!.lineCount > 1 + ? e.rbEntry!.getCounterPosition : e.searchPosition; } @@ -203,11 +207,11 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { ensureTreeCount(insertAt); final _LineIndexEntryAt e = initDistanceLine(insertAt, false); - if (e.rbValue.singleLineDistance == distance) { - e.rbValue.lineCount += count; - e.rbEntry.invalidateCounterBottomUp(true); + if (e.rbValue!.singleLineDistance == distance) { + e.rbValue!.lineCount += count; + e.rbEntry!.invalidateCounterBottomUp(true); } else { - final _DistanceLineCounterEntry rbEntry0 = split(insertAt); + final _DistanceLineCounterEntry? rbEntry0 = split(insertAt); final _DistanceLineCounterEntry entry = createTreeTableEntry(distance, count); if (rbEntry0 == null) { @@ -231,30 +235,30 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { final nested = getNestedDistances(index); if (nested != null && distance != nested.totalDistance) { final _LineIndexEntryAt e = initDistanceLine(index, false); - e.rbEntry.invalidateCounterBottomUp(true); + e.rbEntry!.invalidateCounterBottomUp(true); } } void merge(_DistanceLineCounterEntry entry, bool checkPrevious) { - final _DistanceLineCounterSource value = entry.value; - _DistanceLineCounterEntry previousEntry; + final _DistanceLineCounterSource value = entry.value!; + _DistanceLineCounterEntry? previousEntry; if (checkPrevious) { previousEntry = _rbTree.getPreviousEntry(entry); } - final _DistanceLineCounterEntry nextEntry = _rbTree.getNextEntry(entry); + final _DistanceLineCounterEntry? nextEntry = _rbTree.getNextEntry(entry); bool dirty = false; if (previousEntry != null && - previousEntry.value.singleLineDistance == value.singleLineDistance) { - value.lineCount += previousEntry.value.lineCount; + previousEntry.value!.singleLineDistance == value.singleLineDistance) { + value.lineCount += previousEntry.value!.lineCount; _rbTree.remove(previousEntry); dirty = true; } if (nextEntry != null && - nextEntry.value.singleLineDistance == value.singleLineDistance) { - value.lineCount += nextEntry.value.lineCount; + nextEntry.value!.singleLineDistance == value.singleLineDistance) { + value.lineCount += nextEntry.value!.lineCount; _rbTree.remove(nextEntry); dirty = true; } @@ -269,15 +273,18 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return _rbTree.getCount(); } - _DistanceLineCounterEntry entry = split(removeAt); + _DistanceLineCounterEntry? entry = split(removeAt); split(removeAt + count); - final int n = _rbTree.indexOf(entry); + late int n; + if (entry != null) { + n = _rbTree.indexOf(entry); + } final toDelete = []; int total = 0; while (total < count && entry != null) { - total += entry.value.lineCount; + total += entry.value!.lineCount; toDelete.add(entry); entry = _rbTree.getNextEntry(entry); } @@ -289,22 +296,22 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return n; } - _DistanceLineCounterEntry split(int index) { + _DistanceLineCounterEntry? split(int index) { if (index >= internalCount) { return null; } final _LineIndexEntryAt e = initDistanceLine(index, true); - if (e.rbEntryPosition.lineCount != index) { - final int count1 = index - e.rbEntryPosition.lineCount; - final int count2 = e.rbValue.lineCount - count1; + if (e.rbEntryPosition!.lineCount != index) { + final int count1 = index - e.rbEntryPosition!.lineCount; + final int count2 = e.rbValue!.lineCount - count1; - e.rbValue.lineCount = count1; + e.rbValue!.lineCount = count1; final _DistanceLineCounterEntry rbEntry2 = - createTreeTableEntry(e.rbValue.singleLineDistance, count2); - _rbTree.insert(_rbTree.indexOf(e.rbEntry) + 1, rbEntry2); - e.rbEntry.invalidateCounterBottomUp(true); + createTreeTableEntry(e.rbValue!.singleLineDistance, count2); + _rbTree.insert(_rbTree.indexOf(e.rbEntry!) + 1, rbEntry2); + e.rbEntry!.invalidateCounterBottomUp(true); return rbEntry2; } @@ -340,7 +347,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { final double delta = point - nestedStart; if (delta > 0) { - final _DistanceCounterCollectionBase nestedDcc = + final _DistanceCounterCollectionBase? nestedDcc = getNestedDistances(index); if (nestedDcc != null) { final double r = nestedDcc.getAlignedScrollValue(delta); @@ -370,15 +377,16 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { /// * index - _required_ - The index. /// Returns the nested entities at a given index or null. @override - _DistanceCounterCollectionBase getNestedDistances(int index) { + _DistanceCounterCollectionBase? getNestedDistances(int index) { if (index >= internalCount) { return null; } checkRange('index', 0, count - 1, index); final _LineIndexEntryAt e = initDistanceLine(index, false); - final _NestedDistanceCounterCollectionSource vcs = e.rbValue; - return vcs?.nestedDistances; + final _NestedDistanceCounterCollectionSource vcs = + e.rbValue as _NestedDistanceCounterCollectionSource; + return vcs.nestedDistances; } /// Gets the distance position of the next entity after a given point. @@ -396,7 +404,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { final double nestedStart = getCumulatedDistanceAt(index); final double delta = point - nestedStart; - final _DistanceCounterCollectionBase nestedDcc = getNestedDistances(index); + final _DistanceCounterCollectionBase? nestedDcc = getNestedDistances(index); if (nestedDcc != null) { final double r = nestedDcc.getNextScrollValue(delta); if (!(r == double.nan) && r >= 0 && r < nestedDcc.totalDistance) { @@ -429,13 +437,13 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { } final _LineIndexEntryAt e = initDistanceLine(index + 1, true); - if (e.rbValue.singleLineDistance > 0) { - if (index - e.rbEntryPosition.lineCount < e.rbValue.lineCount) { + if (e.rbValue!.singleLineDistance > 0) { + if (index - e.rbEntryPosition!.lineCount < e.rbValue!.lineCount) { return index + 1; } } - e.rbEntry = _rbTree.getNextVisibleEntry(e.rbEntry); + e.rbEntry = _rbTree.getNextVisibleEntry(e.rbEntry!); if (e.rbEntry == null) { if (internalCount < count) { return internalCount; @@ -444,8 +452,8 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return -1; } - e.rbEntryPosition = e.rbEntry.getCounterPosition; - return e.rbEntryPosition.lineCount; + e.rbEntryPosition = e.rbEntry!.getCounterPosition; + return e.rbEntryPosition!.lineCount; } /// Gets the distance position of the entity preceding a given point. @@ -463,7 +471,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { double delta = point - nestedStart; if (delta > 0) { - final _DistanceCounterCollectionBase nestedDcc = + final _DistanceCounterCollectionBase? nestedDcc = getNestedDistances(index); if (nestedDcc != null) { final double r = nestedDcc.getPreviousScrollValue(delta); @@ -480,7 +488,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { if (index >= 0 && index < count) { nestedStart = getCumulatedDistanceAt(index); - final _DistanceCounterCollectionBase nestedDcc = + final _DistanceCounterCollectionBase? nestedDcc = getNestedDistances(index); if (nestedDcc != null) { delta = nestedDcc.totalDistance; @@ -512,19 +520,19 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { } final _LineIndexEntryAt e = initDistanceLine(index - 1, false); - if (e.rbValue.singleLineDistance > 0) { + if (e.rbValue!.singleLineDistance > 0) { return index - 1; } - e.rbEntry = _rbTree.getPreviousVisibleEntry(e.rbEntry); + e.rbEntry = _rbTree.getPreviousVisibleEntry(e.rbEntry!); if (e.rbEntry == null) { return -1; } e - ..rbEntryPosition = e.rbEntry.getCounterPosition - ..rbValue = e.rbEntry.value; - return e.rbEntryPosition.lineCount + e.rbValue.lineCount - 1; + ..rbEntryPosition = e.rbEntry!.getCounterPosition + ..rbValue = e.rbEntry!.value; + return e.rbEntryPosition!.lineCount + e.rbValue!.lineCount - 1; } /// Gets the index of an entity in this collection for which @@ -541,7 +549,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { @override int indexOfCumulatedDistance(double cumulatedDistance) { final _DistanceLineCounter _distanceLineCounter = - _rbTree.getStartCounterPosition(); + _rbTree.getStartCounterPosition() as _DistanceLineCounter; if (cumulatedDistance < _distanceLineCounter.distance) { return -1; } @@ -572,7 +580,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { final int n = removeHelper(removeAt, count); if (n > 0) { - merge(_rbTree[n - 1], false); + merge(_rbTree[n - 1]!, false); } } @@ -627,7 +635,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { /// * nestedCollection - _required_ - The nested collection. @override void setNestedDistances( - int index, _DistanceCounterCollectionBase nestedCollection) { + int index, _DistanceCounterCollectionBase? nestedCollection) { checkRange('index', 0, count - 1, index); if (getNestedDistances(index) != nestedCollection) { @@ -635,7 +643,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { ensureTreeCount(index + 1); } - final _DistanceLineCounterEntry entry = split(index); + final _DistanceLineCounterEntry entry = split(index)!; split(index + 1); if (nestedCollection != null) { @@ -664,7 +672,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { } final _LineIndexEntryAt e = initDistanceLine(index, false); - return e.rbValue.singleLineDistance; + return e.rbValue!.singleLineDistance; } @override @@ -679,9 +687,9 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { ensureTreeCount(count); } - final _DistanceLineCounterEntry entry = split(index); + final _DistanceLineCounterEntry entry = split(index)!; split(index + 1); - entry.value.singleLineDistance = value; + entry.value!.singleLineDistance = value; entry.invalidateCounterBottomUp(true); } } @@ -689,10 +697,10 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { /// Initializes the LineIndexEntryAt class class _LineIndexEntryAt { - _DistanceLineCounter searchPosition; - _DistanceLineCounterEntry rbEntry; - _DistanceLineCounterSource rbValue; - _DistanceLineCounter rbEntryPosition; + _DistanceLineCounter? searchPosition; + _DistanceLineCounterEntry? rbEntry; + _DistanceLineCounterSource? rbValue; + _DistanceLineCounter? rbEntryPosition; } /// An object that maintains a collection of nested distances and wires @@ -717,31 +725,29 @@ class _NestedDistanceCounterCollectionSource _parentDistances = parentDistances; _nestedDistances = nestedDistances; - if (nestedDistances != null) { - nestedDistances.connectWithParent(this); - } + nestedDistances.connectWithParent(this); } - _DistanceCounterCollectionBase _nestedDistances; - _DistanceCounterCollectionBase _parentDistances; - _DistanceLineCounterEntry entry; + _DistanceCounterCollectionBase? _nestedDistances; + _DistanceCounterCollectionBase? _parentDistances; + _DistanceLineCounterEntry? entry; /// Gets the nested distances. /// /// Returns the nested distances. - _DistanceCounterCollectionBase get nestedDistances => _nestedDistances; + _DistanceCounterCollectionBase? get nestedDistances => _nestedDistances; /// Gets the parent distances. /// /// Returns the parent distances. - _DistanceCounterCollectionBase get parentDistances => _parentDistances; + _DistanceCounterCollectionBase? get parentDistances => _parentDistances; /// Gets the distance of a single line. /// /// Returns the single line distance. @override double get singleLineDistance => - _nestedDistances != null ? _nestedDistances.totalDistance : 0; + _nestedDistances != null ? _nestedDistances!.totalDistance : 0; /// Sets the distance of a single line. @override @@ -752,13 +758,13 @@ class _NestedDistanceCounterCollectionSource /// Returns the `_TreeTableVisibleCounter` object with counters. @override _TreeTableCounterBase getCounter() => _DistanceLineCounter( - _nestedDistances == null ? 0 : _nestedDistances.totalDistance, 1); + _nestedDistances == null ? 0 : _nestedDistances!.totalDistance, 1); /// Marks all counters dirty in this object and parent nodes. @override void invalidateCounterBottomUp() { if (entry != null) { - entry.invalidateCounterBottomUp(true); + entry!.invalidateCounterBottomUp(true); } } @@ -870,7 +876,7 @@ class _DistanceLineCounter extends _TreeTableCounterBase { /// /// Returns the new object @override - _TreeTableCounterBase combine(_TreeTableCounterBase other, int cookie) { + _TreeTableCounterBase combine(_TreeTableCounterBase? other, int cookie) { if (other is _DistanceLineCounter) { return combineBase(other, cookie); } else { @@ -886,7 +892,7 @@ class _DistanceLineCounter extends _TreeTableCounterBase { /// /// Returns a new object which is the combination of the counter values /// of this counter object with the values of another counter object. - _DistanceLineCounter combineBase(_DistanceLineCounter other, int cookie) { + _DistanceLineCounter combineBase(_DistanceLineCounter? other, int cookie) { if (other == null || other.isEmpty(_MathHelper.maxvalue)) { return this; } @@ -907,7 +913,7 @@ class _DistanceLineCounter extends _TreeTableCounterBase { /// /// Returns the compared value @override - double compare(_TreeTableCounterBase other, int cookie) { + double compare(_TreeTableCounterBase? other, int cookie) { if (other is _DistanceLineCounter) { return compareBase(other, cookie); } else { @@ -923,7 +929,7 @@ class _DistanceLineCounter extends _TreeTableCounterBase { /// /// Returns a value indicating the comparison of the current object /// and the given object. - double compareBase(_DistanceLineCounter other, int cookie) { + double compareBase(_DistanceLineCounter? other, int cookie) { if (other == null) { return 0; } @@ -996,15 +1002,15 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// /// Returns `True` if tree contains the specified object, otherwise `false`. @override - bool contains(Object value) => super.contains(value); + bool contains(Object? value) => super.contains(value); /// Gets the total number of counters. /// /// Returns the total number of counters. @override - _DistanceLineCounter getCounterTotal() { + _DistanceLineCounter? getCounterTotal() { if (super.getCounterTotal() is _DistanceLineCounter) { - return super.getCounterTotal(); + return super.getCounterTotal() as _DistanceLineCounter; } else { return null; } @@ -1018,11 +1024,13 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// /// Returns an entry at the specified counter position. @override - _DistanceLineCounterEntry getEntryAtCounterPosition( + _DistanceLineCounterEntry? getEntryAtCounterPosition( Object searchPosition, int cookie) { - if (super.getEntryAtCounterPosition(searchPosition, cookie) - is _DistanceLineCounterEntry) { - return super.getEntryAtCounterPosition(searchPosition, cookie); + if (searchPosition is _TreeTableCounterBase && + super.getEntryAtCounterPosition(searchPosition, cookie) + is _DistanceLineCounterEntry) { + return super.getEntryAtCounterPosition(searchPosition, cookie) + as _DistanceLineCounterEntry; } else { return null; } @@ -1038,12 +1046,14 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// same SearchPosition. /// /// Returns an entry at the specified counter position. - _DistanceLineCounterEntry getEntryAtCounterPositionWithThreeArgs( + _DistanceLineCounterEntry? getEntryAtCounterPositionWithThreeArgs( Object searchPosition, int cookie, bool preferLeftMost) { - if (super.getEntryAtCounterPositionwithThreeParameter( - searchPosition, cookie, preferLeftMost) is _DistanceLineCounterEntry) { + if (searchPosition is _TreeTableCounterBase && + super.getEntryAtCounterPositionwithThreeParameter( + searchPosition, cookie, preferLeftMost) + is _DistanceLineCounterEntry) { return super.getEntryAtCounterPositionwithThreeParameter( - searchPosition, cookie, preferLeftMost); + searchPosition, cookie, preferLeftMost) as _DistanceLineCounterEntry; } else { return null; } @@ -1055,9 +1065,10 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// /// Returns the next entry. @override - _DistanceLineCounterEntry getNextEntry(Object current) { - if (super.getNextEntry(current) is _DistanceLineCounterEntry) { - return super.getNextEntry(current); + _DistanceLineCounterEntry? getNextEntry(Object? current) { + if (current is _TreeTableEntryBase && + super.getNextEntry(current) is _DistanceLineCounterEntry) { + return super.getNextEntry(current) as _DistanceLineCounterEntry; } else { return null; } @@ -1072,11 +1083,13 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// Returns the subsequent entry in the collection for which /// the specific counter is not empty. @override - _DistanceLineCounterEntry getNextNotEmptyCounterEntry( + _DistanceLineCounterEntry? getNextNotEmptyCounterEntry( Object current, int cookie) { - if (super.getNextNotEmptyCounterEntry(current, cookie) - is _DistanceLineCounterEntry) { - return super.getNextNotEmptyCounterEntry(current, cookie); + if (current is _TreeTableEntryBase && + super.getNextNotEmptyCounterEntry(current, cookie) + is _DistanceLineCounterEntry) { + return super.getNextNotEmptyCounterEntry(current, cookie) + as _DistanceLineCounterEntry; } else { return null; } @@ -1090,9 +1103,10 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// Returns the next entry in the collection for which /// CountVisible counter is not empty. @override - _DistanceLineCounterEntry getNextVisibleEntry(Object current) { - if (super.getNextVisibleEntry(current) is _DistanceLineCounterEntry) { - return super.getNextVisibleEntry(current); + _DistanceLineCounterEntry? getNextVisibleEntry(Object current) { + if (current is _TreeTableWithCounterEntry && + super.getNextVisibleEntry(current) is _DistanceLineCounterEntry) { + return super.getNextVisibleEntry(current) as _DistanceLineCounterEntry; } else { return null; } @@ -1104,9 +1118,10 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// /// Returns the previous entry. @override - _DistanceLineCounterEntry getPreviousEntry(Object current) { - if (super.getPreviousEntry(current) is _DistanceLineCounterEntry) { - return super.getPreviousEntry(current); + _DistanceLineCounterEntry? getPreviousEntry(Object current) { + if (current is _TreeTableEntryBase && + super.getPreviousEntry(current) is _DistanceLineCounterEntry) { + return super.getPreviousEntry(current) as _DistanceLineCounterEntry; } else { return null; } @@ -1121,11 +1136,13 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// Returns the previous entry in the collection for which the /// specific counter is not empty. @override - _DistanceLineCounterEntry getPreviousNotEmptyCounterEntry( + _DistanceLineCounterEntry? getPreviousNotEmptyCounterEntry( Object current, int cookie) { - if (super.getPreviousNotEmptyCounterEntry(current, cookie) - is _DistanceLineCounterEntry) { - return super.getPreviousNotEmptyCounterEntry(current, cookie); + if (current is _TreeTableEntryBase && + super.getPreviousNotEmptyCounterEntry(current, cookie) + is _DistanceLineCounterEntry) { + return super.getPreviousNotEmptyCounterEntry(current, cookie) + as _DistanceLineCounterEntry; } else { return null; } @@ -1139,9 +1156,11 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// Returns the previous entry in the collection for which CountVisible /// counter is not empty. @override - _DistanceLineCounterEntry getPreviousVisibleEntry(Object current) { - if (super.getPreviousVisibleEntry(current) is _DistanceLineCounterEntry) { - return super.getPreviousVisibleEntry(current); + _DistanceLineCounterEntry? getPreviousVisibleEntry(Object current) { + if (current is _TreeTableWithCounterEntry && + super.getPreviousVisibleEntry(current) is _DistanceLineCounterEntry) { + return super.getPreviousVisibleEntry(current) + as _DistanceLineCounterEntry; } else { return null; } @@ -1168,7 +1187,7 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// /// * value - _required_ - The object to be removed. @override - bool remove(Object value) => super.remove(value); + bool remove(Object? value) => super.remove(value); /// Gets the distance line counter entry for the given index. /// @@ -1176,9 +1195,9 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// /// Returns the distance line counter entry for the given index. @override - _DistanceLineCounterEntry operator [](int index) { + _DistanceLineCounterEntry? operator [](int index) { if (super[index] is _DistanceLineCounterEntry) { - return super[index]; + return super[index] as _DistanceLineCounterEntry; } else { return null; } @@ -1197,9 +1216,9 @@ class _DistanceLineCounterEntry extends _TreeTableWithCounterEntry { /// /// Returns the cumulative position of this node. @override - _DistanceLineCounter get getCounterPosition { + _DistanceLineCounter? get getCounterPosition { if (super.getCounterPosition is _DistanceLineCounter) { - return super.getCounterPosition; + return super.getCounterPosition as _DistanceLineCounter; } else { return null; } @@ -1209,9 +1228,9 @@ class _DistanceLineCounterEntry extends _TreeTableWithCounterEntry { /// /// Returns the distance line counter source. @override - _DistanceLineCounterSource get value { + _DistanceLineCounterSource? get value { if (super.value is _DistanceLineCounterSource) { - return super.value; + return super.value as _DistanceLineCounterSource; } else { return null; } @@ -1219,7 +1238,7 @@ class _DistanceLineCounterEntry extends _TreeTableWithCounterEntry { /// Sets the distance line counter source. @override - set value(Object value) { + set value(Object? value) { super.value = value; } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/editable_line_size_host_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/editable_line_size_host_base.dart index 26d499392..b9410c320 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/editable_line_size_host_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/editable_line_size_host_base.dart @@ -13,7 +13,7 @@ abstract class _EditableLineSizeHostBase extends _LineSizeHostBase { /// /// Returns the default size of lines. double get defaultLineSize => _defaultLineSize; - double _defaultLineSize = 0; + double _defaultLineSize = 0.0; /// Sets the default size of lines. set defaultLineSize(double value) { @@ -125,7 +125,7 @@ abstract class _EditableLineSizeHostBase extends _LineSizeHostBase { /// be obtained. /// /// Returns the `IEditableLineSizeHost` representing the nested lines. - _EditableLineSizeHostBase getNestedLines(int index); + _EditableLineSizeHostBase? getNestedLines(int index); /// Insert the given number of lines at the given index. /// @@ -135,7 +135,7 @@ abstract class _EditableLineSizeHostBase extends _LineSizeHostBase { /// `RemoveLines` call when lines should be moved. When it is null, /// empty lines with default size are inserted. void insertLines( - int insertAtLine, int count, _EditableLineSizeHostBase moveLines); + int insertAtLine, int count, _EditableLineSizeHostBase? moveLines); /// Removes a number of lines at the given index. /// @@ -144,7 +144,7 @@ abstract class _EditableLineSizeHostBase extends _LineSizeHostBase { /// * moveLines - _required_ - A container to save state for a subsequent /// `InsertLines` call when lines should be moved. void removeLines( - int removeAtLine, int count, _EditableLineSizeHostBase moveLines); + int removeAtLine, int count, _EditableLineSizeHostBase? moveLines); /// Sets the hidden state for the given range of lines. /// @@ -181,7 +181,7 @@ abstract class _EditableLineSizeHostBase extends _LineSizeHostBase { double operator [](int index) => this[index]; /// Sets the line size at the specified index. - void operator []=(int index, Object value) => this[index] = value; + void operator []=(int index, double value) => this[index] = value; } /// An object that implements the `PaddingDistance` property diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_scroll_axis.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_scroll_axis.dart index 6419ae11d..583e1a84d 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_scroll_axis.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_scroll_axis.dart @@ -29,15 +29,13 @@ class _LineScrollAxis extends _ScrollAxisBase { _distances = _DistanceRangeCounterCollection(); } - if (scrollLinesHost != null) { - scrollLinesHost.initializeScrollAxis(this); - } - _distances.defaultDistance = 1.0; + scrollLinesHost.initializeScrollAxis(this); + _distances!.defaultDistance = 1.0; } /// distances holds the visible lines. Each visible line /// has a distance of 1.0. Hidden lines have a distance of 0.0. - _DistanceCounterCollectionBase _distances; + _DistanceCounterCollectionBase? _distances; /// Gets the distances collection which is used internally for mapping /// from a point position to @@ -46,12 +44,12 @@ class _LineScrollAxis extends _ScrollAxisBase { /// Returns the distances collection which is used internally for mapping /// from a point position /// to a line index and vice versa. - _DistanceCounterCollectionBase get distances => _distances; + _DistanceCounterCollectionBase? get distances => _distances; /// Gets the total extent of all line sizes. /// /// Returns the total extent of all line sizes. - double get totalExtent => distances.totalDistance; + double get totalExtent => distances?.totalDistance ?? 0.0; /// Sets the default size of lines. @override @@ -74,13 +72,13 @@ class _LineScrollAxis extends _ScrollAxisBase { /// /// Returns the line count. @override - int get lineCount => _distances.count; + int get lineCount => _distances?.count ?? 0; /// Sets the line count. @override set lineCount(int value) { if (lineCount != value) { - _distances.count = value; + _distances!.count = value; updateScrollbar(); } } @@ -89,7 +87,7 @@ class _LineScrollAxis extends _ScrollAxisBase { /// /// Returns the index of the first visible Line in the Body region. @override - int get scrollLineIndex => scrollBarValueToLineIndex(scrollBar.value); + int get scrollLineIndex => scrollBarValueToLineIndex(scrollBar!.value); /// Sets the index of the first visible Line in the Body region. set scrollLinesIndex(int value) { @@ -106,7 +104,8 @@ class _LineScrollAxis extends _ScrollAxisBase { /// Returns the size of the view. @override double get viewSize { - if (scrollBar.value + scrollBar.largeChange > scrollBar.maximum) { + if (scrollBar != null && + scrollBar!.value + scrollBar!.largeChange > scrollBar!.maximum) { return _viewSize; } else { return renderSize; @@ -114,12 +113,12 @@ class _LineScrollAxis extends _ScrollAxisBase { } int determineLargeChange() { - double sbValue = scrollBar.maximum; + double sbValue = scrollBar!.maximum; final double abortSize = scrollPageSize; int count = 0; _viewSize = 0; - while (sbValue >= scrollBar.minimum) { + while (sbValue >= scrollBar!.minimum) { final int lineIndex = scrollBarValueToLineIndex(sbValue); final double size = getLineSize(lineIndex); if (_viewSize + size > abortSize) { @@ -152,22 +151,22 @@ class _LineScrollAxis extends _ScrollAxisBase { } double lineIndexToScrollBarValue(int lineIndex) => - _distances.getCumulatedDistanceAt(lineIndex); + _distances?.getCumulatedDistanceAt(lineIndex) ?? 0.0; int scrollBarValueToLineIndex(double sbValue) => - _distances.indexOfCumulatedDistance(sbValue); + _distances?.indexOfCumulatedDistance(sbValue) ?? 0; /// Updates the line size for visible lines to be "1" for LineScrollAxis void updateDistances() { - int repeatSizeCount; + int repeatSizeCount = -1; for (int index = 0; index < lineCount; index++) { - final bool hide = scrollLinesHost.getHidden(index, repeatSizeCount)[0]; - repeatSizeCount = scrollLinesHost.getHidden(index, repeatSizeCount)[1]; - final value = hide == true ? 0 : 1.0; + final bool hide = scrollLinesHost!.getHidden(index, repeatSizeCount)[0]; + repeatSizeCount = scrollLinesHost!.getHidden(index, repeatSizeCount)[1]; + final value = hide == true ? 0.0 : 1.0; final int rangeTo = getRangeToHelper(index, lineCount - 1, repeatSizeCount); - _distances.setRange(index, rangeTo, value); + _distances!.setRange(index, rangeTo, value); index = rangeTo; } } @@ -182,8 +181,8 @@ class _LineScrollAxis extends _ScrollAxisBase { /// Returns the index of the next scroll line. @override int getNextScrollLineIndex(int index) { - if (_distances.count > index) { - return _distances.getNextVisibleIndex(index); + if (_distances != null && _distances!.count > index) { + return _distances!.getNextVisibleIndex(index); } else { return 0; } @@ -195,8 +194,8 @@ class _LineScrollAxis extends _ScrollAxisBase { /// Returns the index of the previous scroll line. @override int getPreviousScrollLineIndex(int index) { - if (_distances.count > index) { - return _distances.getPreviousVisibleIndex(index); + if (_distances != null && _distances!.count > index) { + return _distances!.getPreviousVisibleIndex(index); } else { return 0; } @@ -211,7 +210,7 @@ class _LineScrollAxis extends _ScrollAxisBase { @override List getScrollLineIndex(int scrollLineIndex, double scrollLineDelta, [bool isRightToLeft = false]) { - scrollLineIndex = scrollBarValueToLineIndex(scrollBar.value); + scrollLineIndex = scrollBarValueToLineIndex(scrollBar!.value); scrollLineDelta = 0.0; return [scrollLineIndex, scrollLineDelta]; } @@ -235,13 +234,13 @@ class _LineScrollAxis extends _ScrollAxisBase { @override void onLinesInserted(int insertAt, int count) { final int to = insertAt + count - 1; - int repeatSizeCount; + int repeatSizeCount = -1; for (int index = insertAt; index <= to; index++) { - final bool hide = scrollLinesHost.getHidden(index, repeatSizeCount)[0]; - repeatSizeCount = scrollLinesHost.getHidden(index, repeatSizeCount)[1]; - final value = hide == true ? 0 : 1.0; + final bool hide = scrollLinesHost!.getHidden(index, repeatSizeCount)[0]; + repeatSizeCount = scrollLinesHost!.getHidden(index, repeatSizeCount)[1]; + final value = hide == true ? 0.0 : 1.0; final int rangeTo = getRangeToHelper(index, to, repeatSizeCount); - distances.setRange(index, rangeTo, value); + distances!.setRange(index, rangeTo, value); index = rangeTo; } } @@ -258,8 +257,9 @@ class _LineScrollAxis extends _ScrollAxisBase { @override _DoubleSpan rangeToPoints(_ScrollAxisRegion region, int first, int last, bool allowEstimatesForOutOfViewLines) { - bool firstVisible, lastVisible; - _VisibleLineInfo firstLine, lastLine; + bool firstVisible = false; + bool lastVisible = false; + _VisibleLineInfo? firstLine, lastLine; final List lineValues = getLinesAndVisibility( first, last, true, firstVisible, lastVisible, firstLine, lastLine); @@ -348,8 +348,8 @@ class _LineScrollAxis extends _ScrollAxisBase { double corner = lastLine.corner; if (!lastVisible || lastLine.region != _ScrollAxisRegion.body) { - corner = lastBodyVisibleLine.corner; - for (int n = lastBodyVisibleLine.lineIndex + 1; n <= last; n++) { + corner = lastBodyVisibleLine!.corner; + for (int n = lastBodyVisibleLine!.lineIndex + 1; n <= last; n++) { corner += getLineSize(n); } } @@ -357,8 +357,6 @@ class _LineScrollAxis extends _ScrollAxisBase { return _DoubleSpan(origin, corner); } } - - return _DoubleSpan(firstLine.origin, lastLine.corner); } } @@ -377,7 +375,7 @@ class _LineScrollAxis extends _ScrollAxisBase { int first, int last, bool allowEstimatesForOutOfViewLines) { final List<_DoubleSpan> result = []; for (int n = 0; n < 3; n++) { - _ScrollAxisRegion region; + late _ScrollAxisRegion region; if (n == 0) { region = _ScrollAxisRegion.header; } else if (n == 1) { @@ -402,7 +400,7 @@ class _LineScrollAxis extends _ScrollAxisBase { @override void scrollInView(int lineIndex, double lineSize, bool isRightToLeft) { final _VisibleLinesCollection lines = getVisibleLines(); - final _VisibleLineInfo line = lines.getVisibleLineAtLineIndex(lineIndex); + final _VisibleLineInfo? line = lines.getVisibleLineAtLineIndex(lineIndex); double delta = 0; if (line != null) { @@ -414,8 +412,8 @@ class _LineScrollAxis extends _ScrollAxisBase { delta = -1; } else if (!line.isClippedOrigin && line.isClippedCorner) { double y = line.size - line.clippedSize; - double visibleScrollIndex = scrollBar.value; - while (y > 0 && visibleScrollIndex < scrollBar.maximum) { + double visibleScrollIndex = scrollBar!.value; + while (y > 0 && visibleScrollIndex < scrollBar!.maximum) { delta++; visibleScrollIndex++; y -= getLineSize(scrollBarValueToLineIndex(visibleScrollIndex)); @@ -424,17 +422,17 @@ class _LineScrollAxis extends _ScrollAxisBase { } else { double visibleScrollIndex = lineIndexToScrollBarValue(lineIndex); - if (visibleScrollIndex > scrollBar.value) { + if (visibleScrollIndex > scrollBar!.value) { final int scrollIndexLinex = intPreviousPageLineIndex( scrollPageSize - getLineSize(lineIndex), lineIndex); visibleScrollIndex = lineIndexToScrollBarValue(scrollIndexLinex); } - delta = visibleScrollIndex - scrollBar.value; + delta = visibleScrollIndex - scrollBar!.value; } if (delta != 0) { - scrollBar.value += delta; + scrollBar!.value += delta; } super.scrollInView(lineIndex, lineSize, isRightToLeft); @@ -481,8 +479,8 @@ class _LineScrollAxis extends _ScrollAxisBase { @override void setScrollLineIndex(int scrollLineIndex, double scrollLineDelta) { scrollLineIndex = - min(max(_distances.count - 1, 0), max(0, scrollLineIndex)); - scrollBar.value = lineIndexToScrollBarValue(scrollLineIndex); + min(max(_distances!.count - 1, 0), max(0, scrollLineIndex)); + scrollBar!.value = lineIndexToScrollBarValue(scrollLineIndex); resetVisibleLines(); } @@ -523,7 +521,7 @@ class _LineScrollAxis extends _ScrollAxisBase { /// lines. if set to true - [hide]. @override void setLineHiddenState(int from, int to, bool hide) { - _distances.setRange(from, to, hide ? 0.0 : 1.0); + _distances!.setRange(from, to, hide ? 0.0 : 1.0); } /// Sets the size of the lines for the given range of lines. Will do nothing @@ -542,10 +540,10 @@ class _LineScrollAxis extends _ScrollAxisBase { setHeaderLineCount(headerLineCount); setFooterLineCount(footerLineCount); - final _ScrollBarBase sb = scrollBar; + final _ScrollBarBase sb = scrollBar!; final bool isMinimum = sb.minimum == sb.value; sb.minimum = headerLineCount.toDouble(); - final maximum = _distances.totalDistance - footerLineCount; + final maximum = _distances!.totalDistance - footerLineCount; sb ..maximum = max(maximum, 0) ..smallChange = 1 diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_collection.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_collection.dart index 20e0e21d0..3c7d4f6eb 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_collection.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_collection.dart @@ -20,13 +20,13 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase final _SortedRangeValueList _lineSizes = _SortedRangeValueList.from(-1); - _DistanceCounterCollectionBase _distances; + _DistanceCounterCollectionBase? _distances; int _isSuspendUpdates = 0; _SortedRangeValueList _lineHidden = _SortedRangeValueList.from(false); Map _lineNested = {}; - set distances(_DistanceCounterCollectionBase newValue) { + set distances(_DistanceCounterCollectionBase? newValue) { if (_distances == newValue) { return; } @@ -40,7 +40,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// Returns the distances collection which is used internally for mapping /// from a point position to a line index and vice versa. @override - _DistanceCounterCollectionBase get distances { + _DistanceCounterCollectionBase? get distances { if (_distances == null) { _distances = _DistanceRangeCounterCollection.fromPaddingDistance(paddingDistance); @@ -68,7 +68,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase if (onDefaultLineSizeChanged != null) { final defaultLineSizeChangedArgs = _DefaultLineSizeChangedArgs.fromArgs(savedValue, _defaultLineSize); - onDefaultLineSizeChanged(defaultLineSizeChangedArgs); + onDefaultLineSizeChanged!(defaultLineSizeChangedArgs); } } } @@ -79,7 +79,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase if (_footerLineCount != value) { _footerLineCount = value; if (onFooterLineCountChanged != null) { - onFooterLineCountChanged(); + onFooterLineCountChanged!(); } } } @@ -90,7 +90,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase if (_headerLineCount != value) { _headerLineCount = value; if (onHeaderLineCountChanged != null) { - onHeaderLineCountChanged(); + onHeaderLineCountChanged!(); } } } @@ -108,7 +108,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } if (_distances != null) { - _distances.count = _lineCount; + _distances!.count = _lineCount; } } } @@ -156,8 +156,8 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase double get totalExtent { // This only works if the DistanceCollection has been // setup for pixel scrolling. - if (distances.defaultDistance == _defaultLineSize) { - return _distances.totalDistance; + if (distances != null && distances!.defaultDistance == _defaultLineSize) { + return _distances!.totalDistance; } return double.nan; } @@ -182,8 +182,8 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// Returns the nested distances, if a line contains a nested lines /// collection, otherwise null. @override - _DistanceCounterCollectionBase getDistances(int line) { - final Object nestedLines = getNestedLines(line); + _DistanceCounterCollectionBase? getDistances(int line) { + final Object? nestedLines = getNestedLines(line); if (nestedLines is _DistancesHostBase) { return nestedLines.distances; } else { @@ -216,7 +216,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// /// Returns the [IEditableLineSizeHost] representing the nested lines. @override - _EditableLineSizeHostBase getNestedLines(int index) { + _EditableLineSizeHostBase? getNestedLines(int index) { if (_lineNested.containsKey(index)) { return _lineNested[index]; } @@ -235,7 +235,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase @override List getSize(int index, int repeatValueCount) { repeatValueCount = 1; - final _EditableLineSizeHostBase nested = getNestedLines(index); + final _EditableLineSizeHostBase? nested = getNestedLines(index); if (nested != null) { return [nested.totalExtent, repeatValueCount]; } @@ -262,7 +262,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase repeatValueCount = 1; if (_lineNested.containsKey(index)) { - return [_lineNested[index].totalExtent, repeatValueCount]; + return [_lineNested[index]?.totalExtent, repeatValueCount]; } final bool hide = _lineHidden.getRange(index, repeatValueCount)[0]; @@ -280,31 +280,33 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } void initializeDistances() { - distances - ..clear() - ..count = getLineCount() - ..defaultDistance = defaultLineSize; - _lineNested.forEach((key, value) { - int repeatSizeCount; - final bool hide = getHidden(key, repeatSizeCount)[0]; - repeatSizeCount = getHidden(key, repeatSizeCount)[1]; - if (hide) { - _distances.setNestedDistances(key, null); - } else { - _distances.setNestedDistances(key, value.distances); - } - }); + if (distances != null) { + distances! + ..clear() + ..count = getLineCount() + ..defaultDistance = defaultLineSize; + _lineNested.forEach((key, value) { + int repeatSizeCount = -1; + final bool hide = getHidden(key, repeatSizeCount)[0]; + repeatSizeCount = getHidden(key, repeatSizeCount)[1]; + if (hide) { + _distances!.setNestedDistances(key, null); + } else { + _distances!.setNestedDistances(key, value.distances); + } + }); - for (final _RangeValuePair entry in _lineSizes.rangeValues) { - if (entry.value != -2) { - _distances.setRange(entry.start, entry.end, - entry.value < 0 ? defaultLineSize : entry.value); + for (final _RangeValuePair entry in _lineSizes.rangeValues) { + if (entry.value != -2) { + _distances!.setRange(entry.start.toInt(), entry.end.toInt(), + entry.value < 0.0 ? defaultLineSize : entry.value); + } } - } - for (final _RangeValuePair entry in _lineHidden.rangeValues) { - if (entry.value is bool && entry.value) { - setRange(entry.start, entry.end, 0); + for (final _RangeValuePair entry in _lineHidden.rangeValues) { + if (entry.value is bool && entry.value) { + setRange(entry.start.toInt(), entry.end.toInt(), 0.0); + } } } } @@ -327,8 +329,9 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// size are inserted. @override void insertLines( - int insertAtLine, int count, _EditableLineSizeHostBase moveLines) { - final _LineSizeCollection _moveLines = moveLines; + int insertAtLine, int count, _EditableLineSizeHostBase? moveLines) { + final _LineSizeCollection? _moveLines = + moveLines != null ? moveLines as _LineSizeCollection : null; _lineSizes.insertWithThreeArgs( insertAtLine, count, _moveLines == null ? null : _moveLines._lineSizes); _lineHidden.insertWithThreeArgs(insertAtLine, count, @@ -358,13 +361,13 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } if (_distances != null) { - _DistancesUtil.onInserted(_distances, this, insertAtLine, count); + _DistancesUtil.onInserted(_distances!, this, insertAtLine, count); } if (onLinesInserted != null) { final linesInsertedArgs = _LinesInsertedArgs.fromArgs(insertAtLine, count); - onLinesInserted(linesInsertedArgs); + onLinesInserted!(linesInsertedArgs); } } @@ -373,7 +376,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// * scrollAxis - _required_ - The scroll axis. @override void initializeScrollAxis(_ScrollAxisBase scrollAxis) { - final _PixelScrollAxis pixelScrollAxis = scrollAxis; + final _PixelScrollAxis? pixelScrollAxis = scrollAxis as _PixelScrollAxis; if (_lineNested.isNotEmpty && pixelScrollAxis == null) { throw Exception( 'When you have nested line collections you need to use PixelScrolling!'); @@ -390,7 +393,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } for (final entry in _lineNested.entries) { - pixelScrollAxis.setNestedLines(entry.key, entry.value.distances); + pixelScrollAxis!.setNestedLines(entry.key, entry.value.distances); } for (final _RangeValuePair entry in _lineHidden) { @@ -417,8 +420,9 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// call when lines should be moved. @override void removeLines( - int removeAtLine, int count, _EditableLineSizeHostBase moveLines) { - final _LineSizeCollection _moveLines = moveLines; + int removeAtLine, int count, _EditableLineSizeHostBase? moveLines) { + final _LineSizeCollection? _moveLines = + moveLines != null ? moveLines as _LineSizeCollection : null; _lineSizes.removeWithThreeArgs( removeAtLine, count, _moveLines == null ? null : _moveLines._lineSizes); _lineHidden.removeWithThreeArgs(removeAtLine, count, @@ -448,12 +452,12 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } if (_distances != null) { - _distances.remove(removeAtLine, count); + _distances!.remove(removeAtLine, count); } if (onLinesRemoved != null) { final linesRemovedArgs = _LinesRemovedArgs.fromArgs(removeAtLine, count); - onLinesRemoved(linesRemovedArgs); + onLinesRemoved!(linesRemovedArgs); } } @@ -496,7 +500,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase if (onLineHiddenChanged != null) { final hiddenRangeChangedArgs = _HiddenRangeChangedArgs.fromArgs(0, _lineCount - 1, false); - onLineHiddenChanged(hiddenRangeChangedArgs); + onLineHiddenChanged!(hiddenRangeChangedArgs); } } } @@ -518,12 +522,12 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } // DistancesLineHiddenChanged checks both hidden state and sizes together... if (_distances != null) { - _DistancesUtil.distancesLineHiddenChanged(distances, this, from, to); + _DistancesUtil.distancesLineHiddenChanged(distances!, this, from, to); } _HiddenRangeChangedArgs hiddenRangeChangedArgs; if (onLineHiddenChanged != null) { hiddenRangeChangedArgs = _HiddenRangeChangedArgs.fromArgs(from, to, hide); - onLineHiddenChanged(hiddenRangeChangedArgs); + onLineHiddenChanged!(hiddenRangeChangedArgs); } } @@ -598,11 +602,11 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// is null the line will be converted to a normal (not nested) line with /// default line size. @override - void setNestedLines(int index, _EditableLineSizeHostBase nestedLines) { + void setNestedLines(int index, _EditableLineSizeHostBase? nestedLines) { if (nestedLines != null) { _lineSizes[index] = -2; // -1 indicates default value, -2 indicates nested. - _lineNested[index] = nestedLines; + _lineNested[index] = nestedLines as _LineSizeCollection; } else { _lineSizes[index] = -1; // -1 indicates default value, -2 indicates nested. @@ -613,11 +617,11 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } if (_distances != null) { - _DistancesUtil.distancesLineSizeChanged(distances, this, index, index); + _DistancesUtil.distancesLineSizeChanged(distances!, this, index, index); } if (onLineSizeChanged != null) { - onLineSizeChanged(_RangeChangedArgs.fromArgs(index, index)); + onLineSizeChanged!(_RangeChangedArgs.fromArgs(index, index)); } } @@ -640,13 +644,13 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } if (_distances != null) { - _DistancesUtil.distancesLineHiddenChanged(_distances, this, from, to); + _DistancesUtil.distancesLineHiddenChanged(_distances!, this, from, to); } _RangeChangedArgs rangeChangedArgs; if (onLineSizeChanged != null) { rangeChangedArgs = _RangeChangedArgs.fromRangeChangedArgs(from, to, saveValue, size); - onLineSizeChanged(rangeChangedArgs); + onLineSizeChanged!(rangeChangedArgs); } } @@ -662,13 +666,13 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// Returns the line size at the specified index. @override double operator [](int index) { - int repeatValueCount; + final int repeatValueCount = -1; return getRange(index, repeatValueCount)[0]; } /// Sets the line size at the specified index. @override - void operator []=(int index, Object value) { + void operator []=(int index, double value) { setRange(index, index, value); } } @@ -691,7 +695,7 @@ class _DistancesUtil { int to) { final Object ndh = linesHost; for (int n = from; n <= to; n++) { - int repeatSizeCount; + int repeatSizeCount = -1; final bool hide = linesHost.getHidden(n, repeatSizeCount)[0]; repeatSizeCount = linesHost.getHidden(n, repeatSizeCount)[1]; @@ -730,7 +734,7 @@ class _DistancesUtil { for (int n = from; n <= to; n++) { void _setRange() { - int repeatSizeCount; + int repeatSizeCount = -1; final double size = linesHost.getSize(n, repeatSizeCount)[0]; repeatSizeCount = linesHost.getSize(n, repeatSizeCount)[1]; final int rangeTo = getRangeToHelper(n, to, repeatSizeCount); @@ -775,7 +779,7 @@ class _DistancesUtil { _LineSizeHostBase linesHost, int insertAt, int count) { distances.insert(insertAt, count); final int to = insertAt + count - 1; - int repeatSizeCount; + int repeatSizeCount = -1; // Set line sizes for (int index = insertAt; index <= to; index++) { diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_host_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_host_base.dart index 0fc1fb980..a215b1f4a 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_host_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_host_base.dart @@ -35,28 +35,28 @@ typedef _LineSizeChangedCallback = void Function( /// size of line. abstract class _LineSizeHostBase { /// Occurs when the default line size changed. - _DefaultLineSizeChangedCallback onDefaultLineSizeChanged; + _DefaultLineSizeChangedCallback? onDefaultLineSizeChanged; /// Occurs when the footer line count was changed. - _LineCountChangedCallback onFooterLineCountChanged; + _LineCountChangedCallback? onFooterLineCountChanged; /// Occurs when the header line count was changed. - _LineCountChangedCallback onHeaderLineCountChanged; + _LineCountChangedCallback? onHeaderLineCountChanged; /// Occurs when a lines size was changed. - _LineSizeChangedCallback onLineSizeChanged; + _LineSizeChangedCallback? onLineSizeChanged; /// Occurs when a lines hidden state changed. - _LineHiddenChangedCallback onLineHiddenChanged; + _LineHiddenChangedCallback? onLineHiddenChanged; /// Occurs when the line count was changed. - _LineCountChangedCallback onLineCountChanged; + _LineCountChangedCallback? onLineCountChanged; /// Occurs when lines were inserted. - _LinesInsertedCallback onLinesInserted; + _LinesInsertedCallback? onLinesInserted; /// Occurs when lines were removed. - _LinesRemovedCallback onLinesRemoved; + _LinesRemovedCallback? onLinesRemoved; /// Returns the default line size.. double getDefaultLineSize(); @@ -106,7 +106,7 @@ mixin _DistancesHostBase { /// Gets the distances of the lines. /// /// Returns the distances of the lines. - _DistanceCounterCollectionBase get distances; + _DistanceCounterCollectionBase? get distances; } /// An object that implements the `GetDistances` method. @@ -118,5 +118,5 @@ abstract class _NestedDistancesHostBase { /// /// Returns the nested distances, if a line contains a nested lines /// collection, otherwise null. - _DistanceCounterCollectionBase getDistances(int line); + _DistanceCounterCollectionBase? getDistances(int line); } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/pixel_scroll_axis.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/pixel_scroll_axis.dart index b050c3fb5..e1d9ac617 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/pixel_scroll_axis.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/pixel_scroll_axis.dart @@ -39,7 +39,7 @@ class _PixelScrollAxis extends _ScrollAxisBase { /// * scrollLinesHost - _required_ - The scroll lines host. /// * distancesHost - _required_ - The distances host. _PixelScrollAxis.fromPixelScrollAxis(_ScrollBarBase scrollBar, - _LineSizeHostBase scrollLinesHost, _DistancesHostBase distancesHost) + _LineSizeHostBase? scrollLinesHost, _DistancesHostBase? distancesHost) : super(scrollBar, scrollLinesHost) { if (distancesHost != null) { _distancesHost = distancesHost; @@ -56,9 +56,9 @@ class _PixelScrollAxis extends _ScrollAxisBase { /// Distances holds the line sizes. Hidden lines /// have a distance of 0.0. - _DistanceCounterCollectionBase _distances; - _DistancesHostBase _distancesHost; - _ScrollAxisBase _parentScrollAxis; + _DistanceCounterCollectionBase? _distances; + _DistancesHostBase? _distancesHost; + _ScrollAxisBase? _parentScrollAxis; bool inLineResize = false; /// Gets the distances collection which is used internally for mapping @@ -66,9 +66,9 @@ class _PixelScrollAxis extends _ScrollAxisBase { /// /// Returns the distances collection which is used internally for mapping /// from a point position to a line index and vice versa. - _DistanceCounterCollectionBase get distances { + _DistanceCounterCollectionBase? get distances { if (_distancesHost != null) { - return _distancesHost.distances; + return _distancesHost!.distances; } return _distances; @@ -77,33 +77,33 @@ class _PixelScrollAxis extends _ScrollAxisBase { /// Gets the total extent of all line sizes. /// /// Returns the total extent of all line sizes. - double get totalExtent => distances.totalDistance; + double get totalExtent => distances?.totalDistance ?? 0.0; /// Gets the default size of lines. /// /// Returns the default size of lines. @override - double get defaultLineSize => _distances.defaultDistance; + double get defaultLineSize => _distances?.defaultDistance ?? 0.0; /// Sets the default size of lines. @override set defaultLineSize(double value) { if (_defaultLineSize != value) { if (_distances != null) { - _distances + _distances! ..defaultDistance = value ..clear(); if (scrollLinesHost != null) { - scrollLinesHost.initializeScrollAxis(this); + scrollLinesHost!.initializeScrollAxis(this); } } updateScrollbar(); if (_parentScrollAxis != null) { - _parentScrollAxis.setLineSize( - startLineIndex, startLineIndex, distances.totalDistance); + _parentScrollAxis!.setLineSize( + startLineIndex, startLineIndex, distances?.totalDistance ?? 0); } } } @@ -138,14 +138,14 @@ class _PixelScrollAxis extends _ScrollAxisBase { /// /// Returns the line count. @override - int get lineCount => distances.count; + int get lineCount => distances?.count ?? 0; /// Sets the line count. @override set lineCount(int value) { if (lineCount != value) { if (_distances != null) { - _distances.count = value; + _distances!.count = value; } updateScrollbar(); } @@ -156,7 +156,7 @@ class _PixelScrollAxis extends _ScrollAxisBase { /// Returns the index of the scroll line. @override int get scrollLineIndex => - distances.indexOfCumulatedDistance(scrollBar.value); + distances?.indexOfCumulatedDistance(scrollBar?.value ?? 0.0) ?? -1; /// Sets the index of the first visible Line in the Body region. @override @@ -173,14 +173,15 @@ class _PixelScrollAxis extends _ScrollAxisBase { /// /// Returns the size of the view. @override - double get viewSize => min(renderSize, distances.totalDistance); + double get viewSize => min(renderSize, distances?.totalDistance ?? 0.0); /// Aligns the scroll line. @override void alignScrollLine() { - final double d = distances.getAlignedScrollValue(scrollBar.value); + final double d = + distances?.getAlignedScrollValue(scrollBar?.value ?? 0.0) ?? 0.0; if (!(d == double.nan)) { - scrollBar.value = d; + scrollBar!.value = d; } } @@ -190,7 +191,8 @@ class _PixelScrollAxis extends _ScrollAxisBase { /// /// Returns the index of the next scroll line. @override - int getNextScrollLineIndex(int index) => distances.getNextVisibleIndex(index); + int getNextScrollLineIndex(int index) => + distances?.getNextVisibleIndex(index) ?? -1; /// Gets the index of the previous scroll line. /// @@ -199,10 +201,10 @@ class _PixelScrollAxis extends _ScrollAxisBase { /// Returns the index of the previous scroll line. @override int getPreviousScrollLineIndex(int index) { - double point = distances.getCumulatedDistanceAt(index); + double point = distances?.getCumulatedDistanceAt(index) ?? 0.0; point--; if (point > -1) { - return distances.indexOfCumulatedDistance(point); + return distances?.indexOfCumulatedDistance(point) ?? 0; } return -1; } @@ -216,50 +218,53 @@ class _PixelScrollAxis extends _ScrollAxisBase { @override List getScrollLineIndex(int scrollLineIndex, double scrollLineDelta, [bool isRightToLeft = false]) { - if (!isRightToLeft) { + if (!isRightToLeft && scrollBar != null && distances != null) { scrollLineIndex = - max(0, distances.indexOfCumulatedDistance(scrollBar.value)); + max(0, distances!.indexOfCumulatedDistance(scrollBar!.value)); if (scrollLineIndex >= lineCount) { scrollLineDelta = 0.0; } else { - scrollLineDelta = (scrollBar.value - - distances.getCumulatedDistanceAt(scrollLineIndex)) + scrollLineDelta = (scrollBar!.value - + distances!.getCumulatedDistanceAt(scrollLineIndex)) .toDouble(); } } else { scrollLineIndex = max( 0, - distances.indexOfCumulatedDistance(scrollBar.maximum - - scrollBar.largeChange - - scrollBar.value + + distances!.indexOfCumulatedDistance(scrollBar!.maximum - + scrollBar!.largeChange - + scrollBar!.value + (headerLineCount == 0 ? clip.start : clip.start + headerExtent) + - (renderSize > scrollBar.maximum + footerExtent - ? renderSize - scrollBar.maximum - footerExtent - headerExtent + (renderSize > scrollBar!.maximum + footerExtent + ? renderSize - + scrollBar!.maximum - + footerExtent - + headerExtent : 0))); if (scrollLineIndex >= lineCount) { scrollLineDelta = 0.0; } else { - scrollLineDelta = (scrollBar.maximum - - scrollBar.largeChange - - scrollBar.value + + scrollLineDelta = (scrollBar!.maximum - + scrollBar!.largeChange - + scrollBar!.value + (headerLineCount == 0 ? clip.start - : (scrollBar.maximum - - scrollBar.largeChange - - scrollBar.value != + : (scrollBar!.maximum - + scrollBar!.largeChange - + scrollBar!.value != -1 ? clip.start + headerExtent : (clip.start > 0 ? clip.start + headerExtent + 1 : 1))) + - (renderSize > scrollBar.maximum + footerExtent + (renderSize > scrollBar!.maximum + footerExtent ? renderSize - - scrollBar.maximum - + scrollBar!.maximum - footerExtent - headerExtent : 0) - - distances.getCumulatedDistanceAt(scrollLineIndex)) + distances!.getCumulatedDistanceAt(scrollLineIndex)) .toDouble(); } } @@ -280,12 +285,12 @@ class _PixelScrollAxis extends _ScrollAxisBase { if (line.isHeader) { return line.origin; } else if (line.isFooter) { - return scrollBar.maximum - + return scrollBar!.maximum - lines[lines.firstFooterVisibleIndex].origin + line.origin; } - return line.origin - scrollBar.minimum + scrollBar.value; + return line.origin - scrollBar!.minimum + scrollBar!.value; } /// Gets the cumulated corner taking scroll position into account. @@ -301,12 +306,12 @@ class _PixelScrollAxis extends _ScrollAxisBase { if (line.isHeader) { return line.corner; } else if (line.isFooter) { - return scrollBar.maximum - + return scrollBar!.maximum - lines[lines.firstFooterVisibleIndex].origin + line.corner; } - return line.corner - scrollBar.minimum + scrollBar.value; + return line.corner - scrollBar!.minimum + scrollBar!.value; } /// This method is called in response to a MouseWheel event. @@ -314,7 +319,7 @@ class _PixelScrollAxis extends _ScrollAxisBase { /// * delta - _required_ - The delta. @override void mouseWheel(int delta) { - scrollBar.value -= delta; + scrollBar!.value -= delta; } /// Called when lines were removed in ScrollLinesHost. @@ -324,7 +329,7 @@ class _PixelScrollAxis extends _ScrollAxisBase { @override void onLinesRemoved(int removeAt, int count) { if (distances != null) { - distances.remove(removeAt, count); + distances!.remove(removeAt, count); } } @@ -335,7 +340,7 @@ class _PixelScrollAxis extends _ScrollAxisBase { @override void onLinesInserted(int insertAt, int count) { if (distances != null) { - _DistancesUtil.onInserted(distances, scrollLinesHost, insertAt, count); + _DistancesUtil.onInserted(distances!, scrollLinesHost!, insertAt, count); } } @@ -353,14 +358,14 @@ class _PixelScrollAxis extends _ScrollAxisBase { List<_DoubleSpan> rangeToRegionPoints( int first, int last, bool allowEstimatesForOutOfViewLines) { double p1, p2; - p1 = distances.getCumulatedDistanceAt(first); - p2 = last >= distances.count - 1 - ? distances.totalDistance - : distances.getCumulatedDistanceAt(last + 1); + p1 = distances!.getCumulatedDistanceAt(first); + p2 = last >= distances!.count - 1 + ? distances!.totalDistance + : distances!.getCumulatedDistanceAt(last + 1); final List<_DoubleSpan> result = []; for (int n = 0; n < 3; n++) { - _ScrollAxisRegion region; + late _ScrollAxisRegion region; if (n == 0) { region = _ScrollAxisRegion.header; } else if (n == 1) { @@ -379,7 +384,7 @@ class _PixelScrollAxis extends _ScrollAxisBase { inLineResize = true; super.resetLineResize(); if (_parentScrollAxis != null) { - _parentScrollAxis.resetLineResize(); + _parentScrollAxis!.resetLineResize(); } inLineResize = false; @@ -401,16 +406,16 @@ class _PixelScrollAxis extends _ScrollAxisBase { // If line is visible use already calculated values, // otherwise get value from Distances - final _VisibleLineInfo line1 = lines.getVisibleLineAtLineIndex(first); - final _VisibleLineInfo line2 = lines.getVisibleLineAtLineIndex(last); + final _VisibleLineInfo? line1 = lines.getVisibleLineAtLineIndex(first); + final _VisibleLineInfo? line2 = lines.getVisibleLineAtLineIndex(last); double p1, p2; p1 = line1 == null - ? distances.getCumulatedDistanceAt(first) + ? distances!.getCumulatedDistanceAt(first) : getCumulatedOrigin(line1); p2 = line2 == null - ? distances.getCumulatedDistanceAt(last + 1) + ? distances!.getCumulatedDistanceAt(last + 1) : getCumulatedCorner(line2); return rangeToPointsHelper(region, p1, p2); @@ -419,33 +424,33 @@ class _PixelScrollAxis extends _ScrollAxisBase { _DoubleSpan rangeToPointsHelper( _ScrollAxisRegion region, double p1, double p2) { final _VisibleLinesCollection lines = getVisibleLines(); + _DoubleSpan doubleSpan = _DoubleSpan.empty(); switch (region) { case _ScrollAxisRegion.header: if (headerLineCount > 0) { - return _DoubleSpan(p1, p2); - } else { - return _DoubleSpan.empty(); + doubleSpan = _DoubleSpan(p1, p2); } break; case _ScrollAxisRegion.footer: if (isFooterVisible) { final _VisibleLineInfo l = lines[lines.firstFooterVisibleIndex]; - final double p3 = distances.totalDistance - footerExtent; + final double p3 = distances!.totalDistance - footerExtent; p1 += l.origin - p3; p2 += l.origin - p3; - return _DoubleSpan(p1, p2); - } else { - return _DoubleSpan.empty(); + doubleSpan = _DoubleSpan(p1, p2); } break; case _ScrollAxisRegion.body: - p1 += headerExtent - scrollBar.value; - p2 += headerExtent - scrollBar.value; - return _DoubleSpan(p1, p2); + p1 += headerExtent - scrollBar!.value; + p2 += headerExtent - scrollBar!.value; + doubleSpan = _DoubleSpan(p1, p2); + break; + default: + doubleSpan = _DoubleSpan.empty(); break; } - return _DoubleSpan.empty(); + return doubleSpan; } /// Sets the index of the scroll line. @@ -455,46 +460,46 @@ class _PixelScrollAxis extends _ScrollAxisBase { @override void setScrollLineIndex(int scrollLineIndex, double scrollLineDelta) { scrollLineIndex = min(lineCount, max(0, scrollLineIndex)); - scrollBar.value = - distances.getCumulatedDistanceAt(scrollLineIndex) + scrollLineDelta; + scrollBar!.value = + distances!.getCumulatedDistanceAt(scrollLineIndex) + scrollLineDelta; resetVisibleLines(); } /// Scrolls to next page. @override void scrollToNextPage() { - scrollBar.value += max( - scrollBar.smallChange, scrollBar.largeChange - scrollBar.smallChange); + scrollBar!.value += max(scrollBar!.smallChange, + scrollBar!.largeChange - scrollBar!.smallChange); scrollToNextLine(); } /// Scrolls to next line. @override void scrollToNextLine() { - final double d = distances.getNextScrollValue(scrollBar.value); + final double d = distances!.getNextScrollValue(scrollBar!.value); if (!(d == double.nan)) { - scrollBar.value = d <= scrollBar.value - ? distances.getNextScrollValue(scrollBar.value + 1) + scrollBar!.value = d <= scrollBar!.value + ? distances!.getNextScrollValue(scrollBar!.value + 1) : d; } else { - scrollBar.value += scrollBar.smallChange; + scrollBar!.value += scrollBar!.smallChange; } } /// Scrolls to previous line. @override void scrollToPreviousLine() { - final double d = distances.getPreviousScrollValue(scrollBar.value); + final double d = distances!.getPreviousScrollValue(scrollBar!.value); if (!(d == double.nan)) { - scrollBar.value = d; + scrollBar!.value = d; } } /// Scrolls to previous page. @override void scrollToPreviousPage() { - scrollBar.value -= max( - scrollBar.smallChange, scrollBar.largeChange - scrollBar.smallChange); + scrollBar!.value -= max(scrollBar!.smallChange, + scrollBar!.largeChange - scrollBar!.smallChange); alignScrollLine(); } @@ -507,7 +512,7 @@ class _PixelScrollAxis extends _ScrollAxisBase { @override void scrollInView(int lineIndex, double lineSize, bool isRightToLeft) { final _VisibleLinesCollection lines = getVisibleLines(); - final _VisibleLineInfo line = lines.getVisibleLineAtLineIndex(lineIndex); + final _VisibleLineInfo? line = lines.getVisibleLineAtLineIndex(lineIndex); double delta = 0; if (line != null) { @@ -529,17 +534,17 @@ class _PixelScrollAxis extends _ScrollAxisBase { delta = lineSize - line.clippedSize; } } else { - double d = distances.getCumulatedDistanceAt(lineIndex); + double d = distances!.getCumulatedDistanceAt(lineIndex); - if (d > scrollBar.value) { - d = d + lineSize - scrollBar.largeChange; + if (d > scrollBar!.value) { + d = d + lineSize - scrollBar!.largeChange; } - delta = d - scrollBar.value; + delta = d - scrollBar!.value; } if (delta != 0) { - scrollBar.value += delta; + scrollBar!.value += delta; } } @@ -551,12 +556,12 @@ class _PixelScrollAxis extends _ScrollAxisBase { if (value == 0) { _footerExtent = 0; } else { - if (distances.count <= value) { + if (distances!.count <= value) { _footerExtent = 0; return; } - final int n = distances.count - value; + final int n = distances!.count - value; // The Total distance must be reduced by the padding size of the Distance // total size. Then it should be calculated. This issue is occured in @@ -566,13 +571,14 @@ class _PixelScrollAxis extends _ScrollAxisBase { if (!isDistanceCounterSubset) { // Nested Grid cells in GridControl is not // DistanceRangeCounterCollection type. - final _DistanceRangeCounterCollection _distances = distances; - _footerExtent = distances.totalDistance - - _distances.paddingDistance - - distances.getCumulatedDistanceAt(n); + final _DistanceRangeCounterCollection? _distances = + distances as _DistanceRangeCounterCollection; + _footerExtent = distances!.totalDistance - + _distances!.paddingDistance - + distances!.getCumulatedDistanceAt(n); } else { _footerExtent = - distances.totalDistance - distances.getCumulatedDistanceAt(n); + distances!.totalDistance - distances!.getCumulatedDistanceAt(n); } } } @@ -583,7 +589,7 @@ class _PixelScrollAxis extends _ScrollAxisBase { @override void setHeaderLineCount(int value) { _headerExtent = - distances.getCumulatedDistanceAt(min(value, distances.count)); + distances!.getCumulatedDistanceAt(min(value, distances!.count)); } /// Sets the hidden state of the lines. @@ -598,7 +604,7 @@ class _PixelScrollAxis extends _ScrollAxisBase { setLineSize(from, to, 0.0); } else { for (int n = from; n <= to; n++) { - int repeatSizeCount; + int repeatSizeCount = -1; final double size = getLineSizeWithTwoArgs(n, repeatSizeCount)[0]; repeatSizeCount = getLineSizeWithTwoArgs(n, repeatSizeCount)[1]; final int rangeTo = getRangeToHelper(n, to, repeatSizeCount); @@ -617,14 +623,14 @@ class _PixelScrollAxis extends _ScrollAxisBase { @override void setLineSize(int from, int to, double size) { if (_distances != null) { - _distances.setRange(from, to, size); + _distances!.setRange(from, to, size); } // special case for SetLineResize when axis is nested. Parent Scroll // Axis relies on Distances.TotalDistance and this only gets updated if // we temporarily set the value in the collection. else if (_distancesHost != null && inLineResize) { - _distancesHost.distances.setRange(from, to, size); + _distancesHost!.distances?.setRange(from, to, size); } } @@ -636,7 +642,7 @@ class _PixelScrollAxis extends _ScrollAxisBase { @override void setLineResize(int index, double size) { inLineResize = true; - if (distances.getNestedDistances(index) == null) { + if (distances!.getNestedDistances(index) == null) { super.setLineResize(index, size); } else { markDirty(); @@ -644,7 +650,8 @@ class _PixelScrollAxis extends _ScrollAxisBase { } if (_parentScrollAxis != null) { - _parentScrollAxis.setLineResize(startLineIndex, distances.totalDistance); + _parentScrollAxis! + .setLineResize(startLineIndex, distances!.totalDistance); } inLineResize = false; @@ -654,9 +661,9 @@ class _PixelScrollAxis extends _ScrollAxisBase { /// /// * index - _required_ - The index. /// * nestedLines - _required_ - The nested lines. - void setNestedLines(int index, _DistanceCounterCollectionBase nestedLines) { + void setNestedLines(int index, _DistanceCounterCollectionBase? nestedLines) { if (distances != null) { - distances.setNestedDistances(index, nestedLines); + distances!.setNestedDistances(index, nestedLines); } } @@ -664,17 +671,17 @@ class _PixelScrollAxis extends _ScrollAxisBase { /// total size of lines in body. @override void updateScrollbar() { - final _ScrollBarBase sb = scrollBar; + final _ScrollBarBase? sb = scrollBar; setHeaderLineCount(headerLineCount); setFooterLineCount(footerLineCount); - final double delta = headerExtent - sb.minimum; + final double delta = headerExtent - sb!.minimum; final double oldValue = sb.value; sb ..minimum = headerExtent - ..maximum = distances.totalDistance - footerExtent - ..smallChange = distances.defaultDistance; + ..maximum = distances!.totalDistance - footerExtent + ..smallChange = distances!.defaultDistance; final double proposeLargeChange = scrollPageSize; sb.largeChange = proposeLargeChange; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/range_changed_event.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/range_changed_event.dart index ef42e1bc8..51dac4ceb 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/range_changed_event.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/range_changed_event.dart @@ -152,7 +152,7 @@ class _HiddenRangeChangedArgs { int _from = 0; int _to = 0; - bool _hide; + bool _hide = false; /// Gets the start index of the hidden range. /// @@ -214,10 +214,10 @@ class _ScrollChangedArgs { _scrollChangedAction = action; } - _ScrollChangedAction _scrollChangedAction; + _ScrollChangedAction? _scrollChangedAction; /// Gets the scroll changed action. /// /// Returns the `ScrollChangedAction`. - _ScrollChangedAction get action => _scrollChangedAction; + _ScrollChangedAction? get action => _scrollChangedAction; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/row_column_index.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/row_column_index.dart index 9c5c2c626..70cdab16d 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/row_column_index.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/row_column_index.dart @@ -22,7 +22,7 @@ class RowColumnIndex { bool get isEmpty => rowIndex == _MathHelper.minvalue; /// Whether this object and a specified object are equal. - bool equals(Object obj) { + bool equals(RowColumnIndex obj) { final RowColumnIndex other = obj; return other.rowIndex == rowIndex && other.columnIndex == columnIndex; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_axis_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_axis_base.dart index 7558a4317..5cc694093 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_axis_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_axis_base.dart @@ -22,7 +22,8 @@ abstract class _ScrollAxisBase { /// /// * scrollBar The scroll bar state. /// * scrollLinesHost The scroll lines host. - _ScrollAxisBase(_ScrollBarBase scrollBar, _LineSizeHostBase scrollLinesHost) { + _ScrollAxisBase( + _ScrollBarBase? scrollBar, _LineSizeHostBase? scrollLinesHost) { if (scrollBar == null) { throw Exception(); } @@ -35,8 +36,8 @@ abstract class _ScrollAxisBase { } double _renderSize = 0.0; - _ScrollBarBase _scrollBar; - _LineSizeHostBase _scrollLinesHost; + _ScrollBarBase? _scrollBar; + late _LineSizeHostBase? _scrollLinesHost; bool _layoutDirty = false; int _lineResizeIndex = -1; double _lineResizeSize = 0; @@ -66,7 +67,7 @@ abstract class _ScrollAxisBase { } /// Occurs when a scroll axis changed. - _ChangedCallback onChangedCallback; + _ChangedCallback? onChangedCallback; /// Gets the default size of lines. /// @@ -98,7 +99,7 @@ abstract class _ScrollAxisBase { return 0; } - return scrollLinesHost.getFooterLineCount(); + return scrollLinesHost?.getFooterLineCount() ?? 0; } /// Gets the index of the first footer line. @@ -121,7 +122,7 @@ abstract class _ScrollAxisBase { return 0; } - return scrollLinesHost.getHeaderLineCount(); + return scrollLinesHost?.getHeaderLineCount() ?? 0; } /// Gets a value indicating whether footer lines are visible. @@ -138,12 +139,12 @@ abstract class _ScrollAxisBase { /// @value /// `true` if this instance supports pixel scrolling, otherwise `false`. bool get isPixelScroll => _isPixelScroll; - bool _isPixelScroll; + bool _isPixelScroll = false; /// Gets the last visible line. /// /// Returns the last visible line. - _VisibleLineInfo get lastBodyVisibleLine { + _VisibleLineInfo? get lastBodyVisibleLine { final visibleLines = getVisibleLines(); if (visibleLines.isEmpty || visibleLines.lastBodyVisibleIndex > visibleLines.length) { @@ -170,7 +171,7 @@ abstract class _ScrollAxisBase { /// /// Returns the line count. int get lineCount => _lineCount; - int _lineCount; + int _lineCount = 0; /// Sets the line count. set lineCount(int value) { @@ -184,11 +185,11 @@ abstract class _ScrollAxisBase { /// Gets the name. /// /// Returns the name. - String get name => _name; - String _name; + String? get name => _name; + String? _name; /// Sets the name. - set name(String value) { + set name(String? value) { if (value == _name) { return; } @@ -213,8 +214,8 @@ abstract class _ScrollAxisBase { /// Gets the scroll bar state. /// /// Returns the scroll bar state. - _ScrollBarBase get scrollBar => _scrollBar; - set scrollBar(_ScrollBarBase value) { + _ScrollBarBase? get scrollBar => _scrollBar; + set scrollBar(_ScrollBarBase? value) { if (value == _scrollBar) { return; } @@ -225,7 +226,7 @@ abstract class _ScrollAxisBase { /// Gets the scroll lines host. /// /// Returns the scroll lines host. - _LineSizeHostBase get scrollLinesHost => _scrollLinesHost; + _LineSizeHostBase? get scrollLinesHost => _scrollLinesHost; /// Scroll = First Visible Body Line /// @@ -253,7 +254,7 @@ abstract class _ScrollAxisBase { /// /// Returns the index of the first line. int get startLineIndex => _startLineIndex; - int _startLineIndex; + int _startLineIndex = -1; /// Sets the index of the first line in a parent axis. set startLineIndex(int value) { @@ -317,16 +318,20 @@ abstract class _ScrollAxisBase { void defaultLineSizeChangedCallback( _DefaultLineSizeChangedArgs defaultLineSizeChangedArgs) { - defaultLineSize = scrollLinesHost.getDefaultLineSize(); - markDirty(); - updateScrollBar(true); - raiseChanged(_ScrollChangedAction.defaultLineSizeChanged); + if (scrollLinesHost != null) { + defaultLineSize = scrollLinesHost!.getDefaultLineSize(); + markDirty(); + updateScrollBar(true); + raiseChanged(_ScrollChangedAction.defaultLineSizeChanged); + } } void footerLineChangedCallback() { - setFooterLineCount(scrollLinesHost.getFooterLineCount()); - markDirty(); - raiseChanged(_ScrollChangedAction.footerLineCountChanged); + if (scrollLinesHost != null) { + setFooterLineCount(scrollLinesHost!.getFooterLineCount()); + markDirty(); + raiseChanged(_ScrollChangedAction.footerLineCountChanged); + } } /// Freezes the visible lines. @@ -345,8 +350,9 @@ abstract class _ScrollAxisBase { /// then get temporary value previously set with `SetLineResize`. /// If size is negative then `DefaultLineSize` is returned. List getScrollLinesHostSize(int index, int repeatSizeCount) { - double size = scrollLinesHost.getSize(index, repeatSizeCount)[0]; - repeatSizeCount = scrollLinesHost.getSize(index, repeatSizeCount)[1]; + double size = scrollLinesHost!.getSize(index, repeatSizeCount)[0]; + repeatSizeCount = scrollLinesHost!.getSize(index, repeatSizeCount)[1]; + if (size < 0) { size = defaultLineSize; } @@ -361,7 +367,7 @@ abstract class _ScrollAxisBase { /// * repeatSizeCount The number of subsequent values with same size. /// Returns the size from `ScrollLinesHost` or if the line is being resized /// then get temporary value previously set with `SetLineResize` - List getLineSizeWithTwoArgs(int index, int repeatSizeCount) { + List getLineSizeWithTwoArgs(int index, int? repeatSizeCount) { repeatSizeCount = 1; if (index == _lineResizeIndex) { return [_lineResizeSize, repeatSizeCount]; @@ -383,7 +389,7 @@ abstract class _ScrollAxisBase { /// * index The index of the line. /// Returns the size of the line. double getLineSize(int index) { - int repeatSizeCount; + int? repeatSizeCount; return getLineSizeWithTwoArgs(index, repeatSizeCount)[0]; } @@ -459,7 +465,7 @@ abstract class _ScrollAxisBase { _layoutDirty = false; updateScrollBar(true); } - if (_visibleLines.isEmpty || _lastScrollValue != scrollBar.value) { + if (_visibleLines.isEmpty || _lastScrollValue != scrollBar!.value) { _visibleLines.clear(); int visibleIndex = 0; @@ -492,7 +498,7 @@ abstract class _ScrollAxisBase { _visibleLines.firstBodyVisibleIndex = _visibleLines.length; - _VisibleLineInfo lastScrollableLine; + _VisibleLineInfo? lastScrollableLine; // Body point = headerExtent; final int firstBodyLineIndex = @@ -551,7 +557,7 @@ abstract class _ScrollAxisBase { lastScrollableLine = null; } - _lastScrollValue = scrollBar.value; + _lastScrollValue = scrollBar!.value; if (_visibleLines.isNotEmpty) { _visibleLines[_visibleLines.length - 1].isLastLine = true; @@ -590,7 +596,7 @@ abstract class _ScrollAxisBase { updateScrollBar(true); } - if (_visibleLines.isEmpty || _lastScrollValue != scrollBar.value) { + if (_visibleLines.isEmpty || _lastScrollValue != scrollBar!.value) { _visibleLines.clear(); int visibleIndex = 0; int scrollLineIndex = 0; @@ -621,7 +627,7 @@ abstract class _ScrollAxisBase { } _visibleLines.firstBodyVisibleIndex = _visibleLines.length; - _VisibleLineInfo lastScrollableLine; + _VisibleLineInfo? lastScrollableLine; // Body point = headerExtent; @@ -669,14 +675,14 @@ abstract class _ScrollAxisBase { _visibleLines.firstFooterVisibleIndex = _visibleLines.length; if (footerLineCount > 0) { - if (renderSize < scrollBar.maximum + footerExtent) { + if (renderSize < scrollBar!.maximum + footerExtent) { point = min(renderSize + clip.start - headerExtent, clip.start + footerExtent); for (index = firstFooterLine; index != -1 && strictlyLessThan( point - 1, - renderSize < scrollBar.maximum + renderSize < scrollBar!.maximum ? clip.start + footerExtent : renderSize - headerExtent) && index < lineCount; @@ -697,7 +703,7 @@ abstract class _ScrollAxisBase { index != -1 && strictlyLessThan( point - 1, - renderSize < scrollBar.maximum + renderSize < scrollBar!.maximum ? clip.start + footerExtent : renderSize - headerExtent) && index < lineCount; @@ -722,7 +728,7 @@ abstract class _ScrollAxisBase { lastScrollableLine = null; } - _lastScrollValue = scrollBar.value; + _lastScrollValue = scrollBar!.value; if (_visibleLines.isNotEmpty) { _visibleLines[_visibleLines.length - 1].isLastLine = true; } @@ -757,7 +763,7 @@ abstract class _ScrollAxisBase { /// * isRightToLeft The boolean value used to calculate visible columns /// in right to left mode. /// Returns the visible line for a point in the display. - _VisibleLineInfo getVisibleLineAtPoint(double point, + _VisibleLineInfo? getVisibleLineAtPoint(double point, [bool allowOutSideLines = false, bool isRightToLeft = false]) { if (!isRightToLeft) { if (allowOutSideLines) { @@ -773,10 +779,9 @@ abstract class _ScrollAxisBase { point = max(point, 0); } - final _VisibleLinesCollection collection = getVisibleLines(true) - ..reversed; - final lineInfo = collection.getVisibleLineAtPoint(point); - collection.reversed; + final _VisibleLinesCollection collection = getVisibleLines(true); + final reversedCollection = collection.reversed; + final lineInfo = reversedCollection.getVisibleLineAtPoint(point); if (lineInfo != null && (allowOutSideLines || point <= lineInfo.corner)) { return lineInfo; } @@ -792,7 +797,7 @@ abstract class _ScrollAxisBase { /// The visible line that displays the line with the given /// absolute line index. - _VisibleLineInfo getVisibleLineAtLineIndex(int lineIndex) => + _VisibleLineInfo? getVisibleLineAtLineIndex(int lineIndex) => getVisibleLines().getVisibleLineAtLineIndex(lineIndex); /// Gets the visible line that displays the line with the given absolute @@ -806,9 +811,9 @@ abstract class _ScrollAxisBase { /// line and initializes its LineIndex and LineSize. /// Returns the visible line that displays the line with the given /// absolute line index. - _VisibleLineInfo getVisibleLineAtLineIndexWithTwoArgs( + _VisibleLineInfo? getVisibleLineAtLineIndexWithTwoArgs( int lineIndex, bool allowCreateEmptyLineIfNotVisible) { - _VisibleLineInfo line = getVisibleLineAtLineIndex(lineIndex); + _VisibleLineInfo? line = getVisibleLineAtLineIndex(lineIndex); if (line == null && allowCreateEmptyLineIfNotVisible) { final double size = getLineSize(lineIndex); line = _VisibleLineInfo(_MathHelper.maxvalue, lineIndex, size, @@ -925,12 +930,12 @@ abstract class _ScrollAxisBase { /// * side The hit test corner. /// * isRightToLeft The boolean value indicates the right to left mode. /// Returns the visible line. - _VisibleLineInfo getLineNearCornerWithFourArgs( + _VisibleLineInfo? getLineNearCornerWithFourArgs( double point, double hitTestPrecision, _CornerSide side, [bool isRightToLeft = false]) { if (!isRightToLeft) { final _VisibleLinesCollection lines = getVisibleLines(); - _VisibleLineInfo visibleLine = lines.getVisibleLineAtPoint(point); + _VisibleLineInfo? visibleLine = lines.getVisibleLineAtPoint(point); if (visibleLine != null) { double d; @@ -957,14 +962,14 @@ abstract class _ScrollAxisBase { } // last line - check corner instead of origin. - d = visibleLine.clippedCorner - point; + d = visibleLine!.clippedCorner - point; if (d.abs() <= hitTestPrecision) { return lines[visibleLine.visibleIndex]; } } } else { final _VisibleLinesCollection lines = getVisibleLines(true); - _VisibleLineInfo visibleLine = + _VisibleLineInfo? visibleLine = getVisibleLineAtPoint(point, false, isRightToLeft); if (visibleLine != null) { @@ -998,7 +1003,7 @@ abstract class _ScrollAxisBase { } } // last line - check corner instead of origin. - d = visibleLine.clippedCorner - point; + d = visibleLine!.clippedCorner - point; if (d.abs() <= hitTestPrecision) { return lines[visibleLine.visibleIndex]; } @@ -1029,8 +1034,8 @@ abstract class _ScrollAxisBase { bool allowAdjust, bool firstVisible, bool lastVisible, - _VisibleLineInfo firstLine, - _VisibleLineInfo lastLine) { + _VisibleLineInfo? firstLine, + _VisibleLineInfo? lastLine) { final _VisibleLinesCollection visibleLines = getVisibleLines(); if (firstIndex < 0) { @@ -1148,8 +1153,9 @@ abstract class _ScrollAxisBase { /// corner of last line). _DoubleSpan getVisibleLinesClipPoints(int firstIndex, int lastIndex) { - bool firstVisible, lastVisible; - _VisibleLineInfo firstLine, lastLine; + final bool firstVisible = false; + final bool lastVisible = false; + _VisibleLineInfo? firstLine, lastLine; getLinesAndVisibility(firstIndex, lastIndex, true, firstVisible, lastVisible, firstLine, lastLine); @@ -1169,7 +1175,8 @@ abstract class _ScrollAxisBase { _DoubleSpan getClipPoints(_ScrollAxisRegion region, {bool isRightToLeft = false}) { final _VisibleLinesCollection lines = getVisibleLines(); - int start, end; + int start = -1; + int end = -1; final visibleLineSection = getVisibleSection(region, start, end); start = visibleLineSection[0]; end = visibleLineSection[1]; @@ -1227,7 +1234,7 @@ abstract class _ScrollAxisBase { } void headerLineChangedCallback() { - setFooterLineCount(scrollLinesHost.getFooterLineCount()); + setFooterLineCount(scrollLinesHost!.getFooterLineCount()); markDirty(); raiseChanged(_ScrollChangedAction.footerLineCountChanged); } @@ -1238,8 +1245,8 @@ abstract class _ScrollAxisBase { n <= hiddenRangeChangedArgs.to; n++) { int repeatSizeCount = 0; - final bool hide = scrollLinesHost.getHidden(n, repeatSizeCount)[0]; - repeatSizeCount = scrollLinesHost.getHidden(n, repeatSizeCount)[1]; + final bool hide = scrollLinesHost!.getHidden(n, repeatSizeCount)[0]; + repeatSizeCount = scrollLinesHost!.getHidden(n, repeatSizeCount)[1]; final int rangeTo = getRangeToHelper(n, hiddenRangeChangedArgs.to, repeatSizeCount); setLineHiddenState(n, rangeTo, hide); @@ -1273,7 +1280,7 @@ abstract class _ScrollAxisBase { } void lineCountChangedCallback() { - lineCount = scrollLinesHost.getLineCount(); + lineCount = scrollLinesHost!.getLineCount(); markDirty(); updateScrollBar(true); raiseChanged(_ScrollChangedAction.lineCountChanged); @@ -1367,7 +1374,7 @@ abstract class _ScrollAxisBase { void raiseChanged(_ScrollChangedAction action) { if (onChangedCallback != null) { final scrollChangedArgs = _ScrollChangedArgs.fromArgs(action); - onChangedCallback(scrollChangedArgs); + onChangedCallback!(scrollChangedArgs); } } @@ -1470,7 +1477,7 @@ abstract class _ScrollAxisBase { if (scrollLinesHost == null) { return; } - scrollLinesHost + scrollLinesHost! ..onDefaultLineSizeChanged = defaultLineSizeChangedCallback ..onLineHiddenChanged = hiddenRangeChangedCallback ..onLineSizeChanged = rangeChangedCallback @@ -1509,7 +1516,7 @@ abstract class _ScrollAxisBase { point = max(point, 0); } - final _VisibleLineInfo line = + final _VisibleLineInfo? line = getVisibleLines().getVisibleLineAtPoint(point); if (line != null && (allowOutsideLines || point <= line.corner)) { return line.lineIndex; @@ -1531,7 +1538,7 @@ abstract class _ScrollAxisBase { return; } - scrollLinesHost + scrollLinesHost! ..onDefaultLineSizeChanged = defaultLineSizeChangedCallback ..onLineHiddenChanged = hiddenRangeChangedCallback ..onLineSizeChanged = rangeChangedCallback diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_info.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_info.dart index 4ac352310..b567e5764 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_info.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_info.dart @@ -24,19 +24,19 @@ class _ScrollInfo extends _ScrollBarBase { _enabled = true; } - double _proposedLargeChange; - List list; + late double _proposedLargeChange; + late List list; /// Occurs when a property value changes. - _PropertyChangedCallback onPropertyChangedEvent; + _PropertyChangedCallback? onPropertyChangedEvent; /// Occurs when the current position of the scroll box on the scroll bar /// has changed. - _ValueChangedCallback onValueChanged; + _ValueChangedCallback? onValueChanged; /// Occurs when the current position of the scroll box on the scroll bar /// is being changed. - _ValueChangingCallback onValueChanging; + _ValueChangingCallback? onValueChanging; /// Gets a value to be added to or subtracted from the value of the property /// when the scroll box is moved a large distance. @@ -80,7 +80,7 @@ class _ScrollInfo extends _ScrollBarBase { if (this.value != value) { final e = _ValueChangingArgs(value, this.value); if (onValueChanging != null) { - onValueChanging(e); + onValueChanging!(e); } if (!e.cancel) { @@ -131,11 +131,9 @@ class _ScrollInfo extends _ScrollBarBase { /// Returns `True` if the specified `ScrollInfo` is equal /// to the current `ScrollInfo`, otherwise `false`. bool equals(Object obj) { - final _ScrollInfo sb = obj; - if (sb == null && this == null) { + final _ScrollInfo? sb = obj as _ScrollInfo; + if (sb == null) { return true; - } else if (this == null || sb == null) { - return false; } return sb.value == _value && @@ -157,7 +155,7 @@ class _ScrollInfo extends _ScrollBarBase { void onPropertyChanged(String propertyName) { if (onPropertyChangedEvent != null) { final propertyChangedArgs = _PropertyChangedArgs(propertyName); - onPropertyChangedEvent(propertyChangedArgs); + onPropertyChangedEvent!(propertyChangedArgs); } } @@ -220,5 +218,5 @@ class _PropertyChangedArgs { /// /// Returns the property name. String get propertyName => _propertyName; - String _propertyName; + String _propertyName = ''; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scrollbar_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scrollbar_base.dart index 6145891c6..bf14bfa4c 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scrollbar_base.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scrollbar_base.dart @@ -7,7 +7,7 @@ class _ScrollBarBase extends ChangeNotifier { /// Returns a number that represents the current position of the /// scroll box on the scroll bar control. bool get enabled => _enabled; - bool _enabled; + bool _enabled = false; set enabled(bool value) { if (value == _enabled) { return; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/sorted_range_value_list.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/sorted_range_value_list.dart index 7cd3d9b99..79a4b3f17 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/sorted_range_value_list.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/sorted_range_value_list.dart @@ -19,8 +19,8 @@ class _RangeValuePair extends Comparable { /// * value - _required_ - The value for the range. _RangeValuePair.fromRangeValuePair(this.start, this.count, this.value); - int start; - int count; + late int start; + late int count; dynamic value; /// Gets the end of the range. @@ -40,16 +40,18 @@ class _RangeValuePair extends Comparable { /// /// * obj - _required_ - An object to compare with this instance. @override - int compareTo(Object obj) { - final _RangeValuePair x = this; - final _RangeValuePair y = obj; - - if (((x.start >= y.start) && (x.start < y.start + y.count)) || - ((y.start >= x.start) && (y.start < x.start + x.count))) { - return 0; + int compareTo(Object? obj) { + final _RangeValuePair? x = this; + final _RangeValuePair? y = obj as _RangeValuePair; + + if (x != null && y != null) { + if (((x.start >= y.start) && (x.start < y.start + y.count)) || + ((y.start >= x.start) && (y.start < x.start + x.count))) { + return 0; + } } - return start.compareTo(y.start); + return start.compareTo(y!.start); } /// Gets the Debug / text information about the node. @@ -83,12 +85,12 @@ class _SortedRangeValueList } List<_RangeValuePair> rangeValues = []; - T _defaultValue; + T? _defaultValue; /// Gets the count which is the same as the end position of the last range. /// /// Returns the count which is the same as the end position of the last range. - int get count { + num get count { if (rangeValues.isEmpty) { return 0; } @@ -99,13 +101,13 @@ class _SortedRangeValueList /// Gets the default value used for filling gaps. /// /// Returns the default value used for filling gaps. - dynamic get defaultValue { + dynamic? get defaultValue { if (_defaultValue == null) { if (_defaultValue.runtimeType is bool) { return false; } if (_defaultValue.runtimeType is double) { - return 0.0; + return 0.0 as T; } } else { return _defaultValue; @@ -113,13 +115,14 @@ class _SortedRangeValueList } /// Sets the default value used for filling gaps. - set defaultValue(T value) { + set defaultValue(dynamic value) { _defaultValue = value; } void adjustStart(num n, num delta) { + int value = n.toInt(); while (n < rangeValues.length) { - rangeValues[n++].start += delta; + rangeValues[value++].start += delta.toInt(); } } @@ -131,7 +134,7 @@ class _SortedRangeValueList void ensureCount(num index) { if (index - count > 0) { rangeValues.add(_RangeValuePair.fromRangeValuePair( - count, index - count, defaultValue)); + count.toInt(), index.toInt() - count.toInt(), defaultValue)); } } @@ -148,8 +151,8 @@ class _SortedRangeValueList count = _MathHelper.maxvalue; return [defaultValue, count]; } - - final _RangeValuePair rv = getRangeValue(index); + final int value = index.toInt(); + final _RangeValuePair rv = getRangeValue(value); count = rv.end - index + 1; return [rv.value, count]; } @@ -182,8 +185,8 @@ class _SortedRangeValueList /// * moveRanges - _required_ - Allocate this object before a preceding /// Remove call when moving ranges. /// Otherwise specify null. - void insertWithFourArgs(num insertAt, num count, Object value, - _SortedRangeValueList moveRanges) { + void insertWithFourArgs(num insertAt, num count, Object? value, + _SortedRangeValueList? moveRanges) { if (insertAt >= this.count) { if (value == defaultValue && (moveRanges == null || moveRanges.count == 0)) { @@ -191,23 +194,23 @@ class _SortedRangeValueList } ensureCount(insertAt); - rangeValues - .add(_RangeValuePair.fromRangeValuePair(insertAt, count, value)); + rangeValues.add(_RangeValuePair.fromRangeValuePair( + insertAt.toInt(), count.toInt(), value)); if (rangeValues.length >= 2) { merge(rangeValues.length - 2); } } else { var n = _MathHelper.binarySearch<_RangeValuePair>( - rangeValues, _RangeValuePair(insertAt)); + rangeValues, _RangeValuePair(insertAt.toInt())); final _RangeValuePair rv = rangeValues[n]; if (value == (rv.value)) { - rv.count += count; + rv.count += count.toInt(); adjustStart(n + 1, count); } else { n = splitWithTwoArgs(insertAt, n); split(insertAt + 1); - final rv2 = - _RangeValuePair.fromRangeValuePair(insertAt, count, value); + final rv2 = _RangeValuePair.fromRangeValuePair( + insertAt.toInt(), count.toInt(), value); rangeValues.insert(n, rv2); adjustStart(n + 1, count); merge(n); @@ -219,7 +222,8 @@ class _SortedRangeValueList if (moveRanges != null) { for (final _RangeValuePair rv in moveRanges) { - setRange(rv.start + insertAt, rv.count, rv.value); + setRange( + rv.start.toInt() + insertAt.toInt(), rv.count.toInt(), rv.value); } } } @@ -236,7 +240,7 @@ class _SortedRangeValueList /// remove call when moving ranges. /// Otherwise specify null. void insertWithThreeArgs( - num insertAt, num count, _SortedRangeValueList moveRanges) { + num insertAt, num count, _SortedRangeValueList? moveRanges) { insertWithFourArgs(insertAt, count, defaultValue, moveRanges); } @@ -249,7 +253,7 @@ class _SortedRangeValueList /// * insertAt - _required_ - The insertion point. /// * count - _required_ - The count. /// * value - _required_ - The value. - void insertWithThreeArgsGeneric(num insertAt, num count, Object value) { + void insertWithThreeArgsGeneric(num insertAt, num count, Object? value) { insertWithFourArgs(insertAt, count, value, null); } @@ -277,20 +281,20 @@ class _SortedRangeValueList /// when moving ranges /// and pass it to a subsequent insert call. Otherwise specify null. void removeWithThreeArgs( - num removeAt, num count, _SortedRangeValueList moveRanges) { + num removeAt, num count, _SortedRangeValueList? moveRanges) { if (removeAt >= this.count) { return; } - final n = removeHelper(removeAt, count, moveRanges); + final n = removeHelper(removeAt.toInt(), count.toInt(), moveRanges); adjustStart(n, -count); if (n > 0) { - merge(n - 1); + merge(n.toInt() - 1); } } num removeHelper( - int removeAt, int count, _SortedRangeValueList moveRanges) { + int removeAt, int count, _SortedRangeValueList? moveRanges) { if (removeAt >= this.count) { return rangeValues.length; } @@ -300,8 +304,8 @@ class _SortedRangeValueList var total = 0; var deleteCount = 0; while (total < count && n + deleteCount < rangeValues.length) { - final _RangeValuePair rv = rangeValues[n + deleteCount]; - total += rv.count; + final _RangeValuePair rv = rangeValues[n.toInt() + deleteCount]; + total += rv.count.toInt(); deleteCount++; if (moveRanges != null && !(rv.value == defaultValue)) { moveRanges.rangeValues.add(_RangeValuePair.fromRangeValuePair( @@ -309,7 +313,7 @@ class _SortedRangeValueList } } - rangeValues.removeRange(n, n + deleteCount); + rangeValues.removeRange(n.toInt(), n.toInt() + deleteCount); return n; } @@ -334,10 +338,10 @@ class _SortedRangeValueList ensureCount(index); final n = removeHelper(index, count, null); final rv = _RangeValuePair.fromRangeValuePair(index, count, value); - rangeValues.insert(n, rv); - merge(n); + rangeValues.insert(n.toInt(), rv); + merge(n.toInt()); if (n > 0) { - merge(n - 1); + merge(n.toInt() - 1); } } @@ -346,7 +350,7 @@ class _SortedRangeValueList return rangeValues.length; } final n = _MathHelper.binarySearch<_RangeValuePair>( - rangeValues, _RangeValuePair(index)); + rangeValues, _RangeValuePair(index.toInt())); return splitWithTwoArgs(index, n); } @@ -356,11 +360,12 @@ class _SortedRangeValueList return n; } - final int count1 = index - rangeValues[n].start; - final int count2 = rangeValues[n].count - count1; + final int count1 = index.toInt() - rangeValues[n].start.toInt(); + final int count2 = rangeValues[n].count.toInt() - count1.toInt(); rv.count = count1; - final rv2 = _RangeValuePair.fromRangeValuePair(index, count2, rv.value); + final rv2 = + _RangeValuePair.fromRangeValuePair(index.toInt(), count2, rv.value); rangeValues.insert(n + 1, rv2); return n + 1; } @@ -395,7 +400,7 @@ class _SortedRangeValueList /// Returns the value of the range that contains the specified index. /// /// Returns the value range for the specified index. - T operator [](num index) => getRangeValue(index).value; + T operator [](num index) => getRangeValue(index.toInt()).value; void operator []=(num index, Object value) { bool b = false; @@ -409,17 +414,17 @@ class _SortedRangeValueList if (b || (value != this[index])) { ensureCount(index); - final int n = split(index); + final num n = split(index); split(index + 1); if (n == rangeValues.length) { - if (n > 0 && (value == rangeValues[n - 1].value)) { - rangeValues[n - 1].count++; + if (n > 0 && (value == rangeValues[n.toInt() - 1].value)) { + rangeValues[n.toInt() - 1].count++; } else { - rangeValues - .add(_RangeValuePair.fromRangeValuePair(index, 1, value)); + rangeValues.add( + _RangeValuePair.fromRangeValuePair(index.toInt(), 1, value)); } } else { - rangeValues[n].value = value; + rangeValues[n.toInt()].value = value; } } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/visible_line_info.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/visible_line_info.dart index 9ec39f4e1..f49126c42 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/visible_line_info.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/visible_line_info.dart @@ -188,10 +188,10 @@ class _VisibleLineInfo extends Comparable<_VisibleLineInfo> { /// /// A strong-typed collection of `VisibleLineInfo` items. class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { - List<_VisibleLineInfo> visibleLines = []; + List<_VisibleLineInfo?> visibleLines = List.empty(growable: true); _VisibleLineInfoLineIndexComparer lineIndexComparer = _VisibleLineInfoLineIndexComparer(); - Map lineIndexes = {}; + Map? lineIndexes = {}; /// Gets the first visible index of the body. int firstBodyVisibleIndex = 0; @@ -207,7 +207,7 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { /// Gets the visible line indexes. /// /// Returns the visible line indexes. - Map get visibleLineIndexes => lineIndexes; + Map? get visibleLineIndexes => lineIndexes; @override int get length => visibleLines.length; @@ -244,16 +244,16 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { /// * lineIndex - _required_ - Index of the line. /// /// Returns the visible line at line index. - _VisibleLineInfo getVisibleLineAtLineIndex(int lineIndex) { - if (lineIndexes.isEmpty) { + _VisibleLineInfo? getVisibleLineAtLineIndex(int lineIndex) { + if (lineIndexes != null && lineIndexes!.isEmpty) { for (int i = 0; i < length; i++) { - lineIndexes.putIfAbsent(this[i].lineIndex, () => this[i]); + lineIndexes!.putIfAbsent(this[i].lineIndex, () => this[i]); } //this.shadowedLineIndexes = this.lineIndexes; } - _VisibleLineInfo lineInfo; - if (lineIndexes.containsKey(lineIndex)) { - return lineInfo = lineIndexes[lineIndex]; + _VisibleLineInfo? lineInfo; + if (lineIndexes != null && lineIndexes!.containsKey(lineIndex)) { + return lineInfo = lineIndexes![lineIndex]; } return lineInfo; } @@ -265,9 +265,11 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { /// * lineIndex - _required_ - Index of the line. /// /// Returns the first visible line for a line index that is not hidden. - _VisibleLineInfo getVisibleLineNearLineIndex(int lineIndex) { + _VisibleLineInfo? getVisibleLineNearLineIndex(int lineIndex) { + final List<_VisibleLineInfo> _visibleLine = + visibleLines as List<_VisibleLineInfo>; int index = _MathHelper.binarySearch<_VisibleLineInfo>( - visibleLines, _VisibleLineInfo.fromLineIndex(lineIndex)); + _visibleLine, _VisibleLineInfo.fromLineIndex(lineIndex)); index = (index < 0) ? (~index) - 1 : index; if (index >= 0) { return this[index]; @@ -281,9 +283,11 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { /// to be obtained. /// /// Returns the visible line at point. - _VisibleLineInfo getVisibleLineAtPoint(double point) { + _VisibleLineInfo? getVisibleLineAtPoint(double point) { + final List<_VisibleLineInfo> _visibleLines = + visibleLines as List<_VisibleLineInfo>; int index = _MathHelper.binarySearch<_VisibleLineInfo>( - visibleLines, _VisibleLineInfo.fromClippedOrigin(point)); + _visibleLines, _VisibleLineInfo.fromClippedOrigin(point)); index = (index < 0) ? (~index) - 1 : index; if (index >= 0) { return this[index]; @@ -337,20 +341,29 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { /// Removes all elements from the collection. @override void clear() { - lineIndexes.clear(); + lineIndexes?.clear(); lineIndexes = null; visibleLines.clear(); //this.shadowedLineIndexes = null; lineIndexes = {}; } + @override + _VisibleLinesCollection get reversed { + final _VisibleLinesCollection reverseCollection = _VisibleLinesCollection(); + for (var i = this.length - 1; i >= 0; i--) { + reverseCollection.add(this[i]); + } + return reverseCollection; + } + @override void operator []=(int index, _VisibleLineInfo value) { visibleLines[index] = value; } @override - _VisibleLineInfo operator [](int index) => visibleLines[index]; + _VisibleLineInfo operator [](int index) => visibleLines[index]!; } /// Initializes a new instance of the `VisibleLineInfoLineIndexComparer` class. diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility/double_span.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility/double_span.dart index 9eb2d3e87..204c63859 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility/double_span.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility/double_span.dart @@ -16,8 +16,8 @@ class _DoubleSpan { end = -1; } - double start; - double end; + late double start; + late double end; /// Gets a value indicating whether this instance is empty. /// diff --git a/packages/syncfusion_flutter_datagrid/lib/src/sfdatagrid.dart b/packages/syncfusion_flutter_datagrid/lib/src/sfdatagrid.dart index 4276210c1..8f2492246 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/sfdatagrid.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/sfdatagrid.dart @@ -1,33 +1,18 @@ part of datagrid; +/// Signature for the `_DataGridSettings` callback. typedef _DataGridStateDetails = _DataGridSettings Function(); -/// Signature for [SfDataGrid.onQueryCellStyle] callback. -typedef QueryCellStyleCallback = DataGridCellStyle Function( - QueryCellStyleArgs queryCellStyleArgs); - -/// Signature for [SfDataGrid.onQueryRowStyle] callback. -typedef QueryRowStyleCallback = DataGridCellStyle Function( - QueryRowStyleArgs queryRowStyleArgs); - -/// Signature for [SfDataGrid.cellBuilder] callback -typedef CellBuilderCallback = Widget Function( - BuildContext contex, GridColumn column, int rowIndex); - -/// Signature for [SfDataGrid.headerCellBuilder] callback. -typedef HeaderCellBuilderCallback = Widget Function( - BuildContext contex, GridColumn column); - /// Signature for [SfDataGrid.onQueryRowHeight] callback typedef QueryRowHeightCallback = double Function(RowHeightDetails details); /// Signature for [SfDataGrid.onSelectionChanging] callback. typedef SelectionChangingCallback = bool Function( - List addedRows, List removedRows); + List addedRows, List removedRows); /// Signature for [SfDataGrid.onSelectionChanged] callback. typedef SelectionChangedCallback = void Function( - List addedRows, List removedRows); + List addedRows, List removedRows); /// Signature for [SfDataGrid.onCellRenderersCreated] callback. typedef CellRenderersCreatedCallback = void Function( @@ -57,9 +42,88 @@ typedef DataGridCellLongPressCallback = void Function( typedef LoadMoreRows = Future Function(); /// Signature for the [SfDataGrid.loadMoreViewBuilder] function. -typedef LoadMoreViewBuilder = Widget Function( +typedef LoadMoreViewBuilder = Widget? Function( BuildContext context, LoadMoreRows loadMoreRows); +/// Signature for the [SfDataGrid.onSwipeStart] callback. +typedef DataGridSwipeStartCallback = bool Function( + DataGridSwipeStartDetails swipeStartDetails); + +/// Signature for the [SfDataGrid.onSwipeUpdate] callback. +typedef DataGridSwipeUpdateCallback = bool Function( + DataGridSwipeUpdateDetails swipeUpdateDetails); + +/// Signature for the [SfDataGrid.onSwipeEnd] callback. +typedef DataGridSwipeEndCallback = void Function( + DataGridSwipeEndDetails swipeEndDetails); + +/// Holds the arguments for the [SfDataGrid.startSwipeActionsBuilder] callback. +typedef DataGridSwipeActionsBuilder = Widget? Function( + BuildContext context, DataGridRow dataGridRow); + +/// Row configuration and cell data for a [SfDataGrid]. +/// +/// Return this list of [DataGridRow] objects to [DataGridSource.rows] property. +/// +/// The data for each row can be passed as the cells argument to the +/// constructor of each [DataGridRow] object. +class DataGridRow { + /// ToDo + const DataGridRow({required List cells}) : _cells = cells; + + /// The data for this row. + /// + /// There must be exactly as many cells as there are columns in the + /// [SfDataGrid]. + final List _cells; + + /// Returns the collection of [DataGridCell] which is created for + /// [DataGridRow]. + List getCells() { + return _cells; + } +} + +/// The data for a cell of a [SfDataGrid]. +/// +/// The list of [DataGridCell] objects should be passed as the cells argument +/// to the constructor of each [DataGridRow] object. +class DataGridCell { + /// ToDo + const DataGridCell({required this.columnName, required this.value}); + + /// The name of a column + final String columnName; + + /// The value of a cell. + /// + /// Provide value of a cell to perform the sorting for whole data available + /// in datagrid. + final T? value; +} + +/// Row configuration and widget of cell for a [SfDataGrid]. +/// +/// The widget for each cell can be provided in the [DataGridRowAdapter.cells] +/// property. +class DataGridRowAdapter { + /// ToDo + const DataGridRowAdapter( + {required this.cells, this.key, this.color = Colors.transparent}); + + /// ToDo + final Key? key; + + /// The color for the row. + final Color color; + + /// The widget of each cell for this row. + /// + /// There must be exactly as many cells as there are columns in the + /// [SfDataGrid]. + final List cells; +} + /// A material design datagrid. /// /// DataGrid lets you display and manipulate data in a tabular view. It is built @@ -69,8 +133,7 @@ typedef LoadMoreViewBuilder = Widget Function( /// DataGrid supports different types of column types to populate the columns /// for different types of data such as int, double, DateTime, String. /// -/// You can use [GridWidgetColumn] to load any widget in a column. [source] -/// property enables you to populate the data for the [SfDataGrid]. +/// [source] property enables you to populate the data for the [SfDataGrid]. /// /// This sample shows how to populate the data for the [SfDataGrid] and display /// with four columns: id, name, designation and salary. @@ -90,16 +153,12 @@ typedef LoadMoreViewBuilder = Widget Function( /// Widget build(BuildContext context) { /// return SfDataGrid( /// source: _employeeDataSource, -/// columns: [ -/// GridNumericColumn(mappingName: 'id') -/// ..headerText = 'ID', -/// GridTextColumn(mappingName: 'name') -/// ..headerText = 'Name', -/// GridTextColumn(mappingName: 'designation') -/// ..headerText = 'Designation', -/// GridNumericColumn(mappingName: 'salary') -/// ..headerText = 'Salary', -/// ], +/// columnWidthMode: ColumnWidthMode.fill, +/// columns: [ +/// GridTextColumn(columnName: 'id', label: Text('ID')), +/// GridTextColumn(columnName: 'name', label: Text('Name')), +/// GridTextColumn(columnName: 'designation', label: Text('Designation')), +/// GridTextColumn(columnName: 'salary', label: Text('Salary')), /// ); /// } /// @@ -125,30 +184,26 @@ typedef LoadMoreViewBuilder = Widget Function( /// final int salary; /// } /// -/// class EmployeeDataSource extends DataGridSource { +/// class EmployeeDataSource extends DataGridSource { /// @override -/// List get dataSource => _employees; +/// List get rows => _employees +/// .map((dataRow) => DataGridRow(cells: [ +/// DataGridCell(columnName: 'id', value: dataRow.id), +/// DataGridCell(columnName: 'name', value: dataRow.name), +/// DataGridCell( +/// columnName: 'designation', value: dataRow.designation), +/// DataGridCell(columnName: 'salary', value: dataRow.salary), +/// ])) +/// .toList(); /// /// @override -/// getValue(Employee employee, String columnName){ -/// switch (columnName) { -/// case 'id': -/// return employee.id; -/// break; -/// case 'name': -/// return employee.name; -/// break; -/// case 'salary': -/// return employee.salary; -/// break; -/// case 'designation': -/// return employee.designation; -/// break; -/// default: -/// return ‘ ’; -/// break; -/// } +/// DataGridRowAdapter buildRow(DataGridRow row) { +/// return DataGridRowAdapter( +/// cells: row.getCells().map((dataCell) { +/// return Text(dataCell.value.toString()); +/// }).toList()); /// } +/// } /// /// ``` class SfDataGrid extends StatefulWidget { @@ -156,36 +211,29 @@ class SfDataGrid extends StatefulWidget { /// /// The [columns] and [source] argument must be defined and must not be null. const SfDataGrid( - {@required this.source, - @required this.columns, - Key key, - double rowHeight, - double headerRowHeight, - double defaultColumnWidth, - GridLinesVisibility gridLinesVisibility, - GridLinesVisibility headerGridLinesVisibility, - ColumnWidthCalculationMode columnWidthCalculationMode, - ColumnWidthCalculationRange columnWidthCalculationRange, - ColumnWidthMode columnWidthMode, - SelectionMode selectionMode, - GridNavigationMode navigationMode, + {required this.source, + required this.columns, + Key? key, + this.rowHeight = double.nan, + this.headerRowHeight = double.nan, + this.defaultColumnWidth = double.nan, + this.gridLinesVisibility = GridLinesVisibility.horizontal, + this.headerGridLinesVisibility = GridLinesVisibility.horizontal, + this.columnWidthMode = ColumnWidthMode.none, + this.selectionMode = SelectionMode.none, + this.navigationMode = GridNavigationMode.row, this.frozenColumnsCount = 0, this.footerFrozenColumnsCount = 0, this.frozenRowsCount = 0, this.footerFrozenRowsCount = 0, - bool allowSorting, - bool allowMultiColumnSorting, - bool allowTriStateSorting, - bool showSortNumbers, - SortingGestureType sortingGestureType, - List stackedHeaderRows, + this.allowSorting = false, + this.allowMultiColumnSorting = false, + this.allowTriStateSorting = false, + this.showSortNumbers = false, + this.sortingGestureType = SortingGestureType.tap, + this.stackedHeaderRows = const [], this.selectionManager, this.controller, - this.columnSizer, - this.cellBuilder, - this.headerCellBuilder, - this.onQueryCellStyle, - this.onQueryRowStyle, this.onQueryRowHeight, this.onSelectionChanged, this.onSelectionChanging, @@ -196,42 +244,24 @@ class SfDataGrid extends StatefulWidget { this.onCellDoubleTap, this.onCellSecondaryTap, this.onCellLongPress, - bool isScrollbarAlwaysShown, - ScrollPhysics horizontalScrollPhysics, - ScrollPhysics verticalScrollPhysics, - this.loadMoreViewBuilder}) - : assert(source != null), - assert(columns != null), - assert(frozenColumnsCount != null && frozenColumnsCount >= 0), - assert( - footerFrozenColumnsCount != null && footerFrozenColumnsCount >= 0), - assert(frozenRowsCount != null && frozenRowsCount >= 0), - assert(footerFrozenRowsCount != null && footerFrozenRowsCount >= 0), - rowHeight = rowHeight, - headerRowHeight = headerRowHeight, - defaultColumnWidth = defaultColumnWidth, - gridLinesVisibility = - gridLinesVisibility ?? GridLinesVisibility.horizontal, - headerGridLinesVisibility = - headerGridLinesVisibility ?? GridLinesVisibility.horizontal, - columnWidthCalculationMode = - columnWidthCalculationMode ?? ColumnWidthCalculationMode.textSize, - columnWidthMode = columnWidthMode ?? ColumnWidthMode.none, - columnWidthCalculationRange = columnWidthCalculationRange ?? - ColumnWidthCalculationRange.visibleRows, - selectionMode = selectionMode ?? SelectionMode.none, - navigationMode = navigationMode ?? GridNavigationMode.row, - allowSorting = allowSorting ?? false, - allowMultiColumnSorting = allowMultiColumnSorting ?? false, - allowTriStateSorting = allowTriStateSorting ?? false, - showSortNumbers = showSortNumbers ?? false, - sortingGestureType = sortingGestureType ?? SortingGestureType.tap, - stackedHeaderRows = stackedHeaderRows ?? const [], - isScrollbarAlwaysShown = isScrollbarAlwaysShown ?? false, - horizontalScrollPhysics = - horizontalScrollPhysics ?? const AlwaysScrollableScrollPhysics(), - verticalScrollPhysics = - verticalScrollPhysics ?? const AlwaysScrollableScrollPhysics(), + this.isScrollbarAlwaysShown = false, + this.horizontalScrollPhysics = const AlwaysScrollableScrollPhysics(), + this.verticalScrollPhysics = const AlwaysScrollableScrollPhysics(), + this.loadMoreViewBuilder, + this.allowPullToRefresh = false, + this.refreshIndicatorDisplacement = 40.0, + this.refreshIndicatorStrokeWidth = 2.0, + this.allowSwiping = false, + this.swipeMaxOffset = 200.0, + this.onSwipeStart, + this.onSwipeUpdate, + this.onSwipeEnd, + this.startSwipeActionsBuilder, + this.endSwipeActionsBuilder}) + : assert(frozenColumnsCount >= 0), + assert(footerFrozenColumnsCount >= 0), + assert(frozenRowsCount >= 0), + assert(footerFrozenRowsCount >= 0), super(key: key); /// The height of each row except the column header. @@ -241,7 +271,7 @@ class SfDataGrid extends StatefulWidget { /// The height of the column header row. /// - ///Defaults to 56.0 + /// Defaults to 56.0 final double headerRowHeight; /// The collection of the [GridColumn]. @@ -275,37 +305,6 @@ class SfDataGrid extends StatefulWidget { /// Also refer [ColumnWidthMode] final ColumnWidthMode columnWidthMode; - /// How the column widths should be calculated. - /// - /// Provides options to calculate whether each cell in a column should be - /// measured based on the size of the text or should be measured based on the - /// length of the text. - /// When the [ColumnWidthCalculationMode.textLength], the text in each cell - /// which has large length is considered for text size measurement. - /// - /// Defaults to [ColumnWidthCalculationMode.textSize] - /// - /// Also refer [ColumnWidthCalculationMode] - final ColumnWidthCalculationMode columnWidthCalculationMode; - - /// The [ColumnSizer] used to control the column width sizing operation of - /// each columns. - /// - /// You can override the available methods and customize the required - /// operations in the custom [ColumnSizer]. - final ColumnSizer columnSizer; - - /// How the row count should be considered when calculating the width of a - /// column. - /// - /// Provides options to consider only visible rows or all the rows which are - /// available in [SfDataGrid]. - /// - /// Defaults to [ColumnWidthCalculationRange.visibleRows] - /// - /// Also refer [ColumnWidthCalculationRange] - final ColumnWidthCalculationRange columnWidthCalculationRange; - /// How the border should be visible. /// /// Decides whether vertical, horizontal, both the borders and no borders @@ -318,78 +317,22 @@ class SfDataGrid extends StatefulWidget { /// How the border should be visible in header cells. /// - /// Decides whether vertical or horizontal or both the borders or no borders should be drawn. + /// Decides whether vertical or horizontal or both the borders or no borders + /// should be drawn. /// - /// [GridLinesVisibility.horizontal] will be useful if you are using [stackedHeaderRows] to improve the readability. + /// [GridLinesVisibility.horizontal] will be useful if you are using + /// [stackedHeaderRows] to improve the readability. /// /// Defaults to [GridLinesVisibility.horizontal] /// /// Also refer [GridLinesVisibility]. /// - /// See also, [gridLinesVisibility] – To set the border for cells other than header cells. + /// See also, [gridLinesVisibility] – To set the border for cells other than + /// header cells. final GridLinesVisibility headerGridLinesVisibility; - /// Invoked when the style for each cell is applied. - /// - /// Users can set the styling for the cells based on the condition. - final QueryCellStyleCallback onQueryCellStyle; - - /// Invoked when the style for each row is applied. - /// - /// Users can set the styling for the cells based on the condition. - final QueryRowStyleCallback onQueryRowStyle; - - /// A builder that sets the widget for the [GridWidgetColumn]. - /// - /// The widget returned by this method is wrapped in a cell. - final CellBuilderCallback cellBuilder; - - /// A builder that sets the widget for the headercell. - /// - /// The widget returned by this method is wrapped in a header cell and - /// builder will replace the default header. - /// - /// Builder will load the widget in text area alone. When sorting is applied - /// the default sort icon will be loaded even if the widget is loaded from the - /// builder. You can customize the sort icon by using - /// [SfDataGridThemeData.headerStyle.sortIconColor]. - /// - /// ``` dart - /// @override - /// Widget build(BuildContext context) { - /// return SfDataGrid( - /// source: _employeeDataSource, - /// headerCellBuilder: (buildContext, column) { - /// if (column.mappingName == 'name') { - /// return Row(children: [ - /// Icon(Icons.account_circle), - /// SizedBox(width: 5), - /// Flexible( - /// child: Text( - /// 'Employee Name', - /// overflow: TextOverflow.ellipsis, - /// )) - /// ]); - /// } - /// return null; - /// }, - /// columns: [ - /// GridNumericColumn(mappingName: 'id') - /// ..headerText = 'ID', - /// GridTextColumn(mappingName: 'name') - /// ..headerText = 'Name', - /// GridTextColumn(mappingName: 'designation') - /// ..headerText = 'Designation', - /// GridNumericColumn(mappingName: 'salary') - /// ..headerText = 'Salary', - /// ], - /// ); - /// } - /// ``` - final HeaderCellBuilderCallback headerCellBuilder; - /// Invoked when the row height for each row is queried. - final QueryRowHeightCallback onQueryRowHeight; + final QueryRowHeightCallback? onQueryRowHeight; /// How the rows should be selected. /// @@ -404,14 +347,14 @@ class SfDataGrid extends StatefulWidget { /// /// This callback never be called when the [onSelectionChanging] is returned /// as false. - final SelectionChangedCallback onSelectionChanged; + final SelectionChangedCallback? onSelectionChanged; /// Invoked when the row is being selected or being unselected /// /// This callback's return type is [bool]. So, if you want to cancel the /// selection on a row based on the condition, return false. /// Otherwise, return true. - final SelectionChangingCallback onSelectionChanging; + final SelectionChangingCallback? onSelectionChanging; /// The [SelectionManagerBase] used to control the selection operations /// in [SfDataGrid]. @@ -420,7 +363,7 @@ class SfDataGrid extends StatefulWidget { /// operations in the custom [RowSelectionManager]. /// /// Defaults to null - final SelectionManagerBase selectionManager; + final SelectionManagerBase? selectionManager; /// The [DataGridController] used to control the current cell navigation and /// selection operation. @@ -428,13 +371,13 @@ class SfDataGrid extends StatefulWidget { /// Defaults to null. /// /// This object is expected to be long-lived, not recreated with each build. - final DataGridController controller; + final DataGridController? controller; /// Called when the cell renderers are created for each column. /// /// This method is called once when the [SfDataGrid] is loaded. Users can /// provide the custom cell renderer to the existing collection. - final CellRenderersCreatedCallback onCellRenderersCreated; + final CellRenderersCreatedCallback? onCellRenderersCreated; /// Decides whether the navigation in the [SfDataGrid] should be cell wise /// or row wise. @@ -444,28 +387,28 @@ class SfDataGrid extends StatefulWidget { /// /// This callback never be called when the [onCurrentCellActivating] is /// returned as false. - final CurrentCellActivatedCallback onCurrentCellActivated; + final CurrentCellActivatedCallback? onCurrentCellActivated; /// Invoked when the cell is being activated. /// /// This callback's return type is [bool]. So, if you want to cancel cell /// activation based on the condition, return false. Otherwise, /// return true. - final CurrentCellActivatingCallback onCurrentCellActivating; + final CurrentCellActivatingCallback? onCurrentCellActivating; /// Called when a tap with a cell has occurred. - final DataGridCellTapCallback onCellTap; + final DataGridCellTapCallback? onCellTap; /// Called when user is tapped a cell with a primary button at the same cell /// twice in quick succession. - final DataGridCellDoubleTapCallback onCellDoubleTap; + final DataGridCellDoubleTapCallback? onCellDoubleTap; /// Called when a long press gesture with a primary button has been /// recognized for a cell. - final DataGridCellTapCallback onCellSecondaryTap; + final DataGridCellTapCallback? onCellSecondaryTap; /// Called when a tap with a cell has occurred with a secondary button. - final DataGridCellLongPressCallback onCellLongPress; + final DataGridCellLongPressCallback? onCellLongPress; /// The number of non-scrolling columns at the left side of [SfDataGrid]. /// @@ -492,9 +435,9 @@ class SfDataGrid extends StatefulWidget { /// /// See also: /// - /// [SfDataGridThemeData. frozenPaneLineWidth], which is used to customize the + /// [SfDataGridThemeData.frozenPaneLineWidth], which is used to customize the /// width of the frozen line. - /// [SfDataGridThemeData. frozenPaneLineColor], which is used to customize the + /// [SfDataGridThemeData.frozenPaneLineColor], which is used to customize the /// color of the frozen line. final int footerFrozenColumnsCount; @@ -505,9 +448,9 @@ class SfDataGrid extends StatefulWidget { /// See also: /// /// [footerFrozenRowsCount] - /// [SfDataGridThemeData. frozenPaneLineWidth], which is used to customize the + /// [SfDataGridThemeData.frozenPaneLineWidth], which is used to customize the /// width of the frozen line. - /// [SfDataGridThemeData. frozenPaneLineColor], which is used to customize the + /// [SfDataGridThemeData.frozenPaneLineColor], which is used to customize the /// color of the frozen line. final int frozenRowsCount; @@ -517,9 +460,9 @@ class SfDataGrid extends StatefulWidget { /// /// See also: /// - /// [SfDataGridThemeData. frozenPaneLineWidth], which is used to customize the + /// [SfDataGridThemeData.frozenPaneLineWidth], which is used to customize the /// width of the frozen line. - /// [SfDataGridThemeData. frozenPaneLineColor], which is used to customize the + /// [SfDataGridThemeData.frozenPaneLineColor], which is used to customize the /// color of the frozen line. final int footerFrozenRowsCount; @@ -535,42 +478,32 @@ class SfDataGrid extends StatefulWidget { /// source: _employeeDataSource, /// allowSorting: true, /// columns: [ - /// GridNumericColumn(mappingName: 'id') - /// ..headerText = 'ID', - /// GridTextColumn(mappingName: 'name') - /// ..headerText = 'Name', - /// GridTextColumn(mappingName: 'designation') - /// ..headerText = 'Designation', - /// GridNumericColumn(mappingName: 'salary') - /// ..headerText = 'Salary', + /// GridTextColumn(columnName: 'id', label: Text('ID')), + /// GridTextColumn(columnName: 'name', label: Text('Name')), + /// GridTextColumn(columnName: 'designation', label: Text('Designation')), + /// GridTextColumn(columnName: 'salary', label: Text('Salary')), /// ]); /// } /// - /// class EmployeeDataSource extends DataGridSource { - /// - /// @override - /// List get dataSource => _employees; - /// - /// @override - /// Object getValue(Employee data, String columnName) { - /// switch (columnName) { - /// case 'id': - /// return data.id; - /// break; - /// case 'name': - /// return data.name; - /// break; - /// case 'salary': - /// return data.salary; - /// break; - /// case 'designation': - /// return data.designation; - /// break; - /// default: - /// return ' '; - /// break; - /// } - /// } + /// class EmployeeDataSource extends DataGridSource { + /// @override + /// List get rows => _employees + /// .map((dataRow) => DataGridRow(cells: [ + /// DataGridCell(columnName: 'id', value: dataRow.id), + /// DataGridCell(columnName: 'name', value: dataRow.name), + /// DataGridCell( + /// columnName: 'designation', value: dataRow.designation), + /// DataGridCell(columnName: 'salary', value: dataRow.salary), + /// ])) + /// .toList(); + /// @override + /// DataGridRowAdapter buildRow(DataGridRow row) { + /// return DataGridRowAdapter( + /// cells: row.getCells().map((dataCell) { + /// return Text(dataCell.value.toString()); + /// }).toList()); + /// } + /// } /// /// ``` /// @@ -581,7 +514,7 @@ class SfDataGrid extends StatefulWidget { /// [SfDataGrid]. /// * [sortingGestureType] – which allows users to sort the column in tap or /// double tap. - /// * [DataGridController.sortedColumns] - which is the collection of + /// * [DataGridSource.sortedColumns] - which is the collection of /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. /// * [DataGridSource.sort] - call this method when you are adding the /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. @@ -622,7 +555,7 @@ class SfDataGrid extends StatefulWidget { /// Defaults to false /// /// This is applicable only if the [allowSorting] and - ///[allowMultiColumnSorting] are set as true. + /// [allowMultiColumnSorting] are set as true. /// /// See also: /// @@ -652,16 +585,18 @@ class SfDataGrid extends StatefulWidget { /// @override /// Widget build(BuildContext context) { /// return SfDataGrid(source: _employeeDataSource, columns: [ - /// GridNumericColumn(mappingName: 'orderID', headerText: 'ID'), - /// GridDateTimeColumn( - /// mappingName: 'orderDate', headerText: 'Order Date'), - /// GridTextColumn( - /// mappingName: 'designation', headerText: 'Designation'), - /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary') + /// GridTextColumn(columnName: 'id', label: Text('ID')), + /// GridTextColumn(columnName: 'name', label: Text('Name')), + /// GridTextColumn(columnName: 'designation', label: Text('Designation')), + /// GridTextColumn(columnName: 'salary', label: Text('Salary')) /// ], stackedHeaderRows: [ - /// StackedHeaderRow(stackedColumns: [ - /// StackedColumn( - /// childColumns: " orderID, orderDate", headerText: "Order Details"), + /// StackedHeaderRow(cells: [ + /// StackedHeaderCell( + /// columnNames: ['id', 'name', 'designation', 'salary'], + /// child: Center( + /// child: Text('Order Details'), + /// ), + /// ), /// ]) /// ]); /// } @@ -679,13 +614,13 @@ class SfDataGrid extends StatefulWidget { /// How the horizontal scroll view should respond to user input. /// For example, determines how the horizontal scroll view continues to animate after the user stops dragging the scroll view. /// - /// Defaults to AlwaysScrollableScrollPhysics() + /// Defaults to [AlwaysScrollableScrollPhysics]. final ScrollPhysics horizontalScrollPhysics; /// How the vertical scroll view should respond to user input. /// For example, determines how the vertical scroll view continues to animate after the user stops dragging the scroll view. /// - /// Defaults to AlwaysScrollableScrollPhysics() + /// Defaults to [AlwaysScrollableScrollPhysics]. final ScrollPhysics verticalScrollPhysics; /// A builder that sets the widget to display at the bottom of the datagrid @@ -694,7 +629,7 @@ class SfDataGrid extends StatefulWidget { /// You should override [DataGridSource.handleLoadMoreRows] method to load /// more rows and then notify the datagrid about the changes. The /// [DataGridSource.handleLoadMoreRows] can be called to load more rows from - /// this builder using [loadMoreRows] function which is passed as a parameter + /// this builder using `loadMoreRows` function which is passed as a parameter /// to this builder. /// /// ## Infinite scrolling @@ -736,11 +671,10 @@ class SfDataGrid extends StatefulWidget { /// ); /// }, /// columns: [ - /// GridNumericColumn(mappingName: 'id', headerText: 'ID'), - /// GridTextColumn(mappingName: 'name', headerText: 'Name'), - /// GridTextColumn( - /// mappingName: 'designation', headerText: 'Designation'), - /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), + /// GridTextColumn(columnName: 'id', label: Text('ID')), + /// GridTextColumn(columnName: 'name', label: Text('Name')), + /// GridTextColumn(columnName: 'designation', label: Text('Designation')), + /// GridTextColumn(columnName: 'salary', label: Text('Salary')), /// ], /// ), /// ); @@ -752,7 +686,7 @@ class SfDataGrid extends StatefulWidget { /// The example below demonstrates how to show the button when vertical /// scrolling is reached at the end of the datagrid and display the circular /// indicator when you tap that button. In the onPressed flatbutton callback, - /// you can call the [loadMoreRows] function to add more rows. + /// you can call the `loadMoreRows` function to add more rows. /// /// ```dart /// @override @@ -811,41 +745,111 @@ class SfDataGrid extends StatefulWidget { /// }); /// }, /// columns: [ - /// GridNumericColumn(mappingName: 'id', headerText: 'ID'), - /// GridTextColumn(mappingName: 'name', headerText: 'Name'), - /// GridTextColumn( - /// mappingName: 'designation', headerText: 'Designation'), - /// GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), + /// GridTextColumn(columnName: 'id', label: Text('ID')), + /// GridTextColumn(columnName: 'name', label: Text('Name')), + /// GridTextColumn(columnName: 'designation', label: Text('Designation')), + /// GridTextColumn(columnName: 'salary', label: Text('Salary')), /// ], /// ), /// ); /// } /// ``` - final LoadMoreViewBuilder loadMoreViewBuilder; + final LoadMoreViewBuilder? loadMoreViewBuilder; + + /// Decides whether refresh indicator should be shown when datagrid is pulled + /// down. + /// + /// See also, + /// + /// [DataGridSource.handleRefresh] – This will be called when datagrid + /// is pulled down to refresh the data. + final bool allowPullToRefresh; + + /// The distance from the [SfDataGrid]’s top or bottom edge to where the refresh + /// indicator will settle. During the drag that exposes the refresh indicator, + /// its actual displacement may significantly exceed this value. + /// + /// By default, the value of `refreshIndicatorDisplacement` is 40.0. + final double refreshIndicatorDisplacement; + + /// Defines `strokeWidth` for `RefreshIndicator` used by [SfDataGrid]. + /// + /// By default, the value of `refreshIndicatorStrokeWidth` is 2.0 pixels. + final double refreshIndicatorStrokeWidth; + + /// Decides whether to swipe a row “right to left” or “left to right” for custom + /// actions such as deleting, editing, and so on. When the user swipes a row, + /// the row will be moved, and swipe view will be shown for custom actions. + /// + /// You can show the widgets for left or right swipe view using + /// [SfDataGrid.startSwipeActionsBuilder] and [SfDataGrid.endSwipeActionsBuilder]. + /// + /// See also, + /// + /// [SfDataGrid.onSwipeStart] + /// [SfDataGrid.onSwipeUpdate] + /// [SfDataGrid.onSwipeEnd] + final bool allowSwiping; + + /// Defines the maximum offset in which a row can be swiped. + /// + /// Defaults to 200. + final double swipeMaxOffset; + + /// Called when row swiping is started. + /// + /// You can disable the swiping for specific row by returning false. + final DataGridSwipeStartCallback? onSwipeStart; + + /// Called when row is being swiped. + /// + /// You can disable the swiping for specific requirement on swiping itself by + /// returning false. + final DataGridSwipeUpdateCallback? onSwipeUpdate; + + /// Called when swiping of a row is ended (i.e. when reaches the max offset). + final DataGridSwipeEndCallback? onSwipeEnd; + + /// A builder that sets the widget for the background view in which a row is + /// swiped in the reading direction (e.g., from left to right in left-to-right + /// languages). + final DataGridSwipeActionsBuilder? startSwipeActionsBuilder; + + /// A builder that sets the widget for the background view in which a row is + /// swiped in the reverse of reading direction (e.g., from right to left in + /// left-to-right languages). + final DataGridSwipeActionsBuilder? endSwipeActionsBuilder; @override - State createState() => _SfDataGridState(); + State createState() => SfDataGridState(); } -class _SfDataGridState extends State { - Map _cellRenderers = {}; - _RowGenerator _rowGenerator; - _VisualContainerHelper _container; - _DataGridStateDetails _dataGridStateDetails; - _DataGridSettings _dataGridSettings; - ColumnSizer _columnSizer; - TextDirection _textDirection = TextDirection.ltr; - SfDataGridThemeData _dataGridThemeData; - DataGridSource _source; - List _columns; - SelectionManagerBase _rowSelectionManager; - DataGridController _controller; - _CurrentCellManager _currentCell; - +/// Contains the state for a [SfDataGrid]. This class can be used to +/// programmatically show the refresh indicator, see the [refresh] +/// method. +class SfDataGridState extends State + with SingleTickerProviderStateMixin { static const double _minWidth = 300.0; static const double _minHeight = 300.0; - static const double _headerRowHeight = 56.0; static const double _rowHeight = 49.0; + static const double _headerRowHeight = 56.0; + + late _RowGenerator _rowGenerator; + late _VisualContainerHelper _container; + late _DataGridStateDetails _dataGridStateDetails; + late _DataGridSettings _dataGridSettings; + late _ColumnSizer _columnSizer; + late _CurrentCellManager _currentCell; + AnimationController? _swipingAnimationController; + + Map _cellRenderers = {}; + TextDirection _textDirection = TextDirection.ltr; + SfDataGridThemeData? _dataGridThemeData; + DataGridSource? _source; + List? _columns; + SelectionManagerBase? _rowSelectionManager; + DataGridController? _controller; + Animation? _swipingAnimation; @override void initState() { @@ -855,13 +859,15 @@ class _SfDataGridState extends State { _dataGridSettings.gridPaint = Paint(); _rowGenerator = _RowGenerator(dataGridStateDetails: _dataGridStateDetails); _container = _VisualContainerHelper(rowGenerator: _rowGenerator); + _swipingAnimationController = AnimationController( + duration: const Duration(milliseconds: 200), vsync: this); _setUp(); _updateDataGridStateDetails(); super.initState(); } - set textDirection(TextDirection newTextDirection) { - if (_textDirection == newTextDirection) { + void _onDataGridTextDirectionChanged(TextDirection? newTextDirection) { + if (newTextDirection == null || _textDirection == newTextDirection) { return; } @@ -870,8 +876,8 @@ class _SfDataGridState extends State { _container._needToSetHorizontalOffset = true; } - set dataGridThemeData(SfDataGridThemeData newThemeData) { - if (_dataGridThemeData == newThemeData) { + void _onDataGridThemeDataChanged(SfDataGridThemeData? newThemeData) { + if (newThemeData == null || _dataGridThemeData == newThemeData) { return; } @@ -894,21 +900,23 @@ class _SfDataGridState extends State { _dataGridSettings.controller = widget.controller ?? DataGridController() .._dataGridStateDetails = _dataGridStateDetails; - _controller?.addListener(_handleDataSourceChangeListeners); + _controller!._addDataGridPropertyChangeListener( + _handleDataGridPropertyChangeListeners); + //AutoFit controller initializing - _columnSizer = widget.columnSizer ?? ColumnSizer() + _columnSizer = _ColumnSizer() .._dataGridStateDetails = _dataGridStateDetails; //CurrentCell Manager initializing _currentCell = _dataGridSettings.currentCell = - _CurrentCellManager(_dataGridStateDetails); + _CurrentCellManager(dataGridStateDetails: _dataGridStateDetails); //Selection Manager initializing _rowSelectionManager = _dataGridSettings.rowSelectionManager = - widget.selectionManager ?? - RowSelectionManager(dataGridStateDetails: _dataGridStateDetails); - _rowSelectionManager?._dataGridStateDetails = _dataGridStateDetails; - _controller?.addListener(_rowSelectionManager.handleDataGridSourceChanges); + widget.selectionManager ?? RowSelectionManager(); + _rowSelectionManager!._dataGridStateDetails = _dataGridStateDetails; + _controller!._addDataGridPropertyChangeListener( + _rowSelectionManager!._handleSelectionPropertyChanged); _initializeProperties(); } @@ -921,13 +929,9 @@ class _SfDataGridState extends State { @protected void _refreshContainerAndView({bool isDataSourceChanged = false}) { - if (_rowGenerator == null) { - return; - } - if (isDataSourceChanged) { - _rowSelectionManager._updateSelectionController( - isDataSourceChanged: isDataSourceChanged); + _rowSelectionManager! + ._updateSelectionController(isDataSourceChanged: isDataSourceChanged); } _ensureSelectionProperties(); @@ -938,13 +942,12 @@ class _SfDataGridState extends State { } void _updateVisualDensity() { - final baseDensity = _dataGridSettings.visualDensity.baseSizeAdjustment; + final baseDensity = _dataGridSettings.visualDensity?.baseSizeAdjustment; - _dataGridSettings.headerRowHeight = widget.headerRowHeight ?? - _dataGridSettings.headerRowHeight + baseDensity.dy; + _dataGridSettings.headerRowHeight = + _dataGridSettings.headerRowHeight + baseDensity!.dy; - _dataGridSettings.rowHeight = - widget.rowHeight ?? _dataGridSettings.rowHeight + baseDensity.dy; + _dataGridSettings.rowHeight = _dataGridSettings.rowHeight + baseDensity.dy; } void _initializeDataGridDataSource() { @@ -953,19 +956,21 @@ class _SfDataGridState extends State { _source = widget.source; _addDataGridSourceListeners(); } - _source._updateDataSource(); + _source?._updateDataSource(); } void _initializeProperties() { if (!listEquals(_columns, widget.columns)) { - _columns - ..clear() - ..addAll(widget.columns ?? _columns); + if (_columns != null) { + _columns! + ..clear() + ..addAll(widget.columns); + } } _rowSelectionManager?._dataGridStateDetails = _dataGridStateDetails; - _currentCell?._dataGridStateDetails = _dataGridStateDetails; - _columnSizer?._dataGridStateDetails = _dataGridStateDetails; + _currentCell._dataGridStateDetails = _dataGridStateDetails; + _columnSizer._dataGridStateDetails = _dataGridStateDetails; _updateDataGridStateDetails(); } @@ -975,16 +980,11 @@ class _SfDataGridState extends State { GridCellTextFieldRenderer(_dataGridStateDetails); _cellRenderers['ColumnHeader'] = GridHeaderCellRenderer(_dataGridStateDetails); - _cellRenderers['Numeric'] = - GridCellNumericTextFieldRenderer(_dataGridStateDetails); - _cellRenderers['Widget'] = GridCellWidgetRenderer(_dataGridStateDetails); - _cellRenderers['DateTime'] = - GridCellDateTimeRenderer(_dataGridStateDetails); _cellRenderers['StackedHeader'] = _GridStackedHeaderCellRenderer(_dataGridStateDetails); if (widget.onCellRenderersCreated != null) { - widget.onCellRenderersCreated(_cellRenderers); + widget.onCellRenderersCreated!(_cellRenderers); for (final renderer in _cellRenderers.entries) { renderer.value._dataGridStateDetails = _dataGridStateDetails; } @@ -998,17 +998,19 @@ class _SfDataGridState extends State { final columnIndex = _GridIndexResolver.resolveToScrollColumnIndex( _dataGridSettings, rowColumnIndex.columnIndex); - final dataRow = _rowGenerator.items.firstWhere( - (dataRow) => dataRow.rowIndex == rowIndex, - orElse: () => null); + final dataRow = _rowGenerator.items + .firstWhereOrNull((dataRow) => dataRow.rowIndex == rowIndex); if (dataRow == null) { return; } - final dataCell = dataRow._visibleColumns.firstWhere( - (dataCell) => dataCell.columnIndex == columnIndex, - orElse: () => null); + dataRow._dataGridRow = _source!._effectiveRows[rowColumnIndex.rowIndex]; + dataRow._dataGridRowAdapter = _SfDataGridHelper.getDataGridRowAdapter( + _dataGridSettings, dataRow._dataGridRow!); + + final dataCell = dataRow._visibleColumns + .firstWhereOrNull((dataCell) => dataCell.columnIndex == columnIndex); if (dataCell == null) { return; @@ -1025,26 +1027,43 @@ class _SfDataGridState extends State { void _processUpdateDataSource() { setState(() { _initializeDataGridDataSource(); - _dataGridSettings.source = _source; + _dataGridSettings.source = _source!; + if (!listEquals(_columns, widget.columns)) { if (widget.selectionMode != SelectionMode.none && - widget.navigationMode == GridNavigationMode.cell) { - _rowSelectionManager._onRowColumnChanged(-1, widget.columns.length); + widget.navigationMode == GridNavigationMode && + _rowSelectionManager != null) { + _rowSelectionManager!._onRowColumnChanged(-1, widget.columns.length); } _resetColumn(); } if (widget.selectionMode != SelectionMode.none && - widget.navigationMode == GridNavigationMode.cell) { - _rowSelectionManager._onRowColumnChanged( - widget.source._effectiveDataSource.length, -1); + widget.navigationMode == GridNavigationMode.cell && + _rowSelectionManager != null) { + _rowSelectionManager! + ._onRowColumnChanged(widget.source._effectiveRows.length, -1); + } + + if (widget.allowSwiping) { + _dataGridSettings.container.resetSwipeOffset(); } _container .._updateRowAndColumnCount() .._refreshView() .._isDirty = true; + + // FLUT-3219 Need to reset the vertical offset when both the controller offset + // scrollbar offset are not identical + if (_dataGridSettings.verticalController != null && + _dataGridSettings.verticalController!.hasClients && + _dataGridSettings.verticalController!.offset == 0 && + _dataGridSettings.container.verticalOffset > 0) { + _dataGridSettings.container.verticalOffset = 0; + _dataGridSettings.container.verticalScrollBar._value = 0; + } }); if (_dataGridSettings.source.shouldRecalculateColumnWidths()) { _dataGridSettings.columnSizer._resetAutoCalculation(); @@ -1062,10 +1081,12 @@ class _SfDataGridState extends State { } void _resetColumn() { - _columns - ..clear() - ..addAll(widget.columns); - _dataGridSettings.columns = _columns; + if (_columns != null) { + _columns! + ..clear() + ..addAll(widget.columns); + _dataGridSettings.columns = _columns!; + } for (final dataRow in _rowGenerator.items) { for (final dataCell in dataRow._visibleColumns) { @@ -1079,42 +1100,46 @@ class _SfDataGridState extends State { _container._needToRefreshColumn = true; } - void _handleNotifyListeners() { + void _handleListeners() { _processUpdateDataSource(); } - void _handleDataSourceChangeListeners( - {RowColumnIndex rowColumnIndex, - String propertyName, - bool recalculateRowHeight}) { - if (rowColumnIndex != null && propertyName == null) { + void _handleNotifyListeners({RowColumnIndex? rowColumnIndex}) { + if (rowColumnIndex != null) { _processCellUpdate(rowColumnIndex); } - - if (rowColumnIndex == null && propertyName == null) { + if (rowColumnIndex == null) { _processUpdateDataSource(); } + } + + void _handleDataGridPropertyChangeListeners( + {RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false}) { if (propertyName == 'refreshRow') { - final rowIndex = _GridIndexResolver.resolveToRowIndex( - _dataGridSettings, rowColumnIndex.rowIndex); + if (rowColumnIndex != null) { + final rowIndex = _GridIndexResolver.resolveToRowIndex( + _dataGridSettings, rowColumnIndex.rowIndex); - final dataRow = _rowGenerator.items.firstWhere( - (dataRow) => dataRow.rowIndex == rowIndex, - orElse: () => null); - if (dataRow == null) { - return; - } - setState(() { - dataRow - .._isDirty = true - .._rowIndexChanged(); - if (recalculateRowHeight) { - _dataGridSettings.container.rowHeightManager.setDirty(rowIndex); - _dataGridSettings.container - .._needToRefreshColumn = true - ..setRowHeights(); + final dataRow = _rowGenerator.items + .firstWhereOrNull((dataRow) => dataRow.rowIndex == rowIndex); + + if (dataRow == null) { + return; } - }); + setState(() { + dataRow + .._isDirty = true + .._rowIndexChanged(); + if (recalculateRowHeight) { + _dataGridSettings.container.rowHeightManager.setDirty(rowIndex); + _dataGridSettings.container + .._needToRefreshColumn = true + ..setRowHeights(); + } + }); + } } if (rowColumnIndex == null && propertyName == 'Sorting') { @@ -1127,36 +1152,30 @@ class _SfDataGridState extends State { // been set in header cell widget itself }); } + + if (propertyName == 'Swiping') { + setState(() { + _container._isDirty = true; + }); + } } void _updateDataGridStateDetails() { _dataGridSettings + ..source = _source! + ..columns = _columns! ..textDirection = _textDirection ..cellRenderers = _cellRenderers ..container = _container ..rowGenerator = _rowGenerator - ..rowHeight = - widget.rowHeight ?? (_dataGridSettings.rowHeight ?? _rowHeight) - ..headerRowHeight = widget.headerRowHeight ?? - (_dataGridSettings.headerRowHeight ?? _headerRowHeight) ..visualDensity = _dataGridSettings.visualDensity - ..defaultColumnWidth = - widget.defaultColumnWidth ?? _dataGridSettings.defaultColumnWidth - ..source = _source - ..columns = _columns ..headerLineCount = _container._headerLineCount - ..onQueryCellStyle = widget.onQueryCellStyle - ..onQueryRowStyle = widget.onQueryRowStyle - ..cellBuilder = widget.cellBuilder - ..headerCellBuilder = widget.headerCellBuilder ..onQueryRowHeight = widget.onQueryRowHeight ..dataGridThemeData = _dataGridThemeData ..gridLinesVisibility = widget.gridLinesVisibility ..headerGridLinesVisibility = widget.headerGridLinesVisibility ..columnWidthMode = widget.columnWidthMode ..columnSizer = _columnSizer - ..columnWidthCalculationMode = widget.columnWidthCalculationMode - ..columnWidthCalculationRange = widget.columnWidthCalculationRange ..selectionMode = widget.selectionMode ..onSelectionChanged = widget.onSelectionChanged ..onSelectionChanging = widget.onSelectionChanging @@ -1181,25 +1200,53 @@ class _SfDataGridState extends State { ..isScrollbarAlwaysShown = widget.isScrollbarAlwaysShown ..horizontalScrollPhysics = widget.horizontalScrollPhysics ..verticalScrollPhysics = widget.verticalScrollPhysics - ..loadMoreViewBuilder = widget.loadMoreViewBuilder; + ..loadMoreViewBuilder = widget.loadMoreViewBuilder + ..refreshIndicatorDisplacement = widget.refreshIndicatorDisplacement + ..allowPullToRefresh = widget.allowPullToRefresh + ..refreshIndicatorStrokeWidth = widget.refreshIndicatorStrokeWidth + ..allowSwiping = widget.allowSwiping + ..swipeMaxOffset = widget.swipeMaxOffset + ..onSwipeStart = widget.onSwipeStart + ..onSwipeUpdate = widget.onSwipeUpdate + ..onSwipeEnd = widget.onSwipeEnd + ..startSwipeActionsBuilder = widget.startSwipeActionsBuilder + ..endSwipeActionsBuilder = widget.endSwipeActionsBuilder + ..swipingAnimationController ??= _swipingAnimationController + ..swipingAnimation ??= _swipingAnimation + ..swipingOffset = 0.0 + ..isSwipingApplied = false + ..rowHeight = (widget.rowHeight.isNaN + ? _dataGridSettings.rowHeight.isNaN + ? _rowHeight + : _dataGridSettings.rowHeight + : widget.rowHeight) + ..headerRowHeight = (widget.headerRowHeight.isNaN + ? _dataGridSettings.headerRowHeight.isNaN + ? _headerRowHeight + : _dataGridSettings.headerRowHeight + : widget.headerRowHeight) + ..defaultColumnWidth = (widget.defaultColumnWidth.isNaN + ? _dataGridSettings.defaultColumnWidth + : widget.defaultColumnWidth); + + if (widget.allowPullToRefresh) { + _dataGridSettings.refreshIndicatorKey ??= + GlobalKey(); + } } _DataGridSettings _onDataGridStateDetailsChanged() => _dataGridSettings; void _updateProperties(SfDataGrid oldWidget) { final isSourceChanged = widget.source != oldWidget.source; - final isDataSourceChanged = !listEquals( - oldWidget.source.dataSource, widget.source.dataSource); + final isDataSourceChanged = + !listEquals(oldWidget.source.rows, widget.source.rows); final isColumnsChanged = !listEquals(_columns, widget.columns); final isSelectionManagerChanged = oldWidget.selectionManager != widget.selectionManager || oldWidget.selectionMode != widget.selectionMode; - final isColumnSizerChanged = oldWidget.columnSizer != widget.columnSizer || - oldWidget.columnWidthCalculationMode != - widget.columnWidthCalculationMode || - oldWidget.columnWidthMode != widget.columnWidthMode || - oldWidget.columnWidthCalculationRange != - widget.columnWidthCalculationRange; + final isColumnSizerChanged = + oldWidget.columnWidthMode != widget.columnWidthMode; final isDataGridControllerChanged = oldWidget.controller != widget.controller; final isFrozenColumnPaneChanged = oldWidget.frozenColumnsCount != @@ -1215,6 +1262,15 @@ class _SfDataGridState extends State { oldWidget.showSortNumbers != widget.showSortNumbers; final isStackedHeaderRowsChanged = !listEquals( oldWidget.stackedHeaderRows, widget.stackedHeaderRows); + final isPullToRefreshPropertiesChanged = + oldWidget.allowPullToRefresh != widget.allowPullToRefresh || + oldWidget.refreshIndicatorDisplacement != + widget.refreshIndicatorDisplacement || + oldWidget.refreshIndicatorStrokeWidth != + widget.refreshIndicatorStrokeWidth; + final isSwipingChanged = widget.allowSwiping != oldWidget.allowSwiping; + final isMaxSwipeOffsetChanged = + widget.swipeMaxOffset != oldWidget.swipeMaxOffset; if (isSourceChanged || isColumnsChanged || isDataSourceChanged || @@ -1227,6 +1283,7 @@ class _SfDataGridState extends State { isMultiColumnSortingChanged || isShowSortNumbersChanged || isStackedHeaderRowsChanged || + isSwipingChanged || oldWidget.rowHeight != widget.rowHeight || oldWidget.headerRowHeight != widget.headerRowHeight || oldWidget.defaultColumnWidth != widget.defaultColumnWidth || @@ -1247,25 +1304,26 @@ class _SfDataGridState extends State { } if (isDataGridControllerChanged) { - oldWidget.controller?.removeListener(_handleDataSourceChangeListeners); + oldWidget.controller?._addDataGridPropertyChangeListener( + _handleDataGridPropertyChangeListeners); _controller = - _dataGridSettings.controller = widget.controller ?? _controller + _dataGridSettings.controller = widget.controller ?? _controller! .._dataGridStateDetails = _dataGridStateDetails; - _controller?.addListener(_handleDataSourceChangeListeners); + _controller?._addDataGridPropertyChangeListener( + _handleDataGridPropertyChangeListeners); } _initializeProperties(); - if (isStackedHeaderRowsChanged) { + if (isStackedHeaderRowsChanged || isColumnsChanged) { _onStackedHeaderRowsPropertyChanged(oldWidget, widget); } _container._refreshDefaultLineSize(); - _updateSelectionController( - oldWidget: oldWidget, + _updateSelectionController(oldWidget, isDataGridControlChanged: isDataGridControllerChanged, isSelectionManagerChanged: isSelectionManagerChanged, isSourceChanged: isSourceChanged, @@ -1296,6 +1354,18 @@ class _SfDataGridState extends State { _container._refreshView(); } + if (widget.allowSwiping || + (oldWidget.allowSwiping && !widget.allowSwiping)) { + if (isDataSourceChanged || + isColumnSizerChanged || + isMaxSwipeOffsetChanged || + isFrozenRowPaneChanged || + isFrozenColumnPaneChanged || + (oldWidget.allowSwiping && !widget.allowSwiping)) { + _container.resetSwipeOffset(); + } + } + _container._isDirty = true; } else { if (oldWidget.gridLinesVisibility != widget.gridLinesVisibility || @@ -1303,29 +1373,34 @@ class _SfDataGridState extends State { oldWidget.sortingGestureType != widget.sortingGestureType) { _initializeProperties(); _container._isDirty = true; + } else if (isPullToRefreshPropertiesChanged || isMaxSwipeOffsetChanged) { + _initializeProperties(); } } } - void _updateSelectionController( - {SfDataGrid oldWidget, - bool isSelectionManagerChanged, - bool isDataGridControlChanged, - bool isSourceChanged, - bool isDataSourceChanged}) { + void _updateSelectionController(SfDataGrid oldWidget, + {bool isSelectionManagerChanged = false, + bool isDataGridControlChanged = false, + bool isSourceChanged = false, + bool isDataSourceChanged = false}) { if (isSourceChanged) { - oldWidget.controller - ?.removeListener(_rowSelectionManager.handleDataGridSourceChanges); - widget.controller - ?.addListener(_rowSelectionManager.handleDataGridSourceChanges); + oldWidget.controller?._addDataGridPropertyChangeListener( + _rowSelectionManager!._handleSelectionPropertyChanged); + widget.controller?._addDataGridPropertyChangeListener( + _rowSelectionManager!._handleSelectionPropertyChanged); } if (isSelectionManagerChanged) { _rowSelectionManager = _dataGridSettings.rowSelectionManager = - widget.selectionManager ?? _rowSelectionManager + widget.selectionManager ?? _rowSelectionManager! .._dataGridStateDetails = _dataGridStateDetails; } + if (isSourceChanged) { + _rowSelectionManager!.handleDataGridSourceChanges(); + } + _rowSelectionManager?._updateSelectionController( isSelectionModeChanged: oldWidget.selectionMode != widget.selectionMode, isNavigationModeChanged: @@ -1341,24 +1416,42 @@ class _SfDataGridState extends State { SfDataGrid oldWidget, SfDataGrid widget) { _container._refreshHeaderLineCount(); if (oldWidget.stackedHeaderRows.isNotEmpty) { - _container.rowGenerator.items + _rowGenerator.items .removeWhere((row) => row.rowType == RowType.stackedHeaderRow); } if (widget.onQueryRowHeight != null) { _container.rowHeightManager.reset(); } + + // FlUT-3851 Needs to reset the vertical and horizontal offset when both the + // controller's offset and scrollbar's offset are not identical. + if ((oldWidget.stackedHeaderRows.isNotEmpty && + widget.stackedHeaderRows.isEmpty) || + (oldWidget.stackedHeaderRows.isEmpty && + widget.stackedHeaderRows.isNotEmpty)) { + if (_dataGridSettings.verticalController!.hasClients && + _dataGridSettings.container.verticalOffset > 0) { + _dataGridSettings.container.verticalOffset = 0; + _dataGridSettings.container.verticalScrollBar._value = 0; + } + if (_dataGridSettings.horizontalController!.hasClients && + _dataGridSettings.container.horizontalOffset > 0) { + _dataGridSettings.container.horizontalOffset = 0; + _dataGridSettings.container.horizontalScrollBar._value = 0; + } + } } void _ensureSelectionProperties() { - if (_controller.selectedRows.isNotEmpty) { + if (_controller!.selectedRows.isNotEmpty) { _rowSelectionManager?.onSelectedRowsChanged(); } - if (_controller.selectedRow != null) { + if (_controller!.selectedRow != null) { _rowSelectionManager?.onSelectedRowChanged(); } - if (_controller.selectedIndex != null && _controller.selectedIndex != -1) { + if (_controller!.selectedIndex != -1) { _rowSelectionManager?.onSelectedIndexChanged(); } } @@ -1376,7 +1469,7 @@ class _SfDataGridState extends State { void _updateDecoration() { final borderSide = - BorderSide(color: _dataGridThemeData.currentCellStyle.borderColor); + BorderSide(color: _dataGridThemeData!.currentCellStyle.borderColor); final decoration = BoxDecoration( border: Border( bottom: borderSide, @@ -1388,35 +1481,69 @@ class _SfDataGridState extends State { } void _addDataGridSourceListeners() { - _source?.addListener(_handleDataSourceChangeListeners); - _source?.addListener(_handleNotifyListeners); + _source?._addDataGridPropertyChangeListener( + _handleDataGridPropertyChangeListeners); + _source?._addDataGridSourceListener(_handleNotifyListeners); + _source?.addListener(_handleListeners); } void _removeDataGridSourceListeners() { - _source?.removeListener(_handleDataSourceChangeListeners); - _source?.removeListener(_handleNotifyListeners); + _source?._removeDataGridPropertyChangeListener( + _handleDataGridPropertyChangeListeners); + _source?._removeDataGridSourceListener(_handleNotifyListeners); + _source?.removeListener(_handleListeners); + } + + /// Show the refresh indicator and call the + /// [DataGridSource.handleRefresh]. + /// + /// To access this method, create the [SfDataGrid] with a + /// [GlobalKey]. + /// + /// The future returned from this method completes when the + /// [DataGridSource.handleRefresh] method’s future completes. + /// + /// By default, if you call this method without any parameter, + /// [RefreshIndicator] will be shown. If you want to disable the + /// [RefreshIndicator] and call the [DataGridSource.handleRefresh] method + /// alone, pass the parameter as `false`. + Future refresh([bool showRefreshIndicator = true]) async { + if (_dataGridSettings.allowPullToRefresh && + _dataGridSettings.refreshIndicatorKey != null) { + if (showRefreshIndicator) { + await _dataGridSettings.refreshIndicatorKey!.currentState?.show(); + } else { + await _dataGridSettings.source.handleRefresh(); + } + } } @override void didChangeDependencies() { - textDirection = Directionality.of(context); - dataGridThemeData = SfDataGridTheme.of(context); - _dataGridSettings.textScaleFactor = MediaQuery.textScaleFactorOf(context); - _dataGridSettings.headerRowHeight = widget.headerRowHeight ?? - (_dataGridSettings.textScaleFactor > 1.0 - ? 56.0 * _dataGridSettings.textScaleFactor - : 56.0); - _dataGridSettings.rowHeight = widget.rowHeight ?? - (_dataGridSettings.textScaleFactor > 1.0 - ? 49.0 * _dataGridSettings.textScaleFactor - : 49.0); final ThemeData themeData = Theme.of(context); _dataGridSettings._isDesktop = kIsWeb || themeData.platform == TargetPlatform.macOS || - themeData.platform == TargetPlatform.windows; - _dataGridSettings.defaultColumnWidth = - widget.defaultColumnWidth ?? (_dataGridSettings._isDesktop ? 100 : 90); + themeData.platform == TargetPlatform.windows || + themeData.platform == TargetPlatform.linux; + _onDataGridTextDirectionChanged(Directionality.of(context)); + _onDataGridThemeDataChanged(SfDataGridTheme.of(context)); + _dataGridSettings.textScaleFactor = MediaQuery.textScaleFactorOf(context); _dataGridSettings.visualDensity = themeData.visualDensity; + _dataGridSettings.headerRowHeight = widget.headerRowHeight.isNaN + ? (_dataGridSettings.textScaleFactor > 1.0) + ? _headerRowHeight * _dataGridSettings.textScaleFactor + : _headerRowHeight + : widget.headerRowHeight; + _dataGridSettings.rowHeight = widget.rowHeight.isNaN + ? (_dataGridSettings.textScaleFactor > 1.0) + ? _rowHeight * _dataGridSettings.textScaleFactor + : _rowHeight + : widget.rowHeight; + _dataGridSettings.defaultColumnWidth = widget.defaultColumnWidth.isNaN + ? _dataGridSettings._isDesktop + ? 100 + : 90 + : widget.defaultColumnWidth; _updateVisualDensity(); super.didChangeDependencies(); } @@ -1461,76 +1588,98 @@ class _SfDataGridState extends State { @override void dispose() { _removeDataGridSourceListeners(); - _controller?.removeListener(_handleDataSourceChangeListeners); + _controller?.removeListener(_handleDataGridPropertyChangeListeners); _dataGridSettings ..gridPaint = null ..boxPainter = null ..configuration = null; _dataGridThemeData = null; + if (_swipingAnimationController != null) { + _swipingAnimationController!.dispose(); + _swipingAnimationController = null; + } super.dispose(); } } class _DataGridSettings { - Map cellRenderers; - DataGridSource source; - List columns; - double rowHeight; - double headerRowHeight; - double defaultColumnWidth; - double textScaleFactor; - _VisualContainerHelper container; - _RowGenerator rowGenerator; - int headerLineCount; - SfDataGridThemeData dataGridThemeData; - CellBuilderCallback cellBuilder; - HeaderCellBuilderCallback headerCellBuilder; - QueryCellStyleCallback onQueryCellStyle; - QueryRowStyleCallback onQueryRowStyle; - QueryRowHeightCallback onQueryRowHeight; - TextDirection textDirection; - GridLinesVisibility gridLinesVisibility; - GridLinesVisibility headerGridLinesVisibility; - ColumnWidthMode columnWidthMode; - ColumnSizer columnSizer; - ColumnWidthCalculationMode columnWidthCalculationMode; - ColumnWidthCalculationRange columnWidthCalculationRange; - SelectionManagerBase rowSelectionManager; - SelectionMode selectionMode; - SelectionChangingCallback onSelectionChanging; - SelectionChangedCallback onSelectionChanged; - DataGridController controller; - _CurrentCellManager currentCell; - GridNavigationMode navigationMode; - CurrentCellActivatedCallback onCurrentCellActivated; - CurrentCellActivatingCallback onCurrentCellActivating; - ScrollController verticalController; - ScrollController horizontalController; - FocusNode dataGridFocusNode; - VisualDensity visualDensity; - double viewWidth; - double viewHeight; - Paint gridPaint; - ImageConfiguration configuration; - BoxPainter boxPainter; - DataGridCellTapCallback onCellTap; - DataGridCellDoubleTapCallback onCellDoubleTap; - DataGridCellTapCallback onCellSecondaryTap; - DataGridCellLongPressCallback onCellLongPress; - int frozenColumnsCount; - int footerFrozenColumnsCount; - int frozenRowsCount; - int footerFrozenRowsCount; - bool allowSorting; - bool allowMultiColumnSorting; - bool allowTriStateSorting; - bool showSortNumbers; - SortingGestureType sortingGestureType; - bool isControlKeyPressed; - bool _isDesktop; - List stackedHeaderRows; - bool isScrollbarAlwaysShown; - ScrollPhysics horizontalScrollPhysics; - ScrollPhysics verticalScrollPhysics; - LoadMoreViewBuilder loadMoreViewBuilder; + // late assignable values and non-null + late Map cellRenderers; + late DataGridSource source; + late List columns; + late double textScaleFactor; + late _VisualContainerHelper container; + late _RowGenerator rowGenerator; + late ColumnWidthMode columnWidthMode; + late _ColumnSizer columnSizer; + late SelectionManagerBase rowSelectionManager; + late DataGridController controller; + late _CurrentCellManager currentCell; + late double viewWidth; + late double viewHeight; + late ScrollPhysics horizontalScrollPhysics; + late ScrollPhysics verticalScrollPhysics; + + int headerLineCount = 0; + int frozenColumnsCount = 0; + int footerFrozenColumnsCount = 0; + int frozenRowsCount = 0; + int footerFrozenRowsCount = 0; + double rowHeight = double.nan; + double headerRowHeight = double.nan; + double defaultColumnWidth = double.nan; + double swipingOffset = 0.0; + double refreshIndicatorDisplacement = 40.0; + double refreshIndicatorStrokeWidth = 2.0; + double swipeMaxOffset = 200.0; + + bool allowSorting = false; + bool allowMultiColumnSorting = false; + bool isControlKeyPressed = false; + bool allowTriStateSorting = false; + bool showSortNumbers = false; + bool _isDesktop = false; + bool isScrollbarAlwaysShown = false; + bool allowPullToRefresh = false; + bool allowSwiping = false; + // This flag is used to restrict the scrolling while updating the swipe offset + // of a row that already swiped in view. + bool isSwipingApplied = false; + + SortingGestureType sortingGestureType = SortingGestureType.tap; + GridNavigationMode navigationMode = GridNavigationMode.row; + TextDirection textDirection = TextDirection.ltr; + GridLinesVisibility gridLinesVisibility = GridLinesVisibility.horizontal; + GridLinesVisibility headerGridLinesVisibility = + GridLinesVisibility.horizontal; + SelectionMode selectionMode = SelectionMode.none; + + Paint? gridPaint; + BoxPainter? boxPainter; + ScrollController? verticalController; + ScrollController? horizontalController; + FocusNode? dataGridFocusNode; + ImageConfiguration? configuration; + GlobalKey? refreshIndicatorKey; + Animation? swipingAnimation; + AnimationController? swipingAnimationController; + List stackedHeaderRows = []; + VisualDensity? visualDensity; + SfDataGridThemeData? dataGridThemeData; + + DataGridSwipeStartCallback? onSwipeStart; + DataGridSwipeUpdateCallback? onSwipeUpdate; + DataGridSwipeEndCallback? onSwipeEnd; + DataGridSwipeActionsBuilder? startSwipeActionsBuilder; + DataGridSwipeActionsBuilder? endSwipeActionsBuilder; + QueryRowHeightCallback? onQueryRowHeight; + SelectionChangingCallback? onSelectionChanging; + SelectionChangedCallback? onSelectionChanged; + CurrentCellActivatedCallback? onCurrentCellActivated; + CurrentCellActivatingCallback? onCurrentCellActivating; + DataGridCellTapCallback? onCellTap; + DataGridCellDoubleTapCallback? onCellDoubleTap; + DataGridCellTapCallback? onCellSecondaryTap; + DataGridCellLongPressCallback? onCellLongPress; + LoadMoreViewBuilder? loadMoreViewBuilder; } diff --git a/packages/syncfusion_flutter_datagrid/pubspec.yaml b/packages/syncfusion_flutter_datagrid/pubspec.yaml index d176e0b47..03f7a312c 100644 --- a/packages/syncfusion_flutter_datagrid/pubspec.yaml +++ b/packages/syncfusion_flutter_datagrid/pubspec.yaml @@ -1,10 +1,10 @@ name: syncfusion_flutter_datagrid description: The Syncfusion Flutter DataGrid is used to display and manipulate data in a tabular view. Its rich feature set includes different types of columns, selections, column sizing, etc. -version: 18.3.35-beta +version: 19.1.54-beta homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_datagrid environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: @@ -13,4 +13,4 @@ dependencies: syncfusion_flutter_core: path: ../syncfusion_flutter_core - intl: ">=0.15.0 <0.17.0" + diff --git a/packages/syncfusion_flutter_datepicker/CHANGELOG.md b/packages/syncfusion_flutter_datepicker/CHANGELOG.md index 18cce4b25..78043cec2 100644 --- a/packages/syncfusion_flutter_datepicker/CHANGELOG.md +++ b/packages/syncfusion_flutter_datepicker/CHANGELOG.md @@ -1,4 +1,17 @@ -## [13.4.30-beta] +## [Unreleased version] +**Features** +* Implemented the free scroll support in the date range picker. +* Implemented action buttons to confirm and cancel the selection in the date range picker. +* Provided the support for enabling and disable the swiping interaction in the date range picker. + +**Breaking changes** +* Now, the header text will align to the left instead of center when the multiview is enabled in the date range picker. + +## [18.4.34-beta] +**Breaking changes** +* Now, the `date` and `visibleDates` types are changed from dynamic to respective types in the cell builder of the Date range picker. + +## [18.4.30-beta] **Features** * Hijri date picker support is provided. * The custom builder support is provided for the month cells and year cells in the date range picker. diff --git a/packages/syncfusion_flutter_datepicker/README.md b/packages/syncfusion_flutter_datepicker/README.md index a44f65798..643db7b2d 100644 --- a/packages/syncfusion_flutter_datepicker/README.md +++ b/packages/syncfusion_flutter_datepicker/README.md @@ -1,12 +1,10 @@ ![syncfusion_flutter_datepicker_banner](https://cdn.syncfusion.com/content/images/FTControl/Flutter/datepicker.jpg) -# Syncfusion Flutter Date Range Picker +# Flutter Date Range Picker -The Syncfusion Flutter Date Range Picker is a lightweight widget that allows users to easily select a single date, multiple dates, or a range of dates. It provides month, year, decade, and century view options to quickly navigate to the desired date. It supports minimum, maximum, and disabled dates to restrict date selection. +The Flutter Date Range Picker is a lightweight widget that allows users to easily select a single date, multiple dates, or a range of dates. Date Picker provides month, year, decade, and century view options to quickly navigate to the desired date. It supports minimum, maximum, blackout and disabled dates to restrict date selection. -**Disclaimer**: This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or the Syncfusion Community [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE). For more details, please check the LICENSE file. - -**Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. +**Disclaimer**: This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. ## Table of contents @@ -33,9 +31,9 @@ The Syncfusion Flutter Date Range Picker is a lightweight widget that allows use ![multi_date_picker_view](https://cdn.syncfusion.com/content/images/FTControl/Flutter/flutter-daterangepicker-multidatepicker.png) -* **Vertical picker** - Displays two Date Range Pickers side by side in a vertical direction, allowing you to select ranges of dates within two separate months easily. +* **Vertical picker** - Displays two Date Range Pickers side by side in the vertical direction, allowing you to select date ranges between two months easily. Also enable or disable the view navigation using swipe interaction along with snap and free scroll picker view navigation modes. -![vertical_date_picker](https://cdn.syncfusion.com/content/images/FTControl/date+range+picker/side_by_side.png) +![vertical_date_picker](https://cdn.syncfusion.com/content/images/FTControl/Flutter/calendar/free_scroll_gif.gif) * **Hijri date picker** - In addition to the Gregorian calendar, the date range picker supports displaying the Islamic calendar (Hijri date picker). @@ -51,6 +49,10 @@ The Syncfusion Flutter Date Range Picker is a lightweight widget that allows use ![selection_modes](https://cdn.syncfusion.com/content/images/FTControl/Flutter/daterangepicker/selection_mode.png) +* **Action buttons** - Display action buttons to confirm or cancel the selected date values in SfDateRangePicker and SfHijriDateRangePicker. + +![action_buttons](https://cdn.syncfusion.com/content/images/FTControl/Flutter/calendar/action_buttons.png) + * **Limit the date selection range** - Select only a date range with a specific minimum and maximum numbers of days (span of days) by setting the min and max days options. ![min_max_dates](https://cdn.syncfusion.com/content/images/FTControl/Flutter/daterangepicker/min_max_date.png) diff --git a/packages/syncfusion_flutter_datepicker/analysis_options.yaml b/packages/syncfusion_flutter_datepicker/analysis_options.yaml index 221601219..7dea9558d 100644 --- a/packages/syncfusion_flutter_datepicker/analysis_options.yaml +++ b/packages/syncfusion_flutter_datepicker/analysis_options.yaml @@ -3,4 +3,5 @@ include: package:syncfusion_flutter_core/analysis_options.yaml analyzer: errors: include_file_not_found: ignore + invalid_dependency: ignore diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker.dart index 5def1c93f..a148bce48 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker.dart @@ -54,11 +54,7 @@ typedef DateRangePickerSelectionChangedCallback = void Function( // parameters. void _raiseSelectionChangedCallback(_SfDateRangePicker picker, {dynamic value}) { - if (picker.onSelectionChanged == null) { - return; - } - - picker.onSelectionChanged(DateRangePickerSelectionChangedArgs(value)); + picker.onSelectionChanged?.call(DateRangePickerSelectionChangedArgs(value)); } // method that raises the visible dates changed call back with the given @@ -177,35 +173,41 @@ class SfDateRangePicker extends StatelessWidget { /// When the visible view changes, the widget will call the [onViewChanged] /// callback with the current view and the current view visible dates. SfDateRangePicker({ - Key key, - DateRangePickerView view, + Key? key, + DateRangePickerView view = DateRangePickerView.month, this.selectionMode = DateRangePickerSelectionMode.single, this.headerHeight = 40, this.todayHighlightColor, this.backgroundColor, - DateTime initialSelectedDate, - List initialSelectedDates, - PickerDateRange initialSelectedRange, - List initialSelectedRanges, + DateTime? initialSelectedDate, + List? initialSelectedDates, + PickerDateRange? initialSelectedRange, + List? initialSelectedRanges, this.toggleDaySelection = false, this.enablePastDates = true, this.showNavigationArrow = false, + this.confirmText = 'OK', + this.cancelText = 'CANCEL', + this.showActionButtons = false, this.selectionShape = DateRangePickerSelectionShape.circle, this.navigationDirection = DateRangePickerNavigationDirection.horizontal, + this.allowViewNavigation = true, + this.navigationMode = DateRangePickerNavigationMode.snap, + this.enableMultiView = false, this.controller, this.onViewChanged, this.onSelectionChanged, - DateRangePickerHeaderStyle headerStyle, - DateRangePickerYearCellStyle yearCellStyle, - DateRangePickerMonthViewSettings monthViewSettings, - DateTime initialDisplayDate, - DateTime minDate, - DateTime maxDate, - DateRangePickerMonthCellStyle monthCellStyle, - bool allowViewNavigation, - bool enableMultiView, - double viewSpacing, - this.selectionRadius, + this.onCancel, + this.onSubmit, + this.headerStyle = const DateRangePickerHeaderStyle(), + this.yearCellStyle = const DateRangePickerYearCellStyle(), + this.monthViewSettings = const DateRangePickerMonthViewSettings(), + this.monthCellStyle = const DateRangePickerMonthCellStyle(), + DateTime? minDate, + DateTime? maxDate, + DateTime? initialDisplayDate, + double viewSpacing = 20, + this.selectionRadius = -1, this.selectionColor, this.startRangeSelectionColor, this.endRangeSelectionColor, @@ -214,15 +216,7 @@ class SfDateRangePicker extends StatelessWidget { this.rangeTextStyle, this.monthFormat, this.cellBuilder, - }) : headerStyle = headerStyle ?? - (enableMultiView != null && - enableMultiView && - navigationDirection == - DateRangePickerNavigationDirection.horizontal - ? DateRangePickerHeaderStyle(textAlign: TextAlign.center) - : DateRangePickerHeaderStyle()), - yearCellStyle = yearCellStyle ?? DateRangePickerYearCellStyle(), - initialSelectedDate = + }) : initialSelectedDate = controller != null && controller.selectedDate != null ? controller.selectedDate : initialSelectedDate, @@ -239,23 +233,15 @@ class SfDateRangePicker extends StatelessWidget { ? controller.selectedRanges : initialSelectedRanges, view = controller != null && controller.view != null - ? controller.view - : view ?? DateRangePickerView.month, - monthViewSettings = - monthViewSettings ?? DateRangePickerMonthViewSettings(), + ? controller.view! + : view, initialDisplayDate = controller != null && controller.displayDate != null - ? controller.displayDate - : (initialDisplayDate ?? - DateTime(DateTime.now().year, DateTime.now().month, - DateTime.now().day, 08, 45, 0)), + ? controller.displayDate! + : initialDisplayDate ?? DateTime.now(), minDate = minDate ?? DateTime(1900, 01, 01), maxDate = maxDate ?? DateTime(2100, 12, 31), - monthCellStyle = monthCellStyle ?? DateRangePickerMonthCellStyle(), - enableMultiView = enableMultiView ?? false, - viewSpacing = viewSpacing ?? - (enableMultiView != null && enableMultiView ? 20 : 0), - allowViewNavigation = allowViewNavigation ?? true, + viewSpacing = enableMultiView ? viewSpacing : 0, super(key: key); /// Defines the view for the [SfDateRangePicker]. @@ -397,7 +383,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final Color todayHighlightColor; + final Color? todayHighlightColor; /// The color to fill the background of the [SfDateRangePicker]. /// @@ -418,7 +404,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final Color backgroundColor; + final Color? backgroundColor; /// Allows to deselect a date when the [DateRangePickerSelectionMode] set as /// [DateRangePickerSelectionMode.single]. @@ -535,7 +521,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final DateRangePickerCellBuilder cellBuilder; + final DateRangePickerCellBuilder? cellBuilder; /// Used to enable or disable showing multiple views /// @@ -578,6 +564,9 @@ class SfDateRangePicker extends StatelessWidget { /// /// Defaults to value `20`. /// + /// This value not applicable on [SfDateRangePicker] when + /// [navigationMode] is [DateRangePickerNavigationMode.scroll]. + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -648,7 +637,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; /// The text style for the text in the selected range or ranges cell of /// [SfDateRangePicker] month view. @@ -690,7 +679,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; /// The color which fills the [SfDateRangePicker] selection view. /// @@ -720,7 +709,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final Color selectionColor; + final Color? selectionColor; /// The color which fills the [SfDateRangePicker] selection view of the range /// start date. @@ -753,7 +742,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; /// The color which fills the [SfDateRangePicker] selection view for the range /// of dates which falls between the [PickerDateRange.startDate] and @@ -787,7 +776,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final Color rangeSelectionColor; + final Color? rangeSelectionColor; /// The color which fills the [SfDateRangePicker] selection view of the range /// end date. @@ -820,7 +809,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; /// The settings have properties which allow to customize the month view of /// the [SfDateRangePicker]. @@ -1043,7 +1032,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final DateTime initialSelectedDate; + final DateTime? initialSelectedDate; /// The minimum date as much as the [SfDateRangePicker] will navigate. /// @@ -1173,7 +1162,7 @@ class SfDateRangePicker extends StatelessWidget { ///} /// /// ``` - final List initialSelectedDates; + final List? initialSelectedDates; /// The date range to initially select on the [SfDateRangePicker]. /// @@ -1208,7 +1197,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final PickerDateRange initialSelectedRange; + final PickerDateRange? initialSelectedRange; /// The date ranges to initially select on the [SfDateRangePicker]. /// @@ -1246,7 +1235,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final List initialSelectedRanges; + final List? initialSelectedRanges; /// An object that used for programmatic date navigation, date and range /// selection and view switching in [SfDateRangePicker]. @@ -1332,7 +1321,7 @@ class SfDateRangePicker extends StatelessWidget { ///} /// /// ``` - final DateRangePickerController controller; + final DateRangePickerController? controller; /// Displays the navigation arrows on the header view of [SfDateRangePicker]. /// @@ -1455,7 +1444,25 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final String monthFormat; + final String? monthFormat; + + /// Defines the view navigation mode based on its [navigationDirection] + /// for [SfDateRangePicker]. + /// + /// Defaults to [DateRangePickerNavigationMode.snap] + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfDateRangePicker( + /// navigationMode: DateRangePickerNavigationMode.scroll, + /// ), + /// ); + /// } + /// + /// ``` + final DateRangePickerNavigationMode navigationMode; /// Called when the current visible view or visible date range changes. /// @@ -1481,7 +1488,7 @@ class SfDateRangePicker extends StatelessWidget { /// } /// /// ``` - final DateRangePickerViewChangedCallback onViewChanged; + final DateRangePickerViewChangedCallback? onViewChanged; /// Called when the new dates or date ranges selected. /// @@ -1524,7 +1531,140 @@ class SfDateRangePicker extends StatelessWidget { ///} /// /// ``` - final DateRangePickerSelectionChangedCallback onSelectionChanged; + final DateRangePickerSelectionChangedCallback? onSelectionChanged; + + /// Text that displays on the confirm button. + /// + /// See also + /// [showActionButtons] + /// [onSelectionChanged]. + /// + /// ``` dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Date Range Picker'), + /// ), + /// body: Container( + /// child: SfDateRangePicker( + /// confirmText: 'Confirm', + /// showActionButtons: true, + /// ))); + /// } + /// + /// ``` + final String confirmText; + + /// Text that displays on the cancel button. + /// + /// See also + /// [showActionButtons] + /// [onCancel]. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Date Range Picker'), + /// ), + /// body: Container( + /// child: SfDateRangePicker( + /// cancelText: 'Dismiss', + /// showActionButtons: true, + /// ))); + /// } + /// + /// ``` + final String cancelText; + + /// Displays confirm and cancel buttons on the date range picker to perform + /// the confirm and cancel actions. + /// + /// The [onSubmit] and [onCancel] callback is called based on the + /// actions of the buttons. + /// + /// ``` dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Date Range Picker'), + /// ), + /// body: Container( + /// child: SfDateRangePicker( + /// cancelText: 'Dismiss', + /// confirmText: 'Confirm', + /// showActionButtons: true, + /// ))); + /// } + /// + /// ``` + final bool showActionButtons; + + /// Called whenever the cancel button tapped on date range picker. + /// It reset the selected values to confirmed selected values. + /// + /// See also + /// [showActionButtons]. + /// + /// ```dart + /// + ///Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Date Range Picker'), + /// ), + /// body: Container( + /// child: SfDateRangePicker( + /// showActionButtons: true, + /// onCancel: () { + /// Navigator.pop(context); + /// }, + /// ))); + /// } + /// + /// ``` + final VoidCallback? onCancel; + + /// Called whenever the confirm button tapped on date range picker. + /// The dates or ranges that have been selected are confirmed and the + /// selected value is available in the value argument. + /// + /// See also + /// [showActionButtons]. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Date Range Picker'), + /// ), + /// body: Container( + /// child: SfDateRangePicker( + /// showActionButtons: true, + /// onSubmit: (Object value) { + /// if (value is PickerDateRange) {​ + /// final DateTime rangeStartDate = value.startDate; + /// final DateTime rangeEndDate = value.endDate; + /// }​ else if (value is DateTime) {​ + /// final DateTime selectedDate = value; + /// }​ else if (value is List) {​ + /// final List selectedDates = value; + /// }​ else {​ + /// final List selectedRanges = value; + /// }​ + /// }, + /// ))); + /// } + /// + /// ``` + final Function(Object)? onSubmit; @override Widget build(BuildContext context) { @@ -1547,6 +1687,8 @@ class SfDateRangePicker extends StatelessWidget { controller: controller, onViewChanged: onViewChanged, onSelectionChanged: onSelectionChanged, + onCancel: onCancel, + onSubmit: onSubmit, headerStyle: headerStyle, yearCellStyle: yearCellStyle, monthViewSettings: monthViewSettings, @@ -1566,8 +1708,87 @@ class SfDateRangePicker extends StatelessWidget { rangeTextStyle: rangeTextStyle, monthFormat: monthFormat, cellBuilder: cellBuilder, + navigationMode: navigationMode, + confirmText: confirmText, + cancelText: cancelText, + showActionButtons: showActionButtons, ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(EnumProperty('view', view)); + properties.add(EnumProperty( + 'selectionMode', selectionMode)); + properties.add(EnumProperty( + 'selectionShape', selectionShape)); + properties.add(EnumProperty( + 'navigationDirection', navigationDirection)); + properties.add(EnumProperty( + 'navigationMode', navigationMode)); + properties.add(DoubleProperty('headerHeight', headerHeight)); + properties.add(DoubleProperty('viewSpacing', viewSpacing)); + properties.add(DoubleProperty('selectionRadius', selectionRadius)); + properties.add(ColorProperty('todayHighlightColor', todayHighlightColor)); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + properties.add(ColorProperty('selectionColor', selectionColor)); + properties.add( + ColorProperty('startRangeSelectionColor', startRangeSelectionColor)); + properties + .add(ColorProperty('endRangeSelectionColor', endRangeSelectionColor)); + properties.add(ColorProperty('rangeSelectionColor', rangeSelectionColor)); + properties.add(StringProperty('monthFormat', monthFormat)); + properties.add(DiagnosticsProperty( + 'selectionTextStyle', selectionTextStyle)); + properties + .add(DiagnosticsProperty('rangeTextStyle', rangeTextStyle)); + properties.add(DiagnosticsProperty( + 'initialDisplayDate', initialDisplayDate)); + properties.add(DiagnosticsProperty( + 'initialSelectedDate', initialSelectedDate)); + properties.add(IterableDiagnostics(initialSelectedDates) + .toDiagnosticsNode(name: 'initialSelectedDates')); + properties.add(DiagnosticsProperty( + 'initialSelectedRange', initialSelectedRange)); + properties.add(IterableDiagnostics(initialSelectedRanges) + .toDiagnosticsNode(name: 'initialSelectedRanges')); + properties.add(DiagnosticsProperty('minDate', minDate)); + properties.add(DiagnosticsProperty('maxDate', maxDate)); + properties.add(DiagnosticsProperty( + 'cellBuilder', cellBuilder)); + properties.add( + DiagnosticsProperty('allowViewNavigation', allowViewNavigation)); + properties.add( + DiagnosticsProperty('toggleDaySelection', toggleDaySelection)); + properties + .add(DiagnosticsProperty('enablePastDates', enablePastDates)); + properties.add( + DiagnosticsProperty('showNavigationArrow', showNavigationArrow)); + properties + .add(DiagnosticsProperty('showActionButtons', showActionButtons)); + properties.add(StringProperty('cancelText', cancelText)); + properties.add(StringProperty('confirmText', confirmText)); + properties + .add(DiagnosticsProperty('enableMultiView', enableMultiView)); + properties.add(DiagnosticsProperty( + 'onViewChanged', onViewChanged)); + properties.add(DiagnosticsProperty( + 'onSelectionChanged', onSelectionChanged)); + properties.add(DiagnosticsProperty('onCancel', onCancel)); + properties.add(DiagnosticsProperty('onSubmit', onSubmit)); + properties.add(DiagnosticsProperty( + 'controller', controller)); + + properties.add(headerStyle.toDiagnosticsNode(name: 'headerStyle')); + + properties.add(yearCellStyle.toDiagnosticsNode(name: 'yearCellStyle')); + + properties + .add(monthViewSettings.toDiagnosticsNode(name: 'monthViewSettings')); + + properties.add(monthCellStyle.toDiagnosticsNode(name: 'monthCellStyle')); + } } /// A material design date range picker. @@ -1667,35 +1888,41 @@ class SfHijriDateRangePicker extends StatelessWidget { /// When the visible view changes, the widget will call the [onViewChanged] /// callback with the current view and the current view visible dates. SfHijriDateRangePicker({ - Key key, - HijriDatePickerView view, + Key? key, + HijriDatePickerView view = HijriDatePickerView.month, this.selectionMode = DateRangePickerSelectionMode.single, this.headerHeight = 40, this.todayHighlightColor, this.backgroundColor, - HijriDateTime initialSelectedDate, - List initialSelectedDates, - HijriDateRange initialSelectedRange, - List initialSelectedRanges, + HijriDateTime? initialSelectedDate, + List? initialSelectedDates, + HijriDateRange? initialSelectedRange, + List? initialSelectedRanges, this.toggleDaySelection = false, this.enablePastDates = true, this.showNavigationArrow = false, + this.confirmText = 'OK', + this.cancelText = 'CANCEL', + this.showActionButtons = false, this.selectionShape = DateRangePickerSelectionShape.circle, this.navigationDirection = DateRangePickerNavigationDirection.horizontal, + this.navigationMode = DateRangePickerNavigationMode.snap, + this.allowViewNavigation = true, + this.enableMultiView = false, this.controller, this.onViewChanged, this.onSelectionChanged, - DateRangePickerHeaderStyle headerStyle, - HijriDatePickerYearCellStyle yearCellStyle, - HijriDatePickerMonthViewSettings monthViewSettings, - HijriDateTime initialDisplayDate, - HijriDateTime minDate, - HijriDateTime maxDate, - HijriDatePickerMonthCellStyle monthCellStyle, - bool allowViewNavigation, - bool enableMultiView, - double viewSpacing, - this.selectionRadius, + this.onCancel, + this.onSubmit, + this.headerStyle = const DateRangePickerHeaderStyle(), + this.yearCellStyle = const HijriDatePickerYearCellStyle(), + this.monthViewSettings = const HijriDatePickerMonthViewSettings(), + HijriDateTime? initialDisplayDate, + HijriDateTime? minDate, + HijriDateTime? maxDate, + this.monthCellStyle = const HijriDatePickerMonthCellStyle(), + double viewSpacing = 20, + this.selectionRadius = -1, this.selectionColor, this.startRangeSelectionColor, this.endRangeSelectionColor, @@ -1704,15 +1931,7 @@ class SfHijriDateRangePicker extends StatelessWidget { this.rangeTextStyle, this.monthFormat, this.cellBuilder, - }) : headerStyle = headerStyle ?? - (enableMultiView != null && - enableMultiView && - navigationDirection == - DateRangePickerNavigationDirection.horizontal - ? DateRangePickerHeaderStyle(textAlign: TextAlign.center) - : DateRangePickerHeaderStyle()), - yearCellStyle = yearCellStyle ?? HijriDatePickerYearCellStyle(), - initialSelectedDate = + }) : initialSelectedDate = controller != null && controller.selectedDate != null ? controller.selectedDate : initialSelectedDate, @@ -1729,21 +1948,15 @@ class SfHijriDateRangePicker extends StatelessWidget { ? controller.selectedRanges : initialSelectedRanges, view = controller != null && controller.view != null - ? controller.view - : view ?? HijriDatePickerView.month, - monthViewSettings = - monthViewSettings ?? HijriDatePickerMonthViewSettings(), + ? controller.view! + : view, initialDisplayDate = controller != null && controller.displayDate != null - ? controller.displayDate - : (initialDisplayDate ?? HijriDateTime.now()), + ? controller.displayDate! + : initialDisplayDate ?? HijriDateTime.now(), minDate = minDate ?? HijriDateTime(1356, 01, 01), maxDate = maxDate ?? HijriDateTime(1499, 12, 30), - monthCellStyle = monthCellStyle ?? HijriDatePickerMonthCellStyle(), - enableMultiView = enableMultiView ?? false, - viewSpacing = viewSpacing ?? - (enableMultiView != null && enableMultiView ? 20 : 0), - allowViewNavigation = allowViewNavigation ?? true, + viewSpacing = enableMultiView ? viewSpacing : 0, super(key: key); /// Defines the view for the [SfHijriDateRangePicker]. @@ -1883,7 +2096,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final Color todayHighlightColor; + final Color? todayHighlightColor; /// The color to fill the background of the [SfHijriDateRangePicker]. /// @@ -1904,7 +2117,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final Color backgroundColor; + final Color? backgroundColor; /// Allows to deselect a date when the [DateRangePickerSelectionMode] set as /// [DateRangePickerSelectionMode.single]. @@ -1957,8 +2170,8 @@ class SfHijriDateRangePicker extends StatelessWidget { /// ), /// body: SfDateRangePicker( /// controller: _controller, - /// cellBuilder: - /// (BuildContext context, DateRangePickerCellDetails cellDetails) { + /// cellBuilder: (BuildContext context, + /// HijriDateRangePickerCellDetails cellDetails) { /// if (_controller.view == DateRangePickerView.month) { /// return Container( /// width: cellDetails.bounds.width, @@ -1996,7 +2209,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final DateRangePickerCellBuilder cellBuilder; + final HijriDateRangePickerCellBuilder? cellBuilder; /// Used to enable or disable the view switching between /// [HijriDatePickerView] through interaction in the @@ -2066,6 +2279,9 @@ class SfHijriDateRangePicker extends StatelessWidget { /// /// Defaults to value `20`. /// + /// This value not applicable on [SfHijriDateRangePicker] when + /// [navigationMode] is [DateRangePickerNavigationMode.scroll]. + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -2136,7 +2352,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; /// The text style for the text in the selected range or ranges cell of /// [SfHijriDateRangePicker] month view. @@ -2178,7 +2394,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; /// The color which fills the [SfHijriDateRangePicker] selection view. /// @@ -2208,7 +2424,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final Color selectionColor; + final Color? selectionColor; /// The color which fills the [SfHijriDateRangePicker] selection view of the /// range start date. @@ -2241,7 +2457,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; /// The color which fills the [SfHijriDateRangePicker] selection view for the /// range of dates which falls between the [HijriDateRange.startDate] @@ -2276,7 +2492,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final Color rangeSelectionColor; + final Color? rangeSelectionColor; /// The color which fills the [SfHijriDateRangePicker] selection view of the /// range end date. @@ -2309,7 +2525,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; /// Options to customize the month view of the [SfHijriDateRangePicker]. /// @@ -2512,7 +2728,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final HijriDateTime initialSelectedDate; + final HijriDateTime? initialSelectedDate; /// The minimum date as much as the [SfHijriDateRangePicker] will navigate. /// @@ -2645,7 +2861,7 @@ class SfHijriDateRangePicker extends StatelessWidget { ///} /// /// ``` - final List initialSelectedDates; + final List? initialSelectedDates; /// The date range to initially select on the [SfHijriDateRangePicker]. /// @@ -2680,7 +2896,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final HijriDateRange initialSelectedRange; + final HijriDateRange? initialSelectedRange; /// The date ranges to initially select on the [SfHijriDateRangePicker]. /// @@ -2720,7 +2936,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final List initialSelectedRanges; + final List? initialSelectedRanges; /// An object that used for programmatic date navigation, date and range /// selection and view switching in [SfHijriDateRangePicker]. @@ -2809,7 +3025,7 @@ class SfHijriDateRangePicker extends StatelessWidget { ///} /// /// ``` - final HijriDatePickerController controller; + final HijriDatePickerController? controller; /// Displays the navigation arrows on the header view of /// [SfHijriDateRangePicker]. @@ -2933,7 +3149,25 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final String monthFormat; + final String? monthFormat; + + /// Defines the view navigation mode based on its [navigationDirection] + /// for [SfHijriDateRangePicker]. + /// + /// Defaults to [DateRangePickerNavigationMode.snap] + /// + /// ``` dart + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfHijriDateRangePicker( + /// navigationMode: DateRangePickerNavigationMode.scroll, + /// ), + /// ); + /// } + /// + /// ``` + final DateRangePickerNavigationMode navigationMode; /// Called when the current visible view or visible date range changes. /// @@ -2960,7 +3194,7 @@ class SfHijriDateRangePicker extends StatelessWidget { /// } /// /// ``` - final HijriDatePickerViewChangedCallback onViewChanged; + final HijriDatePickerViewChangedCallback? onViewChanged; /// Called when the new dates or date ranges selected. /// @@ -3003,7 +3237,140 @@ class SfHijriDateRangePicker extends StatelessWidget { ///} /// /// ``` - final DateRangePickerSelectionChangedCallback onSelectionChanged; + final DateRangePickerSelectionChangedCallback? onSelectionChanged; + + /// Text that displays on the confirm button. + /// + /// See also + /// [showActionButtons] + /// [onSelectionChanged]. + /// + /// ``` dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Date Range Picker'), + /// ), + /// body: Container( + /// child: SfHijriDateRangePicker( + /// confirmText: 'Confirm', + /// showActionButtons: true, + /// ))); + /// } + /// + /// ``` + final String confirmText; + + /// Text that displays on the cancel button. + /// + /// See also + /// [showActionButtons] + /// [onCancel]. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Date Range Picker'), + /// ), + /// body: Container( + /// child: SfHijriDateRangePicker( + /// cancelText: 'Dismiss', + /// showActionButtons: true, + /// ))); + /// } + /// + /// ``` + final String cancelText; + + /// Displays confirm and cancel buttons on the date range picker to perform + /// the confirm and cancel actions. + /// + /// The [onSubmit] and [onCancel] callback is called based on the + /// actions of the buttons. + /// + /// ``` dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Date Range Picker'), + /// ), + /// body: Container( + /// child: SfHijriDateRangePicker( + /// cancelText: 'Dismiss', + /// confirmText: 'Confirm', + /// showActionButtons: true, + /// ))); + /// } + /// + /// ``` + final bool showActionButtons; + + /// Called whenever the cancel button tapped on date range picker. + /// It reset the selected values to confirmed selected values. + /// + /// See also + /// [showActionButtons]. + /// + /// ```dart + /// + ///Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Date Range Picker'), + /// ), + /// body: Container( + /// child: SfHijriDateRangePicker( + /// showActionButtons: true, + /// onCancel: () { + /// Navigator.pop(context); + /// }, + /// ))); + /// } + /// + /// ``` + final VoidCallback? onCancel; + + /// Called whenever the confirm button tapped on date range picker. + /// The dates or ranges that have been selected are confirmed and the + /// selected value is available in the value argument. + /// + /// See also + /// [showActionButtons]. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Date Range Picker'), + /// ), + /// body: Container( + /// child: SfHijriDateRangePicker( + /// showActionButtons: true, + /// onSubmit: (Object value) { + /// if (value is HijriDateRange) { + /// final HijriDateTime rangeStartDate = value.startDate; + /// final HijriDateTime rangeEndDate = value.endDate; + /// } else if (value is HijriDateTime) { + /// final HijriDateTime selectedDate = value; + /// } else if (value is List) { + /// final List selectedDates = value; + /// } else { + /// final List selectedRanges = value; + /// } + /// }, + /// ))); + /// } + /// + /// ``` + final Function(Object)? onSubmit; @override Widget build(BuildContext context) { @@ -3026,6 +3393,8 @@ class SfHijriDateRangePicker extends StatelessWidget { controller: controller, onViewChanged: onViewChanged, onSelectionChanged: onSelectionChanged, + onCancel: onCancel, + onSubmit: onSubmit, headerStyle: headerStyle, yearCellStyle: yearCellStyle, monthViewSettings: monthViewSettings, @@ -3045,19 +3414,98 @@ class SfHijriDateRangePicker extends StatelessWidget { rangeTextStyle: rangeTextStyle, monthFormat: monthFormat, cellBuilder: cellBuilder, + navigationMode: navigationMode, + confirmText: confirmText, + cancelText: cancelText, + showActionButtons: showActionButtons, isHijri: true, ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(EnumProperty('view', view)); + properties.add(EnumProperty( + 'selectionMode', selectionMode)); + properties.add(EnumProperty( + 'selectionShape', selectionShape)); + properties.add(EnumProperty( + 'navigationDirection', navigationDirection)); + properties.add(EnumProperty( + 'navigationMode', navigationMode)); + properties.add(DoubleProperty('headerHeight', headerHeight)); + properties.add(DoubleProperty('viewSpacing', viewSpacing)); + properties.add(DoubleProperty('selectionRadius', selectionRadius)); + properties.add(ColorProperty('todayHighlightColor', todayHighlightColor)); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + properties.add(ColorProperty('selectionColor', selectionColor)); + properties.add( + ColorProperty('startRangeSelectionColor', startRangeSelectionColor)); + properties + .add(ColorProperty('endRangeSelectionColor', endRangeSelectionColor)); + properties.add(ColorProperty('rangeSelectionColor', rangeSelectionColor)); + properties.add(StringProperty('monthFormat', monthFormat)); + properties.add(DiagnosticsProperty( + 'selectionTextStyle', selectionTextStyle)); + properties + .add(DiagnosticsProperty('rangeTextStyle', rangeTextStyle)); + properties.add(DiagnosticsProperty( + 'initialDisplayDate', initialDisplayDate)); + properties.add(DiagnosticsProperty( + 'initialSelectedDate', initialSelectedDate)); + properties.add(IterableDiagnostics(initialSelectedDates) + .toDiagnosticsNode(name: 'initialSelectedDates')); + properties.add(DiagnosticsProperty( + 'HijriDateRange', initialSelectedRange)); + properties.add(IterableDiagnostics(initialSelectedRanges) + .toDiagnosticsNode(name: 'initialSelectedRanges')); + properties.add(DiagnosticsProperty('minDate', minDate)); + properties.add(DiagnosticsProperty('maxDate', maxDate)); + properties.add(DiagnosticsProperty( + 'cellBuilder', cellBuilder)); + properties.add( + DiagnosticsProperty('allowViewNavigation', allowViewNavigation)); + properties.add( + DiagnosticsProperty('toggleDaySelection', toggleDaySelection)); + properties + .add(DiagnosticsProperty('enablePastDates', enablePastDates)); + properties.add( + DiagnosticsProperty('showNavigationArrow', showNavigationArrow)); + properties + .add(DiagnosticsProperty('showActionButtons', showActionButtons)); + properties.add(StringProperty('cancelText', cancelText)); + properties.add(StringProperty('confirmText', confirmText)); + properties + .add(DiagnosticsProperty('enableMultiView', enableMultiView)); + properties.add(DiagnosticsProperty( + 'onViewChanged', onViewChanged)); + properties.add(DiagnosticsProperty( + 'onSelectionChanged', onSelectionChanged)); + properties.add(DiagnosticsProperty('onCancel', onCancel)); + properties.add(DiagnosticsProperty('onSubmit', onSubmit)); + properties.add(DiagnosticsProperty( + 'controller', controller)); + + properties.add(headerStyle.toDiagnosticsNode(name: 'headerStyle')); + + properties.add(yearCellStyle.toDiagnosticsNode(name: 'yearCellStyle')); + + properties + .add(monthViewSettings.toDiagnosticsNode(name: 'monthViewSettings')); + + properties.add(monthCellStyle.toDiagnosticsNode(name: 'monthCellStyle')); + } } @immutable class _SfDateRangePicker extends StatefulWidget { _SfDateRangePicker({ - Key key, - this.view, - this.selectionMode, + Key? key, + required this.view, + required this.selectionMode, this.isHijri = false, - this.headerHeight, + required this.headerHeight, this.todayHighlightColor, this.backgroundColor, this.initialSelectedDate, @@ -3067,22 +3515,28 @@ class _SfDateRangePicker extends StatefulWidget { this.toggleDaySelection = false, this.enablePastDates = true, this.showNavigationArrow = false, - this.selectionShape, - this.navigationDirection, + required this.selectionShape, + required this.navigationDirection, this.controller, this.onViewChanged, this.onSelectionChanged, - this.headerStyle, - this.yearCellStyle, - this.monthViewSettings, - this.initialDisplayDate, - this.minDate, - this.maxDate, - this.monthCellStyle, - bool allowViewNavigation, - bool enableMultiView, - double viewSpacing, - this.selectionRadius, + this.onCancel, + this.onSubmit, + required this.headerStyle, + required this.yearCellStyle, + required this.monthViewSettings, + required this.initialDisplayDate, + this.confirmText = 'OK', + this.cancelText = 'CANCEL', + this.showActionButtons = false, + required this.minDate, + required this.maxDate, + required this.monthCellStyle, + this.allowViewNavigation = true, + this.enableMultiView = false, + required this.navigationMode, + required this.viewSpacing, + required this.selectionRadius, this.selectionColor, this.startRangeSelectionColor, this.endRangeSelectionColor, @@ -3091,11 +3545,7 @@ class _SfDateRangePicker extends StatefulWidget { this.rangeTextStyle, this.monthFormat, this.cellBuilder, - }) : enableMultiView = enableMultiView ?? false, - viewSpacing = viewSpacing ?? - (enableMultiView != null && enableMultiView ? 20 : 0), - allowViewNavigation = allowViewNavigation ?? true, - super(key: key); + }) : super(key: key); final DateRangePickerView view; @@ -3107,9 +3557,15 @@ class _SfDateRangePicker extends StatefulWidget { final double headerHeight; - final Color todayHighlightColor; + final String confirmText; + + final String cancelText; - final Color backgroundColor; + final bool showActionButtons; + + final Color? todayHighlightColor; + + final Color? backgroundColor; final bool toggleDaySelection; @@ -3121,21 +3577,21 @@ class _SfDateRangePicker extends StatefulWidget { final double selectionRadius; - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; - final Color selectionColor; + final Color? selectionColor; - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; - final Color rangeSelectionColor; + final Color? rangeSelectionColor; - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; final dynamic monthViewSettings; - final DateRangePickerCellBuilder cellBuilder; + final dynamic? cellBuilder; final dynamic yearCellStyle; @@ -3143,7 +3599,7 @@ class _SfDateRangePicker extends StatefulWidget { final dynamic initialDisplayDate; - final dynamic initialSelectedDate; + final dynamic? initialSelectedDate; final dynamic minDate; @@ -3151,13 +3607,13 @@ class _SfDateRangePicker extends StatefulWidget { final bool enablePastDates; - final List initialSelectedDates; + final List? initialSelectedDates; - final dynamic initialSelectedRange; + final dynamic? initialSelectedRange; - final List initialSelectedRanges; + final List? initialSelectedRanges; - final dynamic controller; + final dynamic? controller; final bool showNavigationArrow; @@ -3165,37 +3621,73 @@ class _SfDateRangePicker extends StatefulWidget { final DateRangePickerSelectionShape selectionShape; - final String monthFormat; + final String? monthFormat; + + final dynamic? onViewChanged; - final dynamic onViewChanged; + final DateRangePickerSelectionChangedCallback? onSelectionChanged; - final DateRangePickerSelectionChangedCallback onSelectionChanged; + final DateRangePickerNavigationMode navigationMode; + + final VoidCallback? onCancel; + + final Function(Object)? onSubmit; @override _SfDateRangePickerState createState() => _SfDateRangePickerState(); } class _SfDateRangePickerState extends State<_SfDateRangePicker> { - List _currentViewVisibleDates; + late List _currentViewVisibleDates; dynamic _currentDate, _selectedDate; - double _minWidth, _minHeight; - double _textScaleFactor; - ValueNotifier> _headerVisibleDates; - List _selectedDates; - dynamic _selectedRange; - List _selectedRanges; - GlobalKey _scrollViewKey; - DateRangePickerView _view; - bool _isRtl; - dynamic _controller; - Locale _locale; - SfLocalizations _localizations; - SfDateRangePickerThemeData _datePickerTheme; + double? _minWidth, _minHeight; + late double _textScaleFactor; + late ValueNotifier> _headerVisibleDates; + List? _selectedDates; + dynamic? _selectedRange; + List? _selectedRanges; + GlobalKey<_PickerScrollViewState> _scrollViewKey = + GlobalKey<_PickerScrollViewState>(); + late DateRangePickerView _view; + late bool _isRtl; + late dynamic _controller; + late Locale _locale; + late SfLocalizations _localizations; + late SfDateRangePickerThemeData _datePickerTheme; + + /// Holds the date collection after the display date. + List _forwardDateCollection = []; + + /// Holds the date collection before the display date. + List _backwardDateCollection = []; + + /// Holds the current scroll view key and it used to re initialize the + /// scroll view by create the new instance. + Key _scrollKey = UniqueKey(); + + /// Holds the key value used to specify the forward list that splits the + /// scroll view into forward list and backward list. + Key _pickerKey = UniqueKey(); + + /// Controller used to get the current scrolled position to handle the view + /// change callback. + ScrollController? _pickerScrollController; + + /// Used to store the minimum control width and it's value only assigned for + /// [didChangeDependencies]. + late double _minPickerWidth; + + /// Used to store the minimum control height and it's value only assigned for + /// [didChangeDependencies]. + late double _minPickerHeight; + + /// Store the initial selected values and its value updated by whenever the + /// confirm button pressed. + late PickerStateArgs _previousSelectedValue; @override void initState() { _isRtl = false; - _scrollViewKey = GlobalKey(); //// Update initial values to controller. _initPickerController(); _initNavigation(); @@ -3206,31 +3698,37 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { _headerVisibleDates = ValueNotifier>(_currentViewVisibleDates); _controller.addPropertyChangedListener(_pickerValueChangedListener); + + _previousSelectedValue = PickerStateArgs() + ..selectedDate = _controller.selectedDate + ..selectedDates = + DateRangePickerHelper.cloneList(_controller.selectedDates) + ..selectedRange = _controller.selectedRange + ..selectedRanges = + DateRangePickerHelper.cloneList(_controller.selectedRanges); super.initState(); } @override void didChangeDependencies() { - _textScaleFactor = MediaQuery.of(context).textScaleFactor ?? 1.0; + _textScaleFactor = MediaQuery.of(context).textScaleFactor; final TextDirection direction = Directionality.of(context); // default width value will be device width when the widget placed inside a // infinity width widget - _minWidth = MediaQuery.of(context).size.width; + _minPickerWidth = MediaQuery.of(context).size.width; // default height for the widget when the widget placed inside a infinity // height widget - _minHeight = 300; + _minPickerHeight = 300; _locale = Localizations.localeOf(context); _localizations = SfLocalizations.of(context); final SfDateRangePickerThemeData pickerTheme = SfDateRangePickerTheme.of(context); final ThemeData themeData = Theme.of(context); _datePickerTheme = pickerTheme.copyWith( - todayTextStyle: pickerTheme.todayTextStyle != null && - pickerTheme.todayTextStyle.color == null + todayTextStyle: pickerTheme.todayTextStyle.color == null ? pickerTheme.todayTextStyle.copyWith(color: themeData.accentColor) : pickerTheme.todayTextStyle, - todayCellTextStyle: pickerTheme.todayCellTextStyle != null && - pickerTheme.todayCellTextStyle.color == null + todayCellTextStyle: pickerTheme.todayCellTextStyle.color == null ? pickerTheme.todayCellTextStyle .copyWith(color: themeData.accentColor) : pickerTheme.todayCellTextStyle, @@ -3243,7 +3741,7 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { pickerTheme.endRangeSelectionColor ?? themeData.accentColor, todayHighlightColor: pickerTheme.todayHighlightColor ?? themeData.accentColor); - _isRtl = direction != null && direction == TextDirection.rtl; + _isRtl = direction == TextDirection.rtl; super.didChangeDependencies(); } @@ -3252,15 +3750,17 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { if (oldWidget.controller != widget.controller) { oldWidget.controller ?.removePropertyChangedListener(_pickerValueChangedListener); + _controller.removePropertyChangedListener(_pickerValueChangedListener); if (widget.controller != null) { - _controller.selectedDate = widget.controller.selectedDate; + _controller.selectedDate = widget.controller!.selectedDate; _controller.selectedDates = _getSelectedDates( - DateRangePickerHelper.cloneList(widget.controller.selectedDates)); - _controller.selectedRange = widget.controller.selectedRange; + DateRangePickerHelper.cloneList(widget.controller!.selectedDates)); + _controller.selectedRange = widget.controller!.selectedRange; _controller.selectedRanges = _getSelectedRanges( - DateRangePickerHelper.cloneList(widget.controller.selectedRanges)); - _controller.view = widget.controller.view; - _controller.displayDate = widget.controller.displayDate ?? _currentDate; + DateRangePickerHelper.cloneList(widget.controller!.selectedRanges)); + _controller.view = widget.controller!.view; + _controller.displayDate = + widget.controller!.displayDate ?? _currentDate; _currentDate = getValidDate( widget.minDate, widget.maxDate, _controller.displayDate); } else { @@ -3276,9 +3776,66 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { _view = DateRangePickerHelper.getPickerView(_controller.view); } - if (oldWidget.monthViewSettings.firstDayOfWeek != - widget.monthViewSettings.firstDayOfWeek) { - _updateCurrentVisibleDates(); + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(_controller.view); + if (view == DateRangePickerView.month && + oldWidget.monthViewSettings.firstDayOfWeek != + widget.monthViewSettings.firstDayOfWeek) { + if (widget.navigationMode == DateRangePickerNavigationMode.scroll) { + _forwardDateCollection.clear(); + _backwardDateCollection.clear(); + } else { + _updateCurrentVisibleDates(); + } + } + + if (widget.navigationMode != oldWidget.navigationMode) { + _initializeScrollView(); + } + + if (!widget.isHijri && + view == DateRangePickerView.month && + widget.navigationMode == DateRangePickerNavigationMode.scroll && + oldWidget.monthViewSettings.numberOfWeeksInView != + widget.monthViewSettings.numberOfWeeksInView) { + _initializeScrollView(); + } + + if (view == DateRangePickerView.month && + widget.navigationMode == DateRangePickerNavigationMode.scroll && + widget.navigationDirection == + DateRangePickerNavigationDirection.vertical && + oldWidget.monthViewSettings.viewHeaderHeight != + widget.monthViewSettings.viewHeaderHeight) { + _initializeScrollView(); + } + + if (oldWidget.showActionButtons != widget.showActionButtons) { + if (widget.navigationMode == DateRangePickerNavigationMode.scroll && + widget.navigationDirection == + DateRangePickerNavigationDirection.vertical) { + _initializeScrollView(); + } + + /// Update the previous selected value when show action button enabled. + /// because select the date without action button then the value is + /// confirmed value so store the confirmed selected values when show + /// action buttons enabled. + if (widget.showActionButtons) { + _previousSelectedValue = PickerStateArgs() + ..selectedDate = _controller.selectedDate + ..selectedDates = + DateRangePickerHelper.cloneList(_controller.selectedDates) + ..selectedRange = _controller.selectedRange + ..selectedRanges = + DateRangePickerHelper.cloneList(_controller.selectedRanges); + } + } + + if ((oldWidget.navigationDirection != widget.navigationDirection || + oldWidget.enableMultiView != widget.enableMultiView) && + widget.navigationMode == DateRangePickerNavigationMode.scroll) { + _initializeScrollView(); } if (oldWidget.selectionMode != widget.selectionMode) { @@ -3293,6 +3850,10 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { if (oldWidget.minDate != widget.minDate || oldWidget.maxDate != widget.maxDate) { _currentDate = getValidDate(widget.minDate, widget.maxDate, _currentDate); + if (widget.navigationMode == DateRangePickerNavigationMode.scroll && + !_isScrollViewDatesValid()) { + _initializeScrollView(); + } } if (!widget.isHijri && @@ -3310,33 +3871,35 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { return; } - if (oldWidget.controller.selectedDate != widget.controller.selectedDate) { + if (oldWidget.controller?.selectedDate != widget.controller?.selectedDate) { _selectedDate = _controller.selectedDate; } - if (oldWidget.controller.selectedDates != widget.controller.selectedDates) { + if (oldWidget.controller?.selectedDates != + widget.controller?.selectedDates) { _selectedDates = DateRangePickerHelper.cloneList(_controller.selectedDates); } - if (oldWidget.controller.selectedRange != widget.controller.selectedRange) { + if (oldWidget.controller?.selectedRange != + widget.controller?.selectedRange) { _selectedRange = _controller.selectedRange; } - if (oldWidget.controller.selectedRanges != - widget.controller.selectedRanges) { + if (oldWidget.controller?.selectedRanges != + widget.controller?.selectedRanges) { _selectedRanges = DateRangePickerHelper.cloneList(_controller.selectedRanges); } - if (oldWidget.controller.view != widget.controller.view) { + if (oldWidget.controller?.view != widget.controller?.view) { _view = DateRangePickerHelper.getPickerView(_controller.view); _currentDate = _updateCurrentDate(oldWidget); _controller.displayDate = _currentDate; } - if (oldWidget.controller.displayDate != widget.controller.displayDate && - widget.controller.displayDate != null) { + if (oldWidget.controller?.displayDate != widget.controller?.displayDate && + widget.controller?.displayDate != null) { _currentDate = getValidDate(widget.minDate, widget.maxDate, _controller.displayDate); _controller.displayDate = _currentDate; @@ -3350,14 +3913,24 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { double top = 0, height; return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { + final double? previousWidth = _minWidth; + final double? previousHeight = _minHeight; _minWidth = constraints.maxWidth == double.infinity - ? _minWidth + ? _minPickerWidth : constraints.maxWidth; _minHeight = constraints.maxHeight == double.infinity - ? _minHeight + ? _minPickerHeight : constraints.maxHeight; - height = _minHeight - widget.headerHeight; + final double actionButtonsHeight = widget.showActionButtons + ? _minHeight! * 0.1 < 50 + ? 50 + : _minHeight! * 0.1 + : 0; + _handleScrollViewSizeChanged(_minHeight!, _minWidth!, previousHeight, + previousWidth, actionButtonsHeight); + + height = _minHeight! - widget.headerHeight; top = widget.headerHeight; if (_view == DateRangePickerView.month && widget.navigationDirection == @@ -3370,7 +3943,9 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { width: _minWidth, height: _minHeight, color: widget.backgroundColor ?? _datePickerTheme.backgroundColor, - child: _addChildren(top, height, _minWidth), + child: widget.navigationMode == DateRangePickerNavigationMode.scroll + ? _addScrollView(_minWidth!, _minHeight!, actionButtonsHeight) + : _addChildren(top, height, _minWidth!, actionButtonsHeight), ); }); } @@ -3415,45 +3990,45 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { void _pickerValueChangedListener(String value) { if (value == 'selectedDate') { - _raiseSelectionChangedCallback(widget, value: _controller.selectedDate); if (!mounted || isSameDate(_selectedDate, _controller.selectedDate)) { return; } + _raiseSelectionChangedCallback(widget, value: _controller.selectedDate); setState(() { _selectedDate = _controller.selectedDate; }); } else if (value == 'selectedDates') { - _raiseSelectionChangedCallback(widget, value: _controller.selectedDates); if (!mounted || DateRangePickerHelper.isDateCollectionEquals( _selectedDates, _controller.selectedDates)) { return; } + _raiseSelectionChangedCallback(widget, value: _controller.selectedDates); setState(() { _selectedDates = DateRangePickerHelper.cloneList(_controller.selectedDates); }); } else if (value == 'selectedRange') { - _raiseSelectionChangedCallback(widget, value: _controller.selectedRange); if (!mounted || DateRangePickerHelper.isRangeEquals( _selectedRange, _controller.selectedRange)) { return; } + _raiseSelectionChangedCallback(widget, value: _controller.selectedRange); setState(() { _selectedRange = _controller.selectedRange; }); } else if (value == 'selectedRanges') { - _raiseSelectionChangedCallback(widget, value: _controller.selectedRanges); if (!mounted || DateRangePickerHelper.isDateRangesEquals( _selectedRanges, _controller.selectedRanges)) { return; } + _raiseSelectionChangedCallback(widget, value: _controller.selectedRanges); setState(() { _selectedRanges = DateRangePickerHelper.cloneList(_controller.selectedRanges); @@ -3466,9 +4041,13 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { setState(() { _view = DateRangePickerHelper.getPickerView(_controller.view); - _scrollViewKey.currentState._position = 0.0; - _scrollViewKey.currentState._children.clear(); - _scrollViewKey.currentState._updateVisibleDates(); + if (widget.navigationMode == DateRangePickerNavigationMode.scroll) { + _initializeScrollView(); + } else { + _scrollViewKey.currentState!._position = 0.0; + _scrollViewKey.currentState!._children.clear(); + _scrollViewKey.currentState!._updateVisibleDates(); + } }); } else if (value == 'displayDate') { if (!isSameOrAfterDate(widget.minDate, _controller.displayDate)) { @@ -3494,7 +4073,11 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { setState(() { _currentDate = _controller.displayDate; - _updateCurrentVisibleDates(); + if (widget.navigationMode == DateRangePickerNavigationMode.scroll) { + _initializeScrollView(); + } else { + _updateCurrentVisibleDates(); + } }); } } @@ -3516,12 +4099,11 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { } else { final dynamic currentMonth = _currentViewVisibleDates[ _currentViewVisibleDates.length ~/ - (widget.enableMultiView ? 4 : 2)]; + (_isMultiViewEnabled(widget) ? 4 : 2)]; return date.month == currentMonth.month && date.year == currentMonth.year; } } - break; case DateRangePickerView.year: { final int currentYear = _currentViewVisibleDates[0].year; @@ -3545,8 +4127,6 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { return minYear <= year && maxYear >= year; } } - - return false; } void _updateCurrentVisibleDates() { @@ -3578,113 +4158,924 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { dynamic _updateCurrentDate(_SfDateRangePicker oldWidget) { if (oldWidget.controller == widget.controller && widget.controller != null && - oldWidget.controller.view == DateRangePickerView.month && + oldWidget.controller?.view == DateRangePickerView.month && DateRangePickerHelper.getPickerView(_controller.view) != DateRangePickerView.month) { - return _currentViewVisibleDates[ - _currentViewVisibleDates.length ~/ (widget.enableMultiView ? 4 : 2)]; + return _currentViewVisibleDates[_currentViewVisibleDates.length ~/ + (_isMultiViewEnabled(widget) ? 4 : 2)]; } return _currentViewVisibleDates[0]; } - Widget _addChildren(double top, double height, double width) { - _headerVisibleDates.value = _currentViewVisibleDates; - return Stack(children: [ - Positioned( - top: 0, - right: 0, - left: 0, - height: widget.headerHeight, - child: GestureDetector( - child: Container( - color: widget.headerStyle.backgroundColor ?? - _datePickerTheme.headerBackgroundColor, - child: _PickerHeaderView( - _headerVisibleDates, - widget.headerStyle, - widget.selectionMode, - _view, - DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri), - widget.showNavigationArrow, - widget.navigationDirection, - widget.monthViewSettings.enableSwipeSelection, - widget.minDate, - widget.maxDate, - widget.monthFormat, - _datePickerTheme, - _locale, - width, - widget.headerHeight, - widget.allowViewNavigation, - _controller.backward, - _controller.forward, - widget.enableMultiView, - widget.viewSpacing, - widget.selectionColor ?? _datePickerTheme.selectionColor, - _isRtl, - _textScaleFactor, - widget.isHijri, - _localizations), - height: widget.headerHeight, - ), - onTapUp: (TapUpDetails details) { - _updateCalendarTapCallbackForHeader(); - }, - ), - ), - _getViewHeaderView(), - Positioned( - top: top, - left: 0, - right: 0, - height: height, - child: _PickerScrollView( - widget, - _controller, - width, - height, - _isRtl, - _datePickerTheme, - _locale, - _textScaleFactor, - getPickerStateValues: (PickerStateArgs details) { - _getPickerStateValues(details); - }, - updatePickerStateValues: (PickerStateArgs details) { - _updatePickerStateValues(details); - }, - key: _scrollViewKey, - ), - ), - ]); + /// Initialize the scroll view on scroll navigation mode. + void _initializeScrollView() { + _forwardDateCollection.clear(); + _backwardDateCollection.clear(); + _scrollKey = UniqueKey(); + _pickerKey = UniqueKey(); } - Widget _getViewHeaderView() { - if (_view == DateRangePickerView.month && - widget.navigationDirection == - DateRangePickerNavigationDirection.vertical) { - final Color todayTextColor = - widget.monthCellStyle.todayTextStyle != null && - widget.monthCellStyle.todayTextStyle.color != null - ? widget.monthCellStyle.todayTextStyle.color - : (widget.todayHighlightColor != null && - widget.todayHighlightColor != Colors.transparent - ? widget.todayHighlightColor - : _datePickerTheme.todayHighlightColor); - return Positioned( - left: 0, - top: widget.headerHeight, - right: 0, - height: widget.monthViewSettings.viewHeaderHeight, - child: Container( - color: widget.monthViewSettings.viewHeaderStyle.backgroundColor ?? - _datePickerTheme.viewHeaderBackgroundColor, - child: RepaintBoundary( - child: CustomPaint( + /// Check the scroll navigation mode scroll view have before min date or + /// after max date views. + bool _isScrollViewDatesValid() { + if (_forwardDateCollection.isEmpty) { + return true; + } + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(_controller.view); + final int numberOfWeekInView = DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri); + final List startDates = _backwardDateCollection.isNotEmpty + ? _backwardDateCollection[_backwardDateCollection.length - 1] + : _forwardDateCollection[0]; + final List endDates = + _forwardDateCollection[_forwardDateCollection.length - 1]; + switch (view) { + case DateRangePickerView.month: + { + if (!widget.isHijri && numberOfWeekInView != 6) { + final DateTime visibleStartDate = startDates[startDates.length - 1]; + final DateTime visibleEndDate = endDates[0]; + return isDateWithInDateRange( + widget.minDate, widget.maxDate, visibleStartDate) && + isDateWithInDateRange( + widget.minDate, widget.maxDate, visibleEndDate); + } else { + final DateTime visibleStartDate = + startDates[startDates.length ~/ 2]; + final DateTime visibleEndDate = endDates[endDates.length ~/ 2]; + return (visibleStartDate.year > widget.minDate.year || + (visibleStartDate.year == widget.minDate.year && + visibleStartDate.month >= widget.minDate.month)) && + (visibleStartDate.year < widget.maxDate.year || + (visibleStartDate.year == widget.maxDate.year && + visibleStartDate.month <= widget.maxDate.month)) && + (visibleEndDate.year > widget.minDate.year || + (visibleEndDate.year == widget.minDate.year && + visibleEndDate.month >= widget.minDate.month)) && + (visibleEndDate.year < widget.maxDate.year || + (visibleEndDate.year == widget.maxDate.year && + visibleEndDate.month <= widget.maxDate.month)); + } + } + case DateRangePickerView.year: + { + final int visibleStartYear = startDates[0].year; + final int visibleEndYear = endDates[0].year; + return widget.minDate.year <= visibleStartYear && + widget.maxDate.year >= visibleStartYear && + widget.minDate.year <= visibleEndYear && + widget.maxDate.year >= visibleEndYear; + } + case DateRangePickerView.decade: + { + final int visibleStartYear = (startDates[0].year ~/ 10) * 10; + final int visibleEndYear = (endDates[0].year ~/ 10) * 10; + final int minDateYear = (widget.minDate.year ~/ 10) * 10; + final int maxDateYear = (widget.maxDate.year ~/ 10) * 10; + return minDateYear <= visibleStartYear && + maxDateYear >= visibleStartYear && + minDateYear <= visibleEndYear && + maxDateYear >= visibleEndYear; + } + case DateRangePickerView.century: + { + final int visibleStartYear = (startDates[0].year ~/ 100) * 100; + final int visibleEndYear = (endDates[0].year ~/ 100) * 100; + final int minDateYear = (widget.minDate.year ~/ 100) * 100; + final int maxDateYear = (widget.maxDate.year ~/ 100) * 100; + return minDateYear <= visibleStartYear && + maxDateYear >= visibleStartYear && + minDateYear <= visibleEndYear && + maxDateYear >= visibleEndYear; + } + } + } + + /// Handle the control size changed related view updates on scroll navigation + /// mode. + void _handleScrollViewSizeChanged(double newHeight, double newWidth, + double? oldHeight, double? oldWidth, double actionButtonHeight) { + if (widget.navigationMode != DateRangePickerNavigationMode.scroll || + _pickerScrollController == null || + !_pickerScrollController!.hasClients) { + return; + } + + if (oldWidth != null && + widget.navigationDirection == + DateRangePickerNavigationDirection.horizontal && + oldWidth != newWidth) { + final double index = _pickerScrollController!.position.pixels / oldWidth; + _pickerScrollController!.removeListener(_handleScrollChanged); + _pickerScrollController!.dispose(); + _scrollKey = UniqueKey(); + _pickerKey = UniqueKey(); + _pickerScrollController = + ScrollController(initialScrollOffset: index * newWidth) + ..addListener(_handleScrollChanged); + } else if (oldHeight != null && + widget.navigationDirection == + DateRangePickerNavigationDirection.vertical && + oldHeight != newHeight) { + final double viewHeaderHeight = _view == DateRangePickerView.month + ? widget.monthViewSettings.viewHeaderHeight + : 0; + final double viewSize = oldHeight - viewHeaderHeight - actionButtonHeight; + final double index = _pickerScrollController!.position.pixels / viewSize; + _pickerScrollController!.removeListener(_handleScrollChanged); + _pickerScrollController!.dispose(); + _scrollKey = UniqueKey(); + _pickerKey = UniqueKey(); + _pickerScrollController = ScrollController( + initialScrollOffset: + index * (newHeight - viewHeaderHeight - actionButtonHeight)) + ..addListener(_handleScrollChanged); + } + } + + /// handle the scroll navigation mode scroll view scroll changed. + void _handleScrollChanged() { + final double scrolledPosition = _pickerScrollController!.position.pixels; + final double actionButtonsHeight = widget.showActionButtons + ? _minHeight! * 0.1 < 50 + ? 50 + : _minHeight! * 0.1 + : 0; + double widgetSize = widget.navigationDirection == + DateRangePickerNavigationDirection.horizontal + ? _minWidth! + : _minHeight! - + (_view == DateRangePickerView.month + ? widget.monthViewSettings.viewHeaderHeight + : 0) - + actionButtonsHeight; + if (widget.enableMultiView) { + widgetSize /= 2; + } + + /// Check the current visible date collection and existing visible date + /// collection is equal or not. + bool isViewChanged = false; + List visibleDates; + if (scrolledPosition >= 0) { + final int index = scrolledPosition ~/ widgetSize; + if (index >= _forwardDateCollection.length) { + return; + } + + visibleDates = _forwardDateCollection[index]; + if (isSameDate(_currentViewVisibleDates[0], visibleDates[0])) { + return; + } + + isViewChanged = true; + } else { + final int index = -(scrolledPosition ~/ widgetSize); + if (index >= _backwardDateCollection.length) { + return; + } + + visibleDates = _backwardDateCollection[index]; + if (isSameDate(_currentViewVisibleDates[0], visibleDates[0])) { + return; + } + + isViewChanged = true; + } + + if (!isViewChanged) { + return; + } + + dynamic currentDate = visibleDates[0]; + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri); + if (_view == DateRangePickerView.month && + (numberOfWeeksInView == 6 || widget.isHijri)) { + final dynamic date = visibleDates[visibleDates.length ~/ 2]; + currentDate = DateRangePickerHelper.getDate( + date.year, date.month, 1, widget.isHijri); + } + + _currentDate = getValidDate(widget.minDate, widget.maxDate, currentDate); + _controller.displayDate = _currentDate; + _currentViewVisibleDates = visibleDates; + _notifyCurrentVisibleDatesChanged(); + } + + /// Calculate and add the visible date collection for scroll view based on + /// [isNextView] value. + void _addScrollViewDateCollection( + List dateCollection, + bool isNextView, + dynamic startDate, + DateRangePickerView currentView, + int numberOfWeeksInView, + int visibleDatesCount) { + int count = 0; + dynamic visibleDate = startDate; + while (count < 10) { + switch (currentView) { + case DateRangePickerView.month: + { + final List visibleDates = getVisibleDates( + visibleDate, + null, + widget.monthViewSettings.firstDayOfWeek, + visibleDatesCount, + ); + + if (isNextView) { + if (!widget.isHijri && numberOfWeeksInView != 6) { + final dynamic date = visibleDates[0]; + if (!isSameOrBeforeDate(widget.maxDate, date)) { + count = 10; + break; + } + } else { + final dynamic date = visibleDates[visibleDates.length ~/ 2]; + if (((date.month > widget.maxDate.month && + date.year == widget.maxDate.year) || + date.year > widget.maxDate.year)) { + count = 10; + break; + } + } + } else { + if (numberOfWeeksInView != 6 && !widget.isHijri) { + final dynamic date = visibleDates[visibleDates.length - 1]; + if (!isSameOrAfterDate(widget.minDate, date)) { + count = 10; + break; + } + } else { + final dynamic date = visibleDates[visibleDates.length ~/ 2]; + if (((date.month < widget.minDate.month && + date.year == widget.minDate.year) || + date.year < widget.minDate.year)) { + count = 10; + break; + } + } + } + + dateCollection.add(visibleDates); + if (isNextView) { + visibleDate = DateRangePickerHelper.getNextViewStartDate( + currentView, + numberOfWeeksInView, + visibleDate, + false, + widget.isHijri); + } else { + visibleDate = DateRangePickerHelper.getPreviousViewStartDate( + currentView, + numberOfWeeksInView, + visibleDate, + false, + widget.isHijri); + } + count++; + } + break; + case DateRangePickerView.decade: + case DateRangePickerView.year: + case DateRangePickerView.century: + { + if (isNextView) { + final int currentYear = visibleDate.year; + final int maxYear = widget.maxDate.year; + final int offset = DateRangePickerHelper.getOffset(currentView); + if (((currentYear ~/ offset) * offset) > + ((maxYear ~/ offset) * offset)) { + count = 10; + break; + } + } else { + final int currentYear = visibleDate.year; + final int minYear = widget.minDate.year; + final int offset = DateRangePickerHelper.getOffset(currentView); + if (((currentYear ~/ offset) * offset) < + ((minYear ~/ offset) * offset)) { + count = 10; + break; + } + } + + final List visibleDates = DateRangePickerHelper.getVisibleYearDates( + visibleDate, + currentView, + widget.isHijri, + ); + + dateCollection.add(visibleDates); + if (isNextView) { + visibleDate = DateRangePickerHelper.getNextViewStartDate( + currentView, + numberOfWeeksInView, + visibleDate, + false, + widget.isHijri); + } else { + visibleDate = DateRangePickerHelper.getPreviousViewStartDate( + currentView, + numberOfWeeksInView, + visibleDate, + false, + widget.isHijri); + } + count++; + } + break; + } + } + } + + Widget _addScrollView( + double width, double height, double actionButtonsHeight) { + _pickerScrollController ??= ScrollController() + ..addListener(_handleScrollChanged); + final DateRangePickerView currentView = + DateRangePickerHelper.getPickerView(_view); + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri); + final int visibleDatesCount = DateRangePickerHelper.getViewDatesCount( + currentView, numberOfWeeksInView, widget.isHijri); + final bool isInitialLoading = _forwardDateCollection.isEmpty; + if (isInitialLoading) { + _addScrollViewDateCollection(_forwardDateCollection, true, _currentDate, + currentView, numberOfWeeksInView, visibleDatesCount); + } + + if (_backwardDateCollection.isEmpty) { + final List? lastViewDates = _forwardDateCollection[0]; + dynamic visibleDate = + currentView == DateRangePickerView.month && numberOfWeeksInView != 6 + ? lastViewDates != null && lastViewDates.isNotEmpty + ? lastViewDates[0] + : _currentDate + : lastViewDates != null && lastViewDates.isNotEmpty + ? lastViewDates[lastViewDates.length ~/ 2] + : _currentDate; + visibleDate = DateRangePickerHelper.getPreviousViewStartDate( + currentView, numberOfWeeksInView, visibleDate, false, widget.isHijri); + _addScrollViewDateCollection(_backwardDateCollection, false, visibleDate, + currentView, numberOfWeeksInView, visibleDatesCount); + } + + int forwardCollectionLength = _forwardDateCollection.length; + final int minForwardCollectionLength = widget.enableMultiView ? 2 : 1; + + /// Check the current view have valid views. + /// for eg., if [enableMultiView] enabled and max date is today then + /// current view split into two and render first half with today date + /// month and second half shown empty space. + while (_backwardDateCollection.isNotEmpty && + forwardCollectionLength < minForwardCollectionLength) { + _forwardDateCollection.insert(0, _backwardDateCollection[0]); + _backwardDateCollection.removeAt(0); + forwardCollectionLength += 1; + } + + if (isInitialLoading) { + _currentViewVisibleDates = _forwardDateCollection[0]; + _notifyCurrentVisibleDatesChanged(); + } + + final bool isHorizontal = widget.navigationDirection == + DateRangePickerNavigationDirection.horizontal; + final double topPosition = + (_view == DateRangePickerView.month && !isHorizontal + ? widget.monthViewSettings.viewHeaderHeight + : 0.0); + final double scrollViewHeight = height - topPosition - actionButtonsHeight; + double scrollViewItemHeight = scrollViewHeight; + double scrollViewItemWidth = width; + if (isHorizontal) { + scrollViewItemWidth = widget.enableMultiView + ? scrollViewItemWidth / 2 + : scrollViewItemWidth; + } else { + scrollViewItemHeight = widget.enableMultiView + ? scrollViewItemHeight / 2 + : scrollViewItemHeight; + } + + final Widget scrollView = CustomScrollView( + scrollDirection: isHorizontal ? Axis.horizontal : Axis.vertical, + key: _scrollKey, + physics: const AlwaysScrollableScrollPhysics( + parent: + ClampingScrollPhysics(parent: RangeMaintainingScrollPhysics())), + controller: _pickerScrollController, + center: _pickerKey, + slivers: [ + SliverFixedExtentList( + itemExtent: isHorizontal ? scrollViewItemWidth : scrollViewItemHeight, + delegate: + SliverChildBuilderDelegate((BuildContext context, int index) { + if (_backwardDateCollection.length <= index) { + return null; + } + + /// Send negative index value to differentiate the + /// backward view from forward view. + return _getScrollViewItem( + -(index + 1), + scrollViewItemWidth, + scrollViewItemHeight, + _backwardDateCollection[index], + isHorizontal); + }), + ), + SliverFixedExtentList( + itemExtent: isHorizontal ? scrollViewItemWidth : scrollViewItemHeight, + delegate: + SliverChildBuilderDelegate((BuildContext context, int index) { + if (_forwardDateCollection.length <= index) { + return null; + } + + return _getScrollViewItem( + index, + scrollViewItemWidth, + scrollViewItemHeight, + _forwardDateCollection[index], + isHorizontal); + }), + key: _pickerKey, + ), + ], + ); + + if (isHorizontal) { + return Stack( + children: [ + scrollView, + _getActionsButton(topPosition + scrollViewHeight, actionButtonsHeight) + ], + ); + } else { + return Stack(children: [ + _getViewHeaderView(0), + Positioned( + left: 0, + top: topPosition, + right: 0, + height: scrollViewHeight, + child: scrollView), + _getActionsButton(topPosition + scrollViewHeight, actionButtonsHeight) + ]); + } + } + + /// Return widget that placed on scroll view when navigation mode is scroll. + Widget _getScrollViewItem( + int index, double width, double height, List dates, bool isHorizontal) { + final DateRangePickerView currentView = + DateRangePickerHelper.getPickerView(_view); + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri); + final int visibleDatesCount = DateRangePickerHelper.getViewDatesCount( + currentView, numberOfWeeksInView, widget.isHijri); + if (index >= 0) { + if (_forwardDateCollection.isNotEmpty && + index > _forwardDateCollection.length - 2) { + final List lastViewDates = + _forwardDateCollection[_forwardDateCollection.length - 1]; + dynamic date = currentView == DateRangePickerView.month && + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri) != + 6 + ? lastViewDates[0] + : lastViewDates[lastViewDates.length ~/ 2]; + date = DateRangePickerHelper.getNextViewStartDate( + currentView, numberOfWeeksInView, date, false, widget.isHijri); + _addScrollViewDateCollection(_forwardDateCollection, true, date, + currentView, numberOfWeeksInView, visibleDatesCount); + } + } else { + if (_backwardDateCollection.isNotEmpty && + -index > _backwardDateCollection.length - 2) { + final List lastViewDates = + _backwardDateCollection[_backwardDateCollection.length - 1]; + dynamic date = currentView == DateRangePickerView.month && + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri) != + 6 + ? lastViewDates[0] + : lastViewDates[lastViewDates.length ~/ 2]; + date = DateRangePickerHelper.getPreviousViewStartDate( + currentView, numberOfWeeksInView, date, false, widget.isHijri); + _addScrollViewDateCollection(_backwardDateCollection, false, date, + currentView, numberOfWeeksInView, visibleDatesCount); + } + } + + final double pickerHeight = height - widget.headerHeight; + final double pickerWidth = width - (isHorizontal ? 1 : 0); + double headerWidth = pickerWidth; + if (isHorizontal) { + final String headerText = _getHeaderText( + dates, + _view, + 0, + widget.isHijri, + numberOfWeeksInView, + widget.monthFormat, + false, + widget.headerStyle, + widget.navigationDirection, + _locale, + _localizations); + headerWidth = _getTextWidgetWidth( + headerText, widget.headerHeight, pickerWidth, context, + style: widget.headerStyle.textStyle ?? + _datePickerTheme.headerTextStyle, + widthPadding: 20) + .width; + } + + if (headerWidth > pickerWidth) { + headerWidth = pickerWidth; + } + + Color backgroundColor = widget.headerStyle.backgroundColor ?? + _datePickerTheme.headerBackgroundColor; + if (!isHorizontal && backgroundColor == Colors.transparent) { + backgroundColor = _datePickerTheme.brightness == Brightness.dark + ? Colors.grey[850]! + : Colors.white; + } + final Widget header = Positioned( + top: 0, + left: 0, + width: headerWidth, + height: widget.headerHeight, + child: GestureDetector( + child: Container( + color: backgroundColor, + child: _PickerHeaderView( + ValueNotifier>(dates), + widget.headerStyle, + widget.selectionMode, + _view, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri), + widget.showNavigationArrow, + widget.navigationDirection, + widget.monthViewSettings.enableSwipeSelection, + widget.navigationMode, + widget.minDate, + widget.maxDate, + widget.monthFormat, + _datePickerTheme, + _locale, + headerWidth, + widget.headerHeight, + widget.allowViewNavigation, + _controller.backward, + _controller.forward, + _isMultiViewEnabled(widget), + widget.viewSpacing, + widget.selectionColor ?? _datePickerTheme.selectionColor!, + _isRtl, + _textScaleFactor, + widget.isHijri, + _localizations), + height: widget.headerHeight, + ), + onTapUp: (TapUpDetails details) { + if (_view == DateRangePickerView.century || + !widget.allowViewNavigation) { + return; + } + + /// Get the current tapped view date. + dynamic currentDate = dates[0]; + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri); + if (_view == DateRangePickerView.month && + (numberOfWeeksInView == 6 || widget.isHijri)) { + final dynamic date = dates[dates.length ~/ 2]; + currentDate = DateRangePickerHelper.getDate( + date.year, date.month, 1, widget.isHijri); + } + + currentDate = + getValidDate(widget.minDate, widget.maxDate, currentDate); + + /// Check the moved view visible date not contains tapped + /// header date + /// Eg., If you scroll to place the month view with Dec 2020 + /// and Jan 2021 then it current visible view date is Dec 2020 + /// and tap the Jan 2021 then it moved to year view 2020. So + /// check the tapped date's (Jan 2021) year is current display + /// date year or not. if not then update the display date value. + if ((_view == DateRangePickerView.month && + _currentDate.year != currentDate.year) || + (_view == DateRangePickerView.year && + _currentDate.year ~/ 10 != currentDate.year ~/ 10) || + (_view == DateRangePickerView.decade && + _currentDate.year ~/ 100 != currentDate.year ~/ 100)) { + _currentDate = currentDate; + _controller.displayDate = _currentDate; + } + _updateCalendarTapCallbackForHeader(); + }, + ), + ); + final Widget pickerView = Positioned( + top: widget.headerHeight, + left: 0, + width: pickerWidth, + height: pickerHeight, + child: _PickerView( + widget, + _controller, + dates, + _isMultiViewEnabled(widget), + pickerWidth, + pickerHeight, + _datePickerTheme, + null, + _textScaleFactor, + getPickerStateDetails: _getPickerStateValues, + updatePickerStateDetails: _updatePickerStateValues, + isRtl: _isRtl, + ), + ); + + final List children = [pickerView]; + if (isHorizontal) { + children.add(Positioned( + top: 0, + left: pickerWidth, + width: 1, + height: height, + child: VerticalDivider( + thickness: 1, + ), + )); + } + + children.add(header); + return Container( + width: width, + height: height, + child: _StickyHeader( + isHorizontal: isHorizontal, + isRTL: _isRtl, + children: children, + )); + } + + Widget _addChildren( + double top, double height, double width, double actionButtonsHeight) { + _headerVisibleDates.value = _currentViewVisibleDates; + height -= actionButtonsHeight; + return Stack(children: [ + Positioned( + top: 0, + right: 0, + left: 0, + height: widget.headerHeight, + child: GestureDetector( + child: Container( + color: widget.headerStyle.backgroundColor ?? + _datePickerTheme.headerBackgroundColor, + child: _PickerHeaderView( + _headerVisibleDates, + widget.headerStyle, + widget.selectionMode, + _view, + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri), + widget.showNavigationArrow, + widget.navigationDirection, + widget.monthViewSettings.enableSwipeSelection, + widget.navigationMode, + widget.minDate, + widget.maxDate, + widget.monthFormat, + _datePickerTheme, + _locale, + width, + widget.headerHeight, + widget.allowViewNavigation, + _controller.backward, + _controller.forward, + _isMultiViewEnabled(widget), + widget.viewSpacing, + widget.selectionColor ?? _datePickerTheme.selectionColor!, + _isRtl, + _textScaleFactor, + widget.isHijri, + _localizations), + height: widget.headerHeight, + ), + onTapUp: (TapUpDetails details) { + _updateCalendarTapCallbackForHeader(); + }, + ), + ), + _getViewHeaderView(widget.headerHeight), + Positioned( + top: top, + left: 0, + right: 0, + height: height, + child: _PickerScrollView( + widget, + _controller, + width, + height, + _isRtl, + _datePickerTheme, + _locale, + _textScaleFactor, + getPickerStateValues: (PickerStateArgs details) { + _getPickerStateValues(details); + }, + updatePickerStateValues: (PickerStateArgs details) { + _updatePickerStateValues(details); + }, + key: _scrollViewKey, + ), + ), + _getActionsButton(top + height, actionButtonsHeight) + ]); + } + + Widget _getActionsButton(double top, double actionButtonsHeight) { + if (!widget.showActionButtons) { + return Container(width: 0, height: 0); + } + + Color textColor = + widget.todayHighlightColor ?? _datePickerTheme.todayHighlightColor!; + if (textColor == Colors.transparent) { + final TextStyle style = widget.monthCellStyle.todayTextStyle ?? + _datePickerTheme.todayTextStyle; + textColor = style.color != null ? style.color! : Colors.blue; + } + + return Positioned( + top: top, + left: 0, + right: 0, + height: actionButtonsHeight, + child: Container( + alignment: AlignmentDirectional.centerEnd, + constraints: const BoxConstraints(minHeight: 52.0), + padding: const EdgeInsets.symmetric(horizontal: 8), + child: OverflowBar( + spacing: 8, + children: [ + TextButton( + child: Text( + widget.cancelText, + style: TextStyle(color: textColor), + ), + onPressed: _handleCancel, + ), + TextButton( + child: Text( + widget.confirmText, + style: TextStyle(color: textColor), + ), + onPressed: _handleOk, + ), + ], + ), + ), + ); + } + + void _handleCancel() { + switch (widget.selectionMode) { + case DateRangePickerSelectionMode.single: + { + _selectedDate = _previousSelectedValue.selectedDate ?? null; + if (!isSameDate(_controller.selectedDate, _selectedDate)) { + setState(() { + _controller.selectedDate = _selectedDate; + }); + } + } + break; + case DateRangePickerSelectionMode.multiple: + { + _selectedDates = _previousSelectedValue.selectedDates != null + ? _getSelectedDates(_previousSelectedValue.selectedDates) + : null; + if (!DateRangePickerHelper.isDateCollectionEquals( + _selectedDates, _controller.selectedDates)) { + setState(() { + _controller.selectedDates = + _previousSelectedValue.selectedDates != null + ? _getSelectedDates(_previousSelectedValue.selectedDates) + : null; + }); + } + } + break; + case DateRangePickerSelectionMode.range: + { + _selectedRange = _previousSelectedValue.selectedRange ?? null; + if (!DateRangePickerHelper.isRangeEquals( + _selectedRange, _controller.selectedRange)) { + setState(() { + _controller.selectedRange = _selectedRange; + }); + } + } + break; + case DateRangePickerSelectionMode.multiRange: + { + _selectedRanges = _previousSelectedValue.selectedRanges != null + ? _getSelectedRanges(_previousSelectedValue.selectedRanges) + : null; + if (!DateRangePickerHelper.isDateRangesEquals( + _selectedRanges, _controller.selectedRanges)) { + setState(() { + _controller.selectedRanges = _previousSelectedValue + .selectedRanges != + null + ? _getSelectedRanges(_previousSelectedValue.selectedRanges) + : null; + }); + } + } + } + + widget.onCancel?.call(); + } + + void _handleOk() { + dynamic value = null; + switch (widget.selectionMode) { + case DateRangePickerSelectionMode.single: + { + value = _selectedDate; + _previousSelectedValue.selectedDate = _selectedDate; + } + break; + case DateRangePickerSelectionMode.multiple: + { + value = _getSelectedDates(_selectedDates); + _previousSelectedValue.selectedDates = + _getSelectedDates(_selectedDates); + } + break; + case DateRangePickerSelectionMode.range: + { + value = _selectedRange; + _previousSelectedValue.selectedRange = _selectedRange; + } + break; + case DateRangePickerSelectionMode.multiRange: + { + value = _getSelectedRanges(_selectedRanges); + _previousSelectedValue.selectedRanges = + _getSelectedRanges(_selectedRanges); + } + } + + widget.onSubmit?.call(value); + } + + Widget _getViewHeaderView(double topPosition) { + if (_view == DateRangePickerView.month && + widget.navigationDirection == + DateRangePickerNavigationDirection.vertical) { + final Color todayTextColor = + widget.monthCellStyle.todayTextStyle != null && + widget.monthCellStyle.todayTextStyle!.color != null + ? widget.monthCellStyle.todayTextStyle!.color! + : (widget.todayHighlightColor != null && + widget.todayHighlightColor! != Colors.transparent + ? widget.todayHighlightColor! + : _datePickerTheme.todayHighlightColor!); + return Positioned( + left: 0, + top: topPosition, + right: 0, + height: widget.monthViewSettings.viewHeaderHeight, + child: Container( + color: widget.monthViewSettings.viewHeaderStyle.backgroundColor ?? + _datePickerTheme.viewHeaderBackgroundColor, + child: RepaintBoundary( + child: CustomPaint( painter: _PickerViewHeaderPainter( _currentViewVisibleDates, + widget.navigationMode, widget.monthViewSettings.viewHeaderStyle, widget.monthViewSettings.viewHeaderHeight, widget.monthViewSettings, @@ -3692,7 +5083,7 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { _locale, _isRtl, widget.monthCellStyle, - widget.enableMultiView, + _isMultiViewEnabled(widget), widget.viewSpacing, todayTextColor, _textScaleFactor, @@ -3708,37 +5099,43 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { } void _moveToNextView() { + if (widget.navigationMode == DateRangePickerNavigationMode.scroll) { + return; + } if (!DateRangePickerHelper.canMoveToNextView( _view, DateRangePickerHelper.getNumberOfWeeksInView( widget.monthViewSettings, widget.isHijri), widget.maxDate, _currentViewVisibleDates, - widget.enableMultiView, + _isMultiViewEnabled(widget), widget.isHijri)) { return; } _isRtl - ? _scrollViewKey.currentState._moveToPreviousViewWithAnimation() - : _scrollViewKey.currentState._moveToNextViewWithAnimation(); + ? _scrollViewKey.currentState!._moveToPreviousViewWithAnimation() + : _scrollViewKey.currentState!._moveToNextViewWithAnimation(); } void _moveToPreviousView() { + if (widget.navigationMode == DateRangePickerNavigationMode.scroll) { + return; + } if (!DateRangePickerHelper.canMoveToPreviousView( _view, DateRangePickerHelper.getNumberOfWeeksInView( widget.monthViewSettings, widget.isHijri), widget.minDate, _currentViewVisibleDates, - widget.enableMultiView, + _isMultiViewEnabled(widget), widget.isHijri)) { return; } _isRtl - ? _scrollViewKey.currentState._moveToNextViewWithAnimation() - : _scrollViewKey.currentState._moveToPreviousViewWithAnimation(); + ? _scrollViewKey.currentState!._moveToNextViewWithAnimation() + : _scrollViewKey.currentState!._moveToPreviousViewWithAnimation(); } void _getPickerStateValues(PickerStateArgs details) { @@ -3747,6 +5144,7 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { details.selectedDates = _selectedDates; details.selectedRange = _selectedRange; details.selectedRanges = _selectedRanges; + details.currentViewVisibleDates = _currentViewVisibleDates; details.view = DateRangePickerHelper.getPickerView(_view); } @@ -3764,95 +5162,13 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { _controller.displayDate = _currentDate; } - if (details.currentViewVisibleDates != null && - _currentViewVisibleDates != details.currentViewVisibleDates) { + if (_currentViewVisibleDates != details.currentViewVisibleDates) { _currentViewVisibleDates = details.currentViewVisibleDates; _headerVisibleDates.value = _currentViewVisibleDates; - final DateRangePickerView view = - DateRangePickerHelper.getPickerView(_controller.view); - dynamic visibleDateRange; - switch (view) { - case DateRangePickerView.month: - { - if (widget.isHijri || - (!DateRangePickerHelper.canShowLeadingAndTrailingDates( - widget.monthViewSettings, widget.isHijri) && - DateRangePickerHelper.getNumberOfWeeksInView( - widget.monthViewSettings, widget.isHijri) == - 6)) { - final dynamic visibleDate = _currentViewVisibleDates[ - _currentViewVisibleDates.length ~/ - (widget.enableMultiView ? 4 : 2)]; - if (widget.isHijri) { - visibleDateRange = HijriDateRange( - DateRangePickerHelper.getMonthStartDate( - visibleDate, widget.isHijri), - widget.enableMultiView - ? DateRangePickerHelper.getMonthEndDate( - DateRangePickerHelper.getNextViewStartDate( - DateRangePickerHelper.getPickerView( - _controller.view), - 6, - visibleDate, - _isRtl, - widget.isHijri)) - : DateRangePickerHelper.getMonthEndDate(visibleDate)); - } else { - visibleDateRange = PickerDateRange( - DateRangePickerHelper.getMonthStartDate( - visibleDate, widget.isHijri), - widget.enableMultiView - ? DateRangePickerHelper.getMonthEndDate( - DateRangePickerHelper.getNextViewStartDate( - DateRangePickerHelper.getPickerView( - _controller.view), - 6, - visibleDate, - _isRtl, - widget.isHijri)) - : DateRangePickerHelper.getMonthEndDate(visibleDate)); - } - _raisePickerViewChangedCallback(widget, - visibleDateRange: visibleDateRange, view: _controller.view); - } else { - if (widget.isHijri) { - visibleDateRange = HijriDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1]); - } else { - visibleDateRange = PickerDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1]); - } - _raisePickerViewChangedCallback(widget, - visibleDateRange: visibleDateRange, view: _controller.view); - } - } - break; - case DateRangePickerView.year: - case DateRangePickerView.decade: - case DateRangePickerView.century: - { - if (widget.isHijri) { - visibleDateRange = HijriDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1]); - } else { - visibleDateRange = PickerDateRange( - _currentViewVisibleDates[0], - _currentViewVisibleDates[ - _currentViewVisibleDates.length - 1]); - } - _raisePickerViewChangedCallback(widget, - visibleDateRange: visibleDateRange, view: _controller.view); - } - } + _notifyCurrentVisibleDatesChanged(); } - if (details.view != null && _view != details.view) { + if (_view != details.view) { _controller.view = widget.isHijri ? DateRangePickerHelper.getHijriPickerView(details.view) : DateRangePickerHelper.getPickerView(details.view); @@ -3863,93 +5179,448 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { case DateRangePickerSelectionMode.single: { _selectedDate = details.selectedDate; + final bool isSameSelectedDate = + isSameDate(_controller.selectedDate, _selectedDate); + if (widget.navigationMode == DateRangePickerNavigationMode.scroll && + !isSameSelectedDate) { + setState(() { + /// Update selection views for scroll navigation mode. + }); + } + _controller.selectedDate = _selectedDate; + if (!isSameSelectedDate) { + _raiseSelectionChangedCallback(widget, + value: _controller.selectedDate); + } } break; case DateRangePickerSelectionMode.multiple: { _selectedDates = details.selectedDates; + final bool isSameSelectedDate = + DateRangePickerHelper.isDateCollectionEquals( + _selectedDates, _controller.selectedDates); + if (widget.navigationMode == DateRangePickerNavigationMode.scroll && + !isSameSelectedDate) { + setState(() { + /// Update selection views for scroll navigation mode. + }); + } + _controller.selectedDates = _getSelectedDates(_selectedDates); + if (!isSameSelectedDate) + _raiseSelectionChangedCallback(widget, + value: _controller.selectedDates); } break; case DateRangePickerSelectionMode.range: { _selectedRange = details.selectedRange; + final bool isSameSelectedDate = DateRangePickerHelper.isRangeEquals( + _selectedRange, _controller.selectedRange); + if (widget.navigationMode == DateRangePickerNavigationMode.scroll && + !isSameSelectedDate) { + setState(() { + /// Update selection views for scroll navigation mode. + }); + } + _controller.selectedRange = _selectedRange; + if (!isSameSelectedDate) + _raiseSelectionChangedCallback(widget, + value: _controller.selectedRange); } break; case DateRangePickerSelectionMode.multiRange: { _selectedRanges = details.selectedRanges; + final bool isSameSelectedDate = + DateRangePickerHelper.isDateRangesEquals( + _selectedRanges, _controller.selectedRanges); + if (widget.navigationMode == DateRangePickerNavigationMode.scroll && + !isSameSelectedDate) { + setState(() { + /// Update selection views for scroll navigation mode. + }); + } + _controller.selectedRanges = _getSelectedRanges(_selectedRanges); + if (!isSameSelectedDate) + _raiseSelectionChangedCallback(widget, + value: _controller.selectedRanges); + } + } + } + } + + /// Used to call the view changed callback when [_currentViewVisibleDates] + /// changed. + void _notifyCurrentVisibleDatesChanged() { + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(_controller.view); + dynamic visibleDateRange; + switch (view) { + case DateRangePickerView.month: + { + final bool enableMultiView = _isMultiViewEnabled(widget); + if (widget.isHijri || + (!DateRangePickerHelper.canShowLeadingAndTrailingDates( + widget.monthViewSettings, widget.isHijri) && + DateRangePickerHelper.getNumberOfWeeksInView( + widget.monthViewSettings, widget.isHijri) == + 6)) { + final dynamic visibleDate = _currentViewVisibleDates[ + _currentViewVisibleDates.length ~/ (enableMultiView ? 4 : 2)]; + if (widget.isHijri) { + visibleDateRange = HijriDateRange( + DateRangePickerHelper.getMonthStartDate( + visibleDate, widget.isHijri), + enableMultiView + ? DateRangePickerHelper.getMonthEndDate( + DateRangePickerHelper.getNextViewStartDate( + DateRangePickerHelper.getPickerView( + _controller.view), + 6, + visibleDate, + _isRtl, + widget.isHijri)) + : DateRangePickerHelper.getMonthEndDate(visibleDate)); + } else { + visibleDateRange = PickerDateRange( + DateRangePickerHelper.getMonthStartDate( + visibleDate, widget.isHijri), + enableMultiView + ? DateRangePickerHelper.getMonthEndDate( + DateRangePickerHelper.getNextViewStartDate( + DateRangePickerHelper.getPickerView( + _controller.view), + 6, + visibleDate, + _isRtl, + widget.isHijri)) + : DateRangePickerHelper.getMonthEndDate(visibleDate)); + } + _raisePickerViewChangedCallback(widget, + visibleDateRange: visibleDateRange, view: _controller.view); + } else { + if (widget.isHijri) { + visibleDateRange = HijriDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[ + _currentViewVisibleDates.length - 1]); + } else { + visibleDateRange = PickerDateRange( + _currentViewVisibleDates[0], + _currentViewVisibleDates[ + _currentViewVisibleDates.length - 1]); + } + _raisePickerViewChangedCallback(widget, + visibleDateRange: visibleDateRange, view: _controller.view); } + } + break; + case DateRangePickerView.year: + case DateRangePickerView.decade: + case DateRangePickerView.century: + { + if (widget.isHijri) { + visibleDateRange = HijriDateRange(_currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - 1]); + } else { + visibleDateRange = PickerDateRange(_currentViewVisibleDates[0], + _currentViewVisibleDates[_currentViewVisibleDates.length - 1]); + } + _raisePickerViewChangedCallback(widget, + visibleDateRange: visibleDateRange, view: _controller.view); + } + } + } + + /// returns the selected ranges in the required type list. + List? _getSelectedRanges(List? ranges) { + if (ranges == null) { + return ranges; + } + + List selectedRanges; + if (widget.isHijri) { + selectedRanges = []; + } else { + selectedRanges = []; + } + + for (int i = 0; i < ranges.length; i++) { + selectedRanges.add(ranges[i]); + } + + return selectedRanges; + } + + /// returns the selected dates in the required type list + List? _getSelectedDates(List? dates) { + if (dates == null) { + return dates; + } + + List selectedDates; + if (widget.isHijri) { + selectedDates = []; + } else { + selectedDates = []; + } + + for (int i = 0; i < dates.length; i++) { + selectedDates.add(dates[i]); + } + + return selectedDates; + } + + // method to update the picker tapped call back for the header view + void _updateCalendarTapCallbackForHeader() { + if (_view == DateRangePickerView.century || !widget.allowViewNavigation) { + return; + } + + if (_view == DateRangePickerView.month) { + _controller.view = widget.isHijri + ? DateRangePickerHelper.getHijriPickerView(DateRangePickerView.year) + : DateRangePickerHelper.getPickerView(DateRangePickerView.year); + } else { + if (_view == DateRangePickerView.year) { + _controller.view = widget.isHijri + ? DateRangePickerHelper.getHijriPickerView( + DateRangePickerView.decade) + : DateRangePickerHelper.getPickerView(DateRangePickerView.decade); + } else if (_view == DateRangePickerView.decade) { + _controller.view = widget.isHijri + ? DateRangePickerHelper.getHijriPickerView( + DateRangePickerView.century) + : DateRangePickerHelper.getPickerView(DateRangePickerView.century); } } } +} + +/// Holds content and header to show header like sticky based on content. +class _StickyHeader extends Stack { + _StickyHeader({ + required List children, + AlignmentDirectional alignment = AlignmentDirectional.topStart, + bool isHorizontal = false, + bool isRTL = false, + Key? key, + }) : isHorizontal = isHorizontal, + isRTL = isRTL, + super( + key: key, + children: children, + alignment: alignment, + ); + + final bool isHorizontal; + final bool isRTL; + + @override + RenderStack createRenderObject(BuildContext context) => + _StickyHeaderRenderObject( + scrollableState: Scrollable.of(context)!, + alignment: alignment, + textDirection: textDirection ?? Directionality.of(context), + fit: fit, + isHorizontal: isHorizontal, + isRTL: isRTL, + ); + + @override + @mustCallSuper + void updateRenderObject(BuildContext context, RenderStack renderObject) { + super.updateRenderObject(context, renderObject); + + if (renderObject is _StickyHeaderRenderObject) { + renderObject + ..scrollableState = Scrollable.of(context)! + ..isRTL = isRTL + ..isHorizontal = isHorizontal; + } + } +} + +class _StickyHeaderRenderObject extends RenderStack { + _StickyHeaderRenderObject({ + required ScrollableState scrollableState, + required AlignmentGeometry alignment, + required TextDirection textDirection, + required StackFit fit, + required bool isHorizontal, + required bool isRTL, + }) : _scrollableState = scrollableState, + _isHorizontal = isHorizontal, + _isRTL = isRTL, + super( + alignment: alignment, + textDirection: textDirection, + fit: fit, + ); + + /// Used to update the child position when it scroll changed. + ScrollableState _scrollableState; + + bool _isHorizontal = false; + + bool get isHorizontal => _isHorizontal; + + set isHorizontal(bool value) { + if (_isHorizontal == value) { + return; + } + + _isHorizontal = value; + markNeedsPaint(); + } + + bool _isRTL = false; + + bool get isRTL => _isRTL; + + set isRTL(bool value) { + if (_isRTL == value) { + return; + } + + _isRTL = value; + markNeedsPaint(); + } + + /// Current view port. + RenderAbstractViewport get _stackViewPort => RenderAbstractViewport.of(this)!; + + ScrollableState get scrollableState => _scrollableState; - /// returns the selected ranges in the required type list. - List _getSelectedRanges(List ranges) { - if (ranges == null) { - return ranges; - } + set scrollableState(ScrollableState newScrollable) { + final ScrollableState oldScrollable = _scrollableState; + _scrollableState = newScrollable; - List selectedRanges; - if (widget.isHijri) { - selectedRanges = []; - } else { - selectedRanges = []; + markNeedsPaint(); + if (attached) { + oldScrollable.position.removeListener(markNeedsPaint); + newScrollable.position.addListener(markNeedsPaint); } + } - for (int i = 0; i < ranges.length; i++) { - selectedRanges.add(ranges[i]); - } + /// attach will called when the render object rendered in view. + @override + void attach(PipelineOwner owner) { + super.attach(owner); + scrollableState.position.addListener(markNeedsPaint); + } - return selectedRanges; + /// attach will called when the render object removed from view. + @override + void detach() { + scrollableState.position.removeListener(markNeedsPaint); + super.detach(); } - /// returns the selected dates in the required type list - List _getSelectedDates(List dates) { - if (dates == null) { - return dates; + @override + void paint(PaintingContext context, Offset paintOffset) { + /// Update the child position. + updateHeaderOffset(); + paintStack(context, paintOffset); + } + + void updateHeaderOffset() { + /// Content widget size based on it axis direction. + final double contentSize = + _isHorizontal ? firstChild!.size.width : firstChild!.size.height; + + final RenderBox headerView = lastChild!; + + /// Header view sized based on it axis direction. + final double headerSize = + _isHorizontal ? headerView.size.width : headerView.size.height; + + /// Current view position on scroll view. + final double viewPosition = + _stackViewPort.getOffsetToReveal(this, 0).offset; + + /// Calculate the current view offset by view position on scroll view, + /// scrolled position and scroll view view port. + final double currentViewOffset = + viewPosition - _scrollableState.position.pixels - _scrollableSize; + + /// Check current header offset exits content size, if exist then place the + /// header at content size. + final double offset = _getCurrentOffset(currentViewOffset, contentSize); + final ParentData parentData = headerView.parentData!; + final StackParentData? headerParentData = + parentData is StackParentData ? parentData : null; + + /// Calculate the offset value for horizontal direction with rtl by + /// using content size and header size + /// Eg., If initially header in position 0 then calculate the offset on RTL + /// by content size(total control size) - header size(total header widget + /// size) - 0 and the header placed on right side end. + final double headerYOffset = _isRTL && _isHorizontal + ? contentSize - + headerSize - + _getHeaderOffset(contentSize, offset, headerSize) + : _getHeaderOffset(contentSize, offset, headerSize); + + /// Update the header start y position on vertical direction or update the + /// header start x position on horizontal direction. + if (!_isHorizontal && headerYOffset != headerParentData?.offset.dy) { + headerParentData?.offset = + Offset(headerParentData.offset.dx, headerYOffset); + } else if (_isHorizontal && headerYOffset != headerParentData?.offset.dx) { + headerParentData?.offset = + Offset(headerYOffset, headerParentData.offset.dy); } + } - List selectedDates; - if (widget.isHijri) { - selectedDates = []; - } else { - selectedDates = []; + /// Return the view port size. + double get _scrollableSize { + final Object viewPort = _stackViewPort; + double viewPortSize = 0; + + if (viewPort is RenderBox) { + viewPortSize = _isHorizontal ? viewPort.size.width : viewPort.size.height; } - for (int i = 0; i < dates.length; i++) { - selectedDates.add(dates[i]); + double anchor = 0; + if (viewPort is RenderViewport) { + anchor = viewPort.anchor; } - return selectedDates; + return -viewPortSize * anchor; } - // method to update the picker tapped call back for the header view - void _updateCalendarTapCallbackForHeader() { - if (_view == DateRangePickerView.century || !widget.allowViewNavigation) { - return; - } + /// Check current header offset exits content size, if exist then place the + /// header at content size. + double _getCurrentOffset(double currentOffset, double contentSize) { + final double currentHeaderPosition = + -currentOffset > contentSize ? contentSize : -currentOffset; - if (_view == DateRangePickerView.month) { - _controller.view = widget.isHijri - ? DateRangePickerHelper.getHijriPickerView(DateRangePickerView.year) - : DateRangePickerHelper.getPickerView(DateRangePickerView.year); - } else { - if (_view == DateRangePickerView.year) { - _controller.view = widget.isHijri - ? DateRangePickerHelper.getHijriPickerView( - DateRangePickerView.decade) - : DateRangePickerHelper.getPickerView(DateRangePickerView.decade); - } else if (_view == DateRangePickerView.decade) { - _controller.view = widget.isHijri - ? DateRangePickerHelper.getHijriPickerView( - DateRangePickerView.century) - : DateRangePickerHelper.getPickerView(DateRangePickerView.century); - } + return currentHeaderPosition > 0 ? currentHeaderPosition : 0; + } + + /// Return current offset value from header size and content size. + double _getHeaderOffset( + double contentSize, + double offset, + double headerSize, + ) { + /// Header max start top position is content size position because the + /// view holds header and content. The view's height is header height and + /// content size so header max scroll position is (total height - header + /// height) content size. So vertical direction, header size is 0. + if (!_isHorizontal) { + headerSize = 0; } + return headerSize + offset < contentSize + ? offset + : contentSize - headerSize; } } @@ -3966,6 +5637,7 @@ class _PickerHeaderView extends StatefulWidget { this.showNavigationArrow, this.navigationDirection, this.enableSwipeSelection, + this.navigationMode, this.minDate, this.maxDate, this.monthFormat, @@ -3983,7 +5655,7 @@ class _PickerHeaderView extends StatefulWidget { this.textScaleFactor, this.isHijri, this.localizations, - {Key key}) + {Key? key}) : super(key: key); /// Defines the text scale factor of [SfDateRangePicker]. @@ -4014,11 +5686,13 @@ class _PickerHeaderView extends StatefulWidget { final dynamic maxDate; /// Defines the month format used on header view text. - final String monthFormat; + final String? monthFormat; /// Decides the swipe selection enabled or not. final bool enableSwipeSelection; + final DateRangePickerNavigationMode navigationMode; + /// Decides the view navigation allowed or not. final bool allowViewNavigation; @@ -4032,10 +5706,10 @@ class _PickerHeaderView extends StatefulWidget { final ValueNotifier> visibleDates; /// Holds the previous navigation call back for date range picker. - final VoidCallback previousNavigationCallback; + final VoidCallback? previousNavigationCallback; /// Holds the next navigation call back for date range picker. - final VoidCallback nextNavigationCallback; + final VoidCallback? nextNavigationCallback; /// Holds the picker view width used on widget positioning. final double width; @@ -4066,7 +5740,7 @@ class _PickerHeaderView extends StatefulWidget { } class _PickerHeaderViewState extends State<_PickerHeaderView> { - bool _hovering; + bool _hovering = false; @override void initState() { @@ -4088,46 +5762,57 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { DateRangePickerHelper.isMobileLayout(Theme.of(context).platform); double arrowWidth = 0; double headerWidth = widget.width; - if (widget.showNavigationArrow || + bool showNavigationArrow = widget.showNavigationArrow || ((widget.view == DateRangePickerView.month || !widget.allowViewNavigation) && - widget.enableSwipeSelection && + _isSwipeInteractionEnabled( + widget.enableSwipeSelection, widget.navigationMode) && (widget.selectionMode == DateRangePickerSelectionMode.range || widget.selectionMode == - DateRangePickerSelectionMode.multiRange))) { + DateRangePickerSelectionMode.multiRange)); + showNavigationArrow = showNavigationArrow && + widget.navigationMode != DateRangePickerNavigationMode.scroll; + if (showNavigationArrow) { arrowWidth = widget.width / 6; arrowWidth = arrowWidth > 50 ? 50 : arrowWidth; headerWidth = widget.width - (arrowWidth * 2); } Color arrowColor = widget.headerStyle.textStyle != null - ? widget.headerStyle.textStyle.color - : (widget.datePickerTheme.headerTextStyle.color); + ? widget.headerStyle.textStyle!.color! + : (widget.datePickerTheme.headerTextStyle.color!); arrowColor = arrowColor.withOpacity(arrowColor.opacity * 0.6); Color prevArrowColor = arrowColor; Color nextArrowColor = arrowColor; final List dates = widget.visibleDates.value; - if (!DateRangePickerHelper.canMoveToNextView( - widget.view, - widget.numberOfWeeksInView, - widget.maxDate, - dates, - widget.enableMultiView, - widget.isHijri)) { + if (showNavigationArrow && + !DateRangePickerHelper.canMoveToNextView( + widget.view, + widget.numberOfWeeksInView, + widget.maxDate, + dates, + widget.enableMultiView, + widget.isHijri)) { nextArrowColor = nextArrowColor.withOpacity(arrowColor.opacity * 0.5); } - if (!DateRangePickerHelper.canMoveToPreviousView( - widget.view, - widget.numberOfWeeksInView, - widget.minDate, - dates, - widget.enableMultiView, - widget.isHijri)) { + if (showNavigationArrow && + !DateRangePickerHelper.canMoveToPreviousView( + widget.view, + widget.numberOfWeeksInView, + widget.minDate, + dates, + widget.enableMultiView, + widget.isHijri)) { prevArrowColor = prevArrowColor.withOpacity(arrowColor.opacity * 0.5); } final Widget headerText = _getHeaderText(headerWidth, isMobilePlatform); + if (widget.navigationMode == DateRangePickerNavigationMode.scroll && + widget.navigationDirection == + DateRangePickerNavigationDirection.horizontal) { + return headerText; + } double arrowSize = widget.height * 0.5; arrowSize = arrowSize > 25 ? 25 : arrowSize; @@ -4184,7 +5869,8 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { if (widget.showNavigationArrow || ((widget.view == DateRangePickerView.month || !widget.allowViewNavigation) && - widget.enableSwipeSelection && + _isSwipeInteractionEnabled( + widget.enableSwipeSelection, widget.navigationMode) && (widget.selectionMode == DateRangePickerSelectionMode.range || widget.selectionMode == DateRangePickerSelectionMode.multiRange))) { @@ -4195,7 +5881,7 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { } void _addListener() { - SchedulerBinding.instance.addPostFrameCallback((_) { + SchedulerBinding.instance?.addPostFrameCallback((_) { widget.visibleDates.addListener(_listener); }); } @@ -4253,7 +5939,7 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { ))); } - Widget _getLeftArrow(double arrowWidth, Color arrowColor, + Container _getLeftArrow(double arrowWidth, Color arrowColor, Color prevArrowColor, double arrowSize) { return Container( alignment: Alignment.center, @@ -4261,7 +5947,7 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { widget.datePickerTheme.headerBackgroundColor, width: arrowWidth, padding: const EdgeInsets.all(0), - child: FlatButton( + child: MaterialButton( //// set splash color as transparent when arrow reaches min date(disabled) splashColor: prevArrowColor != arrowColor ? Colors.transparent : null, hoverColor: prevArrowColor != arrowColor ? Colors.transparent : null, @@ -4271,6 +5957,11 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { widget.datePickerTheme.headerBackgroundColor, onPressed: widget.previousNavigationCallback, padding: const EdgeInsets.all(0), + elevation: 0, + focusElevation: 0, + highlightElevation: 0, + disabledElevation: 0, + hoverElevation: 0, child: Semantics( label: 'Backward', child: Icon( @@ -4286,7 +5977,7 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { ); } - Widget _getRightArrow(double arrowWidth, Color arrowColor, + Container _getRightArrow(double arrowWidth, Color arrowColor, Color nextArrowColor, double arrowSize) { return Container( alignment: Alignment.center, @@ -4294,7 +5985,7 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { widget.datePickerTheme.headerBackgroundColor, width: arrowWidth, padding: const EdgeInsets.all(0), - child: FlatButton( + child: MaterialButton( //// set splash color as transparent when arrow reaches max date(disabled) splashColor: nextArrowColor != arrowColor ? Colors.transparent : null, hoverColor: nextArrowColor != arrowColor ? Colors.transparent : null, @@ -4304,6 +5995,11 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { widget.datePickerTheme.headerBackgroundColor, onPressed: widget.nextNavigationCallback, padding: const EdgeInsets.all(0), + elevation: 0, + focusElevation: 0, + highlightElevation: 0, + disabledElevation: 0, + hoverElevation: 0, child: Semantics( label: 'Forward', child: Icon( @@ -4345,7 +6041,7 @@ class _PickerHeaderPainter extends CustomPainter { final int numberOfWeeksInView; final SfDateRangePickerThemeData datePickerTheme; final bool isRtl; - final String monthFormat; + final String? monthFormat; final bool hovering; final bool enableMultiView; final double multiViewSpacing; @@ -4356,14 +6052,13 @@ class _PickerHeaderPainter extends CustomPainter { final SfLocalizations localizations; final DateRangePickerNavigationDirection navigationDirection; ValueNotifier> visibleDates; - String _headerText; - TextPainter _textPainter; + String _headerText = ''; + TextPainter _textPainter = TextPainter(); @override void paint(Canvas canvas, Size size) { canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); double xPosition = 0; - _textPainter = _textPainter ?? TextPainter(); _textPainter.textDirection = TextDirection.ltr; _textPainter.textWidthBasis = TextWidthBasis.longestLine; _textPainter.textScaleFactor = textScaleFactor; @@ -4386,7 +6081,19 @@ class _PickerHeaderPainter extends CustomPainter { final int currentViewIndex = isRtl ? DateRangePickerHelper.getRtlIndex(count, j) : j; xPosition = (currentViewIndex * width) + 10; - final String text = _getHeaderText(j); + + final String text = _getHeaderText( + visibleDates.value, + view, + j, + isHijri, + numberOfWeeksInView, + monthFormat, + enableMultiView, + headerStyle, + navigationDirection, + locale, + localizations); _headerText += j == 1 ? ' ' + text : text; TextStyle style = headerStyle.textStyle ?? datePickerTheme.headerTextStyle; @@ -4425,166 +6132,16 @@ class _PickerHeaderPainter extends CustomPainter { } @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _PickerHeaderPainter oldWidget = oldDelegate; - return oldWidget.headerStyle != headerStyle || - oldWidget.isRtl != isRtl || - oldWidget.numberOfWeeksInView != numberOfWeeksInView || - oldWidget.locale != locale || - oldWidget.datePickerTheme != datePickerTheme || - oldWidget.textScaleFactor != textScaleFactor || - oldWidget.hovering != hovering || - oldWidget.hoverColor != hoverColor; - } - - String _getMonthHeaderText(int startIndex, int endIndex, List dates, - int middleIndex, int datesCount) { - if ((!isHijri && numberOfWeeksInView != 6) && - dates[startIndex].month != dates[endIndex].month) { - final String monthTextFormat = - monthFormat == null || monthFormat.isEmpty ? 'MMM' : monthFormat; - int endIndex = dates.length - 1; - if (enableMultiView && headerStyle.textAlign == TextAlign.center) { - endIndex = endIndex; - } - - final String startText = DateFormat(monthTextFormat, locale.toString()) - .format(dates[startIndex]) - .toString() + - ' ' + - dates[startIndex].year.toString(); - final String endText = DateFormat(monthTextFormat, locale.toString()) - .format(dates[endIndex]) - .toString() + - ' ' + - dates[endIndex].year.toString(); - if (startText == endText) { - return startText; - } - - return startText + ' - ' + endText; - } else { - final String monthTextFormat = monthFormat == null || monthFormat.isEmpty - ? enableMultiView && - navigationDirection == - DateRangePickerNavigationDirection.vertical - ? 'MMM' - : 'MMMM' - : monthFormat; - String text; - dynamic middleDate = dates[middleIndex]; - if (isHijri) { - text = DateRangePickerHelper.getHijriMonthText( - middleDate, localizations, monthTextFormat) + - ' ' + - middleDate.year.toString(); - } else { - text = DateFormat(monthTextFormat, locale.toString()) - .format(middleDate) - .toString() + - ' ' + - middleDate.year.toString(); - } - - /// To restrict the double header when the number of weeks in view given - /// and the dates were the same month. - if (enableMultiView && - navigationDirection == DateRangePickerNavigationDirection.vertical && - numberOfWeeksInView != 6 && - dates[startIndex].month == dates[endIndex].month) { - return text; - } - - if ((enableMultiView && headerStyle.textAlign != TextAlign.center) || - (enableMultiView && - navigationDirection == - DateRangePickerNavigationDirection.vertical)) { - middleDate = dates[datesCount + middleIndex]; - if (isHijri) { - return text + - '_' + - DateRangePickerHelper.getHijriMonthText( - middleDate, localizations, monthTextFormat) + - ' ' + - middleDate.year.toString(); - } else { - return text + - ' - ' + - DateFormat(monthTextFormat, locale.toString()) - .format(middleDate) - .toString() + - ' ' + - middleDate.year.toString(); - } - } - - return text; - } - } - - String _getHeaderText(int index) { - final List dates = visibleDates.value; - final int count = enableMultiView ? 2 : 1; - final int datesCount = dates.length ~/ count; - final int startIndex = index * datesCount; - final int endIndex = ((index + 1) * datesCount) - 1; - final int middleIndex = startIndex + (datesCount ~/ 2); - switch (view) { - case DateRangePickerView.month: - { - return _getMonthHeaderText( - startIndex, endIndex, dates, middleIndex, datesCount); - } - break; - case DateRangePickerView.year: - { - final dynamic date = dates[middleIndex]; - if ((enableMultiView && headerStyle.textAlign != TextAlign.center) || - (enableMultiView && - navigationDirection == - DateRangePickerNavigationDirection.vertical)) { - return date.year.toString() + - ' - ' + - dates[datesCount + middleIndex].year.toString(); - } - - return date.year.toString(); - } - break; - case DateRangePickerView.decade: - { - final int year = (dates[middleIndex].year ~/ 10) * 10; - if ((enableMultiView && headerStyle.textAlign != TextAlign.center) || - (enableMultiView && - navigationDirection == - DateRangePickerNavigationDirection.vertical)) { - return year.toString() + - ' - ' + - (((dates[datesCount + middleIndex].year ~/ 10) * 10) + 9) - .toString(); - } - - return year.toString() + ' - ' + (year + 9).toString(); - } - break; - case DateRangePickerView.century: - { - final int year = (dates[middleIndex].year ~/ 100) * 100; - if ((enableMultiView && headerStyle.textAlign != TextAlign.center) || - (enableMultiView && - navigationDirection == - DateRangePickerNavigationDirection.vertical)) { - return year.toString() + - ' - ' + - (((dates[datesCount + middleIndex].year ~/ 100) * 100) + 99) - .toString(); - } - - return year.toString() + ' - ' + (year + 99).toString(); - } - } - - return ''; + bool shouldRepaint(_PickerHeaderPainter oldDelegate) { + return oldDelegate.headerStyle != headerStyle || + oldDelegate.isRtl != isRtl || + oldDelegate.numberOfWeeksInView != numberOfWeeksInView || + oldDelegate.locale != locale || + oldDelegate.datePickerTheme != datePickerTheme || + oldDelegate.monthFormat != monthFormat || + oldDelegate.textScaleFactor != textScaleFactor || + oldDelegate.hovering != hovering || + oldDelegate.hoverColor != hoverColor; } /// overrides this property to build the semantics information which uses to @@ -4599,7 +6156,7 @@ class _PickerHeaderPainter extends CustomPainter { CustomPainterSemantics( rect: rect, properties: SemanticsProperties( - label: _headerText != null ? _headerText.replaceAll('-', 'to') : '', + label: _headerText.replaceAll('-', 'to'), textDirection: TextDirection.ltr, ), ), @@ -4618,6 +6175,7 @@ class _PickerViewHeaderPainter extends CustomPainter { /// Constructor to create picker view header view instance. _PickerViewHeaderPainter( this.visibleDates, + this.navigationMode, this.viewHeaderStyle, this.viewHeaderHeight, this.monthViewSettings, @@ -4638,6 +6196,9 @@ class _PickerViewHeaderPainter extends CustomPainter { /// Defines the month view settings. final dynamic monthViewSettings; + /// Defines the navigation mode of picker. + final DateRangePickerNavigationMode navigationMode; + /// Holds the visible dates for the month view. final List visibleDates; @@ -4654,7 +6215,7 @@ class _PickerViewHeaderPainter extends CustomPainter { final bool isRtl; /// Defines the today cell highlight color. - final Color todayHighlightColor; + final Color? todayHighlightColor; /// Decides to show the multi view of picker or not. final bool enableMultiView; @@ -4673,7 +6234,10 @@ class _PickerViewHeaderPainter extends CustomPainter { /// Defines the navigation direction for [SfDateRangePicker]. final DateRangePickerNavigationDirection navigationDirection; - TextPainter _textPainter; + TextPainter _textPainter = TextPainter( + textDirection: TextDirection.ltr, + textAlign: TextAlign.left, + textWidthBasis: TextWidthBasis.longestLine); @override void paint(Canvas canvas, Size size) { @@ -4702,6 +6266,9 @@ class _PickerViewHeaderPainter extends CustomPainter { DateRangePickerNavigationDirection.horizontal) ? visibleDates.length ~/ 2 : visibleDates.length; + final bool isVerticalScroll = + navigationDirection == DateRangePickerNavigationDirection.vertical && + navigationMode == DateRangePickerNavigationMode.scroll; for (int j = 0; j < count; j++) { final int currentViewIndex = isRtl ? DateRangePickerHelper.getRtlIndex(count, j) : j; @@ -4718,11 +6285,17 @@ class _PickerViewHeaderPainter extends CustomPainter { final int numberOfWeeksInView = DateRangePickerHelper.getNumberOfWeeksInView( monthViewSettings, isHijri); - final bool hasToday = numberOfWeeksInView > 0 && numberOfWeeksInView < 6 + final bool isTodayMonth = isDateWithInDateRange( + visibleDates[(currentViewIndex * datesCount)], + visibleDates[((currentViewIndex + 1) * datesCount) - 1], + today); + final bool hasToday = isVerticalScroll ? true - : (month == currentMonth && year == currentYear) + : (numberOfWeeksInView > 0 && numberOfWeeksInView < 6 ? true - : false; + : (month == currentMonth && year == currentYear) + ? true + : false); for (int i = 0; i < DateTime.daysPerWeek; i++) { int index = isRtl ? DateRangePickerHelper.getRtlIndex(DateTime.daysPerWeek, i) @@ -4738,10 +6311,7 @@ class _PickerViewHeaderPainter extends CustomPainter { if (hasToday && currentDate.weekday == today.weekday && - isDateWithInDateRange( - visibleDates[(currentViewIndex * datesCount)], - visibleDates[((currentViewIndex + 1) * datesCount) - 1], - today)) { + (isTodayMonth || isVerticalScroll)) { final Color textColor = monthCellStyle.todayTextStyle != null && monthCellStyle.todayTextStyle.color != null ? monthCellStyle.todayTextStyle.color @@ -4756,12 +6326,7 @@ class _PickerViewHeaderPainter extends CustomPainter { style: dayTextStyle, ); - _textPainter = _textPainter ?? - TextPainter( - textDirection: TextDirection.ltr, - textAlign: TextAlign.left, - textScaleFactor: textScaleFactor, - textWidthBasis: TextWidthBasis.longestLine); + _textPainter.textScaleFactor = textScaleFactor; _textPainter.text = dayTextSpan; _textPainter.layout(minWidth: width, maxWidth: width); yPosition = (viewHeaderHeight - _textPainter.height) / 2; @@ -4784,8 +6349,7 @@ class _PickerViewHeaderPainter extends CustomPainter { /// /// Eg: In chinese the first letter or `Sunday` represents `Weekday`, hence /// to avoid this added this condition based on locale. - if (monthViewSettings.dayFormat == 'EE' && - (locale == null || locale.languageCode == 'en')) { + if (monthViewSettings.dayFormat == 'EE' && locale.languageCode == 'en') { dayText = dayText[0]; } @@ -4793,18 +6357,17 @@ class _PickerViewHeaderPainter extends CustomPainter { } @override - bool shouldRepaint(CustomPainter oldDelegate) { - final _PickerViewHeaderPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.viewHeaderStyle != viewHeaderStyle || - oldWidget.viewHeaderHeight != viewHeaderHeight || - oldWidget.todayHighlightColor != todayHighlightColor || - oldWidget.monthViewSettings != monthViewSettings || - oldWidget.datePickerTheme != datePickerTheme || - oldWidget.isRtl != isRtl || - oldWidget.locale != locale || - oldWidget.textScaleFactor != textScaleFactor || - oldWidget.isHijri != isHijri; + bool shouldRepaint(_PickerViewHeaderPainter oldDelegate) { + return oldDelegate.visibleDates != visibleDates || + oldDelegate.viewHeaderStyle != viewHeaderStyle || + oldDelegate.viewHeaderHeight != viewHeaderHeight || + oldDelegate.todayHighlightColor != todayHighlightColor || + oldDelegate.monthViewSettings != monthViewSettings || + oldDelegate.datePickerTheme != datePickerTheme || + oldDelegate.isRtl != isRtl || + oldDelegate.locale != locale || + oldDelegate.textScaleFactor != textScaleFactor || + oldDelegate.isHijri != isHijri; } List _getSemanticsBuilder(Size size) { @@ -4866,9 +6429,8 @@ class _PickerViewHeaderPainter extends CustomPainter { } @override - bool shouldRebuildSemantics(CustomPainter oldDelegate) { - final _PickerViewHeaderPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; + bool shouldRebuildSemantics(_PickerViewHeaderPainter oldDelegate) { + return oldDelegate.visibleDates != visibleDates; } } @@ -4879,7 +6441,9 @@ class _PickerScrollView extends StatefulWidget { /// Constructor to create the picker scroll view instance. const _PickerScrollView(this.picker, this.controller, this.width, this.height, this.isRtl, this.datePickerTheme, this.locale, this.textScaleFactor, - {Key key, this.getPickerStateValues, this.updatePickerStateValues}) + {Key? key, + required this.getPickerStateValues, + required this.updatePickerStateValues}) : super(key: key); /// Holds the picker instance to access the picker details. @@ -4920,52 +6484,48 @@ class _PickerScrollView extends StatefulWidget { class _PickerScrollViewState extends State<_PickerScrollView> with TickerProviderStateMixin { // three views to arrange the view in vertical/horizontal direction and handle the swiping - _PickerView _currentView, _nextView, _previousView; + _PickerView? _currentView, _nextView, _previousView; // the three children which to be added into the layout - List<_PickerView> _children; + List<_PickerView> _children = <_PickerView>[]; // holds the index of the current displaying view - int _currentChildIndex; + int _currentChildIndex = 1; // _scrollStartPosition contains the touch movement starting position - double _scrollStartPosition; + double? _scrollStartPosition; // _position contains distance that the view swiped - double _position; + double _position = 0; // animation controller to control the animation - AnimationController _animationController; + late AnimationController _animationController; // animation handled for the view swiping - Animation _animation; + late Animation _animation; // tween animation to handle the animation - Tween _tween; + late Tween _tween; // three visible dates for the three views, the dates will updated based on // the swiping in the swipe end _currentViewVisibleDates which stores the // visible dates of the current displaying view - List _visibleDates, + late List _visibleDates, _previousViewVisibleDates, _nextViewVisibleDates, _currentViewVisibleDates; /// keys maintained to access the data and methods from the picker view /// class. - GlobalKey<_PickerViewState> _previousViewKey, _currentViewKey, _nextViewKey; + GlobalKey<_PickerViewState> _previousViewKey = GlobalKey<_PickerViewState>(), + _currentViewKey = GlobalKey<_PickerViewState>(), + _nextViewKey = GlobalKey<_PickerViewState>(); - PickerStateArgs _pickerStateDetails; - FocusNode _focusNode; + PickerStateArgs _pickerStateDetails = PickerStateArgs(); + FocusNode _focusNode = FocusNode(); @override void initState() { - _previousViewKey = GlobalKey<_PickerViewState>(); - _currentViewKey = GlobalKey<_PickerViewState>(); - _nextViewKey = GlobalKey<_PickerViewState>(); - _focusNode = FocusNode(); - _pickerStateDetails = PickerStateArgs(); - _currentChildIndex = 1; _updateVisibleDates(); _animationController = AnimationController( duration: const Duration(milliseconds: 250), @@ -5087,37 +6647,36 @@ class _PickerScrollViewState extends State<_PickerScrollView> return; } - if (oldWidget.picker.controller.displayDate != - widget.picker.controller.displayDate || + if (oldWidget.picker.controller?.displayDate != + widget.picker.controller?.displayDate || !isSameDate( _pickerStateDetails.currentDate, widget.controller.displayDate)) { - _pickerStateDetails.currentDate = widget.picker.controller.displayDate; + _pickerStateDetails.currentDate = widget.picker.controller?.displayDate; _updateVisibleDates(); } - _drawSelection(oldWidget); + _drawSelection(oldWidget.picker.controller, widget.picker.controller); widget.getPickerStateValues(_pickerStateDetails); super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { - double leftPosition, rightPosition, topPosition, bottomPosition; + double leftPosition = 0, + rightPosition = 0, + topPosition = 0, + bottomPosition = 0; switch (widget.picker.navigationDirection) { case DateRangePickerNavigationDirection.horizontal: { - leftPosition = leftPosition ?? -widget.width; - rightPosition = rightPosition ?? -widget.width; - topPosition = 0; - bottomPosition = 0; + leftPosition = -widget.width; + rightPosition = -widget.width; } break; case DateRangePickerNavigationDirection.vertical: { - leftPosition = 0; - rightPosition = 0; - topPosition = topPosition ?? -widget.height; - bottomPosition = bottomPosition ?? -widget.height; + topPosition = -widget.height; + bottomPosition = -widget.height; } } @@ -5142,27 +6701,39 @@ class _PickerScrollViewState extends State<_PickerScrollView> _currentChildIndex), ), onHorizontalDragStart: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal + DateRangePickerNavigationDirection.horizontal && + widget.picker.navigationMode != + DateRangePickerNavigationMode.none ? _onHorizontalStart : null, onHorizontalDragUpdate: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal + DateRangePickerNavigationDirection.horizontal && + widget.picker.navigationMode != + DateRangePickerNavigationMode.none ? _onHorizontalUpdate : null, onHorizontalDragEnd: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.horizontal + DateRangePickerNavigationDirection.horizontal && + widget.picker.navigationMode != + DateRangePickerNavigationMode.none ? _onHorizontalEnd : null, onVerticalDragStart: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical + DateRangePickerNavigationDirection.vertical && + widget.picker.navigationMode != + DateRangePickerNavigationMode.none ? _onVerticalStart : null, onVerticalDragUpdate: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical + DateRangePickerNavigationDirection.vertical && + widget.picker.navigationMode != + DateRangePickerNavigationMode.none ? _onVerticalUpdate : null, onVerticalDragEnd: widget.picker.navigationDirection == - DateRangePickerNavigationDirection.vertical + DateRangePickerNavigationDirection.vertical && + widget.picker.navigationMode != + DateRangePickerNavigationMode.none ? _onVerticalEnd : null, ), @@ -5173,10 +6744,8 @@ class _PickerScrollViewState extends State<_PickerScrollView> @override void dispose() { - _animationController?.dispose(); - if (_animation != null) { - _animation.removeListener(_animationListener); - } + _animationController.dispose(); + _animation.removeListener(_animationListener); super.dispose(); } @@ -5200,7 +6769,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> widget.picker.isHijri); dynamic afterNextViewDate; - List afterVisibleDates; + List? afterVisibleDates; if (widget.picker.enableMultiView) { afterNextViewDate = DateRangePickerHelper.getNextViewStartDate( DateRangePickerHelper.getPickerView(widget.controller.view), @@ -5266,7 +6835,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> } if (widget.picker.enableMultiView) { - _updateVisibleDatesForMultiView(afterVisibleDates); + _updateVisibleDatesForMultiView(afterVisibleDates!); } _currentViewVisibleDates = _visibleDates; @@ -5397,7 +6966,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> currentViewDate, widget.isRtl, widget.picker.isHijri); - List afterVisibleDates; + List? afterVisibleDates; dynamic afterNextViewDate; if (widget.picker.enableMultiView && !widget.isRtl) { afterNextViewDate = DateRangePickerHelper.getNextViewStartDate( @@ -5443,7 +7012,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> } if (widget.picker.enableMultiView) { - dates.addAll(_updateNextVisibleDateForMultiView(afterVisibleDates)); + dates.addAll(_updateNextVisibleDateForMultiView(afterVisibleDates!)); } if (_currentChildIndex == 0) { @@ -5456,7 +7025,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> } List _updateNextVisibleDateForMultiView( - List afterVisibleDates) { + List? afterVisibleDates) { List dates; if (widget.picker.isHijri) { dates = []; @@ -5464,7 +7033,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> dates = []; } if (!widget.isRtl) { - for (int i = 0; i < afterVisibleDates.length; i++) { + for (int i = 0; i < afterVisibleDates!.length; i++) { dates.add(afterVisibleDates[i]); } } else { @@ -5504,7 +7073,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> widget.isRtl, widget.picker.isHijri); List dates; - List afterVisibleDates; + List? afterVisibleDates; dynamic afterNextViewDate; if (widget.picker.enableMultiView && widget.isRtl) { afterNextViewDate = DateRangePickerHelper.getPreviousViewStartDate( @@ -5563,7 +7132,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> } List _updatePreviousDatesForMultiView( - List afterVisibleDates) { + List? afterVisibleDates) { List dates; if (widget.picker.isHijri) { dates = []; @@ -5571,7 +7140,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> dates = []; } if (widget.isRtl) { - for (int i = 0; i < (afterVisibleDates.length); i++) { + for (int i = 0; i < (afterVisibleDates!.length); i++) { dates.add(afterVisibleDates[i]); } } else { @@ -5602,11 +7171,12 @@ class _PickerScrollViewState extends State<_PickerScrollView> widget.updatePickerStateValues(_pickerStateDetails); } - _PickerView _getView(List dates, GlobalKey key) { + _PickerView _getView(List dates, Key key) { return _PickerView( widget.picker, widget.controller, dates, + _isMultiViewEnabled(widget.picker), widget.width, widget.height, widget.datePickerTheme, @@ -5624,24 +7194,23 @@ class _PickerScrollViewState extends State<_PickerScrollView> } List _addViews(BuildContext context) { - _children = _children ?? <_PickerView>[]; - if (_children != null && _children.isEmpty) { + if (_children.isEmpty) { _previousView = _getView(_previousViewVisibleDates, _previousViewKey); _currentView = _getView(_visibleDates, _currentViewKey); _nextView = _getView(_nextViewVisibleDates, _nextViewKey); - _children.add(_previousView); - _children.add(_currentView); - _children.add(_nextView); + _children.add(_previousView!); + _children.add(_currentView!); + _children.add(_nextView!); return _children; } final _PickerView previousView = _updateViews( - _previousView, _previousView.visibleDates, _previousViewVisibleDates); + _previousView!, _previousView!.visibleDates, _previousViewVisibleDates); final _PickerView currentView = - _updateViews(_currentView, _currentView.visibleDates, _visibleDates); - final _PickerView nextView = - _updateViews(_nextView, _nextView.visibleDates, _nextViewVisibleDates); + _updateViews(_currentView!, _currentView!.visibleDates, _visibleDates); + final _PickerView nextView = _updateViews( + _nextView!, _nextView!.visibleDates, _nextViewVisibleDates); /// Update views while the all day view height differ from original height, /// else repaint the appointment painter while current child visible @@ -5660,12 +7229,12 @@ class _PickerScrollViewState extends State<_PickerScrollView> } // method to check and update the views and appointments on the swiping end - Widget _updateViews( - Widget view, List viewDates, List visibleDates) { + _PickerView _updateViews( + _PickerView view, List viewDates, List visibleDates) { final int index = _children.indexOf(view); // update the view with the visible dates on swiping end. if (viewDates != visibleDates) { - view = _getView(visibleDates, view.key); + view = _getView(visibleDates, view.key!); _children[index] = view; } // check and update the visible appointments in the view @@ -5709,18 +7278,16 @@ class _PickerScrollViewState extends State<_PickerScrollView> } } - void _drawSelection(_PickerScrollView oldWidget) { + void _drawSelection(dynamic oldValue, dynamic newValue) { final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView(widget.controller.view); switch (widget.picker.selectionMode) { case DateRangePickerSelectionMode.single: { - if ((oldWidget.picker.controller.selectedDate != - widget.picker.controller.selectedDate || - !isSameDate(_pickerStateDetails.selectedDate, - widget.controller.selectedDate))) { - _pickerStateDetails.selectedDate = - widget.picker.controller.selectedDate; + if ((oldValue.selectedDate != newValue.selectedDate || + !isSameDate( + _pickerStateDetails.selectedDate, newValue.selectedDate))) { + _pickerStateDetails.selectedDate = newValue.selectedDate; if (pickerView != DateRangePickerView.month && !widget.picker.allowViewNavigation) { _drawYearSelection(); @@ -5734,13 +7301,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> break; case DateRangePickerSelectionMode.multiple: { - if (oldWidget.picker.controller.selectedDates != - widget.picker.controller.selectedDates || + if (oldValue.selectedDates != newValue.selectedDates || !DateRangePickerHelper.isDateCollectionEquals( - _pickerStateDetails.selectedDates, - widget.picker.controller.selectedDates)) { - _pickerStateDetails.selectedDates = - widget.picker.controller.selectedDates; + _pickerStateDetails.selectedDates, newValue.selectedDates)) { + _pickerStateDetails.selectedDates = newValue.selectedDates; if (pickerView != DateRangePickerView.month && !widget.picker.allowViewNavigation) { _drawYearSelection(); @@ -5754,13 +7318,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> break; case DateRangePickerSelectionMode.range: { - if (oldWidget.picker.controller.selectedRange != - widget.picker.controller.selectedRange || + if (oldValue.selectedRange != newValue.selectedRange || !DateRangePickerHelper.isRangeEquals( - _pickerStateDetails.selectedRange, - widget.picker.controller.selectedRange)) { - _pickerStateDetails.selectedRange = - widget.picker.controller.selectedRange; + _pickerStateDetails.selectedRange, newValue.selectedRange)) { + _pickerStateDetails.selectedRange = newValue.selectedRange; if (pickerView != DateRangePickerView.month && !widget.picker.allowViewNavigation) { _drawYearSelection(); @@ -5774,13 +7335,11 @@ class _PickerScrollViewState extends State<_PickerScrollView> break; case DateRangePickerSelectionMode.multiRange: { - if (oldWidget.picker.controller.selectedRanges != - widget.picker.controller.selectedRanges || + if (oldValue.selectedRanges != newValue.selectedRanges || !DateRangePickerHelper.isDateRangesEquals( _pickerStateDetails.selectedRanges, - widget.picker.controller.selectedRanges)) { - _pickerStateDetails.selectedRanges = - widget.picker.controller.selectedRanges; + newValue.selectedRanges)) { + _pickerStateDetails.selectedRanges = newValue.selectedRanges; if (pickerView != DateRangePickerView.month && !widget.picker.allowViewNavigation) { _drawYearSelection(); @@ -5820,16 +7379,16 @@ class _PickerScrollViewState extends State<_PickerScrollView> switch (view) { case DateRangePickerView.month: { - viewState._monthView.selectionNotifier.value = - !viewState._monthView.selectionNotifier.value; + viewState._monthView!.selectionNotifier.value = + !viewState._monthView!.selectionNotifier.value; } break; case DateRangePickerView.year: case DateRangePickerView.decade: case DateRangePickerView.century: { - viewState._yearView.selectionNotifier.value = - !viewState._yearView.selectionNotifier.value; + viewState._yearView!.selectionNotifier.value = + !viewState._yearView!.selectionNotifier.value; } } } @@ -5850,14 +7409,13 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// Check the visible dates rather than current child index because /// current child index value not updated when the selected date value /// changed on view changed callback - if (viewState == null || - viewState._monthView.visibleDates != - _pickerStateDetails.currentViewVisibleDates) { + if (viewState._monthView!.visibleDates != + _pickerStateDetails.currentViewVisibleDates) { continue; } - viewState._monthView.selectionNotifier.value = - !viewState._monthView.selectionNotifier.value; + viewState._monthView!.selectionNotifier.value = + !viewState._monthView!.selectionNotifier.value; } } @@ -5876,26 +7434,25 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// Check the visible dates rather than current child index because /// current child index value not updated when the selected date value /// changed on view changed callback - if (viewState == null || - viewState._yearView.visibleDates != - _pickerStateDetails.currentViewVisibleDates) { + if (viewState._yearView!.visibleDates != + _pickerStateDetails.currentViewVisibleDates) { continue; } - viewState._yearView.selectionNotifier.value = - !viewState._yearView.selectionNotifier.value; + viewState._yearView!.selectionNotifier.value = + !viewState._yearView!.selectionNotifier.value; } } /// Return the picker view state details based on view index. _PickerViewState _getCurrentViewState(int index) { if (index == 1) { - return _currentViewKey.currentState; + return _currentViewKey.currentState!; } else if (index == 2) { - return _nextViewKey.currentState; + return _nextViewKey.currentState!; } - return _previousViewKey.currentState; + return _previousViewKey.currentState!; } /// Updates the current view visible dates for picker in the swiping end @@ -5998,7 +7555,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> // resets position to zero on the swipe end to avoid the unwanted date // updates. void _resetPosition() { - SchedulerBinding.instance.addPostFrameCallback((_) { + SchedulerBinding.instance!.addPostFrameCallback((_) { if (_position.abs() == widget.width || _position.abs() == widget.height) { _position = 0; } @@ -6007,7 +7564,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// Calculate and return the date time value based on previous selected date, /// keyboard action and current picker view. - dynamic _getYearSelectedDate(dynamic selectedDate, PhysicalKeyboardKey key, + dynamic _getYearSelectedDate(dynamic selectedDate, LogicalKeyboardKey key, _PickerView view, _PickerViewState state) { final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView(widget.controller.view); @@ -6016,7 +7573,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// Calculate the index value for previous selected date. int index = DateRangePickerHelper.getDateCellIndex( view.visibleDates, selectedDate, widget.controller.view); - if (key == PhysicalKeyboardKey.arrowRight) { + if (key == LogicalKeyboardKey.arrowRight) { /// If index value as last cell index in current view then /// navigate to next view. Calculate the selected index on navigated view /// and return the selected date on navigated view on right arrow pressed @@ -6034,7 +7591,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> if (index != -1) { date = _updateNextYearSelectionDate(selectedDate); } - } else if (key == PhysicalKeyboardKey.arrowLeft) { + } else if (key == LogicalKeyboardKey.arrowLeft) { /// If index value as first cell index in current view then /// navigate to previous view. Calculate the selected index on navigated /// view and return the selected date on navigated view on left arrow @@ -6049,14 +7606,14 @@ class _PickerScrollViewState extends State<_PickerScrollView> if (index != -1) { date = _updatePreviousYearSelectionDate(selectedDate); } - } else if (key == PhysicalKeyboardKey.arrowUp) { + } else if (key == LogicalKeyboardKey.arrowUp) { /// If index value not in first row then calculate the date by /// subtracting the index value with 3 and return the date value. if (index >= 3 && index != -1) { index -= 3; date = view.visibleDates[index]; } - } else if (key == PhysicalKeyboardKey.arrowDown) { + } else if (key == LogicalKeyboardKey.arrowDown) { /// If index value not in last row then calculate the date by /// adding the index value with 3 and return the date value. if (index <= 8 && index != -1) { @@ -6081,9 +7638,11 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// Return the next date for year, decade and century view in keyboard /// navigation dynamic _updateNextYearSelectionDate(dynamic selectedDate) { - final int defaultRowCount = 6; final DateRangePickerView view = DateRangePickerHelper.getPickerView(widget.controller.view); + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, widget.picker.isHijri); switch (view) { case DateRangePickerView.month: { @@ -6093,27 +7652,25 @@ class _PickerScrollViewState extends State<_PickerScrollView> { return DateRangePickerHelper.getNextViewStartDate( DateRangePickerView.month, - defaultRowCount, + numberOfWeeksInView, selectedDate, widget.isRtl, widget.picker.isHijri); } - break; case DateRangePickerView.decade: { return DateRangePickerHelper.getNextViewStartDate( DateRangePickerView.year, - null, + numberOfWeeksInView, selectedDate, widget.isRtl, widget.picker.isHijri); } - break; case DateRangePickerView.century: { return DateRangePickerHelper.getNextViewStartDate( DateRangePickerView.decade, - null, + numberOfWeeksInView, selectedDate, widget.isRtl, widget.picker.isHijri); @@ -6126,7 +7683,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// Return the previous date for year, decade and century view in keyboard /// navigation dynamic _updatePreviousYearSelectionDate(dynamic selectedDate) { - final int defaultRowCount = 6; + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, widget.picker.isHijri); final DateRangePickerView view = DateRangePickerHelper.getPickerView(widget.controller.view); switch (view) { @@ -6138,27 +7697,25 @@ class _PickerScrollViewState extends State<_PickerScrollView> { return DateRangePickerHelper.getPreviousViewStartDate( DateRangePickerView.month, - defaultRowCount, + numberOfWeeksInView, selectedDate, widget.isRtl, widget.picker.isHijri); } - break; case DateRangePickerView.decade: { return DateRangePickerHelper.getPreviousViewStartDate( DateRangePickerView.year, - null, + numberOfWeeksInView, selectedDate, widget.isRtl, widget.picker.isHijri); } - break; case DateRangePickerView.century: { return DateRangePickerHelper.getPreviousViewStartDate( DateRangePickerView.decade, - null, + numberOfWeeksInView, selectedDate, widget.isRtl, widget.picker.isHijri); @@ -6173,13 +7730,13 @@ class _PickerScrollViewState extends State<_PickerScrollView> /// EJ2 scheduler, we have used alt + numeric to switch between views in /// datepicker web if (event.isAltPressed) { - if (event.physicalKey == PhysicalKeyboardKey.digit1) { + if (event.logicalKey == LogicalKeyboardKey.digit1) { _pickerStateDetails.view = DateRangePickerView.month; - } else if (event.physicalKey == PhysicalKeyboardKey.digit2) { + } else if (event.logicalKey == LogicalKeyboardKey.digit2) { _pickerStateDetails.view = DateRangePickerView.year; - } else if (event.physicalKey == PhysicalKeyboardKey.digit3) { + } else if (event.logicalKey == LogicalKeyboardKey.digit3) { _pickerStateDetails.view = DateRangePickerView.decade; - } else if (event.physicalKey == PhysicalKeyboardKey.digit4) { + } else if (event.logicalKey == LogicalKeyboardKey.digit4) { _pickerStateDetails.view = DateRangePickerView.century; } @@ -6196,7 +7753,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> if (_pickerStateDetails.selectedDate != null && widget.picker.selectionMode == DateRangePickerSelectionMode.single) { selectedDate = _getYearSelectedDate(_pickerStateDetails.selectedDate, - event.physicalKey, currentVisibleView, currentVisibleViewState); + event.logicalKey, currentVisibleView, currentVisibleViewState); if (selectedDate != null && DateRangePickerHelper.isBetweenMinMaxDateCell( selectedDate, @@ -6210,12 +7767,12 @@ class _PickerScrollViewState extends State<_PickerScrollView> } else if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple && _pickerStateDetails.selectedDates != null && - _pickerStateDetails.selectedDates.isNotEmpty && + _pickerStateDetails.selectedDates!.isNotEmpty && event.isShiftPressed) { final dynamic date = _pickerStateDetails - .selectedDates[_pickerStateDetails.selectedDates.length - 1]; + .selectedDates![_pickerStateDetails.selectedDates!.length - 1]; selectedDate = _getYearSelectedDate( - date, event.physicalKey, currentVisibleView, currentVisibleViewState); + date, event.logicalKey, currentVisibleView, currentVisibleViewState); if (selectedDate != null && DateRangePickerHelper.isBetweenMinMaxDateCell( selectedDate, @@ -6226,16 +7783,16 @@ class _PickerScrollViewState extends State<_PickerScrollView> widget.picker.isHijri)) { _pickerStateDetails.selectedDates = DateRangePickerHelper.cloneList(_pickerStateDetails.selectedDates) - ..add(selectedDate); + ?..add(selectedDate); } } else if (widget.picker.selectionMode == DateRangePickerSelectionMode.range && _pickerStateDetails.selectedRange != null && _pickerStateDetails.selectedRange.startDate != null && event.isShiftPressed) { - final DateTime date = _pickerStateDetails.selectedRange.startDate; + final dynamic date = _pickerStateDetails.selectedRange.startDate; selectedDate = _getYearSelectedDate( - date, event.physicalKey, currentVisibleView, currentVisibleViewState); + date, event.logicalKey, currentVisibleView, currentVisibleViewState); if (selectedDate != null && DateRangePickerHelper.isBetweenMinMaxDateCell( selectedDate, @@ -6249,11 +7806,15 @@ class _PickerScrollViewState extends State<_PickerScrollView> (_pickerStateDetails.selectedRange.endDate == null || isSameDate(_pickerStateDetails.selectedRange.startDate, _pickerStateDetails.selectedRange.endDate))) { - _pickerStateDetails.selectedRange = PickerDateRange( - _pickerStateDetails.selectedRange.startDate, selectedDate); + _pickerStateDetails.selectedRange = widget.picker.isHijri + ? HijriDateRange( + _pickerStateDetails.selectedRange.startDate, selectedDate) + : PickerDateRange( + _pickerStateDetails.selectedRange.startDate, selectedDate); } else { - _pickerStateDetails.selectedRange = - PickerDateRange(selectedDate, null); + _pickerStateDetails.selectedRange = widget.picker.isHijri + ? HijriDateRange(selectedDate, null) + : PickerDateRange(selectedDate, null); } } } @@ -6271,7 +7832,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> break; case DateRangePickerSelectionMode.multiple: { - _pickerStateDetails.selectedDates.add(selectedDate); + _pickerStateDetails.selectedDates!.add(selectedDate); } break; case DateRangePickerSelectionMode.range: @@ -6315,24 +7876,24 @@ class _PickerScrollViewState extends State<_PickerScrollView> if (_pickerStateDetails.selectedDate == null && (_pickerStateDetails.selectedDates == null || - _pickerStateDetails.selectedDates.isEmpty) && + _pickerStateDetails.selectedDates!.isEmpty) && _pickerStateDetails.selectedRange == null && (_pickerStateDetails.selectedRanges == null || - _pickerStateDetails.selectedRanges.isEmpty)) { + _pickerStateDetails.selectedRanges!.isEmpty)) { return; } _PickerViewState currentVisibleViewState; _PickerView currentVisibleView; if (_currentChildIndex == 0) { - currentVisibleViewState = _previousViewKey.currentState; - currentVisibleView = _previousView; + currentVisibleViewState = _previousViewKey.currentState!; + currentVisibleView = _previousView!; } else if (_currentChildIndex == 1) { - currentVisibleViewState = _currentViewKey.currentState; - currentVisibleView = _currentView; - } else if (_currentChildIndex == 2) { - currentVisibleViewState = _nextViewKey.currentState; - currentVisibleView = _nextView; + currentVisibleViewState = _currentViewKey.currentState!; + currentVisibleView = _currentView!; + } else { + currentVisibleViewState = _nextViewKey.currentState!; + currentVisibleView = _nextView!; } if (pickerView != DateRangePickerView.month) { @@ -6389,87 +7950,81 @@ class _PickerScrollViewState extends State<_PickerScrollView> _updateSelectionByKeyboardNavigation(selectedDate); widget.updatePickerStateValues(_pickerStateDetails); - currentVisibleViewState._monthView.selectionNotifier.value = - !currentVisibleViewState._monthView.selectionNotifier.value; + currentVisibleViewState._monthView!.selectionNotifier.value = + !currentVisibleViewState._monthView!.selectionNotifier.value; _updateSelection(); } dynamic _updateSingleSelectionByKeyBoardKeys( RawKeyEvent event, _PickerView currentView) { - if (event.physicalKey == PhysicalKeyboardKey.arrowRight) { + if (event.logicalKey == LogicalKeyboardKey.arrowRight) { if (isSameDate(_pickerStateDetails.selectedDate, currentView.visibleDates[currentView.visibleDates.length - 1])) { _moveToNextViewWithAnimation(); } - return addDuration( - _pickerStateDetails.selectedDate, const Duration(days: 1)); - } else if (event.physicalKey == PhysicalKeyboardKey.arrowLeft) { + return addDays(_pickerStateDetails.selectedDate, 1); + } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { if (isSameDate( _pickerStateDetails.selectedDate, currentView.visibleDates[0])) { _moveToPreviousViewWithAnimation(); } - return subtractDuration( - _pickerStateDetails.selectedDate, const Duration(days: 1)); - } else if (event.physicalKey == PhysicalKeyboardKey.arrowUp) { - return subtractDuration(_pickerStateDetails.selectedDate, - const Duration(days: DateTime.daysPerWeek)); - } else if (event.physicalKey == PhysicalKeyboardKey.arrowDown) { - return addDuration(_pickerStateDetails.selectedDate, - const Duration(days: DateTime.daysPerWeek)); + return addDays(_pickerStateDetails.selectedDate, -1); + } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + return addDays(_pickerStateDetails.selectedDate, -DateTime.daysPerWeek); + } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + return addDays(_pickerStateDetails.selectedDate, DateTime.daysPerWeek); } return null; } dynamic _updateMultiAndRangeSelectionByKeyBoard(RawKeyEvent event) { if (event.isShiftPressed && - event.physicalKey == PhysicalKeyboardKey.arrowRight) { + event.logicalKey == LogicalKeyboardKey.arrowRight) { if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple) { - return addDuration( + return addDays( _pickerStateDetails - .selectedDates[_pickerStateDetails.selectedDates.length - 1], - const Duration(days: 1)); + .selectedDates![_pickerStateDetails.selectedDates!.length - 1], + 1); } else { - return addDuration(_pickerStateDetails.selectedRange.startDate, - const Duration(days: 1)); + return addDays(_pickerStateDetails.selectedRange.startDate, 1); } } else if (event.isShiftPressed && - event.physicalKey == PhysicalKeyboardKey.arrowLeft) { + event.logicalKey == LogicalKeyboardKey.arrowLeft) { if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple) { - return addDuration( + return addDays( _pickerStateDetails - .selectedDates[_pickerStateDetails.selectedDates.length - 1], - const Duration(days: -1)); + .selectedDates![_pickerStateDetails.selectedDates!.length - 1], + -1); } else { - return addDuration(_pickerStateDetails.selectedRange.startDate, - const Duration(days: -1)); + return addDays(_pickerStateDetails.selectedRange.startDate, -1); } } else if (event.isShiftPressed && - event.physicalKey == PhysicalKeyboardKey.arrowUp) { + event.logicalKey == LogicalKeyboardKey.arrowUp) { if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple) { - return addDuration( + return addDays( _pickerStateDetails - .selectedDates[_pickerStateDetails.selectedDates.length - 1], - const Duration(days: -DateTime.daysPerWeek)); + .selectedDates![_pickerStateDetails.selectedDates!.length - 1], + -DateTime.daysPerWeek); } else { - return addDuration(_pickerStateDetails.selectedRange.startDate, - const Duration(days: -DateTime.daysPerWeek)); + return addDays( + _pickerStateDetails.selectedRange.startDate, -DateTime.daysPerWeek); } } else if (event.isShiftPressed && - event.physicalKey == PhysicalKeyboardKey.arrowDown) { + event.logicalKey == LogicalKeyboardKey.arrowDown) { if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple) { - return addDuration( + return addDays( _pickerStateDetails - .selectedDates[_pickerStateDetails.selectedDates.length - 1], - const Duration(days: DateTime.daysPerWeek)); + .selectedDates![_pickerStateDetails.selectedDates!.length - 1], + DateTime.daysPerWeek); } else { - return addDuration(_pickerStateDetails.selectedRange.startDate, - const Duration(days: DateTime.daysPerWeek)); + return addDays( + _pickerStateDetails.selectedRange.startDate, DateTime.daysPerWeek); } } return null; @@ -6514,7 +8069,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> case DateRangePickerNavigationDirection.horizontal: { final double difference = - dragUpdateDetails.globalPosition.dx - _scrollStartPosition; + dragUpdateDetails.globalPosition.dx - _scrollStartPosition!; if (difference < 0 && !DateRangePickerHelper.canMoveToNextViewRtl( pickerView, @@ -6561,7 +8116,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> break; case DateRangePickerNavigationDirection.horizontal: { - _position ??= 0; + _position = _position != 0 ? _position : 0; // condition to check and update the right to left swiping if (-_position >= widget.width / 2) { _tween.begin = _position; @@ -6720,7 +8275,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> case DateRangePickerNavigationDirection.vertical: { final double difference = - dragUpdateDetails.globalPosition.dy - _scrollStartPosition; + dragUpdateDetails.globalPosition.dy - _scrollStartPosition!; if (difference < 0 && !DateRangePickerHelper.canMoveToNextView( pickerView, @@ -6760,7 +8315,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> break; case DateRangePickerNavigationDirection.vertical: { - _position ??= 0; + _position = _position != 0 ? _position : 0; // condition to check and update the bottom to top swiping if (-_position >= widget.height / 2) { _tween.begin = _position; @@ -6898,12 +8453,20 @@ class _PickerScrollViewState extends State<_PickerScrollView> @immutable class _PickerView extends StatefulWidget { /// Constructor to create picker view instance. - const _PickerView(this.picker, this.controller, this.visibleDates, this.width, - this.height, this.datePickerTheme, this.focusNode, this.textScaleFactor, - {Key key, - this.getPickerStateDetails, - this.updatePickerStateDetails, - this.isRtl}) + const _PickerView( + this.picker, + this.controller, + this.visibleDates, + this.enableMultiView, + this.width, + this.height, + this.datePickerTheme, + this.focusNode, + this.textScaleFactor, + {Key? key, + required this.getPickerStateDetails, + required this.updatePickerStateDetails, + this.isRtl = false}) : super(key: key); /// Holds the visible dates for the picker view. @@ -6912,6 +8475,8 @@ class _PickerView extends StatefulWidget { /// Holds the picker instance to access the picker details. final _SfDateRangePicker picker; + final bool enableMultiView; + /// Holds the controller details used on its state final dynamic controller; @@ -6934,7 +8499,7 @@ class _PickerView extends StatefulWidget { final bool isRtl; /// Holds the node and used to request the focus. - final FocusNode focusNode; + final FocusNode? focusNode; /// Defines the text scale factor of [SfDateRangePicker]. final double textScaleFactor; @@ -6946,14 +8511,14 @@ class _PickerView extends StatefulWidget { /// Handle the picker view children position and it interaction. class _PickerViewState extends State<_PickerView> with TickerProviderStateMixin { - PickerStateArgs _pickerStateDetails; + PickerStateArgs _pickerStateDetails = PickerStateArgs(); /// Holds the month view instance used to update selection from scroll view. - MonthView _monthView; + MonthView? _monthView; /// Holds the year view instance used to update selection from scroll view. - YearView _yearView; - ValueNotifier _mouseHoverPosition; + YearView? _yearView; + ValueNotifier _mouseHoverPosition = ValueNotifier(null); /// The date time property used to range selection to store the /// previous selected date value in range. @@ -6962,18 +8527,10 @@ class _PickerViewState extends State<_PickerView> //// drag start boolean variable used to identify whether the drag started or not //// For example., if user start drag from disabled date then the start date of the range not created //// so in drag update method update the end date of existing selected range. - bool _isDragStart; + bool _isDragStart = false; /// Defines wheter the current platform is mobile or not. - bool _isMobilePlatform; - - @override - void initState() { - _pickerStateDetails = PickerStateArgs(); - _mouseHoverPosition = ValueNotifier(null); - _isDragStart = false; - super.initState(); - } + bool _isMobilePlatform = true; @override Widget build(BuildContext context) { @@ -6990,12 +8547,15 @@ class _PickerViewState extends State<_PickerView> { return GestureDetector( child: MouseRegion( - onEnter: _pointerEnterEvent, - onHover: _pointerHoverEvent, - onExit: _pointerExitEvent, - child: - _addMonthView(locale, widget.datePickerTheme, localizations), - ), + onEnter: _pointerEnterEvent, + onHover: _pointerHoverEvent, + onExit: _pointerExitEvent, + child: Container( + width: widget.width, + height: widget.height, + child: _addMonthView( + locale, widget.datePickerTheme, localizations), + )), onTapUp: _updateTapCallback, onHorizontalDragStart: _getDragStartCallback(), onVerticalDragStart: _getDragStartCallback(), @@ -7022,8 +8582,6 @@ class _PickerViewState extends State<_PickerView> ); } } - - return null; } /// Used to draw the selection on month view. @@ -7108,7 +8666,7 @@ class _PickerViewState extends State<_PickerView> widget.picker.selectionShape, widget.picker.selectionRadius, _mouseHoverPosition, - widget.picker.enableMultiView, + widget.enableMultiView, widget.picker.viewSpacing, ValueNotifier(false), widget.textScaleFactor, @@ -7135,12 +8693,12 @@ class _PickerViewState extends State<_PickerView> final Color todayTextColor = widget.picker.monthCellStyle.todayTextStyle != null && - widget.picker.monthCellStyle.todayTextStyle.color != null - ? widget.picker.monthCellStyle.todayTextStyle.color + widget.picker.monthCellStyle.todayTextStyle!.color != null + ? widget.picker.monthCellStyle.todayTextStyle!.color! : (widget.picker.todayHighlightColor != null && widget.picker.todayHighlightColor != Colors.transparent - ? widget.picker.todayHighlightColor - : widget.datePickerTheme.todayHighlightColor); + ? widget.picker.todayHighlightColor! + : widget.datePickerTheme.todayHighlightColor!); return Positioned( left: 0, @@ -7155,6 +8713,7 @@ class _PickerViewState extends State<_PickerView> child: CustomPaint( painter: _PickerViewHeaderPainter( widget.visibleDates, + widget.picker.navigationMode, widget.picker.monthViewSettings.viewHeaderStyle, viewHeaderHeight, widget.picker.monthViewSettings, @@ -7162,7 +8721,7 @@ class _PickerViewState extends State<_PickerView> locale, widget.isRtl, widget.picker.monthCellStyle, - widget.picker.enableMultiView, + widget.enableMultiView, widget.picker.viewSpacing, todayTextColor, widget.textScaleFactor, @@ -7208,8 +8767,8 @@ class _PickerViewState extends State<_PickerView> } } - if (!widget.focusNode.hasFocus) { - widget.focusNode.requestFocus(); + if (widget.focusNode != null && !widget.focusNode!.hasFocus) { + widget.focusNode!.requestFocus(); } } @@ -7220,8 +8779,9 @@ class _PickerViewState extends State<_PickerView> final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView(widget.controller.view); - final RenderBox box = context.findRenderObject(); - final Offset localPosition = box.globalToLocal(globalPosition); + final RenderObject renderObject = context.findRenderObject()!; + final RenderBox? box = renderObject is RenderBox ? renderObject : null; + final Offset localPosition = box!.globalToLocal(globalPosition); final double viewHeaderHeight = pickerView == DateRangePickerView.month && widget.picker.navigationDirection == DateRangePickerNavigationDirection.horizontal @@ -7270,7 +8830,7 @@ class _PickerViewState extends State<_PickerView> widget.datePickerTheme, locale, _mouseHoverPosition, - widget.picker.enableMultiView, + widget.enableMultiView, widget.picker.viewSpacing, widget.picker.selectionTextStyle, widget.picker.rangeTextStyle, @@ -7293,13 +8853,15 @@ class _PickerViewState extends State<_PickerView> widget.height); } - GestureDragStartCallback _getDragStartCallback() { + GestureDragStartCallback? _getDragStartCallback() { final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView(widget.controller.view); //// return drag start start event when selection mode as range or multi range. if ((pickerView != DateRangePickerView.month && widget.picker.allowViewNavigation) || - !widget.picker.monthViewSettings.enableSwipeSelection) { + !_isSwipeInteractionEnabled( + widget.picker.monthViewSettings.enableSwipeSelection, + widget.picker.navigationMode)) { return null; } @@ -7319,17 +8881,17 @@ class _PickerViewState extends State<_PickerView> case DateRangePickerView.century: return _dragStartOnYear; } - - return null; } - GestureDragUpdateCallback _getDragUpdateCallback() { + GestureDragUpdateCallback? _getDragUpdateCallback() { final DateRangePickerView pickerView = DateRangePickerHelper.getPickerView(widget.controller.view); //// return drag update start event when selection mode as range or multi range. if ((pickerView != DateRangePickerView.month && widget.picker.allowViewNavigation) || - !widget.picker.monthViewSettings.enableSwipeSelection) { + !_isSwipeInteractionEnabled( + widget.picker.monthViewSettings.enableSwipeSelection, + widget.picker.navigationMode)) { return null; } @@ -7351,8 +8913,6 @@ class _PickerViewState extends State<_PickerView> return _dragUpdateOnYear; } } - - return null; } int _getYearViewIndex(double xPosition, double yPosition) { @@ -7362,7 +8922,7 @@ class _PickerViewState extends State<_PickerView> double height = widget.height; int rowCount = YearView.maxRowCount; int index = -1; - if (widget.picker.enableMultiView) { + if (widget.enableMultiView) { switch (widget.picker.navigationDirection) { case DateRangePickerNavigationDirection.horizontal: { @@ -7412,7 +8972,7 @@ class _PickerViewState extends State<_PickerView> if (widget.isRtl) { rowIndex = DateRangePickerHelper.getRtlIndex(columnCount, rowIndex); - if (widget.picker.enableMultiView && + if (widget.enableMultiView && widget.picker.navigationDirection == DateRangePickerNavigationDirection.vertical) { if (columnIndex > YearView.maxColumnCount) { @@ -7427,7 +8987,7 @@ class _PickerViewState extends State<_PickerView> index = (columnIndex * YearView.maxColumnCount) + ((rowIndex ~/ YearView.maxColumnCount) * totalDatesCount) + (rowIndex % YearView.maxColumnCount); - return widget.picker.enableMultiView && + return widget.enableMultiView && DateRangePickerHelper.isLeadingCellDate( index, (index ~/ totalDatesCount) * totalDatesCount, @@ -7446,7 +9006,7 @@ class _PickerViewState extends State<_PickerView> final int rowCount = DateRangePickerHelper.getNumberOfWeeksInView( widget.picker.monthViewSettings, widget.picker.isHijri); int totalRowCount = rowCount; - if (widget.picker.enableMultiView) { + if (widget.enableMultiView) { switch (widget.picker.navigationDirection) { case DateRangePickerNavigationDirection.horizontal: { @@ -7506,7 +9066,7 @@ class _PickerViewState extends State<_PickerView> if (widget.isRtl) { rowIndex = DateRangePickerHelper.getRtlIndex(totalColumnCount, rowIndex); - if (widget.picker.enableMultiView && + if (widget.enableMultiView && widget.picker.navigationDirection == DateRangePickerNavigationDirection.vertical) { if (columnIndex >= rowCount) { @@ -7579,7 +9139,7 @@ class _PickerViewState extends State<_PickerView> _previousSelectedDate = selectedDate; widget.updatePickerStateDetails(_pickerStateDetails); - _monthView.selectionNotifier.value = !_monthView.selectionNotifier.value; + _monthView!.selectionNotifier.value = !_monthView!.selectionNotifier.value; } void _dragUpdate(DragUpdateDetails details) { @@ -7635,7 +9195,7 @@ class _PickerViewState extends State<_PickerView> //// Set drag start value as false, identifies the start date of the range updated. _isDragStart = true; widget.updatePickerStateDetails(_pickerStateDetails); - _monthView.selectionNotifier.value = !_monthView.selectionNotifier.value; + _monthView!.selectionNotifier.value = !_monthView!.selectionNotifier.value; } void _updateSelectedRangesOnDragStart(dynamic view, dynamic selectedDate) { @@ -7653,13 +9213,13 @@ class _PickerViewState extends State<_PickerView> case DateRangePickerSelectionMode.multiRange: { _pickerStateDetails.selectedRanges ??= []; - _pickerStateDetails.selectedRanges.add(widget.picker.isHijri + _pickerStateDetails.selectedRanges!.add(widget.picker.isHijri ? HijriDateRange(selectedDate, null) : PickerDateRange(selectedDate, null)); _removeInterceptRanges( - _pickerStateDetails.selectedRanges, - _pickerStateDetails.selectedRanges[ - _pickerStateDetails.selectedRanges.length - 1]); + _pickerStateDetails.selectedRanges!, + _pickerStateDetails.selectedRanges![ + _pickerStateDetails.selectedRanges!.length - 1]); } } } @@ -7698,15 +9258,15 @@ class _PickerViewState extends State<_PickerView> case DateRangePickerSelectionMode.multiRange: { _pickerStateDetails.selectedRanges ??= []; - final int count = _pickerStateDetails.selectedRanges.length; + final int count = _pickerStateDetails.selectedRanges!.length; dynamic _lastRange; if (count > 0) { - _lastRange = _pickerStateDetails.selectedRanges[count - 1]; + _lastRange = _pickerStateDetails.selectedRanges![count - 1]; } //// Check the start date of the range updated or not, if not updated then create the new range. if (!_isDragStart) { - _pickerStateDetails.selectedRanges.add(widget.picker.isHijri + _pickerStateDetails.selectedRanges!.add(widget.picker.isHijri ? HijriDateRange(selectedDate, null) : PickerDateRange(selectedDate, null)); } else { @@ -7718,18 +9278,18 @@ class _PickerViewState extends State<_PickerView> return; } - _pickerStateDetails.selectedRanges[count - 1] = updatedRange; + _pickerStateDetails.selectedRanges![count - 1] = updatedRange; } else { - _pickerStateDetails.selectedRanges.add(widget.picker.isHijri + _pickerStateDetails.selectedRanges!.add(widget.picker.isHijri ? HijriDateRange(selectedDate, null) : PickerDateRange(selectedDate, null)); } } _removeInterceptRanges( - _pickerStateDetails.selectedRanges, - _pickerStateDetails.selectedRanges[ - _pickerStateDetails.selectedRanges.length - 1]); + _pickerStateDetails.selectedRanges!, + _pickerStateDetails.selectedRanges![ + _pickerStateDetails.selectedRanges!.length - 1]); } } } @@ -7913,15 +9473,15 @@ class _PickerViewState extends State<_PickerView> case DateRangePickerSelectionMode.multiRange: { _pickerStateDetails.selectedRanges ??= []; - final int count = _pickerStateDetails.selectedRanges.length; + final int count = _pickerStateDetails.selectedRanges!.length; dynamic _lastRange; if (count > 0) { - _lastRange = _pickerStateDetails.selectedRanges[count - 1]; + _lastRange = _pickerStateDetails.selectedRanges![count - 1]; } //// Check the start date of the range updated or not, if not updated then create the new range. if (!_isDragStart) { - _pickerStateDetails.selectedRanges.add(widget.picker.isHijri + _pickerStateDetails.selectedRanges!.add(widget.picker.isHijri ? HijriDateRange(selectedDate, null) : PickerDateRange(selectedDate, null)); } else { @@ -7933,18 +9493,18 @@ class _PickerViewState extends State<_PickerView> return; } - _pickerStateDetails.selectedRanges[count - 1] = updatedRange; + _pickerStateDetails.selectedRanges![count - 1] = updatedRange; } else { - _pickerStateDetails.selectedRanges.add(widget.picker.isHijri + _pickerStateDetails.selectedRanges!.add(widget.picker.isHijri ? HijriDateRange(selectedDate, null) : PickerDateRange(selectedDate, null)); } } _removeInterceptRanges( - _pickerStateDetails.selectedRanges, - _pickerStateDetails.selectedRanges[ - _pickerStateDetails.selectedRanges.length - 1]); + _pickerStateDetails.selectedRanges!, + _pickerStateDetails.selectedRanges![ + _pickerStateDetails.selectedRanges!.length - 1]); } } } @@ -7976,7 +9536,7 @@ class _PickerViewState extends State<_PickerView> _previousSelectedDate = selectedDate; widget.updatePickerStateDetails(_pickerStateDetails); - _yearView.selectionNotifier.value = !_yearView.selectionNotifier.value; + _yearView!.selectionNotifier.value = !_yearView!.selectionNotifier.value; } void _dragUpdateOnYear(DragUpdateDetails details) { @@ -8004,7 +9564,7 @@ class _PickerViewState extends State<_PickerView> //// Set drag start value as false, identifies the start date of the range updated. _isDragStart = true; widget.updatePickerStateDetails(_pickerStateDetails); - _yearView.selectionNotifier.value = !_yearView.selectionNotifier.value; + _yearView!.selectionNotifier.value = !_yearView!.selectionNotifier.value; } void _handleTouch(Offset details, TapUpDetails tapUpDetails) { @@ -8046,7 +9606,8 @@ class _PickerViewState extends State<_PickerView> _drawSelection(selectedDate); widget.updatePickerStateDetails(_pickerStateDetails); - _monthView.selectionNotifier.value = !_monthView.selectionNotifier.value; + _monthView!.selectionNotifier.value = + !_monthView!.selectionNotifier.value; } } @@ -8055,7 +9616,7 @@ class _PickerViewState extends State<_PickerView> widget.picker.monthViewSettings, widget.picker.isHijri) * DateTime.daysPerWeek; int currentMonthIndex = datesCount ~/ 2; - if (widget.picker.enableMultiView && index >= datesCount) { + if (widget.enableMultiView && index >= datesCount) { currentMonthIndex += datesCount; } @@ -8075,18 +9636,18 @@ class _PickerViewState extends State<_PickerView> void _drawMultipleSelectionForYear(dynamic selectedDate) { int selectedIndex = -1; if (_pickerStateDetails.selectedDates != null && - _pickerStateDetails.selectedDates.isNotEmpty) { + _pickerStateDetails.selectedDates!.isNotEmpty) { selectedIndex = DateRangePickerHelper.getDateCellIndex( - _pickerStateDetails.selectedDates, + _pickerStateDetails.selectedDates!, selectedDate, widget.controller.view); } if (selectedIndex == -1) { _pickerStateDetails.selectedDates ??= []; - _pickerStateDetails.selectedDates.add(selectedDate); + _pickerStateDetails.selectedDates!.add(selectedDate); } else { - _pickerStateDetails.selectedDates.removeAt(selectedIndex); + _pickerStateDetails.selectedDates!.removeAt(selectedIndex); } } @@ -8132,10 +9693,10 @@ class _PickerViewState extends State<_PickerView> void _drawRangesSelectionForYear(dynamic selectedDate) { _pickerStateDetails.selectedRanges ??= []; - int count = _pickerStateDetails.selectedRanges.length; + int count = _pickerStateDetails.selectedRanges!.length; dynamic _lastRange; if (count > 0) { - _lastRange = _pickerStateDetails.selectedRanges[count - 1]; + _lastRange = _pickerStateDetails.selectedRanges![count - 1]; } if (_lastRange != null && @@ -8168,25 +9729,25 @@ class _PickerViewState extends State<_PickerView> final dynamic newRange = widget.picker.isHijri ? HijriDateRange(startDate, endDate) : PickerDateRange(startDate, endDate); - _pickerStateDetails.selectedRanges[count - 1] = newRange; + _pickerStateDetails.selectedRanges![count - 1] = newRange; } else { - _pickerStateDetails.selectedRanges.add(widget.picker.isHijri + _pickerStateDetails.selectedRanges!.add(widget.picker.isHijri ? HijriDateRange(selectedDate, null) : PickerDateRange(selectedDate, null)); } - count = _pickerStateDetails.selectedRanges.length; + count = _pickerStateDetails.selectedRanges!.length; _removeInterceptRanges( - _pickerStateDetails.selectedRanges, + _pickerStateDetails.selectedRanges!, _pickerStateDetails - .selectedRanges[_pickerStateDetails.selectedRanges.length - 1]); + .selectedRanges![_pickerStateDetails.selectedRanges!.length - 1]); _lastRange = _pickerStateDetails - .selectedRanges[_pickerStateDetails.selectedRanges.length - 1]; - if (count != _pickerStateDetails.selectedRanges.length && + .selectedRanges![_pickerStateDetails.selectedRanges!.length - 1]; + if (count != _pickerStateDetails.selectedRanges!.length && (_lastRange.endDate == null || DateRangePickerHelper.isSameCellDates(_lastRange.endDate, _lastRange.startDate, widget.controller.view))) { - _pickerStateDetails.selectedRanges.removeLast(); + _pickerStateDetails.selectedRanges!.removeLast(); } } @@ -8208,7 +9769,7 @@ class _PickerViewState extends State<_PickerView> void _handleYearPanelSelection(Offset details) { final int _selectedIndex = _getYearViewIndex(details.dx, details.dy); - final int viewCount = widget.picker.enableMultiView ? 2 : 1; + final int viewCount = widget.enableMultiView ? 2 : 1; if (_selectedIndex == -1 || _selectedIndex >= 12 * viewCount) { return; } @@ -8228,7 +9789,7 @@ class _PickerViewState extends State<_PickerView> _drawYearCellSelection(date); widget.updatePickerStateDetails(_pickerStateDetails); - _yearView.selectionNotifier.value = !_yearView.selectionNotifier.value; + _yearView!.selectionNotifier.value = !_yearView!.selectionNotifier.value; return; } @@ -8294,12 +9855,12 @@ class _PickerViewState extends State<_PickerView> void _drawMultipleSelectionForMonth(dynamic selectedDate) { final int selectedIndex = DateRangePickerHelper.isDateIndexInCollection( - _pickerStateDetails.selectedDates, selectedDate); + _pickerStateDetails.selectedDates!, selectedDate); if (selectedIndex == -1) { _pickerStateDetails.selectedDates ??= []; - _pickerStateDetails.selectedDates.add(selectedDate); + _pickerStateDetails.selectedDates!.add(selectedDate); } else { - _pickerStateDetails.selectedDates.removeAt(selectedIndex); + _pickerStateDetails.selectedDates!.removeAt(selectedIndex); } } @@ -8329,10 +9890,10 @@ class _PickerViewState extends State<_PickerView> void _drawRangesSelectionForMonth(dynamic selectedDate) { _pickerStateDetails.selectedRanges ??= []; - int count = _pickerStateDetails.selectedRanges.length; + int count = _pickerStateDetails.selectedRanges!.length; dynamic lastRange; if (count > 0) { - lastRange = _pickerStateDetails.selectedRanges[count - 1]; + lastRange = _pickerStateDetails.selectedRanges![count - 1]; } if (lastRange != null && @@ -8350,28 +9911,28 @@ class _PickerViewState extends State<_PickerView> final dynamic _newRange = widget.picker.isHijri ? HijriDateRange(startDate, endDate) : PickerDateRange(startDate, endDate); - _pickerStateDetails.selectedRanges[count - 1] = _newRange; + _pickerStateDetails.selectedRanges![count - 1] = _newRange; } else { - _pickerStateDetails.selectedRanges.add(widget.picker.isHijri + _pickerStateDetails.selectedRanges!.add(widget.picker.isHijri ? HijriDateRange(selectedDate, null) : PickerDateRange(selectedDate, null)); } - count = _pickerStateDetails.selectedRanges.length; + count = _pickerStateDetails.selectedRanges!.length; _removeInterceptRanges( - _pickerStateDetails.selectedRanges, + _pickerStateDetails.selectedRanges!, _pickerStateDetails - .selectedRanges[_pickerStateDetails.selectedRanges.length - 1]); + .selectedRanges![_pickerStateDetails.selectedRanges!.length - 1]); lastRange = _pickerStateDetails - .selectedRanges[_pickerStateDetails.selectedRanges.length - 1]; - if (count != _pickerStateDetails.selectedRanges.length && + .selectedRanges![_pickerStateDetails.selectedRanges!.length - 1]; + if (count != _pickerStateDetails.selectedRanges!.length && (lastRange.endDate == null || isSameDate(lastRange.endDate, lastRange.startDate))) { - _pickerStateDetails.selectedRanges.removeLast(); + _pickerStateDetails.selectedRanges!.removeLast(); } } - int _removeInterceptRangesForMonth(dynamic range, dynamic startDate, + int? _removeInterceptRangesForMonth(dynamic range, dynamic startDate, dynamic endDate, int i, dynamic selectedRangeValue) { if (range != null && !DateRangePickerHelper.isRangeEquals(range, selectedRangeValue) && @@ -8413,7 +9974,7 @@ class _PickerViewState extends State<_PickerView> return null; } - int _removeInterceptRangesForYear(dynamic range, dynamic startDate, + int? _removeInterceptRangesForYear(dynamic range, dynamic startDate, dynamic endDate, int i, dynamic selectedRangeValue) { if (range == null || DateRangePickerHelper.isRangeEquals(range, selectedRangeValue)) { @@ -8512,7 +10073,7 @@ class _PickerViewState extends State<_PickerView> } void _removeInterceptRanges( - List selectedRanges, dynamic selectedRangeValue) { + List? selectedRanges, dynamic? selectedRangeValue) { if (selectedRanges == null || selectedRanges.isEmpty || selectedRangeValue == null) { @@ -8537,7 +10098,7 @@ class _PickerViewState extends State<_PickerView> //// Check the selected start date placed in between range start or end date //// Check the selected end date placed in between range start or end date //// Check the selected range occupies the range. - int index; + int? index; switch (_pickerStateDetails.view) { case DateRangePickerView.month: { @@ -8564,3 +10125,234 @@ class _PickerViewState extends State<_PickerView> } } } + +String _getMonthHeaderText( + int startIndex, + int endIndex, + List dates, + int middleIndex, + int datesCount, + bool isHijri, + int numberOfWeeksInView, + String? monthFormat, + bool enableMultiView, + DateRangePickerHeaderStyle headerStyle, + DateRangePickerNavigationDirection navigationDirection, + Locale locale, + SfLocalizations localizations) { + if ((!isHijri && numberOfWeeksInView != 6) && + dates[startIndex].month != dates[endIndex].month) { + final String monthTextFormat = + monthFormat == null || monthFormat.isEmpty ? 'MMM' : monthFormat; + int endIndex = dates.length - 1; + if (enableMultiView && headerStyle.textAlign == TextAlign.center) { + endIndex = endIndex; + } + + final String startText = DateFormat(monthTextFormat, locale.toString()) + .format(dates[startIndex]) + .toString() + + ' ' + + dates[startIndex].year.toString(); + final String endText = DateFormat(monthTextFormat, locale.toString()) + .format(dates[endIndex]) + .toString() + + ' ' + + dates[endIndex].year.toString(); + if (startText == endText) { + return startText; + } + + return startText + ' - ' + endText; + } else { + final String monthTextFormat = monthFormat == null || monthFormat.isEmpty + ? enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.vertical + ? 'MMM' + : 'MMMM' + : monthFormat; + String text; + dynamic middleDate = dates[middleIndex]; + if (isHijri) { + text = DateRangePickerHelper.getHijriMonthText( + middleDate, localizations, monthTextFormat) + + ' ' + + middleDate.year.toString(); + } else { + text = DateFormat(monthTextFormat, locale.toString()) + .format(middleDate) + .toString() + + ' ' + + middleDate.year.toString(); + } + + /// To restrict the double header when the number of weeks in view given + /// and the dates were the same month. + if (enableMultiView && + navigationDirection == DateRangePickerNavigationDirection.vertical && + numberOfWeeksInView != 6 && + dates[startIndex].month == dates[endIndex].month) { + return text; + } + + if ((enableMultiView && headerStyle.textAlign != TextAlign.center) || + (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.vertical)) { + middleDate = dates[datesCount + middleIndex]; + if (isHijri) { + return text + + ' - ' + + DateRangePickerHelper.getHijriMonthText( + middleDate, localizations, monthTextFormat) + + ' ' + + middleDate.year.toString(); + } else { + return text + + ' - ' + + DateFormat(monthTextFormat, locale.toString()) + .format(middleDate) + .toString() + + ' ' + + middleDate.year.toString(); + } + } + + return text; + } +} + +String _getHeaderText( + List dates, + DateRangePickerView view, + int index, + bool isHijri, + int numberOfWeeksInView, + String? monthFormat, + bool enableMultiView, + DateRangePickerHeaderStyle headerStyle, + DateRangePickerNavigationDirection navigationDirection, + Locale locale, + SfLocalizations localizations) { + final int count = enableMultiView ? 2 : 1; + final int datesCount = dates.length ~/ count; + final int startIndex = index * datesCount; + final int endIndex = ((index + 1) * datesCount) - 1; + final int middleIndex = startIndex + (datesCount ~/ 2); + switch (view) { + case DateRangePickerView.month: + { + return _getMonthHeaderText( + startIndex, + endIndex, + dates, + middleIndex, + datesCount, + isHijri, + numberOfWeeksInView, + monthFormat, + enableMultiView, + headerStyle, + navigationDirection, + locale, + localizations); + } + case DateRangePickerView.year: + { + final dynamic date = dates[middleIndex]; + if ((enableMultiView && headerStyle.textAlign != TextAlign.center) || + (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.vertical)) { + return date.year.toString() + + ' - ' + + dates[datesCount + middleIndex].year.toString(); + } + + return date.year.toString(); + } + case DateRangePickerView.decade: + { + final int year = (dates[middleIndex].year ~/ 10) * 10; + if ((enableMultiView && headerStyle.textAlign != TextAlign.center) || + (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.vertical)) { + return year.toString() + + ' - ' + + (((dates[datesCount + middleIndex].year ~/ 10) * 10) + 9) + .toString(); + } + + return year.toString() + ' - ' + (year + 9).toString(); + } + case DateRangePickerView.century: + { + final int year = (dates[middleIndex].year ~/ 100) * 100; + if ((enableMultiView && headerStyle.textAlign != TextAlign.center) || + (enableMultiView && + navigationDirection == + DateRangePickerNavigationDirection.vertical)) { + return year.toString() + + ' - ' + + (((dates[datesCount + middleIndex].year ~/ 100) * 100) + 99) + .toString(); + } + + return year.toString() + ' - ' + (year + 99).toString(); + } + } +} + +Size _getTextWidgetWidth( + String text, double height, double width, BuildContext context, + {required TextStyle style, + double widthPadding = 10, + double heightPadding = 10}) { + /// Create new text with it style. + final Widget textWidget = Text( + text, + style: style, + maxLines: 1, + softWrap: false, + textDirection: TextDirection.ltr, + textAlign: TextAlign.left, + ).build(context); + + final RichText? richTextWidget = textWidget is RichText ? textWidget : null; + + /// Create and layout the render object based on + /// allocated width and height. + final renderObject = richTextWidget!.createRenderObject(context); + renderObject.layout(BoxConstraints( + minWidth: width, + maxWidth: width, + minHeight: height, + maxHeight: height, + )); + + /// Get the size of text by using render object. + final List textBox = renderObject.getBoxesForSelection( + TextSelection(baseOffset: 0, extentOffset: text.length)); + double textWidth = 0; + double textHeight = 0; + for (final TextBox box in textBox) { + textWidth += box.right - box.left; + final double currentBoxHeight = box.bottom - box.top; + textHeight = textHeight > currentBoxHeight ? textHeight : currentBoxHeight; + } + + return Size(textWidth + widthPadding, textHeight + heightPadding); +} + +bool _isSwipeInteractionEnabled( + bool enableSwipeSelection, DateRangePickerNavigationMode navigationMode) { + return enableSwipeSelection && + navigationMode != DateRangePickerNavigationMode.scroll; +} + +bool _isMultiViewEnabled(_SfDateRangePicker picker) { + return picker.enableMultiView && + picker.navigationMode != DateRangePickerNavigationMode.scroll; +} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker_manager.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker_manager.dart index 0ebb89998..d63126e0f 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker_manager.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker_manager.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import 'picker_helper.dart'; @@ -27,7 +28,7 @@ import 'picker_helper.dart'; /// /// ``` @immutable -class DateRangePickerHeaderStyle { +class DateRangePickerHeaderStyle with Diagnosticable { /// Creates a header style for date range picker. const DateRangePickerHeaderStyle( {this.textAlign = TextAlign.left, this.backgroundColor, this.textStyle}); @@ -59,7 +60,7 @@ class DateRangePickerHeaderStyle { /// } /// /// ``` - final TextStyle textStyle; + final TextStyle? textStyle; /// How the text should be aligned horizontally in [SfDateRangePicker] header /// view. @@ -109,7 +110,7 @@ class DateRangePickerHeaderStyle { /// } /// /// ``` - final Color backgroundColor; + final Color? backgroundColor; @override bool operator ==(dynamic other) { @@ -126,6 +127,14 @@ class DateRangePickerHeaderStyle { otherStyle.backgroundColor == backgroundColor; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(EnumProperty('textAlign', textAlign)); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + properties.add(DiagnosticsProperty('textStyle', textStyle)); + } + @override int get hashCode { return hashValues( @@ -165,7 +174,7 @@ class DateRangePickerHeaderStyle { /// /// ``` @immutable -class DateRangePickerViewHeaderStyle { +class DateRangePickerViewHeaderStyle with Diagnosticable { /// creates a view header style for month view in [SfDateRangePicker]. const DateRangePickerViewHeaderStyle({this.backgroundColor, this.textStyle}); @@ -196,7 +205,7 @@ class DateRangePickerViewHeaderStyle { /// } /// /// ``` - final Color backgroundColor; + final Color? backgroundColor; /// The text style for the text in the [SfDateRangePicker] view header view of /// month view. @@ -228,7 +237,7 @@ class DateRangePickerViewHeaderStyle { /// } /// /// ``` - final TextStyle textStyle; + final TextStyle? textStyle; @override bool operator ==(dynamic other) { @@ -244,6 +253,13 @@ class DateRangePickerViewHeaderStyle { otherStyle.textStyle == textStyle; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(ColorProperty('backgroundColor', backgroundColor)); + properties.add(DiagnosticsProperty('textStyle', textStyle)); + } + @override int get hashCode { return hashValues( @@ -299,7 +315,7 @@ class DateRangePickerViewHeaderStyle { /// /// ``` @immutable -class DateRangePickerMonthViewSettings { +class DateRangePickerMonthViewSettings with Diagnosticable { /// Creates a date range picker month view settings for date range picker. /// /// The properties allows to customize the month view of [SfDateRangePicker]. @@ -310,16 +326,13 @@ class DateRangePickerMonthViewSettings { this.viewHeaderHeight = 30, @Deprecated('Use selectionRadius property in SfDateRangePicker') // ignore: deprecated_member_use, deprecated_member_use_from_same_package - this.selectionRadius, + this.selectionRadius = -1, this.showTrailingAndLeadingDates = false, - DateRangePickerViewHeaderStyle viewHeaderStyle, + this.viewHeaderStyle = const DateRangePickerViewHeaderStyle(), this.enableSwipeSelection = true, this.blackoutDates, this.specialDates, - List weekendDays}) - : viewHeaderStyle = - viewHeaderStyle ?? const DateRangePickerViewHeaderStyle(), - weekendDays = weekendDays ?? const [6, 7]; + this.weekendDays = const [6, 7]}); /// Formats a text in the [SfDateRangePicker] month view view header. /// @@ -585,7 +598,7 @@ class DateRangePickerMonthViewSettings { /// } /// /// ``` - final List blackoutDates; + final List? blackoutDates; /// In the month view of [SfDateRangePicker] highlights the unique dates with /// different style rather than the other dates style. @@ -622,7 +635,7 @@ class DateRangePickerMonthViewSettings { /// } /// /// ``` - final List specialDates; + final List? specialDates; /// The weekends for month view in [SfDateRangePicker]. /// @@ -674,6 +687,25 @@ class DateRangePickerMonthViewSettings { otherStyle.enableSwipeSelection == enableSwipeSelection; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(IterableDiagnostics(blackoutDates) + .toDiagnosticsNode(name: 'blackoutDates')); + properties.add(IterableDiagnostics(specialDates) + .toDiagnosticsNode(name: 'specialDates')); + properties.add(IntProperty('numberOfWeeksInView', numberOfWeeksInView)); + properties.add(IntProperty('firstDayOfWeek', firstDayOfWeek)); + properties.add(DoubleProperty('viewHeaderHeight', viewHeaderHeight)); + properties.add(StringProperty('dayFormat', dayFormat)); + properties.add(DiagnosticsProperty( + 'showTrailingAndLeadingDates', showTrailingAndLeadingDates)); + properties.add(DiagnosticsProperty( + 'enableSwipeSelection', enableSwipeSelection)); + properties.add(viewHeaderStyle.toDiagnosticsNode(name: 'viewHeaderStyle')); + properties.add(IterableProperty('weekendDays', weekendDays)); + } + @override int get hashCode { return hashValues( @@ -684,9 +716,9 @@ class DateRangePickerMonthViewSettings { viewHeaderHeight, showTrailingAndLeadingDates, numberOfWeeksInView, - specialDates, - blackoutDates, - weekendDays); + hashList(specialDates), + hashList(blackoutDates), + hashList(weekendDays)); } } @@ -732,7 +764,7 @@ class DateRangePickerMonthViewSettings { /// /// ``` @immutable -class DateRangePickerYearCellStyle { +class DateRangePickerYearCellStyle with Diagnosticable { /// Creates a date range picker year cell style for date range picker. /// /// The properties allows to customize the year cells in year view of @@ -776,7 +808,7 @@ class DateRangePickerYearCellStyle { /// } /// /// ``` - final TextStyle textStyle; + final TextStyle? textStyle; /// The text style for the text in the today cell of [SfDateRangePicker] /// year, decade and century view. @@ -807,7 +839,7 @@ class DateRangePickerYearCellStyle { /// } /// /// ``` - final TextStyle todayTextStyle; + final TextStyle? todayTextStyle; /// The text style for the text in the leading dates cells of /// [SfDateRangePicker] year, decade and century view. @@ -840,7 +872,7 @@ class DateRangePickerYearCellStyle { /// } /// /// ``` - final TextStyle leadingDatesTextStyle; + final TextStyle? leadingDatesTextStyle; /// The text style for the text in the disabled dates cell of /// [SfDateRangePicker] year, decade and century view. @@ -873,7 +905,7 @@ class DateRangePickerYearCellStyle { /// } /// /// ``` - final TextStyle disabledDatesTextStyle; + final TextStyle? disabledDatesTextStyle; /// The decoration for the disabled cells of [SfDateRangePicker] /// year, decade and century view. @@ -908,7 +940,7 @@ class DateRangePickerYearCellStyle { /// } /// /// ``` - final Decoration disabledDatesDecoration; + final Decoration? disabledDatesDecoration; /// The decoration for the cells of [SfDateRangePicker] year, decade /// and century view. @@ -940,7 +972,7 @@ class DateRangePickerYearCellStyle { /// } /// /// ``` - final Decoration cellDecoration; + final Decoration? cellDecoration; /// The decoration for the today cell of [SfDateRangePicker] year, decade /// and century view. @@ -972,7 +1004,7 @@ class DateRangePickerYearCellStyle { /// } /// /// ``` - final Decoration todayCellDecoration; + final Decoration? todayCellDecoration; /// The decoration for the leading date cells of [SfDateRangePicker] /// year, decade and century view. @@ -1007,7 +1039,7 @@ class DateRangePickerYearCellStyle { /// } /// /// ``` - final Decoration leadingDatesDecoration; + final Decoration? leadingDatesDecoration; @override bool operator ==(dynamic other) { @@ -1029,6 +1061,26 @@ class DateRangePickerYearCellStyle { otherStyle.disabledDatesTextStyle == disabledDatesTextStyle; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('textStyle', textStyle)); + properties + .add(DiagnosticsProperty('todayTextStyle', todayTextStyle)); + properties.add(DiagnosticsProperty( + 'leadingDatesTextStyle', leadingDatesTextStyle)); + properties.add(DiagnosticsProperty( + 'disabledDatesTextStyle', disabledDatesTextStyle)); + properties.add(DiagnosticsProperty( + 'disabledDatesDecoration', disabledDatesDecoration)); + properties + .add(DiagnosticsProperty('cellDecoration', cellDecoration)); + properties.add(DiagnosticsProperty( + 'todayCellDecoration', todayCellDecoration)); + properties.add(DiagnosticsProperty( + 'leadingDatesDecoration', leadingDatesDecoration)); + } + @override int get hashCode { return hashValues( @@ -1088,7 +1140,7 @@ class DateRangePickerYearCellStyle { /// /// ``` @immutable -class DateRangePickerMonthCellStyle { +class DateRangePickerMonthCellStyle with Diagnosticable { /// Creates a date range picker month cell style for date range picker. /// /// The properties allows to customize the month cells in month view of @@ -1159,7 +1211,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final TextStyle textStyle; + final TextStyle? textStyle; /// The text style for the text in the today cell of [SfDateRangePicker] /// month view. @@ -1192,7 +1244,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final TextStyle todayTextStyle; + final TextStyle? todayTextStyle; /// The text style for the text in the trailing dates cell of /// [SfDateRangePicker] month view. @@ -1234,7 +1286,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final TextStyle trailingDatesTextStyle; + final TextStyle? trailingDatesTextStyle; /// The text style for the text in the leading dates cell of /// [SfDateRangePicker] month view. @@ -1276,7 +1328,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final TextStyle leadingDatesTextStyle; + final TextStyle? leadingDatesTextStyle; /// The text style for the text in the disabled dates cell of /// [SfDateRangePicker] month view. @@ -1313,7 +1365,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final TextStyle disabledDatesTextStyle; + final TextStyle? disabledDatesTextStyle; /// The text style for the text in the selected date or dates cell of /// [SfDateRangePicker] month view. @@ -1346,7 +1398,7 @@ class DateRangePickerMonthCellStyle { /// /// ``` @Deprecated('Use selectionTextStyle property in SfDateRangePicker') - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; /// The text style for the text in the selected range or ranges cell of /// [SfDateRangePicker] month view. @@ -1390,7 +1442,7 @@ class DateRangePickerMonthCellStyle { /// /// ``` @Deprecated('Use rangeTextStyle property in SfDateRangePicker') - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; /// The text style for the text in the blackout dates cell of /// [SfDateRangePicker] month view. @@ -1424,7 +1476,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final TextStyle blackoutDateTextStyle; + final TextStyle? blackoutDateTextStyle; /// The text style for the text in the weekend dates cell of /// [SfDateRangePicker] month view. @@ -1458,7 +1510,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final TextStyle weekendTextStyle; + final TextStyle? weekendTextStyle; /// The text style for the text in the special dates cell of /// [SfDateRangePicker] month view. @@ -1491,7 +1543,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final TextStyle specialDatesTextStyle; + final TextStyle? specialDatesTextStyle; /// The decoration for the special date cells of [SfDateRangePicker] /// month view. @@ -1524,7 +1576,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final Decoration specialDatesDecoration; + final Decoration? specialDatesDecoration; /// The decoration for the weekend date cells of [SfDateRangePicker] /// month view. @@ -1557,7 +1609,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final Decoration weekendDatesDecoration; + final Decoration? weekendDatesDecoration; /// The decoration for the blackout date cells of [SfDateRangePicker] /// month view. @@ -1590,7 +1642,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final Decoration blackoutDatesDecoration; + final Decoration? blackoutDatesDecoration; /// The decoration for the disabled date cells of [SfDateRangePicker] /// month view. @@ -1630,7 +1682,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final Decoration disabledDatesDecoration; + final Decoration? disabledDatesDecoration; /// The decoration for the month cells of [SfDateRangePicker] month view. /// @@ -1662,7 +1714,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final Decoration cellDecoration; + final Decoration? cellDecoration; /// The decoration for the today text cell of [SfDateRangePicker] month view. /// @@ -1694,7 +1746,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final Decoration todayCellDecoration; + final Decoration? todayCellDecoration; /// The decoration for the trailing date cells of [SfDateRangePicker] /// month view. @@ -1736,7 +1788,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final Decoration trailingDatesDecoration; + final Decoration? trailingDatesDecoration; /// The decoration for the leading date cells of [SfDateRangePicker] /// month view. @@ -1778,7 +1830,7 @@ class DateRangePickerMonthCellStyle { /// } /// /// ``` - final Decoration leadingDatesDecoration; + final Decoration? leadingDatesDecoration; /// The color which fills the [SfDateRangePicker] selection view. /// @@ -1811,7 +1863,7 @@ class DateRangePickerMonthCellStyle { /// /// ``` @Deprecated('Use selectionColor property in SfDateRangePicker') - final Color selectionColor; + final Color? selectionColor; /// The color which fills the [SfDateRangePicker] selection view of the range /// start date. @@ -1847,7 +1899,7 @@ class DateRangePickerMonthCellStyle { /// /// ``` @Deprecated('Use startRangeSelectionColor property in SfDateRangePicker') - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; /// The color which fills the [SfDateRangePicker] selection view for the range /// of dates which falls between the [PickerDateRange.startDate] and @@ -1884,7 +1936,7 @@ class DateRangePickerMonthCellStyle { /// /// ``` @Deprecated('Use rangeSelectionColor property in SfDateRangePicker') - final Color rangeSelectionColor; + final Color? rangeSelectionColor; /// The color which fills the [SfDateRangePicker] selection view of the range /// end date. @@ -1920,7 +1972,7 @@ class DateRangePickerMonthCellStyle { /// /// ``` @Deprecated('Use endRangeSelectionColor property in SfDateRangePicker') - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; @override bool operator ==(dynamic other) { @@ -1950,9 +2002,45 @@ class DateRangePickerMonthCellStyle { otherStyle.disabledDatesTextStyle == disabledDatesTextStyle; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('textStyle', textStyle)); + properties + .add(DiagnosticsProperty('todayTextStyle', todayTextStyle)); + properties.add(DiagnosticsProperty( + 'trailingDatesTextStyle', trailingDatesTextStyle)); + properties.add(DiagnosticsProperty( + 'leadingDatesTextStyle', leadingDatesTextStyle)); + properties.add(DiagnosticsProperty( + 'blackoutDateTextStyle', blackoutDateTextStyle)); + properties.add( + DiagnosticsProperty('weekendTextStyle', weekendTextStyle)); + properties.add(DiagnosticsProperty( + 'specialDatesTextStyle', specialDatesTextStyle)); + properties.add(DiagnosticsProperty( + 'disabledDatesTextStyle', disabledDatesTextStyle)); + properties.add(DiagnosticsProperty( + 'disabledDatesDecoration', disabledDatesDecoration)); + properties + .add(DiagnosticsProperty('cellDecoration', cellDecoration)); + properties.add(DiagnosticsProperty( + 'todayCellDecoration', todayCellDecoration)); + properties.add(DiagnosticsProperty( + 'trailingDatesDecoration', trailingDatesDecoration)); + properties.add(DiagnosticsProperty( + 'leadingDatesDecoration', leadingDatesDecoration)); + properties.add(DiagnosticsProperty( + 'blackoutDatesDecoration', blackoutDatesDecoration)); + properties.add(DiagnosticsProperty( + 'weekendDatesDecoration', weekendDatesDecoration)); + properties.add(DiagnosticsProperty( + 'specialDatesDecoration', specialDatesDecoration)); + } + @override int get hashCode { - return hashList([ + return hashList([ textStyle, todayTextStyle, trailingDatesTextStyle, @@ -1976,8 +2064,8 @@ class DateRangePickerMonthCellStyle { typedef DateRangePickerValueChangedCallback = void Function(String); /// Notifier used to notify the when the objects properties changed. -class DateRangePickerValueChangeNotifier { - List _listeners; +class DateRangePickerValueChangeNotifier with Diagnosticable { + List? _listeners; /// Calls the listener every time the controller's property changed. /// @@ -1985,7 +2073,7 @@ class DateRangePickerValueChangeNotifier { void addPropertyChangedListener( DateRangePickerValueChangedCallback listener) { _listeners ??= []; - _listeners.add(listener); + _listeners?.add(listener); } /// remove the listener used for notify the data source changes. @@ -2002,7 +2090,7 @@ class DateRangePickerValueChangeNotifier { return; } - _listeners.remove(listener); + _listeners?.remove(listener); } /// Call all the registered listeners. @@ -2022,10 +2110,8 @@ class DateRangePickerValueChangeNotifier { return; } - for (final DateRangePickerValueChangedCallback listener in _listeners) { - if (listener != null) { - listener(value); - } + for (final DateRangePickerValueChangedCallback listener in _listeners!) { + listener.call(value); } } @@ -2125,19 +2211,19 @@ class DateRangePickerValueChangeNotifier { /// /// ``` class DateRangePickerController extends DateRangePickerValueChangeNotifier { - DateTime _selectedDate; - List _selectedDates; - PickerDateRange _selectedRange; - List _selectedRanges; - DateTime _displayDate; - DateRangePickerView _view; + DateTime? _selectedDate; + List? _selectedDates; + PickerDateRange? _selectedRange; + List? _selectedRanges; + DateTime? _displayDate; + DateRangePickerView? _view; /// The selected date in the [SfDateRangePicker]. /// /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.single] for other selection modes this /// property will return as null. - DateTime get selectedDate => _selectedDate; + DateTime? get selectedDate => _selectedDate; /// Selects the given date programmatically in the [SfDateRangePicker] by /// checking that the date falls in between the minimum and maximum date @@ -2182,7 +2268,7 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - set selectedDate(DateTime date) { + set selectedDate(DateTime? date) { if (isSameDate(_selectedDate, date)) { return; } @@ -2196,7 +2282,7 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.multiple] for other selection modes this /// property will return as null. - List get selectedDates => _selectedDates; + List? get selectedDates => _selectedDates; /// Selects the given dates programmatically in the [SfDateRangePicker] by /// checking that the dates falls in between the minimum and maximum date @@ -2244,12 +2330,12 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - set selectedDates(List dates) { + set selectedDates(List? dates) { if (DateRangePickerHelper.isDateCollectionEquals(_selectedDates, dates)) { return; } - _selectedDates = DateRangePickerHelper.cloneList(dates); + _selectedDates = DateRangePickerHelper.cloneList(dates)?.cast(); notifyPropertyChangedListeners('selectedDates'); } @@ -2258,7 +2344,7 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.range] for other selection modes this /// property will return as null. - PickerDateRange get selectedRange => _selectedRange; + PickerDateRange? get selectedRange => _selectedRange; /// Selects the given date range programmatically in the [SfDateRangePicker] /// by checking that the range of dates falls in between the minimum and @@ -2303,7 +2389,7 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - set selectedRange(PickerDateRange range) { + set selectedRange(PickerDateRange? range) { if (DateRangePickerHelper.isRangeEquals(_selectedRange, range)) { return; } @@ -2317,7 +2403,7 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.multiRange] for other selection modes this /// property will return as null. - List get selectedRanges => _selectedRanges; + List? get selectedRanges => _selectedRanges; /// Selects the given date ranges programmatically in the [SfDateRangePicker] /// by checking that the ranges of dates falls in between the minimum and @@ -2365,12 +2451,13 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - set selectedRanges(List ranges) { + set selectedRanges(List? ranges) { if (DateRangePickerHelper.isDateRangesEquals(_selectedRanges, ranges)) { return; } - _selectedRanges = DateRangePickerHelper.cloneList(ranges); + _selectedRanges = + DateRangePickerHelper.cloneList(ranges)?.cast(); notifyPropertyChangedListeners('selectedRanges'); } @@ -2380,7 +2467,7 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { /// If the [MonthViewSettings.numberOfWeeksInView] property set with value /// other then 6, this will return the first visible date of the current /// month. - DateTime get displayDate => _displayDate; + DateTime? get displayDate => _displayDate; /// Navigates to the given date programmatically without any animation in the /// [SfDateRangePicker] by checking that the date falls in between the @@ -2418,7 +2505,7 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - set displayDate(DateTime date) { + set displayDate(DateTime? date) { if (isSameDate(_displayDate, date)) { return; } @@ -2428,7 +2515,7 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { } /// The current visible [DateRangePickerView] of [SfDateRangePicker]. - DateRangePickerView get view => _view; + DateRangePickerView? get view => _view; /// Set the [SfDateRangePickerView] for the [SfDateRangePicker]. /// @@ -2462,7 +2549,7 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - set view(DateRangePickerView value) { + set view(DateRangePickerView? value) { if (_view == value) { return; } @@ -2525,7 +2612,7 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - VoidCallback forward; + VoidCallback? forward; /// Moves to the previous view programmatically with animation by checking /// that the previous view dates falls between the minimum and maximum date @@ -2582,7 +2669,21 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - VoidCallback backward; + VoidCallback? backward; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('displayDate', displayDate)); + properties.add(DiagnosticsProperty('selectedDate', selectedDate)); + properties.add(IterableDiagnostics(selectedDates) + .toDiagnosticsNode(name: 'selectedDates')); + properties.add( + DiagnosticsProperty('selectedRange', selectedRange)); + properties.add(IterableDiagnostics(selectedRanges) + .toDiagnosticsNode(name: 'selectedRanges')); + properties.add(EnumProperty('view', view)); + } } /// Selection modes for [SfDateRangePicker]. @@ -2649,28 +2750,37 @@ enum DateRangePickerNavigationDirection { horizontal } -/// args to update the required properties from picker state to it's children's -class PickerStateArgs { - /// Holds the current view display date. - dynamic currentDate; - - /// Holds the current view visible dates. - List currentViewVisibleDates; - - /// Holds the current selected date. - dynamic selectedDate; - - /// Holds the current selected dates. - List selectedDates; - - /// Holds the current selected range. - dynamic selectedRange; - - /// Holds the current selected ranges. - List selectedRanges; - - /// Holds the current picker view. - DateRangePickerView view; +/// A type specifies how the date picker navigation interaction works. +enum DateRangePickerNavigationMode { + /// Disables the next or previous view dates to be shown by scrolling or + /// swipe interaction in [SfDateRangePicker] and [SfHijriDateRangePicker]. + /// + /// It will not impact [DateRangePickerController.forward()], + /// [DateRangePickerController.backward()] and [showNavigationArrow]. + none, + + /// Allows navigating to previous/next views through swipe interaction in + /// [SfDateRangePicker] and [SfHijriDateRangePicker]. + snap, + + /// Enable free-scrolling based on [SfDateRangePicker]'s navigation direction. + /// + /// Note: + /// 1.Swipe selection is not supported when range and multi-range are the + /// selection modes. + /// 2.[onViewChanged] will be called when the view reaches the starting + /// position of the date range picker view. + /// 3.[DateRangePickerController.forward()], + /// [DateRangePickerController.backward()] and [showNavigationArrow] is + /// not supported. + /// 4. [viewSpacing] value not applicable when [enableMultiView] enabled for + /// scroll mode. + /// 5. [textAlign] in picker header style is not supported on horizontal + /// navigation direction. + /// 6. header view background color changed to white on light theme or + /// grey[850] on dark theme when [backgroundColor] in + /// [DateRangePickerHeaderStyle] is transparent. + scroll } /// The dates that visible on the view changes in [SfDateRangePicker]. @@ -2722,15 +2832,22 @@ class DateRangePickerSelectionChangedArgs { /// Defines a range of dates, covers the dates in between the given [startDate] /// and [endDate] as a range. @immutable -class PickerDateRange { +class PickerDateRange with Diagnosticable { /// Creates a picker date range with the given start and end date. const PickerDateRange(this.startDate, this.endDate); /// The start date of the range. - final DateTime startDate; + final DateTime? startDate; /// The end date of the range. - final DateTime endDate; + final DateTime? endDate; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('startDate', startDate)); + properties.add(DiagnosticsProperty('endDate', endDate)); + } } /// Signature for a function that creates a widget based on date range picker @@ -2741,11 +2858,12 @@ typedef DateRangePickerCellBuilder = Widget Function( /// Contains the details that needed on calendar cell builder. class DateRangePickerCellDetails { /// Constructor to store the details that needed on calendar cell builder. - DateRangePickerCellDetails({this.date, this.bounds, this.visibleDates}); + DateRangePickerCellDetails( + {required this.date, required this.bounds, required this.visibleDates}); /// Date value associated with the picker cell in month, year, decade and /// century views. - final dynamic date; + final DateTime date; /// Position and size of the widget. final Rect bounds; @@ -2753,5 +2871,37 @@ class DateRangePickerCellDetails { /// Visible dates value associated with the current picker month, year, /// decade and century views. It is used to get the cell, leading and /// trailing dates details. - final List visibleDates; + final List visibleDates; +} + +/// Defines the diagnostics for the collection. +class IterableDiagnostics extends DiagnosticableTree { + /// Constructor for collection diagnostics. + IterableDiagnostics(this.collection); + + /// Iterable that used to generate diagnostics. + final List? collection; + + @override + List debugDescribeChildren() { + if (collection != null && collection!.isNotEmpty) { + return collection!.map((T value) { + if (value is Diagnosticable) { + return value.toDiagnosticsNode(); + } else { + return DiagnosticsProperty('', value); + } + }).toList(); + } + return super.debugDescribeChildren(); + } + + @override + String toStringShort() { + return collection == null + ? 'null' + : collection!.isNotEmpty + ? '<' + T.toString() + '>' + : ''; + } } diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/hijri_date_picker_manager.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/hijri_date_picker_manager.dart index 857db1853..2695213a1 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/hijri_date_picker_manager.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/hijri_date_picker_manager.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_datepicker/datepicker.dart'; @@ -44,7 +45,7 @@ import 'picker_helper.dart'; /// /// ``` @immutable -class HijriDatePickerMonthViewSettings { +class HijriDatePickerMonthViewSettings with Diagnosticable { /// Creates a date range picker month view settings for date range picker. /// /// The properties allows to customize the month view of @@ -53,14 +54,11 @@ class HijriDatePickerMonthViewSettings { {this.firstDayOfWeek = 7, this.dayFormat = 'EE', this.viewHeaderHeight = 30, - DateRangePickerViewHeaderStyle viewHeaderStyle, + this.viewHeaderStyle = const DateRangePickerViewHeaderStyle(), this.enableSwipeSelection = true, this.blackoutDates, this.specialDates, - List weekendDays}) - : viewHeaderStyle = - viewHeaderStyle ?? const DateRangePickerViewHeaderStyle(), - weekendDays = weekendDays ?? const [6, 7]; + this.weekendDays = const [6, 7]}); /// Formats a text in the [SfHijriDateRangePicker] month view view header. /// @@ -238,7 +236,7 @@ class HijriDatePickerMonthViewSettings { /// } /// /// ``` - final List blackoutDates; + final List? blackoutDates; /// In the month view of [SfHijriDateRangePicker] highlights the unique dates /// with different style rather than the other dates style. @@ -276,7 +274,7 @@ class HijriDatePickerMonthViewSettings { /// } /// /// ``` - final List specialDates; + final List? specialDates; /// The weekends for month view in [SfHijriDateRangePicker]. /// @@ -326,6 +324,22 @@ class HijriDatePickerMonthViewSettings { otherStyle.enableSwipeSelection == enableSwipeSelection; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(IterableDiagnostics(blackoutDates) + .toDiagnosticsNode(name: 'blackoutDates')); + properties.add(IterableDiagnostics(specialDates) + .toDiagnosticsNode(name: 'specialDates')); + properties.add(IntProperty('firstDayOfWeek', firstDayOfWeek)); + properties.add(DoubleProperty('viewHeaderHeight', viewHeaderHeight)); + properties.add(StringProperty('dayFormat', dayFormat)); + properties.add(DiagnosticsProperty( + 'enableSwipeSelection', enableSwipeSelection)); + properties.add(viewHeaderStyle.toDiagnosticsNode(name: 'viewHeaderStyle')); + properties.add(IterableProperty('weekendDays', weekendDays)); + } + @override int get hashCode { return hashValues( @@ -334,9 +348,9 @@ class HijriDatePickerMonthViewSettings { viewHeaderStyle, enableSwipeSelection, viewHeaderHeight, - specialDates, - blackoutDates, - weekendDays); + hashList(specialDates), + hashList(blackoutDates), + hashList(weekendDays)); } } @@ -377,7 +391,7 @@ class HijriDatePickerMonthViewSettings { /// /// ``` @immutable -class HijriDatePickerYearCellStyle { +class HijriDatePickerYearCellStyle with Diagnosticable { /// Creates a date range picker year cell style for date range picker. /// /// The properties allows to customize the year cells in year view of @@ -419,7 +433,7 @@ class HijriDatePickerYearCellStyle { /// } /// /// ``` - final TextStyle textStyle; + final TextStyle? textStyle; /// The text style for the text in the today cell of [SfHijriDateRangePicker] /// year and decade view. @@ -450,7 +464,7 @@ class HijriDatePickerYearCellStyle { /// } /// /// ``` - final TextStyle todayTextStyle; + final TextStyle? todayTextStyle; /// The text style for the text in the disabled dates cell of /// [SfHijriDateRangePicker] year and decade view. @@ -483,7 +497,7 @@ class HijriDatePickerYearCellStyle { /// } /// /// ``` - final TextStyle disabledDatesTextStyle; + final TextStyle? disabledDatesTextStyle; /// The decoration for the disabled cells of [SfHijriDateRangePicker] /// year and decade view. @@ -518,7 +532,7 @@ class HijriDatePickerYearCellStyle { /// } /// /// ``` - final Decoration disabledDatesDecoration; + final Decoration? disabledDatesDecoration; /// The decoration for the cells of [SfHijriDateRangePicker] year and decade /// view. @@ -550,7 +564,7 @@ class HijriDatePickerYearCellStyle { /// } /// /// ``` - final Decoration cellDecoration; + final Decoration? cellDecoration; /// The decoration for the today cell of [SfHijriDateRangePicker] year and /// decade view. @@ -582,7 +596,7 @@ class HijriDatePickerYearCellStyle { /// } /// /// ``` - final Decoration todayCellDecoration; + final Decoration? todayCellDecoration; @override bool operator ==(dynamic other) { @@ -602,6 +616,22 @@ class HijriDatePickerYearCellStyle { otherStyle.disabledDatesTextStyle == disabledDatesTextStyle; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('textStyle', textStyle)); + properties + .add(DiagnosticsProperty('todayTextStyle', todayTextStyle)); + properties.add(DiagnosticsProperty( + 'disabledDatesTextStyle', disabledDatesTextStyle)); + properties.add(DiagnosticsProperty( + 'disabledDatesDecoration', disabledDatesDecoration)); + properties + .add(DiagnosticsProperty('cellDecoration', cellDecoration)); + properties.add(DiagnosticsProperty( + 'todayCellDecoration', todayCellDecoration)); + } + @override int get hashCode { return hashValues(textStyle, todayTextStyle, disabledDatesTextStyle, @@ -648,7 +678,7 @@ class HijriDatePickerYearCellStyle { /// /// ``` @immutable -class HijriDatePickerMonthCellStyle { +class HijriDatePickerMonthCellStyle with Diagnosticable { /// Creates a date range picker month cell style for date range picker. /// /// The properties allows to customize the month cells in month view of @@ -697,7 +727,7 @@ class HijriDatePickerMonthCellStyle { /// } /// /// ``` - final TextStyle textStyle; + final TextStyle? textStyle; /// The text style for the text in the today cell of [SfHijriDateRangePicker] /// month view. @@ -730,7 +760,7 @@ class HijriDatePickerMonthCellStyle { /// } /// /// ``` - final TextStyle todayTextStyle; + final TextStyle? todayTextStyle; /// The text style for the text in the disabled dates cell of /// [SfHijriDateRangePicker] month view. @@ -767,7 +797,7 @@ class HijriDatePickerMonthCellStyle { /// } /// /// ``` - final TextStyle disabledDatesTextStyle; + final TextStyle? disabledDatesTextStyle; /// The text style for the text in the blackout dates cell of /// [SfHijriDateRangePicker] month view. @@ -801,7 +831,7 @@ class HijriDatePickerMonthCellStyle { /// } /// /// ``` - final TextStyle blackoutDateTextStyle; + final TextStyle? blackoutDateTextStyle; /// The text style for the text in the weekend dates cell of /// [SfHijriDateRangePicker] month view. @@ -835,7 +865,7 @@ class HijriDatePickerMonthCellStyle { /// } /// /// ``` - final TextStyle weekendTextStyle; + final TextStyle? weekendTextStyle; /// The text style for the text in the special dates cell of /// [SfHijriDateRangePicker] month view. @@ -868,7 +898,7 @@ class HijriDatePickerMonthCellStyle { /// } /// /// ``` - final TextStyle specialDatesTextStyle; + final TextStyle? specialDatesTextStyle; /// The decoration for the special date cells of [SfHijriDateRangePicker] /// month view. @@ -901,7 +931,7 @@ class HijriDatePickerMonthCellStyle { /// } /// /// ``` - final Decoration specialDatesDecoration; + final Decoration? specialDatesDecoration; /// The decoration for the weekend date cells of [SfHijriDateRangePicker] /// month view. @@ -934,7 +964,7 @@ class HijriDatePickerMonthCellStyle { /// } /// /// ``` - final Decoration weekendDatesDecoration; + final Decoration? weekendDatesDecoration; /// The decoration for the blackout date cells of [SfHijriDateRangePicker] /// month view. @@ -967,7 +997,7 @@ class HijriDatePickerMonthCellStyle { /// } /// /// ``` - final Decoration blackoutDatesDecoration; + final Decoration? blackoutDatesDecoration; /// The decoration for the disabled date cells of [SfHijriDateRangePicker] /// month view. @@ -1007,7 +1037,7 @@ class HijriDatePickerMonthCellStyle { /// } /// /// ``` - final Decoration disabledDatesDecoration; + final Decoration? disabledDatesDecoration; /// The decoration for the month cells of [SfHijriDateRangePicker] month view. /// @@ -1039,7 +1069,7 @@ class HijriDatePickerMonthCellStyle { /// } /// /// ``` - final Decoration cellDecoration; + final Decoration? cellDecoration; /// The decoration for the today text cell of [SfHijriDateRangePicker] month /// view. @@ -1072,7 +1102,7 @@ class HijriDatePickerMonthCellStyle { /// } /// /// ``` - final Decoration todayCellDecoration; + final Decoration? todayCellDecoration; @override bool operator ==(dynamic other) { @@ -1098,9 +1128,37 @@ class HijriDatePickerMonthCellStyle { otherStyle.disabledDatesTextStyle == disabledDatesTextStyle; } + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('textStyle', textStyle)); + properties + .add(DiagnosticsProperty('todayTextStyle', todayTextStyle)); + properties.add(DiagnosticsProperty( + 'blackoutDateTextStyle', blackoutDateTextStyle)); + properties.add( + DiagnosticsProperty('weekendTextStyle', weekendTextStyle)); + properties.add(DiagnosticsProperty( + 'specialDatesTextStyle', specialDatesTextStyle)); + properties.add(DiagnosticsProperty( + 'disabledDatesTextStyle', disabledDatesTextStyle)); + properties.add(DiagnosticsProperty( + 'disabledDatesDecoration', disabledDatesDecoration)); + properties + .add(DiagnosticsProperty('cellDecoration', cellDecoration)); + properties.add(DiagnosticsProperty( + 'todayCellDecoration', todayCellDecoration)); + properties.add(DiagnosticsProperty( + 'blackoutDatesDecoration', blackoutDatesDecoration)); + properties.add(DiagnosticsProperty( + 'weekendDatesDecoration', weekendDatesDecoration)); + properties.add(DiagnosticsProperty( + 'specialDatesDecoration', specialDatesDecoration)); + } + @override int get hashCode { - return hashList([ + return hashList([ textStyle, todayTextStyle, disabledDatesTextStyle, @@ -1204,19 +1262,19 @@ class HijriDatePickerMonthCellStyle { /// /// ``` class HijriDatePickerController extends DateRangePickerValueChangeNotifier { - HijriDateTime _selectedDate; - List _selectedDates; - HijriDateRange _selectedRange; - List _selectedRanges; - HijriDateTime _displayDate; - HijriDatePickerView _view; + HijriDateTime? _selectedDate; + List? _selectedDates; + HijriDateRange? _selectedRange; + List? _selectedRanges; + HijriDateTime? _displayDate; + HijriDatePickerView? _view; /// The selected date in the [SfHijriDateRangePicker]. /// /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.single] for other selection modes this /// property will return as null. - HijriDateTime get selectedDate => _selectedDate; + HijriDateTime? get selectedDate => _selectedDate; /// Selects the given date programmatically in the [SfHijriDateRangePicker] by /// checking that the date falls in between the minimum and maximum date @@ -1261,7 +1319,7 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - set selectedDate(HijriDateTime date) { + set selectedDate(HijriDateTime? date) { if (isSameDate(_selectedDate, date)) { return; } @@ -1275,7 +1333,7 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.multiple] for other selection modes /// this property will return as null. - List get selectedDates => _selectedDates; + List? get selectedDates => _selectedDates; /// Selects the given dates programmatically in the [SfHijriDateRangePicker] /// by checking that the dates falls in between the minimum and maximum date @@ -1323,12 +1381,13 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - set selectedDates(List dates) { + set selectedDates(List? dates) { if (DateRangePickerHelper.isDateCollectionEquals(_selectedDates, dates)) { return; } - _selectedDates = DateRangePickerHelper.cloneList(dates); + _selectedDates = + DateRangePickerHelper.cloneList(dates)!.cast(); notifyPropertyChangedListeners('selectedDates'); } @@ -1337,7 +1396,7 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.range] for other selection modes this /// property will return as null. - HijriDateRange get selectedRange => _selectedRange; + HijriDateRange? get selectedRange => _selectedRange; /// Selects the given date range programmatically in the /// [SfHijriDateRangePicker] by checking that the range of dates falls in @@ -1382,7 +1441,7 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - set selectedRange(HijriDateRange range) { + set selectedRange(HijriDateRange? range) { if (DateRangePickerHelper.isRangeEquals(_selectedRange, range)) { return; } @@ -1396,7 +1455,7 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.multiRange] for other selection modes /// this property will return as null. - List get selectedRanges => _selectedRanges; + List? get selectedRanges => _selectedRanges; /// Selects the given date ranges programmatically in the /// [SfHijriDateRangePicker] by checking that the ranges of dates falls in @@ -1444,12 +1503,13 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - set selectedRanges(List ranges) { + set selectedRanges(List? ranges) { if (DateRangePickerHelper.isDateRangesEquals(_selectedRanges, ranges)) { return; } - _selectedRanges = DateRangePickerHelper.cloneList(ranges); + _selectedRanges = + DateRangePickerHelper.cloneList(ranges)!.cast(); notifyPropertyChangedListeners('selectedRanges'); } @@ -1460,7 +1520,7 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// If the [HijriDatePickerMonthViewSettings.numberOfWeeksInView] /// property set with value other then 6, this will return the first visible /// date of the current month. - HijriDateTime get displayDate => _displayDate; + HijriDateTime? get displayDate => _displayDate; /// Navigates to the given date programmatically without any animation in the /// [SfHijriDateRangePicker] by checking that the date falls in between the @@ -1499,7 +1559,7 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - set displayDate(HijriDateTime date) { + set displayDate(HijriDateTime? date) { if (isSameDate(_displayDate, date)) { return; } @@ -1510,7 +1570,7 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// The current visible [HijriDatePickerView] of /// [SfHijriDateRangePicker]. - HijriDatePickerView get view => _view; + HijriDatePickerView? get view => _view; /// Set the [HijriDatePickerView] for the [SfHijriDateRangePicker]. /// @@ -1544,7 +1604,7 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - set view(HijriDatePickerView value) { + set view(HijriDatePickerView? value) { if (_view == value) { return; } @@ -1607,7 +1667,7 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - VoidCallback forward; + VoidCallback? forward; /// Moves to the previous view programmatically with animation by checking /// that the previous view dates falls between the minimum and maximum date @@ -1664,7 +1724,23 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { ///} /// /// ``` - VoidCallback backward; + VoidCallback? backward; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + .add(DiagnosticsProperty('displayDate', displayDate)); + properties + .add(DiagnosticsProperty('selectedDate', selectedDate)); + properties.add(IterableDiagnostics(selectedDates) + .toDiagnosticsNode(name: 'selectedDates')); + properties.add( + DiagnosticsProperty('selectedRange', selectedRange)); + properties.add(IterableDiagnostics(selectedRanges) + .toDiagnosticsNode(name: 'selectedRanges')); + properties.add(EnumProperty('view', view)); + } } /// Available views for [SfHijriDateRangePicker]. @@ -1703,13 +1779,44 @@ class HijriDatePickerViewChangedArgs { /// Defines a range of dates, covers the dates in between the given [startDate] /// and [endDate] as a range. @immutable -class HijriDateRange { +class HijriDateRange with Diagnosticable { /// Creates a picker date range with the given start and end date. const HijriDateRange(this.startDate, this.endDate); /// The start date of the range. - final HijriDateTime startDate; + final HijriDateTime? startDate; /// The end date of the range. - final HijriDateTime endDate; + final HijriDateTime? endDate; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('startDate', startDate)); + properties.add(DiagnosticsProperty('endDate', endDate)); + } +} + +/// Signature for a function that creates a widget based on date range picker +/// cell details. +typedef HijriDateRangePickerCellBuilder = Widget Function( + BuildContext context, HijriDateRangePickerCellDetails cellDetails); + +/// Contains the details that needed on calendar cell builder. +class HijriDateRangePickerCellDetails { + /// Constructor to store the details that needed on calendar cell builder. + HijriDateRangePickerCellDetails( + {required this.date, required this.bounds, required this.visibleDates}); + + /// Date value associated with the picker cell in month, year, decade and + /// century views. + final HijriDateTime date; + + /// Position and size of the widget. + final Rect bounds; + + /// Visible dates value associated with the current picker month, year, + /// decade and century views. It is used to get the cell, leading and + /// trailing dates details. + final List visibleDates; } diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view.dart index 58ce98954..72b5dc50f 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view.dart @@ -63,7 +63,7 @@ class MonthView extends StatefulWidget { final bool isRtl; /// Defines the today cell highlight color. - final Color todayHighlightColor; + final Color? todayHighlightColor; /// Holds the theme data for date range picker. final SfDateRangePickerThemeData datePickerTheme; @@ -81,10 +81,10 @@ class MonthView extends StatefulWidget { final bool showLeadingAndTailingDates; /// Holds the blackout dates of the [SfDateRangePicker]. - final List blackoutDates; + final List? blackoutDates; /// Holds the special dates of the [SfDateRangePicker]. - final List specialDates; + final List? specialDates; /// Holds the list of week day index of the [SfDateRangePicker]. final List weekendDays; @@ -99,7 +99,7 @@ class MonthView extends StatefulWidget { final ValueNotifier selectionNotifier; /// Used to specify the mouse hover position of the month view. - final ValueNotifier mouseHoverPosition; + final ValueNotifier mouseHoverPosition; /// Decides to show the multi view of month view or not. final bool enableMultiView; @@ -108,22 +108,22 @@ class MonthView extends StatefulWidget { final double multiViewSpacing; /// Defines the text style for selected month cell. - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; /// Defines the range text style for selected range month cell. - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; /// Defines the background color for selected month cell. - final Color selectionColor; + final Color? selectionColor; /// Defines the background color for selected range start date month cell. - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; /// Defines the background color for selected range end date month cell. - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; /// Defines the background color for selected range in between dates cell. - final Color rangeSelectionColor; + final Color? rangeSelectionColor; /// Defines the text scale factor of [SfDateRangePicker]. final double textScaleFactor; @@ -141,7 +141,7 @@ class MonthView extends StatefulWidget { final UpdatePickerState getPickerStateDetails; /// Used to build the widget that replaces the month cells in month view. - final DateRangePickerCellBuilder cellBuilder; + final dynamic cellBuilder; /// Specifies the pickerType for [SfDateRangePicker]. final bool isHijri; @@ -157,16 +157,15 @@ class MonthView extends StatefulWidget { } class _MonthViewState extends State { - PickerStateArgs _pickerStateDetails; - dynamic _selectedDate; - List _selectedDates; - dynamic _selectedRange; - List _selectedRanges; - List _children; + PickerStateArgs _pickerStateDetails = PickerStateArgs(); + dynamic? _selectedDate; + List? _selectedDates; + dynamic? _selectedRange; + List? _selectedRanges; + List _children = []; @override void initState() { - _pickerStateDetails = PickerStateArgs(); widget.getPickerStateDetails(_pickerStateDetails); _selectedDate = _pickerStateDetails.selectedDate; _selectedDates = @@ -220,7 +219,6 @@ class _MonthViewState extends State { @override Widget build(BuildContext context) { - _children ??= []; if (widget.cellBuilder != null && _children.isEmpty) { double webUIPadding = 0; double width = widget.width; @@ -293,14 +291,19 @@ class _MonthViewState extends State { continue; } - final DateRangePickerCellDetails cellDetails = - DateRangePickerCellDetails( - date: date, - visibleDates: widget.visibleDates, - bounds: Rect.fromLTWH( - xPosition, yPosition, cellWidth, cellHeight)); - final Widget child = widget.cellBuilder(context, cellDetails); - assert(child != null, 'Widget must not be null'); + final Widget child = widget.cellBuilder( + context, + widget.isHijri + ? HijriDateRangePickerCellDetails( + date: date, + visibleDates: widget.visibleDates.cast(), + bounds: Rect.fromLTWH( + xPosition, yPosition, cellWidth, cellHeight)) + : DateRangePickerCellDetails( + date: date, + visibleDates: widget.visibleDates.cast(), + bounds: Rect.fromLTWH( + xPosition, yPosition, cellWidth, cellHeight))); _children.add(child); xPosition += cellWidth; } @@ -356,7 +359,6 @@ class _MonthViewState extends State { _selectedRanges, _pickerStateDetails.selectedRanges); } } - return false; } MultiChildRenderObjectWidget _getMonthRenderWidget() { @@ -510,8 +512,6 @@ class _MonthViewState extends State { widgets: _children); } } - - return null; } } @@ -550,7 +550,7 @@ class _MonthViewSingleSelectionRenderWidget this.isHijri, this.localizations, this.navigationDirection, - {List widgets}) + {List widgets = const []}) : super(children: widgets); final int rowCount; @@ -561,7 +561,7 @@ class _MonthViewSingleSelectionRenderWidget final bool isRtl; - final Color todayHighlightColor; + final Color? todayHighlightColor; final SfDateRangePickerThemeData datePickerTheme; @@ -575,9 +575,9 @@ class _MonthViewSingleSelectionRenderWidget final bool showLeadingAndTailingDates; - final List blackoutDates; + final List? blackoutDates; - final List specialDates; + final List? specialDates; final List weekendDays; @@ -587,23 +587,23 @@ class _MonthViewSingleSelectionRenderWidget final ValueNotifier selectionNotifier; - final ValueNotifier mouseHoverPosition; + final ValueNotifier mouseHoverPosition; final bool enableMultiView; final double multiViewSpacing; - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; - final Color selectionColor; + final Color? selectionColor; - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; - final Color rangeSelectionColor; + final Color? rangeSelectionColor; final double textScaleFactor; @@ -729,7 +729,7 @@ class _MonthViewMultiSelectionRenderWidget this.isHijri, this.localizations, this.navigationDirection, - {List widgets}) + {List widgets = const []}) : super(children: widgets); final int rowCount; @@ -740,7 +740,7 @@ class _MonthViewMultiSelectionRenderWidget final bool isRtl; - final Color todayHighlightColor; + final Color? todayHighlightColor; final SfDateRangePickerThemeData datePickerTheme; @@ -754,9 +754,9 @@ class _MonthViewMultiSelectionRenderWidget final bool showLeadingAndTailingDates; - final List blackoutDates; + final List? blackoutDates; - final List specialDates; + final List? specialDates; final List weekendDays; @@ -766,23 +766,23 @@ class _MonthViewMultiSelectionRenderWidget final ValueNotifier selectionNotifier; - final ValueNotifier mouseHoverPosition; + final ValueNotifier mouseHoverPosition; final bool enableMultiView; final double multiViewSpacing; - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; - final Color selectionColor; + final Color? selectionColor; - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; - final Color rangeSelectionColor; + final Color? rangeSelectionColor; final double textScaleFactor; @@ -790,7 +790,7 @@ class _MonthViewMultiSelectionRenderWidget final double width; - final List selectedDates; + final List? selectedDates; final bool isHijri; @@ -908,7 +908,7 @@ class _MonthViewRangeSelectionRenderWidget this.isHijri, this.localizations, this.navigationDirection, - {List widgets}) + {required List widgets}) : super(children: widgets); final int rowCount; @@ -919,7 +919,7 @@ class _MonthViewRangeSelectionRenderWidget final bool isRtl; - final Color todayHighlightColor; + final Color? todayHighlightColor; final SfDateRangePickerThemeData datePickerTheme; @@ -933,9 +933,9 @@ class _MonthViewRangeSelectionRenderWidget final bool showLeadingAndTailingDates; - final List blackoutDates; + final List? blackoutDates; - final List specialDates; + final List? specialDates; final List weekendDays; @@ -945,23 +945,23 @@ class _MonthViewRangeSelectionRenderWidget final ValueNotifier selectionNotifier; - final ValueNotifier mouseHoverPosition; + final ValueNotifier mouseHoverPosition; final bool enableMultiView; final double multiViewSpacing; - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; - final Color selectionColor; + final Color? selectionColor; - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; - final Color rangeSelectionColor; + final Color? rangeSelectionColor; final double textScaleFactor; @@ -969,7 +969,7 @@ class _MonthViewRangeSelectionRenderWidget final double width; - final dynamic selectedRange; + final dynamic? selectedRange; final bool isHijri; @@ -1087,7 +1087,7 @@ class _MonthViewMultiRangeSelectionRenderWidget this.isHijri, this.localizations, this.navigationDirection, - {List widgets}) + {required List widgets}) : super(children: widgets); final int rowCount; @@ -1098,7 +1098,7 @@ class _MonthViewMultiRangeSelectionRenderWidget final bool isRtl; - final Color todayHighlightColor; + final Color? todayHighlightColor; final SfDateRangePickerThemeData datePickerTheme; @@ -1110,9 +1110,9 @@ class _MonthViewMultiRangeSelectionRenderWidget final bool showLeadingAndTailingDates; - final List blackoutDates; + final List? blackoutDates; - final List specialDates; + final List? specialDates; final List weekendDays; @@ -1122,23 +1122,23 @@ class _MonthViewMultiRangeSelectionRenderWidget final ValueNotifier selectionNotifier; - final ValueNotifier mouseHoverPosition; + final ValueNotifier mouseHoverPosition; final bool enableMultiView; final double multiViewSpacing; - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; - final Color selectionColor; + final Color? selectionColor; - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; - final Color rangeSelectionColor; + final Color? rangeSelectionColor; final double textScaleFactor; @@ -1146,7 +1146,7 @@ class _MonthViewMultiRangeSelectionRenderWidget final double width; - final List selectedRanges; + final List? selectedRanges; final bool isHijri; @@ -1323,11 +1323,11 @@ abstract class _IMonthView extends RenderBox } /// Defines the month cell style. - dynamic _cellStyle; + dynamic? _cellStyle; - dynamic get cellStyle => _cellStyle; + dynamic? get cellStyle => _cellStyle; - set cellStyle(dynamic value) { + set cellStyle(dynamic? value) { if (_cellStyle == value) { return; } @@ -1355,11 +1355,11 @@ abstract class _IMonthView extends RenderBox } /// Defines the today cell highlight color. - Color _todayHighlightColor; + Color? _todayHighlightColor; - Color get todayHighlightColor => _todayHighlightColor; + Color? get todayHighlightColor => _todayHighlightColor; - set todayHighlightColor(Color value) { + set todayHighlightColor(Color? value) { if (_todayHighlightColor == value) { return; } @@ -1463,11 +1463,11 @@ abstract class _IMonthView extends RenderBox } /// Holds the blackout dates of the [SfDateRangePicker]. - List _blackoutDates; + List? _blackoutDates; - List get blackoutDates => _blackoutDates; + List? get blackoutDates => _blackoutDates; - set blackoutDates(List value) { + set blackoutDates(List? value) { if (DateRangePickerHelper.isDateCollectionEquals(_blackoutDates, value)) { return; } @@ -1481,11 +1481,11 @@ abstract class _IMonthView extends RenderBox } /// Holds the special dates of the [SfDateRangePicker]. - List _specialDates; + List? _specialDates; - List get specialDates => _specialDates; + List? get specialDates => _specialDates; - set specialDates(List value) { + set specialDates(List? value) { if (DateRangePickerHelper.isDateCollectionEquals(_specialDates, value)) { return; } @@ -1548,18 +1548,18 @@ abstract class _IMonthView extends RenderBox ValueNotifier selectionNotifier; /// Used to specify the mouse hover position of the month view. - ValueNotifier _mouseHoverPosition; + ValueNotifier _mouseHoverPosition; - ValueNotifier get mouseHoverPosition => _mouseHoverPosition; + ValueNotifier get mouseHoverPosition => _mouseHoverPosition; - set mouseHoverPosition(ValueNotifier value) { + set mouseHoverPosition(ValueNotifier value) { if (_mouseHoverPosition == value) { return; } - _mouseHoverPosition?.removeListener(markNeedsPaint); + _mouseHoverPosition.removeListener(markNeedsPaint); _mouseHoverPosition = value; - _mouseHoverPosition?.addListener(markNeedsPaint); + _mouseHoverPosition.addListener(markNeedsPaint); markNeedsPaint(); } @@ -1600,11 +1600,11 @@ abstract class _IMonthView extends RenderBox } /// Defines the text style for selected month cell. - TextStyle _selectionTextStyle; + TextStyle? _selectionTextStyle; - TextStyle get selectionTextStyle => _selectionTextStyle; + TextStyle? get selectionTextStyle => _selectionTextStyle; - set selectionTextStyle(TextStyle value) { + set selectionTextStyle(TextStyle? value) { if (_selectionTextStyle == value) { return; } @@ -1618,11 +1618,11 @@ abstract class _IMonthView extends RenderBox } /// Defines the range text style for selected range month cell. - TextStyle _rangeTextStyle; + TextStyle? _rangeTextStyle; - TextStyle get rangeTextStyle => _rangeTextStyle; + TextStyle? get rangeTextStyle => _rangeTextStyle; - set rangeTextStyle(TextStyle value) { + set rangeTextStyle(TextStyle? value) { if (_rangeTextStyle == value) { return; } @@ -1636,11 +1636,11 @@ abstract class _IMonthView extends RenderBox } /// Defines the background color for selected month cell. - Color _selectionColor; + Color? _selectionColor; - Color get selectionColor => _selectionColor; + Color? get selectionColor => _selectionColor; - set selectionColor(Color value) { + set selectionColor(Color? value) { if (_selectionColor == value) { return; } @@ -1654,11 +1654,11 @@ abstract class _IMonthView extends RenderBox } /// Defines the background color for selected range start date month cell. - Color _startRangeSelectionColor; + Color? _startRangeSelectionColor; - Color get startRangeSelectionColor => _startRangeSelectionColor; + Color? get startRangeSelectionColor => _startRangeSelectionColor; - set startRangeSelectionColor(Color value) { + set startRangeSelectionColor(Color? value) { if (_startRangeSelectionColor == value) { return; } @@ -1672,11 +1672,11 @@ abstract class _IMonthView extends RenderBox } /// Defines the background color for selected range end date month cell. - Color _endRangeSelectionColor; + Color? _endRangeSelectionColor; - Color get endRangeSelectionColor => _endRangeSelectionColor; + Color? get endRangeSelectionColor => _endRangeSelectionColor; - set endRangeSelectionColor(Color value) { + set endRangeSelectionColor(Color? value) { if (_endRangeSelectionColor == value) { return; } @@ -1690,11 +1690,11 @@ abstract class _IMonthView extends RenderBox } /// Defines the background color for selected range in between dates cell. - Color _rangeSelectionColor; + Color? _rangeSelectionColor; - Color get rangeSelectionColor => _rangeSelectionColor; + Color? get rangeSelectionColor => _rangeSelectionColor; - set rangeSelectionColor(Color value) { + set rangeSelectionColor(Color? value) { if (_rangeSelectionColor == value) { return; } @@ -1779,27 +1779,37 @@ abstract class _IMonthView extends RenderBox SfLocalizations localizations; /// Used to paint the selection of month cell on all the selection mode. - Paint _selectionPainter; + Paint _selectionPainter = Paint(); /// Used to draw month cell text in month view. - TextPainter _textPainter; + TextPainter _textPainter = TextPainter( + textDirection: TextDirection.ltr, + textWidthBasis: TextWidthBasis.longestLine); static const int _selectionPadding = 2; - double _cellWidth, _cellHeight; - double _centerXPosition, _centerYPosition; + /// Caches [SemanticsNode]s created during [assembleSemanticsNode] so they + /// can be re-used when [assembleSemanticsNode] is called again. This ensures + /// stable ids for the [SemanticsNode]s of children across + /// [assembleSemanticsNode] invocations. + /// Ref: assembleSemanticsNode method in RenderParagraph class + /// (https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/paragraph.dart) + List? _cacheNodes; + + late double _cellWidth, _cellHeight; + late double _centerXPosition, _centerYPosition; /// attach will called when the render object rendered in view. @override void attach(PipelineOwner owner) { super.attach(owner); - _mouseHoverPosition?.addListener(markNeedsPaint); + _mouseHoverPosition.addListener(markNeedsPaint); } /// detach will called when the render object removed from view. @override void detach() { - _mouseHoverPosition?.removeListener(markNeedsPaint); + _mouseHoverPosition.removeListener(markNeedsPaint); super.detach(); } @@ -1815,7 +1825,7 @@ abstract class _IMonthView extends RenderBox final Size widgetSize = constraints.biggest; size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, widgetSize.height.isInfinite ? height : widgetSize.height); - RenderBox child = firstChild; + RenderBox? child = firstChild; if (child == null) { return; } @@ -1875,18 +1885,19 @@ abstract class _IMonthView extends RenderBox SemanticsConfiguration config, Iterable children, ) { + _cacheNodes ??= []; final List semantics = _getSemanticsBuilder(size); final List semanticsNodes = []; for (int i = 0; i < semantics.length; i++) { final CustomPainterSemantics currentSemantics = semantics[i]; - final SemanticsNode newChild = SemanticsNode( - key: currentSemantics.key, - ); + final SemanticsNode newChild = _cacheNodes!.isNotEmpty + ? _cacheNodes!.removeAt(0) + : SemanticsNode(key: currentSemantics.key); final SemanticsProperties properties = currentSemantics.properties; final SemanticsConfiguration config = SemanticsConfiguration(); if (properties.label != null) { - config.label = properties.label; + config.label = properties.label!; } if (properties.textDirection != null) { config.textDirection = properties.textDirection; @@ -1909,10 +1920,16 @@ abstract class _IMonthView extends RenderBox final List finalChildren = []; finalChildren.addAll(semanticsNodes); finalChildren.addAll(children); - + _cacheNodes = semanticsNodes; super.assembleSemanticsNode(node, config, finalChildren); } + @override + void clearSemantics() { + super.clearSemantics(); + _cacheNodes = null; + } + @override void visitChildrenForSemantics(RenderObjectVisitor visitor) { return; @@ -1965,8 +1982,8 @@ abstract class _IMonthView extends RenderBox _rowCount, _showLeadingAndTailingDates, currentDate, _isHijri)) { leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( _isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; + left = leftAndTopValue['left']!; + top = leftAndTopValue['top']!; continue; } else if (DateRangePickerHelper.isDateWithInVisibleDates( _visibleDates, _blackoutDates, currentDate)) { @@ -1980,8 +1997,8 @@ abstract class _IMonthView extends RenderBox )); leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( _isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; + left = leftAndTopValue['left']!; + top = leftAndTopValue['top']!; continue; } else if (!DateRangePickerHelper.isEnabledDate( _minDate, _maxDate, _enablePastDates, currentDate, _isHijri)) { @@ -1995,8 +2012,8 @@ abstract class _IMonthView extends RenderBox )); leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( _isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; + left = leftAndTopValue['left']!; + top = leftAndTopValue['top']!; continue; } semanticsBuilder.add(CustomPainterSemantics( @@ -2009,8 +2026,8 @@ abstract class _IMonthView extends RenderBox )); leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( _isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; + left = leftAndTopValue['left']!; + top = leftAndTopValue['top']!; } } @@ -2020,7 +2037,7 @@ abstract class _IMonthView extends RenderBox /// Returns the accessibility text for the month cell. String _getSemanticMonthLabel(dynamic date) { if (_isHijri) { - return DateFormat('EEE').format(date).toString() + + return DateFormat('EEE').format(date.toDateTime()).toString() + ',' + date.day.toString() + '/' + @@ -2038,25 +2055,25 @@ class _MonthViewSingleSelectionRenderObject extends _IMonthView { List visibleDates, int rowCount, dynamic cellStyle, - TextStyle selectionTextStyle, - TextStyle rangeTextStyle, - Color selectionColor, - Color startRangeSelectionColor, - Color endRangeSelectionColor, - Color rangeSelectionColor, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, SfDateRangePickerThemeData datePickerTheme, bool isRtl, - Color todayHighlightColor, + Color? todayHighlightColor, dynamic minDate, dynamic maxDate, bool enablePastDates, bool showLeadingAndTailingDates, - List blackoutDates, - List specialDates, + List? blackoutDates, + List? specialDates, List weekendDays, DateRangePickerSelectionShape selectionShape, double selectionRadius, - ValueNotifier mouseHoverPosition, + ValueNotifier mouseHoverPosition, bool enableMultiView, double multiViewSpacing, ValueNotifier selectionNotifier, @@ -2100,7 +2117,7 @@ class _MonthViewSingleSelectionRenderObject extends _IMonthView { navigationDirection, localizations); - dynamic _selectedDate; + dynamic? _selectedDate; dynamic get selectedDate => _selectedDate; @@ -2166,8 +2183,7 @@ class _MonthViewSingleSelectionRenderObject extends _IMonthView { @override void drawCustomCellSelection(Canvas canvas, double x, double y, int index) { - _selectionPainter ??= Paint(); - _selectionPainter.color = selectionColor ?? datePickerTheme.selectionColor; + _selectionPainter.color = selectionColor ?? datePickerTheme.selectionColor!; _selectionPainter.strokeWidth = 0.0; _selectionPainter.style = PaintingStyle.fill; _selectionPainter.isAntiAlias = true; @@ -2206,25 +2222,25 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { List visibleDates, int rowCount, dynamic cellStyle, - TextStyle selectionTextStyle, - TextStyle rangeTextStyle, - Color selectionColor, - Color startRangeSelectionColor, - Color endRangeSelectionColor, - Color rangeSelectionColor, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, SfDateRangePickerThemeData datePickerTheme, bool isRtl, - Color todayHighlightColor, + Color? todayHighlightColor, dynamic minDate, dynamic maxDate, bool enablePastDates, bool showLeadingAndTailingDates, - List blackoutDates, - List specialDates, + List? blackoutDates, + List? specialDates, List weekendDays, DateRangePickerSelectionShape selectionShape, double selectionRadius, - ValueNotifier mouseHoverPosition, + ValueNotifier mouseHoverPosition, bool enableMultiView, double multiViewSpacing, ValueNotifier selectionNotifier, @@ -2268,11 +2284,11 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { navigationDirection, localizations); - List _selectedDates; + List? _selectedDates; - List get selectedDates => _selectedDates; + List? get selectedDates => _selectedDates; - set selectedDates(List value) { + set selectedDates(List? value) { if (DateRangePickerHelper.isDateCollectionEquals(_selectedDates, value)) { return; } @@ -2310,8 +2326,7 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { @override void drawCustomCellSelection(Canvas canvas, double x, double y, int index) { - _selectionPainter ??= Paint(); - _selectionPainter.color = selectionColor ?? datePickerTheme.selectionColor; + _selectionPainter.color = selectionColor ?? datePickerTheme.selectionColor!; _selectionPainter.strokeWidth = 0.0; _selectionPainter.style = PaintingStyle.fill; _selectionPainter.isAntiAlias = true; @@ -2323,8 +2338,8 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { List getSelectedIndexValues(int viewStartIndex, int viewEndIndex) { final List selectedIndex = []; if (selectedDates != null) { - for (int j = 0; j < selectedDates.length; j++) { - final dynamic date = selectedDates[j]; + for (int j = 0; j < selectedDates!.length; j++) { + final dynamic date = selectedDates![j]; if (!isDateWithInDateRange( visibleDates[viewStartIndex], visibleDates[viewEndIndex], date)) { continue; @@ -2379,25 +2394,25 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { List visibleDates, int rowCount, dynamic cellStyle, - TextStyle selectionTextStyle, - TextStyle rangeTextStyle, - Color selectionColor, - Color startRangeSelectionColor, - Color endRangeSelectionColor, - Color rangeSelectionColor, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, SfDateRangePickerThemeData datePickerTheme, bool isRtl, - Color todayHighlightColor, + Color? todayHighlightColor, dynamic minDate, dynamic maxDate, bool enablePastDates, bool showLeadingAndTailingDates, - List blackoutDates, - List specialDates, + List? blackoutDates, + List? specialDates, List weekendDays, DateRangePickerSelectionShape selectionShape, double selectionRadius, - ValueNotifier mouseHoverPosition, + ValueNotifier mouseHoverPosition, bool enableMultiView, double multiViewSpacing, ValueNotifier selectionNotifier, @@ -2441,11 +2456,11 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { navigationDirection, localizations); - dynamic _selectedRange; + dynamic? _selectedRange; - dynamic get selectedRange => _selectedRange; + dynamic? get selectedRange => _selectedRange; - set selectedRange(dynamic value) { + set selectedRange(dynamic? value) { if (DateRangePickerHelper.isRangeEquals(_selectedRange, value)) { return; } @@ -2458,7 +2473,7 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { } } - List _selectedIndex; + List _selectedIndex = []; @override TextStyle drawSelection(Canvas canvas, double x, double y, int index, @@ -2477,7 +2492,7 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { _cellHeight, x, y, this, _centerYPosition); } else if (isStartRange) { _selectionPainter.color = - rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _drawStartAndEndRange( canvas, this, @@ -2488,12 +2503,12 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { _centerYPosition, x, y, - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor, + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!, heightDifference, isStartRange); } else if (isEndRange) { _selectionPainter.color = - rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _drawStartAndEndRange( canvas, this, @@ -2504,7 +2519,7 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { _centerYPosition, x, y, - endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor, + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!, heightDifference, isStartRange); } else if (isBetweenRange) { @@ -2517,7 +2532,6 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { @override void drawCustomCellSelection(Canvas canvas, double x, double y, int index) { - _selectionPainter ??= Paint(); _selectionPainter.strokeWidth = 0.0; _selectionPainter.style = PaintingStyle.fill; _selectionPainter.isAntiAlias = true; @@ -2528,16 +2542,16 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { final bool isBetweenRange = selectionDetails[3]; if (isSelectedDate) { _selectionPainter.color = - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!; } else if (isStartRange) { _selectionPainter.color = - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!; } else if (isEndRange) { _selectionPainter.color = - endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor; + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!; } else if (isBetweenRange) { _selectionPainter.color = - rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; } canvas.drawRect(Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), @@ -2638,25 +2652,25 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { List visibleDates, int rowCount, dynamic cellStyle, - TextStyle selectionTextStyle, - TextStyle rangeTextStyle, - Color selectionColor, - Color startRangeSelectionColor, - Color endRangeSelectionColor, - Color rangeSelectionColor, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, SfDateRangePickerThemeData datePickerTheme, bool isRtl, - Color todayHighlightColor, + Color? todayHighlightColor, dynamic minDate, dynamic maxDate, bool enablePastDates, bool showLeadingAndTailingDates, - List blackoutDates, - List specialDates, + List? blackoutDates, + List? specialDates, List weekendDays, DateRangePickerSelectionShape selectionShape, double selectionRadius, - ValueNotifier mouseHoverPosition, + ValueNotifier mouseHoverPosition, bool enableMultiView, double multiViewSpacing, ValueNotifier selectionNotifier, @@ -2700,11 +2714,11 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { navigationDirection, localizations); - List _selectedRanges; + List? _selectedRanges; - List get selectedRanges => _selectedRanges; + List? get selectedRanges => _selectedRanges; - set selectedRanges(List value) { + set selectedRanges(List? value) { if (DateRangePickerHelper.isDateRangesEquals(_selectedRanges, value)) { return; } @@ -2717,7 +2731,7 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { } } - List> _selectedRangesIndex; + List> _selectedRangesIndex = >[]; @override TextStyle drawSelection(Canvas canvas, double x, double y, int index, @@ -2736,7 +2750,7 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { _cellHeight, x, y, this, _centerYPosition); } else if (isStartRange) { _selectionPainter.color = - rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _drawStartAndEndRange( canvas, this, @@ -2747,12 +2761,12 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { _centerYPosition, x, y, - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor, + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!, heightDifference, isStartRange); } else if (isEndRange) { _selectionPainter.color = - rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _drawStartAndEndRange( canvas, this, @@ -2763,7 +2777,7 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { _centerYPosition, x, y, - endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor, + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!, heightDifference, isStartRange); } else if (isBetweenRange) { @@ -2776,7 +2790,6 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { @override void drawCustomCellSelection(Canvas canvas, double x, double y, int index) { - _selectionPainter ??= Paint(); _selectionPainter.strokeWidth = 0.0; _selectionPainter.style = PaintingStyle.fill; _selectionPainter.isAntiAlias = true; @@ -2787,16 +2800,16 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { final bool isBetweenRange = selectionDetails[3]; if (isSelectedDate) { _selectionPainter.color = - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!; } else if (isStartRange) { _selectionPainter.color = - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!; } else if (isEndRange) { _selectionPainter.color = - endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor; + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!; } else if (isBetweenRange) { _selectionPainter.color = - rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; } canvas.drawRect(Rect.fromLTRB(x, y, x + _cellWidth, y + _cellHeight), @@ -2857,8 +2870,8 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { final List selectedIndex = []; _selectedRangesIndex = >[]; if (selectedRanges != null) { - for (int j = 0; j < selectedRanges.length; j++) { - final dynamic range = selectedRanges[j]; + for (int j = 0; j < selectedRanges!.length; j++) { + final dynamic range = selectedRanges![j]; final dynamic startDate = range.startDate; final dynamic endDate = range.endDate ?? range.startDate; final List rangeIndex = _getSelectedRangeIndex( @@ -2923,7 +2936,7 @@ void _drawSelectedDate( double centerYPosition) { view._selectionPainter.isAntiAlias = true; view._selectionPainter.color = view.startRangeSelectionColor ?? - view.datePickerTheme.startRangeSelectionColor; + view.datePickerTheme.startRangeSelectionColor!; switch (view.selectionShape) { case DateRangePickerSelectionShape.circle: { @@ -3002,7 +3015,7 @@ TextStyle _drawBetweenSelection( } view._selectionPainter.color = - view.rangeSelectionColor ?? view.datePickerTheme.rangeSelectionColor; + view.rangeSelectionColor ?? view.datePickerTheme.rangeSelectionColor!; _drawRectRangeSelection(canvas, x, y + heightDifference, x + cellWidth, y + cellHeight - heightDifference, view._selectionPainter); return selectionRangeTextStyle; @@ -3014,7 +3027,7 @@ double _getCellRadius( ? maxYRadius - _IMonthView._selectionPadding : maxXRadius - _IMonthView._selectionPadding; - if (selectionRadius == null) { + if (selectionRadius == -1) { return radius; } @@ -3169,18 +3182,16 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, height = (height - webUIPadding) / viewCount; } - monthView._textPainter ??= TextPainter( - textDirection: TextDirection.ltr, - textScaleFactor: monthView.textScaleFactor, - textWidthBasis: TextWidthBasis.longestLine); - TextStyle textStyle = monthView.cellStyle.textStyle; + monthView._textPainter.textScaleFactor = monthView.textScaleFactor; + TextStyle textStyle = monthView.cellStyle.textStyle ?? + monthView.datePickerTheme.activeDatesTextStyle; final int datesCount = monthView.visibleDates.length ~/ viewCount; final bool isNeedWidgetPaint = monthView.childCount != 0; final bool hideLeadingAndTrailingDates = (monthView.rowCount == 6 && !monthView.showLeadingAndTailingDates) || monthView.isHijri; if (isNeedWidgetPaint) { - RenderBox child = monthView.firstChild; + RenderBox? child = monthView.firstChild; for (int j = 0; j < viewCount; j++) { final int currentViewIndex = monthView.isRtl ? DateRangePickerHelper.getRtlIndex(viewCount, j) : j; @@ -3249,25 +3260,24 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, canvas, xPosition, yPosition, currentIndex); } - child.paint(context, Offset(xPosition, yPosition)); + child!.paint(context, Offset(xPosition, yPosition)); child = monthView.childAfter(child); - if (monthView.mouseHoverPosition != null && - monthView.mouseHoverPosition.value != null) { + if (monthView.mouseHoverPosition.value != null) { if (isSelectedDate || isBlackedDate || !isEnableDate) { xPosition += cellWidth; continue; } - if (xPosition <= monthView.mouseHoverPosition.value.dx && - xPosition + cellWidth >= monthView.mouseHoverPosition.value.dx && - yPosition <= monthView.mouseHoverPosition.value.dy && - yPosition + cellHeight >= monthView.mouseHoverPosition.value.dy) { - monthView._selectionPainter ??= Paint(); + if (xPosition <= monthView.mouseHoverPosition.value!.dx && + xPosition + cellWidth >= monthView.mouseHoverPosition.value!.dx && + yPosition <= monthView.mouseHoverPosition.value!.dy && + yPosition + cellHeight >= + monthView.mouseHoverPosition.value!.dy) { monthView._selectionPainter.style = PaintingStyle.fill; monthView._selectionPainter.strokeWidth = 2; monthView._selectionPainter.color = monthView.selectionColor != null - ? monthView.selectionColor.withOpacity(0.4) - : monthView.datePickerTheme.selectionColor.withOpacity(0.4); + ? monthView.selectionColor!.withOpacity(0.4) + : monthView.datePickerTheme.selectionColor!.withOpacity(0.4); canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromLTWH(xPosition, yPosition, cellWidth, cellHeight), @@ -3297,7 +3307,7 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, final TextStyle selectedRangeTextStyle = monthView.rangeTextStyle ?? monthView.datePickerTheme.rangeSelectionTextStyle; - Decoration dateDecoration; + Decoration? dateDecoration; const double padding = 1; final int viewStartIndex = j * datesCount; @@ -3416,8 +3426,7 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, canvas, Offset(xPosition + (cellWidth / 2 - monthView._textPainter.width / 2), yPosition + ((cellHeight - monthView._textPainter.height) / 2))); - if (monthView.mouseHoverPosition != null && - monthView.mouseHoverPosition.value != null) { + if (monthView.mouseHoverPosition.value != null) { if (isSelectedDate || isBlackedDate || !isEnableDate) { xPosition += cellWidth; continue; @@ -3434,16 +3443,15 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, void _addHoveringEffect(Canvas canvas, _IMonthView monthView, double xPosition, double yPosition, double cellWidth, double cellHeight) { - if (xPosition <= monthView.mouseHoverPosition.value.dx && - xPosition + cellWidth >= monthView.mouseHoverPosition.value.dx && - yPosition <= monthView.mouseHoverPosition.value.dy && - yPosition + cellHeight >= monthView.mouseHoverPosition.value.dy) { - monthView._selectionPainter = monthView._selectionPainter ?? Paint(); + if (xPosition <= monthView.mouseHoverPosition.value!.dx && + xPosition + cellWidth >= monthView.mouseHoverPosition.value!.dx && + yPosition <= monthView.mouseHoverPosition.value!.dy && + yPosition + cellHeight >= monthView.mouseHoverPosition.value!.dy) { monthView._selectionPainter.style = PaintingStyle.fill; monthView._selectionPainter.strokeWidth = 2; monthView._selectionPainter.color = monthView.selectionColor != null - ? monthView.selectionColor.withOpacity(0.4) - : monthView.datePickerTheme.selectionColor.withOpacity(0.4); + ? monthView.selectionColor!.withOpacity(0.4) + : monthView.datePickerTheme.selectionColor!.withOpacity(0.4); switch (monthView.selectionShape) { case DateRangePickerSelectionShape.circle: { @@ -3478,9 +3486,8 @@ TextStyle _drawCellAndSelection( TextStyle selectedRangeTextStyle, _IMonthView monthView, int currentIndex) { - monthView._selectionPainter = monthView._selectionPainter ?? Paint(); monthView._selectionPainter.color = - monthView.selectionColor ?? monthView.datePickerTheme.selectionColor; + monthView.selectionColor ?? monthView.datePickerTheme.selectionColor!; //// Unwanted space shown at end of the rectangle while enable anti aliasing property. monthView._selectionPainter.isAntiAlias = false; monthView._selectionPainter.strokeWidth = 0.0; @@ -3509,9 +3516,8 @@ void _drawDecoration( void _drawCurrentDate(Canvas canvas, _IMonthView monthView, double xPosition, double yPosition, double padding, double cellWidth, double cellHeight) { - monthView._selectionPainter = monthView._selectionPainter ?? Paint(); monthView._selectionPainter.color = monthView.todayHighlightColor ?? - monthView.datePickerTheme.todayHighlightColor; + monthView.datePickerTheme.todayHighlightColor!; monthView._selectionPainter.isAntiAlias = true; monthView._selectionPainter.strokeWidth = 1.0; monthView._selectionPainter.style = PaintingStyle.stroke; @@ -3580,9 +3586,8 @@ TextStyle _updateTextStyle( if (isWeekEnd && monthView.cellStyle.weekendTextStyle != null) { return monthView.cellStyle.weekendTextStyle; } else if (isWeekEnd && - monthView.datePickerTheme != null && monthView.datePickerTheme.weekendDatesTextStyle != null) { - return monthView.datePickerTheme.weekendDatesTextStyle; + return monthView.datePickerTheme.weekendDatesTextStyle!; } if (isNextMonth && !monthView.isHijri) { @@ -3596,7 +3601,7 @@ TextStyle _updateTextStyle( return currentDatesTextStyle; } -Decoration _updateDecoration( +Decoration? _updateDecoration( bool isNextMonth, bool isPreviousMonth, _IMonthView monthView, @@ -3606,7 +3611,7 @@ Decoration _updateDecoration( dynamic date, bool isWeekEnd, bool isSpecialDate) { - final Decoration dateDecoration = monthView.cellStyle.cellDecoration; + final Decoration? dateDecoration = monthView.cellStyle.cellDecoration; if (isBlackedDate) { return monthView.cellStyle.blackoutDatesDecoration; diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_helper.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_helper.dart index b9319f890..11f4af8e5 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_helper.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_helper.dart @@ -14,10 +14,6 @@ class DateRangePickerHelper { /// Calculate the visible dates count based on picker view static int getViewDatesCount( DateRangePickerView pickerView, int numberOfWeeks, bool isHijri) { - if (pickerView == null) { - return 0; - } - if (pickerView == DateRangePickerView.month) { if (isHijri) { /// 6 used to render the default number of weeks, since, Hijri type @@ -32,7 +28,7 @@ class DateRangePickerHelper { } /// Checks both the ranges are equal or not. - static bool isRangeEquals(dynamic range1, dynamic range2) { + static bool isRangeEquals(dynamic? range1, dynamic? range2) { if ((range1 == null && range2 != null) || (range2 == null && range1 != null)) { return false; @@ -49,7 +45,7 @@ class DateRangePickerHelper { /// Checks both the range collections are equal or not. static bool isDateRangesEquals( - List rangeCollection1, List rangeCollection2) { + List? rangeCollection1, List? rangeCollection2) { if (rangeCollection1 == rangeCollection2) { return true; } @@ -65,12 +61,12 @@ class DateRangePickerHelper { if ((rangeCollection1 == null && rangeCollection2 != null) || (rangeCollection2 == null && rangeCollection1 != null) || - (rangeCollection1.length != rangeCollection2.length)) { + (rangeCollection1?.length != rangeCollection2?.length)) { return false; } - for (int i = 0; i < rangeCollection1.length; i++) { - if (!isRangeEquals(rangeCollection1[i], rangeCollection2[i])) { + for (int i = 0; i < rangeCollection1!.length; i++) { + if (!isRangeEquals(rangeCollection1[i], rangeCollection2![i])) { return false; } } @@ -81,11 +77,7 @@ class DateRangePickerHelper { /// Calculate the next view visible start date based on picker view. static dynamic getNextViewStartDate(DateRangePickerView pickerView, int numberOfWeeksInView, dynamic date, bool isRtl, bool isHijri) { - if (pickerView == null) { - return date; - } - - if (isRtl != null && isRtl) { + if (isRtl) { return getPreviousViewStartDate( pickerView, numberOfWeeksInView, date, false, isHijri); } @@ -95,8 +87,7 @@ class DateRangePickerHelper { { return isHijri || numberOfWeeksInView == 6 ? getNextMonthDate(date) - : addDuration(date, - Duration(days: numberOfWeeksInView * DateTime.daysPerWeek)); + : addDays(date, numberOfWeeksInView * DateTime.daysPerWeek); } case DateRangePickerView.year: { @@ -111,18 +102,12 @@ class DateRangePickerHelper { return getNextYearDate(date, 100, isHijri); } } - - return date; } /// Calculate the previous view visible start date based on calendar view. static dynamic getPreviousViewStartDate(DateRangePickerView pickerView, int numberOfWeeksInView, dynamic date, bool isRtl, bool isHijri) { - if (pickerView == null) { - return date; - } - - if (isRtl != null && isRtl) { + if (isRtl) { return getNextViewStartDate( pickerView, numberOfWeeksInView, date, false, isHijri); } @@ -132,8 +117,7 @@ class DateRangePickerHelper { { return isHijri || numberOfWeeksInView == 6 ? getPreviousMonthDate(date) - : addDuration(date, - Duration(days: -numberOfWeeksInView * DateTime.daysPerWeek)); + : addDays(date, -numberOfWeeksInView * DateTime.daysPerWeek); } case DateRangePickerView.year: { @@ -148,8 +132,6 @@ class DateRangePickerHelper { return getPreviousYearDate(date, 100, isHijri); } } - - return date; } /// Return the next view date of year view based on it offset value. @@ -173,11 +155,11 @@ class DateRangePickerHelper { /// Return the month end date. static dynamic getMonthEndDate(dynamic date) { - return subtractDuration(getNextMonthDate(date), const Duration(days: 1)); + return addDays(getNextMonthDate(date), -1); } /// Return the index of the date in collection. - static int isDateIndexInCollection(List dates, dynamic date) { + static int isDateIndexInCollection(List? dates, dynamic? date) { if (dates == null || date == null) { return -1; } @@ -194,7 +176,7 @@ class DateRangePickerHelper { /// Checks both the date collection are equal or not. static bool isDateCollectionEquals( - List datesCollection1, List datesCollection2) { + List? datesCollection1, List? datesCollection2) { if (datesCollection1 == datesCollection2) { return true; } @@ -210,12 +192,12 @@ class DateRangePickerHelper { if ((datesCollection1 == null && datesCollection2 != null) || (datesCollection2 == null && datesCollection1 != null) || - (datesCollection1.length != datesCollection2.length)) { + (datesCollection1?.length != datesCollection2?.length)) { return false; } - for (int i = 0; i < datesCollection1.length; i++) { - if (!isSameDate(datesCollection1[i], datesCollection2[i])) { + for (int i = 0; i < datesCollection1!.length; i++) { + if (!isSameDate(datesCollection1[i], datesCollection2![i])) { return false; } } @@ -225,8 +207,8 @@ class DateRangePickerHelper { /// Check the date as enable date or disable date based on min date, max date /// and enable past dates values. - static bool isEnabledDate(dynamic startDate, dynamic endDate, - bool enablePastDates, dynamic date, bool isHijri) { + static bool isEnabledDate(dynamic? startDate, dynamic? endDate, + bool enablePastDates, dynamic? date, bool isHijri) { return isDateWithInDateRange(startDate, endDate, date) && (enablePastDates || (!enablePastDates && @@ -277,7 +259,7 @@ class DateRangePickerHelper { /// Check the date placed in dates collection based on visible dates. static bool isDateWithInVisibleDates( - List visibleDates, List dates, dynamic date) { + List visibleDates, List? dates, dynamic date) { if (dates == null || dates.isEmpty) { return false; } @@ -299,7 +281,7 @@ class DateRangePickerHelper { } /// Check the date week day placed in week end day collection. - static bool isWeekend(List weekendIndex, dynamic date) { + static bool isWeekend(List? weekendIndex, dynamic date) { if (weekendIndex == null || weekendIndex.isEmpty) { return false; } @@ -358,14 +340,13 @@ class DateRangePickerHelper { { if (numberOfWeeksInView != 6 && !isHijri) { DateTime prevViewDate = visibleDates[0]; - prevViewDate = - subtractDuration(prevViewDate, const Duration(days: 1)); + prevViewDate = addDays(prevViewDate, -1); if (!isSameOrAfterDate(minDate, prevViewDate)) { return false; } } else { - final dynamic currentDate = visibleDates[visibleDates.length ~/ - (enableMultiView != null && enableMultiView ? 4 : 2)]; + final dynamic currentDate = + visibleDates[visibleDates.length ~/ (enableMultiView ? 4 : 2)]; final dynamic previousDate = getPreviousMonthDate(currentDate); if (((previousDate.month < minDate.month && previousDate.year == minDate.year) || @@ -379,9 +360,9 @@ class DateRangePickerHelper { case DateRangePickerView.decade: case DateRangePickerView.century: { - final int currentYear = visibleDates[visibleDates.length ~/ - (enableMultiView != null && enableMultiView ? 4 : 2)] - .year; + final int currentYear = + visibleDates[visibleDates.length ~/ (enableMultiView ? 4 : 2)] + .year; final int minYear = minDate.year; final int offset = getOffset(view); @@ -412,9 +393,15 @@ class DateRangePickerHelper { } /// Get the visible dates based on the date value and visible dates count. - static List getVisibleYearDates( + static List getVisibleYearDates( dynamic date, DateRangePickerView view, bool isHijri) { - final List datesCollection = []; + List datesCollection; + if (isHijri) { + datesCollection = []; + } else { + datesCollection = []; + } + dynamic currentDate; const int daysCount = 12; switch (view) { @@ -465,13 +452,13 @@ class DateRangePickerHelper { { if (!isHijri && numberOfWeeksInView != 6) { DateTime nextViewDate = visibleDates[visibleDates.length - 1]; - nextViewDate = addDuration(nextViewDate, const Duration(days: 1)); + nextViewDate = addDays(nextViewDate, 1); if (!isSameOrBeforeDate(maxDate, nextViewDate)) { return false; } } else { - final dynamic currentDate = visibleDates[visibleDates.length ~/ - (enableMultiView != null && enableMultiView ? 4 : 2)]; + final dynamic currentDate = + visibleDates[visibleDates.length ~/ (enableMultiView ? 4 : 2)]; final dynamic nextDate = getNextMonthDate(currentDate); if (((nextDate.month > maxDate.month && nextDate.year == maxDate.year) || @@ -485,9 +472,9 @@ class DateRangePickerHelper { case DateRangePickerView.decade: case DateRangePickerView.century: { - final int currentYear = visibleDates[visibleDates.length ~/ - (enableMultiView != null && enableMultiView ? 4 : 2)] - .year; + final int currentYear = + visibleDates[visibleDates.length ~/ (enableMultiView ? 4 : 2)] + .year; final int maxYear = maxDate.year; final int offset = getOffset(view); if (((currentYear ~/ offset) * offset) + offset > @@ -500,9 +487,9 @@ class DateRangePickerHelper { } /// Return the copy of the list. - static List cloneList(List value) { + static List? cloneList(List? value) { if (value == null || value.isEmpty) { - return null; + return value; } return value.sublist(0); @@ -631,7 +618,7 @@ class DateRangePickerHelper { } } - return null; + return DateRangePickerView.month; } /// Returns teh [HijriDatePickerView] value based on the given value. @@ -659,7 +646,7 @@ class DateRangePickerHelper { } } - return null; + return HijriDatePickerView.month; } /// Returns the number of weeks in view for the picker. @@ -703,7 +690,8 @@ class DateRangePickerHelper { /// Eg., In year view, 20-01-2020 and 21-01-2020 dates are not equal but /// both the dates are placed in same year cell. /// Note: This method not applicable for month view. - static bool isSameCellDates(dynamic date, dynamic currentDate, dynamic view) { + static bool isSameCellDates( + dynamic? date, dynamic? currentDate, dynamic view) { if (date == null || currentDate == null) { return false; } @@ -754,8 +742,8 @@ class DateRangePickerHelper { /// then the year view need to highlight selection because year view only /// consider the month value(max month as 12). /// Note: This method not applicable for month view. - static bool isBetweenMinMaxDateCell(dynamic date, dynamic minDate, - dynamic maxDate, bool enablePastDates, dynamic view, bool isHijri) { + static bool isBetweenMinMaxDateCell(dynamic? date, dynamic? minDate, + dynamic? maxDate, bool enablePastDates, dynamic view, bool isHijri) { if (date == null || minDate == null || maxDate == null) { return true; } @@ -794,7 +782,7 @@ class DateRangePickerHelper { /// Eg., If picker view is year and the date value as 20-01-2020 then /// it return the last date of the month(31-01-2020). /// Note: This method not applicable for month view. - static dynamic getLastDate(dynamic date, dynamic view, bool isHijri) { + static dynamic getLastDate(dynamic? date, dynamic? view, bool isHijri) { final DateRangePickerView pickerView = getPickerView(view); if (pickerView == DateRangePickerView.month) { return date; @@ -803,14 +791,14 @@ class DateRangePickerHelper { if (pickerView == DateRangePickerView.year) { final dynamic currentDate = getDate(date.year, date.month + 1, 1, isHijri); - return subtractDuration(currentDate, const Duration(days: 1)); + return addDays(currentDate, -1); } else if (pickerView == DateRangePickerView.decade) { final dynamic currentDate = getDate(date.year + 1, 1, 1, isHijri); - return subtractDuration(currentDate, const Duration(days: 1)); + return addDays(currentDate, -1); } else if (pickerView == DateRangePickerView.century) { final dynamic currentDate = getDate(((date.year ~/ 10) * 10) + 10, 1, 1, isHijri); - return subtractDuration(currentDate, const Duration(days: 1)); + return addDays(currentDate, -1); } return date; @@ -818,7 +806,7 @@ class DateRangePickerHelper { /// Return index of the date value in dates collection. /// Return -1 when the date does not exist in dates collection. - static int getDateCellIndex(List dates, dynamic date, dynamic view, + static int getDateCellIndex(List dates, dynamic? date, dynamic view, {int viewStartIndex = -1, int viewEndIndex = -1}) { if (date == null) { return -1; @@ -837,3 +825,39 @@ class DateRangePickerHelper { return -1; } } + +/// args to update the required properties from picker state to it's children's +class PickerStateArgs { + /// Holds the current view display date. + dynamic currentDate; + + /// Holds the current view visible dates. + List currentViewVisibleDates = []; + + /// Holds the current selected date. + dynamic selectedDate; + + /// Holds the current selected dates. + List? selectedDates; + + /// Holds the current selected range. + dynamic selectedRange; + + /// Holds the current selected ranges. + List? selectedRanges; + + /// Holds the current picker view. + DateRangePickerView view = DateRangePickerView.month; + + /// clone this picker state args with new instance + PickerStateArgs clone() { + return PickerStateArgs() + ..currentViewVisibleDates = currentViewVisibleDates + ..currentDate = currentDate + ..view = view + ..selectedDate = selectedDate + ..selectedDates = selectedDates + ..selectedRange = selectedRange + ..selectedRanges = selectedRanges; + } +} diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view.dart index b598503f9..f2715c050 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view.dart @@ -52,25 +52,25 @@ class YearView extends StatefulWidget { final dynamic cellStyle; /// Defines the text style for selected year cell. - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; /// Defines the range text style for selected range year cell. - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; /// Defines the background color for selected year cell. - final Color selectionColor; + final Color? selectionColor; /// Defines the navigation direction for [SfDateRangePicker]. final DateRangePickerNavigationDirection navigationDirection; /// Defines the background color for selected range start date year cell. - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; /// Defines the background color for selected range end date year cell. - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; /// Defines the background color for selected range in between dates cell. - final Color rangeSelectionColor; + final Color? rangeSelectionColor; /// Holds the visible dates for the year view. final List visibleDates; @@ -79,7 +79,7 @@ class YearView extends StatefulWidget { final bool isRtl; /// Defines the today cell highlight color. - final Color todayHighlightColor; + final Color? todayHighlightColor; /// The minimum date as much as the [SfDateRangePicker] will navigate. final dynamic minDate; @@ -97,7 +97,7 @@ class YearView extends StatefulWidget { final SfDateRangePickerThemeData datePickerTheme; /// Used to specify the mouse hover position of the year view. - final ValueNotifier mouseHoverPosition; + final ValueNotifier mouseHoverPosition; /// Used to call repaint when the selection changes. final ValueNotifier selectionNotifier; @@ -127,10 +127,10 @@ class YearView extends StatefulWidget { final DateRangePickerView view; /// Used to build the widget that replaces the month cells in month view. - final DateRangePickerCellBuilder cellBuilder; + final dynamic cellBuilder; /// Defines the month format for the year view cell text. - final String monthFormat; + final String? monthFormat; /// Defines the locale of the picker. final Locale locale; @@ -158,16 +158,17 @@ class YearView extends StatefulWidget { } class _YearViewState extends State { - PickerStateArgs _pickerStateDetails; - dynamic _selectedDate; - List _selectedDates; - dynamic _selectedRange; - List _selectedRanges; - List _children; + late PickerStateArgs _pickerStateDetails; + dynamic? _selectedDate; + List? _selectedDates; + dynamic? _selectedRange; + List? _selectedRanges; + late List _children; @override void initState() { _pickerStateDetails = PickerStateArgs(); + _children = []; widget.getPickerStateDetails(_pickerStateDetails); _selectedDate = _pickerStateDetails.selectedDate; _selectedDates = @@ -208,7 +209,6 @@ class _YearViewState extends State { @override Widget build(BuildContext context) { - _children ??= []; if (widget.cellBuilder != null && _children.isEmpty) { double webUIPadding = 0; double width = widget.width; @@ -260,7 +260,7 @@ class _YearViewState extends State { } currentIndex += viewStartIndex; - if (xPosition >= viewEndPosition) { + if (xPosition + 1 >= viewEndPosition) { xPosition = viewStartPosition; yPosition += cellHeight; } @@ -273,15 +273,19 @@ class _YearViewState extends State { } final dynamic date = widget.visibleDates[currentIndex]; - - final DateRangePickerCellDetails cellDetails = - DateRangePickerCellDetails( - date: date, - visibleDates: widget.visibleDates, - bounds: Rect.fromLTWH( - xPosition, yPosition, cellWidth, cellHeight)); - final Widget child = widget.cellBuilder(context, cellDetails); - assert(child != null, 'Widget must not be null'); + final Widget child = widget.cellBuilder( + context, + widget.isHijri + ? HijriDateRangePickerCellDetails( + date: date, + visibleDates: widget.visibleDates.cast(), + bounds: Rect.fromLTWH( + xPosition, yPosition, cellWidth, cellHeight)) + : DateRangePickerCellDetails( + date: date, + visibleDates: widget.visibleDates.cast(), + bounds: Rect.fromLTWH( + xPosition, yPosition, cellWidth, cellHeight))); _children.add(child); xPosition += cellWidth; } @@ -434,8 +438,6 @@ class _YearViewState extends State { widgets: _children); } } - - return null; } void _updateSelection({bool isNeedSetState = true}) { @@ -491,8 +493,6 @@ class _YearViewState extends State { return _selectedRanges; } } - - return null; } bool _isSelectedValueEquals() { @@ -517,7 +517,6 @@ class _YearViewState extends State { _selectedRanges, _pickerStateDetails.selectedRanges); } } - return false; } } @@ -553,7 +552,7 @@ class _SingleSelectionRenderWidget extends MultiChildRenderObjectWidget { this.isHijri, this.localizations, this.navigationDirection, - {List widgets}) + {required List widgets}) : super(children: widgets); /// Defines the year cell style. @@ -562,22 +561,22 @@ class _SingleSelectionRenderWidget extends MultiChildRenderObjectWidget { final DateRangePickerNavigationDirection navigationDirection; /// Defines the text style for selected year cell. - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; /// Defines the range text style for selected range year cell. - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; /// Defines the background color for selected year cell. - final Color selectionColor; + final Color? selectionColor; /// Defines the background color for selected range start date year cell. - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; /// Defines the background color for selected range end date year cell. - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; /// Defines the background color for selected range in between dates cell. - final Color rangeSelectionColor; + final Color? rangeSelectionColor; /// Holds the visible dates for the year view. final List visibleDates; @@ -586,7 +585,7 @@ class _SingleSelectionRenderWidget extends MultiChildRenderObjectWidget { final bool isRtl; /// Defines the today cell highlight color. - final Color todayHighlightColor; + final Color? todayHighlightColor; /// The minimum date as much as the [SfDateRangePicker] will navigate. final dynamic minDate; @@ -604,13 +603,13 @@ class _SingleSelectionRenderWidget extends MultiChildRenderObjectWidget { final SfDateRangePickerThemeData datePickerTheme; /// Used to specify the mouse hover position of the year view. - final ValueNotifier mouseHoverPosition; + final ValueNotifier mouseHoverPosition; /// Used to call repaint when the selection changes. final ValueNotifier selectionNotifier; /// Holds the selected date value. - final dynamic selectedDate; + final dynamic? selectedDate; /// Holds the selection radius of the year cell. final double selectionRadius; @@ -624,7 +623,7 @@ class _SingleSelectionRenderWidget extends MultiChildRenderObjectWidget { /// Defines the text scale factor of [SfDateRangePicker]. final double textScaleFactor; - final String monthFormat; + final String? monthFormat; final Locale locale; @@ -742,7 +741,7 @@ class _MultiSelectionRenderWidget extends MultiChildRenderObjectWidget { this.isHijri, this.localizations, this.navigationDirection, - {List widgets}) + {required List widgets}) : super(children: widgets); /// Defines the year cell style. @@ -751,22 +750,22 @@ class _MultiSelectionRenderWidget extends MultiChildRenderObjectWidget { final DateRangePickerNavigationDirection navigationDirection; /// Defines the text style for selected year cell. - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; /// Defines the range text style for selected range year cell. - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; /// Defines the background color for selected year cell. - final Color selectionColor; + final Color? selectionColor; /// Defines the background color for selected range start date year cell. - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; /// Defines the background color for selected range end date year cell. - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; /// Defines the background color for selected range in between dates cell. - final Color rangeSelectionColor; + final Color? rangeSelectionColor; /// Holds the visible dates for the year view. final List visibleDates; @@ -775,7 +774,7 @@ class _MultiSelectionRenderWidget extends MultiChildRenderObjectWidget { final bool isRtl; /// Defines the today cell highlight color. - final Color todayHighlightColor; + final Color? todayHighlightColor; /// The minimum date as much as the [SfDateRangePicker] will navigate. final dynamic minDate; @@ -793,13 +792,13 @@ class _MultiSelectionRenderWidget extends MultiChildRenderObjectWidget { final SfDateRangePickerThemeData datePickerTheme; /// Used to specify the mouse hover position of the year view. - final ValueNotifier mouseHoverPosition; + final ValueNotifier mouseHoverPosition; /// Used to call repaint when the selection changes. final ValueNotifier selectionNotifier; /// Holds the selected dates value. - final List selectedDates; + final List? selectedDates; /// Holds the selection radius of the year cell. final double selectionRadius; @@ -813,7 +812,7 @@ class _MultiSelectionRenderWidget extends MultiChildRenderObjectWidget { /// Defines the text scale factor of [SfDateRangePicker]. final double textScaleFactor; - final String monthFormat; + final String? monthFormat; final Locale locale; @@ -931,29 +930,29 @@ class _RangeSelectionRenderWidget extends MultiChildRenderObjectWidget { this.isHijri, this.localizations, this.navigationDirection, - {List widgets}) + {required List widgets}) : super(children: widgets); /// Defines the year cell style. final dynamic cellStyle; /// Defines the text style for selected year cell. - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; /// Defines the range text style for selected range year cell. - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; /// Defines the background color for selected year cell. - final Color selectionColor; + final Color? selectionColor; /// Defines the background color for selected range start date year cell. - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; /// Defines the background color for selected range end date year cell. - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; /// Defines the background color for selected range in between dates cell. - final Color rangeSelectionColor; + final Color? rangeSelectionColor; /// Holds the visible dates for the year view. final List visibleDates; @@ -962,7 +961,7 @@ class _RangeSelectionRenderWidget extends MultiChildRenderObjectWidget { final bool isRtl; /// Defines the today cell highlight color. - final Color todayHighlightColor; + final Color? todayHighlightColor; /// The minimum date as much as the [SfDateRangePicker] will navigate. final dynamic minDate; @@ -982,13 +981,13 @@ class _RangeSelectionRenderWidget extends MultiChildRenderObjectWidget { final SfDateRangePickerThemeData datePickerTheme; /// Used to specify the mouse hover position of the year view. - final ValueNotifier mouseHoverPosition; + final ValueNotifier mouseHoverPosition; /// Used to call repaint when the selection changes. final ValueNotifier selectionNotifier; /// Holds the selected range value.. - final dynamic selectedRange; + final dynamic? selectedRange; /// Holds the selection radius of the year cell. final double selectionRadius; @@ -1002,7 +1001,7 @@ class _RangeSelectionRenderWidget extends MultiChildRenderObjectWidget { /// Defines the text scale factor of [SfDateRangePicker]. final double textScaleFactor; - final String monthFormat; + final String? monthFormat; final Locale locale; @@ -1120,29 +1119,29 @@ class _MultiRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { this.isHijri, this.localizations, this.navigationDirection, - {List widgets}) + {required List widgets}) : super(children: widgets); /// Defines the year cell style. final dynamic cellStyle; /// Defines the text style for selected year cell. - final TextStyle selectionTextStyle; + final TextStyle? selectionTextStyle; /// Defines the range text style for selected range year cell. - final TextStyle rangeTextStyle; + final TextStyle? rangeTextStyle; /// Defines the background color for selected year cell. - final Color selectionColor; + final Color? selectionColor; /// Defines the background color for selected range start date year cell. - final Color startRangeSelectionColor; + final Color? startRangeSelectionColor; /// Defines the background color for selected range end date year cell. - final Color endRangeSelectionColor; + final Color? endRangeSelectionColor; /// Defines the background color for selected range in between dates cell. - final Color rangeSelectionColor; + final Color? rangeSelectionColor; /// Holds the visible dates for the year view. final List visibleDates; @@ -1151,7 +1150,7 @@ class _MultiRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { final bool isRtl; /// Defines the today cell highlight color. - final Color todayHighlightColor; + final Color? todayHighlightColor; /// The minimum date as much as the [SfDateRangePicker] will navigate. final dynamic minDate; @@ -1172,13 +1171,13 @@ class _MultiRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { final SfDateRangePickerThemeData datePickerTheme; /// Used to specify the mouse hover position of the year view. - final ValueNotifier mouseHoverPosition; + final ValueNotifier mouseHoverPosition; /// Used to call repaint when the selection changes. final ValueNotifier selectionNotifier; /// Holds the selected value based on [SfDateRangePicker] selection mode. - final List selectedRanges; + final List? selectedRanges; /// Holds the selection radius of the year cell. final double selectionRadius; @@ -1192,7 +1191,7 @@ class _MultiRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { /// Defines the text scale factor of [SfDateRangePicker]. final double textScaleFactor; - final String monthFormat; + final String? monthFormat; final Locale locale; @@ -1330,11 +1329,11 @@ abstract class _IYearViewRenderObject extends RenderBox } } - dynamic _cellStyle; + dynamic? _cellStyle; - dynamic get cellStyle => _cellStyle; + dynamic? get cellStyle => _cellStyle; - set cellStyle(dynamic value) { + set cellStyle(dynamic? value) { if (_cellStyle == value) { return; } @@ -1347,11 +1346,11 @@ abstract class _IYearViewRenderObject extends RenderBox markNeedsPaint(); } - TextStyle _selectionTextStyle; + TextStyle? _selectionTextStyle; - TextStyle get selectionTextStyle => _selectionTextStyle; + TextStyle? get selectionTextStyle => _selectionTextStyle; - set selectionTextStyle(TextStyle value) { + set selectionTextStyle(TextStyle? value) { if (_selectionTextStyle == value) { return; } @@ -1364,11 +1363,11 @@ abstract class _IYearViewRenderObject extends RenderBox markNeedsPaint(); } - TextStyle _rangeTextStyle; + TextStyle? _rangeTextStyle; - TextStyle get rangeTextStyle => _rangeTextStyle; + TextStyle? get rangeTextStyle => _rangeTextStyle; - set rangeTextStyle(TextStyle value) { + set rangeTextStyle(TextStyle? value) { if (_rangeTextStyle == value) { return; } @@ -1381,11 +1380,11 @@ abstract class _IYearViewRenderObject extends RenderBox markNeedsPaint(); } - Color _selectionColor; + Color? _selectionColor; - Color get selectionColor => _selectionColor; + Color? get selectionColor => _selectionColor; - set selectionColor(Color value) { + set selectionColor(Color? value) { if (_selectionColor == value) { return; } @@ -1398,11 +1397,11 @@ abstract class _IYearViewRenderObject extends RenderBox markNeedsPaint(); } - Color _startRangeSelectionColor; + Color? _startRangeSelectionColor; - Color get startRangeSelectionColor => _startRangeSelectionColor; + Color? get startRangeSelectionColor => _startRangeSelectionColor; - set startRangeSelectionColor(Color value) { + set startRangeSelectionColor(Color? value) { if (_startRangeSelectionColor == value) { return; } @@ -1415,11 +1414,11 @@ abstract class _IYearViewRenderObject extends RenderBox markNeedsPaint(); } - Color _endRangeSelectionColor; + Color? _endRangeSelectionColor; - Color get endRangeSelectionColor => _endRangeSelectionColor; + Color? get endRangeSelectionColor => _endRangeSelectionColor; - set endRangeSelectionColor(Color value) { + set endRangeSelectionColor(Color? value) { if (_endRangeSelectionColor == value) { return; } @@ -1432,11 +1431,11 @@ abstract class _IYearViewRenderObject extends RenderBox markNeedsPaint(); } - Color _rangeSelectionColor; + Color? _rangeSelectionColor; - Color get rangeSelectionColor => _rangeSelectionColor; + Color? get rangeSelectionColor => _rangeSelectionColor; - set rangeSelectionColor(Color value) { + set rangeSelectionColor(Color? value) { if (_rangeSelectionColor == value) { return; } @@ -1479,11 +1478,11 @@ abstract class _IYearViewRenderObject extends RenderBox markNeedsPaint(); } - Color _todayHighlightColor; + Color? _todayHighlightColor; - Color get todayHighlightColor => _todayHighlightColor; + Color? get todayHighlightColor => _todayHighlightColor; - set todayHighlightColor(Color value) { + set todayHighlightColor(Color? value) { if (_todayHighlightColor == value) { return; } @@ -1577,16 +1576,16 @@ abstract class _IYearViewRenderObject extends RenderBox markNeedsPaint(); } - ValueNotifier _mouseHoverPosition; + ValueNotifier _mouseHoverPosition; - ValueNotifier get mouseHoverPosition => _mouseHoverPosition; + ValueNotifier get mouseHoverPosition => _mouseHoverPosition; - set mouseHoverPosition(ValueNotifier value) { + set mouseHoverPosition(ValueNotifier value) { if (_mouseHoverPosition == value) { return; } - _mouseHoverPosition?.removeListener(markNeedsPaint); + _mouseHoverPosition.removeListener(markNeedsPaint); _mouseHoverPosition = value; markNeedsPaint(); } @@ -1690,11 +1689,11 @@ abstract class _IYearViewRenderObject extends RenderBox } } - String _monthFormat; + String? _monthFormat; - String get monthFormat => _monthFormat; + String? get monthFormat => _monthFormat; - set monthFormat(String value) { + set monthFormat(String? value) { if (_monthFormat == value) { return; } @@ -1763,23 +1762,35 @@ abstract class _IYearViewRenderObject extends RenderBox SfLocalizations localizations; /// Used to draw year cell text in month view. - TextPainter _textPainter; + TextPainter _textPainter = TextPainter( + textAlign: TextAlign.start, + textDirection: TextDirection.ltr, + maxLines: 2, + textWidthBasis: TextWidthBasis.longestLine); /// Used to paint the selection of year cell and today highlight on all /// the selection mode. - Paint _todayHighlightPaint; + Paint _todayHighlightPaint = Paint(); + + /// Caches [SemanticsNode]s created during [assembleSemanticsNode] so they + /// can be re-used when [assembleSemanticsNode] is called again. This ensures + /// stable ids for the [SemanticsNode]s of children across + /// [assembleSemanticsNode] invocations. + /// Ref: assembleSemanticsNode method in RenderParagraph class + /// (https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/paragraph.dart) + List? _cacheNodes; /// attach will called when the render object rendered in view. @override void attach(PipelineOwner owner) { super.attach(owner); - _mouseHoverPosition?.addListener(markNeedsPaint); + _mouseHoverPosition.addListener(markNeedsPaint); } /// detach will called when the render object removed from view. @override void detach() { - _mouseHoverPosition?.removeListener(markNeedsPaint); + _mouseHoverPosition.removeListener(markNeedsPaint); super.detach(); } @@ -1795,7 +1806,7 @@ abstract class _IYearViewRenderObject extends RenderBox final Size widgetSize = constraints.biggest; size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, widgetSize.height.isInfinite ? height : widgetSize.height); - RenderBox child = firstChild; + RenderBox? child = firstChild; if (child == null) { return; } @@ -1838,18 +1849,19 @@ abstract class _IYearViewRenderObject extends RenderBox SemanticsConfiguration config, Iterable children, ) { + _cacheNodes ??= []; final List semantics = _getSemanticsBuilder(size); final List semanticsNodes = []; for (int i = 0; i < semantics.length; i++) { final CustomPainterSemantics currentSemantics = semantics[i]; - final SemanticsNode newChild = SemanticsNode( - key: currentSemantics.key, - ); + final SemanticsNode newChild = _cacheNodes!.isNotEmpty + ? _cacheNodes!.removeAt(0) + : SemanticsNode(key: currentSemantics.key); final SemanticsProperties properties = currentSemantics.properties; final SemanticsConfiguration config = SemanticsConfiguration(); if (properties.label != null) { - config.label = properties.label; + config.label = properties.label!; } if (properties.textDirection != null) { config.textDirection = properties.textDirection; @@ -1872,10 +1884,16 @@ abstract class _IYearViewRenderObject extends RenderBox final List finalChildren = []; finalChildren.addAll(semanticsNodes); finalChildren.addAll(children); - + _cacheNodes = semanticsNodes; super.assembleSemanticsNode(node, config, finalChildren); } + @override + void clearSemantics() { + super.clearSemantics(); + _cacheNodes = null; + } + @override void visitChildrenForSemantics(RenderObjectVisitor visitor) { return; @@ -1943,8 +1961,8 @@ abstract class _IYearViewRenderObject extends RenderBox startIndex + i, startIndex, _visibleDates, _view)) { leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; + left = leftAndTopValue['left']!; + top = leftAndTopValue['top']!; continue; } @@ -1961,8 +1979,8 @@ abstract class _IYearViewRenderObject extends RenderBox leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; + left = leftAndTopValue['left']!; + top = leftAndTopValue['top']!; continue; } semanticsBuilder.add(CustomPainterSemantics( @@ -1975,8 +1993,8 @@ abstract class _IYearViewRenderObject extends RenderBox )); leftAndTopValue = DateRangePickerHelper.getTopAndLeftValues( isRtl, left, top, cellWidth, cellHeight, width); - left = leftAndTopValue['left']; - top = leftAndTopValue['top']; + left = leftAndTopValue['left']!; + top = leftAndTopValue['top']!; } } @@ -2048,7 +2066,7 @@ abstract class _IYearViewRenderObject extends RenderBox String _getCellText(dynamic date) { if (_view == DateRangePickerView.year) { final String format = - monthFormat == null || monthFormat.isEmpty ? 'MMM' : monthFormat; + monthFormat == null || monthFormat!.isEmpty ? 'MMM' : monthFormat!; if (isHijri) { return DateRangePickerHelper.getHijriMonthText( date, localizations, format); @@ -2098,16 +2116,15 @@ abstract class _IYearViewRenderObject extends RenderBox double xPosition, double yOffset, double yPosition) { - if (xPosition <= _mouseHoverPosition.value.dx && - xPosition + cellWidth >= _mouseHoverPosition.value.dx && - yPosition <= _mouseHoverPosition.value.dy && - yPosition + cellHeight >= _mouseHoverPosition.value.dy) { - _todayHighlightPaint = _todayHighlightPaint ?? Paint(); + if (xPosition <= _mouseHoverPosition.value!.dx && + xPosition + cellWidth >= _mouseHoverPosition.value!.dx && + yPosition <= _mouseHoverPosition.value!.dy && + yPosition + cellHeight >= _mouseHoverPosition.value!.dy) { _todayHighlightPaint.style = PaintingStyle.fill; _todayHighlightPaint.strokeWidth = 2; _todayHighlightPaint.color = selectionColor != null - ? selectionColor.withOpacity(0.4) - : datePickerTheme.selectionColor.withOpacity(0.4); + ? selectionColor!.withOpacity(0.4) + : datePickerTheme.selectionColor!.withOpacity(0.4); if (centerYPosition - textHalfHeight < highlightPadding / 2) { highlightPadding = (centerYPosition - textHalfHeight / 2) - 1; @@ -2145,9 +2162,8 @@ abstract class _IYearViewRenderObject extends RenderBox double textHalfHeight, double xPosition, double yPosition) { - _todayHighlightPaint ??= Paint(); _todayHighlightPaint.color = - todayHighlightColor ?? datePickerTheme.todayHighlightColor; + todayHighlightColor ?? datePickerTheme.todayHighlightColor!; _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.strokeWidth = 1.0; _todayHighlightPaint.style = PaintingStyle.stroke; @@ -2219,7 +2235,7 @@ abstract class _IYearViewRenderObject extends RenderBox return cellStyle.textStyle ?? datePickerTheme.cellTextStyle; } - Decoration _updateCellDecoration( + Decoration? _updateCellDecoration( int j, bool isCurrentDate, bool isEnableDate, bool isActiveDate) { if (!isEnableDate) { return cellStyle.disabledDatesDecoration; @@ -2244,24 +2260,24 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { dynamic minDate, dynamic maxDate, bool enablePastDates, - Color todayHighlightColor, + Color? todayHighlightColor, DateRangePickerSelectionShape selectionShape, bool isRtl, SfDateRangePickerThemeData datePickerTheme, - ValueNotifier mouseHoverPosition, + ValueNotifier mouseHoverPosition, bool enableMultiView, double multiViewSpacing, - TextStyle selectionTextStyle, - TextStyle rangeTextStyle, - Color selectionColor, - Color startRangeSelectionColor, - Color endRangeSelectionColor, - Color rangeSelectionColor, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, double selectionRadius, double textScaleFactor, double width, double height, - String monthFormat, + String? monthFormat, Locale locale, DateRangePickerView view, bool isHijri, @@ -2298,11 +2314,11 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { navigationDirection, localizations); - dynamic _selectedDate; + dynamic? _selectedDate; - dynamic get selectedDate => _selectedDate; + dynamic? get selectedDate => _selectedDate; - set selectedDate(dynamic value) { + set selectedDate(dynamic? value) { if (isSameDate(_selectedDate, value)) { return; } @@ -2332,7 +2348,6 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { double xPosition, double yPosition, TextSpan yearText) { - _todayHighlightPaint ??= Paint(); _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; final double maximumHighlight = @@ -2351,7 +2366,7 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { ? rect.height / 2 : 3; _todayHighlightPaint.color = - selectionColor ?? datePickerTheme.selectionColor; + selectionColor ?? datePickerTheme.selectionColor!; canvas.drawRRect( RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), @@ -2360,11 +2375,10 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { @override void drawCustomCellSelection(Canvas canvas, Rect rect, int index) { - _todayHighlightPaint ??= Paint(); _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; _todayHighlightPaint.color = - selectionColor ?? datePickerTheme.selectionColor; + selectionColor ?? datePickerTheme.selectionColor!; canvas.drawRect(rect, _todayHighlightPaint); } @@ -2393,24 +2407,24 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { dynamic minDate, dynamic maxDate, bool enablePastDates, - Color todayHighlightColor, + Color? todayHighlightColor, DateRangePickerSelectionShape selectionShape, bool isRtl, SfDateRangePickerThemeData datePickerTheme, - ValueNotifier mouseHoverPosition, + ValueNotifier mouseHoverPosition, bool enableMultiView, double multiViewSpacing, - TextStyle selectionTextStyle, - TextStyle rangeTextStyle, - Color selectionColor, - Color startRangeSelectionColor, - Color endRangeSelectionColor, - Color rangeSelectionColor, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, double selectionRadius, double textScaleFactor, double width, double height, - String monthFormat, + String? monthFormat, Locale locale, DateRangePickerView view, bool isHijri, @@ -2447,11 +2461,11 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { navigationDirection, localizations); - List _selectedDates; + List? _selectedDates; - List get selectedDates => _selectedDates; + List? get selectedDates => _selectedDates; - set selectedDates(List value) { + set selectedDates(List? value) { if (DateRangePickerHelper.isDateCollectionEquals(_selectedDates, value)) { return; } @@ -2481,7 +2495,6 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { double xPosition, double yPosition, TextSpan yearText) { - _todayHighlightPaint ??= Paint(); _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; final double maximumHighlight = @@ -2500,7 +2513,7 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { ? rect.height / 2 : 3; _todayHighlightPaint.color = - selectionColor ?? datePickerTheme.selectionColor; + selectionColor ?? datePickerTheme.selectionColor!; canvas.drawRRect( RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius)), @@ -2509,11 +2522,10 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { @override void drawCustomCellSelection(Canvas canvas, Rect rect, int index) { - _todayHighlightPaint ??= Paint(); _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; _todayHighlightPaint.color = - selectionColor ?? datePickerTheme.selectionColor; + selectionColor ?? datePickerTheme.selectionColor!; canvas.drawRect(rect, _todayHighlightPaint); } @@ -2523,9 +2535,9 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { if (_selectedDates == null) { return selectedIndex; } - for (int i = 0; i < _selectedDates.length; i++) { + for (int i = 0; i < _selectedDates!.length; i++) { final int index = DateRangePickerHelper.getDateCellIndex( - visibleDates, _selectedDates[i], _view, + visibleDates, _selectedDates![i], _view, viewStartIndex: viewStartIndex, viewEndIndex: viewEndIndex); if (index != -1) { selectedIndex.add(index); @@ -2543,24 +2555,24 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { dynamic minDate, dynamic maxDate, bool enablePastDates, - Color todayHighlightColor, + Color? todayHighlightColor, DateRangePickerSelectionShape selectionShape, bool isRtl, SfDateRangePickerThemeData datePickerTheme, - ValueNotifier mouseHoverPosition, + ValueNotifier mouseHoverPosition, bool enableMultiView, double multiViewSpacing, - TextStyle selectionTextStyle, - TextStyle rangeTextStyle, - Color selectionColor, - Color startRangeSelectionColor, - Color endRangeSelectionColor, - Color rangeSelectionColor, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, double selectionRadius, double textScaleFactor, double width, double height, - String monthFormat, + String? monthFormat, Locale locale, DateRangePickerView view, bool isHijri, @@ -2597,11 +2609,11 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { navigationDirection, localizations); - dynamic _selectedRange; + dynamic? _selectedRange; - dynamic get selectedRange => _selectedRange; + dynamic? get selectedRange => _selectedRange; - set selectedRange(dynamic value) { + set selectedRange(dynamic? value) { if (DateRangePickerHelper.isRangeEquals(_selectedRange, value)) { return; } @@ -2614,7 +2626,7 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { } } - List _selectedIndex; + List _selectedIndex = []; @override void paint(PaintingContext context, Offset offset) { @@ -2634,7 +2646,6 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { double xPosition, double yPosition, TextSpan yearText) { - _todayHighlightPaint ??= Paint(); _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; final double maximumHighlight = @@ -2665,10 +2676,10 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { final double rightRadius = isEndRange || isSelectedDate ? cornerRadius : 0; if (isSelectedDate) { _todayHighlightPaint.color = - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!; } else if (isStartRange) { _todayHighlightPaint.color = - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!; } else if (isBetweenRange) { yearText = TextSpan( text: yearText.text, @@ -2676,12 +2687,12 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { ); _todayHighlightPaint.color = - rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _textPainter.text = yearText; _textPainter.layout(minWidth: cellWidth, maxWidth: cellWidth); } else if (isEndRange) { _todayHighlightPaint.color = - endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor; + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!; } canvas.drawRRect( @@ -2695,7 +2706,6 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { @override void drawCustomCellSelection(Canvas canvas, Rect rect, int index) { - _todayHighlightPaint ??= Paint(); _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; final List selectionDetails = _getSelectedRangePosition(index); @@ -2705,16 +2715,16 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { final bool isBetweenRange = selectionDetails[3]; if (isSelectedDate) { _todayHighlightPaint.color = - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!; } else if (isStartRange) { _todayHighlightPaint.color = - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!; } else if (isBetweenRange) { _todayHighlightPaint.color = - rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; } else if (isEndRange) { _todayHighlightPaint.color = - endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor; + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!; } canvas.drawRect(rect, _todayHighlightPaint); } @@ -2782,24 +2792,24 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { dynamic minDate, dynamic maxDate, bool enablePastDates, - Color todayHighlightColor, + Color? todayHighlightColor, DateRangePickerSelectionShape selectionShape, bool isRtl, SfDateRangePickerThemeData datePickerTheme, - ValueNotifier mouseHoverPosition, + ValueNotifier mouseHoverPosition, bool enableMultiView, double multiViewSpacing, - TextStyle selectionTextStyle, - TextStyle rangeTextStyle, - Color selectionColor, - Color startRangeSelectionColor, - Color endRangeSelectionColor, - Color rangeSelectionColor, + TextStyle? selectionTextStyle, + TextStyle? rangeTextStyle, + Color? selectionColor, + Color? startRangeSelectionColor, + Color? endRangeSelectionColor, + Color? rangeSelectionColor, double selectionRadius, double textScaleFactor, double width, double height, - String monthFormat, + String? monthFormat, Locale locale, DateRangePickerView view, bool isHijri, @@ -2836,11 +2846,11 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { navigationDirection, localizations); - List _selectedRanges; + List? _selectedRanges; - List get selectedRanges => _selectedRanges; + List? get selectedRanges => _selectedRanges; - set selectedRanges(List value) { + set selectedRanges(List? value) { if (DateRangePickerHelper.isDateRangesEquals(_selectedRanges, value)) { return; } @@ -2853,7 +2863,7 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { } } - List> _rangesIndex; + List> _rangesIndex = >[]; @override void paint(PaintingContext context, Offset offset) { @@ -2873,7 +2883,6 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { double xPosition, double yPosition, TextSpan yearText) { - _todayHighlightPaint ??= Paint(); _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; final double maximumHighlight = @@ -2904,10 +2913,10 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { final double rightRadius = isEndRange || isSelectedDate ? cornerRadius : 0; if (isSelectedDate) { _todayHighlightPaint.color = - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!; } else if (isStartRange) { _todayHighlightPaint.color = - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!; } else if (isBetweenRange) { yearText = TextSpan( text: yearText.text, @@ -2915,12 +2924,12 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { ); _todayHighlightPaint.color = - rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; _textPainter.text = yearText; _textPainter.layout(minWidth: cellWidth, maxWidth: cellWidth); } else if (isEndRange) { _todayHighlightPaint.color = - endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor; + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!; } canvas.drawRRect( @@ -2934,7 +2943,6 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { @override void drawCustomCellSelection(Canvas canvas, Rect rect, int index) { - _todayHighlightPaint ??= Paint(); _todayHighlightPaint.isAntiAlias = true; _todayHighlightPaint.style = PaintingStyle.fill; final List selectionDetails = _getSelectedRangePosition(index); @@ -2944,16 +2952,16 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { final bool isBetweenRange = selectionDetails[3]; if (isSelectedDate) { _todayHighlightPaint.color = - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!; } else if (isStartRange) { _todayHighlightPaint.color = - startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor; + startRangeSelectionColor ?? datePickerTheme.startRangeSelectionColor!; } else if (isBetweenRange) { _todayHighlightPaint.color = - rangeSelectionColor ?? datePickerTheme.rangeSelectionColor; + rangeSelectionColor ?? datePickerTheme.rangeSelectionColor!; } else if (isEndRange) { _todayHighlightPaint.color = - endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor; + endRangeSelectionColor ?? datePickerTheme.endRangeSelectionColor!; } canvas.drawRect(rect, _todayHighlightPaint); } @@ -3014,8 +3022,8 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { return selectedIndex; } - for (int i = 0; i < _selectedRanges.length; i++) { - final dynamic range = _selectedRanges[i]; + for (int i = 0; i < _selectedRanges!.length; i++) { + final dynamic range = _selectedRanges![i]; final dynamic startDate = range.startDate; final dynamic endDate = range.endDate ?? range.startDate; final List index = _getRangeIndex( @@ -3085,7 +3093,7 @@ void _drawYearCells( DateRangePickerHelper.getPickerView(yearView.view); if (isNeedWidgetPaint) { - RenderBox child = yearView.firstChild; + RenderBox? child = yearView.firstChild; for (int j = 0; j < count; j++) { final int currentViewIndex = yearView.isRtl ? DateRangePickerHelper.getRtlIndex(count, j) : j; @@ -3115,7 +3123,7 @@ void _drawYearCells( } currentIndex += viewStartIndex; - if (xPosition >= viewEndPosition) { + if (xPosition + 1 >= viewEndPosition) { xPosition = viewStartPosition; yPosition += cellHeight; } @@ -3145,24 +3153,21 @@ void _drawYearCells( currentIndex); } - child.paint(context, Offset(xPosition, yPosition)); + child!.paint(context, Offset(xPosition, yPosition)); if (!isSelected && isEnableDate && - yearView.mouseHoverPosition != null && yearView.mouseHoverPosition.value != null) { - if (xPosition <= yearView.mouseHoverPosition.value.dx && - xPosition + cellWidth >= yearView.mouseHoverPosition.value.dx && - yPosition <= yearView.mouseHoverPosition.value.dy && - yPosition + cellHeight >= yearView.mouseHoverPosition.value.dy) { - yearView._todayHighlightPaint = - yearView._todayHighlightPaint ?? Paint(); + if (xPosition <= yearView.mouseHoverPosition.value!.dx && + xPosition + cellWidth >= yearView.mouseHoverPosition.value!.dx && + yPosition <= yearView.mouseHoverPosition.value!.dy && + yPosition + cellHeight >= yearView.mouseHoverPosition.value!.dy) { yearView._todayHighlightPaint.style = PaintingStyle.fill; yearView._todayHighlightPaint.strokeWidth = 2; yearView._todayHighlightPaint.color = yearView.selectionColor != null - ? yearView.selectionColor.withOpacity(0.4) - : yearView.datePickerTheme.selectionColor.withOpacity(0.4); + ? yearView.selectionColor!.withOpacity(0.4) + : yearView.datePickerTheme.selectionColor!.withOpacity(0.4); final Rect rect = Rect.fromLTRB(xPosition, yPosition, xPosition + cellWidth, yPosition + cellHeight); @@ -3179,12 +3184,7 @@ void _drawYearCells( } final dynamic today = DateRangePickerHelper.getToday(yearView.isHijri); - yearView._textPainter ??= TextPainter( - textAlign: TextAlign.start, - textDirection: TextDirection.ltr, - maxLines: 2, - textScaleFactor: yearView.textScaleFactor, - textWidthBasis: TextWidthBasis.longestLine); + yearView._textPainter.textScaleFactor = yearView.textScaleFactor; const double decorationPadding = 1; const double selectionPadding = 3; @@ -3220,7 +3220,7 @@ void _drawYearCells( } currentIndex += viewStartIndex; - if (xPosition >= viewEndPosition) { + if (xPosition + 1 >= viewEndPosition) { xPosition = viewStartPosition; yPosition += cellHeight; } @@ -3247,7 +3247,7 @@ void _drawYearCells( date, j, yearView.visibleDates, yearView.enableMultiView, view); final TextStyle style = yearView._updateCellTextStyle( j, isCurrentDate, isSelected, isEnableDate, isActiveDate); - final Decoration yearDecoration = yearView._updateCellDecoration( + final Decoration? yearDecoration = yearView._updateCellDecoration( j, isCurrentDate, isEnableDate, isActiveDate); final TextSpan yearText = TextSpan( @@ -3258,7 +3258,8 @@ void _drawYearCells( yearView._textPainter.text = yearText; yearView._textPainter.layout(minWidth: cellWidth, maxWidth: cellWidth); - final double highlightPadding = yearView.selectionRadius ?? 10; + final double highlightPadding = + yearView.selectionRadius == -1 ? 10 : yearView.selectionRadius; final double textHalfHeight = yearView._textPainter.height / 2; if (isSelected && isEnableDate) { yearView.drawSelection( @@ -3297,7 +3298,6 @@ void _drawYearCells( if (!isSelected && isEnableDate && - yearView.mouseHoverPosition != null && yearView.mouseHoverPosition.value != null) { yearView._addMouseHovering( canvas, diff --git a/packages/syncfusion_flutter_datepicker/pubspec.yaml b/packages/syncfusion_flutter_datepicker/pubspec.yaml index 08ec9c27a..8fd4d72bc 100644 --- a/packages/syncfusion_flutter_datepicker/pubspec.yaml +++ b/packages/syncfusion_flutter_datepicker/pubspec.yaml @@ -1,14 +1,14 @@ name: syncfusion_flutter_datepicker -description: The Syncfusion Flutter Date Range Picker widget allows users to easily select dates or a range of dates. It has built-in views that allow quick navigation to the desired date. -version: 18.3.35-beta -homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_datepicker +description: The Flutter Date Range Picker widget allows users to easily select dates or a range of dates. It has four built-in views that allow quick navigation to the desired date. +version: 19.1.54-beta +homepage: https://github.com/syncfusion/flutter-examples environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter - intl: ">=0.15.0 <0.17.0" + intl: ">=0.15.0 <0.20.0" syncfusion_flutter_core: path: ../syncfusion_flutter_core diff --git a/packages/syncfusion_flutter_gauges/README.md b/packages/syncfusion_flutter_gauges/README.md index 1728f83d3..03fa51841 100644 --- a/packages/syncfusion_flutter_gauges/README.md +++ b/packages/syncfusion_flutter_gauges/README.md @@ -1,28 +1,55 @@ -![syncfusion_flutter_gauge_banner](https://cdn.syncfusion.com/content/images/FTControl/Charts/Flutter-Gauges.png) +![syncfusion_flutter_gauge_banner](https://cdn.syncfusion.com/content/images/FTControl/Charts/Flutter-radial-linear-gauge.png) -# Syncfusion Flutter Gauges +# Flutter Gauges library -Syncfusion Flutter gauges library includes data visualization widgets such as radial gauge, which is written in dart, to create modern, interactive, and animated gauges that are used to craft high-quality mobile app user interfaces using Flutter. +The Flutter Gauges library includes the data visualization widgets Linear Gauge and Radial Gauge (a.k.a. circular gauge) to create modern, interactive, animated gauges. ## Overview +The Linear Gauge is used to display data on a linear scale, while the Radial Gauge is used to display data on a circular scale. Both gauges have a rich set of features, such as axes, ranges, pointers, smooth interactions, and animations that are fully customizable and extendable. -The radial gauge is used to display numerical values on a circular scale. It has a rich set of features such as axes, ranges, pointers, and annotations that are fully customizable and extendable. Use it to create speedometers, temperature monitors, dashboards, meter gauges, multi-axis clocks, watches, modern activity gauges, compasses and more. - -**Disclaimer:** This is a commercial package. To use this package, you need to have either Syncfusion Commercial License or Syncfusion Community license. For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. - -**Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. +**Disclaimer:** This is a commercial package. To use this package, you need to have either Syncfusion Commercial License or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. ## Table of contents + +- [Linear gauge features](#linear-gauge-features) - [Radial gauge features](#radial-gauge-features) - [Get the demo application](#get-the-demo-application) - [Other useful links](#other-useful-links) - [Installation](#installation) - [Getting started](#getting-started) + - [Add linear gauge to the widget tree](#add-linear-gauge-to-the-widget-tree) + - [Add linear gauge elements](#add-linear-gauge-elements) - [Add radial gauge to the widget tree](#add-radial-gauge-to-the-widget-tree) - [Add radial gauge elements](#add-radial-gauge-elements) - [Support and Feedback](#support-and-feedback) - [About Syncfusion](#about-syncfusion) +## Linear gauge features + +* **Orientation** - The Linear Gauge can be set to vertical or horizontal orientation. + +* **Axis** - The Linear Gauge axis is a scale where a set of values can be plotted. An axis can be customized by changing the thickness and edge styles. You can also inverse the axis. +![linear gauge axis](https://cdn.syncfusion.com/content/images/Flutter/pub_images/linear_gauge_images/axis.png) + +* **Labels and ticks** - The Linear Gauge axis elements, such as labels, major ticks, and minor ticks, can be customized to different styles. +![linear gauge axis](https://cdn.syncfusion.com/content/images/Flutter/pub_images/linear_gauge_images/labelsandticks.png) + +* **Ranges** - A range is a visual element that helps you quickly visualize where a range falls on the axis track. Multiple ranges with different styles can be added to a linear gauge. + +![linear gauge range](https://cdn.syncfusion.com/content/images/Flutter/pub_images/linear_gauge_images/ranges.png) + +* **Pointers** - A pointer is used to indicate a specific value on an axis. The widget has three types of pointers: shape marker pointer, widget marker pointer, and bar pointer. All the pointers can be customized as needed. You can add multiple pointers in a linear gauge. + +![linear gauge pointer](https://cdn.syncfusion.com/content/images/Flutter/pub_images/linear_gauge_images/pointers.png) + +* **Pointer interaction** - The shape and widget marker pointers in a Linear Gauge can be moved from one value to another with swipe or drag gestures. + +![linear gauge pointer interaction](https://cdn.syncfusion.com/content/images/Flutter/pub_images/linear_gauge_images/interaction.gif) + +* **Animation** - All the gauge elements can be animated in a visually appealing way. Animate the gauge elements when they are loading, or when their values change. + +![linear gauge animation](https://cdn.syncfusion.com/content/images/Flutter/pub_images/linear_gauge_images/animation.gif) + ## Radial gauge features * **Axes** - The radial gauge axis is a circular arc in which a set of values are displayed along a linear or custom scale based on the design requirements. Axis elements, such as labels, ticks, and axis line, can be easily customized with built-in properties. @@ -31,13 +58,13 @@ The radial gauge is used to display numerical values on a circular scale. It has * **Ranges** - Gauge range is a visual element that helps to quickly visualize where a value falls on the axis. The text can be easily annotated in range to improve the readability. ![radial gauge range](https://cdn.syncfusion.com/content/images/FTControl/Flutter/Range.png) -* **Pointers** - Pointer is used to indicate values on an axis. It has three types of pointers: needle pointer, marker pointer, and range pointer. All the pointers can be customized as needed. +* **Pointers** - Pointer is used to indicate values on an axis. It has four types of pointers: needle pointer, marker pointer, range pointer, and widget pointer. All the pointers can be customized as needed. ![radial gauge pointer](https://cdn.syncfusion.com/content/images/FTControl/Flutter/Pointer.png) * **Animation** - Animates the pointer in a visually appealing way when the pointer moves from one value to another. Gauge supports various pointer animations. It is also possible to apply initial load animation for gauge. ![radial gauge animation](https://cdn.syncfusion.com/content/images/FTControl/Flutter/Animation.gif) -* **Pointer interaction** - Radial gauge provides an option to drag a pointer from one value to another. It is used to change the value at run time. +* **Pointer interaction** - Radial gauge provides an option to drag a pointer from one value to another and also displays overlay while dragging. It is used to change the value at run time. ![radial gauge pointer interaction](https://cdn.syncfusion.com/content/images/FTControl/Flutter/Interaction.gif) * **Annotations** - Add multiple widgets such as text and image as an annotation at a specific point of interest in the radial gauge. @@ -75,6 +102,60 @@ Import the following package. ```dart import 'package:syncfusion_flutter_gauges/gauges.dart'; ``` + +### Add linear gauge to the widget tree + +Add the linear gauge widget as a child of any widget. Here, the gauge widget is added as a child of container widget. + +```dart + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: Container( + child: SfLinearGauge() + ))), + ); + } +``` +### Add linear gauge elements + +Add the gauge elements such as axis, range, and pointers to indicate the current value. + +```dart +class _DemoAppState extends State { + double _pointerValue = 45; + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Center( + child: Container( + child: SfLinearGauge( + ranges: [ + LinearGaugeRange( + startValue: 0, + endValue: 50, + ), + ], + markerPointers: [ + LinearShapePointer( + value: 50, + ), + ], + barPointers: [LinearBarPointer(value: 80)], + ), + ))), + ); + } +} +``` + +The following screenshot illustrates the result of the above code sample. + +![linear gauge widget](https://cdn.syncfusion.com/content/images/Flutter/pub_images/linear_gauge_images/basic_elements.png) + ### Add radial gauge to the widget tree Add the radial gauge widget as a child of any widget. Here, the gauge widget is added as a child of container widget. diff --git a/packages/syncfusion_flutter_gauges/analysis_options.yaml b/packages/syncfusion_flutter_gauges/analysis_options.yaml index 2a22b53a9..1c37f8e35 100644 --- a/packages/syncfusion_flutter_gauges/analysis_options.yaml +++ b/packages/syncfusion_flutter_gauges/analysis_options.yaml @@ -4,3 +4,4 @@ analyzer: errors: include_file_not_found: ignore lines_longer_than_80_chars: ignore + avoid_as: false \ No newline at end of file diff --git a/packages/syncfusion_flutter_gauges/example/analysis_options.yaml b/packages/syncfusion_flutter_gauges/example/analysis_options.yaml new file mode 100644 index 000000000..b08414c80 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/example/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + errors: + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_flutter_gauges/example/lib/main.dart b/packages/syncfusion_flutter_gauges/example/lib/main.dart index a7d8c62eb..08ea5b7e0 100644 --- a/packages/syncfusion_flutter_gauges/example/lib/main.dart +++ b/packages/syncfusion_flutter_gauges/example/lib/main.dart @@ -20,54 +20,82 @@ class GaugeApp extends StatelessWidget { /// Represents MyHomePage class class MyHomePage extends StatefulWidget { /// Creates the instance of MyHomePage - // ignore: prefer_const_constructors_in_immutables - MyHomePage({Key key}) : super(key: key); + MyHomePage({Key? key}) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State { + Widget _getGauge({bool isRadialGauge = true}) { + if (isRadialGauge) { + return _getRadialGauge(); + } else { + return _getLinearGauge(); + } + } + + Widget _getRadialGauge() { + return SfRadialGauge( + title: GaugeTitle( + text: 'Speedometer', + textStyle: + const TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)), + axes: [ + RadialAxis(minimum: 0, maximum: 150, ranges: [ + GaugeRange( + startValue: 0, + endValue: 50, + color: Colors.green, + startWidth: 10, + endWidth: 10), + GaugeRange( + startValue: 50, + endValue: 100, + color: Colors.orange, + startWidth: 10, + endWidth: 10), + GaugeRange( + startValue: 100, + endValue: 150, + color: Colors.red, + startWidth: 10, + endWidth: 10) + ], pointers: [ + NeedlePointer(value: 90) + ], annotations: [ + GaugeAnnotation( + widget: Container( + child: const Text('90.0', + style: TextStyle( + fontSize: 25, fontWeight: FontWeight.bold))), + angle: 90, + positionFactor: 0.5) + ]) + ]); + } + + Widget _getLinearGauge() { + return Container( + child: SfLinearGauge( + minimum: 0.0, + maximum: 100.0, + orientation: LinearGaugeOrientation.horizontal, + majorTickStyle: LinearTickStyle(length: 20), + axisLabelStyle: TextStyle(fontSize: 12.0, color: Colors.black), + axisTrackStyle: LinearAxisTrackStyle( + color: Colors.cyan, + edgeStyle: LinearEdgeStyle.bothFlat, + thickness: 15.0, + borderColor: Colors.grey)), + margin: EdgeInsets.all(10), + ); + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Syncfusion Flutter Radial Gauge')), - body: SfRadialGauge( - title: GaugeTitle( - text: 'Speedometer', - textStyle: const TextStyle( - fontSize: 20.0, fontWeight: FontWeight.bold)), - axes: [ - RadialAxis(minimum: 0, maximum: 150, ranges: [ - GaugeRange( - startValue: 0, - endValue: 50, - color: Colors.green, - startWidth: 10, - endWidth: 10), - GaugeRange( - startValue: 50, - endValue: 100, - color: Colors.orange, - startWidth: 10, - endWidth: 10), - GaugeRange( - startValue: 100, - endValue: 150, - color: Colors.red, - startWidth: 10, - endWidth: 10) - ], pointers: [ - NeedlePointer(value: 90) - ], annotations: [ - GaugeAnnotation( - widget: Container( - child: const Text('90.0', - style: TextStyle( - fontSize: 25, fontWeight: FontWeight.bold))), - angle: 90, - positionFactor: 0.5) - ]) - ])); + appBar: AppBar(title: const Text('Syncfusion Flutter Gauge')), + body: _getGauge()); } } diff --git a/packages/syncfusion_flutter_gauges/example/pubspec.yaml b/packages/syncfusion_flutter_gauges/example/pubspec.yaml index 7a5e7e178..81841b8c8 100644 --- a/packages/syncfusion_flutter_gauges/example/pubspec.yaml +++ b/packages/syncfusion_flutter_gauges/example/pubspec.yaml @@ -14,7 +14,7 @@ description: A new Flutter project. version: 1.0.0+1 environment: - sdk: ">=2.1.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: diff --git a/packages/syncfusion_flutter_gauges/lib/gauges.dart b/packages/syncfusion_flutter_gauges/lib/gauges.dart index ecf12f7cd..c18908a8d 100644 --- a/packages/syncfusion_flutter_gauges/lib/gauges.dart +++ b/packages/syncfusion_flutter_gauges/lib/gauges.dart @@ -12,36 +12,31 @@ /// * [Knowledge base](https://www.syncfusion.com/kb/flutter/sfradialgauge) library gauges; -import 'dart:async'; -import 'dart:math' as math; -import 'dart:typed_data'; -import 'dart:ui'; -import 'dart:ui' as dart_ui; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart' show rootBundle; - -import 'package:intl/intl.dart' show NumberFormat; -import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:flutter/foundation.dart'; +export './src/linear_gauge/axis/linear_axis_label.dart'; +export './src/linear_gauge/axis/linear_axis_track_style.dart'; +export './src/linear_gauge/axis/linear_tick_style.dart'; +export './src/linear_gauge/gauge/linear_gauge.dart'; +export './src/linear_gauge/pointers/linear_bar_pointer.dart'; +export './src/linear_gauge/pointers/linear_shape_pointer.dart'; +export './src/linear_gauge/pointers/linear_widget_pointer.dart'; +export './src/linear_gauge/range/linear_gauge_range.dart'; +export './src/linear_gauge/utils/enum.dart'; +export './src/linear_gauge/utils/linear_gauge_typedef.dart'; // export circular gauge library -part './src/radial_gauge/gauge/radial_gauge.dart'; -part './src/radial_gauge/axis/gauge_axis.dart'; -part './src/radial_gauge/utils/enum.dart'; -part './src/radial_gauge/pointers/gauge_pointer.dart'; -part './src/radial_gauge/axis/radial_axis.dart'; -part './src/radial_gauge/range/gauge_range.dart'; -part './src/radial_gauge/common/common.dart'; -part './src/radial_gauge/utils/helper.dart'; -part './src/radial_gauge/annotation/gauge_annotation.dart'; -part './src/radial_gauge/common/gauge_annotation_renderer.dart'; -part './src/radial_gauge/pointers/range_pointer.dart'; -part './src/radial_gauge/pointers/marker_pointer.dart'; -part './src/radial_gauge/pointers/needle_pointer.dart'; -part './src/radial_gauge/gauge_painter/marker_pointer_painter.dart'; -part './src/radial_gauge/gauge_painter/needle_pointer_painter.dart'; -part './src/radial_gauge/common/radial_gauge_renderer.dart'; -part './src/radial_gauge/gauge_painter/range_pointer_painter.dart'; -part './src/radial_gauge/gauge_painter/range_painter.dart'; -part './src/radial_gauge/gauge_painter/radial_axis_painter.dart'; +export './src/radial_gauge/annotation/gauge_annotation.dart'; +export './src/radial_gauge/axis/gauge_axis.dart'; +export './src/radial_gauge/axis/radial_axis.dart'; +export './src/radial_gauge/common/axis_label.dart'; +export './src/radial_gauge/common/common.dart'; +export './src/radial_gauge/gauge/radial_gauge.dart'; +export './src/radial_gauge/pointers/gauge_pointer.dart'; +export './src/radial_gauge/pointers/marker_pointer.dart'; +export './src/radial_gauge/pointers/needle_pointer.dart'; +export './src/radial_gauge/pointers/range_pointer.dart'; +export './src/radial_gauge/pointers/widget_pointer.dart'; +export './src/radial_gauge/range/gauge_range.dart'; +export './src/radial_gauge/renderers/marker_pointer_renderer.dart'; +export './src/radial_gauge/renderers/needle_pointer_renderer.dart'; +export './src/radial_gauge/renderers/radial_axis_renderer.dart'; +export './src/radial_gauge/utils/enum.dart'; diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_label.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_label.dart new file mode 100644 index 000000000..5fd6bb4d3 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_label.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; + +///This class represents the linear axis label. +class LinearAxisLabel { + ///Creates a linear axis label. + const LinearAxisLabel({required this.text, required this.value}); + + /// An axis value as a text + /// + /// Defaults to [String.Empty]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// onGenerateLabels: () { + /// return [ + /// LinearAxisLabel(text: 'Start', value: 0), + /// LinearAxisLabel(text: 'End', value: 100),]; + /// }) + /// ``` + /// + final String text; + + /// The position of an axis value to get a string. + /// + /// Defaults to 0. + /// + /// ```dart + /// + /// SfLinearGauge( + /// onGenerateLabels: () { + /// return [ + /// LinearAxisLabel(text: 'R', value: 0), + /// LinearAxisLabel(text: 'M', value: 10), + /// LinearAxisLabel(text: 'L', value: 20),]; + /// }) + /// ``` + /// + final double value; + + @override + bool operator ==(Object other) { + late LinearAxisLabel otherStyle; + + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + if (other is LinearAxisLabel) otherStyle = other; + + return otherStyle.text == text && otherStyle.value == value; + } + + @override + int get hashCode { + return hashValues(text, value); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_renderer.dart new file mode 100644 index 000000000..f9d7fb5c8 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_renderer.dart @@ -0,0 +1,1614 @@ +import 'dart:math' as math; +import 'dart:ui' as dart_ui; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show NumberFormat; + +import '../../linear_gauge/axis/linear_axis_label.dart'; +import '../../linear_gauge/axis/linear_axis_track_style.dart'; +import '../../linear_gauge/gauge/linear_gauge.dart'; +import '../../linear_gauge/pointers/linear_shape_renderer.dart'; +import '../../linear_gauge/pointers/linear_widget_renderer.dart'; +import '../../linear_gauge/range/linear_gauge_range.dart'; +import '../../linear_gauge/utils/enum.dart'; +import '../../linear_gauge/utils/linear_gauge_helper.dart'; +import '../../linear_gauge/utils/linear_gauge_typedef.dart'; + +/// Represents the renderer of linear axis. +class LinearAxisRenderObjectWidget extends LeafRenderObjectWidget { + /// Creates the axis render with required properties. + LinearAxisRenderObjectWidget( + {Key? key, required this.linearGauge, this.fadeAnimation}) + : super(key: key); + + /// Holds the linear axis. + final SfLinearGauge linearGauge; + + ///Axis scale animation. + final Animation? fadeAnimation; + + @override + RenderObject createRenderObject(BuildContext context) { + final LinearAxisTrackStyle style = linearGauge.axisTrackStyle; + final Color? majorTickColor = linearGauge.majorTickStyle.color; + final Color? minorTickColor = linearGauge.minorTickStyle.color; + final ThemeData theme = Theme.of(context); + final bool isDarkTheme = theme.brightness == Brightness.dark; + return RenderLinearAxis( + orientation: linearGauge.orientation, + showAxisTrack: linearGauge.showAxisTrack, + thickness: style.thickness, + color: style.color ?? (isDarkTheme ? Colors.white24 : Colors.black12), + borderWidth: style.borderWidth, + borderColor: style.borderColor ?? + (isDarkTheme ? Colors.white30 : Colors.black26), + gradient: style.gradient, + edgeStyle: style.edgeStyle, + minimum: linearGauge.minimum, + maximum: linearGauge.maximum, + interval: linearGauge.interval, + minorTicksPerInterval: linearGauge.minorTicksPerInterval, + numberFormat: linearGauge.numberFormat, + labelOffset: linearGauge.labelOffset, + labelPosition: linearGauge.labelPosition, + tickPosition: linearGauge.tickPosition, + tickOffset: linearGauge.tickOffset, + isMirrored: linearGauge.isMirrored, + textStyle: linearGauge.axisLabelStyle ?? + TextStyle( + fontSize: 12.0, + fontStyle: FontStyle.normal, + color: isDarkTheme ? Colors.white : Colors.black87, + fontWeight: FontWeight.normal), + showLabels: linearGauge.showLabels, + showTicks: linearGauge.showTicks, + isAxisInversed: linearGauge.isAxisInversed, + useRangeColorForAxis: linearGauge.useRangeColorForAxis, + majorTickLength: linearGauge.majorTickStyle.length, + majorTickThickness: linearGauge.majorTickStyle.thickness, + majorTickColor: + majorTickColor ?? (isDarkTheme ? Colors.white30 : Colors.black26), + minorTickLength: linearGauge.minorTickStyle.length, + minorTickThickness: linearGauge.minorTickStyle.thickness, + minorTickColor: + minorTickColor ?? (isDarkTheme ? Colors.white30 : Colors.black26), + maximumLabels: linearGauge.maximumLabels, + axisTrackExtent: linearGauge.axisTrackExtent, + fadeAnimation: fadeAnimation, + onGenerateLabels: linearGauge.onGenerateLabels, + ranges: linearGauge.ranges, + valueToFactorCallback: linearGauge.valueToFactorCallback, + factorToValueCallback: linearGauge.factorToValueCallback, + labelFormatterCallback: linearGauge.labelFormatterCallback); + } + + @override + void updateRenderObject(BuildContext context, RenderLinearAxis renderObject) { + final LinearAxisTrackStyle style = linearGauge.axisTrackStyle; + final Color? majorTickColor = linearGauge.majorTickStyle.color; + final Color? minorTickColor = linearGauge.minorTickStyle.color; + final ThemeData theme = Theme.of(context); + final bool isDarkTheme = theme.brightness == Brightness.dark; + renderObject + ..orientation = linearGauge.orientation + ..showAxisTrack = linearGauge.showAxisTrack + ..thickness = style.thickness + ..color = style.color ?? (isDarkTheme ? Colors.white24 : Colors.black12) + ..borderColor = + style.borderColor ?? (isDarkTheme ? Colors.white30 : Colors.black26) + ..borderWidth = style.borderWidth + ..gradient = style.gradient + ..edgeStyle = style.edgeStyle + ..minimum = linearGauge.minimum + ..maximum = linearGauge.maximum + ..interval = linearGauge.interval + ..minorTicksPerInterval = linearGauge.minorTicksPerInterval + ..numberFormat = linearGauge.numberFormat + ..labelOffset = linearGauge.labelOffset + ..labelPosition = linearGauge.labelPosition + ..tickPosition = linearGauge.tickPosition + ..majorTickLength = linearGauge.majorTickStyle.length + ..majorTickThickness = linearGauge.majorTickStyle.thickness + ..majorTickColor = + majorTickColor ?? (isDarkTheme ? Colors.white30 : Colors.black26) + ..minorTickLength = linearGauge.minorTickStyle.length + ..minorTickThickness = linearGauge.minorTickStyle.thickness + ..minorTickColor = + minorTickColor ?? (isDarkTheme ? Colors.white30 : Colors.black26) + ..tickOffset = linearGauge.tickOffset + ..isMirrored = linearGauge.isMirrored + ..textStyle = linearGauge.axisLabelStyle ?? + TextStyle( + fontSize: 12.0, + fontStyle: FontStyle.normal, + color: isDarkTheme ? Colors.white : Colors.black87, + fontWeight: FontWeight.normal) + ..showLabels = linearGauge.showLabels + ..showTicks = linearGauge.showTicks + ..useRangeColorForAxis = linearGauge.useRangeColorForAxis + ..majorTickLength = linearGauge.majorTickStyle.length + ..isAxisInversed = linearGauge.isAxisInversed + ..maximumLabels = linearGauge.maximumLabels + ..axisTrackExtent = linearGauge.axisTrackExtent + ..onGenerateLabels = linearGauge.onGenerateLabels + ..valueToFactorCallback = linearGauge.valueToFactorCallback + ..factorToValueCallback = linearGauge.factorToValueCallback + ..labelFormatterCallback = linearGauge.labelFormatterCallback + ..fadeAnimation = fadeAnimation + ..ranges = linearGauge.ranges; + + super.updateRenderObject(context, renderObject); + } +} + +/// Represents the render object of linear axis. +class RenderLinearAxis extends RenderBox { + /// Creates a instance for [RenderLinearAxis]. + RenderLinearAxis( + {required LinearGaugeOrientation orientation, + required bool showAxisTrack, + required double thickness, + required Color color, + required Color borderColor, + required double borderWidth, + required LinearEdgeStyle edgeStyle, + required double minimum, + required double maximum, + required int minorTicksPerInterval, + required NumberFormat? numberFormat, + required double labelOffset, + required LinearLabelPosition labelPosition, + required LinearElementPosition tickPosition, + required double tickOffset, + required TextStyle textStyle, + required bool showLabels, + required bool showTicks, + required bool isAxisInversed, + required bool isMirrored, + required bool useRangeColorForAxis, + required int maximumLabels, + required double majorTickLength, + required double majorTickThickness, + required Color majorTickColor, + required double minorTickLength, + required double minorTickThickness, + required Color minorTickColor, + required double axisTrackExtent, + Gradient? gradient, + double? interval, + Animation? fadeAnimation, + GenerateLabelsCallback? onGenerateLabels, + LabelFormatterCallback? labelFormatterCallback, + ValueToFactorCallback? valueToFactorCallback, + FactorToValueCallback? factorToValueCallback, + List? ranges}) + : _orientation = orientation, + _showAxisTrack = showAxisTrack, + _thickness = thickness, + _color = color, + _borderColor = borderColor, + _borderWidth = borderWidth, + _gradient = gradient, + _edgeStyle = edgeStyle, + _minimum = minimum, + _maximum = maximum, + _interval = interval, + _minorTicksPerInterval = minorTicksPerInterval, + _numberFormat = numberFormat, + _labelOffset = labelOffset, + _labelPosition = labelPosition, + _tickPosition = tickPosition, + _tickOffset = tickOffset, + _textStyle = textStyle, + _showLabels = showLabels, + _isMirrored = isMirrored, + _majorTickLength = majorTickLength, + _majorTickThickness = majorTickThickness, + _majorTickColor = majorTickColor, + _minorTickLength = minorTickLength, + _minorTickThickness = minorTickThickness, + _minorTickColor = minorTickColor, + _showTicks = showTicks, + _isAxisInversed = isAxisInversed, + _maximumLabels = maximumLabels, + _onGenerateLabels = onGenerateLabels, + _axisTrackExtent = axisTrackExtent, + _fadeAnimation = fadeAnimation, + _labelFormatterCallback = labelFormatterCallback, + _ranges = ranges, + _useRangeColorForAxis = useRangeColorForAxis, + _factorToValueCallback = factorToValueCallback, + _valueToFactorCallback = valueToFactorCallback { + _axisPaint = Paint()..color = Colors.black12; + _textPainter = TextPainter(textDirection: TextDirection.ltr); + _visibleLabels = []; + _isHorizontalOrientation = orientation == LinearGaugeOrientation.horizontal; + _labelMap = Map(); + _path = Path(); + } + + late Paint _axisPaint; + late TextPainter _textPainter; + late double _tickTop, _labelTop; + late List _visibleLabels; + late Map _labelMap; + late Size _axisActualSize, _minLabelSize, _maxLabelSize; + late bool _isHorizontalOrientation; + late Path _path; + + Rect _axisLineRect = Rect.zero; + + double _maxLabelHeight = 0; + double _maxLabelWidth = 0; + + /// Gets the axis Top assigned to [RenderLinearAxis]. + late double axisOffset; + + /// Gets or sets the pointer start padding assigned to [RenderLinearAxis]. + double? pointerStartPadding; + + /// Gets or sets the pointer start padding assigned to [RenderLinearAxis]. + double? pointerEndPadding; + + /// Gets the offset assigned to [RenderLinearAxis]. + double get labelOffset => _labelOffset; + double _labelOffset; + + /// Sets the offset for [RenderLinearAxis]. + set labelOffset(double value) { + if (value == _labelOffset) { + return; + } + _labelOffset = value; + markNeedsLayout(); + } + + /// Gets the useRangeColorForAxis assigned to [RenderLinearAxis]. + bool get useRangeColorForAxis => _useRangeColorForAxis; + bool _useRangeColorForAxis; + + /// Sets the useRangeForAxis for [RenderLinearAxis]. + set useRangeColorForAxis(bool value) { + if (value == _useRangeColorForAxis) { + return; + } + _useRangeColorForAxis = value; + markNeedsPaint(); + } + + /// Gets the generateLabels callback assigned to [RenderLinearAxis]. + GenerateLabelsCallback? get onGenerateLabels => _onGenerateLabels; + GenerateLabelsCallback? _onGenerateLabels; + + /// Sets the generateLabels callback for [RenderLinearAxis]. + set onGenerateLabels(GenerateLabelsCallback? value) { + if (value == _onGenerateLabels) { + return; + } + _onGenerateLabels = value; + markNeedsLayout(); + } + + /// Gets the valueToFactorCallback assigned to [RenderLinearAxis]. + ValueToFactorCallback? get valueToFactorCallback => _valueToFactorCallback; + ValueToFactorCallback? _valueToFactorCallback; + + /// Sets the valueToFactorCallback for [RenderLinearAxis]. + set valueToFactorCallback(ValueToFactorCallback? value) { + if (value == _valueToFactorCallback) { + return; + } + _valueToFactorCallback = value; + markNeedsLayout(); + } + + /// Gets the valueToFactorCallback assigned to [RenderLinearAxis]. + FactorToValueCallback? get factorToValueCallback => _factorToValueCallback; + FactorToValueCallback? _factorToValueCallback; + + /// Sets the valueToFactorCallback for [RenderLinearAxis]. + set factorToValueCallback(FactorToValueCallback? value) { + if (value == _factorToValueCallback) { + return; + } + + _factorToValueCallback = value; + markNeedsLayout(); + } + + /// Gets the labelFormatterCallback assigned to [RenderLinearAxis]. + LabelFormatterCallback? get labelFormatterCallback => _labelFormatterCallback; + LabelFormatterCallback? _labelFormatterCallback; + + /// Sets the labelFormatterCallback for [RenderLinearAxis]. + set labelFormatterCallback(LabelFormatterCallback? value) { + if (value == _labelFormatterCallback) { + return; + } + _labelFormatterCallback = value; + markNeedsLayout(); + } + + /// Gets the isAxisInversed assigned to [RenderLinearAxis]. + bool get isAxisInversed => _isAxisInversed; + bool _isAxisInversed; + + /// Sets the isInverse for [RenderLinearAxis]. + set isAxisInversed(bool value) { + if (value == _isAxisInversed) { + return; + } + + _isAxisInversed = value; + markNeedsPaint(); + } + + /// Gets the maximum labels assigned to [RenderLinearAxis]. + int get maximumLabels => _maximumLabels; + int _maximumLabels; + + /// Sets the maximum labels for [RenderLinearAxis]. + set maximumLabels(int value) { + if (value == _maximumLabels) { + return; + } + _maximumLabels = value; + markNeedsPaint(); + } + + /// Gets the orientation assigned to [RenderLinearAxis]. + /// + /// Default value is [GaugeOrientation.horizontal]. + /// + LinearGaugeOrientation get orientation => _orientation; + LinearGaugeOrientation _orientation; + + /// Sets the orientation for [RenderLinearAxis]. + /// + /// Default value is [GaugeOrientation.horizontal]. + set orientation(LinearGaugeOrientation value) { + if (value == _orientation) { + return; + } + _orientation = value; + _isHorizontalOrientation = orientation == LinearGaugeOrientation.horizontal; + markNeedsLayout(); + } + + /// Gets the maximum assigned to [RenderLinearAxis]. + /// + /// Default value is 100. + /// + double get maximum => _maximum; + double _maximum; + + /// Sets the maximum for [RenderLinearAxis]. + /// + /// Default value is 100. + set maximum(double value) { + if (value == _maximum) { + return; + } + _maximum = value; + markNeedsLayout(); + } + + /// Gets the minimum assigned to [RenderLinearAxis]. + /// + /// Default value is 0. + /// + double get minimum => _minimum; + double _minimum; + + /// Sets the minimum for [RenderLinearAxis]. + /// + /// Default value is 0. + set minimum(double value) { + if (value == _minimum) { + return; + } + _minimum = value; + markNeedsLayout(); + } + + /// Gets the showAxisLine assigned to [RenderLinearAxis]. + bool get showAxisTrack => _showAxisTrack; + bool _showAxisTrack; + + /// Sets the showAxisLine for [RenderLinearAxis].. + set showAxisTrack(bool value) { + if (value == _showAxisTrack) { + return; + } + _showAxisTrack = value; + markNeedsLayout(); + } + + /// Gets the thickness assigned to [RenderLinearAxis]. + double get thickness => _thickness; + double _thickness; + + /// Sets the axisLineThickness for [RenderLinearAxis].. + set thickness(double value) { + if (value == _thickness) { + return; + } + _thickness = value; + markNeedsLayout(); + } + + /// Gets the color assigned to [RenderLinearAxis]. + Color get color => _color; + Color _color; + + /// Sets the color for [RenderLinearAxis].. + set color(Color value) { + if (value == _color) { + return; + } + _color = value; + markNeedsPaint(); + } + + /// Gets the borderColor assigned to [RenderLinearAxis]. + Color get borderColor => _borderColor; + Color _borderColor; + + /// Sets the borderColor for [RenderLinearAxis].. + set borderColor(Color value) { + if (value == _borderColor) { + return; + } + _borderColor = value; + markNeedsPaint(); + } + + /// Gets the borderWidth assigned to [RenderLinearAxis]. + double get borderWidth => _borderWidth; + double _borderWidth; + + /// Sets the borderWidth for [RenderLinearAxis].. + set borderWidth(double value) { + if (value == _borderWidth) { + return; + } + _borderWidth = value; + markNeedsPaint(); + } + + /// Gets the gradient assigned to [RenderLinearAxis]. + Gradient? get gradient => _gradient; + Gradient? _gradient; + + /// Sets the gradient for [RenderLinearAxis]. + set gradient(Gradient? value) { + if (value == _gradient) { + return; + } + _gradient = value; + markNeedsPaint(); + } + + /// Gets the edgeStyle assigned to [RenderLinearAxis]. + LinearEdgeStyle get edgeStyle => _edgeStyle; + LinearEdgeStyle _edgeStyle; + + /// Sets the edgeStyle for [RenderLinearAxis]. + set edgeStyle(LinearEdgeStyle value) { + if (value == _edgeStyle) { + return; + } + _edgeStyle = value; + markNeedsPaint(); + } + + /// Gets the interval assigned to [RenderLinearAxis]. + double? get interval => _interval; + double? _interval; + + /// Sets the interval for [RenderLinearAxis]. + set interval(double? value) { + if (value == _interval) { + return; + } + _interval = value; + markNeedsPaint(); + } + + /// Gets the minorTicksPerInterval assigned to [RenderLinearAxis]. + int get minorTicksPerInterval => _minorTicksPerInterval; + int _minorTicksPerInterval; + + /// Sets the minorTicksPerInterval for [RenderLinearAxis]. + set minorTicksPerInterval(int value) { + if (value == _minorTicksPerInterval) { + return; + } + _minorTicksPerInterval = value; + markNeedsPaint(); + } + + /// Gets the numberFormat assigned to [RenderLinearAxis]. + NumberFormat? get numberFormat => _numberFormat; + NumberFormat? _numberFormat; + + /// Sets the numberFormat for [RenderLinearAxis]. + set numberFormat(NumberFormat? value) { + if (_numberFormat == value) { + return; + } + _numberFormat = value; + markNeedsPaint(); + } + + /// Gets the majorTickLength assigned to [RenderLinearAxis]. + double get majorTickLength => _majorTickLength; + double _majorTickLength; + + /// Sets the majorTickLength for [RenderLinearAxis]. + set majorTickLength(double value) { + if (value == _majorTickLength) { + return; + } + _majorTickLength = value; + markNeedsLayout(); + } + + /// Gets the majorTickThickness assigned to [RenderLinearAxis]. + double get majorTickThickness => _majorTickThickness; + double _majorTickThickness; + + /// Sets the majorTickThickness for [RenderLinearAxis]. + set majorTickThickness(double value) { + if (value == _majorTickThickness) { + return; + } + + _majorTickThickness = value; + markNeedsPaint(); + } + + /// Gets the majorTickColor assigned to [RenderLinearAxis]. + Color get majorTickColor => _majorTickColor; + Color _majorTickColor; + + /// Sets the majorTickColor for [RenderLinearAxis]. + set majorTickColor(Color value) { + if (value == _majorTickColor) { + return; + } + _majorTickColor = value; + markNeedsPaint(); + } + + /// Gets the majorTickLength assigned to [RenderLinearAxis]. + double get minorTickLength => _minorTickLength; + double _minorTickLength; + + /// Sets the majorTickLength for [RenderLinearAxis]. + set minorTickLength(double value) { + if (value == _minorTickLength) { + return; + } + _minorTickLength = value; + markNeedsLayout(); + } + + /// Gets the majorTickThickness assigned to [RenderLinearAxis]. + double get minorTickThickness => _minorTickThickness; + double _minorTickThickness; + + /// Sets the majorTickThickness for [RenderLinearAxis]. + set minorTickThickness(double value) { + if (value == _minorTickThickness) { + return; + } + + _minorTickThickness = value; + markNeedsPaint(); + } + + /// Gets the majorTickColor assigned to [RenderLinearAxis]. + Color get minorTickColor => _minorTickColor; + Color _minorTickColor; + + /// Sets the majorTickColor for [RenderLinearAxis]. + set minorTickColor(Color value) { + if (value == _minorTickColor) { + return; + } + _minorTickColor = value; + markNeedsPaint(); + } + + /// Gets the textStyle assigned to [RenderLinearAxis]. + TextStyle get textStyle => _textStyle; + TextStyle _textStyle; + + /// Sets the textStyle for [RenderLinearAxis]. + set textStyle(TextStyle value) { + if (value == _textStyle) { + return; + } + _textStyle = value; + markNeedsLayout(); + } + + /// Gets the labelPosition assigned to [RenderLinearAxis]. + LinearLabelPosition get labelPosition => _labelPosition; + LinearLabelPosition _labelPosition; + + /// Sets the labelPosition for [RenderLinearAxis]. + set labelPosition(LinearLabelPosition value) { + if (value == _labelPosition) { + return; + } + _labelPosition = value; + markNeedsLayout(); + } + + /// Gets the tickPosition assigned to [RenderLinearAxis]. + LinearElementPosition get tickPosition => _tickPosition; + LinearElementPosition _tickPosition; + + /// Sets the tickPosition for [RenderLinearAxis]. + set tickPosition(LinearElementPosition value) { + if (value == _tickPosition) { + return; + } + _tickPosition = value; + markNeedsLayout(); + } + + /// Gets the majorTickOffset assigned to [RenderLinearAxis]. + double get tickOffset => _tickOffset; + double _tickOffset; + + /// Sets the majorTickOffset for [RenderLinearAxis]. + set tickOffset(double value) { + if (value == _tickOffset) { + return; + } + + _tickOffset = value; + markNeedsLayout(); + } + + /// Gets the showLabels assigned to [RenderLinearAxis]. + bool get showLabels => _showLabels; + bool _showLabels; + + /// Sets the showLabels for [RenderLinearAxis]. + set showLabels(bool value) { + if (value == _showLabels) { + return; + } + _showLabels = value; + markNeedsLayout(); + } + + /// Gets the showTicks assigned to [RenderLinearAxis]. + bool get showTicks => _showTicks; + bool _showTicks; + + /// Sets the showTicks for [RenderLinearAxis]. + set showTicks(bool value) { + if (value == _showTicks) { + return; + } + _showTicks = value; + markNeedsLayout(); + } + + /// Gets the isMirrored assigned to [_RenderLinearGaugeRenderObject]. + bool get isMirrored => _isMirrored; + bool _isMirrored; + + /// Sets the isMirrored for [_RenderLinearGaugeRenderObject]. + set isMirrored(bool value) { + if (value == _isMirrored) { + return; + } + + _isMirrored = value; + markNeedsLayout(); + } + + /// Gets the axis extent assigned to [_RenderLinearGaugeRenderObject]. + double get axisTrackExtent => _axisTrackExtent; + double _axisTrackExtent; + + /// Sets the axis extent for [_RenderLinearGaugeRenderObject]. + set axisTrackExtent(double value) { + if (value == _axisTrackExtent) { + return; + } + + _axisTrackExtent = value; + markNeedsLayout(); + } + + /// Get the ranges assigned to [_RenderLinearGaugeRenderObject]. + List? get ranges => _ranges; + List? _ranges; + + /// Sets the ranges for [_RenderLinearGaugeRenderObject]. + set ranges(List? value) { + if (value == _ranges) { + return; + } + + _ranges = value; + if (useRangeColorForAxis) { + markNeedsPaint(); + } + } + + /// Gets the fade animation assigned to [RenderLinearRange]. + Animation? get fadeAnimation => _fadeAnimation; + Animation? _fadeAnimation; + + /// Gets the fade animation assigned to [RenderLinearRange]. + set fadeAnimation(Animation? value) { + if (value == _fadeAnimation) { + return; + } + + _removeAnimationListener(); + _fadeAnimation = value; + _addAnimationListener(); + } + + void _addAnimationListener() { + if (_fadeAnimation != null) { + _fadeAnimation!.addListener(markNeedsPaint); + } + } + + void _removeAnimationListener() { + if (_fadeAnimation != null) { + _fadeAnimation!.addListener(markNeedsPaint); + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _addAnimationListener(); + } + + @override + void detach() { + _removeAnimationListener(); + super.detach(); + } + + /// Returns the corresponding range color for the value + Color? _getRangeColor(double value) { + Color? color; + if (ranges != null && ranges!.isNotEmpty) { + for (int i = 0; i < ranges!.length; i++) { + if (ranges![i].startValue <= value && ranges![i].endValue >= value) { + color = ranges![i].color; + break; + } + } + } + + return color; + } + + /// Returns the measured the label size. + Size _measureLabelSize(double? value) { + final label = _labelMap[value]; + final TextSpan textSpan = TextSpan( + text: label, + style: textStyle, + ); + + _textPainter.text = textSpan; + _textPainter.layout(); + + return Size(_textPainter.width, _textPainter.height); + } + + /// Returns the tick size. + double getTickSize() => + showTicks ? (math.max(majorTickLength, minorTickLength) + tickOffset) : 0; + + /// Returns the axis line size. + double getAxisLineThickness() => showAxisTrack ? thickness : 0; + + /// Returns the label size. + double getEffectiveLabelSize() { + _maxLabelHeight = 0; + _maxLabelWidth = 0; + + if (showLabels) { + _visibleLabels.forEach((element) { + final Size labelSize = _measureLabelSize(element.value); + _maxLabelHeight = math.max(_maxLabelHeight, labelSize.height); + _maxLabelWidth = math.max(_maxLabelWidth, labelSize.width); + }); + + return (_isHorizontalOrientation ? _maxLabelHeight : _maxLabelWidth) + + labelOffset; + } + + return 0; + } + + /// Measures the axis element size. + double _measureAxisSize() { + late double _axisWidgetThickness; + final double tickMarginSize = showTicks ? tickOffset : 0; + final double labelSize = getEffectiveLabelSize(); + final double tickSize = getTickSize() - tickMarginSize; + final double axisSize = getAxisLineThickness(); + final LinearElementPosition position = + LinearGaugeHelper.getEffectiveElementPosition(tickPosition, isMirrored); + final LinearLabelPosition labelPlacement = + LinearGaugeHelper.getEffectiveLabelPosition(labelPosition, isMirrored); + + switch (position) { + case LinearElementPosition.inside: + if (labelPlacement == LinearLabelPosition.inside) { + _axisWidgetThickness = axisSize + tickSize + labelSize; + + if (tickMarginSize > labelSize) { + _axisWidgetThickness += tickMarginSize - labelSize; + } + } else { + _axisWidgetThickness = + axisSize + tickSize + labelSize + tickMarginSize; + } + break; + case LinearElementPosition.outside: + if (labelPlacement == LinearLabelPosition.inside) { + _axisWidgetThickness = + axisSize + tickSize + labelSize + tickMarginSize; + } else { + _axisWidgetThickness = axisSize + tickSize + labelSize; + + if (tickMarginSize > labelSize) { + _axisWidgetThickness += tickMarginSize - labelSize; + } + } + break; + case LinearElementPosition.cross: + _axisWidgetThickness = axisSize + + ((axisSize < tickSize) ? tickSize - axisSize : 0.0) + + labelSize; + break; + default: + break; + } + + return _axisWidgetThickness; + } + + /// Calculates the axis elements position. + void _calculateAxisElementPositions() { + final double tickMarginSize = showTicks ? tickOffset : 0; + final double labelMarginSize = showLabels ? labelOffset : 0; + final double tickSize = getTickSize() - tickMarginSize; + final double labelSize = getEffectiveLabelSize() - labelMarginSize; + final double axisSize = getAxisLineThickness(); + final LinearElementPosition? position = + LinearGaugeHelper.getEffectiveElementPosition(tickPosition, isMirrored); + final LinearLabelPosition? labelPlacement = + LinearGaugeHelper.getEffectiveLabelPosition(labelPosition, isMirrored); + + switch (position) { + case LinearElementPosition.inside: + switch (labelPlacement) { + case LinearLabelPosition.inside: + axisOffset = 0; + _tickTop = showTicks ? axisSize + tickMarginSize : 0; + _labelTop = + showLabels ? (axisSize + tickSize + labelMarginSize) : 0; + break; + case LinearLabelPosition.outside: + _labelTop = 0; + axisOffset = showAxisTrack ? labelSize + labelMarginSize : 0; + _tickTop = showTicks + ? labelSize + labelMarginSize + axisSize + tickMarginSize + : 0; + break; + default: + break; + } + break; + case LinearElementPosition.outside: + switch (labelPlacement) { + case LinearLabelPosition.inside: + _tickTop = 0; + axisOffset = showAxisTrack ? tickSize + tickMarginSize : 0; + _labelTop = showLabels + ? axisSize + tickSize + labelMarginSize + tickMarginSize + : 0; + break; + case LinearLabelPosition.outside: + _labelTop = 0; + _tickTop = + showTicks ? (labelSize + labelMarginSize - tickMarginSize) : 0; + axisOffset = + showAxisTrack ? (labelSize + tickSize + labelMarginSize) : 0; + + if (tickMarginSize > labelSize + labelMarginSize) { + _labelTop = tickMarginSize - (labelSize + labelMarginSize); + _tickTop = 0; + axisOffset += tickMarginSize - (labelSize + labelMarginSize); + } + break; + default: + break; + } + break; + case LinearElementPosition.cross: + switch (labelPlacement) { + case LinearLabelPosition.inside: + if (axisSize < tickSize && tickSize > 0) { + _tickTop = 0; + axisOffset = showAxisTrack ? (tickSize - axisSize) / 2 : 0; + _labelTop = showLabels + ? (axisSize + (tickSize - axisSize) + labelMarginSize) + : 0; + } else if (axisSize > tickSize && tickSize > 0) { + axisOffset = 0; + _tickTop = showTicks ? (axisSize - tickSize) / 2 : 0; + _labelTop = showLabels ? axisSize + labelMarginSize : 0; + } else { + axisOffset = 0; + _tickTop = 0; + _labelTop = showLabels ? axisSize + labelMarginSize : 0; + } + break; + case LinearLabelPosition.outside: + if (axisSize < tickSize && tickSize > 0) { + _labelTop = 0; + _tickTop = showTicks ? labelSize + labelMarginSize : 0; + axisOffset = showAxisTrack + ? labelSize + labelMarginSize + (tickSize - axisSize) / 2 + : 0; + } else if (axisSize > tickSize && tickSize > 0) { + _labelTop = 0; + axisOffset = showAxisTrack ? labelSize + labelMarginSize : 0; + _tickTop = showTicks + ? labelSize + labelMarginSize + (axisSize - tickSize) / 2 + : 0; + } else { + _labelTop = 0; + axisOffset = showAxisTrack ? labelSize + labelMarginSize : 0; + _tickTop = showTicks ? labelSize + labelMarginSize : 0; + } + break; + default: + break; + } + break; + default: + break; + } + } + + double _getStartLabelPadding() { + if (showLabels) { + if (_isHorizontalOrientation) { + return isAxisInversed + ? _maxLabelSize.width / 2 + : _minLabelSize.width / 2; + } else { + return isAxisInversed + ? _maxLabelSize.height / 2 + : _minLabelSize.height / 2; + } + } else { + return 0; + } + } + + double _getEndLabelPadding() { + if (showLabels) { + if (_isHorizontalOrientation) { + return isAxisInversed + ? _minLabelSize.width / 2 + : _maxLabelSize.width / 2; + } else { + return isAxisInversed + ? _minLabelSize.height / 2 + : _maxLabelSize.height / 2; + } + } else { + return 0; + } + } + + /// Return the axis layout padding. + double getAxisLayoutPadding() { + double _layoutStartPadding = 0; + double _layoutEndPadding = 0; + + final labelStartPadding = _getStartLabelPadding(); + final labelEndPadding = _getEndLabelPadding(); + + if (axisTrackExtent == 0) { + if (pointerStartPadding! > labelStartPadding) { + _layoutStartPadding = pointerStartPadding! - labelStartPadding; + } + + if (pointerEndPadding! > labelEndPadding) { + _layoutEndPadding = pointerEndPadding! - labelEndPadding; + } + } else { + _layoutStartPadding = 0; + _layoutEndPadding = 0; + + /// Measure the layout start padding based on axis track extent. + if (pointerStartPadding! > axisTrackExtent) { + _layoutStartPadding = pointerStartPadding! - axisTrackExtent; + + if (labelStartPadding > axisTrackExtent) { + _layoutStartPadding -= labelStartPadding - axisTrackExtent; + } + } + + if (pointerStartPadding! < labelStartPadding) { + _layoutStartPadding = 0; + } + + /// Measure the layout end padding based on axis track extent. + if (pointerEndPadding! > axisTrackExtent) { + _layoutEndPadding = pointerEndPadding! - axisTrackExtent; + + if (labelEndPadding > axisTrackExtent) { + _layoutEndPadding -= labelEndPadding - axisTrackExtent; + } + } + + if (pointerEndPadding! < labelEndPadding) { + _layoutEndPadding = 0; + } + } + + return _layoutStartPadding + _layoutEndPadding; + } + + /// Return the axis position padding. + double getAxisPositionPadding() { + final axisStartPadding = _getStartLabelPadding(); + double padding = 0; + + if (axisTrackExtent == 0) { + if (pointerStartPadding! > axisStartPadding) { + padding = (pointerStartPadding! - _getStartLabelPadding()); + } + } else { + if (pointerStartPadding! < axisTrackExtent) { + padding = 0; + } else if (axisTrackExtent <= pointerStartPadding!) { + padding = pointerStartPadding! - axisTrackExtent; + + if (axisStartPadding > axisTrackExtent) { + padding -= axisStartPadding - axisTrackExtent; + } + } + + if (pointerStartPadding! < axisStartPadding) { + padding = 0; + } + } + + return padding; + } + + /// Get the child padding for positioning. + double getChildPadding({dynamic child = null}) { + double paddingSize = math.max( + math.max(axisTrackExtent, pointerStartPadding!), + _getStartLabelPadding()); + + if (child != null && + (child is RenderLinearWidgetPointer || + child is RenderLinearShapePointer)) { + final childSize = + _isHorizontalOrientation ? child.size.width : child.size.height; + + if (child.markerAlignment == LinearMarkerAlignment.start) { + return paddingSize; + } else if (child.markerAlignment == LinearMarkerAlignment.center) { + return paddingSize -= childSize / 2; + } else { + return paddingSize -= childSize; + } + } + + return paddingSize; + } + + /// Calculates the visible labels based on interval and range. + void _generateVisibleLabels(Size size) { + _visibleLabels.clear(); + if (onGenerateLabels == null) { + final actualInterval = interval ?? _calculateAxisInterval(size); + + if (actualInterval != 0 && minimum != maximum) { + for (double i = minimum; i <= maximum; i += actualInterval) { + final LinearAxisLabel currentLabel = _getAxisLabel(i); + _visibleLabels.add(currentLabel); + } + + final LinearAxisLabel label = _visibleLabels[_visibleLabels.length - 1]; + if (label.value != maximum && label.value < maximum) { + final LinearAxisLabel currentLabel = _getAxisLabel(maximum); + _visibleLabels.add(currentLabel); + } + } + } else { + _visibleLabels = onGenerateLabels!(); + } + + _labelMap.clear(); + _visibleLabels + .forEach((element) => _labelMap[element.value] = element.text); + } + + LinearAxisLabel _getAxisLabel(double value) { + String labelText = value.toString(); + + if (numberFormat != null) { + labelText = numberFormat!.format(value); + } + + if (labelFormatterCallback != null) { + labelText = labelFormatterCallback!(labelText); + } + + return LinearAxisLabel(value: value, text: labelText); + } + + /// Generates the labels based on area. + void generateLabels(BoxConstraints constraints) { + final double actualParentWidth = constraints.maxWidth; + final double actualParentHeight = constraints.maxHeight; + + /// Creates the axis labels. + _generateVisibleLabels(Size(actualParentWidth, actualParentHeight)); + + _minLabelSize = _measureLabelSize(minimum); + _maxLabelSize = _measureLabelSize(maximum); + } + + @override + void performLayout() { + double _parentWidgetSize; + + final double actualParentWidth = constraints.maxWidth; + final double actualParentHeight = constraints.maxHeight; + + if (_isHorizontalOrientation) { + _parentWidgetSize = actualParentWidth; + } else { + _parentWidgetSize = actualParentHeight; + } + + final double axisWidgetThickness = _measureAxisSize(); + + /// Calculates the axis, tick and label elements top position. + /// + /// Depending on the axis top value, the location of the range and pointer + /// elements will be changed. + _calculateAxisElementPositions(); + + if (_isHorizontalOrientation) { + _axisActualSize = Size(_parentWidgetSize, axisWidgetThickness); + } else { + _axisActualSize = Size(axisWidgetThickness, _parentWidgetSize); + } + + size = _axisActualSize; + } + + /// To calculate the axis interval based on the maximum axis label count. + double _calculateAxisInterval(Size size) { + final delta = (maximum - minimum).abs(); + final area = _isHorizontalOrientation ? size.width : size.height; + final actualDesiredIntervalsCount = + math.max((area * maximumLabels) / 100, 1.0); + double niceInterval = delta / actualDesiredIntervalsCount; + final minInterval = + math.pow(10, (math.log(niceInterval) / math.log(10)).floor()); + final intervalDivisions = [10, 5, 2, 1]; + for (final intervalDivision in intervalDivisions) { + final double currentInterval = minInterval * intervalDivision; + if (actualDesiredIntervalsCount < (delta / currentInterval)) { + break; + } + + niceInterval = currentInterval; + } + + return niceInterval; + } + + ///Converts the value to factor. + double valueToFactor(double value, {bool isTickPositionCalculation = false}) { + if (value > maximum) { + value = maximum; + } else if (value < minimum) { + value = minimum; + } + + double factor; + if (valueToFactorCallback != null && !isTickPositionCalculation) { + factor = valueToFactorCallback!(value); + } else { + factor = (value - minimum) / (maximum - minimum); + } + + if (_isHorizontalOrientation) { + factor = isAxisInversed ? 1 - factor : factor; + } else { + factor = !isAxisInversed ? 1 - factor : factor; + } + + return factor; + } + + ///Converts the factor to value. + double factorToValue(double factor) { + if (_isHorizontalOrientation) { + factor = isAxisInversed ? 1 - factor : factor; + } else { + factor = !isAxisInversed ? 1 - factor : factor; + } + + if (factorToValueCallback != null) { + return factorToValueCallback!(factor); + } else { + return (factor * (maximum - minimum)) + minimum; + } + } + + /// Returns the pixel position based on value. + double valueToPixel(double value, {bool isTickPositionCalculation = false}) { + final factor = valueToFactor(value, + isTickPositionCalculation: isTickPositionCalculation); + + double? labelStartPadding = _getStartLabelPadding(); + double? labelEndPadding = _getEndLabelPadding(); + + if (axisTrackExtent > 0) { + if (axisTrackExtent >= labelStartPadding) { + labelStartPadding = axisTrackExtent; + } + + if (axisTrackExtent >= labelEndPadding) { + labelEndPadding = axisTrackExtent; + } + } + + return factor * + ((_isHorizontalOrientation + ? _axisActualSize.width + : _axisActualSize.height) - + (labelStartPadding + labelEndPadding)); + } + + void _drawTickLine(double x1, double y1, double x2, double y2, Canvas canvas, + bool isMajorTick) { + final Offset majorTickStartOffset = Offset(x1, y1); + final Offset majorTickEndOffset = Offset(x2, y2); + canvas.drawLine(majorTickStartOffset, majorTickEndOffset, _axisPaint); + } + + double? _getMinorTickGap(int index) { + double endValuePosition; + if (_visibleLabels.length - 1 == index) { + return null; + } else { + endValuePosition = valueToPixel(_visibleLabels[index + 1].value, + isTickPositionCalculation: true); + } + + final width = (endValuePosition - + valueToPixel(_visibleLabels[index].value, + isTickPositionCalculation: true)) + .abs() / + (minorTicksPerInterval + 1); + + if (_isHorizontalOrientation) { + return width * (isAxisInversed ? -1 : 1); + } else { + return width * (isAxisInversed ? 1 : -1); + } + } + + double? _getMinorTickValueGap(int index) { + if (_visibleLabels.length - 1 == index) { + return null; + } else { + return (_visibleLabels[index + 1].value - _visibleLabels[index].value) / + minorTicksPerInterval; + } + } + + void _setPaintColor(Color paintColor) { + double animationValue = 1; + if (_fadeAnimation != null) { + animationValue = _fadeAnimation!.value; + } + + _axisPaint.color = + paintColor.withOpacity(animationValue * paintColor.opacity); + } + + ///Draws minor tick elements. + void _drawMinorTicks(double minorTickLeftPosition, double top, + int majorTickIndex, Canvas canvas) { + final position = + LinearGaugeHelper.getEffectiveElementPosition(tickPosition, isMirrored); + + if (_isHorizontalOrientation) { + if (position == LinearElementPosition.outside) { + top = top + majorTickLength - minorTickLength; + } else if (position == LinearElementPosition.cross) { + top = top + (majorTickLength - minorTickLength) / 2; + } + } else { + if (position == LinearElementPosition.outside) { + minorTickLeftPosition = + minorTickLeftPosition + majorTickLength - minorTickLength; + } else if (position == LinearElementPosition.cross) { + minorTickLeftPosition = + minorTickLeftPosition + (majorTickLength - minorTickLength) / 2; + } + } + + final minorTickGap = _getMinorTickGap(majorTickIndex); + final valueGap = _getMinorTickValueGap(majorTickIndex); + + if (minorTickGap != null) { + for (int minorTickIndex = 1; + minorTickIndex <= minorTicksPerInterval; + minorTickIndex++) { + if (_isHorizontalOrientation) { + minorTickLeftPosition += minorTickGap; + } else { + top += minorTickGap; + } + + _setPaintColor(useRangeColorForAxis + ? _getRangeColor(_visibleLabels[majorTickIndex].value + + (valueGap! * minorTickIndex)) ?? + minorTickColor + : minorTickColor); + + _axisPaint.strokeWidth = minorTickThickness; + + _drawTickLine( + minorTickLeftPosition, + top, + minorTickLeftPosition + + (!_isHorizontalOrientation ? minorTickLength : 0), + top + (_isHorizontalOrientation ? minorTickLength : 0), + canvas, + false); + } + } + } + + /// The style information for text runs, encoded for use by `dart:ui`. + dart_ui.TextStyle _getTextStyle( + {double textScaleFactor = 1.0, required TextStyle style, Color? color}) { + double animationValue = 1; + + if (_fadeAnimation != null) { + animationValue = _fadeAnimation!.value; + } + + if (color != null) { + color = color.withOpacity(animationValue * color.opacity); + } + + return dart_ui.TextStyle( + color: color, + decoration: style.decoration, + decorationColor: style.decorationColor, + decorationStyle: style.decorationStyle, + decorationThickness: style.decorationThickness, + fontWeight: style.fontWeight, + fontStyle: style.fontStyle, + textBaseline: style.textBaseline, + fontFamily: style.fontFamily, + fontFamilyFallback: style.fontFamilyFallback, + fontSize: + style.fontSize == null ? null : style.fontSize! * textScaleFactor, + letterSpacing: style.letterSpacing, + wordSpacing: style.wordSpacing, + height: style.height, + locale: style.locale, + foreground: style.foreground, + background: style.background ?? + (style.backgroundColor != null + ? (Paint()..color = style.backgroundColor!) + : null), + shadows: style.shadows, + fontFeatures: style.fontFeatures, + ); + } + + ///Draws axis label elements. + void _drawLabels(Canvas canvas, int majorTickIndex, + double majorTickLeftPosition, double top) { + final paragraphStyle = dart_ui.ParagraphStyle( + textDirection: TextDirection.ltr, textAlign: TextAlign.left); + final labelText = _labelMap[_visibleLabels[majorTickIndex].value]!; + final value = _visibleLabels[majorTickIndex].value; + final labelTextStyle = _getTextStyle( + style: textStyle, + color: useRangeColorForAxis + ? _getRangeColor(_visibleLabels[majorTickIndex].value) ?? + textStyle.color + : textStyle.color); + final paragraphBuilder = dart_ui.ParagraphBuilder(paragraphStyle) + ..pushStyle(labelTextStyle) + ..addText(labelText); + final paragraph = paragraphBuilder.build(); + final Size labelSize = _measureLabelSize(value); + paragraph.layout(dart_ui.ParagraphConstraints(width: labelSize.width)); + + Offset labelOffset; + + if (_isHorizontalOrientation) { + final _labelLeftPosition = majorTickLeftPosition - (labelSize.width / 2); + labelOffset = Offset(_labelLeftPosition, _labelTop); + } else { + final _labelLeftPosition = top - (labelSize.height / 2); + + if (_labelTop == 0 && _maxLabelWidth > labelSize.width) { + labelOffset = + Offset(_maxLabelWidth - labelSize.width, _labelLeftPosition); + } else { + labelOffset = Offset(_labelTop, _labelLeftPosition); + } + } + + canvas.drawParagraph(paragraph, labelOffset); + } + + /// Draws the axis line. + void _drawAxisLine(Canvas canvas, Offset offset) { + double startLabelPadding = _getStartLabelPadding(); + double endLabelPadding = _getEndLabelPadding(); + + if (startLabelPadding > axisTrackExtent) { + startLabelPadding = startLabelPadding - axisTrackExtent; + } else { + startLabelPadding = 0; + } + + if (endLabelPadding > axisTrackExtent) { + endLabelPadding = endLabelPadding - axisTrackExtent; + } else { + endLabelPadding = 0; + } + + if (_isHorizontalOrientation) { + _axisLineRect = Rect.fromLTWH( + offset.dx + startLabelPadding, + offset.dy + axisOffset, + size.width - (startLabelPadding + endLabelPadding), + thickness); + } else { + _axisLineRect = Rect.fromLTWH( + offset.dx + axisOffset, + offset.dy + startLabelPadding, + thickness, + size.height - (startLabelPadding + endLabelPadding)); + } + + _axisLineRect = Rect.fromLTWH( + _axisLineRect.left + borderWidth / 2, + _axisLineRect.top + borderWidth / 2, + _axisLineRect.width - borderWidth, + _axisLineRect.height - borderWidth); + + if (showAxisTrack) { + _axisPaint.style = PaintingStyle.fill; + _setPaintColor(color); + + if (gradient != null) { + _axisPaint.shader = gradient!.createShader(_axisLineRect); + } + + _path.reset(); + switch (edgeStyle) { + case LinearEdgeStyle.bothFlat: + _path.addRect(_axisLineRect); + break; + case LinearEdgeStyle.bothCurve: + _path.addRRect(RRect.fromRectAndRadius( + _axisLineRect, Radius.circular(thickness))); + break; + case LinearEdgeStyle.startCurve: + _path.addRRect(LinearGaugeHelper.getStartCurve( + isHorizontal: _isHorizontalOrientation, + isAxisInversed: isAxisInversed, + rect: _axisLineRect, + radius: thickness / 2)); + break; + case LinearEdgeStyle.endCurve: + _path.addRRect(LinearGaugeHelper.getEndCurve( + isHorizontal: _isHorizontalOrientation, + isAxisInversed: isAxisInversed, + rect: _axisLineRect, + radius: thickness / 2)); + break; + default: + break; + } + + canvas.drawPath(_path, _axisPaint); + + if (borderWidth > 0) { + _axisPaint.shader = null; + _axisPaint.style = PaintingStyle.stroke; + _axisPaint.strokeWidth = borderWidth; + _setPaintColor(borderColor); + canvas.drawPath(_path, _axisPaint); + } + } + } + + void _drawTicksAndLabels(Canvas canvas, Offset offset) { + final majorTickLeftPosition = + math.max(_getStartLabelPadding(), axisTrackExtent); + + Offset tickStartPoint, tickEndPoint; + + for (int index = 0; index < _visibleLabels.length; index++) { + _axisPaint.shader = null; + _setPaintColor(useRangeColorForAxis + ? _getRangeColor(_visibleLabels[index].value) ?? majorTickColor + : majorTickColor); + _axisPaint.strokeWidth = majorTickThickness; + final calculatedPosition = valueToPixel(_visibleLabels[index].value, + isTickPositionCalculation: true) + + majorTickLeftPosition; + + tickStartPoint = Offset(calculatedPosition, _tickTop); + tickEndPoint = Offset(calculatedPosition, _tickTop + majorTickLength); + + if (orientation == LinearGaugeOrientation.vertical) { + tickStartPoint = Offset(tickStartPoint.dy, tickStartPoint.dx); + tickEndPoint = Offset(tickEndPoint.dy, tickEndPoint.dx); + } + + if (showTicks) { + /// Drawing the major ticks. + _drawTickLine(tickStartPoint.dx, tickStartPoint.dy, tickEndPoint.dx, + tickEndPoint.dy, canvas, true); + _drawMinorTicks(tickStartPoint.dx, tickStartPoint.dy, index, canvas); + } + + if (showLabels) { + _drawLabels(canvas, index, tickStartPoint.dx, tickStartPoint.dy); + } + } + } + + @override + bool get isRepaintBoundary => true; + + @override + void paint(PaintingContext context, Offset offset) { + final canvas = context.canvas; + _drawAxisLine(canvas, offset); + + if (showTicks || showLabels) { + _drawTicksAndLabels(canvas, offset); + } + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_track_style.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_track_style.dart new file mode 100644 index 000000000..e81c5f191 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_track_style.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; + +import '../../linear_gauge/utils/enum.dart'; + +/// [LinearAxisTrackStyle] has properties for customizing the axis track. +class LinearAxisTrackStyle { + /// Creates a style for axis line. + LinearAxisTrackStyle( + {this.thickness = 5.0, + this.edgeStyle = LinearEdgeStyle.bothFlat, + this.color, + this.gradient, + this.borderColor, + this.borderWidth = 0}); + + /// Specifies the thickness value of an axis track. + /// + /// Defaults to 5.0. + /// + /// This snippet shows how to set the thickness for an axis track. + /// + /// ```dart + /// + /// SfLinearGauge( + /// axisLineStyle: LinearAxisLineStyle( + /// thickness: 20 ) + /// ) + /// ``` + /// + final double thickness; + + /// Specifies the color to be applied for an axis track. + /// + /// Defaults to [Colors.black12] in LightTheme + /// and [Colors.white24] in DarkTheme. + /// + /// This snippet shows how to set the color for an axis track. + /// + /// ```dart + /// + /// SfLinearGauge( + /// axisLineStyle: LinearAxisLineStyle( + /// color: Colors.green ) + /// ) + /// ``` + /// + final Color? color; + + /// Specifies the border color for an axis track. + /// + /// Defaults to [Colors.black26] in LightTheme + /// and [Colors.white30] in DarkTheme. + /// + /// This snippet shows how to set border color of an axis track. + /// + /// ```dart + /// + /// SfLinearGauge( + /// axisLineStyle: LinearAxisLineStyle( + /// borderColor: Colors.Brown ) + /// ) + /// ``` + final Color? borderColor; + + /// Specifies the border width of an axis track. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set border width for an axis track. + /// + /// ```dart + /// + /// SfLinearGauge( + /// axisLineStyle: LinearAxisLineStyle( + /// borderWidth: 2 ) + /// ) + /// ``` + /// + final double borderWidth; + + /// Specifies the edge styles for an axis track. + /// + /// Defaults to [LinearEdgeStyle.bothFlat]. + /// + /// This snippet shows how to set edge style for an axis track. + /// + /// ```dart + /// + /// SfLinearGauge( + /// axisLineStyle: LinearAxisLineStyle( + /// edgeStyle: LinearEdgeStyle.endCurce) + /// ) + /// ``` + /// + final LinearEdgeStyle edgeStyle; + + /// Specifies the gradient colors for an axis track. + /// + /// Defaults to null. + /// + /// This snippet shows how to set gradient colors for an axis track. + /// + /// ```dart + /// + /// SfLinearGauge( + /// axisLineStyle: LinearAxisLineStyle( + /// gradient: LinearGradient(colors: [ + /// Colors.red, + /// Colors.orange, + /// Colors.green, + /// ], stops: [ + /// 0.3, + /// 0.5 + /// 0.8 ]),)) + /// ``` + /// + final LinearGradient? gradient; + + @override + bool operator ==(Object other) { + late LinearAxisTrackStyle otherStyle; + + if (identical(this, other)) { + return true; + } + + if (other.runtimeType != runtimeType) { + return false; + } + + if (other is LinearAxisTrackStyle) otherStyle = other; + + return otherStyle.thickness == thickness && + otherStyle.color == color && + otherStyle.borderColor == borderColor && + otherStyle.borderWidth == borderWidth && + otherStyle.edgeStyle == edgeStyle && + otherStyle.gradient == gradient; + } + + @override + int get hashCode { + return hashValues( + thickness, + color, + borderColor, + borderWidth, + edgeStyle, + gradient, + ); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_tick_style.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_tick_style.dart new file mode 100644 index 000000000..2006dc8a7 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_tick_style.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; + +/// Ticks style for the linear axis. +class LinearTickStyle { + /// Creates linear tick style with default or required properties. + /// + /// The arguments [length], [thickness], must be non-negative. + LinearTickStyle({this.length = 4, this.thickness = 1, this.color}); + + /// Specifies the length of major and minor ticks. + /// + /// Defaults to 8.0 for [majorTicks] and 4.0 for [minorTicks]. + /// + /// This snippet shows how to set tick length. + /// + /// ```dart + /// + /// SfLinearGauge(majorTickStyle: LinearTickStyle( + /// length: 20,), + /// minotTickStyle: LinearTickStyle( + /// length: 10,) + /// ) + /// ``` + /// + final double length; + + /// Specifies the thickness of major and minor ticks. + /// + /// Defaults to 1. + /// + /// This snippet shows how to set tick thickness. + /// + /// ```dart + /// + /// SfLinearGauge(majorTickStyle: LinearTickStyle( + /// thickness: 3), + /// minorTickStyle: LinearTickStyle( + /// thickness: 1,) + /// ) + /// ``` + /// + final double thickness; + + /// Specifies the color of ticks. + /// + /// Defaults to [Colors.black26] in Light theme and [Colors.white30] in Dark theme for major ticks. + /// Defaults to [Colors.black26] in Light theme and [Colors.white30] in Dark theme for major ticks. + /// + /// This snippet shows how to set color of ticks. + /// + /// ```dart + /// + /// SfLinearGauge(majorTickStyle: LinearTickStyle( + /// color: Colors.Blue) + /// minorTickStyle: LinearTickStyle( + /// color: Colors.Green,) + /// ) + /// ``` + /// + final Color? color; + + @override + bool operator ==(Object other) { + late LinearTickStyle otherStyle; + + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + if (other is LinearTickStyle) otherStyle = other; + + return otherStyle.length == length && + otherStyle.color == color && + otherStyle.thickness == thickness; + } + + @override + int get hashCode { + return hashValues( + length, + color, + thickness, + ); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge.dart new file mode 100644 index 000000000..aeeda7e42 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge.dart @@ -0,0 +1,783 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +import '../../linear_gauge/axis/linear_axis_renderer.dart'; +import '../../linear_gauge/axis/linear_axis_track_style.dart'; +import '../../linear_gauge/axis/linear_tick_style.dart'; +import '../../linear_gauge/gauge/linear_gauge_render_widget.dart'; +import '../../linear_gauge/gauge/linear_gauge_scope.dart'; +import '../../linear_gauge/pointers/linear_bar_pointer.dart'; +import '../../linear_gauge/pointers/linear_marker_pointer.dart'; +import '../../linear_gauge/range/linear_gauge_range.dart'; +import '../../linear_gauge/utils/enum.dart'; +import '../../linear_gauge/utils/linear_gauge_helper.dart'; +import '../../linear_gauge/utils/linear_gauge_typedef.dart'; + +/// Creates a linear gauge widget to displays the values on a linear scale. +/// It has a rich set of features +/// such as axis, ranges, and pointers that are fully +/// customizable and extendable. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Container( +/// child: SfLinearGauge()); +///} +/// ``` +class SfLinearGauge extends StatefulWidget { + /// Creates a linear gauge + SfLinearGauge( + {Key? key, + this.minimum = 0.0, + this.maximum = 100.0, + this.interval, + this.ranges, + this.barPointers, + this.markerPointers, + this.orientation = LinearGaugeOrientation.horizontal, + this.isAxisInversed = false, + this.isMirrored = false, + this.animateAxis = false, + this.animateRange = false, + this.animationDuration = 1000, + this.showLabels = true, + this.showAxisTrack = true, + this.showTicks = true, + this.minorTicksPerInterval = 1, + this.useRangeColorForAxis = false, + this.axisTrackExtent = 0, + this.labelPosition = LinearLabelPosition.inside, + this.tickPosition = LinearElementPosition.inside, + double tickOffset = 0, + double labelOffset = 4, + this.maximumLabels = 3, + NumberFormat? numberFormat, + this.onGenerateLabels, + this.valueToFactorCallback, + this.factorToValueCallback, + this.labelFormatterCallback, + TextStyle? axisLabelStyle, + LinearAxisTrackStyle? axisTrackStyle, + LinearTickStyle? majorTickStyle, + LinearTickStyle? minorTickStyle}) + : assert(minimum <= maximum, 'Maximum should be greater than minimum.'), + axisLabelStyle = axisLabelStyle, + axisTrackStyle = axisTrackStyle ?? LinearAxisTrackStyle(), + tickOffset = tickOffset > 0 ? tickOffset : 0, + labelOffset = labelOffset > 0 ? labelOffset : 4, + majorTickStyle = majorTickStyle ?? LinearTickStyle(length: 8.0), + minorTickStyle = minorTickStyle ?? LinearTickStyle(length: 4.0), + numberFormat = numberFormat ?? NumberFormat('#.##'), + super(key: key); + + ///Customizes each range by adding it to the [ranges] collection. + /// + /// Also refer [LinearGaugeRange] + /// + final List? ranges; + + ///Customizes each bar pointer by adding it to the [barPointers] collection. + /// + /// Also refer [LinearGaugePointer] + /// + final List? barPointers; + + /// Add a list of gauge shape and widget pointer to the linear gauge and customize + /// each pointer by adding it to the [pointers] collection. + /// + /// Also refer [LinearGaugePointer] + /// + final List? markerPointers; + + /// Specifies the animation for axis. + /// + /// Defaults to false. + /// + /// This snippet shows how to set load time animation for [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// animateAxis: true + /// ) + /// ``` + /// + final bool animateAxis; + + /// Specifies the animation for range. + /// + /// Defaults to false. + /// + /// This snippet shows how to set load time animation. + /// + /// ```dart + /// + /// SfLinearGauge( + /// animateRange: true + /// ) + /// ``` + /// + final bool animateRange; + + /// Specifies the load time animation duration. + /// Duration is defined in milliseconds. + /// + /// Defaults to 1000. + /// + /// This snippet shows how to set loading animation duration for[SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// animationDuration: 7000 + /// ) + /// ``` + /// + final int animationDuration; + + /// Specifies the orientation of [SfLinearGauge]. + /// + /// Defaults to [LinearGaugeOrientation.horizontal]. + /// + /// This snippet shows how to set orientation for [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// orientation: LinearGaugeOrientation.vertical + /// ) + /// ``` + /// + final LinearGaugeOrientation orientation; + + /// Specifies the minimum value of an axis. The axis starts with this value. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set minimum value of an axis. + /// + /// ```dart + /// + /// SfLinearGauge( + /// minimum: 10 + /// ) + /// ``` + /// + final double minimum; + + /// Specifies the maximum value of an axis. The axis ends at this value. + /// + /// Defaults to 100. + /// + /// This snippet shows how to set maximum value of an axis. + /// + /// ```dart + /// + /// SfLinearGauge( + /// maximum: 150 + /// ) + /// ``` + /// + final double maximum; + + /// Specifies the interval between major ticks in an axis. + /// + /// This snippet shows how to set the interval between major ticks. + /// + /// ```dart + /// + /// SfLinearGauge( + /// interval: 20 + /// ) + /// ``` + /// + final double? interval; + + /// Extends the axis on  both ends with the specified value. + /// + /// Defaults to 0. + /// + /// This snippet shows how to extend the axis track. + /// + /// ```dart + /// + /// SfLinearGauge( + /// axisTrackExtent : 5 + /// ) + /// ``` + /// + final double axisTrackExtent; + + /// Specifies the label style of axis. + /// + /// This snippet shows how to set label styles for axis. + /// + /// ```dart + /// + /// SfLinearGauge( + /// axisLabelStyle: TextStyle( + /// fontStyle: FontStyle.italic, + /// fontWeight: FontWeight.bold, + /// fontSize: 12.0, + /// color: Colors.blueGrey), + /// ) + /// ``` + /// + final TextStyle? axisLabelStyle; + + /// Customizes the style of the axis line. + /// + /// Defaults to the [LinearAxisTrackStyle] property with thickness is 5.0 + /// logical pixels. + final LinearAxisTrackStyle axisTrackStyle; + + /// Specifies the minor ticks count per interval in an axis. + /// + /// Defaults to 1. + /// + /// This snippet shows how to set the minor ticks per interval. + /// + /// ```dart + /// + /// SfLinearGauge( + /// minorTicksPerInterval: 5 + /// ) + /// ``` + /// + final int minorTicksPerInterval; + + /// The style to use for the major axis tick lines. + /// + /// Defaults to the [LinearTickStyle] property with length is 8.0 logical + /// pixels. + /// + /// Also refer [LinearTickStyle] + final LinearTickStyle majorTickStyle; + + /// The style to use for the minor axis tick lines. + /// + /// Defaults to the [LinearTickStyle] property with length is 4.0 logical + /// pixels. + /// + /// Also refer [LinearTickStyle] + final LinearTickStyle minorTickStyle; + + /// Determines whether to show the labels in an axis. + /// + /// Defaults to true. + /// + /// This snippet shows how to hide labels of axis in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// showLabels: false + /// ) + /// ``` + /// + final bool showLabels; + + /// Determines whether to show an axis track in [SfLinearGauge]. + /// + /// Defaults to true. + /// + /// This snippet shows how to hide an axis in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// showAxisTrack: false + /// ) + /// ``` + /// + final bool showAxisTrack; + + /// Determines whether to show ticks. + /// + /// Defaults to true. + /// + /// This snippet shows how to hide ticks in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// showTicks: false + /// ) + /// ``` + /// + final bool showTicks; + + /// Determines whether to use range color for an axis in [SfLinearGauge]. + /// + /// Defaults to false. + /// + /// This snippet shows how to apply range color for an axis in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// useRangeColorForAxis: true + /// ) + /// ``` + /// + final bool useRangeColorForAxis; + + /// Specifies the position of labels with respect to an axis. + /// + /// Defaults to [LinearLabelPosition.inside]. + /// + /// This snippet shows how to set position of labels in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// labelPosition: LinearLabelPosition.outside + /// ) + /// ``` + /// + final LinearLabelPosition labelPosition; + + /// Specifies the position of ticks with respect to axis. + /// + /// Defaults to [LinearElementPosition.inside]. + /// + /// This snippet shows how to set position of ticks in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// tickPosition: LinearElementPosition.outside + /// ) + /// ``` + /// + final LinearElementPosition tickPosition; + + /// Specifies the offset value of labels to custom-position them. + /// + /// Defaults to 4. + /// + /// This snippet shows how to set offset for labels. + /// + /// ```dart + /// + /// SfLinearGauge( + /// labelOffset : 5 + /// ) + /// ``` + /// + final double labelOffset; + + /// Specifies the offset value of ticks to custom-position them. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set offset for ticks. + /// + /// ```dart + /// + /// SfLinearGauge( + /// tickOffset: 5 + /// ) + /// ``` + /// + final double tickOffset; + + /// Determines whether to inverse an axis in a [SfLinearGauge]. + /// + /// Defaults to false. + /// + /// This snippet shows how to set inversed axis in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// isAxisInversed: true + /// ) + /// ``` + /// + final bool isAxisInversed; + + /// The maximum number of labels to be displayed in 100 logical pixels. + /// + /// Defaults to 3. + /// + /// This snippet shows how to set [maximumLabels]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// maximumLabels: 6 + /// ) + /// ``` + /// + final int maximumLabels; + + /// Determines whether to mirror the linear gauge. + /// + /// Defaults to false. + /// + /// This snippet shows how to mirror the linear gauge. + /// in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// isMirrored: true + /// ) + /// ``` + /// + final bool isMirrored; + + /// Specifies the number format. + /// + /// Defaults to NumberFormat('#.##'). + /// + /// This snippet shows how to set number formatting for axis labels. + /// + /// ```dart + /// + /// SfLinearGauge( + /// numberFormat: NumberFormat("0", "en_US"), + /// ) + /// ``` + /// + final NumberFormat numberFormat; + + /// Returns the visible labels on [SfLinearGauge] + /// + /// Modify the actual labels. Generate your own labels based on needs, + /// in order to be shown in the gauge. + /// + /// This snippet shows how to set generate labels for axis in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// onGenerateLabels: () { + /// return [ + /// LinearAxisLabel(text: 'R', value: 0), + /// LinearAxisLabel(text: 'M', value: 10), + /// LinearAxisLabel(text: 'L', value: 20), + /// LinearAxisLabel(text: 'XL', value: 30),]; + /// }) + /// ``` + /// + final GenerateLabelsCallback? onGenerateLabels; + + /// Returns the converted factor value from the axis value + /// + /// The arguments to this method is an axis value. + /// The calculated value of the factor should be between 0 and 1. + /// If the axis ranges from 0 to 100 and pass the axis value is 50, + /// this method return factor value as 0.5. + /// Overriding method, you can modify the factor value based on needs. + final ValueToFactorCallback? valueToFactorCallback; + + /// Returns the converted axis value from the factor. + /// + /// The arguments to this method is factor which value between 0 to 1. + /// If the axis ranges from 0 to 100 and pass the factor value is 0.5, + /// this method will return the value as 50. + /// Overriding method, you can modify the axis value based on needs. + final FactorToValueCallback? factorToValueCallback; + + /// Called when the labels are been generated. + /// + /// This snippet shows how to set generate labels for an axis. + /// + /// ```dart + /// SfLinearGauge(onFormatLabel: (value) { + /// if(value == "100") { + /// return '°F'; + /// } + /// else { + /// return value + '°'; + /// }}) + /// + final LabelFormatterCallback? labelFormatterCallback; + + @override + State createState() => _SfLinearGaugeState(); +} + +class _SfLinearGaugeState extends State + with TickerProviderStateMixin { + AnimationController? _animationController; + Animation? _fadeAnimation; + + List? _oldBarPointerList; + List? _oldMarkerPointerList; + + late bool _isPointerAnimationStarted = false; + late List> _pointerAnimations; + late List _pointerAnimationControllers; + late List _linearGaugeWidgets; + + final _pointerAnimationStartValue = 0.5; + + @override + void initState() { + _pointerAnimationControllers = []; + _linearGaugeWidgets = []; + _updateOldList(); + _initializeAnimations(); + super.initState(); + } + + @override + void didUpdateWidget(covariant SfLinearGauge oldWidget) { + if (oldWidget.animateAxis != widget.animateAxis || + oldWidget.animateRange != widget.animateRange || + !_isEqualLists(widget.markerPointers, _oldMarkerPointerList) || + !_isEqualLists(widget.barPointers, _oldBarPointerList)) { + _updateOldList(); + _initializeAnimations(); + } + + super.didUpdateWidget(oldWidget); + } + + void _updateOldList() { + _oldBarPointerList = + (widget.barPointers != null) ? List.from(widget.barPointers!) : null; + _oldMarkerPointerList = (widget.markerPointers != null) + ? List.from(widget.markerPointers!) + : null; + } + + bool _isEqualLists(List? a, List? b) { + if (a == null) return b == null; + if (b == null || a.length != b.length) return false; + for (int index = 0; index < a.length; index++) { + if (a[index].enableAnimation != b[index].enableAnimation || + a[index].animationDuration != b[index].animationDuration || + a[index].animationType != b[index].animationType) { + return false; + } + } + + return true; + } + + /// Initialize the animations. + void _initializeAnimations() { + if (_animationController != null) { + _animationController!.removeListener(_axisAnimationListener); + } + + _disposeAnimationControllers(); + _isPointerAnimationStarted = false; + + if (widget.animateAxis || widget.animateRange) { + _animationController = AnimationController( + vsync: this, + duration: Duration(milliseconds: widget.animationDuration)); + _animationController!.addListener(_axisAnimationListener); + + _fadeAnimation = Tween(begin: 0, end: 1).animate(CurvedAnimation( + parent: _animationController!, + curve: Interval(0.05, 1.0, curve: Curves.ease))); + } else { + if (_animationController != null) { + _animationController!.removeListener(_axisAnimationListener); + _animationController = null; + } + + _fadeAnimation = null; + } + + _createPointerAnimations(); + _animateElements(); + } + + void _addPointerAnimation(int duration, LinearAnimationType animationType) { + final pointerController = AnimationController( + vsync: this, duration: Duration(milliseconds: duration)); + + final animation = Tween(begin: 0, end: 1).animate(CurvedAnimation( + parent: pointerController, + curve: Interval(0, 1, + curve: LinearGaugeHelper.getCurveAnimation(animationType)))); + + _pointerAnimations.add(animation); + _pointerAnimationControllers.add(pointerController); + } + + //Create animation for pointers. + void _createPointerAnimations() { + _pointerAnimations = []; + _pointerAnimationControllers.clear(); + + if (widget.barPointers != null && widget.barPointers!.isNotEmpty) { + ///Adding linear gauge range widgets. + for (final barPointer in widget.barPointers!) { + if (barPointer.position == LinearElementPosition.outside || + barPointer.position == LinearElementPosition.inside) { + if (barPointer.enableAnimation && barPointer.animationDuration > 0) { + _addPointerAnimation( + barPointer.animationDuration, barPointer.animationType); + } + } + } + } + + if (widget.barPointers != null && widget.barPointers!.isNotEmpty) { + ///Adding linear gauge range widgets. + for (final barPointer in widget.barPointers!) { + if (barPointer.position == LinearElementPosition.cross && + barPointer.enableAnimation && + barPointer.animationDuration > 0) { + _addPointerAnimation( + barPointer.animationDuration, barPointer.animationType); + } + } + } + + ///Adding linear gauge range widgets. + if (widget.markerPointers != null && widget.markerPointers!.isNotEmpty) { + for (final dynamic shapePointer in widget.markerPointers!) { + if (shapePointer.enableAnimation && + shapePointer.animationDuration > 0) { + _addPointerAnimation( + shapePointer.animationDuration, shapePointer.animationType); + } + } + } + } + + void _axisAnimationListener() { + if (_pointerAnimationStartValue <= _animationController!.value && + !_isPointerAnimationStarted) { + _isPointerAnimationStarted = true; + _animatePointers(); + } + } + + /// Animates the gauge elements. + void _animateElements() { + if (widget.animateAxis || widget.animateRange) { + _animationController!.forward(from: 0); + } else { + _animatePointers(); + } + } + + void _animatePointers() { + if (_pointerAnimationControllers.isNotEmpty) { + for (int i = 0; i < _pointerAnimationControllers.length; i++) { + _pointerAnimationControllers[i].forward(from: 0); + } + } + } + + void _addChild(Widget child, Animation? animation, + AnimationController? controller) { + _linearGaugeWidgets.add(LinearGaugeScope( + child: child, + animation: animation, + orientation: widget.orientation, + isAxisInversed: widget.isAxisInversed, + isMirrored: widget.isMirrored, + animationController: controller)); + } + + List _buildChildWidgets(BuildContext context) { + _linearGaugeWidgets.clear(); + int index = 0; + + if (widget.ranges != null && widget.ranges!.isNotEmpty) { + ///Adding linear gauge range widgets. + for (final range in widget.ranges!) { + if (range.position == LinearElementPosition.outside || + range.position == LinearElementPosition.inside) { + _addChild(range, (widget.animateRange ? _fadeAnimation : null), null); + } + } + } + + if (widget.barPointers != null && widget.barPointers!.isNotEmpty) { + ///Adding linear gauge range widgets. + for (final barPointer in widget.barPointers!) { + if (barPointer.position == LinearElementPosition.outside || + barPointer.position == LinearElementPosition.inside) { + if (barPointer.enableAnimation && barPointer.animationDuration > 0) { + _addChild(barPointer, _pointerAnimations[index], + _pointerAnimationControllers[index]); + index++; + } else { + _addChild(barPointer, null, null); + } + } + } + } + + /// Adding linear gauge axis widget. + _linearGaugeWidgets.add(LinearAxisRenderObjectWidget( + linearGauge: widget, + fadeAnimation: widget.animateAxis ? _fadeAnimation : null)); + + if (widget.ranges != null && widget.ranges!.isNotEmpty) { + /// Adding linear gauge range widgets. + for (final range in widget.ranges!) { + if (range.position == LinearElementPosition.cross) { + _addChild(range, (widget.animateRange ? _fadeAnimation : null), null); + } + } + } + + if (widget.barPointers != null && widget.barPointers!.isNotEmpty) { + /// Adding linear gauge range widgets. + for (final barPointer in widget.barPointers!) { + if (barPointer.position == LinearElementPosition.cross) { + if (barPointer.enableAnimation && barPointer.animationDuration > 0) { + _addChild(barPointer, _pointerAnimations[index], + _pointerAnimationControllers[index]); + index++; + } else { + _addChild(barPointer, null, null); + } + } + } + } + + if (widget.markerPointers != null && widget.markerPointers!.isNotEmpty) { + /// Adding linear gauge widget bar pointer element. + for (final dynamic pointer in widget.markerPointers!) { + if (pointer.enableAnimation && pointer.animationDuration > 0) { + _addChild(pointer, _pointerAnimations[index], + _pointerAnimationControllers[index]); + index++; + } else { + _addChild(pointer, null, null); + } + } + } + + return _linearGaugeWidgets; + } + + @override + Widget build(BuildContext context) { + return LinearGaugeRenderWidget( + children: _buildChildWidgets(context), + pointerAnimations: _pointerAnimations); + } + + void _disposeAnimationControllers() { + if (_animationController != null) { + _animationController!.dispose(); + } + + if (_pointerAnimationControllers.isNotEmpty) { + _pointerAnimationControllers.forEach((controller) { + controller.dispose(); + }); + } + } + + @override + void dispose() { + _disposeAnimationControllers(); + super.dispose(); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge_render_widget.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge_render_widget.dart new file mode 100644 index 000000000..92edde79c --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge_render_widget.dart @@ -0,0 +1,974 @@ +import 'dart:math' as math; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart' + show + DragStartBehavior, + GestureArenaTeam, + HorizontalDragGestureRecognizer, + TapGestureRecognizer, + VerticalDragGestureRecognizer; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../linear_gauge/axis/linear_axis_renderer.dart'; +import '../../linear_gauge/pointers/linear_bar_renderer.dart'; +import '../../linear_gauge/pointers/linear_shape_renderer.dart'; +import '../../linear_gauge/pointers/linear_widget_renderer.dart'; +import '../../linear_gauge/range/linear_gauge_range_renderer.dart'; +import '../../linear_gauge/utils/enum.dart'; +import '../../linear_gauge/utils/linear_gauge_helper.dart'; + +/// Default widget height. +/// This value is used when the parent layout height is infinite for the linear gauge. +const double kDefaultLinearGaugeHeight = 400.0; + +/// Default widget width. +/// This value is used when the parent layout width is infinite for the linear gauge. +const double kDefaultLinearGaugeWidth = 300.0; + +/// Linear gauge render widget class. +class LinearGaugeRenderWidget extends MultiChildRenderObjectWidget { + /// Creates instance for [LinearGaugeRenderWidget] class. + LinearGaugeRenderWidget( + {Key? key, + required this.pointerAnimations, + required List children}) + : super(key: key, children: children); + + /// Linear gauge pointer animations. + final List> pointerAnimations; + + RenderObject createRenderObject(BuildContext context) => + RenderLinearGauge(pointerAnimations: pointerAnimations); + + @override + void updateRenderObject( + BuildContext context, RenderLinearGauge renderObject) { + renderObject..pointerAnimations = pointerAnimations; + super.updateRenderObject(context, renderObject); + } + + @override + MultiChildRenderObjectElement createElement() => + LinearGaugeRenderElement(this); +} + +/// Linear gauge render widget element class. +class LinearGaugeRenderElement extends MultiChildRenderObjectElement { + /// Creates a instance for Linear gauge render widget element class. + LinearGaugeRenderElement(MultiChildRenderObjectWidget widget) : super(widget); + + @override + // ignore: avoid_as + RenderLinearGauge get renderObject => super.renderObject as RenderLinearGauge; + + @override + void insertRenderObjectChild(RenderObject child, IndexedSlot slot) { + super.insertRenderObjectChild(child, slot); + if (child is RenderLinearRange) { + renderObject.addRange(child); + } else if (child is RenderLinearAxis) { + renderObject.axis = child; + } else if (child is RenderLinearBarPointer) { + renderObject.addBarPointer(child); + } else if (child is RenderLinearShapePointer) { + renderObject.addShapePointer(child); + } else if (child is RenderLinearWidgetPointer) { + renderObject.addWidgetPointer(child); + } + } + + @override + void moveRenderObjectChild(RenderObject child, IndexedSlot oldSlot, + IndexedSlot newSlot) { + super.moveRenderObjectChild(child, oldSlot, newSlot); + } + + @override + void removeRenderObjectChild(RenderObject child, dynamic slot) { + super.removeRenderObjectChild(child, slot); + if (child is RenderLinearRange) { + renderObject.removeRange(child); + } else if (child is RenderLinearAxis) { + renderObject.axis = null; + } else if (child is RenderLinearBarPointer) { + renderObject.removeBarPointer(child); + } else if (child is RenderLinearShapePointer) { + renderObject.removeShapePointer(child); + } else if (child is RenderLinearWidgetPointer) { + renderObject.removeWidgetPointer(child); + } + } +} + +/// Linear gauge render object class. +class RenderLinearGauge extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin, + DebugOverflowIndicatorMixin { + /// Creates a render object. + /// + /// By default, the non-positioned children of the stack are aligned by their + /// top left corners. + RenderLinearGauge({required List> pointerAnimations}) + : _gestureArenaTeam = GestureArenaTeam(), + _pointerAnimations = pointerAnimations { + _ranges = []; + _barPointers = []; + _shapePointers = []; + _widgetPointers = []; + _markerPointers = []; + _verticalDragGestureRecognizer = VerticalDragGestureRecognizer() + ..team = _gestureArenaTeam + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..dragStartBehavior = DragStartBehavior.start; + + _horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer() + ..team = _gestureArenaTeam + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..dragStartBehavior = DragStartBehavior.start; + + _tapGestureRecognizer = TapGestureRecognizer()..onTapDown = _handleTapDown; + } + + final GestureArenaTeam _gestureArenaTeam; + + late TapGestureRecognizer _tapGestureRecognizer; + late VerticalDragGestureRecognizer _verticalDragGestureRecognizer; + late HorizontalDragGestureRecognizer _horizontalDragGestureRecognizer; + + double _axisLineActualSize = 0, + _axisTop = 0, + _axisWidgetThickness = 0, + _insideWidgetElementSize = 0, + _outsideWidgetElementSize = 0, + _pointerStartPadding = 0, + _pointerEndPadding = 0, + _pointX = 0, + _pointY = 0; + + double? _actualSizeDelta, _overflow; + + bool _isAxisInversed = false; + bool _isHorizontalOrientation = true; + bool _isMarkerPointerInteraction = false; + bool _restrictHitTestPointerChange = false; + + late List _barPointers; + late List _shapePointers; + late List _widgetPointers; + late List _ranges; + late List _markerPointers; + + late BoxConstraints _parentConstraints; + late BoxConstraints _childConstraints; + + late dynamic _markerRenderObject; + + /// Gets the axis assigned to [_RenderLinearGaugeRenderObject]. + RenderLinearAxis? get axis => _axis; + RenderLinearAxis? _axis; + + /// Sets the axis for [_RenderLinearGaugeRenderObject]. + set axis(RenderLinearAxis? value) { + if (value == _axis) { + return; + } + + _axis = value; + _updateAxis(); + markNeedsLayout(); + } + + /// Gets the pointer animations assigned to [_RenderLinearGaugeRenderObject]. + List> get pointerAnimations => _pointerAnimations; + List> _pointerAnimations; + + /// Sets the pointer animations for [_RenderLinearGaugeRenderObject]. + set pointerAnimations(List> value) { + if (value == _pointerAnimations) { + return; + } + + _removePointerAnimationListeners(); + _pointerAnimations = value; + _addPointerAnimationListeners(); + markNeedsLayout(); + } + + /// Adds the range render object to range collection. + void addRange(RenderLinearRange range) { + _ranges.add(range..axis = axis); + markNeedsLayout(); + } + + /// Removes the range render object from range collection. + void removeRange(RenderLinearRange range) { + _ranges.remove(range..axis = null); + markNeedsLayout(); + } + + /// Adds the bar render object to bar pointer collection. + void addBarPointer(RenderLinearBarPointer barPointer) { + _barPointers.add(barPointer..axis = axis); + markNeedsLayout(); + } + + /// Removes the bar render object from bar pointer collection. + void removeBarPointer(RenderLinearBarPointer barPointer) { + _barPointers.remove(barPointer..axis = null); + markNeedsLayout(); + } + + /// Adds the shape render object to bar pointer collection. + void addShapePointer(RenderLinearShapePointer shapePointer) { + _shapePointers.add(shapePointer); + markNeedsLayout(); + } + + /// Removes the shape render object from bar pointer collection. + void removeShapePointer(RenderLinearShapePointer shapePointer) { + _shapePointers.remove(shapePointer); + markNeedsLayout(); + } + + /// Adds the widget render object to widget pointer collection. + void addWidgetPointer(RenderLinearWidgetPointer shapePointer) { + _widgetPointers.add(shapePointer); + markNeedsLayout(); + } + + /// Removes the widget render object from widget pointer collection. + void removeWidgetPointer(RenderLinearWidgetPointer shapePointer) { + _widgetPointers.remove(shapePointer..pointerAnimation = null); + markNeedsLayout(); + } + + void _updateAxis() { + _ranges.forEach((element) { + element..axis = axis; + }); + + _barPointers.forEach((element) { + element..axis = axis; + }); + + if (axis != null) { + _isHorizontalOrientation = + _axis!.orientation == LinearGaugeOrientation.horizontal; + } + } + + @override + void setupParentData(RenderBox child) { + if (child.parentData is! MultiChildLayoutParentData) { + child.parentData = MultiChildLayoutParentData(); + } + } + + /// Measures the inside elements size. + void _measureInsideElementSize(double thickness) { + final double labelSize = axis!.getEffectiveLabelSize(); + final double tickSize = axis!.getTickSize(); + final double axisLineSize = axis!.getAxisLineThickness(); + final position = LinearGaugeHelper.getEffectiveElementPosition( + axis!.tickPosition, axis!.isMirrored); + final labelPosition = LinearGaugeHelper.getEffectiveLabelPosition( + axis!.labelPosition, axis!.isMirrored); + final bool isInsideLabel = labelPosition == LinearLabelPosition.inside; + + late double _insideElementSize; + + switch (position) { + case LinearElementPosition.inside: + if (isInsideLabel) { + _insideElementSize = (_axisWidgetThickness - axisLineSize) > thickness + ? 0 + : thickness - (_axisWidgetThickness - axisLineSize); + } else { + _insideElementSize = thickness - tickSize; + } + break; + case LinearElementPosition.outside: + if (isInsideLabel) { + _insideElementSize = thickness - labelSize; + } else { + _insideElementSize = thickness; + } + break; + case LinearElementPosition.cross: + if (isInsideLabel) { + _insideElementSize = (_axisWidgetThickness - axisLineSize) > thickness + ? 0 + : thickness - (_axisWidgetThickness - axisLineSize); + } else { + _insideElementSize = (axis!.showLabels ? thickness : 0) - + (axisLineSize < tickSize ? (tickSize - axisLineSize) / 2 : 0); + } + break; + default: + break; + } + + _insideWidgetElementSize = + math.max(_insideWidgetElementSize, _insideElementSize); + } + + void _updateInsideElementSize(double thickness) { + if (!axis!.showTicks && !axis!.showLabels) { + _insideWidgetElementSize = math.max(thickness, _insideWidgetElementSize); + } else if (thickness > _axisWidgetThickness) { + _insideWidgetElementSize = + math.max(thickness - _axisWidgetThickness, _insideWidgetElementSize); + } + } + + /// Measure the parent size based on cross element. + void _measureCrossElementSize(double thickness) { + final double labelSize = axis!.getEffectiveLabelSize(); + final double tickSize = axis!.getTickSize(); + final double axisSize = axis!.getAxisLineThickness(); + final position = LinearGaugeHelper.getEffectiveElementPosition( + axis!.tickPosition, axis!.isMirrored); + final labelPlacement = LinearGaugeHelper.getEffectiveLabelPosition( + axis!.labelPosition, axis!.isMirrored); + final bool isInsideLabel = labelPlacement == LinearLabelPosition.inside; + + switch (position) { + case LinearElementPosition.inside: + if (axisSize == 0) { + _updateInsideElementSize(thickness); + } else if (axisSize < thickness) { + final double sizeDifference = + (thickness - axisSize) / 2 - (isInsideLabel ? 0 : labelSize); + _outsideWidgetElementSize = + math.max(sizeDifference, _outsideWidgetElementSize); + + if (_axisWidgetThickness < thickness) { + double axisSizeDifference = (thickness - axisSize) / 2; + axisSizeDifference = axisSizeDifference - + (tickSize + (isInsideLabel ? labelSize : 0)); + _insideWidgetElementSize = + math.max(axisSizeDifference, _insideWidgetElementSize); + } + } + break; + case LinearElementPosition.outside: + if (axisSize == 0) { + _updateInsideElementSize(thickness); + } else if (axisSize < thickness) { + final double sizeDifference = (thickness - axisSize) / 2 - + (tickSize + (isInsideLabel ? 0 : labelSize)); + _outsideWidgetElementSize = + math.max(sizeDifference, _outsideWidgetElementSize); + + double axisSizeDifference = (thickness - axisSize) / 2; + axisSizeDifference = + axisSizeDifference - (isInsideLabel ? labelSize : 0); + _insideWidgetElementSize = + math.max(axisSizeDifference, _insideWidgetElementSize); + } + break; + case LinearElementPosition.cross: + if (axisSize == 0) { + _updateInsideElementSize(thickness); + } else if (tickSize > axisSize && axisSize < thickness) { + final double sizeDifference = ((thickness - axisSize) / 2) - + ((tickSize - axisSize) / 2) - + (isInsideLabel ? 0 : labelSize); + _outsideWidgetElementSize = + math.max(sizeDifference, _outsideWidgetElementSize); + + if (_axisWidgetThickness < thickness) { + double axisSizeDifference = + ((thickness - axisSize) / 2) - ((tickSize - axisSize) / 2); + axisSizeDifference = + axisSizeDifference - (isInsideLabel ? labelSize : 0); + _insideWidgetElementSize = + math.max(axisSizeDifference, _insideWidgetElementSize); + } + } else if (axisSize < thickness) { + final double sizeDifference = + ((thickness - axisSize) / 2) - (isInsideLabel ? 0 : labelSize); + + _outsideWidgetElementSize = + math.max(sizeDifference, _outsideWidgetElementSize); + + if (_axisWidgetThickness < thickness) { + double axisSizeDifference = (thickness - axisSize) / 2; + axisSizeDifference = + axisSizeDifference - (isInsideLabel ? labelSize : 0); + _insideWidgetElementSize = + math.max(axisSizeDifference, _insideWidgetElementSize); + } + } + break; + default: + break; + } + } + + /// Layout the pointer child. + void _layoutPointerChild( + {required RenderObject renderObject, + required LinearElementPosition position, + required double thickness, + required double offset}) { + final pointerPosition = LinearGaugeHelper.getEffectiveElementPosition( + position, axis!.isMirrored); + + switch (pointerPosition) { + case LinearElementPosition.inside: + _measureInsideElementSize(thickness + offset); + break; + case LinearElementPosition.outside: + _outsideWidgetElementSize = + math.max(_outsideWidgetElementSize, thickness + offset - _axisTop); + break; + case LinearElementPosition.cross: + _measureCrossElementSize(thickness); + break; + default: + break; + } + } + + /// Position the child elements. + void _positionChildElement(RenderObject linearGaugeChild, + {double thickness = 0}) { + final MultiChildLayoutParentData? childParentData = + // ignore: avoid_as + linearGaugeChild.parentData as MultiChildLayoutParentData?; + final xPoint = _pointX + axis!.getChildPadding(child: linearGaugeChild); + final yPoint = _pointY; + + if (_isHorizontalOrientation) { + childParentData!.offset = + Offset(xPoint - (_isAxisInversed ? thickness : 0), yPoint); + } else { + childParentData!.offset = + Offset(yPoint, xPoint - (!_isAxisInversed ? thickness : 0)); + } + } + + /// Calculates the marker pointer offset. + double? _calculateMarkerOffset( + {required LinearElementPosition elementPosition, + required double offset, + required Size size}) { + final double markerSize = + _isHorizontalOrientation ? size.height : size.width; + final pointerPosition = LinearGaugeHelper.getEffectiveElementPosition( + elementPosition, axis!.isMirrored); + switch (pointerPosition) { + case LinearElementPosition.inside: + return _outsideWidgetElementSize + + _axisTop + + axis!.getAxisLineThickness() + + offset + + (_actualSizeDelta! / 2); + case LinearElementPosition.outside: + return (_actualSizeDelta! / 2) + + (offset * -1) + + (_outsideWidgetElementSize + _axisTop > markerSize + ? _outsideWidgetElementSize + _axisTop - markerSize + : 0); + case LinearElementPosition.cross: + return _outsideWidgetElementSize + + _getCrossElementPosition(markerSize) + + (_actualSizeDelta! / 2); + default: + break; + } + + return null; + } + + double _getCrossElementPosition(double width) { + final double axisSize = axis!.getAxisLineThickness(); + if (axisSize == 0) return axisSize; + + if (axisSize > width) { + return (axisSize - width) / 2 + _axisTop; + } else if (axisSize < width) { + return _axisTop - ((width - axisSize) / 2); + } else { + return _axisTop; + } + } + + void _updatePointerPositionOnDrag(dynamic pointer, + {bool isDragCall = false}) { + double animationValue = 1; + + if (!isDragCall) { + if (pointer.pointerAnimation != null) { + animationValue = pointer.pointerAnimation.value; + } + } + + final startPosition = + (axis!.valueToPixel(pointer.oldValue ?? axis!.minimum)); + final endPosition = (axis!.valueToPixel(pointer.value)).abs(); + + _pointX = startPosition + ((endPosition - startPosition) * animationValue); + _pointY = _calculateMarkerOffset( + elementPosition: pointer.position, + offset: pointer.offset, + size: Size(pointer.size.width, pointer.size.height))!; + + _positionChildElement(pointer); + } + + void _updatePointerPosition() { + if (_actualSizeDelta != null) { + _positionMarkerPointers(); + markNeedsPaint(); + } + } + + void _resetValues() { + _axisWidgetThickness = 0; + _insideWidgetElementSize = 0; + _outsideWidgetElementSize = 0; + + _isAxisInversed = axis!.isAxisInversed; + _isHorizontalOrientation = + _axis!.orientation == LinearGaugeOrientation.horizontal; + + _pointerStartPadding = 0; + _pointerEndPadding = 0; + + _markerPointers.clear(); + _markerPointers = + [_shapePointers, _widgetPointers].expand((x) => x).toList(); + + final width = constraints.hasBoundedWidth + ? constraints.maxWidth + : kDefaultLinearGaugeWidth; + final height = constraints.hasBoundedHeight + ? constraints.maxHeight + : kDefaultLinearGaugeHeight; + _parentConstraints = BoxConstraints(maxWidth: width, maxHeight: height); + } + + /// Layout the marker pointers. + void _layoutMarkerPointers() { + if (_markerPointers.isNotEmpty) { + for (final dynamic pointer in _markerPointers) { + pointer.layout(_parentConstraints, parentUsesSize: true); + + final thickness = (_isHorizontalOrientation + ? pointer.size.width + : pointer.size.height); + + if (pointer.markerAlignment == LinearMarkerAlignment.start) { + _pointerEndPadding = math.max(_pointerEndPadding, thickness); + } else if (pointer.markerAlignment == LinearMarkerAlignment.center) { + _pointerStartPadding = math.max(_pointerStartPadding, thickness / 2); + _pointerEndPadding = math.max(_pointerEndPadding, thickness / 2); + } else { + _pointerStartPadding = math.max(_pointerStartPadding, thickness); + } + } + } + } + + void _updateAxisValues() { + _axisWidgetThickness = + _isHorizontalOrientation ? axis!.size.height : axis!.size.width; + _axisTop = axis!.axisOffset; + _axisLineActualSize = + (axis!.valueToPixel(axis!.maximum) - axis!.valueToPixel(axis!.minimum)) + .abs(); + } + + BoxConstraints _getChildBoxConstraints() { + final padding = axis!.getAxisLayoutPadding(); + if (_isHorizontalOrientation) { + return BoxConstraints( + maxWidth: _parentConstraints.maxWidth - padding, + maxHeight: _parentConstraints.maxHeight); + } else { + return BoxConstraints( + maxWidth: _parentConstraints.maxWidth, + maxHeight: _parentConstraints.maxHeight - padding); + } + } + + void _updateAxisBasicRenderingDetails() { + axis!.pointerStartPadding = _pointerStartPadding; + axis!.pointerEndPadding = _pointerEndPadding; + axis!.generateLabels(_parentConstraints); + _childConstraints = _getChildBoxConstraints(); + } + + /// Layout the axis element. + void _layoutAxis() { + if (axis != null) { + _updateAxisBasicRenderingDetails(); + axis!.layout(_childConstraints, parentUsesSize: true); + _updateAxisValues(); + } + } + + /// Layout the range elements. + void _layoutRange() { + if (_ranges.isNotEmpty) { + for (final range in _ranges) { + range.layout(_childConstraints, parentUsesSize: true); + final rangeThickness = + _isHorizontalOrientation ? range.size.height : range.size.width; + final position = LinearGaugeHelper.getEffectiveElementPosition( + range.position, range.isMirrored); + + switch (position) { + case LinearElementPosition.inside: + _measureInsideElementSize(rangeThickness); + break; + case LinearElementPosition.outside: + _outsideWidgetElementSize = + math.max(_outsideWidgetElementSize, rangeThickness - _axisTop); + break; + case LinearElementPosition.cross: + _measureCrossElementSize(rangeThickness); + break; + default: + break; + } + } + } + } + + /// Layout the bar pointers. + void _layoutBarPointers() { + if (_barPointers.isNotEmpty) { + for (final barPointer in _barPointers) { + barPointer.layout(_childConstraints, parentUsesSize: true); + + _layoutPointerChild( + renderObject: barPointer, + position: barPointer.position, + thickness: barPointer.thickness, + offset: barPointer.offset); + } + } + } + + void _measureMarkerPointersSize() { + if (_markerPointers.isNotEmpty) { + for (final dynamic markerPointer in _markerPointers) { + final thickness = (_isHorizontalOrientation + ? markerPointer.size.height + : markerPointer.size.width); + + _layoutPointerChild( + renderObject: markerPointer, + position: markerPointer.position, + thickness: thickness, + offset: markerPointer.offset); + } + } + } + + Size _getParentLayoutSize() { + /// Layout parent. + double actualHeight, actualWidth; + + if (_isHorizontalOrientation) { + actualHeight = constraints.hasBoundedHeight + ? constraints.maxHeight + : _axisWidgetThickness + + _outsideWidgetElementSize + + _insideWidgetElementSize; + actualWidth = _parentConstraints.maxWidth; + } else { + actualHeight = _parentConstraints.maxHeight; + actualWidth = constraints.hasBoundedWidth + ? constraints.maxWidth + : _axisWidgetThickness + + _outsideWidgetElementSize + + _insideWidgetElementSize; + } + + _actualSizeDelta = (_isHorizontalOrientation ? actualHeight : actualWidth) - + (_axisWidgetThickness + + _outsideWidgetElementSize + + _insideWidgetElementSize); + + return Size(actualWidth, actualHeight); + } + + /// Position the linear gauge axis. + void _positionAxis() { + _overflow = math.max(0.0, -_actualSizeDelta!); + if (axis != null) { + final MultiChildLayoutParentData? childParentData = + // ignore: avoid_as + axis!.parentData as MultiChildLayoutParentData?; + _pointX = axis!.getAxisPositionPadding(); + _pointY = _outsideWidgetElementSize + _actualSizeDelta! / 2; + + if (_isHorizontalOrientation) { + childParentData!.offset = Offset(_pointX, _pointY); + } else { + childParentData!.offset = Offset(_pointY, _pointX); + } + } + } + + /// Position the range elements. + void _positionRanges() { + if (_ranges.isNotEmpty) { + for (final range in _ranges) { + final thickness = + _isHorizontalOrientation ? range.size.height : range.size.width; + final rangeWidth = + _isHorizontalOrientation ? range.size.width : range.size.height; + + _pointX = axis!.valueToPixel(range.startValue).abs(); + + final position = LinearGaugeHelper.getEffectiveElementPosition( + range.position, range.isMirrored); + final double axisSize = axis!.showAxisTrack ? axis!.thickness : 0.0; + + switch (position) { + case LinearElementPosition.inside: + _pointY = _outsideWidgetElementSize + + _axisTop + + axisSize + + (_actualSizeDelta! / 2); + break; + case LinearElementPosition.outside: + final positionY = thickness < _outsideWidgetElementSize + _axisTop + ? _outsideWidgetElementSize + _axisTop - thickness + : 0; + _pointY = positionY + (_actualSizeDelta! / 2); + break; + case LinearElementPosition.cross: + _pointY = _outsideWidgetElementSize + + (_actualSizeDelta! / 2) + + _getCrossElementPosition(thickness); + break; + default: + break; + } + + _positionChildElement(range, thickness: rangeWidth); + } + } + } + + /// Position the bar pointers. + void _positionBarPointers() { + if (_barPointers.isNotEmpty) { + for (final barPointer in _barPointers) { + _pointX = axis!.valueToPixel(axis!.minimum).abs(); + + final barWidth = _isHorizontalOrientation + ? barPointer.size.width + : barPointer.size.height; + + final position = LinearGaugeHelper.getEffectiveElementPosition( + barPointer.position, axis!.isMirrored); + + switch (position) { + case LinearElementPosition.inside: + _pointY = _outsideWidgetElementSize + + _axisTop + + axis!.getAxisLineThickness() + + barPointer.offset + + (_actualSizeDelta! / 2); + break; + case LinearElementPosition.outside: + _pointY = (_actualSizeDelta! / 2) + + (barPointer.offset * -1) + + (_outsideWidgetElementSize + _axisTop > barPointer.thickness + ? _outsideWidgetElementSize + + _axisTop - + barPointer.thickness + : 0); + break; + case LinearElementPosition.cross: + _pointY = _outsideWidgetElementSize + + (_actualSizeDelta! / 2) + + _getCrossElementPosition(barPointer.thickness); + break; + default: + break; + } + + _positionChildElement(barPointer, thickness: barWidth); + } + } + } + + /// Position the marker pointers. + void _positionMarkerPointers() { + if (_markerPointers.isNotEmpty) { + for (final dynamic pointer in _markerPointers) { + _updatePointerPositionOnDrag(pointer); + } + } + } + + void _addPointerAnimationListeners() { + if (_pointerAnimations.isNotEmpty) { + for (final animation in _pointerAnimations) { + animation.addListener(_updatePointerPosition); + } + } + } + + void _removePointerAnimationListeners() { + if (_pointerAnimations.isNotEmpty) { + for (final animation in _pointerAnimations) { + animation.removeListener(_updatePointerPosition); + } + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _addPointerAnimationListeners(); + } + + @override + void detach() { + _removePointerAnimationListeners(); + super.detach(); + } + + @override + void performLayout() { + _resetValues(); + + // Layout the widget pointers first to determine the padding size. + _layoutMarkerPointers(); + _layoutAxis(); + _layoutRange(); + _layoutBarPointers(); + _measureMarkerPointersSize(); + + size = _getParentLayoutSize(); + + _positionAxis(); + _positionRanges(); + _positionBarPointers(); + _positionMarkerPointers(); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + final isHit = super.defaultHitTestChildren(result, position: position); + + if (isHit && !_restrictHitTestPointerChange) { + final child = result.path.last.target; + + if (child is RenderLinearShapePointer || + child is RenderLinearWidgetPointer) { + _isMarkerPointerInteraction = true; + _markerRenderObject = child; + } else { + _isMarkerPointerInteraction = false; + } + } + + return isHit; + } + + ///Get the value from position. + double _getValueFromPosition(Offset localPosition) { + final double actualAxisPadding = axis!.getChildPadding(); + + double visualPosition; + + if (_isHorizontalOrientation) { + visualPosition = + (localPosition.dx - actualAxisPadding) / _axisLineActualSize; + } else { + visualPosition = + (localPosition.dy - actualAxisPadding) / _axisLineActualSize; + } + + return axis!.factorToValue(visualPosition.clamp(0.0, 1.0)); + } + + /// Handles the drag update callback. + void _handleDragUpdate(DragUpdateDetails details) { + final _currentValue = _getValueFromPosition(details.localPosition); + if (_markerRenderObject.onValueChanged != null && + _markerRenderObject.value != _currentValue) { + _markerRenderObject.oldValue = _currentValue; + _markerRenderObject.onValueChanged(_currentValue); + _updatePointerPositionOnDrag(_markerRenderObject, isDragCall: true); + markNeedsPaint(); + } + } + + void _handleDragStart(DragStartDetails details) {} + + void _handleTapDown(TapDownDetails details) {} + + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + assert(debugHandleEvent(event, entry)); + if (event is PointerDownEvent && _isMarkerPointerInteraction) { + _restrictHitTestPointerChange = true; + _tapGestureRecognizer.addPointer(event); + _horizontalDragGestureRecognizer.addPointer(event); + _verticalDragGestureRecognizer.addPointer(event); + } else if (event is PointerUpEvent || event is PointerCancelEvent) { + _restrictHitTestPointerChange = false; + } + + super.handleEvent(event, entry); + } + + @override + void paint(PaintingContext context, Offset offset) { + context.pushClipRect( + needsCompositing, offset, Rect.fromLTWH(0, 0, size.width, size.height), + (context, offset) { + defaultPaint(context, offset); + // There's no point in drawing the children if we're empty. + if (size.isEmpty) { + return; + } + + assert(() { + // Only set this if it's null to save work. It gets reset to null if the + // _direction changes. + final List debugOverflowHints = [ + ErrorDescription( + 'The edge of the $runtimeType that is overflowing has been marked ' + 'in the rendering with a yellow and black striped pattern. This is ' + 'usually caused by the contents being too big for the $runtimeType.'), + ]; + + // Simulate a child rect that overflows by the right amount. This child + // rect is never used for drawing, just for determining the overflow + // location and amount. + Rect overflowChildRect; + + if (_isHorizontalOrientation) { + overflowChildRect = + Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow!); + } else { + overflowChildRect = + Rect.fromLTWH(0.0, 0.0, size.width + _overflow!, 0.0); + } + + paintOverflowIndicator( + context, offset, Offset.zero & size, overflowChildRect, + overflowHints: debugOverflowHints); + return true; + }()); + }); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge_scope.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge_scope.dart new file mode 100644 index 000000000..ad14b4019 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge_scope.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_gauges/src/linear_gauge/utils/enum.dart'; + +import '../../linear_gauge/gauge/linear_gauge.dart'; + +/// Linear gauge scope class. +class LinearGaugeScope extends InheritedWidget { + /// Creates a object for Linear gauge scope. + const LinearGaugeScope( + {Key? key, + required Widget child, + required this.orientation, + required this.isMirrored, + required this.isAxisInversed, + this.animation, + this.animationController}) + : super(key: key, child: child); + + /// Child animation. + final Animation? animation; + + /// Animation controller. + final AnimationController? animationController; + + /// Specifies the orientation of [SfLinearGauge]. + final LinearGaugeOrientation orientation; + + /// Determines whether to mirror the axis elements. + final bool isMirrored; + + /// Determines whether to invert the axis in [SfLinearGauge]. + final bool isAxisInversed; + + ///LinearGaugeScope method. + static LinearGaugeScope of(BuildContext context) { + late LinearGaugeScope scope; + + final widget = context + .getElementForInheritedWidgetOfExactType()! + .widget; + + if (widget is LinearGaugeScope) { + scope = widget; + } + + return scope; + } + + @override + bool updateShouldNotify(LinearGaugeScope old) { + return (orientation != old.orientation || + isMirrored != old.isMirrored || + isAxisInversed != old.isAxisInversed || + animationController != old.animationController || + animation != old.animation); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_pointer.dart new file mode 100644 index 000000000..13c5ef49d --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_pointer.dart @@ -0,0 +1,314 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../linear_gauge/gauge/linear_gauge_scope.dart'; +import '../../linear_gauge/pointers/linear_bar_renderer.dart'; +import '../../linear_gauge/utils/enum.dart'; + +/// [LinearBarPointer] has properties for customizing the linear gauge bar pointer. +class LinearBarPointer extends SingleChildRenderObjectWidget { + /// Creates a new instance for [LinearBarPointer]. + LinearBarPointer( + {Key? key, + required this.value, + this.enableAnimation = true, + this.animationDuration = 1000, + this.animationType = LinearAnimationType.ease, + this.onAnimationCompleted, + this.thickness = 5.0, + double offset = 0, + this.edgeStyle = LinearEdgeStyle.bothFlat, + this.position = LinearElementPosition.cross, + this.shaderCallback, + this.color, + this.borderColor, + this.borderWidth = 0, + Widget? child}) + : offset = offset > 0 ? offset : 0, + super(key: key, child: child); + + /// Specifies the pointer value of [barPointer]. + /// This value must be between the min and max value of an axis track. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set value for pointer in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// barPointers: [ + /// LinearBarPointer( + /// value: 50, + /// )]) + /// + /// ``` + /// + final double value; + + /// Specifies the edge style to a bar pointer. + /// + /// Defaults to [LinearEdgeStyle.bothFlat]. + /// + /// This snippet shows how to set edge style to a bar pointer. + /// + /// ```dart + /// + /// SfLinearGauge( + /// barPointers: [ + /// LinearBarPointer( + /// edgeStyle: LinearEdgeStyle.endCurve, + /// )]) + /// + /// ``` + /// + final LinearEdgeStyle edgeStyle; + + /// Called to get the gradient color for the bar pointer. + /// + /// Defaults to null. + /// + /// This snippet shows how to use a shaderCallback to get the colors for bar pointer. + /// + /// ```dart + /// + /// LinearBarPointer( + /// shaderCallback: (Rect bounds) { + /// return LinearGradient(colors: [ + /// Color(0xffE8DA5D), + /// Color(0xffFB7D55), + /// ], stops: [ + /// 0.4, + /// 0.9 + /// ]).createShader(bounds); + /// ) + /// ``` + /// + final ShaderCallback? shaderCallback; + + /// Specifies the solid color of bar pointers. + /// + /// Defaults to Color(0xff0074E3). + /// + /// This snippet shows how to set the color for bar pointer. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// barPointers: [ + /// LinearBarPointer( + /// color: Colors.Red, + /// )]) + /// ``` + /// + final Color? color; + + /// Specifies the border color for bar pointer. + /// + /// Defaults to Color(0xff0074E3). + /// + /// This snippet shows how to set border color for bar pointers in + /// [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// barPointers: [ + /// LinearBarPointer( + /// borderColor: Colors.Blue, + /// )]) + /// ``` + /// + final Color? borderColor; + + /// Specifies the border width of bar pointer. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set border width for bar pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// barPointers: [ + /// LinearBarPointer( + /// borderWidth: 2, + /// )]) + /// ``` + /// + final double borderWidth; + + /// Specifies the thickness of bar pointer. + /// + /// Defaults to 5.0. + /// + /// This snippet shows how to set thickness of bar pointer. + /// + /// ```dart + /// SfLinearGauge ( + /// barPointers: [ + /// LinearBarPointer( + /// value: 40 + /// thickness: 80, + /// )]) + /// ``` + /// + final double thickness; + + /// Specifies the offset for bar pointer with respect to axis. + /// The offset cannot be negative. + /// The offset value will not be effective when the bar pointer is in cross position. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set margin value for bar pointer. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// barPointers: [ + /// LinearBarPointer( + /// value: 40 + /// offset: 10, + /// )]) + /// ``` + /// + final double offset; + + /// Specifies position of bar pointer with respect to axis. + /// + /// Defaults to [LinearElementPosition.cross]. + /// + /// This snippet shows how to set the bar pointer position in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// barPointers: [ + /// LinearBarPointer( + /// value: 40 + /// position: LinearElementPosition.outside, + /// )]) + /// ``` + /// + final LinearElementPosition position; + + /// Specifies the loading animation for bar pointers with [animationDuration]. + /// + /// Defaults to true. + /// + /// This snippet shows how to set load time animation in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// barPointers: [ + /// LinearBarPointer( + /// value: 20 + /// enableAnimation: true, + /// )]) + /// ``` + /// + final bool enableAnimation; + + /// Specifies the load time animation duration with [enableAnimation]. + /// Duration is defined in milliseconds. + /// + /// Defaults to 1000. + /// + /// This snippet shows how to set animation duration for bar pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// barPointers: [ + /// LinearBarPointer( + /// value: 20 + /// enableAnimation: true, + /// animationDuration: 4000 + /// )]) + /// ``` + /// + final int animationDuration; + + /// Specifies the animation type for bar pointers. + /// + /// Defaults to [LinearAnimationType.ease]. + /// + /// This snippet shows how to set animation type for bar pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// barPointers: [ + /// LinearBarPointer( + /// value: 20 + /// enableAnimation: true, + /// animationType: LinearAnimationType.bounceOut + /// )]) + /// ``` + /// + final LinearAnimationType animationType; + + /// Called when the bar pointer animation is completed. + /// + /// Defaults to null. + /// + /// This snippet shows how to use this [onAnimationCompleted] callback. + /// + /// ```dart + /// SfLinearGauge( + /// barPointer:[ + /// LinearBarPointer( + /// onAnimationCompleted: ()=> { + /// printf("Bar Pointer animation is completed"); + /// }, + /// )]); + /// ``` + final VoidCallback? onAnimationCompleted; + + @override + RenderObject createRenderObject(BuildContext context) { + final linearGaugeScope = LinearGaugeScope.of(context); + return RenderLinearBarPointer( + value: value, + edgeStyle: edgeStyle, + shaderCallback: shaderCallback, + color: color ?? Color(0xff0074E3), + borderColor: borderColor ?? Color(0xff0074E3), + borderWidth: borderWidth, + thickness: thickness, + offset: offset, + position: position, + orientation: linearGaugeScope.orientation, + isAxisInversed: linearGaugeScope.isAxisInversed, + onAnimationCompleted: onAnimationCompleted, + animationController: linearGaugeScope.animationController, + pointerAnimation: linearGaugeScope.animation); + } + + @override + void updateRenderObject( + BuildContext context, RenderLinearBarPointer renderObject) { + final linearGaugeScope = LinearGaugeScope.of(context); + renderObject + ..value = value + ..edgeStyle = edgeStyle + ..shaderCallback = shaderCallback + ..color = color ?? Color(0xff0074E3) + ..borderColor = borderColor ?? Color(0xff0074E3) + ..borderWidth = borderWidth + ..thickness = thickness + ..offset = offset + ..position = position + ..orientation = linearGaugeScope.orientation + ..onAnimationCompleted = onAnimationCompleted + ..pointerAnimation = linearGaugeScope.animation + ..animationController = linearGaugeScope.animationController + ..isAxisInversed = linearGaugeScope.isAxisInversed; + + super.updateRenderObject(context, renderObject); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_renderer.dart new file mode 100644 index 000000000..4ee140a37 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_renderer.dart @@ -0,0 +1,430 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../linear_gauge/axis/linear_axis_renderer.dart'; +import '../../linear_gauge/utils/enum.dart'; +import '../../linear_gauge/utils/linear_gauge_helper.dart'; + +/// Represents the render object of bar pointer. +class RenderLinearBarPointer extends RenderOpacity { + /// Creates a instance for [RenderLinearBarPointer]. + RenderLinearBarPointer( + {required double value, + required LinearEdgeStyle edgeStyle, + ShaderCallback? shaderCallback, + required Color color, + required Color borderColor, + required double borderWidth, + required double thickness, + required double offset, + required LinearElementPosition position, + required LinearGaugeOrientation orientation, + Animation? pointerAnimation, + VoidCallback? onAnimationCompleted, + AnimationController? animationController, + required bool isAxisInversed}) + : _value = value, + _edgeStyle = edgeStyle, + _shaderCallback = shaderCallback, + _color = color, + _borderColor = borderColor, + _borderWidth = borderWidth, + _thickness = thickness, + _offset = offset, + _position = position, + _orientation = orientation, + _pointerAnimation = pointerAnimation, + _onAnimationCompleted = onAnimationCompleted, + animationController = animationController, + _isAxisInversed = isAxisInversed { + _barPaint = Paint(); + _isHorizontal = orientation == LinearGaugeOrientation.horizontal; + _path = Path(); + } + + late Paint _barPaint; + late bool _isHorizontal; + late Path _path; + late Offset _barPointerOffset; + + Rect _barRect = Rect.zero; + Rect _oldBarRect = Rect.zero; + + /// Gets or sets the axis assigned to [RenderLinearBarPointer]. + RenderLinearAxis? axis; + + /// Gets or Sets the animation controller assigned to [RenderLinearBarPointer]. + AnimationController? animationController; + + /// Gets the value assigned to [RenderLinearBarPointer]. + double get value => _value; + double _value; + + /// Sets the value for [RenderLinearBarPointer]. + set value(double value) { + if (value == _value) { + return; + } + + if (animationController != null && animationController!.isAnimating) { + animationController!.stop(); + _oldBarRect = Rect.fromLTWH( + _barPointerOffset.dx, _barPointerOffset.dy, size.width, size.height); + } + + _value = value; + + if (animationController != null) { + animationController!.forward(from: 0.01); + } + + markNeedsLayout(); + } + + /// Gets the thickness assigned to [RenderLinearBarPointer]. + double get thickness => _thickness; + double _thickness; + + /// Sets the thickness for [RenderLinearBarPointer]. + set thickness(double value) { + if (value == _thickness) { + return; + } + + _thickness = value; + markNeedsLayout(); + } + + /// Gets the color assigned to [RenderLinearBarPointer]. + Color get color => _color; + Color _color; + + /// Sets the color for [RenderLinearBarPointer]. + set color(Color value) { + if (value == _color) { + return; + } + + _color = value; + markNeedsPaint(); + } + + /// Gets the borderColor assigned to [RenderLinearBarPointer]. + Color get borderColor => _borderColor; + Color _borderColor; + + /// Sets the borderColor for [RenderLinearBarPointer]. + set borderColor(Color value) { + if (value == _borderColor) { + return; + } + + _borderColor = value; + markNeedsPaint(); + } + + /// Gets the borderWidth assigned to [RenderLinearBarPointer]. + double get borderWidth => _borderWidth; + double _borderWidth; + + /// Sets the borderWidth for [RenderLinearBarPointer]. + set borderWidth(double value) { + if (value == _borderWidth) { + return; + } + + _borderWidth = value; + markNeedsPaint(); + } + + /// Gets the offset assigned to [RenderLinearBarPointer]. + double get offset => _offset; + double _offset; + + /// Sets the offset for [RenderLinearBarPointer]. + set offset(double value) { + if (value == _offset) { + return; + } + + _offset = value; + markNeedsLayout(); + } + + /// Gets the edgeStyle assigned to [RenderLinearBarPointer]. + LinearEdgeStyle get edgeStyle => _edgeStyle; + LinearEdgeStyle _edgeStyle; + + /// Sets the edgeStyle for [RenderLinearBarPointer]. + set edgeStyle(LinearEdgeStyle value) { + if (value == _edgeStyle) { + return; + } + _edgeStyle = value; + markNeedsPaint(); + } + + /// Gets the position assigned to [RenderLinearBarPointer]. + LinearElementPosition get position => _position; + LinearElementPosition _position; + + /// Sets the position for [RenderLinearBarPointer]. + set position(LinearElementPosition value) { + if (value == _position) { + return; + } + _position = value; + markNeedsLayout(); + } + + /// Gets the shader callback assigned to [RenderLinearBarPointer]. + ShaderCallback? get shaderCallback => _shaderCallback; + ShaderCallback? _shaderCallback; + + /// Sets the shader callback for [RenderLinearBarPointer]. + set shaderCallback(ShaderCallback? value) { + if (value == _shaderCallback) { + return; + } + + _shaderCallback = value; + markNeedsPaint(); + } + + /// Gets the isAxisInversed assigned to [RenderLinearBarPointer]. + bool get isAxisInversed => _isAxisInversed; + bool _isAxisInversed; + + /// Sets the isInverse for [RenderLinearAxis]. + set isAxisInversed(bool value) { + if (value == _isAxisInversed) { + return; + } + + _isAxisInversed = value; + markNeedsPaint(); + } + + /// Gets the orientation assigned to [RenderLinearBarPointer]. + /// + /// Default value is [GaugeOrientation.horizontal]. + /// + LinearGaugeOrientation get orientation => _orientation; + LinearGaugeOrientation _orientation; + + /// Sets the orientation for [RenderLinearBarPointer]. + /// + /// Default value is [GaugeOrientation.horizontal]. + set orientation(LinearGaugeOrientation value) { + if (value == _orientation) { + return; + } + _orientation = value; + _isHorizontal = orientation == LinearGaugeOrientation.horizontal; + markNeedsLayout(); + } + + /// Gets the animation completed callback. + VoidCallback? get onAnimationCompleted => _onAnimationCompleted; + VoidCallback? _onAnimationCompleted; + + /// Sets the animation completed callback. + set onAnimationCompleted(VoidCallback? value) { + if (value == _onAnimationCompleted) { + return; + } + + _onAnimationCompleted = value; + } + + /// Gets the animation assigned to [RenderLinearShapePointer]. + Animation? get pointerAnimation => _pointerAnimation; + Animation? _pointerAnimation; + + /// Sets the animation animation for [RenderLinearShapePointer]. + set pointerAnimation(Animation? value) { + if (value == _pointerAnimation) { + return; + } + + _removeAnimationListener(); + _pointerAnimation = value; + _addAnimationListener(); + } + + void _updateAnimation() { + if (child != null) { + opacity = _pointerAnimation!.value; + } else { + markNeedsPaint(); + } + } + + void _animationStatusListener(AnimationStatus status) { + if (status == AnimationStatus.completed) { + if (onAnimationCompleted != null) { + onAnimationCompleted!(); + } + + _oldBarRect = _barRect; + } + } + + void _addAnimationListener() { + if (pointerAnimation != null) { + pointerAnimation!.addListener(_updateAnimation); + pointerAnimation!.addStatusListener(_animationStatusListener); + } + } + + void _removeAnimationListener() { + if (pointerAnimation != null) { + pointerAnimation!.removeListener(_updateAnimation); + pointerAnimation!.removeStatusListener(_animationStatusListener); + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _addAnimationListener(); + } + + @override + void detach() { + _removeAnimationListener(); + super.detach(); + } + + @override + void performLayout() { + double barWidth = 0; + + if (axis != null) { + barWidth = + (axis!.valueToPixel(value) - axis!.valueToPixel(axis!.minimum)).abs(); + } + + Size controlSize; + + if (_isHorizontal) { + controlSize = Size(barWidth, thickness); + } else { + controlSize = Size(thickness, barWidth); + } + + if (child != null) { + child!.layout(BoxConstraints.tight(controlSize)); + } + + size = controlSize; + } + + ///Measures the bar rect. + void _getBarRect(Offset offset) { + if (size.isEmpty) { + _barRect = Rect.zero; + return; + } + + double animationValue = 1; + if (_pointerAnimation != null) { + animationValue = _pointerAnimation!.value; + } + + if (_isHorizontal) { + _barRect = Rect.fromLTWH( + offset.dx + + (isAxisInversed + ? (size.width - _oldBarRect.width) - + ((size.width - _oldBarRect.width) * animationValue) + : 0), + offset.dy, + _oldBarRect.width + + ((size.width - _oldBarRect.width) * animationValue), + size.height); + } else { + _barRect = Rect.fromLTWH( + offset.dx, + offset.dy + + (!isAxisInversed + ? (size.height - _oldBarRect.height) - + ((size.height - _oldBarRect.height) * animationValue) + : 0), + size.width, + _oldBarRect.height + + ((size.height - _oldBarRect.height) * animationValue), + ); + } + + if (borderWidth > 0) { + _barRect = Rect.fromLTWH( + _barRect.left + borderWidth / 2, + _barRect.top + borderWidth / 2, + _barRect.width - borderWidth, + _barRect.height - borderWidth); + } + } + + Path _getBarPointerPath(Offset offset) { + _path.reset(); + switch (edgeStyle) { + case LinearEdgeStyle.bothFlat: + _path.addRect(_barRect); + break; + case LinearEdgeStyle.bothCurve: + _path.addRRect( + RRect.fromRectAndRadius(_barRect, Radius.circular(thickness / 2))); + break; + case LinearEdgeStyle.startCurve: + _path.addRRect(LinearGaugeHelper.getStartCurve( + isHorizontal: _isHorizontal, + isAxisInversed: isAxisInversed, + rect: _barRect, + radius: thickness / 2)); + break; + case LinearEdgeStyle.endCurve: + _path.addRRect(LinearGaugeHelper.getEndCurve( + isHorizontal: _isHorizontal, + isAxisInversed: isAxisInversed, + rect: _barRect, + radius: thickness / 2)); + break; + default: + break; + } + + return _path; + } + + @override + void paint(PaintingContext context, Offset offset) { + _barPointerOffset = offset; + final canvas = context.canvas; + + if (_pointerAnimation == null || + (_pointerAnimation != null && _pointerAnimation!.value > 0)) { + _getBarRect(offset); + _barPaint.style = PaintingStyle.fill; + _barPaint.color = color; + if (shaderCallback != null) { + _barPaint.shader = shaderCallback!(_barRect); + } + + final path = _getBarPointerPath(offset); + canvas.drawPath(path, _barPaint); + + if (borderWidth > 0) { + _barPaint.shader = null; + _barPaint.style = PaintingStyle.stroke; + _barPaint.strokeWidth = borderWidth; + _barPaint.color = borderColor; + canvas.drawPath(path, _barPaint); + } + + super.paint(context, offset); + } + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_marker_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_marker_pointer.dart new file mode 100644 index 000000000..8878f6d37 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_marker_pointer.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; + +import '../../../gauges.dart'; +import '../../linear_gauge/utils/enum.dart'; + +/// [LinearGaugePointer] has properties for customizing linear gauge pointers. +abstract class LinearMarkerPointer { + /// Creates a pointer for linear axis with the default or required properties. + LinearMarkerPointer( + {required this.value, + this.onValueChanged, + this.enableAnimation = false, + this.animationDuration = 1000, + this.animationType = LinearAnimationType.ease, + this.offset = 0.0, + this.markerAlignment = LinearMarkerAlignment.center, + this.position = LinearElementPosition.cross, + this.onAnimationCompleted}); + + /// Specifies the linear axis value to place the pointer. + /// + /// Defaults to 0. + final double value; + + /// Specifies whether to enable animation or not, when the pointer moves. + /// + /// Defaults to false. + final bool enableAnimation; + + /// Specifies the animation duration for the pointer. + /// Duration is defined in milliseconds. + /// + /// Defaults to 1000. + final int animationDuration; + + /// Specifies the type of the animation for the pointer. + /// + /// Defaults to [LinearAnimationType.ease]. + final LinearAnimationType animationType; + + /// Specifies the offset value which represent the gap from the linear axis. + /// Origin of this property will be relates to [LinearGaugePointer.position]. + /// + /// Defaults to 0. + final double offset; + + /// Specifies the annotation position with respect to linear axis. + /// + final LinearElementPosition position; + + /// Signature for callbacks that report that an underlying value has changed. + /// + final ValueChanged? onValueChanged; + + /// Specifies the marker alignment. + /// + /// Defaults to center. + final LinearMarkerAlignment markerAlignment; + + /// Specifies the animation completed callback. + /// + final VoidCallback? onAnimationCompleted; +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_pointer.dart new file mode 100644 index 000000000..1fde6d626 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_pointer.dart @@ -0,0 +1,416 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../linear_gauge/gauge/linear_gauge_scope.dart'; +import '../../linear_gauge/pointers/linear_marker_pointer.dart'; +import '../../linear_gauge/pointers/linear_shape_renderer.dart'; +import '../../linear_gauge/utils/enum.dart'; + +/// [LinearShapePointer] has properties for customizing the shape marker pointer. +class LinearShapePointer extends LeafRenderObjectWidget + implements LinearMarkerPointer { + /// Creates a shape marker pointer for linear axis. + LinearShapePointer( + {Key? key, + required this.value, + this.onValueChanged, + this.enableAnimation = true, + this.animationDuration = 1000, + this.animationType = LinearAnimationType.ease, + this.onAnimationCompleted, + this.width, + this.height, + double offset = 0.0, + this.markerAlignment = LinearMarkerAlignment.center, + this.position = LinearElementPosition.outside, + this.shapeType = LinearShapePointerType.invertedTriangle, + this.color, + this.borderColor, + this.borderWidth = 0.0, + this.elevation = 0, + this.elevationColor = Colors.black}) + : offset = offset > 0 ? offset : 0, + super(key: key); + + /// Specifies the pointer value of [ShapePointer]. + /// This value must be between the min and max value of an axis track. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set a value for a shape marker pointer. + /// + /// ```dart + /// + /// SfLinearGauge( + /// markerPointers: [ + /// LinearShapePointer( + /// value: 50, + /// )]) + /// + /// ``` + /// + @override + final double value; + + /// Specifies the loading animation for shape pointers with [animationDuration]. + /// + /// Defaults to true. + /// + /// This snippet shows how to set load time animation for shape marker pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// value: 20 + /// enableAnimation: true, + /// )]) + /// ``` + /// + @override + final bool enableAnimation; + + /// Specifies the load time animation duration with [enableAnimation]. + /// Duration is defined in milliseconds. + /// + /// Defaults to 1000. + /// + /// This snippet shows how to set loading animation duration of shape pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// value: 20 + /// enableAnimation: true, + /// animationDuration: 4000 + /// )]) + /// ``` + /// + @override + final int animationDuration; + + /// Specifies the animation type of shape pointers. + /// + /// Defaults to [LinearAnimationType.ease]. + /// + /// This snippet shows how to set animation type of shape pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// value: 20 + /// enableAnimation: true, + /// animationType: LinearAnimationType.linear + /// )]) + /// ``` + /// + @override + final LinearAnimationType animationType; + + /// Specifies the width of shape pointers. + /// + /// Defaults to 16.0. + /// + /// This snippet shows how to set a width for the shape pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// value: 50 + /// width: 30 + /// )]) + /// ``` + /// + final double? width; + + /// Specifies the offset for shape pointer with respect to axis. + /// The offset cannot be negative. + /// The offset value will not be effective when the shape pointer is in cross position. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set margin value for shape pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// value: 50 + /// margin: 10 + /// )]) + /// ``` + /// + @override + final double offset; + + /// Specifies the height of the shape pointer. + /// + /// Defaults to 16.0. + /// + /// This snippet shows how to set a height for the shape pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// value: 50 + /// height: 30 + /// )]) + /// ``` + /// + final double? height; + + /// Specifies the position of shape pointers with respect to axis. + /// + /// Defaults to [LinearElementPosition.outside]. + /// + /// This snippet shows how to set the shape pointer position in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// value: 50 + /// position: LinearElementPosition.inside + /// )]) + /// ``` + /// + @override + final LinearElementPosition position; + + /// Signature for callbacks that report that an underlying value has changed. + /// + /// This snippet shows how to call onvaluechange function in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// markerPointers: [ + /// LinearShapePointer( + /// value: _pointerValue + /// onValueChanged: (value) => + /// {setState(() => + /// {_pointerValue = value})}, + /// )]) + /// + /// ``` + /// + @override + final ValueChanged? onValueChanged; + + /// Specifies the built-in shape of shape marker pointer. + /// + /// Defaults to [LinearShapePointerType.invertedTriangle]. + /// + /// This snippet shows how to set a shape to shape marker pointer. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// value: 50 + /// shapeType: LinearShapePointerType.triangle + /// )]) + /// ``` + /// + final LinearShapePointerType shapeType; + + /// Specifies the color for shape marker pointer. + /// + /// Defaults to [Colors.black54] in LightTheme + /// and [Colors.white70] in DarkTheme. + /// + /// This snippet shows how to set color for a shape pointer. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// color: Colors.Grey + /// )]) + /// ``` + /// + final Color? color; + + /// Specifies the border color of shape pointer. + /// + /// Defaults to [Colors.black54] in LightTheme + /// and [Colors.white70] in DarkTheme. + /// + /// This snippet shows how to set a border color for the shape pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// borderColor: Colors.Orange + /// )]) + /// ``` + /// + final Color? borderColor; + + /// Specifies border width of shape pointer. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set a border width for the shape pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// borderWidth: 3 + /// )]) + /// ``` + /// + final double borderWidth; + + /// Provides elevation for shape pointers. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set elevation value for a shape marker pointer. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// value: 50 + /// elevation: 2 + /// )]) + /// ``` + /// + final double elevation; + + /// Specifies elevation color for shape pointers with [elevation] + /// + /// Defaults to [Colors.Black]. + /// + /// This snippet shows how to set elevation color for shape pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// value: 50 + /// elevationColor: Colors.grey[50] + /// )]) + /// ``` + /// + final Color elevationColor; + + /// Specifies the alignment for shape marker pointer. + /// + /// Defaults to [MarkerAlignment.center]. + /// + /// This snippet shows how to set a shape to shape-pointer. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearShapePointer( + /// value: 50 + /// markerAlignment: MarkerAlignment.end + /// )]) + /// ``` + /// + @override + final LinearMarkerAlignment markerAlignment; + + /// Called when the shape pointer animation is completed. + /// + /// Defaults to null. + /// + /// This snippet shows how to use this [onAnimationCompleted] callback. + /// + /// ```dart + /// SfLinearGauge( + /// markerPointers:[ + /// LinearShapePointer( + /// onAnimationCompleted: ()=> { + /// printf("Shape Pointer animation is completed"); + /// }, + /// )]); + /// ``` + @override + final VoidCallback? onAnimationCompleted; + + @override + RenderObject createRenderObject(BuildContext context) { + final linearGaugeScope = LinearGaugeScope.of(context); + final ThemeData theme = Theme.of(context); + final bool isDarkTheme = theme.brightness == Brightness.dark; + return RenderLinearShapePointer( + value: value, + onValueChanged: onValueChanged, + color: color ?? (isDarkTheme ? Colors.white70 : Colors.black54), + borderColor: + borderColor ?? (isDarkTheme ? Colors.white70 : Colors.black54), + borderWidth: borderWidth, + width: width ?? (shapeType == LinearShapePointerType.diamond ? 12 : 16), + height: + height ?? (shapeType == LinearShapePointerType.rectangle ? 8 : 16), + offset: offset, + position: position, + shapeType: shapeType, + elevation: elevation, + elevationColor: elevationColor, + orientation: linearGaugeScope.orientation, + isMirrored: linearGaugeScope.isMirrored, + markerAlignment: markerAlignment, + animationController: linearGaugeScope.animationController, + onAnimationCompleted: onAnimationCompleted, + pointerAnimation: linearGaugeScope.animation); + } + + @override + void updateRenderObject( + BuildContext context, RenderLinearShapePointer renderObject) { + final linearGaugeScope = LinearGaugeScope.of(context); + final ThemeData theme = Theme.of(context); + final bool isDarkTheme = theme.brightness == Brightness.dark; + + renderObject + ..value = value + ..onValueChanged = onValueChanged + ..color = color ?? (isDarkTheme ? Colors.white70 : Colors.black54) + ..borderColor = + borderColor ?? (isDarkTheme ? Colors.white70 : Colors.black54) + ..borderWidth = borderWidth + ..width = width ?? (shapeType == LinearShapePointerType.diamond ? 12 : 16) + ..height = + height ?? (shapeType == LinearShapePointerType.rectangle ? 8 : 16) + ..offset = offset + ..position = position + ..shapeType = shapeType + ..elevation = elevation + ..elevationColor = elevationColor + ..orientation = linearGaugeScope.orientation + ..isMirrored = linearGaugeScope.isMirrored + ..markerAlignment = markerAlignment + ..onAnimationCompleted = onAnimationCompleted + ..animationController = linearGaugeScope.animationController + ..pointerAnimation = linearGaugeScope.animation; + + super.updateRenderObject(context, renderObject); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_renderer.dart new file mode 100644 index 000000000..bea08802d --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_renderer.dart @@ -0,0 +1,492 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../linear_gauge/utils/enum.dart'; + +/// Represents the render object of shape pointer. +class RenderLinearShapePointer extends RenderBox { + /// Creates a instance for [RenderLinearShapePointer]. + RenderLinearShapePointer( + {required double value, + ValueChanged? onValueChanged, + required Color color, + required Color borderColor, + required double borderWidth, + required double width, + required double height, + required double offset, + required LinearElementPosition position, + required LinearShapePointerType shapeType, + required double elevation, + required LinearGaugeOrientation orientation, + required Color elevationColor, + required LinearMarkerAlignment markerAlignment, + required bool isMirrored, + Animation? pointerAnimation, + VoidCallback? onAnimationCompleted, + AnimationController? animationController}) + : _value = value, + _onValueChanged = onValueChanged, + _color = color, + _borderColor = borderColor, + _borderWidth = borderWidth, + _shapeType = shapeType, + _elevation = elevation, + _orientation = orientation, + _elevationColor = elevationColor, + _width = width, + _height = height, + _offset = offset, + _position = position, + _markerAlignment = markerAlignment, + _isMirrored = isMirrored, + animationController = animationController, + _onAnimationCompleted = onAnimationCompleted, + _pointerAnimation = pointerAnimation { + _shapePaint = Paint(); + _path = Path(); + } + + late Paint _shapePaint; + late Path _path; + + Rect _shapeRect = Rect.zero; + + /// Shape Pointer old value. + double? oldValue; + + /// Gets or Sets the animation controller assigned to [RenderLinearShapePointer]. + AnimationController? animationController; + + /// Gets the orientation to [RenderLinearShapePointer]. + LinearGaugeOrientation? get orientation => _orientation; + LinearGaugeOrientation? _orientation; + + /// Sets the orientation for [RenderLinearShapePointer]. + /// + /// Default value is [GaugeOrientation.horizontal]. + set orientation(LinearGaugeOrientation? value) { + if (value == _orientation) { + return; + } + + _orientation = value; + markNeedsLayout(); + } + + /// Gets the shapeType assigned to [RenderLinearShapePointer]. + LinearShapePointerType? get shapeType => _shapeType; + LinearShapePointerType? _shapeType; + + /// Sets the shapeType for [RenderLinearShapePointer]. + set shapeType(LinearShapePointerType? value) { + if (value == _shapeType) { + return; + } + _shapeType = value; + markNeedsPaint(); + } + + /// Gets the value assigned to [RenderLinearShapePointer]. + double get value => _value; + double _value; + + /// Sets the value for [RenderLinearShapePointer]. + set value(double value) { + if (value == _value) { + return; + } + + if (animationController != null && animationController!.isAnimating) { + oldValue = _value; + animationController!.stop(); + } + + _value = value; + + if (animationController != null && oldValue != value) { + animationController!.forward(from: 0.01); + } + + markNeedsLayout(); + } + + /// Gets the onValueChanged assigned to [RenderLinearShapePointer]. + ValueChanged? get onValueChanged => _onValueChanged; + ValueChanged? _onValueChanged; + + /// Sets the onValueChanged callback for [RenderLinearShapePointer]. + set onValueChanged(ValueChanged? value) { + if (value == _onValueChanged) { + return; + } + + _onValueChanged = value; + } + + /// Gets the width assigned to [RenderLinearShapePointer]. + double get width => _width; + double _width; + + /// Sets the width for [RenderLinearShapePointer]. + set width(double value) { + if (value == _width) { + return; + } + + _width = value; + markNeedsLayout(); + } + + /// Gets the height assigned to [RenderLinearShapePointer]. + double get height => _height; + double _height; + + /// Sets the height for [RenderLinearShapePointer]. + set height(double value) { + if (value == _height) { + return; + } + + _height = value; + markNeedsLayout(); + } + + /// Gets the color assigned to [RenderLinearShapePointer]. + Color get color => _color; + Color _color; + + /// Sets the color for [RenderLinearShapePointer]. + set color(Color value) { + if (value == _color) { + return; + } + + _color = value; + markNeedsPaint(); + } + + /// Gets the borderColor assigned to [RenderLinearShapePointer]. + Color get borderColor => _borderColor; + Color _borderColor; + + /// Sets the borderColor for [RenderLinearShapePointer]. + set borderColor(Color value) { + if (value == _borderColor) { + return; + } + + _borderColor = value; + markNeedsPaint(); + } + + /// Gets the borderWidth assigned to [RenderLinearShapePointer]. + double get borderWidth => _borderWidth; + double _borderWidth; + + /// Sets the borderWidth for [RenderLinearShapePointer]. + set borderWidth(double value) { + if (value == _borderWidth) { + return; + } + + _borderWidth = value; + markNeedsPaint(); + } + + /// Gets the offset assigned to [RenderLinearShapePointer]. + double get offset => _offset; + double _offset; + + /// Sets the offset for [RenderLinearShapePointer]. + set offset(double value) { + if (value == _offset) { + return; + } + + _offset = value; + markNeedsLayout(); + } + + /// Gets the position assigned to [RenderLinearShapePointer]. + LinearElementPosition get position => _position; + LinearElementPosition _position; + + /// Sets the position for [RenderLinearShapePointer]. + set position(LinearElementPosition value) { + if (value == _position) { + return; + } + + _position = value; + markNeedsLayout(); + } + + /// Gets the elevation assigned to [RenderLinearShapePointer]. + double get elevation => _elevation; + double _elevation; + + /// Sets the elevation for [RenderLinearShapePointer]. + set elevation(double value) { + if (value == _elevation) { + return; + } + + _elevation = value; + markNeedsPaint(); + } + + /// Gets the elevation color assigned to [RenderLinearShapePointer]. + Color get elevationColor => _elevationColor; + Color _elevationColor; + + /// Sets the elevation color for [RenderLinearShapePointer]. + set elevationColor(Color value) { + if (value == _elevationColor) { + return; + } + + _elevationColor = value; + markNeedsPaint(); + } + + /// Gets the animation assigned to [RenderLinearShapePointer]. + Animation? get pointerAnimation => _pointerAnimation; + Animation? _pointerAnimation; + + /// Sets the animation animation for [RenderLinearShapePointer]. + set pointerAnimation(Animation? value) { + if (value == _pointerAnimation) { + return; + } + + _removeAnimationListener(); + _pointerAnimation = value; + _addAnimationListener(); + } + + /// Gets the Marker Alignment assigned to [RenderLinearShapePointer]. + LinearMarkerAlignment? get markerAlignment => _markerAlignment; + LinearMarkerAlignment? _markerAlignment; + + /// Sets the Marker Alignment for [RenderLinearShapePointer]. + set markerAlignment(LinearMarkerAlignment? value) { + if (value == _markerAlignment) { + return; + } + + _markerAlignment = value; + markNeedsLayout(); + } + + /// Gets the animation completed callback. + VoidCallback? get onAnimationCompleted => _onAnimationCompleted; + VoidCallback? _onAnimationCompleted; + + /// Sets the animation completed callback. + set onAnimationCompleted(VoidCallback? value) { + if (value == _onAnimationCompleted) { + return; + } + + _onAnimationCompleted = value; + } + + /// Gets the isMirrored to [RenderLinearShapePointer]. + bool get isMirrored => _isMirrored; + bool _isMirrored; + + /// Sets the isMirrored for [RenderLinearShapePointer]. + set isMirrored(bool value) { + if (value == _isMirrored) { + return; + } + + _isMirrored = value; + markNeedsPaint(); + } + + void _animationStatusListener(AnimationStatus status) { + if (status == AnimationStatus.completed) { + if (onAnimationCompleted != null) { + onAnimationCompleted!(); + } + + if (oldValue != value) oldValue = value; + } + } + + void _addAnimationListener() { + if (pointerAnimation != null) { + pointerAnimation!.addListener(markNeedsPaint); + pointerAnimation!.addStatusListener(_animationStatusListener); + } + } + + void _removeAnimationListener() { + if (pointerAnimation != null) { + pointerAnimation!.removeListener(markNeedsPaint); + pointerAnimation!.removeStatusListener(_animationStatusListener); + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _addAnimationListener(); + } + + @override + void detach() { + _removeAnimationListener(); + super.detach(); + } + + @override + void performLayout() { + size = Size(width, height); + } + + @override + bool hitTestSelf(Offset position) { + return true; + } + + ///Gets the diamond path for shape drawing. + void _getDiamondPath(double left, double top, double height, double width) { + _path + ..moveTo(left + width / 2.0, top) + ..lineTo(left, top + height / 2.0) + ..lineTo(left + width / 2.0, top + height) + ..lineTo(left + width, top + height / 2.0) + ..close(); + } + + ///Gets the triangle path for shape drawing. + void _getTrianglePath(double left, double top, {bool isInverted = true}) { + if (_orientation == LinearGaugeOrientation.horizontal) { + if (isInverted) { + _path.moveTo(left, top); + _path.lineTo(left + width, top); + _path.lineTo(left + (width / 2), top + height); + } else { + _path.moveTo(left + (width / 2), top); + _path.lineTo(left, top + height); + _path.lineTo(left + width, top + height); + } + } else { + if (isInverted) { + _path.moveTo(left, top); + _path.lineTo(left + width, top + (height / 2)); + _path.lineTo(left, top + height); + } else { + _path.moveTo(left, top + (height / 2)); + _path.lineTo(left + width, top); + _path.lineTo(left + width, top + height); + } + } + + _path.close(); + } + + void _setBorderStyle() { + _shapePaint.style = PaintingStyle.stroke; + _shapePaint.strokeWidth = borderWidth; + _shapePaint.color = borderColor; + } + + void _drawCircleShape(Canvas canvas) { + if (elevation > 0) { + _path.addOval(_shapeRect); + canvas.drawShadow(_path, elevationColor, elevation, true); + } + + canvas.drawOval(_shapeRect, _shapePaint); + + if (borderWidth > 0) { + _setBorderStyle(); + canvas.drawOval(_shapeRect, _shapePaint); + } + } + + void _drawRectangleShape(Canvas canvas) { + _path.addRect(_shapeRect); + if (elevation > 0) { + canvas.drawShadow(_path, elevationColor, elevation, true); + } + + canvas.drawRect(_shapeRect, _shapePaint); + + if (borderWidth > 0) { + _setBorderStyle(); + canvas.drawRect(_shapeRect, _shapePaint); + } + } + + void _drawTriangle(Canvas canvas, bool isInverted) { + _getTrianglePath(_shapeRect.left, _shapeRect.top, isInverted: isInverted); + if (elevation > 0) { + canvas.drawShadow(_path, elevationColor, elevation, true); + } + canvas.drawPath(_path, _shapePaint); + + if (borderWidth > 0) { + _setBorderStyle(); + canvas.drawPath(_path, _shapePaint); + } + } + + void _drawDiamond(Canvas canvas) { + _getDiamondPath( + _shapeRect.left, _shapeRect.top, _shapeRect.height, _shapeRect.width); + + if (elevation > 0) { + canvas.drawShadow(_path, elevationColor, elevation, true); + } + + canvas.drawPath(_path, _shapePaint); + + if (borderWidth > 0) { + _setBorderStyle(); + canvas.drawPath(_path, _shapePaint); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + if (pointerAnimation == null || + (pointerAnimation != null && pointerAnimation!.value > 0)) { + _shapeRect = Rect.fromLTWH( + offset.dx + borderWidth / 2, + offset.dy + borderWidth / 2, + size.width - borderWidth, + size.height - borderWidth); + _shapePaint.style = PaintingStyle.fill; + _shapePaint.color = color; + + final canvas = context.canvas; + _path.reset(); + switch (shapeType) { + case LinearShapePointerType.circle: + _drawCircleShape(canvas); + break; + case LinearShapePointerType.rectangle: + _drawRectangleShape(canvas); + break; + case LinearShapePointerType.triangle: + _drawTriangle(canvas, isMirrored ? true : false); + break; + case LinearShapePointerType.invertedTriangle: + _drawTriangle(canvas, isMirrored ? false : true); + break; + case LinearShapePointerType.diamond: + _drawDiamond(canvas); + break; + default: + break; + } + } + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_pointer.dart new file mode 100644 index 000000000..eef4d8abb --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_pointer.dart @@ -0,0 +1,241 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../linear_gauge/gauge/linear_gauge_scope.dart'; +import '../../linear_gauge/pointers/linear_marker_pointer.dart'; +import '../../linear_gauge/pointers/linear_widget_renderer.dart'; +import '../../linear_gauge/utils/enum.dart'; + +/// [LinearMarkerPointer] has properties for customizing widget marker pointer. +class LinearWidgetPointer extends SingleChildRenderObjectWidget + implements LinearMarkerPointer { + /// Creates a widget marker pointer. + LinearWidgetPointer( + {Key? key, + required this.value, + this.onValueChanged, + this.enableAnimation = true, + this.animationDuration = 1000, + this.animationType = LinearAnimationType.ease, + this.onAnimationCompleted, + double offset = 0.0, + this.position = LinearElementPosition.cross, + this.markerAlignment = LinearMarkerAlignment.center, + required Widget child}) + : offset = offset > 0 ? offset : 0, + super(key: key, child: child); + + /// Specifies the pointer value for [WidgetPointer]. + /// This value must be between the min and max value of an axis track. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set a value for widget marker pointer. + /// + /// ```dart + /// + /// SfLinearGauge( + /// markerPointers: [ + /// LinearWidgetPointer( + /// value: 50, + /// )]) + /// + /// ``` + /// + @override + final double value; + + /// Specifies the loading animation for widget marker pointers with + /// [animationDuration]. + /// + /// Defaults to true. + /// + /// This snippet shows how to set load time animation for widget marker pointers in ///[SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearWidgetPointer( + /// value: 20 + /// enableAnimation: true, + /// )]) + /// ``` + /// + @override + final bool enableAnimation; + + /// Specifies the load time animation duration with [enableAnimation]. + /// Duration is defined in milliseconds. + /// + /// Defaults to 1000. + /// + /// This snippet shows how to set animation duration for widget marker pointers. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearWidgetPointer( + /// value: 20 + /// enableAnimation: true, + /// animationDuration: 4000 + /// )]) + /// ``` + /// + @override + final int animationDuration; + + /// Specifies the animation type for widget marker pointers. + /// + /// Defaults to [LinearAnimationType.ease]. + /// + /// This snippet shows how to set animation type of widget marker pointers in + /// [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearWidgetPointer( + /// value: 20 + /// enableAnimation: true, + /// animationType: LinearAnimationType.linear + /// )]) + /// ``` + /// + @override + final LinearAnimationType animationType; + + /// Specifies the offset for widget pointer with respect to axis. + /// The offset cannot be negative. + /// The offset value will not be effective when the widget pointer is in cross position. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set margin value for widget marker pointers + /// in[SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearWidgetPointer( + /// value: 50 + /// margin: 15 + /// )]) + /// ``` + /// + @override + final double offset; + + /// Specifies the position of widget marker pointers with respect to axis. + /// + /// Defaults to [LinearElementPosition.cross]. + /// + /// This snippet shows how to set the position of widget marker pointers + /// in[SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge ( + /// markerPointers: [ + /// LinearWidgetPointer( + /// value: 50, + /// position: LinearElementPosition.inside + /// )]) + /// ``` + /// + @override + final LinearElementPosition position; + + /// Signature for callbacks that report that an underlying value has changed. + /// + /// This snippet shows how to call onvaluechange function in[SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// markerPointers: [ + /// LinearWidgetPointer( + /// value: _pointerValue + /// onValueChanged: (value) => + /// {setState(() => + /// {_pointerValue = value})}, + /// )]) + /// + /// ``` + /// + @override + final ValueChanged? onValueChanged; + + /// Specifies the marker alignment. + /// + /// Defaults to [MarkerAlignment.center]. + /// + /// This snippet shows how to set marker alignment in[SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// markerPointers: [ + /// LinearWidgetPointer( + /// value: 30, + /// markerAlignment: MarkerAlignment.end + /// )]) + /// + /// ``` + /// + @override + final LinearMarkerAlignment markerAlignment; + + /// Called when the widget pointer animation is completed. + /// + /// Defaults to null. + /// + /// This snippet shows how to use this [onAnimationCompleted] callback. + /// + /// ```dart + /// SfLinearGauge( + /// markerPointers:[ + /// LinearWidgetPointer( + /// onAnimationCompleted: ()=> { + /// printf("Widget Pointer animation is completed"); + /// }, + /// )]); + /// ``` + @override + final VoidCallback? onAnimationCompleted; + + @override + RenderObject createRenderObject(BuildContext context) { + final scope = LinearGaugeScope.of(context); + return RenderLinearWidgetPointer( + value: value, + onValueChanged: onValueChanged, + offset: offset, + position: position, + markerAlignment: markerAlignment, + animationController: scope.animationController, + onAnimationCompleted: onAnimationCompleted, + pointerAnimation: scope.animation); + } + + @override + void updateRenderObject( + BuildContext context, RenderLinearWidgetPointer renderObject) { + final scope = LinearGaugeScope.of(context); + + renderObject + ..value = value + ..onValueChanged = onValueChanged + ..offset = offset + ..position = position + ..markerAlignment = markerAlignment + ..onAnimationCompleted = onAnimationCompleted + ..animationController = scope.animationController + ..pointerAnimation = scope.animation; + + super.updateRenderObject(context, renderObject); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_renderer.dart new file mode 100644 index 000000000..74018f322 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_renderer.dart @@ -0,0 +1,191 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../linear_gauge/utils/enum.dart'; + +/// Represents the render object of shape pointer. +class RenderLinearWidgetPointer extends RenderProxyBox { + /// Creates a instance for [RenderLinearWidgetPointer]. + RenderLinearWidgetPointer( + {required double value, + ValueChanged? onValueChanged, + required double offset, + required LinearElementPosition position, + required LinearMarkerAlignment markerAlignment, + Animation? pointerAnimation, + VoidCallback? onAnimationCompleted, + AnimationController? animationController}) + : _value = value, + _onValueChanged = onValueChanged, + _offset = offset, + _position = position, + _markerAlignment = markerAlignment, + _pointerAnimation = pointerAnimation, + _onAnimationCompleted = onAnimationCompleted, + animationController = animationController; + + /// Gets or sets the shape pointer old value. + double? oldValue; + + /// Gets or sets the animation controller assigned to [RenderLinearShapePointer]. + AnimationController? animationController; + + /// Gets the value assigned to [RenderLinearWidgetPointer]. + double get value => _value; + double _value; + + /// Sets the value for [RenderLinearWidgetPointer]. + set value(double value) { + if (value == _value) { + return; + } + + if (animationController != null && animationController!.isAnimating) { + oldValue = _value; + animationController!.stop(); + } + + _value = value; + + if (animationController != null && oldValue != value) { + animationController!.forward(from: 0.01); + } + + markNeedsLayout(); + } + + /// Gets the onValueChanged assigned to [RenderLinearWidgetPointer]. + ValueChanged? get onValueChanged => _onValueChanged; + ValueChanged? _onValueChanged; + + /// Sets the onValueChanged callback for [RenderLinearWidgetPointer]. + set onValueChanged(ValueChanged? value) { + if (value == _onValueChanged) { + return; + } + + _onValueChanged = value; + } + + /// Gets the offset assigned to [RenderLinearWidgetPointer]. + double get offset => _offset; + double _offset; + + /// Sets the offset for [RenderLinearWidgetPointer]. + set offset(double value) { + if (value == _offset) { + return; + } + + _offset = value; + markNeedsLayout(); + } + + /// Gets the position assigned to [RenderLinearWidgetPointer]. + LinearElementPosition get position => _position; + LinearElementPosition _position; + + /// Sets the position for [RenderLinearWidgetPointer]. + set position(LinearElementPosition value) { + if (value == _position) { + return; + } + + _position = value; + markNeedsLayout(); + } + + /// Gets the animation assigned to [RenderLinearWidgetPointer]. + Animation? get pointerAnimation => _pointerAnimation; + Animation? _pointerAnimation; + + /// Sets the animation for [RenderLinearWidgetPointer]. + set pointerAnimation(Animation? value) { + if (value == _pointerAnimation) { + return; + } + + _removeAnimationListener(); + _pointerAnimation = value; + _addAnimationListener(); + } + + /// Gets the Marker Alignment assigned to [RenderLinearWidgetPointer]. + LinearMarkerAlignment get markerAlignment => _markerAlignment; + LinearMarkerAlignment _markerAlignment; + + /// Sets the Marker Alignment for [RenderLinearWidgetPointer]. + set markerAlignment(LinearMarkerAlignment value) { + if (value == _markerAlignment) { + return; + } + _markerAlignment = value; + markNeedsLayout(); + } + + /// Gets the animation completed callback. + VoidCallback? get onAnimationCompleted => _onAnimationCompleted; + VoidCallback? _onAnimationCompleted; + + /// Sets the animation completed callback. + set onAnimationCompleted(VoidCallback? value) { + if (value == _onAnimationCompleted) { + return; + } + + _onAnimationCompleted = value; + } + + void _animationStatusListener(AnimationStatus status) { + if (status == AnimationStatus.completed) { + if (onAnimationCompleted != null) { + onAnimationCompleted!(); + } + + if (oldValue != value) { + oldValue = value; + } + } + } + + void _addAnimationListener() { + if (pointerAnimation == null) return; + + pointerAnimation!.addListener(markNeedsPaint); + pointerAnimation!.addStatusListener(_animationStatusListener); + } + + void _removeAnimationListener() { + if (pointerAnimation == null) return; + + pointerAnimation!.removeListener(markNeedsPaint); + pointerAnimation!.removeStatusListener(_animationStatusListener); + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _addAnimationListener(); + } + + @override + void detach() { + _removeAnimationListener(); + super.detach(); + } + + @override + bool hitTestSelf(Offset position) { + return true; + } + + @override + void paint(PaintingContext context, Offset offset) { + if ((pointerAnimation == null || + (pointerAnimation != null && pointerAnimation!.value > 0))) { + super.paint(context, offset); + } + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/range/linear_gauge_range.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/range/linear_gauge_range.dart new file mode 100644 index 000000000..4267f33f9 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/range/linear_gauge_range.dart @@ -0,0 +1,253 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../linear_gauge/gauge/linear_gauge_scope.dart'; + +import '../../linear_gauge/range/linear_gauge_range_renderer.dart'; +import '../../linear_gauge/utils/enum.dart'; + +/// [LinearGaugeRange] has properties for customizing linear gauge range. +class LinearGaugeRange extends SingleChildRenderObjectWidget { + /// Creates a new range in linear gauge. + /// + LinearGaugeRange( + {Key? key, + this.startValue = 0, + double? midValue, + this.endValue = 100, + this.startWidth = 5.0, + this.endWidth = 5.0, + double? midWidth, + this.color, + this.shaderCallback, + this.rangeShapeType = LinearRangeShapeType.flat, + this.edgeStyle = LinearEdgeStyle.bothFlat, + this.position = LinearElementPosition.outside, + Widget? child}) + : assert(startValue <= endValue), + midValue = midValue ?? startValue, + midWidth = midWidth ?? startWidth, + super(key: key, child: child); + + /// Specifies the start value of the range. + /// + /// Defaults to 0. + /// + /// This snippet shows how to set the startvalue for [LinearGaugeRange]. + /// + /// ```dart + /// + /// LinearGaugeRange( + /// startValue: 65, + /// ); + /// ``` + /// + final double startValue; + + /// Specifies the mid value of the range. + /// + /// This snippet shows how to set midvalue for [LinearGaugeRange]. + /// + /// ```dart + /// + /// LinearGaugeRange( + /// midValue: 100, + /// ); + /// ``` + /// + final double midValue; + + /// Specifies the end value of the range. + /// Defaults to 100. + /// + /// This snippet shows how to set endvalue for range in [LinearGaugeRange]. + /// + /// ```dart + /// + /// LinearGaugeRange( + /// endValue: 150, + /// ); + /// ``` + /// + final double endValue; + + /// Specifies the start width of the range. + /// Defaults to 5.0. + /// + /// This snippet shows how to set startWidth for [LinearGaugeRange]. + /// + /// ```dart + /// + /// LinearGaugeRange( + /// startValue: 0.0, + /// startWidth: 10, + /// ); + /// ``` + /// + final double startWidth; + + /// Specifies the end width of the range. + /// + /// Defaults to 5.0. + /// + /// This snippet shows how to set endWidth for [LinearGaugeRange]. + /// + /// ```dart + /// + /// LinearGaugeRange( + /// endValue: 500.0, + /// endWidth: 250, + /// ); + /// ``` + /// + final double endWidth; + + /// Specifies the mid width value of the range. + /// + /// + /// This snippet shows how to set midWidth for [LinearGaugeRange]. + /// + /// ```dart + /// + /// LinearGaugeRange( + /// midValue: 100.0, + /// midWidth: 150, + /// ); + /// ``` + /// + final double midWidth; + + /// Specifies the color value of the range. + /// + /// Defaults to Color(0xffF45656) in LightTheme + /// and Color(0xffFF7B7B) in DarkTheme. + /// + /// This snippet shows how to set the color for a [LinearGaugeRange]. + /// + /// ```dart + /// + /// LinearGaugeRange( + /// color: Colors.Red, + /// ); + /// ``` + /// + final Color? color; + + /// Specifies the shape type of a range as flat or curve. + /// + /// Defaults to [LinearRangeShapeType.flat]. + /// + /// This snippet shows how to set rangeShapeType for [LinearGaugeRange]. + /// + /// ```dart + /// + /// LinearGaugeRange( + /// rangeShapeType: LinearRangeShapeType.flat, + /// ); + /// ``` + /// + final LinearRangeShapeType rangeShapeType; + + /// Specifies the edge style of the range. + /// + /// Defaults to [LinearEdgeStyle.bothFlat]. + /// + /// This snippet shows how to set rangeShapeType for a range in [LinearGaugeRange]. + /// + /// ```dart + /// + /// LinearGaugeRange( + /// edgeStyle: LinearEdgeStyle.StartCurve, + /// ); + /// ``` + /// + final LinearEdgeStyle edgeStyle; + + /// Called to get the gradient color for the range. + /// + /// Defaults to null. + /// + /// This snippet shows how to set shaderCallback for a range in [LinearGaugeRange] + /// + /// ```dart + /// + /// LinearGaugeRange( + /// shaderCallback: (Rect bounds) { + /// return LinearGradient(colors: [ + /// Color(0xffE8DA5D), + /// Color(0xffFB7D55), + /// ], stops: [ + /// 0.4, + /// 0.9 + /// ]).createShader(bounds); + /// ) + /// ``` + /// + + final ShaderCallback? shaderCallback; + + /// Specifies position of a range with respect to axis. + /// + /// Defaults to [LinearElementPosition.outside]. + /// + /// This snippet shows how to set the range position in [LinearGaugeRange]. + /// + /// ```dart + /// + /// LinearGaugeRange( + /// position: LinearElementPosition.inside, + /// ); + /// ``` + /// + final LinearElementPosition position; + + @override + RenderObject createRenderObject(BuildContext context) { + final linearGaugeScope = LinearGaugeScope.of(context); + final ThemeData theme = Theme.of(context); + final bool isDarkTheme = theme.brightness == Brightness.dark; + return RenderLinearRange( + color: color ?? (isDarkTheme ? Color(0xffFF7B7B) : Color(0xffF45656)), + position: position, + startValue: startValue, + midValue: midValue, + endValue: endValue, + startThickness: startWidth, + midThickness: midWidth, + endThickness: endWidth, + rangeShapeType: rangeShapeType, + shaderCallback: shaderCallback, + edgeStyle: edgeStyle, + orientation: linearGaugeScope.orientation, + isMirrored: linearGaugeScope.isMirrored, + isAxisInversed: linearGaugeScope.isAxisInversed, + rangeAnimation: linearGaugeScope.animation); + } + + @override + void updateRenderObject( + BuildContext context, RenderLinearRange renderObject) { + final linearGaugeScope = LinearGaugeScope.of(context); + final ThemeData theme = Theme.of(context); + final bool isDarkTheme = theme.brightness == Brightness.dark; + renderObject + ..color = color ?? (isDarkTheme ? Color(0xffFF7B7B) : Color(0xffF45656)) + ..position = position + ..startValue = startValue + ..midValue = midValue + ..endValue = endValue + ..startThickness = startWidth + ..midThickness = midWidth + ..endThickness = endWidth + ..rangeShapeType = rangeShapeType + ..edgeStyle = edgeStyle + ..shaderCallback = shaderCallback + ..orientation = linearGaugeScope.orientation + ..isMirrored = linearGaugeScope.isMirrored + ..isAxisInversed = linearGaugeScope.isAxisInversed + ..rangeAnimation = linearGaugeScope.animation; + + super.updateRenderObject(context, renderObject); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/range/linear_gauge_range_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/range/linear_gauge_range_renderer.dart new file mode 100644 index 000000000..10c176706 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/range/linear_gauge_range_renderer.dart @@ -0,0 +1,467 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../linear_gauge/axis/linear_axis_renderer.dart'; +import '../../linear_gauge/utils/enum.dart'; +import '../../linear_gauge/utils/linear_gauge_helper.dart'; + +/// Represents the render object of range element. +class RenderLinearRange extends RenderOpacity { + ///Creates a instance for RenderLinearRange. + RenderLinearRange( + {required double startValue, + required double midValue, + required double endValue, + required double startThickness, + required double midThickness, + required double endThickness, + required Color color, + required LinearElementPosition position, + required LinearRangeShapeType rangeShapeType, + ShaderCallback? shaderCallback, + required LinearEdgeStyle edgeStyle, + required LinearGaugeOrientation orientation, + Animation? rangeAnimation, + required bool isAxisInversed, + required bool isMirrored, + RenderBox? child}) + : _startValue = startValue, + _midValue = midValue, + _endValue = endValue, + _startThickness = startThickness, + _midThickness = midThickness, + _endThickness = endThickness, + _color = color, + _position = position, + _rangeShapeType = rangeShapeType, + _shaderCallback = shaderCallback, + _edgeStyle = edgeStyle, + _orientation = orientation, + _rangeAnimation = rangeAnimation, + _isAxisInversed = isAxisInversed, + _isMirrored = isMirrored { + _rangePaint = Paint()..color = Colors.black12; + _rangeOffsets = List.filled(5, Offset(0, 0), growable: false); + _isHorizontal = orientation == LinearGaugeOrientation.horizontal; + _path = Path(); + } + + late Paint _rangePaint; + late List _rangeOffsets; + late Offset _rangeOffset; + late bool _isHorizontal; + late Path _path; + + Rect _rangeRect = Rect.zero; + + /// Gets or Sets the axis assigned to [RenderLinearRange]. + RenderLinearAxis? axis; + + /// Gets the range animation assigned to [RenderLinearRange]. + Animation? get rangeAnimation => _rangeAnimation; + Animation? _rangeAnimation; + + /// Gets the range animation assigned to [RenderLinearRange]. + set rangeAnimation(Animation? value) { + if (value == _rangeAnimation) { + return; + } + + _removeAnimationListener(); + _rangeAnimation = value; + _addAnimationListener(); + } + + /// Gets the isAxisInversed assigned to [RenderLinearRange]. + bool get isAxisInversed => _isAxisInversed; + bool _isAxisInversed; + + /// Sets the isInverse for [RenderLinearAxis]. + set isAxisInversed(bool value) { + if (value == _isAxisInversed) { + return; + } + + _isAxisInversed = value; + markNeedsLayout(); + } + + /// Gets the orientation assigned to [RenderLinearRange]. + /// + /// Default value is [GaugeOrientation.horizontal]. + /// + LinearGaugeOrientation get orientation => _orientation; + LinearGaugeOrientation _orientation; + + /// Sets the orientation for [RenderLinearRange]. + /// + /// Default value is [GaugeOrientation.horizontal]. + set orientation(LinearGaugeOrientation value) { + if (value == _orientation) { + return; + } + _orientation = value; + _isHorizontal = orientation == LinearGaugeOrientation.horizontal; + markNeedsLayout(); + } + + /// Gets the isMirrored to [RenderLinearRange]. + bool get isMirrored => _isMirrored; + bool _isMirrored; + + /// Sets the isMirrored for [RenderLinearRange]. + set isMirrored(bool value) { + if (value == _isMirrored) { + return; + } + + _isMirrored = value; + markNeedsPaint(); + } + + /// Gets the color assigned to [RenderLinearRange]. + Color get color => _color; + Color _color; + + /// Sets the color for [RenderLinearRange]. + set color(Color value) { + if (value == _color) { + return; + } + _color = value; + markNeedsPaint(); + } + + /// Gets the position assigned to [RenderLinearRange]. + LinearElementPosition get position => _position; + LinearElementPosition _position; + + /// Sets the position for [RenderLinearRange]. + set position(LinearElementPosition value) { + if (value == _position) { + return; + } + _position = value; + markNeedsLayout(); + } + + /// Gets the startValue assigned to [RenderLinearRange]. + double get startValue => _startValue; + double _startValue; + + /// Sets the startValue for [RenderLinearRange]. + set startValue(double value) { + if (value == _startValue) { + return; + } + + _startValue = value; + markNeedsLayout(); + } + + /// Gets the midValue assigned to [RenderLinearRange]. + double get midValue => _midValue; + double _midValue; + + /// Sets the midValue for [RenderLinearRange]. + set midValue(double value) { + if (value == _midValue) { + return; + } + _midValue = value; + markNeedsLayout(); + } + + /// Gets the endValue assigned to [RenderLinearRange]. + double get endValue => _endValue; + double _endValue; + + /// Sets the endValue for [RenderLinearRange]. + set endValue(double value) { + if (value == _endValue) { + return; + } + _endValue = value; + markNeedsLayout(); + } + + /// Gets the startThickness assigned to [RenderLinearRange]. + double get startThickness => _startThickness; + double _startThickness; + + /// Sets the startThickness for [RenderLinearRange]. + set startThickness(double value) { + if (value == _startThickness) { + return; + } + + _startThickness = value; + markNeedsLayout(); + } + + /// Gets the midThickness assigned to [RenderLinearRange]. + double get midThickness => _midThickness; + double _midThickness; + + /// Sets the midThickness for [RenderLinearRange]. + set midThickness(double value) { + if (value == _midThickness) { + return; + } + _midThickness = value; + markNeedsLayout(); + } + + /// Gets the endThickness assigned to [RenderLinearRange]. + double get endThickness => _endThickness; + double _endThickness; + + /// Sets the endThickness for [RenderLinearRange].. + set endThickness(double value) { + if (value == _endThickness) { + return; + } + _endThickness = value; + markNeedsLayout(); + } + + /// Gets the rangeShapeType assigned to [RenderLinearRange]. + LinearRangeShapeType get rangeShapeType => _rangeShapeType; + LinearRangeShapeType _rangeShapeType; + + /// Sets the rangeShapeType for [RenderLinearRange]. + set rangeShapeType(LinearRangeShapeType value) { + if (value == _rangeShapeType) { + return; + } + _rangeShapeType = value; + markNeedsPaint(); + } + + /// Gets the edgeStyle assigned to [RenderLinearRange]. + LinearEdgeStyle get edgeStyle => _edgeStyle; + LinearEdgeStyle _edgeStyle; + + /// Sets the edgeStyle for [RenderLinearRange]. + set edgeStyle(LinearEdgeStyle value) { + if (value == _edgeStyle) { + return; + } + _edgeStyle = value; + markNeedsPaint(); + } + + /// Gets the shader callback assigned to [RenderLinearRange]. + ShaderCallback? get shaderCallback => _shaderCallback; + ShaderCallback? _shaderCallback; + + /// Sets the shader callback for [RenderLinearRange]. + set shaderCallback(ShaderCallback? value) { + if (value == _shaderCallback) { + return; + } + _shaderCallback = value; + markNeedsPaint(); + } + + void _updateAnimation() { + if (child != null) { + opacity = rangeAnimation!.value; + } else { + markNeedsPaint(); + } + } + + void _addAnimationListener() { + if (_rangeAnimation != null) { + _rangeAnimation?.addListener(_updateAnimation); + } + } + + void _removeAnimationListener() { + if (_rangeAnimation != null) { + _rangeAnimation?.removeListener(_updateAnimation); + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _addAnimationListener(); + } + + @override + void detach() { + _removeAnimationListener(); + super.detach(); + } + + @override + void performLayout() { + final thickness = max(max(startThickness, midThickness), endThickness); + double rangeWidth = 0; + + if (axis != null) { + rangeWidth = + (axis!.valueToPixel(endValue) - axis!.valueToPixel(startValue)).abs(); + } + + Size controlSize; + if (_isHorizontal) { + controlSize = Size(rangeWidth, thickness); + } else { + controlSize = Size(thickness, rangeWidth); + } + + if (child != null) { + child!.layout(BoxConstraints.tight(controlSize)); + } + + size = controlSize; + } + + ///Calculation Position based on value. + double _getPosition(double value) { + double factor = (value - startValue) / (endValue - startValue); + if (_isHorizontal) { + factor = isAxisInversed ? 1 - factor : factor; + return (factor * _rangeRect.width) + _rangeOffset.dx; + } else { + factor = isAxisInversed ? factor : 1 - factor; + return (factor * _rangeRect.height) + _rangeOffset.dy; + } + } + + double _getRangePosition() { + return _isHorizontal ? _rangeOffset.dy : _rangeOffset.dx; + } + + void _getRangeOffsets() { + final rangeElementPosition = + LinearGaugeHelper.getEffectiveElementPosition(position, isMirrored); + double _bottom = _rangeOffset.dy + _rangeRect.height; + + if (orientation == LinearGaugeOrientation.vertical) { + _bottom = _rangeOffset.dx + _rangeRect.width; + } + + final _leftStart = _getPosition(startValue); + final _leftMid = _getPosition(midValue.clamp(startValue, endValue)); + final _leftEnd = _getPosition(endValue); + double _topStart = _bottom - startThickness; + double _topMid = _bottom - midThickness; + double _topEnd = _bottom - endThickness; + + if (rangeElementPosition == LinearElementPosition.inside) { + _topStart = _getRangePosition() + startThickness; + _topMid = _getRangePosition() + midThickness; + _topEnd = _getRangePosition() + endThickness; + _bottom = _getRangePosition(); + } + + _rangeOffsets[0] = Offset(_leftStart, _topStart); + _rangeOffsets[1] = Offset(_leftMid, _topMid); + _rangeOffsets[2] = Offset(_leftEnd, _topEnd); + _rangeOffsets[3] = Offset(_leftEnd, _bottom); + _rangeOffsets[4] = Offset(_leftStart, _bottom); + + if (orientation == LinearGaugeOrientation.vertical) { + for (int i = 0; i < 5; i++) { + _rangeOffsets[i] = Offset(_rangeOffsets[i].dy, _rangeOffsets[i].dx); + } + } + } + + /// Draws the both flat range style. + void _drawRangeStyle(Path path) { + path.moveTo(_rangeOffsets[0].dx, _rangeOffsets[0].dy); + if (rangeShapeType == LinearRangeShapeType.flat) { + path.lineTo(_rangeOffsets[1].dx, _rangeOffsets[1].dy); + path.lineTo(_rangeOffsets[2].dx, _rangeOffsets[2].dy); + } else { + path.quadraticBezierTo(_rangeOffsets[1].dx, _rangeOffsets[1].dy, + _rangeOffsets[2].dx, _rangeOffsets[2].dy); + } + + path.lineTo(_rangeOffsets[3].dx, _rangeOffsets[3].dy); + path.lineTo(_rangeOffsets[4].dx, _rangeOffsets[4].dy); + } + + void _getRangePath() { + if (startThickness == endThickness && startThickness == midThickness) { + final rangeRect = Rect.fromLTRB(_rangeOffsets[0].dx, _rangeOffsets[0].dy, + _rangeOffsets[3].dx, _rangeOffsets[3].dy); + + if (rangeRect.hasNaN) { + return; + } + + switch (edgeStyle) { + case LinearEdgeStyle.bothFlat: + _path.addRect(rangeRect); + break; + case LinearEdgeStyle.bothCurve: + _path.addRRect((RRect.fromRectAndRadius( + rangeRect, Radius.circular(startThickness / 2)))); + break; + case LinearEdgeStyle.startCurve: + _path.addRRect(LinearGaugeHelper.getStartCurve( + isHorizontal: _isHorizontal, + isAxisInversed: isAxisInversed, + rect: rangeRect, + radius: startThickness / 2)); + break; + case LinearEdgeStyle.endCurve: + _path.addRRect(LinearGaugeHelper.getEndCurve( + isHorizontal: _isHorizontal, + isAxisInversed: isAxisInversed, + rect: rangeRect, + radius: startThickness / 2)); + break; + default: + break; + } + } else { + _drawRangeStyle(_path); + } + + _path.close(); + } + + /// Draws the range element. + void _drawRangeElement(Canvas canvas) { + double animationValue = 1; + if (_rangeAnimation != null) { + animationValue = _rangeAnimation!.value; + } + + _rangePaint.color = color.withOpacity(animationValue * color.opacity); + _path.reset(); + _getRangePath(); + canvas.drawPath(_path, _rangePaint); + } + + @override + void paint(PaintingContext context, Offset offset) { + if ((_rangeAnimation != null && _rangeAnimation!.value > 0) || + rangeAnimation == null) { + final Canvas canvas = context.canvas; + _rangePaint.style = PaintingStyle.fill; + _rangeOffset = offset; + _rangePaint.color = color; + _rangeRect = Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height); + if (shaderCallback != null) { + _rangePaint.shader = shaderCallback!(_rangeRect); + } + + _getRangeOffsets(); + _drawRangeElement(canvas); + + // Painting the child render box. + super.paint(context, offset); + } + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/enum.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/enum.dart new file mode 100644 index 000000000..b00dcbc18 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/enum.dart @@ -0,0 +1,107 @@ +/// Orientation of the linear gauge. +enum LinearGaugeOrientation { + ///LinearGaugeOrientation.horizontal will align the linear gauge in vertical orientation. + vertical, + + ///LinearGaugeOrientation.horizontal will align the linear gauge in horizontal orientation. + horizontal +} + +/// Apply the shape style for range element. +enum LinearRangeShapeType { + /// LinearRangeShapeType.flat apply the flat shape between start and end value. + flat, + + /// LinearRangeShapeType.curve apply the curve shape between start and end value. + curve +} + +/// Apply the edge style for range pointer. +enum LinearEdgeStyle { + /// LinearEdgeStyle.bothFlat does not apply the rounded corner on both side + bothFlat, + + /// LinearEdgeStyle.bothCurve apply the rounded corner on both side. + bothCurve, + + /// LinearEdgeStyle.startCurve apply the rounded corner on start(left) side. + startCurve, + + /// LinearEdgeStyle.endCurve apply the rounded corner on end(right) side. + endCurve +} + +/// Apply the different marker pointer. +enum LinearShapePointerType { + /// LinearShapePointerType.invertedTriangle points the value with inverted triangle. + invertedTriangle, + + /// LinearShapePointerType.triangle points the value with triangle. + triangle, + + /// LinearShapePointerType.circle points the value with circle. + circle, + + /// LinearShapePointerType.rectangle points the value with rectangle + rectangle, + + /// LinearShapePointerType.diamond points the value with diamond. + diamond, +} + +/// Apply the different types of animation to pointer. +enum LinearAnimationType { + /// LinearAnimationType.bounceOut animates the pointer with Curves.bounceOut. + bounceOut, + + /// LinearAnimationType.ease animates the pointer with Curves.ease. + ease, + + /// LinearAnimationType.easeInCirc animates the pointer with Curves.easeInCirc. + easeInCirc, + + /// LinearAnimationType.easeOutBack animates the pointer with Curves.easeOutBack. + easeOutBack, + + /// LinearAnimationType.elasticOut animates the pointer with Curves.elasticOut. + elasticOut, + + /// LinearAnimationType.linear animates the pointer with Curves.linear. + linear, + + /// LinearAnimationType.slowMiddle animates the pointers with Curves.slowMiddle. + slowMiddle +} + +/// Apply the different label position based on Axis. +enum LinearLabelPosition { + /// LinearAnimationType.inside places the elements inside the axis. + inside, + + /// LinearAnimationType.outside places the elements inside the axis. + outside +} + +/// Apply the different element position based on Axis. +enum LinearElementPosition { + /// LinearElementPosition.inside places the elements inside the axis. + inside, + + /// LinearElementPosition.outside places the elements outside the axis. + outside, + + /// LinearElementPosition.cross places the elements cross the axis. + cross +} + +/// Apply the different pointer alignment based on Axis. +enum LinearMarkerAlignment { + /// LinearMarkerAlignment.center points the axis from center position. + center, + + /// LinearMarkerAlignment.start points the axis from inside position. + start, + + /// LinearMarkerAlignment.end points the axis from outside position. + end +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/linear_gauge_helper.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/linear_gauge_helper.dart new file mode 100644 index 000000000..2161b59c5 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/linear_gauge_helper.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../linear_gauge/utils/enum.dart'; + +/// This class provide the start and end curve paths. +class LinearGaugeHelper { + static RRect _getHorizontalStartCurve(Rect rect, double radius) { + return RRect.fromRectAndCorners(rect, + topLeft: Radius.circular(radius), bottomLeft: Radius.circular(radius)); + } + + static RRect _getHorizontalEndCurvePath(Rect rect, double radius) { + return RRect.fromRectAndCorners(rect, + topRight: Radius.circular(radius), + bottomRight: Radius.circular(radius)); + } + + static RRect _getVerticalStartCurve(Rect rect, double radius) { + return RRect.fromRectAndCorners(rect, + topLeft: Radius.circular(radius), topRight: Radius.circular(radius)); + } + + static RRect _getVerticalEndCurvePath(Rect rect, double radius) { + return RRect.fromRectAndCorners(rect, + bottomLeft: Radius.circular(radius), + bottomRight: Radius.circular(radius)); + } + + /// Returns the start curve path. + static RRect getStartCurve( + {required bool isHorizontal, + required bool isAxisInversed, + required Rect rect, + required double radius}) { + if (isHorizontal) { + return (!isAxisInversed + ? _getHorizontalStartCurve(rect, radius) + : _getHorizontalEndCurvePath(rect, radius)); + } else { + return (!isAxisInversed + ? _getVerticalEndCurvePath(rect, radius) + : _getVerticalStartCurve(rect, radius)); + } + } + + /// Returns the end curve path. + static RRect getEndCurve( + {required bool isHorizontal, + required bool isAxisInversed, + required Rect rect, + required double radius}) { + if (isHorizontal) { + return (!isAxisInversed + ? _getHorizontalEndCurvePath(rect, radius) + : _getHorizontalStartCurve(rect, radius)); + } else { + return (!isAxisInversed + ? _getVerticalStartCurve(rect, radius) + : _getVerticalEndCurvePath(rect, radius)); + } + } + + /// Returns the effective element position. + static LinearElementPosition getEffectiveElementPosition( + LinearElementPosition position, bool isMirrored) { + if (isMirrored) { + return (position == LinearElementPosition.inside) + ? LinearElementPosition.outside + : (position == LinearElementPosition.outside) + ? LinearElementPosition.inside + : LinearElementPosition.cross; + } + + return position; + } + + /// Returns the effective label position. + static LinearLabelPosition getEffectiveLabelPosition( + LinearLabelPosition labelPlacement, bool isMirrored) { + if (isMirrored) { + labelPlacement = (labelPlacement == LinearLabelPosition.inside) + ? LinearLabelPosition.outside + : LinearLabelPosition.inside; + } + + return labelPlacement; + } + + /// Returns the curve animation function based on the animation type + static Curve getCurveAnimation(LinearAnimationType type) { + Curve curve = Curves.linear; + switch (type) { + case LinearAnimationType.bounceOut: + curve = Curves.bounceOut; + break; + case LinearAnimationType.ease: + curve = Curves.ease; + break; + case LinearAnimationType.easeInCirc: + curve = Curves.easeInCirc; + break; + case LinearAnimationType.easeOutBack: + curve = Curves.easeOutBack; + break; + case LinearAnimationType.elasticOut: + curve = Curves.elasticOut; + break; + case LinearAnimationType.linear: + curve = Curves.linear; + break; + case LinearAnimationType.slowMiddle: + curve = Curves.slowMiddle; + break; + default: + break; + } + return curve; + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/linear_gauge_typedef.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/linear_gauge_typedef.dart new file mode 100644 index 000000000..ec04c7c42 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/linear_gauge_typedef.dart @@ -0,0 +1,14 @@ +import '../../linear_gauge/axis/linear_axis_label.dart'; + +/// Signature used by [SfLinearGauge] to build a representation of the +/// custom labels. +typedef GenerateLabelsCallback = List Function(); + +/// Signature used by [SfLinearGauge] for value to factor. +typedef ValueToFactorCallback = double Function(double value); + +/// Signature used by [SfLinearGauge] for factor to value. +typedef FactorToValueCallback = double Function(double factor); + +/// Signature used by [SfLinearGauge] for label formatting. +typedef LabelFormatterCallback = String Function(String value); diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/annotation/gauge_annotation.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/annotation/gauge_annotation.dart index eea3c9270..ee8c262f7 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/annotation/gauge_annotation.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/annotation/gauge_annotation.dart @@ -1,4 +1,7 @@ -part of gauges; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../utils/enum.dart'; /// [RadialAxis] allows to add widgets such as text and image as /// an annotation to a specific point of interest in the radial gauge. @@ -26,14 +29,13 @@ class GaugeAnnotation { /// The arguments [positionFactor] must not be null and [positionFactor] must /// be non-negative. GaugeAnnotation( - {this.widget, + {required this.widget, this.axisValue, this.horizontalAlignment = GaugeAlignment.center, this.angle, this.verticalAlignment = GaugeAlignment.center, this.positionFactor = 0}) - : assert(positionFactor != null, 'Position factor must not be null.'), - assert( + : assert( positionFactor >= 0, 'Position factor must be greater than zero.'); /// Specifies the annotation widget. @@ -74,7 +76,7 @@ class GaugeAnnotation { /// )); ///} /// ``` - final double axisValue; + final double? axisValue; /// How the annotation should be aligned horizontally in the respective /// position. @@ -165,7 +167,7 @@ class GaugeAnnotation { /// )); ///} /// ``` - final double angle; + final double? angle; @override bool operator ==(Object other) { @@ -185,7 +187,7 @@ class GaugeAnnotation { @override int get hashCode { - final List values = [ + final List values = [ widget, axisValue, horizontalAlignment, diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/gauge_axis.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/gauge_axis.dart index a46844353..0c4db2e95 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/gauge_axis.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/gauge_axis.dart @@ -1,4 +1,12 @@ -part of gauges; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show NumberFormat; +import '../annotation/gauge_annotation.dart'; +import '../common/common.dart'; +import '../pointers/gauge_pointer.dart'; +import '../range/gauge_range.dart'; +import '../utils/enum.dart'; /// [GaugeAxis] has properties for customizing axis elements such as labels, /// ticks and axis lines. @@ -37,10 +45,10 @@ abstract class GaugeAxis { this.ticksPosition = ElementsPosition.inside, this.labelsPosition = ElementsPosition.inside, this.offsetUnit = GaugeSizeUnit.logicalPixel, - GaugeTextStyle axisLabelStyle, - AxisLineStyle axisLineStyle, - MajorTickStyle majorTickStyle, - MajorTickStyle minorTickStyle}) + GaugeTextStyle? axisLabelStyle, + AxisLineStyle? axisLineStyle, + MajorTickStyle? majorTickStyle, + MinorTickStyle? minorTickStyle}) : axisLabelStyle = axisLabelStyle ?? GaugeTextStyle( fontSize: 12.0, @@ -70,7 +78,7 @@ abstract class GaugeAxis { /// )); ///} /// ``` - final List ranges; + final List? ranges; /// Add a list of gauge pointer to the radial gauge and customize /// each pointer by adding it to the [pointers] collection. @@ -87,7 +95,7 @@ abstract class GaugeAxis { /// )); ///} /// ``` - final List pointers; + final List? pointers; /// Add a list of gauge annotation to the radial gauge and customize /// each annotation by adding it to the [annotations] collection. @@ -106,7 +114,7 @@ abstract class GaugeAxis { /// )); ///} /// ``` - final List annotations; + final List? annotations; /// The minimum value for the axis. /// @@ -160,7 +168,7 @@ abstract class GaugeAxis { /// )); ///} /// ``` - final double interval; + final double? interval; /// Add minor ticks count per interval. /// @@ -343,7 +351,7 @@ abstract class GaugeAxis { /// )); ///} /// ``` - final String labelFormat; + final String? labelFormat; /// Formats the axis labels with globalized label formats. /// @@ -361,7 +369,7 @@ abstract class GaugeAxis { /// )); ///} /// ``` - final NumberFormat numberFormat; + final NumberFormat? numberFormat; /// Positions the tick lines inside or outside the axis line. /// @@ -546,36 +554,5 @@ abstract class GaugeAxis { /// } /// /// ``` - final GaugeAxisRendererFactory onCreateAxisRenderer; -} - -/// Represents the renderer for gauge axis -abstract class GaugeAxisRenderer { - /// Represents the gauge axis - GaugeAxis axis; - - /// Returns the visible labels on [GaugeAxis] - /// - /// Modify the actual labels generated, which are calculated on the basis - /// of scale range and interval. - /// Generate your own labels based on needs, in order to be shown in - /// the gauge. - List generateVisibleLabels(); - - /// Returns converted factor value from the axis value. - /// - /// The arguments to this method is axis value. - /// The calculated value of the factor should be between 0 and 1. - /// If the axis range from 0 to 100 and pass the axis value is 50, - /// this method return factor value is 0.5. - /// Overriding method, you can modify the factor value based on needs. - double valueToFactor(double value); - - /// Returns converted axis value from the factor. - /// - /// The arguments to this method is factor which value between 0 to 1. - /// If the axis range from 0 to 100 and pass the factor value is 0.5, - /// this method return axis value is 50. - /// Overriding method, you can modify the axis value based on needs. - double factorToValue(double factor); + final GaugeAxisRendererFactory? onCreateAxisRenderer; } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis.dart index e8c14aa75..7739fdd03 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis.dart @@ -1,4 +1,13 @@ -part of gauges; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show NumberFormat; +import '../annotation/gauge_annotation.dart'; +import '../common/common.dart'; +import '../pointers/gauge_pointer.dart'; +import '../range/gauge_range.dart'; +import '../utils/enum.dart'; +import 'gauge_axis.dart'; /// The [RadialAxis] is a circular arc in which a set of values are /// displayed along a linear or custom scale @@ -34,46 +43,37 @@ class RadialAxis extends GaugeAxis { this.showFirstLabel = true, this.showLastLabel = false, this.canScaleToFit = false, - List ranges, - List pointers, - List annotations, - GaugeTextStyle axisLabelStyle, - AxisLineStyle axisLineStyle, - MajorTickStyle majorTickStyle, - MinorTickStyle minorTickStyle, + List? ranges, + List? pointers, + List? annotations, + GaugeTextStyle? axisLabelStyle, + AxisLineStyle? axisLineStyle, + MajorTickStyle? majorTickStyle, + MinorTickStyle? minorTickStyle, this.backgroundImage, - GaugeAxisRendererFactory onCreateAxisRenderer, + GaugeAxisRendererFactory? onCreateAxisRenderer, double minimum = 0, double maximum = 100, - double interval, - double minorTicksPerInterval, - bool showLabels, - bool showAxisLine, - bool showTicks, + double? interval, + double minorTicksPerInterval = 1, + bool showLabels = true, + bool showAxisLine = true, + bool showTicks = true, double tickOffset = 0, double labelOffset = 15, - bool isInversed, - GaugeSizeUnit offsetUnit, - num maximumLabels = 3, - bool useRangeColorForAxis, - String labelFormat, - NumberFormat numberFormat, - ElementsPosition ticksPosition, - ElementsPosition labelsPosition}) - : assert(minimum != null, 'Minimum must not be null.'), - assert(maximum != null, 'Maximum must not be null.'), - assert(startAngle != null, 'Start angle must not be null.'), - assert(endAngle != null, 'End angle must not be null.'), - assert(radiusFactor != null, 'Radius factor must not be null.'), - assert(centerX != null, 'Center X must not be null.'), - assert(centerY != null, 'Center Y must not be null.'), - assert( + bool isInversed = false, + GaugeSizeUnit offsetUnit = GaugeSizeUnit.logicalPixel, + int maximumLabels = 3, + bool useRangeColorForAxis = false, + String? labelFormat, + NumberFormat? numberFormat, + ElementsPosition ticksPosition = ElementsPosition.inside, + ElementsPosition labelsPosition = ElementsPosition.inside}) + : assert( radiusFactor >= 0, 'Radius factor must be a non-negative value.'), assert(centerX >= 0, 'Center X must be a non-negative value.'), assert(centerY >= 0, 'Center Y must be a non-negative value.'), assert(minimum < maximum, 'Maximum should be greater than minimum.'), - assert(tickOffset != null, 'Tick offset should not be equal to null.'), - assert(labelOffset != null, 'Label offset must not be equal to null.'), super( ranges: ranges, annotations: annotations, @@ -82,20 +82,20 @@ class RadialAxis extends GaugeAxis { minimum: minimum, maximum: maximum, interval: interval, - minorTicksPerInterval: minorTicksPerInterval ?? 1, - showLabels: showLabels ?? true, - showAxisLine: showAxisLine ?? true, - showTicks: showTicks ?? true, + minorTicksPerInterval: minorTicksPerInterval, + showLabels: showLabels, + showAxisLine: showAxisLine, + showTicks: showTicks, tickOffset: tickOffset, labelOffset: labelOffset, - isInversed: isInversed ?? false, + isInversed: isInversed, maximumLabels: maximumLabels, - useRangeColorForAxis: useRangeColorForAxis ?? false, + useRangeColorForAxis: useRangeColorForAxis, labelFormat: labelFormat, - offsetUnit: offsetUnit ?? GaugeSizeUnit.logicalPixel, + offsetUnit: offsetUnit, numberFormat: numberFormat, - ticksPosition: ticksPosition ?? ElementsPosition.inside, - labelsPosition: labelsPosition ?? ElementsPosition.inside, + ticksPosition: ticksPosition, + labelsPosition: labelsPosition, axisLabelStyle: axisLabelStyle ?? GaugeTextStyle( fontSize: 12.0, @@ -274,7 +274,7 @@ class RadialAxis extends GaugeAxis { // } // } /// ``` - final ValueChanged onLabelCreated; + final ValueChanged? onLabelCreated; /// Callback, which is triggered by tapping an axis. /// @@ -292,7 +292,7 @@ class RadialAxis extends GaugeAxis { ///} /// /// ``` - final ValueChanged onAxisTapped; + final ValueChanged? onAxisTapped; /// whether to rotate the labels based on angle. /// @@ -334,7 +334,7 @@ class RadialAxis extends GaugeAxis { /// )); ///} ///``` - final ImageProvider backgroundImage; + final ImageProvider? backgroundImage; /// Adjust the half or quarter gauge to fit the axis boundary. /// @@ -400,7 +400,7 @@ class RadialAxis extends GaugeAxis { @override int get hashCode { - final List values = [ + final List values = [ startAngle, endAngle, hashList(ranges), @@ -442,1243 +442,3 @@ class RadialAxis extends GaugeAxis { return hashList(values); } } - -/// Represents the renderer for radial axis -class RadialAxisRenderer extends GaugeAxisRenderer { - /// Creates the instance for radial axis renderer - RadialAxisRenderer() { - _needsRepaintAxis = true; - } - - /// Specifies whether to include axis elements when calculating the radius - final bool _useAxisElementsInsideRadius = true; - - /// Specifies the axis corresponding to this renderer; - RadialAxis _axis; - - ///Specifies the axis rect - Rect _axisRect; - - /// Specifies the start radian value - double _startRadian; - - /// Specifies the end radian value - double _endRadian; - - ///Specifies the radius value - double _radius; - - ///Specifies the axis center - double _center; - - ///Specifies the center X value of axis - double _centerX; - - ///Specifies the center Y value od axis - double _centerY; - - /// Specifies the actual axis width - double _actualAxisWidth; - - /// Specifies the list of axis labels - List _axisLabels; - - /// Specifies the offset value of major ticks - List<_TickOffset> _majorTickOffsets; - - /// Specifies the offset value of minor ticks - List<_TickOffset> _minorTickOffsets; - - /// Specifies the major tick count - num _majorTicksCount; - - ///Holds the sweep angle of the axis - double _sweepAngle; - - /// Holds the size of the axis - Size _axisSize; - - /// Holds the length of major tick based on coordinate unit - double _actualMajorTickLength; - - /// Holds the length of minor tick based on coordinate unit - double _actualMinorTickLength; - - /// Specifies the maximum label size - Size _maximumLabelSize; - - /// Specifies whether the ticks are placed outside - bool _isTicksOutside; - - /// Specifies whether the labels are placed outside - bool _isLabelsOutside; - - /// Specifies the maximum length of tick by comparing major and minor tick - double _maximumTickLength; - - /// Specifies whether to repaint the axis; - bool _needsRepaintAxis; - - /// Specifies the axis path - Path _axisPath; - - /// Specifies the axis offset - double _axisOffset; - - /// Specifies the start corner radian - double _startCornerRadian; - - /// Specifies the sweep corner radian - double _sweepCornerRadian; - - /// Specifies the actual label offset - double _actualLabelOffset; - - /// Specifies the actual tick offset - double _actualTickOffset; - - /// Specifies the corner angle - double _cornerAngle; - - /// Specifies the listener - ImageStreamListener _listener; - - /// Specifies the background image info; - ImageInfo _backgroundImageInfo; - - /// Specifies the image stream - ImageStream _imageStream; - - /// Specifies the difference in the radius - double _diffInRadius; - - /// Specifies the center point of the axis - Offset _axisCenter; - - /// Specifies the rendering details corresponding to the gauge. - _RenderingDetails _renderingDetails; - - /// Specifies the actual interval of the axis - num _actualInterval; - - /// Specifies whether the maximum value is included in axis labels - bool _isMaxiumValueIncluded = false; - - /// To calculate the radius and the center point based on the angle - Offset _getAxisBounds() { - final Offset centerOffset = _getCenter(); - final double minScale = math.min(_axisSize.height, _axisSize.width); - final double x = ((centerOffset.dx * 2) - minScale) / 2; - final double y = ((centerOffset.dy * 2) - minScale) / 2; - Rect bounds = Rect.fromLTRB(x, y, minScale, minScale); - final double centerYDiff = (_axisSize.height / 2 - centerOffset.dy).abs(); - final double centerXDiff = (_axisSize.width / 2 - centerOffset.dx).abs(); - double diff = 0; - if (_axisSize.width > _axisSize.height) { - diff = centerYDiff / 2; - final double angleRadius = _axisSize.height / 2 + diff; - if (_axisSize.width / 2 < angleRadius) { - final double actualDiff = _axisSize.width / 2 - _axisSize.height / 2; - diff = actualDiff * 0.7; - } - - bounds = Rect.fromLTRB( - x - diff / 2, y, x + minScale + (diff / 2), y + minScale + diff); - } else { - diff = centerXDiff / 2; - final double angleRadius = _axisSize.width / 2 + diff; - - if (_axisSize.height / 2 < angleRadius) { - final double actualDiff = _axisSize.height / 2 - _axisSize.width / 2; - diff = actualDiff * 0.7; - } - - bounds = Rect.fromLTRB(x - diff / 2, y - diff / 2, - x + minScale + (diff / 2), y + minScale + (diff / 2)); - } - - _diffInRadius = diff; - - return Offset( - bounds.left + (bounds.width / 2), bounds.top + (bounds.height / 2)); - } - - /// Calculates the default values of the axis - void _calculateDefaultValues() { - _startRadian = _getDegreeToRadian(_axis.startAngle); - _sweepAngle = _getSweepAngle(); - _endRadian = _getDegreeToRadian(_sweepAngle); - _center = math.min(_axisSize.width / 2, _axisSize.height / 2); - - if (!_axis.canScaleToFit) { - _radius = _center * _axis.radiusFactor; - _centerX = (_axisSize.width / 2) - (_axis.centerX * _axisSize.width); - _centerY = (_axisSize.height / 2) - (_axis.centerY * _axisSize.height); - _axisCenter = Offset( - _axisSize.width / 2 - _centerX, _axisSize.height / 2 - _centerY); - } else { - final Offset centerPoint = _getAxisBounds(); - _centerX = centerPoint.dx; - _centerY = centerPoint.dy; - _radius = (_center + _diffInRadius) * _axis.radiusFactor; - _axisCenter = Offset(_centerX, _centerY); - } - - _actualAxisWidth = _getActualValue(_axis.axisLineStyle.thickness, - _axis.axisLineStyle.thicknessUnit, false); - _actualMajorTickLength = _getTickLength(true); - _actualMinorTickLength = _getTickLength(false); - _maximumTickLength = _actualMajorTickLength > _actualMinorTickLength - ? _actualMajorTickLength - : _actualMinorTickLength; - _actualLabelOffset = - _getActualValue(_axis.labelOffset, _axis.offsetUnit, true); - _actualTickOffset = - _getActualValue(_axis.tickOffset, _axis.offsetUnit, true); - if (_axis.backgroundImage != null) { - _listener = ImageStreamListener(_updateBackgroundImage); - } - } - - /// Method to calculate the axis range - void _calculateAxisRange(BoxConstraints constraints, BuildContext context, - SfGaugeThemeData gaugeThemeData, _RenderingDetails animationDetails) { - _renderingDetails = animationDetails; - _axisSize = Size(constraints.maxWidth, constraints.maxHeight); - _calculateAxisElementsPosition(context); - if (_axis.pointers != null && _axis.pointers.isNotEmpty) { - _renderPointers(); - } - - if (_axis.ranges != null && _axis.ranges.isNotEmpty) { - _renderRanges(); - } - } - - /// Methods to calculate axis elements position - void _calculateAxisElementsPosition(BuildContext context) { - _isTicksOutside = _axis.ticksPosition == ElementsPosition.outside; - _isLabelsOutside = _axis.labelsPosition == ElementsPosition.outside; - _calculateDefaultValues(); - _axisLabels = generateVisibleLabels(); - if (_axis.showLabels) { - _measureAxisLabels(); - } - - _axisOffset = _useAxisElementsInsideRadius ? _getAxisOffset() : 0; - - if (_axis.showTicks) { - _calculateMajorTicksPosition(); - _calculateMinorTickPosition(); - } - - if (_axis.showLabels) { - _calculateAxisLabelsPosition(); - } - - _calculateAxisRect(); - if (_axis.showAxisLine) { - _calculateCornerStylePosition(); - } - - if (_axis.backgroundImage != null && _backgroundImageInfo?.image == null) { - _loadBackgroundImage(context); - } - } - - /// To calculate the center based on the angle - Offset _getCenter() { - final double x = _axisSize.width / 2; - final double y = _axisSize.height / 2; - _radius = _center; - Offset actualCenter = Offset(x, y); - final double actualStartAngle = _getWrapAngle(_axis.startAngle, -630, 630); - final double actualEndAngle = - _getWrapAngle(_axis.startAngle + _sweepAngle.abs(), -630, 630); - final List regions = [ - -630, - -540, - -450, - -360, - -270, - -180, - -90, - 0, - 90, - 180, - 270, - 360, - 450, - 540, - 630 - ]; - final List region = []; - if (actualStartAngle < actualEndAngle) { - for (int i = 0; i < regions.length; i++) { - if (regions[i] > actualStartAngle && regions[i] < actualEndAngle) { - region.add(((regions[i] % 360) < 0 - ? (regions[i] % 360) + 360 - : (regions[i] % 360)) - .toInt()); - } - } - } else { - for (int i = 0; i < regions.length; i++) { - if (regions[i] < actualStartAngle && regions[i] > actualEndAngle) { - region.add(((regions[i] % 360) < 0 - ? (regions[i] % 360) + 360 - : (regions[i] % 360)) - .toInt()); - } - } - } - - final double startRadian = 2 * math.pi * (actualStartAngle / 360); - final double endRadian = 2 * math.pi * (actualEndAngle / 360); - final Offset startPoint = Offset(x + (_radius * math.cos(startRadian)), - y + (_radius * math.sin(startRadian))); - final Offset endPoint = Offset(x + (_radius * math.cos(endRadian)), - y + (_radius * math.sin(endRadian))); - - switch (region.length) { - case 0: - actualCenter = _getCenterForLengthZero( - startPoint, endPoint, x, y, _radius, region); - break; - case 1: - actualCenter = - _getCenterLengthOne(startPoint, endPoint, x, y, _radius, region); - break; - case 2: - actualCenter = - _getCenterForLengthTwo(startPoint, endPoint, x, y, _radius, region); - break; - case 3: - actualCenter = _getCenterForLengthThree( - startPoint, endPoint, x, y, _radius, region); - break; - } - - return actualCenter; - } - - /// Calculate the center point when the region length is zero - Offset _getCenterForLengthZero(Offset startPoint, Offset endPoint, double x, - double y, double radius, List region) { - final double longX = (x - startPoint.dx).abs() > (x - endPoint.dx).abs() - ? startPoint.dx - : endPoint.dx; - final double longY = (y - startPoint.dy).abs() > (y - endPoint.dy).abs() - ? startPoint.dy - : endPoint.dy; - final Offset midPoint = - Offset((x + longX).abs() / 2, (y + longY).abs() / 2); - final double xValue = x + (x - midPoint.dx); - final double yValue = y + (y - midPoint.dy); - return Offset(xValue, yValue); - } - - ///Calculates the center when the region length is two. - Offset _getCenterLengthOne(Offset startPoint, Offset endPoint, double x, - double y, double radius, List region) { - Offset point1; - Offset point2; - final double maxRadian = 2 * math.pi * region[0] / 360; - final Offset maxPoint = Offset( - x + (radius * math.cos(maxRadian)), y + (radius * math.sin(maxRadian))); - - switch (region[0]) { - case 270: - point1 = Offset(startPoint.dx, maxPoint.dy); - point2 = Offset(endPoint.dx, y); - break; - case 0: - case 360: - point1 = Offset(x, endPoint.dy); - point2 = Offset(maxPoint.dx, startPoint.dy); - break; - case 90: - point1 = Offset(endPoint.dx, y); - point2 = Offset(startPoint.dx, maxPoint.dy); - break; - case 180: - point1 = Offset(maxPoint.dx, startPoint.dy); - point2 = Offset(x, endPoint.dy); - break; - } - - final Offset midPoint = - Offset((point1.dx + point2.dx) / 2, (point1.dy + point2.dy) / 2); - final double xValue = - x + ((x - midPoint.dx) >= radius ? 0 : (x - midPoint.dx)); - final double yValue = - y + ((y - midPoint.dy) >= radius ? 0 : (y - midPoint.dy)); - return Offset(xValue, yValue); - } - - ///Calculates the center when the region length is two. - Offset _getCenterForLengthTwo(Offset startPoint, Offset endPoint, double x, - double y, double radius, List region) { - Offset point1; - Offset point2; - final double minRadian = 2 * math.pi * region[0] / 360; - final double maxRadian = 2 * math.pi * region[1] / 360; - final Offset maxPoint = Offset( - x + (radius * math.cos(maxRadian)), y + (radius * math.sin(maxRadian))); - final Offset minPoint = Offset( - x + (radius * math.cos(minRadian)), y + (radius * math.sin(minRadian))); - - if ((region[0] == 0 && region[1] == 90) || - (region[0] == 180 && region[1] == 270)) { - point1 = Offset(minPoint.dx, maxPoint.dy); - } else { - point1 = Offset(maxPoint.dx, minPoint.dy); - } - - if (region[0] == 0 || region[0] == 180) { - point2 = Offset(_getMinMaxValue(startPoint, endPoint, region[0]), - _getMinMaxValue(startPoint, endPoint, region[1])); - } else { - point2 = Offset(_getMinMaxValue(startPoint, endPoint, region[1]), - _getMinMaxValue(startPoint, endPoint, region[0])); - } - - final Offset midPoint = Offset( - (point1.dx - point2.dx).abs() / 2 >= radius - ? 0 - : (point1.dx + point2.dx) / 2, - (point1.dy - point2.dy).abs() / 2 >= radius - ? 0 - : (point1.dy + point2.dy) / 2); - final double xValue = x + - (midPoint.dx == 0 - ? 0 - : (x - midPoint.dx) >= radius - ? 0 - : (x - midPoint.dx)); - final double yValue = y + - (midPoint.dy == 0 - ? 0 - : (y - midPoint.dy) >= radius - ? 0 - : (y - midPoint.dy)); - return Offset(xValue, yValue); - } - - ///Calculates the center when the region length is three. - Offset _getCenterForLengthThree(Offset startPoint, Offset endPoint, double x, - double y, double radius, List region) { - final double region0Radian = 2 * math.pi * region[0] / 360; - final double region1Radian = 2 * math.pi * region[1] / 360; - final double region2Radian = 2 * math.pi * region[2] / 360; - final Offset region0Point = Offset(x + (radius * math.cos(region0Radian)), - y + (radius * math.sin(region0Radian))); - final Offset region1Point = Offset(x + (radius * math.cos(region1Radian)), - y + (radius * math.sin(region1Radian))); - final Offset region2Point = Offset(x + (radius * math.cos(region2Radian)), - y + (radius * math.sin(region2Radian))); - Offset regionStartPoint; - Offset regionEndPoint; - switch (region[2]) { - case 0: - case 360: - regionStartPoint = Offset(region0Point.dx, region1Point.dy); - regionEndPoint = - Offset(region2Point.dx, math.max(startPoint.dy, endPoint.dy)); - break; - case 90: - regionStartPoint = - Offset(math.min(startPoint.dx, endPoint.dx), region0Point.dy); - regionEndPoint = Offset(region1Point.dx, region2Point.dy); - break; - case 180: - regionStartPoint = - Offset(region2Point.dx, math.min(startPoint.dy, endPoint.dy)); - regionEndPoint = Offset(region0Point.dx, region1Point.dy); - break; - case 270: - regionStartPoint = Offset(region1Point.dx, region2Point.dy); - regionEndPoint = - Offset(math.max(startPoint.dx, endPoint.dx), region0Point.dy); - break; - } - - final Offset midRegionPoint = Offset( - (regionStartPoint.dx - regionEndPoint.dx).abs() / 2 >= radius - ? 0 - : (regionStartPoint.dx + regionEndPoint.dx) / 2, - (regionStartPoint.dy - regionEndPoint.dy).abs() / 2 >= radius - ? 0 - : (regionStartPoint.dy + regionEndPoint.dy) / 2); - final double xValue = x + - (midRegionPoint.dx == 0 - ? 0 - : (x - midRegionPoint.dx) >= radius - ? 0 - : (x - midRegionPoint.dx)); - final double yValue = y + - (midRegionPoint.dy == 0 - ? 0 - : (y - midRegionPoint.dy) >= radius - ? 0 - : (y - midRegionPoint.dy)); - return Offset(xValue, yValue); - } - - /// To calculate the value based on the angle - double _getMinMaxValue(Offset startPoint, Offset endPoint, int degree) { - final double minX = math.min(startPoint.dx, endPoint.dx); - final double minY = math.min(startPoint.dy, endPoint.dy); - final double maxX = math.max(startPoint.dx, endPoint.dx); - final double maxY = math.max(startPoint.dy, endPoint.dy); - switch (degree) { - case 270: - return maxY; - case 0: - case 360: - return minX; - case 90: - return minY; - case 180: - return maxX; - } - - return 0; - } - - /// To calculate the wrap angle - double _getWrapAngle(double angle, double min, double max) { - if (max - min == 0) { - return min; - } - - angle = ((angle - min) % (max - min)) + min; - while (angle < min) { - angle += max - min; - } - - return angle; - } - - /// Calculates the rounded corner position - void _calculateCornerStylePosition() { - final double cornerCenter = (_axisRect.right - _axisRect.left) / 2; - _cornerAngle = _cornerRadiusAngle(cornerCenter, _actualAxisWidth / 2); - - switch (_axis.axisLineStyle.cornerStyle) { - case CornerStyle.startCurve: - { - _startCornerRadian = _axis.isInversed - ? _getDegreeToRadian(-_cornerAngle) - : _getDegreeToRadian(_cornerAngle); - _sweepCornerRadian = _axis.isInversed - ? _getDegreeToRadian((-_sweepAngle) + _cornerAngle) - : _getDegreeToRadian(_sweepAngle - _cornerAngle); - } - break; - case CornerStyle.endCurve: - { - _startCornerRadian = _getDegreeToRadian(0); - _sweepCornerRadian = _axis.isInversed - ? _getDegreeToRadian((-_sweepAngle) + _cornerAngle) - : _getDegreeToRadian(_sweepAngle - _cornerAngle); - } - break; - case CornerStyle.bothCurve: - { - _startCornerRadian = _axis.isInversed - ? _getDegreeToRadian(-_cornerAngle) - : _getDegreeToRadian(_cornerAngle); - _sweepCornerRadian = _axis.isInversed - ? _getDegreeToRadian((-_sweepAngle) + (2 * _cornerAngle)) - : _getDegreeToRadian(_sweepAngle - (2 * _cornerAngle)); - } - break; - case CornerStyle.bothFlat: - _startCornerRadian = !_axis.isInversed - ? _getDegreeToRadian(0) - : _getDegreeToRadian(_axis.startAngle + _sweepAngle); - final double _value = _axis.isInversed ? -1 : 1; - _sweepCornerRadian = _getDegreeToRadian(_sweepAngle * _value); - break; - } - } - - /// Calculates the axis rect - void _calculateAxisRect() { - _axisRect = Rect.fromLTRB( - -(_radius - (_actualAxisWidth / 2 + _axisOffset)), - -(_radius - (_actualAxisWidth / 2 + _axisOffset)), - _radius - (_actualAxisWidth / 2 + _axisOffset), - _radius - (_actualAxisWidth / 2 + _axisOffset)); - _axisPath = Path(); - final Rect rect = Rect.fromLTRB( - _axisRect.left + _axisSize.width / 2, - _axisRect.top + _axisSize.height / 2, - _axisRect.right + _axisSize.width / 2, - _axisRect.bottom + _axisSize.height / 2, - ); - _axisPath.arcTo(rect, _startRadian, _endRadian, false); - } - - /// Method to calculate the angle from the tapped point - void _calculateAngleFromOffset(Offset offset) { - final double actualCenterX = _axisSize.width * _axis.centerX; - final double actualCenterY = _axisSize.height * _axis.centerY; - double angle = - math.atan2(offset.dy - actualCenterY, offset.dx - actualCenterX) * - (180 / math.pi) + - 360; - final double actualEndAngle = _axis.startAngle + _sweepAngle; - if (angle < 360 && angle > 180) { - angle += 360; - } - - if (angle > actualEndAngle) { - angle %= 360; - } - - if (angle >= _axis.startAngle && angle <= actualEndAngle) { - final double angleFactor = (angle - _axis.startAngle) / _sweepAngle; - final double value = factorToValue(angleFactor); - if (value >= _axis.minimum && value <= _axis.maximum) { - final double _tappedValue = _angleToValue(angle); - _axis.onAxisTapped(_tappedValue); - } - } - } - - /// Calculate the offset for axis line based on ticks and labels - double _getAxisOffset() { - double offset = 0; - offset = _isTicksOutside - ? _axis.showTicks - ? (_maximumTickLength + _actualTickOffset) - : 0 - : 0; - offset += _isLabelsOutside - ? _axis.showLabels - ? (math.max(_maximumLabelSize.height, _maximumLabelSize.width) / 2 + - _actualLabelOffset) - : 0 - : 0; - return offset; - } - - /// Converts the axis value to angle - double _valueToAngle(double value) { - double angle = 0; - value = _getMinMax(value, _axis.minimum, _axis.maximum); - if (!_axis.isInversed) { - angle = (_sweepAngle / (_axis.maximum - _axis.minimum).abs()) * - (_axis.minimum - value).abs(); - } else { - angle = _sweepAngle - - ((_sweepAngle / (_axis.maximum - _axis.minimum).abs()) * - (_axis.minimum - value).abs()); - } - - return angle; - } - - /// Converts the angle to corresponding axis value - double _angleToValue(double angle) { - double value = 0; - if (!_axis.isInversed) { - value = (angle - - _axis.startAngle + - ((_sweepAngle / (_axis.maximum - _axis.minimum)) * - _axis.minimum)) * - ((_axis.maximum - _axis.minimum) / _sweepAngle); - } else { - value = -(angle - - _axis.startAngle - - _sweepAngle + - ((_sweepAngle / (_axis.maximum - _axis.minimum)) * - _axis.minimum.abs())) * - ((_axis.maximum - _axis.minimum) / _sweepAngle); - } - - return value; - } - - /// Calculates the major ticks position - void _calculateMajorTicksPosition() { - if (_axisLabels != null && _axisLabels.isNotEmpty) { - double angularSpaceForTicks; - if (_actualInterval != null) { - _majorTicksCount = (_axis.maximum - _axis.minimum) / _actualInterval; - angularSpaceForTicks = - _getDegreeToRadian(_sweepAngle / (_majorTicksCount)); - } else { - _majorTicksCount = _axisLabels.length; - angularSpaceForTicks = - _getDegreeToRadian(_sweepAngle / (_majorTicksCount - 1)); - } - - final double axisLineWidth = _axis.showAxisLine ? _actualAxisWidth : 0; - double angleForTicks = 0; - double tickStartOffset = 0; - double tickEndOffset = 0; - _majorTickOffsets = <_TickOffset>[]; - angleForTicks = _axis.isInversed - ? _getDegreeToRadian(_axis.startAngle + _sweepAngle - 90) - : _getDegreeToRadian(_axis.startAngle - 90); - final double offset = _isLabelsOutside - ? _axis.showLabels - ? (math.max(_maximumLabelSize.height, _maximumLabelSize.width) / - 2 + - _actualLabelOffset) - : 0 - : 0; - if (!_isTicksOutside) { - tickStartOffset = - _radius - (axisLineWidth + _actualTickOffset + offset); - tickEndOffset = _radius - - (axisLineWidth + - _actualMajorTickLength + - _actualTickOffset + - offset); - } else { - final bool isGreater = _actualMajorTickLength > _actualMinorTickLength; - - // Calculates the major tick position based on the tick length - // and another features offset value - if (!_useAxisElementsInsideRadius) { - tickStartOffset = _radius + _actualTickOffset; - tickEndOffset = _radius + _actualMajorTickLength + _actualTickOffset; - } else { - tickStartOffset = isGreater - ? _radius - offset - : _radius - - (_maximumTickLength - _actualMajorTickLength + offset); - tickEndOffset = _radius - (offset + _maximumTickLength); - } - } - - _calculateOffsetForMajorTicks( - tickStartOffset, tickEndOffset, angularSpaceForTicks, angleForTicks); - } - } - - /// Calculates the offset for major ticks - void _calculateOffsetForMajorTicks(double tickStartOffset, - double tickEndOffset, double angularSpaceForTicks, double angleForTicks) { - final num length = - _actualInterval != null ? _majorTicksCount : _majorTicksCount - 1; - for (num i = 0; i <= length; i++) { - double tickAngle = 0; - final num count = - _actualInterval != null ? _majorTicksCount : _majorTicksCount - 1; - if (i == 0 || i == count) { - tickAngle = - _getTickPositionInCorner(i, angleForTicks, tickStartOffset, true); - } else { - tickAngle = angleForTicks; - } - final List tickPosition = - _getTickPosition(tickStartOffset, tickEndOffset, tickAngle); - final _TickOffset tickOffset = _TickOffset(); - tickOffset.startPoint = tickPosition[0]; - tickOffset.endPoint = tickPosition[1]; - tickOffset.value = factorToValue( - (_getRadianToDegree(tickAngle) + 90 - _axis.startAngle) / - _sweepAngle); - final Offset centerPoint = !_axis.canScaleToFit - ? Offset(_centerX, _centerY) - : const Offset(0, 0); - tickOffset.startPoint = Offset(tickOffset.startPoint.dx - centerPoint.dx, - tickOffset.startPoint.dy - centerPoint.dy); - tickOffset.endPoint = Offset(tickOffset.endPoint.dx - centerPoint.dx, - tickOffset.endPoint.dy - centerPoint.dy); - _majorTickOffsets.add(tickOffset); - if (_axis.isInversed) { - angleForTicks -= angularSpaceForTicks; - } else { - angleForTicks += angularSpaceForTicks; - } - } - } - - /// Returns the corresponding range color for the value - Color _getRangeColor(double value, SfGaugeThemeData gaugeThemeData) { - Color color; - if (_axis.ranges != null && _axis.ranges.isNotEmpty) { - for (num i = 0; i < _axis.ranges.length; i++) { - if (_axis.ranges[i].startValue <= value && - _axis.ranges[i].endValue >= value) { - color = _axis.ranges[i].color ?? gaugeThemeData.rangeColor; - break; - } - } - } - return color; - } - - /// Calculates the angle to adjust the start and end tick - double _getTickPositionInCorner( - num num, double angleForTicks, double startOffset, bool isMajor) { - final double thickness = isMajor - ? _axis.majorTickStyle.thickness - : _axis.minorTickStyle.thickness; - final double angle = - _cornerRadiusAngle(startOffset + _actualAxisWidth / 2, thickness / 2); - if (num == 0) { - final double ticksAngle = !_axis.isInversed - ? _getRadianToDegree(angleForTicks) + angle - : _getRadianToDegree(angleForTicks) - angle; - return _getDegreeToRadian(ticksAngle); - } else { - final double ticksAngle = !_axis.isInversed - ? _getRadianToDegree(angleForTicks) - angle - : _getRadianToDegree(angleForTicks) + angle; - return _getDegreeToRadian(ticksAngle); - } - } - - /// Calculates the minor tick position - void _calculateMinorTickPosition() { - if (_axisLabels != null && _axisLabels.isNotEmpty) { - final double axisLineWidth = _axis.showAxisLine ? _actualAxisWidth : 0; - double tickStartOffset = 0; - double tickEndOffset = 0; - final double offset = _isLabelsOutside - ? _axis.showLabels - ? (_actualLabelOffset + - math.max(_maximumLabelSize.height, _maximumLabelSize.width) / - 2) - : 0 - : 0; - if (!_isTicksOutside) { - tickStartOffset = - _radius - (axisLineWidth + _actualTickOffset + offset); - tickEndOffset = _radius - - (axisLineWidth + - _actualMinorTickLength + - _actualTickOffset + - offset); - } else { - final bool isGreater = _actualMinorTickLength > _actualMajorTickLength; - if (!_useAxisElementsInsideRadius) { - tickStartOffset = _radius + _actualTickOffset; - tickEndOffset = _radius + _actualMinorTickLength + _actualTickOffset; - } else { - tickStartOffset = isGreater - ? _radius - offset - : _radius - - (_maximumTickLength - _actualMinorTickLength + offset); - tickEndOffset = _radius - (_maximumTickLength + offset); - } - } - - _calculateOffsetForMinorTicks(tickStartOffset, tickEndOffset); - } - } - - /// Calculates the offset for minor ticks - /// - /// This method is quite a long method. This method could be refactored into - /// the smaller method but it leads to passing more number of parameter and - /// which degrades the performance - void _calculateOffsetForMinorTicks( - double tickStartOffset, double tickEndOffset) { - _minorTickOffsets = <_TickOffset>[]; - double angularSpaceForTicks; - double totalMinorTicks; - if (_actualInterval != null) { - final double majorTicksInterval = - (_axis.maximum - _axis.minimum) / _actualInterval; - angularSpaceForTicks = - _getDegreeToRadian(_sweepAngle / majorTicksInterval); - final double maximumLabelValue = - _axisLabels[_axisLabels.length - 2].value.toDouble(); - final double difference = (_axis.maximum - maximumLabelValue); - final double minorTickInterval = - ((_actualInterval / 2) / _axis.minorTicksPerInterval); - final int remainingTicks = difference ~/ minorTickInterval; - totalMinorTicks = - ((_axisLabels.length - 1) * _axis.minorTicksPerInterval) + - remainingTicks; - } else { - angularSpaceForTicks = - _getDegreeToRadian(_sweepAngle / (_majorTicksCount - 1)); - totalMinorTicks = (_axisLabels.length - 1) * _axis.minorTicksPerInterval; - } - - double angleForTicks = _axis.isInversed - ? _getDegreeToRadian(_axis.startAngle + _sweepAngle - 90) - : _getDegreeToRadian(_axis.startAngle - 90); - - const num minorTickIndex = 1; // Since the minor tick rendering - // needs to be start in the index one - final double minorTickAngle = - angularSpaceForTicks / (_axis.minorTicksPerInterval + 1); - - for (num i = minorTickIndex; i <= totalMinorTicks; i++) { - if (_axis.isInversed) { - angleForTicks -= minorTickAngle; - } else { - angleForTicks += minorTickAngle; - } - - final double tickValue = double.parse(factorToValue( - (_getRadianToDegree(angleForTicks) + 90 - _axis.startAngle) / - _sweepAngle) - .toStringAsFixed(5)); - if (tickValue <= _axis.maximum) { - if (tickValue == _axis.maximum) { - angleForTicks = _getTickPositionInCorner( - i, angleForTicks, tickStartOffset, false); - } - final List tickPosition = - _getTickPosition(tickStartOffset, tickEndOffset, angleForTicks); - final _TickOffset tickOffset = _TickOffset(); - tickOffset.startPoint = tickPosition[0]; - tickOffset.endPoint = tickPosition[1]; - tickOffset.value = tickValue; - - final Offset centerPoint = !_axis.canScaleToFit - ? Offset(_centerX, _centerY) - : const Offset(0, 0); - tickOffset.startPoint = Offset( - tickOffset.startPoint.dx - centerPoint.dx, - tickOffset.startPoint.dy - centerPoint.dy); - tickOffset.endPoint = Offset(tickOffset.endPoint.dx - centerPoint.dx, - tickOffset.endPoint.dy - centerPoint.dy); - _minorTickOffsets.add(tickOffset); - if (i % _axis.minorTicksPerInterval == 0) { - if (_axis.isInversed) { - angleForTicks -= minorTickAngle; - } else { - angleForTicks += minorTickAngle; - } - } - } - } - } - - /// Calculate the axis label position - void _calculateAxisLabelsPosition() { - if (_axisLabels != null && _axisLabels.isNotEmpty) { - // Calculates the angle between each axis label - double labelsInterval; - if (_actualInterval != null) { - labelsInterval = (_axis.maximum - _axis.minimum) / _actualInterval; - } else { - labelsInterval = (_axisLabels.length - 1).toDouble(); - } - final double labelSpaceInAngle = _sweepAngle / labelsInterval; - final double labelSpaceInRadian = _getDegreeToRadian(labelSpaceInAngle); - final double tickLength = _actualMajorTickLength > _actualMinorTickLength - ? _actualMajorTickLength - : _actualMinorTickLength; - final double tickPadding = - _axis.showTicks ? tickLength + _actualTickOffset : 0; - double labelRadian = 0; - double labelAngle = 0; - double labelPosition = 0; - if (!_axis.isInversed) { - labelAngle = _axis.startAngle - 90; - labelRadian = _getDegreeToRadian(labelAngle); - } else { - labelAngle = _axis.startAngle + _sweepAngle - 90; - labelRadian = _getDegreeToRadian(labelAngle); - } - - final double labelSize = - math.max(_maximumLabelSize.height, _maximumLabelSize.width) / 2; - if (_isLabelsOutside) { - final double featureOffset = labelSize; - labelPosition = _useAxisElementsInsideRadius - ? _radius - featureOffset - : _radius + tickPadding + _actualLabelOffset; - } else { - labelPosition = - _radius - (_actualAxisWidth + tickPadding + _actualLabelOffset); - } - - _calculateLabelPosition(labelPosition, labelRadian, labelAngle, - labelSpaceInRadian, labelSpaceInAngle); - } - } - - // Method to calculate label position - void _calculateLabelPosition(double labelPosition, double labelRadian, - double labelAngle, double labelSpaceInRadian, double labelSpaceInAngle) { - for (num i = 0; i < _axisLabels.length; i++) { - final CircularAxisLabel label = _axisLabels[i]; - label.angle = labelAngle; - if (_isMaxiumValueIncluded && i == _axisLabels.length - 1) { - labelAngle = _axis.isInversed - ? _axis.startAngle - 90 - : _axis.startAngle + _sweepAngle - 90; - label.value = _axis.maximum; - label.angle = labelAngle; - labelRadian = _getDegreeToRadian(labelAngle); - } else { - label.value = - factorToValue((labelAngle + 90 - _axis.startAngle) / _sweepAngle); - } - - if (!_axis.canScaleToFit) { - final double x = - ((_axisSize.width / 2) - (labelPosition * math.sin(labelRadian))) - - _centerX; - final double y = - ((_axisSize.height / 2) + (labelPosition * math.cos(labelRadian))) - - _centerY; - label.position = Offset(x, y); - } else { - final double x = - _axisCenter.dx - (labelPosition * math.sin(labelRadian)); - final double y = - _axisCenter.dy + (labelPosition * math.cos(labelRadian)); - label.position = Offset(x, y); - } - - if (!_axis.isInversed) { - labelRadian += labelSpaceInRadian; - labelAngle += labelSpaceInAngle; - } else { - labelRadian -= labelSpaceInRadian; - labelAngle -= labelSpaceInAngle; - } - } - } - - /// To find the maximum label size - void _measureAxisLabels() { - _maximumLabelSize = const Size(0, 0); - for (num i = 0; i < _axisLabels.length; i++) { - final CircularAxisLabel label = _axisLabels[i]; - label.labelSize = _getTextSize(label.text, label.labelStyle); - final double maxWidth = _maximumLabelSize.width < label.labelSize.width - ? label._needsRotateLabel - ? label.labelSize.height - : label.labelSize.width - : _maximumLabelSize.width; - final double maxHeight = _maximumLabelSize.height < label.labelSize.height - ? label.labelSize.height - : _maximumLabelSize.height; - - _maximumLabelSize = Size(maxWidth, maxHeight); - } - } - - /// Gets the start and end offset of tick - List _getTickPosition( - double tickStartOffset, double tickEndOffset, double angleForTicks) { - final Offset centerPoint = !_axis.canScaleToFit - ? Offset(_axisSize.width / 2, _axisSize.height / 2) - : _axisCenter; - final double tickStartX = - centerPoint.dx - tickStartOffset * math.sin(angleForTicks); - final double tickStartY = - centerPoint.dy + tickStartOffset * math.cos(angleForTicks); - final double tickStopX = - centerPoint.dx + (1 - tickEndOffset) * math.sin(angleForTicks); - final double tickStopY = - centerPoint.dy - (1 - tickEndOffset) * math.cos(angleForTicks); - final Offset startOffset = Offset(tickStartX, tickStartY); - final Offset endOffset = Offset(tickStopX, tickStopY); - return [startOffset, endOffset]; - } - - ///Method to calculate teh sweep angle of axis - double _getSweepAngle() { - final double actualEndAngle = - _axis.endAngle > 360 ? _axis.endAngle % 360 : _axis.endAngle; - double totalAngle = actualEndAngle - _axis.startAngle; - totalAngle = totalAngle <= 0 ? (totalAngle + 360) : totalAngle; - return totalAngle; - } - - ///Calculates the axis width based on the coordinate unit - double _getActualValue(double value, GaugeSizeUnit sizeUnit, bool isOffset) { - double actualValue = 0; - if (value != null) { - switch (sizeUnit) { - case GaugeSizeUnit.factor: - { - if (!isOffset) { - value = value < 0 ? 0 : value; - value = value > 1 ? 1 : value; - } - - actualValue = value * _radius; - } - break; - case GaugeSizeUnit.logicalPixel: - { - actualValue = value; - } - break; - } - } - - return actualValue; - } - - ///Calculates the maximum tick length - double _getTickLength(bool isMajorTick) { - if (isMajorTick) { - return _getActualValue( - _axis.majorTickStyle.length, _axis.majorTickStyle.lengthUnit, false); - } else { - return _getActualValue( - _axis.minorTickStyle.length, _axis.minorTickStyle.lengthUnit, false); - } - } - - /// Renders the axis pointers - void _renderPointers() { - final int index = _renderingDetails.axisRenderers.indexOf(this); - for (num i = 0; i < _axis.pointers.length; i++) { - final List<_GaugePointerRenderer> pointerRenderers = - _renderingDetails.gaugePointerRenderers[index]; - final _GaugePointerRenderer pointerRenderer = pointerRenderers[i]; - pointerRenderer._axis = _axis; - pointerRenderer._calculatePosition(); - } - } - - /// Method to render the range - void _renderRanges() { - final int index = _renderingDetails.axisRenderers.indexOf(this); - for (num i = 0; i < _axis.ranges.length; i++) { - final List<_GaugeRangeRenderer> rangeRenderers = - _renderingDetails.gaugeRangeRenderers[index]; - final _GaugeRangeRenderer rangeRenderer = rangeRenderers[i]; - rangeRenderer._axis = _axis; - rangeRenderer._calculateRangePosition(); - } - } - - /// Calculates the interval of axis based on its range - num _getNiceInterval() { - if (_axis.interval != null) { - return _axis.interval; - } - - return _calculateAxisInterval(_axis.maximumLabels); - } - - /// To calculate the axis label based on the maximum axis label - num _calculateAxisInterval(int actualMaximumValue) { - final num delta = _getAxisRange(); - final num circumference = 2 * math.pi * _center * (_sweepAngle / 360); - final num desiredIntervalCount = - math.max(circumference * ((0.533 * actualMaximumValue) / 100), 1); - num niceInterval = delta / desiredIntervalCount; - final num minimumInterval = - math.pow(10, (math.log(niceInterval) / math.log(10)).floor()); - final List intervalDivisions = [10, 5, 2, 1]; - for (int i = 0; i < intervalDivisions.length; i++) { - final num currentInterval = minimumInterval * intervalDivisions[i]; - if (desiredIntervalCount < (delta / currentInterval)) { - break; - } - niceInterval = currentInterval; - } - - return niceInterval; // Returns the interval based on the maximum number - // of labels for 100 labels - } - - /// To load the image from the image provider - void _loadBackgroundImage(BuildContext context) { - final ImageStream newImageStream = - _axis.backgroundImage.resolve(createLocalImageConfiguration(context)); - if (newImageStream.key != _imageStream?.key) { - _imageStream?.removeListener(_listener); - _imageStream = newImageStream; - _imageStream.addListener(_listener); - } - } - - /// Update the background image - void _updateBackgroundImage(ImageInfo imageInfo, bool synchronousCall) { - if (imageInfo?.image != null) { - _backgroundImageInfo = imageInfo; - _renderingDetails.axisRepaintNotifier.value++; - } - } - - /// Gets the current axis labels - CircularAxisLabel _getAxisLabel(num i) { - num value = i; - String labelText = value.toString(); - final List list = labelText.split('.'); - value = double.parse(value.toStringAsFixed(3)); - if (list != null && - list.length > 1 && - (list[1] == '0' || list[1] == '00' || list[1] == '000')) { - value = value.round(); - } - - labelText = value.toString(); - - if (_axis.numberFormat != null) { - labelText = _axis.numberFormat.format(value); - } - if (_axis.labelFormat != null) { - labelText = _axis.labelFormat.replaceAll(RegExp('{value}'), labelText); - } - AxisLabelCreatedArgs labelCreatedArgs; - GaugeTextStyle argsLabelStyle; - if (_axis.onLabelCreated != null) { - labelCreatedArgs = AxisLabelCreatedArgs(); - labelCreatedArgs.text = labelText; - _axis.onLabelCreated(labelCreatedArgs); - - labelText = labelCreatedArgs.text; - argsLabelStyle = labelCreatedArgs.labelStyle; - } - - final GaugeTextStyle labelStyle = argsLabelStyle ?? _axis.axisLabelStyle; - final CircularAxisLabel label = CircularAxisLabel(labelStyle, labelText, i, - labelCreatedArgs != null ? labelCreatedArgs.canRotate ?? false : false); - label.value = value; - return label; - } - - /// Returns the axis range - num _getAxisRange() { - return _axis.maximum - _axis.minimum; - } - - /// Calculates the visible labels based on axis interval and range - @override - List generateVisibleLabels() { - _isMaxiumValueIncluded = false; - final List visibleLabels = []; - _actualInterval = _getNiceInterval(); - for (num i = _axis.minimum; i <= _axis.maximum; i += _actualInterval) { - final CircularAxisLabel currentLabel = _getAxisLabel(i); - visibleLabels.add(currentLabel); - } - - final CircularAxisLabel label = visibleLabels[visibleLabels.length - 1]; - if (label.value != _axis.maximum && label.value < _axis.maximum) { - _isMaxiumValueIncluded = true; - final CircularAxisLabel currentLabel = _getAxisLabel(_axis.maximum); - visibleLabels.add(currentLabel); - } - - return visibleLabels; - } - - /// Converts the axis value to factor based on angle - @override - double valueToFactor(double value) { - final double angle = _valueToAngle(value); - return angle / _sweepAngle; - } - - /// Converts the factor value to axis value - @override - double factorToValue(double factor) { - final double angle = (factor * _sweepAngle) + _axis.startAngle; - return _angleToValue(angle); - } -} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/axis_label.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/axis_label.dart new file mode 100644 index 000000000..ea4d4ac8f --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/axis_label.dart @@ -0,0 +1,35 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'common.dart'; + +/// Holds the axis label information. +class CircularAxisLabel { + /// Creates the axis label with default or required properties. + CircularAxisLabel( + this.labelStyle, this.text, this.index, this.needsRotateLabel); + + /// Style for axis label text. + late GaugeTextStyle labelStyle; + + /// Holds the size of axis label + late Size labelSize; + + /// Text to display + late String text; + + /// Specifies the axis index position + late num index; + + ///Specifies the value of the labels + late num value; + + /// Holds the label position + late Offset position; + + /// Holds the corresponding angle for the label + late double angle; + + /// Specifies whether to rotate the corresponding labels + final bool needsRotateLabel; +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/common.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/common.dart index fdebe69c8..0054f118b 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/common.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/common.dart @@ -1,4 +1,8 @@ -part of gauges; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/foundation.dart'; +import '../utils/enum.dart'; /// Signature for the callback that reports the custom renderer has been extended /// and set to the gauge axis @@ -17,7 +21,8 @@ typedef NeedlePointerRendererFactory /// This class has the property of the guage text style. /// -/// Provides the options of color, font family, font style, font size, and font-weight to customize the appearance. +/// Provides the options of color, font family, font style, font size, and +/// font-weight to customize the appearance. class GaugeTextStyle { /// Creates a gauge text style with default or required properties. GaugeTextStyle( @@ -28,7 +33,7 @@ class GaugeTextStyle { this.fontSize = 12}); /// To set the color of guage text. - Color color; + Color? color; /// To set the font family to guage text. /// @@ -66,7 +71,7 @@ class GaugeTextStyle { @override int get hashCode { - final List values = [ + final List values = [ color, fontFamily, fontStyle, @@ -101,18 +106,16 @@ class GaugeTextStyle { class GaugeTitle { /// Creates the gauge title with default or required properties. GaugeTitle( - {@required this.text, - TextStyle textStyle, + {required this.text, + this.textStyle = const TextStyle( + fontSize: 15.0, + fontFamily: 'Segoe UI', + fontStyle: FontStyle.normal, + fontWeight: FontWeight.normal), this.alignment = GaugeAlignment.center, this.borderColor, this.borderWidth = 0, - this.backgroundColor}) - : textStyle = textStyle ?? - const TextStyle( - fontSize: 15.0, - fontFamily: 'Segoe UI', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal); + this.backgroundColor}); /// Text to be displayed as gauge title. /// @@ -175,7 +178,7 @@ class GaugeTitle { /// )); ///} ///``` - final Color backgroundColor; + final Color? backgroundColor; /// The color that fills the border with the title of the gauge. /// @@ -192,7 +195,7 @@ class GaugeTitle { /// )); ///} ///``` - final Color borderColor; + final Color? borderColor; /// Specifies the border width of the gauge title. /// @@ -257,7 +260,7 @@ class GaugeTitle { @override int get hashCode { - final List values = [ + final List values = [ text, alignment, textStyle, @@ -372,7 +375,7 @@ class MajorTickStyle { /// )); ///} /// ``` - final Color color; + final Color? color; /// Specifies the dash array to draw the dashed line. /// @@ -387,7 +390,7 @@ class MajorTickStyle { /// )); ///} /// ``` - final List dashArray; + final List? dashArray; @override bool operator ==(Object other) { @@ -407,7 +410,7 @@ class MajorTickStyle { @override int get hashCode { - final List values = [ + final List values = [ length, thickness, lengthUnit, @@ -438,15 +441,15 @@ class MinorTickStyle extends MajorTickStyle { /// The arguments [length], [thickness], must be non-negative. MinorTickStyle( {double length = 5, - GaugeSizeUnit lengthUnit, - Color color, + GaugeSizeUnit lengthUnit = GaugeSizeUnit.logicalPixel, + Color? color, double thickness = 1.5, - List dashArray}) + List? dashArray}) : assert(length >= 0, 'Tick length must be a non-negative value.'), assert(thickness >= 0, 'Tick thickness must be a non-negative value.'), super( length: length, - lengthUnit: lengthUnit ?? GaugeSizeUnit.logicalPixel, + lengthUnit: lengthUnit, thickness: thickness, dashArray: dashArray, color: color, @@ -546,7 +549,7 @@ class AxisLineStyle { /// )); ///} /// ``` - final Color color; + final Color? color; /// The style to use for the axis line corner edge. /// @@ -584,7 +587,7 @@ class AxisLineStyle { /// )); ///} /// ``` - final List dashArray; + final List? dashArray; /// A gradient to use when filling the axis line. /// @@ -609,7 +612,7 @@ class AxisLineStyle { /// )); ///} /// ``` - final Gradient gradient; + final Gradient? gradient; @override bool operator ==(Object other) { @@ -630,7 +633,7 @@ class AxisLineStyle { @override int get hashCode { - final List values = [ + final List values = [ thickness, thicknessUnit, color, @@ -642,82 +645,27 @@ class AxisLineStyle { } } -/// Holds the axis label information. -class CircularAxisLabel { - /// Creates the axis label with default or required properties. - CircularAxisLabel( - this.labelStyle, this.text, this.index, this._needsRotateLabel); - - /// Style for axis label text. - GaugeTextStyle labelStyle; - - /// Holds the size of axis label - Size labelSize; - - /// Text to display - String text; - - /// Specifies the axis index position - num index; - - ///Specifies the value of the labels - num value; - - /// Holds the label position - Offset position; - - /// Holds the corresponding angle for the label - double angle; - - /// Specifies whether to rotate the corresponding labels - final bool _needsRotateLabel; -} - -/// Represents the arc data -class _ArcData { - /// Represents the start angle - double startAngle; - - /// Represents the end angle - double endAngle; - - /// Represents the arc rect - Rect arcRect; -} - -/// Specifies the offset value of tick -class _TickOffset { - /// Holds the start point - Offset startPoint; - - /// Holds the end point - Offset endPoint; - - /// Holds the tick value - double value; -} - /// Returns the AxisLabelCreatedArgs used by the [ /// RadialAxis.onLabelCreated] event. class AxisLabelCreatedArgs { /// Holds the label text - String text; + late String text; /// Specifies the label style - GaugeTextStyle labelStyle; + GaugeTextStyle? labelStyle; /// whether to rotate the label based on angle. - bool canRotate; + bool? canRotate; } /// Returns the ValueChangingArgs used by the /// [GaugePointer.onValueChanging] event class ValueChangingArgs { /// Specifies the pointer value. - double value; + late double value; /// Whether to cancel the new pointer value. - bool cancel; + bool? cancel; } /// Style for drawing pointer's tail. @@ -766,7 +714,7 @@ class TailStyle { /// )); ///} ///``` - final Color color; + final Color? color; /// Specifies the width of the tail. /// @@ -872,7 +820,7 @@ class TailStyle { /// )); ///} /// ``` - final Color borderColor; + final Color? borderColor; /// A gradient to use when filling the needle tail. /// @@ -902,7 +850,7 @@ class TailStyle { /// )); ///} /// ``` - final LinearGradient gradient; + final LinearGradient? gradient; @override bool operator ==(Object other) { @@ -924,7 +872,7 @@ class TailStyle { @override int get hashCode { - final List values = [ + final List values = [ width, color, borderWidth, @@ -1050,7 +998,7 @@ class KnobStyle { /// )); ///} /// ``` - final Color color; + final Color? color; /// Specifies the knob border color. /// @@ -1068,7 +1016,7 @@ class KnobStyle { /// )); ///} /// ``` - final Color borderColor; + final Color? borderColor; @override bool operator ==(Object other) { @@ -1088,7 +1036,7 @@ class KnobStyle { @override int get hashCode { - final List values = [ + final List values = [ knobRadius, borderWidth, sizeUnit, @@ -1100,16 +1048,16 @@ class KnobStyle { } /// Details for the drawPointer method, such as the location of the pointer, -/// the angle and the radius needed to draw the pointer. +/// the angle, and the radius needed to draw the pointer. @immutable class PointerPaintingDetails { /// Creates the details which are required to paint the pointer const PointerPaintingDetails( - {this.startOffset, - this.endOffset, - this.pointerAngle, - this.axisRadius, - this.axisCenter}); + {required this.startOffset, + required this.endOffset, + required this.pointerAngle, + required this.axisRadius, + required this.axisCenter}); /// Specifies the starting position of the pointer in the logical pixels. final Offset startOffset; diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/gauge_annotation_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/gauge_annotation_renderer.dart index 4d4773249..3a76cb30d 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/gauge_annotation_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/gauge_annotation_renderer.dart @@ -1,21 +1,31 @@ -part of gauges; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../annotation/gauge_annotation.dart'; +import '../axis/radial_axis.dart'; +import '../gauge/radial_gauge.dart'; +import '../renderers/radial_axis_renderer_base.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'radial_gauge_renderer.dart'; /// Represents the annotation renderer /// // ignore: must_be_immutable -class _AnnotationRenderer extends StatefulWidget { +class AnnotationRenderer extends StatefulWidget { ///Creates the annotation renderer /// // ignore: prefer_const_constructors_in_immutables - _AnnotationRenderer( - {Key key, - this.annotation, - this.gauge, - this.axis, + AnnotationRenderer( + {Key? key, + required this.annotation, + required this.gauge, + required this.axis, this.interval, - this.duration, - this.renderingDetails, - this.axisRenderer}) + required this.duration, + required this.renderingDetails, + required this.axisRenderer}) : super( key: key, ); @@ -30,33 +40,34 @@ class _AnnotationRenderer extends StatefulWidget { final RadialAxis axis; /// Specifies the interval duration - final List interval; + final List? interval; /// Specifies the animation duration final int duration; /// Hold the radial gauge animation details - final _RenderingDetails renderingDetails; + final RenderingDetails renderingDetails; - final RadialAxisRenderer axisRenderer; + /// Holds the radial axis renderer + final RadialAxisRendererBase axisRenderer; @override State createState() { - return _AnnotationRendererState(); + return AnnotationRendererState(); } } /// Represents the annotation renderer state -class _AnnotationRendererState extends State<_AnnotationRenderer> +class AnnotationRendererState extends State with SingleTickerProviderStateMixin { /// Holds the animation controller - AnimationController animationController; + AnimationController? animationController; /// Holds the animation value - Animation animation; + Animation? animation; /// Specifies the offset of positioning the annotation - Offset annotationPosition; + late Offset annotationPosition; @override void initState() { @@ -65,8 +76,8 @@ class _AnnotationRendererState extends State<_AnnotationRenderer> animationController = AnimationController(vsync: this) ..duration = Duration(milliseconds: widget.duration); animation = Tween(begin: 0, end: 1).animate(CurvedAnimation( - parent: animationController, - curve: Interval(widget.interval[0], widget.interval[1], + parent: animationController!, + curve: Interval(widget.interval![0]!, widget.interval![1]!, curve: Curves.fastOutSlowIn))); } super.initState(); @@ -74,37 +85,37 @@ class _AnnotationRendererState extends State<_AnnotationRenderer> // Calculate the pixel position in axis to place the annotation widget. void _calculateAxisPosition(GaugeAnnotation annotation, RadialAxis axis, - RadialAxisRenderer axisRenderer) { - final double value = annotation.positionFactor ?? 0; - final double offset = value * (axisRenderer._radius); + RadialAxisRendererBase axisRenderer) { + final double value = annotation.positionFactor; + final double offset = value * (axisRenderer.radius); final double angle = _calculateActualAngle(annotation, axis, axisRenderer); - final double radian = _getDegreeToRadian(angle); + final double radian = getDegreeToRadian(angle); if (!axis.canScaleToFit) { - final double x = (axisRenderer._axisSize.width / 2) + - (offset - (axisRenderer._actualAxisWidth / 2)) * math.cos(radian) - - axisRenderer._centerX; - final double y = (axisRenderer._axisSize.height / 2) + - (offset - (axisRenderer._actualAxisWidth / 2)) * math.sin(radian) - - axisRenderer._centerY; + final double x = (axisRenderer.axisSize.width / 2) + + (offset - (axisRenderer.actualAxisWidth / 2)) * math.cos(radian) - + axisRenderer.centerX; + final double y = (axisRenderer.axisSize.height / 2) + + (offset - (axisRenderer.actualAxisWidth / 2)) * math.sin(radian) - + axisRenderer.centerY; annotationPosition = Offset(x, y); } else { - final double x = axisRenderer._axisCenter.dx + - (offset - (axisRenderer._actualAxisWidth / 2)) * math.cos(radian); - final double y = axisRenderer._axisCenter.dy + - (offset - (axisRenderer._actualAxisWidth / 2)) * math.sin(radian); + final double x = axisRenderer.axisCenter.dx + + (offset - (axisRenderer.actualAxisWidth / 2)) * math.cos(radian); + final double y = axisRenderer.axisCenter.dy + + (offset - (axisRenderer.actualAxisWidth / 2)) * math.sin(radian); annotationPosition = Offset(x, y); } } /// Calculates the actual angle value double _calculateActualAngle(GaugeAnnotation annotation, RadialAxis axis, - RadialAxisRenderer axisRenderer) { + RadialAxisRendererBase axisRenderer) { double actualValue = 0; if (annotation.angle != null) { - actualValue = annotation.angle; + actualValue = annotation.angle!; } else if (annotation.axisValue != null) { - actualValue = (axisRenderer.valueToFactor(annotation.axisValue) * - axisRenderer._sweepAngle) + + actualValue = (axisRenderer.valueToFactor(annotation.axisValue!) * + axisRenderer.sweepAngle) + axis.startAngle; } return actualValue; @@ -113,15 +124,15 @@ class _AnnotationRendererState extends State<_AnnotationRenderer> /// To update the load time animation of annotation void _updateAnimation() { if (widget.gauge.axes[widget.gauge.axes.length - 1] == widget.axis && - widget.axis.annotations[widget.axis.annotations.length - 1] == + widget.axis.annotations![widget.axis.annotations!.length - 1] == widget.annotation && - animation.value == 1) { + animation!.value == 1) { widget.renderingDetails.needsToAnimateAnnotation = false; } } @override - void didUpdateWidget(_AnnotationRenderer oldWidget) { + void didUpdateWidget(AnnotationRenderer oldWidget) { _calculateAxisPosition(widget.annotation, widget.axis, widget.axisRenderer); super.didUpdateWidget(oldWidget); } @@ -129,14 +140,14 @@ class _AnnotationRendererState extends State<_AnnotationRenderer> @override Widget build(BuildContext context) { if (widget.renderingDetails.needsToAnimateAnnotation) { - animationController.forward(from: 0); + animationController!.forward(from: 0); return AnimatedBuilder( - animation: animationController, + animation: animationController!, child: widget.annotation.widget, - builder: (BuildContext context, Widget _widget) { + builder: (BuildContext context, Widget? _widget) { _updateAnimation(); return Opacity( - opacity: animation.value, + opacity: animation!.value, child: _AnnotationRenderObject( child: widget.annotation.widget, position: annotationPosition, @@ -155,7 +166,7 @@ class _AnnotationRendererState extends State<_AnnotationRenderer> @override void dispose() { if (animationController != null) { - animationController + animationController! .dispose(); // Need to dispose the animation controller instance // otherwise it will cause memory leak } @@ -166,11 +177,11 @@ class _AnnotationRendererState extends State<_AnnotationRenderer> /// Represents the render object for annotation widget. class _AnnotationRenderObject extends SingleChildRenderObjectWidget { const _AnnotationRenderObject( - {Key key, - Widget child, - this.position, - this.horizontalAlignment, - this.verticalAlignment}) + {Key? key, + required Widget child, + required this.position, + required this.horizontalAlignment, + required this.verticalAlignment}) : super(key: key, child: child); /// Specifies the offset position for annotation @@ -204,7 +215,7 @@ class _AnnotationRenderObject extends SingleChildRenderObjectWidget { class _RenderAnnotationWidget extends RenderShiftedBox { _RenderAnnotationWidget( this._position, this._horizontalAlignment, this._verticalAlignment, - [RenderBox child]) + [RenderBox? child]) : super(child); /// Holds the annotation position @@ -253,22 +264,23 @@ class _RenderAnnotationWidget extends RenderShiftedBox { void performLayout() { final BoxConstraints constraints = this.constraints; if (child != null) { - child.layout(constraints, parentUsesSize: true); - size = constraints.constrain(Size(child.size.width, child.size.height)); - if (child.parentData is BoxParentData) { - final BoxParentData childParentData = child.parentData; + child!.layout(constraints, parentUsesSize: true); + size = constraints.constrain(Size(child!.size.width, child!.size.height)); + if (child!.parentData is BoxParentData) { + final BoxParentData childParentData = + child!.parentData as BoxParentData; final double dx = position.dx - (horizontalAlignment == GaugeAlignment.near ? 0 : horizontalAlignment == GaugeAlignment.center - ? child.size.width / 2 - : child.size.width); + ? child!.size.width / 2 + : child!.size.width); final double dy = position.dy - (verticalAlignment == GaugeAlignment.near ? 0 : verticalAlignment == GaugeAlignment.center - ? child.size.height / 2 - : child.size.height); + ? child!.size.height / 2 + : child!.size.height); childParentData.offset = Offset(dx, dy); } } else { diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/radial_gauge_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/radial_gauge_renderer.dart index 94e48630b..10a659536 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/radial_gauge_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/radial_gauge_renderer.dart @@ -1,13 +1,41 @@ -part of gauges; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import '../annotation/gauge_annotation.dart'; +import '../axis/radial_axis.dart'; +import '../common/gauge_annotation_renderer.dart'; +import '../common/widget_pointer_renderer.dart'; +import '../gauge/radial_gauge.dart'; +import '../gauge_painter/marker_pointer_painter.dart'; +import '../gauge_painter/needle_pointer_painter.dart'; +import '../gauge_painter/radial_axis_painter.dart'; +import '../gauge_painter/range_painter.dart'; +import '../gauge_painter/range_pointer_painter.dart'; +import '../pointers/gauge_pointer.dart'; +import '../pointers/marker_pointer.dart'; +import '../pointers/needle_pointer.dart'; +import '../pointers/range_pointer.dart'; +import '../pointers/widget_pointer.dart'; +import '../range/gauge_range.dart'; +import '../renderers/gauge_pointer_renderer.dart'; +import '../renderers/gauge_range_renderer.dart'; +import '../renderers/marker_pointer_renderer_base.dart'; +import '../renderers/needle_pointer_renderer_base.dart'; +import '../renderers/radial_axis_renderer_base.dart'; +import '../renderers/range_pointer_renderer.dart'; +import '../renderers/widget_pointer_renderer_base.dart'; +import '../utils/enum.dart'; /// Represents the container to render the axis and its element /// -class _AxisContainer extends StatelessWidget { - const _AxisContainer( +class AxisContainer extends StatelessWidget { + /// Creates the Axis Container + const AxisContainer( this._gauge, this._gaugeThemeData, this._renderingDetails); /// Hold the radial gauge animation details - final _RenderingDetails _renderingDetails; + final RenderingDetails _renderingDetails; /// Specifies the gauge theme data. final SfGaugeThemeData _gaugeThemeData; @@ -17,22 +45,21 @@ class _AxisContainer extends StatelessWidget { /// Method to update the pointer value void _updatePointerValue(BuildContext context, DragUpdateDetails details) { - final RenderBox renderBox = context.findRenderObject(); - if (details != null && details.globalPosition != null) { - final Offset tapPosition = - renderBox.globalToLocal(details.globalPosition); - for (num i = 0; i < _gauge.axes.length; i++) { - final List<_GaugePointerRenderer> _pointerRenderers = - _renderingDetails.gaugePointerRenderers[i]; - if (_gauge.axes[i].pointers != null && - _gauge.axes[i].pointers.isNotEmpty) { - for (num j = 0; j < _gauge.axes[i].pointers.length; j++) { - final GaugePointer pointer = _gauge.axes[i].pointers[j]; - final _GaugePointerRenderer pointerRenderer = _pointerRenderers[j]; - if (pointer.enableDragging && pointerRenderer._isDragStarted) { - pointerRenderer._updateDragValue( - tapPosition.dx, tapPosition.dy, _renderingDetails); - } + final RenderBox? renderBox = context.findRenderObject()! as RenderBox; + final Offset tapPosition = renderBox!.globalToLocal(details.globalPosition); + for (int i = 0; i < _gauge.axes.length; i++) { + final List? _pointerRenderers = + _renderingDetails.gaugePointerRenderers[i]; + if (_gauge.axes[i].pointers != null && + _gauge.axes[i].pointers!.isNotEmpty) { + for (int j = 0; j < _gauge.axes[i].pointers!.length; j++) { + final GaugePointer pointer = _gauge.axes[i].pointers![j]; + final GaugePointerRenderer pointerRenderer = _pointerRenderers![j]; + if (pointer.enableDragging && + pointerRenderer.isDragStarted != null && + pointerRenderer.isDragStarted!) { + pointerRenderer.updateDragValue( + tapPosition.dx, tapPosition.dy, _renderingDetails); } } } @@ -47,51 +74,88 @@ class _AxisContainer extends StatelessWidget { final bool enableAxisTapping = _getIsAxisTapped(); final bool enablePointerDragging = _getIsPointerDragging(); - return GestureDetector( - // onPanStart: enablePointerDragging - // ? (DragStartDetails details) => - // _checkPointerDragging(context, details) - // : null, - // onPanUpdate: enablePointerDragging - // ? (DragUpdateDetails details) => - // _updatePointerValue(context, details) - // : null, - // onPanEnd: enablePointerDragging - // ? (DragEndDetails details) => _checkPointerIsDragged() - // : null, - - onVerticalDragStart: enablePointerDragging - ? (DragStartDetails details) => - _checkPointerDragging(context, details) - : null, - onVerticalDragUpdate: enablePointerDragging - ? (DragUpdateDetails details) => - _updatePointerValue(context, details) - : null, - onVerticalDragEnd: enablePointerDragging - ? (DragEndDetails details) => _checkPointerIsDragged() - : null, - onHorizontalDragStart: enablePointerDragging - ? (DragStartDetails details) => - _checkPointerDragging(context, details) - : null, - onHorizontalDragUpdate: enablePointerDragging - ? (DragUpdateDetails details) => - _updatePointerValue(context, details) + return MouseRegion( + onHover: enablePointerDragging + ? (event) => _enableOverlayForMarkerPointer(context, event.position) : null, - onHorizontalDragEnd: enablePointerDragging - ? (DragEndDetails details) => _checkPointerIsDragged() - : null, - onTapUp: enableAxisTapping - ? (TapUpDetails details) => _checkIsAxisTapped(context, details) - : null, - child: Container( - decoration: const BoxDecoration(color: Colors.transparent), - child: Stack(children: gaugeWidgets), - )); + onExit: (event) => + enablePointerDragging ? (event) => _checkPointerIsDragged() : null, + child: Listener( + onPointerUp: enablePointerDragging + ? (event) => _checkPointerIsDragged() + : null, + onPointerCancel: enablePointerDragging + ? (event) => _checkPointerIsDragged() + : null, + child: GestureDetector( + onVerticalDragStart: enablePointerDragging + ? (DragStartDetails details) => + _checkPointerDragging(context, details) + : null, + onVerticalDragUpdate: enablePointerDragging + ? (DragUpdateDetails details) => + _updatePointerValue(context, details) + : null, + onVerticalDragEnd: enablePointerDragging + ? (DragEndDetails details) => _checkPointerIsDragged() + : null, + onHorizontalDragStart: enablePointerDragging + ? (DragStartDetails details) => + _checkPointerDragging(context, details) + : null, + onHorizontalDragUpdate: enablePointerDragging + ? (DragUpdateDetails details) => + _updatePointerValue(context, details) + : null, + onHorizontalDragEnd: enablePointerDragging + ? (DragEndDetails details) => _checkPointerIsDragged() + : null, + onTapUp: enableAxisTapping + ? (TapUpDetails details) => + _checkIsAxisTapped(context, details) + : null, + child: Container( + decoration: const BoxDecoration(color: Colors.transparent), + child: Stack(children: gaugeWidgets), + )))); + } + + /// Checks whether the mouse pointer is hovered on marker in web + void _enableOverlayForMarkerPointer( + BuildContext context, Offset globalPosition) { + final RenderBox renderBox = context.findRenderObject() as RenderBox; + final Offset hoverPosition = renderBox.globalToLocal(globalPosition); + for (int i = 0; i < _gauge.axes.length; i++) { + final List? pointerRenderers = + _renderingDetails.gaugePointerRenderers[i]; + if (_gauge.axes[i].pointers != null && + _gauge.axes[i].pointers!.isNotEmpty) { + for (int j = 0; j < _gauge.axes[i].pointers!.length; j++) { + final GaugePointer pointer = _gauge.axes[i].pointers![j]; + final GaugePointerRenderer pointerRenderer = pointerRenderers![j]; + if (pointer.enableDragging && pointer is MarkerPointer) { + pointerRenderer.isHovered = false; + _renderingDetails.pointerRepaintNotifier.value++; + Rect pointerRect = pointerRenderer.pointerRect; + pointerRect = Rect.fromLTRB( + pointerRect.left - 10, + pointerRect.top - 10, + pointerRect.right + 10, + pointerRect.bottom + 10); + if (pointerRenderer.pointerRect.contains(hoverPosition) && + pointerRenderers.isNotEmpty && + !(pointerRenderers.any((element) => + element.isHovered != null && element.isHovered!))) { + pointerRenderer.isHovered = true; + _renderingDetails.pointerRepaintNotifier.value++; + break; + } + } + } + } + } } - // Checks whether onAxisTapped callback is activated. bool _getIsAxisTapped() { for (int i = 0; i < _gauge.axes.length; i++) { if (_gauge.axes[i].onAxisTapped != null) { @@ -105,9 +169,9 @@ class _AxisContainer extends StatelessWidget { bool _getIsPointerDragging() { for (int i = 0; i < _gauge.axes.length; i++) { final RadialAxis currentAxis = _gauge.axes[i]; - if (currentAxis.pointers != null && currentAxis.pointers.isNotEmpty) { - for (int j = 0; j < currentAxis.pointers.length; j++) { - if (currentAxis.pointers[j].enableDragging) { + if (currentAxis.pointers != null && currentAxis.pointers!.isNotEmpty) { + for (int j = 0; j < currentAxis.pointers!.length; j++) { + if (currentAxis.pointers![j].enableDragging) { return true; } } @@ -118,18 +182,16 @@ class _AxisContainer extends StatelessWidget { /// Method to check whether the axis is tapped void _checkIsAxisTapped(BuildContext context, TapUpDetails details) { - final RenderBox renderBox = context.findRenderObject(); - if (details != null && details.globalPosition != null) { - if (_gauge.axes.isNotEmpty) { - for (num i = 0; i < _gauge.axes.length; i++) { - final RadialAxis axis = _gauge.axes[i]; - final RadialAxisRenderer axisRenderer = - _renderingDetails.axisRenderers[i]; - final Offset offset = renderBox.globalToLocal(details.globalPosition); - if (axis.onAxisTapped != null && - axisRenderer._axisPath.getBounds().contains(offset)) { - axisRenderer._calculateAngleFromOffset(offset); - } + final RenderBox renderBox = context.findRenderObject() as RenderBox; + if (_gauge.axes.isNotEmpty) { + for (int i = 0; i < _gauge.axes.length; i++) { + final RadialAxis axis = _gauge.axes[i]; + final RadialAxisRendererBase axisRenderer = + _renderingDetails.axisRenderers[i]; + final Offset offset = renderBox.globalToLocal(details.globalPosition); + if (axis.onAxisTapped != null && + axisRenderer.axisPath.getBounds().contains(offset)) { + axisRenderer.calculateAngleFromOffset(offset); } } } @@ -137,61 +199,70 @@ class _AxisContainer extends StatelessWidget { /// Method to check whether the axis pointer is dragging void _checkPointerDragging(BuildContext context, DragStartDetails details) { - final RenderBox renderBox = context.findRenderObject(); - if (details != null && details.globalPosition != null) { - final Offset tapPosition = - renderBox.globalToLocal(details.globalPosition); - for (num i = 0; i < _gauge.axes.length; i++) { - final RadialAxisRenderer axisRenderer = - _renderingDetails.axisRenderers[i]; - final List<_GaugePointerRenderer> pointerRenderers = - _renderingDetails.gaugePointerRenderers[i]; - if (_gauge.axes[i].pointers != null && - _gauge.axes[i].pointers.isNotEmpty) { - for (num j = 0; j < _gauge.axes[i].pointers.length; j++) { - final GaugePointer pointer = _gauge.axes[i].pointers[j]; - final _GaugePointerRenderer pointerRenderer = pointerRenderers[j]; - if (pointer.enableDragging) { - if (pointer is RangePointer) { - final _RangePointerRenderer renderer = pointerRenderer; - final Rect pathRect = Rect.fromLTRB( - renderer._arcPath.getBounds().left + - axisRenderer._axisCenter.dx - - 5, - renderer._arcPath.getBounds().top + - axisRenderer._axisCenter.dy - - 5, - renderer._arcPath.getBounds().right + - axisRenderer._axisCenter.dx + - 5, - renderer._arcPath.getBounds().bottom + - axisRenderer._axisCenter.dy + - 5); - - // Checks whether the tapped position is present inside - // the range pointer path - if (pathRect.contains(tapPosition)) { - renderer._isDragStarted = true; - renderer._createPointerValueChangeStartArgs(); - break; - } else { - renderer._isDragStarted = false; - } + final RenderBox renderBox = context.findRenderObject() as RenderBox; + final Offset tapPosition = renderBox.globalToLocal(details.globalPosition); + for (int i = 0; i < _gauge.axes.length; i++) { + final RadialAxisRendererBase axisRenderer = + _renderingDetails.axisRenderers[i]; + final List? pointerRenderers = + _renderingDetails.gaugePointerRenderers[i]; + if (_gauge.axes[i].pointers != null && + _gauge.axes[i].pointers!.isNotEmpty) { + for (int j = 0; j < _gauge.axes[i].pointers!.length; j++) { + final GaugePointer pointer = _gauge.axes[i].pointers![j]; + final GaugePointerRenderer pointerRenderer = pointerRenderers![j]; + pointerRenderer.isHovered = false; + if (pointer.enableDragging) { + if (pointer is RangePointer) { + final RangePointerRenderer renderer = + pointerRenderer as RangePointerRenderer; + final Rect pathRect = Rect.fromLTRB( + renderer.arcPath.getBounds().left + + axisRenderer.axisCenter.dx - + 5, + renderer.arcPath.getBounds().top + + axisRenderer.axisCenter.dy - + 5, + renderer.arcPath.getBounds().right + + axisRenderer.axisCenter.dx + + 5, + renderer.arcPath.getBounds().bottom + + axisRenderer.axisCenter.dy + + 5); + + // Checks whether the tapped position is present inside + // the range pointer path + if (pathRect.contains(tapPosition)) { + renderer.isDragStarted = true; + renderer.createPointerValueChangeStartArgs(); } else { - Rect pointerRect = pointerRenderer._pointerRect; - pointerRect = Rect.fromLTRB( - pointerRect.left - 20, - pointerRect.top - 20, - pointerRect.right - 20, - pointerRect.bottom - 20); - if (pointerRenderer._pointerRect.contains(tapPosition)) { - pointerRenderer._isDragStarted = true; - pointerRenderer._createPointerValueChangeStartArgs(); - break; - } else { - pointerRenderer._isDragStarted = false; - } + renderer.isDragStarted = false; } + } else { + Rect pointerRect = pointerRenderer.pointerRect; + pointerRect = Rect.fromLTRB( + pointerRect.left - 25, + pointerRect.top - 25, + pointerRect.right + 25, + pointerRect.bottom + 25); + if (pointerRenderer.pointerRect.contains(tapPosition)) { + pointerRenderer.isDragStarted = true; + pointerRenderer.isHovered = true; + pointerRenderer.createPointerValueChangeStartArgs(); + } else { + pointerRenderer.isDragStarted = false; + } + } + } + } + + final List renderers = (pointerRenderers!.where( + (element) => element.isHovered != null && element.isHovered!)) + .toList(); + if (renderers.length > 1) { + for (int i = 0; i < renderers.length; i++) { + if (i < renderers.length - 1) { + renderers[i].isHovered = false; } } } @@ -201,31 +272,31 @@ class _AxisContainer extends StatelessWidget { /// Method to ensure whether the pointer was dragged void _checkPointerIsDragged() { - if (_gauge.axes != null) { - for (num i = 0; i < _gauge.axes.length; i++) { - if (_gauge.axes[i].pointers != null && - _gauge.axes[i].pointers.isNotEmpty) { - final List<_GaugePointerRenderer> pointerRenderers = - _renderingDetails.gaugePointerRenderers[i]; - for (num j = 0; j < _gauge.axes[i].pointers.length; j++) { - final GaugePointer pointer = _gauge.axes[i].pointers[j]; - final _GaugePointerRenderer pointerRenderer = pointerRenderers[j]; - if (pointer.enableDragging) { - if (pointerRenderer._isDragStarted) { - pointerRenderer._createPointerValueChangeEndArgs(); - - if (pointer is NeedlePointer) { - final NeedlePointerRenderer renderer = pointerRenderer; - renderer._animationEndValue = renderer._getSweepAngle(); - } else if (pointer is MarkerPointer) { - final MarkerPointerRenderer renderer = pointerRenderer; - renderer._animationEndValue = renderer._getSweepAngle(); - } else { - final _RangePointerRenderer renderer = pointerRenderer; - renderer._animationEndValue = renderer._getSweepAngle(); - } + for (int i = 0; i < _gauge.axes.length; i++) { + if (_gauge.axes[i].pointers != null && + _gauge.axes[i].pointers!.isNotEmpty) { + final List pointerRenderers = + _renderingDetails.gaugePointerRenderers[i]!; + for (int j = 0; j < _gauge.axes[i].pointers!.length; j++) { + final GaugePointer pointer = _gauge.axes[i].pointers![j]; + final GaugePointerRenderer pointerRenderer = pointerRenderers[j]; + if (pointer.enableDragging) { + if (pointerRenderer.isDragStarted != null && + pointerRenderer.isDragStarted!) { + pointerRenderer.createPointerValueChangeEndArgs(); + if (pointer is RangePointer) { + final RangePointerRenderer renderer = + pointerRenderer as RangePointerRenderer; + renderer.animationEndValue = renderer.getSweepAngle(); + } else { + pointerRenderer.animationEndValue = + pointerRenderer.getSweepAngle(); } - pointerRenderer._isDragStarted = false; + } + pointerRenderer.isDragStarted = false; + pointerRenderer.isHovered = false; + if (!(pointer is WidgetPointer)) { + _renderingDetails.pointerRepaintNotifier.value++; } } } @@ -236,12 +307,12 @@ class _AxisContainer extends StatelessWidget { /// Calculates the axis position void _calculateAxisElementPosition( BuildContext context, BoxConstraints constraints) { - if (_gauge.axes != null && _gauge.axes.isNotEmpty) { + if (_gauge.axes.isNotEmpty) { for (int i = 0; i < _gauge.axes.length; i++) { - final RadialAxisRenderer axisRenderer = + final RadialAxisRendererBase? axisRenderer = _renderingDetails.axisRenderers[i]; if (axisRenderer != null) { - axisRenderer._calculateAxisRange( + axisRenderer.calculateAxisRange( constraints, context, _gaugeThemeData, _renderingDetails); } } @@ -251,47 +322,54 @@ class _AxisContainer extends StatelessWidget { /// Methods to add the gauge elements void _addGaugeElements(BoxConstraints constraints, BuildContext context, List gaugeWidgets) { - if (_gauge.axes != null && _gauge.axes.isNotEmpty) { + if (_gauge.axes.isNotEmpty) { final _AnimationDurationDetails durationDetails = _AnimationDurationDetails(); _calculateDurationForAnimation(durationDetails); for (int i = 0; i < _gauge.axes.length; i++) { final RadialAxis axis = _gauge.axes[i]; - final GaugeAxisRenderer axisRenderer = + final RadialAxisRendererBase axisRenderer = _renderingDetails.axisRenderers[i]; - _addAxis( axis, constraints, gaugeWidgets, durationDetails, axisRenderer); - if (axis.ranges != null && axis.ranges.isNotEmpty) { - final List<_GaugeRangeRenderer> rangeRenderers = - _renderingDetails.gaugeRangeRenderers[i]; + if (axis.ranges != null && axis.ranges!.isNotEmpty) { + final List rangeRenderers = + _renderingDetails.gaugeRangeRenderers[i]!; _addRange(axis, constraints, gaugeWidgets, durationDetails, axisRenderer, rangeRenderers); } - if (axis.pointers != null && axis.pointers.isNotEmpty) { - final List<_GaugePointerRenderer> pointerRenderers = - _renderingDetails.gaugePointerRenderers[i]; - for (num j = 0; j < axis.pointers.length; j++) { - final _GaugePointerRenderer pointerRenderer = pointerRenderers[j]; - if (axis.pointers[j] is NeedlePointer) { - final NeedlePointer needlePointer = axis.pointers[j]; + if (axis.pointers != null && axis.pointers!.isNotEmpty) { + final List pointerRenderers = + _renderingDetails.gaugePointerRenderers[i]!; + for (int j = 0; j < axis.pointers!.length; j++) { + final GaugePointerRenderer pointerRenderer = pointerRenderers[j]; + if (axis.pointers![j] is NeedlePointer) { + final NeedlePointer needlePointer = + axis.pointers![j] as NeedlePointer; _addNeedlePointer(axis, constraints, needlePointer, gaugeWidgets, durationDetails, axisRenderer, pointerRenderer); - } else if (axis.pointers[j] is MarkerPointer) { - final MarkerPointer markerPointer = axis.pointers[j]; + } else if (axis.pointers![j] is MarkerPointer) { + final MarkerPointer markerPointer = + axis.pointers![j] as MarkerPointer; _addMarkerPointer(axis, constraints, markerPointer, gaugeWidgets, durationDetails, axisRenderer, pointerRenderer); - } else if (axis.pointers[j] is RangePointer) { - final RangePointer rangePointer = axis.pointers[j]; + } else if (axis.pointers![j] is RangePointer) { + final RangePointer rangePointer = + axis.pointers![j] as RangePointer; _addRangePointer(axis, constraints, rangePointer, gaugeWidgets, durationDetails, axisRenderer, pointerRenderer); + } else if (axis.pointers![j] is WidgetPointer) { + final WidgetPointer widgetPointer = + axis.pointers![j] as WidgetPointer; + _addWidgetPointer(axis, constraints, widgetPointer, gaugeWidgets, + durationDetails, axisRenderer, pointerRenderer); } } } - if (axis.annotations != null && axis.annotations.isNotEmpty) { + if (axis.annotations != null && axis.annotations!.isNotEmpty) { _addAnnotation(axis, gaugeWidgets, durationDetails, axisRenderer); } } @@ -304,28 +382,28 @@ class _AxisContainer extends StatelessWidget { BoxConstraints constraints, List gaugeWidgets, _AnimationDurationDetails durationDetails, - RadialAxisRenderer axisRenderer) { - Animation axisAnimation; - Animation axisElementAnimation; + RadialAxisRendererBase axisRenderer) { + Animation? axisAnimation; + Animation? axisElementAnimation; if (_renderingDetails.needsToAnimateAxes && (durationDetails.hasAxisLine || durationDetails.hasAxisElements)) { - _renderingDetails.animationController.duration = + _renderingDetails.animationController!.duration = Duration(milliseconds: _gauge.animationDuration.toInt()); // Includes animation duration for axis line if (durationDetails.hasAxisLine) { axisAnimation = Tween(begin: 0, end: 1).animate(CurvedAnimation( - parent: _renderingDetails.animationController, - curve: Interval(durationDetails.axisLineInterval[0], - durationDetails.axisLineInterval[1], + parent: _renderingDetails.animationController!, + curve: Interval(durationDetails.axisLineInterval[0]!, + durationDetails.axisLineInterval[1]!, curve: Curves.easeIn))); } // Includes animation duration for axis ticks and labels if (durationDetails.hasAxisElements) { axisElementAnimation = Tween(begin: 0, end: 1).animate( CurvedAnimation( - parent: _renderingDetails.animationController, - curve: Interval(durationDetails.axisElementsInterval[0], - durationDetails.axisElementsInterval[1], + parent: _renderingDetails.animationController!, + curve: Interval(durationDetails.axisElementsInterval[0]!, + durationDetails.axisElementsInterval[1]!, curve: Curves.easeIn))); } } @@ -333,10 +411,10 @@ class _AxisContainer extends StatelessWidget { gaugeWidgets.add(Container( child: RepaintBoundary( child: CustomPaint( - painter: _AxisPainter( + painter: AxisPainter( _gauge, axis, - axisRenderer._needsRepaintAxis ?? true, + axisRenderer.needsRepaintAxis, _renderingDetails.axisRepaintNotifier, axisAnimation, axisElementAnimation, @@ -348,7 +426,7 @@ class _AxisContainer extends StatelessWidget { )); if (axisAnimation != null || axisElementAnimation != null) { - _renderingDetails.animationController.forward(from: 0.0); + _renderingDetails.animationController!.forward(from: 0.0); } } @@ -359,41 +437,46 @@ class _AxisContainer extends StatelessWidget { RangePointer rangePointer, List gaugeWidgets, _AnimationDurationDetails durationDetails, - RadialAxisRenderer axisRenderer, - _GaugePointerRenderer pointerRenderer) { - final _RangePointerRenderer rangePointerRenderer = pointerRenderer; - rangePointerRenderer._animationEndValue = - rangePointerRenderer._getSweepAngle(); - Animation pointerAnimation; - final List animationIntervals = + RadialAxisRendererBase axisRenderer, + GaugePointerRenderer pointerRenderer) { + final RangePointerRenderer rangePointerRenderer = + pointerRenderer as RangePointerRenderer; + rangePointerRenderer.animationEndValue = + rangePointerRenderer.getSweepAngle(); + Animation? pointerAnimation; + final List animationIntervals = _getPointerAnimationInterval(durationDetails); if (_renderingDetails.needsToAnimatePointers || (rangePointer.enableAnimation && rangePointer.animationDuration > 0 && - rangePointerRenderer._needsAnimate)) { - _renderingDetails.animationController.duration = Duration( + rangePointerRenderer.needsAnimate != null && + rangePointerRenderer.needsAnimate!)) { + _renderingDetails.animationController!.duration = Duration( milliseconds: _getPointerAnimationDuration(rangePointer.animationDuration)); final Curve pointerAnimationType = _getCurveAnimation(rangePointer.animationType); - final double endValue = rangePointerRenderer._animationEndValue; - pointerAnimation = Tween( - begin: rangePointerRenderer._animationStartValue ?? 0, - end: endValue) + double endValue = rangePointerRenderer.animationEndValue!; + final double startValue = rangePointerRenderer.animationStartValue ?? 0; + _renderingDetails.isOpacityAnimation = startValue == endValue; + if (_renderingDetails.isOpacityAnimation) { + endValue = 1; + } + pointerAnimation = Tween(begin: startValue, end: endValue) .animate(CurvedAnimation( - parent: _renderingDetails.animationController, - curve: Interval(animationIntervals[0], animationIntervals[1], + parent: _renderingDetails.animationController!, + curve: Interval(animationIntervals[0]!, animationIntervals[1]!, curve: pointerAnimationType))); } gaugeWidgets.add(Container( child: RepaintBoundary( child: CustomPaint( - painter: _RangePointerPainter( + painter: RangePointerPainter( _gauge, axis, rangePointer, - rangePointerRenderer._needsRepaintPointer ?? true, + rangePointerRenderer.needsRepaintPointer, pointerAnimation, _renderingDetails.pointerRepaintNotifier, _gaugeThemeData, @@ -404,8 +487,8 @@ class _AxisContainer extends StatelessWidget { ), )); if (_renderingDetails.needsToAnimatePointers || - rangePointerRenderer._getIsPointerAnimationEnabled()) { - _renderingDetails.animationController.forward(from: 0.0); + rangePointerRenderer.getIsPointerAnimationEnabled()) { + _renderingDetails.animationController!.forward(from: 0.0); } } @@ -416,41 +499,54 @@ class _AxisContainer extends StatelessWidget { NeedlePointer needlePointer, List gaugeWidgets, _AnimationDurationDetails durationDetails, - RadialAxisRenderer axisRenderer, - _GaugePointerRenderer pointerRenderer) { - final NeedlePointerRenderer needleRenderer = pointerRenderer; - Animation pointerAnimation; - final List intervals = + RadialAxisRendererBase axisRenderer, + GaugePointerRenderer pointerRenderer) { + final NeedlePointerRendererBase needleRenderer = + pointerRenderer as NeedlePointerRendererBase; + Animation? pointerAnimation; + final List intervals = _getPointerAnimationInterval(durationDetails); - needleRenderer._animationEndValue = needleRenderer._getSweepAngle(); + needleRenderer.animationEndValue = needleRenderer.getSweepAngle(); + _renderingDetails.isOpacityAnimation = false; if (_renderingDetails.needsToAnimatePointers || (needlePointer.enableAnimation && needlePointer.animationDuration > 0 && - needleRenderer._needsAnimate)) { - _renderingDetails.animationController.duration = Duration( + needleRenderer.needsAnimate != null && + needleRenderer.needsAnimate!)) { + _renderingDetails.animationController!.duration = Duration( milliseconds: _getPointerAnimationDuration(needlePointer.animationDuration)); final double startValue = axis.isInversed ? 1 : 0; final double endValue = axis.isInversed ? 0 : 1; - double actualValue = needleRenderer._animationStartValue ?? startValue; + double actualValue = needleRenderer.animationStartValue ?? startValue; actualValue = actualValue == endValue ? startValue : actualValue; + _renderingDetails.isOpacityAnimation = (_gauge.enableLoadingAnimation && + actualValue == needleRenderer.animationEndValue); + if (_renderingDetails.isOpacityAnimation) { + needleRenderer.animationEndValue = endValue; + if (axis.isInversed) { + needleRenderer.animationEndValue = actualValue; + actualValue = endValue; + } + } + pointerAnimation = Tween( - begin: actualValue, end: needleRenderer._animationEndValue) + begin: actualValue, end: needleRenderer.animationEndValue) .animate(CurvedAnimation( - parent: _renderingDetails.animationController, - curve: Interval(intervals[0], intervals[1], + parent: _renderingDetails.animationController!, + curve: Interval(intervals[0]!, intervals[1]!, curve: _getCurveAnimation(needlePointer.animationType)))); } gaugeWidgets.add(Container( child: RepaintBoundary( child: CustomPaint( - painter: _NeedlePointerPainter( + painter: NeedlePointerPainter( _gauge, axis, needlePointer, - needleRenderer._needsRepaintPointer ?? true, + needleRenderer.needsRepaintPointer, pointerAnimation, _renderingDetails.pointerRepaintNotifier, _gaugeThemeData, @@ -462,8 +558,8 @@ class _AxisContainer extends StatelessWidget { )); if (_renderingDetails.needsToAnimatePointers || - needleRenderer._getIsPointerAnimationEnabled()) { - _renderingDetails.animationController.forward(from: 0.0); + needleRenderer.getIsPointerAnimationEnabled()) { + _renderingDetails.animationController!.forward(from: 0.0); } } @@ -474,39 +570,51 @@ class _AxisContainer extends StatelessWidget { MarkerPointer markerPointer, List gaugeWidgets, _AnimationDurationDetails durationDetails, - RadialAxisRenderer axisRenderer, - _GaugePointerRenderer pointerRenderer) { - final MarkerPointerRenderer markerRenderer = pointerRenderer; - Animation pointerAnimation; - final List pointerIntervals = + RadialAxisRendererBase axisRenderer, + GaugePointerRenderer pointerRenderer) { + final MarkerPointerRendererBase markerRenderer = + pointerRenderer as MarkerPointerRendererBase; + Animation? pointerAnimation; + final List pointerIntervals = _getPointerAnimationInterval(durationDetails); - markerRenderer._animationEndValue = markerRenderer._getSweepAngle(); + markerRenderer.animationEndValue = markerRenderer.getSweepAngle(); + _renderingDetails.isOpacityAnimation = false; if (_renderingDetails.needsToAnimatePointers || (markerPointer.enableAnimation && markerPointer.animationDuration > 0 && - markerRenderer._needsAnimate)) { - _renderingDetails.animationController.duration = Duration( + markerRenderer.needsAnimate != null && + markerRenderer.needsAnimate!)) { + _renderingDetails.animationController!.duration = Duration( milliseconds: _getPointerAnimationDuration(markerPointer.animationDuration)); - final double startValue = axis.isInversed ? 1 : 0; + final double endValue = axis.isInversed ? 0 : 1; + double actualValue = markerRenderer.animationStartValue ?? startValue; + _renderingDetails.isOpacityAnimation = (_gauge.enableLoadingAnimation && + actualValue == markerRenderer.animationEndValue); + if (_renderingDetails.isOpacityAnimation) { + markerRenderer.animationEndValue = endValue; + if (axis.isInversed) { + markerRenderer.animationEndValue = actualValue; + actualValue = endValue; + } + } + pointerAnimation = Tween( - begin: markerRenderer._animationStartValue ?? startValue, - end: markerRenderer._animationEndValue) + begin: actualValue, end: markerRenderer.animationEndValue) .animate(CurvedAnimation( - parent: _renderingDetails.animationController, - curve: Interval(pointerIntervals[0], pointerIntervals[1], + parent: _renderingDetails.animationController!, + curve: Interval(pointerIntervals[0]!, pointerIntervals[1]!, curve: _getCurveAnimation(markerPointer.animationType)))); } - gaugeWidgets.add(Container( child: RepaintBoundary( child: CustomPaint( - painter: _MarkerPointerPainter( + painter: MarkerPointerPainter( _gauge, axis, markerPointer, - markerRenderer._needsRepaintPointer ?? true, + markerRenderer.needsRepaintPointer, pointerAnimation, _renderingDetails.pointerRepaintNotifier, _gaugeThemeData, @@ -518,9 +626,60 @@ class _AxisContainer extends StatelessWidget { )); if (_renderingDetails.needsToAnimatePointers || - markerRenderer._getIsPointerAnimationEnabled()) { - _renderingDetails.animationController.forward(from: 0.0); + markerRenderer.getIsPointerAnimationEnabled()) { + _renderingDetails.animationController!.forward(from: 0.0); + } + } + + /// Adds the widget pointer + void _addWidgetPointer( + RadialAxis axis, + BoxConstraints constraints, + WidgetPointer widgetPointer, + List gaugeWidgets, + _AnimationDurationDetails durationDetails, + RadialAxisRendererBase axisRenderer, + GaugePointerRenderer pointerRenderer) { + final WidgetPointerRenderer widgetPointerRenderer = + pointerRenderer as WidgetPointerRenderer; + double? actualValue; + final List pointerIntervals = + _getPointerAnimationInterval(durationDetails); + widgetPointerRenderer.animationEndValue = + widgetPointerRenderer.getSweepAngle(); + _renderingDetails.isOpacityAnimation = false; + if (_renderingDetails.needsToAnimatePointers || + (widgetPointer.enableAnimation && + widgetPointer.animationDuration > 0 && + widgetPointerRenderer.needsAnimate != null && + widgetPointerRenderer.needsAnimate!)) { + final double startValue = axis.isInversed ? 1 : 0; + final double endValue = axis.isInversed ? 0 : 1; + actualValue = widgetPointerRenderer.animationStartValue ?? startValue; + _renderingDetails.isOpacityAnimation = (_gauge.enableLoadingAnimation && + actualValue == widgetPointerRenderer.animationEndValue); + if (_renderingDetails.isOpacityAnimation) { + widgetPointerRenderer.animationEndValue = endValue; + if (axis.isInversed) { + widgetPointerRenderer.animationEndValue = actualValue; + actualValue = endValue; + } + } } + final GlobalKey key = GlobalKey(); + gaugeWidgets.add(WidgetPointerContainer( + key: key, + widgetPointer: widgetPointer, + gauge: _gauge, + axis: axis, + interval: pointerIntervals, + duration: _getPointerAnimationDuration(widgetPointer.animationDuration), + renderingDetails: _renderingDetails, + axisRenderer: axisRenderer, + pointerRenderer: widgetPointerRenderer, + startValue: actualValue, + endValue: widgetPointerRenderer.animationEndValue, + curve: _getCurveAnimation(widgetPointer.animationType))); } /// Adds the axis range @@ -529,31 +688,31 @@ class _AxisContainer extends StatelessWidget { BoxConstraints constraints, List gaugeWidgets, _AnimationDurationDetails durationDetails, - RadialAxisRenderer axisRenderer, - List<_GaugeRangeRenderer> rangeRenderers) { - for (num k = 0; k < axis.ranges.length; k++) { - final GaugeRange range = axis.ranges[k]; - final _GaugeRangeRenderer rangeRenderer = rangeRenderers[k]; - Animation rangeAnimation; + RadialAxisRendererBase axisRenderer, + List rangeRenderers) { + for (int k = 0; k < axis.ranges!.length; k++) { + final GaugeRange range = axis.ranges![k]; + final GaugeRangeRenderer rangeRenderer = rangeRenderers[k]; + Animation? rangeAnimation; if (_renderingDetails.needsToAnimateRanges) { - _renderingDetails.animationController.duration = + _renderingDetails.animationController!.duration = Duration(milliseconds: _gauge.animationDuration.toInt()); rangeAnimation = Tween(begin: 0, end: 1).animate( CurvedAnimation( - parent: _renderingDetails.animationController, - curve: Interval(durationDetails.rangesInterval[0], - durationDetails.rangesInterval[1], + parent: _renderingDetails.animationController!, + curve: Interval(durationDetails.rangesInterval[0]!, + durationDetails.rangesInterval[1]!, curve: Curves.easeIn))); } gaugeWidgets.add(Container( child: RepaintBoundary( child: CustomPaint( - painter: _RangePainter( + painter: RangePainter( _gauge, axis, range, - rangeRenderer._needsRepaintRange ?? true, + rangeRenderer.needsRepaintRange, rangeAnimation, _renderingDetails.rangeRepaintNotifier, _gaugeThemeData, @@ -565,7 +724,7 @@ class _AxisContainer extends StatelessWidget { )); if (rangeAnimation != null) { - _renderingDetails.animationController.forward(from: 0.0); + _renderingDetails.animationController!.forward(from: 0.0); } } } @@ -580,9 +739,9 @@ class _AxisContainer extends StatelessWidget { } /// Returns the animation interval of pointers - List _getPointerAnimationInterval( + List _getPointerAnimationInterval( _AnimationDurationDetails durationDetails) { - List pointerIntervals = List(2); + List pointerIntervals = List.filled(2, null); if (_renderingDetails.needsToAnimatePointers) { pointerIntervals = durationDetails.pointersInterval; } else { @@ -598,10 +757,10 @@ class _AxisContainer extends StatelessWidget { RadialAxis axis, List gaugeWidgets, _AnimationDurationDetails durationDetails, - RadialAxisRenderer axisRenderer) { - for (num j = 0; j < axis.annotations.length; j++) { - final GaugeAnnotation annotation = axis.annotations[j]; - gaugeWidgets.add(_AnnotationRenderer( + RadialAxisRendererBase axisRenderer) { + for (int j = 0; j < axis.annotations!.length; j++) { + final GaugeAnnotation annotation = axis.annotations![j]; + gaugeWidgets.add(AnnotationRenderer( annotation: annotation, gauge: _gauge, axis: axis, @@ -619,7 +778,7 @@ class _AxisContainer extends StatelessWidget { double interval; double startValue = 0.05; double endValue = 0; - for (num i = 0; i < _gauge.axes.length; i++) { + for (int i = 0; i < _gauge.axes.length; i++) { _calculateAxisElements(_gauge.axes[i], durationDetails); } @@ -628,7 +787,7 @@ class _AxisContainer extends StatelessWidget { interval = 1 / totalCount; endValue = interval; if (durationDetails.hasAxisLine) { - durationDetails.axisLineInterval = List(2); + durationDetails.axisLineInterval = List.filled(2, null); durationDetails.axisLineInterval[0] = startValue; durationDetails.axisLineInterval[1] = endValue; startValue = endValue; @@ -636,7 +795,7 @@ class _AxisContainer extends StatelessWidget { } if (durationDetails.hasAxisElements) { - durationDetails.axisElementsInterval = List(2); + durationDetails.axisElementsInterval = List.filled(2, null); durationDetails.axisElementsInterval[0] = startValue; durationDetails.axisElementsInterval[1] = endValue; startValue = endValue; @@ -644,7 +803,7 @@ class _AxisContainer extends StatelessWidget { } if (durationDetails.hasRanges) { - durationDetails.rangesInterval = List(2); + durationDetails.rangesInterval = List.filled(2, null); durationDetails.rangesInterval[0] = startValue; durationDetails.rangesInterval[1] = endValue; startValue = endValue; @@ -652,7 +811,7 @@ class _AxisContainer extends StatelessWidget { } if (durationDetails.hasPointers) { - durationDetails.pointersInterval = List(2); + durationDetails.pointersInterval = List.filled(2, null); durationDetails.pointersInterval[0] = startValue; durationDetails.pointersInterval[1] = endValue; startValue = endValue; @@ -660,7 +819,7 @@ class _AxisContainer extends StatelessWidget { } if (durationDetails.hasAnnotations) { - durationDetails.annotationInterval = List(2); + durationDetails.annotationInterval = List.filled(2, null); durationDetails.annotationInterval[0] = startValue; durationDetails.annotationInterval[1] = endValue; } @@ -705,19 +864,19 @@ class _AxisContainer extends StatelessWidget { } if (axis.ranges != null && - axis.ranges.isNotEmpty && + axis.ranges!.isNotEmpty && !durationDetails.hasRanges) { durationDetails.hasRanges = true; } if (axis.pointers != null && - axis.pointers.isNotEmpty && + axis.pointers!.isNotEmpty && !durationDetails.hasPointers) { durationDetails.hasPointers = true; } if (axis.annotations != null && - axis.annotations.isNotEmpty && + axis.annotations!.isNotEmpty && !durationDetails.hasAnnotations) { durationDetails.hasAnnotations = true; } @@ -767,19 +926,19 @@ class _AxisContainer extends StatelessWidget { /// Holds the load time animation duration details. class _AnimationDurationDetails { /// Specifies the axis line interval for animation - List axisLineInterval; + late List axisLineInterval; /// Specifies the axis element interval for load time animation - List axisElementsInterval; + late List axisElementsInterval; /// Specifies the range interval for initial animation - List rangesInterval; + late List rangesInterval; /// Specifies the pointer interval for load time animation - List pointersInterval; + late List pointersInterval; /// Specifies the annotation interval for load time animation - List annotationInterval; + late List annotationInterval; /// Specifies whether the axis line is enabled bool hasAxisLine = false; @@ -796,3 +955,66 @@ class _AnimationDurationDetails { /// Specifies whether the annotation is added bool hasAnnotations = false; } + +/// Holds the animation and repainter details. +class RenderingDetails { + /// Holds the pointer repaint notifier + late ValueNotifier pointerRepaintNotifier; + + /// Holds the range repaint notifier + late ValueNotifier rangeRepaintNotifier; + + /// Holds the axis repaint notifier + late ValueNotifier axisRepaintNotifier; + + /// Holds the animation controller + AnimationController? animationController; + + /// Specifies whether to animate axes + late bool needsToAnimateAxes; + + /// Specifies whether to animate ranges + late bool needsToAnimateRanges; + + /// Specifies whether to animate pointers + late bool needsToAnimatePointers; + + /// Specifies whether to animate annotation + late bool needsToAnimateAnnotation; + + /// Specifies axis renderer corresponding to the axis + late List axisRenderers; + + /// Specifies the gauge pointer renderers + late Map> gaugePointerRenderers; + + /// Specifies the gauge range renderers + late Map> gaugeRangeRenderers; + + /// Specifies whether to animate pointer with opacity + late bool isOpacityAnimation; +} + +/// Specifies the offset value of tick +class TickOffset { + /// Holds the start point + late Offset startPoint; + + /// Holds the end point + late Offset endPoint; + + /// Holds the tick value + late double value; +} + +/// Represents the arc data +class ArcData { + /// Represents the start angle + late double startAngle; + + /// Represents the end angle + late double endAngle; + + /// Represents the arc rect + late Rect arcRect; +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/widget_pointer_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/widget_pointer_renderer.dart new file mode 100644 index 000000000..7676adce3 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/common/widget_pointer_renderer.dart @@ -0,0 +1,317 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../axis/radial_axis.dart'; +import '../common/radial_gauge_renderer.dart'; +import '../gauge/radial_gauge.dart'; +import '../pointers/widget_pointer.dart'; +import '../renderers/radial_axis_renderer_base.dart'; +import '../renderers/widget_pointer_renderer_base.dart'; +import '../utils/helper.dart'; + +/// Represents the annotation renderer +/// +// ignore: must_be_immutable +class WidgetPointerContainer extends StatefulWidget { + ///Creates the annotation renderer + /// + // ignore: prefer_const_constructors_in_immutables + WidgetPointerContainer( + {Key? key, + required this.widgetPointer, + required this.gauge, + required this.axis, + required this.renderingDetails, + required this.interval, + required this.duration, + required this.axisRenderer, + required this.pointerRenderer, + required this.startValue, + required this.endValue, + required this.curve}) + : super( + key: key, + ); + + /// specifies the annotation + final WidgetPointer widgetPointer; + + /// Specifies the radial gauge + final SfRadialGauge gauge; + + /// Specifies the annotation axis + final RadialAxis axis; + + /// Hold the radial gauge animation details + final RenderingDetails renderingDetails; + + /// Specifies the axis renderer + final RadialAxisRendererBase axisRenderer; + + /// Specifies the pointer renderer + final WidgetPointerRenderer pointerRenderer; + + /// Specifies the interval duration + final List interval; + + /// Specifies the animation duration + final int duration; + + /// Specifies the animation start value + final double? startValue; + + /// Specifies the animation end value + final double? endValue; + + /// Specifies the animation curve + final Curve curve; + + @override + State createState() { + return WidgetPointerContainerState(); + } +} + +/// Represents the annotation renderer state +class WidgetPointerContainerState extends State + with SingleTickerProviderStateMixin { + /// Specifies the offset of positioning the annotation + double? pointerAngle; + + /// Holds the animation controller + AnimationController? animationController; + + /// Specifies the pointer animation + Animation? pointerAnimation; + + /// Specifies the pointer needs to get animate + bool needsPointerAnimation = false; + + /// Refreshes the pointer position while dragging + void refresh() { + setState(() {}); + } + + @override + void didUpdateWidget(WidgetPointerContainer oldWidget) { + super.didUpdateWidget(oldWidget); + } + + @override + void initState() { + animationController = AnimationController(vsync: this); + widget.pointerRenderer.pointerRenderer = widget; + super.initState(); + } + + @override + Widget build(BuildContext context) { + needsPointerAnimation = + widget.pointerRenderer.getIsPointerAnimationEnabled(); + final Widget _widget = widget.widgetPointer.child; + if (widget.renderingDetails.needsToAnimatePointers || + needsPointerAnimation) { + animationController!.duration = Duration(milliseconds: widget.duration); + pointerAnimation = + Tween(begin: widget.startValue, end: widget.endValue).animate( + CurvedAnimation( + parent: animationController!, + curve: Interval(widget.interval[0]!, widget.interval[1]!, + curve: widget.curve))); + animationController!.forward(from: 0.0); + + return AnimatedBuilder( + animation: animationController!, + child: _widget, + builder: (context, _widget) { + _updateAnimation(); + final Offset? offset = _getWidgetOffset(); + + return Opacity( + opacity: offset == null || + (widget.renderingDetails.needsToAnimatePointers && + widget.axis.minimum == + widget.pointerRenderer.currentValue) + ? 0 + : 1, + child: _WidgetPointerRenderObject( + child: _widget!, + position: offset ?? Offset(0, 0), + renderer: widget.pointerRenderer)); + }); + } else { + return _WidgetPointerRenderObject( + child: _widget, + position: _getWidgetOffset()!, + renderer: widget.pointerRenderer); + } + } + + /// Updates the animation of widget pointer + void _updateAnimation() { + // Disables the animation once the animation reached the current + // pointer angle + // final double _angle = pointerAngle; + if (needsPointerAnimation && + pointerAngle != null && + pointerAngle == + widget.axisRenderer.sweepAngle * + widget.pointerRenderer.animationEndValue! + + widget.axis.startAngle) { + widget.pointerRenderer.needsAnimate = false; + } + + if (widget.renderingDetails.needsToAnimatePointers && + widget.gauge.axes[widget.gauge.axes.length - 1] == widget.axis && + widget.axis.pointers![widget.axis.pointers!.length - 1] == + widget.widgetPointer && + ((!widget.renderingDetails.isOpacityAnimation && + pointerAngle != null && + pointerAngle == + widget.axisRenderer.sweepAngle * + widget.pointerRenderer.animationEndValue! + + widget.axis.startAngle) || + (widget.renderingDetails.isOpacityAnimation && + pointerAnimation!.value == 1))) { + widget.renderingDetails.needsToAnimatePointers = false; + } + } + + /// Calculates the poition of the widget + Offset? _getWidgetOffset() { + pointerAngle = 0; + Offset? offset; + bool? needsShowPointer; + if (pointerAnimation != null) { + needsShowPointer = widget.axis.isInversed + ? pointerAnimation!.value < 1 + : pointerAnimation!.value > 0; + } + + if ((widget.renderingDetails.needsToAnimatePointers && + (!widget.renderingDetails.isOpacityAnimation && + widget.axis.minimum != widget.pointerRenderer.currentValue)) || + (needsPointerAnimation && + (!widget.gauge.enableLoadingAnimation || + (widget.gauge.enableLoadingAnimation && + pointerAnimation!.value != 1 && + !widget.renderingDetails.isOpacityAnimation && + !widget.renderingDetails.needsToAnimatePointers)))) { + if ((widget.renderingDetails.needsToAnimatePointers && + needsShowPointer != null && + needsShowPointer) || + !widget.renderingDetails.needsToAnimatePointers) { + pointerAngle = + (widget.axisRenderer.sweepAngle * pointerAnimation!.value) + + widget.axis.startAngle; + offset = widget.pointerRenderer.getPointerOffset( + getDegreeToRadian(pointerAngle!), widget.widgetPointer); + } + } else { + offset = widget.pointerRenderer.offset; + pointerAngle = widget.pointerRenderer.angle; + } + + return offset; + } + + @override + void dispose() { + if (animationController != null) { + animationController!.dispose(); + } + + super.dispose(); + } +} + +/// Represents the render object for widget pointer. +class _WidgetPointerRenderObject extends SingleChildRenderObjectWidget { + const _WidgetPointerRenderObject( + {Key? key, + required Widget child, + required this.position, + required this.renderer}) + : super(key: key, child: child); + + /// Specifies the offset position for annotation + final Offset position; + + /// Specifies the widget pointer renderer + final WidgetPointerRenderer renderer; + + @override + RenderObject createRenderObject(BuildContext context) { + return _RenderPointerWidget(position, renderer); + } + + @override + void updateRenderObject( + BuildContext context, covariant _RenderPointerWidget renderObject) { + renderObject + ..position = position + ..renderer = renderer; + } +} + +/// Render the annotation widget in the respective position. +class _RenderPointerWidget extends RenderShiftedBox { + _RenderPointerWidget(this._position, this._renderer, [RenderBox? child]) + : super(child); + + /// Holds the annotation position + Offset? _position; + + /// Gets the annotation position + Offset? get position => _position!; + + /// Sets the annotation position + set position(Offset? value) { + if (_position != value) { + _position = value; + markNeedsLayout(); + } + } + + /// Holds the widget pointer renderer + WidgetPointerRenderer _renderer; + + /// Gets the widget pointer renderer + WidgetPointerRenderer get renderer => _renderer; + + /// Sets the widget pointer renderer + set renderer(WidgetPointerRenderer value) { + if (_renderer != value) { + _renderer = value; + markNeedsLayout(); + } + } + + /// Specifies the margin value for pointer rect + final double _margin = 15; + + @override + void performLayout() { + final BoxConstraints boxConstraints = constraints; + if (child != null) { + child!.layout(boxConstraints, parentUsesSize: true); + size = + boxConstraints.constrain(Size(child!.size.width, child!.size.height)); + + if (child!.parentData is BoxParentData && position != null) { + final BoxParentData childParentData = + child!.parentData as BoxParentData; + final double dx = position!.dx - child!.size.width / 2; + final double dy = position!.dy - child!.size.height / 2; + _renderer.pointerRect = Rect.fromLTRB( + dx - size.width / 2 - _margin, + dy - size.height / 2 - _margin, + dx + size.width / 2 + _margin, + dy + size.height / 2 + _margin); + childParentData.offset = Offset(dx, dy); + } + } else { + size = Size.zero; + } + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge/radial_gauge.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge/radial_gauge.dart index 0e311c49f..8c03c3199 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge/radial_gauge.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge/radial_gauge.dart @@ -1,6 +1,32 @@ -part of gauges; - -/// Create radial gauge widget to displays numerical values on a circular scale. +import 'dart:ui'; +import 'dart:ui' as dart_ui; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import '../axis/radial_axis.dart'; +import '../common/common.dart'; +import '../common/radial_gauge_renderer.dart'; + +import '../pointers/gauge_pointer.dart'; +import '../pointers/marker_pointer.dart'; +import '../pointers/needle_pointer.dart'; +import '../pointers/range_pointer.dart'; +import '../pointers/widget_pointer.dart'; +import '../range/gauge_range.dart'; + +import '../renderers/gauge_pointer_renderer.dart'; +import '../renderers/gauge_range_renderer.dart'; +import '../renderers/marker_pointer_renderer.dart'; +import '../renderers/marker_pointer_renderer_base.dart'; +import '../renderers/needle_pointer_renderer.dart'; +import '../renderers/needle_pointer_renderer_base.dart'; +import '../renderers/radial_axis_renderer.dart'; +import '../renderers/radial_axis_renderer_base.dart'; +import '../renderers/range_pointer_renderer.dart'; +import '../renderers/widget_pointer_renderer_base.dart'; +import '../utils/enum.dart'; + +/// Create a radial gauge widget to displays numerical values on a circular scale. /// It has a rich set of features /// such as axes, ranges, pointers, and annotations that are fully /// customizable and extendable. @@ -8,8 +34,8 @@ part of gauges; /// meter gauges, multiaxis clocks, watches, activity gauges, compasses, /// and more. /// -/// The radial gauge widget allows to customize its appearance -/// using [SfGaugeThemeData] available from [SfGaugeTheme] widget or +/// The radial gauge widget allows customizing its appearance +/// using [SfGaugeThemeData] available from the [SfGaugeTheme] widget or /// the [SfTheme] with the help of [SfThemeData]. /// /// ```dart @@ -30,8 +56,8 @@ class SfRadialGauge extends StatefulWidget { /// To enable the loading animation set [enableLoadingAnimation] is true. // ignore: prefer_const_constructors_in_immutables SfRadialGauge( - {Key key, - List axes, + {Key? key, + List? axes, this.enableLoadingAnimation = false, this.animationDuration = 2000, this.backgroundColor = Colors.transparent, @@ -82,7 +108,7 @@ class SfRadialGauge extends StatefulWidget { /// )); ///} /// ``` - final GaugeTitle title; + final GaugeTitle? title; /// Specifies the load time animation for axis elements, range and /// pointers with [animationDuration]. @@ -142,18 +168,18 @@ class SfRadialGauge extends StatefulWidget { class SfRadialGaugeState extends State with SingleTickerProviderStateMixin { /// Represents the gauge theme - SfGaugeThemeData _gaugeTheme; + late SfGaugeThemeData _gaugeTheme; /// Hold the radial gauge rendering details - _RenderingDetails _renderingDetails; + late RenderingDetails _renderingDetails; @override void initState() { - _renderingDetails = _RenderingDetails(); - _renderingDetails.axisRenderers = []; + _renderingDetails = RenderingDetails(); + _renderingDetails.axisRenderers = []; _renderingDetails.gaugePointerRenderers = - >{}; - _renderingDetails.gaugeRangeRenderers = >{}; + >{}; + _renderingDetails.gaugeRangeRenderers = >{}; _renderingDetails.pointerRepaintNotifier = ValueNotifier(0); _renderingDetails.rangeRepaintNotifier = ValueNotifier(0); _renderingDetails.axisRepaintNotifier = ValueNotifier(0); @@ -173,7 +199,7 @@ class SfRadialGaugeState extends State @override void didUpdateWidget(SfRadialGauge oldWidget) { - if (widget.axes != null && widget.axes.isNotEmpty) { + if (widget.axes.isNotEmpty) { _renderingDetails.needsToAnimateAnnotation = false; _renderingDetails.needsToAnimatePointers = false; _renderingDetails.needsToAnimateRanges = false; @@ -186,22 +212,22 @@ class SfRadialGaugeState extends State @override void didChangeDependencies() { - _gaugeTheme = SfGaugeTheme.of(context); + _gaugeTheme = SfGaugeTheme.of(context) as SfGaugeThemeData; super.didChangeDependencies(); } /// Method is used to create renderers for gauge features void _createRenderer() { - if (widget.axes != null && widget.axes.isNotEmpty) { + if (widget.axes.isNotEmpty) { for (int i = 0; i < widget.axes.length; i++) { final RadialAxis axis = widget.axes[i]; - final RadialAxisRenderer axisRenderer = _createAxisRenderer(axis); + final RadialAxisRendererBase axisRenderer = _createAxisRenderer(axis); _renderingDetails.axisRenderers.add(axisRenderer); - if (axis.ranges != null && axis.ranges.isNotEmpty) { + if (axis.ranges != null && axis.ranges!.isNotEmpty) { _createRangesRenderer(i, axisRenderer); } - if (axis.pointers != null && axis.pointers.isNotEmpty) { + if (axis.pointers != null && axis.pointers!.isNotEmpty) { _createPointersRenderers(i, axisRenderer); } } @@ -209,13 +235,14 @@ class SfRadialGaugeState extends State } /// Method is used to create gauge range renderer - void _createRangesRenderer(int axisIndex, RadialAxisRenderer axisRenderer) { - final List<_GaugeRangeRenderer> rangeRenderers = <_GaugeRangeRenderer>[]; + void _createRangesRenderer( + int axisIndex, RadialAxisRendererBase axisRenderer) { + final List rangeRenderers = []; final RadialAxis axis = widget.axes[axisIndex]; - for (int j = 0; j < axis.ranges.length; j++) { - final GaugeRange range = axis.ranges[j]; - final _GaugeRangeRenderer renderer = _createRangeRenderer(range); - renderer._axisRenderer = axisRenderer; + for (int j = 0; j < axis.ranges!.length; j++) { + final GaugeRange range = axis.ranges![j]; + final GaugeRangeRenderer renderer = _createRangeRenderer(range); + renderer.axisRenderer = axisRenderer; rangeRenderers.add(renderer); } @@ -224,16 +251,16 @@ class SfRadialGaugeState extends State /// Method is used to create gauge pointer renderer void _createPointersRenderers( - int axisIndex, RadialAxisRenderer axisRenderer) { - final List<_GaugePointerRenderer> pointerRenderers = - <_GaugePointerRenderer>[]; + int axisIndex, RadialAxisRendererBase axisRenderer) { + final List pointerRenderers = + []; final RadialAxis axis = widget.axes[axisIndex]; - for (int j = 0; j < axis.pointers.length; j++) { - final GaugePointer pointer = axis.pointers[j]; - final _GaugePointerRenderer renderer = _createPointerRenderer(pointer); - renderer._axisRenderer = axisRenderer; - renderer._needsAnimate = true; - renderer._animationStartValue = + for (int j = 0; j < axis.pointers!.length; j++) { + final GaugePointer pointer = axis.pointers![j]; + final GaugePointerRenderer renderer = _createPointerRenderer(pointer); + renderer.axisRenderer = axisRenderer; + renderer.needsAnimate = true; + renderer.animationStartValue = axis.isInversed && !(pointer is RangePointer) ? 1 : 0; pointerRenderers.add(renderer); } @@ -254,56 +281,59 @@ class SfRadialGaugeState extends State /// Method to check whether the gauge needs to be repainted void _needsRepaintGauge(SfRadialGauge oldGauge, SfRadialGauge newGauge, - _RenderingDetails _renderingDetails) { - final List oldAxisRenderers = + RenderingDetails _renderingDetails) { + final List oldAxisRenderers = _renderingDetails.axisRenderers; - final Map> oldRangeRenderers = + final Map> oldRangeRenderers = _renderingDetails.gaugeRangeRenderers; - final Map> oldPointerRenderers = + final Map> oldPointerRenderers = _renderingDetails.gaugePointerRenderers; - _renderingDetails.axisRenderers = []; - _renderingDetails.gaugeRangeRenderers = >{}; + _renderingDetails.axisRenderers = []; + _renderingDetails.gaugeRangeRenderers = >{}; _renderingDetails.gaugePointerRenderers = - >{}; - if (newGauge.axes != null) { - for (int i = 0; i < newGauge.axes.length; i++) { - final RadialAxis newAxis = newGauge.axes[i]; - int index; - RadialAxisRenderer axisRenderer; - if (oldGauge.axes != null) { - index = i < oldGauge.axes.length && newAxis == oldGauge.axes[i] - ? i - : _getExistingAxisIndex(newAxis, oldGauge); - } + >{}; + for (int i = 0; i < newGauge.axes.length; i++) { + final RadialAxis newAxis = newGauge.axes[i]; + int? index; + RadialAxisRendererBase axisRenderer; + index = i < oldGauge.axes.length && newAxis == oldGauge.axes[i] + ? i + : _getExistingAxisIndex(newAxis, oldGauge); - if (index != null && - index < oldGauge.axes.length && - oldAxisRenderers[index] != null) { - axisRenderer = oldAxisRenderers[index]; - axisRenderer.axis = newAxis; - _needsRepaintAxis(oldGauge.axes[i], newGauge.axes[i], axisRenderer, - index, oldRangeRenderers, oldPointerRenderers); - } else if (oldGauge.axes.length == newGauge.axes.length) { - axisRenderer = oldAxisRenderers[i]; - axisRenderer.axis = newAxis; - final RadialAxis oldAxis = oldGauge.axes[i]; - _needsRepaintExistingAxis(oldAxis, newAxis, axisRenderer, i, - _renderingDetails, oldPointerRenderers, oldRangeRenderers); - } else { - axisRenderer = _createAxisRenderer(newAxis); - axisRenderer._needsRepaintAxis = true; + if (index != null && + index < oldGauge.axes.length && + index < oldAxisRenderers.length) { + axisRenderer = oldAxisRenderers[index]; + axisRenderer.axis = newAxis; + if (axisRenderer.renderer != null) { + axisRenderer.renderer!.axis = newAxis; + } + _needsRepaintAxis(oldGauge.axes[i], newGauge.axes[i], axisRenderer, + index, oldRangeRenderers, oldPointerRenderers); + } else if (oldGauge.axes.length == newGauge.axes.length && + oldAxisRenderers.length == newGauge.axes.length) { + axisRenderer = oldAxisRenderers[i]; + axisRenderer.axis = newAxis; + if (axisRenderer.renderer != null) { + axisRenderer.renderer!.axis = newAxis; + } + final RadialAxis oldAxis = oldGauge.axes[i]; + _needsRepaintExistingAxis(oldAxis, newAxis, axisRenderer, i, + _renderingDetails, oldPointerRenderers, oldRangeRenderers); + } else { + axisRenderer = _createAxisRenderer(newAxis); + axisRenderer.needsRepaintAxis = true; - if (newAxis.ranges != null && newAxis.ranges.isNotEmpty) { - _createRangesRenderer(i, axisRenderer); - } + if (newAxis.ranges != null && newAxis.ranges!.isNotEmpty) { + _createRangesRenderer(i, axisRenderer); + } - if (newAxis.pointers != null && newAxis.pointers.isNotEmpty) { - _createPointersRenderers(i, axisRenderer); - } + if (newAxis.pointers != null && newAxis.pointers!.isNotEmpty) { + _createPointersRenderers(i, axisRenderer); } - axisRenderer._axis = newAxis; - _renderingDetails.axisRenderers.add(axisRenderer); } + axisRenderer.axis = newAxis; + _renderingDetails.axisRenderers.add(axisRenderer); } } @@ -311,88 +341,95 @@ class SfRadialGaugeState extends State void _needsRepaintExistingAxis( RadialAxis oldAxis, RadialAxis newAxis, - RadialAxisRenderer axisRenderer, + RadialAxisRendererBase axisRenderer, int axisIndex, - _RenderingDetails _renderingDetails, - Map> oldPointerRenderers, - Map> oldRangeRenderers) { + RenderingDetails _renderingDetails, + Map> oldPointerRenderers, + Map> oldRangeRenderers) { if (newAxis != oldAxis) { - axisRenderer._needsRepaintAxis = true; + axisRenderer.needsRepaintAxis = true; } else { - axisRenderer._needsRepaintAxis = false; + axisRenderer.needsRepaintAxis = false; } if (oldAxis.pointers != null && newAxis.pointers != null && - oldAxis.pointers.isNotEmpty && - newAxis.pointers.isNotEmpty && - oldAxis.pointers.length == newAxis.pointers.length) { - final List<_GaugePointerRenderer> pointerRenderers = - oldPointerRenderers[axisIndex]; - final List<_GaugePointerRenderer> newPointerRenderers = - <_GaugePointerRenderer>[]; - for (int j = 0; j < newAxis.pointers.length; j++) { + oldAxis.pointers!.isNotEmpty && + newAxis.pointers!.isNotEmpty && + oldAxis.pointers!.length == newAxis.pointers!.length) { + final List pointerRenderers = + oldPointerRenderers[axisIndex]!; + final List newPointerRenderers = + []; + for (int j = 0; j < newAxis.pointers!.length; j++) { _needsAnimatePointers( - oldAxis.pointers[j], newAxis.pointers[j], pointerRenderers[j]); + oldAxis.pointers![j], newAxis.pointers![j], pointerRenderers[j]); _needsResetPointerValue( - oldAxis.pointers[j], newAxis.pointers[j], pointerRenderers[j]); - if (newAxis.pointers[j] != oldAxis.pointers[j]) { - pointerRenderers[j]._needsRepaintPointer = true; + oldAxis.pointers![j], newAxis.pointers![j], pointerRenderers[j]); + if (newAxis.pointers![j] != oldAxis.pointers![j]) { + pointerRenderers[j].needsRepaintPointer = true; } else { - if (axisRenderer._needsRepaintAxis) { - pointerRenderers[j]._needsRepaintPointer = true; + if (axisRenderer.needsRepaintAxis) { + pointerRenderers[j].needsRepaintPointer = true; } else { - pointerRenderers[j]._needsRepaintPointer = false; + pointerRenderers[j].needsRepaintPointer = false; } } - if (pointerRenderers[j] is MarkerPointerRenderer) { - final MarkerPointerRenderer markerRenderer = pointerRenderers[j]; - final MarkerPointer marker = newAxis.pointers[j]; + if (pointerRenderers[j] is MarkerPointerRendererBase) { + final MarkerPointerRendererBase markerRenderer = + pointerRenderers[j] as MarkerPointerRendererBase; + final MarkerPointer marker = newAxis.pointers![j] as MarkerPointer; markerRenderer.pointer = marker; - } else if (pointerRenderers[j] is NeedlePointerRenderer) { - final NeedlePointerRenderer needleRenderer = pointerRenderers[j]; - final NeedlePointer needle = newAxis.pointers[j]; + if (markerRenderer.renderer != null) { + markerRenderer.renderer!.pointer = marker; + } + } else if (pointerRenderers[j] is NeedlePointerRendererBase) { + final NeedlePointerRendererBase needleRenderer = + pointerRenderers[j] as NeedlePointerRendererBase; + final NeedlePointer needle = newAxis.pointers![j] as NeedlePointer; needleRenderer.pointer = needle; + if (needleRenderer.renderer != null) { + needleRenderer.renderer!.pointer = needle; + } } - pointerRenderers[j]._gaugePointer = newAxis.pointers[j]; + pointerRenderers[j].gaugePointer = newAxis.pointers![j]; newPointerRenderers.add(pointerRenderers[j]); } _renderingDetails.gaugePointerRenderers[axisIndex] = newPointerRenderers; } else { - if (newAxis.pointers != null && newAxis.pointers.isNotEmpty) { + if (newAxis.pointers != null && newAxis.pointers!.isNotEmpty) { _needsRepaintPointers( oldAxis, newAxis, axisRenderer, axisIndex, oldPointerRenderers); } } - if (oldAxis.ranges != null && newAxis.ranges != null && - oldAxis.ranges.isNotEmpty && - newAxis.ranges.isNotEmpty && - oldAxis.ranges.length == newAxis.ranges.length) { - final List<_GaugeRangeRenderer> rangeRenderers = - oldRangeRenderers[axisIndex]; - final List<_GaugeRangeRenderer> newRangeRenderers = - <_GaugeRangeRenderer>[]; - for (int j = 0; j < newAxis.ranges.length; j++) { - if (newAxis.ranges[j] != oldAxis.ranges[j]) { - rangeRenderers[j]._needsRepaintRange = true; + oldAxis.ranges!.isNotEmpty && + newAxis.ranges!.isNotEmpty && + oldAxis.ranges!.length == newAxis.ranges!.length && + oldRangeRenderers[axisIndex] != null) { + final List rangeRenderers = + oldRangeRenderers[axisIndex]!; + final List newRangeRenderers = []; + for (int j = 0; j < newAxis.ranges!.length; j++) { + if (newAxis.ranges![j] != oldAxis.ranges![j]) { + rangeRenderers[j].needsRepaintRange = true; } else { - if (axisRenderer._needsRepaintAxis) { - rangeRenderers[j]._needsRepaintRange = true; + if (axisRenderer.needsRepaintAxis) { + rangeRenderers[j].needsRepaintRange = true; } else { - rangeRenderers[j]._needsRepaintRange = false; + rangeRenderers[j].needsRepaintRange = false; } } - rangeRenderers[j]._range = newAxis.ranges[j]; + rangeRenderers[j].range = newAxis.ranges![j]; newRangeRenderers.add(rangeRenderers[j]); } _renderingDetails.gaugeRangeRenderers[axisIndex] = newRangeRenderers; } else { - if (newAxis.ranges != null && newAxis.ranges.isNotEmpty) { + if (newAxis.ranges != null && newAxis.ranges!.isNotEmpty) { _needsRepaintRanges( oldAxis, newAxis, axisRenderer, axisIndex, oldRangeRenderers); } @@ -400,7 +437,7 @@ class SfRadialGaugeState extends State } /// Check current axis index is exist in old gauge - int _getExistingAxisIndex(RadialAxis currentAxis, SfRadialGauge oldGauge) { + int? _getExistingAxisIndex(RadialAxis currentAxis, SfRadialGauge oldGauge) { for (int index = 0; index < oldGauge.axes.length; index++) { final RadialAxis axis = oldGauge.axes[index]; if (axis == currentAxis) { @@ -411,9 +448,9 @@ class SfRadialGaugeState extends State } /// Check current range index is exist in old axis - int _getExistingRangeIndex(GaugeRange currentRange, RadialAxis oldAxis) { - for (int index = 0; index < oldAxis.ranges.length; index++) { - final GaugeRange range = oldAxis.ranges[index]; + int? _getExistingRangeIndex(GaugeRange currentRange, RadialAxis oldAxis) { + for (int index = 0; index < oldAxis.ranges!.length; index++) { + final GaugeRange range = oldAxis.ranges![index]; if (range == currentRange) { return index; } @@ -422,10 +459,10 @@ class SfRadialGaugeState extends State } /// Check current range index is exist in old axis - int _getExistingPointerIndex( + int? _getExistingPointerIndex( GaugePointer currentPointer, RadialAxis oldAxis) { - for (int index = 0; index < oldAxis.pointers.length; index++) { - final GaugePointer pointer = oldAxis.pointers[index]; + for (int index = 0; index < oldAxis.pointers!.length; index++) { + final GaugePointer pointer = oldAxis.pointers![index]; if (pointer == currentPointer) { return index; } @@ -437,16 +474,16 @@ class SfRadialGaugeState extends State void _needsRepaintAxis( RadialAxis oldAxis, RadialAxis newAxis, - RadialAxisRenderer axisRenderer, + RadialAxisRendererBase axisRenderer, int oldAxisIndex, - Map> oldRangeRenderers, - Map> oldPointerRenderers, + Map> oldRangeRenderers, + Map> oldPointerRenderers, ) { if (oldAxis.backgroundImage == newAxis.backgroundImage && - axisRenderer._backgroundImageInfo?.image != null) { - axisRenderer._backgroundImageInfo = axisRenderer._backgroundImageInfo; + axisRenderer.backgroundImageInfo?.image != null) { + axisRenderer.backgroundImageInfo = axisRenderer.backgroundImageInfo; } - axisRenderer._needsRepaintAxis = false; + axisRenderer.needsRepaintAxis = false; if (newAxis.pointers != null) { _needsRepaintPointers( oldAxis, newAxis, axisRenderer, oldAxisIndex, oldPointerRenderers); @@ -461,37 +498,38 @@ class SfRadialGaugeState extends State void _needsRepaintRanges( RadialAxis oldAxis, RadialAxis newAxis, - RadialAxisRenderer axisRenderer, + RadialAxisRendererBase axisRenderer, int oldAxisIndex, - Map> oldRangeRenderers) { - final List<_GaugeRangeRenderer> newRangeRenderers = <_GaugeRangeRenderer>[]; - final List<_GaugeRangeRenderer> renderers = oldRangeRenderers[oldAxisIndex]; - for (int i = 0; i < newAxis.ranges.length; i++) { - final GaugeRange newRange = newAxis.ranges[i]; - int index; - _GaugeRangeRenderer rangeRenderer; + Map> oldRangeRenderers) { + final List newRangeRenderers = []; + final List? renderers = oldRangeRenderers[oldAxisIndex]; + for (int i = 0; i < newAxis.ranges!.length; i++) { + final GaugeRange newRange = newAxis.ranges![i]; + int? index; + GaugeRangeRenderer rangeRenderer; if (oldAxis.ranges != null) { - index = i < oldAxis.ranges.length && newRange == oldAxis.ranges[i] + index = i < oldAxis.ranges!.length && newRange == oldAxis.ranges![i] ? i : _getExistingRangeIndex(newRange, oldAxis); } if (index != null && - index < oldAxis.ranges.length && - renderers[index] != null) { + index < oldAxis.ranges!.length && + renderers != null && + index < renderers.length) { rangeRenderer = renderers[index]; - if (axisRenderer._needsRepaintAxis) { - rangeRenderer._needsRepaintRange = true; + if (axisRenderer.needsRepaintAxis) { + rangeRenderer.needsRepaintRange = true; } else { - rangeRenderer._needsRepaintRange = false; + rangeRenderer.needsRepaintRange = false; } } else { rangeRenderer = _createRangeRenderer(newRange); - rangeRenderer._needsRepaintRange = true; + rangeRenderer.needsRepaintRange = true; } - rangeRenderer._range = newRange; - rangeRenderer._axisRenderer = axisRenderer; + rangeRenderer.range = newRange; + rangeRenderer.axisRenderer = axisRenderer; newRangeRenderers.add(rangeRenderer); } @@ -500,31 +538,30 @@ class SfRadialGaugeState extends State /// Checks whether to animate the pointers void _needsAnimatePointers(GaugePointer oldPointer, GaugePointer newPointer, - _GaugePointerRenderer pointerRenderer) { + GaugePointerRenderer pointerRenderer) { if (oldPointer.value != newPointer.value) { setState(() { // Sets the previous animation end value as current animation start // value of pointer - pointerRenderer._needsAnimate = true; - pointerRenderer._animationStartValue = - pointerRenderer._animationEndValue; + pointerRenderer.needsAnimate = true; + pointerRenderer.animationStartValue = pointerRenderer.animationEndValue; }); } else if (oldPointer.animationType != newPointer.animationType) { - pointerRenderer._needsAnimate = true; + pointerRenderer.needsAnimate = true; } else { setState(() { - pointerRenderer._needsAnimate = false; + pointerRenderer.needsAnimate = false; }); } } /// Check to reset the pointer current value void _needsResetPointerValue(GaugePointer oldPointer, GaugePointer newPointer, - _GaugePointerRenderer pointerRenderer) { + GaugePointerRenderer pointerRenderer) { if (oldPointer.enableDragging == newPointer.enableDragging) { if (!(oldPointer.value == newPointer.value)) { - pointerRenderer._currentValue = newPointer.value; - pointerRenderer._isDragStarted = false; + pointerRenderer.currentValue = newPointer.value; + pointerRenderer.isDragStarted = false; } } } @@ -537,85 +574,95 @@ class SfRadialGaugeState extends State void _needsRepaintPointers( RadialAxis oldAxis, RadialAxis newAxis, - RadialAxisRenderer axisRenderer, + RadialAxisRendererBase axisRenderer, int oldAxisIndex, - Map> oldPointerRenderers) { - final List<_GaugePointerRenderer> newPointerRenderers = - <_GaugePointerRenderer>[]; - final List<_GaugePointerRenderer> renderers = + Map> oldPointerRenderers) { + final List newPointerRenderers = + []; + final List? renderers = oldPointerRenderers[oldAxisIndex]; - for (int i = 0; i < newAxis.pointers.length; i++) { - final GaugePointer newPointer = newAxis.pointers[i]; - int index; - _GaugePointerRenderer pointerRenderer; + for (int i = 0; i < newAxis.pointers!.length; i++) { + final GaugePointer newPointer = newAxis.pointers![i]; + int? index; + GaugePointerRenderer pointerRenderer; if (oldAxis.pointers != null) { - index = i < oldAxis.pointers.length && - (newPointer == oldAxis.pointers[i] || - newPointer.value != oldAxis.pointers[i].value || + index = i < oldAxis.pointers!.length && + (newPointer == oldAxis.pointers![i] || + newPointer.value != oldAxis.pointers![i].value || newPointer.animationType != - oldAxis.pointers[i].animationType) + oldAxis.pointers![i].animationType) ? i : _getExistingPointerIndex(newPointer, oldAxis); } if (index != null && - index < oldAxis.pointers.length && - renderers[index] != null) { + index < oldAxis.pointers!.length && + renderers != null && + index < renderers.length) { pointerRenderer = renderers[index]; - if (axisRenderer._needsRepaintAxis) { - pointerRenderer._needsRepaintPointer = true; + if (axisRenderer.needsRepaintAxis) { + pointerRenderer.needsRepaintPointer = true; } else { - pointerRenderer._needsRepaintPointer = false; + pointerRenderer.needsRepaintPointer = false; } - if (oldAxis.pointers[index].value != newAxis.pointers[i].value) { + if (oldAxis.pointers![index].value != newAxis.pointers![i].value) { // Sets the previous animation end value as current animation start // value of pointer - pointerRenderer._needsAnimate = true; - pointerRenderer._needsRepaintPointer = true; - pointerRenderer._currentValue = newPointer.value; - pointerRenderer._animationStartValue = - pointerRenderer._animationEndValue; - } else if (oldAxis.pointers[index].animationType != - newAxis.pointers[i].animationType) { - pointerRenderer._needsAnimate = true; - pointerRenderer._needsRepaintPointer = true; + pointerRenderer.needsAnimate = true; + pointerRenderer.needsRepaintPointer = true; + pointerRenderer.currentValue = newPointer.value; + pointerRenderer.animationStartValue = + pointerRenderer.animationEndValue; + } else if (oldAxis.pointers![index].animationType != + newAxis.pointers![i].animationType) { + pointerRenderer.needsAnimate = true; + pointerRenderer.needsRepaintPointer = true; } else { - pointerRenderer._needsAnimate = false; + pointerRenderer.needsAnimate = false; } - if (oldAxis.pointers[index].enableDragging == - newAxis.pointers[i].enableDragging) { - if (oldAxis.pointers[i].value == newAxis.pointers[i].value && - pointerRenderer._currentValue != renderers[index]._currentValue) { - pointerRenderer._currentValue = renderers[index]._currentValue; + if (oldAxis.pointers![index].enableDragging == + newAxis.pointers![i].enableDragging) { + if (oldAxis.pointers![i].value == newAxis.pointers![i].value && + pointerRenderer.currentValue != renderers[index].currentValue) { + pointerRenderer.currentValue = renderers[index].currentValue; } - pointerRenderer._isDragStarted = renderers[index]._isDragStarted; + pointerRenderer.isDragStarted = renderers[index].isDragStarted; } } else { pointerRenderer = _createPointerRenderer(newPointer); - pointerRenderer._needsRepaintPointer = true; - pointerRenderer._needsAnimate = false; - if (oldAxis.pointers[i] != null && - oldAxis.pointers[i].enableDragging == newPointer.enableDragging && + pointerRenderer.needsRepaintPointer = true; + pointerRenderer.needsAnimate = false; + if (oldAxis.pointers != null && + i < oldAxis.pointers!.length && + oldAxis.pointers![i].enableDragging == newPointer.enableDragging && oldPointerRenderers[i] != null && - oldAxis.pointers[i].value == newAxis.pointers[i].value) { - pointerRenderer._currentValue = renderers[i]._currentValue; - pointerRenderer._isDragStarted = renderers[i]._isDragStarted; + oldAxis.pointers![i].value == newAxis.pointers![i].value) { + if (renderers != null && i < renderers.length) { + pointerRenderer.currentValue = renderers[i].currentValue; + pointerRenderer.isDragStarted = renderers[i].isDragStarted; + } } } - if (pointerRenderer is MarkerPointerRenderer) { - final MarkerPointerRenderer markerRenderer = pointerRenderer; - final MarkerPointer marker = newPointer; + if (pointerRenderer is MarkerPointerRendererBase) { + final MarkerPointerRendererBase markerRenderer = pointerRenderer; + final MarkerPointer marker = newPointer as MarkerPointer; markerRenderer.pointer = marker; - } else if (pointerRenderer is NeedlePointerRenderer) { - final NeedlePointerRenderer needleRenderer = pointerRenderer; - final NeedlePointer needle = newPointer; + if (markerRenderer.renderer != null) { + markerRenderer.renderer!.pointer = marker; + } + } else if (pointerRenderer is NeedlePointerRendererBase) { + final NeedlePointerRendererBase needleRenderer = pointerRenderer; + final NeedlePointer needle = newPointer as NeedlePointer; needleRenderer.pointer = needle; + if (needleRenderer.renderer != null) { + needleRenderer.renderer!.pointer = needle; + } } - pointerRenderer._gaugePointer = newPointer; - pointerRenderer._axisRenderer = axisRenderer; + pointerRenderer.gaugePointer = newPointer; + pointerRenderer.axisRenderer = axisRenderer; newPointerRenderers.add(pointerRenderer); } @@ -623,106 +670,117 @@ class SfRadialGaugeState extends State } /// Returns the gauge pointer renderer - _GaugePointerRenderer _createPointerRenderer(GaugePointer pointer) { + GaugePointerRenderer _createPointerRenderer(GaugePointer pointer) { if (pointer is MarkerPointer) { return _createMarkerPointerRenderer(pointer); } else if (pointer is NeedlePointer) { return _createNeedlePointerRenderer(pointer); - } else { + } else if (pointer is RangePointer) { return _createRangePointerRenderer(pointer); + } else { + final WidgetPointerRenderer renderer = WidgetPointerRenderer(); + final WidgetPointer widgetPointer = pointer as WidgetPointer; + renderer.gaugePointer = widgetPointer; + renderer.currentValue = widgetPointer.value; + return renderer; } } /// Create the needle pointer renderer. - NeedlePointerRenderer _createNeedlePointerRenderer(GaugePointer pointer) { - NeedlePointerRenderer pointerRenderer; - final NeedlePointer needle = pointer; + NeedlePointerRendererBase _createNeedlePointerRenderer(GaugePointer pointer) { + final NeedlePointerRendererBase pointerRendererBase = + NeedlePointerRendererBase(); + NeedlePointerRenderer? pointerRenderer; + final NeedlePointer needle = pointer as NeedlePointer; if (needle.onCreatePointerRenderer != null) { - pointerRenderer = needle.onCreatePointerRenderer(); + pointerRenderer = needle.onCreatePointerRenderer!(); pointerRenderer.pointer = needle; assert( - pointerRenderer != null, + pointerRenderer is NeedlePointerRenderer, 'This onCreateRenderer callback function should return value as' 'extends from GaugePointerRenderer class and should not be return' 'value as null'); - } else { - pointerRenderer = NeedlePointerRenderer(); } - pointerRenderer._gaugePointer = needle; - pointerRenderer._currentValue = needle.value; - return pointerRenderer; + + pointerRendererBase.renderer = pointerRenderer; + pointerRendererBase.gaugePointer = needle; + pointerRendererBase.currentValue = needle.value; + return pointerRendererBase; } /// Create the marker pointer renderer. - MarkerPointerRenderer _createMarkerPointerRenderer(GaugePointer pointer) { - final MarkerPointer marker = pointer; - MarkerPointerRenderer pointerRenderer; + MarkerPointerRendererBase _createMarkerPointerRenderer(GaugePointer pointer) { + final MarkerPointer marker = pointer as MarkerPointer; + final MarkerPointerRendererBase pointerRendererBase = + MarkerPointerRendererBase(); + MarkerPointerRenderer? pointerRenderer; if (marker.onCreatePointerRenderer != null) { - pointerRenderer = marker.onCreatePointerRenderer(); + pointerRenderer = marker.onCreatePointerRenderer!(); pointerRenderer.pointer = marker; assert( - pointerRenderer != null, + pointerRenderer is MarkerPointerRenderer, 'This onCreatePointerRenderer callback function should return' 'value as extends from GaugePointerRenderer class and should not' 'be return value as null'); - } else { - pointerRenderer = MarkerPointerRenderer(); } - pointerRenderer._gaugePointer = marker; - pointerRenderer._currentValue = marker.value; - return pointerRenderer; + + pointerRendererBase.renderer = pointerRenderer; + pointerRendererBase.gaugePointer = marker; + pointerRendererBase.currentValue = marker.value; + return pointerRendererBase; } /// Create the range pointer renderer. - _RangePointerRenderer _createRangePointerRenderer(GaugePointer pointer) { - final _RangePointerRenderer pointerRenderer = _RangePointerRenderer(); - pointerRenderer._gaugePointer = pointer; - pointerRenderer._currentValue = pointer.value; + RangePointerRenderer _createRangePointerRenderer(GaugePointer pointer) { + final RangePointerRenderer pointerRenderer = RangePointerRenderer(); + pointerRenderer.gaugePointer = pointer; + pointerRenderer.currentValue = pointer.value; return pointerRenderer; } /// Create the radial axis renderer. - GaugeAxisRenderer _createAxisRenderer(RadialAxis axis) { - RadialAxisRenderer axisRenderer; + RadialAxisRendererBase _createAxisRenderer(RadialAxis axis) { + final RadialAxisRendererBase axisRendererBase = RadialAxisRendererBase(); + RadialAxisRenderer? axisRenderer; if (axis.onCreateAxisRenderer != null) { - axisRenderer = axis.onCreateAxisRenderer(); - axisRenderer.axis = axis; + axisRenderer = axis.onCreateAxisRenderer!(); + axisRenderer!.axis = axis; assert( - axisRenderer != null, + axisRenderer is RadialAxisRenderer, 'This onCreateAxisRenderer callback function should return value as' 'extends from RadialAxisRenderer class and should not be return value' 'as null'); - } else { - axisRenderer = RadialAxisRenderer(); } - axisRenderer._axis = axis; - return axisRenderer; + + axisRendererBase.axis = axis; + axisRendererBase.renderer = axisRenderer; + return axisRendererBase; } /// Methods to add the title of circular gauge Widget _addGaugeTitle() { - if (widget.title != null && widget.title.text.isNotEmpty) { + if (widget.title != null && widget.title!.text.isNotEmpty) { final Widget titleWidget = Container( child: Container( padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), decoration: BoxDecoration( - color: widget.title.backgroundColor ?? + color: widget.title!.backgroundColor ?? _gaugeTheme.titleBackgroundColor, border: Border.all( - color: widget.title.borderColor ?? + color: widget.title!.borderColor ?? _gaugeTheme.titleBorderColor, - width: widget.title.borderWidth)), + width: widget.title!.borderWidth)), child: Text( - widget.title.text, - style: widget.title.textStyle, + widget.title!.text, + style: widget.title!.textStyle, textAlign: TextAlign.center, overflow: TextOverflow.clip, ), - alignment: (widget.title.alignment == GaugeAlignment.near) + alignment: (widget.title!.alignment == GaugeAlignment.near) ? Alignment.topLeft - : (widget.title.alignment == GaugeAlignment.far) + : (widget.title!.alignment == GaugeAlignment.far) ? Alignment.topRight - : (widget.title.alignment == GaugeAlignment.center) + : (widget.title!.alignment == GaugeAlignment.center) ? Alignment.topCenter : Alignment.topCenter)); @@ -733,15 +791,15 @@ class SfRadialGaugeState extends State } /// Returns the renderer for gauge range - _GaugeRangeRenderer _createRangeRenderer(GaugeRange range) { - return _GaugeRangeRenderer(range); + GaugeRangeRenderer _createRangeRenderer(GaugeRange range) { + return GaugeRangeRenderer(range); } /// Method to add the elements of gauge Widget _addGaugeElements() { return Expanded(child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - return _AxisContainer(widget, _gaugeTheme, _renderingDetails); + return AxisContainer(widget, _gaugeTheme, _renderingDetails); }, )); } @@ -763,19 +821,17 @@ class SfRadialGaugeState extends State @override void dispose() { if (_renderingDetails.animationController != null) { - _renderingDetails.animationController + _renderingDetails.animationController! .removeListener(_repaintGaugeElements); - _renderingDetails.animationController.dispose(); + _renderingDetails.animationController!.dispose(); } - if (widget.axes != null && - widget.axes.isNotEmpty && - _renderingDetails.axisRenderers != null) { + if (widget.axes.isNotEmpty && _renderingDetails.axisRenderers.isNotEmpty) { for (int i = 0; i < widget.axes.length; i++) { - final RadialAxisRenderer axisRenderer = + final RadialAxisRendererBase axisRenderer = _renderingDetails.axisRenderers[i]; - if (axisRenderer._imageStream != null) { - axisRenderer._imageStream.removeListener(axisRenderer._listener); + if (axisRenderer.imageStream != null && axisRenderer.listener != null) { + axisRenderer.imageStream!.removeListener(axisRenderer.listener!); } } } @@ -834,42 +890,9 @@ class SfRadialGaugeState extends State /// ``` Future toImage({double pixelRatio = 1.0}) async { - final RenderRepaintBoundary boundary = context.findRenderObject(); + final RenderRepaintBoundary boundary = + context.findRenderObject() as RenderRepaintBoundary; final dart_ui.Image image = await boundary.toImage(pixelRatio: pixelRatio); return image; } } - -/// Holds the animation and repainter details. -class _RenderingDetails { - /// Holds the pointer repaint notifier - ValueNotifier pointerRepaintNotifier; - - /// Holds the range repaint notifier - ValueNotifier rangeRepaintNotifier; - - /// Holds the axis repaint notifier - ValueNotifier axisRepaintNotifier; - - /// Holds the animation controller - AnimationController animationController; - - /// Specifies whether to animate axes - bool needsToAnimateAxes; - - /// Specifies whether to animate ranges - bool needsToAnimateRanges; - - /// Specifies whether to animate pointers - bool needsToAnimatePointers; - - /// Specifies whether to animate annotation - bool needsToAnimateAnnotation; - - /// Specifies axis renderer corresponding to the axis - List axisRenderers; - - Map> gaugePointerRenderers; - - Map> gaugeRangeRenderers; -} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/marker_pointer_painter.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/marker_pointer_painter.dart index cab2998bf..1f779b9b3 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/marker_pointer_painter.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/marker_pointer_painter.dart @@ -1,8 +1,22 @@ -part of gauges; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:flutter/foundation.dart'; +import '../axis/radial_axis.dart'; +import '../common/common.dart'; +import '../common/radial_gauge_renderer.dart'; +import '../gauge/radial_gauge.dart'; +import '../pointers/marker_pointer.dart'; +import '../renderers/marker_pointer_renderer_base.dart'; +import '../renderers/radial_axis_renderer_base.dart'; +import '../utils/helper.dart'; /// Represents the painter to draw marker -class _MarkerPointerPainter extends CustomPainter { - _MarkerPointerPainter( +class MarkerPointerPainter extends CustomPainter { + /// Create the painter for marker pointer + /// + MarkerPointerPainter( this._gauge, this._axis, this._markerPointer, @@ -28,44 +42,54 @@ class _MarkerPointerPainter extends CustomPainter { final MarkerPointer _markerPointer; /// Specifies the pointer animation - final Animation _pointerAnimation; + final Animation? _pointerAnimation; /// Specifies the gauge theme data. final SfGaugeThemeData _gaugeThemeData; /// Hold the radial gauge rendering details - final _RenderingDetails _renderingDetails; + final RenderingDetails _renderingDetails; /// Holds the axis renderer - final RadialAxisRenderer _axisRenderer; + final RadialAxisRendererBase _axisRenderer; /// Holds the marker pointer renderer - final MarkerPointerRenderer _markerPointerRenderer; + final MarkerPointerRendererBase _markerPointerRenderer; @override void paint(Canvas canvas, Size size) { final bool needsPointerAnimation = - _markerPointerRenderer._getIsPointerAnimationEnabled(); + _markerPointerRenderer.getIsPointerAnimationEnabled(); double markerAngle = 0; - Offset offset; - bool needsShowPointer; + Offset? offset; + bool? needsShowPointer; if (_pointerAnimation != null) { needsShowPointer = _axis.isInversed - ? _pointerAnimation.value < 1 - : _pointerAnimation.value > 0; + ? _pointerAnimation!.value < 1 + : _pointerAnimation!.value > 0; } - if (_renderingDetails.needsToAnimatePointers || needsPointerAnimation) { - if ((_renderingDetails.needsToAnimatePointers && needsShowPointer) || + if ((_renderingDetails.needsToAnimatePointers && + (!_renderingDetails.isOpacityAnimation && + _axis.minimum != _markerPointerRenderer.currentValue)) || + (needsPointerAnimation && + (!_gauge.enableLoadingAnimation || + (_gauge.enableLoadingAnimation && + _pointerAnimation!.value != 1 && + !_renderingDetails.isOpacityAnimation && + !_renderingDetails.needsToAnimatePointers)))) { + if ((_renderingDetails.needsToAnimatePointers && + needsShowPointer != null && + needsShowPointer) || !_renderingDetails.needsToAnimatePointers) { - markerAngle = (_axisRenderer._sweepAngle * _pointerAnimation.value) + + markerAngle = (_axisRenderer.sweepAngle * _pointerAnimation!.value) + _axis.startAngle; - offset = _markerPointerRenderer._getMarkerOffset( - _getDegreeToRadian(markerAngle), _markerPointer); + offset = _markerPointerRenderer.getMarkerOffset( + getDegreeToRadian(markerAngle), _markerPointer); } } else { - offset = _markerPointerRenderer._offset; - markerAngle = _markerPointerRenderer._angle; + offset = _markerPointerRenderer.offset; + markerAngle = _markerPointerRenderer.angle; } if (offset != null) { final PointerPaintingDetails pointerPaintingDetails = @@ -73,33 +97,45 @@ class _MarkerPointerPainter extends CustomPainter { startOffset: offset, endOffset: offset, pointerAngle: markerAngle, - axisRadius: _axisRenderer._radius, - axisCenter: _axisRenderer._axisCenter); - _markerPointerRenderer.drawPointer( - canvas, pointerPaintingDetails, _gaugeThemeData); + axisRadius: _axisRenderer.radius, + axisCenter: _axisRenderer.axisCenter); + _markerPointerRenderer.renderingDetails = _renderingDetails; + _markerPointerRenderer.pointerAnimation = + _pointerAnimation != null ? _pointerAnimation! : null; + + if (_markerPointerRenderer.renderer != null) { + _markerPointerRenderer.renderer! + .drawPointer(canvas, pointerPaintingDetails, _gaugeThemeData); + } else { + _markerPointerRenderer.drawPointer( + canvas, pointerPaintingDetails, _gaugeThemeData); + } } // Disables the animation once the animation reached the current // pointer angle if (needsPointerAnimation && markerAngle == - _axisRenderer._sweepAngle * - _markerPointerRenderer._animationEndValue + + _axisRenderer.sweepAngle * + _markerPointerRenderer.animationEndValue! + _axis.startAngle) { - _markerPointerRenderer._needsAnimate = false; + _markerPointerRenderer.needsAnimate = false; } if (_renderingDetails.needsToAnimatePointers && _gauge.axes[_gauge.axes.length - 1] == _axis && - _axis.pointers[_axis.pointers.length - 1] == _markerPointer && - markerAngle == - _axisRenderer._sweepAngle * - _markerPointerRenderer._animationEndValue + - _axis.startAngle) { + _axis.pointers![_axis.pointers!.length - 1] == _markerPointer && + ((!_renderingDetails.isOpacityAnimation && + markerAngle == + _axisRenderer.sweepAngle * + _markerPointerRenderer.animationEndValue! + + _axis.startAngle) || + (_renderingDetails.isOpacityAnimation && + _pointerAnimation!.value == 1))) { _renderingDetails.needsToAnimatePointers = false; } } @override - bool shouldRepaint(_MarkerPointerPainter oldDelegate) => _isRepaint; + bool shouldRepaint(MarkerPointerPainter oldDelegate) => _isRepaint; } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/needle_pointer_painter.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/needle_pointer_painter.dart index cda9c4d5f..dbd4c2e37 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/needle_pointer_painter.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/needle_pointer_painter.dart @@ -1,9 +1,20 @@ -part of gauges; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:flutter/foundation.dart'; +import '../axis/radial_axis.dart'; +import '../common/common.dart'; +import '../common/radial_gauge_renderer.dart'; +import '../gauge/radial_gauge.dart'; +import '../pointers/needle_pointer.dart'; +import '../renderers/needle_pointer_renderer_base.dart'; +import '../renderers/radial_axis_renderer_base.dart'; /// Represents the painter to render the needle pointer -class _NeedlePointerPainter extends CustomPainter { +class NeedlePointerPainter extends CustomPainter { /// Creates the needle pointer painter - _NeedlePointerPainter( + NeedlePointerPainter( this._gauge, this._axis, this._needlePointer, @@ -29,90 +40,111 @@ class _NeedlePointerPainter extends CustomPainter { final bool _isRepaint; /// Specifies the animation value of needle pointer - final Animation _pointerAnimation; + final Animation? _pointerAnimation; /// Specifies the gauge theme data. final SfGaugeThemeData _gaugeThemeData; /// Hold the radial gauge animation details - final _RenderingDetails _renderingDetails; + final RenderingDetails _renderingDetails; /// holds the axis renderer - final RadialAxisRenderer _axisRenderer; + final RadialAxisRendererBase _axisRenderer; /// Holds the needle pointer renderer - final NeedlePointerRenderer _needlePointerRenderer; + final NeedlePointerRendererBase _needlePointerRenderer; @override void paint(Canvas canvas, Size size) { - double angle; - bool needsShowPointer; + double? angle; + bool? needsShowPointer; final bool needsPointerAnimation = - _needlePointerRenderer._getIsPointerAnimationEnabled(); + _needlePointerRenderer.getIsPointerAnimationEnabled(); if (_pointerAnimation != null) { needsShowPointer = _axis.isInversed - ? _pointerAnimation.value < 1 - : _pointerAnimation.value > 0; + ? _pointerAnimation!.value < 1 + : _pointerAnimation!.value > 0; } - if (_renderingDetails.needsToAnimatePointers || needsPointerAnimation) { - if ((_renderingDetails.needsToAnimatePointers && needsShowPointer) || + if ((_renderingDetails.needsToAnimatePointers && + (!_renderingDetails.isOpacityAnimation && + _axis.minimum != _needlePointerRenderer.currentValue)) || + (needsPointerAnimation && + (!_gauge.enableLoadingAnimation || + (_gauge.enableLoadingAnimation && + _pointerAnimation!.value != 1 && + !_renderingDetails.isOpacityAnimation && + !_renderingDetails.needsToAnimatePointers)))) { + if ((_renderingDetails.needsToAnimatePointers && + needsShowPointer != null && + needsShowPointer) || !_renderingDetails.needsToAnimatePointers) { - angle = (_axisRenderer._sweepAngle * _pointerAnimation.value) + + angle = (_axisRenderer.sweepAngle * _pointerAnimation!.value) + _axis.startAngle + 90; //Since the needle rect has been // calculated with -90 degree, additional 90 degree is added } } else { - angle = _needlePointerRenderer._angle + 90; //Since the needle rect has + angle = _needlePointerRenderer.angle + 90; //Since the needle rect has //been calculated with -90 degree, additional 90 degree is added } final Offset startPosition = - Offset(_needlePointerRenderer._startX, _needlePointerRenderer._startY); + Offset(_needlePointerRenderer.startX, _needlePointerRenderer.startY); final Offset endPosition = - Offset(_needlePointerRenderer._stopX, _needlePointerRenderer._stopY); + Offset(_needlePointerRenderer.stopX, _needlePointerRenderer.stopY); if (angle != null) { final PointerPaintingDetails pointerPaintingDetails = PointerPaintingDetails( startOffset: startPosition, endOffset: endPosition, pointerAngle: angle, - axisRadius: _axisRenderer._radius, - axisCenter: _axisRenderer._axisCenter); - _needlePointerRenderer.drawPointer( - canvas, pointerPaintingDetails, _gaugeThemeData); + axisRadius: _axisRenderer.radius, + axisCenter: _axisRenderer.axisCenter); + _needlePointerRenderer.renderingDetails = _renderingDetails; + _needlePointerRenderer.pointerAnimation = + _pointerAnimation != null ? _pointerAnimation! : null; + + if (_needlePointerRenderer.renderer != null) { + _needlePointerRenderer.renderer! + .drawPointer(canvas, pointerPaintingDetails, _gaugeThemeData); + } else { + _needlePointerRenderer.drawPointer( + canvas, pointerPaintingDetails, _gaugeThemeData); + } } _setPointerAnimation(needsPointerAnimation, angle); } /// Method to set the pointer animation - void _setPointerAnimation(bool needsPointerAnimation, double angle) { + void _setPointerAnimation(bool needsPointerAnimation, double? angle) { final bool isPointerEndAngle = _getIsEndAngle(angle); // Disables the animation once the animation reached the current // pointer angle if (needsPointerAnimation && isPointerEndAngle) { - _needlePointerRenderer._needsAnimate = false; + _needlePointerRenderer.needsAnimate = false; } // Disables the load time pointer animation if (_renderingDetails.needsToAnimatePointers && _gauge.axes[_gauge.axes.length - 1] == _axis && - _axis.pointers[_axis.pointers.length - 1] == _needlePointer && - isPointerEndAngle) { + _axis.pointers![_axis.pointers!.length - 1] == _needlePointer && + ((!_renderingDetails.isOpacityAnimation && isPointerEndAngle) || + (_renderingDetails.isOpacityAnimation && + _pointerAnimation!.value == 1))) { _renderingDetails.needsToAnimatePointers = false; } } /// Checks whether the current angle is pointer end angle - bool _getIsEndAngle(double angle) { + bool _getIsEndAngle(double? angle) { return angle == - _axisRenderer._sweepAngle * _needlePointerRenderer._animationEndValue + + _axisRenderer.sweepAngle * _needlePointerRenderer.animationEndValue! + _axis.startAngle + 90; } @override - bool shouldRepaint(_NeedlePointerPainter oldDelegate) => _isRepaint; + bool shouldRepaint(NeedlePointerPainter oldDelegate) => _isRepaint; } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/radial_axis_painter.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/radial_axis_painter.dart index f89cd592b..96bd298ee 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/radial_axis_painter.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/radial_axis_painter.dart @@ -1,8 +1,22 @@ -part of gauges; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:flutter/foundation.dart'; +import '../axis/radial_axis.dart'; +import '../common/axis_label.dart'; +import '../common/radial_gauge_renderer.dart'; +import '../gauge/radial_gauge.dart'; +import '../renderers/radial_axis_renderer_base.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; /// Custom painter to paint gauge axis -class _AxisPainter extends CustomPainter { - _AxisPainter( +class AxisPainter extends CustomPainter { + /// Creates the painter for axis pointer + /// + AxisPainter( this._gauge, this._axis, this._isRepaint, @@ -24,59 +38,55 @@ class _AxisPainter extends CustomPainter { final RadialAxis _axis; /// Specifies the animation for axis line - final Animation _axisLineAnimation; + final Animation? _axisLineAnimation; /// Specifies the animation for axis elements - final Animation _axisElementsAnimation; + final Animation? _axisElementsAnimation; /// Specifies the gauge theme data. final SfGaugeThemeData _gaugeThemeData; /// Holds the radial gauge rendering details - final _RenderingDetails _renderingDetails; + final RenderingDetails _renderingDetails; /// Holds the radial axis renderer - final RadialAxisRenderer _axisRenderer; + final RadialAxisRendererBase _axisRenderer; @override void paint(Canvas canvas, Size size) { if (_axis.backgroundImage != null && - _axisRenderer._backgroundImageInfo?.image != null) { + _axisRenderer.backgroundImageInfo != null) { double radius; Rect rect; if (!_axis.canScaleToFit) { radius = math.min( - _axisRenderer._axisSize.width, _axisRenderer._axisSize.height) / + _axisRenderer.axisSize.width, _axisRenderer.axisSize.height) / 2; rect = Rect.fromLTRB( - _axisRenderer._axisSize.width / 2 - radius - _axisRenderer._centerX, - _axisRenderer._axisSize.height / 2 - - radius - - _axisRenderer._centerY, - _axisRenderer._axisSize.width / 2 + radius - _axisRenderer._centerX, - _axisRenderer._axisSize.height / 2 + - radius - - _axisRenderer._centerY); + _axisRenderer.axisSize.width / 2 - radius - _axisRenderer.centerX, + _axisRenderer.axisSize.height / 2 - radius - _axisRenderer.centerY, + _axisRenderer.axisSize.width / 2 + radius - _axisRenderer.centerX, + _axisRenderer.axisSize.height / 2 + radius - _axisRenderer.centerY); } else { - radius = _axisRenderer._radius; + radius = _axisRenderer.radius; rect = Rect.fromLTRB( - _axisRenderer._axisCenter.dx - radius, - _axisRenderer._axisCenter.dy - radius, - _axisRenderer._axisCenter.dx + radius, - _axisRenderer._axisCenter.dx + radius); + _axisRenderer.axisCenter.dx - radius, + _axisRenderer.axisCenter.dy - radius, + _axisRenderer.axisCenter.dx + radius, + _axisRenderer.axisCenter.dx + radius); } // Draws the background image of axis paintImage( canvas: canvas, rect: rect, - scale: _axisRenderer._backgroundImageInfo.scale ?? 1, - image: _axisRenderer._backgroundImageInfo.image, + scale: _axisRenderer.backgroundImageInfo!.scale, + image: _axisRenderer.backgroundImageInfo!.image, fit: BoxFit.fill, ); } - if (_axis.showAxisLine && _axisRenderer._actualAxisWidth > 0) { + if (_axis.showAxisLine && _axisRenderer.actualAxisWidth > 0) { _drawAxisLine(canvas); } @@ -99,14 +109,14 @@ class _AxisPainter extends CustomPainter { /// Checks whether the show axis line is enabled bool _getHasAxisLineAnimation() { - return (_axisLineAnimation != null && _axisLineAnimation.value == 1) || + return (_axisLineAnimation != null && _axisLineAnimation!.value == 1) || _axisLineAnimation == null; } /// Checks whether the labels and the ticks are enabled bool _getHasAxisElementsAnimation() { return (_axisElementsAnimation != null && - _axisElementsAnimation.value == 1) || + _axisElementsAnimation!.value == 1) || _axisElementsAnimation == null; } @@ -114,38 +124,37 @@ class _AxisPainter extends CustomPainter { void _drawAxisLine(Canvas canvas) { // whether the dash array is enabled for axis. final bool isDashedAxisLine = _getIsDashedLine(); - SweepGradient gradient; + SweepGradient? gradient; if (_axis.axisLineStyle.gradient != null && - _axis.axisLineStyle.gradient.colors != null && - _axis.axisLineStyle.gradient.colors.isNotEmpty) { + _axis.axisLineStyle.gradient!.colors.isNotEmpty) { gradient = SweepGradient( - stops: _calculateGradientStops(_getGradientOffset(), _axis.isInversed, - _axisRenderer._sweepAngle), + stops: calculateGradientStops( + _getGradientOffset(), _axis.isInversed, _axisRenderer.sweepAngle), colors: _axis.isInversed - ? _axis.axisLineStyle.gradient.colors.reversed.toList() - : _axis.axisLineStyle.gradient.colors); + ? _axis.axisLineStyle.gradient!.colors.reversed.toList() + : _axis.axisLineStyle.gradient!.colors); } if (_axis.axisLineStyle.cornerStyle == CornerStyle.bothFlat || isDashedAxisLine) { - _drawAxisPath(canvas, _axisRenderer._startRadian, - _axisRenderer._endRadian, gradient, isDashedAxisLine); + _drawAxisPath(canvas, _axisRenderer.startRadian, _axisRenderer.endRadian, + gradient, isDashedAxisLine); } else { - _drawAxisPath(canvas, _axisRenderer._startCornerRadian, - _axisRenderer._sweepCornerRadian, gradient, isDashedAxisLine); + _drawAxisPath(canvas, _axisRenderer.startCornerRadian, + _axisRenderer.sweepCornerRadian, gradient, isDashedAxisLine); } } /// Returns the gradient stop of axis line gradient - List _getGradientOffset() { - if (_axis.axisLineStyle.gradient.stops != null && - _axis.axisLineStyle.gradient.stops.isNotEmpty) { - return _axis.axisLineStyle.gradient.stops; + List _getGradientOffset() { + if (_axis.axisLineStyle.gradient!.stops != null && + _axis.axisLineStyle.gradient!.stops!.isNotEmpty) { + return _axis.axisLineStyle.gradient!.stops!; } else { // Calculates the gradient stop values based on the provided color - final double difference = 1 / _axis.axisLineStyle.gradient.colors.length; - final List offsets = - List(_axis.axisLineStyle.gradient.colors.length); - for (int i = 0; i < _axis.axisLineStyle.gradient.colors.length; i++) { + final double difference = 1 / _axis.axisLineStyle.gradient!.colors.length; + final List offsets = List.filled( + _axis.axisLineStyle.gradient!.colors.length, null); + for (int i = 0; i < _axis.axisLineStyle.gradient!.colors.length; i++) { offsets[i] = i * difference; } @@ -155,17 +164,16 @@ class _AxisPainter extends CustomPainter { /// Method to draw axis line void _drawAxisPath(Canvas canvas, double startRadian, double endRadian, - SweepGradient gradient, bool isDashedAxisLine) { + SweepGradient? gradient, bool isDashedAxisLine) { if (_axisLineAnimation != null) { - endRadian = endRadian * _axisLineAnimation.value; + endRadian = endRadian * _axisLineAnimation!.value; } canvas.save(); - canvas.translate( - _axisRenderer._axisCenter.dx, _axisRenderer._axisCenter.dy); + canvas.translate(_axisRenderer.axisCenter.dx, _axisRenderer.axisCenter.dy); canvas.rotate(_axis.isInversed - ? _getDegreeToRadian(_axis.startAngle + _axisRenderer._sweepAngle) - : _getDegreeToRadian(_axis.startAngle)); + ? getDegreeToRadian(_axis.startAngle + _axisRenderer.sweepAngle) + : getDegreeToRadian(_axis.startAngle)); Path path = Path(); //whether the style of paint is fill @@ -176,8 +184,8 @@ class _AxisPainter extends CustomPainter { } else { isFill = true; final double outerRadius = - _axisRenderer._radius - _axisRenderer._axisOffset; - final double innerRadius = outerRadius - _axisRenderer._actualAxisWidth; + _axisRenderer.radius - _axisRenderer.axisOffset; + final double innerRadius = outerRadius - _axisRenderer.actualAxisWidth; // Adds the rounded corner at start of axis line if (_axis.axisLineStyle.cornerStyle == CornerStyle.startCurve || @@ -187,7 +195,7 @@ class _AxisPainter extends CustomPainter { path.addArc( Rect.fromCircle(center: const Offset(0, 0), radius: outerRadius), - _axisRenderer._startCornerRadian, + _axisRenderer.startCornerRadian, endRadian); // Adds the rounded corner at end of axis line @@ -197,7 +205,7 @@ class _AxisPainter extends CustomPainter { } path.arcTo( Rect.fromCircle(center: const Offset(0, 0), radius: innerRadius), - endRadian + _axisRenderer._startCornerRadian, + endRadian + _axisRenderer.startCornerRadian, -endRadian, false); } @@ -210,16 +218,18 @@ class _AxisPainter extends CustomPainter { // Method to render the path void _renderPath(bool isDashedAxisLine, Path path, Canvas canvas, - Gradient gradient, bool isFill) { + SweepGradient? gradient, bool isFill) { final Paint paint = _getPaint(gradient, isFill); if (!isDashedAxisLine) { canvas.drawPath(path, paint); } else { - canvas.drawPath( - _dashPath(path, - dashArray: - _CircularIntervalList(_axis.axisLineStyle.dashArray)), - paint); + if (_axis.axisLineStyle.dashArray != null) { + canvas.drawPath( + dashPath(path, + dashArray: CircularIntervalList( + _axis.axisLineStyle.dashArray!)), + paint); + } } canvas.restore(); @@ -233,17 +243,17 @@ class _AxisPainter extends CustomPainter { endRadian = endRadian * -1; } - path.addArc(_axisRenderer._axisRect, 0, endRadian); + path.addArc(_axisRenderer.axisRect, 0, endRadian); return path; } - Paint _getPaint(SweepGradient gradient, bool isFill) { + Paint _getPaint(SweepGradient? gradient, bool isFill) { final Paint paint = Paint() ..color = _axis.axisLineStyle.color ?? _gaugeThemeData.axisLineColor ..style = !isFill ? PaintingStyle.stroke : PaintingStyle.fill - ..strokeWidth = _axisRenderer._actualAxisWidth; + ..strokeWidth = _axisRenderer.actualAxisWidth; if (gradient != null) { - paint.shader = gradient.createShader(_axisRenderer._axisRect); + paint.shader = gradient.createShader(_axisRenderer.axisRect); } return paint; @@ -252,15 +262,15 @@ class _AxisPainter extends CustomPainter { /// Draws the start corner style void _drawStartCurve( Path path, double endRadian, double innerRadius, double outerRadius) { - final Offset midPoint = _getDegreeToPoint( + final Offset midPoint = getDegreeToPoint( _axis.isInversed - ? -_axisRenderer._cornerAngle - : _axisRenderer._cornerAngle, + ? -_axisRenderer.cornerAngle + : _axisRenderer.cornerAngle, (innerRadius + outerRadius) / 2, const Offset(0, 0)); - final double midStartAngle = _getDegreeToRadian(180); + final double midStartAngle = getDegreeToRadian(180); - double midEndAngle = midStartAngle + _getDegreeToRadian(180); + double midEndAngle = midStartAngle + getDegreeToRadian(180); midEndAngle = _axis.isInversed ? -midEndAngle : midEndAngle; path.addArc( Rect.fromCircle( @@ -274,19 +284,19 @@ class _AxisPainter extends CustomPainter { Path path, double sweepRadian, double innerRadius, double outerRadius) { final double cornerAngle = _axis.axisLineStyle.cornerStyle == CornerStyle.bothCurve - ? _axisRenderer._cornerAngle + ? _axisRenderer.cornerAngle : 0; final double angle = _axis.isInversed - ? _getRadianToDegree(sweepRadian) - cornerAngle - : _getRadianToDegree(sweepRadian) + cornerAngle; - final Offset midPoint = _getDegreeToPoint( + ? getRadianToDegree(sweepRadian) - cornerAngle + : getRadianToDegree(sweepRadian) + cornerAngle; + final Offset midPoint = getDegreeToPoint( angle, (innerRadius + outerRadius) / 2, const Offset(0, 0)); final double midStartAngle = sweepRadian / 2; final double midEndAngle = _axis.isInversed - ? midStartAngle - _getDegreeToRadian(180) - : midStartAngle + _getDegreeToRadian(180); + ? midStartAngle - getDegreeToRadian(180) + : midStartAngle + getDegreeToRadian(180); path.arcTo( Rect.fromCircle( @@ -299,66 +309,63 @@ class _AxisPainter extends CustomPainter { /// Checks whether the axis line is dashed line bool _getIsDashedLine() { return _axis.axisLineStyle.dashArray != null && - _axis.axisLineStyle.dashArray.isNotEmpty && - _axis.axisLineStyle.dashArray.length > 1 && - _axis.axisLineStyle.dashArray[0] > 0 && - _axis.axisLineStyle.dashArray[1] > 0; + _axis.axisLineStyle.dashArray!.isNotEmpty && + _axis.axisLineStyle.dashArray!.length > 1 && + _axis.axisLineStyle.dashArray![0] > 0 && + _axis.axisLineStyle.dashArray![1] > 0; } /// Method to draw the major ticks void _drawMajorTicks(Canvas canvas) { - double length = _axisRenderer._majorTickOffsets.length.toDouble(); + double length = _axisRenderer.majorTickOffsets.length.toDouble(); if (_axisElementsAnimation != null) { length = - _axisRenderer._majorTickOffsets.length * _axisElementsAnimation.value; + _axisRenderer.majorTickOffsets.length * _axisElementsAnimation!.value; } - if (_axisRenderer._actualMajorTickLength > 0 && - _axis.majorTickStyle.thickness != null && + if (_axisRenderer.actualMajorTickLength > 0 && _axis.majorTickStyle.thickness > 0) { final Paint tickPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = _axis.majorTickStyle.thickness; - for (num i = 0; i < length; i++) { - final _TickOffset tickOffset = _axisRenderer._majorTickOffsets[i]; - if (!(i == 0 && _axisRenderer._sweepAngle == 360)) { + for (int i = 0; i < length; i++) { + final TickOffset tickOffset = _axisRenderer.majorTickOffsets[i]; + if (!(i == 0 && _axisRenderer.sweepAngle == 360)) { tickPaint.color = _axis.useRangeColorForAxis - ? _axisRenderer._getRangeColor( + ? _axisRenderer.getRangeColor( tickOffset.value, _gaugeThemeData) ?? _axis.majorTickStyle.color ?? _gaugeThemeData.majorTickColor : _axis.majorTickStyle.color ?? _gaugeThemeData.majorTickColor; if (_axis.majorTickStyle.dashArray != null && - _axis.majorTickStyle.dashArray.isNotEmpty) { + _axis.majorTickStyle.dashArray!.isNotEmpty) { final Path path = Path() ..moveTo(tickOffset.startPoint.dx, tickOffset.startPoint.dy) ..lineTo(tickOffset.endPoint.dx, tickOffset.endPoint.dy); canvas.drawPath( - _dashPath(path, - dashArray: _CircularIntervalList( - _axis.majorTickStyle.dashArray)), + dashPath(path, + dashArray: CircularIntervalList( + _axis.majorTickStyle.dashArray!)), tickPaint); } else { - if ((i == _axisRenderer._majorTickOffsets.length - 1) && - _axisRenderer._sweepAngle == 360) { + if ((i == _axisRenderer.majorTickOffsets.length - 1) && + _axisRenderer.sweepAngle == 360) { // Reposition the last tick when its sweep angle is 360 final double x1 = - (_axisRenderer._majorTickOffsets[0].startPoint.dx + - _axisRenderer._majorTickOffsets[i].startPoint.dx) / + (_axisRenderer.majorTickOffsets[0].startPoint.dx + + _axisRenderer.majorTickOffsets[i].startPoint.dx) / 2; final double y1 = - (_axisRenderer._majorTickOffsets[0].startPoint.dy + - _axisRenderer._majorTickOffsets[i].startPoint.dy) / - 2; - final double x2 = - (_axisRenderer._majorTickOffsets[0].endPoint.dx + - _axisRenderer._majorTickOffsets[i].endPoint.dx) / - 2; - final double y2 = - (_axisRenderer._majorTickOffsets[0].endPoint.dy + - _axisRenderer._majorTickOffsets[i].endPoint.dy) / + (_axisRenderer.majorTickOffsets[0].startPoint.dy + + _axisRenderer.majorTickOffsets[i].startPoint.dy) / 2; + final double x2 = (_axisRenderer.majorTickOffsets[0].endPoint.dx + + _axisRenderer.majorTickOffsets[i].endPoint.dx) / + 2; + final double y2 = (_axisRenderer.majorTickOffsets[0].endPoint.dy + + _axisRenderer.majorTickOffsets[i].endPoint.dy) / + 2; canvas.drawLine(Offset(x1, y1), Offset(x2, y2), tickPaint); } else { canvas.drawLine( @@ -372,33 +379,32 @@ class _AxisPainter extends CustomPainter { /// Method to draw the mior ticks void _drawMinorTicks(Canvas canvas) { - double length = _axisRenderer._minorTickOffsets.length.toDouble(); + double length = _axisRenderer.minorTickOffsets.length.toDouble(); if (_axisElementsAnimation != null) { length = - _axisRenderer._minorTickOffsets.length * _axisElementsAnimation.value; + _axisRenderer.minorTickOffsets.length * _axisElementsAnimation!.value; } - if (_axisRenderer._actualMinorTickLength > 0 && - _axis.minorTickStyle.thickness != null && + if (_axisRenderer.actualMinorTickLength > 0 && _axis.minorTickStyle.thickness > 0) { final Paint tickPaint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = _axis.minorTickStyle.thickness; for (int i = 0; i < length; i++) { - final _TickOffset tickOffset = _axisRenderer._minorTickOffsets[i]; + final TickOffset tickOffset = _axisRenderer.minorTickOffsets[i]; tickPaint.color = _axis.useRangeColorForAxis - ? _axisRenderer._getRangeColor(tickOffset.value, _gaugeThemeData) ?? + ? _axisRenderer.getRangeColor(tickOffset.value, _gaugeThemeData) ?? _axis.minorTickStyle.color ?? _gaugeThemeData.minorTickColor : _axis.minorTickStyle.color ?? _gaugeThemeData.minorTickColor; if (_axis.minorTickStyle.dashArray != null && - _axis.minorTickStyle.dashArray.isNotEmpty) { + _axis.minorTickStyle.dashArray!.isNotEmpty) { final Path path = Path() ..moveTo(tickOffset.startPoint.dx, tickOffset.startPoint.dy) ..lineTo(tickOffset.endPoint.dx, tickOffset.endPoint.dy); canvas.drawPath( - _dashPath(path, - dashArray: _CircularIntervalList( - _axis.minorTickStyle.dashArray)), + dashPath(path, + dashArray: CircularIntervalList( + _axis.minorTickStyle.dashArray!)), tickPaint); } else { canvas.drawLine( @@ -410,25 +416,25 @@ class _AxisPainter extends CustomPainter { /// Method to draw the axis labels void _drawAxisLabels(Canvas canvas) { - double length = _axisRenderer._axisLabels.length.toDouble(); + double length = _axisRenderer.axisLabels!.length.toDouble(); if (_axisElementsAnimation != null) { - length = _axisRenderer._axisLabels.length * _axisElementsAnimation.value; + length = _axisRenderer.axisLabels!.length * _axisElementsAnimation!.value; } for (int i = 0; i < length; i++) { if (!((i == 0 && !_axis.showFirstLabel) || - (i == _axisRenderer._axisLabels.length - 1 && + (i == _axisRenderer.axisLabels!.length - 1 && !_axis.showLastLabel && - _axisRenderer._isMaxiumValueIncluded))) { - final CircularAxisLabel label = _axisRenderer._axisLabels[i]; + _axisRenderer.isMaxiumValueIncluded))) { + final CircularAxisLabel label = _axisRenderer.axisLabels![i]; final Color labelColor = label.labelStyle.color ?? _gaugeThemeData.axisLabelColor; final TextSpan span = TextSpan( text: label.text, style: TextStyle( color: _axis.ranges != null && - _axis.ranges.isNotEmpty && + _axis.ranges!.isNotEmpty && _axis.useRangeColorForAxis - ? _axisRenderer._getRangeColor( + ? _axisRenderer.getRangeColor( label.value, _gaugeThemeData) ?? labelColor : labelColor, @@ -450,11 +456,11 @@ class _AxisPainter extends CustomPainter { // Methods to render the range label void _renderText( Canvas canvas, TextPainter textPainter, CircularAxisLabel label) { - if (_axis.canRotateLabels || label._needsRotateLabel) { + if (_axis.canRotateLabels || label.needsRotateLabel) { canvas.save(); canvas.translate(label.position.dx, label.position.dy); // Rotates the labels to its calculated angle - canvas.rotate(_getDegreeToRadian(label.angle)); + canvas.rotate(getDegreeToRadian(label.angle)); canvas.scale(-1); textPainter.paint(canvas, Offset(-label.labelSize.width / 2, -label.labelSize.height / 2)); @@ -468,5 +474,5 @@ class _AxisPainter extends CustomPainter { } @override - bool shouldRepaint(_AxisPainter oldDelegate) => _isRepaint; + bool shouldRepaint(AxisPainter oldDelegate) => _isRepaint; } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/range_painter.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/range_painter.dart index 9e9f7187e..0d93fc707 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/range_painter.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/range_painter.dart @@ -1,9 +1,20 @@ -part of gauges; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:flutter/foundation.dart'; +import '../axis/radial_axis.dart'; +import '../common/radial_gauge_renderer.dart'; +import '../gauge/radial_gauge.dart'; +import '../range/gauge_range.dart'; +import '../renderers/gauge_range_renderer.dart'; +import '../renderers/radial_axis_renderer_base.dart'; +import '../utils/helper.dart'; /// Represents the painter to render axis range -class _RangePainter extends CustomPainter { +class RangePainter extends CustomPainter { /// Creates the range painter - _RangePainter( + RangePainter( this._gauge, this._axis, this._range, @@ -29,58 +40,58 @@ class _RangePainter extends CustomPainter { final bool _isRepaint; /// Specifies the range animation - final Animation _rangeAnimation; + final Animation? _rangeAnimation; /// Hold the radial gauge rendering details - final _RenderingDetails _renderingDetails; + final RenderingDetails _renderingDetails; /// Specifies the gauge theme data final SfGaugeThemeData _gaugeThemeData; /// Holds the radial axis renderer - final RadialAxisRenderer _axisRenderer; + final RadialAxisRendererBase _axisRenderer; /// Holds the range renderer - final _GaugeRangeRenderer _rangeRenderer; + final GaugeRangeRenderer _rangeRenderer; @override - bool shouldRepaint(_RangePainter oldDelegate) => _isRepaint; + bool shouldRepaint(RangePainter oldDelegate) => _isRepaint; @override void paint(Canvas canvas, Size size) { Paint paint; final Path path = Path(); - if (_rangeRenderer._actualStartValue != _rangeRenderer._actualEndValue) { + if (_rangeRenderer.actualStartValue != _rangeRenderer.actualEndValue) { canvas.save(); if (!_axis.canScaleToFit) { - canvas.translate(_rangeRenderer._center.dx - _axisRenderer._centerX, - _rangeRenderer._center.dy - _axisRenderer._centerY); + canvas.translate(_rangeRenderer.center.dx - _axisRenderer.centerX, + _rangeRenderer.center.dy - _axisRenderer.centerY); } else { canvas.translate( - _axisRenderer._axisCenter.dx, _axisRenderer._axisCenter.dy); + _axisRenderer.axisCenter.dx, _axisRenderer.axisCenter.dy); } - canvas.rotate(_rangeRenderer._rangeStartRadian); + canvas.rotate(_rangeRenderer.rangeStartRadian); - if (_rangeRenderer._rangeRect == null) { + if (_rangeRenderer.rangeRect == null) { path.arcTo( - _rangeRenderer._outerArc.arcRect, - _getDegreeToRadian(_rangeRenderer._outerArc.startAngle), - _getDegreeToRadian(_rangeRenderer._outerArcSweepAngle), + _rangeRenderer.outerArc.arcRect, + getDegreeToRadian(_rangeRenderer.outerArc.startAngle), + getDegreeToRadian(_rangeRenderer.outerArcSweepAngle), false); path.arcTo( - _rangeRenderer._innerArc.arcRect, - _getDegreeToRadian(_rangeRenderer._innerArc.endAngle), - _getDegreeToRadian(_rangeRenderer._innerArcSweepAngle), + _rangeRenderer.innerArc.arcRect, + getDegreeToRadian(_rangeRenderer.innerArc.endAngle), + getDegreeToRadian(_rangeRenderer.innerArcSweepAngle), false); - paint = _getRangePaint(true, _rangeRenderer._pathRect, 0); + paint = _getRangePaint(true, _rangeRenderer.pathRect, 0); canvas.drawPath(path, paint); } else { paint = _getRangePaint( - false, _rangeRenderer._rangeRect, _rangeRenderer._thickness); - canvas.drawArc(_rangeRenderer._rangeRect, 0, - _rangeRenderer._rangeEndRadian, false, paint); + false, _rangeRenderer.rangeRect!, _rangeRenderer.thickness); + canvas.drawArc(_rangeRenderer.rangeRect!, 0, + _rangeRenderer.rangeEndRadian, false, paint); } canvas.restore(); } @@ -92,9 +103,9 @@ class _RangePainter extends CustomPainter { // Disables the load time animation once the end value of the range // is reached if (_gauge.axes[_gauge.axes.length - 1] == _axis && - _axis.ranges[_axis.ranges.length - 1] == _range && + _axis.ranges![_axis.ranges!.length - 1] == _range && _rangeAnimation != null && - _rangeAnimation.value == 1) { + _rangeAnimation!.value == 1) { _renderingDetails.needsToAnimateRanges = false; } } @@ -103,7 +114,7 @@ class _RangePainter extends CustomPainter { Paint _getRangePaint(bool isFill, Rect rect, double strokeWidth) { double opacity = 1; if (_rangeAnimation != null) { - opacity = _rangeAnimation.value; + opacity = _rangeAnimation!.value; } final Paint paint = Paint() @@ -112,29 +123,28 @@ class _RangePainter extends CustomPainter { ..color = _range.color ?? _gaugeThemeData.rangeColor; final double actualOpacity = paint.color.opacity; paint.color = paint.color.withOpacity(opacity * actualOpacity); - if (_range.gradient != null && - _range.gradient.colors != null && - _range.gradient.colors.isNotEmpty) { - List colors = _range.gradient.colors; + if (_range.gradient != null && _range.gradient!.colors.isNotEmpty) { + List colors = _range.gradient!.colors; if (_axis.isInversed) { - colors = _range.gradient.colors.reversed.toList(); + colors = _range.gradient!.colors.reversed.toList(); } - paint.shader = SweepGradient(colors: colors, stops: _getGradientStops()) + paint.shader = SweepGradient( + colors: colors, stops: _getGradientStops() as List) .createShader(rect); } return paint; } /// To calculate the gradient stop based on the sweep angle - List _getGradientStops() { + List _getGradientStops() { final double sweepRadian = - _rangeRenderer._actualStartWidth != _rangeRenderer._actualEndWidth - ? _rangeRenderer._rangeEndRadian - _rangeRenderer._rangeStartRadian - : _rangeRenderer._rangeEndRadian; + _rangeRenderer.actualStartWidth != _rangeRenderer.actualEndWidth + ? _rangeRenderer.rangeEndRadian - _rangeRenderer.rangeStartRadian + : _rangeRenderer.rangeEndRadian; double rangeStartAngle = - _axisRenderer.valueToFactor(_rangeRenderer._actualStartValue) * - _axisRenderer._sweepAngle + + _axisRenderer.valueToFactor(_rangeRenderer.actualStartValue) * + _axisRenderer.sweepAngle + _axis.startAngle; if (rangeStartAngle < 0) { rangeStartAngle += 360; @@ -144,21 +154,22 @@ class _RangePainter extends CustomPainter { rangeStartAngle -= 360; } - final double sweepAngle = _getRadianToDegree(sweepRadian).abs(); - return _calculateGradientStops( + final double sweepAngle = getRadianToDegree(sweepRadian).abs(); + return calculateGradientStops( _getGradientOffset(), _axis.isInversed, sweepAngle); } /// Returns the gradient stop of axis line gradient - List _getGradientOffset() { - if (_range.gradient.stops != null && _range.gradient.stops.isNotEmpty) { - return _range.gradient.stops; + List _getGradientOffset() { + if (_range.gradient!.stops != null && _range.gradient!.stops!.isNotEmpty) { + return _range.gradient!.stops!; } else { // Calculates the gradient stop values based on the number of provided // color - final double difference = 1 / _range.gradient.colors.length; - final List offsets = List(_range.gradient.colors.length); - for (int i = 0; i < _range.gradient.colors.length; i++) { + final double difference = 1 / _range.gradient!.colors.length; + final List offsets = + List.filled(_range.gradient!.colors.length, null); + for (int i = 0; i < _range.gradient!.colors.length; i++) { offsets[i] = i * difference; } @@ -170,12 +181,12 @@ class _RangePainter extends CustomPainter { void _renderRangeText(Canvas canvas) { double opacity = 1; if (_rangeAnimation != null) { - opacity = _rangeAnimation.value; + opacity = _rangeAnimation!.value; } final Color color = _range.color ?? _gaugeThemeData.rangeColor; final Color labelColor = - _range.labelStyle.color ?? _getSaturationColor(color); + _range.labelStyle.color ?? getSaturationColor(color); final double actualOpacity = labelColor.opacity; final TextSpan span = TextSpan( text: _range.label, @@ -192,13 +203,13 @@ class _RangePainter extends CustomPainter { textPainter.layout(); canvas.save(); canvas.translate( - _rangeRenderer._labelPosition.dx, _rangeRenderer._labelPosition.dy); - canvas.rotate(_getDegreeToRadian(_rangeRenderer._labelAngle)); + _rangeRenderer.labelPosition.dx, _rangeRenderer.labelPosition.dy); + canvas.rotate(getDegreeToRadian(_rangeRenderer.labelAngle)); canvas.scale(-1); textPainter.paint( canvas, - Offset(-_rangeRenderer._labelSize.width / 2, - -_rangeRenderer._labelSize.height / 2)); + Offset(-_rangeRenderer.labelSize.width / 2, + -_rangeRenderer.labelSize.height / 2)); canvas.restore(); } } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/range_pointer_painter.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/range_pointer_painter.dart index 638271761..50c0ac4c0 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/range_pointer_painter.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge_painter/range_pointer_painter.dart @@ -1,9 +1,22 @@ -part of gauges; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:flutter/foundation.dart'; +import '../axis/radial_axis.dart'; +import '../common/radial_gauge_renderer.dart'; +import '../gauge/radial_gauge.dart'; +import '../pointers/range_pointer.dart'; +import '../renderers/radial_axis_renderer_base.dart'; +import '../renderers/range_pointer_renderer.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; /// Represents the painter to render the range pointer -class _RangePointerPainter extends CustomPainter { +class RangePointerPainter extends CustomPainter { /// Creates the range pointer - _RangePointerPainter( + RangePointerPainter( this._gauge, this._axis, this._rangePointer, @@ -29,46 +42,46 @@ class _RangePointerPainter extends CustomPainter { final bool _isRepaint; /// Specifies the pointer animation - final Animation _pointerAnimation; + final Animation? _pointerAnimation; /// Specifies the gauge theme data. final SfGaugeThemeData _gaugeThemeData; /// Hold the radial gauge rendering details - final _RenderingDetails _renderingDetails; + final RenderingDetails _renderingDetails; /// Holds the radial axis renderer - final RadialAxisRenderer _axisRenderer; + final RadialAxisRendererBase _axisRenderer; /// Holds the gauge range renderer - final _RangePointerRenderer _rangePointerRenderer; + final RangePointerRenderer _rangePointerRenderer; @override void paint(Canvas canvas, Size size) { final bool needsToAnimatePointer = _getNeedsPointerAnimation(); double sweepRadian = - _getPointerSweepRadian(_rangePointerRenderer._sweepCornerRadian); + _getPointerSweepRadian(_rangePointerRenderer.sweepCornerRadian); final double outerRadius = - _axisRenderer._radius - _rangePointerRenderer._totalOffset; + _axisRenderer.radius - _rangePointerRenderer.totalOffset; final double innerRadius = - outerRadius - _rangePointerRenderer._actualRangeThickness; + outerRadius - _rangePointerRenderer.actualRangeThickness; final double cornerRadius = (innerRadius - outerRadius).abs() / 2; final double value = (2 * math.pi * (innerRadius + outerRadius) / 2 * - _getRadianToDegree(_rangePointerRenderer._sweepCornerRadian) / + getRadianToDegree(_rangePointerRenderer.sweepCornerRadian) / 360) .abs(); final Path path = Path(); final bool isDashedPointerLine = _getIsDashedLine(); // Specifies whether the painting style is fill bool isFill; - if (_rangePointerRenderer._currentValue > _axis.minimum) { + if (_rangePointerRenderer.currentValue > _axis.minimum) { canvas.save(); canvas.translate( - _axisRenderer._axisCenter.dx, _axisRenderer._axisCenter.dy); - canvas.rotate(_getDegreeToRadian(_rangePointerRenderer._startArc)); + _axisRenderer.axisCenter.dx, _axisRenderer.axisCenter.dy); + canvas.rotate(getDegreeToRadian(_rangePointerRenderer.startArc)); final double curveRadius = _rangePointer.cornerStyle != CornerStyle.bothFlat ? _rangePointer.cornerStyle == CornerStyle.startCurve @@ -89,7 +102,7 @@ class _RangePointerPainter extends CustomPainter { if (needsToAnimatePointer) { path.addArc( Rect.fromCircle(center: const Offset(0, 0), radius: outerRadius), - _rangePointerRenderer._startCornerRadian, + _rangePointerRenderer.startCornerRadian, sweepRadian); } @@ -103,7 +116,7 @@ class _RangePointerPainter extends CustomPainter { if (needsToAnimatePointer) { path.arcTo( Rect.fromCircle(center: const Offset(0, 0), radius: innerRadius), - sweepRadian + _rangePointerRenderer._startCornerRadian, + sweepRadian + _rangePointerRenderer.startCornerRadian, -sweepRadian, false); } @@ -111,21 +124,23 @@ class _RangePointerPainter extends CustomPainter { isFill = false; sweepRadian = _rangePointer.cornerStyle == CornerStyle.bothFlat ? sweepRadian - : _getDegreeToRadian(_rangePointerRenderer._endArc); + : getDegreeToRadian(_rangePointerRenderer.endArc); - path.addArc(_rangePointerRenderer._arcRect, 0, sweepRadian); + path.addArc(_rangePointerRenderer.arcRect, 0, sweepRadian); } final Paint paint = - _getPointerPaint(_rangePointerRenderer._arcRect, isFill); + _getPointerPaint(_rangePointerRenderer.arcRect, isFill); if (!isDashedPointerLine) { canvas.drawPath(path, paint); } else { - canvas.drawPath( - _dashPath(path, - dashArray: - _CircularIntervalList(_rangePointer.dashArray)), - paint); + if (_rangePointer.dashArray != null) { + canvas.drawPath( + dashPath(path, + dashArray: + CircularIntervalList(_rangePointer.dashArray!)), + paint); + } } canvas.restore(); @@ -135,15 +150,15 @@ class _RangePointerPainter extends CustomPainter { /// Draws the start corner style void _drawStartCurve(Path path, double innerRadius, double outerRadius) { - final Offset midPoint = _getDegreeToPoint( + final Offset midPoint = getDegreeToPoint( _axis.isInversed - ? -_rangePointerRenderer._cornerAngle - : _rangePointerRenderer._cornerAngle, + ? -_rangePointerRenderer.cornerAngle + : _rangePointerRenderer.cornerAngle, (innerRadius + outerRadius) / 2, const Offset(0, 0)); - final double midStartAngle = _getDegreeToRadian(180); + final double midStartAngle = getDegreeToRadian(180); - double midEndAngle = midStartAngle + _getDegreeToRadian(180); + double midEndAngle = midStartAngle + getDegreeToRadian(180); midEndAngle = _axis.isInversed ? -midEndAngle : midEndAngle; path.addArc( Rect.fromCircle( @@ -157,19 +172,19 @@ class _RangePointerPainter extends CustomPainter { Path path, double sweepRadian, double innerRadius, double outerRadius) { final double cornerAngle = _rangePointer.cornerStyle == CornerStyle.bothCurve - ? _rangePointerRenderer._cornerAngle + ? _rangePointerRenderer.cornerAngle : 0; final double angle = _axis.isInversed - ? _getRadianToDegree(sweepRadian) - cornerAngle - : _getRadianToDegree(sweepRadian) + cornerAngle; - final Offset midPoint = _getDegreeToPoint( + ? getRadianToDegree(sweepRadian) - cornerAngle + : getRadianToDegree(sweepRadian) + cornerAngle; + final Offset midPoint = getDegreeToPoint( angle, (innerRadius + outerRadius) / 2, const Offset(0, 0)); final double midStartAngle = sweepRadian / 2; final double midEndAngle = _axis.isInversed - ? midStartAngle - _getDegreeToRadian(180) - : midStartAngle + _getDegreeToRadian(180); + ? midStartAngle - getDegreeToRadian(180) + : midStartAngle + getDegreeToRadian(180); path.arcTo( Rect.fromCircle( @@ -184,44 +199,44 @@ class _RangePointerPainter extends CustomPainter { final bool isPointerEndAngle = _getIsEndAngle(sweepRadian, isFill); // Disables the pointer animation once its reached the end value - if (_rangePointerRenderer._getIsPointerAnimationEnabled() && + if (_rangePointerRenderer.getIsPointerAnimationEnabled() && isPointerEndAngle) { - _rangePointerRenderer._needsAnimate = false; + _rangePointerRenderer.needsAnimate = false; } // Disables the load time animation of pointers once // its reached the end value if (_renderingDetails.needsToAnimatePointers && _gauge.axes[_gauge.axes.length - 1] == _axis && - _axis.pointers[_axis.pointers.length - 1] == _rangePointer && - (isPointerEndAngle || _pointerAnimation.isCompleted)) { + _axis.pointers![_axis.pointers!.length - 1] == _rangePointer && + (isPointerEndAngle || _pointerAnimation!.isCompleted)) { _renderingDetails.needsToAnimatePointers = false; } } /// Checks whether the current angle is end angle bool _getIsEndAngle(double sweepRadian, bool isFill) { - return sweepRadian == _rangePointerRenderer._sweepCornerRadian || + return sweepRadian == _rangePointerRenderer.sweepCornerRadian || (_rangePointer.cornerStyle != CornerStyle.bothFlat && isFill && - sweepRadian == _getDegreeToRadian(_rangePointerRenderer._endArc)); + sweepRadian == getDegreeToRadian(_rangePointerRenderer.endArc)); } /// Checks whether the axis line is dashed line bool _getIsDashedLine() { return _rangePointer.dashArray != null && - _rangePointer.dashArray.isNotEmpty && - _rangePointer.dashArray.length > 1 && - _rangePointer.dashArray[0] > 0 && - _rangePointer.dashArray[1] > 0; + _rangePointer.dashArray!.isNotEmpty && + _rangePointer.dashArray!.length > 1 && + _rangePointer.dashArray![0] > 0 && + _rangePointer.dashArray![1] > 0; } /// Returns the sweep radian for pointer double _getPointerSweepRadian(double sweepRadian) { if (_renderingDetails.needsToAnimatePointers || - _rangePointerRenderer._getIsPointerAnimationEnabled()) { - return _getDegreeToRadian( - _axisRenderer._sweepAngle * _pointerAnimation.value); + _rangePointerRenderer.getIsPointerAnimationEnabled()) { + return getDegreeToRadian( + _axisRenderer.sweepAngle * _pointerAnimation!.value); } else { return sweepRadian; } @@ -230,35 +245,35 @@ class _RangePointerPainter extends CustomPainter { /// Returns whether to animate the pointers bool _getNeedsPointerAnimation() { return _pointerAnimation == null || - !_rangePointerRenderer._needsAnimate || - (_pointerAnimation.value.abs() > 0 && + (_rangePointerRenderer.needsAnimate != null && + !_rangePointerRenderer.needsAnimate!) || + (_pointerAnimation!.value.abs() > 0 && (_renderingDetails.needsToAnimatePointers || - _rangePointerRenderer._needsAnimate)); + (_rangePointerRenderer.needsAnimate != null && + _rangePointerRenderer.needsAnimate!))); } /// Returns the paint for the pointer Paint _getPointerPaint(Rect rect, bool isFill) { final Paint paint = Paint() ..color = _rangePointer.color ?? _gaugeThemeData.rangePointerColor - ..strokeWidth = _rangePointerRenderer._actualRangeThickness + ..strokeWidth = _rangePointerRenderer.actualRangeThickness ..style = isFill ? PaintingStyle.fill : PaintingStyle.stroke; if (_rangePointer.gradient != null && - _rangePointer.gradient.colors != null && - _rangePointer.gradient.colors.isNotEmpty) { + _rangePointer.gradient!.colors.isNotEmpty) { // Holds the color for gradient List gradientColors; final double sweepAngle = - _getRadianToDegree(_rangePointerRenderer._sweepCornerRadian).abs(); - final List offsets = _getGradientOffsets(); - gradientColors = _rangePointer.gradient.colors; + getRadianToDegree(_rangePointerRenderer.sweepCornerRadian).abs(); + final List offsets = _getGradientOffsets(); + gradientColors = _rangePointer.gradient!.colors; if (_axis.isInversed) { - gradientColors = _rangePointer.gradient.colors.reversed.toList(); + gradientColors = _rangePointer.gradient!.colors.reversed.toList(); } // gradient for the range pointer final SweepGradient gradient = SweepGradient( colors: gradientColors, - stops: - _calculateGradientStops(offsets, _axis.isInversed, sweepAngle)); + stops: calculateGradientStops(offsets, _axis.isInversed, sweepAngle)); paint.shader = gradient.createShader(rect); } @@ -266,17 +281,17 @@ class _RangePointerPainter extends CustomPainter { } /// Returns the gradient offset - List _getGradientOffsets() { - if (_rangePointer.gradient.stops != null && - _rangePointer.gradient.stops.isNotEmpty) { - return _rangePointer.gradient.stops; + List _getGradientOffsets() { + if (_rangePointer.gradient!.stops != null && + _rangePointer.gradient!.stops!.isNotEmpty) { + return _rangePointer.gradient!.stops!; } else { // Calculates the gradient stop values based on the number of // provided color - final double difference = 1 / _rangePointer.gradient.colors.length; - final List offsets = - List(_rangePointer.gradient.colors.length); - for (int i = 0; i < _rangePointer.gradient.colors.length; i++) { + final double difference = 1 / _rangePointer.gradient!.colors.length; + final List offsets = + List.filled(_rangePointer.gradient!.colors.length, null); + for (int i = 0; i < _rangePointer.gradient!.colors.length; i++) { offsets[i] = i * difference; } @@ -285,5 +300,5 @@ class _RangePointerPainter extends CustomPainter { } @override - bool shouldRepaint(_RangePointerPainter oldDelegate) => _isRepaint; + bool shouldRepaint(RangePointerPainter oldDelegate) => _isRepaint; } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/gauge_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/gauge_pointer.dart index 1507ed9ed..1de4e5e29 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/gauge_pointer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/gauge_pointer.dart @@ -1,4 +1,7 @@ -part of gauges; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../common/common.dart'; +import '../utils/enum.dart'; /// [GaugePointer] has properties for customizing gauge pointers. abstract class GaugePointer { @@ -81,7 +84,7 @@ abstract class GaugePointer { /// ])); /// } /// ``` - final ValueChanged onValueChanged; + final ValueChanged? onValueChanged; /// Called when the user starts selecting a new value of pointer by dragging. /// @@ -110,7 +113,7 @@ abstract class GaugePointer { /// ])); /// } /// ``` - final ValueChanged onValueChangeStart; + final ValueChanged? onValueChangeStart; /// Called when the user is done selecting a new value of the pointer /// by dragging. @@ -138,7 +141,7 @@ abstract class GaugePointer { /// ])); /// } /// ``` - final ValueChanged onValueChangeEnd; + final ValueChanged? onValueChangeEnd; /// Called during a drag when the user is selecting before a new value /// for the pointer by dragging. @@ -170,7 +173,7 @@ abstract class GaugePointer { /// ])); /// } /// ``` - final ValueChanged onValueChanging; + final ValueChanged? onValueChanging; /// Whether to enable the pointer animation. /// @@ -235,153 +238,3 @@ abstract class GaugePointer { ///``` final AnimationType animationType; } - -/// This class has the methods for customizing the default gauge axis -abstract class _GaugePointerRenderer { - _GaugePointerRenderer() { - _needsRepaintPointer = true; - _isDragStarted = false; - _animationEndValue = 0; - } - - /// Holds the corresponding gauge pointer - GaugePointer _gaugePointer; - - ///Holds the correponding axis renderer - RadialAxisRenderer _axisRenderer; - - /// Specifies the axis for this pointer - RadialAxis _axis; - - /// Specifies whether to repaint the marker - bool _needsRepaintPointer; - - /// Specifies the current value of the point - double _currentValue; - - /// Specifies the pointer rect - Rect _pointerRect; - - /// Specifies the value whether the pointer is dragged - bool _isDragStarted; - - /// Holds the end value of pointer animation - double _animationEndValue; - - /// Holds the animation start value; - double _animationStartValue; - - /// Holds the value whether to animate the pointer - bool _needsAnimate; - - /// Method to calculates the pointer position - void _calculatePosition(); - - /// Method to update the drag value - void _updateDragValue( - double x, double y, _RenderingDetails animationDetails) { - final double actualCenterX = _axisRenderer._axisSize.width * _axis.centerX; - final double actualCenterY = _axisRenderer._axisSize.height * _axis.centerY; - double angle = - math.atan2(y - actualCenterY, x - actualCenterX) * (180 / math.pi) + - 360; - final double endAngle = _axis.startAngle + _axisRenderer._sweepAngle; - if (angle < 360 && angle > 180) { - angle += 360; - } - - if (angle > endAngle) { - angle %= 360; - } - - if (angle >= _axis.startAngle && angle <= endAngle) { - double dragValue = 0; - - /// The current pointer value is calculated from the angle - if (!_axis.isInversed) { - dragValue = _axis.minimum + - (angle - _axis.startAngle) * - ((_axis.maximum - _axis.minimum) / _axisRenderer._sweepAngle); - } else { - dragValue = _axis.maximum - - (angle - _axis.startAngle) * - ((_axis.maximum - _axis.minimum) / _axisRenderer._sweepAngle); - } - - if (this is RangePointer) { - final num calculatedInterval = _axisRenderer._calculateAxisInterval(3); - // Restricts the dragging of range pointer from the minimum value - // of axis - if (dragValue < _axis.minimum + calculatedInterval / 2) { - return; - } - } - - _setCurrentPointerValue(dragValue, animationDetails); - } - } - - /// Method to set the current pointer value - void _setCurrentPointerValue( - double dragValue, _RenderingDetails animationDetails) { - final double actualValue = - _getMinMax(dragValue, _axis.minimum, _axis.maximum); - const int maximumLabel = 3; - final int niceInterval = - _axisRenderer._calculateAxisInterval(maximumLabel).toInt(); - - // Restricts the dragging of pointer once the maximum value of axis - // is reached - if (_axisRenderer._sweepAngle != 360 && - niceInterval != _axis.maximum / 2 && - ((actualValue.round() <= niceInterval && - _currentValue >= _axis.maximum - niceInterval) || - (actualValue.round() >= _axis.maximum - niceInterval && - _currentValue <= niceInterval))) { - _isDragStarted = false; - return; - } - - if (_gaugePointer.onValueChanging != null) { - final ValueChangingArgs args = ValueChangingArgs()..value = actualValue; - _gaugePointer.onValueChanging(args); - if (args.cancel != null && args.cancel) { - return; - } - } - - _currentValue = actualValue; - _calculatePosition(); - _createPointerValueChangedArgs(); - animationDetails.pointerRepaintNotifier.value++; - } - - /// Method to fire the on value change end event - void _createPointerValueChangeEndArgs() { - if (_gaugePointer.onValueChangeEnd != null) { - _gaugePointer.onValueChangeEnd(_currentValue); - } - } - - /// Method to fire the on value changed event - void _createPointerValueChangedArgs() { - if (_gaugePointer.onValueChanged != null) { - _gaugePointer.onValueChanged(_currentValue); - } - } - - /// Method to fire the on value change start event - void _createPointerValueChangeStartArgs() { - if (_gaugePointer.onValueChangeStart != null) { - _gaugePointer.onValueChangeStart(_currentValue); - } - } - - /// Specifies whether the pointer animation is enabled - bool _getIsPointerAnimationEnabled() { - return _gaugePointer.enableAnimation && - _gaugePointer.animationDuration > 0 && - _needsAnimate != null && - _needsAnimate; - } -} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer.dart index cd5d0cc96..48bcf493a 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer.dart @@ -1,4 +1,8 @@ -part of gauges; +import 'package:flutter/rendering.dart'; +import '../common/common.dart'; +import '../pointers/gauge_pointer.dart'; +import '../renderers/marker_pointer_renderer.dart'; +import '../utils/enum.dart'; /// Create the pointer to indicate the value with built-in shape. /// @@ -24,29 +28,32 @@ class MarkerPointer extends GaugePointer { /// [animationDuration], [markerWidth], [markerHeight], [borderWidth] /// must be non-negative. /// - MarkerPointer( - {double value = 0, - bool enableDragging, - ValueChanged onValueChanged, - ValueChanged onValueChangeStart, - ValueChanged onValueChangeEnd, - ValueChanged onValueChanging, - this.markerType = MarkerType.invertedTriangle, - this.color, - this.markerWidth = 10, - this.markerHeight = 10, - this.borderWidth = 0, - this.markerOffset = 0, - this.text, - this.borderColor, - this.offsetUnit = GaugeSizeUnit.logicalPixel, - this.imageUrl, - this.onCreatePointerRenderer, - AnimationType animationType, - GaugeTextStyle textStyle, - bool enableAnimation, - double animationDuration = 1000}) - : textStyle = textStyle ?? + MarkerPointer({ + double value = 0, + bool enableDragging = false, + ValueChanged? onValueChanged, + ValueChanged? onValueChangeStart, + ValueChanged? onValueChangeEnd, + ValueChanged? onValueChanging, + AnimationType animationType = AnimationType.ease, + bool enableAnimation = false, + double animationDuration = 1000, + this.markerType = MarkerType.invertedTriangle, + this.color, + this.markerWidth = 10, + this.markerHeight = 10, + this.borderWidth = 0, + this.markerOffset = 0, + this.text, + this.borderColor, + this.offsetUnit = GaugeSizeUnit.logicalPixel, + this.imageUrl, + this.onCreatePointerRenderer, + GaugeTextStyle? textStyle, + this.overlayColor, + this.overlayRadius, + this.elevation = 0, + }) : textStyle = textStyle ?? GaugeTextStyle( fontSize: 12.0, fontFamily: 'Segoe UI', @@ -54,20 +61,20 @@ class MarkerPointer extends GaugePointer { fontWeight: FontWeight.normal), assert(animationDuration > 0, 'Animation duration must be a non-negative value.'), - assert(value != null, 'Pointer value should not be null.'), assert(markerWidth >= 0, 'Marker width must be a non-negative value.'), assert(markerHeight >= 0, 'Marker height must be non-negative value.'), assert(borderWidth >= 0, 'Border width must be non-negative value.'), - assert(markerOffset != null, 'Marker offset should not be null.'), + assert( + elevation >= 0, 'Shadow elevation must be a non-negative value.'), super( value: value, - enableDragging: enableDragging ?? false, + enableDragging: enableDragging, onValueChanged: onValueChanged, onValueChangeStart: onValueChangeStart, onValueChangeEnd: onValueChangeEnd, onValueChanging: onValueChanging, - animationType: animationType ?? AnimationType.ease, - enableAnimation: enableAnimation ?? false, + animationType: animationType, + enableAnimation: enableAnimation, animationDuration: animationDuration); /// Specifies the built-in shape type for pointer. @@ -112,7 +119,7 @@ class MarkerPointer extends GaugePointer { /// )); ///} /// ``` - final Color color; + final Color? color; /// Specifies the marker height in logical pixels. /// @@ -169,7 +176,7 @@ class MarkerPointer extends GaugePointer { /// )); ///} /// ``` - final String imageUrl; + final String? imageUrl; /// Specifies the text for marker pointer. /// @@ -190,7 +197,7 @@ class MarkerPointer extends GaugePointer { /// )); ///} /// ``` - final String text; + final String? text; /// The style to use for the marker pointer text. /// @@ -290,7 +297,7 @@ class MarkerPointer extends GaugePointer { /// )); ///} /// ``` - final Color borderColor; + final Color? borderColor; /// Specifies the border width for marker. /// @@ -343,9 +350,83 @@ class MarkerPointer extends GaugePointer { /// _CustomPointerRenderer class implementation /// } ///``` - final MarkerPointerRendererFactory + final MarkerPointerRendererFactory? onCreatePointerRenderer; + /// Elevation of the pointer. + /// + /// The pointer can be elevated by rendering with the shadow behind it. + /// By default, `Colors.black` is used as shadow color. + /// This property controls the size of the shadow. + /// + /// Defaults to `0` + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfRadialGauge( + /// axes:[RadialAxis( + /// pointers: [MarkerPointer(value: 50, + /// enableDragging: true, + /// borderColor: Colors.red, elevation : 2)], + /// )] + /// )); + ///} + /// ``` + final double elevation; + + /// Color of the overlay drawn around the marker pointer. + /// + /// If [enableDragging] is set to true, while touching the marker pointer in mobile and on hovering/clicking + /// the marker pointer in web, the overlay will be displayed in the marker’s color by default with reduced + /// opacity. If [enableDragging] is set to false, the overlay will not be displayed. + /// + ///If it has to be hidden, `Colors.transparent` can be set to this property. + /// + /// Defaults to `null` + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfRadialGauge( + /// axes:[RadialAxis( + /// pointers: [MarkerPointer(value: 50, + /// enableDragging: true, + /// overlayColor: Colors.red[50])], + /// )] + /// )); + ///} + /// ``` + final Color? overlayColor; + + /// Radius of the overlay drawn around the marker pointer. + /// + ///If [enableDragging] is set to true, while touching the marker pointer in mobile and on hovering/clicking + /// the marker pointer in web, the overlay will be displayed. If [enableDragging] is set to false, the + /// overlay will not be displayed. + /// + ///If the value of this property is `null`, the overlay radius is calculated by adding 15 logical pixels to + /// the marker's width. Else it will be rendered with the specified value as radius. + /// + ///If it has to be hidden, `0` can be set to this property. + /// + ///Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfRadialGauge( + /// axes:[RadialAxis( + /// pointers: [MarkerPointer(value: 50, + /// enableDragging: true, + /// overlayColor: Colors.red[50], + /// overlayRadius: 35)], + /// )] + /// )); + ///} + /// ``` + final double? overlayRadius; + @override bool operator ==(Object other) { if (identical(this, other)) { @@ -374,12 +455,15 @@ class MarkerPointer extends GaugePointer { other.imageUrl == imageUrl && other.offsetUnit == offsetUnit && other.onCreatePointerRenderer == onCreatePointerRenderer && - other.textStyle == textStyle; + other.textStyle == textStyle && + other.overlayColor == overlayColor && + other.overlayRadius == overlayRadius && + other.elevation == elevation; } @override int get hashCode { - final List values = [ + final List values = [ value, enableDragging, onValueChanged, @@ -399,313 +483,11 @@ class MarkerPointer extends GaugePointer { imageUrl, offsetUnit, textStyle, - onCreatePointerRenderer + onCreatePointerRenderer, + overlayColor, + overlayRadius, + elevation ]; return hashList(values); } } - -/// The [MarkerPointerRenderer] has methods to render marker pointer -/// -class MarkerPointerRenderer extends _GaugePointerRenderer { - /// Creates the instance for marker pointer renderer - MarkerPointerRenderer() : super(); - - /// Represents the marker pointer which is corresponding to this renderer - MarkerPointer pointer; - - /// Specifies the margin for calculating - /// marker pointer rect - final double _margin = 15; - - /// Specifies the marker image - dart_ui.Image _image; - - /// Specifies the marker offset - Offset _offset; - - /// Specifies the radian value of the marker - double _radian; - - /// Specifies the angle value - double _angle; - - /// Specifies the marker text size - Size _textSize; - - /// Specifies the total offset considering axis element - double _totalOffset; - - /// Specifies actual marker offset value - double _actualMarkerOffset; - - /// method to calculate the marker position - @override - void _calculatePosition() { - final MarkerPointer markerPointer = _gaugePointer; - _angle = _getPointerAngle(); - _radian = _getDegreeToRadian(_angle); - final Offset offset = _getMarkerOffset(_radian, markerPointer); - if (markerPointer.markerType == MarkerType.image && - markerPointer.imageUrl != null) { - _loadImage(markerPointer); - } else if (markerPointer.markerType == MarkerType.text && - markerPointer.text != null) { - _textSize = _getTextSize(markerPointer.text, markerPointer.textStyle); - } - - _pointerRect = Rect.fromLTRB( - offset.dx - markerPointer.markerWidth / 2 - _margin, - offset.dy - markerPointer.markerHeight / 2 - _margin, - offset.dx + markerPointer.markerWidth / 2 + _margin, - offset.dy + markerPointer.markerHeight / 2 + _margin); - } - - /// Method returns the angle of current pointer value - double _getPointerAngle() { - _currentValue = _getMinMax(_currentValue, _axis.minimum, _axis.maximum); - return (_axisRenderer.valueToFactor(_currentValue) * - _axisRenderer._sweepAngle) + - _axis.startAngle; - } - - /// Method returns the sweep angle of pointer - double _getSweepAngle() { - return _axisRenderer.valueToFactor(_currentValue); - } - - /// Calculates the marker offset position - Offset _getMarkerOffset(double markerRadian, MarkerPointer markerPointer) { - _actualMarkerOffset = _axisRenderer._getActualValue( - markerPointer.markerOffset, markerPointer.offsetUnit, true); - _totalOffset = _actualMarkerOffset < 0 - ? _axisRenderer._getAxisOffset() + _actualMarkerOffset - : (_actualMarkerOffset + _axisRenderer._axisOffset); - if (!_axis.canScaleToFit) { - final double x = (_axisRenderer._axisSize.width / 2) + - (_axisRenderer._radius - - _totalOffset - - (_axisRenderer._actualAxisWidth / 2)) * - math.cos(markerRadian) - - _axisRenderer._centerX; - final double y = (_axisRenderer._axisSize.height / 2) + - (_axisRenderer._radius - - _totalOffset - - (_axisRenderer._actualAxisWidth / 2)) * - math.sin(markerRadian) - - _axisRenderer._centerY; - _offset = Offset(x, y); - } else { - final double x = _axisRenderer._axisCenter.dx + - (_axisRenderer._radius - - _totalOffset - - (_axisRenderer._actualAxisWidth / 2)) * - math.cos(markerRadian); - final double y = _axisRenderer._axisCenter.dy + - (_axisRenderer._radius - - _totalOffset - - (_axisRenderer._actualAxisWidth / 2)) * - math.sin(markerRadian); - _offset = Offset(x, y); - } - return _offset; - } - - /// To load the image from the image url -// ignore: avoid_void_async - void _loadImage(MarkerPointer markerPointer) async { - await _renderImage(markerPointer); - _axisRenderer._renderingDetails.pointerRepaintNotifier.value++; - } - - /// Renders the image from the image url -// ignore: prefer_void_to_null - Future _renderImage(MarkerPointer markerPointer) async { - final ByteData imageData = await rootBundle.load(markerPointer.imageUrl); - final dart_ui.Codec imageCodec = - await dart_ui.instantiateImageCodec(imageData.buffer.asUint8List()); - final dart_ui.FrameInfo frameInfo = await imageCodec.getNextFrame(); - _image = frameInfo.image; - } - - /// Method to draw pointer the marker pointer. - /// - /// By overriding this method, you can draw the customized marker - /// pointer using required values. - /// - void drawPointer(Canvas canvas, PointerPaintingDetails pointerPaintingDetails, - SfGaugeThemeData gaugeThemeData) { - final MarkerPointer markerPointer = _gaugePointer; - final Paint paint = Paint() - ..color = markerPointer.color ?? gaugeThemeData.markerColor - ..style = PaintingStyle.fill; - - Paint borderPaint; - if (markerPointer.borderWidth != null && markerPointer.borderWidth > 0) { - borderPaint = Paint() - ..color = markerPointer.borderColor ?? gaugeThemeData.markerBorderColor - ..strokeWidth = markerPointer.borderWidth - ..style = PaintingStyle.stroke; - } - canvas.save(); - switch (markerPointer.markerType) { - case MarkerType.circle: - _drawCircle(canvas, paint, pointerPaintingDetails.startOffset, - borderPaint, markerPointer); - break; - case MarkerType.rectangle: - _drawRectangle(canvas, paint, pointerPaintingDetails.startOffset, - pointerPaintingDetails.pointerAngle, borderPaint, markerPointer); - break; - case MarkerType.image: - _drawMarkerImage(canvas, paint, pointerPaintingDetails.startOffset, - pointerPaintingDetails.pointerAngle, markerPointer); - break; - case MarkerType.triangle: - case MarkerType.invertedTriangle: - _drawTriangle(canvas, paint, pointerPaintingDetails.startOffset, - pointerPaintingDetails.pointerAngle, borderPaint, markerPointer); - break; - case MarkerType.diamond: - _drawDiamond(canvas, paint, pointerPaintingDetails.startOffset, - pointerPaintingDetails.pointerAngle, borderPaint, markerPointer); - break; - case MarkerType.text: - if (markerPointer.text != null) { - _drawText( - canvas, - paint, - pointerPaintingDetails.startOffset, - pointerPaintingDetails.pointerAngle, - gaugeThemeData, - markerPointer); - } - - break; - } - - canvas.restore(); - } - - /// To render the MarkerShape.Text - void _drawText( - Canvas canvas, - Paint paint, - Offset startPosition, - double pointerAngle, - SfGaugeThemeData gaugeThemeData, - MarkerPointer markerPointer) { - final TextSpan span = TextSpan( - text: markerPointer.text, - style: TextStyle( - color: - markerPointer.textStyle.color ?? gaugeThemeData.axisLabelColor, - fontSize: markerPointer.textStyle.fontSize, - fontFamily: markerPointer.textStyle.fontFamily, - fontStyle: markerPointer.textStyle.fontStyle, - fontWeight: markerPointer.textStyle.fontWeight)); - final TextPainter textPainter = TextPainter( - text: span, - textDirection: TextDirection.ltr, - textAlign: TextAlign.center); - textPainter.layout(); - canvas.save(); - canvas.translate(startPosition.dx, startPosition.dy); - canvas.rotate(_getDegreeToRadian(pointerAngle - 90)); - canvas.scale(-1); - textPainter.paint( - canvas, Offset(-_textSize.width / 2, -_textSize.height / 2)); - canvas.restore(); - } - - /// Renders the MarkerShape.circle - void _drawCircle(Canvas canvas, Paint paint, Offset startPosition, - Paint borderPaint, MarkerPointer markerPointer) { - final Rect rect = Rect.fromLTRB( - startPosition.dx - markerPointer.markerWidth / 2, - startPosition.dy - markerPointer.markerHeight / 2, - startPosition.dx + markerPointer.markerWidth / 2, - startPosition.dy + markerPointer.markerHeight / 2); - canvas.drawOval(rect, paint); - if (borderPaint != null) { - canvas.drawOval(rect, borderPaint); - } - } - - /// Renders the MarkerShape.rectangle - void _drawRectangle(Canvas canvas, Paint paint, Offset startPosition, - double pointerAngle, Paint borderPaint, MarkerPointer markerPointer) { - canvas.translate(startPosition.dx, startPosition.dy); - canvas.rotate(_getDegreeToRadian(pointerAngle)); - canvas.drawRect( - Rect.fromLTRB( - -markerPointer.markerWidth / 2, - -markerPointer.markerHeight / 2, - markerPointer.markerWidth / 2, - markerPointer.markerHeight / 2), - paint); - if (borderPaint != null) { - canvas.drawRect( - Rect.fromLTRB( - -markerPointer.markerWidth / 2, - -markerPointer.markerHeight / 2, - markerPointer.markerWidth / 2, - markerPointer.markerHeight / 2), - borderPaint); - } - } - - /// Renders the MarkerShape.image - void _drawMarkerImage(Canvas canvas, Paint paint, Offset startPosition, - double pointerAngle, MarkerPointer markerPointer) { - canvas.translate(startPosition.dx, startPosition.dy); - canvas.rotate(_getDegreeToRadian(pointerAngle + 90)); - final Rect rect = Rect.fromLTRB( - -markerPointer.markerWidth / 2, - -markerPointer.markerHeight / 2, - markerPointer.markerWidth / 2, - markerPointer.markerHeight / 2); - if (_image != null) { - canvas.drawImageNine(_image, rect, rect, paint); - } - } - - /// Renders the MarkerShape.diamond - void _drawDiamond(Canvas canvas, Paint paint, Offset startPosition, - double pointerAngle, Paint borderPaint, MarkerPointer markerPointer) { - canvas.translate(startPosition.dx, startPosition.dy); - canvas.rotate(_getDegreeToRadian(pointerAngle - 90)); - final Path path = Path(); - path.moveTo(-markerPointer.markerWidth / 2, 0); - path.lineTo(0, markerPointer.markerHeight / 2); - path.lineTo(markerPointer.markerWidth / 2, 0); - path.lineTo(0, -markerPointer.markerHeight / 2); - path.lineTo(-markerPointer.markerWidth / 2, 0); - path.close(); - canvas.drawPath(path, paint); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } - } - - /// Renders the triangle and the inverted triangle - void _drawTriangle(Canvas canvas, Paint paint, Offset startPosition, - double pointerAngle, Paint borderPaint, MarkerPointer markerPointer) { - canvas.translate(startPosition.dx, startPosition.dy); - final double triangleAngle = markerPointer.markerType == MarkerType.triangle - ? pointerAngle + 90 - : pointerAngle - 90; - canvas.rotate(_getDegreeToRadian(triangleAngle)); - - final Path path = Path(); - path.moveTo(-markerPointer.markerWidth / 2, markerPointer.markerHeight / 2); - path.lineTo(markerPointer.markerWidth / 2, markerPointer.markerHeight / 2); - path.lineTo(0, -markerPointer.markerHeight / 2); - path.lineTo(-markerPointer.markerWidth / 2, markerPointer.markerHeight / 2); - path.close(); - canvas.drawPath(path, paint); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } - } -} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer.dart index fca5b2b1b..275d7f3e7 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer.dart @@ -1,4 +1,8 @@ -part of gauges; +import 'package:flutter/rendering.dart'; +import '../common/common.dart'; +import '../pointers/gauge_pointer.dart'; +import '../renderers/needle_pointer_renderer.dart'; +import '../utils/enum.dart'; /// Create the pointer to indicate the value with needle or arrow shape. /// @@ -23,12 +27,12 @@ class NeedlePointer extends GaugePointer { /// [needleLength], [needleStartWidth], [needleEndWidth] must be non-negative. NeedlePointer( {double value = 0, - bool enableDragging, - ValueChanged onValueChanged, - ValueChanged onValueChangeStart, - ValueChanged onValueChangeEnd, - ValueChanged onValueChanging, - KnobStyle knobStyle, + bool enableDragging = false, + ValueChanged? onValueChanged, + ValueChanged? onValueChangeStart, + ValueChanged? onValueChangeEnd, + ValueChanged? onValueChanging, + KnobStyle? knobStyle, this.tailStyle, this.gradient, this.needleLength = 0.6, @@ -36,14 +40,13 @@ class NeedlePointer extends GaugePointer { this.needleStartWidth = 1, this.needleEndWidth = 10, this.onCreatePointerRenderer, - bool enableAnimation, + bool enableAnimation = false, double animationDuration = 1000, - AnimationType animationType, + AnimationType animationType = AnimationType.ease, this.needleColor}) : knobStyle = knobStyle ?? KnobStyle(knobRadius: 0.08), assert(animationDuration > 0, 'Animation duration must be a non-negative value'), - assert(value != null, 'Value should not be null.'), assert(needleLength >= 0, 'Needle length must be greater than zero.'), assert(needleStartWidth >= 0, 'Needle start width must be greater than zero.'), @@ -51,13 +54,13 @@ class NeedlePointer extends GaugePointer { needleEndWidth >= 0, 'Needle end width must be greater than zero.'), super( value: value, - enableDragging: enableDragging ?? false, + enableDragging: enableDragging, onValueChanged: onValueChanged, onValueChangeStart: onValueChangeStart, onValueChangeEnd: onValueChangeEnd, onValueChanging: onValueChanging, - animationType: animationType ?? AnimationType.ease, - enableAnimation: enableAnimation ?? false, + animationType: animationType, + enableAnimation: enableAnimation, animationDuration: animationDuration); /// The style to use for the needle knob. @@ -102,7 +105,7 @@ class NeedlePointer extends GaugePointer { /// )); ///} /// ``` - final TailStyle tailStyle; + final TailStyle? tailStyle; /// Adjusts the needle pointer length from center. /// @@ -207,7 +210,7 @@ class NeedlePointer extends GaugePointer { /// )); ///} /// ``` - final Color needleColor; + final Color? needleColor; /// A gradient to use when filling the needle pointer. /// @@ -234,7 +237,7 @@ class NeedlePointer extends GaugePointer { /// )); ///} /// ``` - final LinearGradient gradient; + final LinearGradient? gradient; /// The callback that is called when the custom renderer for /// the needle pointer is created. and it is not applicable for @@ -266,7 +269,7 @@ class NeedlePointer extends GaugePointer { /// _CustomPointerRenderer class implementation /// } ///``` - final NeedlePointerRendererFactory + final NeedlePointerRendererFactory? onCreatePointerRenderer; @override @@ -299,7 +302,7 @@ class NeedlePointer extends GaugePointer { @override int get hashCode { - final List values = [ + final List values = [ value, enableDragging, onValueChanged, @@ -321,316 +324,3 @@ class NeedlePointer extends GaugePointer { return hashList(values); } } - -/// The [NeedlePointerRenderer] has methods to render needle pointer -class NeedlePointerRenderer extends _GaugePointerRenderer { - /// Creates the instance for needle pointer renderer - NeedlePointerRenderer() : super(); - - /// Represents the needle pointer which is corresponding to this renderer - NeedlePointer pointer; - - /// Specifies the actual tail length - double _actualTailLength; - - /// Specifies the actual length of the pointer based on the coordinate unit - double _actualNeedleLength; - - /// Specifies the actual knob radius - double _actualCapRadius; - - /// Specifies the angle of the needle pointer - double _angle; - - /// Specifies the radian value of needle pointer - double _radian; - - /// Specifies the stop x value - double _stopX; - - /// Specifies the stop y value - double _stopY; - - /// Specifies the start left x value - double _startLeftX; - - /// Specifies the start left y value - double _startLeftY; - - /// Specifies the start right x value - double _startRightX; - - /// Specifies the start right y value - double _startRightY; - - /// Specifies the stop left x value - double _stopLeftX; - - /// Specifies the stop left y value - double _stopLeftY; - - /// Specifies the stop right x value - double _stopRightX; - - /// Specifies the stop right y value - double _stopRightY; - - /// Specifies the start x value - double _startX; - - /// Specifies the start y value - double _startY; - - /// Specifies the tail left start x value - double _tailLeftStartX; - - /// Specifies the tail left start y value - double _tailLeftStartY; - - /// Specifies the tail left end x value - double _tailLeftEndX; - - /// Specifies the tail left end y value - double _tailLeftEndY; - - /// Specifies the tail right start x value - double _tailRightStartX; - - /// Specifies the tail right start y value - double _tailRightStartY; - - /// Specifies the tail right end x value - double _tailRightEndX; - - /// Specifies the tail right end y value - double _tailRightEndY; - - /// Specified the axis center point - Offset _centerPoint; - - /// Calculates the needle position - @override - void _calculatePosition() { - final NeedlePointer needlePointer = _gaugePointer; - _calculateDefaultValue(needlePointer); - _calculateNeedleOffset(needlePointer); - } - - /// Calculates the sweep angle of the pointer - double _getSweepAngle() { - return _axisRenderer.valueToFactor(_currentValue); - } - - /// Calculates the default value - void _calculateDefaultValue(NeedlePointer needlePointer) { - _actualNeedleLength = _axisRenderer._getActualValue( - needlePointer.needleLength, needlePointer.lengthUnit, false); - _actualCapRadius = _axisRenderer._getActualValue( - needlePointer.knobStyle.knobRadius, - needlePointer.knobStyle.sizeUnit, - false); - _currentValue = _getMinMax(_currentValue, _axis.minimum, _axis.maximum); - _angle = (_axisRenderer.valueToFactor(_currentValue) * - _axisRenderer._sweepAngle) + - _axis.startAngle; - _radian = _getDegreeToRadian(_angle); - _centerPoint = _axisRenderer._axisCenter; - } - - /// Calculates the needle pointer offset - void _calculateNeedleOffset(NeedlePointer needlePointer) { - final double needleRadian = _getDegreeToRadian(-90); - _stopX = _actualNeedleLength * math.cos(needleRadian); - _stopY = _actualNeedleLength * math.sin(needleRadian); - _startX = 0; - _startY = 0; - - if (needlePointer.needleEndWidth != null) { - _startLeftX = - _startX - needlePointer.needleEndWidth * math.cos(needleRadian - 90); - _startLeftY = - _startY - needlePointer.needleEndWidth * math.sin(needleRadian - 90); - _startRightX = - _startX - needlePointer.needleEndWidth * math.cos(needleRadian + 90); - _startRightY = - _startY - needlePointer.needleEndWidth * math.sin(needleRadian + 90); - } - - if (needlePointer.needleStartWidth != null) { - _stopLeftX = - _stopX - needlePointer.needleStartWidth * math.cos(needleRadian - 90); - _stopLeftY = - _stopY - needlePointer.needleStartWidth * math.sin(needleRadian - 90); - _stopRightX = - _stopX - needlePointer.needleStartWidth * math.cos(needleRadian + 90); - _stopRightY = - _stopY - needlePointer.needleStartWidth * math.sin(needleRadian + 90); - } - - _calculatePointerRect(); - if (needlePointer.tailStyle != null && - needlePointer.tailStyle.width != null && - needlePointer.tailStyle.width > 0) { - _calculateTailPosition(needleRadian, needlePointer); - } - } - - /// Calculates the needle pointer rect based on - /// its start and the stop value - void _calculatePointerRect() { - double x1 = _centerPoint.dx; - double x2 = _centerPoint.dx + _actualNeedleLength * math.cos(_radian); - double y1 = _centerPoint.dy; - double y2 = _centerPoint.dy + _actualNeedleLength * math.sin(_radian); - - if (x1 > x2) { - final double temp = x1; - x1 = x2; - x2 = temp; - } - - if (y1 > y2) { - final double temp = y1; - y1 = y2; - y2 = temp; - } - - if (y2 - y1 < 20) { - y1 -= 10; // Creates the pointer rect with minimum height - y2 += 10; - } - - if (x2 - x1 < 20) { - x1 -= 10; // Creates the pointer rect with minimum width - x2 += 10; - } - - _pointerRect = Rect.fromLTRB(x1, y1, x2, y2); - } - - /// Calculates the values to render the needle tail - void _calculateTailPosition( - double needleRadian, NeedlePointer needlePointer) { - final double pointerWidth = needlePointer.tailStyle.width; - _actualTailLength = _axisRenderer._getActualValue( - needlePointer.tailStyle.length, - needlePointer.tailStyle.lengthUnit, - false); - if (_actualTailLength > 0) { - final double tailEndX = - _startX - _actualTailLength * math.cos(needleRadian); - final double tailEndY = - _startY - _actualTailLength * math.sin(needleRadian); - _tailLeftStartX = _startX - pointerWidth * math.cos(needleRadian - 90); - _tailLeftStartY = _startY - pointerWidth * math.sin(needleRadian - 90); - _tailRightStartX = _startX - pointerWidth * math.cos(needleRadian + 90); - _tailRightStartY = _startY - pointerWidth * math.sin(needleRadian + 90); - - _tailLeftEndX = tailEndX - pointerWidth * math.cos(needleRadian - 90); - _tailLeftEndY = tailEndY - pointerWidth * math.sin(needleRadian - 90); - _tailRightEndX = tailEndX - pointerWidth * math.cos(needleRadian + 90); - _tailRightEndY = tailEndY - pointerWidth * math.sin(needleRadian + 90); - } - } - - /// Method to draw pointer the needle pointer. - /// - /// By overriding this method, you can draw the customized needled pointer - /// using required values. - /// - void drawPointer(Canvas canvas, PointerPaintingDetails pointerPaintingDetails, - SfGaugeThemeData gaugeThemeData) { - final NeedlePointer needlePointer = _gaugePointer; - final double pointerRadian = - _getDegreeToRadian(pointerPaintingDetails.pointerAngle); - if (_actualNeedleLength != null && _actualNeedleLength > 0) { - _renderNeedle(canvas, pointerRadian, gaugeThemeData, needlePointer); - } - if (_actualTailLength != null && _actualTailLength > 0) { - _renderTail(canvas, pointerRadian, gaugeThemeData, needlePointer); - } - _renderCap(canvas, gaugeThemeData, needlePointer); - } - - /// To render the needle of the pointer - void _renderNeedle(Canvas canvas, double pointerRadian, - SfGaugeThemeData gaugeThemeData, NeedlePointer needlePointer) { - final Paint paint = Paint() - ..color = needlePointer.needleColor ?? gaugeThemeData.needleColor - ..style = PaintingStyle.fill; - final Path path = Path(); - path.moveTo(_startLeftX, _startLeftY); - path.lineTo(_stopLeftX, _stopLeftY); - path.lineTo(_stopRightX, _stopRightY); - path.lineTo(_startRightX, _startRightY); - path.close(); - - if (needlePointer.gradient != null) { - paint.shader = needlePointer.gradient.createShader(path.getBounds()); - } - - canvas.save(); - canvas.translate(_centerPoint.dx, _centerPoint.dy); - canvas.rotate(pointerRadian); - canvas.drawPath(path, paint); - canvas.restore(); - } - - /// To render the tail of the pointer - void _renderTail(Canvas canvas, double pointerRadian, - SfGaugeThemeData gaugeThemeData, NeedlePointer needlePointer) { - final Path tailPath = Path(); - tailPath.moveTo(_tailLeftStartX, _tailLeftStartY); - tailPath.lineTo(_tailLeftEndX, _tailLeftEndY); - tailPath.lineTo(_tailRightEndX, _tailRightEndY); - tailPath.lineTo(_tailRightStartX, _tailRightStartY); - tailPath.close(); - - canvas.save(); - canvas.translate(_centerPoint.dx, _centerPoint.dy); - canvas.rotate(pointerRadian); - - final Paint tailPaint = Paint() - ..color = needlePointer.tailStyle.color ?? gaugeThemeData.tailColor; - if (needlePointer.tailStyle.gradient != null) { - tailPaint.shader = - needlePointer.tailStyle.gradient.createShader(tailPath.getBounds()); - } - - canvas.drawPath(tailPath, tailPaint); - - if (needlePointer.tailStyle.borderWidth > 0) { - final Paint tailStrokePaint = Paint() - ..color = needlePointer.tailStyle.borderColor ?? - gaugeThemeData.tailBorderColor - ..style = PaintingStyle.stroke - ..strokeWidth = needlePointer.tailStyle.borderWidth; - canvas.drawPath(tailPath, tailStrokePaint); - } - - canvas.restore(); - } - - /// To render the cap of needle - void _renderCap(Canvas canvas, SfGaugeThemeData gaugeThemeData, - NeedlePointer needlePointer) { - if (_actualCapRadius > 0) { - final Paint knobPaint = Paint() - ..color = needlePointer.knobStyle.color ?? gaugeThemeData.knobColor; - canvas.drawCircle(_axisRenderer._axisCenter, _actualCapRadius, knobPaint); - - if (needlePointer.knobStyle.borderWidth > 0) { - final double actualBorderWidth = _axisRenderer._getActualValue( - needlePointer.knobStyle.borderWidth, - needlePointer.knobStyle.sizeUnit, - false); - final Paint strokePaint = Paint() - ..color = needlePointer.knobStyle.borderColor ?? - gaugeThemeData.knobBorderColor - ..style = PaintingStyle.stroke - ..strokeWidth = actualBorderWidth; - canvas.drawCircle(_centerPoint, _actualCapRadius, strokePaint); - } - } - } -} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer.dart index 5aa3ae58f..d201b0ff9 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer.dart @@ -1,4 +1,8 @@ -part of gauges; +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; +import '../common/common.dart'; +import '../pointers/gauge_pointer.dart'; +import '../utils/enum.dart'; /// Create the pointer to indicate the value with rounded range bar arc. /// @@ -22,15 +26,15 @@ class RangePointer extends GaugePointer { /// [animationDuration], [width], must be non-negative. RangePointer( {double value = 0, - bool enableDragging, - ValueChanged onValueChanged, - ValueChanged onValueChangeStart, - ValueChanged onValueChangeEnd, - ValueChanged onValueChanging, - AnimationType animationType, + bool enableDragging = false, + ValueChanged? onValueChanged, + ValueChanged? onValueChangeStart, + ValueChanged? onValueChangeEnd, + ValueChanged? onValueChanging, + AnimationType animationType = AnimationType.ease, this.cornerStyle = CornerStyle.bothFlat, this.gradient, - bool enableAnimation, + bool enableAnimation = false, double animationDuration = 1000, this.pointerOffset = 0, this.sizeUnit = GaugeSizeUnit.logicalPixel, @@ -39,23 +43,21 @@ class RangePointer extends GaugePointer { this.color}) : assert( animationDuration > 0, 'Animation duration must be non-negative'), - assert(value != null, 'Value should not be null'), assert(width >= 0, 'Width must be a non-negative value.'), - assert(pointerOffset != null, 'Pointer offset must not be null.'), assert( (gradient != null && gradient is SweepGradient) || gradient == null, 'The gradient must be null or else the gradient must be equal to ' 'sweep gradient.'), super( - value: value ?? 0, - enableDragging: enableDragging ?? false, - animationType: animationType ?? AnimationType.ease, + value: value, + enableDragging: enableDragging, + animationType: animationType, onValueChanged: onValueChanged, onValueChangeStart: onValueChangeStart, onValueChangeEnd: onValueChangeEnd, onValueChanging: onValueChanging, - enableAnimation: enableAnimation ?? false, - animationDuration: animationDuration ?? 1000); + enableAnimation: enableAnimation, + animationDuration: animationDuration); /// Adjusts the range pointer position. /// @@ -152,7 +154,7 @@ class RangePointer extends GaugePointer { /// )); ///} /// ``` - final Color color; + final Color? color; /// The style to use for the range pointer corner edge. /// @@ -200,7 +202,7 @@ class RangePointer extends GaugePointer { /// )); ///} /// ``` - final Gradient gradient; + final Gradient? gradient; /// Specifies the dash array to draw the dashed line. /// @@ -216,7 +218,7 @@ class RangePointer extends GaugePointer { /// )); ///} /// ``` - final List dashArray; + final List? dashArray; @override bool operator ==(Object other) { @@ -246,7 +248,7 @@ class RangePointer extends GaugePointer { @override int get hashCode { - final List values = [ + final List values = [ value, enableDragging, onValueChanged, @@ -266,150 +268,3 @@ class RangePointer extends GaugePointer { return hashList(values); } } - -/// This class has methods to render the range pointer -/// -class _RangePointerRenderer extends _GaugePointerRenderer { - /// Creates the instance for range pointer renderer - _RangePointerRenderer() : super() { - _isDragStarted = false; - _animationEndValue = 0; - } - - /// Holds the start arc value - double _startArc; - - /// Holds the end arc value - double _endArc; - - /// Holds the actual range thickness - double _actualRangeThickness; - - /// Specifies the range arc top - double _rangeArcTop; - - /// Specifies the range arc bottom - double _rangeArcBottom; - - /// Specifies the range arc left - double _rangeArcLeft; - - /// Specifies the range arc right - double _rangeArcRight; - - /// Specifies the arc rect - Rect _arcRect; - - /// Specifies the arc path - Path _arcPath; - - /// Specifies the start radian of range arc - double _startCornerRadian; - - /// Specifies the sweep radian of range arc - double _sweepCornerRadian; - - /// Specifies the center value for range corner - double _cornerCenter; - - /// Specifies the angle for corner cap - double _cornerAngle; - - /// Specifies the actual pointer offset value - double _actualPointerOffset; - - /// Specifies total offset for the range pointer - double _totalOffset; - - /// Method to calculate pointer position - @override - void _calculatePosition() { - final RangePointer rangePointer = _gaugePointer; - _currentValue = _getMinMax(_currentValue, _axis.minimum, _axis.maximum); - _actualRangeThickness = _axisRenderer._getActualValue( - rangePointer.width, rangePointer.sizeUnit, false); - _actualPointerOffset = _axisRenderer._getActualValue( - rangePointer.pointerOffset, rangePointer.sizeUnit, true); - _totalOffset = _actualPointerOffset < 0 - ? _axisRenderer._getAxisOffset() + _actualPointerOffset - : (_actualPointerOffset + _axisRenderer._axisOffset); - _startArc = (_axisRenderer.valueToFactor(_axis.minimum) * - _axisRenderer._sweepAngle) + - _axis.startAngle; - final double rangeEndAngle = (_axisRenderer.valueToFactor(_currentValue) * - _axisRenderer._sweepAngle) + - _axis.startAngle; - _endArc = rangeEndAngle - _startArc; - - _rangeArcLeft = - -(_axisRenderer._radius - (_actualRangeThickness / 2 + _totalOffset)); - _rangeArcTop = - -(_axisRenderer._radius - (_actualRangeThickness / 2 + _totalOffset)); - _rangeArcRight = - _axisRenderer._radius - (_actualRangeThickness / 2 + _totalOffset); - _rangeArcBottom = - _axisRenderer._radius - (_actualRangeThickness / 2 + _totalOffset); - - _createRangeRect(rangePointer); - } - - /// To creates the arc rect for range pointer - void _createRangeRect(RangePointer rangePointer) { - _arcRect = Rect.fromLTRB( - _rangeArcLeft, _rangeArcTop, _rangeArcRight, _rangeArcBottom); - _pointerRect = Rect.fromLTRB( - _rangeArcLeft, _rangeArcTop, _rangeArcRight, _rangeArcBottom); - _arcPath = Path(); - _arcPath.arcTo(_arcRect, _getDegreeToRadian(_startArc), - _getDegreeToRadian(_endArc), true); - _calculateCornerStylePosition(rangePointer); - } - - /// Calculates the rounded corner position - void _calculateCornerStylePosition(RangePointer rangePointer) { - _cornerCenter = (_arcRect.right - _arcRect.left) / 2; - _cornerAngle = _cornerRadiusAngle(_cornerCenter, _actualRangeThickness / 2); - - switch (rangePointer.cornerStyle) { - case CornerStyle.startCurve: - { - _startCornerRadian = _axis.isInversed - ? _getDegreeToRadian(-_cornerAngle) - : _getDegreeToRadian(_cornerAngle); - _sweepCornerRadian = _axis.isInversed - ? _getDegreeToRadian(_endArc + _cornerAngle) - : _getDegreeToRadian(_endArc - _cornerAngle); - } - break; - case CornerStyle.endCurve: - { - _startCornerRadian = _getDegreeToRadian(0); - _sweepCornerRadian = _axis.isInversed - ? _getDegreeToRadian(_endArc + _cornerAngle) - : _getDegreeToRadian(_endArc - _cornerAngle); - } - break; - case CornerStyle.bothCurve: - { - _startCornerRadian = _axis.isInversed - ? _getDegreeToRadian(-_cornerAngle) - : _getDegreeToRadian(_cornerAngle); - _sweepCornerRadian = _axis.isInversed - ? _getDegreeToRadian(_endArc + 2 * _cornerAngle) - : _getDegreeToRadian(_endArc - 2 * _cornerAngle); - } - break; - case CornerStyle.bothFlat: - { - _startCornerRadian = _getDegreeToRadian(_startArc); - _sweepCornerRadian = _getDegreeToRadian(_endArc); - } - break; - } - } - - /// Calculates the range sweep angle - double _getSweepAngle() { - return _getRadianToDegree(_sweepCornerRadian) / _axisRenderer._sweepAngle; - } -} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/widget_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/widget_pointer.dart new file mode 100644 index 000000000..eae26d191 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/widget_pointer.dart @@ -0,0 +1,190 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../common/common.dart'; +import '../pointers/gauge_pointer.dart'; +import '../utils/enum.dart'; + +/// Create the pointer to indicate the value with built-in shape. +/// +/// To highlight values, set any desired widget to [child] property. +/// +/// ```dart +/// Widget build(BuildContext context) { +/// return Container( +/// child: SfRadialGauge( +/// axes:[RadialAxis( +/// pointers: [WidgetPointer(value: 50, +/// widget: Container( height: 30, width: 30, +/// decoration: BoxDecoration( +/// shape: BoxShape.circle, +/// border: Border.all(color: Colors.red)), +/// child: Icon( Icons.check, color: Colors.black )) +/// )], +/// )] +/// )); +///} +/// ``` +class WidgetPointer extends GaugePointer { + /// Create a widget pointer with the default or required properties. + /// + /// The arguments [child], [value], [offset], must not be null and + /// [animationDuration] must be non-negative. + /// + WidgetPointer( + {required this.child, + this.offsetUnit = GaugeSizeUnit.logicalPixel, + this.offset = 0, + double value = 0, + bool enableDragging = false, + ValueChanged? onValueChanged, + ValueChanged? onValueChangeStart, + ValueChanged? onValueChangeEnd, + ValueChanged? onValueChanging, + AnimationType animationType = AnimationType.ease, + bool enableAnimation = false, + double animationDuration = 1000}) + : assert(animationDuration > 0, + 'Animation duration must be a non-negative value.'), + super( + value: value, + enableDragging: enableDragging, + onValueChanged: onValueChanged, + onValueChangeStart: onValueChangeStart, + onValueChangeEnd: onValueChangeEnd, + onValueChanging: onValueChanging, + animationType: animationType, + enableAnimation: enableAnimation, + animationDuration: animationDuration); + + /// A widget, which is to be used as the pointer. + /// + /// You can set any custom widget as pointer to point a value in the gauge scale. + /// + /// Defaults to `null`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfRadialGauge( + /// axes:[RadialAxis( + /// pointers: [WidgetPointer(value: 50, + /// widget: Container( height: 30, width: 30, + /// decoration: BoxDecoration( + /// shape: BoxShape.circle, + /// border: Border.all(color: Colors.red)), + /// child: Icon( Icons.check, color: Colors.black )) + /// )], + /// )] + /// )); + ///} + /// ``` + final Widget child; + + /// Calculates the pointer position either in logical pixel or radius factor. + /// + /// Using [GaugeSizeUnit], widget pointer position is calculated. + /// + /// Defaults to `GaugeSizeUnit.logicalPixel`. + /// + /// Also refer [GaugeSizeUnit]. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfRadialGauge( + /// axes:[RadialAxis( + /// pointers: [WidgetPointer(pointerOffset: 0.3, + /// value: 20, + /// offsetUnit: GaugeSizeUnit.factor, width: 0.5 + /// widget: Container( height: 30, width: 30, + /// decoration: BoxDecoration( + /// shape: BoxShape.circle, + /// border: Border.all(color: Colors.red)), + /// child: Icon( Icons.check, color: Colors.black )) + /// )], + /// )] + /// )); + ///} + /// ``` + final GaugeSizeUnit offsetUnit; + + /// Adjusts the widget pointer position. + /// + /// You can specify position value either in logical pixel or radius factor + /// using the [offsetUnit] property. + /// + /// If offsetUnit is `GaugeSizeUnit.factor`, value can be specified from + /// 0 to 1. Here pointer placing position is calculated by + /// (offset * axis radius value). For example: offset value is 0.2 + /// and axis radius is 100, pointer is moving 20(0.2 * 100) logical pixels + /// from axis outer radius. + /// + /// If offsetUnit is `GaugeSizeUnit.logicalPixel`, pointer will move to the + /// defined value's distance from the outer radius axis. + /// + /// If you specify negative value to this property, the pointer will be + /// positioned outside the axis. + /// + /// Defaults to `0` and offsetUnit is `GaugeSizeUnit.logicalPixel`. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfRadialGauge( + /// axes:[RadialAxis( + /// pointers: [WidegtPointer(offset: 30, + /// widget: Container( height: 30, width: 30, + /// decoration: BoxDecoration( + /// shape: BoxShape.circle, + /// border: Border.all(color: Colors.red)), + /// child: Icon( Icons.check, color: Colors.black )) + /// value: 20)], + /// )] + /// )); + ///} + /// ``` + final double offset; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is WidgetPointer && + other.value == value && + other.enableDragging == enableDragging && + other.onValueChanged == onValueChanged && + other.onValueChangeStart == onValueChangeStart && + other.onValueChanging == onValueChanging && + other.onValueChangeEnd == onValueChangeEnd && + other.enableAnimation == enableAnimation && + other.animationDuration == animationDuration && + other.animationType == animationType && + other.child == child && + other.offset == offset && + other.offsetUnit == offsetUnit; + } + + @override + int get hashCode { + final List values = [ + value, + enableDragging, + onValueChanged, + onValueChangeStart, + onValueChanging, + onValueChangeEnd, + enableAnimation, + animationDuration, + animationType, + child, + offset, + offsetUnit + ]; + return hashList(values); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/range/gauge_range.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/range/gauge_range.dart index 0f58bc44e..ca5d6037b 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/range/gauge_range.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/range/gauge_range.dart @@ -1,6 +1,8 @@ -part of gauges; +import 'package:flutter/rendering.dart'; +import '../common/common.dart'; +import '../utils/enum.dart'; -/// Create the range to add color bar in the gauge. +/// Create the range to add a color bar in the gauge. /// /// [GaugeRange] is a visual element that helps to quickly visualize /// where a value falls on the axis. @@ -22,16 +24,16 @@ class GaugeRange { /// /// The arguments [startValue], [endValue], must not be null. GaugeRange( - {@required this.startValue, - @required this.endValue, - double startWidth, - double endWidth, + {required this.startValue, + required this.endValue, + double? startWidth, + double? endWidth, this.sizeUnit = GaugeSizeUnit.logicalPixel, this.color, this.gradient, this.rangeOffset = 0, this.label, - GaugeTextStyle labelStyle}) + GaugeTextStyle? labelStyle}) : startWidth = startWidth = startWidth ?? (label != null ? startWidth : 10), endWidth = endWidth = endWidth ?? (label != null ? endWidth : 10), @@ -41,14 +43,6 @@ class GaugeRange { fontFamily: 'Segoe UI', fontStyle: FontStyle.normal, fontWeight: FontWeight.normal), - assert( - startValue != null, - 'It is necessary to provide the start' - ' value for range.'), - assert( - endValue != null, - 'It is necessary to provide the end value' - ' for range.'), assert( (gradient != null && gradient is SweepGradient) || gradient == null, 'The gradient must be null or else the gradient must be equal' @@ -121,7 +115,7 @@ class GaugeRange { /// )); ///} /// ``` - final double startWidth; + final double? startWidth; /// Specifies the range end width. /// @@ -148,7 +142,7 @@ class GaugeRange { /// )); ///} /// ``` - final double endWidth; + final double? endWidth; /// Calculates the range position and size either in logical pixel /// or radius factor. @@ -218,7 +212,7 @@ class GaugeRange { /// )); ///} /// ``` - final Color color; + final Color? color; /// The style to use for the range label text. /// @@ -260,7 +254,7 @@ class GaugeRange { /// )); ///} /// ``` - final String label; + final String? label; /// A gradient to use when filling the range. /// @@ -285,7 +279,7 @@ class GaugeRange { /// )); ///} /// ``` - final Gradient gradient; + final Gradient? gradient; @override bool operator ==(Object other) { @@ -310,7 +304,7 @@ class GaugeRange { @override int get hashCode { - final List values = [ + final List values = [ startValue, endValue, startWidth, @@ -325,394 +319,3 @@ class GaugeRange { return hashList(values); } } - -/// This class has methods to render the gauge range -/// -class _GaugeRangeRenderer { - /// Creates the instance for gauge range renderer - _GaugeRangeRenderer(GaugeRange range) { - _range = range; - _needsRepaintRange = true; - } - - /// Holds the instance of corresponding gauge range - GaugeRange _range; - - /// Holds the radial axis renderer - RadialAxisRenderer _axisRenderer; - - /// Specifies whether to repaint the range - bool _needsRepaintRange; - - /// Specifies the range axis - RadialAxis _axis; - - /// Specifies the start width - double _actualStartWidth; - - /// Specifies the actual end width - double _actualEndWidth; - - /// Specifies the outer start offset - double _outerStartOffset; - - /// Specifies the outer end offset - double _outerEndOffset; - - /// Specifies the inner start offset - double _innerStartOffset; - - /// Specifies the inner end offset - double _innerEndOffset; - - /// Specifies the outer arc - _ArcData _outerArc; - - /// Specifies the inner arc - _ArcData _innerArc; - - /// Specifies the outer arc sweep angle - double _outerArcSweepAngle; - - /// Specifies the inner arc sweep angle - double _innerArcSweepAngle; - - /// Specifies the thickness value - double _thickness; - - /// Specifies the range rect - Rect _rangeRect; - - /// Specifies the range start radian - double _rangeStartRadian; - - /// Specifies the range end radian - double _rangeEndRadian; - - /// Specifies the range mid radian - double _rangeMidRadian; - - /// Specifies the center value - Offset _center; - - /// Specifies the maximum angle - double _maxAngle; - - /// Specifies the range start value - double _rangeStartValue; - - /// Specifies the range ed value - double _rangeEndValue; - - /// Specifies the range mid value - double _rangeMidValue; - - /// Specifies the label angle - double _labelAngle; - - /// Holds the label position - Offset _labelPosition; - - /// Holds the label size - Size _labelSize; - - /// Holds the actual start value - double _actualStartValue; - - /// Holds the actual end value - double _actualEndValue; - - /// Holds the total offset - double _totalOffset; - - /// Specifies the actual range offset - double _actualRangeOffset; - - /// Specifies the path rect - Rect _pathRect; - - /// Calculates the range position - void _calculateRangePosition() { - _calculateActualWidth(); - _actualRangeOffset = _axisRenderer._getActualValue( - _range.rangeOffset, _range.sizeUnit, true); - _center = !_axis.canScaleToFit - ? Offset(_axisRenderer._axisSize.width / 2, - _axisRenderer._axisSize.height / 2) - : _axisRenderer._axisCenter; - _totalOffset = _actualRangeOffset < 0 - ? _axisRenderer._getAxisOffset() + _actualRangeOffset - : (_actualRangeOffset + _axisRenderer._axisOffset); - _maxAngle = _axisRenderer._sweepAngle; - _actualStartValue = _getMinMax( - _range.startValue ?? _axis.minimum, _axis.minimum, _axis.maximum); - _actualEndValue = _getMinMax( - _range.endValue ?? _axis.maximum, _axis.minimum, _axis.maximum); - _calculateRangeAngle(); - if (_actualStartWidth != _actualEndWidth) { - _calculateInEqualWidthArc(); - } else { - _calculateEqualWidthArc(); - } - - if (_range.label != null) { - _labelSize = _getTextSize(_range.label, _range.labelStyle); - _calculateLabelPosition(); - } - } - - /// Method to calculate rect for in equal width range - void _calculateInEqualWidthArc() { - _outerEndOffset = 0; - _outerStartOffset = _outerEndOffset; - _innerStartOffset = _actualStartWidth; - _innerEndOffset = _actualEndWidth; - - _outerArc = _getRadiusToAngleConversion(_outerStartOffset, _outerEndOffset); - _innerArc = _getRadiusToAngleConversion(_innerStartOffset, _innerEndOffset); - - _outerArcSweepAngle = - _getSweepAngle(_outerArc.endAngle - _outerArc.startAngle); - _innerArcSweepAngle = - _getSweepAngle(_innerArc.endAngle - _innerArc.startAngle); - _innerArcSweepAngle *= -1; - - final double left = _outerArc.arcRect.left < _innerArc.arcRect.left - ? _outerArc.arcRect.left - : _innerArc.arcRect.left; - final double top = _outerArc.arcRect.top < _innerArc.arcRect.top - ? _outerArc.arcRect.top - : _innerArc.arcRect.top; - final double right = _outerArc.arcRect.right < _innerArc.arcRect.right - ? _innerArc.arcRect.right - : _outerArc.arcRect.right; - final double bottom = _outerArc.arcRect.bottom < _innerArc.arcRect.bottom - ? _innerArc.arcRect.bottom - : _outerArc.arcRect.bottom; - _pathRect = Rect.fromLTRB(left, top, right, bottom); - } - - /// Calculates the range angle - void _calculateRangeAngle() { - if (!_axis.isInversed) { - _rangeStartValue = _axis.startAngle + - (_maxAngle / - ((_axis.maximum - _axis.minimum) / - (_actualStartValue - _axis.minimum))); - _rangeEndValue = _axis.startAngle + - (_maxAngle / - ((_axis.maximum - _axis.minimum) / - (_actualEndValue - _axis.minimum))); - _rangeMidValue = _axis.startAngle + - (_maxAngle / - ((_axis.maximum - _axis.minimum) / - ((_actualEndValue - _actualStartValue) / 2 + - _actualStartValue))); - } else { - _rangeStartValue = _axis.startAngle + - _maxAngle - - (_maxAngle / - ((_axis.maximum - _axis.minimum) / - (_actualStartValue - _axis.minimum))); - _rangeEndValue = _axis.startAngle + - _maxAngle - - (_maxAngle / - ((_axis.maximum - _axis.minimum) / - (_actualEndValue - _axis.minimum))); - _rangeMidValue = _axis.startAngle + - _maxAngle - - (_maxAngle / - ((_axis.maximum - _axis.minimum) / - ((_actualEndValue - _actualStartValue) / 2 + - _actualStartValue))); - } - - _rangeStartRadian = _getDegreeToRadian(_rangeStartValue); - _rangeEndRadian = _getDegreeToRadian(_rangeEndValue); - _rangeMidRadian = _getDegreeToRadian(_rangeMidValue); - } - - /// Method to calculate the rect for range with equal start and end width - void _calculateEqualWidthArc() { - _thickness = _actualStartWidth; - _rangeStartRadian = _getDegreeToRadian( - (_axisRenderer.valueToFactor(_actualStartValue) * - _axisRenderer._sweepAngle) + - _axis.startAngle); - final double endRadian = _getDegreeToRadian( - (_axisRenderer.valueToFactor(_actualEndValue) * - _axisRenderer._sweepAngle) + - _axis.startAngle); - _rangeEndRadian = endRadian - _rangeStartRadian; - - _rangeRect = Rect.fromLTRB( - -(_axisRenderer._radius - (_actualStartWidth / 2 + _totalOffset)), - -(_axisRenderer._radius - (_actualStartWidth / 2 + _totalOffset)), - _axisRenderer._radius - (_actualStartWidth / 2 + _totalOffset), - _axisRenderer._radius - (_actualStartWidth / 2 + _totalOffset)); - } - - /// Method to calculate the sweep angle - double _getSweepAngle(double sweepAngle) { - if (sweepAngle < 0 && !_axis.isInversed) { - sweepAngle += 360; - } - - if (sweepAngle > 0 && _axis.isInversed) { - sweepAngle -= 360; - } - return sweepAngle; - } - - /// Converts radius to angle - _ArcData _getRadiusToAngleConversion(double startOffset, double endOffset) { - final double startRadius = _axisRenderer._radius - startOffset; - final double endRadius = _axisRenderer._radius - endOffset; - final double midRadius = - _axisRenderer._radius - (startOffset + endOffset) / 2; - - final double startX = startRadius * math.cos(_getDegreeToRadian(0)); - final double startY = startRadius * math.sin(_getDegreeToRadian(0)); - final Offset rangeStartOffset = Offset(startX, startY); - - final double endX = - endRadius * math.cos(_rangeEndRadian - _rangeStartRadian); - final double endY = - endRadius * math.sin(_rangeEndRadian - _rangeStartRadian); - final Offset rangeEndOffset = Offset(endX, endY); - - final double midX = - midRadius * math.cos(_rangeMidRadian - _rangeStartRadian); - final double midY = - midRadius * math.sin(_rangeMidRadian - _rangeStartRadian); - final Offset rangeMidOffset = Offset(midX, midY); - return _getArcData(rangeStartOffset, rangeEndOffset, rangeMidOffset); - } - - /// Method to create the arc data - _ArcData _getArcData( - Offset rangeStartOffset, Offset rangeEndOffset, Offset rangeMidOffset) { - final Offset controlPoint = - _getPointConversion(rangeStartOffset, rangeEndOffset, rangeMidOffset); - - final double distance = math.sqrt( - math.pow(rangeStartOffset.dx - controlPoint.dx, 2) + - math.pow(rangeStartOffset.dy - controlPoint.dy, 2)); - - double actualStartAngle = _getRadianToDegree(math.atan2( - rangeStartOffset.dy - controlPoint.dy, - rangeStartOffset.dx - controlPoint.dx, - )); - double actualEndAngle = _getRadianToDegree(math.atan2( - rangeEndOffset.dy - controlPoint.dy, - rangeEndOffset.dx - controlPoint.dx, - )); - - if (actualStartAngle < 0) { - actualStartAngle += 360; - } - - if (actualEndAngle < 0) { - actualEndAngle += 360; - } - - if (_actualStartValue > _actualEndValue) { - final double temp = actualEndAngle; - actualEndAngle = actualStartAngle; - actualStartAngle = temp; - } - - final _ArcData arcData = _ArcData(); - arcData.startAngle = actualStartAngle; - arcData.endAngle = actualEndAngle; - arcData.arcRect = Rect.fromLTRB( - controlPoint.dx - distance + _totalOffset, - controlPoint.dy - distance + _totalOffset, - controlPoint.dx + distance - _totalOffset, - controlPoint.dy + distance - _totalOffset); - return arcData; - } - - /// calculates the control point for range arc - Offset _getPointConversion(Offset offset1, Offset offset2, Offset offset3) { - double distance1 = (offset1.dy - offset2.dy) / (offset1.dx - offset2.dx); - distance1 = (offset1.dy - offset2.dy) == 0 || (offset1.dx - offset2.dx) == 0 - ? 0 - : distance1; - double distance2 = (offset3.dy - offset2.dy) / (offset3.dx - offset2.dx); - distance2 = (offset3.dy - offset2.dy) == 0 || (offset3.dx - offset2.dx) == 0 - ? 0 - : distance2; - double x = (distance1 * distance2 * (offset3.dy - offset1.dy) + - distance1 * (offset2.dx + offset3.dx) - - distance2 * (offset1.dx + offset2.dx)) / - (2 * (distance1 - distance2)); - final double diff = (1 / distance1).isInfinite ? 0 : (1 / distance1); - double y = -diff * (x - ((offset1.dx + offset2.dx) / 2)) + - ((offset1.dy + offset2.dy) / 2); - x = x.isNaN ? 0 : x; - y = y.isNaN ? 0 : y; - return Offset(x, y); - } - - /// Calculates the actual range width - void _calculateActualWidth() { - _actualStartWidth = _getRangeValue(_range.startWidth); - _actualEndWidth = _getRangeValue(_range.endWidth); - } - - /// Calculates the actual value - double _getRangeValue(double value) { - double actualValue = 0; - if (value != null) { - switch (_range.sizeUnit) { - case GaugeSizeUnit.factor: - { - actualValue = value * _axisRenderer._radius; - } - break; - case GaugeSizeUnit.logicalPixel: - { - actualValue = value; - } - } - } else if (_range.label != null) { - final Size size = _getTextSize(_range.label, _range.labelStyle); - actualValue = size.height; - } - - return actualValue; - } - - /// Calculates the label position - void _calculateLabelPosition() { - final double midAngle = (_axisRenderer - .valueToFactor((_actualEndValue + _actualStartValue) / 2) * - _axisRenderer._sweepAngle) + - _axis.startAngle; - final double labelRadian = _getDegreeToRadian(midAngle); - _labelAngle = midAngle - 90; - final double height = _actualStartWidth != _actualEndWidth - ? (_actualEndWidth - _actualStartWidth).abs() / 2 - : _actualEndWidth / 2; - if (!_axis.canScaleToFit) { - final double x = _axisRenderer._axisSize.width / 2 + - ((_axisRenderer._radius - (_totalOffset + height)) * - math.cos(labelRadian)) - - _axisRenderer._centerX; - final double y = _axisRenderer._axisSize.height / 2 + - ((_axisRenderer._radius - (_totalOffset + height)) * - math.sin(labelRadian)) - - _axisRenderer._centerY; - _labelPosition = Offset(x, y); - } else { - final double x = _axisRenderer._axisCenter.dx + - ((_axisRenderer._radius - (_totalOffset + height)) * - math.cos(labelRadian)); - final double y = _axisRenderer._axisCenter.dy + - ((_axisRenderer._radius - (_totalOffset + height)) * - math.sin(labelRadian)); - _labelPosition = Offset(x, y); - } - } -} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/gauge_pointer_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/gauge_pointer_renderer.dart new file mode 100644 index 000000000..70daab27f --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/gauge_pointer_renderer.dart @@ -0,0 +1,200 @@ +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../axis/radial_axis.dart'; +import '../common/common.dart'; +import '../common/radial_gauge_renderer.dart'; +import '../pointers/gauge_pointer.dart'; +import '../pointers/range_pointer.dart'; +import '../pointers/widget_pointer.dart'; +import '../renderers/radial_axis_renderer_base.dart'; +import '../renderers/widget_pointer_renderer_base.dart'; +import '../utils/helper.dart'; + +/// This class has the methods for customizing the default gauge axis +abstract class GaugePointerRenderer { + /// Creates the + GaugePointerRenderer() { + needsRepaintPointer = true; + isDragStarted = false; + animationEndValue = 0; + } + + /// Holds the corresponding gauge pointer + late GaugePointer gaugePointer; + + ///Holds the correponding axis renderer + late RadialAxisRendererBase axisRenderer; + + /// Specifies the axis for this pointer + late RadialAxis axis; + + /// Specifies whether to repaint the marker + late bool needsRepaintPointer; + + /// Specifies the current value of the point + late double currentValue; + + /// Specifies the pointer rect + late Rect pointerRect; + + /// Specifies the value whether the pointer is dragged + bool? isDragStarted; + + /// Specifies the animation value of pointer + Animation? pointerAnimation; + + /// Holds the end value of pointer animation + double? animationEndValue; + + /// Holds the animation start value; + double? animationStartValue; + + /// Holds the animation rendering details + late RenderingDetails renderingDetails; + + /// Holds the value whether to animate the pointer + bool? needsAnimate; + + /// Specifies whether the pointer is hovered + bool? isHovered; + + /// Method to calculates the pointer position + void calculatePosition(); + + /// Method to update the drag value + void updateDragValue(double x, double y, RenderingDetails animationDetails) { + final double actualCenterX = axisRenderer.axisSize.width * axis.centerX; + final double actualCenterY = axisRenderer.axisSize.height * axis.centerY; + double angle = + math.atan2(y - actualCenterY, x - actualCenterX) * (180 / math.pi) + + 360; + final double endAngle = axis.startAngle + axisRenderer.sweepAngle; + if (angle < 360 && angle > 180) { + angle += 360; + } + + if (angle > endAngle) { + angle %= 360; + } + + if (angle >= axis.startAngle && angle <= endAngle) { + double dragValue = 0; + + /// The current pointer value is calculated from the angle + if (!axis.isInversed) { + dragValue = axis.minimum + + (angle - axis.startAngle) * + ((axis.maximum - axis.minimum) / axisRenderer.sweepAngle); + } else { + dragValue = axis.maximum - + (angle - axis.startAngle) * + ((axis.maximum - axis.minimum) / axisRenderer.sweepAngle); + } + + if (this is RangePointer) { + final num calculatedInterval = axisRenderer.calculateAxisInterval(3); + // Restricts the dragging of range pointer from the minimum value + // of axis + if (dragValue < axis.minimum + calculatedInterval / 2) { + return; + } + } + _setCurrentPointerValue(dragValue, animationDetails); + } + } + + /// Method to set the current pointer value + void _setCurrentPointerValue( + double dragValue, RenderingDetails animationDetails) { + final double actualValue = getMinMax(dragValue, axis.minimum, axis.maximum); + const int maximumLabel = 3; + final int niceInterval = + axisRenderer.calculateAxisInterval(maximumLabel).toInt(); + + // Restricts the dragging of pointer once the maximum value of axis + // is reached + if (niceInterval != axis.maximum / 2 && + ((actualValue.round() <= niceInterval && + currentValue >= axis.maximum - niceInterval) || + (actualValue.round() >= axis.maximum - niceInterval && + currentValue <= niceInterval))) { + return; + } + + if (gaugePointer.onValueChanging != null) { + final ValueChangingArgs args = ValueChangingArgs()..value = actualValue; + gaugePointer.onValueChanging!(args); + if (args.cancel != null && args.cancel!) { + return; + } + } + + if (isHovered != null && !isHovered!) { + isHovered = true; + } + + final int index = + axisRenderer.renderingDetails.axisRenderers.indexOf(axisRenderer); + final List? pointerRenderers = + axisRenderer.renderingDetails.gaugePointerRenderers[index]; + final List renderers = (pointerRenderers!.where( + (element) => element.isHovered != null && element.isHovered!)).toList(); + for (int i = 0; i < renderers.length; i++) { + if (renderers[i].gaugePointer != this.gaugePointer) { + renderers[i].isHovered = false; + } + } + + currentValue = actualValue; + calculatePosition(); + createPointerValueChangedArgs(); + if (gaugePointer is WidgetPointer) { + final WidgetPointerRenderer pointerRenderer = + this as WidgetPointerRenderer; + pointerRenderer.refreshPointer(); + } else { + animationDetails.pointerRepaintNotifier.value++; + } + } + + /// Method to fire the on value change end event + void createPointerValueChangeEndArgs() { + if (gaugePointer.onValueChangeEnd != null) { + gaugePointer.onValueChangeEnd!(currentValue); + } + } + + /// Method to fire the on value changed event + void createPointerValueChangedArgs() { + if (gaugePointer.onValueChanged != null) { + gaugePointer.onValueChanged!(currentValue); + } + } + + /// Method to fire the on value change start event + void createPointerValueChangeStartArgs() { + if (gaugePointer.onValueChangeStart != null) { + gaugePointer.onValueChangeStart!(currentValue); + } + } + + /// Specifies whether the pointer animation is enabled + bool getIsPointerAnimationEnabled() { + return gaugePointer.enableAnimation && + gaugePointer.animationDuration > 0 && + needsAnimate != null && + needsAnimate!; + } + + /// Calculates the sweep angle of the pointer + double getSweepAngle() { + return (axis.onCreateAxisRenderer != null && + axisRenderer.renderer != null && + axisRenderer.renderer!.valueToFactor(currentValue) != null) + ? axisRenderer.renderer!.valueToFactor(currentValue) ?? + axisRenderer.valueToFactor(currentValue) + : axisRenderer.valueToFactor(currentValue); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/gauge_range_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/gauge_range_renderer.dart new file mode 100644 index 000000000..2d4c42c97 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/gauge_range_renderer.dart @@ -0,0 +1,412 @@ +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../axis/radial_axis.dart'; +import '../common/radial_gauge_renderer.dart'; +import '../range/gauge_range.dart'; +import '../renderers/radial_axis_renderer_base.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// This class has methods to render the gauge range +/// +class GaugeRangeRenderer { + /// Creates the instance for gauge range renderer + GaugeRangeRenderer(GaugeRange gaugeRange) { + range = gaugeRange; + needsRepaintRange = true; + } + + /// Holds the instance of corresponding gauge range + late GaugeRange range; + + /// Holds the radial axis renderer + late RadialAxisRendererBase axisRenderer; + + /// Specifies whether to repaint the range + late bool needsRepaintRange; + + /// Specifies the range axis + late RadialAxis axis; + + /// Specifies the start width + late double actualStartWidth; + + /// Specifies the actual end width + late double actualEndWidth; + + /// Specifies the outer start offset + late double _outerStartOffset; + + /// Specifies the outer end offset + late double _outerEndOffset; + + /// Specifies the inner start offset + late double _innerStartOffset; + + /// Specifies the inner end offset + late double _innerEndOffset; + + /// Specifies the outer arc + late ArcData outerArc; + + /// Specifies the inner arc + late ArcData innerArc; + + /// Specifies the outer arc sweep angle + late double outerArcSweepAngle; + + /// Specifies the inner arc sweep angle + late double innerArcSweepAngle; + + /// Specifies the thickness value + late double thickness; + + /// Specifies the range rect + Rect? rangeRect; + + /// Specifies the range start radian + late double rangeStartRadian; + + /// Specifies the range end radian + late double rangeEndRadian; + + /// Specifies the range mid radian + late double _rangeMidRadian; + + /// Specifies the center value + late Offset center; + + /// Specifies the maximum angle + late double _maxAngle; + + /// Specifies the range start value + late double _rangeStartValue; + + /// Specifies the range ed value + late double _rangeEndValue; + + /// Specifies the range mid value + late double _rangeMidValue; + + /// Specifies the label angle + late double labelAngle; + + /// Holds the label position + late Offset labelPosition; + + /// Holds the label size + late Size labelSize; + + /// Holds the actual start value + late double actualStartValue; + + /// Holds the actual end value + late double actualEndValue; + + /// Holds the total offset + late double _totalOffset; + + /// Specifies the actual range offset + late double _actualRangeOffset; + + /// Specifies the path rect + late Rect pathRect; + + /// Calculates the range position + void calculateRangePosition() { + _calculateActualWidth(); + _actualRangeOffset = + axisRenderer.getActualValue(range.rangeOffset, range.sizeUnit, true); + center = !axis.canScaleToFit + ? Offset( + axisRenderer.axisSize.width / 2, axisRenderer.axisSize.height / 2) + : axisRenderer.axisCenter; + _totalOffset = _actualRangeOffset < 0 + ? axisRenderer.getAxisOffset() + _actualRangeOffset + : (_actualRangeOffset + axisRenderer.axisOffset); + _maxAngle = axisRenderer.sweepAngle; + actualStartValue = getMinMax(range.startValue, axis.minimum, axis.maximum); + actualEndValue = getMinMax(range.endValue, axis.minimum, axis.maximum); + _calculateRangeAngle(); + if (actualStartWidth != actualEndWidth) { + _calculateInEqualWidthArc(); + } else { + _calculateEqualWidthArc(); + } + + if (range.label != null) { + labelSize = getTextSize(range.label!, range.labelStyle); + _calculateLabelPosition(); + } + } + + /// Method to calculate rect for in equal width range + void _calculateInEqualWidthArc() { + _outerEndOffset = 0; + _outerStartOffset = _outerEndOffset; + _innerStartOffset = actualStartWidth; + _innerEndOffset = actualEndWidth; + + outerArc = _getRadiusToAngleConversion(_outerStartOffset, _outerEndOffset); + innerArc = _getRadiusToAngleConversion(_innerStartOffset, _innerEndOffset); + + outerArcSweepAngle = + _getSweepAngle(outerArc.endAngle - outerArc.startAngle); + innerArcSweepAngle = + _getSweepAngle(innerArc.endAngle - innerArc.startAngle); + innerArcSweepAngle *= -1; + + final double left = outerArc.arcRect.left < innerArc.arcRect.left + ? outerArc.arcRect.left + : innerArc.arcRect.left; + final double top = outerArc.arcRect.top < innerArc.arcRect.top + ? outerArc.arcRect.top + : innerArc.arcRect.top; + final double right = outerArc.arcRect.right < innerArc.arcRect.right + ? innerArc.arcRect.right + : outerArc.arcRect.right; + final double bottom = outerArc.arcRect.bottom < innerArc.arcRect.bottom + ? innerArc.arcRect.bottom + : outerArc.arcRect.bottom; + pathRect = Rect.fromLTRB(left, top, right, bottom); + } + + /// Calculates the range angle + void _calculateRangeAngle() { + if (!axis.isInversed) { + _rangeStartValue = axis.startAngle + + (_maxAngle / + ((axis.maximum - axis.minimum) / + (actualStartValue - axis.minimum))); + _rangeEndValue = axis.startAngle + + (_maxAngle / + ((axis.maximum - axis.minimum) / + (actualEndValue - axis.minimum))); + _rangeMidValue = axis.startAngle + + (_maxAngle / + ((axis.maximum - axis.minimum) / + ((actualEndValue - actualStartValue) / 2 + + actualStartValue))); + } else { + _rangeStartValue = axis.startAngle + + _maxAngle - + (_maxAngle / + ((axis.maximum - axis.minimum) / + (actualStartValue - axis.minimum))); + _rangeEndValue = axis.startAngle + + _maxAngle - + (_maxAngle / + ((axis.maximum - axis.minimum) / + (actualEndValue - axis.minimum))); + _rangeMidValue = axis.startAngle + + _maxAngle - + (_maxAngle / + ((axis.maximum - axis.minimum) / + ((actualEndValue - actualStartValue) / 2 + + actualStartValue))); + } + + rangeStartRadian = getDegreeToRadian(_rangeStartValue); + rangeEndRadian = getDegreeToRadian(_rangeEndValue); + _rangeMidRadian = getDegreeToRadian(_rangeMidValue); + } + + /// Method to calculate the rect for range with equal start and end width + void _calculateEqualWidthArc() { + thickness = actualStartWidth; + final double startFactor = (axis.onCreateAxisRenderer != null && + axisRenderer.renderer != null && + axisRenderer.renderer!.valueToFactor(actualStartValue) != null) + ? axisRenderer.renderer!.valueToFactor(actualStartValue) ?? + axisRenderer.valueToFactor(actualStartValue) + : axisRenderer.valueToFactor(actualStartValue); + rangeStartRadian = getDegreeToRadian( + (startFactor * axisRenderer.sweepAngle) + axis.startAngle); + final double endFactor = (axis.onCreateAxisRenderer != null && + axisRenderer.renderer != null && + axisRenderer.renderer!.valueToFactor(actualEndValue) != null) + ? axisRenderer.renderer!.valueToFactor(actualEndValue) ?? + axisRenderer.valueToFactor(actualEndValue) + : axisRenderer.valueToFactor(actualEndValue); + final double endRadian = getDegreeToRadian( + (endFactor * axisRenderer.sweepAngle) + axis.startAngle); + rangeEndRadian = endRadian - rangeStartRadian; + + rangeRect = Rect.fromLTRB( + -(axisRenderer.radius - (actualStartWidth / 2 + _totalOffset)), + -(axisRenderer.radius - (actualStartWidth / 2 + _totalOffset)), + axisRenderer.radius - (actualStartWidth / 2 + _totalOffset), + axisRenderer.radius - (actualStartWidth / 2 + _totalOffset)); + } + + /// Method to calculate the sweep angle + double _getSweepAngle(double sweepAngle) { + if (sweepAngle < 0 && !axis.isInversed) { + sweepAngle += 360; + } + + if (sweepAngle > 0 && axis.isInversed) { + sweepAngle -= 360; + } + return sweepAngle; + } + + /// Converts radius to angle + ArcData _getRadiusToAngleConversion(double startOffset, double endOffset) { + final double startRadius = axisRenderer.radius - startOffset; + final double endRadius = axisRenderer.radius - endOffset; + final double midRadius = + axisRenderer.radius - (startOffset + endOffset) / 2; + + final double startX = startRadius * math.cos(getDegreeToRadian(0)); + final double startY = startRadius * math.sin(getDegreeToRadian(0)); + final Offset rangeStartOffset = Offset(startX, startY); + + final double endX = endRadius * math.cos(rangeEndRadian - rangeStartRadian); + final double endY = endRadius * math.sin(rangeEndRadian - rangeStartRadian); + final Offset rangeEndOffset = Offset(endX, endY); + + final double midX = + midRadius * math.cos(_rangeMidRadian - rangeStartRadian); + final double midY = + midRadius * math.sin(_rangeMidRadian - rangeStartRadian); + final Offset rangeMidOffset = Offset(midX, midY); + return _getArcData(rangeStartOffset, rangeEndOffset, rangeMidOffset); + } + + /// Method to create the arc data + ArcData _getArcData( + Offset rangeStartOffset, Offset rangeEndOffset, Offset rangeMidOffset) { + final Offset controlPoint = + _getPointConversion(rangeStartOffset, rangeEndOffset, rangeMidOffset); + + final double distance = math.sqrt( + math.pow(rangeStartOffset.dx - controlPoint.dx, 2) + + math.pow(rangeStartOffset.dy - controlPoint.dy, 2)); + + double actualStartAngle = getRadianToDegree(math.atan2( + rangeStartOffset.dy - controlPoint.dy, + rangeStartOffset.dx - controlPoint.dx, + )); + double actualEndAngle = getRadianToDegree(math.atan2( + rangeEndOffset.dy - controlPoint.dy, + rangeEndOffset.dx - controlPoint.dx, + )); + + if (actualStartAngle < 0) { + actualStartAngle += 360; + } + + if (actualEndAngle < 0) { + actualEndAngle += 360; + } + + if (actualStartValue > actualEndValue) { + final double temp = actualEndAngle; + actualEndAngle = actualStartAngle; + actualStartAngle = temp; + } + + final ArcData arcData = ArcData(); + arcData.startAngle = actualStartAngle; + arcData.endAngle = actualEndAngle; + arcData.arcRect = Rect.fromLTRB( + controlPoint.dx - distance + _totalOffset, + controlPoint.dy - distance + _totalOffset, + controlPoint.dx + distance - _totalOffset, + controlPoint.dy + distance - _totalOffset); + return arcData; + } + + /// calculates the control point for range arc + Offset _getPointConversion(Offset offset1, Offset offset2, Offset offset3) { + double distance1 = (offset1.dy - offset2.dy) / (offset1.dx - offset2.dx); + distance1 = (offset1.dy - offset2.dy) == 0 || (offset1.dx - offset2.dx) == 0 + ? 0 + : distance1; + double distance2 = (offset3.dy - offset2.dy) / (offset3.dx - offset2.dx); + distance2 = (offset3.dy - offset2.dy) == 0 || (offset3.dx - offset2.dx) == 0 + ? 0 + : distance2; + double x = (distance1 * distance2 * (offset3.dy - offset1.dy) + + distance1 * (offset2.dx + offset3.dx) - + distance2 * (offset1.dx + offset2.dx)) / + (2 * (distance1 - distance2)); + final double diff = (1 / distance1).isInfinite ? 0 : (1 / distance1); + double y = -diff * (x - ((offset1.dx + offset2.dx) / 2)) + + ((offset1.dy + offset2.dy) / 2); + x = x.isNaN ? 0 : x; + y = y.isNaN ? 0 : y; + return Offset(x, y); + } + + /// Calculates the actual range width + void _calculateActualWidth() { + actualStartWidth = _getRangeValue(range.startWidth); + actualEndWidth = _getRangeValue(range.endWidth); + } + + /// Calculates the actual value + double _getRangeValue(double? value) { + double actualValue = 0; + if (value != null) { + switch (range.sizeUnit) { + case GaugeSizeUnit.factor: + { + actualValue = value * axisRenderer.radius; + } + break; + case GaugeSizeUnit.logicalPixel: + { + actualValue = value; + } + } + } else if (range.label != null) { + final Size size = getTextSize(range.label!, range.labelStyle); + actualValue = size.height; + } + + return actualValue; + } + + /// Calculates the label position + void _calculateLabelPosition() { + final double midValueFactor = (axis.onCreateAxisRenderer != null && + axisRenderer.renderer != null && + axisRenderer.renderer! + .valueToFactor((actualEndValue + actualStartValue) / 2) != + null) + ? axisRenderer.renderer! + .valueToFactor((actualEndValue + actualStartValue) / 2) ?? + axisRenderer.valueToFactor((actualEndValue + actualStartValue) / 2) + : axisRenderer.valueToFactor((actualEndValue + actualStartValue) / 2); + final double midAngle = + (midValueFactor * axisRenderer.sweepAngle) + axis.startAngle; + final double labelRadian = getDegreeToRadian(midAngle); + labelAngle = midAngle - 90; + final double height = actualStartWidth != actualEndWidth + ? (actualEndWidth - actualStartWidth).abs() / 2 + : actualEndWidth / 2; + if (!axis.canScaleToFit) { + final double x = axisRenderer.axisSize.width / 2 + + ((axisRenderer.radius - (_totalOffset + height)) * + math.cos(labelRadian)) - + axisRenderer.centerX; + final double y = axisRenderer.axisSize.height / 2 + + ((axisRenderer.radius - (_totalOffset + height)) * + math.sin(labelRadian)) - + axisRenderer.centerY; + labelPosition = Offset(x, y); + } else { + final double x = axisRenderer.axisCenter.dx + + ((axisRenderer.radius - (_totalOffset + height)) * + math.cos(labelRadian)); + final double y = axisRenderer.axisCenter.dy + + ((axisRenderer.radius - (_totalOffset + height)) * + math.sin(labelRadian)); + labelPosition = Offset(x, y); + } + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/marker_pointer_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/marker_pointer_renderer.dart new file mode 100644 index 000000000..aaf414cee --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/marker_pointer_renderer.dart @@ -0,0 +1,24 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import '../common/common.dart'; +import '../pointers/marker_pointer.dart'; + +/// The [MarkerPointerRenderer] has methods to render marker pointer +/// +class MarkerPointerRenderer { + /// Creates the instance for marker pointer renderer + MarkerPointerRenderer(); + + /// Represents the marker pointer which is corresponding to this renderer + late MarkerPointer pointer; + + /// Method to draw pointer the marker pointer. + /// + /// By overriding this method, you can draw the customized marker + /// pointer using required values. + /// + void drawPointer(Canvas canvas, PointerPaintingDetails pointerPaintingDetails, + SfGaugeThemeData gaugeThemeData) {} +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/marker_pointer_renderer_base.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/marker_pointer_renderer_base.dart new file mode 100644 index 000000000..5109efd55 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/marker_pointer_renderer_base.dart @@ -0,0 +1,512 @@ +import 'dart:async'; +import 'dart:math' as math; +import 'dart:typed_data'; +import 'dart:ui'; +import 'dart:ui' as dart_ui; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart' show rootBundle; +import 'package:syncfusion_flutter_core/theme.dart'; +import '../common/common.dart'; +import '../pointers/marker_pointer.dart'; +import '../renderers/gauge_pointer_renderer.dart'; +import '../renderers/marker_pointer_renderer.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// The [MarkerPointerRenderer] has methods to render marker pointer +/// +class MarkerPointerRendererBase extends GaugePointerRenderer { + /// Creates the instance for marker pointer renderer + MarkerPointerRendererBase() : super(); + + /// Represents the marker pointer which is corresponding to this renderer + late MarkerPointer pointer; + + /// Represents the marker pointer renderer + MarkerPointerRenderer? renderer; + + /// Specifies the margin for calculating marker pointer rect + final double _margin = 15; + + /// Specifies the marker image + dart_ui.Image? _image; + + /// Specifies the marker offset + late Offset offset; + + /// Specifies the radian value of the marker + late double _radian; + + /// Specifies the angle value + late double angle; + + /// Specifies the marker text size + Size? _textSize; + + /// Specifies the total offset considering axis element + late double _totalOffset; + + /// Specifies actual marker offset value + late double _actualMarkerOffset; + + /// method to calculate the marker position + @override + void calculatePosition() { + final MarkerPointer markerPointer = gaugePointer as MarkerPointer; + angle = _getPointerAngle(); + _radian = getDegreeToRadian(angle); + final Offset offset = getMarkerOffset(_radian, markerPointer); + if (markerPointer.markerType == MarkerType.image && + markerPointer.imageUrl != null) { + _loadImage(markerPointer); + } else if (markerPointer.markerType == MarkerType.text && + markerPointer.text != null) { + _textSize = getTextSize(markerPointer.text!, markerPointer.textStyle); + } + + pointerRect = Rect.fromLTRB( + offset.dx - markerPointer.markerWidth / 2 - _margin, + offset.dy - markerPointer.markerHeight / 2 - _margin, + offset.dx + markerPointer.markerWidth / 2 + _margin, + offset.dy + markerPointer.markerHeight / 2 + _margin); + } + + /// Method returns the angle of current pointer value + double _getPointerAngle() { + currentValue = getMinMax(currentValue, axis.minimum, axis.maximum); + final double currentFactor = (axis.onCreateAxisRenderer != null && + axisRenderer.renderer != null && + axisRenderer.renderer?.valueToFactor(currentValue) != null) + ? axisRenderer.renderer?.valueToFactor(currentValue) ?? + axisRenderer.valueToFactor(currentValue) + : axisRenderer.valueToFactor(currentValue); + return (currentFactor * axisRenderer.sweepAngle) + axis.startAngle; + } + + /// Calculates the marker offset position + Offset getMarkerOffset(double markerRadian, MarkerPointer markerPointer) { + _actualMarkerOffset = axisRenderer.getActualValue( + markerPointer.markerOffset, markerPointer.offsetUnit, true); + _totalOffset = _actualMarkerOffset < 0 + ? axisRenderer.getAxisOffset() + _actualMarkerOffset + : (_actualMarkerOffset + axisRenderer.axisOffset); + if (!axis.canScaleToFit) { + final double x = (axisRenderer.axisSize.width / 2) + + (axisRenderer.radius - + _totalOffset - + (axisRenderer.actualAxisWidth / 2)) * + math.cos(markerRadian) - + axisRenderer.centerX; + final double y = (axisRenderer.axisSize.height / 2) + + (axisRenderer.radius - + _totalOffset - + (axisRenderer.actualAxisWidth / 2)) * + math.sin(markerRadian) - + axisRenderer.centerY; + offset = Offset(x, y); + } else { + final double x = axisRenderer.axisCenter.dx + + (axisRenderer.radius - + _totalOffset - + (axisRenderer.actualAxisWidth / 2)) * + math.cos(markerRadian); + final double y = axisRenderer.axisCenter.dy + + (axisRenderer.radius - + _totalOffset - + (axisRenderer.actualAxisWidth / 2)) * + math.sin(markerRadian); + offset = Offset(x, y); + } + return offset; + } + + /// To load the image from the image url +// ignore: avoid_void_async + void _loadImage(MarkerPointer markerPointer) async { + await _renderImage(markerPointer); + axisRenderer.renderingDetails.pointerRepaintNotifier.value++; + } + + /// Renders the image from the image url +// ignore: prefer_void_to_null + Future _renderImage(MarkerPointer markerPointer) async { + final ByteData imageData = await rootBundle.load(markerPointer.imageUrl!); + final dart_ui.Codec imageCodec = + await dart_ui.instantiateImageCodec(imageData.buffer.asUint8List()); + final dart_ui.FrameInfo frameInfo = await imageCodec.getNextFrame(); + _image = frameInfo.image; + } + + /// Method to draw pointer the marker pointer. + /// + /// By overriding this method, you can draw the customized marker + /// pointer using required values. + /// + void drawPointer(Canvas canvas, PointerPaintingDetails pointerPaintingDetails, + SfGaugeThemeData gaugeThemeData) { + final MarkerPointer markerPointer = gaugePointer as MarkerPointer; + final bool hideMarker = + renderingDetails.needsToAnimatePointers && axis.minimum == currentValue; + final Paint paint = Paint() + ..color = markerPointer.color ?? gaugeThemeData.markerColor + ..style = PaintingStyle.fill; + Color shadowColor = Colors.black; + if (hideMarker) { + final double actualOpacity = paint.color.opacity; + final double opacity = + pointerAnimation != null ? pointerAnimation!.value : 1; + paint.color = paint.color.withOpacity(opacity * actualOpacity); + final double shadowColorOpacity = shadowColor.opacity; + shadowColor = shadowColor.withOpacity(opacity * shadowColorOpacity); + } + + Paint? overlayPaint; + if ((isHovered != null && isHovered!) && + markerPointer.overlayColor != Colors.transparent) { + overlayPaint = Paint() + ..color = markerPointer.overlayColor ?? + markerPointer.color?.withOpacity(0.12) ?? + gaugeThemeData.markerColor.withOpacity(0.12) + ..style = PaintingStyle.fill; + if (hideMarker) { + final double actualOpacity = overlayPaint.color.opacity; + final double opacity = + pointerAnimation != null ? pointerAnimation!.value : 1; + overlayPaint.color = + overlayPaint.color.withOpacity(opacity * actualOpacity); + } + } + + Paint? borderPaint; + if (markerPointer.borderWidth > 0) { + borderPaint = Paint() + ..color = markerPointer.borderColor ?? gaugeThemeData.markerBorderColor + ..strokeWidth = markerPointer.borderWidth + ..style = PaintingStyle.stroke; + + if (hideMarker) { + final double actualOpacity = borderPaint.color.opacity; + final double opacity = pointerAnimation!.value; + borderPaint.color = + borderPaint.color.withOpacity(opacity * actualOpacity); + } + } + canvas.save(); + switch (markerPointer.markerType) { + case MarkerType.circle: + _drawCircle(canvas, paint, pointerPaintingDetails.startOffset, + borderPaint, overlayPaint, markerPointer, shadowColor); + break; + case MarkerType.rectangle: + _drawRectangle( + canvas, + paint, + pointerPaintingDetails.startOffset, + pointerPaintingDetails.pointerAngle, + borderPaint, + overlayPaint, + markerPointer, + shadowColor); + break; + case MarkerType.image: + _drawMarkerImage(canvas, paint, pointerPaintingDetails.startOffset, + pointerPaintingDetails.pointerAngle, markerPointer); + break; + case MarkerType.triangle: + case MarkerType.invertedTriangle: + _drawTriangle( + canvas, + paint, + pointerPaintingDetails.startOffset, + pointerPaintingDetails.pointerAngle, + borderPaint, + overlayPaint, + markerPointer, + shadowColor); + break; + case MarkerType.diamond: + _drawDiamond( + canvas, + paint, + pointerPaintingDetails.startOffset, + pointerPaintingDetails.pointerAngle, + borderPaint, + overlayPaint, + markerPointer, + shadowColor); + break; + case MarkerType.text: + if (markerPointer.text != null) { + _drawText( + canvas, + paint, + pointerPaintingDetails.startOffset, + pointerPaintingDetails.pointerAngle, + gaugeThemeData, + markerPointer); + } + + break; + } + + canvas.restore(); + } + + /// To render the MarkerShape.Text + void _drawText( + Canvas canvas, + Paint paint, + Offset startPosition, + double pointerAngle, + SfGaugeThemeData gaugeThemeData, + MarkerPointer markerPointer) { + Color labelColor = + markerPointer.textStyle.color ?? gaugeThemeData.axisLabelColor; + if (renderingDetails.needsToAnimatePointers && + renderingDetails.isOpacityAnimation && + axis.minimum == currentValue) { + final double actualOpacity = labelColor.opacity; + final double opacity = + pointerAnimation != null ? pointerAnimation!.value : 1; + labelColor = labelColor.withOpacity(opacity * actualOpacity); + } + + final TextSpan span = TextSpan( + text: markerPointer.text, + style: TextStyle( + color: labelColor, + fontSize: markerPointer.textStyle.fontSize, + fontFamily: markerPointer.textStyle.fontFamily, + fontStyle: markerPointer.textStyle.fontStyle, + fontWeight: markerPointer.textStyle.fontWeight)); + final TextPainter textPainter = TextPainter( + text: span, + textDirection: TextDirection.ltr, + textAlign: TextAlign.center); + textPainter.layout(); + canvas.save(); + canvas.translate(startPosition.dx, startPosition.dy); + canvas.rotate(getDegreeToRadian(pointerAngle - 90)); + canvas.scale(-1); + textPainter.paint( + canvas, Offset(-_textSize!.width / 2, -_textSize!.height / 2)); + canvas.restore(); + } + + /// Renders the MarkerShape.circle + void _drawCircle( + Canvas canvas, + Paint paint, + Offset startPosition, + Paint? borderPaint, + Paint? overlayPaint, + MarkerPointer markerPointer, + Color shadowColor) { + final Rect rect = Rect.fromLTRB( + startPosition.dx - markerPointer.markerWidth / 2, + startPosition.dy - markerPointer.markerHeight / 2, + startPosition.dx + markerPointer.markerWidth / 2, + startPosition.dy + markerPointer.markerHeight / 2); + final double overlayRadius = markerPointer.overlayRadius ?? 15; + Rect overlayRect; + if (markerPointer.overlayRadius != null) { + overlayRect = Rect.fromLTRB( + startPosition.dx - overlayRadius, + startPosition.dy - overlayRadius, + startPosition.dx + overlayRadius, + startPosition.dy + overlayRadius); + } else { + overlayRect = Rect.fromLTRB( + rect.left - overlayRadius, + rect.top - overlayRadius, + rect.right + overlayRadius, + rect.bottom + overlayRadius); + } + + if (overlayPaint != null) { + canvas.drawOval(overlayRect, overlayPaint); + } + + if (markerPointer.elevation >= 0) { + final Path path = Path(); + path.addOval(rect); + canvas.drawShadow(path, shadowColor, markerPointer.elevation, true); + } + canvas.drawOval(rect, paint); + if (borderPaint != null) { + canvas.drawOval(rect, borderPaint); + } + } + + /// Renders the MarkerShape.rectangle + void _drawRectangle( + Canvas canvas, + Paint paint, + Offset startPosition, + double pointerAngle, + Paint? borderPaint, + Paint? overlayPaint, + MarkerPointer markerPointer, + Color shadowColor) { + final Rect rect = Rect.fromLTRB( + -markerPointer.markerWidth / 2, + -markerPointer.markerHeight / 2, + markerPointer.markerWidth / 2, + markerPointer.markerHeight / 2); + final double overlayRadius = markerPointer.overlayRadius ?? 15; + Rect overlayRect; + if (markerPointer.overlayRadius != null) { + overlayRect = Rect.fromLTRB( + -overlayRadius, -overlayRadius, overlayRadius, overlayRadius); + } else { + overlayRect = Rect.fromLTRB( + rect.left - overlayRadius, + rect.top - overlayRadius, + rect.right + overlayRadius, + rect.bottom + overlayRadius); + } + + canvas.translate(startPosition.dx, startPosition.dy); + canvas.rotate(getDegreeToRadian(pointerAngle)); + if (overlayPaint != null) { + canvas.drawRect(overlayRect, overlayPaint); + } + + if (markerPointer.elevation >= 0) { + final Path path = Path(); + path.addRect(rect); + canvas.drawShadow(path, shadowColor, markerPointer.elevation, true); + } + + canvas.drawRect(rect, paint); + if (borderPaint != null) { + canvas.drawRect(rect, borderPaint); + } + } + + /// Renders the MarkerShape.image + void _drawMarkerImage(Canvas canvas, Paint paint, Offset startPosition, + double pointerAngle, MarkerPointer markerPointer) { + canvas.translate(startPosition.dx, startPosition.dy); + canvas.rotate(getDegreeToRadian(pointerAngle + 90)); + final Rect rect = Rect.fromLTRB( + -markerPointer.markerWidth / 2, + -markerPointer.markerHeight / 2, + markerPointer.markerWidth / 2, + markerPointer.markerHeight / 2); + if (_image != null) { + canvas.drawImageNine(_image!, rect, rect, paint); + } + } + + /// Renders the MarkerShape.diamond + void _drawDiamond( + Canvas canvas, + Paint paint, + Offset startPosition, + double pointerAngle, + Paint? borderPaint, + Paint? overlayPaint, + MarkerPointer markerPointer, + Color shadowColor) { + canvas.translate(startPosition.dx, startPosition.dy); + canvas.rotate(getDegreeToRadian(pointerAngle - 90)); + final Path path = Path(); + path.moveTo(-markerPointer.markerWidth / 2, 0); + path.lineTo(0, markerPointer.markerHeight / 2); + path.lineTo(markerPointer.markerWidth / 2, 0); + path.lineTo(0, -markerPointer.markerHeight / 2); + path.lineTo(-markerPointer.markerWidth / 2, 0); + path.close(); + + if (overlayPaint != null) { + final double overlayRadius = markerPointer.overlayRadius ?? 30; + final Path overlayPath = Path(); + if (markerPointer.overlayRadius != null) { + overlayPath.moveTo(-overlayRadius, 0); + overlayPath.lineTo(0, overlayRadius); + overlayPath.lineTo(overlayRadius, 0); + overlayPath.lineTo(0, -overlayRadius); + overlayPath.lineTo(-overlayRadius, 0); + } else { + overlayPath.moveTo( + -((markerPointer.markerWidth + overlayRadius) / 2), 0); + overlayPath.lineTo( + 0, ((markerPointer.markerHeight + overlayRadius) / 2)); + overlayPath.lineTo( + ((markerPointer.markerWidth + overlayRadius) / 2), 0); + overlayPath.lineTo( + 0, -((markerPointer.markerHeight + overlayRadius) / 2)); + overlayPath.lineTo( + -((markerPointer.markerWidth + overlayRadius) / 2), 0); + } + + overlayPath.close(); + canvas.drawPath(overlayPath, overlayPaint); + } + if (markerPointer.elevation >= 0) { + canvas.drawShadow(path, shadowColor, markerPointer.elevation, true); + } + + canvas.drawPath(path, paint); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } + } + + /// Renders the triangle and the inverted triangle + void _drawTriangle( + Canvas canvas, + Paint paint, + Offset startPosition, + double pointerAngle, + Paint? borderPaint, + Paint? overlayPaint, + MarkerPointer markerPointer, + Color shadowColor) { + canvas.translate(startPosition.dx, startPosition.dy); + final double triangleAngle = markerPointer.markerType == MarkerType.triangle + ? pointerAngle + 90 + : pointerAngle - 90; + canvas.rotate(getDegreeToRadian(triangleAngle)); + + final Path path = Path(); + path.moveTo(-markerPointer.markerWidth / 2, markerPointer.markerHeight / 2); + path.lineTo(markerPointer.markerWidth / 2, markerPointer.markerHeight / 2); + path.lineTo(0, -markerPointer.markerHeight / 2); + path.lineTo(-markerPointer.markerWidth / 2, markerPointer.markerHeight / 2); + path.close(); + if (overlayPaint != null) { + final double overlayRadius = markerPointer.overlayRadius ?? 30; + final Path overlayPath = Path(); + if (markerPointer.overlayRadius != null) { + overlayPath.moveTo(-overlayRadius, overlayRadius); + overlayPath.lineTo(overlayRadius, overlayRadius); + overlayPath.lineTo(0, -overlayRadius); + overlayPath.lineTo(-overlayRadius, overlayRadius); + } else { + overlayPath.moveTo(-((markerPointer.markerWidth + overlayRadius) / 2), + (markerPointer.markerHeight + overlayRadius) / 2); + overlayPath.lineTo(((markerPointer.markerWidth + overlayRadius) / 2), + ((markerPointer.markerHeight + overlayRadius) / 2)); + overlayPath.lineTo( + 0, -((markerPointer.markerHeight + overlayRadius) / 2)); + overlayPath.lineTo(-((markerPointer.markerWidth + overlayRadius) / 2), + ((markerPointer.markerHeight + overlayRadius) / 2)); + } + + overlayPath.close(); + canvas.drawPath(overlayPath, overlayPaint); + } + + if (markerPointer.elevation >= 0) { + canvas.drawShadow(path, shadowColor, markerPointer.elevation, true); + } + + canvas.drawPath(path, paint); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/needle_pointer_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/needle_pointer_renderer.dart new file mode 100644 index 000000000..b6510379b --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/needle_pointer_renderer.dart @@ -0,0 +1,24 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import '../common/common.dart'; +import '../pointers/needle_pointer.dart'; + +/// The [NeedlePointerRenderer] has methods to render marker pointer +/// +class NeedlePointerRenderer { + /// Creates the instance for marker pointer renderer + NeedlePointerRenderer(); + + /// Represents the marker pointer which is corresponding to this renderer + late NeedlePointer pointer; + + /// Method to draw pointer the marker pointer. + /// + /// By overriding this method, you can draw the customized marker + /// pointer using required values. + /// + void drawPointer(Canvas canvas, PointerPaintingDetails pointerPaintingDetails, + SfGaugeThemeData gaugeThemeData) {} +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/needle_pointer_renderer_base.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/needle_pointer_renderer_base.dart new file mode 100644 index 000000000..1a79d287e --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/needle_pointer_renderer_base.dart @@ -0,0 +1,377 @@ +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import '../common/common.dart'; +import '../pointers/needle_pointer.dart'; +import '../renderers/gauge_pointer_renderer.dart'; +import '../renderers/needle_pointer_renderer.dart'; +import '../utils/helper.dart'; + +/// The [NeedlePointerRenderer] has methods to render needle pointer +class NeedlePointerRendererBase extends GaugePointerRenderer { + /// Creates the instance for needle pointer renderer + NeedlePointerRendererBase() : super(); + + /// Represents the needle pointer which is corresponding to this renderer + late NeedlePointer pointer; + + /// Represents the renderer class + NeedlePointerRenderer? renderer; + + /// Specifies the actual tail length + double _actualTailLength = 0; + + /// Specifies the actual length of the pointer based on the coordinate unit + late double _actualNeedleLength; + + /// Specifies the actual knob radius + late double _actualCapRadius; + + /// Specifies the angle of the needle pointer + late double angle; + + /// Specifies the radian value of needle pointer + late double _radian; + + /// Specifies the stop x value + late double stopX; + + /// Specifies the stop y value + late double stopY; + + /// Specifies the start left x value + late double _startLeftX; + + /// Specifies the start left y value + late double _startLeftY; + + /// Specifies the start right x value + late double _startRightX; + + /// Specifies the start right y value + late double _startRightY; + + /// Specifies the stop left x value + late double _stopLeftX; + + /// Specifies the stop left y value + late double _stopLeftY; + + /// Specifies the stop right x value + late double _stopRightX; + + /// Specifies the stop right y value + late double _stopRightY; + + /// Specifies the start x value + late double startX; + + /// Specifies the start y value + late double startY; + + /// Specifies the tail left start x value + late double _tailLeftStartX; + + /// Specifies the tail left start y value + late double _tailLeftStartY; + + /// Specifies the tail left end x value + late double _tailLeftEndX; + + /// Specifies the tail left end y value + late double _tailLeftEndY; + + /// Specifies the tail right start x value + late double _tailRightStartX; + + /// Specifies the tail right start y value + late double _tailRightStartY; + + /// Specifies the tail right end x value + late double _tailRightEndX; + + /// Specifies the tail right end y value + late double _tailRightEndY; + + /// Specified the axis center point + late Offset _centerPoint; + + /// Calculates the needle position + @override + void calculatePosition() { + final NeedlePointer needlePointer = gaugePointer as NeedlePointer; + _calculateDefaultValue(needlePointer); + _calculateNeedleOffset(needlePointer); + } + + /// Calculates the default value + void _calculateDefaultValue(NeedlePointer needlePointer) { + _actualNeedleLength = axisRenderer.getActualValue( + needlePointer.needleLength, needlePointer.lengthUnit, false); + _actualCapRadius = axisRenderer.getActualValue( + needlePointer.knobStyle.knobRadius, + needlePointer.knobStyle.sizeUnit, + false); + currentValue = getMinMax(currentValue, axis.minimum, axis.maximum); + final double currentFactor = (axis.onCreateAxisRenderer != null && + axisRenderer.renderer != null && + axisRenderer.renderer!.valueToFactor(currentValue) != null) + ? axisRenderer.renderer!.valueToFactor(currentValue) ?? + axisRenderer.valueToFactor(currentValue) + : axisRenderer.valueToFactor(currentValue); + angle = (currentFactor * axisRenderer.sweepAngle) + axis.startAngle; + _radian = getDegreeToRadian(angle); + _centerPoint = axisRenderer.axisCenter; + } + + /// Calculates the needle pointer offset + void _calculateNeedleOffset(NeedlePointer needlePointer) { + final double needleRadian = getDegreeToRadian(-90); + stopX = _actualNeedleLength * math.cos(needleRadian); + stopY = _actualNeedleLength * math.sin(needleRadian); + startX = 0; + startY = 0; + + if (needlePointer.needleEndWidth >= 0) { + _startLeftX = + startX - needlePointer.needleEndWidth * math.cos(needleRadian - 90); + _startLeftY = + startY - needlePointer.needleEndWidth * math.sin(needleRadian - 90); + _startRightX = + startX - needlePointer.needleEndWidth * math.cos(needleRadian + 90); + _startRightY = + startY - needlePointer.needleEndWidth * math.sin(needleRadian + 90); + } + + if (needlePointer.needleStartWidth >= 0) { + _stopLeftX = + stopX - needlePointer.needleStartWidth * math.cos(needleRadian - 90); + _stopLeftY = + stopY - needlePointer.needleStartWidth * math.sin(needleRadian - 90); + _stopRightX = + stopX - needlePointer.needleStartWidth * math.cos(needleRadian + 90); + _stopRightY = + stopY - needlePointer.needleStartWidth * math.sin(needleRadian + 90); + } + + _calculatePointerRect(); + if (needlePointer.tailStyle != null && needlePointer.tailStyle!.width > 0) { + _calculateTailPosition(needleRadian, needlePointer); + } + } + + /// Calculates the needle pointer rect based on + /// its start and the stop value + void _calculatePointerRect() { + double x1 = _centerPoint.dx; + double x2 = _centerPoint.dx + _actualNeedleLength * math.cos(_radian); + double y1 = _centerPoint.dy; + double y2 = _centerPoint.dy + _actualNeedleLength * math.sin(_radian); + + if (x1 > x2) { + final double temp = x1; + x1 = x2; + x2 = temp; + } + + if (y1 > y2) { + final double temp = y1; + y1 = y2; + y2 = temp; + } + + if (y2 - y1 < 20) { + y1 -= 10; // Creates the pointer rect with minimum height + y2 += 10; + } + + if (x2 - x1 < 20) { + x1 -= 10; // Creates the pointer rect with minimum width + x2 += 10; + } + + pointerRect = Rect.fromLTRB(x1, y1, x2, y2); + } + + /// Calculates the values to render the needle tail + void _calculateTailPosition( + double needleRadian, NeedlePointer needlePointer) { + final double pointerWidth = needlePointer.tailStyle!.width; + _actualTailLength = axisRenderer.getActualValue( + needlePointer.tailStyle!.length, + needlePointer.tailStyle!.lengthUnit, + false); + if (_actualTailLength > 0) { + final double tailEndX = + startX - _actualTailLength * math.cos(needleRadian); + final double tailEndY = + startY - _actualTailLength * math.sin(needleRadian); + _tailLeftStartX = startX - pointerWidth * math.cos(needleRadian - 90); + _tailLeftStartY = startY - pointerWidth * math.sin(needleRadian - 90); + _tailRightStartX = startX - pointerWidth * math.cos(needleRadian + 90); + _tailRightStartY = startY - pointerWidth * math.sin(needleRadian + 90); + + _tailLeftEndX = tailEndX - pointerWidth * math.cos(needleRadian - 90); + _tailLeftEndY = tailEndY - pointerWidth * math.sin(needleRadian - 90); + _tailRightEndX = tailEndX - pointerWidth * math.cos(needleRadian + 90); + _tailRightEndY = tailEndY - pointerWidth * math.sin(needleRadian + 90); + } + } + + /// Method to draw pointer the needle pointer. + /// + /// By overriding this method, you can draw the customized needled pointer + /// using required values. + /// + void drawPointer(Canvas canvas, PointerPaintingDetails pointerPaintingDetails, + SfGaugeThemeData gaugeThemeData) { + final NeedlePointer needlePointer = gaugePointer as NeedlePointer; + final double pointerRadian = + getDegreeToRadian(pointerPaintingDetails.pointerAngle); + if (_actualNeedleLength > 0) { + _renderNeedle(canvas, pointerRadian, gaugeThemeData, needlePointer); + } + if (_actualTailLength > 0) { + _renderTail(canvas, pointerRadian, gaugeThemeData, needlePointer); + } + _renderCap(canvas, gaugeThemeData, needlePointer); + } + + /// To render the needle of the pointer + void _renderNeedle(Canvas canvas, double pointerRadian, + SfGaugeThemeData gaugeThemeData, NeedlePointer needlePointer) { + final Paint paint = Paint() + ..color = needlePointer.needleColor ?? gaugeThemeData.needleColor + ..style = PaintingStyle.fill; + + if (renderingDetails.needsToAnimatePointers && + axis.minimum == currentValue) { + final double actualOpacity = paint.color.opacity; + final double opacity = + pointerAnimation != null ? pointerAnimation!.value : 1; + final double colorOpacity = + opacity * actualOpacity > 1.0 ? 1.0 : opacity * actualOpacity; + paint.color = paint.color.withOpacity(colorOpacity); + } + + final Path path = Path(); + path.moveTo(_startLeftX, _startLeftY); + path.lineTo(_stopLeftX, _stopLeftY); + path.lineTo(_stopRightX, _stopRightY); + path.lineTo(_startRightX, _startRightY); + path.close(); + + if (needlePointer.gradient != null) { + paint.shader = needlePointer.gradient!.createShader(path.getBounds()); + } + + canvas.save(); + canvas.translate(_centerPoint.dx, _centerPoint.dy); + canvas.rotate(pointerRadian); + canvas.drawPath(path, paint); + canvas.restore(); + } + + /// To render the tail of the pointer + void _renderTail(Canvas canvas, double pointerRadian, + SfGaugeThemeData gaugeThemeData, NeedlePointer needlePointer) { + final Path tailPath = Path(); + tailPath.moveTo(_tailLeftStartX, _tailLeftStartY); + tailPath.lineTo(_tailLeftEndX, _tailLeftEndY); + tailPath.lineTo(_tailRightEndX, _tailRightEndY); + tailPath.lineTo(_tailRightStartX, _tailRightStartY); + tailPath.close(); + + canvas.save(); + canvas.translate(_centerPoint.dx, _centerPoint.dy); + canvas.rotate(pointerRadian); + + final Paint tailPaint = Paint() + ..color = needlePointer.tailStyle!.color ?? gaugeThemeData.tailColor; + if (needlePointer.tailStyle!.gradient != null) { + tailPaint.shader = + needlePointer.tailStyle!.gradient!.createShader(tailPath.getBounds()); + } + + if (renderingDetails.needsToAnimatePointers && + axis.minimum == currentValue) { + final double actualOpacity = tailPaint.color.opacity; + final double opacity = + pointerAnimation != null ? pointerAnimation!.value : 1; + final double colorOpacity = + opacity * actualOpacity > 1.0 ? 1.0 : opacity * actualOpacity; + tailPaint.color = tailPaint.color.withOpacity(colorOpacity); + } + + canvas.drawPath(tailPath, tailPaint); + + if (needlePointer.tailStyle!.borderWidth > 0) { + final Paint tailStrokePaint = Paint() + ..color = needlePointer.tailStyle!.borderColor ?? + gaugeThemeData.tailBorderColor + ..style = PaintingStyle.stroke + ..strokeWidth = needlePointer.tailStyle!.borderWidth; + + if (renderingDetails.needsToAnimatePointers && + axis.minimum == currentValue) { + final double actualOpacity = tailStrokePaint.color.opacity; + final double opacity = + pointerAnimation != null ? pointerAnimation!.value : 1; + final double colorOpacity = + opacity * actualOpacity > 1.0 ? 1.0 : opacity * actualOpacity; + tailStrokePaint.color = tailStrokePaint.color.withOpacity(colorOpacity); + } + + canvas.drawPath(tailPath, tailStrokePaint); + } + + canvas.restore(); + } + + /// To render the cap of needle + void _renderCap(Canvas canvas, SfGaugeThemeData gaugeThemeData, + NeedlePointer needlePointer) { + if (_actualCapRadius > 0) { + final Paint knobPaint = Paint() + ..color = needlePointer.knobStyle.color ?? gaugeThemeData.knobColor; + + if (renderingDetails.needsToAnimatePointers && + axis.minimum == currentValue) { + final double actualOpacity = knobPaint.color.opacity; + final double opacity = + pointerAnimation != null ? pointerAnimation!.value : 1; + final double colorOpacity = + opacity * actualOpacity > 1.0 ? 1.0 : opacity * actualOpacity; + knobPaint.color = knobPaint.color.withOpacity(colorOpacity); + } + + canvas.drawCircle(axisRenderer.axisCenter, _actualCapRadius, knobPaint); + + if (needlePointer.knobStyle.borderWidth > 0) { + final double actualBorderWidth = axisRenderer.getActualValue( + needlePointer.knobStyle.borderWidth, + needlePointer.knobStyle.sizeUnit, + false); + final Paint strokePaint = Paint() + ..color = needlePointer.knobStyle.borderColor ?? + gaugeThemeData.knobBorderColor + ..style = PaintingStyle.stroke + ..strokeWidth = actualBorderWidth; + + if (renderingDetails.needsToAnimatePointers && + axis.minimum == currentValue) { + final double actualOpacity = strokePaint.color.opacity; + final double opacity = + pointerAnimation != null ? pointerAnimation!.value : 1; + final double colorOpacity = + opacity * actualOpacity > 1.0 ? 1.0 : opacity * actualOpacity; + strokePaint.color = strokePaint.color.withOpacity(colorOpacity); + } + + canvas.drawCircle(_centerPoint, _actualCapRadius, strokePaint); + } + } + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/radial_axis_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/radial_axis_renderer.dart new file mode 100644 index 000000000..86e425940 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/radial_axis_renderer.dart @@ -0,0 +1,54 @@ +import '../axis/gauge_axis.dart'; +import '../common/axis_label.dart'; + +/// Represents the renderer for gauge axis +abstract class GaugeAxisRenderer { + /// Represents the gauge axis + late GaugeAxis axis; + + /// Returns the visible labels on [GaugeAxis] + /// + /// Modify the actual labels generated, which are calculated on the basis + /// of scale range and interval. + /// Generate your own labels based on needs, in order to be shown in + /// the gauge. + List? generateVisibleLabels(); + + /// Returns converted factor value from the axis value. + /// + /// The arguments to this method is axis value. + /// The calculated value of the factor should be between 0 and 1. + /// If the axis range from 0 to 100 and pass the axis value is 50, + /// this method return factor value is 0.5. + /// Overriding method, you can modify the factor value based on needs. + double? valueToFactor(double value); + + /// Returns converted axis value from the factor. + /// + /// The arguments to this method is factor which value between 0 to 1. + /// If the axis range from 0 to 100 and pass the factor value is 0.5, + /// this method return axis value is 50. + /// Overriding method, you can modify the axis value based on needs. + double? factorToValue(double factor); +} + +/// Represents the renderer for radial axis +class RadialAxisRenderer extends GaugeAxisRenderer { + /// Creates the instance for radial axis renderer + RadialAxisRenderer() : super(); + + @override + List? generateVisibleLabels() { + return null; + } + + @override + double? valueToFactor(double value) { + return null; + } + + @override + double? factorToValue(double factor) { + return null; + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/radial_axis_renderer_base.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/radial_axis_renderer_base.dart new file mode 100644 index 000000000..a4334154d --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/radial_axis_renderer_base.dart @@ -0,0 +1,1283 @@ +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import '../axis/radial_axis.dart'; +import '../common/axis_label.dart'; +import '../common/common.dart'; +import '../common/radial_gauge_renderer.dart'; +import '../renderers/gauge_pointer_renderer.dart'; +import '../renderers/gauge_range_renderer.dart'; +import '../renderers/radial_axis_renderer.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Represents the renderer for radial axis +class RadialAxisRendererBase { + /// Creates the instance for radial axis renderer + RadialAxisRendererBase() { + needsRepaintAxis = true; + } + + /// Specifies the radial axis renderer + RadialAxisRenderer? renderer; + + /// Specifies whether to include axis elements when calculating the radius + final bool _useAxisElementsInsideRadius = true; + + /// Specifies the axis corresponding to this renderer; + late RadialAxis axis; + + ///Specifies the axis rect + late Rect axisRect; + + /// Specifies the start radian value + late double startRadian; + + /// Specifies the end radian value + late double endRadian; + + ///Specifies the radius value + late double radius; + + ///Specifies the axis center + late double _center; + + ///Specifies the center X value of axis + late double centerX; + + ///Specifies the center Y value od axis + late double centerY; + + /// Specifies the actual axis width + late double actualAxisWidth; + + /// Specifies the list of axis labels + List? axisLabels; + + /// Specifies the offset value of major ticks + late List majorTickOffsets; + + /// Specifies the offset value of minor ticks + late List minorTickOffsets; + + /// Specifies the major tick count + late num _majorTicksCount; + + ///Holds the sweep angle of the axis + late double sweepAngle; + + /// Holds the size of the axis + late Size axisSize; + + /// Holds the length of major tick based on coordinate unit + late double actualMajorTickLength; + + /// Holds the length of minor tick based on coordinate unit + late double actualMinorTickLength; + + /// Specifies the maximum label size + late Size _maximumLabelSize; + + /// Specifies whether the ticks are placed outside + late bool _isTicksOutside; + + /// Specifies whether the labels are placed outside + late bool _isLabelsOutside; + + /// Specifies the maximum length of tick by comparing major and minor tick + late double _maximumTickLength; + + /// Specifies whether to repaint the axis; + late bool needsRepaintAxis; + + /// Specifies the axis path + late Path axisPath; + + /// Specifies the axis offset + late double axisOffset; + + /// Specifies the start corner radian + late double startCornerRadian; + + /// Specifies the sweep corner radian + late double sweepCornerRadian; + + /// Specifies the actual label offset + late double _actualLabelOffset; + + /// Specifies the actual tick offset + late double _actualTickOffset; + + /// Specifies the corner angle + late double cornerAngle; + + /// Specifies the listener + ImageStreamListener? listener; + + /// Specifies the background image info; + ImageInfo? backgroundImageInfo; + + /// Specifies the image stream + ImageStream? imageStream; + + /// Specifies the difference in the radius + late double _diffInRadius; + + /// Specifies the center point of the axis + late Offset axisCenter; + + /// Specifies the rendering details corresponding to the gauge. + late RenderingDetails renderingDetails; + + /// Specifies the actual interval of the axis + num? _actualInterval; + + /// Specifies whether the maximum value is included in axis labels + late bool isMaxiumValueIncluded = false; + + /// To calculate the radius and the center point based on the angle + Offset _getAxisBounds() { + final Offset centerOffset = _getCenter(); + final double minScale = math.min(axisSize.height, axisSize.width); + final double x = ((centerOffset.dx * 2) - minScale) / 2; + final double y = ((centerOffset.dy * 2) - minScale) / 2; + Rect bounds = Rect.fromLTRB(x, y, minScale, minScale); + final double centerYDiff = (axisSize.height / 2 - centerOffset.dy).abs(); + final double centerXDiff = (axisSize.width / 2 - centerOffset.dx).abs(); + double diff = 0; + if (axisSize.width > axisSize.height) { + diff = centerYDiff / 2; + final double angleRadius = axisSize.height / 2 + diff; + if (axisSize.width / 2 < angleRadius) { + final double actualDiff = axisSize.width / 2 - axisSize.height / 2; + diff = actualDiff * 0.7; + } + + bounds = Rect.fromLTRB( + x - diff / 2, y, x + minScale + (diff / 2), y + minScale + diff); + } else { + diff = centerXDiff / 2; + final double angleRadius = axisSize.width / 2 + diff; + + if (axisSize.height / 2 < angleRadius) { + final double actualDiff = axisSize.height / 2 - axisSize.width / 2; + diff = actualDiff * 0.7; + } + + bounds = Rect.fromLTRB(x - diff / 2, y - diff / 2, + x + minScale + (diff / 2), y + minScale + (diff / 2)); + } + + _diffInRadius = diff; + + return Offset( + bounds.left + (bounds.width / 2), bounds.top + (bounds.height / 2)); + } + + /// Calculates the default values of the axis + void _calculateDefaultValues() { + startRadian = getDegreeToRadian(axis.startAngle); + sweepAngle = _getSweepAngle(); + endRadian = getDegreeToRadian(sweepAngle); + _center = math.min(axisSize.width / 2, axisSize.height / 2); + + if (!axis.canScaleToFit) { + radius = _center * axis.radiusFactor; + centerX = (axisSize.width / 2) - (axis.centerX * axisSize.width); + centerY = (axisSize.height / 2) - (axis.centerY * axisSize.height); + axisCenter = + Offset(axisSize.width / 2 - centerX, axisSize.height / 2 - centerY); + } else { + final Offset centerPoint = _getAxisBounds(); + centerX = centerPoint.dx; + centerY = centerPoint.dy; + radius = (_center + _diffInRadius) * axis.radiusFactor; + axisCenter = Offset(centerX, centerY); + } + + actualAxisWidth = getActualValue( + axis.axisLineStyle.thickness, axis.axisLineStyle.thicknessUnit, false); + actualMajorTickLength = _getTickLength(true); + actualMinorTickLength = _getTickLength(false); + _maximumTickLength = actualMajorTickLength > actualMinorTickLength + ? actualMajorTickLength + : actualMinorTickLength; + _actualLabelOffset = + getActualValue(axis.labelOffset, axis.offsetUnit, true); + _actualTickOffset = getActualValue(axis.tickOffset, axis.offsetUnit, true); + if (axis.backgroundImage != null) { + listener = ImageStreamListener(_updateBackgroundImage); + } + } + + /// Method to calculate the axis range + void calculateAxisRange(BoxConstraints constraints, BuildContext context, + SfGaugeThemeData gaugeThemeData, RenderingDetails animationDetails) { + renderingDetails = animationDetails; + axisSize = Size(constraints.maxWidth, constraints.maxHeight); + _calculateAxisElementsPosition(context); + if (axis.pointers != null && axis.pointers!.isNotEmpty) { + _renderPointers(); + } + + if (axis.ranges != null && axis.ranges!.isNotEmpty) { + _renderRanges(); + } + } + + /// Methods to calculate axis elements position + void _calculateAxisElementsPosition(BuildContext context) { + _isTicksOutside = axis.ticksPosition == ElementsPosition.outside; + _isLabelsOutside = axis.labelsPosition == ElementsPosition.outside; + _calculateDefaultValues(); + axisLabels = (axis.onCreateAxisRenderer != null && + renderer != null && + renderer!.generateVisibleLabels() != null) + ? renderer!.generateVisibleLabels() ?? generateVisibleLabels() + : generateVisibleLabels(); + if (axis.showLabels) { + _measureAxisLabels(); + } + + axisOffset = _useAxisElementsInsideRadius ? getAxisOffset() : 0; + + if (axis.showTicks) { + _calculateMajorTicksPosition(); + _calculateMinorTickPosition(); + } + + if (axis.showLabels) { + _calculateAxisLabelsPosition(); + } + + _calculateAxisRect(); + if (axis.showAxisLine) { + _calculateCornerStylePosition(); + } + + if (axis.backgroundImage != null && backgroundImageInfo?.image == null) { + _loadBackgroundImage(context); + } + } + + /// To calculate the center based on the angle + Offset _getCenter() { + final double x = axisSize.width / 2; + final double y = axisSize.height / 2; + radius = _center; + Offset actualCenter = Offset(x, y); + final double actualStartAngle = _getWrapAngle(axis.startAngle, -630, 630); + final double actualEndAngle = + _getWrapAngle(axis.startAngle + sweepAngle.abs(), -630, 630); + final List regions = [ + -630, + -540, + -450, + -360, + -270, + -180, + -90, + 0, + 90, + 180, + 270, + 360, + 450, + 540, + 630 + ]; + final List region = []; + if (actualStartAngle < actualEndAngle) { + for (int i = 0; i < regions.length; i++) { + if (regions[i] > actualStartAngle && regions[i] < actualEndAngle) { + region.add(((regions[i] % 360) < 0 + ? (regions[i] % 360) + 360 + : (regions[i] % 360)) + .toInt()); + } + } + } else { + for (int i = 0; i < regions.length; i++) { + if (regions[i] < actualStartAngle && regions[i] > actualEndAngle) { + region.add(((regions[i] % 360) < 0 + ? (regions[i] % 360) + 360 + : (regions[i] % 360)) + .toInt()); + } + } + } + + final double startRadian = 2 * math.pi * (actualStartAngle / 360); + final double endRadian = 2 * math.pi * (actualEndAngle / 360); + final Offset startPoint = Offset(x + (radius * math.cos(startRadian)), + y + (radius * math.sin(startRadian))); + final Offset endPoint = Offset( + x + (radius * math.cos(endRadian)), y + (radius * math.sin(endRadian))); + + switch (region.length) { + case 0: + actualCenter = + _getCenterForLengthZero(startPoint, endPoint, x, y, radius, region); + break; + case 1: + actualCenter = + _getCenterLengthOne(startPoint, endPoint, x, y, radius, region); + break; + case 2: + actualCenter = + _getCenterForLengthTwo(startPoint, endPoint, x, y, radius, region); + break; + case 3: + actualCenter = _getCenterForLengthThree( + startPoint, endPoint, x, y, radius, region); + break; + } + + return actualCenter; + } + + /// Calculate the center point when the region length is zero + Offset _getCenterForLengthZero(Offset startPoint, Offset endPoint, double x, + double y, double radius, List region) { + final double longX = (x - startPoint.dx).abs() > (x - endPoint.dx).abs() + ? startPoint.dx + : endPoint.dx; + final double longY = (y - startPoint.dy).abs() > (y - endPoint.dy).abs() + ? startPoint.dy + : endPoint.dy; + final Offset midPoint = + Offset((x + longX).abs() / 2, (y + longY).abs() / 2); + final double xValue = x + (x - midPoint.dx); + final double yValue = y + (y - midPoint.dy); + return Offset(xValue, yValue); + } + + ///Calculates the center when the region length is two. + Offset _getCenterLengthOne(Offset startPoint, Offset endPoint, double x, + double y, double radius, List region) { + Offset point1 = Offset(0, 0); + Offset point2 = Offset(0, 0); + final double maxRadian = 2 * math.pi * region[0] / 360; + final Offset maxPoint = Offset( + x + (radius * math.cos(maxRadian)), y + (radius * math.sin(maxRadian))); + + switch (region[0]) { + case 270: + point1 = Offset(startPoint.dx, maxPoint.dy); + point2 = Offset(endPoint.dx, y); + break; + case 0: + case 360: + point1 = Offset(x, endPoint.dy); + point2 = Offset(maxPoint.dx, startPoint.dy); + break; + case 90: + point1 = Offset(endPoint.dx, y); + point2 = Offset(startPoint.dx, maxPoint.dy); + break; + case 180: + point1 = Offset(maxPoint.dx, startPoint.dy); + point2 = Offset(x, endPoint.dy); + break; + } + + final Offset midPoint = + Offset((point1.dx + point2.dx) / 2, (point1.dy + point2.dy) / 2); + final double xValue = + x + ((x - midPoint.dx) >= radius ? 0 : (x - midPoint.dx)); + final double yValue = + y + ((y - midPoint.dy) >= radius ? 0 : (y - midPoint.dy)); + return Offset(xValue, yValue); + } + + ///Calculates the center when the region length is two. + Offset _getCenterForLengthTwo(Offset startPoint, Offset endPoint, double x, + double y, double radius, List region) { + Offset point1; + Offset point2; + final double minRadian = 2 * math.pi * region[0] / 360; + final double maxRadian = 2 * math.pi * region[1] / 360; + final Offset maxPoint = Offset( + x + (radius * math.cos(maxRadian)), y + (radius * math.sin(maxRadian))); + final Offset minPoint = Offset( + x + (radius * math.cos(minRadian)), y + (radius * math.sin(minRadian))); + + if ((region[0] == 0 && region[1] == 90) || + (region[0] == 180 && region[1] == 270)) { + point1 = Offset(minPoint.dx, maxPoint.dy); + } else { + point1 = Offset(maxPoint.dx, minPoint.dy); + } + + if (region[0] == 0 || region[0] == 180) { + point2 = Offset(_getMinMaxValue(startPoint, endPoint, region[0]), + _getMinMaxValue(startPoint, endPoint, region[1])); + } else { + point2 = Offset(_getMinMaxValue(startPoint, endPoint, region[1]), + _getMinMaxValue(startPoint, endPoint, region[0])); + } + + final Offset midPoint = Offset( + (point1.dx - point2.dx).abs() / 2 >= radius + ? 0 + : (point1.dx + point2.dx) / 2, + (point1.dy - point2.dy).abs() / 2 >= radius + ? 0 + : (point1.dy + point2.dy) / 2); + final double xValue = x + + (midPoint.dx == 0 + ? 0 + : (x - midPoint.dx) >= radius + ? 0 + : (x - midPoint.dx)); + final double yValue = y + + (midPoint.dy == 0 + ? 0 + : (y - midPoint.dy) >= radius + ? 0 + : (y - midPoint.dy)); + return Offset(xValue, yValue); + } + + ///Calculates the center when the region length is three. + Offset _getCenterForLengthThree(Offset startPoint, Offset endPoint, double x, + double y, double radius, List region) { + final double region0Radian = 2 * math.pi * region[0] / 360; + final double region1Radian = 2 * math.pi * region[1] / 360; + final double region2Radian = 2 * math.pi * region[2] / 360; + final Offset region0Point = Offset(x + (radius * math.cos(region0Radian)), + y + (radius * math.sin(region0Radian))); + final Offset region1Point = Offset(x + (radius * math.cos(region1Radian)), + y + (radius * math.sin(region1Radian))); + final Offset region2Point = Offset(x + (radius * math.cos(region2Radian)), + y + (radius * math.sin(region2Radian))); + Offset regionStartPoint = Offset(0, 0); + Offset regionEndPoint = Offset(0, 0); + switch (region[2]) { + case 0: + case 360: + regionStartPoint = Offset(region0Point.dx, region1Point.dy); + regionEndPoint = + Offset(region2Point.dx, math.max(startPoint.dy, endPoint.dy)); + break; + case 90: + regionStartPoint = + Offset(math.min(startPoint.dx, endPoint.dx), region0Point.dy); + regionEndPoint = Offset(region1Point.dx, region2Point.dy); + break; + case 180: + regionStartPoint = + Offset(region2Point.dx, math.min(startPoint.dy, endPoint.dy)); + regionEndPoint = Offset(region0Point.dx, region1Point.dy); + break; + case 270: + regionStartPoint = Offset(region1Point.dx, region2Point.dy); + regionEndPoint = + Offset(math.max(startPoint.dx, endPoint.dx), region0Point.dy); + break; + } + + final Offset midRegionPoint = Offset( + (regionStartPoint.dx - regionEndPoint.dx).abs() / 2 >= radius + ? 0 + : (regionStartPoint.dx + regionEndPoint.dx) / 2, + (regionStartPoint.dy - regionEndPoint.dy).abs() / 2 >= radius + ? 0 + : (regionStartPoint.dy + regionEndPoint.dy) / 2); + final double xValue = x + + (midRegionPoint.dx == 0 + ? 0 + : (x - midRegionPoint.dx) >= radius + ? 0 + : (x - midRegionPoint.dx)); + final double yValue = y + + (midRegionPoint.dy == 0 + ? 0 + : (y - midRegionPoint.dy) >= radius + ? 0 + : (y - midRegionPoint.dy)); + return Offset(xValue, yValue); + } + + /// To calculate the value based on the angle + double _getMinMaxValue(Offset startPoint, Offset endPoint, int degree) { + final double minX = math.min(startPoint.dx, endPoint.dx); + final double minY = math.min(startPoint.dy, endPoint.dy); + final double maxX = math.max(startPoint.dx, endPoint.dx); + final double maxY = math.max(startPoint.dy, endPoint.dy); + switch (degree) { + case 270: + return maxY; + case 0: + case 360: + return minX; + case 90: + return minY; + case 180: + return maxX; + } + + return 0; + } + + /// To calculate the wrap angle + double _getWrapAngle(double angle, double min, double max) { + if (max - min == 0) { + return min; + } + + angle = ((angle - min) % (max - min)) + min; + while (angle < min) { + angle += max - min; + } + + return angle; + } + + /// Calculates the rounded corner position + void _calculateCornerStylePosition() { + final double cornerCenter = (axisRect.right - axisRect.left) / 2; + cornerAngle = cornerRadiusAngle(cornerCenter, actualAxisWidth / 2); + + switch (axis.axisLineStyle.cornerStyle) { + case CornerStyle.startCurve: + { + startCornerRadian = axis.isInversed + ? getDegreeToRadian(-cornerAngle) + : getDegreeToRadian(cornerAngle); + sweepCornerRadian = axis.isInversed + ? getDegreeToRadian((-sweepAngle) + cornerAngle) + : getDegreeToRadian(sweepAngle - cornerAngle); + } + break; + case CornerStyle.endCurve: + { + startCornerRadian = getDegreeToRadian(0); + sweepCornerRadian = axis.isInversed + ? getDegreeToRadian((-sweepAngle) + cornerAngle) + : getDegreeToRadian(sweepAngle - cornerAngle); + } + break; + case CornerStyle.bothCurve: + { + startCornerRadian = axis.isInversed + ? getDegreeToRadian(-cornerAngle) + : getDegreeToRadian(cornerAngle); + sweepCornerRadian = axis.isInversed + ? getDegreeToRadian((-sweepAngle) + (2 * cornerAngle)) + : getDegreeToRadian(sweepAngle - (2 * cornerAngle)); + } + break; + case CornerStyle.bothFlat: + startCornerRadian = !axis.isInversed + ? getDegreeToRadian(0) + : getDegreeToRadian(axis.startAngle + sweepAngle); + final double _value = axis.isInversed ? -1 : 1; + sweepCornerRadian = getDegreeToRadian(sweepAngle * _value); + break; + } + } + + /// Calculates the axis rect + void _calculateAxisRect() { + axisRect = Rect.fromLTRB( + -(radius - (actualAxisWidth / 2 + axisOffset)), + -(radius - (actualAxisWidth / 2 + axisOffset)), + radius - (actualAxisWidth / 2 + axisOffset), + radius - (actualAxisWidth / 2 + axisOffset)); + axisPath = Path(); + final Rect rect = Rect.fromLTRB( + axisRect.left + axisSize.width / 2, + axisRect.top + axisSize.height / 2, + axisRect.right + axisSize.width / 2, + axisRect.bottom + axisSize.height / 2, + ); + axisPath.arcTo(rect, startRadian, endRadian, false); + } + + /// Method to calculate the angle from the tapped point + void calculateAngleFromOffset(Offset offset) { + final double actualCenterX = axisSize.width * axis.centerX; + final double actualCenterY = axisSize.height * axis.centerY; + double angle = + math.atan2(offset.dy - actualCenterY, offset.dx - actualCenterX) * + (180 / math.pi) + + 360; + final double actualEndAngle = axis.startAngle + sweepAngle; + if (angle < 360 && angle > 180) { + angle += 360; + } + + if (angle > actualEndAngle) { + angle %= 360; + } + + if (angle >= axis.startAngle && angle <= actualEndAngle) { + final double angleFactor = (angle - axis.startAngle) / sweepAngle; + final double value = (axis.onCreateAxisRenderer != null && + renderer != null && + renderer!.factorToValue(angleFactor) != null) + ? renderer!.factorToValue(angleFactor) ?? factorToValue(angleFactor) + : factorToValue(angleFactor); + if (value >= axis.minimum && value <= axis.maximum) { + final double _tappedValue = _angleToValue(angle); + axis.onAxisTapped!(_tappedValue); + } + } + } + + /// Calculate the offset for axis line based on ticks and labels + double getAxisOffset() { + double offset = 0; + offset = _isTicksOutside + ? axis.showTicks + ? (_maximumTickLength + _actualTickOffset) + : 0 + : 0; + offset += _isLabelsOutside + ? axis.showLabels + ? (math.max(_maximumLabelSize.height, _maximumLabelSize.width) / 2 + + _actualLabelOffset) + : 0 + : 0; + return offset; + } + + /// Converts the axis value to angle + double _valueToAngle(double value) { + double angle = 0; + value = getMinMax(value, axis.minimum, axis.maximum); + if (!axis.isInversed) { + angle = (sweepAngle / (axis.maximum - axis.minimum).abs()) * + (axis.minimum - value).abs(); + } else { + angle = sweepAngle - + ((sweepAngle / (axis.maximum - axis.minimum).abs()) * + (axis.minimum - value).abs()); + } + + return angle; + } + + /// Converts the angle to corresponding axis value + double _angleToValue(double angle) { + double value = 0; + if (!axis.isInversed) { + value = (((angle - axis.startAngle) / sweepAngle) * + (axis.maximum - axis.minimum)) + + axis.minimum; + } else { + value = (axis.maximum - + (((angle - axis.startAngle) / sweepAngle) * + (axis.maximum - axis.minimum))); + } + + return value; + } + + /// Calculates the major ticks position + void _calculateMajorTicksPosition() { + if (axisLabels != null && axisLabels!.isNotEmpty) { + double angularSpaceForTicks; + if (_actualInterval != null) { + _majorTicksCount = (axis.maximum - axis.minimum) / _actualInterval!; + angularSpaceForTicks = + getDegreeToRadian(sweepAngle / (_majorTicksCount)); + } else { + _majorTicksCount = axisLabels!.length; + angularSpaceForTicks = + getDegreeToRadian(sweepAngle / (_majorTicksCount - 1)); + } + + final double axisLineWidth = axis.showAxisLine ? actualAxisWidth : 0; + double angleForTicks = 0; + double tickStartOffset = 0; + double tickEndOffset = 0; + majorTickOffsets = []; + angleForTicks = axis.isInversed + ? getDegreeToRadian(axis.startAngle + sweepAngle - 90) + : getDegreeToRadian(axis.startAngle - 90); + final double offset = _isLabelsOutside + ? axis.showLabels + ? (math.max(_maximumLabelSize.height, _maximumLabelSize.width) / + 2 + + _actualLabelOffset) + : 0 + : 0; + if (!_isTicksOutside) { + tickStartOffset = radius - (axisLineWidth + _actualTickOffset + offset); + tickEndOffset = radius - + (axisLineWidth + + actualMajorTickLength + + _actualTickOffset + + offset); + } else { + final bool isGreater = actualMajorTickLength > actualMinorTickLength; + + // Calculates the major tick position based on the tick length + // and another features offset value + if (!_useAxisElementsInsideRadius) { + tickStartOffset = radius + _actualTickOffset; + tickEndOffset = radius + actualMajorTickLength + _actualTickOffset; + } else { + tickStartOffset = isGreater + ? radius - offset + : radius - (_maximumTickLength - actualMajorTickLength + offset); + tickEndOffset = radius - (offset + _maximumTickLength); + } + } + + _calculateOffsetForMajorTicks( + tickStartOffset, tickEndOffset, angularSpaceForTicks, angleForTicks); + } + } + + /// Calculates the offset for major ticks + void _calculateOffsetForMajorTicks(double tickStartOffset, + double tickEndOffset, double angularSpaceForTicks, double angleForTicks) { + final num length = + _actualInterval != null ? _majorTicksCount : _majorTicksCount - 1; + for (num i = 0; i <= length; i++) { + double tickAngle = 0; + final num count = + _actualInterval != null ? _majorTicksCount : _majorTicksCount - 1; + if (i == 0 || i == count) { + tickAngle = + _getTickPositionInCorner(i, angleForTicks, tickStartOffset, true); + } else { + tickAngle = angleForTicks; + } + final List tickPosition = + _getTickPosition(tickStartOffset, tickEndOffset, tickAngle); + final TickOffset tickOffset = TickOffset(); + tickOffset.startPoint = tickPosition[0]; + tickOffset.endPoint = tickPosition[1]; + final double degree = (axis.isInversed + ? getRadianToDegree(tickAngle) + + 90 - + (axis.startAngle + sweepAngle) + : (getRadianToDegree(tickAngle) + 90 - axis.startAngle)) / + sweepAngle; + tickOffset.value = (axis.onCreateAxisRenderer != null && + renderer != null && + renderer!.factorToValue(degree) != null) + ? renderer!.factorToValue(degree) ?? factorToValue(degree) + : factorToValue(degree); + final Offset centerPoint = + !axis.canScaleToFit ? Offset(centerX, centerY) : const Offset(0, 0); + tickOffset.startPoint = Offset(tickOffset.startPoint.dx - centerPoint.dx, + tickOffset.startPoint.dy - centerPoint.dy); + tickOffset.endPoint = Offset(tickOffset.endPoint.dx - centerPoint.dx, + tickOffset.endPoint.dy - centerPoint.dy); + majorTickOffsets.add(tickOffset); + if (axis.isInversed) { + angleForTicks -= angularSpaceForTicks; + } else { + angleForTicks += angularSpaceForTicks; + } + } + } + + /// Returns the corresponding range color for the value + Color? getRangeColor(num value, SfGaugeThemeData gaugeThemeData) { + Color? color; + if (axis.ranges != null && axis.ranges!.isNotEmpty) { + for (int i = 0; i < axis.ranges!.length; i++) { + if (axis.ranges![i].startValue <= value.roundToDouble() && + axis.ranges![i].endValue >= value.roundToDouble()) { + color = axis.ranges![i].color ?? gaugeThemeData.rangeColor; + break; + } + } + } + return color; + } + + /// Calculates the angle to adjust the start and end tick + double _getTickPositionInCorner( + num num, double angleForTicks, double startOffset, bool isMajor) { + final double thickness = + isMajor ? axis.majorTickStyle.thickness : axis.minorTickStyle.thickness; + final double angle = + cornerRadiusAngle(startOffset + actualAxisWidth / 2, thickness / 2); + if (num == 0) { + final double ticksAngle = !axis.isInversed + ? getRadianToDegree(angleForTicks) + angle + : getRadianToDegree(angleForTicks) - angle; + return getDegreeToRadian(ticksAngle); + } else { + final double ticksAngle = !axis.isInversed + ? getRadianToDegree(angleForTicks) - angle + : getRadianToDegree(angleForTicks) + angle; + return getDegreeToRadian(ticksAngle); + } + } + + /// Calculates the minor tick position + void _calculateMinorTickPosition() { + if (axisLabels != null && axisLabels!.isNotEmpty) { + final double axisLineWidth = axis.showAxisLine ? actualAxisWidth : 0; + double tickStartOffset = 0; + double tickEndOffset = 0; + final double offset = _isLabelsOutside + ? axis.showLabels + ? (_actualLabelOffset + + math.max(_maximumLabelSize.height, _maximumLabelSize.width) / + 2) + : 0 + : 0; + if (!_isTicksOutside) { + tickStartOffset = radius - (axisLineWidth + _actualTickOffset + offset); + tickEndOffset = radius - + (axisLineWidth + + actualMinorTickLength + + _actualTickOffset + + offset); + } else { + final bool isGreater = actualMinorTickLength > actualMajorTickLength; + if (!_useAxisElementsInsideRadius) { + tickStartOffset = radius + _actualTickOffset; + tickEndOffset = radius + actualMinorTickLength + _actualTickOffset; + } else { + tickStartOffset = isGreater + ? radius - offset + : radius - (_maximumTickLength - actualMinorTickLength + offset); + tickEndOffset = radius - (_maximumTickLength + offset); + } + } + + _calculateOffsetForMinorTicks(tickStartOffset, tickEndOffset); + } + } + + /// Calculates the offset for minor ticks + /// + /// This method is quite a long method. This method could be refactored into + /// the smaller method but it leads to passing more number of parameter and + /// which degrades the performance + void _calculateOffsetForMinorTicks( + double tickStartOffset, double tickEndOffset) { + minorTickOffsets = []; + double angularSpaceForTicks; + double totalMinorTicks; + if (_actualInterval != null) { + final double majorTicksInterval = + (axis.maximum - axis.minimum) / _actualInterval!; + angularSpaceForTicks = getDegreeToRadian(sweepAngle / majorTicksInterval); + final double maximumLabelValue = + axisLabels![axisLabels!.length - 2].value.toDouble(); + int remainingTicks; + final double difference = (axis.maximum - maximumLabelValue); + if (difference == _actualInterval) { + remainingTicks = 0; + } else { + final double minorTickInterval = + ((_actualInterval! / 2) / axis.minorTicksPerInterval); + remainingTicks = difference ~/ minorTickInterval; + } + + final int labelLength = difference == _actualInterval + ? axisLabels!.length - 1 + : axisLabels!.length - 2; + totalMinorTicks = + (labelLength * axis.minorTicksPerInterval) + remainingTicks; + } else { + angularSpaceForTicks = + getDegreeToRadian(sweepAngle / (_majorTicksCount - 1)); + totalMinorTicks = (axisLabels!.length - 1) * axis.minorTicksPerInterval; + } + + double angleForTicks = axis.isInversed + ? getDegreeToRadian(axis.startAngle + sweepAngle - 90) + : getDegreeToRadian(axis.startAngle - 90); + + const num minorTickIndex = 1; // Since the minor tick rendering + // needs to be start in the index one + final double minorTickAngle = + angularSpaceForTicks / (axis.minorTicksPerInterval + 1); + + for (num i = minorTickIndex; i <= totalMinorTicks; i++) { + if (axis.isInversed) { + angleForTicks -= minorTickAngle; + } else { + angleForTicks += minorTickAngle; + } + + final double factor = (axis.isInversed + ? getRadianToDegree(angleForTicks) + + 90 - + (axis.startAngle + sweepAngle) + : (getRadianToDegree(angleForTicks) + 90 - axis.startAngle)) / + sweepAngle; + + final double tickFactor = + (axis.onCreateAxisRenderer != null && renderer != null) + ? renderer!.factorToValue(factor) ?? factorToValue(factor) + : factorToValue(factor); + final double tickValue = double.parse(tickFactor.toStringAsFixed(5)); + if (tickValue <= axis.maximum && tickValue >= axis.minimum) { + if (tickValue == axis.maximum) { + angleForTicks = _getTickPositionInCorner( + i, angleForTicks, tickStartOffset, false); + } + final List tickPosition = + _getTickPosition(tickStartOffset, tickEndOffset, angleForTicks); + final TickOffset tickOffset = TickOffset(); + tickOffset.startPoint = tickPosition[0]; + tickOffset.endPoint = tickPosition[1]; + tickOffset.value = tickValue; + + final Offset centerPoint = + !axis.canScaleToFit ? Offset(centerX, centerY) : const Offset(0, 0); + tickOffset.startPoint = Offset( + tickOffset.startPoint.dx - centerPoint.dx, + tickOffset.startPoint.dy - centerPoint.dy); + tickOffset.endPoint = Offset(tickOffset.endPoint.dx - centerPoint.dx, + tickOffset.endPoint.dy - centerPoint.dy); + minorTickOffsets.add(tickOffset); + if (i % axis.minorTicksPerInterval == 0) { + if (axis.isInversed) { + angleForTicks -= minorTickAngle; + } else { + angleForTicks += minorTickAngle; + } + } + } + } + } + + /// Calculate the axis label position + void _calculateAxisLabelsPosition() { + if (axisLabels != null && axisLabels!.isNotEmpty) { + // Calculates the angle between each axis label + double labelsInterval; + if (_actualInterval != null) { + labelsInterval = (axis.maximum - axis.minimum) / _actualInterval!; + } else { + labelsInterval = (axisLabels!.length - 1).toDouble(); + } + final double labelSpaceInAngle = sweepAngle / labelsInterval; + final double labelSpaceInRadian = getDegreeToRadian(labelSpaceInAngle); + final double tickLength = actualMajorTickLength > actualMinorTickLength + ? actualMajorTickLength + : actualMinorTickLength; + final double tickPadding = + axis.showTicks ? tickLength + _actualTickOffset : 0; + double labelRadian = 0; + double labelAngle = 0; + double labelPosition = 0; + if (!axis.isInversed) { + labelAngle = axis.startAngle - 90; + labelRadian = getDegreeToRadian(labelAngle); + } else { + labelAngle = axis.startAngle + sweepAngle - 90; + labelRadian = getDegreeToRadian(labelAngle); + } + + final double labelSize = + math.max(_maximumLabelSize.height, _maximumLabelSize.width) / 2; + if (_isLabelsOutside) { + final double featureOffset = labelSize; + labelPosition = _useAxisElementsInsideRadius + ? radius - featureOffset + : radius + tickPadding + _actualLabelOffset; + } else { + labelPosition = + radius - (actualAxisWidth + tickPadding + _actualLabelOffset); + } + + _calculateLabelPosition(labelPosition, labelRadian, labelAngle, + labelSpaceInRadian, labelSpaceInAngle); + } + } + + // Method to calculate label position + void _calculateLabelPosition(double labelPosition, double labelRadian, + double labelAngle, double labelSpaceInRadian, double labelSpaceInAngle) { + for (int i = 0; i < axisLabels!.length; i++) { + final CircularAxisLabel label = axisLabels![i]; + label.angle = labelAngle; + if (isMaxiumValueIncluded && i == axisLabels!.length - 1) { + labelAngle = axis.isInversed + ? axis.startAngle - 90 + : axis.startAngle + sweepAngle - 90; + label.value = axis.maximum; + label.angle = labelAngle; + labelRadian = getDegreeToRadian(labelAngle); + } else { + final double coordinateValue = (axis.isInversed + ? labelAngle + 90 - (axis.startAngle + sweepAngle) + : (labelAngle + 90 - axis.startAngle)) / + sweepAngle; + label.value = (axis.onCreateAxisRenderer != null && + renderer != null && + renderer!.factorToValue(coordinateValue) != null) + ? renderer!.factorToValue(coordinateValue) ?? + factorToValue(coordinateValue) + : factorToValue(coordinateValue); + } + + if (!axis.canScaleToFit) { + final double x = + ((axisSize.width / 2) - (labelPosition * math.sin(labelRadian))) - + centerX; + final double y = + ((axisSize.height / 2) + (labelPosition * math.cos(labelRadian))) - + centerY; + label.position = Offset(x, y); + } else { + final double x = + axisCenter.dx - (labelPosition * math.sin(labelRadian)); + final double y = + axisCenter.dy + (labelPosition * math.cos(labelRadian)); + label.position = Offset(x, y); + } + + if (!axis.isInversed) { + labelRadian += labelSpaceInRadian; + labelAngle += labelSpaceInAngle; + } else { + labelRadian -= labelSpaceInRadian; + labelAngle -= labelSpaceInAngle; + } + } + } + + /// To find the maximum label size + void _measureAxisLabels() { + _maximumLabelSize = const Size(0, 0); + for (int i = 0; i < axisLabels!.length; i++) { + final CircularAxisLabel label = axisLabels![i]; + label.labelSize = getTextSize(label.text, label.labelStyle); + final double maxWidth = _maximumLabelSize.width < label.labelSize.width + ? label.needsRotateLabel + ? label.labelSize.height + : label.labelSize.width + : _maximumLabelSize.width; + final double maxHeight = _maximumLabelSize.height < label.labelSize.height + ? label.labelSize.height + : _maximumLabelSize.height; + + _maximumLabelSize = Size(maxWidth, maxHeight); + } + } + + /// Gets the start and end offset of tick + List _getTickPosition( + double tickStartOffset, double tickEndOffset, double angleForTicks) { + final Offset centerPoint = !axis.canScaleToFit + ? Offset(axisSize.width / 2, axisSize.height / 2) + : axisCenter; + final double tickStartX = + centerPoint.dx - tickStartOffset * math.sin(angleForTicks); + final double tickStartY = + centerPoint.dy + tickStartOffset * math.cos(angleForTicks); + final double tickStopX = + centerPoint.dx + (1 - tickEndOffset) * math.sin(angleForTicks); + final double tickStopY = + centerPoint.dy - (1 - tickEndOffset) * math.cos(angleForTicks); + final Offset startOffset = Offset(tickStartX, tickStartY); + final Offset endOffset = Offset(tickStopX, tickStopY); + return [startOffset, endOffset]; + } + + ///Method to calculate teh sweep angle of axis + double _getSweepAngle() { + final double actualEndAngle = + axis.endAngle > 360 ? axis.endAngle % 360 : axis.endAngle; + double totalAngle = actualEndAngle - axis.startAngle; + totalAngle = totalAngle <= 0 ? (totalAngle + 360) : totalAngle; + return totalAngle; + } + + ///Calculates the axis width based on the coordinate unit + double getActualValue(double? value, GaugeSizeUnit sizeUnit, bool isOffset) { + double actualValue = 0; + if (value != null) { + switch (sizeUnit) { + case GaugeSizeUnit.factor: + { + if (!isOffset) { + value = value < 0 ? 0 : value; + value = value > 1 ? 1 : value; + } + + actualValue = value * radius; + } + break; + case GaugeSizeUnit.logicalPixel: + { + actualValue = value; + } + break; + } + } + + return actualValue; + } + + ///Calculates the maximum tick length + double _getTickLength(bool isMajorTick) { + if (isMajorTick) { + return getActualValue( + axis.majorTickStyle.length, axis.majorTickStyle.lengthUnit, false); + } else { + return getActualValue( + axis.minorTickStyle.length, axis.minorTickStyle.lengthUnit, false); + } + } + + /// Renders the axis pointers + void _renderPointers() { + final int index = renderingDetails.axisRenderers.indexOf(this); + for (int i = 0; i < axis.pointers!.length; i++) { + final List pointerRenderers = + renderingDetails.gaugePointerRenderers[index]!; + final GaugePointerRenderer pointerRenderer = pointerRenderers[i]; + pointerRenderer.axis = axis; + pointerRenderer.calculatePosition(); + } + } + + /// Method to render the range + void _renderRanges() { + final int index = renderingDetails.axisRenderers.indexOf(this); + for (int i = 0; i < axis.ranges!.length; i++) { + final List rangeRenderers = + renderingDetails.gaugeRangeRenderers[index]!; + final GaugeRangeRenderer rangeRenderer = rangeRenderers[i]; + rangeRenderer.axis = axis; + rangeRenderer.calculateRangePosition(); + } + } + + /// Calculates the interval of axis based on its range + num _getNiceInterval() { + if (axis.interval != null) { + return axis.interval!; + } + + return calculateAxisInterval(axis.maximumLabels); + } + + /// To calculate the axis label based on the maximum axis label + num calculateAxisInterval(int actualMaximumValue) { + final num delta = _getAxisRange(); + final num circumference = 2 * math.pi * _center * (sweepAngle / 360); + final num desiredIntervalCount = + math.max(circumference * ((0.533 * actualMaximumValue) / 100), 1); + num niceInterval = delta / desiredIntervalCount; + final num minimumInterval = + math.pow(10, (math.log(niceInterval) / math.log(10)).floor()); + final List intervalDivisions = [10, 5, 2, 1]; + for (int i = 0; i < intervalDivisions.length; i++) { + final num currentInterval = minimumInterval * intervalDivisions[i]; + if (desiredIntervalCount < (delta / currentInterval)) { + break; + } + niceInterval = currentInterval; + } + + return niceInterval; // Returns the interval based on the maximum number + // of labels for 100 labels + } + + /// To load the image from the image provider + void _loadBackgroundImage(BuildContext context) { + final ImageStream newImageStream = + axis.backgroundImage!.resolve(createLocalImageConfiguration(context)); + if (newImageStream.key != imageStream?.key) { + imageStream?.removeListener(listener!); + imageStream = newImageStream; + imageStream?.addListener(listener!); + } + } + + /// Update the background image + void _updateBackgroundImage(ImageInfo? imageInfo, bool synchronousCall) { + if (imageInfo?.image != null) { + backgroundImageInfo = imageInfo; + renderingDetails.axisRepaintNotifier.value++; + } + } + + /// Gets the current axis labels + CircularAxisLabel _getAxisLabel(num i) { + num value = i; + String labelText = value.toString(); + final List list = labelText.split('.'); + value = double.parse(value.toStringAsFixed(3)); + if (list.isNotEmpty && + list.length > 1 && + (list[1] == '0' || list[1] == '00' || list[1] == '000')) { + value = value.round(); + } + + labelText = value.toString(); + + if (axis.numberFormat != null) { + labelText = axis.numberFormat!.format(value); + } + if (axis.labelFormat != null) { + labelText = axis.labelFormat!.replaceAll(RegExp('{value}'), labelText); + } + AxisLabelCreatedArgs? labelCreatedArgs; + GaugeTextStyle? argsLabelStyle; + if (axis.onLabelCreated != null) { + labelCreatedArgs = AxisLabelCreatedArgs(); + labelCreatedArgs.text = labelText; + axis.onLabelCreated!(labelCreatedArgs); + + labelText = labelCreatedArgs.text; + argsLabelStyle = labelCreatedArgs.labelStyle; + } + + final GaugeTextStyle labelStyle = argsLabelStyle ?? axis.axisLabelStyle; + final CircularAxisLabel label = CircularAxisLabel(labelStyle, labelText, i, + labelCreatedArgs != null ? labelCreatedArgs.canRotate ?? false : false); + label.value = value; + return label; + } + + /// Returns the axis range + num _getAxisRange() { + return axis.maximum - axis.minimum; + } + + /// Calculates the visible labels based on axis interval and range + List? generateVisibleLabels() { + isMaxiumValueIncluded = false; + final List visibleLabels = []; + _actualInterval = _getNiceInterval(); + for (num i = axis.minimum; i <= axis.maximum; i += _actualInterval!) { + final CircularAxisLabel currentLabel = _getAxisLabel(i); + visibleLabels.add(currentLabel); + } + + final CircularAxisLabel label = visibleLabels[visibleLabels.length - 1]; + if (label.value != axis.maximum && label.value < axis.maximum) { + isMaxiumValueIncluded = true; + final CircularAxisLabel currentLabel = _getAxisLabel(axis.maximum); + visibleLabels.add(currentLabel); + } + + return visibleLabels; + } + + /// Converts the axis value to factor based on angle + double valueToFactor(double value) { + final double angle = _valueToAngle(value); + return angle / sweepAngle; + } + + /// Converts the factor value to axis value + double factorToValue(double factor) { + final double angle = axis.isInversed + ? (factor * sweepAngle) + axis.startAngle + sweepAngle + : (factor * sweepAngle) + axis.startAngle; + + return _angleToValue(angle); + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/range_pointer_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/range_pointer_renderer.dart new file mode 100644 index 000000000..3ef943cfa --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/range_pointer_renderer.dart @@ -0,0 +1,164 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../../gauges.dart'; +import '../pointers/range_pointer.dart'; +import '../renderers/gauge_pointer_renderer.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// This class has methods to render the range pointer +/// +class RangePointerRenderer extends GaugePointerRenderer { + /// Creates the instance for range pointer renderer + RangePointerRenderer() : super() { + isDragStarted = false; + animationEndValue = 0; + } + + /// Holds the start arc value + late double startArc; + + /// Holds the end arc value + late double endArc; + + /// Holds the actual range thickness + late double actualRangeThickness; + + /// Specifies the range arc top + late double _rangeArcTop; + + /// Specifies the range arc bottom + late double _rangeArcBottom; + + /// Specifies the range arc left + late double _rangeArcLeft; + + /// Specifies the range arc right + late double _rangeArcRight; + + /// Specifies the arc rect + late Rect arcRect; + + /// Specifies the arc path + late Path arcPath; + + /// Specifies the start radian of range arc + late double startCornerRadian; + + /// Specifies the sweep radian of range arc + late double sweepCornerRadian; + + /// Specifies the center value for range corner + late double _cornerCenter; + + /// Specifies the angle for corner cap + late double cornerAngle; + + /// Specifies the actual pointer offset value + late double _actualPointerOffset; + + /// Specifies total offset for the range pointer + late double totalOffset; + + /// Method to calculate pointer position + @override + void calculatePosition() { + final RangePointer rangePointer = gaugePointer as RangePointer; + currentValue = getMinMax(currentValue, axis.minimum, axis.maximum); + actualRangeThickness = axisRenderer.getActualValue( + rangePointer.width, rangePointer.sizeUnit, false); + _actualPointerOffset = axisRenderer.getActualValue( + rangePointer.pointerOffset, rangePointer.sizeUnit, true); + totalOffset = _actualPointerOffset < 0 + ? axisRenderer.getAxisOffset() + _actualPointerOffset + : (_actualPointerOffset + axisRenderer.axisOffset); + final double minFactor = (axis.onCreateAxisRenderer != null && + axisRenderer.renderer != null && + axisRenderer.renderer!.valueToFactor(axis.minimum) != null) + ? axisRenderer.renderer!.valueToFactor(axis.minimum) ?? + axisRenderer.valueToFactor(axis.minimum) + : axisRenderer.valueToFactor(axis.minimum); + startArc = (minFactor * axisRenderer.sweepAngle) + axis.startAngle; + final double maxFactor = (axis.onCreateAxisRenderer != null && + axisRenderer.renderer != null && + axisRenderer.renderer!.valueToFactor(currentValue) != null) + ? axisRenderer.renderer!.valueToFactor(currentValue) ?? + axisRenderer.valueToFactor(currentValue) + : axisRenderer.valueToFactor(currentValue); + final double rangeEndAngle = + (maxFactor * axisRenderer.sweepAngle) + axis.startAngle; + endArc = rangeEndAngle - startArc; + + _rangeArcLeft = + -(axisRenderer.radius - (actualRangeThickness / 2 + totalOffset)); + _rangeArcTop = + -(axisRenderer.radius - (actualRangeThickness / 2 + totalOffset)); + _rangeArcRight = + axisRenderer.radius - (actualRangeThickness / 2 + totalOffset); + _rangeArcBottom = + axisRenderer.radius - (actualRangeThickness / 2 + totalOffset); + + _createRangeRect(rangePointer); + } + + /// To creates the arc rect for range pointer + void _createRangeRect(RangePointer rangePointer) { + arcRect = Rect.fromLTRB( + _rangeArcLeft, _rangeArcTop, _rangeArcRight, _rangeArcBottom); + pointerRect = Rect.fromLTRB( + _rangeArcLeft, _rangeArcTop, _rangeArcRight, _rangeArcBottom); + arcPath = Path(); + arcPath.arcTo( + arcRect, getDegreeToRadian(startArc), getDegreeToRadian(endArc), true); + _calculateCornerStylePosition(rangePointer); + } + + /// Calculates the rounded corner position + void _calculateCornerStylePosition(RangePointer rangePointer) { + _cornerCenter = (arcRect.right - arcRect.left) / 2; + cornerAngle = cornerRadiusAngle(_cornerCenter, actualRangeThickness / 2); + + switch (rangePointer.cornerStyle) { + case CornerStyle.startCurve: + { + startCornerRadian = axis.isInversed + ? getDegreeToRadian(-cornerAngle) + : getDegreeToRadian(cornerAngle); + sweepCornerRadian = axis.isInversed + ? getDegreeToRadian(endArc + cornerAngle) + : getDegreeToRadian(endArc - cornerAngle); + } + break; + case CornerStyle.endCurve: + { + startCornerRadian = getDegreeToRadian(0); + sweepCornerRadian = axis.isInversed + ? getDegreeToRadian(endArc + cornerAngle) + : getDegreeToRadian(endArc - cornerAngle); + } + break; + case CornerStyle.bothCurve: + { + startCornerRadian = axis.isInversed + ? getDegreeToRadian(-cornerAngle) + : getDegreeToRadian(cornerAngle); + sweepCornerRadian = axis.isInversed + ? getDegreeToRadian(endArc + 2 * cornerAngle) + : getDegreeToRadian(endArc - 2 * cornerAngle); + } + break; + case CornerStyle.bothFlat: + { + startCornerRadian = getDegreeToRadian(startArc); + sweepCornerRadian = getDegreeToRadian(endArc); + } + break; + } + } + + /// Calculates the range sweep angle + double getSweepAngle() { + return getRadianToDegree(sweepCornerRadian) / axisRenderer.sweepAngle; + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/widget_pointer_renderer_base.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/widget_pointer_renderer_base.dart new file mode 100644 index 000000000..ff4a6d282 --- /dev/null +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/widget_pointer_renderer_base.dart @@ -0,0 +1,121 @@ +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../common/widget_pointer_renderer.dart'; +import '../pointers/widget_pointer.dart'; +import '../renderers/gauge_pointer_renderer.dart'; +import '../utils/helper.dart'; + +/// The [WidgetPointerRenderer] has methods to render marker pointer +/// +class WidgetPointerRenderer extends GaugePointerRenderer { + /// Creates the instance for widget pointer renderer + WidgetPointerRenderer() : super(); + + /// Specifies the margin for calculating + /// marker pointer rect + final double _margin = 15; + + /// Specifies the marker offset + late Offset offset; + + /// Specifies the radian value of the marker + late double _radian; + + /// Specifies the angle value + late double angle; + + /// Specifies the total offset considering axis element + late double _totalOffset; + + /// Specifies actual marker offset value + late double _actualWidgetOffset; + + /// Specifies the widget size + Size? widgetSize; + + /// Specifies the pointer renderer + late WidgetPointerContainer? pointerRenderer; + + /// method to calculate the marker position + @override + void calculatePosition() { + final WidgetPointer widgetPointer = gaugePointer as WidgetPointer; + angle = getPointerAngle(); + _radian = getDegreeToRadian(angle); + final Offset offset = getPointerOffset(_radian, widgetPointer); + + if (widgetSize != null) { + pointerRect = Rect.fromLTRB( + offset.dx - widgetSize!.width / 2 - _margin, + offset.dy - widgetSize!.height / 2 - _margin, + offset.dx + widgetSize!.width / 2 + _margin, + offset.dy + widgetSize!.height / 2 + _margin); + } + } + + /// Method returns the angle of current pointer value + double getPointerAngle() { + currentValue = getMinMax(currentValue, axis.minimum, axis.maximum); + return (axisRenderer.valueToFactor(currentValue) * + axisRenderer.sweepAngle) + + axis.startAngle; + } + + /// Method to refresh the widget pointer + void refreshPointer() { + if (pointerRenderer != null) { + final GlobalKey? key = + pointerRenderer!.key as GlobalKey; + if (key != null && key.currentState != null) { + key.currentState!.refresh(); + } + } + } + + /// Calculates the marker offset position + Offset getPointerOffset(double pointerRadian, WidgetPointer widgetPointer) { + _actualWidgetOffset = axisRenderer.getActualValue( + widgetPointer.offset, widgetPointer.offsetUnit, true); + _totalOffset = _actualWidgetOffset < 0 + ? axisRenderer.getAxisOffset() + _actualWidgetOffset + : (_actualWidgetOffset + axisRenderer.axisOffset); + if (!axis.canScaleToFit) { + final double x = (axisRenderer.axisSize.width / 2) + + (axisRenderer.radius - + _totalOffset - + (axisRenderer.actualAxisWidth / 2)) * + math.cos(pointerRadian) - + axisRenderer.centerX; + final double y = (axisRenderer.axisSize.height / 2) + + (axisRenderer.radius - + _totalOffset - + (axisRenderer.actualAxisWidth / 2)) * + math.sin(pointerRadian) - + axisRenderer.centerY; + offset = Offset(x, y); + } else { + final double x = axisRenderer.axisCenter.dx + + (axisRenderer.radius - + _totalOffset - + (axisRenderer.actualAxisWidth / 2)) * + math.cos(pointerRadian); + final double y = axisRenderer.axisCenter.dy + + (axisRenderer.radius - + _totalOffset - + (axisRenderer.actualAxisWidth / 2)) * + math.sin(pointerRadian); + offset = Offset(x, y); + } + return offset; + } + + /// Specifies whether the pointer animation is enabled + bool getIsPointerAnimationEnabled() { + return gaugePointer.enableAnimation && + gaugePointer.animationDuration > 0 && + needsAnimate != null && + needsAnimate!; + } +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/utils/enum.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/utils/enum.dart index e59948417..dd6d98de2 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/utils/enum.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/utils/enum.dart @@ -1,5 +1,3 @@ -part of gauges; - /// A alignment along either the horizontal or vertical. enum GaugeAlignment { /// GaugeAlignment.near aligns the gauge element to near either diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/utils/helper.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/utils/helper.dart index 3a3c5f321..694c07693 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/utils/helper.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/utils/helper.dart @@ -1,24 +1,28 @@ -part of gauges; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../common/common.dart'; /// Converts degree to radian -double _getDegreeToRadian(double degree) { +double getDegreeToRadian(double degree) { return degree * (math.pi / 180); } /// Converts radian to degree -double _getRadianToDegree(double radian) { +double getRadianToDegree(double radian) { return 180 / math.pi * radian; } /// To get the degree from the point -Offset _getDegreeToPoint(double degree, double radius, Offset center) { - degree = _getDegreeToRadian(degree); +Offset getDegreeToPoint(double degree, double radius, Offset center) { + degree = getDegreeToRadian(degree); return Offset(center.dx + math.cos(degree) * radius, center.dy + math.sin(degree) * radius); } /// Methods to get the saturation color -Color _getSaturationColor(Color color) { +Color getSaturationColor(Color color) { final num contrast = ((color.red * 299 + color.green * 587 + color.blue * 114) / 1000).round(); final Color saturationColor = @@ -28,13 +32,13 @@ Color _getSaturationColor(Color color) { /// Method to check whether the value ranges between /// the minimum and maximum value -double _getMinMax(double value, double min, double max) { +double getMinMax(double value, double min, double max) { return value > max ? max : (value < min ? min : value); } -// Measure the text and return the text size +/// Measure the text and return the text size //ignore: unused_element -Size _getTextSize(String textValue, GaugeTextStyle textStyle, [int angle]) { +Size getTextSize(String textValue, GaugeTextStyle textStyle) { Size size; final TextPainter textPainter = TextPainter( textAlign: TextAlign.center, @@ -54,13 +58,13 @@ Size _getTextSize(String textValue, GaugeTextStyle textStyle, [int angle]) { } /// Returns the revised gradient stop -List _calculateGradientStops( - List offsets, bool isInversed, double sweepAngle) { - final List gradientStops = List(offsets.length); +List calculateGradientStops( + List offsets, bool isInversed, double sweepAngle) { + final List gradientStops = List.filled(offsets.length, 0); // Normalizes the provided offset values to the corresponding sweep angle - for (num i = 0; i < offsets.length; i++) { - final double offset = offsets[i]; + for (int i = 0; i < offsets.length; i++) { + final double offset = offsets[i]!; double _stop = ((sweepAngle / 360) * offset).abs(); if (isInversed) { _stop = 1 - _stop; @@ -72,9 +76,9 @@ List _calculateGradientStops( } /// Represents the circular interval list -class _CircularIntervalList { +class CircularIntervalList { /// Creates the circular interval list - _CircularIntervalList(this._values); + CircularIntervalList(this._values); /// Specifies the list of value final List _values; @@ -82,6 +86,7 @@ class _CircularIntervalList { /// Specifies the index value int _index = 0; + /// To get the value of next T get next { if (_index >= _values.length) { _index = 0; @@ -91,33 +96,30 @@ class _CircularIntervalList { } /// Method to draw the dashed path -Path _dashPath(Path source, - {@required _CircularIntervalList dashArray}) { +Path dashPath(Path source, {CircularIntervalList? dashArray}) { final Path path = Path(); const double intialValue = 0.0; - if (source == null) { - return null; - } - - for (final PathMetric measurePath in source.computeMetrics()) { - double distance = intialValue; - bool draw = true; - while (distance < measurePath.length) { - final double length = dashArray.next; - if (draw) { - path.addPath( - measurePath.extractPath(distance, distance + length), Offset.zero); + if (dashArray != null) { + for (final PathMetric measurePath in source.computeMetrics()) { + double distance = intialValue; + bool draw = true; + while (distance < measurePath.length) { + final double length = dashArray.next; + if (draw) { + path.addPath(measurePath.extractPath(distance, distance + length), + Offset.zero); + } + distance += length; + draw = !draw; } - distance += length; - draw = !draw; } } return path; } /// Calculates the corner radius angle -double _cornerRadiusAngle(double totalRadius, double circleRadius) { +double cornerRadiusAngle(double totalRadius, double circleRadius) { final double perimeter = (totalRadius + totalRadius + circleRadius) / 2; final double area = math.sqrt(perimeter * (perimeter - totalRadius) * diff --git a/packages/syncfusion_flutter_gauges/pubspec.yaml b/packages/syncfusion_flutter_gauges/pubspec.yaml index 17fbb54c4..8864a1b6c 100644 --- a/packages/syncfusion_flutter_gauges/pubspec.yaml +++ b/packages/syncfusion_flutter_gauges/pubspec.yaml @@ -1,17 +1,17 @@ name: syncfusion_flutter_gauges -description: Syncfusion Flutter gauges library includes data visualization widgets such as radial gauge, which is written in dart, to create modern, interactive, and animated gauges. -version: 18.3.35 +description: The Flutter gauges library includes a linear gauge and radial gauge (a.k.a. circular gauge) to create modern, interactive, animated gauges and radial sliders. +version: 19.1.54 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_gauges environment: - sdk: ">=2.1.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: sdk: flutter syncfusion_flutter_core: path: ../syncfusion_flutter_core - intl: ">=0.15.0 <0.17.0" + intl: ^0.17.0 flutter: \ No newline at end of file diff --git a/packages/syncfusion_flutter_maps/CHANGELOG.md b/packages/syncfusion_flutter_maps/CHANGELOG.md index 23f0607b8..151204f3b 100644 --- a/packages/syncfusion_flutter_maps/CHANGELOG.md +++ b/packages/syncfusion_flutter_maps/CHANGELOG.md @@ -1,3 +1,54 @@ +## Unreleased + +**Features** + +* Inverted circle. +* Inverted polygon. +* Legend title. +* Double tap zooming. + +**Breaking changes** + +* The `title` property has been removed from `SfMaps`. + +## [18.4.30-beta] - 12/17/2020 + +**Features** + +* Shape sublayer support. +* Load JSON from different sources. +* Tooltip for markers. +* Bar legend with gradient support. +* Vector shapes. +* Animation improvements while zooming and panning. +* Diagnostics support. + +**Breaking changes** + +* The `palette` property has been removed from `MapShapeLayer`. + +* The `enableShapeTooltip` property has been removed and the tooltip can be enabled by setting the `shapeTooltipBuilder` property. +* The `shapeTooltipTextMapper` property has been removed and the same behavior can be achieved by returning a custom widget from the `shapeTooltipBuilder` property. + +* The `showBubbles` property has been removed and the same behavior can be achieved by setting the `bubbleSizeMapper` property. +* The `enableBubbleTooltip` property has been removed and the tooltip can be enabled by setting the `bubbleTooltipBuilder` property. +* The `bubbleTooltipTextMapper` property has been removed and the same behavior can be achieved by returning a custom widget from the `bubbleTooltipBuilder` property. + +* The `enableSelection` property has been removed and the same behavior can be achieved by setting the `onSelectionChanged` property. +* The `initialSelectedIndex` property has been changed to `selectedIndex`. To observe the changes in the UI, the user must call `setState()`. + +* The `delegate` property has been changed to `source` property and the type of the delegate property `MapShapeLayerDelegate` has been changed into `MapShapeSource` with named constructors such as `MapShapeSource.asset`, `MapShapeSource.network`, and `MapShapeSource.memory` to load json data from various sources. + +* The `legendSettings` property has been renamed as `legend` and the `MapLegendSettings` has been renamed as the `MapLegend`. +* The `legendSource` property has been renamed as `source` and is now moved to the `MapLegend`. +* The `MapLegend.none` enum has been removed and the same behavior can be achieved by setting the `legend` property as `null`. +* The `showIcon` property has been removed and the same behavior can be achieved by setting `iconSize` property of the `MapLegend` class as `Size.empty`. +* The `opacity` property has been removed from `MapBubbleSettings` and `MapSelectionSettings` classes and the same behavior can be achieved by setting opacity value in `color` property of the `MapBubbleSettings` and `MapSelectionSettings`. +* The `MapIconType.square` enum has been changed to `MapIconType.rectangle`. +* The `MapLabelOverflowMode` has been renamed as the `MapLabelOverflow`. The `MapLabelOverflowMode.trim` and `MapLabelOverflowMode.none` enum values have been renamed to `MapLabelOverflow.ellipsis` and `MapLabelOverflow.visible` respectively. The `MapLabelOverflow` enum values are `visible`, `ellipsis`, and `hide`. + +* The `textStyle` and `tooltipTextStyle` property has been removed from `MapTooltipSettings` and `SfMapsThemeData` classes respectively since the built-in tooltip shape is removed. + ## [18.3.35-beta] - 10/01/2020 **Features** diff --git a/packages/syncfusion_flutter_maps/README.md b/packages/syncfusion_flutter_maps/README.md index 5a77cd612..8b5fb0f67 100644 --- a/packages/syncfusion_flutter_maps/README.md +++ b/packages/syncfusion_flutter_maps/README.md @@ -1,16 +1,14 @@ -![syncfusion_flutter_map_banner](https://cdn.syncfusion.com/content/images/Flutter/pub_images/maps_images/banner.jpg) +![syncfusion_flutter_map_banner](https://cdn.syncfusion.com/content/images/Flutter/pub_images/maps_images/banner.jpg) -# Syncfusion Flutter Maps +# Flutter Maps library -Syncfusion Flutter Maps is a data visualization library written natively in Dart for creating beautiful and customizable maps. They are used to build high-performance mobile applications with rich UIs using Flutter. +The Flutter Maps package is a data visualization library for creating beautiful, interactive, and customizable maps from shape files or WMTS services to visualize the geographical area. ## Overview -Create a highly interactive and customizable maps widget that has features set includes tile rendering from OpenStreetMap, Bing Maps, and other tile providers with marker support and shape layer with features like selection, legends, labels, markers, tooltips, bubbles, color mapping, and much more. +Create a highly interactive and customizable Flutter Maps that has features set like tile rendering from OpenStreetMaps, Azure Maps API, Bing Maps API, Google Maps Tile API, TomTom, Mapbox, Esri’s ArcGIS, and other tile providers with marker support and shape layer with features like selection, legends, labels, markers, tooltips, bubbles, color mapping, and much more. -**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or Syncfusion Community License. For more details, please check the LICENSE file. - -**Note:** Our packages are now compatible with Flutter for Web. However, this will be in beta until Flutter for web becomes stable. +**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. ## Table of contents - [Maps features](#maps-features) @@ -63,6 +61,10 @@ Add shapes such as polylines, lines, polygons, circles, and arcs as a sublayer i ![maps zoompan](https://cdn.syncfusion.com/content/images/zoompan.gif) +**Inverted circle** - Support has been provided for applying color to the inverted circle with the inner circle being transparent and the outer portion covered by an overlay color. + +**Inverted polygon** - Support has been provided for applying color to the inverted polygon with the inner polygon being transparent and the outer portion covered by an overlay color. + ### Tile layer **Markers** - Show markers for the tile layer in the specific latitudes and longitudes. Display additional information about the markers using a customizable tooltip on a map. @@ -83,6 +85,14 @@ Add shapes such as polylines, lines, polygons, circles, and arcs as a sublayer i **Zooming and panning** - Zoom in tile layer for a closer look at a specific region by pinching, scrolling the mouse wheel or track pad, or using the toolbar on the web. Pan the map to navigate across the regions. +**Inverted circle** - Support has been provided for applying color to the inverted circle with the inner circle being transparent and the outer portion covered by an overlay color. + +![inverted circle](https://cdn.syncfusion.com/content/images/Flutter/pub_images/maps_images/inverted-circle.png) + +**Inverted polygon** - Support has been provided for applying color to the inverted polygon with the inner polygon being transparent and the outer portion covered by an overlay color. + +![inverted polygon](https://cdn.syncfusion.com/content/images/Flutter/pub_images/maps_images/inverted-polygon.png) + ## Get the demo application Explore the full capability of our Flutter widgets on your device by installing our sample browser application from the following app stores. View sample codes in GitHub. @@ -108,7 +118,7 @@ Take a look at the following to learn more about Syncfusion Flutter Maps: Install the latest version from [pub](https://pub.dev/packages/syncfusion_flutter_maps#-installing-tab-). -## Getting started +## Flutter map example Import the following package. @@ -116,7 +126,7 @@ Import the following package. import 'package:syncfusion_flutter_maps/maps.dart'; ``` -### Add maps to the widget tree +### Create map After importing the package, initialize the maps widget as a child of any widget. @@ -138,16 +148,25 @@ The `layers` in `SfMaps` contains collection of `MapShapeLayer`. The actual geog The `shapeDataField` property of the `MapShapeSource` is used to refer the unique field name in the .json file to identify each shapes. In 'Mapping the data source' section of this document, this `shapeDataField` will be used to map with respective value returned in `primaryValueMapper` from the data source. ```dart +MapShapeSource _mapSource; + +@override +void initState() { + _mapSource = MapShapeSource.asset( + 'assets/australia.json', + shapeDataField: 'STATE_NAME', + ); + + super.initState(); +} + @override Widget build(BuildContext context) { return Scaffold( body: SfMaps( layers: [ MapShapeLayer( - source: MapShapeSource.asset( - 'assets/australia.json', - shapeDataField: 'STATE_NAME', - ), + source: _mapSource, ), ], ), @@ -163,6 +182,7 @@ By default, the value specified for the `shapeDataField` in the GeoJSON file wil ```dart List data; +MapShapeSource _mapSource; @override void initState() { @@ -178,6 +198,13 @@ void initState() { Model('Australian Capital Territory', 'ACT') ]; + _mapSource = MapShapeSource.asset( + 'assets/australia.json', + shapeDataField: 'STATE_NAME', + dataCount: data.length, + primaryValueMapper: (int index) => data[index].state, + ); + super.initState(); } @@ -187,12 +214,7 @@ Widget build(BuildContext context) { body: SfMaps( layers: [ MapShapeLayer( - source: MapShapeSource.asset( - 'assets/australia.json', - shapeDataField: 'STATE_NAME', - dataCount: data.length, - primaryValueMapper: (int index) => data[index].state, - ), + source: _mapSource, ), ], ), @@ -221,6 +243,7 @@ Add the basic maps elements such as title, data labels, legend, and tooltip as s ```dart List data; +MapShapeSource _mapSource; @override void initState() { @@ -238,6 +261,15 @@ void initState() { Model('Tasmania', Color.fromRGBO(99, 164, 230, 1), 'Tasmania'), Model('Australian Capital Territory', Colors.teal, 'ACT') ]; + + _mapSource = MapShapeSource.asset( + 'assets/australia.json', + shapeDataField: 'STATE_NAME', + dataCount: data.length, + primaryValueMapper: (int index) => data[index].state, + dataLabelMapper: (int index) => data[index].stateCode, + shapeColorValueMapper: (int index) => data[index].color, + ); super.initState(); } @@ -252,14 +284,7 @@ Widget build(BuildContext context) { title: const MapTitle('Australia map'), layers: [ MapShapeLayer( - source: MapShapeSource.asset( - 'assets/australia.json', - shapeDataField: 'STATE_NAME', - dataCount: data.length, - primaryValueMapper: (int index) => data[index].state, - dataLabelMapper: (int index) => data[index].stateCode, - shapeColorValueMapper: (int index) => data[index].color, - ), + source: _mapSource, legend: MapLegend(MapElement.shape), showDataLabels: true, shapeTooltipBuilder: (BuildContext context, int index) { diff --git a/packages/syncfusion_flutter_maps/example/lib/main.dart b/packages/syncfusion_flutter_maps/example/lib/main.dart index 73205dca4..eb2a6deba 100644 --- a/packages/syncfusion_flutter_maps/example/lib/main.dart +++ b/packages/syncfusion_flutter_maps/example/lib/main.dart @@ -20,7 +20,7 @@ class MapsApp extends StatelessWidget { /// This widget is the home page of the application. class MyHomePage extends StatefulWidget { /// Initialize the instance of the [MyHomePage] class. - const MyHomePage({Key key}) : super(key: key); + const MyHomePage({Key? key}) : super(key: key); @override _MyHomePageState createState() => _MyHomePageState(); @@ -29,7 +29,8 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { _MyHomePageState(); - List _data; + late List _data; + late MapShapeSource _mapSource; @override void initState() { @@ -47,6 +48,15 @@ class _MyHomePageState extends State { Model('Tasmania', Color.fromRGBO(99, 164, 230, 1), 'Tasmania'), Model('Australian Capital Territory', Colors.teal, 'ACT') ]; + + _mapSource = MapShapeSource.asset( + 'assets/australia.json', + shapeDataField: 'STATE_NAME', + dataCount: _data.length, + primaryValueMapper: (int index) => _data[index].state, + dataLabelMapper: (int index) => _data[index].stateCode, + shapeColorValueMapper: (int index) => _data[index].color, + ); super.initState(); } @@ -57,17 +67,9 @@ class _MyHomePageState extends State { height: 520, child: Center( child: SfMaps( - title: const MapTitle('Australia map'), layers: [ MapShapeLayer( - source: MapShapeSource.asset( - 'assets/australia.json', - shapeDataField: 'STATE_NAME', - dataCount: _data.length, - primaryValueMapper: (int index) => _data[index].state, - dataLabelMapper: (int index) => _data[index].stateCode, - shapeColorValueMapper: (int index) => _data[index].color, - ), + source: _mapSource, showDataLabels: true, legend: MapLegend(MapElement.shape), tooltipSettings: MapTooltipSettings( @@ -90,7 +92,7 @@ class _MyHomePageState extends State { color: Colors.black, fontWeight: FontWeight.bold, fontSize: - Theme.of(context).textTheme.caption.fontSize)), + Theme.of(context).textTheme.caption!.fontSize)), ), ], ), diff --git a/packages/syncfusion_flutter_maps/example/pubspec.yaml b/packages/syncfusion_flutter_maps/example/pubspec.yaml index 7a960db6e..b3977d242 100644 --- a/packages/syncfusion_flutter_maps/example/pubspec.yaml +++ b/packages/syncfusion_flutter_maps/example/pubspec.yaml @@ -14,7 +14,7 @@ description: A new Flutter project. version: 1.0.0+1 environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: @@ -22,7 +22,7 @@ dependencies: intl: ">=0.15.0 <0.17.0" syncfusion_flutter_maps: - path: ../../syncfusion_flutter_maps/ + path: ../../syncfusion_flutter_maps # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. diff --git a/packages/syncfusion_flutter_maps/lib/maps.dart b/packages/syncfusion_flutter_maps/lib/maps.dart index 4fcff55e8..af6e2bfce 100644 --- a/packages/syncfusion_flutter_maps/lib/maps.dart +++ b/packages/syncfusion_flutter_maps/lib/maps.dart @@ -3,147 +3,21 @@ library maps; import 'package:flutter/foundation.dart' show DiagnosticableTree; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; import 'src/layer/layer_base.dart'; import 'src/layer/shape_layer.dart'; export 'src/behavior/zoom_pan_behavior.dart' - hide Gesture, BehaviorViewRenderObjectWidget; + hide BehaviorViewRenderObjectWidget; export 'src/controller/shape_layer_controller.dart'; -export 'src/elements/legend.dart' hide MapLayerLegend; -export 'src/elements/marker.dart' - hide ShapeLayerMarkerContainer, RenderMapMarker; -export 'src/enum.dart' hide MapLayerElement; -export 'src/layer/layer_base.dart' - hide SublayerContainer, RenderSublayerContainer; -export 'src/layer/shape_layer.dart' - hide - MapProvider, - RenderShapeLayer, - AssetMapProvider, - MemoryMapProvider, - NetworkMapProvider; -export 'src/layer/tile_layer.dart' hide MapZoomLevel; +export 'src/elements/legend.dart' hide MapLegendWidget; +export 'src/elements/marker.dart' hide MarkerContainer; +export 'src/enum.dart'; +export 'src/layer/layer_base.dart'; +export 'src/layer/shape_layer.dart'; +export 'src/layer/tile_layer.dart'; export 'src/layer/vector_layers.dart' hide MapVectorLayer; export 'src/settings.dart'; -/// Title for the [SfMaps]. -/// -/// [MapTitle] can define and customize the title for the [SfMaps]. The text -/// property of the [MapTitle] is used to set the text of the title. -// -/// It also provides option for customizing text style, alignment, decoration, -/// background color, margin and padding. -/// -/// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return SfMaps( -/// title: MapTitle( -/// text : 'World map' -/// ), -/// ); -/// } -/// ``` -class MapTitle extends DiagnosticableTree { - /// Creates a [MapTitle]. - const MapTitle( - this.text, { - this.textStyle, - this.alignment, - this.decoration, - this.color, - this.margin, - this.padding, - }); - - /// Specifies the text to be displayed as map title. - /// - /// See also: - /// * [textStyle], to customize the text. - /// * [alignment], to align the title. - final String text; - - /// Customizes the style of the [text]. - /// - /// See also: - /// * [text], to set the text for the title. - final TextStyle textStyle; - - /// Specifies the position of the title. - /// - /// Defaults to `center`. - /// - /// See also: - /// * [text], to set the text for the title. - final AlignmentGeometry alignment; - - /// Customizes the appearance of the title. - /// - /// See also: - /// * [Decoration], to set the decoration for the title. - /// * [text], to set the text for the title. - final Decoration decoration; - - /// Specifies the background color of the title. - /// - /// See also: - /// * [text], to set the text for the title. - final Color color; - - /// Customizes the margin of the title. - /// - /// See also: - /// * [EdgeInsetsGeometry], to use the EdgeInsets values. - /// * [text], to set the text for the title. - final EdgeInsetsGeometry margin; - - /// Customize the space around the title [text]. - /// - /// See also: - /// * [EdgeInsetsGeometry], to use the EdgeInsets values. - /// * [text], to set the text for the title. - final EdgeInsetsGeometry padding; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MapTitle && - other.text == text && - other.textStyle == textStyle && - other.alignment == alignment && - other.decoration == decoration && - other.color == color && - other.margin == margin && - other.padding == padding; - } - - @override - int get hashCode => hashValues( - text, textStyle, alignment, decoration, color, margin, padding); - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.add(StringProperty('text', text)); - if (textStyle != null) { - properties.add(textStyle.toDiagnosticsNode(name: 'textStyle')); - } - properties - .add(DiagnosticsProperty('alignment', alignment)); - properties.add(DiagnosticsProperty('decoration', decoration)); - properties.add(DiagnosticsProperty('color', color)); - properties.add(DiagnosticsProperty('margin', margin)); - properties.add(DiagnosticsProperty('padding', padding)); - } -} - /// A data visualization component that displays statistical information for a /// geographical area. /// @@ -282,31 +156,10 @@ class MapTitle extends DiagnosticableTree { class SfMaps extends StatefulWidget { /// Creates a [SfMaps]. const SfMaps({ - Key key, - this.title, - this.layers, + Key? key, + required this.layers, }) : super(key: key); - /// Title for the [SfMaps]. - /// - /// It can define and customize the title for the [SfMaps]. The text - /// property of the [MapTitle] is used to set the text of the title. - /// - /// It also provides option for customizing text style, alignment, decoration, - /// background color, margin and padding of the title. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// title: MapTitle( - /// text : 'World map' - /// ), - /// ); - /// } - /// ``` - final MapTitle title; - /// The collection of map shape layer in which geographical rendering is done. /// /// The snippet below shows how to render the basic world map using the data @@ -338,15 +191,9 @@ class SfMaps extends StatefulWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - if (title != null) { - properties.add( - title.toDiagnosticsNode(name: 'title'), - ); - } - - if (layers != null && layers.isNotEmpty) { - final _DebugPointerTree pointerTreeNode = _DebugPointerTree(layers); - properties.add(pointerTreeNode.toDiagnosticsNode(name: 'layers')); + if (layers.isNotEmpty) { + final _DebugLayerTree pointerTreeNode = _DebugLayerTree(layers); + properties.add(pointerTreeNode.toDiagnosticsNode()); } } } @@ -354,55 +201,18 @@ class SfMaps extends StatefulWidget { class _SfMapsState extends State { @override Widget build(BuildContext context) { - assert(widget.layers != null && widget.layers.isNotEmpty); + assert(widget.layers.isNotEmpty); return _MapsRenderObjectWidget( - child: widget.title != null && widget.title.text != null - ? Column( - children: [ - _title(context), - Expanded( - child: LayoutBuilder(builder: - (BuildContext context, BoxConstraints constraints) { - return Stack( - alignment: Alignment.center, - children: [widget.layers.last], - ); - }), - ), - ], - ) - : Stack( - alignment: Alignment.center, - children: [widget.layers.last], - ), - ); - } - - /// Returns the title of the [SfMaps]. - Widget _title(BuildContext context) { - final SfMapsThemeData themeData = SfMapsTheme.of(context); - return Align( - alignment: widget.title.alignment ?? Alignment.center, - child: Container( - child: Text( - widget.title.text, - style: Theme.of(context) - .textTheme - .subtitle1 - .merge(widget.title.textStyle ?? themeData.titleTextStyle), - ), - color: widget.title.color, - decoration: widget.title.decoration, - margin: widget.title.margin, - padding: - widget.title.padding ?? const EdgeInsets.symmetric(vertical: 8), + child: Stack( + alignment: Alignment.center, + children: [widget.layers.last], ), ); } } class _MapsRenderObjectWidget extends SingleChildRenderObjectWidget { - const _MapsRenderObjectWidget({Key key, Widget child}) + const _MapsRenderObjectWidget({Key? key, required Widget child}) : super(key: key, child: child); @override @@ -418,21 +228,23 @@ class _RenderMaps extends RenderProxyBox { constraints.hasBoundedWidth ? constraints.maxWidth : 300; final double height = constraints.hasBoundedHeight ? constraints.maxHeight : 300; - child.layout(BoxConstraints.loose(Size(width, height)), - parentUsesSize: true); - size = child.size; + if (child != null) { + child!.layout(BoxConstraints.loose(Size(width, height)), + parentUsesSize: true); + size = child!.size; + } } } -class _DebugPointerTree extends DiagnosticableTree { - _DebugPointerTree(this._layer); +class _DebugLayerTree extends DiagnosticableTree { + _DebugLayerTree(this.layers); - final List _layer; + final List layers; @override List debugDescribeChildren() { - if (_layer != null && _layer.isNotEmpty) { - return _layer.map((MapLayer layer) { + if (layers.isNotEmpty) { + return layers.map((MapLayer layer) { return layer.toDiagnosticsNode(); }).toList(); } @@ -441,6 +253,8 @@ class _DebugPointerTree extends DiagnosticableTree { @override String toStringShort() { - return 'contains ${_layer.length} layers'; + return layers.length > 1 + ? 'contains ${layers.length} layers' + : 'contains ${layers.length} layer'; } } diff --git a/packages/syncfusion_flutter_maps/lib/src/behavior/zoom_pan_behavior.dart b/packages/syncfusion_flutter_maps/lib/src/behavior/zoom_pan_behavior.dart index a655c3b5f..ad9d3269e 100644 --- a/packages/syncfusion_flutter_maps/lib/src/behavior/zoom_pan_behavior.dart +++ b/packages/syncfusion_flutter_maps/lib/src/behavior/zoom_pan_behavior.dart @@ -6,16 +6,15 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_maps/maps.dart'; -import '../controller/default_controller.dart'; +import '../controller/map_controller.dart'; import '../settings.dart'; import '../utils.dart'; // ignore: public_member_api_docs -enum Gesture { scale, pan } /// Base class of the map behaviors. abstract class MapBehavior extends DiagnosticableTree { - MapController _controller; + MapController? _controller; /// Render box of the internal widget which handles the [MapZoomPanBehavior]. /// @@ -23,12 +22,12 @@ abstract class MapBehavior extends DiagnosticableTree { /// This is only valid after the layout phase, and should therefore only be /// examined from paint callbacks or interaction event handlers. RenderBox get renderBox => _renderBox; - RenderBox _renderBox; + late RenderBox _renderBox; /// Override this method to handle pointer events that hit this render box. @mustCallSuper void handleEvent(PointerEvent event) { - _controller.notifyListeners(); + _controller?.notifyListeners(); } /// Paints this render box into the given context at the given offset. @@ -42,8 +41,9 @@ abstract class MapBehavior extends DiagnosticableTree { /// Zooming and panning will start working when the new instance of /// [MapZoomPanBehavior] is set to [MapShapeLayer.zoomPanBehavior] or /// [MapTileLayer.zoomPanBehavior]. However, if you need to restrict pinch -/// zooming or panning for any specific requirements, you can set the -/// [enablePinching] and [enablePanning] properties to false respectively. +/// zooming or double tap zooming or panning for any specific requirements, +/// you can set the [enablePinching], [enableDoubleTapZooming], and +/// [enablePanning] properties to false respectively. /// /// The [focalLatLng] is the focal point of the map layer based on which zooming /// happens. @@ -74,13 +74,19 @@ abstract class MapBehavior extends DiagnosticableTree { /// and [MapTileLayer]. /// /// ```dart -/// MapZoomPanBehavior _zoomPanBehavior; +/// MapZoomPanBehavior _zoomPanBehavior; +/// MapShapeSource _mapSource; /// /// @override /// void initState() { +/// _mapSource = MapShapeSource.asset( +/// 'assets/world_map.json', +/// shapeDataField: 'continent', +/// ); /// _zoomPanBehavior = MapZoomPanBehavior() /// ..zoomLevel = 4 -/// ..focalLatLng = MapLatLng(19.0759837, 72.8776559); +/// ..focalLatLng = MapLatLng(19.0759837, 72.8776559) +/// ..toolbarSettings = MapToolbarSettings(); /// super.initState(); /// } /// @@ -93,10 +99,7 @@ abstract class MapBehavior extends DiagnosticableTree { /// body: SfMaps( /// layers: [ /// MapShapeLayer( -/// source: MapShapeSource.asset( -/// 'assets/world_map.json', -/// shapeDataField: 'continent', -/// ), +/// source: _mapSource, /// zoomPanBehavior: _zoomPanBehavior, /// ), /// ], @@ -108,19 +111,21 @@ class MapZoomPanBehavior extends MapBehavior { /// Creates a new [MapZoomPanBehavior]. MapZoomPanBehavior({ double zoomLevel = 1.0, - MapLatLng focalLatLng, + MapLatLng? focalLatLng, double minZoomLevel = 1.0, double maxZoomLevel = 15.0, bool enablePinching = true, bool enablePanning = true, + bool enableDoubleTapZooming = false, bool showToolbar = true, MapToolbarSettings toolbarSettings = const MapToolbarSettings(), - }) : _zoomLevel = interpolateValue(zoomLevel, minZoomLevel, maxZoomLevel), + }) : _zoomLevel = zoomLevel.clamp(minZoomLevel, maxZoomLevel), _focalLatLng = focalLatLng, _minZoomLevel = minZoomLevel, _maxZoomLevel = maxZoomLevel, _enablePinching = enablePinching, _enablePanning = enablePanning, + _enableDoubleTapZooming = enableDoubleTapZooming, _showToolbar = showToolbar, _toolbarSettings = toolbarSettings; @@ -154,9 +159,9 @@ class MapZoomPanBehavior extends MapBehavior { /// viewport for [MapShapeLayer] and the available bounds for the /// [MapTileLayer] based on the [focalLatLng] (Please check the documentation /// of [MapTileLayer] to know more details about how [zoomLevel] works in it). - MapLatLng get focalLatLng => _focalLatLng; - MapLatLng _focalLatLng; - set focalLatLng(MapLatLng value) { + MapLatLng? get focalLatLng => _focalLatLng; + MapLatLng? _focalLatLng; + set focalLatLng(MapLatLng? value) { if (_focalLatLng == value) { return; } @@ -175,7 +180,7 @@ class MapZoomPanBehavior extends MapBehavior { return; } _minZoomLevel = value; - zoomLevel = interpolateValue(zoomLevel, _minZoomLevel, _maxZoomLevel); + zoomLevel = _zoomLevel.clamp(_minZoomLevel, _maxZoomLevel); } /// Maximum zoom level of the map layer. @@ -192,7 +197,7 @@ class MapZoomPanBehavior extends MapBehavior { return; } _maxZoomLevel = value; - zoomLevel = interpolateValue(zoomLevel, _minZoomLevel, _maxZoomLevel); + zoomLevel = _zoomLevel.clamp(_minZoomLevel, _maxZoomLevel); } /// Option to enable pinch zooming support. @@ -222,6 +227,18 @@ class MapZoomPanBehavior extends MapBehavior { _enablePanning = value; } + /// Enables double tap across the map layer. + /// + /// Defaults to `false`. + bool get enableDoubleTapZooming => _enableDoubleTapZooming; + bool _enableDoubleTapZooming; + set enableDoubleTapZooming(bool value) { + if (_enableDoubleTapZooming == value) { + return; + } + _enableDoubleTapZooming = value; + } + /// Shows zooming toolbar in the web platform. /// /// Defaults to `true` in web platform. @@ -274,8 +291,8 @@ class MapZoomPanBehavior extends MapBehavior { /// pointers in contact with the screen. void onZooming(MapZoomDetails details) { if (_controller != null) { - _controller.notifyZoomingListeners(details); - _controller.notifyListeners(); + _controller!.notifyZoomingListeners(details); + _controller!.notifyListeners(); } } @@ -302,16 +319,18 @@ class MapZoomPanBehavior extends MapBehavior { /// pointers in contact with the screen. void onPanning(MapPanDetails details) { if (_controller != null) { - _controller.notifyPanningListeners(details); - _controller.notifyListeners(); + _controller!.notifyPanningListeners(details); + _controller!.notifyListeners(); } } /// When this method is called, the map will be reset to the /// [MapZoomPanBehavior.minZoomLevel]. void reset() { - _controller.notifyResetListeners(); - _controller.notifyListeners(); + if (_controller != null) { + _controller!.notifyResetListeners(); + _controller!.notifyListeners(); + } } @override @@ -331,6 +350,11 @@ class MapZoomPanBehavior extends MapBehavior { ifTrue: 'Pinching is enabled', ifFalse: 'Pinching is disabled', showName: false)); + properties.add(FlagProperty('enableDoubleTapZooming', + value: enableDoubleTapZooming, + ifTrue: 'Double tap is enabled', + ifFalse: 'Double tap is disabled', + showName: false)); properties.add(DiagnosticsProperty('focalLatLng', focalLatLng)); properties.add(FlagProperty('showToolbar', value: showToolbar, @@ -372,12 +396,11 @@ class MapLatLng { other.longitude == longitude; } - /// Linearely interpolating between two latlngs. + /// Linearly interpolating between two [MapLatLng]. /// /// The arguments must not be null. - static MapLatLng lerp(MapLatLng a, MapLatLng b, double t) { - assert(t != null); - if (a == null && b == null) { + static MapLatLng? lerp(MapLatLng? a, MapLatLng? b, double t) { + if (a == null || b == null) { return null; } @@ -431,74 +454,87 @@ class MapLatLngBounds { class MapZoomDetails { /// Creates a [MapZoomDetails]. MapZoomDetails({ + this.newVisibleBounds, this.localFocalPoint, this.globalFocalPoint, this.previousZoomLevel, this.newZoomLevel, this.previousVisibleBounds, - this.newVisibleBounds, }); /// The global focal point of the pointers in contact with the screen. - final Offset globalFocalPoint; + final Offset? globalFocalPoint; /// The local focal point of the pointers in contact with the screen. - final Offset localFocalPoint; + final Offset? localFocalPoint; /// Provides the zoom level before the current zooming operation completes /// i.e. current zoom level. - final double previousZoomLevel; + final double? previousZoomLevel; /// Provides the new zoom level when the current zoom completes. /// /// Hence, if the `super.onZooming(details)` is not called, there will be no /// changes in the UI. - final double newZoomLevel; + final double? newZoomLevel; /// Provides the visible bounds before the current zooming operation completes /// i.e. current visible bounds. - final MapLatLngBounds previousVisibleBounds; + final MapLatLngBounds? previousVisibleBounds; /// Provides the new visible bounds when the current zoom completes. /// /// Hence, if the `super.onZooming(details)` is not called, there will be no /// changes in the UI. - final MapLatLngBounds newVisibleBounds; + final MapLatLngBounds? newVisibleBounds; + + /// Creates a copy of this class but with the given fields + /// replaced with the new values. + MapZoomDetails copyWith({double? newZoomLevel}) { + return MapZoomDetails( + localFocalPoint: localFocalPoint, + globalFocalPoint: globalFocalPoint, + previousZoomLevel: previousZoomLevel, + newZoomLevel: newZoomLevel ?? this.newZoomLevel, + previousVisibleBounds: previousVisibleBounds, + newVisibleBounds: newVisibleBounds, + ); + } } /// Contains details about the current pan position. class MapPanDetails { /// Creates a [MapPanDetails]. MapPanDetails({ + this.newVisibleBounds, this.zoomLevel, this.delta, this.previousVisibleBounds, - this.newVisibleBounds, this.globalFocalPoint, this.localFocalPoint, }); /// Provides the current zoom level. - final double zoomLevel; + final double? zoomLevel; /// The difference in pixels between touch start and current touch position. - final Offset delta; + final Offset? delta; /// Provides the visible bounds before the current panning operation /// completes i.e. current visible bounds. - final MapLatLngBounds previousVisibleBounds; + final MapLatLngBounds? previousVisibleBounds; /// Provides the new visible bounds when the current pan completes. /// /// Hence, if the `super.onPanning(details)` is not called, there will be no /// changes in the UI. - final MapLatLngBounds newVisibleBounds; + final MapLatLngBounds? newVisibleBounds; /// The global focal point of the pointers in contact with the screen. - final Offset globalFocalPoint; + final Offset? globalFocalPoint; /// The local focal point of the pointers in contact with the screen. - final Offset localFocalPoint; + final Offset? localFocalPoint; } /// Render object widget of the internal widget which handles @@ -506,9 +542,9 @@ class MapPanDetails { class BehaviorViewRenderObjectWidget extends LeafRenderObjectWidget { /// Creates [BehaviorViewRenderObjectWidget]. const BehaviorViewRenderObjectWidget({ - Key key, - this.controller, - this.zoomPanBehavior, + Key? key, + required this.controller, + required this.zoomPanBehavior, }) : super(key: key); /// Used to coordinate with [MapShapeLayer] and its elements. @@ -536,9 +572,9 @@ class BehaviorViewRenderObjectWidget extends LeafRenderObjectWidget { class _RenderBehaviorView extends RenderBox { _RenderBehaviorView({ - MapController listener, - MapZoomPanBehavior zoomPanBehavior, - }) : controller = listener, + required MapController listener, + required MapZoomPanBehavior zoomPanBehavior, + }) : controller = listener, _zoomPanBehavior = zoomPanBehavior { _zoomPanBehavior._renderBox = this; _zoomPanBehavior._controller = controller; @@ -586,7 +622,7 @@ class _RenderBehaviorView extends RenderBox { @override void paint(PaintingContext context, Offset offset) { - zoomPanBehavior?.paint(context, offset); + zoomPanBehavior.paint(context, offset); super.paint(context, offset); } } diff --git a/packages/syncfusion_flutter_maps/lib/src/common.dart b/packages/syncfusion_flutter_maps/lib/src/common.dart new file mode 100644 index 000000000..25dcfb032 --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/common.dart @@ -0,0 +1,356 @@ +import 'package:flutter/foundation.dart' show DiagnosticableTree; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../maps.dart'; +import 'controller/map_controller.dart'; +import 'enum.dart'; +import 'layer/layer_base.dart'; +import 'utils.dart'; + +// ignore_for_file: public_member_api_docs +enum Gesture { scale, pan } + +enum MarkerAction { insert, removeAt, replace, clear } + +enum LayerType { shape, tile } + +/// Specifies the layer for tooltip. +enum MapLayerElement { + /// Shows tooltip for shape layer. + shape, + + /// Shows tooltip for bubble. + bubble, + + /// Shows tooltip for marker. + marker, + + /// Shows tooltip for vector layers. + vector, +} + +/// Specifies the kind of pointer. +enum PointerKind { + /// Indicates the pointer kind as touch. + touch, + + /// Indicates the pointer kind as hover. + hover +} + +/// Provides the information about the current and previous zoom level. +class TileZoomLevelDetails { + /// Represents the visual tiles origin. + Offset? origin; + + /// Represents the tile zoom level. + late double zoomLevel; + + /// Provides the distance moved from the origin when doing pinch zooming. + late Offset translatePoint; + + /// Represents the fractional zoom value. + late double scale; +} + +class MapModel { + MapModel({ + required this.primaryKey, + required this.actualIndex, + required this.rawPoints, + this.pixelPoints, + this.dataIndex, + this.legendMapperIndex, + this.shapePath, + this.isSelected = false, + this.shapeColor, + this.dataLabelText, + this.visibleDataLabelText, + this.bubbleColor, + this.bubbleSizeValue, + this.bubbleRadius, + this.bubblePath, + this.tooltipText, + }); + + /// Contains [sourceDataPath] values. + late String primaryKey; + + /// Contains data Source index. + int? dataIndex; + + /// Specifies an item index in the data source list. + late int actualIndex; + + /// Contains the index of the shape or bubble legend. + int? legendMapperIndex; + + /// Contains pixel points. + List>? pixelPoints; + + /// Contains coordinate points. + late List> rawPoints; + + /// Option to select the particular shape + /// based on the position of the tap or click. + /// + /// Returns `true` when the shape is selected else it will be `false`. + /// + /// See also: + /// * [MapSelectionSettings] option to customize the shape selection. + bool isSelected = false; + + /// Contains data source Label text. + String? dataLabelText; + + /// Use this text while zooming. + String? visibleDataLabelText; + + /// Contains the tooltip text. + String? tooltipText; + + /// Contains data source color. + Color? shapeColor; + + /// Contains the actual shape color value. + dynamic? shapeColorValue; + + /// Contains the shape path. + Path? shapePath; + + // Center of shape path which is used to position the data labels. + Offset? shapePathCenter; + + // Width for smart position the data labels. + double? shapeWidth; + + /// Contains data source bubble color. + Color? bubbleColor; + + /// Contains data source bubble size. + double? bubbleSizeValue; + + /// Contains data source bubble radius. + double? bubbleRadius; + + /// Contains the bubble path. + Path? bubblePath; + + void reset() { + dataIndex = null; + isSelected = false; + dataLabelText = null; + visibleDataLabelText = null; + tooltipText = null; + shapeColor = null; + shapeColorValue = null; + shapePath = null; + shapePathCenter = null; + shapeWidth = null; + bubbleColor = null; + bubbleSizeValue = null; + bubbleRadius = null; + bubblePath = null; + } +} + +/// Base class for shape and tile layer for internal usage. +class MapLayerInheritedWidget extends InheritedWidget { + /// Creates [MapLayerInheritedWidget]. + const MapLayerInheritedWidget( + {required Widget child, required this.controller, this.sublayers}) + : super(child: child); + + /// Creates [MapController]. + final MapController controller; + + /// Collection of [MapShapeSublayer], [MapLineLayer], [MapPolylineLayer], + /// [MapPolygonLayer], [MapCircleLayer], and [MapArcLayer]. + final List? sublayers; + + @override + bool updateShouldNotify(covariant InheritedWidget oldWidget) { + return false; + } +} + +/// Adds [MapSublayer] into a stack widget. +class SublayerContainer extends Stack { + /// Creates a [SublayerContainer]. + SublayerContainer({ + required this.ancestor, + required List children, + }) : super(children: children); + + final MapLayerInheritedWidget ancestor; + + @override + RenderStack createRenderObject(BuildContext context) { + return RenderSublayerContainer(container: this, context: context); + } +} + +/// Adds [MapSublayer] into a stack widget. +class RenderSublayerContainer extends RenderStack { + /// Creates a [RenderSublayerContainer]. + RenderSublayerContainer({ + required this.container, + required this.context, + }) : super(textDirection: Directionality.of(context)); + + /// The build context. + final BuildContext context; + + final SublayerContainer container; + + @override + void performLayout() { + size = getBoxSize(constraints); + RenderBox? child = firstChild; + while (child != null) { + final StackParentData childParentData = + // ignore: avoid_as + child.parentData as StackParentData; + child.layout(constraints); + child = childParentData.nextSibling; + } + } +} + +class ShapeLayerChildRenderBoxBase extends RenderProxyBox { + void onHover(MapModel? item, MapLayerElement? element) { + // Handle hover interaction here. + } + + void paintTooltip(int? elementIndex, Rect? elementRect, + MapLayerElement? element, PointerKind kind, + [int? sublayerIndex, Offset? position]) {} + + void onExit() { + // Handle exit interaction here. + } + + void refresh() { + /// Refresh/repaint the current view. + } + + void hideTooltip({bool immediately = false}) { + // Hides the tooltip. + } +} + +class MapIconShape { + const MapIconShape(); + + /// Returns the size based on the value passed to it. + Size getPreferredSize(Size iconSize, SfMapsThemeData? themeData) { + return iconSize; + } + + /// Paints the shapes based on the value passed to it. + void paint( + PaintingContext context, + Offset offset, { + required RenderBox parentBox, + required SfMapsThemeData themeData, + required Size iconSize, + required Color color, + required double strokeWidth, + required MapIconType iconType, + Color? strokeColor, + }) { + iconSize = getPreferredSize(iconSize, themeData); + final double halfIconWidth = iconSize.width / 2; + final double halfIconHeight = iconSize.height / 2; + final bool hasStroke = strokeWidth > 0 && + strokeColor != null && + strokeColor != Colors.transparent; + final Paint paint = Paint() + ..isAntiAlias = true + ..color = color; + Path path; + + switch (iconType) { + case MapIconType.circle: + final Rect rect = Rect.fromLTWH( + offset.dx, offset.dy, iconSize.width, iconSize.height); + context.canvas.drawOval(rect, paint); + if (hasStroke) { + paint + ..strokeWidth = strokeWidth + ..color = strokeColor + ..style = PaintingStyle.stroke; + context.canvas.drawOval(rect, paint); + } + break; + case MapIconType.rectangle: + final Rect rect = Rect.fromLTWH( + offset.dx, offset.dy, iconSize.width, iconSize.height); + context.canvas.drawRect(rect, paint); + if (hasStroke) { + paint + ..strokeWidth = strokeWidth + ..color = strokeColor + ..style = PaintingStyle.stroke; + context.canvas.drawRect(rect, paint); + } + break; + case MapIconType.triangle: + path = Path() + ..moveTo(offset.dx + halfIconWidth, offset.dy) + ..lineTo(offset.dx + iconSize.width, offset.dy + iconSize.height) + ..lineTo(offset.dx, offset.dy + iconSize.height) + ..close(); + context.canvas.drawPath(path, paint); + if (hasStroke) { + paint + ..strokeWidth = strokeWidth + ..color = strokeColor + ..style = PaintingStyle.stroke; + context.canvas.drawPath(path, paint); + } + break; + case MapIconType.diamond: + path = Path() + ..moveTo(offset.dx + halfIconWidth, offset.dy) + ..lineTo(offset.dx + iconSize.width, offset.dy + halfIconHeight) + ..lineTo(offset.dx + halfIconWidth, offset.dy + iconSize.height) + ..lineTo(offset.dx, offset.dy + halfIconHeight) + ..close(); + context.canvas.drawPath(path, paint); + if (hasStroke) { + paint + ..strokeWidth = strokeWidth + ..color = strokeColor + ..style = PaintingStyle.stroke; + context.canvas.drawPath(path, paint); + } + break; + } + } +} + +class DebugSublayerTree extends DiagnosticableTree { + DebugSublayerTree(this.sublayers); + + final List sublayers; + + @override + List debugDescribeChildren() { + if (sublayers.isNotEmpty) { + return sublayers.map((MapSublayer sublayer) { + return sublayer.toDiagnosticsNode(); + }).toList(); + } + return super.debugDescribeChildren(); + } + + @override + String toStringShort() { + return sublayers.length > 1 + ? 'contains ${sublayers.length} sublayers' + : 'contains ${sublayers.length} sublayer'; + } +} diff --git a/packages/syncfusion_flutter_maps/lib/src/controller/default_controller.dart b/packages/syncfusion_flutter_maps/lib/src/controller/map_controller.dart similarity index 71% rename from packages/syncfusion_flutter_maps/lib/src/controller/default_controller.dart rename to packages/syncfusion_flutter_maps/lib/src/controller/map_controller.dart index e54920ddc..8750b14f0 100644 --- a/packages/syncfusion_flutter_maps/lib/src/controller/default_controller.dart +++ b/packages/syncfusion_flutter_maps/lib/src/controller/map_controller.dart @@ -7,8 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import '../behavior/zoom_pan_behavior.dart'; -import '../elements/shapes.dart'; -import '../layer/tile_layer.dart'; +import '../common.dart'; import '../utils.dart'; // ignore_for_file: public_member_api_docs @@ -18,44 +17,45 @@ typedef _PanningCallback = void Function(MapPanDetails details); typedef _ZoomToCallback = void Function(double factor, {MapLatLng latlng}); -typedef _PanToCallback = void Function(MapLatLng latlng); +typedef _PanToCallback = void Function(MapLatLng? latlng); class MapController { final List _toggledIndices = []; List get toggledIndices => _toggledIndices; - ObserverList _listeners = ObserverList(); - ObserverList<_ZoomingCallback> _zoomingListeners = + ObserverList? _listeners = ObserverList(); + ObserverList<_ZoomingCallback>? _zoomingListeners = ObserverList<_ZoomingCallback>(); - ObserverList<_PanningCallback> _panningListeners = + ObserverList<_PanningCallback>? _panningListeners = ObserverList<_PanningCallback>(); - ObserverList _refreshListeners = ObserverList(); - ObserverList _resetListeners = ObserverList(); - ObserverList _toggledListeners = ObserverList(); + ObserverList? _refreshListeners = ObserverList(); + ObserverList? _resetListeners = ObserverList(); + ObserverList? _toggledListeners = ObserverList(); - _ZoomToCallback onZoomLevelChange; - _PanToCallback onPanChange; - - GlobalKey tooltipKey; - Size shapeLayerBoxSize; double shapeLayerSizeFactor = 1.0; - Offset shapeLayerOffset; - Offset shapeLayerOrigin = Offset.zero; - Gesture gesture; - MapLatLng visibleFocalLatLng; - Rect visibleBounds; - MapLatLngBounds visibleLatLngBounds; double localScale = 1.0; + double tileLayerLocalScale = 1.0; bool isInInteractive = false; Offset pinchCenter = Offset.zero; Offset panDistance = Offset.zero; Offset normalize = Offset.zero; + Offset shapeLayerOrigin = Offset.zero; Rect currentBounds = Rect.zero; - double tileLayerZoomLevel; - MapZoomLevel tileCurrentLevelDetails; - Size totalTileSize; - Size tileLayerBoxSize; - bool isTileLayerChild = false; - double tileLayerLocalScale = 1.0; + + late _ZoomToCallback onZoomLevelChange; + late _PanToCallback onPanChange; + late Size shapeLayerBoxSize; + late Offset shapeLayerOffset; + late double tileZoomLevel; + late TileZoomLevelDetails tileCurrentLevelDetails; + + GlobalKey? tooltipKey; + Size? tileLayerBoxSize; + Gesture? gesture; + MapLatLng? visibleFocalLatLng; + Rect? visibleBounds; + MapLatLngBounds? visibleLatLngBounds; + Size? totalTileSize; + LayerType? layerType; // Stores the index of current tapped item which is used // to identify whether the legend item is toggled or un-toggled. @@ -73,7 +73,7 @@ class MapController { } void addToggleListener(VoidCallback listener) { - _toggledListeners.add(listener); + _toggledListeners?.add(listener); } void removeToggleListener(VoidCallback listener) { @@ -81,7 +81,7 @@ class MapController { } void notifyToggleListeners() { - for (final VoidCallback listener in _toggledListeners) { + for (final VoidCallback listener in _toggledListeners!) { listener(); } } @@ -91,7 +91,7 @@ class MapController { } void addZoomingListener(_ZoomingCallback listener) { - _zoomingListeners.add(listener); + _zoomingListeners?.add(listener); } void removeZoomingListener(_ZoomingCallback listener) { @@ -99,7 +99,7 @@ class MapController { } void addPanningListener(_PanningCallback listener) { - _panningListeners.add(listener); + _panningListeners?.add(listener); } void removePanningListener(_PanningCallback listener) { @@ -107,69 +107,69 @@ class MapController { } void addResetListener(VoidCallback listener) { - _resetListeners.add(listener); + _resetListeners?.add(listener); } void removeResetListener(VoidCallback listener) { - _resetListeners.remove(listener); + _resetListeners?.remove(listener); } void addRefreshListener(VoidCallback listener) { - _refreshListeners.add(listener); + _refreshListeners?.add(listener); } void removeRefreshListener(VoidCallback listener) { - _refreshListeners.remove(listener); + _refreshListeners?.remove(listener); } void addListener(VoidCallback listener) { - _listeners.add(listener); + _listeners?.add(listener); } void removeListener(VoidCallback listener) { - _listeners.remove(listener); + _listeners?.remove(listener); } void notifyZoomingListeners(MapZoomDetails details) { - for (final _ZoomingCallback listener in _zoomingListeners) { + for (final _ZoomingCallback listener in _zoomingListeners!) { listener(details); } } void notifyPanningListeners(MapPanDetails details) { - for (final _PanningCallback listener in _panningListeners) { + for (final _PanningCallback listener in _panningListeners!) { listener(details); } } void notifyResetListeners() { - for (final VoidCallback listener in _resetListeners) { + for (final VoidCallback listener in _resetListeners!) { listener(); } } void notifyRefreshListeners() { - for (final VoidCallback listener in _refreshListeners) { + for (final VoidCallback listener in _refreshListeners!) { listener(); } } void notifyListeners() { - for (final VoidCallback listener in _listeners) { + for (final VoidCallback listener in _listeners!) { listener(); } } - void updateVisibleBounds([Offset translation, double factor]) { + void updateVisibleBounds([Offset? translation, double? factor]) { factor ??= shapeLayerSizeFactor; translation ??= shapeLayerOffset; visibleFocalLatLng = getVisibleFocalLatLng(translation, factor); visibleBounds = getVisibleBounds(translation, factor, visibleFocalLatLng); - visibleLatLngBounds = getVisibleLatLngBounds( - visibleBounds.topRight, visibleBounds.bottomLeft, translation, factor); + visibleLatLngBounds = getVisibleLatLngBounds(visibleBounds!.topRight, + visibleBounds!.bottomLeft, translation, factor); } - MapLatLng getVisibleFocalLatLng([Offset translation, double factor]) { + MapLatLng getVisibleFocalLatLng([Offset? translation, double? factor]) { factor ??= shapeLayerSizeFactor; translation ??= shapeLayerOffset; return pixelToLatLng( @@ -180,14 +180,14 @@ class MapController { } Rect getVisibleBounds( - [Offset translation, double factor, MapLatLng focalLatLng]) { + [Offset? translation, double? factor, MapLatLng? focalLatLng]) { factor ??= shapeLayerSizeFactor; translation ??= shapeLayerOffset; focalLatLng ??= getVisibleFocalLatLng(translation, factor); return Rect.fromCenter( center: pixelFromLatLng( - visibleFocalLatLng.latitude, - visibleFocalLatLng.longitude, + visibleFocalLatLng!.latitude, + visibleFocalLatLng!.longitude, shapeLayerBoxSize, translation, factor), @@ -196,7 +196,7 @@ class MapController { } MapLatLngBounds getVisibleLatLngBounds(Offset topRight, Offset bottomLeft, - [Offset translation, double factor]) { + [Offset? translation, double? factor]) { factor ??= shapeLayerSizeFactor; translation ??= shapeLayerOffset; return MapLatLngBounds( @@ -205,7 +205,7 @@ class MapController { } Offset getZoomingTranslation( - {Offset origin, double scale, Offset previousOrigin}) { + {Offset? origin, double? scale, Offset? previousOrigin}) { origin ??= pinchCenter; scale ??= localScale; previousOrigin ??= shapeLayerOffset; @@ -218,14 +218,14 @@ class MapController { return previousOrigin + Offset(dx, dy); } - Size getTileSize({double zoomLevel}) { - zoomLevel ??= tileLayerZoomLevel; - return Size.square(256 * pow(2, zoomLevel)); + Size getTileSize([double? zoomLevel]) { + zoomLevel ??= tileZoomLevel; + return Size.square(256 * pow(2, zoomLevel).toDouble()); } void applyTransform(PaintingContext context, Offset offset, [bool isVectorLayer = false]) { - if (isVectorLayer && tileCurrentLevelDetails != null) { + if (isVectorLayer && layerType == LayerType.tile) { context.canvas ..translate(offset.dx + tileCurrentLevelDetails.translatePoint.dx, offset.dy + tileCurrentLevelDetails.translatePoint.dy) diff --git a/packages/syncfusion_flutter_maps/lib/src/controller/map_provider.dart b/packages/syncfusion_flutter_maps/lib/src/controller/map_provider.dart new file mode 100644 index 000000000..e94b5b235 --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/controller/map_provider.dart @@ -0,0 +1,163 @@ +import 'dart:convert' show utf8; +import 'dart:typed_data' show Uint8List; + +import 'package:flutter/services.dart' show rootBundle; +import 'package:http/http.dart' as http; + +import 'package:flutter/material.dart'; + +/// Converts the given source file to future string based on source type. +abstract class MapProvider { + /// Abstract const constructor. This constructor enables subclasses to provide + /// const constructors so that they can be used in const expressions. + const MapProvider(); + + /// Returns the json file as future string value. + Future loadString(); + + /// Returns shape path which is given. + String? get shapePath; + + /// Returns shape bytes which is given. + Uint8List? get bytes; +} + +/// Decodes the given json file as a map. +/// +/// This class behaves like similar to [Image.asset]. +/// +/// See also: +/// +/// [MapShapeSource.asset] for the [SfMaps] widget shorthand, +/// backed up by [AssetMapProvider]. +class AssetMapProvider extends MapProvider { + /// Creates an object that decodes a [String] buffer as a map. + AssetMapProvider(String assetName) : assert(assetName.isNotEmpty) { + _shapePath = assetName; + } + + late String _shapePath; + + @override + Future loadString() async { + return await rootBundle.loadString(_shapePath); + } + + @override + String? get shapePath => _shapePath; + + @override + Uint8List? get bytes => null; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is AssetMapProvider && other.shapePath == shapePath; + } + + @override + int get hashCode => hashValues(shapePath, bytes); +} + +// Decodes the given map URL from the network. +/// +/// The map will be fetched and saved in local temporary directory for map +/// manipulation. +/// +/// This class behaves like similar to [Image.network]. +/// +/// See also: +/// +/// [MapShapeSource.network] for the [SfMaps] widget shorthand, +/// backed up by [NetworkMapProvider]. +class NetworkMapProvider extends MapProvider { + /// Creates an object that decodes the map at the given URL. + NetworkMapProvider(String url) : assert(url.isNotEmpty) { + _url = url; + } + + late String _url; + + @override + Future loadString() async { + final response = await http.get(Uri.tryParse(_url)!); + if (response.statusCode == 200) { + return response.body; + } else { + throw Exception('Failed to load JSON'); + } + } + + @override + String? get shapePath => _url; + + @override + Uint8List? get bytes => null; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is NetworkMapProvider && other.shapePath == shapePath; + } + + @override + int get hashCode => hashValues(shapePath, bytes); +} + +/// Decodes the given [Uint8List] buffer as an map. +/// +/// The provided [bytes] buffer should not be changed after it is provided +/// to a [MemoryMapProvider]. +/// +/// This class behaves like similar to [Image.memory]. +/// +/// See also: +/// +/// [MapShapeSource.memory] for the [SfMaps] widget shorthand, +/// backed up by [MemoryMapProvider]. +class MemoryMapProvider extends MapProvider { + /// Creates an object that decodes a [Uint8List] buffer as a map. + MemoryMapProvider(Uint8List bytes) { + _mapBytes = bytes; + } + + late Uint8List _mapBytes; + + @override + Future loadString() async { + return utf8.decode(_mapBytes); + } + + @override + String? get shapePath => null; + + @override + Uint8List? get bytes => _mapBytes; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is MemoryMapProvider && other.bytes == bytes; + } + + @override + int get hashCode => hashValues(shapePath, bytes); +} diff --git a/packages/syncfusion_flutter_maps/lib/src/controller/shape_layer_controller.dart b/packages/syncfusion_flutter_maps/lib/src/controller/shape_layer_controller.dart index 3471bd7ac..244b78f66 100644 --- a/packages/syncfusion_flutter_maps/lib/src/controller/shape_layer_controller.dart +++ b/packages/syncfusion_flutter_maps/lib/src/controller/shape_layer_controller.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; -import '../utils.dart'; +import '../common.dart'; -typedef _ListenerEntry = void Function( - [MarkerAction action, List indices]); +typedef _ListenerEntry = void Function(MarkerAction action, + [List? indices]); /// Base class of [MapShapeLayerController] and [MapTileLayerController]. abstract class MapLayerController extends ChangeNotifier { - ObserverList<_ListenerEntry> _listeners = ObserverList<_ListenerEntry>(); + ObserverList<_ListenerEntry>? _listeners = ObserverList<_ListenerEntry>(); /// Adds marker dynamically in the provided index. /// @@ -40,8 +40,8 @@ abstract class MapLayerController extends ChangeNotifier { } /// Call all the registered listeners. - void _notifyMarkerListeners([MarkerAction action, List indices]) { - for (final _ListenerEntry listener in _listeners) { + void _notifyMarkerListeners(MarkerAction action, [List? indices]) { + for (final _ListenerEntry listener in _listeners!) { listener(action, indices); } } @@ -49,18 +49,20 @@ abstract class MapLayerController extends ChangeNotifier { @override void addListener(Object listener) { if (listener is _ListenerEntry) { - _listeners.add(listener); + _listeners!.add(listener); } else { - super.addListener(listener); + // ignore: avoid_as + super.addListener(listener as VoidCallback); } } @override void removeListener(Object listener) { if (listener is _ListenerEntry) { - _listeners.remove(listener); + _listeners!.remove(listener); } else { - super.addListener(listener); + // ignore: avoid_as + super.removeListener(listener as VoidCallback); } } diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart b/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart index 831958737..5917ea722 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart @@ -6,38 +6,38 @@ import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../behavior/zoom_pan_behavior.dart'; -import '../controller/default_controller.dart'; +import '../common.dart'; +import '../controller/map_controller.dart'; import '../elements/legend.dart'; import '../enum.dart'; import '../layer/shape_layer.dart'; import '../settings.dart'; import '../utils.dart'; -import 'shapes.dart'; // ignore_for_file: public_member_api_docs class MapBubble extends LeafRenderObjectWidget { const MapBubble({ - Key key, - this.source, - this.mapDataSource, - this.bubbleSettings, - this.legend, - this.showDataLabels, - this.themeData, - this.controller, - this.bubbleAnimationController, - this.dataLabelAnimationController, - this.toggleAnimationController, - this.hoverBubbleAnimationController, + Key? key, + required this.source, + required this.mapDataSource, + required this.bubbleSettings, + required this.legend, + required this.showDataLabels, + required this.themeData, + required this.controller, + required this.bubbleAnimationController, + required this.dataLabelAnimationController, + required this.toggleAnimationController, + required this.hoverBubbleAnimationController, }) : super(key: key); final MapShapeSource source; final Map mapDataSource; final MapBubbleSettings bubbleSettings; - final MapLayerLegend legend; + final MapLegendWidget? legend; final bool showDataLabels; final SfMapsThemeData themeData; - final MapController controller; + final MapController? controller; final AnimationController bubbleAnimationController; final AnimationController dataLabelAnimationController; final AnimationController toggleAnimationController; @@ -78,18 +78,18 @@ class MapBubble extends LeafRenderObjectWidget { class RenderMapBubble extends ShapeLayerChildRenderBoxBase { RenderMapBubble({ - MapShapeSource source, - Map mapDataSource, - MapBubbleSettings bubbleSettings, - MapLayerLegend legend, - bool showDataLabels, - SfMapsThemeData themeData, - MapController controller, - AnimationController bubbleAnimationController, - AnimationController dataLabelAnimationController, - AnimationController toggleAnimationController, - AnimationController hoverBubbleAnimationController, - }) : _source = source, + required MapShapeSource source, + required Map mapDataSource, + required MapBubbleSettings bubbleSettings, + required MapLegendWidget? legend, + required bool showDataLabels, + required SfMapsThemeData themeData, + required MapController? controller, + required AnimationController bubbleAnimationController, + required AnimationController dataLabelAnimationController, + required AnimationController toggleAnimationController, + required AnimationController hoverBubbleAnimationController, + }) : _source = source, mapDataSource = mapDataSource, _bubbleSettings = bubbleSettings, _legend = legend, @@ -123,34 +123,34 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { _hoverBubbleAnimation = CurvedAnimation( parent: hoverBubbleAnimationController, curve: Curves.easeInOut); - if (_legend != null && _legend.enableToggleInteraction) { + if (_legend != null && _legend!.enableToggleInteraction) { _initializeToggledBubbleTweenColors(); } if (_themeData.bubbleHoverColor != Colors.transparent || _themeData.bubbleHoverStrokeColor != Colors.transparent || - _themeData.bubbleHoverStrokeWidth > 0.0) { + _themeData.bubbleHoverStrokeWidth! > 0.0) { _initializeHoverBubbleTweenColors(); } } - Animation _bubbleAnimation; - Animation _toggleBubbleAnimation; - Animation _hoverBubbleAnimation; - MapModel _currentHoverItem; - MapModel _previousHoverItem; - ColorTween _forwardToggledBubbleColorTween; - ColorTween _forwardToggledBubbleStrokeColorTween; - ColorTween _reverseToggledBubbleColorTween; - ColorTween _reverseToggledBubbleStrokeColorTween; - ColorTween _forwardBubbleHoverColorTween; - ColorTween _forwardBubbleHoverStrokeColorTween; - ColorTween _reverseBubbleHoverColorTween; - ColorTween _reverseBubbleHoverStrokeColorTween; + late Animation _bubbleAnimation; + late Animation _toggleBubbleAnimation; + late Animation _hoverBubbleAnimation; + MapModel? _currentHoverItem; + MapModel? _previousHoverItem; + late ColorTween _forwardToggledBubbleColorTween; + late ColorTween _forwardToggledBubbleStrokeColorTween; + late ColorTween _reverseToggledBubbleColorTween; + late ColorTween _reverseToggledBubbleStrokeColorTween; + late ColorTween _forwardBubbleHoverColorTween; + late ColorTween _forwardBubbleHoverStrokeColorTween; + late ColorTween _reverseBubbleHoverColorTween; + late ColorTween _reverseBubbleHoverStrokeColorTween; Map mapDataSource; bool showDataLabels; - MapController controller; + MapController? controller; AnimationController bubbleAnimationController; AnimationController dataLabelAnimationController; AnimationController toggleAnimationController; @@ -185,30 +185,31 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { return; } _themeData = value; - if (_legend != null && _legend.enableToggleInteraction) { + if (_legend != null && _legend!.enableToggleInteraction) { _initializeToggledBubbleTweenColors(); } if (_themeData.bubbleHoverColor != Colors.transparent || _themeData.bubbleHoverStrokeColor != Colors.transparent || - _themeData.bubbleHoverStrokeWidth > 0.0) { + (_themeData.bubbleHoverStrokeWidth != null && + _themeData.bubbleHoverStrokeWidth! > 0.0)) { _initializeHoverBubbleTweenColors(); } markNeedsPaint(); } - MapLayerLegend get legend => _legend; - MapLayerLegend _legend; - set legend(MapLayerLegend value) { + MapLegendWidget? get legend => _legend; + MapLegendWidget? _legend; + set legend(MapLegendWidget? value) { // Update [MapsShapeLayer.legend] value only when // [MapsShapeLayer.legend] property is set to bubble. - if (_legend != null && _legend.source != MapElement.bubble || + if (_legend != null && _legend!.source != MapElement.bubble || _legend == value) { return; } _legend = value; - if (_legend.enableToggleInteraction) { + if (_legend!.enableToggleInteraction) { _initializeToggledBubbleTweenColors(); } @@ -216,19 +217,19 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { } bool get hasDefaultStroke => - _bubbleSettings.strokeWidth > 0 && + _bubbleSettings.strokeWidth! > 0 && _bubbleSettings.strokeColor != null && _bubbleSettings.strokeColor != Colors.transparent; bool get hasHoverStroke => - _themeData.bubbleHoverStrokeWidth > 0 && + _themeData.bubbleHoverStrokeWidth! > 0 && _themeData.bubbleHoverStrokeColor != Colors.transparent; bool get hasToggledStroke => _legend != null && - _legend.toggledItemStrokeWidth > 0 && - _legend.toggledItemStrokeColor != null && - _legend.toggledItemStrokeColor != Colors.transparent; + _legend!.toggledItemStrokeWidth > 0 && + _legend!.toggledItemStrokeColor != null && + _legend!.toggledItemStrokeColor != Colors.transparent; void _handleZooming(MapZoomDetails details) { if (_currentHoverItem != null) { @@ -256,7 +257,7 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { bool get isRepaintBoundary => true; @override - void onHover(MapModel item, MapLayerElement element) { + void onHover(MapModel? item, MapLayerElement? element) { if (element == MapLayerElement.bubble && _currentHoverItem != item) { _previousHoverItem = _currentHoverItem; _currentHoverItem = item; @@ -276,20 +277,20 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { void _updateHoverItemTween() { final double opacity = - _bubbleAnimation.value * _bubbleSettings.color.opacity; - final Color defaultColor = bubbleSettings.color.withOpacity(opacity); + _bubbleAnimation.value * _bubbleSettings.color!.opacity; + final Color defaultColor = bubbleSettings.color!.withOpacity(opacity); if (_currentHoverItem != null) { _forwardBubbleHoverColorTween.begin = - _currentHoverItem.bubbleColor ?? defaultColor; + _currentHoverItem!.bubbleColor ?? defaultColor; _forwardBubbleHoverColorTween.end = - _getHoverFillColor(opacity, defaultColor, _currentHoverItem); + _getHoverFillColor(opacity, defaultColor, _currentHoverItem!); } if (_previousHoverItem != null) { _reverseBubbleHoverColorTween.begin = - _getHoverFillColor(opacity, defaultColor, _previousHoverItem); + _getHoverFillColor(opacity, defaultColor, _previousHoverItem!); _reverseBubbleHoverColorTween.end = - _previousHoverItem.bubbleColor ?? defaultColor; + _previousHoverItem!.bubbleColor ?? defaultColor; } hoverBubbleAnimationController.forward(from: 0.0); @@ -298,12 +299,12 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { Color _getHoverFillColor(double opacity, Color defaultColor, MapModel model) { final Color bubbleColor = model.bubbleColor ?? defaultColor; final bool canAdjustHoverOpacity = (model.bubbleColor != null && - double.parse(model.bubbleColor.opacity.toStringAsFixed(2)) != + double.parse(model.bubbleColor!.opacity.toStringAsFixed(2)) != hoverColorOpacity) || - _bubbleSettings.color.opacity != hoverColorOpacity; + _bubbleSettings.color!.opacity != hoverColorOpacity; return _themeData.bubbleHoverColor != null && _themeData.bubbleHoverColor != Colors.transparent - ? _themeData.bubbleHoverColor + ? _themeData.bubbleHoverColor! : bubbleColor.withOpacity( canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); } @@ -319,15 +320,10 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { _bubbleAnimation ..addListener(markNeedsPaint) ..addStatusListener(_handleAnimationStatusChange); - toggleAnimationController?.addListener(markNeedsPaint); - hoverBubbleAnimationController?.addListener(markNeedsPaint); - if (controller == null) { - final RenderShapeLayer shapeLayerRenderBox = parent; - controller = shapeLayerRenderBox.controller; - } - + toggleAnimationController.addListener(markNeedsPaint); + hoverBubbleAnimationController.addListener(markNeedsPaint); if (controller != null) { - controller + controller! ..addToggleListener(_handleToggleChange) ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) @@ -338,7 +334,7 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { void _handleAnimationStatusChange(AnimationStatus status) { if (status == AnimationStatus.completed && showDataLabels) { - dataLabelAnimationController?.forward(); + dataLabelAnimationController.forward(); } } @@ -347,10 +343,10 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { _bubbleAnimation ..removeListener(markNeedsPaint) ..removeStatusListener(_handleAnimationStatusChange); - toggleAnimationController?.removeListener(markNeedsPaint); - hoverBubbleAnimationController?.removeListener(markNeedsPaint); + toggleAnimationController.removeListener(markNeedsPaint); + hoverBubbleAnimationController.removeListener(markNeedsPaint); if (controller != null) { - controller + controller! ..removeToggleListener(_handleToggleChange) ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) @@ -362,16 +358,16 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { } void _handleToggleChange() { - if (legend.source == MapElement.bubble) { + if (legend!.source == MapElement.bubble) { _updateToggledBubbleTweenColor(); toggleAnimationController.forward(from: 0); } } void _initializeToggledBubbleTweenColors() { - final Color toggledBubbleColor = _themeData.toggledItemColor != + final Color? toggledBubbleColor = _themeData.toggledItemColor != Colors.transparent - ? _themeData.toggledItemColor.withOpacity(_legend.toggledItemOpacity) + ? _themeData.toggledItemColor.withOpacity(_legend!.toggledItemOpacity) : null; _forwardToggledBubbleColorTween.end = toggledBubbleColor; @@ -394,85 +390,80 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { } Color _getHoverStrokeColor() { - final Color bubbleStrokeColor = _bubbleSettings.strokeColor; + final Color bubbleStrokeColor = _bubbleSettings.strokeColor!; final bool canAdjustHoverOpacity = double.parse(bubbleStrokeColor.opacity.toStringAsFixed(2)) != hoverColorOpacity; - return _themeData.bubbleHoverStrokeColor != null ?? + return _themeData.bubbleHoverStrokeColor != null && _themeData.bubbleHoverStrokeColor != Colors.transparent - ? _themeData.bubbleHoverStrokeColor + ? _themeData.bubbleHoverStrokeColor! : bubbleStrokeColor.withOpacity( canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); } void _updateToggledBubbleTweenColor() { - if (mapDataSource != null) { - MapModel model; - if (source.bubbleColorMappers == null) { - model = - mapDataSource.values.elementAt(controller.currentToggledItemIndex); - } else { - for (final mapModel in mapDataSource.values) { - if (mapModel.dataIndex != null && - mapModel.legendMapperIndex == - controller.currentToggledItemIndex) { - model = mapModel; - break; - } + late MapModel model; + if (source.bubbleColorMappers == null) { + model = + mapDataSource.values.elementAt(controller!.currentToggledItemIndex); + } else { + for (final mapModel in mapDataSource.values) { + if (mapModel.dataIndex != null && + mapModel.legendMapperIndex == controller!.currentToggledItemIndex) { + model = mapModel; + break; } } - - final Color bubbleColor = model.bubbleColor ?? _themeData.bubbleColor; - _forwardToggledBubbleColorTween.begin = bubbleColor; - _reverseToggledBubbleColorTween.end = bubbleColor; } + + final Color bubbleColor = model.bubbleColor ?? _themeData.bubbleColor; + _forwardToggledBubbleColorTween.begin = bubbleColor; + _reverseToggledBubbleColorTween.end = bubbleColor; } @override void paint(PaintingContext context, Offset offset) { - if (mapDataSource != null) { - final Rect bounds = - Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height); - context.canvas - ..save() - ..clipRect(bounds); - controller.applyTransform(context, offset); - - final double opacity = - _bubbleAnimation.value * _bubbleSettings.color.opacity; - final Color defaultColor = bubbleSettings.color.withOpacity(opacity); - final bool hasToggledIndices = controller.toggledIndices.isNotEmpty; - final Paint fillPaint = Paint()..isAntiAlias = true; - final Paint strokePaint = Paint() - ..isAntiAlias = true - ..style = PaintingStyle.stroke; - - mapDataSource.forEach((String key, MapModel model) { - if (model.bubbleSizeValue == null || - (_currentHoverItem != null && - _currentHoverItem.primaryKey == model.primaryKey)) { - return; - } + final Rect bounds = + Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height); + context.canvas + ..save() + ..clipRect(bounds); + controller!.applyTransform(context, offset); - final double bubbleRadius = - _getDesiredValue(_bubbleAnimation.value * model.bubbleRadius); - _updateFillColor(model, fillPaint, hasToggledIndices, defaultColor); - if (fillPaint.color != null && fillPaint.color != Colors.transparent) { - context.canvas - .drawCircle(model.shapePathCenter, bubbleRadius, fillPaint); - } + final double opacity = + _bubbleAnimation.value * _bubbleSettings.color!.opacity; + final Color defaultColor = bubbleSettings.color!.withOpacity(opacity); + final bool hasToggledIndices = controller!.toggledIndices.isNotEmpty; + final Paint fillPaint = Paint()..isAntiAlias = true; + final Paint strokePaint = Paint() + ..isAntiAlias = true + ..style = PaintingStyle.stroke; - _drawBubbleStroke( - context, model, strokePaint, bubbleRadius, hasToggledIndices); - }); + mapDataSource.forEach((String key, MapModel model) { + if (model.bubbleSizeValue == null || + (_currentHoverItem != null && + _currentHoverItem!.primaryKey == model.primaryKey)) { + return; + } - _drawHoveredBubble(context, opacity, defaultColor); - } + final double bubbleRadius = + _getDesiredValue(_bubbleAnimation.value * model.bubbleRadius!); + _updateFillColor(model, fillPaint, hasToggledIndices, defaultColor); + if (fillPaint.color != Colors.transparent) { + context.canvas + .drawCircle(model.shapePathCenter!, bubbleRadius, fillPaint); + } + + _drawBubbleStroke( + context, model, strokePaint, bubbleRadius, hasToggledIndices); + }); + + _drawHoveredBubble(context, opacity, defaultColor); } double _getDesiredValue(double value) { return value / - (controller.gesture == Gesture.scale ? controller.localScale : 1); + (controller!.gesture == Gesture.scale ? controller!.localScale : 1); } void _drawBubbleStroke(PaintingContext context, MapModel model, @@ -480,9 +471,9 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { if (hasToggledStroke || hasDefaultStroke) { _updateStrokePaint(model, strokePaint, hasToggledIndices, bubbleRadius); strokePaint.strokeWidth /= - controller.gesture == Gesture.scale ? controller.localScale : 1; + controller!.gesture == Gesture.scale ? controller!.localScale : 1; context.canvas.drawCircle( - model.shapePathCenter, + model.shapePathCenter!, _getDesiredRadius(bubbleRadius, strokePaint.strokeWidth), strokePaint); } @@ -499,18 +490,18 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { void _updateFillColor(MapModel model, Paint fillPaint, bool hasToggledIndices, Color defaultColor) { fillPaint.style = PaintingStyle.fill; - if (_legend != null && _legend.source == MapElement.bubble) { - if (controller.currentToggledItemIndex == model.legendMapperIndex) { + if (_legend != null && _legend!.source == MapElement.bubble) { + if (controller!.currentToggledItemIndex == model.legendMapperIndex) { // Set tween color to the bubble based on the currently tapped // legend item. If the legend item is toggled, then the // [_forwardToggledBubbleColorTween] return. If the legend item is // un-toggled, then the [_reverseToggledBubbleColorTween] return. - final Color bubbleColor = controller.wasToggled(model) + final Color? bubbleColor = controller!.wasToggled(model) ? _forwardToggledBubbleColorTween.evaluate(_toggleBubbleAnimation) : _reverseToggledBubbleColorTween.evaluate(_toggleBubbleAnimation); fillPaint.color = bubbleColor ?? Colors.transparent; return; - } else if (hasToggledIndices && controller.wasToggled(model)) { + } else if (hasToggledIndices && controller!.wasToggled(model)) { // Set toggled color to the previously toggled bubbles. fillPaint.color = _forwardToggledBubbleColorTween.end ?? Colors.transparent; @@ -519,10 +510,10 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { } if (_previousHoverItem != null && - _previousHoverItem.primaryKey == model.primaryKey) { + _previousHoverItem!.primaryKey == model.primaryKey) { fillPaint.color = _themeData.bubbleHoverColor != Colors.transparent - ? _reverseBubbleHoverColorTween.evaluate(_hoverBubbleAnimation) - : (_previousHoverItem.bubbleColor ?? defaultColor); + ? _reverseBubbleHoverColorTween.evaluate(_hoverBubbleAnimation)! + : (_previousHoverItem!.bubbleColor ?? defaultColor); return; } fillPaint.color = model.bubbleColor ?? defaultColor; @@ -533,40 +524,40 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { void _updateStrokePaint(MapModel model, Paint strokePaint, bool hasToggledIndices, double bubbleRadius) { strokePaint.style = PaintingStyle.stroke; - if (_legend != null && _legend.source == MapElement.bubble) { - if (controller.currentToggledItemIndex == model.legendMapperIndex) { + if (_legend != null && _legend!.source == MapElement.bubble) { + if (controller!.currentToggledItemIndex == model.legendMapperIndex) { // Set tween color to the bubble based on the currently tapped // legend item. If the legend item is toggled, then the // [_forwardToggledBubbleStrokeColorTween] return. // If the legend item is un-toggled, then the // [_reverseToggledBubbleStrokeColorTween] return. strokePaint - ..color = controller.wasToggled(model) + ..color = controller!.wasToggled(model) ? _forwardToggledBubbleStrokeColorTween - .evaluate(_toggleBubbleAnimation) + .evaluate(_toggleBubbleAnimation)! : _reverseToggledBubbleStrokeColorTween - .evaluate(_toggleBubbleAnimation) - ..strokeWidth = controller.wasToggled(model) - ? _legend.toggledItemStrokeWidth - : _bubbleSettings.strokeWidth; + .evaluate(_toggleBubbleAnimation)! + ..strokeWidth = controller!.wasToggled(model) + ? _legend!.toggledItemStrokeWidth + : _bubbleSettings.strokeWidth!; return; - } else if (hasToggledIndices && controller.wasToggled(model)) { + } else if (hasToggledIndices && controller!.wasToggled(model)) { // Set toggled stroke color to the previously toggled bubbles. strokePaint - ..color = _forwardToggledBubbleStrokeColorTween.end - ..strokeWidth = _legend.toggledItemStrokeWidth; + ..color = _forwardToggledBubbleStrokeColorTween.end! + ..strokeWidth = _legend!.toggledItemStrokeWidth; return; } } if (_previousHoverItem != null && - _previousHoverItem.primaryKey == model.primaryKey) { - if (_themeData.bubbleHoverStrokeWidth > 0.0 && + _previousHoverItem!.primaryKey == model.primaryKey) { + if (_themeData.bubbleHoverStrokeWidth! > 0.0 && _themeData.bubbleHoverStrokeColor != Colors.transparent) { strokePaint ..style = PaintingStyle.stroke ..color = _reverseBubbleHoverStrokeColorTween - .evaluate(_hoverBubbleAnimation) + .evaluate(_hoverBubbleAnimation)! ..strokeWidth = _themeData.bubbleStrokeWidth; return; } else if (hasDefaultStroke) { @@ -578,26 +569,26 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { } } strokePaint - ..color = _bubbleSettings.strokeColor - ..strokeWidth = _bubbleSettings.strokeWidth > bubbleRadius + ..color = _bubbleSettings.strokeColor! + ..strokeWidth = _bubbleSettings.strokeWidth! > bubbleRadius ? bubbleRadius - : _bubbleSettings.strokeWidth; + : _bubbleSettings.strokeWidth!; } void _drawHoveredBubble( PaintingContext context, double opacity, Color defaultColor) { if (_currentHoverItem != null) { final double bubbleRadius = - _getDesiredValue(_currentHoverItem.bubbleRadius); - final Color defaultColor = bubbleSettings.color; + _getDesiredValue(_currentHoverItem!.bubbleRadius!); + final Color defaultColor = bubbleSettings.color!; final Paint paint = Paint() ..style = PaintingStyle.fill ..color = _themeData.bubbleHoverColor != Colors.transparent - ? _forwardBubbleHoverColorTween.evaluate(_hoverBubbleAnimation) - : (_currentHoverItem.bubbleColor ?? defaultColor); - if (paint.color != null && paint.color != Colors.transparent) { - context.canvas - .drawCircle(_currentHoverItem.shapePathCenter, bubbleRadius, paint); + ? _forwardBubbleHoverColorTween.evaluate(_hoverBubbleAnimation)! + : (_currentHoverItem!.bubbleColor ?? defaultColor); + if (paint.color != Colors.transparent) { + context.canvas.drawCircle( + _currentHoverItem!.shapePathCenter!, bubbleRadius, paint); } _drawHoveredBubbleStroke(context, bubbleRadius); @@ -608,12 +599,12 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { final Paint strokePaint = Paint() ..isAntiAlias = true ..style = PaintingStyle.stroke; - if (_themeData.bubbleHoverStrokeWidth > 0.0 && + if (_themeData.bubbleHoverStrokeWidth! > 0.0 && _themeData.bubbleHoverStrokeColor != Colors.transparent) { strokePaint ..color = - _forwardBubbleHoverStrokeColorTween.evaluate(_hoverBubbleAnimation) - ..strokeWidth = _getDesiredValue(themeData.bubbleHoverStrokeWidth); + _forwardBubbleHoverStrokeColorTween.evaluate(_hoverBubbleAnimation)! + ..strokeWidth = _getDesiredValue(themeData.bubbleHoverStrokeWidth!); } else if (hasDefaultStroke) { strokePaint ..color = _themeData.bubbleStrokeColor @@ -623,7 +614,7 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { if (strokePaint.strokeWidth > 0.0 && strokePaint.color != Colors.transparent) { context.canvas.drawCircle( - _currentHoverItem.shapePathCenter, + _currentHoverItem!.shapePathCenter!, strokePaint.strokeWidth > bubbleRadius ? bubbleRadius / 2 : bubbleRadius - strokePaint.strokeWidth / 2, diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart b/packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart index beab601f7..0a2db617d 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart @@ -6,23 +6,23 @@ import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../behavior/zoom_pan_behavior.dart'; -import '../controller/default_controller.dart'; +import '../common.dart'; +import '../controller/map_controller.dart'; import '../enum.dart'; import '../layer/shape_layer.dart'; import '../settings.dart'; import '../utils.dart'; -import 'shapes.dart'; // ignore_for_file: public_member_api_docs class MapDataLabel extends LeafRenderObjectWidget { const MapDataLabel({ - this.source, - this.mapDataSource, - this.settings, - this.effectiveTextStyle, - this.themeData, - this.controller, - this.dataLabelAnimationController, + required this.source, + required this.mapDataSource, + required this.settings, + required this.effectiveTextStyle, + required this.themeData, + required this.controller, + required this.dataLabelAnimationController, }); final MapShapeSource source; @@ -30,7 +30,7 @@ class MapDataLabel extends LeafRenderObjectWidget { final MapDataLabelSettings settings; final TextStyle effectiveTextStyle; final SfMapsThemeData themeData; - final MapController controller; + final MapController? controller; final AnimationController dataLabelAnimationController; @override @@ -63,15 +63,15 @@ class MapDataLabel extends LeafRenderObjectWidget { class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { _RenderMapDataLabel({ - MapShapeSource source, - Map mapDataSource, - MapDataLabelSettings settings, - TextStyle effectiveTextStyle, - SfMapsThemeData themeData, - MapController controller, - AnimationController dataLabelAnimationController, - MediaQueryData mediaQueryData, - }) : source = source, + required MapShapeSource source, + required Map mapDataSource, + required MapDataLabelSettings settings, + required TextStyle effectiveTextStyle, + required SfMapsThemeData themeData, + required MapController? controller, + required AnimationController dataLabelAnimationController, + required MediaQueryData mediaQueryData, + }) : source = source, mapDataSource = mapDataSource, _settings = settings, _effectiveTextStyle = effectiveTextStyle, @@ -92,16 +92,16 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { _checkDataLabelColor(); } - double _effectiveTextScaleFactor; - Animation _dataLabelAnimation; - Tween _opacityTween; - bool _isCustomTextColor; - TextPainter _textPainter; + late double _effectiveTextScaleFactor; + late Animation _dataLabelAnimation; + late Tween _opacityTween; + late bool _isCustomTextColor; + late TextPainter _textPainter; MapShapeSource source; Map mapDataSource; - MapController controller; AnimationController dataLabelAnimationController; + MapController? controller; MapDataLabelSettings get settings => _settings; MapDataLabelSettings _settings; @@ -164,13 +164,8 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { void attach(PipelineOwner owner) { super.attach(owner); dataLabelAnimationController.addListener(markNeedsPaint); - if (controller == null) { - final RenderShapeLayer shapeLayerRenderBox = parent; - controller = shapeLayerRenderBox.controller; - } - if (controller != null) { - controller + controller! ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) ..addRefreshListener(_handleRefresh) @@ -182,7 +177,7 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { void detach() { dataLabelAnimationController.removeListener(markNeedsPaint); if (controller != null) { - controller + controller! ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) ..removeRefreshListener(_handleRefresh) @@ -210,62 +205,61 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { @override void paint(PaintingContext context, Offset offset) { - if (mapDataSource != null) { - context.canvas - ..save() - ..clipRect(offset & size); - controller.applyTransform(context, offset); - - String dataLabelText; - final TextStyle textStyle = _effectiveTextStyle.copyWith( - color: _getAnimatedColor(_effectiveTextStyle.color)); - final bool hasMapper = source.dataLabelMapper != null; - mapDataSource.forEach((String key, MapModel model) { - dataLabelText = controller.isInInteractive - ? model.visibleDataLabelText - : hasMapper - ? model.dataLabelText - : model.primaryKey; - if (dataLabelText == null) { - return; - } - - final TextStyle desiredTextStyle = - _updateLuminanceColor(textStyle, _isCustomTextColor, model); - _textPainter.text = - TextSpan(style: desiredTextStyle, text: dataLabelText); - _textPainter.layout(); - if (!controller.isInInteractive) { - if (_settings.overflowMode == MapLabelOverflow.hide) { - if (_textPainter.width > model.shapeWidth) { - model.visibleDataLabelText = null; - return; - } - } else if (_settings.overflowMode == MapLabelOverflow.ellipsis) { - final String trimmedText = getTrimText( - dataLabelText, - desiredTextStyle, - model.shapeWidth, - _textPainter, - _textPainter.width); - _textPainter.text = - TextSpan(style: desiredTextStyle, text: trimmedText); - _textPainter.layout(); + context.canvas + ..save() + ..clipRect(offset & size); + controller?.applyTransform(context, offset); + + String? dataLabelText; + final TextStyle textStyle = _effectiveTextStyle.copyWith( + color: _getAnimatedColor(_effectiveTextStyle.color!)); + final bool hasMapper = source.dataLabelMapper != null; + mapDataSource.forEach((String key, MapModel model) { + dataLabelText = controller!.isInInteractive + ? model.visibleDataLabelText + : hasMapper + ? model.dataLabelText + : model.primaryKey; + if (dataLabelText == null) { + return; + } + + final TextStyle desiredTextStyle = + _updateLuminanceColor(textStyle, _isCustomTextColor, model); + _textPainter.text = + TextSpan(style: desiredTextStyle, text: dataLabelText); + _textPainter.layout(); + if (!controller!.isInInteractive) { + if (_settings.overflowMode == MapLabelOverflow.hide) { + if (_textPainter.width > model.shapeWidth!) { + model.visibleDataLabelText = null; + return; } - - final TextSpan textSpan = _textPainter.text; - model.visibleDataLabelText = textSpan.text; + } else if (_settings.overflowMode == MapLabelOverflow.ellipsis) { + final String trimmedText = getTrimText( + dataLabelText!, + desiredTextStyle, + model.shapeWidth!, + _textPainter, + _textPainter.width); + _textPainter.text = + TextSpan(style: desiredTextStyle, text: trimmedText); + _textPainter.layout(); } - context.canvas - ..save() - ..translate(model.shapePathCenter.dx, model.shapePathCenter.dy) - ..scale(1 / controller.localScale); - _textPainter.paint(context.canvas, - Offset(-_textPainter.width / 2, -_textPainter.height / 2)); - context.canvas.restore(); - }); + + // ignore: avoid_as + final TextSpan textSpan = _textPainter.text as TextSpan; + model.visibleDataLabelText = textSpan.text; + } + context.canvas + ..save() + ..translate(model.shapePathCenter!.dx, model.shapePathCenter!.dy) + ..scale(1 / controller!.localScale); + _textPainter.paint(context.canvas, + Offset(-_textPainter.width / 2, -_textPainter.height / 2)); context.canvas.restore(); - } + }); + context.canvas.restore(); } TextStyle _updateLuminanceColor( diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart b/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart index 5057d7494..05ef41830 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart @@ -8,18 +8,14 @@ import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_maps/src/utils.dart'; -import '../controller/default_controller.dart'; +import '../common.dart'; +import '../controller/map_controller.dart'; import '../enum.dart'; import '../layer/shape_layer.dart'; import '../settings.dart'; import '../utils.dart'; -import 'shapes.dart'; -enum _MapLegendType { - vector, - - bar -} +enum _MapLegendType { vector, bar } /// Shows legend for the bubbles or shapes. /// @@ -68,27 +64,126 @@ enum _MapLegendType { /// [MapShapeSource.shapeDataField] property and the legend item's /// icon will have the default color. /// +/// The below code snippet represents how to setting default legend +/// to the shape. +/// /// ```dart +/// List _data; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// super.initState(); +/// +/// _data = [ +/// DataModel('India', 280, "Low"), +/// DataModel('United States of America', 190, "High"), +/// DataModel('Pakistan', 37, "Low"), +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: _data.length, +/// primaryValueMapper: (int index) { +/// return _data[index].country; +/// }, +/// shapeColorValueMapper: (int index) { +/// return _data[index].storage; +/// }, +/// shapeColorMappers: [ +/// MapColorMapper(value: "Low", color: Colors.red), +/// MapColorMapper(value: "High", color: Colors.green) +/// ], +/// ); +/// } +/// /// @override /// Widget build(BuildContext context) { -/// return SfMaps( -/// layers: [ -/// MapShapeLayer( -/// legend: MapLegend( -/// MapElement.bubble, -/// padding: EdgeInsets.all(10) -/// ), -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "continent", -/// dataCount: bubbleData.length, -/// primaryValueMapper: (int index) { -/// return bubbleData[index].country; -/// }), -/// ) +/// return Scaffold( +/// appBar: AppBar( +/// title: Text('Default legend'), +/// ), +/// body: Center( +/// child: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// legend: MapLegend(MapElement.shape), +/// ) +/// ], +/// ), +/// ), +/// ); +/// } +/// +/// class DataModel { +/// const DataModel(this.country, this.count, this.storage); +/// +/// final String country; +/// final double count; +/// final String storage; +/// } +/// ``` +/// The below code snippet represents how to setting bar legend to the shape. +/// +/// ```dart +/// List _data; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// super.initState(); +/// +/// _data = [ +/// DataModel('India', 280, "Low"), +/// DataModel('United States of America', 190, "High"), +/// DataModel('Pakistan', 37, "Low"), +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: _data.length, +/// primaryValueMapper: (int index) { +/// return _data[index].country; +/// }, +/// shapeColorValueMapper: (int index) { +/// return _data[index].storage; +/// }, +/// shapeColorMappers: [ +/// MapColorMapper(value: "Low", color: Colors.red), +/// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar( +/// title: Text('Bar legend'), +/// ), +/// body: Center( +/// child: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// legend: MapLegend.bar(MapElement.shape), +/// ) +/// ], +/// ), +/// ), +/// ); +/// } +/// +/// class DataModel { +/// const DataModel(this.country, this.count, this.storage); +/// +/// final String country; +/// final double count; +/// final String storage; +/// } /// ``` @immutable class MapLegend extends DiagnosticableTree { @@ -141,48 +236,86 @@ class MapLegend extends DiagnosticableTree { /// icon will have the default color. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.bubble, - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (int index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend(MapElement.shape), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: /// * `MapLegend.bar()` named constructor, for bar legend type. const MapLegend( this.source, { + this.title, this.position = MapLegendPosition.top, this.offset, this.overflowMode = MapLegendOverflowMode.wrap, this.direction, this.padding = const EdgeInsets.all(10.0), this.spacing = 10.0, - MapIconType iconType, - Size iconSize, + MapIconType iconType = MapIconType.circle, + Size iconSize = const Size(8.0, 8.0), this.textStyle, bool enableToggleInteraction = false, - Color toggledItemColor, - Color toggledItemStrokeColor, + Color? toggledItemColor, + Color? toggledItemStrokeColor, double toggledItemStrokeWidth = 1.0, double toggledItemOpacity = 1.0, }) : _legendType = _MapLegendType.vector, - _iconType = iconType ?? MapIconType.circle, - _iconSize = iconSize ?? const Size(8.0, 8.0), + _iconType = iconType, + _iconSize = iconSize, _enableToggleInteraction = enableToggleInteraction, _toggledItemColor = toggledItemColor, _toggledItemStrokeColor = toggledItemStrokeColor, @@ -193,11 +326,10 @@ class MapLegend extends DiagnosticableTree { _edgeLabelsPlacement = null, _labelOverflow = null, _segmentPaintingStyle = null, - assert(spacing != null && spacing >= 0), + assert(spacing >= 0), assert(padding != null), - assert(toggledItemStrokeWidth == null || toggledItemStrokeWidth >= 0), - assert(toggledItemOpacity == null || - (toggledItemOpacity >= 0 && toggledItemOpacity <= 1)); + assert(toggledItemStrokeWidth >= 0), + assert(toggledItemOpacity >= 0 && toggledItemOpacity <= 1); /// Creates a bar type legend for the bubbles or shapes. /// @@ -266,26 +398,62 @@ class MapLegend extends DiagnosticableTree { /// icon will have the default color. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend.bar( - /// MapElement.bubble, - /// padding: EdgeInsets.all(10) - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (int index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Bar legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend.bar(MapElement.shape), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -293,18 +461,20 @@ class MapLegend extends DiagnosticableTree { /// diamond, rectangle and triangle. const MapLegend.bar( this.source, { + this.title, this.overflowMode = MapLegendOverflowMode.scroll, this.padding = const EdgeInsets.all(10.0), this.position = MapLegendPosition.top, this.offset, this.spacing = 2.0, - Size segmentSize, + Size? segmentSize, this.textStyle, this.direction, - MapLegendLabelsPlacement labelsPlacement, - MapLegendEdgeLabelsPlacement edgeLabelsPlacement, - MapLabelOverflow labelOverflow, - MapLegendPaintingStyle segmentPaintingStyle, + MapLegendLabelsPlacement? labelsPlacement, + MapLegendEdgeLabelsPlacement edgeLabelsPlacement = + MapLegendEdgeLabelsPlacement.inside, + MapLabelOverflow labelOverflow = MapLabelOverflow.visible, + MapLegendPaintingStyle segmentPaintingStyle = MapLegendPaintingStyle.solid, }) : _legendType = _MapLegendType.bar, _enableToggleInteraction = false, _toggledItemColor = null, @@ -312,15 +482,13 @@ class MapLegend extends DiagnosticableTree { _toggledItemStrokeWidth = 0.0, _toggledItemOpacity = 0.0, _labelsPlacement = labelsPlacement, - _edgeLabelsPlacement = - edgeLabelsPlacement ?? MapLegendEdgeLabelsPlacement.inside, - _labelOverflow = labelOverflow ?? MapLabelOverflow.visible, - _segmentPaintingStyle = - segmentPaintingStyle ?? MapLegendPaintingStyle.solid, + _edgeLabelsPlacement = edgeLabelsPlacement, + _labelOverflow = labelOverflow, + _segmentPaintingStyle = segmentPaintingStyle, _segmentSize = segmentSize, _iconType = null, _iconSize = null, - assert(spacing != null && spacing >= 0), + assert(spacing >= 0), assert(padding != null); /// Shows legend for the bubbles or shapes. @@ -373,33 +541,113 @@ class MapLegend extends DiagnosticableTree { /// the appearance of the legend items. final MapElement source; + /// Sets a title for the legend. + /// + /// Defaults to null. + /// + /// Typically a [Text] widget. + /// + /// ## Example + /// + /// This snippet shows how to create a map with legends and legend’s title. + /// + /// ```dart + /// @override + /// void initState() { + /// super.initState(); + /// _shapeSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// } + /// @override + /// Widget build (BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _shapeSource, + /// legend: MapLegend(MapElement.shape, + /// title: Text (‘World Map’), + /// ), + /// ), + /// ], + /// ), + /// ); + /// } + /// ... + /// + /// See also: + /// + /// * `MapLegend()`, to know about the legend in maps. + final Widget? title; + /// Sets the padding around the legend. /// /// Defaults to EdgeInsets.all(10.0). /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// padding: EdgeInsets.all(10) - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend( + /// MapElement.shape, + /// padding: EdgeInsets.all(10.0), + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` - final EdgeInsetsGeometry padding; + final EdgeInsetsGeometry? padding; /// Arranges the legend items in either horizontal or vertical direction. /// @@ -407,57 +655,135 @@ class MapLegend extends DiagnosticableTree { /// Defaults to vertical, if the [position] is left or right. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// direction: Axis.horizontal - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend( + /// MapElement.shape, + /// direction: Axis.horizontal, + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: /// * [position], to set the position of the legend. - final Axis direction; + final Axis? direction; /// Positions the legend in the different directions. /// /// Defaults to [MapLegendPosition.top]. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// position: MapLegendPosition.bottom - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Bar legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend.bar( + /// MapElement.shape, + /// position: MapLegendPosition.right, + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// See also: /// * [offset], to place the legend in custom position. @@ -471,85 +797,202 @@ class MapLegend extends DiagnosticableTree { /// it and will be drawn on the top of map. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// offset: Offset(0, 5) - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Bar legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend.bar( + /// MapElement.shape, + /// offset: Offset(10.0, 10.0), + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: /// * [position], to set the position of the legend. - final Offset offset; + final Offset? offset; /// Specifies the space between the each legend items. /// /// Defaults to 10.0 for default legend and 2.0 for bar legend. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// spacing: 10 - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend( + /// MapElement.shape, + /// spacing: 10.0, + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` final double spacing; /// Customizes the legend item's text style. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// textStyle: TextStyle(color: Colors.red) - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend( + /// MapElement.shape, + /// textStyle: TextStyle(color: Colors.red), + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` - final TextStyle textStyle; + final TextStyle? textStyle; /// Wraps or scrolls the legend items when it overflows. /// @@ -563,26 +1006,65 @@ class MapLegend extends DiagnosticableTree { /// [MapLegendOverflowMode.scroll] for bar legend. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// overflowMode: MapLegendOverflowMode.scroll - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend( + /// MapElement.shape, + /// overflowMode: MapLegendOverflowMode.scroll, + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -598,26 +1080,65 @@ class MapLegend extends DiagnosticableTree { /// be toggled on tap or click. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// enableToggleInteraction: true - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend( + /// MapElement.shape, + /// enableToggleInteraction: true, + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` final bool _enableToggleInteraction; @@ -627,66 +1148,144 @@ class MapLegend extends DiagnosticableTree { /// This snippet shows how to set toggledItemColor in [SfMaps]. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// enableToggleInteraction: true, - /// toggledItemColor: Colors.blueGrey - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend( + /// MapElement.shape, + /// enableToggleInteraction: true, + /// toggledItemColor: Colors.blueGrey + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: /// * [toggledItemStrokeColor], [toggledItemStrokeWidth], to set the stroke /// for the toggled legend item's shape or bubble. - final Color _toggledItemColor; + final Color? _toggledItemColor; /// Stroke color for the toggled legend item's respective shape or bubble. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// enableToggleInteraction: true, - /// toggledItemColor: Colors.blueGrey, - /// toggledItemStrokeColor: Colors.white, - /// toggledItemStrokeWidth: 0.5 - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend( + /// MapElement.shape, + /// enableToggleInteraction: true, + /// toggledItemColor: Colors.blueGrey, + /// toggledItemStrokeColor: Colors.white, + /// toggledItemStrokeWidth: 0.5 + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: /// * [toggledItemStrokeWidth], to set the stroke width for the /// toggled legend item's shape. - final Color _toggledItemStrokeColor; + final Color? _toggledItemStrokeColor; /// Stroke width for the toggled legend item's respective shape or /// bubble. @@ -696,29 +1295,68 @@ class MapLegend extends DiagnosticableTree { /// This snippet shows how to set toggledItemStrokeWidth in [SfMaps]. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// enableToggleInteraction: true, - /// toggledItemColor: Colors.blueGrey, - /// toggledItemStrokeColor: Colors.white, - /// toggledItemStrokeWidth: 0.5 - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend( + /// MapElement.shape, + /// enableToggleInteraction: true, + /// toggledItemColor: Colors.blueGrey, + /// toggledItemStrokeColor: Colors.white, + /// toggledItemStrokeWidth: 0.5 + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -734,30 +1372,69 @@ class MapLegend extends DiagnosticableTree { /// This snippet shows how to set toggledItemOpacity in [SfMaps]. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// enableToggleInteraction: true, - /// toggledItemColor: Colors.blueGrey, - /// toggledItemStrokeColor: Colors.white, - /// toggledItemStrokeWidth: 0.5, - /// toggledItemOpacity: 0.5 - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend( + /// MapElement.shape, + /// enableToggleInteraction: true, + /// toggledItemColor: Colors.blueGrey, + /// toggledItemStrokeColor: Colors.white, + /// toggledItemStrokeWidth: 0.5, + /// toggledItemOpacity: 0.5, + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// final double _toggledItemOpacity; @@ -767,90 +1444,207 @@ class MapLegend extends DiagnosticableTree { /// Defaults to [MapIconType.circle]. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// iconType: MapIconType.rectangle - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend( + /// MapElement.shape, + /// iconType: MapIconType.rectangle, + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: /// * [iconSize], to set the size of the icon. - final MapIconType _iconType; + final MapIconType? _iconType; /// Customizes the size of the bar segments. /// /// Defaults to Size(80.0, 12.0). /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// segmentSize: Size(30, 10) - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Bar legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend.bar( + /// MapElement.shape, + /// segmentSize: Size(70.0, 10.0), + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` - final Size _segmentSize; + final Size? _segmentSize; /// Customizes the size of the icon. /// /// Defaults to Size(12.0, 12.0). /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// iconSize: Size(30, 10) - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Default legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend( + /// MapElement.shape, + /// iconSize: Size(8.0, 8.0), + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: /// * [iconType], to set shape of the default legend icon. - final Size _iconSize; + final Size? _iconSize; /// Place the labels either between the segments or on the segments. /// @@ -861,26 +1655,65 @@ class MapLegend extends DiagnosticableTree { /// This snippet shows how to set label placement in [SfMaps]. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend.bar( - /// MapElement.bubble, - /// labelsPlacement: MapLegendLabelsPlacement.onItem, - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Bar legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend.bar( + /// MapElement.shape, + /// labelsPlacement: MapLegendLabelsPlacement.onItem, + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -889,7 +1722,7 @@ class MapLegend extends DiagnosticableTree { /// /// * [labelOverflow], to trims or removes the legend text /// when it is overflowed from the bar legend. - final MapLegendLabelsPlacement _labelsPlacement; + final MapLegendLabelsPlacement? _labelsPlacement; /// Place the edge labels either inside or outside of the bar legend. /// @@ -901,26 +1734,57 @@ class MapLegend extends DiagnosticableTree { /// This snippet shows how to set edge label placement in [SfMaps]. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend.bar( - /// MapElement.bubble, - /// edgeLabelsPlacement: MapLegendEdgeLabelsPlacement.inside, - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 100, "Low"), + /// DataModel('United States of America', 200, "High"), + /// DataModel('Pakistan', 75, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset("assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, shapeColorValueMapper: (int index) { + /// return _data[index].count; + /// }, shapeColorMappers: [ + /// MapColorMapper(from: 0, to: 100, color: Colors.red), + /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) + /// ]); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Bar legend')), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend.bar( + /// MapElement.shape, + /// edgeLabelsPlacement: MapLegendEdgeLabelsPlacement.inside, + /// ), + /// ) + /// ], + /// ), + /// ); + /// } + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -928,7 +1792,7 @@ class MapLegend extends DiagnosticableTree { /// on the segments. /// * [labelOverflow], to trims or removes the legend text /// when it is overflowed from the bar legend. - final MapLegendEdgeLabelsPlacement _edgeLabelsPlacement; + final MapLegendEdgeLabelsPlacement? _edgeLabelsPlacement; /// Trims or removes the legend text when it is overflowed from the /// bar legend. @@ -942,26 +1806,65 @@ class MapLegend extends DiagnosticableTree { /// in [SfMaps]. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// legend: MapLegend( - /// MapElement.shape, - /// labelOverflow: MapLabelOverflow.hide - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) /// ], /// ); /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Bar legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend.bar( + /// MapElement.shape, + /// labelOverflow: MapLabelOverflow.hide, + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -969,12 +1872,83 @@ class MapLegend extends DiagnosticableTree { /// on the segments. /// * [edgeLabelsPlacement], to place the edge labels either inside or /// outside of the bar legend. - final MapLabelOverflow _labelOverflow; + final MapLabelOverflow? _labelOverflow; /// Specifies the type of the legend. final _MapLegendType _legendType; - final MapLegendPaintingStyle _segmentPaintingStyle; + /// Applies gradient or solid color for the bar segments. + /// + /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low"), + /// DataModel('United States of America', 190, "High"), + /// DataModel('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) + /// ], + /// ); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Bar legend'), + /// ), + /// body: Center( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// legend: MapLegend.bar( + /// MapElement.shape, + /// labelOverflow: MapLabelOverflow.hide, + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } + /// ```dart + /// + /// See also: + /// * [labelsPlacement], place the labels either between the segments or + /// on the segments. + /// * [labelOverflow], to trims or removes the legend text + /// when it is overflowed from the bar legend. + /// * [edgeLabelsPlacement], to place the edge labels either inside or + /// outside of the bar legend. + final MapLegendPaintingStyle? _segmentPaintingStyle; @override bool operator ==(Object other) { @@ -1025,38 +1999,40 @@ class MapLegend extends DiagnosticableTree { /// Creates a copy of this class but with the given fields /// replaced with the new values. MapLegend copyWith({ - Axis direction, - EdgeInsetsGeometry padding, - MapLegendPosition position, - Offset offset, - double spacing, - MapIconType iconType, - TextStyle textStyle, - Size iconSize, - Size segmentSize, - MapLegendOverflowMode overflowMode, - bool enableToggleInteraction, - Color toggledItemColor, - Color toggledItemStrokeColor, - double toggledItemStrokeWidth, - double toggledItemOpacity, - MapElement source, - MapLegendLabelsPlacement labelsPlacement, - MapLegendEdgeLabelsPlacement edgeLabelsPlacement, - MapLabelOverflow labelOverflow, - MapLegendPaintingStyle segmentPaintingStyle, + Axis? direction, + EdgeInsetsGeometry? padding, + MapLegendPosition? position, + Widget? title, + Offset? offset, + double? spacing, + MapIconType? iconType, + TextStyle? textStyle, + Size? iconSize, + Size? segmentSize, + MapLegendOverflowMode? overflowMode, + bool? enableToggleInteraction, + Color? toggledItemColor, + Color? toggledItemStrokeColor, + double? toggledItemStrokeWidth, + double? toggledItemOpacity, + MapElement? source, + MapLegendLabelsPlacement? labelsPlacement, + MapLegendEdgeLabelsPlacement? edgeLabelsPlacement, + MapLabelOverflow? labelOverflow, + MapLegendPaintingStyle? segmentPaintingStyle, }) { if (_legendType == _MapLegendType.vector) { return MapLegend( source ?? this.source, + title: title ?? this.title, direction: direction ?? this.direction, padding: padding ?? this.padding, position: position ?? this.position, offset: offset ?? this.offset, spacing: spacing ?? this.spacing, - iconType: iconType ?? _iconType, + iconType: iconType ?? _iconType!, textStyle: textStyle ?? this.textStyle, - iconSize: iconSize ?? _iconSize, + iconSize: iconSize ?? _iconSize!, overflowMode: overflowMode ?? this.overflowMode, enableToggleInteraction: enableToggleInteraction ?? _enableToggleInteraction, @@ -1070,6 +2046,7 @@ class MapLegend extends DiagnosticableTree { } else { return MapLegend.bar( source ?? this.source, + title: title ?? this.title, direction: direction ?? this.direction, padding: padding ?? this.padding, position: position ?? this.position, @@ -1079,9 +2056,9 @@ class MapLegend extends DiagnosticableTree { segmentSize: segmentSize ?? _segmentSize, overflowMode: overflowMode ?? this.overflowMode, labelsPlacement: labelsPlacement ?? _labelsPlacement, - edgeLabelsPlacement: edgeLabelsPlacement ?? _edgeLabelsPlacement, - labelOverflow: labelOverflow ?? _labelOverflow, - segmentPaintingStyle: segmentPaintingStyle ?? _segmentPaintingStyle, + edgeLabelsPlacement: edgeLabelsPlacement ?? _edgeLabelsPlacement!, + labelOverflow: labelOverflow ?? _labelOverflow!, + segmentPaintingStyle: segmentPaintingStyle ?? _segmentPaintingStyle!, ); } } @@ -1103,7 +2080,7 @@ class MapLegend extends DiagnosticableTree { .add(EnumProperty('overflowMode', overflowMode)); properties.add(EnumProperty('position', position)); if (textStyle != null) { - properties.add(textStyle.toDiagnosticsNode(name: 'textStyle')); + properties.add(textStyle!.toDiagnosticsNode(name: 'textStyle')); } if (_legendType == _MapLegendType.vector) { @@ -1141,11 +2118,11 @@ class MapLegend extends DiagnosticableTree { } /// For rendering the map legend based on legend type. -class MapLayerLegend extends StatefulWidget { +class MapLegendWidget extends StatefulWidget { /// Creates a [MapMarker]. - MapLayerLegend({ + MapLegendWidget({ this.dataSource, - this.legend, + required this.legend, this.themeData, this.controller, this.toggleAnimationController, @@ -1167,17 +2144,17 @@ class MapLayerLegend extends StatefulWidget { final MapElement source; /// Customizes the legend item's text style. - final TextStyle textStyle; + final TextStyle? textStyle; /// Enables the toggle interaction for the legend. final bool enableToggleInteraction; /// Fills the toggled legend item's icon and the respective shape or bubble /// by this color. - final Color toggledItemColor; + final Color? toggledItemColor; /// Stroke color for the toggled legend item's respective shape or bubble. - final Color toggledItemStrokeColor; + final Color? toggledItemStrokeColor; /// Stroke width for the toggled legend item's respective shape or /// bubble. @@ -1190,24 +2167,24 @@ class MapLayerLegend extends StatefulWidget { /// Holds the color and typography values for a [SfMapsTheme]. /// Use this class to configure a [SfMapsTheme] widget, /// or to set the [SfThemeData.mapsThemeData] for a [SfTheme] widget. - final SfMapsThemeData themeData; + final SfMapsThemeData? themeData; /// updating legend toggled indices. - final MapController controller; + final MapController? controller; /// Applies animation for toggled legend item. - final AnimationController toggleAnimationController; + final AnimationController? toggleAnimationController; /// Creates a copy of this class but with the given fields /// replaced with the new values. - MapLayerLegend copyWith({ + MapLegendWidget copyWith({ dynamic dataSource, - MapLegend legend, - SfMapsThemeData themeData, - MapController controller, - AnimationController toggleAnimationController, + required MapLegend? legend, + required SfMapsThemeData? themeData, + required MapController? controller, + required AnimationController? toggleAnimationController, }) { - return MapLayerLegend( + return MapLegendWidget( dataSource: dataSource ?? this.dataSource, legend: legend ?? this.legend, themeData: themeData ?? this.themeData, @@ -1221,9 +2198,9 @@ class MapLayerLegend extends StatefulWidget { _MapLegendState createState() => _MapLegendState(); } -class _MapLegendState extends State { +class _MapLegendState extends State { @override - void didUpdateWidget(MapLayerLegend oldWidget) { + void didUpdateWidget(MapLegendWidget oldWidget) { if (oldWidget.legend.source != widget.legend.source) { _resetToggleState(); } @@ -1238,31 +2215,63 @@ class _MapLegendState extends State { } void _resetToggleState() { - widget.controller.currentToggledItemIndex = -1; - widget.controller.toggledIndices.clear(); + widget.controller!.currentToggledItemIndex = -1; + widget.controller!.toggledIndices.clear(); } @override // ignore: missing_return Widget build(BuildContext context) { - switch (widget.legend.overflowMode) { - case MapLegendOverflowMode.scroll: - return SingleChildScrollView( - scrollDirection: widget.legend.position == MapLegendPosition.top || - widget.legend.position == MapLegendPosition.bottom - ? Axis.horizontal - : Axis.vertical, - child: actualChild, + if (widget.legend.title == null) { + switch (widget.legend.overflowMode) { + case MapLegendOverflowMode.scroll: + return _scrollableLegend; + case MapLegendOverflowMode.wrap: + return actualChild; + } + } else { + if (widget.legend.position == MapLegendPosition.top || + widget.legend.position == MapLegendPosition.bottom) { + return Column( + mainAxisAlignment: widget.legend.position == MapLegendPosition.top + ? MainAxisAlignment.start + : MainAxisAlignment.end, + children: [ + widget.legend.title!, + widget.legend.overflowMode == MapLegendOverflowMode.scroll + ? _scrollableLegend + : actualChild + ], + ); + } else { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + widget.legend.title!, + Flexible( + child: + widget.legend.overflowMode == MapLegendOverflowMode.scroll + ? _scrollableLegend + : actualChild), + ], ); - case MapLegendOverflowMode.wrap: - return actualChild; + } } } + Widget get _scrollableLegend { + return SingleChildScrollView( + scrollDirection: widget.legend.position == MapLegendPosition.top || + widget.legend.position == MapLegendPosition.bottom + ? Axis.horizontal + : Axis.vertical, + child: actualChild); + } + Widget get actualChild { if (widget.legend._legendType == _MapLegendType.vector) { return Padding( - padding: widget.legend.padding, + padding: widget.legend.padding!, // Mapped the legend properties to the Wrap widget. child: Wrap( direction: widget.legend.direction ?? @@ -1271,10 +2280,10 @@ class _MapLegendState extends State { ? Axis.horizontal : Axis.vertical), spacing: widget.legend.spacing, - children: _getLegendItems(), runSpacing: 6, runAlignment: WrapAlignment.center, alignment: WrapAlignment.start, + children: _getLegendItems(), ), ); } else { @@ -1282,13 +2291,13 @@ class _MapLegendState extends State { return _SolidBarLegend( dataSource: widget.dataSource, legend: widget.legend, - themeData: widget.themeData, + themeData: widget.themeData!, ); } else { return _GradientBarLegend( dataSource: widget.dataSource, settings: widget.legend, - themeData: widget.themeData, + themeData: widget.themeData!, ); } } @@ -1304,11 +2313,9 @@ class _MapLegendState extends State { final int length = widget.dataSource.length; for (int i = 0; i < length; i++) { final MapColorMapper colorMapper = widget.dataSource[i]; - assert(colorMapper != null); final String text = colorMapper.text ?? colorMapper.value ?? '${colorMapper.from} - ${colorMapper.to}'; - assert(text != null); legendItems.add(_getLegendItem( text, colorMapper.color, @@ -1319,11 +2326,10 @@ class _MapLegendState extends State { } else { // Here source is map data source. widget.dataSource.forEach((String key, MapModel mapModel) { - assert(mapModel.primaryKey != null); legendItems.add(_getLegendItem( mapModel.primaryKey, isLegendForBubbles ? mapModel.bubbleColor : mapModel.shapeColor, - mapModel.legendMapperIndex, + mapModel.legendMapperIndex!, isLegendForBubbles, )); }); @@ -1335,34 +2341,33 @@ class _MapLegendState extends State { /// Returns the legend icon and label. Widget _getLegendItem( - String text, Color color, int index, bool isLegendForBubbles) { - assert(text != null); + String text, Color? color, int index, bool isLegendForBubbles) { return _MapLegendItem( index: index, text: text, iconShapeColor: color ?? (isLegendForBubbles - ? widget.themeData.bubbleColor - : widget.themeData.layerColor), + ? widget.themeData!.bubbleColor + : widget.themeData!.layerColor), source: widget.legend.source, legend: widget.legend, - themeData: widget.themeData, - controller: widget.controller, - toggleAnimationController: widget.toggleAnimationController, + themeData: widget.themeData!, + controller: widget.controller!, + toggleAnimationController: widget.toggleAnimationController!, ); } } class _MapLegendItem extends LeafRenderObjectWidget { const _MapLegendItem({ - this.index, - this.text, - this.iconShapeColor, - this.source, - this.legend, - this.themeData, - this.controller, - this.toggleAnimationController, + required this.index, + required this.text, + required this.iconShapeColor, + required this.source, + required this.legend, + required this.themeData, + required this.controller, + required this.toggleAnimationController, }); final int index; @@ -1404,16 +2409,16 @@ class _MapLegendItem extends LeafRenderObjectWidget { class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { _RenderLegendItem({ - int index, - String text, - Color iconShapeColor, - MapElement source, - MapLegend legend, - SfMapsThemeData themeData, - MapController controller, - AnimationController toggleAnimationController, - MediaQueryData mediaQueryData, - }) : _index = index, + required int index, + required String text, + required Color iconShapeColor, + required MapElement source, + required MapLegend legend, + required SfMapsThemeData themeData, + required MapController controller, + required AnimationController toggleAnimationController, + required MediaQueryData mediaQueryData, + }) : _index = index, _text = text, _iconShapeColor = iconShapeColor, _source = source, @@ -1448,20 +2453,21 @@ class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { bool _wasToggled = false; - TextPainter _textPainter; + late TextPainter _textPainter; - TapGestureRecognizer _tapGestureRecognizer; + late TapGestureRecognizer _tapGestureRecognizer; - Animation _toggleColorAnimation; + late Animation _toggleColorAnimation; - Tween _textOpacityTween; + late Tween _textOpacityTween; - ColorTween _iconColorTween; + late ColorTween _iconColorTween; - Color _toggledIconColor; + late Color _toggledIconColor; String get text => _text; String _text; + set text(String value) { if (_text == value) { return; @@ -1473,6 +2479,7 @@ class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { Color get iconShapeColor => _iconShapeColor; Color _iconShapeColor; + set iconShapeColor(Color value) { if (_iconShapeColor == value) { return; @@ -1483,6 +2490,7 @@ class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { MapElement get source => _source; MapElement _source; + set source(MapElement value) { if (_source == value) { return; @@ -1494,6 +2502,7 @@ class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { MapLegend get legend => _legend; MapLegend _legend; + set legend(MapLegend value) { if (_legend == value) { return; @@ -1505,6 +2514,7 @@ class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { SfMapsThemeData get themeData => _themeData; SfMapsThemeData _themeData; + set themeData(SfMapsThemeData value) { if (_themeData == value) { return; @@ -1516,6 +2526,7 @@ class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { MediaQueryData get mediaQueryData => _mediaQueryData; MediaQueryData _mediaQueryData; + set mediaQueryData(MediaQueryData value) { if (_mediaQueryData == value) { return; @@ -1531,16 +2542,16 @@ class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { : SystemMouseCursors.basic; @override - PointerEnterEventListener get onEnter => null; + PointerEnterEventListener? get onEnter => null; // As onHover property of MouseHoverAnnotation was removed only in the // beta channel, once it is moved to stable, will remove this property. @override // ignore: override_on_non_overriding_member - PointerHoverEventListener get onHover => null; + PointerHoverEventListener? get onHover => null; @override - PointerExitEventListener get onExit => null; + PointerExitEventListener? get onExit => null; @override // ignore: override_on_non_overriding_member @@ -1598,32 +2609,32 @@ class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { @override void attach(PipelineOwner owner) { super.attach(owner); - _toggleAnimationController?.addListener(markNeedsPaint); + _toggleAnimationController.addListener(markNeedsPaint); } @override void detach() { - _toggleAnimationController?.removeListener(markNeedsPaint); + _toggleAnimationController.removeListener(markNeedsPaint); super.detach(); } @override void performLayout() { final double width = - _legend._iconSize.width + _spacing + _textPainter.width; + _legend._iconSize!.width + _spacing + _textPainter.width; final double height = - max(_legend._iconSize.height, _textPainter.height) + _spacing; + max(_legend._iconSize!.height, _textPainter.height) + _spacing; size = Size(width, height); } @override void paint(PaintingContext context, Offset offset) { double toggledLegendItemOpacity; - Color iconColor; + Color? iconColor; Offset actualOffset; if (_wasToggled || controller.currentToggledItemIndex == _index) { if (controller.currentToggledItemIndex == _index) { - iconColor = _iconColorTween.evaluate(_toggleColorAnimation); + iconColor = _iconColorTween.evaluate(_toggleColorAnimation)!; toggledLegendItemOpacity = _textOpacityTween.evaluate(_toggleColorAnimation); } else { @@ -1636,23 +2647,25 @@ class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { } final Size halfIconSize = - _iconShape.getPreferredSize(_legend._iconSize, _themeData) / 2; + _iconShape.getPreferredSize(_legend._iconSize!, _themeData) / 2; actualOffset = offset + Offset(0, (size.height - (halfIconSize.height * 2)) / 2); _iconShape.paint(context, actualOffset, parentBox: this, - iconSize: _legend._iconSize, - color: iconColor ?? Colors.transparent, - iconType: _legend._iconType); + themeData: themeData, + iconSize: _legend._iconSize!, + color: iconColor, + strokeWidth: 0.0, + iconType: _legend._iconType!); _textPainter.text = TextSpan( - style: _legend.textStyle.copyWith( - color: - _legend.textStyle.color.withOpacity(toggledLegendItemOpacity)), + style: _legend.textStyle!.copyWith( + color: _legend.textStyle!.color! + .withOpacity(toggledLegendItemOpacity)), text: _text); _textPainter.layout(); actualOffset = offset + - Offset(_legend._iconSize.width + _spacing, + Offset(_legend._iconSize!.width + _spacing, (size.height - _textPainter.height) / 2); _textPainter.paint(context.canvas, actualOffset); } @@ -1660,9 +2673,9 @@ class _RenderLegendItem extends RenderBox implements MouseTrackerAnnotation { class _SolidBarLegend extends StatefulWidget { const _SolidBarLegend({ - this.dataSource, - this.legend, - this.themeData, + required this.dataSource, + required this.legend, + required this.themeData, }); final dynamic dataSource; @@ -1674,12 +2687,12 @@ class _SolidBarLegend extends StatefulWidget { } class _SolidBarLegendState extends State<_SolidBarLegend> { - Axis _direction; - TextDirection _textDirection; - MapLegendLabelsPlacement _labelsPlacement; - TextPainter _textPainter; bool _isOverlapSegmentText = false; - Size _segmentSize; + late Axis _direction; + late TextDirection _textDirection; + late Size _segmentSize; + late TextPainter _textPainter; + MapLegendLabelsPlacement? _labelsPlacement; @override void initState() { @@ -1702,16 +2715,16 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { : (_direction == Axis.vertical ? TextDirection.ltr : textDirection); _textPainter.textScaleFactor = MediaQuery.of(context).textScaleFactor; return Padding( - padding: widget.legend.padding, + padding: widget.legend.padding!, child: Directionality( textDirection: _textDirection, child: Wrap( direction: _direction, spacing: widget.legend.spacing, - children: _getBarSegments(), runSpacing: 6, runAlignment: WrapAlignment.center, alignment: WrapAlignment.start, + children: _getBarSegments(), ), ), ); @@ -1735,33 +2748,41 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { List _getSegmentsForColorMapper(bool isLegendForBubbles) { final List legendItems = []; final int length = widget.dataSource.length; + String? currentText; for (int i = 0; i < length; i++) { _isOverlapSegmentText = false; final MapColorMapper colorMapper = widget.dataSource[i]; - String currentText; - if (i == 0) { - final List firstSegmentLabels = - _getStartSegmentLabel(colorMapper); - currentText = (firstSegmentLabels.length > 1 - ? firstSegmentLabels[1] - : firstSegmentLabels[0]); - } else { - currentText = _getText(colorMapper); - } - - if (i < length - 1 && - _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { - currentText = _getTrimmedText( - currentText, _getText(widget.dataSource[i + 1]), i, length); - } else if (_direction == Axis.horizontal && - _labelsPlacement == MapLegendLabelsPlacement.onItem) { - _isOverlapSegmentText = _getTextWidth(currentText) > _segmentSize.width; - } - _labelsPlacement = _labelsPlacement ?? (colorMapper.from != null ? MapLegendLabelsPlacement.betweenItems : MapLegendLabelsPlacement.onItem); + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + if (i == length - 1) { + currentText = _getTrimmedText( + _getText(widget.dataSource[i]), currentText, i, length); + } else { + if (i == 0) { + final List firstSegmentLabels = + _getStartSegmentLabel(colorMapper); + currentText = (firstSegmentLabels.length > 1 + ? firstSegmentLabels[1] + : firstSegmentLabels[0]); + } else { + currentText = _getText(colorMapper); + } + + currentText = _getTrimmedText( + currentText, _getText(widget.dataSource[i + 1]), i, length); + } + } else { + currentText = _getText(colorMapper); + if (_direction == Axis.horizontal && + _labelsPlacement == MapLegendLabelsPlacement.onItem) { + _isOverlapSegmentText = + _getTextWidth(currentText) > _segmentSize.width; + } + } + legendItems.add(_getSegment(currentText, colorMapper.color, i, isLegendForBubbles, length, colorMapper)); } @@ -1780,23 +2801,28 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { final Iterator currentIterator = widget.dataSource.values.iterator; final Iterator nextIterator = widget.dataSource.values.iterator; + String? text; nextIterator.moveNext(); while (currentIterator.moveNext()) { final MapModel mapModel = currentIterator.current; - String text = mapModel.primaryKey; - if (nextIterator.moveNext() && - _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { - text = _getTrimmedText(text, nextIterator.current.primaryKey, - mapModel.actualIndex, length); + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + if (nextIterator.moveNext()) { + text = _getTrimmedText(mapModel.primaryKey, + nextIterator.current.primaryKey, mapModel.actualIndex, length); + } else { + text = _getTrimmedText(currentIterator.current.primaryKey, text, + mapModel.actualIndex, length); + } } else if (_direction == Axis.horizontal && _labelsPlacement == MapLegendLabelsPlacement.onItem) { + text = mapModel.primaryKey; _isOverlapSegmentText = _getTextWidth(text) > _segmentSize.width; } barSegments.add(_getSegment( - text, + text!, isLegendForBubbles ? mapModel.bubbleColor : mapModel.shapeColor, - mapModel.legendMapperIndex, + mapModel.legendMapperIndex!, isLegendForBubbles, length)); } @@ -1805,33 +2831,47 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { } String _getTrimmedText( - String currentText, String nextText, int index, int length) { - if (widget.legend._labelOverflow == MapLabelOverflow.visible) { + String currentText, String? nextText, int index, int length) { + if (widget.legend._labelOverflow == MapLabelOverflow.visible || + currentText.isEmpty || + (nextText != null && nextText.isEmpty) || + nextText == null) { return currentText; } final Size barSize = _segmentSize; + double refCurrentTextWidth; + double refNextTextWidth; if (_direction == Axis.horizontal && _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { - final double refCurrentTextWidth = _getTextWidth(currentText) / 2; - final double refNextTextWidth = index + 1 == length - 1 && - widget.legend._edgeLabelsPlacement == - MapLegendEdgeLabelsPlacement.inside - ? _getTextWidth(nextText) - : _getTextWidth(nextText) / 2; + bool isLastInsideItem = false; + if (index == length - 1) { + isLastInsideItem = widget.legend._edgeLabelsPlacement == + MapLegendEdgeLabelsPlacement.inside; + refNextTextWidth = _getTextWidth(nextText) / 2; + refCurrentTextWidth = isLastInsideItem + ? _getTextWidth(currentText) + : _getTextWidth(currentText) / 2; + } else { + refCurrentTextWidth = _getTextWidth(currentText) / 2; + refNextTextWidth = index + 1 == length - 1 && + widget.legend._edgeLabelsPlacement == + MapLegendEdgeLabelsPlacement.inside + ? _getTextWidth(nextText) + : _getTextWidth(nextText) / 2; + } _isOverlapSegmentText = refCurrentTextWidth + refNextTextWidth > barSize.width + widget.legend.spacing; if (widget.legend._labelOverflow == MapLabelOverflow.ellipsis) { - if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { - final double textWidth = refCurrentTextWidth + refNextTextWidth; - return getTrimText( - currentText, - widget.legend.textStyle, - _segmentSize.width + widget.legend.spacing / 2, - _textPainter, - textWidth, - refNextTextWidth); - } + final double textWidth = refCurrentTextWidth + refNextTextWidth; + return getTrimText( + currentText, + widget.legend.textStyle!, + _segmentSize.width + widget.legend.spacing / 2, + _textPainter, + textWidth, + refNextTextWidth, + isLastInsideItem); } } @@ -1856,8 +2896,8 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { /// Returns the bar legend icon and label. Widget _getSegment( - String text, Color color, int index, bool isLegendForBubbles, int length, - [MapColorMapper colorMapper]) { + String text, Color? color, int index, bool isLegendForBubbles, int length, + [MapColorMapper? colorMapper]) { final Color iconColor = color ?? (isLegendForBubbles ? widget.themeData.bubbleColor @@ -1866,14 +2906,13 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { } Widget _getBarWithLabel(Color iconColor, int index, String text, - MapColorMapper colorMapper, int dataSourceLength) { + MapColorMapper? colorMapper, int dataSourceLength) { Offset textOffset = _getTextOffset(index, text, dataSourceLength); final CrossAxisAlignment crossAxisAlignment = _getCrossAxisAlignment(index, dataSourceLength); if (_direction == Axis.horizontal) { - textOffset = _textDirection == TextDirection.rtl && textOffset != null - ? -textOffset - : textOffset; + textOffset = + _textDirection == TextDirection.rtl ? -textOffset : textOffset; return Container( width: _segmentSize.width, child: Column( @@ -1898,7 +2937,7 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { } Widget _getVerticalBar(CrossAxisAlignment crossAxisAlignment, Color iconColor, - int index, String text, MapColorMapper colorMapper, Offset textOffset) { + int index, String text, MapColorMapper? colorMapper, Offset textOffset) { return Container( height: _segmentSize.width, child: Row( @@ -1927,8 +2966,8 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { } } - Widget _getTextWidget( - int index, String text, MapColorMapper colorMapper, Offset legendOffset) { + Widget _getTextWidget(int index, String text, MapColorMapper? colorMapper, + Offset legendOffset) { if (index == 0 && colorMapper != null && colorMapper.from != null && @@ -1942,7 +2981,7 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { Widget _getStartSegmentText( MapColorMapper colorMapper, String text, Offset legendOffset) { bool isStartTextOverlapping = false; - String startText; + String? startText; final List firstSegmentLabels = _getStartSegmentLabel(colorMapper); if (firstSegmentLabels.length > 1) { startText = firstSegmentLabels[0]; @@ -1952,7 +2991,9 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { if (_direction == Axis.horizontal && widget.legend._labelOverflow != MapLabelOverflow.visible && - startText != null) { + startText != null && + startText.isNotEmpty && + text.isNotEmpty) { final double refStartTextWidth = widget.legend._edgeLabelsPlacement == MapLegendEdgeLabelsPlacement.inside ? _getTextWidth(startText) @@ -1963,7 +3004,7 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { startText = getTrimText( startText, - widget.legend.textStyle, + widget.legend.textStyle!, _segmentSize.width + widget.legend.spacing / 2, _textPainter, refStartTextWidth + refCurrentTextWidth, @@ -1971,7 +3012,7 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { } } - Offset startTextOffset = _getStartTextOffset(startText); + Offset startTextOffset = _getStartTextOffset(startText!); startTextOffset = _textDirection == TextDirection.rtl && _direction == Axis.horizontal ? -startTextOffset @@ -1988,9 +3029,10 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { List _getStartSegmentLabel(MapColorMapper colorMapper) { if (colorMapper.from != null && colorMapper.text != null && - colorMapper.text[0] == '{' && + colorMapper.text!.isNotEmpty && + colorMapper.text![0] == '{' && _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { - final List splitText = colorMapper.text.split('},{'); + final List splitText = colorMapper.text!.split('},{'); if (splitText.length > 1) { splitText[0] = splitText[0].replaceAll('{', ''); splitText[1] = splitText[1].replaceAll('}', ''); @@ -2003,10 +3045,12 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { } Widget _getAlignedTextWidget(Offset offset, String text, bool isOverlapping) { - if (widget.legend._labelOverflow == MapLabelOverflow.hide && - isOverlapping) { + if ((widget.legend._labelOverflow == MapLabelOverflow.hide && + isOverlapping) || + text.isEmpty) { return SizedBox(width: 0.0, height: 0.0); } + return Directionality( textDirection: TextDirection.ltr, child: offset != Offset.zero @@ -2023,7 +3067,11 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { text, textAlign: TextAlign.center, softWrap: false, - overflow: TextOverflow.ellipsis, + overflow: + widget.legend._labelOverflow == MapLabelOverflow.ellipsis && + _labelsPlacement == MapLegendLabelsPlacement.onItem + ? TextOverflow.ellipsis + : TextOverflow.visible, style: widget.legend.textStyle, ), ); @@ -2108,37 +3156,39 @@ class _SolidBarLegendState extends State<_SolidBarLegend> { } } -class _MapLabel { - _MapLabel(this.label, this.offset); +class _GradientBarLabel { + _GradientBarLabel(this.label, this.offset, [this.isOverlapping]); - String label; + String? label; Offset offset; + + bool? isOverlapping; } // ignore: must_be_immutable class _GradientBarLegend extends StatelessWidget { _GradientBarLegend({ - this.dataSource, + required this.dataSource, this.source, - this.settings, - this.themeData, + required this.settings, + required this.themeData, }); final dynamic dataSource; - final MapElement source; + final MapElement? source; final MapLegend settings; final SfMapsThemeData themeData; final List colors = []; - final List<_MapLabel> labels = <_MapLabel>[]; + final List<_GradientBarLabel> labels = <_GradientBarLabel>[]; - Axis _direction; - Size _segmentSize; bool _isRTL = false; - double _referenceArea; - TextPainter _textPainter; - bool _isRangeColorMapper = false; - MapLegendLabelsPlacement _labelsPlacement; + bool _isOverlapSegmentText = false; + late Axis _direction; + late Size _segmentSize; + late TextPainter _textPainter; + late double _referenceArea; + MapLegendLabelsPlacement? _labelsPlacement; @override Widget build(BuildContext context) { @@ -2154,7 +3204,7 @@ class _GradientBarLegend extends StatelessWidget { ? Axis.horizontal : Axis.vertical); return Padding( - padding: settings.padding, + padding: settings.padding!, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { _updateSegmentSize(getBoxSize(constraints).shortestSide); @@ -2166,25 +3216,25 @@ class _GradientBarLegend extends StatelessWidget { void _updateSegmentSize(double shortestSide) { if (_direction == Axis.horizontal) { - final double availableWidth = shortestSide - settings.padding.horizontal; + final double availableWidth = shortestSide - settings.padding!.horizontal; _segmentSize = settings._segmentSize == null ? Size(availableWidth, 12.0) : Size( - settings._segmentSize.width > availableWidth + settings._segmentSize!.width > availableWidth ? availableWidth - : settings._segmentSize.width, - settings._segmentSize.height); + : settings._segmentSize!.width, + settings._segmentSize!.height); return; } - final double availableHeight = shortestSide - settings.padding.vertical; + final double availableHeight = shortestSide - settings.padding!.vertical; _segmentSize = settings._segmentSize == null ? Size(12.0, availableHeight) : Size( - settings._segmentSize.width, - settings._segmentSize.height > availableHeight + settings._segmentSize!.width, + settings._segmentSize!.height > availableHeight ? availableHeight - : settings._segmentSize.height); + : settings._segmentSize!.height); } void _collectLabelsAndColors() { @@ -2192,31 +3242,73 @@ class _GradientBarLegend extends StatelessWidget { _referenceArea = _direction == Axis.horizontal ? _segmentSize.width : _segmentSize.height; - final double slab = _referenceArea / (length - 1); final bool isLegendForBubbles = settings.source == MapElement.bubble; if (dataSource != null && dataSource.isNotEmpty) { if (dataSource is List) { _collectColorMapperLabelsAndColors(length); } else { - int index = 0; - dataSource.forEach((String key, MapModel mapModel) { - assert(mapModel.primaryKey != null); - labels.add(_MapLabel(mapModel.primaryKey, - _getTextOffset(mapModel.primaryKey, index, length - 1, slab))); - colors.add( - isLegendForBubbles ? mapModel.bubbleColor : mapModel.shapeColor); - index++; - }); + final int length = dataSource.length; + String? text; + double slab; + // If we use as iterator, it will check first and second model and + // then check third and fourth model. But we can't check second and + // third item is overlapping or not. Since the iterator in second model. + // So we uses two iterator. If we use move next first iterator gives + // current model and second iterator gives next model. + final Iterator currentIterator = dataSource.values.iterator; + final Iterator nextIterator = dataSource.values.iterator; + nextIterator.moveNext(); + while (currentIterator.moveNext()) { + final MapModel mapModel = currentIterator.current; + int positionIndex; + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + slab = _referenceArea / (length - 1); + positionIndex = mapModel.actualIndex; + } else { + slab = _referenceArea / length; + positionIndex = mapModel.actualIndex + 1; + } + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + if (nextIterator.moveNext()) { + text = _getTrimmedText(mapModel.primaryKey, positionIndex, length, + slab, nextIterator.current.primaryKey); + } else { + text = _getTrimmedText( + mapModel.primaryKey, positionIndex, length, slab, text); + } + } else { + if (_direction == Axis.horizontal) { + text = _getTrimmedText( + mapModel.primaryKey, positionIndex, length, slab); + } + } + + labels.add(_GradientBarLabel( + text, + _getTextOffset(text!, positionIndex, length - 1, slab), + _isOverlapSegmentText)); + final Color iconColor = (isLegendForBubbles + ? mapModel.bubbleColor + : mapModel.shapeColor) ?? + (isLegendForBubbles + ? themeData.bubbleColor + : themeData.layerColor); + + colors.add(iconColor); + } } } } void _collectColorMapperLabelsAndColors(int length) { if (dataSource.isNotEmpty) { - _isRangeColorMapper = dataSource[0].from != null; - final double slab = - _referenceArea / (_isRangeColorMapper ? length : length - 1); + final double slab = _referenceArea / + (_labelsPlacement == MapLegendLabelsPlacement.betweenItems && + dataSource[0].value != null + ? length - 1 + : length); for (int i = 0; i < length; i++) { + _isOverlapSegmentText = false; final MapColorMapper colorMapper = dataSource[i]; String text; if (i == 0) { @@ -2229,42 +3321,147 @@ class _GradientBarLegend extends StatelessWidget { text = _getActualText(colorMapper); } - if (_isRangeColorMapper) { - if (i == 0 && - _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { - String startText; - final List firstSegmentLabels = - _getStartSegmentLabel(colorMapper); - if (firstSegmentLabels.length > 1) { - startText = firstSegmentLabels[0]; - } else { - startText = colorMapper.from?.toString(); - } - - labels.add(_MapLabel( - startText, _getTextOffset(startText, 0, length, slab))); - } - // For range color mapper, slab is equals to the color mapper - // length. So adding +1 to point out its position index. - labels - .add(_MapLabel(text, _getTextOffset(text, i + 1, length, slab))); + if (dataSource[0].from != null) { + _collectRageColorMapperLabels(i, colorMapper, text, slab, length); } else { + final int positionIndex = + _labelsPlacement == MapLegendLabelsPlacement.onItem ? i + 1 : i; + if (_labelsPlacement == MapLegendLabelsPlacement.onItem) { + text = _getTrimmedText(text, i, length, slab); + } else if (i < length - 1) { + text = _getTrimmedText( + text, i, length, slab, _getActualText(dataSource[i + 1])); + } // For equal color mapper, slab is equals to the color mapper // length -1. - labels - .add(_MapLabel(text, _getTextOffset(text, i, length - 1, slab))); + labels.add(_GradientBarLabel( + text, + _getTextOffset(text, positionIndex, length - 1, slab), + _isOverlapSegmentText)); } + colors.add(colorMapper.color); } } } + void _collectRageColorMapperLabels( + int i, MapColorMapper colorMapper, String text, double slab, int length) { + if (i == 0 && _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + String? startText; + final List firstSegmentLabels = + _getStartSegmentLabel(colorMapper); + if (firstSegmentLabels.length > 1) { + startText = firstSegmentLabels[0]; + } else { + startText = colorMapper.from?.toString(); + } + + if (_direction == Axis.horizontal && + _labelsPlacement == MapLegendLabelsPlacement.betweenItems && + startText != null && + startText.isNotEmpty && + text.isNotEmpty) { + final double refCurrentTextWidth = + settings._edgeLabelsPlacement == MapLegendEdgeLabelsPlacement.inside + ? _getTextWidth(startText) + : _getTextWidth(startText) / 2; + final double refNextTextWidth = _getTextWidth(text) / 2; + _isOverlapSegmentText = refCurrentTextWidth + refNextTextWidth > slab; + if (settings._labelOverflow == MapLabelOverflow.ellipsis) { + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + final double textWidth = refCurrentTextWidth + refNextTextWidth; + startText = getTrimText(startText, settings.textStyle!, slab, + _textPainter, textWidth, refNextTextWidth); + } + } + } + + labels.add(_GradientBarLabel(startText, + _getTextOffset(startText!, i, length, slab), _isOverlapSegmentText)); + } + + if (_labelsPlacement == MapLegendLabelsPlacement.onItem) { + text = _getTrimmedText(text, i, length, slab); + } else if (i < length - 1) { + text = _getTrimmedText( + text, i, length, slab, _getActualText(dataSource[i + 1])); + } + + // For range color mapper, slab is equals to the color mapper + // length. So adding +1 to point out its position index. + labels.add(_GradientBarLabel(text, + _getTextOffset(text, i + 1, length, slab), _isOverlapSegmentText)); + } + + String _getTrimmedText(String currentText, int index, int length, double slab, + [String? nextText]) { + if (settings._labelOverflow == MapLabelOverflow.visible || + currentText.isEmpty || + (nextText != null && nextText.isEmpty) || + nextText == null) { + return currentText; + } + + if (_direction == Axis.horizontal && + _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + double refCurrentTextWidth; + double refNextTextWidth; + bool isLastInsideItem = false; + if (index == length - 1) { + refNextTextWidth = _getTextWidth(nextText) / 2; + + if (settings._edgeLabelsPlacement == + MapLegendEdgeLabelsPlacement.inside) { + refCurrentTextWidth = _getTextWidth(currentText); + isLastInsideItem = true; + } else { + refCurrentTextWidth = _getTextWidth(currentText) / 2; + isLastInsideItem = false; + } + } else { + refCurrentTextWidth = _getTextWidth(currentText) / 2; + refNextTextWidth = index + 1 == length - 1 && + settings._edgeLabelsPlacement == + MapLegendEdgeLabelsPlacement.inside + ? _getTextWidth(nextText) + : _getTextWidth(nextText) / 2; + } + _isOverlapSegmentText = refCurrentTextWidth + refNextTextWidth > slab; + if (settings._labelOverflow == MapLabelOverflow.ellipsis && + _isOverlapSegmentText) { + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + final double textWidth = refCurrentTextWidth + refNextTextWidth; + return getTrimText(currentText, settings.textStyle!, slab, + _textPainter, textWidth, refNextTextWidth, isLastInsideItem); + } + } + } else if (_direction == Axis.horizontal && + _labelsPlacement == MapLegendLabelsPlacement.onItem) { + final double textWidth = _getTextWidth(currentText); + _isOverlapSegmentText = textWidth > slab; + if (_isOverlapSegmentText) { + return getTrimText( + currentText, settings.textStyle!, slab, _textPainter, textWidth); + } + } + + return currentText; + } + + double _getTextWidth(String text) { + _textPainter.text = TextSpan(text: text, style: settings.textStyle); + _textPainter.layout(); + return _textPainter.width; + } + List _getStartSegmentLabel(MapColorMapper colorMapper) { if (colorMapper.from != null && colorMapper.text != null && - colorMapper.text[0] == '{' && + colorMapper.text!.isNotEmpty && + colorMapper.text![0] == '{' && _labelsPlacement == MapLegendLabelsPlacement.betweenItems) { - final List splitText = colorMapper.text.split('},{'); + final List splitText = colorMapper.text!.split('},{'); if (splitText.length > 1) { splitText[0] = splitText[0].replaceAll('{', ''); splitText[1] = splitText[1].replaceAll('}', ''); @@ -2283,7 +3480,8 @@ class _GradientBarLegend extends StatelessWidget { final bool canAdjustLabelToCenter = settings._edgeLabelsPlacement == MapLegendEdgeLabelsPlacement.center && (positionIndex == 0 || positionIndex == length) || - (positionIndex > 0 && positionIndex < length); + (positionIndex > 0 && positionIndex < length) || + settings._labelsPlacement == MapLegendLabelsPlacement.onItem; if (_direction == Axis.horizontal) { return _getHorizontalOffset( canAdjustLabelToCenter, positionIndex, slab, length); @@ -2291,7 +3489,12 @@ class _GradientBarLegend extends StatelessWidget { final double referenceTextWidth = canAdjustLabelToCenter ? _textPainter.height / 2 : (positionIndex == length ? _textPainter.height : 0.0); - return Offset(0.0, slab * positionIndex - referenceTextWidth); + if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { + return Offset(0.0, slab * positionIndex - referenceTextWidth); + } + + return Offset( + 0.0, (slab * positionIndex) - referenceTextWidth - slab / 2); } } @@ -2300,13 +3503,9 @@ class _GradientBarLegend extends StatelessWidget { if (_isRTL) { final double referenceTextWidth = canAdjustLabelToCenter ? -_textPainter.width / 2 - : (positionIndex == 0 ? _textPainter.width : 0.0); - double dx = slab * positionIndex - referenceTextWidth; - if (positionIndex == 0) { - dx = _segmentSize.width + dx; - } else { - dx = _segmentSize.width - dx; - } + : (positionIndex == 0 ? -_textPainter.width : 0.0); + final double dx = + _segmentSize.width - (slab * positionIndex - referenceTextWidth); if (_labelsPlacement == MapLegendLabelsPlacement.betweenItems) { return Offset(dx, 0.0); @@ -2332,10 +3531,8 @@ class _GradientBarLegend extends StatelessWidget { } List _getChildren() { - double labelBoxWidth = _segmentSize.width; - double labelBoxHeight; - double horizontalSpacing = 0.0; - double verticalSpacing = settings.spacing; + double? labelBoxWidth = _segmentSize.width; + double? labelBoxHeight; Alignment startAlignment = Alignment.centerLeft; Alignment endAlignment = Alignment.centerRight; @@ -2344,8 +3541,6 @@ class _GradientBarLegend extends StatelessWidget { labelBoxHeight = _segmentSize.height; startAlignment = Alignment.topCenter; endAlignment = Alignment.bottomCenter; - horizontalSpacing = settings.spacing; - verticalSpacing = 0.0; } if (_isRTL && _direction == Axis.horizontal) { @@ -2363,7 +3558,9 @@ class _GradientBarLegend extends StatelessWidget { begin: startAlignment, end: endAlignment, colors: colors), ), ), - SizedBox(width: horizontalSpacing, height: verticalSpacing), + SizedBox( + width: _direction == Axis.vertical ? 7.0 : 0.0, + height: _direction == Axis.horizontal ? 7.0 : 0.0), Container( width: labelBoxWidth, height: labelBoxHeight, child: _getLabels()), ]; @@ -2372,17 +3569,24 @@ class _GradientBarLegend extends StatelessWidget { Widget _getLabels() { return Stack( textDirection: TextDirection.ltr, - children: List.generate( - labels.length, - (int index) => Transform.translate( - offset: labels[index].offset, - child: Text( - labels[index].label, - style: settings.textStyle, - softWrap: false, - ), - ), - ), + children: List.generate(labels.length, (int index) { + if ((settings._labelOverflow == MapLabelOverflow.hide && + labels[index].isOverlapping!) || + labels[index].label!.isEmpty) { + return SizedBox(width: 0.0, height: 0.0); + } + + return Directionality( + textDirection: TextDirection.ltr, + child: Transform.translate( + offset: labels[index].offset, + child: Text( + labels[index].label!, + style: settings.textStyle, + softWrap: false, + ), + )); + }), ); } diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart b/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart index 4f25b3bf6..22c0c9b4a 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart @@ -7,36 +7,35 @@ import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../behavior/zoom_pan_behavior.dart'; -import '../controller/default_controller.dart'; +import '../common.dart'; +import '../controller/map_controller.dart'; import '../enum.dart'; import '../layer/layer_base.dart'; import '../layer/shape_layer.dart'; import '../utils.dart'; -import 'shapes.dart'; // ignore_for_file: public_member_api_docs -class ShapeLayerMarkerContainer extends Stack { - ShapeLayerMarkerContainer({ - this.tooltipKey, - this.markerTooltipBuilder, - List children, - this.controller, +class MarkerContainer extends Stack { + MarkerContainer({ + required this.markerTooltipBuilder, + required this.controller, this.sublayer, + this.ancestor, + List? children, }) : super(children: children ?? []); - final GlobalKey tooltipKey; - final IndexedWidgetBuilder markerTooltipBuilder; + final IndexedWidgetBuilder? markerTooltipBuilder; final MapController controller; - final MapShapeSublayer sublayer; + final MapShapeSublayer? sublayer; + final MapLayerInheritedWidget? ancestor; @override RenderStack createRenderObject(BuildContext context) { return _RenderMarkerContainer() - ..tooltipKey = tooltipKey ..markerTooltipBuilder = markerTooltipBuilder ..controller = controller ..sublayer = sublayer - ..markerContainer = this; + ..container = this; } @override @@ -45,105 +44,58 @@ class ShapeLayerMarkerContainer extends Stack { renderObject ..markerTooltipBuilder = markerTooltipBuilder ..sublayer = sublayer - ..markerContainer = this; + ..container = this; } } class _RenderMarkerContainer extends RenderStack { - GlobalKey tooltipKey; - - IndexedWidgetBuilder markerTooltipBuilder; - - MapController controller; - - MapShapeSublayer sublayer; - - ShapeLayerMarkerContainer markerContainer; + late MarkerContainer container; + late MapController controller; + GlobalKey? tooltipKey; + IndexedWidgetBuilder? markerTooltipBuilder; + MapShapeSublayer? sublayer; int getMarkerIndex(MapMarker marker) { - return markerContainer.children.indexOf(marker); + return container.children.indexOf(marker); } - Size get markerContainerSize => - controller.isTileLayerChild ? controller.getTileSize() : size; - - double get shapeLayerSizeFactor => controller.isTileLayerChild - ? controller.shapeLayerSizeFactor - : (controller.shapeLayerSizeFactor * - ((controller.gesture == Gesture.scale) ? controller.localScale : 1)); - - Offset get shapeLayerOffset { - if (!controller.isInInteractive) { - return controller.shapeLayerOffset; - } else { - if (controller.gesture == Gesture.scale) { - return controller.getZoomingTranslation() + controller.normalize; - } else { - return controller.shapeLayerOffset + controller.panDistance; - } - } - } + Size get containerSize => controller.layerType == LayerType.tile + ? controller.getTileSize(controller.tileCurrentLevelDetails.zoomLevel) + : controller.shapeLayerBoxSize; void _handleZooming(MapZoomDetails details) { - _updateChildren(); - markNeedsPaint(); + markNeedsLayout(); } void _handlePanning(MapPanDetails details) { - _updateChildren(); - markNeedsPaint(); + markNeedsLayout(); } void _handleReset() { - _updateChildren(); - markNeedsPaint(); + markNeedsLayout(); } void _handleRefresh() { - _updateChildren(); - markNeedsPaint(); - } - - void _updateChildren() { - final double factor = shapeLayerSizeFactor; - final Offset translation = shapeLayerOffset; - RenderBox child = firstChild; - while (child != null) { - final RenderMapMarker marker = child; - final StackParentData childParentData = child.parentData; - childParentData.offset = pixelFromLatLng(marker.latitude, - marker.longitude, markerContainerSize, translation, factor) - - Offset(marker.size.width / 2, marker.size.height / 2); - child = childParentData.nextSibling; - } + markNeedsLayout(); } @override void attach(PipelineOwner owner) { super.attach(owner); - if (controller == null) { - final RenderShapeLayer subLayerParent = parent.parent; - controller = subLayerParent.controller; - } - - if (controller != null) { - controller - ..addZoomingListener(_handleZooming) - ..addPanningListener(_handlePanning) - ..addResetListener(_handleReset) - ..addRefreshListener(_handleRefresh); - } + controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset) + ..addRefreshListener(_handleRefresh); } @override void detach() { - if (controller != null) { - controller - ..removeZoomingListener(_handleZooming) - ..removePanningListener(_handlePanning) - ..removeResetListener(_handleReset) - ..removeRefreshListener(_handleRefresh); - } + controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset) + ..removeRefreshListener(_handleRefresh); super.detach(); } @@ -152,16 +104,28 @@ class _RenderMarkerContainer extends RenderStack { @override void performLayout() { - size = getBoxSize(constraints); - final double factor = shapeLayerSizeFactor; - final Offset translation = shapeLayerOffset; - RenderBox child = firstChild; + size = controller.layerType == LayerType.shape + ? controller.shapeLayerBoxSize + : controller.tileLayerBoxSize!; + final double factor = getLayerSizeFactor(controller); + final Offset translation = getTranslationOffset(controller); + RenderBox? child = firstChild; while (child != null) { - final RenderMapMarker marker = child; - final StackParentData childParentData = child.parentData; + // ignore: avoid_as + final _RenderMapMarker marker = child as _RenderMapMarker; + final StackParentData childParentData = + // ignore: avoid_as + child.parentData as StackParentData; child.layout(constraints, parentUsesSize: true); childParentData.offset = pixelFromLatLng(marker.latitude, - marker.longitude, markerContainerSize, translation, factor) - + marker.longitude, containerSize, translation, factor); + if (controller.layerType == LayerType.tile) { + final TileZoomLevelDetails level = controller.tileCurrentLevelDetails; + childParentData.offset = + childParentData.offset.scale(level.scale, level.scale) + + level.translatePoint; + } + childParentData.offset -= Offset(marker.size.width / 2, marker.size.height / 2); child = childParentData.nextSibling; } @@ -169,12 +133,14 @@ class _RenderMarkerContainer extends RenderStack { @override void paint(PaintingContext context, Offset offset) { - RenderBox child = firstChild; + RenderBox? child = firstChild; while (child != null) { - final StackParentData childParentData = child.parentData; + final StackParentData childParentData = + // ignore: avoid_as + child.parentData as StackParentData; final Rect childRect = Rect.fromLTWH(childParentData.offset.dx, childParentData.offset.dy, child.size.width, child.size.height); - if (sublayer != null || controller.visibleBounds.overlaps(childRect)) { + if (sublayer != null || controller.visibleBounds!.overlaps(childRect)) { context.paintChild(child, childParentData.offset); } child = childParentData.nextSibling; @@ -196,6 +162,7 @@ class _RenderMarkerContainer extends RenderStack { /// /// ```dart /// List data; +/// MapShapeSource _mapSource; /// /// @override /// void initState() { @@ -207,10 +174,16 @@ class _RenderMarkerContainer extends RenderStack { /// Model('Russia', 61.52401, 105.318756) /// ]; /// +/// _mapSource = MapShapeSource.asset( +/// 'assets/world_map.json', +/// shapeDataField: 'name', +/// dataCount: data.length, +/// primaryValueMapper: (int index) => data[index].country, +/// ); /// super.initState(); /// } /// -/// @override +/// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( @@ -221,14 +194,9 @@ class _RenderMarkerContainer extends RenderStack { /// child: SfMaps( /// layers: [ /// MapShapeLayer( -/// source: MapShapeSource.asset( -/// 'assets/world_map.json', -/// shapeDataField: 'name', -/// dataCount: data.length, -/// primaryValueMapper: (index) => data[index].country, -/// ), +/// source: _mapSource, /// initialMarkersCount: 5, -/// markerBuilder: (BuildContext context, int index){ +/// markerBuilder: (BuildContext context, int index) { /// return MapMarker( /// latitude: data[index].latitude, /// longitude: data[index].longitude, @@ -257,23 +225,22 @@ class _RenderMarkerContainer extends RenderStack { class MapMarker extends SingleChildRenderObjectWidget { /// Creates a [MapMarker]. const MapMarker({ - Key key, - @required this.latitude, - @required this.longitude, + Key? key, + required this.latitude, + required this.longitude, this.size, this.iconColor, this.iconStrokeColor, - this.iconStrokeWidth = 1.0, + this.iconStrokeWidth, this.iconType = MapIconType.circle, - Widget child, - }) : assert(longitude != null), - assert(latitude != null), - super(key: key, child: child); + Widget? child, + }) : super(key: key, child: child); /// Sets the latitude for the marker on the map. /// /// ```dart /// List data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { @@ -285,10 +252,16 @@ class MapMarker extends SingleChildRenderObjectWidget { /// Model('Russia', 61.52401, 105.318756) /// ]; /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// dataCount: data.length, + /// primaryValueMapper: (int index) => data[index].country, + /// ); /// super.initState(); /// } /// - /// @override + /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( @@ -299,14 +272,9 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'name', - /// dataCount: data.length, - /// primaryValueMapper: (index) => data[index].country, - /// ), + /// source: _mapSource, /// initialMarkersCount: 5, - /// markerBuilder: (BuildContext context, int index){ + /// markerBuilder: (BuildContext context, int index) { /// return MapMarker( /// latitude: data[index].latitude, /// longitude: data[index].longitude, @@ -338,6 +306,7 @@ class MapMarker extends SingleChildRenderObjectWidget { /// /// ```dart /// List data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { @@ -349,10 +318,16 @@ class MapMarker extends SingleChildRenderObjectWidget { /// Model('Russia', 61.52401, 105.318756) /// ]; /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// dataCount: data.length, + /// primaryValueMapper: (int index) => data[index].country, + /// ); /// super.initState(); /// } /// - /// @override + /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( @@ -363,14 +338,9 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'name', - /// dataCount: data.length, - /// primaryValueMapper: (index) => data[index].country, - /// ), + /// source: _mapSource, /// initialMarkersCount: 5, - /// markerBuilder: (BuildContext context, int index){ + /// markerBuilder: (BuildContext context, int index) { /// return MapMarker( /// latitude: data[index].latitude, /// longitude: data[index].longitude, @@ -407,6 +377,7 @@ class MapMarker extends SingleChildRenderObjectWidget { /// /// ```dart /// List data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { @@ -418,10 +389,16 @@ class MapMarker extends SingleChildRenderObjectWidget { /// Model('Russia', 61.52401, 105.318756) /// ]; /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// dataCount: data.length, + /// primaryValueMapper: (int index) => data[index].country, + /// ); /// super.initState(); /// } /// - /// @override + /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( @@ -432,14 +409,9 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'name', - /// dataCount: data.length, - /// primaryValueMapper: (index) => data[index].country, - /// ), + /// source: _mapSource, /// initialMarkersCount: 5, - /// markerBuilder: (BuildContext context, int index){ + /// markerBuilder: (BuildContext context, int index) { /// return MapMarker( /// latitude: data[index].latitude, /// longitude: data[index].longitude, @@ -466,12 +438,13 @@ class MapMarker extends SingleChildRenderObjectWidget { /// See also: /// * [MapShapeLayerController], [MapTileLayerController] for dynamically /// updating the markers. - final Size size; + final Size? size; /// Sets the icon color for the marker. /// /// ```dart /// List data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { @@ -483,10 +456,16 @@ class MapMarker extends SingleChildRenderObjectWidget { /// Model('Russia', 61.52401, 105.318756) /// ]; /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// dataCount: data.length, + /// primaryValueMapper: (int index) => data[index].country, + /// ); /// super.initState(); /// } /// - /// @override + /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( @@ -497,14 +476,9 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'name', - /// dataCount: data.length, - /// primaryValueMapper: (index) => data[index].country, - /// ), + /// source: _mapSource, /// initialMarkersCount: 5, - /// markerBuilder: (BuildContext context, int index){ + /// markerBuilder: (BuildContext context, int index) { /// return MapMarker( /// latitude: data[index].latitude, /// longitude: data[index].longitude, @@ -531,12 +505,13 @@ class MapMarker extends SingleChildRenderObjectWidget { /// See also: /// * [MapShapeLayerController], [MapTileLayerController] for dynamically /// updating the markers. - final Color iconColor; + final Color? iconColor; /// Sets the icon's stroke color for the marker. /// /// ```dart /// List data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { @@ -548,10 +523,16 @@ class MapMarker extends SingleChildRenderObjectWidget { /// Model('Russia', 61.52401, 105.318756) /// ]; /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// dataCount: data.length, + /// primaryValueMapper: (int index) => data[index].country, + /// ); /// super.initState(); /// } /// - /// @override + /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( @@ -562,14 +543,9 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'name', - /// dataCount: data.length, - /// primaryValueMapper: (index) => data[index].country, - /// ), + /// source: _mapSource, /// initialMarkersCount: 5, - /// markerBuilder: (BuildContext context, int index){ + /// markerBuilder: (BuildContext context, int index) { /// return MapMarker( /// latitude: data[index].latitude, /// longitude: data[index].longitude, @@ -597,12 +573,13 @@ class MapMarker extends SingleChildRenderObjectWidget { /// See also: /// * [MapShapeLayerController], [MapTileLayerController] for dynamically /// updating the markers. - final Color iconStrokeColor; + final Color? iconStrokeColor; /// Sets the icon's stroke width for the marker. /// /// ```dart /// List data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { @@ -614,10 +591,16 @@ class MapMarker extends SingleChildRenderObjectWidget { /// Model('Russia', 61.52401, 105.318756) /// ]; /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// dataCount: data.length, + /// primaryValueMapper: (int index) => data[index].country, + /// ); /// super.initState(); /// } /// - /// @override + /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( @@ -628,14 +611,9 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'name', - /// dataCount: data.length, - /// primaryValueMapper: (index) => data[index].country, - /// ), + /// source: _mapSource, /// initialMarkersCount: 5, - /// markerBuilder: (BuildContext context, int index){ + /// markerBuilder: (BuildContext context, int index) { /// return MapMarker( /// latitude: data[index].latitude, /// longitude: data[index].longitude, @@ -663,7 +641,7 @@ class MapMarker extends SingleChildRenderObjectWidget { /// See also: /// * [MapShapeLayerController], [MapTileLayerController] for dynamically /// updating the markers. - final double iconStrokeWidth; + final double? iconStrokeWidth; /// Sets the icon's shape of the marker. /// @@ -671,6 +649,7 @@ class MapMarker extends SingleChildRenderObjectWidget { /// /// ```dart /// List data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { @@ -682,10 +661,16 @@ class MapMarker extends SingleChildRenderObjectWidget { /// Model('Russia', 61.52401, 105.318756) /// ]; /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// dataCount: data.length, + /// primaryValueMapper: (int index) => data[index].country, + /// ); /// super.initState(); /// } /// - /// @override + /// @override /// Widget build(BuildContext context) { /// return Scaffold( /// body: Center( @@ -696,23 +681,17 @@ class MapMarker extends SingleChildRenderObjectWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'name', - /// dataCount: data.length, - /// primaryValueMapper: (index) => data[index].country, - /// ), + /// source: _mapSource, /// initialMarkersCount: 5, - /// markerBuilder: (BuildContext context, int index){ + /// markerBuilder: (BuildContext context, int index) { /// return MapMarker( /// latitude: data[index].latitude, /// longitude: data[index].longitude, - /// iconStrokeColor: Colors.green[900], /// iconType: MapIconType.triangle, /// ); /// }, /// ), - /// ],s + /// ], /// ), /// ), /// ) @@ -735,7 +714,7 @@ class MapMarker extends SingleChildRenderObjectWidget { @override RenderObject createRenderObject(BuildContext context) { - return RenderMapMarker( + return _RenderMapMarker( longitude: longitude, latitude: latitude, markerSize: size, @@ -743,13 +722,13 @@ class MapMarker extends SingleChildRenderObjectWidget { iconStrokeColor: iconStrokeColor, iconStrokeWidth: iconStrokeWidth, iconType: iconType, - themeData: SfMapsTheme.of(context), + themeData: SfMapsTheme.of(context)!, marker: this, ); } @override - void updateRenderObject(BuildContext context, RenderMapMarker renderObject) { + void updateRenderObject(BuildContext context, _RenderMapMarker renderObject) { renderObject ..longitude = longitude ..latitude = latitude @@ -758,23 +737,24 @@ class MapMarker extends SingleChildRenderObjectWidget { ..iconStrokeColor = iconStrokeColor ..iconStrokeWidth = iconStrokeWidth ..iconType = iconType - ..themeData = SfMapsTheme.of(context) + ..themeData = SfMapsTheme.of(context)! ..marker = this; } } -class RenderMapMarker extends RenderProxyBox implements MouseTrackerAnnotation { - RenderMapMarker({ - double longitude, - double latitude, - Size markerSize, - Color iconColor, - Color iconStrokeColor, - double iconStrokeWidth, - MapIconType iconType, - SfMapsThemeData themeData, - MapMarker marker, - }) : _longitude = longitude, +class _RenderMapMarker extends RenderProxyBox + implements MouseTrackerAnnotation { + _RenderMapMarker({ + required double longitude, + required double latitude, + required Size? markerSize, + required Color? iconColor, + required Color? iconStrokeColor, + required double? iconStrokeWidth, + required MapIconType iconType, + required SfMapsThemeData themeData, + required MapMarker marker, + }) : _longitude = longitude, _latitude = latitude, _markerSize = markerSize, _iconColor = iconColor, @@ -787,10 +767,8 @@ class RenderMapMarker extends RenderProxyBox implements MouseTrackerAnnotation { } final MapIconShape _iconShape = const MapIconShape(); - final Size _defaultMarkerSize = const Size(14.0, 14.0); - - TapGestureRecognizer _tapGestureRecognizer; + late TapGestureRecognizer _tapGestureRecognizer; MapMarker marker; @@ -800,13 +778,8 @@ class RenderMapMarker extends RenderProxyBox implements MouseTrackerAnnotation { if (_latitude == value) { return; } - assert(value != null); _latitude = value; - - if (parent is _RenderMarkerContainer) { - _updatePosition(); - } - markNeedsPaint(); + markNeedsLayout(); } double get longitude => _longitude; @@ -815,31 +788,23 @@ class RenderMapMarker extends RenderProxyBox implements MouseTrackerAnnotation { if (_longitude == value) { return; } - assert(value != null); _longitude = value; - - if (parent is _RenderMarkerContainer) { - _updatePosition(); - } - markNeedsPaint(); + markNeedsLayout(); } - Size get markerSize => _markerSize; - Size _markerSize; - set markerSize(Size value) { + Size? get markerSize => _markerSize; + Size? _markerSize; + set markerSize(Size? value) { if (_markerSize == value) { return; } _markerSize = value; - if (parent is _RenderMarkerContainer) { - _updatePosition(); - } markNeedsLayout(); } - Color get iconColor => _iconColor; - Color _iconColor; - set iconColor(Color value) { + Color? get iconColor => _iconColor; + Color? _iconColor; + set iconColor(Color? value) { if (_iconColor == value) { return; } @@ -849,9 +814,9 @@ class RenderMapMarker extends RenderProxyBox implements MouseTrackerAnnotation { } } - Color get iconStrokeColor => _iconStrokeColor; - Color _iconStrokeColor; - set iconStrokeColor(Color value) { + Color? get iconStrokeColor => _iconStrokeColor; + Color? _iconStrokeColor; + set iconStrokeColor(Color? value) { if (_iconStrokeColor == value) { return; } @@ -861,13 +826,12 @@ class RenderMapMarker extends RenderProxyBox implements MouseTrackerAnnotation { } } - double get iconStrokeWidth => _iconStrokeWidth; - double _iconStrokeWidth; - set iconStrokeWidth(double value) { + double? get iconStrokeWidth => _iconStrokeWidth; + double? _iconStrokeWidth; + set iconStrokeWidth(double? value) { if (_iconStrokeWidth == value) { return; } - assert(value != null); _iconStrokeWidth = value; if (child == null) { markNeedsPaint(); @@ -880,7 +844,6 @@ class RenderMapMarker extends RenderProxyBox implements MouseTrackerAnnotation { if (_iconType == value) { return; } - assert(value != null); _iconType = value; if (child == null) { markNeedsPaint(); @@ -904,67 +867,56 @@ class RenderMapMarker extends RenderProxyBox implements MouseTrackerAnnotation { } void _handlePointerEnter(PointerEnterEvent event) { - _handleInteraction(); + _handleInteraction(PointerKind.hover); } void _handlePointerExit(PointerExitEvent event) { - _handleInteraction(isExit: true); + final _RenderMarkerContainer markerContainer = + // ignore: avoid_as + parent as _RenderMarkerContainer; + if (markerContainer.markerTooltipBuilder != null) { + final ShapeLayerChildRenderBoxBase tooltipRenderBox = + markerContainer.controller.tooltipKey?.currentContext! + // ignore: avoid_as + .findRenderObject() as ShapeLayerChildRenderBoxBase; + tooltipRenderBox.hideTooltip(); + } } - void _handleInteraction({bool isExit = false}) { - // For [MapMarker] shape and tile layer, we had different parent classes. - // So, we used the dynamic keyword to access both parent commonly. - final dynamic markerParent = parent; - int sublayerIndex; - if (markerParent.markerTooltipBuilder != null) { - if (markerParent is _RenderMarkerContainer && - markerParent.sublayer != null) { - final RenderShapeLayer shapeLayerRenderBox = markerParent.parent.parent; - final RenderSublayerContainer sublayerContainer = - shapeLayerRenderBox.parent; - sublayerIndex = - sublayerContainer.getSublayerIndex(markerParent.sublayer); + void _handleInteraction([PointerKind kind = PointerKind.touch]) { + int? sublayerIndex; + final _RenderMarkerContainer markerContainerRenderBox = + // ignore: avoid_as + parent as _RenderMarkerContainer; + if (markerContainerRenderBox.markerTooltipBuilder != null) { + if (markerContainerRenderBox.sublayer != null) { + sublayerIndex = markerContainerRenderBox.container.ancestor!.sublayers! + .indexOf(markerContainerRenderBox.sublayer!); } - final ShapeLayerChildRenderBoxBase tooltipRenderObject = - markerParent.controller.tooltipKey.currentContext.findRenderObject(); - final StackParentData childParentData = parentData; - // The [sublayerIndex] is not applicable, if the actual layer is - // shape or tile layer. - tooltipRenderObject.paintTooltip( - isExit ? null : markerParent.getMarkerIndex(marker), + final ShapeLayerChildRenderBoxBase tooltipRenderBox = + markerContainerRenderBox.controller.tooltipKey?.currentContext! + // ignore: avoid_as + .findRenderObject() as ShapeLayerChildRenderBoxBase; + // ignore: avoid_as + final StackParentData childParentData = parentData as StackParentData; + tooltipRenderBox.paintTooltip( + markerContainerRenderBox.getMarkerIndex(marker), childParentData.offset & size, MapLayerElement.marker, + kind, + // [sublayerIndex] is applicable only when the markers + // added to the [MapShapeSublayer]. sublayerIndex); } } - void _updatePosition() { - final _RenderMarkerContainer markerParent = parent; - final StackParentData childParentData = parentData; - if (parent != null) { - childParentData.offset = pixelFromLatLng( - _latitude, - _longitude, - markerParent.size, - markerParent.shapeLayerOffset, - markerParent.shapeLayerSizeFactor) - - Offset(size.width / 2, size.height / 2); - } - } - @override MouseCursor get cursor => SystemMouseCursors.basic; @override PointerEnterEventListener get onEnter => _handlePointerEnter; - // As onHover property of MouseHoverAnnotation was removed only in the - // beta channel, once it is moved to stable, will remove this property. - @override - // ignore: override_on_non_overriding_member - PointerHoverEventListener get onHover => null; - @override PointerExitEventListener get onExit => _handlePointerExit; @@ -988,19 +940,19 @@ class RenderMapMarker extends RenderProxyBox implements MouseTrackerAnnotation { if (event.down && event is PointerDownEvent) { _tapGestureRecognizer.addPointer(event); } else if (event is PointerHoverEvent) { - _handleInteraction(); + _handleInteraction(PointerKind.hover); } } @override void performLayout() { if (_markerSize != null) { - child?.layout(BoxConstraints.tight(_markerSize)); - size = _markerSize; + child?.layout(BoxConstraints.tight(_markerSize!)); + size = _markerSize!; } else { if (child != null) { - child.layout(constraints.loosen(), parentUsesSize: true); - size = child.size; + child!.layout(constraints.loosen(), parentUsesSize: true); + size = child!.size; } else { size = _defaultMarkerSize; } @@ -1019,7 +971,7 @@ class RenderMapMarker extends RenderProxyBox implements MouseTrackerAnnotation { strokeWidth: _iconStrokeWidth ?? _themeData.markerIconStrokeWidth, iconType: _iconType); } else { - context.paintChild(child, offset); + context.paintChild(child!, offset); } } } diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/shapes.dart b/packages/syncfusion_flutter_maps/lib/src/elements/shapes.dart deleted file mode 100644 index 96043794a..000000000 --- a/packages/syncfusion_flutter_maps/lib/src/elements/shapes.dart +++ /dev/null @@ -1,212 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; - -import '../enum.dart'; - -// ignore_for_file: public_member_api_docs -class ShapeLayerChildRenderBoxBase extends RenderProxyBox { - void onHover(MapModel item, MapLayerElement element) {} - - void paintTooltip(int elementIndex, Rect elementRect, MapLayerElement element, - [int sublayerIndex, Offset position]) {} - - void onExit() {} - - void refresh() {} - - void hideTooltip({bool immediately = false}) {} -} - -class MapModel { - MapModel({ - this.primaryKey, - this.pixelPoints, - this.rawPoints, - this.actualIndex, - this.dataIndex, - this.legendMapperIndex, - this.shapePath, - this.isSelected = false, - this.shapeColor, - this.dataLabelText, - this.visibleDataLabelText, - this.bubbleColor, - this.bubbleSizeValue, - this.bubbleRadius, - this.bubblePath, - this.tooltipText, - }); - - /// Contains [sourceDataPath] values. - String primaryKey; - - /// Contains data Source index. - int dataIndex; - - /// Specifies an item index in the data source list. - int actualIndex; - - /// Contains the index of the shape or bubble legend. - int legendMapperIndex; - - /// Contains pixel points. - List> pixelPoints; - - /// Contains coordinate points. - List> rawPoints; - - /// Option to select the particular shape - /// based on the position of the tap or click. - /// - /// Returns `true` when the shape is selected else it will be `false`. - /// - /// See also: - /// * [MapSelectionSettings] option to customize the shape selection. - bool isSelected = false; - - /// Contains data source Label text. - String dataLabelText; - - /// Use this text while zooming. - String visibleDataLabelText; - - /// Contains the tooltip text. - String tooltipText; - - /// Contains data source color. - Color shapeColor; - - /// Contains the actual shape color value. - dynamic shapeColorValue; - - /// Contains the shape path. - Path shapePath; - - // Center of shape path which is used to position the data labels. - Offset shapePathCenter; - - // Width for smart position the data labels. - double shapeWidth; - - /// Contains data source bubble color. - Color bubbleColor; - - /// Contains data source bubble size. - double bubbleSizeValue; - - /// Contains data source bubble radius. - double bubbleRadius; - - /// Contains the bubble path. - Path bubblePath; - - void reset() { - dataIndex = null; - isSelected = false; - dataLabelText = null; - visibleDataLabelText = null; - tooltipText = null; - shapeColor = null; - shapeColorValue = null; - shapePath = null; - shapePathCenter = null; - shapeWidth = null; - bubbleColor = null; - bubbleSizeValue = null; - bubbleRadius = null; - bubblePath = null; - } -} - -class MapIconShape { - const MapIconShape(); - - /// Returns the size based on the value passed to it. - Size getPreferredSize(Size iconSize, SfMapsThemeData themeData) { - return iconSize; - } - - /// Paints the shapes based on the value passed to it. - void paint( - PaintingContext context, - Offset offset, { - RenderBox parentBox, - SfMapsThemeData themeData, - Size iconSize, - Color color, - Color strokeColor, - double strokeWidth, - MapIconType iconType, - }) { - iconSize = getPreferredSize(iconSize, themeData); - final double halfIconWidth = iconSize.width / 2; - final double halfIconHeight = iconSize.height / 2; - final bool hasStroke = strokeWidth != null && - strokeWidth > 0 && - strokeColor != null && - strokeColor != Colors.transparent; - final Paint paint = Paint() - ..isAntiAlias = true - ..color = color; - Path path; - - switch (iconType) { - case MapIconType.circle: - final Rect rect = Rect.fromLTWH( - offset.dx, offset.dy, iconSize.width, iconSize.height); - context.canvas.drawOval(rect, paint); - if (hasStroke) { - paint - ..strokeWidth = strokeWidth - ..color = strokeColor - ..style = PaintingStyle.stroke; - context.canvas.drawOval(rect, paint); - } - break; - case MapIconType.rectangle: - final Rect rect = Rect.fromLTWH( - offset.dx, offset.dy, iconSize.width, iconSize.height); - context.canvas.drawRect(rect, paint); - if (hasStroke) { - paint - ..strokeWidth = strokeWidth - ..color = strokeColor - ..style = PaintingStyle.stroke; - context.canvas.drawRect(rect, paint); - } - break; - case MapIconType.triangle: - path = Path() - ..moveTo(offset.dx + halfIconWidth, offset.dy) - ..lineTo(offset.dx + iconSize.width, offset.dy + iconSize.height) - ..lineTo(offset.dx, offset.dy + iconSize.height) - ..close(); - context.canvas.drawPath(path, paint); - if (hasStroke) { - paint - ..strokeWidth = strokeWidth - ..color = strokeColor - ..style = PaintingStyle.stroke; - context.canvas.drawPath(path, paint); - } - break; - case MapIconType.diamond: - path = Path() - ..moveTo(offset.dx + halfIconWidth, offset.dy) - ..lineTo(offset.dx + iconSize.width, offset.dy + halfIconHeight) - ..lineTo(offset.dx + halfIconWidth, offset.dy + iconSize.height) - ..lineTo(offset.dx, offset.dy + halfIconHeight) - ..close(); - context.canvas.drawPath(path, paint); - if (hasStroke) { - paint - ..strokeWidth = strokeWidth - ..color = strokeColor - ..style = PaintingStyle.stroke; - context.canvas.drawPath(path, paint); - } - break; - } - } -} diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart b/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart index a13d66aed..daa50ea22 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart @@ -5,24 +5,23 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import '../behavior/zoom_pan_behavior.dart'; -import '../controller/default_controller.dart'; +import '../controller/map_controller.dart'; import '../enum.dart'; import '../settings.dart'; -import '../utils.dart'; // ignore_for_file: public_member_api_docs enum _ToolbarIcon { zoomIn, zoomOut, reset } class MapToolbar extends StatelessWidget { const MapToolbar({ - this.onWillZoom, - this.zoomPanBehavior, - this.controller, + required this.onWillZoom, + required this.zoomPanBehavior, + required this.controller, }); - final WillZoomCallback onWillZoom; + final WillZoomCallback? onWillZoom; final MapZoomPanBehavior zoomPanBehavior; - final MapController controller; + final MapController? controller; @override Widget build(BuildContext context) { @@ -81,19 +80,19 @@ class MapToolbar extends StatelessWidget { class _ToolbarItem extends StatefulWidget { const _ToolbarItem({ - this.controller, - this.zoomPanBehavior, - this.onWillZoom, - this.iconData, - this.icon, - this.tooltipText, + required this.controller, + required this.zoomPanBehavior, + required this.onWillZoom, + required this.iconData, + required this.icon, + required this.tooltipText, }); - final MapController controller; + final MapController? controller; final MapZoomPanBehavior zoomPanBehavior; - final WillZoomCallback onWillZoom; + final WillZoomCallback? onWillZoom; final IconData iconData; @@ -155,7 +154,7 @@ class _ToolbarItemState extends State<_ToolbarItem> { @override void initState() { if (widget.controller != null) { - widget.controller + widget.controller! ..addZoomingListener(_handleZooming) ..addResetListener(_handleReset) ..addRefreshListener(_handleRefresh); @@ -167,7 +166,7 @@ class _ToolbarItemState extends State<_ToolbarItem> { @override void dispose() { if (widget.controller != null) { - widget.controller + widget.controller! ..removeZoomingListener(_handleZooming) ..removeResetListener(_handleReset) ..removeRefreshListener(_handleRefresh); @@ -228,9 +227,9 @@ class _ToolbarItemState extends State<_ToolbarItem> { : const Color.fromRGBO(255, 255, 255, 0.24), icon: Icon(widget.iconData), color: widget.zoomPanBehavior.toolbarSettings.iconColor ?? - _isLightTheme - ? const Color.fromRGBO(0, 0, 0, 0.54) - : const Color.fromRGBO(255, 255, 255, 0.54), + (_isLightTheme + ? const Color.fromRGBO(0, 0, 0, 0.54) + : const Color.fromRGBO(255, 255, 255, 0.54)), onPressed: _enabled ? () { _handlePointerUp(); @@ -247,13 +246,14 @@ class _ToolbarItemState extends State<_ToolbarItem> { Color _getIconBackgroundColor() { return _isHovered - ? widget.zoomPanBehavior.toolbarSettings.itemHoverColor ?? _isLightTheme - ? const Color.fromRGBO(0, 0, 0, 0.08) - : const Color.fromRGBO(255, 255, 255, 0.12) + ? widget.zoomPanBehavior.toolbarSettings.itemHoverColor ?? + (_isLightTheme + ? const Color.fromRGBO(0, 0, 0, 0.08) + : const Color.fromRGBO(255, 255, 255, 0.12)) : widget.zoomPanBehavior.toolbarSettings.itemBackgroundColor ?? - _isLightTheme - ? const Color.fromRGBO(250, 250, 250, 1) - : const Color.fromRGBO(66, 66, 66, 1); + (_isLightTheme + ? const Color.fromRGBO(250, 250, 250, 1) + : const Color.fromRGBO(66, 66, 66, 1)); } void _handlePointerUp() { @@ -270,16 +270,14 @@ class _ToolbarItemState extends State<_ToolbarItem> { break; } - newZoomLevel = interpolateValue( - newZoomLevel, - widget.zoomPanBehavior.minZoomLevel, + newZoomLevel = newZoomLevel.clamp(widget.zoomPanBehavior.minZoomLevel, widget.zoomPanBehavior.maxZoomLevel); final MapZoomDetails details = MapZoomDetails( previousZoomLevel: widget.zoomPanBehavior.zoomLevel, newZoomLevel: newZoomLevel, ); - if (widget.onWillZoom == null || widget.onWillZoom(details)) { - widget.zoomPanBehavior?.onZooming(details); + if (widget.onWillZoom == null || widget.onWillZoom!(details)) { + widget.zoomPanBehavior.onZooming(details); } } } diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart b/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart index ab20bd51f..1032c490c 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart @@ -9,36 +9,35 @@ import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_maps/maps.dart'; import '../behavior/zoom_pan_behavior.dart'; -import '../controller/default_controller.dart'; -import '../enum.dart'; +import '../common.dart'; +import '../controller/map_controller.dart'; import '../layer/layer_base.dart'; import '../layer/shape_layer.dart'; import '../settings.dart'; import '../utils.dart'; -import 'shapes.dart'; // ignore_for_file: public_member_api_docs class MapTooltip extends StatefulWidget { const MapTooltip({ - Key key, + Key? key, this.mapSource, - this.sublayers, + required this.sublayers, this.shapeTooltipBuilder, this.bubbleTooltipBuilder, - this.markerTooltipBuilder, - this.tooltipSettings, - this.themeData, - this.controller, + required this.markerTooltipBuilder, + required this.tooltipSettings, + required this.themeData, + required this.controller, }) : super(key: key); - final MapShapeSource mapSource; - final List sublayers; - final IndexedWidgetBuilder shapeTooltipBuilder; - final IndexedWidgetBuilder bubbleTooltipBuilder; - final IndexedWidgetBuilder markerTooltipBuilder; + final MapShapeSource? mapSource; + final List? sublayers; + final IndexedWidgetBuilder? shapeTooltipBuilder; + final IndexedWidgetBuilder? bubbleTooltipBuilder; + final IndexedWidgetBuilder? markerTooltipBuilder; final MapTooltipSettings tooltipSettings; final SfMapsThemeData themeData; - final MapController controller; + final MapController? controller; @override _MapTooltipState createState() => _MapTooltipState(); @@ -46,48 +45,66 @@ class MapTooltip extends StatefulWidget { class _MapTooltipState extends State with SingleTickerProviderStateMixin { - AnimationController controller; - Widget _child; - bool isDesktop; + late AnimationController controller; + late bool isDesktop; - void adoptChild(int index, MapLayerElement element, {int sublayerIndex}) { + Widget? _child; + + void adoptChild(int index, MapLayerElement element, {int? sublayerIndex}) { + late Widget? current; switch (element) { case MapLayerElement.shape: setState(() { if (sublayerIndex != null) { - final MapShapeSublayer sublayer = widget.sublayers[sublayerIndex]; - _child = sublayer.shapeTooltipBuilder?.call(context, index); + final MapShapeSublayer sublayer = + // ignore: avoid_as + widget.sublayers![sublayerIndex] as MapShapeSublayer; + current = sublayer.shapeTooltipBuilder?.call(context, index); } else { - _child = widget.shapeTooltipBuilder?.call(context, index); + current = widget.shapeTooltipBuilder?.call(context, index); } + + _child = IgnorePointer(child: current); }); break; case MapLayerElement.bubble: setState(() { if (sublayerIndex != null) { - final MapShapeSublayer sublayer = widget.sublayers[sublayerIndex]; - _child = sublayer.bubbleTooltipBuilder?.call(context, index); + final MapShapeSublayer sublayer = + // ignore: avoid_as + widget.sublayers![sublayerIndex] as MapShapeSublayer; + current = sublayer.bubbleTooltipBuilder?.call(context, index); } else { - _child = widget.bubbleTooltipBuilder?.call(context, index); + current = widget.bubbleTooltipBuilder?.call(context, index); } + + _child = IgnorePointer(child: current); }); break; case MapLayerElement.marker: setState(() { if (sublayerIndex != null) { - final MapShapeSublayer sublayer = widget.sublayers[sublayerIndex]; - _child = sublayer.markerTooltipBuilder?.call(context, index); + final MapShapeSublayer sublayer = + // ignore: avoid_as + widget.sublayers![sublayerIndex] as MapShapeSublayer; + current = sublayer.markerTooltipBuilder?.call(context, index); } else { - _child = widget.markerTooltipBuilder?.call(context, index); + current = widget.markerTooltipBuilder?.call(context, index); } + + _child = IgnorePointer(child: current); }); break; case MapLayerElement.vector: setState(() { if (sublayerIndex != null) { - final MapSublayer sublayer = widget.sublayers[sublayerIndex]; - _child = sublayer.tooltipBuilder?.call(context, index); + final MapSublayer sublayer = + // ignore: avoid_as + widget.sublayers![sublayerIndex]; + current = sublayer.tooltipBuilder?.call(context, index); } + + _child = IgnorePointer(child: current); }); break; } @@ -102,7 +119,7 @@ class _MapTooltipState extends State @override void dispose() { - controller?.dispose(); + controller.dispose(); super.dispose(); } @@ -111,13 +128,14 @@ class _MapTooltipState extends State final ThemeData themeData = Theme.of(context); isDesktop = kIsWeb || themeData.platform == TargetPlatform.macOS || - themeData.platform == TargetPlatform.windows; + themeData.platform == TargetPlatform.windows || + themeData.platform == TargetPlatform.linux; return _MapTooltipRenderObjectWidget( - child: _child, source: widget.mapSource, tooltipSettings: widget.tooltipSettings, themeData: widget.themeData, state: this, + child: _child, ); } } @@ -125,14 +143,14 @@ class _MapTooltipState extends State /// A Render object widget which draws tooltip shape. class _MapTooltipRenderObjectWidget extends SingleChildRenderObjectWidget { const _MapTooltipRenderObjectWidget({ - Widget child, - this.source, - this.tooltipSettings, - this.themeData, - this.state, + required this.source, + required this.tooltipSettings, + required this.themeData, + required this.state, + Widget? child, }) : super(child: child); - final MapShapeSource source; + final MapShapeSource? source; final MapTooltipSettings tooltipSettings; final SfMapsThemeData themeData; final _MapTooltipState state; @@ -163,13 +181,13 @@ class _MapTooltipRenderObjectWidget extends SingleChildRenderObjectWidget { class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { _RenderMapTooltip({ - MapShapeSource source, - MapTooltipSettings tooltipSettings, - SfMapsThemeData themeData, - MediaQueryData mediaQueryData, - BuildContext context, - _MapTooltipState state, - }) : _source = source, + required MapShapeSource? source, + required MapTooltipSettings tooltipSettings, + required SfMapsThemeData themeData, + required MediaQueryData mediaQueryData, + required BuildContext context, + required _MapTooltipState state, + }) : _source = source, _tooltipSettings = tooltipSettings, _themeData = themeData, _mediaQueryData = mediaQueryData, @@ -184,14 +202,15 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { final _TooltipShape _tooltipShape = const _TooltipShape(); final Duration _waitDuration = const Duration(seconds: 3); final Duration _hideDeferDuration = const Duration(milliseconds: 500); - Animation _scaleAnimation; - Timer _showTimer; - Timer _hideDeferTimer; - Offset _currentPosition; - MapLayerElement _previousElement; - int _previousSublayerIndex; - int _previousElementIndex; - Rect _elementRect; + late Animation _scaleAnimation; + Timer? _showTimer; + Timer? _hideDeferTimer; + Offset? _currentPosition; + MapLayerElement? _previousElement; + int? _previousSublayerIndex; + int? _previousElementIndex; + Rect? _elementRect; + PointerKind? _pointerKind; bool _preferTooltipOnTop = true; bool _shouldCalculateTooltipPosition = false; @@ -206,9 +225,9 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { _tooltipSettings = value; } - MapShapeSource get source => _source; - MapShapeSource _source; - set source(MapShapeSource value) { + MapShapeSource? get source => _source; + MapShapeSource? _source; + set source(MapShapeSource? value) { if (_source == value) { return; } @@ -236,15 +255,15 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { hideTooltip(immediately: true); } - void _update( - Offset position, int index, MapLayerElement element, int sublayerIndex) { + void _update(Offset? position, int? index, MapLayerElement? element, + int? sublayerIndex) { if (index != null) { - _state.adoptChild(index, element, sublayerIndex: sublayerIndex); + _state.adoptChild(index, element!, sublayerIndex: sublayerIndex); _currentPosition = position; - if (child != null && child.attached) { + if (child != null && child!.attached) { _showTooltip(); } - } else if (child != null && child.attached) { + } else if (child != null && child!.attached) { _hideDeferTimer?.cancel(); _hideDeferTimer = Timer(_hideDeferDuration, hideTooltip); } @@ -257,12 +276,21 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { if (_state.controller.value == 0 || _state.controller.status == AnimationStatus.reverse) { _state.controller.forward(from: 0.0); + _startShowTimer(); return; } + + _startShowTimer(); markNeedsPaint(); } else { _state.controller.forward(from: 0.0); - _showTimer?.cancel(); + _startShowTimer(); + } + } + + void _startShowTimer() { + _showTimer?.cancel(); + if (_pointerKind == PointerKind.touch) { _showTimer = Timer(_waitDuration, hideTooltip); } } @@ -272,22 +300,23 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { _previousElement = null; _previousElementIndex = null; _previousSublayerIndex = null; + _pointerKind = null; _showTimer?.cancel(); immediately ? _state.controller.reset() : _state.controller.reverse(); } - void _updateTooltipIfNeeded(Offset position, int index, Rect elementRect, - MapLayerElement element, int sublayerIndex) { + void _updateTooltipIfNeeded(Offset? position, int? index, Rect? elementRect, + MapLayerElement? element, int? sublayerIndex) { if (sublayerIndex == _previousSublayerIndex && element != MapLayerElement.shape && element != MapLayerElement.vector && _previousElement == element) { if (_previousElementIndex == index) { - if (_state.isDesktop) { + if (_state.isDesktop && _pointerKind == PointerKind.hover) { return; } - _showTimer?.cancel(); - _showTimer = Timer(_waitDuration, hideTooltip); + + _startShowTimer(); return; } } @@ -313,8 +342,10 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { } @override - void paintTooltip(int elementIndex, Rect elementRect, MapLayerElement element, - [int sublayerIndex, Offset position]) { + void paintTooltip(int? elementIndex, Rect? elementRect, + MapLayerElement? element, PointerKind kind, + [int? sublayerIndex, Offset? position]) { + _pointerKind = kind; _updateTooltipIfNeeded( position, elementIndex, elementRect, element, sublayerIndex); } @@ -345,7 +376,7 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { super.attach(owner); _scaleAnimation.addListener(markNeedsPaint); if (_state.widget.controller != null) { - _state.widget.controller + _state.widget.controller! ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) ..addResetListener(_handleReset); @@ -358,7 +389,7 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { _hideDeferTimer?.cancel(); _scaleAnimation.removeListener(markNeedsPaint); if (_state.widget.controller != null) { - _state.widget.controller + _state.widget.controller! ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) ..removeResetListener(_handleReset); @@ -369,7 +400,8 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { @override void paint(PaintingContext context, Offset offset) { if (child != null && - child.attached && + child!.attached && + (child!.size.width > 0 || child!.size.height > 0) && (_currentPosition != null || _elementRect != null)) { // We are calculating the tooltip position in paint method because of the // child didn't get layouts while forwarding the animation controller. @@ -381,7 +413,7 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { _tooltipShape.paint( context, offset, - _currentPosition, + _currentPosition!, _preferTooltipOnTop, this, _scaleAnimation, @@ -391,51 +423,51 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { } void _updateTooltipPositionAndDirection() { - final double tooltipHeight = child.size.height; + final double tooltipHeight = child!.size.height; final Rect bounds = Offset.zero & size; // For bubble and marker. if (_elementRect != null) { // Checking tooltip element rect lies inside tooltip render box bounds. // If not, we are creating a new rect based on the visible area. - if (bounds.overlaps(_elementRect)) { + if (bounds.overlaps(_elementRect!)) { _updateElementRect(bounds); } - final double halfRectWidth = _elementRect.width / 2; - Offset tooltipPosition = _elementRect.topLeft + - Offset(halfRectWidth, _elementRect.height * tooltipHeightFactor); + final double halfRectWidth = _elementRect!.width / 2; + Offset tooltipPosition = _elementRect!.topLeft + + Offset(halfRectWidth, _elementRect!.height * tooltipHeightFactor); _preferTooltipOnTop = bounds.contains( tooltipPosition - Offset(0.0, tooltipHeight + tooltipTriangleHeight)); if (!_preferTooltipOnTop) { // To get the tooltip position at bottom based on the current rect, // we had subtracted 1 with the tooltipHeightFactor. - tooltipPosition = _elementRect.topLeft + - Offset( - halfRectWidth, _elementRect.height * (1 - tooltipHeightFactor)); + tooltipPosition = _elementRect!.topLeft + + Offset(halfRectWidth, + _elementRect!.height * (1 - tooltipHeightFactor)); } _currentPosition = tooltipPosition; } // For shape. else { - _preferTooltipOnTop = bounds.contains(_currentPosition - + _preferTooltipOnTop = bounds.contains(_currentPosition! - Offset(0.0, tooltipHeight + tooltipTriangleHeight)); } } void _updateElementRect(Rect bounds) { - double left = _elementRect.left; - double right = _elementRect.right; - double top = _elementRect.top; - double bottom = _elementRect.bottom; - if (_elementRect.left < bounds.left) { + double left = _elementRect!.left; + double right = _elementRect!.right; + double top = _elementRect!.top; + double bottom = _elementRect!.bottom; + if (_elementRect!.left < bounds.left) { left = bounds.left; } - if (_elementRect.right > bounds.right) { + if (_elementRect!.right > bounds.right) { right = bounds.right; } - if (_elementRect.top < bounds.top) { + if (_elementRect!.top < bounds.top) { top = bounds.top; } - if (_elementRect.bottom > bounds.bottom) { + if (_elementRect!.bottom > bounds.bottom) { bottom = bounds.bottom; } @@ -464,12 +496,10 @@ class _TooltipShape { const double elevation = 0.0; Path path = Path(); - final Paint paint = Paint() - ..style = PaintingStyle.fill - ..color = tooltipSettings.color ?? themeData.tooltipColor; - BorderRadius borderRadius = themeData.tooltipBorderRadius; - final double tooltipWidth = parentBox.child.size.width; - double tooltipHeight = parentBox.child.size.height; + BorderRadius borderRadius = + themeData.tooltipBorderRadius.resolve(TextDirection.ltr); + final double tooltipWidth = parentBox.child!.size.width; + double tooltipHeight = parentBox.child!.size.height; final double halfTooltipWidth = tooltipWidth / 2; double halfTooltipHeight = tooltipHeight / 2; @@ -541,21 +571,29 @@ class _TooltipShape { context.canvas .translate(center.dx, center.dy - triangleHeight - halfTooltipHeight); context.canvas.scale(tooltipAnimation.value); - context.canvas.drawPath(path, paint); - final Color strokeColor = + // In web HTML rendering, fill color clipped half of its tooltip's size. + // To avoid this issue we are drawing stroke before fill. + final Color? strokeColor = tooltipSettings.strokeColor ?? themeData.tooltipStrokeColor; - if (strokeColor != null && strokeColor != Colors.transparent) { - paint - ..color = strokeColor - ..strokeWidth = - tooltipSettings.strokeWidth ?? themeData.tooltipStrokeWidth - ..style = PaintingStyle.stroke; - context.canvas.drawPath(path, paint); - } + final Paint paint = Paint() + ..color = strokeColor ?? Colors.transparent + // We are drawing stroke before fill to avoid tooltip rendering issue + // in a web HTML rendering. Due to this, half of the stroke width only + // visible to us so that we are twice the stroke width. + ..strokeWidth = + (tooltipSettings.strokeWidth ?? themeData.tooltipStrokeWidth) * 2 + ..style = PaintingStyle.stroke; + // Drawing stroke. + context.canvas.drawPath(path, paint); + // Drawing fill color. + paint + ..style = PaintingStyle.fill + ..color = tooltipSettings.color ?? themeData.tooltipColor; + context.canvas.drawPath(path, paint); context.canvas.clipPath(path); - context.paintChild( - parentBox.child, offset - _getShiftPosition(offset, center, parentBox)); + context.paintChild(parentBox.child!, + offset - _getShiftPosition(offset, center, parentBox)); context.canvas.restore(); } @@ -656,7 +694,7 @@ class _TooltipShape { Offset _getShiftPosition( Offset offset, Offset center, RenderProxyBox parent) { - final Size childSize = parent.child.size; + final Size childSize = parent.child!.size; final double halfChildWidth = childSize.width / 2; final double halfChildHeight = childSize.height / 2; diff --git a/packages/syncfusion_flutter_maps/lib/src/enum.dart b/packages/syncfusion_flutter_maps/lib/src/enum.dart index f29064ce1..f9e8c1c43 100644 --- a/packages/syncfusion_flutter_maps/lib/src/enum.dart +++ b/packages/syncfusion_flutter_maps/lib/src/enum.dart @@ -94,21 +94,6 @@ enum MapLegendEdgeLabelsPlacement { center } -/// Specifies the layer for tooltip. -enum MapLayerElement { - /// Shows tooltip for shape layer. - shape, - - /// Shows tooltip for bubble. - bubble, - - /// Shows tooltip for marker. - marker, - - /// Shows tooltip for vector layers. - vector, -} - /// Applies gradient or solid color for the bar segments. enum MapLegendPaintingStyle { /// Applies solid color for bar segments. diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/layer_base.dart b/packages/syncfusion_flutter_maps/lib/src/layer/layer_base.dart index c8dfc90b7..029e11503 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/layer_base.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/layer_base.dart @@ -1,13 +1,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_maps/src/layer/vector_layers.dart'; import '../behavior/zoom_pan_behavior.dart'; -import '../controller/default_controller.dart'; import '../layer/shape_layer.dart'; +import '../layer/tile_layer.dart'; import '../settings.dart'; -import '../utils.dart'; /// Base class for the [MapShapeLayer] and [MapTileLayer]. /// @@ -16,12 +14,12 @@ import '../utils.dart'; abstract class MapLayer extends StatelessWidget { /// Creates a [MapLayer]. const MapLayer({ - Key key, + Key? key, this.sublayers, - this.initialMarkersCount, + this.initialMarkersCount = 0, this.markerBuilder, this.markerTooltipBuilder, - this.tooltipSettings, + this.tooltipSettings = const MapTooltipSettings(), this.zoomPanBehavior, this.onWillZoom, this.onWillPan, @@ -33,51 +31,76 @@ abstract class MapLayer extends StatelessWidget { /// It is applicable for both the [MapShapeLayer] and [MapTileLayer]. /// /// ```dart + /// MapShapeSource _mapSource; + /// int _selectedIndex; + /// List _data; + /// + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.asset( + /// 'assets/india.json', + /// shapeDataField: 'name', + /// ); + /// + /// _data = [ + /// DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(11.1271, 78.6569)), + /// DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(9.9312, 76.2673)), + /// DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(15.3173, 75.7139)), + /// DataModel(MapLatLng(28.7041, 77.1025), MapLatLng(18.1124, 79.0193)), + /// ]; + /// super.initState(); + /// } + /// /// @override /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Column( - /// children: [ - /// Container( + /// return Scaffold( + /// appBar: AppBar(title: Text('Line layer')), + /// body: Column( + /// children: [ + /// Container( /// child: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'continent', - /// ), - /// sublayers: [ + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ /// MapLineLayer( - /// lines: List.generate( - /// data.length, - /// (int index) { - /// return MapLine( - /// from: data[index].from, - /// to: data[index].to, - /// dashArray: [8, 3, 4, 2], + /// lines: List.generate( + /// _data.length, + /// (int index) { + /// return MapLine( + /// from: _data[index].from, + /// to: _data[index].to, + /// dashArray: [8, 3, 4, 2], /// color: _selectedIndex == index - /// ? Colors.red - /// : Colors.blue, - /// width: 2, - /// onTap: () { - /// setState(() { - /// _selectedIndex = index; - /// }); + /// ? Colors.red + /// : Colors.blue, + /// width: 2, + /// onTap: () { + /// setState(() { + /// _selectedIndex = index; + /// }); /// }); - /// }, - /// ).toSet(), - /// ), + /// }, + /// ).toSet(), + /// ), /// ], - /// ), - /// ], + /// ), + /// ], /// ), - /// ), - /// ], - /// ), - /// ); - /// } - ///``` - final List sublayers; + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class DataModel { + /// DataModel(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; + /// } + /// ``` + final List? sublayers; /// Option to set markers count initially. It cannot be be updated /// dynamically. @@ -107,11 +130,12 @@ abstract class MapLayer extends StatelessWidget { /// for child in [MapMarker] constructor. /// /// ```dart - /// List data; + /// List _data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { - /// data = const [ + /// _data = const [ /// Model('Brazil', -14.235004, -51.92528), /// Model('Germany', 51.16569, 10.451526), /// Model('Australia', -25.274398, 133.775136), @@ -119,6 +143,13 @@ abstract class MapLayer extends StatelessWidget { /// Model('Russia', 61.52401, 105.318756) /// ]; /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// dataCount: _data.length, + /// primaryValueMapper: (int index) => _data[index].country, + /// ); + /// /// super.initState(); /// } /// @@ -133,17 +164,12 @@ abstract class MapLayer extends StatelessWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'name', - /// dataCount: data.length, - /// primaryValueMapper: (index) => data[index].country, - /// ), + /// source: _mapSource, /// initialMarkersCount: 5, /// markerBuilder: (BuildContext context, int index){ /// return MapMarker( - /// latitude: data[index].latitude, - /// longitude: data[index].longitude, + /// latitude: _data[index].latitude, + /// longitude: _data[index].longitude, /// ); /// }, /// ), @@ -166,7 +192,7 @@ abstract class MapLayer extends StatelessWidget { /// See also: /// * [MapShapeLayerController], for dynamically updating the markers. /// * [MapMarker], to create a map marker. - final MapMarkerBuilder markerBuilder; + final MapMarkerBuilder? markerBuilder; /// Returns the widget for the tooltip of the [MapMarker]. /// @@ -179,6 +205,29 @@ abstract class MapLayer extends StatelessWidget { /// mouse enabled devices. /// /// ```dart + /// List _worldMapData; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _worldMapData = const [ + /// Model('Brazil', -14.235004, -51.92528), + /// Model('Germany', 51.16569, 10.451526), + /// Model('Australia', -25.274398, 133.775136), + /// Model('India', 20.593684, 78.96288), + /// Model('Russia', 61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// dataCount: _worldMapData.length, + /// primaryValueMapper: (int index) => _worldMapData[index].country, + /// ); + /// + /// super.initState(); + /// } + /// /// @override /// Widget build(BuildContext context) { /// return Scaffold( @@ -187,13 +236,14 @@ abstract class MapLayer extends StatelessWidget { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'continent', - /// dataCount: worldMapData.length, - /// primaryValueMapper: (int index) => - /// worldMapData[index].primaryKey, - /// ), + /// source: _mapSource, + /// initialMarkersCount: _worldMapData.length, + /// markerBuilder: (BuildContext context, int index){ + /// return MapMarker( + /// latitude: _worldMapData[index].latitude, + /// longitude: _worldMapData[index].longitude, + /// ); + /// }, /// markerTooltipBuilder: (BuildContext context, int index) { /// if(index == 0) { /// return Container( @@ -207,20 +257,27 @@ abstract class MapLayer extends StatelessWidget { /// ); /// } /// }, - /// /// ), /// ], /// ), /// ), /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.latitude, this.longitude); + /// + /// final String country; + /// final double latitude; + /// final double longitude; + /// } /// ``` /// See also: /// * [MapLayer.tooltipSettings], to customize the color and stroke of the /// tooltip. /// * [SfMapsThemeData.tooltipBorderRadius], to customize the corners of the /// tooltip. - final IndexedWidgetBuilder markerTooltipBuilder; + final IndexedWidgetBuilder? markerTooltipBuilder; /// Customizes the bubble, marker, and shape tooltip's appearance. /// @@ -276,10 +333,16 @@ abstract class MapLayer extends StatelessWidget { /// and [MapTileLayer]. /// /// ```dart + /// MapShapeSource _mapSource; /// MapZoomPanBehavior _zoomPanBehavior; /// /// @override /// void initState() { + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ); + /// /// _zoomPanBehavior = MapZoomPanBehavior() /// ..zoomLevel = 4 /// ..focalLatLng = MapLatLng(19.0759837, 72.8776559); @@ -295,10 +358,7 @@ abstract class MapLayer extends StatelessWidget { /// body: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'continent', - /// ), + /// source: _mapSource, /// zoomPanBehavior: _zoomPanBehavior, /// ), /// ], @@ -306,7 +366,7 @@ abstract class MapLayer extends StatelessWidget { /// ); /// } /// ``` - final MapZoomPanBehavior zoomPanBehavior; + final MapZoomPanBehavior? zoomPanBehavior; /// Called whenever zooming is happening. /// @@ -329,7 +389,7 @@ abstract class MapLayer extends StatelessWidget { /// pointers in contact with the screen. /// * [MapZoomDetails.localFocalPoint] - The local focal point of the /// pointers in contact with the screen. - final WillZoomCallback onWillZoom; + final WillZoomCallback? onWillZoom; /// Called whenever panning is happening. /// @@ -349,14 +409,14 @@ abstract class MapLayer extends StatelessWidget { /// pointers in contact with the screen. /// * [MapPanDetails.localFocalPoint] - The local focal point of the /// pointers in contact with the screen. - final WillPanCallback onWillPan; + final WillPanCallback? onWillPan; } /// Base class for all vector shapes like [MapLineLayer], [MapCircleLayer], /// [MapArcLayer], [MapPolylineLayer], and [MapPolygonLayer]. abstract class MapSublayer extends StatelessWidget { /// Creates a [MapSublayer]. - const MapSublayer({Key key, this.tooltipBuilder}) : super(key: key); + const MapSublayer({Key? key, this.tooltipBuilder}) : super(key: key); /// Returns a widget for the map line tooltip based on the index. /// @@ -368,100 +428,87 @@ abstract class MapSublayer extends StatelessWidget { /// user interacts with the shape. /// /// ```dart + /// MapShapeSource _mapSource; + /// int _selectedIndex; + /// List _data; /// - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'continent', - /// ), - /// sublayers: [ - /// MapLineLayer( - /// lines: List.generate( - /// lines.length, - /// (int index) { - /// return MapLine( - /// from: lines[index].from, - /// to: lines[index].to, - /// ); - /// }, - /// ).toSet(), - /// tooltipBuilder: (BuildContext context, int index) { - /// if (index == 0) { - /// return Container( - /// child: Icon(Icons.airplanemode_inactive), - /// ); - /// } - /// else { - /// return Container( - /// child: Icon(Icons.airplanemode_active), - /// ); - /// } - /// }, - /// ), - /// ], + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.asset( + /// 'assets/usa.json', + /// ); + /// + /// _data = [ + /// DataModel(MapLatLng(40.7128, -74.0060), + /// MapLatLng(44.9778, -93.2650)), + /// DataModel(MapLatLng(40.7128, -74.0060), + /// MapLatLng(33.4484, -112.0740)), + /// DataModel(MapLatLng(40.7128, -74.0060), + /// MapLatLng(29.7604, -95.3698)), + /// DataModel(MapLatLng(40.7128, -74.0060), + /// MapLatLng(39.7392, -104.9903)), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Line layer')), + /// body: Column( + /// children: [ + /// Container( + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _data.length, + /// (int index) { + /// return MapLine( + /// from: _data[index].from, + /// to: _data[index].to, + /// dashArray: [8, 3, 4, 2], + /// color: _selectedIndex == index + /// ? Colors.red + /// : Colors.blue, + /// width: 2, + /// onTap: () { + /// setState(() { + /// _selectedIndex = index; + /// }); + /// }); + /// }, + /// ).toSet(), + /// tooltipBuilder: (BuildContext context, int index) { + /// if (index == 0) { + /// return Container( + /// child: Icon(Icons.airplanemode_inactive), + /// ); + /// } else { + /// return Container( + /// child: Icon(Icons.airplanemode_active), + /// ); + /// } + /// }, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ), + /// ], /// ), - /// ], - /// ), - /// ); + /// ); + /// } + /// + /// class DataModel { + /// DataModel(this.from, this.to); + /// MapLatLng from; + /// MapLatLng to; /// } /// ``` - final IndexedWidgetBuilder tooltipBuilder; -} - -/// Adds [MapSublayer] into a stack widget. -class SublayerContainer extends Stack { - /// Creates a [SublayerContainer]. - SublayerContainer({ - List children, - this.controller, - this.tooltipKey, - }) : super(children: children ?? []); - - /// Holds the parent's map controller values. - final MapController controller; - - /// Key is used to get tooltip render box. - final GlobalKey tooltipKey; - - @override - RenderStack createRenderObject(BuildContext context) { - return RenderSublayerContainer( - controller: controller, tooltipKey: tooltipKey, context: context); - } -} - -/// Adds [MapSublayer] into a stack widget. -class RenderSublayerContainer extends RenderStack { - /// Creates a [RenderSublayerContainer]. - RenderSublayerContainer({ - this.controller, - this.tooltipKey, - this.context, - }) : super(textDirection: Directionality.of(context)); - - /// Used to connect all sub elements into the parent layer. - final MapController controller; - - /// Key is used to get tooltip render box. - final GlobalKey tooltipKey; - - /// The build context. - final BuildContext context; - - /// Returns the sublayer index. - int getSublayerIndex(MapSublayer sublayer) { - final SublayerContainer sublayerContainer = context.widget; - return sublayerContainer.children.indexOf(sublayer); - } - - @override - void performLayout() { - size = getBoxSize(constraints); - super.performLayout(); - } + final IndexedWidgetBuilder? tooltipBuilder; } diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart b/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart index 4ad930d35..e6e5ed34d 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart @@ -8,21 +8,21 @@ import 'package:collection/collection.dart' show MapEquality; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/physics.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart' show SchedulerBinding; -import 'package:flutter/services.dart' show rootBundle; -import 'package:http/http.dart' as http; import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_maps/maps.dart'; import '../behavior/zoom_pan_behavior.dart'; -import '../controller/default_controller.dart'; +import '../common.dart'; +import '../controller/map_controller.dart'; +import '../controller/map_provider.dart'; import '../controller/shape_layer_controller.dart'; import '../elements/bubble.dart'; import '../elements/data_label.dart'; import '../elements/legend.dart'; import '../elements/marker.dart'; -import '../elements/shapes.dart'; import '../elements/toolbar.dart'; import '../elements/tooltip.dart'; import '../enum.dart'; @@ -30,6 +30,190 @@ import '../layer/layer_base.dart'; import '../layer/vector_layers.dart'; import '../settings.dart'; import '../utils.dart'; +import 'tile_layer.dart'; + +class _ShapeBounds { + _ShapeBounds( + {this.minLongitude, + this.minLatitude, + this.maxLongitude, + this.maxLatitude}); + + num? minLongitude; + + num? minLatitude; + + num? maxLongitude; + + num? maxLatitude; + + _ShapeBounds get empty => _ShapeBounds( + minLongitude: null, + minLatitude: null, + maxLongitude: null, + maxLatitude: null); +} + +class _ShapeFileData { + Map? decodedJsonData; + + late Map mapDataSource; + + late _ShapeBounds bounds; + + late MapModel initialSelectedModel; + + void reset() { + decodedJsonData?.clear(); + mapDataSource.clear(); + bounds = bounds.empty; + } +} + +Future<_ShapeFileData> _retrieveDataFromShapeFile( + MapProvider provider, + String? shapeDataField, + _ShapeFileData shapeFileData, + bool isShapeFileDecoded, + bool isSublayer) async { + if (isShapeFileDecoded) { + return shapeFileData; + } + final String assertBundleData = await provider.loadString(); + final Map data = { + 'AssertBundleData': assertBundleData, + 'ShapeDataField': shapeDataField, + 'ShapeFileData': shapeFileData, + 'IsSublayer': isSublayer + }; + return compute(_decodeJsonData, data); +} + +_ShapeFileData _decodeJsonData(Map data) { + data['ShapeFileData'].decodedJsonData = jsonDecode(data['AssertBundleData']); + _readJsonFile(data); + return data['ShapeFileData']; +} + +void _readJsonFile(Map data) { + List polygonGeometryData; + int multipolygonGeometryLength; + late Map geometry; + Map? properties; + + final _ShapeFileData shapeFileData = data['ShapeFileData']; + final String? shapeDataField = data['ShapeDataField']; + final bool isSublayer = data['IsSublayer']; + final bool hasFeatures = + shapeFileData.decodedJsonData!.containsKey('features'); + final bool hasGeometries = + shapeFileData.decodedJsonData!.containsKey('geometries'); + final String? key = hasFeatures + ? 'features' + : hasGeometries + ? 'geometries' + : null; + final int jsonLength = key == null || key.isEmpty + ? 0 + : shapeFileData.decodedJsonData![key].length; + if (isSublayer) { + shapeFileData.bounds = _ShapeBounds( + minLatitude: minimumLatitude, + maxLatitude: maximumLatitude, + minLongitude: minimumLongitude, + maxLongitude: maximumLongitude); + } + + for (int i = 0; i < jsonLength; i++) { + if (hasFeatures) { + final dynamic features = shapeFileData.decodedJsonData![key][i]; + geometry = features['geometry']; + properties = features['properties']; + } else if (hasGeometries) { + geometry = shapeFileData.decodedJsonData![key][i]; + } + + if (geometry['type'] == 'Polygon') { + polygonGeometryData = geometry['coordinates'][0]; + _updateMapDataSource(shapeFileData, shapeDataField, properties, + polygonGeometryData, isSublayer); + } else { + multipolygonGeometryLength = geometry['coordinates'].length; + for (int j = 0; j < multipolygonGeometryLength; j++) { + polygonGeometryData = geometry['coordinates'][j][0]; + _updateMapDataSource(shapeFileData, shapeDataField, properties, + polygonGeometryData, isSublayer); + } + } + } +} + +void _updateMapDataSource(_ShapeFileData shapeFileData, String? shapeDataField, + Map? properties, List points, bool isSublayer) { + final String dataPath = + properties != null ? (properties[shapeDataField] ?? '') : ''; + shapeFileData.mapDataSource.update( + dataPath, + (MapModel model) { + model.rawPoints.add(points); + return model; + }, + ifAbsent: () { + final int dataSourceIndex = shapeFileData.mapDataSource.length; + return MapModel( + primaryKey: dataPath, + actualIndex: dataSourceIndex, + legendMapperIndex: dataSourceIndex, + rawPoints: >[points], + ); + }, + ); + if (!isSublayer) { + _updateShapeBounds(shapeFileData, points); + } +} + +void _updateShapeBounds( + _ShapeFileData shapeFileData, List coordinates) { + List data; + num longitude, latitude; + final int length = coordinates.length; + for (int i = 0; i < length; i++) { + data = coordinates[i]; + longitude = data[0]; + latitude = data[1]; + if (shapeFileData.bounds.minLongitude == null) { + shapeFileData.bounds.minLongitude = longitude; + shapeFileData.bounds.minLatitude = latitude; + shapeFileData.bounds.maxLongitude = longitude; + shapeFileData.bounds.maxLatitude = latitude; + } else { + shapeFileData.bounds.minLongitude = + min(longitude, shapeFileData.bounds.minLongitude!); + shapeFileData.bounds.minLatitude = + min(latitude, shapeFileData.bounds.minLatitude!); + shapeFileData.bounds.maxLongitude = + max(longitude, shapeFileData.bounds.maxLongitude!); + shapeFileData.bounds.maxLatitude = + max(latitude, shapeFileData.bounds.maxLatitude!); + } + } +} + +MapProvider _sourceProvider( + Object geoJsonSource, _MapSourceType geoJSONSourceType) { + switch (geoJSONSourceType) { + case _MapSourceType.asset: + return AssetMapProvider(geoJsonSource.toString()); + case _MapSourceType.network: + return NetworkMapProvider(geoJsonSource.toString()); + case _MapSourceType.memory: + // ignore: avoid_as + return MemoryMapProvider(geoJsonSource as Uint8List); + } +} + +enum _MapSourceType { asset, network, memory } /// The source that maps the data source with the shape file and provides /// data for the elements of the shape layer like data labels, bubbles, tooltip, @@ -59,26 +243,51 @@ import '../utils.dart'; /// [MapShapeSource.shapeColorMappers], etc. /// /// ```dart -/// @override +/// MapShapeSource _mapSource; +/// List _data; +/// +/// @override +/// void initState() { +/// +/// _data = [ +/// Model('India', 280, "Low"), +/// Model('United States of America', 190, "High"), +/// Model('Pakistan', 37, "Low"), +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// 'assets/world_map.json', +/// shapeDataField: 'name', +/// dataCount: _data.length, +/// primaryValueMapper: (int index) { +/// return _data[index].country; +/// }, +/// dataLabelMapper: (int index) { +/// return _data[index].country; +/// }, +/// ); +/// } +/// +/// @override /// Widget build(BuildContext context) { /// return /// SfMaps( /// layers: [ /// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "name", -/// dataCount: data.length, -/// primaryValueMapper: (index) { -/// return data[index].country; -/// }, -/// dataLabelMapper: (index) { -/// return data[index].countryCode; -/// }), -/// ) +/// source: _mapSource, +/// showDataLabels: true, +/// ) /// ], /// ); /// } +/// +/// class Model { +/// const Model(this.country, this.count, this.storage); +/// +/// final String country; +/// final double count; +/// final String storage; +/// } /// ``` /// See also: /// * [MapShapeSource.primaryValueMapper], to map the data of the data @@ -96,6 +305,16 @@ class MapShapeSource extends DiagnosticableTree { /// Creates a layer using the .json file from an asset bundle. /// /// ```dart + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.asset( + /// 'assets/Ireland.json', + /// shapeDataField: 'name', + /// ); + /// } + /// /// @override /// Widget build(BuildContext context) { /// return Scaffold( @@ -107,10 +326,7 @@ class MapShapeSource extends DiagnosticableTree { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/Ireland.json', - /// shapeDataField: 'name', - /// ), + /// source: _mapSource, /// showDataLabels: true, /// color: Colors.orange[100], /// ), @@ -120,10 +336,10 @@ class MapShapeSource extends DiagnosticableTree { /// ); /// } /// ``` - MapShapeSource.asset( + const MapShapeSource.asset( String name, { this.shapeDataField, - this.dataCount, + this.dataCount = 0, this.primaryValueMapper, this.shapeColorMappers, this.bubbleColorMappers, @@ -131,23 +347,33 @@ class MapShapeSource extends DiagnosticableTree { this.bubbleSizeMapper, this.shapeColorValueMapper, this.bubbleColorValueMapper, - }) : _mapProvider = AssetMapProvider(name), - assert(name != null), - assert(dataCount == null || dataCount > 0), - assert(primaryValueMapper == null || - (primaryValueMapper != null && dataCount != null && dataCount > 0)), - assert(shapeColorMappers == null || - (shapeColorMappers != null && + }) : _geoJSONSource = name, + _geoJSONSourceType = _MapSourceType.asset, + assert((primaryValueMapper != null && dataCount > 0) || + primaryValueMapper == null), + assert((shapeColorMappers != null && primaryValueMapper != null && - shapeColorValueMapper != null)), - assert(bubbleColorMappers == null || - (bubbleColorMappers != null && + shapeColorValueMapper != null) || + shapeColorMappers == null), + assert((bubbleColorMappers != null && primaryValueMapper != null && - bubbleColorValueMapper != null)); + bubbleColorValueMapper != null) || + bubbleColorMappers == null); /// Creates a layer using the .json file from the network. /// /// ```dart + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.network( + /// 'http://www.json-generator.com/api/json/get/bVqXoJvfjC?indent=2', + /// ); + /// + /// super.initState(); + /// } + /// /// @override /// Widget build(BuildContext context) { /// return Scaffold( @@ -157,19 +383,17 @@ class MapShapeSource extends DiagnosticableTree { /// body: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.network( - /// 'http://www.json-generator.com/api/json/get/bVqXoJvfjC?indent=2', - /// ), + /// source: _mapSource, /// ), /// ], /// ), /// ); /// } /// ``` - MapShapeSource.network( + const MapShapeSource.network( String src, { this.shapeDataField, - this.dataCount, + this.dataCount = 0, this.primaryValueMapper, this.shapeColorMappers, this.bubbleColorMappers, @@ -177,23 +401,32 @@ class MapShapeSource extends DiagnosticableTree { this.bubbleSizeMapper, this.shapeColorValueMapper, this.bubbleColorValueMapper, - }) : _mapProvider = NetworkMapProvider(src), - assert(src != null), - assert(dataCount == null || dataCount > 0), - assert(primaryValueMapper == null || - (primaryValueMapper != null && dataCount != null && dataCount > 0)), - assert(shapeColorMappers == null || - (shapeColorMappers != null && + }) : _geoJSONSource = src, + _geoJSONSourceType = _MapSourceType.network, + assert((primaryValueMapper != null && dataCount > 0) || + primaryValueMapper == null), + assert((shapeColorMappers != null && primaryValueMapper != null && - shapeColorValueMapper != null)), - assert(bubbleColorMappers == null || - (bubbleColorMappers != null && + shapeColorValueMapper != null) || + shapeColorMappers == null), + assert((bubbleColorMappers != null && primaryValueMapper != null && - bubbleColorValueMapper != null)); + bubbleColorValueMapper != null) || + bubbleColorMappers == null); /// Creates a layer using the GeoJSON source as bytes from [Uint8List]. /// /// ```dart + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.memory( + /// bytes, + /// shapeDataField: 'name' + /// ); + /// } + /// /// @override /// Widget build(BuildContext context) { /// return Scaffold( @@ -204,14 +437,7 @@ class MapShapeSource extends DiagnosticableTree { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.memory( - /// bytes, - /// shapeDataField: 'name', - /// dataCount: 6, - /// primaryValueMapper: (index) => dataSource[index].key, - /// dataLabelMapper: (index) => dataSource[index].dataLabel, - /// shapeColorValueMapper: (index) => dataSource[index].color, - /// ), + /// source: _mapSource, /// showDataLabels: true, /// color: Colors.orange[100], /// ), @@ -220,10 +446,10 @@ class MapShapeSource extends DiagnosticableTree { /// ); /// } /// ``` - MapShapeSource.memory( + const MapShapeSource.memory( Uint8List bytes, { this.shapeDataField, - this.dataCount, + this.dataCount = 0, this.primaryValueMapper, this.shapeColorMappers, this.bubbleColorMappers, @@ -231,26 +457,25 @@ class MapShapeSource extends DiagnosticableTree { this.bubbleSizeMapper, this.shapeColorValueMapper, this.bubbleColorValueMapper, - }) : _mapProvider = MemoryMapProvider(bytes), - assert(bytes != null), - assert(dataCount == null || dataCount > 0), - assert(primaryValueMapper == null || - (primaryValueMapper != null && dataCount != null && dataCount > 0)), - assert(shapeColorMappers == null || - (shapeColorMappers != null && + }) : _geoJSONSource = bytes, + _geoJSONSourceType = _MapSourceType.memory, + assert((primaryValueMapper != null && dataCount > 0) || + primaryValueMapper == null), + assert((shapeColorMappers != null && primaryValueMapper != null && - shapeColorValueMapper != null)), - assert(bubbleColorMappers == null || - (bubbleColorMappers != null && + shapeColorValueMapper != null) || + shapeColorMappers == null), + assert((bubbleColorMappers != null && primaryValueMapper != null && - bubbleColorValueMapper != null)); + bubbleColorValueMapper != null) || + bubbleColorMappers == null); /// Field name in the .json file to identify each shape. /// /// It is used to refer the field name in the .json file to identify /// each shape and map that shape with the respective data in /// the data source. - final String shapeDataField; + final String? shapeDataField; /// Length of the data source. final int dataCount; @@ -269,17 +494,34 @@ class MapShapeSource extends DiagnosticableTree { /// based on the [MapColorMapper.value] property of [MapColorMapper]. /// /// ```dart - /// List data; + /// List _data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// - /// data = [ + /// _data = [ /// Model('India', 280, "Low"), /// Model('United States of America', 190, "High"), /// Model('Pakistan', 37, "Low"), /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) + /// ], + /// ); /// } /// /// @override @@ -287,41 +529,53 @@ class MapShapeSource extends DiagnosticableTree { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].storage; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(value: "Low", color: Colors.red), - /// MapColorMapper(value: "High", color: Colors.green) - /// ]), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// The below code snippet represents how color can be applied to the shape /// based on the range between [MapColorMapper.from] and [MapColorMapper.to] /// properties of [MapColorMapper]. /// /// ```dart - /// List data; + /// List _data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// - /// data = [ + /// _data = [ /// Model('India', 100, "Low"), /// Model('United States of America', 200, "High"), /// Model('Pakistan', 75, "Low"), /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].count; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(from: 0, to: 100, color: Colors.red), + /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) + /// ] + /// ); /// } /// /// @override @@ -329,26 +583,21 @@ class MapShapeSource extends DiagnosticableTree { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].count; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.red), - /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) - /// ]), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` - final List shapeColorMappers; + final List? shapeColorMappers; /// Collection of [MapColorMapper] which specifies bubble's color /// based on the data. @@ -361,17 +610,37 @@ class MapShapeSource extends DiagnosticableTree { /// based on the [MapColorMapper.value] property of [MapColorMapper]. /// /// ```dart - /// List data; + /// List _data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// - /// data = [ + /// _data = [ /// Model('India', 280, "Low"), /// Model('United States of America', 190, "High"), /// Model('Pakistan', 37, "Low"), /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// bubbleColorValueMapper: (int index) { + /// return _data[index].count; + /// }, + /// bubbleSizeMapper: (int index) { + /// return _data[index].count; + /// }, + /// bubbleColorMappers: [ + /// MapColorMapper(from: 0, to: 100, color: Colors.red), + /// MapColorMapper(from: 101, to: 300, color: Colors.yellow) + /// ] + /// ); /// } /// /// @override @@ -379,44 +648,56 @@ class MapShapeSource extends DiagnosticableTree { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// bubbleColorValueMapper: (index) { - /// return data[index].usersCount; - /// }, - /// bubbleSizeMapper: (index) { - /// return data[index].usersCount; - /// }, - /// bubbleColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.red), - /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) - /// ]), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// The below code snippet represents how color can be applied to the bubble /// based on the range between [MapColorMapper.from] and [MapColorMapper.to] /// properties of [MapColorMapper]. /// /// ```dart - /// List data; + /// List _data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// - /// data = [ + /// _data = [ /// Model('India', 280, "Low"), /// Model('United States of America', 190, "High"), /// Model('Pakistan', 37, "Low"), /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// bubbleColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// bubbleSizeMapper: (int index) { + /// return _data[index].count; + /// }, + /// bubbleColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.yellow) + /// ] + /// ); /// } /// /// @override @@ -424,29 +705,21 @@ class MapShapeSource extends DiagnosticableTree { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// bubbleColorValueMapper: (index) { - /// return data[index].storage; - /// }, - /// bubbleSizeMapper: (index) { - /// return data[index].usersCount; - /// }, - /// bubbleColorMappers: [ - /// MapColorMapper(value: "Low", color: Colors.red), - /// MapColorMapper(value: "High", color: Colors.yellow) - /// ]), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` - final List bubbleColorMappers; + final List? bubbleColorMappers; /// Returns the the primary value for the every data in the data source /// collection. @@ -456,52 +729,100 @@ class MapShapeSource extends DiagnosticableTree { /// in the rendering of bubbles, data labels, shape colors, tooltip /// in their respective shape's coordinates. /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// ); + /// } + /// /// @override /// Widget build(BuildContext context) { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// } - /// ), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` - final IndexedStringValueMapper primaryValueMapper; + final IndexedStringValueMapper? primaryValueMapper; /// Returns the data label text for each shape. /// /// ```dart - /// @override + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// dataLabelMapper: (int index) { + /// return _data[index].country; + /// }, + /// ); + /// } + /// + /// @override /// Widget build(BuildContext context) { /// return SfMaps( /// layers: [ /// MapShapeLayer( /// showDataLabels: true, - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// dataLabelMapper: (index) { - /// return bubbleData[index].country; - /// } - /// ), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` - final IndexedStringValueMapper dataLabelMapper; + final IndexedStringValueMapper? dataLabelMapper; /// Returns a value based on which bubble size will be calculated. /// @@ -509,28 +830,52 @@ class MapShapeSource extends DiagnosticableTree { /// [MapBubbleSettings.minRadius] and [MapBubbleSettings.maxRadius]. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// bubbleSizeMapper: (int index) { + /// return _data[index].usersCount; + /// } + /// ); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// } - /// ), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.usersCount, this.storage); + /// + /// final String country; + /// final double usersCount; + /// final String storage; + /// } /// ``` - final IndexedDoubleValueMapper bubbleSizeMapper; + final IndexedDoubleValueMapper? bubbleSizeMapper; /// Returns a color or value based on which shape color will be updated. /// @@ -546,28 +891,53 @@ class MapShapeSource extends DiagnosticableTree { /// the respective shape. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low", Colors.red), + /// Model('United States of America', 190, "High", Colors.green), + /// Model('Pakistan', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].color; + /// } + /// ); + /// } + /// /// @override /// Widget build(BuildContext context) { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return bubbleData[index].country; - /// } - /// ), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.usersCount, this.storage, this.color); + /// + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } /// ``` - final IndexedColorValueMapper shapeColorValueMapper; + final IndexedColorValueMapper? shapeColorValueMapper; /// Returns a color or value based on which bubble color will be updated. /// @@ -583,44 +953,73 @@ class MapShapeSource extends DiagnosticableTree { /// the respective bubble. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low", Colors.red), + /// Model('United States of America', 190, "High", Colors.green), + /// Model('Pakistan', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// bubbleColorValueMapper: (int index) { + /// return _data[index].color; + /// }, + /// bubbleSizeMapper: (int index) { + /// return _data[index].usersCount; + /// } + /// ); + /// } + /// /// @override /// Widget build(BuildContext context) { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleColorValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// } - /// ), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.usersCount, this.storage, this.color); + /// + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } /// ``` - final IndexedColorValueMapper bubbleColorValueMapper; + final IndexedColorValueMapper? bubbleColorValueMapper; - /// Converts json file to future string based on asset, network, - /// memory and file. - final MapProvider _mapProvider; + /// Specifies the GeoJSON data source file. + final Object _geoJSONSource; + + /// Specifies the type of the source. + final _MapSourceType _geoJSONSourceType; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - if (_mapProvider.shapePath != null) { - properties.add(StringProperty(null, _mapProvider.shapePath)); + final MapProvider provider = + _sourceProvider(_geoJSONSource, _geoJSONSourceType); + if (provider.shapePath != null) { + properties.add(StringProperty('', provider.shapePath)); } - if (_mapProvider.bytes != null) { - properties.add(StringProperty(null, 'Shape source in bytes')); + if (provider.bytes != null) { + properties.add(StringProperty('', 'Shape source in bytes')); } properties.add(StringProperty('shapeDataField', shapeDataField)); properties.add(IntProperty('dataCount', dataCount)); @@ -641,173 +1040,6 @@ class MapShapeSource extends DiagnosticableTree { } } -class _ShapeBounds { - _ShapeBounds( - {this.minLongitude, - this.minLatitude, - this.maxLongitude, - this.maxLatitude}); - - num minLongitude; - - num minLatitude; - - num maxLongitude; - - num maxLatitude; - - _ShapeBounds get empty => _ShapeBounds( - minLongitude: null, - minLatitude: null, - maxLongitude: null, - maxLatitude: null); -} - -class _ShapeFileData { - Map decodedJsonData; - - Map mapDataSource; - - _ShapeBounds bounds; - - MapModel initialSelectedModel; - - void reset() { - decodedJsonData?.clear(); - mapDataSource?.clear(); - bounds = bounds?.empty; - } -} - -Future<_ShapeFileData> _retrieveDataFromShapeFile( - MapProvider provider, - String shapeDataField, - _ShapeFileData shapeFileData, - bool isShapeFileDecoded, - bool isSublayer) async { - if (isShapeFileDecoded) { - return shapeFileData; - } - final String assertBundleData = await provider.loadString(); - final Map data = { - 'AssertBundleData': assertBundleData, - 'ShapeDataField': shapeDataField, - 'ShapeFileData': shapeFileData, - 'IsSublayer': isSublayer - }; - return compute(_decodeJsonData, data); -} - -_ShapeFileData _decodeJsonData(Map data) { - data['ShapeFileData'].decodedJsonData = jsonDecode(data['AssertBundleData']); - _readJsonFile(data); - return data['ShapeFileData']; -} - -void _readJsonFile(Map data) { - List polygonGeometryData; - int multipolygonGeometryLength; - Map geometry; - Map properties; - - final _ShapeFileData shapeFileData = data['ShapeFileData']; - final String shapeDataField = data['ShapeDataField']; - final bool isSublayer = data['IsSublayer']; - final bool hasFeatures = - shapeFileData.decodedJsonData.containsKey('features'); - final bool hasGeometries = - shapeFileData.decodedJsonData.containsKey('geometries'); - final String key = hasFeatures - ? 'features' - : hasGeometries - ? 'geometries' - : null; - final int jsonLength = - key.isEmpty ? 0 : shapeFileData.decodedJsonData[key].length; - if (isSublayer) { - shapeFileData.bounds = _ShapeBounds( - minLatitude: minimumLatitude, - maxLatitude: maximumLatitude, - minLongitude: minimumLongitude, - maxLongitude: maximumLongitude); - } - - for (int i = 0; i < jsonLength; i++) { - if (hasFeatures) { - final dynamic features = shapeFileData.decodedJsonData[key][i]; - geometry = features['geometry']; - properties = features['properties']; - } else if (hasGeometries) { - geometry = shapeFileData.decodedJsonData[key][i]; - } - - if (geometry['type'] == 'Polygon') { - polygonGeometryData = geometry['coordinates'][0]; - _updateMapDataSource(shapeFileData, shapeDataField, properties, - polygonGeometryData, isSublayer); - } else { - multipolygonGeometryLength = geometry['coordinates'].length; - for (int j = 0; j < multipolygonGeometryLength; j++) { - polygonGeometryData = geometry['coordinates'][j][0]; - _updateMapDataSource(shapeFileData, shapeDataField, properties, - polygonGeometryData, isSublayer); - } - } - } -} - -void _updateMapDataSource(_ShapeFileData shapeFileData, String shapeDataField, - Map properties, List points, bool isSublayer) { - final String dataPath = - properties != null ? properties[shapeDataField] : null; - shapeFileData.mapDataSource.update( - dataPath, - (MapModel model) { - model.rawPoints.add(points); - return model; - }, - ifAbsent: () { - final int dataSourceIndex = shapeFileData.mapDataSource.length; - return MapModel( - primaryKey: dataPath, - actualIndex: dataSourceIndex, - legendMapperIndex: dataSourceIndex, - rawPoints: >[points], - ); - }, - ); - if (!isSublayer) { - _updateShapeBounds(shapeFileData, points); - } -} - -void _updateShapeBounds( - _ShapeFileData shapeFileData, List coordinates) { - List data; - num longitude, latitude; - final int length = coordinates.length; - for (int i = 0; i < length; i++) { - data = coordinates[i]; - longitude = data[0]; - latitude = data[1]; - if (shapeFileData.bounds.minLongitude == null) { - shapeFileData.bounds.minLongitude = longitude; - shapeFileData.bounds.minLatitude = latitude; - shapeFileData.bounds.maxLongitude = longitude; - shapeFileData.bounds.maxLatitude = latitude; - } else { - shapeFileData.bounds.minLongitude = - min(longitude, shapeFileData.bounds.minLongitude); - shapeFileData.bounds.minLatitude = - min(latitude, shapeFileData.bounds.minLatitude); - shapeFileData.bounds.maxLongitude = - max(longitude, shapeFileData.bounds.maxLongitude); - shapeFileData.bounds.maxLatitude = - max(latitude, shapeFileData.bounds.maxLatitude); - } - } -} - /// Provides option for adding, removing, deleting and updating marker /// collection. /// @@ -818,19 +1050,24 @@ void _updateShapeBounds( /// shown in the below code snippet. /// /// ```dart -/// List data; +/// List _data; /// MapShapeLayerController controller; +/// MapShapeSource _mapSource; /// Random random = Random(); /// /// @override /// void initState() { -/// data = [ +/// _data = [ /// Model(-14.235004, -51.92528), /// Model(51.16569, 10.451526), /// Model(-25.274398, 133.775136), /// Model(20.593684, 78.96288), /// Model(61.52401, 105.318756) /// ]; +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// ); /// /// controller = MapShapeLayerController(); /// super.initState(); @@ -849,15 +1086,12 @@ void _updateShapeBounds( /// SfMaps( /// layers: [ /// MapShapeLayer( -/// source: MapShapeSource.asset( -/// 'assets/world_map.json', -/// shapeDataField: 'name', -/// ), +/// source: _mapSource, /// initialMarkersCount: 5, /// markerBuilder: (BuildContext context, int index){ /// return MapMarker( -/// latitude: data[index].latitude, -/// longitude: data[index].longitude, +/// latitude: _data[index].latitude, +/// longitude: _data[index].longitude, /// child: Icon(Icons.add_location), /// ); /// }, @@ -890,7 +1124,7 @@ void _updateShapeBounds( /// } /// ``` class MapShapeLayerController extends MapLayerController { - RenderShapeLayer _parentBox; + late _RenderGeoJSONLayer _parentBox; /// Returns the current markers count. int get markersCount => _markersCount; @@ -901,58 +1135,70 @@ class MapShapeLayerController extends MapLayerController { return getPixelToLatLng( position, _parentBox.size, - _parentBox.controller.shapeLayerOffset, - _parentBox.controller.shapeLayerSizeFactor); + _parentBox._controller.shapeLayerOffset, + _parentBox._controller.shapeLayerSizeFactor); } } -/// The sublayer in which geographical rendering is done. +/// The shape sublayer for tile and shape layer. /// /// This sublayer can be added as a sublayer of both [MapShapeLayer] and /// [MapTileLayer]. /// /// The actual geographical rendering is done here using the -/// [MapShapeLayer.source]. The source can be set as the .json file from an +/// [MapShapeSublayer.source]. The source can be set as the .json file from an /// asset bundle, network or from [Uint8List] as bytes. /// -/// The [MapShapeSource.shapeDataField] property is used to +/// The [MapShapeSublayer.shapeDataField] property is used to /// refer the unique field name in the .json file to identify each shapes and /// map with the respective data in the data source. /// /// By default, the value specified for the -/// [MapShapeSource.shapeDataField] in the GeoJSON source will be used in +/// [MapShapeSublayer.shapeDataField] in the GeoJSON source will be used in /// the elements like data labels, and tooltip for their respective /// shapes. /// /// However, it is possible to keep a data source and customize these elements /// based on the requirement. The value of the -/// [MapShapeSource.shapeDataField] will be used to map with the -/// respective data returned in [MapShapeSource.primaryValueMapper] +/// [MapShapeSublayer.shapeDataField] will be used to map with the +/// respective data returned in [MapShapeSublayer.primaryValueMapper] /// from the data source. /// /// Once the above mapping is done, you can customize the elements using the -/// APIs like [MapShapeSource.dataLabelMapper], -/// [MapShapeSource.shapeColorMappers], etc. +/// APIs like [MapShapeSublayer.dataLabelMapper], +/// [MapShapeSublayer.shapeColorMappers], etc. /// /// The snippet below shows how to render the basic world map using the data /// from .json file. /// /// ```dart -/// @override +/// MapShapeSource _mapSource; +/// MapShapeSource _mapSublayerSource; +/// +/// @override +/// void initState() { +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// ); +/// +/// _mapSublayerSource = MapShapeSource.asset( +/// "assets/africa.json", +/// shapeDataField: "name", +/// ); +/// +/// super.initState(); +/// } +/// @override /// Widget build(BuildContext context) { /// return SfMaps( /// layers: [ /// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "name", -/// ), -/// sublayer:[ +/// source: _mapSource, +/// sublayers:[ /// MapShapeSublayer( -/// source: MapShapeSource.asset( -/// "assets/africa.json", -/// shapeDataField: "name", -/// ), +/// source: _mapSublayerSource, +/// color: Colors.red, /// ), /// ] /// ) @@ -966,8 +1212,8 @@ class MapShapeLayerController extends MapLayerController { class MapShapeSublayer extends MapSublayer { /// Creates a [MapShapeSublayer]. const MapShapeSublayer({ - Key key, - @required this.source, + Key? key, + required this.source, this.controller, this.initialMarkersCount = 0, this.markerBuilder, @@ -1011,25 +1257,36 @@ class MapShapeSublayer extends MapSublayer { /// [MapShapeSource.shapeColorMappers], etc. /// /// ```dart - /// @override + /// MapShapeSource _mapSource; + /// MapShapeSource _mapSublayerSource; + /// + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// ); + /// + /// _mapSublayerSource = MapShapeSource.asset( + /// "assets/africa.json", + /// shapeDataField: "name", + /// ); + /// + /// super.initState(); + /// } + /// + /// @override /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// sublayers: [ - /// MapShapeSublayer( - /// source: MapShapeSource.asset( - /// 'assets/africa.json', - /// shapeDataField: 'name', - /// ), - /// color: Colors.blue.withOpacity(0.7), - /// strokeColor: Colors.blue, - /// ), - /// ], + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers:[ + /// MapShapeSublayer( + /// source: _mapSublayerSource, + /// color: Colors.red, + /// ), + /// ] /// ) /// ], /// ); @@ -1066,7 +1323,7 @@ class MapShapeSublayer extends MapSublayer { /// /// For rendering the custom widget for the marker, pass the required widget /// for child in [MapMarker] constructor. - final MapMarkerBuilder markerBuilder; + final MapMarkerBuilder? markerBuilder; /// Returns a widget for the shape tooltip based on the index. /// @@ -1082,7 +1339,84 @@ class MapShapeSublayer extends MapSublayer { /// The [MapShapeSublayer.shapeTooltipBuilder] callback will be called when /// the user interacts with the shapes i.e., while tapping in touch devices /// and hovering in the mouse enabled devices. - final IndexedWidgetBuilder shapeTooltipBuilder; + /// + /// ```dart + /// MapShapeSource _mapSource; + /// MapShapeSource _mapSublayerSource; + /// List _data; + /// MapZoomPanBehavior _zoomPanBehavior; + /// + /// @override + /// void initState() { + /// _data = [ + /// DataModel('Orissa', 280, "Low", Colors.red), + /// DataModel('Karnataka', 190, "High", Colors.green), + /// DataModel('Tamil Nadu', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset("assets/world_map.json", + /// shapeDataField: "continent"); + /// + /// _mapSublayerSource = MapShapeSource.asset("assets/india.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) => _data[index].country, + /// shapeColorValueMapper: (int index) => _data[index].color); + /// + /// _zoomPanBehavior = MapZoomPanBehavior( + /// zoomLevel: 5, focalLatLng: MapLatLng(28.7041, 77.1025)); + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Padding( + /// padding: EdgeInsets.all(15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapShapeSublayer( + /// source: _mapSublayerSource, + /// shapeTooltipBuilder: (BuildContext context, int index) { + /// if (index == 0) { + /// return Container( + /// child: Icon(Icons.airplanemode_inactive), + /// ); + /// } else { + /// return Container( + /// child: Icon(Icons.airplanemode_active), + /// ); + /// } + /// }, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel( + /// this.country, + /// this.usersCount, + /// this.storage, + /// this.color, + /// ); + /// + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } + /// ``` + final IndexedWidgetBuilder? shapeTooltipBuilder; /// Returns a widget for the bubble tooltip based on the index. /// @@ -1098,11 +1432,93 @@ class MapShapeSublayer extends MapSublayer { /// The [MapShapeSublayer.bubbleTooltipBuilder] callback will be called when /// the user interacts with the bubbles i.e., while tapping in touch devices /// and hovering in the mouse enabled devices. - final IndexedWidgetBuilder bubbleTooltipBuilder; - - /// Returns the widget for the tooltip of the [MapMarker]. /// - /// To show the tooltip for markers, return a customized widget in this. + /// ```dart + /// MapShapeSource _mapSource; + /// MapShapeSource _mapSublayerSource; + /// List _data; + /// MapZoomPanBehavior _zoomPanBehavior; + /// + /// @override + /// void initState() { + /// _data = [ + /// DataModel('Orissa', 280, "Low", Colors.red), + /// DataModel('Karnataka', 190, "High", Colors.green), + /// DataModel('Tamil Nadu', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset("assets/world_map.json", + /// shapeDataField: "continent"); + /// + /// _mapSublayerSource = MapShapeSource.asset("assets/india.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) => _data[index].country, + /// bubbleColorValueMapper: (int index) { + /// return _data[index].color; + /// }, + /// bubbleSizeMapper: (int index) { + /// return _data[index].usersCount; + /// }); + /// + /// _zoomPanBehavior = MapZoomPanBehavior( + /// zoomLevel: 5, focalLatLng: MapLatLng(28.7041, 77.1025)); + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Padding( + /// padding: EdgeInsets.all(15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapShapeSublayer( + /// source: _mapSublayerSource, + /// bubbleTooltipBuilder: (BuildContext context, int index) { + /// if (index == 0) { + /// return Container( + /// child: Icon(Icons.airplanemode_inactive), + /// ); + /// } else { + /// return Container( + /// child: Icon(Icons.airplanemode_active), + /// ); + /// } + /// }, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel( + /// this.country, + /// this.usersCount, + /// this.storage, + /// this.color, + /// ); + /// + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } + /// ``` + final IndexedWidgetBuilder? bubbleTooltipBuilder; + + /// Returns the widget for the tooltip of the [MapMarker]. + /// + /// To show the tooltip for markers, return a customized widget in this. /// This widget will then be wrapped in the in-built shape which comes with /// the nose at the bottom. /// @@ -1114,13 +1530,98 @@ class MapShapeSublayer extends MapSublayer { /// tooltip. /// * [SfMapsThemeData.tooltipBorderRadius], to customize the corners of the /// tooltip. - final IndexedWidgetBuilder markerTooltipBuilder; + /// + /// ```dart + /// MapShapeSource _mapSource; + /// MapShapeSource _mapSublayerSource; + /// List _data; + /// MapZoomPanBehavior _zoomPanBehavior; + /// + /// @override + /// void initState() { + /// _data = [ + /// MapLatLng(11.1271, 78.6569), + /// MapLatLng(15.3173, 75.7139), + /// MapLatLng(28.7041, 77.1025) + /// ]; + /// + /// _mapSource = MapShapeSource.asset("assets/world_map.json", + /// shapeDataField: "continent"); + /// + /// _mapSublayerSource = MapShapeSource.asset( + /// "assets/india.json", + /// shapeDataField: "name", + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior( + /// zoomLevel: 5, focalLatLng: MapLatLng(28.7041, 77.1025)); + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Padding( + /// padding: EdgeInsets.all(15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapShapeSublayer( + /// source: _mapSublayerSource, + /// initialMarkersCount: 3, + /// markerBuilder: (BuildContext context, int index) { + /// return MapMarker( + /// latitude: _data[index].latitude, + /// longitude: _data[index].longitude, + /// ); + /// }, + /// markerTooltipBuilder: (BuildContext context, int index) { + /// if(index == 0) { + /// return Container( + /// child: Icon(Icons.airplanemode_inactive), + /// ); + /// } + /// else + /// { + /// return Container( + /// child: Icon(Icons.airplanemode_active), + /// ); + /// } + /// }, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel( + /// this.country, + /// this.usersCount, + /// this.storage, + /// this.color, + /// ); + /// + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } + /// ``` + final IndexedWidgetBuilder? markerTooltipBuilder; /// Provides option for adding, removing, deleting and updating marker /// collection. /// /// You can also get the current markers count from this. - final MapShapeLayerController controller; + final MapShapeLayerController? controller; /// Shows or hides the data labels in the sublayer. /// @@ -1128,13 +1629,13 @@ class MapShapeSublayer extends MapSublayer { final bool showDataLabels; /// Color which is used to paint the sublayer shapes. - final Color color; + final Color? color; /// Color which is used to paint the stroke of the sublayer shapes. - final Color strokeColor; + final Color? strokeColor; /// Sets the stroke width of the sublayer shapes. - final double strokeWidth; + final double? strokeWidth; /// Customizes the appearance of the data labels. final MapDataLabelSettings dataLabelSettings; @@ -1166,12 +1667,11 @@ class MapShapeSublayer extends MapSublayer { /// The map passes the selected index to the [onSelectionChanged] callback but /// does not actually change this value until the parent widget rebuilds the /// maps with the new value. - final ValueChanged onSelectionChanged; + final ValueChanged? onSelectionChanged; @override Widget build(BuildContext context) { - return _MapsShapeLayer( - key: key, + return _GeoJSONLayer( source: source, controller: controller, initialMarkersCount: initialMarkersCount, @@ -1189,9 +1689,52 @@ class MapShapeSublayer extends MapSublayer { tooltipSettings: const MapTooltipSettings(), selectedIndex: selectedIndex, onSelectionChanged: onSelectionChanged, - sublayer: this, + sublayerAncestor: this, ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(source.toDiagnosticsNode(name: 'source')); + if (controller != null) { + properties.add(IntProperty('markersCount', controller!.markersCount)); + } else { + properties.add(IntProperty('markersCount', initialMarkersCount)); + } + + properties.add(ObjectFlagProperty.has( + 'markerBuilder', markerBuilder)); + properties.add(ObjectFlagProperty.has( + 'shapeTooltip', shapeTooltipBuilder)); + properties.add(ObjectFlagProperty.has( + 'bubbleTooltip', bubbleTooltipBuilder)); + properties.add(ObjectFlagProperty.has( + 'markerTooltip', markerTooltipBuilder)); + properties.add(FlagProperty('showDataLabels', + value: showDataLabels, + ifTrue: 'Data labels are showing', + ifFalse: 'Data labels are not showing', + showName: false)); + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + if (strokeColor != null) { + properties.add(ColorProperty('strokeColor', strokeColor)); + } + + if (strokeWidth != null) { + properties.add(DoubleProperty('strokeWidth', strokeWidth)); + } + + properties.add(IntProperty('selectedIndex', selectedIndex)); + properties + .add(dataLabelSettings.toDiagnosticsNode(name: 'dataLabelSettings')); + properties.add(bubbleSettings.toDiagnosticsNode(name: 'bubbleSettings')); + properties + .add(selectionSettings.toDiagnosticsNode(name: 'selectionSettings')); + } } /// The shape layer in which geographical rendering is done. @@ -1223,15 +1766,23 @@ class MapShapeSublayer extends MapSublayer { /// from .json file. /// /// ```dart -/// @override +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// ); +/// super.initState(); +/// } +/// +/// @override /// Widget build(BuildContext context) { /// return SfMaps( /// layers: [ /// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "name", -/// ), +/// source: _mapSource, /// ) /// ], /// ); @@ -1243,16 +1794,16 @@ class MapShapeSublayer extends MapSublayer { class MapShapeLayer extends MapLayer { /// Creates a [MapShapeLayer]. const MapShapeLayer({ - Key key, - @required this.source, + Key? key, + required this.source, this.loadingBuilder, this.controller, - List sublayers, + List? sublayers, int initialMarkersCount = 0, - MapMarkerBuilder markerBuilder, + MapMarkerBuilder? markerBuilder, this.shapeTooltipBuilder, this.bubbleTooltipBuilder, - IndexedWidgetBuilder markerTooltipBuilder, + IndexedWidgetBuilder? markerTooltipBuilder, this.showDataLabels = false, this.color, this.strokeColor, @@ -1263,10 +1814,10 @@ class MapShapeLayer extends MapLayer { this.selectionSettings = const MapSelectionSettings(), MapTooltipSettings tooltipSettings = const MapTooltipSettings(), this.selectedIndex = -1, - MapZoomPanBehavior zoomPanBehavior, + MapZoomPanBehavior? zoomPanBehavior, this.onSelectionChanged, - WillZoomCallback onWillZoom, - WillPanCallback onWillPan, + WillZoomCallback? onWillZoom, + WillPanCallback? onWillPan, }) : super( key: key, sublayers: sublayers, @@ -1306,26 +1857,53 @@ class MapShapeLayer extends MapLayer { /// [MapShapeSource.shapeColorMappers], etc. /// /// ```dart + /// MapShapeSource _mapSource; + /// List _data; + /// + /// @override + /// void initState() { + /// + /// _data = [ + /// Model('India', 280, "Low", Colors.red), + /// Model('United States of America', 190, "High", Colors.green), + /// Model('Pakistan', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// dataLabelMapper: (int index) { + /// return _data[index].country; + /// } + /// ); + /// super.initState(); + /// } + /// /// @override /// Widget build(BuildContext context) { /// return /// SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// dataLabelMapper: (index) { - /// return data[index].countryCode; - /// }), + /// source: _mapSource, + /// showDataLabels: true, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.usersCount, this.storage, this.color); + /// + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } /// ``` /// See also: /// * [MapShapeSource.primaryValueMapper], to map the data of the data @@ -1345,6 +1923,19 @@ class MapShapeLayer extends MapLayer { /// map is still loading. /// /// ```dart + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// super.initState(); + /// } + /// /// @override /// Widget build(BuildContext context) { /// return Scaffold( @@ -1353,10 +1944,7 @@ class MapShapeLayer extends MapLayer { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'continent', - /// ), + /// source: _mapSource, /// loadingBuilder: (BuildContext context) { /// return Container( /// height: 25, @@ -1373,7 +1961,7 @@ class MapShapeLayer extends MapLayer { /// ); /// } /// ``` - final MapLoadingBuilder loadingBuilder; + final MapLoadingBuilder? loadingBuilder; /// Returns a widget for the shape tooltip based on the index. /// @@ -1390,6 +1978,29 @@ class MapShapeLayer extends MapLayer { /// tapping in touch devices and hovering in the mouse enabled devices. /// /// ```dart + /// MapShapeSource _mapSource; + /// List _data; + /// + /// @override + /// void initState() { + /// + /// _data = [ + /// Model('India', 280, "Low", Colors.red), + /// Model('United States of America', 190, "High", Colors.green), + /// Model('Pakistan', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) => _data[index].country, + /// shapeColorValueMapper: (int index) => _data[index].color + /// ); + /// + /// super.initState(); + /// } + /// /// @override /// Widget build(BuildContext context) { /// return Scaffold( @@ -1398,13 +2009,7 @@ class MapShapeLayer extends MapLayer { /// child: SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'continent', - /// dataCount: worldMapData.length, - /// primaryValueMapper: (index) => - /// worldMapData[index].primaryKey, - /// ), + /// source: _mapSource, /// shapeTooltipBuilder: (BuildContext context, int index) { /// if(index == 0) { /// return Container( @@ -1424,8 +2029,17 @@ class MapShapeLayer extends MapLayer { /// ), /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.usersCount, this.storage, this.color); + /// + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } /// ``` - final IndexedWidgetBuilder shapeTooltipBuilder; + final IndexedWidgetBuilder? shapeTooltipBuilder; /// Returns a widget for the bubble tooltip based on the index. /// @@ -1442,42 +2056,69 @@ class MapShapeLayer extends MapLayer { /// tapping in touch devices and hovering in the mouse enabled devices. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Padding( - /// padding: EdgeInsets.all(15), - /// child: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'continent', - /// dataCount: worldMapData.length, - /// primaryValueMapper: (index) => - /// worldMapData[index].primaryKey, - /// ), - /// bubbleTooltipBuilder: (BuildContext context, int index) { - /// if(index == 0) { - /// return Container( - /// child: Icon(Icons.airplanemode_inactive), - /// ); - /// } - /// else - /// { - /// return Container( - /// child: Icon(Icons.airplanemode_active), - /// ); - /// } - /// }, - /// ), - /// ], - /// ), - /// ), + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low", Colors.red), + /// Model('United States of America', 190, "High", Colors.green), + /// Model('Pakistan', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// bubbleColorValueMapper: (int index) { + /// return _data[index].color; + /// }, + /// bubbleSizeMapper: (int index) { + /// return _data[index].usersCount; + /// } /// ); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// bubbleTooltipBuilder: (BuildContext context, int index) { + /// if(index == 0) { + /// return Container( + /// child: Icon(Icons.airplanemode_inactive), + /// ); + /// } + /// else + /// { + /// return Container( + /// child: Icon(Icons.airplanemode_active), + /// ); + /// } + /// }, + /// ) + /// ], + /// ); + /// } + /// + /// class Model { + /// const Model(this.country, this.usersCount, this.storage, this.color); + /// + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; /// } /// ``` - final IndexedWidgetBuilder bubbleTooltipBuilder; + final IndexedWidgetBuilder? bubbleTooltipBuilder; /// Provides option for adding, removing, deleting and updating marker /// collection. @@ -1486,13 +2127,14 @@ class MapShapeLayer extends MapLayer { /// this. /// /// ```dart - /// List data; - /// MapShapeLayerController controller; - /// Random random = Random(); + /// List _data; + /// MapShapeLayerController _controller; + /// Random _random = Random(); + /// MapShapeSource _mapSource; /// /// @override /// void initState() { - /// data = [ + /// _data = [ /// Model(-14.235004, -51.92528), /// Model(51.16569, 10.451526), /// Model(-25.274398, 133.775136), @@ -1500,7 +2142,12 @@ class MapShapeLayer extends MapLayer { /// Model(61.52401, 105.318756) /// ]; /// - /// controller = MapShapeLayerController(); + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// ); + /// + /// _controller = MapShapeLayerController(); /// super.initState(); /// } /// @@ -1517,29 +2164,26 @@ class MapShapeLayer extends MapLayer { /// SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'name', - /// ), + /// source: _mapSource, /// initialMarkersCount: 5, /// markerBuilder: (BuildContext context, int index) { /// return MapMarker( - /// latitude: data[index].latitude, - /// longitude: data[index].longitude, + /// latitude: _data[index].latitude, + /// longitude: _data[index].longitude, /// child: Icon(Icons.add_location), /// ); /// }, - /// controller: controller, + /// controller: _controller, /// ), /// ], /// ), /// RaisedButton( /// child: Text('Add marker'), /// onPressed: () { - /// data.add(Model( - /// -180 + random.nextInt(360).toDouble(), - /// -55 + random.nextInt(139).toDouble())); - /// controller.insertMarker(5); + /// _data.add(Model( + /// -180 + _random.nextInt(360).toDouble(), + /// -55 + _random.nextInt(139).toDouble())); + /// _controller.insertMarker(5); /// }, /// ), /// ], @@ -1557,7 +2201,7 @@ class MapShapeLayer extends MapLayer { /// final double longitude; /// } /// ``` - final MapShapeLayerController controller; + final MapShapeLayerController? controller; /// Shows or hides the data labels. /// @@ -1570,13 +2214,13 @@ class MapShapeLayer extends MapLayer { final bool showDataLabels; /// Color which is used to paint the shapes. - final Color color; + final Color? color; /// Color which is used to paint the stroke of the shapes. - final Color strokeColor; + final Color? strokeColor; /// Sets the stroke width of the shapes. - final double strokeWidth; + final double? strokeWidth; /// Customizes the appearance of the data labels. final MapDataLabelSettings dataLabelSettings; @@ -1636,7 +2280,7 @@ class MapShapeLayer extends MapLayer { /// /// See also: /// * [MapLegend.source], to enable legend for shape or bubbles. - final MapLegend legend; + final MapLegend? legend; /// Customizes the appearance of the selected shape. /// @@ -1671,27 +2315,81 @@ class MapShapeLayer extends MapLayer { /// This snippet shows how to use onSelectionChanged callback in [SfMaps]. /// /// ```dart - /// int _selectedIndex = -1; - /// - /// SfMaps( - /// layers: [MultiChildMapShapeLayer( - /// source: source, - /// selectedIndex: _selectedIndex, - /// onSelectionChanged: (int index) { - /// setState(() { - /// _selectedIndex = (_selectedIndex == index) ? -1 : index; - /// }); - /// }, - /// )] - /// ) + /// List _data; + /// MapShapeSource _mapSource; + /// int _selectedIndex = -1; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low", Colors.red), + /// DataModel('United States of America', 190, "High", Colors.green), + /// DataModel('Pakistan', 37, "Low", Colors.yellow), + /// ]; /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) => _data[index].country, + /// shapeColorValueMapper: (int index) => _data[index].color, + /// ); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: Column( + /// children: [ + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// selectedIndex: _selectedIndex, + /// selectionSettings: MapSelectionSettings( + /// color: Colors.pink,), + /// onSelectionChanged: (int index) { + /// setState(() { + /// _selectedIndex = (_selectedIndex == index) ? + /// -1 : index; + /// }); + /// }, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ), + /// )), + /// ); + /// } + /// } + /// + /// class DataModel { + /// const DataModel( + /// this.country, + /// this.usersCount, + /// this.storage, + /// this.color, + /// ); + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } /// ``` - final ValueChanged onSelectionChanged; + final ValueChanged? onSelectionChanged; @override Widget build(BuildContext context) { - return _MapsShapeLayer( - key: key, + return _ShapeLayer( source: source, loadingBuilder: loadingBuilder, controller: controller, @@ -1725,16 +2423,22 @@ class MapShapeLayer extends MapLayer { properties.add(ObjectFlagProperty.has( 'loadingBuilder', loadingBuilder)); if (controller != null) { - properties.add(IntProperty('markersCount', controller.markersCount)); + properties.add(IntProperty('markersCount', controller!.markersCount)); } else { properties.add(IntProperty('markersCount', initialMarkersCount)); } + if (sublayers != null && sublayers!.isNotEmpty) { + final DebugSublayerTree pointerTreeNode = DebugSublayerTree(sublayers!); + properties.add(pointerTreeNode.toDiagnosticsNode()); + } properties.add(ObjectFlagProperty.has( 'markerBuilder', markerBuilder)); properties.add(ObjectFlagProperty.has( 'shapeTooltip', shapeTooltipBuilder)); properties.add(ObjectFlagProperty.has( 'bubbleTooltip', bubbleTooltipBuilder)); + properties.add(ObjectFlagProperty.has( + 'markerTooltip', markerTooltipBuilder)); properties.add(FlagProperty('showDataLabels', value: showDataLabels, ifTrue: 'Data labels are showing', @@ -1748,7 +2452,7 @@ class MapShapeLayer extends MapLayer { properties.add(ColorProperty('strokeColor', strokeColor)); } - if (strokeColor != null) { + if (strokeWidth != null) { properties.add(DoubleProperty('strokeWidth', strokeWidth)); } @@ -1756,7 +2460,7 @@ class MapShapeLayer extends MapLayer { properties .add(dataLabelSettings.toDiagnosticsNode(name: 'dataLabelSettings')); if (legend != null) { - properties.add(legend.toDiagnosticsNode(name: 'legend')); + properties.add(legend!.toDiagnosticsNode(name: 'legend')); } properties.add(bubbleSettings.toDiagnosticsNode(name: 'bubbleSettings')); properties @@ -1764,7 +2468,7 @@ class MapShapeLayer extends MapLayer { properties.add(tooltipSettings.toDiagnosticsNode(name: 'tooltipSettings')); if (zoomPanBehavior != null) { properties - .add(zoomPanBehavior.toDiagnosticsNode(name: 'zoomPanBehavior')); + .add(zoomPanBehavior!.toDiagnosticsNode(name: 'zoomPanBehavior')); } properties.add( ObjectFlagProperty.has('onWillZoom', onWillZoom)); @@ -1773,77 +2477,187 @@ class MapShapeLayer extends MapLayer { } } -class _MapsShapeLayer extends StatefulWidget { - const _MapsShapeLayer({ - Key key, - this.source, +class _ShapeLayer extends StatefulWidget { + _ShapeLayer({ + required this.source, + required this.loadingBuilder, + required this.controller, + required this.sublayers, + required this.initialMarkersCount, + required this.markerBuilder, + required this.shapeTooltipBuilder, + required this.bubbleTooltipBuilder, + required this.markerTooltipBuilder, + required this.showDataLabels, + required this.color, + required this.strokeColor, + required this.strokeWidth, + required this.legend, + required this.dataLabelSettings, + required this.bubbleSettings, + required this.selectionSettings, + required this.tooltipSettings, + required this.selectedIndex, + required this.zoomPanBehavior, + required this.onSelectionChanged, + required this.onWillZoom, + required this.onWillPan, + }); + + final MapShapeSource source; + final MapLoadingBuilder? loadingBuilder; + final MapShapeLayerController? controller; + final List? sublayers; + final int initialMarkersCount; + final MapMarkerBuilder? markerBuilder; + final IndexedWidgetBuilder? shapeTooltipBuilder; + final IndexedWidgetBuilder? bubbleTooltipBuilder; + final IndexedWidgetBuilder? markerTooltipBuilder; + final bool showDataLabels; + final Color? color; + final Color? strokeColor; + final double? strokeWidth; + final MapDataLabelSettings dataLabelSettings; + final MapLegend? legend; + final MapBubbleSettings bubbleSettings; + final MapSelectionSettings selectionSettings; + final MapTooltipSettings tooltipSettings; + final int selectedIndex; + final MapZoomPanBehavior? zoomPanBehavior; + final ValueChanged? onSelectionChanged; + final WillZoomCallback? onWillZoom; + final WillPanCallback? onWillPan; + + @override + _ShapeLayerState createState() => _ShapeLayerState(); +} + +class _ShapeLayerState extends State<_ShapeLayer> { + late MapController _controller; + + @override + void initState() { + _controller = MapController() + ..tooltipKey = GlobalKey() + ..layerType = LayerType.shape; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return MapLayerInheritedWidget( + controller: _controller, + sublayers: widget.sublayers, + child: _GeoJSONLayer( + source: widget.source, + loadingBuilder: widget.loadingBuilder, + controller: widget.controller, + sublayers: widget.sublayers, + initialMarkersCount: widget.initialMarkersCount, + markerBuilder: widget.markerBuilder, + shapeTooltipBuilder: widget.shapeTooltipBuilder, + bubbleTooltipBuilder: widget.bubbleTooltipBuilder, + markerTooltipBuilder: widget.markerTooltipBuilder, + showDataLabels: widget.showDataLabels, + color: widget.color, + strokeColor: widget.strokeColor, + strokeWidth: widget.strokeWidth, + legend: widget.legend, + dataLabelSettings: widget.dataLabelSettings, + bubbleSettings: widget.bubbleSettings, + selectionSettings: widget.selectionSettings, + tooltipSettings: widget.tooltipSettings, + selectedIndex: widget.selectedIndex, + zoomPanBehavior: widget.zoomPanBehavior, + onSelectionChanged: widget.onSelectionChanged, + onWillZoom: widget.onWillZoom, + onWillPan: widget.onWillPan, + ), + ); + } +} + +class _GeoJSONLayer extends StatefulWidget { + const _GeoJSONLayer({ + required this.source, + required this.controller, + required this.initialMarkersCount, + required this.markerBuilder, + required this.shapeTooltipBuilder, + required this.bubbleTooltipBuilder, + required this.markerTooltipBuilder, + required this.showDataLabels, + required this.color, + required this.strokeColor, + required this.strokeWidth, + required this.dataLabelSettings, + required this.bubbleSettings, + required this.selectionSettings, + required this.tooltipSettings, + required this.selectedIndex, + required this.onSelectionChanged, this.loadingBuilder, - this.controller, - this.sublayers, - this.initialMarkersCount, - this.markerBuilder, - this.shapeTooltipBuilder, - this.bubbleTooltipBuilder, - this.markerTooltipBuilder, - this.showDataLabels, - this.color, - this.strokeColor, - this.strokeWidth, this.legend, - this.dataLabelSettings, - this.bubbleSettings, - this.selectionSettings, - this.tooltipSettings, - this.selectedIndex, this.zoomPanBehavior, - this.onSelectionChanged, this.onWillZoom, this.onWillPan, - this.sublayer, - }) : super(key: key); + this.sublayerAncestor, + this.sublayers, + }); final MapShapeSource source; - final MapLoadingBuilder loadingBuilder; - final MapShapeLayerController controller; - final List sublayers; + final MapShapeLayerController? controller; final int initialMarkersCount; - final MapMarkerBuilder markerBuilder; - final IndexedWidgetBuilder shapeTooltipBuilder; - final IndexedWidgetBuilder bubbleTooltipBuilder; - final IndexedWidgetBuilder markerTooltipBuilder; + final MapMarkerBuilder? markerBuilder; + final IndexedWidgetBuilder? shapeTooltipBuilder; + final IndexedWidgetBuilder? bubbleTooltipBuilder; + final IndexedWidgetBuilder? markerTooltipBuilder; final bool showDataLabels; - final Color color; - final Color strokeColor; - final double strokeWidth; + final Color? color; + final Color? strokeColor; + final double? strokeWidth; final MapDataLabelSettings dataLabelSettings; - final MapLegend legend; final MapBubbleSettings bubbleSettings; final MapSelectionSettings selectionSettings; final MapTooltipSettings tooltipSettings; final int selectedIndex; - final MapZoomPanBehavior zoomPanBehavior; - final ValueChanged onSelectionChanged; - final WillZoomCallback onWillZoom; - final WillPanCallback onWillPan; - final MapShapeSublayer sublayer; + final ValueChanged? onSelectionChanged; + final MapLoadingBuilder? loadingBuilder; + final MapLegend? legend; + final MapZoomPanBehavior? zoomPanBehavior; + final WillZoomCallback? onWillZoom; + final WillPanCallback? onWillPan; + final MapShapeSublayer? sublayerAncestor; + final List? sublayers; @override - _MapsShapeLayerState createState() => _MapsShapeLayerState(); + _GeoJSONLayerState createState() => _GeoJSONLayerState(); } -class _MapsShapeLayerState extends State<_MapsShapeLayer> +class _GeoJSONLayerState extends State<_GeoJSONLayer> with TickerProviderStateMixin { - GlobalKey bubbleKey; - GlobalKey tooltipKey; - - List _markers; - MapLegend _legendConfiguration; - MapLayerLegend _legendWidget; - _ShapeFileData shapeFileData; - SfMapsThemeData _mapsThemeData; - - double minBubbleValue; - double maxBubbleValue; + late GlobalKey bubbleKey; + late _ShapeFileData shapeFileData; + late SfMapsThemeData _mapsThemeData; + late MapLayerInheritedWidget ancestor; + // Converts the given source file to future string based on source type. + late MapProvider _provider; + + late AnimationController toggleAnimationController; + late AnimationController _hoverBubbleAnimationController; + late AnimationController bubbleAnimationController; + late AnimationController dataLabelAnimationController; + late AnimationController hoverShapeAnimationController; + late AnimationController selectionAnimationController; + late AnimationController zoomLevelAnimationController; + late AnimationController focalLatLngAnimationController; + + List? _markers; + MapLegend? _legendConfiguration; + MapLegendWidget? _legendWidget; + + double? minBubbleValue; + double? maxBubbleValue; bool _isShapeFileDecoded = false; bool _shouldUpdateMapDataSource = true; @@ -1851,404 +2665,145 @@ class _MapsShapeLayerState extends State<_MapsShapeLayer> bool _hasSublayer = false; bool isSublayer = false; - MapController controller; - AnimationController toggleAnimationController; - AnimationController _hoverBubbleAnimationController; - AnimationController bubbleAnimationController; - AnimationController dataLabelAnimationController; - AnimationController hoverShapeAnimationController; - AnimationController selectionAnimationController; - AnimationController zoomLevelAnimationController; - AnimationController focalLatLngAnimationController; + MapController? _controller; - @override - void initState() { - super.initState(); - assert(widget.source != null); - bubbleKey = GlobalKey(); - tooltipKey = GlobalKey(); - shapeFileData = _ShapeFileData() - ..mapDataSource = {} - ..bounds = _ShapeBounds(); - dataLabelAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 750)); - bubbleAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 500)); - toggleAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); - _hoverBubbleAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); - hoverShapeAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 250)); - selectionAnimationController = AnimationController( - vsync: this, value: 1.0, duration: const Duration(milliseconds: 200)); - zoomLevelAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 650)); - focalLatLngAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 650)); - - if (widget.controller != null) { - widget.controller._markersCount = widget.initialMarkersCount; + List get _geoJSONLayerChildren { + final List children = []; + if (_hasSublayer) { + children.add(_sublayerContainer); } - - _hasSublayer = widget.sublayers != null && widget.sublayers.isNotEmpty; - MapMarker marker; - _markers = []; - for (int i = 0; i < widget.initialMarkersCount; i++) { - marker = widget.markerBuilder(context, i); - assert(marker != null); - _markers.add(marker); + if (_markers != null && _markers!.isNotEmpty) { + children.add(_markerContainer); } - widget.controller?.addListener(refreshMarkers); - - isSublayer = widget.sublayer != null; - // For sublayer, we will use parent's map controller. - if (!isSublayer) { - controller = MapController()..tooltipKey = tooltipKey; - } + return children; } - @override - void dispose() { - dataLabelAnimationController?.dispose(); - bubbleAnimationController?.dispose(); - selectionAnimationController?.dispose(); - toggleAnimationController?.dispose(); - hoverShapeAnimationController?.dispose(); - _hoverBubbleAnimationController.dispose(); - zoomLevelAnimationController?.dispose(); - focalLatLngAnimationController?.dispose(); - - _markers?.clear(); - widget.controller?.removeListener(refreshMarkers); - if (!isSublayer) { - controller?.dispose(); - } - - shapeFileData?.reset(); - super.dispose(); - } - - @override - void didUpdateWidget(_MapsShapeLayer oldWidget) { - assert(widget.source != null); - _shouldUpdateMapDataSource = oldWidget.source != widget.source; - _hasSublayer = widget.sublayers != null && widget.sublayers.isNotEmpty; - isSublayer = widget.sublayer != null; - - if (oldWidget.source._mapProvider != widget.source._mapProvider) { - _isShapeFileDecoded = false; - shapeFileData?.reset(); - } - - if (oldWidget.controller != widget.controller) { - widget.controller._parentBox = context.findRenderObject(); - } - - if (_shouldUpdateMapDataSource && !isSublayer) { - controller.visibleFocalLatLng = null; - } + Widget get _shapeLayerRenderObjectWidget => _GeoJSONLayerRenderObjectWidget( + controller: _controller!, + mapDataSource: shapeFileData.mapDataSource, + mapSource: widget.source, + selectedIndex: widget.selectedIndex, + legend: _legendWidget, + selectionSettings: widget.selectionSettings, + zoomPanBehavior: widget.zoomPanBehavior, + bubbleSettings: widget.bubbleSettings.copyWith( + color: _mapsThemeData.bubbleColor, + strokeColor: _mapsThemeData.bubbleStrokeColor, + strokeWidth: _mapsThemeData.bubbleStrokeWidth), + themeData: _mapsThemeData, + state: this, + children: _geoJSONLayerChildren, + ); - super.didUpdateWidget(oldWidget); - } + Widget get _bubbleWidget => MapBubble( + key: bubbleKey, + controller: _controller, + source: widget.source, + mapDataSource: shapeFileData.mapDataSource, + bubbleSettings: widget.bubbleSettings.copyWith( + color: _mapsThemeData.bubbleColor, + strokeColor: _mapsThemeData.bubbleStrokeColor, + strokeWidth: _mapsThemeData.bubbleStrokeWidth), + legend: _legendWidget, + showDataLabels: widget.showDataLabels, + themeData: _mapsThemeData, + bubbleAnimationController: bubbleAnimationController, + dataLabelAnimationController: dataLabelAnimationController, + toggleAnimationController: toggleAnimationController, + hoverBubbleAnimationController: _hoverBubbleAnimationController, + ); - @override - Widget build(BuildContext context) { - assert(!widget.showDataLabels || - (widget.showDataLabels && widget.source.shapeDataField != null)); - assert(widget.source.bubbleSizeMapper == null || - widget.source.bubbleSizeMapper != null && - widget.source.primaryValueMapper != null); - assert(widget.source.dataLabelMapper == null || - (widget.source.dataLabelMapper != null && widget.showDataLabels)); - assert(widget.source.shapeColorMappers == null || - widget.source.shapeColorMappers.isNotEmpty); - assert(widget.selectedIndex != null); + Widget get _dataLabelWidget => MapDataLabel( + controller: _controller, + source: widget.source, + mapDataSource: shapeFileData.mapDataSource, + settings: widget.dataLabelSettings, + effectiveTextStyle: Theme.of(context).textTheme.caption!.merge( + widget.dataLabelSettings.textStyle ?? + _mapsThemeData.dataLabelTextStyle), + themeData: _mapsThemeData, + dataLabelAnimationController: dataLabelAnimationController, + ); - final ThemeData themeData = Theme.of(context); - _mapsThemeData = SfMapsTheme.of(context); - if (widget.legend != null) { - _legendConfiguration = widget.legend.copyWith( - textStyle: themeData.textTheme.caption - .copyWith( - color: themeData.textTheme.caption.color.withOpacity(0.87)) - .merge(widget.legend.textStyle ?? _mapsThemeData.legendTextStyle), - toggledItemColor: _mapsThemeData.toggledItemColor, - toggledItemStrokeColor: _mapsThemeData.toggledItemStrokeColor, - toggledItemStrokeWidth: _mapsThemeData.toggledItemStrokeWidth); - _legendWidget = MapLayerLegend(legend: _legendConfiguration); - } else { - _legendConfiguration = null; - } + Widget get _sublayerContainer => + SublayerContainer(ancestor: ancestor, children: widget.sublayers!); - isDesktop = kIsWeb || - themeData.platform == TargetPlatform.macOS || - themeData.platform == TargetPlatform.windows; - _updateThemeData(context); - return FutureBuilder<_ShapeFileData>( - future: _retrieveDataFromShapeFile( - widget.source._mapProvider, - widget.source.shapeDataField, - shapeFileData, - _isShapeFileDecoded, - isSublayer), - builder: (BuildContext context, AsyncSnapshot<_ShapeFileData> snapshot) { - if (snapshot.hasData && _isShapeFileDecoded) { - shapeFileData = snapshot.data; - if (_shouldUpdateMapDataSource) { - minBubbleValue = null; - maxBubbleValue = null; - if (shapeFileData.mapDataSource != null) { - shapeFileData.mapDataSource.values - .forEach((MapModel model) => model.reset()); - } - _bindMapsSourceIntoDataSource(); - _shouldUpdateMapDataSource = false; - } - return _actualChild; - } else { - _isShapeFileDecoded = true; - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - final Size size = getBoxSize(constraints); - return Container( - width: size.width, - height: size.height, - alignment: Alignment.center, - child: widget.loadingBuilder?.call(context), - ); - }, - ); - } - }, - ); - } + Widget get _markerContainer => MarkerContainer( + controller: _controller!, + markerTooltipBuilder: widget.markerTooltipBuilder, + sublayer: widget.sublayerAncestor, + ancestor: ancestor, + children: _markers, + ); - void _updateThemeData(BuildContext context) { - final bool isLightTheme = _mapsThemeData.brightness == Brightness.light; - _mapsThemeData = _mapsThemeData.copyWith( - layerColor: widget.color ?? - (isSublayer - ? (isLightTheme - ? const Color.fromRGBO(198, 198, 198, 1) - : const Color.fromRGBO(71, 71, 71, 1)) - : _mapsThemeData.layerColor), - layerStrokeColor: widget.strokeColor ?? - (isSublayer - ? (isLightTheme - ? const Color.fromRGBO(145, 145, 145, 1) - : const Color.fromRGBO(133, 133, 133, 1)) - : _mapsThemeData.layerStrokeColor), - layerStrokeWidth: widget.strokeWidth ?? - (isSublayer - ? (isLightTheme ? 0.5 : 0.25) - : _mapsThemeData.layerStrokeWidth), - shapeHoverStrokeWidth: _mapsThemeData.shapeHoverStrokeWidth ?? - _mapsThemeData.layerStrokeWidth, - legendTextStyle: _legendWidget?.textStyle, - bubbleColor: widget.bubbleSettings.color ?? _mapsThemeData.bubbleColor, - bubbleStrokeColor: - widget.bubbleSettings.strokeColor ?? _mapsThemeData.bubbleStrokeColor, - bubbleStrokeWidth: - widget.bubbleSettings.strokeWidth ?? _mapsThemeData.bubbleStrokeWidth, - bubbleHoverStrokeWidth: _mapsThemeData.bubbleHoverStrokeWidth ?? - _mapsThemeData.bubbleStrokeWidth, - selectionColor: - widget.selectionSettings.color ?? _mapsThemeData.selectionColor, - selectionStrokeColor: widget.selectionSettings.strokeColor ?? - _mapsThemeData.selectionStrokeColor, - selectionStrokeWidth: widget.selectionSettings.strokeWidth ?? - _mapsThemeData.selectionStrokeWidth, - tooltipColor: widget.tooltipSettings.color ?? _mapsThemeData.tooltipColor, - tooltipStrokeColor: widget.tooltipSettings.strokeColor ?? - _mapsThemeData.tooltipStrokeColor, - tooltipStrokeWidth: widget.tooltipSettings.strokeWidth ?? - _mapsThemeData.tooltipStrokeWidth, - tooltipBorderRadius: _mapsThemeData.tooltipBorderRadius - .resolve(Directionality.of(context)), - toggledItemColor: _legendWidget?.toggledItemColor, - toggledItemStrokeColor: _legendWidget?.toggledItemStrokeColor, - toggledItemStrokeWidth: _legendWidget?.toggledItemStrokeWidth, - ); - } + Widget get _behaviorViewRenderObjectWidget => BehaviorViewRenderObjectWidget( + controller: _controller!, zoomPanBehavior: widget.zoomPanBehavior!); - bool _hasTooltipBuilder() { - if (isSublayer) { - return false; - } + Widget get _toolbarWidget => MapToolbar( + controller: _controller, + onWillZoom: widget.onWillZoom, + zoomPanBehavior: widget.zoomPanBehavior!, + ); - if (widget.shapeTooltipBuilder != null || - widget.bubbleTooltipBuilder != null || - widget.markerTooltipBuilder != null) { - return true; - } else if (_hasSublayer) { - final Iterator iterator = widget.sublayers.iterator; - while (iterator.moveNext()) { - final MapSublayer sublayer = iterator.current; - if ((sublayer is MapShapeSublayer && - (sublayer.shapeTooltipBuilder != null || - sublayer.bubbleTooltipBuilder != null || - sublayer.markerTooltipBuilder != null)) || - (sublayer is MapVectorLayer && sublayer.tooltipBuilder != null)) { - return true; - } - } - } - return false; - } + Widget get _tooltipWidget => MapTooltip( + key: _controller!.tooltipKey, + controller: _controller, + mapSource: widget.source, + sublayers: widget.sublayers, + tooltipSettings: widget.tooltipSettings, + shapeTooltipBuilder: widget.shapeTooltipBuilder, + bubbleTooltipBuilder: widget.bubbleTooltipBuilder, + markerTooltipBuilder: widget.markerTooltipBuilder, + themeData: _mapsThemeData, + ); - Widget get _shapeLayerRenderObjectWidget { + Widget get _shapeLayerWithElements { final List children = []; + children.add(_shapeLayerRenderObjectWidget); if (widget.source.bubbleSizeMapper != null) { - children.add( - MapBubble( - key: bubbleKey, - source: widget.source, - mapDataSource: shapeFileData.mapDataSource, - bubbleSettings: widget.bubbleSettings.copyWith( - color: _mapsThemeData.bubbleColor, - strokeColor: _mapsThemeData.bubbleStrokeColor, - strokeWidth: _mapsThemeData.bubbleStrokeWidth), - legend: _legendWidget, - showDataLabels: widget.showDataLabels, - themeData: _mapsThemeData, - controller: controller, - bubbleAnimationController: bubbleAnimationController, - dataLabelAnimationController: dataLabelAnimationController, - toggleAnimationController: toggleAnimationController, - hoverBubbleAnimationController: _hoverBubbleAnimationController, - ), - ); + children.add(_bubbleWidget); } if (widget.showDataLabels) { - children.add( - MapDataLabel( - source: widget.source, - mapDataSource: shapeFileData.mapDataSource, - settings: widget.dataLabelSettings, - effectiveTextStyle: Theme.of(context).textTheme.caption.merge( - widget.dataLabelSettings.textStyle ?? - _mapsThemeData.dataLabelTextStyle), - themeData: _mapsThemeData, - controller: controller, - dataLabelAnimationController: dataLabelAnimationController, - ), - ); + children.add(_dataLabelWidget); } - if (_hasSublayer) { - children.add( - ClipRect( - child: SublayerContainer( - controller: controller, - tooltipKey: tooltipKey, - children: widget.sublayers, - ), - ), - ); - } - - if (_markers != null && _markers.isNotEmpty) { - children.add( - ClipRect( - child: ShapeLayerMarkerContainer( - tooltipKey: tooltipKey, - markerTooltipBuilder: widget.markerTooltipBuilder, - children: _markers, - controller: controller, - sublayer: widget.sublayer, - ), - ), - ); - } - - if (widget.zoomPanBehavior != null) { - children.add( - BehaviorViewRenderObjectWidget( - controller: controller, - zoomPanBehavior: widget.zoomPanBehavior, - ), - ); - - if (widget.zoomPanBehavior.showToolbar && isDesktop) { - children.add( - MapToolbar( - onWillZoom: widget.onWillZoom, - zoomPanBehavior: widget.zoomPanBehavior, - controller: controller, - ), - ); + if (!isSublayer) { + if (widget.zoomPanBehavior != null) { + children.add(_behaviorViewRenderObjectWidget); + if (widget.zoomPanBehavior!.showToolbar && isDesktop) { + children.add(_toolbarWidget); + } } - } - if (_hasTooltipBuilder()) { - children.add( - MapTooltip( - key: tooltipKey, - mapSource: widget.source, - controller: controller, - sublayers: widget.sublayers, - tooltipSettings: widget.tooltipSettings, - shapeTooltipBuilder: widget.shapeTooltipBuilder, - bubbleTooltipBuilder: widget.bubbleTooltipBuilder, - markerTooltipBuilder: widget.markerTooltipBuilder, - themeData: _mapsThemeData, - ), - ); + if (_hasTooltipBuilder()) { + children.add(_tooltipWidget); + } } - return _MapShapeLayerRenderObjectWidget( - children: children, - mapDataSource: shapeFileData.mapDataSource, - mapSource: widget.source, - selectedIndex: widget.selectedIndex, - legend: _legendWidget, - selectionSettings: widget.selectionSettings, - zoomPanBehavior: widget.zoomPanBehavior, - bubbleSettings: widget.bubbleSettings.copyWith( - color: _mapsThemeData.bubbleColor, - strokeColor: _mapsThemeData.bubbleStrokeColor, - strokeWidth: _mapsThemeData.bubbleStrokeWidth), - themeData: _mapsThemeData, - controller: controller, - state: this, - ); + return ClipRect(child: Stack(children: children)); } - Widget get _actualChild { + Widget get _shapeLayerWithLegend { if (_legendConfiguration != null) { _updateLegendWidget(); - if (_legendConfiguration.offset == null) { - switch (_legendConfiguration.position) { + if (_legendConfiguration!.offset == null) { + switch (_legendConfiguration!.position) { case MapLegendPosition.top: return Column( - children: [ - _legendWidget, - _expandedShapeLayerWidget, - ], + children: [_legendWidget!, _expandedShapeLayerWidget], ); case MapLegendPosition.bottom: return Column( - children: [ - _expandedShapeLayerWidget, - _legendWidget, - ], - ); + children: [_expandedShapeLayerWidget, _legendWidget!]); case MapLegendPosition.left: return Row( - children: [ - _legendWidget, - _expandedShapeLayerWidget, - ], + children: [_legendWidget!, _expandedShapeLayerWidget], ); case MapLegendPosition.right: return Row( - children: [ - _expandedShapeLayerWidget, - _legendWidget, - ], + children: [_expandedShapeLayerWidget, _legendWidget!], ); } } else { @@ -2256,44 +2811,23 @@ class _MapsShapeLayerState extends State<_MapsShapeLayer> } } - return _shapeLayerRenderObjectWidget; - } - - void _updateLegendWidget() { - _legendWidget = _legendWidget.copyWith( - dataSource: _getLegendSource() ?? shapeFileData.mapDataSource, - legend: _legendConfiguration, - themeData: _mapsThemeData, - controller: controller, - toggleAnimationController: toggleAnimationController, - ); - } - - List _getLegendSource() { - switch (widget.legend.source) { - case MapElement.bubble: - return widget.source.bubbleColorMappers; - break; - case MapElement.shape: - return widget.source.shapeColorMappers; - break; - } - return null; + return _shapeLayerWithElements; } Widget get _expandedShapeLayerWidget => Expanded( child: Stack( alignment: Alignment.center, - children: [_shapeLayerRenderObjectWidget], + children: [_shapeLayerWithElements], ), ); /// Returns the legend and map overlapping widget. Widget get _stackedLegendAndShapeLayerWidget => Stack( children: [ - _shapeLayerRenderObjectWidget, + _shapeLayerWithElements, Align( - alignment: _getActualLegendAlignment(_legendConfiguration.position), + alignment: + _getActualLegendAlignment(_legendConfiguration!.position), // Padding widget is used to set the custom position to the legend. child: Padding( padding: _getActualLegendOffset(context), @@ -2303,30 +2837,68 @@ class _MapsShapeLayerState extends State<_MapsShapeLayer> ], ); + bool _hasTooltipBuilder() { + if (isSublayer) { + return false; + } + + if (widget.shapeTooltipBuilder != null || + widget.bubbleTooltipBuilder != null || + widget.markerTooltipBuilder != null) { + return true; + } else if (_hasSublayer) { + final Iterator iterator = widget.sublayers!.iterator; + while (iterator.moveNext()) { + final MapSublayer sublayer = iterator.current; + if ((sublayer is MapShapeSublayer && + (sublayer.shapeTooltipBuilder != null || + sublayer.bubbleTooltipBuilder != null || + sublayer.markerTooltipBuilder != null)) || + (sublayer is MapVectorLayer && sublayer.tooltipBuilder != null)) { + return true; + } + } + } + return false; + } + + void _updateLegendWidget() { + _legendWidget = _legendWidget!.copyWith( + dataSource: _getLegendSource() ?? shapeFileData.mapDataSource, + legend: _legendConfiguration!, + themeData: _mapsThemeData, + controller: _controller, + toggleAnimationController: toggleAnimationController, + ); + } + + List? _getLegendSource() { + switch (widget.legend!.source) { + case MapElement.bubble: + return widget.source.bubbleColorMappers; + case MapElement.shape: + return widget.source.shapeColorMappers; + } + } + /// Returns the alignment for the legend if we set the legend offset. AlignmentGeometry _getActualLegendAlignment(MapLegendPosition position) { switch (position) { case MapLegendPosition.top: return Alignment.topCenter; - break; case MapLegendPosition.bottom: return Alignment.bottomCenter; - break; case MapLegendPosition.left: return Alignment.centerLeft; - break; case MapLegendPosition.right: return Alignment.centerRight; - break; } - return Alignment.topCenter; } /// Returns the padding value to render the legend based on offset value. EdgeInsetsGeometry _getActualLegendOffset(BuildContext context) { - final Offset offset = _legendConfiguration.offset; - final MapLegendPosition legendPosition = - _legendConfiguration.position ?? MapLegendPosition.top; + final Offset offset = _legendConfiguration!.offset!; + final MapLegendPosition legendPosition = _legendConfiguration!.position; // Here the default alignment is center for all the positions. // So need to handle the offset by multiplied it by 2. switch (legendPosition) { @@ -2336,37 +2908,117 @@ class _MapsShapeLayerState extends State<_MapsShapeLayer> left: offset.dx > 0 ? offset.dx * 2 : 0, right: offset.dx < 0 ? offset.dx.abs() * 2 : 0, top: offset.dy > 0 ? offset.dy : 0); - break; // Returns the insets for the offset if the legend position is left. case MapLegendPosition.left: return EdgeInsets.only( top: offset.dy > 0 ? offset.dy * 2 : 0, bottom: offset.dy < 0 ? offset.dy.abs() * 2 : 0, left: offset.dx > 0 ? offset.dx : 0); - break; // Returns the insets for the offset if the legend position is right. case MapLegendPosition.right: return EdgeInsets.only( top: offset.dy > 0 ? offset.dy * 2 : 0, bottom: offset.dy < 0 ? offset.dy.abs() * 2 : 0, right: offset.dx < 0 ? offset.dx.abs() : 0); - break; // Returns the insets for the offset if the legend position is bottom. case MapLegendPosition.bottom: return EdgeInsets.only( left: offset.dx > 0 ? offset.dx * 2 : 0, right: offset.dx < 0 ? offset.dx.abs() * 2 : 0, bottom: offset.dy < 0 ? offset.dy.abs() : 0); - break; } - return EdgeInsets.zero; + } + + void _updateThemeData(BuildContext context) { + final bool isLightTheme = _mapsThemeData.brightness == Brightness.light; + _mapsThemeData = _mapsThemeData.copyWith( + layerColor: widget.color ?? + (isSublayer + ? (isLightTheme + ? const Color.fromRGBO(198, 198, 198, 1) + : const Color.fromRGBO(71, 71, 71, 1)) + : _mapsThemeData.layerColor), + layerStrokeColor: widget.strokeColor ?? + (isSublayer + ? (isLightTheme + ? const Color.fromRGBO(145, 145, 145, 1) + : const Color.fromRGBO(133, 133, 133, 1)) + : _mapsThemeData.layerStrokeColor), + layerStrokeWidth: widget.strokeWidth ?? + (isSublayer + ? (isLightTheme ? 0.5 : 0.25) + : _mapsThemeData.layerStrokeWidth), + shapeHoverStrokeWidth: _mapsThemeData.shapeHoverStrokeWidth ?? + _mapsThemeData.layerStrokeWidth, + legendTextStyle: _legendWidget?.textStyle, + bubbleColor: widget.bubbleSettings.color ?? _mapsThemeData.bubbleColor, + bubbleStrokeColor: + widget.bubbleSettings.strokeColor ?? _mapsThemeData.bubbleStrokeColor, + bubbleStrokeWidth: + widget.bubbleSettings.strokeWidth ?? _mapsThemeData.bubbleStrokeWidth, + bubbleHoverStrokeWidth: _mapsThemeData.bubbleHoverStrokeWidth ?? + _mapsThemeData.bubbleStrokeWidth, + selectionColor: + widget.selectionSettings.color ?? _mapsThemeData.selectionColor, + selectionStrokeColor: widget.selectionSettings.strokeColor ?? + _mapsThemeData.selectionStrokeColor, + selectionStrokeWidth: widget.selectionSettings.strokeWidth ?? + _mapsThemeData.selectionStrokeWidth, + tooltipColor: widget.tooltipSettings.color ?? _mapsThemeData.tooltipColor, + tooltipStrokeColor: widget.tooltipSettings.strokeColor ?? + _mapsThemeData.tooltipStrokeColor, + tooltipStrokeWidth: widget.tooltipSettings.strokeWidth ?? + _mapsThemeData.tooltipStrokeWidth, + tooltipBorderRadius: _mapsThemeData.tooltipBorderRadius + .resolve(Directionality.of(context)), + toggledItemColor: _legendWidget?.toggledItemColor, + toggledItemStrokeColor: _legendWidget?.toggledItemStrokeColor, + toggledItemStrokeWidth: _legendWidget?.toggledItemStrokeWidth, + ); + } + + Widget _buildShapeLayer() { + return FutureBuilder<_ShapeFileData>( + future: _retrieveDataFromShapeFile( + _provider, + widget.source.shapeDataField, + shapeFileData, + _isShapeFileDecoded, + isSublayer), + builder: (BuildContext context, AsyncSnapshot<_ShapeFileData> snapshot) { + if (snapshot.hasData && _isShapeFileDecoded) { + shapeFileData = snapshot.data!; + if (_shouldUpdateMapDataSource) { + minBubbleValue = null; + maxBubbleValue = null; + shapeFileData.mapDataSource.values + .forEach((MapModel model) => model.reset()); + _bindMapsSourceIntoDataSource(); + _shouldUpdateMapDataSource = false; + } + return _shapeLayerWithLegend; + } else { + _isShapeFileDecoded = true; + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final Size size = getBoxSize(constraints); + return Container( + width: size.width, + height: size.height, + alignment: Alignment.center, + child: widget.loadingBuilder?.call(context), + ); + }, + ); + } + }, + ); } /// Updating [modelSource] data index based on [dataMapper] /// value and data color based on [colorValueMapper] value. void _bindMapsSourceIntoDataSource() { - if (widget.source.dataCount != null && - widget.source.dataCount > 0 && + if (widget.source.dataCount > 0 && widget.source.primaryValueMapper != null) { final bool hasShapeColorValueMapper = widget.source.shapeColorValueMapper != null; @@ -2376,13 +3028,13 @@ class _MapsShapeLayerState extends State<_MapsShapeLayer> final bool hasBubbleSizeMapper = widget.source.bubbleSizeMapper != null; for (int i = 0; i < widget.source.dataCount; i++) { - final MapModel mapModel = - shapeFileData.mapDataSource[widget.source.primaryValueMapper(i)]; + final MapModel? mapModel = + shapeFileData.mapDataSource[widget.source.primaryValueMapper!(i)]; if (mapModel != null) { mapModel.dataIndex = i; _updateShapeColor(hasShapeColorValueMapper, i, mapModel); if (hasDataLabelMapper) { - mapModel.dataLabelText = widget.source.dataLabelMapper(i); + mapModel.dataLabelText = widget.source.dataLabelMapper!(i); } _updateBubbleColor(hasBubbleColorValueMapper, i, mapModel); @@ -2400,7 +3052,7 @@ class _MapsShapeLayerState extends State<_MapsShapeLayer> bool hasShapeColorValueMapper, int index, MapModel mapModel) { if (hasShapeColorValueMapper) { mapModel.shapeColor = _getActualColor( - widget.source.shapeColorValueMapper(index), + widget.source.shapeColorValueMapper!(index), widget.source.shapeColorMappers, mapModel); } @@ -2410,7 +3062,7 @@ class _MapsShapeLayerState extends State<_MapsShapeLayer> bool hasBubbleColorValueMapper, int index, MapModel mapModel) { if (hasBubbleColorValueMapper) { mapModel.bubbleColor = _getActualColor( - widget.source.bubbleColorValueMapper(index), + widget.source.bubbleColorValueMapper!(index), widget.source.bubbleColorMappers, mapModel); } @@ -2419,29 +3071,31 @@ class _MapsShapeLayerState extends State<_MapsShapeLayer> void _validateBubbleSize( bool hasBubbleSizeMapper, int index, MapModel mapModel) { if (hasBubbleSizeMapper) { - mapModel.bubbleSizeValue = widget.source.bubbleSizeMapper(index); - if (mapModel.bubbleSizeValue != null) { - if (minBubbleValue == null) { - minBubbleValue = mapModel.bubbleSizeValue; - maxBubbleValue = mapModel.bubbleSizeValue; - } else { - minBubbleValue = min(mapModel.bubbleSizeValue, minBubbleValue); - maxBubbleValue = max(mapModel.bubbleSizeValue, maxBubbleValue); - } + mapModel.bubbleSizeValue = widget.source.bubbleSizeMapper!(index); + if (minBubbleValue == null) { + minBubbleValue = mapModel.bubbleSizeValue; + maxBubbleValue = mapModel.bubbleSizeValue; + } else if (mapModel.bubbleSizeValue != null) { + minBubbleValue = min(mapModel.bubbleSizeValue!, minBubbleValue!); + maxBubbleValue = max(mapModel.bubbleSizeValue!, maxBubbleValue!); } } } /// Returns color from [MapColorMapper] based on the data source value. - Color _getActualColor( - Object colorValue, List colorMappers, MapModel mapModel) { + Color? _getActualColor(Object? colorValue, List? colorMappers, + MapModel? mapModel) { MapColorMapper mapper; + if (colorValue == null) { + return null; + } + final int length = colorMappers != null ? colorMappers.length : 0; // Handles equal color mapping. if (colorValue is String) { for (int i = 0; i < length; i++) { - mapper = colorMappers[i]; - assert(mapper.value != null && mapper.color != null); + mapper = colorMappers![i]; + assert(mapper.value != null); if (mapper.value == colorValue) { mapModel?.legendMapperIndex = i; return mapper.color; @@ -2452,58 +3106,58 @@ class _MapsShapeLayerState extends State<_MapsShapeLayer> // Handles range color mapping. if (colorValue is num) { for (int i = 0; i < length; i++) { - mapper = colorMappers[i]; - assert( - mapper.from != null && mapper.to != null && mapper.color != null); - if (mapper.from <= colorValue && mapper.to >= colorValue) { + mapper = colorMappers![i]; + assert(mapper.from != null && mapper.to != null); + if (mapper.from! <= colorValue && mapper.to! >= colorValue) { mapModel?.legendMapperIndex = i; if (mapper.minOpacity != null && mapper.maxOpacity != null) { return mapper.color.withOpacity(lerpDouble( mapper.minOpacity, mapper.maxOpacity, - (colorValue - mapper.from) / (mapper.to - mapper.from))); + (colorValue - mapper.from!) / (mapper.to! - mapper.from!))!); } return mapper.color; } } } - return colorValue; + // ignore: avoid_as + return colorValue as Color; } - void refreshMarkers([MarkerAction action, List indices]) { + void refreshMarkers(MarkerAction action, [List? indices]) { MapMarker marker; switch (action) { case MarkerAction.insert: - int index = indices[0]; - assert(index <= widget.controller._markersCount); - if (index > widget.controller._markersCount) { - index = widget.controller._markersCount; + int index = indices![0]; + assert(index <= widget.controller!._markersCount); + if (index > widget.controller!._markersCount) { + index = widget.controller!._markersCount; } - marker = widget.markerBuilder(context, index); - if (index < widget.controller._markersCount) { - _markers.insert(index, marker); - } else if (index == widget.controller._markersCount) { - _markers.add(marker); + marker = widget.markerBuilder!(context, index); + if (index < widget.controller!._markersCount) { + _markers!.insert(index, marker); + } else if (index == widget.controller!._markersCount) { + _markers!.add(marker); } - widget.controller._markersCount++; + widget.controller!._markersCount++; break; case MarkerAction.removeAt: - final int index = indices[0]; - assert(index < widget.controller._markersCount); - _markers.removeAt(index); - widget.controller._markersCount--; + final int index = indices![0]; + assert(index < widget.controller!._markersCount); + _markers!.removeAt(index); + widget.controller!._markersCount--; break; case MarkerAction.replace: - for (final int index in indices) { - assert(index < widget.controller._markersCount); - marker = widget.markerBuilder(context, index); - _markers[index] = marker; + for (final int index in indices!) { + assert(index < widget.controller!._markersCount); + marker = widget.markerBuilder!(context, index); + _markers![index] = marker; } break; case MarkerAction.clear: - _markers.clear(); - widget.controller._markersCount = 0; + _markers!.clear(); + widget.controller!._markersCount = 0; break; } @@ -2511,37 +3165,176 @@ class _MapsShapeLayerState extends State<_MapsShapeLayer> // Rebuilds to visually update the markers when it was updated or added. }); } + + @override + void initState() { + super.initState(); + bubbleKey = GlobalKey(); + shapeFileData = _ShapeFileData() + ..mapDataSource = {} + ..bounds = _ShapeBounds(); + dataLabelAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 750)); + bubbleAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 500)); + toggleAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 250)); + _hoverBubbleAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 250)); + hoverShapeAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 250)); + selectionAnimationController = AnimationController( + vsync: this, value: 1.0, duration: const Duration(milliseconds: 150)); + zoomLevelAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 650)); + focalLatLngAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 650)); + + if (widget.controller != null) { + widget.controller!._markersCount = widget.initialMarkersCount; + } + + _hasSublayer = widget.sublayers != null && widget.sublayers!.isNotEmpty; + MapMarker? marker; + _markers = []; + for (int i = 0; i < widget.initialMarkersCount; i++) { + marker = widget.markerBuilder!(context, i); + _markers!.add(marker); + } + + widget.controller?.addListener(refreshMarkers); + isSublayer = widget.sublayerAncestor != null; + _provider = _sourceProvider( + widget.source._geoJSONSource, widget.source._geoJSONSourceType); + } + + @override + void didChangeDependencies() { + if (_controller == null) { + ancestor = context + .dependOnInheritedWidgetOfExactType()!; + _controller = ancestor.controller; + } + super.didChangeDependencies(); + } + + @override + void didUpdateWidget(_GeoJSONLayer oldWidget) { + _shouldUpdateMapDataSource = oldWidget.source != widget.source; + _hasSublayer = widget.sublayers != null && widget.sublayers!.isNotEmpty; + isSublayer = widget.sublayerAncestor != null; + + final MapProvider currentProvider = _sourceProvider( + widget.source._geoJSONSource, widget.source._geoJSONSourceType); + if (_provider != currentProvider) { + _provider = currentProvider; + _isShapeFileDecoded = false; + shapeFileData.reset(); + } + + if (oldWidget.controller != widget.controller) { + widget.controller!._parentBox = + // ignore: avoid_as + context.findRenderObject() as _RenderGeoJSONLayer; + } + + if (_controller != null && _shouldUpdateMapDataSource && !isSublayer) { + _controller!.visibleFocalLatLng = null; + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + dataLabelAnimationController.dispose(); + bubbleAnimationController.dispose(); + selectionAnimationController.dispose(); + toggleAnimationController.dispose(); + hoverShapeAnimationController.dispose(); + _hoverBubbleAnimationController.dispose(); + zoomLevelAnimationController.dispose(); + focalLatLngAnimationController.dispose(); + + _markers?.clear(); + widget.controller?.removeListener(refreshMarkers); + if (!isSublayer) { + _controller?.dispose(); + } + + shapeFileData.reset(); + _controller = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + assert(!widget.showDataLabels || + (widget.showDataLabels && widget.source.shapeDataField != null)); + assert(widget.source.bubbleSizeMapper == null || + widget.source.bubbleSizeMapper != null && + widget.source.primaryValueMapper != null); + assert(widget.source.dataLabelMapper == null || + (widget.source.dataLabelMapper != null && widget.showDataLabels)); + assert(widget.source.shapeColorMappers == null || + widget.source.shapeColorMappers!.isNotEmpty); + + final ThemeData themeData = Theme.of(context); + _mapsThemeData = SfMapsTheme.of(context)!; + if (widget.legend != null) { + _legendConfiguration = widget.legend!.copyWith( + textStyle: themeData.textTheme.caption! + .copyWith( + color: themeData.textTheme.caption!.color!.withOpacity(0.87)) + .merge( + widget.legend!.textStyle ?? _mapsThemeData.legendTextStyle), + toggledItemColor: _mapsThemeData.toggledItemColor, + toggledItemStrokeColor: _mapsThemeData.toggledItemStrokeColor, + toggledItemStrokeWidth: _mapsThemeData.toggledItemStrokeWidth); + _legendWidget = MapLegendWidget(legend: _legendConfiguration!); + } else { + _legendConfiguration = null; + } + + isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows || + themeData.platform == TargetPlatform.linux; + _updateThemeData(context); + return _buildShapeLayer(); + } } -class _MapShapeLayerRenderObjectWidget extends Stack { - _MapShapeLayerRenderObjectWidget({ - List children, - this.mapDataSource, - this.mapSource, - this.selectedIndex, - this.legend, - this.bubbleSettings, - this.selectionSettings, - this.zoomPanBehavior, - this.themeData, - this.controller, - this.state, +class _GeoJSONLayerRenderObjectWidget extends Stack { + _GeoJSONLayerRenderObjectWidget({ + required this.controller, + required this.mapDataSource, + required this.mapSource, + required this.selectedIndex, + required this.legend, + required this.bubbleSettings, + required this.selectionSettings, + required this.zoomPanBehavior, + required this.themeData, + required this.state, + List? children, }) : super(children: children ?? []); + final MapController controller; final Map mapDataSource; final MapShapeSource mapSource; final int selectedIndex; - final MapLayerLegend legend; final MapBubbleSettings bubbleSettings; final MapSelectionSettings selectionSettings; - final MapZoomPanBehavior zoomPanBehavior; final SfMapsThemeData themeData; - final MapController controller; - final _MapsShapeLayerState state; + final _GeoJSONLayerState state; + final MapLegendWidget? legend; + final MapZoomPanBehavior? zoomPanBehavior; @override RenderStack createRenderObject(BuildContext context) { - return RenderShapeLayer( + return _RenderGeoJSONLayer( + controller: controller, mapDataSource: mapDataSource, mapSource: mapSource, selectedIndex: selectedIndex, @@ -2550,14 +3343,14 @@ class _MapShapeLayerRenderObjectWidget extends Stack { selectionSettings: selectionSettings, zoomPanBehavior: zoomPanBehavior, themeData: themeData, - controller: controller, context: context, state: state, ); } @override - void updateRenderObject(BuildContext context, RenderShapeLayer renderObject) { + void updateRenderObject( + BuildContext context, _RenderGeoJSONLayer renderObject) { renderObject ..mapDataSource = mapDataSource ..mapSource = mapSource @@ -2571,21 +3364,22 @@ class _MapShapeLayerRenderObjectWidget extends Stack { } } -// ignore_for_file: public_member_api_docs -class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { - RenderShapeLayer({ - Map mapDataSource, - MapShapeSource mapSource, - int selectedIndex, - MapLayerLegend legend, - MapBubbleSettings bubbleSettings, - MapSelectionSettings selectionSettings, - MapZoomPanBehavior zoomPanBehavior, - SfMapsThemeData themeData, - MapController controller, - BuildContext context, - _MapsShapeLayerState state, - }) : _mapDataSource = mapDataSource, +class _RenderGeoJSONLayer extends RenderStack + implements MouseTrackerAnnotation { + _RenderGeoJSONLayer({ + required MapController controller, + required Map mapDataSource, + required MapShapeSource mapSource, + required int selectedIndex, + required MapLegendWidget? legend, + required MapBubbleSettings bubbleSettings, + required MapSelectionSettings selectionSettings, + required MapZoomPanBehavior? zoomPanBehavior, + required SfMapsThemeData themeData, + required BuildContext context, + required _GeoJSONLayerState state, + }) : _controller = controller, + _mapDataSource = mapDataSource, _mapSource = mapSource, _selectedIndex = selectedIndex, _legend = legend, @@ -2593,19 +3387,20 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { _selectionSettings = selectionSettings, _zoomPanBehavior = zoomPanBehavior, _themeData = themeData, - context = context, - controller = controller, _state = state, + context = context, super(textDirection: Directionality.of(state.context)) { _scaleGestureRecognizer = ScaleGestureRecognizer() ..onStart = _handleScaleStart ..onUpdate = _handleScaleUpdate ..onEnd = _handleScaleEnd; + _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; + if (!_state.isSublayer) { - _state.controller + _controller ..onZoomLevelChange = _handleZoomLevelChange - ..onPanChange = _handlePanTo; + ..onPanChange = _handleFocalLatLngChange; } _forwardToggledShapeColorTween = ColorTween(); @@ -2625,7 +3420,7 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { if (_zoomPanBehavior != null) { _initializeZoomPanAnimations(); - _currentZoomLevel = _zoomPanBehavior.zoomLevel; + _currentZoomLevel = _zoomPanBehavior!.zoomLevel; } if (_selectedIndex != -1) { @@ -2633,7 +3428,7 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { _initializeSelectionTween(); } - if (_legend != null && _legend.enableToggleInteraction) { + if (_legend != null && _legend!.enableToggleInteraction) { _initializeToggledShapeTweenColors(); } @@ -2644,58 +3439,67 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { _state.widget.controller?._parentBox = this; } - final _MapsShapeLayerState _state; + static const double _frictionCoefficient = 0.005; + final _GeoJSONLayerState _state; final int _minPanDistance = 5; - Size _size; + final MapController _controller; double _actualFactor = 1.0; - Size _actualShapeSize; - Offset _downGlobalPoint; - Offset _downLocalPoint; + double _currentZoomLevel = 1.0; + double _maximumReachedScaleOnInteraction = 1.0; int _pointerCount = 0; - bool _singleTapConfirmed = false; + Offset _panDistanceBeforeFlinging = Offset.zero; bool _isZoomedUsingToolbar = false; - MapModel _prevSelectedItem; - MapModel _currentSelectedItem; - MapModel _currentHoverItem; - MapModel _previousHoverItem; - MapModel _currentInteractedItem; - MapLayerElement _currentInteractedElement; - ScaleGestureRecognizer _scaleGestureRecognizer; - Animation _selectionColorAnimation; - Animation _toggleShapeAnimation; - Timer _zoomingDelayTimer; - Rect _referenceShapeBounds; - Rect _referenceVisibleBounds; - MapZoomDetails _zoomDetails; - MapPanDetails _panDetails; bool _avoidPanUpdate = false; - double _currentZoomLevel = 1.0; - - Animation _hoverColorAnimation; - ColorTween _forwardSelectionColorTween; - ColorTween _forwardSelectionStrokeColorTween; - ColorTween _reverseSelectionColorTween; - ColorTween _reverseSelectionStrokeColorTween; - ColorTween _forwardHoverColorTween; - ColorTween _forwardHoverStrokeColorTween; - ColorTween _reverseHoverColorTween; - ColorTween _reverseHoverStrokeColorTween; - ColorTween _forwardToggledShapeColorTween; - ColorTween _forwardToggledShapeStrokeColorTween; - ColorTween _reverseToggledShapeColorTween; - ColorTween _reverseToggledShapeStrokeColorTween; - - Animation _zoomLevelAnimation; - Animation _focalLatLngAnimation; - MapLatLngTween _focalLatLngTween; - Tween _zoomLevelTween; + bool _isFlingAnimationActive = false; + bool _doubleTapEnabled = false; + late Size _size; + late Size _actualShapeSize; + late ScaleGestureRecognizer _scaleGestureRecognizer; + late TapGestureRecognizer _tapGestureRecognizer; + late Animation _selectionColorAnimation; + late Animation _toggleShapeAnimation; + late Animation _hoverColorAnimation; + late ColorTween _forwardSelectionStrokeColorTween; + late ColorTween _reverseSelectionColorTween; + late ColorTween _reverseSelectionStrokeColorTween; + late ColorTween _forwardHoverColorTween; + late ColorTween _forwardHoverStrokeColorTween; + late ColorTween _reverseHoverColorTween; + late ColorTween _reverseHoverStrokeColorTween; + late ColorTween _forwardToggledShapeColorTween; + late ColorTween _forwardToggledShapeStrokeColorTween; + late ColorTween _reverseToggledShapeColorTween; + late ColorTween _reverseToggledShapeStrokeColorTween; + late CurvedAnimation _flingZoomLevelCurvedAnimation; + late CurvedAnimation _flingFocalLatLngCurvedAnimation; + late CurvedAnimation _focalLatLngCurvedAnimation; + late MapLatLngTween _focalLatLngTween; + late Tween _zoomLevelTween; + + Offset? _downGlobalPoint; + Offset? _downLocalPoint; + MapModel? _prevSelectedItem; + MapModel? _currentSelectedItem; + MapModel? _currentHoverItem; + MapModel? _previousHoverItem; + MapModel? _currentInteractedItem; + MapLayerElement? _currentInteractedElement; + Timer? _zoomingDelayTimer; + Timer? _doubleTapTimer; + Rect? _referenceShapeBounds; + Rect? _referenceVisibleBounds; + MapZoomDetails? _zoomDetails; + MapPanDetails? _panDetails; + ColorTween? _forwardSelectionColorTween; + CurvedAnimation? _zoomLevelCurvedAnimation; BuildContext context; - MapController controller; bool get canZoom => _zoomPanBehavior != null && - (_zoomPanBehavior.enablePinching || _zoomPanBehavior.enablePanning); + (_zoomPanBehavior!.enablePinching || + _zoomPanBehavior!.enablePanning || + _zoomPanBehavior!.enableDoubleTapZooming); bool get isInteractive => _state.widget.shapeTooltipBuilder != null || @@ -2706,12 +3510,12 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { bool get hasBubbleHoverColor => _themeData.bubbleHoverColor != Colors.transparent || (_themeData.bubbleHoverStrokeColor != Colors.transparent && - _themeData.bubbleHoverStrokeWidth > 0); + _themeData.bubbleHoverStrokeWidth! > 0); bool get hasShapeHoverColor => _themeData.shapeHoverColor != Colors.transparent || (_themeData.shapeHoverStrokeColor != Colors.transparent && - _themeData.shapeHoverStrokeWidth > 0); + _themeData.shapeHoverStrokeWidth! > 0); Map get mapDataSource => _mapDataSource; Map _mapDataSource; @@ -2725,16 +3529,18 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { markNeedsPaint(); } - MapShapeSource get mapSource => _mapSource; - MapShapeSource _mapSource; - set mapSource(MapShapeSource value) { + MapShapeSource? get mapSource => _mapSource; + MapShapeSource? _mapSource; + set mapSource(MapShapeSource? value) { if (_mapSource == value) { return; } if (_mapSource != null && value != null && - _mapSource._mapProvider != value._mapProvider) { + _sourceProvider( + _mapSource!._geoJSONSource, _mapSource!._geoJSONSourceType) != + _sourceProvider(value._geoJSONSource, value._geoJSONSourceType)) { _mapSource = value; return; } @@ -2747,7 +3553,7 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { markNeedsPaint(); _state.dataLabelAnimationController.value = 0.0; _state.bubbleAnimationController.value = 0.0; - SchedulerBinding.instance.addPostFrameCallback(_initiateInitialAnimations); + SchedulerBinding.instance?.addPostFrameCallback(_initiateInitialAnimations); } MapBubbleSettings get bubbleSettings => _bubbleSettings; @@ -2768,17 +3574,17 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { markNeedsPaint(); } - MapLayerLegend get legend => _legend; - MapLayerLegend _legend; - set legend(MapLayerLegend value) { + MapLegendWidget? get legend => _legend; + MapLegendWidget? _legend; + set legend(MapLegendWidget? value) { // Update [MapsShapeLayer.legend] value only when // [MapsShapeLayer.legend] property is set to shape. - if (_legend != null && _legend.source != MapElement.shape || + if (_legend != null && _legend!.source != MapElement.shape || _legend == value) { return; } _legend = value; - if (_legend.enableToggleInteraction) { + if (_legend!.enableToggleInteraction) { _initializeToggledShapeTweenColors(); } markNeedsPaint(); @@ -2793,18 +3599,18 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { _selectionSettings = value; } - MapZoomPanBehavior get zoomPanBehavior => _zoomPanBehavior; - MapZoomPanBehavior _zoomPanBehavior; - set zoomPanBehavior(MapZoomPanBehavior value) { + MapZoomPanBehavior? get zoomPanBehavior => _zoomPanBehavior; + MapZoomPanBehavior? _zoomPanBehavior; + set zoomPanBehavior(MapZoomPanBehavior? value) { if (_zoomPanBehavior == value) { return; } _zoomPanBehavior = value; if (_zoomPanBehavior != null) { - if (_zoomLevelAnimation == null) { + if (_zoomLevelCurvedAnimation == null) { _initializeZoomPanAnimations(); } - _currentZoomLevel = _zoomPanBehavior.zoomLevel; + _currentZoomLevel = _zoomPanBehavior!.zoomLevel; } } @@ -2815,7 +3621,6 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { return; } - assert(_selectedIndex != null); _selectedIndex = value; if (_forwardSelectionColorTween == null) { _initializeSelectionTween(); @@ -2835,7 +3640,7 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { if (_forwardSelectionColorTween != null) { _updateSelectionTweenColors(); } - if (_legend != null && _legend.enableToggleInteraction) { + if (_legend != null && _legend!.enableToggleInteraction) { _initializeToggledShapeTweenColors(); } @@ -2850,18 +3655,18 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { Rect get paintBounds => Offset.zero & _size; @override - MouseCursor get cursor => controller.gesture == Gesture.pan + MouseCursor get cursor => _controller.gesture == Gesture.pan ? SystemMouseCursors.grabbing : SystemMouseCursors.basic; @override - PointerEnterEventListener get onEnter => null; + PointerEnterEventListener? get onEnter => null; // As onHover property of MouseHoverAnnotation was removed only in the // beta channel, once it is moved to stable, will remove this property. @override // ignore: override_on_non_overriding_member - PointerHoverEventListener get onHover => null; + PointerHoverEventListener? get onHover => null; @override PointerExitEventListener get onExit => _handleExit; @@ -2871,9 +3676,14 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { bool get validForMouseTracker => true; void _initializeZoomPanAnimations() { - _zoomLevelAnimation = CurvedAnimation( + _flingZoomLevelCurvedAnimation = CurvedAnimation( + parent: _state.zoomLevelAnimationController, curve: Curves.decelerate); + _flingFocalLatLngCurvedAnimation = CurvedAnimation( + parent: _state.focalLatLngAnimationController, + curve: Curves.decelerate); + _zoomLevelCurvedAnimation = CurvedAnimation( parent: _state.zoomLevelAnimationController, curve: Curves.easeInOut); - _focalLatLngAnimation = CurvedAnimation( + _focalLatLngCurvedAnimation = CurvedAnimation( parent: _state.focalLatLngAnimationController, curve: Curves.easeInOut); _focalLatLngTween = MapLatLngTween(); _zoomLevelTween = Tween(); @@ -2891,7 +3701,7 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { void _updateSelectionTweenColors() { final Color selectionColor = _themeData.selectionColor; - _forwardSelectionColorTween.end = selectionColor; + _forwardSelectionColorTween!.end = selectionColor; _forwardSelectionStrokeColorTween.begin = _themeData.layerStrokeColor; _forwardSelectionStrokeColorTween.end = _themeData.selectionStrokeColor; @@ -2903,13 +3713,13 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { void _updateCurrentSelectedItemTween() { if (_currentSelectedItem != null && - (_state.isSublayer || !controller.wasToggled(_currentSelectedItem))) { - _forwardSelectionColorTween.begin = - getActualShapeColor(_currentSelectedItem); + (_state.isSublayer || !_controller.wasToggled(_currentSelectedItem!))) { + _forwardSelectionColorTween!.begin = + getActualShapeColor(_currentSelectedItem!); } if (_prevSelectedItem != null) { - _reverseSelectionColorTween.end = getActualShapeColor(_prevSelectedItem); + _reverseSelectionColorTween.end = getActualShapeColor(_prevSelectedItem!); } } @@ -2921,24 +3731,70 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { _reverseHoverStrokeColorTween.end = _themeData.layerStrokeColor; } + void _updateHoverItemTween() { + if (_currentHoverItem != null) { + _forwardHoverColorTween.begin = getActualShapeColor(_currentHoverItem!); + _forwardHoverColorTween.end = _getHoverFillColor(_currentHoverItem!); + } + + if (_previousHoverItem != null) { + _reverseHoverColorTween.begin = _getHoverFillColor(_previousHoverItem!); + _reverseHoverColorTween.end = getActualShapeColor(_previousHoverItem!); + } + + _state.hoverShapeAnimationController.forward(from: 0); + } + + void _initializeToggledShapeTweenColors() { + final Color? toggledShapeColor = _themeData.toggledItemColor != + Colors.transparent + ? _themeData.toggledItemColor.withOpacity(_legend!.toggledItemOpacity) + : null; + + _forwardToggledShapeColorTween.end = toggledShapeColor; + _forwardToggledShapeStrokeColorTween.begin = _themeData.layerStrokeColor; + _forwardToggledShapeStrokeColorTween.end = + _themeData.toggledItemStrokeColor != Colors.transparent + ? _themeData.toggledItemStrokeColor + : null; + + _reverseToggledShapeColorTween.begin = toggledShapeColor; + _reverseToggledShapeStrokeColorTween.begin = + _themeData.toggledItemStrokeColor != Colors.transparent + ? _themeData.toggledItemStrokeColor + : null; + _reverseToggledShapeStrokeColorTween.end = _themeData.layerStrokeColor; + } + + Color _getHoverFillColor(MapModel model) { + final bool canAdjustHoverOpacity = + double.parse(getActualShapeColor(model).opacity.toStringAsFixed(2)) != + hoverColorOpacity; + return _themeData.shapeHoverColor != null && + _themeData.shapeHoverColor != Colors.transparent + ? _themeData.shapeHoverColor! + : getActualShapeColor(model).withOpacity( + canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); + } + Color _getHoverStrokeColor() { final bool canAdjustHoverOpacity = double.parse(_themeData.layerStrokeColor.opacity.toStringAsFixed(2)) != hoverColorOpacity; return _themeData.shapeHoverStrokeColor != null && _themeData.shapeHoverStrokeColor != Colors.transparent - ? _themeData.shapeHoverStrokeColor + ? _themeData.shapeHoverStrokeColor! : _themeData.layerStrokeColor.withOpacity( canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); } - void _refresh([MapLatLng latlng]) { - if (hasSize && _mapDataSource != null && _mapDataSource.isNotEmpty) { + void _refresh([MapLatLng? latlng]) { + if (hasSize && _mapDataSource.isNotEmpty) { if (_state.isSublayer) { // For tile layer's sublayer. - if (controller.isTileLayerChild) { - _size = controller.getTileSize(); - latlng = controller.visibleFocalLatLng; + if (_controller.layerType == LayerType.tile) { + _size = _controller.getTileSize(); + latlng = _controller.visibleFocalLatLng; } // For shape layer's sublayer. else { @@ -2948,25 +3804,25 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { } } _computeActualFactor(); - controller.shapeLayerSizeFactor = _actualFactor; + _controller.shapeLayerSizeFactor = _actualFactor; if (_zoomPanBehavior != null) { - controller.shapeLayerSizeFactor *= _zoomPanBehavior.zoomLevel; + _controller.shapeLayerSizeFactor *= _zoomPanBehavior!.zoomLevel; final double inflateWidth = - _size.width * _zoomPanBehavior.zoomLevel / 2 - _size.width / 2; + _size.width * _zoomPanBehavior!.zoomLevel / 2 - _size.width / 2; final double inflateHeight = - _size.height * _zoomPanBehavior.zoomLevel / 2 - _size.height / 2; - controller.shapeLayerOrigin = Offset( + _size.height * _zoomPanBehavior!.zoomLevel / 2 - _size.height / 2; + _controller.shapeLayerOrigin = Offset( paintBounds.left - inflateWidth, paintBounds.top - inflateHeight); } - controller.shapeLayerOffset = - _getTranslationPoint(controller.shapeLayerSizeFactor); - final Offset offsetBeforeAdjust = controller.shapeLayerOffset; + _controller.shapeLayerOffset = + _getTranslationPoint(_controller.shapeLayerSizeFactor); + final Offset offsetBeforeAdjust = _controller.shapeLayerOffset; _adjustLayerOffsetTo(latlng); if (!_state.isSublayer) { - controller.shapeLayerOrigin += - controller.shapeLayerOffset - offsetBeforeAdjust; - controller.updateVisibleBounds(); + _controller.shapeLayerOrigin += + _controller.shapeLayerOffset - offsetBeforeAdjust; + _controller.updateVisibleBounds(); } _updateMapDataSourceForVisual(); @@ -2974,31 +3830,14 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { } } - void _adjustLayerOffsetTo(MapLatLng latlng) { - latlng ??= _zoomPanBehavior?.focalLatLng; - if (latlng != null) { - final Offset focalPoint = pixelFromLatLng( - latlng.latitude, - latlng.longitude, - _size, - controller.shapeLayerOffset, - controller.shapeLayerSizeFactor, - ); - final Offset center = - _getShapeBounds(controller.shapeLayerSizeFactor).center; - controller.shapeLayerOffset += - center + controller.shapeLayerOffset - focalPoint; - } - } - void _computeActualFactor() { final Offset minPoint = pixelFromLatLng( - _state.shapeFileData.bounds.minLatitude, - _state.shapeFileData.bounds.minLongitude, + _state.shapeFileData.bounds.minLatitude!, + _state.shapeFileData.bounds.minLongitude!, _size); final Offset maxPoint = pixelFromLatLng( - _state.shapeFileData.bounds.maxLatitude, - _state.shapeFileData.bounds.maxLongitude, + _state.shapeFileData.bounds.maxLatitude!, + _state.shapeFileData.bounds.maxLongitude!, _size); _actualShapeSize = Size( (maxPoint.dx - minPoint.dx).abs(), (maxPoint.dy - minPoint.dy).abs()); @@ -3006,14 +3845,16 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { _size.width / _actualShapeSize.width); } - Offset _getTranslationPoint(double factor, [Rect bounds]) { - assert(factor != null); + Offset _getTranslationPoint(double factor, [Rect? bounds]) { bounds ??= _getShapeBounds(factor); // 0.0 is default translation value. - final double dx = interpolateValue( - 0.0, _size.width - _actualShapeSize.width, -bounds.left); - final double dy = interpolateValue( - 0.0, _size.height - _actualShapeSize.height, -bounds.top); + // We cant use the clamp() directly here because the upperLimit must be + // greater than or equal to lowerLimit. This shows assert error when using. + // So, we have used the min and max mathematical function for clamping. + final double dx = + min(max(0.0, _size.width - _actualShapeSize.width), -bounds.left); + final double dy = + min(max(0.0, _size.height - _actualShapeSize.height), -bounds.top); final Size widgetSize = _state.isSublayer ? size : _size; final Offset shift = Offset( widgetSize.width - _actualShapeSize.width * factor, @@ -3023,72 +3864,88 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { Rect _getShapeBounds(double factor, [Offset translation = Offset.zero]) { final Offset minPoint = pixelFromLatLng( - _state.shapeFileData.bounds.minLatitude, - _state.shapeFileData.bounds.minLongitude, + _state.shapeFileData.bounds.minLatitude!, + _state.shapeFileData.bounds.minLongitude!, _size, translation, factor); final Offset maxPoint = pixelFromLatLng( - _state.shapeFileData.bounds.maxLatitude, - _state.shapeFileData.bounds.maxLongitude, + _state.shapeFileData.bounds.maxLatitude!, + _state.shapeFileData.bounds.maxLongitude!, _size, translation, factor); return Rect.fromPoints(minPoint, maxPoint); } + void _adjustLayerOffsetTo(MapLatLng? latlng) { + latlng ??= _zoomPanBehavior?.focalLatLng; + if (latlng != null) { + final Offset focalPoint = pixelFromLatLng( + latlng.latitude, + latlng.longitude, + _size, + _controller.shapeLayerOffset, + _controller.shapeLayerSizeFactor, + ); + final Offset center = + _getShapeBounds(_controller.shapeLayerSizeFactor).center; + _controller.shapeLayerOffset += + center + _controller.shapeLayerOffset - focalPoint; + } + } + void _updateMapDataSourceForVisual() { - if (_mapDataSource != null) { - Offset point; - Path shapePath; - dynamic coordinate; - List pixelPoints; - List rawPoints; - int rawPointsLength, pointsLength; - _mapDataSource.forEach((String key, MapModel mapModel) { - double signedArea = 0.0, centerX = 0.0, centerY = 0.0; - rawPointsLength = mapModel.rawPoints.length; - mapModel.pixelPoints = List>(rawPointsLength); - shapePath = Path(); - for (int j = 0; j < rawPointsLength; j++) { - rawPoints = mapModel.rawPoints[j]; - pointsLength = rawPoints.length; - pixelPoints = mapModel.pixelPoints[j] = List(pointsLength); - for (int k = 0; k < pointsLength; k++) { - coordinate = rawPoints[k]; - point = pixelPoints[k] = pixelFromLatLng( - coordinate[1], - coordinate[0], - _size, - controller.shapeLayerOffset, - controller.shapeLayerSizeFactor); - if (k == 0) { - shapePath.moveTo(point.dx, point.dy); - } else { - shapePath.lineTo(point.dx, point.dy); - final int l = k - 1; - if ((_state.widget.showDataLabels || - _state.widget.source.bubbleSizeMapper != null) && - l + 1 < pixelPoints.length) { - // Used mathematical formula to find - // the center of polygon points. - final double x0 = pixelPoints[l].dx, y0 = pixelPoints[l].dy; - final double x1 = pixelPoints[l + 1].dx, - y1 = pixelPoints[l + 1].dy; - signedArea += (x0 * y1) - (y0 * x1); - centerX += (x0 + x1) * (x0 * y1 - x1 * y0); - centerY += (y0 + y1) * (x0 * y1 - x1 * y0); - } + Offset point; + Path shapePath; + dynamic coordinate; + List pixelPoints; + List rawPoints; + int rawPointsLength, pointsLength; + _mapDataSource.forEach((String key, MapModel mapModel) { + double signedArea = 0.0, centerX = 0.0, centerY = 0.0; + rawPointsLength = mapModel.rawPoints.length; + mapModel.pixelPoints = List.filled(rawPointsLength, []); + shapePath = Path(); + for (int j = 0; j < rawPointsLength; j++) { + rawPoints = mapModel.rawPoints[j]; + pointsLength = rawPoints.length; + pixelPoints = + mapModel.pixelPoints![j] = List.filled(pointsLength, Offset.zero); + for (int k = 0; k < pointsLength; k++) { + coordinate = rawPoints[k]; + point = pixelPoints[k] = pixelFromLatLng( + coordinate[1], + coordinate[0], + _size, + _controller.shapeLayerOffset, + _controller.shapeLayerSizeFactor); + if (k == 0) { + shapePath.moveTo(point.dx, point.dy); + } else { + shapePath.lineTo(point.dx, point.dy); + final int l = k - 1; + if ((_state.widget.showDataLabels || + _state.widget.source.bubbleSizeMapper != null) && + l + 1 < pixelPoints.length) { + // Used mathematical formula to find + // the center of polygon points. + final double x0 = pixelPoints[l].dx, y0 = pixelPoints[l].dy; + final double x1 = pixelPoints[l + 1].dx, + y1 = pixelPoints[l + 1].dy; + signedArea += (x0 * y1) - (y0 * x1); + centerX += (x0 + x1) * (x0 * y1 - x1 * y0); + centerY += (y0 + y1) * (x0 * y1 - x1 * y0); } } - shapePath.close(); } + shapePath.close(); + } - mapModel.shapePath = shapePath; - _findPathCenterAndWidth(signedArea, centerX, centerY, mapModel); - _updateBubbleRadiusAndPath(mapModel); - }); - } + mapModel.shapePath = shapePath; + _findPathCenterAndWidth(signedArea, centerX, centerY, mapModel); + _updateBubbleRadiusAndPath(mapModel); + }); } void _findPathCenterAndWidth( @@ -3100,14 +3957,14 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { centerX = centerX / (6 * signedArea); centerY = centerY / (6 * signedArea); mapModel.shapePathCenter = Offset(centerX, centerY); - double minX, maxX; + double? minX, maxX; double distance, minDistance = double.infinity, maxDistance = double.negativeInfinity; final List minDistances = [double.infinity]; final List maxDistances = [double.negativeInfinity]; - for (final List points in mapModel.pixelPoints) { + for (final List points in mapModel.pixelPoints!) { for (final Offset point in points) { distance = (centerY - point.dy).abs(); if (point.dx < centerX) { @@ -3133,21 +3990,21 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { } } - mapModel.shapeWidth = max(maxX, maxDistances.reduce(max)) - - min(minX, minDistances.reduce(min)); + mapModel.shapeWidth = max(maxX!, maxDistances.reduce(max)) - + min(minX!, minDistances.reduce(min)); } } void _updateBubbleRadiusAndPath(MapModel mapModel) { - final double bubbleSizeValue = mapModel.bubbleSizeValue; + final double? bubbleSizeValue = mapModel.bubbleSizeValue; if (bubbleSizeValue != null) { if (bubbleSizeValue == _state.minBubbleValue) { mapModel.bubbleRadius = _bubbleSettings.minRadius; } else if (bubbleSizeValue == _state.maxBubbleValue) { mapModel.bubbleRadius = _bubbleSettings.maxRadius; } else { - final double percentage = ((bubbleSizeValue - _state.minBubbleValue) / - (_state.maxBubbleValue - _state.minBubbleValue)) * + final double percentage = ((bubbleSizeValue - _state.minBubbleValue!) / + (_state.maxBubbleValue! - _state.minBubbleValue!)) * 100; mapModel.bubbleRadius = bubbleSettings.minRadius + (bubbleSettings.maxRadius - bubbleSettings.minRadius) * @@ -3160,8 +4017,8 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { mapModel.bubblePath = Path() ..addOval( Rect.fromCircle( - center: mapModel.shapePathCenter, - radius: mapModel.bubbleRadius, + center: mapModel.shapePathCenter!, + radius: mapModel.bubbleRadius!, ), ); } @@ -3179,75 +4036,233 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { } void _handleScaleStart(ScaleStartDetails details) { - if (canZoom) { - if (controller.gesture == Gesture.scale) { + if (canZoom && + !_state.zoomLevelAnimationController.isAnimating && + !_state.focalLatLngAnimationController.isAnimating) { + if (_controller.gesture == Gesture.scale) { _zoomEnd(); } - controller.isInInteractive = true; - controller.gesture = null; - _downGlobalPoint = details.focalPoint; - _downLocalPoint = details.localFocalPoint; - _referenceVisibleBounds = - controller.getVisibleBounds(controller.shapeLayerOffset); - _referenceShapeBounds = _getShapeBounds( - controller.shapeLayerSizeFactor, controller.shapeLayerOffset); - _zoomDetails = MapZoomDetails( - newVisibleBounds: controller.getVisibleLatLngBounds( - _referenceVisibleBounds.topRight, - _referenceVisibleBounds.bottomLeft, - ), - ); - _panDetails = MapPanDetails( - newVisibleBounds: _zoomDetails.newVisibleBounds, - ); + _controller.isInInteractive = true; + _controller.gesture = null; + _startInteraction(details.localFocalPoint, details.focalPoint); } } + void _startInteraction(Offset localFocalPoint, Offset globalFocalPoint) { + _maximumReachedScaleOnInteraction = 1.0; + _downGlobalPoint = globalFocalPoint; + _downLocalPoint = localFocalPoint; + _referenceVisibleBounds = + _controller.getVisibleBounds(_controller.shapeLayerOffset); + _referenceShapeBounds = _getShapeBounds( + _controller.shapeLayerSizeFactor, _controller.shapeLayerOffset); + _zoomDetails = MapZoomDetails( + newVisibleBounds: _controller.getVisibleLatLngBounds( + _referenceVisibleBounds!.topRight, + _referenceVisibleBounds!.bottomLeft, + ), + ); + _panDetails = MapPanDetails( + newVisibleBounds: _zoomDetails!.newVisibleBounds!, + ); + } + // Scale and pan are handled in scale gesture. void _handleScaleUpdate(ScaleUpdateDetails details) { - controller.isInInteractive = true; - controller.gesture ??= - _getGestureType(details.scale, details.localFocalPoint); - if (!canZoom || controller.gesture == null) { + if (canZoom && + !_doubleTapEnabled && + !_state.zoomLevelAnimationController.isAnimating && + !_state.focalLatLngAnimationController.isAnimating) { + final double zoomLevel = _getZoomLevel(details.scale); + final double scale = _getScale(zoomLevel); + _controller.isInInteractive = true; + // Before the completion of the double tap zooming animation, when we + // zoomed or panned the _downLocalPoint will be null. So we had updated + // the current offset as interaction start offsets. + if (_downLocalPoint == null) { + _startInteraction(details.localFocalPoint, details.focalPoint); + } + + _controller.gesture ??= _getGestureType(scale, details.localFocalPoint); + if (_controller.gesture == null) { + return; + } else if (_controller.gesture == Gesture.scale && + zoomLevel == _currentZoomLevel) { + _resetDoubleTapTimer(); + return; + } + + // We have stored the [_previousMaximumScale] value to check whether + // the last fling is zoomed in or out. + if (_controller.localScale < scale) { + _maximumReachedScaleOnInteraction = scale; + } + + _resetDoubleTapTimer(); + switch (_controller.gesture!) { + case Gesture.scale: + if (_zoomPanBehavior!.enablePinching && + !_state.zoomLevelAnimationController.isAnimating && + !_state.focalLatLngAnimationController.isAnimating) { + _invokeOnZooming(scale, _downLocalPoint, _downGlobalPoint); + } + return; + case Gesture.pan: + if (_zoomPanBehavior!.enablePanning && + !_state.focalLatLngAnimationController.isAnimating && + !_state.zoomLevelAnimationController.isAnimating) { + _invokeOnPanning( + details.localFocalPoint, _downLocalPoint!, details.focalPoint); + } + return; + } + } + } + + void _handleScaleEnd(ScaleEndDetails details) { + if (_controller.gesture == null) { + _controller.isInInteractive = false; return; + } else if (_state.zoomLevelAnimationController.isAnimating || + _state.focalLatLngAnimationController.isAnimating) { + return; + } + if (_zoomPanBehavior != null && + details.velocity.pixelsPerSecond.distance >= kMinFlingVelocity) { + _resetDoubleTapTimer(); + // Calculating the end focalLatLng based on the obtained velocity. + if (_controller.gesture == Gesture.pan && + _zoomPanBehavior!.enablePanning) { + _startFlingAnimationForPanning(details); + } + // Calculating the end zoomLevel based on the obtained velocity. + else if (_controller.gesture == Gesture.scale && + _zoomPanBehavior!.enablePinching) { + _startFlingAnimationForPinching(details); + } + } else { + switch (_controller.gesture!) { + case Gesture.scale: + _zoomEnd(); + break; + case Gesture.pan: + _panEnd(); + break; + } + + _controller.gesture = null; } + } - switch (controller.gesture) { - case Gesture.scale: - _singleTapConfirmed = false; - if (_zoomPanBehavior.enablePinching && - controller.shapeLayerSizeFactor * details.scale >= _actualFactor) { - _invokeOnZooming(details.scale, _downLocalPoint, _downGlobalPoint); - } - return; - case Gesture.pan: - _singleTapConfirmed = false; - if (_zoomPanBehavior.enablePanning) { - _invokeOnPanning( - details.localFocalPoint, _downLocalPoint, details.focalPoint); - } + void _startFlingAnimationForPanning(ScaleEndDetails details) { + _isFlingAnimationActive = true; + final Offset currentPixelPoint = pixelFromLatLng( + _controller.visibleFocalLatLng!.latitude, + _controller.visibleFocalLatLng!.longitude, + _size, + _controller.shapeLayerOffset, + _controller.shapeLayerSizeFactor); + final FrictionSimulation frictionX = FrictionSimulation( + _frictionCoefficient, + currentPixelPoint.dx, + -details.velocity.pixelsPerSecond.dx, + ); + + final FrictionSimulation frictionY = FrictionSimulation( + _frictionCoefficient, + currentPixelPoint.dy, + -details.velocity.pixelsPerSecond.dy, + ); + + final MapLatLng latLng = getPixelToLatLng( + Offset(frictionX.finalX, frictionY.finalX), + _size, + _controller.shapeLayerOffset, + _controller.shapeLayerSizeFactor); + _state.focalLatLngAnimationController.duration = _getFlingAnimationDuration( + details.velocity.pixelsPerSecond.distance, _frictionCoefficient); + _controller.isInInteractive = false; + _panDistanceBeforeFlinging = _controller.panDistance; + _zoomPanBehavior!.focalLatLng = latLng; + } + + void _startFlingAnimationForPinching(ScaleEndDetails details) { + _isFlingAnimationActive = true; + final int direction = + _controller.localScale >= _maximumReachedScaleOnInteraction ? 1 : -1; + double newZoomLevel = _currentZoomLevel + + (direction * + (details.velocity.pixelsPerSecond.distance / kMaxFlingVelocity) * + _zoomPanBehavior!.maxZoomLevel); + newZoomLevel = newZoomLevel.clamp( + _zoomPanBehavior!.minZoomLevel, _zoomPanBehavior!.maxZoomLevel); + _state.zoomLevelAnimationController.duration = _getFlingAnimationDuration( + details.velocity.pixelsPerSecond.distance, _frictionCoefficient); + _controller.isInInteractive = false; + _zoomPanBehavior!.zoomLevel = newZoomLevel; + } + + // Returns the animation duration for the given distance and + // friction co-efficient. + Duration _getFlingAnimationDuration( + double distance, double frictionCoefficient) { + final int duration = + (log(10.0 / distance) / log(frictionCoefficient / 100)).round(); + final int durationInMs = (duration * 650).round(); + return Duration(milliseconds: durationInMs < 350 ? 350 : durationInMs); + } + + /// Handling zooming using mouse wheel scrolling. + void _handleScrollEvent(PointerScrollEvent event) { + if (_zoomPanBehavior != null && _zoomPanBehavior!.enablePinching) { + _controller.isInInteractive = true; + _controller.gesture ??= Gesture.scale; + if (_controller.gesture != Gesture.scale) { return; + } + + if (_currentHoverItem != null) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = null; + } + _downGlobalPoint ??= event.position; + _downLocalPoint ??= event.localPosition; + // In flutter, the default mouse wheel scroll delta value is 20. Here, the + // scale measurement was chosen at random on all devices, to feel normal + // including trackpads and mousewheels. + double scale = _controller.localScale - (event.scrollDelta.dy / 200); + if (_controller.shapeLayerSizeFactor * scale < _actualFactor) { + scale = _actualFactor / _controller.shapeLayerSizeFactor; + } + + _invokeOnZooming(scale, _downLocalPoint, _downGlobalPoint); + // When the user didn't scrolled or scaled for certain time period, + // we will refresh the map to the corresponding zoom level. + _zoomingDelayTimer?.cancel(); + _zoomingDelayTimer = Timer(const Duration(milliseconds: 250), () { + _zoomEnd(); + }); } } void _invokeOnZooming(double scale, - [Offset localFocalPoint, Offset globalFocalPoint]) { + [Offset? localFocalPoint, Offset? globalFocalPoint]) { final double newZoomLevel = _getZoomLevel(scale); final double newShapeLayerSizeFactor = _getScale(newZoomLevel); final Offset newShapeLayerOffset = - controller.getZoomingTranslation(origin: localFocalPoint); - final Rect newVisibleBounds = controller.getVisibleBounds( + _controller.getZoomingTranslation(origin: localFocalPoint); + final Rect newVisibleBounds = _controller.getVisibleBounds( newShapeLayerOffset, newShapeLayerSizeFactor); _zoomDetails = MapZoomDetails( localFocalPoint: localFocalPoint, globalFocalPoint: globalFocalPoint, - previousZoomLevel: _zoomPanBehavior.zoomLevel, + previousZoomLevel: _zoomPanBehavior!.zoomLevel, newZoomLevel: newZoomLevel, previousVisibleBounds: _zoomDetails != null - ? _zoomDetails.newVisibleBounds - : controller.visibleLatLngBounds, - newVisibleBounds: controller.getVisibleLatLngBounds( + ? _zoomDetails!.newVisibleBounds + : _controller.visibleLatLngBounds, + newVisibleBounds: _controller.getVisibleLatLngBounds( newVisibleBounds.topRight, newVisibleBounds.bottomLeft, newShapeLayerOffset, @@ -3255,9 +4270,118 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { ), ); if (_state.widget.onWillZoom == null || - _state.widget.onWillZoom(_zoomDetails)) { - _zoomPanBehavior?.onZooming(_zoomDetails); + _state.widget.onWillZoom!(_zoomDetails!)) { + _zoomPanBehavior?.onZooming(_zoomDetails!); + } + } + + void _handleZooming(MapZoomDetails details) { + if (_state.isSublayer) { + markNeedsPaint(); + return; + } + + if (_controller.isInInteractive && details.localFocalPoint != null) { + // Updating map while pinching and scrolling. + _controller.localScale = _getScale(details.newZoomLevel!); + _controller.pinchCenter = details.localFocalPoint!; + _controller.updateVisibleBounds( + _controller.getZoomingTranslation() + _controller.normalize, + _controller.shapeLayerSizeFactor * _controller.localScale); + _validateEdges(details.newZoomLevel!); + } else if (!_doubleTapEnabled) { + // Updating map via toolbar. + _downLocalPoint = null; + _downGlobalPoint = null; + _isZoomedUsingToolbar = true; + } + _zoomPanBehavior!.zoomLevel = details.newZoomLevel!; + } + + void _handleZoomLevelChange(double zoomLevel, {MapLatLng? latlng}) { + if (_controller.isInInteractive && + !_state.focalLatLngAnimationController.isAnimating && + !_state.zoomLevelAnimationController.isAnimating) { + _currentZoomLevel = zoomLevel; + markNeedsPaint(); + } else if (_zoomPanBehavior!.zoomLevel != _currentZoomLevel) { + if (!_isFlingAnimationActive && !_doubleTapEnabled) { + _state.zoomLevelAnimationController.duration = + const Duration(milliseconds: 650); + } + _zoomLevelTween.begin = _currentZoomLevel; + _zoomLevelTween.end = _zoomPanBehavior!.zoomLevel; + if (!_isFlingAnimationActive && !_doubleTapEnabled) { + _downLocalPoint = pixelFromLatLng( + _controller.visibleFocalLatLng!.latitude, + _controller.visibleFocalLatLng!.longitude, + _size, + _controller.shapeLayerOffset, + _controller.shapeLayerSizeFactor); + } + _controller.isInInteractive = true; + _controller.gesture = Gesture.scale; + _controller.pinchCenter = _downLocalPoint!; + _state.zoomLevelAnimationController.forward(from: 0.0); + } + } + + void _handleZoomLevelAnimation() { + if (_zoomLevelTween.end != null) { + _currentZoomLevel = _zoomLevelTween.evaluate(_isFlingAnimationActive + ? _flingZoomLevelCurvedAnimation + : _zoomLevelCurvedAnimation!); + } + _controller.localScale = _getScale(_currentZoomLevel); + _controller.updateVisibleBounds( + _controller.getZoomingTranslation() + _controller.normalize, + _controller.shapeLayerSizeFactor * _controller.localScale); + _validateEdges(_currentZoomLevel); + _controller.notifyRefreshListeners(); + markNeedsPaint(); + } + + void _handleZoomLevelAnimationStatusChange(AnimationStatus status) { + if (status == AnimationStatus.completed && _zoomLevelTween.end != null) { + _handleZoomingAnimationEnd(); + } + } + + void _handleZoomingAnimationEnd() { + _isFlingAnimationActive = false; + _zoomEnd(); + if (!_isZoomedUsingToolbar && !_doubleTapEnabled) { + _invokeOnZooming(_getScale(_currentZoomLevel)); + } + _isZoomedUsingToolbar = false; + _doubleTapEnabled = false; + } + + void _zoomEnd() { + _controller.isInInteractive = false; + _controller.gesture = null; + _zoomingDelayTimer?.cancel(); + _zoomingDelayTimer = null; + _zoomDetails = null; + _panDetails = null; + if (_zoomPanBehavior != null && + _zoomPanBehavior!.enablePinching && + !_state.isSublayer) { + _controller.shapeLayerOffset = + _controller.getZoomingTranslation() + _controller.normalize; + _controller.shapeLayerOrigin = _controller.getZoomingTranslation( + previousOrigin: _controller.shapeLayerOrigin) + + _controller.normalize; + _controller.shapeLayerSizeFactor *= _controller.localScale; + _updateMapDataSourceForVisual(); + _controller.notifyRefreshListeners(); + markNeedsPaint(); } + + _downLocalPoint = null; + _downGlobalPoint = null; + _controller.normalize = Offset.zero; + _controller.localScale = 1.0; } void _invokeOnPanning( @@ -3266,388 +4390,364 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { _avoidPanUpdate = canAvoidPanUpdate; final Offset delta = _getValidPanDelta(localFocalPoint - previousFocalPoint); - final Rect visibleBounds = controller.getVisibleBounds( - controller.shapeLayerOffset + + final Rect visibleBounds = _controller.getVisibleBounds( + _controller.shapeLayerOffset + (canAvoidPanUpdate ? Offset.zero : delta)); _panDetails = MapPanDetails( globalFocalPoint: focalPoint, localFocalPoint: localFocalPoint, - zoomLevel: _zoomPanBehavior.zoomLevel, + zoomLevel: _zoomPanBehavior!.zoomLevel, delta: delta, previousVisibleBounds: _panDetails != null - ? _panDetails.newVisibleBounds - : controller.visibleLatLngBounds, - newVisibleBounds: controller.getVisibleLatLngBounds( + ? _panDetails!.newVisibleBounds + : _controller.visibleLatLngBounds, + newVisibleBounds: _controller.getVisibleLatLngBounds( visibleBounds.topRight, visibleBounds.bottomLeft, - controller.shapeLayerOffset + + _controller.shapeLayerOffset + (canAvoidPanUpdate ? Offset.zero : delta)), ); if (_state.widget.onWillPan == null || - _state.widget.onWillPan(_panDetails)) { - _zoomPanBehavior?.onPanning(_panDetails); + _state.widget.onWillPan!(_panDetails!)) { + _zoomPanBehavior?.onPanning(_panDetails!); } } - Offset _getValidPanDelta(Offset delta) { - final Rect currentShapeBounds = _getShapeBounds( - controller.shapeLayerSizeFactor, controller.shapeLayerOffset + delta); - double dx = 0.0, dy = 0.0; - if (_referenceVisibleBounds.width < _referenceShapeBounds.width) { - dx = delta.dx; - if (currentShapeBounds.left > _referenceVisibleBounds.left) { - dx = _referenceVisibleBounds.left - _referenceShapeBounds.left; - } - - if (currentShapeBounds.right < _referenceVisibleBounds.right) { - dx = _referenceVisibleBounds.right - _referenceShapeBounds.right; - } + void _handlePanning(MapPanDetails details) { + if (_avoidPanUpdate) { + _avoidPanUpdate = false; + return; } - if (_referenceVisibleBounds.height < _referenceShapeBounds.height) { - dy = delta.dy; - if (currentShapeBounds.top > _referenceVisibleBounds.top) { - dy = _referenceVisibleBounds.top - _referenceShapeBounds.top; - } + if (_currentHoverItem != null) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = null; + } - if (currentShapeBounds.bottom < _referenceVisibleBounds.bottom) { - dy = _referenceVisibleBounds.bottom - _referenceShapeBounds.bottom; - } + if (!_state.isSublayer) { + _controller.panDistance = details.delta!; + _controller + .updateVisibleBounds(_controller.shapeLayerOffset + details.delta!); } - return Offset(dx, dy); + markNeedsPaint(); } - Gesture _getGestureType(double scale, Offset point) { - if (scale == 1) { - if (_downLocalPoint != null) { - final Offset distance = point - _downLocalPoint; - return distance.dx.abs() > _minPanDistance || - distance.dy.abs() > _minPanDistance - ? Gesture.pan - : null; + void _handleFocalLatLngChange(MapLatLng? latlng) { + if (!_controller.isInInteractive || + _controller.visibleFocalLatLng != _zoomPanBehavior!.focalLatLng) { + if (!_isFlingAnimationActive) { + _state.focalLatLngAnimationController.duration = + const Duration(milliseconds: 650); } - } - return Gesture.scale; - } + if (_state.focalLatLngAnimationController.isAnimating) { + _panDistanceBeforeFlinging = _controller.panDistance; + } - Offset _getNormalizedOffset(double zoomLevel) { - double dx = 0.0, dy = 0.0; - final Rect currentBounds = controller.currentBounds; - if (currentBounds.left > paintBounds.left) { - dx = paintBounds.left - currentBounds.left; + _focalLatLngTween.begin = _controller.visibleFocalLatLng; + _focalLatLngTween.end = _zoomPanBehavior!.focalLatLng; + _downLocalPoint = pixelFromLatLng( + _controller.visibleFocalLatLng!.latitude, + _controller.visibleFocalLatLng!.longitude, + _size, + _controller.shapeLayerOffset + _panDistanceBeforeFlinging, + _controller.shapeLayerSizeFactor); + _referenceVisibleBounds = + _controller.getVisibleBounds(_controller.shapeLayerOffset); + _referenceShapeBounds = _getShapeBounds( + _controller.shapeLayerSizeFactor, _controller.shapeLayerOffset); + _controller.isInInteractive = true; + _controller.gesture = Gesture.pan; + _state.focalLatLngAnimationController.forward(from: 0.0); } + } - if (currentBounds.right < paintBounds.right) { - dx = paintBounds.right - currentBounds.right; - } + void _handleFocalLatLngAnimation() { + final MapLatLng latLng = _focalLatLngTween.evaluate(_isFlingAnimationActive + ? _flingFocalLatLngCurvedAnimation + : _focalLatLngCurvedAnimation); - if (currentBounds.top > paintBounds.top) { - dy = paintBounds.top - currentBounds.top; - } + final Offset localFocalPoint = pixelFromLatLng( + latLng.latitude, + latLng.longitude, + _size, + _controller.shapeLayerOffset + _panDistanceBeforeFlinging, + _controller.shapeLayerSizeFactor); + final Offset delta = _getValidPanDelta(_downLocalPoint! - localFocalPoint); + _controller.panDistance = _panDistanceBeforeFlinging + delta; + _controller.updateVisibleBounds( + _controller.shapeLayerOffset + _panDistanceBeforeFlinging + delta); + _controller.notifyRefreshListeners(); + markNeedsPaint(); + } - if (currentBounds.bottom < paintBounds.bottom) { - dy = paintBounds.bottom - currentBounds.bottom; + void _handleFocalLatLngAnimationStatusChange(AnimationStatus status) { + if (status == AnimationStatus.completed && _focalLatLngTween.end != null) { + _handleFocalLatLngAnimationEnd(); } - - return Offset(dx, dy); } - void _validateEdges(double zoomLevel, [Offset origin]) { - final Offset leftTop = controller.getZoomingTranslation( - origin: origin, - scale: _getScale(zoomLevel), - previousOrigin: controller.shapeLayerOrigin); - controller.currentBounds = Rect.fromLTWH(leftTop.dx, leftTop.dy, - _size.width * zoomLevel, _size.height * zoomLevel); - controller.normalize = _getNormalizedOffset(zoomLevel); + void _handleFocalLatLngAnimationEnd() { + _isFlingAnimationActive = false; + _panEnd(); + _referenceVisibleBounds = + _controller.getVisibleBounds(_controller.shapeLayerOffset); + _referenceShapeBounds = _getShapeBounds( + _controller.shapeLayerSizeFactor, _controller.shapeLayerOffset); + final Offset localFocalPoint = pixelFromLatLng( + _controller.visibleFocalLatLng!.latitude, + _controller.visibleFocalLatLng!.longitude, + _size, + _controller.shapeLayerOffset, + _controller.shapeLayerSizeFactor); + final Offset previousFocalPoint = pixelFromLatLng( + _focalLatLngTween.begin!.latitude, + _focalLatLngTween.begin!.longitude, + _size, + _controller.shapeLayerOffset, + _controller.shapeLayerSizeFactor); + _invokeOnPanning(localFocalPoint, previousFocalPoint, + localToGlobal(localFocalPoint), true); } - void _handleZooming(MapZoomDetails details) { - if (_state.isSublayer) { + void _panEnd() { + _controller.isInInteractive = false; + _zoomDetails = null; + _panDetails = null; + _panDistanceBeforeFlinging = Offset.zero; + if (_zoomPanBehavior!.enablePanning && !_state.isSublayer) { + _controller.shapeLayerOffset += _controller.panDistance; + _controller.shapeLayerOrigin += _controller.panDistance; + _updateMapDataSourceForVisual(); + _controller.notifyRefreshListeners(); markNeedsPaint(); - return; } - if (controller.isInInteractive && details.localFocalPoint != null) { - // Updating map while pinching and scrolling. - controller.localScale = _getScale(details.newZoomLevel); - controller.pinchCenter = details.localFocalPoint; - controller.updateVisibleBounds( - controller.getZoomingTranslation() + controller.normalize, - controller.shapeLayerSizeFactor * controller.localScale); - _validateEdges(details.newZoomLevel); - } else { - // Updating map via toolbar. - _downLocalPoint = null; - _downGlobalPoint = null; - _isZoomedUsingToolbar = true; - } - _zoomPanBehavior.zoomLevel = details.newZoomLevel; + _referenceVisibleBounds = null; + _referenceShapeBounds = null; + _downLocalPoint = null; + _downGlobalPoint = null; + _controller.gesture = null; + _controller.panDistance = Offset.zero; } - void _handlePanning(MapPanDetails details) { - if (_avoidPanUpdate) { - _avoidPanUpdate = false; - return; + void _handleFlingAnimations() { + if (_state.zoomLevelAnimationController.isAnimating && !_doubleTapEnabled) { + _state.zoomLevelAnimationController.stop(); + _isZoomedUsingToolbar = false; + _handleZoomingAnimationEnd(); } - - if (_currentHoverItem != null) { - _previousHoverItem = _currentHoverItem; - _currentHoverItem = null; + if (_state.focalLatLngAnimationController.isAnimating) { + _state.focalLatLngAnimationController.stop(); + _handleFocalLatLngAnimationEnd(); } + } - if (!_state.isSublayer) { - controller.panDistance = details.delta; - controller - .updateVisibleBounds(controller.shapeLayerOffset + details.delta); + void _handleRefresh() { + if (_state.isSublayer) { + _refresh(); } + } - markNeedsPaint(); + void _handleReset() { + _zoomPanBehavior!.zoomLevel = _zoomPanBehavior!.minZoomLevel; } - void _handleScaleEnd(ScaleEndDetails details) { - if (controller.gesture == null) { - controller.isInInteractive = false; - return; - } + void _handleTapUp(TapUpDetails details) { + _handleTap(details.localPosition, PointerDeviceKind.touch); + } - switch (controller.gesture) { - case Gesture.scale: - _zoomEnd(); - break; - case Gesture.pan: - _panEnd(); - break; + void _handleTap(Offset position, PointerDeviceKind deviceKind) { + _invokeSelectionChangedCallback(_currentInteractedItem); + if (_currentInteractedItem != null && + deviceKind != PointerDeviceKind.mouse) { + _invokeTooltip( + position: position, + model: _currentInteractedItem, + element: _currentInteractedElement!); } - controller.gesture = null; + _downLocalPoint = null; + _downGlobalPoint = null; + if (_currentSelectedItem != null) { + _currentHoverItem = null; + } } - void _handleRefresh() { - if (_state.isSublayer) { - _refresh(); + void _handleDoubleTap() { + if (_controller.gesture == null && _zoomPanBehavior != null) { + double newZoomLevel = _currentZoomLevel + 1; + newZoomLevel = newZoomLevel.clamp( + _zoomPanBehavior!.minZoomLevel, _zoomPanBehavior!.maxZoomLevel); + if (newZoomLevel == _currentZoomLevel) { + return; + } + + _state.zoomLevelAnimationController.duration = + const Duration(milliseconds: 200); + _doubleTapEnabled = true; + // Based on the isInInteractive value we have updated the maps at + // _handleZooming(). To avoid this at double tap zooming, we have reset + // the isInInteractive. + _controller.isInInteractive = false; + _invokeOnZooming( + _getScale(newZoomLevel), _downLocalPoint, _downGlobalPoint); } } - void _zoomEnd() { - controller.isInInteractive = false; - controller.gesture = null; - _zoomingDelayTimer?.cancel(); - _zoomingDelayTimer = null; - _zoomDetails = null; - _panDetails = null; - if (_zoomPanBehavior != null && - _zoomPanBehavior.enablePinching && - !_state.isSublayer) { - controller.shapeLayerOffset = - controller.getZoomingTranslation() + controller.normalize; - controller.shapeLayerOrigin = controller.getZoomingTranslation( - previousOrigin: controller.shapeLayerOrigin) + - controller.normalize; - controller.shapeLayerSizeFactor *= controller.localScale; - _updateMapDataSourceForVisual(); - controller.notifyRefreshListeners(); - markNeedsPaint(); + void _resetDoubleTapTimer() { + _pointerCount = 0; + if (_doubleTapTimer != null) { + _doubleTapTimer?.cancel(); + _doubleTapTimer = null; } + } - _downLocalPoint = null; - _downGlobalPoint = null; - controller.normalize = Offset.zero; - controller.localScale = 1.0; + void _handleHover(PointerHoverEvent event) { + // ignore: avoid_as + final RenderBox renderBox = context.findRenderObject() as RenderBox; + final Offset localPosition = renderBox.globalToLocal(event.position); + _prevSelectedItem = null; + _performChildHover(localPosition); } - void _panEnd() { - controller.isInInteractive = false; - _zoomDetails = null; - _panDetails = null; - if (_zoomPanBehavior.enablePanning && !_state.isSublayer) { - controller.shapeLayerOffset += controller.panDistance; - controller.shapeLayerOrigin += controller.panDistance; - _updateMapDataSourceForVisual(); - controller.notifyRefreshListeners(); - markNeedsPaint(); + void _handleExit(PointerExitEvent event) { + if (_state.widget.source.bubbleSizeMapper != null && hasBubbleHoverColor) { + final ShapeLayerChildRenderBoxBase bubbleRenderObject = + _state.bubbleKey.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; + bubbleRenderObject.onExit(); } - _downLocalPoint = null; - _downGlobalPoint = null; - controller.gesture = null; - controller.panDistance = Offset.zero; + if (hasShapeHoverColor && _currentHoverItem != null) { + _previousHoverItem = _currentHoverItem; + _currentHoverItem = null; + _updateHoverItemTween(); + } + + // In sublayer, we have updated [hitTestSelf] as true only if the cursor + // position lies inside a shape. If not, we will make it as false. + // When setting false to [hitTestSelf], the framework will invoke the + // [_handleExit] method in desktop. To hide the previous rendered tooltip, + // we had passed the null value for model. + if ((_state.widget.shapeTooltipBuilder != null || + _state.widget.bubbleTooltipBuilder != null)) { + final ShapeLayerChildRenderBoxBase tooltipRenderObject = + _controller.tooltipKey!.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; + tooltipRenderObject.hideTooltip(); + } } - /// Handling zooming using mouse wheel scrolling. - void _handleScrollEvent(PointerScrollEvent event) { - if (_zoomPanBehavior != null && _zoomPanBehavior.enablePinching) { - controller.isInInteractive = true; - controller.gesture ??= Gesture.scale; - if (controller.gesture != Gesture.scale) { - return; + Gesture? _getGestureType(double scale, Offset point) { + if (scale == 1) { + if (_downLocalPoint != null) { + final Offset distance = point - _downLocalPoint!; + return distance.dx.abs() > _minPanDistance || + distance.dy.abs() > _minPanDistance + ? Gesture.pan + : null; } + } - if (_currentHoverItem != null) { - _previousHoverItem = _currentHoverItem; - _currentHoverItem = null; - } - _downGlobalPoint ??= event.position; - _downLocalPoint ??= event.localPosition; - double scale = controller.localScale - (event.scrollDelta.dy / 60); - if (controller.shapeLayerSizeFactor * scale < _actualFactor) { - scale = _actualFactor / controller.shapeLayerSizeFactor; + return Gesture.scale; + } + + Offset _getValidPanDelta(Offset delta) { + final Rect currentShapeBounds = _getShapeBounds( + _controller.shapeLayerSizeFactor, _controller.shapeLayerOffset + delta); + double dx = 0.0, dy = 0.0; + if (_referenceVisibleBounds!.width < _referenceShapeBounds!.width) { + dx = delta.dx; + if (currentShapeBounds.left > _referenceVisibleBounds!.left) { + dx = _referenceVisibleBounds!.left - _referenceShapeBounds!.left; } - _invokeOnZooming(scale, _downLocalPoint, _downGlobalPoint); - // When the user didn't scrolled or scaled for certain time period, - // we will refresh the map to the corresponding zoom level. - _zoomingDelayTimer?.cancel(); - _zoomingDelayTimer = Timer(const Duration(milliseconds: 250), () { - _zoomEnd(); - }); + if (currentShapeBounds.right < _referenceVisibleBounds!.right) { + dx = _referenceVisibleBounds!.right - _referenceShapeBounds!.right; + } } - } - void _handleZoomLevelChange(double zoomLevel, {MapLatLng latlng}) { - if (controller.isInInteractive && - !_state.zoomLevelAnimationController.isAnimating) { - _currentZoomLevel = zoomLevel; - markNeedsPaint(); - } else { - _zoomLevelTween.begin = _currentZoomLevel; - _zoomLevelTween.end = _zoomPanBehavior.zoomLevel; - _downLocalPoint = pixelFromLatLng( - controller.visibleFocalLatLng.latitude, - controller.visibleFocalLatLng.longitude, - size, - controller.shapeLayerOffset, - controller.shapeLayerSizeFactor); - controller.isInInteractive = true; - controller.gesture = Gesture.scale; - controller.pinchCenter = _downLocalPoint; - _state.zoomLevelAnimationController.forward(from: 0.0); - } - } + if (_referenceVisibleBounds!.height < _referenceShapeBounds!.height) { + dy = delta.dy; + if (currentShapeBounds.top > _referenceVisibleBounds!.top) { + dy = _referenceVisibleBounds!.top - _referenceShapeBounds!.top; + } - void _handlePanTo(MapLatLng latlng) { - if (!controller.isInInteractive) { - _focalLatLngTween.begin = controller.visibleFocalLatLng; - _focalLatLngTween.end = _zoomPanBehavior.focalLatLng; - _state.focalLatLngAnimationController.forward(from: 0.0); + if (currentShapeBounds.bottom < _referenceVisibleBounds!.bottom) { + dy = _referenceVisibleBounds!.bottom - _referenceShapeBounds!.bottom; + } } - } - void _handleReset() { - _zoomPanBehavior.zoomLevel = _zoomPanBehavior.minZoomLevel; + return Offset(dx, dy); } - void _handleZoomLevelAnimation() { - if (_zoomLevelTween.end != null) { - _currentZoomLevel = _zoomLevelTween.evaluate(_zoomLevelAnimation); - } - controller.localScale = _getScale(_currentZoomLevel); - controller.updateVisibleBounds( - controller.getZoomingTranslation() + controller.normalize, - controller.shapeLayerSizeFactor * controller.localScale); - _validateEdges(_currentZoomLevel); - controller.notifyRefreshListeners(); - markNeedsPaint(); + void _validateEdges(double zoomLevel, [Offset? origin]) { + final Offset leftTop = _controller.getZoomingTranslation( + origin: origin, + scale: _getScale(zoomLevel), + previousOrigin: _controller.shapeLayerOrigin); + _controller.currentBounds = Rect.fromLTWH(leftTop.dx, leftTop.dy, + _size.width * zoomLevel, _size.height * zoomLevel); + _controller.normalize = _getNormalizedOffset(zoomLevel); } - void _handleFocalLatLngAnimation() { - if (_focalLatLngTween.end != null) { - controller.visibleFocalLatLng = - _focalLatLngTween.evaluate(_focalLatLngAnimation); + Offset _getNormalizedOffset(double zoomLevel) { + double dx = 0.0, dy = 0.0; + final Rect currentBounds = _controller.currentBounds; + if (currentBounds.left > paintBounds.left) { + dx = paintBounds.left - currentBounds.left; } - _handleZoomPanAnimation(); - } - void _handleZoomPanAnimation() { - _validateEdges( - _currentZoomLevel, Offset(_size.width / 2, _size.height / 2)); - controller.shapeLayerOrigin = controller.getZoomingTranslation( - origin: Offset(_size.width / 2, _size.height / 2), - scale: _getScale(_currentZoomLevel), - previousOrigin: controller.shapeLayerOrigin) + - controller.normalize; - controller.shapeLayerSizeFactor = _actualFactor * _currentZoomLevel; - controller.shapeLayerOffset = - _getTranslationPoint(controller.shapeLayerSizeFactor) + - controller.normalize; - if (_currentZoomLevel != 1) { - _adjustLayerOffsetTo(controller.visibleFocalLatLng); - } - - controller.updateVisibleBounds(); - _updateMapDataSourceForVisual(); - controller.notifyRefreshListeners(); - markNeedsPaint(); - } + if (currentBounds.right < paintBounds.right) { + dx = paintBounds.right - currentBounds.right; + } - void _handleExit(PointerExitEvent event) { - if (_state.widget.source.bubbleSizeMapper != null && hasBubbleHoverColor) { - final ShapeLayerChildRenderBoxBase bubbleRenderObject = - _state.bubbleKey.currentContext.findRenderObject(); - bubbleRenderObject.onExit(); + if (currentBounds.top > paintBounds.top) { + dy = paintBounds.top - currentBounds.top; } - if (hasShapeHoverColor && _currentHoverItem != null) { - _previousHoverItem = _currentHoverItem; - _currentHoverItem = null; - _updateHoverItemTween(); + if (currentBounds.bottom < paintBounds.bottom) { + dy = paintBounds.bottom - currentBounds.bottom; } - // In sublayer, we have updated [hitTestSelf] as true only if the cursor - // position lies inside a shape. If not, we will make it as false. - // When setting false to [hitTestSelf], the framework will invoke the - // [_handleExit] method in desktop. To hide the previous rendered tooltip, - // we had passed the null value for model. - _invokeTooltip(); + return Offset(dx, dy); } double _getZoomLevel(double scale) { - return interpolateValue( - controller.shapeLayerSizeFactor * scale / _actualFactor, - _zoomPanBehavior.minZoomLevel, - _zoomPanBehavior.maxZoomLevel, + return (_controller.shapeLayerSizeFactor * scale / _actualFactor).clamp( + _zoomPanBehavior!.minZoomLevel, + _zoomPanBehavior!.maxZoomLevel, ); } double _getScale(double zoomLevel) { - return _actualFactor * zoomLevel / controller.shapeLayerSizeFactor; - } - - void _handleTapUp(Offset localPosition) { - _handleInteraction(localPosition); - if (_currentSelectedItem != null) { - _currentHoverItem = null; - } - } - - void _handleHover(PointerHoverEvent event) { - final RenderBox renderBox = context.findRenderObject(); - final Offset localPosition = renderBox.globalToLocal(event.position); - _handleInteraction(localPosition, isHover: true); + return _actualFactor * zoomLevel / _controller.shapeLayerSizeFactor; } bool _isElementLiesOnPosition(Offset position) { - if (!isInteractive && (_mapDataSource == null || _mapDataSource.isEmpty)) { + if (!isInteractive && _mapDataSource.isEmpty) { return false; } - double bubbleRadius; + double? bubbleRadius; _currentInteractedItem = null; _currentInteractedElement = null; for (final MapModel mapModel in _mapDataSource.values) { - final bool wasToggled = controller.wasToggled(mapModel); + final bool wasToggled = _controller.wasToggled(mapModel); if (_isBubbleContains(position, mapModel)) { _currentInteractedElement = MapLayerElement.bubble; if (!wasToggled && - (bubbleRadius == null || mapModel.bubbleRadius < bubbleRadius)) { + (bubbleRadius == null || mapModel.bubbleRadius! < bubbleRadius)) { bubbleRadius = mapModel.bubbleRadius; _currentInteractedItem = mapModel; } } else if (_isShapeContains( position, mapModel, _currentInteractedElement) && - !(wasToggled && _state.widget.legend.source == MapElement.shape)) { + !(wasToggled && _state.widget.legend!.source == MapElement.shape)) { _currentInteractedItem = mapModel; _currentInteractedElement = MapLayerElement.shape; if (!(_state.widget.bubbleTooltipBuilder != null || @@ -3660,59 +4760,36 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { return _currentInteractedItem != null && _currentInteractedElement != null; } - void _handleInteraction(Offset position, {bool isHover = false}) { - if (isHover) { - _prevSelectedItem = null; - _performChildHover(position); - } else { - _invokeSelectionChangedCallback(_currentInteractedItem); - _performChildTap(position); - } - } - bool _isBubbleContains(Offset position, MapModel mapModel) { return (_state.widget.bubbleTooltipBuilder != null || hasBubbleHoverColor) && mapModel.bubblePath != null && - mapModel.bubblePath.contains(position); + mapModel.bubblePath!.contains(position); } bool _isShapeContains( - Offset position, MapModel mapModel, MapLayerElement element) { + Offset position, MapModel mapModel, MapLayerElement? element) { return (_state.widget.onSelectionChanged != null || _state.widget.shapeTooltipBuilder != null || hasShapeHoverColor) && element != MapLayerElement.bubble && - mapModel.shapePath.contains(position); - } - - void _invokeSelectionChangedCallback(MapModel mapModel) { - if (_state.widget.onSelectionChanged != null && - mapModel != null && - mapModel.dataIndex != null) { - _state.widget.onSelectionChanged(mapModel.dataIndex); - } - } - - void _performChildTap(Offset position) { - if (_currentInteractedItem != null) { - _invokeTooltip( - position: position, - model: _currentInteractedItem, - element: _currentInteractedElement); - } + mapModel.shapePath!.contains(position); } void _performChildHover(Offset position) { + final MapModel? currentInteractedItem = _currentInteractedItem; _invokeTooltip( position: position, model: _currentInteractedItem, - element: _currentInteractedElement); + element: _currentInteractedElement, + kind: PointerKind.hover); if (_state.widget.source.bubbleSizeMapper != null) { final ShapeLayerChildRenderBoxBase bubbleRenderObject = - _state.bubbleKey.currentContext.findRenderObject(); + _state.bubbleKey.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; bubbleRenderObject.onHover( - _currentInteractedItem, _currentInteractedElement); + currentInteractedItem, _currentInteractedElement); } if (hasShapeHoverColor && @@ -3734,71 +4811,58 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { } } + void _invokeSelectionChangedCallback(MapModel? mapModel) { + if (_state.widget.onSelectionChanged != null && + mapModel != null && + mapModel.dataIndex != null) { + _state.widget.onSelectionChanged!(mapModel.dataIndex!); + } + } + void _invokeTooltip( - {MapModel model, Offset position, MapLayerElement element}) { + {MapModel? model, + Offset? position, + MapLayerElement? element, + PointerKind kind = PointerKind.touch}) { if ((_state.widget.shapeTooltipBuilder != null || _state.widget.bubbleTooltipBuilder != null)) { - Rect elementRect; + Rect? elementRect; final ShapeLayerChildRenderBoxBase tooltipRenderObject = - controller.tooltipKey.currentContext.findRenderObject(); + _controller.tooltipKey!.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; if (model != null && element == MapLayerElement.bubble) { elementRect = Rect.fromCircle( - center: model.shapePathCenter, radius: model.bubbleRadius); + center: model.shapePathCenter!, radius: model.bubbleRadius!); } - int sublayerIndex; + int? sublayerIndex; if (_state.isSublayer) { - final RenderSublayerContainer sublayerContainer = parent; sublayerIndex = - sublayerContainer.getSublayerIndex(_state.widget.sublayer); + _state.ancestor.sublayers!.indexOf(_state.widget.sublayerAncestor!); } // The elementRect is not applicable, if the actual element is shape. The // sublayerIndex is not applicable, if the actual layer is shape layer. - tooltipRenderObject.paintTooltip( - model?.dataIndex, elementRect, element, sublayerIndex, position); - } - } - - void _updateHoverItemTween() { - if (_currentHoverItem != null) { - _forwardHoverColorTween.begin = getActualShapeColor(_currentHoverItem); - _forwardHoverColorTween.end = _getHoverFillColor(_currentHoverItem); - } - - if (_previousHoverItem != null) { - _reverseHoverColorTween.begin = _getHoverFillColor(_previousHoverItem); - _reverseHoverColorTween.end = getActualShapeColor(_previousHoverItem); + tooltipRenderObject.paintTooltip(model?.dataIndex, elementRect, element, + kind, sublayerIndex, position); } - - _state.hoverShapeAnimationController.forward(from: 0); - } - - Color _getHoverFillColor(MapModel model) { - final bool canAdjustHoverOpacity = - double.parse(getActualShapeColor(model).opacity.toStringAsFixed(2)) != - hoverColorOpacity; - return _themeData.shapeHoverColor != null && - _themeData.shapeHoverColor != Colors.transparent - ? _themeData.shapeHoverColor - : getActualShapeColor(model).withOpacity( - canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); } void _handleShapeLayerSelection() { - assert(_selectedIndex < _mapSource.dataCount); + assert(_selectedIndex < _mapSource!.dataCount); _prevSelectedItem = _currentSelectedItem; if (_selectedIndex == -1) { if (_prevSelectedItem != null) { - _prevSelectedItem.isSelected = false; + _prevSelectedItem!.isSelected = false; } _currentSelectedItem = null; } else { _currentSelectedItem = _mapDataSource.values.firstWhere( (MapModel element) => element.dataIndex == _selectedIndex); - _currentSelectedItem.isSelected = !_currentSelectedItem.isSelected; + _currentSelectedItem!.isSelected = !_currentSelectedItem!.isSelected; if (_prevSelectedItem != null) { - _prevSelectedItem.isSelected = false; + _prevSelectedItem!.isSelected = false; } } @@ -3806,40 +4870,19 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { _state.selectionAnimationController.forward(from: 0); } - void _initializeToggledShapeTweenColors() { - final Color toggledShapeColor = _themeData.toggledItemColor != - Colors.transparent - ? _themeData.toggledItemColor.withOpacity(_legend.toggledItemOpacity) - : null; - - _forwardToggledShapeColorTween.end = toggledShapeColor; - _forwardToggledShapeStrokeColorTween.begin = _themeData.layerStrokeColor; - _forwardToggledShapeStrokeColorTween.end = - _themeData.toggledItemStrokeColor != Colors.transparent - ? _themeData.toggledItemStrokeColor - : null; - - _reverseToggledShapeColorTween.begin = toggledShapeColor; - _reverseToggledShapeStrokeColorTween.begin = - _themeData.toggledItemStrokeColor != Colors.transparent - ? _themeData.toggledItemStrokeColor - : null; - _reverseToggledShapeStrokeColorTween.end = _themeData.layerStrokeColor; - } - void _handleToggleChange() { _previousHoverItem = null; if (_state.widget.legend != null && - _state.widget.legend.source == MapElement.shape) { - MapModel model; + _state.widget.legend!.source == MapElement.shape) { + late MapModel model; if (_state.widget.source.shapeColorMappers == null) { model = - mapDataSource.values.elementAt(controller.currentToggledItemIndex); + mapDataSource.values.elementAt(_controller.currentToggledItemIndex); } else { for (final mapModel in _mapDataSource.values) { if (mapModel.dataIndex != null && mapModel.legendMapperIndex == - controller.currentToggledItemIndex) { + _controller.currentToggledItemIndex) { model = mapModel; break; } @@ -3847,7 +4890,7 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { } final Color shapeColor = (_currentSelectedItem != null && - _currentSelectedItem.actualIndex == model.actualIndex) + _currentSelectedItem!.actualIndex == model.actualIndex) ? _themeData.selectionColor : getActualShapeColor(model); _forwardToggledShapeColorTween.begin = shapeColor; @@ -3856,103 +4899,56 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { } } - void _handleZoomLevelAnimationStatusChange(AnimationStatus status) { - if (status == AnimationStatus.completed && _zoomLevelTween.end != null) { - _zoomEnd(); - if (!_isZoomedUsingToolbar) { - _invokeOnZooming(_getScale(_zoomPanBehavior.zoomLevel)); - } - _isZoomedUsingToolbar = false; - } - } - - void _handleFocalLatLngAnimationStatusChange(AnimationStatus status) { - if (status == AnimationStatus.completed && _focalLatLngTween.end != null) { - _referenceVisibleBounds = - controller.getVisibleBounds(controller.shapeLayerOffset); - _referenceShapeBounds = _getShapeBounds( - controller.shapeLayerSizeFactor, controller.shapeLayerOffset); - final Offset localFocalPoint = pixelFromLatLng( - _focalLatLngTween.end.latitude, - _focalLatLngTween.end.longitude, - size, - controller.shapeLayerOffset, - controller.shapeLayerSizeFactor); - final Offset previousFocalPoint = pixelFromLatLng( - _focalLatLngTween.begin.latitude, - _focalLatLngTween.begin.longitude, - size, - controller.shapeLayerOffset, - controller.shapeLayerSizeFactor); - _invokeOnPanning(localFocalPoint, previousFocalPoint, - localToGlobal(localFocalPoint), true); - } - } - @override void attach(PipelineOwner owner) { super.attach(owner); - _state.selectionAnimationController?.addListener(markNeedsPaint); - _state.toggleAnimationController?.addListener(markNeedsPaint); - _state.hoverShapeAnimationController?.addListener(markNeedsPaint); - if (_state.isSublayer && controller == null) { - final RenderSublayerContainer sublayerContainer = parent; - controller = sublayerContainer.controller; - } + _state.selectionAnimationController.addListener(markNeedsPaint); + _state.toggleAnimationController.addListener(markNeedsPaint); + _state.hoverShapeAnimationController.addListener(markNeedsPaint); + + _controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset); + if (_state.isSublayer) { + _controller.addRefreshListener(_handleRefresh); + } else { + _controller.addToggleListener(_handleToggleChange); - if (controller != null) { - controller - ..addZoomingListener(_handleZooming) - ..addPanningListener(_handlePanning) - ..addResetListener(_handleReset); - if (_state.isSublayer) { - controller.addRefreshListener(_handleRefresh); - } else { - controller.addToggleListener(_handleToggleChange); - if (_state.zoomLevelAnimationController != null) { - _state.zoomLevelAnimationController - ..addListener(_handleZoomLevelAnimation) - ..addStatusListener(_handleZoomLevelAnimationStatusChange); - } + _state.zoomLevelAnimationController + ..addListener(_handleZoomLevelAnimation) + ..addStatusListener(_handleZoomLevelAnimationStatusChange); - if (_state.focalLatLngAnimationController != null) { - _state.focalLatLngAnimationController - ..addListener(_handleFocalLatLngAnimation) - ..addStatusListener(_handleFocalLatLngAnimationStatusChange); - } - } + _state.focalLatLngAnimationController + ..addListener(_handleFocalLatLngAnimation) + ..addStatusListener(_handleFocalLatLngAnimationStatusChange); } - SchedulerBinding.instance.addPostFrameCallback(_initiateInitialAnimations); + SchedulerBinding.instance?.addPostFrameCallback(_initiateInitialAnimations); } @override void detach() { _state.dataLabelAnimationController.value = 0.0; _state.bubbleAnimationController.value = 0.0; - _state.selectionAnimationController?.removeListener(markNeedsPaint); - _state.toggleAnimationController?.removeListener(markNeedsPaint); - _state.hoverShapeAnimationController?.removeListener(markNeedsPaint); - if (controller != null) { - controller - ..removeZoomingListener(_handleZooming) - ..removePanningListener(_handlePanning) - ..removeResetListener(_handleReset); - if (_state.isSublayer) { - controller.removeRefreshListener(_handleRefresh); - } else { - controller.removeToggleListener(_handleToggleChange); - if (_state.zoomLevelAnimationController != null) { - _state.zoomLevelAnimationController - ..removeListener(_handleZoomLevelAnimation) - ..removeStatusListener(_handleZoomLevelAnimationStatusChange); - } + _state.selectionAnimationController.removeListener(markNeedsPaint); + _state.toggleAnimationController.removeListener(markNeedsPaint); + _state.hoverShapeAnimationController.removeListener(markNeedsPaint); + _controller + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset); + if (_state.isSublayer) { + _controller.removeRefreshListener(_handleRefresh); + } else { + _controller.removeToggleListener(_handleToggleChange); - if (_state.focalLatLngAnimationController != null) { - _state.focalLatLngAnimationController - ..removeListener(_handleFocalLatLngAnimation) - ..removeStatusListener(_handleFocalLatLngAnimationStatusChange); - } - } + _state.zoomLevelAnimationController + ..removeListener(_handleZoomLevelAnimation) + ..removeStatusListener(_handleZoomLevelAnimationStatusChange); + + _state.focalLatLngAnimationController + ..removeListener(_handleFocalLatLngAnimation) + ..removeStatusListener(_handleFocalLatLngAnimationStatusChange); } _zoomingDelayTimer?.cancel(); super.detach(); @@ -3967,47 +4963,57 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { @override void handleEvent(PointerEvent event, HitTestEntry entry) { _zoomPanBehavior?.handleEvent(event); - if ((isInteractive || canZoom) && event.down && event is PointerDownEvent) { - _pointerCount++; + if (event is PointerDownEvent && event.down && (isInteractive || canZoom)) { + if (isInteractive && + !_state.zoomLevelAnimationController.isAnimating && + !_state.focalLatLngAnimationController.isAnimating) { + _tapGestureRecognizer.addPointer(event); + } else { + _handleFlingAnimations(); + } + if (canZoom) { _scaleGestureRecognizer.addPointer(event); + if (_zoomPanBehavior!.enableDoubleTapZooming) { + _doubleTapTimer ??= Timer(kDoubleTapTimeout, _resetDoubleTapTimer); + } } - _singleTapConfirmed = _pointerCount == 1; - } else if (event is PointerUpEvent || event is PointerCancelEvent) { - if (_singleTapConfirmed) { - _handleTapUp(event.localPosition); - _downLocalPoint = null; - _downGlobalPoint = null; + } else if (event is PointerUpEvent && canZoom) { + if (_doubleTapTimer != null && _doubleTapTimer!.isActive) { + _pointerCount++; } - _pointerCount = 0; + if (_pointerCount == 2) { + _downLocalPoint = event.localPosition; + _downGlobalPoint = event.position; + _resetDoubleTapTimer(); + _handleDoubleTap(); + } + } else if (event is PointerCancelEvent && isInteractive) { + _handleTap(event.localPosition, event.kind); } else if (event is PointerScrollEvent) { _handleScrollEvent(event); } else if (_state.isDesktop && event is PointerHoverEvent) { // PointerHoverEvent is applicable only for web platform. _handleHover(event); - } else if (event is PointerMoveEvent && event.delta != Offset.zero) { - // In sublayer, we haven't handled the scale gestures. If we start panning - // on a sublayer shape, it takes the tap down and when tap up, it will - // perform tapping related event. So to avoid that, we had updated - // _singleTapConfirmed as false. - _singleTapConfirmed = false; } } @override void performLayout() { _size = getBoxSize(constraints); - controller.shapeLayerBoxSize = _size; + _controller.shapeLayerBoxSize = _size; if (!hasSize || size != _size) { size = _size; - _refresh(controller.visibleFocalLatLng); + _refresh(_controller.visibleFocalLatLng); } final BoxConstraints looseConstraints = BoxConstraints.loose(size); - RenderBox child = firstChild; + RenderBox? child = firstChild; while (child != null) { - final StackParentData childParentData = child.parentData; + final StackParentData childParentData = + // ignore: avoid_as + child.parentData as StackParentData; child.layout(looseConstraints); child = childParentData.nextSibling; } @@ -4018,25 +5024,22 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { @override void paint(PaintingContext context, Offset offset) { - if (_mapDataSource != null && _mapDataSource.isNotEmpty) { - context.canvas - ..save() - ..clipRect(offset & controller.shapeLayerBoxSize); - controller.applyTransform(context, offset); - final bool hasToggledIndices = controller.toggledIndices.isNotEmpty; + if (_mapDataSource.isNotEmpty) { + context.canvas.save(); + _controller.applyTransform(context, offset); + final bool hasToggledIndices = _controller.toggledIndices.isNotEmpty; final Paint fillPaint = Paint()..isAntiAlias = true; final Paint strokePaint = Paint() ..isAntiAlias = true ..style = PaintingStyle.stroke; final bool hasPrevSelectedItem = _prevSelectedItem != null && - !controller.wasToggled(_prevSelectedItem); - + !_controller.wasToggled(_prevSelectedItem!); final bool hasCurrentSelectedItem = _currentSelectedItem != null && - !controller.wasToggled(_currentSelectedItem); + !_controller.wasToggled(_currentSelectedItem!); _mapDataSource.forEach((String key, MapModel model) { if (_currentHoverItem != null && - _currentHoverItem.primaryKey == model.primaryKey) { + _currentHoverItem!.primaryKey == model.primaryKey) { return; } @@ -4044,26 +5047,26 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { return; } - if (hasPrevSelectedItem && _prevSelectedItem.primaryKey == key) { + if (hasPrevSelectedItem && _prevSelectedItem!.primaryKey == key) { fillPaint.color = - _reverseSelectionColorTween.evaluate(_selectionColorAnimation); + _reverseSelectionColorTween.evaluate(_selectionColorAnimation)!; strokePaint ..color = _reverseSelectionStrokeColorTween - .evaluate(_selectionColorAnimation) + .evaluate(_selectionColorAnimation)! ..strokeWidth = _themeData.selectionStrokeWidth; } else if (_previousHoverItem != null && - _previousHoverItem.primaryKey == key && - !controller.wasToggled(_previousHoverItem) && + _previousHoverItem!.primaryKey == key && + !_controller.wasToggled(_previousHoverItem!) && _previousHoverItem != _currentHoverItem) { fillPaint.color = _themeData.shapeHoverColor != Colors.transparent - ? _reverseHoverColorTween.evaluate(_hoverColorAnimation) + ? _reverseHoverColorTween.evaluate(_hoverColorAnimation)! : getActualShapeColor(model); - if (_themeData.shapeHoverStrokeWidth > 0.0 && + if (_themeData.shapeHoverStrokeWidth! > 0.0 && _themeData.shapeHoverStrokeColor != Colors.transparent) { strokePaint ..color = - _reverseHoverStrokeColorTween.evaluate(_hoverColorAnimation) + _reverseHoverStrokeColorTween.evaluate(_hoverColorAnimation)! ..strokeWidth = _themeData.layerStrokeWidth; } else { strokePaint @@ -4075,12 +5078,12 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { _updateStrokePaint(model, strokePaint, hasToggledIndices); } - context.canvas.drawPath(model.shapePath, fillPaint); + context.canvas.drawPath(model.shapePath!, fillPaint); if (strokePaint.strokeWidth > 0.0 && strokePaint.color != Colors.transparent) { strokePaint.strokeWidth = _getIntrinsicStrokeWidth(strokePaint.strokeWidth); - context.canvas.drawPath(model.shapePath, strokePaint); + context.canvas.drawPath(model.shapePath!, strokePaint); } }); @@ -4091,26 +5094,15 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { } } - // Returns the color to the shape based on the [shapeColorMappers] and - // [layerColor] properties. - Color getActualShapeColor(MapModel model) { - return model.shapeColor ?? _themeData.layerColor; - } - - double _getIntrinsicStrokeWidth(double strokeWidth) { - return strokeWidth /= - controller.gesture == Gesture.scale ? controller.localScale : 1; - } - // Set the color to the toggled and un-toggled shapes based on // the [legendController.toggledIndices] collection. void _updateFillColor( MapModel model, Paint fillPaint, bool hasToggledIndices) { fillPaint.style = PaintingStyle.fill; if (_state.widget.legend != null && - _state.widget.legend.source == MapElement.shape) { - if (controller.currentToggledItemIndex == model.legendMapperIndex) { - final Color shapeColor = controller.wasToggled(model) + _state.widget.legend!.source == MapElement.shape) { + if (_controller.currentToggledItemIndex == model.legendMapperIndex) { + final Color? shapeColor = _controller.wasToggled(model) ? _forwardToggledShapeColorTween.evaluate(_toggleShapeAnimation) : _reverseToggledShapeColorTween.evaluate(_toggleShapeAnimation); // Set tween color to the shape based on the currently tapped @@ -4119,7 +5111,7 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { // un-toggled, then the [_reverseToggledShapeColorTween] return. fillPaint.color = shapeColor ?? Colors.transparent; return; - } else if (hasToggledIndices && controller.wasToggled(model)) { + } else if (hasToggledIndices && _controller.wasToggled(model)) { // Set toggled color to the previously toggled shapes. fillPaint.color = _forwardToggledShapeColorTween.end ?? Colors.transparent; @@ -4135,9 +5127,9 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { void _updateStrokePaint( MapModel model, Paint strokePaint, bool hasToggledIndices) { if (_state.widget.legend != null && - _state.widget.legend.source == MapElement.shape) { - if (controller.currentToggledItemIndex == model.legendMapperIndex) { - final Color shapeStrokeColor = controller.wasToggled(model) + _state.widget.legend!.source == MapElement.shape) { + if (_controller.currentToggledItemIndex == model.legendMapperIndex) { + final Color? shapeStrokeColor = _controller.wasToggled(model) ? _forwardToggledShapeStrokeColorTween .evaluate(_toggleShapeAnimation) : _reverseToggledShapeStrokeColorTween @@ -4148,16 +5140,16 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { // un-toggled, then the [_reverseToggledShapeStrokeColorTween] return. strokePaint ..color = shapeStrokeColor ?? Colors.transparent - ..strokeWidth = controller.wasToggled(model) - ? _legend.toggledItemStrokeWidth + ..strokeWidth = _controller.wasToggled(model) + ? _legend!.toggledItemStrokeWidth : _themeData.layerStrokeWidth; return; - } else if (hasToggledIndices && controller.wasToggled(model)) { + } else if (hasToggledIndices && _controller.wasToggled(model)) { // Set toggled stroke color to the previously toggled shapes. strokePaint ..color = _forwardToggledShapeStrokeColorTween.end ?? Colors.transparent - ..strokeWidth = _legend.toggledItemStrokeWidth; + ..strokeWidth = _legend!.toggledItemStrokeWidth; return; } } @@ -4167,20 +5159,31 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { ..strokeWidth = _themeData.layerStrokeWidth; } + // Returns the color to the shape based on the [shapeColorMappers] and + // [layerColor] properties. + Color getActualShapeColor(MapModel model) { + return model.shapeColor ?? _themeData.layerColor; + } + + double _getIntrinsicStrokeWidth(double strokeWidth) { + return strokeWidth /= + _controller.gesture == Gesture.scale ? _controller.localScale : 1; + } + void _drawSelectedShape( PaintingContext context, Paint fillPaint, Paint strokePaint) { if (_currentSelectedItem != null && - !controller.wasToggled(_currentSelectedItem)) { + !_controller.wasToggled(_currentSelectedItem!)) { fillPaint.color = - _forwardSelectionColorTween.evaluate(_selectionColorAnimation); - context.canvas.drawPath(_currentSelectedItem.shapePath, fillPaint); + _forwardSelectionColorTween!.evaluate(_selectionColorAnimation)!; + context.canvas.drawPath(_currentSelectedItem!.shapePath!, fillPaint); if (_themeData.selectionStrokeWidth > 0.0) { strokePaint ..color = _forwardSelectionStrokeColorTween - .evaluate(_selectionColorAnimation) + .evaluate(_selectionColorAnimation)! ..strokeWidth = _getIntrinsicStrokeWidth(_themeData.selectionStrokeWidth); - context.canvas.drawPath(_currentSelectedItem.shapePath, strokePaint); + context.canvas.drawPath(_currentSelectedItem!.shapePath!, strokePaint); } } } @@ -4189,15 +5192,16 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { PaintingContext context, Paint fillPaint, Paint strokePaint) { if (_currentHoverItem != null) { fillPaint.color = _themeData.shapeHoverColor != Colors.transparent - ? _forwardHoverColorTween.evaluate(_hoverColorAnimation) - : getActualShapeColor(_currentHoverItem); - context.canvas.drawPath(_currentHoverItem.shapePath, fillPaint); - if (_themeData.shapeHoverStrokeWidth > 0.0 && + ? _forwardHoverColorTween.evaluate(_hoverColorAnimation)! + : getActualShapeColor(_currentHoverItem!); + context.canvas.drawPath(_currentHoverItem!.shapePath!, fillPaint); + if (_themeData.shapeHoverStrokeWidth! > 0.0 && _themeData.shapeHoverStrokeColor != Colors.transparent) { strokePaint - ..color = _forwardHoverStrokeColorTween.evaluate(_hoverColorAnimation) + ..color = + _forwardHoverStrokeColorTween.evaluate(_hoverColorAnimation)! ..strokeWidth = - _getIntrinsicStrokeWidth(_themeData.shapeHoverStrokeWidth); + _getIntrinsicStrokeWidth(_themeData.shapeHoverStrokeWidth!); } else { strokePaint ..color = _themeData.layerStrokeColor @@ -4206,169 +5210,8 @@ class RenderShapeLayer extends RenderStack implements MouseTrackerAnnotation { if (strokePaint.strokeWidth > 0.0 && strokePaint.color != Colors.transparent) { - context.canvas.drawPath(_currentHoverItem.shapePath, strokePaint); + context.canvas.drawPath(_currentHoverItem!.shapePath!, strokePaint); } } } } - -/// Converts json file to future string based on -/// assert, network, memory and file. -abstract class MapProvider { - /// Abstract const constructor. This constructor enables subclasses to provide - /// const constructors so that they can be used in const expressions. - const MapProvider(); - - /// Returns the json file as future string value. - Future loadString(); - - /// Returns shape path which is given. - String get shapePath; - - /// Returns shape bytes which is given. - Uint8List get bytes; -} - -/// Decodes the given json file as a map. -/// -/// This class behaves like similar to [Image.asset]. -/// -/// See also: -/// -/// [MapShapeSource.asset] for the [SfMaps] widget shorthand, -/// backed up by [AssetMapProvider]. -class AssetMapProvider extends MapProvider { - /// Creates an object that decodes a [String] buffer as a map. - AssetMapProvider(String assetName) - : assert(assetName != null), - assert(assetName.isNotEmpty) { - _shapePath = assetName; - } - - String _shapePath; - - @override - Future loadString() async { - return await rootBundle.loadString(_shapePath); - } - - @override - String get shapePath => _shapePath; - - @override - Uint8List get bytes => null; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is AssetMapProvider && other.shapePath == shapePath; - } - - @override - int get hashCode => hashValues(shapePath, bytes); -} - -// Decodes the given map URL from the network. -/// -/// The map will be fetched and saved in local temporary directory for map -/// manipulation. -/// -/// This class behaves like similar to [Image.network]. -/// -/// See also: -/// -/// [MapShapeSource.network] for the [SfMaps] widget shorthand, -/// backed up by [NetworkMapProvider]. -class NetworkMapProvider extends MapProvider { - /// Creates an object that decodes the map at the given URL. - NetworkMapProvider(String url) - : assert(url != null), - assert(url.isNotEmpty) { - _url = url; - } - - String _url; - - @override - Future loadString() async { - final response = await http.get(_url); - if (response.statusCode == 200) { - return response.body; - } else { - throw Exception('Failed to load json'); - } - } - - @override - String get shapePath => _url; - - @override - Uint8List get bytes => null; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is NetworkMapProvider && other.shapePath == shapePath; - } - - @override - int get hashCode => hashValues(shapePath, bytes); -} - -/// Decodes the given [Uint8List] buffer as an map. -/// -/// The provided [bytes] buffer should not be changed after it is provided -/// to a [MemoryMapProvider]. -/// -/// This class behaves like similar to [Image.memory]. -/// -/// See also: -/// -/// [MapShapeSource.memory] for the [SfMaps] widget shorthand, -/// backed up by [MemoryMapProvider]. -class MemoryMapProvider extends MapProvider { - /// Creates an object that decodes a [Uint8List] buffer as a map. - MemoryMapProvider(Uint8List bytes) : assert(bytes != null) { - _mapBytes = bytes; - } - - Uint8List _mapBytes; - - @override - Future loadString() async { - return utf8.decode(_mapBytes); - } - - @override - String get shapePath => null; - - @override - Uint8List get bytes => _mapBytes; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is MemoryMapProvider && other.bytes == bytes; - } - - @override - int get hashCode => hashValues(shapePath, bytes); -} diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart b/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart index 608f128ec..89ce15234 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart @@ -12,7 +12,8 @@ import 'package:http/http.dart' as http; import 'package:syncfusion_flutter_core/theme.dart'; import '../behavior/zoom_pan_behavior.dart'; -import '../controller/default_controller.dart'; +import '../common.dart'; +import '../controller/map_controller.dart'; import '../controller/shape_layer_controller.dart'; import '../elements/marker.dart'; import '../elements/toolbar.dart'; @@ -23,35 +24,6 @@ import '../layer/vector_layers.dart'; import '../settings.dart'; import '../utils.dart'; -Offset _pixelFromLatLng(MapLatLng latLng, double scale) { - final double latitude = - _clip(latLng.latitude, minimumLatitude, maximumLatitude); - final double longitude = - _clip(latLng.longitude, minimumLongitude, maximumLongitude); - final double x = (longitude + 180) / 360; - final double sinLatitude = sin(latitude * pi / 180); - final double y = 0.5 - log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * pi); - - final double tileSize = getTotalTileWidth(scale); - final double pixelX = _clip(x * tileSize + 0.5, 0, tileSize - 1); - final double pixelY = _clip(y * tileSize + 0.5, 0, tileSize - 1); - return Offset(pixelX, pixelY); -} - -MapLatLng _latLngFromPixel(Offset point, {double scale}) { - final double tileSize = getTotalTileWidth(scale); - final double x = (_clip(point.dx, 0, tileSize - 1) / tileSize) - 0.5; - final double y = 0.5 - (_clip(point.dy, 0, tileSize - 1) / tileSize); - - final double latitude = 90 - 360 * atan(exp(-y * 2 * pi)) / pi; - final double longitude = 360 * x; - return MapLatLng(latitude, longitude); -} - -double _clip(double value, double minValue, double maxValue) { - return min(max(value, minValue), maxValue); -} - /// Returns the URL template in the required format for the Bing Maps. /// /// For Bing Maps, an additional step is required. The format of the required @@ -62,6 +34,14 @@ double _clip(double value, double minValue, double maxValue) { /// [MapTileLayer.urlTemplate] property. /// /// ```dart +/// MapZoomPanBehavior _zoomPanBehavior; +/// +/// @override +/// void initState() { +/// _zoomPanBehavior = MapZoomPanBehavior(); +/// super.initState(); +/// } +/// /// @override /// Widget build(BuildContext context) { /// return FutureBuilder( @@ -74,7 +54,7 @@ double _clip(double value, double minValue, double maxValue) { /// layers: [ /// MapTileLayer( /// initialFocalLatLng: MapLatLng(20.5937, 78.9629), -/// zoomPanBehavior: MapZoomPanBehavior(), +/// zoomPanBehavior: _zoomPanBehavior, /// initialZoomLevel: 3, /// urlTemplate: snapshot.data, /// ), @@ -85,13 +65,13 @@ double _clip(double value, double minValue, double maxValue) { /// }); /// } /// ``` -Future getBingUrlTemplate(String url) async { +Future getBingUrlTemplate(String url) async { final http.Response response = await _fetchResponse(url); assert(response.statusCode == 200, 'Invalid key'); if (response.statusCode == 200) { final Map decodedJson = json.decode(response.body); - String imageUrl; - String imageUrlSubDomains; + late String imageUrl; + late String imageUrlSubDomains; if (decodedJson['authenticationResultCode'] == 'ValidCredentials') { for (final String key in decodedJson.keys) { if (key == 'resourceSets') { @@ -119,14 +99,98 @@ Future getBingUrlTemplate(String url) async { } Future _fetchResponse(String url) { - return http.get(url); + return http.get(Uri.tryParse(url)!); +} + +Offset _pixelFromLatLng(MapLatLng latLng, double scale) { + final double latitude = + latLng.latitude.clamp(minimumLatitude, maximumLatitude); + final double longitude = + latLng.longitude.clamp(minimumLongitude, maximumLongitude); + return pixelFromLatLng( + latitude, longitude, Size.square(getTotalTileWidth(scale))); +} + +MapLatLng _pixelToLatLng(Offset point, double scale) { + return pixelToLatLng(point, Size.square(getTotalTileWidth(scale))); +} + +/// Represents the tile factor like x position, y position and scale value. +class _MapTileCoordinate { + /// Creates a [_MapTileCoordinate]. + _MapTileCoordinate(this.x, this.y, this.z); + + /// Represents the x-coordinate of the tile. + int x; + + /// Represents the y-coordinate of the tile. + int y; + + /// Represents the scale value of the tile. + int z; + + double distanceTo(Offset startPoint, Offset endPoint) { + final double dx = startPoint.dx - endPoint.dx; + final double dy = startPoint.dy - endPoint.dy; + return sqrt(dx * dx + dy * dy); + } + + @override + String toString() => '_MapTileCoordinate($x, $y, $z)'; + + @override + bool operator ==(dynamic other) { + if (other is _MapTileCoordinate) { + return x == other.x && y == other.y && z == other.z; + } + return false; + } + + @override + int get hashCode => hashValues(x.hashCode, y.hashCode, z.hashCode); +} + +/// Represents the information about the tile. +class _MapTile { + /// Creates a [_MapTile]. + _MapTile({ + required this.xyzKey, + required this.coordinates, + required this.tilePos, + required this.level, + required this.image, + }); + + /// Provides a unique key for each tile. The format of [xyzKey] looks `x:y:z`, + /// where z denotes zoom level, x and y denotes tile coordinates. + final String xyzKey; + + /// Represents the tile x-position, y-position and scale value. + final _MapTileCoordinate coordinates; + + /// Represents the tile left and top position. + final Offset tilePos; + + /// Represents the scale transform details of the tile. + final TileZoomLevelDetails level; + + /// Add tile to the image. + final Widget image; + + @override + int get hashCode => coordinates.hashCode; + + @override + bool operator ==(other) { + return other is _MapTile && coordinates == other.coordinates; + } } /// Provides options for adding, removing, deleting, updating markers /// collection and converting pixel points to latitude and longitude. class MapTileLayerController extends MapLayerController { - /// Instance of _MapTileLayerState. - _MapTileLayerState _state; + /// Creates of [_TileLayerState]. + late _TileLayerState _state; /// Returns the current markers count. int get markersCount => _markersCount; @@ -135,13 +199,13 @@ class MapTileLayerController extends MapLayerController { /// Converts pixel point to [MapLatLng]. MapLatLng pixelToLatLng(Offset position) { final Offset localPointCenterDiff = Offset( - (_state._size.width / 2) - position.dx, - (_state._size.height / 2) - position.dy); + (_state._size!.width / 2) - position.dx, + (_state._size!.height / 2) - position.dy); final Offset actualCenterPixelPosition = _pixelFromLatLng(_state._currentFocalLatLng, _state._currentZoomLevel); final Offset newCenterPoint = actualCenterPixelPosition - localPointCenterDiff; - return _latLngFromPixel(newCenterPoint, scale: _state._currentZoomLevel); + return _pixelToLatLng(newCenterPoint, _state._currentZoomLevel); } } @@ -207,19 +271,19 @@ class MapTileLayerController extends MapLayerController { class MapTileLayer extends MapLayer { /// Creates a [MapTileLayer]. MapTileLayer({ - Key key, - @required this.urlTemplate, + Key? key, + required this.urlTemplate, this.initialFocalLatLng = const MapLatLng(0.0, 0.0), this.initialZoomLevel = 1, this.controller, - List sublayers, + List? sublayers, int initialMarkersCount = 0, - MapMarkerBuilder markerBuilder, - IndexedWidgetBuilder markerTooltipBuilder, + MapMarkerBuilder? markerBuilder, + IndexedWidgetBuilder? markerTooltipBuilder, MapTooltipSettings tooltipSettings = const MapTooltipSettings(), - MapZoomPanBehavior zoomPanBehavior, - WillZoomCallback onWillZoom, - WillPanCallback onWillPan, + MapZoomPanBehavior? zoomPanBehavior, + WillZoomCallback? onWillZoom, + WillPanCallback? onWillPan, }) : assert(initialZoomLevel >= 1 && initialZoomLevel <= 15), assert(initialMarkersCount == 0 || initialMarkersCount != 0 && markerBuilder != null), @@ -276,6 +340,14 @@ class MapTileLayer extends MapLayer { /// [urlTemplate] property. /// /// ```dart + /// MapZoomPanBehavior _zoomPanBehavior; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior(); + /// super.initState(); + /// } + /// /// @override /// Widget build(BuildContext context) { /// return FutureBuilder( @@ -288,7 +360,7 @@ class MapTileLayer extends MapLayer { /// layers: [ /// MapTileLayer( /// initialFocalLatLng: MapLatLng(20.5937, 78.9629), - /// zoomPanBehavior: MapZoomPanBehavior(), + /// zoomPanBehavior: _zoomPanBehavior, /// initialZoomLevel: 3, /// urlTemplate: snapshot.data, /// ), @@ -361,6 +433,7 @@ class MapTileLayer extends MapLayer { /// List data; /// MapTileLayerController controller; /// Random random = Random(); + /// MapZoomPanBehavior _zoomPanBehavior; /// /// @override /// void initState() { @@ -375,6 +448,7 @@ class MapTileLayer extends MapLayer { /// ]; /// /// controller = MapTileLayerController(); + /// _zoomPanBehavior = MapZoomPanBehavior(); /// } /// /// @override @@ -391,7 +465,7 @@ class MapTileLayer extends MapLayer { /// layers: [ /// MapTileLayer( /// initialFocalLatLng: MapLatLng(20.5937, 78.9629), - /// zoomPanBehavior: MapZoomPanBehavior(), + /// zoomPanBehavior: _zoomPanBehavior, /// initialZoomLevel: 3, /// urlTemplate: snapshot.data, /// initialMarkersCount: 5, @@ -420,12 +494,11 @@ class MapTileLayer extends MapLayer { /// }); /// } /// ``` - final MapTileLayerController controller; + final MapTileLayerController? controller; @override Widget build(BuildContext context) { - return _MapTileLayer( - key: key, + return _TileLayerBase( urlTemplate: urlTemplate, initialFocalLatLng: initialFocalLatLng, initialZoomLevel: initialZoomLevel, @@ -449,14 +522,18 @@ class MapTileLayer extends MapLayer { properties.add(DiagnosticsProperty( 'initialFocalLatLng', initialFocalLatLng)); properties.add(IntProperty('initialZoomLevel', initialZoomLevel)); + if (sublayers != null && sublayers!.isNotEmpty) { + final DebugSublayerTree pointerTreeNode = DebugSublayerTree(sublayers!); + properties.add(pointerTreeNode.toDiagnosticsNode()); + } if (zoomPanBehavior != null) { properties - .add(zoomPanBehavior.toDiagnosticsNode(name: 'zoomPanBehavior')); + .add(zoomPanBehavior!.toDiagnosticsNode(name: 'zoomPanBehavior')); } properties.add(ObjectFlagProperty.has( 'markerBuilder', markerBuilder)); if (controller != null) { - properties.add(IntProperty('markersCount', controller.markersCount)); + properties.add(IntProperty('markersCount', controller!.markersCount)); } else { properties.add(IntProperty('markersCount', initialMarkersCount)); } @@ -467,289 +544,230 @@ class MapTileLayer extends MapLayer { } } -class _MapTileLayer extends StatefulWidget { - _MapTileLayer({ - Key key, - this.urlTemplate, - this.initialFocalLatLng, - this.initialZoomLevel, - this.zoomPanBehavior, - this.sublayers, - this.initialMarkersCount, - this.markerBuilder, - this.markerTooltipBuilder, - this.tooltipSettings, - this.controller, - this.onWillZoom, - this.onWillPan, - }) : super(key: key); +class _TileLayerBase extends StatefulWidget { + const _TileLayerBase({ + required this.urlTemplate, + required this.initialFocalLatLng, + required this.initialZoomLevel, + required this.zoomPanBehavior, + required this.sublayers, + required this.initialMarkersCount, + required this.markerBuilder, + required this.markerTooltipBuilder, + required this.tooltipSettings, + required this.controller, + required this.onWillZoom, + required this.onWillPan, + }); final String urlTemplate; final MapLatLng initialFocalLatLng; final int initialZoomLevel; - final MapZoomPanBehavior zoomPanBehavior; - final List sublayers; + final MapZoomPanBehavior? zoomPanBehavior; + final List? sublayers; final int initialMarkersCount; - final MapMarkerBuilder markerBuilder; - final IndexedWidgetBuilder markerTooltipBuilder; + final MapMarkerBuilder? markerBuilder; + final IndexedWidgetBuilder? markerTooltipBuilder; final MapTooltipSettings tooltipSettings; - final MapTileLayerController controller; - final WillZoomCallback onWillZoom; - final WillPanCallback onWillPan; + final MapTileLayerController? controller; + final WillZoomCallback? onWillZoom; + final WillPanCallback? onWillPan; @override - _MapTileLayerState createState() => _MapTileLayerState(); + _TileLayerBaseState createState() => _TileLayerBaseState(); } -class _MapTileLayerState extends State<_MapTileLayer> - with TickerProviderStateMixin { - // Both width and height of each tile is 256. - static const double tileSize = 256; - static const double _frictionCoefficient = 0.0000135; - // The [MapController] handles the events of the [ZoomPanBehavior]. - MapController _controller; - AnimationController _zoomLevelAnimationController; - AnimationController _focalLatLngAnimationController; - Animation _zoomLevelAnimation; - Animation _focalLatLngAnimation; - MapLatLngTween _focalLatLngTween; - Tween _zoomLevelTween; - - // Stores the integer zoom level in the [_nextZoomLevel] field - // like 1, 2, 3 etc. - int _nextZoomLevel; - final Map _tiles = {}; - final Map _levels = {}; - // Stores the current zoom level in the [_currentZoomLevel] field - // like 1, 1.1, 1.2 etc. - double _currentZoomLevel; - double _touchStartZoomLevel; - MapLatLng _touchStartLatLng; - MapLatLng _currentFocalLatLng; - Offset _touchStartLocalPoint; - Offset _touchStartGlobalPoint; - Size _size; - - GlobalKey _tooltipKey; - Gesture _gestureType; - bool _isSizeChanged = false; - bool _isDesktop; - bool _hasSublayer = false; - bool _isZoomedUsingToolbar = false; - List _markers; - MapZoomDetails _zoomDetails; - MapPanDetails _panDetails; - Timer _zoomingDelayTimer; - MapLatLng _mouseCenterLatLng; - double _mouseStartZoomLevel; - Offset _mouseStartLocalPoint; - Offset _mouseStartGlobalPoint; - Offset _touchStartOffset; - MapLatLng _newFocalLatLng; - SfMapsThemeData _mapsThemeData; +class _TileLayerBaseState extends State<_TileLayerBase> { + late MapController _controller; @override void initState() { - super.initState(); - _tooltipKey = GlobalKey(); - _currentFocalLatLng = - widget.zoomPanBehavior?.focalLatLng ?? widget.initialFocalLatLng; - _currentZoomLevel = (widget.zoomPanBehavior != null && - widget.zoomPanBehavior.zoomLevel != 1) - ? widget.zoomPanBehavior.zoomLevel - : widget.initialZoomLevel.toDouble(); - widget.zoomPanBehavior?.zoomLevel = _currentZoomLevel; - _nextZoomLevel = _currentZoomLevel.floor(); - if (widget.controller != null) { - widget.controller._markersCount = widget.initialMarkersCount; - } - - widget.controller?._state = this; - _markers = []; - for (int i = 0; i < widget.initialMarkersCount; i++) { - final MapMarker marker = widget.markerBuilder(context, i); - assert(marker != null); - _markers.add(marker); - } - - _zoomLevelAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 650)) - ..addListener(_handleZoomLevelAnimation) - ..addStatusListener(_handleZoomLevelAnimationStatusChange); - _focalLatLngAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 650)) - ..addListener(_handleFocalLatLngAnimation) - ..addStatusListener(_handleFocalLatLngAnimationStatusChange); - _zoomLevelAnimation = CurvedAnimation( - parent: _zoomLevelAnimationController, curve: Curves.easeInOut); - _focalLatLngAnimation = CurvedAnimation( - parent: _focalLatLngAnimationController, curve: Curves.decelerate); - _focalLatLngTween = MapLatLngTween(); - _zoomLevelTween = Tween(); - _hasSublayer = widget.sublayers != null && widget.sublayers.isNotEmpty; - widget.controller?.addListener(refreshMarkers); _controller = MapController() - ..addZoomingListener(_handleZooming) - ..addPanningListener(_handlePanning) - ..addResetListener(_handleReset) - ..tileLayerZoomLevel = _currentZoomLevel - ..visibleFocalLatLng = _currentFocalLatLng - ..onZoomLevelChange = _handleZoomTo - ..onPanChange = _handlePanTo - ..tooltipKey = _tooltipKey - ..isTileLayerChild = true; + ..tooltipKey = GlobalKey() + ..layerType = LayerType.tile; + super.initState(); } @override - void dispose() { - if (_controller != null) { - _controller - ..removeZoomingListener(_handleZooming) - ..removePanningListener(_handlePanning) - ..removeResetListener(_handleReset); - } - - if (_zoomLevelAnimationController != null) { - _zoomLevelAnimationController - ..removeListener(_handleZoomLevelAnimation) - ..removeStatusListener(_handleZoomLevelAnimationStatusChange) - ..dispose(); - } + Widget build(BuildContext context) { + return MapLayerInheritedWidget( + controller: _controller, + sublayers: widget.sublayers, + child: _TileLayer( + urlTemplate: widget.urlTemplate, + initialFocalLatLng: widget.initialFocalLatLng, + initialZoomLevel: widget.initialZoomLevel, + sublayers: widget.sublayers, + initialMarkersCount: widget.initialMarkersCount, + markerBuilder: widget.markerBuilder, + markerTooltipBuilder: widget.markerTooltipBuilder, + tooltipSettings: widget.tooltipSettings, + zoomPanBehavior: widget.zoomPanBehavior, + controller: widget.controller, + onWillZoom: widget.onWillZoom, + onWillPan: widget.onWillPan, + ), + ); + } +} - if (_focalLatLngAnimationController != null) { - _focalLatLngAnimationController - ..removeListener(_handleFocalLatLngAnimation) - ..removeStatusListener(_handleFocalLatLngAnimationStatusChange) - ..dispose(); - } +class _TileLayer extends StatefulWidget { + _TileLayer({ + required this.urlTemplate, + required this.initialFocalLatLng, + required this.initialZoomLevel, + required this.zoomPanBehavior, + required this.sublayers, + required this.initialMarkersCount, + required this.markerBuilder, + required this.markerTooltipBuilder, + required this.tooltipSettings, + required this.controller, + required this.onWillZoom, + required this.onWillPan, + }); - widget.controller?.removeListener(refreshMarkers); - _controller?.dispose(); - _markers.clear(); - _tiles?.clear(); - _levels?.clear(); - super.dispose(); - } + final String urlTemplate; + final MapLatLng initialFocalLatLng; + final int initialZoomLevel; + final MapZoomPanBehavior? zoomPanBehavior; + final List? sublayers; + final int initialMarkersCount; + final MapMarkerBuilder? markerBuilder; + final IndexedWidgetBuilder? markerTooltipBuilder; + final MapTooltipSettings tooltipSettings; + final MapTileLayerController? controller; + final WillZoomCallback? onWillZoom; + final WillPanCallback? onWillPan; @override - void didUpdateWidget(_MapTileLayer oldWidget) { - _hasSublayer = widget.sublayers != null && widget.sublayers.isNotEmpty; - super.didUpdateWidget(oldWidget); - } + _TileLayerState createState() => _TileLayerState(); +} - @override - Widget build(BuildContext context) { - final ThemeData themeData = Theme.of(context); - _isDesktop = kIsWeb || - themeData.platform == TargetPlatform.macOS || - themeData.platform == TargetPlatform.windows; - _mapsThemeData = SfMapsTheme.of(context); - _mapsThemeData = _mapsThemeData.copyWith( - tooltipColor: widget.tooltipSettings.color ?? _mapsThemeData.tooltipColor, - tooltipStrokeColor: widget.tooltipSettings.strokeColor ?? - _mapsThemeData.tooltipStrokeColor, - tooltipStrokeWidth: widget.tooltipSettings.strokeWidth ?? - _mapsThemeData.tooltipStrokeWidth, - tooltipBorderRadius: _mapsThemeData.tooltipBorderRadius - .resolve(Directionality.of(context)), - ); - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - if (_size != null) { - _isSizeChanged = _size.width != constraints.maxWidth || - _size.height != constraints.maxHeight; - } +class _TileLayerState extends State<_TileLayer> with TickerProviderStateMixin { + // Both width and height of each tile is 256. + static const double tileSize = 256; + static const double _frictionCoefficient = 0.05; - _size = getBoxSize(constraints); - _controller.tileLayerBoxSize = _size; - return Container( - width: _size.width, - height: _size.height, - child: Listener( - onPointerDown: _handlePointerDown, - onPointerMove: _handlePointerMove, - onPointerUp: _handlePointerUp, - onPointerSignal: _handleMouseWheelZooming, - child: GestureDetector( - onScaleStart: _handleScaleStart, - onScaleUpdate: _handleScaleUpdate, - onScaleEnd: _handleScaleEnd, - child: _getTileLayerElements(context, themeData), - ), - ), - ); - }, - ); - } + final Map _tiles = {}; + final Map _levels = {}; + + late double _currentZoomLevel; + late int _nextZoomLevel; + late double _touchStartZoomLevel; + late MapLatLng _touchStartLatLng; + late MapLatLng _currentFocalLatLng; + late Offset _touchStartLocalPoint; + late Offset _touchStartGlobalPoint; + late bool _isDesktop; + late double _maximumReachedScaleOnInteraction; + late MapLatLng _newFocalLatLng; + late SfMapsThemeData _mapsThemeData; + late MapLayerInheritedWidget _ancestor; + late AnimationController _zoomLevelAnimationController; + late AnimationController _focalLatLngAnimationController; + late CurvedAnimation _flingZoomLevelCurvedAnimation; + late CurvedAnimation _flingFocalLatLngCurvedAnimation; + late CurvedAnimation _zoomLevelCurvedAnimation; + late CurvedAnimation _focalLatLngCurvedAnimation; + late MapLatLngTween _focalLatLngTween; + late Tween _zoomLevelTween; + + Size? _size; + MapController? _controller; + int _pointerCount = 0; + Gesture? _gestureType; + Timer? _doubleTapTimer; + bool _isSizeChanged = false; + bool _hasSublayer = false; + bool _isZoomedUsingToolbar = false; + bool _isFlingAnimationActive = false; + bool _doubleTapEnabled = false; + List? _markers; + MapZoomDetails? _zoomDetails; + MapPanDetails? _panDetails; + Timer? _zoomingDelayTimer; + MapLatLng? _mouseCenterLatLng; + double? _mouseStartZoomLevel; + Offset? _mouseStartLocalPoint; + Offset? _mouseStartGlobalPoint; + Offset? _touchStartOffset; + + Widget get _sublayerContainer => + SublayerContainer(ancestor: _ancestor, children: widget.sublayers!); + + Widget get _markerContainer => MarkerContainer( + markerTooltipBuilder: widget.markerTooltipBuilder, + controller: _controller!, + children: _markers, + ); - // Add elements in the tile layer. - Widget _getTileLayerElements(BuildContext context, ThemeData themeData) { + Widget get _behaviorViewRenderObjectWidget => BehaviorViewRenderObjectWidget( + controller: _controller!, zoomPanBehavior: widget.zoomPanBehavior!); + + Widget get _toolbarWidget => MapToolbar( + onWillZoom: widget.onWillZoom, + zoomPanBehavior: widget.zoomPanBehavior!, + controller: _controller, + ); + + Widget get _tooltipWidget => MapTooltip( + key: _controller!.tooltipKey, + controller: _controller, + sublayers: widget.sublayers, + markerTooltipBuilder: widget.markerTooltipBuilder, + tooltipSettings: widget.tooltipSettings, + themeData: _mapsThemeData, + ); + + Widget get _tileLayerChildren { final List children = []; children.add(_getTiles()); if (_hasSublayer) { - children.add( - ClipRect( - child: SublayerContainer( - controller: _controller, - tooltipKey: _tooltipKey, - children: widget.sublayers, - ), - ), - ); + children.add(_sublayerContainer); } - if (_markers != null && _markers.isNotEmpty) { - children.add(ClipRect( - child: _TileLayerMarkerContainer( - tooltipKey: _tooltipKey, - markerTooltipBuilder: widget.markerTooltipBuilder, - children: _markers, - controller: _controller, - ))); + if (_markers != null && _markers!.isNotEmpty) { + children.add(_markerContainer); } - if (widget.zoomPanBehavior != null) { - children.add(BehaviorViewRenderObjectWidget( - controller: _controller, - zoomPanBehavior: widget.zoomPanBehavior, - )); - } + return Listener( + onPointerDown: _handlePointerDown, + onPointerMove: _handlePointerMove, + onPointerUp: _handlePointerUp, + onPointerSignal: _handleMouseWheelZooming, + child: GestureDetector( + onScaleStart: _handleScaleStart, + onScaleUpdate: _handleScaleUpdate, + onScaleEnd: _handleScaleEnd, + child: Stack(children: children), + ), + ); + } - if (widget.zoomPanBehavior != null && - widget.zoomPanBehavior.showToolbar && - _isDesktop) { - children.add( - MapToolbar( - onWillZoom: widget.onWillZoom, - zoomPanBehavior: widget.zoomPanBehavior, - controller: _controller, - ), - ); + Widget get _tileLayerElements { + final List children = []; + children.add(_tileLayerChildren); + + if (widget.zoomPanBehavior != null) { + children.add(_behaviorViewRenderObjectWidget); + if (_isDesktop && widget.zoomPanBehavior!.showToolbar) { + children.add(_toolbarWidget); + } } if (_hasTooltipBuilder()) { - children.add( - MapTooltip( - key: _tooltipKey, - controller: _controller, - sublayers: widget.sublayers, - markerTooltipBuilder: widget.markerTooltipBuilder, - tooltipSettings: widget.tooltipSettings, - themeData: _mapsThemeData, - ), - ); + children.add(_tooltipWidget); } - return Stack( - children: children, - ); + return ClipRect(child: Stack(children: children)); } bool _hasTooltipBuilder() { if (widget.markerTooltipBuilder != null) { return true; } else if (_hasSublayer) { - final Iterator iterator = widget.sublayers.iterator; + final Iterator iterator = widget.sublayers!.iterator; while (iterator.moveNext()) { final MapSublayer sublayer = iterator.current; if ((sublayer is MapShapeSublayer && @@ -771,18 +789,14 @@ class _MapTileLayerState extends State<_MapTileLayer> // and request new tiles for the visible bounds. This method called only // when new zoom level reached. _requestAndPopulateNewTiles(); - // The populated tiles are stored in the [_tiles] collection field. final tiles = _tiles.values.toList(); final List positionedTiles = []; - - for (final tile in tiles) { + for (final _MapTile tile in tiles) { positionedTiles.add(_getPositionedTiles(tile)); } - return Stack( - children: positionedTiles, - ); + return Stack(children: positionedTiles); } // Generate tiles for the new zoom level based on the focalLatLng value. @@ -791,8 +805,15 @@ class _MapTileLayerState extends State<_MapTileLayer> final double tileCount = pow(2, _nextZoomLevel).toDouble(); final Offset actualCenterPixelPosition = _pixelFromLatLng(_currentFocalLatLng, _currentZoomLevel); - - final Offset halfSize = Offset(_size.width, _size.height) / 2; + final Rect newVisibleBounds = Rect.fromCenter( + center: actualCenterPixelPosition, + width: _size!.width, + height: _size!.height); + final MapLatLngBounds visibleLatLngBounds = MapLatLngBounds( + _pixelToLatLng(newVisibleBounds.topRight, _currentZoomLevel), + _pixelToLatLng(newVisibleBounds.bottomLeft, _currentZoomLevel)); + _updateVisibleBounds(visibleLatLngBounds); + final Offset halfSize = Offset(_size!.width, _size!.height) / 2; // The [globalTileStart] and [globalTileEnd] represents the tile start and // end factor value based on the zoom level. @@ -804,7 +825,7 @@ class _MapTileLayerState extends State<_MapTileLayer> final Offset visualTileStart = actualCenterPixelPosition - halfSize; final Offset visualTileEnd = actualCenterPixelPosition + halfSize; final double currentLevelTileSize = - tileSize * _levels[_nextZoomLevel].scale; + tileSize * _levels[_nextZoomLevel]!.scale; // The [startX], [startY], [endX] and [endY] represents the visual // bounds of the tiles in factor. @@ -826,7 +847,7 @@ class _MapTileLayerState extends State<_MapTileLayer> continue; } - final _MapTile tile = _tiles[_tileFactorToKey(tileCoordinate)]; + final _MapTile? tile = _tiles[_tileFactorToKey(tileCoordinate)]; if (tile != null) { continue; } else { @@ -846,73 +867,331 @@ class _MapTileLayerState extends State<_MapTileLayer> } } - // This method called when start pinch zooming or panning action. - void _handleScaleStart(ScaleStartDetails details) { - if (widget.zoomPanBehavior != null) { - if (widget.zoomPanBehavior.enablePinching || - widget.zoomPanBehavior.enablePanning) { - _focalLatLngAnimationController?.stop(); - _gestureType = null; - _touchStartLocalPoint = details.localFocalPoint; - _touchStartGlobalPoint = details.focalPoint; - _touchStartZoomLevel = _currentZoomLevel.toDouble(); - final Offset localPointCenterDiff = Offset( - (_size.width / 2) - _touchStartLocalPoint.dx, - (_size.height / 2) - _touchStartLocalPoint.dy); - final Offset actualCenterPixelPosition = - _pixelFromLatLng(_currentFocalLatLng, _touchStartZoomLevel); - final Offset point = actualCenterPixelPosition - localPointCenterDiff; - _touchStartLatLng = - _latLngFromPixel(point, scale: _touchStartZoomLevel); - - final Rect newVisibleBounds = Rect.fromCenter( - center: _pixelFromLatLng(_currentFocalLatLng, _touchStartZoomLevel), - width: _size.width, - height: _size.height); - final MapLatLngBounds newVisibleLatLng = MapLatLngBounds( - _latLngFromPixel(newVisibleBounds.topRight, - scale: _touchStartZoomLevel), - _latLngFromPixel(newVisibleBounds.bottomLeft, - scale: _touchStartZoomLevel)); - final MapLatLng touchStartFocalLatLng = _calculateVisibleLatLng( - _touchStartLocalPoint, _touchStartZoomLevel); - _touchStartOffset = - _pixelFromLatLng(touchStartFocalLatLng, _touchStartZoomLevel); + void _updateVisibleBounds(MapLatLngBounds latLngBounds) { + final TileZoomLevelDetails level = _levels[_nextZoomLevel]!; + Offset topRight = + _pixelFromLatLng(latLngBounds.northeast, level.zoomLevel) - + level.origin!; + // Adjust the topRight value based on the current scale value. + topRight = Offset(topRight.dx * level.scale, topRight.dy * level.scale) + + level.translatePoint; + Offset bottomLeft = + _pixelFromLatLng(latLngBounds.southwest, level.zoomLevel) - + level.origin!; + // Adjust the bottomLeft value based on the current scale value. + bottomLeft = + Offset(bottomLeft.dx * level.scale, bottomLeft.dy * level.scale) + + level.translatePoint; + _controller!.visibleBounds = Rect.fromPoints(bottomLeft, topRight); + } - _zoomDetails = MapZoomDetails(newVisibleBounds: newVisibleLatLng); - _panDetails = MapPanDetails(newVisibleBounds: newVisibleLatLng); - _newFocalLatLng = _currentFocalLatLng; - } + // Calculate tiles visual bounds origin for each new zoom level. + TileZoomLevelDetails? _updateZoomLevel() { + final int? zoom = _nextZoomLevel; + if (zoom == null) { + return null; } - } - // This method called when doing pinch zooming or panning action. - void _handleScaleUpdate(ScaleUpdateDetails details) { - if (widget.zoomPanBehavior != null && - (widget.zoomPanBehavior.enablePinching || - widget.zoomPanBehavior.enablePanning)) { - final double zoomLevel = _touchStartZoomLevel + log(details.scale) / ln2; - if ((widget.zoomPanBehavior.maxZoomLevel != null && - zoomLevel > widget.zoomPanBehavior.maxZoomLevel) || - (widget.zoomPanBehavior.minZoomLevel != null && - zoomLevel < widget.zoomPanBehavior.minZoomLevel)) { - return; - } + // Remove zoom-out level tiles from the [_tiles] collection when doing + // zoom-out action. + final List removePrevLevels = []; + for (final level in _levels.entries) { + final double key = level.key; - _gestureType ??= _getGestureType(details.scale, details.localFocalPoint); - if (_gestureType == null) { - return; + if (_levels.entries.length > 1) { + if (zoom < key) { + removePrevLevels.add(key); + } + } + } + + for (final levelKey in removePrevLevels) { + _removeTilesAtZoom(levelKey); + _levels.remove(levelKey); + } + + TileZoomLevelDetails? level = _levels[zoom]; + if (level == null) { + // The [_levels] collection contains each integer zoom level origin, + // scale and translationPoint. The scale and translationPoint are + // calculated in every pinch zoom level for scaling the tiles. + level = _levels[zoom.toDouble()] = TileZoomLevelDetails(); + level.origin = _getLevelOrigin(_currentFocalLatLng, zoom.toDouble()); + level.zoomLevel = zoom.toDouble(); + + if (_gestureType != null && _gestureType == Gesture.scale) { + _updateZoomLevelTransform( + level, _currentFocalLatLng, _currentZoomLevel); + } else { + level.scale = 1.0; + level.translatePoint = Offset.zero; + } + } + + // Recalculate tiles bounds origin for all existing zoom level + // when size changed. + if (_isSizeChanged) { + _updateZoomLevelTransforms(_currentFocalLatLng, _currentZoomLevel); + } + + final double totalTileWidth = getTotalTileWidth(level.zoomLevel); + _controller! + ..totalTileSize = Size(totalTileWidth, totalTileWidth) + ..tileCurrentLevelDetails = _levels[_nextZoomLevel]!; + return level; + } + + void _removeTilesAtZoom(double zoom) { + final List removePrevTiles = []; + for (final tile in _tiles.entries) { + if (tile.value.coordinates.z != zoom) { + continue; + } + removePrevTiles.add(tile.key); + } + + for (final key in removePrevTiles) { + _removeTile(key); + } + } + + void _removeTile(String key) { + final _MapTile? tile = _tiles[key]; + if (tile == null) { + return; + } + + _tiles.remove(key); + } + + Offset _getLevelOrigin(MapLatLng center, double zoom) { + final Offset halfSize = Offset(_size!.width / 2.0, _size!.height / 2.0); + return _pixelFromLatLng(center, zoom) - halfSize; + } + + // Calculate amount of scale and translation for each zoom level while + // scaling. + void _updateZoomLevelTransform( + TileZoomLevelDetails level, MapLatLng center, double zoom) { + if (level.origin == null) { + return; + } + final double currentScale = + getTotalTileWidth(zoom) / getTotalTileWidth(level.zoomLevel); + final Offset scaledPixelOrigin = _getLevelOrigin(center, zoom); + level.translatePoint = Offset( + (level.origin!.dx * currentScale) - scaledPixelOrigin.dx, + (level.origin!.dy * currentScale) - scaledPixelOrigin.dy); + level.scale = currentScale; + + if (level.zoomLevel == _nextZoomLevel) { + _controller!.tileCurrentLevelDetails.translatePoint = + level.translatePoint; + _controller!.tileCurrentLevelDetails.scale = level.scale; + } + } + + String _tileFactorToKey(_MapTileCoordinate tileFactor) { + return '${tileFactor.x}:${tileFactor.y}:${tileFactor.z}'; + } + + // This method is used to request visual tiles and store it in a [_tiles] + // collection. + void _addTile(_MapTileCoordinate tileFactor) { + final String tileFactorToKey = _tileFactorToKey(tileFactor); + final String url = _getTileUrl(tileFactor.x, tileFactor.y, tileFactor.z); + final TileZoomLevelDetails level = _levels[tileFactor.z]!; + final double tileLeftPos = (tileFactor.x * tileSize) - level.origin!.dx; + final double tileTopPos = (tileFactor.y * tileSize) - level.origin!.dy; + + _tiles[tileFactorToKey] = _MapTile( + coordinates: tileFactor, + xyzKey: tileFactorToKey, + tilePos: Offset(tileLeftPos, tileTopPos), + level: _levels[tileFactor.z]!, + image: Image.network( + url, + fit: BoxFit.fill, + ), + ); + } + + // Converts the [urlTemplate] format into respective map + // providers URL format. + String _getTileUrl(int x, int y, int z) { + String? previousLetter; + String? currentLetter; + String url = ''; + + if (widget.urlTemplate.contains('{quadkey}')) { + final String quadKey = _getQuadKey(x, y, z); + final splitQuad = widget.urlTemplate.split('{quadkey}'); + url = splitQuad[0] + quadKey + splitQuad[1]; + } else { + for (int i = 0; i < widget.urlTemplate.length; i++) { + previousLetter = currentLetter; + currentLetter = widget.urlTemplate[i]; + + if (previousLetter == '{' && widget.urlTemplate[i] == 'x') { + currentLetter = x.toString(); + } else if (previousLetter == '{' && widget.urlTemplate[i] == 'y') { + currentLetter = y.toString(); + } else if (previousLetter == '{' && widget.urlTemplate[i] == 'z') { + currentLetter = z.toString(); + } + + if (widget.urlTemplate[i] != '{' && widget.urlTemplate[i] != '}') { + url += currentLetter; + } + } + } + return url; + } + + // Converts the xyz coordinate into quadKey for bing map. + String _getQuadKey(int x, int y, int z) { + final StringBuffer quadKey = StringBuffer(); + for (int i = z; i > 0; i--) { + int digit = 0; + final int mask = 1 << (i - 1); + if ((x & mask) != 0) { + digit++; + } + if ((y & mask) != 0) { + digit++; + digit++; + } + quadKey.write(digit); + } + return quadKey.toString(); + } + + // This method used to move each tile based on the amount of + // translation and scaling value in the respective levels. + Widget _getPositionedTiles(_MapTile tile) { + final Offset tilePos = tile.tilePos; + final TileZoomLevelDetails level = tile.level; + final double tileLeftPos = + (tilePos.dx * level.scale) + level.translatePoint.dx; + final double tileTopPos = + (tilePos.dy * level.scale) + level.translatePoint.dy; + + return Positioned( + left: tileLeftPos, + top: tileTopPos, + width: tileSize * level.scale, + height: tileSize * level.scale, + child: tile.image, + ); + } + + void _handlePointerDown(PointerDownEvent event) { + if (_zoomLevelAnimationController.isAnimating && !_doubleTapEnabled) { + _zoomLevelAnimationController.stop(); + _isZoomedUsingToolbar = false; + _handleZoomingAnimationEnd(); + } + + if (_focalLatLngAnimationController.isAnimating) { + _focalLatLngAnimationController.stop(); + _handleFocalLatLngAnimationEnd(); + } + + if (widget.zoomPanBehavior != null && + widget.zoomPanBehavior!.enableDoubleTapZooming) { + _doubleTapTimer ??= Timer(kDoubleTapTimeout, _resetDoubleTapTimer); + } + + widget.zoomPanBehavior?.handleEvent(event); + } + + void _handlePointerMove(PointerMoveEvent event) { + widget.zoomPanBehavior?.handleEvent(event); + } + + void _handlePointerUp(PointerUpEvent event) { + if (_doubleTapTimer != null && _doubleTapTimer!.isActive) { + _pointerCount++; + } + + if (_pointerCount == 2) { + _touchStartLocalPoint = event.localPosition; + // ignore: avoid_as + final RenderBox renderBox = context.findRenderObject() as RenderBox; + _touchStartGlobalPoint = renderBox.localToGlobal(_touchStartLocalPoint); + _resetDoubleTapTimer(); + _handleDoubleTap(); + } + + widget.zoomPanBehavior?.handleEvent(event); + } + + void _resetDoubleTapTimer() { + _pointerCount = 0; + if (_doubleTapTimer != null) { + _doubleTapTimer!.cancel(); + _doubleTapTimer = null; + } + } + + // This method called when start pinch zooming or panning action. + void _handleScaleStart(ScaleStartDetails details) { + if (widget.zoomPanBehavior != null && + !_zoomLevelAnimationController.isAnimating && + !_focalLatLngAnimationController.isAnimating) { + if (widget.zoomPanBehavior!.enablePinching || + widget.zoomPanBehavior!.enablePanning) { + _gestureType = null; + _touchStartOffset = + _getTouchStartOffset(details.localFocalPoint, details.focalPoint); + } + } + } + + Offset _getTouchStartOffset(Offset localFocalPoint, Offset globalFocalPoint) { + _maximumReachedScaleOnInteraction = 1.0; + _touchStartLocalPoint = localFocalPoint; + _touchStartGlobalPoint = globalFocalPoint; + _touchStartZoomLevel = _currentZoomLevel; + _calculateLatLngFromTappedPoint(); + final MapLatLng touchStartFocalLatLng = + _calculateVisibleLatLng(_touchStartLocalPoint, _touchStartZoomLevel); + return _pixelFromLatLng(touchStartFocalLatLng, _touchStartZoomLevel); + } + + // This method called when doing pinch zooming or panning action. + void _handleScaleUpdate(ScaleUpdateDetails details) { + if (widget.zoomPanBehavior != null && + !_doubleTapEnabled && + (widget.zoomPanBehavior!.enablePinching || + widget.zoomPanBehavior!.enablePanning)) { + final double zoomLevel = _touchStartZoomLevel + log(details.scale) / ln2; + if (zoomLevel > widget.zoomPanBehavior!.maxZoomLevel || + zoomLevel < widget.zoomPanBehavior!.minZoomLevel) { + _resetDoubleTapTimer(); + return; + } + + _gestureType ??= _getGestureType(details.scale, details.localFocalPoint); + if (_gestureType == null) { + return; + } + + if (_controller!.localScale < details.scale) { + _maximumReachedScaleOnInteraction = details.scale; } - _controller.localScale = details.scale; + _resetDoubleTapTimer(); + _controller!.localScale = details.scale; final double newZoomLevel = _getZoomLevel(_touchStartZoomLevel + log(details.scale) / ln2); - switch (_gestureType) { + switch (_gestureType!) { case Gesture.scale: - if (widget.zoomPanBehavior.enablePinching) { + if (widget.zoomPanBehavior!.enablePinching && + !_focalLatLngAnimationController.isAnimating) { final MapLatLng newFocalLatLng = _calculateVisibleLatLng(_touchStartLocalPoint, newZoomLevel); - _controller + _controller! ..isInInteractive = true ..gesture = _gestureType ..localScale = details.scale @@ -922,17 +1201,20 @@ class _MapTileLayerState extends State<_MapTileLayer> } return; case Gesture.pan: - if (widget.zoomPanBehavior.enablePanning) { + if (widget.zoomPanBehavior!.enablePanning && + !_zoomLevelAnimationController.isAnimating) { + _touchStartOffset ??= _getTouchStartOffset( + details.localFocalPoint, details.focalPoint); final MapLatLng newFocalLatLng = _calculateVisibleLatLng(details.localFocalPoint, newZoomLevel); final Offset newFocalOffset = _pixelFromLatLng(newFocalLatLng, newZoomLevel); - _controller + _controller! ..isInInteractive = true ..gesture = _gestureType ..localScale = 1.0 - ..panDistance = _touchStartOffset - newFocalOffset; + ..panDistance = _touchStartOffset! - newFocalOffset; _invokeOnPanning(newZoomLevel, localFocalPoint: details.localFocalPoint, globalFocalPoint: details.focalPoint, @@ -944,256 +1226,153 @@ class _MapTileLayerState extends State<_MapTileLayer> } } + // This method is called when the pinching or panning action ends. void _handleScaleEnd(ScaleEndDetails details) { - // Calculating the end focalLatLng based on the obtained velocity. - if (_gestureType == Gesture.pan && + if (widget.zoomPanBehavior != null && details.velocity.pixelsPerSecond.distance >= kMinFlingVelocity) { - final Offset currentPixelPoint = - _pixelFromLatLng(_currentFocalLatLng, _currentZoomLevel); - final FrictionSimulation frictionX = FrictionSimulation( - _frictionCoefficient, - currentPixelPoint.dx, - -details.velocity.pixelsPerSecond.dx, - ); - - final FrictionSimulation frictionY = FrictionSimulation( - _frictionCoefficient, - currentPixelPoint.dy, - -details.velocity.pixelsPerSecond.dy, - ); - - final double duration = - _getDuration(details.velocity.pixelsPerSecond.distance); - - final MapLatLng latLng = _latLngFromPixel( - Offset(frictionX.finalX, frictionY.finalX), - scale: _currentZoomLevel); - _gestureType = null; - _focalLatLngAnimationController.duration = - Duration(milliseconds: (duration * 650).round()); - widget.zoomPanBehavior.focalLatLng = latLng; + _resetDoubleTapTimer(); + if (_gestureType == Gesture.pan && + widget.zoomPanBehavior!.enablePanning && + !_zoomLevelAnimationController.isAnimating) { + _startFlingAnimationForPanning(details); + } else if (_gestureType == Gesture.scale && + widget.zoomPanBehavior!.enablePinching && + !_focalLatLngAnimationController.isAnimating) { + _startFlingAnimationForPinching(details); + } } - _controller.localScale = 1.0; + _controller!.localScale = 1.0; + _touchStartOffset = null; _gestureType = null; _panDetails = null; _zoomDetails = null; _invalidateSublayer(); } - // Calculate the time at which movement comes to a stop. - double _getDuration(double distance) { - return log(10.0 / distance) / log(_frictionCoefficient / 100); - } - - void _invalidateSublayer() { - _controller - ..isInInteractive = false - ..normalize = Offset.zero - ..gesture = null - ..localScale = 1.0; - if (_hasSublayer) { - _controller?.notifyRefreshListeners(); - } - } - - // This method called for both pinch zooming action and mouse wheel zooming - // action for passing [MapZoomDetails] parameters to notify user about the - // zooming details. - void _invokeOnZooming(double newZoomLevel, - [Offset localFocalPoint, - Offset globalFocalPoint, - MapLatLng newFocalLatLng]) { - final Rect newVisibleBounds = Rect.fromCenter( - center: _pixelFromLatLng(newFocalLatLng, newZoomLevel), - width: _size.width, - height: _size.height); - - _zoomDetails = MapZoomDetails( - localFocalPoint: localFocalPoint, - globalFocalPoint: globalFocalPoint, - previousZoomLevel: widget.zoomPanBehavior.zoomLevel, - newZoomLevel: newZoomLevel, - previousVisibleBounds: _zoomDetails != null - ? _zoomDetails.newVisibleBounds - : _controller.visibleLatLngBounds, - newVisibleBounds: MapLatLngBounds( - _latLngFromPixel(newVisibleBounds.topRight, scale: newZoomLevel), - _latLngFromPixel(newVisibleBounds.bottomLeft, - scale: newZoomLevel))); - _newFocalLatLng = newFocalLatLng; - if (widget.onWillZoom == null || widget.onWillZoom(_zoomDetails)) { - widget.zoomPanBehavior?.onZooming(_zoomDetails); - } - } - - void _invokeOnPanning(double newZoomLevel, - {Offset localFocalPoint, - Offset globalFocalPoint, - Offset touchStartLocalPoint, - MapLatLng newFocalLatLng}) { - final Rect newVisibleBounds = Rect.fromCenter( - center: _pixelFromLatLng(newFocalLatLng, newZoomLevel), - width: _size.width, - height: _size.height); - - _panDetails = MapPanDetails( - globalFocalPoint: globalFocalPoint, - localFocalPoint: localFocalPoint, - zoomLevel: widget.zoomPanBehavior.zoomLevel, - delta: localFocalPoint - touchStartLocalPoint, - previousVisibleBounds: _panDetails != null - ? _panDetails.newVisibleBounds - : _controller.visibleLatLngBounds, - newVisibleBounds: MapLatLngBounds( - _latLngFromPixel(newVisibleBounds.topRight, scale: newZoomLevel), - _latLngFromPixel(newVisibleBounds.bottomLeft, - scale: newZoomLevel))); - _newFocalLatLng = newFocalLatLng; - if (widget.onWillPan == null || widget.onWillPan(_panDetails)) { - widget.zoomPanBehavior?.onPanning(_panDetails); - } - } - - // Restricting new zoom level value either to - // [widget.zoomPanBehavior.minZoomLevel] or - // [widget.zoomPanBehavior.maxZoomLevel] if the new zoom level value is not - // in zoom level range. - double _getZoomLevel(double zoomLevel) { - return zoomLevel.isNaN - ? widget.zoomPanBehavior.minZoomLevel - : interpolateValue( - zoomLevel, - widget.zoomPanBehavior.minZoomLevel, - widget.zoomPanBehavior.maxZoomLevel, - ); - } - - // This method called when dynamically changing the [zoomLevel] property of - // ZoomPanBehavior. - void _handleZoomTo(double zoomLevel, {MapLatLng latlng}) { - if (_gestureType == null) { - _zoomLevelTween.begin = _currentZoomLevel; - _zoomLevelTween.end = widget.zoomPanBehavior.zoomLevel; - _zoomLevelAnimationController.forward(from: 0.0); - } - } - - // This method called when dynamically changing the [focalLatLng] property of - // ZoomPanBehavior. - void _handlePanTo(MapLatLng latlng) { - if (_gestureType == null) { - _focalLatLngTween.begin = _currentFocalLatLng; - _focalLatLngTween.end = widget.zoomPanBehavior.focalLatLng; - _focalLatLngAnimationController.forward(from: 0.0); - } - } - - void _handleZoomLevelAnimation() { - if (_zoomLevelTween.end != null) { - _currentZoomLevel = _zoomLevelTween.evaluate(_zoomLevelAnimation); - } - _handleZoomPanAnimation(); - } - - void _handleFocalLatLngAnimation() { - if (_focalLatLngTween.end != null) { - _currentFocalLatLng = _focalLatLngTween.evaluate(_focalLatLngAnimation); + void _handleDoubleTap() { + if (_gestureType == null && widget.zoomPanBehavior != null) { + _zoomLevelAnimationController.duration = + const Duration(milliseconds: 200); + _touchStartZoomLevel = _currentZoomLevel; + _calculateLatLngFromTappedPoint(); + // When double tapping, we had incremented zoom level by one from the + // currentZoomLevel. + double newZoomLevel = _currentZoomLevel + 1; + newZoomLevel = newZoomLevel.clamp(widget.zoomPanBehavior!.minZoomLevel, + widget.zoomPanBehavior!.maxZoomLevel); + if (newZoomLevel == _currentZoomLevel) { + return; + } + _doubleTapEnabled = true; + final MapLatLng newFocalLatLng = + _calculateVisibleLatLng(_touchStartLocalPoint, newZoomLevel); + _invokeOnZooming(newZoomLevel, _touchStartLocalPoint, + _touchStartGlobalPoint, newFocalLatLng); } - _handleZoomPanAnimation(); } - void _handleZoomPanAnimation() { - setState(() { - _handleTransform(); - if (_hasSublayer) { - _controller.visibleFocalLatLng = _currentFocalLatLng; - _controller.tileLayerZoomLevel = _currentZoomLevel; - _invalidateSublayer(); - } else { - _controller.notifyRefreshListeners(); - } - }); + // This methods performs fling animation for panning. + void _startFlingAnimationForPanning(ScaleEndDetails details) { + _isFlingAnimationActive = true; + final Offset currentFocalOffset = + _pixelFromLatLng(_currentFocalLatLng, _currentZoomLevel); + final MapLatLng newFocalLatLng = _pixelToLatLng( + Offset( + FrictionSimulation( + _frictionCoefficient, + currentFocalOffset.dx, + -details.velocity.pixelsPerSecond.dx, + ).finalX, + FrictionSimulation( + _frictionCoefficient, + currentFocalOffset.dy, + -details.velocity.pixelsPerSecond.dy, + ).finalX), + _currentZoomLevel); + _gestureType = null; + _focalLatLngAnimationController.duration = _getFlingAnimationDuration( + details.velocity.pixelsPerSecond.distance, _frictionCoefficient); + widget.zoomPanBehavior!.focalLatLng = newFocalLatLng; } - void _handleZoomLevelAnimationStatusChange(AnimationStatus status) { - if (status == AnimationStatus.completed) { - if (_zoomLevelTween.end != null && !_isZoomedUsingToolbar) { - _invokeOnZooming(widget.zoomPanBehavior.zoomLevel, Offset.zero, - Offset.zero, _currentFocalLatLng); - } else { - _isZoomedUsingToolbar = false; - } - } + // This methods performs fling animation for pinching. + void _startFlingAnimationForPinching(ScaleEndDetails details) { + _isFlingAnimationActive = true; + final int direction = + _controller!.localScale >= _maximumReachedScaleOnInteraction ? 1 : -1; + double newZoomLevel = _currentZoomLevel + + (direction * + (details.velocity.pixelsPerSecond.distance / kMaxFlingVelocity) * + widget.zoomPanBehavior!.maxZoomLevel); + newZoomLevel = newZoomLevel.clamp(widget.zoomPanBehavior!.minZoomLevel, + widget.zoomPanBehavior!.maxZoomLevel); + _gestureType = null; + _zoomLevelAnimationController.duration = _getFlingAnimationDuration( + details.velocity.pixelsPerSecond.distance, _frictionCoefficient); + widget.zoomPanBehavior!.zoomLevel = newZoomLevel; } - void _handleFocalLatLngAnimationStatusChange(AnimationStatus status) { - if (status == AnimationStatus.completed && _focalLatLngTween.end != null) { - final Offset previousFocalPoint = - _pixelFromLatLng(_focalLatLngTween.begin, _currentZoomLevel); - final Offset currentFocalPoint = - _pixelFromLatLng(_focalLatLngTween.end, _currentZoomLevel); - _invokeOnPanning(_currentZoomLevel, - localFocalPoint: currentFocalPoint, - touchStartLocalPoint: previousFocalPoint, - newFocalLatLng: _currentFocalLatLng); - _focalLatLngAnimationController.duration = - const Duration(milliseconds: 650); - } + // Returns the animation duration for the given distance and + // friction co-efficient. + Duration _getFlingAnimationDuration( + double distance, double frictionCoefficient) { + final int duration = + (log(10.0 / distance) / log(frictionCoefficient / 100)).round(); + final int durationInMs = (duration * 1000).round(); + return Duration(milliseconds: durationInMs < 350 ? 350 : durationInMs); } // This method called when doing mouse wheel action in web. void _handleMouseWheelZooming(PointerSignalEvent event) { if (widget.zoomPanBehavior != null && - widget.zoomPanBehavior.enablePinching) { + widget.zoomPanBehavior!.enablePinching) { if (event is PointerScrollEvent) { - widget.zoomPanBehavior.handleEvent(event); + widget.zoomPanBehavior!.handleEvent(event); _gestureType = Gesture.scale; _mouseStartZoomLevel ??= _currentZoomLevel; _mouseCenterLatLng ??= _currentFocalLatLng; _mouseStartLocalPoint ??= event.localPosition; _mouseStartGlobalPoint ??= event.position; + final MapZoomPanBehavior zoomPanBehavior = widget.zoomPanBehavior!; final Offset localPointCenterDiff = Offset( - (_size.width / 2) - _mouseStartLocalPoint.dx, - (_size.height / 2) - _mouseStartLocalPoint.dy); + (_size!.width / 2) - _mouseStartLocalPoint!.dx, + (_size!.height / 2) - _mouseStartLocalPoint!.dy); final Offset actualCenterPixelPosition = - _pixelFromLatLng(_mouseCenterLatLng, _mouseStartZoomLevel); + _pixelFromLatLng(_mouseCenterLatLng!, _mouseStartZoomLevel!); final Offset point = actualCenterPixelPosition - localPointCenterDiff; - _touchStartLatLng = - _latLngFromPixel(point, scale: _mouseStartZoomLevel); - double scale = _controller.tileLayerLocalScale - - (event.scrollDelta.dy / _size.height); - _controller.tileLayerLocalScale = scale; + _touchStartLatLng = _pixelToLatLng(point, _mouseStartZoomLevel!); + double scale = _controller!.tileLayerLocalScale - + (event.scrollDelta.dy / _size!.height); + _controller!.tileLayerLocalScale = scale; final double newZoomLevel = - _getZoomLevel(_mouseStartZoomLevel + log(scale) / ln2); + _getZoomLevel(_mouseStartZoomLevel! + log(scale) / ln2); // If the scale * _mouseStartZoomLevel value goes beyond - // minZoomLevel, setted the min scale value. - if (scale * _mouseStartZoomLevel < - widget.zoomPanBehavior.minZoomLevel) { - scale = widget.zoomPanBehavior.minZoomLevel / _mouseStartZoomLevel; + // minZoomLevel, set the min scale value. + if (scale * _mouseStartZoomLevel! < zoomPanBehavior.minZoomLevel) { + scale = zoomPanBehavior.minZoomLevel / _mouseStartZoomLevel!; } // If the scale * _mouseStartZoomLevel value goes beyond - // maxZoomLevel, setted the max scale value. - else if (scale * _mouseStartZoomLevel > - widget.zoomPanBehavior.maxZoomLevel) { - scale = widget.zoomPanBehavior.maxZoomLevel / _mouseStartZoomLevel; + // maxZoomLevel, set the max scale value. + else if (scale * _mouseStartZoomLevel! > zoomPanBehavior.maxZoomLevel) { + scale = zoomPanBehavior.maxZoomLevel / _mouseStartZoomLevel!; } - _controller + _controller! ..isInInteractive = true ..gesture = _gestureType ..localScale = scale ..pinchCenter = event.localPosition; final MapLatLng newFocalLatLng = _calculateVisibleLatLng(event.localPosition, newZoomLevel); - _invokeOnZooming(newZoomLevel, _mouseStartLocalPoint, - _mouseStartGlobalPoint, newFocalLatLng); + _invokeOnZooming(newZoomLevel, _mouseStartLocalPoint!, + _mouseStartGlobalPoint!, newFocalLatLng); _zoomingDelayTimer?.cancel(); _zoomingDelayTimer = Timer(const Duration(milliseconds: 300), () { _invalidateSublayer(); - _controller.tileLayerLocalScale = 1.0; + _controller!.tileLayerLocalScale = 1.0; _mouseStartZoomLevel = null; _mouseStartLocalPoint = null; _mouseStartGlobalPoint = null; @@ -1204,38 +1383,98 @@ class _MapTileLayerState extends State<_MapTileLayer> } } - void _handlePointerDown(PointerDownEvent event) { - widget.zoomPanBehavior?.handleEvent(event); - } - - void _handlePointerMove(PointerMoveEvent event) { - widget.zoomPanBehavior?.handleEvent(event); - } + // Calculate the actual latLng value in the place of tapped location. + void _calculateLatLngFromTappedPoint() { + final Offset localPointCenterDiff = Offset( + (_size!.width / 2) - _touchStartLocalPoint.dx, + (_size!.height / 2) - _touchStartLocalPoint.dy); + final Offset actualCenterPixelPosition = + _pixelFromLatLng(_currentFocalLatLng, _touchStartZoomLevel); + final Offset point = actualCenterPixelPosition - localPointCenterDiff; + _touchStartLatLng = _pixelToLatLng(point, _touchStartZoomLevel); - void _handlePointerUp(PointerUpEvent event) { - widget.zoomPanBehavior?.handleEvent(event); + final Rect newVisibleBounds = Rect.fromCenter( + center: _pixelFromLatLng(_currentFocalLatLng, _touchStartZoomLevel), + width: _size!.width, + height: _size!.height); + final MapLatLngBounds newVisibleLatLng = MapLatLngBounds( + _pixelToLatLng(newVisibleBounds.topRight, _touchStartZoomLevel), + _pixelToLatLng(newVisibleBounds.bottomLeft, _touchStartZoomLevel)); + _zoomDetails = MapZoomDetails(newVisibleBounds: newVisibleLatLng); + _panDetails = MapPanDetails(newVisibleBounds: newVisibleLatLng); + _newFocalLatLng = _currentFocalLatLng; } // Check whether gesture type is scale or pan. - Gesture _getGestureType(double scale, Offset focalPoint) { + Gesture? _getGestureType(double scale, Offset focalPoint) { // The minimum distance required to start scale or pan gesture. final int minScaleDistance = 3; - if (_touchStartLocalPoint != null) { - final Offset distance = focalPoint - _touchStartLocalPoint; - if (scale == 1) { - final Offset distance = focalPoint - _touchStartLocalPoint; - return distance.dx.abs() > minScaleDistance || - distance.dy.abs() > minScaleDistance - ? Gesture.pan - : null; - } - - return (distance.dx.abs() > minScaleDistance || - distance.dy.abs() > minScaleDistance) - ? Gesture.scale + final Offset distance = focalPoint - _touchStartLocalPoint; + if (scale == 1) { + return distance.dx.abs() > minScaleDistance || + distance.dy.abs() > minScaleDistance + ? Gesture.pan : null; } - return null; + + return (distance.dx.abs() > minScaleDistance || + distance.dy.abs() > minScaleDistance) + ? Gesture.scale + : null; + } + + // Calculate new focal coordinate value while scaling, panning or mouse wheel + // actions. + MapLatLng _calculateVisibleLatLng( + Offset localFocalPoint, double newZoomLevel) { + final Offset focalStartPoint = + _pixelFromLatLng(_touchStartLatLng, newZoomLevel); + final Offset newCenterPoint = focalStartPoint - + localFocalPoint + + Offset(_size!.width / 2, _size!.height / 2); + return _pixelToLatLng(newCenterPoint, newZoomLevel); + } + + // Restricting new zoom level value either to + // [widget.zoomPanBehavior.minZoomLevel] or + // [widget.zoomPanBehavior.maxZoomLevel] if the new zoom level value is not + // in zoom level range. + double _getZoomLevel(double zoomLevel) { + return zoomLevel.isNaN + ? widget.zoomPanBehavior!.minZoomLevel + : zoomLevel.clamp( + widget.zoomPanBehavior!.minZoomLevel, + widget.zoomPanBehavior!.maxZoomLevel, + ); + } + + // This method called for both pinch zooming action and mouse wheel zooming + // action for passing [MapZoomDetails] parameters to notify user about the + // zooming details. + void _invokeOnZooming(double newZoomLevel, + [Offset? localFocalPoint, + Offset? globalFocalPoint, + MapLatLng? newFocalLatLng]) { + final Rect newVisibleBounds = Rect.fromCenter( + center: _pixelFromLatLng(newFocalLatLng!, newZoomLevel), + width: _size!.width, + height: _size!.height); + + _zoomDetails = MapZoomDetails( + localFocalPoint: localFocalPoint, + globalFocalPoint: globalFocalPoint, + previousZoomLevel: widget.zoomPanBehavior!.zoomLevel, + newZoomLevel: newZoomLevel, + previousVisibleBounds: _zoomDetails != null + ? _zoomDetails!.newVisibleBounds + : _controller!.visibleLatLngBounds, + newVisibleBounds: MapLatLngBounds( + _pixelToLatLng(newVisibleBounds.topRight, newZoomLevel), + _pixelToLatLng(newVisibleBounds.bottomLeft, newZoomLevel))); + _newFocalLatLng = newFocalLatLng; + if (widget.onWillZoom == null || widget.onWillZoom!(_zoomDetails!)) { + widget.zoomPanBehavior?.onZooming(_zoomDetails!); + } } // This method invoked when user override the [onZooming] method in @@ -1243,150 +1482,198 @@ class _MapTileLayerState extends State<_MapTileLayer> void _handleZooming(MapZoomDetails details) { if (_gestureType != null) { // Updating map while pinching and scrolling. - _currentZoomLevel = details.newZoomLevel; - _controller.tileLayerZoomLevel = _currentZoomLevel; + _currentZoomLevel = details.newZoomLevel!; + _controller!.tileZoomLevel = _currentZoomLevel; _currentFocalLatLng = _newFocalLatLng; - _controller.visibleFocalLatLng = _currentFocalLatLng; - _controller.visibleLatLngBounds = details.newVisibleBounds; - widget.zoomPanBehavior.focalLatLng = _currentFocalLatLng; + _controller!.visibleFocalLatLng = _currentFocalLatLng; + _controller!.visibleLatLngBounds = details.newVisibleBounds; + widget.zoomPanBehavior!.focalLatLng = _currentFocalLatLng; setState(() { _handleTransform(); }); } else { - // Updating map via toolbar. + // Updating map via toolbar or double tap or fling animation end. final Rect newVisibleBounds = Rect.fromCenter( - center: _pixelFromLatLng(_currentFocalLatLng, details.newZoomLevel), - width: _size.width, - height: _size.height); - _controller.visibleLatLngBounds = MapLatLngBounds( - _latLngFromPixel(newVisibleBounds.topRight, - scale: details.newZoomLevel), - _latLngFromPixel(newVisibleBounds.bottomLeft, - scale: details.newZoomLevel)); - _isZoomedUsingToolbar = true; + center: _pixelFromLatLng(_currentFocalLatLng, details.newZoomLevel!), + width: _size!.width, + height: _size!.height); + _controller!.visibleLatLngBounds = MapLatLngBounds( + _pixelToLatLng(newVisibleBounds.topRight, details.newZoomLevel!), + _pixelToLatLng(newVisibleBounds.bottomLeft, details.newZoomLevel!)); + _isZoomedUsingToolbar = _currentZoomLevel != details.newZoomLevel!; } - widget.zoomPanBehavior.zoomLevel = details.newZoomLevel; + widget.zoomPanBehavior!.zoomLevel = details.newZoomLevel!; } - // This method invoked when user override the [onPanning] method in - // [ZoomPanBehavior] and called [super.onPanning(details)]. - void _handlePanning(MapPanDetails details) { + // This method called when dynamically changing the [zoomLevel] property of + // ZoomPanBehavior. + void _handleZoomLevelChange(double zoomLevel, {MapLatLng? latlng}) { + if (_gestureType == null && + _currentZoomLevel != widget.zoomPanBehavior!.zoomLevel) { + if (!_isFlingAnimationActive && !_doubleTapEnabled) { + _zoomLevelAnimationController.duration = + const Duration(milliseconds: 650); + } + + _zoomLevelTween.begin = _currentZoomLevel; + _zoomLevelTween.end = widget.zoomPanBehavior!.zoomLevel; + _zoomLevelAnimationController.forward(from: 0.0); + } + } + + void _handleZoomLevelAnimation() { + if (_zoomLevelTween.end != null) { + _currentZoomLevel = _zoomLevelTween.evaluate(_isFlingAnimationActive + ? _flingZoomLevelCurvedAnimation + : _zoomLevelCurvedAnimation); + } + + if (_isFlingAnimationActive || _doubleTapEnabled) { + _currentFocalLatLng = + _calculateVisibleLatLng(_touchStartLocalPoint, _currentZoomLevel); + } + + _handleZoomPanAnimation(); + } + + void _handleZoomPanAnimation() { setState(() { - _currentFocalLatLng = _newFocalLatLng; - widget.zoomPanBehavior.focalLatLng = _currentFocalLatLng; - _controller.visibleFocalLatLng = _currentFocalLatLng; - _controller.visibleLatLngBounds = details.newVisibleBounds; _handleTransform(); + if (_hasSublayer) { + _controller!.visibleFocalLatLng = _currentFocalLatLng; + _controller!.tileZoomLevel = _currentZoomLevel; + _invalidateSublayer(); + } else { + _controller!.notifyRefreshListeners(); + } }); } - // This method invoked when user called the [reset] method in - // [ZoomPanBehavior]. - void _handleReset() { - widget.zoomPanBehavior.zoomLevel = widget.zoomPanBehavior.minZoomLevel; + void _handleZoomLevelAnimationStatusChange(AnimationStatus status) { + if (status == AnimationStatus.completed) { + _handleZoomingAnimationEnd(); + } } - // Calculate new focal coordinate value while scaling, panning or mouse wheel - // actions. - MapLatLng _calculateVisibleLatLng( - Offset localFocalPoint, double newZoomLevel) { - final Offset focalStartPoint = - _pixelFromLatLng(_touchStartLatLng, newZoomLevel); - final Offset newCenterPoint = focalStartPoint - - localFocalPoint + - Offset(_size.width / 2, _size.height / 2); - return _latLngFromPixel(newCenterPoint, scale: newZoomLevel); + void _handleZoomingAnimationEnd() { + _isFlingAnimationActive = false; + if (_zoomLevelTween.end != null && + !_isZoomedUsingToolbar && + !_doubleTapEnabled) { + _touchStartLocalPoint = + _pixelFromLatLng(_currentFocalLatLng, _zoomLevelTween.begin!); + // ignore: avoid_as + final RenderBox renderBox = context.findRenderObject() as RenderBox; + _invokeOnZooming(_currentZoomLevel, _touchStartLocalPoint, + renderBox.localToGlobal(_touchStartLocalPoint), _currentFocalLatLng); + } else { + _isZoomedUsingToolbar = false; + _doubleTapEnabled = false; + } } - // This method used to move each tile based on the amount of - // translation and scaling value in the respective levels. - Widget _getPositionedTiles(_MapTile tile) { - final Offset tilePos = tile.tilePos; - final MapZoomLevel level = tile.level; - final double tileLeftPos = - (tilePos.dx * level.scale) + level.translatePoint.dx; - final double tileTopPos = - (tilePos.dy * level.scale) + level.translatePoint.dy; + void _invokeOnPanning(double newZoomLevel, + {required Offset localFocalPoint, + required Offset globalFocalPoint, + required Offset touchStartLocalPoint, + required MapLatLng newFocalLatLng}) { + final Rect newVisibleBounds = Rect.fromCenter( + center: _pixelFromLatLng(newFocalLatLng, newZoomLevel), + width: _size!.width, + height: _size!.height); - return Positioned( - left: tileLeftPos, - top: tileTopPos, - width: tileSize * level.scale, - height: tileSize * level.scale, - child: tile.image, - ); + _panDetails = MapPanDetails( + globalFocalPoint: globalFocalPoint, + localFocalPoint: localFocalPoint, + zoomLevel: widget.zoomPanBehavior!.zoomLevel, + delta: localFocalPoint - touchStartLocalPoint, + previousVisibleBounds: _panDetails != null + ? _panDetails!.newVisibleBounds + : _controller!.visibleLatLngBounds, + newVisibleBounds: MapLatLngBounds( + _pixelToLatLng(newVisibleBounds.topRight, newZoomLevel), + _pixelToLatLng(newVisibleBounds.bottomLeft, newZoomLevel))); + _newFocalLatLng = newFocalLatLng; + if (widget.onWillPan == null || widget.onWillPan!(_panDetails!)) { + widget.zoomPanBehavior?.onPanning(_panDetails!); + } } - // This method is used to request visual tiles and store it in a [_tiles] - // collection. - void _addTile(_MapTileCoordinate tileFactor) { - final String tileFactorToKey = _tileFactorToKey(tileFactor); - final String url = _getTileUrl(tileFactor.x, tileFactor.y, tileFactor.z); - final MapZoomLevel level = _levels[tileFactor.z]; - final double tileLeftPos = (tileFactor.x * tileSize) - level.origin.dx; - final double tileTopPos = (tileFactor.y * tileSize) - level.origin.dy; - - _tiles[tileFactorToKey] = _MapTile( - coordinates: tileFactor, - xyzKey: tileFactorToKey, - tilePos: Offset(tileLeftPos, tileTopPos), - level: _levels[tileFactor.z], - image: Image.network( - url, - fit: BoxFit.fill, - ), - ); + // This method invoked when user override the [onPanning] method in + // [ZoomPanBehavior] and called [super.onPanning(details)]. + void _handlePanning(MapPanDetails details) { + setState(() { + _currentFocalLatLng = _newFocalLatLng; + widget.zoomPanBehavior!.focalLatLng = _currentFocalLatLng; + _controller!.visibleFocalLatLng = _currentFocalLatLng; + _controller!.visibleLatLngBounds = details.newVisibleBounds; + _handleTransform(); + }); } - // Converts the [urlTemplate] format into respective map - // providers URL format. - String _getTileUrl(int x, int y, int z) { - String previousLetter; - String currentLetter; - String url = ''; + // This method called when dynamically changing the [focalLatLng] property of + // ZoomPanBehavior. + void _handleFocalLatLngChange(MapLatLng? latlng) { + if (_gestureType == null && + _currentFocalLatLng != widget.zoomPanBehavior!.focalLatLng) { + if (!_isFlingAnimationActive) { + _focalLatLngAnimationController.duration = + const Duration(milliseconds: 650); + } - if (widget.urlTemplate.contains('{quadkey}')) { - final String quadKey = _getQuadKey(x, y, z); - final splittedQuad = widget.urlTemplate.split('{quadkey}'); - url = splittedQuad[0] + quadKey + splittedQuad[1]; - } else { - for (int i = 0; i < widget.urlTemplate.length; i++) { - previousLetter = currentLetter; - currentLetter = widget.urlTemplate[i]; + _focalLatLngTween.begin = _currentFocalLatLng; + _focalLatLngTween.end = widget.zoomPanBehavior!.focalLatLng; + _focalLatLngAnimationController.forward(from: 0.0); + } + } - if (previousLetter == '{' && widget.urlTemplate[i] == 'x') { - currentLetter = x.toString(); - } else if (previousLetter == '{' && widget.urlTemplate[i] == 'y') { - currentLetter = y.toString(); - } else if (previousLetter == '{' && widget.urlTemplate[i] == 'z') { - currentLetter = z.toString(); - } + void _handleFocalLatLngAnimation() { + if (_focalLatLngTween.end != null) { + _currentFocalLatLng = _focalLatLngTween.evaluate(_isFlingAnimationActive + ? _flingFocalLatLngCurvedAnimation + : _focalLatLngCurvedAnimation); + } - if (widget.urlTemplate[i] != '{' && widget.urlTemplate[i] != '}') { - url += currentLetter; - } - } + _handleZoomPanAnimation(); + } + + void _handleFocalLatLngAnimationStatusChange(AnimationStatus status) { + if (status == AnimationStatus.completed && _focalLatLngTween.end != null) { + _handleFocalLatLngAnimationEnd(); } - return url; } - // Converts the xyz coordinate into quadKey for bing map. - String _getQuadKey(int x, int y, int z) { - final StringBuffer quadKey = StringBuffer(); - for (int i = z; i > 0; i--) { - int digit = 0; - final int mask = 1 << (i - 1); - if ((x & mask) != 0) { - digit++; - } - if ((y & mask) != 0) { - digit++; - digit++; - } - quadKey.write(digit); + void _handleFocalLatLngAnimationEnd() { + _isFlingAnimationActive = false; + final Offset previousFocalPoint = + _pixelFromLatLng(_focalLatLngTween.begin!, _currentZoomLevel); + final Offset currentFocalPoint = + _pixelFromLatLng(_currentFocalLatLng, _currentZoomLevel); + // ignore: avoid_as + final RenderBox renderBox = context.findRenderObject() as RenderBox; + _invokeOnPanning(_currentZoomLevel, + localFocalPoint: currentFocalPoint, + globalFocalPoint: renderBox.localToGlobal(currentFocalPoint), + touchStartLocalPoint: previousFocalPoint, + newFocalLatLng: _currentFocalLatLng); + } + + // This method invoked when user called the [reset] method in + // [ZoomPanBehavior]. + void _handleReset() { + widget.zoomPanBehavior!.zoomLevel = widget.zoomPanBehavior!.minZoomLevel; + } + + void _invalidateSublayer() { + _controller! + ..isInInteractive = false + ..normalize = Offset.zero + ..gesture = null + ..localScale = 1.0; + if (_hasSublayer) { + _controller!.notifyRefreshListeners(); } - return quadKey.toString(); } // Scale and transform the existing level tiles if the current zoom level @@ -1409,153 +1696,44 @@ class _MapTileLayerState extends State<_MapTileLayer> // scaling. void _updateZoomLevelTransforms(MapLatLng center, double zoom) { for (final key in _levels.keys) { - _updateZoomLevelTransform(_levels[key], center, zoom); - } - } - - // Calculate amount of scale and translation for each zoom level while - // scaling. - void _updateZoomLevelTransform( - MapZoomLevel level, MapLatLng center, double zoom) { - if (level.origin == null) { - return; - } - final double currentScale = - getTotalTileWidth(zoom) / getTotalTileWidth(level.zoom); - final Offset scaledPixelOrigin = _getLevelOrigin(center, zoom); - level.translatePoint = Offset( - (level.origin.dx * currentScale) - scaledPixelOrigin.dx, - (level.origin.dy * currentScale) - scaledPixelOrigin.dy); - level.scale = currentScale; - - if (level.zoom == _nextZoomLevel) { - _controller.tileCurrentLevelDetails.translatePoint = level.translatePoint; - _controller.tileCurrentLevelDetails.scale = level.scale; - } - } - - // Calculate tiles visual bounds origin for each new zoom level. - MapZoomLevel _updateZoomLevel() { - final int zoom = _nextZoomLevel; - if (zoom == null) { - return null; - } - - // Remove zoom-out level tiles from the [_tiles] collection when doing - // zoom-out action. - final List removePrevLevels = []; - for (final level in _levels.entries) { - final double key = level.key; - - if (_levels.entries.length > 1) { - if (zoom < key) { - removePrevLevels.add(key); - } - } - } - - for (final levelKey in removePrevLevels) { - _removeTilesAtZoom(levelKey); - _levels.remove(levelKey); - } - - MapZoomLevel level = _levels[zoom]; - if (level == null) { - // The [_levels] collection contains each integer zoom level origin, - // scale and translationPoint. The scale and translationPoint are - // calculated in every pinch zoom level for scaling the tiles. - level = _levels[zoom.toDouble()] = MapZoomLevel(); - level.origin = - _getLevelOrigin(_currentFocalLatLng, zoom.toDouble()) ?? Offset.zero; - level.zoom = zoom.toDouble(); - - if (_gestureType != null && _gestureType == Gesture.scale) { - _updateZoomLevelTransform( - level, _currentFocalLatLng, _currentZoomLevel); - } else { - level.scale = 1.0; - level.translatePoint = Offset.zero; - } - } - - // Recalculate tiles bounds origin for all existing zoom level - // when size changed. - if (_isSizeChanged) { - _updateZoomLevelTransforms(_currentFocalLatLng, _currentZoomLevel); - } - - final double totalTileWidth = getTotalTileWidth(level.zoom); - _controller - ..totalTileSize = Size(totalTileWidth, totalTileWidth) - ..tileCurrentLevelDetails = _levels[_nextZoomLevel]; - return level; - } - - Offset _getLevelOrigin(MapLatLng center, double zoom) { - final Offset halfSize = Offset(_size.width / 2.0, _size.height / 2.0); - return _pixelFromLatLng(center, zoom) - halfSize; - } - - void _removeTilesAtZoom(double zoom) { - final List removePrevTiles = []; - for (final tile in _tiles.entries) { - if (tile.value.coordinates.z != zoom) { - continue; - } - removePrevTiles.add(tile.key); - } - - for (final key in removePrevTiles) { - _removeTile(key); - } - } - - void _removeTile(String key) { - final _MapTile tile = _tiles[key]; - if (tile == null) { - return; + _updateZoomLevelTransform(_levels[key]!, center, zoom); } - - _tiles.remove(key); - } - - String _tileFactorToKey(_MapTileCoordinate tileFactor) { - return '${tileFactor.x}:${tileFactor.y}:${tileFactor.z}'; } - void refreshMarkers([MarkerAction action, List indices]) { + void refreshMarkers(MarkerAction action, [List? indices]) { MapMarker marker; + final MapTileLayerController controller = widget.controller!; switch (action) { case MarkerAction.insert: - int index = indices[0]; - assert(index <= widget.controller._markersCount); - if (index > widget.controller._markersCount) { - index = widget.controller._markersCount; + int index = indices![0]; + assert(index <= controller._markersCount); + if (index > controller._markersCount) { + index = controller._markersCount; } - marker = widget.markerBuilder(context, index); - if (index < widget.controller._markersCount) { - _markers.insert(index, marker); - } else if (index == widget.controller._markersCount) { - _markers.add(marker); + marker = widget.markerBuilder!(context, index); + if (index < controller._markersCount) { + _markers!.insert(index, marker); + } else if (index == controller._markersCount) { + _markers!.add(marker); } - widget.controller._markersCount++; + controller._markersCount++; break; case MarkerAction.removeAt: - final int index = indices[0]; - assert(index < widget.controller._markersCount); - _markers.removeAt(index); - widget.controller._markersCount--; + final int index = indices![0]; + assert(index < controller._markersCount); + _markers!.removeAt(index); + controller._markersCount--; break; case MarkerAction.replace: - for (final int index in indices) { - assert(index < widget.controller._markersCount); - marker = widget.markerBuilder(context, index); - _markers[index] = marker; + for (final int index in indices!) { + assert(index < controller._markersCount); + marker = widget.markerBuilder!(context, index); + _markers![index] = marker; } break; case MarkerAction.clear: - _markers.clear(); - widget.controller._markersCount = 0; + _markers!.clear(); + controller._markersCount = 0; break; } @@ -1563,223 +1741,137 @@ class _MapTileLayerState extends State<_MapTileLayer> // Rebuilds to visually update the markers when it was updated or added. }); } -} - -/// Represents the tile factor like x position, y position and scale value. -class _MapTileCoordinate { - /// Creates a [_MapTileCoordinate]. - _MapTileCoordinate(this.x, this.y, this.z); - - /// Represents the x-coordinate of the tile. - int x; - - /// Represents the y-coordinate of the tile. - int y; - - /// Represents the scale value of the tile. - int z; - - double distanceTo(Offset startPoint, Offset endPoint) { - final double dx = startPoint.dx - endPoint.dx; - final double dy = startPoint.dy - endPoint.dy; - return sqrt(dx * dx + dy * dy); - } - - @override - String toString() => '_MapTileCoordinate($x, $y, $z)'; - @override - bool operator ==(dynamic other) { - if (other is _MapTileCoordinate) { - return x == other.x && y == other.y && z == other.z; + void _initializeMapController() { + if (_controller == null) { + _ancestor = context + .dependOnInheritedWidgetOfExactType()!; + _controller = _ancestor.controller + ..addZoomingListener(_handleZooming) + ..addPanningListener(_handlePanning) + ..addResetListener(_handleReset) + ..tileZoomLevel = _currentZoomLevel + ..visibleFocalLatLng = _currentFocalLatLng + ..onZoomLevelChange = _handleZoomLevelChange + ..onPanChange = _handleFocalLatLngChange; } - return false; } @override - int get hashCode => hashValues(x.hashCode, y.hashCode, z.hashCode); -} - -/// Provides the information about the current and previous zoom level. -class MapZoomLevel { - /// Represents the visual tiles origin. - Offset origin; - - /// Represents the tile zoom level. - double zoom; - - /// Provides the distance moved from the origin when doing pinch zooming. - Offset translatePoint; - - /// Represents the fractional zoom value. - double scale; -} - -/// Represents the information about the tile. -class _MapTile { - /// Creates a [_MapTile]. - _MapTile({ - this.xyzKey, - this.coordinates, - this.tilePos, - this.level, - this.image, - }); - - /// Provides a unique key for each tile. The format of [xyzKey] looks `x:y:z`, - /// where z denotes zoom level, x and y denotes tile coordinates. - final String xyzKey; - - /// Represents the tile x-position, y-position and scale value. - final _MapTileCoordinate coordinates; - - /// Represents the tile left and top position. - final Offset tilePos; - - /// Represents the scale transform details of the tile. - final MapZoomLevel level; + void initState() { + super.initState(); + _currentFocalLatLng = + widget.zoomPanBehavior?.focalLatLng ?? widget.initialFocalLatLng; + _currentZoomLevel = (widget.zoomPanBehavior != null && + widget.zoomPanBehavior!.zoomLevel != 1) + ? widget.zoomPanBehavior!.zoomLevel + : widget.initialZoomLevel.toDouble(); + widget.zoomPanBehavior?.zoomLevel = _currentZoomLevel; + _nextZoomLevel = _currentZoomLevel.floor(); + if (widget.controller != null) { + widget.controller!._markersCount = widget.initialMarkersCount; + } - /// Add tile to the image. - final Widget image; + widget.controller?._state = this; + _markers = []; + for (int i = 0; i < widget.initialMarkersCount; i++) { + final MapMarker marker = widget.markerBuilder!(context, i); + _markers!.add(marker); + } - @override - int get hashCode => coordinates.hashCode; + _zoomLevelAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 650)) + ..addListener(_handleZoomLevelAnimation) + ..addStatusListener(_handleZoomLevelAnimationStatusChange); + _focalLatLngAnimationController = AnimationController( + vsync: this, duration: const Duration(milliseconds: 650)) + ..addListener(_handleFocalLatLngAnimation) + ..addStatusListener(_handleFocalLatLngAnimationStatusChange); + _flingZoomLevelCurvedAnimation = CurvedAnimation( + parent: _zoomLevelAnimationController, curve: Curves.decelerate); + _flingFocalLatLngCurvedAnimation = CurvedAnimation( + parent: _focalLatLngAnimationController, curve: Curves.decelerate); + _zoomLevelCurvedAnimation = CurvedAnimation( + parent: _zoomLevelAnimationController, curve: Curves.easeInOut); + _focalLatLngCurvedAnimation = CurvedAnimation( + parent: _focalLatLngAnimationController, curve: Curves.easeInOut); - @override - bool operator ==(other) { - return other is _MapTile && coordinates == other.coordinates; + _focalLatLngTween = MapLatLngTween(); + _zoomLevelTween = Tween(); + _hasSublayer = widget.sublayers != null && widget.sublayers!.isNotEmpty; + widget.controller?.addListener(refreshMarkers); } -} - -class _TileLayerMarkerContainer extends Stack { - _TileLayerMarkerContainer({ - Key tooltipKey, - IndexedWidgetBuilder markerTooltipBuilder, - List children, - MapController controller, - }) : tooltipKey = tooltipKey, - markerTooltipBuilder = markerTooltipBuilder, - controller = controller, - super( - children: children ?? [], - ); - - final Key tooltipKey; - final IndexedWidgetBuilder markerTooltipBuilder; - final MapController controller; @override - _MapRenderTileMarker createRenderObject(BuildContext context) { - return _MapRenderTileMarker( - tooltipKey: tooltipKey, - markerTooltipBuilder: markerTooltipBuilder, - controller: controller, - markerContainer: this, - ); + void didChangeDependencies() { + _initializeMapController(); + super.didChangeDependencies(); } @override - void updateRenderObject( - BuildContext context, _MapRenderTileMarker renderObject) { - renderObject - ..markerTooltipBuilder = markerTooltipBuilder - ..markerContainer = this; - } -} - -class _MapRenderTileMarker extends RenderStack { - _MapRenderTileMarker({ - this.tooltipKey, - this.markerTooltipBuilder, - this.state, - this.markerContainer, - this.controller, - }); - - Key tooltipKey; - IndexedWidgetBuilder markerTooltipBuilder; - MapController controller; - _MapTileLayerState state; - _TileLayerMarkerContainer markerContainer; - - int getMarkerIndex(MapMarker marker) { - return markerContainer.children.indexOf(marker); - } - - void _handleZooming(MapZoomDetails details) { - markNeedsLayout(); - } - - void _handlePanning(MapPanDetails details) { - markNeedsLayout(); - } - - void _handleReset() { - markNeedsLayout(); - } - - void _handleRefresh() { - markNeedsLayout(); + void didUpdateWidget(_TileLayer oldWidget) { + _hasSublayer = widget.sublayers != null && widget.sublayers!.isNotEmpty; + super.didUpdateWidget(oldWidget); } @override - void attach(PipelineOwner owner) { - super.attach(owner); - controller - ..addZoomingListener(_handleZooming) - ..addPanningListener(_handlePanning) - ..addResetListener(_handleReset) - ..addRefreshListener(_handleRefresh); - } + void dispose() { + if (_controller != null) { + _controller! + ..removeZoomingListener(_handleZooming) + ..removePanningListener(_handlePanning) + ..removeResetListener(_handleReset); + _controller = null; + } - @override - void detach() { - controller - ..removeZoomingListener(_handleZooming) - ..removePanningListener(_handlePanning) - ..removeResetListener(_handleReset) - ..removeRefreshListener(_handleRefresh); - super.detach(); - } + _zoomLevelAnimationController + ..removeListener(_handleZoomLevelAnimation) + ..removeStatusListener(_handleZoomLevelAnimationStatusChange) + ..dispose(); - @override - void performLayout() { - size = controller.tileLayerBoxSize; - RenderBox child = firstChild; - while (child != null) { - final RenderMapMarker marker = child; - final StackParentData childParentData = child.parentData; - child.layout(constraints, parentUsesSize: true); - final Offset pixelValues = _pixelFromLatLng( - MapLatLng(marker.latitude, marker.longitude), - controller.tileCurrentLevelDetails.zoom); - final MapZoomLevel level = controller.tileCurrentLevelDetails; - final Offset translationOffset = pixelValues - level.origin; - childParentData.offset = Offset(translationOffset.dx * level.scale, - translationOffset.dy * level.scale) + - level.translatePoint - - Offset(child.size.width / 2, child.size.height / 2); - child = childParentData.nextSibling; - } + _focalLatLngAnimationController + ..removeListener(_handleFocalLatLngAnimation) + ..removeStatusListener(_handleFocalLatLngAnimationStatusChange) + ..dispose(); + + widget.controller?.removeListener(refreshMarkers); + _controller?.dispose(); + _markers!.clear(); + _tiles.clear(); + _levels.clear(); + super.dispose(); } @override - void paint(PaintingContext context, Offset offset) { - RenderBox child = firstChild; - final Rect visibleRect = - Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height); - while (child != null) { - final StackParentData childParentData = child.parentData; - final Rect childRect = Rect.fromLTWH( - childParentData.offset.dx + offset.dx, - childParentData.offset.dy + offset.dy, - child.size.width, - child.size.height); - if (visibleRect.overlaps(childRect)) { - context.paintChild(child, offset + childParentData.offset); - } - - child = childParentData.nextSibling; - } + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + _isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows || + themeData.platform == TargetPlatform.linux; + _mapsThemeData = SfMapsTheme.of(context)!; + _mapsThemeData = _mapsThemeData.copyWith( + tooltipColor: widget.tooltipSettings.color ?? _mapsThemeData.tooltipColor, + tooltipStrokeColor: widget.tooltipSettings.strokeColor ?? + _mapsThemeData.tooltipStrokeColor, + tooltipStrokeWidth: widget.tooltipSettings.strokeWidth ?? + _mapsThemeData.tooltipStrokeWidth, + tooltipBorderRadius: _mapsThemeData.tooltipBorderRadius + .resolve(Directionality.of(context)), + ); + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + if (_size != null) { + _isSizeChanged = _size!.width != constraints.maxWidth || + _size!.height != constraints.maxHeight; + } + _size = getBoxSize(constraints); + _controller!.tileLayerBoxSize = _size; + return Container( + width: _size!.width, + height: _size!.height, + child: _tileLayerElements); + }, + ); } } diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart b/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart index 3f09f34b5..72a2a1a17 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart @@ -9,41 +9,88 @@ import 'package:syncfusion_flutter_core/theme.dart'; import '../../maps.dart'; import '../behavior/zoom_pan_behavior.dart'; -import '../controller/default_controller.dart'; -import '../elements/shapes.dart'; -import '../enum.dart'; +import '../common.dart'; +import '../controller/map_controller.dart'; import '../layer/layer_base.dart'; import '../utils.dart'; +import 'shape_layer.dart'; +import 'tile_layer.dart'; -double _getDesiredValue(double value, MapController controller) { - return controller.tileCurrentLevelDetails != null - ? value / controller.tileCurrentLevelDetails.scale - : value / +/// This enum supported only for circle and polygon shapes. +enum _VectorFillType { inner, outer } + +double _getCurrentWidth(double width, MapController controller) { + return controller.layerType == LayerType.tile + ? width / controller.tileCurrentLevelDetails.scale + : width / (controller.gesture == Gesture.scale ? controller.localScale : 1); } -Offset _getTranslationOffset(MapController controller, bool isTileLayer) { - return isTileLayer - ? -controller.tileCurrentLevelDetails.origin +Offset _getTranslation(MapController controller) { + return controller.layerType == LayerType.tile + ? -controller.tileCurrentLevelDetails.origin! : controller.shapeLayerOffset; } -Offset _updatePointsToScaledPosition(Offset point, MapController controller) { - if (controller.tileCurrentLevelDetails != null) { - return Offset(point.dx * controller.tileCurrentLevelDetails.scale, - point.dy * controller.tileCurrentLevelDetails.scale) + +Offset _getScaledOffset(Offset offset, MapController controller) { + if (controller.layerType == LayerType.tile) { + return Offset(offset.dx * controller.tileCurrentLevelDetails.scale, + offset.dy * controller.tileCurrentLevelDetails.scale) + controller.tileCurrentLevelDetails.translatePoint; } - return point; + return offset; +} + +void _drawInvertedPath( + PaintingContext context, + Path path, + MapController controller, + Paint fillPaint, + Paint strokePaint, + Offset offset) { + // Path.combine option is not supported in web platform, so we have obtained + // inverted rendering using [Path.fillType] for web and [Path.combine] for + // other platforms. + if (kIsWeb) { + context.canvas.drawPath( + Path() + ..addPath(path, offset) + ..addRect(controller.visibleBounds!) + ..fillType = PathFillType.evenOdd, + fillPaint); + context.canvas.drawPath(path, strokePaint); + } else { + context.canvas + ..drawPath( + Path.combine( + PathOperation.difference, + Path()..addRect(controller.visibleBounds!), + path, + ), + fillPaint) + ..drawPath(path, strokePaint); + } +} + +Color _getHoverColor( + Color? elementColor, Color layerColor, SfMapsThemeData themeData) { + final Color color = elementColor ?? layerColor; + final bool canAdjustHoverOpacity = + double.parse(color.opacity.toStringAsFixed(2)) != hoverColorOpacity; + return themeData.shapeHoverColor != null && + themeData.shapeHoverColor != Colors.transparent + ? themeData.shapeHoverColor! + : color.withOpacity( + canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); } /// Base class for all vector layers. abstract class MapVectorLayer extends MapSublayer { /// Creates a [MapVectorLayer]. const MapVectorLayer({ - Key key, - IndexedWidgetBuilder tooltipBuilder, + Key? key, + IndexedWidgetBuilder? tooltipBuilder, }) : super(key: key, tooltipBuilder: tooltipBuilder); } @@ -51,33 +98,64 @@ abstract class MapVectorLayer extends MapSublayer { /// [MapTileLayer]. /// /// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// body: SfMaps( -/// layers: [ -/// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "continent", -/// ), -/// sublayers: [ -/// MapLineLayer( -/// lines: List.generate( -/// lines.length, -/// (int index) { -/// return MapLine( -/// from: lines[index].from, -/// to: lines[index].to, -/// ); -/// }, -/// ).toSet(), -/// ), -/// ], -/// ), -/// ], -/// ), -/// ); +/// List _lines; +/// MapZoomPanBehavior _zoomPanBehavior; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// _zoomPanBehavior = MapZoomPanBehavior( +/// focalLatLng: MapLatLng(40.7128, -95.3698), +/// zoomLevel: 3, +/// ); +/// +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ); +/// +/// _lines = [ +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), +/// ]; +/// +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// zoomPanBehavior: _zoomPanBehavior, +/// sublayers: [ +/// MapLineLayer( +/// lines: List.generate( +/// _lines.length, +/// (int index) { +/// return MapLine( +/// from: _lines[index].from, +/// to: _lines[index].to, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], +/// ), +/// ); +/// } +/// +/// class Model { +/// Model(this.from, this.to); +/// +/// MapLatLng from; +/// MapLatLng to; /// } /// ``` /// @@ -89,14 +167,13 @@ abstract class MapVectorLayer extends MapSublayer { class MapLineLayer extends MapVectorLayer { /// Creates the [MapLineLayer]. MapLineLayer({ - Key key, - @required this.lines, + Key? key, + required this.lines, this.animation, this.color, this.width = 2, - IndexedWidgetBuilder tooltipBuilder, - }) : assert(lines != null), - super(key: key, tooltipBuilder: tooltipBuilder); + IndexedWidgetBuilder? tooltipBuilder, + }) : super(key: key, tooltipBuilder: tooltipBuilder); /// A collection of [MapLine]. /// @@ -104,33 +181,64 @@ class MapLineLayer extends MapVectorLayer { /// straight line. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapLineLayer( - /// lines: List.generate( - /// lines.length, - /// (int index) { - /// return MapLine( - /// from: lines[index].from, - /// to: lines[index].to, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// List _lines; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _lines = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _lines.length, + /// (int index) { + /// return MapLine( + /// from: _lines[index].from, + /// to: _lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } /// ``` final Set lines; @@ -142,62 +250,87 @@ class MapLineLayer extends MapVectorLayer { /// the animation flow, curve, duration and listen to the animation status. /// /// ```dart - /// AnimationController _animationController; - /// Animation _animation; + /// AnimationController _animationController; + /// Animation _animation; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// List _lines; /// - /// @override - /// void initState() { - /// _animationController = AnimationController( - /// duration: Duration(seconds: 3), - /// vsync: this, - /// ); + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); /// - /// _animation = CurvedAnimation( - /// parent: _animationController, - /// curve: Curves.easeInOut), - /// ); + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); /// - /// _animationController.forward(from: 0); - /// super.initState(); - /// } + /// _lines = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; /// - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapLineLayer( - /// lines: List.generate( - /// lines.length, - /// (int index) { - /// return MapLine( - /// from: lines[index].from, - /// to: lines[index].to, - /// ); - /// }, - /// ).toSet(), - /// animation: _animation, - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// _animationController = AnimationController( + /// duration: Duration(seconds: 3), + /// vsync: this, + /// ); + /// + /// _animation = + /// CurvedAnimation(parent: _animationController, + /// curve: Curves.easeInOut); + /// + /// _animationController.forward(from: 0); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _lines.length, + /// (int index) { + /// return MapLine( + /// from: _lines[index].from, + /// to: _lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// animation: _animation, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// @override + /// void dispose() { + /// _animationController?.dispose(); + /// super.dispose(); + /// } /// } /// - /// @override - /// void dispose() { - /// animationController?.dispose(); - /// super.dispose(); - /// } + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; + /// } /// ``` - final Animation animation; + final Animation? animation; /// The color of all the [lines]. /// @@ -205,41 +338,71 @@ class MapLineLayer extends MapVectorLayer { /// property. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapLineLayer( - /// lines: List.generate( - /// lines.length, - /// (int index) { - /// return MapLine( - /// from: lines[index].from, - /// to: lines[index].to, - /// ); - /// }, - /// ).toSet(), - /// color: Colors.green, - /// width: 2, - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// List _lines; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _lines = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _lines.length, + /// (int index) { + /// return MapLine( + /// from: _lines[index].from, + /// to: _lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// color: Colors.green, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } /// ``` /// /// See also: /// [width], to set the width. - final Color color; + final Color? color; /// The width of all the [lines]. /// @@ -247,34 +410,65 @@ class MapLineLayer extends MapVectorLayer { /// property. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapLineLayer( - /// lines: List.generate( - /// lines.length, - /// (int index) { - /// return MapLine( - /// from: lines[index].from, - /// to: lines[index].to, - /// ); - /// }, - /// ).toSet(), - /// width: 2, - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// List _lines; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _lines = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _lines.length, + /// (int index) { + /// return MapLine( + /// from: _lines[index].from, + /// to: _lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// width: 2, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } /// ``` /// @@ -293,23 +487,41 @@ class MapLineLayer extends MapVectorLayer { lineLayer: this, ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + if (lines.isNotEmpty) { + final _DebugVectorShapeTree pointerTreeNode = + _DebugVectorShapeTree(lines); + properties.add(pointerTreeNode.toDiagnosticsNode()); + } + properties.add(ObjectFlagProperty.has('animation', animation)); + properties.add(ObjectFlagProperty.has( + 'tooltip', tooltipBuilder)); + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + properties.add(DoubleProperty('width', width)); + } } class _MapLineLayer extends StatefulWidget { _MapLineLayer({ - this.lines, - this.animation, - this.color, - this.width, - this.tooltipBuilder, - this.lineLayer, + required this.lines, + required this.animation, + required this.color, + required this.width, + required this.tooltipBuilder, + required this.lineLayer, }); final Set lines; - final Animation animation; - final Color color; + final Animation? animation; + final Color? color; final double width; - final IndexedWidgetBuilder tooltipBuilder; + final IndexedWidgetBuilder? tooltipBuilder; final MapLineLayer lineLayer; @override @@ -318,8 +530,10 @@ class _MapLineLayer extends StatefulWidget { class _MapLineLayerState extends State<_MapLineLayer> with SingleTickerProviderStateMixin { - AnimationController _hoverAnimationController; - SfMapsThemeData _mapsThemeData; + MapController? _controller; + late AnimationController _hoverAnimationController; + late SfMapsThemeData _mapsThemeData; + late MapLayerInheritedWidget ancestor; @override void initState() { @@ -328,9 +542,20 @@ class _MapLineLayerState extends State<_MapLineLayer> vsync: this, duration: const Duration(milliseconds: 250)); } + @override + void didChangeDependencies() { + if (_controller == null) { + ancestor = context + .dependOnInheritedWidgetOfExactType()!; + _controller = ancestor.controller; + } + super.didChangeDependencies(); + } + @override void dispose() { - _hoverAnimationController?.dispose(); + _controller = null; + _hoverAnimationController.dispose(); super.dispose(); } @@ -339,9 +564,11 @@ class _MapLineLayerState extends State<_MapLineLayer> final ThemeData themeData = Theme.of(context); final bool isDesktop = kIsWeb || themeData.platform == TargetPlatform.macOS || - themeData.platform == TargetPlatform.windows; - _mapsThemeData = SfMapsTheme.of(context); + themeData.platform == TargetPlatform.windows || + themeData.platform == TargetPlatform.linux; + _mapsThemeData = SfMapsTheme.of(context)!; return _MapLineLayerRenderObject( + controller: _controller, lines: widget.lines, animation: widget.animation, color: widget.color ?? @@ -354,36 +581,42 @@ class _MapLineLayerState extends State<_MapLineLayer> themeData: _mapsThemeData, isDesktop: isDesktop, hoverAnimationController: _hoverAnimationController, + state: this, ); } } class _MapLineLayerRenderObject extends LeafRenderObjectWidget { const _MapLineLayerRenderObject({ - this.lines, - this.animation, - this.color, - this.width, - this.tooltipBuilder, - this.lineLayer, - this.themeData, - this.isDesktop, - this.hoverAnimationController, + required this.controller, + required this.lines, + required this.animation, + required this.color, + required this.width, + required this.tooltipBuilder, + required this.lineLayer, + required this.themeData, + required this.isDesktop, + required this.hoverAnimationController, + required this.state, }); + final MapController? controller; final Set lines; - final Animation animation; + final Animation? animation; final Color color; final double width; - final IndexedWidgetBuilder tooltipBuilder; + final IndexedWidgetBuilder? tooltipBuilder; final MapLineLayer lineLayer; final SfMapsThemeData themeData; final bool isDesktop; final AnimationController hoverAnimationController; + final _MapLineLayerState state; @override _RenderMapLine createRenderObject(BuildContext context) { return _RenderMapLine( + controller: controller, lines: lines, animation: animation, color: color, @@ -394,6 +627,7 @@ class _MapLineLayerRenderObject extends LeafRenderObjectWidget { themeData: themeData, isDesktop: isDesktop, hoverAnimationController: hoverAnimationController, + state: state, ); } @@ -401,29 +635,32 @@ class _MapLineLayerRenderObject extends LeafRenderObjectWidget { void updateRenderObject(BuildContext context, _RenderMapLine renderObject) { renderObject ..lines = lines - .._animation = animation + ..animation = animation ..color = color ..width = width ..tooltipBuilder = tooltipBuilder ..context = context - ..lineLayer = lineLayer - ..themeData = themeData; + ..themeData = themeData + ..state = state; } } class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { _RenderMapLine({ - Set lines, - Animation animation, - Color color, - double width, - IndexedWidgetBuilder tooltipBuilder, - BuildContext context, - MapLineLayer lineLayer, - SfMapsThemeData themeData, - bool isDesktop, - AnimationController hoverAnimationController, - }) : _lines = lines, + required MapController? controller, + required Set lines, + required Animation? animation, + required Color color, + required double width, + required IndexedWidgetBuilder? tooltipBuilder, + required BuildContext context, + required MapLineLayer lineLayer, + required SfMapsThemeData themeData, + required bool isDesktop, + required AnimationController hoverAnimationController, + required this.state, + }) : _controller = controller, + _lines = lines, _animation = animation, _color = color, _width = width, @@ -438,52 +675,54 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { _reverseHoverColor = ColorTween(); _hoverColorAnimation = CurvedAnimation( parent: hoverAnimationController, curve: Curves.easeInOut); - linesInList = _lines?.toList(); + linesInList = _lines.toList(); _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; } - TapGestureRecognizer _tapGestureRecognizer; - MapController controller; - AnimationController hoverAnimationController; - RenderSublayerContainer vectorLayerContainer; - MapLineLayer lineLayer; - BuildContext context; - MapLine selectedLine; + final MapController? _controller; + _MapLineLayerState state; int selectedIndex = -1; double touchTolerance = 5; - List linesInList; - List selectedLinePoints; - Animation _hoverColorAnimation; - ColorTween _forwardHoverColor; - ColorTween _reverseHoverColor; - MapLine _previousHoverItem; - MapLine _currentHoverItem; - bool isDesktop; + + late TapGestureRecognizer _tapGestureRecognizer; + late AnimationController hoverAnimationController; + late MapLineLayer lineLayer; + late BuildContext context; + late Animation _hoverColorAnimation; + late ColorTween _forwardHoverColor; + late ColorTween _reverseHoverColor; + late bool isDesktop; + + MapLine? selectedLine; + List? linesInList; + List? selectedLinePoints; + MapLine? _previousHoverItem; + MapLine? _currentHoverItem; Set get lines => _lines; Set _lines; - set lines(Set value) { + set lines(Set? value) { assert(value != null); if (_lines == value || value == null) { return; } _lines = value; - linesInList = _lines?.toList(); + linesInList = _lines.toList(); markNeedsPaint(); } - Animation get animation => _animation; - Animation _animation; - set animation(Animation value) { + Animation? get animation => _animation; + Animation? _animation; + set animation(Animation? value) { if (_animation == value) { return; } _animation = value; } - IndexedWidgetBuilder get tooltipBuilder => _tooltipBuilder; - IndexedWidgetBuilder _tooltipBuilder; - set tooltipBuilder(IndexedWidgetBuilder value) { + IndexedWidgetBuilder? get tooltipBuilder => _tooltipBuilder; + IndexedWidgetBuilder? _tooltipBuilder; + set tooltipBuilder(IndexedWidgetBuilder? value) { if (_tooltipBuilder == value) { return; } @@ -528,8 +767,9 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { void _updateHoverItemTween() { if (isDesktop) { - final Color hoverStrokeColor = _getHoverColor(selectedLine); - final Color beginColor = selectedLine.color ?? _color; + final Color hoverStrokeColor = + _getHoverColor(selectedLine?.color, _color, _themeData); + final Color beginColor = selectedLine?.color ?? _color; if (_previousHoverItem != null) { _reverseHoverColor.begin = hoverStrokeColor; @@ -544,19 +784,8 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { } } - Color _getHoverColor(MapLine line) { - final Color color = line.color ?? _color; - final bool canAdjustHoverOpacity = - double.parse(color.opacity.toStringAsFixed(2)) != hoverColorOpacity; - return _themeData.shapeHoverColor != null && - _themeData.shapeHoverColor != Colors.transparent - ? _themeData.shapeHoverColor - : color.withOpacity( - canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); - } - void _handleTapUp(TapUpDetails details) { - selectedLine.onTap?.call(); + selectedLine?.onTap?.call(); _handleInteraction(details.localPosition); } @@ -567,10 +796,11 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { _updateHoverItemTween(); } - final RenderSublayerContainer vectorParent = parent; final ShapeLayerChildRenderBoxBase tooltipRenderer = - vectorParent.tooltipKey?.currentContext?.findRenderObject(); - tooltipRenderer?.hideTooltip(); + _controller!.tooltipKey!.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; + tooltipRenderer.hideTooltip(); } void _handleZooming(MapZoomDetails details) { @@ -581,22 +811,16 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } - void _handleReset() { - markNeedsPaint(); - } - - void _handleRefresh() { - markNeedsPaint(); - } - - void _handleInteraction(Offset position) { - final RenderSublayerContainer vectorParent = parent; - if (vectorParent.tooltipKey != null && _tooltipBuilder != null) { + void _handleInteraction(Offset position, + [PointerKind kind = PointerKind.touch]) { + if (_controller?.tooltipKey != null && _tooltipBuilder != null) { final ShapeLayerChildRenderBoxBase tooltipRenderer = - vectorParent.tooltipKey.currentContext.findRenderObject(); - if (selectedLinePoints != null && selectedLinePoints.isNotEmpty) { - final Offset startPoint = selectedLinePoints[0]; - final Offset endPoint = selectedLinePoints[1]; + _controller!.tooltipKey!.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; + if (selectedLinePoints != null && selectedLinePoints!.isNotEmpty) { + final Offset startPoint = selectedLinePoints![0]; + final Offset endPoint = selectedLinePoints![1]; final Offset lineMidPosition = Offset( min(startPoint.dx, endPoint.dx) + ((startPoint.dx - endPoint.dx).abs() / 2), @@ -608,7 +832,8 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { selectedIndex, null, MapLayerElement.vector, - vectorParent.getSublayerIndex(lineLayer), + kind, + state.ancestor.sublayers!.indexOf(lineLayer), position, ); } @@ -619,13 +844,13 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { MouseCursor get cursor => SystemMouseCursors.basic; @override - PointerEnterEventListener get onEnter => null; + PointerEnterEventListener? get onEnter => null; // As onHover property of MouseHoverAnnotation was removed only in the // beta channel, once it is moved to stable, will remove this property. @override // ignore: override_on_non_overriding_member - PointerHoverEventListener get onHover => null; + PointerHoverEventListener? get onHover => null; @override PointerExitEventListener get onExit => _handlePointerExit; @@ -636,16 +861,16 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { @override bool hitTestSelf(Offset position) { - if (_animation != null && !_animation.isCompleted) { + if (_animation != null && !_animation!.isCompleted) { return false; } - final bool isTileLayer = controller.tileCurrentLevelDetails != null; - final Size boxSize = isTileLayer ? controller.totalTileSize : size; - final Offset translationOffset = - _getTranslationOffset(controller, isTileLayer); - int index = linesInList.length - 1; - for (final MapLine line in linesInList?.reversed) { + final Size boxSize = _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; + final Offset translationOffset = _getTranslation(_controller!); + int index = linesInList!.length - 1; + for (final MapLine line in linesInList!.reversed) { final double width = line.width ?? _width; if (line.onTap != null || _tooltipBuilder != null || isDesktop) { final double actualTouchTolerance = @@ -655,23 +880,23 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { line.from.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor, + _controller!.shapeLayerSizeFactor, ); Offset endPoint = pixelFromLatLng( line.to.latitude, line.to.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor, + _controller!.shapeLayerSizeFactor, ); - startPoint = _updatePointsToScaledPosition(startPoint, controller); - endPoint = _updatePointsToScaledPosition(endPoint, controller); + startPoint = _getScaledOffset(startPoint, _controller!); + endPoint = _getScaledOffset(endPoint, _controller!); if (_liesPointOnLine( startPoint, endPoint, actualTouchTolerance, position)) { selectedLine = line; selectedIndex = index; - selectedLinePoints + selectedLinePoints! ..clear() ..add(startPoint) ..add(endPoint); @@ -688,45 +913,45 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { void handleEvent(PointerEvent event, HitTestEntry entry) { if (event is PointerDownEvent) { _tapGestureRecognizer.addPointer(event); - } else if (event is PointerHoverEvent) { - if (isDesktop && _currentHoverItem != selectedLine) { + } else if (event is PointerHoverEvent && isDesktop) { + if (_currentHoverItem != selectedLine) { _previousHoverItem = _currentHoverItem; _currentHoverItem = selectedLine; _updateHoverItemTween(); } - final RenderBox renderBox = context.findRenderObject(); - _handleInteraction(renderBox.globalToLocal(event.position)); + // ignore: avoid_as + final RenderBox renderBox = context.findRenderObject() as RenderBox; + _handleInteraction( + renderBox.globalToLocal(event.position), PointerKind.hover); } } @override void attach(PipelineOwner owner) { super.attach(owner); - vectorLayerContainer = parent; - controller = vectorLayerContainer.controller; - if (controller != null) { - controller + if (_controller != null) { + _controller! ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) - ..addResetListener(_handleReset) - ..addRefreshListener(_handleRefresh); + ..addResetListener(markNeedsPaint) + ..addRefreshListener(markNeedsPaint); } _animation?.addListener(markNeedsPaint); - _hoverColorAnimation?.addListener(markNeedsPaint); + _hoverColorAnimation.addListener(markNeedsPaint); } @override void detach() { - if (controller != null) { - controller + if (_controller != null) { + _controller! ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) - ..removeResetListener(_handleReset) - ..removeRefreshListener(_handleRefresh); + ..removeResetListener(markNeedsPaint) + ..removeRefreshListener(markNeedsPaint); } _animation?.removeListener(markNeedsPaint); - _hoverColorAnimation?.removeListener(markNeedsPaint); + _hoverColorAnimation.removeListener(markNeedsPaint); linesInList?.clear(); linesInList = null; super.detach(); @@ -742,21 +967,21 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { @override void paint(PaintingContext context, Offset offset) { - if (_animation != null && _animation.value == 0.0) { + if (_animation != null && _animation!.value == 0.0) { return; } context.canvas.save(); Offset startPoint; Offset endPoint; - final bool isTileLayer = controller.tileCurrentLevelDetails != null; final Paint paint = Paint() ..isAntiAlias = true ..style = PaintingStyle.stroke; Path path = Path(); - final Size boxSize = isTileLayer ? controller.totalTileSize : size; - final Offset translationOffset = - _getTranslationOffset(controller, isTileLayer); - controller.applyTransform(context, offset, true); + final Size boxSize = _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; + final Offset translationOffset = _getTranslation(_controller!); + _controller!.applyTransform(context, offset, true); for (final MapLine line in lines) { startPoint = pixelFromLatLng( @@ -764,35 +989,35 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { line.from.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor, + _controller!.shapeLayerSizeFactor, ); endPoint = pixelFromLatLng( line.to.latitude, line.to.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor, + _controller!.shapeLayerSizeFactor, ); if (_previousHoverItem != null && _previousHoverItem == line && _themeData.shapeHoverColor != Colors.transparent) { - paint.color = _reverseHoverColor.evaluate(_hoverColorAnimation); + paint.color = _reverseHoverColor.evaluate(_hoverColorAnimation)!; } else if (_currentHoverItem != null && selectedLine == line && _themeData.shapeHoverColor != Colors.transparent) { - paint.color = _forwardHoverColor.evaluate(_hoverColorAnimation); + paint.color = _forwardHoverColor.evaluate(_hoverColorAnimation)!; } else { paint.color = line.color ?? _color; } - paint.strokeWidth = _getDesiredValue(line.width ?? _width, controller); + paint.strokeWidth = _getCurrentWidth(line.width ?? _width, _controller!); path ..reset() ..moveTo(startPoint.dx, startPoint.dy) ..lineTo(endPoint.dx, endPoint.dy); if (_animation != null) { - path = _getAnimatedPath(path, _animation); + path = _getAnimatedPath(path, _animation!); } _drawDashedLine(context.canvas, line.dashArray, paint, path); } @@ -804,33 +1029,63 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { /// [MapTileLayer]. /// /// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// body: SfMaps( -/// layers: [ -/// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "continent", +/// List _arcs; +/// MapZoomPanBehavior _zoomPanBehavior; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// _zoomPanBehavior = MapZoomPanBehavior( +/// focalLatLng: MapLatLng(40.7128, -95.3698), +/// zoomLevel: 3, +/// ); +/// +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ); +/// +/// _arcs = [ +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// zoomPanBehavior: _zoomPanBehavior, +/// sublayers: [ +/// MapArcLayer( +/// arcs: List.generate( +/// _arcs.length, +/// (int index) { +/// return MapArc( +/// from: _arcs[index].from, +/// to: _arcs[index].to, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], /// ), -/// sublayers: [ -/// MapArcLayer( -/// arcs: List.generate( -/// arcs.length, -/// (int index) { -/// return MapArc( -/// from: arcs[index].from, -/// to: arcs[index].to, -/// ); -/// }, -/// ).toSet(), -/// ), -/// ], -/// ), -/// ], -/// ), -/// ); +/// ], +/// ), +/// ); +/// } +/// +/// class Model { +/// Model(this.from, this.to); +/// +/// MapLatLng from; +/// MapLatLng to; /// } /// ``` /// @@ -842,14 +1097,13 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { class MapArcLayer extends MapVectorLayer { /// Creates the [MapArcLayer]. MapArcLayer({ - Key key, - @required this.arcs, + Key? key, + required this.arcs, this.animation, this.color, this.width = 2, - IndexedWidgetBuilder tooltipBuilder, - }) : assert(arcs != null), - super(key: key, tooltipBuilder: tooltipBuilder); + IndexedWidgetBuilder? tooltipBuilder, + }) : super(key: key, tooltipBuilder: tooltipBuilder); /// A collection of [MapArc]. /// @@ -858,33 +1112,63 @@ class MapArcLayer extends MapVectorLayer { /// modified to change the appearance of the arcs. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _arcs; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _arcs = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// _arcs.length, + /// (int index) { + /// return MapArc( + /// from: _arcs[index].from, + /// to: _arcs[index].to, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], /// ), - /// sublayers: [ - /// MapArcLayer( - /// arcs: List.generate( - /// arcs.length, - /// (int index) { - /// return MapArc( - /// from: arcs[index].from, - /// to: arcs[index].to, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } /// ``` final Set arcs; @@ -896,62 +1180,87 @@ class MapArcLayer extends MapVectorLayer { /// the animation flow, curve, duration and listen to the animation status. /// /// ```dart - /// AnimationController _animationController; - /// Animation _animation; + /// AnimationController _animationController; + /// Animation _animation; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// List _arcs; /// - /// @override - /// void initState() { - /// _animationController = AnimationController( - /// duration: Duration(seconds: 3), - /// vsync: this, - /// ); + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); /// - /// _animation = CurvedAnimation( - /// parent: _animationController, - /// curve: Curves.easeInOut), - /// ); + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); /// - /// _animationController.forward(from: 0); - /// super.initState(); - /// } + /// _arcs = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; /// - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapArcLayer( - /// arcs: List.generate( - /// arcs.length, - /// (int index) { - /// return MapArc( - /// from: arcs[index].from, - /// to: arcs[index].to, - /// ); - /// }, - /// ).toSet(), - /// animation: _animation, - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// _animationController = AnimationController( + /// duration: Duration(seconds: 3), + /// vsync: this, + /// ); + /// + /// _animation = + /// CurvedAnimation(parent: _animationController, + /// curve: Curves.easeInOut); + /// + /// _animationController.forward(from: 0); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// _arcs.length, + /// (int index) { + /// return MapArc( + /// from: _arcs[index].from, + /// to: _arcs[index].to, + /// ); + /// }, + /// ).toSet(), + /// animation: _animation, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// @override + /// void dispose() { + /// _animationController?.dispose(); + /// super.dispose(); + /// } /// } /// - /// @override - /// void dispose() { - /// animationController?.dispose(); - /// super.dispose(); - /// } + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; + /// } /// ``` - final Animation animation; + final Animation? animation; /// The color of all the [arcs]. /// @@ -959,76 +1268,135 @@ class MapArcLayer extends MapVectorLayer { /// property. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapArcLayer( - /// arcs: List.generate( - /// arcs.length, - /// (int index) { - /// return MapArc( - /// from: arcs[index].from, - /// to: arcs[index].to, - /// ); - /// }, - /// ).toSet(), - /// color: Colors.green, - /// width: 2, - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); - /// } - /// ``` + /// List _arcs; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; /// - /// See also: - /// [width], to set the width for the map arc. - final Color color; - - /// The width of all the [arcs]. + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); /// - /// For setting width for each [MapArc], please check the [MapArc.width] + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _arcs = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// _arcs.length, + /// (int index) { + /// return MapArc( + /// from: _arcs[index].from, + /// to: _arcs[index].to, + /// ); + /// }, + /// ).toSet(), + /// color: Colors.green, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; + /// } + /// ``` + /// + /// See also: + /// [width], to set the width for the map arc. + final Color? color; + + /// The width of all the [arcs]. + /// + /// For setting width for each [MapArc], please check the [MapArc.width] /// property. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _arcs; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _arcs = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// _arcs.length, + /// (int index) { + /// return MapArc( + /// from: _arcs[index].from, + /// to: _arcs[index].to, + /// ); + /// }, + /// ).toSet(), + /// width: 2.0, + /// ), + /// ], /// ), - /// sublayers: [ - /// MapArcLayer( - /// arcs: List.generate( - /// arcs.length, - /// (int index) { - /// return MapArc( - /// from: arcs[index].from, - /// to: arcs[index].to, - /// ); - /// }, - /// ).toSet(), - /// width: 2, - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } /// ``` /// See also: @@ -1046,23 +1414,40 @@ class MapArcLayer extends MapVectorLayer { arcLayer: this, ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + if (arcs.isNotEmpty) { + final _DebugVectorShapeTree pointerTreeNode = _DebugVectorShapeTree(arcs); + properties.add(pointerTreeNode.toDiagnosticsNode()); + } + properties.add(ObjectFlagProperty.has('animation', animation)); + properties.add(ObjectFlagProperty.has( + 'tooltip', tooltipBuilder)); + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + properties.add(DoubleProperty('width', width)); + } } class _MapArcLayer extends StatefulWidget { const _MapArcLayer({ - this.arcs, - this.animation, - this.color, - this.width, - this.tooltipBuilder, - this.arcLayer, + required this.arcs, + required this.animation, + required this.color, + required this.width, + required this.tooltipBuilder, + required this.arcLayer, }); final Set arcs; - final Animation animation; - final Color color; + final Animation? animation; + final Color? color; final double width; - final IndexedWidgetBuilder tooltipBuilder; + final IndexedWidgetBuilder? tooltipBuilder; final MapArcLayer arcLayer; @override @@ -1071,8 +1456,10 @@ class _MapArcLayer extends StatefulWidget { class _MapArcLayerState extends State<_MapArcLayer> with SingleTickerProviderStateMixin { - AnimationController _hoverAnimationController; - SfMapsThemeData _mapsThemeData; + MapController? _controller; + late AnimationController _hoverAnimationController; + late SfMapsThemeData _mapsThemeData; + late MapLayerInheritedWidget ancestor; @override void initState() { @@ -1081,9 +1468,20 @@ class _MapArcLayerState extends State<_MapArcLayer> vsync: this, duration: const Duration(milliseconds: 250)); } + @override + void didChangeDependencies() { + if (_controller == null) { + ancestor = context + .dependOnInheritedWidgetOfExactType()!; + _controller = ancestor.controller; + } + super.didChangeDependencies(); + } + @override void dispose() { - _hoverAnimationController?.dispose(); + _controller = null; + _hoverAnimationController.dispose(); super.dispose(); } @@ -1092,9 +1490,11 @@ class _MapArcLayerState extends State<_MapArcLayer> final ThemeData themeData = Theme.of(context); final bool isDesktop = kIsWeb || themeData.platform == TargetPlatform.macOS || - themeData.platform == TargetPlatform.windows; - _mapsThemeData = SfMapsTheme.of(context); + themeData.platform == TargetPlatform.windows || + themeData.platform == TargetPlatform.linux; + _mapsThemeData = SfMapsTheme.of(context)!; return _MapArcLayerRenderObject( + controller: _controller, arcs: widget.arcs, animation: widget.animation, color: widget.color ?? @@ -1107,36 +1507,42 @@ class _MapArcLayerState extends State<_MapArcLayer> themeData: _mapsThemeData, isDesktop: isDesktop, hoverAnimationController: _hoverAnimationController, + state: this, ); } } class _MapArcLayerRenderObject extends LeafRenderObjectWidget { const _MapArcLayerRenderObject({ - this.arcs, - this.animation, - this.color, - this.width, - this.tooltipBuilder, - this.arcLayer, - this.themeData, - this.isDesktop, - this.hoverAnimationController, + required this.controller, + required this.arcs, + required this.animation, + required this.color, + required this.width, + required this.tooltipBuilder, + required this.arcLayer, + required this.themeData, + required this.isDesktop, + required this.hoverAnimationController, + required this.state, }); + final MapController? controller; final Set arcs; - final Animation animation; + final Animation? animation; final Color color; final double width; - final IndexedWidgetBuilder tooltipBuilder; + final IndexedWidgetBuilder? tooltipBuilder; final MapArcLayer arcLayer; final SfMapsThemeData themeData; final bool isDesktop; final AnimationController hoverAnimationController; + final _MapArcLayerState state; @override _RenderMapArc createRenderObject(BuildContext context) { return _RenderMapArc( + controller: controller, arcs: arcs, animation: animation, color: color, @@ -1147,6 +1553,7 @@ class _MapArcLayerRenderObject extends LeafRenderObjectWidget { themeData: themeData, isDesktop: isDesktop, hoverAnimationController: hoverAnimationController, + state: state, ); } @@ -1159,24 +1566,27 @@ class _MapArcLayerRenderObject extends LeafRenderObjectWidget { ..width = width ..tooltipBuilder = tooltipBuilder ..context = context - ..arcLayer = arcLayer - ..themeData = themeData; + ..themeData = themeData + ..state = state; } } class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { _RenderMapArc({ - Set arcs, - Animation animation, - Color color, - double width, - IndexedWidgetBuilder tooltipBuilder, - BuildContext context, - MapArcLayer arcLayer, - SfMapsThemeData themeData, - bool isDesktop, - AnimationController hoverAnimationController, - }) : _arcs = arcs, + required MapController? controller, + required Set arcs, + required Animation? animation, + required Color color, + required double width, + required IndexedWidgetBuilder? tooltipBuilder, + required BuildContext context, + required MapArcLayer arcLayer, + required SfMapsThemeData themeData, + required bool isDesktop, + required AnimationController hoverAnimationController, + required this.state, + }) : _controller = controller, + _arcs = arcs, _color = color, _width = width, _animation = animation, @@ -1190,43 +1600,45 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { _reverseHoverColor = ColorTween(); _hoverColorAnimation = CurvedAnimation( parent: hoverAnimationController, curve: Curves.easeInOut); - arcsInList = _arcs?.toList(); + arcsInList = _arcs.toList(); _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; } - TapGestureRecognizer _tapGestureRecognizer; - MapController controller; - RenderSublayerContainer vectorLayerContainer; - MapArcLayer arcLayer; - BuildContext context; - MapArc selectedArc; + final MapController? _controller; int selectedIndex = -1; double touchTolerance = 5; - List selectedLinePoints; - List arcsInList; - AnimationController hoverAnimationController; - Animation _hoverColorAnimation; - ColorTween _forwardHoverColor; - ColorTween _reverseHoverColor; - MapArc _previousHoverItem; - MapArc _currentHoverItem; - bool isDesktop; + + late _MapArcLayerState state; + late MapArcLayer arcLayer; + late BuildContext context; + late MapArc selectedArc; + late AnimationController hoverAnimationController; + late TapGestureRecognizer _tapGestureRecognizer; + late Animation _hoverColorAnimation; + late ColorTween _forwardHoverColor; + late ColorTween _reverseHoverColor; + late List selectedLinePoints; + late bool isDesktop; + + List? arcsInList; + MapArc? _previousHoverItem; + MapArc? _currentHoverItem; Set get arcs => _arcs; Set _arcs; - set arcs(Set value) { + set arcs(Set? value) { assert(value != null); if (_arcs == value || value == null) { return; } _arcs = value; - arcsInList = _arcs?.toList(); + arcsInList = _arcs.toList(); markNeedsPaint(); } - IndexedWidgetBuilder get tooltipBuilder => _tooltipBuilder; - IndexedWidgetBuilder _tooltipBuilder; - set tooltipBuilder(IndexedWidgetBuilder value) { + IndexedWidgetBuilder? get tooltipBuilder => _tooltipBuilder; + IndexedWidgetBuilder? _tooltipBuilder; + set tooltipBuilder(IndexedWidgetBuilder? value) { if (_tooltipBuilder == value) { return; } @@ -1268,9 +1680,9 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } - Animation get animation => _animation; - Animation _animation; - set animation(Animation value) { + Animation? get animation => _animation; + Animation? _animation; + set animation(Animation? value) { if (_animation == value) { return; } @@ -1279,7 +1691,8 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { void _updateHoverItemTween() { if (isDesktop) { - final Color hoverStrokeColor = _getHoverColor(selectedArc); + final Color hoverStrokeColor = + _getHoverColor(selectedArc.color, _color, _themeData); final Color beginColor = selectedArc.color ?? _color; if (_previousHoverItem != null) { @@ -1295,17 +1708,6 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { } } - Color _getHoverColor(MapArc arc) { - final Color color = arc.color ?? _color; - final bool canAdjustHoverOpacity = - double.parse(color.opacity.toStringAsFixed(2)) != hoverColorOpacity; - return _themeData.shapeHoverColor != null && - _themeData.shapeHoverColor != Colors.transparent - ? _themeData.shapeHoverColor - : color.withOpacity( - canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); - } - void _handleTapUp(TapUpDetails details) { selectedArc.onTap?.call(); _handleInteraction(details.localPosition); @@ -1318,10 +1720,11 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { _updateHoverItemTween(); } - final RenderSublayerContainer vectorParent = parent; final ShapeLayerChildRenderBoxBase tooltipRenderer = - vectorParent.tooltipKey?.currentContext?.findRenderObject(); - tooltipRenderer?.hideTooltip(); + _controller!.tooltipKey!.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; + tooltipRenderer.hideTooltip(); } void _handleZooming(MapZoomDetails details) { @@ -1332,25 +1735,17 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } - void _handleReset() { - markNeedsPaint(); - } - - void _handleRefresh() { - markNeedsPaint(); - } - @override MouseCursor get cursor => SystemMouseCursors.basic; @override - PointerEnterEventListener get onEnter => null; + PointerEnterEventListener? get onEnter => null; // As onHover property of MouseHoverAnnotation was removed only in the // beta channel, once it is moved to stable, will remove this property. @override // ignore: override_on_non_overriding_member - PointerHoverEventListener get onHover => null; + PointerHoverEventListener? get onHover => null; @override PointerExitEventListener get onExit => _handlePointerExit; @@ -1359,16 +1754,19 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { // ignore: override_on_non_overriding_member bool get validForMouseTracker => true; - void _handleInteraction(Offset position) { - final RenderSublayerContainer vectorParent = parent; - if (vectorParent.tooltipKey != null && _tooltipBuilder != null) { + void _handleInteraction(Offset position, + [PointerKind kind = PointerKind.touch]) { + if (_controller?.tooltipKey != null && _tooltipBuilder != null) { final ShapeLayerChildRenderBoxBase tooltipRenderer = - vectorParent.tooltipKey.currentContext.findRenderObject(); + _controller!.tooltipKey!.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; tooltipRenderer.paintTooltip( selectedIndex, null, MapLayerElement.vector, - vectorParent.getSublayerIndex(arcLayer), + kind, + state.ancestor.sublayers?.indexOf(arcLayer), position, ); } @@ -1376,16 +1774,16 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { @override bool hitTestSelf(Offset position) { - if (_animation != null && !_animation.isCompleted) { + if (_animation != null && !_animation!.isCompleted) { return false; } - final bool isTileLayer = controller.tileCurrentLevelDetails != null; - final Size boxSize = isTileLayer ? controller.totalTileSize : size; - final Offset translationOffset = - _getTranslationOffset(controller, isTileLayer); - int index = arcsInList.length - 1; - for (final MapArc arc in arcsInList?.reversed) { + final Size boxSize = _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; + final Offset translationOffset = _getTranslation(_controller!); + int index = arcsInList!.length - 1; + for (final MapArc arc in arcsInList!.reversed) { final double width = arc.width ?? _width; if (arc.onTap != null || _tooltipBuilder != null || isDesktop) { final double actualTouchTolerance = @@ -1395,17 +1793,17 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { arc.from.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor, + _controller!.shapeLayerSizeFactor, ); Offset endPoint = pixelFromLatLng( arc.to.latitude, arc.to.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor, + _controller!.shapeLayerSizeFactor, ); - startPoint = _updatePointsToScaledPosition(startPoint, controller); - endPoint = _updatePointsToScaledPosition(endPoint, controller); + startPoint = _getScaledOffset(startPoint, _controller!); + endPoint = _getScaledOffset(endPoint, _controller!); final Offset controlPoint = _calculateControlPoint( startPoint, endPoint, arc.heightFactor, arc.controlPointFactor); @@ -1426,45 +1824,45 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { void handleEvent(PointerEvent event, HitTestEntry entry) { if (event is PointerDownEvent) { _tapGestureRecognizer.addPointer(event); - } else if (event is PointerHoverEvent) { - if (isDesktop && _currentHoverItem != selectedArc) { + } else if (event is PointerHoverEvent && isDesktop) { + if (_currentHoverItem != selectedArc) { _previousHoverItem = _currentHoverItem; _currentHoverItem = selectedArc; _updateHoverItemTween(); } - final RenderBox renderBox = context.findRenderObject(); - _handleInteraction(renderBox.globalToLocal(event.position)); + // ignore: avoid_as + final RenderBox renderBox = context.findRenderObject() as RenderBox; + _handleInteraction( + renderBox.globalToLocal(event.position), PointerKind.hover); } } @override void attach(PipelineOwner owner) { super.attach(owner); - vectorLayerContainer = parent; - controller = vectorLayerContainer.controller; - if (controller != null) { - controller + if (_controller != null) { + _controller! ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) - ..addResetListener(_handleReset) - ..addRefreshListener(_handleRefresh); + ..addResetListener(markNeedsPaint) + ..addRefreshListener(markNeedsPaint); } _animation?.addListener(markNeedsPaint); - _hoverColorAnimation?.addListener(markNeedsPaint); + _hoverColorAnimation.addListener(markNeedsPaint); } @override void detach() { - if (controller != null) { - controller + if (_controller != null) { + _controller! ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) - ..removeResetListener(_handleReset) - ..removeRefreshListener(_handleRefresh); + ..removeResetListener(markNeedsPaint) + ..removeRefreshListener(markNeedsPaint); } _animation?.removeListener(markNeedsPaint); - _hoverColorAnimation?.removeListener(markNeedsPaint); + _hoverColorAnimation.removeListener(markNeedsPaint); arcsInList?.clear(); arcsInList = null; super.detach(); @@ -1480,21 +1878,21 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { @override void paint(PaintingContext context, Offset offset) { - if (_animation != null && _animation.value == 0) { + if (_animation != null && _animation!.value == 0) { return; } context.canvas.save(); Offset startPoint; Offset endPoint; - final bool isTileLayer = controller.tileCurrentLevelDetails != null; final Paint paint = Paint() ..isAntiAlias = true ..style = PaintingStyle.stroke; Path path = Path(); - final Size boxSize = isTileLayer ? controller.totalTileSize : size; - final Offset translationOffset = - _getTranslationOffset(controller, isTileLayer); - controller.applyTransform(context, offset, true); + final Size boxSize = _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; + final Offset translationOffset = _getTranslation(_controller!); + _controller!.applyTransform(context, offset, true); for (final MapArc arc in arcs) { startPoint = pixelFromLatLng( @@ -1502,14 +1900,14 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { arc.from.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor, + _controller!.shapeLayerSizeFactor, ); endPoint = pixelFromLatLng( arc.to.latitude, arc.to.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor, + _controller!.shapeLayerSizeFactor, ); final Offset controlPoint = _calculateControlPoint( startPoint, endPoint, arc.heightFactor, arc.controlPointFactor); @@ -1517,23 +1915,23 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { if (_previousHoverItem != null && _previousHoverItem == arc && _themeData.shapeHoverColor != Colors.transparent) { - paint.color = _reverseHoverColor.evaluate(_hoverColorAnimation); + paint.color = _reverseHoverColor.evaluate(_hoverColorAnimation)!; } else if (_currentHoverItem != null && selectedArc == arc && _themeData.shapeHoverColor != Colors.transparent) { - paint.color = _forwardHoverColor.evaluate(_hoverColorAnimation); + paint.color = _forwardHoverColor.evaluate(_hoverColorAnimation)!; } else { paint.color = arc.color ?? _color; } - paint.strokeWidth = _getDesiredValue(arc.width ?? _width, controller); + paint.strokeWidth = _getCurrentWidth(arc.width ?? _width, _controller!); path ..reset() ..moveTo(startPoint.dx, startPoint.dy) ..quadraticBezierTo( controlPoint.dx, controlPoint.dy, endPoint.dx, endPoint.dy); if (_animation != null) { - path = _getAnimatedPath(path, _animation); + path = _getAnimatedPath(path, _animation!); } _drawDashedLine(context.canvas, arc.dashArray, paint, path); } @@ -1573,33 +1971,59 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { /// [MapTileLayer]. /// /// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// body: SfMaps( -/// layers: [ -/// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "continent", -/// ), -/// sublayers: [ -/// MapPolylineLayer( -/// polylines: List.generate( -/// polylines.length, -/// (int index) { -/// return MapPolyline( -/// points: polylines[index].points, -/// ); -/// }, -/// ).toSet(), -/// ), -/// ], +/// MapZoomPanBehavior _zoomPanBehavior; +/// List _polyLines; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// _polyLines = [ +/// MapLatLng(13.0827, 80.2707), +/// MapLatLng(14.4673, 78.8242), +/// MapLatLng(14.9091, 78.0092), +/// MapLatLng(16.2160, 77.3566), +/// MapLatLng(17.1557, 76.8697), +/// MapLatLng(18.0975, 75.4249), +/// MapLatLng(18.5204, 73.8567), +/// MapLatLng(19.0760, 72.8777), +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// 'assets/india.json', +/// shapeDataField: 'name', +/// ); +/// +/// _zoomPanBehavior = MapZoomPanBehavior( +/// zoomLevel: 3, focalLatLng: MapLatLng(15.3173, 76.7139)); +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar(title: Text('Polyline')), +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// sublayers: [ +/// MapPolylineLayer( +/// polylines: List.generate( +/// 1, +/// (int index) { +/// return MapPolyline( +/// points: _polyLines, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// zoomPanBehavior: _zoomPanBehavior, +/// ), +/// ], /// ), -/// ], -/// ), -/// ); -/// } +/// ); +/// } /// ``` /// /// See also: @@ -1610,14 +2034,13 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { class MapPolylineLayer extends MapVectorLayer { /// Creates the [MapPolylineLayer]. MapPolylineLayer({ - Key key, - @required this.polylines, + Key? key, + required this.polylines, this.animation, this.color, this.width = 2, - IndexedWidgetBuilder tooltipBuilder, - }) : assert(polylines != null), - super(key: key, tooltipBuilder: tooltipBuilder); + IndexedWidgetBuilder? tooltipBuilder, + }) : super(key: key, tooltipBuilder: tooltipBuilder); /// A collection of [MapPolyline]. /// @@ -1625,33 +2048,59 @@ class MapPolylineLayer extends MapVectorLayer { /// group of [MapPolyline.points]. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapPolylineLayer( - /// polylines: List.generate( - /// polylines.length, - /// (int index) { - /// return MapPolyline( - /// points: polylines[index].points, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polyLines; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polyLines = [ + /// MapLatLng(13.0827, 80.2707), + /// MapLatLng(14.4673, 78.8242), + /// MapLatLng(14.9091, 78.0092), + /// MapLatLng(16.2160, 77.3566), + /// MapLatLng(17.1557, 76.8697), + /// MapLatLng(18.0975, 75.4249), + /// MapLatLng(18.5204, 73.8567), + /// MapLatLng(19.0760, 72.8777), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/india.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior( + /// zoomLevel: 3, focalLatLng: MapLatLng(15.3173, 76.7139)); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polyline')), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// 1, + /// (int index) { + /// return MapPolyline( + /// points: _polyLines, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// ); + /// } /// ``` final Set polylines; @@ -1664,59 +2113,75 @@ class MapPolylineLayer extends MapVectorLayer { /// ```dart /// AnimationController _animationController; /// Animation _animation; + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polyLines; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { + /// _polyLines = [ + /// MapLatLng(13.0827, 80.2707), + /// MapLatLng(14.4673, 78.8242), + /// MapLatLng(14.9091, 78.0092), + /// MapLatLng(16.2160, 77.3566), + /// MapLatLng(17.1557, 76.8697), + /// MapLatLng(18.0975, 75.4249), + /// MapLatLng(18.5204, 73.8567), + /// MapLatLng(19.0760, 72.8777), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/india.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior( + /// zoomLevel: 3, focalLatLng: MapLatLng(15.3173, 76.7139)); + /// /// _animationController = AnimationController( - /// duration: Duration(seconds: 3), - /// vsync: this, + /// duration: Duration(seconds: 3), + /// vsync: this, /// ); /// - /// _animation = CurvedAnimation( - /// parent: _animationController, - /// curve: Curves.easeInOut), - /// ); + /// _animation = + /// CurvedAnimation(parent: _animationController, + /// curve: Curves.easeInOut); /// - /// _animationController.forward(from: 0); - /// super.initState(); + /// _animationController.forward(from: 0); + /// super.initState(); /// } /// /// @override /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapPolylineLayer( - /// polylines: List.generate( - /// polylines.length, - /// (int index) { - /// return MapPolyline( - /// points: polylines[index].points, - /// ); - /// }, - /// ).toSet(), - /// animation: _animation, - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: [ + /// MapPolyline( + /// points: _polyLines, + /// ) + /// ].toSet(), + /// animation: _animation, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); /// } /// - /// @override - /// void dispose() { - /// _animationController?.dispose(); - /// super.dispose(); - /// } + /// @override + /// void dispose() { + /// _animationController?.dispose(); + /// super.dispose(); + /// } /// ``` - final Animation animation; + final Animation? animation; /// The color of all the [polylines]. /// @@ -1724,39 +2189,65 @@ class MapPolylineLayer extends MapVectorLayer { /// [MapPolyline.color] property. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapPolylineLayer( - /// polylines: List.generate( - /// polylines.length, - /// (int index) { - /// return MapPolyline( - /// points: polylines[index].points, - /// ); - /// }, - /// ).toSet(), - /// color: Colors.green, - /// ), - /// ], + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polyLines; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polyLines = [ + /// MapLatLng(13.0827, 80.2707), + /// MapLatLng(14.4673, 78.8242), + /// MapLatLng(14.9091, 78.0092), + /// MapLatLng(16.2160, 77.3566), + /// MapLatLng(17.1557, 76.8697), + /// MapLatLng(18.0975, 75.4249), + /// MapLatLng(18.5204, 73.8567), + /// MapLatLng(19.0760, 72.8777), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/india.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior( + /// zoomLevel: 3, focalLatLng: MapLatLng(15.3173, 76.7139)); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polyline')), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// 1, + /// (int index) { + /// return MapPolyline( + /// points: _polyLines, + /// ); + /// }, + /// ).toSet(), + /// color: Colors.green, + /// ), + /// ], + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// ); + /// } /// ``` /// /// See also: /// [width], for setting the width. - final Color color; + final Color? color; /// The width of all the [polylines]. /// @@ -1764,35 +2255,60 @@ class MapPolylineLayer extends MapVectorLayer { /// [MapPolyline.width] property. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapPolylineLayer( - /// polylines: List.generate( - /// polylines.length, - /// (int index) { - /// return MapPolyline( - /// points: polylines[index].points, - /// ); - /// }, - /// ).toSet(), - /// color: Colors.green, - /// width: 5, - /// ), - /// ], + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polyLines; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polyLines = [ + /// MapLatLng(13.0827, 80.2707), + /// MapLatLng(14.4673, 78.8242), + /// MapLatLng(14.9091, 78.0092), + /// MapLatLng(16.2160, 77.3566), + /// MapLatLng(17.1557, 76.8697), + /// MapLatLng(18.0975, 75.4249), + /// MapLatLng(18.5204, 73.8567), + /// MapLatLng(19.0760, 72.8777), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/india.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior( + /// zoomLevel: 3, focalLatLng: MapLatLng(15.3173, 76.7139)); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polyline')), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// 1, + /// (int index) { + /// return MapPolyline( + /// points: _polyLines, + /// ); + /// }, + /// ).toSet(), + /// width : 2.0, + /// ), + /// ], + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// ); + /// } /// ``` /// /// See also: @@ -1810,23 +2326,41 @@ class MapPolylineLayer extends MapVectorLayer { polylineLayer: this, ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + if (polylines.isNotEmpty) { + final _DebugVectorShapeTree pointerTreeNode = + _DebugVectorShapeTree(polylines); + properties.add(pointerTreeNode.toDiagnosticsNode()); + } + properties.add(ObjectFlagProperty.has('animation', animation)); + properties.add(ObjectFlagProperty.has( + 'tooltip', tooltipBuilder)); + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + properties.add(DoubleProperty('width', width)); + } } class _MapPolylineLayer extends StatefulWidget { const _MapPolylineLayer({ - this.polylines, - this.animation, - this.color, - this.width, - this.tooltipBuilder, - this.polylineLayer, + required this.polylines, + required this.animation, + required this.color, + required this.width, + required this.tooltipBuilder, + required this.polylineLayer, }); final Set polylines; - final Animation animation; - final Color color; + final Animation? animation; + final Color? color; final double width; - final IndexedWidgetBuilder tooltipBuilder; + final IndexedWidgetBuilder? tooltipBuilder; final MapPolylineLayer polylineLayer; @override @@ -1835,8 +2369,10 @@ class _MapPolylineLayer extends StatefulWidget { class _MapPolylineLayerState extends State<_MapPolylineLayer> with SingleTickerProviderStateMixin { - AnimationController _hoverAnimationController; - SfMapsThemeData _mapsThemeData; + MapController? _controller; + late AnimationController _hoverAnimationController; + late SfMapsThemeData _mapsThemeData; + late MapLayerInheritedWidget ancestor; @override void initState() { @@ -1845,9 +2381,20 @@ class _MapPolylineLayerState extends State<_MapPolylineLayer> vsync: this, duration: const Duration(milliseconds: 250)); } + @override + void didChangeDependencies() { + if (_controller == null) { + ancestor = context + .dependOnInheritedWidgetOfExactType()!; + _controller = ancestor.controller; + } + super.didChangeDependencies(); + } + @override void dispose() { - _hoverAnimationController?.dispose(); + _controller = null; + _hoverAnimationController.dispose(); super.dispose(); } @@ -1856,9 +2403,11 @@ class _MapPolylineLayerState extends State<_MapPolylineLayer> final ThemeData themeData = Theme.of(context); final bool isDesktop = kIsWeb || themeData.platform == TargetPlatform.macOS || - themeData.platform == TargetPlatform.windows; - _mapsThemeData = SfMapsTheme.of(context); + themeData.platform == TargetPlatform.windows || + themeData.platform == TargetPlatform.linux; + _mapsThemeData = SfMapsTheme.of(context)!; return _MapPolylineLayerRenderObject( + controller: _controller, polylines: widget.polylines, animation: widget.animation, color: widget.color ?? @@ -1871,36 +2420,42 @@ class _MapPolylineLayerState extends State<_MapPolylineLayer> isDesktop: isDesktop, themeData: _mapsThemeData, hoverAnimationController: _hoverAnimationController, + state: this, ); } } class _MapPolylineLayerRenderObject extends LeafRenderObjectWidget { const _MapPolylineLayerRenderObject({ - this.polylines, - this.animation, - this.color, - this.width, - this.tooltipBuilder, - this.polylineLayer, - this.themeData, - this.isDesktop, - this.hoverAnimationController, + required this.controller, + required this.polylines, + required this.animation, + required this.color, + required this.width, + required this.tooltipBuilder, + required this.polylineLayer, + required this.themeData, + required this.isDesktop, + required this.hoverAnimationController, + required this.state, }); + final MapController? controller; final Set polylines; - final Animation animation; + final Animation? animation; final Color color; final double width; - final IndexedWidgetBuilder tooltipBuilder; + final IndexedWidgetBuilder? tooltipBuilder; final MapPolylineLayer polylineLayer; final SfMapsThemeData themeData; final bool isDesktop; final AnimationController hoverAnimationController; + final _MapPolylineLayerState state; @override _RenderMapPolyline createRenderObject(BuildContext context) { return _RenderMapPolyline( + controller: controller, polylines: polylines, animation: animation, color: color, @@ -1911,6 +2466,7 @@ class _MapPolylineLayerRenderObject extends LeafRenderObjectWidget { themeData: themeData, isDesktop: isDesktop, hoverAnimationController: hoverAnimationController, + state: state, ); } @@ -1924,24 +2480,27 @@ class _MapPolylineLayerRenderObject extends LeafRenderObjectWidget { ..width = width ..tooltipBuilder = tooltipBuilder ..context = context - ..polylineLayer = polylineLayer - ..themeData = themeData; + ..themeData = themeData + ..state = state; } } class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { _RenderMapPolyline({ - Set polylines, - Animation animation, - Color color, - double width, - IndexedWidgetBuilder tooltipBuilder, - BuildContext context, - MapPolylineLayer polylineLayer, - SfMapsThemeData themeData, - bool isDesktop, - AnimationController hoverAnimationController, - }) : _polylines = polylines, + required MapController? controller, + required Set polylines, + required Animation? animation, + required Color color, + required double width, + required IndexedWidgetBuilder? tooltipBuilder, + required BuildContext context, + required MapPolylineLayer polylineLayer, + required SfMapsThemeData themeData, + required bool isDesktop, + required AnimationController hoverAnimationController, + required this.state, + }) : _controller = controller, + _polylines = polylines, _color = color, _width = width, _animation = animation, @@ -1955,51 +2514,52 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { _reverseHoverColor = ColorTween(); _hoverColorAnimation = CurvedAnimation( parent: hoverAnimationController, curve: Curves.easeInOut); - polylinesInList = _polylines?.toList(); + polylinesInList = _polylines.toList(); _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; } - TapGestureRecognizer _tapGestureRecognizer; - MapController controller; - RenderSublayerContainer vectorLayerContainer; - MapPolylineLayer polylineLayer; - BuildContext context; - MapPolyline selectedPolyline; + final MapController? _controller; int selectedIndex = -1; double touchTolerance = 5; - List polylinesInList; - AnimationController hoverAnimationController; - Animation _hoverColorAnimation; - ColorTween _forwardHoverColor; - ColorTween _reverseHoverColor; - MapPolyline _previousHoverItem; - MapPolyline _currentHoverItem; - bool isDesktop; + late _MapPolylineLayerState state; + late TapGestureRecognizer _tapGestureRecognizer; + late MapPolylineLayer polylineLayer; + late BuildContext context; + late MapPolyline selectedPolyline; + late AnimationController hoverAnimationController; + late Animation _hoverColorAnimation; + late ColorTween _forwardHoverColor; + late ColorTween _reverseHoverColor; + late bool isDesktop; + + List? polylinesInList; + MapPolyline? _previousHoverItem; + MapPolyline? _currentHoverItem; Set get polylines => _polylines; Set _polylines; - set polylines(Set value) { + set polylines(Set? value) { assert(value != null); if (_polylines == value || value == null) { return; } _polylines = value; - polylinesInList = _polylines?.toList(); + polylinesInList = _polylines.toList(); markNeedsPaint(); } - Animation get animation => _animation; - Animation _animation; - set animation(Animation value) { + Animation? get animation => _animation; + Animation? _animation; + set animation(Animation? value) { if (_animation == value) { return; } _animation = value; } - IndexedWidgetBuilder get tooltipBuilder => _tooltipBuilder; - IndexedWidgetBuilder _tooltipBuilder; - set tooltipBuilder(IndexedWidgetBuilder value) { + IndexedWidgetBuilder? get tooltipBuilder => _tooltipBuilder; + IndexedWidgetBuilder? _tooltipBuilder; + set tooltipBuilder(IndexedWidgetBuilder? value) { if (_tooltipBuilder == value) { return; } @@ -2044,7 +2604,8 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { void _updateHoverItemTween() { if (isDesktop) { - final Color hoverStrokeColor = _getHoverColor(selectedPolyline); + final Color hoverStrokeColor = + _getHoverColor(selectedPolyline.color, _color, _themeData); final Color beginColor = selectedPolyline.color ?? _color; if (_previousHoverItem != null) { @@ -2060,17 +2621,6 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { } } - Color _getHoverColor(MapPolyline polyline) { - final Color color = polyline.color ?? _color; - final bool canAdjustHoverOpacity = - double.parse(color.opacity.toStringAsFixed(2)) != hoverColorOpacity; - return _themeData.shapeHoverColor != null && - _themeData.shapeHoverColor != Colors.transparent - ? _themeData.shapeHoverColor - : color.withOpacity( - canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); - } - void _handleTapUp(TapUpDetails details) { selectedPolyline.onTap?.call(); _handleInteraction(details.localPosition); @@ -2083,10 +2633,11 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { _updateHoverItemTween(); } - final RenderSublayerContainer vectorParent = parent; final ShapeLayerChildRenderBoxBase tooltipRenderer = - vectorParent.tooltipKey?.currentContext?.findRenderObject(); - tooltipRenderer?.hideTooltip(); + _controller!.tooltipKey!.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; + tooltipRenderer.hideTooltip(); } void _handleZooming(MapZoomDetails details) { @@ -2097,24 +2648,19 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } - void _handleReset() { - markNeedsPaint(); - } - - void _handleRefresh() { - markNeedsPaint(); - } - - void _handleInteraction(Offset position) { - final RenderSublayerContainer vectorParent = parent; - if (vectorParent.tooltipKey != null && _tooltipBuilder != null) { + void _handleInteraction(Offset position, + [PointerKind kind = PointerKind.touch]) { + if (_controller?.tooltipKey != null && _tooltipBuilder != null) { final ShapeLayerChildRenderBoxBase tooltipRenderer = - vectorParent.tooltipKey.currentContext.findRenderObject(); + _controller!.tooltipKey!.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; tooltipRenderer.paintTooltip( selectedIndex, null, MapLayerElement.vector, - vectorParent.getSublayerIndex(polylineLayer), + kind, + state.ancestor.sublayers?.indexOf(polylineLayer), position, ); } @@ -2124,13 +2670,13 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { MouseCursor get cursor => SystemMouseCursors.basic; @override - PointerEnterEventListener get onEnter => null; + PointerEnterEventListener? get onEnter => null; // As onHover property of MouseHoverAnnotation was removed only in the // beta channel, once it is moved to stable, will remove this property. @override // ignore: override_on_non_overriding_member - PointerHoverEventListener get onHover => null; + PointerHoverEventListener? get onHover => null; @override PointerExitEventListener get onExit => _handlePointerExit; @@ -2141,17 +2687,17 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { @override bool hitTestSelf(Offset position) { - if (_animation != null && !_animation.isCompleted) { + if (_animation != null && !_animation!.isCompleted) { return false; } - final bool isTileLayer = controller.tileCurrentLevelDetails != null; - final Size boxSize = isTileLayer ? controller.totalTileSize : size; - final Offset translationOffset = - _getTranslationOffset(controller, isTileLayer); + final Size boxSize = _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; + final Offset translationOffset = _getTranslation(_controller!); bool tappedOnLine = false; - int index = polylinesInList.length - 1; - for (final MapPolyline polyline in polylinesInList?.reversed) { + int index = polylinesInList!.length - 1; + for (final MapPolyline polyline in polylinesInList!.reversed) { if (tappedOnLine) { return true; } @@ -2168,17 +2714,17 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { currentPoint.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor, + _controller!.shapeLayerSizeFactor, ); Offset endPoint = pixelFromLatLng( nextPoint.latitude, nextPoint.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor, + _controller!.shapeLayerSizeFactor, ); - startPoint = _updatePointsToScaledPosition(startPoint, controller); - endPoint = _updatePointsToScaledPosition(endPoint, controller); + startPoint = _getScaledOffset(startPoint, _controller!); + endPoint = _getScaledOffset(endPoint, _controller!); if (_liesPointOnLine( startPoint, endPoint, actualTouchTolerance, position)) { @@ -2198,30 +2744,28 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { @override void attach(PipelineOwner owner) { super.attach(owner); - vectorLayerContainer = parent; - controller = vectorLayerContainer.controller; - if (controller != null) { - controller + if (_controller != null) { + _controller! ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) - ..addResetListener(_handleReset) - ..addRefreshListener(_handleRefresh); + ..addResetListener(markNeedsPaint) + ..addRefreshListener(markNeedsPaint); } _animation?.addListener(markNeedsPaint); - _hoverColorAnimation?.addListener(markNeedsPaint); + _hoverColorAnimation.addListener(markNeedsPaint); } @override void detach() { - if (controller != null) { - controller + if (_controller != null) { + _controller! ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) - ..removeResetListener(_handleReset) - ..removeRefreshListener(_handleRefresh); + ..removeResetListener(markNeedsPaint) + ..removeRefreshListener(markNeedsPaint); } _animation?.removeListener(markNeedsPaint); - _hoverColorAnimation?.removeListener(markNeedsPaint); + _hoverColorAnimation.removeListener(markNeedsPaint); polylinesInList?.clear(); polylinesInList = null; super.detach(); @@ -2231,15 +2775,17 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { void handleEvent(PointerEvent event, HitTestEntry entry) { if (event is PointerDownEvent) { _tapGestureRecognizer.addPointer(event); - } else if (event is PointerHoverEvent) { - if (isDesktop && _currentHoverItem != selectedPolyline) { + } else if (event is PointerHoverEvent && isDesktop) { + if (_currentHoverItem != selectedPolyline) { _previousHoverItem = _currentHoverItem; _currentHoverItem = selectedPolyline; _updateHoverItemTween(); } - final RenderBox renderBox = context.findRenderObject(); - _handleInteraction(renderBox.globalToLocal(event.position)); + // ignore: avoid_as + final RenderBox renderBox = context.findRenderObject() as RenderBox; + _handleInteraction( + renderBox.globalToLocal(event.position), PointerKind.hover); } } @@ -2253,7 +2799,7 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { @override void paint(PaintingContext context, Offset offset) { - if (_animation != null && _animation.value == 0.0) { + if (_animation != null && _animation!.value == 0.0) { return; } context.canvas.save(); @@ -2261,54 +2807,52 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { ..isAntiAlias = true ..style = PaintingStyle.stroke; Path path = Path(); - final bool isTileLayer = controller.tileCurrentLevelDetails != null; - final Size boxSize = isTileLayer ? controller.totalTileSize : size; - final Offset translationOffset = - _getTranslationOffset(controller, isTileLayer); - controller.applyTransform(context, offset, true); + final Size boxSize = _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; + final Offset translationOffset = _getTranslation(_controller!); + _controller!.applyTransform(context, offset, true); for (final MapPolyline polyline in polylines) { - if (polyline.points != null) { - final MapLatLng startCoordinate = polyline.points[0]; - final Offset startPoint = pixelFromLatLng( - startCoordinate.latitude, - startCoordinate.longitude, + final MapLatLng startCoordinate = polyline.points[0]; + final Offset startPoint = pixelFromLatLng( + startCoordinate.latitude, + startCoordinate.longitude, + boxSize, + translationOffset, + _controller!.shapeLayerSizeFactor); + path + ..reset() + ..moveTo(startPoint.dx, startPoint.dy); + + for (int j = 1; j < polyline.points.length; j++) { + final MapLatLng nextCoordinate = polyline.points[j]; + final Offset nextPoint = pixelFromLatLng( + nextCoordinate.latitude, + nextCoordinate.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor); - path - ..reset() - ..moveTo(startPoint.dx, startPoint.dy); - - for (int j = 1; j < polyline.points.length; j++) { - final MapLatLng nextCoordinate = polyline.points[j]; - final Offset nextPoint = pixelFromLatLng( - nextCoordinate.latitude, - nextCoordinate.longitude, - boxSize, - translationOffset, - controller.shapeLayerSizeFactor); - path.lineTo(nextPoint.dx, nextPoint.dy); - } + _controller!.shapeLayerSizeFactor); + path.lineTo(nextPoint.dx, nextPoint.dy); + } - if (_previousHoverItem != null && - _previousHoverItem == polyline && - _themeData.shapeHoverColor != Colors.transparent) { - paint.color = _reverseHoverColor.evaluate(_hoverColorAnimation); - } else if (_currentHoverItem != null && - selectedPolyline == polyline && - _themeData.shapeHoverColor != Colors.transparent) { - paint.color = _forwardHoverColor.evaluate(_hoverColorAnimation); - } else { - paint.color = polyline.color ?? _color; - } + if (_previousHoverItem != null && + _previousHoverItem == polyline && + _themeData.shapeHoverColor != Colors.transparent) { + paint.color = _reverseHoverColor.evaluate(_hoverColorAnimation)!; + } else if (_currentHoverItem != null && + selectedPolyline == polyline && + _themeData.shapeHoverColor != Colors.transparent) { + paint.color = _forwardHoverColor.evaluate(_hoverColorAnimation)!; + } else { + paint.color = polyline.color ?? _color; + } - paint.strokeWidth = - _getDesiredValue(polyline.width ?? _width, controller); - if (_animation != null) { - path = _getAnimatedPath(path, _animation); - } - _drawDashedLine(context.canvas, polyline.dashArray, paint, path); + paint.strokeWidth = + _getCurrentWidth(polyline.width ?? _width, _controller!); + if (_animation != null) { + path = _getAnimatedPath(path, _animation!); } + _drawDashedLine(context.canvas, polyline.dashArray, paint, path); } context.canvas.restore(); } @@ -2318,44 +2862,129 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { /// [MapTileLayer]. /// /// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// body: SfMaps( -/// layers: [ +/// MapZoomPanBehavior _zoomPanBehavior; +/// List _polygon; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// _polygon = [ +/// MapLatLng(38.8026, -116.4194), +/// MapLatLng(46.8797, -110.3626), +/// MapLatLng(41.8780, -93.0977), +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// 'assets/usa.json', +/// shapeDataField: 'name', +/// ); +/// +/// _zoomPanBehavior = MapZoomPanBehavior(); +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar(title: Text('Polygon shape')), +/// body: SfMaps(layers: [ /// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "continent", -/// ), +/// source: _mapSource, /// sublayers: [ /// MapPolygonLayer( /// polygons: List.generate( -/// polygons.length, +/// 1, /// (int index) { /// return MapPolygon( -/// points: polygons[index].points, +/// points: _polygon, /// ); /// }, /// ).toSet(), /// ), /// ], +/// zoomPanBehavior: _zoomPanBehavior, /// ), -/// ], -/// ), -/// ); -/// } +/// ]), +/// ); +/// } +/// +/// See also: +/// * `[MapPolygonLayer.inverted]`, named constructor, for adding inverted +/// polygon shape. /// ``` class MapPolygonLayer extends MapVectorLayer { /// Creates the [MapPolygonLayer]. MapPolygonLayer({ - Key key, - @required this.polygons, - this.color = const Color.fromRGBO(51, 153, 144, 1), + Key? key, + required this.polygons, + this.color, + this.strokeWidth = 1, + this.strokeColor, + IndexedWidgetBuilder? tooltipBuilder, + }) : _fillType = _VectorFillType.inner, + super(key: key, tooltipBuilder: tooltipBuilder); + + /// Creates the inverted color polygon shape. + /// + /// You can highlight particular on the map to make more readable by applying + /// the opacity to the outer area of highlighted polygon area using the + /// polygons property of [MapPolygonLayer.inverted]. + /// + /// ```dart + /// List _polygon; + /// MapShapeSource _dataSource; + /// + /// @override + /// void initState() { + /// _polygon = [ + /// MapLatLng(27.6648, -81.5158), + /// MapLatLng(32.3078, -64.7505), + /// MapLatLng(18.2208, -66.5901), + /// ]; + /// + /// _dataSource = MapShapeSource.asset( + /// 'assets/usa.json', + /// shapeDataField: 'name', + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _dataSource, + /// sublayers: [ + /// MapPolygonLayer.inverted( + /// polygons: List.generate( + /// 1, + /// (int index) { + /// return MapPolygon( + /// points: _polygon, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// See also: + /// * [`MapPolygonLayer`], for adding normal polygon shape. + /// ``` + MapPolygonLayer.inverted({ + Key? key, + required this.polygons, this.strokeWidth = 1, - this.strokeColor = const Color.fromRGBO(51, 153, 144, 1), - IndexedWidgetBuilder tooltipBuilder, - }) : assert(polygons != null), + this.color, + this.strokeColor, + IndexedWidgetBuilder? tooltipBuilder, + }) : _fillType = _VectorFillType.outer, super(key: key, tooltipBuilder: tooltipBuilder); /// A collection of [MapPolygon]. @@ -2364,33 +2993,51 @@ class MapPolygonLayer extends MapVectorLayer { /// location coordinates through group of [MapPolygon.points]. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polygon; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polygon = [ + /// MapLatLng(38.8026, -116.4194), + /// MapLatLng(46.8797, -110.3626), + /// MapLatLng(41.8780, -93.0977), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/usa.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polygon shape')), + /// body: SfMaps(layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), + /// source: _mapSource, /// sublayers: [ /// MapPolygonLayer( /// polygons: List.generate( - /// polygons.length, + /// 1, /// (int index) { /// return MapPolygon( - /// points: polygons[index].points, + /// points: _polygon, /// ); /// }, /// ).toSet(), /// ), /// ], + /// zoomPanBehavior: _zoomPanBehavior, /// ), - /// ], - /// ), - /// ); - /// } + /// ]), + /// ); + /// } ///``` final Set polygons; @@ -2400,36 +3047,54 @@ class MapPolygonLayer extends MapVectorLayer { /// [MapPolygon.color] property. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polygon; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polygon = [ + /// MapLatLng(38.8026, -116.4194), + /// MapLatLng(46.8797, -110.3626), + /// MapLatLng(41.8780, -93.0977), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/usa.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polygon shape')), + /// body: SfMaps(layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), + /// source: _mapSource, /// sublayers: [ /// MapPolygonLayer( /// polygons: List.generate( - /// polygons.length, + /// 1, /// (int index) { /// return MapPolygon( - /// points: polygons[index].points, + /// points: _polygon, /// ); /// }, /// ).toSet(), - /// color: Colors.red, + /// color: Colors.green, /// ), /// ], + /// zoomPanBehavior: _zoomPanBehavior, /// ), - /// ], - /// ), - /// ); - /// } + /// ]), + /// ); + /// } ///``` - final Color color; + final Color? color; /// The stroke width of all the [MapPolygon]. /// @@ -2437,35 +3102,53 @@ class MapPolygonLayer extends MapVectorLayer { /// [MapPolygon.strokeWidth] property. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polygon; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polygon = [ + /// MapLatLng(38.8026, -116.4194), + /// MapLatLng(46.8797, -110.3626), + /// MapLatLng(41.8780, -93.0977), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/usa.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polygon shape')), + /// body: SfMaps(layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), + /// source: _mapSource, /// sublayers: [ /// MapPolygonLayer( /// polygons: List.generate( - /// polygons.length, + /// 1, /// (int index) { /// return MapPolygon( - /// points: polygons[index].points, + /// points: _polygon, /// ); /// }, /// ).toSet(), /// strokeColor: Colors.red, - /// strokeWidth: 5, + /// strokeWidth: 5.0, /// ), /// ], + /// zoomPanBehavior: _zoomPanBehavior, /// ), - /// ], - /// ), - /// ); - /// } + /// ]), + /// ); + /// } ///``` /// See also: /// [strokeColor], to set the stroke color for the polygon. @@ -2477,40 +3160,61 @@ class MapPolygonLayer extends MapVectorLayer { /// [MapPolygon.strokeColor] property. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polygon; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polygon = [ + /// MapLatLng(38.8026, -116.4194), + /// MapLatLng(46.8797, -110.3626), + /// MapLatLng(41.8780, -93.0977), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/usa.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polygon shape')), + /// body: SfMaps(layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), + /// source: _mapSource, /// sublayers: [ /// MapPolygonLayer( /// polygons: List.generate( - /// polygons.length, + /// 1, /// (int index) { /// return MapPolygon( - /// points: polygons[index].points, + /// points: _polygon, /// ); /// }, /// ).toSet(), /// strokeColor: Colors.red, - /// strokeWidth: 5, + /// strokeWidth: 5.0, /// ), /// ], + /// zoomPanBehavior: _zoomPanBehavior, /// ), - /// ], - /// ), - /// ); - /// } + /// ]), + /// ); + /// } ///``` /// /// See also: /// [strokeWidth], to set the stroke width for the polygon. - final Color strokeColor; + final Color? strokeColor; + + /// Strategies for painting a polygon in a canvas. + final _VectorFillType _fillType; @override Widget build(BuildContext context) { @@ -2521,26 +3225,50 @@ class MapPolygonLayer extends MapVectorLayer { strokeColor: strokeColor, tooltipBuilder: tooltipBuilder, polygonLayer: this, + fillType: _fillType, ); } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + if (polygons.isNotEmpty) { + final _DebugVectorShapeTree pointerTreeNode = + _DebugVectorShapeTree(polygons); + properties.add(pointerTreeNode.toDiagnosticsNode()); + } + properties.add(ObjectFlagProperty.has( + 'tooltip', tooltipBuilder)); + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + if (strokeColor != null) { + properties.add(ColorProperty('strokeColor', strokeColor)); + } + + properties.add(DoubleProperty('strokeWidth', strokeWidth)); + } } class _MapPolygonLayer extends StatefulWidget { const _MapPolygonLayer({ - this.polygons, - this.color, - this.strokeWidth, - this.strokeColor, - this.tooltipBuilder, - this.polygonLayer, + required this.polygons, + required this.color, + required this.strokeWidth, + required this.strokeColor, + required this.tooltipBuilder, + required this.polygonLayer, + required this.fillType, }); final Set polygons; - final Color color; + final Color? color; final double strokeWidth; - final Color strokeColor; - final IndexedWidgetBuilder tooltipBuilder; + final Color? strokeColor; + final IndexedWidgetBuilder? tooltipBuilder; final MapPolygonLayer polygonLayer; + final _VectorFillType fillType; @override _MapPolygonLayerState createState() => _MapPolygonLayerState(); @@ -2548,8 +3276,10 @@ class _MapPolygonLayer extends StatefulWidget { class _MapPolygonLayerState extends State<_MapPolygonLayer> with SingleTickerProviderStateMixin { - AnimationController _hoverAnimationController; - SfMapsThemeData _mapsThemeData; + MapController? _controller; + late AnimationController _hoverAnimationController; + late SfMapsThemeData _mapsThemeData; + late MapLayerInheritedWidget ancestor; @override void initState() { @@ -2558,59 +3288,126 @@ class _MapPolygonLayerState extends State<_MapPolygonLayer> vsync: this, duration: const Duration(milliseconds: 250)); } + @override + void didChangeDependencies() { + if (_controller == null) { + ancestor = context + .dependOnInheritedWidgetOfExactType()!; + _controller = ancestor.controller; + } + super.didChangeDependencies(); + } + @override void dispose() { - _hoverAnimationController?.dispose(); + _controller = null; + _hoverAnimationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { + assert(() { + if (widget.fillType == _VectorFillType.outer) { + for (final MapPolygon polygon in widget.polygons) { + assert( + polygon.color == null, + throw FlutterError.fromParts([ + ErrorSummary('Incorrect MapPolygon arguments.'), + ErrorDescription( + 'Inverted polygons cannot be customized individually.'), + ErrorHint( + '''To customize all the polygon's color, use MapPolygonLayer.color''') + ])); + assert( + polygon.strokeColor == null, + throw FlutterError.fromParts([ + ErrorSummary('Incorrect MapPolygon arguments.'), + ErrorDescription( + 'Inverted polygons cannot be customized individually.'), + ErrorHint( + '''To customize all the polygon's stroke color, use MapPolygonLayer.strokeColor''') + ])); + assert( + polygon.strokeWidth == null, + throw FlutterError.fromParts([ + ErrorSummary('Incorrect MapPolygon arguments.'), + ErrorDescription( + 'Inverted polygons cannot be customized individually.'), + ErrorHint( + '''To customize all the polygon's stroke width, use MapPolygonLayer.strokeWidth''') + ])); + } + } + return true; + }()); + final ThemeData themeData = Theme.of(context); final bool isDesktop = kIsWeb || themeData.platform == TargetPlatform.macOS || - themeData.platform == TargetPlatform.windows; - _mapsThemeData = SfMapsTheme.of(context); + themeData.platform == TargetPlatform.windows || + themeData.platform == TargetPlatform.linux; + _mapsThemeData = SfMapsTheme.of(context)!; return _MapPolygonLayerRenderObject( + controller: _controller, polygons: widget.polygons, - color: widget.color, + color: widget.color ?? + (widget.fillType == _VectorFillType.inner + ? const Color.fromRGBO(51, 153, 144, 1) + : (_mapsThemeData.brightness == Brightness.light + ? const Color.fromRGBO(3, 3, 3, 0.15) + : const Color.fromRGBO(0, 0, 0, 0.2))), strokeWidth: widget.strokeWidth, - strokeColor: widget.strokeColor, + strokeColor: widget.strokeColor ?? + (widget.fillType == _VectorFillType.inner + ? const Color.fromRGBO(51, 153, 144, 1) + : (_mapsThemeData.brightness == Brightness.light + ? const Color.fromRGBO(98, 0, 238, 1) + : const Color.fromRGBO(187, 134, 252, 0.5))), tooltipBuilder: widget.tooltipBuilder, polygonLayer: widget.polygonLayer, hoverAnimationController: _hoverAnimationController, themeData: _mapsThemeData, isDesktop: isDesktop, + fillType: widget.fillType, + state: this, ); } } class _MapPolygonLayerRenderObject extends LeafRenderObjectWidget { const _MapPolygonLayerRenderObject({ - this.polygons, - this.color, - this.strokeWidth, - this.strokeColor, - this.tooltipBuilder, - this.polygonLayer, - this.hoverAnimationController, - this.themeData, - this.isDesktop, + required this.controller, + required this.polygons, + required this.color, + required this.strokeWidth, + required this.strokeColor, + required this.tooltipBuilder, + required this.polygonLayer, + required this.hoverAnimationController, + required this.themeData, + required this.isDesktop, + required this.fillType, + required this.state, }); + final MapController? controller; final Set polygons; final Color color; final double strokeWidth; final Color strokeColor; - final IndexedWidgetBuilder tooltipBuilder; + final IndexedWidgetBuilder? tooltipBuilder; final MapPolygonLayer polygonLayer; final AnimationController hoverAnimationController; final SfMapsThemeData themeData; final bool isDesktop; + final _VectorFillType fillType; + final _MapPolygonLayerState state; @override _RenderMapPolygon createRenderObject(BuildContext context) { return _RenderMapPolygon( + controller: controller, polygons: polygons, color: color, strokeColor: strokeColor, @@ -2621,6 +3418,8 @@ class _MapPolygonLayerRenderObject extends LeafRenderObjectWidget { polygonLayer: polygonLayer, hoverAnimationController: hoverAnimationController, isDesktop: isDesktop, + fillType: fillType, + state: state, ); } @@ -2635,23 +3434,28 @@ class _MapPolygonLayerRenderObject extends LeafRenderObjectWidget { ..tooltipBuilder = tooltipBuilder ..themeData = themeData ..context = context - ..polygonLayer = polygonLayer; + ..fillType = fillType + ..state = state; } } class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { _RenderMapPolygon({ - Set polygons, - Color color, - double strokeWidth, - Color strokeColor, - IndexedWidgetBuilder tooltipBuilder, - SfMapsThemeData themeData, - BuildContext context, - MapPolygonLayer polygonLayer, - AnimationController hoverAnimationController, - bool isDesktop, - }) : _polygons = polygons, + required MapController? controller, + required Set polygons, + required Color color, + required double strokeWidth, + required Color strokeColor, + required IndexedWidgetBuilder? tooltipBuilder, + required SfMapsThemeData themeData, + required BuildContext context, + required MapPolygonLayer polygonLayer, + required AnimationController hoverAnimationController, + required bool isDesktop, + required this.fillType, + required this.state, + }) : _controller = controller, + _polygons = polygons, _color = color, _strokeWidth = strokeWidth, _strokeColor = strokeColor, @@ -2667,43 +3471,45 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { _reverseHoverStrokeColor = ColorTween(); _hoverColorAnimation = CurvedAnimation( parent: hoverAnimationController, curve: Curves.easeInOut); - _polygonsInList = _polygons?.toList(); + _polygonsInList = _polygons.toList(); _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; } - MapController controller; - AnimationController hoverAnimationController; - RenderSublayerContainer vectorLayerContainer; - bool isDesktop; - MapPolygonLayer polygonLayer; - BuildContext context; - TapGestureRecognizer _tapGestureRecognizer; - Animation _hoverColorAnimation; - ColorTween _forwardHoverColor; - ColorTween _reverseHoverColor; - ColorTween _forwardHoverStrokeColor; - ColorTween _reverseHoverStrokeColor; - MapPolygon _previousHoverItem; - MapPolygon _currentHoverItem; - List _polygonsInList; + final MapController? _controller; int _selectedIndex = -1; - MapPolygon _selectedPolygon; + late _MapPolygonLayerState state; + late _VectorFillType fillType; + late AnimationController hoverAnimationController; + late bool isDesktop; + late MapPolygonLayer polygonLayer; + late BuildContext context; + late TapGestureRecognizer _tapGestureRecognizer; + late Animation _hoverColorAnimation; + late ColorTween _forwardHoverColor; + late ColorTween _reverseHoverColor; + late ColorTween _forwardHoverStrokeColor; + late ColorTween _reverseHoverStrokeColor; + late MapPolygon _selectedPolygon; + + MapPolygon? _previousHoverItem; + MapPolygon? _currentHoverItem; + List? _polygonsInList; Set get polygons => _polygons; Set _polygons; - set polygons(Set value) { + set polygons(Set? value) { assert(value != null); if (_polygons == value || value == null) { return; } _polygons = value; - _polygonsInList = _polygons?.toList(); + _polygonsInList = _polygons.toList(); markNeedsPaint(); } - IndexedWidgetBuilder get tooltipBuilder => _tooltipBuilder; - IndexedWidgetBuilder _tooltipBuilder; - set tooltipBuilder(IndexedWidgetBuilder value) { + IndexedWidgetBuilder? get tooltipBuilder => _tooltipBuilder; + IndexedWidgetBuilder? _tooltipBuilder; + set tooltipBuilder(IndexedWidgetBuilder? value) { if (_tooltipBuilder == value) { return; } @@ -2801,7 +3607,7 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { Color _getHoverFillColor(MapPolygon polygon) { if (hasHoverColor) { - return _themeData.shapeHoverColor; + return _themeData.shapeHoverColor!; } final Color color = polygon.color ?? _color; return color.withOpacity( @@ -2812,7 +3618,7 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { Color _getHoverStrokeColor(MapPolygon polygon) { if (hasHoverStrokeColor) { - return _themeData.shapeHoverStrokeColor; + return _themeData.shapeHoverStrokeColor!; } final Color strokeColor = polygon.strokeColor ?? _strokeColor; return strokeColor.withOpacity( @@ -2828,10 +3634,12 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { _currentHoverItem = null; _initializeHoverItemTween(); } - final RenderSublayerContainer vectorParent = parent; + final ShapeLayerChildRenderBoxBase tooltipRenderer = - vectorParent.tooltipKey?.currentContext?.findRenderObject(); - tooltipRenderer?.hideTooltip(); + _controller!.tooltipKey!.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; + tooltipRenderer.hideTooltip(); } void _handleZooming(MapZoomDetails details) { @@ -2842,21 +3650,15 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } - void _handleReset() { - markNeedsPaint(); - } - - void _handleRefresh() { - markNeedsPaint(); - } - - void _handleInteraction(Offset position) { - final RenderSublayerContainer vectorParent = parent; - if (vectorParent.tooltipKey != null && _tooltipBuilder != null) { + void _handleInteraction(Offset position, + [PointerKind kind = PointerKind.touch]) { + if (_controller?.tooltipKey != null && _tooltipBuilder != null) { final ShapeLayerChildRenderBoxBase tooltipRenderer = - vectorParent.tooltipKey.currentContext.findRenderObject(); + _controller!.tooltipKey!.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; tooltipRenderer.paintTooltip(_selectedIndex, null, MapLayerElement.vector, - vectorParent.getSublayerIndex(polygonLayer), position); + kind, state.ancestor.sublayers?.indexOf(polygonLayer), position); } } @@ -2869,13 +3671,13 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { MouseCursor get cursor => SystemMouseCursors.basic; @override - PointerEnterEventListener get onEnter => null; + PointerEnterEventListener? get onEnter => null; // As onHover property of MouseHoverAnnotation was removed only in the // beta channel, once it is moved to stable, will remove this property. @override // ignore: override_on_non_overriding_member - PointerHoverEventListener get onHover => null; + PointerHoverEventListener? get onHover => null; @override PointerExitEventListener get onExit => _handlePointerExit; @@ -2886,40 +3688,41 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { @override bool hitTestSelf(Offset position) { - int index = _polygonsInList.length - 1; - final bool isTileLayer = controller.tileCurrentLevelDetails != null; - final Size boxSize = isTileLayer ? controller.totalTileSize : size; - final Offset translationOffset = - _getTranslationOffset(controller, isTileLayer); - for (final MapPolygon polygon in _polygonsInList?.reversed) { + int index = _polygonsInList!.length - 1; + final Size boxSize = _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; + final Offset translationOffset = getTranslationOffset(_controller!); + for (final MapPolygon polygon in _polygonsInList!.reversed) { if (polygon.onTap != null || _tooltipBuilder != null || canHover) { final Path path = Path(); - if (polygon.points != null) { - final MapLatLng startCoordinate = polygon.points[0]; - final Offset startPoint = pixelFromLatLng( - startCoordinate.latitude, - startCoordinate.longitude, + + final MapLatLng startCoordinate = polygon.points[0]; + Offset startPoint = pixelFromLatLng( + startCoordinate.latitude, + startCoordinate.longitude, + boxSize, + translationOffset, + getLayerSizeFactor(_controller!)); + startPoint = _getScaledOffset(startPoint, _controller!); + path.moveTo(startPoint.dx, startPoint.dy); + + for (int j = 1; j < polygon.points.length; j++) { + final MapLatLng nextCoordinate = polygon.points[j]; + Offset nextPoint = pixelFromLatLng( + nextCoordinate.latitude, + nextCoordinate.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor); - path.moveTo(startPoint.dx, startPoint.dy); - - for (int j = 1; j < polygon.points.length; j++) { - final MapLatLng nextCoordinate = polygon.points[j]; - final Offset nextPoint = pixelFromLatLng( - nextCoordinate.latitude, - nextCoordinate.longitude, - boxSize, - translationOffset, - controller.shapeLayerSizeFactor); - path.lineTo(nextPoint.dx, nextPoint.dy); - } - path.close(); - if (path.contains(position)) { - _selectedPolygon = polygon; - _selectedIndex = index; - return true; - } + getLayerSizeFactor(_controller!)); + nextPoint = _getScaledOffset(nextPoint, _controller!); + path.lineTo(nextPoint.dx, nextPoint.dy); + } + path.close(); + if (path.contains(position)) { + _selectedPolygon = polygon; + _selectedIndex = index; + return true; } } index--; @@ -2939,36 +3742,36 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { _initializeHoverItemTween(); } - final RenderBox renderBox = context.findRenderObject(); - _handleInteraction(renderBox.globalToLocal(event.position)); + // ignore: avoid_as + final RenderBox renderBox = context.findRenderObject() as RenderBox; + _handleInteraction( + renderBox.globalToLocal(event.position), PointerKind.hover); } } @override void attach(PipelineOwner owner) { super.attach(owner); - vectorLayerContainer = parent; - controller = vectorLayerContainer.controller; - if (controller != null) { - controller + if (_controller != null) { + _controller! ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) - ..addResetListener(_handleReset) - ..addRefreshListener(_handleRefresh); + ..addResetListener(markNeedsPaint) + ..addRefreshListener(markNeedsPaint); } - _hoverColorAnimation?.addListener(markNeedsPaint); + _hoverColorAnimation.addListener(markNeedsPaint); } @override void detach() { - if (controller != null) { - controller + if (_controller != null) { + _controller! ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) - ..removeResetListener(_handleReset) - ..removeRefreshListener(_handleRefresh); + ..removeResetListener(markNeedsPaint) + ..removeRefreshListener(markNeedsPaint); } - _hoverColorAnimation?.removeListener(markNeedsPaint); + _hoverColorAnimation.removeListener(markNeedsPaint); _polygonsInList?.clear(); _polygonsInList = null; super.detach(); @@ -2989,40 +3792,63 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { final Paint strokePaint = Paint() ..isAntiAlias = true ..style = PaintingStyle.stroke; - final bool isTileLayer = controller.tileCurrentLevelDetails != null; - final Size boxSize = isTileLayer ? controller.totalTileSize : size; - final Offset translationOffset = - _getTranslationOffset(controller, isTileLayer); - controller.applyTransform(context, offset, true); + final Size boxSize = _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; + final Offset translationOffset = getTranslationOffset(_controller!); + // Check whether the color will apply to the inner side or outer side of + // the polygon shape. + final bool isInverted = fillType == _VectorFillType.outer; + Path path = Path(); for (final MapPolygon polygon in polygons) { - final Path path = Path(); - if (polygon.points != null) { - final MapLatLng startLatLng = polygon.points[0]; - final Offset startPoint = pixelFromLatLng( - startLatLng.latitude, - startLatLng.longitude, + // Creating new path for each polygon for applying individual polygon + // customization like color, strokeColor and strokeWidth customization. + // For inverted polygon, all polygons are added in a single path to make + // the polygons area to be transparent while combining the path. + if (!isInverted) { + path = Path(); + } + + final MapLatLng startLatLng = polygon.points[0]; + Offset startPoint = pixelFromLatLng( + startLatLng.latitude, + startLatLng.longitude, + boxSize, + translationOffset, + getLayerSizeFactor(_controller!)); + + startPoint = _getScaledOffset(startPoint, _controller!); + path.moveTo(startPoint.dx, startPoint.dy); + + for (int j = 1; j < polygon.points.length; j++) { + final MapLatLng nextCoordinate = polygon.points[j]; + Offset nextPoint = pixelFromLatLng( + nextCoordinate.latitude, + nextCoordinate.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor); - path.moveTo(startPoint.dx, startPoint.dy); + getLayerSizeFactor(_controller!)); + nextPoint = _getScaledOffset(nextPoint, _controller!); + path.lineTo(nextPoint.dx, nextPoint.dy); + } + path.close(); - for (int j = 1; j < polygon.points.length; j++) { - final MapLatLng nextCoordinate = polygon.points[j]; - final Offset nextPoint = pixelFromLatLng( - nextCoordinate.latitude, - nextCoordinate.longitude, - boxSize, - translationOffset, - controller.shapeLayerSizeFactor); - path.lineTo(nextPoint.dx, nextPoint.dy); - } - path.close(); + if (!isInverted) { _updateFillColor(polygon, fillPaint); _updateStroke(polygon, strokePaint); context.canvas..drawPath(path, fillPaint)..drawPath(path, strokePaint); } } + + if (isInverted) { + fillPaint.color = _color; + strokePaint + ..strokeWidth = _strokeWidth + ..color = _strokeColor; + _drawInvertedPath( + context, path, _controller!, fillPaint, strokePaint, offset); + } context.canvas.restore(); } @@ -3034,11 +3860,11 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { if (_previousHoverItem != null && _previousHoverItem == polygon) { paint.color = _themeData.shapeHoverColor != Colors.transparent - ? _reverseHoverColor.evaluate(_hoverColorAnimation) + ? _reverseHoverColor.evaluate(_hoverColorAnimation)! : (polygon.color ?? _color); } else if (_currentHoverItem != null && _currentHoverItem == polygon) { paint.color = _themeData.shapeHoverColor != Colors.transparent - ? _forwardHoverColor.evaluate(_hoverColorAnimation) + ? _forwardHoverColor.evaluate(_hoverColorAnimation)! : (polygon.color ?? _color); } else { paint.color = polygon.color ?? _color; @@ -3053,17 +3879,14 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { if (_previousHoverItem != null && _previousHoverItem == polygon) { _updateHoverStrokeColor(paint, polygon, _reverseHoverStrokeColor); - paint.strokeWidth = - _getDesiredValue(polygon.strokeWidth ?? _strokeWidth, controller); + paint.strokeWidth = polygon.strokeWidth ?? _strokeWidth; } else if (_currentHoverItem != null && _currentHoverItem == polygon) { _updateHoverStrokeColor(paint, polygon, _forwardHoverStrokeColor); if (_themeData.shapeHoverStrokeWidth != null && - _themeData.shapeHoverStrokeWidth > 0.0) { - paint.strokeWidth = - _getDesiredValue(_themeData.shapeHoverStrokeWidth, controller); + _themeData.shapeHoverStrokeWidth! > 0.0) { + paint.strokeWidth = _themeData.shapeHoverStrokeWidth!; } else { - paint.strokeWidth = - _getDesiredValue(polygon.strokeWidth ?? _strokeWidth, controller); + paint.strokeWidth = polygon.strokeWidth ?? _strokeWidth; } } else { _updateDefaultStroke(paint, polygon); @@ -3073,14 +3896,13 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { void _updateDefaultStroke(Paint paint, MapPolygon polygon) { paint ..color = polygon.strokeColor ?? _strokeColor - ..strokeWidth = - _getDesiredValue(polygon.strokeWidth ?? _strokeWidth, controller); + ..strokeWidth = polygon.strokeWidth ?? _strokeWidth; } void _updateHoverStrokeColor( Paint paint, MapPolygon polygon, ColorTween tween) { if (_themeData.shapeHoverStrokeColor != Colors.transparent) { - paint.color = tween.evaluate(_hoverColorAnimation); + paint.color = tween.evaluate(_hoverColorAnimation)!; } else { paint.color = polygon.strokeColor ?? _strokeColor; } @@ -3091,45 +3913,139 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { /// [MapTileLayer]. /// /// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// body: SfMaps( -/// layers: [ -/// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "continent", +/// List _circles; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// _circles = const [ +/// MapLatLng(-14.235004, -51.92528), +/// MapLatLng(51.16569, 10.451526), +/// MapLatLng(-25.274398, 133.775136), +/// MapLatLng(20.593684, 78.96288), +/// MapLatLng(61.52401, 105.318756) +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// 'assets/world_map.json', +/// shapeDataField: 'name', +/// ); +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: Center( +/// child: Container( +/// height: 350, +/// child: Padding( +/// padding: EdgeInsets.only(left: 15, right: 15), +/// child: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// sublayers: [ +/// MapCircleLayer( +/// circles: List.generate( +/// _circles.length, +/// (int index) { +/// return MapCircle( +/// center: _circles[index], +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], /// ), -/// sublayers: [ -/// MapCircleLayer( -/// circles: List.generate( -/// circles.length, -/// (int index) { -/// return MapCircle( -/// center: circles[index], -/// ); -/// }, -/// ).toSet(), -/// ), -/// ], /// ), -/// ], -/// ), -/// ); -/// } +/// )), +/// ); +/// } +/// +/// See also: +/// * `MapCircleLayer.inverted()` named constructor, for inverted color support. /// ``` class MapCircleLayer extends MapVectorLayer { /// Creates the [MapCircleLayer]. MapCircleLayer({ - Key key, - @required this.circles, + Key? key, + required this.circles, + this.animation, + this.color, + this.strokeWidth = 1, + this.strokeColor, + IndexedWidgetBuilder? tooltipBuilder, + }) : _fillType = _VectorFillType.inner, + super(key: key, tooltipBuilder: tooltipBuilder); + + /// You may highlight a specific area on a map to make it more readable by + /// using the [circles] property of [MapCircleLayer.inverted] by adding mask + /// to the outer region of the highlighted circles. + /// + /// Only one [MapCircleLayer.inverted] can be added and must be positioned at + /// the top of all sublayers added under the [sublayers] property. + /// + /// ```dart + /// List _circles; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _circles = const [ + /// MapLatLng(-14.235004, -51.92528), + /// MapLatLng(51.16569, 10.451526), + /// MapLatLng(-25.274398, 133.775136), + /// MapLatLng(20.593684, 78.96288), + /// MapLatLng(61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapCircleLayer.inverted( + /// circles: List.generate( + /// _circles.length, + /// (int index) { + /// return MapCircle( + /// center: _circles[index], + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// See also: + /// * [MapCircleLayer()], for normal circle shape on the map. + /// ``` + MapCircleLayer.inverted({ + Key? key, + required this.circles, this.animation, - this.color = const Color.fromRGBO(51, 153, 144, 1), this.strokeWidth = 1, - this.strokeColor = const Color.fromRGBO(51, 153, 144, 1), - IndexedWidgetBuilder tooltipBuilder, - }) : assert(circles != null), + this.color, + this.strokeColor, + IndexedWidgetBuilder? tooltipBuilder, + }) : _fillType = _VectorFillType.outer, super(key: key, tooltipBuilder: tooltipBuilder); /// A collection of [MapCircle]. @@ -3138,99 +4054,139 @@ class MapCircleLayer extends MapVectorLayer { /// and [MapCircle.radius]. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _circles; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _circles = const [ + /// MapLatLng(-14.235004, -51.92528), + /// MapLatLng(51.16569, 10.451526), + /// MapLatLng(-25.274398, 133.775136), + /// MapLatLng(20.593684, 78.96288), + /// MapLatLng(61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// _circles.length, + /// (int index) { + /// return MapCircle( + /// center: _circles[index], + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], /// ), - /// sublayers: [ - /// MapCircleLayer( - /// circles: List.generate( - /// circles.length, - /// (int index) { - /// return MapCircle( - /// center: circles[index], - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// )), + /// ); + /// } /// ``` final Set circles; - /// Animation for the [circles] in [MapPolylineLayer]. + /// Animation for the [circles] in [MapCircleLayer]. /// /// By default, [circles] will be rendered without any animation. The /// animation can be set as shown in the below code snippet. You can customise /// the animation flow, curve, duration and listen to the animation status. /// /// ```dart - /// AnimationController _animationController; - /// Animation _animation; + /// List circles; + /// MapShapeSource _mapSource; + /// AnimationController _animationController; + /// Animation _animation; /// - /// @override - /// void initState() { - /// _animationController = AnimationController( + /// @override + /// void initState() { + /// _circles = const [ + /// MapLatLng(-14.235004, -51.92528), + /// MapLatLng(51.16569, 10.451526), + /// MapLatLng(-25.274398, 133.775136), + /// MapLatLng(20.593684, 78.96288), + /// MapLatLng(61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ); + /// _animationController = AnimationController( /// duration: Duration(seconds: 3), /// vsync: this, - /// ); + /// ); /// - /// _animation = CurvedAnimation( - /// parent: _animationController, - /// curve: Curves.easeInOut), - /// ); + /// _animation = + /// CurvedAnimation(parent: _animationController, + /// curve: Curves.easeInOut); /// - /// _animationController.forward(from: 0); - /// super.initState(); - /// } + /// _animationController.forward(from: 0); + /// super.initState(); + /// } /// - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// _circles.length, + /// (int index) { + /// return MapCircle( + /// center: _circles[index], + /// ); + /// }, + /// ).toSet(), + /// animation: _animation, + /// ), + /// ], + /// ), + /// ], /// ), - /// sublayers: [ - /// MapCircleLayer( - /// circles: List.generate( - /// circles.length, - /// (int index) { - /// return MapCircle( - /// center: circles[index], - /// ); - /// }, - /// ).toSet(), - /// animation: _animation, - /// ), - /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// )), + /// ); + /// } /// /// @override /// void dispose() { - /// _animationController?.dispose(); + /// _animationController?.dispose(); /// super.dispose(); /// } - /// /// ``` - final Animation animation; + final Animation? animation; /// The fill color of all the [MapCircle]. /// @@ -3238,36 +4194,60 @@ class MapCircleLayer extends MapVectorLayer { /// [MapCircle.color] property. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _circles; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _circles = const [ + /// MapLatLng(-14.235004, -51.92528), + /// MapLatLng(51.16569, 10.451526), + /// MapLatLng(-25.274398, 133.775136), + /// MapLatLng(20.593684, 78.96288), + /// MapLatLng(61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// _circles.length, + /// (int index) { + /// return MapCircle( + /// center: _circles[index], + /// ); + /// }, + /// ).toSet(), + /// color: Colors.red, + /// ), + /// ], + /// ), + /// ], /// ), - /// sublayers: [ - /// MapCircleLayer( - /// circles: List.generate( - /// circles.length, - /// (int index) { - /// return MapCircle( - /// center: circles[index], - /// ); - /// }, - /// ).toSet(), - /// color: Colors.red, - /// ), - /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// )), + /// ); + /// } /// ``` - final Color color; + final Color? color; /// The stroke width of all the [MapCircle]. /// @@ -3275,34 +4255,59 @@ class MapCircleLayer extends MapVectorLayer { /// [MapCircle.strokeWidth] property. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _circles; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _circles = const [ + /// MapLatLng(-14.235004, -51.92528), + /// MapLatLng(51.16569, 10.451526), + /// MapLatLng(-25.274398, 133.775136), + /// MapLatLng(20.593684, 78.96288), + /// MapLatLng(61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// _circles.length, + /// (int index) { + /// return MapCircle( + /// center: _circles[index], + /// ); + /// }, + /// ).toSet(), + /// strokeWidth: 4.0, + /// strokeColor: Colors.red, + /// ), + /// ], + /// ), + /// ], /// ), - /// sublayers: [ - /// MapCircleLayer( - /// circles: List.generate( - /// circles.length, - /// (int index) { - /// return MapCircle( - /// center: circles[index], - /// ); - /// }, - /// ).toSet(), - /// strokeWidth: 4, - /// ), - /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// )), + /// ); + /// } /// ``` /// See also: /// [strokeColor], to set the stroke color for the circles. @@ -3314,40 +4319,67 @@ class MapCircleLayer extends MapVectorLayer { /// [MapCircle.strokeColor] property. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _circles; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _circles = const [ + /// MapLatLng(-14.235004, -51.92528), + /// MapLatLng(51.16569, 10.451526), + /// MapLatLng(-25.274398, 133.775136), + /// MapLatLng(20.593684, 78.96288), + /// MapLatLng(61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// _circles.length, + /// (int index) { + /// return MapCircle( + /// center: _circles[index], + /// ); + /// }, + /// ).toSet(), + /// strokeWidth: 4.0, + /// strokeColor: Colors.red, + /// ), + /// ], + /// ), + /// ], /// ), - /// sublayers: [ - /// MapCircleLayer( - /// circles: List.generate( - /// circles.length, - /// (int index) { - /// return MapCircle( - /// center: circles[index], - /// ); - /// }, - /// ).toSet(), - /// strokeWidth: 4, - /// strokeColor: Colors.red, - /// ), - /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// )), + /// ); + /// } /// ``` /// /// See also: /// [strokeWidth], to set the stroke width for the circles. - final Color strokeColor; + final Color? strokeColor; + + /// Strategies for painting a circle in a canvas. + final _VectorFillType _fillType; @override Widget build(BuildContext context) { @@ -3359,28 +4391,54 @@ class MapCircleLayer extends MapVectorLayer { strokeWidth: strokeWidth, tooltipBuilder: tooltipBuilder, circleLayer: this, + fillType: _fillType, ); } -} -class _MapCircleLayer extends StatefulWidget { + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + if (circles.isNotEmpty) { + final _DebugVectorShapeTree pointerTreeNode = + _DebugVectorShapeTree(circles); + properties.add(pointerTreeNode.toDiagnosticsNode()); + } + properties.add(ObjectFlagProperty.has('animation', animation)); + properties.add(ObjectFlagProperty.has( + 'tooltip', tooltipBuilder)); + + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + if (strokeColor != null) { + properties.add(ColorProperty('strokeColor', strokeColor)); + } + + properties.add(DoubleProperty('strokeWidth', strokeWidth)); + } +} + +class _MapCircleLayer extends StatefulWidget { const _MapCircleLayer({ - this.circles, - this.animation, - this.color, - this.strokeWidth, - this.strokeColor, - this.tooltipBuilder, - this.circleLayer, + required this.circles, + required this.animation, + required this.color, + required this.strokeWidth, + required this.strokeColor, + required this.tooltipBuilder, + required this.circleLayer, + required this.fillType, }); final Set circles; - final Animation animation; - final Color color; + final Animation? animation; + final Color? color; final double strokeWidth; - final Color strokeColor; - final IndexedWidgetBuilder tooltipBuilder; + final Color? strokeColor; + final IndexedWidgetBuilder? tooltipBuilder; final MapCircleLayer circleLayer; + final _VectorFillType fillType; @override _MapCircleLayerState createState() => _MapCircleLayerState(); @@ -3388,8 +4446,10 @@ class _MapCircleLayer extends StatefulWidget { class _MapCircleLayerState extends State<_MapCircleLayer> with SingleTickerProviderStateMixin { - AnimationController _hoverAnimationController; - SfMapsThemeData _mapsThemeData; + MapController? _controller; + late AnimationController _hoverAnimationController; + late SfMapsThemeData _mapsThemeData; + late MapLayerInheritedWidget ancestor; @override void initState() { @@ -3398,62 +4458,129 @@ class _MapCircleLayerState extends State<_MapCircleLayer> vsync: this, duration: const Duration(milliseconds: 250)); } + @override + void didChangeDependencies() { + if (_controller == null) { + ancestor = context + .dependOnInheritedWidgetOfExactType()!; + _controller = ancestor.controller; + } + super.didChangeDependencies(); + } + @override void dispose() { - _hoverAnimationController?.dispose(); + _controller = null; + _hoverAnimationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { + assert(() { + if (widget.fillType == _VectorFillType.outer) { + for (final MapCircle circle in widget.circles) { + assert( + circle.color == null, + throw FlutterError.fromParts([ + ErrorSummary('Incorrect MapCircle arguments.'), + ErrorDescription( + 'Inverted circles cannot be customized individually.'), + ErrorHint( + '''To customize all the circle's color, use MapCircleLayer.color''') + ])); + assert( + circle.strokeColor == null, + throw FlutterError.fromParts([ + ErrorSummary('Incorrect MapCircle arguments.'), + ErrorDescription( + 'Inverted circles cannot be customized individually.'), + ErrorHint( + '''To customize all the circle's stroke color, use MapCircleLayer.strokeColor''') + ])); + assert( + circle.strokeWidth == null, + throw FlutterError.fromParts([ + ErrorSummary('Incorrect MapCircle arguments.'), + ErrorDescription( + 'Inverted circles cannot be customized individually.'), + ErrorHint( + '''To customize all the circle's stroke width, use MapCircleLayer.strokeWidth''') + ])); + } + } + return true; + }()); + final ThemeData themeData = Theme.of(context); final bool isDesktop = kIsWeb || themeData.platform == TargetPlatform.macOS || - themeData.platform == TargetPlatform.windows; - _mapsThemeData = SfMapsTheme.of(context); + themeData.platform == TargetPlatform.windows || + themeData.platform == TargetPlatform.linux; + _mapsThemeData = SfMapsTheme.of(context)!; return _MapCircleLayerRenderObject( + controller: _controller, circles: widget.circles, animation: widget.animation, - color: widget.color, + color: widget.color ?? + (widget.fillType == _VectorFillType.inner + ? const Color.fromRGBO(51, 153, 144, 1) + : (_mapsThemeData.brightness == Brightness.light + ? const Color.fromRGBO(3, 3, 3, 0.15) + : const Color.fromRGBO(0, 0, 0, 0.2))), strokeWidth: widget.strokeWidth, - strokeColor: widget.strokeColor, + strokeColor: widget.strokeColor ?? + (widget.fillType == _VectorFillType.inner + ? const Color.fromRGBO(51, 153, 144, 1) + : (_mapsThemeData.brightness == Brightness.light + ? const Color.fromRGBO(98, 0, 238, 1) + : const Color.fromRGBO(187, 134, 252, 0.5))), tooltipBuilder: widget.tooltipBuilder, circleLayer: widget.circleLayer, hoverAnimationController: _hoverAnimationController, themeData: _mapsThemeData, isDesktop: isDesktop, + fillType: widget.fillType, + state: this, ); } } class _MapCircleLayerRenderObject extends LeafRenderObjectWidget { const _MapCircleLayerRenderObject({ - this.circles, - this.animation, - this.color, - this.strokeWidth, - this.strokeColor, - this.tooltipBuilder, - this.circleLayer, - this.hoverAnimationController, - this.themeData, - this.isDesktop, + required this.controller, + required this.circles, + required this.animation, + required this.color, + required this.strokeWidth, + required this.strokeColor, + required this.tooltipBuilder, + required this.circleLayer, + required this.hoverAnimationController, + required this.themeData, + required this.isDesktop, + required this.fillType, + required this.state, }); + final MapController? controller; final Set circles; - final Animation animation; + final Animation? animation; final Color color; final double strokeWidth; final Color strokeColor; - final IndexedWidgetBuilder tooltipBuilder; + final IndexedWidgetBuilder? tooltipBuilder; final MapCircleLayer circleLayer; final AnimationController hoverAnimationController; final SfMapsThemeData themeData; final bool isDesktop; + final _VectorFillType fillType; + final _MapCircleLayerState state; @override _RenderMapCircle createRenderObject(BuildContext context) { return _RenderMapCircle( + controller: controller, circles: circles, animation: animation, color: color, @@ -3465,6 +4592,8 @@ class _MapCircleLayerRenderObject extends LeafRenderObjectWidget { circleLayer: circleLayer, hoverAnimationController: hoverAnimationController, isDesktop: isDesktop, + fillType: fillType, + state: state, ); } @@ -3479,24 +4608,29 @@ class _MapCircleLayerRenderObject extends LeafRenderObjectWidget { ..tooltipBuilder = tooltipBuilder ..themeData = themeData ..context = context - ..circleLayer = circleLayer; + ..fillType = fillType + ..state = state; } } class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { _RenderMapCircle({ - Set circles, - Animation animation, - Color color, - double strokeWidth, - Color strokeColor, - IndexedWidgetBuilder tooltipBuilder, - SfMapsThemeData themeData, - BuildContext context, - MapCircleLayer circleLayer, - AnimationController hoverAnimationController, - bool isDesktop, - }) : _circles = circles, + required MapController? controller, + required Set circles, + required Animation? animation, + required Color color, + required double strokeWidth, + required Color strokeColor, + required IndexedWidgetBuilder? tooltipBuilder, + required SfMapsThemeData themeData, + required BuildContext context, + required MapCircleLayer circleLayer, + required AnimationController hoverAnimationController, + required bool isDesktop, + required this.fillType, + required this.state, + }) : _controller = controller, + _circles = circles, _animation = animation, _color = color, _strokeColor = strokeColor, @@ -3513,52 +4647,54 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { _reverseHoverStrokeColor = ColorTween(); _hoverColorAnimation = CurvedAnimation( parent: hoverAnimationController, curve: Curves.easeInOut); - _circlesInList = _circles?.toList(); + _circlesInList = _circles.toList(); _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; } - MapController controller; - AnimationController hoverAnimationController; - RenderSublayerContainer vectorLayerContainer; - MapCircleLayer circleLayer; - bool isDesktop; - BuildContext context; - TapGestureRecognizer _tapGestureRecognizer; - Animation _hoverColorAnimation; - ColorTween _forwardHoverColor; - ColorTween _reverseHoverColor; - ColorTween _forwardHoverStrokeColor; - ColorTween _reverseHoverStrokeColor; - MapCircle _previousHoverItem; - MapCircle _currentHoverItem; - int _selectedIndex = -1; - MapCircle _selectedCircle; - List _circlesInList; + final MapController? _controller; + late int _selectedIndex = -1; + late _VectorFillType fillType; + late _MapCircleLayerState state; + late AnimationController hoverAnimationController; + late MapCircleLayer circleLayer; + late bool isDesktop; + late BuildContext context; + late TapGestureRecognizer _tapGestureRecognizer; + late Animation _hoverColorAnimation; + late ColorTween _forwardHoverColor; + late ColorTween _reverseHoverColor; + late ColorTween _forwardHoverStrokeColor; + late ColorTween _reverseHoverStrokeColor; + late MapCircle _selectedCircle; + + MapCircle? _previousHoverItem; + MapCircle? _currentHoverItem; + List? _circlesInList; Set get circles => _circles; Set _circles; - set circles(Set value) { + set circles(Set? value) { assert(value != null); if (_circles == value || value == null) { return; } _circles = value; - _circlesInList = _circles?.toList(); + _circlesInList = _circles.toList(); markNeedsPaint(); } - Animation get animation => _animation; - Animation _animation; - set animation(Animation value) { + Animation? get animation => _animation; + Animation? _animation; + set animation(Animation? value) { if (_animation == value) { return; } _animation = value; } - IndexedWidgetBuilder get tooltipBuilder => _tooltipBuilder; - IndexedWidgetBuilder _tooltipBuilder; - set tooltipBuilder(IndexedWidgetBuilder value) { + IndexedWidgetBuilder? get tooltipBuilder => _tooltipBuilder; + IndexedWidgetBuilder? _tooltipBuilder; + set tooltipBuilder(IndexedWidgetBuilder? value) { if (_tooltipBuilder == value) { return; } @@ -3656,7 +4792,7 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { Color _getHoverFillColor(MapCircle circle) { if (hasHoverColor) { - return _themeData.shapeHoverColor; + return _themeData.shapeHoverColor!; } final Color color = circle.color ?? _color; return color.withOpacity( @@ -3667,7 +4803,7 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { Color _getHoverStrokeColor(MapCircle circle) { if (hasHoverStrokeColor) { - return _themeData.shapeHoverStrokeColor; + return _themeData.shapeHoverStrokeColor!; } final Color strokeColor = circle.strokeColor ?? _strokeColor; return strokeColor.withOpacity( @@ -3684,10 +4820,13 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { _initializeHoverItemTween(); } - final RenderSublayerContainer vectorParent = parent; final ShapeLayerChildRenderBoxBase tooltipRenderer = - vectorParent.tooltipKey?.currentContext?.findRenderObject(); - tooltipRenderer?.hideTooltip(); + // ignore: lines_longer_than_80_chars + // ignore: avoid_as + _controller!.tooltipKey!.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; + tooltipRenderer.hideTooltip(); } void _handleZooming(MapZoomDetails details) { @@ -3698,32 +4837,28 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } - void _handleReset() { - markNeedsPaint(); - } - - void _handleRefresh() { - markNeedsPaint(); - } - - void _handleInteraction(Offset position) { - final RenderSublayerContainer vectorParent = parent; - if (vectorParent.tooltipKey != null && _tooltipBuilder != null) { + void _handleInteraction(Offset position, + [PointerKind kind = PointerKind.touch]) { + if (_controller?.tooltipKey != null && _tooltipBuilder != null) { final ShapeLayerChildRenderBoxBase tooltipRenderer = - vectorParent.tooltipKey.currentContext.findRenderObject(); - final bool isTileLayer = controller.tileCurrentLevelDetails != null; - final Size boxSize = isTileLayer ? controller.totalTileSize : size; - final Offset translationOffset = - _getTranslationOffset(controller, isTileLayer); + // ignore: avoid_as + _controller!.tooltipKey!.currentContext!.findRenderObject() + // ignore: avoid_as + as ShapeLayerChildRenderBoxBase; + final Size boxSize = _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; + final Offset translationOffset = _getTranslation(_controller!); if (_selectedIndex != -1) { - final Offset center = pixelFromLatLng( + Offset center = pixelFromLatLng( _selectedCircle.center.latitude, _selectedCircle.center.longitude, boxSize, translationOffset, - controller.shapeLayerSizeFactor, + _controller!.shapeLayerSizeFactor, ); + center = _getScaledOffset(center, _controller!); final Rect circleRect = Rect.fromCircle(center: center, radius: _selectedCircle.radius); @@ -3731,7 +4866,8 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { _selectedIndex, circleRect, MapLayerElement.vector, - vectorParent.getSublayerIndex(circleLayer), + kind, + state.ancestor.sublayers?.indexOf(circleLayer), position, ); } @@ -3747,13 +4883,13 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { MouseCursor get cursor => SystemMouseCursors.basic; @override - PointerEnterEventListener get onEnter => null; + PointerEnterEventListener? get onEnter => null; // As onHover property of MouseHoverAnnotation was removed only in the // beta channel, once it is moved to stable, will remove this property. @override // ignore: override_on_non_overriding_member - PointerHoverEventListener get onHover => null; + PointerHoverEventListener? get onHover => null; @override PointerExitEventListener get onExit => _handlePointerExit; @@ -3764,29 +4900,32 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { @override bool hitTestSelf(Offset position) { - if (_animation != null && !_animation.isCompleted) { + if (_animation != null && !_animation!.isCompleted) { return false; } - int index = _circlesInList.length - 1; - final bool isTileLayer = controller.tileCurrentLevelDetails != null; - final Size boxSize = isTileLayer ? controller.totalTileSize : size; - final Offset translationOffset = - _getTranslationOffset(controller, isTileLayer); + int index = _circlesInList!.length - 1; + final Size boxSize = _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; + final Offset translationOffset = getTranslationOffset(_controller!); - for (final MapCircle circle in _circlesInList?.reversed) { + for (final MapCircle circle in _circlesInList!.reversed) { if (circle.onTap != null || _tooltipBuilder != null || canHover) { - final Path path = Path() - ..addOval(Rect.fromCircle( - center: pixelFromLatLng( - circle.center.latitude, - circle.center.longitude, - boxSize, - translationOffset, - controller.shapeLayerSizeFactor, - ), - radius: circle.radius, - )); + final Path path = Path(); + Offset center = pixelFromLatLng( + circle.center.latitude, + circle.center.longitude, + boxSize, + translationOffset, + getLayerSizeFactor(_controller!), + ); + + center = _getScaledOffset(center, _controller!); + path.addOval(Rect.fromCircle( + center: center, + radius: circle.radius, + )); if (path.contains(position)) { _selectedCircle = circle; _selectedIndex = index; @@ -3801,30 +4940,28 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { @override void attach(PipelineOwner owner) { super.attach(owner); - vectorLayerContainer = parent; - controller = vectorLayerContainer.controller; - if (controller != null) { - controller + if (_controller != null) { + _controller! ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) - ..addResetListener(_handleReset) - ..addRefreshListener(_handleRefresh); + ..addResetListener(markNeedsPaint) + ..addRefreshListener(markNeedsPaint); } _animation?.addListener(markNeedsPaint); - _hoverColorAnimation?.addListener(markNeedsPaint); + _hoverColorAnimation.addListener(markNeedsPaint); } @override void detach() { - if (controller != null) { - controller + if (_controller != null) { + _controller! ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) - ..removeResetListener(_handleReset) - ..removeRefreshListener(_handleRefresh); + ..removeResetListener(markNeedsPaint) + ..removeRefreshListener(markNeedsPaint); } _animation?.removeListener(markNeedsPaint); - _hoverColorAnimation?.removeListener(markNeedsPaint); + _hoverColorAnimation.removeListener(markNeedsPaint); _circlesInList?.clear(); _circlesInList = null; super.detach(); @@ -3841,8 +4978,10 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { _initializeHoverItemTween(); } - final RenderBox renderBox = context.findRenderObject(); - _handleInteraction(renderBox.globalToLocal(event.position)); + // ignore: avoid_as + final RenderBox renderBox = context.findRenderObject() as RenderBox; + _handleInteraction( + renderBox.globalToLocal(event.position), PointerKind.hover); } } @@ -3856,37 +4995,61 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { @override void paint(PaintingContext context, Offset offset) { - if (_animation != null && _animation.value == 0.0) { + if (_animation != null && _animation!.value == 0.0) { return; } + context.canvas.save(); final Paint fillPaint = Paint()..isAntiAlias = true; final Paint strokePaint = Paint() ..isAntiAlias = true ..style = PaintingStyle.stroke; - final bool isTileLayer = controller.tileCurrentLevelDetails != null; - final Size boxSize = isTileLayer ? controller.totalTileSize : size; - final Offset translationOffset = - _getTranslationOffset(controller, isTileLayer); - controller.applyTransform(context, offset, true); + final Size boxSize = _controller?.layerType == LayerType.tile + ? _controller!.totalTileSize! + : size; + final Offset translationOffset = getTranslationOffset(_controller!); + // Check whether the color will apply to the inner side or outer side of + // the circle shape. + final bool isInverted = fillType == _VectorFillType.outer; + Path path = Path(); for (final MapCircle circle in circles) { - final Path path = Path(); - path - ..addOval(Rect.fromCircle( - center: pixelFromLatLng( - circle.center.latitude, - circle.center.longitude, - boxSize, - translationOffset, - controller.shapeLayerSizeFactor, - ), - radius: _getDesiredValue( - circle.radius * (_animation?.value ?? 1.0), controller), - )); - _updateFillColor(circle, fillPaint); - _updateStroke(circle, strokePaint); - context.canvas..drawPath(path, fillPaint)..drawPath(path, strokePaint); + // Creating new path for each circle for applying individual circle + // customization like color, strokeColor and strokeWidth customization. + // For inverted circle, all circles are added in a single path to make + // the circles area to be transparent while combining the path. + if (!isInverted) { + path = Path(); + } + Offset circleCenter = pixelFromLatLng( + circle.center.latitude, + circle.center.longitude, + boxSize, + translationOffset, + getLayerSizeFactor(_controller!), + ); + + circleCenter = _getScaledOffset(circleCenter, _controller!); + final Rect circleRect = Rect.fromCircle( + center: circleCenter, + radius: circle.radius * (_animation?.value ?? 1.0), + ); + + path.addOval(circleRect); + if (!isInverted) { + _updateFillColor(circle, fillPaint); + _updateStroke(circle, strokePaint); + context.canvas..drawPath(path, fillPaint)..drawPath(path, strokePaint); + } + } + + if (isInverted) { + fillPaint.color = _color; + strokePaint + ..strokeWidth = _strokeWidth + ..color = _strokeColor; + _drawInvertedPath( + context, path, _controller!, fillPaint, strokePaint, offset); } context.canvas.restore(); } @@ -3899,11 +5062,11 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { if (_previousHoverItem != null && _previousHoverItem == circle) { paint.color = _themeData.shapeHoverColor != Colors.transparent - ? _reverseHoverColor.evaluate(_hoverColorAnimation) + ? _reverseHoverColor.evaluate(_hoverColorAnimation)! : (circle.color ?? _color); } else if (_currentHoverItem != null && _currentHoverItem == circle) { paint.color = _themeData.shapeHoverColor != Colors.transparent - ? _forwardHoverColor.evaluate(_hoverColorAnimation) + ? _forwardHoverColor.evaluate(_hoverColorAnimation)! : (circle.color ?? _color); } else { paint.color = circle.color ?? _color; @@ -3918,17 +5081,14 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { if (_previousHoverItem != null && _previousHoverItem == circle) { _updateHoverStrokeColor(paint, circle, _reverseHoverStrokeColor); - paint.strokeWidth = - _getDesiredValue(circle.strokeWidth ?? _strokeWidth, controller); + paint.strokeWidth = circle.strokeWidth ?? _strokeWidth; } else if (_currentHoverItem != null && _currentHoverItem == circle) { _updateHoverStrokeColor(paint, circle, _forwardHoverStrokeColor); if (_themeData.shapeHoverStrokeWidth != null && - _themeData.shapeHoverStrokeWidth > 0.0) { - paint.strokeWidth = - _getDesiredValue(_themeData.shapeHoverStrokeWidth, controller); + _themeData.shapeHoverStrokeWidth! > 0.0) { + paint.strokeWidth = _themeData.shapeHoverStrokeWidth!; } else { - paint.strokeWidth = - _getDesiredValue(circle.strokeWidth ?? _strokeWidth, controller); + paint.strokeWidth = circle.strokeWidth ?? _strokeWidth; } } else { _updateDefaultStroke(paint, circle); @@ -3938,14 +5098,13 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { void _updateDefaultStroke(Paint paint, MapCircle circle) { paint ..color = circle.strokeColor ?? _strokeColor - ..strokeWidth = - _getDesiredValue(circle.strokeWidth ?? _strokeWidth, controller); + ..strokeWidth = circle.strokeWidth ?? _strokeWidth; } void _updateHoverStrokeColor( Paint paint, MapCircle circle, ColorTween tween) { if (_themeData.shapeHoverStrokeColor != Colors.transparent) { - paint.color = tween.evaluate(_hoverColorAnimation); + paint.color = tween.evaluate(_hoverColorAnimation)!; } else { paint.color = circle.strokeColor ?? _strokeColor; } @@ -3955,40 +5114,71 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { /// Creates a line between the two geographical coordinates on the map. /// /// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// body: SfMaps( -/// layers: [ -/// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "continent", -/// ), -/// sublayers: [ -/// MapLineLayer( -/// lines: List.generate( -/// lines.length, -/// (int index) { -/// return MapLine( -/// from: lines[index].from, -/// to: lines[index].to, -/// ); -/// }, -/// ).toSet(), -/// ), -/// ], -/// ), -/// ], -/// ), -/// ); +/// List _lines; +/// MapZoomPanBehavior _zoomPanBehavior; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// _zoomPanBehavior = MapZoomPanBehavior( +/// focalLatLng: MapLatLng(40.7128, -95.3698), +/// zoomLevel: 3, +/// ); +/// +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ); +/// +/// _lines = [ +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), +/// ]; +/// +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// zoomPanBehavior: _zoomPanBehavior, +/// sublayers: [ +/// MapLineLayer( +/// lines: List.generate( +/// _lines.length, +/// (int index) { +/// return MapLine( +/// from: _lines[index].from, +/// to: _lines[index].to, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], +/// ), +/// ); +/// } +/// +/// class Model { +/// Model(this.from, this.to); +/// +/// MapLatLng from; +/// MapLatLng to; /// } /// ``` -class MapLine { +class MapLine extends DiagnosticableTree { /// Creates a [MapLine]. const MapLine({ - @required this.from, - @required this.to, + required this.from, + required this.to, this.dashArray = const [0, 0], this.color, this.width, @@ -3998,33 +5188,64 @@ class MapLine { /// The starting coordinate of the line. /// ///```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapLineLayer( - /// lines: List.generate( - /// lines.length, - /// (int index) { - /// return MapLine( - /// from: lines[index].from, - /// to: lines[index].to, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// List _lines; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _lines = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _lines.length, + /// (int index) { + /// return MapLine( + /// from: _lines[index].from, + /// to: _lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` final MapLatLng from; @@ -4032,33 +5253,64 @@ class MapLine { /// The ending coordinate of the line. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapLineLayer( - /// lines: List.generate( - /// lines.length, - /// (int index) { - /// return MapLine( - /// from: lines[index].from, - /// to: lines[index].to, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// List _lines; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _lines = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _lines.length, + /// (int index) { + /// return MapLine( + /// from: _lines[index].from, + /// to: _lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` final MapLatLng to; @@ -4070,34 +5322,65 @@ class MapLine { /// again till the end of the line. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapLineLayer( - /// lines: List.generate( - /// lines.length, - /// (int index) { - /// return MapLine( - /// from: lines[index].from, - /// to: lines[index].to, - /// dashArray: [8, 3, 4, 3], - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// List _lines; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _lines = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _lines.length, + /// (int index) { + /// return MapLine( + /// from: _lines[index].from, + /// to: _lines[index].to, + /// dashArray: [8, 3, 4, 3], + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` final List dashArray; @@ -4105,73 +5388,134 @@ class MapLine { /// Color of the line. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapLineLayer( - /// lines: List.generate( - /// lines.length, - /// (int index) { - /// return MapLine( - /// from: lines[index].from, - /// to: lines[index].to, - /// color: Colors.blue, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// List _lines; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _lines = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _lines.length, + /// (int index) { + /// return MapLine( + /// from: _lines[index].from, + /// to: _lines[index].to, + /// color: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` - final Color color; + final Color? color; /// Width of the line. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapLineLayer( - /// lines: List.generate( - /// lines.length, - /// (int index) { - /// return MapLine( - /// from: lines[index].from, - /// to: lines[index].to, - /// width: 4, - /// color: Colors.blue, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// List _lines; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _lines = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _lines.length, + /// (int index) { + /// return MapLine( + /// from: _lines[index].from, + /// to: _lines[index].to, + /// width: 4.0, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` - final double width; + final double? width; /// Callback to receive tap event for this line. /// @@ -4179,82 +5523,156 @@ class MapLine { /// passed on it as shown in the below code snippet. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _lines; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// int _selectedIndex; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _lines = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _lines.length, + /// (int index) { + /// return MapLine( + /// from: _lines[index].from, + /// to: _lines[index].to, + /// color: + /// _selectedIndex == index ? Colors.blue + /// : Colors.red, + /// onTap: () { + /// setState(() { + /// _selectedIndex = index; + /// }); + /// }); + /// }, + /// ).toSet(), + /// ), + /// ], /// ), - /// sublayers: [ - /// MapLineLayer( - /// lines: List.generate( - /// lines.length, - /// (int index) { - /// return MapLine( - /// from: lines[index].from, - /// to: lines[index].to, - /// color: _selectedIndex == index - /// ? Colors.blue - /// : Colors.red, - /// onTap: () { - /// setState(() { - /// _selectedIndex = index; - /// }); - /// }, - /// ); - /// }, - /// ).toSet(), - /// ), - // ], - /// ), - /// ], - /// ), - /// ); + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` - final VoidCallback onTap; + final VoidCallback? onTap; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('from', from)); + properties.add(DiagnosticsProperty('to', to)); + properties.add((DiagnosticsProperty>('dashArray', dashArray))); + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + if (width != null) { + properties.add(DoubleProperty('width', width)); + } + + properties.add(ObjectFlagProperty.has('onTap', onTap)); + } } /// Creates a polyline by connecting multiple geographical coordinates through /// group of [points]. /// /// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// body: SfMaps( -/// layers: [ -/// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "continent", -/// ), -/// sublayers: [ -/// MapPolylineLayer( -/// polylines: List.generate( -/// polylines.length, -/// (int index) { -/// return MapPolyline( -/// points: polylines[index].points, -/// ); -/// }, -/// ).toSet(), -/// ), -/// ], +/// MapZoomPanBehavior _zoomPanBehavior; +/// List _polyLines; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// _polyLines = [ +/// MapLatLng(13.0827, 80.2707), +/// MapLatLng(14.4673, 78.8242), +/// MapLatLng(14.9091, 78.0092), +/// MapLatLng(16.2160, 77.3566), +/// MapLatLng(17.1557, 76.8697), +/// MapLatLng(18.0975, 75.4249), +/// MapLatLng(18.5204, 73.8567), +/// MapLatLng(19.0760, 72.8777), +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// 'assets/india.json', +/// shapeDataField: 'name', +/// ); +/// +/// _zoomPanBehavior = MapZoomPanBehavior( +/// zoomLevel: 3, focalLatLng: MapLatLng(15.3173, 76.7139)); +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar(title: Text('Polyline')), +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// sublayers: [ +/// MapPolylineLayer( +/// polylines: List.generate( +/// 1, +/// (int index) { +/// return MapPolyline( +/// points: _polyLines, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// zoomPanBehavior: _zoomPanBehavior, +/// ), +/// ], /// ), -/// ], -/// ), -/// ); -/// } +/// ); +/// } /// ``` -class MapPolyline { +class MapPolyline extends DiagnosticableTree { /// Creates a [MapPolyline]. const MapPolyline({ - @required this.points, + required this.points, this.dashArray = const [0, 0], this.color, this.width, @@ -4266,33 +5684,59 @@ class MapPolyline { /// Lines are drawn between consecutive [points]. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapPolylineLayer( - /// polylines: List.generate( - /// lines.length, + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polyLines; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polyLines = [ + /// MapLatLng(13.0827, 80.2707), + /// MapLatLng(14.4673, 78.8242), + /// MapLatLng(14.9091, 78.0092), + /// MapLatLng(16.2160, 77.3566), + /// MapLatLng(17.1557, 76.8697), + /// MapLatLng(18.0975, 75.4249), + /// MapLatLng(18.5204, 73.8567), + /// MapLatLng(19.0760, 72.8777), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/india.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior( + /// zoomLevel: 3, focalLatLng: MapLatLng(15.3173, 76.7139)); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polyline')), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// 1, /// (int index) { - /// return MapPolyline( - /// points: lines[index].points, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); - /// } + /// return MapPolyline( + /// points: _polyLines, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], + /// ), + /// ); + /// } /// ``` final List points; @@ -4303,105 +5747,182 @@ class MapPolyline { /// again till the end of the polyline. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapPolylineLayer( - /// polylines: List.generate( - /// lines.length, + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polyLines; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polyLines = [ + /// MapLatLng(13.0827, 80.2707), + /// MapLatLng(14.4673, 78.8242), + /// MapLatLng(14.9091, 78.0092), + /// MapLatLng(16.2160, 77.3566), + /// MapLatLng(17.1557, 76.8697), + /// MapLatLng(18.0975, 75.4249), + /// MapLatLng(18.5204, 73.8567), + /// MapLatLng(19.0760, 72.8777), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/india.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior( + /// zoomLevel: 3, focalLatLng: MapLatLng(15.3173, 76.7139)); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polyline')), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// 1, /// (int index) { - /// return MapPolyline( - /// points: lines[index].points, - /// dashArray: [8, 3, 4, 3], - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); - /// } + /// return MapPolyline( + /// points: _polyLines, + /// dashArray: [8, 3, 4, 3], + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], + /// ), + /// ); + /// } /// ``` final List dashArray; /// Color of the polyline. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapPolylineLayer( - /// polylines: List.generate( - /// lines.length, + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polyLines; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polyLines = [ + /// MapLatLng(13.0827, 80.2707), + /// MapLatLng(14.4673, 78.8242), + /// MapLatLng(14.9091, 78.0092), + /// MapLatLng(16.2160, 77.3566), + /// MapLatLng(17.1557, 76.8697), + /// MapLatLng(18.0975, 75.4249), + /// MapLatLng(18.5204, 73.8567), + /// MapLatLng(19.0760, 72.8777), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/india.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior( + /// zoomLevel: 3, focalLatLng: MapLatLng(15.3173, 76.7139)); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polyline')), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// 1, /// (int index) { - /// return MapPolyline( - /// points: lines[index].points, - /// color: Colors.blue, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); - /// } + /// return MapPolyline( + /// points: _polyLines, + /// color: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], + /// ), + /// ); + /// } /// ``` - final Color color; + final Color? color; /// Width of the polyline. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapPolylineLayer( - /// polylines: List.generate( - /// lines.length, + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polyLines; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polyLines = [ + /// MapLatLng(13.0827, 80.2707), + /// MapLatLng(14.4673, 78.8242), + /// MapLatLng(14.9091, 78.0092), + /// MapLatLng(16.2160, 77.3566), + /// MapLatLng(17.1557, 76.8697), + /// MapLatLng(18.0975, 75.4249), + /// MapLatLng(18.5204, 73.8567), + /// MapLatLng(19.0760, 72.8777), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/india.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior( + /// zoomLevel: 3, focalLatLng: MapLatLng(15.3173, 76.7139)); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polyline')), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// 1, /// (int index) { - /// return MapPolyline( - /// points: lines[index].points, - /// color: Colors.blue, - /// width: 4, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); - /// } + /// return MapPolyline( + /// points: _polyLines, + /// width: 4, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], + /// ), + /// ); + /// } /// ``` - final double width; + final double? width; /// Callback to receive tap event for this polyline. /// @@ -4409,81 +5930,142 @@ class MapPolyline { /// index passed in it as shown in the below code snippet. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ - /// MapPolylineLayer( - /// polylines: List.generate( - /// lines.length, - /// (int index) { - /// return MapPolyline( - /// points: lines[index].points, - /// color: _selectedIndex == index - /// ? Colors.blue - /// : Colors.red, - /// onTap: () { - /// setState(() { - /// _selectedIndex = index; - /// }); + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polyLines; + /// MapShapeSource _mapSource; + /// int _selectedIndex; + /// + /// @override + /// void initState() { + /// _polyLines = [ + /// MapLatLng(13.0827, 80.2707), + /// MapLatLng(14.4673, 78.8242), + /// MapLatLng(14.9091, 78.0092), + /// MapLatLng(16.2160, 77.3566), + /// MapLatLng(17.1557, 76.8697), + /// MapLatLng(18.0975, 75.4249), + /// MapLatLng(18.5204, 73.8567), + /// MapLatLng(19.0760, 72.8777), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/india.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior( + /// zoomLevel: 3, focalLatLng: MapLatLng(15.3173, 76.7139)); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polyline')), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// 1, + /// (int index) { + /// return MapPolyline( + /// points: _polyLines, + /// color: _selectedIndex == index + /// ? Colors.blue + /// : Colors.red, + /// onTap: () { + /// setState(() { + /// _selectedIndex = index; + /// }); + /// }, + /// ); /// }, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); - /// } + /// ).toSet(), + /// ), + /// ], + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], + /// ), + /// ); + /// } /// ``` - final VoidCallback onTap; + final VoidCallback? onTap; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty>('points', points)); + properties.add((DiagnosticsProperty>('dashArray', dashArray))); + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + if (width != null) { + properties.add(DoubleProperty('width', width)); + } + + properties.add(ObjectFlagProperty.has('onTap', onTap)); + } } /// Creates a closed path which connects multiple geographical coordinates /// through group of [MapPolygon.points]. /// /// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// body: SfMaps( -/// layers: [ +/// MapZoomPanBehavior _zoomPanBehavior; +/// List _polygon; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// _polygon = [ +/// MapLatLng(38.8026, -116.4194), +/// MapLatLng(46.8797, -110.3626), +/// MapLatLng(41.8780, -93.0977), +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// 'assets/usa.json', +/// shapeDataField: 'name', +/// ); +/// +/// _zoomPanBehavior = MapZoomPanBehavior(); +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar(title: Text('Polygon shape')), +/// body: SfMaps(layers: [ /// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "continent", -/// ), +/// source: _mapSource, /// sublayers: [ /// MapPolygonLayer( /// polygons: List.generate( -/// polygons.length, +/// 1, /// (int index) { /// return MapPolygon( -/// points: polygons[index].points, +/// points: _polygon, /// ); /// }, /// ).toSet(), /// ), /// ], +/// zoomPanBehavior: _zoomPanBehavior, /// ), -/// ], -/// ), -/// ); -/// } +/// ]), +/// ); +/// } /// ``` -class MapPolygon { +class MapPolygon extends DiagnosticableTree { /// Creates a [MapPolygon]. const MapPolygon({ - @required this.points, + required this.points, this.color, this.strokeColor, this.strokeWidth, @@ -4495,138 +6077,211 @@ class MapPolygon { /// Lines are drawn between consecutive [points] to form a closed shape. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polygon; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polygon = [ + /// MapLatLng(38.8026, -116.4194), + /// MapLatLng(46.8797, -110.3626), + /// MapLatLng(41.8780, -93.0977), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/usa.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polygon shape')), + /// body: SfMaps(layers: [ + /// MapShapeLayer( + /// source: _mapSource, /// sublayers: [ /// MapPolygonLayer( /// polygons: List.generate( - /// polygons.length, + /// 1, /// (int index) { /// return MapPolygon( - /// points: polygons[index].points, - /// ); - /// }, + /// points: _polygon, + /// ); + /// }, /// ).toSet(), /// ), /// ], + /// zoomPanBehavior: _zoomPanBehavior, /// ), - /// ], - /// ), - /// ); - /// } + /// ]), + /// ); + /// } /// ``` final List points; /// Specifies the fill color of the polygon. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polygon; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polygon = [ + /// MapLatLng(38.8026, -116.4194), + /// MapLatLng(46.8797, -110.3626), + /// MapLatLng(41.8780, -93.0977), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/usa.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polygon shape')), + /// body: SfMaps(layers: [ + /// MapShapeLayer( + /// source: _mapSource, /// sublayers: [ /// MapPolygonLayer( /// polygons: List.generate( - /// polygons.length, + /// 1, /// (int index) { /// return MapPolygon( - /// points: polygons[index].points, + /// points: _polygon, /// color: Colors.blue, - /// ); - /// }, + /// ); + /// }, /// ).toSet(), /// ), /// ], + /// zoomPanBehavior: _zoomPanBehavior, /// ), - /// ], - /// ), - /// ); - /// } + /// ]), + /// ); + /// } /// ``` - final Color color; + final Color? color; /// Specifies the stroke color of the polygon. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polygon; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polygon = [ + /// MapLatLng(38.8026, -116.4194), + /// MapLatLng(46.8797, -110.3626), + /// MapLatLng(41.8780, -93.0977), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/usa.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polygon shape')), + /// body: SfMaps(layers: [ + /// MapShapeLayer( + /// source: _mapSource, /// sublayers: [ /// MapPolygonLayer( /// polygons: List.generate( - /// polygons.length, + /// 1, /// (int index) { /// return MapPolygon( - /// points: polygons[index].points, + /// points: _polygon, /// strokeColor: Colors.red, - /// ); - /// }, + /// strokeWidth: 4.0 + /// ); + /// }, /// ).toSet(), /// ), /// ], + /// zoomPanBehavior: _zoomPanBehavior, /// ), - /// ], - /// ), - /// ); - /// } + /// ]), + /// ); + /// } /// ``` - final Color strokeColor; + final Color? strokeColor; /// Specifies the stroke width of the polygon. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polygon; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polygon = [ + /// MapLatLng(38.8026, -116.4194), + /// MapLatLng(46.8797, -110.3626), + /// MapLatLng(41.8780, -93.0977), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/usa.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polygon shape')), + /// body: SfMaps(layers: [ + /// MapShapeLayer( + /// source: _mapSource, /// sublayers: [ /// MapPolygonLayer( /// polygons: List.generate( - /// polygons.length, + /// 1, /// (int index) { /// return MapPolygon( - /// points: polygons[index].points, + /// points: _polygon, /// strokeWidth: 4, /// strokeColor: Colors.red, - /// ); - /// }, + /// ); + /// }, /// ).toSet(), /// ), /// ], + /// zoomPanBehavior: _zoomPanBehavior, /// ), - /// ], - /// ), - /// ); - /// } + /// ]), + /// ); + /// } /// ``` - final double strokeWidth; + final double? strokeWidth; /// Callback to receive tap event for this polygon. /// @@ -4634,26 +6289,45 @@ class MapPolygon { /// passed on it as shown in the below code snippet. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ + /// MapZoomPanBehavior _zoomPanBehavior; + /// List _polygon; + /// MapShapeSource _mapSource; + /// int _selectedIndex; + /// + /// @override + /// void initState() { + /// _polygon = [ + /// MapLatLng(38.8026, -116.4194), + /// MapLatLng(46.8797, -110.3626), + /// MapLatLng(41.8780, -93.0977), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/usa.json', + /// shapeDataField: 'name', + /// ); + /// + /// _zoomPanBehavior = MapZoomPanBehavior(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Polygon shape')), + /// body: SfMaps(layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// ), - /// sublayers: [ + /// source: _mapSource, + /// sublayers: [ /// MapPolygonLayer( /// polygons: List.generate( - /// polygons.length, + /// 1, /// (int index) { - /// return MapPolygon( - /// points: polygons[index].points, - /// color: _selectedIndex == index - /// ? Colors.blue - /// : Colors.red, + /// return MapPolygon( + /// points: _polygon, + /// color: _selectedIndex == index + /// ? Colors.blue + /// : Colors.red, /// onTap: () { /// setState(() { /// _selectedIndex = index; @@ -4664,50 +6338,93 @@ class MapPolygon { /// ).toSet(), /// ), /// ], + /// zoomPanBehavior: _zoomPanBehavior, /// ), - /// ], - /// ), - /// ); - ///} - final VoidCallback onTap; + /// ]), + /// ); + /// } + final VoidCallback? onTap; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty>('points', points)); + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + if (strokeColor != null) { + properties.add(ColorProperty('strokeColor', strokeColor)); + } + + if (strokeWidth != null) { + properties.add(DoubleProperty('strokeWidth', strokeWidth)); + } + + properties.add(ObjectFlagProperty.has('onTap', onTap)); + } } /// Creates a circle which is drawn based on the given [center] and /// [radius]. /// /// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// body: SfMaps( -/// layers: [ -/// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "continent", +/// List _circles; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// _circles = const [ +/// MapLatLng(-14.235004, -51.92528), +/// MapLatLng(51.16569, 10.451526), +/// MapLatLng(-25.274398, 133.775136), +/// MapLatLng(20.593684, 78.96288), +/// MapLatLng(61.52401, 105.318756) +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// 'assets/world_map.json', +/// shapeDataField: 'name', +/// ); +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: Center( +/// child: Container( +/// height: 350, +/// child: Padding( +/// padding: EdgeInsets.only(left: 15, right: 15), +/// child: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// sublayers: [ +/// MapCircleLayer( +/// circles: List.generate( +/// _circles.length, +/// (int index) { +/// return MapCircle( +/// center: _circles[index], +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], +/// ), +/// ], /// ), -/// sublayers: [ -/// MapCircleLayer( -/// circles: List.generate( -/// circles.length, -/// (int index) { -/// return MapCircle( -/// center: circles[index], -/// ); -/// }, -/// ).toSet(), -/// ), -/// ], /// ), -/// ], -/// ), -/// ); -/// } +/// )), +/// ); +/// } /// ``` -class MapCircle { +class MapCircle extends DiagnosticableTree { /// Creates a [MapCircle]. const MapCircle({ - @required this.center, + required this.center, this.radius = 5, this.color, this.strokeColor, @@ -4718,172 +6435,293 @@ class MapCircle { /// The center of the circle. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _circles; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _circles = const [ + /// MapLatLng(-14.235004, -51.92528), + /// MapLatLng(51.16569, 10.451526), + /// MapLatLng(-25.274398, 133.775136), + /// MapLatLng(20.593684, 78.96288), + /// MapLatLng(61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// _circles.length, + /// (int index) { + /// return MapCircle( + /// center: _circles[index], + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], /// ), - /// sublayers: [ - /// MapCircleLayer( - /// circles: List.generate( - /// circles.length, - /// (int index) { - /// return MapCircle( - /// center: circles[index], - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// )), + /// ); + /// } /// ``` final MapLatLng center; /// The radius of the circle. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _circles; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _circles = const [ + /// MapLatLng(-14.235004, -51.92528), + /// MapLatLng(51.16569, 10.451526), + /// MapLatLng(-25.274398, 133.775136), + /// MapLatLng(20.593684, 78.96288), + /// MapLatLng(61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// _circles.length, + /// (int index) { + /// return MapCircle( + /// center: _circles[index], + /// radius: 20, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], /// ), - /// sublayers: [ - /// MapCircleLayer( - /// circles: List.generate( - /// circles.length, - /// (int index) { - /// return MapCircle( - /// center: circles[index], - /// radius: 20, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// )), + /// ); + /// } /// ``` final double radius; /// The fill color of the circle. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _circles; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _circles = const [ + /// MapLatLng(-14.235004, -51.92528), + /// MapLatLng(51.16569, 10.451526), + /// MapLatLng(-25.274398, 133.775136), + /// MapLatLng(20.593684, 78.96288), + /// MapLatLng(61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// _circles.length, + /// (int index) { + /// return MapCircle( + /// center: _circles[index], + /// color: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], /// ), - /// sublayers: [ - /// MapCircleLayer( - /// circles: List.generate( - /// circles.length, - /// (int index) { - /// return MapCircle( - /// center: circles[index], - /// color: Colors.blue, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// )), + /// ); + /// } /// ``` - final Color color; + final Color? color; /// Stroke width of the circle. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _circles; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _circles = const [ + /// MapLatLng(-14.235004, -51.92528), + /// MapLatLng(51.16569, 10.451526), + /// MapLatLng(-25.274398, 133.775136), + /// MapLatLng(20.593684, 78.96288), + /// MapLatLng(61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// _circles.length, + /// (int index) { + /// return MapCircle( + /// center: _circles[index], + /// strokeWidth: 4, + /// strokeColor: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], /// ), - /// sublayers: [ - /// MapCircleLayer( - /// circles: List.generate( - /// circles.length, - /// (int index) { - /// return MapCircle( - /// center: circles[index], - /// strokeWidth: 4, - /// strokeColor: Colors.blue, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// )), + /// ); + /// } /// ``` - final double strokeWidth; + final double? strokeWidth; /// Stroke color of the circle. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _circles; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _circles = const [ + /// MapLatLng(-14.235004, -51.92528), + /// MapLatLng(51.16569, 10.451526), + /// MapLatLng(-25.274398, 133.775136), + /// MapLatLng(20.593684, 78.96288), + /// MapLatLng(61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// _circles.length, + /// (int index) { + /// return MapCircle( + /// center: _circles[index], + /// strokeWidth: 4, + /// strokeColor: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], /// ), - /// sublayers: [ - /// MapCircleLayer( - /// circles: List.generate( - /// circles.length, - /// (int index) { - /// return MapCircle( - /// center: circles[index], - /// strokeColor: Colors.blue, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// )), + /// ); + /// } /// ``` - final Color strokeColor; + final Color? strokeColor; /// Callback to receive tap event for this circle. /// @@ -4891,82 +6729,156 @@ class MapCircle { /// passed on it as shown in the below code snippet. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _circles; + /// MapShapeSource _mapSource; + /// int _selectedIndex; + /// + /// @override + /// void initState() { + /// _circles = const [ + /// MapLatLng(-14.235004, -51.92528), + /// MapLatLng(51.16569, 10.451526), + /// MapLatLng(-25.274398, 133.775136), + /// MapLatLng(20.593684, 78.96288), + /// MapLatLng(61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapCircleLayer( + /// circles: List.generate( + /// _circles.length, + /// (int index) { + /// return MapCircle( + /// center: _circles[index], + /// color: _selectedIndex == index + /// ? Colors.blue + /// : Colors.red, + /// onTap: () { + /// setState(() { + /// _selectedIndex = index; + /// }); + /// }); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], /// ), - /// sublayers: [ - /// MapCircleLayer( - /// circles: List.generate( - /// circles.length, - /// (int index) { - /// return MapCircle( - /// center: circles[index], - /// color: _selectedIndex == index - /// ? Colors.blue - /// : Colors.red, - /// onTap: () { - /// setState(() { - /// _selectedIndex = index; - /// }); - /// }, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], /// ), - /// ], - /// ), - /// ); - /// } + /// )), + /// ); + /// } /// ``` - final VoidCallback onTap; + final VoidCallback? onTap; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('center', center)); + properties.add(DoubleProperty('radius', radius)); + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + if (strokeColor != null) { + properties.add(ColorProperty('strokeColor', strokeColor)); + } + + if (strokeWidth != null) { + properties.add(DoubleProperty('strokeWidth', strokeWidth)); + } + + properties.add(ObjectFlagProperty.has('onTap', onTap)); + } } /// Creates an arc by connecting the two geographical coordinates. /// /// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return Scaffold( -/// body: SfMaps( -/// layers: [ -/// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "continent", +/// List _arcs; +/// MapZoomPanBehavior _zoomPanBehavior; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// _zoomPanBehavior = MapZoomPanBehavior( +/// focalLatLng: MapLatLng(40.7128, -95.3698), +/// zoomLevel: 3, +/// ); +/// +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "continent", +/// ); +/// +/// _arcs = [ +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), +/// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// zoomPanBehavior: _zoomPanBehavior, +/// sublayers: [ +/// MapArcLayer( +/// arcs: List.generate( +/// _arcs.length, +/// (int index) { +/// return MapArc( +/// from: _arcs[index].from, +/// to: _arcs[index].to, +/// ); +/// }, +/// ).toSet(), +/// ), +/// ], /// ), -/// sublayers: [ -/// MapArcLayer( -/// arcs: List.generate( -/// arcs.length, -/// (int index) { -/// return MapArc( -/// from: arcs[index].from, -/// to: arcs[index].to, -/// ); -/// }, -/// ).toSet(), -/// ), -/// ], -/// ), -/// ], -/// ), -/// ); +/// ], +/// ), +/// ); +/// } +/// +/// class Model { +/// Model(this.from, this.to); +/// +/// MapLatLng from; +/// MapLatLng to; /// } /// ``` -class MapArc { +class MapArc extends DiagnosticableTree { /// Creates a [MapArc]. const MapArc({ - @required this.from, - @required this.to, + required this.from, + required this.to, this.heightFactor = 0.2, this.controlPointFactor = 0.5, this.dashArray = const [0, 0], @@ -4978,33 +6890,63 @@ class MapArc { /// Represents the start coordinate of an arc. /// ///```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'continent', + /// List _arcs; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _arcs = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// _arcs.length, + /// (int index) { + /// return MapArc( + /// from: _arcs[index].from, + /// to: _arcs[index].to, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], /// ), - /// subLayers: [ - /// MapArcLayer( - /// arcs: List.generate( - /// arcs.length, - /// (int index) { - /// return MapArc( - /// from: arcs[index].from, - /// to: arcs[index].to, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` final MapLatLng from; @@ -5012,33 +6954,63 @@ class MapArc { /// Represents the end coordinate of an arc. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'continent', + /// List _arcs; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _arcs = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// _arcs.length, + /// (int index) { + /// return MapArc( + /// from: _arcs[index].from, + /// to: _arcs[index].to, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], /// ), - /// subLayers: [ - /// MapArcLayer( - /// arcs: List.generate( - /// arcs.length, - /// (int index) { - /// return MapArc( - /// from: arcs[index].from, - /// to: arcs[index].to, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` final MapLatLng to; @@ -5054,34 +7026,64 @@ class MapArc { /// To render the arc below the points, set the value between -1 to 0. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'continent', + /// List _arcs; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _arcs = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// _arcs.length, + /// (int index) { + /// return MapArc( + /// from: _arcs[index].from, + /// to: _arcs[index].to, + /// heightFactor: 0.6, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], /// ), - /// subLayers: [ - /// MapArcLayer( - /// arcs: List.generate( - /// arcs.length, - /// (int index) { - /// return MapArc( - /// from: arcs[index].from, - /// to: arcs[index].to, - /// heightFactor: 0.6, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` final double heightFactor; @@ -5096,34 +7098,64 @@ class MapArc { /// The value ranges from 0 to 1. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// 'assets/world_map.json', - /// shapeDataField: 'continent', + /// List _arcs; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _arcs = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// _arcs.length, + /// (int index) { + /// return MapArc( + /// from: _arcs[index].from, + /// to: _arcs[index].to, + /// controlPointFactor: 0.4, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], /// ), - /// subLayers: [ - /// MapArcLayer( - /// arcs: List.generate( - /// arcs.length, - /// (int index) { - /// return MapArc( - /// from: arcs[index].from, - /// to: arcs[index].to, - /// controlPointFactor: 0.4, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` final double controlPointFactor; @@ -5135,34 +7167,64 @@ class MapArc { /// again till the end of the arc. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _arcs; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _arcs = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// _arcs.length, + /// (int index) { + /// return MapArc( + /// from: _arcs[index].from, + /// to: _arcs[index].to, + /// dashArray: [8, 3, 4, 3], + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], /// ), - /// subLayers: [ - /// MapArcLayer( - /// arcs: List.generate( - /// arcs.length, - /// (int index) { - /// return MapArc( - /// from: arcs[index].from, - /// to: arcs[index].to, - /// dashArray: [8, 3, 4, 3], - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` final List dashArray; @@ -5170,73 +7232,132 @@ class MapArc { /// Color of the arc. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _arcs; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _arcs = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// _arcs.length, + /// (int index) { + /// return MapArc( + /// from: _arcs[index].from, + /// to: _arcs[index].to, + /// color: Colors.blue, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], /// ), - /// subLayers: [ - /// MapArcLayer( - /// arcs: List.generate( - /// arcs.length, - /// (int index) { - /// return MapArc( - /// from: arcs[index].from, - /// to: arcs[index].to, - /// color: Colors.blue, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` - final Color color; + final Color? color; /// Width of the arc. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _arcs; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _arcs = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// _arcs.length, + /// (int index) { + /// return MapArc( + /// from: _arcs[index].from, + /// to: _arcs[index].to, + /// width: 4, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], /// ), - /// subLayers: [ - /// MapArcLayer( - /// arcs: List.generate( - /// arcs.length, - /// (int index) { - /// return MapArc( - /// from: arcs[index].from, - /// to: arcs[index].to, - /// width: 4, - /// color: Colors.blue, - /// ); - /// }, - /// ).toSet(), - /// ), - /// ], - /// ), - /// ], - /// ), - /// ); + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` - final double width; + final double? width; /// Callback to receive tap event for this arc. /// @@ -5244,50 +7365,100 @@ class MapArc { /// passed on it as shown in the below code snippet. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: SfMaps( - /// layers: [ - /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", + /// List _arcs; + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// int _selectedIndex; + /// + /// @override + /// void initState() { + /// _zoomPanBehavior = MapZoomPanBehavior( + /// focalLatLng: MapLatLng(40.7128, -95.3698), + /// zoomLevel: 3, + /// ); + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _arcs = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// sublayers: [ + /// MapArcLayer( + /// arcs: List.generate( + /// _arcs.length, + /// (int index) { + /// return MapArc( + /// from: _arcs[index].from, + /// to: _arcs[index].to, + /// color: _selectedIndex == index + /// ? Colors.blue + /// : Colors.red, + /// onTap: () { + /// setState(() { + /// _selectedIndex = index; + /// }); + /// }, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], /// ), - /// subLayers: [ - /// MapArcLayer( - /// arcs: List.generate( - /// arcs.length, - /// (int index) { - /// return MapArc( - /// from: arcs[index].from, - /// to: arcs[index].to, - /// color: _selectedIndex == index - /// ? Colors.blue - /// : Colors.red, - /// onTap: () { - /// setState(() { - /// _selectedIndex = index; - /// }); - /// }, - /// ); - /// }, - /// ).toSet(), - /// ), - // ], - /// ), - /// ], - /// ), - /// ); + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; /// } ///``` - final VoidCallback onTap; + final VoidCallback? onTap; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('from', from)); + properties.add(DiagnosticsProperty('to', to)); + properties.add(DoubleProperty('heightFactor', heightFactor)); + properties.add(DoubleProperty('controlPointFactor', controlPointFactor)); + properties.add((DiagnosticsProperty>('dashArray', dashArray))); + if (color != null) { + properties.add(ColorProperty('color', color)); + } + + if (width != null) { + properties.add(DoubleProperty('width', width)); + } + + properties.add(ObjectFlagProperty.has('onTap', onTap)); + } } // To calculate dash array path for series -Path _dashPath( - Path source, { - @required _IntervalList dashArray, +Path? _dashPath( + Path? source, { + required _IntervalList dashArray, }) { if (source == null) { return null; @@ -5324,7 +7495,7 @@ void _drawDashedLine( _dashPath( path, dashArray: _IntervalList(dashArray), - ), + )!, paint); } else { canvas.drawPath(path, paint); @@ -5434,3 +7605,56 @@ bool _liesPointOnArc(Offset startPoint, Offset endPoint, Offset controlPoint, ..close(); return path.contains(touchPosition); } + +class _DebugVectorShapeTree extends DiagnosticableTree { + _DebugVectorShapeTree(this.vectorShapes); + + final Set vectorShapes; + + @override + List debugDescribeChildren() { + if (vectorShapes.isNotEmpty) { + return vectorShapes.map((Object vectorShape) { + if (vectorShape is MapLine) { + return vectorShape.toDiagnosticsNode(); + } else if (vectorShape is MapArc) { + return vectorShape.toDiagnosticsNode(); + } else if (vectorShape is MapCircle) { + return vectorShape.toDiagnosticsNode(); + } else if (vectorShape is MapPolyline) { + return vectorShape.toDiagnosticsNode(); + } else { + // ignore: avoid_as + final MapPolygon polygonShape = vectorShape as MapPolygon; + return polygonShape.toDiagnosticsNode(); + } + }).toList(); + } + return super.debugDescribeChildren(); + } + + @override + String toStringShort() { + if (vectorShapes is Set) { + return vectorShapes.length > 1 + ? 'contains ${vectorShapes.length} lines' + : 'contains ${vectorShapes.length} line'; + } else if (vectorShapes is Set) { + return vectorShapes.length > 1 + ? 'contains ${vectorShapes.length} circles' + : 'contains ${vectorShapes.length} circle'; + } else if (vectorShapes is Set) { + return vectorShapes.length > 1 + ? 'contains ${vectorShapes.length} arcs' + : 'contains ${vectorShapes.length} arc'; + } else if (vectorShapes is Set) { + return vectorShapes.length > 1 + ? 'contains ${vectorShapes.length} polylines' + : 'contains ${vectorShapes.length} polyline'; + } else { + return vectorShapes.length > 1 + ? 'contains ${vectorShapes.length} polygons' + : 'contains ${vectorShapes.length} polygon'; + } + } +} diff --git a/packages/syncfusion_flutter_maps/lib/src/settings.dart b/packages/syncfusion_flutter_maps/lib/src/settings.dart index 4704ff9ba..16b9ad701 100644 --- a/packages/syncfusion_flutter_maps/lib/src/settings.dart +++ b/packages/syncfusion_flutter_maps/lib/src/settings.dart @@ -17,7 +17,7 @@ typedef IndexedStringValueMapper = String Function(int index); /// Signature to return the double values from the data source /// based on the index. -typedef IndexedDoubleValueMapper = double Function(int index); +typedef IndexedDoubleValueMapper = double? Function(int index); /// Signature to return the colors or other types from the data source based on /// the index based on which colors will be applied. @@ -65,17 +65,34 @@ typedef WillPanCallback = bool Function(MapPanDetails); /// based on the [MapColorMapper.value] property of [MapColorMapper]. /// /// ```dart -/// List data; +/// List _data; +/// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// -/// data = [ +/// _data = [ /// Model('India', 280, "Low"), /// Model('United States of America', 190, "High"), /// Model('Pakistan', 37, "Low"), /// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: _data.length, +/// primaryValueMapper: (int index) { +/// return _data[index].country; +/// }, +/// shapeColorValueMapper: (int index) { +/// return _data[index].storage; +/// }, +/// shapeColorMappers: [ +/// MapColorMapper(value: "Low", color: Colors.red), +/// MapColorMapper(value: "High", color: Colors.green) +/// ], +/// ); /// } /// /// @override @@ -83,41 +100,53 @@ typedef WillPanCallback = bool Function(MapPanDetails); /// return SfMaps( /// layers: [ /// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "name", -/// dataCount: data.length, -/// primaryValueMapper: (index) { -/// return data[index].country; -/// }, -/// shapeColorValueMapper: (index) { -/// return data[index].storage; -/// }, -/// shapeColorMappers: [ -/// MapColorMapper(value: "Low", color: Colors.red), -/// MapColorMapper(value: "High", color: Colors.green) -/// ]), +/// source: _mapSource, /// ) /// ], /// ); /// } +/// +/// class Model { +/// const Model(this.country, this.count, this.storage); +/// +/// final String country; +/// final double count; +/// final String storage; +/// } /// ``` /// The below code snippet represents how color can be applied to the shape /// based on the range between [MapColorMapper.from] and [MapColorMapper.to] /// properties of [MapColorMapper]. /// /// ```dart -/// List data; +/// List _data; +/// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// -/// data = [ +/// _data = [ /// Model('India', 100, "Low"), /// Model('United States of America', 200, "High"), /// Model('Pakistan', 75, "Low"), /// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: _data.length, +/// primaryValueMapper: (int index) { +/// return _data[index].country; +/// }, +/// shapeColorValueMapper: (int index) { +/// return _data[index].count; +/// }, +/// shapeColorMappers: [ +/// MapColorMapper(from: 0, to: 100, color: Colors.red), +/// MapColorMapper(from: 101, to: 200, color: Colors.yellow) +/// ] +/// ); /// } /// /// @override @@ -125,24 +154,19 @@ typedef WillPanCallback = bool Function(MapPanDetails); /// return SfMaps( /// layers: [ /// MapShapeLayer( -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "name", -/// dataCount: data.length, -/// primaryValueMapper: (index) { -/// return data[index].country; -/// }, -/// shapeColorValueMapper: (index) { -/// return data[index].count; -/// }, -/// shapeColorMappers: [ -/// MapColorMapper(from: 0, to: 100, color: Colors.red), -/// MapColorMapper(from: 101, to: 200, color: Colors.yellow) -/// ]), +/// source: _mapSource, /// ) /// ], /// ); /// } +/// +/// class Model { +/// const Model(this.country, this.count, this.storage); +/// +/// final String country; +/// final double count; +/// final String storage; +/// } /// ``` /// /// See also: @@ -158,7 +182,7 @@ class MapColorMapper { this.from, this.to, this.value, - this.color, + required this.color, this.minOpacity, this.maxOpacity, this.text, @@ -175,17 +199,34 @@ class MapColorMapper { /// and [to] range. /// /// ```dart - /// List data; + /// List _data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// - /// data = [ + /// _data = [ /// Model('India', 100, "Low"), /// Model('United States of America', 200, "High"), /// Model('Pakistan', 75, "Low"), /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].count; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(from: 0, to: 100, color: Colors.red), + /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) + /// ] + /// ); /// } /// /// @override @@ -193,24 +234,19 @@ class MapColorMapper { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].count; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.red), - /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) - /// ]), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -220,7 +256,7 @@ class MapColorMapper { /// based on the specific value. /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors /// based on the specific value. - final double from; + final double? from; /// Sets the range end for the color mapping. /// @@ -230,17 +266,34 @@ class MapColorMapper { /// and [to] range. /// /// ```dart - /// List data; + /// List _data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// - /// data = [ + /// _data = [ /// Model('India', 100, "Low"), /// Model('United States of America', 200, "High"), /// Model('Pakistan', 75, "Low"), /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].count; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(from: 0, to: 100, color: Colors.red), + /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) + /// ] + /// ); /// } /// /// @override @@ -248,24 +301,19 @@ class MapColorMapper { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].count; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.red), - /// MapColorMapper(from: 101, to: 200, color: Colors.yellow) - /// ]), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -275,7 +323,7 @@ class MapColorMapper { /// on the specific value. /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors /// based on the specific value. - final double to; + final double? to; /// Sets the value for the equal color mapping. /// @@ -284,17 +332,34 @@ class MapColorMapper { /// [MapShapeSource.bubbleColorValueMapper] is equal to this [value]. /// /// ```dart - /// List data; + /// List _data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// - /// data = [ + /// _data = [ /// Model('India', 280, "Low"), /// Model('United States of America', 190, "High"), /// Model('Pakistan', 37, "Low"), /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) + /// ], + /// ); /// } /// /// @override @@ -302,24 +367,19 @@ class MapColorMapper { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].storage; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(value: "Low", color: Colors.red), - /// MapColorMapper(value: "High", color: Colors.green) - /// ]), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -328,22 +388,39 @@ class MapColorMapper { /// based on the specific value. /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors /// based on the specific value. - final String value; + final String? value; /// Specifies the color applies to the shape or bubble based on the value. /// /// ```dart - /// List data; + /// List _data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// - /// data = [ + /// _data = [ /// Model('India', 280, "Low"), /// Model('United States of America', 190, "High"), /// Model('Pakistan', 37, "Low"), /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red), + /// MapColorMapper(value: "High", color: Colors.green) + /// ], + /// ); /// } /// /// @override @@ -351,24 +428,19 @@ class MapColorMapper { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].storage; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(value: "Low", color: Colors.red), - /// MapColorMapper(value: "High", color: Colors.green) - /// ]), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -390,17 +462,42 @@ class MapColorMapper { /// between the range will get a opacity based on their respective value. /// /// ```dart - /// List data; + /// List _data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// - /// data = [ - /// Model('India', 100, "Low"), - /// Model('United States of America', 200, "High"), - /// Model('Pakistan', 75, "Low"), + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper( + /// value: "Low", + /// color: Colors.red, + /// minOpacity: 0.3, + /// maxOpacity: 0.7), + /// MapColorMapper( + /// value: "High", + /// color: Colors.green, + /// minOpacity: 0.5, + /// maxOpacity: 0.9) + /// ], + /// ); /// } /// /// @override @@ -408,26 +505,19 @@ class MapColorMapper { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].count; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.yellow, - /// maxOpacity: 0.2, minOpacity: 0.5), - /// MapColorMapper(from: 101, to: 200, color: Colors.red, - /// maxOpacity: 0.6, minOpacity: 1) - /// ]), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -435,7 +525,7 @@ class MapColorMapper { /// based on the specific value. /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors /// based on the specific value. - final double minOpacity; + final double? minOpacity; /// Specifies the maximum opacity applies to the shape or bubble while using /// [from] and [to]. @@ -446,17 +536,42 @@ class MapColorMapper { /// between the range will get a opacity based on their respective value. /// /// ```dart - /// List data; + /// List _data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// - /// data = [ - /// Model('India', 100, "Low"), - /// Model('United States of America', 200, "High"), - /// Model('Pakistan', 75, "Low"), + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper( + /// value: "Low", + /// color: Colors.red, + /// minOpacity: 0.3, + /// maxOpacity: 0.7), + /// MapColorMapper( + /// value: "High", + /// color: Colors.green, + /// minOpacity: 0.5, + /// maxOpacity: 0.9) + /// ], + /// ); /// } /// /// @override @@ -464,26 +579,19 @@ class MapColorMapper { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].count; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.yellow, - /// maxOpacity: 0.2, minOpacity: 0.5), - /// MapColorMapper(from: 101, to: 200, color: Colors.red, - /// maxOpacity: 0.6, minOpacity: 1) - /// ]), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -491,7 +599,7 @@ class MapColorMapper { /// on the specific value. /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors /// based on the specific value. - final double maxOpacity; + final double? maxOpacity; /// Specifies the text to be used for the legend item. /// @@ -499,17 +607,34 @@ class MapColorMapper { /// [MapColorMapper.value] will be used as the text of the legend item. /// /// ```dart - /// List data; + /// List _data; + /// MapShapeSource _mapSource; /// /// @override /// void initState() { /// super.initState(); /// - /// data = [ - /// Model('India', 100, "Low"), - /// Model('United States of America', 200, "High"), - /// Model('Pakistan', 75, "Low"), + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// shapeColorValueMapper: (int index) { + /// return _data[index].storage; + /// }, + /// shapeColorMappers: [ + /// MapColorMapper(value: "Low", color: Colors.red, text: 'Low+'), + /// MapColorMapper(value: "High", color: Colors.green, text: 'High+') + /// ], + /// ); /// } /// /// @override @@ -517,26 +642,19 @@ class MapColorMapper { /// return SfMaps( /// layers: [ /// MapShapeLayer( - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: data.length, - /// primaryValueMapper: (index) { - /// return data[index].country; - /// }, - /// shapeColorValueMapper: (index) { - /// return data[index].count; - /// }, - /// shapeColorMappers: [ - /// MapColorMapper(from: 0, to: 100, color: Colors.yellow, - /// maxOpacity: 0.2, minOpacity: 0.5, text: "low"), - /// MapColorMapper(from: 101, to: 200, color: Colors.red, - /// maxOpacity: 0.6, minOpacity: 1, text: "high") - /// ]), + /// source: _mapSource, /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: @@ -545,7 +663,7 @@ class MapColorMapper { /// * [MapShapeSource.bubbleColorMappers], to set the bubble colors /// based on the specific value. - final String text; + final String? text; @override bool operator ==(Object other) { @@ -577,28 +695,55 @@ class MapColorMapper { /// data labels when it exceeds their respective shapes. /// /// ```dart +/// List _data; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// super.initState(); +/// +/// _data = [ +/// Model('India', 280, "Low"), +/// Model('United States of America', 190, "High"), +/// Model('Pakistan', 37, "Low"), +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: _data.length, +/// primaryValueMapper: (int index) { +/// return _data[index].country; +/// }, +/// dataLabelMapper: (int index) { +/// return _data[index].country; +/// }, +/// ); +/// } +/// /// @override /// Widget build(BuildContext context) { -/// return -/// SfMaps( -/// layers: [ -/// MapShapeLayer( -/// dataLabelSettings: +/// return SfMaps( +/// layers: [ +/// MapShapeLayer( +/// showDataLabels: true, +/// source: _mapSource, +/// dataLabelSettings: /// MapDataLabelSettings( /// textStyle: TextStyle(color: Colors.red) /// ), -/// source: MapShapeSource.asset( -/// showDataLabels: true, -/// "assets/world_map.json", -/// shapeDataField: "continent", -/// dataCount: bubbleData.length, -/// primaryValueMapper: (index) { -/// return bubbleData[index].country; -/// }), -/// ) -/// ], +/// ) +/// ], /// ); /// } +/// +/// class Model { +/// const Model(this.country, this.count, this.storage); +/// +/// final String country; +/// final double count; +/// final String storage; +/// } /// ``` @immutable class MapDataLabelSettings extends DiagnosticableTree { @@ -613,30 +758,57 @@ class MapDataLabelSettings extends DiagnosticableTree { /// This snippet shows how to set [textStyle] for the data labels in [SfMaps]. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// dataLabelMapper: (int index) { + /// return _data[index].country; + /// }, + /// ); + /// } + /// /// @override /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// dataLabelSettings: + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// showDataLabels: true, + /// source: _mapSource, + /// dataLabelSettings: /// MapDataLabelSettings( /// textStyle: TextStyle(color: Colors.red) /// ), - /// source: MapShapeSource.asset( - /// showDataLabels: true, - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], + /// ) + /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` - final TextStyle textStyle; + final TextStyle? textStyle; /// Trims or removes the data label when it is overflowed from the shape. /// @@ -650,28 +822,55 @@ class MapDataLabelSettings extends DiagnosticableTree { /// [SfMaps]. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// dataLabelMapper: (int index) { + /// return _data[index].country; + /// }, + /// ); + /// } + /// /// @override /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// dataLabelSettings: + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// showDataLabels: true, + /// source: _mapSource, + /// dataLabelSettings: /// MapDataLabelSettings( /// overflowMode: MapLabelOverflow.hide /// ), - /// source: MapShapeSource.asset( - /// showDataLabels: true, - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], + /// ) + /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` final MapLabelOverflow overflowMode; @@ -697,7 +896,7 @@ class MapDataLabelSettings extends DiagnosticableTree { super.debugFillProperties(properties); if (textStyle != null) { - properties.add(textStyle.toDiagnosticsNode(name: 'textStyle')); + properties.add(textStyle!.toDiagnosticsNode(name: 'textStyle')); } properties .add(EnumProperty('overflowMode', overflowMode)); @@ -710,27 +909,51 @@ class MapDataLabelSettings extends DiagnosticableTree { /// bubbles. /// /// ```dart +/// List _data; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// super.initState(); +/// +/// _data = [ +/// Model('India', 280, "Low"), +/// Model('United States of America', 190, "High"), +/// Model('Pakistan', 37, "Low"), +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: _data.length, +/// primaryValueMapper: (int index) { +/// return _data[index].country; +/// }, +/// bubbleSizeMapper: (int index) { +/// return _data[index].count; +/// }, +/// ); +/// } +/// /// @override /// Widget build(BuildContext context) { -/// return -/// SfMaps( -/// layers: [ -/// MapShapeLayer( -/// bubbleSettings: MapBubbleSettings(maxRadius: 10, minRadius: 2), -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "name", -/// dataCount: bubbleData.length, -/// primaryValueMapper: (index) { -/// return bubbleData[index].country; -/// }, -/// bubbleSizeMapper: (index) { -/// return bubbleData[index].usersCount; -/// }), +/// return SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// bubbleSettings: MapBubbleSettings(maxRadius: 10, minRadius: 2), /// ) /// ], /// ); /// } +/// +/// class Model { +/// const Model(this.country, this.count, this.storage); +/// +/// final String country; +/// final double count; +/// final String storage; +/// } /// ``` @immutable class MapBubbleSettings extends DiagnosticableTree { @@ -751,27 +974,51 @@ class MapBubbleSettings extends DiagnosticableTree { /// [maxRadius]. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// bubbleSizeMapper: (int index) { + /// return _data[index].count; + /// }, + /// ); + /// } + /// /// @override /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// bubbleSettings: MapBubbleSettings(maxRadius: 10, minRadius: 2), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// }), + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// bubbleSettings: MapBubbleSettings(maxRadius: 10, minRadius: 2), /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` final double minRadius; @@ -783,126 +1030,223 @@ class MapBubbleSettings extends DiagnosticableTree { /// [maxRadius]. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// bubbleSizeMapper: (int index) { + /// return _data[index].count; + /// }, + /// ); + /// } + /// /// @override /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// bubbleSettings: MapBubbleSettings(maxRadius: 10, minRadius: 2), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// }), + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// bubbleSettings: MapBubbleSettings(maxRadius: 10, minRadius: 2), /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` final double maxRadius; /// Default color of the bubbles. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// bubbleSizeMapper: (int index) { + /// return _data[index].count; + /// }, + /// ); + /// } + /// /// @override /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( - /// bubbleSettings: MapBubbleSettings( - /// color: Colors.black), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// }), + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// bubbleSettings: MapBubbleSettings(color: Colors.red), /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: /// * [MapShapeSource.bubbleColorMappers] and /// [MapShapeSource.bubbleColorValueMapper], to customize the bubble /// colors based on the data. - final Color color; + final Color? color; /// Stroke width of the bubbles. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// bubbleSizeMapper: (int index) { + /// return _data[index].count; + /// }, + /// ); + /// } + /// /// @override /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, /// bubbleSettings: MapBubbleSettings( - /// strokeColor: Colors.red, - /// strokeWidth: 2), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// }), + /// strokeWidth: 2.0, + /// strokeColor: Colors.red, + /// ), /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: /// * [strokeColor], to set the stroke color. - final double strokeWidth; + final double? strokeWidth; /// Stroke color of the bubbles. /// /// ```dart + /// List _data; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// Model('India', 280, "Low"), + /// Model('United States of America', 190, "High"), + /// Model('Pakistan', 37, "Low"), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) { + /// return _data[index].country; + /// }, + /// bubbleSizeMapper: (int index) { + /// return _data[index].count; + /// }, + /// ); + /// } + /// /// @override /// Widget build(BuildContext context) { - /// return - /// SfMaps( - /// layers: [ - /// MapShapeLayer( + /// return SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, /// bubbleSettings: MapBubbleSettings( - /// strokeColor: Colors.red, - /// strokeWidth: 2), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }, - /// bubbleSizeMapper: (index) { - /// return bubbleData[index].usersCount; - /// }), + /// strokeWidth: 2.0, + /// strokeColor: Colors.red, + /// ), /// ) /// ], /// ); /// } + /// + /// class Model { + /// const Model(this.country, this.count, this.storage); + /// + /// final String country; + /// final double count; + /// final String storage; + /// } /// ``` /// /// See also: /// * [strokeWidth], to set the stroke width. - final Color strokeColor; + final Color? strokeColor; @override bool operator ==(Object other) { @@ -928,11 +1272,11 @@ class MapBubbleSettings extends DiagnosticableTree { /// Creates a copy of this class but with the given fields /// replaced with the new values. MapBubbleSettings copyWith({ - double minRadius, - double maxRadius, - Color color, - double strokeWidth, - Color strokeColor, + double? minRadius, + double? maxRadius, + Color? color, + double? strokeWidth, + Color? strokeColor, }) { return MapBubbleSettings( minRadius: minRadius ?? this.minRadius, @@ -950,9 +1294,7 @@ class MapBubbleSettings extends DiagnosticableTree { properties.add(ColorProperty('color', color)); } - if (strokeWidth != null) { - properties.add(DoubleProperty('strokeWidth', strokeWidth)); - } + properties.add(DoubleProperty('strokeWidth', strokeWidth)); if (strokeColor != null) { properties.add(ColorProperty('strokeColor', strokeColor)); @@ -966,34 +1308,75 @@ class MapBubbleSettings extends DiagnosticableTree { /// Customizes the appearance of the selected shape. /// /// ```dart -/// int _selectedIndex = -1; +/// List _data; +/// MapShapeSource _mapSource; +/// int _selectedIndex = -1; /// -/// @override -/// Widget build(BuildContext context) { -/// return SfMaps( -/// layers: [ -/// MapShapeLayer( -/// selectedIndex: _selectedIndex, -/// onSelectionChanged: (int index) { -/// setState(() { -/// // Passing -1 to the unselect the previously selected shape. -/// _selectedIndex = (_selectedIndex == index) ? -1 : index; -/// }); -/// }, -/// selectionSettings: MapSelectionSettings( -/// color: Colors.black -/// ), -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "name", -/// dataCount: bubbleData.length, -/// primaryValueMapper: (index) { -/// return bubbleData[index].country; -/// }), -/// ) -/// ], -/// ); -/// } +/// @override +/// void initState() { +/// super.initState(); +/// +/// _data = [ +/// DataModel('India', 280, "Low", Colors.red), +/// DataModel('United States of America', 190, "High", Colors.green), +/// DataModel('Pakistan', 37, "Low", Colors.yellow), +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: _data.length, +/// primaryValueMapper: (int index) => _data[index].country, +/// shapeColorValueMapper: (int index) => _data[index].color, +/// ); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: Center( +/// child: Container( +/// height: 350, +/// child: Padding( +/// padding: EdgeInsets.only(left: 15, right: 15), +/// child: Column( +/// children: [ +/// SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// selectedIndex: _selectedIndex, +/// selectionSettings: MapSelectionSettings( +/// color: Colors.black), +/// onSelectionChanged: (int index) { +/// setState(() { +/// _selectedIndex = (_selectedIndex == index) ? +/// -1 : index; +/// }); +/// }, +/// ), +/// ], +/// ), +/// ], +/// ), +/// ), +/// )), +/// ); +/// } +/// } +/// +/// class DataModel { +/// const DataModel( +/// this.country, +/// this.usersCount, +/// this.storage, +/// this.color, +/// ); +/// final String country; +/// final double usersCount; +/// final String storage; +/// final Color color; +/// } /// ``` @immutable class MapSelectionSettings extends DiagnosticableTree { @@ -1005,112 +1388,239 @@ class MapSelectionSettings extends DiagnosticableTree { /// This snippet shows how to set selection color in [SfMaps]. /// /// ```dart - /// int _selectedIndex = -1; - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// selectedIndex: _selectedIndex, - /// onSelectionChanged: (int index) { - /// setState(() { - /// // Passing -1 to the unselect the previously selected shape. - /// _selectedIndex = (_selectedIndex == index) ? -1 : index; - /// }); - /// }, - /// selectionSettings: MapSelectionSettings( - /// color: Colors.black - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } + /// List _data; + /// MapShapeSource _mapSource; + /// int _selectedIndex = -1; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low", Colors.red), + /// DataModel('United States of America', 190, "High", Colors.green), + /// DataModel('Pakistan', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) => _data[index].country, + /// shapeColorValueMapper: (int index) => _data[index].color, + /// ); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: Column( + /// children: [ + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// selectedIndex: _selectedIndex, + /// selectionSettings: MapSelectionSettings( + /// color: Colors.black), + /// onSelectionChanged: (int index) { + /// setState(() { + /// _selectedIndex = (_selectedIndex == index) ? + /// -1 : index; + /// }); + /// }, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ), + /// )), + /// ); + /// } + /// } + /// + /// class DataModel { + /// const DataModel( + /// this.country, + /// this.usersCount, + /// this.storage, + /// this.color, + /// ); + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } /// ``` /// See also: /// * [strokeColor], to set stroke color for selected shape. - final Color color; + final Color? color; /// Applies stroke color for the selected shape. /// /// This snippet shows how to set stroke color for the selected shape. /// /// ```dart - /// int _selectedIndex = -1; - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// selectedIndex: _selectedIndex, - /// onSelectionChanged: (int index) { - /// setState(() { - /// // Passing -1 to the unselect the previously selected shape. - /// _selectedIndex = (_selectedIndex == index) ? -1 : index; - /// }); - /// }, - /// selectionSettings: MapSelectionSettings( - /// strokeColor: Colors.white - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } + /// List _data; + /// MapShapeSource _mapSource; + /// int _selectedIndex = -1; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low", Colors.red), + /// DataModel('United States of America', 190, "High", Colors.green), + /// DataModel('Pakistan', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) => _data[index].country, + /// shapeColorValueMapper: (int index) => _data[index].color, + /// ); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: Column( + /// children: [ + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// selectedIndex: _selectedIndex, + /// selectionSettings: MapSelectionSettings( + /// strokeColor: Colors.white, + /// strokeWidth: 2.0, + /// ), + /// onSelectionChanged: (int index) { + /// setState(() { + /// _selectedIndex = (_selectedIndex == index) ? + /// -1 : index; + /// }); + /// }, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ), + /// )), + /// ); + /// } + /// } + /// + /// class DataModel { + /// const DataModel( + /// this.country, + /// this.usersCount, + /// this.storage, + /// this.color, + /// ); + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } /// ``` /// See also: /// * [Color], to set selected shape color. - final Color strokeColor; + final Color? strokeColor; /// Stroke width which applies to the selected shape. /// /// This snippet shows how to set stroke width for the selected shape. /// /// ```dart - /// int _selectedIndex = -1; - /// - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// selectedIndex: _selectedIndex, - /// onSelectionChanged: (int index) { - /// setState(() { - /// // Passing -1 to the unselect the previously selected shape. - /// _selectedIndex = (_selectedIndex == index) ? -1 : index; - /// }); - /// }, - /// selectionSettings: MapSelectionSettings( - /// strokeWidth: 2 - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "name", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], - /// ); - /// } + /// List _data; + /// MapShapeSource _mapSource; + /// int _selectedIndex = -1; + /// + /// @override + /// void initState() { + /// super.initState(); + /// + /// _data = [ + /// DataModel('India', 280, "Low", Colors.red), + /// DataModel('United States of America', 190, "High", Colors.green), + /// DataModel('Pakistan', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) => _data[index].country, + /// shapeColorValueMapper: (int index) => _data[index].color, + /// ); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: Column( + /// children: [ + /// SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// selectedIndex: _selectedIndex, + /// selectionSettings: MapSelectionSettings( + /// strokeColor: Colors.white, + /// strokeWidth: 2.0, + /// ), + /// onSelectionChanged: (int index) { + /// setState(() { + /// _selectedIndex = (_selectedIndex == index) ? + /// -1 : index; + /// }); + /// }, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ), + /// )), + /// ); + /// } + /// } + /// + /// class DataModel { + /// const DataModel( + /// this.country, + /// this.usersCount, + /// this.storage, + /// this.color, + /// ); + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } /// ``` - final double strokeWidth; + final double? strokeWidth; @override bool operator ==(Object other) { @@ -1150,25 +1660,67 @@ class MapSelectionSettings extends DiagnosticableTree { /// Customizes the appearance of the bubble's or shape's tooltip. /// /// ```dart -/// @override -/// Widget build(BuildContext context) { -/// return SfMaps( -/// layers: [ -/// MapShapeLayer( -/// tooltipSettings: MapTooltipSettings( -/// color: Colors.black -/// ), -/// source: MapShapeSource.asset( -/// "assets/world_map.json", -/// shapeDataField: "continent", -/// dataCount: bubbleData.length, -/// primaryValueMapper: (index) { -/// return bubbleData[index].country; -/// }), -/// ) -/// ], +/// MapShapeSource _mapSource; +/// List _data; +/// +/// @override +/// void initState() { +/// +/// _data = [ +/// Model('India', 280, "Low", Colors.red), +/// Model('United States of America', 190, "High", Colors.green), +/// Model('Pakistan', 37, "Low", Colors.yellow), +/// ]; +/// +/// _mapSource = MapShapeSource.asset( +/// "assets/world_map.json", +/// shapeDataField: "name", +/// dataCount: _data.length, +/// primaryValueMapper: (int index) => _data[index].country, +/// shapeColorValueMapper: (int index) => _data[index].color /// ); -/// } +/// +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: Padding( +/// padding: EdgeInsets.all(15), +/// child: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// tooltipSettings: MapTooltipSettings(color: Colors.red), +/// shapeTooltipBuilder: (BuildContext context, int index) { +/// if(index == 0) { +/// return Container( +/// child: Icon(Icons.airplanemode_inactive), +/// ); +/// } +/// else +/// { +/// return Container( +/// child: Icon(Icons.airplanemode_active), +/// ); +/// } +/// }, +/// ), +/// ], +/// ), +/// ), +/// ); +/// } +/// +/// class Model { +/// const Model(this.country, this.usersCount, this.storage, this.color); +/// +/// final String country; +/// final double usersCount; +/// final String storage; +/// final Color color; +/// } /// ``` /// /// See also: @@ -1188,88 +1740,220 @@ class MapTooltipSettings extends DiagnosticableTree { /// This snippet shows how to set the tooltip color in [SfMaps]. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// tooltipSettings: MapTooltipSettings( - /// color: Colors.black - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], + /// MapShapeSource _mapSource; + /// List _data; + /// + /// @override + /// void initState() { + /// + /// _data = [ + /// Model('India', 280, "Low", Colors.red), + /// Model('United States of America', 190, "High", Colors.green), + /// Model('Pakistan', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) => _data[index].country, + /// shapeColorValueMapper: (int index) => _data[index].color /// ); - /// } + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Padding( + /// padding: EdgeInsets.all(15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// tooltipSettings: MapTooltipSettings(color: Colors.red), + /// shapeTooltipBuilder: (BuildContext context, int index) { + /// if(index == 0) { + /// return Container( + /// child: Icon(Icons.airplanemode_inactive), + /// ); + /// } + /// else + /// { + /// return Container( + /// child: Icon(Icons.airplanemode_active), + /// ); + /// } + /// }, + /// ), + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class Model { + /// const Model(this.country, this.usersCount, this.storage, this.color); + /// + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } /// ``` /// See also: /// * [textStyle], for customizing the style of the tooltip text. - final Color color; + final Color? color; /// Specifies the stroke width applies to the tooltip. /// /// This snippet shows how to customize the stroke width in [SfMaps]. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// tooltipSettings: MapTooltipSettings( - /// strokeWidth: 2 - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], + /// MapShapeSource _mapSource; + /// List _data; + /// + /// @override + /// void initState() { + /// + /// _data = [ + /// Model('India', 280, "Low", Colors.red), + /// Model('United States of America', 190, "High", Colors.green), + /// Model('Pakistan', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) => _data[index].country, + /// shapeColorValueMapper: (int index) => _data[index].color /// ); - /// } + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Padding( + /// padding: EdgeInsets.all(15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// tooltipSettings: MapTooltipSettings( + /// strokeWidth: 2.0, + /// strokeColor: Colors.black, + /// ), + /// shapeTooltipBuilder: (BuildContext context, int index) { + /// if(index == 0) { + /// return Container( + /// child: Icon(Icons.airplanemode_inactive), + /// ); + /// } + /// else + /// { + /// return Container( + /// child: Icon(Icons.airplanemode_active), + /// ); + /// } + /// }, + /// ), + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class Model { + /// const Model(this.country, this.usersCount, this.storage, this.color); + /// + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } /// ``` /// See also: /// * [strokeColor], for customizing the stroke color of the tooltip. - final double strokeWidth; + final double? strokeWidth; /// Specifies the stroke color applies to the tooltip. /// /// This snippet shows how to customize stroke color in [SfMaps]. /// /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfMaps( - /// layers: [ - /// MapShapeLayer( - /// tooltipSettings: MapTooltipSettings( - /// strokeColor: Colors.white - /// ), - /// source: MapShapeSource.asset( - /// "assets/world_map.json", - /// shapeDataField: "continent", - /// dataCount: bubbleData.length, - /// primaryValueMapper: (index) { - /// return bubbleData[index].country; - /// }), - /// ) - /// ], + /// MapShapeSource _mapSource; + /// List _data; + /// + /// @override + /// void initState() { + /// + /// _data = [ + /// Model('India', 280, "Low", Colors.red), + /// Model('United States of America', 190, "High", Colors.green), + /// Model('Pakistan', 37, "Low", Colors.yellow), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "name", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) => _data[index].country, + /// shapeColorValueMapper: (int index) => _data[index].color /// ); - /// } + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Padding( + /// padding: EdgeInsets.all(15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// tooltipSettings: MapTooltipSettings( + /// strokeWidth: 2.0, + /// strokeColor: Colors.black, + /// ), + /// shapeTooltipBuilder: (BuildContext context, int index) { + /// if(index == 0) { + /// return Container( + /// child: Icon(Icons.airplanemode_inactive), + /// ); + /// } + /// else + /// { + /// return Container( + /// child: Icon(Icons.airplanemode_active), + /// ); + /// } + /// }, + /// ), + /// ], + /// ), + /// ), + /// ); + /// } + /// + /// class Model { + /// const Model(this.country, this.usersCount, this.storage, this.color); + /// + /// final String country; + /// final double usersCount; + /// final String storage; + /// final Color color; + /// } /// ``` /// /// See also: /// * [strokeWidth] for customizing the stroke width of the tooltip. - final Color strokeColor; + final Color? strokeColor; @override bool operator ==(Object other) { @@ -1296,9 +1980,7 @@ class MapTooltipSettings extends DiagnosticableTree { properties.add(ColorProperty('color', color)); } - if (strokeWidth != null) { - properties.add(DoubleProperty('strokeWidth', strokeWidth)); - } + properties.add(DoubleProperty('strokeWidth', strokeWidth)); if (strokeColor != null) { properties.add(ColorProperty('strokeColor', strokeColor)); @@ -1308,6 +1990,41 @@ class MapTooltipSettings extends DiagnosticableTree { /// Provides options for customizing the appearance of the toolbar in the web /// platform. +/// +/// ```dart +/// MapZoomPanBehavior _zoomPanBehavior; +/// MapShapeSource _mapSource; +/// +/// @override +/// void initState() { +/// _mapSource = MapShapeSource.asset( +/// 'assets/world_map.json', +/// shapeDataField: 'continent', +/// ); +/// _zoomPanBehavior = MapZoomPanBehavior() +/// ..zoomLevel = 4 +/// ..focalLatLng = MapLatLng(19.0759837, 72.8776559) +/// ..toolbarSettings = MapToolbarSettings(); +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar( +/// title: Text('Zoom pan'), +/// ), +/// body: SfMaps( +/// layers: [ +/// MapShapeLayer( +/// source: _mapSource, +/// zoomPanBehavior: _zoomPanBehavior, +/// ), +/// ], +/// ), +/// ); +/// } +/// ``` class MapToolbarSettings extends DiagnosticableTree { /// Creates a [MapToolbarSettings]. const MapToolbarSettings({ @@ -1319,22 +2036,201 @@ class MapToolbarSettings extends DiagnosticableTree { }); /// Specifies the color applies to the tooltip icons. - final Color iconColor; + /// + ///```dart + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ); + /// _zoomPanBehavior = MapZoomPanBehavior() + /// ..zoomLevel = 4 + /// ..focalLatLng = MapLatLng(19.0759837, 72.8776559) + /// ..toolbarSettings = MapToolbarSettings(iconColor: Colors.blue); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Zoom pan'), + /// ), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Color? iconColor; /// Specifies the color applies to the tooltip icon's background. - final Color itemBackgroundColor; + /// + ///```dart + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ); + /// _zoomPanBehavior = MapZoomPanBehavior() + /// ..zoomLevel = 4 + /// ..focalLatLng = MapLatLng(19.0759837, 72.8776559) + /// ..toolbarSettings = MapToolbarSettings( + /// itemBackgroundColor: Colors.blue); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Zoom pan'), + /// ), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Color? itemBackgroundColor; /// Specifies the color applies to the tooltip icon's background on hovering. - final Color itemHoverColor; + /// + ///```dart + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ); + /// _zoomPanBehavior = MapZoomPanBehavior() + /// ..zoomLevel = 4 + /// ..focalLatLng = MapLatLng(19.0759837, 72.8776559) + /// ..toolbarSettings = MapToolbarSettings( + /// itemHoverColor: Colors.red); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Zoom pan'), + /// ), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final Color? itemHoverColor; /// Arranges the toolbar items in either horizontal or vertical direction. /// /// Defaults to [Axis.horizontal]. + /// + ///```dart + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ); + /// _zoomPanBehavior = MapZoomPanBehavior() + /// ..zoomLevel = 4 + /// ..focalLatLng = MapLatLng(19.0759837, 72.8776559) + /// ..toolbarSettings = MapToolbarSettings( + /// direction: Axis.vertical); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Zoom pan'), + /// ), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` final Axis direction; /// Option to position the toolbar in all the corners of the maps. /// /// Defaults to [MapToolbarPosition.topRight]. + /// + ///```dart + /// MapZoomPanBehavior _zoomPanBehavior; + /// MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'continent', + /// ); + /// _zoomPanBehavior = MapZoomPanBehavior() + /// ..zoomLevel = 4 + /// ..focalLatLng = MapLatLng(19.0759837, 72.8776559) + /// ..toolbarSettings = MapToolbarSettings( + /// position: MapToolbarPosition.topRight); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Zoom pan'), + /// ), + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// zoomPanBehavior: _zoomPanBehavior, + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` final MapToolbarPosition position; @override diff --git a/packages/syncfusion_flutter_maps/lib/src/utils.dart b/packages/syncfusion_flutter_maps/lib/src/utils.dart index 8d8d0841b..c68cb1485 100644 --- a/packages/syncfusion_flutter_maps/lib/src/utils.dart +++ b/packages/syncfusion_flutter_maps/lib/src/utils.dart @@ -6,6 +6,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'behavior/zoom_pan_behavior.dart'; +import 'common.dart'; +import 'controller/map_controller.dart'; // ignore_for_file: public_member_api_docs @@ -26,8 +28,6 @@ const double maximumLatitude = 85.05112878; const double minimumLongitude = -180; const double maximumLongitude = 180; -enum MarkerAction { insert, removeAt, replace, clear } - Size getBoxSize(BoxConstraints constraints) { final double width = constraints.hasBoundedWidth ? constraints.maxWidth : 300; final double height = @@ -36,56 +36,37 @@ Size getBoxSize(BoxConstraints constraints) { } Offset pixelFromLatLng(num latitude, num longitude, Size size, - [Offset offset = const Offset(0, 0), double factor = 1.0]) { - assert(latitude != null); - assert(longitude != null); - assert(size != null); - + [Offset offset = const Offset(0, 0), double scale = 1.0]) { final double x = (longitude + 180.0) / 360.0; final double sinLatitude = sin(latitude * pi / 180.0); final double y = 0.5 - log((1.0 + sinLatitude) / (1.0 - sinLatitude)) / (4.0 * pi); - final double mapSize = size.longestSide * factor; - final double dx = offset.dx + _clip(x * mapSize + 0.5, 0.0, mapSize - 1); - final double dy = offset.dy + _clip(y * mapSize + 0.5, 0.0, mapSize - 1); + final double mapSize = size.longestSide * scale; + final double dx = offset.dx + ((x * mapSize + 0.5).clamp(0.0, mapSize - 1)); + final double dy = offset.dy + ((y * mapSize + 0.5).clamp(0.0, mapSize - 1)); return Offset(dx, dy); } MapLatLng getPixelToLatLng( - Offset offset, Size size, Offset translation, double factor) { - return pixelToLatLng(offset, size, translation, factor); + Offset offset, Size size, Offset translation, double scale) { + return pixelToLatLng(offset, size, translation, scale); } -MapLatLng pixelToLatLng( - Offset offset, Size size, Offset translation, double factor) { - final double mapSize = size.longestSide * factor; +MapLatLng pixelToLatLng(Offset offset, Size size, + [Offset translation = const Offset(0, 0), double scale = 1.0]) { + final double mapSize = size.longestSide * scale; final double x = - (_clip(offset.dx - translation.dx, 0, mapSize - 1) / mapSize) - 0.5; + ((offset.dx - translation.dx).clamp(0, mapSize - 1) / mapSize) - 0.5; final double y = - 0.5 - (_clip(offset.dy - translation.dy, 0, mapSize - 1) / mapSize); + 0.5 - ((offset.dy - translation.dy).clamp(0, mapSize - 1) / mapSize); final double latitude = 90 - 360 * atan(exp(-y * 2 * pi)) / pi; final double longitude = 360 * x; return MapLatLng(latitude, longitude); } -double _clip(double value, double minValue, double maxValue) { - return min(max(value, minValue), maxValue); -} - -double interpolateValue(double value, double min, double max) { - assert(min != null); - max ??= value; - if (value > max) { - value = max; - } else if (value < min) { - value = min; - } - return value; -} - String getTrimText(String text, TextStyle style, double maxWidth, TextPainter painter, double width, - [double nextTextHalfWidth]) { + [double? nextTextHalfWidth, bool isInsideLastLabel = false]) { final int actualTextLength = text.length; String trimmedText = text; int trimLength = 3; // 3 dots @@ -103,27 +84,54 @@ String getTrimText(String text, TextStyle style, double maxWidth, trimLength++; } - width = nextTextHalfWidth != null - ? painter.width / 2 + nextTextHalfWidth - : painter.width; + if (isInsideLastLabel && nextTextHalfWidth != null) { + width = painter.width + nextTextHalfWidth; + } else { + width = nextTextHalfWidth != null + ? painter.width / 2 + nextTextHalfWidth + : painter.width; + } } return trimmedText; } double getTotalTileWidth(double zoom) { - return 256 * pow(2, zoom); + return 256 * pow(2, zoom).toDouble(); +} + +Offset getTranslationOffset(MapController controller) { + if (controller.layerType == LayerType.tile) { + return -controller.tileCurrentLevelDetails.origin!; + } else { + if (!controller.isInInteractive) { + return controller.shapeLayerOffset; + } else { + if (controller.gesture == Gesture.scale) { + return controller.getZoomingTranslation() + controller.normalize; + } + + return controller.shapeLayerOffset + controller.panDistance; + } + } +} + +double getLayerSizeFactor(MapController controller) { + return controller.layerType == LayerType.tile + ? 1.0 + : (controller.shapeLayerSizeFactor * + (controller.gesture == Gesture.scale ? controller.localScale : 1.0)); } -/// An interpolation between two latlngs. +/// An interpolation between two latlng. /// /// This class specializes the interpolation of [Tween] to use /// [MapLatLng.lerp]. class MapLatLngTween extends Tween { /// Creates an [MapLatLng] tween. - MapLatLngTween({MapLatLng begin, MapLatLng end}) + MapLatLngTween({MapLatLng? begin, MapLatLng? end}) : super(begin: begin, end: end); @override - MapLatLng lerp(double t) => MapLatLng.lerp(begin, end, t); + MapLatLng lerp(double t) => MapLatLng.lerp(begin, end, t)!; } diff --git a/packages/syncfusion_flutter_maps/pubspec.yaml b/packages/syncfusion_flutter_maps/pubspec.yaml index e1f0e3355..e85eae4f8 100644 --- a/packages/syncfusion_flutter_maps/pubspec.yaml +++ b/packages/syncfusion_flutter_maps/pubspec.yaml @@ -1,18 +1,21 @@ name: syncfusion_flutter_maps -description: Syncfusion Flutter Maps is a data visualization library written natively in Dart for creating beautiful and customizable maps. -version: 18.3.35-beta +description: A Flutter Maps library for creating beautiful, interactive, and customizable maps from shape files or WMTS services to visualize the geographical area. +version: 19.1.54-beta homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_maps environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter + syncfusion_flutter_core: path: ../syncfusion_flutter_core - collection: ">=1.9.0 <1.16.0" - http: ^0.12.2 + collection: ">=1.9.0 <=1.15.0" + http: ">=0.12.0 <=0.13.0" flutter: + + \ No newline at end of file diff --git a/packages/syncfusion_officechart/CHANGELOG.md b/packages/syncfusion_flutter_officechart/CHANGELOG.md similarity index 100% rename from packages/syncfusion_officechart/CHANGELOG.md rename to packages/syncfusion_flutter_officechart/CHANGELOG.md diff --git a/packages/syncfusion_officechart/LICENSE b/packages/syncfusion_flutter_officechart/LICENSE similarity index 100% rename from packages/syncfusion_officechart/LICENSE rename to packages/syncfusion_flutter_officechart/LICENSE diff --git a/packages/syncfusion_officechart/README.md b/packages/syncfusion_flutter_officechart/README.md similarity index 57% rename from packages/syncfusion_officechart/README.md rename to packages/syncfusion_flutter_officechart/README.md index d0d73809b..f2ccc5596 100644 --- a/packages/syncfusion_officechart/README.md +++ b/packages/syncfusion_flutter_officechart/README.md @@ -1,4 +1,4 @@ -![syncfusion_flutter_officechart_banner](https://cdn.syncfusion.com/content/images/Flutter-OfficeChart-Banner.png) +![syncfusion_flutter_officechart_banner](https://cdn.syncfusion.com/content/images/Flutter-OfficeChart-Banner.png) # Syncfusion Flutter OfficeChart @@ -8,7 +8,7 @@ Syncfusion Flutter OfficeChart is a feature rich and high-performance non-UI Exc The Excel package is a non-UI and reusable Flutter library to create different Excel charts programmatically with chart elements. The creation of Excel file are in XLSX (Excel 2007 and above) format. -**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion Commercial License or Syncfusion Community license. For more details, please check the [LICENSE](LICENSE) file. +**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion Commercial License or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. **Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. @@ -27,8 +27,6 @@ The Excel package is a non-UI and reusable Flutter library to create different E - [Add line chart](#add-line-chart) - [Add stacked chart](#add-stacked-chart) - [Add chart elements](#add-chart-elements) - - [Add area chart](#add-area-chart) - - [Add stacked 100 chart](#add-stacked-100-chart) - [Support and feedback](#support-and-feedback) - [About Syncfusion](#about-syncfusion) @@ -43,8 +41,6 @@ The following are the chart features of Syncfusion Flutter OfficeChart. * Add bar chart to Excel worksheet. * Add stacked chart to Excel worksheet. * Add Chart elements. -* Add area chart to Excel worksheet. -* Add stacked100 chart to Excel worksheet. ## Get the demo application @@ -63,13 +59,14 @@ Explore the full capability of our Flutter widgets on your device by installing Take a look at the following to learn more about Syncfusion Flutter XlsIO: -* [User guide documentation]() +* [Syncfusion Flutter Excel product page](https://www.syncfusion.com/flutter-widgets/excel-library) +* [User guide documentation](https://help.syncfusion.com/flutter/xlsio/overview) * [Knowledge base](https://www.syncfusion.com/kb) ## Installation -Install the latest version from [pub](https://pub.dev/packages/syncfusion_flutter_xlsio/install) +Install the latest version from [pub](https://pub.dev/packages/syncfusion_officechart/install) ## Getting started @@ -77,7 +74,7 @@ Import the following package to your project to create a Excel chart document fr ```dart import 'package:syncfusion_flutter_xlsio/xlsio.dart'; -import 'package:syncfusion_flutter_officechart/officechart.dart'; +import 'package:syncfusion_officechart/officechart.dart'; ``` ### Create a chart in Excel document @@ -115,7 +112,7 @@ workbook.dispose(); ``` ### Add pie chart -Use the following code to add pie chart to Excel worksheet. +Use the following code to add pie chart to excel worksheet. ```dart // Create a new Excel document. @@ -153,7 +150,7 @@ workbook.dispose(); ### Add column chart -Use the following code to add column chart to Excel worksheet. +Use the following code to add column chart to excel worksheet. ```dart // Create a new Excel document. @@ -191,7 +188,7 @@ workbook.dispose(); ### Add bar chart -Use the following code to add bar chart to Excel worksheet. +Use the following code to add bar chart to excel worksheet. ```dart // Create a new Excel document. @@ -236,7 +233,7 @@ workbook.dispose(); ### Add line chart -Use the following code to add line chart to Excel worksheet. +Use the following code to add line chart to excel worksheet. ```dart // Create a new Excel document. @@ -279,7 +276,7 @@ This section covers the various stacked chart. **Stacked Column chart** -Use the following code to add stacked column chart to Excel worksheet. +Use the following code to add stacked column chart to excel worksheet. ```dart // Create a new Excel document. @@ -324,7 +321,7 @@ workbook.dispose(); **Stacked bar chart** -Use the following code to add stacked bar chart to Excel worksheet. +Use the following code to add stacked bar chart to excel worksheet. ```dart // Create a new Excel document. @@ -369,7 +366,7 @@ workbook.dispose(); **Stacked Line chart** -Use the following code to add stacked line chart to Excel worksheet. +Use the following code to add stacked line chart to excel worksheet. ```dart // Create a new Excel document. @@ -411,75 +408,9 @@ List bytes = workbook.saveAsStream(); File('LineStackedChart.xlsx').writeAsBytes(bytes); workbook.dispose(); ``` - -**Stacked Area chart** - -Use the following code to add stacked area chart to Excel worksheet. - -```dart -// Create a new Excel document. -final Workbook workbook = Workbook(); -// Accessing worksheet via index. -final Worksheet sheet = workbook.worksheets[0]; -// Setting value in the cell. -sheet.getRangeByName('A1').text = 'Fruits'; -sheet.getRangeByName('A2').text = 'Apples'; -sheet.getRangeByName('A3').text = 'Grapes'; -sheet.getRangeByName('A4').text = 'Bananas'; -sheet.getRangeByName('A5').text = 'Oranges'; -sheet.getRangeByName('A6').text = 'Melons'; -sheet.getRangeByName('B1').text = 'Joey'; -sheet.getRangeByName('B2').number = 5; -sheet.getRangeByName('B3').number = 4; -sheet.getRangeByName('B4').number = 4; -sheet.getRangeByName('B5').number = 2; -sheet.getRangeByName('B6').number = 2; -sheet.getRangeByName('C1').text = 'Mathew'; -sheet.getRangeByName('C2').number = 3; -sheet.getRangeByName('C3').number = 5; -sheet.getRangeByName('C4').number = 4; -sheet.getRangeByName('C5').number = 1; -sheet.getRangeByName('C6').number = 7; -sheet.getRangeByName('D1').text = 'Peter'; -sheet.getRangeByName('D2').number = 2; -sheet.getRangeByName('D3').number = 2; -sheet.getRangeByName('D4').number = 3; -sheet.getRangeByName('D5').number = 5; -sheet.getRangeByName('D6').number = 6; - -// Create an instances of chart collection. -final ChartCollection charts = ChartCollection(sheet); -// Add the chart. -final Chart chart = charts.add(); -// Set Chart Type. -chart.chartType = ExcelChartType.areaStacked; -// Set data range in the worksheet. -chart.dataRange = sheet.getRangeByName('A1:D6'); -chart.isSeriesInRows = false; - -//Set Chart Title -chart.chartTitle = 'Stacked Area Chart'; - -//Set Legend -chart.hasLegend = true; -chart.legend.position = ExcelLegendPosition.bottom; - -//Positioning the chart in the worksheet -chart.topRow = 8; -chart.leftColumn = 1; -chart.bottomRow = 23; -chart.rightColumn = 8; -// set charts to worksheet. -sheet.charts = charts; -// save and dispose the workbook. -final List bytes = workbook.saveAsStream(); -workbook.dispose(); -File('AreaStackedChart.xlsx').writeAsBytes(bytes); -``` - ### Add chart elements -Use the following code to add chart elements to Excel worksheet. +Use the following code to add chart elements to excel worksheet. ```dart // Create a new Excel document. @@ -543,337 +474,6 @@ List bytes = workbook.saveAsStream(); File('ChartElement.xlsx').writeAsBytes(bytes); workbook.dispose(); ``` - -### Add area chart - -Use the following code to add area chart to Excel worksheet. - -```dart -// Create a new Excel document. -final Workbook workbook = Workbook(); -// Accessing worksheet via index. -final Worksheet sheet = workbook.worksheets[0]; -// Setting value in the cell. -sheet.getRangeByName('A1').text = 'Fruits'; -sheet.getRangeByName('A2').text = 'Apples'; -sheet.getRangeByName('A3').text = 'Grapes'; -sheet.getRangeByName('A4').text = 'Bananas'; -sheet.getRangeByName('A5').text = 'Oranges'; -sheet.getRangeByName('A6').text = 'Melons'; -sheet.getRangeByName('B1').text = 'Joey'; -sheet.getRangeByName('B2').number = 5; -sheet.getRangeByName('B3').number = 4; -sheet.getRangeByName('B4').number = 4; -sheet.getRangeByName('B5').number = 2; -sheet.getRangeByName('B6').number = 2; -sheet.getRangeByName('C1').text = 'Mathew'; -sheet.getRangeByName('C2').number = 3; -sheet.getRangeByName('C3').number = 5; -sheet.getRangeByName('C4').number = 4; -sheet.getRangeByName('C5').number = 1; -sheet.getRangeByName('C6').number = 7; -sheet.getRangeByName('D1').text = 'Peter'; -sheet.getRangeByName('D2').number = 2; -sheet.getRangeByName('D3').number = 2; -sheet.getRangeByName('D4').number = 3; -sheet.getRangeByName('D5').number = 5; -sheet.getRangeByName('D6').number = 6; - -// Create an instances of chart collection. -final ChartCollection charts = ChartCollection(sheet); -// Add the chart. -final Chart chart = charts.add(); -// Set Chart Type. -chart.chartType = ExcelChartType.area; -// Set data range in the worksheet. -chart.dataRange = sheet.getRangeByName('A1:D6'); -chart.isSeriesInRows = false; - -//Set Chart Title -chart.chartTitle = 'Area Chart'; - -//Set Legend -chart.hasLegend = true; -chart.legend.position = ExcelLegendPosition.bottom; - -//Positioning the chart in the worksheet -chart.topRow = 8; -chart.leftColumn = 1; -chart.bottomRow = 23; -chart.rightColumn = 8; -// set charts to worksheet. -sheet.charts = charts; -// save and dispose the workbook. -final List bytes = workbook.saveAsStream(); -workbook.dispose(); -File('AreaChart.xlsx').writeAsBytes(bytes); -``` - -### Add stacked 100 chart - -This section covers the various stacked 100 chart. - -**Stacked 100 Column chart** - -Use the following code to add stacked 100 column chart to Excel worksheet. - -```dart -// Create a new Excel document. -final Workbook workbook = Workbook(); -// Accessing worksheet via index. -final Worksheet sheet = workbook.worksheets[0]; -// Setting value in the cell. -sheet.getRangeByName('A1').text = 'Fruits'; -sheet.getRangeByName('A2').text = 'Apples'; -sheet.getRangeByName('A3').text = 'Grapes'; -sheet.getRangeByName('A4').text = 'Bananas'; -sheet.getRangeByName('A5').text = 'Oranges'; -sheet.getRangeByName('A6').text = 'Melons'; -sheet.getRangeByName('B1').text = 'Joey'; -sheet.getRangeByName('B2').number = 5; -sheet.getRangeByName('B3').number = 4; -sheet.getRangeByName('B4').number = 4; -sheet.getRangeByName('B5').number = 2; -sheet.getRangeByName('B6').number = 2; -sheet.getRangeByName('C1').text = 'Mathew'; -sheet.getRangeByName('C2').number = 3; -sheet.getRangeByName('C3').number = 5; -sheet.getRangeByName('C4').number = 4; -sheet.getRangeByName('C5').number = 1; -sheet.getRangeByName('C6').number = 7; -sheet.getRangeByName('D1').text = 'Peter'; -sheet.getRangeByName('D2').number = 2; -sheet.getRangeByName('D3').number = 2; -sheet.getRangeByName('D4').number = 3; -sheet.getRangeByName('D5').number = 5; -sheet.getRangeByName('D6').number = 6; - -// Create an instances of chart collection. -final ChartCollection charts = ChartCollection(sheet); -// Add the chart. -final Chart chart = charts.add(); -// Set Chart Type. -chart.chartType = ExcelChartType.columnStacked100; -// Set data range in the worksheet. -chart.dataRange = sheet.getRangeByName('A1:D6'); -chart.isSeriesInRows = false; - -//Set Chart Title -chart.chartTitle = 'Stacked 100 Column Chart'; - -//Set Legend -chart.hasLegend = true; -chart.legend.position = ExcelLegendPosition.bottom; - -//Positioning the chart in the worksheet -chart.topRow = 8; -chart.leftColumn = 1; -chart.bottomRow = 23; -chart.rightColumn = 8; -// set charts to worksheet. -sheet.charts = charts; -// save and dispose the workbook. -final List bytes = workbook.saveAsStream(); -workbook.dispose(); -File('ColumnStacked100Chart.xlsx').writeAsBytes(bytes); -``` - -**Stacked 100 bar chart** - -Use the following code to add stacked 100 bar chart to Excel worksheet. - -```dart -// Create a new Excel document. -final Workbook workbook = Workbook(); -// Accessing worksheet via index. -final Worksheet sheet = workbook.worksheets[0]; -// Setting value in the cell. -sheet.getRangeByName('A1').text = 'Fruits'; -sheet.getRangeByName('A2').text = 'Apples'; -sheet.getRangeByName('A3').text = 'Grapes'; -sheet.getRangeByName('A4').text = 'Bananas'; -sheet.getRangeByName('A5').text = 'Oranges'; -sheet.getRangeByName('A6').text = 'Melons'; -sheet.getRangeByName('B1').text = 'Joey'; -sheet.getRangeByName('B2').number = 5; -sheet.getRangeByName('B3').number = 4; -sheet.getRangeByName('B4').number = 4; -sheet.getRangeByName('B5').number = 2; -sheet.getRangeByName('B6').number = 2; -sheet.getRangeByName('C1').text = 'Mathew'; -sheet.getRangeByName('C2').number = 3; -sheet.getRangeByName('C3').number = 5; -sheet.getRangeByName('C4').number = 4; -sheet.getRangeByName('C5').number = 1; -sheet.getRangeByName('C6').number = 7; -sheet.getRangeByName('D1').text = 'Peter'; -sheet.getRangeByName('D2').number = 2; -sheet.getRangeByName('D3').number = 2; -sheet.getRangeByName('D4').number = 3; -sheet.getRangeByName('D5').number = 5; -sheet.getRangeByName('D6').number = 6; - -// Create an instances of chart collection. -final ChartCollection charts = ChartCollection(sheet); -// Add the chart. -final Chart chart = charts.add(); -// Set Chart Type. -chart.chartType = ExcelChartType.barStacked100; -// Set data range in the worksheet. -chart.dataRange = sheet.getRangeByName('A1:D6'); -chart.isSeriesInRows = false; - -//Set Chart Title -chart.chartTitle = 'Stacked 100 bar Chart'; - -//Set Legend -chart.hasLegend = true; -chart.legend.position = ExcelLegendPosition.bottom; - -//Positioning the chart in the worksheet -chart.topRow = 8; -chart.leftColumn = 1; -chart.bottomRow = 23; -chart.rightColumn = 8; -// set charts to worksheet. -sheet.charts = charts; -// save and dispose the workbook. -final List bytes = workbook.saveAsStream(); -workbook.dispose(); -File('BarStacked100Chart.xlsx').writeAsBytes(bytes); -``` - -**Stacked 100 Line chart** - -Use the following code to add stacked 100 line chart to Excel worksheet. - -```dart -// Create a new Excel document. -final Workbook workbook = Workbook(); -// Accessing worksheet via index. -final Worksheet sheet = workbook.worksheets[0]; -// Setting value in the cell. -sheet.getRangeByName('A1').text = 'Fruits'; -sheet.getRangeByName('A2').text = 'Apples'; -sheet.getRangeByName('A3').text = 'Grapes'; -sheet.getRangeByName('A4').text = 'Bananas'; -sheet.getRangeByName('A5').text = 'Oranges'; -sheet.getRangeByName('A6').text = 'Melons'; -sheet.getRangeByName('B1').text = 'Joey'; -sheet.getRangeByName('B2').number = 5; -sheet.getRangeByName('B3').number = 4; -sheet.getRangeByName('B4').number = 4; -sheet.getRangeByName('B5').number = 2; -sheet.getRangeByName('B6').number = 2; -sheet.getRangeByName('C1').text = 'Mathew'; -sheet.getRangeByName('C2').number = 3; -sheet.getRangeByName('C3').number = 5; -sheet.getRangeByName('C4').number = 4; -sheet.getRangeByName('C5').number = 1; -sheet.getRangeByName('C6').number = 7; -sheet.getRangeByName('D1').text = 'Peter'; -sheet.getRangeByName('D2').number = 2; -sheet.getRangeByName('D3').number = 2; -sheet.getRangeByName('D4').number = 3; -sheet.getRangeByName('D5').number = 5; -sheet.getRangeByName('D6').number = 6; - -// Create an instances of chart collection. -final ChartCollection charts = ChartCollection(sheet); -// Add the chart. -final Chart chart = charts.add(); -// Set Chart Type. -chart.chartType = ExcelChartType.lineStacked100; -// Set data range in the worksheet. -chart.dataRange = sheet.getRangeByName('A1:D6'); -chart.isSeriesInRows = false; - -//Set Chart Title -chart.chartTitle = 'Stacked 100 line Chart'; - -//Set Legend -chart.hasLegend = true; -chart.legend.position = ExcelLegendPosition.bottom; - -//Positioning the chart in the worksheet -chart.topRow = 8; -chart.leftColumn = 1; -chart.bottomRow = 23; -chart.rightColumn = 8; -// set charts to worksheet. -sheet.charts = charts; -// save and dispose the workbook. -final List bytes = workbook.saveAsStream(); -workbook.dispose(); -File('lineStacked100Chart.xlsx').writeAsBytes(bytes); - -``` - -**Stacked 100 Area chart** - -Use the following code to add stacked 100 area chart to Excel worksheet. - -```dart -// Create a new Excel document. -final Workbook workbook = Workbook(); -// Accessing worksheet via index. -final Worksheet sheet = workbook.worksheets[0]; -// Setting value in the cell. -sheet.getRangeByName('A1').text = 'Fruits'; -sheet.getRangeByName('A2').text = 'Apples'; -sheet.getRangeByName('A3').text = 'Grapes'; -sheet.getRangeByName('A4').text = 'Bananas'; -sheet.getRangeByName('A5').text = 'Oranges'; -sheet.getRangeByName('A6').text = 'Melons'; -sheet.getRangeByName('B1').text = 'Joey'; -sheet.getRangeByName('B2').number = 5; -sheet.getRangeByName('B3').number = 4; -sheet.getRangeByName('B4').number = 4; -sheet.getRangeByName('B5').number = 2; -sheet.getRangeByName('B6').number = 2; -sheet.getRangeByName('C1').text = 'Mathew'; -sheet.getRangeByName('C2').number = 3; -sheet.getRangeByName('C3').number = 5; -sheet.getRangeByName('C4').number = 4; -sheet.getRangeByName('C5').number = 1; -sheet.getRangeByName('C6').number = 7; -sheet.getRangeByName('D1').text = 'Peter'; -sheet.getRangeByName('D2').number = 2; -sheet.getRangeByName('D3').number = 2; -sheet.getRangeByName('D4').number = 3; -sheet.getRangeByName('D5').number = 5; -sheet.getRangeByName('D6').number = 6; - -// Create an instances of chart collection. -final ChartCollection charts = ChartCollection(sheet); -// Add the chart. -final Chart chart = charts.add(); -// Set Chart Type. -chart.chartType = ExcelChartType.areaStacked100; -// Set data range in the worksheet. -chart.dataRange = sheet.getRangeByName('A1:D6'); -chart.isSeriesInRows = false; - -//Set Chart Title -chart.chartTitle = 'Stacked 100 Area Chart'; - -//Set Legend -chart.hasLegend = true; -chart.legend.position = ExcelLegendPosition.bottom; - -//Positioning the chart in the worksheet -chart.topRow = 8; -chart.leftColumn = 1; -chart.bottomRow = 23; -chart.rightColumn = 8; -// set charts to worksheet. -sheet.charts = charts; -// save and dispose the workbook. -final List bytes = workbook.saveAsStream(); -workbook.dispose(); -File('AreaStacked100Chart.xlsx').writeAsBytes(bytes); -``` - ## Support and feedback * For any other queries, contact our [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident) or post the queries through the [Community forums](https://www.syncfusion.com/forums). You can also submit a feature request or a bug through our [Feedback portal](https://www.syncfusion.com/feedback/flutter). diff --git a/packages/syncfusion_officechart/analysis_options.yaml b/packages/syncfusion_flutter_officechart/analysis_options.yaml similarity index 85% rename from packages/syncfusion_officechart/analysis_options.yaml rename to packages/syncfusion_flutter_officechart/analysis_options.yaml index 8c73a8bae..d46e1fc06 100644 --- a/packages/syncfusion_officechart/analysis_options.yaml +++ b/packages/syncfusion_flutter_officechart/analysis_options.yaml @@ -5,4 +5,5 @@ analyzer: include_file_not_found: ignore lines_longer_than_80_chars: ignore avoid_as: ignore + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_officechart/example/README.md b/packages/syncfusion_flutter_officechart/example/README.md similarity index 100% rename from packages/syncfusion_officechart/example/README.md rename to packages/syncfusion_flutter_officechart/example/README.md diff --git a/packages/syncfusion_officechart/example/analysis_options.yaml b/packages/syncfusion_flutter_officechart/example/analysis_options.yaml similarity index 100% rename from packages/syncfusion_officechart/example/analysis_options.yaml rename to packages/syncfusion_flutter_officechart/example/analysis_options.yaml diff --git a/packages/syncfusion_officechart/example/android/.gitignore b/packages/syncfusion_flutter_officechart/example/android/.gitignore similarity index 100% rename from packages/syncfusion_officechart/example/android/.gitignore rename to packages/syncfusion_flutter_officechart/example/android/.gitignore diff --git a/packages/syncfusion_officechart/example/android/app/build.gradle b/packages/syncfusion_flutter_officechart/example/android/app/build.gradle similarity index 100% rename from packages/syncfusion_officechart/example/android/app/build.gradle rename to packages/syncfusion_flutter_officechart/example/android/app/build.gradle diff --git a/packages/syncfusion_officechart/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_officechart/example/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/debug/AndroidManifest.xml rename to packages/syncfusion_flutter_officechart/example/android/app/src/debug/AndroidManifest.xml diff --git a/packages/syncfusion_officechart/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_officechart/example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/AndroidManifest.xml rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/AndroidManifest.xml diff --git a/packages/syncfusion_officechart/example/android/app/src/main/kotlin/com/example/officechart_example/MainActivity.kt b/packages/syncfusion_flutter_officechart/example/android/app/src/main/kotlin/com/example/officechart_example/MainActivity.kt similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/kotlin/com/example/officechart_example/MainActivity.kt rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/kotlin/com/example/officechart_example/MainActivity.kt diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/drawable/launch_background.xml b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/drawable/launch_background.xml rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/values/styles.xml rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/values/styles.xml diff --git a/packages/syncfusion_officechart/example/android/app/src/profile/AndroidManifest.xml b/packages/syncfusion_flutter_officechart/example/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/profile/AndroidManifest.xml rename to packages/syncfusion_flutter_officechart/example/android/app/src/profile/AndroidManifest.xml diff --git a/packages/syncfusion_officechart/example/android/build.gradle b/packages/syncfusion_flutter_officechart/example/android/build.gradle similarity index 100% rename from packages/syncfusion_officechart/example/android/build.gradle rename to packages/syncfusion_flutter_officechart/example/android/build.gradle diff --git a/packages/syncfusion_officechart/example/android/gradle.properties b/packages/syncfusion_flutter_officechart/example/android/gradle.properties similarity index 100% rename from packages/syncfusion_officechart/example/android/gradle.properties rename to packages/syncfusion_flutter_officechart/example/android/gradle.properties diff --git a/packages/syncfusion_officechart/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_officechart/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/syncfusion_officechart/example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/syncfusion_flutter_officechart/example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/syncfusion_officechart/example/android/officechart_example_android.iml b/packages/syncfusion_flutter_officechart/example/android/officechart_example_android.iml similarity index 100% rename from packages/syncfusion_officechart/example/android/officechart_example_android.iml rename to packages/syncfusion_flutter_officechart/example/android/officechart_example_android.iml diff --git a/packages/syncfusion_officechart/example/android/settings.gradle b/packages/syncfusion_flutter_officechart/example/android/settings.gradle similarity index 100% rename from packages/syncfusion_officechart/example/android/settings.gradle rename to packages/syncfusion_flutter_officechart/example/android/settings.gradle diff --git a/packages/syncfusion_officechart/example/ios/.gitignore b/packages/syncfusion_flutter_officechart/example/ios/.gitignore similarity index 100% rename from packages/syncfusion_officechart/example/ios/.gitignore rename to packages/syncfusion_flutter_officechart/example/ios/.gitignore diff --git a/packages/syncfusion_officechart/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_officechart/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from packages/syncfusion_officechart/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/syncfusion_flutter_officechart/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/packages/syncfusion_officechart/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_officechart/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/syncfusion_officechart/example/ios/Flutter/Debug.xcconfig rename to packages/syncfusion_flutter_officechart/example/ios/Flutter/Debug.xcconfig diff --git a/packages/syncfusion_officechart/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_officechart/example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/syncfusion_officechart/example/ios/Flutter/Release.xcconfig rename to packages/syncfusion_flutter_officechart/example/ios/Flutter/Release.xcconfig diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.pbxproj diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/syncfusion_officechart/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_officechart/example/ios/Runner/AppDelegate.swift similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/AppDelegate.swift rename to packages/syncfusion_flutter_officechart/example/ios/Runner/AppDelegate.swift diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/packages/syncfusion_officechart/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/syncfusion_flutter_officechart/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/syncfusion_officechart/example/ios/Runner/Base.lproj/Main.storyboard b/packages/syncfusion_flutter_officechart/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/syncfusion_officechart/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_officechart/example/ios/Runner/Info.plist similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Info.plist rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Info.plist diff --git a/packages/syncfusion_officechart/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_officechart/example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Runner-Bridging-Header.h rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Runner-Bridging-Header.h diff --git a/packages/syncfusion_officechart/example/lib/main.dart b/packages/syncfusion_flutter_officechart/example/lib/main.dart similarity index 92% rename from packages/syncfusion_officechart/example/lib/main.dart rename to packages/syncfusion_flutter_officechart/example/lib/main.dart index 5dbab4890..ae49245c9 100644 --- a/packages/syncfusion_officechart/example/lib/main.dart +++ b/packages/syncfusion_flutter_officechart/example/lib/main.dart @@ -22,7 +22,7 @@ class CreateOfficeChartWidget extends StatelessWidget { /// Represents the office chart stateful widget class. class CreateOfficeChartStatefulWidget extends StatefulWidget { /// Initalize the instance of the [CreateOfficeChartStatefulWidget] class. - const CreateOfficeChartStatefulWidget({Key key, this.title}) + const CreateOfficeChartStatefulWidget({Key? key, required this.title}) : super(key: key); /// title. @@ -42,12 +42,13 @@ class _CreateOfficeChartState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - FlatButton( - child: const Text( - 'Generate Excel', - style: TextStyle(color: Colors.white), + TextButton( + child: const Text('Generate Excel Chart'), + style: TextButton.styleFrom( + primary: Colors.white, + backgroundColor: Colors.lightBlue, + onSurface: Colors.grey, ), - color: Colors.blue, onPressed: generateOfficeChart, ) ], @@ -74,13 +75,13 @@ class _CreateOfficeChartState extends State { sheet1.getRangeByName('A1:A18').rowHeight = 20.2; //Adding cell style. - final CellStyle style1 = workbook.styles.add('Style1'); + final Style style1 = workbook.styles.add('Style1'); style1.backColor = '#D9E1F2'; style1.hAlign = HAlignType.left; style1.vAlign = VAlignType.center; style1.bold = true; - final CellStyle style2 = workbook.styles.add('Style2'); + final Style style2 = workbook.styles.add('Style2'); style2.backColor = '#8EA9DB'; style2.vAlign = VAlignType.center; style2.numberFormat = '[Red](\$#,###)'; @@ -166,7 +167,8 @@ class _CreateOfficeChartState extends State { chart.rightColumn = 5; sheet1.charts = charts; - final List bytes = workbook.saveAsStream(); + //Save and launch Excel. + final List? bytes = workbook.saveAsStream(); workbook.dispose(); //Get the storage folder location using path_provider package. @@ -174,7 +176,7 @@ class _CreateOfficeChartState extends State { await path_provider.getApplicationDocumentsDirectory(); final String path = directory.path; final File file = File('$path/output.xlsx'); - await file.writeAsBytes(bytes); + await file.writeAsBytes(bytes!); //Launch the file (used open_file package) await open_file.OpenFile.open('$path/output.xlsx'); diff --git a/packages/syncfusion_officechart/example/officechart_example.iml b/packages/syncfusion_flutter_officechart/example/officechart_example.iml similarity index 100% rename from packages/syncfusion_officechart/example/officechart_example.iml rename to packages/syncfusion_flutter_officechart/example/officechart_example.iml diff --git a/packages/syncfusion_officechart/example/pubspec.yaml b/packages/syncfusion_flutter_officechart/example/pubspec.yaml similarity index 83% rename from packages/syncfusion_officechart/example/pubspec.yaml rename to packages/syncfusion_flutter_officechart/example/pubspec.yaml index e1a449103..12ec7b558 100644 --- a/packages/syncfusion_officechart/example/pubspec.yaml +++ b/packages/syncfusion_flutter_officechart/example/pubspec.yaml @@ -1,10 +1,13 @@ name: officechart_example description: Demo for creating a Excel file with chart using syncfusion_officechart package. +environment: + sdk: ">=2.12.0 <3.0.0" + dependencies: flutter: sdk: flutter - path_provider: ^1.6.7 + path_provider: ^2.0.1 open_file: ^3.0.1 syncfusion_officechart: path: ../ diff --git a/packages/syncfusion_officechart/lib/officechart.dart b/packages/syncfusion_flutter_officechart/lib/officechart.dart similarity index 100% rename from packages/syncfusion_officechart/lib/officechart.dart rename to packages/syncfusion_flutter_officechart/lib/officechart.dart diff --git a/packages/syncfusion_flutter_officechart/lib/src/chart/chart_axis.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_axis.dart new file mode 100644 index 000000000..0c081d2c3 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_axis.dart @@ -0,0 +1,78 @@ +part of officechart; + +/// Represents an axis on the chart. +class ChartAxis { + /// Represents the parent chart. + late Chart _parentChart; + + /// Represent the chart text area object. + ChartTextArea? _titleArea; + + /// Represents number format. + late String numberFormat; + + /// Represents minimumvalue of Axis. + double _minimumValue = 0; + + /// Represents maximum value of Axis. + double _maximumValue = 0; + + /// Gets or sets a boolean value indicating if the axis has major grid lines. + bool hasMajorGridLines = false; + + /// Automatic minimum selected. + bool _isAutoMin = true; + + /// Automatic minimum selected. + bool _isAutoMax = true; + + /// Gets chart text area object. + ChartTextArea get titleArea { + _titleArea ??= ChartTextArea(_parentChart); + + return _titleArea!; + } + + /// Sets chart text area object. + set titleArea(ChartTextArea? value) { + _titleArea = value; + } + + /// Gets chart axis title. + String? get title { + if (_titleArea == null) return null; + return _titleArea!.text; + } + + /// Sets chart axis title. + set title(String? value) { + titleArea.text = value; + } + + /// Gets indicates whether chart axis have title or not. + bool get _hasAxisTitle { + return _titleArea != null; + } + + /// Represents minimumvalue of Axis. + double get minimumValue { + return _minimumValue; + } + + /// Sets minimumvalue of Axis. + set minimumValue(double value) { + _minimumValue = value; + _isAutoMin = false; + } + + /// Represents maximumvalue of Axis. + double get maximumValue { + return _maximumValue; + } + + /// Sets maximumvalue of Axis. + set maximumValue(double value) { + _maximumValue = value; + _isAutoMax = false; + } +} diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_category_axis.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_category_axis.dart similarity index 81% rename from packages/syncfusion_officechart/lib/src/chart/chart_category_axis.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_category_axis.dart index 9f29268c2..5d87b08ed 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_category_axis.dart +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_category_axis.dart @@ -4,27 +4,24 @@ part of officechart; class ChartCategoryAxis extends ChartAxis { /// Create an instances of [ChartCategoryAxis] class. ChartCategoryAxis(Worksheet worksheet, Chart chart) { - _worksheet = worksheet; + _sheet = worksheet; _chart = chart; super._parentChart = _chart; } /// Parent worksheet. - Worksheet _sheet; - - set _worksheet(Worksheet value) { - value = _sheet; - } + // ignore: unused_field + late Worksheet _sheet; /// True to cut unused plot area. otherwise False. Default for area and surface charts. // ignore: prefer_final_fields bool _isBetween = false; /// Parent chart. - Chart _chart; + late Chart _chart; /// sets the category labels for the chart. - set _categoryLabels(Range value) { + set _categoryLabels(Range? value) { final ChartSeriesCollection coll = _chart.series; final int iLen = coll.count; for (int i = 0; i < iLen; i++) { diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_collection.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_collection.dart similarity index 91% rename from packages/syncfusion_officechart/lib/src/chart/chart_collection.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_collection.dart index db8d68755..b07c841df 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_collection.dart +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_collection.dart @@ -15,7 +15,7 @@ class ChartCollection extends ChartHelper { } /// Parent worksheet. - Worksheet _worksheet; + late Worksheet _worksheet; /// Represents parent worksheet. Worksheet get worksheet { @@ -23,10 +23,10 @@ class ChartCollection extends ChartHelper { } /// Parent Serializer - ChartSerialization _chartSerialization; + ChartSerialization? _chartSerialization; /// Inner list. - List _innerList; + late List _innerList; /// Represents the innerlist List get innerList { @@ -78,7 +78,7 @@ class ChartCollection extends ChartHelper { void serializeCharts(Worksheet sheet) { _chartSerialization ??= ChartSerialization(sheet.workbook); - _chartSerialization._saveCharts(sheet); + _chartSerialization!._saveCharts(sheet); } /// Serialize the chart drawings. @@ -86,6 +86,6 @@ class ChartCollection extends ChartHelper { void serializeChartDrawing(XmlBuilder builder, Worksheet sheet) { _chartSerialization ??= ChartSerialization(sheet.workbook); - _chartSerialization._serializeChartDrawing(builder, sheet); + _chartSerialization!._serializeChartDrawing(builder, sheet); } } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_datalabel.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_datalabel.dart similarity index 89% rename from packages/syncfusion_officechart/lib/src/chart/chart_datalabel.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_datalabel.dart index 33929204b..25494e487 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_datalabel.dart +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_datalabel.dart @@ -19,19 +19,19 @@ class ChartDataLabels { bool isSeriesName = false; /// Represent the chart text area object. - ChartTextArea _textArea; + ChartTextArea? _textArea; /// Represents number format. - String numberFormat; + String? numberFormat; /// Represent parent Serie. - ChartSerie _parentSerie; + late ChartSerie _parentSerie; /// Gets chart text area object. ChartTextArea get textArea { _textArea ??= ChartTextArea(_parentSerie); - return _textArea; + return _textArea!; } /// Sets chart text area object. diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_enum.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_enum.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_enum.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_enum.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_impl.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_impl.dart similarity index 95% rename from packages/syncfusion_officechart/lib/src/chart/chart_impl.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_impl.dart index 778bf7b1f..8738e4c7c 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_impl.dart +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_impl.dart @@ -10,32 +10,32 @@ class Chart { } /// Represents chart plot area. - ChartPlotArea _plotArea; + ChartPlotArea? _plotArea; /// Parent worksheet. - Worksheet _worksheet; + late Worksheet _worksheet; /// DataRange for the chart series. - Range _dataRange; + Range? _dataRange; /// DataRange for the chart serieValues used in helper methods. - Range _serieValue; + Range? _serieValue; /// True if series are in rows in DataRange; /// otherwise False. bool _bSeriesInRows = true; /// Collection of all the series of this chart. - ChartSeriesCollection _series; + late ChartSeriesCollection _series; /// Get or Set primaryCategoryAxis - ChartCategoryAxis _primaryCategoryAxis; + late ChartCategoryAxis _primaryCategoryAxis; /// Get or Set primary value axis - ChartValueAxis _primaryValueAxis; + late ChartValueAxis _primaryValueAxis; /// Represent the chart text area object. - ChartTextArea _textArea; + ChartTextArea? _textArea; /// Represent the default chart title name. final String _defaultChartTitle = 'Chart Title'; @@ -44,7 +44,7 @@ class Chart { bool _bHasLegend = false; /// Represent the chart legend. - ChartLegend _legend; + ChartLegend? _legend; /// Represent the clustered chart collection. final List _chartsCluster = [ @@ -72,7 +72,7 @@ class Chart { ExcelChartType _chartType = ExcelChartType.column; /// Represent chart index. - int index; + late int index; /// Represents the chart top row. /// ```dart @@ -96,7 +96,7 @@ class Chart { /// File('Chart.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` - int topRow; + late int topRow; /// Represent the chart left colunm. /// ```dart @@ -120,7 +120,7 @@ class Chart { /// File('Chart.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` - int leftColumn; + late int leftColumn; /// Represent the chart bottom row. /// ```dart @@ -144,7 +144,7 @@ class Chart { /// File('Chart.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` - int bottomRow; + late int bottomRow; /// Represents the chart right colunm. /// ```dart @@ -168,7 +168,7 @@ class Chart { /// File('Chart.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` - int rightColumn; + int rightColumn = 0; /// Excel chart type /// @@ -287,7 +287,7 @@ class Chart { /// File('ChartLineColor.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` - String linePatternColor; + String? linePatternColor; /// Gets the chart legend. /// ```dart @@ -311,7 +311,7 @@ class Chart { /// File('ChartLegendPosition.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` - ChartLegend get legend { + ChartLegend? get legend { return _legend; } @@ -337,7 +337,7 @@ class Chart { /// File('ChartName.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` - String name; + late String name; /// Gets chart text area. /// @@ -367,11 +367,11 @@ class Chart { /// ``` ChartTextArea get chartTitleArea { if (_textArea == null) _createChartTitle(); - return _textArea; + return _textArea!; } /// Gets chart title. - String get chartTitle { + String? get chartTitle { return chartTitleArea.text; } @@ -398,15 +398,15 @@ class Chart { /// File('ChartTitle.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` - set chartTitle(String title) { - chartTitleArea.text = title; + set chartTitle(String? value) { + chartTitleArea.text = value; } /// Gets indicates whether to display chart title or not. bool get hasTitle { bool result = false; if (_textArea != null) { - if (_textArea.text != null) { + if (_textArea!.text != null) { result = true; } } @@ -438,10 +438,10 @@ class Chart { /// ``` set hasTitle(bool value) { if (_textArea != null) { - if (value && _textArea.text == null) { - _textArea.text = _defaultChartTitle; + if (value && _textArea!.text == null) { + _textArea!.text = _defaultChartTitle; } else if (!value) { - _textArea.text = null; + _textArea!.text = null; } } } @@ -535,7 +535,7 @@ class Chart { /// workbook.dispose(); /// ``` ChartPlotArea get plotArea { - return _plotArea; + return _plotArea!; } /// Get the primaryCategoryAxis @@ -602,7 +602,7 @@ class Chart { } /// Gets the data range for the chart series. - Range get dataRange { + Range? get dataRange { return _dataRange; } @@ -628,7 +628,7 @@ class Chart { /// File('Chart.xlsx').writeAsBytes(bytes); /// workbook.dispose(); /// ``` - set dataRange(Range value) { + set dataRange(Range? value) { if (_dataRange != value) { _dataRange = value; if (value == null) return; @@ -685,7 +685,7 @@ class Chart { return; } - Range serieValue, serieNameRange, axisRange; + Range? serieValue, serieNameRange, axisRange; serieNameRange = _getSerieOrAxisRange(_dataRange, _bSeriesInRows, serieValue); @@ -709,7 +709,7 @@ class Chart { } /// Gets data range that represents series name or category axis. - Range _getSerieOrAxisRange(Range range, bool bIsInRow, Range serieRange) { + Range? _getSerieOrAxisRange(Range? range, bool bIsInRow, Range? serieRange) { if (range == null) throw ('range-Value should not be null'); final int iFirstLen = bIsInRow ? range.row : range.column; @@ -757,9 +757,9 @@ class Chart { } /// Updates series value by data range. - void _updateSeriesByDataRange(Range serieValue, Range serieNameRange, - Range axisRange, int iIndex, bool isSeriesInRows) { - Worksheet sheet; + void _updateSeriesByDataRange(Range? serieValue, Range? serieNameRange, + Range? axisRange, int iIndex, bool isSeriesInRows) { + Worksheet? sheet; if (serieValue != null) sheet = serieValue.worksheet; if (sheet == null && serieNameRange != null) { sheet = serieNameRange.worksheet; @@ -768,9 +768,9 @@ class Chart { final int iLen = _series.count; for (int i = 0; i < iLen; i++) { final Range value = (isSeriesInRows) - ? sheet.getRangeByIndex(serieValue.row + i, serieValue.column, + ? sheet.getRangeByIndex(serieValue!.row + i, serieValue.column, serieValue.row + i, serieValue.lastColumn) - : sheet.getRangeByIndex(serieValue.row, serieValue.column + i, + : sheet.getRangeByIndex(serieValue!.row, serieValue.column + i, serieValue.lastRow, serieValue.column + i); final ChartSerie serie = series[i]; @@ -783,7 +783,7 @@ class Chart { iAddIndex += (isSeriesInRows) ? serieNameRange.row : serieNameRange.column; - String formula = (isSeriesInRows) + String? formula = (isSeriesInRows) ? sheet .getRangeByIndex(iAddIndex + i, serieNameRange.column, iAddIndex + i, serieNameRange.lastColumn) @@ -809,7 +809,7 @@ class Chart { /// Validates Series range for min Series count of custom chart type. bool _validateSerieRangeForChartType( - Range serieValue, ExcelChartType type, bool isSeriesInRows) { + Range? serieValue, ExcelChartType type, bool isSeriesInRows) { if (serieValue == null) throw ('serieValue - Value cannot be null'); final int iSeriesInRangeCount = (isSeriesInRows) diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_legend.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_legend.dart similarity index 76% rename from packages/syncfusion_officechart/lib/src/chart/chart_legend.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_legend.dart index 22f16d484..a04b3b958 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_legend.dart +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_legend.dart @@ -9,20 +9,17 @@ class ChartLegend { } /// Parent worksheet. - Worksheet _sheet; - - set _worksheet(Worksheet value) { - value = _sheet; - } + // ignore: unused_field + late Worksheet _worksheet; /// Parent chart. - Chart _chart; + late Chart _chart; /// Gets and sets the chart legend position. - ExcelLegendPosition position; + ExcelLegendPosition position = ExcelLegendPosition.right; /// Represent the chart text area. - ChartTextArea _textArea; + ChartTextArea? _textArea; /// Gets the chart legend have text area or not. bool get _hasTextArea { @@ -32,6 +29,6 @@ class ChartLegend { /// Gets the chart legend text area. ChartTextArea get textArea { _textArea ??= ChartTextArea(_chart); - return _textArea; + return _textArea!; } } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_plotarea.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_plotarea.dart similarity index 79% rename from packages/syncfusion_officechart/lib/src/chart/chart_plotarea.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_plotarea.dart index 91452e9ea..6de5c6e35 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_plotarea.dart +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_plotarea.dart @@ -9,19 +9,16 @@ class ChartPlotArea { } /// Parent worksheet. - Worksheet _sheet; - - set _worksheet(Worksheet value) { - value = _sheet; - } + // ignore: unused_field + late Worksheet _worksheet; /// Parent chart. // ignore: unused_field - Chart _chart; + late Chart _chart; /// Represent PlotArea border line property ExcelChartLinePattern linePattern = ExcelChartLinePattern.none; /// ChartArea border line color property - String linePatternColor; + String? linePatternColor; } diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serialization.dart similarity index 89% rename from packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_serialization.dart index c8c4ad191..3d2d42311 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serialization.dart @@ -8,7 +8,7 @@ class ChartSerialization { } /// Workbook to serialize. - Workbook _workbook; + late Workbook _workbook; final Map _linePattern = { ExcelChartLinePattern.roundDot: 'sysDot', @@ -43,7 +43,7 @@ class ChartSerialization { if (chart.hasTitle) _serializeTitle(builder, chart.chartTitleArea); _serializePlotArea(builder, chart); if (chart.series.count > 0 && chart.hasLegend) { - _serializeLegend(builder, chart.legend); + _serializeLegend(builder, chart.legend!); } builder.element('c:plotVisOnly', nest: () { builder.attribute('val', '1'); @@ -173,7 +173,7 @@ class ChartSerialization { builder.attribute('sz', size.toInt().toString()); builder.attribute('b', textArea.bold ? '1' : '0'); builder.attribute('i', textArea.italic ? '1' : '0'); - if (textArea.color != null) { + if (textArea.color != '' && textArea.color != 'FF000000') { builder.element('a:solidFill', nest: () { builder.element('a:srgbClr', nest: () { builder.attribute('val', textArea.color.replaceAll('#', '')); @@ -231,7 +231,7 @@ class ChartSerialization { builder.element('a:r', nest: () { _serializeParagraphRunProperties(builder, chartTextArea); builder.element('a:t', nest: () { - builder.text(chartTextArea.text); + builder.text(chartTextArea.text!); }); }); }); @@ -256,7 +256,7 @@ class ChartSerialization { builder.attribute('b', textArea.bold ? '1' : '0'); builder.attribute('i', textArea.italic ? '1' : '0'); builder.attribute('baseline', '0'); - if (textArea.color != null) { + if (textArea.color != '' && textArea.color != 'FF000000') { builder.element('a:solidFill', nest: () { builder.element('a:srgbClr', nest: () { builder.attribute('val', textArea.color.replaceAll('#', '')); @@ -292,9 +292,6 @@ class ChartSerialization { /// serializes main chart tag. void _serializeMainChartTypeTag(XmlBuilder builder, Chart chart) { - if (builder == null) throw ("writer - Value can't be null"); - - if (chart == null) throw ("chart - Value can't be null"); switch (chart.chartType) { case ExcelChartType.column: case ExcelChartType.columnStacked: @@ -325,10 +322,6 @@ class ChartSerialization { /// serializes Line chart. void _serializeLineChart(XmlBuilder builder, Chart chart) { - if (builder == null) throw ("writer - Value can't be null"); - - if (chart == null) throw ("chart - Value can't be null"); - builder.element('c:lineChart', nest: () { _serializeChartGrouping(builder, chart); builder.element('c:varyColors', nest: () { @@ -350,10 +343,6 @@ class ChartSerialization { /// serializes Bar/ColumnClustered chart. void _serializeBarChart(XmlBuilder builder, Chart chart) { - if (builder == null) throw ("writer - Value can't be null"); - - if (chart == null) throw ("chart - Value can't be null"); - builder.element('c:barChart', nest: () { final String strDirection = chart.chartType.toString().contains('bar') ? 'bar' : 'col'; @@ -389,10 +378,6 @@ class ChartSerialization { /// serializes Area chart. void _serializeAreaChart(XmlBuilder builder, Chart chart) { - if (builder == null) throw ("writer - Value can't be null"); - - if (chart == null) throw ("chart - Value can't be null"); - builder.element('c:areaChart', nest: () { _serializeChartGrouping(builder, chart); builder.element('c:varyColors', nest: () { @@ -431,10 +416,6 @@ class ChartSerialization { /// serializes pie chart. void _serializePieChart(XmlBuilder builder, Chart chart) { - if (builder == null) throw ("writer - Value can't be null"); - - if (chart == null) throw ("chart - Value can't be null"); - builder.element('c:pieChart', nest: () { builder.element('c:varyColors', nest: () { builder.attribute('val', 1); @@ -478,14 +459,14 @@ class ChartSerialization { }); }); } - if (firstSerie.dataLabels != null && firstSerie.dataLabels.isValue) { + if (firstSerie.dataLabels.isValue) { builder.element('c:dLbls', nest: () { if (firstSerie.dataLabels.numberFormat != null && firstSerie.dataLabels.numberFormat != '' && firstSerie.dataLabels.numberFormat != 'General') { builder.element('c:numFmt', nest: () { builder.attribute( - 'formatCode', firstSerie.dataLabels.numberFormat); + 'formatCode', firstSerie.dataLabels.numberFormat!); builder.attribute('sourceLinked', '0'); }); } @@ -497,9 +478,7 @@ class ChartSerialization { builder.element('a:effectLst', nest: () {}); }); final ChartTextArea textArea = firstSerie.dataLabels.textArea; - if (textArea != null) { - _serializeChartTextArea(builder, textArea); - } + _serializeChartTextArea(builder, textArea); builder.element('c:showLegendKey', nest: () { builder.attribute('val', 0); }); @@ -527,15 +506,16 @@ class ChartSerialization { } if (firstSerie._categoryLabels != null) { final Range firstRange = firstSerie._chart._worksheet.getRangeByIndex( - firstSerie._categoryLabels.row, firstSerie._categoryLabels.column); + firstSerie._categoryLabels!.row, + firstSerie._categoryLabels!.column); builder.element('c:cat', nest: () { Worksheet tempSheet = firstSerie._chart._worksheet; - if (firstSerie._categoryLabels.addressGlobal != null) { + if (firstSerie._categoryLabels!.addressGlobal != '') { for (final Worksheet sheet in firstSerie._chart._worksheet.workbook.worksheets.innerList) { - if (firstSerie._categoryLabels.addressGlobal + if (firstSerie._categoryLabels!.addressGlobal .contains(RegExp(sheet.name + '!')) || - firstSerie._categoryLabels.addressGlobal + firstSerie._categoryLabels!.addressGlobal .contains(RegExp(sheet.name + "'" + '!'))) { tempSheet = sheet; break; @@ -545,10 +525,10 @@ class ChartSerialization { if (firstRange.text == null && firstRange.number != null) { builder.element('c:numRef', nest: () { builder.element('c:f', - nest: firstSerie._categoryLabels.addressGlobal); + nest: firstSerie._categoryLabels!.addressGlobal); final Range firstRange = firstSerie._chart._worksheet - .getRangeByIndex(firstSerie._categoryLabels.row, - firstSerie._categoryLabels.column); + .getRangeByIndex(firstSerie._categoryLabels!.row, + firstSerie._categoryLabels!.column); builder.element('c:numCache', nest: () { if (firstRange.numberFormat != null && firstRange.numberFormat != 'General') { @@ -561,7 +541,7 @@ class ChartSerialization { } else { _serializeStringReference( builder, - firstSerie._categoryLabels.addressGlobal, + firstSerie._categoryLabels!.addressGlobal, firstSerie, 'cat', tempSheet); @@ -571,17 +551,17 @@ class ChartSerialization { if (firstSerie._values != null) { builder.element('c:val', nest: () { builder.element('c:numRef', nest: () { - builder.element('c:f', nest: firstSerie._values.addressGlobal); + builder.element('c:f', nest: firstSerie._values!.addressGlobal); final Range firstRange = firstSerie._chart._worksheet .getRangeByIndex( - firstSerie._values.row, firstSerie._values.column); + firstSerie._values!.row, firstSerie._values!.column); Worksheet tempSheet = firstSerie._chart._worksheet; - if (firstSerie._values.addressGlobal != null) { + if (firstSerie._values!.addressGlobal != '') { for (final Worksheet sheet in firstSerie ._chart._worksheet.workbook.worksheets.innerList) { - if (firstSerie._values.addressGlobal + if (firstSerie._values!.addressGlobal .contains(RegExp(sheet.name + '!')) || - firstSerie._values.addressGlobal + firstSerie._values!.addressGlobal .contains(RegExp(sheet.name + "'" + '!'))) { tempSheet = sheet; break; @@ -621,7 +601,7 @@ class ChartSerialization { builder.attribute('b', textArea.bold ? '1' : '0'); builder.attribute('i', textArea.italic ? '1' : '0'); builder.attribute('baseline', '0'); - if (textArea.color != null) { + if (textArea.color != '' && textArea.color != 'FF000000') { builder.element('a:solidFill', nest: () { builder.element('a:srgbClr', nest: () { builder.attribute('val', textArea.color.replaceAll('#', '')); @@ -646,29 +626,33 @@ class ChartSerialization { /// serializes number cache values. void _serializeNumCacheValues( XmlBuilder builder, ChartSerie firstSerie, Worksheet dataSheet) { - final Range serieRange = firstSerie._values; - final int count = serieRange.count; - final int serieStartRow = serieRange.row; - final int serieStartColumn = serieRange.column; - builder.element('c:ptCount', nest: () { - builder.attribute('val', count); - }); - int index = 0; - while (index < count) { - final double value = dataSheet - .getRangeByIndex(serieStartRow + index, serieStartColumn) - .number; - builder.element('c:pt', nest: () { - builder.attribute('idx', index); - if (firstSerie.name != null) builder.element('c:v', nest: value); - }); - index++; + final Range? serieRange = firstSerie._values; + if (serieRange != null) { + final int count = serieRange.count; + final int serieStartRow = serieRange.row; + final int serieStartColumn = serieRange.column; + builder.element('c:ptCount', nest: () { + builder.attribute('val', count); + }); + int index = 0; + while (index < count) { + final double? value = dataSheet + .getRangeByIndex(serieStartRow + index, serieStartColumn) + .number; + if (value != null) { + builder.element('c:pt', nest: () { + builder.attribute('idx', index); + builder.element('c:v', nest: value); + }); + } + index++; + } } } /// serializes string cache reference. void _serializeStringReference(XmlBuilder builder, String range, - ChartSerie firstSerie, String tagName, Worksheet dataSheet) { + ChartSerie firstSerie, String tagName, Worksheet? dataSheet) { builder.element('c:strRef', nest: () { builder.element('c:f', nest: range); builder.element('c:strCache', nest: () { @@ -683,24 +667,29 @@ class ChartSerialization { /// serializes catergory cache values. void _serializeCategoryTagCacheValues( - XmlBuilder builder, ChartSerie firstSerie, Worksheet dataSheet) { - final Range serieRange = firstSerie._categoryLabels; - final int count = serieRange.count; - final int serieStartRow = serieRange.row; - final int serieStartColumn = serieRange.column; - builder.element('c:ptCount', nest: () { - builder.attribute('val', count); - }); - int index = 0; - while (index < count) { - final String value = dataSheet - .getRangeByIndex(serieStartRow + index, serieStartColumn) - .text; - builder.element('c:pt', nest: () { - builder.attribute('idx', index); - if (firstSerie.name != null) builder.element('c:v', nest: value); - }); - index++; + XmlBuilder builder, ChartSerie firstSerie, Worksheet? dataSheet) { + final Range? serieRange = firstSerie._categoryLabels; + if (serieRange != null) { + final int count = serieRange.count; + final int serieStartRow = serieRange.row; + final int serieStartColumn = serieRange.column; + builder.element('c:ptCount', nest: () { + builder.attribute('val', count); + }); + int index = 0; + while (index < count) { + final String value = dataSheet != null + ? dataSheet + .getRangeByIndex(serieStartRow + index, serieStartColumn) + .text! + : ''; + + builder.element('c:pt', nest: () { + builder.attribute('idx', index); + builder.element('c:v', nest: value); + }); + index++; + } } } @@ -719,7 +708,7 @@ class ChartSerialization { /// serializes fill for the charts. void _serializeFill(XmlBuilder builder, ExcelChartLinePattern linePattern, - String lineColor, bool hasSerie) { + String? lineColor, bool hasSerie) { builder.element('c:spPr', nest: () { if (lineColor == null) { builder.element('a:solidFill', nest: () { @@ -809,9 +798,7 @@ class ChartSerialization { if (axis._hasAxisTitle) { _serializeChartTextArea(builder, axis.titleArea); } - if (axis.numberFormat != null && - axis.numberFormat != '' && - axis.numberFormat != 'General') { + if (axis.numberFormat != '' && axis.numberFormat != 'General') { builder.element('c:numFmt', nest: () { builder.attribute('formatCode', axis.numberFormat); builder.attribute('sourceLinked', '0'); @@ -863,12 +850,12 @@ class ChartSerialization { builder.element('c:orientation', nest: () { builder.attribute('val', 'minMax'); }); - if (axis.maximumValue != null) { + if (!axis._isAutoMax) { builder.element('c:max', nest: () { builder.attribute('val', axis.maximumValue); }); } - if (axis.minimumValue != null) { + if (axis._isAutoMin) { builder.element('c:min', nest: () { builder.attribute('val', axis.minimumValue); }); @@ -883,9 +870,7 @@ class ChartSerialization { if (axis._hasAxisTitle) { _serializeChartTextArea(builder, axis.titleArea); } - if (axis.numberFormat != null && - axis.numberFormat != '' && - axis.numberFormat != 'General') { + if (axis.numberFormat != '' && axis.numberFormat != 'General') { builder.element('c:numFmt', nest: () { builder.attribute('formatCode', axis.numberFormat); builder.attribute('sourceLinked', '0'); diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_serie.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie.dart similarity index 88% rename from packages/syncfusion_officechart/lib/src/chart/chart_serie.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie.dart index 6190bbc54..84d1f6e5f 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_serie.dart +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie.dart @@ -9,41 +9,38 @@ class ChartSerie { } /// Parent worksheet. - Worksheet _sheet; - - set _worksheet(Worksheet value) { - value = _sheet; - } + // ignore: unused_field + late Worksheet _worksheet; /// Parent chart. - Chart _chart; + late Chart _chart; /// Represent the serie name. - String name; + String? name; /// serie name. - int _index; + late int _index; /// serie name or formula. - String _nameOrFormula; + late String _nameOrFormula; /// Check if the serie name is default. - bool _isDefaultName; + bool _isDefaultName = false; /// Chart Range Values - Range _values; + Range? _values; /// Chart Range Values - Range _categoryLabels; + Range? _categoryLabels; /// Chart DataLabels - ChartDataLabels _dataLabels; + ChartDataLabels? _dataLabels; /// Represents the PlotArea border line property ExcelChartLinePattern linePattern = ExcelChartLinePattern.none; /// ChartArea border line color property - String linePatternColor; + String? linePatternColor; /// Chart type for the series. ExcelChartType get _serieType { diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_series_collection.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_series_collection.dart similarity index 91% rename from packages/syncfusion_officechart/lib/src/chart/chart_series_collection.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_series_collection.dart index fa9bc681f..cc818fffe 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_series_collection.dart +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_series_collection.dart @@ -10,13 +10,13 @@ class ChartSeriesCollection { } /// Parent worksheet. - Worksheet _worksheet; + late Worksheet _worksheet; /// Parent chart. - Chart _chart; + late Chart _chart; /// Inner list. - List _innerList; + late List _innerList; /// Represents parent worksheet. Worksheet get worksheet { diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_text_area.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_text_area.dart similarity index 74% rename from packages/syncfusion_officechart/lib/src/chart/chart_text_area.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_text_area.dart index b1f77caf1..a55b2c6ad 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_text_area.dart +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_text_area.dart @@ -3,37 +3,39 @@ part of officechart; /// Represent the Chart Text Area. class ChartTextArea { /// Create an instances of [ChartTextArea] class. - ChartTextArea(Object _parent) { - _parent = _parent; + ChartTextArea(Object parent) { + _parent = parent; if (_parent is Chart) { - _chart = _parent; + _chart = _parent as Chart; } else if (_parent is ChartCategoryAxis) { - _chart = (_parent)._chart; + _chart = (_parent as ChartCategoryAxis)._chart; } else if (_parent is ChartValueAxis) { - _chart = (_parent)._chart; - } else if (_parent is ChartSerie) _chart = (_parent)._chart; + _chart = (_parent as ChartValueAxis)._chart; + } else if (_parent is ChartSerie) _chart = (_parent as ChartSerie)._chart; _createFont(); } /// Represent the title name. - String text; + String? text; /// Represent the color. - String color; - - /// Parent chart. - Chart _parentChart; + String get color { + return _font.color; + } - set _chart(Chart value) { - value = _parentChart; + set color(String value) { + _font.color = value; } - /// Parent object. + /// Parent chart. // ignore: unused_field - Object _parent; + late Chart _chart; + + /// Parent object. + late Object _parent; /// Reprent the font. - Font _font; + late Font _font; /// Boolean value indicates whether other elements in chart can overlap this text area. final bool _overlay = false; diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_value_axis.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_value_axis.dart similarity index 77% rename from packages/syncfusion_officechart/lib/src/chart/chart_value_axis.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_value_axis.dart index a45922457..b6196deb2 100644 --- a/packages/syncfusion_officechart/lib/src/chart/chart_value_axis.dart +++ b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_value_axis.dart @@ -10,12 +10,9 @@ class ChartValueAxis extends ChartAxis { } // Parent worksheet. - Worksheet _sheet; - - set _worksheet(Worksheet value) { - value = _sheet; - } + // ignore: unused_field + late Worksheet _worksheet; // Parent chart. - Chart _chart; + late Chart _chart; } diff --git a/packages/syncfusion_officechart/pubspec.yaml b/packages/syncfusion_flutter_officechart/pubspec.yaml similarity index 73% rename from packages/syncfusion_officechart/pubspec.yaml rename to packages/syncfusion_flutter_officechart/pubspec.yaml index 3b226c6ff..56d6902d0 100644 --- a/packages/syncfusion_officechart/pubspec.yaml +++ b/packages/syncfusion_flutter_officechart/pubspec.yaml @@ -1,15 +1,15 @@ name: syncfusion_officechart description: Syncfusion Flutter Office Chart is a library written natively in Dart for creating Office charts from scratch. -version: 18.3.35-beta.1 +version: 19.1.54-beta.1 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_officechart environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter - xml: ^4.5.1 - archive: ^2.0.13 + xml: ^5.0.2 + archive: ^3.1.2 syncfusion_flutter_xlsio: - path: ../syncfusion_flutter_xlsio + path: ../syncfusion_flutter_xlsio \ No newline at end of file diff --git a/packages/syncfusion_officechart/syncfusion_officechart.iml b/packages/syncfusion_flutter_officechart/syncfusion_officechart.iml similarity index 100% rename from packages/syncfusion_officechart/syncfusion_officechart.iml rename to packages/syncfusion_flutter_officechart/syncfusion_officechart.iml diff --git a/packages/syncfusion_officecore/CHANGELOG.md b/packages/syncfusion_flutter_officecore/CHANGELOG.md similarity index 100% rename from packages/syncfusion_officecore/CHANGELOG.md rename to packages/syncfusion_flutter_officecore/CHANGELOG.md diff --git a/packages/syncfusion_officecore/LICENSE b/packages/syncfusion_flutter_officecore/LICENSE similarity index 100% rename from packages/syncfusion_officecore/LICENSE rename to packages/syncfusion_flutter_officecore/LICENSE diff --git a/packages/syncfusion_officecore/README.md b/packages/syncfusion_flutter_officecore/README.md similarity index 93% rename from packages/syncfusion_officecore/README.md rename to packages/syncfusion_flutter_officecore/README.md index 436c56538..d124c3978 100644 --- a/packages/syncfusion_officecore/README.md +++ b/packages/syncfusion_flutter_officecore/README.md @@ -3,9 +3,9 @@ Syncfusion Flutter OfficeCore is a dependent package for the following Syncfusion Flutter packages. * [syncfusion_flutter_xlsio](https://pub.dev/packages/syncfusion_flutter_xlsio) -* [syncfusion_flutter_officechart](https://pub.dev/packages/syncfusion_flutter_officechart) +* [syncfusion_flutter_officechart](https://pub.dev/packages/syncfusion_officechart) -**Disclaimer:** This is a commercial package. To use this package, you need to have either Syncfusion Commercial License or Syncfusion Community license. For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. +**Disclaimer:** This is a commercial package. To use this package, you need to have either Syncfusion Commercial License or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. **Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. diff --git a/packages/syncfusion_officecore/analysis_options.yaml b/packages/syncfusion_flutter_officecore/analysis_options.yaml similarity index 100% rename from packages/syncfusion_officecore/analysis_options.yaml rename to packages/syncfusion_flutter_officecore/analysis_options.yaml diff --git a/packages/syncfusion_officecore/example/README.md b/packages/syncfusion_flutter_officecore/example/README.md similarity index 100% rename from packages/syncfusion_officecore/example/README.md rename to packages/syncfusion_flutter_officecore/example/README.md diff --git a/packages/syncfusion_officecore/example/analysis_options.yaml b/packages/syncfusion_flutter_officecore/example/analysis_options.yaml similarity index 100% rename from packages/syncfusion_officecore/example/analysis_options.yaml rename to packages/syncfusion_flutter_officecore/example/analysis_options.yaml diff --git a/packages/syncfusion_officecore/example/android/.gitignore b/packages/syncfusion_flutter_officecore/example/android/.gitignore similarity index 100% rename from packages/syncfusion_officecore/example/android/.gitignore rename to packages/syncfusion_flutter_officecore/example/android/.gitignore diff --git a/packages/syncfusion_officecore/example/android/app/build.gradle b/packages/syncfusion_flutter_officecore/example/android/app/build.gradle similarity index 100% rename from packages/syncfusion_officecore/example/android/app/build.gradle rename to packages/syncfusion_flutter_officecore/example/android/app/build.gradle diff --git a/packages/syncfusion_officecore/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_officecore/example/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/debug/AndroidManifest.xml rename to packages/syncfusion_flutter_officecore/example/android/app/src/debug/AndroidManifest.xml diff --git a/packages/syncfusion_officecore/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_officecore/example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/AndroidManifest.xml rename to packages/syncfusion_flutter_officecore/example/android/app/src/main/AndroidManifest.xml diff --git a/packages/syncfusion_officecore/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt b/packages/syncfusion_flutter_officecore/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt rename to packages/syncfusion_flutter_officecore/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/drawable/launch_background.xml b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/drawable/launch_background.xml rename to packages/syncfusion_flutter_officecore/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/values/styles.xml rename to packages/syncfusion_flutter_officecore/example/android/app/src/main/res/values/styles.xml diff --git a/packages/syncfusion_officecore/example/android/app/src/profile/AndroidManifest.xml b/packages/syncfusion_flutter_officecore/example/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/profile/AndroidManifest.xml rename to packages/syncfusion_flutter_officecore/example/android/app/src/profile/AndroidManifest.xml diff --git a/packages/syncfusion_officecore/example/android/build.gradle b/packages/syncfusion_flutter_officecore/example/android/build.gradle similarity index 100% rename from packages/syncfusion_officecore/example/android/build.gradle rename to packages/syncfusion_flutter_officecore/example/android/build.gradle diff --git a/packages/syncfusion_officecore/example/android/gradle.properties b/packages/syncfusion_flutter_officecore/example/android/gradle.properties similarity index 100% rename from packages/syncfusion_officecore/example/android/gradle.properties rename to packages/syncfusion_flutter_officecore/example/android/gradle.properties diff --git a/packages/syncfusion_officecore/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_officecore/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/syncfusion_officecore/example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/syncfusion_flutter_officecore/example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/syncfusion_officecore/example/android/officecore_example_android.iml b/packages/syncfusion_flutter_officecore/example/android/officecore_example_android.iml similarity index 100% rename from packages/syncfusion_officecore/example/android/officecore_example_android.iml rename to packages/syncfusion_flutter_officecore/example/android/officecore_example_android.iml diff --git a/packages/syncfusion_officecore/example/android/settings.gradle b/packages/syncfusion_flutter_officecore/example/android/settings.gradle similarity index 100% rename from packages/syncfusion_officecore/example/android/settings.gradle rename to packages/syncfusion_flutter_officecore/example/android/settings.gradle diff --git a/packages/syncfusion_officecore/example/ios/.gitignore b/packages/syncfusion_flutter_officecore/example/ios/.gitignore similarity index 100% rename from packages/syncfusion_officecore/example/ios/.gitignore rename to packages/syncfusion_flutter_officecore/example/ios/.gitignore diff --git a/packages/syncfusion_officecore/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_officecore/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from packages/syncfusion_officecore/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/syncfusion_flutter_officecore/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/packages/syncfusion_officecore/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_officecore/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/syncfusion_officecore/example/ios/Flutter/Debug.xcconfig rename to packages/syncfusion_flutter_officecore/example/ios/Flutter/Debug.xcconfig diff --git a/packages/syncfusion_officecore/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_officecore/example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/syncfusion_officecore/example/ios/Flutter/Release.xcconfig rename to packages/syncfusion_flutter_officecore/example/ios/Flutter/Release.xcconfig diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.pbxproj diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/syncfusion_officecore/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_officecore/example/ios/Runner/AppDelegate.swift similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/AppDelegate.swift rename to packages/syncfusion_flutter_officecore/example/ios/Runner/AppDelegate.swift diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/packages/syncfusion_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/syncfusion_flutter_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/syncfusion_officecore/example/ios/Runner/Base.lproj/Main.storyboard b/packages/syncfusion_flutter_officecore/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/syncfusion_officecore/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_officecore/example/ios/Runner/Info.plist similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Info.plist rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Info.plist diff --git a/packages/syncfusion_officecore/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_officecore/example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Runner-Bridging-Header.h rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Runner-Bridging-Header.h diff --git a/packages/syncfusion_officecore/example/lib/main.dart b/packages/syncfusion_flutter_officecore/example/lib/main.dart similarity index 94% rename from packages/syncfusion_officecore/example/lib/main.dart rename to packages/syncfusion_flutter_officecore/example/lib/main.dart index 2abbc4470..9b677b200 100644 --- a/packages/syncfusion_officecore/example/lib/main.dart +++ b/packages/syncfusion_flutter_officecore/example/lib/main.dart @@ -21,7 +21,8 @@ class CreateExcelWidget extends StatelessWidget { /// Represents the XlsIO stateful widget class. class CreateExcelStatefulWidget extends StatefulWidget { /// Initalize the instance of the [CreateExcelStatefulWidget] class. - const CreateExcelStatefulWidget({Key key, this.title}) : super(key: key); + const CreateExcelStatefulWidget({Key? key, required this.title}) + : super(key: key); /// title. final String title; @@ -40,12 +41,13 @@ class _CreateExcelState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - FlatButton( - child: const Text( - 'Generate Excel', - style: TextStyle(color: Colors.white), + TextButton( + child: const Text('Generate Excel'), + style: TextButton.styleFrom( + primary: Colors.white, + backgroundColor: Colors.lightBlue, + onSurface: Colors.grey, ), - color: Colors.blue, onPressed: generateExcel, ) ], @@ -214,7 +216,7 @@ class _CreateExcelState extends State { range9.cellStyle.vAlign = VAlignType.center; //Save and launch the excel. - final List bytes = workbook.saveAsStream(); + final List? bytes = workbook.saveAsStream(); //Dispose the document. workbook.dispose(); @@ -223,7 +225,7 @@ class _CreateExcelState extends State { await path_provider.getApplicationDocumentsDirectory(); final String path = directory.path; final File file = File('$path/output.xlsx'); - await file.writeAsBytes(bytes); + await file.writeAsBytes(bytes!); //Launch the file (used open_file package) await open_file.OpenFile.open('$path/output.xlsx'); diff --git a/packages/syncfusion_officecore/example/officecore_example.iml b/packages/syncfusion_flutter_officecore/example/officecore_example.iml similarity index 100% rename from packages/syncfusion_officecore/example/officecore_example.iml rename to packages/syncfusion_flutter_officecore/example/officecore_example.iml diff --git a/packages/syncfusion_officecore/example/pubspec.yaml b/packages/syncfusion_flutter_officecore/example/pubspec.yaml similarity index 54% rename from packages/syncfusion_officecore/example/pubspec.yaml rename to packages/syncfusion_flutter_officecore/example/pubspec.yaml index af268bab7..829637df3 100644 --- a/packages/syncfusion_officecore/example/pubspec.yaml +++ b/packages/syncfusion_flutter_officecore/example/pubspec.yaml @@ -1,17 +1,16 @@ name: officecore_example description: Demo for creating a Excel file using syncfusion_flutter_officecore package. +environment: + sdk: ">=2.12.0 <3.0.0" + dependencies: flutter: sdk: flutter - path_provider: ^1.6.7 + path_provider: ^2.0.1 open_file: ^3.0.1 syncfusion_flutter_xlsio: - git: - url: https://buildautomation:Coolcomp299@gitlab.syncfusion.com/essential-studio/flutter-xlsio - path: flutter_xlsio/syncfusion_flutter_xlsio - branch: release/18.4.0.1 - ref: release/18.4.0.1 + path: ../../syncfusion_flutter_xlsio # The following section is specific to Flutter. diff --git a/packages/syncfusion_officecore/lib/officecore.dart b/packages/syncfusion_flutter_officecore/lib/officecore.dart similarity index 100% rename from packages/syncfusion_officecore/lib/officecore.dart rename to packages/syncfusion_flutter_officecore/lib/officecore.dart diff --git a/packages/syncfusion_officecore/lib/src/built_in_properties.dart b/packages/syncfusion_flutter_officecore/lib/src/built_in_properties.dart similarity index 70% rename from packages/syncfusion_officecore/lib/src/built_in_properties.dart rename to packages/syncfusion_flutter_officecore/lib/src/built_in_properties.dart index 83c4a4914..fdfc0fa82 100644 --- a/packages/syncfusion_officecore/lib/src/built_in_properties.dart +++ b/packages/syncfusion_flutter_officecore/lib/src/built_in_properties.dart @@ -3,35 +3,35 @@ part of officecore; ///Represent the document properties in a MS Excel Document. class BuiltInProperties { /// Gets or Sets author of the document. - String author; + String? author; /// Gets or Sets comments of the document. - String comments; + String? comments; /// Gets or Sets category of the document. - String category; + String? category; /// Gets or Sets company of the document. - String company; + String? company; /// Gets or Sets manager of the document. - String manager; + String? manager; /// Gets or Sets subject of the document. - String subject; + String? subject; /// Gets or Sets title of the document. - String title; + String? title; /// Gets or Sets creation date of the document. - DateTime createdDate = DateTime.now(); + DateTime? createdDate = DateTime.now(); /// Gets or Sets modified date of the document. - DateTime modifiedDate = DateTime.now(); + DateTime? modifiedDate = DateTime.now(); /// Gets or Sets tags of the document. - String tags; + String? tags; /// Gets or Sets status of the document. - String status; + String? status; } diff --git a/packages/syncfusion_officecore/pubspec.yaml b/packages/syncfusion_flutter_officecore/pubspec.yaml similarity index 74% rename from packages/syncfusion_officecore/pubspec.yaml rename to packages/syncfusion_flutter_officecore/pubspec.yaml index ec9bfe17e..456192640 100644 --- a/packages/syncfusion_officecore/pubspec.yaml +++ b/packages/syncfusion_flutter_officecore/pubspec.yaml @@ -1,13 +1,13 @@ name: syncfusion_officecore -description: Syncfusion Flutter Office Core is a dependant library for Syncfusion Flutter XlsIO, written natively in Dart for creating Excel charts from scratch. -version: 18.3.35-beta +description: Syncfusion Flutter Office Core is a dependant library for Syncfusion Flutter XlsIO, written natively in Dart for creating Excel from scratch. +version: 19.1.54-beta homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_officecore environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter syncfusion_flutter_core: - path: ../syncfusion_flutter_core + path: ../syncfusion_flutter_core \ No newline at end of file diff --git a/packages/syncfusion_officecore/syncfusion_officecore.iml b/packages/syncfusion_flutter_officecore/syncfusion_officecore.iml similarity index 100% rename from packages/syncfusion_officecore/syncfusion_officecore.iml rename to packages/syncfusion_flutter_officecore/syncfusion_officecore.iml diff --git a/packages/syncfusion_flutter_pdf/CHANGELOG.md b/packages/syncfusion_flutter_pdf/CHANGELOG.md index 7cf9bcf9e..564a9097f 100644 --- a/packages/syncfusion_flutter_pdf/CHANGELOG.md +++ b/packages/syncfusion_flutter_pdf/CHANGELOG.md @@ -1,117 +1,113 @@ -## [Unreleased] +## Unreleased **Breaking changes** -* The method `extractTextWithLine` has been removed and add new method `extractTextLines` instead. +* The property flatten has been removed from the `PdfAnnotation` and `PdfAnnotationCollection`. And added a new method called `flatten` and `flattenAllAnnotations` instead. **Features** -* Support provided to encrypt or decrypt a PDF document. -* Support provided to create, read, and edit layers in a PDF document. -* Support provided to create PDF conformance document. -* Support provided to extract text with the layout. -* Support provided to draw an image with pagination. -* Support provided to add an attachment to the PDF document. -* Support provided to add document information in a PDF document. +* Provided the support to add image position in the PDF grid cell. +* Provided the support to set clip using the path data on the PDF graphics. +* Provided the support to add encryption options when protecting the PDF files. +* Provided the support to create, read, modify, fill, and flatten PDF form fields. +* Provided the support to digitally sign the PDF document. **Bugs** -* The bookmark parsing issue has been resolved now. - -## [18.3.52-beta] - 12/01/2020 - -* The method `extractTextWithLine` from `PdfTextExtractor` has been deprecated and added new method called `extractTextLines` instead. +* The wrong header row index retrieved from the PDF grid begin cell callback has been resolved now. -## [18.3.51-beta] - 11/24/2020 +## [18.4.48-beta] - 03/23/2021 **Bugs** -* The typecasting issue has been resolved now. +* The page size is not updated properly when adding margins issue resolved now. -## [18.3.48-beta] - 11/11/2020 +## [18.4.43-beta] - 02/16/2021 -No changes. +**Bugs** -## [18.3.47-beta] - 11/05/2020 +* The bookmark Unicode text preservation issue resolved now. -No changes. +## [18.4.42-beta] - 02/09/2021 -## [18.3.44-beta] - 10/27/2020 +**Bugs** -No changes. +* The unhandled exception when adding watermarks to the PDF document is resolved now. -## [18.3.42-beta] - 10/20/2020 +## [18.4.41-beta] - 02/02/2021 -No changes. +**Bugs** -## [18.3.40-beta] - 10/13/2020 +* The text rendering issue while using the PdfTextElement is resolved now. -No changes. +## [18.4.34-beta] - 01/12/2021 -## [18.3.38-beta] - 10/07/2020 +**Features** -No changes. +* Provided the support to set clip using path data on the PDF graphics. -## [18.3.35-beta] - 10/01/2020 +## [18.4.32-beta] - 12/30/2020 **Features** -* Support provided to parse the existing PDF document. -* Support provided to add or remove the PDF pages in an existing PDF document. -* Support provided to add the graphical content to the existing PDF document page. -* Provided the incremental update support for the existing PDF document. -* Support provided to create and load the annotations in a new or existing PDF document. -* Support provided to load the existing PDF document bookmarks with its destination. -* Support provided to extract the text in an existing PDF document along with its bounds. -* Support provided to find the text in an existing PDF document along with its bounds and page index. -* Support provided to flatten the supported annotations in an existing PDF document. -* Support provided to save the PDF document with a cross-reference stream. +* Provided the support to add image position in PDF grid cell. -## [18.2.59-beta.1] - 09/24/2020 +## [18.4.31-beta] - 12/22/2020 **Bugs** -* The meta package issue has been resolved now. - -## [18.2.59-beta] - 09/23/2020 - -No changes. +* The header row index issue has been resolved now. -## [18.2.57-beta] - 09/08/2020 +## [18.4.30-beta] - 12/17/2020 -No changes. +**Breaking changes** -## [18.2.56-beta] - 09/01/2020 +* The `extractTextWithLine` method has been removed and added a new `extractTextLines` method instead. -No changes. +**Features** -## [18.2.55-beta] - 08/25/2020 +* Provided the support to encrypt or decrypt a PDF document. +* Provided the support to create, read, and edit layers in PDF documents. +* Provided the support to create a PDF conformance document. +* Provided the support to extract text with the layout. +* Provided the support to draw an image with pagination. +* Provided the support to add an attachment to the PDF document. +* Provided the support to add the document information in a PDF document. -No changes. +**Bugs** -## [18.2.54-beta] - 08/18/2020 +* The bookmark parsing issue has been resolved now. -No changes. +## [18.3.52-beta] - 12/01/2020 -## [18.2.48-beta] - 08/04/2020 +* The method `extractTextWithLine` from `PdfTextExtractor` has been deprecated and added new method called `extractTextLines` instead. -No changes. +## [18.3.51-beta] - 11/24/2020 -## [18.2.47-beta] - 07/28/2020 +**Bugs** -No changes. +* The typecasting issue has been resolved now. -## [18.2.46-beta] - 07/21/2020 +## [18.3.35-beta] - 10/01/2020 -No changes. +**Features** -## [18.2.45-beta] - 07/14/2020 +* Support provided to parse the existing PDF document. +* Support provided to add or remove the PDF pages in an existing PDF document. +* Support provided to add the graphical content to the existing PDF document page. +* Provided the incremental update support for the existing PDF document. +* Support provided to create and load the annotations in a new or existing PDF document. +* Support provided to load the existing PDF document bookmarks with its destination. +* Support provided to extract the text in an existing PDF document along with its bounds. +* Support provided to find the text in an existing PDF document along with its bounds and page index. +* Support provided to flatten the supported annotations in an existing PDF document. +* Support provided to save the PDF document with a cross-reference stream. -No changes. +## [18.2.59-beta.1] - 09/24/2020 -## [18.2.44-beta] - 07/07/2020 +**Bugs** -No changes. +* The meta package issue has been resolved now. ## [18.1.52-beta] - 05/14/2020 @@ -119,35 +115,12 @@ No changes. * Text will be preserved properly while using the TrueType font. -## [18.1.48-beta] - 05/05/2020 - -No changes. - -## [18.1.46-beta] - 04/28/2020 - -No changes. - -## [18.1.45-beta] - 04/21/2020 - -No changes. - -## [18.1.44-beta] - 04/14/2020 - -No changes. - -## [18.1.43-beta] - 04/07/2020 - -No changes. - -## [18.1.42-beta] - 04/01/2020 - -No changes. - ## [18.1.36-beta] - 03/19/2020 Initial release **Features** + * Provided the support for creating a PDF document with pages and sections. * Provided the support for adding text, images, shapes, and more. * Provided the support for adding Unicode text with True Type font. diff --git a/packages/syncfusion_flutter_pdf/README.md b/packages/syncfusion_flutter_pdf/README.md index 1d7f73f16..2785b07fd 100644 --- a/packages/syncfusion_flutter_pdf/README.md +++ b/packages/syncfusion_flutter_pdf/README.md @@ -1,16 +1,14 @@ ![syncfusion_flutter_pdf_banner](https://cdn.syncfusion.com/content/images/FTControl/Flutter-PDF-Banner.png) -# Syncfusion Flutter PDF +# Flutter PDF library -Syncfusion Flutter PDF is a feature-rich and high-performance non-UI PDF library written natively in Dart. It allows you to add robust PDF functionalities to Flutter applications. +Flutter PDF is a feature-rich and high-performance non-UI PDF library written natively in Dart. It allows you to add robust PDF functionalities to Flutter applications. ## Overview -The PDF package is a non-UI and reusable Flutter library for creating PDF reports programmatically with formatted text, images, shapes, tables, links, lists, headers, footers, and more. The library can be used in Flutter mobile and web platforms without dependency on Adobe Acrobat. The creation of a PDF follows the most popular PDF 1.7 (ISO 32000-1) and latest PDF 2.0 (ISO 32000-2) specifications. +The PDF package is a non-UI, reusable Flutter library for creating PDF reports programmatically with formatted text, images, shapes, tables, links, lists, headers, footers, and more. The library can be used to create, read, edit, and secure PDF documents in Flutter mobile and web platforms without dependency on Adobe Acrobat. The creation of a PDF follows the most popular PDF 1.7 (ISO 32000-1) and latest PDF 2.0 (ISO 32000-2) specifications. -**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or Syncfusion Community License. For more details, please check the [LICENSE](LICENSE) file. - -**Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. +**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. ![PDF Overview](https://cdn.syncfusion.com/content/images/FTControl/Flutter/Flutter-PDF-Overview.png) @@ -34,6 +32,8 @@ The PDF package is a non-UI and reusable Flutter library for creating PDF report - [Find text](#find-text) - [Encryption and decryption](#encryption-and-decryption) - [PDF conformance](#pdf-conformance) + - [PDF form](#pdf-form) + - [Digital signature](#digital-signature) - [Support and feedback](#support-and-feedback) - [About Syncfusion](#about-syncfusion) @@ -43,19 +43,21 @@ The following are the key features of Syncfusion Flutter PDF: * Create multipage PDF files from scratch. * Add Unicode and RTL text. -* Insert JPEG and PNG images to the PDF document. -* Generate table in PDF files with different styles and formats. +* Insert JPEG and PNG images in the PDF document. +* Generate tables in PDF files with different styles and formats. * Add headers and footers. -* Add different shapes to PDF file. -* Add, modify, and remove interactive elements such as bookmarks, hyperlinks and attachments. -* Add paragraph, bullets, and lists. +* Add different shapes to PDF files. +* Add paragraphs, bullets, and lists. * Open, modify, and save existing PDF files. -* Ability to encrypt and decrypt PDF files with advanced standards. +* Encrypt and decrypt PDF files with advanced standards. +* Add, modify, and remove interactive elements such as bookmarks, annotations, hyperlinks, and attachments. +* Create PDF/A-1B, PDF/A-2B, PDF/A-3B conformances. +* Digitally sign PDF documents. * Use on mobile and web platforms. ## Get the demo application -Explore the full capability of our Flutter widgets on your device by installing our sample browser application from the following app stores and view sample code in GitHub. +Explore the full capability of our Flutter widgets on your device by installing our sample browser application from the following app stores and viewing the sample code in GitHub.

@@ -171,7 +173,7 @@ final PdfLayoutResult layoutResult = PdfTextElement( page: page, bounds: Rect.fromLTWH( 0, 0, page.getClientSize().width, page.getClientSize().height), - format: PdfLayoutFormat(layoutType: PdfLayoutType.paginate)); + format: PdfLayoutFormat(layoutType: PdfLayoutType.paginate))!; // Draw the next paragraph/content. page.graphics.drawLine( PdfPen(PdfColor(255, 0, 0)), @@ -243,7 +245,7 @@ final PdfDocument document = PdfDocument(); final PdfPage page = document.pages.add(); // Create a PDF grid class to add tables. final PdfGrid grid = PdfGrid(); -// Specify the grid columns count. +// Specify the grid column count. grid.columns.add(count: 3); // Add a grid header row. final PdfGridRow headerRow = grid.headers.add(1)[0]; @@ -370,7 +372,7 @@ document.pages[0].annotations.add(PdfRectangleAnnotation( Rect.fromLTWH(0, 0, 150, 100), 'Rectangle', color: PdfColor(255, 0, 0), setAppearance: true)); //Save the document. -File('output.pdf').writeAsBytes(document.save()); +File('annotations.pdf').writeAsBytes(document.save()); //Dispose the document. document.dispose(); ``` @@ -380,7 +382,7 @@ Add the following code to load the annotation and modify it. ```dart //Load and modify the existing annotation. final PdfRectangleAnnotation rectangleAnnotation = - document.pages[0].annotations[0]; + document.pages[0].annotations[0] as PdfRectangleAnnotation; //Change the annotation text. rectangleAnnotation.text = 'Changed'; ``` @@ -402,7 +404,7 @@ bookmark.destination = PdfDestination(document.pages[1], Offset(20, 20)); //Set the bookmark color. bookmark.color = PdfColor(255, 0, 0); //Save the document. -File('output1.pdf').writeAsBytes(document.save()); +File('bookmark.pdf').writeAsBytes(document.save()); //Dispose the document. document.dispose(); ``` @@ -466,9 +468,9 @@ document.dispose(); Refer to our [documentation](https://help.syncfusion.com/flutter/pdf/working-with-text-extraction#working-with-find-text) for more details. -## Encryption and decryption +### Encryption and decryption -Encrypt new or existing PDF documents with encryption standards like 40-bit RC4, 128-bit RC4, 128-bit AES, 256-bit AES, and advanced encryption standard 256-bit AES Revision 6 (PDF 2.0) to protect documents against unauthorized access. Using this package, you can also decrypt existing encrypted documents. +Encrypt new or existing PDF documents with encryption standards like 40-bit RC4, 128-bit RC4, 128-bit AES, and 256-bit AES, and the advanced encryption standard 256-bit AES Revision 6 (PDF 2.0) to protect documents against unauthorized access. Using this package, you can also decrypt existing encrypted documents. Add the following code to encrypt an existing PDF document. @@ -488,13 +490,15 @@ security.ownerPassword = 'ownerpassword@123'; security.algorithm = PdfEncryptionAlgorithm.aesx256Bit; //Save the document. -File('output1.pdf').writeAsBytes(document.save()); +File('secured.pdf').writeAsBytes(document.save()); //Dispose the document. document.dispose(); ``` -## PDF conformance +Refer to our [documentation](https://help.syncfusion.com/flutter/pdf/working-with-security) for more details. + +### PDF conformance Using this package, we can create PDF conformance documents, such as: @@ -510,14 +514,140 @@ final PdfDocument document = PdfDocument(conformanceLevel: PdfConformanceLevel.a ..pages.add().graphics.drawString('Hello World', PdfTrueTypeFont(File('Roboto-Regular.ttf').readAsBytesSync(), 12), bounds: Rect.fromLTWH(20, 20, 200, 50), brush: PdfBrushes.black); +//Save and dispose the document. +File('conformance.pdf').writeAsBytesSync(document.save()); +document.dispose(); +``` + +Refer to our [documentation](https://help.syncfusion.com/flutter/pdf/working-with-pdf-conformance) for more details. + +### PDF form + +PDF forms provide the best way to collect information from users. Using this package, we can create, modify, fill, and flatten PDF forms. + +Add the following code to create PDF form. + +```dart +//Create a new PDF document. +PdfDocument document = PdfDocument(); + +//Create a new page to add form fields. +PdfPage page = document.pages.add(); + +//Create text box field and add to the forms collection. +document.form.fields.add(PdfTextBoxField( + page, 'firstname', Rect.fromLTWH(0, 0, 100, 20), + text: 'John')); + +//Create check box field and add to the form. +document.form.fields.add(PdfCheckBoxField( + page, 'checkbox', Rect.fromLTWH(150, 0, 30, 30), + isChecked: true)); + +//Save and dispose the document. +File('form.pdf').writeAsBytesSync(document.save()); +document.dispose(); +``` + +Add the following code to fill the existing PDF form. + +```dart +//Load the existing PDF document. +final PdfDocument document = + PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); + +//Get the form. +PdfForm form = document.form; + +//Get text box and fill value. +PdfTextBoxField name = document.form.fields[0] as PdfTextBoxField; +name.text = 'John'; + +//Get the radio button and select. +PdfRadioButtonListField gender = form.fields[1] as PdfRadioButtonListField; +gender.selectedIndex = 1; + +//Save and dispose the document. +File('output.pdf').writeAsBytesSync(document.save()); +document.dispose(); +``` + +Add the following code to flatten the existing form. + +```dart +//Load the existing PDF document. +final PdfDocument document = + PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); + +//Get the form. +PdfForm form = document.form; + +//Flatten all the form fields. +form.flattenAllFields(); + //Save and dispose the document. File('output.pdf').writeAsBytesSync(document.save()); document.dispose(); ``` +Refer to our [documentation](https://help.syncfusion.com/flutter/pdf/overview) for more details. + +### Digital signature + +PDF digital signature is the best way to protect your PDF files from being forged. Using this package, we can digitally sign a PDF document using X509 certificates (.pfx file with private key). + +Add the following code to sign the PDF document. + +```dart +//Create a new PDF document. +PdfDocument document = PdfDocument(); + +//Add a new PDF page. +PdfPage page = document.pages.add(); + +//Create signature field. +PdfSignatureField signatureField = PdfSignatureField(page, 'Signature', + bounds: Rect.fromLTWH(0, 0, 200, 50), + signature: PdfSignature( + certificate: + PdfCertificate(File('certificate.pfx').readAsBytesSync(), 'password@123') + )); + +//Add the signature field to the document. +document.form.fields.add(signatureField); + +//Save and dispose the PDF document +File('signed.pdf').writeAsBytes(document.save()); +document.dispose(); +``` +Add the following code to sign the existing PDF document. + +```dart +//Load the existing PDF document. +final PdfDocument document = + PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); + +//Get the signature field. +PdfSignatureField signatureField = + document.form.fields[0] as PdfSignatureField; + +//Get signature field and sign. +signatureField.signature = PdfSignature( + certificate: + PdfCertificate(File('certificate.pfx').readAsBytesSync(), 'password@123'), +); + +//Save and dispose the document. +File('output.pdf').writeAsBytesSync(document.save()); +document.dispose(); +``` + +Refer to our [documentation](https://help.syncfusion.com/flutter/pdf/overview) for more details. + + ## Support and feedback -* For any questions, please contact our [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident) or post them in our [community forums](https://www.syncfusion.com/forums). You can also submit a feature request or a bug alert through our [feedback portal](https://www.syncfusion.com/feedback/flutter). +* For any questions, please post them in our [community forums](https://www.syncfusion.com/forums) or contact our [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident). You can also submit a feature request or a bug alert through our [feedback portal](https://www.syncfusion.com/feedback/flutter). * To renew your subscription, click [renew](https://www.syncfusion.com/sales/products) or contact our sales team at salessupport@syncfusion.com | Toll free: 1-888-9 DOTNET. ## About Syncfusion diff --git a/packages/syncfusion_flutter_pdf/dartdoc_options.yaml b/packages/syncfusion_flutter_pdf/dartdoc_options.yaml new file mode 100644 index 000000000..2fd85ba9b --- /dev/null +++ b/packages/syncfusion_flutter_pdf/dartdoc_options.yaml @@ -0,0 +1,4 @@ +dartdoc: + ignore: + - broken-link + - missing-from-search-index \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdf/example/analysis_options.yaml b/packages/syncfusion_flutter_pdf/example/analysis_options.yaml index 615a68571..471e37e20 100644 --- a/packages/syncfusion_flutter_pdf/example/analysis_options.yaml +++ b/packages/syncfusion_flutter_pdf/example/analysis_options.yaml @@ -6,3 +6,4 @@ analyzer: lines_longer_than_80_chars: ignore avoid_as: ignore uri_does_not_exist: ignore + invalid_dependency: ignore diff --git a/packages/syncfusion_flutter_pdf/example/lib/main.dart b/packages/syncfusion_flutter_pdf/example/lib/main.dart index d053b0684..5596f7240 100644 --- a/packages/syncfusion_flutter_pdf/example/lib/main.dart +++ b/packages/syncfusion_flutter_pdf/example/lib/main.dart @@ -14,7 +14,7 @@ class CreatePdfWidget extends StatelessWidget { @override Widget build(BuildContext context) { return const MaterialApp( - home: CreatePdfStatefulWidget(title: 'Create PDF document'), + home: CreatePdfStatefulWidget(), ); } } @@ -22,10 +22,8 @@ class CreatePdfWidget extends StatelessWidget { /// Represents the PDF stateful widget class. class CreatePdfStatefulWidget extends StatefulWidget { /// Initalize the instance of the [CreatePdfStatefulWidget] class. - const CreatePdfStatefulWidget({Key key, this.title}) : super(key: key); + const CreatePdfStatefulWidget({Key? key}) : super(key: key); - /// title. - final String title; @override _CreatePdfState createState() => _CreatePdfState(); } @@ -35,18 +33,19 @@ class _CreatePdfState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(widget.title), + title: Text('Create PDF document'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - FlatButton( - child: const Text( - 'Generate PDF', - style: TextStyle(color: Colors.white), + TextButton( + child: const Text('Generate PDF'), + style: TextButton.styleFrom( + primary: Colors.white, + backgroundColor: Colors.lightBlue, + onSurface: Colors.grey, ), - color: Colors.blue, onPressed: generateInvoice, ) ], @@ -138,13 +137,13 @@ class _CreatePdfState extends State { return PdfTextElement(text: address, font: contentFont).draw( page: page, bounds: Rect.fromLTWH(30, 120, - pageSize.width - (contentSize.width + 30), pageSize.height - 120)); + pageSize.width - (contentSize.width + 30), pageSize.height - 120))!; } //Draws the grid void drawGrid(PdfPage page, PdfGrid grid, PdfLayoutResult result) { - Rect totalPriceCellBounds; - Rect quantityCellBounds; + Rect? totalPriceCellBounds; + Rect? quantityCellBounds; //Invoke the beginCellLayout event. grid.beginCellLayout = (Object sender, PdfGridBeginCellLayoutArgs args) { final PdfGrid grid = sender as PdfGrid; @@ -156,23 +155,23 @@ class _CreatePdfState extends State { }; //Draw the PDF grid and get the result. result = grid.draw( - page: page, bounds: Rect.fromLTWH(0, result.bounds.bottom + 40, 0, 0)); + page: page, bounds: Rect.fromLTWH(0, result.bounds.bottom + 40, 0, 0))!; //Draw grand total. page.graphics.drawString('Grand Total', PdfStandardFont(PdfFontFamily.helvetica, 9, style: PdfFontStyle.bold), bounds: Rect.fromLTWH( - quantityCellBounds.left, + quantityCellBounds!.left, result.bounds.bottom + 10, - quantityCellBounds.width, - quantityCellBounds.height)); + quantityCellBounds!.width, + quantityCellBounds!.height)); page.graphics.drawString(getTotalAmount(grid).toString(), PdfStandardFont(PdfFontFamily.helvetica, 9, style: PdfFontStyle.bold), bounds: Rect.fromLTWH( - totalPriceCellBounds.left, + totalPriceCellBounds!.left, result.bounds.bottom + 10, - totalPriceCellBounds.width, - totalPriceCellBounds.height)); + totalPriceCellBounds!.width, + totalPriceCellBounds!.height)); } //Draw the invoice footer data. diff --git a/packages/syncfusion_flutter_pdf/example/pubspec.yaml b/packages/syncfusion_flutter_pdf/example/pubspec.yaml index 8bc94b073..c3a9b6f3a 100644 --- a/packages/syncfusion_flutter_pdf/example/pubspec.yaml +++ b/packages/syncfusion_flutter_pdf/example/pubspec.yaml @@ -1,11 +1,15 @@ name: pdf_example description: Demo for creating a PDF file using syncfusion_flutter_pdf package. +version: 1.0.0+1 + +environment: + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter - path_provider: ^1.6.7 - open_file: ^3.0.1 + path_provider: ^2.0.1 + open_file: ^3.1.0 syncfusion_flutter_pdf: path: ../ diff --git a/packages/syncfusion_flutter_pdf/lib/pdf.dart b/packages/syncfusion_flutter_pdf/lib/pdf.dart index 6ee6dfd1f..eb036bb88 100644 --- a/packages/syncfusion_flutter_pdf/lib/pdf.dart +++ b/packages/syncfusion_flutter_pdf/lib/pdf.dart @@ -1,12 +1,14 @@ -// Support for creating PDF documents. +/// The Syncfusion Flutter PDF is a library written natively in Dart for +/// creating, reading, editing, and securing PDF files in Android, iOS, +/// and web platforms. library pdf; import 'dart:collection'; import 'dart:convert'; import 'dart:math'; import 'dart:ui'; +import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart'; -import 'package:flutter/material.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/intl.dart'; import 'package:xml/xml.dart'; @@ -135,11 +137,9 @@ part 'src/pdf/implementation/pdf_document/automatic_fields/pdf_destination_page_ part 'src/pdf/implementation/graphics/fonts/rtl/arabic_shape_renderer.dart'; part 'src/pdf/implementation/graphics/fonts/rtl/bidi.dart'; part 'src/pdf/implementation/annotations/pdf_annotation.dart'; -part 'src/pdf/implementation/annotations/pdf_link_annotation.dart'; part 'src/pdf/implementation/annotations/enum.dart'; part 'src/pdf/implementation/actions/pdf_action.dart'; part 'src/pdf/implementation/actions/pdf_uri_action.dart'; -part 'src/pdf/implementation/annotations/pdf_action_link_annotation.dart'; part 'src/pdf/implementation/annotations/pdf_uri_annotation.dart'; part 'src/pdf/implementation/annotations/pdf_text_web_link.dart'; part 'src/pdf/implementation/annotations/pdf_annotation_collection.dart'; @@ -193,6 +193,34 @@ part 'src/pdf/implementation/general/embedded_file.dart'; part 'src/pdf/implementation/general/embedded_file_specification.dart'; part 'src/pdf/implementation/general/file_specification_base.dart'; part 'src/pdf/implementation/general/embedded_file_params.dart'; +part 'src/pdf/implementation/forms/enum.dart'; +part 'src/pdf/implementation/forms/pdf_form.dart'; +part 'src/pdf/implementation/forms/pdf_field.dart'; +part 'src/pdf/implementation/forms/pdf_form_field_collection.dart'; +part 'src/pdf/implementation/forms/pdf_text_box_field.dart'; +part 'src/pdf/implementation/forms/pdf_field_painter.dart'; +part 'src/pdf/implementation/annotations/widget_annotation.dart'; +part 'src/pdf/implementation/annotations/widget_appearance.dart'; +part 'src/pdf/implementation/general/pdf_default_appearance.dart'; +part 'src/pdf/implementation/forms/pdf_check_field_base.dart'; +part 'src/pdf/implementation/annotations/appearance/pdf_appearance_state.dart'; +part 'src/pdf/implementation/annotations/appearance/pdf_extended_appearance.dart'; +part 'src/pdf/implementation/forms/pdf_check_box_field.dart'; +part 'src/pdf/implementation/forms/pdf_radio_button_item_collection.dart'; +part 'src/pdf/implementation/forms/pdf_radio_button_list_field.dart'; +part 'src/pdf/implementation/forms/pdf_radio_button_list_item.dart'; +part 'src/pdf/implementation/forms/pdf_list_field.dart'; +part 'src/pdf/implementation/forms/pdf_list_field_item.dart'; +part 'src/pdf/implementation/forms/pdf_list_field_item_collection.dart'; +part 'src/pdf/implementation/forms/pdf_combo_box_field.dart'; +part 'src/pdf/implementation/forms/pdf_list_box_field.dart'; +part 'src/pdf/implementation/forms/pdf_button_field.dart'; +part 'src/pdf/implementation/actions/pdf_annotation_action.dart'; +part 'src/pdf/implementation/actions/pdf_field_actions.dart'; +part 'src/pdf/implementation/actions/pdf_submit_action.dart'; +part 'src/pdf/implementation/forms/pdf_signature_field.dart'; +part 'src/pdf/implementation/forms/pdf_field_item.dart'; +part 'src/pdf/implementation/forms/pdf_field_item_collection.dart'; /// Compression part 'src/pdf/implementation/compression/compressed_stream_writer.dart'; @@ -215,4 +243,31 @@ part 'src/pdf/implementation/security/enum.dart'; part 'src/pdf/implementation/security/digital_signature/cryptography/aes_engine.dart'; part 'src/pdf/implementation/security/digital_signature/cryptography/aes_cipher.dart'; part 'src/pdf/implementation/security/digital_signature/cryptography/cipher_block_chaining_mode.dart'; - +part 'src/pdf/implementation/security/digital_signature/cryptography/cipher_utils.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/data_encryption.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/data_ede_algorithm.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/rc2_algorithm.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/rsa_algorithm.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/pkcs1_encoding.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/ipadding.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/buffered_block_padding_base.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/rmd_signer.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/pdf_cms_signer.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/random_array.dart'; +part 'src/pdf/implementation/security/digital_signature/cryptography/signature_utilities.dart'; +part 'src/pdf/implementation/security/digital_signature/pkcs/pfx_data.dart'; +part 'src/pdf/implementation/security/digital_signature/pkcs/password_utility.dart'; +part 'src/pdf/implementation/security/digital_signature/asn1/asn1.dart'; +part 'src/pdf/implementation/security/digital_signature/asn1/asn1_stream.dart'; +part 'src/pdf/implementation/security/digital_signature/asn1/asn1_parser.dart'; +part 'src/pdf/implementation/security/digital_signature/asn1/ber.dart'; +part 'src/pdf/implementation/security/digital_signature/asn1/der.dart'; +part 'src/pdf/implementation/security/digital_signature/asn1/enum.dart'; +part 'src/pdf/implementation/security/digital_signature/x509/x509_certificates.dart'; +part 'src/pdf/implementation/security/digital_signature/x509/x509_name.dart'; +part 'src/pdf/implementation/security/digital_signature/x509/x509_time.dart'; +part 'src/pdf/implementation/security/digital_signature/pdf_certificate.dart'; +part 'src/pdf/implementation/security/digital_signature/pdf_pkcs_certificate.dart'; +part 'src/pdf/implementation/security/digital_signature/pdf_signature.dart'; +part 'src/pdf/implementation/security/digital_signature/pdf_signature_dictionary.dart'; +part 'src/pdf/implementation/security/digital_signature/pdf_external_signer.dart'; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_action.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_action.dart index 1f021d23b..47998917a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_action.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_action.dart @@ -7,19 +7,18 @@ class PdfAction implements _IPdfWrapper { _initialize(); } // fields - PdfAction _action; + PdfAction? _action; final _PdfDictionary _dictionary = _PdfDictionary(); // properties /// Gets the next action /// to be performed after the action represented by this instance. - PdfAction get next => _action; + PdfAction? get next => _action; /// Sets the next action /// to be performed after the action represented by this instance. - set next(PdfAction value) { - ArgumentError.checkNotNull(value, 'next'); - if (_action != value) { + set next(PdfAction? value) { + if (value != null && _action != value) { _action = value; _dictionary._setArray(_DictionaryProperties.next, <_IPdfPrimitive>[_PdfReferenceHolder(_action)]); @@ -34,5 +33,5 @@ class PdfAction implements _IPdfWrapper { } @override - _IPdfPrimitive _element; + _IPdfPrimitive? _element; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_annotation_action.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_annotation_action.dart new file mode 100644 index 000000000..66db77fac --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_annotation_action.dart @@ -0,0 +1,122 @@ +part of pdf; + +/// Represents additional actions of the annotations. +class PdfAnnotationActions implements _IPdfWrapper { + //Constructor + /// Initializes a new instance of the [PdfAnnotationActions] class. + PdfAnnotationActions( + {PdfAction? mouseEnter, + PdfAction? mouseLeave, + PdfAction? mouseUp, + PdfAction? mouseDown, + PdfAction? gotFocus, + PdfAction? lostFocus}) { + _initValues( + mouseEnter, mouseLeave, mouseUp, mouseDown, gotFocus, lostFocus); + } + + PdfAnnotationActions._loaded(_PdfDictionary? dictionary) { + _dictionary = dictionary; + } + + //Fields + _PdfDictionary? _dictionary = _PdfDictionary(); + PdfAction? _mouseEnter; + PdfAction? _mouseLeave; + PdfAction? _mouseDown; + PdfAction? _mouseUp; + PdfAction? _gotFocus; + PdfAction? _lostFocus; + + //Properties + /// Gets or sets the action to be performed when the cursor + /// enters the annotation’s + PdfAction? get mouseEnter => _mouseEnter; + set mouseEnter(PdfAction? value) { + if (value != null && _mouseEnter != value) { + _mouseEnter = value; + _dictionary!.setProperty(_DictionaryProperties.e, _mouseEnter); + } + } + + /// Gets or sets the action to be performed when the cursor + /// exits the annotation’s + PdfAction? get mouseLeave => _mouseLeave; + set mouseLeave(PdfAction? value) { + if (value != null && _mouseLeave != value) { + _mouseLeave = value; + _dictionary!.setProperty(_DictionaryProperties.x, _mouseLeave); + } + } + + /// Gets or sets the action to be performed when the mouse button is pressed + /// inside the annotation’s active area. + PdfAction? get mouseDown => _mouseDown; + set mouseDown(PdfAction? value) { + if (value != null && _mouseDown != value) { + _mouseDown = value; + _dictionary!.setProperty(_DictionaryProperties.d, _mouseDown); + } + } + + /// Gets or sets the action to be performed when the mouse button is released + PdfAction? get mouseUp => _mouseUp; + set mouseUp(PdfAction? value) { + if (value != null && _mouseUp != value) { + _mouseUp = value; + _dictionary!.setProperty(_DictionaryProperties.u, _mouseUp); + } + } + + /// Gets or sets the action to be performed when the annotation receives + /// the input focus. + PdfAction? get gotFocus => _gotFocus; + set gotFocus(PdfAction? value) { + if (value != null && _gotFocus != value) { + _gotFocus = value; + _dictionary!.setProperty(_DictionaryProperties.fo, _gotFocus); + } + } + + /// Gets or sets the action to be performed when the annotation loses the + /// input focus. + PdfAction? get lostFocus => _lostFocus; + set lostFocus(PdfAction? value) { + if (value != null && _lostFocus != value) { + _lostFocus = value; + _dictionary!.setProperty(_DictionaryProperties.bl, _lostFocus); + } + } + + // Implementation + void _initValues(PdfAction? mEnter, PdfAction? mLeave, PdfAction? mUp, + PdfAction? mDown, PdfAction? gotF, PdfAction? lostF) { + if (mEnter != null) { + mouseEnter = mEnter; + } + if (mLeave != null) { + mouseLeave = mLeave; + } + if (mUp != null) { + mouseUp = mUp; + } + if (mDown != null) { + mouseDown = mDown; + } + if (gotF != null) { + gotFocus = gotF; + } + if (lostF != null) { + lostFocus = lostF; + } + } + + @override + _IPdfPrimitive? get _element => _dictionary; + + @override + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + _element = value; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_field_actions.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_field_actions.dart new file mode 100644 index 000000000..a4039b5a3 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_field_actions.dart @@ -0,0 +1,272 @@ +part of pdf; + +/// Represents actions to be performed as response to field events. +class PdfFieldActions implements _IPdfWrapper { + //Constructor + /// Initializes a new instance of the [PdfFieldActions] class with + /// the [PdfAnnotationActions] + PdfFieldActions(PdfAnnotationActions annotationActions, + {PdfJavaScriptAction? keyPressed, + PdfJavaScriptAction? format, + PdfJavaScriptAction? validate, + PdfJavaScriptAction? calculate}) { + _annotationActions = annotationActions; + _initValues(keyPressed, format, validate, calculate); + } + + PdfFieldActions._loaded(_PdfDictionary dictionary) { + _dictionary = dictionary; + _annotationActions = PdfAnnotationActions._loaded(dictionary); + } + + //Fields + _PdfDictionary? _dictionary = _PdfDictionary(); + late PdfAnnotationActions _annotationActions; + PdfJavaScriptAction? _keyPressed; + PdfJavaScriptAction? _format; + PdfJavaScriptAction? _validate; + PdfJavaScriptAction? _calculate; + bool _isChanged = false; + + //Properties + /// Gets or sets the JavaScript action to be performed when + /// the user types a keystroke + PdfJavaScriptAction? get keyPressed => _keyPressed; + set keyPressed(PdfJavaScriptAction? value) { + if (value != null && _keyPressed != value) { + _keyPressed = value; + _dictionary!.setProperty(_DictionaryProperties.k, _keyPressed); + _isChanged = true; + } + } + + /// Gets or sets the JavaScript action to be performed before + /// the field is formatted + PdfJavaScriptAction? get format => _format; + set format(PdfJavaScriptAction? value) { + if (value != null && _format != value) { + _format = value; + _dictionary!.setProperty(_DictionaryProperties.f, _format); + _isChanged = true; + } + } + + /// Gets or sets the JavaScript action to be performed when + /// the field’s value is changed. + PdfJavaScriptAction? get validate => _validate; + set validate(PdfJavaScriptAction? value) { + if (value != null && _validate != value) { + _validate = value; + _dictionary!.setProperty(_DictionaryProperties.v, _validate); + _isChanged = true; + } + } + + /// Gets or sets the JavaScript action to be performed to recalculate + /// the value of this field when that of another field changes. + PdfJavaScriptAction? get calculate => _calculate; + set calculate(PdfJavaScriptAction? value) { + if (value != null && _calculate != value) { + _calculate = value; + _dictionary!.setProperty(_DictionaryProperties.c, _calculate); + _isChanged = true; + } + } + + /// Gets or sets the action to be performed when the mouse cursor enters + /// the fields’s area. + PdfAction? get mouseEnter => _annotationActions.mouseEnter; + set mouseEnter(PdfAction? value) { + if (value != null) { + _annotationActions.mouseEnter = value; + _isChanged = true; + } + } + + /// Gets or sets the action to be performed when the cursor exits + /// the fields’s area. + PdfAction? get mouseLeave => _annotationActions.mouseLeave; + set mouseLeave(PdfAction? value) { + if (value != null) { + _annotationActions.mouseLeave = value; + _isChanged = true; + } + } + + /// Gets or sets the action to be performed when the mouse button is released + /// inside the field’s area. + PdfAction? get mouseUp => _annotationActions.mouseUp; + set mouseUp(PdfAction? value) { + if (value != null) { + _annotationActions.mouseUp = value; + _isChanged = true; + } + } + + /// Gets or sets the action to be performed when the mouse button is pressed inside the + /// field’s area. + PdfAction? get mouseDown => _annotationActions.mouseDown; + set mouseDown(PdfAction? value) { + if (value != null) { + _annotationActions.mouseDown = value; + _isChanged = true; + } + } + + /// Gets or sets the action to be performed when the field receives the + /// input focus. + PdfAction? get gotFocus => _annotationActions.gotFocus; + set gotFocus(PdfAction? value) { + if (value != null) { + _annotationActions.gotFocus = value; + _isChanged = true; + } + } + + /// Gets or sets the action to be performed when the field loses the + /// input focus. + PdfAction? get lostFocus => _annotationActions.lostFocus; + set lostFocus(PdfAction? value) { + if (value != null) { + _annotationActions.lostFocus = value; + _isChanged = true; + } + } + + // Implementation + void _initValues( + PdfJavaScriptAction? keyPress, + PdfJavaScriptAction? fmt, + PdfJavaScriptAction? val, + PdfJavaScriptAction? cal, + ) { + if (keyPress != null) { + keyPressed = keyPress; + } + if (fmt != null) { + format = fmt; + } + if (val != null) { + validate = val; + } + if (cal != null) { + calculate = cal; + } + } + + @override + _IPdfPrimitive? get _element => _dictionary; + + @override + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + _element = value; + } +} + +/// Represents an java script action in PDF document. +class PdfJavaScriptAction extends PdfAction { + //Constructor + /// Initializes a new instance of the [PdfJavaScriptAction] class with + /// the java script code + PdfJavaScriptAction(String javaScript) : super._() { + _initValue(javaScript); + } + + //Fields + String _javaScript = ''; + + //Properties + /// Gets or sets the javascript code to be executed when + /// this action is executed. + String get javaScript => _javaScript; + set javaScript(String value) { + if (_javaScript != value) { + _javaScript = value; + _dictionary._setString(_DictionaryProperties.js, _javaScript); + } + } + + //Implementation + @override + void _initialize() { + super._initialize(); + _dictionary.setProperty( + _DictionaryProperties.s, _PdfName(_DictionaryProperties.javaScript)); + _dictionary.setProperty(_DictionaryProperties.js, _PdfString(_javaScript)); + } + + void _initValue(String js) { + javaScript = js; + } +} + +/// Represents the action on form fields. +class PdfFormAction extends PdfAction { + //Constrcutor + /// Initializes a new instance of the [PdfFormAction] class. + PdfFormAction._() : super._(); + + //Fields + PdfFormFieldCollection? _fields; + + /// Gets or sets a value indicating whether fields contained in the fields + /// collection will be included for resetting or submitting. + /// + /// If the [include] property is true, only the fields in this collection + /// will be reset or submitted. + /// If the [include] property is false, the fields in this collection + /// are not reset or submitted and only the remaining form fields are + /// reset or submitted. + /// If the collection is empty, then all the form fields are reset + /// and the [include] property is ignored. + bool include = false; + + ///Gets the fields. + PdfFormFieldCollection get fields { + if (_fields == null) { + _fields = PdfFormFieldCollection._(); + _dictionary.setProperty(_DictionaryProperties.fields, _fields); + } + _fields!._isAction = true; + return _fields!; + } +} + +/// Represents PDF form's reset action,this action allows a user to reset +/// the form fields to their default values. +class PdfResetAction extends PdfFormAction { + //Constructor + /// Initializes a new instance of the [PdfResetAction] class. + PdfResetAction({bool? include, List? fields}) : super._() { + _initValues(include, fields); + } + + //Properties + @override + bool get include => super.include; + set include(bool value) { + if (super.include != value) { + super.include = value; + _dictionary._setNumber( + _DictionaryProperties.flags, super.include ? 0 : 1); + } + } + + //Implementation + @override + void _initialize() { + super._initialize(); + _dictionary.setProperty( + _DictionaryProperties.s, _PdfName(_DictionaryProperties.resetForm)); + } + + void _initValues(bool? initInclude, List? field) { + if (initInclude != null) { + include = initInclude; + } + if (field != null) { + field.forEach((f) => fields.add(f)); + } + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_submit_action.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_submit_action.dart new file mode 100644 index 000000000..e4d3776cb --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_submit_action.dart @@ -0,0 +1,294 @@ +part of pdf; + +/// Represents PDF form's submit action.submit action allows submission of data +/// that is entered in the PDF form +class PdfSubmitAction extends PdfFormAction { + //Constructor + /// Initializes a new instance of the [PdfSubmitAction] class with + /// URL to submit the form data + PdfSubmitAction(String url, + {HttpMethod httpMethod = HttpMethod.post, + SubmitDataFormat dataFormat = SubmitDataFormat.fdf, + bool canonicalDateTimeFormat = false, + bool submitCoordinates = false, + bool includeNoValueFields = false, + bool includeIncrementalUpdates = false, + bool includeAnnotations = false, + bool excludeNonUserAnnotations = false, + bool embedForm = false, + bool include = false, + List? fields}) + : super._() { + if (url.isEmpty) { + ArgumentError.value('The URL can\'t be an empty string.'); + } + _url = url; + _dictionary.setProperty(_DictionaryProperties.f, _PdfString(_url)); + _initValues( + httpMethod = HttpMethod.post, + dataFormat, + canonicalDateTimeFormat, + submitCoordinates, + includeNoValueFields, + includeIncrementalUpdates, + includeAnnotations, + excludeNonUserAnnotations, + embedForm, + include, + fields); + } + + //Fields + String _url = ''; + HttpMethod _httpMethod = HttpMethod.post; + SubmitDataFormat _dataFormat = SubmitDataFormat.fdf; + List _flags = []; + bool _canonicalDateTimeFormat = false; + bool _submitCoordinates = false; + bool _includeNoValueFields = false; + bool _includeIncrementalUpdates = false; + bool _includeAnnotations = false; + bool _excludeNonUserAnnotations = false; + bool _embedForm = false; + + //Properties + /// Gets an Url address where the data should be transferred. + String get url => _url; + + /// Gets or sets the [SubmitDataFormat]. + SubmitDataFormat get dataFormat => _dataFormat; + set dataFormat(SubmitDataFormat value) { + if (_dataFormat != value) { + _dataFormat = value; + switch (_dataFormat) { + case SubmitDataFormat.pdf: + _flags.add(PdfSubmitFormFlags.submitPdf); + break; + case SubmitDataFormat.xfdf: + _flags.add(PdfSubmitFormFlags.xfdf); + break; + case SubmitDataFormat.html: + _flags.add(PdfSubmitFormFlags.exportFormat); + break; + case SubmitDataFormat.fdf: + break; + } + } + } + + /// Gets or sets the HTTP method. + HttpMethod get httpMethod => _httpMethod; + set httpMethod(HttpMethod value) { + if (_httpMethod != value) { + _httpMethod = value; + if (_httpMethod == HttpMethod.getHttp) { + _flags.add(PdfSubmitFormFlags.getMethod); + } else { + _flags.remove(PdfSubmitFormFlags.getMethod); + } + } + } + + /// If set, any submitted field values representing dates are converted to + /// the standard format. + bool get canonicalDateTimeFormat => _canonicalDateTimeFormat; + set canonicalDateTimeFormat(bool value) { + if (_canonicalDateTimeFormat != value) { + _canonicalDateTimeFormat = value; + + if (_canonicalDateTimeFormat) { + _flags.add(PdfSubmitFormFlags.canonicalFormat); + } else { + _flags.remove(PdfSubmitFormFlags.canonicalFormat); + } + } + } + + /// Gets or sets a value indicating whether to submit mouse pointer coordinates. + bool get submitCoordinates => _submitCoordinates; + set submitCoordinates(bool value) { + if (_submitCoordinates != value) { + _submitCoordinates = value; + + if (_submitCoordinates) { + _flags.add(PdfSubmitFormFlags.submitCoordinates); + } else { + _flags.remove(PdfSubmitFormFlags.submitCoordinates); + } + } + } + + /// Gets or sets a value indicating whether to submit fields without value. + bool get includeNoValueFields => _includeNoValueFields; + set includeNoValueFields(bool value) { + if (_includeNoValueFields != value) { + _includeNoValueFields = value; + + if (_includeNoValueFields) { + _flags.add(PdfSubmitFormFlags.includeNoValueFields); + } else { + _flags.remove(PdfSubmitFormFlags.includeNoValueFields); + } + } + } + + /// Gets or sets a value indicating whether to submit + /// form's incremental updates. + bool get includeIncrementalUpdates => _includeIncrementalUpdates; + set includeIncrementalUpdates(bool value) { + if (_includeIncrementalUpdates != value) { + _includeIncrementalUpdates = value; + + if (_includeIncrementalUpdates) { + _flags.add(PdfSubmitFormFlags.includeAppendSaves); + } else { + _flags.remove(PdfSubmitFormFlags.includeAppendSaves); + } + } + } + + /// Gets or sets a value indicating whether to submit annotations. + bool get includeAnnotations => _includeAnnotations; + set includeAnnotations(bool value) { + if (_includeAnnotations != value) { + _includeAnnotations = value; + + if (_includeAnnotations) { + _flags.add(PdfSubmitFormFlags.includeAnnotations); + } else { + _flags.remove(PdfSubmitFormFlags.includeAnnotations); + } + } + } + + /// Gets or sets a value indicating whether to exclude non user annotations + /// form submit data stream. + bool get excludeNonUserAnnotations => _excludeNonUserAnnotations; + set excludeNonUserAnnotations(bool value) { + if (_excludeNonUserAnnotations != value) { + _excludeNonUserAnnotations = value; + + if (_excludeNonUserAnnotations) { + _flags.add(PdfSubmitFormFlags.exclNonUserAnnots); + } else { + _flags.remove(PdfSubmitFormFlags.exclNonUserAnnots); + } + } + } + + /// Gets or sets a value indicating whether to include form + /// to submit data stream. + bool get embedForm => _embedForm; + set embedForm(bool value) { + if (_embedForm != value) { + _embedForm = value; + if (_embedForm) { + _flags.add(PdfSubmitFormFlags.embedForm); + } else { + _flags.remove(PdfSubmitFormFlags.embedForm); + } + } + } + + @override + bool get include => super.include; + set include(bool value) { + if (super.include != value) { + super.include = value; + if (super.include) { + _flags.remove(PdfSubmitFormFlags.includeExclude); + } else { + _flags.add(PdfSubmitFormFlags.includeExclude); + } + } + } + + //Implementation + @override + void _initialize() { + super._initialize(); + _dictionary._beginSave = _dictionaryBeginSave; + _dictionary.setProperty( + _DictionaryProperties.s, _PdfName(_DictionaryProperties.submitForm)); + } + + void _dictionaryBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { + _dictionary.setProperty( + _DictionaryProperties.flags, _PdfNumber(_getFlagValue(_flags))); + } + + void _initValues( + HttpMethod http, + SubmitDataFormat format, + bool canonicalDateTime, + bool submit, + bool includeNoValue, + bool includeIncremental, + bool includeAnnot, + bool excludeNonUserAnnot, + bool embed, + bool initInclude, + List? field) { + httpMethod = http; + dataFormat = format; + canonicalDateTimeFormat = canonicalDateTime; + submitCoordinates = submit; + includeNoValueFields = includeNoValue; + includeIncrementalUpdates = includeIncremental; + includeAnnotations = includeAnnot; + excludeNonUserAnnotations = excludeNonUserAnnot; + embedForm = embed; + include = initInclude; + if (field != null) { + field.forEach((f) => fields.add(f)); + } + } + + int _getFlagValue(List sumbitFlags) { + int result = 0; + for (final PdfSubmitFormFlags sumbitFlag in sumbitFlags) { + switch (sumbitFlag) { + case PdfSubmitFormFlags.includeExclude: + result = result + 1; + break; + case PdfSubmitFormFlags.includeNoValueFields: + result = result + 2; + break; + case PdfSubmitFormFlags.exportFormat: + result = result + 4; + break; + case PdfSubmitFormFlags.getMethod: + result = result + 8; + break; + case PdfSubmitFormFlags.submitCoordinates: + result = result + 16; + break; + case PdfSubmitFormFlags.xfdf: + result = result + 32; + break; + case PdfSubmitFormFlags.includeAppendSaves: + result = result + 64; + break; + case PdfSubmitFormFlags.includeAnnotations: + result = result + 128; + break; + case PdfSubmitFormFlags.submitPdf: + result = result + 256; + break; + case PdfSubmitFormFlags.canonicalFormat: + result = result + 512; + break; + case PdfSubmitFormFlags.exclNonUserAnnots: + result = result + 1024; + break; + case PdfSubmitFormFlags.exclFKey: + result = result + 2048; + break; + case PdfSubmitFormFlags.embedForm: + result = result + 4096; + break; + } + } + return result; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_uri_action.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_uri_action.dart index 359960c1c..3bffbd783 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_uri_action.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/actions/pdf_uri_action.dart @@ -6,7 +6,7 @@ class PdfUriAction extends PdfAction { /// Initializes a new instance of the [PdfUriAction] class. /// /// [uri] - the unique resource identifier. - PdfUriAction([String uri]) : super._() { + PdfUriAction([String? uri]) : super._() { if (uri != null) { this.uri = uri; } @@ -22,7 +22,6 @@ class PdfUriAction extends PdfAction { /// Sets the unique resource identifier. set uri(String value) { - ArgumentError.checkNotNull(value, 'uri'); _uri = value; _dictionary._setString(_DictionaryProperties.uri, _uri); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/appearance/pdf_appearance_state.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/appearance/pdf_appearance_state.dart new file mode 100644 index 000000000..0af926db9 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/appearance/pdf_appearance_state.dart @@ -0,0 +1,55 @@ +part of pdf; + +/// Represents the states of an annotation's appearance. +class _PdfAppearanceState implements _IPdfWrapper { + //Constructor + /// Initializes a new instance of the [PdfAppearanceState] class. + _PdfAppearanceState() : super() { + _dictionary._beginSave = _dictionaryBeginSave; + } + + //Fields + final _PdfDictionary _dictionary = _PdfDictionary(); + PdfTemplate? _on; + PdfTemplate? _off; + String _onMappingName = _DictionaryProperties.yes; + static const String _offMappingName = _DictionaryProperties.off; + + //Properties + /// Gets the active state template. + PdfTemplate? get activate => _on; + + /// Sets the active state template. + set activate(PdfTemplate? value) { + if (value != _on) { + _on = value; + } + } + + /// Gets or sets the inactive state. + PdfTemplate? get off => _off; + set off(PdfTemplate? value) { + if (value != _off) { + _off = value; + } + } + + //Implementation + void _dictionaryBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { + if (_on != null) { + _dictionary.setProperty(_onMappingName, _PdfReferenceHolder(_on)); + } + if (_off != null) { + _dictionary.setProperty(_offMappingName, _PdfReferenceHolder(_off)); + } + } + + @override + _IPdfPrimitive get _element => _dictionary; + + @override + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + _element = value; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/appearance/pdf_extended_appearance.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/appearance/pdf_extended_appearance.dart new file mode 100644 index 000000000..02acdcdae --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/appearance/pdf_extended_appearance.dart @@ -0,0 +1,55 @@ +part of pdf; + +/// Represents extended appearance of the annotation. It has two states such as On state and Off state. +class _PdfExtendedAppearance implements _IPdfWrapper { + //Constructor + /// Initializes a new instance of the [PdfExtendedAppearance] class. + _PdfExtendedAppearance() : super(); + + //Fields + _PdfAppearanceState? _normal; + _PdfAppearanceState? _pressed; + _PdfAppearanceState? _mouseHover; + final _PdfDictionary _dictionary = _PdfDictionary(); + + //Properties + /// Gets the normal appearance of the annotation. + _PdfAppearanceState get normal { + if (_normal == null) { + _normal = _PdfAppearanceState(); + _dictionary.setProperty( + _DictionaryProperties.n, _PdfReferenceHolder(_normal)); + } + return _normal!; + } + + /// Gets the appearance when mouse is hovered. + _PdfAppearanceState get mouseHover { + if (_mouseHover == null) { + _mouseHover = _PdfAppearanceState(); + _dictionary.setProperty( + _DictionaryProperties.r, _PdfReferenceHolder(_mouseHover)); + } + return _mouseHover!; + } + + /// Gets the pressed state annotation. + _PdfAppearanceState get pressed { + if (_pressed == null) { + _pressed = _PdfAppearanceState(); + _dictionary.setProperty( + _DictionaryProperties.d, _PdfReferenceHolder(_pressed)); + } + return _pressed!; + } + + //Implementation + @override + _IPdfPrimitive get _element => _dictionary; + + @override + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + _element = value; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/enum.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/enum.dart index be6ec6486..5cc6cb35c 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/enum.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/enum.dart @@ -45,6 +45,9 @@ enum _PdfAnnotationTypes { /// PolygonAnnotation type. polygonAnnotation, + /// WidgetAnnotation type + widgetAnnotation, + /// No annotation. noAnnotation } @@ -122,3 +125,112 @@ enum PdfLineEndingStyle { /// Indicates Slash slash } + +/// Specifies the available data formats for submitting the form data. +enum PdfSubmitFormFlags { + /// If clear, the Fields array specifies which fields to + /// include in the submission. (All descendants of the specified fields in + /// the field hierarchy are submitted as well.) + /// If set, the Fields array tells which fields to exclude. All fields in the + /// document’s interactive form are submitted except those listed in the + /// Fields array and those whose NoExport flag. + includeExclude, + + /// If set, all fields designated by the Fields array and the Include/ + /// Exclude flag are submitted, regardless of whether they have a value. + /// For fields without a value, only the + /// field name is transmitted. + + includeNoValueFields, + + /// Meaningful only if the SubmitPDF and XFDF flags are clear. If set, + /// field names and values are submitted in HTML Form format. If + /// clear, they are submitted in Forms Data Format + exportFormat, + + /// If set, field names and values are submitted using an HTTP GET + /// request. If clear, they are submitted using a POST request. This flag + /// is meaningful only when the ExportFormat flag is set; if ExportFormat + /// is clear, this flag must also be clear. + getMethod, + + /// If set, the coordinates of the mouse click that caused the submitform + /// action are transmitted as part of the form data. The coordinate + /// values are relative to the upper-left corner of the field’s widget annotation + /// rectangle. + submitCoordinates, + + /// Meaningful only if the SubmitPDF flags are clear. If set, + /// field names and values are submitted as XML Forms Data Format. + xfdf, + + /// Meaningful only when the form is being submitted in + /// Forms Data Format (that is, when both the XFDF and ExportFormat + /// flags are clear). If set, the submitted FDF file includes the contents + /// of all incremental updates to the underlying PDF document, + /// as contained in the Differences entry in the FDF dictionary. + /// If clear, the incremental updates are not included. + includeAppendSaves, + + /// Meaningful only when the form is being submitted in + /// Forms Data Format (that is, when both the XFDF and ExportFormat + /// flags are clear). If set, the submitted FDF file includes all markup + /// annotations in the underlying PDF document. + /// If clear, markup annotations are not included. + includeAnnotations, + + /// If set, the document is submitted as PDF, using the + /// MIME content type application/pdf (described in Internet RFC + /// 2045, Multipurpose Internet Mail Extensions (MIME), Part One: + /// Format of Internet Message Bodies; see the Bibliography). If set, all + /// other flags are ignored except GetMethod. + submitPdf, + + /// If set, any submitted field values representing dates are + /// converted to the standard format described. + canonicalFormat, + + /// Meaningful only when the form is being submitted in + /// Forms Data Format (that is, when both the XFDF and + /// ExportFormat flags are clear) and the IncludeAnnotations flag is + /// set. If set, it includes only those markup annotations whose T entry + /// matches the name of the current user, as determined + /// by the remote server to which the form is being submitted. + exclNonUserAnnots, + + /// Meaningful only when the form is being submitted in + /// Forms Data Format (that is, when both the XFDF and ExportFormat + /// flags are clear). If set, the submitted FDF excludes the F entry. + exclFKey, + + /// Meaningful only when the form is being submitted in + /// Forms Data Format (that is, when both the XFDF and ExportFormat + /// flags are clear). If set, the F entry of the submitted FDF is a file + /// specification containing an embedded file stream representing the + /// PDF file from which the FDF is being submitted. + embedForm +} + +/// Specifies the enumeration of submit data formats. +enum SubmitDataFormat { + /// Data should be transmitted as Html. + html, + + /// Data should be transmitted as Pdf. + pdf, + + /// Data should be transmitted as Forms Data Format. + fdf, + + /// Data should be transmitted as XML Forms Data Format. + xfdf +} + +/// Specifies Http request method. +enum HttpMethod { + /// Data submitted using Http Get method. + getHttp, + + /// Data submitted using Http Post method. + post +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_action_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_action_annotation.dart index 1ab5d36c8..5f18c72ef 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_action_annotation.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_action_annotation.dart @@ -1,5 +1,119 @@ part of pdf; +/// Represents the base class for the link annotations. +abstract class PdfLinkAnnotation extends PdfAnnotation { + // constructor + /// Initializes new instance of + /// [PdfLinkAnnotation] class with specified bounds. + PdfLinkAnnotation(Rect bounds) : super._(bounds: bounds); + + PdfLinkAnnotation._(_PdfDictionary dictionary, _PdfCrossTable crossTable) + : super._internal(dictionary, crossTable); + + // fields + PdfHighlightMode _highlightMode = PdfHighlightMode.noHighlighting; + + //properties + /// Gets or sets the highlight mode of the link annotation. + /// ```dart + /// //Create a new Pdf document + /// PdfDocument document = PdfDocument(); + /// //Create document link annotation and add to the PDF page. + /// document.pages.add() + /// ..annotations.add(PdfDocumentLinkAnnotation(Rect.fromLTWH(10, 40, 30, 30), + /// PdfDestination(document.pages.add(), Offset(10, 0))) + /// ..highlightMode = PdfHighlightMode.outline); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfHighlightMode get highlightMode => + _isLoadedAnnotation ? _obtainHighlightMode() : _highlightMode; + set highlightMode(PdfHighlightMode value) { + _highlightMode = value; + final String? mode = _getHighlightMode(_highlightMode); + _dictionary.._setName(_PdfName(_DictionaryProperties.h), mode); + } + + // implementation + @override + void _initialize() { + super._initialize(); + _dictionary.setProperty(_PdfName(_DictionaryProperties.subtype), + _PdfName(_DictionaryProperties.link)); + } + + String _getHighlightMode(PdfHighlightMode mode) { + String hightlightMode = 'N'; + switch (mode) { + case PdfHighlightMode.invert: + hightlightMode = 'I'; + break; + case PdfHighlightMode.noHighlighting: + hightlightMode = 'N'; + break; + case PdfHighlightMode.outline: + hightlightMode = 'O'; + break; + case PdfHighlightMode.push: + hightlightMode = 'P'; + break; + } + return hightlightMode; + } + + PdfHighlightMode _obtainHighlightMode() { + PdfHighlightMode mode = PdfHighlightMode.noHighlighting; + if (_dictionary.containsKey(_DictionaryProperties.h)) { + final _PdfName name = _dictionary[_DictionaryProperties.h] as _PdfName; + switch (name._name) { + case 'I': + mode = PdfHighlightMode.invert; + break; + case 'N': + mode = PdfHighlightMode.noHighlighting; + break; + case 'O': + mode = PdfHighlightMode.outline; + break; + case 'P': + mode = PdfHighlightMode.push; + break; + } + } + return mode; + } +} + +/// Represents base class for link annotations with associated action. +abstract class PdfActionLinkAnnotation extends PdfLinkAnnotation { + // constructor + /// Initializes a new instance of the [PdfActionLinkAnnotation] + /// class with specified bounds and action to be performed. + PdfActionLinkAnnotation(Rect bounds, [PdfAction? action]) : super(bounds) { + if (action != null) { + _action = action; + } + } + + PdfActionLinkAnnotation._( + _PdfDictionary dictionary, _PdfCrossTable crossTable) + : super._(dictionary, crossTable); + + // fields + PdfAction? _action; + + // properties + /// Gets or sets the action for the link annotation. + PdfAction? get action => _action; + set action(PdfAction? value) { + if (value != null) { + _action = value; + } + } +} + /// Represents the annotation with associated action. class PdfActionAnnotation extends PdfActionLinkAnnotation { // constructor @@ -7,14 +121,13 @@ class PdfActionAnnotation extends PdfActionLinkAnnotation { /// [PdfActionAnnotation] class with specified bounds and action. PdfActionAnnotation(Rect bounds, PdfAction action) : super(bounds, action); - // implementation - @override void _save() { super._save(); - _dictionary.setProperty(_PdfName(_DictionaryProperties.a), action._element); + _dictionary.setProperty( + _PdfName(_DictionaryProperties.a), action!._element); } @override - _IPdfPrimitive _element; + _IPdfPrimitive? _element; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_action_link_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_action_link_annotation.dart deleted file mode 100644 index b87249d29..000000000 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_action_link_annotation.dart +++ /dev/null @@ -1,30 +0,0 @@ -part of pdf; - -/// Represents base class for link annotations with associated action. -abstract class PdfActionLinkAnnotation extends PdfLinkAnnotation { - // constructor - /// Initializes a new instance of the [PdfActionLinkAnnotation] - /// class with specified bounds and action to be performed. - PdfActionLinkAnnotation(Rect bounds, [PdfAction action]) : super(bounds) { - if (action != null) { - this.action = action; - } - } - - PdfActionLinkAnnotation._( - _PdfDictionary dictionary, _PdfCrossTable crossTable) - : super._(dictionary, crossTable); - - // fields - PdfAction _action; - - // properties - /// Gets the action for the link annotation. - PdfAction get action => _action; - - /// Sets the action for the link annotation. - set action(PdfAction value) { - ArgumentError.checkNotNull(value, 'action'); - _action = value; - } -} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation.dart index 76fe6915e..a9ee94684 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation.dart @@ -4,91 +4,79 @@ part of pdf; abstract class PdfAnnotation implements _IPdfWrapper { // constructor PdfAnnotation._( - {PdfPage page, - String text, - Rect bounds, - PdfAnnotationBorder border, - PdfColor color, - PdfColor innerColor, - String author, - double opacity, - String subject, - DateTime modifiedDate, - bool setAppearance, - bool flatten}) { + {PdfPage? page, + String? text, + Rect? bounds, + PdfAnnotationBorder? border, + PdfColor? color, + PdfColor? innerColor, + String? author, + double? opacity, + String? subject, + DateTime? modifiedDate, + bool? setAppearance}) { _initialize(); - _initializeAnnotationProperties( - page, - text, - bounds, - border, - color, - innerColor, - author, - opacity, - subject, - modifiedDate, - setAppearance, - flatten); + _initializeAnnotationProperties(page, text, bounds, border, color, + innerColor, author, opacity, subject, modifiedDate, setAppearance); } PdfAnnotation._internal( _PdfDictionary dictionary, _PdfCrossTable crossTable) { - ArgumentError.checkNotNull(dictionary, 'dictionary'); - ArgumentError.checkNotNull(crossTable, 'crossTable'); _dictionary = dictionary; _crossTable = crossTable; _isLoadedAnnotation = true; - _PdfName name; + _PdfName? name; if (dictionary.containsKey(_DictionaryProperties.subtype)) { - name = dictionary._items[_PdfName(_DictionaryProperties.subtype)] - as _PdfName; + name = dictionary._items![_PdfName(_DictionaryProperties.subtype)] + as _PdfName?; } if (name != null) { if (name._name == _DictionaryProperties.circle || name._name == _DictionaryProperties.square || name._name == _DictionaryProperties.line || name._name == _DictionaryProperties.polygon) { - crossTable._document._catalog._beginSave = _dictionaryBeginSave; - crossTable._document._catalog.modify(); + crossTable._document!._catalog._beginSave = _dictionaryBeginSave; + crossTable._document!._catalog.modify(); } } } // fields - PdfPage _page; - String _text = ''; + PdfPage? _page; + String? _text = ''; _PdfDictionary _dictionary = _PdfDictionary(); _Rectangle _rectangle = _Rectangle.empty; - PdfColor _innerColor; - PdfAnnotationBorder _border; - _PdfCrossTable _cTable; + PdfColor? _innerColor; + PdfAnnotationBorder? _border; + _PdfCrossTable? _cTable; bool _isLoadedAnnotation = false; PdfColor _color = PdfColor.empty; - PdfMargins _margins = PdfMargins(); - String _author = ''; - String _subject = ''; - DateTime _modifiedDate; + PdfMargins? _margins = PdfMargins(); + String? _author = ''; + String? _subject = ''; + DateTime? _modifiedDate; double _opacity = 1.0; - bool _setAppearance = false; - PdfAppearance _pdfAppearance; + PdfAppearance? _pdfAppearance; bool _saved = false; bool _isBounds = false; + /// Gets or sets whether the annotation needs appearance. + bool setAppearance = false; + // properties /// Gets a page of the annotation. Read-Only. - PdfPage get page => _page; + PdfPage? get page => _page; /// Gets annotation's bounds in the PDF page. Rect get bounds { if (!_isLoadedAnnotation) { - return _rectangle == null ? Rect.zero : _rectangle.rect; + return _rectangle.rect; } else { final _Rectangle rect = _getBounds(_dictionary, _crossTable); rect.y = page != null ? rect.y == 0 && rect.height == 0 ? rect.y + rect.height - : page.size.height - (rect.y + rect.height) + : page!.size.height - (rect.y + rect.height) : rect.y - rect.height; return rect.rect; } @@ -96,7 +84,6 @@ abstract class PdfAnnotation implements _IPdfWrapper { /// Sets annotation's bounds in the PDF page. set bounds(Rect value) { - ArgumentError.checkNotNull(value); if (!_isLoadedAnnotation) { final _Rectangle rectangle = _Rectangle.fromRect(value); if (_rectangle != rectangle) { @@ -109,7 +96,7 @@ abstract class PdfAnnotation implements _IPdfWrapper { if (value.isEmpty) { throw ArgumentError('rectangle'); } - final double height = page.size.height; + final double height = page!.size.height; final List<_PdfNumber> values = <_PdfNumber>[ _PdfNumber(value.left), _PdfNumber(height - (value.top + value.height)), @@ -131,8 +118,8 @@ abstract class PdfAnnotation implements _IPdfWrapper { _dictionary.setProperty(_DictionaryProperties.border, _border); } } - _border._isLineBorder = _isLineBorder(); - return _border; + _border!._isLineBorder = _isLineBorder(); + return _border!; } /// Sets annotation's border properties like width, horizontal radius etc. @@ -153,26 +140,24 @@ abstract class PdfAnnotation implements _IPdfWrapper { _text = (_dictionary[_DictionaryProperties.contents] as _PdfString).value; } - return _text; + return _text!; } else { - return _text == null || _text.isEmpty ? _obtainText() : _text; + return _text == null || _text!.isEmpty ? _obtainText()! : _text!; } } /// Sets content of the annotation. /// The string value specifies the text of the annotation. set text(String value) { - ArgumentError.checkNotNull(value, 'text'); if (_text != value) { _text = value; _dictionary._setString(_DictionaryProperties.contents, _text); } } - _PdfCrossTable get _crossTable => _cTable; + _PdfCrossTable get _crossTable => _cTable!; set _crossTable(_PdfCrossTable value) { - ArgumentError.checkNotNull(value, 'CrossTable'); if (value != _cTable) { _cTable = value; } @@ -185,9 +170,9 @@ abstract class PdfAnnotation implements _IPdfWrapper { set color(PdfColor value) { if (_color != value) { _color = value; - PdfColorSpace cs = PdfColorSpace.rgb; - if (page != null && !page._isLoadedPage) { - cs = page._section._parent._document.colorSpace; + PdfColorSpace? cs = PdfColorSpace.rgb; + if (page != null && !page!._isLoadedPage) { + cs = page!._section!._parent!._document!.colorSpace; } final _PdfArray colours = _color._toArray(cs); _dictionary.setProperty(_DictionaryProperties.c, colours); @@ -201,16 +186,16 @@ abstract class PdfAnnotation implements _IPdfWrapper { } else { _innerColor = _obtainInnerColor(); } - return _innerColor; + return _innerColor!; } /// Sets the inner color of the annotation. set innerColor(PdfColor value) { _innerColor = value; if (_isLoadedAnnotation) { - if (_innerColor._alpha != 0) { + if (_innerColor!._alpha != 0) { _dictionary.setProperty( - _DictionaryProperties.iC, _innerColor._toArray(PdfColorSpace.rgb)); + _DictionaryProperties.iC, _innerColor!._toArray(PdfColorSpace.rgb)); } else if (_dictionary.containsKey(_DictionaryProperties.iC)) { _dictionary.remove(_DictionaryProperties.iC); } @@ -229,12 +214,11 @@ abstract class PdfAnnotation implements _IPdfWrapper { } else { _author = _obtainAuthor(); } - return _author; + return _author!; } /// Sets the author of the annotation. set author(String value) { - ArgumentError.checkNotNull(value); if (_author != value) { _author = value; _dictionary._setString(_DictionaryProperties.t, _author); @@ -254,12 +238,11 @@ abstract class PdfAnnotation implements _IPdfWrapper { (_dictionary[_DictionaryProperties.subj] as _PdfString).value; } } - return _subject; + return _subject!; } /// Sets the subject of the annotation. set subject(String value) { - ArgumentError.checkNotNull(value); if (subject != value) { _subject = value; _dictionary._setString(_DictionaryProperties.subj, _subject); @@ -267,20 +250,20 @@ abstract class PdfAnnotation implements _IPdfWrapper { } /// Gets the ModifiedDate of the annotation. - DateTime get modifiedDate => + DateTime? get modifiedDate => _isLoadedAnnotation ? _obtainModifiedDate() : _modifiedDate; /// Sets the ModifiedDate of the annotation. - set modifiedDate(DateTime value) { + set modifiedDate(DateTime? value) { if (_modifiedDate != value) { _modifiedDate = value; - _dictionary._setDateTime(_DictionaryProperties.m, _modifiedDate); + _dictionary._setDateTime(_DictionaryProperties.m, _modifiedDate!); } } ///Gets or sets the boolean flag to flatten the annotation, ///by default, its become false. - bool flatten = false; + bool _flatten = false; /// Gets or sets flatten annotations popup. // ignore: prefer_final_fields @@ -289,10 +272,12 @@ abstract class PdfAnnotation implements _IPdfWrapper { /// Gets the opacity of the annotation. double get opacity { if (_isLoadedAnnotation) { - return _obtainOpacity(); + return _obtainOpacity()!; } - if (_dictionary._items.containsKey(_PdfName('CA'))) { - _opacity = (_dictionary._items[_PdfName('CA')] as _PdfNumber).value; + if (_dictionary._items!.containsKey(_PdfName('CA'))) { + final _PdfNumber ca = _dictionary._items![_PdfName('CA')] as _PdfNumber; + _opacity = ca.value!.toDouble(); + (_dictionary._items![_PdfName('CA')] as _PdfNumber).value as double?; } return _opacity; } @@ -310,27 +295,23 @@ abstract class PdfAnnotation implements _IPdfWrapper { } } - /// Gets appearance of the annotation. - bool get setAppearance => _setAppearance; - - /// Sets appearance of the annotation. - set setAppearance(bool value) { - if (value != null) { - _setAppearance = value; - } - } - /// Gets appearance of the annotation. PdfAppearance get appearance { _pdfAppearance ??= PdfAppearance(this); - return _pdfAppearance; + return _pdfAppearance!; } /// Sets appearance of the annotation. set appearance(PdfAppearance value) { - if (value != null) { - _pdfAppearance = value; - } + _pdfAppearance = value; + } + + //Public methods + /// Flatten the annotation. + /// + /// The flatten will add at the time of saving the current document. + void flatten() { + _flatten = true; } // implementation @@ -352,26 +333,25 @@ abstract class PdfAnnotation implements _IPdfWrapper { } void _initializeAnnotationProperties( - PdfPage page, - String text, - Rect bounds, - PdfAnnotationBorder border, - PdfColor color, - PdfColor innerColor, - String author, - double opacity, - String subject, - DateTime modifiedDate, - bool setAppearance, - bool flatten) { + PdfPage? page, + String? annotText, + Rect? bounds, + PdfAnnotationBorder? border, + PdfColor? color, + PdfColor? innerColor, + String? author, + double? opacity, + String? subject, + DateTime? modifiedDate, + bool? setAppearance) { if (page != null) { _page = page; } if (bounds != null) { this.bounds = bounds; } - if (text != null) { - this.text = text ??= _text; + if (annotText != null) { + text = annotText; _dictionary.setProperty( _PdfName(_DictionaryProperties.contents), _PdfString(text)); } @@ -399,12 +379,9 @@ abstract class PdfAnnotation implements _IPdfWrapper { if (setAppearance != null) { this.setAppearance = setAppearance; } - if (flatten != null) { - this.flatten = flatten; - } } - void _dictionaryBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { + void _dictionaryBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { if (_isContainsAnnotation()) { if (!_saved) { _save(); @@ -415,14 +392,14 @@ abstract class PdfAnnotation implements _IPdfWrapper { bool _isContainsAnnotation() { bool contains = false; - _PdfArray annotation; + _PdfArray? annotation; if (page != null && - page._dictionary.containsKey(_DictionaryProperties.annots)) { + page!._dictionary.containsKey(_DictionaryProperties.annots)) { annotation = _PdfCrossTable._dereference( - page._dictionary[_DictionaryProperties.annots]) as _PdfArray; + page!._dictionary[_DictionaryProperties.annots]) as _PdfArray?; if (annotation != null && annotation._elements.isNotEmpty && - annotation._contains(annotation._elements[0])) { + annotation._contains(annotation._elements[0]!)) { contains = true; } } @@ -430,18 +407,18 @@ abstract class PdfAnnotation implements _IPdfWrapper { } void _save() { - if (_page._document != null && - _page._document._conformanceLevel != PdfConformanceLevel.none) { + if (_page!._document != null && + _page!._document!._conformanceLevel != PdfConformanceLevel.none) { if (this is PdfActionAnnotation && - _page._document._conformanceLevel == PdfConformanceLevel.a1b) { + _page!._document!._conformanceLevel == PdfConformanceLevel.a1b) { throw ArgumentError( 'The specified annotation type is not supported by PDF/A1-B or PDF/A1-A standard documents.'); } //This is needed to attain specific PDF/A conformance. if (!(this is PdfLinkAnnotation) && !setAppearance && - (_page._document._conformanceLevel == PdfConformanceLevel.a2b || - _page._document._conformanceLevel == PdfConformanceLevel.a3b)) { + (_page!._document!._conformanceLevel == PdfConformanceLevel.a2b || + _page!._document!._conformanceLevel == PdfConformanceLevel.a3b)) { throw ArgumentError( 'The appearance dictionary doesn\'t contain an entry. Enable setAppearance in PdfAnnotation class to overcome this error.'); } @@ -456,10 +433,10 @@ abstract class PdfAnnotation implements _IPdfWrapper { } final _Rectangle nativeRectangle = _obtainNativeRectangle(); if (_innerColor != null && - !_innerColor.isEmpty && - _innerColor._alpha != 0.0) { + !_innerColor!.isEmpty && + _innerColor!._alpha != 0.0) { _dictionary.setProperty(_PdfName(_DictionaryProperties.ic), - _innerColor._toArray(PdfColorSpace.rgb)); + _innerColor!._toArray(PdfColorSpace.rgb)); } _dictionary.setProperty(_PdfName(_DictionaryProperties.rect), _PdfArray.fromRectangle(nativeRectangle)); @@ -468,42 +445,40 @@ abstract class PdfAnnotation implements _IPdfWrapper { _Rectangle _obtainNativeRectangle() { final _Rectangle nativeRectangle = _Rectangle(bounds.left, bounds.bottom, bounds.width, bounds.height); - Size size; - _PdfArray cropOrMediaBox; + Size? size; + _PdfArray? cropOrMediaBox; if (_page != null) { - if (!_page._isLoadedPage) { - final PdfSection section = _page._section; + if (!_page!._isLoadedPage) { + final PdfSection section = _page!._section!; nativeRectangle.location = - section._pointToNativePdf(page, nativeRectangle.location); + section._pointToNativePdf(page!, nativeRectangle.location); } else { - size = _page.size; + size = _page!.size; nativeRectangle.y = size.height - _rectangle.bottom; } - cropOrMediaBox = _getCropOrMediaBox(_page, cropOrMediaBox); + cropOrMediaBox = _getCropOrMediaBox(_page!, cropOrMediaBox); } if (cropOrMediaBox != null) { - if (cropOrMediaBox.count > 2 && - cropOrMediaBox[0] != null && - cropOrMediaBox[1] != null) { + if (cropOrMediaBox.count > 2) { if ((cropOrMediaBox[0] as _PdfNumber).value != 0 || (cropOrMediaBox[1] as _PdfNumber).value != 0) { - nativeRectangle.x = - nativeRectangle.x + (cropOrMediaBox[0] as _PdfNumber).value; - nativeRectangle.y = - nativeRectangle.y + (cropOrMediaBox[1] as _PdfNumber).value; + nativeRectangle.x = nativeRectangle.x + + (cropOrMediaBox[0] as _PdfNumber).value!.toDouble(); + nativeRectangle.y = nativeRectangle.y + + (cropOrMediaBox[1] as _PdfNumber).value!.toDouble(); } } } return nativeRectangle; } - _PdfArray _getCropOrMediaBox(PdfPage page, _PdfArray cropOrMediaBox) { + _PdfArray? _getCropOrMediaBox(PdfPage page, _PdfArray? cropOrMediaBox) { if (page._dictionary.containsKey(_DictionaryProperties.cropBox)) { cropOrMediaBox = _PdfCrossTable._dereference( - page._dictionary[_DictionaryProperties.cropBox]) as _PdfArray; + page._dictionary[_DictionaryProperties.cropBox]) as _PdfArray?; } else if (page._dictionary.containsKey(_DictionaryProperties.mediaBox)) { cropOrMediaBox = _PdfCrossTable._dereference( - page._dictionary[_DictionaryProperties.mediaBox]) as _PdfArray; + page._dictionary[_DictionaryProperties.mediaBox]) as _PdfArray?; } return cropOrMediaBox; } @@ -511,46 +486,49 @@ abstract class PdfAnnotation implements _IPdfWrapper { void _setPage(PdfPage page) { _page = page; if (!page._isLoadedPage) { - if (_page._document != null) { - _page._document._catalog._beginSaveList ??= []; - final PdfGraphics graphics = page.graphics; - ArgumentError.checkNotNull(graphics, 'graphics'); + if (_page!._document != null) { + _page!._document!._catalog._beginSaveList ??= []; + final PdfGraphics graphics = + page.graphics; //Accessed for creating page content. + ArgumentError.checkNotNull(graphics); if (_dictionary.containsKey(_DictionaryProperties.subtype)) { - final _PdfName name = _dictionary - ._items[_PdfName(_DictionaryProperties.subtype)] as _PdfName; + final _PdfName? name = _dictionary + ._items![_PdfName(_DictionaryProperties.subtype)] as _PdfName?; if (name != null) { if (name._name == _DictionaryProperties.text || name._name == _DictionaryProperties.square || - flatten) { - _page._document._catalog._beginSaveList.add(_dictionaryBeginSave); - _page._document._catalog.modify(); + _flatten) { + _page!._document!._catalog._beginSaveList! + .add(_dictionaryBeginSave); + _page!._document!._catalog.modify(); } } - } else if (flatten) { - _page._document._catalog._beginSaveList.add(_dictionaryBeginSave); - _page._document._catalog.modify(); + } else if (_flatten) { + _page!._document!._catalog._beginSaveList!.add(_dictionaryBeginSave); + _page!._document!._catalog.modify(); } } } else { if (_dictionary.containsKey(_DictionaryProperties.subtype)) { - final _PdfName name = _dictionary - ._items[_PdfName(_DictionaryProperties.subtype)] as _PdfName; - _page._document._catalog._beginSaveList ??= []; + final _PdfName? name = _dictionary + ._items![_PdfName(_DictionaryProperties.subtype)] as _PdfName?; + _page!._document!._catalog._beginSaveList ??= []; if (name != null) { if (name._name == _DictionaryProperties.circle || name._name == _DictionaryProperties.square || name._name == _DictionaryProperties.line || name._name == _DictionaryProperties.polygon) { - _page._document._catalog._beginSaveList.add(_dictionaryBeginSave); - _page._document._catalog.modify(); + _page!._document!._catalog._beginSaveList! + .add(_dictionaryBeginSave); + _page!._document!._catalog.modify(); } } - } else if (flatten) { - _page._document._catalog._beginSaveList.add(_dictionaryBeginSave); - _page._document._catalog.modify(); + } else if (_flatten) { + _page!._document!._catalog._beginSaveList!.add(_dictionaryBeginSave); + _page!._document!._catalog.modify(); } } - if (_page != null && !_page._isLoadedPage) { + if (_page != null && !_page!._isLoadedPage) { _dictionary.setProperty( _PdfName(_DictionaryProperties.p), _PdfReferenceHolder(_page)); } @@ -558,40 +536,43 @@ abstract class PdfAnnotation implements _IPdfWrapper { /// Gets the bounds. _Rectangle _getBounds(_PdfDictionary dictionary, _PdfCrossTable crossTable) { - _PdfArray array; + _PdfArray? array; if (dictionary.containsKey(_DictionaryProperties.rect)) { array = crossTable._getObject(dictionary[_DictionaryProperties.rect]) - as _PdfArray; + as _PdfArray?; } - return array.toRectangle(); + return array!.toRectangle(); } // Gets the border. PdfAnnotationBorder _obtainBorder() { final PdfAnnotationBorder border = PdfAnnotationBorder(); if (_dictionary.containsKey(_DictionaryProperties.border)) { - final _PdfArray borderArray = + final _PdfArray? borderArray = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.border]) - as _PdfArray; + as _PdfArray?; if (borderArray != null && borderArray.count >= 2) { if (borderArray[0] is _PdfNumber && borderArray[1] is _PdfNumber && borderArray[2] is _PdfNumber) { - final double width = (borderArray[0] as _PdfNumber).value.toDouble(); + final double width = (borderArray[0] as _PdfNumber).value!.toDouble(); final double hRadius = - (borderArray[1] as _PdfNumber).value.toDouble(); + (borderArray[1] as _PdfNumber).value!.toDouble(); final double vRadius = - (borderArray[2] as _PdfNumber).value.toDouble(); + (borderArray[2] as _PdfNumber).value!.toDouble(); border.width = vRadius; border.horizontalRadius = width; border.verticalRadius = hRadius; } } } else if (_dictionary.containsKey(_DictionaryProperties.bs)) { - final _PdfDictionary lbDic = - _crossTable._getObject(_dictionary[_DictionaryProperties.bs]); + final _PdfDictionary lbDic = _crossTable + ._getObject(_dictionary[_DictionaryProperties.bs]) as _PdfDictionary; if (lbDic.containsKey(_DictionaryProperties.w)) { - border.width = (lbDic[_DictionaryProperties.w] as _PdfNumber).value; + final _PdfNumber? value = lbDic[_DictionaryProperties.w] as _PdfNumber?; + if (value != null) { + border.width = value.value!.toDouble(); + } } if (lbDic.containsKey(_DictionaryProperties.s)) { final _PdfName bstr = @@ -600,18 +581,16 @@ abstract class PdfAnnotation implements _IPdfWrapper { border.borderStyle = _getBorderStyle(bstr._name.toString()); } if (lbDic.containsKey(_DictionaryProperties.d)) { - final _PdfArray _dasharray = + final _PdfArray? _dasharray = _PdfCrossTable._dereference(lbDic[_DictionaryProperties.d]) - as _PdfArray; + as _PdfArray?; if (_dasharray != null) { final _PdfNumber dashArray = _dasharray[0] as _PdfNumber; - if (dashArray != null) { - final int _dashArray = dashArray.value.toInt(); - _dasharray._clear(); - _dasharray._insert(0, _PdfNumber(_dashArray)); - _dasharray._insert(1, _PdfNumber(_dashArray)); - border.dashArray = _dashArray; - } + final int _dashArray = dashArray.value!.toInt(); + _dasharray._clear(); + _dasharray._insert(0, _PdfNumber(_dashArray)); + _dasharray._insert(1, _PdfNumber(_dashArray)); + border.dashArray = _dashArray; } } } @@ -619,64 +598,62 @@ abstract class PdfAnnotation implements _IPdfWrapper { } // Gets the text. - String _obtainText() { - String text; + String? _obtainText() { + String tempText; if (_dictionary.containsKey(_DictionaryProperties.contents)) { - final _PdfString mText = _PdfCrossTable._dereference( - _dictionary[_DictionaryProperties.contents]) as _PdfString; + final _PdfString? mText = _PdfCrossTable._dereference( + _dictionary[_DictionaryProperties.contents]) as _PdfString?; if (mText != null) { _text = mText.value.toString(); } return _text; } else { - text = ''; - return text; + tempText = ''; + return tempText; } } // Gets the color. PdfColor _obtainColor() { PdfColor color = PdfColor.empty; - _PdfArray colours; + _PdfArray? colours; if (_dictionary.containsKey(_DictionaryProperties.c)) { - colours = _dictionary[_DictionaryProperties.c] as _PdfArray; + colours = _dictionary[_DictionaryProperties.c] as _PdfArray?; } if (colours != null && colours._elements.length == 1) { //Convert the float color values into bytes - final _PdfNumber color0 = - _crossTable._getObject(colours[0]) as _PdfNumber; + final _PdfNumber? color0 = + _crossTable._getObject(colours[0]) as _PdfNumber?; if (color0 != null) { - color = PdfColor._fromGray(color0.value); + color = PdfColor._fromGray(color0.value as double); } } else if (colours != null && colours._elements.length == 3) { final _PdfNumber color0 = colours[0] as _PdfNumber; final _PdfNumber color1 = colours[1] as _PdfNumber; final _PdfNumber color2 = colours[2] as _PdfNumber; - if (color0 != null && color1 != null && color2 != null) { - //Convert the float color values into bytes - final int red = (color0.value * 255).round().toUnsigned(8).toInt(); - final int green = (color1.value * 255).round().toUnsigned(8).toInt(); - final int blue = (color2.value * 255).round().toUnsigned(8).toInt(); - color = PdfColor(red, green, blue); - } + //Convert the float color values into bytes + final int red = (color0.value! * 255).round().toUnsigned(8).toInt(); + final int green = (color1.value! * 255).round().toUnsigned(8).toInt(); + final int blue = (color2.value! * 255).round().toUnsigned(8).toInt(); + color = PdfColor(red, green, blue); } else if (colours != null && colours._elements.length == 4) { - final _PdfNumber color0 = - _crossTable._getObject(colours[0]) as _PdfNumber; - final _PdfNumber color1 = - _crossTable._getObject(colours[1]) as _PdfNumber; - final _PdfNumber color2 = - _crossTable._getObject(colours[2]) as _PdfNumber; - final _PdfNumber color3 = - _crossTable._getObject(colours[3]) as _PdfNumber; + final _PdfNumber? color0 = + _crossTable._getObject(colours[0]) as _PdfNumber?; + final _PdfNumber? color1 = + _crossTable._getObject(colours[1]) as _PdfNumber?; + final _PdfNumber? color2 = + _crossTable._getObject(colours[2]) as _PdfNumber?; + final _PdfNumber? color3 = + _crossTable._getObject(colours[3]) as _PdfNumber?; if (color0 != null && color1 != null && color2 != null && color3 != null) { //Convert the float color values into bytes - final double cyan = color0.value; - final double magenta = color1.value; - final double yellow = color2.value; - final double black = color3.value; + final double cyan = color0.value as double; + final double magenta = color1.value as double; + final double yellow = color2.value as double; + final double black = color3.value as double; color = PdfColor.fromCMYK(cyan, magenta, yellow, black); } } @@ -684,37 +661,37 @@ abstract class PdfAnnotation implements _IPdfWrapper { } // Gets the Opacity. - double _obtainOpacity() { + double? _obtainOpacity() { if (_dictionary.containsKey(_DictionaryProperties.ca)) { - _opacity = _getNumber(_DictionaryProperties.ca); + _opacity = _getNumber(_DictionaryProperties.ca)!; } return _opacity; } // Gets the number value. - double _getNumber(String keyName) { - double result = 0; - final _PdfNumber numb = _dictionary[keyName] as _PdfNumber; + double? _getNumber(String keyName) { + double? result = 0; + final _PdfNumber? numb = _dictionary[keyName] as _PdfNumber?; if (numb != null) { - result = numb.value; + result = numb.value as double?; } return result; } // Gets the Author. - String _obtainAuthor() { - String author; + String? _obtainAuthor() { + String? author; if (_dictionary.containsKey(_DictionaryProperties.author)) { - final _PdfString _author = + final _PdfString? _author = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.author]) - as _PdfString; + as _PdfString?; if (_author != null) { author = _author.value; } } else if (_dictionary.containsKey(_DictionaryProperties.t)) { - final _PdfString _author = + final _PdfString? _author = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.t]) - as _PdfString; + as _PdfString?; if (_author != null) { author = _author.value; } @@ -723,18 +700,18 @@ abstract class PdfAnnotation implements _IPdfWrapper { } // Gets the Subject. - String _obtainSubject() { - String subject; + String? _obtainSubject() { + String? subject; if (_dictionary.containsKey(_DictionaryProperties.subject)) { - final _PdfString _subject = _PdfCrossTable._dereference( - _dictionary[_DictionaryProperties.subject]) as _PdfString; + final _PdfString? _subject = _PdfCrossTable._dereference( + _dictionary[_DictionaryProperties.subject]) as _PdfString?; if (_subject != null) { subject = _subject.value; } } else if (_dictionary.containsKey(_DictionaryProperties.subj)) { - final _PdfString _subject = + final _PdfString? _subject = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.subj]) - as _PdfString; + as _PdfString?; if (_subject != null) { subject = _subject.value; } @@ -743,52 +720,48 @@ abstract class PdfAnnotation implements _IPdfWrapper { } // Gets the ModifiedDate. - DateTime _obtainModifiedDate() { + DateTime? _obtainModifiedDate() { if (_dictionary.containsKey(_DictionaryProperties.modificationDate) || _dictionary.containsKey(_DictionaryProperties.m)) { - _PdfString modifiedDate = - _dictionary[_DictionaryProperties.modificationDate] as _PdfString; - modifiedDate ??= _dictionary[_DictionaryProperties.m] as _PdfString; - _modifiedDate = _dictionary._getDateTime(modifiedDate); + _PdfString? modifiedDate = + _dictionary[_DictionaryProperties.modificationDate] as _PdfString?; + modifiedDate ??= _dictionary[_DictionaryProperties.m] as _PdfString?; + _modifiedDate = _dictionary._getDateTime(modifiedDate!); } return _modifiedDate; } - String _getEnumName(dynamic text) { - text = text.toString(); - final int index = text.indexOf('.'); - final String name = text.substring(index + 1); + String _getEnumName(dynamic annotText) { + annotText = annotText.toString(); + final int index = annotText.indexOf('.'); + final String name = annotText.substring(index + 1); return name[0].toUpperCase() + name.substring(1); } //Get the inner line color PdfColor _obtainInnerColor() { PdfColor color = PdfColor.empty; - _PdfArray colours; + _PdfArray? colours; if (_dictionary.containsKey(_DictionaryProperties.iC)) { colours = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.iC]) - as _PdfArray; + as _PdfArray?; if (colours != null && colours.count > 0) { final int red = - ((colours[0] as _PdfNumber).value * 255).round().toUnsigned(8); + ((colours[0] as _PdfNumber).value! * 255).round().toUnsigned(8); final int green = - ((colours[1] as _PdfNumber).value * 255).round().toUnsigned(8); + ((colours[1] as _PdfNumber).value! * 255).round().toUnsigned(8); final int blue = - ((colours[2] as _PdfNumber).value * 255).round().toUnsigned(8); + ((colours[2] as _PdfNumber).value! * 255).round().toUnsigned(8); color = PdfColor(red, green, blue); } } return color; } - PdfMargins _obtainMargin() { - if (page != null) { - if (page._section != null && - page._section.pageSettings != null && - page._section.pageSettings.margins != null) { - _margins = page._section.pageSettings.margins; - } + PdfMargins? _obtainMargin() { + if (page != null && page!._section != null) { + _margins = page!._section!.pageSettings.margins; } return _margins; } @@ -796,7 +769,7 @@ abstract class PdfAnnotation implements _IPdfWrapper { void _flattenPopup() { if (page != null && !_isLoadedAnnotation) { _flattenAnnotationPopups( - page, color, bounds, border, author, subject, text); + page!, color, bounds, border, author, subject, text); } } @@ -811,37 +784,40 @@ abstract class PdfAnnotation implements _IPdfWrapper { Rect bounds = Rect.fromLTWH(x, y, 180, 142); // Draw annotation based on bounds if (_dictionary[_DictionaryProperties.popup] != null) { - final _IPdfPrimitive obj = _dictionary[_DictionaryProperties.popup]; - final _PdfDictionary dictionary = - _PdfCrossTable._dereference(obj) as _PdfDictionary; + final _IPdfPrimitive? obj = _dictionary[_DictionaryProperties.popup]; + final _PdfDictionary? dictionary = + _PdfCrossTable._dereference(obj) as _PdfDictionary?; if (dictionary != null) { - final _PdfArray rectValue = + final _PdfArray? rectValue = _PdfCrossTable._dereference(dictionary[_DictionaryProperties.rect]) - as _PdfArray; - final _PdfCrossTable crosstable = page._crossTable; + as _PdfArray?; + final _PdfCrossTable? crosstable = page._crossTable; if (rectValue != null) { final _PdfNumber left = - crosstable._getReference(rectValue[0]) as _PdfNumber; + crosstable!._getReference(rectValue[0]) as _PdfNumber; final _PdfNumber top = crosstable._getReference(rectValue[1]) as _PdfNumber; final _PdfNumber width = crosstable._getReference(rectValue[2]) as _PdfNumber; final _PdfNumber height = crosstable._getReference(rectValue[3]) as _PdfNumber; - bounds = Rect.fromLTWH(left.value, top.value, - width.value - left.value, height.value - top.value); + bounds = Rect.fromLTWH( + left.value as double, + top.value as double, + width.value! - (left.value as double), + height.value! - (top.value as double)); } } } final PdfBrush backBrush = PdfSolidBrush(color); final double borderWidth = border.width / 2; - double trackingHeight = 0; + double? trackingHeight = 0; final PdfBrush aBrush = PdfSolidBrush(_getForeColor(color)); - if (author != null && author != '') { - final Map returnedValue = _drawAuthor(author, subject, + if (author != '') { + final Map returnedValue = _drawAuthor(author, subject, bounds, backBrush, aBrush, page, trackingHeight, border); trackingHeight = returnedValue['height']; - } else if (subject != null && subject != '') { + } else if (subject != '') { final Rect titleRect = Rect.fromLTWH(bounds.left + borderWidth, bounds.top + borderWidth, bounds.width - border.width, 40); _saveGraphics(page, PdfBlendMode.hardLight); @@ -870,7 +846,7 @@ abstract class PdfAnnotation implements _IPdfWrapper { } Rect cRect = Rect.fromLTWH( bounds.left + borderWidth, - bounds.top + borderWidth + trackingHeight, + bounds.top + borderWidth + trackingHeight!, bounds.width - border.width, bounds.height - (trackingHeight + border.width)); _saveGraphics(page, PdfBlendMode.hardLight); @@ -909,14 +885,14 @@ abstract class PdfAnnotation implements _IPdfWrapper { : PdfColor(255, 255, 255, 255); } - Map _drawAuthor( + Map _drawAuthor( String author, String subject, Rect bounds, PdfBrush backBrush, PdfBrush aBrush, PdfPage page, - double trackingHeight, + double? trackingHeight, PdfAnnotationBorder border) { final double borderWidth = border.width / 2; final _Rectangle titleRect = _Rectangle.fromRect(Rect.fromLTWH( @@ -924,7 +900,7 @@ abstract class PdfAnnotation implements _IPdfWrapper { bounds.top + borderWidth, bounds.width - border.width, 20)); - if (subject != '' && subject != null) { + if (subject != '') { titleRect.height += 20; trackingHeight = titleRect.height; _saveGraphics(page, PdfBlendMode.hardLight); @@ -968,11 +944,11 @@ abstract class PdfAnnotation implements _IPdfWrapper { trackingHeight = titleRect.height; page.graphics.restore(); } - return {'height': trackingHeight}; + return {'height': trackingHeight}; } Rect _calculateTemplateBounds( - Rect bounds, PdfPage page, PdfTemplate template, bool isNormalMatrix) { + Rect bounds, PdfPage? page, PdfTemplate? template, bool isNormalMatrix) { double x = bounds.left, y = bounds.top, width = bounds.width, @@ -1012,7 +988,7 @@ abstract class PdfAnnotation implements _IPdfWrapper { } void _setMatrix(_PdfDictionary template) { - final _PdfArray bbox = template[_DictionaryProperties.bBox] as _PdfArray; + final _PdfArray? bbox = template[_DictionaryProperties.bBox] as _PdfArray?; if (bbox != null) { final List elements = [ 1, @@ -1021,10 +997,10 @@ abstract class PdfAnnotation implements _IPdfWrapper { 1, ((bbox[0] as _PdfNumber).value == 0) ? 0 - : -(bbox[0] as _PdfNumber).value, + : -(bbox[0] as _PdfNumber).value! as double, ((bbox[0] as _PdfNumber).value == 0) ? 0 - : -(bbox[1] as _PdfNumber).value + : -(bbox[1] as _PdfNumber).value! as double ]; template[_DictionaryProperties.matrix] = _PdfArray(elements); } @@ -1061,7 +1037,7 @@ abstract class PdfAnnotation implements _IPdfWrapper { double borderLength) { _Rectangle bounds = _Rectangle.fromRect(this.bounds); final PdfPath path = PdfPath(); - if (linePoints != null && linePoints.length == 4) { + if (linePoints.length == 4) { final double x1 = linePoints[0].toDouble(); final double y1 = linePoints[1].toDouble(); final double x2 = linePoints[2].toDouble(); @@ -1114,54 +1090,52 @@ abstract class PdfAnnotation implements _IPdfWrapper { for (int i = 0; i < lineStyle.count; i++) { final _PdfName lineEndingStyle = lineStyle[i] as _PdfName; final _Point point = _Point.empty; - if (lineEndingStyle != null) { - switch (_getEnumName(lineEndingStyle._name)) { - case 'Square': - case 'Circle': - case 'Diamond': - { - point.x = 3; - point.y = 3; - } - break; - case 'OpenArrow': - case 'ClosedArrow': - { - point.x = 1; - point.y = 5; - } - break; - case 'ROpenArrow': - case 'RClosedArrow': - { - point.x = 9 + (borderLength / 2); - point.y = 5 + (borderLength / 2); - } - break; - case 'Slash': - { - point.x = 5; - point.y = 9; - } - break; - case 'Butt': - { - point.x = 1; - point.y = 3; - } - break; - default: - { - point.x = 0; - point.y = 0; - } - break; - } + switch (_getEnumName(lineEndingStyle._name)) { + case 'Square': + case 'Circle': + case 'Diamond': + { + point.x = 3; + point.y = 3; + } + break; + case 'OpenArrow': + case 'ClosedArrow': + { + point.x = 1; + point.y = 5; + } + break; + case 'ROpenArrow': + case 'RClosedArrow': + { + point.x = 9 + (borderLength / 2); + point.y = 5 + (borderLength / 2); + } + break; + case 'Slash': + { + point.x = 5; + point.y = 9; + } + break; + case 'Butt': + { + point.x = 1; + point.y = 3; + } + break; + default: + { + point.x = 0; + point.y = 0; + } + break; } stylePoint.add(point); } - final List widthX = List(2); - final List heightY = List(2); + final List widthX = List.filled(2, 0); + final List heightY = List.filled(2, 0); if ((lineAngle >= 45 && lineAngle <= 135) || (lineAngle >= 225 && lineAngle <= 315)) { @@ -1176,7 +1150,7 @@ abstract class PdfAnnotation implements _IPdfWrapper { heightY[1] = stylePoint[1].y; } - final double height = [heightY[0], heightY[1]].reduce(max); + final double height = max(heightY[0], heightY[1]); if (startingPoint[0] == [startingPoint[0], endingPoint[0]].reduce(min)) { startingPoint[0] -= widthX[0] * borderLength; @@ -1218,7 +1192,7 @@ abstract class PdfAnnotation implements _IPdfWrapper { List _getAxisValue(List value, double angle, double length) { final double degToRad = pi / 180.0; - final List xy = List(2); + final List xy = List.filled(2, 0); xy[0] = value[0] + cos(angle * degToRad) * length; xy[1] = value[1] + sin(angle * degToRad) * length; @@ -1247,10 +1221,10 @@ abstract class PdfAnnotation implements _IPdfWrapper { void _setLineEndingStyles( List startingPoint, List endingPoint, - PdfGraphics graphics, + PdfGraphics? graphics, double angle, - PdfPen borderPen, - PdfBrush backBrush, + PdfPen? borderPen, + PdfBrush? backBrush, _PdfArray lineStyle, double borderLength) { List axisPoint; @@ -1259,7 +1233,7 @@ abstract class PdfAnnotation implements _IPdfWrapper { borderPen = null; } if (backBrush is PdfSolidBrush) { - if ((backBrush as PdfSolidBrush).color.isEmpty) { + if (backBrush.color.isEmpty) { backBrush = null; } } @@ -1270,182 +1244,180 @@ abstract class PdfAnnotation implements _IPdfWrapper { } else { axisPoint = endingPoint; } - if (lineEndingStyle != null) { - switch (lineEndingStyle._name) { - case 'Square': - { - final Rect rect = Rect.fromLTWH( - axisPoint[0] - (3 * borderLength), - -(axisPoint[1] + (3 * borderLength)), - 6 * borderLength, - 6 * borderLength); - graphics.drawRectangle( - bounds: rect, pen: borderPen, brush: backBrush); - } - break; - case 'Circle': - { - final Rect rect = Rect.fromLTWH( - axisPoint[0] - (3 * borderLength), - -(axisPoint[1] + (3 * borderLength)), - 6 * borderLength, - 6 * borderLength); - graphics.drawEllipse(rect, pen: borderPen, brush: backBrush); + switch (lineEndingStyle._name) { + case 'Square': + { + final Rect rect = Rect.fromLTWH( + axisPoint[0] - (3 * borderLength), + -(axisPoint[1] + (3 * borderLength)), + 6 * borderLength, + 6 * borderLength); + graphics! + .drawRectangle(bounds: rect, pen: borderPen, brush: backBrush); + } + break; + case 'Circle': + { + final Rect rect = Rect.fromLTWH( + axisPoint[0] - (3 * borderLength), + -(axisPoint[1] + (3 * borderLength)), + 6 * borderLength, + 6 * borderLength); + graphics!.drawEllipse(rect, pen: borderPen, brush: backBrush); + } + break; + case 'OpenArrow': + { + int arraowAngle = 0; + if (i == 0) { + arraowAngle = 30; + } else { + arraowAngle = 150; } - break; - case 'OpenArrow': - { - int arraowAngle = 0; - if (i == 0) { - arraowAngle = 30; - } else { - arraowAngle = 150; - } - final double length = 9 * borderLength; - List startPoint; - if (i == 0) { - startPoint = _getAxisValue(axisPoint, angle, borderLength); - } else { - startPoint = _getAxisValue(axisPoint, angle, -borderLength); - } - final List point1 = - _getAxisValue(startPoint, angle + arraowAngle, length); - final List point2 = - _getAxisValue(startPoint, angle - arraowAngle, length); - - final PdfPath path = PdfPath(pen: borderPen); - path.addLine(Offset(startPoint[0], -startPoint[1]), - Offset(point1[0], -point1[1])); - path.addLine(Offset(startPoint[0], -startPoint[1]), - Offset(point2[0], -point2[1])); - graphics.drawPath(path, pen: borderPen); + final double length = 9 * borderLength; + List startPoint; + if (i == 0) { + startPoint = _getAxisValue(axisPoint, angle, borderLength); + } else { + startPoint = _getAxisValue(axisPoint, angle, -borderLength); } - break; - case 'ClosedArrow': - { - int arraowAngle = 0; - if (i == 0) { - arraowAngle = 30; - } else { - arraowAngle = 150; - } - final double length = 9 * borderLength; - List startPoint; - if (i == 0) { - startPoint = _getAxisValue(axisPoint, angle, borderLength); - } else { - startPoint = _getAxisValue(axisPoint, angle, -borderLength); - } - final List point1 = - _getAxisValue(startPoint, angle + arraowAngle, length); - final List point2 = - _getAxisValue(startPoint, angle - arraowAngle, length); - final List points = [ - Offset(startPoint[0], -startPoint[1]), - Offset(point1[0], -point1[1]), - Offset(point2[0], -point2[1]) - ]; - graphics.drawPolygon(points, pen: borderPen, brush: backBrush); + final List point1 = + _getAxisValue(startPoint, angle + arraowAngle, length); + final List point2 = + _getAxisValue(startPoint, angle - arraowAngle, length); + + final PdfPath path = PdfPath(pen: borderPen); + path.addLine(Offset(startPoint[0], -startPoint[1]), + Offset(point1[0], -point1[1])); + path.addLine(Offset(startPoint[0], -startPoint[1]), + Offset(point2[0], -point2[1])); + graphics!.drawPath(path, pen: borderPen); + } + break; + case 'ClosedArrow': + { + int arraowAngle = 0; + if (i == 0) { + arraowAngle = 30; + } else { + arraowAngle = 150; } - break; - case 'ROpenArrow': - { - int arraowAngle = 0; - if (i == 0) { - arraowAngle = 150; - } else { - arraowAngle = 30; - } - final double length = 9 * borderLength; - List startPoint; - if (i == 0) { - startPoint = _getAxisValue(axisPoint, angle, -borderLength); - } else { - startPoint = _getAxisValue(axisPoint, angle, borderLength); - } - final List point1 = - _getAxisValue(startPoint, angle + arraowAngle, length); - final List point2 = - _getAxisValue(startPoint, angle - arraowAngle, length); - - final PdfPath path = PdfPath(pen: borderPen); - path.addLine(Offset(startPoint[0], -startPoint[1]), - Offset(point1[0], -point1[1])); - path.addLine(Offset(startPoint[0], -startPoint[1]), - Offset(point2[0], -point2[1])); - graphics.drawPath(path, pen: borderPen); + final double length = 9 * borderLength; + List startPoint; + if (i == 0) { + startPoint = _getAxisValue(axisPoint, angle, borderLength); + } else { + startPoint = _getAxisValue(axisPoint, angle, -borderLength); } - break; - case 'RClosedArrow': - { - int arraowAngle = 0; - if (i == 0) { - arraowAngle = 150; - } else { - arraowAngle = 30; - } - final double length = 9 * borderLength; - List startPoint; - if (i == 0) { - startPoint = _getAxisValue(axisPoint, angle, -borderLength); - } else { - startPoint = _getAxisValue(axisPoint, angle, borderLength); - } - - final List point1 = - _getAxisValue(startPoint, angle + arraowAngle, length); - final List point2 = - _getAxisValue(startPoint, angle - arraowAngle, length); - final List points = [ - Offset(startPoint[0], -startPoint[1]), - Offset(point1[0], -point1[1]), - Offset(point2[0], -point2[1]) - ]; - graphics.drawPolygon(points, pen: borderPen, brush: backBrush); + final List point1 = + _getAxisValue(startPoint, angle + arraowAngle, length); + final List point2 = + _getAxisValue(startPoint, angle - arraowAngle, length); + final List points = [ + Offset(startPoint[0], -startPoint[1]), + Offset(point1[0], -point1[1]), + Offset(point2[0], -point2[1]) + ]; + graphics!.drawPolygon(points, pen: borderPen, brush: backBrush); + } + break; + case 'ROpenArrow': + { + int arraowAngle = 0; + if (i == 0) { + arraowAngle = 150; + } else { + arraowAngle = 30; } - break; - case 'Slash': - { - final double length = 9 * borderLength; - final List point1 = - _getAxisValue(axisPoint, angle + 60, length); - final List point2 = - _getAxisValue(axisPoint, angle - 120, length); - graphics.drawLine(borderPen, Offset(axisPoint[0], -axisPoint[1]), - Offset(point1[0], -point1[1])); - graphics.drawLine(borderPen, Offset(axisPoint[0], -axisPoint[1]), - Offset(point2[0], -point2[1])); + final double length = 9 * borderLength; + List startPoint; + if (i == 0) { + startPoint = _getAxisValue(axisPoint, angle, -borderLength); + } else { + startPoint = _getAxisValue(axisPoint, angle, borderLength); } - break; - case 'Diamond': - { - final double length = 3 * borderLength; - final List point1 = _getAxisValue(axisPoint, 180, length); - final List point2 = _getAxisValue(axisPoint, 90, length); - final List point3 = _getAxisValue(axisPoint, 0, length); - final List point4 = _getAxisValue(axisPoint, -90, length); - final List points = [ - Offset(point1[0], -point1[1]), - Offset(point2[0], -point2[1]), - Offset(point3[0], -point3[1]), - Offset(point4[0], -point4[1]) - ]; - graphics.drawPolygon(points, pen: borderPen, brush: backBrush); + final List point1 = + _getAxisValue(startPoint, angle + arraowAngle, length); + final List point2 = + _getAxisValue(startPoint, angle - arraowAngle, length); + + final PdfPath path = PdfPath(pen: borderPen); + path.addLine(Offset(startPoint[0], -startPoint[1]), + Offset(point1[0], -point1[1])); + path.addLine(Offset(startPoint[0], -startPoint[1]), + Offset(point2[0], -point2[1])); + graphics!.drawPath(path, pen: borderPen); + } + break; + case 'RClosedArrow': + { + int arraowAngle = 0; + if (i == 0) { + arraowAngle = 150; + } else { + arraowAngle = 30; } - break; - case 'Butt': - { - final double length = 3 * borderLength; - final List point1 = - _getAxisValue(axisPoint, angle + 90, length); - final List point2 = - _getAxisValue(axisPoint, angle - 90, length); - - graphics.drawLine(borderPen, Offset(point1[0], -point1[1]), - Offset(point2[0], -point2[1])); + final double length = 9 * borderLength; + List startPoint; + if (i == 0) { + startPoint = _getAxisValue(axisPoint, angle, -borderLength); + } else { + startPoint = _getAxisValue(axisPoint, angle, borderLength); } - break; - } + + final List point1 = + _getAxisValue(startPoint, angle + arraowAngle, length); + final List point2 = + _getAxisValue(startPoint, angle - arraowAngle, length); + final List points = [ + Offset(startPoint[0], -startPoint[1]), + Offset(point1[0], -point1[1]), + Offset(point2[0], -point2[1]) + ]; + graphics!.drawPolygon(points, pen: borderPen, brush: backBrush); + } + break; + case 'Slash': + { + final double length = 9 * borderLength; + final List point1 = + _getAxisValue(axisPoint, angle + 60, length); + final List point2 = + _getAxisValue(axisPoint, angle - 120, length); + graphics!.drawLine(borderPen!, Offset(axisPoint[0], -axisPoint[1]), + Offset(point1[0], -point1[1])); + graphics.drawLine(borderPen, Offset(axisPoint[0], -axisPoint[1]), + Offset(point2[0], -point2[1])); + } + break; + case 'Diamond': + { + final double length = 3 * borderLength; + final List point1 = _getAxisValue(axisPoint, 180, length); + final List point2 = _getAxisValue(axisPoint, 90, length); + final List point3 = _getAxisValue(axisPoint, 0, length); + final List point4 = _getAxisValue(axisPoint, -90, length); + final List points = [ + Offset(point1[0], -point1[1]), + Offset(point2[0], -point2[1]), + Offset(point3[0], -point3[1]), + Offset(point4[0], -point4[1]) + ]; + graphics!.drawPolygon(points, pen: borderPen, brush: backBrush); + } + break; + case 'Butt': + { + final double length = 3 * borderLength; + final List point1 = + _getAxisValue(axisPoint, angle + 90, length); + final List point2 = + _getAxisValue(axisPoint, angle - 90, length); + + graphics!.drawLine(borderPen!, Offset(point1[0], -point1[1]), + Offset(point2[0], -point2[1])); + } + break; } } } @@ -1454,20 +1426,15 @@ abstract class PdfAnnotation implements _IPdfWrapper { bool _validateTemplateMatrix(_PdfDictionary dictionary) { bool isRotatedMatrix = false; if (dictionary.containsKey(_DictionaryProperties.matrix)) { - final _PdfArray matrix = + final _PdfArray? matrix = _PdfCrossTable._dereference(dictionary[_DictionaryProperties.matrix]) - as _PdfArray; + as _PdfArray?; if (matrix != null && matrix.count > 3) { - if (matrix[0] != null && - matrix[1] != null && - matrix[2] != null && - matrix[3] != null) { - if ((matrix[0] as _PdfNumber).value == 1 && - (matrix[1] as _PdfNumber).value == 0 && - (matrix[2] as _PdfNumber).value == 0 && - (matrix[3] as _PdfNumber).value == 1) { - isRotatedMatrix = true; - } + if ((matrix[0] as _PdfNumber).value == 1 && + (matrix[1] as _PdfNumber).value == 0 && + (matrix[2] as _PdfNumber).value == 0 && + (matrix[3] as _PdfNumber).value == 1) { + isRotatedMatrix = true; } } } else { @@ -1478,26 +1445,23 @@ abstract class PdfAnnotation implements _IPdfWrapper { // Flatten annotation template void _flattenAnnotationTemplate(PdfTemplate appearance, bool isNormalMatrix) { - final PdfGraphicsState state = page.graphics.save(); + final PdfGraphicsState state = page!.graphics.save(); if (opacity < 1) { - page.graphics.setTransparency(opacity); + page!.graphics.setTransparency(opacity); } final Rect bound = _calculateTemplateBounds(bounds, page, appearance, isNormalMatrix); - page.graphics.drawPdfTemplate(appearance, bound.topLeft, bounds.size); - page.graphics.restore(state); - page.annotations.remove(this); + page!.graphics.drawPdfTemplate(appearance, bound.topLeft, bounds.size); + page!.graphics.restore(state); + page!.annotations.remove(this); } // Draw CloudStye to the Shapes - void _drawCloudStyle(PdfGraphics graphics, PdfBrush brush, PdfPen pen, + void _drawCloudStyle(PdfGraphics graphics, PdfBrush? brush, PdfPen? pen, double radius, double overlap, List points, bool isAppearance) { if (_isClockWise(points)) { - final List sortedPoints = List(points.length); - for (int i = points.length - 1, j = 0; i >= 0; i--, j++) { - sortedPoints[j] = points[i]; - } - points = sortedPoints; + points = List.generate( + points.length, (i) => (points[points.length - (i + 1)])); } // Create a list of circles @@ -1577,15 +1541,11 @@ abstract class PdfAnnotation implements _IPdfWrapper { sweepAngel); } path.closeFigure(); - List tempPoints = List(path._points.length); + PdfPath pdfPath = PdfPath(); if (isAppearance) { for (int i = 0; i < path._points.length; i++) { - tempPoints[i] = Offset(path._points[i].dx, -path._points[i].dy); + pdfPath._points.add(Offset(path._points[i].dx, -path._points[i].dy)); } - } - PdfPath pdfPath = PdfPath(); - if (isAppearance) { - pdfPath._points.addAll(tempPoints); } else { pdfPath._points.addAll(path._points); } @@ -1604,15 +1564,11 @@ abstract class PdfAnnotation implements _IPdfWrapper { curr.endAngle + incise); } path.closeFigure(); - tempPoints = List(path._points.length); + pdfPath = PdfPath(); if (isAppearance) { for (int i = 0; i < path._points.length; i++) { - tempPoints[i] = Offset(path._points[i].dx, -path._points[i].dy); + pdfPath._points.add(Offset(path._points[i].dx, -path._points[i].dy)); } - } - pdfPath = PdfPath(); - if (isAppearance) { - pdfPath._points.addAll(tempPoints); } else { pdfPath._points.addAll(path._points); } @@ -1648,11 +1604,11 @@ abstract class PdfAnnotation implements _IPdfWrapper { } // Gets the value. - static _IPdfPrimitive _getValue(_PdfDictionary dictionary, - _PdfCrossTable crossTable, String value, bool inheritable) { - _IPdfPrimitive primitive; + static _IPdfPrimitive? _getValue(_PdfDictionary dictionary, + _PdfCrossTable? crossTable, String value, bool inheritable) { + _IPdfPrimitive? primitive; if (dictionary.containsKey(value)) { - primitive = crossTable._getObject(dictionary[value]); + primitive = crossTable!._getObject(dictionary[value]); } else { if (inheritable) { primitive = _searchInParents(dictionary, crossTable, value); @@ -1662,17 +1618,17 @@ abstract class PdfAnnotation implements _IPdfWrapper { } // Searches the in parents. - static _IPdfPrimitive _searchInParents( - _PdfDictionary dictionary, _PdfCrossTable crossTable, String value) { - _IPdfPrimitive primitive; - _PdfDictionary dic = dictionary; + static _IPdfPrimitive? _searchInParents( + _PdfDictionary dictionary, _PdfCrossTable? crossTable, String value) { + _IPdfPrimitive? primitive; + _PdfDictionary? dic = dictionary; while ((primitive == null) && (dic != null)) { if (dic.containsKey(value)) { - primitive = crossTable._getObject(dic[value]); + primitive = crossTable!._getObject(dic[value]); } else { if (dic.containsKey(_DictionaryProperties.parent)) { - dic = crossTable._getObject(dic[_DictionaryProperties.parent]) - as _PdfDictionary; + dic = crossTable!._getObject(dic[_DictionaryProperties.parent]) + as _PdfDictionary?; } else { dic = null; } @@ -1680,10 +1636,19 @@ abstract class PdfAnnotation implements _IPdfWrapper { } return primitive; } + + //Overrides + @override + _IPdfPrimitive? get _element => _dictionary; + + @override + set _element(_IPdfPrimitive? value) { + throw ArgumentError('Primitive element can\'t be set'); + } } class _CloudStyleArc { - Offset point; + late Offset point; double endAngle = 0; double startAngle = 0; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation_border.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation_border.dart index d86b15db5..03b47d625 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation_border.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation_border.dart @@ -10,91 +10,99 @@ class PdfAnnotationBorder implements _IPdfWrapper { /// /// The borderStyle and dashArray only used for shape annotations. PdfAnnotationBorder( - [double borderWidth, - double horizontalRadius, - double verticalRadius, - PdfBorderStyle borderStyle, - int dashArray]) { + [double? borderWidth, + double? horizontalRadius, + double? verticalRadius, + PdfBorderStyle? borderStyle, + int? dashArray]) { _array._add(_PdfNumber(0)); _array._add(_PdfNumber(0)); _array._add(_PdfNumber(1)); this.horizontalRadius = horizontalRadius ??= 0; width = borderWidth ??= 1; this.verticalRadius = verticalRadius ??= 0; - this.borderStyle = borderStyle ??= PdfBorderStyle.solid; + _borderStyle = borderStyle ??= PdfBorderStyle.solid; + _dictionary._setName( + _PdfName(_DictionaryProperties.s), _styleToString(_borderStyle)); if (dashArray != null) { this.dashArray = dashArray; } } + PdfAnnotationBorder._asWidgetBorder() { + _dictionary.setProperty( + _DictionaryProperties.type, _PdfName(_DictionaryProperties.border)); + _borderStyle = PdfBorderStyle.solid; + _dictionary._setName( + _PdfName(_DictionaryProperties.s), _styleToString(_borderStyle)); + _isWidgetBorder = true; + } + // fields double _horizontalRadius = 0; double _verticalRadius = 0; double _borderWidth = 1; - int _dashArray; - PdfBorderStyle _borderStyle; - //ignore: prefer_final_fields + int? _dashArray; + late PdfBorderStyle _borderStyle; bool _isLineBorder = false; + bool _isWidgetBorder = false; final _PdfDictionary _dictionary = _PdfDictionary(); final _PdfArray _array = _PdfArray(); // properties - /// Gets a horizontal corner radius of the annotations. + /// Gets or sets the horizontal corner radius of the annotations. double get horizontalRadius => _horizontalRadius; - /// Sets a horizontal corner radius of the annotations. set horizontalRadius(double value) { - if (value != _horizontalRadius && value != null) { + if (value != _horizontalRadius) { _horizontalRadius = value; _setNumber(0, value); } } - /// Gets a vertical corner radius of the annotation. + /// Gets or sets the vertical corner radius of the annotation. double get verticalRadius => _verticalRadius; - /// Sets a vertical corner radius of the annotation. set verticalRadius(double value) { - if (value != null && value != _verticalRadius) { + if (value != _verticalRadius) { _verticalRadius = value; _setNumber(1, value); } } - /// Gets the width of annotation's border. + /// Gets or sets the width of annotation's border. double get width => _borderWidth; - /// Sets the width of annotation's border. set width(double value) { - if (value != null && value != _borderWidth) { + if (value != _borderWidth) { _borderWidth = value; - _setNumber(2, value); + if (!_isWidgetBorder) { + _setNumber(2, value); + } _dictionary._setNumber(_DictionaryProperties.w, _borderWidth.toInt()); } } - /// Gets the border style. + /// Gets or sets the border style. PdfBorderStyle get borderStyle => _borderStyle; - /// Sets the border style. set borderStyle(PdfBorderStyle value) { - if (value != null && value != _borderStyle) { + if (value != _borderStyle) { _borderStyle = value; _dictionary._setName( _PdfName(_DictionaryProperties.s), _styleToString(_borderStyle)); } } - /// Gets the line dash of the annotation. - int get dashArray => _dashArray; + /// Gets or sets the line dash of the annotation. + int? get dashArray => _dashArray; - /// Sets the line dash of the annotation. - set dashArray(int value) { + set dashArray(int? value) { if (value != null && _dashArray != value) { _dashArray = value; final _PdfArray dasharray = _PdfArray(); - dasharray._add(_PdfNumber(_dashArray)); - dasharray._add(_PdfNumber(_dashArray)); + dasharray._add(_PdfNumber(_dashArray!)); + dasharray._add(_PdfNumber(_dashArray!)); _dictionary.setProperty(_DictionaryProperties.d, dasharray); } } @@ -104,32 +112,25 @@ class PdfAnnotationBorder implements _IPdfWrapper { number.value = value; } - String _styleToString(PdfBorderStyle borderStyle) { + String _styleToString(PdfBorderStyle? borderStyle) { switch (borderStyle) { - case PdfBorderStyle.solid: - return 'S'; - break; case PdfBorderStyle.beveled: return 'B'; - break; case PdfBorderStyle.dashed: case PdfBorderStyle.dot: return 'D'; - break; case PdfBorderStyle.inset: return 'I'; - break; case PdfBorderStyle.underline: return 'U'; - break; default: - return 's'; + return 'S'; } } @override _IPdfPrimitive get _element { - if (_isLineBorder) { + if (_isLineBorder || _isWidgetBorder) { return _dictionary; } else { return _array; @@ -137,7 +138,8 @@ class PdfAnnotationBorder implements _IPdfWrapper { } @override - set _element(_IPdfPrimitive value) { + // ignore: unused_element + set _element(_IPdfPrimitive? value) { _element = value; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation_collection.dart index ba0d05f8a..9eb5a70e2 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation_collection.dart @@ -7,15 +7,13 @@ class PdfAnnotationCollection extends PdfObjectCollection /// Initializes a new instance of the [PdfAnnotationCollection] /// class with the specified page. PdfAnnotationCollection(PdfPage page) : super() { - ArgumentError.checkNotNull(page, 'page'); _page = page; } PdfAnnotationCollection._(PdfPage page) : super() { - ArgumentError.checkNotNull(page, 'page'); _page = page; for (int i = 0; i < _page._terminalAnnotation.length; ++i) { - final PdfAnnotation annot = _getAnnotation(i); + final PdfAnnotation? annot = _getAnnotation(i); if (annot != null) { _doAdd(annot); } @@ -23,29 +21,25 @@ class PdfAnnotationCollection extends PdfObjectCollection } // Fields - PdfPage _page; + late PdfPage _page; _PdfArray _annotations = _PdfArray(); bool _flatten = false; // Properties - /// Gets the annotation flatten. - bool get flatten => _flatten; - /// Sets the annotation flatten. - set flatten(bool value) { - ArgumentError.checkNotNull(value, 'flatten can be set to null'); + set _flattenAll(bool value) { _flatten = value; if (_flatten && _page._document != null) { - final _PdfCrossTable cross = _page._crossTable; + final _PdfCrossTable? cross = _page._crossTable; if (cross != null && _page._dictionary.containsKey(_DictionaryProperties.annots)) { - final _PdfArray annots = + final _PdfArray? annots = cross._getObject(_page._dictionary[_DictionaryProperties.annots]) - as _PdfArray; + as _PdfArray?; if (annots != null) { for (int count = 0; count < annots.count; ++count) { - final _PdfDictionary annotDicrionary = - cross._getObject(annots[count]) as _PdfDictionary; + final _PdfDictionary? annotDicrionary = + cross._getObject(annots[count]) as _PdfDictionary?; if (annotDicrionary != null) { if (annotDicrionary.containsKey(_DictionaryProperties.ft)) { annotDicrionary.remove(_DictionaryProperties.ft); @@ -62,7 +56,7 @@ class PdfAnnotationCollection extends PdfObjectCollection /// Gets the annotations array. _PdfArray get _internalAnnotations => _annotations; - set _internalAnnotations(_PdfArray value) { + set _internalAnnotations(_PdfArray? value) { if (value != null) { _annotations = value; } @@ -74,11 +68,11 @@ class PdfAnnotationCollection extends PdfObjectCollection if (index < 0 || index >= count) { throw ArgumentError('$index, Index is out of range.'); } - final PdfAnnotation annotation = _list[index]; + final PdfAnnotation annotation = _list[index] as PdfAnnotation; if (!_page._isLoadedPage) { return annotation; } else { - annotation != null && annotation._isLoadedAnnotation + annotation._isLoadedAnnotation ? annotation._page = _page : annotation._setPage(_page); } @@ -87,33 +81,37 @@ class PdfAnnotationCollection extends PdfObjectCollection /// Adds a new annotation to the collection. int add(PdfAnnotation annotation) { - ArgumentError.checkNotNull(annotation, 'annotation'); return _doAdd(annotation); } /// Removes the specified annotation from the collection. void remove(PdfAnnotation annot) { - ArgumentError.checkNotNull(annot); _doRemove(annot); } /// Determines whether a specified annotation is in the annotation collection. bool contains(PdfAnnotation annotation) { - ArgumentError.checkNotNull(annotation); return _list.contains(annotation); } + /// Flatten all the annotations. + /// + /// The flatten will add at the time of saving the current document. + void flattenAllAnnotations() { + _flattenAll = true; + } + // implementation int _doAdd(PdfAnnotation annot) { - if (flatten == true) { - annot.flatten = true; + if (_flatten) { + annot._flatten = true; } annot._setPage(_page); if (_page._isLoadedPage) { - _PdfArray array; + _PdfArray? array; if (_page._dictionary.containsKey(_DictionaryProperties.annots)) { array = _PdfCrossTable._dereference( - _page._dictionary[_DictionaryProperties.annots]) as _PdfArray; + _page._dictionary[_DictionaryProperties.annots]) as _PdfArray?; } array ??= _PdfArray(); final _PdfReferenceHolder reference = @@ -135,7 +133,8 @@ class PdfAnnotationCollection extends PdfObjectCollection if (!result) { for (int i = 0; i < array._elements.length; i++) { if (array._elements[i] is _PdfReferenceHolder) { - final _PdfReferenceHolder holder = array._elements[i]; + final _PdfReferenceHolder holder = + array._elements[i] as _PdfReferenceHolder; if (holder.object == reference.object) { result = true; break; @@ -148,91 +147,144 @@ class PdfAnnotationCollection extends PdfObjectCollection void _doRemove(PdfAnnotation annot) { if (_page._isLoadedPage) { - final _PdfDictionary pageDic = _page._dictionary; - _PdfArray annots; - if (pageDic.containsKey(_DictionaryProperties.annots)) { - annots = _page._crossTable - ._getObject(pageDic[_DictionaryProperties.annots]) as _PdfArray; - } else { - annots = _PdfArray(); - } - annot._dictionary - .setProperty(_DictionaryProperties.p, _PdfReferenceHolder(_page)); - for (int i = 0; i < annots.count; i++) { - if (annot._dictionary == - _page._crossTable._getObject(annots[i]) as _PdfDictionary) { - annots._elements.removeAt(i); - annots._isChanged = true; - break; + _removeFromDictionaries(annot); + } + final int index = _list.indexOf(annot); + _annotations._elements.removeAt(index); + _list.removeAt(index); + } + + void _removeFromDictionaries(PdfAnnotation annot) { + final _PdfDictionary pageDic = _page._dictionary; + _PdfArray? annots; + if (pageDic.containsKey(_DictionaryProperties.annots)) { + annots = _page._crossTable! + ._getObject(pageDic[_DictionaryProperties.annots]) as _PdfArray?; + } else { + annots = _PdfArray(); + } + if (annot._dictionary.containsKey(_DictionaryProperties.popup)) { + final _IPdfPrimitive? popUpDictionary = + (annot._dictionary[_PdfName(_DictionaryProperties.popup)] + is _PdfReferenceHolder) + ? (annot._dictionary[_PdfName(_DictionaryProperties.popup)] + as _PdfReferenceHolder) + .object + : annot._dictionary[_PdfName(_DictionaryProperties.popup)]; + if (popUpDictionary is _PdfDictionary) { + for (int i = 0; i < annots!.count; i++) { + if (popUpDictionary == + _page._crossTable!._getObject(annots[i]) as _PdfDictionary?) { + annots._elements.removeAt(i); + annots._isChanged = true; + break; + } + } + final _IPdfPrimitive popUpObj = + _page._crossTable!._getObject(popUpDictionary)!; + final int? popUpIndex = _page._crossTable!._items!._lookFor(popUpObj); + if (popUpIndex != null && popUpIndex != -1) { + _page._crossTable!._items!._objectCollection!.removeAt(popUpIndex); } + _removeAllReference(popUpObj); + _page._terminalAnnotation.remove(popUpDictionary); + } + } + for (int i = 0; i < annots!.count; i++) { + if (annot._dictionary == + _page._crossTable!._getObject(annots[i]) as _PdfDictionary?) { + annots._elements.removeAt(i); + annots._isChanged = true; + break; } - _page._dictionary.setProperty(_DictionaryProperties.annots, annots); - } else { - final int index = _list.indexOf(annot); - _annotations._elements.removeAt(index); - _list.removeAt(index); + } + annot._dictionary._isChanged = false; + _page._dictionary.setProperty(_DictionaryProperties.annots, annots); + } + + void _removeAllReference(_IPdfPrimitive obj) { + final _IPdfPrimitive? dictionary = + obj is _PdfReferenceHolder ? obj.object : obj; + if (dictionary is _PdfDictionary) { + dictionary._items!.forEach((k, v) { + if ((v is _PdfReferenceHolder || v is _PdfDictionary) && + k!._name != _DictionaryProperties.p && + k._name != _DictionaryProperties.parent) { + final _IPdfPrimitive newobj = _page._crossTable!._getObject(v)!; + final int? index = _page._crossTable!._items!._lookFor(newobj); + if (index != null && index != -1) { + _page._crossTable!._items!._objectCollection!.removeAt(index); + } + _removeAllReference(v!); + (_PdfCrossTable._dereference(v) as _PdfStream) + ..dispose() + .._isChanged = false; + } + }); } } // Gets the annotation. - PdfAnnotation _getAnnotation(int index) { + PdfAnnotation? _getAnnotation(int index) { final _PdfDictionary dictionary = _page._terminalAnnotation[index]; - final _PdfCrossTable crossTable = _page._crossTable; - PdfAnnotation annot; - if (dictionary != null && - dictionary.containsKey(_DictionaryProperties.subtype)) { + final _PdfCrossTable? crossTable = _page._crossTable; + PdfAnnotation? annot; + if (dictionary.containsKey(_DictionaryProperties.subtype)) { final _PdfName name = PdfAnnotation._getValue( dictionary, crossTable, _DictionaryProperties.subtype, true) as _PdfName; final _PdfAnnotationTypes type = _getAnnotationType(name, dictionary, crossTable); - final _PdfArray rectValue = + final _PdfArray? rectValue = _PdfCrossTable._dereference(dictionary[_DictionaryProperties.rect]) - as _PdfArray; + as _PdfArray?; if (rectValue != null) { String text = ''; if (dictionary.containsKey(_DictionaryProperties.contents)) { - final _PdfString str = _PdfCrossTable._dereference( - dictionary[_DictionaryProperties.contents]) as _PdfString; + final _PdfString? str = _PdfCrossTable._dereference( + dictionary[_DictionaryProperties.contents]) as _PdfString?; if (str != null) { text = str.value.toString(); } } switch (type) { case _PdfAnnotationTypes.documentLinkAnnotation: - annot = _createDocumentLinkAnnotation(dictionary, crossTable); + annot = _createDocumentLinkAnnotation(dictionary, crossTable!); break; case _PdfAnnotationTypes.linkAnnotation: if (dictionary.containsKey(_DictionaryProperties.a)) { - final _PdfDictionary remoteLinkDic = _PdfCrossTable._dereference( - dictionary[_DictionaryProperties.a]) as _PdfDictionary; + final _PdfDictionary? remoteLinkDic = _PdfCrossTable._dereference( + dictionary[_DictionaryProperties.a]) as _PdfDictionary?; if (remoteLinkDic != null && remoteLinkDic.containsKey(_DictionaryProperties.s)) { - _PdfName gotor; + _PdfName? gotor; gotor = _PdfCrossTable._dereference( - remoteLinkDic[_DictionaryProperties.s]) as _PdfName; + remoteLinkDic[_DictionaryProperties.s]) as _PdfName?; if (gotor != null && gotor._name == 'URI') { - annot = _createLinkAnnotation(dictionary, crossTable, text); + annot = _createLinkAnnotation(dictionary, crossTable!, text); } } } else { - annot = _createLinkAnnotation(dictionary, crossTable, text); + annot = _createLinkAnnotation(dictionary, crossTable!, text); } break; case _PdfAnnotationTypes.lineAnnotation: - annot = _createLineAnnotation(dictionary, crossTable, text); + annot = _createLineAnnotation(dictionary, crossTable!, text); break; case _PdfAnnotationTypes.circleAnnotation: - annot = _createEllipseAnnotation(dictionary, crossTable, text); + annot = _createEllipseAnnotation(dictionary, crossTable!, text); break; case _PdfAnnotationTypes.rectangleAnnotation: - annot = _createRectangleAnnotation(dictionary, crossTable, text); + annot = _createRectangleAnnotation(dictionary, crossTable!, text); break; case _PdfAnnotationTypes.polygonAnnotation: - annot = _createPolygonAnnotation(dictionary, crossTable, text); + annot = _createPolygonAnnotation(dictionary, crossTable!, text); break; case _PdfAnnotationTypes.textWebLinkAnnotation: - annot = _createTextWebLinkAnnotation(dictionary, crossTable, text); + annot = _createTextWebLinkAnnotation(dictionary, crossTable!, text); + break; + case _PdfAnnotationTypes.widgetAnnotation: + annot = _createWidgetAnnotation(dictionary, crossTable!); break; default: break; @@ -247,43 +299,41 @@ class PdfAnnotationCollection extends PdfObjectCollection /// Gets the type of the annotation. _PdfAnnotationTypes _getAnnotationType( - _PdfName name, _PdfDictionary dictionary, _PdfCrossTable crossTable) { - final String str = name._name; + _PdfName name, _PdfDictionary dictionary, _PdfCrossTable? crossTable) { + final String str = name._name!; _PdfAnnotationTypes type = _PdfAnnotationTypes.noAnnotation; switch (str.toLowerCase()) { case 'link': - _PdfDictionary linkDic; + _PdfDictionary? linkDic; if (dictionary.containsKey(_DictionaryProperties.a)) { linkDic = _PdfCrossTable._dereference(dictionary[_DictionaryProperties.a]) - as _PdfDictionary; + as _PdfDictionary?; } if (linkDic != null && linkDic.containsKey(_DictionaryProperties.s)) { name = _PdfCrossTable._dereference(linkDic[_DictionaryProperties.s]) as _PdfName; - if (name != null) { - final _PdfArray border = (_PdfCrossTable._dereference( - dictionary[_DictionaryProperties.border]) is _PdfArray) - ? _PdfCrossTable._dereference( - dictionary[_DictionaryProperties.border]) - : null; - final bool mType = _findAnnotation(border); - if (name._name == 'URI') { + final _PdfArray? border = (_PdfCrossTable._dereference( + dictionary[_DictionaryProperties.border]) is _PdfArray) + ? _PdfCrossTable._dereference( + dictionary[_DictionaryProperties.border]) as _PdfArray? + : null; + final bool mType = _findAnnotation(border); + if (name._name == 'URI') { + type = _PdfAnnotationTypes.linkAnnotation; + if (!mType) { type = _PdfAnnotationTypes.linkAnnotation; - if (!mType) { - type = _PdfAnnotationTypes.linkAnnotation; - } else { - type = _PdfAnnotationTypes.textWebLinkAnnotation; - } - } else if (name._name == 'GoToR') { - type = _PdfAnnotationTypes.linkAnnotation; - } else if (name._name == 'GoTo') { - type = _PdfAnnotationTypes.documentLinkAnnotation; + } else { + type = _PdfAnnotationTypes.textWebLinkAnnotation; } + } else if (name._name == 'GoToR') { + type = _PdfAnnotationTypes.linkAnnotation; + } else if (name._name == 'GoTo') { + type = _PdfAnnotationTypes.documentLinkAnnotation; } } else if (dictionary.containsKey(_DictionaryProperties.subtype)) { - final _PdfName strText = _PdfCrossTable._dereference( - dictionary[_DictionaryProperties.subtype]) as _PdfName; + final _PdfName? strText = _PdfCrossTable._dereference( + dictionary[_DictionaryProperties.subtype]) as _PdfName?; if (strText != null) { switch (strText._name) { case 'Link': @@ -305,6 +355,9 @@ class PdfAnnotationCollection extends PdfObjectCollection case 'polygon': type = _PdfAnnotationTypes.polygonAnnotation; break; + case 'widget': + type = _PdfAnnotationTypes.widgetAnnotation; + break; default: break; } @@ -376,28 +429,38 @@ class PdfAnnotationCollection extends PdfObjectCollection return annot; } - bool _findAnnotation(_PdfArray arr) { + //Creates the widget annotation. + PdfAnnotation _createWidgetAnnotation( + _PdfDictionary dictionary, _PdfCrossTable crossTable) { + final PdfAnnotation annot = _WidgetAnnotation._(dictionary, crossTable); + annot._setPage(_page); + return annot; + } + + bool _findAnnotation(_PdfArray? arr) { if (arr == null) { return false; } for (int i = 0; i < arr.count; i++) { if (arr[i] is _PdfArray) { - final _PdfArray temp = arr[i]; + final _PdfArray temp = arr[i] as _PdfArray; for (int j = 0; j < temp.count; j++) { - final _PdfNumber value = (temp[j] is _PdfNumber) ? temp[j] : null; - int val = 0; + final _PdfNumber? value = + (temp[j] is _PdfNumber) ? temp[j] as _PdfNumber? : null; + int? val = 0; if (value != null) { - val = value.value; + val = value.value as int?; } - if (val > 0) { + if (val! > 0) { return false; } } } else { int val = 0; - final _PdfNumber value = (arr[i] is _PdfNumber) ? arr[i] : null; + final _PdfNumber? value = + (arr[i] is _PdfNumber) ? arr[i] as _PdfNumber? : null; if (value != null) { - val = value.value.toInt(); + val = value.value!.toInt(); } if (val > 0) { return false; @@ -407,6 +470,41 @@ class PdfAnnotationCollection extends PdfObjectCollection return true; } + _PdfArray? _rearrange(_PdfReference reference, int tabIndex, int index) { + final _PdfArray? annots = _page._crossTable! + ._getObject(_page._dictionary[_DictionaryProperties.annots]) + as _PdfArray?; + if (annots != null) { + if (tabIndex > annots.count) { + tabIndex = 0; + } + if (index >= annots.count) { + index = _page._annotsReference._indexOf(reference); + } + final _IPdfPrimitive? annotReference = annots._elements[index]; + if (annotReference != null && annotReference is _PdfReferenceHolder) { + final _IPdfPrimitive? annotObject = annotReference.object; + if (annotObject != null && + annotObject is _PdfDictionary && + annotObject.containsKey(_DictionaryProperties.parent)) { + final _IPdfPrimitive? annotParent = + annotObject[_DictionaryProperties.parent]; + if (annotReference.reference == reference || + (annotParent != null && + annotParent is _PdfReferenceHolder && + reference == annotParent.reference)) { + final _IPdfPrimitive? temp = annots[index]; + if (temp != null) { + annots._elements[index] = annots[tabIndex]; + annots._elements[tabIndex] = temp; + } + } + } + } + } + return annots; + } + @override - _IPdfPrimitive _element; + _IPdfPrimitive? _element; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_appearance.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_appearance.dart index 57df3b9cc..1ac639eb9 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_appearance.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_appearance.dart @@ -3,15 +3,16 @@ part of pdf; /// Represents the appearance of an annotation. class PdfAppearance implements _IPdfWrapper { // Constructor - /// Initializes a new instance of the [PdfAppearance] class. + /// Initializes a instance of the [PdfAppearance] class. PdfAppearance(PdfAnnotation annotation) : super() { _annotation = annotation; } // Fields - PdfTemplate _templateNormal; - PdfAnnotation _annotation; + PdfTemplate? _templateNormal; + late PdfAnnotation _annotation; final _PdfDictionary _dictionary = _PdfDictionary(); + PdfTemplate? _templatePressed; // Properties /// Gets PdfTmplate object which applied to annotation in normal state. @@ -22,13 +23,11 @@ class PdfAppearance implements _IPdfWrapper { _dictionary.setProperty( _DictionaryProperties.n, _PdfReferenceHolder(_templateNormal)); } - return _templateNormal; + return _templateNormal!; } /// Sets PdfTmplate object which applied to annotation in normal state. set normal(PdfTemplate value) { - ArgumentError.checkNotNull(value); - if (_templateNormal != value) { _templateNormal = value; _dictionary.setProperty( @@ -36,12 +35,32 @@ class PdfAppearance implements _IPdfWrapper { } } + /// Gets or sets [PdfTemplate] object which applied to an annotation when mouse button is pressed. + PdfTemplate get pressed { + if (_templatePressed == null) { + _templatePressed = + PdfTemplate(_annotation.bounds.width, _annotation.bounds.height); + _dictionary.setProperty( + _DictionaryProperties.d, _PdfReferenceHolder(_templatePressed)); + } + return _templatePressed!; + } + + set pressed(PdfTemplate value) { + if (value != _templatePressed) { + _templatePressed = value; + _dictionary.setProperty( + _DictionaryProperties.d, _PdfReferenceHolder(_templatePressed)); + } + } + // Implementation @override _IPdfPrimitive get _element => _dictionary; @override - set _element(_IPdfPrimitive value) { + // ignore: unused_element + set _element(_IPdfPrimitive? value) { _element = value; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_document_link_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_document_link_annotation.dart index a2acb8a57..430fee97a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_document_link_annotation.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_document_link_annotation.dart @@ -2,13 +2,38 @@ part of pdf; /// Represents an annotation object with holds link on /// another location within a document. +/// ```dart +/// //Create a new Pdf document +/// PdfDocument document = PdfDocument(); +/// //Create a document link and add to the PDF page. +/// document.pages.add().annotations.add(PdfDocumentLinkAnnotation( +/// Rect.fromLTWH(10, 40, 30, 30), +/// PdfDestination(document.pages.add(), Offset(10, 0)))); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { // constructor /// Initializes new [PdfDocumentLinkAnnotation] instance /// with specified bounds and destination. + /// ```dart + /// //Create a new Pdf document + /// PdfDocument document = PdfDocument(); + /// //Create a new Pdf document + /// PdfDocument document = PdfDocument(); + /// //Create a document link and add to the PDF page. + /// document.pages.add().annotations.add(PdfDocumentLinkAnnotation( + /// Rect.fromLTWH(10, 40, 30, 30), + /// PdfDestination(document.pages.add(), Offset(10, 0)))); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfDocumentLinkAnnotation(Rect bounds, PdfDestination destination) : super(bounds) { - ArgumentError.checkNotNull(destination, 'destination'); this.destination = destination; } @@ -17,51 +42,69 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { : super._(dictionary, crossTable); // fields - PdfDestination _destination; + PdfDestination? _destination; // properties - /// Gets the destination of the annotation. - PdfDestination get destination => + /// Gets or sets the destination of the annotation. + /// + /// ```dart + /// //Create a new Pdf document + /// PdfDocument document = PdfDocument(); + /// //Create PDF page. + /// PdfPage page = document.pages.add(); + /// //Create a document link + /// PdfDocumentLinkAnnotation documentLinkAnnotation = PdfDocumentLinkAnnotation( + /// Rect.fromLTWH(10, 40, 30, 30), + /// PdfDestination(document.pages.add(), Offset(10, 0))); + /// //Gets the destination and set the destination mode. + /// documentLinkAnnotation.destination.mode = PdfDestinationMode.fitToPage; + /// //Add the document link to the page + /// page.annotations.add(documentLinkAnnotation); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfDestination? get destination => _isLoadedAnnotation ? _obtainDestination() : _destination; - - /// Sets the destination of the annotation. - set destination(PdfDestination value) { - ArgumentError.checkNotNull(value, 'destibation'); - if (value != _destination) { - _destination = value; - } - if (_isLoadedAnnotation) { - _dictionary.setProperty(_DictionaryProperties.dest, value); + set destination(PdfDestination? value) { + if (value != null) { + if (value != _destination) { + _destination = value; + } + if (_isLoadedAnnotation) { + _dictionary.setProperty(_DictionaryProperties.dest, value); + } } } // implementation // Gets the destination of the document link annotation - PdfDestination _obtainDestination() { - PdfDestination _destination; + PdfDestination? _obtainDestination() { + PdfDestination? _destination; if (_dictionary.containsKey(_DictionaryProperties.dest)) { - final _IPdfPrimitive obj = + final _IPdfPrimitive? obj = _crossTable._getObject(_dictionary[_DictionaryProperties.dest]); - _PdfArray array; + _PdfArray? array; if (obj is _PdfArray) { array = obj; } else if (_crossTable._document != null && - _crossTable._document._isLoadedDocument) { + _crossTable._document!._isLoadedDocument) { if (obj is _PdfName || obj is _PdfString) { - array = _crossTable._document._getNamedDestination(obj); + array = _crossTable._document!._getNamedDestination(obj!); } } PdfPage page; if (array != null && array[0] is _PdfReferenceHolder) { - final _PdfDictionary dic = _crossTable - ._getObject(array[0] as _PdfReferenceHolder) as _PdfDictionary; - page = _crossTable._document.pages._getPage(dic); - final _PdfName mode = array[1] as _PdfName; - if (page != null && mode != null) { + final _PdfDictionary? dic = _crossTable + ._getObject(array[0] as _PdfReferenceHolder) as _PdfDictionary?; + page = _crossTable._document!.pages._getPage(dic); + final _PdfName? mode = array[1] as _PdfName?; + if (mode != null) { if (mode._name == 'XYZ') { - _PdfNumber left; - _PdfNumber top; - _PdfNumber zoom; + _PdfNumber? left; + _PdfNumber? top; + _PdfNumber? zoom; if (array[2] is _PdfNumber) { left = array[2] as _PdfNumber; } @@ -72,30 +115,31 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { zoom = array[4] as _PdfNumber; } final double topValue = - (top == null) ? 0 : page.size.height - (top.value.toDouble()); - final double leftValue = (left == null) ? 0 : left.value.toDouble(); + (top == null) ? 0 : page.size.height - (top.value!.toDouble()); + final double leftValue = + (left == null) ? 0 : left.value!.toDouble(); _destination = PdfDestination(page, Offset(leftValue, topValue)); if (zoom != null) { - _destination.zoom = zoom.value.toDouble(); + _destination.zoom = zoom.value!.toDouble(); } _destination.mode = PdfDestinationMode.location; } else if (mode._name == 'Fit') { _destination = PdfDestination(page); _destination.mode = PdfDestinationMode.fitToPage; } else if (mode._name == 'FitH') { - _PdfNumber top; + late _PdfNumber top; if (array[2] is _PdfNumber) { top = array[2] as _PdfNumber; } - final double topValue = (page.size.height - top.value).toDouble(); + final double topValue = (page.size.height - top.value!).toDouble(); _destination = PdfDestination(page, Offset(0, topValue)); _destination.mode = PdfDestinationMode.fitH; } else if (mode._name == 'FitR') { if (array.count == 6) { - final double left = (array[2] as _PdfNumber).value.toDouble(); - final double top = (array[3] as _PdfNumber).value.toDouble(); - final double width = (array[4] as _PdfNumber).value.toDouble(); - final double height = (array[5] as _PdfNumber).value.toDouble(); + final double left = (array[2] as _PdfNumber).value!.toDouble(); + final double top = (array[3] as _PdfNumber).value!.toDouble(); + final double width = (array[4] as _PdfNumber).value!.toDouble(); + final double height = (array[5] as _PdfNumber).value!.toDouble(); _destination = PdfDestination._(page, _Rectangle(left, top, width, height)); _destination.mode = PdfDestinationMode.fitR; @@ -106,47 +150,45 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { } else if (_dictionary.containsKey(_DictionaryProperties.a) && (_destination == null)) { _IPdfPrimitive obj = - _crossTable._getObject(_dictionary[_DictionaryProperties.a]); + _crossTable._getObject(_dictionary[_DictionaryProperties.a])!; final _PdfDictionary destDic = obj as _PdfDictionary; - obj = destDic[_DictionaryProperties.d]; + obj = destDic[_DictionaryProperties.d]!; if (obj is _PdfReferenceHolder) { - obj = (obj as _PdfReferenceHolder).object; + obj = obj.object!; } - _PdfArray array; + _PdfArray? array; if (obj is _PdfArray) { array = obj; } else if (_crossTable._document != null && - _crossTable._document._isLoadedDocument) { + _crossTable._document!._isLoadedDocument) { if (obj is _PdfName || obj is _PdfString) { - array = _crossTable._document._getNamedDestination(obj); + array = _crossTable._document!._getNamedDestination(obj); } } if (array != null && array[0] is _PdfReferenceHolder) { final _PdfReferenceHolder holder = array[0] as _PdfReferenceHolder; - PdfPage page; - if (holder != null) { - final _IPdfPrimitive primitiveObj = - _PdfCrossTable._dereference(holder); - final _PdfDictionary dic = primitiveObj as _PdfDictionary; - if (dic != null) { - page = _crossTable._document.pages._getPage(dic); - } + PdfPage? page; + final _IPdfPrimitive? primitiveObj = + _PdfCrossTable._dereference(holder); + final _PdfDictionary? dic = primitiveObj as _PdfDictionary?; + if (dic != null) { + page = _crossTable._document!.pages._getPage(dic); } if (page != null) { final _PdfName mode = array[1] as _PdfName; if (mode._name == 'FitBH' || mode._name == 'FitH') { - _PdfNumber top; + _PdfNumber? top; if (array[2] is _PdfNumber) { top = array[2] as _PdfNumber; } final double topValue = - (top == null) ? 0 : page.size.height - (top.value.toDouble()); + (top == null) ? 0 : page.size.height - (top.value!.toDouble()); _destination = PdfDestination(page, Offset(0, topValue)); _destination.mode = PdfDestinationMode.fitH; } else if (mode._name == 'XYZ') { - _PdfNumber left; - _PdfNumber top; - _PdfNumber zoom; + _PdfNumber? left; + _PdfNumber? top; + _PdfNumber? zoom; if (array[2] is _PdfNumber) { left = array[2] as _PdfNumber; } @@ -156,17 +198,15 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { if (array[4] is _PdfNumber) { zoom = array[4] as _PdfNumber; } - if (page != null) { - final double topValue = - (top == null) ? 0 : page.size.height - (top.value.toDouble()); - final double leftValue = - (left == null) ? 0 : left.value.toDouble(); - _destination = PdfDestination(page, Offset(leftValue, topValue)); - if (zoom != null) { - _destination.zoom = zoom.value.toDouble(); - } - _destination.mode = PdfDestinationMode.location; + final double topValue = + (top == null) ? 0 : page.size.height - (top.value!.toDouble()); + final double leftValue = + (left == null) ? 0 : left.value!.toDouble(); + _destination = PdfDestination(page, Offset(leftValue, topValue)); + if (zoom != null) { + _destination.zoom = zoom.value!.toDouble(); } + _destination.mode = PdfDestinationMode.location; } else if (mode._name == 'FitR') { if (array.count == 6) { final _PdfNumber left = array[2] as _PdfNumber; @@ -175,12 +215,12 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { final _PdfNumber top = array[5] as _PdfNumber; _destination = PdfDestination._( page, - _Rectangle(left.value.toDouble(), bottom.value.toDouble(), - right.value.toDouble(), top.value.toDouble())); + _Rectangle(left.value!.toDouble(), bottom.value!.toDouble(), + right.value!.toDouble(), top.value!.toDouble())); _destination.mode = PdfDestinationMode.fitR; } } else { - if (page != null && mode._name == 'Fit') { + if (mode._name == 'Fit') { _destination = PdfDestination(page); _destination.mode = PdfDestinationMode.fitToPage; } @@ -196,10 +236,10 @@ class PdfDocumentLinkAnnotation extends PdfLinkAnnotation { super._save(); if (_destination != null) { _dictionary.setProperty( - _PdfName(_DictionaryProperties.dest), _destination._element); + _PdfName(_DictionaryProperties.dest), _destination!._element); } } @override - _IPdfPrimitive _element; + _IPdfPrimitive? _element; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_ellipse_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_ellipse_annotation.dart index 360dc9434..67b498052 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_ellipse_annotation.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_ellipse_annotation.dart @@ -20,15 +20,14 @@ class PdfEllipseAnnotation extends PdfAnnotation { /// document.dispose(); /// ``` PdfEllipseAnnotation(Rect bounds, String text, - {PdfColor color, - PdfColor innerColor, - PdfAnnotationBorder border, - String author, - String subject, - DateTime modifiedDate, - double opacity, - bool setAppearance, - bool flatten}) + {PdfColor? color, + PdfColor? innerColor, + PdfAnnotationBorder? border, + String? author, + String? subject, + DateTime? modifiedDate, + double? opacity, + bool? setAppearance}) : super._( bounds: bounds, text: text, @@ -39,13 +38,11 @@ class PdfEllipseAnnotation extends PdfAnnotation { subject: subject, modifiedDate: modifiedDate, opacity: opacity, - setAppearance: setAppearance, - flatten: flatten); + setAppearance: setAppearance); PdfEllipseAnnotation._( _PdfDictionary dictionary, _PdfCrossTable crossTable, String text) : super._internal(dictionary, crossTable) { - ArgumentError.checkNotNull(text, 'Text must be not null'); _dictionary = dictionary; _crossTable = crossTable; this.text = text; @@ -61,17 +58,17 @@ class PdfEllipseAnnotation extends PdfAnnotation { @override void _save() { - if (page.annotations.flatten) { - flatten = true; + if (page!.annotations._flatten) { + _flatten = true; } - if (flatten || setAppearance || _pdfAppearance != null) { - PdfTemplate appearance; + if (_flatten || setAppearance || _pdfAppearance != null) { + PdfTemplate? appearance; if (_pdfAppearance != null) { - appearance = _pdfAppearance.normal ??= _createAppearance(); + appearance = _pdfAppearance!.normal; } else { appearance = _createAppearance(); } - if (flatten) { + if (_flatten) { if (appearance != null || _isLoadedAnnotation) { if (page != null) { _flattenAnnotation(page, appearance); @@ -85,7 +82,7 @@ class PdfEllipseAnnotation extends PdfAnnotation { } } } - if (!flatten && !_isLoadedAnnotation) { + if (!_flatten && !_isLoadedAnnotation) { super._save(); _dictionary.setProperty(_DictionaryProperties.bs, border); } @@ -94,28 +91,24 @@ class PdfEllipseAnnotation extends PdfAnnotation { } } - void _flattenAnnotation(PdfPage page, PdfTemplate appearance) { + void _flattenAnnotation(PdfPage? page, PdfTemplate? appearance) { if (_isLoadedAnnotation) { final bool isContainsAP = _dictionary.containsKey(_DictionaryProperties.ap); if (isContainsAP && appearance == null) { - _PdfDictionary appearanceDictionary = + _PdfDictionary? appearanceDictionary = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.ap]) - as _PdfDictionary; + as _PdfDictionary?; if (appearanceDictionary != null) { appearanceDictionary = _PdfCrossTable._dereference( - appearanceDictionary[_DictionaryProperties.n]) as _PdfDictionary; + appearanceDictionary[_DictionaryProperties.n]) as _PdfDictionary?; if (appearanceDictionary != null) { final _PdfStream appearanceStream = appearanceDictionary as _PdfStream; - if (appearanceStream != null) { - appearance = PdfTemplate._fromPdfStream(appearanceStream); - if (appearance != null) { - final bool isNormalMatrix = - _validateTemplateMatrix(appearanceDictionary); - _flattenAnnotationTemplate(appearance, isNormalMatrix); - } - } + appearance = PdfTemplate._fromPdfStream(appearanceStream); + final bool isNormalMatrix = + _validateTemplateMatrix(appearanceDictionary); + _flattenAnnotationTemplate(appearance, isNormalMatrix); } else { setAppearance = true; appearance = _createAppearance(); @@ -136,24 +129,24 @@ class PdfEllipseAnnotation extends PdfAnnotation { } } else { final bool isNormalMatrix = - _validateTemplateMatrix(appearance._content); + _validateTemplateMatrix(appearance!._content); _flattenAnnotationTemplate(appearance, isNormalMatrix); } } else { - page.graphics.save(); + page!.graphics.save(); final Rect rectangle = super._calculateTemplateBounds(bounds, page, appearance, true); if (opacity < 1) { page.graphics.setTransparency(opacity, mode: PdfBlendMode.normal); } page.graphics.drawPdfTemplate( - appearance, Offset(rectangle.left, rectangle.top), rectangle.size); + appearance!, Offset(rectangle.left, rectangle.top), rectangle.size); page.annotations.remove(this); page.graphics.restore(); } } - PdfTemplate _createAppearance() { + PdfTemplate? _createAppearance() { if (_isLoadedAnnotation && !setAppearance) { return null; } @@ -171,15 +164,15 @@ class PdfEllipseAnnotation extends PdfAnnotation { if (border.width > 0 && color._alpha != 0) { paintParams._borderPen = mBorderPen; } - PdfBrush mBackBrush; + PdfBrush? mBackBrush; if (innerColor._alpha != 0) { mBackBrush = PdfSolidBrush(innerColor); } paintParams._foreBrush = PdfSolidBrush(color); paintParams._backBrush = mBackBrush; - final PdfGraphics graphics = template.graphics; + final PdfGraphics? graphics = template.graphics; if (opacity < 1) { - graphics.save(); + graphics!.save(); graphics.setTransparency(opacity); } if (_isLoadedAnnotation) { @@ -188,7 +181,7 @@ class PdfEllipseAnnotation extends PdfAnnotation { if (_dictionary.containsKey(_DictionaryProperties.be)) { _drawAppearance(rectangle, borderWidth, graphics, paintParams); } else { - graphics.drawEllipse( + graphics!.drawEllipse( Rect.fromLTWH(rectangle.x + borderWidth, rectangle.y, rectangle.width - border.width, rectangle.height), pen: paintParams._borderPen, @@ -197,14 +190,14 @@ class PdfEllipseAnnotation extends PdfAnnotation { } else { final Rect rect = Rect.fromLTWH(nativeRectangle.left, nativeRectangle.top, nativeRectangle.width, nativeRectangle.height); - graphics.drawEllipse( + graphics!.drawEllipse( Rect.fromLTWH(rect.left + borderWidth, rect.top + borderWidth, rect.width - border.width, rect.height - border.width), pen: paintParams._borderPen, brush: paintParams._backBrush); } if (opacity < 1) { - graphics.restore(); + graphics!.restore(); } return template; } @@ -213,41 +206,41 @@ class PdfEllipseAnnotation extends PdfAnnotation { _Rectangle _obtainStyle( PdfPen mBorderPen, _Rectangle rectangle, double borderWidth) { if (_dictionary.containsKey(_DictionaryProperties.bs)) { - final _PdfDictionary bSDictionary = + final _PdfDictionary? bSDictionary = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.bs]) - as _PdfDictionary; + as _PdfDictionary?; if (bSDictionary != null && bSDictionary.containsKey(_DictionaryProperties.d)) { final _PdfArray dashPatternArray = _PdfCrossTable._dereference(bSDictionary[_DictionaryProperties.d]) as _PdfArray; - final List dashPattern = List(dashPatternArray.count); + final List dashPattern = []; for (int i = 0; i < dashPatternArray.count; i++) { - dashPattern[i] = - (dashPatternArray._elements[i] as _PdfNumber).value.toDouble(); + dashPattern.add( + (dashPatternArray._elements[i]! as _PdfNumber).value!.toDouble()); } mBorderPen.dashStyle = PdfDashStyle.dash; mBorderPen.dashPattern = dashPattern; } } if (!_isBounds && _dictionary[_DictionaryProperties.rd] != null) { - final _PdfArray mRdArray = + final _PdfArray? mRdArray = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.rd]) - as _PdfArray; + as _PdfArray?; if (mRdArray != null) { - final _PdfNumber num1 = mRdArray._elements[0] as _PdfNumber; - final _PdfNumber num2 = mRdArray._elements[1] as _PdfNumber; - final _PdfNumber num3 = mRdArray._elements[2] as _PdfNumber; - final _PdfNumber num4 = mRdArray._elements[3] as _PdfNumber; - rectangle.x = rectangle.x + num1.value; - rectangle.y = rectangle.y + borderWidth + num2.value; - rectangle.width = rectangle.width - (2 * num3.value); + final _PdfNumber num1 = mRdArray._elements[0]! as _PdfNumber; + final _PdfNumber num2 = mRdArray._elements[1]! as _PdfNumber; + final _PdfNumber num3 = mRdArray._elements[2]! as _PdfNumber; + final _PdfNumber num4 = mRdArray._elements[3]! as _PdfNumber; + rectangle.x += num1.value!; + rectangle.y += borderWidth + num2.value!; + rectangle.width = rectangle.width - (2 * num3.value!); rectangle.height = rectangle.height - border.width; - rectangle.height = rectangle.height - (2 * num4.value); + rectangle.height = rectangle.height - (2 * num4.value!); } } else { - rectangle.y = rectangle.y + borderWidth; + rectangle.y += borderWidth; rectangle.height = bounds.height - border.width; } return rectangle; @@ -255,22 +248,23 @@ class PdfEllipseAnnotation extends PdfAnnotation { // Draw appearance for annotation void _drawAppearance(_Rectangle rectangle, double borderWidth, - PdfGraphics graphics, _PaintParams paintParams) { + PdfGraphics? graphics, _PaintParams paintParams) { final PdfPath graphicsPath = PdfPath(); graphicsPath.addEllipse(Rect.fromLTWH( rectangle.x + borderWidth, -rectangle.y - rectangle.height, rectangle.width - border.width, rectangle.height)); - double radius = 0; + double? radius = 0; if (_dictionary.containsKey(_DictionaryProperties.rd)) { - final _PdfArray rdArray = _PdfCrossTable._dereference( - _dictionary._items[_PdfName(_DictionaryProperties.rd)]) as _PdfArray; + final _PdfArray? rdArray = _PdfCrossTable._dereference( + _dictionary._items![_PdfName(_DictionaryProperties.rd)]) + as _PdfArray?; if (rdArray != null) { - radius = (rdArray._elements[0] as _PdfNumber).value; + radius = (rdArray._elements[0]! as _PdfNumber).value as double?; } } - if (radius > 0) { + if (radius! > 0) { final _Rectangle rect = _Rectangle( rectangle.x + borderWidth, -rectangle.y - rectangle.height, @@ -300,14 +294,14 @@ class PdfEllipseAnnotation extends PdfAnnotation { _createBezier( startPointList[i], controlPointList[i], endPointList[i], points); } - _drawCloudStyle(graphics, paintParams._backBrush, paintParams._borderPen, + _drawCloudStyle(graphics!, paintParams._backBrush, paintParams._borderPen, radius, 0.833, points, false); startPointList.clear(); controlPointList.clear(); endPointList.clear(); points.clear(); } else { - graphics.drawEllipse( + graphics!.drawEllipse( Rect.fromLTWH(rectangle.x + borderWidth, -rectangle.y, rectangle.width - border.width, -rectangle.height), pen: paintParams._borderPen, @@ -349,7 +343,7 @@ class PdfEllipseAnnotation extends PdfAnnotation { _IPdfPrimitive get _element => _dictionary; @override - set _element(_IPdfPrimitive value) { + set _element(_IPdfPrimitive? value) { _element = value; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_line_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_line_annotation.dart index 7dc82eff7..8ba556231 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_line_annotation.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_line_annotation.dart @@ -26,22 +26,21 @@ class PdfLineAnnotation extends PdfAnnotation { /// document.dispose(); /// ``` PdfLineAnnotation(List linePoints, String text, - {PdfColor color, - PdfColor innerColor, - PdfAnnotationBorder border, - String author, - String subject, - DateTime modifiedDate, - double opacity, - bool setAppearance, - bool flatten, - PdfLineEndingStyle beginLineStyle, - PdfLineEndingStyle endLineStyle, - PdfLineCaptionType captionType, - PdfLineIntent lineIntent, - int leaderLine, - int leaderLineExt, - bool lineCaption}) + {PdfColor? color, + PdfColor? innerColor, + PdfAnnotationBorder? border, + String? author, + String? subject, + DateTime? modifiedDate, + double? opacity, + bool? setAppearance, + PdfLineEndingStyle beginLineStyle = PdfLineEndingStyle.none, + PdfLineEndingStyle endLineStyle = PdfLineEndingStyle.none, + PdfLineCaptionType captionType = PdfLineCaptionType.inline, + PdfLineIntent lineIntent = PdfLineIntent.lineArrow, + int? leaderLine, + int? leaderLineExt, + bool lineCaption = false}) : super._( text: text, color: color, @@ -51,15 +50,14 @@ class PdfLineAnnotation extends PdfAnnotation { subject: subject, modifiedDate: modifiedDate, opacity: opacity, - setAppearance: setAppearance, - flatten: flatten) { + setAppearance: setAppearance) { _linePoints = _PdfArray(linePoints); _points = linePoints; - this.beginLineStyle = beginLineStyle ??= PdfLineEndingStyle.none; - this.endLineStyle = endLineStyle ??= PdfLineEndingStyle.none; - this.captionType = captionType ??= PdfLineCaptionType.inline; - this.lineIntent = lineIntent ??= PdfLineIntent.lineArrow; - this.lineCaption = lineCaption ??= false; + this.beginLineStyle = beginLineStyle; + this.endLineStyle = endLineStyle; + this.captionType = captionType; + this.lineIntent = lineIntent; + this.lineCaption = lineCaption; if (leaderLine != null) { this.leaderLine = leaderLine; } @@ -69,25 +67,24 @@ class PdfLineAnnotation extends PdfAnnotation { } PdfLineAnnotation._( - _PdfDictionary dictionary, _PdfCrossTable crossTable, String text) + _PdfDictionary dictionary, _PdfCrossTable crossTable, String annotText) : super._internal(dictionary, crossTable) { - ArgumentError.checkNotNull(text, 'Text must be not null'); _dictionary = dictionary; _crossTable = crossTable; - this.text = text; + text = annotText; } // Fields - _PdfArray _linePoints; - _PdfArray _lineStyle; + _PdfArray? _linePoints; + _PdfArray? _lineStyle; int _leaderLine = 0; List _points = []; - PdfLineEndingStyle _beginLineStyle; - PdfLineEndingStyle _endLineStyle; + late PdfLineEndingStyle _beginLineStyle; + late PdfLineEndingStyle _endLineStyle; int _leaderLineExt = 0; - bool _lineCaption; - PdfLineIntent _lineIntent; - PdfLineCaptionType _captionType; + late bool _lineCaption; + late PdfLineIntent _lineIntent; + late PdfLineCaptionType _captionType; // Properties /// Gets the leader line. @@ -164,10 +161,9 @@ class PdfLineAnnotation extends PdfAnnotation { if (!_isLoadedAnnotation) { _beginLineStyle = value; } else { - final _PdfArray _lineStyle = _obtainLineStyle(); + _PdfArray? _lineStyle = _obtainLineStyle(); if (_lineStyle == null) { - _lineStyle._insert( - 1, _PdfName(_getEnumName(PdfLineEndingStyle.square))); + _lineStyle = _PdfArray(); } else { _lineStyle._elements.removeAt(0); } @@ -188,10 +184,9 @@ class PdfLineAnnotation extends PdfAnnotation { if (!_isLoadedAnnotation) { _endLineStyle = value; } else { - final _PdfArray _lineStyle = _obtainLineStyle(); + _PdfArray? _lineStyle = _obtainLineStyle(); if (_lineStyle == null) { - _lineStyle._insert( - 0, _PdfName(_getEnumName(PdfLineEndingStyle.square))); + _lineStyle = _PdfArray(); } else { _lineStyle._elements.removeAt(1); } @@ -219,6 +214,19 @@ class PdfLineAnnotation extends PdfAnnotation { } } + /// Gets or sets the content of the annotation. + /// + /// The string value specifies the text of the annotation. + @override + String get text => super.text; + @override + set text(String value) { + if (_text != value) { + _text = value; + _dictionary._setString(_DictionaryProperties.contents, _text); + } + } + // Implementation @override void _initialize() { @@ -228,29 +236,28 @@ class PdfLineAnnotation extends PdfAnnotation { } List _obtainLinePoints() { - List points; + List points = []; if (!_isLoadedAnnotation) { if (_linePoints != null) { - points = List(_linePoints.count); - int i = 0; // ignore: prefer_final_in_for_each - for (_PdfNumber linePoint in _linePoints._elements) { - points[i] = linePoint.value.toInt(); - i++; + for (_IPdfPrimitive? linePoint in _linePoints!._elements) { + if (linePoint is _PdfNumber) { + points.add(linePoint.value!.toInt()); + } } } } else { if (_dictionary.containsKey(_DictionaryProperties.l)) { _linePoints = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.l]) - as _PdfArray; + as _PdfArray?; if (_linePoints != null) { - points = List(_linePoints.count); - int i = 0; + points = []; // ignore: prefer_final_in_for_each - for (_PdfNumber value in _linePoints._elements) { - points[i] = value.value.toInt(); - i++; + for (_IPdfPrimitive? value in _linePoints!._elements) { + if (value is _PdfNumber) { + points.add(value.value!.toInt()); + } } } } @@ -260,10 +267,9 @@ class PdfLineAnnotation extends PdfAnnotation { _Rectangle _obtainLineBounds() { _Rectangle bounds = _Rectangle.fromRect(this.bounds); - if ((_points != null && _points.length == 4) || - (_isLoadedAnnotation && linePoints != null && _points != null)) { - final List linePoints = _obtainLinePoints(); - if (linePoints != null && linePoints.length == 4) { + if (_points.length == 4 || _isLoadedAnnotation) { + final List lPoints = _obtainLinePoints(); + if (lPoints.length == 4) { final _PdfArray lineStyle = _PdfArray(); if (lineStyle._elements.isNotEmpty) { lineStyle._insert(0, _PdfName(_getEnumName(beginLineStyle))); @@ -273,9 +279,9 @@ class PdfLineAnnotation extends PdfAnnotation { lineStyle._add(_PdfName(endLineStyle.toString())); } bounds = _isLoadedAnnotation - ? _calculateLineBounds(linePoints, leaderLineExt, leaderLine, + ? _calculateLineBounds(lPoints, leaderLineExt, leaderLine, _obtainLeaderOffset(), lineStyle, border.width.toDouble()) - : _calculateLineBounds(linePoints, leaderLineExt, _leaderLine, 0, + : _calculateLineBounds(lPoints, leaderLineExt, _leaderLine, 0, lineStyle, border.width.toDouble()); bounds = _Rectangle(bounds.left - 8, bounds.top - 8, bounds.width + 2 * 8, bounds.height + 2 * 8); @@ -290,19 +296,19 @@ class PdfLineAnnotation extends PdfAnnotation { if (_dictionary.containsKey(_DictionaryProperties.llo)) { final _PdfNumber lOffset = _dictionary[_DictionaryProperties.llo] as _PdfNumber; - lLineOffset = lOffset.value.toInt(); + lLineOffset = lOffset.value!.toInt(); } return lLineOffset; } @override void _save() { - if (page.annotations.flatten) { - flatten = true; + if (page!.annotations._flatten) { + _flatten = true; } - if (flatten || setAppearance) { - final PdfTemplate appearance = _createAppearance(); - if (flatten) { + if (_flatten || setAppearance) { + final PdfTemplate? appearance = _createAppearance(); + if (_flatten) { if (appearance != null || _isLoadedAnnotation) { if (page != null) { _flattenAnnotation(page, appearance); @@ -316,7 +322,7 @@ class PdfLineAnnotation extends PdfAnnotation { } } } - if (!flatten && !_isLoadedAnnotation) { + if (!_flatten && !_isLoadedAnnotation) { super._save(); _savePdfLineDictionary(); } @@ -328,12 +334,12 @@ class PdfLineAnnotation extends PdfAnnotation { void _savePdfLineDictionary() { super._save(); _lineStyle = _PdfArray(); - if (_lineStyle._elements.isNotEmpty) { - _lineStyle._insert(0, _PdfName(_getEnumName(beginLineStyle))); - _lineStyle._insert(1, _PdfName(_getEnumName(endLineStyle))); + if (_lineStyle!._elements.isNotEmpty) { + _lineStyle!._insert(0, _PdfName(_getEnumName(beginLineStyle))); + _lineStyle!._insert(1, _PdfName(_getEnumName(endLineStyle))); } else { - _lineStyle._add(_PdfName(_getEnumName(beginLineStyle))); - _lineStyle._add(_PdfName(_getEnumName(endLineStyle))); + _lineStyle!._add(_PdfName(_getEnumName(beginLineStyle))); + _lineStyle!._add(_PdfName(_getEnumName(endLineStyle))); } _dictionary.setProperty(_DictionaryProperties.le, _lineStyle); if (_linePoints != null) { @@ -349,7 +355,7 @@ class PdfLineAnnotation extends PdfAnnotation { } } _dictionary.setProperty(_DictionaryProperties.bs, border); - if (innerColor != null && !innerColor.isEmpty && innerColor._alpha != 0) { + if (!innerColor.isEmpty && innerColor._alpha != 0) { _dictionary.setProperty( _DictionaryProperties.ic, innerColor._toArray(PdfColorSpace.rgb)); } @@ -373,28 +379,24 @@ class PdfLineAnnotation extends PdfAnnotation { _PdfArray.fromRectangle(_obtainLineBounds())); } - void _flattenAnnotation(PdfPage page, PdfTemplate appearance) { + void _flattenAnnotation(PdfPage? page, PdfTemplate? appearance) { if (_isLoadedAnnotation) { final bool isContainsAP = _dictionary.containsKey(_DictionaryProperties.ap); if (isContainsAP && appearance == null) { - _PdfDictionary appearanceDictionary = + _PdfDictionary? appearanceDictionary = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.ap]) - as _PdfDictionary; + as _PdfDictionary?; if (appearanceDictionary != null) { appearanceDictionary = _PdfCrossTable._dereference( - appearanceDictionary[_DictionaryProperties.n]) as _PdfDictionary; + appearanceDictionary[_DictionaryProperties.n]) as _PdfDictionary?; if (appearanceDictionary != null) { final _PdfStream appearanceStream = appearanceDictionary as _PdfStream; - if (appearanceStream != null) { - appearance = PdfTemplate._fromPdfStream(appearanceStream); - if (appearance != null) { - final bool isNormalMatrix = - _validateTemplateMatrix(appearanceDictionary); - _flattenAnnotationTemplate(appearance, isNormalMatrix); - } - } + appearance = PdfTemplate._fromPdfStream(appearanceStream); + final bool isNormalMatrix = + _validateTemplateMatrix(appearanceDictionary); + _flattenAnnotationTemplate(appearance, isNormalMatrix); } else { setAppearance = true; appearance = _createAppearance(); @@ -415,24 +417,24 @@ class PdfLineAnnotation extends PdfAnnotation { } } else { final bool isNormalMatrix = - _validateTemplateMatrix(appearance._content); + _validateTemplateMatrix(appearance!._content); _flattenAnnotationTemplate(appearance, isNormalMatrix); } } else { - page.graphics.save(); + page!.graphics.save(); final Rect rectangle = super._calculateTemplateBounds(bounds, page, appearance, true); if (opacity < 1) { page.graphics.setTransparency(opacity); } page.graphics.drawPdfTemplate( - appearance, Offset(rectangle.left, rectangle.top), rectangle.size); + appearance!, Offset(rectangle.left, rectangle.top), rectangle.size); page.annotations.remove(this); page.graphics.restore(); } } - PdfTemplate _createAppearance() { + PdfTemplate? _createAppearance() { if (_isLoadedAnnotation && !setAppearance) { return null; } @@ -441,7 +443,7 @@ class PdfLineAnnotation extends PdfAnnotation { _setMatrix(template._content); template._writeTransformation = false; final _PaintParams paintParams = _PaintParams(); - final PdfGraphics graphics = template.graphics; + final PdfGraphics? graphics = template.graphics; final PdfPen mBorderPen = PdfPen(color, width: border.width.toDouble()); if (border.borderStyle == PdfBorderStyle.dashed) { mBorderPen.dashStyle = PdfDashStyle.dash; @@ -458,7 +460,7 @@ class PdfLineAnnotation extends PdfAnnotation { format.lineAlignment = PdfVerticalAlignment.middle; final double lineWidth = mFont.measureString(text, format: format).width; final List linePoints = _obtainLinePoints(); - if (linePoints != null && linePoints.length == 4) { + if (linePoints.length == 4) { final double x1 = linePoints[0].toDouble(); final double y1 = linePoints[1].toDouble(); final double x2 = linePoints[2].toDouble(); @@ -516,16 +518,16 @@ class PdfLineAnnotation extends PdfAnnotation { } final String caption = _getEnumName(captionType); if (opacity < 1) { - graphics.save(); + graphics!.save(); graphics.setTransparency(opacity); } - if (text == null || text.isEmpty || caption == 'Top' || !lineCaption) { - graphics.drawLine( + if (text.isEmpty || caption == 'Top' || !lineCaption) { + graphics!.drawLine( mBorderPen, Offset(lineStartingPoint[0], -lineStartingPoint[1]), Offset(lineEndingPoint[0], -lineEndingPoint[1])); } else { - graphics.drawLine( + graphics!.drawLine( mBorderPen, Offset(lineStartingPoint[0], -lineStartingPoint[1]), Offset(middlePoint1[0], -middlePoint1[1])); @@ -580,12 +582,12 @@ class PdfLineAnnotation extends PdfAnnotation { _dictionary.setProperty(_DictionaryProperties.rect, _PdfArray.fromRectangle(_obtainLineBounds())); } - if (!_isLoadedAnnotation && flatten) { - final double pageHeight = page.size.height; - final PdfMargins margins = _obtainMargin(); + if (!_isLoadedAnnotation && _flatten) { + final double pageHeight = page!.size.height; + final PdfMargins? margins = _obtainMargin(); if (page != null) { bounds = Rect.fromLTWH( - nativeRectangle.left - margins.left, + nativeRectangle.left - margins!.left, pageHeight - (nativeRectangle.top + nativeRectangle.height) - margins.top, @@ -604,9 +606,9 @@ class PdfLineAnnotation extends PdfAnnotation { List _getCaptionPosition( String caption, List centerPoint, double angle, PdfFont font) { - List captionPosition = List(2); + List captionPosition = List.filled(2, 0); if (_isLoadedAnnotation) { - final bool isContainsMeasure = _dictionary._items + final bool isContainsMeasure = _dictionary._items! .containsKey(_PdfName(_DictionaryProperties.measure)); final double length = caption == 'Top' ? isContainsMeasure @@ -628,7 +630,7 @@ class PdfLineAnnotation extends PdfAnnotation { int lLine = 0; if (_dictionary.containsKey(_DictionaryProperties.ll)) { final _PdfNumber ll = _dictionary[_DictionaryProperties.ll] as _PdfNumber; - lLine = ll.value.toInt(); + lLine = ll.value!.toInt(); } return lLine; } @@ -664,7 +666,7 @@ class PdfLineAnnotation extends PdfAnnotation { if (_dictionary.containsKey(_DictionaryProperties.cap)) { final _PdfBoolean lCap = _dictionary[_DictionaryProperties.cap] as _PdfBoolean; - lCaption = lCap.value; + lCaption = lCap.value!; } return lCaption; } @@ -675,7 +677,7 @@ class PdfLineAnnotation extends PdfAnnotation { if (_dictionary.containsKey(_DictionaryProperties.lle)) { final _PdfNumber lExt = _dictionary[_DictionaryProperties.lle] as _PdfNumber; - lLineExt = lExt.value.toInt(); + lLineExt = lExt.value!.toInt(); } return lLineExt; } @@ -684,7 +686,7 @@ class PdfLineAnnotation extends PdfAnnotation { PdfLineEndingStyle _getLineStyle(dynamic value) { PdfLineEndingStyle linestyle = PdfLineEndingStyle.none; if (value is int) { - final _PdfArray array = _obtainLineStyle(); + final _PdfArray? array = _obtainLineStyle(); if (array != null) { final _PdfName style = array[value] as _PdfName; linestyle = _getLineStyle(style._name); @@ -727,11 +729,11 @@ class PdfLineAnnotation extends PdfAnnotation { } // Gets line style of the annotation. - _PdfArray _obtainLineStyle() { - _PdfArray array; + _PdfArray? _obtainLineStyle() { + _PdfArray? array; if (_dictionary.containsKey(_DictionaryProperties.le)) { array = _crossTable._getObject(_dictionary[_DictionaryProperties.le]) - as _PdfArray; + as _PdfArray?; } return array; } @@ -761,7 +763,7 @@ class PdfLineAnnotation extends PdfAnnotation { _IPdfPrimitive get _element => _dictionary; @override - set _element(_IPdfPrimitive value) { + set _element(_IPdfPrimitive? value) { _element = value; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_link_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_link_annotation.dart deleted file mode 100644 index 73b553dee..000000000 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_link_annotation.dart +++ /dev/null @@ -1,76 +0,0 @@ -part of pdf; - -/// Represents the base class for the link annotations. -abstract class PdfLinkAnnotation extends PdfAnnotation { - // constructor - /// Initializes new instance of - /// [PdfLinkAnnotation] class with specified bounds. - PdfLinkAnnotation([Rect bounds]) : super._(bounds: bounds); - - PdfLinkAnnotation._(_PdfDictionary dictionary, _PdfCrossTable crossTable) - : super._internal(dictionary, crossTable); - - // fields - PdfHighlightMode _highlightMode = PdfHighlightMode.noHighlighting; - - //properties - /// Gets the highlight mode of the link annotation. - PdfHighlightMode get highlightMode => - _isLoadedAnnotation ? _obtainHighlightMode() : _highlightMode; - - /// Sets the highlight mode of the link annotation. - set highlightMode(PdfHighlightMode value) { - _highlightMode = value; - final String mode = _getHighlightMode(_highlightMode); - _dictionary.._setName(_PdfName(_DictionaryProperties.h), mode); - } - - // implementation - @override - void _initialize() { - super._initialize(); - _dictionary.setProperty(_PdfName(_DictionaryProperties.subtype), - _PdfName(_DictionaryProperties.link)); - } - - String _getHighlightMode(PdfHighlightMode mode) { - String hightlightMode; - switch (mode) { - case PdfHighlightMode.invert: - hightlightMode = 'I'; - break; - case PdfHighlightMode.noHighlighting: - hightlightMode = 'N'; - break; - case PdfHighlightMode.outline: - hightlightMode = 'O'; - break; - case PdfHighlightMode.push: - hightlightMode = 'P'; - break; - } - return hightlightMode; - } - - PdfHighlightMode _obtainHighlightMode() { - PdfHighlightMode mode = PdfHighlightMode.noHighlighting; - if (_dictionary.containsKey(_DictionaryProperties.h)) { - final _PdfName name = _dictionary[_DictionaryProperties.h]; - switch (name._name) { - case 'I': - mode = PdfHighlightMode.invert; - break; - case 'N': - mode = PdfHighlightMode.noHighlighting; - break; - case 'O': - mode = PdfHighlightMode.outline; - break; - case 'P': - mode = PdfHighlightMode.push; - break; - } - } - return mode; - } -} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_paintparams.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_paintparams.dart index 1809ed5e1..b8f9e4e57 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_paintparams.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_paintparams.dart @@ -1,17 +1,31 @@ part of pdf; class _PaintParams { - _PaintParams({ - PdfBrush backBrush, - PdfBrush foreBrush, - PdfPen borderPen, - }) { + _PaintParams( + {PdfBrush? backBrush, + PdfBrush? foreBrush, + PdfPen? borderPen, + Rect? bounds, + PdfBorderStyle? style, + int? borderWidth, + PdfBrush? shadowBrush, + int? rotationAngle}) { _backBrush = backBrush; _foreBrush = foreBrush; _borderPen = borderPen; + _bounds = bounds; + _style = style; + _borderWidth = borderWidth; + _shadowBrush = shadowBrush; + _rotationAngle = rotationAngle; } // Fields - PdfBrush _backBrush; - PdfBrush _foreBrush; - PdfPen _borderPen; + PdfBrush? _backBrush; + PdfBrush? _foreBrush; + PdfPen? _borderPen; + Rect? _bounds; + PdfBorderStyle? _style; + int? _borderWidth; + PdfBrush? _shadowBrush; + int? _rotationAngle; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_polygon_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_polygon_annotation.dart index c71496da5..65ca977c1 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_polygon_annotation.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_polygon_annotation.dart @@ -29,15 +29,14 @@ class PdfPolygonAnnotation extends PdfAnnotation { /// document.dispose(); /// ``` PdfPolygonAnnotation(List points, String text, - {PdfColor color, - PdfColor innerColor, - PdfAnnotationBorder border, - String author, - String subject, - DateTime modifiedDate, - double opacity, - bool setAppearance, - bool flatten}) + {PdfColor? color, + PdfColor? innerColor, + PdfAnnotationBorder? border, + String? author, + String? subject, + DateTime? modifiedDate, + double? opacity, + bool? setAppearance}) : super._( text: text, color: color, @@ -47,8 +46,7 @@ class PdfPolygonAnnotation extends PdfAnnotation { subject: subject, modifiedDate: modifiedDate, opacity: opacity, - setAppearance: setAppearance, - flatten: flatten) { + setAppearance: setAppearance) { _linePoints = _PdfArray(points); _polygonPoints = points; } @@ -56,27 +54,26 @@ class PdfPolygonAnnotation extends PdfAnnotation { PdfPolygonAnnotation._( _PdfDictionary dictionary, _PdfCrossTable crossTable, String text) : super._internal(dictionary, crossTable) { - ArgumentError.checkNotNull(text, 'Text must be not null'); _dictionary = dictionary; _crossTable = crossTable; this.text = text; } // Fields - _PdfArray _linePoints; - List _polygonPoints; + _PdfArray? _linePoints; + late List _polygonPoints; /// Gets the polygon points of the annotation. List get polygonPoints { if (_isLoadedAnnotation) { final List points = []; if (_dictionary.containsKey(_DictionaryProperties.vertices)) { - final _PdfArray linePoints = - _dictionary[_DictionaryProperties.vertices]; + final _PdfArray? linePoints = + _dictionary[_DictionaryProperties.vertices] as _PdfArray?; if (linePoints != null) { - linePoints._elements.forEach((element) { - if (element is _PdfNumber) { - points.add(element.value.toInt()); + linePoints._elements.forEach((_IPdfPrimitive? element) { + if (element != null && element is _PdfNumber) { + points.add(element.value!.toInt()); } }); } @@ -97,8 +94,8 @@ class PdfPolygonAnnotation extends PdfAnnotation { @override void _save() { - if (page.annotations.flatten) { - flatten = true; + if (page!.annotations._flatten) { + _flatten = true; } if (_isLoadedAnnotation) { _saveOldPolygonAnnotation(); @@ -121,34 +118,34 @@ class PdfPolygonAnnotation extends PdfAnnotation { appearance.normal = PdfTemplate._fromRect(nativeRectangle); final PdfTemplate template = appearance.normal; template._writeTransformation = false; - final PdfGraphics graphics = template.graphics; - final PdfBrush _backBrush = + final PdfGraphics? graphics = template.graphics; + final PdfBrush? _backBrush = innerColor.isEmpty ? null : PdfSolidBrush(innerColor); - PdfPen _borderPen; + PdfPen? _borderPen; if (border.width > 0 && color._alpha != 0) { _borderPen = PdfPen(color, width: border.width.toDouble()); } - if (flatten) { - page.annotations.remove(this); - page.graphics.drawPolygon(_getLinePoints(), + if (_flatten) { + page!.annotations.remove(this); + page!.graphics.drawPolygon(_getLinePoints()!, pen: _borderPen, brush: _backBrush); } else { - graphics.drawPolygon(_getLinePoints(), + graphics!.drawPolygon(_getLinePoints()!, pen: _borderPen, brush: _backBrush); } } } - if (flatten && !setAppearance) { - page.annotations.remove(this); - PdfPen _borderPen; + if (_flatten && !setAppearance) { + page!.annotations.remove(this); + PdfPen? _borderPen; if (border.width > 0 && color._alpha != 0) { _borderPen = PdfPen(color, width: border.width.toDouble()); } - final PdfBrush _backBrush = + final PdfBrush? _backBrush = innerColor.isEmpty ? null : PdfSolidBrush(innerColor); - page.graphics - .drawPolygon(_getLinePoints(), pen: _borderPen, brush: _backBrush); - } else if (!flatten) { + page!.graphics + .drawPolygon(_getLinePoints()!, pen: _borderPen, brush: _backBrush); + } else if (!_flatten) { super._save(); _dictionary.setProperty( _DictionaryProperties.vertices, _PdfArray(_linePoints)); @@ -164,13 +161,13 @@ class PdfPolygonAnnotation extends PdfAnnotation { } void _saveOldPolygonAnnotation() { - PdfGraphicsState state; + PdfGraphicsState? state; _Rectangle nativeRectangle = _Rectangle.empty; if (setAppearance) { _getBoundsValue(); nativeRectangle = _Rectangle( bounds.left - border.width, - page.size.height - bounds.top - (border.width) - bounds.height, + page!.size.height - bounds.top - (border.width) - bounds.height, bounds.width + (2 * border.width), bounds.height + (2 * border.width)); _dictionary.setProperty(_DictionaryProperties.ap, appearance); @@ -178,108 +175,109 @@ class PdfPolygonAnnotation extends PdfAnnotation { appearance.normal = PdfTemplate._fromRect(nativeRectangle.rect); final PdfTemplate template = appearance.normal; template._writeTransformation = false; - final PdfGraphics graphics = appearance.normal.graphics; - PdfBrush backgroundBrush; + final PdfGraphics? graphics = appearance.normal.graphics; + PdfBrush? backgroundBrush; if (innerColor._alpha != 0) { backgroundBrush = PdfSolidBrush(innerColor); } - PdfPen _borderPen; + PdfPen? _borderPen; if (border.width > 0) { _borderPen = PdfPen(color, width: border.width.toDouble()); } if (_dictionary.containsKey(_DictionaryProperties.bs)) { - _PdfDictionary bSDictionary; - if (_dictionary._items[_PdfName(_DictionaryProperties.bs)] + _PdfDictionary? bSDictionary; + if (_dictionary._items![_PdfName(_DictionaryProperties.bs)] is _PdfReferenceHolder) { bSDictionary = - (_dictionary._items[_PdfName(_DictionaryProperties.bs)] + (_dictionary._items![_PdfName(_DictionaryProperties.bs)] as _PdfReferenceHolder) - ._object as _PdfDictionary; + ._object as _PdfDictionary?; } else { bSDictionary = _dictionary - ._items[_PdfName(_DictionaryProperties.bs)] as _PdfDictionary; + ._items![_PdfName(_DictionaryProperties.bs)] as _PdfDictionary?; } - if (bSDictionary.containsKey(_DictionaryProperties.d)) { - final _PdfArray dashPatternArray = _PdfCrossTable._dereference( - bSDictionary._items[_PdfName(_DictionaryProperties.d)]) - as _PdfArray; + if (bSDictionary!.containsKey(_DictionaryProperties.d)) { + final _PdfArray? dashPatternArray = _PdfCrossTable._dereference( + bSDictionary._items![_PdfName(_DictionaryProperties.d)]) + as _PdfArray?; if (dashPatternArray != null) { - final List dashPattern = - List(dashPatternArray.count); + final List dashPattern = List.filled( + dashPatternArray.count, 0, + growable: true); for (int i = 0; i < dashPatternArray.count; i++) { - if (dashPatternArray._elements[i] as _PdfNumber != null) { - dashPattern[i] = (dashPatternArray._elements[i] as _PdfNumber) - .value - .toDouble(); + final _IPdfPrimitive? pdfPrimitive = + dashPatternArray._elements[i]; + if (pdfPrimitive != null && pdfPrimitive is _PdfNumber) { + dashPattern[i] = pdfPrimitive.value!.toDouble(); } } - _borderPen.dashStyle = PdfDashStyle.dash; + _borderPen!.dashStyle = PdfDashStyle.dash; _borderPen._isSkipPatternWidth = true; _borderPen.dashPattern = dashPattern; } } } - if (flatten) { - _page.annotations.remove(this); + if (_flatten) { + _page!.annotations.remove(this); if (opacity < 1) { - state = page.graphics.save(); - page.graphics.setTransparency(opacity); + state = page!.graphics.save(); + page!.graphics.setTransparency(opacity); } if (_dictionary.containsKey(_DictionaryProperties.be)) { final _PdfDictionary beDictionary = _dictionary[_PdfName(_DictionaryProperties.be)] as _PdfDictionary; - final double iNumber = (beDictionary - ._items[_PdfName(_DictionaryProperties.i)] as _PdfNumber) - .value; + final double? iNumber = (beDictionary + ._items![_PdfName(_DictionaryProperties.i)] as _PdfNumber) + .value as double?; final double radius = iNumber == 1 ? 5 : 10; if (radius > 0) { - final List points = _getLinePoints(); + final List points = _getLinePoints()!; if (points[0].dy > points[points.length - 1].dy) { - _drawCloudStyle(graphics, backgroundBrush, _borderPen, radius, - 0.833, _getLinePoints(), false); + _drawCloudStyle(graphics!, backgroundBrush, _borderPen, radius, + 0.833, _getLinePoints()!, false); } - _drawCloudStyle(page.graphics, backgroundBrush, _borderPen, - radius, 0.833, _getLinePoints(), false); + _drawCloudStyle(page!.graphics, backgroundBrush, _borderPen, + radius, 0.833, _getLinePoints()!, false); } else { - page.graphics.drawPolygon(_getLinePoints(), + page!.graphics.drawPolygon(_getLinePoints()!, pen: _borderPen, brush: backgroundBrush); } } else { - page.graphics.drawPolygon(_getLinePoints(), + page!.graphics.drawPolygon(_getLinePoints()!, pen: _borderPen, brush: backgroundBrush); } if (opacity < 1) { - page.graphics.restore(state); + page!.graphics.restore(state); } } else { if (opacity < 1) { - state = graphics.save(); + state = graphics!.save(); graphics.setTransparency(opacity); } if (_dictionary.containsKey(_DictionaryProperties.be)) { final _PdfDictionary beDictionary = _dictionary[_PdfName(_DictionaryProperties.be)] as _PdfDictionary; - final double iNumber = (beDictionary - ._items[_PdfName(_DictionaryProperties.i)] as _PdfNumber) - .value; + final double? iNumber = (beDictionary + ._items![_PdfName(_DictionaryProperties.i)] as _PdfNumber) + .value as double?; final double radius = iNumber == 1 ? 5 : 10; - List points = _getLinePoints(); + List points = _getLinePoints()!; if (points[0].dy > points[points.length - 1].dy) { - final List point = List(points.length); + final List point = []; for (int i = 0; i < points.length; i++) { - point[i] = Offset(points[i].dx, -points[i].dy); + point.add(Offset(points[i].dx, -points[i].dy)); } points = point; - _drawCloudStyle(graphics, backgroundBrush, _borderPen, radius, + _drawCloudStyle(graphics!, backgroundBrush, _borderPen, radius, 0.833, points, true); } else { - _drawCloudStyle(graphics, backgroundBrush, _borderPen, radius, + _drawCloudStyle(graphics!, backgroundBrush, _borderPen, radius, 0.833, points, false); } } else { - graphics.drawPolygon(_getLinePoints(), + graphics!.drawPolygon(_getLinePoints()!, pen: _borderPen, brush: backgroundBrush); } if (opacity < 1) { @@ -290,61 +288,57 @@ class PdfPolygonAnnotation extends PdfAnnotation { _PdfArray.fromRectangle(nativeRectangle)); } } - if (flatten && !setAppearance) { + if (_flatten && !setAppearance) { if (_dictionary[_DictionaryProperties.ap] != null) { - _IPdfPrimitive obj = _dictionary[_DictionaryProperties.ap]; - _PdfDictionary dic = _PdfCrossTable._dereference(obj) as _PdfDictionary; - PdfTemplate template; + _IPdfPrimitive? obj = _dictionary[_DictionaryProperties.ap]; + _PdfDictionary? dic = + _PdfCrossTable._dereference(obj) as _PdfDictionary?; + PdfTemplate? template; if (dic != null) { obj = dic[_DictionaryProperties.n]; - dic = _PdfCrossTable._dereference(obj) as _PdfDictionary; - if (dic != null) { - final _PdfStream stream = dic as _PdfStream; - if (stream != null) { - template = PdfTemplate._fromPdfStream(stream); - if (template != null) { - state = page.graphics.save(); - if (opacity < 1) { - page.graphics.setTransparency(opacity); - } - final bool isNormalMatrix = _validateTemplateMatrix(dic); - final Rect rect = _calculateTemplateBounds( - bounds, page, template, isNormalMatrix); - page.graphics - .drawPdfTemplate(template, rect.topLeft, rect.size); - page.graphics.restore(state); - page.annotations.remove(this); - } + dic = _PdfCrossTable._dereference(obj) as _PdfDictionary?; + if (dic != null && dic is _PdfStream) { + final _PdfStream stream = dic; + template = PdfTemplate._fromPdfStream(stream); + state = page!.graphics.save(); + if (opacity < 1) { + page!.graphics.setTransparency(opacity); } + final bool isNormalMatrix = _validateTemplateMatrix(dic); + final Rect rect = _calculateTemplateBounds( + bounds, page, template, isNormalMatrix); + page!.graphics.drawPdfTemplate(template, rect.topLeft, rect.size); + page!.graphics.restore(state); + page!.annotations.remove(this); } } } else { - page.annotations.remove(this); + page!.annotations.remove(this); final PdfPen _borderPen = PdfPen(color, width: border.width.toDouble()); - final PdfBrush backgroundBrush = + final PdfBrush? backgroundBrush = innerColor.isEmpty ? null : PdfSolidBrush(innerColor); if (opacity < 1) { - state = page.graphics.save(); - page.graphics.setTransparency(opacity); + state = page!.graphics.save(); + page!.graphics.setTransparency(opacity); } if (_dictionary.containsKey(_DictionaryProperties.be)) { - final _IPdfPrimitive primitive = + final _IPdfPrimitive? primitive = _dictionary[_PdfName(_DictionaryProperties.be)]; final _PdfDictionary beDictionary = (primitive is _PdfReferenceHolder ? primitive._object : primitive) as _PdfDictionary; - final double iNumber = (beDictionary - ._items[_PdfName(_DictionaryProperties.i)] as _PdfNumber) - .value; + final double? iNumber = (beDictionary + ._items![_PdfName(_DictionaryProperties.i)] as _PdfNumber) + .value as double?; final double radius = iNumber == 1 ? 5 : 10; - _drawCloudStyle(page.graphics, backgroundBrush, _borderPen, radius, - 0.833, _getLinePoints(), false); + _drawCloudStyle(page!.graphics, backgroundBrush, _borderPen, radius, + 0.833, _getLinePoints()!, false); } else { - page.graphics.drawPolygon(_getLinePoints(), + page!.graphics.drawPolygon(_getLinePoints()!, pen: _borderPen, brush: backgroundBrush); } if (opacity < 1) { - page.graphics.restore(state); + page!.graphics.restore(state); } } if (_flattenPopups) { @@ -353,27 +347,25 @@ class PdfPolygonAnnotation extends PdfAnnotation { } } - List _getLinePoints() { + List? _getLinePoints() { if (_isLoadedAnnotation) { - List points; + List? points; if (_dictionary.containsKey(_DictionaryProperties.vertices)) { - final _PdfArray linePoints = - _dictionary[_DictionaryProperties.vertices] as _PdfArray; + final _PdfArray? linePoints = + _dictionary[_DictionaryProperties.vertices] as _PdfArray?; if (linePoints != null) { - final List point = List(linePoints.count); + final List point = []; for (int i = 0; i < linePoints.count; i++) { final _PdfNumber number = linePoints[i] as _PdfNumber; - point[i] = number.value.toDouble(); + point.add(number.value!.toDouble()); } - points = List(point.length ~/ 2); - int count = 0; + points = []; for (int j = 0; j < point.length; j = j + 2) { - if (flatten) { - points[count] = Offset(point[j], page.size.height - point[j + 1]); + if (_flatten) { + points.add(Offset(point[j], page!.size.height - point[j + 1])); } else { - points[count] = Offset(point[j].toDouble(), -point[j + 1]); + points.add(Offset(point[j].toDouble(), -point[j + 1])); } - count++; } } } @@ -383,20 +375,22 @@ class PdfPolygonAnnotation extends PdfAnnotation { if (_linePoints != null) { final List pointsValue = []; // ignore: prefer_final_in_for_each - for (_PdfNumber linePoint in _linePoints._elements) { - pointsValue.add(linePoint.value.toDouble()); + for (_IPdfPrimitive? linePoint in _linePoints!._elements) { + if (linePoint is _PdfNumber) { + pointsValue.add(linePoint.value!.toDouble()); + } } for (int j = 0; j < pointsValue.length; j = j + 2) { - final double pageHeight = page.size.height; - if (flatten) { - page._isLoadedPage + final double pageHeight = page!.size.height; + if (_flatten) { + page!._isLoadedPage ? points.add( Offset(pointsValue[j], (pageHeight - pointsValue[j + 1]))) : points.add(Offset( - pointsValue[j] - page._section.pageSettings.margins.left, + pointsValue[j] - page!._section!.pageSettings.margins.left, pageHeight - pointsValue[j + 1] - - page._section.pageSettings.margins.right)); + page!._section!.pageSettings.margins.right)); } else { points.add(Offset(pointsValue[j], -pointsValue[j + 1])); } @@ -417,10 +411,11 @@ class PdfPolygonAnnotation extends PdfAnnotation { final _PdfArray linePoints = _PdfCrossTable._dereference( _dictionary[_DictionaryProperties.vertices]) as _PdfArray; if (linePoints.count > 0) { - final List points = List(linePoints.count); + final List points = + List.filled(linePoints.count, 0, growable: true); for (int j = 0; j < linePoints.count; j++) { - final _PdfNumber number = linePoints[j]; - points[j] = number.value.toDouble(); + final _PdfNumber number = linePoints[j] as _PdfNumber; + points[j] = number.value!.toDouble(); } for (int i = 0; i < points.length; i++) { if (i % 2 == 0) { @@ -438,11 +433,13 @@ class PdfPolygonAnnotation extends PdfAnnotation { } else { final List xval = []; final List yval = []; - if (_linePoints.count > 0) { + if (_linePoints!.count > 0) { final List pointsValue = []; // ignore: prefer_final_in_for_each - for (_PdfNumber linePoint in _linePoints._elements) { - pointsValue.add(linePoint.value.toDouble()); + for (_IPdfPrimitive? linePoint in _linePoints!._elements) { + if (linePoint is _PdfNumber) { + pointsValue.add(linePoint.value!.toDouble()); + } } for (int i = 0; i < pointsValue.length; i++) { if (i % 2 == 0) { @@ -463,7 +460,7 @@ class PdfPolygonAnnotation extends PdfAnnotation { _IPdfPrimitive get _element => _dictionary; @override - set _element(_IPdfPrimitive value) { + set _element(_IPdfPrimitive? value) { _element = value; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_rectangle_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_rectangle_annotation.dart index d64ac71ab..78938721f 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_rectangle_annotation.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_rectangle_annotation.dart @@ -15,15 +15,14 @@ class PdfRectangleAnnotation extends PdfAnnotation { /// document.dispose(); /// ``` PdfRectangleAnnotation(Rect bounds, String text, - {PdfColor color, - PdfColor innerColor, - PdfAnnotationBorder border, - String author, - String subject, - double opacity, - DateTime modifiedDate, - bool setAppearance, - bool flatten}) + {PdfColor? color, + PdfColor? innerColor, + PdfAnnotationBorder? border, + String? author, + String? subject, + double? opacity, + DateTime? modifiedDate, + bool? setAppearance}) : super._( bounds: bounds, text: text, @@ -34,13 +33,11 @@ class PdfRectangleAnnotation extends PdfAnnotation { subject: subject, modifiedDate: modifiedDate, opacity: opacity, - setAppearance: setAppearance, - flatten: flatten); + setAppearance: setAppearance); PdfRectangleAnnotation._( _PdfDictionary dictionary, _PdfCrossTable crossTable, String text) : super._internal(dictionary, crossTable) { - ArgumentError.checkNotNull(text, 'Text must be not null'); _dictionary = dictionary; _crossTable = crossTable; this.text = text; @@ -56,17 +53,17 @@ class PdfRectangleAnnotation extends PdfAnnotation { @override void _save() { - if (page.annotations.flatten) { - flatten = true; + if (page!.annotations._flatten) { + _flatten = true; } - if (flatten || setAppearance || _pdfAppearance != null) { - PdfTemplate appearance; + if (_flatten || setAppearance || _pdfAppearance != null) { + PdfTemplate? appearance; if (_pdfAppearance != null) { - appearance = _pdfAppearance.normal ??= _createAppearance(); + appearance = _pdfAppearance!.normal; } else { appearance = _createAppearance(); } - if (flatten) { + if (_flatten) { if (appearance != null || _isLoadedAnnotation) { if (page != null) { _flattenAnnotation(page, appearance); @@ -80,7 +77,7 @@ class PdfRectangleAnnotation extends PdfAnnotation { } } } - if (!flatten && !_isLoadedAnnotation) { + if (!_flatten && !_isLoadedAnnotation) { super._save(); _dictionary.setProperty(_DictionaryProperties.bs, border); } @@ -89,16 +86,16 @@ class PdfRectangleAnnotation extends PdfAnnotation { } } - PdfTemplate _createAppearance() { + PdfTemplate? _createAppearance() { if (_isLoadedAnnotation) { if (setAppearance) { final _PaintParams paintParams = _PaintParams(); final double borderWidth = border.width / 2; final PdfPen mBorderPen = PdfPen(color, width: border.width.toDouble()); - PdfBrush mBackBrush; + PdfBrush? mBackBrush; final Map result = _calculateCloudBorderBounds(); final double borderIntensity = result['borderIntensity'] as double; - final String borderStyle = result['borderStyle'] as String; + final String? borderStyle = result['borderStyle'] as String?; final _Rectangle nativeRectangle = _Rectangle(0, 0, bounds.width, bounds.height); final PdfTemplate template = @@ -107,7 +104,7 @@ class PdfRectangleAnnotation extends PdfAnnotation { if (borderIntensity > 0 && borderStyle == 'C') { template._writeTransformation = false; } - final PdfGraphics graphics = template.graphics; + final PdfGraphics? graphics = template.graphics; if (innerColor._alpha != 0) { mBackBrush = PdfSolidBrush(innerColor); } @@ -120,21 +117,21 @@ class PdfRectangleAnnotation extends PdfAnnotation { mBorderPen, nativeRectangle, borderWidth, borderIntensity: borderIntensity, borderStyle: borderStyle); if (opacity < 1) { - graphics.save(); + graphics!.save(); graphics.setTransparency(opacity); } if (borderIntensity > 0 && borderStyle == 'C') { _drawAppearance( rectangle, borderWidth, graphics, paintParams, borderIntensity); } else { - graphics.drawRectangle( + graphics!.drawRectangle( pen: paintParams._borderPen, brush: paintParams._backBrush, bounds: Rect.fromLTWH( rectangle.x, rectangle.y, rectangle.width, rectangle.height)); } if (opacity < 1) { - graphics.restore(); + graphics!.restore(); } return template; } @@ -145,12 +142,12 @@ class PdfRectangleAnnotation extends PdfAnnotation { final PdfTemplate template = PdfTemplate(bounds.width, bounds.height); _setMatrix(template._content); final _PaintParams paintParams = _PaintParams(); - final PdfGraphics graphics = template.graphics; + final PdfGraphics graphics = template.graphics!; if (border.width > 0 && color._alpha != 0) { final PdfPen mBorderPen = PdfPen(color, width: border.width.toDouble()); paintParams._borderPen = mBorderPen; } - PdfBrush mBackBrush; + PdfBrush? mBackBrush; if (innerColor._alpha != 0) { mBackBrush = PdfSolidBrush(innerColor); } @@ -184,14 +181,15 @@ class PdfRectangleAnnotation extends PdfAnnotation { if (!_dictionary.containsKey(_DictionaryProperties.rd) && _dictionary.containsKey(_DictionaryProperties.be)) { final _PdfDictionary dict = - _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.be]); + _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.be]) + as _PdfDictionary; if (dict.containsKey(_DictionaryProperties.s)) { borderStyle = _getEnumName((dict[_DictionaryProperties.s] as _PdfName)._name); } if (dict.containsKey(_DictionaryProperties.i)) { borderIntensity = - (dict[_DictionaryProperties.i] as _PdfNumber).value.toDouble(); + (dict[_DictionaryProperties.i] as _PdfNumber).value!.toDouble(); } if (borderIntensity != 0 && borderStyle == 'C') { final Rect cloudRectangle = Rect.fromLTWH( @@ -213,15 +211,15 @@ class PdfRectangleAnnotation extends PdfAnnotation { if (!_isBounds && _dictionary[_DictionaryProperties.rd] != null) { final _PdfArray mRdArray = _dictionary[_DictionaryProperties.rd] as _PdfArray; - final _PdfNumber num1 = mRdArray._elements[0] as _PdfNumber; - final _PdfNumber num2 = mRdArray._elements[1] as _PdfNumber; - final _PdfNumber num3 = mRdArray._elements[2] as _PdfNumber; - final _PdfNumber num4 = mRdArray._elements[3] as _PdfNumber; + final _PdfNumber num1 = mRdArray._elements[0]! as _PdfNumber; + final _PdfNumber num2 = mRdArray._elements[1]! as _PdfNumber; + final _PdfNumber num3 = mRdArray._elements[2]! as _PdfNumber; + final _PdfNumber num4 = mRdArray._elements[3]! as _PdfNumber; Rect cloudRectangle = Rect.fromLTWH( - bounds.left + num1.value.toDouble(), - bounds.top + num2.value.toDouble(), - bounds.width - num3.value.toDouble() * 2, - bounds.height - num4.value.toDouble() * 2); + bounds.left + num1.value!.toDouble(), + bounds.top + num2.value!.toDouble(), + bounds.width - num3.value!.toDouble() * 2, + bounds.height - num4.value!.toDouble() * 2); if (borderIntensity != 0 && borderStyle == 'C') { cloudRectangle = Rect.fromLTWH( cloudRectangle.left - borderIntensity * 5 - border.width / 2, @@ -244,28 +242,24 @@ class PdfRectangleAnnotation extends PdfAnnotation { return {'borderIntensity': borderIntensity, 'borderStyle': borderStyle}; } - void _flattenAnnotation(PdfPage page, PdfTemplate appearance) { + void _flattenAnnotation(PdfPage? page, PdfTemplate? appearance) { if (_isLoadedAnnotation) { final bool isContainsAP = _dictionary.containsKey(_DictionaryProperties.ap); if (isContainsAP && appearance == null) { - _PdfDictionary appearanceDictionary = + _PdfDictionary? appearanceDictionary = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.ap]) - as _PdfDictionary; + as _PdfDictionary?; if (appearanceDictionary != null) { appearanceDictionary = _PdfCrossTable._dereference( - appearanceDictionary[_DictionaryProperties.n]) as _PdfDictionary; + appearanceDictionary[_DictionaryProperties.n]) as _PdfDictionary?; if (appearanceDictionary != null) { final _PdfStream appearanceStream = appearanceDictionary as _PdfStream; - if (appearanceStream != null) { - appearance = PdfTemplate._fromPdfStream(appearanceStream); - if (appearance != null) { - final bool isNormalMatrix = - _validateTemplateMatrix(appearanceDictionary); - _flattenAnnotationTemplate(appearance, isNormalMatrix); - } - } + appearance = PdfTemplate._fromPdfStream(appearanceStream); + final bool isNormalMatrix = + _validateTemplateMatrix(appearanceDictionary); + _flattenAnnotationTemplate(appearance, isNormalMatrix); } else { setAppearance = true; appearance = _createAppearance(); @@ -286,18 +280,19 @@ class PdfRectangleAnnotation extends PdfAnnotation { } } else { final bool isNormalMatrix = - _validateTemplateMatrix(appearance._content); + _validateTemplateMatrix(appearance!._content); _flattenAnnotationTemplate(appearance, isNormalMatrix); } } else { - page.graphics.save(); + page!.graphics.save(); final Rect rectangle = super._calculateTemplateBounds(bounds, page, appearance, true); + if (opacity < 1) { page.graphics.setTransparency(opacity, mode: PdfBlendMode.normal); } page.graphics.drawPdfTemplate( - appearance, Offset(rectangle.left, rectangle.top), rectangle.size); + appearance!, Offset(rectangle.left, rectangle.top), rectangle.size); page.annotations.remove(this); page.graphics.restore(); } @@ -306,20 +301,20 @@ class PdfRectangleAnnotation extends PdfAnnotation { // Obtain Style from annotation _Rectangle _obtainStyle( PdfPen mBorderPen, _Rectangle rectangle, double borderWidth, - {double borderIntensity, String borderStyle}) { + {double? borderIntensity, String? borderStyle}) { if (_dictionary.containsKey(_DictionaryProperties.bs)) { - final _PdfDictionary bSDictionary = + final _PdfDictionary? bSDictionary = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.bs]) - as _PdfDictionary; + as _PdfDictionary?; if (bSDictionary != null && bSDictionary.containsKey(_DictionaryProperties.d)) { final _PdfArray dashPatternArray = _PdfCrossTable._dereference(bSDictionary[_DictionaryProperties.d]) as _PdfArray; - final List dashPattern = List(dashPatternArray.count); + final List dashPattern = []; for (int i = 0; i < dashPatternArray.count; i++) { - dashPattern[i] = - (dashPatternArray._elements[i] as _PdfNumber).value.toDouble(); + dashPattern.add( + (dashPatternArray._elements[i]! as _PdfNumber).value!.toDouble()); } mBorderPen.dashStyle = PdfDashStyle.dash; mBorderPen.dashPattern = dashPattern; @@ -335,17 +330,17 @@ class PdfRectangleAnnotation extends PdfAnnotation { rectangle.width = rectangle.width - (2 * radius) - 2 * borderWidth; rectangle.height = rectangle.height - (2 * radius) - 2 * borderWidth; } else { - rectangle.x = rectangle.x + borderWidth; - rectangle.y = rectangle.y + borderWidth; - rectangle.width = rectangle.width - border.width; - rectangle.height = bounds.height - border.width; + rectangle.x += borderWidth; + rectangle.y += borderWidth; + rectangle.width -= border.width; + rectangle.height -= border.width; } return rectangle; } // Draw appearance for annotation void _drawAppearance(_Rectangle rectangle, double borderWidth, - PdfGraphics graphics, _PaintParams paintParams, double borderIntensity) { + PdfGraphics? graphics, _PaintParams paintParams, double borderIntensity) { final PdfPath graphicsPath = PdfPath(); graphicsPath.addRectangle(rectangle.rect); final double radius = borderIntensity * 4.25; @@ -354,15 +349,15 @@ class PdfRectangleAnnotation extends PdfAnnotation { graphicsPath._points[4] == Offset.zero) { graphicsPath._points.removeAt(4); } - final List points = List(graphicsPath._points.length); + final List points = []; for (int i = 0; i < graphicsPath._points.length; i++) { - points[i] = - Offset(graphicsPath._points[i].dx, -graphicsPath._points[i].dy); + points.add( + Offset(graphicsPath._points[i].dx, -graphicsPath._points[i].dy)); } - _drawCloudStyle(graphics, paintParams._backBrush, paintParams._borderPen, + _drawCloudStyle(graphics!, paintParams._backBrush, paintParams._borderPen, radius, 0.833, points, false); } else { - graphics.drawRectangle( + graphics!.drawRectangle( pen: paintParams._borderPen, brush: paintParams._backBrush, bounds: Rect.fromLTWH(rectangle.x + borderWidth, rectangle.y, @@ -374,7 +369,7 @@ class PdfRectangleAnnotation extends PdfAnnotation { _IPdfPrimitive get _element => _dictionary; @override - set _element(_IPdfPrimitive value) { + set _element(_IPdfPrimitive? value) { _element = value; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_text_web_link.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_text_web_link.dart index b8730e129..9c923c3e2 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_text_web_link.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_text_web_link.dart @@ -1,65 +1,207 @@ part of pdf; /// Represents the class for text web link annotation. +/// ``` dart +/// //Create a new Pdf document +/// PdfDocument document = PdfDocument(); +/// //Create and draw the web link in the PDF page +/// PdfTextWebLink( +/// url: 'www.google.co.in', +/// text: 'google', +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 14), +/// brush: PdfSolidBrush(PdfColor(0, 0, 0)), +/// pen: PdfPens.brown, +/// format: PdfStringFormat( +/// alignment: PdfTextAlignment.center, +/// lineAlignment: PdfVerticalAlignment.middle)) +/// .draw(document.pages.add(), Offset(50, 40)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfTextWebLink extends PdfAnnotation { // Constructor /// Initializes a new instance of the [PdfTextWebLink] class. + /// ``` dart + /// //Create a new Pdf document + /// PdfDocument document = PdfDocument(); + /// //Create and draw the web link in the PDF page + /// PdfTextWebLink( + /// url: 'www.google.co.in', + /// text: 'google', + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 14), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// pen: PdfPens.brown, + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.middle)) + /// .draw(document.pages.add(), Offset(50, 40)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfTextWebLink( - {String url, - String text, - PdfBrush brush, - PdfFont font, - PdfPen pen, - PdfStringFormat format}) + {required String url, + String? text, + PdfBrush? brush, + PdfFont? font, + PdfPen? pen, + PdfStringFormat? format}) : super._() { _initializeWebLink(text, font, pen, brush, format); this.url = url; } PdfTextWebLink._( - _PdfDictionary dictionary, _PdfCrossTable crossTable, String text) + _PdfDictionary dictionary, _PdfCrossTable crossTable, String? annotText) : super._internal(dictionary, crossTable) { + text = annotText != null && annotText.isNotEmpty ? annotText : ''; _crossTable = crossTable; } // fields - String _url; - PdfUriAnnotation _uriAnnotation; + String? _url; + late PdfUriAnnotation _uriAnnotation; /// Get or sets the font. - PdfFont font; + /// ```dart + /// //Create a new Pdf document + /// PdfDocument document = PdfDocument(); + /// //Create the web link in the PDF page + /// PdfTextWebLink textWebLink = PdfTextWebLink( + /// url: 'www.google.co.in', + /// text: 'google', + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// pen: PdfPens.brown, + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.middle)); + /// //Gets or sets the font + /// textWebLink.font = PdfStandardFont(PdfFontFamily.timesRoman, 14); + /// //Draw the web link in the PDF page + /// textWebLink.draw(document.pages.add(), Offset(50, 40)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfFont? font; /// Get or sets the pen. - PdfPen pen; + ///```dart + /// //Create a new Pdf document + /// PdfDocument document = PdfDocument(); + /// //Create the web link in the PDF page + /// PdfTextWebLink textWebLink = PdfTextWebLink( + /// url: 'www.google.co.in', + /// text: 'google', + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 14), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.middle)); + /// //Gets or sets the pen + /// textWebLink.pen = PdfPens.brown; + /// //Draw the web link in the PDF page + /// textWebLink.draw(document.pages.add(), Offset(50, 40)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfPen? pen; /// Get or sets the brush. - PdfBrush brush; + /// ```dart + /// //Create a new Pdf document + /// PdfDocument document = PdfDocument(); + /// //Create the web link in the PDF page + /// PdfTextWebLink textWebLink = PdfTextWebLink( + /// url: 'www.google.co.in', + /// text: 'google', + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 14), + /// pen: PdfPens.brown, + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.middle)); + /// //Gets or sets the brush + /// textWebLink.brush = PdfSolidBrush(PdfColor(0, 0, 0)); + /// //Draw the web link in the PDF page + /// textWebLink.draw(document.pages.add(), Offset(50, 40)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfBrush? brush; /// Get or sets the stringFormat. - PdfStringFormat stringFormat; + /// ```dart + /// //Create a new Pdf document + /// PdfDocument document = PdfDocument(); + /// //Create the web link in the PDF page + /// PdfTextWebLink textWebLink = PdfTextWebLink( + /// url: 'www.google.co.in', + /// text: 'google', + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 14), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// pen: PdfPens.brown); + /// //Gets or sets the stringFormat + /// textWebLink.stringFormat = PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.middle); + /// //Draw the web link in the PDF page + /// textWebLink.draw(document.pages.add(), Offset(50, 40)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfStringFormat? stringFormat; // properties - /// Gets the Uri address. + /// Gets or sets the Uri address. + /// ```dart + /// //Create a new Pdf document + /// PdfDocument document = PdfDocument(); + /// //Create the web link in the PDF page + /// PdfTextWebLink textWebLink = PdfTextWebLink( + /// text: 'google', + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 14), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// pen: PdfPens.brown, + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.middle)); + /// //Sets the url + /// textWebLink.url = 'www.google.co.in'; + /// //Draw the web link in the PDF page + /// textWebLink.draw(document.pages.add(), Offset(50, 40)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` String get url { if (_isLoadedAnnotation) { - return _obtainUrl(); + return _obtainUrl()!; } else { - return _url; + return _url!; } } - /// Sets the Uri address. set url(String value) { - ArgumentError.checkNotNull(value, 'url'); if (value == '') { throw ArgumentError.value('Url - string can not be empty'); } _url = value; if (_isLoadedAnnotation) { if (_dictionary.containsKey(_DictionaryProperties.a)) { - final _PdfDictionary dictionary = + final _PdfDictionary? dictionary = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.a]) - as _PdfDictionary; + as _PdfDictionary?; if (dictionary != null) { dictionary._setString(_DictionaryProperties.uri, _url); } @@ -69,9 +211,9 @@ class PdfTextWebLink extends PdfAnnotation { } // implementation - void _initializeWebLink(String text, PdfFont font, PdfPen pen, PdfBrush brush, - PdfStringFormat format) { - this.text = text != null && text.isNotEmpty ? text : ''; + void _initializeWebLink(String? annotText, PdfFont? font, PdfPen? pen, + PdfBrush? brush, PdfStringFormat? format) { + text = annotText != null && annotText.isNotEmpty ? annotText : ''; if (font != null) { this.font = font; } @@ -87,38 +229,58 @@ class PdfTextWebLink extends PdfAnnotation { } /// Draws a text web link on the PDF page. + /// ```dart + /// //Create a new Pdf document + /// PdfDocument document = PdfDocument(); + /// //Create the web link in the PDF page + /// PdfTextWebLink textWebLink = PdfTextWebLink( + /// url: 'www.google.co.in', + /// text: 'google', + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 14), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// pen: PdfPens.brown, + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.middle)); + /// //Draw the web link in the PDF page + /// textWebLink.draw(document.pages.add(), Offset(50, 40)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` void draw(PdfPage page, Offset location) { if (!_isLoadedAnnotation) { - final Size textSize = font.measureString(text); + final PdfFont pdfFont = + font != null ? font! : PdfStandardFont(PdfFontFamily.helvetica, 8); + final Size textSize = pdfFont.measureString(text); final Rect rect = Rect.fromLTWH( location.dx, location.dy, textSize.width, textSize.height); _uriAnnotation = PdfUriAnnotation(bounds: rect, uri: url); _uriAnnotation.border = PdfAnnotationBorder(0, 0, 0); page.annotations.add(_uriAnnotation); - _drawInternal(page.graphics, rect); + _drawInternal(page.graphics, rect, pdfFont); } } - void _drawInternal(PdfGraphics graphics, Rect bounds) { - ArgumentError.checkNotNull(graphics); - ArgumentError.checkNotNull(font, 'Font cannot be null'); - graphics.drawString(text, font, + void _drawInternal(PdfGraphics graphics, Rect bounds, PdfFont pdfFont) { + graphics.drawString(text, pdfFont, pen: pen, brush: brush, bounds: bounds, format: stringFormat); } - String _obtainUrl() { - String url = ''; + String? _obtainUrl() { + String? url = ''; if (_dictionary.containsKey(_DictionaryProperties.a)) { - final _PdfDictionary dictionary = + final _PdfDictionary? dictionary = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.a]) - as _PdfDictionary; + as _PdfDictionary?; if (dictionary != null && dictionary.containsKey(_DictionaryProperties.uri)) { - final _PdfString text = + final _PdfString? uriText = _PdfCrossTable._dereference(dictionary[_DictionaryProperties.uri]) - as _PdfString; - if (text != null) { - url = text.value; + as _PdfString?; + if (uriText != null) { + url = uriText.value; } } } @@ -126,5 +288,5 @@ class PdfTextWebLink extends PdfAnnotation { } @override - _IPdfPrimitive _element; + _IPdfPrimitive? _element; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_uri_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_uri_annotation.dart index 44218dc63..556505499 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_uri_annotation.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_uri_annotation.dart @@ -5,8 +5,8 @@ class PdfUriAnnotation extends PdfActionLinkAnnotation { // constructor /// Initializes a new instance of the /// [PdfUriAnnotation] class with specified bounds and Uri. - PdfUriAnnotation({Rect bounds, String uri}) : super(bounds) { - ArgumentError.checkNotNull(uri, 'uri'); + PdfUriAnnotation({required Rect bounds, required String uri}) + : super(bounds) { _uriAction ??= PdfUriAction(); this.uri = uri; } @@ -19,13 +19,13 @@ class PdfUriAnnotation extends PdfActionLinkAnnotation { // fields String _uri = ''; - PdfUriAction _uriAction; + PdfUriAction? _uriAction; // properties /// Gets the Uri address. String get uri { if (_isLoadedAnnotation) { - return _getUriText(); + return _getUriText()!; } else { return _uri; } @@ -33,28 +33,28 @@ class PdfUriAnnotation extends PdfActionLinkAnnotation { /// Sets the Uri address. set uri(String value) { - ArgumentError.checkNotNull(value, 'uri'); if (_isLoadedAnnotation) { if (_uri != value) { _uri = value; if (_dictionary.containsKey(_DictionaryProperties.a)) { - final _PdfDictionary dictionary = - _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.a]); + final _PdfDictionary? dictionary = + _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.a]) + as _PdfDictionary?; if (dictionary != null) { dictionary._setString(_DictionaryProperties.uri, _uri); + dictionary.modify(); } - dictionary.modify(); } } } else { _uri = value; - if (_uriAction.uri != value) { - _uriAction.uri = value; + if (_uriAction!.uri != value) { + _uriAction!.uri = value; if (_isLoadedAnnotation) { - _PdfDictionary dictionary = _dictionary; + _PdfDictionary? dictionary = _dictionary; if (_dictionary.containsKey(_DictionaryProperties.a)) { dictionary = _PdfCrossTable._dereference( - _dictionary[_DictionaryProperties.a]) as _PdfDictionary; + _dictionary[_DictionaryProperties.a]) as _PdfDictionary?; if (dictionary != null) { dictionary._setString(_DictionaryProperties.uri, _uri); } @@ -68,13 +68,15 @@ class PdfUriAnnotation extends PdfActionLinkAnnotation { @override /// Gets the action. - PdfAction get action => super.action; + PdfAction? get action => super.action; /// Sets the action. @override - set action(PdfAction value) { - super.action = value; - _uriAction.next = value; + set action(PdfAction? value) { + if (value != null) { + super.action = value; + _uriAction!.next = value; + } } // implementation @@ -85,22 +87,22 @@ class PdfUriAnnotation extends PdfActionLinkAnnotation { _dictionary.setProperty(_PdfName(_DictionaryProperties.subtype), _PdfName(_DictionaryProperties.link)); _dictionary.setProperty(_PdfName(_DictionaryProperties.a), - _uriAction._element ??= _uriAction._dictionary); + _uriAction!._element ??= _uriAction!._dictionary); } - String _getUriText() { - String uriText = ''; + String? _getUriText() { + String? uriText = ''; if (_dictionary.containsKey(_DictionaryProperties.a)) { - final _PdfDictionary dictionary = + final _PdfDictionary? dictionary = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.a]) - as _PdfDictionary; + as _PdfDictionary?; if (dictionary != null && dictionary.containsKey(_DictionaryProperties.uri)) { - final _PdfString text = + final _PdfString? tempText = _PdfCrossTable._dereference(dictionary[_DictionaryProperties.uri]) - as _PdfString; - if (text != null) { - uriText = text.value; + as _PdfString?; + if (tempText != null) { + uriText = tempText.value; } } } @@ -108,5 +110,5 @@ class PdfUriAnnotation extends PdfActionLinkAnnotation { } @override - _IPdfPrimitive _element; + _IPdfPrimitive? _element; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/widget_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/widget_annotation.dart new file mode 100644 index 000000000..83cd5f882 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/widget_annotation.dart @@ -0,0 +1,190 @@ +part of pdf; + +/// Represents the widget annotation. +class _WidgetAnnotation extends PdfAnnotation { + //Constructor + _WidgetAnnotation() : super._() { + _alignment = PdfTextAlignment.left; + _widgetAppearance = _WidgetAppearance(); + _highlightMode = PdfHighlightMode.invert; + } + + _WidgetAnnotation._(_PdfDictionary dictionary, _PdfCrossTable crossTable) + : super._internal(dictionary, crossTable); + + //Fields + _PdfDefaultAppearance? _defaultAppearance; + PdfField? _parent; + PdfTextAlignment? _alignment; + PdfAnnotationBorder? _widgetBorder; + _WidgetAppearance? _widgetAppearance; + PdfHighlightMode? _highlightMode; + _PdfExtendedAppearance? _extendedAppearance; + String? _appearState; + PdfAnnotationActions? _actions; + + //Events + _SavePdfPrimitiveCallback? _beginSave; + + //Properties + /// Gets the default appearance. + _PdfDefaultAppearance get defaultAppearance { + return _defaultAppearance ??= _PdfDefaultAppearance(); + } + + /// Sets the parent. + set parent(PdfField? value) { + if (_parent != value) { + _parent = value; + _parent != null + ? _dictionary.setProperty( + _DictionaryProperties.parent, _PdfReferenceHolder(_parent)) + : _dictionary.remove(_DictionaryProperties.parent); + } + } + + /// Gets and sets the text alignment. + PdfTextAlignment? get textAlignment => _alignment; + set textAlignment(PdfTextAlignment? value) { + if (_alignment != value) { + _alignment = value; + _dictionary.setProperty( + _DictionaryProperties.q, _PdfNumber(_alignment!.index)); + } + } + + /// Gets or sets the highlighting mode. + PdfHighlightMode? get highlightMode => + _isLoadedAnnotation ? _obtainHighlightMode() : _highlightMode; + set highlightMode(PdfHighlightMode? value) { + _highlightMode = value; + _dictionary._setName(_PdfName(_DictionaryProperties.h), + _highlightModeToString(_highlightMode)); + } + + _PdfExtendedAppearance? get extendedAppearance { + _extendedAppearance ??= _PdfExtendedAppearance(); + return _extendedAppearance; + } + + set extendedAppearance(_PdfExtendedAppearance? value) { + _extendedAppearance = value; + } + + set _appearanceState(String value) { + if (_appearState != value) { + _appearState = value; + _dictionary._setName( + _PdfName(_DictionaryProperties.usageApplication), _appearState); + } + } + + PdfAnnotationActions? get actions { + if (_actions == null) { + _actions = PdfAnnotationActions(); + _dictionary.setProperty(_DictionaryProperties.aa, _actions); + } + return _actions; + } + + set actions(PdfAnnotationActions? value) { + if (value != null) { + _actions = value; + _dictionary.setProperty(_DictionaryProperties.aa, _actions); + } + } + + //Implementations + @override + void _save() { + if (_pdfAppearance == null && + _page!._document != null && + (_page!._document!._conformanceLevel == PdfConformanceLevel.a1b || + _page!._document!._conformanceLevel == PdfConformanceLevel.a2b || + _page!._document!._conformanceLevel == PdfConformanceLevel.a3b)) { + throw ArgumentError( + 'The appearance dictionary doesn\'t contain an entry in the conformance PDF.'); + } + super._save(); + _onBeginSave(); + if (_extendedAppearance != null) { + _dictionary.setProperty(_DictionaryProperties.ap, _extendedAppearance); + _dictionary.setProperty(_DictionaryProperties.mk, _widgetAppearance); + } else { + _dictionary.setProperty(_DictionaryProperties.ap, null); + bool isSignatureField = false; + _dictionary.setProperty( + _DictionaryProperties.ap, + _pdfAppearance != null && _pdfAppearance!._templateNormal != null + ? _pdfAppearance + : null); + if (_dictionary.containsKey(_DictionaryProperties.ft)) { + final _IPdfPrimitive? signatureName = + _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.ft]); + if (signatureName is _PdfName && + signatureName._name == _DictionaryProperties.sig) { + isSignatureField = true; + } + } + if (!isSignatureField) { + _dictionary.setProperty(_DictionaryProperties.mk, _widgetAppearance); + } + _dictionary.setProperty(_DictionaryProperties.usageApplication, null); + } + if (_defaultAppearance != null) { + _dictionary.setProperty(_DictionaryProperties.da, + _PdfString(_defaultAppearance!._toString())); + } + } + + @override + void _initialize() { + super._initialize(); + _dictionary._setNumber(_DictionaryProperties.f, 4); //Sets print. + _dictionary.setProperty( + _DictionaryProperties.subtype, _PdfName(_DictionaryProperties.widget)); + _dictionary.setProperty(_DictionaryProperties.bs, + _widgetBorder ??= PdfAnnotationBorder._asWidgetBorder()); + } + + String _highlightModeToString(PdfHighlightMode? highlightingMode) { + switch (highlightingMode) { + case PdfHighlightMode.noHighlighting: + return 'N'; + case PdfHighlightMode.outline: + return 'O'; + case PdfHighlightMode.push: + return 'P'; + default: + return 'I'; + } + } + + void _onBeginSave({_SavePdfPrimitiveArgs? args}) { + if (_beginSave != null) { + _beginSave!(this, args); + } + } + + PdfHighlightMode _obtainHighlightMode() { + PdfHighlightMode mode = PdfHighlightMode.noHighlighting; + if (_dictionary.containsKey(_DictionaryProperties.h)) { + final _PdfName name = _dictionary[_DictionaryProperties.h] as _PdfName; + switch (name._name) { + case 'I': + mode = PdfHighlightMode.invert; + break; + case 'N': + mode = PdfHighlightMode.noHighlighting; + break; + case 'O': + mode = PdfHighlightMode.outline; + break; + case 'P': + mode = PdfHighlightMode.push; + break; + } + } + return mode; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/widget_appearance.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/widget_appearance.dart new file mode 100644 index 000000000..4e6385532 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/widget_appearance.dart @@ -0,0 +1,64 @@ +part of pdf; + +/// The Syncfusion.Pdf.Interactive namespace contains classes used to create interactive elements. +class _WidgetAppearance implements _IPdfWrapper { + //Constructors + _WidgetAppearance() : super() { + _dictionary.setProperty( + _DictionaryProperties.bc, _borderColor._toArray(PdfColorSpace.rgb)); + _dictionary.setProperty( + _DictionaryProperties.bg, _backColor._toArray(PdfColorSpace.rgb)); + } + + //Fields + final _PdfDictionary _dictionary = _PdfDictionary(); + PdfColor _borderColor = PdfColor(0, 0, 0); + PdfColor _backColor = PdfColor(255, 255, 255); + String? _normalCaption = ''; + + //Properties + /// Gets or sets the color of the border. + PdfColor get borderColor => _borderColor; + set borderColor(PdfColor value) { + if (_borderColor != value) { + _borderColor = value; + value._alpha == 0 + ? _dictionary.setProperty(_DictionaryProperties.bc, _PdfArray([])) + : _dictionary.setProperty(_DictionaryProperties.bc, + _borderColor._toArray(PdfColorSpace.rgb)); + } + } + + /// Gets or sets the color of the background. + PdfColor get backColor => _backColor; + set backColor(PdfColor value) { + if (_backColor != value) { + _backColor = value; + if (_backColor._alpha == 0) { + _dictionary.setProperty(_DictionaryProperties.bc, _PdfArray([0, 0, 0])); + _dictionary.remove(_DictionaryProperties.bg); + } else { + _dictionary.setProperty( + _DictionaryProperties.bg, _backColor._toArray(PdfColorSpace.rgb)); + } + } + } + + String? get normalCaption => _normalCaption; + set normalCaption(String? value) { + if (_normalCaption != value) { + _normalCaption = value; + _dictionary._setString(_DictionaryProperties.ca, _normalCaption); + } + } + + //Overrides + @override + _IPdfPrimitive get _element => _dictionary; + + @override + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + throw ArgumentError('Primitive element can\'t be set'); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/color_space/pdf_icc_color_profile.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/color_space/pdf_icc_color_profile.dart index 2e0bf836c..521540df5 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/color_space/pdf_icc_color_profile.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/color_space/pdf_icc_color_profile.dart @@ -21,7 +21,7 @@ class _PdfICCColorProfile implements _IPdfWrapper { //Implementation. //Handles the BeginSave event of the Stream control. - void _beginSaveStream(Object sender, _SavePdfPrimitiveArgs args) { + void _beginSaveStream(Object sender, _SavePdfPrimitiveArgs? args) { _stream._clearStream(); _stream._dataStream = base64.decode(profileData).toList(); } @@ -32,7 +32,8 @@ class _PdfICCColorProfile implements _IPdfWrapper { _IPdfPrimitive get _element => _stream; @override - set _element(_IPdfPrimitive value) { + // ignore: unused_element + set _element(_IPdfPrimitive? value) { throw ArgumentError('value of element cannot be set'); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_reader.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_reader.dart index eb5201566..d0ed43e42 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_reader.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_reader.dart @@ -2,36 +2,36 @@ part of pdf; class _CompressedStreamReader { //constructor - _CompressedStreamReader(List data, [bool noWrap]) { + _CompressedStreamReader(List data, [bool? noWrap]) { _data = data; _noWrap = noWrap ?? false; _initialize(); } //Fields - List _data; - bool _noWrap; - int _offset; - int _bufferedBits; - int _buffer; - List _tempBuffer; - int _maxUnsingedLimit; - int _defHeaderMethodMask; - int _windowSize; - int _defHeaderInfoMask; - int _maxValue; - int _defHeaderFlagsFdict; - bool _canReadNextBlock; - bool _readingUncompressed; - int _uncompressedDataLength; - int _currentPosition; + late List _data; + late bool _noWrap; + late int _offset; + late int _bufferedBits; + late int _buffer; + late List _tempBuffer; + late int _maxUnsingedLimit; + late int _defHeaderMethodMask; + late int _windowSize; + late int _defHeaderInfoMask; + late int _maxValue; + late int _defHeaderFlagsFdict; + late bool _canReadNextBlock; + late bool _readingUncompressed; + late int _uncompressedDataLength; + late int _currentPosition; int _dataLength = 0; bool _canReadMoreData = true; bool _checkSumRead = false; int _checkSum = 1; - List _blockBuffer; - _DecompressorHuffmanTree _currentLengthTree; - _DecompressorHuffmanTree _currentDistanceTree; + List? _blockBuffer; + _DecompressorHuffmanTree? _currentLengthTree; + _DecompressorHuffmanTree? _currentDistanceTree; static const List def_huffman_dyntree_repeat_bits = [2, 3, 7]; static const List def_huffman_dyntree_repeat_minimums = [3, 3, 11]; static const List def_huffman_repeat_length_base = [ @@ -200,7 +200,7 @@ class _CompressedStreamReader { if ((header & _defHeaderMethodMask) != (8 << 8)) { throw ArgumentError.value(header, 'Unsupported compression method.'); } - _windowSize = pow(2, ((header & _defHeaderInfoMask) >> 12) + 8); + _windowSize = pow(2, ((header & _defHeaderInfoMask) >> 12) + 8) as int; if (_windowSize > _maxValue) { throw ArgumentError.value( header, 'Unsupported window size for deflate compression method.'); @@ -264,8 +264,9 @@ class _CompressedStreamReader { return true; } - Map _decodeDynamicHeader(_DecompressorHuffmanTree lengthTree, - _DecompressorHuffmanTree distanceTree) { + Map _decodeDynamicHeader( + _DecompressorHuffmanTree? lengthTree, + _DecompressorHuffmanTree? distanceTree) { List arrDecoderCodeLengths; List arrResultingCodeLengths; @@ -453,8 +454,8 @@ class _CompressedStreamReader { Map _readByte() { if (_offset < _data.length) { - final int result = _data[_offset]; - _offset++; + final int? result = _data[_offset]; + _offset = _offset + 1; return {'hasRead': true, 'result': result}; } else { return {'hasRead': false, 'result': -1}; @@ -480,7 +481,7 @@ class _CompressedStreamReader { // if something left, skip it. if (count > 0) { // Skip entire bytes. - _offset += count >> 3; + _offset = _offset + count >> 3; count &= 7; // Skip bits. if (count > 0) { @@ -496,7 +497,6 @@ class _CompressedStreamReader { } Map _read(List buffer, int offset, int length) { - ArgumentError.checkNotNull(buffer); if (offset < 0 || offset > buffer.length - 1) { throw ArgumentError.value( offset, 'Offset does not belong to specified buffer.'); @@ -513,7 +513,7 @@ class _CompressedStreamReader { dataToCopy = min(dataToCopy, length); // Copy data. - List.copyRange(buffer, offset, _blockBuffer, inBlockPosition, + List.copyRange(buffer, offset, _blockBuffer!, inBlockPosition, inBlockPosition + dataToCopy); _currentPosition += dataToCopy; offset += dataToCopy; @@ -541,13 +541,11 @@ class _CompressedStreamReader { final int dataToRead = min(_uncompressedDataLength, _maxValue - inBlockPosition); final int dataRead = - _readPackedBytes(_blockBuffer, inBlockPosition, dataToRead); - + _readPackedBytes(_blockBuffer!, inBlockPosition, dataToRead); if (dataToRead != dataRead) { throw ArgumentError.value( dataToRead, 'Not enough data in stream.'); } - _uncompressedDataLength -= dataRead; _dataLength += dataRead; } @@ -582,14 +580,12 @@ class _CompressedStreamReader { }; } - void _checksumUpdate(List buffer, int offset, int length) { + void _checksumUpdate(List? buffer, int offset, int length) { _checkSum = _ChecksumCalculator._checksumUpdate(_checkSum, buffer, offset, length); } int _readPackedBytes(List buffer, int offset, int length) { - ArgumentError.checkNotNull(buffer); - if (offset < 0 || offset > buffer.length - 1) { throw ArgumentError.value(offset, '''Offset can not be less than zero or greater than buffer length - 1.'''); @@ -623,9 +619,10 @@ class _CompressedStreamReader { } if (length > 0) { - final Map returnValue = _read(buffer, offset, length); - result += returnValue['length']; - buffer = returnValue['buffer']; + final Map value = _read(buffer, offset, length); + final int val = value['length']; + result += val.toInt(); + buffer = value['buffer']; } return result; } @@ -635,8 +632,9 @@ class _CompressedStreamReader { bool dataRead = false; int symbol = 0; while (free >= def_huffman_repeat_max) { - while (((symbol = _currentLengthTree._unpackSymbol(this)) & ~0xff) == 0) { - _blockBuffer[_dataLength++ % _maxValue] = symbol.toUnsigned(8); + while ( + ((symbol = _currentLengthTree!._unpackSymbol(this)) & ~0xff) == 0) { + _blockBuffer![_dataLength++ % _maxValue] = symbol.toUnsigned(8); dataRead = true; if (--free < def_huffman_repeat_max) { return true; @@ -670,7 +668,7 @@ class _CompressedStreamReader { } // Unpack repeat distance. - symbol = _currentDistanceTree._unpackSymbol(this); + symbol = _currentDistanceTree!._unpackSymbol(this); if (symbol < 0 || symbol > def_huffman_repeat_distanse_base.length) { throw ArgumentError.value(symbol, 'Wrong distance code.'); @@ -686,8 +684,8 @@ class _CompressedStreamReader { } // Copy data in slow repeat mode for (int i = 0; i < iRepeatLength; i++) { - _blockBuffer[_dataLength % _maxValue] = - _blockBuffer[(_dataLength - iRepeatDistance) % _maxValue]; + _blockBuffer![_dataLength % _maxValue] = + _blockBuffer![(_dataLength - iRepeatDistance) % _maxValue]; _dataLength++; free--; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_writer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_writer.dart index dd45a72c0..934269c6e 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_writer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_writer.dart @@ -51,8 +51,7 @@ class _Utils { class _CompressedStreamWriter { _CompressedStreamWriter(List outputStream, bool bNoWrap, - PdfCompressionLevel level, bool bCloseStream) { - ArgumentError.checkNotNull(outputStream, 'outputStream'); + PdfCompressionLevel? level, bool bCloseStream) { _treeLiteral = _CompressorHuffmanTree( this, def_huffman_literal_alphabet_length, 257, 15); _treeDistances = _CompressorHuffmanTree( @@ -70,10 +69,10 @@ class _CompressedStreamWriter { _hashPrevious = List.filled(wsize, 0); _blockStart = _stringStart = 1; - _goodLength = good_length[_getCompressionLevel(level)]; - _niceLength = nice_length[_getCompressionLevel(level)]; - _maximumChainLength = max_chain[_getCompressionLevel(level)]; - _maximumLazySearch = max_lazy[_getCompressionLevel(level)]; + _goodLength = good_length[_getCompressionLevel(level)!]; + _niceLength = nice_length[_getCompressionLevel(level)!]; + _maximumChainLength = max_chain[_getCompressionLevel(level)!]; + _maximumLazySearch = max_lazy[_getCompressionLevel(level)!]; if (!bNoWrap) { _writeZLIBHeader(); @@ -194,22 +193,22 @@ class _CompressedStreamWriter { final List _pendingBuffer = List.filled(def_pending_buffer_size, 0); int _pendingBufferLength = 0; - List _arrDistancesBuffer; - List _arrLiteralsBuffer; - List _stream; - PdfCompressionLevel _level; + late List _arrDistancesBuffer; + late List _arrLiteralsBuffer; + late List _stream; + PdfCompressionLevel? _level; bool _bNoWrap = false; bool _bCloseStream = false; - List _dataWindow; - List _hashHead; - List _hashPrevious; + List? _dataWindow; + late List _hashHead; + late List _hashPrevious; int _blockStart = 0; int _stringStart = 0; int _lookAhead = 0; int _maximumChainLength = 0; int _niceLength = 0; int _goodLength = 0; - List _inputBuffer; + List? _inputBuffer; int _inputOffset = 0; int _inputEnd = 0; bool _bStreamClosed = false; @@ -221,20 +220,20 @@ class _CompressedStreamWriter { int _matchLength = 0; int _iBufferPosition = 0; bool _matchPreviousAvailable = false; - _CompressorHuffmanTree _treeLiteral; - _CompressorHuffmanTree _treeDistances; - _CompressorHuffmanTree _treeCodeLengths; + late _CompressorHuffmanTree _treeLiteral; + late _CompressorHuffmanTree _treeDistances; + _CompressorHuffmanTree? _treeCodeLengths; int _iExtraBits = 0; - static List _arrLiteralCodes; - static List _arrLiteralLengths; - static List _arrDistanceCodes; - static List _arrDistanceLengths; + static List? _arrLiteralCodes; + static late List _arrLiteralLengths; + static late List _arrDistanceCodes; + static late List _arrDistanceLengths; bool get _needsInput => _inputEnd == _inputOffset; bool get _pendingBufferIsFlushed => _pendingBufferLength == 0; bool get _huffmanIsFull => _iBufferPosition >= def_huffman_buffer_size; int _maximumLazySearch = 0; - int _getCompressionLevel(PdfCompressionLevel level) { + int? _getCompressionLevel(PdfCompressionLevel? level) { switch (level) { case PdfCompressionLevel.none: return 0; @@ -261,19 +260,19 @@ class _CompressedStreamWriter { List.filled(def_huffman_literal_alphabet_length, 0); int i = 0; while (i < 144) { - _arrLiteralCodes[i] = _Utils.bitReverse((0x030 + i) << 8); + _arrLiteralCodes![i] = _Utils.bitReverse((0x030 + i) << 8); _arrLiteralLengths[i++] = 8; } while (i < 256) { - _arrLiteralCodes[i] = _Utils.bitReverse(((0x190 - 144) + i) << 7); + _arrLiteralCodes![i] = _Utils.bitReverse(((0x190 - 144) + i) << 7); _arrLiteralLengths[i++] = 9; } while (i < 280) { - _arrLiteralCodes[i] = _Utils.bitReverse(((0x000 - 256) + i) << 9); + _arrLiteralCodes![i] = _Utils.bitReverse(((0x000 - 256) + i) << 9); _arrLiteralLengths[i++] = 7; } while (i < def_huffman_literal_alphabet_length) { - _arrLiteralCodes[i] = _Utils.bitReverse(((0x0c0 - 280) + i) << 8); + _arrLiteralCodes![i] = _Utils.bitReverse(((0x0c0 - 280) + i) << 8); _arrLiteralLengths[i++] = 8; } _arrDistanceCodes = @@ -292,7 +291,7 @@ class _CompressedStreamWriter { // Initialize header. int iHeaderData = def_zlib_header_template; // Save compression level. - iHeaderData |= ((_getCompressionLevel(_level) >> 2) & 3) << 6; + iHeaderData |= ((_getCompressionLevel(_level)! >> 2) & 3) << 6; // Align header. iHeaderData += 31 - (iHeaderData % 31); // Write header to stream. @@ -305,7 +304,6 @@ class _CompressedStreamWriter { } void write(List data, int offset, int length, bool bCloseAfterWrite) { - ArgumentError.checkNotNull(data, 'data'); final int end = offset + length; if (0 > offset || offset > end || end > data.length) { throw Exception('Offset or length is incorrect.'); @@ -371,7 +369,7 @@ class _CompressedStreamWriter { while (_lookAhead >= min_lookahead || flush) { if (_lookAhead == 0) { if (_matchPreviousAvailable) { - _huffmanTallyLit(_dataWindow[_stringStart - 1] & 0xff); + _huffmanTallyLit(_dataWindow![_stringStart - 1] & 0xff); } _matchPreviousAvailable = false; @@ -426,7 +424,7 @@ class _CompressedStreamWriter { _matchLength = min_match - 1; } else { if (_matchPreviousAvailable) { - _huffmanTallyLit(_dataWindow[_stringStart - 1] & 0xff); + _huffmanTallyLit(_dataWindow![_stringStart - 1] & 0xff); } _matchPreviousAvailable = true; @@ -499,7 +497,7 @@ class _CompressedStreamWriter { continue; } else { - _huffmanTallyLit(_dataWindow[_stringStart] & 0xff); + _huffmanTallyLit(_dataWindow![_stringStart] & 0xff); ++_stringStart; --_lookAhead; } @@ -525,8 +523,8 @@ class _CompressedStreamWriter { final int limit = max(_stringStart - max_dist, 0); final int strend = _stringStart + max_match - 1; - int scanEnd1 = _dataWindow[bestEnd - 1]; - int scanEnd = _dataWindow[bestEnd]; + int scanEnd1 = _dataWindow![bestEnd - 1]; + int scanEnd = _dataWindow![bestEnd]; /// Do not waste too much time if we already have a good match. if (bestLen >= _goodLength) { @@ -541,10 +539,10 @@ class _CompressedStreamWriter { } do { - if (_dataWindow[curMatch + bestLen] != scanEnd || - _dataWindow[curMatch + bestLen - 1] != scanEnd1 || - _dataWindow[curMatch] != _dataWindow[scan] || - _dataWindow[curMatch + 1] != _dataWindow[scan + 1]) { + if (_dataWindow![curMatch + bestLen] != scanEnd || + _dataWindow![curMatch + bestLen - 1] != scanEnd1 || + _dataWindow![curMatch] != _dataWindow![scan] || + _dataWindow![curMatch + 1] != _dataWindow![scan + 1]) { continue; } @@ -554,14 +552,14 @@ class _CompressedStreamWriter { /// We check for insufficient _lookAhead only every 8th comparison /// and the 256th check will be made at _stringStart + 258. - while (_dataWindow[++scan] == _dataWindow[++match] && - _dataWindow[++scan] == _dataWindow[++match] && - _dataWindow[++scan] == _dataWindow[++match] && - _dataWindow[++scan] == _dataWindow[++match] && - _dataWindow[++scan] == _dataWindow[++match] && - _dataWindow[++scan] == _dataWindow[++match] && - _dataWindow[++scan] == _dataWindow[++match] && - _dataWindow[++scan] == _dataWindow[++match] && + while (_dataWindow![++scan] == _dataWindow![++match] && + _dataWindow![++scan] == _dataWindow![++match] && + _dataWindow![++scan] == _dataWindow![++match] && + _dataWindow![++scan] == _dataWindow![++match] && + _dataWindow![++scan] == _dataWindow![++match] && + _dataWindow![++scan] == _dataWindow![++match] && + _dataWindow![++scan] == _dataWindow![++match] && + _dataWindow![++scan] == _dataWindow![++match] && scan < strend) {} if (scan > bestEnd) { @@ -573,8 +571,8 @@ class _CompressedStreamWriter { break; } - scanEnd1 = _dataWindow[bestEnd - 1]; - scanEnd = _dataWindow[bestEnd]; + scanEnd1 = _dataWindow![bestEnd - 1]; + scanEnd = _dataWindow![bestEnd]; } scan = _stringStart; } while ((curMatch = _hashPrevious[curMatch & wmask] & 0xffff) > limit && @@ -604,7 +602,7 @@ class _CompressedStreamWriter { } void _huffmanFlushBlock( - List stored, int storedOffset, int storedLength, bool lastBlock) { + List? stored, int storedOffset, int storedLength, bool lastBlock) { _treeLiteral._codeFrequences[def_huffman_endblock_symbol]++; // Build trees. @@ -616,19 +614,19 @@ class _CompressedStreamWriter { _treeDistances._calcBLFreq(_treeCodeLengths); // Build bitlen tree. - _treeCodeLengths._buildTree(); + _treeCodeLengths!._buildTree(); int blTreeCodes = 4; for (int i = 18; i > blTreeCodes; i--) { - if (_treeCodeLengths - ._codeLengths[def_huffman_dyntree_codelengths_order[i]] > + if (_treeCodeLengths! + ._codeLengths![def_huffman_dyntree_codelengths_order[i]] > 0) { blTreeCodes = i + 1; } } int optLen = 14 + blTreeCodes * 3 + - _treeCodeLengths._getEncodedLength() + + _treeCodeLengths!._getEncodedLength() + _treeLiteral._getEncodedLength() + _treeDistances._getEncodedLength() + _iExtraBits; @@ -646,11 +644,11 @@ class _CompressedStreamWriter { } if (storedOffset >= 0 && storedLength + 4 < optLen >> 3) { - _huffmanFlushStoredBlock(stored, storedOffset, storedLength, lastBlock); + _huffmanFlushStoredBlock(stored!, storedOffset, storedLength, lastBlock); } else if (optLen == staticLen) { // Encode with static tree. _pendingBufferWriteBits((1 << 1) + (lastBlock ? 1 : 0), 3); - _treeLiteral._setStaticCodes(_arrLiteralCodes, _arrLiteralLengths); + _treeLiteral._setStaticCodes(_arrLiteralCodes!, _arrLiteralLengths); _treeDistances._setStaticCodes(_arrDistanceCodes, _arrDistanceLengths); _huffmanCompressBlock(); _huffmanReset(); @@ -664,7 +662,7 @@ class _CompressedStreamWriter { } void _huffmanSendAllTrees(int blTreeCodes) { - _treeCodeLengths._buildCodes(); + _treeCodeLengths!._buildCodes(); _treeLiteral._buildCodes(); _treeDistances._buildCodes(); _pendingBufferWriteBits(_treeLiteral._codeCount - 257, 5); @@ -673,8 +671,8 @@ class _CompressedStreamWriter { for (int rank = 0; rank < blTreeCodes; rank++) { _pendingBufferWriteBits( - _treeCodeLengths - ._codeLengths[def_huffman_dyntree_codelengths_order[rank]], + _treeCodeLengths! + ._codeLengths![def_huffman_dyntree_codelengths_order[rank]], 3); } @@ -685,7 +683,7 @@ class _CompressedStreamWriter { int _insertString() { int match; final int hash = ((_currentHash << hash_shift) ^ - _dataWindow[_stringStart + (min_match - 1)]) & + _dataWindow![_stringStart + (min_match - 1)]) & hash_mask; _hashPrevious[_stringStart & wmask] = match = _hashHead[hash]; @@ -763,7 +761,7 @@ class _CompressedStreamWriter { _iExtraBits = 0; _treeLiteral._reset(); _treeDistances._reset(); - _treeCodeLengths._reset(); + _treeCodeLengths!._reset(); } void _pendingBufferWriteShort(int s) { @@ -816,7 +814,7 @@ class _CompressedStreamWriter { if (more > _inputEnd - _inputOffset) { more = _inputEnd - _inputOffset; } - List.copyRange(_dataWindow, _stringStart + _lookAhead, _inputBuffer, + List.copyRange(_dataWindow!, _stringStart + _lookAhead, _inputBuffer!, _inputOffset, _inputOffset + more); _inputOffset += more; @@ -829,7 +827,7 @@ class _CompressedStreamWriter { } void _slideWindow() { - List.copyRange(_dataWindow, 0, _dataWindow, wsize, wsize + wsize); + List.copyRange(_dataWindow!, 0, _dataWindow!, wsize, wsize + wsize); _matchStart -= wsize; _stringStart -= wsize; _blockStart -= wsize; @@ -846,8 +844,8 @@ class _CompressedStreamWriter { } void _updateHash() { - _currentHash = (_dataWindow[_stringStart] << hash_shift) ^ - _dataWindow[_stringStart + 1]; + _currentHash = (_dataWindow![_stringStart] << hash_shift) ^ + _dataWindow![_stringStart + 1]; } void _pendingBufferFlush() { @@ -908,7 +906,7 @@ class _ChecksumCalculator { static const int checksumIterationCount = 3800; static int _checksumUpdate( - int checksum, List buffer, int offset, int length) { + int checksum, List? buffer, int offset, int length) { int checksumUint = checksum.toUnsigned(32); int s1 = checksumUint & 65535; int s2 = checksumUint >> checkSumBitOffset; @@ -916,7 +914,7 @@ class _ChecksumCalculator { int steps = min(length, checksumIterationCount); length -= steps; while (--steps >= 0) { - s1 = s1 + (buffer[offset++] & 255).toUnsigned(32); + s1 = s1 + (buffer![offset++] & 255).toUnsigned(32); s2 += s1; } s1 %= checksumBase; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressor_huffman_tree.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressor_huffman_tree.dart index 0dd6736f6..31b60e1d5 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressor_huffman_tree.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressor_huffman_tree.dart @@ -29,14 +29,14 @@ class _CompressorHuffmanTree { 7, 15 ]; - List _codeFrequences; - List _codes; - List _codeLengths; - List _lengthCounts; - int _codeMinimumCount; - int _codeCount; - int _maximumLength; - _CompressedStreamWriter _writer; + late List _codeFrequences; + List? _codes; + List? _codeLengths; + late List _lengthCounts; + late int _codeMinimumCount; + late int _codeCount; + int? _maximumLength; + late _CompressedStreamWriter _writer; void _buildTree() { final int iCodesCount = _codeFrequences.length; final List arrTree = List.filled(iCodesCount, 0); @@ -151,7 +151,7 @@ class _CompressorHuffmanTree { final int numLeafs = (numNodes + 1) ~/ 2; int overflow = 0; - for (int i = 0; i < _maximumLength; i++) { + for (int i = 0; i < _maximumLength!; i++) { _lengthCounts[i] = 0; } @@ -165,8 +165,8 @@ class _CompressorHuffmanTree { if (childs[iChildIndex] != -1) { int bitLength = lengths[i] + 1; - if (bitLength > _maximumLength) { - bitLength = _maximumLength; + if (bitLength > _maximumLength!) { + bitLength = _maximumLength!; overflow++; } @@ -175,7 +175,7 @@ class _CompressorHuffmanTree { } else { final int bitLength = lengths[i]; _lengthCounts[bitLength - 1]++; - _codeLengths[childs[iChildIndex - 1]] = lengths[i].toUnsigned(8); + _codeLengths![childs[iChildIndex - 1]] = lengths[i].toUnsigned(8); } } @@ -183,7 +183,7 @@ class _CompressorHuffmanTree { return; } - int iIncreasableLength = _maximumLength - 1; + int iIncreasableLength = _maximumLength! - 1; do { // Find the first bit _codeLengths which could increase. @@ -192,31 +192,31 @@ class _CompressorHuffmanTree { do { _lengthCounts[iIncreasableLength]--; _lengthCounts[++iIncreasableLength]++; - overflow -= 1 << (_maximumLength - 1 - iIncreasableLength); - } while (overflow > 0 && iIncreasableLength < _maximumLength - 1); + overflow -= 1 << (_maximumLength! - 1 - iIncreasableLength); + } while (overflow > 0 && iIncreasableLength < _maximumLength! - 1); } while (overflow > 0); - _lengthCounts[_maximumLength - 1] += overflow; - _lengthCounts[_maximumLength - 2] -= overflow; + _lengthCounts[_maximumLength! - 1] += overflow; + _lengthCounts[_maximumLength! - 2] -= overflow; // Recreate tree. int nodePtr = 2 * numLeafs; - for (int bits = _maximumLength; bits != 0; bits--) { - int n = _lengthCounts[bits - 1]; + for (int? bits = _maximumLength; bits != 0; bits--) { + int n = _lengthCounts[bits! - 1]; while (n > 0) { final int childPtr = 2 * childs[nodePtr++]; if (childs[childPtr + 1] == -1) { - _codeLengths[childs[childPtr]] = bits.toUnsigned(8); + _codeLengths![childs[childPtr]] = bits.toUnsigned(8); n--; } } } } - void _calcBLFreq(_CompressorHuffmanTree blTree) { + void _calcBLFreq(_CompressorHuffmanTree? blTree) { int maxCount; int minCount; int count; @@ -224,7 +224,7 @@ class _CompressorHuffmanTree { int i = 0; while (i < _codeCount) { count = 1; - final int nextlen = _codeLengths[i]; + final int nextlen = _codeLengths![i]; if (nextlen == 0) { maxCount = 138; minCount = 3; @@ -232,26 +232,26 @@ class _CompressorHuffmanTree { maxCount = 6; minCount = 3; if (curlen != nextlen) { - blTree._codeFrequences[nextlen]++; + blTree!._codeFrequences[nextlen]++; count = 0; } } curlen = nextlen; i++; - while (i < _codeCount && curlen == _codeLengths[i]) { + while (i < _codeCount && curlen == _codeLengths![i]) { i++; if (++count >= maxCount) { break; } } if (count < minCount) { - blTree._codeFrequences[curlen] += count.toSigned(16); + blTree!._codeFrequences[curlen] += count.toSigned(16); } else if (curlen != 0) { - blTree._codeFrequences[16]++; + blTree!._codeFrequences[16]++; } else if (count <= 10) { - blTree._codeFrequences[17]++; + blTree!._codeFrequences[17]++; } else { - blTree._codeFrequences[18]++; + blTree!._codeFrequences[18]++; } } } @@ -259,13 +259,14 @@ class _CompressorHuffmanTree { int _getEncodedLength() { int len = 0; for (int i = 0; i < _codeFrequences.length; i++) { - len += _codeFrequences[i] * _codeLengths[i]; + len += _codeFrequences[i] * _codeLengths![i]; } return len; } void _writeCodeToStream(int code) { - _writer._pendingBufferWriteBits(_codes[code] & 0xffff, _codeLengths[code]); + _writer._pendingBufferWriteBits( + _codes![code] & 0xffff, _codeLengths![code]); } void _setStaticCodes(List codes, List lengths) { @@ -273,7 +274,7 @@ class _CompressorHuffmanTree { _codeLengths = List.from(lengths); } - void _writeTree(_CompressorHuffmanTree blTree) { + void _writeTree(_CompressorHuffmanTree? blTree) { int iMaxRepeatCount; int iMinRepeatCount; int iCurrentRepeatCount; @@ -282,7 +283,7 @@ class _CompressorHuffmanTree { int i = 0; while (i < _codeCount) { iCurrentRepeatCount = 1; - final int nextlen = _codeLengths[i]; + final int nextlen = _codeLengths![i]; if (nextlen == 0) { iMaxRepeatCount = 138; @@ -292,7 +293,7 @@ class _CompressorHuffmanTree { iMinRepeatCount = 3; if (iCurrentCodeLength != nextlen) { - blTree._writeCodeToStream(nextlen); + blTree!._writeCodeToStream(nextlen); iCurrentRepeatCount = 0; } } @@ -300,7 +301,7 @@ class _CompressorHuffmanTree { iCurrentCodeLength = nextlen; i++; - while (i < _codeCount && iCurrentCodeLength == _codeLengths[i]) { + while (i < _codeCount && iCurrentCodeLength == _codeLengths![i]) { i++; if (++iCurrentRepeatCount >= iMaxRepeatCount) { @@ -310,36 +311,36 @@ class _CompressorHuffmanTree { if (iCurrentRepeatCount < iMinRepeatCount) { while (iCurrentRepeatCount-- > 0) { - blTree._writeCodeToStream(iCurrentCodeLength); + blTree!._writeCodeToStream(iCurrentCodeLength); } } else if (iCurrentCodeLength != 0) { - blTree._writeCodeToStream(16); + blTree!._writeCodeToStream(16); _writer._pendingBufferWriteBits(iCurrentRepeatCount - 3, 2); } else if (iCurrentRepeatCount <= 10) { - blTree._writeCodeToStream(17); + blTree!._writeCodeToStream(17); _writer._pendingBufferWriteBits(iCurrentRepeatCount - 3, 3); } else { - blTree._writeCodeToStream(18); + blTree!._writeCodeToStream(18); _writer._pendingBufferWriteBits(iCurrentRepeatCount - 11, 7); } } } void _buildCodes() { - final List nextCode = List.filled(_maximumLength, 0); + final List nextCode = List.filled(_maximumLength!, 0); _codes = List.filled(_codeCount, 0); int code = 0; - for (int bitsCount = 0; bitsCount < _maximumLength; bitsCount++) { + for (int bitsCount = 0; bitsCount < _maximumLength!; bitsCount++) { nextCode[bitsCount] = code; code += _lengthCounts[bitsCount] << (15 - bitsCount); } for (int i = 0; i < _codeCount; i++) { - final int bits = _codeLengths[i]; + final int bits = _codeLengths![i]; if (bits > 0) { - _codes[i] = _bitReverse(nextCode[bits - 1]); + _codes![i] = _bitReverse(nextCode[bits - 1]); nextCode[bits - 1] += 1 << (16 - bits); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/decompressor_huffman_tree.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/decompressor_huffman_tree.dart index d16286a6b..1c9868797 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/decompressor_huffman_tree.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/decompressor_huffman_tree.dart @@ -7,19 +7,19 @@ class _DecompressorHuffmanTree { //Fields static const int _maxBitLength = 15; - List _tree; - static _DecompressorHuffmanTree _lengthTree; - static _DecompressorHuffmanTree _distanceTree; + late List _tree; + static _DecompressorHuffmanTree? _lengthTree; + static _DecompressorHuffmanTree? _distanceTree; //Properties - static _DecompressorHuffmanTree get lengthTree { + static _DecompressorHuffmanTree? get lengthTree { if (_lengthTree == null) { _initialize(); } return _lengthTree; } - static _DecompressorHuffmanTree get distanceTree { + static _DecompressorHuffmanTree? get distanceTree { if (_distanceTree == null) { _initialize(); } @@ -64,17 +64,17 @@ class _DecompressorHuffmanTree { List.filled(_maxBitLength + 1, 0, growable: true); final List nextCode = List.filled(_maxBitLength + 1, 0, growable: true); - int treeSize; - int code = 0; + int? treeSize; + int? code = 0; final Map result = _prepareData(blCount, nextCode, lengths, treeSize); treeSize = result['treeSize']; code = result['code']; - _tree = _treeFromData(blCount, nextCode, lengths, code, treeSize); + _tree = _treeFromData(blCount, nextCode, lengths, code, treeSize!); } Map _prepareData( - List blCount, List nextCode, List lengths, int treeSize) { + List blCount, List nextCode, List lengths, int? treeSize) { int code = 0; treeSize = 512; for (int i = 0; i < lengths.length; i++) { @@ -90,19 +90,19 @@ class _DecompressorHuffmanTree { if (bits >= 10) { final int start = nextCode[bits] & 0x1ff80; final int end = code & 0x1ff80; - treeSize += (end - start) >> (16 - bits); + treeSize = treeSize! + ((end - start) >> (16 - bits)); } } return {'treeSize': treeSize, 'code': code}; } List _treeFromData(List blCount, List nextCode, - List lengths, int code, int treeSize) { + List lengths, int? code, int treeSize) { final List tree = List.filled(treeSize, 0, growable: true); int pointer = 512; const int increment = 1 << 7; for (int bits = _maxBitLength; bits >= 10; bits--) { - final int end = code & 0x1ff80; + final int end = code! & 0x1ff80; code -= blCount[bits] << (16 - bits); final int start = code & 0x1ff80; for (int i = start; i < end; i += increment) { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/decompressed_output.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/decompressed_output.dart index 4ad18957a..632894319 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/decompressed_output.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/decompressed_output.dart @@ -11,17 +11,17 @@ class _DecompressedOutput { //Fields static const int _dOutSize = 32768; static const int _dOutMask = 32767; - List _dOutput; - int _end; - int _usedBytes; + List? _dOutput; + int _end = 0; + int _usedBytes = 0; //Properties int get unusedBytes => _dOutSize - _usedBytes; - int get usedBytes => _usedBytes; + int? get usedBytes => _usedBytes; //Implementation void _write(int b) { - _dOutput[_end++] = b; + _dOutput![_end++] = b; _end &= _dOutMask; ++_usedBytes; } @@ -32,16 +32,17 @@ class _DecompressedOutput { final int border = _dOutSize - length; if (copyStart <= border && _end < border) { if (length <= distance) { - List.copyRange(_dOutput, _end, _dOutput, copyStart, copyStart + length); + List.copyRange( + _dOutput!, _end, _dOutput!, copyStart, copyStart + length); _end += length; } else { while (length-- > 0) { - _dOutput[_end++] = _dOutput[copyStart++]; + _dOutput![_end++] = _dOutput![copyStart++]; } } } else { while (length-- > 0) { - _dOutput[_end++] = _dOutput[copyStart++]; + _dOutput![_end++] = _dOutput![copyStart++]; _end &= _dOutMask; copyStart &= _dOutMask; } @@ -66,7 +67,7 @@ class _DecompressedOutput { } Map _copyTo(List output, int offset, int length) { - int end; + int? end; if (length > _usedBytes) { end = _end; length = _usedBytes; @@ -79,20 +80,20 @@ class _DecompressedOutput { if (tailLen > 0) { for (int i = 0; i < tailLen && - i + sourceStart < _dOutput.length && + i + sourceStart < _dOutput!.length && i + offset < output.length; i++) { - output[offset + i] = _dOutput[sourceStart + i]; + output[offset + i] = _dOutput![sourceStart + i]; } final int sourceStartIndex = _dOutSize - tailLen; - List.copyRange(output, offset, _dOutput, sourceStartIndex, + List.copyRange(output, offset, _dOutput!, sourceStartIndex, sourceStartIndex + tailLen); offset += tailLen; length = end; } sourceStart = end - length; final int sourceStartIndex = end - length; - List.copyRange(output, offset, _dOutput, sourceStartIndex, end); + List.copyRange(output, offset, _dOutput!, sourceStartIndex, end); _usedBytes -= copied; return {'count': copied, 'data': output}; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/deflate_stream.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/deflate_stream.dart index 1ceee7cc3..cdf4c9b83 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/deflate_stream.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/deflate_stream.dart @@ -3,7 +3,6 @@ part of pdf; class _DeflateStream { //Constructor _DeflateStream(List data, int offset, bool leaveOpen) { - ArgumentError.checkNotNull(data); _offset = offset; _data = data; _leaveOpen = leaveOpen; @@ -12,24 +11,24 @@ class _DeflateStream { } //Fields - List _data; + late List _data; // ignore: unused_field - bool _leaveOpen; - int _offset; - List _buffer; - _Inflater _inflater; + bool? _leaveOpen; + late int _offset; + List? _buffer; + late _Inflater _inflater; //Implementation - Map _read(List array, int offset, int count) { - int length; + Map _read(List? array, int offset, int count) { + int? length; int cOffset = offset; int rCount = count; while (true) { final Map inflateResult = - _inflater._inflate(array, cOffset, rCount); + _inflater._inflate(array!, cOffset, rCount); length = inflateResult['count']; array = inflateResult['data']; - cOffset += length; + cOffset += length!; rCount -= length; if (rCount == 0) { break; @@ -38,12 +37,12 @@ class _DeflateStream { break; } final Map result = _readBytes(); - final int bytes = result['count']; + final int? bytes = result['count']; _buffer = result['buffer']; if (bytes == 0) { break; } - _inflater._setInput(_buffer, 0, bytes); + _inflater._setInput(_buffer, 0, bytes!); } return {'count': count - rCount, 'data': array}; } @@ -53,8 +52,8 @@ class _DeflateStream { return {'buffer': [], 'count': 0}; } else { int count = 0; - for (int i = 0; i < _buffer.length && i + _offset < _data.length; i++) { - _buffer[i] = _data[_offset + i]; + for (int i = 0; i < _buffer!.length && i + _offset < _data.length; i++) { + _buffer![i] = _data[_offset + i]; count++; } _offset += count; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/huffman_tree.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/huffman_tree.dart index 7132e89bd..c5b6b7577 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/huffman_tree.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/huffman_tree.dart @@ -2,7 +2,7 @@ part of pdf; class _HuffmanTree { // Constructors - _HuffmanTree({List code, bool isLtree}) { + _HuffmanTree({List? code, bool? isLtree}) { if (code == null && isLtree != null) { _clArray = isLtree ? _getLTree() : _getDTree(); } else if (code != null) { @@ -25,12 +25,12 @@ class _HuffmanTree { static const int _nCLength = 19; // Fields - int _tBits; - List _table; - List _left; - List _right; - List _clArray; - int _tMask; + late int _tBits; + late List _table; + List? _left; + List? _right; + late List _clArray; + late int _tMask; //Implementation List _getLTree() { @@ -123,9 +123,9 @@ class _HuffmanTree { throw ArgumentError.value('Invalid Data.'); } if ((start & bitMask) == 0) { - array = _left; + array = _left!; } else { - array = _right; + array = _right!; } index = -value; bitMask <<= 1; @@ -138,19 +138,19 @@ class _HuffmanTree { } int _getNextSymbol(_InBuffer input) { - final int bitBuffer = input._load16Bits(); + final int? bitBuffer = input._load16Bits(); if (input.bits == 0) { return -1; } - int symbol = _table[bitBuffer & _tMask]; + int symbol = _table[bitBuffer! & _tMask]; if (symbol < 0) { int mask = (1 << _tBits).toUnsigned(32); do { symbol = -symbol; if ((bitBuffer & mask) == 0) { - symbol = _left[symbol]; + symbol = _left![symbol]; } else { - symbol = _right[symbol]; + symbol = _right![symbol]; } mask <<= 1; } while (symbol < 0); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/in_buffer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/in_buffer.dart index 30b988d2b..1cac1de4b 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/in_buffer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/in_buffer.dart @@ -10,11 +10,11 @@ class _InBuffer { } //Fields - List _buffer; - int _begin; - int _end; - int _bBuffer; - int _bInBuffer; + List? _buffer; + late int _begin; + late int _end; + late int _bBuffer; + late int _bInBuffer; //Properties int get bytes => (_end - _begin) + (_bInBuffer ~/ 8); @@ -26,32 +26,32 @@ class _InBuffer { if (_needsInput()) { return false; } - _bBuffer |= _buffer[_begin++].toUnsigned(32) << _bInBuffer; + _bBuffer |= _buffer![_begin++].toUnsigned(32) << _bInBuffer; _bInBuffer += 8; if (_bInBuffer < count) { if (_needsInput()) { return false; } - _bBuffer |= _buffer[_begin++].toUnsigned(32) << _bInBuffer; + _bBuffer |= _buffer![_begin++].toUnsigned(32) << _bInBuffer; _bInBuffer += 8; } } return true; } - int _load16Bits() { + int? _load16Bits() { if (_bInBuffer < 8) { if (_begin < _end) { - _bBuffer |= _buffer[_begin++].toUnsigned(32) << _bInBuffer; + _bBuffer |= _buffer![_begin++].toUnsigned(32) << _bInBuffer; _bInBuffer += 8; } if (_begin < _end) { - _bBuffer |= _buffer[_begin++].toUnsigned(32) << _bInBuffer; + _bBuffer |= _buffer![_begin++].toUnsigned(32) << _bInBuffer; _bInBuffer += 8; } } else if (_bInBuffer < 16) { if (_begin < _end) { - _bBuffer |= _buffer[_begin++].toUnsigned(32) << _bInBuffer; + _bBuffer |= _buffer![_begin++].toUnsigned(32) << _bInBuffer; _bInBuffer += 8; } } @@ -72,10 +72,10 @@ class _InBuffer { return result; } - int _copyTo(List output, int offset, int length) { + int _copyTo(List? output, int offset, int length) { int bitBuffer = 0; while (_bInBuffer > 0 && length > 0) { - output[offset++] = _bBuffer.toUnsigned(8); + output![offset++] = _bBuffer.toUnsigned(8); _bBuffer >>= 8; _bInBuffer -= 8; length--; @@ -89,9 +89,11 @@ class _InBuffer { length = avail; } for (int i = 0; - i < length && i + _begin < _buffer.length && i + offset < output.length; + i < length && + i + _begin < _buffer!.length && + i + offset < output!.length; i++) { - output[offset + i] = _buffer[_begin + i]; + output[offset + i] = _buffer![_begin + i]; } _begin += length; return bitBuffer + length; @@ -101,7 +103,7 @@ class _InBuffer { return _begin == _end; } - void _setInput(List buffer, int offset, int length) { + void _setInput(List? buffer, int offset, int length) { _buffer = buffer; _begin = offset; _end = offset + length; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/in_flatter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/in_flatter.dart index b8a4fc492..ce6acbb19 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/in_flatter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/deflate/in_flatter.dart @@ -16,36 +16,38 @@ class _Inflater { _output = _DecompressedOutput(); _input = _InBuffer(); _loopCounter = 0; - _codeList = List(_HuffmanTree._maxLTree + _HuffmanTree._maxDTree); - _cltcl = List(_HuffmanTree._nCLength); + _codeList = List.filled( + _HuffmanTree._maxLTree + _HuffmanTree._maxDTree, 0, + growable: true); + _cltcl = List.filled(_HuffmanTree._nCLength, 0, growable: true); _inflaterstate = _InflaterState.readingBFinal; } //Fields - _DecompressedOutput _output; - _InBuffer _input; - _HuffmanTree _llTree; - _HuffmanTree _distanceTree; - _InflaterState _inflaterstate; - int _bfinal; - _BlockType _blockType; - List _blBuffer; - int _bLength; - int _length; - int _distanceCode; - int _extraBits; - int _loopCounter; - int _llCodeCount; - int _dCodeCount; - int _clCodeCount; - int _caSize; - int _lengthCode; - List _codeList; - List _cltcl; - _HuffmanTree _clTree; + late _DecompressedOutput _output; + late _InBuffer _input; + late _HuffmanTree _llTree; + late _HuffmanTree _distanceTree; + late _InflaterState _inflaterstate; + late int _bfinal; + late _BlockType _blockType; + late List _blBuffer; + late int _bLength; + late int _length; + late int _distanceCode; + late int _extraBits; + late int _loopCounter; + late int _llCodeCount; + late int _dCodeCount; + late int _clCodeCount; + late int _caSize; + late int _lengthCode; + late List _codeList; + late List _cltcl; + late _HuffmanTree _clTree; //Implementation - void _setInput(List inputBytes, int offset, int length) { + void _setInput(List? inputBytes, int offset, int length) { _input._setInput(inputBytes, offset, length); } @@ -68,13 +70,13 @@ class _Inflater { if (length == 0) { break; } - } while (!_finished && _decode()); + } while (!_finished && _decode()!); return {'count': i, 'data': bytes}; } - bool _decode() { - bool eob = false; - bool result = false; + bool? _decode() { + bool? eob = false; + bool? result = false; if (_finished) { return true; } @@ -122,13 +124,13 @@ class _Inflater { eob = returnedValue['eob']; _output = returnedValue['output']; } - if (eob && (_bfinal != 0)) { + if (eob! && (_bfinal != 0)) { _inflaterstate = _InflaterState.done; } return result; } - Map _decodeUncompressedBlock(bool endblock) { + Map _decodeUncompressedBlock(bool? endblock) { endblock = false; while (true) { switch (_inflaterstate) { @@ -205,7 +207,7 @@ class _Inflater { return true; } - Map _decodeBlock(bool endblock) { + Map _decodeBlock(bool? endblock) { endblock = false; int fb = _output.unusedBytes; while (fb > 258) { @@ -575,7 +577,7 @@ class _Inflater { } } - int _getInflaterStateValue(_InflaterState state) { + int _getInflaterStateValue(_InflaterState? state) { switch (state) { case _InflaterState.readingHeader: return 0; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/pdf_png_filter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/pdf_png_filter.dart index e6a32682e..7e5da1283 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/pdf_png_filter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/pdf_png_filter.dart @@ -8,12 +8,11 @@ class _PdfPngFilter { } //Fields - _RowFilter _decompressFilter; - int bytesPerPixel; + _RowFilter? _decompressFilter; + late int bytesPerPixel; //Implementation List _decompress(List data, int bytesPerRow) { - ArgumentError.checkNotNull(data); if (bytesPerRow <= 0) { throw ArgumentError.value(bytesPerRow, 'There cannot be less or equal to zero bytes in a line.'); @@ -21,7 +20,7 @@ class _PdfPngFilter { return _modify(data, bytesPerRow + 1, _decompressFilter, false); } - List _modify(List data, int bpr, _RowFilter filter, bool pack) { + List _modify(List data, int bpr, _RowFilter? filter, bool pack) { int index = 0; final int length = data.length; final int items = length ~/ bpr; @@ -32,7 +31,7 @@ class _PdfPngFilter { int currentRow = 0; while (index + bpr <= length) { - result = filter(data, index, bpr, result, currentRow, outBPR); + result = filter!(data, index, bpr, result, currentRow, outBPR); currentRow += outBPR; index += bpr; } @@ -83,8 +82,7 @@ class _PdfPngFilter { List result, int resIndex, int resBPR) { for (int i = 0; i < resBPR; ++i) { result[resIndex] = - (data[inIndex] + ((i > 0) ? (result[resIndex - 1]) : 0)) - .toUnsigned(8); + (data[inIndex] + ((i > 0) ? result[resIndex - 1] : 0)).toUnsigned(8); ++resIndex; ++inIndex; } @@ -96,7 +94,7 @@ class _PdfPngFilter { int prevIndex = resIndex - resBPR; for (int i = 0; i < resBPR; ++i) { result[resIndex] = - (data[inIndex] + ((prevIndex < 0) ? 0 : (result[prevIndex]))) + (data[inIndex] + ((prevIndex < 0) ? 0 : result[prevIndex])) .toUnsigned(8); ++resIndex; ++inIndex; @@ -124,15 +122,17 @@ class _PdfPngFilter { } for (int i = bytesPerPixel; i < resBPR; i++) { if (prevIndex < 0) { - result[resIndex] += (((result[resIndex - bytesPerPixel] & 0xff) + - (previous[resIndex] & 0xff)) ~/ - 2) - .toUnsigned(8); + result[resIndex] = result[resIndex] + + (((result[resIndex - bytesPerPixel] & 0xff) + + (previous[resIndex] & 0xff)) ~/ + 2) + .toUnsigned(8); } else { - result[resIndex] += (((result[resIndex - bytesPerPixel] & 0xff) + - (result[prevIndex] & 0xff)) ~/ - 2) - .toUnsigned(8); + result[resIndex] = result[resIndex] + + (((result[resIndex - bytesPerPixel] & 0xff) + + (result[prevIndex] & 0xff)) ~/ + 2) + .toUnsigned(8); } ++resIndex; ++inIndex; @@ -148,7 +148,7 @@ class _PdfPngFilter { result[resIndex + i] = data[inIndex + i]; } for (int i = 0; i < bytesPerPixel; i++) { - result[resIndex] += result[prevIndex]; + result[resIndex] = result[resIndex] + result[prevIndex]; resIndex++; prevIndex++; } @@ -156,7 +156,7 @@ class _PdfPngFilter { final int a = result[resIndex - bytesPerPixel] & 0xff; final int b = result[prevIndex] & 0xff; final int c = result[prevIndex - bytesPerPixel] & 0xff; - result[resIndex] += _paethPredictor(a, b, c); + result[resIndex] = result[resIndex] + _paethPredictor(a, b, c); ++resIndex; ++inIndex; ++prevIndex; @@ -178,7 +178,7 @@ class _PdfPngFilter { } } - _Type _getType(int type) { + _Type _getType(int? type) { _Type result; if (type == 0) { result = _Type.none; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/pdf_zlib_compressor.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/pdf_zlib_compressor.dart index 9ff292ec0..d965aacf9 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/pdf_zlib_compressor.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/pdf_zlib_compressor.dart @@ -7,23 +7,22 @@ class _PdfZlibCompressor { } //fields - PdfCompressionLevel level; + PdfCompressionLevel? level; //const int defaultBufferSize = 32; //Implementation List decompress(List data) { - ArgumentError.checkNotNull(data, 'data'); List outputStream = []; List buffer = List.filled(defaultBufferSize, 0, growable: true); final _CompressedStreamReader reader = _CompressedStreamReader(data); - int len = 0; + int? len = 0; try { Map returnValue = reader._read(buffer, 0, buffer.length); len = returnValue['length']; buffer = returnValue['buffer']; - while (len > 0) { + while (len! > 0) { for (int i = 0; i < len; i++) { outputStream.add(buffer[i]); } @@ -35,14 +34,14 @@ class _PdfZlibCompressor { if (e1.message.contains('Checksum check failed.')) { final _DeflateStream deflateStream = _DeflateStream(data, 2, true); buffer = List.filled(4096, 0, growable: true); - int numRead = 0; + int? numRead = 0; outputStream = []; do { final Map result = deflateStream._read(buffer, 0, buffer.length); numRead = result['count']; buffer = result['data']; - for (int i = 0; i < numRead; i++) { + for (int i = 0; i < numRead!; i++) { outputStream.add(buffer[i]); } } while (numRead > 0); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/drawing/color.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/drawing/color.dart index 2368393e6..1035d9f8b 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/drawing/color.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/drawing/color.dart @@ -7,26 +7,26 @@ class _Color { static const int argbGreebShift = 8; static const int argbBlueShift = 0; - int _value; + int? _value; final _KnownColor _knownColor; int get r { - return ((value >> argbRedShift) & 0xFF).toUnsigned(8); + return ((value! >> argbRedShift) & 0xFF).toUnsigned(8); } int get g { - return ((value >> argbGreebShift) & 0xFF).toUnsigned(8); + return ((value! >> argbGreebShift) & 0xFF).toUnsigned(8); } int get b { - return ((value >> argbBlueShift) & 0xFF).toUnsigned(8); + return ((value! >> argbBlueShift) & 0xFF).toUnsigned(8); } int get a { - return ((value >> argbAlphaShift) & 0xFF).toUnsigned(8); + return ((value! >> argbAlphaShift) & 0xFF).toUnsigned(8); } - int get value { + int? get value { _value ??= _KnownColorTable().knowColorToArgb(_knownColor); return _value; } @@ -182,7 +182,7 @@ class _KnownColorTable { colorTable[0xa7] = -6632142; } - int knowColorToArgb(_KnownColor color) { + int? knowColorToArgb(_KnownColor color) { if (getKnownColor(color) <= getKnownColor(_KnownColor.menuHighlight)) { return colorTable[getKnownColor(color)]; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/drawing/drawing.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/drawing/drawing.dart index 1daddf642..d7172c366 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/drawing/drawing.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/drawing/drawing.dart @@ -21,10 +21,10 @@ class _Size { int get hashCode => width.hashCode; /// Gets or sets the width value. - double width; + late double width; /// Gets or sets the height value. - double height; + late double height; /// Gets the empty size. static _Size get empty => _Size(0, 0); @@ -50,10 +50,10 @@ class _Point { int get hashCode => x.hashCode; /// Gets or sets the x value. - double x; + late double x; /// Gets or sets the y value. - double y; + late double y; /// Gets the empty [_Point]. static _Point get empty => _Point(0, 0); @@ -87,16 +87,16 @@ class _Rectangle { int get hashCode => x.hashCode; /// Gets or sets the [x] value. - double x; + late double x; /// Gets or sets the [y] value. - double y; + late double y; /// Gets or sets the [width] value. - double width; + late double width; /// Gets or sets the [height] value. - double height; + late double height; /// Gets the [location]. _Point get location => _Point(x, y); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_file2.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_file2.dart index 562caf999..92310e00e 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_file2.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_file2.dart @@ -7,4216 +7,4216 @@ class _AdobeGlyphList { } // Fields - Map map; + Map? map; // Implementations void initialize() { map = {}; - map['A'] = 'A'; - map['AE'] = 'Æ'; - map['AEacute'] = 'Ǽ'; - map['AEmacron'] = 'Ǣ'; - map['AEsmall'] = ''; - map['Aacute'] = 'Á'; - map['Aacutesmall'] = ''; - map['Abreve'] = 'Ă'; - map['Abreveacute'] = 'Ắ'; - map['Abrevecyrillic'] = 'Ӑ'; - map['Abrevedotbelow'] = 'Ặ'; - map['Abrevegrave'] = 'Ằ'; - map['Abrevehookabove'] = 'Ẳ'; - map['Abrevetilde'] = 'Ẵ'; - map['Acaron'] = 'Ǎ'; - map['Acircle'] = 'Ⓐ'; - map['Acircumflex'] = 'Â'; - map['Acircumflexacute'] = 'Ấ'; - map['Acircumflexdotbelow'] = 'Ậ'; - map['Acircumflexgrave'] = 'Ầ'; - map['Acircumflexhookabove'] = 'Ẩ'; - map['Acircumflexsmall'] = ''; - map['Acircumflextilde'] = 'Ẫ'; - map['Acute'] = ''; - map['Acutesmall'] = ''; - map['Acyrillic'] = 'А'; - map['Adblgrave'] = 'Ȁ'; - map['Adieresis'] = 'Ä'; - map['Adieresiscyrillic'] = 'Ӓ'; - map['Adieresismacron'] = 'Ǟ'; - map['Adieresissmall'] = ''; - map['Adotbelow'] = 'Ạ'; - map['Adotmacron'] = 'Ǡ'; - map['Agrave'] = 'À'; - map['Agravesmall'] = ''; - map['Ahookabove'] = 'Ả'; - map['Aiecyrillic'] = 'Ӕ'; - map['Ainvertedbreve'] = 'Ȃ'; - map['Alpha'] = 'Α'; - map['Alphatonos'] = 'Ά'; - map['Amacron'] = 'Ā'; - map['Amonospace'] = 'A'; - map['Aogonek'] = 'Ą'; - map['Aring'] = 'Å'; - map['Aringacute'] = 'Ǻ'; - map['Aringbelow'] = 'Ḁ'; - map['Aringsmall'] = ''; - map['Asmall'] = ''; - map['Atilde'] = 'Ã'; - map['Atildesmall'] = ''; - map['Aybarmenian'] = 'Ա'; - map['B'] = 'B'; - map['Bcircle'] = 'Ⓑ'; - map['Bdotaccent'] = 'Ḃ'; - map['Bdotbelow'] = 'Ḅ'; - map['Becyrillic'] = 'Б'; - map['Benarmenian'] = 'Բ'; - map['Beta'] = 'Β'; - map['Bhook'] = 'Ɓ'; - map['Blinebelow'] = 'Ḇ'; - map['Bmonospace'] = 'B'; - map['Brevesmall'] = ''; - map['Bsmall'] = ''; - map['Btopbar'] = 'Ƃ'; - map['C'] = 'C'; - map['Caarmenian'] = 'Ծ'; - map['Cacute'] = 'Ć'; - map['Caron'] = ''; - map['Caronsmall'] = ''; - map['Ccaron'] = 'Č'; - map['Ccedilla'] = 'Ç'; - map['Ccedillaacute'] = 'Ḉ'; - map['Ccedillasmall'] = ''; - map['Ccircle'] = 'Ⓒ'; - map['Ccircumflex'] = 'Ĉ'; - map['Cdot'] = 'Ċ'; - map['Cdotaccent'] = 'Ċ'; - map['Cedillasmall'] = ''; - map['Chaarmenian'] = 'Չ'; - map['Cheabkhasiancyrillic'] = 'Ҽ'; - map['Checyrillic'] = 'Ч'; - map['Chedescenderabkhasiancyrillic'] = 'Ҿ'; - map['Chedescendercyrillic'] = 'Ҷ'; - map['Chedieresiscyrillic'] = 'Ӵ'; - map['Cheharmenian'] = 'Ճ'; - map['Chekhakassiancyrillic'] = 'Ӌ'; - map['Cheverticalstrokecyrillic'] = 'Ҹ'; - map['Chi'] = 'Χ'; - map['Chook'] = 'Ƈ'; - map['Circumflexsmall'] = ''; - map['Cmonospace'] = 'C'; - map['Coarmenian'] = 'Ց'; - map['Csmall'] = ''; - map['D'] = 'D'; - map['DZ'] = 'DZ'; - map['DZcaron'] = 'DŽ'; - map['Daarmenian'] = 'Դ'; - map['Dafrican'] = 'Ɖ'; - map['Dcaron'] = 'Ď'; - map['Dcedilla'] = 'Ḑ'; - map['Dcircle'] = 'Ⓓ'; - map['Dcircumflexbelow'] = 'Ḓ'; - map['Dcroat'] = 'Đ'; - map['Ddotaccent'] = 'Ḋ'; - map['Ddotbelow'] = 'Ḍ'; - map['Decyrillic'] = 'Д'; - map['Deicoptic'] = 'Ϯ'; - map['Delta'] = '∆'; - map['Deltagreek'] = 'Δ'; - map['Dhook'] = 'Ɗ'; - map['Dieresis'] = ''; - map['DieresisAcute'] = ''; - map['DieresisGrave'] = ''; - map['Dieresissmall'] = ''; - map['Digammagreek'] = 'Ϝ'; - map['Djecyrillic'] = 'Ђ'; - map['Dlinebelow'] = 'Ḏ'; - map['Dmonospace'] = 'D'; - map['Dotaccentsmall'] = ''; - map['Dslash'] = 'Đ'; - map['Dsmall'] = ''; - map['Dtopbar'] = 'Ƌ'; - map['Dz'] = 'Dz'; - map['Dzcaron'] = 'Dž'; - map['Dzeabkhasiancyrillic'] = 'Ӡ'; - map['Dzecyrillic'] = 'Ѕ'; - map['Dzhecyrillic'] = 'Џ'; - map['E'] = 'E'; - map['Eacute'] = 'É'; - map['Eacutesmall'] = ''; - map['Ebreve'] = 'Ĕ'; - map['Ecaron'] = 'Ě'; - map['Ecedillabreve'] = 'Ḝ'; - map['Echarmenian'] = 'Ե'; - map['Ecircle'] = 'Ⓔ'; - map['Ecircumflex'] = 'Ê'; - map['Ecircumflexacute'] = 'Ế'; - map['Ecircumflexbelow'] = 'Ḙ'; - map['Ecircumflexdotbelow'] = 'Ệ'; - map['Ecircumflexgrave'] = 'Ề'; - map['Ecircumflexhookabove'] = 'Ể'; - map['Ecircumflexsmall'] = ''; - map['Ecircumflextilde'] = 'Ễ'; - map['Ecyrillic'] = 'Є'; - map['Edblgrave'] = 'Ȅ'; - map['Edieresis'] = 'Ë'; - map['Edieresissmall'] = ''; - map['Edot'] = 'Ė'; - map['Edotaccent'] = 'Ė'; - map['Edotbelow'] = 'Ẹ'; - map['Efcyrillic'] = 'Ф'; - map['Egrave'] = 'È'; - map['Egravesmall'] = ''; - map['Eharmenian'] = 'Է'; - map['Ehookabove'] = 'Ẻ'; - map['Eightroman'] = 'Ⅷ'; - map['Einvertedbreve'] = 'Ȇ'; - map['Eiotifiedcyrillic'] = 'Ѥ'; - map['Elcyrillic'] = 'Л'; - map['Elevenroman'] = 'Ⅺ'; - map['Emacron'] = 'Ē'; - map['Emacronacute'] = 'Ḗ'; - map['Emacrongrave'] = 'Ḕ'; - map['Emcyrillic'] = 'М'; - map['Emonospace'] = 'E'; - map['Encyrillic'] = 'Н'; - map['Endescendercyrillic'] = 'Ң'; - map['Eng'] = 'Ŋ'; - map['Enghecyrillic'] = 'Ҥ'; - map['Enhookcyrillic'] = 'Ӈ'; - map['Eogonek'] = 'Ę'; - map['Eopen'] = 'Ɛ'; - map['Epsilon'] = 'Ε'; - map['Epsilontonos'] = 'Έ'; - map['Ercyrillic'] = 'Р'; - map['Ereversed'] = 'Ǝ'; - map['Ereversedcyrillic'] = 'Э'; - map['Escyrillic'] = 'С'; - map['Esdescendercyrillic'] = 'Ҫ'; - map['Esh'] = 'Ʃ'; - map['Esmall'] = ''; - map['Eta'] = 'Η'; - map['Etarmenian'] = 'Ը'; - map['Etatonos'] = 'Ή'; - map['Eth'] = 'Ð'; - map['Ethsmall'] = ''; - map['Etilde'] = 'Ẽ'; - map['Etildebelow'] = 'Ḛ'; - map['Euro'] = '€'; - map['Ezh'] = 'Ʒ'; - map['Ezhcaron'] = 'Ǯ'; - map['Ezhreversed'] = 'Ƹ'; - map['F'] = 'F'; - map['Fcircle'] = 'Ⓕ'; - map['Fdotaccent'] = 'Ḟ'; - map['Feharmenian'] = 'Ֆ'; - map['Feicoptic'] = 'Ϥ'; - map['Fhook'] = 'Ƒ'; - map['Fitacyrillic'] = 'Ѳ'; - map['Fiveroman'] = 'Ⅴ'; - map['Fmonospace'] = 'F'; - map['Fourroman'] = 'Ⅳ'; - map['Fsmall'] = ''; - map['G'] = 'G'; - map['GBsquare'] = '㎇'; - map['Gacute'] = 'Ǵ'; - map['Gamma'] = 'Γ'; - map['Gammaafrican'] = 'Ɣ'; - map['Gangiacoptic'] = 'Ϫ'; - map['Gbreve'] = 'Ğ'; - map['Gcaron'] = 'Ǧ'; - map['Gcedilla'] = 'Ģ'; - map['Gcircle'] = 'Ⓖ'; - map['Gcircumflex'] = 'Ĝ'; - map['Gcommaaccent'] = 'Ģ'; - map['Gdot'] = 'Ġ'; - map['Gdotaccent'] = 'Ġ'; - map['Gecyrillic'] = 'Г'; - map['Ghadarmenian'] = 'Ղ'; - map['Ghemiddlehookcyrillic'] = 'Ҕ'; - map['Ghestrokecyrillic'] = 'Ғ'; - map['Gheupturncyrillic'] = 'Ґ'; - map['Ghook'] = 'Ɠ'; - map['Gimarmenian'] = 'Գ'; - map['Gjecyrillic'] = 'Ѓ'; - map['Gmacron'] = 'Ḡ'; - map['Gmonospace'] = 'G'; - map['Grave'] = ''; - map['Gravesmall'] = ''; - map['Gsmall'] = ''; - map['Gsmallhook'] = 'ʛ'; - map['Gstroke'] = 'Ǥ'; - map['H'] = 'H'; - map['H18533'] = '●'; - map['H18543'] = '▪'; - map['H18551'] = '▫'; - map['H22073'] = '□'; - map['HPsquare'] = '㏋'; - map['Haabkhasiancyrillic'] = 'Ҩ'; - map['Hadescendercyrillic'] = 'Ҳ'; - map['Hardsigncyrillic'] = 'Ъ'; - map['Hbar'] = 'Ħ'; - map['Hbrevebelow'] = 'Ḫ'; - map['Hcedilla'] = 'Ḩ'; - map['Hcircle'] = 'Ⓗ'; - map['Hcircumflex'] = 'Ĥ'; - map['Hdieresis'] = 'Ḧ'; - map['Hdotaccent'] = 'Ḣ'; - map['Hdotbelow'] = 'Ḥ'; - map['Hmonospace'] = 'H'; - map['Hoarmenian'] = 'Հ'; - map['Horicoptic'] = 'Ϩ'; - map['Hsmall'] = ''; - map['Hungarumlaut'] = ''; - map['Hungarumlautsmall'] = ''; - map['Hzsquare'] = '㎐'; - map['I'] = 'I'; - map['IAcyrillic'] = 'Я'; - map['IJ'] = 'IJ'; - map['IUcyrillic'] = 'Ю'; - map['Iacute'] = 'Í'; - map['Iacutesmall'] = ''; - map['Ibreve'] = 'Ĭ'; - map['Icaron'] = 'Ǐ'; - map['Icircle'] = 'Ⓘ'; - map['Icircumflex'] = 'Î'; - map['Icircumflexsmall'] = ''; - map['Icyrillic'] = 'І'; - map['Idblgrave'] = 'Ȉ'; - map['Idieresis'] = 'Ï'; - map['Idieresisacute'] = 'Ḯ'; - map['Idieresiscyrillic'] = 'Ӥ'; - map['Idieresissmall'] = ''; - map['Idot'] = 'İ'; - map['Idotaccent'] = 'İ'; - map['Idotbelow'] = 'Ị'; - map['Iebrevecyrillic'] = 'Ӗ'; - map['Iecyrillic'] = 'Е'; - map['Ifraktur'] = 'ℑ'; - map['Igrave'] = 'Ì'; - map['Igravesmall'] = ''; - map['Ihookabove'] = 'Ỉ'; - map['Iicyrillic'] = 'И'; - map['Iinvertedbreve'] = 'Ȋ'; - map['Iishortcyrillic'] = 'Й'; - map['Imacron'] = 'Ī'; - map['Imacroncyrillic'] = 'Ӣ'; - map['Imonospace'] = 'I'; - map['Iniarmenian'] = 'Ի'; - map['Iocyrillic'] = 'Ё'; - map['Iogonek'] = 'Į'; - map['Iota'] = 'Ι'; - map['Iotaafrican'] = 'Ɩ'; - map['Iotadieresis'] = 'Ϊ'; - map['Iotatonos'] = 'Ί'; - map['Ismall'] = ''; - map['Istroke'] = 'Ɨ'; - map['Itilde'] = 'Ĩ'; - map['Itildebelow'] = 'Ḭ'; - map['Izhitsacyrillic'] = 'Ѵ'; - map['Izhitsadblgravecyrillic'] = 'Ѷ'; - map['J'] = 'J'; - map['Jaarmenian'] = 'Ձ'; - map['Jcircle'] = 'Ⓙ'; - map['Jcircumflex'] = 'Ĵ'; - map['Jecyrillic'] = 'Ј'; - map['Jheharmenian'] = 'Ջ'; - map['Jmonospace'] = 'J'; - map['Jsmall'] = ''; - map['K'] = 'K'; - map['KBsquare'] = '㎅'; - map['KKsquare'] = '㏍'; - map['Kabashkircyrillic'] = 'Ҡ'; - map['Kacute'] = 'Ḱ'; - map['Kacyrillic'] = 'К'; - map['Kadescendercyrillic'] = 'Қ'; - map['Kahookcyrillic'] = 'Ӄ'; - map['Kappa'] = 'Κ'; - map['Kastrokecyrillic'] = 'Ҟ'; - map['Kaverticalstrokecyrillic'] = 'Ҝ'; - map['Kcaron'] = 'Ǩ'; - map['Kcedilla'] = 'Ķ'; - map['Kcircle'] = 'Ⓚ'; - map['Kcommaaccent'] = 'Ķ'; - map['Kdotbelow'] = 'Ḳ'; - map['Keharmenian'] = 'Ք'; - map['Kenarmenian'] = 'Կ'; - map['Khacyrillic'] = 'Х'; - map['Kheicoptic'] = 'Ϧ'; - map['Khook'] = 'Ƙ'; - map['Kjecyrillic'] = 'Ќ'; - map['Klinebelow'] = 'Ḵ'; - map['Kmonospace'] = 'K'; - map['Koppacyrillic'] = 'Ҁ'; - map['Koppagreek'] = 'Ϟ'; - map['Ksicyrillic'] = 'Ѯ'; - map['Ksmall'] = ''; - map['L'] = 'L'; - map['LJ'] = 'LJ'; - map['LL'] = ''; - map['Lacute'] = 'Ĺ'; - map['Lambda'] = 'Λ'; - map['Lcaron'] = 'Ľ'; - map['Lcedilla'] = 'Ļ'; - map['Lcircle'] = 'Ⓛ'; - map['Lcircumflexbelow'] = 'Ḽ'; - map['Lcommaaccent'] = 'Ļ'; - map['Ldot'] = 'Ŀ'; - map['Ldotaccent'] = 'Ŀ'; - map['Ldotbelow'] = 'Ḷ'; - map['Ldotbelowmacron'] = 'Ḹ'; - map['Liwnarmenian'] = 'Լ'; - map['Lj'] = 'Lj'; - map['Ljecyrillic'] = 'Љ'; - map['Llinebelow'] = 'Ḻ'; - map['Lmonospace'] = 'L'; - map['Lslash'] = 'Ł'; - map['Lslashsmall'] = ''; - map['Lsmall'] = ''; - map['M'] = 'M'; - map['MBsquare'] = '㎆'; - map['Macron'] = ''; - map['Macronsmall'] = ''; - map['Macute'] = 'Ḿ'; - map['Mcircle'] = 'Ⓜ'; - map['Mdotaccent'] = 'Ṁ'; - map['Mdotbelow'] = 'Ṃ'; - map['Menarmenian'] = 'Մ'; - map['Mmonospace'] = 'M'; - map['Msmall'] = ''; - map['Mturned'] = 'Ɯ'; - map['Mu'] = 'Μ'; - map['N'] = 'N'; - map['NJ'] = 'NJ'; - map['Nacute'] = 'Ń'; - map['Ncaron'] = 'Ň'; - map['Ncedilla'] = 'Ņ'; - map['Ncircle'] = 'Ⓝ'; - map['Ncircumflexbelow'] = 'Ṋ'; - map['Ncommaaccent'] = 'Ņ'; - map['Ndotaccent'] = 'Ṅ'; - map['Ndotbelow'] = 'Ṇ'; - map['Nhookleft'] = 'Ɲ'; - map['Nineroman'] = 'Ⅸ'; - map['Nj'] = 'Nj'; - map['Njecyrillic'] = 'Њ'; - map['Nlinebelow'] = 'Ṉ'; - map['Nmonospace'] = 'N'; - map['Nowarmenian'] = 'Ն'; - map['Nsmall'] = ''; - map['Ntilde'] = 'Ñ'; - map['Ntildesmall'] = ''; - map['Nu'] = 'Ν'; - map['O'] = 'O'; - map['OE'] = 'Œ'; - map['OEsmall'] = ''; - map['Oacute'] = 'Ó'; - map['Oacutesmall'] = ''; - map['Obarredcyrillic'] = 'Ө'; - map['Obarreddieresiscyrillic'] = 'Ӫ'; - map['Obreve'] = 'Ŏ'; - map['Ocaron'] = 'Ǒ'; - map['Ocenteredtilde'] = 'Ɵ'; - map['Ocircle'] = 'Ⓞ'; - map['Ocircumflex'] = 'Ô'; - map['Ocircumflexacute'] = 'Ố'; - map['Ocircumflexdotbelow'] = 'Ộ'; - map['Ocircumflexgrave'] = 'Ồ'; - map['Ocircumflexhookabove'] = 'Ổ'; - map['Ocircumflexsmall'] = ''; - map['Ocircumflextilde'] = 'Ỗ'; - map['Ocyrillic'] = 'О'; - map['Odblacute'] = 'Ő'; - map['Odblgrave'] = 'Ȍ'; - map['Odieresis'] = 'Ö'; - map['Odieresiscyrillic'] = 'Ӧ'; - map['Odieresissmall'] = ''; - map['Odotbelow'] = 'Ọ'; - map['Ogoneksmall'] = ''; - map['Ograve'] = 'Ò'; - map['Ogravesmall'] = ''; - map['Oharmenian'] = 'Օ'; - map['Ohm'] = 'Ω'; - map['Ohookabove'] = 'Ỏ'; - map['Ohorn'] = 'Ơ'; - map['Ohornacute'] = 'Ớ'; - map['Ohorndotbelow'] = 'Ợ'; - map['Ohorngrave'] = 'Ờ'; - map['Ohornhookabove'] = 'Ở'; - map['Ohorntilde'] = 'Ỡ'; - map['Ohungarumlaut'] = 'Ő'; - map['Oi'] = 'Ƣ'; - map['Oinvertedbreve'] = 'Ȏ'; - map['Omacron'] = 'Ō'; - map['Omacronacute'] = 'Ṓ'; - map['Omacrongrave'] = 'Ṑ'; - map['Omega'] = 'Ω'; - map['Omegacyrillic'] = 'Ѡ'; - map['Omegagreek'] = 'Ω'; - map['Omegaroundcyrillic'] = 'Ѻ'; - map['Omegatitlocyrillic'] = 'Ѽ'; - map['Omegatonos'] = 'Ώ'; - map['Omicron'] = 'Ο'; - map['Omicrontonos'] = 'Ό'; - map['Omonospace'] = 'O'; - map['Oneroman'] = 'Ⅰ'; - map['Oogonek'] = 'Ǫ'; - map['Oogonekmacron'] = 'Ǭ'; - map['Oopen'] = 'Ɔ'; - map['Oslash'] = 'Ø'; - map['Oslashacute'] = 'Ǿ'; - map['Oslashsmall'] = ''; - map['Osmall'] = ''; - map['Ostrokeacute'] = 'Ǿ'; - map['Otcyrillic'] = 'Ѿ'; - map['Otilde'] = 'Õ'; - map['Otildeacute'] = 'Ṍ'; - map['Otildedieresis'] = 'Ṏ'; - map['Otildesmall'] = ''; - map['P'] = 'P'; - map['Pacute'] = 'Ṕ'; - map['Pcircle'] = 'Ⓟ'; - map['Pdotaccent'] = 'Ṗ'; - map['Pecyrillic'] = 'П'; - map['Peharmenian'] = 'Պ'; - map['Pemiddlehookcyrillic'] = 'Ҧ'; - map['Phi'] = 'Φ'; - map['Phook'] = 'Ƥ'; - map['Pi'] = 'Π'; - map['Piwrarmenian'] = 'Փ'; - map['Pmonospace'] = 'P'; - map['Psi'] = 'Ψ'; - map['Psicyrillic'] = 'Ѱ'; - map['Psmall'] = ''; - map['Q'] = 'Q'; - map['Qcircle'] = 'Ⓠ'; - map['Qmonospace'] = 'Q'; - map['Qsmall'] = ''; - map['R'] = 'R'; - map['Raarmenian'] = 'Ռ'; - map['Racute'] = 'Ŕ'; - map['Rcaron'] = 'Ř'; - map['Rcedilla'] = 'Ŗ'; - map['Rcircle'] = 'Ⓡ'; - map['Rcommaaccent'] = 'Ŗ'; - map['Rdblgrave'] = 'Ȑ'; - map['Rdotaccent'] = 'Ṙ'; - map['Rdotbelow'] = 'Ṛ'; - map['Rdotbelowmacron'] = 'Ṝ'; - map['Reharmenian'] = 'Ր'; - map['Rfraktur'] = 'ℜ'; - map['Rho'] = 'Ρ'; - map['Ringsmall'] = ''; - map['Rinvertedbreve'] = 'Ȓ'; - map['Rlinebelow'] = 'Ṟ'; - map['Rmonospace'] = 'R'; - map['Rsmall'] = ''; - map['Rsmallinverted'] = 'ʁ'; - map['Rsmallinvertedsuperior'] = 'ʶ'; - map['S'] = 'S'; - map['SF010000'] = '┌'; - map['SF020000'] = '└'; - map['SF030000'] = '┐'; - map['SF040000'] = '┘'; - map['SF050000'] = '┼'; - map['SF060000'] = '┬'; - map['SF070000'] = '┴'; - map['SF080000'] = '├'; - map['SF090000'] = '┤'; - map['SF100000'] = '─'; - map['SF110000'] = '│'; - map['SF190000'] = '╡'; - map['SF200000'] = '╢'; - map['SF210000'] = '╖'; - map['SF220000'] = '╕'; - map['SF230000'] = '╣'; - map['SF240000'] = '║'; - map['SF250000'] = '╗'; - map['SF260000'] = '╝'; - map['SF270000'] = '╜'; - map['SF280000'] = '╛'; - map['SF360000'] = '╞'; - map['SF370000'] = '╟'; - map['SF380000'] = '╚'; - map['SF390000'] = '╔'; - map['SF400000'] = '╩'; - map['SF410000'] = '╦'; - map['SF420000'] = '╠'; - map['SF430000'] = '═'; - map['SF440000'] = '╬'; - map['SF450000'] = '╧'; - map['SF460000'] = '╨'; - map['SF470000'] = '╤'; - map['SF480000'] = '╥'; - map['SF490000'] = '╙'; - map['SF500000'] = '╘'; - map['SF510000'] = '╒'; - map['SF520000'] = '╓'; - map['SF530000'] = '╫'; - map['SF540000'] = '╪'; - map['Sacute'] = 'Ś'; - map['Sacutedotaccent'] = 'Ṥ'; - map['Sampigreek'] = 'Ϡ'; - map['Scaron'] = 'Š'; - map['Scarondotaccent'] = 'Ṧ'; - map['Scaronsmall'] = ''; - map['Scedilla'] = 'Ş'; - map['Schwa'] = 'Ə'; - map['Schwacyrillic'] = 'Ә'; - map['Schwadieresiscyrillic'] = 'Ӛ'; - map['Scircle'] = 'Ⓢ'; - map['Scircumflex'] = 'Ŝ'; - map['Scommaaccent'] = 'Ș'; - map['Sdotaccent'] = 'Ṡ'; - map['Sdotbelow'] = 'Ṣ'; - map['Sdotbelowdotaccent'] = 'Ṩ'; - map['Seharmenian'] = 'Ս'; - map['Sevenroman'] = 'Ⅶ'; - map['Shaarmenian'] = 'Շ'; - map['Shacyrillic'] = 'Ш'; - map['Shchacyrillic'] = 'Щ'; - map['Sheicoptic'] = 'Ϣ'; - map['Shhacyrillic'] = 'Һ'; - map['Shimacoptic'] = 'Ϭ'; - map['Sigma'] = 'Σ'; - map['Sixroman'] = 'Ⅵ'; - map['Smonospace'] = 'S'; - map['Softsigncyrillic'] = 'Ь'; - map['Ssmall'] = ''; - map['Stigmagreek'] = 'Ϛ'; - map['T'] = 'T'; - map['Tau'] = 'Τ'; - map['Tbar'] = 'Ŧ'; - map['Tcaron'] = 'Ť'; - map['Tcedilla'] = 'Ţ'; - map['Tcircle'] = 'Ⓣ'; - map['Tcircumflexbelow'] = 'Ṱ'; - map['Tcommaaccent'] = 'Ţ'; - map['Tdotaccent'] = 'Ṫ'; - map['Tdotbelow'] = 'Ṭ'; - map['Tecyrillic'] = 'Т'; - map['Tedescendercyrillic'] = 'Ҭ'; - map['Tenroman'] = 'Ⅹ'; - map['Tetsecyrillic'] = 'Ҵ'; - map['Theta'] = 'Θ'; - map['Thook'] = 'Ƭ'; - map['Thorn'] = 'Þ'; - map['Thornsmall'] = ''; - map['Threeroman'] = 'Ⅲ'; - map['Tildesmall'] = ''; - map['Tiwnarmenian'] = 'Տ'; - map['Tlinebelow'] = 'Ṯ'; - map['Tmonospace'] = 'T'; - map['Toarmenian'] = 'Թ'; - map['Tonefive'] = 'Ƽ'; - map['Tonesix'] = 'Ƅ'; - map['Tonetwo'] = 'Ƨ'; - map['Tretroflexhook'] = 'Ʈ'; - map['Tsecyrillic'] = 'Ц'; - map['Tshecyrillic'] = 'Ћ'; - map['Tsmall'] = ''; - map['Twelveroman'] = 'Ⅻ'; - map['Tworoman'] = 'Ⅱ'; - map['U'] = 'U'; - map['Uacute'] = 'Ú'; - map['Uacutesmall'] = ''; - map['Ubreve'] = 'Ŭ'; - map['Ucaron'] = 'Ǔ'; - map['Ucircle'] = 'Ⓤ'; - map['Ucircumflex'] = 'Û'; - map['Ucircumflexbelow'] = 'Ṷ'; - map['Ucircumflexsmall'] = ''; - map['Ucyrillic'] = 'У'; - map['Udblacute'] = 'Ű'; - map['Udblgrave'] = 'Ȕ'; - map['Udieresis'] = 'Ü'; - map['Udieresisacute'] = 'Ǘ'; - map['Udieresisbelow'] = 'Ṳ'; - map['Udieresiscaron'] = 'Ǚ'; - map['Udieresiscyrillic'] = 'Ӱ'; - map['Udieresisgrave'] = 'Ǜ'; - map['Udieresismacron'] = 'Ǖ'; - map['Udieresissmall'] = ''; - map['Udotbelow'] = 'Ụ'; - map['Ugrave'] = 'Ù'; - map['Ugravesmall'] = ''; - map['Uhookabove'] = 'Ủ'; - map['Uhorn'] = 'Ư'; - map['Uhornacute'] = 'Ứ'; - map['Uhorndotbelow'] = 'Ự'; - map['Uhorngrave'] = 'Ừ'; - map['Uhornhookabove'] = 'Ử'; - map['Uhorntilde'] = 'Ữ'; - map['Uhungarumlaut'] = 'Ű'; - map['Uhungarumlautcyrillic'] = 'Ӳ'; - map['Uinvertedbreve'] = 'Ȗ'; - map['Ukcyrillic'] = 'Ѹ'; - map['Umacron'] = 'Ū'; - map['Umacroncyrillic'] = 'Ӯ'; - map['Umacrondieresis'] = 'Ṻ'; - map['Umonospace'] = 'U'; - map['Uogonek'] = 'Ų'; - map['Upsilon'] = 'Υ'; - map['Upsilon1'] = 'ϒ'; - map['Upsilonacutehooksymbolgreek'] = 'ϓ'; - map['Upsilonafrican'] = 'Ʊ'; - map['Upsilondieresis'] = 'Ϋ'; - map['Upsilondieresishooksymbolgreek'] = 'ϔ'; - map['Upsilonhooksymbol'] = 'ϒ'; - map['Upsilontonos'] = 'Ύ'; - map['Uring'] = 'Ů'; - map['Ushortcyrillic'] = 'Ў'; - map['Usmall'] = ''; - map['Ustraightcyrillic'] = 'Ү'; - map['Ustraightstrokecyrillic'] = 'Ұ'; - map['Utilde'] = 'Ũ'; - map['Utildeacute'] = 'Ṹ'; - map['Utildebelow'] = 'Ṵ'; - map['V'] = 'V'; - map['Vcircle'] = 'Ⓥ'; - map['Vdotbelow'] = 'Ṿ'; - map['Vecyrillic'] = 'В'; - map['Vewarmenian'] = 'Վ'; - map['Vhook'] = 'Ʋ'; - map['Vmonospace'] = 'V'; - map['Voarmenian'] = 'Ո'; - map['Vsmall'] = ''; - map['Vtilde'] = 'Ṽ'; - map['W'] = 'W'; - map['Wacute'] = 'Ẃ'; - map['Wcircle'] = 'Ⓦ'; - map['Wcircumflex'] = 'Ŵ'; - map['Wdieresis'] = 'Ẅ'; - map['Wdotaccent'] = 'Ẇ'; - map['Wdotbelow'] = 'Ẉ'; - map['Wgrave'] = 'Ẁ'; - map['Wmonospace'] = 'W'; - map['Wsmall'] = ''; - map['X'] = 'X'; - map['Xcircle'] = 'Ⓧ'; - map['Xdieresis'] = 'Ẍ'; - map['Xdotaccent'] = 'Ẋ'; - map['Xeharmenian'] = 'Խ'; - map['Xi'] = 'Ξ'; - map['Xmonospace'] = 'X'; - map['Xsmall'] = ''; - map['Y'] = 'Y'; - map['Yacute'] = 'Ý'; - map['Yacutesmall'] = ''; - map['Yatcyrillic'] = 'Ѣ'; - map['Ycircle'] = 'Ⓨ'; - map['Ycircumflex'] = 'Ŷ'; - map['Ydieresis'] = 'Ÿ'; - map['Ydieresissmall'] = ''; - map['Ydotaccent'] = 'Ẏ'; - map['Ydotbelow'] = 'Ỵ'; - map['Yericyrillic'] = 'Ы'; - map['Yerudieresiscyrillic'] = 'Ӹ'; - map['Ygrave'] = 'Ỳ'; - map['Yhook'] = 'Ƴ'; - map['Yhookabove'] = 'Ỷ'; - map['Yiarmenian'] = 'Յ'; - map['Yicyrillic'] = 'Ї'; - map['Yiwnarmenian'] = 'Ւ'; - map['Ymonospace'] = 'Y'; - map['Ysmall'] = ''; - map['Ytilde'] = 'Ỹ'; - map['Yusbigcyrillic'] = 'Ѫ'; - map['Yusbigiotifiedcyrillic'] = 'Ѭ'; - map['Yuslittlecyrillic'] = 'Ѧ'; - map['Yuslittleiotifiedcyrillic'] = 'Ѩ'; - map['Z'] = 'Z'; - map['Zaarmenian'] = 'Զ'; - map['Zacute'] = 'Ź'; - map['Zcaron'] = 'Ž'; - map['Zcaronsmall'] = ''; - map['Zcircle'] = 'Ⓩ'; - map['Zcircumflex'] = 'Ẑ'; - map['Zdot'] = 'Ż'; - map['Zdotaccent'] = 'Ż'; - map['Zdotbelow'] = 'Ẓ'; - map['Zecyrillic'] = 'З'; - map['Zedescendercyrillic'] = 'Ҙ'; - map['Zedieresiscyrillic'] = 'Ӟ'; - map['Zeta'] = 'Ζ'; - map['Zhearmenian'] = 'Ժ'; - map['Zhebrevecyrillic'] = 'Ӂ'; - map['Zhecyrillic'] = 'Ж'; - map['Zhedescendercyrillic'] = 'Җ'; - map['Zhedieresiscyrillic'] = 'Ӝ'; - map['Zlinebelow'] = 'Ẕ'; - map['Zmonospace'] = 'Z'; - map['Zsmall'] = ''; - map['Zstroke'] = 'Ƶ'; - map['a'] = 'a'; - map['aabengali'] = 'আ'; - map['aacute'] = 'á'; - map['aadeva'] = 'आ'; - map['aagujarati'] = 'આ'; - map['aagurmukhi'] = 'ਆ'; - map['aamatragurmukhi'] = 'ਾ'; - map['aarusquare'] = '㌃'; - map['aavowelsignbengali'] = 'া'; - map['aavowelsigndeva'] = 'ा'; - map['aavowelsigngujarati'] = 'ા'; - map['abbreviationmarkarmenian'] = '՟'; - map['abbreviationsigndeva'] = '॰'; - map['abengali'] = 'অ'; - map['abopomofo'] = 'ㄚ'; - map['abreve'] = 'ă'; - map['abreveacute'] = 'ắ'; - map['abrevecyrillic'] = 'ӑ'; - map['abrevedotbelow'] = 'ặ'; - map['abrevegrave'] = 'ằ'; - map['abrevehookabove'] = 'ẳ'; - map['abrevetilde'] = 'ẵ'; - map['acaron'] = 'ǎ'; - map['acircle'] = 'ⓐ'; - map['acircumflex'] = 'â'; - map['acircumflexacute'] = 'ấ'; - map['acircumflexdotbelow'] = 'ậ'; - map['acircumflexgrave'] = 'ầ'; - map['acircumflexhookabove'] = 'ẩ'; - map['acircumflextilde'] = 'ẫ'; - map['acute'] = '´'; - map['acutebelowcmb'] = '̗'; - map['acutecmb'] = '́'; - map['acutecomb'] = '́'; - map['acutedeva'] = '॔'; - map['acutelowmod'] = 'ˏ'; - map['acutetonecmb'] = '́'; - map['acyrillic'] = 'а'; - map['adblgrave'] = 'ȁ'; - map['addakgurmukhi'] = 'ੱ'; - map['adeva'] = 'अ'; - map['adieresis'] = 'ä'; - map['adieresiscyrillic'] = 'ӓ'; - map['adieresismacron'] = 'ǟ'; - map['adotbelow'] = 'ạ'; - map['adotmacron'] = 'ǡ'; - map['ae'] = 'æ'; - map['aeacute'] = 'ǽ'; - map['aekorean'] = 'ㅐ'; - map['aemacron'] = 'ǣ'; - map['afii00208'] = '―'; - map['afii08941'] = '₤'; - map['afii10017'] = 'А'; - map['afii10018'] = 'Б'; - map['afii10019'] = 'В'; - map['afii10020'] = 'Г'; - map['afii10021'] = 'Д'; - map['afii10022'] = 'Е'; - map['afii10023'] = 'Ё'; - map['afii10024'] = 'Ж'; - map['afii10025'] = 'З'; - map['afii10026'] = 'И'; - map['afii10027'] = 'Й'; - map['afii10028'] = 'К'; - map['afii10029'] = 'Л'; - map['afii10030'] = 'М'; - map['afii10031'] = 'Н'; - map['afii10032'] = 'О'; - map['afii10033'] = 'П'; - map['afii10034'] = 'Р'; - map['afii10035'] = 'С'; - map['afii10036'] = 'Т'; - map['afii10037'] = 'У'; - map['afii10038'] = 'Ф'; - map['afii10039'] = 'Х'; - map['afii10040'] = 'Ц'; - map['afii10041'] = 'Ч'; - map['afii10042'] = 'Ш'; - map['afii10043'] = 'Щ'; - map['afii10044'] = 'Ъ'; - map['afii10045'] = 'Ы'; - map['afii10046'] = 'Ь'; - map['afii10047'] = 'Э'; - map['afii10048'] = 'Ю'; - map['afii10049'] = 'Я'; - map['afii10050'] = 'Ґ'; - map['afii10051'] = 'Ђ'; - map['afii10052'] = 'Ѓ'; - map['afii10053'] = 'Є'; - map['afii10054'] = 'Ѕ'; - map['afii10055'] = 'І'; - map['afii10056'] = 'Ї'; - map['afii10057'] = 'Ј'; - map['afii10058'] = 'Љ'; - map['afii10059'] = 'Њ'; - map['afii10060'] = 'Ћ'; - map['afii10061'] = 'Ќ'; - map['afii10062'] = 'Ў'; - map['afii10063'] = ''; - map['afii10064'] = ''; - map['afii10065'] = 'а'; - map['afii10066'] = 'б'; - map['afii10067'] = 'в'; - map['afii10068'] = 'г'; - map['afii10069'] = 'д'; - map['afii10070'] = 'е'; - map['afii10071'] = 'ё'; - map['afii10072'] = 'ж'; - map['afii10073'] = 'з'; - map['afii10074'] = 'и'; - map['afii10075'] = 'й'; - map['afii10076'] = 'к'; - map['afii10077'] = 'л'; - map['afii10078'] = 'м'; - map['afii10079'] = 'н'; - map['afii10080'] = 'о'; - map['afii10081'] = 'п'; - map['afii10082'] = 'р'; - map['afii10083'] = 'с'; - map['afii10084'] = 'т'; - map['afii10085'] = 'у'; - map['afii10086'] = 'ф'; - map['afii10087'] = 'х'; - map['afii10088'] = 'ц'; - map['afii10089'] = 'ч'; - map['afii10090'] = 'ш'; - map['afii10091'] = 'щ'; - map['afii10092'] = 'ъ'; - map['afii10093'] = 'ы'; - map['afii10094'] = 'ь'; - map['afii10095'] = 'э'; - map['afii10096'] = 'ю'; - map['afii10097'] = 'я'; - map['afii10098'] = 'ґ'; - map['afii10099'] = 'ђ'; - map['afii10100'] = 'ѓ'; - map['afii10101'] = 'є'; - map['afii10102'] = 'ѕ'; - map['afii10103'] = 'і'; - map['afii10104'] = 'ї'; - map['afii10105'] = 'ј'; - map['afii10106'] = 'љ'; - map['afii10107'] = 'њ'; - map['afii10108'] = 'ћ'; - map['afii10109'] = 'ќ'; - map['afii10110'] = 'ў'; - map['afii10145'] = 'Џ'; - map['afii10146'] = 'Ѣ'; - map['afii10147'] = 'Ѳ'; - map['afii10148'] = 'Ѵ'; - map['afii10192'] = ''; - map['afii10193'] = 'џ'; - map['afii10194'] = 'ѣ'; - map['afii10195'] = 'ѳ'; - map['afii10196'] = 'ѵ'; - map['afii10831'] = ''; - map['afii10832'] = ''; - map['afii10846'] = 'ә'; - map['afii299'] = '‎'; - map['afii300'] = '‏'; - map['afii301'] = '‍'; - map['afii57381'] = '٪'; - map['afii57388'] = '،'; - map['afii57392'] = '٠'; - map['afii57393'] = '١'; - map['afii57394'] = '٢'; - map['afii57395'] = '٣'; - map['afii57396'] = '٤'; - map['afii57397'] = '٥'; - map['afii57398'] = '٦'; - map['afii57399'] = '٧'; - map['afii57400'] = '٨'; - map['afii57401'] = '٩'; - map['afii57403'] = '؛'; - map['afii57407'] = '؟'; - map['afii57409'] = 'ء'; - map['afii57410'] = 'آ'; - map['afii57411'] = 'أ'; - map['afii57412'] = 'ؤ'; - map['afii57413'] = 'إ'; - map['afii57414'] = 'ئ'; - map['afii57415'] = 'ا'; - map['afii57416'] = 'ب'; - map['afii57417'] = 'ة'; - map['afii57418'] = 'ت'; - map['afii57419'] = 'ث'; - map['afii57420'] = 'ج'; - map['afii57421'] = 'ح'; - map['afii57422'] = 'خ'; - map['afii57423'] = 'د'; - map['afii57424'] = 'ذ'; - map['afii57425'] = 'ر'; - map['afii57426'] = 'ز'; - map['afii57427'] = 'س'; - map['afii57428'] = 'ش'; - map['afii57429'] = 'ص'; - map['afii57430'] = 'ض'; - map['afii57431'] = 'ط'; - map['afii57432'] = 'ظ'; - map['afii57433'] = 'ع'; - map['afii57434'] = 'غ'; - map['afii57440'] = 'ـ'; - map['afii57441'] = 'ف'; - map['afii57442'] = 'ق'; - map['afii57443'] = 'ك'; - map['afii57444'] = 'ل'; - map['afii57445'] = 'م'; - map['afii57446'] = 'ن'; - map['afii57448'] = 'و'; - map['afii57449'] = 'ى'; - map['afii57450'] = 'ي'; - map['afii57451'] = 'ً'; - map['afii57452'] = 'ٌ'; - map['afii57453'] = 'ٍ'; - map['afii57454'] = 'َ'; - map['afii57455'] = 'ُ'; - map['afii57456'] = 'ِ'; - map['afii57457'] = 'ّ'; - map['afii57458'] = 'ْ'; - map['afii57470'] = 'ه'; - map['afii57505'] = 'ڤ'; - map['afii57506'] = 'پ'; - map['afii57507'] = 'چ'; - map['afii57508'] = 'ژ'; - map['afii57509'] = 'گ'; - map['afii57511'] = 'ٹ'; - map['afii57512'] = 'ڈ'; - map['afii57513'] = 'ڑ'; - map['afii57514'] = 'ں'; - map['afii57519'] = 'ے'; - map['afii57534'] = 'ە'; - map['afii57636'] = '₪'; - map['afii57645'] = '־'; - map['afii57658'] = '׃'; - map['afii57664'] = 'א'; - map['afii57665'] = 'ב'; - map['afii57666'] = 'ג'; - map['afii57667'] = 'ד'; - map['afii57668'] = 'ה'; - map['afii57669'] = 'ו'; - map['afii57670'] = 'ז'; - map['afii57671'] = 'ח'; - map['afii57672'] = 'ט'; - map['afii57673'] = 'י'; - map['afii57674'] = 'ך'; - map['afii57675'] = 'כ'; - map['afii57676'] = 'ל'; - map['afii57677'] = 'ם'; - map['afii57678'] = 'מ'; - map['afii57679'] = 'ן'; - map['afii57680'] = 'נ'; - map['afii57681'] = 'ס'; - map['afii57682'] = 'ע'; - map['afii57683'] = 'ף'; - map['afii57684'] = 'פ'; - map['afii57685'] = 'ץ'; - map['afii57686'] = 'צ'; - map['afii57687'] = 'ק'; - map['afii57688'] = 'ר'; - map['afii57689'] = 'ש'; - map['afii57690'] = 'ת'; - map['afii57694'] = 'שׁ'; - map['afii57695'] = 'שׂ'; - map['afii57700'] = 'וֹ'; - map['afii57705'] = 'ײַ'; - map['afii57716'] = 'װ'; - map['afii57717'] = 'ױ'; - map['afii57718'] = 'ײ'; - map['afii57723'] = 'וּ'; - map['afii57793'] = 'ִ'; - map['afii57794'] = 'ֵ'; - map['afii57795'] = 'ֶ'; - map['afii57796'] = 'ֻ'; - map['afii57797'] = 'ָ'; - map['afii57798'] = 'ַ'; - map['afii57799'] = 'ְ'; - map['afii57800'] = 'ֲ'; - map['afii57801'] = 'ֱ'; - map['afii57802'] = 'ֳ'; - map['afii57803'] = 'ׂ'; - map['afii57804'] = 'ׁ'; - map['afii57806'] = 'ֹ'; - map['afii57807'] = 'ּ'; - map['afii57839'] = 'ֽ'; - map['afii57841'] = 'ֿ'; - map['afii57842'] = '׀'; - map['afii57929'] = 'ʼ'; - map['afii61248'] = '℅'; - map['afii61289'] = 'ℓ'; - map['afii61352'] = '№'; - map['afii61573'] = '‬'; - map['afii61574'] = '‭'; - map['afii61575'] = '‮'; - map['afii61664'] = '‌'; - map['afii63167'] = '٭'; - map['afii64937'] = 'ʽ'; - map['agrave'] = 'à'; - map['agujarati'] = 'અ'; - map['agurmukhi'] = 'ਅ'; - map['ahiragana'] = 'あ'; - map['ahookabove'] = 'ả'; - map['aibengali'] = 'ঐ'; - map['aibopomofo'] = 'ㄞ'; - map['aideva'] = 'ऐ'; - map['aiecyrillic'] = 'ӕ'; - map['aigujarati'] = 'ઐ'; - map['aigurmukhi'] = 'ਐ'; - map['aimatragurmukhi'] = 'ੈ'; - map['ainarabic'] = 'ع'; - map['ainfinalarabic'] = 'ﻊ'; - map['aininitialarabic'] = 'ﻋ'; - map['ainmedialarabic'] = 'ﻌ'; - map['ainvertedbreve'] = 'ȃ'; - map['aivowelsignbengali'] = 'ৈ'; - map['aivowelsigndeva'] = 'ै'; - map['aivowelsigngujarati'] = 'ૈ'; - map['akatakana'] = 'ア'; - map['akatakanahalfwidth'] = 'ア'; - map['akorean'] = 'ㅏ'; - map['alef'] = 'א'; - map['alefarabic'] = 'ا'; - map['alefdageshhebrew'] = 'אּ'; - map['aleffinalarabic'] = 'ﺎ'; - map['alefhamzaabovearabic'] = 'أ'; - map['alefhamzaabovefinalarabic'] = 'ﺄ'; - map['alefhamzabelowarabic'] = 'إ'; - map['alefhamzabelowfinalarabic'] = 'ﺈ'; - map['alefhebrew'] = 'א'; - map['aleflamedhebrew'] = 'ﭏ'; - map['alefmaddaabovearabic'] = 'آ'; - map['alefmaddaabovefinalarabic'] = 'ﺂ'; - map['alefmaksuraarabic'] = 'ى'; - map['alefmaksurafinalarabic'] = 'ﻰ'; - map['alefmaksurainitialarabic'] = 'ﻳ'; - map['alefmaksuramedialarabic'] = 'ﻴ'; - map['alefpatahhebrew'] = 'אַ'; - map['alefqamatshebrew'] = 'אָ'; - map['aleph'] = 'ℵ'; - map['allequal'] = '≌'; - map['alpha'] = 'α'; - map['alphatonos'] = 'ά'; - map['amacron'] = 'ā'; - map['amonospace'] = 'a'; - map['ampersand'] = '&'; - map['ampersandmonospace'] = '&'; - map['ampersandsmall'] = ''; - map['amsquare'] = '㏂'; - map['anbopomofo'] = 'ㄢ'; - map['angbopomofo'] = 'ㄤ'; - map['angkhankhuthai'] = '๚'; - map['angle'] = '∠'; - map['angbracketleft'] = '〈'; - map['anglebracketleft'] = '〈'; - map['anglebracketleftvertical'] = '︿'; - map['angbracketright'] = '〉'; - map['anglebracketright'] = '〉'; - map['anglebracketrightvertical'] = '﹀'; - map['angleleft'] = '〈'; - map['angleright'] = '〉'; - map['angstrom'] = 'Å'; - map['anoteleia'] = '·'; - map['anudattadeva'] = '॒'; - map['anusvarabengali'] = 'ং'; - map['anusvaradeva'] = 'ं'; - map['anusvaragujarati'] = 'ં'; - map['aogonek'] = 'ą'; - map['apaatosquare'] = '㌀'; - map['aparen'] = '⒜'; - map['apostrophearmenian'] = '՚'; - map['apostrophemod'] = 'ʼ'; - map['apple'] = ''; - map['approaches'] = '≐'; - map['approxequal'] = '≈'; - map['approxequalorimage'] = '≒'; - map['approximatelyequal'] = '≅'; - map['araeaekorean'] = 'ㆎ'; - map['araeakorean'] = 'ㆍ'; - map['arc'] = '⌒'; - map['arighthalfring'] = 'ẚ'; - map['aring'] = 'å'; - map['aringacute'] = 'ǻ'; - map['aringbelow'] = 'ḁ'; - map['arrowboth'] = '↔'; - map['arrowdashdown'] = '⇣'; - map['arrowdashleft'] = '⇠'; - map['arrowdashright'] = '⇢'; - map['arrowdashup'] = '⇡'; - map['arrowdblboth'] = '⇔'; - map['arrowdbldown'] = '⇓'; - map['arrowdblleft'] = '⇐'; - map['arrowdblright'] = '⇒'; - map['arrowdblup'] = '⇑'; - map['arrowdown'] = '↓'; - map['arrowdownleft'] = '↙'; - map['arrowdownright'] = '↘'; - map['arrowdownwhite'] = '⇩'; - map['arrowheaddownmod'] = '˅'; - map['arrowheadleftmod'] = '˂'; - map['arrowheadrightmod'] = '˃'; - map['arrowheadupmod'] = '˄'; - map['arrowhorizex'] = ''; - map['arrowleft'] = '←'; - map['arrowleftdbl'] = '⇐'; - map['arrowleftdblstroke'] = '⇍'; - map['arrowleftoverright'] = '⇆'; - map['arrowleftwhite'] = '⇦'; - map['arrowright'] = '→'; - map['arrowrightdblstroke'] = '⇏'; - map['arrowrightheavy'] = '➞'; - map['arrowrightoverleft'] = '⇄'; - map['arrowrightwhite'] = '⇨'; - map['arrowtableft'] = '⇤'; - map['arrowtabright'] = '⇥'; - map['arrowup'] = '↑'; - map['arrowupdn'] = '↕'; - map['arrowupdnbse'] = '↨'; - map['arrowupdownbase'] = '↨'; - map['arrowupleft'] = '↖'; - map['arrowupleftofdown'] = '⇅'; - map['arrowupright'] = '↗'; - map['arrowupwhite'] = '⇧'; - map['arrowvertex'] = ''; - map['asciicircum'] = '^'; - map['asciicircummonospace'] = '^'; - map['asciitilde'] = '~'; - map['asciitildemonospace'] = '~'; - map['ascript'] = 'ɑ'; - map['ascriptturned'] = 'ɒ'; - map['asmallhiragana'] = 'ぁ'; - map['asmallkatakana'] = 'ァ'; - map['asmallkatakanahalfwidth'] = 'ァ'; - map['asterisk'] = '*'; - map['asteriskaltonearabic'] = '٭'; - map['asteriskarabic'] = '٭'; - map['asteriskmath'] = '∗'; - map['asteriskmonospace'] = '*'; - map['asterisksmall'] = '﹡'; - map['asterism'] = '⁂'; - map['asuperior'] = ''; - map['asymptoticallyequal'] = '≃'; - map['at'] = '@'; - map['atilde'] = 'ã'; - map['atmonospace'] = '@'; - map['atsmall'] = '﹫'; - map['aturned'] = 'ɐ'; - map['aubengali'] = 'ঔ'; - map['aubopomofo'] = 'ㄠ'; - map['audeva'] = 'औ'; - map['augujarati'] = 'ઔ'; - map['augurmukhi'] = 'ਔ'; - map['aulengthmarkbengali'] = 'ৗ'; - map['aumatragurmukhi'] = 'ੌ'; - map['auvowelsignbengali'] = 'ৌ'; - map['auvowelsigndeva'] = 'ौ'; - map['auvowelsigngujarati'] = 'ૌ'; - map['avagrahadeva'] = 'ऽ'; - map['aybarmenian'] = 'ա'; - map['ayin'] = 'ע'; - map['ayinaltonehebrew'] = 'ﬠ'; - map['ayinhebrew'] = 'ע'; - map['b'] = 'b'; - map['babengali'] = 'ব'; - map['backslash'] = '\\'; - map['backslashmonospace'] = '\'; - map['badeva'] = 'ब'; - map['bagujarati'] = 'બ'; - map['bagurmukhi'] = 'ਬ'; - map['bahiragana'] = 'ば'; - map['bahtthai'] = '฿'; - map['bakatakana'] = 'バ'; - map['bar'] = '|'; - map['barmonospace'] = '|'; - map['bbopomofo'] = 'ㄅ'; - map['bcircle'] = 'ⓑ'; - map['bdotaccent'] = 'ḃ'; - map['bdotbelow'] = 'ḅ'; - map['beamedsixteenthnotes'] = '♬'; - map['because'] = '∵'; - map['becyrillic'] = 'б'; - map['beharabic'] = 'ب'; - map['behfinalarabic'] = 'ﺐ'; - map['behinitialarabic'] = 'ﺑ'; - map['behiragana'] = 'べ'; - map['behmedialarabic'] = 'ﺒ'; - map['behmeeminitialarabic'] = 'ﲟ'; - map['behmeemisolatedarabic'] = 'ﰈ'; - map['behnoonfinalarabic'] = 'ﱭ'; - map['bekatakana'] = 'ベ'; - map['benarmenian'] = 'բ'; - map['bet'] = 'ב'; - map['beta'] = 'β'; - map['betasymbolgreek'] = 'ϐ'; - map['betdagesh'] = 'בּ'; - map['betdageshhebrew'] = 'בּ'; - map['bethebrew'] = 'ב'; - map['betrafehebrew'] = 'בֿ'; - map['bhabengali'] = 'ভ'; - map['bhadeva'] = 'भ'; - map['bhagujarati'] = 'ભ'; - map['bhagurmukhi'] = 'ਭ'; - map['bhook'] = 'ɓ'; - map['bihiragana'] = 'び'; - map['bikatakana'] = 'ビ'; - map['bilabialclick'] = 'ʘ'; - map['bindigurmukhi'] = 'ਂ'; - map['birusquare'] = '㌱'; - map['blackcircle'] = '●'; - map['blackdiamond'] = '◆'; - map['blackdownpointingtriangle'] = '▼'; - map['blackleftpointingpointer'] = '◄'; - map['blackleftpointingtriangle'] = '◀'; - map['blacklenticularbracketleft'] = '【'; - map['blacklenticularbracketleftvertical'] = '︻'; - map['blacklenticularbracketright'] = '】'; - map['blacklenticularbracketrightvertical'] = '︼'; - map['blacklowerlefttriangle'] = '◣'; - map['blacklowerrighttriangle'] = '◢'; - map['blackrectangle'] = '▬'; - map['blackrightpointingpointer'] = '►'; - map['blackrightpointingtriangle'] = '▶'; - map['blacksmallsquare'] = '▪'; - map['blacksmilingface'] = '☻'; - map['blacksquare'] = '■'; - map['blackstar'] = '★'; - map['blackupperlefttriangle'] = '◤'; - map['blackupperrighttriangle'] = '◥'; - map['blackuppointingsmalltriangle'] = '▴'; - map['blackuppointingtriangle'] = '▲'; - map['blank'] = '␣'; - map['blinebelow'] = 'ḇ'; - map['block'] = '█'; - map['bmonospace'] = 'b'; - map['bobaimaithai'] = 'บ'; - map['bohiragana'] = 'ぼ'; - map['bokatakana'] = 'ボ'; - map['bparen'] = '⒝'; - map['bqsquare'] = '㏃'; - map['braceex'] = ''; - map['braceleft'] = '{'; - map['braceleftbt'] = ''; - map['braceleftmid'] = ''; - map['braceleftmonospace'] = '{'; - map['braceleftsmall'] = '﹛'; - map['bracelefttp'] = ''; - map['braceleftvertical'] = '︷'; - map['braceright'] = '}'; - map['bracerightbt'] = ''; - map['bracerightmid'] = ''; - map['bracerightmonospace'] = '}'; - map['bracerightsmall'] = '﹜'; - map['bracerighttp'] = ''; - map['bracerightvertical'] = '︸'; - map['bracketleft'] = '['; - map['bracketleftbt'] = ''; - map['bracketleftex'] = ''; - map['bracketleftmonospace'] = '['; - map['bracketlefttp'] = ''; - map['bracketright'] = ']'; - map['bracketrightbt'] = ''; - map['bracketrightex'] = ''; - map['bracketrightmonospace'] = ']'; - map['bracketrighttp'] = ''; - map['breve'] = '˘'; - map['brevebelowcmb'] = '̮'; - map['brevecmb'] = '̆'; - map['breveinvertedbelowcmb'] = '̯'; - map['breveinvertedcmb'] = '̑'; - map['breveinverteddoublecmb'] = '͡'; - map['bridgebelowcmb'] = '̪'; - map['bridgeinvertedbelowcmb'] = '̺'; - map['brokenbar'] = '¦'; - map['bstroke'] = 'ƀ'; - map['bsuperior'] = ''; - map['btopbar'] = 'ƃ'; - map['buhiragana'] = 'ぶ'; - map['bukatakana'] = 'ブ'; - map['bullet'] = '•'; - map['bulletinverse'] = '◘'; - map['bulletoperator'] = '∙'; - map['bullseye'] = '◎'; - map['c'] = 'c'; - map['caarmenian'] = 'ծ'; - map['cabengali'] = 'চ'; - map['cacute'] = 'ć'; - map['cadeva'] = 'च'; - map['cagujarati'] = 'ચ'; - map['cagurmukhi'] = 'ਚ'; - map['calsquare'] = '㎈'; - map['candrabindubengali'] = 'ঁ'; - map['candrabinducmb'] = '̐'; - map['candrabindudeva'] = 'ँ'; - map['candrabindugujarati'] = 'ઁ'; - map['capslock'] = '⇪'; - map['careof'] = '℅'; - map['caron'] = 'ˇ'; - map['caronbelowcmb'] = '̬'; - map['caroncmb'] = '̌'; - map['carriagereturn'] = '↵'; - map['cbopomofo'] = 'ㄘ'; - map['ccaron'] = 'č'; - map['ccedilla'] = 'ç'; - map['ccedillaacute'] = 'ḉ'; - map['ccircle'] = 'ⓒ'; - map['ccircumflex'] = 'ĉ'; - map['ccurl'] = 'ɕ'; - map['cdot'] = 'ċ'; - map['cdotaccent'] = 'ċ'; - map['cdsquare'] = '㏅'; - map['cedilla'] = '¸'; - map['cedillacmb'] = '̧'; - map['cent'] = '¢'; - map['centigrade'] = '℃'; - map['centinferior'] = ''; - map['centmonospace'] = '¢'; - map['centoldstyle'] = ''; - map['centsuperior'] = ''; - map['chaarmenian'] = 'չ'; - map['chabengali'] = 'ছ'; - map['chadeva'] = 'छ'; - map['chagujarati'] = 'છ'; - map['chagurmukhi'] = 'ਛ'; - map['chbopomofo'] = 'ㄔ'; - map['cheabkhasiancyrillic'] = 'ҽ'; - map['checkmark'] = '✓'; - map['checyrillic'] = 'ч'; - map['chedescenderabkhasiancyrillic'] = 'ҿ'; - map['chedescendercyrillic'] = 'ҷ'; - map['chedieresiscyrillic'] = 'ӵ'; - map['cheharmenian'] = 'ճ'; - map['chekhakassiancyrillic'] = 'ӌ'; - map['cheverticalstrokecyrillic'] = 'ҹ'; - map['chi'] = 'χ'; - map['chieuchacirclekorean'] = '㉷'; - map['chieuchaparenkorean'] = '㈗'; - map['chieuchcirclekorean'] = '㉩'; - map['chieuchkorean'] = 'ㅊ'; - map['chieuchparenkorean'] = '㈉'; - map['chochangthai'] = 'ช'; - map['chochanthai'] = 'จ'; - map['chochingthai'] = 'ฉ'; - map['chochoethai'] = 'ฌ'; - map['chook'] = 'ƈ'; - map['cieucacirclekorean'] = '㉶'; - map['cieucaparenkorean'] = '㈖'; - map['cieuccirclekorean'] = '㉨'; - map['cieuckorean'] = 'ㅈ'; - map['cieucparenkorean'] = '㈈'; - map['cieucuparenkorean'] = '㈜'; - map['circle'] = '○'; - map['circlemultiply'] = '⊗'; - map['circleot'] = '⊙'; - map['circleplus'] = '⊕'; - map['circlepostalmark'] = '〶'; - map['circlewithlefthalfblack'] = '◐'; - map['circlewithrighthalfblack'] = '◑'; - map['circumflex'] = 'ˆ'; - map['circumflexbelowcmb'] = '̭'; - map['circumflexcmb'] = '̂'; - map['clear'] = '⌧'; - map['clickalveolar'] = 'ǂ'; - map['clickdental'] = 'ǀ'; - map['clicklateral'] = 'ǁ'; - map['clickretroflex'] = 'ǃ'; - map['club'] = '♣'; - map['clubsuitblack'] = '♣'; - map['clubsuitwhite'] = '♧'; - map['cmcubedsquare'] = '㎤'; - map['cmonospace'] = 'c'; - map['cmsquaredsquare'] = '㎠'; - map['coarmenian'] = 'ց'; - map['colon'] = ':'; - map['colonmonetary'] = '₡'; - map['colonmonospace'] = ':'; - map['colonsign'] = '₡'; - map['colonsmall'] = '﹕'; - map['colontriangularhalfmod'] = 'ˑ'; - map['colontriangularmod'] = 'ː'; - map['comma'] = ','; - map['commaabovecmb'] = '̓'; - map['commaaboverightcmb'] = '̕'; - map['commaaccent'] = ''; - map['commaarabic'] = '،'; - map['commaarmenian'] = '՝'; - map['commainferior'] = ''; - map['commamonospace'] = ','; - map['commareversedabovecmb'] = '̔'; - map['commareversedmod'] = 'ʽ'; - map['commasmall'] = '﹐'; - map['commasuperior'] = ''; - map['commaturnedabovecmb'] = '̒'; - map['commaturnedmod'] = 'ʻ'; - map['compass'] = '☼'; - map['congruent'] = '≅'; - map['contourintegral'] = '∮'; - map['control'] = '⌃'; - map['controlACK'] = '\u0006'; - map['controlBEL'] = '\a'; - map['controlBS'] = '\b'; - map['controlCAN'] = '\u0018'; - map['controlCR'] = '\r'; - map['controlDC1'] = '\u0011'; - map['controlDC2'] = '\u0012'; - map['controlDC3'] = '\u0013'; - map['controlDC4'] = '\u0014'; - map['controlDEL'] = '\u007f'; - map['controlDLE'] = '\u0010'; - map['controlEM'] = '\u0019'; - map['controlENQ'] = '\u0005'; - map['controlEOT'] = '\u0004'; - map['controlESC'] = '\u001b'; - map['controlETB'] = '\u0017'; - map['controlETX'] = '\u0003'; - map['controlFF'] = '\f'; - map['controlFS'] = '\u001c'; - map['controlGS'] = '\u001d'; - map['controlHT'] = '\t'; - map['controlLF'] = '\n'; - map['controlNAK'] = '\u0015'; - map['controlRS'] = '\u001e'; - map['controlSI'] = '\u000f'; - map['controlSO'] = '\u000e'; - map['controlSOT'] = '\u0002'; - map['controlSTX'] = '\u0001'; - map['controlSUB'] = '\u001a'; - map['controlSYN'] = '\u0016'; - map['controlUS'] = '\u001f'; - map['controlVT'] = '\v'; - map['copyright'] = '©'; - map['copyrightsans'] = ''; - map['copyrightserif'] = ''; - map['cornerbracketleft'] = '「'; - map['cornerbracketlefthalfwidth'] = '「'; - map['cornerbracketleftvertical'] = '﹁'; - map['cornerbracketright'] = '」'; - map['cornerbracketrighthalfwidth'] = '」'; - map['cornerbracketrightvertical'] = '﹂'; - map['corporationsquare'] = '㍿'; - map['cosquare'] = '㏇'; - map['coverkgsquare'] = '㏆'; - map['cparen'] = '⒞'; - map['cruzeiro'] = '₢'; - map['cstretched'] = 'ʗ'; - map['curlyand'] = '⋏'; - map['curlyor'] = '⋎'; - map['currency'] = '¤'; - map['cyrBreve'] = ''; - map['cyrFlex'] = ''; - map['cyrbreve'] = ''; - map['cyrflex'] = ''; - map['d'] = 'd'; - map['daarmenian'] = 'դ'; - map['dabengali'] = 'দ'; - map['dadarabic'] = 'ض'; - map['dadeva'] = 'द'; - map['dadfinalarabic'] = 'ﺾ'; - map['dadinitialarabic'] = 'ﺿ'; - map['dadmedialarabic'] = 'ﻀ'; - map['dagesh'] = 'ּ'; - map['dageshhebrew'] = 'ּ'; - map['dagger'] = '†'; - map['daggerdbl'] = '‡'; - map['dagujarati'] = 'દ'; - map['dagurmukhi'] = 'ਦ'; - map['dahiragana'] = 'だ'; - map['dakatakana'] = 'ダ'; - map['dalarabic'] = 'د'; - map['dalet'] = 'ד'; - map['daletdagesh'] = 'דּ'; - map['daletdageshhebrew'] = 'דּ'; - map['dalethebrew'] = 'ד'; - map['dalfinalarabic'] = 'ﺪ'; - map['dammaarabic'] = 'ُ'; - map['dammalowarabic'] = 'ُ'; - map['dammatanaltonearabic'] = 'ٌ'; - map['dammatanarabic'] = 'ٌ'; - map['danda'] = '।'; - map['dargahebrew'] = '֧'; - map['dargalefthebrew'] = '֧'; - map['dasiapneumatacyrilliccmb'] = '҅'; - map['dblGrave'] = ''; - map['dblanglebracketleft'] = '《'; - map['dblanglebracketleftvertical'] = '︽'; - map['dblanglebracketright'] = '》'; - map['dblanglebracketrightvertical'] = '︾'; - map['dblarchinvertedbelowcmb'] = '̫'; - map['dblarrowleft'] = '⇔'; - map['dblarrowright'] = '⇒'; - map['dbldanda'] = '॥'; - map['dblgrave'] = ''; - map['dblgravecmb'] = '̏'; - map['dblintegral'] = '∬'; - map['dbllowline'] = '‗'; - map['dbllowlinecmb'] = '̳'; - map['dbloverlinecmb'] = '̿'; - map['dblprimemod'] = 'ʺ'; - map['dblverticalbar'] = '‖'; - map['dblverticallineabovecmb'] = '̎'; - map['dbopomofo'] = 'ㄉ'; - map['dbsquare'] = '㏈'; - map['dcaron'] = 'ď'; - map['dcedilla'] = 'ḑ'; - map['dcircle'] = 'ⓓ'; - map['dcircumflexbelow'] = 'ḓ'; - map['dcroat'] = 'đ'; - map['ddabengali'] = 'ড'; - map['ddadeva'] = 'ड'; - map['ddagujarati'] = 'ડ'; - map['ddagurmukhi'] = 'ਡ'; - map['ddalarabic'] = 'ڈ'; - map['ddalfinalarabic'] = 'ﮉ'; - map['dddhadeva'] = 'ड़'; - map['ddhabengali'] = 'ঢ'; - map['ddhadeva'] = 'ढ'; - map['ddhagujarati'] = 'ઢ'; - map['ddhagurmukhi'] = 'ਢ'; - map['ddotaccent'] = 'ḋ'; - map['ddotbelow'] = 'ḍ'; - map['decimalseparatorarabic'] = '٫'; - map['decimalseparatorpersian'] = '٫'; - map['decyrillic'] = 'д'; - map['degree'] = '°'; - map['dehihebrew'] = '֭'; - map['dehiragana'] = 'で'; - map['deicoptic'] = 'ϯ'; - map['dekatakana'] = 'デ'; - map['deleteleft'] = '⌫'; - map['deleteright'] = '⌦'; - map['delta'] = 'δ'; - map['deltaturned'] = 'ƍ'; - map['denominatorminusonenumeratorbengali'] = '৸'; - map['dezh'] = 'ʤ'; - map['dhabengali'] = 'ধ'; - map['dhadeva'] = 'ध'; - map['dhagujarati'] = 'ધ'; - map['dhagurmukhi'] = 'ਧ'; - map['dhook'] = 'ɗ'; - map['dialytikatonos'] = '΅'; - map['dialytikatonoscmb'] = '̈́'; - map['diamond'] = '♦'; - map['diamondsuitwhite'] = '♢'; - map['dieresis'] = '¨'; - map['dieresisacute'] = ''; - map['dieresisbelowcmb'] = '̤'; - map['dieresiscmb'] = '̈'; - map['dieresisgrave'] = ''; - map['dieresistonos'] = '΅'; - map['dihiragana'] = 'ぢ'; - map['dikatakana'] = 'ヂ'; - map['dittomark'] = '〃'; - map['divide'] = '÷'; - map['divides'] = '∣'; - map['divisionslash'] = '∕'; - map['djecyrillic'] = 'ђ'; - map['dkshade'] = '▓'; - map['dlinebelow'] = 'ḏ'; - map['dlsquare'] = '㎗'; - map['dmacron'] = 'đ'; - map['dmonospace'] = 'd'; - map['dnblock'] = '▄'; - map['dochadathai'] = 'ฎ'; - map['dodekthai'] = 'ด'; - map['dohiragana'] = 'ど'; - map['dokatakana'] = 'ド'; - map['dollar'] = '\$'; - map['dollarinferior'] = ''; - map['dollarmonospace'] = '$'; - map['dollaroldstyle'] = ''; - map['dollarsmall'] = '﹩'; - map['dollarsuperior'] = ''; - map['dong'] = '₫'; - map['dorusquare'] = '㌦'; - map['dotaccent'] = '˙'; - map['dotaccentcmb'] = '̇'; - map['dotbelowcmb'] = '̣'; - map['dotbelowcomb'] = '̣'; - map['dotkatakana'] = '・'; - map['dotlessi'] = 'ı'; - map['dotlessj'] = ''; - map['dotlessjstrokehook'] = 'ʄ'; - map['dotmath'] = '⋅'; - map['dottedcircle'] = '◌'; - map['doubleyodpatah'] = 'ײַ'; - map['doubleyodpatahhebrew'] = 'ײַ'; - map['downtackbelowcmb'] = '̞'; - map['downtackmod'] = '˕'; - map['dparen'] = '⒟'; - map['dsuperior'] = ''; - map['dtail'] = 'ɖ'; - map['dtopbar'] = 'ƌ'; - map['duhiragana'] = 'づ'; - map['dukatakana'] = 'ヅ'; - map['dz'] = 'dz'; - map['dzaltone'] = 'ʣ'; - map['dzcaron'] = 'dž'; - map['dzcurl'] = 'ʥ'; - map['dzeabkhasiancyrillic'] = 'ӡ'; - map['dzecyrillic'] = 'ѕ'; - map['dzhecyrillic'] = 'џ'; - map['e'] = 'e'; - map['eacute'] = 'é'; - map['earth'] = '♁'; - map['ebengali'] = 'এ'; - map['ebopomofo'] = 'ㄜ'; - map['ebreve'] = 'ĕ'; - map['ecandradeva'] = 'ऍ'; - map['ecandragujarati'] = 'ઍ'; - map['ecandravowelsigndeva'] = 'ॅ'; - map['ecandravowelsigngujarati'] = 'ૅ'; - map['ecaron'] = 'ě'; - map['ecedillabreve'] = 'ḝ'; - map['echarmenian'] = 'ե'; - map['echyiwnarmenian'] = 'և'; - map['ecircle'] = 'ⓔ'; - map['ecircumflex'] = 'ê'; - map['ecircumflexacute'] = 'ế'; - map['ecircumflexbelow'] = 'ḙ'; - map['ecircumflexdotbelow'] = 'ệ'; - map['ecircumflexgrave'] = 'ề'; - map['ecircumflexhookabove'] = 'ể'; - map['ecircumflextilde'] = 'ễ'; - map['ecyrillic'] = 'є'; - map['edblgrave'] = 'ȅ'; - map['edeva'] = 'ए'; - map['edieresis'] = 'ë'; - map['edot'] = 'ė'; - map['edotaccent'] = 'ė'; - map['edotbelow'] = 'ẹ'; - map['eegurmukhi'] = 'ਏ'; - map['eematragurmukhi'] = 'ੇ'; - map['efcyrillic'] = 'ф'; - map['egrave'] = 'è'; - map['egujarati'] = 'એ'; - map['eharmenian'] = 'է'; - map['ehbopomofo'] = 'ㄝ'; - map['ehiragana'] = 'え'; - map['ehookabove'] = 'ẻ'; - map['eibopomofo'] = 'ㄟ'; - map['eight'] = '8'; - map['eightarabic'] = '٨'; - map['eightbengali'] = '৮'; - map['eightcircle'] = '⑧'; - map['eightcircleinversesansserif'] = '➑'; - map['eightdeva'] = '८'; - map['eighteencircle'] = '⑱'; - map['eighteenparen'] = '⒅'; - map['eighteenperiod'] = '⒙'; - map['eightgujarati'] = '૮'; - map['eightgurmukhi'] = '੮'; - map['eighthackarabic'] = '٨'; - map['eighthangzhou'] = '〨'; - map['eighthnotebeamed'] = '♫'; - map['eightideographicparen'] = '㈧'; - map['eightinferior'] = '₈'; - map['eightmonospace'] = '8'; - map['eightoldstyle'] = ''; - map['eightparen'] = '⑻'; - map['eightperiod'] = '⒏'; - map['eightpersian'] = '۸'; - map['eightroman'] = 'ⅷ'; - map['eightsuperior'] = '⁸'; - map['eightthai'] = '๘'; - map['einvertedbreve'] = 'ȇ'; - map['eiotifiedcyrillic'] = 'ѥ'; - map['ekatakana'] = 'エ'; - map['ekatakanahalfwidth'] = 'エ'; - map['ekonkargurmukhi'] = 'ੴ'; - map['ekorean'] = 'ㅔ'; - map['elcyrillic'] = 'л'; - map['element'] = '∈'; - map['elevencircle'] = '⑪'; - map['elevenparen'] = '⑾'; - map['elevenperiod'] = '⒒'; - map['elevenroman'] = 'ⅺ'; - map['ellipsis'] = '…'; - map['ellipsisvertical'] = '⋮'; - map['emacron'] = 'ē'; - map['emacronacute'] = 'ḗ'; - map['emacrongrave'] = 'ḕ'; - map['emcyrillic'] = 'м'; - map['emdash'] = '—'; - map['emdashvertical'] = '︱'; - map['emonospace'] = 'e'; - map['emphasismarkarmenian'] = '՛'; - map['emptyset'] = '∅'; - map['enbopomofo'] = 'ㄣ'; - map['encyrillic'] = 'н'; - map['endash'] = '–'; - map['endashvertical'] = '︲'; - map['endescendercyrillic'] = 'ң'; - map['eng'] = 'ŋ'; - map['engbopomofo'] = 'ㄥ'; - map['enghecyrillic'] = 'ҥ'; - map['enhookcyrillic'] = 'ӈ'; - map['enspace'] = '\u2002'; - map['eogonek'] = 'ę'; - map['eokorean'] = 'ㅓ'; - map['eopen'] = 'ɛ'; - map['eopenclosed'] = 'ʚ'; - map['eopenreversed'] = 'ɜ'; - map['eopenreversedclosed'] = 'ɞ'; - map['eopenreversedhook'] = 'ɝ'; - map['eparen'] = '⒠'; - map['epsilon'] = 'ε'; - map['epsilontonos'] = 'έ'; - map['equal'] = '='; - map['equalmonospace'] = '='; - map['equalsmall'] = '﹦'; - map['equalsuperior'] = '⁼'; - map['equivalence'] = '≡'; - map['erbopomofo'] = 'ㄦ'; - map['ercyrillic'] = 'р'; - map['ereversed'] = 'ɘ'; - map['ereversedcyrillic'] = 'э'; - map['escyrillic'] = 'с'; - map['esdescendercyrillic'] = 'ҫ'; - map['esh'] = 'ʃ'; - map['eshcurl'] = 'ʆ'; - map['eshortdeva'] = 'ऎ'; - map['eshortvowelsigndeva'] = 'ॆ'; - map['eshreversedloop'] = 'ƪ'; - map['eshsquatreversed'] = 'ʅ'; - map['esmallhiragana'] = 'ぇ'; - map['esmallkatakana'] = 'ェ'; - map['esmallkatakanahalfwidth'] = 'ェ'; - map['estimated'] = '℮'; - map['esuperior'] = ''; - map['eta'] = 'η'; - map['etarmenian'] = 'ը'; - map['etatonos'] = 'ή'; - map['eth'] = 'ð'; - map['etilde'] = 'ẽ'; - map['etildebelow'] = 'ḛ'; - map['etnahtafoukhhebrew'] = '֑'; - map['etnahtafoukhlefthebrew'] = '֑'; - map['etnahtahebrew'] = '֑'; - map['etnahtalefthebrew'] = '֑'; - map['eturned'] = 'ǝ'; - map['eukorean'] = 'ㅡ'; - map['euro'] = '€'; - map['evowelsignbengali'] = 'ে'; - map['evowelsigndeva'] = 'े'; - map['evowelsigngujarati'] = 'ે'; - map['exclam'] = '!'; - map['exclamarmenian'] = '՜'; - map['exclamdbl'] = '‼'; - map['exclamdown'] = '¡'; - map['exclamdownsmall'] = ''; - map['exclammonospace'] = '!'; - map['exclamsmall'] = ''; - map['existential'] = '∃'; - map['ezh'] = 'ʒ'; - map['ezhcaron'] = 'ǯ'; - map['ezhcurl'] = 'ʓ'; - map['ezhreversed'] = 'ƹ'; - map['ezhtail'] = 'ƺ'; - map['f'] = 'f'; - map['fadeva'] = 'फ़'; - map['fagurmukhi'] = 'ਫ਼'; - map['fahrenheit'] = '℉'; - map['fathaarabic'] = 'َ'; - map['fathalowarabic'] = 'َ'; - map['fathatanarabic'] = 'ً'; - map['fbopomofo'] = 'ㄈ'; - map['fcircle'] = 'ⓕ'; - map['fdotaccent'] = 'ḟ'; - map['feharabic'] = 'ف'; - map['feharmenian'] = 'ֆ'; - map['fehfinalarabic'] = 'ﻒ'; - map['fehinitialarabic'] = 'ﻓ'; - map['fehmedialarabic'] = 'ﻔ'; - map['feicoptic'] = 'ϥ'; - map['female'] = '♀'; - map['ff'] = 'ff'; - map['ffi'] = 'ffi'; - map['ffl'] = 'ffl'; - map['fi'] = 'fi'; - map['fifteencircle'] = '⑮'; - map['fifteenparen'] = '⒂'; - map['fifteenperiod'] = '⒖'; - map['figuredash'] = '‒'; - map['filledbox'] = '■'; - map['filledrect'] = '▬'; - map['finalkaf'] = 'ך'; - map['finalkafdagesh'] = 'ךּ'; - map['finalkafdageshhebrew'] = 'ךּ'; - map['finalkafhebrew'] = 'ך'; - map['finalmem'] = 'ם'; - map['finalmemhebrew'] = 'ם'; - map['finalnun'] = 'ן'; - map['finalnunhebrew'] = 'ן'; - map['finalpe'] = 'ף'; - map['finalpehebrew'] = 'ף'; - map['finaltsadi'] = 'ץ'; - map['finaltsadihebrew'] = 'ץ'; - map['firsttonechinese'] = 'ˉ'; - map['fisheye'] = '◉'; - map['fitacyrillic'] = 'ѳ'; - map['five'] = '5'; - map['fivearabic'] = '٥'; - map['fivebengali'] = '৫'; - map['fivecircle'] = '⑤'; - map['fivecircleinversesansserif'] = '➎'; - map['fivedeva'] = '५'; - map['fiveeighths'] = '⅝'; - map['fivegujarati'] = '૫'; - map['fivegurmukhi'] = '੫'; - map['fivehackarabic'] = '٥'; - map['fivehangzhou'] = '〥'; - map['fiveideographicparen'] = '㈤'; - map['fiveinferior'] = '₅'; - map['fivemonospace'] = '5'; - map['fiveoldstyle'] = ''; - map['fiveparen'] = '⑸'; - map['fiveperiod'] = '⒌'; - map['fivepersian'] = '۵'; - map['fiveroman'] = 'ⅴ'; - map['fivesuperior'] = '⁵'; - map['fivethai'] = '๕'; - map['fl'] = 'fl'; - map['florin'] = 'ƒ'; - map['fmonospace'] = 'f'; - map['fmsquare'] = '㎙'; - map['fofanthai'] = 'ฟ'; - map['fofathai'] = 'ฝ'; - map['fongmanthai'] = '๏'; - map['forall'] = '∀'; - map['four'] = '4'; - map['fourarabic'] = '٤'; - map['fourbengali'] = '৪'; - map['fourcircle'] = '④'; - map['fourcircleinversesansserif'] = '➍'; - map['fourdeva'] = '४'; - map['fourgujarati'] = '૪'; - map['fourgurmukhi'] = '੪'; - map['fourhackarabic'] = '٤'; - map['fourhangzhou'] = '〤'; - map['fourideographicparen'] = '㈣'; - map['fourinferior'] = '₄'; - map['fourmonospace'] = '4'; - map['fournumeratorbengali'] = '৷'; - map['fouroldstyle'] = ''; - map['fourparen'] = '⑷'; - map['fourperiod'] = '⒋'; - map['fourpersian'] = '۴'; - map['fourroman'] = 'ⅳ'; - map['foursuperior'] = '⁴'; - map['fourteencircle'] = '⑭'; - map['fourteenparen'] = '⒁'; - map['fourteenperiod'] = '⒕'; - map['fourthai'] = '๔'; - map['fourthtonechinese'] = 'ˋ'; - map['fparen'] = '⒡'; - map['fraction'] = '⁄'; - map['franc'] = '₣'; - map['g'] = 'g'; - map['gabengali'] = 'গ'; - map['gacute'] = 'ǵ'; - map['gadeva'] = 'ग'; - map['gafarabic'] = 'گ'; - map['gaffinalarabic'] = 'ﮓ'; - map['gafinitialarabic'] = 'ﮔ'; - map['gafmedialarabic'] = 'ﮕ'; - map['gagujarati'] = 'ગ'; - map['gagurmukhi'] = 'ਗ'; - map['gahiragana'] = 'が'; - map['gakatakana'] = 'ガ'; - map['gamma'] = 'γ'; - map['gammalatinsmall'] = 'ɣ'; - map['gammasuperior'] = 'ˠ'; - map['gangiacoptic'] = 'ϫ'; - map['gbopomofo'] = 'ㄍ'; - map['gbreve'] = 'ğ'; - map['gcaron'] = 'ǧ'; - map['gcedilla'] = 'ģ'; - map['gcircle'] = 'ⓖ'; - map['gcircumflex'] = 'ĝ'; - map['gcommaaccent'] = 'ģ'; - map['gdot'] = 'ġ'; - map['gdotaccent'] = 'ġ'; - map['gecyrillic'] = 'г'; - map['gehiragana'] = 'げ'; - map['gekatakana'] = 'ゲ'; - map['geometricallyequal'] = '≑'; - map['gereshaccenthebrew'] = '֜'; - map['gereshhebrew'] = '׳'; - map['gereshmuqdamhebrew'] = '֝'; - map['germandbls'] = 'ß'; - map['gershayimaccenthebrew'] = '֞'; - map['gershayimhebrew'] = '״'; - map['getamark'] = '〓'; - map['ghabengali'] = 'ঘ'; - map['ghadarmenian'] = 'ղ'; - map['ghadeva'] = 'घ'; - map['ghagujarati'] = 'ઘ'; - map['ghagurmukhi'] = 'ਘ'; - map['ghainarabic'] = 'غ'; - map['ghainfinalarabic'] = 'ﻎ'; - map['ghaininitialarabic'] = 'ﻏ'; - map['ghainmedialarabic'] = 'ﻐ'; - map['ghemiddlehookcyrillic'] = 'ҕ'; - map['ghestrokecyrillic'] = 'ғ'; - map['gheupturncyrillic'] = 'ґ'; - map['ghhadeva'] = 'ग़'; - map['ghhagurmukhi'] = 'ਗ਼'; - map['ghook'] = 'ɠ'; - map['ghzsquare'] = '㎓'; - map['gihiragana'] = 'ぎ'; - map['gikatakana'] = 'ギ'; - map['gimarmenian'] = 'գ'; - map['gimel'] = 'ג'; - map['gimeldagesh'] = 'גּ'; - map['gimeldageshhebrew'] = 'גּ'; - map['gimelhebrew'] = 'ג'; - map['gjecyrillic'] = 'ѓ'; - map['glottalinvertedstroke'] = 'ƾ'; - map['glottalstop'] = 'ʔ'; - map['glottalstopinverted'] = 'ʖ'; - map['glottalstopmod'] = 'ˀ'; - map['glottalstopreversed'] = 'ʕ'; - map['glottalstopreversedmod'] = 'ˁ'; - map['glottalstopreversedsuperior'] = 'ˤ'; - map['glottalstopstroke'] = 'ʡ'; - map['glottalstopstrokereversed'] = 'ʢ'; - map['gmacron'] = 'ḡ'; - map['gmonospace'] = 'g'; - map['gohiragana'] = 'ご'; - map['gokatakana'] = 'ゴ'; - map['gparen'] = '⒢'; - map['gpasquare'] = '㎬'; - map['gradient'] = '∇'; - map['grave'] = '`'; - map['gravebelowcmb'] = '̖'; - map['gravecmb'] = '̀'; - map['gravecomb'] = '̀'; - map['gravedeva'] = '॓'; - map['gravelowmod'] = 'ˎ'; - map['gravemonospace'] = '`'; - map['gravetonecmb'] = '̀'; - map['greater'] = '>'; - map['greaterequal'] = '≥'; - map['greaterequalorless'] = '⋛'; - map['greatermonospace'] = '>'; - map['greaterorequivalent'] = '≳'; - map['greaterorless'] = '≷'; - map['greateroverequal'] = '≧'; - map['greatersmall'] = '﹥'; - map['gscript'] = 'ɡ'; - map['gstroke'] = 'ǥ'; - map['guhiragana'] = 'ぐ'; - map['guillemotleft'] = '«'; - map['guillemotright'] = '»'; - map['guilsinglleft'] = '‹'; - map['guilsinglright'] = '›'; - map['gukatakana'] = 'グ'; - map['guramusquare'] = '㌘'; - map['gysquare'] = '㏉'; - map['h'] = 'h'; - map['haabkhasiancyrillic'] = 'ҩ'; - map['haaltonearabic'] = 'ہ'; - map['habengali'] = 'হ'; - map['hadescendercyrillic'] = 'ҳ'; - map['hadeva'] = 'ह'; - map['hagujarati'] = 'હ'; - map['hagurmukhi'] = 'ਹ'; - map['haharabic'] = 'ح'; - map['hahfinalarabic'] = 'ﺢ'; - map['hahinitialarabic'] = 'ﺣ'; - map['hahiragana'] = 'は'; - map['hahmedialarabic'] = 'ﺤ'; - map['haitusquare'] = '㌪'; - map['hakatakana'] = 'ハ'; - map['hakatakanahalfwidth'] = 'ハ'; - map['halantgurmukhi'] = '੍'; - map['hamzaarabic'] = 'ء'; - map['hamzalowarabic'] = 'ء'; - map['hangulfiller'] = 'ㅤ'; - map['hardsigncyrillic'] = 'ъ'; - map['harpoonleftbarbup'] = '↼'; - map['harpoonrightbarbup'] = '⇀'; - map['hasquare'] = '㏊'; - map['hatafpatah'] = 'ֲ'; - map['hatafpatah16'] = 'ֲ'; - map['hatafpatah23'] = 'ֲ'; - map['hatafpatah2f'] = 'ֲ'; - map['hatafpatahhebrew'] = 'ֲ'; - map['hatafpatahnarrowhebrew'] = 'ֲ'; - map['hatafpatahquarterhebrew'] = 'ֲ'; - map['hatafpatahwidehebrew'] = 'ֲ'; - map['hatafqamats'] = 'ֳ'; - map['hatafqamats1b'] = 'ֳ'; - map['hatafqamats28'] = 'ֳ'; - map['hatafqamats34'] = 'ֳ'; - map['hatafqamatshebrew'] = 'ֳ'; - map['hatafqamatsnarrowhebrew'] = 'ֳ'; - map['hatafqamatsquarterhebrew'] = 'ֳ'; - map['hatafqamatswidehebrew'] = 'ֳ'; - map['hatafsegol'] = 'ֱ'; - map['hatafsegol17'] = 'ֱ'; - map['hatafsegol24'] = 'ֱ'; - map['hatafsegol30'] = 'ֱ'; - map['hatafsegolhebrew'] = 'ֱ'; - map['hatafsegolnarrowhebrew'] = 'ֱ'; - map['hatafsegolquarterhebrew'] = 'ֱ'; - map['hatafsegolwidehebrew'] = 'ֱ'; - map['hbar'] = 'ħ'; - map['hbopomofo'] = 'ㄏ'; - map['hbrevebelow'] = 'ḫ'; - map['hcedilla'] = 'ḩ'; - map['hcircle'] = 'ⓗ'; - map['hcircumflex'] = 'ĥ'; - map['hdieresis'] = 'ḧ'; - map['hdotaccent'] = 'ḣ'; - map['hdotbelow'] = 'ḥ'; - map['he'] = 'ה'; - map['heart'] = '♥'; - map['heartsuitblack'] = '♥'; - map['heartsuitwhite'] = '♡'; - map['hedagesh'] = 'הּ'; - map['hedageshhebrew'] = 'הּ'; - map['hehaltonearabic'] = 'ہ'; - map['heharabic'] = 'ه'; - map['hehebrew'] = 'ה'; - map['hehfinalaltonearabic'] = 'ﮧ'; - map['hehfinalalttwoarabic'] = 'ﻪ'; - map['hehfinalarabic'] = 'ﻪ'; - map['hehhamzaabovefinalarabic'] = 'ﮥ'; - map['hehhamzaaboveisolatedarabic'] = 'ﮤ'; - map['hehinitialaltonearabic'] = 'ﮨ'; - map['hehinitialarabic'] = 'ﻫ'; - map['hehiragana'] = 'へ'; - map['hehmedialaltonearabic'] = 'ﮩ'; - map['hehmedialarabic'] = 'ﻬ'; - map['heiseierasquare'] = '㍻'; - map['hekatakana'] = 'ヘ'; - map['hekatakanahalfwidth'] = 'ヘ'; - map['hekutaarusquare'] = '㌶'; - map['henghook'] = 'ɧ'; - map['herutusquare'] = '㌹'; - map['het'] = 'ח'; - map['hethebrew'] = 'ח'; - map['hhook'] = 'ɦ'; - map['hhooksuperior'] = 'ʱ'; - map['hieuhacirclekorean'] = '㉻'; - map['hieuhaparenkorean'] = '㈛'; - map['hieuhcirclekorean'] = '㉭'; - map['hieuhkorean'] = 'ㅎ'; - map['hieuhparenkorean'] = '㈍'; - map['hihiragana'] = 'ひ'; - map['hikatakana'] = 'ヒ'; - map['hikatakanahalfwidth'] = 'ヒ'; - map['hiriq'] = 'ִ'; - map['hiriq14'] = 'ִ'; - map['hiriq21'] = 'ִ'; - map['hiriq2d'] = 'ִ'; - map['hiriqhebrew'] = 'ִ'; - map['hiriqnarrowhebrew'] = 'ִ'; - map['hiriqquarterhebrew'] = 'ִ'; - map['hiriqwidehebrew'] = 'ִ'; - map['hlinebelow'] = 'ẖ'; - map['hmonospace'] = 'h'; - map['hoarmenian'] = 'հ'; - map['hohipthai'] = 'ห'; - map['hohiragana'] = 'ほ'; - map['hokatakana'] = 'ホ'; - map['hokatakanahalfwidth'] = 'ホ'; - map['holam'] = 'ֹ'; - map['holam19'] = 'ֹ'; - map['holam26'] = 'ֹ'; - map['holam32'] = 'ֹ'; - map['holamhebrew'] = 'ֹ'; - map['holamnarrowhebrew'] = 'ֹ'; - map['holamquarterhebrew'] = 'ֹ'; - map['holamwidehebrew'] = 'ֹ'; - map['honokhukthai'] = 'ฮ'; - map['hookabovecomb'] = '̉'; - map['hookcmb'] = '̉'; - map['hookpalatalizedbelowcmb'] = '̡'; - map['hookretroflexbelowcmb'] = '̢'; - map['hoonsquare'] = '㍂'; - map['horicoptic'] = 'ϩ'; - map['horizontalbar'] = '―'; - map['horncmb'] = '̛'; - map['hotsprings'] = '♨'; - map['house'] = '⌂'; - map['hparen'] = '⒣'; - map['hsuperior'] = 'ʰ'; - map['hturned'] = 'ɥ'; - map['huhiragana'] = 'ふ'; - map['huiitosquare'] = '㌳'; - map['hukatakana'] = 'フ'; - map['hukatakanahalfwidth'] = 'フ'; - map['hungarumlaut'] = '˝'; - map['hungarumlautcmb'] = '̋'; - map['hv'] = 'ƕ'; - map['hyphen'] = '-'; - map['hypheninferior'] = ''; - map['hyphenmonospace'] = '-'; - map['hyphensmall'] = '﹣'; - map['hyphensuperior'] = ''; - map['hyphentwo'] = '‐'; - map['i'] = 'i'; - map['iacute'] = 'í'; - map['iacyrillic'] = 'я'; - map['ibengali'] = 'ই'; - map['ibopomofo'] = 'ㄧ'; - map['ibreve'] = 'ĭ'; - map['icaron'] = 'ǐ'; - map['icircle'] = 'ⓘ'; - map['icircumflex'] = 'î'; - map['icyrillic'] = 'і'; - map['idblgrave'] = 'ȉ'; - map['ideographearthcircle'] = '㊏'; - map['ideographfirecircle'] = '㊋'; - map['ideographicallianceparen'] = '㈿'; - map['ideographiccallparen'] = '㈺'; - map['ideographiccentrecircle'] = '㊥'; - map['ideographicclose'] = '〆'; - map['ideographiccomma'] = '、'; - map['ideographiccommaleft'] = '、'; - map['ideographiccongratulationparen'] = '㈷'; - map['ideographiccorrectcircle'] = '㊣'; - map['ideographicearthparen'] = '㈯'; - map['ideographicenterpriseparen'] = '㈽'; - map['ideographicexcellentcircle'] = '㊝'; - map['ideographicfestivalparen'] = '㉀'; - map['ideographicfinancialcircle'] = '㊖'; - map['ideographicfinancialparen'] = '㈶'; - map['ideographicfireparen'] = '㈫'; - map['ideographichaveparen'] = '㈲'; - map['ideographichighcircle'] = '㊤'; - map['ideographiciterationmark'] = '々'; - map['ideographiclaborcircle'] = '㊘'; - map['ideographiclaborparen'] = '㈸'; - map['ideographicleftcircle'] = '㊧'; - map['ideographiclowcircle'] = '㊦'; - map['ideographicmedicinecircle'] = '㊩'; - map['ideographicmetalparen'] = '㈮'; - map['ideographicmoonparen'] = '㈪'; - map['ideographicnameparen'] = '㈴'; - map['ideographicperiod'] = '。'; - map['ideographicprintcircle'] = '㊞'; - map['ideographicreachparen'] = '㉃'; - map['ideographicrepresentparen'] = '㈹'; - map['ideographicresourceparen'] = '㈾'; - map['ideographicrightcircle'] = '㊨'; - map['ideographicsecretcircle'] = '㊙'; - map['ideographicselfparen'] = '㉂'; - map['ideographicsocietyparen'] = '㈳'; - map['ideographicspace'] = '\u3000'; - map['ideographicspecialparen'] = '㈵'; - map['ideographicstockparen'] = '㈱'; - map['ideographicstudyparen'] = '㈻'; - map['ideographicsunparen'] = '㈰'; - map['ideographicsuperviseparen'] = '㈼'; - map['ideographicwaterparen'] = '㈬'; - map['ideographicwoodparen'] = '㈭'; - map['ideographiczero'] = '〇'; - map['ideographmetalcircle'] = '㊎'; - map['ideographmooncircle'] = '㊊'; - map['ideographnamecircle'] = '㊔'; - map['ideographsuncircle'] = '㊐'; - map['ideographwatercircle'] = '㊌'; - map['ideographwoodcircle'] = '㊍'; - map['ideva'] = 'इ'; - map['idieresis'] = 'ï'; - map['idieresisacute'] = 'ḯ'; - map['idieresiscyrillic'] = 'ӥ'; - map['idotbelow'] = 'ị'; - map['iebrevecyrillic'] = 'ӗ'; - map['iecyrillic'] = 'е'; - map['ieungacirclekorean'] = '㉵'; - map['ieungaparenkorean'] = '㈕'; - map['ieungcirclekorean'] = '㉧'; - map['ieungkorean'] = 'ㅇ'; - map['ieungparenkorean'] = '㈇'; - map['igrave'] = 'ì'; - map['igujarati'] = 'ઇ'; - map['igurmukhi'] = 'ਇ'; - map['ihiragana'] = 'い'; - map['ihookabove'] = 'ỉ'; - map['iibengali'] = 'ঈ'; - map['iicyrillic'] = 'и'; - map['iideva'] = 'ई'; - map['iigujarati'] = 'ઈ'; - map['iigurmukhi'] = 'ਈ'; - map['iimatragurmukhi'] = 'ੀ'; - map['iinvertedbreve'] = 'ȋ'; - map['iishortcyrillic'] = 'й'; - map['iivowelsignbengali'] = 'ী'; - map['iivowelsigndeva'] = 'ी'; - map['iivowelsigngujarati'] = 'ી'; - map['ij'] = 'ij'; - map['ikatakana'] = 'イ'; - map['ikatakanahalfwidth'] = 'イ'; - map['ikorean'] = 'ㅣ'; - map['ilde'] = '˜'; - map['iluyhebrew'] = '֬'; - map['imacron'] = 'ī'; - map['imacroncyrillic'] = 'ӣ'; - map['imageorapproximatelyequal'] = '≓'; - map['imatragurmukhi'] = 'ਿ'; - map['imonospace'] = 'i'; - map['increment'] = '∆'; - map['infinity'] = '∞'; - map['iniarmenian'] = 'ի'; - map['integral'] = '∫'; - map['integralbottom'] = '⌡'; - map['integralbt'] = '⌡'; - map['integralex'] = ''; - map['integraltop'] = '⌠'; - map['integraltp'] = '⌠'; - map['intersection'] = '∩'; - map['intisquare'] = '㌅'; - map['invbullet'] = '◘'; - map['invcircle'] = '◙'; - map['invsmileface'] = '☻'; - map['iocyrillic'] = 'ё'; - map['iogonek'] = 'į'; - map['iota'] = 'ι'; - map['iotadieresis'] = 'ϊ'; - map['iotadieresistonos'] = 'ΐ'; - map['iotalatin'] = 'ɩ'; - map['iotatonos'] = 'ί'; - map['iparen'] = '⒤'; - map['irigurmukhi'] = 'ੲ'; - map['ismallhiragana'] = 'ぃ'; - map['ismallkatakana'] = 'ィ'; - map['ismallkatakanahalfwidth'] = 'ィ'; - map['issharbengali'] = '৺'; - map['istroke'] = 'ɨ'; - map['isuperior'] = ''; - map['iterationhiragana'] = 'ゝ'; - map['iterationkatakana'] = 'ヽ'; - map['itilde'] = 'ĩ'; - map['itildebelow'] = 'ḭ'; - map['iubopomofo'] = 'ㄩ'; - map['iucyrillic'] = 'ю'; - map['ivowelsignbengali'] = 'ি'; - map['ivowelsigndeva'] = 'ि'; - map['ivowelsigngujarati'] = 'િ'; - map['izhitsacyrillic'] = 'ѵ'; - map['izhitsadblgravecyrillic'] = 'ѷ'; - map['j'] = 'j'; - map['jaarmenian'] = 'ձ'; - map['jabengali'] = 'জ'; - map['jadeva'] = 'ज'; - map['jagujarati'] = 'જ'; - map['jagurmukhi'] = 'ਜ'; - map['jbopomofo'] = 'ㄐ'; - map['jcaron'] = 'ǰ'; - map['jcircle'] = 'ⓙ'; - map['jcircumflex'] = 'ĵ'; - map['jcrossedtail'] = 'ʝ'; - map['jdotlessstroke'] = 'ɟ'; - map['jecyrillic'] = 'ј'; - map['jeemarabic'] = 'ج'; - map['jeemfinalarabic'] = 'ﺞ'; - map['jeeminitialarabic'] = 'ﺟ'; - map['jeemmedialarabic'] = 'ﺠ'; - map['jeharabic'] = 'ژ'; - map['jehfinalarabic'] = 'ﮋ'; - map['jhabengali'] = 'ঝ'; - map['jhadeva'] = 'झ'; - map['jhagujarati'] = 'ઝ'; - map['jhagurmukhi'] = 'ਝ'; - map['jheharmenian'] = 'ջ'; - map['jis'] = '〄'; - map['jmonospace'] = 'j'; - map['jparen'] = '⒥'; - map['jsuperior'] = 'ʲ'; - map['k'] = 'k'; - map['kabashkircyrillic'] = 'ҡ'; - map['kabengali'] = 'ক'; - map['kacute'] = 'ḱ'; - map['kacyrillic'] = 'к'; - map['kadescendercyrillic'] = 'қ'; - map['kadeva'] = 'क'; - map['kaf'] = 'כ'; - map['kafarabic'] = 'ك'; - map['kafdagesh'] = 'כּ'; - map['kafdageshhebrew'] = 'כּ'; - map['kaffinalarabic'] = 'ﻚ'; - map['kafhebrew'] = 'כ'; - map['kafinitialarabic'] = 'ﻛ'; - map['kafmedialarabic'] = 'ﻜ'; - map['kafrafehebrew'] = 'כֿ'; - map['kagujarati'] = 'ક'; - map['kagurmukhi'] = 'ਕ'; - map['kahiragana'] = 'か'; - map['kahookcyrillic'] = 'ӄ'; - map['kakatakana'] = 'カ'; - map['kakatakanahalfwidth'] = 'カ'; - map['kappa'] = 'κ'; - map['kappasymbolgreek'] = 'ϰ'; - map['kapyeounmieumkorean'] = 'ㅱ'; - map['kapyeounphieuphkorean'] = 'ㆄ'; - map['kapyeounpieupkorean'] = 'ㅸ'; - map['kapyeounssangpieupkorean'] = 'ㅹ'; - map['karoriisquare'] = '㌍'; - map['kashidaautoarabic'] = 'ـ'; - map['kashidaautonosidebearingarabic'] = 'ـ'; - map['kasmallkatakana'] = 'ヵ'; - map['kasquare'] = '㎄'; - map['kasraarabic'] = 'ِ'; - map['kasratanarabic'] = 'ٍ'; - map['kastrokecyrillic'] = 'ҟ'; - map['katahiraprolongmarkhalfwidth'] = 'ー'; - map['kaverticalstrokecyrillic'] = 'ҝ'; - map['kbopomofo'] = 'ㄎ'; - map['kcalsquare'] = '㎉'; - map['kcaron'] = 'ǩ'; - map['kcedilla'] = 'ķ'; - map['kcircle'] = 'ⓚ'; - map['kcommaaccent'] = 'ķ'; - map['kdotbelow'] = 'ḳ'; - map['keharmenian'] = 'ք'; - map['kehiragana'] = 'け'; - map['kekatakana'] = 'ケ'; - map['kekatakanahalfwidth'] = 'ケ'; - map['kenarmenian'] = 'կ'; - map['kesmallkatakana'] = 'ヶ'; - map['kgreenlandic'] = 'ĸ'; - map['khabengali'] = 'খ'; - map['khacyrillic'] = 'х'; - map['khadeva'] = 'ख'; - map['khagujarati'] = 'ખ'; - map['khagurmukhi'] = 'ਖ'; - map['khaharabic'] = 'خ'; - map['khahfinalarabic'] = 'ﺦ'; - map['khahinitialarabic'] = 'ﺧ'; - map['khahmedialarabic'] = 'ﺨ'; - map['kheicoptic'] = 'ϧ'; - map['khhadeva'] = 'ख़'; - map['khhagurmukhi'] = 'ਖ਼'; - map['khieukhacirclekorean'] = '㉸'; - map['khieukhaparenkorean'] = '㈘'; - map['khieukhcirclekorean'] = '㉪'; - map['khieukhkorean'] = 'ㅋ'; - map['khieukhparenkorean'] = '㈊'; - map['khokhaithai'] = 'ข'; - map['khokhonthai'] = 'ฅ'; - map['khokhuatthai'] = 'ฃ'; - map['khokhwaithai'] = 'ค'; - map['khomutthai'] = '๛'; - map['khook'] = 'ƙ'; - map['khorakhangthai'] = 'ฆ'; - map['khzsquare'] = '㎑'; - map['kihiragana'] = 'き'; - map['kikatakana'] = 'キ'; - map['kikatakanahalfwidth'] = 'キ'; - map['kiroguramusquare'] = '㌕'; - map['kiromeetorusquare'] = '㌖'; - map['kirosquare'] = '㌔'; - map['kiyeokacirclekorean'] = '㉮'; - map['kiyeokaparenkorean'] = '㈎'; - map['kiyeokcirclekorean'] = '㉠'; - map['kiyeokkorean'] = 'ㄱ'; - map['kiyeokparenkorean'] = '㈀'; - map['kiyeoksioskorean'] = 'ㄳ'; - map['kjecyrillic'] = 'ќ'; - map['klinebelow'] = 'ḵ'; - map['klsquare'] = '㎘'; - map['kmcubedsquare'] = '㎦'; - map['kmonospace'] = 'k'; - map['kmsquaredsquare'] = '㎢'; - map['kohiragana'] = 'こ'; - map['kohmsquare'] = '㏀'; - map['kokaithai'] = 'ก'; - map['kokatakana'] = 'コ'; - map['kokatakanahalfwidth'] = 'コ'; - map['kooposquare'] = '㌞'; - map['koppacyrillic'] = 'ҁ'; - map['koreanstandardsymbol'] = '㉿'; - map['koroniscmb'] = '̓'; - map['kparen'] = '⒦'; - map['kpasquare'] = '㎪'; - map['ksicyrillic'] = 'ѯ'; - map['ktsquare'] = '㏏'; - map['kturned'] = 'ʞ'; - map['kuhiragana'] = 'く'; - map['kukatakana'] = 'ク'; - map['kukatakanahalfwidth'] = 'ク'; - map['kvsquare'] = '㎸'; - map['kwsquare'] = '㎾'; - map['l'] = 'l'; - map['labengali'] = 'ল'; - map['lacute'] = 'ĺ'; - map['ladeva'] = 'ल'; - map['lagujarati'] = 'લ'; - map['lagurmukhi'] = 'ਲ'; - map['lakkhangyaothai'] = 'ๅ'; - map['lamaleffinalarabic'] = 'ﻼ'; - map['lamalefhamzaabovefinalarabic'] = 'ﻸ'; - map['lamalefhamzaaboveisolatedarabic'] = 'ﻷ'; - map['lamalefhamzabelowfinalarabic'] = 'ﻺ'; - map['lamalefhamzabelowisolatedarabic'] = 'ﻹ'; - map['lamalefisolatedarabic'] = 'ﻻ'; - map['lamalefmaddaabovefinalarabic'] = 'ﻶ'; - map['lamalefmaddaaboveisolatedarabic'] = 'ﻵ'; - map['lamarabic'] = 'ل'; - map['lambda'] = 'λ'; - map['lambdastroke'] = 'ƛ'; - map['lamed'] = 'ל'; - map['lameddagesh'] = 'לּ'; - map['lameddageshhebrew'] = 'לּ'; - map['lamedhebrew'] = 'ל'; - map['lamfinalarabic'] = 'ﻞ'; - map['lamhahinitialarabic'] = 'ﳊ'; - map['laminitialarabic'] = 'ﻟ'; - map['lamjeeminitialarabic'] = 'ﳉ'; - map['lamkhahinitialarabic'] = 'ﳋ'; - map['lamlamhehisolatedarabic'] = 'ﷲ'; - map['lammedialarabic'] = 'ﻠ'; - map['lammeemhahinitialarabic'] = 'ﶈ'; - map['lammeeminitialarabic'] = 'ﳌ'; - map['largecircle'] = '◯'; - map['lbar'] = 'ƚ'; - map['lbelt'] = 'ɬ'; - map['lbopomofo'] = 'ㄌ'; - map['lcaron'] = 'ľ'; - map['lcedilla'] = 'ļ'; - map['lcircle'] = 'ⓛ'; - map['lcircumflexbelow'] = 'ḽ'; - map['lcommaaccent'] = 'ļ'; - map['ldot'] = 'ŀ'; - map['ldotaccent'] = 'ŀ'; - map['ldotbelow'] = 'ḷ'; - map['ldotbelowmacron'] = 'ḹ'; - map['leftangleabovecmb'] = '̚'; - map['lefttackbelowcmb'] = '̘'; - map['less'] = '<'; - map['lessequal'] = '≤'; - map['lessequalorgreater'] = '⋚'; - map['lessmonospace'] = '<'; - map['lessorequivalent'] = '≲'; - map['lessorgreater'] = '≶'; - map['lessoverequal'] = '≦'; - map['lesssmall'] = '﹤'; - map['lezh'] = 'ɮ'; - map['lfblock'] = '▌'; - map['lhookretroflex'] = 'ɭ'; - map['lira'] = '₤'; - map['liwnarmenian'] = 'լ'; - map['lj'] = 'lj'; - map['ljecyrillic'] = 'љ'; - map['ll'] = ''; - map['lladeva'] = 'ळ'; - map['llagujarati'] = 'ળ'; - map['llinebelow'] = 'ḻ'; - map['llladeva'] = 'ऴ'; - map['llvocalicbengali'] = 'ৡ'; - map['llvocalicdeva'] = 'ॡ'; - map['llvocalicvowelsignbengali'] = 'ৣ'; - map['llvocalicvowelsigndeva'] = 'ॣ'; - map['lmiddletilde'] = 'ɫ'; - map['lmonospace'] = 'l'; - map['lmsquare'] = '㏐'; - map['lochulathai'] = 'ฬ'; - map['logicaland'] = '∧'; - map['logicalnot'] = '¬'; - map['logicalnotreversed'] = '⌐'; - map['logicalor'] = '∨'; - map['lolingthai'] = 'ล'; - map['longs'] = 'ſ'; - map['lowlinecenterline'] = '﹎'; - map['lowlinecmb'] = '̲'; - map['lowlinedashed'] = '﹍'; - map['lozenge'] = '◊'; - map['lparen'] = '⒧'; - map['lslash'] = 'ł'; - map['lsquare'] = 'ℓ'; - map['lsuperior'] = ''; - map['ltshade'] = '░'; - map['luthai'] = 'ฦ'; - map['lvocalicbengali'] = 'ঌ'; - map['lvocalicdeva'] = 'ऌ'; - map['lvocalicvowelsignbengali'] = 'ৢ'; - map['lvocalicvowelsigndeva'] = 'ॢ'; - map['lxsquare'] = '㏓'; - map['m'] = 'm'; - map['mabengali'] = 'ম'; - map['macron'] = '¯'; - map['macronbelowcmb'] = '̱'; - map['macroncmb'] = '̄'; - map['macronlowmod'] = 'ˍ'; - map['macronmonospace'] = ' ̄'; - map['macute'] = 'ḿ'; - map['madeva'] = 'म'; - map['magujarati'] = 'મ'; - map['magurmukhi'] = 'ਮ'; - map['mahapakhhebrew'] = '֤'; - map['mahapakhlefthebrew'] = '֤'; - map['mahiragana'] = 'ま'; - map['maichattawalowleftthai'] = ''; - map['maichattawalowrightthai'] = ''; - map['maichattawathai'] = '๋'; - map['maichattawaupperleftthai'] = ''; - map['maieklowleftthai'] = ''; - map['maieklowrightthai'] = ''; - map['maiekthai'] = '่'; - map['maiekupperleftthai'] = ''; - map['maihanakatleftthai'] = ''; - map['maihanakatthai'] = 'ั'; - map['maitaikhuleftthai'] = ''; - map['maitaikhuthai'] = '็'; - map['maitholowleftthai'] = ''; - map['maitholowrightthai'] = ''; - map['maithothai'] = '้'; - map['maithoupperleftthai'] = ''; - map['maitrilowleftthai'] = ''; - map['maitrilowrightthai'] = ''; - map['maitrithai'] = '๊'; - map['maitriupperleftthai'] = ''; - map['maiyamokthai'] = 'ๆ'; - map['makatakana'] = 'マ'; - map['makatakanahalfwidth'] = 'マ'; - map['male'] = '♂'; - map['mansyonsquare'] = '㍇'; - map['maqafhebrew'] = '־'; - map['mars'] = '♂'; - map['masoracirclehebrew'] = '֯'; - map['masquare'] = '㎃'; - map['mbopomofo'] = 'ㄇ'; - map['mbsquare'] = '㏔'; - map['mcircle'] = 'ⓜ'; - map['mcubedsquare'] = '㎥'; - map['mdotaccent'] = 'ṁ'; - map['mdotbelow'] = 'ṃ'; - map['meemarabic'] = 'م'; - map['meemfinalarabic'] = 'ﻢ'; - map['meeminitialarabic'] = 'ﻣ'; - map['meemmedialarabic'] = 'ﻤ'; - map['meemmeeminitialarabic'] = 'ﳑ'; - map['meemmeemisolatedarabic'] = 'ﱈ'; - map['meetorusquare'] = '㍍'; - map['mehiragana'] = 'め'; - map['meizierasquare'] = '㍾'; - map['mekatakana'] = 'メ'; - map['mekatakanahalfwidth'] = 'メ'; - map['mem'] = 'מ'; - map['memdagesh'] = 'מּ'; - map['memdageshhebrew'] = 'מּ'; - map['memhebrew'] = 'מ'; - map['menarmenian'] = 'մ'; - map['merkhahebrew'] = '֥'; - map['merkhakefulahebrew'] = '֦'; - map['merkhakefulalefthebrew'] = '֦'; - map['merkhalefthebrew'] = '֥'; - map['mhook'] = 'ɱ'; - map['mhzsquare'] = '㎒'; - map['middledotkatakanahalfwidth'] = '・'; - map['middot'] = '·'; - map['mieumacirclekorean'] = '㉲'; - map['mieumaparenkorean'] = '㈒'; - map['mieumcirclekorean'] = '㉤'; - map['mieumkorean'] = 'ㅁ'; - map['mieumpansioskorean'] = 'ㅰ'; - map['mieumparenkorean'] = '㈄'; - map['mieumpieupkorean'] = 'ㅮ'; - map['mieumsioskorean'] = 'ㅯ'; - map['mihiragana'] = 'み'; - map['mikatakana'] = 'ミ'; - map['mikatakanahalfwidth'] = 'ミ'; - map['negationslash'] = '-'; - map['minus'] = '−'; - map['minusbelowcmb'] = '̠'; - map['minuscircle'] = '⊖'; - map['minusmod'] = '˗'; - map['minusplus'] = '∓'; - map['minute'] = '′'; - map['miribaarusquare'] = '㍊'; - map['mirisquare'] = '㍉'; - map['mlonglegturned'] = 'ɰ'; - map['mlsquare'] = '㎖'; - map['mmcubedsquare'] = '㎣'; - map['mmonospace'] = 'm'; - map['mmsquaredsquare'] = '㎟'; - map['mohiragana'] = 'も'; - map['mohmsquare'] = '㏁'; - map['mokatakana'] = 'モ'; - map['mokatakanahalfwidth'] = 'モ'; - map['molsquare'] = '㏖'; - map['momathai'] = 'ม'; - map['moverssquare'] = '㎧'; - map['moverssquaredsquare'] = '㎨'; - map['mparen'] = '⒨'; - map['mpasquare'] = '㎫'; - map['mssquare'] = '㎳'; - map['msuperior'] = ''; - map['mturned'] = 'ɯ'; - map['mu'] = 'µ'; - map['mu1'] = 'µ'; - map['muasquare'] = '㎂'; - map['muchgreater'] = '≫'; - map['muchless'] = '≪'; - map['mufsquare'] = '㎌'; - map['mugreek'] = 'μ'; - map['mugsquare'] = '㎍'; - map['muhiragana'] = 'む'; - map['mukatakana'] = 'ム'; - map['mukatakanahalfwidth'] = 'ム'; - map['mulsquare'] = '㎕'; - map['multiply'] = '×'; - map['mumsquare'] = '㎛'; - map['munahhebrew'] = '֣'; - map['munahlefthebrew'] = '֣'; - map['musicalnote'] = '♪'; - map['musicalnotedbl'] = '♫'; - map['musicflatsign'] = '♭'; - map['musicsharpsign'] = '♯'; - map['mussquare'] = '㎲'; - map['muvsquare'] = '㎶'; - map['muwsquare'] = '㎼'; - map['mvmegasquare'] = '㎹'; - map['mvsquare'] = '㎷'; - map['mwmegasquare'] = '㎿'; - map['mwsquare'] = '㎽'; - map['n'] = 'n'; - map['nabengali'] = 'ন'; - map['nabla'] = '∇'; - map['nacute'] = 'ń'; - map['nadeva'] = 'न'; - map['nagujarati'] = 'ન'; - map['nagurmukhi'] = 'ਨ'; - map['nahiragana'] = 'な'; - map['nakatakana'] = 'ナ'; - map['nakatakanahalfwidth'] = 'ナ'; - map['napostrophe'] = 'ʼn'; - map['nasquare'] = '㎁'; - map['nbopomofo'] = 'ㄋ'; - map['nbspace'] = '\u00a0'; - map['ncaron'] = 'ň'; - map['ncedilla'] = 'ņ'; - map['ncircle'] = 'ⓝ'; - map['ncircumflexbelow'] = 'ṋ'; - map['ncommaaccent'] = 'ņ'; - map['ndotaccent'] = 'ṅ'; - map['ndotbelow'] = 'ṇ'; - map['nehiragana'] = 'ね'; - map['nekatakana'] = 'ネ'; - map['nekatakanahalfwidth'] = 'ネ'; - map['newsheqelsign'] = '₪'; - map['nfsquare'] = '㎋'; - map['ngabengali'] = 'ঙ'; - map['ngadeva'] = 'ङ'; - map['ngagujarati'] = 'ઙ'; - map['ngagurmukhi'] = 'ਙ'; - map['ngonguthai'] = 'ง'; - map['nhiragana'] = 'ん'; - map['nhookleft'] = 'ɲ'; - map['nhookretroflex'] = 'ɳ'; - map['nieunacirclekorean'] = '㉯'; - map['nieunaparenkorean'] = '㈏'; - map['nieuncieuckorean'] = 'ㄵ'; - map['nieuncirclekorean'] = '㉡'; - map['nieunhieuhkorean'] = 'ㄶ'; - map['nieunkorean'] = 'ㄴ'; - map['nieunpansioskorean'] = 'ㅨ'; - map['nieunparenkorean'] = '㈁'; - map['nieunsioskorean'] = 'ㅧ'; - map['nieuntikeutkorean'] = 'ㅦ'; - map['nihiragana'] = 'に'; - map['nikatakana'] = 'ニ'; - map['nikatakanahalfwidth'] = 'ニ'; - map['nikhahitleftthai'] = ''; - map['nikhahitthai'] = 'ํ'; - map['nine'] = '9'; - map['ninearabic'] = '٩'; - map['ninebengali'] = '৯'; - map['ninecircle'] = '⑨'; - map['ninecircleinversesansserif'] = '➒'; - map['ninedeva'] = '९'; - map['ninegujarati'] = '૯'; - map['ninegurmukhi'] = '੯'; - map['ninehackarabic'] = '٩'; - map['ninehangzhou'] = '〩'; - map['nineideographicparen'] = '㈨'; - map['nineinferior'] = '₉'; - map['ninemonospace'] = '9'; - map['nineoldstyle'] = ''; - map['nineparen'] = '⑼'; - map['nineperiod'] = '⒐'; - map['ninepersian'] = '۹'; - map['nineroman'] = 'ⅸ'; - map['ninesuperior'] = '⁹'; - map['nineteencircle'] = '⑲'; - map['nineteenparen'] = '⒆'; - map['nineteenperiod'] = '⒚'; - map['ninethai'] = '๙'; - map['nj'] = 'nj'; - map['njecyrillic'] = 'њ'; - map['nkatakana'] = 'ン'; - map['nkatakanahalfwidth'] = 'ン'; - map['nlegrightlong'] = 'ƞ'; - map['nlinebelow'] = 'ṉ'; - map['nmonospace'] = 'n'; - map['nmsquare'] = '㎚'; - map['nnabengali'] = 'ণ'; - map['nnadeva'] = 'ण'; - map['nnagujarati'] = 'ણ'; - map['nnagurmukhi'] = 'ਣ'; - map['nnnadeva'] = 'ऩ'; - map['nohiragana'] = 'の'; - map['nokatakana'] = 'ノ'; - map['nokatakanahalfwidth'] = 'ノ'; - map['nonbreakingspace'] = '\u00a0'; - map['nonenthai'] = 'ณ'; - map['nonuthai'] = 'น'; - map['noonarabic'] = 'ن'; - map['noonfinalarabic'] = 'ﻦ'; - map['noonghunnaarabic'] = 'ں'; - map['noonghunnafinalarabic'] = 'ﮟ'; - map['nooninitialarabic'] = 'ﻧ'; - map['noonjeeminitialarabic'] = 'ﳒ'; - map['noonjeemisolatedarabic'] = 'ﱋ'; - map['noonmedialarabic'] = 'ﻨ'; - map['noonmeeminitialarabic'] = 'ﳕ'; - map['noonmeemisolatedarabic'] = 'ﱎ'; - map['noonnoonfinalarabic'] = 'ﲍ'; - map['notcontains'] = '∌'; - map['notelement'] = '∉'; - map['notelementof'] = '∉'; - map['notequal'] = '≠'; - map['notgreater'] = '≯'; - map['notgreaternorequal'] = '≱'; - map['notgreaternorless'] = '≹'; - map['notidentical'] = '≢'; - map['notless'] = '≮'; - map['notlessnorequal'] = '≰'; - map['notparallel'] = '∦'; - map['notprecedes'] = '⊀'; - map['notsubset'] = '⊄'; - map['notsucceeds'] = '⊁'; - map['notsuperset'] = '⊅'; - map['nowarmenian'] = 'ն'; - map['nparen'] = '⒩'; - map['nssquare'] = '㎱'; - map['nsuperior'] = 'ⁿ'; - map['ntilde'] = 'ñ'; - map['nu'] = 'ν'; - map['nuhiragana'] = 'ぬ'; - map['nukatakana'] = 'ヌ'; - map['nukatakanahalfwidth'] = 'ヌ'; - map['nuktabengali'] = '়'; - map['nuktadeva'] = '़'; - map['nuktagujarati'] = '઼'; - map['nuktagurmukhi'] = '਼'; - map['numbersign'] = '#'; - map['numbersignmonospace'] = '#'; - map['numbersignsmall'] = '﹟'; - map['numeralsigngreek'] = 'ʹ'; - map['numeralsignlowergreek'] = '͵'; - map['numero'] = '№'; - map['nun'] = 'נ'; - map['nundagesh'] = 'נּ'; - map['nundageshhebrew'] = 'נּ'; - map['nunhebrew'] = 'נ'; - map['nvsquare'] = '㎵'; - map['nwsquare'] = '㎻'; - map['nyabengali'] = 'ঞ'; - map['nyadeva'] = 'ञ'; - map['nyagujarati'] = 'ઞ'; - map['nyagurmukhi'] = 'ਞ'; - map['o'] = 'o'; - map['oacute'] = 'ó'; - map['oangthai'] = 'อ'; - map['obarred'] = 'ɵ'; - map['obarredcyrillic'] = 'ө'; - map['obarreddieresiscyrillic'] = 'ӫ'; - map['obengali'] = 'ও'; - map['obopomofo'] = 'ㄛ'; - map['obreve'] = 'ŏ'; - map['ocandradeva'] = 'ऑ'; - map['ocandragujarati'] = 'ઑ'; - map['ocandravowelsigndeva'] = 'ॉ'; - map['ocandravowelsigngujarati'] = 'ૉ'; - map['ocaron'] = 'ǒ'; - map['ocircle'] = 'ⓞ'; - map['ocircumflex'] = 'ô'; - map['ocircumflexacute'] = 'ố'; - map['ocircumflexdotbelow'] = 'ộ'; - map['ocircumflexgrave'] = 'ồ'; - map['ocircumflexhookabove'] = 'ổ'; - map['ocircumflextilde'] = 'ỗ'; - map['ocyrillic'] = 'о'; - map['odblacute'] = 'ő'; - map['odblgrave'] = 'ȍ'; - map['odeva'] = 'ओ'; - map['odieresis'] = 'ö'; - map['odieresiscyrillic'] = 'ӧ'; - map['odotbelow'] = 'ọ'; - map['oe'] = 'œ'; - map['oekorean'] = 'ㅚ'; - map['ogonek'] = '˛'; - map['ogonekcmb'] = '̨'; - map['ograve'] = 'ò'; - map['ogujarati'] = 'ઓ'; - map['oharmenian'] = 'օ'; - map['ohiragana'] = 'お'; - map['ohookabove'] = 'ỏ'; - map['ohorn'] = 'ơ'; - map['ohornacute'] = 'ớ'; - map['ohorndotbelow'] = 'ợ'; - map['ohorngrave'] = 'ờ'; - map['ohornhookabove'] = 'ở'; - map['ohorntilde'] = 'ỡ'; - map['ohungarumlaut'] = 'ő'; - map['oi'] = 'ƣ'; - map['oinvertedbreve'] = 'ȏ'; - map['okatakana'] = 'オ'; - map['okatakanahalfwidth'] = 'オ'; - map['okorean'] = 'ㅗ'; - map['olehebrew'] = '֫'; - map['omacron'] = 'ō'; - map['omacronacute'] = 'ṓ'; - map['omacrongrave'] = 'ṑ'; - map['omdeva'] = 'ॐ'; - map['omega'] = 'ω'; - map['omega1'] = 'ϖ'; - map['omegacyrillic'] = 'ѡ'; - map['omegalatinclosed'] = 'ɷ'; - map['omegaroundcyrillic'] = 'ѻ'; - map['omegatitlocyrillic'] = 'ѽ'; - map['omegatonos'] = 'ώ'; - map['omgujarati'] = 'ૐ'; - map['omicron'] = 'ο'; - map['omicrontonos'] = 'ό'; - map['omonospace'] = 'o'; - map['one'] = '1'; - map['onearabic'] = '١'; - map['onebengali'] = '১'; - map['onecircle'] = '①'; - map['onecircleinversesansserif'] = '➊'; - map['onedeva'] = '१'; - map['onedotenleader'] = '․'; - map['oneeighth'] = '⅛'; - map['onefitted'] = ''; - map['onegujarati'] = '૧'; - map['onegurmukhi'] = '੧'; - map['onehackarabic'] = '١'; - map['onehalf'] = '½'; - map['onehangzhou'] = '〡'; - map['oneideographicparen'] = '㈠'; - map['oneinferior'] = '₁'; - map['onemonospace'] = '1'; - map['onenumeratorbengali'] = '৴'; - map['oneoldstyle'] = ''; - map['oneparen'] = '⑴'; - map['oneperiod'] = '⒈'; - map['onepersian'] = '۱'; - map['onequarter'] = '¼'; - map['oneroman'] = 'ⅰ'; - map['onesuperior'] = '¹'; - map['onethai'] = '๑'; - map['onethird'] = '⅓'; - map['oogonek'] = 'ǫ'; - map['oogonekmacron'] = 'ǭ'; - map['oogurmukhi'] = 'ਓ'; - map['oomatragurmukhi'] = 'ੋ'; - map['oopen'] = 'ɔ'; - map['oparen'] = '⒪'; - map['openbullet'] = '◦'; - map['option'] = '⌥'; - map['ordfeminine'] = 'ª'; - map['ordmasculine'] = 'º'; - map['orthogonal'] = '∟'; - map['oshortdeva'] = 'ऒ'; - map['oshortvowelsigndeva'] = 'ॊ'; - map['oslash'] = 'ø'; - map['oslashacute'] = 'ǿ'; - map['osmallhiragana'] = 'ぉ'; - map['osmallkatakana'] = 'ォ'; - map['osmallkatakanahalfwidth'] = 'ォ'; - map['ostrokeacute'] = 'ǿ'; - map['osuperior'] = ''; - map['otcyrillic'] = 'ѿ'; - map['otilde'] = 'õ'; - map['otildeacute'] = 'ṍ'; - map['otildedieresis'] = 'ṏ'; - map['oubopomofo'] = 'ㄡ'; - map['overline'] = '‾'; - map['overlinecenterline'] = '﹊'; - map['overlinecmb'] = '̅'; - map['overlinedashed'] = '﹉'; - map['overlinedblwavy'] = '﹌'; - map['overlinewavy'] = '﹋'; - map['overscore'] = '¯'; - map['ovowelsignbengali'] = 'ো'; - map['ovowelsigndeva'] = 'ो'; - map['ovowelsigngujarati'] = 'ો'; - map['p'] = 'p'; - map['paampssquare'] = '㎀'; - map['paasentosquare'] = '㌫'; - map['pabengali'] = 'প'; - map['pacute'] = 'ṕ'; - map['padeva'] = 'प'; - map['pagedown'] = '⇟'; - map['pageup'] = '⇞'; - map['pagujarati'] = 'પ'; - map['pagurmukhi'] = 'ਪ'; - map['pahiragana'] = 'ぱ'; - map['paiyannoithai'] = 'ฯ'; - map['pakatakana'] = 'パ'; - map['palatalizationcyrilliccmb'] = '҄'; - map['palochkacyrillic'] = 'Ӏ'; - map['pansioskorean'] = 'ㅿ'; - map['paragraph'] = '¶'; - map['parallel'] = '∥'; - map['parenleft'] = '('; - map['parenleftaltonearabic'] = '﴾'; - map['parenleftbt'] = ''; - map['parenleftex'] = ''; - map['parenleftinferior'] = '₍'; - map['parenleftmonospace'] = '('; - map['parenleftsmall'] = '﹙'; - map['parenleftsuperior'] = '⁽'; - map['parenlefttp'] = ''; - map['parenleftvertical'] = '︵'; - map['parenright'] = ')'; - map['parenrightaltonearabic'] = '﴿'; - map['parenrightbt'] = ''; - map['parenrightex'] = ''; - map['parenrightinferior'] = '₎'; - map['parenrightmonospace'] = ')'; - map['parenrightsmall'] = '﹚'; - map['parenrightsuperior'] = '⁾'; - map['parenrighttp'] = ''; - map['parenrightvertical'] = '︶'; - map['partialdiff'] = '∂'; - map['paseqhebrew'] = '׀'; - map['pashtahebrew'] = '֙'; - map['pasquare'] = '㎩'; - map['patah'] = 'ַ'; - map['patah11'] = 'ַ'; - map['patah1d'] = 'ַ'; - map['patah2a'] = 'ַ'; - map['patahhebrew'] = 'ַ'; - map['patahnarrowhebrew'] = 'ַ'; - map['patahquarterhebrew'] = 'ַ'; - map['patahwidehebrew'] = 'ַ'; - map['pazerhebrew'] = '֡'; - map['pbopomofo'] = 'ㄆ'; - map['pcircle'] = 'ⓟ'; - map['pdotaccent'] = 'ṗ'; - map['pe'] = 'פ'; - map['pecyrillic'] = 'п'; - map['pedagesh'] = 'פּ'; - map['pedageshhebrew'] = 'פּ'; - map['peezisquare'] = '㌻'; - map['pefinaldageshhebrew'] = 'ףּ'; - map['peharabic'] = 'پ'; - map['peharmenian'] = 'պ'; - map['pehebrew'] = 'פ'; - map['pehfinalarabic'] = 'ﭗ'; - map['pehinitialarabic'] = 'ﭘ'; - map['pehiragana'] = 'ぺ'; - map['pehmedialarabic'] = 'ﭙ'; - map['pekatakana'] = 'ペ'; - map['pemiddlehookcyrillic'] = 'ҧ'; - map['perafehebrew'] = 'פֿ'; - map['percent'] = '%'; - map['percentarabic'] = '٪'; - map['percentmonospace'] = '%'; - map['percentsmall'] = '﹪'; - map['period'] = '.'; - map['periodarmenian'] = '։'; - map['periodcentered'] = '·'; - map['periodhalfwidth'] = '。'; - map['periodinferior'] = ''; - map['periodmonospace'] = '.'; - map['periodsmall'] = '﹒'; - map['periodsuperior'] = ''; - map['perispomenigreekcmb'] = '͂'; - map['perpendicular'] = '⊥'; - map['perthousand'] = '‰'; - map['peseta'] = '₧'; - map['pfsquare'] = '㎊'; - map['phabengali'] = 'ফ'; - map['phadeva'] = 'फ'; - map['phagujarati'] = 'ફ'; - map['phagurmukhi'] = 'ਫ'; - map['phi'] = 'φ'; - map['phi1'] = 'ϕ'; - map['phieuphacirclekorean'] = '㉺'; - map['phieuphaparenkorean'] = '㈚'; - map['phieuphcirclekorean'] = '㉬'; - map['phieuphkorean'] = 'ㅍ'; - map['phieuphparenkorean'] = '㈌'; - map['philatin'] = 'ɸ'; - map['phinthuthai'] = 'ฺ'; - map['phisymbolgreek'] = 'ϕ'; - map['phook'] = 'ƥ'; - map['phophanthai'] = 'พ'; - map['phophungthai'] = 'ผ'; - map['phosamphaothai'] = 'ภ'; - map['pi'] = 'π'; - map['pieupacirclekorean'] = '㉳'; - map['pieupaparenkorean'] = '㈓'; - map['pieupcieuckorean'] = 'ㅶ'; - map['pieupcirclekorean'] = '㉥'; - map['pieupkiyeokkorean'] = 'ㅲ'; - map['pieupkorean'] = 'ㅂ'; - map['pieupparenkorean'] = '㈅'; - map['pieupsioskiyeokkorean'] = 'ㅴ'; - map['pieupsioskorean'] = 'ㅄ'; - map['pieupsiostikeutkorean'] = 'ㅵ'; - map['pieupthieuthkorean'] = 'ㅷ'; - map['pieuptikeutkorean'] = 'ㅳ'; - map['pihiragana'] = 'ぴ'; - map['pikatakana'] = 'ピ'; - map['pisymbolgreek'] = 'ϖ'; - map['piwrarmenian'] = 'փ'; - map['plus'] = '+'; - map['plusbelowcmb'] = '̟'; - map['pluscircle'] = '⊕'; - map['plusminus'] = '±'; - map['plusmod'] = '˖'; - map['plusmonospace'] = '+'; - map['plussmall'] = '﹢'; - map['plussuperior'] = '⁺'; - map['pmonospace'] = 'p'; - map['pmsquare'] = '㏘'; - map['pohiragana'] = 'ぽ'; - map['pointingindexdownwhite'] = '☟'; - map['pointingindexleftwhite'] = '☜'; - map['pointingindexrightwhite'] = '☞'; - map['pointingindexupwhite'] = '☝'; - map['pokatakana'] = 'ポ'; - map['poplathai'] = 'ป'; - map['postalmark'] = '〒'; - map['postalmarkface'] = '〠'; - map['pparen'] = '⒫'; - map['precedes'] = '≺'; - map['prescription'] = '℞'; - map['primemod'] = 'ʹ'; - map['primereversed'] = '‵'; - map['product'] = '∏'; - map['projective'] = '⌅'; - map['prolongedkana'] = 'ー'; - map['propellor'] = '⌘'; - map['propersubset'] = '⊂'; - map['propersuperset'] = '⊃'; - map['proportion'] = '∷'; - map['proportional'] = '∝'; - map['psi'] = 'ψ'; - map['psicyrillic'] = 'ѱ'; - map['psilipneumatacyrilliccmb'] = '҆'; - map['pssquare'] = '㎰'; - map['puhiragana'] = 'ぷ'; - map['pukatakana'] = 'プ'; - map['pvsquare'] = '㎴'; - map['pwsquare'] = '㎺'; - map['q'] = 'q'; - map['qadeva'] = 'क़'; - map['qadmahebrew'] = '֨'; - map['qafarabic'] = 'ق'; - map['qaffinalarabic'] = 'ﻖ'; - map['qafinitialarabic'] = 'ﻗ'; - map['qafmedialarabic'] = 'ﻘ'; - map['qamats'] = 'ָ'; - map['qamats10'] = 'ָ'; - map['qamats1a'] = 'ָ'; - map['qamats1c'] = 'ָ'; - map['qamats27'] = 'ָ'; - map['qamats29'] = 'ָ'; - map['qamats33'] = 'ָ'; - map['qamatsde'] = 'ָ'; - map['qamatshebrew'] = 'ָ'; - map['qamatsnarrowhebrew'] = 'ָ'; - map['qamatsqatanhebrew'] = 'ָ'; - map['qamatsqatannarrowhebrew'] = 'ָ'; - map['qamatsqatanquarterhebrew'] = 'ָ'; - map['qamatsqatanwidehebrew'] = 'ָ'; - map['qamatsquarterhebrew'] = 'ָ'; - map['qamatswidehebrew'] = 'ָ'; - map['qarneyparahebrew'] = '֟'; - map['qbopomofo'] = 'ㄑ'; - map['qcircle'] = 'ⓠ'; - map['qhook'] = 'ʠ'; - map['qmonospace'] = 'q'; - map['qof'] = 'ק'; - map['qofdagesh'] = 'קּ'; - map['qofdageshhebrew'] = 'קּ'; - map['qparen'] = '⒬'; - map['quarternote'] = '♩'; - map['qubuts'] = 'ֻ'; - map['qubuts18'] = 'ֻ'; - map['qubuts25'] = 'ֻ'; - map['qubuts31'] = 'ֻ'; - map['qubutshebrew'] = 'ֻ'; - map['qubutsnarrowhebrew'] = 'ֻ'; - map['qubutsquarterhebrew'] = 'ֻ'; - map['qubutswidehebrew'] = 'ֻ'; - map['question'] = '?'; - map['questionarabic'] = '؟'; - map['questionarmenian'] = '՞'; - map['questiondown'] = '¿'; - map['questiondownsmall'] = ''; - map['questiongreek'] = ';'; - map['questionmonospace'] = '?'; - map['questionsmall'] = ''; - map['quotedbl'] = '"'; - map['quotedblbase'] = '„'; - map['quotedblleft'] = '“'; - map['quotedblmonospace'] = '"'; - map['quotedblprime'] = '〞'; - map['quotedblprimereversed'] = '〝'; - map['quotedblright'] = '”'; - map['quoteleft'] = '‘'; - map['quoteleftreversed'] = '‛'; - map['quotereversed'] = '‛'; - map['quoteright'] = '’'; - map['quoterightn'] = 'ʼn'; - map['quotesinglbase'] = '‚'; - map['quotesingle'] = '\''; - map['quotesinglemonospace'] = '''; - map['r'] = 'r'; - map['raarmenian'] = 'ռ'; - map['rabengali'] = 'র'; - map['racute'] = 'ŕ'; - map['radeva'] = 'र'; - map['radical'] = '√'; - map['radicalex'] = ''; - map['radoverssquare'] = '㎮'; - map['radoverssquaredsquare'] = '㎯'; - map['radsquare'] = '㎭'; - map['rafe'] = 'ֿ'; - map['rafehebrew'] = 'ֿ'; - map['ragujarati'] = 'ર'; - map['ragurmukhi'] = 'ਰ'; - map['rahiragana'] = 'ら'; - map['rakatakana'] = 'ラ'; - map['rakatakanahalfwidth'] = 'ラ'; - map['ralowerdiagonalbengali'] = 'ৱ'; - map['ramiddlediagonalbengali'] = 'ৰ'; - map['ramshorn'] = 'ɤ'; - map['ratio'] = '∶'; - map['rbopomofo'] = 'ㄖ'; - map['rcaron'] = 'ř'; - map['rcedilla'] = 'ŗ'; - map['rcircle'] = 'ⓡ'; - map['rcommaaccent'] = 'ŗ'; - map['rdblgrave'] = 'ȑ'; - map['rdotaccent'] = 'ṙ'; - map['rdotbelow'] = 'ṛ'; - map['rdotbelowmacron'] = 'ṝ'; - map['referencemark'] = '※'; - map['reflexsubset'] = '⊆'; - map['reflexsuperset'] = '⊇'; - map['registered'] = '®'; - map['registersans'] = ''; - map['registerserif'] = ''; - map['reharabic'] = 'ر'; - map['reharmenian'] = 'ր'; - map['rehfinalarabic'] = 'ﺮ'; - map['rehiragana'] = 'れ'; - map['rekatakana'] = 'レ'; - map['rekatakanahalfwidth'] = 'レ'; - map['resh'] = 'ר'; - map['reshdageshhebrew'] = 'רּ'; - map['reshhebrew'] = 'ר'; - map['reversedtilde'] = '∽'; - map['reviahebrew'] = '֗'; - map['reviamugrashhebrew'] = '֗'; - map['revlogicalnot'] = '⌐'; - map['rfishhook'] = 'ɾ'; - map['rfishhookreversed'] = 'ɿ'; - map['rhabengali'] = 'ঢ়'; - map['rhadeva'] = 'ढ़'; - map['rho'] = 'ρ'; - map['rhook'] = 'ɽ'; - map['rhookturned'] = 'ɻ'; - map['rhookturnedsuperior'] = 'ʵ'; - map['rhosymbolgreek'] = 'ϱ'; - map['rhotichookmod'] = '˞'; - map['rieulacirclekorean'] = '㉱'; - map['rieulaparenkorean'] = '㈑'; - map['rieulcirclekorean'] = '㉣'; - map['rieulhieuhkorean'] = 'ㅀ'; - map['rieulkiyeokkorean'] = 'ㄺ'; - map['rieulkiyeoksioskorean'] = 'ㅩ'; - map['rieulkorean'] = 'ㄹ'; - map['rieulmieumkorean'] = 'ㄻ'; - map['rieulpansioskorean'] = 'ㅬ'; - map['rieulparenkorean'] = '㈃'; - map['rieulphieuphkorean'] = 'ㄿ'; - map['rieulpieupkorean'] = 'ㄼ'; - map['rieulpieupsioskorean'] = 'ㅫ'; - map['rieulsioskorean'] = 'ㄽ'; - map['rieulthieuthkorean'] = 'ㄾ'; - map['rieultikeutkorean'] = 'ㅪ'; - map['rieulyeorinhieuhkorean'] = 'ㅭ'; - map['rightangle'] = '∟'; - map['righttackbelowcmb'] = '̙'; - map['righttriangle'] = '⊿'; - map['rihiragana'] = 'り'; - map['rikatakana'] = 'リ'; - map['rikatakanahalfwidth'] = 'リ'; - map['ring'] = '˚'; - map['ringbelowcmb'] = '̥'; - map['ringcmb'] = '̊'; - map['ringhalfleft'] = 'ʿ'; - map['ringhalfleftarmenian'] = 'ՙ'; - map['ringhalfleftbelowcmb'] = '̜'; - map['ringhalfleftcentered'] = '˓'; - map['ringhalfright'] = 'ʾ'; - map['ringhalfrightbelowcmb'] = '̹'; - map['ringhalfrightcentered'] = '˒'; - map['rinvertedbreve'] = 'ȓ'; - map['rittorusquare'] = '㍑'; - map['rlinebelow'] = 'ṟ'; - map['rlongleg'] = 'ɼ'; - map['rlonglegturned'] = 'ɺ'; - map['rmonospace'] = 'r'; - map['rohiragana'] = 'ろ'; - map['rokatakana'] = 'ロ'; - map['rokatakanahalfwidth'] = 'ロ'; - map['roruathai'] = 'ร'; - map['rparen'] = '⒭'; - map['rrabengali'] = 'ড়'; - map['rradeva'] = 'ऱ'; - map['rragurmukhi'] = 'ੜ'; - map['rreharabic'] = 'ڑ'; - map['rrehfinalarabic'] = 'ﮍ'; - map['rrvocalicbengali'] = 'ৠ'; - map['rrvocalicdeva'] = 'ॠ'; - map['rrvocalicgujarati'] = 'ૠ'; - map['rrvocalicvowelsignbengali'] = 'ৄ'; - map['rrvocalicvowelsigndeva'] = 'ॄ'; - map['rrvocalicvowelsigngujarati'] = 'ૄ'; - map['rsuperior'] = ''; - map['rtblock'] = '▐'; - map['rturned'] = 'ɹ'; - map['rturnedsuperior'] = 'ʴ'; - map['ruhiragana'] = 'る'; - map['rukatakana'] = 'ル'; - map['rukatakanahalfwidth'] = 'ル'; - map['rupeemarkbengali'] = '৲'; - map['rupeesignbengali'] = '৳'; - map['rupiah'] = ''; - map['ruthai'] = 'ฤ'; - map['rvocalicbengali'] = 'ঋ'; - map['rvocalicdeva'] = 'ऋ'; - map['rvocalicgujarati'] = 'ઋ'; - map['rvocalicvowelsignbengali'] = 'ৃ'; - map['rvocalicvowelsigndeva'] = 'ृ'; - map['rvocalicvowelsigngujarati'] = 'ૃ'; - map['s'] = 's'; - map['sabengali'] = 'স'; - map['sacute'] = 'ś'; - map['sacutedotaccent'] = 'ṥ'; - map['sadarabic'] = 'ص'; - map['sadeva'] = 'स'; - map['sadfinalarabic'] = 'ﺺ'; - map['sadinitialarabic'] = 'ﺻ'; - map['sadmedialarabic'] = 'ﺼ'; - map['sagujarati'] = 'સ'; - map['sagurmukhi'] = 'ਸ'; - map['sahiragana'] = 'さ'; - map['sakatakana'] = 'サ'; - map['sakatakanahalfwidth'] = 'サ'; - map['sallallahoualayhewasallamarabic'] = 'ﷺ'; - map['samekh'] = 'ס'; - map['samekhdagesh'] = 'סּ'; - map['samekhdageshhebrew'] = 'סּ'; - map['samekhhebrew'] = 'ס'; - map['saraaathai'] = 'า'; - map['saraaethai'] = 'แ'; - map['saraaimaimalaithai'] = 'ไ'; - map['saraaimaimuanthai'] = 'ใ'; - map['saraamthai'] = 'ำ'; - map['saraathai'] = 'ะ'; - map['saraethai'] = 'เ'; - map['saraiileftthai'] = ''; - map['saraiithai'] = 'ี'; - map['saraileftthai'] = ''; - map['saraithai'] = 'ิ'; - map['saraothai'] = 'โ'; - map['saraueeleftthai'] = ''; - map['saraueethai'] = 'ื'; - map['saraueleftthai'] = ''; - map['sarauethai'] = 'ึ'; - map['sarauthai'] = 'ุ'; - map['sarauuthai'] = 'ู'; - map['sbopomofo'] = 'ㄙ'; - map['scaron'] = 'š'; - map['scarondotaccent'] = 'ṧ'; - map['scedilla'] = 'ş'; - map['schwa'] = 'ə'; - map['schwacyrillic'] = 'ә'; - map['schwadieresiscyrillic'] = 'ӛ'; - map['schwahook'] = 'ɚ'; - map['scircle'] = 'ⓢ'; - map['scircumflex'] = 'ŝ'; - map['scommaaccent'] = 'ș'; - map['sdotaccent'] = 'ṡ'; - map['sdotbelow'] = 'ṣ'; - map['sdotbelowdotaccent'] = 'ṩ'; - map['seagullbelowcmb'] = '̼'; - map['second'] = '″'; - map['secondtonechinese'] = 'ˊ'; - map['section'] = '§'; - map['seenarabic'] = 'س'; - map['seenfinalarabic'] = 'ﺲ'; - map['seeninitialarabic'] = 'ﺳ'; - map['seenmedialarabic'] = 'ﺴ'; - map['segol'] = 'ֶ'; - map['segol13'] = 'ֶ'; - map['segol1f'] = 'ֶ'; - map['segol2c'] = 'ֶ'; - map['segolhebrew'] = 'ֶ'; - map['segolnarrowhebrew'] = 'ֶ'; - map['segolquarterhebrew'] = 'ֶ'; - map['segoltahebrew'] = '֒'; - map['segolwidehebrew'] = 'ֶ'; - map['seharmenian'] = 'ս'; - map['sehiragana'] = 'せ'; - map['sekatakana'] = 'セ'; - map['sekatakanahalfwidth'] = 'セ'; - map['semicolon'] = ';'; - map['semicolonarabic'] = '؛'; - map['semicolonmonospace'] = ';'; - map['semicolonsmall'] = '﹔'; - map['semivoicedmarkkana'] = '゜'; - map['semivoicedmarkkanahalfwidth'] = '゚'; - map['sentisquare'] = '㌢'; - map['sentosquare'] = '㌣'; - map['seven'] = '7'; - map['sevenarabic'] = '٧'; - map['sevenbengali'] = '৭'; - map['sevencircle'] = '⑦'; - map['sevencircleinversesansserif'] = '➐'; - map['sevendeva'] = '७'; - map['seveneighths'] = '⅞'; - map['sevengujarati'] = '૭'; - map['sevengurmukhi'] = '੭'; - map['sevenhackarabic'] = '٧'; - map['sevenhangzhou'] = '〧'; - map['sevenideographicparen'] = '㈦'; - map['seveninferior'] = '₇'; - map['sevenmonospace'] = '7'; - map['sevenoldstyle'] = ''; - map['sevenparen'] = '⑺'; - map['sevenperiod'] = '⒎'; - map['sevenpersian'] = '۷'; - map['sevenroman'] = 'ⅶ'; - map['sevensuperior'] = '⁷'; - map['seventeencircle'] = '⑰'; - map['seventeenparen'] = '⒄'; - map['seventeenperiod'] = '⒘'; - map['seventhai'] = '๗'; - map['sfthyphen'] = '­'; - map['shaarmenian'] = 'շ'; - map['shabengali'] = 'শ'; - map['shacyrillic'] = 'ш'; - map['shaddaarabic'] = 'ّ'; - map['shaddadammaarabic'] = 'ﱡ'; - map['shaddadammatanarabic'] = 'ﱞ'; - map['shaddafathaarabic'] = 'ﱠ'; - map['shaddakasraarabic'] = 'ﱢ'; - map['shaddakasratanarabic'] = 'ﱟ'; - map['shade'] = '▒'; - map['shadedark'] = '▓'; - map['shadelight'] = '░'; - map['shademedium'] = '▒'; - map['shadeva'] = 'श'; - map['shagujarati'] = 'શ'; - map['shagurmukhi'] = 'ਸ਼'; - map['shalshelethebrew'] = '֓'; - map['shbopomofo'] = 'ㄕ'; - map['shchacyrillic'] = 'щ'; - map['sheenarabic'] = 'ش'; - map['sheenfinalarabic'] = 'ﺶ'; - map['sheeninitialarabic'] = 'ﺷ'; - map['sheenmedialarabic'] = 'ﺸ'; - map['sheicoptic'] = 'ϣ'; - map['sheqel'] = '₪'; - map['sheqelhebrew'] = '₪'; - map['sheva'] = 'ְ'; - map['sheva115'] = 'ְ'; - map['sheva15'] = 'ְ'; - map['sheva22'] = 'ְ'; - map['sheva2e'] = 'ְ'; - map['shevahebrew'] = 'ְ'; - map['shevanarrowhebrew'] = 'ְ'; - map['shevaquarterhebrew'] = 'ְ'; - map['shevawidehebrew'] = 'ְ'; - map['shhacyrillic'] = 'һ'; - map['shimacoptic'] = 'ϭ'; - map['shin'] = 'ש'; - map['shindagesh'] = 'שּ'; - map['shindageshhebrew'] = 'שּ'; - map['shindageshshindot'] = 'שּׁ'; - map['shindageshshindothebrew'] = 'שּׁ'; - map['shindageshsindot'] = 'שּׂ'; - map['shindageshsindothebrew'] = 'שּׂ'; - map['shindothebrew'] = 'ׁ'; - map['shinhebrew'] = 'ש'; - map['shinshindot'] = 'שׁ'; - map['shinshindothebrew'] = 'שׁ'; - map['shinsindot'] = 'שׂ'; - map['shinsindothebrew'] = 'שׂ'; - map['shook'] = 'ʂ'; - map['sigma'] = 'σ'; - map['sigma1'] = 'ς'; - map['sigmafinal'] = 'ς'; - map['sigmalunatesymbolgreek'] = 'ϲ'; - map['sihiragana'] = 'し'; - map['sikatakana'] = 'シ'; - map['sikatakanahalfwidth'] = 'シ'; - map['siluqhebrew'] = 'ֽ'; - map['siluqlefthebrew'] = 'ֽ'; - map['similar'] = '∼'; - map['sindothebrew'] = 'ׂ'; - map['siosacirclekorean'] = '㉴'; - map['siosaparenkorean'] = '㈔'; - map['sioscieuckorean'] = 'ㅾ'; - map['sioscirclekorean'] = '㉦'; - map['sioskiyeokkorean'] = 'ㅺ'; - map['sioskorean'] = 'ㅅ'; - map['siosnieunkorean'] = 'ㅻ'; - map['siosparenkorean'] = '㈆'; - map['siospieupkorean'] = 'ㅽ'; - map['siostikeutkorean'] = 'ㅼ'; - map['six'] = '6'; - map['sixarabic'] = '٦'; - map['sixbengali'] = '৬'; - map['sixcircle'] = '⑥'; - map['sixcircleinversesansserif'] = '➏'; - map['sixdeva'] = '६'; - map['sixgujarati'] = '૬'; - map['sixgurmukhi'] = '੬'; - map['sixhackarabic'] = '٦'; - map['sixhangzhou'] = '〦'; - map['sixideographicparen'] = '㈥'; - map['sixinferior'] = '₆'; - map['sixmonospace'] = '6'; - map['sixoldstyle'] = ''; - map['sixparen'] = '⑹'; - map['sixperiod'] = '⒍'; - map['sixpersian'] = '۶'; - map['sixroman'] = 'ⅵ'; - map['sixsuperior'] = '⁶'; - map['sixteencircle'] = '⑯'; - map['sixteencurrencydenominatorbengali'] = '৹'; - map['sixteenparen'] = '⒃'; - map['sixteenperiod'] = '⒗'; - map['sixthai'] = '๖'; - map['slash'] = '/'; - map['slashmonospace'] = '/'; - map['slong'] = 'ſ'; - map['slongdotaccent'] = 'ẛ'; - map['smileface'] = '☺'; - map['smonospace'] = 's'; - map['sofpasuqhebrew'] = '׃'; - map['softhyphen'] = '­'; - map['softsigncyrillic'] = 'ь'; - map['sohiragana'] = 'そ'; - map['sokatakana'] = 'ソ'; - map['sokatakanahalfwidth'] = 'ソ'; - map['soliduslongoverlaycmb'] = '̸'; - map['solidusshortoverlaycmb'] = '̷'; - map['sorusithai'] = 'ษ'; - map['sosalathai'] = 'ศ'; - map['sosothai'] = 'ซ'; - map['sosuathai'] = 'ส'; - map['space'] = ' '; - map['spacehackarabic'] = ' '; - map['spade'] = '♠'; - map['spadesuitblack'] = '♠'; - map['spadesuitwhite'] = '♤'; - map['sparen'] = '⒮'; - map['squarebelowcmb'] = '̻'; - map['squarecc'] = '㏄'; - map['squarecm'] = '㎝'; - map['squarediagonalcrosshatchfill'] = '▩'; - map['squarehorizontalfill'] = '▤'; - map['squarekg'] = '㎏'; - map['squarekm'] = '㎞'; - map['squarekmcapital'] = '㏎'; - map['squareln'] = '㏑'; - map['squarelog'] = '㏒'; - map['squaremg'] = '㎎'; - map['squaremil'] = '㏕'; - map['squaremm'] = '㎜'; - map['squaremsquared'] = '㎡'; - map['squareorthogonalcrosshatchfill'] = '▦'; - map['squareupperlefttolowerrightfill'] = '▧'; - map['squareupperrighttolowerleftfill'] = '▨'; - map['squareverticalfill'] = '▥'; - map['squarewhitewithsmallblack'] = '▣'; - map['srsquare'] = '㏛'; - map['ssabengali'] = 'ষ'; - map['ssadeva'] = 'ष'; - map['ssagujarati'] = 'ષ'; - map['ssangcieuckorean'] = 'ㅉ'; - map['ssanghieuhkorean'] = 'ㆅ'; - map['ssangieungkorean'] = 'ㆀ'; - map['ssangkiyeokkorean'] = 'ㄲ'; - map['ssangnieunkorean'] = 'ㅥ'; - map['ssangpieupkorean'] = 'ㅃ'; - map['ssangsioskorean'] = 'ㅆ'; - map['ssangtikeutkorean'] = 'ㄸ'; - map['ssuperior'] = ''; - map['sterling'] = '£'; - map['sterlingmonospace'] = '£'; - map['strokelongoverlaycmb'] = '̶'; - map['strokeshortoverlaycmb'] = '̵'; - map['subset'] = '⊂'; - map['subsetnotequal'] = '⊊'; - map['subsetorequal'] = '⊆'; - map['succeeds'] = '≻'; - map['suchthat'] = '∋'; - map['suhiragana'] = 'す'; - map['sukatakana'] = 'ス'; - map['sukatakanahalfwidth'] = 'ス'; - map['sukunarabic'] = 'ْ'; - map['summation'] = '∑'; - map['sun'] = '☼'; - map['superset'] = '⊃'; - map['supersetnotequal'] = '⊋'; - map['supersetorequal'] = '⊇'; - map['svsquare'] = '㏜'; - map['syouwaerasquare'] = '㍼'; - map['t'] = 't'; - map['tabengali'] = 'ত'; - map['tackdown'] = '⊤'; - map['tackleft'] = '⊣'; - map['tadeva'] = 'त'; - map['tagujarati'] = 'ત'; - map['tagurmukhi'] = 'ਤ'; - map['taharabic'] = 'ط'; - map['tahfinalarabic'] = 'ﻂ'; - map['tahinitialarabic'] = 'ﻃ'; - map['tahiragana'] = 'た'; - map['tahmedialarabic'] = 'ﻄ'; - map['taisyouerasquare'] = '㍽'; - map['takatakana'] = 'タ'; - map['takatakanahalfwidth'] = 'タ'; - map['tatweelarabic'] = 'ـ'; - map['tau'] = 'τ'; - map['tav'] = 'ת'; - map['tavdages'] = 'תּ'; - map['tavdagesh'] = 'תּ'; - map['tavdageshhebrew'] = 'תּ'; - map['tavhebrew'] = 'ת'; - map['tbar'] = 'ŧ'; - map['tbopomofo'] = 'ㄊ'; - map['tcaron'] = 'ť'; - map['tccurl'] = 'ʨ'; - map['tcedilla'] = 'ţ'; - map['tcheharabic'] = 'چ'; - map['tchehfinalarabic'] = 'ﭻ'; - map['tchehinitialarabic'] = 'ﭼ'; - map['tchehmedialarabic'] = 'ﭽ'; - map['tcircle'] = 'ⓣ'; - map['tcircumflexbelow'] = 'ṱ'; - map['tcommaaccent'] = 'ţ'; - map['tdieresis'] = 'ẗ'; - map['tdotaccent'] = 'ṫ'; - map['tdotbelow'] = 'ṭ'; - map['tecyrillic'] = 'т'; - map['tedescendercyrillic'] = 'ҭ'; - map['teharabic'] = 'ت'; - map['tehfinalarabic'] = 'ﺖ'; - map['tehhahinitialarabic'] = 'ﲢ'; - map['tehhahisolatedarabic'] = 'ﰌ'; - map['tehinitialarabic'] = 'ﺗ'; - map['tehiragana'] = 'て'; - map['tehjeeminitialarabic'] = 'ﲡ'; - map['tehjeemisolatedarabic'] = 'ﰋ'; - map['tehmarbutaarabic'] = 'ة'; - map['tehmarbutafinalarabic'] = 'ﺔ'; - map['tehmedialarabic'] = 'ﺘ'; - map['tehmeeminitialarabic'] = 'ﲤ'; - map['tehmeemisolatedarabic'] = 'ﰎ'; - map['tehnoonfinalarabic'] = 'ﱳ'; - map['tekatakana'] = 'テ'; - map['tekatakanahalfwidth'] = 'テ'; - map['telephone'] = '℡'; - map['telephoneblack'] = '☎'; - map['telishagedolahebrew'] = '֠'; - map['telishaqetanahebrew'] = '֩'; - map['tencircle'] = '⑩'; - map['tenideographicparen'] = '㈩'; - map['tenparen'] = '⑽'; - map['tenperiod'] = '⒑'; - map['tenroman'] = 'ⅹ'; - map['tesh'] = 'ʧ'; - map['tet'] = 'ט'; - map['tetdagesh'] = 'טּ'; - map['tetdageshhebrew'] = 'טּ'; - map['tethebrew'] = 'ט'; - map['tetsecyrillic'] = 'ҵ'; - map['tevirhebrew'] = '֛'; - map['tevirlefthebrew'] = '֛'; - map['thabengali'] = 'থ'; - map['thadeva'] = 'थ'; - map['thagujarati'] = 'થ'; - map['thagurmukhi'] = 'ਥ'; - map['thalarabic'] = 'ذ'; - map['thalfinalarabic'] = 'ﺬ'; - map['thanthakhatlowleftthai'] = ''; - map['thanthakhatlowrightthai'] = ''; - map['thanthakhatthai'] = '์'; - map['thanthakhatupperleftthai'] = ''; - map['theharabic'] = 'ث'; - map['thehfinalarabic'] = 'ﺚ'; - map['thehinitialarabic'] = 'ﺛ'; - map['thehmedialarabic'] = 'ﺜ'; - map['thereexists'] = '∃'; - map['therefore'] = '∴'; - map['theta'] = 'θ'; - map['theta1'] = 'ϑ'; - map['thetasymbolgreek'] = 'ϑ'; - map['thieuthacirclekorean'] = '㉹'; - map['thieuthaparenkorean'] = '㈙'; - map['thieuthcirclekorean'] = '㉫'; - map['thieuthkorean'] = 'ㅌ'; - map['thieuthparenkorean'] = '㈋'; - map['thirteencircle'] = '⑬'; - map['thirteenparen'] = '⒀'; - map['thirteenperiod'] = '⒔'; - map['thonangmonthothai'] = 'ฑ'; - map['thook'] = 'ƭ'; - map['thophuthaothai'] = 'ฒ'; - map['thorn'] = 'þ'; - map['thothahanthai'] = 'ท'; - map['thothanthai'] = 'ฐ'; - map['thothongthai'] = 'ธ'; - map['thothungthai'] = 'ถ'; - map['thousandcyrillic'] = '҂'; - map['thousandsseparatorarabic'] = '٬'; - map['thousandsseparatorpersian'] = '٬'; - map['three'] = '3'; - map['threearabic'] = '٣'; - map['threebengali'] = '৩'; - map['threecircle'] = '③'; - map['threecircleinversesansserif'] = '➌'; - map['threedeva'] = '३'; - map['threeeighths'] = '⅜'; - map['threegujarati'] = '૩'; - map['threegurmukhi'] = '੩'; - map['threehackarabic'] = '٣'; - map['threehangzhou'] = '〣'; - map['threeideographicparen'] = '㈢'; - map['threeinferior'] = '₃'; - map['threemonospace'] = '3'; - map['threenumeratorbengali'] = '৶'; - map['threeoldstyle'] = ''; - map['threeparen'] = '⑶'; - map['threeperiod'] = '⒊'; - map['threepersian'] = '۳'; - map['threequarters'] = '¾'; - map['threequartersemdash'] = ''; - map['threeroman'] = 'ⅲ'; - map['threesuperior'] = '³'; - map['threethai'] = '๓'; - map['thzsquare'] = '㎔'; - map['tihiragana'] = 'ち'; - map['tikatakana'] = 'チ'; - map['tikatakanahalfwidth'] = 'チ'; - map['tikeutacirclekorean'] = '㉰'; - map['tikeutaparenkorean'] = '㈐'; - map['tikeutcirclekorean'] = '㉢'; - map['tikeutkorean'] = 'ㄷ'; - map['tikeutparenkorean'] = '㈂'; - map['tilde'] = '˜'; - map['tildebelowcmb'] = '̰'; - map['tildecmb'] = '̃'; - map['tildecomb'] = '̃'; - map['tildedoublecmb'] = '͠'; - map['tildeoperator'] = '∼'; - map['tildeoverlaycmb'] = '̴'; - map['tildeverticalcmb'] = '̾'; - map['timescircle'] = '⊗'; - map['tipehahebrew'] = '֖'; - map['tipehalefthebrew'] = '֖'; - map['tippigurmukhi'] = 'ੰ'; - map['titlocyrilliccmb'] = '҃'; - map['tiwnarmenian'] = 'տ'; - map['tlinebelow'] = 'ṯ'; - map['tmonospace'] = 't'; - map['toarmenian'] = 'թ'; - map['tohiragana'] = 'と'; - map['tokatakana'] = 'ト'; - map['tokatakanahalfwidth'] = 'ト'; - map['tonebarextrahighmod'] = '˥'; - map['tonebarextralowmod'] = '˩'; - map['tonebarhighmod'] = '˦'; - map['tonebarlowmod'] = '˨'; - map['tonebarmidmod'] = '˧'; - map['tonefive'] = 'ƽ'; - map['tonesix'] = 'ƅ'; - map['tonetwo'] = 'ƨ'; - map['tonos'] = '΄'; - map['tonsquare'] = '㌧'; - map['topatakthai'] = 'ฏ'; - map['tortoiseshellbracketleft'] = '〔'; - map['tortoiseshellbracketleftsmall'] = '﹝'; - map['tortoiseshellbracketleftvertical'] = '︹'; - map['tortoiseshellbracketright'] = '〕'; - map['tortoiseshellbracketrightsmall'] = '﹞'; - map['tortoiseshellbracketrightvertical'] = '︺'; - map['totaothai'] = 'ต'; - map['tpalatalhook'] = 'ƫ'; - map['tparen'] = '⒯'; - map['trademark'] = '™'; - map['trademarksans'] = ''; - map['trademarkserif'] = ''; - map['tretroflexhook'] = 'ʈ'; - map['triagdn'] = '▼'; - map['triaglf'] = '◄'; - map['triagrt'] = '►'; - map['triagup'] = '▲'; - map['ts'] = 'ʦ'; - map['tsadi'] = 'צ'; - map['tsadidagesh'] = 'צּ'; - map['tsadidageshhebrew'] = 'צּ'; - map['tsadihebrew'] = 'צ'; - map['tsecyrillic'] = 'ц'; - map['tsere'] = 'ֵ'; - map['tsere12'] = 'ֵ'; - map['tsere1e'] = 'ֵ'; - map['tsere2b'] = 'ֵ'; - map['tserehebrew'] = 'ֵ'; - map['tserenarrowhebrew'] = 'ֵ'; - map['tserequarterhebrew'] = 'ֵ'; - map['tserewidehebrew'] = 'ֵ'; - map['tshecyrillic'] = 'ћ'; - map['tsuperior'] = ''; - map['ttabengali'] = 'ট'; - map['ttadeva'] = 'ट'; - map['ttagujarati'] = 'ટ'; - map['ttagurmukhi'] = 'ਟ'; - map['tteharabic'] = 'ٹ'; - map['ttehfinalarabic'] = 'ﭧ'; - map['ttehinitialarabic'] = 'ﭨ'; - map['ttehmedialarabic'] = 'ﭩ'; - map['tthabengali'] = 'ঠ'; - map['tthadeva'] = 'ठ'; - map['tthagujarati'] = 'ઠ'; - map['tthagurmukhi'] = 'ਠ'; - map['tturned'] = 'ʇ'; - map['tuhiragana'] = 'つ'; - map['tukatakana'] = 'ツ'; - map['tukatakanahalfwidth'] = 'ツ'; - map['tusmallhiragana'] = 'っ'; - map['tusmallkatakana'] = 'ッ'; - map['tusmallkatakanahalfwidth'] = 'ッ'; - map['twelvecircle'] = '⑫'; - map['twelveparen'] = '⑿'; - map['twelveperiod'] = '⒓'; - map['twelveroman'] = 'ⅻ'; - map['twentycircle'] = '⑳'; - map['twentyhangzhou'] = '卄'; - map['twentyparen'] = '⒇'; - map['twentyperiod'] = '⒛'; - map['two'] = '2'; - map['twoarabic'] = '٢'; - map['twobengali'] = '২'; - map['twocircle'] = '②'; - map['twocircleinversesansserif'] = '➋'; - map['twodeva'] = '२'; - map['twodotenleader'] = '‥'; - map['twodotleader'] = '‥'; - map['twodotleadervertical'] = '︰'; - map['twogujarati'] = '૨'; - map['twogurmukhi'] = '੨'; - map['twohackarabic'] = '٢'; - map['twohangzhou'] = '〢'; - map['twoideographicparen'] = '㈡'; - map['twoinferior'] = '₂'; - map['twomonospace'] = '2'; - map['twonumeratorbengali'] = '৵'; - map['twooldstyle'] = ''; - map['twoparen'] = '⑵'; - map['twoperiod'] = '⒉'; - map['twopersian'] = '۲'; - map['tworoman'] = 'ⅱ'; - map['twostroke'] = 'ƻ'; - map['twosuperior'] = '²'; - map['twothai'] = '๒'; - map['twothirds'] = '⅔'; - map['u'] = 'u'; - map['uacute'] = 'ú'; - map['ubar'] = 'ʉ'; - map['ubengali'] = 'উ'; - map['ubopomofo'] = 'ㄨ'; - map['ubreve'] = 'ŭ'; - map['ucaron'] = 'ǔ'; - map['ucircle'] = 'ⓤ'; - map['ucircumflex'] = 'û'; - map['ucircumflexbelow'] = 'ṷ'; - map['ucyrillic'] = 'у'; - map['udattadeva'] = '॑'; - map['udblacute'] = 'ű'; - map['udblgrave'] = 'ȕ'; - map['udeva'] = 'उ'; - map['udieresis'] = 'ü'; - map['udieresisacute'] = 'ǘ'; - map['udieresisbelow'] = 'ṳ'; - map['udieresiscaron'] = 'ǚ'; - map['udieresiscyrillic'] = 'ӱ'; - map['udieresisgrave'] = 'ǜ'; - map['udieresismacron'] = 'ǖ'; - map['udotbelow'] = 'ụ'; - map['ugrave'] = 'ù'; - map['ugujarati'] = 'ઉ'; - map['ugurmukhi'] = 'ਉ'; - map['uhiragana'] = 'う'; - map['uhookabove'] = 'ủ'; - map['uhorn'] = 'ư'; - map['uhornacute'] = 'ứ'; - map['uhorndotbelow'] = 'ự'; - map['uhorngrave'] = 'ừ'; - map['uhornhookabove'] = 'ử'; - map['uhorntilde'] = 'ữ'; - map['uhungarumlaut'] = 'ű'; - map['uhungarumlautcyrillic'] = 'ӳ'; - map['uinvertedbreve'] = 'ȗ'; - map['ukatakana'] = 'ウ'; - map['ukatakanahalfwidth'] = 'ウ'; - map['ukcyrillic'] = 'ѹ'; - map['ukorean'] = 'ㅜ'; - map['umacron'] = 'ū'; - map['umacroncyrillic'] = 'ӯ'; - map['umacrondieresis'] = 'ṻ'; - map['umatragurmukhi'] = 'ੁ'; - map['umonospace'] = 'u'; - map['underscore'] = '_'; - map['underscoredbl'] = '‗'; - map['underscoremonospace'] = '_'; - map['underscorevertical'] = '︳'; - map['underscorewavy'] = '﹏'; - map['union'] = '∪'; - map['universal'] = '∀'; - map['uogonek'] = 'ų'; - map['uparen'] = '⒰'; - map['upblock'] = '▀'; - map['upperdothebrew'] = 'ׄ'; - map['upsilon'] = 'υ'; - map['upsilondieresis'] = 'ϋ'; - map['upsilondieresistonos'] = 'ΰ'; - map['upsilonlatin'] = 'ʊ'; - map['upsilontonos'] = 'ύ'; - map['uptackbelowcmb'] = '̝'; - map['uptackmod'] = '˔'; - map['uragurmukhi'] = 'ੳ'; - map['uring'] = 'ů'; - map['ushortcyrillic'] = 'ў'; - map['usmallhiragana'] = 'ぅ'; - map['usmallkatakana'] = 'ゥ'; - map['usmallkatakanahalfwidth'] = 'ゥ'; - map['ustraightcyrillic'] = 'ү'; - map['ustraightstrokecyrillic'] = 'ұ'; - map['utilde'] = 'ũ'; - map['utildeacute'] = 'ṹ'; - map['utildebelow'] = 'ṵ'; - map['uubengali'] = 'ঊ'; - map['uudeva'] = 'ऊ'; - map['uugujarati'] = 'ઊ'; - map['uugurmukhi'] = 'ਊ'; - map['uumatragurmukhi'] = 'ੂ'; - map['uuvowelsignbengali'] = 'ূ'; - map['uuvowelsigndeva'] = 'ू'; - map['uuvowelsigngujarati'] = 'ૂ'; - map['uvowelsignbengali'] = 'ু'; - map['uvowelsigndeva'] = 'ु'; - map['uvowelsigngujarati'] = 'ુ'; - map['v'] = 'v'; - map['vadeva'] = 'व'; - map['vagujarati'] = 'વ'; - map['vagurmukhi'] = 'ਵ'; - map['vakatakana'] = 'ヷ'; - map['vav'] = 'ו'; - map['vavdagesh'] = 'וּ'; - map['vavdagesh65'] = 'וּ'; - map['vavdageshhebrew'] = 'וּ'; - map['vavhebrew'] = 'ו'; - map['vavholam'] = 'וֹ'; - map['vavholamhebrew'] = 'וֹ'; - map['vavvavhebrew'] = 'װ'; - map['vavyodhebrew'] = 'ױ'; - map['vcircle'] = 'ⓥ'; - map['vdotbelow'] = 'ṿ'; - map['vecyrillic'] = 'в'; - map['veharabic'] = 'ڤ'; - map['vehfinalarabic'] = 'ﭫ'; - map['vehinitialarabic'] = 'ﭬ'; - map['vehmedialarabic'] = 'ﭭ'; - map['vekatakana'] = 'ヹ'; - map['venus'] = '♀'; - map['verticalbar'] = '|'; - map['verticallineabovecmb'] = '̍'; - map['verticallinebelowcmb'] = '̩'; - map['verticallinelowmod'] = 'ˌ'; - map['verticallinemod'] = 'ˈ'; - map['vewarmenian'] = 'վ'; - map['vhook'] = 'ʋ'; - map['vikatakana'] = 'ヸ'; - map['viramabengali'] = '্'; - map['viramadeva'] = '्'; - map['viramagujarati'] = '્'; - map['visargabengali'] = 'ঃ'; - map['visargadeva'] = 'ः'; - map['visargagujarati'] = 'ઃ'; - map['vmonospace'] = 'v'; - map['voarmenian'] = 'ո'; - map['voicediterationhiragana'] = 'ゞ'; - map['voicediterationkatakana'] = 'ヾ'; - map['voicedmarkkana'] = '゛'; - map['voicedmarkkanahalfwidth'] = '゙'; - map['vokatakana'] = 'ヺ'; - map['vparen'] = '⒱'; - map['vtilde'] = 'ṽ'; - map['vturned'] = 'ʌ'; - map['vuhiragana'] = 'ゔ'; - map['vukatakana'] = 'ヴ'; - map['w'] = 'w'; - map['wacute'] = 'ẃ'; - map['waekorean'] = 'ㅙ'; - map['wahiragana'] = 'わ'; - map['wakatakana'] = 'ワ'; - map['wakatakanahalfwidth'] = 'ワ'; - map['wakorean'] = 'ㅘ'; - map['wasmallhiragana'] = 'ゎ'; - map['wasmallkatakana'] = 'ヮ'; - map['wattosquare'] = '㍗'; - map['wavedash'] = '〜'; - map['wavyunderscorevertical'] = '︴'; - map['wawarabic'] = 'و'; - map['wawfinalarabic'] = 'ﻮ'; - map['wawhamzaabovearabic'] = 'ؤ'; - map['wawhamzaabovefinalarabic'] = 'ﺆ'; - map['wbsquare'] = '㏝'; - map['wcircle'] = 'ⓦ'; - map['wcircumflex'] = 'ŵ'; - map['wdieresis'] = 'ẅ'; - map['wdotaccent'] = 'ẇ'; - map['wdotbelow'] = 'ẉ'; - map['wehiragana'] = 'ゑ'; - map['weierstrass'] = '℘'; - map['wekatakana'] = 'ヱ'; - map['wekorean'] = 'ㅞ'; - map['weokorean'] = 'ㅝ'; - map['wgrave'] = 'ẁ'; - map['whitebullet'] = '◦'; - map['whitecircle'] = '○'; - map['whitecircleinverse'] = '◙'; - map['whitecornerbracketleft'] = '『'; - map['whitecornerbracketleftvertical'] = '﹃'; - map['whitecornerbracketright'] = '』'; - map['whitecornerbracketrightvertical'] = '﹄'; - map['whitediamond'] = '◇'; - map['whitediamondcontainingblacksmalldiamond'] = '◈'; - map['whitedownpointingsmalltriangle'] = '▿'; - map['whitedownpointingtriangle'] = '▽'; - map['whiteleftpointingsmalltriangle'] = '◃'; - map['whiteleftpointingtriangle'] = '◁'; - map['whitelenticularbracketleft'] = '〖'; - map['whitelenticularbracketright'] = '〗'; - map['whiterightpointingsmalltriangle'] = '▹'; - map['whiterightpointingtriangle'] = '▷'; - map['whitesmallsquare'] = '▫'; - map['whitesmilingface'] = '☺'; - map['whitesquare'] = '□'; - map['whitestar'] = '☆'; - map['whitetelephone'] = '☏'; - map['whitetortoiseshellbracketleft'] = '〘'; - map['whitetortoiseshellbracketright'] = '〙'; - map['whiteuppointingsmalltriangle'] = '▵'; - map['whiteuppointingtriangle'] = '△'; - map['wihiragana'] = 'ゐ'; - map['wikatakana'] = 'ヰ'; - map['wikorean'] = 'ㅟ'; - map['wmonospace'] = 'w'; - map['wohiragana'] = 'を'; - map['wokatakana'] = 'ヲ'; - map['wokatakanahalfwidth'] = 'ヲ'; - map['won'] = '₩'; - map['wonmonospace'] = '₩'; - map['wowaenthai'] = 'ว'; - map['wparen'] = '⒲'; - map['wring'] = 'ẘ'; - map['wsuperior'] = 'ʷ'; - map['wturned'] = 'ʍ'; - map['wynn'] = 'ƿ'; - map['x'] = 'x'; - map['xabovecmb'] = '̽'; - map['xbopomofo'] = 'ㄒ'; - map['xcircle'] = 'ⓧ'; - map['xdieresis'] = 'ẍ'; - map['xdotaccent'] = 'ẋ'; - map['xeharmenian'] = 'խ'; - map['xi'] = 'ξ'; - map['xmonospace'] = 'x'; - map['xparen'] = '⒳'; - map['xsuperior'] = 'ˣ'; - map['y'] = 'y'; - map['yaadosquare'] = '㍎'; - map['yabengali'] = 'য'; - map['yacute'] = 'ý'; - map['yadeva'] = 'य'; - map['yaekorean'] = 'ㅒ'; - map['yagujarati'] = 'ય'; - map['yagurmukhi'] = 'ਯ'; - map['yahiragana'] = 'や'; - map['yakatakana'] = 'ヤ'; - map['yakatakanahalfwidth'] = 'ヤ'; - map['yakorean'] = 'ㅑ'; - map['yamakkanthai'] = '๎'; - map['yasmallhiragana'] = 'ゃ'; - map['yasmallkatakana'] = 'ャ'; - map['yasmallkatakanahalfwidth'] = 'ャ'; - map['yatcyrillic'] = 'ѣ'; - map['ycircle'] = 'ⓨ'; - map['ycircumflex'] = 'ŷ'; - map['ydieresis'] = 'ÿ'; - map['ydotaccent'] = 'ẏ'; - map['ydotbelow'] = 'ỵ'; - map['yeharabic'] = 'ي'; - map['yehbarreearabic'] = 'ے'; - map['yehbarreefinalarabic'] = 'ﮯ'; - map['yehfinalarabic'] = 'ﻲ'; - map['yehhamzaabovearabic'] = 'ئ'; - map['yehhamzaabovefinalarabic'] = 'ﺊ'; - map['yehhamzaaboveinitialarabic'] = 'ﺋ'; - map['yehhamzaabovemedialarabic'] = 'ﺌ'; - map['yehinitialarabic'] = 'ﻳ'; - map['yehmedialarabic'] = 'ﻴ'; - map['yehmeeminitialarabic'] = 'ﳝ'; - map['yehmeemisolatedarabic'] = 'ﱘ'; - map['yehnoonfinalarabic'] = 'ﲔ'; - map['yehthreedotsbelowarabic'] = 'ۑ'; - map['yekorean'] = 'ㅖ'; - map['yen'] = '¥'; - map['yenmonospace'] = '¥'; - map['yeokorean'] = 'ㅕ'; - map['yeorinhieuhkorean'] = 'ㆆ'; - map['yerahbenyomohebrew'] = '֪'; - map['yerahbenyomolefthebrew'] = '֪'; - map['yericyrillic'] = 'ы'; - map['yerudieresiscyrillic'] = 'ӹ'; - map['yesieungkorean'] = 'ㆁ'; - map['yesieungpansioskorean'] = 'ㆃ'; - map['yesieungsioskorean'] = 'ㆂ'; - map['yetivhebrew'] = '֚'; - map['ygrave'] = 'ỳ'; - map['yhook'] = 'ƴ'; - map['yhookabove'] = 'ỷ'; - map['yiarmenian'] = 'յ'; - map['yicyrillic'] = 'ї'; - map['yikorean'] = 'ㅢ'; - map['yinyang'] = '☯'; - map['yiwnarmenian'] = 'ւ'; - map['ymonospace'] = 'y'; - map['yod'] = 'י'; - map['yoddagesh'] = 'יּ'; - map['yoddageshhebrew'] = 'יּ'; - map['yodhebrew'] = 'י'; - map['yodyodhebrew'] = 'ײ'; - map['yodyodpatahhebrew'] = 'ײַ'; - map['yohiragana'] = 'よ'; - map['yoikorean'] = 'ㆉ'; - map['yokatakana'] = 'ヨ'; - map['yokatakanahalfwidth'] = 'ヨ'; - map['yokorean'] = 'ㅛ'; - map['yosmallhiragana'] = 'ょ'; - map['yosmallkatakana'] = 'ョ'; - map['yosmallkatakanahalfwidth'] = 'ョ'; - map['yotgreek'] = 'ϳ'; - map['yoyaekorean'] = 'ㆈ'; - map['yoyakorean'] = 'ㆇ'; - map['yoyakthai'] = 'ย'; - map['yoyingthai'] = 'ญ'; - map['yparen'] = '⒴'; - map['ypogegrammeni'] = 'ͺ'; - map['ypogegrammenigreekcmb'] = 'ͅ'; - map['yr'] = 'Ʀ'; - map['yring'] = 'ẙ'; - map['ysuperior'] = 'ʸ'; - map['ytilde'] = 'ỹ'; - map['yturned'] = 'ʎ'; - map['yuhiragana'] = 'ゆ'; - map['yuikorean'] = 'ㆌ'; - map['yukatakana'] = 'ユ'; - map['yukatakanahalfwidth'] = 'ユ'; - map['yukorean'] = 'ㅠ'; - map['yusbigcyrillic'] = 'ѫ'; - map['yusbigiotifiedcyrillic'] = 'ѭ'; - map['yuslittlecyrillic'] = 'ѧ'; - map['yuslittleiotifiedcyrillic'] = 'ѩ'; - map['yusmallhiragana'] = 'ゅ'; - map['yusmallkatakana'] = 'ュ'; - map['yusmallkatakanahalfwidth'] = 'ュ'; - map['yuyekorean'] = 'ㆋ'; - map['yuyeokorean'] = 'ㆊ'; - map['yyabengali'] = 'য়'; - map['yyadeva'] = 'य़'; - map['z'] = 'z'; - map['zaarmenian'] = 'զ'; - map['zacute'] = 'ź'; - map['zadeva'] = 'ज़'; - map['zagurmukhi'] = 'ਜ਼'; - map['zaharabic'] = 'ظ'; - map['zahfinalarabic'] = 'ﻆ'; - map['zahinitialarabic'] = 'ﻇ'; - map['zahiragana'] = 'ざ'; - map['zahmedialarabic'] = 'ﻈ'; - map['zainarabic'] = 'ز'; - map['zainfinalarabic'] = 'ﺰ'; - map['zakatakana'] = 'ザ'; - map['zaqefgadolhebrew'] = '֕'; - map['zaqefqatanhebrew'] = '֔'; - map['zarqahebrew'] = '֘'; - map['zayin'] = 'ז'; - map['zayindagesh'] = 'זּ'; - map['zayindageshhebrew'] = 'זּ'; - map['zayinhebrew'] = 'ז'; - map['zbopomofo'] = 'ㄗ'; - map['zcaron'] = 'ž'; - map['zcircle'] = 'ⓩ'; - map['zcircumflex'] = 'ẑ'; - map['zcurl'] = 'ʑ'; - map['zdot'] = 'ż'; - map['zdotaccent'] = 'ż'; - map['zdotbelow'] = 'ẓ'; - map['zecyrillic'] = 'з'; - map['zedescendercyrillic'] = 'ҙ'; - map['zedieresiscyrillic'] = 'ӟ'; - map['zehiragana'] = 'ぜ'; - map['zekatakana'] = 'ゼ'; - map['zero'] = '0'; - map['zeroarabic'] = '٠'; - map['zerobengali'] = '০'; - map['zerodeva'] = '०'; - map['zerogujarati'] = '૦'; - map['zerogurmukhi'] = '੦'; - map['zerohackarabic'] = '٠'; - map['zeroinferior'] = '₀'; - map['zeromonospace'] = '0'; - map['zerooldstyle'] = ''; - map['zeropersian'] = '۰'; - map['zerosuperior'] = '⁰'; - map['zerothai'] = '๐'; - map['zerowidthjoiner'] = ''; - map['zerowidthnonjoiner'] = '‌'; - map['zerowidthspace'] = '​'; - map['zeta'] = 'ζ'; - map['zhbopomofo'] = 'ㄓ'; - map['zhearmenian'] = 'ժ'; - map['zhebrevecyrillic'] = 'ӂ'; - map['zhecyrillic'] = 'ж'; - map['zhedescendercyrillic'] = 'җ'; - map['zhedieresiscyrillic'] = 'ӝ'; - map['zihiragana'] = 'じ'; - map['zikatakana'] = 'ジ'; - map['zinorhebrew'] = '֮'; - map['zlinebelow'] = 'ẕ'; - map['zmonospace'] = 'z'; - map['zohiragana'] = 'ぞ'; - map['zokatakana'] = 'ゾ'; - map['zparen'] = '⒵'; - map['zretroflexhook'] = 'ʐ'; - map['zstroke'] = 'ƶ'; - map['zuhiragana'] = 'ず'; - map['zukatakana'] = 'ズ'; + map!['A'] = 'A'; + map!['AE'] = 'Æ'; + map!['AEacute'] = 'Ǽ'; + map!['AEmacron'] = 'Ǣ'; + map!['AEsmall'] = ''; + map!['Aacute'] = 'Á'; + map!['Aacutesmall'] = ''; + map!['Abreve'] = 'Ă'; + map!['Abreveacute'] = 'Ắ'; + map!['Abrevecyrillic'] = 'Ӑ'; + map!['Abrevedotbelow'] = 'Ặ'; + map!['Abrevegrave'] = 'Ằ'; + map!['Abrevehookabove'] = 'Ẳ'; + map!['Abrevetilde'] = 'Ẵ'; + map!['Acaron'] = 'Ǎ'; + map!['Acircle'] = 'Ⓐ'; + map!['Acircumflex'] = 'Â'; + map!['Acircumflexacute'] = 'Ấ'; + map!['Acircumflexdotbelow'] = 'Ậ'; + map!['Acircumflexgrave'] = 'Ầ'; + map!['Acircumflexhookabove'] = 'Ẩ'; + map!['Acircumflexsmall'] = ''; + map!['Acircumflextilde'] = 'Ẫ'; + map!['Acute'] = ''; + map!['Acutesmall'] = ''; + map!['Acyrillic'] = 'А'; + map!['Adblgrave'] = 'Ȁ'; + map!['Adieresis'] = 'Ä'; + map!['Adieresiscyrillic'] = 'Ӓ'; + map!['Adieresismacron'] = 'Ǟ'; + map!['Adieresissmall'] = ''; + map!['Adotbelow'] = 'Ạ'; + map!['Adotmacron'] = 'Ǡ'; + map!['Agrave'] = 'À'; + map!['Agravesmall'] = ''; + map!['Ahookabove'] = 'Ả'; + map!['Aiecyrillic'] = 'Ӕ'; + map!['Ainvertedbreve'] = 'Ȃ'; + map!['Alpha'] = 'Α'; + map!['Alphatonos'] = 'Ά'; + map!['Amacron'] = 'Ā'; + map!['Amonospace'] = 'A'; + map!['Aogonek'] = 'Ą'; + map!['Aring'] = 'Å'; + map!['Aringacute'] = 'Ǻ'; + map!['Aringbelow'] = 'Ḁ'; + map!['Aringsmall'] = ''; + map!['Asmall'] = ''; + map!['Atilde'] = 'Ã'; + map!['Atildesmall'] = ''; + map!['Aybarmenian'] = 'Ա'; + map!['B'] = 'B'; + map!['Bcircle'] = 'Ⓑ'; + map!['Bdotaccent'] = 'Ḃ'; + map!['Bdotbelow'] = 'Ḅ'; + map!['Becyrillic'] = 'Б'; + map!['Benarmenian'] = 'Բ'; + map!['Beta'] = 'Β'; + map!['Bhook'] = 'Ɓ'; + map!['Blinebelow'] = 'Ḇ'; + map!['Bmonospace'] = 'B'; + map!['Brevesmall'] = ''; + map!['Bsmall'] = ''; + map!['Btopbar'] = 'Ƃ'; + map!['C'] = 'C'; + map!['Caarmenian'] = 'Ծ'; + map!['Cacute'] = 'Ć'; + map!['Caron'] = ''; + map!['Caronsmall'] = ''; + map!['Ccaron'] = 'Č'; + map!['Ccedilla'] = 'Ç'; + map!['Ccedillaacute'] = 'Ḉ'; + map!['Ccedillasmall'] = ''; + map!['Ccircle'] = 'Ⓒ'; + map!['Ccircumflex'] = 'Ĉ'; + map!['Cdot'] = 'Ċ'; + map!['Cdotaccent'] = 'Ċ'; + map!['Cedillasmall'] = ''; + map!['Chaarmenian'] = 'Չ'; + map!['Cheabkhasiancyrillic'] = 'Ҽ'; + map!['Checyrillic'] = 'Ч'; + map!['Chedescenderabkhasiancyrillic'] = 'Ҿ'; + map!['Chedescendercyrillic'] = 'Ҷ'; + map!['Chedieresiscyrillic'] = 'Ӵ'; + map!['Cheharmenian'] = 'Ճ'; + map!['Chekhakassiancyrillic'] = 'Ӌ'; + map!['Cheverticalstrokecyrillic'] = 'Ҹ'; + map!['Chi'] = 'Χ'; + map!['Chook'] = 'Ƈ'; + map!['Circumflexsmall'] = ''; + map!['Cmonospace'] = 'C'; + map!['Coarmenian'] = 'Ց'; + map!['Csmall'] = ''; + map!['D'] = 'D'; + map!['DZ'] = 'DZ'; + map!['DZcaron'] = 'DŽ'; + map!['Daarmenian'] = 'Դ'; + map!['Dafrican'] = 'Ɖ'; + map!['Dcaron'] = 'Ď'; + map!['Dcedilla'] = 'Ḑ'; + map!['Dcircle'] = 'Ⓓ'; + map!['Dcircumflexbelow'] = 'Ḓ'; + map!['Dcroat'] = 'Đ'; + map!['Ddotaccent'] = 'Ḋ'; + map!['Ddotbelow'] = 'Ḍ'; + map!['Decyrillic'] = 'Д'; + map!['Deicoptic'] = 'Ϯ'; + map!['Delta'] = '∆'; + map!['Deltagreek'] = 'Δ'; + map!['Dhook'] = 'Ɗ'; + map!['Dieresis'] = ''; + map!['DieresisAcute'] = ''; + map!['DieresisGrave'] = ''; + map!['Dieresissmall'] = ''; + map!['Digammagreek'] = 'Ϝ'; + map!['Djecyrillic'] = 'Ђ'; + map!['Dlinebelow'] = 'Ḏ'; + map!['Dmonospace'] = 'D'; + map!['Dotaccentsmall'] = ''; + map!['Dslash'] = 'Đ'; + map!['Dsmall'] = ''; + map!['Dtopbar'] = 'Ƌ'; + map!['Dz'] = 'Dz'; + map!['Dzcaron'] = 'Dž'; + map!['Dzeabkhasiancyrillic'] = 'Ӡ'; + map!['Dzecyrillic'] = 'Ѕ'; + map!['Dzhecyrillic'] = 'Џ'; + map!['E'] = 'E'; + map!['Eacute'] = 'É'; + map!['Eacutesmall'] = ''; + map!['Ebreve'] = 'Ĕ'; + map!['Ecaron'] = 'Ě'; + map!['Ecedillabreve'] = 'Ḝ'; + map!['Echarmenian'] = 'Ե'; + map!['Ecircle'] = 'Ⓔ'; + map!['Ecircumflex'] = 'Ê'; + map!['Ecircumflexacute'] = 'Ế'; + map!['Ecircumflexbelow'] = 'Ḙ'; + map!['Ecircumflexdotbelow'] = 'Ệ'; + map!['Ecircumflexgrave'] = 'Ề'; + map!['Ecircumflexhookabove'] = 'Ể'; + map!['Ecircumflexsmall'] = ''; + map!['Ecircumflextilde'] = 'Ễ'; + map!['Ecyrillic'] = 'Є'; + map!['Edblgrave'] = 'Ȅ'; + map!['Edieresis'] = 'Ë'; + map!['Edieresissmall'] = ''; + map!['Edot'] = 'Ė'; + map!['Edotaccent'] = 'Ė'; + map!['Edotbelow'] = 'Ẹ'; + map!['Efcyrillic'] = 'Ф'; + map!['Egrave'] = 'È'; + map!['Egravesmall'] = ''; + map!['Eharmenian'] = 'Է'; + map!['Ehookabove'] = 'Ẻ'; + map!['Eightroman'] = 'Ⅷ'; + map!['Einvertedbreve'] = 'Ȇ'; + map!['Eiotifiedcyrillic'] = 'Ѥ'; + map!['Elcyrillic'] = 'Л'; + map!['Elevenroman'] = 'Ⅺ'; + map!['Emacron'] = 'Ē'; + map!['Emacronacute'] = 'Ḗ'; + map!['Emacrongrave'] = 'Ḕ'; + map!['Emcyrillic'] = 'М'; + map!['Emonospace'] = 'E'; + map!['Encyrillic'] = 'Н'; + map!['Endescendercyrillic'] = 'Ң'; + map!['Eng'] = 'Ŋ'; + map!['Enghecyrillic'] = 'Ҥ'; + map!['Enhookcyrillic'] = 'Ӈ'; + map!['Eogonek'] = 'Ę'; + map!['Eopen'] = 'Ɛ'; + map!['Epsilon'] = 'Ε'; + map!['Epsilontonos'] = 'Έ'; + map!['Ercyrillic'] = 'Р'; + map!['Ereversed'] = 'Ǝ'; + map!['Ereversedcyrillic'] = 'Э'; + map!['Escyrillic'] = 'С'; + map!['Esdescendercyrillic'] = 'Ҫ'; + map!['Esh'] = 'Ʃ'; + map!['Esmall'] = ''; + map!['Eta'] = 'Η'; + map!['Etarmenian'] = 'Ը'; + map!['Etatonos'] = 'Ή'; + map!['Eth'] = 'Ð'; + map!['Ethsmall'] = ''; + map!['Etilde'] = 'Ẽ'; + map!['Etildebelow'] = 'Ḛ'; + map!['Euro'] = '€'; + map!['Ezh'] = 'Ʒ'; + map!['Ezhcaron'] = 'Ǯ'; + map!['Ezhreversed'] = 'Ƹ'; + map!['F'] = 'F'; + map!['Fcircle'] = 'Ⓕ'; + map!['Fdotaccent'] = 'Ḟ'; + map!['Feharmenian'] = 'Ֆ'; + map!['Feicoptic'] = 'Ϥ'; + map!['Fhook'] = 'Ƒ'; + map!['Fitacyrillic'] = 'Ѳ'; + map!['Fiveroman'] = 'Ⅴ'; + map!['Fmonospace'] = 'F'; + map!['Fourroman'] = 'Ⅳ'; + map!['Fsmall'] = ''; + map!['G'] = 'G'; + map!['GBsquare'] = '㎇'; + map!['Gacute'] = 'Ǵ'; + map!['Gamma'] = 'Γ'; + map!['Gammaafrican'] = 'Ɣ'; + map!['Gangiacoptic'] = 'Ϫ'; + map!['Gbreve'] = 'Ğ'; + map!['Gcaron'] = 'Ǧ'; + map!['Gcedilla'] = 'Ģ'; + map!['Gcircle'] = 'Ⓖ'; + map!['Gcircumflex'] = 'Ĝ'; + map!['Gcommaaccent'] = 'Ģ'; + map!['Gdot'] = 'Ġ'; + map!['Gdotaccent'] = 'Ġ'; + map!['Gecyrillic'] = 'Г'; + map!['Ghadarmenian'] = 'Ղ'; + map!['Ghemiddlehookcyrillic'] = 'Ҕ'; + map!['Ghestrokecyrillic'] = 'Ғ'; + map!['Gheupturncyrillic'] = 'Ґ'; + map!['Ghook'] = 'Ɠ'; + map!['Gimarmenian'] = 'Գ'; + map!['Gjecyrillic'] = 'Ѓ'; + map!['Gmacron'] = 'Ḡ'; + map!['Gmonospace'] = 'G'; + map!['Grave'] = ''; + map!['Gravesmall'] = ''; + map!['Gsmall'] = ''; + map!['Gsmallhook'] = 'ʛ'; + map!['Gstroke'] = 'Ǥ'; + map!['H'] = 'H'; + map!['H18533'] = '●'; + map!['H18543'] = '▪'; + map!['H18551'] = '▫'; + map!['H22073'] = '□'; + map!['HPsquare'] = '㏋'; + map!['Haabkhasiancyrillic'] = 'Ҩ'; + map!['Hadescendercyrillic'] = 'Ҳ'; + map!['Hardsigncyrillic'] = 'Ъ'; + map!['Hbar'] = 'Ħ'; + map!['Hbrevebelow'] = 'Ḫ'; + map!['Hcedilla'] = 'Ḩ'; + map!['Hcircle'] = 'Ⓗ'; + map!['Hcircumflex'] = 'Ĥ'; + map!['Hdieresis'] = 'Ḧ'; + map!['Hdotaccent'] = 'Ḣ'; + map!['Hdotbelow'] = 'Ḥ'; + map!['Hmonospace'] = 'H'; + map!['Hoarmenian'] = 'Հ'; + map!['Horicoptic'] = 'Ϩ'; + map!['Hsmall'] = ''; + map!['Hungarumlaut'] = ''; + map!['Hungarumlautsmall'] = ''; + map!['Hzsquare'] = '㎐'; + map!['I'] = 'I'; + map!['IAcyrillic'] = 'Я'; + map!['IJ'] = 'IJ'; + map!['IUcyrillic'] = 'Ю'; + map!['Iacute'] = 'Í'; + map!['Iacutesmall'] = ''; + map!['Ibreve'] = 'Ĭ'; + map!['Icaron'] = 'Ǐ'; + map!['Icircle'] = 'Ⓘ'; + map!['Icircumflex'] = 'Î'; + map!['Icircumflexsmall'] = ''; + map!['Icyrillic'] = 'І'; + map!['Idblgrave'] = 'Ȉ'; + map!['Idieresis'] = 'Ï'; + map!['Idieresisacute'] = 'Ḯ'; + map!['Idieresiscyrillic'] = 'Ӥ'; + map!['Idieresissmall'] = ''; + map!['Idot'] = 'İ'; + map!['Idotaccent'] = 'İ'; + map!['Idotbelow'] = 'Ị'; + map!['Iebrevecyrillic'] = 'Ӗ'; + map!['Iecyrillic'] = 'Е'; + map!['Ifraktur'] = 'ℑ'; + map!['Igrave'] = 'Ì'; + map!['Igravesmall'] = ''; + map!['Ihookabove'] = 'Ỉ'; + map!['Iicyrillic'] = 'И'; + map!['Iinvertedbreve'] = 'Ȋ'; + map!['Iishortcyrillic'] = 'Й'; + map!['Imacron'] = 'Ī'; + map!['Imacroncyrillic'] = 'Ӣ'; + map!['Imonospace'] = 'I'; + map!['Iniarmenian'] = 'Ի'; + map!['Iocyrillic'] = 'Ё'; + map!['Iogonek'] = 'Į'; + map!['Iota'] = 'Ι'; + map!['Iotaafrican'] = 'Ɩ'; + map!['Iotadieresis'] = 'Ϊ'; + map!['Iotatonos'] = 'Ί'; + map!['Ismall'] = ''; + map!['Istroke'] = 'Ɨ'; + map!['Itilde'] = 'Ĩ'; + map!['Itildebelow'] = 'Ḭ'; + map!['Izhitsacyrillic'] = 'Ѵ'; + map!['Izhitsadblgravecyrillic'] = 'Ѷ'; + map!['J'] = 'J'; + map!['Jaarmenian'] = 'Ձ'; + map!['Jcircle'] = 'Ⓙ'; + map!['Jcircumflex'] = 'Ĵ'; + map!['Jecyrillic'] = 'Ј'; + map!['Jheharmenian'] = 'Ջ'; + map!['Jmonospace'] = 'J'; + map!['Jsmall'] = ''; + map!['K'] = 'K'; + map!['KBsquare'] = '㎅'; + map!['KKsquare'] = '㏍'; + map!['Kabashkircyrillic'] = 'Ҡ'; + map!['Kacute'] = 'Ḱ'; + map!['Kacyrillic'] = 'К'; + map!['Kadescendercyrillic'] = 'Қ'; + map!['Kahookcyrillic'] = 'Ӄ'; + map!['Kappa'] = 'Κ'; + map!['Kastrokecyrillic'] = 'Ҟ'; + map!['Kaverticalstrokecyrillic'] = 'Ҝ'; + map!['Kcaron'] = 'Ǩ'; + map!['Kcedilla'] = 'Ķ'; + map!['Kcircle'] = 'Ⓚ'; + map!['Kcommaaccent'] = 'Ķ'; + map!['Kdotbelow'] = 'Ḳ'; + map!['Keharmenian'] = 'Ք'; + map!['Kenarmenian'] = 'Կ'; + map!['Khacyrillic'] = 'Х'; + map!['Kheicoptic'] = 'Ϧ'; + map!['Khook'] = 'Ƙ'; + map!['Kjecyrillic'] = 'Ќ'; + map!['Klinebelow'] = 'Ḵ'; + map!['Kmonospace'] = 'K'; + map!['Koppacyrillic'] = 'Ҁ'; + map!['Koppagreek'] = 'Ϟ'; + map!['Ksicyrillic'] = 'Ѯ'; + map!['Ksmall'] = ''; + map!['L'] = 'L'; + map!['LJ'] = 'LJ'; + map!['LL'] = ''; + map!['Lacute'] = 'Ĺ'; + map!['Lambda'] = 'Λ'; + map!['Lcaron'] = 'Ľ'; + map!['Lcedilla'] = 'Ļ'; + map!['Lcircle'] = 'Ⓛ'; + map!['Lcircumflexbelow'] = 'Ḽ'; + map!['Lcommaaccent'] = 'Ļ'; + map!['Ldot'] = 'Ŀ'; + map!['Ldotaccent'] = 'Ŀ'; + map!['Ldotbelow'] = 'Ḷ'; + map!['Ldotbelowmacron'] = 'Ḹ'; + map!['Liwnarmenian'] = 'Լ'; + map!['Lj'] = 'Lj'; + map!['Ljecyrillic'] = 'Љ'; + map!['Llinebelow'] = 'Ḻ'; + map!['Lmonospace'] = 'L'; + map!['Lslash'] = 'Ł'; + map!['Lslashsmall'] = ''; + map!['Lsmall'] = ''; + map!['M'] = 'M'; + map!['MBsquare'] = '㎆'; + map!['Macron'] = ''; + map!['Macronsmall'] = ''; + map!['Macute'] = 'Ḿ'; + map!['Mcircle'] = 'Ⓜ'; + map!['Mdotaccent'] = 'Ṁ'; + map!['Mdotbelow'] = 'Ṃ'; + map!['Menarmenian'] = 'Մ'; + map!['Mmonospace'] = 'M'; + map!['Msmall'] = ''; + map!['Mturned'] = 'Ɯ'; + map!['Mu'] = 'Μ'; + map!['N'] = 'N'; + map!['NJ'] = 'NJ'; + map!['Nacute'] = 'Ń'; + map!['Ncaron'] = 'Ň'; + map!['Ncedilla'] = 'Ņ'; + map!['Ncircle'] = 'Ⓝ'; + map!['Ncircumflexbelow'] = 'Ṋ'; + map!['Ncommaaccent'] = 'Ņ'; + map!['Ndotaccent'] = 'Ṅ'; + map!['Ndotbelow'] = 'Ṇ'; + map!['Nhookleft'] = 'Ɲ'; + map!['Nineroman'] = 'Ⅸ'; + map!['Nj'] = 'Nj'; + map!['Njecyrillic'] = 'Њ'; + map!['Nlinebelow'] = 'Ṉ'; + map!['Nmonospace'] = 'N'; + map!['Nowarmenian'] = 'Ն'; + map!['Nsmall'] = ''; + map!['Ntilde'] = 'Ñ'; + map!['Ntildesmall'] = ''; + map!['Nu'] = 'Ν'; + map!['O'] = 'O'; + map!['OE'] = 'Œ'; + map!['OEsmall'] = ''; + map!['Oacute'] = 'Ó'; + map!['Oacutesmall'] = ''; + map!['Obarredcyrillic'] = 'Ө'; + map!['Obarreddieresiscyrillic'] = 'Ӫ'; + map!['Obreve'] = 'Ŏ'; + map!['Ocaron'] = 'Ǒ'; + map!['Ocenteredtilde'] = 'Ɵ'; + map!['Ocircle'] = 'Ⓞ'; + map!['Ocircumflex'] = 'Ô'; + map!['Ocircumflexacute'] = 'Ố'; + map!['Ocircumflexdotbelow'] = 'Ộ'; + map!['Ocircumflexgrave'] = 'Ồ'; + map!['Ocircumflexhookabove'] = 'Ổ'; + map!['Ocircumflexsmall'] = ''; + map!['Ocircumflextilde'] = 'Ỗ'; + map!['Ocyrillic'] = 'О'; + map!['Odblacute'] = 'Ő'; + map!['Odblgrave'] = 'Ȍ'; + map!['Odieresis'] = 'Ö'; + map!['Odieresiscyrillic'] = 'Ӧ'; + map!['Odieresissmall'] = ''; + map!['Odotbelow'] = 'Ọ'; + map!['Ogoneksmall'] = ''; + map!['Ograve'] = 'Ò'; + map!['Ogravesmall'] = ''; + map!['Oharmenian'] = 'Օ'; + map!['Ohm'] = 'Ω'; + map!['Ohookabove'] = 'Ỏ'; + map!['Ohorn'] = 'Ơ'; + map!['Ohornacute'] = 'Ớ'; + map!['Ohorndotbelow'] = 'Ợ'; + map!['Ohorngrave'] = 'Ờ'; + map!['Ohornhookabove'] = 'Ở'; + map!['Ohorntilde'] = 'Ỡ'; + map!['Ohungarumlaut'] = 'Ő'; + map!['Oi'] = 'Ƣ'; + map!['Oinvertedbreve'] = 'Ȏ'; + map!['Omacron'] = 'Ō'; + map!['Omacronacute'] = 'Ṓ'; + map!['Omacrongrave'] = 'Ṑ'; + map!['Omega'] = 'Ω'; + map!['Omegacyrillic'] = 'Ѡ'; + map!['Omegagreek'] = 'Ω'; + map!['Omegaroundcyrillic'] = 'Ѻ'; + map!['Omegatitlocyrillic'] = 'Ѽ'; + map!['Omegatonos'] = 'Ώ'; + map!['Omicron'] = 'Ο'; + map!['Omicrontonos'] = 'Ό'; + map!['Omonospace'] = 'O'; + map!['Oneroman'] = 'Ⅰ'; + map!['Oogonek'] = 'Ǫ'; + map!['Oogonekmacron'] = 'Ǭ'; + map!['Oopen'] = 'Ɔ'; + map!['Oslash'] = 'Ø'; + map!['Oslashacute'] = 'Ǿ'; + map!['Oslashsmall'] = ''; + map!['Osmall'] = ''; + map!['Ostrokeacute'] = 'Ǿ'; + map!['Otcyrillic'] = 'Ѿ'; + map!['Otilde'] = 'Õ'; + map!['Otildeacute'] = 'Ṍ'; + map!['Otildedieresis'] = 'Ṏ'; + map!['Otildesmall'] = ''; + map!['P'] = 'P'; + map!['Pacute'] = 'Ṕ'; + map!['Pcircle'] = 'Ⓟ'; + map!['Pdotaccent'] = 'Ṗ'; + map!['Pecyrillic'] = 'П'; + map!['Peharmenian'] = 'Պ'; + map!['Pemiddlehookcyrillic'] = 'Ҧ'; + map!['Phi'] = 'Φ'; + map!['Phook'] = 'Ƥ'; + map!['Pi'] = 'Π'; + map!['Piwrarmenian'] = 'Փ'; + map!['Pmonospace'] = 'P'; + map!['Psi'] = 'Ψ'; + map!['Psicyrillic'] = 'Ѱ'; + map!['Psmall'] = ''; + map!['Q'] = 'Q'; + map!['Qcircle'] = 'Ⓠ'; + map!['Qmonospace'] = 'Q'; + map!['Qsmall'] = ''; + map!['R'] = 'R'; + map!['Raarmenian'] = 'Ռ'; + map!['Racute'] = 'Ŕ'; + map!['Rcaron'] = 'Ř'; + map!['Rcedilla'] = 'Ŗ'; + map!['Rcircle'] = 'Ⓡ'; + map!['Rcommaaccent'] = 'Ŗ'; + map!['Rdblgrave'] = 'Ȑ'; + map!['Rdotaccent'] = 'Ṙ'; + map!['Rdotbelow'] = 'Ṛ'; + map!['Rdotbelowmacron'] = 'Ṝ'; + map!['Reharmenian'] = 'Ր'; + map!['Rfraktur'] = 'ℜ'; + map!['Rho'] = 'Ρ'; + map!['Ringsmall'] = ''; + map!['Rinvertedbreve'] = 'Ȓ'; + map!['Rlinebelow'] = 'Ṟ'; + map!['Rmonospace'] = 'R'; + map!['Rsmall'] = ''; + map!['Rsmallinverted'] = 'ʁ'; + map!['Rsmallinvertedsuperior'] = 'ʶ'; + map!['S'] = 'S'; + map!['SF010000'] = '┌'; + map!['SF020000'] = '└'; + map!['SF030000'] = '┐'; + map!['SF040000'] = '┘'; + map!['SF050000'] = '┼'; + map!['SF060000'] = '┬'; + map!['SF070000'] = '┴'; + map!['SF080000'] = '├'; + map!['SF090000'] = '┤'; + map!['SF100000'] = '─'; + map!['SF110000'] = '│'; + map!['SF190000'] = '╡'; + map!['SF200000'] = '╢'; + map!['SF210000'] = '╖'; + map!['SF220000'] = '╕'; + map!['SF230000'] = '╣'; + map!['SF240000'] = '║'; + map!['SF250000'] = '╗'; + map!['SF260000'] = '╝'; + map!['SF270000'] = '╜'; + map!['SF280000'] = '╛'; + map!['SF360000'] = '╞'; + map!['SF370000'] = '╟'; + map!['SF380000'] = '╚'; + map!['SF390000'] = '╔'; + map!['SF400000'] = '╩'; + map!['SF410000'] = '╦'; + map!['SF420000'] = '╠'; + map!['SF430000'] = '═'; + map!['SF440000'] = '╬'; + map!['SF450000'] = '╧'; + map!['SF460000'] = '╨'; + map!['SF470000'] = '╤'; + map!['SF480000'] = '╥'; + map!['SF490000'] = '╙'; + map!['SF500000'] = '╘'; + map!['SF510000'] = '╒'; + map!['SF520000'] = '╓'; + map!['SF530000'] = '╫'; + map!['SF540000'] = '╪'; + map!['Sacute'] = 'Ś'; + map!['Sacutedotaccent'] = 'Ṥ'; + map!['Sampigreek'] = 'Ϡ'; + map!['Scaron'] = 'Š'; + map!['Scarondotaccent'] = 'Ṧ'; + map!['Scaronsmall'] = ''; + map!['Scedilla'] = 'Ş'; + map!['Schwa'] = 'Ə'; + map!['Schwacyrillic'] = 'Ә'; + map!['Schwadieresiscyrillic'] = 'Ӛ'; + map!['Scircle'] = 'Ⓢ'; + map!['Scircumflex'] = 'Ŝ'; + map!['Scommaaccent'] = 'Ș'; + map!['Sdotaccent'] = 'Ṡ'; + map!['Sdotbelow'] = 'Ṣ'; + map!['Sdotbelowdotaccent'] = 'Ṩ'; + map!['Seharmenian'] = 'Ս'; + map!['Sevenroman'] = 'Ⅶ'; + map!['Shaarmenian'] = 'Շ'; + map!['Shacyrillic'] = 'Ш'; + map!['Shchacyrillic'] = 'Щ'; + map!['Sheicoptic'] = 'Ϣ'; + map!['Shhacyrillic'] = 'Һ'; + map!['Shimacoptic'] = 'Ϭ'; + map!['Sigma'] = 'Σ'; + map!['Sixroman'] = 'Ⅵ'; + map!['Smonospace'] = 'S'; + map!['Softsigncyrillic'] = 'Ь'; + map!['Ssmall'] = ''; + map!['Stigmagreek'] = 'Ϛ'; + map!['T'] = 'T'; + map!['Tau'] = 'Τ'; + map!['Tbar'] = 'Ŧ'; + map!['Tcaron'] = 'Ť'; + map!['Tcedilla'] = 'Ţ'; + map!['Tcircle'] = 'Ⓣ'; + map!['Tcircumflexbelow'] = 'Ṱ'; + map!['Tcommaaccent'] = 'Ţ'; + map!['Tdotaccent'] = 'Ṫ'; + map!['Tdotbelow'] = 'Ṭ'; + map!['Tecyrillic'] = 'Т'; + map!['Tedescendercyrillic'] = 'Ҭ'; + map!['Tenroman'] = 'Ⅹ'; + map!['Tetsecyrillic'] = 'Ҵ'; + map!['Theta'] = 'Θ'; + map!['Thook'] = 'Ƭ'; + map!['Thorn'] = 'Þ'; + map!['Thornsmall'] = ''; + map!['Threeroman'] = 'Ⅲ'; + map!['Tildesmall'] = ''; + map!['Tiwnarmenian'] = 'Տ'; + map!['Tlinebelow'] = 'Ṯ'; + map!['Tmonospace'] = 'T'; + map!['Toarmenian'] = 'Թ'; + map!['Tonefive'] = 'Ƽ'; + map!['Tonesix'] = 'Ƅ'; + map!['Tonetwo'] = 'Ƨ'; + map!['Tretroflexhook'] = 'Ʈ'; + map!['Tsecyrillic'] = 'Ц'; + map!['Tshecyrillic'] = 'Ћ'; + map!['Tsmall'] = ''; + map!['Twelveroman'] = 'Ⅻ'; + map!['Tworoman'] = 'Ⅱ'; + map!['U'] = 'U'; + map!['Uacute'] = 'Ú'; + map!['Uacutesmall'] = ''; + map!['Ubreve'] = 'Ŭ'; + map!['Ucaron'] = 'Ǔ'; + map!['Ucircle'] = 'Ⓤ'; + map!['Ucircumflex'] = 'Û'; + map!['Ucircumflexbelow'] = 'Ṷ'; + map!['Ucircumflexsmall'] = ''; + map!['Ucyrillic'] = 'У'; + map!['Udblacute'] = 'Ű'; + map!['Udblgrave'] = 'Ȕ'; + map!['Udieresis'] = 'Ü'; + map!['Udieresisacute'] = 'Ǘ'; + map!['Udieresisbelow'] = 'Ṳ'; + map!['Udieresiscaron'] = 'Ǚ'; + map!['Udieresiscyrillic'] = 'Ӱ'; + map!['Udieresisgrave'] = 'Ǜ'; + map!['Udieresismacron'] = 'Ǖ'; + map!['Udieresissmall'] = ''; + map!['Udotbelow'] = 'Ụ'; + map!['Ugrave'] = 'Ù'; + map!['Ugravesmall'] = ''; + map!['Uhookabove'] = 'Ủ'; + map!['Uhorn'] = 'Ư'; + map!['Uhornacute'] = 'Ứ'; + map!['Uhorndotbelow'] = 'Ự'; + map!['Uhorngrave'] = 'Ừ'; + map!['Uhornhookabove'] = 'Ử'; + map!['Uhorntilde'] = 'Ữ'; + map!['Uhungarumlaut'] = 'Ű'; + map!['Uhungarumlautcyrillic'] = 'Ӳ'; + map!['Uinvertedbreve'] = 'Ȗ'; + map!['Ukcyrillic'] = 'Ѹ'; + map!['Umacron'] = 'Ū'; + map!['Umacroncyrillic'] = 'Ӯ'; + map!['Umacrondieresis'] = 'Ṻ'; + map!['Umonospace'] = 'U'; + map!['Uogonek'] = 'Ų'; + map!['Upsilon'] = 'Υ'; + map!['Upsilon1'] = 'ϒ'; + map!['Upsilonacutehooksymbolgreek'] = 'ϓ'; + map!['Upsilonafrican'] = 'Ʊ'; + map!['Upsilondieresis'] = 'Ϋ'; + map!['Upsilondieresishooksymbolgreek'] = 'ϔ'; + map!['Upsilonhooksymbol'] = 'ϒ'; + map!['Upsilontonos'] = 'Ύ'; + map!['Uring'] = 'Ů'; + map!['Ushortcyrillic'] = 'Ў'; + map!['Usmall'] = ''; + map!['Ustraightcyrillic'] = 'Ү'; + map!['Ustraightstrokecyrillic'] = 'Ұ'; + map!['Utilde'] = 'Ũ'; + map!['Utildeacute'] = 'Ṹ'; + map!['Utildebelow'] = 'Ṵ'; + map!['V'] = 'V'; + map!['Vcircle'] = 'Ⓥ'; + map!['Vdotbelow'] = 'Ṿ'; + map!['Vecyrillic'] = 'В'; + map!['Vewarmenian'] = 'Վ'; + map!['Vhook'] = 'Ʋ'; + map!['Vmonospace'] = 'V'; + map!['Voarmenian'] = 'Ո'; + map!['Vsmall'] = ''; + map!['Vtilde'] = 'Ṽ'; + map!['W'] = 'W'; + map!['Wacute'] = 'Ẃ'; + map!['Wcircle'] = 'Ⓦ'; + map!['Wcircumflex'] = 'Ŵ'; + map!['Wdieresis'] = 'Ẅ'; + map!['Wdotaccent'] = 'Ẇ'; + map!['Wdotbelow'] = 'Ẉ'; + map!['Wgrave'] = 'Ẁ'; + map!['Wmonospace'] = 'W'; + map!['Wsmall'] = ''; + map!['X'] = 'X'; + map!['Xcircle'] = 'Ⓧ'; + map!['Xdieresis'] = 'Ẍ'; + map!['Xdotaccent'] = 'Ẋ'; + map!['Xeharmenian'] = 'Խ'; + map!['Xi'] = 'Ξ'; + map!['Xmonospace'] = 'X'; + map!['Xsmall'] = ''; + map!['Y'] = 'Y'; + map!['Yacute'] = 'Ý'; + map!['Yacutesmall'] = ''; + map!['Yatcyrillic'] = 'Ѣ'; + map!['Ycircle'] = 'Ⓨ'; + map!['Ycircumflex'] = 'Ŷ'; + map!['Ydieresis'] = 'Ÿ'; + map!['Ydieresissmall'] = ''; + map!['Ydotaccent'] = 'Ẏ'; + map!['Ydotbelow'] = 'Ỵ'; + map!['Yericyrillic'] = 'Ы'; + map!['Yerudieresiscyrillic'] = 'Ӹ'; + map!['Ygrave'] = 'Ỳ'; + map!['Yhook'] = 'Ƴ'; + map!['Yhookabove'] = 'Ỷ'; + map!['Yiarmenian'] = 'Յ'; + map!['Yicyrillic'] = 'Ї'; + map!['Yiwnarmenian'] = 'Ւ'; + map!['Ymonospace'] = 'Y'; + map!['Ysmall'] = ''; + map!['Ytilde'] = 'Ỹ'; + map!['Yusbigcyrillic'] = 'Ѫ'; + map!['Yusbigiotifiedcyrillic'] = 'Ѭ'; + map!['Yuslittlecyrillic'] = 'Ѧ'; + map!['Yuslittleiotifiedcyrillic'] = 'Ѩ'; + map!['Z'] = 'Z'; + map!['Zaarmenian'] = 'Զ'; + map!['Zacute'] = 'Ź'; + map!['Zcaron'] = 'Ž'; + map!['Zcaronsmall'] = ''; + map!['Zcircle'] = 'Ⓩ'; + map!['Zcircumflex'] = 'Ẑ'; + map!['Zdot'] = 'Ż'; + map!['Zdotaccent'] = 'Ż'; + map!['Zdotbelow'] = 'Ẓ'; + map!['Zecyrillic'] = 'З'; + map!['Zedescendercyrillic'] = 'Ҙ'; + map!['Zedieresiscyrillic'] = 'Ӟ'; + map!['Zeta'] = 'Ζ'; + map!['Zhearmenian'] = 'Ժ'; + map!['Zhebrevecyrillic'] = 'Ӂ'; + map!['Zhecyrillic'] = 'Ж'; + map!['Zhedescendercyrillic'] = 'Җ'; + map!['Zhedieresiscyrillic'] = 'Ӝ'; + map!['Zlinebelow'] = 'Ẕ'; + map!['Zmonospace'] = 'Z'; + map!['Zsmall'] = ''; + map!['Zstroke'] = 'Ƶ'; + map!['a'] = 'a'; + map!['aabengali'] = 'আ'; + map!['aacute'] = 'á'; + map!['aadeva'] = 'आ'; + map!['aagujarati'] = 'આ'; + map!['aagurmukhi'] = 'ਆ'; + map!['aamatragurmukhi'] = 'ਾ'; + map!['aarusquare'] = '㌃'; + map!['aavowelsignbengali'] = 'া'; + map!['aavowelsigndeva'] = 'ा'; + map!['aavowelsigngujarati'] = 'ા'; + map!['abbreviationmarkarmenian'] = '՟'; + map!['abbreviationsigndeva'] = '॰'; + map!['abengali'] = 'অ'; + map!['abopomofo'] = 'ㄚ'; + map!['abreve'] = 'ă'; + map!['abreveacute'] = 'ắ'; + map!['abrevecyrillic'] = 'ӑ'; + map!['abrevedotbelow'] = 'ặ'; + map!['abrevegrave'] = 'ằ'; + map!['abrevehookabove'] = 'ẳ'; + map!['abrevetilde'] = 'ẵ'; + map!['acaron'] = 'ǎ'; + map!['acircle'] = 'ⓐ'; + map!['acircumflex'] = 'â'; + map!['acircumflexacute'] = 'ấ'; + map!['acircumflexdotbelow'] = 'ậ'; + map!['acircumflexgrave'] = 'ầ'; + map!['acircumflexhookabove'] = 'ẩ'; + map!['acircumflextilde'] = 'ẫ'; + map!['acute'] = '´'; + map!['acutebelowcmb'] = '̗'; + map!['acutecmb'] = '́'; + map!['acutecomb'] = '́'; + map!['acutedeva'] = '॔'; + map!['acutelowmod'] = 'ˏ'; + map!['acutetonecmb'] = '́'; + map!['acyrillic'] = 'а'; + map!['adblgrave'] = 'ȁ'; + map!['addakgurmukhi'] = 'ੱ'; + map!['adeva'] = 'अ'; + map!['adieresis'] = 'ä'; + map!['adieresiscyrillic'] = 'ӓ'; + map!['adieresismacron'] = 'ǟ'; + map!['adotbelow'] = 'ạ'; + map!['adotmacron'] = 'ǡ'; + map!['ae'] = 'æ'; + map!['aeacute'] = 'ǽ'; + map!['aekorean'] = 'ㅐ'; + map!['aemacron'] = 'ǣ'; + map!['afii00208'] = '―'; + map!['afii08941'] = '₤'; + map!['afii10017'] = 'А'; + map!['afii10018'] = 'Б'; + map!['afii10019'] = 'В'; + map!['afii10020'] = 'Г'; + map!['afii10021'] = 'Д'; + map!['afii10022'] = 'Е'; + map!['afii10023'] = 'Ё'; + map!['afii10024'] = 'Ж'; + map!['afii10025'] = 'З'; + map!['afii10026'] = 'И'; + map!['afii10027'] = 'Й'; + map!['afii10028'] = 'К'; + map!['afii10029'] = 'Л'; + map!['afii10030'] = 'М'; + map!['afii10031'] = 'Н'; + map!['afii10032'] = 'О'; + map!['afii10033'] = 'П'; + map!['afii10034'] = 'Р'; + map!['afii10035'] = 'С'; + map!['afii10036'] = 'Т'; + map!['afii10037'] = 'У'; + map!['afii10038'] = 'Ф'; + map!['afii10039'] = 'Х'; + map!['afii10040'] = 'Ц'; + map!['afii10041'] = 'Ч'; + map!['afii10042'] = 'Ш'; + map!['afii10043'] = 'Щ'; + map!['afii10044'] = 'Ъ'; + map!['afii10045'] = 'Ы'; + map!['afii10046'] = 'Ь'; + map!['afii10047'] = 'Э'; + map!['afii10048'] = 'Ю'; + map!['afii10049'] = 'Я'; + map!['afii10050'] = 'Ґ'; + map!['afii10051'] = 'Ђ'; + map!['afii10052'] = 'Ѓ'; + map!['afii10053'] = 'Є'; + map!['afii10054'] = 'Ѕ'; + map!['afii10055'] = 'І'; + map!['afii10056'] = 'Ї'; + map!['afii10057'] = 'Ј'; + map!['afii10058'] = 'Љ'; + map!['afii10059'] = 'Њ'; + map!['afii10060'] = 'Ћ'; + map!['afii10061'] = 'Ќ'; + map!['afii10062'] = 'Ў'; + map!['afii10063'] = ''; + map!['afii10064'] = ''; + map!['afii10065'] = 'а'; + map!['afii10066'] = 'б'; + map!['afii10067'] = 'в'; + map!['afii10068'] = 'г'; + map!['afii10069'] = 'д'; + map!['afii10070'] = 'е'; + map!['afii10071'] = 'ё'; + map!['afii10072'] = 'ж'; + map!['afii10073'] = 'з'; + map!['afii10074'] = 'и'; + map!['afii10075'] = 'й'; + map!['afii10076'] = 'к'; + map!['afii10077'] = 'л'; + map!['afii10078'] = 'м'; + map!['afii10079'] = 'н'; + map!['afii10080'] = 'о'; + map!['afii10081'] = 'п'; + map!['afii10082'] = 'р'; + map!['afii10083'] = 'с'; + map!['afii10084'] = 'т'; + map!['afii10085'] = 'у'; + map!['afii10086'] = 'ф'; + map!['afii10087'] = 'х'; + map!['afii10088'] = 'ц'; + map!['afii10089'] = 'ч'; + map!['afii10090'] = 'ш'; + map!['afii10091'] = 'щ'; + map!['afii10092'] = 'ъ'; + map!['afii10093'] = 'ы'; + map!['afii10094'] = 'ь'; + map!['afii10095'] = 'э'; + map!['afii10096'] = 'ю'; + map!['afii10097'] = 'я'; + map!['afii10098'] = 'ґ'; + map!['afii10099'] = 'ђ'; + map!['afii10100'] = 'ѓ'; + map!['afii10101'] = 'є'; + map!['afii10102'] = 'ѕ'; + map!['afii10103'] = 'і'; + map!['afii10104'] = 'ї'; + map!['afii10105'] = 'ј'; + map!['afii10106'] = 'љ'; + map!['afii10107'] = 'њ'; + map!['afii10108'] = 'ћ'; + map!['afii10109'] = 'ќ'; + map!['afii10110'] = 'ў'; + map!['afii10145'] = 'Џ'; + map!['afii10146'] = 'Ѣ'; + map!['afii10147'] = 'Ѳ'; + map!['afii10148'] = 'Ѵ'; + map!['afii10192'] = ''; + map!['afii10193'] = 'џ'; + map!['afii10194'] = 'ѣ'; + map!['afii10195'] = 'ѳ'; + map!['afii10196'] = 'ѵ'; + map!['afii10831'] = ''; + map!['afii10832'] = ''; + map!['afii10846'] = 'ә'; + map!['afii299'] = '‎'; + map!['afii300'] = '‏'; + map!['afii301'] = '‍'; + map!['afii57381'] = '٪'; + map!['afii57388'] = '،'; + map!['afii57392'] = '٠'; + map!['afii57393'] = '١'; + map!['afii57394'] = '٢'; + map!['afii57395'] = '٣'; + map!['afii57396'] = '٤'; + map!['afii57397'] = '٥'; + map!['afii57398'] = '٦'; + map!['afii57399'] = '٧'; + map!['afii57400'] = '٨'; + map!['afii57401'] = '٩'; + map!['afii57403'] = '؛'; + map!['afii57407'] = '؟'; + map!['afii57409'] = 'ء'; + map!['afii57410'] = 'آ'; + map!['afii57411'] = 'أ'; + map!['afii57412'] = 'ؤ'; + map!['afii57413'] = 'إ'; + map!['afii57414'] = 'ئ'; + map!['afii57415'] = 'ا'; + map!['afii57416'] = 'ب'; + map!['afii57417'] = 'ة'; + map!['afii57418'] = 'ت'; + map!['afii57419'] = 'ث'; + map!['afii57420'] = 'ج'; + map!['afii57421'] = 'ح'; + map!['afii57422'] = 'خ'; + map!['afii57423'] = 'د'; + map!['afii57424'] = 'ذ'; + map!['afii57425'] = 'ر'; + map!['afii57426'] = 'ز'; + map!['afii57427'] = 'س'; + map!['afii57428'] = 'ش'; + map!['afii57429'] = 'ص'; + map!['afii57430'] = 'ض'; + map!['afii57431'] = 'ط'; + map!['afii57432'] = 'ظ'; + map!['afii57433'] = 'ع'; + map!['afii57434'] = 'غ'; + map!['afii57440'] = 'ـ'; + map!['afii57441'] = 'ف'; + map!['afii57442'] = 'ق'; + map!['afii57443'] = 'ك'; + map!['afii57444'] = 'ل'; + map!['afii57445'] = 'م'; + map!['afii57446'] = 'ن'; + map!['afii57448'] = 'و'; + map!['afii57449'] = 'ى'; + map!['afii57450'] = 'ي'; + map!['afii57451'] = 'ً'; + map!['afii57452'] = 'ٌ'; + map!['afii57453'] = 'ٍ'; + map!['afii57454'] = 'َ'; + map!['afii57455'] = 'ُ'; + map!['afii57456'] = 'ِ'; + map!['afii57457'] = 'ّ'; + map!['afii57458'] = 'ْ'; + map!['afii57470'] = 'ه'; + map!['afii57505'] = 'ڤ'; + map!['afii57506'] = 'پ'; + map!['afii57507'] = 'چ'; + map!['afii57508'] = 'ژ'; + map!['afii57509'] = 'گ'; + map!['afii57511'] = 'ٹ'; + map!['afii57512'] = 'ڈ'; + map!['afii57513'] = 'ڑ'; + map!['afii57514'] = 'ں'; + map!['afii57519'] = 'ے'; + map!['afii57534'] = 'ە'; + map!['afii57636'] = '₪'; + map!['afii57645'] = '־'; + map!['afii57658'] = '׃'; + map!['afii57664'] = 'א'; + map!['afii57665'] = 'ב'; + map!['afii57666'] = 'ג'; + map!['afii57667'] = 'ד'; + map!['afii57668'] = 'ה'; + map!['afii57669'] = 'ו'; + map!['afii57670'] = 'ז'; + map!['afii57671'] = 'ח'; + map!['afii57672'] = 'ט'; + map!['afii57673'] = 'י'; + map!['afii57674'] = 'ך'; + map!['afii57675'] = 'כ'; + map!['afii57676'] = 'ל'; + map!['afii57677'] = 'ם'; + map!['afii57678'] = 'מ'; + map!['afii57679'] = 'ן'; + map!['afii57680'] = 'נ'; + map!['afii57681'] = 'ס'; + map!['afii57682'] = 'ע'; + map!['afii57683'] = 'ף'; + map!['afii57684'] = 'פ'; + map!['afii57685'] = 'ץ'; + map!['afii57686'] = 'צ'; + map!['afii57687'] = 'ק'; + map!['afii57688'] = 'ר'; + map!['afii57689'] = 'ש'; + map!['afii57690'] = 'ת'; + map!['afii57694'] = 'שׁ'; + map!['afii57695'] = 'שׂ'; + map!['afii57700'] = 'וֹ'; + map!['afii57705'] = 'ײַ'; + map!['afii57716'] = 'װ'; + map!['afii57717'] = 'ױ'; + map!['afii57718'] = 'ײ'; + map!['afii57723'] = 'וּ'; + map!['afii57793'] = 'ִ'; + map!['afii57794'] = 'ֵ'; + map!['afii57795'] = 'ֶ'; + map!['afii57796'] = 'ֻ'; + map!['afii57797'] = 'ָ'; + map!['afii57798'] = 'ַ'; + map!['afii57799'] = 'ְ'; + map!['afii57800'] = 'ֲ'; + map!['afii57801'] = 'ֱ'; + map!['afii57802'] = 'ֳ'; + map!['afii57803'] = 'ׂ'; + map!['afii57804'] = 'ׁ'; + map!['afii57806'] = 'ֹ'; + map!['afii57807'] = 'ּ'; + map!['afii57839'] = 'ֽ'; + map!['afii57841'] = 'ֿ'; + map!['afii57842'] = '׀'; + map!['afii57929'] = 'ʼ'; + map!['afii61248'] = '℅'; + map!['afii61289'] = 'ℓ'; + map!['afii61352'] = '№'; + map!['afii61573'] = '‬'; + map!['afii61574'] = '‭'; + map!['afii61575'] = '‮'; + map!['afii61664'] = '‌'; + map!['afii63167'] = '٭'; + map!['afii64937'] = 'ʽ'; + map!['agrave'] = 'à'; + map!['agujarati'] = 'અ'; + map!['agurmukhi'] = 'ਅ'; + map!['ahiragana'] = 'あ'; + map!['ahookabove'] = 'ả'; + map!['aibengali'] = 'ঐ'; + map!['aibopomofo'] = 'ㄞ'; + map!['aideva'] = 'ऐ'; + map!['aiecyrillic'] = 'ӕ'; + map!['aigujarati'] = 'ઐ'; + map!['aigurmukhi'] = 'ਐ'; + map!['aimatragurmukhi'] = 'ੈ'; + map!['ainarabic'] = 'ع'; + map!['ainfinalarabic'] = 'ﻊ'; + map!['aininitialarabic'] = 'ﻋ'; + map!['ainmedialarabic'] = 'ﻌ'; + map!['ainvertedbreve'] = 'ȃ'; + map!['aivowelsignbengali'] = 'ৈ'; + map!['aivowelsigndeva'] = 'ै'; + map!['aivowelsigngujarati'] = 'ૈ'; + map!['akatakana'] = 'ア'; + map!['akatakanahalfwidth'] = 'ア'; + map!['akorean'] = 'ㅏ'; + map!['alef'] = 'א'; + map!['alefarabic'] = 'ا'; + map!['alefdageshhebrew'] = 'אּ'; + map!['aleffinalarabic'] = 'ﺎ'; + map!['alefhamzaabovearabic'] = 'أ'; + map!['alefhamzaabovefinalarabic'] = 'ﺄ'; + map!['alefhamzabelowarabic'] = 'إ'; + map!['alefhamzabelowfinalarabic'] = 'ﺈ'; + map!['alefhebrew'] = 'א'; + map!['aleflamedhebrew'] = 'ﭏ'; + map!['alefmaddaabovearabic'] = 'آ'; + map!['alefmaddaabovefinalarabic'] = 'ﺂ'; + map!['alefmaksuraarabic'] = 'ى'; + map!['alefmaksurafinalarabic'] = 'ﻰ'; + map!['alefmaksurainitialarabic'] = 'ﻳ'; + map!['alefmaksuramedialarabic'] = 'ﻴ'; + map!['alefpatahhebrew'] = 'אַ'; + map!['alefqamatshebrew'] = 'אָ'; + map!['aleph'] = 'ℵ'; + map!['allequal'] = '≌'; + map!['alpha'] = 'α'; + map!['alphatonos'] = 'ά'; + map!['amacron'] = 'ā'; + map!['amonospace'] = 'a'; + map!['ampersand'] = '&'; + map!['ampersandmonospace'] = '&'; + map!['ampersandsmall'] = ''; + map!['amsquare'] = '㏂'; + map!['anbopomofo'] = 'ㄢ'; + map!['angbopomofo'] = 'ㄤ'; + map!['angkhankhuthai'] = '๚'; + map!['angle'] = '∠'; + map!['angbracketleft'] = '〈'; + map!['anglebracketleft'] = '〈'; + map!['anglebracketleftvertical'] = '︿'; + map!['angbracketright'] = '〉'; + map!['anglebracketright'] = '〉'; + map!['anglebracketrightvertical'] = '﹀'; + map!['angleleft'] = '〈'; + map!['angleright'] = '〉'; + map!['angstrom'] = 'Å'; + map!['anoteleia'] = '·'; + map!['anudattadeva'] = '॒'; + map!['anusvarabengali'] = 'ং'; + map!['anusvaradeva'] = 'ं'; + map!['anusvaragujarati'] = 'ં'; + map!['aogonek'] = 'ą'; + map!['apaatosquare'] = '㌀'; + map!['aparen'] = '⒜'; + map!['apostrophearmenian'] = '՚'; + map!['apostrophemod'] = 'ʼ'; + map!['apple'] = ''; + map!['approaches'] = '≐'; + map!['approxequal'] = '≈'; + map!['approxequalorimage'] = '≒'; + map!['approximatelyequal'] = '≅'; + map!['araeaekorean'] = 'ㆎ'; + map!['araeakorean'] = 'ㆍ'; + map!['arc'] = '⌒'; + map!['arighthalfring'] = 'ẚ'; + map!['aring'] = 'å'; + map!['aringacute'] = 'ǻ'; + map!['aringbelow'] = 'ḁ'; + map!['arrowboth'] = '↔'; + map!['arrowdashdown'] = '⇣'; + map!['arrowdashleft'] = '⇠'; + map!['arrowdashright'] = '⇢'; + map!['arrowdashup'] = '⇡'; + map!['arrowdblboth'] = '⇔'; + map!['arrowdbldown'] = '⇓'; + map!['arrowdblleft'] = '⇐'; + map!['arrowdblright'] = '⇒'; + map!['arrowdblup'] = '⇑'; + map!['arrowdown'] = '↓'; + map!['arrowdownleft'] = '↙'; + map!['arrowdownright'] = '↘'; + map!['arrowdownwhite'] = '⇩'; + map!['arrowheaddownmod'] = '˅'; + map!['arrowheadleftmod'] = '˂'; + map!['arrowheadrightmod'] = '˃'; + map!['arrowheadupmod'] = '˄'; + map!['arrowhorizex'] = ''; + map!['arrowleft'] = '←'; + map!['arrowleftdbl'] = '⇐'; + map!['arrowleftdblstroke'] = '⇍'; + map!['arrowleftoverright'] = '⇆'; + map!['arrowleftwhite'] = '⇦'; + map!['arrowright'] = '→'; + map!['arrowrightdblstroke'] = '⇏'; + map!['arrowrightheavy'] = '➞'; + map!['arrowrightoverleft'] = '⇄'; + map!['arrowrightwhite'] = '⇨'; + map!['arrowtableft'] = '⇤'; + map!['arrowtabright'] = '⇥'; + map!['arrowup'] = '↑'; + map!['arrowupdn'] = '↕'; + map!['arrowupdnbse'] = '↨'; + map!['arrowupdownbase'] = '↨'; + map!['arrowupleft'] = '↖'; + map!['arrowupleftofdown'] = '⇅'; + map!['arrowupright'] = '↗'; + map!['arrowupwhite'] = '⇧'; + map!['arrowvertex'] = ''; + map!['asciicircum'] = '^'; + map!['asciicircummonospace'] = '^'; + map!['asciitilde'] = '~'; + map!['asciitildemonospace'] = '~'; + map!['ascript'] = 'ɑ'; + map!['ascriptturned'] = 'ɒ'; + map!['asmallhiragana'] = 'ぁ'; + map!['asmallkatakana'] = 'ァ'; + map!['asmallkatakanahalfwidth'] = 'ァ'; + map!['asterisk'] = '*'; + map!['asteriskaltonearabic'] = '٭'; + map!['asteriskarabic'] = '٭'; + map!['asteriskmath'] = '∗'; + map!['asteriskmonospace'] = '*'; + map!['asterisksmall'] = '﹡'; + map!['asterism'] = '⁂'; + map!['asuperior'] = ''; + map!['asymptoticallyequal'] = '≃'; + map!['at'] = '@'; + map!['atilde'] = 'ã'; + map!['atmonospace'] = '@'; + map!['atsmall'] = '﹫'; + map!['aturned'] = 'ɐ'; + map!['aubengali'] = 'ঔ'; + map!['aubopomofo'] = 'ㄠ'; + map!['audeva'] = 'औ'; + map!['augujarati'] = 'ઔ'; + map!['augurmukhi'] = 'ਔ'; + map!['aulengthmarkbengali'] = 'ৗ'; + map!['aumatragurmukhi'] = 'ੌ'; + map!['auvowelsignbengali'] = 'ৌ'; + map!['auvowelsigndeva'] = 'ौ'; + map!['auvowelsigngujarati'] = 'ૌ'; + map!['avagrahadeva'] = 'ऽ'; + map!['aybarmenian'] = 'ա'; + map!['ayin'] = 'ע'; + map!['ayinaltonehebrew'] = 'ﬠ'; + map!['ayinhebrew'] = 'ע'; + map!['b'] = 'b'; + map!['babengali'] = 'ব'; + map!['backslash'] = '\\'; + map!['backslashmonospace'] = '\'; + map!['badeva'] = 'ब'; + map!['bagujarati'] = 'બ'; + map!['bagurmukhi'] = 'ਬ'; + map!['bahiragana'] = 'ば'; + map!['bahtthai'] = '฿'; + map!['bakatakana'] = 'バ'; + map!['bar'] = '|'; + map!['barmonospace'] = '|'; + map!['bbopomofo'] = 'ㄅ'; + map!['bcircle'] = 'ⓑ'; + map!['bdotaccent'] = 'ḃ'; + map!['bdotbelow'] = 'ḅ'; + map!['beamedsixteenthnotes'] = '♬'; + map!['because'] = '∵'; + map!['becyrillic'] = 'б'; + map!['beharabic'] = 'ب'; + map!['behfinalarabic'] = 'ﺐ'; + map!['behinitialarabic'] = 'ﺑ'; + map!['behiragana'] = 'べ'; + map!['behmedialarabic'] = 'ﺒ'; + map!['behmeeminitialarabic'] = 'ﲟ'; + map!['behmeemisolatedarabic'] = 'ﰈ'; + map!['behnoonfinalarabic'] = 'ﱭ'; + map!['bekatakana'] = 'ベ'; + map!['benarmenian'] = 'բ'; + map!['bet'] = 'ב'; + map!['beta'] = 'β'; + map!['betasymbolgreek'] = 'ϐ'; + map!['betdagesh'] = 'בּ'; + map!['betdageshhebrew'] = 'בּ'; + map!['bethebrew'] = 'ב'; + map!['betrafehebrew'] = 'בֿ'; + map!['bhabengali'] = 'ভ'; + map!['bhadeva'] = 'भ'; + map!['bhagujarati'] = 'ભ'; + map!['bhagurmukhi'] = 'ਭ'; + map!['bhook'] = 'ɓ'; + map!['bihiragana'] = 'び'; + map!['bikatakana'] = 'ビ'; + map!['bilabialclick'] = 'ʘ'; + map!['bindigurmukhi'] = 'ਂ'; + map!['birusquare'] = '㌱'; + map!['blackcircle'] = '●'; + map!['blackdiamond'] = '◆'; + map!['blackdownpointingtriangle'] = '▼'; + map!['blackleftpointingpointer'] = '◄'; + map!['blackleftpointingtriangle'] = '◀'; + map!['blacklenticularbracketleft'] = '【'; + map!['blacklenticularbracketleftvertical'] = '︻'; + map!['blacklenticularbracketright'] = '】'; + map!['blacklenticularbracketrightvertical'] = '︼'; + map!['blacklowerlefttriangle'] = '◣'; + map!['blacklowerrighttriangle'] = '◢'; + map!['blackrectangle'] = '▬'; + map!['blackrightpointingpointer'] = '►'; + map!['blackrightpointingtriangle'] = '▶'; + map!['blacksmallsquare'] = '▪'; + map!['blacksmilingface'] = '☻'; + map!['blacksquare'] = '■'; + map!['blackstar'] = '★'; + map!['blackupperlefttriangle'] = '◤'; + map!['blackupperrighttriangle'] = '◥'; + map!['blackuppointingsmalltriangle'] = '▴'; + map!['blackuppointingtriangle'] = '▲'; + map!['blank'] = '␣'; + map!['blinebelow'] = 'ḇ'; + map!['block'] = '█'; + map!['bmonospace'] = 'b'; + map!['bobaimaithai'] = 'บ'; + map!['bohiragana'] = 'ぼ'; + map!['bokatakana'] = 'ボ'; + map!['bparen'] = '⒝'; + map!['bqsquare'] = '㏃'; + map!['braceex'] = ''; + map!['braceleft'] = '{'; + map!['braceleftbt'] = ''; + map!['braceleftmid'] = ''; + map!['braceleftmonospace'] = '{'; + map!['braceleftsmall'] = '﹛'; + map!['bracelefttp'] = ''; + map!['braceleftvertical'] = '︷'; + map!['braceright'] = '}'; + map!['bracerightbt'] = ''; + map!['bracerightmid'] = ''; + map!['bracerightmonospace'] = '}'; + map!['bracerightsmall'] = '﹜'; + map!['bracerighttp'] = ''; + map!['bracerightvertical'] = '︸'; + map!['bracketleft'] = '['; + map!['bracketleftbt'] = ''; + map!['bracketleftex'] = ''; + map!['bracketleftmonospace'] = '['; + map!['bracketlefttp'] = ''; + map!['bracketright'] = ']'; + map!['bracketrightbt'] = ''; + map!['bracketrightex'] = ''; + map!['bracketrightmonospace'] = ']'; + map!['bracketrighttp'] = ''; + map!['breve'] = '˘'; + map!['brevebelowcmb'] = '̮'; + map!['brevecmb'] = '̆'; + map!['breveinvertedbelowcmb'] = '̯'; + map!['breveinvertedcmb'] = '̑'; + map!['breveinverteddoublecmb'] = '͡'; + map!['bridgebelowcmb'] = '̪'; + map!['bridgeinvertedbelowcmb'] = '̺'; + map!['brokenbar'] = '¦'; + map!['bstroke'] = 'ƀ'; + map!['bsuperior'] = ''; + map!['btopbar'] = 'ƃ'; + map!['buhiragana'] = 'ぶ'; + map!['bukatakana'] = 'ブ'; + map!['bullet'] = '•'; + map!['bulletinverse'] = '◘'; + map!['bulletoperator'] = '∙'; + map!['bullseye'] = '◎'; + map!['c'] = 'c'; + map!['caarmenian'] = 'ծ'; + map!['cabengali'] = 'চ'; + map!['cacute'] = 'ć'; + map!['cadeva'] = 'च'; + map!['cagujarati'] = 'ચ'; + map!['cagurmukhi'] = 'ਚ'; + map!['calsquare'] = '㎈'; + map!['candrabindubengali'] = 'ঁ'; + map!['candrabinducmb'] = '̐'; + map!['candrabindudeva'] = 'ँ'; + map!['candrabindugujarati'] = 'ઁ'; + map!['capslock'] = '⇪'; + map!['careof'] = '℅'; + map!['caron'] = 'ˇ'; + map!['caronbelowcmb'] = '̬'; + map!['caroncmb'] = '̌'; + map!['carriagereturn'] = '↵'; + map!['cbopomofo'] = 'ㄘ'; + map!['ccaron'] = 'č'; + map!['ccedilla'] = 'ç'; + map!['ccedillaacute'] = 'ḉ'; + map!['ccircle'] = 'ⓒ'; + map!['ccircumflex'] = 'ĉ'; + map!['ccurl'] = 'ɕ'; + map!['cdot'] = 'ċ'; + map!['cdotaccent'] = 'ċ'; + map!['cdsquare'] = '㏅'; + map!['cedilla'] = '¸'; + map!['cedillacmb'] = '̧'; + map!['cent'] = '¢'; + map!['centigrade'] = '℃'; + map!['centinferior'] = ''; + map!['centmonospace'] = '¢'; + map!['centoldstyle'] = ''; + map!['centsuperior'] = ''; + map!['chaarmenian'] = 'չ'; + map!['chabengali'] = 'ছ'; + map!['chadeva'] = 'छ'; + map!['chagujarati'] = 'છ'; + map!['chagurmukhi'] = 'ਛ'; + map!['chbopomofo'] = 'ㄔ'; + map!['cheabkhasiancyrillic'] = 'ҽ'; + map!['checkmark'] = '✓'; + map!['checyrillic'] = 'ч'; + map!['chedescenderabkhasiancyrillic'] = 'ҿ'; + map!['chedescendercyrillic'] = 'ҷ'; + map!['chedieresiscyrillic'] = 'ӵ'; + map!['cheharmenian'] = 'ճ'; + map!['chekhakassiancyrillic'] = 'ӌ'; + map!['cheverticalstrokecyrillic'] = 'ҹ'; + map!['chi'] = 'χ'; + map!['chieuchacirclekorean'] = '㉷'; + map!['chieuchaparenkorean'] = '㈗'; + map!['chieuchcirclekorean'] = '㉩'; + map!['chieuchkorean'] = 'ㅊ'; + map!['chieuchparenkorean'] = '㈉'; + map!['chochangthai'] = 'ช'; + map!['chochanthai'] = 'จ'; + map!['chochingthai'] = 'ฉ'; + map!['chochoethai'] = 'ฌ'; + map!['chook'] = 'ƈ'; + map!['cieucacirclekorean'] = '㉶'; + map!['cieucaparenkorean'] = '㈖'; + map!['cieuccirclekorean'] = '㉨'; + map!['cieuckorean'] = 'ㅈ'; + map!['cieucparenkorean'] = '㈈'; + map!['cieucuparenkorean'] = '㈜'; + map!['circle'] = '○'; + map!['circlemultiply'] = '⊗'; + map!['circleot'] = '⊙'; + map!['circleplus'] = '⊕'; + map!['circlepostalmark'] = '〶'; + map!['circlewithlefthalfblack'] = '◐'; + map!['circlewithrighthalfblack'] = '◑'; + map!['circumflex'] = 'ˆ'; + map!['circumflexbelowcmb'] = '̭'; + map!['circumflexcmb'] = '̂'; + map!['clear'] = '⌧'; + map!['clickalveolar'] = 'ǂ'; + map!['clickdental'] = 'ǀ'; + map!['clicklateral'] = 'ǁ'; + map!['clickretroflex'] = 'ǃ'; + map!['club'] = '♣'; + map!['clubsuitblack'] = '♣'; + map!['clubsuitwhite'] = '♧'; + map!['cmcubedsquare'] = '㎤'; + map!['cmonospace'] = 'c'; + map!['cmsquaredsquare'] = '㎠'; + map!['coarmenian'] = 'ց'; + map!['colon'] = ':'; + map!['colonmonetary'] = '₡'; + map!['colonmonospace'] = ':'; + map!['colonsign'] = '₡'; + map!['colonsmall'] = '﹕'; + map!['colontriangularhalfmod'] = 'ˑ'; + map!['colontriangularmod'] = 'ː'; + map!['comma'] = ','; + map!['commaabovecmb'] = '̓'; + map!['commaaboverightcmb'] = '̕'; + map!['commaaccent'] = ''; + map!['commaarabic'] = '،'; + map!['commaarmenian'] = '՝'; + map!['commainferior'] = ''; + map!['commamonospace'] = ','; + map!['commareversedabovecmb'] = '̔'; + map!['commareversedmod'] = 'ʽ'; + map!['commasmall'] = '﹐'; + map!['commasuperior'] = ''; + map!['commaturnedabovecmb'] = '̒'; + map!['commaturnedmod'] = 'ʻ'; + map!['compass'] = '☼'; + map!['congruent'] = '≅'; + map!['contourintegral'] = '∮'; + map!['control'] = '⌃'; + map!['controlACK'] = '\u0006'; + map!['controlBEL'] = '\a'; + map!['controlBS'] = '\b'; + map!['controlCAN'] = '\u0018'; + map!['controlCR'] = '\r'; + map!['controlDC1'] = '\u0011'; + map!['controlDC2'] = '\u0012'; + map!['controlDC3'] = '\u0013'; + map!['controlDC4'] = '\u0014'; + map!['controlDEL'] = '\u007f'; + map!['controlDLE'] = '\u0010'; + map!['controlEM'] = '\u0019'; + map!['controlENQ'] = '\u0005'; + map!['controlEOT'] = '\u0004'; + map!['controlESC'] = '\u001b'; + map!['controlETB'] = '\u0017'; + map!['controlETX'] = '\u0003'; + map!['controlFF'] = '\f'; + map!['controlFS'] = '\u001c'; + map!['controlGS'] = '\u001d'; + map!['controlHT'] = '\t'; + map!['controlLF'] = '\n'; + map!['controlNAK'] = '\u0015'; + map!['controlRS'] = '\u001e'; + map!['controlSI'] = '\u000f'; + map!['controlSO'] = '\u000e'; + map!['controlSOT'] = '\u0002'; + map!['controlSTX'] = '\u0001'; + map!['controlSUB'] = '\u001a'; + map!['controlSYN'] = '\u0016'; + map!['controlUS'] = '\u001f'; + map!['controlVT'] = '\v'; + map!['copyright'] = '©'; + map!['copyrightsans'] = ''; + map!['copyrightserif'] = ''; + map!['cornerbracketleft'] = '「'; + map!['cornerbracketlefthalfwidth'] = '「'; + map!['cornerbracketleftvertical'] = '﹁'; + map!['cornerbracketright'] = '」'; + map!['cornerbracketrighthalfwidth'] = '」'; + map!['cornerbracketrightvertical'] = '﹂'; + map!['corporationsquare'] = '㍿'; + map!['cosquare'] = '㏇'; + map!['coverkgsquare'] = '㏆'; + map!['cparen'] = '⒞'; + map!['cruzeiro'] = '₢'; + map!['cstretched'] = 'ʗ'; + map!['curlyand'] = '⋏'; + map!['curlyor'] = '⋎'; + map!['currency'] = '¤'; + map!['cyrBreve'] = ''; + map!['cyrFlex'] = ''; + map!['cyrbreve'] = ''; + map!['cyrflex'] = ''; + map!['d'] = 'd'; + map!['daarmenian'] = 'դ'; + map!['dabengali'] = 'দ'; + map!['dadarabic'] = 'ض'; + map!['dadeva'] = 'द'; + map!['dadfinalarabic'] = 'ﺾ'; + map!['dadinitialarabic'] = 'ﺿ'; + map!['dadmedialarabic'] = 'ﻀ'; + map!['dagesh'] = 'ּ'; + map!['dageshhebrew'] = 'ּ'; + map!['dagger'] = '†'; + map!['daggerdbl'] = '‡'; + map!['dagujarati'] = 'દ'; + map!['dagurmukhi'] = 'ਦ'; + map!['dahiragana'] = 'だ'; + map!['dakatakana'] = 'ダ'; + map!['dalarabic'] = 'د'; + map!['dalet'] = 'ד'; + map!['daletdagesh'] = 'דּ'; + map!['daletdageshhebrew'] = 'דּ'; + map!['dalethebrew'] = 'ד'; + map!['dalfinalarabic'] = 'ﺪ'; + map!['dammaarabic'] = 'ُ'; + map!['dammalowarabic'] = 'ُ'; + map!['dammatanaltonearabic'] = 'ٌ'; + map!['dammatanarabic'] = 'ٌ'; + map!['danda'] = '।'; + map!['dargahebrew'] = '֧'; + map!['dargalefthebrew'] = '֧'; + map!['dasiapneumatacyrilliccmb'] = '҅'; + map!['dblGrave'] = ''; + map!['dblanglebracketleft'] = '《'; + map!['dblanglebracketleftvertical'] = '︽'; + map!['dblanglebracketright'] = '》'; + map!['dblanglebracketrightvertical'] = '︾'; + map!['dblarchinvertedbelowcmb'] = '̫'; + map!['dblarrowleft'] = '⇔'; + map!['dblarrowright'] = '⇒'; + map!['dbldanda'] = '॥'; + map!['dblgrave'] = ''; + map!['dblgravecmb'] = '̏'; + map!['dblintegral'] = '∬'; + map!['dbllowline'] = '‗'; + map!['dbllowlinecmb'] = '̳'; + map!['dbloverlinecmb'] = '̿'; + map!['dblprimemod'] = 'ʺ'; + map!['dblverticalbar'] = '‖'; + map!['dblverticallineabovecmb'] = '̎'; + map!['dbopomofo'] = 'ㄉ'; + map!['dbsquare'] = '㏈'; + map!['dcaron'] = 'ď'; + map!['dcedilla'] = 'ḑ'; + map!['dcircle'] = 'ⓓ'; + map!['dcircumflexbelow'] = 'ḓ'; + map!['dcroat'] = 'đ'; + map!['ddabengali'] = 'ড'; + map!['ddadeva'] = 'ड'; + map!['ddagujarati'] = 'ડ'; + map!['ddagurmukhi'] = 'ਡ'; + map!['ddalarabic'] = 'ڈ'; + map!['ddalfinalarabic'] = 'ﮉ'; + map!['dddhadeva'] = 'ड़'; + map!['ddhabengali'] = 'ঢ'; + map!['ddhadeva'] = 'ढ'; + map!['ddhagujarati'] = 'ઢ'; + map!['ddhagurmukhi'] = 'ਢ'; + map!['ddotaccent'] = 'ḋ'; + map!['ddotbelow'] = 'ḍ'; + map!['decimalseparatorarabic'] = '٫'; + map!['decimalseparatorpersian'] = '٫'; + map!['decyrillic'] = 'д'; + map!['degree'] = '°'; + map!['dehihebrew'] = '֭'; + map!['dehiragana'] = 'で'; + map!['deicoptic'] = 'ϯ'; + map!['dekatakana'] = 'デ'; + map!['deleteleft'] = '⌫'; + map!['deleteright'] = '⌦'; + map!['delta'] = 'δ'; + map!['deltaturned'] = 'ƍ'; + map!['denominatorminusonenumeratorbengali'] = '৸'; + map!['dezh'] = 'ʤ'; + map!['dhabengali'] = 'ধ'; + map!['dhadeva'] = 'ध'; + map!['dhagujarati'] = 'ધ'; + map!['dhagurmukhi'] = 'ਧ'; + map!['dhook'] = 'ɗ'; + map!['dialytikatonos'] = '΅'; + map!['dialytikatonoscmb'] = '̈́'; + map!['diamond'] = '♦'; + map!['diamondsuitwhite'] = '♢'; + map!['dieresis'] = '¨'; + map!['dieresisacute'] = ''; + map!['dieresisbelowcmb'] = '̤'; + map!['dieresiscmb'] = '̈'; + map!['dieresisgrave'] = ''; + map!['dieresistonos'] = '΅'; + map!['dihiragana'] = 'ぢ'; + map!['dikatakana'] = 'ヂ'; + map!['dittomark'] = '〃'; + map!['divide'] = '÷'; + map!['divides'] = '∣'; + map!['divisionslash'] = '∕'; + map!['djecyrillic'] = 'ђ'; + map!['dkshade'] = '▓'; + map!['dlinebelow'] = 'ḏ'; + map!['dlsquare'] = '㎗'; + map!['dmacron'] = 'đ'; + map!['dmonospace'] = 'd'; + map!['dnblock'] = '▄'; + map!['dochadathai'] = 'ฎ'; + map!['dodekthai'] = 'ด'; + map!['dohiragana'] = 'ど'; + map!['dokatakana'] = 'ド'; + map!['dollar'] = '\$'; + map!['dollarinferior'] = ''; + map!['dollarmonospace'] = '$'; + map!['dollaroldstyle'] = ''; + map!['dollarsmall'] = '﹩'; + map!['dollarsuperior'] = ''; + map!['dong'] = '₫'; + map!['dorusquare'] = '㌦'; + map!['dotaccent'] = '˙'; + map!['dotaccentcmb'] = '̇'; + map!['dotbelowcmb'] = '̣'; + map!['dotbelowcomb'] = '̣'; + map!['dotkatakana'] = '・'; + map!['dotlessi'] = 'ı'; + map!['dotlessj'] = ''; + map!['dotlessjstrokehook'] = 'ʄ'; + map!['dotmath'] = '⋅'; + map!['dottedcircle'] = '◌'; + map!['doubleyodpatah'] = 'ײַ'; + map!['doubleyodpatahhebrew'] = 'ײַ'; + map!['downtackbelowcmb'] = '̞'; + map!['downtackmod'] = '˕'; + map!['dparen'] = '⒟'; + map!['dsuperior'] = ''; + map!['dtail'] = 'ɖ'; + map!['dtopbar'] = 'ƌ'; + map!['duhiragana'] = 'づ'; + map!['dukatakana'] = 'ヅ'; + map!['dz'] = 'dz'; + map!['dzaltone'] = 'ʣ'; + map!['dzcaron'] = 'dž'; + map!['dzcurl'] = 'ʥ'; + map!['dzeabkhasiancyrillic'] = 'ӡ'; + map!['dzecyrillic'] = 'ѕ'; + map!['dzhecyrillic'] = 'џ'; + map!['e'] = 'e'; + map!['eacute'] = 'é'; + map!['earth'] = '♁'; + map!['ebengali'] = 'এ'; + map!['ebopomofo'] = 'ㄜ'; + map!['ebreve'] = 'ĕ'; + map!['ecandradeva'] = 'ऍ'; + map!['ecandragujarati'] = 'ઍ'; + map!['ecandravowelsigndeva'] = 'ॅ'; + map!['ecandravowelsigngujarati'] = 'ૅ'; + map!['ecaron'] = 'ě'; + map!['ecedillabreve'] = 'ḝ'; + map!['echarmenian'] = 'ե'; + map!['echyiwnarmenian'] = 'և'; + map!['ecircle'] = 'ⓔ'; + map!['ecircumflex'] = 'ê'; + map!['ecircumflexacute'] = 'ế'; + map!['ecircumflexbelow'] = 'ḙ'; + map!['ecircumflexdotbelow'] = 'ệ'; + map!['ecircumflexgrave'] = 'ề'; + map!['ecircumflexhookabove'] = 'ể'; + map!['ecircumflextilde'] = 'ễ'; + map!['ecyrillic'] = 'є'; + map!['edblgrave'] = 'ȅ'; + map!['edeva'] = 'ए'; + map!['edieresis'] = 'ë'; + map!['edot'] = 'ė'; + map!['edotaccent'] = 'ė'; + map!['edotbelow'] = 'ẹ'; + map!['eegurmukhi'] = 'ਏ'; + map!['eematragurmukhi'] = 'ੇ'; + map!['efcyrillic'] = 'ф'; + map!['egrave'] = 'è'; + map!['egujarati'] = 'એ'; + map!['eharmenian'] = 'է'; + map!['ehbopomofo'] = 'ㄝ'; + map!['ehiragana'] = 'え'; + map!['ehookabove'] = 'ẻ'; + map!['eibopomofo'] = 'ㄟ'; + map!['eight'] = '8'; + map!['eightarabic'] = '٨'; + map!['eightbengali'] = '৮'; + map!['eightcircle'] = '⑧'; + map!['eightcircleinversesansserif'] = '➑'; + map!['eightdeva'] = '८'; + map!['eighteencircle'] = '⑱'; + map!['eighteenparen'] = '⒅'; + map!['eighteenperiod'] = '⒙'; + map!['eightgujarati'] = '૮'; + map!['eightgurmukhi'] = '੮'; + map!['eighthackarabic'] = '٨'; + map!['eighthangzhou'] = '〨'; + map!['eighthnotebeamed'] = '♫'; + map!['eightideographicparen'] = '㈧'; + map!['eightinferior'] = '₈'; + map!['eightmonospace'] = '8'; + map!['eightoldstyle'] = ''; + map!['eightparen'] = '⑻'; + map!['eightperiod'] = '⒏'; + map!['eightpersian'] = '۸'; + map!['eightroman'] = 'ⅷ'; + map!['eightsuperior'] = '⁸'; + map!['eightthai'] = '๘'; + map!['einvertedbreve'] = 'ȇ'; + map!['eiotifiedcyrillic'] = 'ѥ'; + map!['ekatakana'] = 'エ'; + map!['ekatakanahalfwidth'] = 'エ'; + map!['ekonkargurmukhi'] = 'ੴ'; + map!['ekorean'] = 'ㅔ'; + map!['elcyrillic'] = 'л'; + map!['element'] = '∈'; + map!['elevencircle'] = '⑪'; + map!['elevenparen'] = '⑾'; + map!['elevenperiod'] = '⒒'; + map!['elevenroman'] = 'ⅺ'; + map!['ellipsis'] = '…'; + map!['ellipsisvertical'] = '⋮'; + map!['emacron'] = 'ē'; + map!['emacronacute'] = 'ḗ'; + map!['emacrongrave'] = 'ḕ'; + map!['emcyrillic'] = 'м'; + map!['emdash'] = '—'; + map!['emdashvertical'] = '︱'; + map!['emonospace'] = 'e'; + map!['emphasismarkarmenian'] = '՛'; + map!['emptyset'] = '∅'; + map!['enbopomofo'] = 'ㄣ'; + map!['encyrillic'] = 'н'; + map!['endash'] = '–'; + map!['endashvertical'] = '︲'; + map!['endescendercyrillic'] = 'ң'; + map!['eng'] = 'ŋ'; + map!['engbopomofo'] = 'ㄥ'; + map!['enghecyrillic'] = 'ҥ'; + map!['enhookcyrillic'] = 'ӈ'; + map!['enspace'] = '\u2002'; + map!['eogonek'] = 'ę'; + map!['eokorean'] = 'ㅓ'; + map!['eopen'] = 'ɛ'; + map!['eopenclosed'] = 'ʚ'; + map!['eopenreversed'] = 'ɜ'; + map!['eopenreversedclosed'] = 'ɞ'; + map!['eopenreversedhook'] = 'ɝ'; + map!['eparen'] = '⒠'; + map!['epsilon'] = 'ε'; + map!['epsilontonos'] = 'έ'; + map!['equal'] = '='; + map!['equalmonospace'] = '='; + map!['equalsmall'] = '﹦'; + map!['equalsuperior'] = '⁼'; + map!['equivalence'] = '≡'; + map!['erbopomofo'] = 'ㄦ'; + map!['ercyrillic'] = 'р'; + map!['ereversed'] = 'ɘ'; + map!['ereversedcyrillic'] = 'э'; + map!['escyrillic'] = 'с'; + map!['esdescendercyrillic'] = 'ҫ'; + map!['esh'] = 'ʃ'; + map!['eshcurl'] = 'ʆ'; + map!['eshortdeva'] = 'ऎ'; + map!['eshortvowelsigndeva'] = 'ॆ'; + map!['eshreversedloop'] = 'ƪ'; + map!['eshsquatreversed'] = 'ʅ'; + map!['esmallhiragana'] = 'ぇ'; + map!['esmallkatakana'] = 'ェ'; + map!['esmallkatakanahalfwidth'] = 'ェ'; + map!['estimated'] = '℮'; + map!['esuperior'] = ''; + map!['eta'] = 'η'; + map!['etarmenian'] = 'ը'; + map!['etatonos'] = 'ή'; + map!['eth'] = 'ð'; + map!['etilde'] = 'ẽ'; + map!['etildebelow'] = 'ḛ'; + map!['etnahtafoukhhebrew'] = '֑'; + map!['etnahtafoukhlefthebrew'] = '֑'; + map!['etnahtahebrew'] = '֑'; + map!['etnahtalefthebrew'] = '֑'; + map!['eturned'] = 'ǝ'; + map!['eukorean'] = 'ㅡ'; + map!['euro'] = '€'; + map!['evowelsignbengali'] = 'ে'; + map!['evowelsigndeva'] = 'े'; + map!['evowelsigngujarati'] = 'ે'; + map!['exclam'] = '!'; + map!['exclamarmenian'] = '՜'; + map!['exclamdbl'] = '‼'; + map!['exclamdown'] = '¡'; + map!['exclamdownsmall'] = ''; + map!['exclammonospace'] = '!'; + map!['exclamsmall'] = ''; + map!['existential'] = '∃'; + map!['ezh'] = 'ʒ'; + map!['ezhcaron'] = 'ǯ'; + map!['ezhcurl'] = 'ʓ'; + map!['ezhreversed'] = 'ƹ'; + map!['ezhtail'] = 'ƺ'; + map!['f'] = 'f'; + map!['fadeva'] = 'फ़'; + map!['fagurmukhi'] = 'ਫ਼'; + map!['fahrenheit'] = '℉'; + map!['fathaarabic'] = 'َ'; + map!['fathalowarabic'] = 'َ'; + map!['fathatanarabic'] = 'ً'; + map!['fbopomofo'] = 'ㄈ'; + map!['fcircle'] = 'ⓕ'; + map!['fdotaccent'] = 'ḟ'; + map!['feharabic'] = 'ف'; + map!['feharmenian'] = 'ֆ'; + map!['fehfinalarabic'] = 'ﻒ'; + map!['fehinitialarabic'] = 'ﻓ'; + map!['fehmedialarabic'] = 'ﻔ'; + map!['feicoptic'] = 'ϥ'; + map!['female'] = '♀'; + map!['ff'] = 'ff'; + map!['ffi'] = 'ffi'; + map!['ffl'] = 'ffl'; + map!['fi'] = 'fi'; + map!['fifteencircle'] = '⑮'; + map!['fifteenparen'] = '⒂'; + map!['fifteenperiod'] = '⒖'; + map!['figuredash'] = '‒'; + map!['filledbox'] = '■'; + map!['filledrect'] = '▬'; + map!['finalkaf'] = 'ך'; + map!['finalkafdagesh'] = 'ךּ'; + map!['finalkafdageshhebrew'] = 'ךּ'; + map!['finalkafhebrew'] = 'ך'; + map!['finalmem'] = 'ם'; + map!['finalmemhebrew'] = 'ם'; + map!['finalnun'] = 'ן'; + map!['finalnunhebrew'] = 'ן'; + map!['finalpe'] = 'ף'; + map!['finalpehebrew'] = 'ף'; + map!['finaltsadi'] = 'ץ'; + map!['finaltsadihebrew'] = 'ץ'; + map!['firsttonechinese'] = 'ˉ'; + map!['fisheye'] = '◉'; + map!['fitacyrillic'] = 'ѳ'; + map!['five'] = '5'; + map!['fivearabic'] = '٥'; + map!['fivebengali'] = '৫'; + map!['fivecircle'] = '⑤'; + map!['fivecircleinversesansserif'] = '➎'; + map!['fivedeva'] = '५'; + map!['fiveeighths'] = '⅝'; + map!['fivegujarati'] = '૫'; + map!['fivegurmukhi'] = '੫'; + map!['fivehackarabic'] = '٥'; + map!['fivehangzhou'] = '〥'; + map!['fiveideographicparen'] = '㈤'; + map!['fiveinferior'] = '₅'; + map!['fivemonospace'] = '5'; + map!['fiveoldstyle'] = ''; + map!['fiveparen'] = '⑸'; + map!['fiveperiod'] = '⒌'; + map!['fivepersian'] = '۵'; + map!['fiveroman'] = 'ⅴ'; + map!['fivesuperior'] = '⁵'; + map!['fivethai'] = '๕'; + map!['fl'] = 'fl'; + map!['florin'] = 'ƒ'; + map!['fmonospace'] = 'f'; + map!['fmsquare'] = '㎙'; + map!['fofanthai'] = 'ฟ'; + map!['fofathai'] = 'ฝ'; + map!['fongmanthai'] = '๏'; + map!['forall'] = '∀'; + map!['four'] = '4'; + map!['fourarabic'] = '٤'; + map!['fourbengali'] = '৪'; + map!['fourcircle'] = '④'; + map!['fourcircleinversesansserif'] = '➍'; + map!['fourdeva'] = '४'; + map!['fourgujarati'] = '૪'; + map!['fourgurmukhi'] = '੪'; + map!['fourhackarabic'] = '٤'; + map!['fourhangzhou'] = '〤'; + map!['fourideographicparen'] = '㈣'; + map!['fourinferior'] = '₄'; + map!['fourmonospace'] = '4'; + map!['fournumeratorbengali'] = '৷'; + map!['fouroldstyle'] = ''; + map!['fourparen'] = '⑷'; + map!['fourperiod'] = '⒋'; + map!['fourpersian'] = '۴'; + map!['fourroman'] = 'ⅳ'; + map!['foursuperior'] = '⁴'; + map!['fourteencircle'] = '⑭'; + map!['fourteenparen'] = '⒁'; + map!['fourteenperiod'] = '⒕'; + map!['fourthai'] = '๔'; + map!['fourthtonechinese'] = 'ˋ'; + map!['fparen'] = '⒡'; + map!['fraction'] = '⁄'; + map!['franc'] = '₣'; + map!['g'] = 'g'; + map!['gabengali'] = 'গ'; + map!['gacute'] = 'ǵ'; + map!['gadeva'] = 'ग'; + map!['gafarabic'] = 'گ'; + map!['gaffinalarabic'] = 'ﮓ'; + map!['gafinitialarabic'] = 'ﮔ'; + map!['gafmedialarabic'] = 'ﮕ'; + map!['gagujarati'] = 'ગ'; + map!['gagurmukhi'] = 'ਗ'; + map!['gahiragana'] = 'が'; + map!['gakatakana'] = 'ガ'; + map!['gamma'] = 'γ'; + map!['gammalatinsmall'] = 'ɣ'; + map!['gammasuperior'] = 'ˠ'; + map!['gangiacoptic'] = 'ϫ'; + map!['gbopomofo'] = 'ㄍ'; + map!['gbreve'] = 'ğ'; + map!['gcaron'] = 'ǧ'; + map!['gcedilla'] = 'ģ'; + map!['gcircle'] = 'ⓖ'; + map!['gcircumflex'] = 'ĝ'; + map!['gcommaaccent'] = 'ģ'; + map!['gdot'] = 'ġ'; + map!['gdotaccent'] = 'ġ'; + map!['gecyrillic'] = 'г'; + map!['gehiragana'] = 'げ'; + map!['gekatakana'] = 'ゲ'; + map!['geometricallyequal'] = '≑'; + map!['gereshaccenthebrew'] = '֜'; + map!['gereshhebrew'] = '׳'; + map!['gereshmuqdamhebrew'] = '֝'; + map!['germandbls'] = 'ß'; + map!['gershayimaccenthebrew'] = '֞'; + map!['gershayimhebrew'] = '״'; + map!['getamark'] = '〓'; + map!['ghabengali'] = 'ঘ'; + map!['ghadarmenian'] = 'ղ'; + map!['ghadeva'] = 'घ'; + map!['ghagujarati'] = 'ઘ'; + map!['ghagurmukhi'] = 'ਘ'; + map!['ghainarabic'] = 'غ'; + map!['ghainfinalarabic'] = 'ﻎ'; + map!['ghaininitialarabic'] = 'ﻏ'; + map!['ghainmedialarabic'] = 'ﻐ'; + map!['ghemiddlehookcyrillic'] = 'ҕ'; + map!['ghestrokecyrillic'] = 'ғ'; + map!['gheupturncyrillic'] = 'ґ'; + map!['ghhadeva'] = 'ग़'; + map!['ghhagurmukhi'] = 'ਗ਼'; + map!['ghook'] = 'ɠ'; + map!['ghzsquare'] = '㎓'; + map!['gihiragana'] = 'ぎ'; + map!['gikatakana'] = 'ギ'; + map!['gimarmenian'] = 'գ'; + map!['gimel'] = 'ג'; + map!['gimeldagesh'] = 'גּ'; + map!['gimeldageshhebrew'] = 'גּ'; + map!['gimelhebrew'] = 'ג'; + map!['gjecyrillic'] = 'ѓ'; + map!['glottalinvertedstroke'] = 'ƾ'; + map!['glottalstop'] = 'ʔ'; + map!['glottalstopinverted'] = 'ʖ'; + map!['glottalstopmod'] = 'ˀ'; + map!['glottalstopreversed'] = 'ʕ'; + map!['glottalstopreversedmod'] = 'ˁ'; + map!['glottalstopreversedsuperior'] = 'ˤ'; + map!['glottalstopstroke'] = 'ʡ'; + map!['glottalstopstrokereversed'] = 'ʢ'; + map!['gmacron'] = 'ḡ'; + map!['gmonospace'] = 'g'; + map!['gohiragana'] = 'ご'; + map!['gokatakana'] = 'ゴ'; + map!['gparen'] = '⒢'; + map!['gpasquare'] = '㎬'; + map!['gradient'] = '∇'; + map!['grave'] = '`'; + map!['gravebelowcmb'] = '̖'; + map!['gravecmb'] = '̀'; + map!['gravecomb'] = '̀'; + map!['gravedeva'] = '॓'; + map!['gravelowmod'] = 'ˎ'; + map!['gravemonospace'] = '`'; + map!['gravetonecmb'] = '̀'; + map!['greater'] = '>'; + map!['greaterequal'] = '≥'; + map!['greaterequalorless'] = '⋛'; + map!['greatermonospace'] = '>'; + map!['greaterorequivalent'] = '≳'; + map!['greaterorless'] = '≷'; + map!['greateroverequal'] = '≧'; + map!['greatersmall'] = '﹥'; + map!['gscript'] = 'ɡ'; + map!['gstroke'] = 'ǥ'; + map!['guhiragana'] = 'ぐ'; + map!['guillemotleft'] = '«'; + map!['guillemotright'] = '»'; + map!['guilsinglleft'] = '‹'; + map!['guilsinglright'] = '›'; + map!['gukatakana'] = 'グ'; + map!['guramusquare'] = '㌘'; + map!['gysquare'] = '㏉'; + map!['h'] = 'h'; + map!['haabkhasiancyrillic'] = 'ҩ'; + map!['haaltonearabic'] = 'ہ'; + map!['habengali'] = 'হ'; + map!['hadescendercyrillic'] = 'ҳ'; + map!['hadeva'] = 'ह'; + map!['hagujarati'] = 'હ'; + map!['hagurmukhi'] = 'ਹ'; + map!['haharabic'] = 'ح'; + map!['hahfinalarabic'] = 'ﺢ'; + map!['hahinitialarabic'] = 'ﺣ'; + map!['hahiragana'] = 'は'; + map!['hahmedialarabic'] = 'ﺤ'; + map!['haitusquare'] = '㌪'; + map!['hakatakana'] = 'ハ'; + map!['hakatakanahalfwidth'] = 'ハ'; + map!['halantgurmukhi'] = '੍'; + map!['hamzaarabic'] = 'ء'; + map!['hamzalowarabic'] = 'ء'; + map!['hangulfiller'] = 'ㅤ'; + map!['hardsigncyrillic'] = 'ъ'; + map!['harpoonleftbarbup'] = '↼'; + map!['harpoonrightbarbup'] = '⇀'; + map!['hasquare'] = '㏊'; + map!['hatafpatah'] = 'ֲ'; + map!['hatafpatah16'] = 'ֲ'; + map!['hatafpatah23'] = 'ֲ'; + map!['hatafpatah2f'] = 'ֲ'; + map!['hatafpatahhebrew'] = 'ֲ'; + map!['hatafpatahnarrowhebrew'] = 'ֲ'; + map!['hatafpatahquarterhebrew'] = 'ֲ'; + map!['hatafpatahwidehebrew'] = 'ֲ'; + map!['hatafqamats'] = 'ֳ'; + map!['hatafqamats1b'] = 'ֳ'; + map!['hatafqamats28'] = 'ֳ'; + map!['hatafqamats34'] = 'ֳ'; + map!['hatafqamatshebrew'] = 'ֳ'; + map!['hatafqamatsnarrowhebrew'] = 'ֳ'; + map!['hatafqamatsquarterhebrew'] = 'ֳ'; + map!['hatafqamatswidehebrew'] = 'ֳ'; + map!['hatafsegol'] = 'ֱ'; + map!['hatafsegol17'] = 'ֱ'; + map!['hatafsegol24'] = 'ֱ'; + map!['hatafsegol30'] = 'ֱ'; + map!['hatafsegolhebrew'] = 'ֱ'; + map!['hatafsegolnarrowhebrew'] = 'ֱ'; + map!['hatafsegolquarterhebrew'] = 'ֱ'; + map!['hatafsegolwidehebrew'] = 'ֱ'; + map!['hbar'] = 'ħ'; + map!['hbopomofo'] = 'ㄏ'; + map!['hbrevebelow'] = 'ḫ'; + map!['hcedilla'] = 'ḩ'; + map!['hcircle'] = 'ⓗ'; + map!['hcircumflex'] = 'ĥ'; + map!['hdieresis'] = 'ḧ'; + map!['hdotaccent'] = 'ḣ'; + map!['hdotbelow'] = 'ḥ'; + map!['he'] = 'ה'; + map!['heart'] = '♥'; + map!['heartsuitblack'] = '♥'; + map!['heartsuitwhite'] = '♡'; + map!['hedagesh'] = 'הּ'; + map!['hedageshhebrew'] = 'הּ'; + map!['hehaltonearabic'] = 'ہ'; + map!['heharabic'] = 'ه'; + map!['hehebrew'] = 'ה'; + map!['hehfinalaltonearabic'] = 'ﮧ'; + map!['hehfinalalttwoarabic'] = 'ﻪ'; + map!['hehfinalarabic'] = 'ﻪ'; + map!['hehhamzaabovefinalarabic'] = 'ﮥ'; + map!['hehhamzaaboveisolatedarabic'] = 'ﮤ'; + map!['hehinitialaltonearabic'] = 'ﮨ'; + map!['hehinitialarabic'] = 'ﻫ'; + map!['hehiragana'] = 'へ'; + map!['hehmedialaltonearabic'] = 'ﮩ'; + map!['hehmedialarabic'] = 'ﻬ'; + map!['heiseierasquare'] = '㍻'; + map!['hekatakana'] = 'ヘ'; + map!['hekatakanahalfwidth'] = 'ヘ'; + map!['hekutaarusquare'] = '㌶'; + map!['henghook'] = 'ɧ'; + map!['herutusquare'] = '㌹'; + map!['het'] = 'ח'; + map!['hethebrew'] = 'ח'; + map!['hhook'] = 'ɦ'; + map!['hhooksuperior'] = 'ʱ'; + map!['hieuhacirclekorean'] = '㉻'; + map!['hieuhaparenkorean'] = '㈛'; + map!['hieuhcirclekorean'] = '㉭'; + map!['hieuhkorean'] = 'ㅎ'; + map!['hieuhparenkorean'] = '㈍'; + map!['hihiragana'] = 'ひ'; + map!['hikatakana'] = 'ヒ'; + map!['hikatakanahalfwidth'] = 'ヒ'; + map!['hiriq'] = 'ִ'; + map!['hiriq14'] = 'ִ'; + map!['hiriq21'] = 'ִ'; + map!['hiriq2d'] = 'ִ'; + map!['hiriqhebrew'] = 'ִ'; + map!['hiriqnarrowhebrew'] = 'ִ'; + map!['hiriqquarterhebrew'] = 'ִ'; + map!['hiriqwidehebrew'] = 'ִ'; + map!['hlinebelow'] = 'ẖ'; + map!['hmonospace'] = 'h'; + map!['hoarmenian'] = 'հ'; + map!['hohipthai'] = 'ห'; + map!['hohiragana'] = 'ほ'; + map!['hokatakana'] = 'ホ'; + map!['hokatakanahalfwidth'] = 'ホ'; + map!['holam'] = 'ֹ'; + map!['holam19'] = 'ֹ'; + map!['holam26'] = 'ֹ'; + map!['holam32'] = 'ֹ'; + map!['holamhebrew'] = 'ֹ'; + map!['holamnarrowhebrew'] = 'ֹ'; + map!['holamquarterhebrew'] = 'ֹ'; + map!['holamwidehebrew'] = 'ֹ'; + map!['honokhukthai'] = 'ฮ'; + map!['hookabovecomb'] = '̉'; + map!['hookcmb'] = '̉'; + map!['hookpalatalizedbelowcmb'] = '̡'; + map!['hookretroflexbelowcmb'] = '̢'; + map!['hoonsquare'] = '㍂'; + map!['horicoptic'] = 'ϩ'; + map!['horizontalbar'] = '―'; + map!['horncmb'] = '̛'; + map!['hotsprings'] = '♨'; + map!['house'] = '⌂'; + map!['hparen'] = '⒣'; + map!['hsuperior'] = 'ʰ'; + map!['hturned'] = 'ɥ'; + map!['huhiragana'] = 'ふ'; + map!['huiitosquare'] = '㌳'; + map!['hukatakana'] = 'フ'; + map!['hukatakanahalfwidth'] = 'フ'; + map!['hungarumlaut'] = '˝'; + map!['hungarumlautcmb'] = '̋'; + map!['hv'] = 'ƕ'; + map!['hyphen'] = '-'; + map!['hypheninferior'] = ''; + map!['hyphenmonospace'] = '-'; + map!['hyphensmall'] = '﹣'; + map!['hyphensuperior'] = ''; + map!['hyphentwo'] = '‐'; + map!['i'] = 'i'; + map!['iacute'] = 'í'; + map!['iacyrillic'] = 'я'; + map!['ibengali'] = 'ই'; + map!['ibopomofo'] = 'ㄧ'; + map!['ibreve'] = 'ĭ'; + map!['icaron'] = 'ǐ'; + map!['icircle'] = 'ⓘ'; + map!['icircumflex'] = 'î'; + map!['icyrillic'] = 'і'; + map!['idblgrave'] = 'ȉ'; + map!['ideographearthcircle'] = '㊏'; + map!['ideographfirecircle'] = '㊋'; + map!['ideographicallianceparen'] = '㈿'; + map!['ideographiccallparen'] = '㈺'; + map!['ideographiccentrecircle'] = '㊥'; + map!['ideographicclose'] = '〆'; + map!['ideographiccomma'] = '、'; + map!['ideographiccommaleft'] = '、'; + map!['ideographiccongratulationparen'] = '㈷'; + map!['ideographiccorrectcircle'] = '㊣'; + map!['ideographicearthparen'] = '㈯'; + map!['ideographicenterpriseparen'] = '㈽'; + map!['ideographicexcellentcircle'] = '㊝'; + map!['ideographicfestivalparen'] = '㉀'; + map!['ideographicfinancialcircle'] = '㊖'; + map!['ideographicfinancialparen'] = '㈶'; + map!['ideographicfireparen'] = '㈫'; + map!['ideographichaveparen'] = '㈲'; + map!['ideographichighcircle'] = '㊤'; + map!['ideographiciterationmark'] = '々'; + map!['ideographiclaborcircle'] = '㊘'; + map!['ideographiclaborparen'] = '㈸'; + map!['ideographicleftcircle'] = '㊧'; + map!['ideographiclowcircle'] = '㊦'; + map!['ideographicmedicinecircle'] = '㊩'; + map!['ideographicmetalparen'] = '㈮'; + map!['ideographicmoonparen'] = '㈪'; + map!['ideographicnameparen'] = '㈴'; + map!['ideographicperiod'] = '。'; + map!['ideographicprintcircle'] = '㊞'; + map!['ideographicreachparen'] = '㉃'; + map!['ideographicrepresentparen'] = '㈹'; + map!['ideographicresourceparen'] = '㈾'; + map!['ideographicrightcircle'] = '㊨'; + map!['ideographicsecretcircle'] = '㊙'; + map!['ideographicselfparen'] = '㉂'; + map!['ideographicsocietyparen'] = '㈳'; + map!['ideographicspace'] = '\u3000'; + map!['ideographicspecialparen'] = '㈵'; + map!['ideographicstockparen'] = '㈱'; + map!['ideographicstudyparen'] = '㈻'; + map!['ideographicsunparen'] = '㈰'; + map!['ideographicsuperviseparen'] = '㈼'; + map!['ideographicwaterparen'] = '㈬'; + map!['ideographicwoodparen'] = '㈭'; + map!['ideographiczero'] = '〇'; + map!['ideographmetalcircle'] = '㊎'; + map!['ideographmooncircle'] = '㊊'; + map!['ideographnamecircle'] = '㊔'; + map!['ideographsuncircle'] = '㊐'; + map!['ideographwatercircle'] = '㊌'; + map!['ideographwoodcircle'] = '㊍'; + map!['ideva'] = 'इ'; + map!['idieresis'] = 'ï'; + map!['idieresisacute'] = 'ḯ'; + map!['idieresiscyrillic'] = 'ӥ'; + map!['idotbelow'] = 'ị'; + map!['iebrevecyrillic'] = 'ӗ'; + map!['iecyrillic'] = 'е'; + map!['ieungacirclekorean'] = '㉵'; + map!['ieungaparenkorean'] = '㈕'; + map!['ieungcirclekorean'] = '㉧'; + map!['ieungkorean'] = 'ㅇ'; + map!['ieungparenkorean'] = '㈇'; + map!['igrave'] = 'ì'; + map!['igujarati'] = 'ઇ'; + map!['igurmukhi'] = 'ਇ'; + map!['ihiragana'] = 'い'; + map!['ihookabove'] = 'ỉ'; + map!['iibengali'] = 'ঈ'; + map!['iicyrillic'] = 'и'; + map!['iideva'] = 'ई'; + map!['iigujarati'] = 'ઈ'; + map!['iigurmukhi'] = 'ਈ'; + map!['iimatragurmukhi'] = 'ੀ'; + map!['iinvertedbreve'] = 'ȋ'; + map!['iishortcyrillic'] = 'й'; + map!['iivowelsignbengali'] = 'ী'; + map!['iivowelsigndeva'] = 'ी'; + map!['iivowelsigngujarati'] = 'ી'; + map!['ij'] = 'ij'; + map!['ikatakana'] = 'イ'; + map!['ikatakanahalfwidth'] = 'イ'; + map!['ikorean'] = 'ㅣ'; + map!['ilde'] = '˜'; + map!['iluyhebrew'] = '֬'; + map!['imacron'] = 'ī'; + map!['imacroncyrillic'] = 'ӣ'; + map!['imageorapproximatelyequal'] = '≓'; + map!['imatragurmukhi'] = 'ਿ'; + map!['imonospace'] = 'i'; + map!['increment'] = '∆'; + map!['infinity'] = '∞'; + map!['iniarmenian'] = 'ի'; + map!['integral'] = '∫'; + map!['integralbottom'] = '⌡'; + map!['integralbt'] = '⌡'; + map!['integralex'] = ''; + map!['integraltop'] = '⌠'; + map!['integraltp'] = '⌠'; + map!['intersection'] = '∩'; + map!['intisquare'] = '㌅'; + map!['invbullet'] = '◘'; + map!['invcircle'] = '◙'; + map!['invsmileface'] = '☻'; + map!['iocyrillic'] = 'ё'; + map!['iogonek'] = 'į'; + map!['iota'] = 'ι'; + map!['iotadieresis'] = 'ϊ'; + map!['iotadieresistonos'] = 'ΐ'; + map!['iotalatin'] = 'ɩ'; + map!['iotatonos'] = 'ί'; + map!['iparen'] = '⒤'; + map!['irigurmukhi'] = 'ੲ'; + map!['ismallhiragana'] = 'ぃ'; + map!['ismallkatakana'] = 'ィ'; + map!['ismallkatakanahalfwidth'] = 'ィ'; + map!['issharbengali'] = '৺'; + map!['istroke'] = 'ɨ'; + map!['isuperior'] = ''; + map!['iterationhiragana'] = 'ゝ'; + map!['iterationkatakana'] = 'ヽ'; + map!['itilde'] = 'ĩ'; + map!['itildebelow'] = 'ḭ'; + map!['iubopomofo'] = 'ㄩ'; + map!['iucyrillic'] = 'ю'; + map!['ivowelsignbengali'] = 'ি'; + map!['ivowelsigndeva'] = 'ि'; + map!['ivowelsigngujarati'] = 'િ'; + map!['izhitsacyrillic'] = 'ѵ'; + map!['izhitsadblgravecyrillic'] = 'ѷ'; + map!['j'] = 'j'; + map!['jaarmenian'] = 'ձ'; + map!['jabengali'] = 'জ'; + map!['jadeva'] = 'ज'; + map!['jagujarati'] = 'જ'; + map!['jagurmukhi'] = 'ਜ'; + map!['jbopomofo'] = 'ㄐ'; + map!['jcaron'] = 'ǰ'; + map!['jcircle'] = 'ⓙ'; + map!['jcircumflex'] = 'ĵ'; + map!['jcrossedtail'] = 'ʝ'; + map!['jdotlessstroke'] = 'ɟ'; + map!['jecyrillic'] = 'ј'; + map!['jeemarabic'] = 'ج'; + map!['jeemfinalarabic'] = 'ﺞ'; + map!['jeeminitialarabic'] = 'ﺟ'; + map!['jeemmedialarabic'] = 'ﺠ'; + map!['jeharabic'] = 'ژ'; + map!['jehfinalarabic'] = 'ﮋ'; + map!['jhabengali'] = 'ঝ'; + map!['jhadeva'] = 'झ'; + map!['jhagujarati'] = 'ઝ'; + map!['jhagurmukhi'] = 'ਝ'; + map!['jheharmenian'] = 'ջ'; + map!['jis'] = '〄'; + map!['jmonospace'] = 'j'; + map!['jparen'] = '⒥'; + map!['jsuperior'] = 'ʲ'; + map!['k'] = 'k'; + map!['kabashkircyrillic'] = 'ҡ'; + map!['kabengali'] = 'ক'; + map!['kacute'] = 'ḱ'; + map!['kacyrillic'] = 'к'; + map!['kadescendercyrillic'] = 'қ'; + map!['kadeva'] = 'क'; + map!['kaf'] = 'כ'; + map!['kafarabic'] = 'ك'; + map!['kafdagesh'] = 'כּ'; + map!['kafdageshhebrew'] = 'כּ'; + map!['kaffinalarabic'] = 'ﻚ'; + map!['kafhebrew'] = 'כ'; + map!['kafinitialarabic'] = 'ﻛ'; + map!['kafmedialarabic'] = 'ﻜ'; + map!['kafrafehebrew'] = 'כֿ'; + map!['kagujarati'] = 'ક'; + map!['kagurmukhi'] = 'ਕ'; + map!['kahiragana'] = 'か'; + map!['kahookcyrillic'] = 'ӄ'; + map!['kakatakana'] = 'カ'; + map!['kakatakanahalfwidth'] = 'カ'; + map!['kappa'] = 'κ'; + map!['kappasymbolgreek'] = 'ϰ'; + map!['kapyeounmieumkorean'] = 'ㅱ'; + map!['kapyeounphieuphkorean'] = 'ㆄ'; + map!['kapyeounpieupkorean'] = 'ㅸ'; + map!['kapyeounssangpieupkorean'] = 'ㅹ'; + map!['karoriisquare'] = '㌍'; + map!['kashidaautoarabic'] = 'ـ'; + map!['kashidaautonosidebearingarabic'] = 'ـ'; + map!['kasmallkatakana'] = 'ヵ'; + map!['kasquare'] = '㎄'; + map!['kasraarabic'] = 'ِ'; + map!['kasratanarabic'] = 'ٍ'; + map!['kastrokecyrillic'] = 'ҟ'; + map!['katahiraprolongmarkhalfwidth'] = 'ー'; + map!['kaverticalstrokecyrillic'] = 'ҝ'; + map!['kbopomofo'] = 'ㄎ'; + map!['kcalsquare'] = '㎉'; + map!['kcaron'] = 'ǩ'; + map!['kcedilla'] = 'ķ'; + map!['kcircle'] = 'ⓚ'; + map!['kcommaaccent'] = 'ķ'; + map!['kdotbelow'] = 'ḳ'; + map!['keharmenian'] = 'ք'; + map!['kehiragana'] = 'け'; + map!['kekatakana'] = 'ケ'; + map!['kekatakanahalfwidth'] = 'ケ'; + map!['kenarmenian'] = 'կ'; + map!['kesmallkatakana'] = 'ヶ'; + map!['kgreenlandic'] = 'ĸ'; + map!['khabengali'] = 'খ'; + map!['khacyrillic'] = 'х'; + map!['khadeva'] = 'ख'; + map!['khagujarati'] = 'ખ'; + map!['khagurmukhi'] = 'ਖ'; + map!['khaharabic'] = 'خ'; + map!['khahfinalarabic'] = 'ﺦ'; + map!['khahinitialarabic'] = 'ﺧ'; + map!['khahmedialarabic'] = 'ﺨ'; + map!['kheicoptic'] = 'ϧ'; + map!['khhadeva'] = 'ख़'; + map!['khhagurmukhi'] = 'ਖ਼'; + map!['khieukhacirclekorean'] = '㉸'; + map!['khieukhaparenkorean'] = '㈘'; + map!['khieukhcirclekorean'] = '㉪'; + map!['khieukhkorean'] = 'ㅋ'; + map!['khieukhparenkorean'] = '㈊'; + map!['khokhaithai'] = 'ข'; + map!['khokhonthai'] = 'ฅ'; + map!['khokhuatthai'] = 'ฃ'; + map!['khokhwaithai'] = 'ค'; + map!['khomutthai'] = '๛'; + map!['khook'] = 'ƙ'; + map!['khorakhangthai'] = 'ฆ'; + map!['khzsquare'] = '㎑'; + map!['kihiragana'] = 'き'; + map!['kikatakana'] = 'キ'; + map!['kikatakanahalfwidth'] = 'キ'; + map!['kiroguramusquare'] = '㌕'; + map!['kiromeetorusquare'] = '㌖'; + map!['kirosquare'] = '㌔'; + map!['kiyeokacirclekorean'] = '㉮'; + map!['kiyeokaparenkorean'] = '㈎'; + map!['kiyeokcirclekorean'] = '㉠'; + map!['kiyeokkorean'] = 'ㄱ'; + map!['kiyeokparenkorean'] = '㈀'; + map!['kiyeoksioskorean'] = 'ㄳ'; + map!['kjecyrillic'] = 'ќ'; + map!['klinebelow'] = 'ḵ'; + map!['klsquare'] = '㎘'; + map!['kmcubedsquare'] = '㎦'; + map!['kmonospace'] = 'k'; + map!['kmsquaredsquare'] = '㎢'; + map!['kohiragana'] = 'こ'; + map!['kohmsquare'] = '㏀'; + map!['kokaithai'] = 'ก'; + map!['kokatakana'] = 'コ'; + map!['kokatakanahalfwidth'] = 'コ'; + map!['kooposquare'] = '㌞'; + map!['koppacyrillic'] = 'ҁ'; + map!['koreanstandardsymbol'] = '㉿'; + map!['koroniscmb'] = '̓'; + map!['kparen'] = '⒦'; + map!['kpasquare'] = '㎪'; + map!['ksicyrillic'] = 'ѯ'; + map!['ktsquare'] = '㏏'; + map!['kturned'] = 'ʞ'; + map!['kuhiragana'] = 'く'; + map!['kukatakana'] = 'ク'; + map!['kukatakanahalfwidth'] = 'ク'; + map!['kvsquare'] = '㎸'; + map!['kwsquare'] = '㎾'; + map!['l'] = 'l'; + map!['labengali'] = 'ল'; + map!['lacute'] = 'ĺ'; + map!['ladeva'] = 'ल'; + map!['lagujarati'] = 'લ'; + map!['lagurmukhi'] = 'ਲ'; + map!['lakkhangyaothai'] = 'ๅ'; + map!['lamaleffinalarabic'] = 'ﻼ'; + map!['lamalefhamzaabovefinalarabic'] = 'ﻸ'; + map!['lamalefhamzaaboveisolatedarabic'] = 'ﻷ'; + map!['lamalefhamzabelowfinalarabic'] = 'ﻺ'; + map!['lamalefhamzabelowisolatedarabic'] = 'ﻹ'; + map!['lamalefisolatedarabic'] = 'ﻻ'; + map!['lamalefmaddaabovefinalarabic'] = 'ﻶ'; + map!['lamalefmaddaaboveisolatedarabic'] = 'ﻵ'; + map!['lamarabic'] = 'ل'; + map!['lambda'] = 'λ'; + map!['lambdastroke'] = 'ƛ'; + map!['lamed'] = 'ל'; + map!['lameddagesh'] = 'לּ'; + map!['lameddageshhebrew'] = 'לּ'; + map!['lamedhebrew'] = 'ל'; + map!['lamfinalarabic'] = 'ﻞ'; + map!['lamhahinitialarabic'] = 'ﳊ'; + map!['laminitialarabic'] = 'ﻟ'; + map!['lamjeeminitialarabic'] = 'ﳉ'; + map!['lamkhahinitialarabic'] = 'ﳋ'; + map!['lamlamhehisolatedarabic'] = 'ﷲ'; + map!['lammedialarabic'] = 'ﻠ'; + map!['lammeemhahinitialarabic'] = 'ﶈ'; + map!['lammeeminitialarabic'] = 'ﳌ'; + map!['largecircle'] = '◯'; + map!['lbar'] = 'ƚ'; + map!['lbelt'] = 'ɬ'; + map!['lbopomofo'] = 'ㄌ'; + map!['lcaron'] = 'ľ'; + map!['lcedilla'] = 'ļ'; + map!['lcircle'] = 'ⓛ'; + map!['lcircumflexbelow'] = 'ḽ'; + map!['lcommaaccent'] = 'ļ'; + map!['ldot'] = 'ŀ'; + map!['ldotaccent'] = 'ŀ'; + map!['ldotbelow'] = 'ḷ'; + map!['ldotbelowmacron'] = 'ḹ'; + map!['leftangleabovecmb'] = '̚'; + map!['lefttackbelowcmb'] = '̘'; + map!['less'] = '<'; + map!['lessequal'] = '≤'; + map!['lessequalorgreater'] = '⋚'; + map!['lessmonospace'] = '<'; + map!['lessorequivalent'] = '≲'; + map!['lessorgreater'] = '≶'; + map!['lessoverequal'] = '≦'; + map!['lesssmall'] = '﹤'; + map!['lezh'] = 'ɮ'; + map!['lfblock'] = '▌'; + map!['lhookretroflex'] = 'ɭ'; + map!['lira'] = '₤'; + map!['liwnarmenian'] = 'լ'; + map!['lj'] = 'lj'; + map!['ljecyrillic'] = 'љ'; + map!['ll'] = ''; + map!['lladeva'] = 'ळ'; + map!['llagujarati'] = 'ળ'; + map!['llinebelow'] = 'ḻ'; + map!['llladeva'] = 'ऴ'; + map!['llvocalicbengali'] = 'ৡ'; + map!['llvocalicdeva'] = 'ॡ'; + map!['llvocalicvowelsignbengali'] = 'ৣ'; + map!['llvocalicvowelsigndeva'] = 'ॣ'; + map!['lmiddletilde'] = 'ɫ'; + map!['lmonospace'] = 'l'; + map!['lmsquare'] = '㏐'; + map!['lochulathai'] = 'ฬ'; + map!['logicaland'] = '∧'; + map!['logicalnot'] = '¬'; + map!['logicalnotreversed'] = '⌐'; + map!['logicalor'] = '∨'; + map!['lolingthai'] = 'ล'; + map!['longs'] = 'ſ'; + map!['lowlinecenterline'] = '﹎'; + map!['lowlinecmb'] = '̲'; + map!['lowlinedashed'] = '﹍'; + map!['lozenge'] = '◊'; + map!['lparen'] = '⒧'; + map!['lslash'] = 'ł'; + map!['lsquare'] = 'ℓ'; + map!['lsuperior'] = ''; + map!['ltshade'] = '░'; + map!['luthai'] = 'ฦ'; + map!['lvocalicbengali'] = 'ঌ'; + map!['lvocalicdeva'] = 'ऌ'; + map!['lvocalicvowelsignbengali'] = 'ৢ'; + map!['lvocalicvowelsigndeva'] = 'ॢ'; + map!['lxsquare'] = '㏓'; + map!['m'] = 'm'; + map!['mabengali'] = 'ম'; + map!['macron'] = '¯'; + map!['macronbelowcmb'] = '̱'; + map!['macroncmb'] = '̄'; + map!['macronlowmod'] = 'ˍ'; + map!['macronmonospace'] = ' ̄'; + map!['macute'] = 'ḿ'; + map!['madeva'] = 'म'; + map!['magujarati'] = 'મ'; + map!['magurmukhi'] = 'ਮ'; + map!['mahapakhhebrew'] = '֤'; + map!['mahapakhlefthebrew'] = '֤'; + map!['mahiragana'] = 'ま'; + map!['maichattawalowleftthai'] = ''; + map!['maichattawalowrightthai'] = ''; + map!['maichattawathai'] = '๋'; + map!['maichattawaupperleftthai'] = ''; + map!['maieklowleftthai'] = ''; + map!['maieklowrightthai'] = ''; + map!['maiekthai'] = '่'; + map!['maiekupperleftthai'] = ''; + map!['maihanakatleftthai'] = ''; + map!['maihanakatthai'] = 'ั'; + map!['maitaikhuleftthai'] = ''; + map!['maitaikhuthai'] = '็'; + map!['maitholowleftthai'] = ''; + map!['maitholowrightthai'] = ''; + map!['maithothai'] = '้'; + map!['maithoupperleftthai'] = ''; + map!['maitrilowleftthai'] = ''; + map!['maitrilowrightthai'] = ''; + map!['maitrithai'] = '๊'; + map!['maitriupperleftthai'] = ''; + map!['maiyamokthai'] = 'ๆ'; + map!['makatakana'] = 'マ'; + map!['makatakanahalfwidth'] = 'マ'; + map!['male'] = '♂'; + map!['mansyonsquare'] = '㍇'; + map!['maqafhebrew'] = '־'; + map!['mars'] = '♂'; + map!['masoracirclehebrew'] = '֯'; + map!['masquare'] = '㎃'; + map!['mbopomofo'] = 'ㄇ'; + map!['mbsquare'] = '㏔'; + map!['mcircle'] = 'ⓜ'; + map!['mcubedsquare'] = '㎥'; + map!['mdotaccent'] = 'ṁ'; + map!['mdotbelow'] = 'ṃ'; + map!['meemarabic'] = 'م'; + map!['meemfinalarabic'] = 'ﻢ'; + map!['meeminitialarabic'] = 'ﻣ'; + map!['meemmedialarabic'] = 'ﻤ'; + map!['meemmeeminitialarabic'] = 'ﳑ'; + map!['meemmeemisolatedarabic'] = 'ﱈ'; + map!['meetorusquare'] = '㍍'; + map!['mehiragana'] = 'め'; + map!['meizierasquare'] = '㍾'; + map!['mekatakana'] = 'メ'; + map!['mekatakanahalfwidth'] = 'メ'; + map!['mem'] = 'מ'; + map!['memdagesh'] = 'מּ'; + map!['memdageshhebrew'] = 'מּ'; + map!['memhebrew'] = 'מ'; + map!['menarmenian'] = 'մ'; + map!['merkhahebrew'] = '֥'; + map!['merkhakefulahebrew'] = '֦'; + map!['merkhakefulalefthebrew'] = '֦'; + map!['merkhalefthebrew'] = '֥'; + map!['mhook'] = 'ɱ'; + map!['mhzsquare'] = '㎒'; + map!['middledotkatakanahalfwidth'] = '・'; + map!['middot'] = '·'; + map!['mieumacirclekorean'] = '㉲'; + map!['mieumaparenkorean'] = '㈒'; + map!['mieumcirclekorean'] = '㉤'; + map!['mieumkorean'] = 'ㅁ'; + map!['mieumpansioskorean'] = 'ㅰ'; + map!['mieumparenkorean'] = '㈄'; + map!['mieumpieupkorean'] = 'ㅮ'; + map!['mieumsioskorean'] = 'ㅯ'; + map!['mihiragana'] = 'み'; + map!['mikatakana'] = 'ミ'; + map!['mikatakanahalfwidth'] = 'ミ'; + map!['negationslash'] = '-'; + map!['minus'] = '−'; + map!['minusbelowcmb'] = '̠'; + map!['minuscircle'] = '⊖'; + map!['minusmod'] = '˗'; + map!['minusplus'] = '∓'; + map!['minute'] = '′'; + map!['miribaarusquare'] = '㍊'; + map!['mirisquare'] = '㍉'; + map!['mlonglegturned'] = 'ɰ'; + map!['mlsquare'] = '㎖'; + map!['mmcubedsquare'] = '㎣'; + map!['mmonospace'] = 'm'; + map!['mmsquaredsquare'] = '㎟'; + map!['mohiragana'] = 'も'; + map!['mohmsquare'] = '㏁'; + map!['mokatakana'] = 'モ'; + map!['mokatakanahalfwidth'] = 'モ'; + map!['molsquare'] = '㏖'; + map!['momathai'] = 'ม'; + map!['moverssquare'] = '㎧'; + map!['moverssquaredsquare'] = '㎨'; + map!['mparen'] = '⒨'; + map!['mpasquare'] = '㎫'; + map!['mssquare'] = '㎳'; + map!['msuperior'] = ''; + map!['mturned'] = 'ɯ'; + map!['mu'] = 'µ'; + map!['mu1'] = 'µ'; + map!['muasquare'] = '㎂'; + map!['muchgreater'] = '≫'; + map!['muchless'] = '≪'; + map!['mufsquare'] = '㎌'; + map!['mugreek'] = 'μ'; + map!['mugsquare'] = '㎍'; + map!['muhiragana'] = 'む'; + map!['mukatakana'] = 'ム'; + map!['mukatakanahalfwidth'] = 'ム'; + map!['mulsquare'] = '㎕'; + map!['multiply'] = '×'; + map!['mumsquare'] = '㎛'; + map!['munahhebrew'] = '֣'; + map!['munahlefthebrew'] = '֣'; + map!['musicalnote'] = '♪'; + map!['musicalnotedbl'] = '♫'; + map!['musicflatsign'] = '♭'; + map!['musicsharpsign'] = '♯'; + map!['mussquare'] = '㎲'; + map!['muvsquare'] = '㎶'; + map!['muwsquare'] = '㎼'; + map!['mvmegasquare'] = '㎹'; + map!['mvsquare'] = '㎷'; + map!['mwmegasquare'] = '㎿'; + map!['mwsquare'] = '㎽'; + map!['n'] = 'n'; + map!['nabengali'] = 'ন'; + map!['nabla'] = '∇'; + map!['nacute'] = 'ń'; + map!['nadeva'] = 'न'; + map!['nagujarati'] = 'ન'; + map!['nagurmukhi'] = 'ਨ'; + map!['nahiragana'] = 'な'; + map!['nakatakana'] = 'ナ'; + map!['nakatakanahalfwidth'] = 'ナ'; + map!['napostrophe'] = 'ʼn'; + map!['nasquare'] = '㎁'; + map!['nbopomofo'] = 'ㄋ'; + map!['nbspace'] = '\u00a0'; + map!['ncaron'] = 'ň'; + map!['ncedilla'] = 'ņ'; + map!['ncircle'] = 'ⓝ'; + map!['ncircumflexbelow'] = 'ṋ'; + map!['ncommaaccent'] = 'ņ'; + map!['ndotaccent'] = 'ṅ'; + map!['ndotbelow'] = 'ṇ'; + map!['nehiragana'] = 'ね'; + map!['nekatakana'] = 'ネ'; + map!['nekatakanahalfwidth'] = 'ネ'; + map!['newsheqelsign'] = '₪'; + map!['nfsquare'] = '㎋'; + map!['ngabengali'] = 'ঙ'; + map!['ngadeva'] = 'ङ'; + map!['ngagujarati'] = 'ઙ'; + map!['ngagurmukhi'] = 'ਙ'; + map!['ngonguthai'] = 'ง'; + map!['nhiragana'] = 'ん'; + map!['nhookleft'] = 'ɲ'; + map!['nhookretroflex'] = 'ɳ'; + map!['nieunacirclekorean'] = '㉯'; + map!['nieunaparenkorean'] = '㈏'; + map!['nieuncieuckorean'] = 'ㄵ'; + map!['nieuncirclekorean'] = '㉡'; + map!['nieunhieuhkorean'] = 'ㄶ'; + map!['nieunkorean'] = 'ㄴ'; + map!['nieunpansioskorean'] = 'ㅨ'; + map!['nieunparenkorean'] = '㈁'; + map!['nieunsioskorean'] = 'ㅧ'; + map!['nieuntikeutkorean'] = 'ㅦ'; + map!['nihiragana'] = 'に'; + map!['nikatakana'] = 'ニ'; + map!['nikatakanahalfwidth'] = 'ニ'; + map!['nikhahitleftthai'] = ''; + map!['nikhahitthai'] = 'ํ'; + map!['nine'] = '9'; + map!['ninearabic'] = '٩'; + map!['ninebengali'] = '৯'; + map!['ninecircle'] = '⑨'; + map!['ninecircleinversesansserif'] = '➒'; + map!['ninedeva'] = '९'; + map!['ninegujarati'] = '૯'; + map!['ninegurmukhi'] = '੯'; + map!['ninehackarabic'] = '٩'; + map!['ninehangzhou'] = '〩'; + map!['nineideographicparen'] = '㈨'; + map!['nineinferior'] = '₉'; + map!['ninemonospace'] = '9'; + map!['nineoldstyle'] = ''; + map!['nineparen'] = '⑼'; + map!['nineperiod'] = '⒐'; + map!['ninepersian'] = '۹'; + map!['nineroman'] = 'ⅸ'; + map!['ninesuperior'] = '⁹'; + map!['nineteencircle'] = '⑲'; + map!['nineteenparen'] = '⒆'; + map!['nineteenperiod'] = '⒚'; + map!['ninethai'] = '๙'; + map!['nj'] = 'nj'; + map!['njecyrillic'] = 'њ'; + map!['nkatakana'] = 'ン'; + map!['nkatakanahalfwidth'] = 'ン'; + map!['nlegrightlong'] = 'ƞ'; + map!['nlinebelow'] = 'ṉ'; + map!['nmonospace'] = 'n'; + map!['nmsquare'] = '㎚'; + map!['nnabengali'] = 'ণ'; + map!['nnadeva'] = 'ण'; + map!['nnagujarati'] = 'ણ'; + map!['nnagurmukhi'] = 'ਣ'; + map!['nnnadeva'] = 'ऩ'; + map!['nohiragana'] = 'の'; + map!['nokatakana'] = 'ノ'; + map!['nokatakanahalfwidth'] = 'ノ'; + map!['nonbreakingspace'] = '\u00a0'; + map!['nonenthai'] = 'ณ'; + map!['nonuthai'] = 'น'; + map!['noonarabic'] = 'ن'; + map!['noonfinalarabic'] = 'ﻦ'; + map!['noonghunnaarabic'] = 'ں'; + map!['noonghunnafinalarabic'] = 'ﮟ'; + map!['nooninitialarabic'] = 'ﻧ'; + map!['noonjeeminitialarabic'] = 'ﳒ'; + map!['noonjeemisolatedarabic'] = 'ﱋ'; + map!['noonmedialarabic'] = 'ﻨ'; + map!['noonmeeminitialarabic'] = 'ﳕ'; + map!['noonmeemisolatedarabic'] = 'ﱎ'; + map!['noonnoonfinalarabic'] = 'ﲍ'; + map!['notcontains'] = '∌'; + map!['notelement'] = '∉'; + map!['notelementof'] = '∉'; + map!['notequal'] = '≠'; + map!['notgreater'] = '≯'; + map!['notgreaternorequal'] = '≱'; + map!['notgreaternorless'] = '≹'; + map!['notidentical'] = '≢'; + map!['notless'] = '≮'; + map!['notlessnorequal'] = '≰'; + map!['notparallel'] = '∦'; + map!['notprecedes'] = '⊀'; + map!['notsubset'] = '⊄'; + map!['notsucceeds'] = '⊁'; + map!['notsuperset'] = '⊅'; + map!['nowarmenian'] = 'ն'; + map!['nparen'] = '⒩'; + map!['nssquare'] = '㎱'; + map!['nsuperior'] = 'ⁿ'; + map!['ntilde'] = 'ñ'; + map!['nu'] = 'ν'; + map!['nuhiragana'] = 'ぬ'; + map!['nukatakana'] = 'ヌ'; + map!['nukatakanahalfwidth'] = 'ヌ'; + map!['nuktabengali'] = '়'; + map!['nuktadeva'] = '़'; + map!['nuktagujarati'] = '઼'; + map!['nuktagurmukhi'] = '਼'; + map!['numbersign'] = '#'; + map!['numbersignmonospace'] = '#'; + map!['numbersignsmall'] = '﹟'; + map!['numeralsigngreek'] = 'ʹ'; + map!['numeralsignlowergreek'] = '͵'; + map!['numero'] = '№'; + map!['nun'] = 'נ'; + map!['nundagesh'] = 'נּ'; + map!['nundageshhebrew'] = 'נּ'; + map!['nunhebrew'] = 'נ'; + map!['nvsquare'] = '㎵'; + map!['nwsquare'] = '㎻'; + map!['nyabengali'] = 'ঞ'; + map!['nyadeva'] = 'ञ'; + map!['nyagujarati'] = 'ઞ'; + map!['nyagurmukhi'] = 'ਞ'; + map!['o'] = 'o'; + map!['oacute'] = 'ó'; + map!['oangthai'] = 'อ'; + map!['obarred'] = 'ɵ'; + map!['obarredcyrillic'] = 'ө'; + map!['obarreddieresiscyrillic'] = 'ӫ'; + map!['obengali'] = 'ও'; + map!['obopomofo'] = 'ㄛ'; + map!['obreve'] = 'ŏ'; + map!['ocandradeva'] = 'ऑ'; + map!['ocandragujarati'] = 'ઑ'; + map!['ocandravowelsigndeva'] = 'ॉ'; + map!['ocandravowelsigngujarati'] = 'ૉ'; + map!['ocaron'] = 'ǒ'; + map!['ocircle'] = 'ⓞ'; + map!['ocircumflex'] = 'ô'; + map!['ocircumflexacute'] = 'ố'; + map!['ocircumflexdotbelow'] = 'ộ'; + map!['ocircumflexgrave'] = 'ồ'; + map!['ocircumflexhookabove'] = 'ổ'; + map!['ocircumflextilde'] = 'ỗ'; + map!['ocyrillic'] = 'о'; + map!['odblacute'] = 'ő'; + map!['odblgrave'] = 'ȍ'; + map!['odeva'] = 'ओ'; + map!['odieresis'] = 'ö'; + map!['odieresiscyrillic'] = 'ӧ'; + map!['odotbelow'] = 'ọ'; + map!['oe'] = 'œ'; + map!['oekorean'] = 'ㅚ'; + map!['ogonek'] = '˛'; + map!['ogonekcmb'] = '̨'; + map!['ograve'] = 'ò'; + map!['ogujarati'] = 'ઓ'; + map!['oharmenian'] = 'օ'; + map!['ohiragana'] = 'お'; + map!['ohookabove'] = 'ỏ'; + map!['ohorn'] = 'ơ'; + map!['ohornacute'] = 'ớ'; + map!['ohorndotbelow'] = 'ợ'; + map!['ohorngrave'] = 'ờ'; + map!['ohornhookabove'] = 'ở'; + map!['ohorntilde'] = 'ỡ'; + map!['ohungarumlaut'] = 'ő'; + map!['oi'] = 'ƣ'; + map!['oinvertedbreve'] = 'ȏ'; + map!['okatakana'] = 'オ'; + map!['okatakanahalfwidth'] = 'オ'; + map!['okorean'] = 'ㅗ'; + map!['olehebrew'] = '֫'; + map!['omacron'] = 'ō'; + map!['omacronacute'] = 'ṓ'; + map!['omacrongrave'] = 'ṑ'; + map!['omdeva'] = 'ॐ'; + map!['omega'] = 'ω'; + map!['omega1'] = 'ϖ'; + map!['omegacyrillic'] = 'ѡ'; + map!['omegalatinclosed'] = 'ɷ'; + map!['omegaroundcyrillic'] = 'ѻ'; + map!['omegatitlocyrillic'] = 'ѽ'; + map!['omegatonos'] = 'ώ'; + map!['omgujarati'] = 'ૐ'; + map!['omicron'] = 'ο'; + map!['omicrontonos'] = 'ό'; + map!['omonospace'] = 'o'; + map!['one'] = '1'; + map!['onearabic'] = '١'; + map!['onebengali'] = '১'; + map!['onecircle'] = '①'; + map!['onecircleinversesansserif'] = '➊'; + map!['onedeva'] = '१'; + map!['onedotenleader'] = '․'; + map!['oneeighth'] = '⅛'; + map!['onefitted'] = ''; + map!['onegujarati'] = '૧'; + map!['onegurmukhi'] = '੧'; + map!['onehackarabic'] = '١'; + map!['onehalf'] = '½'; + map!['onehangzhou'] = '〡'; + map!['oneideographicparen'] = '㈠'; + map!['oneinferior'] = '₁'; + map!['onemonospace'] = '1'; + map!['onenumeratorbengali'] = '৴'; + map!['oneoldstyle'] = ''; + map!['oneparen'] = '⑴'; + map!['oneperiod'] = '⒈'; + map!['onepersian'] = '۱'; + map!['onequarter'] = '¼'; + map!['oneroman'] = 'ⅰ'; + map!['onesuperior'] = '¹'; + map!['onethai'] = '๑'; + map!['onethird'] = '⅓'; + map!['oogonek'] = 'ǫ'; + map!['oogonekmacron'] = 'ǭ'; + map!['oogurmukhi'] = 'ਓ'; + map!['oomatragurmukhi'] = 'ੋ'; + map!['oopen'] = 'ɔ'; + map!['oparen'] = '⒪'; + map!['openbullet'] = '◦'; + map!['option'] = '⌥'; + map!['ordfeminine'] = 'ª'; + map!['ordmasculine'] = 'º'; + map!['orthogonal'] = '∟'; + map!['oshortdeva'] = 'ऒ'; + map!['oshortvowelsigndeva'] = 'ॊ'; + map!['oslash'] = 'ø'; + map!['oslashacute'] = 'ǿ'; + map!['osmallhiragana'] = 'ぉ'; + map!['osmallkatakana'] = 'ォ'; + map!['osmallkatakanahalfwidth'] = 'ォ'; + map!['ostrokeacute'] = 'ǿ'; + map!['osuperior'] = ''; + map!['otcyrillic'] = 'ѿ'; + map!['otilde'] = 'õ'; + map!['otildeacute'] = 'ṍ'; + map!['otildedieresis'] = 'ṏ'; + map!['oubopomofo'] = 'ㄡ'; + map!['overline'] = '‾'; + map!['overlinecenterline'] = '﹊'; + map!['overlinecmb'] = '̅'; + map!['overlinedashed'] = '﹉'; + map!['overlinedblwavy'] = '﹌'; + map!['overlinewavy'] = '﹋'; + map!['overscore'] = '¯'; + map!['ovowelsignbengali'] = 'ো'; + map!['ovowelsigndeva'] = 'ो'; + map!['ovowelsigngujarati'] = 'ો'; + map!['p'] = 'p'; + map!['paampssquare'] = '㎀'; + map!['paasentosquare'] = '㌫'; + map!['pabengali'] = 'প'; + map!['pacute'] = 'ṕ'; + map!['padeva'] = 'प'; + map!['pagedown'] = '⇟'; + map!['pageup'] = '⇞'; + map!['pagujarati'] = 'પ'; + map!['pagurmukhi'] = 'ਪ'; + map!['pahiragana'] = 'ぱ'; + map!['paiyannoithai'] = 'ฯ'; + map!['pakatakana'] = 'パ'; + map!['palatalizationcyrilliccmb'] = '҄'; + map!['palochkacyrillic'] = 'Ӏ'; + map!['pansioskorean'] = 'ㅿ'; + map!['paragraph'] = '¶'; + map!['parallel'] = '∥'; + map!['parenleft'] = '('; + map!['parenleftaltonearabic'] = '﴾'; + map!['parenleftbt'] = ''; + map!['parenleftex'] = ''; + map!['parenleftinferior'] = '₍'; + map!['parenleftmonospace'] = '('; + map!['parenleftsmall'] = '﹙'; + map!['parenleftsuperior'] = '⁽'; + map!['parenlefttp'] = ''; + map!['parenleftvertical'] = '︵'; + map!['parenright'] = ')'; + map!['parenrightaltonearabic'] = '﴿'; + map!['parenrightbt'] = ''; + map!['parenrightex'] = ''; + map!['parenrightinferior'] = '₎'; + map!['parenrightmonospace'] = ')'; + map!['parenrightsmall'] = '﹚'; + map!['parenrightsuperior'] = '⁾'; + map!['parenrighttp'] = ''; + map!['parenrightvertical'] = '︶'; + map!['partialdiff'] = '∂'; + map!['paseqhebrew'] = '׀'; + map!['pashtahebrew'] = '֙'; + map!['pasquare'] = '㎩'; + map!['patah'] = 'ַ'; + map!['patah11'] = 'ַ'; + map!['patah1d'] = 'ַ'; + map!['patah2a'] = 'ַ'; + map!['patahhebrew'] = 'ַ'; + map!['patahnarrowhebrew'] = 'ַ'; + map!['patahquarterhebrew'] = 'ַ'; + map!['patahwidehebrew'] = 'ַ'; + map!['pazerhebrew'] = '֡'; + map!['pbopomofo'] = 'ㄆ'; + map!['pcircle'] = 'ⓟ'; + map!['pdotaccent'] = 'ṗ'; + map!['pe'] = 'פ'; + map!['pecyrillic'] = 'п'; + map!['pedagesh'] = 'פּ'; + map!['pedageshhebrew'] = 'פּ'; + map!['peezisquare'] = '㌻'; + map!['pefinaldageshhebrew'] = 'ףּ'; + map!['peharabic'] = 'پ'; + map!['peharmenian'] = 'պ'; + map!['pehebrew'] = 'פ'; + map!['pehfinalarabic'] = 'ﭗ'; + map!['pehinitialarabic'] = 'ﭘ'; + map!['pehiragana'] = 'ぺ'; + map!['pehmedialarabic'] = 'ﭙ'; + map!['pekatakana'] = 'ペ'; + map!['pemiddlehookcyrillic'] = 'ҧ'; + map!['perafehebrew'] = 'פֿ'; + map!['percent'] = '%'; + map!['percentarabic'] = '٪'; + map!['percentmonospace'] = '%'; + map!['percentsmall'] = '﹪'; + map!['period'] = '.'; + map!['periodarmenian'] = '։'; + map!['periodcentered'] = '·'; + map!['periodhalfwidth'] = '。'; + map!['periodinferior'] = ''; + map!['periodmonospace'] = '.'; + map!['periodsmall'] = '﹒'; + map!['periodsuperior'] = ''; + map!['perispomenigreekcmb'] = '͂'; + map!['perpendicular'] = '⊥'; + map!['perthousand'] = '‰'; + map!['peseta'] = '₧'; + map!['pfsquare'] = '㎊'; + map!['phabengali'] = 'ফ'; + map!['phadeva'] = 'फ'; + map!['phagujarati'] = 'ફ'; + map!['phagurmukhi'] = 'ਫ'; + map!['phi'] = 'φ'; + map!['phi1'] = 'ϕ'; + map!['phieuphacirclekorean'] = '㉺'; + map!['phieuphaparenkorean'] = '㈚'; + map!['phieuphcirclekorean'] = '㉬'; + map!['phieuphkorean'] = 'ㅍ'; + map!['phieuphparenkorean'] = '㈌'; + map!['philatin'] = 'ɸ'; + map!['phinthuthai'] = 'ฺ'; + map!['phisymbolgreek'] = 'ϕ'; + map!['phook'] = 'ƥ'; + map!['phophanthai'] = 'พ'; + map!['phophungthai'] = 'ผ'; + map!['phosamphaothai'] = 'ภ'; + map!['pi'] = 'π'; + map!['pieupacirclekorean'] = '㉳'; + map!['pieupaparenkorean'] = '㈓'; + map!['pieupcieuckorean'] = 'ㅶ'; + map!['pieupcirclekorean'] = '㉥'; + map!['pieupkiyeokkorean'] = 'ㅲ'; + map!['pieupkorean'] = 'ㅂ'; + map!['pieupparenkorean'] = '㈅'; + map!['pieupsioskiyeokkorean'] = 'ㅴ'; + map!['pieupsioskorean'] = 'ㅄ'; + map!['pieupsiostikeutkorean'] = 'ㅵ'; + map!['pieupthieuthkorean'] = 'ㅷ'; + map!['pieuptikeutkorean'] = 'ㅳ'; + map!['pihiragana'] = 'ぴ'; + map!['pikatakana'] = 'ピ'; + map!['pisymbolgreek'] = 'ϖ'; + map!['piwrarmenian'] = 'փ'; + map!['plus'] = '+'; + map!['plusbelowcmb'] = '̟'; + map!['pluscircle'] = '⊕'; + map!['plusminus'] = '±'; + map!['plusmod'] = '˖'; + map!['plusmonospace'] = '+'; + map!['plussmall'] = '﹢'; + map!['plussuperior'] = '⁺'; + map!['pmonospace'] = 'p'; + map!['pmsquare'] = '㏘'; + map!['pohiragana'] = 'ぽ'; + map!['pointingindexdownwhite'] = '☟'; + map!['pointingindexleftwhite'] = '☜'; + map!['pointingindexrightwhite'] = '☞'; + map!['pointingindexupwhite'] = '☝'; + map!['pokatakana'] = 'ポ'; + map!['poplathai'] = 'ป'; + map!['postalmark'] = '〒'; + map!['postalmarkface'] = '〠'; + map!['pparen'] = '⒫'; + map!['precedes'] = '≺'; + map!['prescription'] = '℞'; + map!['primemod'] = 'ʹ'; + map!['primereversed'] = '‵'; + map!['product'] = '∏'; + map!['projective'] = '⌅'; + map!['prolongedkana'] = 'ー'; + map!['propellor'] = '⌘'; + map!['propersubset'] = '⊂'; + map!['propersuperset'] = '⊃'; + map!['proportion'] = '∷'; + map!['proportional'] = '∝'; + map!['psi'] = 'ψ'; + map!['psicyrillic'] = 'ѱ'; + map!['psilipneumatacyrilliccmb'] = '҆'; + map!['pssquare'] = '㎰'; + map!['puhiragana'] = 'ぷ'; + map!['pukatakana'] = 'プ'; + map!['pvsquare'] = '㎴'; + map!['pwsquare'] = '㎺'; + map!['q'] = 'q'; + map!['qadeva'] = 'क़'; + map!['qadmahebrew'] = '֨'; + map!['qafarabic'] = 'ق'; + map!['qaffinalarabic'] = 'ﻖ'; + map!['qafinitialarabic'] = 'ﻗ'; + map!['qafmedialarabic'] = 'ﻘ'; + map!['qamats'] = 'ָ'; + map!['qamats10'] = 'ָ'; + map!['qamats1a'] = 'ָ'; + map!['qamats1c'] = 'ָ'; + map!['qamats27'] = 'ָ'; + map!['qamats29'] = 'ָ'; + map!['qamats33'] = 'ָ'; + map!['qamatsde'] = 'ָ'; + map!['qamatshebrew'] = 'ָ'; + map!['qamatsnarrowhebrew'] = 'ָ'; + map!['qamatsqatanhebrew'] = 'ָ'; + map!['qamatsqatannarrowhebrew'] = 'ָ'; + map!['qamatsqatanquarterhebrew'] = 'ָ'; + map!['qamatsqatanwidehebrew'] = 'ָ'; + map!['qamatsquarterhebrew'] = 'ָ'; + map!['qamatswidehebrew'] = 'ָ'; + map!['qarneyparahebrew'] = '֟'; + map!['qbopomofo'] = 'ㄑ'; + map!['qcircle'] = 'ⓠ'; + map!['qhook'] = 'ʠ'; + map!['qmonospace'] = 'q'; + map!['qof'] = 'ק'; + map!['qofdagesh'] = 'קּ'; + map!['qofdageshhebrew'] = 'קּ'; + map!['qparen'] = '⒬'; + map!['quarternote'] = '♩'; + map!['qubuts'] = 'ֻ'; + map!['qubuts18'] = 'ֻ'; + map!['qubuts25'] = 'ֻ'; + map!['qubuts31'] = 'ֻ'; + map!['qubutshebrew'] = 'ֻ'; + map!['qubutsnarrowhebrew'] = 'ֻ'; + map!['qubutsquarterhebrew'] = 'ֻ'; + map!['qubutswidehebrew'] = 'ֻ'; + map!['question'] = '?'; + map!['questionarabic'] = '؟'; + map!['questionarmenian'] = '՞'; + map!['questiondown'] = '¿'; + map!['questiondownsmall'] = ''; + map!['questiongreek'] = ';'; + map!['questionmonospace'] = '?'; + map!['questionsmall'] = ''; + map!['quotedbl'] = '"'; + map!['quotedblbase'] = '„'; + map!['quotedblleft'] = '“'; + map!['quotedblmonospace'] = '"'; + map!['quotedblprime'] = '〞'; + map!['quotedblprimereversed'] = '〝'; + map!['quotedblright'] = '”'; + map!['quoteleft'] = '‘'; + map!['quoteleftreversed'] = '‛'; + map!['quotereversed'] = '‛'; + map!['quoteright'] = '’'; + map!['quoterightn'] = 'ʼn'; + map!['quotesinglbase'] = '‚'; + map!['quotesingle'] = '\''; + map!['quotesinglemonospace'] = '''; + map!['r'] = 'r'; + map!['raarmenian'] = 'ռ'; + map!['rabengali'] = 'র'; + map!['racute'] = 'ŕ'; + map!['radeva'] = 'र'; + map!['radical'] = '√'; + map!['radicalex'] = ''; + map!['radoverssquare'] = '㎮'; + map!['radoverssquaredsquare'] = '㎯'; + map!['radsquare'] = '㎭'; + map!['rafe'] = 'ֿ'; + map!['rafehebrew'] = 'ֿ'; + map!['ragujarati'] = 'ર'; + map!['ragurmukhi'] = 'ਰ'; + map!['rahiragana'] = 'ら'; + map!['rakatakana'] = 'ラ'; + map!['rakatakanahalfwidth'] = 'ラ'; + map!['ralowerdiagonalbengali'] = 'ৱ'; + map!['ramiddlediagonalbengali'] = 'ৰ'; + map!['ramshorn'] = 'ɤ'; + map!['ratio'] = '∶'; + map!['rbopomofo'] = 'ㄖ'; + map!['rcaron'] = 'ř'; + map!['rcedilla'] = 'ŗ'; + map!['rcircle'] = 'ⓡ'; + map!['rcommaaccent'] = 'ŗ'; + map!['rdblgrave'] = 'ȑ'; + map!['rdotaccent'] = 'ṙ'; + map!['rdotbelow'] = 'ṛ'; + map!['rdotbelowmacron'] = 'ṝ'; + map!['referencemark'] = '※'; + map!['reflexsubset'] = '⊆'; + map!['reflexsuperset'] = '⊇'; + map!['registered'] = '®'; + map!['registersans'] = ''; + map!['registerserif'] = ''; + map!['reharabic'] = 'ر'; + map!['reharmenian'] = 'ր'; + map!['rehfinalarabic'] = 'ﺮ'; + map!['rehiragana'] = 'れ'; + map!['rekatakana'] = 'レ'; + map!['rekatakanahalfwidth'] = 'レ'; + map!['resh'] = 'ר'; + map!['reshdageshhebrew'] = 'רּ'; + map!['reshhebrew'] = 'ר'; + map!['reversedtilde'] = '∽'; + map!['reviahebrew'] = '֗'; + map!['reviamugrashhebrew'] = '֗'; + map!['revlogicalnot'] = '⌐'; + map!['rfishhook'] = 'ɾ'; + map!['rfishhookreversed'] = 'ɿ'; + map!['rhabengali'] = 'ঢ়'; + map!['rhadeva'] = 'ढ़'; + map!['rho'] = 'ρ'; + map!['rhook'] = 'ɽ'; + map!['rhookturned'] = 'ɻ'; + map!['rhookturnedsuperior'] = 'ʵ'; + map!['rhosymbolgreek'] = 'ϱ'; + map!['rhotichookmod'] = '˞'; + map!['rieulacirclekorean'] = '㉱'; + map!['rieulaparenkorean'] = '㈑'; + map!['rieulcirclekorean'] = '㉣'; + map!['rieulhieuhkorean'] = 'ㅀ'; + map!['rieulkiyeokkorean'] = 'ㄺ'; + map!['rieulkiyeoksioskorean'] = 'ㅩ'; + map!['rieulkorean'] = 'ㄹ'; + map!['rieulmieumkorean'] = 'ㄻ'; + map!['rieulpansioskorean'] = 'ㅬ'; + map!['rieulparenkorean'] = '㈃'; + map!['rieulphieuphkorean'] = 'ㄿ'; + map!['rieulpieupkorean'] = 'ㄼ'; + map!['rieulpieupsioskorean'] = 'ㅫ'; + map!['rieulsioskorean'] = 'ㄽ'; + map!['rieulthieuthkorean'] = 'ㄾ'; + map!['rieultikeutkorean'] = 'ㅪ'; + map!['rieulyeorinhieuhkorean'] = 'ㅭ'; + map!['rightangle'] = '∟'; + map!['righttackbelowcmb'] = '̙'; + map!['righttriangle'] = '⊿'; + map!['rihiragana'] = 'り'; + map!['rikatakana'] = 'リ'; + map!['rikatakanahalfwidth'] = 'リ'; + map!['ring'] = '˚'; + map!['ringbelowcmb'] = '̥'; + map!['ringcmb'] = '̊'; + map!['ringhalfleft'] = 'ʿ'; + map!['ringhalfleftarmenian'] = 'ՙ'; + map!['ringhalfleftbelowcmb'] = '̜'; + map!['ringhalfleftcentered'] = '˓'; + map!['ringhalfright'] = 'ʾ'; + map!['ringhalfrightbelowcmb'] = '̹'; + map!['ringhalfrightcentered'] = '˒'; + map!['rinvertedbreve'] = 'ȓ'; + map!['rittorusquare'] = '㍑'; + map!['rlinebelow'] = 'ṟ'; + map!['rlongleg'] = 'ɼ'; + map!['rlonglegturned'] = 'ɺ'; + map!['rmonospace'] = 'r'; + map!['rohiragana'] = 'ろ'; + map!['rokatakana'] = 'ロ'; + map!['rokatakanahalfwidth'] = 'ロ'; + map!['roruathai'] = 'ร'; + map!['rparen'] = '⒭'; + map!['rrabengali'] = 'ড়'; + map!['rradeva'] = 'ऱ'; + map!['rragurmukhi'] = 'ੜ'; + map!['rreharabic'] = 'ڑ'; + map!['rrehfinalarabic'] = 'ﮍ'; + map!['rrvocalicbengali'] = 'ৠ'; + map!['rrvocalicdeva'] = 'ॠ'; + map!['rrvocalicgujarati'] = 'ૠ'; + map!['rrvocalicvowelsignbengali'] = 'ৄ'; + map!['rrvocalicvowelsigndeva'] = 'ॄ'; + map!['rrvocalicvowelsigngujarati'] = 'ૄ'; + map!['rsuperior'] = ''; + map!['rtblock'] = '▐'; + map!['rturned'] = 'ɹ'; + map!['rturnedsuperior'] = 'ʴ'; + map!['ruhiragana'] = 'る'; + map!['rukatakana'] = 'ル'; + map!['rukatakanahalfwidth'] = 'ル'; + map!['rupeemarkbengali'] = '৲'; + map!['rupeesignbengali'] = '৳'; + map!['rupiah'] = ''; + map!['ruthai'] = 'ฤ'; + map!['rvocalicbengali'] = 'ঋ'; + map!['rvocalicdeva'] = 'ऋ'; + map!['rvocalicgujarati'] = 'ઋ'; + map!['rvocalicvowelsignbengali'] = 'ৃ'; + map!['rvocalicvowelsigndeva'] = 'ृ'; + map!['rvocalicvowelsigngujarati'] = 'ૃ'; + map!['s'] = 's'; + map!['sabengali'] = 'স'; + map!['sacute'] = 'ś'; + map!['sacutedotaccent'] = 'ṥ'; + map!['sadarabic'] = 'ص'; + map!['sadeva'] = 'स'; + map!['sadfinalarabic'] = 'ﺺ'; + map!['sadinitialarabic'] = 'ﺻ'; + map!['sadmedialarabic'] = 'ﺼ'; + map!['sagujarati'] = 'સ'; + map!['sagurmukhi'] = 'ਸ'; + map!['sahiragana'] = 'さ'; + map!['sakatakana'] = 'サ'; + map!['sakatakanahalfwidth'] = 'サ'; + map!['sallallahoualayhewasallamarabic'] = 'ﷺ'; + map!['samekh'] = 'ס'; + map!['samekhdagesh'] = 'סּ'; + map!['samekhdageshhebrew'] = 'סּ'; + map!['samekhhebrew'] = 'ס'; + map!['saraaathai'] = 'า'; + map!['saraaethai'] = 'แ'; + map!['saraaimaimalaithai'] = 'ไ'; + map!['saraaimaimuanthai'] = 'ใ'; + map!['saraamthai'] = 'ำ'; + map!['saraathai'] = 'ะ'; + map!['saraethai'] = 'เ'; + map!['saraiileftthai'] = ''; + map!['saraiithai'] = 'ี'; + map!['saraileftthai'] = ''; + map!['saraithai'] = 'ิ'; + map!['saraothai'] = 'โ'; + map!['saraueeleftthai'] = ''; + map!['saraueethai'] = 'ื'; + map!['saraueleftthai'] = ''; + map!['sarauethai'] = 'ึ'; + map!['sarauthai'] = 'ุ'; + map!['sarauuthai'] = 'ู'; + map!['sbopomofo'] = 'ㄙ'; + map!['scaron'] = 'š'; + map!['scarondotaccent'] = 'ṧ'; + map!['scedilla'] = 'ş'; + map!['schwa'] = 'ə'; + map!['schwacyrillic'] = 'ә'; + map!['schwadieresiscyrillic'] = 'ӛ'; + map!['schwahook'] = 'ɚ'; + map!['scircle'] = 'ⓢ'; + map!['scircumflex'] = 'ŝ'; + map!['scommaaccent'] = 'ș'; + map!['sdotaccent'] = 'ṡ'; + map!['sdotbelow'] = 'ṣ'; + map!['sdotbelowdotaccent'] = 'ṩ'; + map!['seagullbelowcmb'] = '̼'; + map!['second'] = '″'; + map!['secondtonechinese'] = 'ˊ'; + map!['section'] = '§'; + map!['seenarabic'] = 'س'; + map!['seenfinalarabic'] = 'ﺲ'; + map!['seeninitialarabic'] = 'ﺳ'; + map!['seenmedialarabic'] = 'ﺴ'; + map!['segol'] = 'ֶ'; + map!['segol13'] = 'ֶ'; + map!['segol1f'] = 'ֶ'; + map!['segol2c'] = 'ֶ'; + map!['segolhebrew'] = 'ֶ'; + map!['segolnarrowhebrew'] = 'ֶ'; + map!['segolquarterhebrew'] = 'ֶ'; + map!['segoltahebrew'] = '֒'; + map!['segolwidehebrew'] = 'ֶ'; + map!['seharmenian'] = 'ս'; + map!['sehiragana'] = 'せ'; + map!['sekatakana'] = 'セ'; + map!['sekatakanahalfwidth'] = 'セ'; + map!['semicolon'] = ';'; + map!['semicolonarabic'] = '؛'; + map!['semicolonmonospace'] = ';'; + map!['semicolonsmall'] = '﹔'; + map!['semivoicedmarkkana'] = '゜'; + map!['semivoicedmarkkanahalfwidth'] = '゚'; + map!['sentisquare'] = '㌢'; + map!['sentosquare'] = '㌣'; + map!['seven'] = '7'; + map!['sevenarabic'] = '٧'; + map!['sevenbengali'] = '৭'; + map!['sevencircle'] = '⑦'; + map!['sevencircleinversesansserif'] = '➐'; + map!['sevendeva'] = '७'; + map!['seveneighths'] = '⅞'; + map!['sevengujarati'] = '૭'; + map!['sevengurmukhi'] = '੭'; + map!['sevenhackarabic'] = '٧'; + map!['sevenhangzhou'] = '〧'; + map!['sevenideographicparen'] = '㈦'; + map!['seveninferior'] = '₇'; + map!['sevenmonospace'] = '7'; + map!['sevenoldstyle'] = ''; + map!['sevenparen'] = '⑺'; + map!['sevenperiod'] = '⒎'; + map!['sevenpersian'] = '۷'; + map!['sevenroman'] = 'ⅶ'; + map!['sevensuperior'] = '⁷'; + map!['seventeencircle'] = '⑰'; + map!['seventeenparen'] = '⒄'; + map!['seventeenperiod'] = '⒘'; + map!['seventhai'] = '๗'; + map!['sfthyphen'] = '­'; + map!['shaarmenian'] = 'շ'; + map!['shabengali'] = 'শ'; + map!['shacyrillic'] = 'ш'; + map!['shaddaarabic'] = 'ّ'; + map!['shaddadammaarabic'] = 'ﱡ'; + map!['shaddadammatanarabic'] = 'ﱞ'; + map!['shaddafathaarabic'] = 'ﱠ'; + map!['shaddakasraarabic'] = 'ﱢ'; + map!['shaddakasratanarabic'] = 'ﱟ'; + map!['shade'] = '▒'; + map!['shadedark'] = '▓'; + map!['shadelight'] = '░'; + map!['shademedium'] = '▒'; + map!['shadeva'] = 'श'; + map!['shagujarati'] = 'શ'; + map!['shagurmukhi'] = 'ਸ਼'; + map!['shalshelethebrew'] = '֓'; + map!['shbopomofo'] = 'ㄕ'; + map!['shchacyrillic'] = 'щ'; + map!['sheenarabic'] = 'ش'; + map!['sheenfinalarabic'] = 'ﺶ'; + map!['sheeninitialarabic'] = 'ﺷ'; + map!['sheenmedialarabic'] = 'ﺸ'; + map!['sheicoptic'] = 'ϣ'; + map!['sheqel'] = '₪'; + map!['sheqelhebrew'] = '₪'; + map!['sheva'] = 'ְ'; + map!['sheva115'] = 'ְ'; + map!['sheva15'] = 'ְ'; + map!['sheva22'] = 'ְ'; + map!['sheva2e'] = 'ְ'; + map!['shevahebrew'] = 'ְ'; + map!['shevanarrowhebrew'] = 'ְ'; + map!['shevaquarterhebrew'] = 'ְ'; + map!['shevawidehebrew'] = 'ְ'; + map!['shhacyrillic'] = 'һ'; + map!['shimacoptic'] = 'ϭ'; + map!['shin'] = 'ש'; + map!['shindagesh'] = 'שּ'; + map!['shindageshhebrew'] = 'שּ'; + map!['shindageshshindot'] = 'שּׁ'; + map!['shindageshshindothebrew'] = 'שּׁ'; + map!['shindageshsindot'] = 'שּׂ'; + map!['shindageshsindothebrew'] = 'שּׂ'; + map!['shindothebrew'] = 'ׁ'; + map!['shinhebrew'] = 'ש'; + map!['shinshindot'] = 'שׁ'; + map!['shinshindothebrew'] = 'שׁ'; + map!['shinsindot'] = 'שׂ'; + map!['shinsindothebrew'] = 'שׂ'; + map!['shook'] = 'ʂ'; + map!['sigma'] = 'σ'; + map!['sigma1'] = 'ς'; + map!['sigmafinal'] = 'ς'; + map!['sigmalunatesymbolgreek'] = 'ϲ'; + map!['sihiragana'] = 'し'; + map!['sikatakana'] = 'シ'; + map!['sikatakanahalfwidth'] = 'シ'; + map!['siluqhebrew'] = 'ֽ'; + map!['siluqlefthebrew'] = 'ֽ'; + map!['similar'] = '∼'; + map!['sindothebrew'] = 'ׂ'; + map!['siosacirclekorean'] = '㉴'; + map!['siosaparenkorean'] = '㈔'; + map!['sioscieuckorean'] = 'ㅾ'; + map!['sioscirclekorean'] = '㉦'; + map!['sioskiyeokkorean'] = 'ㅺ'; + map!['sioskorean'] = 'ㅅ'; + map!['siosnieunkorean'] = 'ㅻ'; + map!['siosparenkorean'] = '㈆'; + map!['siospieupkorean'] = 'ㅽ'; + map!['siostikeutkorean'] = 'ㅼ'; + map!['six'] = '6'; + map!['sixarabic'] = '٦'; + map!['sixbengali'] = '৬'; + map!['sixcircle'] = '⑥'; + map!['sixcircleinversesansserif'] = '➏'; + map!['sixdeva'] = '६'; + map!['sixgujarati'] = '૬'; + map!['sixgurmukhi'] = '੬'; + map!['sixhackarabic'] = '٦'; + map!['sixhangzhou'] = '〦'; + map!['sixideographicparen'] = '㈥'; + map!['sixinferior'] = '₆'; + map!['sixmonospace'] = '6'; + map!['sixoldstyle'] = ''; + map!['sixparen'] = '⑹'; + map!['sixperiod'] = '⒍'; + map!['sixpersian'] = '۶'; + map!['sixroman'] = 'ⅵ'; + map!['sixsuperior'] = '⁶'; + map!['sixteencircle'] = '⑯'; + map!['sixteencurrencydenominatorbengali'] = '৹'; + map!['sixteenparen'] = '⒃'; + map!['sixteenperiod'] = '⒗'; + map!['sixthai'] = '๖'; + map!['slash'] = '/'; + map!['slashmonospace'] = '/'; + map!['slong'] = 'ſ'; + map!['slongdotaccent'] = 'ẛ'; + map!['smileface'] = '☺'; + map!['smonospace'] = 's'; + map!['sofpasuqhebrew'] = '׃'; + map!['softhyphen'] = '­'; + map!['softsigncyrillic'] = 'ь'; + map!['sohiragana'] = 'そ'; + map!['sokatakana'] = 'ソ'; + map!['sokatakanahalfwidth'] = 'ソ'; + map!['soliduslongoverlaycmb'] = '̸'; + map!['solidusshortoverlaycmb'] = '̷'; + map!['sorusithai'] = 'ษ'; + map!['sosalathai'] = 'ศ'; + map!['sosothai'] = 'ซ'; + map!['sosuathai'] = 'ส'; + map!['space'] = ' '; + map!['spacehackarabic'] = ' '; + map!['spade'] = '♠'; + map!['spadesuitblack'] = '♠'; + map!['spadesuitwhite'] = '♤'; + map!['sparen'] = '⒮'; + map!['squarebelowcmb'] = '̻'; + map!['squarecc'] = '㏄'; + map!['squarecm'] = '㎝'; + map!['squarediagonalcrosshatchfill'] = '▩'; + map!['squarehorizontalfill'] = '▤'; + map!['squarekg'] = '㎏'; + map!['squarekm'] = '㎞'; + map!['squarekmcapital'] = '㏎'; + map!['squareln'] = '㏑'; + map!['squarelog'] = '㏒'; + map!['squaremg'] = '㎎'; + map!['squaremil'] = '㏕'; + map!['squaremm'] = '㎜'; + map!['squaremsquared'] = '㎡'; + map!['squareorthogonalcrosshatchfill'] = '▦'; + map!['squareupperlefttolowerrightfill'] = '▧'; + map!['squareupperrighttolowerleftfill'] = '▨'; + map!['squareverticalfill'] = '▥'; + map!['squarewhitewithsmallblack'] = '▣'; + map!['srsquare'] = '㏛'; + map!['ssabengali'] = 'ষ'; + map!['ssadeva'] = 'ष'; + map!['ssagujarati'] = 'ષ'; + map!['ssangcieuckorean'] = 'ㅉ'; + map!['ssanghieuhkorean'] = 'ㆅ'; + map!['ssangieungkorean'] = 'ㆀ'; + map!['ssangkiyeokkorean'] = 'ㄲ'; + map!['ssangnieunkorean'] = 'ㅥ'; + map!['ssangpieupkorean'] = 'ㅃ'; + map!['ssangsioskorean'] = 'ㅆ'; + map!['ssangtikeutkorean'] = 'ㄸ'; + map!['ssuperior'] = ''; + map!['sterling'] = '£'; + map!['sterlingmonospace'] = '£'; + map!['strokelongoverlaycmb'] = '̶'; + map!['strokeshortoverlaycmb'] = '̵'; + map!['subset'] = '⊂'; + map!['subsetnotequal'] = '⊊'; + map!['subsetorequal'] = '⊆'; + map!['succeeds'] = '≻'; + map!['suchthat'] = '∋'; + map!['suhiragana'] = 'す'; + map!['sukatakana'] = 'ス'; + map!['sukatakanahalfwidth'] = 'ス'; + map!['sukunarabic'] = 'ْ'; + map!['summation'] = '∑'; + map!['sun'] = '☼'; + map!['superset'] = '⊃'; + map!['supersetnotequal'] = '⊋'; + map!['supersetorequal'] = '⊇'; + map!['svsquare'] = '㏜'; + map!['syouwaerasquare'] = '㍼'; + map!['t'] = 't'; + map!['tabengali'] = 'ত'; + map!['tackdown'] = '⊤'; + map!['tackleft'] = '⊣'; + map!['tadeva'] = 'त'; + map!['tagujarati'] = 'ત'; + map!['tagurmukhi'] = 'ਤ'; + map!['taharabic'] = 'ط'; + map!['tahfinalarabic'] = 'ﻂ'; + map!['tahinitialarabic'] = 'ﻃ'; + map!['tahiragana'] = 'た'; + map!['tahmedialarabic'] = 'ﻄ'; + map!['taisyouerasquare'] = '㍽'; + map!['takatakana'] = 'タ'; + map!['takatakanahalfwidth'] = 'タ'; + map!['tatweelarabic'] = 'ـ'; + map!['tau'] = 'τ'; + map!['tav'] = 'ת'; + map!['tavdages'] = 'תּ'; + map!['tavdagesh'] = 'תּ'; + map!['tavdageshhebrew'] = 'תּ'; + map!['tavhebrew'] = 'ת'; + map!['tbar'] = 'ŧ'; + map!['tbopomofo'] = 'ㄊ'; + map!['tcaron'] = 'ť'; + map!['tccurl'] = 'ʨ'; + map!['tcedilla'] = 'ţ'; + map!['tcheharabic'] = 'چ'; + map!['tchehfinalarabic'] = 'ﭻ'; + map!['tchehinitialarabic'] = 'ﭼ'; + map!['tchehmedialarabic'] = 'ﭽ'; + map!['tcircle'] = 'ⓣ'; + map!['tcircumflexbelow'] = 'ṱ'; + map!['tcommaaccent'] = 'ţ'; + map!['tdieresis'] = 'ẗ'; + map!['tdotaccent'] = 'ṫ'; + map!['tdotbelow'] = 'ṭ'; + map!['tecyrillic'] = 'т'; + map!['tedescendercyrillic'] = 'ҭ'; + map!['teharabic'] = 'ت'; + map!['tehfinalarabic'] = 'ﺖ'; + map!['tehhahinitialarabic'] = 'ﲢ'; + map!['tehhahisolatedarabic'] = 'ﰌ'; + map!['tehinitialarabic'] = 'ﺗ'; + map!['tehiragana'] = 'て'; + map!['tehjeeminitialarabic'] = 'ﲡ'; + map!['tehjeemisolatedarabic'] = 'ﰋ'; + map!['tehmarbutaarabic'] = 'ة'; + map!['tehmarbutafinalarabic'] = 'ﺔ'; + map!['tehmedialarabic'] = 'ﺘ'; + map!['tehmeeminitialarabic'] = 'ﲤ'; + map!['tehmeemisolatedarabic'] = 'ﰎ'; + map!['tehnoonfinalarabic'] = 'ﱳ'; + map!['tekatakana'] = 'テ'; + map!['tekatakanahalfwidth'] = 'テ'; + map!['telephone'] = '℡'; + map!['telephoneblack'] = '☎'; + map!['telishagedolahebrew'] = '֠'; + map!['telishaqetanahebrew'] = '֩'; + map!['tencircle'] = '⑩'; + map!['tenideographicparen'] = '㈩'; + map!['tenparen'] = '⑽'; + map!['tenperiod'] = '⒑'; + map!['tenroman'] = 'ⅹ'; + map!['tesh'] = 'ʧ'; + map!['tet'] = 'ט'; + map!['tetdagesh'] = 'טּ'; + map!['tetdageshhebrew'] = 'טּ'; + map!['tethebrew'] = 'ט'; + map!['tetsecyrillic'] = 'ҵ'; + map!['tevirhebrew'] = '֛'; + map!['tevirlefthebrew'] = '֛'; + map!['thabengali'] = 'থ'; + map!['thadeva'] = 'थ'; + map!['thagujarati'] = 'થ'; + map!['thagurmukhi'] = 'ਥ'; + map!['thalarabic'] = 'ذ'; + map!['thalfinalarabic'] = 'ﺬ'; + map!['thanthakhatlowleftthai'] = ''; + map!['thanthakhatlowrightthai'] = ''; + map!['thanthakhatthai'] = '์'; + map!['thanthakhatupperleftthai'] = ''; + map!['theharabic'] = 'ث'; + map!['thehfinalarabic'] = 'ﺚ'; + map!['thehinitialarabic'] = 'ﺛ'; + map!['thehmedialarabic'] = 'ﺜ'; + map!['thereexists'] = '∃'; + map!['therefore'] = '∴'; + map!['theta'] = 'θ'; + map!['theta1'] = 'ϑ'; + map!['thetasymbolgreek'] = 'ϑ'; + map!['thieuthacirclekorean'] = '㉹'; + map!['thieuthaparenkorean'] = '㈙'; + map!['thieuthcirclekorean'] = '㉫'; + map!['thieuthkorean'] = 'ㅌ'; + map!['thieuthparenkorean'] = '㈋'; + map!['thirteencircle'] = '⑬'; + map!['thirteenparen'] = '⒀'; + map!['thirteenperiod'] = '⒔'; + map!['thonangmonthothai'] = 'ฑ'; + map!['thook'] = 'ƭ'; + map!['thophuthaothai'] = 'ฒ'; + map!['thorn'] = 'þ'; + map!['thothahanthai'] = 'ท'; + map!['thothanthai'] = 'ฐ'; + map!['thothongthai'] = 'ธ'; + map!['thothungthai'] = 'ถ'; + map!['thousandcyrillic'] = '҂'; + map!['thousandsseparatorarabic'] = '٬'; + map!['thousandsseparatorpersian'] = '٬'; + map!['three'] = '3'; + map!['threearabic'] = '٣'; + map!['threebengali'] = '৩'; + map!['threecircle'] = '③'; + map!['threecircleinversesansserif'] = '➌'; + map!['threedeva'] = '३'; + map!['threeeighths'] = '⅜'; + map!['threegujarati'] = '૩'; + map!['threegurmukhi'] = '੩'; + map!['threehackarabic'] = '٣'; + map!['threehangzhou'] = '〣'; + map!['threeideographicparen'] = '㈢'; + map!['threeinferior'] = '₃'; + map!['threemonospace'] = '3'; + map!['threenumeratorbengali'] = '৶'; + map!['threeoldstyle'] = ''; + map!['threeparen'] = '⑶'; + map!['threeperiod'] = '⒊'; + map!['threepersian'] = '۳'; + map!['threequarters'] = '¾'; + map!['threequartersemdash'] = ''; + map!['threeroman'] = 'ⅲ'; + map!['threesuperior'] = '³'; + map!['threethai'] = '๓'; + map!['thzsquare'] = '㎔'; + map!['tihiragana'] = 'ち'; + map!['tikatakana'] = 'チ'; + map!['tikatakanahalfwidth'] = 'チ'; + map!['tikeutacirclekorean'] = '㉰'; + map!['tikeutaparenkorean'] = '㈐'; + map!['tikeutcirclekorean'] = '㉢'; + map!['tikeutkorean'] = 'ㄷ'; + map!['tikeutparenkorean'] = '㈂'; + map!['tilde'] = '˜'; + map!['tildebelowcmb'] = '̰'; + map!['tildecmb'] = '̃'; + map!['tildecomb'] = '̃'; + map!['tildedoublecmb'] = '͠'; + map!['tildeoperator'] = '∼'; + map!['tildeoverlaycmb'] = '̴'; + map!['tildeverticalcmb'] = '̾'; + map!['timescircle'] = '⊗'; + map!['tipehahebrew'] = '֖'; + map!['tipehalefthebrew'] = '֖'; + map!['tippigurmukhi'] = 'ੰ'; + map!['titlocyrilliccmb'] = '҃'; + map!['tiwnarmenian'] = 'տ'; + map!['tlinebelow'] = 'ṯ'; + map!['tmonospace'] = 't'; + map!['toarmenian'] = 'թ'; + map!['tohiragana'] = 'と'; + map!['tokatakana'] = 'ト'; + map!['tokatakanahalfwidth'] = 'ト'; + map!['tonebarextrahighmod'] = '˥'; + map!['tonebarextralowmod'] = '˩'; + map!['tonebarhighmod'] = '˦'; + map!['tonebarlowmod'] = '˨'; + map!['tonebarmidmod'] = '˧'; + map!['tonefive'] = 'ƽ'; + map!['tonesix'] = 'ƅ'; + map!['tonetwo'] = 'ƨ'; + map!['tonos'] = '΄'; + map!['tonsquare'] = '㌧'; + map!['topatakthai'] = 'ฏ'; + map!['tortoiseshellbracketleft'] = '〔'; + map!['tortoiseshellbracketleftsmall'] = '﹝'; + map!['tortoiseshellbracketleftvertical'] = '︹'; + map!['tortoiseshellbracketright'] = '〕'; + map!['tortoiseshellbracketrightsmall'] = '﹞'; + map!['tortoiseshellbracketrightvertical'] = '︺'; + map!['totaothai'] = 'ต'; + map!['tpalatalhook'] = 'ƫ'; + map!['tparen'] = '⒯'; + map!['trademark'] = '™'; + map!['trademarksans'] = ''; + map!['trademarkserif'] = ''; + map!['tretroflexhook'] = 'ʈ'; + map!['triagdn'] = '▼'; + map!['triaglf'] = '◄'; + map!['triagrt'] = '►'; + map!['triagup'] = '▲'; + map!['ts'] = 'ʦ'; + map!['tsadi'] = 'צ'; + map!['tsadidagesh'] = 'צּ'; + map!['tsadidageshhebrew'] = 'צּ'; + map!['tsadihebrew'] = 'צ'; + map!['tsecyrillic'] = 'ц'; + map!['tsere'] = 'ֵ'; + map!['tsere12'] = 'ֵ'; + map!['tsere1e'] = 'ֵ'; + map!['tsere2b'] = 'ֵ'; + map!['tserehebrew'] = 'ֵ'; + map!['tserenarrowhebrew'] = 'ֵ'; + map!['tserequarterhebrew'] = 'ֵ'; + map!['tserewidehebrew'] = 'ֵ'; + map!['tshecyrillic'] = 'ћ'; + map!['tsuperior'] = ''; + map!['ttabengali'] = 'ট'; + map!['ttadeva'] = 'ट'; + map!['ttagujarati'] = 'ટ'; + map!['ttagurmukhi'] = 'ਟ'; + map!['tteharabic'] = 'ٹ'; + map!['ttehfinalarabic'] = 'ﭧ'; + map!['ttehinitialarabic'] = 'ﭨ'; + map!['ttehmedialarabic'] = 'ﭩ'; + map!['tthabengali'] = 'ঠ'; + map!['tthadeva'] = 'ठ'; + map!['tthagujarati'] = 'ઠ'; + map!['tthagurmukhi'] = 'ਠ'; + map!['tturned'] = 'ʇ'; + map!['tuhiragana'] = 'つ'; + map!['tukatakana'] = 'ツ'; + map!['tukatakanahalfwidth'] = 'ツ'; + map!['tusmallhiragana'] = 'っ'; + map!['tusmallkatakana'] = 'ッ'; + map!['tusmallkatakanahalfwidth'] = 'ッ'; + map!['twelvecircle'] = '⑫'; + map!['twelveparen'] = '⑿'; + map!['twelveperiod'] = '⒓'; + map!['twelveroman'] = 'ⅻ'; + map!['twentycircle'] = '⑳'; + map!['twentyhangzhou'] = '卄'; + map!['twentyparen'] = '⒇'; + map!['twentyperiod'] = '⒛'; + map!['two'] = '2'; + map!['twoarabic'] = '٢'; + map!['twobengali'] = '২'; + map!['twocircle'] = '②'; + map!['twocircleinversesansserif'] = '➋'; + map!['twodeva'] = '२'; + map!['twodotenleader'] = '‥'; + map!['twodotleader'] = '‥'; + map!['twodotleadervertical'] = '︰'; + map!['twogujarati'] = '૨'; + map!['twogurmukhi'] = '੨'; + map!['twohackarabic'] = '٢'; + map!['twohangzhou'] = '〢'; + map!['twoideographicparen'] = '㈡'; + map!['twoinferior'] = '₂'; + map!['twomonospace'] = '2'; + map!['twonumeratorbengali'] = '৵'; + map!['twooldstyle'] = ''; + map!['twoparen'] = '⑵'; + map!['twoperiod'] = '⒉'; + map!['twopersian'] = '۲'; + map!['tworoman'] = 'ⅱ'; + map!['twostroke'] = 'ƻ'; + map!['twosuperior'] = '²'; + map!['twothai'] = '๒'; + map!['twothirds'] = '⅔'; + map!['u'] = 'u'; + map!['uacute'] = 'ú'; + map!['ubar'] = 'ʉ'; + map!['ubengali'] = 'উ'; + map!['ubopomofo'] = 'ㄨ'; + map!['ubreve'] = 'ŭ'; + map!['ucaron'] = 'ǔ'; + map!['ucircle'] = 'ⓤ'; + map!['ucircumflex'] = 'û'; + map!['ucircumflexbelow'] = 'ṷ'; + map!['ucyrillic'] = 'у'; + map!['udattadeva'] = '॑'; + map!['udblacute'] = 'ű'; + map!['udblgrave'] = 'ȕ'; + map!['udeva'] = 'उ'; + map!['udieresis'] = 'ü'; + map!['udieresisacute'] = 'ǘ'; + map!['udieresisbelow'] = 'ṳ'; + map!['udieresiscaron'] = 'ǚ'; + map!['udieresiscyrillic'] = 'ӱ'; + map!['udieresisgrave'] = 'ǜ'; + map!['udieresismacron'] = 'ǖ'; + map!['udotbelow'] = 'ụ'; + map!['ugrave'] = 'ù'; + map!['ugujarati'] = 'ઉ'; + map!['ugurmukhi'] = 'ਉ'; + map!['uhiragana'] = 'う'; + map!['uhookabove'] = 'ủ'; + map!['uhorn'] = 'ư'; + map!['uhornacute'] = 'ứ'; + map!['uhorndotbelow'] = 'ự'; + map!['uhorngrave'] = 'ừ'; + map!['uhornhookabove'] = 'ử'; + map!['uhorntilde'] = 'ữ'; + map!['uhungarumlaut'] = 'ű'; + map!['uhungarumlautcyrillic'] = 'ӳ'; + map!['uinvertedbreve'] = 'ȗ'; + map!['ukatakana'] = 'ウ'; + map!['ukatakanahalfwidth'] = 'ウ'; + map!['ukcyrillic'] = 'ѹ'; + map!['ukorean'] = 'ㅜ'; + map!['umacron'] = 'ū'; + map!['umacroncyrillic'] = 'ӯ'; + map!['umacrondieresis'] = 'ṻ'; + map!['umatragurmukhi'] = 'ੁ'; + map!['umonospace'] = 'u'; + map!['underscore'] = '_'; + map!['underscoredbl'] = '‗'; + map!['underscoremonospace'] = '_'; + map!['underscorevertical'] = '︳'; + map!['underscorewavy'] = '﹏'; + map!['union'] = '∪'; + map!['universal'] = '∀'; + map!['uogonek'] = 'ų'; + map!['uparen'] = '⒰'; + map!['upblock'] = '▀'; + map!['upperdothebrew'] = 'ׄ'; + map!['upsilon'] = 'υ'; + map!['upsilondieresis'] = 'ϋ'; + map!['upsilondieresistonos'] = 'ΰ'; + map!['upsilonlatin'] = 'ʊ'; + map!['upsilontonos'] = 'ύ'; + map!['uptackbelowcmb'] = '̝'; + map!['uptackmod'] = '˔'; + map!['uragurmukhi'] = 'ੳ'; + map!['uring'] = 'ů'; + map!['ushortcyrillic'] = 'ў'; + map!['usmallhiragana'] = 'ぅ'; + map!['usmallkatakana'] = 'ゥ'; + map!['usmallkatakanahalfwidth'] = 'ゥ'; + map!['ustraightcyrillic'] = 'ү'; + map!['ustraightstrokecyrillic'] = 'ұ'; + map!['utilde'] = 'ũ'; + map!['utildeacute'] = 'ṹ'; + map!['utildebelow'] = 'ṵ'; + map!['uubengali'] = 'ঊ'; + map!['uudeva'] = 'ऊ'; + map!['uugujarati'] = 'ઊ'; + map!['uugurmukhi'] = 'ਊ'; + map!['uumatragurmukhi'] = 'ੂ'; + map!['uuvowelsignbengali'] = 'ূ'; + map!['uuvowelsigndeva'] = 'ू'; + map!['uuvowelsigngujarati'] = 'ૂ'; + map!['uvowelsignbengali'] = 'ু'; + map!['uvowelsigndeva'] = 'ु'; + map!['uvowelsigngujarati'] = 'ુ'; + map!['v'] = 'v'; + map!['vadeva'] = 'व'; + map!['vagujarati'] = 'વ'; + map!['vagurmukhi'] = 'ਵ'; + map!['vakatakana'] = 'ヷ'; + map!['vav'] = 'ו'; + map!['vavdagesh'] = 'וּ'; + map!['vavdagesh65'] = 'וּ'; + map!['vavdageshhebrew'] = 'וּ'; + map!['vavhebrew'] = 'ו'; + map!['vavholam'] = 'וֹ'; + map!['vavholamhebrew'] = 'וֹ'; + map!['vavvavhebrew'] = 'װ'; + map!['vavyodhebrew'] = 'ױ'; + map!['vcircle'] = 'ⓥ'; + map!['vdotbelow'] = 'ṿ'; + map!['vecyrillic'] = 'в'; + map!['veharabic'] = 'ڤ'; + map!['vehfinalarabic'] = 'ﭫ'; + map!['vehinitialarabic'] = 'ﭬ'; + map!['vehmedialarabic'] = 'ﭭ'; + map!['vekatakana'] = 'ヹ'; + map!['venus'] = '♀'; + map!['verticalbar'] = '|'; + map!['verticallineabovecmb'] = '̍'; + map!['verticallinebelowcmb'] = '̩'; + map!['verticallinelowmod'] = 'ˌ'; + map!['verticallinemod'] = 'ˈ'; + map!['vewarmenian'] = 'վ'; + map!['vhook'] = 'ʋ'; + map!['vikatakana'] = 'ヸ'; + map!['viramabengali'] = '্'; + map!['viramadeva'] = '्'; + map!['viramagujarati'] = '્'; + map!['visargabengali'] = 'ঃ'; + map!['visargadeva'] = 'ः'; + map!['visargagujarati'] = 'ઃ'; + map!['vmonospace'] = 'v'; + map!['voarmenian'] = 'ո'; + map!['voicediterationhiragana'] = 'ゞ'; + map!['voicediterationkatakana'] = 'ヾ'; + map!['voicedmarkkana'] = '゛'; + map!['voicedmarkkanahalfwidth'] = '゙'; + map!['vokatakana'] = 'ヺ'; + map!['vparen'] = '⒱'; + map!['vtilde'] = 'ṽ'; + map!['vturned'] = 'ʌ'; + map!['vuhiragana'] = 'ゔ'; + map!['vukatakana'] = 'ヴ'; + map!['w'] = 'w'; + map!['wacute'] = 'ẃ'; + map!['waekorean'] = 'ㅙ'; + map!['wahiragana'] = 'わ'; + map!['wakatakana'] = 'ワ'; + map!['wakatakanahalfwidth'] = 'ワ'; + map!['wakorean'] = 'ㅘ'; + map!['wasmallhiragana'] = 'ゎ'; + map!['wasmallkatakana'] = 'ヮ'; + map!['wattosquare'] = '㍗'; + map!['wavedash'] = '〜'; + map!['wavyunderscorevertical'] = '︴'; + map!['wawarabic'] = 'و'; + map!['wawfinalarabic'] = 'ﻮ'; + map!['wawhamzaabovearabic'] = 'ؤ'; + map!['wawhamzaabovefinalarabic'] = 'ﺆ'; + map!['wbsquare'] = '㏝'; + map!['wcircle'] = 'ⓦ'; + map!['wcircumflex'] = 'ŵ'; + map!['wdieresis'] = 'ẅ'; + map!['wdotaccent'] = 'ẇ'; + map!['wdotbelow'] = 'ẉ'; + map!['wehiragana'] = 'ゑ'; + map!['weierstrass'] = '℘'; + map!['wekatakana'] = 'ヱ'; + map!['wekorean'] = 'ㅞ'; + map!['weokorean'] = 'ㅝ'; + map!['wgrave'] = 'ẁ'; + map!['whitebullet'] = '◦'; + map!['whitecircle'] = '○'; + map!['whitecircleinverse'] = '◙'; + map!['whitecornerbracketleft'] = '『'; + map!['whitecornerbracketleftvertical'] = '﹃'; + map!['whitecornerbracketright'] = '』'; + map!['whitecornerbracketrightvertical'] = '﹄'; + map!['whitediamond'] = '◇'; + map!['whitediamondcontainingblacksmalldiamond'] = '◈'; + map!['whitedownpointingsmalltriangle'] = '▿'; + map!['whitedownpointingtriangle'] = '▽'; + map!['whiteleftpointingsmalltriangle'] = '◃'; + map!['whiteleftpointingtriangle'] = '◁'; + map!['whitelenticularbracketleft'] = '〖'; + map!['whitelenticularbracketright'] = '〗'; + map!['whiterightpointingsmalltriangle'] = '▹'; + map!['whiterightpointingtriangle'] = '▷'; + map!['whitesmallsquare'] = '▫'; + map!['whitesmilingface'] = '☺'; + map!['whitesquare'] = '□'; + map!['whitestar'] = '☆'; + map!['whitetelephone'] = '☏'; + map!['whitetortoiseshellbracketleft'] = '〘'; + map!['whitetortoiseshellbracketright'] = '〙'; + map!['whiteuppointingsmalltriangle'] = '▵'; + map!['whiteuppointingtriangle'] = '△'; + map!['wihiragana'] = 'ゐ'; + map!['wikatakana'] = 'ヰ'; + map!['wikorean'] = 'ㅟ'; + map!['wmonospace'] = 'w'; + map!['wohiragana'] = 'を'; + map!['wokatakana'] = 'ヲ'; + map!['wokatakanahalfwidth'] = 'ヲ'; + map!['won'] = '₩'; + map!['wonmonospace'] = '₩'; + map!['wowaenthai'] = 'ว'; + map!['wparen'] = '⒲'; + map!['wring'] = 'ẘ'; + map!['wsuperior'] = 'ʷ'; + map!['wturned'] = 'ʍ'; + map!['wynn'] = 'ƿ'; + map!['x'] = 'x'; + map!['xabovecmb'] = '̽'; + map!['xbopomofo'] = 'ㄒ'; + map!['xcircle'] = 'ⓧ'; + map!['xdieresis'] = 'ẍ'; + map!['xdotaccent'] = 'ẋ'; + map!['xeharmenian'] = 'խ'; + map!['xi'] = 'ξ'; + map!['xmonospace'] = 'x'; + map!['xparen'] = '⒳'; + map!['xsuperior'] = 'ˣ'; + map!['y'] = 'y'; + map!['yaadosquare'] = '㍎'; + map!['yabengali'] = 'য'; + map!['yacute'] = 'ý'; + map!['yadeva'] = 'य'; + map!['yaekorean'] = 'ㅒ'; + map!['yagujarati'] = 'ય'; + map!['yagurmukhi'] = 'ਯ'; + map!['yahiragana'] = 'や'; + map!['yakatakana'] = 'ヤ'; + map!['yakatakanahalfwidth'] = 'ヤ'; + map!['yakorean'] = 'ㅑ'; + map!['yamakkanthai'] = '๎'; + map!['yasmallhiragana'] = 'ゃ'; + map!['yasmallkatakana'] = 'ャ'; + map!['yasmallkatakanahalfwidth'] = 'ャ'; + map!['yatcyrillic'] = 'ѣ'; + map!['ycircle'] = 'ⓨ'; + map!['ycircumflex'] = 'ŷ'; + map!['ydieresis'] = 'ÿ'; + map!['ydotaccent'] = 'ẏ'; + map!['ydotbelow'] = 'ỵ'; + map!['yeharabic'] = 'ي'; + map!['yehbarreearabic'] = 'ے'; + map!['yehbarreefinalarabic'] = 'ﮯ'; + map!['yehfinalarabic'] = 'ﻲ'; + map!['yehhamzaabovearabic'] = 'ئ'; + map!['yehhamzaabovefinalarabic'] = 'ﺊ'; + map!['yehhamzaaboveinitialarabic'] = 'ﺋ'; + map!['yehhamzaabovemedialarabic'] = 'ﺌ'; + map!['yehinitialarabic'] = 'ﻳ'; + map!['yehmedialarabic'] = 'ﻴ'; + map!['yehmeeminitialarabic'] = 'ﳝ'; + map!['yehmeemisolatedarabic'] = 'ﱘ'; + map!['yehnoonfinalarabic'] = 'ﲔ'; + map!['yehthreedotsbelowarabic'] = 'ۑ'; + map!['yekorean'] = 'ㅖ'; + map!['yen'] = '¥'; + map!['yenmonospace'] = '¥'; + map!['yeokorean'] = 'ㅕ'; + map!['yeorinhieuhkorean'] = 'ㆆ'; + map!['yerahbenyomohebrew'] = '֪'; + map!['yerahbenyomolefthebrew'] = '֪'; + map!['yericyrillic'] = 'ы'; + map!['yerudieresiscyrillic'] = 'ӹ'; + map!['yesieungkorean'] = 'ㆁ'; + map!['yesieungpansioskorean'] = 'ㆃ'; + map!['yesieungsioskorean'] = 'ㆂ'; + map!['yetivhebrew'] = '֚'; + map!['ygrave'] = 'ỳ'; + map!['yhook'] = 'ƴ'; + map!['yhookabove'] = 'ỷ'; + map!['yiarmenian'] = 'յ'; + map!['yicyrillic'] = 'ї'; + map!['yikorean'] = 'ㅢ'; + map!['yinyang'] = '☯'; + map!['yiwnarmenian'] = 'ւ'; + map!['ymonospace'] = 'y'; + map!['yod'] = 'י'; + map!['yoddagesh'] = 'יּ'; + map!['yoddageshhebrew'] = 'יּ'; + map!['yodhebrew'] = 'י'; + map!['yodyodhebrew'] = 'ײ'; + map!['yodyodpatahhebrew'] = 'ײַ'; + map!['yohiragana'] = 'よ'; + map!['yoikorean'] = 'ㆉ'; + map!['yokatakana'] = 'ヨ'; + map!['yokatakanahalfwidth'] = 'ヨ'; + map!['yokorean'] = 'ㅛ'; + map!['yosmallhiragana'] = 'ょ'; + map!['yosmallkatakana'] = 'ョ'; + map!['yosmallkatakanahalfwidth'] = 'ョ'; + map!['yotgreek'] = 'ϳ'; + map!['yoyaekorean'] = 'ㆈ'; + map!['yoyakorean'] = 'ㆇ'; + map!['yoyakthai'] = 'ย'; + map!['yoyingthai'] = 'ญ'; + map!['yparen'] = '⒴'; + map!['ypogegrammeni'] = 'ͺ'; + map!['ypogegrammenigreekcmb'] = 'ͅ'; + map!['yr'] = 'Ʀ'; + map!['yring'] = 'ẙ'; + map!['ysuperior'] = 'ʸ'; + map!['ytilde'] = 'ỹ'; + map!['yturned'] = 'ʎ'; + map!['yuhiragana'] = 'ゆ'; + map!['yuikorean'] = 'ㆌ'; + map!['yukatakana'] = 'ユ'; + map!['yukatakanahalfwidth'] = 'ユ'; + map!['yukorean'] = 'ㅠ'; + map!['yusbigcyrillic'] = 'ѫ'; + map!['yusbigiotifiedcyrillic'] = 'ѭ'; + map!['yuslittlecyrillic'] = 'ѧ'; + map!['yuslittleiotifiedcyrillic'] = 'ѩ'; + map!['yusmallhiragana'] = 'ゅ'; + map!['yusmallkatakana'] = 'ュ'; + map!['yusmallkatakanahalfwidth'] = 'ュ'; + map!['yuyekorean'] = 'ㆋ'; + map!['yuyeokorean'] = 'ㆊ'; + map!['yyabengali'] = 'য়'; + map!['yyadeva'] = 'य़'; + map!['z'] = 'z'; + map!['zaarmenian'] = 'զ'; + map!['zacute'] = 'ź'; + map!['zadeva'] = 'ज़'; + map!['zagurmukhi'] = 'ਜ਼'; + map!['zaharabic'] = 'ظ'; + map!['zahfinalarabic'] = 'ﻆ'; + map!['zahinitialarabic'] = 'ﻇ'; + map!['zahiragana'] = 'ざ'; + map!['zahmedialarabic'] = 'ﻈ'; + map!['zainarabic'] = 'ز'; + map!['zainfinalarabic'] = 'ﺰ'; + map!['zakatakana'] = 'ザ'; + map!['zaqefgadolhebrew'] = '֕'; + map!['zaqefqatanhebrew'] = '֔'; + map!['zarqahebrew'] = '֘'; + map!['zayin'] = 'ז'; + map!['zayindagesh'] = 'זּ'; + map!['zayindageshhebrew'] = 'זּ'; + map!['zayinhebrew'] = 'ז'; + map!['zbopomofo'] = 'ㄗ'; + map!['zcaron'] = 'ž'; + map!['zcircle'] = 'ⓩ'; + map!['zcircumflex'] = 'ẑ'; + map!['zcurl'] = 'ʑ'; + map!['zdot'] = 'ż'; + map!['zdotaccent'] = 'ż'; + map!['zdotbelow'] = 'ẓ'; + map!['zecyrillic'] = 'з'; + map!['zedescendercyrillic'] = 'ҙ'; + map!['zedieresiscyrillic'] = 'ӟ'; + map!['zehiragana'] = 'ぜ'; + map!['zekatakana'] = 'ゼ'; + map!['zero'] = '0'; + map!['zeroarabic'] = '٠'; + map!['zerobengali'] = '০'; + map!['zerodeva'] = '०'; + map!['zerogujarati'] = '૦'; + map!['zerogurmukhi'] = '੦'; + map!['zerohackarabic'] = '٠'; + map!['zeroinferior'] = '₀'; + map!['zeromonospace'] = '0'; + map!['zerooldstyle'] = ''; + map!['zeropersian'] = '۰'; + map!['zerosuperior'] = '⁰'; + map!['zerothai'] = '๐'; + map!['zerowidthjoiner'] = ''; + map!['zerowidthnonjoiner'] = '‌'; + map!['zerowidthspace'] = '​'; + map!['zeta'] = 'ζ'; + map!['zhbopomofo'] = 'ㄓ'; + map!['zhearmenian'] = 'ժ'; + map!['zhebrevecyrillic'] = 'ӂ'; + map!['zhecyrillic'] = 'ж'; + map!['zhedescendercyrillic'] = 'җ'; + map!['zhedieresiscyrillic'] = 'ӝ'; + map!['zihiragana'] = 'じ'; + map!['zikatakana'] = 'ジ'; + map!['zinorhebrew'] = '֮'; + map!['zlinebelow'] = 'ẕ'; + map!['zmonospace'] = 'z'; + map!['zohiragana'] = 'ぞ'; + map!['zokatakana'] = 'ゾ'; + map!['zparen'] = '⒵'; + map!['zretroflexhook'] = 'ʐ'; + map!['zstroke'] = 'ƶ'; + map!['zuhiragana'] = 'ず'; + map!['zukatakana'] = 'ズ'; } - String getUnicode(String names) { + String? getUnicode(String? names) { if (names == null) { return '\0'; } @@ -4241,9 +4241,9 @@ class _AdobeGlyphList { return result; } - String getUnicodeForName(String name) { - if (map.containsKey(name)) { - return map[name]; + String? getUnicodeForName(String name) { + if (map!.containsKey(name)) { + return map![name]; } if (name.startsWith('uni')) { name = name.substring(3); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_structure.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_structure.dart index 6167a1a57..a0ffdf857 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_structure.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_structure.dart @@ -2,37 +2,40 @@ part of pdf; class _FontStructure { //constructor - _FontStructure([_IPdfPrimitive fontDict, String fontRefNum]) { + _FontStructure([_IPdfPrimitive? fontDict, String? fontRefNum]) { if (fontDict != null) { fontDictionary = fontDict as _PdfDictionary; if (fontDictionary.containsKey(_PdfName('Subtype'))) { - fontType = fontDictionary._items[_PdfName('Subtype')] as _PdfName; + fontType = fontDictionary._items![_PdfName('Subtype')] as _PdfName?; } } _initialize(); if (fontRefNum != null) { fontRefNumber = fontRefNum; if (fontType != null) { - if (fontType._name == 'Type3') { - if (fontDictionary._items.containsKey(_PdfName('CharProcs'))) { - _PdfDictionary charProcs; + if (fontType!._name == 'Type3') { + if (fontDictionary._items!.containsKey(_PdfName('CharProcs'))) { + _PdfDictionary? charProcs; if (fontDictionary['CharProcs'] is _PdfDictionary) { - charProcs = fontDictionary['CharProcs'] as _PdfDictionary; + charProcs = fontDictionary['CharProcs'] as _PdfDictionary?; } else { charProcs = (fontDictionary['CharProcs'] as _PdfReferenceHolder) - .object as _PdfDictionary; + .object as _PdfDictionary?; } - final List<_PdfName> names = charProcs._items.keys.toList(); + final List<_PdfName?> names = charProcs!._items!.keys.toList(); int i = 0; - for (final _PdfReferenceHolder value in charProcs._items.values) { - type3FontCharProcsDict[names[i]._name] = - value.object as _PdfStream; - i++; - } + charProcs._items!.values.forEach((_IPdfPrimitive? value) { + if (value != null && value is _PdfReferenceHolder) { + final _PdfReferenceHolder holder = value; + type3FontCharProcsDict[names[i]!._name] = + holder.object as _PdfStream?; + i++; + } + }); } - } else if (fontType._name == 'Type1') { + } else if (fontType!._name == 'Type1') { _isStandardFont = _checkStandardFont(); - } else if (fontType._name == 'Type0') { + } else if (fontType!._name == 'Type0') { _isStandardCJKFont = _checkStandardCJKFont(); } } @@ -46,55 +49,55 @@ class _FontStructure { //Fields bool isWhiteSpace = false; bool isSameFont = false; - String _fontEncoding; - _PdfDictionary fontDictionary; - String _fontName; - double fontSize; - Map _characterMapTable; - Map _reverseMapTable; + String? _fontEncoding; + late _PdfDictionary fontDictionary; + String? _fontName; + double? fontSize; + Map? _characterMapTable; + Map? _reverseMapTable; Map tempMapTable = {}; - Map differenceEncoding; + Map? differenceEncoding; List tempStringList = []; - Map _differencesDictionary; - Map differenceTable = {}; - Map _octDecMapTable; - Map _macEncodeTable; + Map? _differencesDictionary; + late Map differenceTable; + Map? _octDecMapTable; + Map? _macEncodeTable; final Map _macRomanMapTable = {}; final Map _winansiMapTable = {}; - Map reverseDictMapping = {}; - _PdfDictionary cidSystemInfoDictionary; - Map cidToGidTable; - Map _cidToGidReverseMapTable; - Map type3FontCharProcsDict = {}; + Map reverseDictMapping = {}; + _PdfDictionary? cidSystemInfoDictionary; + Map? cidToGidTable; + Map? _cidToGidReverseMapTable; + Map type3FontCharProcsDict = {}; bool isContainFontfile2 = false; bool isMappingDone = false; bool isSystemFontExist = false; bool isTextExtraction = false; bool isEmbedded = false; bool containsCmap = true; - String zapfPostScript = ''; - String fontRefNumber = ''; - _PdfName fontType; + late String zapfPostScript; + late String fontRefNumber; + _PdfName? fontType; bool isAdobeIdentity = false; bool _isCidFontType = false; - List _fontStyle; - Map _fontGlyphWidth; - double defaultGlyphWidth; - bool _containsCmap; - Map _unicodeCharMapTable; - double _type1GlyphHeight; - bool _isStandardFont; - bool _isStandardCJKFont; - PdfFont font; - String _standardFontName = ''; - String _standardCJKFontName = ''; - List standardFontNames; - List standardCJKFontNames; - List cjkEncoding; - List _windows1252MapTable; + List? _fontStyle; + Map? _fontGlyphWidth; + double? defaultGlyphWidth; + late bool _containsCmap; + Map? _unicodeCharMapTable; + late double _type1GlyphHeight; + late bool _isStandardFont; + late bool _isStandardCJKFont; + PdfFont? font; + String? _standardFontName = ''; + String? _standardCJKFontName = ''; + late List standardFontNames; + late List standardCJKFontNames; + late List cjkEncoding; + late List _windows1252MapTable; //Properties - String get fontEncoding => _fontEncoding ??= getFontEncoding(); + String? get fontEncoding => _fontEncoding ??= getFontEncoding(); Map get characterMapTable => _characterMapTable ??= getCharacterMapTable(); @@ -103,10 +106,10 @@ class _FontStructure { _characterMapTable = value; } - Map get differencesDictionary => + Map get differencesDictionary => _differencesDictionary ??= getDifferencesDictionary(); - set differencesDictionary(Map value) { + set differencesDictionary(Map value) { _differencesDictionary = value; } @@ -116,10 +119,10 @@ class _FontStructure { _octDecMapTable = value; } - Map get reverseMapTable => + Map? get reverseMapTable => _reverseMapTable ??= getReverseMapTable(); - set reverseMapTable(Map value) { + set reverseMapTable(Map? value) { _reverseMapTable = value; } @@ -130,18 +133,18 @@ class _FontStructure { _cidToGidReverseMapTable = value; } - Map get macEncodeTable { + Map? get macEncodeTable { if (_macEncodeTable == null) { getMacEncodeTable(); } return _macEncodeTable; } - set macEncodeTable(Map value) { + set macEncodeTable(Map? value) { _macEncodeTable = value; } - String get fontName => _fontName ??= getFontName(); + String? get fontName => _fontName ??= getFontName(); bool get isCid { _isCidFontType = isCIDFontType(); @@ -152,14 +155,14 @@ class _FontStructure { _isCidFontType = value; } - List get fontStyle { - if (_fontStyle.length == 1 && _fontStyle[0] == PdfFontStyle.regular) { + List? get fontStyle { + if (_fontStyle!.length == 1 && _fontStyle![0] == PdfFontStyle.regular) { _fontStyle = getFontStyle(); } return _fontStyle; } - Map get fontGlyphWidths { + Map? get fontGlyphWidths { if (fontEncoding == 'Identity-H' || fontEncoding == 'Identity#2DH') { _getGlyphWidths(); } else { @@ -168,19 +171,22 @@ class _FontStructure { return _fontGlyphWidth; } - Map get unicodeCharMapTable { + Map? get unicodeCharMapTable { _unicodeCharMapTable ??= {}; return _unicodeCharMapTable; } - set unicodeCharMapTable(Map value) { + set unicodeCharMapTable(Map? value) { _unicodeCharMapTable = value; } - _PdfNumber get flags => _getFlagValue(); + _PdfNumber? get flags => _getFlagValue(); //Implementation void _initialize() { + differenceTable = {}; + zapfPostScript = ''; + fontRefNumber = ''; _type1GlyphHeight = 0; _isStandardFont = false; _isStandardCJKFont = false; @@ -506,14 +512,14 @@ class _FontStructure { ]; } - _PdfNumber _getFlagValue() { - _PdfNumber flagvalue; + _PdfNumber? _getFlagValue() { + _PdfNumber? flagvalue; if (fontEncoding != 'Identity-H') { if (fontDictionary.containsKey(_DictionaryProperties.fontDescriptor)) { - _IPdfPrimitive primitive = + _IPdfPrimitive? primitive = fontDictionary[_DictionaryProperties.fontDescriptor]; if (primitive != null && primitive is _PdfReferenceHolder) { - primitive = (primitive as _PdfReferenceHolder).object; + primitive = primitive.object; if (primitive != null && primitive is _PdfDictionary) { final _PdfDictionary dic = primitive; if (dic.containsKey(_DictionaryProperties.flags)) { @@ -528,27 +534,26 @@ class _FontStructure { } } else { if (fontDictionary.containsKey(_DictionaryProperties.descendantFonts)) { - _IPdfPrimitive primitive = + _IPdfPrimitive? primitive = fontDictionary[_DictionaryProperties.descendantFonts]; if (primitive is _PdfReferenceHolder) { - primitive = (primitive as _PdfReferenceHolder).object; + primitive = primitive.object; } if (primitive != null && primitive is _PdfArray) { final _PdfArray descenarray = primitive; if (descenarray.count > 0 && descenarray[0] is _PdfReferenceHolder) { final _PdfReferenceHolder referenceholder = descenarray[0] as _PdfReferenceHolder; - final _IPdfPrimitive primitiveObject = referenceholder.object; + final _IPdfPrimitive? primitiveObject = referenceholder.object; if (primitiveObject != null && primitiveObject is _PdfDictionary) { final _PdfDictionary dictionary = primitiveObject; if (dictionary .containsKey(_DictionaryProperties.fontDescriptor)) { - _IPdfPrimitive fontDescriptor = + _IPdfPrimitive? fontDescriptor = dictionary[_DictionaryProperties.fontDescriptor]; - _PdfDictionary descriptorDictionary; + _PdfDictionary? descriptorDictionary; if (fontDescriptor is _PdfReferenceHolder) { - fontDescriptor = - (fontDescriptor as _PdfReferenceHolder).object; + fontDescriptor = fontDescriptor.object; if (fontDescriptor != null && fontDescriptor is _PdfDictionary) { descriptorDictionary = fontDescriptor; @@ -578,11 +583,11 @@ class _FontStructure { if (fontEncoding == 'Identity-H' || fontEncoding == 'Identity#2DH') { _PdfDictionary dictionary = fontDictionary; if (dictionary.containsKey(_DictionaryProperties.descendantFonts)) { - _IPdfPrimitive primitive = + _IPdfPrimitive? primitive = dictionary[_DictionaryProperties.descendantFonts]; - _PdfArray arr; + _PdfArray? arr; if (primitive is _PdfReferenceHolder) { - primitive = (primitive as _PdfReferenceHolder).object; + primitive = primitive.object; if (primitive != null && primitive is _PdfArray) { arr = primitive; } @@ -591,9 +596,9 @@ class _FontStructure { } if (arr != null && arr.count > 0) { if (arr[0] is _PdfDictionary) { - dictionary = arr[0]; + dictionary = arr[0] as _PdfDictionary; } else if (arr[0] is _PdfReferenceHolder) { - final _IPdfPrimitive holder = + final _IPdfPrimitive? holder = (arr[0] as _PdfReferenceHolder).object; if (holder != null && holder is _PdfDictionary) { dictionary = holder; @@ -602,25 +607,25 @@ class _FontStructure { } } _fontGlyphWidth = {}; - _PdfArray w; + _PdfArray? w; int index = 0; int endIndex = 0; - _PdfArray widthArray; + _PdfArray? widthArray; if (dictionary.containsKey(_DictionaryProperties.w)) { - _IPdfPrimitive holder = dictionary[_DictionaryProperties.w]; + _IPdfPrimitive? holder = dictionary[_DictionaryProperties.w]; if (holder is _PdfArray) { w = holder; } else if (holder is _PdfReferenceHolder) { - holder = (holder as _PdfReferenceHolder).object; + holder = holder.object; if (holder != null && holder is _PdfArray) { w = holder; } } } if (dictionary.containsKey(_DictionaryProperties.dw)) { - final _IPdfPrimitive holder = dictionary[_DictionaryProperties.dw]; + final _IPdfPrimitive? holder = dictionary[_DictionaryProperties.dw]; if (holder is _PdfNumber) { - defaultGlyphWidth = holder.value.toDouble(); + defaultGlyphWidth = holder.value!.toDouble(); } } try { @@ -629,39 +634,39 @@ class _FontStructure { } for (int i = 0; i < w.count;) { if (w[i] is _PdfNumber) { - index = (w[i] as _PdfNumber).value.toInt(); + index = (w[i] as _PdfNumber).value!.toInt(); } i++; if (w[i] is _PdfArray) { widthArray = (w[i] as _PdfArray); for (int j = 0; j < widthArray.count; j++) { if (!_containsCmap) { - _fontGlyphWidth[index] = - (widthArray[j] as _PdfNumber).value.toInt(); - } else if (!_fontGlyphWidth.containsKey(index)) { - _fontGlyphWidth[index] = - (widthArray[j] as _PdfNumber).value.toInt(); + _fontGlyphWidth![index] = + (widthArray[j] as _PdfNumber).value!.toInt(); + } else if (!_fontGlyphWidth!.containsKey(index)) { + _fontGlyphWidth![index] = + (widthArray[j] as _PdfNumber).value!.toInt(); } index++; } } else if (w[i] is _PdfNumber) { - endIndex = (w[i] as _PdfNumber).value.toInt(); + endIndex = (w[i] as _PdfNumber).value!.toInt(); i++; for (; index <= endIndex; index++) { - if (!_fontGlyphWidth.containsKey(index)) { - _fontGlyphWidth[index] = (w[i] as _PdfNumber).value.toInt(); + if (!_fontGlyphWidth!.containsKey(index)) { + _fontGlyphWidth![index] = (w[i] as _PdfNumber).value!.toInt(); } } } else if (w[i] is _PdfReferenceHolder) { - widthArray = (w[i] as _PdfReferenceHolder).object as _PdfArray; - for (int j = 0; j < widthArray.count; j++) { + widthArray = (w[i] as _PdfReferenceHolder).object as _PdfArray?; + for (int j = 0; j < widthArray!.count; j++) { if (!_containsCmap) { - _fontGlyphWidth[index] = - (widthArray[j] as _PdfNumber).value.toInt(); + _fontGlyphWidth![index] = + (widthArray[j] as _PdfNumber).value!.toInt(); } else { - if (!_fontGlyphWidth.containsKey(index)) { - _fontGlyphWidth[index] = - (widthArray[j] as _PdfNumber).value.toInt(); + if (!_fontGlyphWidth!.containsKey(index)) { + _fontGlyphWidth![index] = + (widthArray[j] as _PdfNumber).value!.toInt(); } } index++; @@ -674,7 +679,6 @@ class _FontStructure { } w = null; widthArray = null; - dictionary = null; } } @@ -682,30 +686,31 @@ class _FontStructure { int firstChar = 0; final _PdfDictionary dictionary = fontDictionary; if (dictionary.containsKey(_DictionaryProperties.dw)) { - defaultGlyphWidth = - (dictionary[_DictionaryProperties.dw] as _PdfNumber).value.toDouble(); + defaultGlyphWidth = (dictionary[_DictionaryProperties.dw] as _PdfNumber) + .value! + .toDouble(); } if (dictionary.containsKey(_DictionaryProperties.firstChar)) { firstChar = (dictionary[_DictionaryProperties.firstChar] as _PdfNumber) - .value + .value! .toInt(); } _fontGlyphWidth = {}; - _PdfArray w; + _PdfArray? w; int index = 0; if (dictionary.containsKey(_DictionaryProperties.widths)) { - _IPdfPrimitive primitive = dictionary[_DictionaryProperties.widths]; + _IPdfPrimitive? primitive = dictionary[_DictionaryProperties.widths]; if (primitive is _PdfArray) { w = primitive; } else if (primitive is _PdfReferenceHolder) { - primitive = (primitive as _PdfReferenceHolder).object; + primitive = primitive.object; if (primitive != null && primitive is _PdfArray) { w = primitive; } } } if (dictionary.containsKey(_DictionaryProperties.descendantFonts)) { - _IPdfPrimitive primitive = + _IPdfPrimitive? primitive = dictionary[_DictionaryProperties.descendantFonts]; if (primitive != null && primitive is _PdfArray) { final _PdfArray descendantdicArray = primitive; @@ -729,54 +734,53 @@ class _FontStructure { try { for (int i = 0; i < w.count; i++) { index = firstChar + i; - if ((characterMapTable != null && characterMapTable.isNotEmpty) || - (differencesDictionary != null && - differencesDictionary.isNotEmpty)) { + if (characterMapTable.isNotEmpty || + differencesDictionary.isNotEmpty) { if (characterMapTable.containsKey(index)) { - if (!_fontGlyphWidth.containsKey(index)) { - _fontGlyphWidth[index] = (w[i] as _PdfNumber).value.toInt(); + if (!_fontGlyphWidth!.containsKey(index)) { + _fontGlyphWidth![index] = (w[i] as _PdfNumber).value!.toInt(); } } else if (differencesDictionary.containsKey(index.toString())) { - if (!_fontGlyphWidth.containsKey(index)) { - _fontGlyphWidth[index] = (w[i] as _PdfNumber).value.toInt(); + if (!_fontGlyphWidth!.containsKey(index)) { + _fontGlyphWidth![index] = (w[i] as _PdfNumber).value!.toInt(); } - } else if (!_fontGlyphWidth.containsKey(index)) { - _fontGlyphWidth[index] = (w[i] as _PdfNumber).value.toInt(); + } else if (!_fontGlyphWidth!.containsKey(index)) { + _fontGlyphWidth![index] = (w[i] as _PdfNumber).value!.toInt(); } } else if (w[i] is _PdfArray) { final _PdfArray tempW = w[i] as _PdfArray; for (int j = i; j < tempW.count; j++) { index = firstChar + j; - if ((characterMapTable != null && characterMapTable.isNotEmpty) || - (differencesDictionary != null && - differencesDictionary.isNotEmpty)) { + if (characterMapTable.isNotEmpty || + differencesDictionary.isNotEmpty) { if (characterMapTable.containsKey(index)) { - final String mappingString = characterMapTable[index]; + final String mappingString = characterMapTable[index]!; final int entryValue = mappingString.codeUnitAt(0); - if (!_fontGlyphWidth.containsKey(entryValue)) { - _fontGlyphWidth[entryValue] = - (tempW[j] as _PdfNumber).value.toInt(); + if (!_fontGlyphWidth!.containsKey(entryValue)) { + _fontGlyphWidth![entryValue] = + (tempW[j] as _PdfNumber).value!.toInt(); } } else if (differencesDictionary .containsKey(index.toString())) { - if (!_fontGlyphWidth.containsKey(index)) { - _fontGlyphWidth[index] = - (tempW[j] as _PdfNumber).value.toInt(); + if (!_fontGlyphWidth!.containsKey(index)) { + _fontGlyphWidth![index] = + (tempW[j] as _PdfNumber).value!.toInt(); } } else { - if (!_fontGlyphWidth.containsKey(index)) { - _fontGlyphWidth[index] = - (tempW[j] as _PdfNumber).value.toInt(); + if (!_fontGlyphWidth!.containsKey(index)) { + _fontGlyphWidth![index] = + (tempW[j] as _PdfNumber).value!.toInt(); } } } else { - _fontGlyphWidth[index] = (tempW[j] as _PdfNumber).value.toInt(); + _fontGlyphWidth![index] = + (tempW[j] as _PdfNumber).value!.toInt(); } } } else { - final int value = (w[i] as _PdfNumber).value.toInt(); - if (!_fontGlyphWidth.containsKey(index)) { - _fontGlyphWidth[index] = value; + final int value = (w[i] as _PdfNumber).value!.toInt(); + if (!_fontGlyphWidth!.containsKey(index)) { + _fontGlyphWidth![index] = value; } } } @@ -789,12 +793,13 @@ class _FontStructure { bool _checkStandardFont() { bool result = !_checkStandardFontDictionary(); if (result && fontDictionary.containsKey(_DictionaryProperties.baseFont)) { - _IPdfPrimitive primitive = fontDictionary[_DictionaryProperties.baseFont]; - _PdfName baseFont; + _IPdfPrimitive? primitive = + fontDictionary[_DictionaryProperties.baseFont]; + _PdfName? baseFont; if (primitive is _PdfName) { baseFont = primitive; } else if (primitive is _PdfReferenceHolder) { - primitive = (primitive as _PdfReferenceHolder).object; + primitive = primitive.object; if (primitive != null && primitive is _PdfName) { baseFont = primitive; } @@ -815,25 +820,26 @@ class _FontStructure { bool _checkStandardCJKFont() { bool result = !_checkStandardFontDictionary(); if (result && fontDictionary.containsKey(_DictionaryProperties.baseFont)) { - _IPdfPrimitive primitive = fontDictionary[_DictionaryProperties.baseFont]; - _PdfName baseFont; + _IPdfPrimitive? primitive = + fontDictionary[_DictionaryProperties.baseFont]; + _PdfName? baseFont; if (primitive is _PdfName) { baseFont = primitive; } else if (primitive is _PdfReferenceHolder) { - primitive = (primitive as _PdfReferenceHolder).object; + primitive = primitive.object; if (primitive != null && primitive is _PdfName) { baseFont = primitive; } } if (baseFont != null) { _standardCJKFontName = baseFont._name; - _PdfName encoding; + _PdfName? encoding; if (fontDictionary.containsKey(_DictionaryProperties.encoding)) { primitive = fontDictionary[_DictionaryProperties.encoding]; if (primitive is _PdfName) { encoding = primitive; } else if (primitive is _PdfReferenceHolder) { - primitive = (primitive as _PdfReferenceHolder).object; + primitive = primitive.object; if (primitive != null && primitive is _PdfName) { encoding = primitive; } @@ -851,7 +857,7 @@ class _FontStructure { return result; } - String _resolveFontName(String fontName) { + String _resolveFontName(String? fontName) { final String tempFontName = fontName.toString(); if (tempFontName.contains('times') || tempFontName.contains('Times')) { return 'Times New Roman'; @@ -869,10 +875,10 @@ class _FontStructure { fontDictionary.containsKey(_DictionaryProperties.fontDescriptor); } - PdfFont _createStandardFont(double size) { + PdfFont? _createStandardFont(double size) { if (_standardFontName != '') { - final PdfFontFamily fontFamily = _getFontFamily(_standardFontName); - final List styles = _getFontStyle(_standardFontName); + final PdfFontFamily fontFamily = _getFontFamily(_standardFontName!); + final List styles = _getFontStyle(_standardFontName!); if (styles.contains(PdfFontStyle.bold) && styles.contains(PdfFontStyle.italic)) { font = PdfStandardFont(fontFamily, size, @@ -888,11 +894,11 @@ class _FontStructure { return font; } - PdfFont _createStandardCJKFont(double size) { + PdfFont? _createStandardCJKFont(double size) { if (_standardCJKFontName != '') { final PdfCjkFontFamily fontFamily = - _getCJKFontFamily(_standardCJKFontName); - final List styles = _getCJKFontStyle(_standardCJKFontName); + _getCJKFontFamily(_standardCJKFontName!); + final List styles = _getCJKFontStyle(_standardCJKFontName!); if (styles.contains(PdfFontStyle.bold) && styles.contains(PdfFontStyle.italic)) { font = PdfCjkStandardFont(fontFamily, size, @@ -930,8 +936,7 @@ class _FontStructure { fontFamily = PdfFontFamily.zapfDingbats; break; default: - throw ArgumentError.value(fontName, 'invalid font name'); - break; + throw ArgumentError.value(fontName, 'fontName', 'invalid font name'); } return fontFamily; } @@ -964,37 +969,36 @@ class _FontStructure { fontFamily = PdfCjkFontFamily.hanyangSystemsShinMyeongJoMedium; break; default: - throw ArgumentError.value(fontName, 'invalid font name'); - break; + throw ArgumentError.value(fontName, 'fontName', 'invalid font name'); } return fontFamily; } // Extracts the font encoding associated with the text string // Font style. - String getFontEncoding() { - _PdfName baseFont = _PdfName(); - String fontEncoding = ''; + String? getFontEncoding() { + _PdfName? baseFont = _PdfName(); + String? fontEncoding = ''; if (fontDictionary.containsKey(_DictionaryProperties.encoding)) { if (fontDictionary[_DictionaryProperties.encoding] is _PdfName) { - baseFont = fontDictionary[_DictionaryProperties.encoding]; - fontEncoding = baseFont._name; + baseFont = fontDictionary[_DictionaryProperties.encoding] as _PdfName?; + fontEncoding = baseFont!._name; } else { - _PdfDictionary baseFontDict = _PdfDictionary(); + _PdfDictionary? baseFontDict = _PdfDictionary(); if (fontDictionary[_DictionaryProperties.encoding] is _PdfDictionary) { baseFontDict = - fontDictionary[_DictionaryProperties.encoding] as _PdfDictionary; + fontDictionary[_DictionaryProperties.encoding] as _PdfDictionary?; if (baseFontDict == null) { baseFont = (fontDictionary[_DictionaryProperties.encoding] as _PdfReferenceHolder) - .object as _PdfName; - fontEncoding = baseFont._name; + .object as _PdfName?; + fontEncoding = baseFont!._name; } } else if (fontDictionary[_DictionaryProperties.encoding] is _PdfReferenceHolder) { baseFontDict = (fontDictionary[_DictionaryProperties.encoding] as _PdfReferenceHolder) - .object as _PdfDictionary; + .object as _PdfDictionary?; } if (baseFontDict != null && baseFontDict.containsKey(_DictionaryProperties.type)) { @@ -1009,27 +1013,27 @@ class _FontStructure { return fontEncoding; } - Map getReverseMapTable() { + Map? getReverseMapTable() { _reverseMapTable = {}; characterMapTable.forEach((double k, String v) { - if (!_reverseMapTable.containsKey(v)) { - _reverseMapTable[v] = k; + if (!_reverseMapTable!.containsKey(v)) { + _reverseMapTable![v] = k; } }); return _reverseMapTable; } // Extracts the font name associated with the string. - String getFontName() { - String fontName = ''; + String? getFontName() { + String? fontName = ''; isSystemFontExist = false; if (fontDictionary.containsKey(_DictionaryProperties.baseFont)) { - _PdfName baseFont = - fontDictionary[_DictionaryProperties.baseFont] as _PdfName; + _PdfName? baseFont = + fontDictionary[_DictionaryProperties.baseFont] as _PdfName?; baseFont ??= (fontDictionary[_DictionaryProperties.baseFont] as _PdfReferenceHolder) - .object as _PdfName; - String font = baseFont._name; + .object as _PdfName?; + String font = baseFont!._name!; if (font.contains('#20') && !font.contains('+')) { final int startIndex = font.lastIndexOf('#20'); font = font.substring(0, startIndex); @@ -1037,13 +1041,13 @@ class _FontStructure { } if (font.contains('+')) {} if (!isSystemFontExist) { - if (baseFont._name.contains('+')) { - fontName = baseFont._name.split('+')[1]; + if (baseFont._name!.contains('+')) { + fontName = baseFont._name!.split('+')[1]; } else { fontName = baseFont._name; } - if (fontName.contains('-')) { + if (fontName!.contains('-')) { fontName = fontName.split('-')[0]; } else if (fontName.contains(',')) { fontName = fontName.split(',')[0]; @@ -1065,18 +1069,19 @@ class _FontStructure { List getFontStyle() { List styles = []; if (fontDictionary.containsKey(_DictionaryProperties.baseFont)) { - _IPdfPrimitive primitive = fontDictionary[_DictionaryProperties.baseFont]; - _PdfName baseFont; + _IPdfPrimitive? primitive = + fontDictionary[_DictionaryProperties.baseFont]; + _PdfName? baseFont; if (primitive is _PdfName) { baseFont = primitive; } else if (primitive is _PdfReferenceHolder) { - primitive = (primitive as _PdfReferenceHolder).object; + primitive = primitive.object; if (primitive is _PdfName) { baseFont = primitive; } } if (baseFont != null) { - styles = _getFontStyle(baseFont._name); + styles = _getFontStyle(baseFont._name!); } } if (styles.isEmpty) { @@ -1153,7 +1158,7 @@ class _FontStructure { } String decodeHexFontName(String fontName) { - String newFontname; + String? newFontname; for (int i = 0; i < fontName.length; i++) { if (fontName[i] == '#') { final String hexValue = @@ -1181,20 +1186,20 @@ class _FontStructure { int endOfTable = 0; final Map mapTable = {}; if (fontDictionary.containsKey(_DictionaryProperties.toUnicode)) { - _IPdfPrimitive unicodeMap = + _IPdfPrimitive? unicodeMap = fontDictionary[_DictionaryProperties.toUnicode]; - _PdfStream mapStream; + _PdfStream? mapStream; if (unicodeMap is _PdfReferenceHolder) { - mapStream = unicodeMap.object as _PdfStream; + mapStream = unicodeMap.object as _PdfStream?; } else { - mapStream = unicodeMap as _PdfStream; + mapStream = unicodeMap as _PdfStream?; } unicodeMap = null; if (mapStream != null) { mapStream._decompress(); mapStream._isChanged = false; - final String text = utf8.decode(mapStream._dataStream); + final String text = utf8.decode(mapStream._dataStream as List); bool isBfRange = false, isBfChar = false; int start, end, startCmap, endCmap, endPointer; startCmap = text.indexOf('begincmap'); @@ -1480,58 +1485,61 @@ class _FontStructure { // Builds the mapping table that is used to map the decoded // text to get the expected text. - Map getDifferencesDictionary() { - final Map differencesDictionary = {}; - _PdfDictionary encodingDictionary; + Map getDifferencesDictionary() { + final Map differencesDictionary = {}; + _PdfDictionary? encodingDictionary; if (fontDictionary.containsKey(_DictionaryProperties.encoding)) { if (fontDictionary[_DictionaryProperties.encoding] is _PdfReferenceHolder) { encodingDictionary = (fontDictionary[_DictionaryProperties.encoding] as _PdfReferenceHolder) - .object as _PdfDictionary; + .object as _PdfDictionary?; } else if (fontDictionary[_DictionaryProperties.encoding] is _PdfDictionary) { encodingDictionary = - fontDictionary[_DictionaryProperties.encoding] as _PdfDictionary; + fontDictionary[_DictionaryProperties.encoding] as _PdfDictionary?; } if (encodingDictionary != null) { if (encodingDictionary.containsKey(_DictionaryProperties.differences)) { int differenceCount = 0; - _PdfArray differences = - encodingDictionary[_DictionaryProperties.differences] - as _PdfArray; - differences ??= (encodingDictionary[_DictionaryProperties.differences] - as _PdfReferenceHolder) - .object as _PdfArray; - for (int i = 0; i < differences.count; i++) { - String text = ''; - - if (differences[i] is _PdfNumber) { - final _PdfNumber number = differences[i]; - text = number.value.toString(); - differenceCount = number.value.toInt(); - } else if (differences[i] is _PdfName) { - text = (differences[i] as _PdfName)._name; - if ((fontType._name == 'Type1') && (text == '.notdef')) { - text = ' '; - differencesDictionary[differenceCount.toString()] = - getLatinCharacter(text); - differenceCount++; - } else { - text = getLatinCharacter(text); - text = getSpecialCharacter(text); - if (!differencesDictionary - .containsKey(differenceCount.toString())) { + final _IPdfPrimitive? obj = + encodingDictionary[_DictionaryProperties.differences]; + _PdfArray? differences; + if (obj is _PdfArray) { + differences = obj; + } else if (obj is _PdfReferenceHolder && obj.object is _PdfArray) { + differences = obj.object as _PdfArray?; + } + if (differences != null) { + for (int i = 0; i < differences.count; i++) { + String? text = ''; + if (differences[i] is _PdfNumber) { + final _PdfNumber number = differences[i] as _PdfNumber; + text = number.value.toString(); + differenceCount = number.value!.toInt(); + } else if (differences[i] is _PdfName) { + text = (differences[i] as _PdfName)._name; + if ((fontType!._name == 'Type1') && (text == '.notdef')) { + text = ' '; differencesDictionary[differenceCount.toString()] = getLatinCharacter(text); + differenceCount++; + } else { + text = getLatinCharacter(text); + text = getSpecialCharacter(text); + if (!differencesDictionary + .containsKey(differenceCount.toString())) { + differencesDictionary[differenceCount.toString()] = + getLatinCharacter(text); + } + differenceCount++; } - differenceCount++; } - } else {} + } + differences = null; } - differences = null; } } } @@ -1541,7 +1549,7 @@ class _FontStructure { // Gets Latin Character //Latin Character Set (APPENDIX D Pdf version-1.7) Page- 997 - String getLatinCharacter(String decodedCharacter) { + String? getLatinCharacter(String? decodedCharacter) { switch (decodedCharacter) { case 'zero': return '0'; @@ -1822,7 +1830,7 @@ class _FontStructure { // Gets Latin Character //Latin Character Set (APPENDIX D Pdf version-1.7) Page- 997 - String getSpecialCharacter(String decodedCharacter) { + String? getSpecialCharacter(String? decodedCharacter) { switch (decodedCharacter) { case 'head2right': return '\u27A2'; @@ -2144,135 +2152,135 @@ class _FontStructure { void getMacEncodeTable() { _macEncodeTable = {}; - _macEncodeTable[127] = ' '; - _macEncodeTable[128] = 'Ä'; - _macEncodeTable[129] = 'Å'; - _macEncodeTable[130] = 'Ç'; - _macEncodeTable[131] = 'É'; - _macEncodeTable[132] = 'Ñ'; - _macEncodeTable[133] = 'Ö'; - _macEncodeTable[134] = 'Ü'; - _macEncodeTable[135] = 'á'; - _macEncodeTable[136] = 'à'; - _macEncodeTable[137] = 'â'; - _macEncodeTable[138] = 'ä'; - _macEncodeTable[139] = 'ã'; - _macEncodeTable[140] = 'å'; - _macEncodeTable[141] = 'ç'; - _macEncodeTable[142] = 'é'; - _macEncodeTable[143] = 'è'; - _macEncodeTable[144] = 'ê'; - _macEncodeTable[145] = 'ë'; - _macEncodeTable[146] = 'í'; - _macEncodeTable[147] = 'ì'; - _macEncodeTable[148] = 'î'; - _macEncodeTable[149] = 'ï'; - _macEncodeTable[150] = 'ñ'; - _macEncodeTable[151] = 'ó'; - _macEncodeTable[152] = 'ò'; - _macEncodeTable[153] = 'ô'; - _macEncodeTable[154] = 'ö'; - _macEncodeTable[155] = 'õ'; - _macEncodeTable[156] = 'ú'; - _macEncodeTable[157] = 'ù'; - _macEncodeTable[158] = 'û'; - _macEncodeTable[159] = 'ü'; - _macEncodeTable[160] = '†'; - _macEncodeTable[161] = '°'; - _macEncodeTable[162] = '¢'; - _macEncodeTable[163] = '£'; - _macEncodeTable[164] = '§'; - _macEncodeTable[165] = '•'; - _macEncodeTable[166] = '¶'; - _macEncodeTable[167] = 'ß'; - _macEncodeTable[168] = '®'; - _macEncodeTable[169] = '©'; - _macEncodeTable[170] = '™'; - _macEncodeTable[171] = '´'; - _macEncodeTable[172] = '¨'; - _macEncodeTable[173] = '≠'; - _macEncodeTable[174] = 'Æ'; - _macEncodeTable[175] = 'Ø'; - _macEncodeTable[176] = '∞'; - _macEncodeTable[177] = '±'; - _macEncodeTable[178] = '≤'; - _macEncodeTable[179] = '≥'; - _macEncodeTable[180] = '¥'; - _macEncodeTable[181] = 'µ'; - _macEncodeTable[182] = '∂'; - _macEncodeTable[183] = '∑'; - _macEncodeTable[184] = '∏'; - _macEncodeTable[185] = 'π'; - _macEncodeTable[186] = '∫'; - _macEncodeTable[187] = 'ª'; - _macEncodeTable[188] = 'º'; - _macEncodeTable[189] = 'Ω'; - _macEncodeTable[190] = 'æ'; - _macEncodeTable[191] = 'ø'; - _macEncodeTable[192] = '¿'; - _macEncodeTable[193] = '¡'; - _macEncodeTable[194] = '¬'; - _macEncodeTable[195] = '√'; - _macEncodeTable[196] = 'ƒ'; - _macEncodeTable[197] = '≈'; - _macEncodeTable[198] = '∆'; - _macEncodeTable[199] = '«'; - _macEncodeTable[200] = '»'; - _macEncodeTable[201] = '…'; - _macEncodeTable[202] = ' '; - _macEncodeTable[203] = 'À'; - _macEncodeTable[204] = 'Ã'; - _macEncodeTable[205] = 'Õ'; - _macEncodeTable[206] = 'Œ'; - _macEncodeTable[207] = 'œ'; - _macEncodeTable[208] = '–'; - _macEncodeTable[209] = '—'; - _macEncodeTable[210] = '“'; - _macEncodeTable[211] = '”'; - _macEncodeTable[212] = '‘'; - _macEncodeTable[213] = '’'; - _macEncodeTable[214] = '÷'; - _macEncodeTable[215] = '◊'; - _macEncodeTable[216] = 'ÿ'; - _macEncodeTable[217] = 'Ÿ'; - _macEncodeTable[218] = '⁄'; - _macEncodeTable[219] = '€'; - _macEncodeTable[220] = '‹'; - _macEncodeTable[221] = '›'; - _macEncodeTable[222] = 'fi'; - _macEncodeTable[223] = 'fl'; - _macEncodeTable[224] = '‡'; - _macEncodeTable[225] = '·'; - _macEncodeTable[226] = ','; - _macEncodeTable[227] = '„'; - _macEncodeTable[228] = '‰'; - _macEncodeTable[229] = 'Â'; - _macEncodeTable[230] = 'Ê'; - _macEncodeTable[231] = 'Á'; - _macEncodeTable[232] = 'Ë'; - _macEncodeTable[233] = 'È'; - _macEncodeTable[234] = 'Í'; - _macEncodeTable[235] = 'Î'; - _macEncodeTable[236] = 'Ï'; - _macEncodeTable[237] = 'Ì'; - _macEncodeTable[238] = 'Ó'; - _macEncodeTable[239] = 'Ô'; - _macEncodeTable[240] = ''; - _macEncodeTable[241] = 'Ò'; - _macEncodeTable[242] = 'Ú'; - _macEncodeTable[243] = 'Û'; - _macEncodeTable[244] = 'Ù'; - _macEncodeTable[245] = 'ı'; - _macEncodeTable[246] = 'ˆ'; - _macEncodeTable[247] = '˜'; - _macEncodeTable[248] = '¯'; - _macEncodeTable[249] = '˘'; - _macEncodeTable[250] = '˙'; - _macEncodeTable[251] = '˚'; - _macEncodeTable[252] = '¸'; - _macEncodeTable[253] = '˝'; - _macEncodeTable[254] = '˛'; - _macEncodeTable[255] = 'ˇ'; + _macEncodeTable![127] = ' '; + _macEncodeTable![128] = 'Ä'; + _macEncodeTable![129] = 'Å'; + _macEncodeTable![130] = 'Ç'; + _macEncodeTable![131] = 'É'; + _macEncodeTable![132] = 'Ñ'; + _macEncodeTable![133] = 'Ö'; + _macEncodeTable![134] = 'Ü'; + _macEncodeTable![135] = 'á'; + _macEncodeTable![136] = 'à'; + _macEncodeTable![137] = 'â'; + _macEncodeTable![138] = 'ä'; + _macEncodeTable![139] = 'ã'; + _macEncodeTable![140] = 'å'; + _macEncodeTable![141] = 'ç'; + _macEncodeTable![142] = 'é'; + _macEncodeTable![143] = 'è'; + _macEncodeTable![144] = 'ê'; + _macEncodeTable![145] = 'ë'; + _macEncodeTable![146] = 'í'; + _macEncodeTable![147] = 'ì'; + _macEncodeTable![148] = 'î'; + _macEncodeTable![149] = 'ï'; + _macEncodeTable![150] = 'ñ'; + _macEncodeTable![151] = 'ó'; + _macEncodeTable![152] = 'ò'; + _macEncodeTable![153] = 'ô'; + _macEncodeTable![154] = 'ö'; + _macEncodeTable![155] = 'õ'; + _macEncodeTable![156] = 'ú'; + _macEncodeTable![157] = 'ù'; + _macEncodeTable![158] = 'û'; + _macEncodeTable![159] = 'ü'; + _macEncodeTable![160] = '†'; + _macEncodeTable![161] = '°'; + _macEncodeTable![162] = '¢'; + _macEncodeTable![163] = '£'; + _macEncodeTable![164] = '§'; + _macEncodeTable![165] = '•'; + _macEncodeTable![166] = '¶'; + _macEncodeTable![167] = 'ß'; + _macEncodeTable![168] = '®'; + _macEncodeTable![169] = '©'; + _macEncodeTable![170] = '™'; + _macEncodeTable![171] = '´'; + _macEncodeTable![172] = '¨'; + _macEncodeTable![173] = '≠'; + _macEncodeTable![174] = 'Æ'; + _macEncodeTable![175] = 'Ø'; + _macEncodeTable![176] = '∞'; + _macEncodeTable![177] = '±'; + _macEncodeTable![178] = '≤'; + _macEncodeTable![179] = '≥'; + _macEncodeTable![180] = '¥'; + _macEncodeTable![181] = 'µ'; + _macEncodeTable![182] = '∂'; + _macEncodeTable![183] = '∑'; + _macEncodeTable![184] = '∏'; + _macEncodeTable![185] = 'π'; + _macEncodeTable![186] = '∫'; + _macEncodeTable![187] = 'ª'; + _macEncodeTable![188] = 'º'; + _macEncodeTable![189] = 'Ω'; + _macEncodeTable![190] = 'æ'; + _macEncodeTable![191] = 'ø'; + _macEncodeTable![192] = '¿'; + _macEncodeTable![193] = '¡'; + _macEncodeTable![194] = '¬'; + _macEncodeTable![195] = '√'; + _macEncodeTable![196] = 'ƒ'; + _macEncodeTable![197] = '≈'; + _macEncodeTable![198] = '∆'; + _macEncodeTable![199] = '«'; + _macEncodeTable![200] = '»'; + _macEncodeTable![201] = '…'; + _macEncodeTable![202] = ' '; + _macEncodeTable![203] = 'À'; + _macEncodeTable![204] = 'Ã'; + _macEncodeTable![205] = 'Õ'; + _macEncodeTable![206] = 'Œ'; + _macEncodeTable![207] = 'œ'; + _macEncodeTable![208] = '–'; + _macEncodeTable![209] = '—'; + _macEncodeTable![210] = '“'; + _macEncodeTable![211] = '”'; + _macEncodeTable![212] = '‘'; + _macEncodeTable![213] = '’'; + _macEncodeTable![214] = '÷'; + _macEncodeTable![215] = '◊'; + _macEncodeTable![216] = 'ÿ'; + _macEncodeTable![217] = 'Ÿ'; + _macEncodeTable![218] = '⁄'; + _macEncodeTable![219] = '€'; + _macEncodeTable![220] = '‹'; + _macEncodeTable![221] = '›'; + _macEncodeTable![222] = 'fi'; + _macEncodeTable![223] = 'fl'; + _macEncodeTable![224] = '‡'; + _macEncodeTable![225] = '·'; + _macEncodeTable![226] = ','; + _macEncodeTable![227] = '„'; + _macEncodeTable![228] = '‰'; + _macEncodeTable![229] = 'Â'; + _macEncodeTable![230] = 'Ê'; + _macEncodeTable![231] = 'Á'; + _macEncodeTable![232] = 'Ë'; + _macEncodeTable![233] = 'È'; + _macEncodeTable![234] = 'Í'; + _macEncodeTable![235] = 'Î'; + _macEncodeTable![236] = 'Ï'; + _macEncodeTable![237] = 'Ì'; + _macEncodeTable![238] = 'Ó'; + _macEncodeTable![239] = 'Ô'; + _macEncodeTable![240] = ''; + _macEncodeTable![241] = 'Ò'; + _macEncodeTable![242] = 'Ú'; + _macEncodeTable![243] = 'Û'; + _macEncodeTable![244] = 'Ù'; + _macEncodeTable![245] = 'ı'; + _macEncodeTable![246] = 'ˆ'; + _macEncodeTable![247] = '˜'; + _macEncodeTable![248] = '¯'; + _macEncodeTable![249] = '˘'; + _macEncodeTable![250] = '˙'; + _macEncodeTable![251] = '˚'; + _macEncodeTable![252] = '¸'; + _macEncodeTable![253] = '˝'; + _macEncodeTable![254] = '˛'; + _macEncodeTable![255] = 'ˇ'; } String skipEscapeSequence(String text) { @@ -2318,10 +2326,10 @@ class _FontStructure { try { text = _unescape(text); } catch (e) { - if (text != null && text.isNotEmpty) { + if (text.isNotEmpty) { text = _unescape(RegExp.escape(text)); } else { - throw Exception(e.errMsg()); + throw Exception(); } } } @@ -2485,10 +2493,9 @@ class _FontStructure { (fontEncoding == 'Identity-H') || (fontEncoding == 'Identity-H' && containsCmap)) { isMappingDone = true; - if (characterMapTable != null && characterMapTable.isNotEmpty) { + if (characterMapTable.isNotEmpty) { decodedText = mapCharactersFromTable(decodedText); - } else if (differencesDictionary != null && - differencesDictionary.isNotEmpty) { + } else if (differencesDictionary.isNotEmpty) { decodedText = mapDifferences(decodedText); } else if (fontEncoding != '') { decodedText = skipEscapeSequence(decodedText); @@ -2506,7 +2513,7 @@ class _FontStructure { for (int i = 0; i < decodedText.length; i++) { final int b = decodedText[i].codeUnitAt(0).toUnsigned(8); if (b > 126) { - final String x = macEncodeTable[b]; + final String x = macEncodeTable![b]!; tempstring += x; } else { tempstring += decodedText[i]; @@ -2516,13 +2523,10 @@ class _FontStructure { decodedText = tempstring; } } - encodedText = null; if (decodedText.contains('\u0092')) { decodedText = decodedText.replaceAll('\u0092', '’'); } - isWhiteSpace = (decodedText == null || - decodedText.isEmpty || - decodedText.trimRight() == ''); + isWhiteSpace = decodedText.isEmpty || (decodedText.trimRight() == ''); return decodedText; } @@ -2634,9 +2638,7 @@ class _FontStructure { } } decodedText = skipEscapeSequence(decodedText); - isWhiteSpace = (decodedText == null || - decodedText.isEmpty || - decodedText.trimRight() == ''); + isWhiteSpace = decodedText.isEmpty || (decodedText.trimRight() == ''); return decodedList; } @@ -2767,10 +2769,9 @@ class _FontStructure { (fontEncoding == 'Identity-H') || (fontEncoding == 'Identity-H' && containsCmap)) { isMappingDone = true; - if (characterMapTable != null && characterMapTable.isNotEmpty) { + if (characterMapTable.isNotEmpty) { listElement = mapCharactersFromTable(listElement); - } else if (differencesDictionary != null && - differencesDictionary.isNotEmpty) { + } else if (differencesDictionary.isNotEmpty) { listElement = mapDifferences(listElement); } else if (fontEncoding != '') { listElement = skipEscapeSequence(listElement); @@ -2829,14 +2830,11 @@ class _FontStructure { break; } decodedText = skipEscapeSequence(decodedText); - isWhiteSpace = (decodedText == null || - decodedText.isEmpty || - decodedText.trimRight() == ''); + isWhiteSpace = (decodedText.isEmpty || decodedText.trimRight() == ''); return decodedList; } String fromUnicodeText(String value) { - ArgumentError.checkNotNull(value); String result = ''; if (value.length % 2 != 0) { for (int i = 0; i < value.length; i++) { @@ -2855,8 +2853,7 @@ class _FontStructure { i++; } } - isWhiteSpace = - (result == null || result.isEmpty || result.trimRight() == ''); + isWhiteSpace = (result.isEmpty || result.trimRight() == ''); return result; } @@ -2881,10 +2878,10 @@ class _FontStructure { encodedText = skipEscapeSequence(encodedText); } if (fontDictionary.containsKey(_DictionaryProperties.encoding)) { - final _IPdfPrimitive primitive = + final _IPdfPrimitive? primitive = fontDictionary[_DictionaryProperties.encoding]; if (primitive is _PdfName) { - final String encoding = primitive._name; + final String? encoding = primitive._name; if (encoding == 'Identity-H') { String text = encodedText; if (!hasEscapeChar) { @@ -3000,7 +2997,6 @@ class _FontStructure { break; } if (encodedText.contains('\u0000') && - characterMapTable != null && characterMapTable.isNotEmpty && !characterMapTable.containsKey('\u0000'.codeUnitAt(0))) { encodedText = encodedText.replaceAll('\u0000', ''); @@ -3008,16 +3004,14 @@ class _FontStructure { if (!isTextExtraction) { encodedText = skipEscapeSequence(encodedText); } - isWhiteSpace = (encodedText == null || - encodedText.isEmpty || - encodedText.trimRight() == ''); + isWhiteSpace = (encodedText.isEmpty || encodedText.trimRight() == ''); return encodedText; } bool isCIDFontType() { bool iscid = false; if (fontDictionary.containsKey(_DictionaryProperties.descendantFonts)) { - _IPdfPrimitive primitive = + _IPdfPrimitive? primitive = fontDictionary[_DictionaryProperties.descendantFonts]; if (primitive is _PdfReferenceHolder) { final _PdfReferenceHolder descendantFontArrayReference = primitive; @@ -3034,7 +3028,8 @@ class _FontStructure { primitive = descendantDictionary[_DictionaryProperties.subtype]; if (primitive is _PdfName) { final _PdfName subtype = - descendantDictionary[_DictionaryProperties.subtype]; + descendantDictionary[_DictionaryProperties.subtype] + as _PdfName; if (subtype._name == 'CIDFontType2' || subtype._name == 'CIDFontType0') { iscid = true; @@ -3056,7 +3051,8 @@ class _FontStructure { primitive = descendantDictionary[_DictionaryProperties.subtype]; if (primitive is _PdfName) { final _PdfName subtype = - descendantDictionary[_DictionaryProperties.subtype]; + descendantDictionary[_DictionaryProperties.subtype] + as _PdfName; if (subtype._name == 'CIDFontType2' || subtype._name == 'CIDFontType0') { iscid = true; @@ -3145,12 +3141,12 @@ class _FontStructure { // Takes in the decoded text and maps it with its // corresponding entry in the CharacterMapTable - String mapDifferences(String encodedText) { + String mapDifferences(String? encodedText) { String decodedText = ''; bool skip = false; if (isTextExtraction) { try { - encodedText = _unescape(encodedText); + encodedText = _unescape(encodedText!); } catch (e) { if (encodedText != null && encodedText.isNotEmpty) { encodedText = RegExp.escape(encodedText) @@ -3161,19 +3157,19 @@ class _FontStructure { } } } else { - skipEscapeSequence(encodedText); + skipEscapeSequence(encodedText!); } for (int i = 0; i < encodedText.length; i++) { final int character = encodedText.codeUnitAt(i); if (differencesDictionary.containsKey(character.toString())) { - final String tempString = differencesDictionary[character.toString()]; + final String tempString = differencesDictionary[character.toString()]!; if ((tempString.length > 1) && - (fontType._name != 'Type3') && + (fontType!._name != 'Type3') && (!isTextExtraction)) { decodedText += character.toString(); } else { if (!isTextExtraction) { - String textDecoded = differencesDictionary[character.toString()]; + String textDecoded = differencesDictionary[character.toString()]!; if (textDecoded.length == 7 && textDecoded.toLowerCase().startsWith('uni')) { textDecoded = String.fromCharCode( @@ -3183,11 +3179,11 @@ class _FontStructure { } else { if (!differencesDictionary.containsKey(character.toString())) { final _AdobeGlyphList glyphList = _AdobeGlyphList(); - decodedText += glyphList.getUnicode(tempString); - glyphList.map.clear(); + decodedText += glyphList.getUnicode(tempString)!; + glyphList.map!.clear(); } else { final String textDecoded = - differencesDictionary[character.toString()]; + differencesDictionary[character.toString()]!; decodedText += textDecoded; } } @@ -3201,9 +3197,9 @@ class _FontStructure { decodedText = mapDifferenceOfWingDings(decodedText); } - final String specialCharacter = getSpecialCharacter(decodedText); + final String? specialCharacter = getSpecialCharacter(decodedText); if (decodedText != specialCharacter && !isEmbedded) { - decodedText = decodedText.replaceAll(decodedText, specialCharacter); + decodedText = decodedText.replaceAll(decodedText, specialCharacter!); } skip = false; } else { @@ -3213,14 +3209,14 @@ class _FontStructure { if (differencesDictionary .containsKey('\n'.codeUnitAt(0).toString())) { decodedText += - differencesDictionary['\n'.codeUnitAt(0).toString()]; + differencesDictionary['\n'.codeUnitAt(0).toString()]!; } break; case 'r': if (differencesDictionary .containsKey('\r'.codeUnitAt(0).toString())) { decodedText += - differencesDictionary['\r'.codeUnitAt(0).toString()]; + differencesDictionary['\r'.codeUnitAt(0).toString()]!; } break; default: @@ -4053,10 +4049,10 @@ class _FontStructure { break; default: - if (reverseMapTable.containsKey(encodedText)) { + if (reverseMapTable!.containsKey(encodedText)) { decodedtext = encodedText; - final int charPosition = reverseMapTable[decodedtext].toInt(); - zapfPostScript = differenceTable[charPosition]; + final int charPosition = reverseMapTable![decodedtext]!.toInt(); + zapfPostScript = differenceTable[charPosition]!; } else { decodedtext = '\u2708'; zapfPostScript = 'a118'; @@ -4089,8 +4085,8 @@ class _FontStructure { for (int i = 0; i < decodedText.length; i++) { final int character = decodedText.codeUnitAt(0); - if (cidToGidTable.containsKey(character) && !skip) { - String mappingString = cidToGidTable[character]; + if (cidToGidTable!.containsKey(character) && !skip) { + String mappingString = cidToGidTable![character]!; if (mappingString.contains('�')) { final int index = mappingString.indexOf('�'); mappingString = mappingString.replaceAll(mappingString[index], ''); @@ -4104,7 +4100,7 @@ class _FontStructure { finalText += mappingString; skip = false; } else if (tempMapTable.containsKey(character) && !skip) { - String mappingString = tempMapTable[character]; + String mappingString = tempMapTable[character]!; if (mappingString.contains('�')) { final int index = mappingString.indexOf('�'); mappingString = mappingString.replaceAll(mappingString[index], ''); @@ -4113,51 +4109,51 @@ class _FontStructure { skip = false; } else { if (skip) { - switch (character.toString()) { + switch (String.fromCharCode(character)) { case 'n': - if (cidToGidTable.containsKey(10)) { - finalText += characterMapTable[10]; + if (cidToGidTable!.containsKey(10)) { + finalText += characterMapTable[10]!; } break; case 'r': - if (cidToGidTable.containsKey(13)) { - finalText += characterMapTable[13]; + if (cidToGidTable!.containsKey(13)) { + finalText += characterMapTable[13]!; } break; case 'b': - if (cidToGidTable.containsKey(8)) { - finalText += characterMapTable[8]; + if (cidToGidTable!.containsKey(8)) { + finalText += characterMapTable[8]!; } break; case 'a': - if (cidToGidTable.containsKey(7)) { - finalText += characterMapTable[7]; + if (cidToGidTable!.containsKey(7)) { + finalText += characterMapTable[7]!; } break; case 'f': - if (cidToGidTable.containsKey(12)) { - finalText += characterMapTable[12]; + if (cidToGidTable!.containsKey(12)) { + finalText += characterMapTable[12]!; } break; case 't': - if (cidToGidTable.containsKey(9)) { - finalText += characterMapTable[9]; + if (cidToGidTable!.containsKey(9)) { + finalText += characterMapTable[9]!; } break; case 'v': - if (cidToGidTable.containsKey(11)) { - finalText += characterMapTable[11]; + if (cidToGidTable!.containsKey(11)) { + finalText += characterMapTable[11]!; } break; case "'": - if (cidToGidTable.containsKey(39)) { - finalText += characterMapTable[39]; + if (cidToGidTable!.containsKey(39)) { + finalText += characterMapTable[39]!; } break; default: { - if (cidToGidTable.containsKey(character)) { - finalText += characterMapTable[character]; + if (cidToGidTable!.containsKey(character)) { + finalText += characterMapTable[character]!; } } break; @@ -4227,10 +4223,9 @@ class _FontStructure { final int decimalValue = int.parse(octalText, radix: 8).toUnsigned(64); String temp = ''; final String decodedChar = String.fromCharCode(decimalValue); - if (characterMapTable != null && characterMapTable.isNotEmpty) { + if (characterMapTable.isNotEmpty) { temp = decodedChar.toString(); - } else if (differencesDictionary != null && - differencesDictionary.isNotEmpty && + } else if (differencesDictionary.isNotEmpty && differencesDictionary.containsKey(decimalValue.toString())) { temp = decodedChar.toString(); } else { @@ -4292,7 +4287,7 @@ class _FontStructure { getMacEncodeTable(); for (int i = 0; i < decodedText.length; i++) { final int decimalValue = decodedText[i].codeUnitAt(0); - if (_macEncodeTable.containsValue(decodedText[i].toString())) { + if (_macEncodeTable!.containsValue(decodedText[i].toString())) { if (!_macRomanMapTable.containsKey(decimalValue)) { final int charbytes = decimalValue.toUnsigned(8); _macRomanMapTable[decimalValue] = String.fromCharCode(charbytes); @@ -4339,9 +4334,9 @@ class _FontStructure { String getHexaDecimalString(String hexEncodedText) { String decodedText = ''; // IsHexaDecimalString = true; - if (hexEncodedText != null && hexEncodedText.isNotEmpty) { + if (hexEncodedText.isNotEmpty) { final _PdfName fontType = - fontDictionary._items[_PdfName('Subtype')] as _PdfName; + fontDictionary._items![_PdfName('Subtype')] as _PdfName; int limit = 2; if (fontType._name != 'Type1' && fontType._name != 'TrueType' && @@ -4351,7 +4346,7 @@ class _FontStructure { hexEncodedText = escapeSymbols(hexEncodedText); final String tempHexEncodedText = hexEncodedText; final String tempDecodedText = decodedText; - String decodedTxt; + late String decodedTxt; while (hexEncodedText.isNotEmpty) { if (hexEncodedText.length % 4 != 0) { limit = 2; @@ -4360,25 +4355,25 @@ class _FontStructure { if (fontDictionary.containsKey(_DictionaryProperties.descendantFonts) && !fontDictionary.containsKey(_DictionaryProperties.toUnicode)) { - final _PdfArray descendantArray = + final _PdfArray? descendantArray = fontDictionary[_DictionaryProperties.descendantFonts] - as _PdfArray; + as _PdfArray?; if (descendantArray != null) { - _PdfDictionary descendantDictionary; + _PdfDictionary? descendantDictionary; if (descendantArray[0] is _PdfReferenceHolder) { descendantDictionary = (descendantArray[0] as _PdfReferenceHolder) - .object as _PdfDictionary; + .object as _PdfDictionary?; } else if (descendantArray[0] is _PdfDictionary) { descendantDictionary = descendantArray[0] as _PdfDictionary; } if (descendantDictionary != null) { - _PdfDictionary descriptorDictionary; + _PdfDictionary? descriptorDictionary; if (descendantDictionary .containsKey(_DictionaryProperties.fontDescriptor)) { - _IPdfPrimitive primitive = + _IPdfPrimitive? primitive = descendantDictionary[_DictionaryProperties.fontDescriptor]; if (primitive is _PdfReferenceHolder) { - primitive = (primitive as _PdfReferenceHolder).object; + primitive = primitive.object; if (primitive != null && primitive is _PdfDictionary) { descriptorDictionary = primitive; } @@ -4400,31 +4395,31 @@ class _FontStructure { } } } - } else if (fontDictionary._items + } else if (fontDictionary._items! .containsKey(_PdfName(_DictionaryProperties.descendantFonts))) { - final _PdfReferenceHolder descendantFontArrayReference = - fontDictionary - ._items[_PdfName(_DictionaryProperties.descendantFonts)] - as _PdfReferenceHolder; + final _PdfReferenceHolder? descendantFontArrayReference = + fontDictionary._items![ + _PdfName(_DictionaryProperties.descendantFonts)] + as _PdfReferenceHolder?; if (descendantFontArrayReference != null) { - _PdfName subtype; + _PdfName? subtype; final _PdfArray descendantFontArray = descendantFontArrayReference.object as _PdfArray; - if (descendantFontArray[0] as _PdfReferenceHolder != null) { - final _PdfDictionary descendantDictionary = + if (descendantFontArray[0] is _PdfReferenceHolder) { + final _PdfDictionary? descendantDictionary = (descendantFontArray[0] as _PdfReferenceHolder).object - as _PdfDictionary; + as _PdfDictionary?; if (descendantDictionary != null && descendantDictionary .containsKey(_DictionaryProperties.cidSystemInfo) && descendantDictionary .containsKey(_DictionaryProperties.subtype)) { subtype = descendantDictionary[_DictionaryProperties.subtype] - as _PdfName; - final _PdfDictionary cidSystemInfo = + as _PdfName?; + final _PdfDictionary? cidSystemInfo = (descendantDictionary[_DictionaryProperties.cidSystemInfo] as _PdfReferenceHolder) - .object as _PdfDictionary; + .object as _PdfDictionary?; if (cidSystemInfo != null && cidSystemInfo .containsKey(_DictionaryProperties.registry) && @@ -4435,19 +4430,19 @@ class _FontStructure { final _PdfString pdfRegistry = cidSystemInfo[_DictionaryProperties.registry] as _PdfString; - final _PdfNumber pdfSupplement = + final _PdfNumber? pdfSupplement = cidSystemInfo[_DictionaryProperties.supplement] - as _PdfNumber; - final _PdfString pdfOrdering = + as _PdfNumber?; + final _PdfString? pdfOrdering = cidSystemInfo[_DictionaryProperties.ordering] - as _PdfString; + as _PdfString?; if (pdfRegistry.value != null && - pdfSupplement.value != null && - pdfOrdering.value != null) { + pdfSupplement!.value != null && + pdfOrdering!.value != null) { if (pdfRegistry.value == 'Adobe' && pdfOrdering.value == 'Identity' && pdfSupplement.value == 0 && - subtype._name == 'CIDFontType2' && + subtype!._name == 'CIDFontType2' && cidSystemInfoDictionary == null && !isContainFontfile2) { isAdobeIdentity = true; @@ -4529,16 +4524,16 @@ class _FontStructure { for (int i = 0; i < decodedText.length; i++) { final String character = decodedText[i]; if (characterMapTable.containsKey(character.codeUnitAt(0)) && !skip) { - String mappingString = characterMapTable[character.codeUnitAt(0)]; + String mappingString = characterMapTable[character.codeUnitAt(0)]!; if (mappingString.contains('�')) { mappingString = mappingString.replaceAll('�', ''); - if (fontName.contains('ZapfDingbats')) { + if (fontName!.contains('ZapfDingbats')) { mappingString = character.toString(); } } if (fontEncoding != 'Identity-H' && !isTextExtraction && - characterMapTable.length != reverseMapTable.length) { + characterMapTable.length != reverseMapTable!.length) { if (isCancel(mappingString) || isNonPrintableCharacter( character)) //Contains 'CANCEL' of ASCII value 24 @@ -4553,16 +4548,16 @@ class _FontStructure { final List bytes = _encodeBigEndian(character.toString()); if (bytes[0] != 92) { if (characterMapTable.containsKey(bytes[0])) { - finalText += characterMapTable[bytes[0]]; + finalText += characterMapTable[bytes[0]]!; skip = false; } } } else if (tempMapTable.containsKey(character.codeUnitAt(0)) && !skip) { - String mappingString = tempMapTable[character.codeUnitAt(0)]; + String? mappingString = tempMapTable[character.codeUnitAt(0)]; if (character == '\\' && isTextExtraction) { mappingString = ''; } - if (mappingString.contains('�')) { + if (mappingString!.contains('�')) { final int index = mappingString.indexOf('�'); mappingString = mappingString.replaceAll(mappingString[index], ''); } @@ -4573,48 +4568,48 @@ class _FontStructure { switch (character.toString()) { case 'n': if (characterMapTable.containsKey(10)) { - finalText += characterMapTable[10]; + finalText += characterMapTable[10]!; } break; case 'r': if (characterMapTable.containsKey(13)) { - finalText += characterMapTable[13]; + finalText += characterMapTable[13]!; } break; case 'b': if (characterMapTable.containsKey(8)) { - finalText += characterMapTable[8]; + finalText += characterMapTable[8]!; } break; case 'a': if (characterMapTable.containsKey(7)) { - finalText += characterMapTable[7]; + finalText += characterMapTable[7]!; } break; case 'f': if (characterMapTable.containsKey(12)) { - finalText += characterMapTable[12]; + finalText += characterMapTable[12]!; } break; case 't': if (characterMapTable.containsKey(9)) { - finalText += characterMapTable[9]; + finalText += characterMapTable[9]!; } break; case 'v': if (characterMapTable.containsKey(11)) { - finalText += characterMapTable[11]; + finalText += characterMapTable[11]!; } break; case "'": if (characterMapTable.containsKey(39)) { - finalText += characterMapTable[39]; + finalText += characterMapTable[39]!; } break; default: { if (characterMapTable.containsKey(character.codeUnitAt(0))) { - finalText += characterMapTable[character.codeUnitAt(0)]; + finalText += characterMapTable[character.codeUnitAt(0)]!; } } break; @@ -4642,7 +4637,7 @@ class _FontStructure { bool isNonPrintableCharacter(String str) { bool isNonPrintable = false; if (!isTextExtraction && - fontType._name == 'Type1' && + fontType!._name == 'Type1' && fontEncoding == 'Encoding' && fontName != 'ZapfDingbats' && (characterMapTable.length == differencesDictionary.length)) { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/glyph.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/glyph.dart index b156b8d57..b6820119f 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/glyph.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/glyph.dart @@ -6,24 +6,24 @@ class _Glyph { } //Fields - double _descent; + late double _descent; //Properties - double ascent; - _MatrixHelper transformMatrix; - Rect boundingRect; - double charSpacing; - double wordSpacing; - double horizontalScaling; - double fontSize; - String fontFamily; - String name; - int charId; - double width; - List fontStyle; - String toUnicode; - bool isRotated; - int rotationAngle; + late double ascent; + late _MatrixHelper transformMatrix; + late Rect boundingRect; + late double charSpacing; + late double wordSpacing; + late double horizontalScaling; + late double fontSize; + String fontFamily = ''; + String name = ''; + int charId = -1; + late double width; + List fontStyle = []; + String toUnicode = ''; + late bool isRotated; + late int rotationAngle; double get descent { return _descent; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/graphic_object_data.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/graphic_object_data.dart index b3e4194d4..91e47ae61 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/graphic_object_data.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/graphic_object_data.dart @@ -16,36 +16,36 @@ class _GraphicObjectData { } //Fields - _MatrixHelper currentTransformationMatrix; - _MatrixHelper drawing2dMatrixCTM; - _MatrixHelper documentMatrix; - _MatrixHelper textLineMatrix; - _MatrixHelper textMatrix; - _MatrixHelper textMatrixUpdate; - _MatrixHelper transformMatrixTM; - double _horizontalScaling; - double _mitterLength; - int rise; - double characterSpacing; - double wordSpacing; - double _nonStrokingOpacity; - double _strokingOpacity; - String currentFont; - double textLeading; - double fontSize; + _MatrixHelper? currentTransformationMatrix; + _MatrixHelper? drawing2dMatrixCTM; + _MatrixHelper? documentMatrix; + _MatrixHelper? textLineMatrix; + _MatrixHelper? textMatrix; + _MatrixHelper? textMatrixUpdate; + _MatrixHelper? transformMatrixTM; + double? _horizontalScaling; + double? _mitterLength; + int? rise; + double? characterSpacing; + double? wordSpacing; + double? _nonStrokingOpacity; + double? _strokingOpacity; + String? currentFont; + double? textLeading; + double? fontSize; } class _GraphicsObject { _GraphicsObject() { _transformMatrix = _MatrixHelper(1, 0, 0, 1, 0, 0); } - _MatrixHelper _transformMatrix; - _GraphicsState _graphicState; + _MatrixHelper? _transformMatrix; + _GraphicsState? _graphicState; //Implementation - _GraphicsState _save() { + _GraphicsState? _save() { _graphicState = _GraphicsState(); - _graphicState._transformMatrix = _transformMatrix; + _graphicState!._transformMatrix = _transformMatrix; return _graphicState; } @@ -54,22 +54,22 @@ class _GraphicsObject { } void _multiplyTransform(_MatrixHelper matrix) { - _transformMatrix = _transformMatrix * matrix; + _transformMatrix = _transformMatrix! * matrix; } void _scaleTransform(double scaleX, double scaleY) { - _transformMatrix = _transformMatrix._scale(scaleX, scaleY, 0, 0); + _transformMatrix = _transformMatrix!._scale(scaleX, scaleY, 0, 0); } void _translateTransform(double offsetX, double offsetY) { - _transformMatrix = _transformMatrix._translate(offsetX, offsetY); + _transformMatrix = _transformMatrix!._translate(offsetX, offsetY); } void _rotateTransform(double angle) { - _transformMatrix = _transformMatrix._rotate(angle, 0, 0); + _transformMatrix = _transformMatrix!._rotate(angle, 0, 0); } } class _GraphicsState { - _MatrixHelper _transformMatrix; + _MatrixHelper? _transformMatrix; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/graphic_object_data_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/graphic_object_data_collection.dart index 826a5cdda..ef6c9df13 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/graphic_object_data_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/graphic_object_data_collection.dart @@ -7,7 +7,7 @@ class _GraphicObjectDataCollection { } //fields - Queue<_GraphicObjectData> _elements; + late Queue<_GraphicObjectData> _elements; //properties _GraphicObjectData get last => _elements.last; @@ -22,8 +22,8 @@ class _GraphicObjectDataCollection { return _elements.removeLast(); } - double get textLeading { - double result; + double? get textLeading { + double? result; if (last.currentFont != null) { result = last.textLeading; } else { @@ -38,8 +38,8 @@ class _GraphicObjectDataCollection { return result; } - String get currentFont { - String result; + String? get currentFont { + String? result; if (last.currentFont != null) { result = last.currentFont; } else { @@ -55,8 +55,8 @@ class _GraphicObjectDataCollection { return result; } - double get fontSize { - double result; + double? get fontSize { + double? result; if (last.currentFont != null) { result = last.fontSize; } else { @@ -76,21 +76,21 @@ class _GraphicObjectDataCollection { class _GraphicStateCollection { //constructor _GraphicStateCollection() { - _elements = Queue<_GraphicsState>(); + _elements = Queue<_GraphicsState?>(); } //fields - Queue<_GraphicsState> _elements; + late Queue<_GraphicsState?> _elements; //Properties int get count => _elements.length; //Implementation - void _push(_GraphicsState element) { + void _push(_GraphicsState? element) { _elements.addLast(element); } - _GraphicsState _pop() { + _GraphicsState? _pop() { return _elements.removeLast(); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/image_renderer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/image_renderer.dart index 59360d705..51c7753d1 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/image_renderer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/image_renderer.dart @@ -2,9 +2,9 @@ part of pdf; class _ImageRenderer { //Constructor - _ImageRenderer(_PdfRecordCollection contentElements, + _ImageRenderer(_PdfRecordCollection? contentElements, _PdfPageResources resources, double pageBottom, - [_GraphicsObject g]) { + [_GraphicsObject? g]) { final int dpiX = 96; _graphicsObject = _GraphicsObject(); _graphicsState = _GraphicStateCollection(); @@ -13,11 +13,11 @@ class _ImageRenderer { newObject.currentTransformationMatrix = _MatrixHelper(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); final _MatrixHelper transformMatrix = - g != null ? g._transformMatrix : _graphicsObject._transformMatrix; - newObject.currentTransformationMatrix._translate( + g != null ? g._transformMatrix! : _graphicsObject!._transformMatrix!; + newObject.currentTransformationMatrix!._translate( transformMatrix.offsetX / 1.333, transformMatrix.offsetY / 1.333); newObject.drawing2dMatrixCTM = _MatrixHelper(1, 0, 0, 1, 0, 0); - newObject.drawing2dMatrixCTM._translate( + newObject.drawing2dMatrixCTM!._translate( transformMatrix.offsetX / 1.333, transformMatrix.offsetY / 1.333); newObject.documentMatrix = _MatrixHelper( 1.33333333333333 * (dpiX / 96) * transformMatrix.m11, @@ -26,8 +26,8 @@ class _ImageRenderer { -1.33333333333333 * (dpiX / 96) * transformMatrix.m22, 0, pageBottom * transformMatrix.m22); - _objects._push(newObject); - _objects._push(newObject); + _objects!._push(newObject); + _objects!._push(newObject); _contentElements = contentElements; _resources = resources; currentPageHeight = pageBottom; @@ -36,104 +36,105 @@ class _ImageRenderer { } //Fields - _GraphicObjectDataCollection _objects; - _GraphicStateCollection _graphicsState; - _GraphicsObject _graphicsObject; - _PdfRecordCollection _contentElements; - _PdfPageResources _resources; - - double currentPageHeight; - List<_Glyph> imageRenderGlyphList; - _PdfRecordCollection _type3RecordCollection; - bool _isType3Font; - List _symbolChars; - bool _isXGraphics; - int _xobjectGraphicsCount; - int _renderingMode; - bool _textMatrix; - bool _isCurrentPositionChanged; - Offset _currentLocation; - Offset _endTextPosition; - bool isScaledText; - bool _skipRendering; - Map _layersVisibilityDictionary; - int _inlayersCount; - bool _selectablePrintDocument; - double _pageHeight; - bool _isExtractLineCollection; - double _textScaling; - bool _isNextFill; - double pageRotation; - double zoomFactor; - Map _substitutedFontsList; - double _textElementWidth; - List<_TextElement> extractTextElement; + _GraphicObjectDataCollection? _objects; + _GraphicStateCollection? _graphicsState; + _GraphicsObject? _graphicsObject; + _PdfRecordCollection? _contentElements; + _PdfPageResources? _resources; + + double? currentPageHeight; + late List<_Glyph> imageRenderGlyphList; + _PdfRecordCollection? _type3RecordCollection; + late bool _isType3Font; + late List _symbolChars; + late bool _isXGraphics; + late int _xobjectGraphicsCount; + int? _renderingMode; + late bool _textMatrix; + late bool _isCurrentPositionChanged; + Offset? _currentLocation; + late Offset _endTextPosition; + late bool isScaledText; + late bool _skipRendering; + late Map _layersVisibilityDictionary; + late int _inlayersCount; + bool? _selectablePrintDocument; + double? _pageHeight; + late bool _isExtractLineCollection; + double? _textScaling = 100; + late bool _isNextFill; + double? pageRotation; + double? zoomFactor; + Map? _substitutedFontsList; + late double _textElementWidth; + late List<_TextElement> extractTextElement; + late List<_TextElement> _whiteSpace; //Properties - _GraphicObjectData get objects => _objects.last; - _MatrixHelper get textLineMatrix { + _GraphicObjectData get objects => _objects!.last; + _MatrixHelper? get textLineMatrix { return objects.textLineMatrix; } - set textLineMatrix(_MatrixHelper value) { + set textLineMatrix(_MatrixHelper? value) { objects.textLineMatrix = value; } - _MatrixHelper get textMatrix { + _MatrixHelper? get textMatrix { return objects.textMatrix; } - set textMatrix(_MatrixHelper value) { + set textMatrix(_MatrixHelper? value) { objects.textMatrix = value; } - _MatrixHelper get drawing2dMatrixCTM { + _MatrixHelper? get drawing2dMatrixCTM { return objects.drawing2dMatrixCTM; } - set drawing2dMatrixCTM(_MatrixHelper value) { + set drawing2dMatrixCTM(_MatrixHelper? value) { objects.drawing2dMatrixCTM = value; } - _MatrixHelper get currentTransformationMatrix { + _MatrixHelper? get currentTransformationMatrix { return objects.currentTransformationMatrix; } - set currentTransformationMatrix(_MatrixHelper value) { + set currentTransformationMatrix(_MatrixHelper? value) { objects.currentTransformationMatrix = value; } - _MatrixHelper get documentMatrix { + _MatrixHelper? get documentMatrix { return objects.documentMatrix; } - set documentMatrix(_MatrixHelper value) { + set documentMatrix(_MatrixHelper? value) { objects.documentMatrix = value; } - Offset get currentLocation { + Offset? get currentLocation { return _currentLocation; } - set currentLocation(Offset value) { + set currentLocation(Offset? value) { _currentLocation = value; _isCurrentPositionChanged = true; } - double get textLeading => _objects.textLeading; + double? get textLeading => _objects!.textLeading; - set textLeading(double value) { + set textLeading(double? value) { objects.textLeading = value; } - String get currentFont => _objects.currentFont; + String? get currentFont => _objects!.currentFont; - set currentFont(String value) { + set currentFont(String? value) { objects.currentFont = value; } - double get fontSize => _objects.fontSize; + double? get fontSize => _objects!.fontSize; - set fontSize(double value) { + set fontSize(double? value) { objects.fontSize = value; } @@ -159,17 +160,18 @@ class _ImageRenderer { zoomFactor = 1; _substitutedFontsList = {}; extractTextElement = <_TextElement>[]; + _whiteSpace = <_TextElement>[]; } void _renderAsImage() { - final _PdfRecordCollection _recordCollection = + final _PdfRecordCollection? _recordCollection = _isType3Font ? _type3RecordCollection : _contentElements; if (_recordCollection != null) { final List<_PdfRecord> records = _recordCollection._recordCollection; for (int i = 0; i < records.length; i++) { final _PdfRecord record = records[i]; - final String token = record._operatorName; - final List elements = record._operands; + final String token = record._operatorName!; + final List? elements = record._operands; for (int j = 0; j < _symbolChars.length; j++) { if (token.contains(_symbolChars[j])) { token.replaceAll(_symbolChars[j], ''); @@ -178,10 +180,10 @@ class _ImageRenderer { switch (token.trim()) { case 'BDC': { - if (elements.length > 1) { + if (elements!.length > 1) { final String layerID = elements[1].replaceAll('/', ''); if (_layersVisibilityDictionary.containsKey(layerID) && - !_layersVisibilityDictionary[layerID]) { + !_layersVisibilityDictionary[layerID]!) { _skipRendering = true; } if (_skipRendering) { @@ -203,8 +205,8 @@ class _ImageRenderer { case 'q': { final _GraphicObjectData data = _GraphicObjectData(); - if (_objects.count > 0) { - final _GraphicObjectData prevData = _objects.last; + if (_objects!.count > 0) { + final _GraphicObjectData prevData = _objects!.last; data.currentTransformationMatrix = prevData.currentTransformationMatrix; data._mitterLength = prevData._mitterLength; @@ -223,9 +225,9 @@ class _ImageRenderer { if (_isXGraphics) { _xobjectGraphicsCount++; } - _objects._push(data); - final _GraphicsState state = _graphicsObject._save(); - _graphicsState._push(state); + _objects!._push(data); + final _GraphicsState? state = _graphicsObject!._save(); + _graphicsState!._push(state); break; } case 'Q': @@ -233,32 +235,32 @@ class _ImageRenderer { if (_isXGraphics) { _xobjectGraphicsCount--; } - _objects._pop(); - if (_graphicsState.count > 0) { - _graphicsObject._restore(_graphicsState._pop()); + _objects!._pop(); + if (_graphicsState!.count > 0) { + _graphicsObject!._restore(_graphicsState!._pop()!); } break; } case 'Tr': { - _renderingMode = int.parse(elements[0]); + _renderingMode = int.parse(elements![0]); break; } case 'Tm': { - final double a = double.tryParse(elements[0]); - final double b = double.tryParse(elements[1]); - final double c = double.tryParse(elements[2]); - final double d = double.tryParse(elements[3]); - final double e = double.tryParse(elements[4]); - final double f = double.tryParse(elements[5]); + final double a = double.tryParse(elements![0])!; + final double b = double.tryParse(elements[1])!; + final double c = double.tryParse(elements[2])!; + final double d = double.tryParse(elements[3])!; + final double e = double.tryParse(elements[4])!; + final double f = double.tryParse(elements[5])!; _setTextMatrix(a, b, c, d, e, f); if (_textMatrix) { - _graphicsObject._restore(_graphicsState._pop()); + _graphicsObject!._restore(_graphicsState!._pop()!); } - final _GraphicsState state = _graphicsObject._save(); - _graphicsState._push(state); - _graphicsObject + final _GraphicsState? state = _graphicsObject!._save(); + _graphicsState!._push(state); + _graphicsObject! ._multiplyTransform(_MatrixHelper(a, -b, -c, d, e, -f)); currentLocation = Offset(0, 0); _textMatrix = true; @@ -266,12 +268,12 @@ class _ImageRenderer { } case 'cm': { - final double a = double.tryParse(elements[0]); - final double b = double.tryParse(elements[1]); - final double c = double.tryParse(elements[2]); - final double d = double.tryParse(elements[3]); - final double e = double.tryParse(elements[4]); - final double f = double.tryParse(elements[5]); + final double a = double.tryParse(elements![0])!; + final double b = double.tryParse(elements[1])!; + final double c = double.tryParse(elements[2])!; + final double d = double.tryParse(elements[3])!; + final double e = double.tryParse(elements[4])!; + final double f = double.tryParse(elements[5])!; drawing2dMatrixCTM = _setMatrix(a, b, c, d, e, f); break; } @@ -287,10 +289,10 @@ class _ImageRenderer { currentLocation = Offset(0, 0); if (isScaledText) { isScaledText = false; - _graphicsObject._restore(_graphicsState._pop()); + _graphicsObject!._restore(_graphicsState!._pop()!); } if (_textMatrix) { - _graphicsObject._restore(_graphicsState._pop()); + _graphicsObject!._restore(_graphicsState!._pop()!); _textMatrix = false; } if (_renderingMode == 2 && @@ -313,7 +315,7 @@ class _ImageRenderer { break; } if (fontSize != 0) { - _renderTextElementWithSpacing(elements, token); + _renderTextElementWithSpacing(elements!, token); } break; } @@ -323,7 +325,7 @@ class _ImageRenderer { break; } if (fontSize != 0) { - _renderTextElementWithLeading(elements, token); + _renderTextElementWithLeading(elements!, token); } break; } @@ -334,58 +336,58 @@ class _ImageRenderer { _getTextRenderingMatrix(false); objects.textMatrixUpdate = transformMatrix; if (_textScaling != 100) { - final _GraphicsState state = _graphicsObject._save(); - _graphicsState._push(state); - _graphicsObject._scaleTransform(_textScaling / 100, 1); + final _GraphicsState? state = _graphicsObject!._save(); + _graphicsState!._push(state); + _graphicsObject!._scaleTransform(_textScaling! / 100, 1); isScaledText = true; currentLocation = Offset( - currentLocation.dx / (_textScaling / 100), - currentLocation.dy); + currentLocation!.dx / (_textScaling! / 100), + currentLocation!.dy); } - _renderTextElementWithLeading(elements, token); + _renderTextElementWithLeading(elements!, token); break; } case 'Tf': { - _renderFont(elements); + _renderFont(elements!); break; } case 'TD': { currentLocation = Offset( - currentLocation.dx + double.tryParse(elements[0]), - currentLocation.dy - double.tryParse(elements[1])); + currentLocation!.dx + double.tryParse(elements![0])!, + currentLocation!.dy - double.tryParse(elements[1])!); _moveToNextLineWithLeading(elements); break; } case 'Td': { - final double dx = double.tryParse(elements[0]); - final double dy = double.tryParse(elements[1]); + final double dx = double.tryParse(elements![0])!; + final double dy = double.tryParse(elements[1])!; currentLocation = - Offset(currentLocation.dx + dx, currentLocation.dy - dy); + Offset(currentLocation!.dx + dx, currentLocation!.dy - dy); _moveToNextLine(dx, dy); break; } case 'TL': { - textLeading = -double.tryParse(elements[0]); + textLeading = -double.tryParse(elements![0])!; break; } case 'Tw': { - _getWordSpacing(elements); + _getWordSpacing(elements!); break; } case 'Tc': { - _getCharacterSpacing(elements); + _getCharacterSpacing(elements!); break; } case 'Tz': { - _getScalingFactor(elements); + _getScalingFactor(elements!); break; } case 'Do': @@ -393,7 +395,7 @@ class _ImageRenderer { if (_skipRendering) { break; } - _getXObject(elements); + _getXObject(elements!); break; } case 're': @@ -406,10 +408,10 @@ class _ImageRenderer { 'f') { _isNextFill = true; } - if (!(drawing2dMatrixCTM.m11 == 0 && - drawing2dMatrixCTM.m21 == 0 && + if (!(drawing2dMatrixCTM!.m11 == 0 && + drawing2dMatrixCTM!.m21 == 0 && _isNextFill)) { - _getClipRectangle(elements); + _getClipRectangle(elements!); } break; } @@ -419,14 +421,14 @@ class _ImageRenderer { if (_skipRendering) { break; } - final _MatrixHelper initialmatrix = documentMatrix; + final _MatrixHelper initialmatrix = documentMatrix!; final _MatrixHelper currentCTM = _MatrixHelper( - drawing2dMatrixCTM.m11, - drawing2dMatrixCTM.m12, - drawing2dMatrixCTM.m21, - drawing2dMatrixCTM.m22, - drawing2dMatrixCTM.offsetX, - drawing2dMatrixCTM.offsetY); + drawing2dMatrixCTM!.m11, + drawing2dMatrixCTM!.m12, + drawing2dMatrixCTM!.m21, + drawing2dMatrixCTM!.m22, + drawing2dMatrixCTM!.offsetX, + drawing2dMatrixCTM!.offsetY); final _MatrixHelper result = currentCTM * initialmatrix; final _MatrixHelper transformMatrix = _MatrixHelper( result.m11, @@ -439,13 +441,13 @@ class _ImageRenderer { _MatrixHelper(1, 0, 0, 1, 0, 0); graphicsTransformMatrix = graphicsTransformMatrix * transformMatrix; - _graphicsObject._transformMatrix = + _graphicsObject!._transformMatrix = _MatrixHelper(1, 0, 0, 1, 0, 0) * transformMatrix; break; } case 'W': case 'W*': - _graphicsObject._transformMatrix = _MatrixHelper(1, 0, 0, 1, 0, 0); + _graphicsObject!._transformMatrix = _MatrixHelper(1, 0, 0, 1, 0, 0); break; default: break; @@ -456,13 +458,13 @@ class _ImageRenderer { _MatrixHelper _getTextRenderingMatrix(bool isPath) { _MatrixHelper mat = _MatrixHelper( - fontSize * (objects._horizontalScaling / 100), + fontSize! * (objects._horizontalScaling! / 100), 0, 0, - (isPath ? fontSize : (-fontSize)), + (isPath ? fontSize! : (-fontSize!)), 0, - (isPath ? objects.rise : (fontSize + objects.rise))); - mat *= textLineMatrix * currentTransformationMatrix; + (isPath ? objects.rise! : (fontSize! + objects.rise!)) as double); + mat *= textLineMatrix! * currentTransformationMatrix!; return mat; } @@ -475,17 +477,17 @@ class _ImageRenderer { } void _getClipRectangle(List rectangle) { - double x = double.tryParse(rectangle[0]); - final double y = double.tryParse(rectangle[1]); - double height = double.tryParse(rectangle[3]); - if (x < 0 && height < 0 && _isNextFill) { - if (-(height) >= currentPageHeight) { + double x = double.tryParse(rectangle[0])!; + final double y = double.tryParse(rectangle[1])!; + double? height = double.tryParse(rectangle[3]); + if (x < 0 && height! < 0 && _isNextFill) { + if (-(height) >= currentPageHeight!) { x = 0; height = 0; } } _isNextFill = false; - _currentLocation = Offset(x, y + height); + _currentLocation = Offset(x, y + height!); } void _renderFont(List fontElements) { @@ -497,28 +499,28 @@ class _ImageRenderer { } } fontSize = double.tryParse(fontElements[i + 1]); - if (_resources.containsKey(currentFont)) { + if (_resources!.containsKey(currentFont)) { final _FontStructure structure = - _resources[currentFont] as _FontStructure; + _resources![currentFont!] as _FontStructure; if (structure._isStandardFont) { - structure._createStandardFont(fontSize); + structure._createStandardFont(fontSize!); } else if (structure._isStandardCJKFont) { - structure._createStandardCJKFont(fontSize); + structure._createStandardCJKFont(fontSize!); } } } void _drawNewLine() { _isCurrentPositionChanged = true; - if ((-textLeading) != 0) { + if ((-textLeading!) != 0) { _currentLocation = Offset( - _currentLocation.dx, - (-textLeading) < 0 - ? _currentLocation.dy - (-textLeading) - : _currentLocation.dy + (-textLeading)); + _currentLocation!.dx, + (-textLeading!) < 0 + ? _currentLocation!.dy - (-textLeading!) + : _currentLocation!.dy + (-textLeading!)); } else { _currentLocation = - Offset(_currentLocation.dx, _currentLocation.dy + fontSize); + Offset(_currentLocation!.dx, _currentLocation!.dy + fontSize!); } } @@ -529,13 +531,13 @@ class _ImageRenderer { void _getXObject(List xobjectElement) { final String key = xobjectElement[0].replaceAll('/', ''); - if (_resources.containsKey(key)) { - final dynamic resource = _resources[key]; + if (_resources!.containsKey(key)) { + final dynamic resource = _resources![key]; if (resource is _XObjectElement) { - List<_Glyph> xObjectGlyphs; + List<_Glyph>? xObjectGlyphs; final _XObjectElement xObjectElement = resource; xObjectElement._isExtractTextLine = _isExtractLineCollection; - if (_selectablePrintDocument) { + if (_selectablePrintDocument!) { xObjectElement._isPrintSelected = _selectablePrintDocument; xObjectElement._pageHeight = _pageHeight; } @@ -549,13 +551,13 @@ class _ImageRenderer { _graphicsState = result['graphicStates']; _objects = result['objects']; xObjectGlyphs = result['glyphList']; - final List<_TextElement> tempExtractTextElement = + final List<_TextElement>? tempExtractTextElement = result['extractTextElement']; if (tempExtractTextElement != null && tempExtractTextElement.isNotEmpty) { extractTextElement.addAll(tempExtractTextElement); } - imageRenderGlyphList.addAll(xObjectGlyphs); + imageRenderGlyphList.addAll(xObjectGlyphs!); xObjectGlyphs.clear(); } } @@ -564,10 +566,10 @@ class _ImageRenderer { void _renderTextElementWithLeading( List textElements, String tokenType) { String text = textElements.join(); - if (_resources.containsKey(currentFont)) { + if (_resources!.containsKey(currentFont)) { final _FontStructure structure = - _resources[currentFont] as _FontStructure; - structure.isSameFont = _resources.isSameFont(); + _resources![currentFont!] as _FontStructure; + structure.isSameFont = _resources!.isSameFont(); structure.fontSize = fontSize; if (!structure.isEmbedded && @@ -579,76 +581,93 @@ class _ImageRenderer { structure._isStandardFont) { text = structure.getEncodedText(text, true); } else { - text = structure.decodeTextExtraction(text, _resources.isSameFont()); + text = structure.decodeTextExtraction(text, _resources!.isSameFont()); + } + final _TextElement element = _TextElement(text, documentMatrix); + element.fontStyle = structure.fontStyle!; + element.fontName = structure.fontName!; + element.fontSize = fontSize!; + element.textScaling = _textScaling; + element.fontEncoding = structure.fontEncoding; + element.fontGlyphWidths = structure.fontGlyphWidths; + element.defaultGlyphWidth = structure.defaultGlyphWidth; + element._text = text; + element.unicodeCharMapTable = structure.unicodeCharMapTable; + final Map glyphWidths = structure.fontGlyphWidths!; + element.characterMapTable = structure.characterMapTable; + element.reverseMapTable = structure.reverseMapTable; + element.structure = structure; + element.isEmbeddedFont = structure.isEmbedded; + element.currentTransformationMatrix = currentTransformationMatrix; + element.textLineMatrix = textMatrix; + element._rise = objects.rise; + element.transformMatrix = documentMatrix; + element.documentMatrix = documentMatrix; + element.fontId = currentFont; + element.octDecMapTable = structure.octDecMapTable; + element.textHorizontalScaling = objects._horizontalScaling; + element.zapfPostScript = structure.zapfPostScript; + element.lineWidth = objects._mitterLength; + element.renderingMode = _renderingMode; + element.pageRotation = pageRotation; + element.zoomFactor = zoomFactor; + element.substitutedFontsList = _substitutedFontsList; + element.wordSpacing = objects.wordSpacing; + element.characterSpacing = objects.characterSpacing; + final _MatrixHelper tempTextMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); + tempTextMatrix.type = _MatrixTypes.identity; + if (_isCurrentPositionChanged) { + _isCurrentPositionChanged = false; + _endTextPosition = currentLocation!; + _textElementWidth = element._render( + _graphicsObject, + Offset(_endTextPosition.dx, + _endTextPosition.dy + ((-textLeading!) / 4)), + _textScaling, + glyphWidths, + structure._type1GlyphHeight, + structure.differenceTable, + structure.differencesDictionary, + structure.differenceEncoding, + tempTextMatrix); + } else { + _endTextPosition = Offset( + _endTextPosition.dx + _textElementWidth, _endTextPosition.dy); + _textElementWidth = element._render( + _graphicsObject, + Offset( + _endTextPosition.dx, _endTextPosition.dy + (-textLeading! / 4)), + _textScaling, + glyphWidths, + structure._type1GlyphHeight, + structure.differenceTable, + structure.differencesDictionary, + structure.differenceEncoding, + tempTextMatrix); } if (!structure.isWhiteSpace) { - final _TextElement element = _TextElement(text, documentMatrix); - element.fontStyle = structure.fontStyle; - element.fontName = structure.fontName; - element.fontSize = fontSize; - element.textScaling = _textScaling; - element.fontEncoding = structure.fontEncoding; - element.fontGlyphWidths = structure.fontGlyphWidths; - element.defaultGlyphWidth = structure.defaultGlyphWidth; - element._text = text; - element.unicodeCharMapTable = structure.unicodeCharMapTable; - final Map glyphWidths = structure.fontGlyphWidths; - element.characterMapTable = structure.characterMapTable; - element.reverseMapTable = structure.reverseMapTable; - element.structure = structure; - element.isEmbeddedFont = structure.isEmbedded; - element.currentTransformationMatrix = currentTransformationMatrix; - element.textLineMatrix = textMatrix; - element._rise = objects.rise; - element.transformMatrix = documentMatrix; - element.documentMatrix = documentMatrix; - element.fontId = currentFont; - element.octDecMapTable = structure.octDecMapTable; - element.textHorizontalScaling = objects._horizontalScaling; - element.zapfPostScript = structure.zapfPostScript; - element.lineWidth = objects._mitterLength; - element.renderingMode = _renderingMode; - element.pageRotation = pageRotation; - element.zoomFactor = zoomFactor; - element.substitutedFontsList = _substitutedFontsList; - element.wordSpacing = objects.wordSpacing; - element.characterSpacing = objects.characterSpacing; - final _MatrixHelper tempTextMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); - tempTextMatrix.type = _MatrixTypes.identity; - if (_isCurrentPositionChanged) { - _isCurrentPositionChanged = false; - _endTextPosition = currentLocation; - _textElementWidth = element._render( - _graphicsObject, - Offset(_endTextPosition.dx, - _endTextPosition.dy + ((-textLeading) / 4)), - _textScaling, - glyphWidths, - structure._type1GlyphHeight, - structure.differenceTable, - structure.differencesDictionary, - structure.differenceEncoding, - tempTextMatrix); - imageRenderGlyphList.addAll(element.textElementGlyphList); - } else { - _endTextPosition = Offset( - _endTextPosition.dx + _textElementWidth, _endTextPosition.dy); - _textElementWidth = element._render( - _graphicsObject, - Offset(_endTextPosition.dx, - _endTextPosition.dy + (-textLeading / 4)), - _textScaling, - glyphWidths, - structure._type1GlyphHeight, - structure.differenceTable, - structure.differencesDictionary, - structure.differenceEncoding, - tempTextMatrix); - imageRenderGlyphList.addAll(element.textElementGlyphList); + if (_whiteSpace.isNotEmpty && + extractTextElement.isNotEmpty && + _whiteSpace.length == 1) { + if (extractTextElement[extractTextElement.length - 1] + .textLineMatrix! + .offsetY == + element.textLineMatrix!.offsetY && + _whiteSpace[0].textLineMatrix!.offsetY == + element.textLineMatrix!.offsetY) { + element.textElementGlyphList + .insert(0, _whiteSpace[0].textElementGlyphList[0]); + extractTextElement.add(_whiteSpace[0]); + } + _whiteSpace = <_TextElement>[]; } + imageRenderGlyphList.addAll(element.textElementGlyphList); if (_isExtractLineCollection) { extractTextElement.add(element); } + _whiteSpace = <_TextElement>[]; + } else { + _whiteSpace.add(element); } } } @@ -657,117 +676,127 @@ class _ImageRenderer { List textElements, String tokenType) { List decodedList = []; final String text = textElements.join(); - if (_resources.containsKey(currentFont)) { - final _FontStructure structure = _resources[currentFont]; - structure.isSameFont = _resources.isSameFont(); + if (_resources!.containsKey(currentFont)) { + final _FontStructure structure = _resources![currentFont!]; + structure.isSameFont = _resources!.isSameFont(); structure.fontSize = fontSize; - List characterSpacings; + List? characterSpacings; if (!structure.isEmbedded && structure._isStandardCJKFont && structure.font != null) { decodedList = - structure.decodeCjkTextExtractionTJ(text, _resources.isSameFont()); + structure.decodeCjkTextExtractionTJ(text, _resources!.isSameFont()); } else { decodedList = - structure.decodeTextExtractionTJ(text, _resources.isSameFont()); + structure.decodeTextExtractionTJ(text, _resources!.isSameFont()); } final List bytes = - utf8.encode(structure.getEncodedText(text, _resources.isSameFont())); + utf8.encode(structure.getEncodedText(text, _resources!.isSameFont())); final Map encodedTextBytes = {}; int z = 0; for (int j = 0; j < bytes.length; j = j + 2) { encodedTextBytes[z] = bytes[j]; z++; } + final _TextElement element = _TextElement(text, documentMatrix); + element.fontStyle = structure.fontStyle!; + element.fontName = structure.fontName!; + element.fontSize = fontSize!; + element.textScaling = _textScaling; + element.encodedTextBytes = encodedTextBytes; + element.fontEncoding = structure.fontEncoding; + element.fontGlyphWidths = structure.fontGlyphWidths; + element.defaultGlyphWidth = structure.defaultGlyphWidth; + element.renderingMode = _renderingMode; + element.unicodeCharMapTable = structure.unicodeCharMapTable; + final Map glyphWidths = structure.fontGlyphWidths!; + element.cidToGidReverseMapTable = structure.cidToGidReverseMapTable; + element.characterMapTable = structure.characterMapTable; + element.reverseMapTable = structure.reverseMapTable; + // //element.fontfile2Glyph = structure.glyphFontFile2; + element.structure = structure; + element.isEmbeddedFont = structure.isEmbedded; + element.currentTransformationMatrix = currentTransformationMatrix; + element.textLineMatrix = textMatrix; + element._rise = objects.rise; + element.transformMatrix = documentMatrix; + element.documentMatrix = documentMatrix; + element.fontId = currentFont; + element.octDecMapTable = structure.octDecMapTable; + element.textHorizontalScaling = objects._horizontalScaling; + element.zapfPostScript = structure.zapfPostScript; + element.lineWidth = objects._mitterLength; + element.renderingMode = _renderingMode; + element.pageRotation = pageRotation; + element.zoomFactor = zoomFactor; + element.substitutedFontsList = _substitutedFontsList; + if (structure.flags != null) { + element.fontFlag = structure.flags!.value!.toInt(); + } + element.wordSpacing = objects.wordSpacing; + element.characterSpacing = objects.characterSpacing; + final _MatrixHelper tempTextMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); + tempTextMatrix.type = _MatrixTypes.identity; + if (_isCurrentPositionChanged) { + _isCurrentPositionChanged = false; + _endTextPosition = currentLocation!; + } else { + _endTextPosition = Offset( + _endTextPosition.dx + _textElementWidth, _endTextPosition.dy); + } + _textElementWidth = element._renderWithSpacing( + _graphicsObject, + Offset(_endTextPosition.dx, _endTextPosition.dy - fontSize!), + decodedList, + characterSpacings, + _textScaling, + glyphWidths, + structure._type1GlyphHeight, + structure.differenceTable, + structure.differencesDictionary, + structure.differenceEncoding, + tempTextMatrix); if (!structure.isWhiteSpace) { - final List bytes = utf8 - .encode(structure.getEncodedText(text, _resources.isSameFont())); - final Map encodedTextBytes = {}; - int z = 0; - for (int j = 0; j < bytes.length; j = j + 2) { - encodedTextBytes[z] = bytes[j]; - z++; - } - final _TextElement element = _TextElement(text, documentMatrix); - element.fontStyle = structure.fontStyle; - element.fontName = structure.fontName; - element.fontSize = fontSize; - element.textScaling = _textScaling; - element.encodedTextBytes = encodedTextBytes; - element.fontEncoding = structure.fontEncoding; - element.fontGlyphWidths = structure.fontGlyphWidths; - element.defaultGlyphWidth = structure.defaultGlyphWidth; - element.renderingMode = _renderingMode; - element.unicodeCharMapTable = structure.unicodeCharMapTable; - final Map glyphWidths = structure.fontGlyphWidths; - element.cidToGidReverseMapTable = structure.cidToGidReverseMapTable; - element.characterMapTable = structure.characterMapTable; - element.reverseMapTable = structure.reverseMapTable; - // //element.fontfile2Glyph = structure.glyphFontFile2; - element.structure = structure; - element.isEmbeddedFont = structure.isEmbedded; - element.currentTransformationMatrix = currentTransformationMatrix; - element.textLineMatrix = textMatrix; - element._rise = objects.rise; - element.transformMatrix = documentMatrix; - element.documentMatrix = documentMatrix; - element.fontId = currentFont; - element.octDecMapTable = structure.octDecMapTable; - element.textHorizontalScaling = objects._horizontalScaling; - element.zapfPostScript = structure.zapfPostScript; - element.lineWidth = objects._mitterLength; - element.renderingMode = _renderingMode; - element.pageRotation = pageRotation; - element.zoomFactor = zoomFactor; - element.substitutedFontsList = _substitutedFontsList; - if (structure.flags != null) { - element.fontFlag = structure.flags.value.toInt(); - } - element.wordSpacing = objects.wordSpacing; - element.characterSpacing = objects.characterSpacing; - final _MatrixHelper tempTextMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); - tempTextMatrix.type = _MatrixTypes.identity; - if (_isCurrentPositionChanged) { - _isCurrentPositionChanged = false; - _endTextPosition = currentLocation; - } else { - _endTextPosition = Offset( - _endTextPosition.dx + _textElementWidth, _endTextPosition.dy); + if (_whiteSpace.isNotEmpty && + extractTextElement.isNotEmpty && + _whiteSpace.length == 1) { + if (extractTextElement[extractTextElement.length - 1] + .textLineMatrix! + .offsetY == + element.textLineMatrix!.offsetY && + _whiteSpace[0].textLineMatrix!.offsetY == + element.textLineMatrix!.offsetY) { + element.textElementGlyphList + .insert(0, _whiteSpace[0].textElementGlyphList[0]); + extractTextElement.add(_whiteSpace[0]); + } + _whiteSpace = <_TextElement>[]; } - _textElementWidth = element._renderWithSpacing( - _graphicsObject, - Offset(_endTextPosition.dx, _endTextPosition.dy - fontSize), - decodedList, - characterSpacings, - _textScaling, - glyphWidths, - structure._type1GlyphHeight, - structure.differenceTable, - structure.differencesDictionary, - structure.differenceEncoding, - tempTextMatrix); imageRenderGlyphList.addAll(element.textElementGlyphList); if (_isExtractLineCollection) { extractTextElement.add(element); } + _whiteSpace = <_TextElement>[]; + } else { + _whiteSpace.add(element); } } } void _moveToNextLineWithCurrentTextLeading() { - _moveToNextLine(0, textLeading); + _moveToNextLine(0, textLeading!); } void _moveToNextLineWithLeading(List elements) { - final double dx = double.tryParse(elements[0]); - final double dy = double.tryParse(elements[1]); + final double dx = double.tryParse(elements[0])!; + final double dy = double.tryParse(elements[1])!; textLeading = dy; _moveToNextLine(dx, dy); } void _moveToNextLine(double tx, double ty) { textLineMatrix = - textMatrix = _MatrixHelper(1, 0, 0, 1, tx, ty) * textLineMatrix; + textMatrix = _MatrixHelper(1, 0, 0, 1, tx, ty) * textLineMatrix!; } void _setTextMatrix( @@ -778,13 +807,13 @@ class _ImageRenderer { _MatrixHelper _setMatrix( double a, double b, double c, double d, double e, double f) { currentTransformationMatrix = _MatrixHelper(a, b, c, d, e, f) * - _objects.last.currentTransformationMatrix; + _objects!.last.currentTransformationMatrix!; return _MatrixHelper( - currentTransformationMatrix.m11, - currentTransformationMatrix.m12, - currentTransformationMatrix.m21, - currentTransformationMatrix.m22, - currentTransformationMatrix.offsetX, - currentTransformationMatrix.offsetY); + currentTransformationMatrix!.m11, + currentTransformationMatrix!.m12, + currentTransformationMatrix!.m21, + currentTransformationMatrix!.m22, + currentTransformationMatrix!.offsetX, + currentTransformationMatrix!.offsetY); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/matched_item.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/matched_item.dart index ebf0c1cb8..b394e47e1 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/matched_item.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/matched_item.dart @@ -11,13 +11,13 @@ class MatchedItem { //Fields /// The searched text. - String text; + late String text; /// Rectangle bounds of the searched text. - Rect bounds; + late Rect bounds; /// Page number of the searched text. - int pageIndex; + late int pageIndex; } /// Defines the constants that specify the option for text search. diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/matrix_helper.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/matrix_helper.dart index a3974d7d6..40cae36e5 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/matrix_helper.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/matrix_helper.dart @@ -19,13 +19,13 @@ class _MatrixHelper { _MatrixHelper(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); //Fields - double m11; - double m12; - double m21; - double m22; - double offsetX; - double offsetY; - _MatrixTypes type; + late double m11; + late double m12; + late double m21; + late double m22; + late double offsetX; + late double offsetY; + late _MatrixTypes type; //Implementation _MatrixHelper operator *(_MatrixHelper matrix) { @@ -97,7 +97,7 @@ class _MatrixHelper { } else if (typeIndex == 4) { return _MatrixTypes.unknown; } else { - throw ArgumentError.value(typeIndex); + throw ArgumentError.value(typeIndex, 'typeIndex', 'Invalid Type'); } } @@ -105,22 +105,14 @@ class _MatrixHelper { switch (type) { case _MatrixTypes.identity: return 0; - break; case _MatrixTypes.translation: return 1; - break; case _MatrixTypes.scaling: return 2; - break; case _MatrixTypes.scalingAndTranslation: return 3; - break; - case _MatrixTypes.unknown: - return 4; - break; default: - throw ArgumentError.value(type); - break; + return 4; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/page_resource_loader.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/page_resource_loader.dart index 7f9d8d3ad..f73138fa6 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/page_resource_loader.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/page_resource_loader.dart @@ -9,29 +9,29 @@ class _PageResourceLoader { _PdfPageResources getPageResources(PdfPage page) { _PdfPageResources pageResources = _PdfPageResources(); double resourceNumber = 0; - _PdfDictionary resources = page._getResources(); + _PdfDictionary? resources = page._getResources(); pageResources = updatePageResources(pageResources, getFontResources(resources, page)); pageResources = updatePageResources(pageResources, getFormResources(resources)); while (resources != null && resources.containsKey(_DictionaryProperties.xObject)) { - _PdfDictionary tempResources = resources; - _PdfDictionary xobjects; + _PdfDictionary? tempResources = resources; + _PdfDictionary? xobjects; if (resources[_DictionaryProperties.xObject] is _PdfReferenceHolder) { xobjects = (resources[_DictionaryProperties.xObject] as _PdfReferenceHolder) - .object as _PdfDictionary; + .object as _PdfDictionary?; } else { - xobjects = resources[_DictionaryProperties.xObject] as _PdfDictionary; + xobjects = resources[_DictionaryProperties.xObject] as _PdfDictionary?; } - resources = xobjects[_DictionaryProperties.resources] as _PdfDictionary; - for (final dynamic objValue in xobjects._items.values) { - _PdfDictionary xobjectDictionary; + resources = xobjects![_DictionaryProperties.resources] as _PdfDictionary?; + for (final dynamic objValue in xobjects._items!.values) { + _PdfDictionary? xobjectDictionary; if (objValue is _PdfReferenceHolder) { - xobjectDictionary = objValue.object as _PdfDictionary; + xobjectDictionary = objValue.object as _PdfDictionary?; } else { - xobjectDictionary = objValue as _PdfDictionary; + xobjectDictionary = objValue as _PdfDictionary?; } if (xobjectDictionary != null && xobjectDictionary.containsKey(_DictionaryProperties.resources)) { @@ -40,15 +40,15 @@ class _PageResourceLoader { final _PdfReferenceHolder resourceReference = xobjectDictionary[_DictionaryProperties.resources] as _PdfReferenceHolder; - if (resourceNumber != resourceReference.reference._objNum) { - resources = resourceReference.object as _PdfDictionary; - resourceNumber = resourceReference.reference._objNum.toDouble(); + if (resourceNumber != resourceReference.reference!._objNum) { + resources = resourceReference.object as _PdfDictionary?; + resourceNumber = resourceReference.reference!._objNum!.toDouble(); } else { continue; } } else { resources = xobjectDictionary[_DictionaryProperties.resources] - as _PdfDictionary; + as _PdfDictionary?; } if (resources == tempResources) { resources = null; @@ -70,15 +70,15 @@ class _PageResourceLoader { return pageResources; } - Map getFormResources(_PdfDictionary resourceDictionary) { - final Map pageResources = {}; + Map getFormResources(_PdfDictionary? resourceDictionary) { + final Map pageResources = {}; if (resourceDictionary != null && resourceDictionary.containsKey(_DictionaryProperties.xObject)) { - final _IPdfPrimitive primitive = + final _IPdfPrimitive? primitive = resourceDictionary[_DictionaryProperties.xObject]; - _PdfDictionary xObjects; + _PdfDictionary? xObjects; if (primitive is _PdfReferenceHolder) { - final _IPdfPrimitive holder = primitive.object; + final _IPdfPrimitive? holder = primitive.object; if (holder != null && holder is _PdfDictionary) { xObjects = holder; } @@ -86,22 +86,22 @@ class _PageResourceLoader { xObjects = primitive; } if (xObjects != null) { - xObjects._items.forEach((_PdfName key, _IPdfPrimitive value) { - _PdfDictionary xObjectDictionary; + xObjects._items!.forEach((_PdfName? key, _IPdfPrimitive? value) { + _PdfDictionary? xObjectDictionary; if (value is _PdfReferenceHolder && value.object is _PdfDictionary) { - xObjectDictionary = value.object; + xObjectDictionary = value.object as _PdfDictionary?; } else if (value is _PdfDictionary) { xObjectDictionary = value; } if (xObjectDictionary != null && xObjectDictionary.containsKey(_DictionaryProperties.subtype)) { - final _IPdfPrimitive subType = + final _IPdfPrimitive? subType = xObjectDictionary[_DictionaryProperties.subtype]; if (subType is _PdfName && (subType._name == _DictionaryProperties.form || (subType._name != _DictionaryProperties.image && - !pageResources.containsKey(key._name)))) { - pageResources[key._name] = + !pageResources.containsKey(key!._name)))) { + pageResources[key!._name] = _XObjectElement(xObjectDictionary, key._name); } } @@ -113,33 +113,33 @@ class _PageResourceLoader { // Collects all the fonts in the page in a dictionary. // Returns dictionary containing font name and the font. - Map getFontResources(_PdfDictionary resourceDictionary, - [PdfPage page]) { - final Map pageResources = {}; + Map getFontResources(_PdfDictionary? resourceDictionary, + [PdfPage? page]) { + final Map pageResources = {}; if (resourceDictionary != null) { - _IPdfPrimitive fonts = resourceDictionary[_DictionaryProperties.font]; + _IPdfPrimitive? fonts = resourceDictionary[_DictionaryProperties.font]; if (fonts != null) { - _PdfDictionary fontsDictionary; + _PdfDictionary? fontsDictionary; if (fonts is _PdfReferenceHolder) { - fontsDictionary = fonts.object as _PdfDictionary; + fontsDictionary = fonts.object as _PdfDictionary?; } else { fontsDictionary = fonts as _PdfDictionary; } if (fontsDictionary != null) { - fontsDictionary._items.forEach((_PdfName k, _IPdfPrimitive v) { + fontsDictionary._items!.forEach((_PdfName? k, _IPdfPrimitive? v) { if (v is _PdfReferenceHolder) { if (v.reference != null) { - pageResources[k._name] = + pageResources[k!._name] = _FontStructure(v.object, v.reference.toString()); } else { - pageResources[k._name] = _FontStructure(v.object); + pageResources[k!._name] = _FontStructure(v.object); } } else { if (v is _PdfDictionary) { - pageResources[k._name] = _FontStructure(v); + pageResources[k!._name] = _FontStructure(v); } else { - pageResources[k._name] = _FontStructure( + pageResources[k!._name] = _FontStructure( v, (v as _PdfReferenceHolder).reference.toString()); } } @@ -147,22 +147,22 @@ class _PageResourceLoader { } } if (page != null) { - final _IPdfPrimitive parentPage = + final _IPdfPrimitive? parentPage = page._dictionary[_DictionaryProperties.parent]; if (parentPage != null) { - final _IPdfPrimitive parentRef = + final _IPdfPrimitive? parentRef = (parentPage as _PdfReferenceHolder).object; final _PdfResources parentResources = - _PdfResources(parentRef as _PdfDictionary); + _PdfResources(parentRef as _PdfDictionary?); fonts = parentResources[_DictionaryProperties.font]; if (fonts != null) { - final _PdfDictionary fontsDictionary = fonts as _PdfDictionary; + final _PdfDictionary? fontsDictionary = fonts as _PdfDictionary; if (fontsDictionary != null) { - fontsDictionary._items.forEach((_PdfName k, _IPdfPrimitive v) { + fontsDictionary._items!.forEach((_PdfName? k, _IPdfPrimitive? v) { if (v is _PdfDictionary) { - pageResources[k._name] = (v as _PdfReferenceHolder).object; + pageResources[k!._name] = (v as _PdfReferenceHolder).object; } - pageResources[k._name] = _FontStructure( + pageResources[k!._name] = _FontStructure( v, (v as _PdfReferenceHolder).reference.toString()); }); } @@ -175,8 +175,8 @@ class _PageResourceLoader { // Updates the resources in the page _PdfPageResources updatePageResources( - _PdfPageResources pageResources, Map objects) { - objects.forEach((String k, Object v) { + _PdfPageResources pageResources, Map objects) { + objects.forEach((String? k, Object? v) { pageResources.add(k, v); }); return pageResources; @@ -186,18 +186,17 @@ class _PageResourceLoader { class _PdfPageResources { // Initializes the new instance of the class [PdfPageResources]. _PdfPageResources() { - resources = {}; + resources = {}; } //Fields - Map resources; - final Map _fontCollection = - {}; + late Map resources; + final Map _fontCollection = + {}; dynamic operator [](String key) => _returnValue(key); dynamic _returnValue(String key) { - ArgumentError.checkNotNull(key); if (resources.containsKey(key)) { return resources[key]; } else { @@ -208,8 +207,8 @@ class _PdfPageResources { /// Returns true if the FontCollection has same font face. bool isSameFont() { int i = 0; - _fontCollection.forEach((String k, _FontStructure v) { - _fontCollection.forEach((String k1, _FontStructure v1) { + _fontCollection.forEach((String? k, _FontStructure v) { + _fontCollection.forEach((String? k1, _FontStructure v1) { if (v.fontName != v1.fontName) { i = 1; } @@ -223,7 +222,7 @@ class _PdfPageResources { } // Adds the resource with the specified name. - void add(String resourceName, Object resource) { + void add(String? resourceName, Object? resource) { if (resourceName == 'ProcSet') { return; } @@ -236,7 +235,7 @@ class _PdfPageResources { } // Returns true if the key already exists. - bool containsKey(String key) { + bool containsKey(String? key) { return resources.containsKey(key); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/parser/content_lexer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/parser/content_lexer.dart index 73a0663f9..2e14a1cfd 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/parser/content_lexer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/parser/content_lexer.dart @@ -2,14 +2,14 @@ part of pdf; class _ContentLexer { // Constructor - _ContentLexer(List contentStream) { + _ContentLexer(List? contentStream) { _contentStream = contentStream; _operatorParams = StringBuffer(); } // fields - List _contentStream; - StringBuffer _operatorParams; + List? _contentStream; + StringBuffer? _operatorParams; String _currentChar = '0'; String _nextChar = '0'; int _charPointer = 0; @@ -73,7 +73,7 @@ class _ContentLexer { } void _resetToken() { - _operatorParams.clear(); + _operatorParams!.clear(); } String _moveToNextChar() { @@ -101,7 +101,7 @@ class _ContentLexer { if (value) { return _nextChar; } - if (_contentStream.length <= _charPointer) { + if (_contentStream!.length <= _charPointer) { if (_nextChar == '81' || (_currentChar == '68' && _nextChar == '111')) { _currentChar = _nextChar; _nextChar = '65535'; @@ -111,14 +111,14 @@ class _ContentLexer { _nextChar = '65535'; } else { _currentChar = _nextChar; - _nextChar = _contentStream[_charPointer++].toString(); + _nextChar = _contentStream![_charPointer++].toString(); if (_currentChar == '13') { if (_nextChar == '10') { _currentChar = _nextChar; - if (_contentStream.length <= _charPointer) { + if (_contentStream!.length <= _charPointer) { _nextChar = '65535'; } else { - _nextChar = _contentStream[_charPointer++].toString(); + _nextChar = _contentStream![_charPointer++].toString(); } } else { _currentChar = '10'; @@ -149,14 +149,14 @@ class _ContentLexer { _PdfTokenType _getNumber() { String ch = _currentChar; if (ch == '43' || ch == '45') { - _operatorParams.write(String.fromCharCode(int.parse(_currentChar))); + _operatorParams!.write(String.fromCharCode(int.parse(_currentChar))); ch = _getNextChar(); } while (true) { if (isDigit(String.fromCharCode(int.parse(ch)))) { - _operatorParams.write(String.fromCharCode(int.parse(_currentChar))); + _operatorParams!.write(String.fromCharCode(int.parse(_currentChar))); } else if (ch == '46') { - _operatorParams.write(String.fromCharCode(int.parse(_currentChar))); + _operatorParams!.write(String.fromCharCode(int.parse(_currentChar))); } else { break; } @@ -167,7 +167,7 @@ class _ContentLexer { String _consumeValue() { final String data = String.fromCharCode(int.parse(_currentChar)); - _operatorParams.write(data); + _operatorParams!.write(data); if (_isContainsArtifacts && _operatorParams.toString().contains('/Contents') && !_isContentEnded) { @@ -195,14 +195,14 @@ class _ContentLexer { while (true) { if (String.fromCharCode(int.parse(beginChar)) == '(') { literal = _getLiterals(ch); - _operatorParams.write(literal); + _operatorParams!.write(literal); ch = String.fromCharCode(int.parse(_getNextChar())); break; } else { if (String.fromCharCode(int.parse(ch)) == '(') { ch = _consumeValue(); literal = _getLiterals(ch); - _operatorParams.write(literal); + _operatorParams!.write(literal); ch = _getNextChar(); continue; } else if (String.fromCharCode(int.parse(ch)) == ']') { @@ -297,12 +297,12 @@ class _ContentLexer { } String _getNextInlineChar() { - if (_contentStream.length <= _charPointer) { + if (_contentStream!.length <= _charPointer) { _currentChar = '65535'; _nextChar = '65535'; } else { _currentChar = _nextChar; - _nextChar = _contentStream[_charPointer++].toString(); + _nextChar = _contentStream![_charPointer++].toString(); if (_currentChar == '13') { if (_nextChar == '10') { _currentChar = '13'; @@ -315,19 +315,19 @@ class _ContentLexer { } String _getNextCharforInlineStream() { - if (_contentStream.length <= _charPointer) { + if (_contentStream!.length <= _charPointer) { _currentChar = '65535'; _nextChar = '65535'; } else { _currentChar = _nextChar; - _nextChar = _contentStream[_charPointer++].toString(); + _nextChar = _contentStream![_charPointer++].toString(); if (_currentChar == '13') { if (_nextChar == '10') { _currentChar = _nextChar; - if (_contentStream.length <= _charPointer) { + if (_contentStream!.length <= _charPointer) { _nextChar = '65535'; } else { - _nextChar = _contentStream[_charPointer++].toString(); + _nextChar = _contentStream![_charPointer++].toString(); } } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/parser/content_parser.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/parser/content_parser.dart index 97f12e176..834537021 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/parser/content_parser.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/parser/content_parser.dart @@ -2,7 +2,7 @@ part of pdf; class _ContentParser { // Constructor - _ContentParser(List contentStream) { + _ContentParser(List? contentStream) { _lexer = _ContentLexer(contentStream); _operatorParams = _lexer._operatorParams; _recordCollection = _PdfRecordCollection(); @@ -10,11 +10,11 @@ class _ContentParser { } // Fields - _ContentLexer _lexer; - StringBuffer _operatorParams; - _PdfRecordCollection _recordCollection; + late _ContentLexer _lexer; + StringBuffer? _operatorParams; + _PdfRecordCollection? _recordCollection; bool _isByteOperands = false; - bool _isTextExtractionProcess; + late bool _isTextExtractionProcess; final bool _conformanceEnabled = false; static const List _operators = [ @@ -100,7 +100,7 @@ class _ContentParser { ]; // Implementation - _PdfRecordCollection _readContent() { + _PdfRecordCollection? _readContent() { _parseObject(_PdfTokenType.eof); if (_isTextExtractionProcess) { _lexer._dispose(); @@ -145,16 +145,13 @@ class _ContentParser { case _PdfTokenType.operators: if (_operatorParams.toString() == 'true') { _operands.add(_operatorParams.toString()); - break; } else if (_operatorParams.toString() == 'ID') { _createRecord(_operands); _operands.clear(); _consumeValue(_operands); - break; } else { _createRecord(_operands); _operands.clear(); - break; } break; @@ -175,7 +172,7 @@ class _ContentParser { return _lexer._getNextToken(); } - void _createRecord(List operands, [List inlineImageBytes]) { + void _createRecord(List operands, [List? inlineImageBytes]) { final String operand = _operatorParams.toString(); final int occurence = _operators.indexOf(operand); if (occurence < 0) { @@ -191,15 +188,15 @@ class _ContentParser { if (!_isByteOperands) { record = _PdfRecord(operand, op); } else { - final List imBytes = inlineImageBytes.isNotEmpty - ? List(inlineImageBytes.length) + final List imBytes = inlineImageBytes!.isNotEmpty + ? List.filled(inlineImageBytes.length, 0, growable: true) : []; if (inlineImageBytes.isNotEmpty) { List.copyRange(imBytes, 0, inlineImageBytes); } record = _PdfRecord.fromImage(operand, imBytes); } - _recordCollection._add(record); + _recordCollection!._add(record); } void _consumeValue(List operands) { @@ -246,10 +243,11 @@ class _ContentParser { String.fromCharCode( int.parse(thirdChar[thirdChar.length - 1])) == 'S') { - _operatorParams.clear(); - _operatorParams + _operatorParams!.clear(); + _operatorParams! .write(String.fromCharCode(int.parse(currentChar))); - _operatorParams.write(String.fromCharCode(int.parse(nextChar))); + _operatorParams! + .write(String.fromCharCode(int.parse(nextChar))); _isByteOperands = true; _createRecord(operands, _inlineImageBytes); _isByteOperands = false; @@ -316,10 +314,11 @@ class _ContentParser { if (String.fromCharCode(int.parse(thirdChar)) == 'Q' || thirdChar == '65535' || String.fromCharCode(int.parse(thirdChar)) == 'S') { - _operatorParams.clear(); - _operatorParams + _operatorParams!.clear(); + _operatorParams! .write(String.fromCharCode(int.parse(currentChar))); - _operatorParams.write(String.fromCharCode(int.parse(nextChar))); + _operatorParams! + .write(String.fromCharCode(int.parse(nextChar))); _isByteOperands = true; _createRecord(operands, _inlineImageBytes); _isByteOperands = false; @@ -353,7 +352,7 @@ class _PdfRecordCollection { } // Fields - List<_PdfRecord> _recordCollection; + late List<_PdfRecord> _recordCollection; //Properties int get _count => _recordCollection.length; @@ -377,8 +376,8 @@ class _PdfRecord { } // Fields - String _operatorName; - List _operands; + String? _operatorName; + List? _operands; // ignore: unused_field - List _inlineImageBytes; + List? _inlineImageBytes; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/pdf_text_extractor.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/pdf_text_extractor.dart index 99f80fc40..7cb756f4a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/pdf_text_extractor.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/pdf_text_extractor.dart @@ -15,36 +15,32 @@ class PdfTextExtractor { /// document.dispose(); /// ``` PdfTextExtractor(PdfDocument document) { - if (document != null) { - if (!document._isLoadedDocument) { - ArgumentError.value(document, 'document', - 'document instance is not a loaded PDF document'); - } - _document = document; - } else { - ArgumentError.checkNotNull(document); + if (!document._isLoadedDocument) { + ArgumentError.value(document, 'document', + 'document instance is not a loaded PDF document'); } + _document = document; _initialize(); } //Fields - PdfDocument _document; - List _symbolChars; - String _currentFont; - double _fontSize; - PdfPage _currentPage; - _PageResourceLoader _resourceLoader; - int _currentPageIndex; - bool _isLayout; - double _characterSpacing; - double _wordSpacing; - _MatrixHelper _textLineMatrix; - _MatrixHelper _textMatrix; - _MatrixHelper _currentTextMatrix; - Rect _tempBoundingRectangle; - bool _hasLeading; - _MatrixHelper _currentTransformationMatrix; - bool _hasBDC; + late PdfDocument _document; + late List _symbolChars; + String? _currentFont; + double? _fontSize; + PdfPage? _currentPage; + late _PageResourceLoader _resourceLoader; + late int _currentPageIndex; + bool _isLayout = false; + double _characterSpacing = 0; + double _wordSpacing = 0; + _MatrixHelper? _textLineMatrix; + _MatrixHelper? _textMatrix; + _MatrixHelper? _currentTextMatrix; + Rect? _tempBoundingRectangle; + bool _hasLeading = false; + late _MatrixHelper _currentTransformationMatrix; + bool _hasBDC = false; //Public methods /// Extract text from an existing PDF document @@ -65,7 +61,8 @@ class PdfTextExtractor { /// //Dispose the document. /// document.dispose(); /// ``` - String extractText({int startPageIndex, int endPageIndex, bool layoutText}) { + String extractText( + {int? startPageIndex, int? endPageIndex, bool? layoutText}) { _isLayout = layoutText ?? false; return _extractText(startPageIndex, endPageIndex); } @@ -88,7 +85,7 @@ class PdfTextExtractor { /// //Dispose the document. /// document.dispose(); /// ``` - List extractTextLines({int startPageIndex, int endPageIndex}) { + List extractTextLines({int? startPageIndex, int? endPageIndex}) { return _extractTextLines(startPageIndex, endPageIndex); } @@ -117,45 +114,41 @@ class PdfTextExtractor { /// document.dispose(); /// ``` List findText(List searchString, - {int startPageIndex, int endPageIndex, TextSearchOption searchOption}) { + {int? startPageIndex, + int? endPageIndex, + TextSearchOption? searchOption}) { return _findText(searchString, startPageIndex, endPageIndex, searchOption); } //Implementation void _checkPageNumber(int pageNumber) { if (pageNumber < 0 || pageNumber >= _document.pages.count) { - throw ArgumentError.value(pageNumber, 'Index out of range'); + throw ArgumentError.value(pageNumber, 'pageNumber', 'Index out of range'); } } void _initialize() { _symbolChars = ['(', ')', '[', ']', '<', '>']; _resourceLoader = _PageResourceLoader(); - _characterSpacing = 0; - _wordSpacing = 0; _currentTextMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); _textLineMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); _textMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); _tempBoundingRectangle = Rect.fromLTWH(0, 0, 0, 0); - _hasLeading = false; _currentTransformationMatrix = _MatrixHelper(1, 0, 0, 1, 0, 0); - _hasLeading = false; - _hasBDC = false; - _isLayout = false; } - String _extractText(int startPageIndex, int endPageIndex) { + String _extractText(int? startPageIndex, int? endPageIndex) { if (startPageIndex == null) { if (endPageIndex != null) { - throw ArgumentError.value( - endPageIndex, 'Invalid argument. start page index cannot be null'); + throw ArgumentError.value(endPageIndex, 'endPageIndex', + 'Invalid argument. start page index cannot be null'); } else { return _extractTextFromRange(0, _document.pages.count - 1); } } else if (endPageIndex == null) { _checkPageNumber(startPageIndex); - final String text = _getText(_document.pages[startPageIndex]); - return text ?? ''; + _currentPageIndex = startPageIndex; + return _getText(_document.pages[startPageIndex]); } else { _checkPageNumber(startPageIndex); _checkPageNumber(endPageIndex); @@ -163,16 +156,17 @@ class PdfTextExtractor { } } - List _extractTextLines(int startPageIndex, int endPageIndex) { + List _extractTextLines(int? startPageIndex, int? endPageIndex) { if (startPageIndex == null) { if (endPageIndex != null) { - throw ArgumentError.value( - endPageIndex, 'Invalid argument. start page index cannot be null'); + throw ArgumentError.value(endPageIndex, 'endPageIndex', + 'Invalid argument. start page index cannot be null'); } else { return _extractTextLineFromRange(0, _document.pages.count - 1); } } else if (endPageIndex == null) { _checkPageNumber(startPageIndex); + _currentPageIndex = startPageIndex; return _getTextLine(_document.pages[startPageIndex]); } else { _checkPageNumber(startPageIndex); @@ -181,18 +175,19 @@ class PdfTextExtractor { } } - List _findText(List searchString, int startPageIndex, - int endPageIndex, TextSearchOption searchOption) { + List _findText(List searchString, int? startPageIndex, + int? endPageIndex, TextSearchOption? searchOption) { if (startPageIndex == null) { if (endPageIndex != null) { - throw ArgumentError.value( - endPageIndex, 'Invalid argument. start page index cannot be null'); + throw ArgumentError.value(endPageIndex, 'endPageIndex', + 'Invalid argument. start page index cannot be null'); } else { return _findTextFromRange( 0, _document.pages.count - 1, searchString, searchOption); } } else if (endPageIndex == null) { _checkPageNumber(startPageIndex); + _currentPageIndex = startPageIndex; return _searchInBackground( _document.pages[startPageIndex], searchString, searchOption); } else { @@ -207,8 +202,7 @@ class PdfTextExtractor { String resultText = ''; for (int i = startPageIndex; i <= endPageIndex; i++) { final String text = _getText(_document.pages[i]); - resultText = - resultText + (i > startPageIndex ? '\r\n' : '') + (text ?? ''); + resultText = resultText + (i > startPageIndex ? '\r\n' : '') + text; } return resultText; } @@ -227,7 +221,7 @@ class PdfTextExtractor { } List _findTextFromRange(int startPageIndex, int endPageIndex, - List searchString, TextSearchOption searchOption) { + List searchString, TextSearchOption? searchOption) { final List result = []; for (int i = startPageIndex; i <= endPageIndex; i++) { _currentPageIndex = i; @@ -243,23 +237,23 @@ class PdfTextExtractor { String _getText(PdfPage page) { _currentPage = page; _fontSize = 0; - String resultantText; page._isTextExtraction = true; final bool isChanged = _checkPageDictionary(page); final bool isContentChanged = _checkContentArray(page); - final _PdfRecordCollection recordCollection = _getRecordCollection(page); - _PdfPageResources pageResources = _resourceLoader.getPageResources(page); - resultantText = _isLayout + final _PdfRecordCollection? recordCollection = _getRecordCollection(page); + final _PdfPageResources pageResources = + _resourceLoader.getPageResources(page); + String resultantText = _isLayout ? _renderTextAsLayout(recordCollection, pageResources) : _renderText(recordCollection, pageResources); - recordCollection._recordCollection.clear(); + if (recordCollection != null) { + recordCollection._recordCollection.clear(); + } pageResources.resources.clear(); - if (pageResources._fontCollection != null && - pageResources._fontCollection.isNotEmpty) { + if (pageResources._fontCollection.isNotEmpty) { pageResources._fontCollection.clear(); } - pageResources = null; - if (resultantText != null && resultantText != '') { + if (resultantText != '') { resultantText = _skipEscapeSequence(resultantText); } page._contents._isChanged = isContentChanged; @@ -275,7 +269,8 @@ class PdfTextExtractor { pdfPage._isTextExtraction = true; final bool isChanged = _checkPageDictionary(pdfPage); final bool isContentChanged = _checkContentArray(pdfPage); - final _PdfRecordCollection recordCollection = _getRecordCollection(pdfPage); + final _PdfRecordCollection? recordCollection = + _getRecordCollection(pdfPage); final _PdfPageResources pageResources = _resourceLoader.getPageResources(pdfPage); final _ImageRenderer renderer = _ImageRenderer(recordCollection, @@ -284,10 +279,10 @@ class PdfTextExtractor { renderer._renderAsImage(); renderer._isExtractLineCollection = false; int i = 0; - double width = 0; - double height = 0; - double dx = 0; - double dy = 0; + double? width = 0; + double? height = 0; + double? dx = 0; + double? dy = 0; int offsetY = 0; double yPos = 0; String pagestring = ''; @@ -304,8 +299,7 @@ class PdfTextExtractor { yPos = renderer.imageRenderGlyphList[i].boundingRect.top; if ((i != 0 && yPos.toInt() != offsetY && - renderer.extractTextElement[k].renderedText != '' && - renderer.extractTextElement[k].renderedText != ' ') || + renderer.extractTextElement[k].renderedText != '') || (i == renderer.imageRenderGlyphList.length - 1)) { offsetY = yPos.toInt(); if (textLine.wordCollection.isNotEmpty) { @@ -318,27 +312,26 @@ class PdfTextExtractor { final _TextElement textElement = renderer.extractTextElement[k]; final List words = textElement.renderedText.split(' '); textElement._text = ' '; - TextWord textwords; + TextWord? textwords; + List glyphs = []; for (int x = 0; x < words.length; x++) { - if (pagestring.contains(words[x]) && - words[x] != null && - words[x].isNotEmpty) { - textwords = TextWord._(); + if (pagestring.contains(words[x]) && words[x].isNotEmpty) { + glyphs = []; String tempText = ''; int lastIndex = i; for (int m = i; m < i + words[x].length; m++) { - final TextGlyph textGlyph = TextGlyph._(); - textGlyph.fontName = textElement.fontName; - textGlyph.fontSize = textElement.fontSize; - textGlyph.fontStyle = textElement.fontStyle; - textGlyph.text = renderer.imageRenderGlyphList[m].toUnicode; + final Rect tempBounds = + renderer.imageRenderGlyphList[m].boundingRect; + final Rect glyphBounds = Rect.fromLTRB(tempBounds.left, + tempBounds.top, tempBounds.right, tempBounds.bottom); + final TextGlyph textGlyph = TextGlyph._( + renderer.imageRenderGlyphList[m].toUnicode, + textElement.fontName, + textElement.fontStyle, + glyphBounds, + textElement.fontSize); tempText += textGlyph.text; - textGlyph.bounds = Rect.fromLTRB( - renderer.imageRenderGlyphList[m].boundingRect.left, - renderer.imageRenderGlyphList[m].boundingRect.top, - renderer.imageRenderGlyphList[m].boundingRect.right, - renderer.imageRenderGlyphList[m].boundingRect.bottom); - textwords.glyphs.add(textGlyph); + glyphs.add(textGlyph); lastIndex = m; if (words[x] == tempText) { break; @@ -359,15 +352,17 @@ class PdfTextExtractor { renderer.imageRenderGlyphList[lastIndex].boundingRect.width; } i = lastIndex + 1; - textwords.bounds = Rect.fromLTWH(dx, dy, width - dx, height); - textwords.text = words[x]; - textwords.fontName = textElement.fontName; - textwords.fontSize = textElement.fontSize; - textwords.fontStyle = textElement.fontStyle; + textwords = TextWord._( + words[x], + textElement.fontName, + textElement.fontStyle, + glyphs, + Rect.fromLTWH(dx, dy, width - dx, height), + textElement.fontSize); textLine.wordCollection.add(textwords); } textElement._text = words[x]; - if (textElement._text != null && textElement._text != '') { + if (textElement._text != '') { if (x < words.length - 1) { if (i != 0) { final Map tempResult = _addSpace(textwords, @@ -442,7 +437,7 @@ class PdfTextExtractor { } List _searchInBackground( - PdfPage page, List searchString, TextSearchOption searchOption) { + PdfPage page, List searchString, TextSearchOption? searchOption) { final List result = []; final String pageText = _getText(page); if (pageText != '') { @@ -467,7 +462,7 @@ class PdfTextExtractor { page._isTextExtraction = true; final bool isChanged = _checkPageDictionary(page); final bool isContentChanged = _checkContentArray(page); - final _PdfRecordCollection recordCollection = + final _PdfRecordCollection? recordCollection = _getRecordCollection(page); final _PdfPageResources pageResources = _resourceLoader.getPageResources(page); @@ -565,9 +560,9 @@ class PdfTextExtractor { return textIndex - adjustableLength; } - _PdfRecordCollection _getRecordCollection(PdfPage page) { - _PdfRecordCollection recordCollection; - final List combinedData = page.layers._combineContent(true); + _PdfRecordCollection? _getRecordCollection(PdfPage page) { + _PdfRecordCollection? recordCollection; + final List? combinedData = page.layers._combineContent(true); if (combinedData != null) { final _ContentParser parser = _ContentParser(combinedData); parser._isTextExtractionProcess = true; @@ -578,7 +573,7 @@ class PdfTextExtractor { } bool _checkPageDictionary(PdfPage page) { - return page._dictionary._isChanged != null && page._dictionary._isChanged + return page._dictionary._isChanged != null && page._dictionary._isChanged! ? true : false; } @@ -586,44 +581,41 @@ class PdfTextExtractor { bool _checkContentArray(PdfPage page) { bool isContentChanged = false; if (page._dictionary.containsKey(_DictionaryProperties.contents)) { - final _IPdfPrimitive contents = + final _IPdfPrimitive? contents = page._dictionary[_DictionaryProperties.contents]; if (contents is _PdfReferenceHolder) { final _PdfReferenceHolder holder = contents; - final _IPdfPrimitive primitive = holder.object; + final _IPdfPrimitive? primitive = holder.object; if (primitive is _PdfArray) { - isContentChanged = primitive._isChanged ? true : false; + isContentChanged = primitive._isChanged! ? true : false; } else if (primitive is _PdfStream) { - isContentChanged = primitive._isChanged ? true : false; + isContentChanged = primitive._isChanged! ? true : false; } } else if (contents is _PdfArray) { - isContentChanged = contents._isChanged ? true : false; + isContentChanged = contents._isChanged! ? true : false; } } return isContentChanged; } Map _addSpace( - TextWord textwords, + TextWord? textwords, _ImageRenderer renderer, _TextElement textElement, int i, - double dx, - double dy, - double width, - double height) { - textwords = TextWord._(); - final TextGlyph textGlyph = TextGlyph._(); - textGlyph.text = renderer.imageRenderGlyphList[i].toUnicode; - textGlyph.fontName = textElement.fontName; - textGlyph.fontSize = textElement.fontSize; - textGlyph.fontStyle = textElement.fontStyle; - textGlyph.bounds = Rect.fromLTWH( - renderer.imageRenderGlyphList[i].boundingRect.left, - renderer.imageRenderGlyphList[i].boundingRect.top, - renderer.imageRenderGlyphList[i].boundingRect.width, - renderer.imageRenderGlyphList[i].boundingRect.height); - textwords.glyphs.add(textGlyph); + double? dx, + double? dy, + double? width, + double? height) { + final Rect tempBounds = renderer.imageRenderGlyphList[i].boundingRect; + final Rect glyphBounds = Rect.fromLTWH( + tempBounds.left, tempBounds.top, tempBounds.width, tempBounds.height); + final TextGlyph textGlyph = TextGlyph._( + renderer.imageRenderGlyphList[i].toUnicode, + textElement.fontName, + textElement.fontStyle, + glyphBounds, + textElement.fontSize); dx = renderer.imageRenderGlyphList[i].boundingRect.left; dy = renderer.imageRenderGlyphList[i].boundingRect.top; height = renderer.imageRenderGlyphList[i].boundingRect.height; @@ -634,13 +626,14 @@ class PdfTextExtractor { width = (renderer.imageRenderGlyphList[i].boundingRect.left - dx) + renderer.imageRenderGlyphList[i].boundingRect.width; } - textwords.bounds = Rect.fromLTWH(dx, dy, width, height); - textwords.text = ' '; - textwords.fontName = textElement.fontName; - textwords.fontSize = textElement.fontSize; - textwords.fontStyle = textElement.fontStyle; return { - 'word': textwords, + 'word': TextWord._( + ' ', + textElement.fontName, + textElement.fontStyle, + [textGlyph], + Rect.fromLTWH(dx, dy, width, height), + textElement.fontSize), 'dx': dx, 'dy': dy, 'width': width, @@ -750,10 +743,10 @@ class PdfTextExtractor { bool isSameFontName = true; bool isSameFontSize = true; bool isSameFontStyle = true; - String fontName = ''; - double fontSize = 0; + String? fontName = ''; + double? fontSize = 0; textLine.pageIndex = _currentPageIndex; - List fontStyle = [PdfFontStyle.regular]; + List? fontStyle = [PdfFontStyle.regular]; textLine.bounds = Rect.fromLTWH( renderer.imageRenderGlyphList[lineStartIndex].boundingRect.left, renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.top, @@ -768,22 +761,22 @@ class PdfTextExtractor { fontSize = glyph.fontSize; fontStyle = glyph.fontStyle; } - textLine.text += glyph.toUnicode; + textLine.text = textLine.text + glyph.toUnicode; if (fontName == glyph.fontFamily && isSameFontName) { - textLine.fontName = fontName; + textLine.fontName = fontName!; } else { isSameFontName = false; textLine.fontName = ''; } if (fontSize == glyph.fontSize && isSameFontSize) { - textLine.fontSize = fontSize; + textLine.fontSize = fontSize!; } else { isSameFontSize = false; textLine.fontSize = 0; } if (fontStyle == glyph.fontStyle && isSameFontStyle) { - textLine.fontStyle = fontStyle; + textLine.fontStyle = fontStyle!; } else { isSameFontStyle = false; textLine.fontStyle = [PdfFontStyle.regular]; @@ -802,15 +795,15 @@ class PdfTextExtractor { } String _renderText( - _PdfRecordCollection recordCollection, _PdfPageResources pageResources) { + _PdfRecordCollection? recordCollection, _PdfPageResources pageResources) { String resultantText = ''; if (recordCollection != null && recordCollection._recordCollection.isNotEmpty) { final List<_PdfRecord> records = recordCollection._recordCollection; for (int i = 0; i < records.length; i++) { final _PdfRecord record = records[i]; - final String token = record._operatorName; - final List elements = record._operands; + final String token = record._operatorName!; + final List? elements = record._operands; for (int j = 0; j < _symbolChars.length; j++) { if (token.contains(_symbolChars[j])) { token.replaceAll(_symbolChars[j], ''); @@ -824,7 +817,7 @@ class PdfTextExtractor { } case 'Tf': { - _renderFont(elements, pageResources); + _renderFont(elements!, pageResources); break; } case 'ET': @@ -836,8 +829,8 @@ class PdfTextExtractor { case 'TJ': case '\'': { - final String resultText = - _renderTextElement(elements, token, pageResources); + final String? resultText = + _renderTextElement(elements!, token, pageResources); if (resultText != null) { resultantText += resultText; } @@ -848,8 +841,8 @@ class PdfTextExtractor { } case 'Do': { - final String result = - _getXObject(resultantText, elements, pageResources); + final String? result = + _getXObject(resultantText, elements!, pageResources); if (result != null && result != '') { resultantText += result; } @@ -864,19 +857,19 @@ class PdfTextExtractor { } String _renderTextAsLayout( - _PdfRecordCollection recordCollection, _PdfPageResources pageResources) { - double currentMatrixY = 0; - double prevMatrixY = 0; - double currentY = 0; - double prevY = 0; + _PdfRecordCollection? recordCollection, _PdfPageResources pageResources) { + double? currentMatrixY = 0; + double? prevMatrixY = 0; + double? currentY = 0; + double? prevY = 0; double differenceX = 0; - String currentText = ''; + String? currentText = ''; bool hasTj = false; bool hasTm = false; _hasBDC = false; String resultantText = ''; - double textLeading = 0; - double horizontalScaling = 100; + double? textLeading = 0; + double? horizontalScaling = 100; bool hasNoSpacing = false; bool spaceBetweenWord = false; _tempBoundingRectangle = Rect.fromLTWH(0, 0, 0, 0); @@ -885,8 +878,8 @@ class PdfTextExtractor { final List<_PdfRecord> records = recordCollection._recordCollection; for (int i = 0; i < records.length; i++) { final _PdfRecord record = records[i]; - final String token = record._operatorName; - final List elements = record._operands; + final String token = record._operatorName!; + final List? elements = record._operands; for (int j = 0; j < _symbolChars.length; j++) { if (token.contains(_symbolChars[j])) { token.replaceAll(_symbolChars[j], ''); @@ -895,29 +888,29 @@ class PdfTextExtractor { switch (token.trim()) { case 'Tw': { - _wordSpacing = double.tryParse(elements[0]); + _wordSpacing = double.tryParse(elements![0])!; break; } case 'Tc': { - _characterSpacing = double.tryParse(elements[0]); + _characterSpacing = double.tryParse(elements![0])!; break; } case 'Tm': { - final double a = double.tryParse(elements[0]); - final double b = double.tryParse(elements[1]); - final double c = double.tryParse(elements[2]); - final double d = double.tryParse(elements[3]); - final double e = double.tryParse(elements[4]); - final double f = double.tryParse(elements[5]); + final double a = double.tryParse(elements![0])!; + final double b = double.tryParse(elements[1])!; + final double c = double.tryParse(elements[2])!; + final double d = double.tryParse(elements[3])!; + final double e = double.tryParse(elements[4])!; + final double f = double.tryParse(elements[5])!; _textLineMatrix = _textMatrix = _MatrixHelper(a, b, c, d, e, f); - if (_textMatrix.offsetY == _textLineMatrix.offsetY && - _textMatrix.offsetX != _textLineMatrix.offsetX) { + if (_textMatrix!.offsetY == _textLineMatrix!.offsetY && + _textMatrix!.offsetX != _textLineMatrix!.offsetX) { _textLineMatrix = _textMatrix; } - if (_textLineMatrix.offsetY != _currentTextMatrix.offsetY || - ((_textLineMatrix.offsetX != _currentTextMatrix.offsetX) && + if (_textLineMatrix!.offsetY != _currentTextMatrix!.offsetY || + ((_textLineMatrix!.offsetX != _currentTextMatrix!.offsetX) && _hasBDC && !hasTj)) { _tempBoundingRectangle = Rect.fromLTWH(0, 0, 0, 0); @@ -927,14 +920,14 @@ class PdfTextExtractor { } case 'TL': { - textLeading = -double.tryParse(elements[0]); + textLeading = -double.tryParse(elements![0])!; break; } case 'cm': { - currentMatrixY = double.tryParse(elements[5]); - final int current = currentMatrixY.toInt(); - final int prev = prevMatrixY.toInt(); + currentMatrixY = double.tryParse(elements![5]); + final int current = currentMatrixY!.toInt(); + final int prev = prevMatrixY!.toInt(); final int locationY = (current - prev) ~/ 10; if ((current != prev) && hasTm && @@ -952,18 +945,18 @@ class PdfTextExtractor { } case 'TD': { - textLeading = double.tryParse(elements[1]); + textLeading = double.tryParse(elements![1]); _textLineMatrix = _textMatrix = _MatrixHelper( 1, 0, 0, 1, - double.tryParse(elements[0]), - double.tryParse(elements[1])) * - _textLineMatrix; - if (_textLineMatrix.offsetY != _currentTextMatrix.offsetY || + double.tryParse(elements[0])!, + double.tryParse(elements[1])!) * + _textLineMatrix!; + if (_textLineMatrix!.offsetY != _currentTextMatrix!.offsetY || (_hasBDC && - _textLineMatrix.offsetX != _currentTextMatrix.offsetX && + _textLineMatrix!.offsetX != _currentTextMatrix!.offsetX && !hasTj)) { _tempBoundingRectangle = Rect.fromLTWH(0, 0, 0, 0); _hasBDC = false; @@ -977,20 +970,22 @@ class PdfTextExtractor { 0, 0, 1, - double.tryParse(elements[0]), - double.tryParse(elements[1])) * - _textLineMatrix; - if (_textLineMatrix.offsetY != _currentTextMatrix.offsetY || + double.tryParse(elements![0])!, + double.tryParse(elements[1])!) * + _textLineMatrix!; + if (_textLineMatrix!.offsetY != _currentTextMatrix!.offsetY || (_hasBDC && - _textLineMatrix.offsetX != _currentTextMatrix.offsetX)) { + _textLineMatrix!.offsetX != + _currentTextMatrix!.offsetX)) { _tempBoundingRectangle = Rect.fromLTWH(0, 0, 0, 0); _hasBDC = false; } - if ((_textLineMatrix.offsetX - _currentTextMatrix.offsetX) > 0 && + if ((_textLineMatrix!.offsetX - _currentTextMatrix!.offsetX) > + 0 && !spaceBetweenWord && hasTj) { differenceX = - (_textLineMatrix.offsetX - _currentTextMatrix.offsetX) + (_textLineMatrix!.offsetX - _currentTextMatrix!.offsetX) .toDouble(); spaceBetweenWord = true; } @@ -998,7 +993,7 @@ class PdfTextExtractor { } case 'Tz': { - horizontalScaling = double.tryParse(elements[0]); + horizontalScaling = double.tryParse(elements![0]); break; } case 'BT': @@ -1009,18 +1004,19 @@ class PdfTextExtractor { case 'T*': { _textLineMatrix = _textMatrix = - _MatrixHelper(1, 0, 0, 1, 0, textLeading) * _textLineMatrix; + _MatrixHelper(1, 0, 0, 1, 0, textLeading!) * _textLineMatrix!; break; } case 'Tf': { - _renderFont(elements, pageResources); + _renderFont(elements!, pageResources); break; } case 'ET': { final double endTextPosition = - (_textLineMatrix.offsetX - _tempBoundingRectangle.right) / 10; + (_textLineMatrix!.offsetX - _tempBoundingRectangle!.right) / + 10; if (_hasLeading && endTextPosition == 0 && hasNoSpacing) { resultantText += ' '; _tempBoundingRectangle = Rect.fromLTWH(0, 0, 0, 0); @@ -1032,19 +1028,19 @@ class PdfTextExtractor { case 'TJ': { final String currentToken = token.trim(); - currentY = _textMatrix.offsetY; + currentY = _textMatrix!.offsetY; double difference = 0; - if (_fontSize >= 10) { - difference = ((currentY - prevY) / 10).round().toDouble(); + if (_fontSize! >= 10) { + difference = ((currentY - prevY!) / 10).round().toDouble(); } else { difference = - ((currentY - prevY) / _fontSize).round().toDouble(); + ((currentY - prevY!) / _fontSize!).round().toDouble(); } if (difference < 0) { difference = -difference; } if (spaceBetweenWord) { - if (differenceX > _fontSize) { + if (differenceX > _fontSize!) { differenceX = 0; } spaceBetweenWord = false; @@ -1055,11 +1051,11 @@ class PdfTextExtractor { } currentText = currentToken == 'TJ' ? _renderTextElementTJ( - elements, token, pageResources, horizontalScaling) - : _renderTextElement(elements, token, pageResources); + elements!, token, pageResources, horizontalScaling) + : _renderTextElement(elements!, token, pageResources); _currentTextMatrix = _textLineMatrix; prevY = currentY; - resultantText += currentText; + resultantText += currentText!; _textMatrix = _textLineMatrix; if (currentToken == 'TJ') { _hasBDC = true; @@ -1068,14 +1064,14 @@ class PdfTextExtractor { } case '\'': { - currentY = _textMatrix.offsetY; + currentY = _textMatrix!.offsetY; hasNoSpacing = false; double difference = 0; - if (_fontSize >= 10) { - difference = ((currentY - prevY) / 10).round().toDouble(); + if (_fontSize! >= 10) { + difference = ((currentY - prevY!) / 10).round().toDouble(); } else { difference = - ((currentY - prevY) / _fontSize).round().toDouble(); + ((currentY - prevY!) / _fontSize!).round().toDouble(); } if (difference < 0) { difference = -difference; @@ -1086,23 +1082,23 @@ class PdfTextExtractor { } prevY = currentY; final int currentXPosition = - (_textLineMatrix.offsetX).toInt().toSigned(64); + _textLineMatrix!.offsetX.toInt().toSigned(64); final int prevXPosition = - (_currentTextMatrix.offsetX).toInt().toSigned(64); + _currentTextMatrix!.offsetX.toInt().toSigned(64); if ((prevXPosition - currentXPosition) > 0) { hasNoSpacing = true; } _textLineMatrix = _textMatrix = - _MatrixHelper(1, 0, 0, 1, 0, textLeading) * _textLineMatrix; - currentText = _renderTextElement(elements, token, pageResources); + _MatrixHelper(1, 0, 0, 1, 0, textLeading!) * _textLineMatrix!; + currentText = _renderTextElement(elements!, token, pageResources); _currentTextMatrix = _textLineMatrix; - resultantText += currentText; + resultantText += currentText!; break; } case 'Do': { - final String result = - _getXObject(resultantText, elements, pageResources); + final String? result = + _getXObject(resultantText, elements!, pageResources); if (result != null && result != '') { resultantText += result; } @@ -1145,30 +1141,28 @@ class PdfTextExtractor { _fontSize = double.tryParse(elements[i + 1]); if (resources.containsKey(_currentFont)) { final _FontStructure structure = - resources[_currentFont] as _FontStructure; + resources[_currentFont!] as _FontStructure; if (structure._isStandardFont) { - structure._createStandardFont(_fontSize); + structure._createStandardFont(_fontSize!); } else if (structure._isStandardCJKFont) { - structure._createStandardCJKFont(_fontSize); + structure._createStandardCJKFont(_fontSize!); } } } String _renderTextElementTJ(List elements, String tokenType, - _PdfPageResources pageResources, double horizontalScaling) { + _PdfPageResources pageResources, double? horizontalScaling) { List decodedList = []; final String text = elements.join(); String tempText = ''; if (pageResources.containsKey(_currentFont)) { - _FontStructure fontStructure; - final dynamic returnValue = pageResources[_currentFont]; + _FontStructure? fontStructure; + final dynamic returnValue = pageResources[_currentFont!]; if (returnValue != null && returnValue is _FontStructure) { fontStructure = returnValue; } - fontStructure.isTextExtraction = true; - if (fontStructure != null) { - fontStructure.fontSize = _fontSize; - } + fontStructure!.isTextExtraction = true; + fontStructure.fontSize = _fontSize; if (!fontStructure.isEmbedded && fontStructure._isStandardCJKFont && fontStructure.font != null) { @@ -1185,15 +1179,15 @@ class PdfTextExtractor { return tempText; } - String _renderTextFromTJ(List decodedList, double horizontalScaling, - _FontStructure fontStructure) { + String _renderTextFromTJ(List decodedList, double? horizontalScaling, + _FontStructure? fontStructure) { String extractedText = ''; decodedList.forEach((String word) { - final double space = double.tryParse(word); + final double? space = double.tryParse(word); if (space != null) { _textLineMatrix = - _updateTextMatrixWithSpacing(space, horizontalScaling); - if ((_textLineMatrix.offsetX - _textMatrix.offsetX).toInt() > 1 && + _updateTextMatrixWithSpacing(space, horizontalScaling!); + if ((_textLineMatrix!.offsetX - _textMatrix!.offsetX).toInt() > 1 && !_hasBDC) { extractedText += ' '; } @@ -1205,7 +1199,7 @@ class PdfTextExtractor { for (int i = 0; i < word.length; i++) { final String renderedCharacter = word[i]; _MatrixHelper transform = _MatrixHelper(1, 0, 0, 1, 0, 0); - if (!fontStructure.isEmbedded && + if (!fontStructure!.isEmbedded && fontStructure._isStandardFont && fontStructure.font != null) { final PdfStandardFont font = fontStructure.font as PdfStandardFont; @@ -1222,35 +1216,36 @@ class PdfTextExtractor { _characterWidth = _getCharacterWidth(renderedCharacter, fontStructure); } - _textMatrix = _getTextRenderingMatrix(horizontalScaling); + _textMatrix = _getTextRenderingMatrix(horizontalScaling!); final _MatrixHelper identity = _MatrixHelper.identity._clone(); identity._scale(0.01, 0.01, 0.0, 0.0); identity._translate(0.0, 1.0); final _MatrixHelper matrix = transform._clone(); transform = matrix; - double tempFontSize; - if (_textMatrix.m11 > 0) { - tempFontSize = _textMatrix.m11; - } else if (_textMatrix.m12 != 0 && _textMatrix.m21 != 0) { - if (_textMatrix.m12 < 0) { - tempFontSize = -_textMatrix.m12; + double? tempFontSize; + if (_textMatrix!.m11 > 0) { + tempFontSize = _textMatrix!.m11; + } else if (_textMatrix!.m12 != 0 && _textMatrix!.m21 != 0) { + if (_textMatrix!.m12 < 0) { + tempFontSize = -_textMatrix!.m12; } else { - tempFontSize = _textMatrix.m12; + tempFontSize = _textMatrix!.m12; } } else { tempFontSize = _fontSize; } final Rect boundingRect = Rect.fromLTWH( matrix.offsetX / 1.3333333333333333, - (matrix.offsetY - tempFontSize) / 1.3333333333333333, + (matrix.offsetY - tempFontSize!) / 1.3333333333333333, _characterWidth * tempFontSize, tempFontSize); if (_tempBoundingRectangle != null) { final double boundingDifference = - ((boundingRect.left - _tempBoundingRectangle.right) / 10) + ((boundingRect.left - _tempBoundingRectangle!.right) / 10) .round() .toDouble(); - if ((_tempBoundingRectangle.right != 0 && boundingRect.left != 0) && + if ((_tempBoundingRectangle!.right != 0 && + boundingRect.left != 0) && boundingDifference >= 1 && _hasLeading) { extractedText += ' '; @@ -1267,25 +1262,23 @@ class PdfTextExtractor { return extractedText; } - String _renderTextElement(List elements, String tokenType, + String? _renderTextElement(List elements, String tokenType, _PdfPageResources pageResources) { try { String text = elements.join(); if (!pageResources.containsKey(_currentFont)) { - if (_currentFont != null && _currentFont.contains('-')) { - _currentFont = _currentFont.replaceAll('-', '#2D'); + if (_currentFont != null && _currentFont!.contains('-')) { + _currentFont = _currentFont!.replaceAll('-', '#2D'); } } if (pageResources.containsKey(_currentFont)) { - _FontStructure fontStructure; - final dynamic returnValue = pageResources[_currentFont]; + _FontStructure? fontStructure; + final dynamic returnValue = pageResources[_currentFont!]; if (returnValue != null && returnValue is _FontStructure) { fontStructure = returnValue; } - fontStructure.isTextExtraction = true; - if (fontStructure != null) { - fontStructure.fontSize = _fontSize; - } + fontStructure!.isTextExtraction = true; + fontStructure.fontSize = _fontSize; text = fontStructure.decodeTextExtraction(text, true); fontStructure.isTextExtraction = false; } @@ -1295,23 +1288,23 @@ class PdfTextExtractor { } } - String _getXObject(String resultantText, List xobjectElement, + String? _getXObject(String resultantText, List xobjectElement, _PdfPageResources pageResources) { - String result; + String? result; final String key = xobjectElement[0].replaceAll('/', ''); if (pageResources.containsKey(key)) { final dynamic element = pageResources[key]; if (element is _XObjectElement) { - final _PdfRecordCollection collection = element.render(pageResources); - final _PdfDictionary xobjects = element.dictionary; + final _PdfRecordCollection collection = element.render(pageResources)!; + final _PdfDictionary xobjects = element.dictionary!; _PdfPageResources childResource = _PdfPageResources(); if (xobjects.containsKey(_DictionaryProperties.resources)) { - _PdfDictionary pageDictionary = _PdfDictionary(); - final _IPdfPrimitive resource = + _PdfDictionary? pageDictionary = _PdfDictionary(); + final _IPdfPrimitive? resource = xobjects[_DictionaryProperties.resources]; if (resource is _PdfReferenceHolder && resource.object is _PdfDictionary) { - pageDictionary = resource.object; + pageDictionary = resource.object as _PdfDictionary?; } else if (resource is _PdfDictionary) { pageDictionary = resource; } @@ -1335,7 +1328,7 @@ class PdfTextExtractor { _PdfPageResources _updateFontResources(_PdfPageResources pageResources) { final _PdfPageResources resources = _PdfPageResources(); - pageResources.resources.forEach((String key, dynamic value) { + pageResources.resources.forEach((String? key, dynamic value) { if (value is _FontStructure) { resources.resources[key] = value; resources._fontCollection[key] = value; @@ -1344,40 +1337,40 @@ class PdfTextExtractor { return resources; } - _MatrixHelper _updateTextMatrixWithSpacing( + _MatrixHelper? _updateTextMatrixWithSpacing( double space, double horizontalScaling) { - final double x = -(space * 0.001 * _fontSize * horizontalScaling / 100); - final Offset point = _textLineMatrix._transform(Offset(0.0, 0.0)); - final Offset point2 = _textLineMatrix._transform(Offset(x, 0.0)); + final double x = -(space * 0.001 * _fontSize! * horizontalScaling / 100); + final Offset point = _textLineMatrix!._transform(Offset(0.0, 0.0)); + final Offset point2 = _textLineMatrix!._transform(Offset(x, 0.0)); if (point.dx != point2.dx) { - _textLineMatrix.offsetX = point2.dx; + _textLineMatrix!.offsetX = point2.dx; } else { - _textLineMatrix.offsetY = point2.dy; + _textLineMatrix!.offsetY = point2.dy; } return _textLineMatrix; } _MatrixHelper _getTextRenderingMatrix(double textHorizontalScaling) { - return _MatrixHelper(_fontSize * (textHorizontalScaling / 100), 0, 0, - -_fontSize, 0, _fontSize) * - _textLineMatrix * + return _MatrixHelper(_fontSize! * (textHorizontalScaling / 100), 0, 0, + -_fontSize!, 0, _fontSize!) * + _textLineMatrix! * _currentTransformationMatrix; } double _getCharacterWidth(String character, _FontStructure structure) { final int _charID = character.codeUnitAt(0); return (structure.fontGlyphWidths != null && - structure.fontType._name == 'TrueType' && - structure.fontGlyphWidths.containsKey(_charID)) - ? structure.fontGlyphWidths[_charID] * 0.001 + structure.fontType!._name == 'TrueType' && + structure.fontGlyphWidths!.containsKey(_charID)) + ? structure.fontGlyphWidths![_charID]! * 0.001 : 1.0; } _MatrixHelper _updateTextMatrix( double characterWidth, double horizontalScaling) { final double offsetX = - (characterWidth * _fontSize + _characterSpacing + _wordSpacing) * + (characterWidth * _fontSize! + _characterSpacing + _wordSpacing) * (horizontalScaling / 100); - return _MatrixHelper(1.0, 0.0, 0.0, 1.0, offsetX, 0.0) * _textLineMatrix; + return _MatrixHelper(1.0, 0.0, 0.0, 1.0, offsetX, 0.0) * _textLineMatrix!; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_element.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_element.dart index 82aa01686..cb410178b 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_element.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_element.dart @@ -2,57 +2,59 @@ part of pdf; class _TextElement { //constructor - _TextElement(String text, _MatrixHelper transformMatrix) { + _TextElement(String text, _MatrixHelper? transformMatrix) { _text = text; transformations = _TransformationStack(transformMatrix); _initialize(); } //Fields - String _text; - _TransformationStack transformations; - List<_Glyph> textElementGlyphList; - bool isExtractTextData; - String fontName; - List fontStyle; - double fontSize; - double textScaling; - double characterSpacing; - double wordSpacing; - int renderingMode; - Map encodedTextBytes; - String fontEncoding; - Map fontGlyphWidths; - double defaultGlyphWidth; - Map unicodeCharMapTable; - Map cidToGidReverseMapTable; - Map characterMapTable; - Map reverseMapTable; - _FontStructure structure; - bool isEmbeddedFont; - _MatrixHelper currentTransformationMatrix; - _MatrixHelper textLineMatrix; - _MatrixHelper transformMatrix; - int _rise; - _MatrixHelper documentMatrix; - String fontId; - Map octDecMapTable; - double textHorizontalScaling; - String zapfPostScript; - double lineWidth; - double pageRotation; - double zoomFactor; - Map substitutedFontsList; - PdfFont font; + late String _text; + late _TransformationStack transformations; + late List<_Glyph> textElementGlyphList; + bool? isExtractTextData; + late String fontName; + late List fontStyle; + double fontSize = 0; + double? textScaling; + double? characterSpacing; + double? wordSpacing; + int? renderingMode; + Map? encodedTextBytes; + String? fontEncoding; + Map? fontGlyphWidths; + double? defaultGlyphWidth; + Map? unicodeCharMapTable; + Map? cidToGidReverseMapTable; + late Map characterMapTable; + Map? reverseMapTable; + late _FontStructure structure; + late bool isEmbeddedFont; + _MatrixHelper? currentTransformationMatrix; + _MatrixHelper? textLineMatrix; + _MatrixHelper? transformMatrix; + int? _rise; + _MatrixHelper? documentMatrix; + String? fontId; + Map? octDecMapTable; + double? textHorizontalScaling; + String? zapfPostScript; + double? lineWidth; + double? pageRotation; + double? zoomFactor; + Map? substitutedFontsList; + PdfFont? font; String renderedText = ''; - int fontFlag; - bool isTextGlyphAdded; - double currentGlyphWidth; - double charSizeMultiplier; - List macRomanToUnicode; + int? fontFlag; + late bool isTextGlyphAdded; + double? currentGlyphWidth; + late double charSizeMultiplier; + List? macRomanToUnicode; //Implementation void _initialize() { + fontName = ''; + fontStyle = []; charSizeMultiplier = 0.001; currentGlyphWidth = 0; isTextGlyphAdded = false; @@ -62,12 +64,11 @@ class _TextElement { textHorizontalScaling = 100; characterSpacing = 0; wordSpacing = 0; - fontSize = 0; defaultGlyphWidth = 0; reverseMapTable = {}; isEmbeddedFont = false; documentMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); - documentMatrix.type = _MatrixTypes.identity; + documentMatrix!.type = _MatrixTypes.identity; fontId = ''; lineWidth = 0; pageRotation = 0; @@ -207,22 +208,22 @@ class _TextElement { } _MatrixHelper _getTextRenderingMatrix() { - return _MatrixHelper(fontSize * (textHorizontalScaling / 100), 0, 0, - -fontSize, 0, fontSize + _rise) * - textLineMatrix * - currentTransformationMatrix; + return _MatrixHelper(fontSize * (textHorizontalScaling! / 100), 0, 0, + -fontSize, 0, fontSize + _rise!) * + textLineMatrix! * + currentTransformationMatrix!; } double _render( - _GraphicsObject g, + _GraphicsObject? g, Offset currentLocation, - double textScaling, - Map glyphWidths, - double type1Height, + double? textScaling, + Map? glyphWidths, + double? type1Height, Map differenceTable, - Map differenceMappedTable, - Map differenceEncoding, - _MatrixHelper txtMatrix) { + Map differenceMappedTable, + Map? differenceEncoding, + _MatrixHelper? txtMatrix) { txtMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); txtMatrix.type = _MatrixTypes.identity; double changeInX = currentLocation.dx.toDouble(); @@ -230,7 +231,8 @@ class _TextElement { if (!isEmbeddedFont && (structure._isStandardFont || structure._isStandardCJKFont) && structure.font != null) { - final _MatrixHelper defaultTransformations = g._transformMatrix._clone(); + final _MatrixHelper defaultTransformations = + g!._transformMatrix!._clone(); for (int i = 0; i < _text.length; i++) { final String character = _text[i]; g._transformMatrix = _MatrixHelper(1, 0, 0, 1, 0, 0); @@ -240,10 +242,10 @@ class _TextElement { glyph.fontStyle = fontStyle; glyph.transformMatrix = _getTextRenderingMatrix(); glyph.name = character; - glyph.horizontalScaling = textHorizontalScaling; + glyph.horizontalScaling = textHorizontalScaling!; glyph.charId = character.codeUnitAt(0); glyph.toUnicode = character; - glyph.charSpacing = characterSpacing; + glyph.charSpacing = characterSpacing!; if (structure._isStandardFont) { final PdfStandardFont font = structure.font as PdfStandardFont; glyph.width = font._getCharWidthInternal(character) * @@ -257,11 +259,11 @@ class _TextElement { identity._scale(0.01, 0.01, 0.0, 0.0); identity._translate(0.0, 1.0); transformations._pushTransform(identity * glyph.transformMatrix); - final _MatrixHelper transform = g._transformMatrix; + final _MatrixHelper transform = g._transformMatrix!; _MatrixHelper matrix = transform._clone(); - matrix = matrix * transformations.currentTransform._clone(); + matrix = matrix * transformations.currentTransform!._clone(); g._transformMatrix = matrix; - double tempFontSize = 0; + double? tempFontSize = 0; if (glyph.transformMatrix.m11 > 0) { tempFontSize = glyph.transformMatrix.m11; } else if (glyph.transformMatrix.m12 != 0 && @@ -275,10 +277,10 @@ class _TextElement { tempFontSize = glyph.fontSize; } glyph.boundingRect = Rect.fromLTWH( - (matrix.offsetX / 1.3333333333333333) / zoomFactor, - ((matrix.offsetY - (tempFontSize * zoomFactor)) / + (matrix.offsetX / 1.3333333333333333) / zoomFactor!, + ((matrix.offsetY - (tempFontSize * zoomFactor!)) / 1.3333333333333333) / - zoomFactor, + zoomFactor!, glyph.width * tempFontSize, tempFontSize); textElementGlyphList.add(glyph); @@ -298,85 +300,85 @@ class _TextElement { if (charCode.toUnsigned(8) > 126 && fontEncoding == 'MacRomanEncoding' && !isEmbeddedFont) { - txtMatrix = drawSystemFontGlyphShape(letter, g, txtMatrix); + txtMatrix = drawSystemFontGlyphShape(letter, g!, txtMatrix); } else { if (renderingMode == 1) { - txtMatrix = drawSystemFontGlyphShape(letter, g, txtMatrix); - } else if (reverseMapTable.isNotEmpty && - reverseMapTable.containsKey(letter)) { - final int tempCharCode = reverseMapTable[letter].toInt(); + txtMatrix = drawSystemFontGlyphShape(letter, g!, txtMatrix); + } else if (reverseMapTable!.isNotEmpty && + reverseMapTable!.containsKey(letter)) { + final int tempCharCode = reverseMapTable![letter]!.toInt(); if (fontGlyphWidths != null) { - currentGlyphWidth = (fontGlyphWidths.containsKey(tempCharCode) - ? fontGlyphWidths[tempCharCode] - : defaultGlyphWidth) * + currentGlyphWidth = (fontGlyphWidths!.containsKey(tempCharCode) + ? fontGlyphWidths![tempCharCode] + : defaultGlyphWidth)! * charSizeMultiplier; } else { - currentGlyphWidth = defaultGlyphWidth * charSizeMultiplier; + currentGlyphWidth = defaultGlyphWidth! * charSizeMultiplier; } - txtMatrix = drawGlyphs(currentGlyphWidth, g, txtMatrix, letter); + txtMatrix = drawGlyphs(currentGlyphWidth, g!, txtMatrix, letter); isTextGlyphAdded = true; } else { if (characterMapTable.isNotEmpty && characterMapTable.containsKey(charCode)) { - final String tempLetter = characterMapTable[charCode][0]; + final String tempLetter = characterMapTable[charCode]![0]; isTextGlyphAdded = true; - txtMatrix = drawSystemFontGlyphShape(tempLetter, g, txtMatrix); + txtMatrix = drawSystemFontGlyphShape(tempLetter, g!, txtMatrix); } } if (!isTextGlyphAdded) { if (characterMapTable.isNotEmpty && characterMapTable.containsKey(charCode)) { - final String unicode = characterMapTable[charCode][0]; + final String unicode = characterMapTable[charCode]![0]; if (fontGlyphWidths == null) { - currentGlyphWidth = defaultGlyphWidth * charSizeMultiplier; + currentGlyphWidth = defaultGlyphWidth! * charSizeMultiplier; } else { - if (structure.fontType._name == 'Type0') { + if (structure.fontType!._name == 'Type0') { if (cidToGidReverseMapTable != null && - cidToGidReverseMapTable.containsKey(charCode) && + cidToGidReverseMapTable!.containsKey(charCode) && !structure.isMappingDone) { currentGlyphWidth = - fontGlyphWidths[cidToGidReverseMapTable[charCode]] * + fontGlyphWidths![cidToGidReverseMapTable![charCode]!]! * charSizeMultiplier; - } else if (fontGlyphWidths.containsKey(charCode)) { + } else if (fontGlyphWidths!.containsKey(charCode)) { currentGlyphWidth = - fontGlyphWidths[charCode] * charSizeMultiplier; + fontGlyphWidths![charCode]! * charSizeMultiplier; } else { - if (reverseMapTable.containsKey(unicode) && - !fontGlyphWidths - .containsKey((reverseMapTable[unicode]).toInt())) { + if (reverseMapTable!.containsKey(unicode) && + !fontGlyphWidths! + .containsKey(reverseMapTable![unicode]!.toInt())) { currentGlyphWidth = - defaultGlyphWidth * charSizeMultiplier; + defaultGlyphWidth! * charSizeMultiplier; } } - } else if (structure.fontType._name == 'TrueType' && - fontGlyphWidths.containsKey(charCode)) { + } else if (structure.fontType!._name == 'TrueType' && + fontGlyphWidths!.containsKey(charCode)) { currentGlyphWidth = - fontGlyphWidths[charCode] * charSizeMultiplier; + fontGlyphWidths![charCode]! * charSizeMultiplier; } } } else if (cidToGidReverseMapTable != null && - cidToGidReverseMapTable.isNotEmpty) { - if (cidToGidReverseMapTable.containsKey(charCode)) { - final int cidGidKey = cidToGidReverseMapTable[charCode]; + cidToGidReverseMapTable!.isNotEmpty) { + if (cidToGidReverseMapTable!.containsKey(charCode)) { + final int? cidGidKey = cidToGidReverseMapTable![charCode]; if (fontGlyphWidths != null && - fontGlyphWidths.containsKey(cidGidKey)) { + fontGlyphWidths!.containsKey(cidGidKey)) { currentGlyphWidth = - fontGlyphWidths[cidGidKey] * charSizeMultiplier; + fontGlyphWidths![cidGidKey!]! * charSizeMultiplier; } } } else if (fontGlyphWidths != null) { - currentGlyphWidth = (fontGlyphWidths.containsKey(charCode) - ? fontGlyphWidths[charCode] - : defaultGlyphWidth) * + currentGlyphWidth = (fontGlyphWidths!.containsKey(charCode) + ? fontGlyphWidths![charCode] + : defaultGlyphWidth)! * charSizeMultiplier; } } } if (letterCount < _text.length) { - location = Offset(location.dx + characterSpacing, location.dy); + location = Offset(location.dx + characterSpacing!, location.dy); } if (!isTextGlyphAdded) { - txtMatrix = drawGlyphs(currentGlyphWidth, g, txtMatrix, letter); + txtMatrix = drawGlyphs(currentGlyphWidth, g!, txtMatrix, letter); } } } @@ -385,23 +387,23 @@ class _TextElement { } double _renderWithSpacing( - _GraphicsObject g, + _GraphicsObject? g, Offset currentLocation, List decodedList, - List characterSpacing, - double textScaling, - Map glyphWidths, - double type1Height, + List? characterSpacing, + double? textScaling, + Map? glyphWidths, + double? type1Height, Map differenceTable, - Map differenceMappedTable, - Map differenceEncoding, - _MatrixHelper txtMatrix) { + Map differenceMappedTable, + Map? differenceEncoding, + _MatrixHelper? txtMatrix) { txtMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); txtMatrix.type = _MatrixTypes.identity; double changeInX = currentLocation.dx.toDouble(); Offset location = Offset(currentLocation.dx, currentLocation.dy); decodedList.forEach((String word) { - final double space = double.tryParse(word); + final double? space = double.tryParse(word); if (space != null) { _updateTextMatrixWithSpacing(space); } else { @@ -409,7 +411,7 @@ class _TextElement { structure.font != null && (structure._isStandardFont || structure._isStandardCJKFont)) { final _MatrixHelper defaultTransformations = - g._transformMatrix._clone(); + g!._transformMatrix!._clone(); if (word != '' && word[word.length - 1] == 's') { word = word.substring(0, word.length - 1); } @@ -422,10 +424,10 @@ class _TextElement { glyph.fontStyle = fontStyle; glyph.transformMatrix = _getTextRenderingMatrix(); glyph.name = character; - glyph.horizontalScaling = textHorizontalScaling; + glyph.horizontalScaling = textHorizontalScaling!; glyph.charId = character.codeUnitAt(0); glyph.toUnicode = character; - glyph.charSpacing = this.characterSpacing; + glyph.charSpacing = this.characterSpacing!; if (structure._isStandardFont) { final PdfStandardFont font = structure.font as PdfStandardFont; glyph.width = font._getCharWidthInternal(character) * @@ -440,11 +442,11 @@ class _TextElement { identity._scale(0.01, 0.01, 0.0, 0.0); identity._translate(0.0, 1.0); transformations._pushTransform(identity * glyph.transformMatrix); - final _MatrixHelper transform = g._transformMatrix; + final _MatrixHelper transform = g._transformMatrix!; _MatrixHelper matrix = transform._clone(); - matrix = matrix * transformations.currentTransform._clone(); + matrix = matrix * transformations.currentTransform!._clone(); g._transformMatrix = matrix; - double tempFontSize = 0; + double? tempFontSize = 0; if (glyph.transformMatrix.m11 > 0) { tempFontSize = glyph.transformMatrix.m11; } else if (glyph.transformMatrix.m12 != 0 && @@ -458,10 +460,10 @@ class _TextElement { tempFontSize = glyph.fontSize; } glyph.boundingRect = Rect.fromLTWH( - (matrix.offsetX / 1.3333333333333333) / zoomFactor, - ((matrix.offsetY - (tempFontSize * zoomFactor)) / + (matrix.offsetX / 1.3333333333333333) / zoomFactor!, + ((matrix.offsetY - (tempFontSize * zoomFactor!)) / 1.3333333333333333) / - zoomFactor, + zoomFactor!, glyph.width * tempFontSize, tempFontSize); textElementGlyphList.add(glyph); @@ -478,14 +480,14 @@ class _TextElement { if (word != '') { int letterCount = 0; bool isComplexScript = false; - if (reverseMapTable.isNotEmpty && - reverseMapTable.containsKey(word)) { - final int charCode = reverseMapTable[word].toInt(); + if (reverseMapTable!.isNotEmpty && + reverseMapTable!.containsKey(word)) { + final int charCode = reverseMapTable![word]!.toInt(); if (characterMapTable.isNotEmpty && characterMapTable.containsKey(charCode)) { - final String tempLetter = characterMapTable[charCode]; + final String tempLetter = characterMapTable[charCode]!; isTextGlyphAdded = true; - txtMatrix = drawSystemFontGlyphShape(tempLetter, g, txtMatrix); + txtMatrix = drawSystemFontGlyphShape(tempLetter, g!, txtMatrix); isComplexScript = true; } } @@ -498,78 +500,78 @@ class _TextElement { if (charCode.toUnsigned(8) > 126 && fontEncoding == 'MacRomanEncoding' && !isEmbeddedFont) { - txtMatrix = drawSystemFontGlyphShape(letter, g, txtMatrix); + txtMatrix = drawSystemFontGlyphShape(letter, g!, txtMatrix); } else { if (renderingMode == 1) { - txtMatrix = drawSystemFontGlyphShape(letter, g, txtMatrix); + txtMatrix = drawSystemFontGlyphShape(letter, g!, txtMatrix); } else { - if (reverseMapTable.isNotEmpty && - reverseMapTable.containsKey(letter)) { - charCode = reverseMapTable[letter].toInt(); + if (reverseMapTable!.isNotEmpty && + reverseMapTable!.containsKey(letter)) { + charCode = reverseMapTable![letter]!.toInt(); } if (characterMapTable.isNotEmpty && characterMapTable.containsKey(charCode)) { - final String tempLetter = characterMapTable[charCode][0]; + final String tempLetter = characterMapTable[charCode]![0]; isTextGlyphAdded = true; txtMatrix = - drawSystemFontGlyphShape(tempLetter, g, txtMatrix); + drawSystemFontGlyphShape(tempLetter, g!, txtMatrix); } } if (characterMapTable.isNotEmpty && characterMapTable.containsKey(charCode)) { - final String unicode = characterMapTable[charCode][0]; + final String unicode = characterMapTable[charCode]![0]; if (fontGlyphWidths == null) { currentGlyphWidth = - defaultGlyphWidth * charSizeMultiplier; + defaultGlyphWidth! * charSizeMultiplier; } else { - if (structure.fontType._name == 'Type0') { + if (structure.fontType!._name == 'Type0') { if (cidToGidReverseMapTable != null && - cidToGidReverseMapTable.containsKey(charCode) && + cidToGidReverseMapTable!.containsKey(charCode) && !structure.isMappingDone) { - currentGlyphWidth = fontGlyphWidths[ - cidToGidReverseMapTable[charCode]] * + currentGlyphWidth = fontGlyphWidths![ + cidToGidReverseMapTable![charCode]!]! * charSizeMultiplier; - } else if (fontGlyphWidths.containsKey(charCode)) { + } else if (fontGlyphWidths!.containsKey(charCode)) { currentGlyphWidth = - fontGlyphWidths[charCode] * charSizeMultiplier; + fontGlyphWidths![charCode]! * charSizeMultiplier; } else { - if (reverseMapTable.containsKey(unicode) && - !fontGlyphWidths.containsKey( - (reverseMapTable[unicode]).toInt())) { + if (reverseMapTable!.containsKey(unicode) && + !fontGlyphWidths!.containsKey( + reverseMapTable![unicode]!.toInt())) { currentGlyphWidth = - defaultGlyphWidth * charSizeMultiplier; + defaultGlyphWidth! * charSizeMultiplier; } } - } else if (structure.fontType._name == 'TrueType' && - fontGlyphWidths.containsKey(charCode)) { + } else if (structure.fontType!._name == 'TrueType' && + fontGlyphWidths!.containsKey(charCode)) { currentGlyphWidth = - fontGlyphWidths[charCode] * charSizeMultiplier; + fontGlyphWidths![charCode]! * charSizeMultiplier; } } } else if (cidToGidReverseMapTable != null && - cidToGidReverseMapTable.isNotEmpty) { - if (cidToGidReverseMapTable.containsKey(charCode)) { - final int cidGidKey = cidToGidReverseMapTable[charCode]; + cidToGidReverseMapTable!.isNotEmpty) { + if (cidToGidReverseMapTable!.containsKey(charCode)) { + final int? cidGidKey = cidToGidReverseMapTable![charCode]; if (fontGlyphWidths != null && - fontGlyphWidths.containsKey(cidGidKey)) { + fontGlyphWidths!.containsKey(cidGidKey)) { currentGlyphWidth = - fontGlyphWidths[cidGidKey] * charSizeMultiplier; + fontGlyphWidths![cidGidKey!]! * charSizeMultiplier; } } } else if (fontGlyphWidths != null) { - currentGlyphWidth = (fontGlyphWidths.containsKey(charCode) - ? fontGlyphWidths[charCode] - : defaultGlyphWidth) * + currentGlyphWidth = (fontGlyphWidths!.containsKey(charCode) + ? fontGlyphWidths![charCode] + : defaultGlyphWidth)! * charSizeMultiplier; } } if (letterCount < word.length) { location = - Offset(location.dx + this.characterSpacing, location.dy); + Offset(location.dx + this.characterSpacing!, location.dy); } if (!isTextGlyphAdded) { txtMatrix = - drawGlyphs(currentGlyphWidth, g, txtMatrix, letter); + drawGlyphs(currentGlyphWidth, g!, txtMatrix, letter); } } } @@ -581,50 +583,46 @@ class _TextElement { return changeInX; } - _MatrixHelper drawGlyphs(double glyphwidth, _GraphicsObject g, - _MatrixHelper temptextmatrix, String glyphChar) { - final _MatrixHelper defaultTransformations = g._transformMatrix._clone(); + _MatrixHelper? drawGlyphs(double? glyphwidth, _GraphicsObject g, + _MatrixHelper? temptextmatrix, String? glyphChar) { + final _MatrixHelper defaultTransformations = g._transformMatrix!._clone(); g._transformMatrix = _MatrixHelper(1, 0, 0, 1, 0, 0); final _Glyph glyph = _Glyph(); glyph.fontSize = fontSize; glyph.fontFamily = fontName; glyph.fontStyle = fontStyle; glyph.transformMatrix = _getTextRenderingMatrix(); - glyph.horizontalScaling = textHorizontalScaling; - glyph.width = glyphwidth; - glyph.charSpacing = characterSpacing; + glyph.horizontalScaling = textHorizontalScaling!; + glyph.width = glyphwidth!; + glyph.charSpacing = characterSpacing!; final _MatrixHelper identity = _MatrixHelper(1, 0, 0, 1, 0, 0); identity._scale(0.01, 0.01, 0.0, 0.0); identity._translate(0.0, 1.0); transformations._pushTransform(identity * glyph.transformMatrix); - final _MatrixHelper transform = g._transformMatrix; + final _MatrixHelper transform = g._transformMatrix!; _MatrixHelper matrix = transform._clone(); - matrix *= transformations.currentTransform._clone(); + matrix *= transformations.currentTransform!._clone(); g._transformMatrix = matrix; if (!structure.isMappingDone) { if (cidToGidReverseMapTable != null && - cidToGidReverseMapTable.containsKey(glyphChar.codeUnitAt(0)) && - (structure.characterMapTable != null && - structure.characterMapTable.isNotEmpty)) { - glyphChar = - characterMapTable[cidToGidReverseMapTable[glyphChar.codeUnitAt(0)]]; - } else if (structure.characterMapTable != null && - structure.characterMapTable.isNotEmpty) { - glyphChar = structure.mapCharactersFromTable(glyphChar); - } else if (structure.differencesDictionary != null && - structure.differencesDictionary.isNotEmpty) { + cidToGidReverseMapTable!.containsKey(glyphChar!.codeUnitAt(0)) && + (structure.characterMapTable.isNotEmpty)) { + glyphChar = characterMapTable[ + cidToGidReverseMapTable![glyphChar.codeUnitAt(0)]]; + } else if (structure.characterMapTable.isNotEmpty) { + glyphChar = structure.mapCharactersFromTable(glyphChar!); + } else if (structure.differencesDictionary.isNotEmpty) { glyphChar = structure.mapDifferences(glyphChar); - } else if (structure.cidToGidReverseMapTable != null && - structure.cidToGidReverseMapTable - .containsKey(glyphChar.codeUnitAt(0))) { + } else if (structure.cidToGidReverseMapTable + .containsKey(glyphChar!.codeUnitAt(0))) { glyphChar = String.fromCharCode( - structure.cidToGidReverseMapTable[glyphChar.codeUnitAt(0)]); + structure.cidToGidReverseMapTable[glyphChar.codeUnitAt(0)]!); } - if (glyphChar.contains('\u0092')) { + if (glyphChar!.contains('\u0092')) { glyphChar = glyphChar.replaceAll('\u0092', '’'); } } - double tempFontSize; + double? tempFontSize; if (glyph.transformMatrix.m11 > 0) { tempFontSize = glyph.transformMatrix.m11; } else if (glyph.transformMatrix.m12 != 0 && @@ -635,7 +633,7 @@ class _TextElement { } else { tempFontSize = glyph.fontSize; } - glyph.toUnicode = glyphChar; + glyph.toUnicode = glyphChar!; if (matrix.m12 != 0 && matrix.m21 != 0) { glyph.isRotated = true; if (matrix.m12 < 0 && matrix.m21 > 0) { @@ -649,19 +647,19 @@ class _TextElement { ((matrix.offsetX + ((tempFontSize + (glyph.ascent / 1000.0)) * matrix.m21)) / 1.3333333333333333) / - zoomFactor, + zoomFactor!, (((matrix.offsetY - (tempFontSize * matrix.m21)) / 1.3333333333333333) - - (tempFontSize * zoomFactor / 1.3333333333333333)) / - zoomFactor, + (tempFontSize * zoomFactor! / 1.3333333333333333)) / + zoomFactor!, glyph.width * tempFontSize, tempFontSize); } else { glyph.boundingRect = Rect.fromLTWH( - (matrix.offsetX / 1.3333333333333333) / zoomFactor, - ((matrix.offsetY - (tempFontSize * zoomFactor)) / + (matrix.offsetX / 1.3333333333333333) / zoomFactor!, + ((matrix.offsetY - (tempFontSize * zoomFactor!)) / 1.3333333333333333) / - zoomFactor, + zoomFactor!, glyph.width * tempFontSize, tempFontSize); } @@ -682,28 +680,28 @@ class _TextElement { return temptextmatrix; } - _MatrixHelper drawSystemFontGlyphShape( - String letter, _GraphicsObject g, _MatrixHelper temptextmatrix) { - final _MatrixHelper defaultTransformations = g._transformMatrix; + _MatrixHelper? drawSystemFontGlyphShape( + String letter, _GraphicsObject g, _MatrixHelper? temptextmatrix) { + final _MatrixHelper? defaultTransformations = g._transformMatrix; g._transformMatrix = _MatrixHelper(1, 0, 0, 1, 0, 0); final _Glyph gly = _Glyph(); - gly.horizontalScaling = textHorizontalScaling; - gly.charSpacing = characterSpacing; + gly.horizontalScaling = textHorizontalScaling!; + gly.charSpacing = characterSpacing!; gly.fontSize = fontSize; gly.name = letter; gly.charId = letter.codeUnitAt(0).toUnsigned(8).toInt(); gly.transformMatrix = _getTextRenderingMatrix(); - gly.wordSpacing = wordSpacing; - double systemFontGlyph; - if (fontGlyphWidths != null && fontGlyphWidths.isNotEmpty) { - if (reverseMapTable != null && reverseMapTable.containsKey(letter)) { - final int charCode = reverseMapTable[letter].toInt(); - if (fontGlyphWidths.containsKey(charCode)) { - systemFontGlyph = fontGlyphWidths[charCode] * charSizeMultiplier; + gly.wordSpacing = wordSpacing!; + double? systemFontGlyph; + if (fontGlyphWidths != null && fontGlyphWidths!.isNotEmpty) { + if (reverseMapTable != null && reverseMapTable!.containsKey(letter)) { + final int charCode = reverseMapTable![letter]!.toInt(); + if (fontGlyphWidths!.containsKey(charCode)) { + systemFontGlyph = fontGlyphWidths![charCode]! * charSizeMultiplier; } - } else if (fontGlyphWidths.containsKey(letter.codeUnitAt(0))) { + } else if (fontGlyphWidths!.containsKey(letter.codeUnitAt(0))) { systemFontGlyph = - fontGlyphWidths[letter.codeUnitAt(0)] * charSizeMultiplier; + fontGlyphWidths![letter.codeUnitAt(0)]! * charSizeMultiplier; } } gly.width = (systemFontGlyph == null) ? 0 : systemFontGlyph; @@ -711,11 +709,11 @@ class _TextElement { identity._scale(0.01, 0.01, 0.0, 0.0); identity._translate(0.0, 1.0); transformations._pushTransform(identity * gly.transformMatrix); - final _MatrixHelper transform = g._transformMatrix; + final _MatrixHelper transform = g._transformMatrix!; _MatrixHelper matrix = transform._clone(); - matrix = matrix * transformations.currentTransform._clone(); + matrix = matrix * transformations.currentTransform!._clone(); g._transformMatrix = matrix; - double tempFontSize = 0; + double? tempFontSize = 0; if (gly.transformMatrix.m11 > 0) { tempFontSize = gly.transformMatrix.m11; } else if (gly.transformMatrix.m12 != 0 && gly.transformMatrix.m21 != 0) { @@ -725,35 +723,31 @@ class _TextElement { } else { tempFontSize = gly.fontSize; } - String glyphName = letter; + String? glyphName = letter; if (!structure.isMappingDone) { if (cidToGidReverseMapTable != null && - cidToGidReverseMapTable.containsKey(glyphName.codeUnitAt(0)) && - (structure.characterMapTable != null && - structure.characterMapTable.isNotEmpty)) { - glyphName = - characterMapTable[cidToGidReverseMapTable[glyphName.codeUnitAt(0)]]; - } else if (structure.characterMapTable != null && - structure.characterMapTable.isNotEmpty) { + cidToGidReverseMapTable!.containsKey(glyphName.codeUnitAt(0)) && + (structure.characterMapTable.isNotEmpty)) { + glyphName = characterMapTable[ + cidToGidReverseMapTable![glyphName.codeUnitAt(0)]]; + } else if (structure.characterMapTable.isNotEmpty) { glyphName = structure.mapCharactersFromTable(glyphName); - } else if (structure.differencesDictionary != null && - structure.differencesDictionary.isNotEmpty) { + } else if (structure.differencesDictionary.isNotEmpty) { glyphName = structure.mapDifferences(glyphName); - } else if (structure.cidToGidReverseMapTable != null && - structure.cidToGidReverseMapTable - .containsKey(glyphName.codeUnitAt(0))) { + } else if (structure.cidToGidReverseMapTable + .containsKey(glyphName.codeUnitAt(0))) { glyphName = String.fromCharCode( - structure.cidToGidReverseMapTable[glyphName.codeUnitAt(0)]); + structure.cidToGidReverseMapTable[glyphName.codeUnitAt(0)]!); } - if (glyphName.contains('\u0092')) { + if (glyphName!.contains('\u0092')) { glyphName = glyphName.replaceAll('\u0092', '’'); } } gly.toUnicode = glyphName; gly.boundingRect = Rect.fromLTWH( - (matrix.offsetX / 1.3333333333333333) / zoomFactor, - ((matrix.offsetY - (tempFontSize * zoomFactor)) / 1.3333333333333333) / - zoomFactor, + (matrix.offsetX / 1.3333333333333333) / zoomFactor!, + ((matrix.offsetY - (tempFontSize * zoomFactor!)) / 1.3333333333333333) / + zoomFactor!, gly.width * tempFontSize, tempFontSize); textElementGlyphList.add(gly); @@ -766,23 +760,23 @@ class _TextElement { } void _updateTextMatrixWithSpacing(double space) { - final double x = -(space * 0.001 * fontSize * textHorizontalScaling / 100); - final Offset point = textLineMatrix._transform(Offset(0.0, 0.0)); - final Offset point2 = textLineMatrix._transform(Offset(x, 0.0)); + final double x = -(space * 0.001 * fontSize * textHorizontalScaling! / 100); + final Offset point = textLineMatrix!._transform(Offset(0.0, 0.0)); + final Offset point2 = textLineMatrix!._transform(Offset(x, 0.0)); if (point.dx != point2.dx) { - textLineMatrix.offsetX = point2.dx; + textLineMatrix!.offsetX = point2.dx; } else { - textLineMatrix.offsetY = point2.dy; + textLineMatrix!.offsetY = point2.dy; } } void _updateTextMatrix(_Glyph glyph) { - textLineMatrix = _calculateTextMatrix(textLineMatrix, glyph); + textLineMatrix = _calculateTextMatrix(textLineMatrix!, glyph); } _MatrixHelper _calculateTextMatrix(_MatrixHelper m, _Glyph glyph) { if (glyph.charId == 32) { - glyph.wordSpacing = wordSpacing; + glyph.wordSpacing = wordSpacing!; } final double width = glyph.width; final double offsetX = @@ -793,7 +787,7 @@ class _TextElement { } class _TransformationStack { - _TransformationStack([_MatrixHelper transformMatrix]) { + _TransformationStack([_MatrixHelper? transformMatrix]) { _initialTransform = (transformMatrix != null) ? transformMatrix : _MatrixHelper(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); @@ -801,16 +795,16 @@ class _TransformationStack { } //Fields - _MatrixHelper _currentTransform; - _MatrixHelper _initialTransform; - Queue<_MatrixHelper> transformStack; + late _MatrixHelper _currentTransform; + _MatrixHelper? _initialTransform; + late Queue<_MatrixHelper> transformStack; //Properties - _MatrixHelper get currentTransform { + _MatrixHelper? get currentTransform { if (transformStack.isEmpty) { return _initialTransform; } - return _currentTransform * _initialTransform; + return _currentTransform * _initialTransform!; } //Implementation diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_glyph.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_glyph.dart index b5286b3ef..dabfbf581 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_glyph.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_glyph.dart @@ -2,29 +2,28 @@ part of pdf; /// Gets the details of character in the word. class TextGlyph { - TextGlyph._() { - _initialize(); + TextGlyph._(String text, String fontName, List fontStyle, + [Rect bounds = const Rect.fromLTWH(0, 0, 0, 0), double fontSize = 0]) { + this.text = text; + this.fontName = fontName; + this.fontStyle = fontStyle; + this.bounds = bounds; + this.fontSize = fontSize; } //Fields /// Gets the bounds of glyph. - Rect bounds; + late Rect bounds; /// Gets the text of glyph. - String text; + late String text; /// Gets the font size of glyph. - double fontSize; + late double fontSize; /// Gets the font name of glyph. - String fontName; + late String fontName; /// Gets the font style of glyph. - List fontStyle; - - //Implementation - void _initialize() { - bounds = Rect.fromLTWH(0, 0, 0, 0); - fontSize = 0; - } + late List fontStyle; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_line.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_line.dart index 79589cd15..131c0b061 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_line.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_line.dart @@ -8,27 +8,30 @@ class TextLine { bounds = Rect.fromLTWH(0, 0, 0, 0); fontSize = 0; text = ''; + pageIndex = 0; + fontName = ''; + fontStyle = []; } //Fields /// Gets the collection of words present in the line. - List wordCollection; + late List wordCollection; /// Gets the bounds of the text. - Rect bounds; + late Rect bounds; /// Gets the font name of the text. - String fontName; + late String fontName; /// Gets the font style of the text. - List fontStyle; + late List fontStyle; /// Gets the font size of the text. - double fontSize; + late double fontSize; /// Gets the page index. - int pageIndex; + late int pageIndex; /// Gets the text. - String text; + late String text; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_word.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_word.dart index e3b5fa07a..4d16b9f34 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_word.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_word.dart @@ -3,28 +3,33 @@ part of pdf; /// Details of a word present in the line. class TextWord { //constructor - TextWord._() { - _glyphs = []; - bounds = Rect.fromLTWH(0, 0, 0, 0); - fontSize = 0; + TextWord._(String text, String fontName, List fontStyle, + List glyphs, + [Rect bounds = const Rect.fromLTWH(0, 0, 0, 0), double fontSize = 0]) { + this.text = text; + this.fontName = fontName; + this.fontStyle = fontStyle; + _glyphs = glyphs; + this.bounds = bounds; + this.fontSize = fontSize; } //Fields /// Gets the text. - String text; + late String text; /// Gets the bounds of the word. - Rect bounds; + late Rect bounds; /// Gets the font size of the word. - double fontSize; + late double fontSize; /// Gets the font name of the word. - String fontName; + late String fontName; /// Gets the font style of the word. - List fontStyle; - List _glyphs; + late List fontStyle; + List _glyphs = []; //Properties /// Gets the text glyph with bounds in the word. diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/xobject_element.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/xobject_element.dart index 270ba8d3e..06d046611 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/xobject_element.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/xobject_element.dart @@ -2,10 +2,8 @@ part of pdf; class _XObjectElement { //constructor - _XObjectElement(_PdfDictionary dictionary, String name) { - if (dictionary != null) { - this.dictionary = dictionary; - } + _XObjectElement(_PdfDictionary dictionary, String? name) { + this.dictionary = dictionary; _objectName = name; getObjectType(); _isPrintSelected = false; @@ -13,30 +11,30 @@ class _XObjectElement { } //Fields - _PdfDictionary dictionary; + _PdfDictionary? dictionary; // ignore: unused_field - String _objectName; - String _objectType; - bool _isPrintSelected; - double _pageHeight; - bool _isExtractTextLine; + String? _objectName; + String? _objectType; + bool? _isPrintSelected; + double? _pageHeight; + bool? _isExtractTextLine; //Implementation void getObjectType() { - if (dictionary.containsKey(_DictionaryProperties.subtype)) { - final _IPdfPrimitive primitive = - dictionary[_DictionaryProperties.subtype]; + if (dictionary!.containsKey(_DictionaryProperties.subtype)) { + final _IPdfPrimitive? primitive = + dictionary![_DictionaryProperties.subtype]; if (primitive is _PdfName) { _objectType = primitive._name; } } } - _PdfRecordCollection render(_PdfPageResources resources) { + _PdfRecordCollection? render(_PdfPageResources? resources) { if (_objectType != null && _objectType == 'Form' && dictionary is _PdfStream) { - final _PdfStream stream = dictionary; + final _PdfStream stream = dictionary as _PdfStream; stream._decompress(); return _ContentParser(stream._dataStream)._readContent(); } else { @@ -45,30 +43,30 @@ class _XObjectElement { } Map _render( - _GraphicsObject g, - _PdfPageResources resources, - _GraphicStateCollection graphicsStates, - _GraphicObjectDataCollection objects, - double currentPageHeight, - List<_Glyph> glyphList) { + _GraphicsObject? g, + _PdfPageResources? resources, + _GraphicStateCollection? graphicsStates, + _GraphicObjectDataCollection? objects, + double? currentPageHeight, + List<_Glyph>? glyphList) { glyphList = <_Glyph>[]; - List<_TextElement> extractTextElement; + List<_TextElement>? extractTextElement; if (_objectType != null && _objectType == 'Form' && dictionary != null && dictionary is _PdfStream) { - final _PdfStream stream = dictionary; + final _PdfStream stream = dictionary as _PdfStream; stream._decompress(); final _ContentParser parser = _ContentParser(stream._dataStream); - final _PdfRecordCollection contentTree = parser._readContent(); + final _PdfRecordCollection? contentTree = parser._readContent(); final _PageResourceLoader resourceLoader = _PageResourceLoader(); _PdfDictionary pageDictionary = _PdfDictionary(); - final _PdfDictionary xobjects = dictionary; + final _PdfDictionary xobjects = dictionary!; _PdfPageResources childResource = _PdfPageResources(); if (xobjects.containsKey(_DictionaryProperties.resources)) { - _IPdfPrimitive primitive = xobjects[_DictionaryProperties.resources]; + _IPdfPrimitive? primitive = xobjects[_DictionaryProperties.resources]; if (primitive is _PdfReferenceHolder) { - primitive = (primitive as _PdfReferenceHolder).object; + primitive = primitive.object; if (primitive != null && primitive is _PdfDictionary) { pageDictionary = primitive; } @@ -82,66 +80,64 @@ class _XObjectElement { } _MatrixHelper xFormsMatrix = _MatrixHelper(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); if (xobjects.containsKey(_DictionaryProperties.matrix)) { - final _IPdfPrimitive arrayReference = + final _IPdfPrimitive? arrayReference = xobjects[_DictionaryProperties.matrix]; if (arrayReference is _PdfArray) { final _PdfArray matrixArray = arrayReference; - final double a = (matrixArray[0] as _PdfNumber).value.toDouble(); - final double b = (matrixArray[1] as _PdfNumber).value.toDouble(); - final double c = (matrixArray[2] as _PdfNumber).value.toDouble(); - final double d = (matrixArray[3] as _PdfNumber).value.toDouble(); - final double e = (matrixArray[4] as _PdfNumber).value.toDouble(); - final double f = (matrixArray[5] as _PdfNumber).value.toDouble(); + final double a = (matrixArray[0] as _PdfNumber).value!.toDouble(); + final double b = (matrixArray[1] as _PdfNumber).value!.toDouble(); + final double c = (matrixArray[2] as _PdfNumber).value!.toDouble(); + final double d = (matrixArray[3] as _PdfNumber).value!.toDouble(); + final double e = (matrixArray[4] as _PdfNumber).value!.toDouble(); + final double f = (matrixArray[5] as _PdfNumber).value!.toDouble(); xFormsMatrix = _MatrixHelper(a, b, c, d, e, f); if (e != 0 || f != 0) { - g._translateTransform(e, -f); + g!._translateTransform(e, -f); } if (a != 0 || d != 0) { - g._scaleTransform(a, d); + g!._scaleTransform(a, d); } //check for rotate transform final double degree = ((180 / pi) * acos(a)).round().toDouble(); final double checkDegree = ((180 / pi) * asin(b)).round().toDouble(); if (degree == checkDegree) { - g._rotateTransform(-degree); + g!._rotateTransform(-degree); } else { if (!checkDegree.isNaN) { - g._rotateTransform(-checkDegree); + g!._rotateTransform(-checkDegree); } else if (!degree.isNaN) { - g._rotateTransform(-degree); + g!._rotateTransform(-degree); } } } } - if (pageDictionary != null) { - final _ImageRenderer renderer = - _ImageRenderer(contentTree, childResource, currentPageHeight, g); - renderer._isExtractLineCollection = _isExtractTextLine; - renderer._objects = objects; - final _MatrixHelper parentMatrix = - objects.last.currentTransformationMatrix; - final _MatrixHelper newMatrix = xFormsMatrix * parentMatrix; - objects.last.drawing2dMatrixCTM = _MatrixHelper( - newMatrix.m11, - newMatrix.m12, - newMatrix.m21, - newMatrix.m22, - newMatrix.offsetX, - newMatrix.offsetY); - objects.last.currentTransformationMatrix = newMatrix; - renderer._selectablePrintDocument = _isPrintSelected; - renderer._pageHeight = _pageHeight; - renderer._isXGraphics = true; - renderer._renderAsImage(); - renderer._isXGraphics = false; - while (renderer._xobjectGraphicsCount > 0) { - objects._pop(); - renderer._xobjectGraphicsCount--; - } - glyphList = renderer.imageRenderGlyphList; - objects.last.currentTransformationMatrix = parentMatrix; - extractTextElement = renderer.extractTextElement; + final _ImageRenderer renderer = + _ImageRenderer(contentTree, childResource, currentPageHeight!, g); + renderer._isExtractLineCollection = _isExtractTextLine!; + renderer._objects = objects; + final _MatrixHelper parentMatrix = + objects!.last.currentTransformationMatrix!; + final _MatrixHelper newMatrix = xFormsMatrix * parentMatrix; + objects.last.drawing2dMatrixCTM = _MatrixHelper( + newMatrix.m11, + newMatrix.m12, + newMatrix.m21, + newMatrix.m22, + newMatrix.offsetX, + newMatrix.offsetY); + objects.last.currentTransformationMatrix = newMatrix; + renderer._selectablePrintDocument = _isPrintSelected; + renderer._pageHeight = _pageHeight; + renderer._isXGraphics = true; + renderer._renderAsImage(); + renderer._isXGraphics = false; + while (renderer._xobjectGraphicsCount > 0) { + objects._pop(); + renderer._xobjectGraphicsCount--; } + glyphList = renderer.imageRenderGlyphList; + objects.last.currentTransformationMatrix = parentMatrix; + extractTextElement = renderer.extractTextElement; } return { 'graphicStates': graphicsStates, @@ -192,20 +188,20 @@ String _unescape(String str) { } if (str[0] != '{') { final String str1 = str.substring(0, 4); - final int intValue = int.tryParse(str1, radix: 16); + final int? intValue = int.tryParse(str1, radix: 16); if (intValue == null || intValue < 0) { break; } str = str.substring(4); buffer.writeCharCode(intValue); } else { - final Match match = RegExp(r'{([a-zA-Z0-9]+)}').matchAsPrefix(str); + final Match? match = RegExp(r'{([a-zA-Z0-9]+)}').matchAsPrefix(str); if (match == null) { break; } else { str = str.substring(match.end); - final String str1 = match[1]; - final int intValue = int.tryParse(str1, radix: 16); + final String str1 = match[1]!; + final int? intValue = int.tryParse(str1, radix: 16); if (intValue == null || intValue < 0) { break; } @@ -220,7 +216,7 @@ String _unescape(String str) { } final String subStr = str.substring(0, 2); str = str.substring(2); - final int intValue = int.tryParse(subStr, radix: 16); + final int? intValue = int.tryParse(subStr, radix: 16); if (intValue == null || intValue < 0) { break; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/enum.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/enum.dart new file mode 100644 index 000000000..45fcb8069 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/enum.dart @@ -0,0 +1,167 @@ +part of pdf; + +/// Represents fields flags enum. +enum _FieldFlags { + //Common flags + /// Default field flag. + defaultFieldFlag, + + /// If set, the user may not change the value of the field. Any associated widget annotations + /// will not interact with the user; that is, they will not respond to mouse clicks or + /// change their appearance in response to mouse motions. This flag is useful + /// for fields whose values are computed or imported from a database. + readOnly, + + /// If set, the field must have a value at the time it is exported by a submit-form action. + requiredFieldFlag, + + /// If set, the field must not be exported by a submit-form action + noExport, + + //Text field flags + /// If set, the field can contain multiple lines of text; + /// if clear, the field’s text is restricted to a single line. + multiline, + + /// If set, the field is intended for entering a secure password that should not be + /// echoed visibly to the screen. Characters typed from the keyboard should instead + /// be echoed in some unreadable form, such as asterisks or bullet characters. + password, + + /// If set, the text entered in the field represents the pathname of a file whose + /// contents are to be submitted as the value of the field. + fileSelect, + + /// If set, text entered in the field is not spell-checked. + doNotSpellCheck, + + /// If set, the field does not scroll (horizontally for single-line fields, vertically + /// for multiple-line fields) to accommodate more text than fits within its annotation + /// rectangle. Once the field is full, no further text is accepted. + doNotScroll, + + /// Meaningful only if the MaxLen entry is present in the text field dictionary and if + /// the Multiline, Password, and FileSelect flags are clear. If set, the field is + /// automatically divided into as many equally spaced positions, or combs, as the + /// value of MaxLen, and the text is laid out into those combs. + comb, + + /// If set, the value of this field should be represented as a rich text string. + /// If the field has a value, the RVentry of the field dictionary specifies + /// the rich text string. + richText, + + //Button field flags + /// If set, exactly one radio button must be selected at all times; clicking + /// the currently selected button has no effect. If clear, clicking the selected + /// button reselects it, leaving no button selected. + noToggleToOff, + + /// If set, the field is a set of radio buttons; if clear, the field is a check box. + /// This flag is meaningful only if the Pushbutton flag is clear. + radio, + + /// If set, the field is a pushbutton that does not retain a permanent value. + pushButton, + + /// If set, a group of radio buttons within a radio button field that use the same value + /// for the on state will turn on and off in unison; that is if one is checked, they + /// are all checked. If clear, the buttons are mutually exclusive. + radiosInUnison, + + //Choise field flags + /// If set, the field is a combo box; if clear, the field is a list box. + combo, + + /// If set, the combo box includes an editable text box as well as a drop-down + /// list; if clear, it includes only a drop-down list. This flag is meaningful only + /// if the Combo flag is set. + edit, + + /// If set, the field’s option items should be sorted alphabetically. This flag + /// is intended for use by form authoring tools, not by PDF viewer applications. + sort, + + /// If set, more than one of the field’s option items may be selected simultaneously; + /// if clear, no more than one item at a time may be selected. + multiSelect, + + /// If set, the new value is committed as soon as a selection is made with the pointing + /// device. This option enables applications to perform an action once a selection is + /// made, without requiring the user to exit the field. If clear, the new value is not + /// committed until the user exits the field. + commitOnSelChange +} + +/// Specifies the style for a check box field. +enum PdfCheckBoxStyle { + /// A tick mark is used for the checked state. + check, + + /// A circle is used for the checked state. + circle, + + /// A cross is used for the checked state. + cross, + + /// A diamond symbol is used for the checked state. + diamond, + + /// A square is used for the checked state. + square, + + /// A star is used for the checked state. + star +} + +enum _PdfCheckFieldState { + /// Indicated unchecked/unpressed state. + unchecked, + + /// Indicated checked unpressed state. + checked, + + /// Indicated pressed unchecked state. + pressedUnchecked, + + /// Indicated pressed checked state. + pressedChecked +} + +enum _PdfFieldTypes { + //Identify text field. + textField, + //Identify push button field. + pushButton, + //Identify check box field. + checkBox, + //Identify radio button field. + radioButton, + //Identify signature field. + signatureField, + //Identify listbox field. + listBox, + //Identify combobox field. + comboBox, + //Identify that field has no type. + none +} + +enum _SignatureFlags { + /// No flags specified. + none, + + /// If set, the document contains at least one signature field. This flag allows a viewer + /// application to enable user interface items (such as menu items or pushbuttons) related + /// to signature processing without having to scan the entire document for the presence + /// of signature fields. + signaturesExists, + + /// If set, the document contains signatures that may be invalidated if the file is saved + /// (written) in a way that alters its previous contents, as opposed to an incremental + /// update. Merely updating the file by appending new information to the end of the + /// previous version is safe. Viewer applications can use this flag to present + /// a user requesting a full save with an additional alert box warning that signatures + /// will be invalidated and requiring explicit confirmation before continuing with the operation. + appendOnly +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_button_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_button_field.dart new file mode 100644 index 000000000..ac81e2900 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_button_field.dart @@ -0,0 +1,499 @@ +part of pdf; + +/// Represents button field in the PDF form. +class PdfButtonField extends PdfField { + //Constructor + /// Initializes an instance of the [PdfButtonField] class with the specific + /// page, name, and bounds. + PdfButtonField(PdfPage page, String name, Rect bounds, + {String? text, + PdfFont? font, + PdfColor? borderColor, + PdfColor? backColor, + PdfColor? foreColor, + int? borderWidth, + PdfHighlightMode highlightMode = PdfHighlightMode.invert, + PdfBorderStyle borderStyle = PdfBorderStyle.solid, + PdfFieldActions? actions, + String? tooltip}) + : super(page, name, bounds, + tooltip: tooltip, + font: font, + borderColor: borderColor, + backColor: backColor, + foreColor: foreColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle) { + _initValues(text, actions); + } + + PdfButtonField._loaded(_PdfDictionary dictionary, _PdfCrossTable crossTable) + : super._load(dictionary, crossTable); + + //Fields + String _text = ''; + PdfFieldActions? _actions; + + // Properties + /// Gets or sets the caption text. + String get text => _isLoadedField ? _obtainText() : _text; + set text(String value) { + if (_isLoadedField) { + final bool readOnly = ((1 & (_flagValues ?? 65536)) != 0); + if (!readOnly) { + this.form!._setAppearanceDictionary = true; + _assignText(value); + } + } else { + if (_text != value) { + _text = value; + _widget!._widgetAppearance!.normalCaption = _text; + } + } + } + + /// Gets or sets the font. + PdfFont get font => _font!; + set font(PdfFont value) { + _font = value; + } + + /// Gets or sets the border style. + /// + /// The default style is solid. + PdfBorderStyle get borderStyle => _borderStyle; + set borderStyle(PdfBorderStyle value) => _borderStyle = value; + + /// Gets or sets the color of the border. + /// + /// The default color is black. + PdfColor get borderColor => _borderColor; + set borderColor(PdfColor value) => _borderColor = value; + + /// Gets or sets the color of the background. + /// + /// The default color is empty. + PdfColor get backColor => _backColor; + set backColor(PdfColor value) => _backColor = value; + + /// Gets or sets the color of the text. + /// + /// The default color is black. + PdfColor get foreColor => _foreColor; + set foreColor(PdfColor value) => _foreColor = value; + + /// Gets or sets the width of the border. + /// + /// The default value is 1. + int get borderWidth => _borderWidth; + set borderWidth(int value) => _borderWidth = value; + + /// Gets or sets the highlighting mode. + /// + /// The default mode is invert. + PdfHighlightMode get highlightMode => _highlightMode; + set highlightMode(PdfHighlightMode value) => _highlightMode = value; + + /// Gets the actions of the field.{Read-Only} + PdfFieldActions get actions { + if (_isLoadedField && _actions == null) { + if (_dictionary.containsKey(_DictionaryProperties.aa)) { + final _PdfDictionary actionDict = + _crossTable!._getObject(_dictionary[_DictionaryProperties.aa]) + as _PdfDictionary; + _actions = PdfFieldActions._loaded(actionDict); + _widget!.actions = _actions!._annotationActions; + } else { + _actions = PdfFieldActions._loaded(_PdfDictionary()); + _dictionary.setProperty(_DictionaryProperties.aa, _actions); + } + _changed = true; + } else { + if (_actions == null) { + _actions = PdfFieldActions(_widget!.actions!); + _dictionary.setProperty(_DictionaryProperties.aa, _actions); + } + } + return _actions!; + } + + //Implementation + void _initValues(String? txt, PdfFieldActions? action) { + _format!.alignment = PdfTextAlignment.center; + _widget!.textAlignment = PdfTextAlignment.center; + text = txt != null ? txt : name!; + if (action != null) { + _actions = action; + _widget!.actions = action._annotationActions; + _dictionary.setProperty(_DictionaryProperties.aa, _actions); + } + } + + @override + void _initialize() { + super._initialize(); + _dictionary.setProperty( + _DictionaryProperties.ft, _PdfName(_DictionaryProperties.btn)); + _backColor = PdfColor(211, 211, 211, 255); + _flags.add(_FieldFlags.pushButton); + } + + /// Adds Print action to current button field. + void addPrintAction() { + _addPrintAction(); + } + + void _addPrintAction() { + final _PdfDictionary actionDictionary = _PdfDictionary(); + actionDictionary.setProperty( + _DictionaryProperties.n, _PdfName(_DictionaryProperties.print)); + actionDictionary.setProperty(_DictionaryProperties.s, _PdfName('Named')); + if (_isLoadedField) { + final _PdfArray? kidsArray = _crossTable! + ._getObject(_dictionary[_DictionaryProperties.kids]) as _PdfArray?; + if (kidsArray != null) { + final _PdfReferenceHolder buttonObject = + kidsArray[0] as _PdfReferenceHolder; + final _PdfDictionary buttonDictionary = + buttonObject._object as _PdfDictionary; + buttonDictionary.setProperty(_DictionaryProperties.a, actionDictionary); + } else { + _dictionary.setProperty(_DictionaryProperties.a, actionDictionary); + } + } else { + final _PdfArray kidsArray = + _dictionary[_DictionaryProperties.kids] as _PdfArray; + final _PdfReferenceHolder buttonObject = + kidsArray[0] as _PdfReferenceHolder; + final _PdfDictionary buttonDictionary = + buttonObject._object as _PdfDictionary; + buttonDictionary.setProperty(_DictionaryProperties.a, actionDictionary); + } + } + + @override + void _save() { + super._save(); + if (page != null && + page!.formFieldsTabOrder == PdfFormFieldsTabOrder.manual && + !(page!._isLoadedPage)) { + final PdfPage? page = this.page; + final PdfField textField = this; + if (textField._widget != null) { + page!.annotations.remove(textField._widget!); + page.annotations._annotations + ._insert(this.tabIndex, _PdfReferenceHolder(textField._widget)); + page.annotations._list.insert(this.tabIndex, textField._widget!); + } + } + if (form != null && !form!._needAppearances!) { + if (_widget!._pdfAppearance == null) { + _drawAppearance(_widget!.appearance.normal); + } + } + if (form != null && !form!._needAppearances!) { + if (_widget!.appearance._templatePressed == null) { + _drawPressedAppearance(_widget!.appearance.pressed); + } + } + } + + @override + void _drawAppearance(PdfTemplate template) { + super._drawAppearance(template); + if (text.isEmpty) { + text = name!; + } + final _PaintParams paintParams = _PaintParams( + bounds: Rect.fromLTWH( + 0, 0, _widget!.bounds.size.width, _widget!.bounds.size.height), + backBrush: PdfSolidBrush(_backColor), + foreBrush: PdfSolidBrush(_foreColor), + borderPen: _borderPen, + style: _borderStyle, + borderWidth: _borderWidth, + shadowBrush: PdfSolidBrush(_backColor), + rotationAngle: 0); + _FieldPainter().drawButton( + template.graphics!, + paintParams, + text, + (_font == null) ? PdfStandardFont(PdfFontFamily.helvetica, 8) : _font!, + _format); + } + + void _drawPressedAppearance(PdfTemplate template) { + if (text.isEmpty) { + text = name!; + } + final _PaintParams paintParams = _PaintParams( + bounds: Rect.fromLTWH( + 0, 0, _widget!.bounds.size.width, _widget!.bounds.size.height), + backBrush: PdfSolidBrush(_backColor), + foreBrush: PdfSolidBrush(_foreColor), + borderPen: _borderPen, + style: _borderStyle, + borderWidth: _borderWidth, + shadowBrush: PdfSolidBrush(_backColor), + rotationAngle: 0); + _FieldPainter().drawPressedButton( + template.graphics!, + paintParams, + text, + (_font == null) ? PdfStandardFont(PdfFontFamily.helvetica, 8) : _font!, + _format); + } + + String _obtainText() { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + String? str; + if (widget.containsKey(_DictionaryProperties.mk)) { + final _PdfDictionary appearance = _crossTable! + ._getObject(widget[_DictionaryProperties.mk]) as _PdfDictionary; + if (appearance.containsKey(_DictionaryProperties.ca)) { + final _PdfString text = _crossTable! + ._getObject(appearance[_DictionaryProperties.ca]) as _PdfString; + str = text.value; + } + } + if (str == null) { + _PdfString? val = _crossTable! + ._getObject(_dictionary[_DictionaryProperties.v]) as _PdfString?; + if (val == null) { + val = PdfField._getValue( + _dictionary, _crossTable, _DictionaryProperties.v, true) + as _PdfString?; + } + if (val != null) { + str = val.value; + } else { + str = ''; + } + } + return str!; + } + + void _assignText(String value) { + final String text = value; + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + if (widget.containsKey(_DictionaryProperties.mk)) { + final _PdfDictionary appearance = _crossTable! + ._getObject(widget[_DictionaryProperties.mk]) as _PdfDictionary; + appearance._setString(_DictionaryProperties.ca, text); + widget.setProperty( + _DictionaryProperties.mk, _PdfReferenceHolder(appearance)); + } else { + final _PdfDictionary appearance = _PdfDictionary(); + appearance._setString(_DictionaryProperties.ca, text); + widget.setProperty( + _DictionaryProperties.mk, _PdfReferenceHolder(appearance)); + } + if (widget.containsKey(_DictionaryProperties.ap)) { + _applyAppearance(widget, null); + } + _changed = true; + } + + @override + void _beginSave() { + super._beginSave(); + final _PdfArray? kids = _obtainKids(); + if ((kids != null)) { + for (int i = 0; i < kids.count; ++i) { + final _PdfDictionary? widget = + _crossTable!._getObject(kids[i]) as _PdfDictionary?; + _applyAppearance(widget, this); + } + } + } + + void _draw() { + super._draw(); + if (_isLoadedField) { + final _PdfArray? kids = _obtainKids(); + if ((kids != null) && (kids.count > 1)) { + for (int i = 0; i < kids.count; ++i) { + if (this.page != null) { + final _PdfDictionary? widget = + _crossTable!._getObject(kids[i]) as _PdfDictionary?; + _drawButton(this.page!.graphics, this, widget); + } + } + } else { + _drawButton(page!.graphics, null); + } + } else { + if (_widget!._pdfAppearance != null) { + page!.graphics.drawPdfTemplate(_widget!._pdfAppearance!.normal, + Offset(_widget!.bounds.left, _widget!.bounds.top)); + } else { + Rect rect = bounds; + rect = Rect.fromLTWH(0, 0, bounds.width, bounds.height); + PdfFont? font = _font; + if (font == null) { + font = PdfStandardFont(PdfFontFamily.helvetica, 8); + } + final _PaintParams params = _PaintParams( + bounds: rect, + backBrush: _backBrush, + foreBrush: _foreBrush, + borderPen: _borderPen, + style: borderStyle, + borderWidth: borderWidth, + shadowBrush: _shadowBrush, + rotationAngle: 0); + final PdfTemplate template = PdfTemplate(rect.width, rect.height); + _FieldPainter() + .drawButton(template.graphics!, params, text, font, _stringFormat); + page!.graphics.drawPdfTemplate( + template, Offset(bounds.left, bounds.top), rect.size); + page!.graphics.drawString((text.isEmpty) ? name! : text, font, + brush: params._foreBrush, bounds: bounds, format: _stringFormat); + } + } + } + + _PdfArray? _obtainKids() { + _PdfArray? kids; + if (_dictionary.containsKey(_DictionaryProperties.kids)) { + kids = _crossTable!._getObject(_dictionary[_DictionaryProperties.kids]) + as _PdfArray?; + } + return kids; + } + + void _applyAppearance(_PdfDictionary? widget, PdfButtonField? item) { + if ((_actions != null) && _actions!._isChanged) { + widget!.setProperty(_DictionaryProperties.aa, _actions); + } + if ((widget != null) && (widget.containsKey(_DictionaryProperties.ap))) { + final _PdfDictionary? appearance = _crossTable! + ._getObject(widget[_DictionaryProperties.ap]) as _PdfDictionary?; + if ((appearance != null) && + (appearance.containsKey(_DictionaryProperties.n))) { + final Rect bounds = (item == null) ? super.bounds : item.bounds; + PdfTemplate template = PdfTemplate(bounds.width, bounds.height); + final PdfTemplate pressedTemplate = + PdfTemplate(bounds.width, bounds.height); + if (widget.containsKey(_DictionaryProperties.mk)) { + _PdfDictionary? mkDic; + if (widget[_DictionaryProperties.mk] is _PdfReferenceHolder) { + mkDic = _crossTable!._getObject(widget[_DictionaryProperties.mk]) + as _PdfDictionary?; + } else { + mkDic = widget[_DictionaryProperties.mk] as _PdfDictionary?; + } + if (mkDic != null && mkDic.containsKey(_DictionaryProperties.r)) { + final _PdfNumber? angle = + mkDic[_DictionaryProperties.r] as _PdfNumber?; + if (angle != null) { + if (angle.value == 90) { + template = PdfTemplate(bounds.size.height, bounds.size.width); + template._writeTransformation = false; + template._content[_DictionaryProperties.matrix] = + _PdfArray([0, 1, -1, 0, bounds.size.width, 0]); + } else if (angle.value == 180) { + template = PdfTemplate(bounds.size.width, bounds.size.height); + template._writeTransformation = false; + template._content[_DictionaryProperties.matrix] = + _PdfArray([ + -1, + 0, + 0, + -1, + bounds.size.width, + bounds.size.height + ]); + } else if (angle.value == 270) { + template = PdfTemplate(bounds.size.height, bounds.size.width); + template._writeTransformation = false; + template._content[_DictionaryProperties.matrix] = + _PdfArray([0, -1, 1, 0, 0, bounds.size.height]); + } + } + } + } + _drawButton(template.graphics!, item, widget); + _drawButton(pressedTemplate.graphics!, item, widget); + appearance.setProperty( + _DictionaryProperties.n, _PdfReferenceHolder(template)); + appearance.setProperty( + _DictionaryProperties.d, _PdfReferenceHolder(pressedTemplate)); + widget.setProperty(_DictionaryProperties.ap, appearance); + } + } else if (super.form!._setAppearanceDictionary) { + super.form!._needAppearances = true; + } + } + + void _drawButton(PdfGraphics? graphics, PdfButtonField? item, + [_PdfDictionary? widget]) { + final _GraphicsProperties gp = _GraphicsProperties(this); + if (!_flattenField) { + gp._bounds = Rect.fromLTWH(0, 0, gp._bounds!.width, gp._bounds!.height); + } + final _PaintParams prms = _PaintParams( + bounds: gp._bounds, + backBrush: gp._backBrush, + foreBrush: gp._foreBrush, + borderPen: gp._borderPen, + style: gp._style, + borderWidth: gp._borderWidth, + shadowBrush: gp._shadowBrush, + rotationAngle: 0); + if (this._dictionary.containsKey(_DictionaryProperties.ap) && + !(graphics!._layer != null && + graphics._page!._rotation != PdfPageRotateAngle.rotateAngle0)) { + _IPdfPrimitive? buttonAppearance = + this._dictionary[_DictionaryProperties.ap]; + if (buttonAppearance == null) { + buttonAppearance = widget![_DictionaryProperties.ap]; + } + _PdfDictionary? buttonResource = + _PdfCrossTable._dereference(buttonAppearance) as _PdfDictionary?; + if (buttonResource != null) { + buttonAppearance = buttonResource[_DictionaryProperties.n]; + buttonResource = + _PdfCrossTable._dereference(buttonAppearance) as _PdfDictionary?; + if (buttonResource != null) { + final _PdfStream? stream = buttonResource as _PdfStream?; + if (stream != null) { + final PdfTemplate buttonShape = PdfTemplate._fromPdfStream(stream); + page!.graphics + .drawPdfTemplate(buttonShape, Offset(bounds.left, bounds.top)); + } + } + } + } else if (this._dictionary.containsKey(_DictionaryProperties.kids) && + item != null && + !(graphics!._layer != null && + graphics._page!._rotation != PdfPageRotateAngle.rotateAngle0)) { + _IPdfPrimitive? buttonAppearance = + item._dictionary[_DictionaryProperties.ap]; + if (buttonAppearance == null) { + buttonAppearance = widget![_DictionaryProperties.ap]; + } + _PdfDictionary? buttonResource = + _PdfCrossTable._dereference(buttonAppearance) as _PdfDictionary?; + if (buttonResource != null) { + buttonAppearance = buttonResource[_DictionaryProperties.n]; + buttonResource = + _PdfCrossTable._dereference(buttonAppearance) as _PdfDictionary?; + if (buttonResource != null) { + final _PdfStream? stream = buttonResource as _PdfStream?; + if (stream != null) { + final PdfTemplate buttonShape = PdfTemplate._fromPdfStream(stream); + page!.graphics + .drawPdfTemplate(buttonShape, Offset(bounds.left, bounds.top)); + } + } + } + } else { + _FieldPainter() + .drawButton(graphics!, prms, text, gp._font!, gp._stringFormat); + } + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_check_box_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_check_box_field.dart new file mode 100644 index 000000000..0d4754f26 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_check_box_field.dart @@ -0,0 +1,314 @@ +part of pdf; + +/// Represents check box field in the PDF form. +class PdfCheckBoxField extends PdfCheckFieldBase { + //Constructor + /// Initializes a new instance of the [PdfCheckBoxField] class with + /// the specific page, name and bounds. + PdfCheckBoxField(PdfPage page, String name, Rect bounds, + {bool isChecked = false, + PdfCheckBoxStyle style = PdfCheckBoxStyle.check, + PdfColor? borderColor, + PdfColor? backColor, + PdfColor? foreColor, + int? borderWidth, + PdfHighlightMode highlightMode = PdfHighlightMode.invert, + PdfBorderStyle borderStyle = PdfBorderStyle.solid, + String? tooltip}) + : super(page, name, bounds, + style: style, + borderColor: borderColor, + backColor: backColor, + foreColor: foreColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip) { + _setCheckBoxValue(isChecked); + } + + PdfCheckBoxField._loaded(_PdfDictionary dictionary, _PdfCrossTable crossTable) + : super._loaded(dictionary, crossTable) { + _items = PdfFieldItemCollection._(this); + final _PdfArray? kids = _kids; + if (kids != null) { + for (int i = 0; i < kids.count; ++i) { + final _PdfDictionary? itemDictionary = + crossTable._getObject(kids[i]) as _PdfDictionary?; + _items!._add(PdfCheckBoxItem._(this, i, itemDictionary)); + } + _array = kids; + } + } + + //Fields + bool _checked = false; + PdfFieldItemCollection? _items; + + //Properties + /// Gets or sets a value indicating whether this [PdfCheckBoxField] is checked. + /// + /// The default value is false. + bool get isChecked { + if (_isLoadedField) { + if (items != null && items!.count > 0) { + final _IPdfPrimitive? state = _PdfCrossTable._dereference( + items![_defaultIndex] + ._dictionary![_DictionaryProperties.usageApplication]); + if (state == null) { + final _IPdfPrimitive? name = PdfField._getValue( + _dictionary, _crossTable, _DictionaryProperties.v, false); + if (name != null && name is _PdfName) { + _checked = (name._name == + _getItemValue(items![_defaultIndex]._dictionary!, _crossTable)); + } + } else if (state is _PdfName) { + _checked = (state._name != _DictionaryProperties.off); + } + return _checked; + } + if (_dictionary.containsKey(_DictionaryProperties.v)) { + final _PdfName chk = _dictionary[_DictionaryProperties.v] as _PdfName; + _checked = (chk._name != 'Off') ? true : false; + } + } + return _checked; + } + + set isChecked(bool value) { + if (_isLoadedField) { + if (_dictionary.containsKey(_DictionaryProperties.v)) { + final _PdfName chk = _dictionary[_DictionaryProperties.v] as _PdfName; + _checked = (chk._name != 'Off') ? true : false; + } + this.form!._setAppearanceDictionary = true; + if (_form!._needAppearances == false) { + _changed = true; + } + } + if (_checked != value) { + _checked = value; + String? val; + if (_isLoadedField) { + val = _enableCheckBox(value); + _enableItems(value, val); + } + if (_checked) { + _dictionary._setName(_PdfName(_DictionaryProperties.v), + val != null ? val : _DictionaryProperties.yes); + } else { + _dictionary.remove(_DictionaryProperties.v); + if (_dictionary.containsKey(_DictionaryProperties.usageApplication)) { + _dictionary._setName(_PdfName(_DictionaryProperties.usageApplication), + _DictionaryProperties.off); + } + } + } + } + + /// Gets the collection of check box field items. + PdfFieldItemCollection? get items => _items; + + //Implementation + @override + void _save() { + super._save(); + if (form != null) { + if (!isChecked) { + _widget!._appearanceState = _DictionaryProperties.off; + } else { + _widget!._appearanceState = _DictionaryProperties.yes; + } + } + if (_fieldItems != null && _fieldItems!.length > 1) { + for (int i = 1; i < _fieldItems!.length; i++) { + final PdfCheckBoxField field = _fieldItems![i] as PdfCheckBoxField; + field.isChecked = isChecked; + field._save(); + } + } + } + + String? _enableCheckBox(bool value) { + bool isChecked = false; + String? val = null; + if (_dictionary.containsKey(_DictionaryProperties.usageApplication)) { + final _PdfName? state = _PdfCrossTable._dereference( + _dictionary[_DictionaryProperties.usageApplication]) as _PdfName?; + if (state != null) { + isChecked = state._name != _DictionaryProperties.off; + } + } + if (value != isChecked) { + val = _getItemValue(_dictionary, _crossTable); + if (value) { + if (val == null || val == '') { + val = _DictionaryProperties.yes; + } + _dictionary.setProperty( + _DictionaryProperties.usageApplication, _PdfName(val)); + _changed = true; + } + } + return val; + } + + void _enableItems(bool check, String? value) { + if (items != null && items!.count > 0) { + final _PdfDictionary? dic = items![_defaultIndex]._dictionary; + if (dic != null) { + if (value == null || value.isEmpty) { + value = _getItemValue(dic, _crossTable); + } + if (value == null || value.isEmpty) { + value = _DictionaryProperties.yes; + } + if (check) { + dic.setProperty( + _DictionaryProperties.usageApplication, _PdfName(value)); + dic.setProperty(_DictionaryProperties.v, _PdfName(value)); + } else { + dic._setName(_PdfName(_DictionaryProperties.usageApplication), + _DictionaryProperties.off); + } + } + } + } + + @override + void _drawCheckAppearance() { + super._drawCheckAppearance(); + final _PaintParams paintParams = _PaintParams( + bounds: Rect.fromLTWH( + 0, 0, _widget!.bounds.size.width, _widget!.bounds.size.height), + backBrush: _backBrush, + foreBrush: _foreBrush, + borderPen: _borderPen, + style: _borderStyle, + borderWidth: _borderWidth, + shadowBrush: _shadowBrush); + + PdfTemplate template = _widget!.extendedAppearance!.normal.activate!; + _FieldPainter()._drawCheckBox(template.graphics!, paintParams, + _styleToString(style), _PdfCheckFieldState.checked, _font); + template = _widget!.extendedAppearance!.normal.off!; + _FieldPainter()._drawCheckBox(template.graphics!, paintParams, + _styleToString(style), _PdfCheckFieldState.unchecked, _font); + + template = _widget!.extendedAppearance!.pressed.activate!; + _FieldPainter()._drawCheckBox(template.graphics!, paintParams, + _styleToString(style), _PdfCheckFieldState.pressedChecked, _font); + template = _widget!.extendedAppearance!.pressed.off!; + _FieldPainter()._drawCheckBox(template.graphics!, paintParams, + _styleToString(style), _PdfCheckFieldState.pressedUnchecked, _font); + } + + void _setCheckBoxValue(bool isChecked) { + this.isChecked = isChecked; + } + + @override + void _beginSave() { + final _PdfArray? kids = _obtainKids(); + if (kids != null) { + for (int i = 0; i < kids.count; ++i) { + final _PdfDictionary? widget = + _crossTable!._getObject(kids[i]) as _PdfDictionary?; + _applyAppearance(widget, null, _items![i]); + } + } else { + _applyAppearance(null, this); + } + } + + void _draw() { + super._draw(); + final _PdfCheckFieldState state = + isChecked ? _PdfCheckFieldState.checked : _PdfCheckFieldState.unchecked; + if (!_isLoadedField) { + final _PaintParams params = _PaintParams( + bounds: bounds, + backBrush: _backBrush, + foreBrush: _foreBrush, + borderPen: _borderPen, + style: borderStyle, + borderWidth: borderWidth, + shadowBrush: _shadowBrush); + if (_fieldItems != null && _fieldItems!.length > 0) { + for (int i = 0; i < _array.count; i++) { + final PdfCheckBoxField item = _fieldItems![i] as PdfCheckBoxField; + params._bounds = item.bounds; + params._backBrush = item._backBrush; + params._foreBrush = item._foreBrush; + params._borderPen = item._borderPen; + params._style = item._borderStyle; + params._borderWidth = item._borderWidth; + params._shadowBrush = item._shadowBrush; + _FieldPainter()._drawCheckBox( + item.page!.graphics, params, _styleToString(item.style), state); + } + } else { + _FieldPainter()._drawCheckBox( + page!.graphics, params, _styleToString(style), state); + } + } else { + final _PdfArray? kids = _kids; + if ((kids != null)) { + for (int i = 0; i < kids.count; ++i) { + final PdfCheckBoxItem item = _items![i] as PdfCheckBoxItem; + if (item.page != null) { + _drawStateItem(item.page!.graphics, state, null, item); + } + } + } else { + _drawStateItem(page!.graphics, state, this); + } + } + } +} + +/// Represents loaded check box item. +class PdfCheckBoxItem extends PdfFieldItem { + PdfCheckBoxItem._(PdfField field, int index, _PdfDictionary? dictionary) + : super._(field, index, dictionary); + + //Implementation + void _setStyle(PdfCheckBoxStyle value) { + String style = ''; + if (_dictionary!.containsKey(_DictionaryProperties.mk)) { + switch (value) { + case PdfCheckBoxStyle.check: + style = "4"; + break; + case PdfCheckBoxStyle.circle: + style = "l"; + break; + case PdfCheckBoxStyle.cross: + style = "8"; + break; + case PdfCheckBoxStyle.diamond: + style = "u"; + break; + case PdfCheckBoxStyle.square: + style = "n"; + break; + case PdfCheckBoxStyle.star: + style = "H"; + break; + } + final _IPdfPrimitive? mk = _dictionary![_DictionaryProperties.mk]; + if (mk is _PdfReferenceHolder) { + final _IPdfPrimitive? widgetDict = mk.object; + if (widgetDict is _PdfDictionary) { + if (widgetDict.containsKey(_DictionaryProperties.ca)) { + widgetDict[_DictionaryProperties.ca] = _PdfString(style); + } else { + widgetDict.setProperty(_DictionaryProperties.ca, _PdfString(style)); + } + } + } else if (mk is _PdfDictionary) { + mk[_DictionaryProperties.ca] = _PdfString(style); + } + } + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_check_field_base.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_check_field_base.dart new file mode 100644 index 000000000..970aba8c6 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_check_field_base.dart @@ -0,0 +1,356 @@ +part of pdf; + +/// Represents base class for field which can be checked and unchecked states. +class PdfCheckFieldBase extends PdfField { + //Contructor + /// Initializes a instance of the [PdfCheckFieldBase] class with + /// the specific page, name and bounds. + PdfCheckFieldBase(PdfPage? page, String? name, Rect bounds, + {PdfCheckBoxStyle? style, + PdfColor? borderColor, + PdfColor? backColor, + PdfColor? foreColor, + int? borderWidth, + PdfHighlightMode? highlightMode, + PdfBorderStyle? borderStyle, + String? tooltip}) + : super(page, name, bounds, + borderColor: borderColor, + backColor: backColor, + foreColor: foreColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip) { + _initValues(style); + } + + PdfCheckFieldBase._loaded( + _PdfDictionary dictionary, _PdfCrossTable crossTable) + : super._load(dictionary, crossTable); + + //Fields + PdfCheckBoxStyle _style = PdfCheckBoxStyle.check; + PdfTemplate? _checkedTemplate; + PdfTemplate? _uncheckedTemplate; + PdfTemplate? _pressedCheckedTemplate; + PdfTemplate? _pressedUncheckedTemplate; + + //Properties + /// Gets or sets the style. + /// + /// The default style is check. + PdfCheckBoxStyle get style => _isLoadedField ? _obtainStyle() : _style; + set style(PdfCheckBoxStyle value) { + if (_isLoadedField) { + _assignStyle(value); + if (this is PdfCheckBoxField && + (this as PdfCheckBoxField).items != null) { + final PdfFieldItemCollection items = (this as PdfCheckBoxField).items!; + for (int i = 0; i < items.count; i++) { + (items[i] as PdfCheckBoxItem)._setStyle(value); + } + } + if (form!._needAppearances == false) { + _changed = true; + _fieldChanged = true; + } + } else { + if (_style != value) { + _style = value; + _widget!._widgetAppearance!.normalCaption = _styleToString(_style); + } + } + } + + /// Gets or sets the color of the border. + /// + /// The default color is black. + PdfColor get borderColor => _borderColor; + set borderColor(PdfColor value) => _borderColor = value; + + /// Gets or sets the color of the background. + /// + /// The default color is empty. + PdfColor get backColor => _backColor; + set backColor(PdfColor value) => _backColor = value; + + /// Gets or sets the color of the text. + /// + /// The default color is black. + PdfColor get foreColor => _foreColor; + set foreColor(PdfColor value) => _foreColor = value; + + /// Gets or sets the width of the border. + /// + /// The default value is 1. + int get borderWidth => _borderWidth; + set borderWidth(int value) => _borderWidth = value; + + /// Gets or sets the highlighting mode. + /// + /// The default mode is invert. + PdfHighlightMode get highlightMode => _highlightMode; + set highlightMode(PdfHighlightMode value) => _highlightMode = value; + + /// Gets or sets the border style. + /// + /// The default style is solid. + PdfBorderStyle get borderStyle => _borderStyle; + set borderStyle(PdfBorderStyle value) => _borderStyle = value; + + //Implementation + String _styleToString(PdfCheckBoxStyle style) { + switch (style) { + case PdfCheckBoxStyle.circle: + return 'l'; + case PdfCheckBoxStyle.cross: + return '8'; + case PdfCheckBoxStyle.diamond: + return 'u'; + case PdfCheckBoxStyle.square: + return 'n'; + case PdfCheckBoxStyle.star: + return 'H'; + case PdfCheckBoxStyle.check: + default: + return '4'; + } + } + + @override + void _initialize() { + super._initialize(); + _dictionary.setProperty( + _DictionaryProperties.ft, _PdfName(_DictionaryProperties.btn)); + } + + void _initValues(PdfCheckBoxStyle? boxStyle) { + if (boxStyle != null) { + style = boxStyle; + } + } + + @override + void _save() { + super._save(); + if (form != null) { + final Map checkValue = + _createTemplate(_checkedTemplate); + _checkedTemplate = checkValue['template']; + final Map unCheckValue = + _createTemplate(_uncheckedTemplate); + _uncheckedTemplate = unCheckValue['template']; + final Map pressedValue = + _createTemplate(_pressedCheckedTemplate); + _pressedCheckedTemplate = pressedValue['template']; + final Map unPressedValue = + _createTemplate(_pressedUncheckedTemplate); + _pressedUncheckedTemplate = unPressedValue['template']; + + _widget!.extendedAppearance!.normal.activate = _checkedTemplate; + _widget!.extendedAppearance!.normal.off = _uncheckedTemplate; + _widget!.extendedAppearance!.pressed.activate = _pressedCheckedTemplate; + _widget!.extendedAppearance!.pressed.off = _pressedUncheckedTemplate; + _drawCheckAppearance(); + } else { + _releaseTemplate(_checkedTemplate); + _releaseTemplate(_uncheckedTemplate); + _releaseTemplate(_pressedCheckedTemplate); + _releaseTemplate(_pressedUncheckedTemplate); + } + } + + Map _createTemplate(PdfTemplate? template) { + if (template == null) { + template = + PdfTemplate(_widget!.bounds.size.width, _widget!.bounds.size.height); + } else { + template.reset(_widget!.bounds.size.width, _widget!.bounds.size.height); + } + return {'template': template}; + } + + void _releaseTemplate(PdfTemplate? template) { + if (template != null) { + template.reset(); + _widget!._extendedAppearance = null; + } + } + + void _drawCheckAppearance() {} + + void _applyAppearance(_PdfDictionary? widget, PdfCheckFieldBase? item, + [PdfFieldItem? fieldItem]) { + if (widget != null && item != null) { + if (item._dictionary.containsKey(_DictionaryProperties.v) && + !(item is PdfRadioButtonListItem)) { + widget._setName( + _PdfName(_DictionaryProperties.v), _DictionaryProperties.yes); + widget._setName(_PdfName(_DictionaryProperties.usageApplication), + _DictionaryProperties.yes); + } else if (!item._dictionary.containsKey(_DictionaryProperties.v) && + !(item is PdfRadioButtonListItem)) { + widget.remove(_DictionaryProperties.v); + widget._setName(_PdfName(_DictionaryProperties.usageApplication), + _DictionaryProperties.off); + } + } else if (widget != null && fieldItem != null) { + widget = fieldItem._dictionary; + } else { + widget = item!._dictionary; + } + if ((widget != null) && (widget.containsKey(_DictionaryProperties.ap))) { + final _PdfDictionary? appearance = _crossTable! + ._getObject(widget[_DictionaryProperties.ap]) as _PdfDictionary?; + if ((appearance != null) && + (appearance.containsKey(_DictionaryProperties.n))) { + String? value = ''; + Rect rect; + if (item != null) { + value = _getItemValue(widget, item._crossTable); + rect = item.bounds; + } else if (fieldItem != null) { + value = _getItemValue(widget, fieldItem._field._crossTable); + rect = fieldItem.bounds; + } else { + value = _getItemValue(widget, _crossTable); + rect = bounds; + } + _IPdfPrimitive? holder = + _PdfCrossTable._dereference(appearance[_DictionaryProperties.n]); + _PdfDictionary? normal = holder as _PdfDictionary?; + if (this._fieldChanged == true && normal != null) { + normal = _PdfDictionary(); + final PdfTemplate checkedTemplate = + PdfTemplate(rect.width, rect.height); + final PdfTemplate unchekedTemplate = + PdfTemplate(rect.width, rect.height); + _drawStateItem(checkedTemplate.graphics!, _PdfCheckFieldState.checked, + item, fieldItem); + _drawStateItem(unchekedTemplate.graphics!, + _PdfCheckFieldState.unchecked, item, fieldItem); + normal.setProperty(value, _PdfReferenceHolder(checkedTemplate)); + normal.setProperty( + _DictionaryProperties.off, _PdfReferenceHolder(unchekedTemplate)); + appearance.remove(_DictionaryProperties.n); + appearance[_DictionaryProperties.n] = _PdfReferenceHolder(normal); + } + holder = + _PdfCrossTable._dereference(appearance[_DictionaryProperties.d]); + _PdfDictionary? pressed = holder as _PdfDictionary?; + if (this._fieldChanged == true && pressed != null) { + pressed = _PdfDictionary(); + final PdfTemplate checkedTemplate = + PdfTemplate(rect.width, rect.height); + final PdfTemplate unchekedTemplate = + PdfTemplate(rect.width, rect.height); + _drawStateItem(checkedTemplate.graphics!, + _PdfCheckFieldState.pressedChecked, item, fieldItem); + _drawStateItem(unchekedTemplate.graphics!, + _PdfCheckFieldState.pressedUnchecked, item, fieldItem); + pressed.setProperty( + _DictionaryProperties.off, _PdfReferenceHolder(unchekedTemplate)); + pressed.setProperty(value, _PdfReferenceHolder(checkedTemplate)); + appearance.remove(_DictionaryProperties.d); + appearance[_DictionaryProperties.d] = _PdfReferenceHolder(pressed); + } + } + widget.setProperty(_DictionaryProperties.ap, appearance); + } else if ((this).form!._setAppearanceDictionary) { + (this).form!._needAppearances = true; + } else if ((this)._form!._setAppearanceDictionary && + !_form!._needAppearances!) { + final _PdfDictionary dic = _PdfDictionary(); + final PdfTemplate template = PdfTemplate(bounds.width, bounds.height); + _drawAppearance(template); + dic.setProperty(_DictionaryProperties.n, _PdfReferenceHolder(template)); + widget!.setProperty(_DictionaryProperties.ap, dic); + } + } + + PdfCheckBoxStyle _obtainStyle() { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + PdfCheckBoxStyle style = PdfCheckBoxStyle.check; + if (widget.containsKey(_DictionaryProperties.mk)) { + final _PdfDictionary bs = _crossTable! + ._getObject(widget[_DictionaryProperties.mk]) as _PdfDictionary; + style = _createStyle(bs); + } + return style; + } + + PdfCheckBoxStyle _createStyle(_PdfDictionary bs) { + PdfCheckBoxStyle style = PdfCheckBoxStyle.check; + if (bs.containsKey(_DictionaryProperties.ca)) { + final _PdfString? name = + _crossTable!._getObject(bs[_DictionaryProperties.ca]) as _PdfString?; + if (name != null) { + final String ch = name.value!.toLowerCase(); + switch (ch) { + case '4': + style = PdfCheckBoxStyle.check; + break; + case 'l': + style = PdfCheckBoxStyle.circle; + break; + case '8': + style = PdfCheckBoxStyle.cross; + break; + case 'u': + style = PdfCheckBoxStyle.diamond; + break; + case 'n': + style = PdfCheckBoxStyle.square; + break; + case 'h': + style = PdfCheckBoxStyle.star; + break; + } + } + } + return style; + } + + void _assignStyle(PdfCheckBoxStyle checkStyle) { + String style = ''; + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + if (widget.containsKey(_DictionaryProperties.mk)) { + switch (checkStyle) { + case PdfCheckBoxStyle.check: + style = "4"; + break; + case PdfCheckBoxStyle.circle: + style = "l"; + break; + case PdfCheckBoxStyle.cross: + style = "8"; + break; + case PdfCheckBoxStyle.diamond: + style = "u"; + break; + case PdfCheckBoxStyle.square: + style = "n"; + break; + case PdfCheckBoxStyle.star: + style = "H"; + break; + } + if (widget[_DictionaryProperties.mk] is _PdfReferenceHolder) { + final _PdfDictionary widgetDict = _crossTable! + ._getObject(widget[_DictionaryProperties.mk]) as _PdfDictionary; + if (widgetDict.containsKey(_DictionaryProperties.ca)) { + widgetDict[_DictionaryProperties.ca] = _PdfString(style); + } else { + widgetDict.setProperty(_DictionaryProperties.ca, _PdfString(style)); + } + } else { + (widget[_DictionaryProperties.mk] + as _PdfDictionary)[_DictionaryProperties.ca] = _PdfString(style); + } + _widget!._widgetAppearance!.normalCaption = style; + } + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_combo_box_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_combo_box_field.dart new file mode 100644 index 000000000..b5d0d5831 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_combo_box_field.dart @@ -0,0 +1,322 @@ +part of pdf; + +/// Represents combo box field in the PDF Form. +class PdfComboBoxField extends PdfListField { + /// Initializes a new instance of the [PdfComboBoxField] class with + /// the specific page, name and bounds. + PdfComboBoxField(PdfPage page, String name, Rect bounds, + {List? items, + bool editable = false, + int? selectedIndex, + String? selectedValue, + PdfFont? font, + PdfTextAlignment alignment = PdfTextAlignment.left, + PdfColor? borderColor, + PdfColor? foreColor, + PdfColor? backColor, + int? borderWidth, + PdfHighlightMode highlightMode = PdfHighlightMode.invert, + PdfBorderStyle borderStyle = PdfBorderStyle.solid, + String? tooltip}) + : super._(page, name, bounds, + font: font, + alignment: alignment, + items: items, + borderColor: borderColor, + foreColor: foreColor, + backColor: backColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip) { + this.editable = editable; + if (selectedIndex != null) { + this.selectedIndex = selectedIndex; + } + if (selectedValue != null) { + this.selectedValue = selectedValue; + } + } + + /// Initializes a new instance of the [PdfComboBoxField] class. + PdfComboBoxField._load(_PdfDictionary dictionary, _PdfCrossTable crossTable) + : super._load(dictionary, crossTable); + + //Fields + bool _editable = false; + + //Properties + /// Gets or sets a value indicating whether this [PdfComboBoxField] is editable. + /// + /// The default value is false. + bool get editable { + if (_isLoadedField) { + _editable = + _isFlagPresent(_FieldFlags.edit) || _flags.contains(_FieldFlags.edit); + } + return _editable; + } + + set editable(bool value) { + if (_editable != value || _isLoadedField) { + _editable = value; + _editable + ? _flags.add(_FieldFlags.edit) + : _isLoadedField + ? _removeFlag(_FieldFlags.edit) + : _flags.remove(_FieldFlags.edit); + } + } + + /// Gets or sets the selected index in the list. + int get selectedIndex => _selectedIndexes[0]; + set selectedIndex(int value) => _selectedIndexes = [value]; + + /// Gets or sets the selected value in the list. + String get selectedValue => _selectedValues[0]; + set selectedValue(String value) => _selectedValues = [value]; + + /// Gets the selected item in the list. + PdfListFieldItem? get selectedItem => _selectedItems[0]; + + //Implementations + @override + void _initialize() { + super._initialize(); + _flags.add(_FieldFlags.combo); + } + + @override + void _drawAppearance(PdfTemplate template) { + super._drawAppearance(template); + final _PaintParams params = _PaintParams( + bounds: Rect.fromLTWH(0, 0, bounds.width, bounds.height), + backBrush: _backBrush, + foreBrush: _foreBrush, + borderPen: _borderPen, + style: borderStyle, + borderWidth: borderWidth, + shadowBrush: _shadowBrush); + _FieldPainter().drawRectangularControl(template.graphics!, params); + if (selectedIndex != -1 && + items[selectedIndex].text != '' && + page!._document!._conformanceLevel == PdfConformanceLevel.none) { + final int multiplier = params._style == PdfBorderStyle.beveled || + params._style == PdfBorderStyle.inset + ? 2 + : 1; + final Rect rectangle = Rect.fromLTWH( + params._bounds!.left + (2 * multiplier) * params._borderWidth!, + params._bounds!.top + (2 * multiplier) * params._borderWidth!, + params._bounds!.width - (4 * multiplier) * params._borderWidth!, + params._bounds!.height - (4 * multiplier) * params._borderWidth!); + template.graphics!.drawString(items[selectedIndex].text, + font ?? PdfStandardFont(PdfFontFamily.timesRoman, 12), + brush: params._foreBrush, bounds: rectangle, format: _format); + } + } + + void _beginSave() { + super._beginSave(); + _applyAppearance(_getWidgetAnnotation(_dictionary, _crossTable)); + } + + void _applyAppearance(_PdfDictionary widget) { + if (widget.containsKey(_DictionaryProperties.ap) && + !_form!._needAppearances!) { + final _IPdfPrimitive? appearance = + _crossTable!._getObject(widget[_DictionaryProperties.ap]); + if ((appearance != null) && + appearance is _PdfDictionary && + (appearance.containsKey(_DictionaryProperties.n))) { + final PdfTemplate template = PdfTemplate(bounds.width, bounds.height); + _drawComboBox(template.graphics); + appearance.remove(_DictionaryProperties.n); + appearance.setProperty( + _DictionaryProperties.n, _PdfReferenceHolder(template)); + widget.setProperty(_DictionaryProperties.ap, appearance); + } + } else if (_form!.readOnly == true || readOnly == true) { + _form!._setAppearanceDictionary = true; + } else if (_form!._setAppearanceDictionary && !_form!._needAppearances!) { + final _PdfDictionary dic = _PdfDictionary(); + final PdfTemplate template = PdfTemplate(bounds.width, bounds.height); + _drawAppearance(template); + dic.setProperty(_DictionaryProperties.n, _PdfReferenceHolder(template)); + widget.setProperty(_DictionaryProperties.ap, dic); + } + } + + void _draw() { + super._draw(); + if (!_isLoadedField && _widget!._pdfAppearance != null) { + page!.graphics + .drawPdfTemplate(_widget!.appearance.normal, bounds.topLeft); + } else { + final Rect rect = Rect.fromLTWH(0, 0, bounds.width, bounds.height); + final PdfFont font = this.font ?? + PdfStandardFont( + PdfFontFamily.helvetica, _getFontHeight(PdfFontFamily.helvetica)); + final _PaintParams parameters = _PaintParams( + bounds: rect, + backBrush: _backBrush, + foreBrush: _foreBrush, + borderPen: _borderPen, + style: borderStyle, + borderWidth: this.borderWidth, + shadowBrush: _shadowBrush); + final PdfTemplate template = PdfTemplate(rect.width, rect.height); + String? text = ''; + if (selectedIndex != -1) { + text = selectedItem!.text; + } else if (_isLoadedField) { + if (selectedIndex == -1 && + _dictionary.containsKey(_DictionaryProperties.v) && + _dictionary.containsKey(_DictionaryProperties.ap) && + !_dictionary.containsKey(_DictionaryProperties.parent)) { + final _IPdfPrimitive? value = + _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.v]); + if (value != null && value is _PdfString) { + text = value.value; + } + } else if (_dictionary.containsKey(_DictionaryProperties.dv)) { + if (_dictionary[_DictionaryProperties.dv] is _PdfString) { + text = (_dictionary[_DictionaryProperties.dv] as _PdfString).value; + } else { + final _IPdfPrimitive? str = _PdfCrossTable._dereference( + _dictionary[_DictionaryProperties.dv]); + if (str != null && str is _PdfString) { + text = str.value; + } + } + } + } + if (!_isLoadedField) { + _FieldPainter().drawRectangularControl(template.graphics!, parameters); + final double borderWidth = parameters._borderWidth!.toDouble(); + final double doubleBorderWidth = 2 * borderWidth; + final bool padding = (parameters._style == PdfBorderStyle.inset || + parameters._style == PdfBorderStyle.beveled); + final Offset point = padding + ? Offset(2 * doubleBorderWidth, 2 * borderWidth) + : Offset(doubleBorderWidth, borderWidth); + final double width = parameters._bounds!.width - doubleBorderWidth; + final Rect itemTextBound = Rect.fromLTWH( + point.dx, + point.dy, + width - point.dx, + parameters._bounds!.height - + (padding ? doubleBorderWidth : borderWidth)); + template.graphics!.drawString(text!, font, + brush: _foreBrush, bounds: itemTextBound, format: _format); + page!.graphics.drawPdfTemplate(template, bounds.topLeft, rect.size); + } else { + final _GraphicsProperties gp = _GraphicsProperties(this); + final _PaintParams prms = _PaintParams( + bounds: gp._bounds, + backBrush: gp._backBrush, + foreBrush: gp._foreBrush, + borderPen: gp._borderPen, + style: gp._style, + borderWidth: gp._borderWidth, + shadowBrush: gp._shadowBrush); + if (gp._font!.height > bounds.height) { + _setFittingFontSize(gp, prms, text!); + } + _FieldPainter().drawComboBox( + page!.graphics, prms, text, gp._font, gp._stringFormat); + } + } + } + + void _drawComboBox(PdfGraphics? graphics) { + final _GraphicsProperties gp = _GraphicsProperties(this); + gp._bounds = Rect.fromLTWH(0, 0, bounds.width, bounds.height); + final _PaintParams prms = _PaintParams( + bounds: gp._bounds, + backBrush: gp._backBrush, + foreBrush: gp._foreBrush, + borderPen: gp._borderPen, + style: gp._style, + borderWidth: gp._borderWidth, + shadowBrush: gp._shadowBrush); + String? text; + if (_selectedItems.count > 0 && selectedIndex != -1 && !_flattenField) { + text = _selectedItems[0].text; + } else if (_dictionary.containsKey(_DictionaryProperties.dv) && + !_flattenField) { + final _IPdfPrimitive? defaultValue = + _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.dv]); + if (defaultValue != null && defaultValue is _PdfString) { + text = defaultValue.value; + } + } + if (_selectedItems.count == 0) { + _FieldPainter().drawComboBox( + graphics!, prms, selectedValue, gp._font, gp._stringFormat); + } else if (text != null && !_flattenField) { + _FieldPainter() + .drawComboBox(graphics!, prms, text, gp._font, gp._stringFormat); + } else { + _FieldPainter().drawRectangularControl(graphics!, prms); + } + } + + double _getFontHeight(PdfFontFamily family) { + double fontSize = 0; + final List widths = []; + if (selectedIndex != -1) { + final PdfFont itemFont = PdfStandardFont(family, 12); + widths.add(itemFont.measureString(selectedItem!.text).width); + } else { + final PdfFont sfont = PdfStandardFont(family, 12); + double max = sfont.measureString(items[0].text).width; + for (int i = 1; i < items.count; ++i) { + final double value = sfont.measureString(items[i].text).width; + max = (max > value) ? max : value; + widths.add(max); + } + } + widths.sort(); + double s = widths.length > 0 + ? ((12 * (bounds.size.width - 4 * borderWidth)) / + widths[widths.length - 1]) + : 12; + if (selectedIndex != -1) { + final PdfFont font = PdfStandardFont(family, s); + final String text = selectedValue; + final Size textSize = font.measureString(text); + if (textSize.width > bounds.width || textSize.height > bounds.height) { + final double width = bounds.width - 4 * borderWidth; + final double h = bounds.height - 4 * borderWidth; + final double minimumFontSize = 0.248; + for (double i = 1; i <= bounds.height; i++) { + font._setSize(i); + Size textSize = font.measureString(text); + if (textSize.width > bounds.width || textSize.height > h) { + fontSize = i; + do { + fontSize = fontSize - 0.001; + font._setSize(fontSize); + final double textWidth = font._getLineWidth(text, _format); + if (fontSize < minimumFontSize) { + font._setSize(minimumFontSize); + break; + } + textSize = font.measureString(text, format: _format); + if (textWidth < width && textSize.height < h) { + font._setSize(fontSize); + break; + } + } while (fontSize > minimumFontSize); + s = fontSize; + break; + } + } + } + } else if (s > 12) { + s = 12; + } + return s; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field.dart new file mode 100644 index 000000000..29d63a960 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field.dart @@ -0,0 +1,2400 @@ +part of pdf; + +/// Represents field of the PDF document's interactive form. +abstract class PdfField implements _IPdfWrapper { + //Constructor + /// Initializes a new instance of the [PdfField] class with the specific page and name. + PdfField(PdfPage? page, String? name, Rect bounds, + {PdfFont? font, + PdfTextAlignment? alignment, + PdfColor? borderColor, + PdfColor? foreColor, + PdfColor? backColor, + int? borderWidth, + PdfHighlightMode? highlightMode, + PdfBorderStyle? borderStyle, + String? tooltip}) + : super() { + if (this is PdfSignatureField) { + if (page != null && page._document != null) { + _form = page._document!.form; + } + } + _initialize(); + if (page != null) { + _page = page; + } + this.bounds = bounds; + if (name != null) { + _name = name; + _dictionary.setProperty(_DictionaryProperties.t, _PdfString(name)); + } + if (font != null) { + _font = font; + } + if (alignment != null) { + _textAlignment = alignment; + } + if (borderColor != null) { + _borderColor = borderColor; + } + if (foreColor != null) { + _foreColor = foreColor; + } + if (backColor != null) { + _backColor = backColor; + } + if (borderWidth != null) { + _borderWidth = borderWidth; + } + if (highlightMode != null) { + _highlightMode = highlightMode; + } + if (borderStyle != null) { + _borderStyle = borderStyle; + } + if (tooltip != null) { + this.tooltip = tooltip; + } + if (this is PdfSignatureField) { + _addAnnotationToPage(page, _widget); + } + } + + PdfField._load(_PdfDictionary dictionary, _PdfCrossTable crossTable) { + _dictionary = dictionary; + _crossTable = crossTable; + _widget = _WidgetAnnotation(); + _isLoadedField = true; + } + + //Fields + final List<_FieldFlags> _flagCollection = [_FieldFlags.defaultFieldFlag]; + String? _name = ''; + PdfPage? _page; + _PdfDictionary _dictionary = _PdfDictionary(); + PdfForm? _form; + String? _mappingName = ''; + String? _tooltip = ''; + PdfFont? _internalFont; + _WidgetAnnotation? _widget; + PdfStringFormat? _stringFormat; + PdfPen? _bPen; + _PdfArray _array = _PdfArray(); + PdfBrush? _bBrush; + PdfBrush? _fBrush; + PdfBrush? _sBrush; + bool _changed = false; + bool _isLoadedField = false; + int _defaultIndex = 0; + _PdfCrossTable? _crossTable; + _PdfReferenceHolder? _requiredReference; + int? _flagValues; + int _tabIndex = 0; + int _annotationIndex = 0; + bool _flatten = false; + bool _readOnly = false; + bool _isTextChanged = false; + bool _fieldChanged = false; + List? _fieldItems; + + //Events + late _BeforeNameChangesEventHandler _beforeNameChanges; + + //Properties + /// Gets the form of the [PdfField]. + PdfForm? get form => _form; + + /// Gets the page of the field. + PdfPage? get page { + if (_isLoadedField && _page == null) { + _page = _getLoadedPage(); + } else if (_page != null && _page!._isLoadedPage && _changed || + (_form != null && _form!._flatten) || + _flattenField) { + _page = _getLoadedPage(); + } + return _page; + } + + /// Gets or sets the name of the [PdfField]. + String? get name { + if (_isLoadedField && (_name == null || _name!.isEmpty)) { + _name = _getFieldName(); + } + return _name; + } + + set name(String? value) => _setName(value); + + /// Gets or sets a value indicating whether this [PdfField] field is read-only. + /// + /// The default value is false. + bool get readOnly { + if (_isLoadedField) { + _readOnly = _isFlagPresent(_FieldFlags.readOnly); + return _readOnly || form!.readOnly; + } + return _readOnly; + } + + set readOnly(bool value) { + if (_isLoadedField) { + value || form!.readOnly + ? _setFlags([_FieldFlags.readOnly]) + : _removeFlag(_FieldFlags.readOnly); + } + _readOnly = value; + } + + /// Gets or sets the mapping name to be used when exporting interactive form + /// field data from the document. + String get mappingName { + if (_isLoadedField && (_mappingName == null || _mappingName!.isEmpty)) { + final _IPdfPrimitive? str = + _getValue(_dictionary, _crossTable, _DictionaryProperties.tm, false); + if (str != null && str is _PdfString) { + _mappingName = str.value; + } + } + return _mappingName!; + } + + set mappingName(String value) { + if (_mappingName != value) { + _mappingName = value; + _dictionary._setString(_DictionaryProperties.tm, _mappingName); + } + if (_isLoadedField) { + _changed = true; + } + } + + /// Gets or sets the tool tip. + String get tooltip { + if (_isLoadedField && (_tooltip == null || _tooltip!.isEmpty)) { + final _IPdfPrimitive? str = + _getValue(_dictionary, _crossTable, _DictionaryProperties.tu, false); + if (str != null && str is _PdfString) { + _tooltip = str.value; + } + } + return _tooltip!; + } + + set tooltip(String value) { + if (_tooltip != value) { + _tooltip = value; + _dictionary._setString(_DictionaryProperties.tu, _tooltip); + } + if (_isLoadedField) { + _changed = true; + } + } + + /// Gets or sets the bounds. + Rect get bounds { + if (_isLoadedField) { + final Rect rect = _getBounds(); + double x = 0; + double y = 0; + if (page != null && + page!._dictionary.containsKey(_DictionaryProperties.cropBox)) { + _PdfArray? cropBox; + if (page!._dictionary[_DictionaryProperties.cropBox] is _PdfArray) { + cropBox = + page!._dictionary[_DictionaryProperties.cropBox] as _PdfArray?; + } else { + final _PdfReferenceHolder cropBoxHolder = + page!._dictionary[_DictionaryProperties.cropBox] + as _PdfReferenceHolder; + cropBox = cropBoxHolder.object as _PdfArray?; + } + if ((cropBox![0] as _PdfNumber).value != 0 || + (cropBox[1] as _PdfNumber).value != 0 || + page!.size.width == (cropBox[2] as _PdfNumber).value || + page!.size.height == (cropBox[3] as _PdfNumber).value) { + x = rect.left - (cropBox[0] as _PdfNumber).value!; + y = ((cropBox[3] as _PdfNumber).value! - (rect.top + rect.height)); + } else { + y = (page!.size.height - (rect.top + rect.height)); + } + } else if (page != null && + page!._dictionary.containsKey(_DictionaryProperties.mediaBox)) { + _PdfArray? mediaBox; + if (_PdfCrossTable._dereference( + page!._dictionary[_DictionaryProperties.mediaBox]) is _PdfArray) { + mediaBox = _PdfCrossTable._dereference( + page!._dictionary[_DictionaryProperties.mediaBox]) as _PdfArray?; + } + if ((mediaBox![0] as _PdfNumber).value! > 0 || + (mediaBox[1] as _PdfNumber).value! > 0 || + page!.size.width == (mediaBox[2] as _PdfNumber).value || + page!.size.height == (mediaBox[3] as _PdfNumber).value) { + x = rect.left - (mediaBox[0] as _PdfNumber).value!; + y = ((mediaBox[3] as _PdfNumber).value! - (rect.top + rect.height)); + } else { + y = (page!.size.height - (rect.top + rect.height)); + } + } else if (page != null) { + y = (page!.size.height - (rect.top + rect.height)); + } else { + y = (rect.top + rect.height); + } + return Rect.fromLTWH(x == 0 ? rect.left : x, y == 0 ? rect.top : y, + rect.width, rect.height); + } else { + return _widget!.bounds; + } + } + + set bounds(Rect value) { + if (value.isEmpty && !(this is PdfSignatureField)) { + ArgumentError('bounds can\'t be empty.'); + } + if (_isLoadedField) { + final Rect rect = value; + final double height = page!.size.height; + final List<_PdfNumber> values = [ + _PdfNumber(rect.left), + _PdfNumber(height - (rect.top + rect.height)), + _PdfNumber(rect.left + rect.width), + _PdfNumber(height - rect.top) + ]; + _PdfDictionary dic = _dictionary; + if (!dic.containsKey(_DictionaryProperties.rect)) { + dic = _getWidgetAnnotation(_dictionary, _crossTable); + } + dic._setArray(_DictionaryProperties.rect, values); + _changed = true; + } else { + _widget!.bounds = value; + } + } + + /// Gets or sets a value indicating whether to flatten this [PdfField]. + bool get _flattenField { + if (_form != null) { + _flatten |= _form!._flatten; + } + return _flatten; + } + + set _flattenField(bool value) { + _flatten = value; + } + + /// Gets or sets the color of the border. + PdfColor get _borderColor { + if (_isLoadedField) { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + PdfColor bc = PdfColor(0, 0, 0); + if (widget.containsKey(_DictionaryProperties.mk)) { + final _IPdfPrimitive? getObject = + _crossTable!._getObject(widget[_DictionaryProperties.mk]); + if (getObject != null && + getObject is _PdfDictionary && + getObject.containsKey(_DictionaryProperties.bc)) { + final _PdfArray array = + getObject[_DictionaryProperties.bc] as _PdfArray; + bc = _createColor(array); + } + } + return bc; + } else { + return _widget!._widgetAppearance!.borderColor; + } + } + + set _borderColor(PdfColor value) { + _widget!._widgetAppearance!.borderColor = value; + if (_isLoadedField) { + form!._setAppearanceDictionary = true; + _assignBorderColor(value); + if (form!._needAppearances == false) { + _changed = true; + _fieldChanged = true; + } + } + _createBorderPen(); + } + + /// Gets or sets the color of the background. + PdfColor get _backColor => + _isLoadedField ? _getBackColor() : _widget!._widgetAppearance!.backColor; + + set _backColor(PdfColor value) { + if (_isLoadedField) { + _assignBackColor(value); + if (form!._needAppearances == false) { + _changed = true; + _fieldChanged = true; + } + } else { + _widget!._widgetAppearance!.backColor = value; + _createBackBrush(); + } + } + + /// Gets or sets the color of the text. + PdfColor get _foreColor { + if (_isLoadedField) { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + PdfColor color = PdfColor(0, 0, 0); + if (widget.containsKey(_DictionaryProperties.da)) { + final _PdfString defaultAppearance = _crossTable! + ._getObject(widget[_DictionaryProperties.da]) as _PdfString; + color = _getForeColor(defaultAppearance.value); + } else { + final _IPdfPrimitive? defaultAppearance = widget._getValue( + _DictionaryProperties.da, _DictionaryProperties.parent); + if (defaultAppearance != null && defaultAppearance is _PdfString) { + color = _getForeColor(defaultAppearance.value); + } + } + return color; + } else { + return _widget!.defaultAppearance.foreColor; + } + } + + set _foreColor(PdfColor value) { + if (_isLoadedField) { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + double? height = 0; + String? name; + if (widget.containsKey(_DictionaryProperties.da)) { + final _PdfString str = widget[_DictionaryProperties.da] as _PdfString; + final dynamic fontName = _fontName(str.value!); + name = fontName['name']; + height = fontName['height']; + } else if (_dictionary.containsKey(_DictionaryProperties.da)) { + final _PdfString str = + _dictionary[_DictionaryProperties.da] as _PdfString; + final dynamic fontName = _fontName(str.value!); + name = fontName['name']; + height = fontName['height']; + } + if (name != null) { + final _PdfDefaultAppearance defaultAppearance = _PdfDefaultAppearance(); + defaultAppearance.fontName = name; + defaultAppearance.fontSize = height; + defaultAppearance.foreColor = value; + widget[_DictionaryProperties.da] = + _PdfString(defaultAppearance._toString()); + } else if (_font != null) { + final _PdfDefaultAppearance defaultAppearance = _PdfDefaultAppearance(); + defaultAppearance.fontName = _font!.name; + defaultAppearance.fontSize = _font!.size; + defaultAppearance.foreColor = value; + widget[_DictionaryProperties.da] = + _PdfString(defaultAppearance._toString()); + } + form!._setAppearanceDictionary = true; + } else { + _widget!._defaultAppearance!.foreColor = value; + _foreBrush = PdfSolidBrush(value); + } + } + + /// Gets or sets the width of the border. + int get _borderWidth => _isLoadedField + ? _obtainBorderWidth() + : _widget!._widgetBorder!.width.toInt(); + set _borderWidth(int value) { + if (_widget!._widgetBorder!.width != value) { + _widget!._widgetBorder!.width = value.toDouble(); + if (_isLoadedField) { + _assignBorderWidth(value); + } else { + value == 0 + ? _widget!._widgetAppearance!.borderColor = PdfColor(255, 255, 255) + : _createBorderPen(); + } + } + } + + /// Gets or sets the highlighting mode. + PdfHighlightMode get _highlightMode => + _isLoadedField ? _obtainHighlightMode() : _widget!.highlightMode!; + set _highlightMode(PdfHighlightMode value) => _isLoadedField + ? _assignHighlightMode(value) + : _widget!.highlightMode = value; + + /// Gets or sets the border style. + PdfBorderStyle get _borderStyle => _isLoadedField + ? _obtainBorderStyle() + : _widget!._widgetBorder!.borderStyle; + set _borderStyle(PdfBorderStyle value) { + if (_isLoadedField) { + _assignBorderStyle(value); + if (form!._needAppearances == false) { + _changed = true; + _fieldChanged = true; + } + } else { + _widget!._widgetBorder!.borderStyle = value; + } + _createBorderPen(); + } + + /// Gets or sets the tab index for form fields. + /// + /// The default value is 0. + int get tabIndex { + if (_isLoadedField) { + if (page != null) { + final _PdfDictionary annotDic = + _getWidgetAnnotation(_dictionary, _crossTable); + final _PdfReference reference = + page!._crossTable!._getReference(annotDic); + _tabIndex = page!._annotsReference._indexOf(reference); + } + } + return _tabIndex; + } + + set tabIndex(int value) { + _tabIndex = value; + if (_isLoadedField && + page != null && + page!.formFieldsTabOrder == PdfFormFieldsTabOrder.manual) { + final PdfAnnotation annotationReference = + _WidgetAnnotation._(_dictionary, _crossTable!); + final _PdfReference reference = + page!._crossTable!._getReference(annotationReference._element); + int index = page!._annotsReference._indexOf(reference); + if (index < 0) { + index = _annotationIndex; + } + final _PdfArray? annots = + page!.annotations._rearrange(reference, _tabIndex, index); + page!._dictionary.setProperty(_DictionaryProperties.annots, annots); + } + } + + //Gets or sets the font. + PdfFont? get _font { + if (_isLoadedField) { + if (_internalFont != null && + !_dictionary.containsKey(_DictionaryProperties.kids)) { + return _internalFont!; + } + bool? isCorrectFont = false; + PdfFont font = PdfStandardFont(PdfFontFamily.helvetica, 8); + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + if (widget.containsKey(_DictionaryProperties.da) || + _dictionary.containsKey(_DictionaryProperties.da)) { + _IPdfPrimitive? defaultAppearance = + _crossTable!._getObject(widget[_DictionaryProperties.da]); + if (defaultAppearance == null) { + defaultAppearance = + _crossTable!._getObject(_dictionary[_DictionaryProperties.da]); + } + String? fontName; + if (defaultAppearance != null && defaultAppearance is _PdfString) { + final Map value = _getFont(defaultAppearance.value!); + font = value['font']; + isCorrectFont = value['isCorrectFont']; + fontName = value['fontName']; + if (!isCorrectFont! && fontName != null) { + widget.setProperty( + _DictionaryProperties.da, + _PdfString( + defaultAppearance.value!.replaceAll(fontName, '/Helv'))); + } + } + } + return font; + } + return _internalFont; + } + + set _font(PdfFont? value) { + if (value != null && _internalFont != value) { + _internalFont = value; + if (_isLoadedField) { + if (_form != null) { + _form!._setAppearanceDictionary = true; + } + final _PdfDefaultAppearance defaultAppearance = _PdfDefaultAppearance(); + defaultAppearance.fontName = _internalFont!.name.replaceAll(' ', ''); + defaultAppearance.fontSize = _internalFont!.size; + defaultAppearance.foreColor = _foreColor; + final _IPdfPrimitive? fontDictionary = _PdfCrossTable._dereference( + _form!._resources[_DictionaryProperties.font]); + if (fontDictionary != null && + fontDictionary is _PdfDictionary && + !fontDictionary.containsKey(defaultAppearance.fontName)) { + final _IPdfWrapper fontWrapper = _internalFont!; + final _PdfDictionary? fontElement = + fontWrapper._element as _PdfDictionary?; + fontDictionary._items![_PdfName(defaultAppearance.fontName)] = + _PdfReferenceHolder(fontElement); + } + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + widget[_DictionaryProperties.da] = + _PdfString(defaultAppearance._toString()); + } else { + _defineDefaultAppearance(); + } + } + } + + //Gets or sets the text alignment. + PdfTextAlignment get _textAlignment => + _isLoadedField ? _format!.alignment : _widget!.textAlignment!; + set _textAlignment(PdfTextAlignment value) { + if (_isLoadedField) { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + widget.setProperty(_DictionaryProperties.q, _PdfNumber(value.index)); + _changed = true; + } else if (_widget!.textAlignment != value) { + _widget!.textAlignment = value; + _format = PdfStringFormat( + alignment: value, lineAlignment: PdfVerticalAlignment.middle); + } + } + + //Gets the flags. + List<_FieldFlags> get _flags => _flagCollection; + + int get _flagValue => _flagValues ??= _getFlagValue(); + + bool _isFlagPresent(flag) { + return _getFieldFlagsValue(flag) & _flagValue != 0; + } + + PdfStringFormat? get _format => + _isLoadedField ? _assignStringFormat() : _stringFormat; + set _format(PdfStringFormat? value) => _stringFormat = value; + + PdfBrush? get _backBrush => + _isLoadedField ? PdfSolidBrush(_getBackColor(true)) : _bBrush; + set _backBrush(PdfBrush? value) { + if (_isLoadedField && value is PdfSolidBrush) { + _assignBackColor(value.color); + } else { + _bBrush = value; + } + } + + PdfBrush? get _foreBrush => + _isLoadedField ? PdfSolidBrush(_foreColor) : _fBrush; + set _foreBrush(PdfBrush? value) => _fBrush = value; + + PdfBrush? get _shadowBrush => _isLoadedField ? _obtainShadowBrush() : _sBrush; + set _shadowBrush(PdfBrush? value) => _sBrush = value; + + PdfPen? get _borderPen => _isLoadedField ? _obtainBorderPen() : _bPen; + set _borderPen(PdfPen? value) => _bPen = value; + + _PdfArray? get _kids => _obtainKids(); + + //Public methods + /// Flattens the field. + void flatten() { + _flattenField = true; + } + + //Implementations + /// Sets the name of the field. + void _setName(String? name) { + if (name == null || name.isEmpty) { + throw ArgumentError('Field name cannot be null/empty.'); + } + if (_isLoadedField) { + if (this.name != null && this.name != name) { + final List nameParts = this.name!.split('.'); + if (nameParts[nameParts.length - 1] == name) { + return; + } else { + if (_form != null) { + _beforeNameChanges(name); + } + _name = name; + _changed = true; + } + } + } else { + _name = name; + } + _dictionary.setProperty(_DictionaryProperties.t, _PdfString(name)); + } + + void _initialize() { + _dictionary._beginSave = + this is PdfSignatureField ? _dictionaryBeginSave : _dictBeginSave; + _widget = _WidgetAnnotation(); + if (this is PdfSignatureField && form!.fieldAutoNaming) { + _createBorderPen(); + _createBackBrush(); + _dictionary = _widget!._dictionary; + } else { + _widget!.parent = this; + if (!(this is PdfSignatureField)) { + _format = PdfStringFormat( + alignment: _widget!._alignment!, + lineAlignment: PdfVerticalAlignment.middle); + _createBorderPen(); + _createBackBrush(); + } + final _PdfArray array = _PdfArray(); + array._add(_PdfReferenceHolder(_widget)); + _dictionary.setProperty(_DictionaryProperties.kids, _PdfArray(array)); + } + _widget!.defaultAppearance.fontName = 'TiRo'; + } + + void _dictionaryBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { + if (_dictionary.containsKey(_DictionaryProperties.kids) && + _dictionary.containsKey(_DictionaryProperties.tu)) { + final _IPdfPrimitive? kids = (_dictionary[_DictionaryProperties.kids]); + if (kids != null && kids is _PdfArray) { + for (int i = 0; i < kids.count; i++) { + final _IPdfPrimitive? kidsReferenceHolder = kids._elements[i]; + if (kidsReferenceHolder != null && + kidsReferenceHolder is _PdfReferenceHolder) { + final _IPdfPrimitive? widgetAnnot = kidsReferenceHolder._object; + if (widgetAnnot != null && + widgetAnnot is _PdfDictionary && + !widgetAnnot.containsKey(_DictionaryProperties.tu)) { + final _IPdfPrimitive? toolTip = + (_dictionary[_DictionaryProperties.tu]); + if (toolTip != null && toolTip is _PdfString) { + widgetAnnot._setString(_DictionaryProperties.tu, toolTip.value); + } + } + } + } + } + } + } + + void _save() { + if (readOnly || (_form != null && _form!.readOnly)) { + _flags.add(_FieldFlags.readOnly); + } + _setFlags(_flags); + if (page != null && + page!.formFieldsTabOrder == PdfFormFieldsTabOrder.manual) { + page!.annotations.remove(_widget!); + page!.annotations._annotations + ._insert(tabIndex, _PdfReferenceHolder(_widget)); + page!.annotations._list.insert(tabIndex, _widget!); + } + if (form != null && + !form!._needAppearances! && + _widget!._pdfAppearance == null) { + _widget!.setAppearance = true; + _drawAppearance(_widget!.appearance.normal); + } + } + + void _dictBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { + _save(); + } + + void _beginSave() { + if (_backBrush != null && + _backBrush is PdfSolidBrush && + (_backBrush as PdfSolidBrush).color.isEmpty) { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + final _PdfDictionary mk = _PdfDictionary(); + final _PdfArray arr = _PdfArray([1, 1, 1]); + mk.setProperty(_DictionaryProperties.bg, arr); + widget.setProperty(_DictionaryProperties.mk, mk); + } + } + + int _getFieldFlagsValue(_FieldFlags value) { + switch (value) { + case _FieldFlags.readOnly: + return 1; + case _FieldFlags.requiredFieldFlag: + return 1 << 1; + case _FieldFlags.noExport: + return 1 << 2; + case _FieldFlags.multiline: + return 1 << 12; + case _FieldFlags.password: + return 1 << 13; + case _FieldFlags.fileSelect: + return 1 << 20; + case _FieldFlags.doNotSpellCheck: + return 1 << 22; + case _FieldFlags.doNotScroll: + return 1 << 23; + case _FieldFlags.comb: + return 1 << 24; + case _FieldFlags.richText: + return 1 << 25; + case _FieldFlags.noToggleToOff: + return 1 << 14; + case _FieldFlags.radio: + return 1 << 15; + case _FieldFlags.pushButton: + return 1 << 16; + case _FieldFlags.radiosInUnison: + return 1 << 25; + case _FieldFlags.combo: + return 1 << 17; + case _FieldFlags.edit: + return 1 << 18; + case _FieldFlags.sort: + return 1 << 19; + case _FieldFlags.multiSelect: + return 1 << 21; + case _FieldFlags.commitOnSelChange: + return 1 << 26; + default: + return 0; + } + } + + void _setForm(PdfForm? form) { + _form = form; + _defineDefaultAppearance(); + } + + void _setFlags(List<_FieldFlags> value) { + int flagValue = _isLoadedField ? _flagValue : 0; + value.forEach((element) => flagValue |= _getFieldFlagsValue(element)); + _flagValues = flagValue; + _dictionary._setNumber(_DictionaryProperties.fieldFlags, _flagValues); + } + + void _removeFlag(_FieldFlags flag) { + _flagValues = _flagValue & ~_getFieldFlagsValue(flag); + } + + int _getFlagValue() { + final _IPdfPrimitive? number = _getValue( + _dictionary, _crossTable, _DictionaryProperties.fieldFlags, true); + return number != null && number is _PdfNumber ? number.value!.toInt() : 0; + } + + void _defineDefaultAppearance() { + if (form != null && _internalFont != null) { + if (_isLoadedField) { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + final _PdfName name = _form!._resources._getName(_font!); + _form!._resources._add(_internalFont, name); + _form!._needAppearances = true; + final _PdfDefaultAppearance defaultAppearance = _PdfDefaultAppearance(); + defaultAppearance.fontName = name._name; + defaultAppearance.fontSize = _internalFont!.size; + defaultAppearance.foreColor = _foreColor; + widget[_DictionaryProperties.da] = + _PdfString(defaultAppearance._toString()); + if (this is PdfRadioButtonListField) { + final PdfRadioButtonListField radioButtonListField = + this as PdfRadioButtonListField; + for (int i = 0; i < radioButtonListField.items.count; i++) { + final PdfRadioButtonListItem item = radioButtonListField.items[i]; + if (item._font != null) + form!._resources._add(radioButtonListField.items[i]._font, + _PdfName(item._widget!._defaultAppearance!.fontName)); + } + } + } else { + final _PdfName name = form!._resources._getName(_internalFont!); + _widget!.defaultAppearance.fontName = name._name; + _widget!.defaultAppearance.fontSize = _internalFont!.size; + } + } else if (!_isLoadedField && _internalFont != null) { + _widget!.defaultAppearance.fontName = _internalFont!.name; + _widget!.defaultAppearance.fontSize = _internalFont!.size; + } + } + + void _applyName(String? name) { + if (_isLoadedField) { + _setName(name); + } else { + _name = name; + _dictionary.setProperty(_DictionaryProperties.t, _PdfString(name!)); + } + } + + //Creates the border pen. + void _createBorderPen() { + final double width = _widget!._widgetBorder!.width.toDouble(); + _borderPen = PdfPen(_widget!._widgetAppearance!.borderColor, width: width); + if (_widget!._widgetBorder!.borderStyle == PdfBorderStyle.dashed || + _widget!._widgetBorder!.borderStyle == PdfBorderStyle.dot) { + _borderPen!.dashStyle = PdfDashStyle.custom; + _borderPen!.dashPattern = [3 / width]; + } + } + + //Creates the back brush. + void _createBackBrush() { + final PdfColor bc = _widget!._widgetAppearance!._backColor; + _backBrush = PdfSolidBrush(bc); + final PdfColor color = PdfColor(bc.r, bc.g, bc.b); + color.r = (color.r - 64 >= 0 ? color.r - 64 : 0).toUnsigned(8); + color.g = (color.g - 64 >= 0 ? color.g - 64 : 0).toUnsigned(8); + color.b = (color.b - 64 >= 0 ? color.b - 64 : 0).toUnsigned(8); + _shadowBrush = PdfSolidBrush(color); + } + + void _drawAppearance(PdfTemplate template) { + if (_font != null) { + if ((_font is PdfStandardFont || _font is PdfCjkStandardFont) && + page != null && + _page!._document != null && + _page!._document!._conformanceLevel != PdfConformanceLevel.none) { + throw ArgumentError( + 'All the fonts must be embedded in ${_page!._document!._conformanceLevel.toString()} document.'); + } else if (_font is PdfTrueTypeFont && + _page!._document != null && + _page!._document!._conformanceLevel == PdfConformanceLevel.a1b) { + (_font as PdfTrueTypeFont)._fontInternal._initializeCidSet(); + } + } + } + + //Gets the widget annotation. + _PdfDictionary _getWidgetAnnotation( + _PdfDictionary dictionary, _PdfCrossTable? crossTable) { + _PdfDictionary? dic; + if (dictionary.containsKey(_DictionaryProperties.kids)) { + final _IPdfPrimitive? array = + crossTable!._getObject(dictionary[_DictionaryProperties.kids]); + if (array is _PdfArray && array.count > 0) { + final _IPdfPrimitive reference = + crossTable._getReference(array[_defaultIndex]); + if (reference is _PdfReference) { + dic = crossTable._getObject(reference) as _PdfDictionary?; + } + } + } + return dic ?? dictionary; + } + + // Gets the value. + static _IPdfPrimitive? _getValue(_PdfDictionary dictionary, + _PdfCrossTable? crossTable, String value, bool inheritable) { + _IPdfPrimitive? primitive; + if (dictionary.containsKey(value)) { + primitive = crossTable!._getObject(dictionary[value]); + } else if (inheritable) { + primitive = _searchInParents(dictionary, crossTable, value); + } + return primitive; + } + + // Searches the in parents. + static _IPdfPrimitive? _searchInParents( + _PdfDictionary dictionary, _PdfCrossTable? crossTable, String value) { + _IPdfPrimitive? primitive; + _PdfDictionary? dic = dictionary; + while (primitive == null && dic != null) { + if (dic.containsKey(value)) { + primitive = crossTable!._getObject(dic[value]); + } else { + dic = dic.containsKey(_DictionaryProperties.parent) + ? (crossTable!._getObject(dic[_DictionaryProperties.parent]) + as _PdfDictionary?)! + : null; + } + } + return primitive; + } + + String? _getFieldName() { + String? name; + _PdfString? str; + if (!_dictionary.containsKey(_DictionaryProperties.parent)) { + str = _getValue(_dictionary, _crossTable, _DictionaryProperties.t, false) + as _PdfString?; + } else { + _IPdfPrimitive? dic = + _crossTable!._getObject(_dictionary[_DictionaryProperties.parent]); + while (dic != null && + dic is _PdfDictionary && + dic.containsKey(_DictionaryProperties.parent)) { + if (dic.containsKey(_DictionaryProperties.t)) { + name = name == null + ? (_getValue(dic, _crossTable, _DictionaryProperties.t, false) + as _PdfString) + .value + : (_getValue(dic, _crossTable, _DictionaryProperties.t, false) + as _PdfString) + .value! + + '.' + + name; + } + dic = _crossTable!._getObject(dic[_DictionaryProperties.parent]) + as _PdfDictionary?; + } + if (dic != null && + dic is _PdfDictionary && + dic.containsKey(_DictionaryProperties.t)) { + name = name == null + ? (_getValue(dic, _crossTable, _DictionaryProperties.t, false) + as _PdfString) + .value + : (_getValue(dic, _crossTable, _DictionaryProperties.t, false) + as _PdfString) + .value! + + '.' + + name; + final _IPdfPrimitive? strName = + _getValue(_dictionary, _crossTable, _DictionaryProperties.t, false); + if (strName != null && strName is _PdfString) { + name = name! + '.' + strName.value!; + } + } else if (_dictionary.containsKey(_DictionaryProperties.t)) { + str = + _getValue(_dictionary, _crossTable, _DictionaryProperties.t, false) + as _PdfString?; + } + } + if (str != null && str is _PdfString) { + name = str.value; + } + return name; + } + + PdfPage? _getLoadedPage() { + PdfPage? page = _page; + if (page == null || (page._isLoadedPage) && _crossTable != null) { + final PdfDocument? doc = _crossTable!._document; + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + if (widget.containsKey(_DictionaryProperties.p) && + !(_PdfCrossTable._dereference(widget[_DictionaryProperties.p]) + is _PdfNull)) { + final _IPdfPrimitive? pageRef = + _crossTable!._getObject(widget[_DictionaryProperties.p]); + if (pageRef != null && pageRef is _PdfDictionary) { + page = doc!.pages._getPage(pageRef); + } + } else { + final _PdfReference widgetReference = + _crossTable!._getReference(widget); + for (int j = 0; j < doc!.pages.count; j++) { + final PdfPage loadedPage = doc.pages[j]; + final _PdfArray? lAnnots = loadedPage._obtainAnnotations(); + if (lAnnots != null) { + for (int i = 0; i < lAnnots.count; i++) { + final _PdfReferenceHolder holder = + lAnnots[i] as _PdfReferenceHolder; + if (holder.reference!._objNum == widgetReference._objNum && + holder.reference!._genNum == widgetReference._genNum) { + page = loadedPage; + return page; + } else if (_requiredReference != null && + _requiredReference!.reference!._objNum == + holder.reference!._objNum && + _requiredReference!.reference!._genNum == + holder.reference!._genNum) { + page = loadedPage; + return page; + } + } + } + } + } + } + if (page!._dictionary.containsKey(_DictionaryProperties.tabs)) { + final _PdfName tabName = + page._dictionary[_DictionaryProperties.tabs] as _PdfName; + if (tabName._name == '') { + page._dictionary[_DictionaryProperties.tabs] = _PdfName(' '); + } + } + return page; + } + + void _draw() { + _removeAnnotationFromPage(); + } + + void _removeAnnotationFromPage([PdfPage? page]) { + page ??= this.page; + if (page != null) { + if (!page._isLoadedPage) { + page.annotations.remove(_widget!); + } else { + final _PdfDictionary pageDic = page._dictionary; + final _PdfArray annots = pageDic + .containsKey(_DictionaryProperties.annots) + ? page._crossTable! + ._getObject(pageDic[_DictionaryProperties.annots]) as _PdfArray + : _PdfArray(); + _widget!._dictionary + .setProperty(_DictionaryProperties.p, _PdfReferenceHolder(page)); + for (int i = 0; i < annots.count; i++) { + final _IPdfPrimitive? obj = annots[i]; + if (obj != null && + obj is _PdfReferenceHolder && + obj.object is _PdfDictionary && + obj.object == _widget!._dictionary) { + annots._remove(obj); + break; + } + } + page._dictionary.setProperty(_DictionaryProperties.annots, annots); + } + } + } + + Map _getFont(String fontString) { + bool isCorrectFont = true; + final Map fontNameDic = _fontName(fontString); + final String? name = fontNameDic['name']; + double height = fontNameDic['height']; + PdfFont font = PdfStandardFont(PdfFontFamily.helvetica, height); + _IPdfPrimitive? fontDictionary = + _crossTable!._getObject(form!._resources[_DictionaryProperties.font]); + if (fontDictionary != null && + name != null && + fontDictionary is _PdfDictionary && + fontDictionary.containsKey(name)) { + fontDictionary = _crossTable!._getObject(fontDictionary[name]); + if (fontDictionary != null && + fontDictionary is _PdfDictionary && + fontDictionary.containsKey(_DictionaryProperties.subtype)) { + final _PdfName fontSubtype = _crossTable! + ._getObject(fontDictionary[_DictionaryProperties.subtype]) + as _PdfName; + if (fontSubtype._name == _DictionaryProperties.type1) { + final _PdfName baseFont = _crossTable! + ._getObject(fontDictionary[_DictionaryProperties.baseFont]) + as _PdfName; + final List fontStyle = _getFontStyle(baseFont._name!); + final dynamic fontFamilyDic = _getFontFamily(baseFont._name!); + final PdfFontFamily? fontFamily = fontFamilyDic['fontFamily']; + final String? standardName = fontFamilyDic['standardName']; + if (standardName == null) { + font = PdfStandardFont(fontFamily!, height, multiStyle: fontStyle); + if (!_isTextChanged) { + font = _updateFontEncoding(font, fontDictionary); + } + } else { + if (height == 0 && standardName != _getEnumName(fontFamily)) { + _PdfDictionary? appearanceDictionary = _PdfDictionary(); + if (_dictionary.containsKey(_DictionaryProperties.ap)) { + appearanceDictionary = + _dictionary[_DictionaryProperties.ap] as _PdfDictionary?; + } else { + if (_dictionary.containsKey(_DictionaryProperties.kids) && + _dictionary[_DictionaryProperties.kids] is _PdfArray) { + final _PdfArray kidsArray = + _dictionary[_DictionaryProperties.kids] as _PdfArray; + for (int i = 0; i < kidsArray.count; i++) { + if (kidsArray[i] is _PdfReferenceHolder) { + final _PdfReferenceHolder kids = + kidsArray[i] as _PdfReferenceHolder; + final _IPdfPrimitive? dictionary = kids.object; + appearanceDictionary = dictionary != null && + dictionary is _PdfDictionary && + dictionary + .containsKey(_DictionaryProperties.ap) && + dictionary[_DictionaryProperties.ap] + is _PdfDictionary + ? dictionary[_DictionaryProperties.ap] + as _PdfDictionary? + : null; + break; + } + } + } + } + if (appearanceDictionary != null && + appearanceDictionary is _PdfDictionary && + appearanceDictionary.containsKey(_DictionaryProperties.n)) { + final _IPdfPrimitive? dic = _PdfCrossTable._dereference( + appearanceDictionary[_DictionaryProperties.n]); + if (dic != null && dic is _PdfStream) { + final _PdfStream stream = dic; + stream._decompress(); + final dynamic fontNameDict = + _fontName(utf8.decode(stream._dataStream!.toList())); + height = fontNameDict['height']; + } + } + } + if (height == 0 && standardName != _getEnumName(fontFamily)) { + final PdfStandardFont stdf = font as PdfStandardFont; + height = _getFontHeight(stdf.fontFamily); + font = PdfStandardFont.prototype(stdf, height); + } + if (fontStyle != [PdfFontStyle.regular]) { + font = PdfStandardFont(PdfFontFamily.helvetica, height, + multiStyle: fontStyle); + } + if (standardName != _getEnumName(fontFamily)) { + font = _updateFontEncoding(font, fontDictionary); + } + font._metrics = _createFont(fontDictionary, height, baseFont); + font._fontInternals = fontDictionary; + } + } else if (fontSubtype._name == 'TrueType') { + final _PdfName baseFont = _crossTable! + ._getObject(fontDictionary[_DictionaryProperties.baseFont]) + as _PdfName; + final List fontStyle = _getFontStyle(baseFont._name!); + font = PdfStandardFont.prototype( + PdfStandardFont(PdfFontFamily.helvetica, 8), height, + multiStyle: fontStyle); + final _IPdfPrimitive? tempName = + fontDictionary[_DictionaryProperties.name]; + if (tempName != null && tempName is _PdfName) { + if (font.name != tempName._name) { + font._metrics = _createFont(fontDictionary, height, baseFont); + } + } + } else if (fontSubtype._name == _DictionaryProperties.type0) { + final _IPdfPrimitive? baseFont = _crossTable! + ._getObject(fontDictionary[_DictionaryProperties.baseFont]); + if (baseFont != null && + baseFont is _PdfName && + _isCjkFont(baseFont._name)) { + font = PdfCjkStandardFont( + _getCjkFontFamily(baseFont._name)!, height, + multiStyle: _getFontStyle(baseFont._name!)); + } else { + _IPdfPrimitive? descendantFontsArray; + _IPdfPrimitive? descendantFontsDic; + _IPdfPrimitive? fontDescriptor; + _IPdfPrimitive? fontDescriptorDic; + _IPdfPrimitive? fontName; + descendantFontsArray = _crossTable!._getObject( + fontDictionary[_DictionaryProperties.descendantFonts]); + if (descendantFontsArray != null && + descendantFontsArray is _PdfArray && + descendantFontsArray.count > 0) { + descendantFontsDic = descendantFontsArray[0] is _PdfDictionary + ? descendantFontsArray[0] + : (descendantFontsArray[0] as _PdfReferenceHolder).object; + } + if (descendantFontsDic != null && + descendantFontsDic is _PdfDictionary) { + fontDescriptor = + descendantFontsDic[_DictionaryProperties.fontDescriptor]; + } + if (fontDescriptor != null && + fontDescriptor is _PdfReferenceHolder) { + fontDescriptorDic = fontDescriptor.object; + } + if (fontDescriptorDic != null && + fontDescriptorDic is _PdfDictionary) + fontName = fontDescriptorDic[_DictionaryProperties.fontName]; + if (fontName != null && fontName is _PdfName) { + String fontNameStr = + fontName._name!.substring(fontName._name!.indexOf('+') + 1); + final _PdfFontMetrics fontMetrics = _createFont( + descendantFontsDic as _PdfDictionary, + height, + _PdfName(fontNameStr)); + if (fontNameStr.contains('PSMT')) { + fontNameStr = fontNameStr.replaceAll('PSMT', ''); + } + if (fontNameStr.contains('PS')) { + fontNameStr = fontNameStr.replaceAll('PS', ''); + } + if (fontNameStr.contains('-')) { + fontNameStr = fontNameStr.replaceAll('-', ''); + } + if (font.name != fontNameStr) { + final _WidthTable? widthTable = font._metrics!._widthTable; + font._metrics = fontMetrics; + font._metrics!._widthTable = widthTable; + } + } + } + } + } + } else { + final PdfFont? usedFont = _getFontByName(name, height); + usedFont != null ? font = usedFont : isCorrectFont = false; + } + if (height == 0) { + if (font is PdfStandardFont) { + height = _getFontHeight(font.fontFamily); + if (height == 0) { + height = 12; + } + font._setSize(height); + } + } + return {'font': font, 'isCorrectFont': isCorrectFont, 'FontName': name}; + } + + bool _isCjkFont(String? fontName) { + final List fontString = [ + 'STSong-Light', + 'HeiseiMin-W3', + 'HeiseiKakuGo-W5', + 'HYSMyeongJo-Medium', + 'MSung-Light', + 'MHei-Medium', + 'HYGoThic-Medium' + ]; + for (int i = 0; i < 7; i++) { + if (fontName!.contains(fontString[i])) { + return true; + } + } + return false; + } + + PdfCjkFontFamily? _getCjkFontFamily(String? fontName) { + final List fontString = [ + 'STSong-Light', + 'HeiseiMin-W3', + 'HeiseiKakuGo-W5', + 'HYSMyeongJo-Medium', + 'MSung-Light', + 'MHei-Medium', + 'HYGoThic-Medium' + ]; + String? value; + for (int i = 0; i < 7; i++) { + if (fontName!.contains(fontString[i])) { + value = fontString[i]; + break; + } + } + switch (value) { + case 'STSong-Light': + return PdfCjkFontFamily.sinoTypeSongLight; + case 'HeiseiMin-W3': + return PdfCjkFontFamily.heiseiMinchoW3; + case 'HeiseiKakuGo-W5': + return PdfCjkFontFamily.heiseiKakuGothicW5; + case 'HYSMyeongJo-Medium': + return PdfCjkFontFamily.hanyangSystemsShinMyeongJoMedium; + case 'MSung-Light': + return PdfCjkFontFamily.monotypeSungLight; + case 'MHei-Medium': + return PdfCjkFontFamily.monotypeHeiMedium; + case 'HYGoThic-Medium': + return PdfCjkFontFamily.hanyangSystemsGothicMedium; + } + return null; + } + + Map _fontName(String fontString) { + if (fontString.contains('#2C')) { + fontString = fontString.replaceAll("#2C", ","); + } + final _PdfReader reader = _PdfReader(utf8.encode(fontString)); + reader.position = 0; + String? prevToken = reader._getNextToken(); + String? token = reader._getNextToken(); + String? name; + double height = 0; + while (token != null && token.isNotEmpty) { + name = prevToken; + prevToken = token; + token = reader._getNextToken(); + if (token == _Operators.setFont) { + try { + height = double.parse(prevToken); + } catch (e) { + height = 0; + } + break; + } + } + return {'name': name, 'height': height}; + } + + //Gets the font style + List _getFontStyle(String fontFamilyString) { + String standardName = fontFamilyString; + int position = standardName.indexOf('-'); + if (position >= 0) { + standardName = standardName.substring(position + 1, standardName.length); + } + position = standardName.indexOf(','); + if (position >= 0) { + standardName = standardName.substring(position + 1, standardName.length); + } + List style = [PdfFontStyle.regular]; + if (position >= 0) { + switch (standardName) { + case 'Italic': + case 'Oblique': + case 'ItalicMT': + case 'It': + style = [PdfFontStyle.italic]; + break; + case 'Bold': + case 'BoldMT': + style = [PdfFontStyle.bold]; + break; + case 'BoldItalic': + case 'BoldOblique': + case 'BoldItalicMT': + style = [PdfFontStyle.italic, PdfFontStyle.bold]; + break; + } + } + return style; + } + + Map _getFontFamily(String fontFamilyString) { + String? standardName; + final int position = fontFamilyString.indexOf('-'); + PdfFontFamily fontFamily = PdfFontFamily.helvetica; + standardName = fontFamilyString; + if (position >= 0) { + standardName = fontFamilyString.substring(0, position); + } + if (standardName == 'Times') { + fontFamily = PdfFontFamily.timesRoman; + standardName = null; + } + final List fontFamilyList = [ + 'Helvetica', + 'Courier', + 'TimesRoman', + 'Symbol', + 'ZapfDingbats' + ]; + if (standardName != null && fontFamilyList.contains(standardName)) { + fontFamily = PdfFontFamily.values[fontFamilyList.indexOf(standardName)]; + standardName = null; + } + return {'fontFamily': fontFamily, 'standardName': standardName}; + } + + PdfFont _updateFontEncoding(PdfFont font, _PdfDictionary fontDictionary) { + final _PdfDictionary? fontInternalDictionary = + font._fontInternals as _PdfDictionary?; + if (fontDictionary._items! + .containsKey(_PdfName(_DictionaryProperties.encoding))) { + final _PdfName encodingName = _PdfName(_DictionaryProperties.encoding); + final _IPdfPrimitive? encodingReferenceHolder = + fontDictionary._items![_PdfName(_DictionaryProperties.encoding)]; + if (encodingReferenceHolder != null && + encodingReferenceHolder is _PdfReferenceHolder) { + final _IPdfPrimitive? dictionary = encodingReferenceHolder.object; + if (dictionary != null && dictionary is _PdfDictionary) { + if (fontInternalDictionary!._items! + .containsKey(_PdfName(_DictionaryProperties.encoding))) { + fontInternalDictionary._items! + .remove(_PdfName(_DictionaryProperties.encoding)); + } + fontInternalDictionary._items![encodingName] = dictionary; + } + } else { + final _IPdfPrimitive? encodingDictionary = + fontDictionary._items![_PdfName(_DictionaryProperties.encoding)]; + if (encodingDictionary != null && + encodingDictionary is _PdfDictionary) { + if (fontInternalDictionary!._items! + .containsKey(_PdfName(_DictionaryProperties.encoding))) { + fontInternalDictionary._items! + .remove(_PdfName(_DictionaryProperties.encoding)); + } + fontInternalDictionary._items![encodingName] = encodingDictionary; + } + } + } + return font; + } + + String _getEnumName(dynamic annotText) { + annotText = annotText.toString(); + final int index = annotText.indexOf('.'); + final String name = annotText.substring(index + 1); + return name[0].toUpperCase() + name.substring(1); + } + + _PdfFontMetrics _createFont( + _PdfDictionary fontDictionary, double? height, _PdfName baseFont) { + final _PdfFontMetrics fontMetrics = _PdfFontMetrics(); + if (fontDictionary.containsKey(_DictionaryProperties.fontDescriptor)) { + _IPdfPrimitive? createFontDictionary; + final _IPdfPrimitive? fontReferenceHolder = + fontDictionary[_DictionaryProperties.fontDescriptor]; + if (fontReferenceHolder != null && + fontReferenceHolder is _PdfReferenceHolder) { + createFontDictionary = fontReferenceHolder.object; + } else { + createFontDictionary = + fontDictionary[_DictionaryProperties.fontDescriptor]; + } + if (createFontDictionary != null && + createFontDictionary is _PdfDictionary) { + fontMetrics.ascent = + (createFontDictionary[_DictionaryProperties.ascent] as _PdfNumber) + .value as double; + fontMetrics.descent = + (createFontDictionary[_DictionaryProperties.descent] as _PdfNumber) + .value as double; + fontMetrics.size = height!; + fontMetrics.height = fontMetrics.ascent - fontMetrics.descent; + fontMetrics.postScriptName = baseFont._name; + } + } + _PdfArray? array; + if (fontDictionary.containsKey(_DictionaryProperties.widths)) { + if (fontDictionary[_DictionaryProperties.widths] is _PdfReferenceHolder) { + final _PdfReferenceHolder tableReference = + _PdfReferenceHolder(fontDictionary[_DictionaryProperties.widths]); + final _PdfReferenceHolder tableArray = + tableReference.object as _PdfReferenceHolder; + array = (tableArray.object) as _PdfArray?; + final List widthTable = []; + for (int i = 0; i < array!.count; i++) { + widthTable.add((array[i] as _PdfNumber).value as int); + } + fontMetrics._widthTable = _StandardWidthTable(widthTable); + } else { + array = (fontDictionary[_DictionaryProperties.widths] as _PdfArray?); + final List widthTable = []; + for (int i = 0; i < array!.count; i++) { + widthTable.add((array[i] as _PdfNumber).value!.toInt()); + } + fontMetrics._widthTable = _StandardWidthTable(widthTable); + } + } + fontMetrics.name = baseFont._name!; + return fontMetrics; + } + + PdfFont? _getFontByName(String? name, double height) { + PdfFont? font; + switch (name) { + case 'CoBO': //"Courier-BoldOblique" + font = PdfStandardFont(PdfFontFamily.courier, height, + multiStyle: [PdfFontStyle.bold, PdfFontStyle.italic]); + break; + case 'CoBo': //"Courier-Bold" + font = PdfStandardFont(PdfFontFamily.courier, height, + style: PdfFontStyle.bold); + break; + case 'CoOb': //"Courier-Oblique" + font = PdfStandardFont(PdfFontFamily.courier, height, + style: PdfFontStyle.italic); + break; + case 'Courier': + case 'Cour': //"Courier" + font = PdfStandardFont(PdfFontFamily.courier, height, + style: PdfFontStyle.regular); + break; + case 'HeBO': //"Helvetica-BoldOblique" + font = PdfStandardFont(PdfFontFamily.helvetica, height, + multiStyle: [PdfFontStyle.bold, PdfFontStyle.italic]); + break; + case 'HeBo': //"Helvetica-Bold" + font = PdfStandardFont(PdfFontFamily.helvetica, height, + style: PdfFontStyle.bold); + break; + case 'HeOb': //"Helvetica-Oblique" + font = PdfStandardFont(PdfFontFamily.helvetica, height, + style: PdfFontStyle.italic); + break; + case 'Helv': //"Helvetica" + font = PdfStandardFont(PdfFontFamily.helvetica, height, + style: PdfFontStyle.regular); + break; + case 'Symb': // "Symbol" + font = PdfStandardFont(PdfFontFamily.symbol, height); + break; + case 'TiBI': // "Times-BoldItalic" + font = PdfStandardFont(PdfFontFamily.timesRoman, height, + multiStyle: [PdfFontStyle.bold, PdfFontStyle.italic]); + break; + case 'TiBo': // "Times-Bold" + font = PdfStandardFont(PdfFontFamily.timesRoman, height, + style: PdfFontStyle.bold); + break; + case 'TiIt': // "Times-Italic" + font = PdfStandardFont(PdfFontFamily.timesRoman, height, + style: PdfFontStyle.italic); + break; + case 'TiRo': // "Times-Roman" + font = PdfStandardFont(PdfFontFamily.timesRoman, height, + style: PdfFontStyle.regular); + break; + case 'ZaDb': // "ZapfDingbats" + font = PdfStandardFont(PdfFontFamily.zapfDingbats, height); + break; + } + return font; + } + + //Gets the font color. + PdfColor _getForeColor(String? defaultAppearance) { + PdfColor colour = PdfColor(0, 0, 0); + if (defaultAppearance == null || defaultAppearance.isEmpty) { + colour = PdfColor(0, 0, 0); + } else { + final _PdfReader reader = _PdfReader(utf8.encode(defaultAppearance)); + reader.position = 0; + bool symbol = false; + final List operands = []; + String? token = reader._getNextToken(); + if (token == '/') { + symbol = true; + } + while (token != null && token.isNotEmpty) { + if (symbol == true) { + token = reader._getNextToken(); + } + symbol = true; + if (token == 'g') { + colour = PdfColor._fromGray(_parseFloatColour(operands.last!)); + } else if (token == 'rg') { + colour = PdfColor( + (_parseFloatColour(operands.elementAt(operands.length - 3)!) * + 255) + .toInt() + .toUnsigned(8), + (_parseFloatColour(operands.elementAt(operands.length - 2)!) * + 255) + .toInt() + .toUnsigned(8), + (_parseFloatColour(operands.last!) * 255).toInt().toUnsigned(8)); + operands.clear(); + } else if (token == 'k') { + colour = PdfColor.fromCMYK( + _parseFloatColour(operands.elementAt(operands.length - 4)!), + _parseFloatColour(operands.elementAt(operands.length - 3)!), + _parseFloatColour(operands.elementAt(operands.length - 2)!), + _parseFloatColour(operands.last!)); + operands.clear(); + } else { + operands.add(token); + } + } + } + return colour; + } + + double _parseFloatColour(String text) { + double number; + try { + number = double.parse(text); + } catch (e) { + number = 0; + } + return number; + } + + PdfColor _getBackColor([bool isBrush = false]) { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + PdfColor c = isBrush ? PdfColor(255, 255, 255, 255) : PdfColor(0, 0, 0); + if (widget.containsKey(_DictionaryProperties.mk)) { + final _IPdfPrimitive? bs = + _crossTable!._getObject(widget[_DictionaryProperties.mk]); + if (bs is _PdfDictionary) { + _IPdfPrimitive? array; + if (bs.containsKey(_DictionaryProperties.bg)) { + array = bs[_DictionaryProperties.bg]; + } else if (bs.containsKey(_DictionaryProperties.bs)) { + array = bs[_DictionaryProperties.bs]; + } + if (array != null && array is _PdfArray) { + c = _createColor(array); + } + } + } + return c; + } + + PdfColor _createColor(_PdfArray array) { + final int dim = array.count; + PdfColor color = PdfColor.empty; + final List colors = []; + for (int i = 0; i < array.count; ++i) { + final _PdfNumber number = _crossTable!._getObject(array[i]) as _PdfNumber; + colors.add(number.value!.toDouble()); + } + switch (dim) { + case 1: + color = (colors[0] > 0.0) && (colors[0] <= 1.0) + ? PdfColor._fromGray(colors[0]) + : PdfColor._fromGray(colors[0].toInt().toUnsigned(8).toDouble()); + break; + case 3: + color = ((colors[0] > 0.0) && (colors[0] <= 1.0)) || + ((colors[1] > 0.0) && (colors[1] <= 1.0)) || + ((colors[2] > 0.0) && (colors[2] <= 1.0)) + ? PdfColor( + (colors[0] * 255).toInt().toUnsigned(8), + (colors[1] * 255).toInt().toUnsigned(8), + (colors[2] * 255).toInt().toUnsigned(8)) + : PdfColor( + colors[0].toInt().toUnsigned(8), + colors[1].toInt().toUnsigned(8), + colors[2].toInt().toUnsigned(8)); + break; + case 4: + color = ((colors[0] > 0.0) && (colors[0] <= 1.0)) || + ((colors[1] > 0.0) && (colors[1] <= 1.0)) || + ((colors[2] > 0.0) && (colors[2] <= 1.0)) || + ((colors[3] > 0.0) && (colors[3] <= 1.0)) + ? PdfColor.fromCMYK(colors[0], colors[1], colors[2], colors[3]) + : PdfColor.fromCMYK( + colors[0].toInt().toUnsigned(8).toDouble(), + colors[1].toInt().toUnsigned(8).toDouble(), + colors[2].toInt().toUnsigned(8).toDouble(), + colors[3].toInt().toUnsigned(8).toDouble()); + break; + } + return color; + } + + void _assignBackColor(PdfColor? value) { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + if (widget.containsKey(_DictionaryProperties.mk)) { + final _PdfDictionary mk = _crossTable! + ._getObject(widget[_DictionaryProperties.mk]) as _PdfDictionary; + final _PdfArray array = value!._toArray(); + mk[_DictionaryProperties.bg] = array; + } else { + final _PdfDictionary mk = _PdfDictionary(); + final _PdfArray array = value!._toArray(); + mk[_DictionaryProperties.bg] = array; + widget[_DictionaryProperties.mk] = mk; + } + form!._setAppearanceDictionary = true; + } + + void _assignBorderColor(PdfColor borderColor) { + if (_dictionary.containsKey(_DictionaryProperties.kids)) { + final _IPdfPrimitive? kids = + _crossTable!._getObject(_dictionary[_DictionaryProperties.kids]); + if (kids != null && kids is _PdfArray) { + for (int i = 0; i < kids.count; i++) { + final _IPdfPrimitive? widget = _PdfCrossTable._dereference(kids[i]); + if (widget != null && widget is _PdfDictionary) { + if (widget.containsKey(_DictionaryProperties.mk)) { + final _IPdfPrimitive? mk = + _crossTable!._getObject(widget[_DictionaryProperties.mk]); + if (mk != null && mk is _PdfDictionary) { + final _PdfArray array = borderColor._toArray(); + if (borderColor._alpha == 0) { + mk[_DictionaryProperties.bc] = _PdfArray([]); + } else { + mk[_DictionaryProperties.bc] = array; + } + } + } else { + final _PdfDictionary mk = _PdfDictionary(); + final _PdfArray array = borderColor._toArray(); + if (borderColor._alpha == 0) { + mk[_DictionaryProperties.bc] = _PdfArray([]); + } else { + mk[_DictionaryProperties.bc] = array; + } + widget[_DictionaryProperties.mk] = mk; + } + } + } + } + } else { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + if (widget.containsKey(_DictionaryProperties.mk)) { + final _IPdfPrimitive? mk = + _crossTable!._getObject(widget[_DictionaryProperties.mk]); + if (mk != null && mk is _PdfDictionary) { + final _PdfArray array = borderColor._toArray(); + if (borderColor._alpha == 0) { + mk[_DictionaryProperties.bc] = _PdfArray([]); + } else { + mk[_DictionaryProperties.bc] = array; + } + } + } else { + final _PdfDictionary mk = _PdfDictionary(); + final _PdfArray array = borderColor._toArray(); + if (borderColor._alpha == 0) { + mk[_DictionaryProperties.bc] = _PdfArray([]); + } else { + mk[_DictionaryProperties.bc] = array; + } + widget[_DictionaryProperties.mk] = mk; + } + } + } + + int _obtainBorderWidth() { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + int width = 0; + final _IPdfPrimitive? name = + _crossTable!._getObject(widget[_DictionaryProperties.ft]); + if (widget.containsKey(_DictionaryProperties.bs)) { + width = 1; + final _PdfDictionary bs = _crossTable! + ._getObject(widget[_DictionaryProperties.bs]) as _PdfDictionary; + final _IPdfPrimitive? number = + _crossTable!._getObject(bs[_DictionaryProperties.w]); + if (number != null && number is _PdfNumber) { + width = number.value!.toInt(); + } + } else if (name != null && name is _PdfName && name._name == 'Btn') { + width = 1; + } + return width; + } + + void _assignBorderWidth(int width) { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + if (widget.containsKey(_DictionaryProperties.bs)) { + if (widget[_DictionaryProperties.bs] is _PdfReferenceHolder) { + final _PdfDictionary widgetDict = _crossTable! + ._getObject(widget[_DictionaryProperties.bs]) as _PdfDictionary; + if (widgetDict.containsKey(_DictionaryProperties.w)) { + widgetDict[_DictionaryProperties.w] = _PdfNumber(width); + } else { + widgetDict.setProperty(_DictionaryProperties.w, _PdfNumber(width)); + } + } else { + (widget[_DictionaryProperties.bs] + as _PdfDictionary)[_DictionaryProperties.w] = _PdfNumber(width); + } + _createBorderPen(); + } else { + if (!widget.containsKey(_DictionaryProperties.bs)) { + widget.setProperty( + _DictionaryProperties.bs, _widget!._widgetBorder!._dictionary); + (widget[_DictionaryProperties.bs] as _PdfDictionary) + .setProperty(_DictionaryProperties.w, _PdfNumber(width)); + _createBorderPen(); + } + } + } + + PdfStringFormat _assignStringFormat() { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + final PdfStringFormat stringFormat = PdfStringFormat(); + stringFormat.lineAlignment = PdfVerticalAlignment.middle; + stringFormat.lineAlignment = + ((_flagValue & _getFieldFlagsValue(_FieldFlags.multiline)) > 0) + ? PdfVerticalAlignment.top + : PdfVerticalAlignment.middle; + _IPdfPrimitive? number; + if (widget.containsKey(_DictionaryProperties.q)) { + number = _crossTable!._getObject(widget[_DictionaryProperties.q]); + } else if (_dictionary.containsKey(_DictionaryProperties.q)) { + number = _crossTable!._getObject(_dictionary[_DictionaryProperties.q]); + } + if (number != null && number is _PdfNumber) { + stringFormat.alignment = PdfTextAlignment.values[number.value!.toInt()]; + } + return stringFormat; + } + + PdfBrush? _obtainShadowBrush() { + PdfBrush? brush = PdfBrushes.white; + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + if (widget.containsKey(_DictionaryProperties.da)) { + PdfColor? color = PdfColor(255, 255, 255); + if (_backBrush is PdfSolidBrush) { + color = (_backBrush as PdfSolidBrush).color; + } + color.r = (color.r - 64 >= 0 ? color.r - 64 : 0).toUnsigned(8); + color.g = (color.g - 64 >= 0 ? color.g - 64 : 0).toUnsigned(8); + color.b = (color.b - 64 >= 0 ? color.b - 64 : 0).toUnsigned(8); + brush = PdfSolidBrush(color); + } + return brush; + } + + PdfBorderStyle _obtainBorderStyle() { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + PdfBorderStyle style = PdfBorderStyle.solid; + if (widget.containsKey(_DictionaryProperties.bs)) { + final _PdfDictionary bs = _crossTable! + ._getObject(widget[_DictionaryProperties.bs]) as _PdfDictionary; + style = _createBorderStyle(bs); + } + return style; + } + + PdfBorderStyle _createBorderStyle(_PdfDictionary bs) { + PdfBorderStyle style = PdfBorderStyle.solid; + if (bs.containsKey(_DictionaryProperties.s)) { + final _IPdfPrimitive? name = + _crossTable!._getObject(bs[_DictionaryProperties.s]); + if (name != null && name is _PdfName) { + switch (name._name!.toLowerCase()) { + case 'd': + style = PdfBorderStyle.dashed; + break; + case 'b': + style = PdfBorderStyle.beveled; + break; + case 'i': + style = PdfBorderStyle.inset; + break; + case 'u': + style = PdfBorderStyle.underline; + break; + } + } + } + return style; + } + + void _assignBorderStyle(PdfBorderStyle? borderStyle) { + String style = ''; + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + if (widget.containsKey(_DictionaryProperties.bs)) { + switch (borderStyle) { + case PdfBorderStyle.dashed: + case PdfBorderStyle.dot: + style = 'D'; + break; + case PdfBorderStyle.beveled: + style = 'B'; + break; + case PdfBorderStyle.inset: + style = 'I'; + break; + case PdfBorderStyle.underline: + style = 'U'; + break; + default: + style = 'S'; + break; + } + if (widget[_DictionaryProperties.bs] is _PdfReferenceHolder) { + final _PdfDictionary widgetDict = _crossTable! + ._getObject(widget[_DictionaryProperties.bs]) as _PdfDictionary; + if (widgetDict.containsKey(_DictionaryProperties.s)) { + widgetDict[_DictionaryProperties.s] = _PdfName(style); + } else { + widgetDict.setProperty(_DictionaryProperties.s, _PdfName(style)); + } + } else { + final _PdfDictionary bsDict = + (widget[_DictionaryProperties.bs] as _PdfDictionary); + if (bsDict.containsKey(_DictionaryProperties.s)) { + bsDict[_DictionaryProperties.s] = _PdfName(style); + } else { + bsDict.setProperty(_DictionaryProperties.s, _PdfName(style)); + } + } + _widget!._widgetBorder!.borderStyle = borderStyle!; + } else { + if (!widget.containsKey(_DictionaryProperties.bs)) { + _widget!._widgetBorder!.borderStyle = borderStyle!; + widget.setProperty( + _DictionaryProperties.bs, _widget!._widgetBorder!._dictionary); + } + } + if (widget.containsKey(_DictionaryProperties.mk) && + widget[_DictionaryProperties.mk] is _PdfDictionary) { + final _PdfDictionary mkDict = + (widget[_DictionaryProperties.mk] as _PdfDictionary); + if (!mkDict.containsKey(_DictionaryProperties.bc) && + !mkDict.containsKey(_DictionaryProperties.bg)) { + _widget!._widgetAppearance!._dictionary._items! + .forEach((_PdfName? key, _IPdfPrimitive? value) { + mkDict.setProperty(key, value); + }); + } + } else { + widget.setProperty(_DictionaryProperties.mk, _widget!._widgetAppearance); + } + } + + PdfHighlightMode _obtainHighlightMode() { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + PdfHighlightMode mode = PdfHighlightMode.noHighlighting; + if (widget.containsKey(_DictionaryProperties.h)) { + final _PdfName name = widget[_DictionaryProperties.h] as _PdfName; + switch (name._name) { + case 'I': + mode = PdfHighlightMode.invert; + break; + case 'N': + mode = PdfHighlightMode.noHighlighting; + break; + case 'O': + mode = PdfHighlightMode.outline; + break; + case 'P': + mode = PdfHighlightMode.push; + break; + } + } + return mode; + } + + void _assignHighlightMode(PdfHighlightMode? highlightMode) { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + widget._setName(_PdfName(_DictionaryProperties.h), + _widget!._highlightModeToString(highlightMode)); + _changed = true; + } + + Rect _getBounds() { + _IPdfPrimitive? array; + if (_dictionary.containsKey(_DictionaryProperties.kids)) { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + if (widget.containsKey(_DictionaryProperties.rect)) { + array = _crossTable!._getObject(widget[_DictionaryProperties.rect]); + } + } else { + if (_dictionary.containsKey(_DictionaryProperties.parent)) { + final _IPdfPrimitive? parentDictionary = + (_dictionary[_DictionaryProperties.parent] as _PdfReferenceHolder) + .object; + if (parentDictionary != null && + parentDictionary is _PdfDictionary && + parentDictionary.containsKey(_DictionaryProperties.kids)) { + if (parentDictionary.containsKey(_DictionaryProperties.ft) && + (parentDictionary[_DictionaryProperties.ft] as _PdfName)._name == + _DictionaryProperties.btn) { + final _PdfDictionary widget = + _getWidgetAnnotation(parentDictionary, _crossTable); + if (widget.containsKey(_DictionaryProperties.rect)) { + array = + _crossTable!._getObject(widget[_DictionaryProperties.rect]); + } + } + } + } + if (array == null && + _dictionary.containsKey(_DictionaryProperties.rect)) { + array = + _crossTable!._getObject(_dictionary[_DictionaryProperties.rect]); + } + } + Rect bounds; + if (array != null && array is _PdfArray) { + bounds = array.toRectangle().rect; + double? y = 0; + if ((_PdfCrossTable._dereference(array[1]) as _PdfNumber).value! < 0) { + y = (_PdfCrossTable._dereference(array[1]) as _PdfNumber).value + as double?; + if ((_PdfCrossTable._dereference(array[1]) as _PdfNumber).value! > + (_PdfCrossTable._dereference(array[3]) as _PdfNumber).value!) { + y = y! - bounds.height; + } + } + bounds = Rect.fromLTWH( + bounds.left, y! <= 0 ? bounds.top : y, bounds.width, bounds.height); + } else { + bounds = Rect.fromLTWH(0, 0, 0, 0); + } + return bounds; + } + + PdfPen? _obtainBorderPen() { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + PdfPen? pen; + if (widget.containsKey(_DictionaryProperties.mk)) { + final _IPdfPrimitive? mk = + _crossTable!._getObject(widget[_DictionaryProperties.mk]); + if (mk is _PdfDictionary && mk.containsKey(_DictionaryProperties.bc)) { + final _PdfArray array = + _crossTable!._getObject(mk[_DictionaryProperties.bc]) as _PdfArray; + pen = PdfPen(_createColor(array)); + } + } + if (pen != null) { + pen.width = _borderWidth.toDouble(); + if (_borderStyle == PdfBorderStyle.dashed) { + final List? dashPatern = _obtainDashPatern(); + pen.dashStyle = PdfDashStyle.custom; + if (dashPatern != null) { + pen.dashPattern = dashPatern; + } else if (_borderWidth > 0) { + pen.dashPattern = [3 / _borderWidth]; + } + } + } + return (pen == null) ? _bPen : pen; + } + + List? _obtainDashPatern() { + List? array = null; + if (_borderStyle == PdfBorderStyle.dashed) { + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + if (widget.containsKey(_DictionaryProperties.d)) { + final _IPdfPrimitive? dashes = + _crossTable!._getObject(widget[_DictionaryProperties.d]); + if (dashes != null && dashes is _PdfArray) { + if (dashes.count == 2) { + array = [0, 0]; + _IPdfPrimitive? number = dashes[0]; + if (number != null && number is _PdfNumber) { + array[0] = number.value!.toDouble(); + } + number = dashes[1]; + if (number != null && number is _PdfNumber) { + array[1] = number.value!.toDouble(); + } + } else { + array = [0]; + final _IPdfPrimitive? number = dashes[0]; + if (number != null && number is _PdfNumber) { + array[0] = number.value!.toDouble(); + } + } + } + } + } + return array; + } + + void _setFittingFontSize( + _GraphicsProperties gp, _PaintParams prms, String text) { + double fontSize = 0; + final double width = prms._style == PdfBorderStyle.beveled || + prms._style == PdfBorderStyle.inset + ? gp._bounds!.width - 8 * prms._borderWidth! + : gp._bounds!.width - 4 * prms._borderWidth!; + final double height = gp._bounds!.height - 2 * gp._borderWidth!; + final double minimumFontSize = 0.248; + if (text.endsWith(' ')) { + gp._stringFormat!.measureTrailingSpaces = true; + } + for (double i = 0; i <= gp._bounds!.height; i++) { + gp._font!._setSize(i); + Size textSize = gp._font!.measureString(text, format: gp._stringFormat); + if (textSize.width > gp._bounds!.width || textSize.height > height) { + fontSize = i; + do { + fontSize = fontSize - 0.001; + gp._font!._setSize(fontSize); + final double textWidth = + gp._font!._getLineWidth(text, gp._stringFormat); + if (fontSize < minimumFontSize) { + gp._font!._setSize(minimumFontSize); + break; + } + textSize = gp._font!.measureString(text, format: gp._stringFormat); + if (textWidth < width && textSize.height < height) { + gp._font!._setSize(fontSize); + break; + } + } while (fontSize > minimumFontSize); + break; + } + } + } + + double _getFontHeight(PdfFontFamily family) { + return 0; + } + + void _addAnnotationToPage(PdfPage? page, PdfAnnotation? widget) { + if (page != null && !page._isLoadedPage) { + widget!._dictionary + .setProperty(_DictionaryProperties.t, _PdfString(_name!)); + } else { + final _PdfDictionary pageDic = page!._dictionary; + _PdfArray? annots; + if (pageDic.containsKey(_DictionaryProperties.annots)) { + final _IPdfPrimitive? obj = + page._crossTable!._getObject(pageDic[_DictionaryProperties.annots]); + if (obj != null && obj is _PdfArray) { + annots = obj; + } + } + annots ??= _PdfArray(); + widget!._dictionary + .setProperty(_DictionaryProperties.p, _PdfReferenceHolder(page)); + form!.fieldAutoNaming + ? widget._dictionary + .setProperty(_DictionaryProperties.t, _PdfString(_name!)) + : _dictionary.setProperty( + _DictionaryProperties.t, _PdfString(_name!)); + annots._add(_PdfReferenceHolder(widget)); + page._dictionary.setProperty(_DictionaryProperties.annots, annots); + } + } + + void _beginMarkupSequence(_PdfStream stream) { + stream._write('/'); + stream._write('Tx'); + stream._write(' '); + stream._write('BMC'); + stream._write('\r\n'); + } + + void _endMarkupSequence(_PdfStream stream) { + stream._write('EMC'); + stream._write('\r\n'); + } + + void _drawStateItem( + PdfGraphics graphics, _PdfCheckFieldState state, PdfCheckFieldBase? item, + [PdfFieldItem? fieldItem]) { + final _GraphicsProperties gp = item != null + ? _GraphicsProperties(item) + : _GraphicsProperties.fromFieldItem(fieldItem!); + if (!_flattenField) { + gp._bounds = Rect.fromLTWH(0, 0, gp._bounds!.width, gp._bounds!.height); + } + if (gp._borderPen != null && gp._borderWidth == 0) { + gp._borderWidth = 1; + } + graphics.save(); + final _PaintParams prms = _PaintParams( + bounds: gp._bounds, + backBrush: gp._backBrush, + foreBrush: gp._foreBrush, + borderPen: gp._borderPen, + style: gp._style, + borderWidth: gp._borderWidth, + shadowBrush: gp._shadowBrush); + if (_fieldChanged == true) { + _drawFields(graphics, gp, prms, state); + } else { + graphics._streamWriter!._setTextRenderingMode(0); + final PdfTemplate? stateTemplate = _getStateTemplate( + state, item != null ? item._dictionary : fieldItem!._dictionary); + if (stateTemplate != null) { + final Rect bounds = item == null && fieldItem == null + ? this.bounds + : item != null + ? item.bounds + : fieldItem!.bounds; + bool encryptedContent = false; + if (_crossTable != null && + _crossTable!._document != null && + _crossTable!._document!._isLoadedDocument) { + final PdfDocument? loadedDocument = _crossTable!._document; + if (loadedDocument != null && loadedDocument._isEncrypted) { + if (loadedDocument.security._encryptor._encrypt! && + loadedDocument.security.encryptionOptions == + PdfEncryptionOptions.encryptAllContents) + encryptedContent = true; + } + } + final _PdfStream? pdfStream = stateTemplate._content; + if (pdfStream != null && + encryptedContent && + pdfStream.encrypt! && + !pdfStream.decrypted! && + this is PdfCheckBoxField) { + gp._font = null; + _FieldPainter()._drawCheckBox( + graphics, + prms, + (this as PdfCheckBoxField) + ._styleToString((this as PdfCheckBoxField).style), + state, + gp._font); + } else { + graphics.drawPdfTemplate(stateTemplate, bounds.topLeft, bounds.size); + } + } else { + _drawFields(graphics, gp, prms, state); + } + } + graphics.restore(); + } + + void _drawFields(PdfGraphics graphics, _GraphicsProperties gp, + _PaintParams params, _PdfCheckFieldState state) { + if (gp._font!.size >= 0) { + gp._font = null; + } + if (this is PdfCheckBoxField) { + _FieldPainter()._drawCheckBox( + graphics, + params, + (this as PdfCheckBoxField) + ._styleToString((this as PdfCheckBoxField).style), + state, + gp._font); + } else if (this is PdfRadioButtonListItem) { + _FieldPainter().drawRadioButton( + graphics, + params, + (this as PdfRadioButtonListItem) + ._styleToString((this as PdfRadioButtonListItem).style), + state); + } + } + + PdfTemplate? _getStateTemplate( + _PdfCheckFieldState state, _PdfDictionary? itemDictionary) { + final _PdfDictionary dic = + itemDictionary != null ? itemDictionary : _dictionary; + final String? value = state == _PdfCheckFieldState.checked + ? _getItemValue(dic, _crossTable) + : _DictionaryProperties.off; + PdfTemplate? template; + if (dic.containsKey(_DictionaryProperties.ap)) { + final _IPdfPrimitive? appearance = + _PdfCrossTable._dereference(dic[_DictionaryProperties.ap]); + if (appearance != null && appearance is _PdfDictionary) { + final _IPdfPrimitive? norm = + _PdfCrossTable._dereference(appearance[_DictionaryProperties.n]); + if (value != null && + value.isNotEmpty && + norm != null && + norm is _PdfDictionary) { + final _IPdfPrimitive? xObject = + _PdfCrossTable._dereference(norm[value]); + if (xObject != null && xObject is _PdfStream) { + template = PdfTemplate._fromPdfStream(xObject); + if (value == _DictionaryProperties.off && + xObject.encrypt! && + xObject.decrypted!) { + //AP stream undecrypted might cause document corruption + template = null; + } + } + } + } + } + return template; + } + + _PdfArray? _obtainKids() { + _IPdfPrimitive? kids; + if (_dictionary.containsKey(_DictionaryProperties.kids)) { + kids = _crossTable!._getObject(_dictionary[_DictionaryProperties.kids]); + } + return kids != null && kids is _PdfArray ? kids : null; + } + + String? _getItemValue(_PdfDictionary dictionary, _PdfCrossTable? crossTable) { + String? value = ''; + _PdfName? name; + if (dictionary.containsKey(_DictionaryProperties.usageApplication)) { + name = crossTable! + ._getObject(dictionary[_DictionaryProperties.usageApplication]) + as _PdfName?; + if (name != null && name._name != _DictionaryProperties.off) { + value = name._name; + } + } + if (value!.isEmpty) { + if (dictionary.containsKey(_DictionaryProperties.ap)) { + final _PdfDictionary dic = crossTable! + ._getObject(dictionary[_DictionaryProperties.ap]) as _PdfDictionary; + if (dic.containsKey(_DictionaryProperties.n)) { + final _PdfReference reference = + crossTable._getReference(dic[_DictionaryProperties.n]); + final _PdfDictionary normalAppearance = + crossTable._getObject(reference) as _PdfDictionary; + final List list = []; + normalAppearance._items! + .forEach((_PdfName? key, _IPdfPrimitive? value) { + list.add(key); + }); + for (int i = 0; i < list.length; ++i) { + name = list[i] as _PdfName?; + if (name!._name != _DictionaryProperties.off) { + value = name._name; + break; + } + } + } + } + } + return value; + } + + //Overrides + @override + _IPdfPrimitive? get _element => _dictionary; + + @override + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + throw ArgumentError('Primitive element can\'t be set'); + } +} + +/// Represents the graphic properties of field. +class _GraphicsProperties { + //Constructor + _GraphicsProperties(PdfField field) { + _bounds = field.bounds; + _borderPen = field._borderPen; + _style = field._borderStyle; + _borderWidth = field._borderWidth; + _backBrush = field._backBrush; + _foreBrush = field._foreBrush; + _shadowBrush = field._shadowBrush; + _font = field._font; + _stringFormat = field._format; + if (field.page != null && + field.page!._rotation != PdfPageRotateAngle.rotateAngle0) { + _bounds = + _rotateTextbox(field.bounds, field.page!.size, field.page!._rotation); + } + } + + _GraphicsProperties.fromFieldItem(PdfFieldItem item) { + _bounds = item.bounds; + _borderPen = item._borderPen; + _style = item._borderStyle; + _borderWidth = item._borderWidth; + _backBrush = item._backBrush; + _foreBrush = item._foreBrush; + _shadowBrush = item._shadowBrush; + _font = item._font; + _stringFormat = item._format; + if (item.page != null && + item.page!._rotation != PdfPageRotateAngle.rotateAngle0) { + _bounds = + _rotateTextbox(item.bounds, item.page!.size, item.page!._rotation); + } + } + + //Fields + Rect? _bounds; + PdfBrush? _foreBrush; + PdfBrush? _backBrush; + PdfBrush? _shadowBrush; + int? _borderWidth; + PdfBorderStyle? _style; + PdfPen? _borderPen; + PdfFont? _font; + PdfStringFormat? _stringFormat; + + //Implementation + Rect _rotateTextbox(Rect rect, Size? size, PdfPageRotateAngle angle) { + Rect rectangle = rect; + if (angle == PdfPageRotateAngle.rotateAngle180) { + rectangle = Rect.fromLTWH(size!.width - rect.left - rect.width, + size.height - rect.top - rect.height, rect.width, rect.height); + } + if (angle == PdfPageRotateAngle.rotateAngle270) { + rectangle = Rect.fromLTWH(rect.top, size!.width - rect.left - rect.width, + rect.height, rect.width); + } + if (angle == PdfPageRotateAngle.rotateAngle90) { + rectangle = Rect.fromLTWH(size!.height - rect.top - rect.height, + rect.left, rect.height, rect.width); + } + return rectangle; + } +} + +//typedef for NameChanged event handler. +typedef _BeforeNameChangesEventHandler = void Function(String name); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item.dart new file mode 100644 index 000000000..07bc21b7b --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item.dart @@ -0,0 +1,165 @@ +part of pdf; + +/// Represents base class for field's group items. +class PdfFieldItem { + //Constructor + /// Initializes a new instance of the [PdfFieldItem] class. + PdfFieldItem._(PdfField field, int index, _PdfDictionary? dictionary) { + _field = field; + _collectionIndex = index; + _dictionary = dictionary; + } + + //Field + late PdfField _field; + int _collectionIndex = 0; + _PdfDictionary? _dictionary; + PdfPage? _page; + + //Properties + /// Gets or sets the bounds. + Rect get bounds { + final int backUpIndex = _field._defaultIndex; + _field._defaultIndex = _collectionIndex; + final Rect rect = _field.bounds; + _field._defaultIndex = backUpIndex; + return rect; + } + + set bounds(Rect value) { + if (value.isEmpty) { + ArgumentError('bounds can\'t be null/empty'); + } + final int backUpIndex = _field._defaultIndex; + _field._defaultIndex = _collectionIndex; + _field.bounds = value; + _field._defaultIndex = backUpIndex; + } + + /// Gets the page of the field. + PdfPage? get page { + if (_page == null) { + final int backUpIndex = _field._defaultIndex; + _field._defaultIndex = _collectionIndex; + _page = _field.page; + final _PdfName pName = _PdfName(_DictionaryProperties.p); + if (_field._kids != null && _field._kids!.count > 0) { + final PdfDocument? doc = _field._crossTable!._document; + if (doc != null && doc._isLoadedDocument) { + if (_dictionary!.containsKey(pName)) { + final _IPdfPrimitive? pageRef = _field._crossTable! + ._getObject(_dictionary![_DictionaryProperties.p]); + if (pageRef != null && pageRef is _PdfDictionary) { + final _PdfReference widgetReference = + _field._crossTable!._getReference(_dictionary); + for (int i = 0; i < doc.pages.count; i++) { + final PdfPage loadedPage = doc.pages[i]; + final _PdfArray? lAnnots = loadedPage._obtainAnnotations(); + if (lAnnots != null) { + for (int i = 0; i < lAnnots.count; i++) { + final _IPdfPrimitive? holder = lAnnots[i]; + if (holder != null && + holder is _PdfReferenceHolder && + holder.reference!._objNum == widgetReference._objNum && + holder.reference!._genNum == widgetReference._genNum) { + _page = doc.pages._getPage(pageRef); + _field._defaultIndex = backUpIndex; + return _page; + } + } + } + } + _field._defaultIndex = backUpIndex; + _page = null; + } + } else { + final _PdfReference widgetReference = + _field._crossTable!._getReference(_dictionary); + for (int i = 0; i < doc.pages.count; i++) { + final PdfPage loadedPage = doc.pages[i]; + final _PdfArray? lAnnots = loadedPage._obtainAnnotations(); + if (lAnnots != null) { + for (int i = 0; i < lAnnots.count; i++) { + final _IPdfPrimitive? holder = lAnnots[i]; + if (holder != null && + holder is _PdfReferenceHolder && + holder.reference!._objNum == widgetReference._objNum && + holder.reference!._genNum == widgetReference._genNum) { + return _page = loadedPage; + } + } + } + } + _page = null; + } + } + } + _field._defaultIndex = backUpIndex; + } + return _page; + } + + PdfPen? get _borderPen { + final int backUpIndex = _field._defaultIndex; + _field._defaultIndex = _collectionIndex; + final PdfPen? pen = _field._borderPen; + _field._defaultIndex = backUpIndex; + return pen; + } + + PdfBorderStyle? get _borderStyle { + final int backUpIndex = _field._defaultIndex; + _field._defaultIndex = _collectionIndex; + final PdfBorderStyle? bs = _field._borderStyle; + _field._defaultIndex = backUpIndex; + return bs; + } + + int get _borderWidth { + final int backUpIndex = _field._defaultIndex; + _field._defaultIndex = _collectionIndex; + final int borderWidth = _field._borderWidth; + _field._defaultIndex = backUpIndex; + return borderWidth; + } + + PdfStringFormat? get _format { + final int backUpIndex = _field._defaultIndex; + _field._defaultIndex = _collectionIndex; + final PdfStringFormat? sFormat = _field._format; + _field._defaultIndex = backUpIndex; + return sFormat; + } + + PdfBrush? get _backBrush { + final int backUpIndex = _field._defaultIndex; + _field._defaultIndex = _collectionIndex; + final PdfBrush? backBrush = _field._backBrush; + _field._defaultIndex = backUpIndex; + return backBrush; + } + + PdfBrush? get _foreBrush { + final int backUpIndex = _field._defaultIndex; + _field._defaultIndex = _collectionIndex; + final PdfBrush? foreBrush = _field._foreBrush; + _field._defaultIndex = backUpIndex; + return foreBrush; + } + + PdfBrush? get _shadowBrush { + final int backUpIndex = _field._defaultIndex; + _field._defaultIndex = _collectionIndex; + final PdfBrush? shadowBrush = _field._shadowBrush; + _field._defaultIndex = backUpIndex; + return shadowBrush; + } + + PdfFont get _font { + final int backUpIndex = _field._defaultIndex; + _field._defaultIndex = _collectionIndex; + final PdfFont font = _field._font!; + _field._defaultIndex = backUpIndex; + return font; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item_collection.dart new file mode 100644 index 000000000..34665d905 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item_collection.dart @@ -0,0 +1,33 @@ +part of pdf; + +/// Represents collection field items. +class PdfFieldItemCollection extends PdfObjectCollection { + //Constructor + PdfFieldItemCollection._(PdfField field) { + _field = field; + } + + //Properties + /// Gets the Field item at the specified index. + PdfFieldItem operator [](int index) { + if (index < 0 || index >= count) { + throw RangeError('index'); + } + return _list[index] as PdfFieldItem; + } + + //Fields + late PdfField _field; + + //Implementations + /// Clears all items in the list. + void clear() { + _field._array._clear(); + _list.clear(); + _field._dictionary.remove(_DictionaryProperties.kids); + } + + void _add(PdfFieldItem item) { + _list.add(item); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_painter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_painter.dart new file mode 100644 index 000000000..a4b6003b2 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_painter.dart @@ -0,0 +1,566 @@ +part of pdf; + +/// Represents class which draws form fields. +class _FieldPainter { + //Constructor + _FieldPainter(); + + //Implementations + //Draws a rectangular control. + void drawRectangularControl(PdfGraphics graphics, _PaintParams params) { + graphics.drawRectangle( + bounds: params._bounds ?? Rect.fromLTWH(0, 0, 0, 0), + brush: params._backBrush); + drawBorder(graphics, params._bounds, params._borderPen, params._style, + params._borderWidth); + switch (params._style) { + case PdfBorderStyle.inset: + drawLeftTopShadow( + graphics, params._bounds!, params._borderWidth!, PdfBrushes.gray); + drawRightBottomShadow( + graphics, params._bounds!, params._borderWidth!, PdfBrushes.silver); + break; + case PdfBorderStyle.beveled: + drawLeftTopShadow( + graphics, params._bounds!, params._borderWidth!, PdfBrushes.white); + drawRightBottomShadow(graphics, params._bounds!, params._borderWidth!, + params._shadowBrush); + break; + default: + break; + } + } + + void _drawCheckBox(PdfGraphics g, _PaintParams paintParams, + String checkSymbol, _PdfCheckFieldState state, + [PdfFont? font]) { + switch (state) { + case _PdfCheckFieldState.unchecked: + case _PdfCheckFieldState.checked: + if (paintParams._borderPen != null && + paintParams._borderPen!.color._alpha != 0) { + g.drawRectangle( + brush: paintParams._backBrush, bounds: paintParams._bounds!); + } + break; + + case _PdfCheckFieldState.pressedChecked: + case _PdfCheckFieldState.pressedUnchecked: + if ((paintParams._style == PdfBorderStyle.beveled) || + (paintParams._style == PdfBorderStyle.underline)) { + if (paintParams._borderPen != null && + paintParams._borderPen!.color._alpha != 0) { + g.drawRectangle( + brush: paintParams._backBrush, bounds: paintParams._bounds!); + } + } else { + if (paintParams._borderPen != null && + paintParams._borderPen!.color._alpha != 0) { + g.drawRectangle( + brush: paintParams._shadowBrush, bounds: paintParams._bounds!); + } + } + break; + } + + drawBorder(g, paintParams._bounds, paintParams._borderPen, + paintParams._style, paintParams._borderWidth); + + if ((state == _PdfCheckFieldState.pressedChecked) || + (state == _PdfCheckFieldState.pressedUnchecked)) { + switch (paintParams._style) { + case PdfBorderStyle.inset: + drawLeftTopShadow(g, paintParams._bounds!, paintParams._borderWidth!, + PdfBrushes.black); + drawRightBottomShadow(g, paintParams._bounds!, + paintParams._borderWidth!, PdfBrushes.white); + break; + + case PdfBorderStyle.beveled: + drawLeftTopShadow(g, paintParams._bounds!, paintParams._borderWidth!, + paintParams._shadowBrush); + drawRightBottomShadow(g, paintParams._bounds!, + paintParams._borderWidth!, PdfBrushes.white); + break; + default: + } + } else { + switch (paintParams._style) { + case PdfBorderStyle.inset: + drawLeftTopShadow(g, paintParams._bounds!, paintParams._borderWidth!, + PdfBrushes.gray); + drawRightBottomShadow(g, paintParams._bounds!, + paintParams._borderWidth!, PdfBrushes.silver); + break; + + case PdfBorderStyle.beveled: + drawLeftTopShadow(g, paintParams._bounds!, paintParams._borderWidth!, + PdfBrushes.white); + drawRightBottomShadow(g, paintParams._bounds!, + paintParams._borderWidth!, paintParams._shadowBrush); + break; + default: + } + } + double yOffset = 0; + double size = 0; + switch (state) { + case _PdfCheckFieldState.pressedChecked: + case _PdfCheckFieldState.checked: + if (font == null) { + final bool extraBorder = + paintParams._style == PdfBorderStyle.beveled || + paintParams._style == PdfBorderStyle.inset; + + double borderWidth = paintParams._borderWidth!.toDouble(); + if (extraBorder) { + borderWidth *= 2; + } + double xPosition = (extraBorder + ? 2.0 * paintParams._borderWidth! + : paintParams._borderWidth!.toDouble()); + xPosition = max(xPosition, 1); + final double xOffset = min(borderWidth, xPosition); + + size = (paintParams._bounds!.width > paintParams._bounds!.height) + ? paintParams._bounds!.height + : paintParams._bounds!.width; + + final double fontSize = size - 2 * xOffset; + + font = PdfStandardFont(PdfFontFamily.zapfDingbats, fontSize); + if (paintParams._bounds!.width > paintParams._bounds!.height) { + yOffset = ((paintParams._bounds!.height - font.height) / 2); + } + } else { + font = PdfStandardFont(PdfFontFamily.zapfDingbats, font.size); + } + if (size == 0) { + size = paintParams._bounds!.height; + } + + if (size < font.size) { + ArgumentError.value( + 'Font size cannot be greater than CheckBox height'); + } + g.drawString(checkSymbol, font, + brush: paintParams._foreBrush, + bounds: Rect.fromLTWH( + paintParams._bounds!.left, + paintParams._bounds!.top - yOffset, + paintParams._bounds!.width, + paintParams._bounds!.height), + format: PdfStringFormat( + alignment: PdfTextAlignment.center, + lineAlignment: PdfVerticalAlignment.middle)); + break; + default: + } + } + + //Draws a border. + void drawBorder(PdfGraphics graphics, Rect? bounds, PdfPen? borderPen, + PdfBorderStyle? style, int? borderWidth) { + if (borderPen != null) { + if (borderWidth! > 0 && !borderPen.color.isEmpty) { + if (style == PdfBorderStyle.underline) { + graphics.drawLine( + borderPen, + Offset( + bounds!.left, bounds.top + bounds.height - borderWidth / 2), + Offset(bounds.left + bounds.width, + bounds.top + bounds.height - borderWidth / 2)); + } else { + graphics.drawRectangle( + pen: borderPen, + bounds: Rect.fromLTWH( + bounds!.left + borderWidth / 2, + bounds.top + borderWidth / 2, + bounds.width - borderWidth, + bounds.height - borderWidth)); + } + } + } + } + + void drawRadioButton(PdfGraphics? g, _PaintParams paintParams, + String checkSymbol, _PdfCheckFieldState state) { + //if the symbol is not a circle type ("l") then we need to draw the checkbox appearance + if (checkSymbol != 'l') { + _drawCheckBox(g!, paintParams, checkSymbol, state, null); + } else { + switch (state) { + case _PdfCheckFieldState.unchecked: + case _PdfCheckFieldState.checked: + g!.drawEllipse(paintParams._bounds!, brush: paintParams._backBrush); + break; + + case _PdfCheckFieldState.pressedChecked: + case _PdfCheckFieldState.pressedUnchecked: + if ((paintParams._style == PdfBorderStyle.beveled) || + (paintParams._style == PdfBorderStyle.underline)) { + g!.drawEllipse(paintParams._bounds!, brush: paintParams._backBrush); + } else { + g!.drawEllipse(paintParams._bounds!, + brush: paintParams._shadowBrush); + } + + break; + } + drawRoundBorder(g, paintParams._bounds, paintParams._borderPen, + paintParams._borderWidth); + drawRoundShadow(g, paintParams, state); + switch (state) { + case _PdfCheckFieldState.checked: + case _PdfCheckFieldState.pressedChecked: + final Rect outward = Rect.fromLTWH( + paintParams._bounds!.left + paintParams._borderWidth! / 2.0, + paintParams._bounds!.top + paintParams._borderWidth! / 2.0, + paintParams._bounds!.width - paintParams._borderWidth!, + paintParams._bounds!.height - paintParams._borderWidth!); + Rect checkedBounds = outward; + checkedBounds = Rect.fromLTWH( + checkedBounds.left + (outward.width / 4), + checkedBounds.top + (outward.width / 4), + checkedBounds.width - (outward.width / 2), + checkedBounds.height - (outward.width / 2)); + g.drawEllipse(checkedBounds, + brush: paintParams._foreBrush ?? PdfBrushes.black); + break; + default: + break; + } + } + } + + //Draws the left top shadow. + void drawLeftTopShadow( + PdfGraphics graphics, Rect bounds, int width, PdfBrush? brush) { + final List points = [ + Offset(bounds.left + width, bounds.top + width), + Offset(bounds.left + width, bounds.bottom - width), + Offset(bounds.left + 2 * width, bounds.bottom - 2 * width), + Offset(bounds.left + 2 * width, bounds.top + 2 * width), + Offset(bounds.right - 2 * width, bounds.top + 2 * width), + Offset(bounds.right - width, bounds.top + width) + ]; + graphics.drawPath(PdfPath()..addPolygon(points), brush: brush); + } + + //Draws the right bottom shadow. + void drawRightBottomShadow( + PdfGraphics graphics, Rect bounds, int width, PdfBrush? brush) { + final List points = [ + Offset(bounds.left + width, bounds.bottom - width), + Offset(bounds.left + 2 * width, bounds.bottom - 2 * width), + Offset(bounds.right - 2 * width, bounds.bottom - 2 * width), + Offset(bounds.right - 2 * width, bounds.top + 2 * width), + Offset(bounds.left + bounds.width - width, bounds.top + width), + Offset(bounds.right - width, bounds.bottom - width) + ]; + graphics.drawPath(PdfPath()..addPolygon(points), brush: brush); + } + + void drawButton(PdfGraphics g, _PaintParams paintParams, String text, + PdfFont font, PdfStringFormat? format) { + drawRectangularControl(g, paintParams); + Rect? rectangle = paintParams._bounds; + + if ((g._layer != null && + g._page != null && + g._page!._rotation != PdfPageRotateAngle.rotateAngle0) || + (paintParams._rotationAngle! > 0)) { + final PdfGraphicsState state = g.save(); + + if ((g._layer != null) && + (g._page!._rotation != PdfPageRotateAngle.rotateAngle0)) { + if (g._page!._rotation == PdfPageRotateAngle.rotateAngle90) { + g.translateTransform(g.size.height, 0); + g.rotateTransform(90); + final double y = + g._page!.size.height - (rectangle!.left + rectangle.width); + final double x = rectangle.top; + rectangle = Rect.fromLTWH(x, y, rectangle.height, rectangle.width); + } else if (g._page!._rotation == PdfPageRotateAngle.rotateAngle180) { + g.translateTransform(g._page!.size.width, g._page!.size.height); + g.rotateTransform(-180); + final Size size = g._page!.size; + final double x = size.width - (rectangle!.left + rectangle.width); + final double y = size.height - (rectangle.top + rectangle.height); + rectangle = Rect.fromLTWH(x, y, rectangle.width, rectangle.height); + } else if (g._page!._rotation == PdfPageRotateAngle.rotateAngle270) { + g.translateTransform(0, g.size.width); + g.rotateTransform(270); + final double x = + g._page!.size.width - (rectangle!.top + rectangle.height); + final double y = rectangle.left; + rectangle = Rect.fromLTWH(x, y, rectangle.height, rectangle.width); + } + } + g.drawString(text, font, + brush: paintParams._foreBrush, bounds: rectangle, format: format); + g.restore(state); + } else { + g.drawString(text, font, + brush: paintParams._foreBrush, bounds: rectangle, format: format); + } + } + + void drawPressedButton(PdfGraphics g, _PaintParams paintParams, String text, + PdfFont font, PdfStringFormat? format) { + switch (paintParams._style) { + case PdfBorderStyle.inset: + g.drawRectangle( + brush: paintParams._shadowBrush, bounds: paintParams._bounds!); + break; + default: + g.drawRectangle( + brush: paintParams._backBrush, bounds: paintParams._bounds!); + break; + } + + drawBorder(g, paintParams._bounds, paintParams._borderPen, + paintParams._style, paintParams._borderWidth); + + final Rect rectangle = Rect.fromLTWH( + paintParams._borderWidth!.toDouble(), + paintParams._borderWidth!.toDouble(), + paintParams._bounds!.size.width - paintParams._borderWidth!, + paintParams._bounds!.size.height - paintParams._borderWidth!); + g.drawString(text, font, + brush: paintParams._foreBrush, bounds: rectangle, format: format); + + switch (paintParams._style) { + case PdfBorderStyle.inset: + drawLeftTopShadow(g, paintParams._bounds!, paintParams._borderWidth!, + PdfBrushes.gray); + drawRightBottomShadow(g, paintParams._bounds!, + paintParams._borderWidth!, PdfBrushes.silver); + break; + + case PdfBorderStyle.beveled: + drawLeftTopShadow(g, paintParams._bounds!, paintParams._borderWidth!, + paintParams._shadowBrush); + drawRightBottomShadow(g, paintParams._bounds!, + paintParams._borderWidth!, PdfBrushes.white); + break; + + default: + drawLeftTopShadow(g, paintParams._bounds!, paintParams._borderWidth!, + paintParams._shadowBrush); + break; + } + } + + void drawRoundBorder( + PdfGraphics? g, Rect? bounds, PdfPen? borderPen, int? borderWidth) { + Rect? outward = bounds; + if (outward != Rect.zero) { + outward = Rect.fromLTWH( + bounds!.left + borderWidth! / 2.0, + bounds.top + borderWidth / 2.0, + bounds.width - borderWidth, + bounds.height - borderWidth); + g!.drawEllipse(outward, pen: borderPen); + } + } + + void drawRoundShadow( + PdfGraphics? g, _PaintParams paintParams, _PdfCheckFieldState state) { + final double borderWidth = paintParams._borderWidth!.toDouble(); + final Rect rectangle = paintParams._bounds!; + rectangle.inflate(-1.5 * borderWidth); + PdfPen? leftTopPen; + PdfPen? rightBottomPen; + final PdfSolidBrush shadowBrush = paintParams._shadowBrush as PdfSolidBrush; + final PdfColor shadowColor = shadowBrush.color; + + switch (paintParams._style) { + case PdfBorderStyle.beveled: + switch (state) { + case _PdfCheckFieldState.pressedChecked: + case _PdfCheckFieldState.pressedUnchecked: + leftTopPen = PdfPen(shadowColor, width: borderWidth); + rightBottomPen = + PdfPen(PdfColor(255, 255, 255), width: borderWidth); + break; + + case _PdfCheckFieldState.checked: + case _PdfCheckFieldState.unchecked: + leftTopPen = PdfPen(PdfColor(255, 255, 255), width: borderWidth); + rightBottomPen = PdfPen(shadowColor, width: borderWidth); + break; + default: + } + break; + + case PdfBorderStyle.inset: + switch (state) { + case _PdfCheckFieldState.pressedChecked: + case _PdfCheckFieldState.pressedUnchecked: + leftTopPen = PdfPen(PdfColor(0, 0, 0), width: borderWidth); + rightBottomPen = PdfPen(PdfColor(0, 0, 0), width: borderWidth); + break; + + case _PdfCheckFieldState.checked: + case _PdfCheckFieldState.unchecked: + leftTopPen = + PdfPen(PdfColor(255, 128, 128, 128), width: borderWidth); + rightBottomPen = + PdfPen(PdfColor(255, 192, 192, 192), width: borderWidth); + break; + } + break; + default: + } + if (leftTopPen != null && rightBottomPen != null) { + g!.drawArc(rectangle, 135, 180, pen: leftTopPen); + g.drawArc(rectangle, -45, 180, pen: rightBottomPen); + } + } + + //Draws the combo box + void drawComboBox(PdfGraphics graphics, _PaintParams paintParams, + String? text, PdfFont? font, PdfStringFormat? format) { + drawRectangularControl(graphics, paintParams); + Rect? rectangle = paintParams._bounds; + if (graphics._layer != null && + graphics._page!._rotation != PdfPageRotateAngle.rotateAngle0) { + final PdfGraphicsState state = graphics.save(); + final Size? size = graphics._page!.size; + if (graphics._page!._rotation == PdfPageRotateAngle.rotateAngle90) { + graphics.translateTransform(graphics.size.height, 0); + graphics.rotateTransform(90); + rectangle = Rect.fromLTWH( + rectangle!.left, + size!.height - rectangle.left - rectangle.width, + rectangle.height, + rectangle.width); + } else if (graphics._page!._rotation == + PdfPageRotateAngle.rotateAngle180) { + graphics.translateTransform( + graphics._page!.size.width, graphics._page!.size.height); + graphics.rotateTransform(-180); + rectangle = Rect.fromLTWH( + size!.width - rectangle!.left - rectangle.width, + size.height - rectangle.top - rectangle.height, + rectangle.width, + rectangle.height); + } else if (graphics._page!._rotation == + PdfPageRotateAngle.rotateAngle270) { + graphics.translateTransform(0, graphics.size.width); + graphics.rotateTransform(270); + rectangle = Rect.fromLTWH( + size!.width - rectangle!.top - rectangle.height, + rectangle.left, + rectangle.height, + rectangle.width); + } + graphics.drawString(text!, font!, + brush: paintParams._foreBrush, bounds: rectangle, format: format); + graphics.restore(state); + } else { + graphics.drawString(text!, font!, + brush: paintParams._foreBrush, bounds: rectangle, format: format); + } + } + + //Draws the list box + void drawListBox( + PdfGraphics graphics, + _PaintParams params, + PdfListFieldItemCollection items, + List selectedItem, + PdfFont font, + PdfStringFormat? stringFormat) { + _FieldPainter().drawRectangularControl(graphics, params); + for (int index = 0; index < items.count; index++) { + final PdfListFieldItem item = items[index]; + final int borderWidth = params._borderWidth!; + final double doubleBorderWidth = (2 * borderWidth).toDouble(); + final bool padding = params._style == PdfBorderStyle.inset || + params._style == PdfBorderStyle.beveled; + final Offset point = padding + ? Offset(2 * doubleBorderWidth, + ((index + 2) * borderWidth + font.size * index)) + : Offset(doubleBorderWidth, + ((index + 1) * borderWidth + font.size * index)); + PdfBrush? brush = params._foreBrush; + double width = params._bounds!.width - doubleBorderWidth; + final Rect rectangle = Rect.fromLTWH( + params._bounds!.left, + params._bounds!.top, + params._bounds!.width, + params._bounds!.height - (padding ? doubleBorderWidth : borderWidth)); + graphics.setClip(bounds: rectangle, mode: PdfFillMode.winding); + bool selected = false; + for (final selectedIndex in selectedItem) { + if (selectedIndex == index) { + selected = true; + } + } + if (selected) { + double x = rectangle.left + borderWidth; + if (padding) { + x += borderWidth; + width -= doubleBorderWidth; + } + brush = PdfSolidBrush(PdfColor(51, 153, 255, 255)); + graphics.drawRectangle( + brush: brush, + bounds: Rect.fromLTWH(x, point.dy, width, font.height)); + brush = PdfSolidBrush(PdfColor(255, 255, 255, 255)); + } + final String value = item.text; + final _Rectangle itemTextBound = + _Rectangle(point.dx, point.dy, width - point.dx, font.height); + graphics._layoutString(value, font, + brush: brush ?? PdfSolidBrush(PdfColor(0, 0, 0)), + layoutRectangle: itemTextBound, + format: stringFormat); + } + } + + //Draws the text box + void drawTextBox(PdfGraphics graphics, _PaintParams params, String text, + PdfFont font, PdfStringFormat format, bool insertSpaces, bool multiline) { + if (!insertSpaces) { + _FieldPainter().drawRectangularControl(graphics, params); + } + final int multiplier = params._style == PdfBorderStyle.beveled || + params._style == PdfBorderStyle.inset + ? 2 + : 1; + Rect rectangle = Rect.fromLTWH( + params._bounds!.left + (2 * multiplier) * params._borderWidth!, + params._bounds!.top + (2 * multiplier) * params._borderWidth!, + params._bounds!.width - (4 * multiplier) * params._borderWidth!, + params._bounds!.height - (4 * multiplier) * params._borderWidth!); + // Calculate position of the text. + if (multiline) { + final double? tempHeight = + format.lineSpacing == 0 ? font.height : format.lineSpacing; + final bool subScript = + format.subSuperscript == PdfSubSuperscript.subscript; + final double ascent = font._metrics!._getAscent(format); + final double descent = font._metrics!._getDescent(format); + final double shift = subScript + ? tempHeight! - (font.height + descent) + : tempHeight! - ascent; + if (rectangle.left == 0 && rectangle.top == 0) { + rectangle = Rect.fromLTWH(rectangle.left, -(rectangle.top - shift), + rectangle.width, rectangle.height); + } + } + graphics.drawString(text, font, + brush: params._foreBrush, + bounds: rectangle, + format: PdfStringFormat( + alignment: format.alignment, + lineAlignment: PdfVerticalAlignment.middle)); + } + + void drawSignature(PdfGraphics graphics, _PaintParams paintParams) { + drawRectangularControl(graphics, paintParams); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form.dart new file mode 100644 index 000000000..dcfb9b9cd --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form.dart @@ -0,0 +1,672 @@ +part of pdf; + +/// Represents interactive form of the PDF document. +class PdfForm implements _IPdfWrapper { + //Constructors + /// Initializes a new instance of the [PdfForm] class. + PdfForm() : super() { + _fields = PdfFormFieldCollection._(); + _fields!._form = this; + _dictionary.setProperty(_DictionaryProperties.fields, _fields); + if (!_isLoadedForm) { + _dictionary._beginSave = _beginSave; + } + _setAppearanceDictionary = true; + } + + PdfForm._internal(_PdfCrossTable? crossTable, + [_PdfDictionary? formDictionary]) { + _isLoadedForm = true; + _crossTable = crossTable; + _dictionary._setBoolean( + _DictionaryProperties.needAppearances, _needAppearances); + _crossTable!._document!._catalog._beginSaveList ??= []; + _crossTable!._document!._catalog._beginSaveList!.add(_beginSave); + _crossTable!._document!._catalog.modify(); + if (_crossTable!._document!._catalog + .containsKey(_DictionaryProperties.perms)) { + _checkPerms(_crossTable!._document!._catalog); + } + if (formDictionary != null) { + _initialize(formDictionary, crossTable!); + } + } + + //Fields + _PdfDictionary _dictionary = _PdfDictionary(); + PdfFormFieldCollection? _fields; + bool? _needAppearances = false; + bool _setAppearanceDictionary = true; + _PdfResources? _resource; + final List _fieldNames = []; + _PdfCrossTable? _crossTable; + List<_PdfDictionary> _terminalFields = []; + bool _formHasKids = false; + bool _isLoadedForm = false; + bool _isDefaultAppearance = true; + Map>? _widgetDictionary; + bool _readOnly = false; + bool _isUR3 = false; + List<_SignatureFlags> _signatureFlags = [_SignatureFlags.none]; + + /// Gets or sets a value indicating whether field auto naming. + /// + /// The default value is true. + bool fieldAutoNaming = true; + + //Properties + /// Gets the fields collection. + PdfFormFieldCollection get fields { + if (_isLoadedForm) { + _fields ??= PdfFormFieldCollection._(this); + } + return _fields!; + } + + //Gets or sets the resources. + _PdfResources get _resources { + if (_resource == null) { + _resource = _PdfResources(); + _dictionary.setProperty(_DictionaryProperties.dr, _resource); + } + return _resource!; + } + + set _resources(_PdfResources value) { + _resource = value; + if (_isLoadedForm) { + _dictionary.setProperty(_DictionaryProperties.dr, value); + } + } + + /// Gets or sets a value indicating whether the form is read only. + /// + /// The default value is false. + bool get readOnly => _readOnly; + set readOnly(bool value) { + _readOnly = value; + if (_isLoadedForm) { + for (int i = 0; i < fields.count; i++) { + fields[i].readOnly = value; + } + } + } + + /// Gets or sets a value indicating whether this [PdfForm] is flatten. + bool _flatten = false; + + //Public methods. + /// Specifies whether to set the default appearance for the form or not. + void setDefaultAppearance(bool value) { + _needAppearances = value; + _setAppearanceDictionary = true; + _isDefaultAppearance = true; + } + + /// Flatten all the fields available in the form. + /// + /// The flatten will add at the time of saving the current document. + void flattenAllFields() { + _flatten = true; + } + + //Implementation + //Raises before stream saves. + void _beginSave(Object sender, _SavePdfPrimitiveArgs? ars) { + if (!_isLoadedForm) { + if (_signatureFlags.length > 1 || + (_signatureFlags.isNotEmpty && + !_signatureFlags.contains(_SignatureFlags.none))) { + _setSignatureFlags(_signatureFlags); + _needAppearances = false; + } + _checkFlatten(); + if (fields.count > 0 && _setAppearanceDictionary) { + _dictionary._setBoolean( + _DictionaryProperties.needAppearances, _needAppearances); + } + } else { + int i = 0; + if (_signatureFlags.length > 1 || + (_signatureFlags.isNotEmpty && + !_signatureFlags.contains(_SignatureFlags.none))) { + _setSignatureFlags(_signatureFlags); + _dictionary.changed = true; + if (!_isDefaultAppearance) { + _needAppearances = false; + } + if (_dictionary.containsKey(_DictionaryProperties.needAppearances)) + _dictionary._setBoolean( + _DictionaryProperties.needAppearances, _needAppearances); + } + while (i < fields.count) { + final PdfField? field = fields[i]; + if (field != null) { + if (field._isLoadedField) { + final _PdfDictionary dic = field._dictionary; + bool isNeedAppearance = false; + if (!dic.containsKey(_DictionaryProperties.ap) && + _isDefaultAppearance && + !_needAppearances! && + !field._changed) { + isNeedAppearance = true; + } + if (field._flags.length > 1) { + field._changed = true; + field._setFlags(field._flags); + } + int fieldFlag = 0; + if (dic.containsKey(_DictionaryProperties.f)) { + final _IPdfPrimitive? flag = + _PdfCrossTable._dereference(dic[_DictionaryProperties.f]); + if (flag != null && flag is _PdfNumber) { + fieldFlag = flag.value!.toInt(); + } + } + _PdfArray? kids; + if (field._dictionary.containsKey(_DictionaryProperties.kids)) { + kids = _PdfCrossTable._dereference( + field._dictionary[_DictionaryProperties.kids]) as _PdfArray?; + } + if (field._flattenField && fieldFlag != 6) { + if (field.page != null || kids != null) { + field._draw(); + } + fields.remove(field); + final int? index = + _crossTable!._items!._lookFor(field._dictionary); + if (index != -1) { + _crossTable!._items!._objectCollection!.removeAt(index!); + } + --i; + } else if (field._changed || isNeedAppearance) { + field._beginSave(); + } + } else { + if (field._flattenField) { + fields.remove(field); + field._draw(); + --i; + } else { + field._save(); + } + } + } + ++i; + } + if (_setAppearanceDictionary) { + _dictionary._setBoolean( + _DictionaryProperties.needAppearances, _needAppearances); + } + } + } + + void _setSignatureFlags(List<_SignatureFlags> value) { + int n = 0; + value.forEach((element) => n |= element.index); + _dictionary._setNumber(_DictionaryProperties.sigFlags, n); + } + + String? _getCorrectName(String? name) { + String? correctName = name; + if (_fieldNames.contains(name)) { + final int firstIndex = _fieldNames.indexOf(name); + final int lastIndex = _fieldNames.lastIndexOf(name); + if (firstIndex != lastIndex) { + correctName = name! + '_' + _PdfResources._globallyUniqueIdentifier; + _fieldNames.removeAt(lastIndex); + _fieldNames.add(correctName); + } + } + return correctName; + } + + void _deleteFromPages(PdfField field) { + final _PdfDictionary dic = field._dictionary; + final _PdfName kidsName = _PdfName(_DictionaryProperties.kids); + final _PdfName annotsName = _PdfName(_DictionaryProperties.annots); + final _PdfName pName = _PdfName(_DictionaryProperties.p); + final bool isLoaded = field._isLoadedField; + if (dic._items != null) { + if (dic.containsKey(kidsName)) { + final _PdfArray array = (dic[kidsName]) as _PdfArray; + for (int i = 0; i < array.count; ++i) { + final _PdfReferenceHolder holder = array[i] as _PdfReferenceHolder; + final _PdfDictionary? widget = holder.object as _PdfDictionary?; + _PdfDictionary? page; + if (!isLoaded) { + final _PdfReferenceHolder pageRef = + (widget![pName]) as _PdfReferenceHolder; + page = (pageRef.object) as _PdfDictionary?; + } else { + _PdfReference? pageRef; + if (widget!.containsKey(pName) && + !(widget[_DictionaryProperties.p] is _PdfNull)) { + pageRef = _crossTable!._getReference(widget[pName]); + } else if (dic.containsKey(pName) && + !(dic[_DictionaryProperties.p] is _PdfNull)) { + pageRef = _crossTable!._getReference(dic[pName]); + } else if (field.page != null) { + pageRef = _crossTable!._getReference(field.page!._dictionary); + } + page = _crossTable!._getObject(pageRef) as _PdfDictionary?; + } + if (page != null && page.containsKey(annotsName)) { + _PdfArray? annots; + if (isLoaded) { + annots = _crossTable!._getObject(page[annotsName]) as _PdfArray?; + for (int i = 0; i < annots!.count; i++) { + final _IPdfPrimitive? obj = annots._elements[i]; + if (obj != null && + obj is _PdfReferenceHolder && + obj.object is _PdfDictionary && + obj.object == holder.object) { + annots._remove(obj); + break; + } + } + annots.changed = true; + page.setProperty(annotsName, annots); + } else { + if (page[_DictionaryProperties.annots] is _PdfReferenceHolder) { + final _PdfReferenceHolder annotReference = + (page[_DictionaryProperties.annots]) as _PdfReferenceHolder; + annots = (annotReference.object) as _PdfArray?; + for (int i = 0; i < annots!.count; i++) { + final _IPdfPrimitive? obj = annots._elements[i]; + if (obj != null && + obj is _PdfReferenceHolder && + obj.object is _PdfDictionary && + obj.object == holder.object) { + annots._remove(obj); + break; + } + } + annots.changed = true; + page.setProperty(_DictionaryProperties.annots, annots); + } else if (page[_DictionaryProperties.annots] is _PdfArray) { + if (field._page != null) { + final PdfAnnotationCollection? annotCollection = + field._page!.annotations; + if (annotCollection != null && annotCollection.count > 0) { + final int index = + annotCollection._annotations._indexOf(holder); + if (index >= 0 && index < annotCollection.count) { + annotCollection.remove(annotCollection[index]); + } + } + } + if (annots != null && annots._contains(holder)) { + annots._remove(holder); + annots.changed = true; + page.setProperty(_DictionaryProperties.annots, annots); + } + } + } + } else if (isLoaded) { + field._requiredReference = holder; + if (field.page != null && + field.page!._dictionary.containsKey(annotsName)) { + final _PdfArray annots = _crossTable! + ._getObject(field.page!._dictionary[annotsName]) as _PdfArray; + for (int i = 0; i < annots.count; i++) { + final _IPdfPrimitive? obj = annots._elements[i]; + if (obj != null && + obj is _PdfReferenceHolder && + obj.object is _PdfDictionary && + obj.object == widget) { + annots._remove(obj); + break; + } + } + annots.changed = true; + } + if (_crossTable!._items != null && + _crossTable!._items!.contains(widget)) { + _crossTable!._items!._objectCollection! + .remove(_crossTable!._items!._lookFor(widget)); + } + field._requiredReference = null; + } + } + } else { + _PdfDictionary? page; + if (!isLoaded) { + final _PdfReferenceHolder pageRef = dic.containsKey(pName) + ? (dic[pName] as _PdfReferenceHolder?)! + : _PdfReferenceHolder(field._page!._dictionary); + page = pageRef.object as _PdfDictionary?; + } else { + _PdfReference? pageRef; + if (dic.containsKey(pName) && + !(dic[_DictionaryProperties.p] is _PdfNull)) { + pageRef = _crossTable!._getReference(dic[pName]); + } else if (field.page != null) { + pageRef = _crossTable!._getReference(field.page!._dictionary); + } + page = _crossTable!._getObject(pageRef) as _PdfDictionary?; + } + if (page != null && page.containsKey(_DictionaryProperties.annots)) { + final _IPdfPrimitive? annots = isLoaded + ? _crossTable!._getObject(page[annotsName]) + : page[_DictionaryProperties.annots]; + if (annots != null && annots is _PdfArray) { + for (int i = 0; i < annots.count; i++) { + final _IPdfPrimitive? obj = annots._elements[i]; + if (obj != null && + obj is _PdfReferenceHolder && + obj.object is _PdfDictionary && + obj.object == dic) { + annots._remove(obj); + break; + } + } + annots.changed = true; + page.setProperty(_DictionaryProperties.annots, annots); + } + } else if (isLoaded && + field.page != null && + field.page!._dictionary.containsKey(annotsName)) { + final _PdfArray annots = _crossTable! + ._getObject(field.page!._dictionary[annotsName]) as _PdfArray; + for (int i = 0; i < annots.count; i++) { + final _IPdfPrimitive? obj = annots._elements[i]; + if (obj != null && + obj is _PdfReferenceHolder && + obj.object is _PdfDictionary && + obj.object == dic) { + annots._remove(obj); + break; + } + } + annots.changed = true; + } + } + } + } + + void _deleteAnnotation(PdfField field) { + final _PdfDictionary dic = field._dictionary; + if (dic._items != null) { + if (dic.containsKey(_DictionaryProperties.kids)) { + _PdfArray? array; + array = !field._isLoadedField + ? dic[_DictionaryProperties.kids] as _PdfArray? + : _crossTable!._getObject(dic[_DictionaryProperties.kids]) + as _PdfArray?; + array!._clear(); + dic.setProperty(_DictionaryProperties.kids, array); + } + } + } + + void _initialize(_PdfDictionary formDictionary, _PdfCrossTable crossTable) { + _dictionary = formDictionary; + //Get terminal fields. + _createFields(); + //Gets NeedAppearance + if (_dictionary.containsKey(_DictionaryProperties.needAppearances)) { + final _PdfBoolean needAppearance = _crossTable! + ._getObject(_dictionary[_DictionaryProperties.needAppearances]) + as _PdfBoolean; + _needAppearances = needAppearance.value; + _setAppearanceDictionary = true; + } else { + _setAppearanceDictionary = false; + } + //Gets resource dictionary + if (_dictionary.containsKey(_DictionaryProperties.dr)) { + final _IPdfPrimitive? resources = + _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.dr]); + if (resources != null && resources is _PdfDictionary) { + _resources = _PdfResources(resources); + } + } + } + + //Retrieves the terminal fields. + void _createFields() { + _PdfArray? fields; + if (_dictionary.containsKey(_DictionaryProperties.fields)) { + final _IPdfPrimitive? obj = + _crossTable!._getObject(_dictionary[_DictionaryProperties.fields]); + if (obj != null) { + fields = obj as _PdfArray; + } + } + int count = 0; + final Queue<_NodeInfo> nodes = Queue(); + while (true && fields != null) { + for (; count < fields!.count; ++count) { + final _IPdfPrimitive? fieldDictionary = + _crossTable!._getObject(fields[count]); + _PdfArray? fieldKids; + if (fieldDictionary != null && + fieldDictionary is _PdfDictionary && + fieldDictionary.containsKey(_DictionaryProperties.kids)) { + final _IPdfPrimitive? fieldKid = _crossTable! + ._getObject(fieldDictionary[_DictionaryProperties.kids]); + if (fieldKid != null && fieldKid is _PdfArray) { + fieldKids = fieldKid; + for (int i = 0; i < fieldKids.count; i++) { + final _IPdfPrimitive? kidsDict = + _PdfCrossTable._dereference(fieldKids[i]); + if (kidsDict != null && + kidsDict is _PdfDictionary && + !kidsDict.containsKey(_DictionaryProperties.parent)) { + kidsDict[_DictionaryProperties.parent] = + _PdfReferenceHolder(fieldDictionary); + } + } + } + } + if (fieldKids == null) { + if (fieldDictionary != null) { + if (!_terminalFields.contains(fieldDictionary)) { + _terminalFields.add(fieldDictionary as _PdfDictionary); + } + } + } else { + if (!(fieldDictionary as _PdfDictionary) + .containsKey(_DictionaryProperties.ft) || + _isNode(fieldKids)) { + nodes.addFirst(_NodeInfo(fields, count)); + _formHasKids = true; + count = -1; + fields = fieldKids; + } else { + _terminalFields.add(fieldDictionary); + } + } + } + if (nodes.isEmpty) { + break; + } + final _NodeInfo nInfo = nodes.elementAt(0); + nodes.removeFirst(); + fields = nInfo._fields; + count = nInfo._count + 1; + } + } + + //Determines whether the specified kids is node. + bool _isNode(_PdfArray kids) { + bool isNode = false; + if (kids.count >= 1) { + final _PdfDictionary dictionary = + _crossTable!._getObject(kids[0]) as _PdfDictionary; + if (dictionary.containsKey(_DictionaryProperties.subtype)) { + final _PdfName name = _crossTable! + ._getObject(dictionary[_DictionaryProperties.subtype]) as _PdfName; + if (name._name != _DictionaryProperties.widget) { + isNode = true; + } + } + } + return isNode; + } + + //Removes field and kids annotation from dictionaries. + void _removeFromDictionaries(PdfField field) { + if (_fields != null && _fields!.count > 0) { + final _PdfName fieldsDict = _PdfName(_DictionaryProperties.fields); + final _PdfArray fields = + _crossTable!._getObject(_dictionary[fieldsDict]) as _PdfArray; + late _PdfReferenceHolder holder; + for (int i = 0; i < fields._elements.length; i++) { + final _IPdfPrimitive? obj = fields._elements[i]; + if (obj != null && + obj is _PdfReferenceHolder && + obj.object is _PdfDictionary && + obj.object == field._dictionary) { + holder = obj; + break; + } + } + fields._remove(holder); + fields.changed = true; + if (!_formHasKids || + !(field._dictionary._items! + .containsKey(_PdfName(_DictionaryProperties.parent)))) { + for (int i = 0; i < fields.count; i++) { + final _IPdfPrimitive? fieldDictionary = + _PdfCrossTable._dereference(_crossTable!._getObject(fields[i])); + final _PdfName kidsName = _PdfName(_DictionaryProperties.kids); + if (fieldDictionary != null && + fieldDictionary is _PdfDictionary && + fieldDictionary.containsKey(kidsName)) { + final _PdfArray kids = + _crossTable!._getObject(fieldDictionary[kidsName]) as _PdfArray; + for (int i = 0; i < kids.count; i++) { + final _IPdfPrimitive? obj = kids[i]; + if (obj != null && + obj is _PdfReferenceHolder && + obj.object == holder.object) kids._remove(obj); + } + } + } + } else { + if (field._dictionary._items! + .containsKey(_PdfName(_DictionaryProperties.parent))) { + final _PdfDictionary dic = + (field._dictionary[_DictionaryProperties.parent] + as _PdfReferenceHolder) + .object as _PdfDictionary; + final _PdfArray kids = + dic._items![_PdfName(_DictionaryProperties.kids)] as _PdfArray; + for (int k = 0; k < kids.count; k++) { + final _PdfReferenceHolder kidsReference = + kids[k] as _PdfReferenceHolder; + if (kidsReference.object == holder.object) { + kids._remove(kidsReference); + } + } + } + } + _dictionary.setProperty(fieldsDict, fields); + } + if (field._isLoadedField) { + _deleteFromPages(field); + _deleteAnnotation(field); + } + } + + void _checkFlatten() { + int i = 0; + while (i < _fields!.count) { + final PdfField field = _fields![i]; + if (field._flattenField) { + int? fieldFlag = 0; + final _PdfDictionary fieldDictionary = field._dictionary; + if (fieldDictionary.containsKey(_DictionaryProperties.f)) { + fieldFlag = (fieldDictionary[_DictionaryProperties.f] as _PdfNumber) + .value as int?; + } + if (fieldFlag != 6) { + _addFieldResourcesToPage(field); + field._draw(); + _fields!.remove(field); + _deleteFromPages(field); + _deleteAnnotation(field); + --i; + } + } else if (field._isLoadedField) { + field._beginSave(); + } else { + field._save(); + } + ++i; + } + } + + void _addFieldResourcesToPage(PdfField field) { + final _PdfResources formResources = field._form!._resources; + if (formResources.containsKey(_DictionaryProperties.font)) { + _IPdfPrimitive? fieldFontResource = + formResources[_DictionaryProperties.font]; + if (fieldFontResource is _PdfReferenceHolder) { + fieldFontResource = fieldFontResource.object as _PdfDictionary?; + } + if (fieldFontResource != null && fieldFontResource is _PdfDictionary) { + fieldFontResource._items!.keys.forEach((key) { + final _PdfResources pageResources = field.page!._getResources()!; + _IPdfPrimitive? pageFontResource = + pageResources[_DictionaryProperties.font]; + if (pageFontResource is _PdfDictionary) { + } else if (pageFontResource is _PdfReferenceHolder) { + pageFontResource = pageFontResource.object as _PdfDictionary?; + } + if (pageFontResource == null || + (pageFontResource is _PdfDictionary && + !pageFontResource.containsKey(key))) { + final _PdfReferenceHolder? fieldFontReference = (fieldFontResource + as _PdfDictionary)[key] as _PdfReferenceHolder?; + if (pageFontResource == null) { + final _PdfDictionary fontDictionary = _PdfDictionary(); + fontDictionary._items![key] = fieldFontReference; + pageResources[_DictionaryProperties.font] = fontDictionary; + } else { + (pageFontResource as _PdfDictionary)._items![key] = + fieldFontReference; + } + } + }); + } + } + } + + void _checkPerms(_PdfCatalog catalog) { + _IPdfPrimitive? permission = catalog[_DictionaryProperties.perms]; + if (permission is _PdfReferenceHolder) { + permission = + (catalog[_DictionaryProperties.perms] as _PdfReferenceHolder).object; + } + if (permission != null && + permission is _PdfDictionary && + permission.containsKey('UR3')) { + _isUR3 = true; + } + } + + //Overrides + @override + _IPdfPrimitive get _element => _dictionary; + + @override + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + throw ArgumentError('Primitive element can\'t be set'); + } +} + +class _NodeInfo { + //Constructor + _NodeInfo(_PdfArray? fields, int count) { + _fields = fields; + _count = count; + } + + //Fields + _PdfArray? _fields; + late int _count; +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form_field_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form_field_collection.dart new file mode 100644 index 000000000..b863a5568 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form_field_collection.dart @@ -0,0 +1,451 @@ +part of pdf; + +/// Represents a collection of form fields. +class PdfFormFieldCollection extends PdfObjectCollection + implements _IPdfWrapper { + //Constructor + /// Initializes a new instance of the [PdfFormFieldCollection] class. + PdfFormFieldCollection._([PdfForm? form]) : super() { + if (form != null) { + _form = form; + for (int i = 0; i < _form!._terminalFields.length; ++i) { + final PdfField? field = _getField(index: i); + if (field != null) { + _doAdd(field); + } + } + } + } + + //Fields + PdfForm? _form; + final _PdfArray _array = _PdfArray(); + bool _isAction = false; + final List _addedFieldNames = []; + + //Properties + /// Gets the [PdfField] at the specified index. + PdfField operator [](int index) { + if ((count < 0) || (index >= count)) { + throw RangeError('index'); + } + return _list[index] as PdfField; + } + + //Public methods + /// Adds the specified field to the collection. + int add(PdfField field) { + return _doAdd(field); + } + + /// Adds a list of fields to the collection. + void addAll(List fields) { + if (fields.isEmpty) { + throw ArgumentError('fields can\'t be empty'); + } + fields.forEach((element) => add(element)); + } + + /// Removes the specified field in the collection. + void remove(PdfField field) { + _doRemove(field); + } + + /// Removes field at the specified position. + void removeAt(int index) { + _doRemoveAt(index); + } + + /// Gets the index of the specific field. + int indexOf(PdfField field) { + return _list.indexOf(field); + } + + /// Determines whether field is contained within the collection. + bool contains(PdfField field) { + return _list.contains(field); + } + + /// Clears the form field collection. + void clear() { + _doClear(); + } + + //Implementations + int _doAdd(PdfField field) { + final bool isLoaded = _form != null ? _form!._isLoadedForm : false; + if (!_isAction) { + field._setForm(_form); + String? name = field.name; + _PdfArray? array; + bool skipField = false; + if (isLoaded) { + array = _form!._dictionary.containsKey(_DictionaryProperties.fields) + ? _form!._crossTable!._getObject( + _form!._dictionary[_DictionaryProperties.fields]) as _PdfArray? + : _PdfArray(); + if (field._dictionary._items! + .containsKey(_PdfName(_DictionaryProperties.parent))) { + skipField = true; + } + } else { + if (name == null || name.isEmpty) { + name = _PdfResources._globallyUniqueIdentifier; + } + _form!._fieldNames.add(name); + } + if (!isLoaded || !field._isLoadedField) { + if (_form!.fieldAutoNaming && !skipField) { + if (!isLoaded) { + field._applyName(_form!._getCorrectName(name)); + } else { + field._applyName(_getCorrectName(name)); + array!._add(_PdfReferenceHolder(field)); + _form!._dictionary.setProperty(_DictionaryProperties.fields, array); + } + } else if (isLoaded && !_addedFieldNames.contains(name) && !skipField) { + array!._add(_PdfReferenceHolder(field)); + _form!._dictionary.setProperty(_DictionaryProperties.fields, array); + } else if (isLoaded && + (!_addedFieldNames.contains(name) && skipField) || + (_form!.fieldAutoNaming && skipField)) { + _addedFieldNames.add(field.name); + } else if (count > 0 && !isLoaded) { + for (int i = 0; i < count; i++) { + if (_list[i] is PdfField) { + final PdfField oldField = _list[i] as PdfField; + if (oldField._name == field._name) { + if ((field is PdfTextBoxField && oldField is PdfTextBoxField) || + (field is PdfCheckBoxField && + oldField is PdfCheckBoxField)) { + final _PdfDictionary dic = field._widget!._dictionary; + dic.remove(_DictionaryProperties.parent); + field._widget!.parent = oldField; + if (field._page != null) { + field._page!.annotations.add(field._widget!); + } + bool isPresent = false; + for (int i = 0; i < oldField._array.count; i++) { + final _IPdfPrimitive? obj = oldField._array._elements[i]; + if (obj != null && + obj is _PdfReferenceHolder && + obj.object != null && + obj.object is _PdfDictionary && + obj.object == oldField._widget!._dictionary) { + isPresent = true; + break; + } + } + if (!isPresent) { + oldField._array._add(_PdfReferenceHolder(oldField._widget)); + oldField._fieldItems ??= []; + oldField._fieldItems!.add(oldField); + } + oldField._array._add(_PdfReferenceHolder(field._widget)); + oldField._fieldItems ??= []; + oldField._fieldItems!.add(field); + oldField._dictionary + .setProperty(_DictionaryProperties.kids, oldField._array); + return count - 1; + } else if (field is PdfSignatureField) { + final PdfSignatureField currentField = field; + final _PdfDictionary dictionary = + currentField._widget!._dictionary; + if (dictionary.containsKey(_DictionaryProperties.parent)) { + dictionary.remove(_DictionaryProperties.parent); + } + currentField._widget!.parent = oldField; + _IPdfPrimitive? oldKids; + _IPdfPrimitive? newKids; + if (oldField._dictionary + .containsKey(_DictionaryProperties.kids)) { + oldKids = oldField._dictionary + ._items![_PdfName(_DictionaryProperties.kids)]; + } + if (field._dictionary + .containsKey(_DictionaryProperties.kids)) { + newKids = field._dictionary + ._items![_PdfName(_DictionaryProperties.kids)]; + } + if (newKids != null && newKids is _PdfArray) { + if (oldKids == null || !(oldKids is _PdfArray)) { + oldKids = _PdfArray(); + } + for (int i = 0; i < newKids.count; i++) { + final _IPdfPrimitive? kidsReference = newKids[i]; + if (kidsReference != null && + kidsReference is _PdfReferenceHolder) { + oldKids._add(kidsReference); + } + } + } + oldField._dictionary + .setProperty(_DictionaryProperties.kids, oldKids); + currentField._skipKidsCertificate = true; + if (!field.page!.annotations + .contains(currentField._widget!)) { + field.page!.annotations.add(currentField._widget!); + } + return count - 1; + } + } + } + } + } + } + } + if (isLoaded && !_addedFieldNames.contains(field.name)) { + _addedFieldNames.add(field.name); + } + if (!(field is PdfRadioButtonListField) && field._page != null) { + field._page!.annotations.add(field._widget!); + } + _array._add(_PdfReferenceHolder(field)); + _list.add(field); + field._annotationIndex = _list.length - 1; + return _list.length - 1; + } + + void _doRemove(PdfField field) { + if (field._isLoadedField || + (field._form != null && field._form!._isLoadedForm)) { + _removeFromDictionary(field); + } + field._setForm(null); + final int index = _list.indexOf(field); + _array._removeAt(index); + _list.removeAt(index); + } + + void _doRemoveAt(int index) { + if (_list[index] is PdfField && (_list[index] as PdfField)._isLoadedField) { + _removeFromDictionary(_list[index] as PdfField); + } + _array._removeAt(index); + _list.removeAt(index); + } + + void _doClear() { + if (count > 0) { + for (int i = 0; i < count; i++) { + if (_list[i] is PdfField) { + final PdfField field = _list[i] as PdfField; + if (field._isLoadedField) { + _removeFromDictionary(field); + } else { + _form!._deleteFromPages(field); + _form!._deleteAnnotation(field); + field._page = null; + if (field._dictionary._items != null) { + field._dictionary.clear(); + } + field._setForm(null); + } + } + } + } + _addedFieldNames.clear(); + _form!._terminalFields.clear(); + _array._clear(); + _list.clear(); + } + + void _createFormFieldsFromWidgets(int startFormFieldIndex) { + for (int i = startFormFieldIndex; i < _form!._terminalFields.length; ++i) { + final PdfField? field = _getField(index: i); + if (field != null) { + _doAdd(field); + } + } + if (_form!._widgetDictionary != null && + _form!._widgetDictionary!.isNotEmpty) { + for (final dictValue in _form!._widgetDictionary!.values) { + if (dictValue.isNotEmpty) { + final PdfField? field = _getField(dictionary: dictValue[0])!; + if (field != null) { + _form!._terminalFields.add(field._dictionary); + _doAdd(field); + } + } + } + } + } + + // Gets the field. + PdfField? _getField({int? index, _PdfDictionary? dictionary}) { + index != null + ? dictionary = _form!._terminalFields[index] + : ArgumentError.checkNotNull( + dictionary, 'method cannot be initialized without parameters'); + final _PdfCrossTable? crossTable = _form!._crossTable; + PdfField? field; + final _PdfName? name = PdfField._getValue( + dictionary!, crossTable, _DictionaryProperties.ft, true) as _PdfName?; + _PdfFieldTypes type = _PdfFieldTypes.none; + if (name != null) { + type = _getFieldType(name, dictionary, crossTable); + } + switch (type) { + case _PdfFieldTypes.comboBox: + field = _createComboBox(dictionary, crossTable!); + break; + case _PdfFieldTypes.listBox: + field = _createListBox(dictionary, crossTable!); + break; + case _PdfFieldTypes.textField: + field = _createTextField(dictionary, crossTable!); + break; + case _PdfFieldTypes.checkBox: + field = _createCheckBox(dictionary, crossTable!); + break; + case _PdfFieldTypes.radioButton: + field = _createRadioButton(dictionary, crossTable!); + break; + case _PdfFieldTypes.pushButton: + field = _createPushButton(dictionary, crossTable!); + break; + case _PdfFieldTypes.signatureField: + field = _createSignatureField(dictionary, crossTable!); + break; + default: + break; + } + if (field != null) { + field._setForm(_form); + field._beforeNameChanges = (String name) { + if (_addedFieldNames.contains(name)) { + throw ArgumentError('Field with the same name already exist'); + } + }; + } + return field; + } + + //Gets the type of the field. + _PdfFieldTypes _getFieldType( + _PdfName name, _PdfDictionary dictionary, _PdfCrossTable? crossTable) { + final String str = name._name!; + _PdfFieldTypes type = _PdfFieldTypes.none; + final _PdfNumber? number = PdfField._getValue( + dictionary, crossTable, _DictionaryProperties.fieldFlags, true) + as _PdfNumber?; + int fieldFlags = 0; + if (number != null) { + fieldFlags = number.value!.toInt(); + } + switch (str.toLowerCase()) { + case 'ch': + //check with _FieldFlags.combo value. + if ((fieldFlags & 1 << 17) != 0) { + type = _PdfFieldTypes.comboBox; + } else { + type = _PdfFieldTypes.listBox; + } + break; + case 'tx': + type = _PdfFieldTypes.textField; + break; + case 'btn': + if ((fieldFlags & 1 << 15) != 0) { + type = _PdfFieldTypes.radioButton; + } else if ((fieldFlags & 1 << 16) != 0) { + type = _PdfFieldTypes.pushButton; + } else { + type = _PdfFieldTypes.checkBox; + } + break; + case 'sig': + type = _PdfFieldTypes.signatureField; + break; + } + return type; + } + + //Creates the combo box. + PdfField _createComboBox( + _PdfDictionary dictionary, _PdfCrossTable crossTable) { + final PdfField field = PdfComboBoxField._load(dictionary, crossTable); + field._setForm(_form); + return field; + } + + //Creates the list box. + PdfField _createListBox( + _PdfDictionary dictionary, _PdfCrossTable crossTable) { + final PdfField field = PdfListBoxField._load(dictionary, crossTable); + field._setForm(_form); + return field; + } + + //Creates the text field. + PdfField _createTextField( + _PdfDictionary dictionary, _PdfCrossTable crossTable) { + final PdfField field = PdfTextBoxField._load(dictionary, crossTable); + field._setForm(_form); + return field; + } + + PdfField _createCheckBox( + _PdfDictionary dictionary, _PdfCrossTable crossTable) { + final PdfField field = PdfCheckBoxField._loaded(dictionary, crossTable); + field._setForm(_form); + return field; + } + + PdfField _createRadioButton( + _PdfDictionary dictionary, _PdfCrossTable crossTable) { + final PdfField field = + PdfRadioButtonListField._loaded(dictionary, crossTable); + field._setForm(_form); + return field; + } + + PdfField _createPushButton( + _PdfDictionary dictionary, _PdfCrossTable crossTable) { + final PdfField field = PdfButtonField._loaded(dictionary, crossTable); + field._setForm(_form); + return field; + } + + PdfField _createSignatureField( + _PdfDictionary dictionary, _PdfCrossTable crossTable) { + final PdfField field = PdfSignatureField._(dictionary, crossTable); + field._setForm(_form); + return field; + } + + /// Gets the new name of the field. + String? _getCorrectName(String? name) { + final List list = []; + for (int i = 0; i < count; i++) { + if (_list[i] is PdfField) { + list.add((_list[i] as PdfField).name); + } + } + String? correctName = name; + int index = 0; + while (list.indexOf(correctName) != -1) { + correctName = name! + index.toString(); + ++index; + } + list.clear(); + return correctName; + } + + void _removeFromDictionary(PdfField field) { + if (field._isLoadedField) { + _form!._removeFromDictionaries(field); + } + _addedFieldNames.remove(field.name); + } + + //Overrides + @override + _IPdfPrimitive get _element => _array; + + @override + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + throw ArgumentError('Primitive element can\'t be set'); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_box_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_box_field.dart new file mode 100644 index 000000000..bda06e8db --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_box_field.dart @@ -0,0 +1,239 @@ +part of pdf; + +/// Represents list box field of the PDF form. +class PdfListBoxField extends PdfListField { + //Constructor + /// Initializes a new instance of the [PdfListBoxField] class with the specific page and name. + PdfListBoxField(PdfPage page, String name, Rect bounds, + {List? items, + bool multiSelect = false, + List? selectedIndexes, + List? selectedValues, + PdfFont? font, + PdfTextAlignment alignment = PdfTextAlignment.left, + PdfColor? borderColor, + PdfColor? foreColor, + PdfColor? backColor, + int? borderWidth, + PdfHighlightMode highlightMode = PdfHighlightMode.invert, + PdfBorderStyle borderStyle = PdfBorderStyle.solid, + String? tooltip}) + : super._(page, name, bounds, + font: font, + alignment: alignment, + items: items, + borderColor: borderColor, + foreColor: foreColor, + backColor: backColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip) { + this.multiSelect = multiSelect; + if (selectedIndexes != null) { + this.selectedIndexes = selectedIndexes; + } + if (selectedValues != null) { + this.selectedValues = selectedValues; + } + } + + /// Initializes a new instance of the [PdfListBoxField] class. + PdfListBoxField._load(_PdfDictionary dictionary, _PdfCrossTable crossTable) + : super._load(dictionary, crossTable); + + //Fields + bool _multiSelect = false; + + //Properties + /// Gets or sets a value indicating whether the field is multi-selectable. + /// + /// The default value is false. + bool get multiSelect { + if (_isLoadedField) { + _multiSelect = _isFlagPresent(_FieldFlags.multiSelect) || + _flags.contains(_FieldFlags.multiSelect); + } + return _multiSelect; + } + + set multiSelect(bool value) { + if (_multiSelect != value || _isLoadedField) { + _multiSelect = value; + _multiSelect + ? _flags.add(_FieldFlags.multiSelect) + : _isLoadedField + ? _removeFlag(_FieldFlags.multiSelect) + : _flags.remove(_FieldFlags.multiSelect); + } + } + + /// Gets or sets selected indexes in the list. + /// + /// Multiple indexes will be selected only when multiSelect property is enabled, + /// Otherwise only the first index in the collection will be selected. + List get selectedIndexes => _selectedIndexes; + set selectedIndexes(List value) { + if (value.isNotEmpty) { + _selectedIndexes = multiSelect ? value : [value[0]]; + } + } + + /// Gets or sets the selected values in the list. + /// + /// Multiple values will be selected only when multiSelect property is enabled, + /// Otherwise only the first value in the collection will be selected. + List get selectedValues => _selectedValues; + set selectedValues(List value) { + if (value.isNotEmpty) { + _selectedValues = multiSelect ? value : [value[0]]; + } + } + + /// Gets the selected items in the list. + PdfListFieldItemCollection get selectedItems => _selectedItems; + + //Implementation. + @override + void _drawAppearance(PdfTemplate template) { + super._drawAppearance(template); + final _PaintParams params = _PaintParams( + bounds: Rect.fromLTWH(0, 0, bounds.width, bounds.height), + backBrush: _backBrush, + foreBrush: _foreBrush, + borderPen: _borderPen, + style: borderStyle, + borderWidth: borderWidth, + shadowBrush: _shadowBrush); + PdfFont font; + if (this.font == null) { + if (_page!._document != null && + _page!._document!._conformanceLevel != PdfConformanceLevel.none) { + throw ArgumentError( + 'Font data is not embedded to the conformance PDF.'); + } + font = PdfStandardFont( + PdfFontFamily.timesRoman, _getFontHeight(PdfFontFamily.timesRoman)); + } else { + font = this.font!; + } + _FieldPainter().drawListBox( + template.graphics!, params, items, _selectedIndexes, font, _format); + } + + void _draw() { + super._draw(); + if (!_isLoadedField) { + if (_widget!._pdfAppearance != null) { + page!.graphics + .drawPdfTemplate(_widget!.appearance.normal, bounds.topLeft); + } else { + final Rect rect = Rect.fromLTWH(0, 0, bounds.width, bounds.height); + final PdfFont font = this.font ?? + PdfStandardFont(PdfFontFamily.helvetica, + _getFontHeight(PdfFontFamily.helvetica)); + final _PaintParams parameters = _PaintParams( + bounds: rect, + backBrush: _backBrush, + foreBrush: _foreBrush, + borderPen: _borderPen, + style: borderStyle, + borderWidth: borderWidth, + shadowBrush: _shadowBrush); + final PdfTemplate template = PdfTemplate(rect.width, rect.height); + _FieldPainter().drawListBox(template.graphics!, parameters, items, + selectedIndexes, font, _format); + page!.graphics.drawPdfTemplate(template, bounds.topLeft, rect.size); + } + } else { + final PdfGraphics graphics = page!.graphics; + final PdfTemplate template = PdfTemplate(bounds.width, bounds.height); + if (_flattenField && graphics._page != null) { + graphics.save(); + final double width = page!.graphics.size.width; + final double height = page!.graphics.size.height; + if (graphics._page!._rotation == PdfPageRotateAngle.rotateAngle90) { + page!.graphics.translateTransform(width, height); + page!.graphics.rotateTransform(90); + } else if (graphics._page!._rotation == + PdfPageRotateAngle.rotateAngle180) { + page!.graphics.translateTransform(width, height); + page!.graphics.rotateTransform(-180); + } else if (graphics._page!._rotation == + PdfPageRotateAngle.rotateAngle270) { + page!.graphics.translateTransform(width, height); + page!.graphics.rotateTransform(270); + } + } + _drawListBox(template.graphics!); + page!.graphics.drawPdfTemplate(template, bounds.topLeft); + graphics.restore(); + } + } + + void _beginSave() { + super._beginSave(); + _applyAppearance(_getWidgetAnnotation(_dictionary, _crossTable)); + } + + void _applyAppearance(_PdfDictionary widget) { + if (widget.containsKey(_DictionaryProperties.ap) && + !_form!._needAppearances!) { + final _IPdfPrimitive? appearance = + _crossTable!._getObject(widget[_DictionaryProperties.ap]); + if (appearance != null && + appearance is _PdfDictionary && + appearance.containsKey(_DictionaryProperties.n)) { + final PdfTemplate template = PdfTemplate(bounds.width, bounds.height); + template._writeTransformation = false; + _beginMarkupSequence(template.graphics!._streamWriter!._stream!); + template.graphics!._initializeCoordinates(); + _drawListBox(template.graphics!); + _endMarkupSequence(template.graphics!._streamWriter!._stream!); + appearance.remove(_DictionaryProperties.n); + appearance.setProperty( + _DictionaryProperties.n, _PdfReferenceHolder(template)); + widget.setProperty(_DictionaryProperties.ap, appearance); + } + } else if (_form!._setAppearanceDictionary && !_form!._needAppearances!) { + final _PdfDictionary dic = _PdfDictionary(); + final PdfTemplate template = PdfTemplate(bounds.width, bounds.height); + _drawAppearance(template); + dic.setProperty(_DictionaryProperties.n, _PdfReferenceHolder(template)); + widget.setProperty(_DictionaryProperties.ap, dic); + } + } + + void _drawListBox(PdfGraphics graphics) { + final _GraphicsProperties gp = _GraphicsProperties(this); + gp._bounds = Rect.fromLTWH(0, 0, bounds.width, bounds.height); + final _PaintParams prms = _PaintParams( + bounds: gp._bounds, + backBrush: gp._backBrush, + foreBrush: gp._foreBrush, + borderPen: gp._borderPen, + style: gp._style, + borderWidth: gp._borderWidth, + shadowBrush: gp._shadowBrush); + if (!_form!._setAppearanceDictionary && !_form!._flatten) { + prms._backBrush = null; + } + _FieldPainter().drawListBox( + graphics, prms, items, _selectedIndexes, gp._font!, gp._stringFormat); + } + + double _getFontHeight(PdfFontFamily family) { + double s = 0; + if (items.count > 0) { + final PdfFont font = PdfStandardFont(family, 12); + double max = font.measureString(items[0].text).width; + for (int i = 1; i < items.count; ++i) { + final double temp = font.measureString(items[i].text).width; + max = (max > temp) ? max : temp; + } + s = ((12 * (bounds.size.width - 4 * borderWidth)) / max); + s = (s > 12) ? 12 : s; + } + return s; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field.dart new file mode 100644 index 000000000..cd9585a07 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field.dart @@ -0,0 +1,358 @@ +part of pdf; + +/// Represents base class for form's list fields. +abstract class PdfListField extends PdfField { + //Constructor + PdfListField._(PdfPage? page, String name, Rect bounds, + {List? items, + PdfFont? font, + PdfTextAlignment? alignment, + PdfColor? borderColor, + PdfColor? foreColor, + PdfColor? backColor, + int? borderWidth, + PdfHighlightMode? highlightMode, + PdfBorderStyle? borderStyle, + String? tooltip}) + : super(page, name, bounds, + font: font, + alignment: alignment, + borderColor: borderColor, + foreColor: foreColor, + backColor: backColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip) { + if (items != null && items.isNotEmpty) { + items.forEach((element) => this.items.add(element)); + } + } + + PdfListField._load(_PdfDictionary dictionary, _PdfCrossTable crossTable) + : super._load(dictionary, crossTable); + + //Fields + PdfListFieldItemCollection? _items; + List _selectedIndex = [-1]; + + //Properties + /// Gets the list field items. + PdfListFieldItemCollection get items { + if (_items == null) { + if (!_isLoadedField) { + _items = PdfListFieldItemCollection._(); + _dictionary.setProperty(_DictionaryProperties.opt, _items); + } else { + _items = _getListItemCollection(); + } + } + return _items!; + } + + List get _selectedIndexes => + _isLoadedField ? _obtainSelectedIndex() : _selectedIndex; + set _selectedIndexes(List value) { + value.forEach((element) { + if (element < 0 || element >= items.count) { + throw RangeError('index'); + } + }); + if (_isLoadedField) { + _assignSelectedIndex(value); + } else { + if (_selectedIndex != value && _selectedIndex.isNotEmpty) { + _selectedIndex = value; + _dictionary.setProperty( + _DictionaryProperties.i, _PdfArray(_selectedIndex)); + } + } + } + + List get _selectedValues { + if (_isLoadedField) { + return _obtainSelectedValue(); + } else { + if (_selectedIndex == [-1]) { + throw ArgumentError('No value is selected.'); + } + final List values = []; + _selectedIndex.forEach((element) => values.add(_items![element].value)); + return values; + } + } + + set _selectedValues(List value) { + if (value.isEmpty) { + throw ArgumentError('selected value can\'t be null/Empty'); + } + if (_isLoadedField) { + bool isText = false; + if (items[0].value.isEmpty) { + isText = true; + } + _assignSelectedValue(value, isText); + } else { + for (int i = 0; i < _items!.count; i++) { + if (value.contains(_items![i].value)) { + _selectedIndex.add(i); + if (_selectedIndex.contains(-1)) { + _selectedIndex.remove(-1); + } + break; + } + } + _dictionary.setProperty( + _DictionaryProperties.i, _PdfArray(_selectedIndex)); + } + } + + PdfListFieldItemCollection get _selectedItems { + if (_selectedIndex == [-1]) { + throw ArgumentError('No item is selected.'); + } + final PdfListFieldItemCollection item = + PdfListFieldItemCollection._(_isLoadedField ? this : null); + for (final index in _selectedIndexes) { + if (index > -1 && items.count > 0 && items.count > index) { + item._addItem(items[index]); + } + } + return item; + } + + /// Gets or sets the font. + PdfFont? get font => _font; + set font(PdfFont? value) { + if (value != null) { + _font = value; + } + } + + /// Gets or sets the text alignment. + /// + /// The default alignment is left. + PdfTextAlignment get textAlignment => _textAlignment; + set textAlignment(PdfTextAlignment value) => _textAlignment = value; + + /// Gets or sets the color of the border. + /// + /// The default color is black. + PdfColor get borderColor => _borderColor; + set borderColor(PdfColor value) => _borderColor = value; + + /// Gets or sets the color of the background. + /// + /// The default color is empty. + PdfColor get backColor => _backColor; + set backColor(PdfColor value) => _backColor = value; + + /// Gets or sets the color of the text. + /// + /// The default color is black. + PdfColor get foreColor => _foreColor; + set foreColor(PdfColor value) => _foreColor = value; + + /// Gets or sets the width of the border. + /// + /// The default value is 1. + int get borderWidth => _borderWidth; + set borderWidth(int value) => _borderWidth = value; + + /// Gets or sets the highlighting mode. + /// + /// The default mode is invert. + PdfHighlightMode get highlightMode => _highlightMode; + set highlightMode(PdfHighlightMode value) => _highlightMode = value; + + /// Gets or sets the border style. + /// + /// The default style is solid. + PdfBorderStyle get borderStyle => _borderStyle; + set borderStyle(PdfBorderStyle value) => _borderStyle = value; + + //Implementations + @override + void _initialize() { + super._initialize(); + _dictionary.setProperty( + _DictionaryProperties.ft, _PdfName(_DictionaryProperties.ch)); + } + + // Gets the list item. + PdfListFieldItemCollection _getListItemCollection() { + final PdfListFieldItemCollection items = PdfListFieldItemCollection._(this); + final _IPdfPrimitive? array = PdfField._getValue( + _dictionary, _crossTable, _DictionaryProperties.opt, true); + if (array != null && array is _PdfArray) { + for (int i = 0; i < array.count; i++) { + final _IPdfPrimitive? primitive = _crossTable!._getObject(array[i]); + PdfListFieldItem item; + if (primitive is _PdfString) { + final _PdfString str = primitive; + item = PdfListFieldItem._load(str.value, null, this, _crossTable); + } else { + final _PdfArray arr = primitive as _PdfArray; + final _PdfString value = + _crossTable!._getObject(arr[0]) as _PdfString; + final _PdfString text = _crossTable!._getObject(arr[1]) as _PdfString; + item = PdfListFieldItem._load( + text.value, value.value, this, _crossTable); + } + items._addItem(item); + } + } + return items; + } + + List _obtainSelectedIndex() { + final List selectedIndex = []; + if (_dictionary.containsKey(_DictionaryProperties.i)) { + final _IPdfPrimitive? array = + _crossTable!._getObject(_dictionary[_DictionaryProperties.i]); + if (array != null && array is _PdfArray) { + if (array.count > 0) { + for (int i = 0; i < array.count; i++) { + final _IPdfPrimitive? number = _crossTable!._getObject(array[i]); + if (number != null && number is _PdfNumber) { + selectedIndex.add(number.value!.toInt()); + } + } + } + } else { + final _IPdfPrimitive? number = + _crossTable!._getObject(_dictionary[_DictionaryProperties.i]); + if (number != null && number is _PdfNumber) { + selectedIndex.add(number.value!.toInt()); + } + } + } + if (selectedIndex.length == 0) { + selectedIndex.add(-1); + } + return selectedIndex; + } + + //Gets selected value. + List _obtainSelectedValue() { + final List value = []; + if (_dictionary.containsKey(_DictionaryProperties.v)) { + final _IPdfPrimitive? primitive = + _crossTable!._getObject(_dictionary[_DictionaryProperties.v]); + if (primitive is _PdfString) { + value.add(primitive.value!); + } else { + final _PdfArray array = primitive as _PdfArray; + for (int i = 0; i < array.count; i++) { + final _PdfString stringValue = array[i] as _PdfString; + value.add(stringValue.value!); + } + } + } else { + for (final index in _selectedIndexes) { + if (index > -1) { + value.add(items[index].value); + } + } + } + return value; + } + + void _assignSelectedIndex(List value) { + if ((value.length == 0) || (value.length > items.count)) { + throw RangeError('selectedIndex'); + } + value.forEach((element) { + if ((element >= items.count)) { + throw RangeError('selectedIndex'); + } + }); + if (readOnly == false) { + value.sort(); + _dictionary.setProperty(_DictionaryProperties.i, _PdfArray(value)); + List selectedValues = []; + bool isText = false; + value.forEach((element) { + if (element >= 0) { + selectedValues.add(items[element].value); + } + }); + if (items[0].value.isEmpty) { + selectedValues = []; + isText = true; + value.forEach((element) { + selectedValues.add(items[element].text); + }); + } + if (selectedValues.length > 0) { + _assignSelectedValue(selectedValues, isText); + } + _changed = true; + } + } + + void _assignSelectedValue(List values, bool isText) { + final List selectedIndexes = []; + final PdfListFieldItemCollection? collection = items; + if (readOnly == false) { + values.forEach((element) { + bool isvaluePresent = false; + for (int i = 0; i < collection!.count; i++) { + if ((isText ? collection[i].text : collection[i].value) == element) { + isvaluePresent = true; + selectedIndexes.add(i); + } + } + if (!isvaluePresent && + (this is PdfComboBoxField) && + !(this as PdfComboBoxField).editable) { + throw new RangeError('index'); + } + }); + if (this is PdfListBoxField && values.length > 1) { + final PdfListBoxField listfield = this as PdfListBoxField; + if (!listfield.multiSelect) { + selectedIndexes.removeRange(1, selectedIndexes.length - 1); + values = [collection![selectedIndexes[0]].value]; + } + } + if (selectedIndexes.length != 0) { + selectedIndexes.sort(); + _dictionary.setProperty( + _DictionaryProperties.i, _PdfArray(selectedIndexes)); + } else + _dictionary.remove(_DictionaryProperties.i); + } + if (_dictionary.containsKey(_DictionaryProperties.v)) { + final _IPdfPrimitive? primitive = + _crossTable!._getObject(_dictionary[_DictionaryProperties.v]); + if ((primitive == null) || (primitive is _PdfString)) { + if (this is PdfListBoxField) { + final _PdfArray array = _PdfArray(); + for (final selectedValue in values) { + array._add(_PdfString(selectedValue!)); + } + _dictionary.setProperty(_DictionaryProperties.v, array); + } else { + _dictionary._setString(_DictionaryProperties.v, values[0]); + } + } else { + final _PdfArray array = primitive as _PdfArray; + array._clear(); + for (final selectedValue in values) { + array._add(_PdfString(selectedValue!)); + } + _dictionary.setProperty(_DictionaryProperties.v, array); + } + } else if (this is PdfComboBoxField) { + _dictionary._setString(_DictionaryProperties.v, values[0]); + } else { + final _PdfArray array = _PdfArray(); + for (final selectedValue in values) { + array._add(_PdfString(selectedValue!)); + } + _dictionary.setProperty(_DictionaryProperties.v, array); + } + _changed = true; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field_item.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field_item.dart new file mode 100644 index 000000000..4e2280c01 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field_item.dart @@ -0,0 +1,95 @@ +part of pdf; + +/// Represents an item of the list fields. +class PdfListFieldItem implements _IPdfWrapper { + //Constructor + /// Initializes a new instance of the [PdfListFieldItem] class. + PdfListFieldItem(String text, String value) : super() { + _initialize(text, value); + } + + /// Initializes a new instance of the [PdfListFieldItem] class. + PdfListFieldItem._load( + String? text, String? value, PdfListField field, _PdfCrossTable? cTable) { + _field = field; + _crossTable = cTable; + _text = text == null ? '' : text; + _value = value == null ? '' : value; + } + + //Fields + String? _text; + String? _value; + final _PdfArray _array = _PdfArray(); + PdfListField? _field; + _PdfCrossTable? _crossTable; + + //Properties + /// Gets or sets the text. + String get text => _text!; + set text(String value) { + if (_text != value) { + if (_field != null && _field!._isLoadedField) { + _assignValues(value, true); + } else { + //text index: 1. + _text = (_array[1] as _PdfString).value = value; + } + } + } + + /// Gets or sets the value. + String get value => _value!; + set value(String value) { + if (_value != value) { + if (_field != null && _field!._isLoadedField) { + _assignValues(value, false); + } else { + //value index: 0. + _value = (_array[0] as _PdfString).value = value; + } + } + } + + //Implementation + void _initialize(String text, String value) { + _array._add(_PdfString(value)); + _array._add(_PdfString(text)); + _value = value; + _text = text; + } + + //Sets the text of the item. + void _assignValues(String value, bool isText) { + final _PdfDictionary fieldDic = _field!._dictionary; + if (fieldDic.containsKey(_DictionaryProperties.opt)) { + final _PdfArray array = _crossTable! + ._getObject(fieldDic[_DictionaryProperties.opt]) as _PdfArray; + final _PdfArray item = (isText + ? (_PdfArray().._add(_PdfString(_value!)).._add(_PdfString(value))) + : (_PdfArray().._add(_PdfString(value)).._add(_PdfString(_text!)))); + for (int i = 0; i < array.count; ++i) { + final _IPdfPrimitive primitive = _crossTable!._getObject(array[i])!; + final _PdfArray arr = primitive as _PdfArray; + final _PdfString text = _crossTable!._getObject(arr[1]) as _PdfString; + if (text.value == _text || text.value == _value) { + isText ? _text = value : _value = value; + array._removeAt(i); + array._insert(i, item); + } + } + fieldDic.setProperty(_DictionaryProperties.opt, array); + _field!._changed = true; + } + } + + //overrides + @override + _IPdfPrimitive get _element => _array; + + @override + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + throw ArgumentError('Primitive element can\'t be set'); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field_item_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field_item_collection.dart new file mode 100644 index 000000000..65aa72d54 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field_item_collection.dart @@ -0,0 +1,152 @@ +part of pdf; + +/// Represents list field item collection. +class PdfListFieldItemCollection extends PdfObjectCollection + implements _IPdfWrapper { + //Constructor + PdfListFieldItemCollection._([PdfListField? field]) : super() { + if (field != null) { + _field = field; + } + } + + //Fields + final _PdfArray _items = _PdfArray(); + PdfListField? _field; + + //Properties + /// Gets the [PdfListFieldItem] at the specified index. + PdfListFieldItem operator [](int index) { + if (index < 0 || index >= count) { + throw RangeError('index'); + } + return _list[index] as PdfListFieldItem; + } + + //Public methods + /// Adds the specified item in the collection and returns its index. + int add(PdfListFieldItem item) { + return _doAdd(item); + } + + /// Inserts the list item field at the specified index. + void insert(int index, PdfListFieldItem item) { + if (index < 0 || index > count) { + throw RangeError('index'); + } + _doInsert(index, item); + } + + /// Removes the specified [PdfListFieldItem]. + void remove(PdfListFieldItem item) { + if (_list.contains(item)) { + _doRemove(item); + } + } + + /// Removes the item at the specified position. + void removeAt(int index) { + if ((index < 0) || (index >= count)) { + throw RangeError('index'); + } + _doRemove(null, index); + } + + /// Determines whether the item is present in the collection. + bool contains(PdfListFieldItem item) { + return _list.contains(item); + } + + /// Gets the index of the specified item. + int indexOf(PdfListFieldItem item) { + return _list.indexOf(item); + } + + /// Clears the collection. + void clear() { + if (_field != null && _field!._isLoadedField) { + final _PdfArray list = _getItems().._clear(); + _field!._dictionary.setProperty(_DictionaryProperties.opt, list); + } else { + _items._clear(); + } + _list.clear(); + } + + //Implementations + int _doAdd(PdfListFieldItem item) { + if (_field != null && _field!._isLoadedField) { + final _PdfArray list = _getItems(); + final _PdfArray itemArray = _getArray(item); + list._add(itemArray); + _field!._dictionary.setProperty(_DictionaryProperties.opt, list); + } else { + _items._add(item._element); + } + _list.add(item); + return count - 1; + } + + void _doInsert(int index, PdfListFieldItem item) { + if (_field != null && _field!._isLoadedField) { + final _PdfArray list = _getItems(); + final _PdfArray itemArray = _getArray(item); + list._insert(index, itemArray); + _field!._dictionary.setProperty(_DictionaryProperties.opt, list); + } else { + _items._insert(index, item._element); + } + _list.insert(index, item); + } + + void _doRemove(PdfListFieldItem? item, [int? index]) { + if (index == null && item != null) { + index = _list.indexOf(item); + } + if (_field != null && _field!._isLoadedField) { + final _PdfArray list = _getItems().._removeAt(index!); + _field!._dictionary.setProperty(_DictionaryProperties.opt, list); + } else { + _items._removeAt(index!); + } + _list.removeAt(index); + } + + _PdfArray _getItems() { + _PdfArray? items; + if (_field!._dictionary.containsKey(_DictionaryProperties.opt)) { + final _IPdfPrimitive? obj = _field!._crossTable! + ._getObject(_field!._dictionary[_DictionaryProperties.opt]); + if (obj != null && obj is _PdfArray) { + items = obj; + } + } + return items ?? _PdfArray(); + } + + _PdfArray _getArray(PdfListFieldItem item) { + final _PdfArray array = _PdfArray(); + if (item.value != '') { + array._add(_PdfString(item.value)); + } + if (item.value != '') { + array._add(_PdfString(item.text)); + } + return array; + } + + int _addItem(PdfListFieldItem item) { + _list.add(item); + return count - 1; + } + + //overrides + @override + _IPdfPrimitive get _element => _items; + + @override + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + throw ArgumentError('Primitive element can\'t be set'); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_radio_button_item_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_radio_button_item_collection.dart new file mode 100644 index 000000000..c520b9f7c --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_radio_button_item_collection.dart @@ -0,0 +1,104 @@ +part of pdf; + +/// Represents an item of a radio button list. +class PdfRadioButtonItemCollection extends PdfObjectCollection + implements _IPdfWrapper { + //Constructor + /// Initializes a new instance of the [PdfRadioButtonItemCollection] + /// class with the specific [PdfRadioButtonListField]. + PdfRadioButtonItemCollection._(PdfRadioButtonListField field) { + _field = field; + } + //Fields + PdfRadioButtonListField? _field; + final _PdfArray _array = _PdfArray(); + + //Properties + /// Gets the PdfRadioButtonListItem at the specified index. + PdfRadioButtonListItem operator [](int index) => + _list[index] as PdfRadioButtonListItem; + + //Implementation + /// Adds the specified item. + int add(PdfRadioButtonListItem item) { + return _doAdd(item); + } + + /// Inserts an item at the specified index. + void insert(int index, PdfRadioButtonListItem item) { + _doInsert(index, item); + } + + /// Removes the specified item from the collection. + void remove(PdfRadioButtonListItem item) { + _doRemove(item); + } + + /// Removes the item at the specified index. + void removeAt(int index) { + RangeError.range(index, 0, _list.length); + _array._removeAt(index); + final PdfRadioButtonListItem item = _list[index] as PdfRadioButtonListItem; + item._setField(null); + _list.removeAt(index); + if ((_field != null && _field!._selectedIndex >= index) || + (_list.isEmpty)) { + _field?._selectedIndex = -1; + } + } + + /// Gets the index of the item within the collection. + int indexOf(PdfRadioButtonListItem item) => _list.indexOf(item); + + /// Determines whether the collection contains the specified item. + bool contains(PdfRadioButtonListItem item) => _list.contains(item); + + /// Clears the item collection. + void clear() => _doClear(); + + int _doAdd(PdfRadioButtonListItem item, [bool isItem = false]) { + _array._add(_PdfReferenceHolder(item)); + item._setField(_field, isItem); + _list.add(item); + return _list.length - 1; + } + + void _doInsert(int index, PdfRadioButtonListItem item) { + _array._insert(index, _PdfReferenceHolder(item)); + item._setField(_field); + _list.insert(index, item); + } + + void _doRemove(PdfRadioButtonListItem item) { + if (_list.contains(item)) { + final int index = _list.indexOf(item); + _array._removeAt(index); + item._setField(null); + _list.removeAt(index); + if ((_field != null && _field!._selectedIndex >= index) || + (_list.isEmpty)) { + _field?._selectedIndex = -1; + } + } + } + + void _doClear() { + for (final Object? item in _list) { + if (item is PdfRadioButtonListItem) { + item._setField(null); + } + } + _array._clear(); + _list.clear(); + _field?._selectedIndex = -1; + } + + @override + _IPdfPrimitive get _element => _array; + + @override + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + _element = value; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_radio_button_list_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_radio_button_list_field.dart new file mode 100644 index 000000000..53b5e44e6 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_radio_button_list_field.dart @@ -0,0 +1,269 @@ +part of pdf; + +/// Represents radio button field in the PDF form. +class PdfRadioButtonListField extends PdfField { + //Constructor + /// Initializes a new instance of the [PdfRadioButtonListField] class with + /// the specific page, name and bounds. + PdfRadioButtonListField(PdfPage page, String name, + {List? items, + int? selectedIndex, + String? selectedValue}) + : super(page, name, Rect.zero) { + _initValues(items, selectedIndex, selectedValue); + _flags.add(_FieldFlags.radio); + _dictionary.setProperty( + _DictionaryProperties.ft, _PdfName(_DictionaryProperties.btn)); + } + + PdfRadioButtonListField._loaded( + _PdfDictionary dictionary, _PdfCrossTable crossTable) + : super._load(dictionary, crossTable); + + //Fields + PdfRadioButtonItemCollection? _items; + int _selectedIndex = -1; + + //Properties + /// Gets the items of the radio button field.{Read-Only} + PdfRadioButtonItemCollection get items { + if (_isLoadedField) { + if (_items == null) { + _items = _getRadioButtonListItems(PdfRadioButtonItemCollection._(this)); + } + return _items!; + } else { + if (_items == null) { + _items = PdfRadioButtonItemCollection._(this); + _dictionary.setProperty(_DictionaryProperties.kids, _items); + } + return _items!; + } + } + + /// Gets or sets the first selected item in the list. + int get selectedIndex { + if (_isLoadedField && _selectedIndex == -1) { + _selectedIndex = _obtainSelectedIndex(); + } + if (_selectedIndex == -1) { + ArgumentError.value('None of the item to be selected in the list'); + } + return _selectedIndex; + } + + set selectedIndex(int value) { + RangeError.range(value, 0, items.count, 'SelectedIndex'); + if (selectedIndex != value) { + if (_isLoadedField) { + _assignSelectedIndex(value); + _changed = true; + } + _selectedIndex = value; + final PdfRadioButtonListItem item = _items![_selectedIndex]; + _dictionary._setName(_PdfName(_DictionaryProperties.v), item.value); + _dictionary._setName(_PdfName(_DictionaryProperties.dv), item.value); + } + } + + /// Gets the first selected item in the list.{Read-Only} + PdfRadioButtonListItem? get selectedItem { + PdfRadioButtonListItem? item; + if (selectedIndex != -1) { + item = items[_selectedIndex]; + } + return item; + } + + /// Gets or sets the value of the first selected item in the list. + String get selectedValue { + if (_isLoadedField) { + if (selectedIndex == -1) { + _selectedIndex = _obtainSelectedIndex(); + } + if (_selectedIndex != -1) { + return _items![_selectedIndex].value; + } else { + ArgumentError('None of the item to be selected in the list'); + } + return _items![_selectedIndex].value; + } else { + if (_selectedIndex == -1) { + ArgumentError('None of the item to be selected in the list'); + } + return _items![_selectedIndex].value; + } + } + + set selectedValue(String value) { + if (_isLoadedField) { + _assignSelectedValue(value); + _changed = true; + } else { + for (final Object? item in items._list) { + if (item is PdfRadioButtonListItem && item.value == value) { + _selectedIndex = items.indexOf(item); + _dictionary._setName(_PdfName(_DictionaryProperties.v), item.value); + _dictionary._setName(_PdfName(_DictionaryProperties.dv), item.value); + break; + } + } + } + } + + //Implementation + void _initValues( + List? radioItems, int? index, String? value) { + if (radioItems != null) { + radioItems.forEach((item) => items.add(item)); + } + if (index != null) { + selectedIndex = index; + } + if (value != null) { + selectedValue = value; + } + } + + PdfRadioButtonItemCollection _getRadioButtonListItems( + PdfRadioButtonItemCollection listItems) { + final _PdfArray fieldKids = _obtainKids()!; + for (int i = 0; i < fieldKids.count; i++) { + final _IPdfPrimitive? kidsDict = + _PdfCrossTable._dereference(fieldKids[i]); + if (kidsDict != null && kidsDict is _PdfDictionary) { + final PdfRadioButtonListItem item = + PdfRadioButtonListItem._loaded(kidsDict, _crossTable!, this); + listItems._doAdd(item, true); + } + } + return listItems; + } + + int _obtainSelectedIndex() { + int index = -1; + for (int i = 0; i < items.count; ++i) { + final PdfRadioButtonListItem item = items[i]; + final _PdfDictionary dic = item._dictionary; + final _IPdfPrimitive? checkNamePrimitive = + PdfField._searchInParents(dic, _crossTable, _DictionaryProperties.v); + final _PdfName? checkName = checkNamePrimitive as _PdfName?; + _PdfString? checkNameString; + if (checkName == null) { + checkNameString = checkNamePrimitive as _PdfString?; + } + if (dic.containsKey(_DictionaryProperties.usageApplication) && + (checkName != null || checkNameString != null)) { + final _PdfName name = + _crossTable!._getObject(dic[_DictionaryProperties.usageApplication]) + as _PdfName; + if (name._name!.toLowerCase() != "off") { + if (checkName != null && checkName._name!.toLowerCase() != "off") { + if (name._name == checkName._name) index = i; + break; + } else if (checkNameString != null && + checkNameString.value!.toLowerCase() != "off") { + if (name._name == checkNameString.value) index = i; + break; + } + } + } + } + return index; + } + + @override + void _beginSave() { + super._beginSave(); + final _PdfArray? kids = _obtainKids(); + int i = 0; + if ((kids != null)) { + for (i = 0; i < kids.count; ++i) { + final _PdfDictionary? widget = + _crossTable!._getObject(kids[i]) as _PdfDictionary?; + final PdfRadioButtonListItem item = items[i]; + item._applyAppearance(widget, item); + } + } + while (i < items.count) { + final PdfRadioButtonListItem item = items[i]; + item._save(); + i++; + } + } + + void _assignSelectedIndex(int value) { + final int index = _selectedIndex; + if (index != value) { + _PdfName? name; + if (_dictionary.containsKey(_DictionaryProperties.v)) { + name = _dictionary[_DictionaryProperties.v] as _PdfName?; + _dictionary.remove(_DictionaryProperties.v); + _dictionary.remove(_DictionaryProperties.dv); + } + if (name != null) { + for (int i = 0; i < items.count; i++) { + final PdfRadioButtonListItem item = items[i]; + if (item.value == name._name) { + item._dictionary._setName( + _PdfName(_DictionaryProperties.usageApplication), + _DictionaryProperties.off); + } + } + } + items[value]._dictionary._setName( + _PdfName(_DictionaryProperties.usageApplication), items[value].value); + } + } + + void _assignSelectedValue(String value) { + _PdfName? name; + if (_dictionary.containsKey(_DictionaryProperties.v)) { + name = _dictionary[_DictionaryProperties.v] as _PdfName?; + _dictionary.remove(_DictionaryProperties.v); + _dictionary.remove(_DictionaryProperties.dv); + } + if (name != null) { + for (int i = 0; i < items.count; i++) { + final PdfRadioButtonListItem item = items[i]; + if (item.value == name._name) { + item._dictionary._setName( + _PdfName(_DictionaryProperties.usageApplication), + _DictionaryProperties.off); + } + } + } + for (final Object? item in items._list) { + if (item is PdfRadioButtonListItem && item.value == value) { + _selectedIndex = items.indexOf(item); + _dictionary._setName(_PdfName(_DictionaryProperties.v), item.value); + _dictionary._setName(_PdfName(_DictionaryProperties.dv), item.value); + item._dictionary._setName( + _PdfName(_DictionaryProperties.usageApplication), item.value); + break; + } + } + } + + void _draw() { + if (_isLoadedField) { + final _PdfArray? kids = _obtainKids(); + if ((kids != null)) { + for (int i = 0; i < kids.count; ++i) { + final PdfRadioButtonListItem item = items[i]; + _PdfCheckFieldState state = _PdfCheckFieldState.unchecked; + if ((selectedIndex >= 0) && (selectedValue == item.value)) { + state = _PdfCheckFieldState.checked; + } + if (item.page != null) { + _drawStateItem(item.page!.graphics, state, item); + } + } + } + } else { + for (int i = 0; i < items.count; ++i) { + items[i]._draw(); + } + } + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_radio_button_list_item.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_radio_button_list_item.dart new file mode 100644 index 000000000..42fc96878 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_radio_button_list_item.dart @@ -0,0 +1,265 @@ +part of pdf; + +/// Represents an item of a radio button list. +class PdfRadioButtonListItem extends PdfCheckFieldBase { + //Constructor + /// Initializes a instance of the [PdfRadioButtonListItem] class with + /// the specific value and bounds. + PdfRadioButtonListItem(String value, Rect bounds, + {PdfCheckBoxStyle style = PdfCheckBoxStyle.circle, + PdfColor? borderColor, + PdfColor? backColor, + PdfColor? foreColor, + int? borderWidth, + PdfHighlightMode highlightMode = PdfHighlightMode.invert, + PdfBorderStyle borderStyle = PdfBorderStyle.solid, + String? tooltip}) + : super(null, null, bounds, + style: style, + borderColor: borderColor, + backColor: backColor, + foreColor: foreColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip) { + this.value = value; + _dictionary._beginSave = _dictionaryBeginSave; + } + + PdfRadioButtonListItem._loaded(_PdfDictionary dictionary, + _PdfCrossTable crossTable, PdfRadioButtonListField field) + : super._loaded(dictionary, crossTable) { + _field = field; + } + + //Fields + String? _value = ''; + PdfRadioButtonListField? _field; + + //Properties + ///Gets or sets the value. + String get value { + if (_isLoadedField) { + _value = _getItemValue(this._dictionary, this._crossTable); + } + return _value!; + } + + set value(String value) { + if (value.isEmpty) { + ArgumentError.value('value should not be empty'); + } + if (_isLoadedField) { + _setItemValue(value); + } + _value = value; + } + + /// Gets the form of the field.{Read-Only} + @override + PdfForm? get form => (_field != null) ? _field!.form : null; + + @override + set style(PdfCheckBoxStyle value) { + if (_isLoadedField) { + _assignStyle(value); + if (form!._needAppearances == false) { + _field!._changed = true; + _field!._fieldChanged = true; + } + } else { + if (_style != value) { + _style = value; + _widget!._widgetAppearance!.normalCaption = _styleToString(_style); + } + } + } + + //Implementation + @override + void _initialize() { + super._initialize(); + _widget!._beginSave = _widgetSave; + style = PdfCheckBoxStyle.circle; + } + + void _widgetSave(Object sender, _SavePdfPrimitiveArgs? e) { + _save(); + } + + @override + void _save() { + super._save(); + if (form != null) { + final String value = _obtainValue(); + _widget!.extendedAppearance!.normal._onMappingName = value; + _widget!.extendedAppearance!.pressed._onMappingName = value; + if (_field!.selectedItem == this) { + _widget!._appearanceState = _obtainValue(); + } else { + _widget!._appearanceState = _DictionaryProperties.off; + } + } + } + + String _obtainValue() { + String? returnValue; + if (_value!.isEmpty) { + final int index = _field!.items.indexOf(this); + returnValue = index.toString(); + } else { + returnValue = _value; + } + return returnValue!.replaceAll(RegExp(r'\s+'), '#20'); + } + + void _setField(PdfRadioButtonListField? field, [bool? isItem]) { + _widget!.parent = field; + final PdfPage page = (field != null) ? field.page! : _field!.page!; + if (!page._isLoadedPage) { + if (field == null) { + page.annotations.remove(_widget!); + } else { + page.annotations.add(_widget!); + } + } else if (page._isLoadedPage && !isItem!) { + final _PdfDictionary pageDic = page._dictionary; + _PdfArray? annots; + if (pageDic.containsKey(_DictionaryProperties.annots)) { + annots = page._crossTable! + ._getObject(pageDic[_DictionaryProperties.annots]) as _PdfArray?; + } else { + annots = _PdfArray(); + } + final _PdfReferenceHolder reference = _PdfReferenceHolder(_widget); + if (field == null) { + final int index = annots!._indexOf(reference); + if (index >= 0) { + annots._removeAt(index); + } + } else { + annots!._add(reference); + if (!field.page!.annotations.contains(this._widget!)) { + field.page!.annotations.add(this._widget!); + } + field.page!._dictionary + .setProperty(_DictionaryProperties.annots, annots); + } + } + if (field != null) { + _field = field; + } + } + + @override + void _drawCheckAppearance() { + super._drawCheckAppearance(); + final _PaintParams paintParams = _PaintParams( + bounds: Rect.fromLTWH( + 0, 0, _widget!.bounds.size.width, _widget!.bounds.size.height), + backBrush: PdfSolidBrush(_backColor), + foreBrush: PdfSolidBrush(_foreColor), + borderPen: _borderPen, + style: _borderStyle, + borderWidth: _borderWidth, + shadowBrush: PdfSolidBrush(_backColor)); + + PdfTemplate template = _widget!.extendedAppearance!.normal.activate!; + _FieldPainter().drawRadioButton(template.graphics, paintParams, + _styleToString(style), _PdfCheckFieldState.checked); + + template = _widget!.extendedAppearance!.normal.off!; + _FieldPainter().drawRadioButton(template.graphics, paintParams, + _styleToString(style), _PdfCheckFieldState.unchecked); + + template = _widget!.extendedAppearance!.pressed.activate!; + _FieldPainter().drawRadioButton(template.graphics, paintParams, + _styleToString(style), _PdfCheckFieldState.pressedChecked); + + template = _widget!.extendedAppearance!.pressed.off!; + _FieldPainter().drawRadioButton(template.graphics, paintParams, + _styleToString(style), _PdfCheckFieldState.pressedUnchecked); + } + + void _setItemValue(String value) { + final String str = value; + if (_dictionary.containsKey(_DictionaryProperties.ap)) { + _PdfDictionary dic = _crossTable! + ._getObject(_dictionary[_DictionaryProperties.ap]) as _PdfDictionary; + if (dic.containsKey(_DictionaryProperties.n)) { + final _PdfReference normal = + _crossTable!._getReference(dic[_DictionaryProperties.n]); + dic = _crossTable!._getObject(normal) as _PdfDictionary; + final String? dicValue = + _getItemValue(this._dictionary, this._crossTable); + if (dic.containsKey(dicValue)) { + final _PdfReference valRef = + _crossTable!._getReference(dic[dicValue]); + dic.remove(this.value); + dic.setProperty( + str, _PdfReferenceHolder.fromReference(valRef, _crossTable)); + } + } + } + if (str == _field!.selectedValue) { + _dictionary._setName( + _PdfName(_DictionaryProperties.usageApplication), str); + } else { + _dictionary._setName(_PdfName(_DictionaryProperties.usageApplication), + _DictionaryProperties.off); + } + } + + void _draw() { + _removeAnnotationFromPage(_field!.page); + final _PaintParams params = _PaintParams( + bounds: bounds, + backBrush: _backBrush, + foreBrush: _foreBrush, + borderPen: _borderPen, + style: borderStyle, + borderWidth: borderWidth, + shadowBrush: _shadowBrush); + if (params._borderPen != null && params._borderWidth == 0) { + params._borderWidth = 1; + } + _PdfCheckFieldState state = _PdfCheckFieldState.unchecked; + if ((_field!.selectedIndex >= 0) && (_field!.selectedValue == value)) { + state = _PdfCheckFieldState.checked; + } + _FieldPainter().drawRadioButton( + _field!.page!.graphics, params, _styleToString(style), state); + } + + @override + Rect _getBounds() { + _IPdfPrimitive? array; + if (array == null && + this._dictionary.containsKey(_DictionaryProperties.rect)) { + array = + _crossTable!._getObject(this._dictionary[_DictionaryProperties.rect]); + } + Rect bounds; + if (array != null && array is _PdfArray) { + bounds = array.toRectangle().rect; + double? y = 0; + if ((_PdfCrossTable._dereference(array[1]) as _PdfNumber).value! < 0) { + y = (_PdfCrossTable._dereference(array[1]) as _PdfNumber).value + as double?; + if ((_PdfCrossTable._dereference(array[1]) as _PdfNumber).value! > + (_PdfCrossTable._dereference(array[3]) as _PdfNumber).value!) { + y = y! - bounds.height; + } + } + bounds = Rect.fromLTWH( + bounds.left, y! <= 0 ? bounds.top : y, bounds.width, bounds.height); + } else { + bounds = Rect.fromLTWH(0, 0, 0, 0); + } + return bounds; + } + + @override + _IPdfPrimitive? get _element => _widget!._element; +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_signature_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_signature_field.dart new file mode 100644 index 000000000..debe7c53c --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_signature_field.dart @@ -0,0 +1,352 @@ +part of pdf; + +/// Represents signature field in the PDF Form. +class PdfSignatureField extends PdfField { + //Constructor + /// Initializes a new instance of the [PdfSignatureField] class. + PdfSignatureField(PdfPage page, String name, + {Rect bounds = Rect.zero, + int borderWidth = 1, + PdfHighlightMode? highlightMode, + PdfSignature? signature, + String? tooltip}) + : super(page, name, bounds, + borderWidth: borderWidth, + highlightMode: highlightMode, + tooltip: tooltip) { + if (page._document != null) { + form!._signatureFlags = [ + _SignatureFlags.signaturesExists, + _SignatureFlags.appendOnly + ]; + } + if (signature != null) { + this.signature = signature; + } + } + + PdfSignatureField._(_PdfDictionary dictionary, _PdfCrossTable crossTable) + : super._load(dictionary, crossTable); + + //Fields + bool _skipKidsCertificate = false; + PdfSignature? _signature; + + //Properties + /// Gets or sets the width of the border. + /// + /// The default value is 1. + int get borderWidth => _borderWidth; + set borderWidth(int value) => _borderWidth = value; + + /// Gets or sets the highlighting mode. + /// + /// The default mode is invert. + PdfHighlightMode get highlightMode => _highlightMode; + set highlightMode(PdfHighlightMode value) => _highlightMode = value; + + ///Gets the visual appearance of the field + PdfAppearance get appearance => _widget!.appearance; + + /// Gets or sets the digital signature for signing the field. + PdfSignature? get signature { + if (_isLoadedField && _signature == null) { + if (_dictionary.containsKey(_DictionaryProperties.v)) { + _setSignature(_dictionary[_DictionaryProperties.v]); + } + } + return _signature; + } + + set signature(PdfSignature? value) { + _initializeSignature(value); + } + + //Implementations + @override + void _initialize() { + super._initialize(); + form!.fieldAutoNaming + ? _widget!._dictionary.setProperty( + _DictionaryProperties.ft, _PdfName(_DictionaryProperties.sig)) + : _dictionary.setProperty( + _DictionaryProperties.ft, _PdfName(_DictionaryProperties.sig)); + } + + void _initializeSignature(PdfSignature? value) { + if (value != null) { + _signature = value; + _signature!._page = page; + _signature!._document = _signature!._page!._document; + _signature!._checkAnnotationElementsContainsSignature(page!, name); + _signature!._field = this; + _signature!._document!._catalog._beginSave = + _signature!._catalogBeginSave; + _dictionary._beginSaveList ??= []; + _dictionary._beginSaveList!.add(_signature!._dictionaryBeginSave); + if (!_skipKidsCertificate) { + _signature!._signatureDictionary = + _PdfSignatureDictionary(_signature!._document!, _signature!); + if (!_signature!._document!._isLoadedDocument || + _signature!._document!.fileStructure.incrementalUpdate != false) { + _signature!._document!._objects + ._add(_signature!._signatureDictionary!._element); + _signature! + ._document! + ._objects[_signature!._document!._objects._count - 1] + ._isModified = true; + _signature!._signatureDictionary!._element.position = -1; + } + if (_isLoadedField) { + form!._signatureFlags = <_SignatureFlags>[ + _SignatureFlags.signaturesExists, + _SignatureFlags.appendOnly + ]; + final _PdfDictionary widget = + _getWidgetAnnotation(_dictionary, _crossTable); + widget[_DictionaryProperties.v] = + _PdfReferenceHolder(_signature!._signatureDictionary); + widget.modify(); + _changed = true; + widget.setProperty(_DictionaryProperties.fieldFlags, _PdfNumber(0)); + _signature!._signatureDictionary!._dictionary._archive = false; + } else { + _widget!._dictionary.setProperty(_DictionaryProperties.v, + _PdfReferenceHolder(_signature!._signatureDictionary)); + _widget!._dictionary + .setProperty(_DictionaryProperties.fieldFlags, _PdfNumber(0)); + } + } else { + _widget!._dictionary + .setProperty(_DictionaryProperties.fieldFlags, _PdfNumber(0)); + } + _widget!.bounds = bounds; + } + } + + void _draw() { + if (!_isLoadedField) { + super._draw(); + if (_widget!._pdfAppearance != null) { + page!.graphics + .drawPdfTemplate(_widget!.appearance.normal, bounds.topLeft); + } + } else if (_flattenField) { + if (_dictionary[_DictionaryProperties.ap] != null) { + final _IPdfPrimitive? dictionary = + _dictionary[_DictionaryProperties.ap]; + final _IPdfPrimitive? appearanceDictionary = + _PdfCrossTable._dereference(dictionary); + PdfTemplate template; + if (appearanceDictionary != null && + appearanceDictionary is _PdfDictionary) { + final _IPdfPrimitive? appearanceRefHolder = + appearanceDictionary[_DictionaryProperties.n]; + final _IPdfPrimitive? objectDictionary = + _PdfCrossTable._dereference(appearanceRefHolder); + if (objectDictionary != null && objectDictionary is _PdfDictionary) { + if (objectDictionary is _PdfStream) { + final _PdfStream stream = objectDictionary; + template = PdfTemplate._fromPdfStream(stream); + page!.graphics.drawPdfTemplate(template, bounds.topLeft); + } + } + } + } else { + //signature field without appearance dictionary + final PdfBrush brush = PdfSolidBrush(_getBackColor(true)); + final _GraphicsProperties graphicsProperties = + _GraphicsProperties(this); + final _PaintParams paintingParameters = _PaintParams( + bounds: graphicsProperties._bounds, + backBrush: brush, + foreBrush: graphicsProperties._foreBrush, + borderPen: graphicsProperties._borderPen, + style: graphicsProperties._style, + borderWidth: graphicsProperties._borderWidth, + shadowBrush: graphicsProperties._shadowBrush); + _FieldPainter().drawSignature(page!.graphics, paintingParameters); + } + } + } + + void _drawAppearance(PdfTemplate template) { + super._drawAppearance(template); + _FieldPainter().drawSignature(template.graphics!, _PaintParams()); + } + + void _setSignature(_IPdfPrimitive? signature) { + if (signature is _PdfReferenceHolder && + signature.object != null && + signature.object is _PdfDictionary) { + final _PdfDictionary signatureDictionary = + signature.object as _PdfDictionary; + _signature = PdfSignature(); + _signature!._document = _crossTable!._document; + String? subFilterType = ''; + if (signatureDictionary.containsKey(_DictionaryProperties.subFilter)) { + final _IPdfPrimitive? filter = _PdfCrossTable._dereference( + signatureDictionary[_DictionaryProperties.subFilter]); + if (filter != null && filter is _PdfName) { + subFilterType = filter._name; + } + if (subFilterType == 'ETSI.CAdES.detached') { + _signature!.cryptographicStandard = CryptographicStandard.cades; + } + } + if (_crossTable!._document != null && + !_crossTable!._document!._isLoadedDocument) { + if (signatureDictionary.containsKey(_DictionaryProperties.reference)) { + final _IPdfPrimitive? tempArray = + signatureDictionary[_DictionaryProperties.reference]; + if (tempArray != null && tempArray is _PdfArray) { + final _IPdfPrimitive? tempDictionary = tempArray._elements[0]; + if (tempDictionary != null && tempDictionary is _PdfDictionary) { + if (tempDictionary.containsKey(_DictionaryProperties.data)) { + final _PdfMainObjectCollection? mainObjectCollection = + _crossTable!._document!._objects; + _IPdfPrimitive? tempReferenceHolder = + tempDictionary[_DictionaryProperties.data]; + if (tempReferenceHolder != null && + tempReferenceHolder is _PdfReferenceHolder && + !mainObjectCollection! + ._containsReference(tempReferenceHolder.reference!)) { + final _IPdfPrimitive? tempObject = mainObjectCollection + ._objectCollection![ + tempReferenceHolder.reference!.objectCollectionIndex!] + ._object; + tempReferenceHolder = _PdfReferenceHolder(tempObject); + tempDictionary.setProperty( + _DictionaryProperties.data, tempReferenceHolder); + } + } + } + } + } + signatureDictionary.remove(_DictionaryProperties.byteRange); + _PdfSignatureDictionary._fromDictionary( + _crossTable!._document!, signatureDictionary); + _dictionary.remove(_DictionaryProperties.contents); + _dictionary.remove(_DictionaryProperties.byteRange); + } + if (signatureDictionary.containsKey(_DictionaryProperties.m) && + signatureDictionary[_DictionaryProperties.m] is _PdfString) { + _signature!._signedDate = _dictionary._getDateTime( + signatureDictionary[_DictionaryProperties.m] as _PdfString); + } + if (signatureDictionary.containsKey(_DictionaryProperties.name) && + signatureDictionary[_DictionaryProperties.name] is _PdfString) { + _signature!.signedName = + (signatureDictionary[_DictionaryProperties.name] as _PdfString) + .value; + } + if (signatureDictionary.containsKey(_DictionaryProperties.reason)) { + final _IPdfPrimitive? reason = _PdfCrossTable._dereference( + signatureDictionary[_DictionaryProperties.reason]); + if (reason != null && reason is _PdfString) { + _signature!.reason = reason.value; + } + } + if (signatureDictionary.containsKey(_DictionaryProperties.location)) { + final _IPdfPrimitive? location = _PdfCrossTable._dereference( + signatureDictionary[_DictionaryProperties.location]); + if (location != null && location is _PdfString) { + _signature!.locationInfo = location.value; + } + } + if (signatureDictionary.containsKey(_DictionaryProperties.contactInfo)) { + final _IPdfPrimitive? contactInfo = _PdfCrossTable._dereference( + signatureDictionary[_DictionaryProperties.contactInfo]); + if (contactInfo != null && contactInfo is _PdfString) { + _signature!.contactInfo = contactInfo.value; + } + } + if (signatureDictionary.containsKey(_DictionaryProperties.byteRange)) { + _signature!._byteRange = + signatureDictionary[_DictionaryProperties.byteRange] as _PdfArray?; + if (_crossTable!.documentCatalog != null) { + final _PdfDictionary catalog = _crossTable!._documentCatalog!; + bool hasPermission = false; + if (catalog.containsKey(_DictionaryProperties.perms)) { + final _IPdfPrimitive? primitive = + catalog[_DictionaryProperties.perms]; + final _IPdfPrimitive? catalogDictionary = + (primitive is _PdfReferenceHolder) + ? primitive.object + : primitive; + if (catalogDictionary != null && + catalogDictionary is _PdfDictionary && + catalogDictionary.containsKey(_DictionaryProperties.docMDP)) { + final _IPdfPrimitive? docPermission = + catalogDictionary[_DictionaryProperties.docMDP]; + final _IPdfPrimitive? permissionDictionary = + (docPermission is _PdfReferenceHolder) + ? docPermission.object + : docPermission; + if (permissionDictionary != null && + permissionDictionary is _PdfDictionary && + permissionDictionary + .containsKey(_DictionaryProperties.byteRange)) { + final _IPdfPrimitive? byteRange = _PdfCrossTable._dereference( + permissionDictionary[_DictionaryProperties.byteRange]); + bool isValid = true; + if (byteRange != null && + byteRange is _PdfArray && + _signature != null && + _signature!._byteRange != null) { + for (int i = 0; i < byteRange.count; i++) { + final _IPdfPrimitive? byteValue = byteRange[i]; + final _IPdfPrimitive? signByte = _signature!._byteRange![i]; + if (byteValue != null && + signByte != null && + byteValue is _PdfNumber && + signByte is _PdfNumber && + byteValue.value != signByte.value) { + isValid = false; + break; + } + } + } + + hasPermission = isValid; + } + } + } + if (hasPermission && + signatureDictionary + .containsKey(_DictionaryProperties.reference)) { + _IPdfPrimitive? primitive = + signatureDictionary[_DictionaryProperties.reference]; + if (primitive is _PdfArray) { + primitive = primitive._elements[0]; + } + _IPdfPrimitive? reference = (primitive is _PdfReferenceHolder) + ? primitive.object + : primitive; + if (reference != null && + reference is _PdfDictionary && + reference.containsKey('TransformParams')) { + primitive = reference['TransformParams']; + if (primitive is _PdfReferenceHolder) { + reference = primitive.object as _PdfDictionary; + } else if (primitive is _PdfDictionary) { + reference = primitive; + } + if (reference is _PdfDictionary && + reference.containsKey(_DictionaryProperties.p)) { + final _IPdfPrimitive? permissionNumber = + _PdfCrossTable._dereference( + reference[_DictionaryProperties.p]); + if (permissionNumber != null && + permissionNumber is _PdfNumber) { + _signature!.documentPermissions = _signature! + ._getCertificateFlags(permissionNumber.value as int); + } + } + } + } + } + } + } + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_text_box_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_text_box_field.dart new file mode 100644 index 000000000..85e16226e --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_text_box_field.dart @@ -0,0 +1,636 @@ +part of pdf; + +/// Represents text box field in the PDF form. +class PdfTextBoxField extends PdfField { + //Constructor + /// Initializes a new instance of the [PdfTextBoxField] class with the provided page and name. + PdfTextBoxField(PdfPage page, String name, Rect bounds, + {PdfFont? font, + String? text, + String? defaultValue, + int maxLength = 0, + bool spellCheck = false, + bool insertSpaces = false, + bool multiline = false, + bool isPassword = false, + bool scrollable = false, + PdfTextAlignment alignment = PdfTextAlignment.left, + PdfColor? borderColor, + PdfColor? foreColor, + PdfColor? backColor, + int? borderWidth, + PdfHighlightMode highlightMode = PdfHighlightMode.invert, + PdfBorderStyle borderStyle = PdfBorderStyle.solid, + String? tooltip}) + : super(page, name, bounds, + font: font, + alignment: alignment, + borderColor: borderColor, + foreColor: foreColor, + backColor: backColor, + borderWidth: borderWidth, + highlightMode: highlightMode, + borderStyle: borderStyle, + tooltip: tooltip) { + this.font = + font != null ? font : PdfStandardFont(PdfFontFamily.helvetica, 8); + _init(text, defaultValue, maxLength, spellCheck, insertSpaces, multiline, + isPassword, scrollable); + } + + /// Initializes a new instance of the [PdfTextBoxField] class. + PdfTextBoxField._load(_PdfDictionary dictionary, _PdfCrossTable crossTable) + : super._load(dictionary, crossTable) { + _items = PdfFieldItemCollection._(this); + final _PdfArray? kids = _kids; + if (kids != null) { + for (int i = 0; i < kids.count; ++i) { + final _PdfDictionary? itemDictionary = + crossTable._getObject(kids[i]) as _PdfDictionary?; + _items!._add(PdfTextBoxItem._(this, i, itemDictionary)); + } + _array = kids; + } + } + + //Fields + String? _text = ''; + String? _defaultValue = ''; + bool _spellCheck = false; + bool _insertSpaces = false; + bool _multiline = false; + bool _password = false; + bool _scrollable = true; + int _maxLength = 0; + PdfFieldItemCollection? _items; + + //Properties + /// Gets or sets the text in the text box. + String get text { + if (_isLoadedField) { + _IPdfPrimitive? str; + final _IPdfPrimitive? referenceHolder = + _dictionary[_DictionaryProperties.v]; + if (referenceHolder != null && referenceHolder is _PdfReferenceHolder) { + final _IPdfPrimitive? textObject = + _PdfCrossTable._dereference(referenceHolder); + if (textObject is _PdfStream) { + final _PdfStream stream = referenceHolder.object as _PdfStream; + stream._decompress(); + final List bytes = stream._dataStream!; + final String data = utf8.decode(bytes); + str = _PdfString(data); + } else if (textObject is _PdfString) { + str = PdfField._getValue( + _dictionary, _crossTable, _DictionaryProperties.v, true); + } else { + str = _PdfString(''); + } + } else { + str = PdfField._getValue( + _dictionary, _crossTable, _DictionaryProperties.v, true); + } + _text = str != null && str is _PdfString ? str.value : ''; + return _text!; + } + return _text!; + } + + set text(String value) { + if (_isLoadedField) { + //check if not readOnly. + if (!_isFlagPresent(_FieldFlags.readOnly)) { + _isTextChanged = true; + if (_dictionary.containsKey(_DictionaryProperties.aa)) { + final _IPdfPrimitive? dic = _dictionary[_DictionaryProperties.aa]; + if (dic != null && dic is _PdfDictionary) { + final _IPdfPrimitive? dicRef = dic[_DictionaryProperties.k]; + if (dicRef != null && dicRef is _PdfReferenceHolder) { + final _IPdfPrimitive? dict = dicRef.object; + if (dict != null && dict is _PdfDictionary) { + final _IPdfPrimitive? str = + _PdfCrossTable._dereference(dict['JS']); + if (str != null && str is _PdfString) { + _dictionary.setProperty( + _DictionaryProperties.v, _PdfString(str.value!)); + } + } + } + } + } + _dictionary.setProperty(_DictionaryProperties.v, _PdfString(value)); + _changed = true; + _form!._setAppearanceDictionary = true; + if (_form!._isUR3) { + _dictionary._beginSaveList ??= []; + _dictionary._beginSaveList!.add(_dictSave); + } + } else { + _changed = false; + } + } else { + if (_text != value) { + _text = value; + _dictionary._setString(_DictionaryProperties.v, _text); + } + } + } + + /// Gets or sets the font. + PdfFont get font => _font!; + set font(PdfFont value) { + _font = value; + } + + /// Gets or sets the default value. + String get defaultValue { + if (_isLoadedField) { + final _IPdfPrimitive? str = PdfField._getValue( + _dictionary, _crossTable, _DictionaryProperties.dv, true); + if (str != null && str is _PdfString) { + _defaultValue = str.value; + } + } + return _defaultValue!; + } + + set defaultValue(String value) { + if (defaultValue != value) { + _defaultValue = value; + _dictionary._setString(_DictionaryProperties.dv, _defaultValue); + if (_isLoadedField) { + _changed = true; + } + } + } + + /// Gets or sets the maximum number of characters that can be entered in the text box. + /// + /// The default value is 0. + int get maxLength { + if (_isLoadedField) { + final _IPdfPrimitive? number = PdfField._getValue( + _dictionary, _crossTable, _DictionaryProperties.maxLen, true); + if (number != null && number is _PdfNumber) { + _maxLength = number.value!.toInt(); + } + } + return _maxLength; + } + + set maxLength(int value) { + if (maxLength != value) { + _maxLength = value; + _dictionary._setNumber(_DictionaryProperties.maxLen, _maxLength); + if (_isLoadedField) { + _changed = true; + } + } + } + + /// Gets or sets a value indicating whether to check spelling. + /// + /// The default value is false. + bool get spellCheck { + if (_isLoadedField) { + _spellCheck = !(_isFlagPresent(_FieldFlags.doNotSpellCheck) || + _flags.contains(_FieldFlags.doNotSpellCheck)); + } + return _spellCheck; + } + + set spellCheck(bool value) { + if (spellCheck != value) { + _spellCheck = value; + _spellCheck + ? _isLoadedField + ? _removeFlag(_FieldFlags.doNotSpellCheck) + : _flags.remove(_FieldFlags.doNotSpellCheck) + : _flags.add(_FieldFlags.doNotSpellCheck); + } + } + + /// Meaningful only if the maxLength property is set and the multiline, isPassword properties are false. + /// + /// If set, the field is automatically divided into as many equally spaced positions, or combs, + /// as the value of maxLength, and the text is laid out into those combs. + /// + /// The default value is false. + bool get insertSpaces { + _insertSpaces = _flags.contains(_FieldFlags.comb) && + !_flags.contains(_FieldFlags.multiline) && + !_flags.contains(_FieldFlags.password) && + !_flags.contains(_FieldFlags.fileSelect); + if (_isLoadedField) { + _insertSpaces = _insertSpaces || + (_isFlagPresent(_FieldFlags.comb) && + !_isFlagPresent(_FieldFlags.multiline) && + !_isFlagPresent(_FieldFlags.password) && + !_isFlagPresent(_FieldFlags.fileSelect)); + } + return _insertSpaces; + } + + set insertSpaces(bool value) { + if (insertSpaces != value) { + _insertSpaces = value; + _insertSpaces + ? _flags.add(_FieldFlags.comb) + : _isLoadedField + ? _removeFlag(_FieldFlags.comb) + : _flags.remove(_FieldFlags.comb); + } + } + + /// Gets or sets a value indicating whether this [PdfTextBoxField] is multiline. + /// + /// The default value is false. + bool get multiline { + if (_isLoadedField) { + _multiline = _isFlagPresent(_FieldFlags.multiline) || + _flags.contains(_FieldFlags.multiline); + } + return _multiline; + } + + set multiline(bool value) { + if (multiline != value) { + _multiline = value; + if (_multiline) { + _flags.add(_FieldFlags.multiline); + _format!.lineAlignment = PdfVerticalAlignment.top; + } else { + _isLoadedField + ? _removeFlag(_FieldFlags.multiline) + : _flags.remove(_FieldFlags.multiline); + _format!.lineAlignment = PdfVerticalAlignment.middle; + } + } + } + + /// Gets or sets a value indicating whether this [PdfTextBoxField] is password field. + /// + /// The default value is false. + bool get isPassword { + if (_isLoadedField) { + _password = _isFlagPresent(_FieldFlags.password) || + _flags.contains(_FieldFlags.password); + } + return _password; + } + + set isPassword(bool value) { + if (isPassword != value) { + _password = value; + _password + ? _flags.add(_FieldFlags.password) + : _isLoadedField + ? _removeFlag(_FieldFlags.password) + : _flags.remove(_FieldFlags.password); + } + } + + /// Gets or sets a value indicating whether this [PdfTextBoxField] is scrollable. + /// + /// The default value is true. + bool get scrollable { + if (_isLoadedField) { + _scrollable = !(_isFlagPresent(_FieldFlags.doNotScroll) || + _flags.contains(_FieldFlags.doNotScroll)); + } + return _scrollable; + } + + set scrollable(bool value) { + if (scrollable != value) { + _scrollable = value; + _spellCheck + ? _isLoadedField + ? _removeFlag(_FieldFlags.doNotScroll) + : _flags.remove(_FieldFlags.doNotScroll) + : _flags.add(_FieldFlags.doNotScroll); + } + } + + /// Gets or sets the text alignment. + /// + /// The default alignment is left. + PdfTextAlignment get textAlignment => _textAlignment; + set textAlignment(PdfTextAlignment value) => _textAlignment = value; + + /// Gets or sets the color of the border. + /// + /// The default color is black. + PdfColor get borderColor => _borderColor; + set borderColor(PdfColor value) => _borderColor = value; + + /// Gets or sets the color of the background. + /// + /// The default color is empty. + PdfColor get backColor => _backColor; + set backColor(PdfColor value) => _backColor = value; + + /// Gets or sets the color of the text. + /// + /// The default color is black. + PdfColor get foreColor => _foreColor; + set foreColor(PdfColor value) => _foreColor = value; + + /// Gets or sets the width of the border. + /// + /// The default value is 1. + int get borderWidth => _borderWidth; + set borderWidth(int value) => _borderWidth = value; + + /// Gets or sets the highlighting mode. + /// + /// The default mode is invert. + PdfHighlightMode get highlightMode => _highlightMode; + set highlightMode(PdfHighlightMode value) => _highlightMode = value; + + /// Gets or sets the border style. + /// + /// The default style is solid. + PdfBorderStyle get borderStyle => _borderStyle; + set borderStyle(PdfBorderStyle value) => _borderStyle = value; + + /// Gets the collection of text box field items. + PdfFieldItemCollection? get items => _items; + + //Implementations + @override + void _initialize() { + super._initialize(); + _flags.add(_FieldFlags.doNotSpellCheck); + _dictionary.setProperty( + _DictionaryProperties.ft, _PdfName(_DictionaryProperties.tx)); + } + + @override + void _save() { + super._save(); + if (_fieldItems != null && _fieldItems!.length > 1) { + for (int i = 1; i < _fieldItems!.length; i++) { + final PdfTextBoxField field = _fieldItems![i] as PdfTextBoxField; + field.text = text; + field._save(); + } + } + } + + void _dictSave(Object sender, _SavePdfPrimitiveArgs? ars) { + _beginSave(); + } + + @override + void _drawAppearance(PdfTemplate template) { + super._drawAppearance(template); + final _PaintParams params = _PaintParams( + bounds: Rect.fromLTWH(0, 0, bounds.width, bounds.height), + backBrush: _backBrush, + foreBrush: _foreBrush, + borderPen: _borderPen, + style: borderStyle, + borderWidth: borderWidth, + shadowBrush: _shadowBrush); + template._writeTransformation = false; + final PdfGraphics graphics = template.graphics!; + _beginMarkupSequence(graphics._streamWriter!._stream!); + graphics._initializeCoordinates(); + if (params._borderWidth == 0 && params._borderPen != null) { + params._borderWidth = 1; + params._borderPen!.width = 1; + } + _drawTextBox(graphics, params: params); + _endMarkupSequence(graphics._streamWriter!._stream!); + } + + void _init(String? text, String? defaultValue, int maxLength, bool spellCheck, + bool insertSpaces, bool multiline, bool password, bool scrollable) { + if (text != null) { + this.text = text; + } + if (defaultValue != null) { + this.defaultValue = defaultValue; + } + this.maxLength = maxLength; + this.spellCheck = spellCheck; + this.insertSpaces = insertSpaces; + this.multiline = multiline; + this.isPassword = password; + this.scrollable = scrollable; + } + + void _drawTextBox(PdfGraphics? graphics, + {_PaintParams? params, PdfFieldItem? item}) { + if (params != null) { + String newText = text; + if (isPassword && text.isNotEmpty) { + newText = ''; + for (int i = 0; i < text.length; ++i) { + newText += '*'; + } + } + graphics!.save(); + if (insertSpaces) { + double width = 0; + final List ch = text.split(''); + if (maxLength > 0) { + width = params._bounds!.width / maxLength; + } + graphics.drawRectangle(bounds: params._bounds!, pen: _borderPen); + for (int i = 0; i < maxLength; i++) { + if (_format!.alignment != PdfTextAlignment.right) { + if (_format!.alignment == PdfTextAlignment.center && + ch.length < maxLength) { + final int startLocation = + (maxLength / 2 - (ch.length / 2).ceil()).toInt(); + newText = i >= startLocation && i < startLocation + ch.length + ? ch[i - startLocation] + : ''; + } else { + newText = ch.length > i ? ch[i] : ''; + } + } else { + newText = maxLength - ch.length <= i + ? ch[i - (maxLength - ch.length)] + : ''; + } + params._bounds = Rect.fromLTWH(params._bounds!.left, + params._bounds!.top, width, params._bounds!.height); + final PdfStringFormat format = PdfStringFormat( + alignment: PdfTextAlignment.center, + lineAlignment: _format!.lineAlignment); + _FieldPainter().drawTextBox( + graphics, params, newText, font, format, insertSpaces, multiline); + params._bounds = Rect.fromLTWH(params._bounds!.left + width, + params._bounds!.top, width, params._bounds!.height); + if (params._borderWidth != 0) { + graphics.drawLine( + params._borderPen!, + Offset(params._bounds!.left, params._bounds!.top), + Offset(params._bounds!.left, + params._bounds!.top + params._bounds!.height)); + } + } + } else { + _FieldPainter().drawTextBox( + graphics, params, newText, font, _format!, insertSpaces, multiline); + } + graphics.restore(); + } else { + final _GraphicsProperties gp = item != null + ? _GraphicsProperties.fromFieldItem(item) + : _GraphicsProperties(this); + if (gp._borderWidth == 0 && gp._borderPen != null) { + gp._borderWidth = 1; + gp._borderPen!.width = 1; + } + if (graphics!._layer == null) { + gp._bounds = Rect.fromLTWH(gp._bounds!.left, gp._bounds!.top, + graphics.size.width, graphics.size.height); + } + if (!_flattenField) { + gp._bounds = Rect.fromLTWH(0, 0, gp._bounds!.width, gp._bounds!.height); + } + final _PaintParams prms = _PaintParams( + bounds: gp._bounds, + backBrush: gp._backBrush, + foreBrush: gp._foreBrush, + borderPen: gp._borderPen, + style: gp._style, + borderWidth: gp._borderWidth, + shadowBrush: gp._shadowBrush); + _drawTextBox(graphics, params: prms); + } + } + + @override + void _beginSave() { + super._beginSave(); + final _PdfArray? kids = _kids; + if ((kids != null)) { + for (int i = 0; i < kids.count; ++i) { + final _PdfDictionary? widget = + _crossTable!._getObject(kids[i]) as _PdfDictionary?; + _applyAppearance(widget, items![i]); + } + } else { + _applyAppearance(_getWidgetAnnotation(_dictionary, _crossTable)); + } + } + + void _applyAppearance(_PdfDictionary? widget, [PdfFieldItem? item]) { + if (_form!._setAppearanceDictionary) { + if (widget != null && !_form!._needAppearances!) { + final _PdfDictionary appearance = _PdfDictionary(); + final Rect bounds = item == null ? this.bounds : item.bounds; + PdfTemplate? template; + if (widget.containsKey(_DictionaryProperties.mk)) { + final _IPdfPrimitive? mkDic = widget[_DictionaryProperties.mk]; + if (mkDic != null && + mkDic is _PdfDictionary && + mkDic.containsKey(_DictionaryProperties.r)) { + final _IPdfPrimitive? angle = mkDic[_DictionaryProperties.r]; + if (angle != null && angle is _PdfNumber) { + if (angle.value == 90) { + template = PdfTemplate(bounds.size.height, bounds.size.width); + template._content[_DictionaryProperties.matrix] = + _PdfArray([0, 1, -1, 0, bounds.size.width, 0]); + } else if (angle.value == 180) { + template = PdfTemplate(bounds.size.width, bounds.size.height); + template._content[_DictionaryProperties.matrix] = _PdfArray( + [-1, 0, 0, -1, bounds.size.width, bounds.size.height]); + } else if (angle.value == 270) { + template = PdfTemplate(bounds.size.height, bounds.size.width); + template._content[_DictionaryProperties.matrix] = + _PdfArray([0, -1, 1, 0, 0, bounds.size.height]); + } + if (template != null) { + template._writeTransformation = false; + } + } + } + } + if (template == null) { + template = PdfTemplate(bounds.size.width, bounds.size.height); + template._writeTransformation = false; + template._content[_DictionaryProperties.matrix] = + _PdfArray([1, 0, 0, 1, 0, 0]); + } + if (item != null) { + _beginMarkupSequence(template.graphics!._streamWriter!._stream!); + template.graphics!._initializeCoordinates(); + _drawTextBox(template.graphics, item: item); + _endMarkupSequence(template.graphics!._streamWriter!._stream!); + } else { + _drawAppearance(template); + } + appearance.setProperty( + _DictionaryProperties.n, _PdfReferenceHolder(template)); + widget.setProperty(_DictionaryProperties.ap, appearance); + } else { + _form!._needAppearances = true; + } + } + } + + double _getFontHeight(PdfFontFamily family) { + double s = 12; + if (!multiline) { + final PdfStandardFont font = PdfStandardFont(family, 12); + final Size fontSize = font.measureString(text); + s = (8 * (bounds.size.width - 4 * borderWidth)) / fontSize.width; + s = (s > 8) ? 8 : s; + } else { + s = 12.5; + } + return s; + } + + void _draw() { + super._draw(); + if (!_isLoadedField && _widget!._pdfAppearance != null) { + page!.graphics.drawPdfTemplate( + _widget!._pdfAppearance!.normal, Offset(bounds.width, bounds.height)); + if (_fieldItems != null && _fieldItems!.length > 1) { + for (int i = 1; i < _fieldItems!.length; i++) { + final PdfTextBoxField field = _fieldItems![i] as PdfTextBoxField; + field.text = text; + field.page!.graphics.drawPdfTemplate( + field._widget!._pdfAppearance!.normal, + Offset(field.bounds.width, field.bounds.height)); + } + } + } else { + if (_isLoadedField) { + final _PdfArray? kids = _kids; + if (kids != null) { + for (int i = 0; i < kids.count; ++i) { + final PdfFieldItem item = items![i]; + if (item.page != null && item.page!._isLoadedPage) { + _drawTextBox(item.page!.graphics, item: item); + } + } + } else { + _drawTextBox(page!.graphics); + } + } else { + _drawTextBox(page!.graphics); + if (_fieldItems != null && _fieldItems!.length > 1) { + for (int i = 1; i < _fieldItems!.length; i++) { + final PdfTextBoxField field = _fieldItems![i] as PdfTextBoxField; + field.text = text; + field._drawTextBox(field.page!.graphics); + } + } + } + } + } +} + +/// Represents an item in a text box field collection. +class PdfTextBoxItem extends PdfFieldItem { + PdfTextBoxItem._(PdfField field, int index, _PdfDictionary? dictionary) + : super._(field, index, dictionary); +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file.dart index b345387ee..949c91147 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file.dart @@ -5,8 +5,6 @@ class _EmbeddedFile implements _IPdfWrapper { //Constructor. /// Initializes a new instance of the [EmbeddedFile] class. _EmbeddedFile(String fileName, List data) : super() { - ArgumentError.checkNotNull(data, 'data'); - ArgumentError.checkNotNull(fileName, 'fileName'); this.data = data.toList(); _initialize(); this.fileName = fileName; @@ -14,7 +12,7 @@ class _EmbeddedFile implements _IPdfWrapper { //Fields. /// Gets or sets the data. - List data; + late List data; final _PdfStream _stream = _PdfStream(); String _fileName = ''; final _EmbeddedFileParams _params = _EmbeddedFileParams(); @@ -26,9 +24,7 @@ class _EmbeddedFile implements _IPdfWrapper { /// Sets the name of the file. set fileName(String value) { - if (_fileName != null) { - _fileName = _getFileName(value); - } + _fileName = _getFileName(value); } /// Gets the type of the MIME. @@ -59,20 +55,19 @@ class _EmbeddedFile implements _IPdfWrapper { return fileName[fileName.length - 1]; } - void _streamBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { + void _streamBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { _stream._clearStream(); _stream.compress = false; - if (data != null) { - _stream._dataStream = data; - _params._size = data.length; - } + _stream._dataStream = data; + _params._size = data.length; } @override _IPdfPrimitive get _element => _stream; @override - set _element(_IPdfPrimitive value) { + // ignore: unused_element + set _element(_IPdfPrimitive? value) { throw ArgumentError('primitive element can\'t be set'); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_params.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_params.dart index a0e457612..80e4e354e 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_params.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_params.dart @@ -12,7 +12,7 @@ class _EmbeddedFileParams implements _IPdfWrapper { DateTime _cDate = DateTime.now(); DateTime _mDate = DateTime.now(); final _PdfDictionary _dictionary = _PdfDictionary(); - int _fileSize; + int? _fileSize; //Properties. DateTime get _creationDate => _cDate; @@ -40,7 +40,8 @@ class _EmbeddedFileParams implements _IPdfWrapper { _IPdfPrimitive get _element => _dictionary; @override - set _element(_IPdfPrimitive value) { + // ignore: unused_element + set _element(_IPdfPrimitive? value) { throw ArgumentError('primitive element can\'t be set'); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_specification.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_specification.dart index 9140a72a5..0bcded1ec 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_specification.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/embedded_file_specification.dart @@ -4,15 +4,13 @@ part of pdf; class _PdfEmbeddedFileSpecification extends _PdfFileSpecificationBase { //Constructor /// Initializes a new instance of the [PdfEmbeddedFileSpecification] class - _PdfEmbeddedFileSpecification(String fileName, List data) - : super(fileName) { - ArgumentError.checkNotNull(data, 'data'); + _PdfEmbeddedFileSpecification(String fileName, List data) : super() { _embeddedFile = _EmbeddedFile(fileName, data); description = fileName; } //Fields - _EmbeddedFile _embeddedFile; + late _EmbeddedFile _embeddedFile; String _description = ''; final _PdfDictionary _dict = _PdfDictionary(); // ignore: prefer_final_fields @@ -49,7 +47,8 @@ class _PdfEmbeddedFileSpecification extends _PdfFileSpecificationBase { @override void _save() { _dict[_DictionaryProperties.f] = _PdfReferenceHolder(_embeddedFile); - final _PdfString str = _PdfString(_formatFileName(fileName, false)); + final _PdfString str = + _PdfString(_formatFileName(_embeddedFile.fileName, false)); _dictionary.setProperty(_DictionaryProperties.f, str); _dictionary.setProperty(_DictionaryProperties.uf, str); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/file_specification_base.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/file_specification_base.dart index 57f369c3c..d7f3d1844 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/file_specification_base.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/file_specification_base.dart @@ -4,17 +4,13 @@ part of pdf; abstract class _PdfFileSpecificationBase implements _IPdfWrapper { //Constructor. /// Initializes a new instance of the [PdfFileSpecificationBase] class. - _PdfFileSpecificationBase(String fileName) { - ArgumentError.checkNotNull(fileName, 'fileName'); + _PdfFileSpecificationBase() { _initialize(); } //Fields final _PdfDictionary _dictionary = _PdfDictionary(); - /// Gets or sets the name of the file. - String fileName; - //Implementations. //Initializes instance. void _initialize() { @@ -28,7 +24,6 @@ abstract class _PdfFileSpecificationBase implements _IPdfWrapper { const String oldSlash = '\\'; const String newSlash = '/'; const String driveDelimiter = ':'; - ArgumentError.checkNotNull(fileName, 'fileName'); if (fileName.isEmpty) { throw ArgumentError('fileName, String can not be empty'); } @@ -44,7 +39,7 @@ abstract class _PdfFileSpecificationBase implements _IPdfWrapper { } //Handles the BeginSave event of the m_dictionary control. - void _dictionaryBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { + void _dictionaryBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { _save(); } @@ -56,7 +51,8 @@ abstract class _PdfFileSpecificationBase implements _IPdfWrapper { _IPdfPrimitive get _element => _dictionary; @override - set _element(_IPdfPrimitive value) { + // ignore: unused_element + set _element(_IPdfPrimitive? value) { throw ArgumentError('primitive element can\'t be set'); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_collection.dart index a42e34e0d..934e986bd 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_collection.dart @@ -9,7 +9,7 @@ class PdfObjectCollection { } //Fields - List _list; + late List _list; //Properties /// Gets number of the elements in the collection. diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_default_appearance.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_default_appearance.dart new file mode 100644 index 000000000..f3a478af5 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_default_appearance.dart @@ -0,0 +1,22 @@ +part of pdf; + +/// Represents default appearance string. +class _PdfDefaultAppearance { + //Constructor + _PdfDefaultAppearance(); + + //Fields + /// Internal variable to store fore color. + PdfColor foreColor = PdfColor(0, 0, 0); + + /// Internal variable to store font name. + String? fontName = ''; + + /// Internal variable to store font size. + double? fontSize = 0; + + //Implementation + String _toString() { + return '/$fontName ${fontSize! % 1 == 0 ? fontSize!.toInt() : fontSize} Tf ${foreColor._toString(PdfColorSpace.rgb, false)}'; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_destination.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_destination.dart index ca93cfe1e..816cafc12 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_destination.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_destination.dart @@ -6,14 +6,12 @@ class PdfDestination implements _IPdfWrapper { // constructor /// Initializes a new instance of the [PdfDestination] class with /// specified page and location. - PdfDestination(PdfPage page, [Offset location]) { - ArgumentError.checkNotNull(page, 'page'); + PdfDestination(PdfPage page, [Offset? location]) { this.page = page; this.location = (location == null) ? Offset(0, _location.y) : location; } PdfDestination._(PdfPage page, _Rectangle rect) { - ArgumentError.checkNotNull(page, 'page'); this.page = page; location = rect.location.offset; _bounds = rect; @@ -23,9 +21,9 @@ class PdfDestination implements _IPdfWrapper { double _zoom = 0; _Point _location = _Point.empty; _Rectangle _rect = _Rectangle.empty; - PdfPage _page; + PdfPage? _page; final _PdfArray _array = _PdfArray(); - PdfDestinationMode _destinationMode; + PdfDestinationMode _destinationMode = PdfDestinationMode.location; // Properties /// Gets zoom factor. @@ -40,11 +38,10 @@ class PdfDestination implements _IPdfWrapper { } /// Gets a page where the destination is situated. - PdfPage get page => _page; + PdfPage get page => _page!; /// Sets a page where the destination is situated. set page(PdfPage value) { - ArgumentError.checkNotNull(value, 'page'); if (value != _page) { _page = value; _initializePrimitive(); @@ -53,7 +50,6 @@ class PdfDestination implements _IPdfWrapper { /// Gets mode of the destination. PdfDestinationMode get mode { - _destinationMode ??= PdfDestinationMode.location; return _destinationMode; } @@ -70,9 +66,8 @@ class PdfDestination implements _IPdfWrapper { /// Sets a location of the destination. set location(Offset value) { - ArgumentError.checkNotNull(value); final _Point position = _Point.fromOffset(value); - if (position != null && position != _location) { + if (position != _location) { _location = position; _initializePrimitive(); } @@ -121,7 +116,7 @@ class PdfDestination implements _IPdfWrapper { break; case PdfDestinationMode.fitH: - final PdfPage page = _page; + final PdfPage page = _page!; double value = 0; if (page._isLoadedPage) { value = page.size.height - _location.y; @@ -131,15 +126,17 @@ class PdfDestination implements _IPdfWrapper { _array._add(_PdfName(_DictionaryProperties.fitH)); _array._add(_PdfNumber(value)); break; + default: + break; } _element = _array; } _Point _pointToNativePdf(PdfPage page, _Point point) { - final PdfSection section = page._section; + final PdfSection section = page._section!; return section._pointToNativePdf(page, point); } @override - _IPdfPrimitive _element; + _IPdfPrimitive? _element; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_named_destination.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_named_destination.dart index 22513af8d..713a76420 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_named_destination.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_named_destination.dart @@ -6,7 +6,6 @@ class PdfNamedDestination implements _IPdfWrapper { /// Initializes a new instance of the [PdfNamedDestination] class /// with the title to be displayed. PdfNamedDestination(String title) { - ArgumentError.checkNotNull(title, 'The title can\'t be null'); this.title = title; _initialize(); } @@ -20,7 +19,7 @@ class PdfNamedDestination implements _IPdfWrapper { } /// Internal variable to store named destination's destination. - PdfDestination _destination; + PdfDestination? _destination; /// Internal variable to store dictinary. _PdfDictionary _dictionary = _PdfDictionary(); @@ -28,7 +27,7 @@ class PdfNamedDestination implements _IPdfWrapper { bool _isLoaded = false; /// Gets the named destination's destination. - PdfDestination get destination { + PdfDestination? get destination { if (_isLoaded) { return _obtainDestination(); } else { @@ -39,8 +38,7 @@ class PdfNamedDestination implements _IPdfWrapper { /// Sets the named destination's destination. /// The destination property has to be mentioned as multiples of 100. /// If we mention as 2, the zoom value will be 200. - set destination(PdfDestination value) { - ArgumentError.checkNotNull(value, 'The title can\'t be null'); + set destination(PdfDestination? value) { if (value != null) { _destination = value; _dictionary.setProperty(_DictionaryProperties.d, _destination); @@ -50,88 +48,88 @@ class PdfNamedDestination implements _IPdfWrapper { /// Gets the named destination title. String get title { if (_isLoaded) { - String title = ''; + String? title = ''; if (_dictionary.containsKey(_DictionaryProperties.title)) { final _PdfString str = _crossTable ._getObject(_dictionary[_DictionaryProperties.title]) as _PdfString; title = str.value; } - return title; + return title!; } else { - final _PdfString title = - _dictionary[_DictionaryProperties.title] as _PdfString; - String value; + final _PdfString? title = + _dictionary[_DictionaryProperties.title] as _PdfString?; + String? value; if (title != null) { value = title.value; } - return value; + return value!; } } /// Sets the named destination title. set title(String value) { - ArgumentError.checkNotNull(value, 'The title can\'t be null'); _dictionary[_DictionaryProperties.title] = _PdfString(value); } /// Initializes instance. void _initialize() { - _dictionary._beginSave = (Object sender, _SavePdfPrimitiveArgs ars) { + _dictionary._beginSave = (Object sender, _SavePdfPrimitiveArgs? ars) { _dictionary.setProperty(_DictionaryProperties.d, _destination); }; _dictionary.setProperty( _DictionaryProperties.s, _PdfName(_DictionaryProperties.goTo)); } - PdfDestination _obtainDestination() { + PdfDestination? _obtainDestination() { if (_dictionary.containsKey(_DictionaryProperties.d) && (_destination == null)) { - final _IPdfPrimitive obj = + final _IPdfPrimitive? obj = _crossTable._getObject(_dictionary[_DictionaryProperties.d]); - final _PdfArray destination = obj as _PdfArray; + final _PdfArray? destination = obj as _PdfArray?; if (destination != null && destination.count > 1) { - final _PdfReferenceHolder referenceHolder = - destination[0] as _PdfReferenceHolder; - PdfPage page; + final _PdfReferenceHolder? referenceHolder = + destination[0] as _PdfReferenceHolder?; + PdfPage? page; if (referenceHolder != null) { - final _PdfDictionary dictionary = - _crossTable._getObject(referenceHolder) as _PdfDictionary; + final _PdfDictionary? dictionary = + _crossTable._getObject(referenceHolder) as _PdfDictionary?; if (dictionary != null) { - page = _crossTable._document.pages._getPage(dictionary); + page = _crossTable._document!.pages._getPage(dictionary); } } - final _PdfName mode = destination[1] as _PdfName; + final _PdfName? mode = destination[1] as _PdfName?; if (mode != null) { if ((mode._name == 'FitBH' || mode._name == 'FitH') && destination.count > 2) { - final _PdfNumber top = destination[2] as _PdfNumber; + final _PdfNumber? top = destination[2] as _PdfNumber?; if (page != null) { final double topValue = - (top == null) ? 0 : page.size.height - top.value; + (top == null) ? 0 : page.size.height - top.value!; _destination = PdfDestination(page, Offset(0, topValue)); - _destination.mode = PdfDestinationMode.fitH; + _destination!.mode = PdfDestinationMode.fitH; } } else if (mode._name == 'XYZ' && destination.count > 3) { - final _PdfNumber left = destination[2] as _PdfNumber; - final _PdfNumber top = destination[3] as _PdfNumber; - _PdfNumber zoom; + final _PdfNumber? left = destination[2] as _PdfNumber?; + final _PdfNumber? top = destination[3] as _PdfNumber?; + _PdfNumber? zoom; if (destination.count > 4 && destination[4] is _PdfNumber) { zoom = destination[4] as _PdfNumber; } if (page != null) { final double topValue = - (top == null) ? 0 : page.size.height - top.value; - final double leftValue = (left == null) ? 0 : left.value; + (top == null) ? 0 : page.size.height - top.value!; + final double leftValue = + (left == null) ? 0 : left.value as double; _destination = PdfDestination(page, Offset(leftValue, topValue)); if (zoom != null) { - _destination.zoom = zoom.value.toDouble(); + _destination!.zoom = zoom.value!.toDouble(); } } } else { if (page != null && mode._name == 'Fit') { _destination = PdfDestination(page); - _destination.mode = PdfDestinationMode.fitToPage; + _destination!.mode = PdfDestinationMode.fitToPage; } } } @@ -145,7 +143,8 @@ class PdfNamedDestination implements _IPdfWrapper { _IPdfPrimitive get _element => _dictionary; @override - set _element(_IPdfPrimitive value) { + // ignore: unused_element + set _element(_IPdfPrimitive? value) { throw ArgumentError(); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_named_destination_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_named_destination_collection.dart index 6adc760f1..d6f28b162 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_named_destination_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/pdf_named_destination_collection.dart @@ -8,62 +8,62 @@ class PdfNamedDestinationCollection implements _IPdfWrapper { } PdfNamedDestinationCollection._( - _PdfDictionary dictionary, _PdfCrossTable crossTable) { + _PdfDictionary? dictionary, _PdfCrossTable? crossTable) { _dictionary = dictionary; if (crossTable != null) { _crossTable = crossTable; } if (_dictionary != null && - _dictionary.containsKey(_DictionaryProperties.dests)) { - final _PdfDictionary destination = - _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.dests]) - as _PdfDictionary; + _dictionary!.containsKey(_DictionaryProperties.dests)) { + final _PdfDictionary? destination = + _PdfCrossTable._dereference(_dictionary![_DictionaryProperties.dests]) + as _PdfDictionary?; if (destination != null && destination.containsKey(_DictionaryProperties.names)) { _addCollection(destination); } else if (destination != null && destination.containsKey(_DictionaryProperties.kids)) { - final _PdfArray kids = + final _PdfArray? kids = _PdfCrossTable._dereference(destination[_DictionaryProperties.kids]) - as _PdfArray; + as _PdfArray?; if (kids != null) { for (int i = 0; i < kids.count; i++) { _findDestination( - _PdfCrossTable._dereference(kids[i]) as _PdfDictionary); + _PdfCrossTable._dereference(kids[i]) as _PdfDictionary?); } } } } - _crossTable._document._catalog._beginSave = - (Object sender, _SavePdfPrimitiveArgs ars) { + _crossTable._document!._catalog._beginSave = + (Object sender, _SavePdfPrimitiveArgs? ars) { for (final PdfNamedDestination values in _namedCollections) { _namedDestination._add(_PdfString(values.title)); _namedDestination._add(_PdfReferenceHolder(values)); } - _dictionary.setProperty( + _dictionary!.setProperty( _DictionaryProperties.names, _PdfReferenceHolder(_namedDestination)); - if (_dictionary.containsKey(_DictionaryProperties.dests)) { - final _PdfDictionary destsDictionary = _PdfCrossTable._dereference( - _dictionary[_DictionaryProperties.dests]) as _PdfDictionary; + if (_dictionary!.containsKey(_DictionaryProperties.dests)) { + final _PdfDictionary? destsDictionary = _PdfCrossTable._dereference( + _dictionary![_DictionaryProperties.dests]) as _PdfDictionary?; if (destsDictionary != null && !destsDictionary.containsKey(_DictionaryProperties.kids)) { destsDictionary.setProperty(_DictionaryProperties.names, _PdfReferenceHolder(_namedDestination)); } } else { - _dictionary.setProperty(_DictionaryProperties.names, + _dictionary!.setProperty(_DictionaryProperties.names, _PdfReferenceHolder(_namedDestination)); } }; - _crossTable._document._catalog.modify(); + _crossTable._document!._catalog.modify(); } /// Collection of the named destinations. final List _namedCollections = []; /// Internal variable to store dictinary. - _PdfDictionary _dictionary = _PdfDictionary(); + _PdfDictionary? _dictionary = _PdfDictionary(); _PdfCrossTable _crossTable = _PdfCrossTable(); /// Array of the named destinations. @@ -82,22 +82,17 @@ class PdfNamedDestinationCollection implements _IPdfWrapper { /// Creates and adds a named destination. void add(PdfNamedDestination namedDestination) { - ArgumentError.checkNotNull( - namedDestination, 'The named destination value can\'t be null'); _namedCollections.add(namedDestination); } /// Determines whether the specified named destinations /// presents in the collection. bool contains(PdfNamedDestination namedDestination) { - ArgumentError.checkNotNull( - namedDestination, 'The named destination value can\'t be null'); return _namedCollections.contains(namedDestination); } /// Remove the specified named destination from the document. void remove(String title) { - ArgumentError.checkNotNull(title, 'The title can\'t be null'); int index = -1; for (int i = 0; i < _namedCollections.length; i++) { if (_namedCollections[i].title == title) { @@ -124,8 +119,6 @@ class PdfNamedDestinationCollection implements _IPdfWrapper { /// Inserts a new named destination at the specified index. void insert(int index, PdfNamedDestination namedDestination) { - ArgumentError.checkNotNull( - namedDestination, 'The named destination value can\'t be null'); if (index < 0 || index > count) { throw RangeError( 'The index can\'t be less then zero or greater then Count.'); @@ -135,42 +128,44 @@ class PdfNamedDestinationCollection implements _IPdfWrapper { /// Initializes instance. void _initialize() { - _dictionary._beginSave = (Object sender, _SavePdfPrimitiveArgs ars) { + _dictionary!._beginSave = (Object sender, _SavePdfPrimitiveArgs? ars) { for (final PdfNamedDestination values in _namedCollections) { _namedDestination._add(_PdfString(values.title)); _namedDestination._add(_PdfReferenceHolder(values)); } - _dictionary.setProperty( + _dictionary!.setProperty( _DictionaryProperties.names, _PdfReferenceHolder(_namedDestination)); }; } void _addCollection(_PdfDictionary namedDictionary) { - final _PdfArray elements = _PdfCrossTable._dereference( - namedDictionary[_DictionaryProperties.names]) as _PdfArray; + final _PdfArray? elements = _PdfCrossTable._dereference( + namedDictionary[_DictionaryProperties.names]) as _PdfArray?; if (elements != null) { for (int i = 1; i <= elements.count; i = i + 2) { - final _PdfReferenceHolder reference = - elements[i] as _PdfReferenceHolder; - _PdfDictionary dictionary; + _PdfReferenceHolder? reference; + if (elements[i] is _PdfReferenceHolder) { + reference = elements[i] as _PdfReferenceHolder; + } + _PdfDictionary? dictionary; if (reference != null && reference.object is _PdfArray) { dictionary = _PdfDictionary(); dictionary.setProperty(_DictionaryProperties.d, - _PdfArray(reference.object as _PdfArray)); + _PdfArray(reference.object as _PdfArray?)); } else if (reference == null && elements[i] is _PdfArray) { dictionary = _PdfDictionary(); final _PdfArray referenceArray = elements[i] as _PdfArray; dictionary.setProperty( _DictionaryProperties.d, _PdfArray(referenceArray)); } else { - dictionary = reference.object as _PdfDictionary; + dictionary = reference!.object as _PdfDictionary?; } if (dictionary != null) { final PdfNamedDestination namedDestinations = PdfNamedDestination._(dictionary, _crossTable, true); - final _PdfString title = elements[i - 1] as _PdfString; + final _PdfString? title = elements[i - 1] as _PdfString?; if (title != null) { - namedDestinations.title = title.value; + namedDestinations.title = title.value!; } _namedCollections.add(namedDestinations); } @@ -178,19 +173,19 @@ class PdfNamedDestinationCollection implements _IPdfWrapper { } } - void _findDestination(_PdfDictionary destination) { + void _findDestination(_PdfDictionary? destination) { if (destination != null && destination.containsKey(_DictionaryProperties.names)) { _addCollection(destination); } else if (destination != null && destination.containsKey(_DictionaryProperties.kids)) { - final _PdfArray kids = + final _PdfArray? kids = _PdfCrossTable._dereference(destination[_DictionaryProperties.kids]) - as _PdfArray; + as _PdfArray?; if (kids != null) { for (int i = 0; i < kids.count; i++) { _findDestination( - _PdfCrossTable._dereference(kids[i]) as _PdfDictionary); + _PdfCrossTable._dereference(kids[i]) as _PdfDictionary?); } } } @@ -198,10 +193,11 @@ class PdfNamedDestinationCollection implements _IPdfWrapper { /// Gets the element. @override - _IPdfPrimitive get _element => _dictionary; + _IPdfPrimitive? get _element => _dictionary; @override - set _element(_IPdfPrimitive value) { + // ignore: unused_element + set _element(_IPdfPrimitive? value) { throw ArgumentError(); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/brushes/pdf_brush.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/brushes/pdf_brush.dart index 34d970157..9a542c464 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/brushes/pdf_brush.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/brushes/pdf_brush.dart @@ -20,9 +20,12 @@ part of pdf; /// doc.dispose(); /// ``` abstract class PdfBrush { - bool _monitorChanges(PdfBrush brush, _PdfStreamWriter streamWriter, - Function getResources, bool saveChanges, PdfColorSpace currentColorSpace); - void _resetChanges(_PdfStreamWriter streamWriter); + bool _monitorChanges( + PdfBrush? brush, + _PdfStreamWriter? streamWriter, + Function? getResources, + bool saveChanges, + PdfColorSpace? currentColorSpace); } /// Brushes for all the standard colors. @@ -57,7 +60,7 @@ class PdfBrushes { /// ``` static PdfBrush get aliceBlue { if (_brushes.containsKey(_KnownColor.aliceBlue)) { - return _brushes[_KnownColor.aliceBlue]; + return _brushes[_KnownColor.aliceBlue]!; } else { return _getBrush(_KnownColor.aliceBlue); } @@ -79,7 +82,7 @@ class PdfBrushes { /// ``` static PdfBrush get antiqueWhite { if (_brushes.containsKey(_KnownColor.antiqueWhite)) { - return _brushes[_KnownColor.antiqueWhite]; + return _brushes[_KnownColor.antiqueWhite]!; } else { return _getBrush(_KnownColor.antiqueWhite); } @@ -100,7 +103,7 @@ class PdfBrushes { /// ``` static PdfBrush get aqua { if (_brushes.containsKey(_KnownColor.aqua)) { - return _brushes[_KnownColor.aqua]; + return _brushes[_KnownColor.aqua]!; } else { return _getBrush(_KnownColor.aqua); } @@ -121,7 +124,7 @@ class PdfBrushes { /// ``` static PdfBrush get aquamarine { if (_brushes.containsKey(_KnownColor.aquamarine)) { - return _brushes[_KnownColor.aquamarine]; + return _brushes[_KnownColor.aquamarine]!; } else { return _getBrush(_KnownColor.aquamarine); } @@ -142,7 +145,7 @@ class PdfBrushes { /// ``` static PdfBrush get azure { if (_brushes.containsKey(_KnownColor.azure)) { - return _brushes[_KnownColor.azure]; + return _brushes[_KnownColor.azure]!; } else { return _getBrush(_KnownColor.azure); } @@ -163,7 +166,7 @@ class PdfBrushes { /// ``` static PdfBrush get beige { if (_brushes.containsKey(_KnownColor.beige)) { - return _brushes[_KnownColor.beige]; + return _brushes[_KnownColor.beige]!; } else { return _getBrush(_KnownColor.beige); } @@ -184,7 +187,7 @@ class PdfBrushes { /// ``` static PdfBrush get bisque { if (_brushes.containsKey(_KnownColor.bisque)) { - return _brushes[_KnownColor.bisque]; + return _brushes[_KnownColor.bisque]!; } else { return _getBrush(_KnownColor.bisque); } @@ -205,7 +208,7 @@ class PdfBrushes { /// ``` static PdfBrush get black { if (_brushes.containsKey(_KnownColor.black)) { - return _brushes[_KnownColor.black]; + return _brushes[_KnownColor.black]!; } else { return _getBrush(_KnownColor.black); } @@ -227,7 +230,7 @@ class PdfBrushes { /// ``` static PdfBrush get blanchedAlmond { if (_brushes.containsKey(_KnownColor.blanchedAlmond)) { - return _brushes[_KnownColor.blanchedAlmond]; + return _brushes[_KnownColor.blanchedAlmond]!; } else { return _getBrush(_KnownColor.blanchedAlmond); } @@ -248,7 +251,7 @@ class PdfBrushes { /// ``` static PdfBrush get blue { if (_brushes.containsKey(_KnownColor.blue)) { - return _brushes[_KnownColor.blue]; + return _brushes[_KnownColor.blue]!; } else { return _getBrush(_KnownColor.blue); } @@ -269,7 +272,7 @@ class PdfBrushes { /// ``` static PdfBrush get blueViolet { if (_brushes.containsKey(_KnownColor.blueViolet)) { - return _brushes[_KnownColor.blueViolet]; + return _brushes[_KnownColor.blueViolet]!; } else { return _getBrush(_KnownColor.blueViolet); } @@ -290,7 +293,7 @@ class PdfBrushes { /// ``` static PdfBrush get brown { if (_brushes.containsKey(_KnownColor.brown)) { - return _brushes[_KnownColor.brown]; + return _brushes[_KnownColor.brown]!; } else { return _getBrush(_KnownColor.brown); } @@ -311,7 +314,7 @@ class PdfBrushes { /// ``` static PdfBrush get burlyWood { if (_brushes.containsKey(_KnownColor.burlyWood)) { - return _brushes[_KnownColor.burlyWood]; + return _brushes[_KnownColor.burlyWood]!; } else { return _getBrush(_KnownColor.burlyWood); } @@ -332,7 +335,7 @@ class PdfBrushes { /// ``` static PdfBrush get cadetBlue { if (_brushes.containsKey(_KnownColor.cadetBlue)) { - return _brushes[_KnownColor.cadetBlue]; + return _brushes[_KnownColor.cadetBlue]!; } else { return _getBrush(_KnownColor.cadetBlue); } @@ -353,7 +356,7 @@ class PdfBrushes { /// ``` static PdfBrush get chartreuse { if (_brushes.containsKey(_KnownColor.chartreuse)) { - return _brushes[_KnownColor.chartreuse]; + return _brushes[_KnownColor.chartreuse]!; } else { return _getBrush(_KnownColor.chartreuse); } @@ -374,7 +377,7 @@ class PdfBrushes { /// ``` static PdfBrush get chocolate { if (_brushes.containsKey(_KnownColor.chocolate)) { - return _brushes[_KnownColor.chocolate]; + return _brushes[_KnownColor.chocolate]!; } else { return _getBrush(_KnownColor.chocolate); } @@ -395,7 +398,7 @@ class PdfBrushes { /// ``` static PdfBrush get coral { if (_brushes.containsKey(_KnownColor.coral)) { - return _brushes[_KnownColor.coral]; + return _brushes[_KnownColor.coral]!; } else { return _getBrush(_KnownColor.coral); } @@ -417,7 +420,7 @@ class PdfBrushes { /// ``` static PdfBrush get cornflowerBlue { if (_brushes.containsKey(_KnownColor.cornflowerBlue)) { - return _brushes[_KnownColor.cornflowerBlue]; + return _brushes[_KnownColor.cornflowerBlue]!; } else { return _getBrush(_KnownColor.cornflowerBlue); } @@ -438,7 +441,7 @@ class PdfBrushes { /// ``` static PdfBrush get cornsilk { if (_brushes.containsKey(_KnownColor.cornsilk)) { - return _brushes[_KnownColor.cornsilk]; + return _brushes[_KnownColor.cornsilk]!; } else { return _getBrush(_KnownColor.cornsilk); } @@ -459,7 +462,7 @@ class PdfBrushes { /// ``` static PdfBrush get crimson { if (_brushes.containsKey(_KnownColor.crimson)) { - return _brushes[_KnownColor.crimson]; + return _brushes[_KnownColor.crimson]!; } else { return _getBrush(_KnownColor.crimson); } @@ -480,7 +483,7 @@ class PdfBrushes { /// ``` static PdfBrush get cyan { if (_brushes.containsKey(_KnownColor.cyan)) { - return _brushes[_KnownColor.cyan]; + return _brushes[_KnownColor.cyan]!; } else { return _getBrush(_KnownColor.cyan); } @@ -501,7 +504,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkBlue { if (_brushes.containsKey(_KnownColor.darkBlue)) { - return _brushes[_KnownColor.darkBlue]; + return _brushes[_KnownColor.darkBlue]!; } else { return _getBrush(_KnownColor.darkBlue); } @@ -522,7 +525,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkCyan { if (_brushes.containsKey(_KnownColor.darkCyan)) { - return _brushes[_KnownColor.darkCyan]; + return _brushes[_KnownColor.darkCyan]!; } else { return _getBrush(_KnownColor.darkCyan); } @@ -544,7 +547,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkGoldenrod { if (_brushes.containsKey(_KnownColor.darkGoldenrod)) { - return _brushes[_KnownColor.darkGoldenrod]; + return _brushes[_KnownColor.darkGoldenrod]!; } else { return _getBrush(_KnownColor.darkGoldenrod); } @@ -565,7 +568,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkGray { if (_brushes.containsKey(_KnownColor.darkGray)) { - return _brushes[_KnownColor.darkGray]; + return _brushes[_KnownColor.darkGray]!; } else { return _getBrush(_KnownColor.darkGray); } @@ -586,7 +589,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkGreen { if (_brushes.containsKey(_KnownColor.darkGreen)) { - return _brushes[_KnownColor.darkGreen]; + return _brushes[_KnownColor.darkGreen]!; } else { return _getBrush(_KnownColor.darkGreen); } @@ -607,7 +610,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkKhaki { if (_brushes.containsKey(_KnownColor.darkKhaki)) { - return _brushes[_KnownColor.darkKhaki]; + return _brushes[_KnownColor.darkKhaki]!; } else { return _getBrush(_KnownColor.darkKhaki); } @@ -628,7 +631,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkMagenta { if (_brushes.containsKey(_KnownColor.darkMagenta)) { - return _brushes[_KnownColor.darkMagenta]; + return _brushes[_KnownColor.darkMagenta]!; } else { return _getBrush(_KnownColor.darkMagenta); } @@ -650,7 +653,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkOliveGreen { if (_brushes.containsKey(_KnownColor.darkOliveGreen)) { - return _brushes[_KnownColor.darkOliveGreen]; + return _brushes[_KnownColor.darkOliveGreen]!; } else { return _getBrush(_KnownColor.darkOliveGreen); } @@ -671,7 +674,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkOrange { if (_brushes.containsKey(_KnownColor.darkOrange)) { - return _brushes[_KnownColor.darkOrange]; + return _brushes[_KnownColor.darkOrange]!; } else { return _getBrush(_KnownColor.darkOrange); } @@ -692,7 +695,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkOrchid { if (_brushes.containsKey(_KnownColor.darkOrchid)) { - return _brushes[_KnownColor.darkOrchid]; + return _brushes[_KnownColor.darkOrchid]!; } else { return _getBrush(_KnownColor.darkOrchid); } @@ -713,7 +716,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkRed { if (_brushes.containsKey(_KnownColor.darkRed)) { - return _brushes[_KnownColor.darkRed]; + return _brushes[_KnownColor.darkRed]!; } else { return _getBrush(_KnownColor.darkRed); } @@ -734,7 +737,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkSalmon { if (_brushes.containsKey(_KnownColor.darkSalmon)) { - return _brushes[_KnownColor.darkSalmon]; + return _brushes[_KnownColor.darkSalmon]!; } else { return _getBrush(_KnownColor.darkSalmon); } @@ -756,7 +759,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkSeaGreen { if (_brushes.containsKey(_KnownColor.darkSeaGreen)) { - return _brushes[_KnownColor.darkSeaGreen]; + return _brushes[_KnownColor.darkSeaGreen]!; } else { return _getBrush(_KnownColor.darkSeaGreen); } @@ -778,7 +781,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkSlateBlue { if (_brushes.containsKey(_KnownColor.darkSlateBlue)) { - return _brushes[_KnownColor.darkSlateBlue]; + return _brushes[_KnownColor.darkSlateBlue]!; } else { return _getBrush(_KnownColor.darkSlateBlue); } @@ -800,7 +803,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkSlateGray { if (_brushes.containsKey(_KnownColor.darkSlateGray)) { - return _brushes[_KnownColor.darkSlateGray]; + return _brushes[_KnownColor.darkSlateGray]!; } else { return _getBrush(_KnownColor.darkSlateGray); } @@ -822,7 +825,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkTurquoise { if (_brushes.containsKey(_KnownColor.darkTurquoise)) { - return _brushes[_KnownColor.darkTurquoise]; + return _brushes[_KnownColor.darkTurquoise]!; } else { return _getBrush(_KnownColor.darkTurquoise); } @@ -843,7 +846,7 @@ class PdfBrushes { /// ``` static PdfBrush get darkViolet { if (_brushes.containsKey(_KnownColor.darkViolet)) { - return _brushes[_KnownColor.darkViolet]; + return _brushes[_KnownColor.darkViolet]!; } else { return _getBrush(_KnownColor.darkViolet); } @@ -864,7 +867,7 @@ class PdfBrushes { /// ``` static PdfBrush get deepPink { if (_brushes.containsKey(_KnownColor.deepPink)) { - return _brushes[_KnownColor.deepPink]; + return _brushes[_KnownColor.deepPink]!; } else { return _getBrush(_KnownColor.deepPink); } @@ -885,7 +888,7 @@ class PdfBrushes { /// ``` static PdfBrush get deepSkyBlue { if (_brushes.containsKey(_KnownColor.deepSkyBlue)) { - return _brushes[_KnownColor.deepSkyBlue]; + return _brushes[_KnownColor.deepSkyBlue]!; } else { return _getBrush(_KnownColor.deepSkyBlue); } @@ -906,7 +909,7 @@ class PdfBrushes { /// ``` static PdfBrush get dimGray { if (_brushes.containsKey(_KnownColor.dimGray)) { - return _brushes[_KnownColor.dimGray]; + return _brushes[_KnownColor.dimGray]!; } else { return _getBrush(_KnownColor.dimGray); } @@ -927,7 +930,7 @@ class PdfBrushes { /// ``` static PdfBrush get dodgerBlue { if (_brushes.containsKey(_KnownColor.dodgerBlue)) { - return _brushes[_KnownColor.dodgerBlue]; + return _brushes[_KnownColor.dodgerBlue]!; } else { return _getBrush(_KnownColor.dodgerBlue); } @@ -948,7 +951,7 @@ class PdfBrushes { /// ``` static PdfBrush get firebrick { if (_brushes.containsKey(_KnownColor.firebrick)) { - return _brushes[_KnownColor.firebrick]; + return _brushes[_KnownColor.firebrick]!; } else { return _getBrush(_KnownColor.firebrick); } @@ -969,7 +972,7 @@ class PdfBrushes { /// ``` static PdfBrush get floralWhite { if (_brushes.containsKey(_KnownColor.floralWhite)) { - return _brushes[_KnownColor.floralWhite]; + return _brushes[_KnownColor.floralWhite]!; } else { return _getBrush(_KnownColor.floralWhite); } @@ -990,7 +993,7 @@ class PdfBrushes { /// ``` static PdfBrush get forestGreen { if (_brushes.containsKey(_KnownColor.forestGreen)) { - return _brushes[_KnownColor.forestGreen]; + return _brushes[_KnownColor.forestGreen]!; } else { return _getBrush(_KnownColor.forestGreen); } @@ -1011,7 +1014,7 @@ class PdfBrushes { /// ``` static PdfBrush get fuchsia { if (_brushes.containsKey(_KnownColor.fuchsia)) { - return _brushes[_KnownColor.fuchsia]; + return _brushes[_KnownColor.fuchsia]!; } else { return _getBrush(_KnownColor.fuchsia); } @@ -1032,7 +1035,7 @@ class PdfBrushes { /// ``` static PdfBrush get gainsboro { if (_brushes.containsKey(_KnownColor.gainsboro)) { - return _brushes[_KnownColor.gainsboro]; + return _brushes[_KnownColor.gainsboro]!; } else { return _getBrush(_KnownColor.gainsboro); } @@ -1053,7 +1056,7 @@ class PdfBrushes { /// ``` static PdfBrush get ghostWhite { if (_brushes.containsKey(_KnownColor.ghostWhite)) { - return _brushes[_KnownColor.ghostWhite]; + return _brushes[_KnownColor.ghostWhite]!; } else { return _getBrush(_KnownColor.ghostWhite); } @@ -1074,7 +1077,7 @@ class PdfBrushes { /// ``` static PdfBrush get gold { if (_brushes.containsKey(_KnownColor.gold)) { - return _brushes[_KnownColor.gold]; + return _brushes[_KnownColor.gold]!; } else { return _getBrush(_KnownColor.gold); } @@ -1095,7 +1098,7 @@ class PdfBrushes { /// ``` static PdfBrush get goldenrod { if (_brushes.containsKey(_KnownColor.goldenrod)) { - return _brushes[_KnownColor.goldenrod]; + return _brushes[_KnownColor.goldenrod]!; } else { return _getBrush(_KnownColor.goldenrod); } @@ -1116,7 +1119,7 @@ class PdfBrushes { /// ``` static PdfBrush get gray { if (_brushes.containsKey(_KnownColor.gray)) { - return _brushes[_KnownColor.gray]; + return _brushes[_KnownColor.gray]!; } else { return _getBrush(_KnownColor.gray); } @@ -1137,7 +1140,7 @@ class PdfBrushes { /// ``` static PdfBrush get green { if (_brushes.containsKey(_KnownColor.green)) { - return _brushes[_KnownColor.green]; + return _brushes[_KnownColor.green]!; } else { return _getBrush(_KnownColor.green); } @@ -1158,7 +1161,7 @@ class PdfBrushes { /// ``` static PdfBrush get greenYellow { if (_brushes.containsKey(_KnownColor.greenYellow)) { - return _brushes[_KnownColor.greenYellow]; + return _brushes[_KnownColor.greenYellow]!; } else { return _getBrush(_KnownColor.greenYellow); } @@ -1179,7 +1182,7 @@ class PdfBrushes { /// ``` static PdfBrush get honeydew { if (_brushes.containsKey(_KnownColor.honeydew)) { - return _brushes[_KnownColor.honeydew]; + return _brushes[_KnownColor.honeydew]!; } else { return _getBrush(_KnownColor.honeydew); } @@ -1200,7 +1203,7 @@ class PdfBrushes { /// ``` static PdfBrush get hotPink { if (_brushes.containsKey(_KnownColor.hotPink)) { - return _brushes[_KnownColor.hotPink]; + return _brushes[_KnownColor.hotPink]!; } else { return _getBrush(_KnownColor.hotPink); } @@ -1221,7 +1224,7 @@ class PdfBrushes { /// ``` static PdfBrush get indianRed { if (_brushes.containsKey(_KnownColor.indianRed)) { - return _brushes[_KnownColor.indianRed]; + return _brushes[_KnownColor.indianRed]!; } else { return _getBrush(_KnownColor.indianRed); } @@ -1242,7 +1245,7 @@ class PdfBrushes { /// ``` static PdfBrush get indigo { if (_brushes.containsKey(_KnownColor.indigo)) { - return _brushes[_KnownColor.indigo]; + return _brushes[_KnownColor.indigo]!; } else { return _getBrush(_KnownColor.indigo); } @@ -1263,7 +1266,7 @@ class PdfBrushes { /// ``` static PdfBrush get ivory { if (_brushes.containsKey(_KnownColor.ivory)) { - return _brushes[_KnownColor.ivory]; + return _brushes[_KnownColor.ivory]!; } else { return _getBrush(_KnownColor.ivory); } @@ -1284,7 +1287,7 @@ class PdfBrushes { /// ``` static PdfBrush get khaki { if (_brushes.containsKey(_KnownColor.khaki)) { - return _brushes[_KnownColor.khaki]; + return _brushes[_KnownColor.khaki]!; } else { return _getBrush(_KnownColor.khaki); } @@ -1305,7 +1308,7 @@ class PdfBrushes { /// ``` static PdfBrush get lavender { if (_brushes.containsKey(_KnownColor.lavender)) { - return _brushes[_KnownColor.lavender]; + return _brushes[_KnownColor.lavender]!; } else { return _getBrush(_KnownColor.lavender); } @@ -1327,7 +1330,7 @@ class PdfBrushes { /// ``` static PdfBrush get lavenderBlush { if (_brushes.containsKey(_KnownColor.lavenderBlush)) { - return _brushes[_KnownColor.lavenderBlush]; + return _brushes[_KnownColor.lavenderBlush]!; } else { return _getBrush(_KnownColor.lavenderBlush); } @@ -1348,7 +1351,7 @@ class PdfBrushes { /// ``` static PdfBrush get lawnGreen { if (_brushes.containsKey(_KnownColor.lawnGreen)) { - return _brushes[_KnownColor.lawnGreen]; + return _brushes[_KnownColor.lawnGreen]!; } else { return _getBrush(_KnownColor.lawnGreen); } @@ -1370,7 +1373,7 @@ class PdfBrushes { /// ``` static PdfBrush get lemonChiffon { if (_brushes.containsKey(_KnownColor.lemonChiffon)) { - return _brushes[_KnownColor.lemonChiffon]; + return _brushes[_KnownColor.lemonChiffon]!; } else { return _getBrush(_KnownColor.lemonChiffon); } @@ -1391,7 +1394,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightBlue { if (_brushes.containsKey(_KnownColor.lightBlue)) { - return _brushes[_KnownColor.lightBlue]; + return _brushes[_KnownColor.lightBlue]!; } else { return _getBrush(_KnownColor.lightBlue); } @@ -1412,7 +1415,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightCoral { if (_brushes.containsKey(_KnownColor.lightCoral)) { - return _brushes[_KnownColor.lightCoral]; + return _brushes[_KnownColor.lightCoral]!; } else { return _getBrush(_KnownColor.lightCoral); } @@ -1433,7 +1436,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightCyan { if (_brushes.containsKey(_KnownColor.lightCyan)) { - return _brushes[_KnownColor.lightCyan]; + return _brushes[_KnownColor.lightCyan]!; } else { return _getBrush(_KnownColor.lightCyan); } @@ -1455,7 +1458,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightGoldenrodYellow { if (_brushes.containsKey(_KnownColor.lightGoldenrodYellow)) { - return _brushes[_KnownColor.lightGoldenrodYellow]; + return _brushes[_KnownColor.lightGoldenrodYellow]!; } else { return _getBrush(_KnownColor.lightGoldenrodYellow); } @@ -1476,7 +1479,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightGray { if (_brushes.containsKey(_KnownColor.lightGray)) { - return _brushes[_KnownColor.lightGray]; + return _brushes[_KnownColor.lightGray]!; } else { return _getBrush(_KnownColor.lightGray); } @@ -1497,7 +1500,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightGreen { if (_brushes.containsKey(_KnownColor.lightGreen)) { - return _brushes[_KnownColor.lightGreen]; + return _brushes[_KnownColor.lightGreen]!; } else { return _getBrush(_KnownColor.lightGreen); } @@ -1518,7 +1521,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightPink { if (_brushes.containsKey(_KnownColor.lightPink)) { - return _brushes[_KnownColor.lightPink]; + return _brushes[_KnownColor.lightPink]!; } else { return _getBrush(_KnownColor.lightPink); } @@ -1539,7 +1542,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightSalmon { if (_brushes.containsKey(_KnownColor.lightSalmon)) { - return _brushes[_KnownColor.lightSalmon]; + return _brushes[_KnownColor.lightSalmon]!; } else { return _getBrush(_KnownColor.lightSalmon); } @@ -1561,7 +1564,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightSeaGreen { if (_brushes.containsKey(_KnownColor.lightSeaGreen)) { - return _brushes[_KnownColor.lightSeaGreen]; + return _brushes[_KnownColor.lightSeaGreen]!; } else { return _getBrush(_KnownColor.lightSeaGreen); } @@ -1583,7 +1586,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightSkyBlue { if (_brushes.containsKey(_KnownColor.lightSkyBlue)) { - return _brushes[_KnownColor.lightSkyBlue]; + return _brushes[_KnownColor.lightSkyBlue]!; } else { return _getBrush(_KnownColor.lightSkyBlue); } @@ -1605,7 +1608,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightSlateGray { if (_brushes.containsKey(_KnownColor.lightSlateGray)) { - return _brushes[_KnownColor.lightSlateGray]; + return _brushes[_KnownColor.lightSlateGray]!; } else { return _getBrush(_KnownColor.lightSlateGray); } @@ -1627,7 +1630,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightSteelBlue { if (_brushes.containsKey(_KnownColor.lightSteelBlue)) { - return _brushes[_KnownColor.lightSteelBlue]; + return _brushes[_KnownColor.lightSteelBlue]!; } else { return _getBrush(_KnownColor.lightSteelBlue); } @@ -1648,7 +1651,7 @@ class PdfBrushes { /// ``` static PdfBrush get lightYellow { if (_brushes.containsKey(_KnownColor.lightYellow)) { - return _brushes[_KnownColor.lightYellow]; + return _brushes[_KnownColor.lightYellow]!; } else { return _getBrush(_KnownColor.lightYellow); } @@ -1669,7 +1672,7 @@ class PdfBrushes { /// ``` static PdfBrush get lime { if (_brushes.containsKey(_KnownColor.lime)) { - return _brushes[_KnownColor.lime]; + return _brushes[_KnownColor.lime]!; } else { return _getBrush(_KnownColor.lime); } @@ -1690,7 +1693,7 @@ class PdfBrushes { /// ``` static PdfBrush get limeGreen { if (_brushes.containsKey(_KnownColor.limeGreen)) { - return _brushes[_KnownColor.limeGreen]; + return _brushes[_KnownColor.limeGreen]!; } else { return _getBrush(_KnownColor.limeGreen); } @@ -1711,7 +1714,7 @@ class PdfBrushes { /// ``` static PdfBrush get linen { if (_brushes.containsKey(_KnownColor.linen)) { - return _brushes[_KnownColor.linen]; + return _brushes[_KnownColor.linen]!; } else { return _getBrush(_KnownColor.linen); } @@ -1732,7 +1735,7 @@ class PdfBrushes { /// ``` static PdfBrush get magenta { if (_brushes.containsKey(_KnownColor.magenta)) { - return _brushes[_KnownColor.magenta]; + return _brushes[_KnownColor.magenta]!; } else { return _getBrush(_KnownColor.magenta); } @@ -1753,7 +1756,7 @@ class PdfBrushes { /// ``` static PdfBrush get maroon { if (_brushes.containsKey(_KnownColor.maroon)) { - return _brushes[_KnownColor.maroon]; + return _brushes[_KnownColor.maroon]!; } else { return _getBrush(_KnownColor.maroon); } @@ -1775,7 +1778,7 @@ class PdfBrushes { /// ``` static PdfBrush get mediumAquamarine { if (_brushes.containsKey(_KnownColor.mediumAquamarine)) { - return _brushes[_KnownColor.mediumAquamarine]; + return _brushes[_KnownColor.mediumAquamarine]!; } else { return _getBrush(_KnownColor.mediumAquamarine); } @@ -1796,7 +1799,7 @@ class PdfBrushes { /// ``` static PdfBrush get mediumBlue { if (_brushes.containsKey(_KnownColor.mediumBlue)) { - return _brushes[_KnownColor.mediumBlue]; + return _brushes[_KnownColor.mediumBlue]!; } else { return _getBrush(_KnownColor.mediumBlue); } @@ -1818,7 +1821,7 @@ class PdfBrushes { /// ``` static PdfBrush get mediumOrchid { if (_brushes.containsKey(_KnownColor.mediumOrchid)) { - return _brushes[_KnownColor.mediumOrchid]; + return _brushes[_KnownColor.mediumOrchid]!; } else { return _getBrush(_KnownColor.mediumOrchid); } @@ -1840,7 +1843,7 @@ class PdfBrushes { /// ``` static PdfBrush get mediumPurple { if (_brushes.containsKey(_KnownColor.mediumPurple)) { - return _brushes[_KnownColor.mediumPurple]; + return _brushes[_KnownColor.mediumPurple]!; } else { return _getBrush(_KnownColor.mediumPurple); } @@ -1862,7 +1865,7 @@ class PdfBrushes { /// ``` static PdfBrush get mediumSeaGreen { if (_brushes.containsKey(_KnownColor.mediumSeaGreen)) { - return _brushes[_KnownColor.mediumSeaGreen]; + return _brushes[_KnownColor.mediumSeaGreen]!; } else { return _getBrush(_KnownColor.mediumSeaGreen); } @@ -1884,7 +1887,7 @@ class PdfBrushes { /// ``` static PdfBrush get mediumSlateBlue { if (_brushes.containsKey(_KnownColor.mediumSlateBlue)) { - return _brushes[_KnownColor.mediumSlateBlue]; + return _brushes[_KnownColor.mediumSlateBlue]!; } else { return _getBrush(_KnownColor.mediumSlateBlue); } @@ -1906,7 +1909,7 @@ class PdfBrushes { /// ``` static PdfBrush get mediumSpringGreen { if (_brushes.containsKey(_KnownColor.mediumSpringGreen)) { - return _brushes[_KnownColor.mediumSpringGreen]; + return _brushes[_KnownColor.mediumSpringGreen]!; } else { return _getBrush(_KnownColor.mediumSpringGreen); } @@ -1928,7 +1931,7 @@ class PdfBrushes { /// ``` static PdfBrush get mediumTurquoise { if (_brushes.containsKey(_KnownColor.mediumTurquoise)) { - return _brushes[_KnownColor.mediumTurquoise]; + return _brushes[_KnownColor.mediumTurquoise]!; } else { return _getBrush(_KnownColor.mediumTurquoise); } @@ -1950,7 +1953,7 @@ class PdfBrushes { /// ``` static PdfBrush get mediumVioletRed { if (_brushes.containsKey(_KnownColor.mediumVioletRed)) { - return _brushes[_KnownColor.mediumVioletRed]; + return _brushes[_KnownColor.mediumVioletRed]!; } else { return _getBrush(_KnownColor.mediumVioletRed); } @@ -1972,7 +1975,7 @@ class PdfBrushes { /// ``` static PdfBrush get midnightBlue { if (_brushes.containsKey(_KnownColor.midnightBlue)) { - return _brushes[_KnownColor.midnightBlue]; + return _brushes[_KnownColor.midnightBlue]!; } else { return _getBrush(_KnownColor.midnightBlue); } @@ -1993,7 +1996,7 @@ class PdfBrushes { /// ``` static PdfBrush get mintCream { if (_brushes.containsKey(_KnownColor.mintCream)) { - return _brushes[_KnownColor.mintCream]; + return _brushes[_KnownColor.mintCream]!; } else { return _getBrush(_KnownColor.mintCream); } @@ -2014,7 +2017,7 @@ class PdfBrushes { /// ``` static PdfBrush get mistyRose { if (_brushes.containsKey(_KnownColor.mistyRose)) { - return _brushes[_KnownColor.mistyRose]; + return _brushes[_KnownColor.mistyRose]!; } else { return _getBrush(_KnownColor.mistyRose); } @@ -2035,7 +2038,7 @@ class PdfBrushes { /// ``` static PdfBrush get moccasin { if (_brushes.containsKey(_KnownColor.moccasin)) { - return _brushes[_KnownColor.moccasin]; + return _brushes[_KnownColor.moccasin]!; } else { return _getBrush(_KnownColor.moccasin); } @@ -2056,7 +2059,7 @@ class PdfBrushes { /// ``` static PdfBrush get navajoWhite { if (_brushes.containsKey(_KnownColor.navajoWhite)) { - return _brushes[_KnownColor.navajoWhite]; + return _brushes[_KnownColor.navajoWhite]!; } else { return _getBrush(_KnownColor.navajoWhite); } @@ -2077,7 +2080,7 @@ class PdfBrushes { /// ``` static PdfBrush get navy { if (_brushes.containsKey(_KnownColor.navy)) { - return _brushes[_KnownColor.navy]; + return _brushes[_KnownColor.navy]!; } else { return _getBrush(_KnownColor.navy); } @@ -2098,7 +2101,7 @@ class PdfBrushes { /// ``` static PdfBrush get oldLace { if (_brushes.containsKey(_KnownColor.oldLace)) { - return _brushes[_KnownColor.oldLace]; + return _brushes[_KnownColor.oldLace]!; } else { return _getBrush(_KnownColor.oldLace); } @@ -2119,7 +2122,7 @@ class PdfBrushes { /// ``` static PdfBrush get olive { if (_brushes.containsKey(_KnownColor.olive)) { - return _brushes[_KnownColor.olive]; + return _brushes[_KnownColor.olive]!; } else { return _getBrush(_KnownColor.olive); } @@ -2140,7 +2143,7 @@ class PdfBrushes { /// ``` static PdfBrush get oliveDrab { if (_brushes.containsKey(_KnownColor.oliveDrab)) { - return _brushes[_KnownColor.oliveDrab]; + return _brushes[_KnownColor.oliveDrab]!; } else { return _getBrush(_KnownColor.oliveDrab); } @@ -2161,7 +2164,7 @@ class PdfBrushes { /// ``` static PdfBrush get orange { if (_brushes.containsKey(_KnownColor.orange)) { - return _brushes[_KnownColor.orange]; + return _brushes[_KnownColor.orange]!; } else { return _getBrush(_KnownColor.orange); } @@ -2182,7 +2185,7 @@ class PdfBrushes { /// ``` static PdfBrush get orangeRed { if (_brushes.containsKey(_KnownColor.orangeRed)) { - return _brushes[_KnownColor.orangeRed]; + return _brushes[_KnownColor.orangeRed]!; } else { return _getBrush(_KnownColor.orangeRed); } @@ -2203,7 +2206,7 @@ class PdfBrushes { /// ``` static PdfBrush get orchid { if (_brushes.containsKey(_KnownColor.orchid)) { - return _brushes[_KnownColor.orchid]; + return _brushes[_KnownColor.orchid]!; } else { return _getBrush(_KnownColor.orchid); } @@ -2225,7 +2228,7 @@ class PdfBrushes { /// ``` static PdfBrush get paleGoldenrod { if (_brushes.containsKey(_KnownColor.paleGoldenrod)) { - return _brushes[_KnownColor.paleGoldenrod]; + return _brushes[_KnownColor.paleGoldenrod]!; } else { return _getBrush(_KnownColor.paleGoldenrod); } @@ -2246,7 +2249,7 @@ class PdfBrushes { /// ``` static PdfBrush get paleGreen { if (_brushes.containsKey(_KnownColor.paleGreen)) { - return _brushes[_KnownColor.paleGreen]; + return _brushes[_KnownColor.paleGreen]!; } else { return _getBrush(_KnownColor.paleGreen); } @@ -2268,7 +2271,7 @@ class PdfBrushes { /// ``` static PdfBrush get paleTurquoise { if (_brushes.containsKey(_KnownColor.paleTurquoise)) { - return _brushes[_KnownColor.paleTurquoise]; + return _brushes[_KnownColor.paleTurquoise]!; } else { return _getBrush(_KnownColor.paleTurquoise); } @@ -2290,7 +2293,7 @@ class PdfBrushes { /// ``` static PdfBrush get paleVioletRed { if (_brushes.containsKey(_KnownColor.paleVioletRed)) { - return _brushes[_KnownColor.paleVioletRed]; + return _brushes[_KnownColor.paleVioletRed]!; } else { return _getBrush(_KnownColor.paleVioletRed); } @@ -2311,7 +2314,7 @@ class PdfBrushes { /// ``` static PdfBrush get papayaWhip { if (_brushes.containsKey(_KnownColor.papayaWhip)) { - return _brushes[_KnownColor.papayaWhip]; + return _brushes[_KnownColor.papayaWhip]!; } else { return _getBrush(_KnownColor.papayaWhip); } @@ -2332,7 +2335,7 @@ class PdfBrushes { /// ``` static PdfBrush get peachPuff { if (_brushes.containsKey(_KnownColor.peachPuff)) { - return _brushes[_KnownColor.peachPuff]; + return _brushes[_KnownColor.peachPuff]!; } else { return _getBrush(_KnownColor.peachPuff); } @@ -2353,7 +2356,7 @@ class PdfBrushes { /// ``` static PdfBrush get peru { if (_brushes.containsKey(_KnownColor.peru)) { - return _brushes[_KnownColor.peru]; + return _brushes[_KnownColor.peru]!; } else { return _getBrush(_KnownColor.peru); } @@ -2374,7 +2377,7 @@ class PdfBrushes { /// ``` static PdfBrush get pink { if (_brushes.containsKey(_KnownColor.pink)) { - return _brushes[_KnownColor.pink]; + return _brushes[_KnownColor.pink]!; } else { return _getBrush(_KnownColor.pink); } @@ -2395,7 +2398,7 @@ class PdfBrushes { /// ``` static PdfBrush get plum { if (_brushes.containsKey(_KnownColor.plum)) { - return _brushes[_KnownColor.plum]; + return _brushes[_KnownColor.plum]!; } else { return _getBrush(_KnownColor.plum); } @@ -2416,7 +2419,7 @@ class PdfBrushes { /// ``` static PdfBrush get powderBlue { if (_brushes.containsKey(_KnownColor.powderBlue)) { - return _brushes[_KnownColor.powderBlue]; + return _brushes[_KnownColor.powderBlue]!; } else { return _getBrush(_KnownColor.powderBlue); } @@ -2437,7 +2440,7 @@ class PdfBrushes { /// ``` static PdfBrush get purple { if (_brushes.containsKey(_KnownColor.purple)) { - return _brushes[_KnownColor.purple]; + return _brushes[_KnownColor.purple]!; } else { return _getBrush(_KnownColor.purple); } @@ -2458,7 +2461,7 @@ class PdfBrushes { /// ``` static PdfBrush get red { if (_brushes.containsKey(_KnownColor.red)) { - return _brushes[_KnownColor.red]; + return _brushes[_KnownColor.red]!; } else { return _getBrush(_KnownColor.red); } @@ -2479,7 +2482,7 @@ class PdfBrushes { /// ``` static PdfBrush get rosyBrown { if (_brushes.containsKey(_KnownColor.rosyBrown)) { - return _brushes[_KnownColor.rosyBrown]; + return _brushes[_KnownColor.rosyBrown]!; } else { return _getBrush(_KnownColor.rosyBrown); } @@ -2500,7 +2503,7 @@ class PdfBrushes { /// ``` static PdfBrush get royalBlue { if (_brushes.containsKey(_KnownColor.royalBlue)) { - return _brushes[_KnownColor.royalBlue]; + return _brushes[_KnownColor.royalBlue]!; } else { return _getBrush(_KnownColor.royalBlue); } @@ -2521,7 +2524,7 @@ class PdfBrushes { /// ``` static PdfBrush get saddleBrown { if (_brushes.containsKey(_KnownColor.saddleBrown)) { - return _brushes[_KnownColor.saddleBrown]; + return _brushes[_KnownColor.saddleBrown]!; } else { return _getBrush(_KnownColor.saddleBrown); } @@ -2542,7 +2545,7 @@ class PdfBrushes { /// ``` static PdfBrush get salmon { if (_brushes.containsKey(_KnownColor.salmon)) { - return _brushes[_KnownColor.salmon]; + return _brushes[_KnownColor.salmon]!; } else { return _getBrush(_KnownColor.salmon); } @@ -2563,7 +2566,7 @@ class PdfBrushes { /// ``` static PdfBrush get sandyBrown { if (_brushes.containsKey(_KnownColor.sandyBrown)) { - return _brushes[_KnownColor.sandyBrown]; + return _brushes[_KnownColor.sandyBrown]!; } else { return _getBrush(_KnownColor.sandyBrown); } @@ -2584,7 +2587,7 @@ class PdfBrushes { /// ``` static PdfBrush get seaGreen { if (_brushes.containsKey(_KnownColor.seaGreen)) { - return _brushes[_KnownColor.seaGreen]; + return _brushes[_KnownColor.seaGreen]!; } else { return _getBrush(_KnownColor.seaGreen); } @@ -2605,7 +2608,7 @@ class PdfBrushes { /// ``` static PdfBrush get seaShell { if (_brushes.containsKey(_KnownColor.seaShell)) { - return _brushes[_KnownColor.seaShell]; + return _brushes[_KnownColor.seaShell]!; } else { return _getBrush(_KnownColor.seaShell); } @@ -2626,7 +2629,7 @@ class PdfBrushes { /// ``` static PdfBrush get sienna { if (_brushes.containsKey(_KnownColor.sienna)) { - return _brushes[_KnownColor.sienna]; + return _brushes[_KnownColor.sienna]!; } else { return _getBrush(_KnownColor.sienna); } @@ -2647,7 +2650,7 @@ class PdfBrushes { /// ``` static PdfBrush get silver { if (_brushes.containsKey(_KnownColor.silver)) { - return _brushes[_KnownColor.silver]; + return _brushes[_KnownColor.silver]!; } else { return _getBrush(_KnownColor.silver); } @@ -2668,7 +2671,7 @@ class PdfBrushes { /// ``` static PdfBrush get skyBlue { if (_brushes.containsKey(_KnownColor.skyBlue)) { - return _brushes[_KnownColor.skyBlue]; + return _brushes[_KnownColor.skyBlue]!; } else { return _getBrush(_KnownColor.skyBlue); } @@ -2689,7 +2692,7 @@ class PdfBrushes { /// ``` static PdfBrush get slateBlue { if (_brushes.containsKey(_KnownColor.slateBlue)) { - return _brushes[_KnownColor.slateBlue]; + return _brushes[_KnownColor.slateBlue]!; } else { return _getBrush(_KnownColor.slateBlue); } @@ -2710,7 +2713,7 @@ class PdfBrushes { /// ``` static PdfBrush get slateGray { if (_brushes.containsKey(_KnownColor.slateGray)) { - return _brushes[_KnownColor.slateGray]; + return _brushes[_KnownColor.slateGray]!; } else { return _getBrush(_KnownColor.slateGray); } @@ -2731,7 +2734,7 @@ class PdfBrushes { /// ``` static PdfBrush get snow { if (_brushes.containsKey(_KnownColor.snow)) { - return _brushes[_KnownColor.snow]; + return _brushes[_KnownColor.snow]!; } else { return _getBrush(_KnownColor.snow); } @@ -2752,7 +2755,7 @@ class PdfBrushes { /// ``` static PdfBrush get springGreen { if (_brushes.containsKey(_KnownColor.springGreen)) { - return _brushes[_KnownColor.springGreen]; + return _brushes[_KnownColor.springGreen]!; } else { return _getBrush(_KnownColor.springGreen); } @@ -2773,7 +2776,7 @@ class PdfBrushes { /// ``` static PdfBrush get steelBlue { if (_brushes.containsKey(_KnownColor.steelBlue)) { - return _brushes[_KnownColor.steelBlue]; + return _brushes[_KnownColor.steelBlue]!; } else { return _getBrush(_KnownColor.steelBlue); } @@ -2794,7 +2797,7 @@ class PdfBrushes { /// ``` static PdfBrush get tan { if (_brushes.containsKey(_KnownColor.tan)) { - return _brushes[_KnownColor.tan]; + return _brushes[_KnownColor.tan]!; } else { return _getBrush(_KnownColor.tan); } @@ -2815,7 +2818,7 @@ class PdfBrushes { /// ``` static PdfBrush get teal { if (_brushes.containsKey(_KnownColor.teal)) { - return _brushes[_KnownColor.teal]; + return _brushes[_KnownColor.teal]!; } else { return _getBrush(_KnownColor.teal); } @@ -2836,7 +2839,7 @@ class PdfBrushes { /// ``` static PdfBrush get thistle { if (_brushes.containsKey(_KnownColor.thistle)) { - return _brushes[_KnownColor.thistle]; + return _brushes[_KnownColor.thistle]!; } else { return _getBrush(_KnownColor.thistle); } @@ -2857,7 +2860,7 @@ class PdfBrushes { /// ``` static PdfBrush get tomato { if (_brushes.containsKey(_KnownColor.tomato)) { - return _brushes[_KnownColor.tomato]; + return _brushes[_KnownColor.tomato]!; } else { return _getBrush(_KnownColor.tomato); } @@ -2878,7 +2881,7 @@ class PdfBrushes { /// ``` static PdfBrush get transparent { if (_brushes.containsKey(_KnownColor.transparent)) { - return _brushes[_KnownColor.transparent]; + return _brushes[_KnownColor.transparent]!; } else { return _getBrush(_KnownColor.transparent); } @@ -2899,7 +2902,7 @@ class PdfBrushes { /// ``` static PdfBrush get turquoise { if (_brushes.containsKey(_KnownColor.turquoise)) { - return _brushes[_KnownColor.turquoise]; + return _brushes[_KnownColor.turquoise]!; } else { return _getBrush(_KnownColor.turquoise); } @@ -2920,7 +2923,7 @@ class PdfBrushes { /// ``` static PdfBrush get violet { if (_brushes.containsKey(_KnownColor.violet)) { - return _brushes[_KnownColor.violet]; + return _brushes[_KnownColor.violet]!; } else { return _getBrush(_KnownColor.violet); } @@ -2941,7 +2944,7 @@ class PdfBrushes { /// ``` static PdfBrush get wheat { if (_brushes.containsKey(_KnownColor.wheat)) { - return _brushes[_KnownColor.wheat]; + return _brushes[_KnownColor.wheat]!; } else { return _getBrush(_KnownColor.wheat); } @@ -2962,7 +2965,7 @@ class PdfBrushes { /// ``` static PdfBrush get white { if (_brushes.containsKey(_KnownColor.white)) { - return _brushes[_KnownColor.white]; + return _brushes[_KnownColor.white]!; } else { return _getBrush(_KnownColor.white); } @@ -2983,7 +2986,7 @@ class PdfBrushes { /// ``` static PdfBrush get whiteSmoke { if (_brushes.containsKey(_KnownColor.whiteSmoke)) { - return _brushes[_KnownColor.whiteSmoke]; + return _brushes[_KnownColor.whiteSmoke]!; } else { return _getBrush(_KnownColor.whiteSmoke); } @@ -3004,7 +3007,7 @@ class PdfBrushes { /// ``` static PdfBrush get yellow { if (_brushes.containsKey(_KnownColor.yellow)) { - return _brushes[_KnownColor.yellow]; + return _brushes[_KnownColor.yellow]!; } else { return _getBrush(_KnownColor.yellow); } @@ -3025,7 +3028,7 @@ class PdfBrushes { /// ``` static PdfBrush get yellowGreen { if (_brushes.containsKey(_KnownColor.yellowGreen)) { - return _brushes[_KnownColor.yellowGreen]; + return _brushes[_KnownColor.yellowGreen]!; } else { return _getBrush(_KnownColor.yellowGreen); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/brushes/pdf_solid_brush.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/brushes/pdf_solid_brush.dart index 895e5d9a8..f8d7f3aa3 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/brushes/pdf_solid_brush.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/brushes/pdf_solid_brush.dart @@ -5,14 +5,14 @@ class PdfSolidBrush implements PdfBrush { //Constructor /// Initializes a new instance of the [PdfSolidBrush] class. PdfSolidBrush(PdfColor color) { - this.color = color ?? PdfColor(0, 0, 0); + this.color = color; } //Fields final PdfColorSpace _colorSpace = PdfColorSpace.rgb; /// Indicates the color of the [PdfSolidBrush]. - PdfColor color; + late PdfColor color; @override bool operator ==(Object other) { @@ -28,39 +28,38 @@ class PdfSolidBrush implements PdfBrush { @override bool _monitorChanges( - PdfBrush brush, - _PdfStreamWriter streamWriter, - Function getResources, + PdfBrush? brush, + _PdfStreamWriter? streamWriter, + Function? getResources, bool saveChanges, - PdfColorSpace currentColorSpace) { - ArgumentError.checkNotNull(getResources); - ArgumentError.checkNotNull(streamWriter); + PdfColorSpace? currentColorSpace) { bool diff = false; - if (brush == null) { - diff = true; - streamWriter._setColorAndSpace(color, currentColorSpace, false); - } else if (brush != this) { - final PdfSolidBrush solidBrush = brush as PdfSolidBrush; - if (solidBrush != null) { - if (solidBrush.color != color || - solidBrush._colorSpace != currentColorSpace) { - diff = true; + if (getResources != null && streamWriter != null) { + if (brush == null) { + diff = true; + streamWriter._setColorAndSpace(color, currentColorSpace, false); + } else if (brush != this) { + final PdfSolidBrush? solidBrush = brush as PdfSolidBrush; + if (solidBrush != null) { + if (solidBrush.color != color || + solidBrush._colorSpace != currentColorSpace) { + diff = true; + streamWriter._setColorAndSpace(color, currentColorSpace, false); + } else if (solidBrush._colorSpace == currentColorSpace && + currentColorSpace == PdfColorSpace.rgb) { + diff = true; + streamWriter._setColorAndSpace(color, currentColorSpace, false); + } + } else { + brush._resetChanges(streamWriter); streamWriter._setColorAndSpace(color, currentColorSpace, false); - } else if (solidBrush._colorSpace == currentColorSpace && - currentColorSpace == PdfColorSpace.rgb) { diff = true; - streamWriter._setColorAndSpace(color, currentColorSpace, false); } - } else { - brush._resetChanges(streamWriter); - streamWriter._setColorAndSpace(color, currentColorSpace, false); - diff = true; } } return diff; } - @override void _resetChanges(_PdfStreamWriter streamWriter) { streamWriter._setColorAndSpace(PdfColor(0, 0, 0), PdfColorSpace.rgb, false); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/enums.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/enums.dart index e26422e5d..5662fa151 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/enums.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/enums.dart @@ -1,6 +1,18 @@ part of pdf; /// Specifies the type of horizontal text alignment. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString( +/// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), +/// format: PdfStringFormat(alignment: PdfTextAlignment.left)); +/// //Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` enum PdfTextAlignment { /// Specifies the text is aligned to Left. left, @@ -16,6 +28,18 @@ enum PdfTextAlignment { } /// Specifies the type of Vertical alignment. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString( +/// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), +/// format: PdfStringFormat(lineAlignment: PdfVerticalAlignment.top)); +/// //Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` enum PdfVerticalAlignment { /// Specifies the element is aligned to Top. top, @@ -28,6 +52,18 @@ enum PdfVerticalAlignment { } /// Represents the text rendering direction. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString( +/// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), +/// format: PdfStringFormat(textDirection: PdfTextAlignment.none)); +/// //Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` enum PdfTextDirection { /// Specifies the default text order. none, @@ -40,6 +76,20 @@ enum PdfTextDirection { } /// Defines set of color spaces. +/// +/// ```dart +/// //Creates a new PDF document. +/// PdfDocument doc = PdfDocument(); +/// //Create PDF graphics for the page +/// doc.pages.add().graphics +/// ..colorSpace = PdfColorSpace.grayScale +/// ..drawRectangle( +/// brush: PdfBrushes.red, bounds: Rect.fromLTWH(0, 0, 515, 762)); +/// //Saves the document. +/// List bytes = doc.save(); +/// //Dispose the document. +/// doc.dispose(); +/// ``` enum PdfColorSpace { /// RGB color space. rgb, @@ -73,6 +123,20 @@ class _TextRenderingMode { } /// Possible dash styles of the pen. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawRectangle( +/// pen: PdfPen(PdfColor(255, 0, 0), +/// dashStyle: PdfDashStyle.custom, lineCap: PdfLineCap.round) +/// ..dashPattern = [4, 2, 1, 3], +/// bounds: Rect.fromLTWH(0, 0, 200, 100)); +/// /Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` enum PdfDashStyle { /// Solid line. solid, @@ -94,6 +158,20 @@ enum PdfDashStyle { } /// Specifies the corner style of the shapes. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawRectangle( +/// pen: PdfPen(PdfColor(255, 0, 0), +/// dashStyle: PdfDashStyle.custom, lineJoin: PdfLineJoin.bevel) +/// ..dashPattern = [4, 2, 1, 3], +/// bounds: Rect.fromLTWH(0, 0, 200, 100)); +/// /Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` enum PdfLineJoin { /// The outer edges for the two segments are extended /// until they meet at an angle. @@ -110,6 +188,20 @@ enum PdfLineJoin { } /// Specifies the line cap style to be used at the ends of the lines. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawRectangle( +/// pen: PdfPen(PdfColor(255, 0, 0), +/// dashStyle: PdfDashStyle.custom, lineCap: PdfLineCap.round) +/// ..dashPattern = [4, 2, 1, 3], +/// bounds: Rect.fromLTWH(0, 0, 200, 100)); +/// /Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` enum PdfLineCap { /// The stroke is squared off at the endpoint of the path. /// There is no projection beyond the end of the path. @@ -125,6 +217,21 @@ enum PdfLineCap { } /// Specifies how the shapes are filled. +/// +/// ```dart +/// //Creates a new PDF document. +/// PdfDocument doc = PdfDocument(); +/// //Create PDF graphics for the page +/// doc.pages.add().graphics +/// //set clip. +/// ..setClip(bounds: Rect.fromLTWH(0, 0, 50, 12), mode: PdfFillMode.alternate) +/// ..drawString('Hello world!', PdfStandardFont(PdfFontFamily.helvetica, 12), +/// pen: PdfPens.red); +/// //Saves the document. +/// List bytes = doc.save(); +/// //Dispose the document. +/// doc.dispose(); +/// ``` enum PdfFillMode { /// Nonzero winding number rule of determining "insideness" of point. winding, @@ -134,6 +241,21 @@ enum PdfFillMode { } /// Specifies the blend mode for transparency. +/// +/// ```dart +/// //Creates a new PDF document. +/// PdfDocument doc = PdfDocument(); +/// //Create PDF graphics for the page +/// doc.pages.add().graphics +/// ..setTransparency(0.5, alphaBrush: 0.5, mode: PdfBlendMode.hardLight) +/// ..drawString('Hello world!', +/// PdfStandardFont(PdfFontFamily.helvetica, 12, style: PdfFontStyle.bold), +/// brush: PdfBrushes.red, pen: PdfPens.black); +/// //Saves the document. +/// List bytes = doc.save(); +/// //Dispose the document. +/// doc.dispose(); +/// ``` enum PdfBlendMode { /// Selects the source color, ignoring the backdrop. normal, diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/element_layouter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/element_layouter.dart index 675416160..834d4f472 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/element_layouter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/element_layouter.dart @@ -5,29 +5,26 @@ abstract class _ElementLayouter { //Constructor /// Initializes a new instance of the [ElementLayouter] class. _ElementLayouter(PdfLayoutElement element) { - ArgumentError.checkNotNull(element); _element = element; } //Fields /// Layout the element. - PdfLayoutElement _element; + PdfLayoutElement? _element; //Properties /// Gets element`s layout. - PdfLayoutElement get element => _element; + PdfLayoutElement? get element => _element; //Implementation - PdfLayoutResult _layout(_PdfLayoutParams param) { - ArgumentError.checkNotNull(param); + PdfLayoutResult? _layout(_PdfLayoutParams param) { return _layoutInternal(param); } - PdfLayoutResult _layoutInternal(_PdfLayoutParams param); - PdfPage _getNextPage(PdfPage currentPage) { - ArgumentError.checkNotNull(currentPage); - final PdfSection section = currentPage._section; - PdfPage nextPage; + PdfLayoutResult? _layoutInternal(_PdfLayoutParams param); + PdfPage? _getNextPage(PdfPage currentPage) { + final PdfSection section = currentPage._section!; + PdfPage? nextPage; final int index = section._indexOf(currentPage); if (index == section._count - 1) { nextPage = section.pages.add(); @@ -38,11 +35,10 @@ abstract class _ElementLayouter { } _Rectangle _getPaginateBounds(_PdfLayoutParams param) { - ArgumentError.checkNotNull(param); - return param.format._boundsSet - ? _Rectangle.fromRect(param.format.paginateBounds) + return param.format!._boundsSet + ? _Rectangle.fromRect(param.format!.paginateBounds) : _Rectangle( - param.bounds.x, 0, param.bounds.width, param.bounds.height); + param.bounds!.x, 0, param.bounds!.width, param.bounds!.height); } } @@ -51,9 +47,9 @@ class PdfLayoutFormat { //Constructor /// Initializes a new instance of the [PdfLayoutFormat] class. PdfLayoutFormat( - {PdfLayoutType layoutType, - PdfLayoutBreakType breakType, - Rect paginateBounds}) { + {PdfLayoutType? layoutType, + PdfLayoutBreakType? breakType, + Rect? paginateBounds}) { this.breakType = breakType ?? PdfLayoutBreakType.fitPage; this.layoutType = layoutType ?? PdfLayoutType.paginate; if (paginateBounds != null) { @@ -68,7 +64,6 @@ class PdfLayoutFormat { /// Initializes a new instance of the [PdfLayoutFormat] class /// from an existing format. PdfLayoutFormat.fromFormat(PdfLayoutFormat baseFormat) { - ArgumentError.checkNotNull(baseFormat); breakType = baseFormat.breakType; layoutType = baseFormat.layoutType; _paginateBounds = baseFormat._paginateBounds; @@ -77,26 +72,25 @@ class PdfLayoutFormat { //Fields /// Layout type of the element. - PdfLayoutType layoutType; + PdfLayoutType layoutType = PdfLayoutType.paginate; /// Gets the bounds on the next page. Rect get paginateBounds => _paginateBounds.rect; /// Sets the bounds on the next page. set paginateBounds(Rect value) { - ArgumentError.checkNotNull(value); _paginateBounds = _Rectangle.fromRect(value); _boundsSet = true; } /// Break type of the element. - PdfLayoutBreakType breakType; + PdfLayoutBreakType breakType = PdfLayoutBreakType.fitPage; /// Indicates whether PaginateBounds were set and should be used or not. - bool _boundsSet; + late bool _boundsSet; /// Bounds for the paginating. - _Rectangle _paginateBounds; + late _Rectangle _paginateBounds; } /// Represents the layouting result format. @@ -111,10 +105,10 @@ class PdfLayoutResult { //Fields /// The last page where the element was drawn. - PdfPage _page; + late PdfPage _page; /// The bounds of the element on the last page where it was drawn. - Rect _bounds; + late Rect _bounds; //Properties /// Gets the last page where the element was drawn. @@ -127,11 +121,11 @@ class PdfLayoutResult { /// Represents the layouting parameters. class _PdfLayoutParams { /// Gets and Sets Start lay outing page. - PdfPage page; + PdfPage? page; /// Gets and Sets Lay outing bounds. - _Rectangle bounds; + _Rectangle? bounds; /// Gets and Sets Layout settings. - PdfLayoutFormat format; + PdfLayoutFormat? format; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/layout_element.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/layout_element.dart index 0a149206a..ec4570631 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/layout_element.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/layout_element.dart @@ -15,11 +15,19 @@ abstract class PdfLayoutElement { /// /// If both graphics and page provide in the arguments /// then page takes more precedence than graphics - PdfLayoutResult draw( - {PdfGraphics graphics, - PdfPage page, - Rect bounds, - PdfLayoutFormat format}) { + PdfLayoutResult? draw( + {PdfGraphics? graphics, + PdfPage? page, + Rect? bounds, + PdfLayoutFormat? format}) { + return _draw(graphics, page, bounds, format); + } + + //Implementation + PdfLayoutResult? _layout(_PdfLayoutParams param); + + PdfLayoutResult? _draw(PdfGraphics? graphics, PdfPage? page, Rect? bounds, + PdfLayoutFormat? format) { if (page != null) { final _PdfLayoutParams param = _PdfLayoutParams(); param.page = page; @@ -30,15 +38,15 @@ abstract class PdfLayoutElement { } else if (graphics != null) { final _Rectangle rectangle = bounds != null ? _Rectangle.fromRect(bounds) : _Rectangle.empty; - final bool _bNeedSave = rectangle.x != 0 || rectangle.y != 0; - PdfGraphicsState gState; - if (_bNeedSave) { - gState = graphics.save(); + if (rectangle.x != 0 || rectangle.y != 0) { + final PdfGraphicsState gState = graphics.save(); graphics.translateTransform(rectangle.x, rectangle.y); - } - _drawInternal(graphics, rectangle); - if (_bNeedSave) { + rectangle.x = 0; + rectangle.y = 0; + _drawInternal(graphics, rectangle); graphics.restore(gState); + } else { + _drawInternal(graphics, rectangle); } return null; } else { @@ -46,17 +54,15 @@ abstract class PdfLayoutElement { } } - //Implementation - PdfLayoutResult _layout(_PdfLayoutParams param); void _onBeginPageLayout(BeginPageLayoutArgs e) { if (beginPageLayout != null) { - beginPageLayout(this, e); + beginPageLayout!(this, e); } } void _onEndPageLayout(EndPageLayoutArgs e) { if (endPageLayout != null) { - endPageLayout(this, e); + endPageLayout!(this, e); } } @@ -64,16 +70,16 @@ abstract class PdfLayoutElement { //Events /// Raises before the element should be printed on the page. - BeginPageLayoutCallback beginPageLayout; + BeginPageLayoutCallback? beginPageLayout; /// Raises after the element was printed on the page. - EndPageLayoutCallback endPageLayout; + EndPageLayoutCallback? endPageLayout; } /// Represents the base class for classes that contain event data, and provides /// a value to use for events, once completed the text lay outing on the page. class EndTextPageLayoutArgs extends EndPageLayoutArgs { - /// Initializes a new instance of the [EndTextPageLayoutEventArgs] class + /// Initializes a new instance of the [EndTextPageLayoutArgs] class /// with the specified [PdfTextLayoutResult]. EndTextPageLayoutArgs(PdfTextLayoutResult result) : super(result); } @@ -83,14 +89,13 @@ class EndPageLayoutArgs extends PdfCancelArgs { //Constructor /// Initialize an instance of [EndPageLayoutArgs]. EndPageLayoutArgs(PdfLayoutResult result) { - ArgumentError.checkNotNull(result); _result = result; } //Fields /// The next page for lay outing. - PdfPage nextPage; - PdfLayoutResult _result; + PdfPage? nextPage; + late PdfLayoutResult _result; //Properties /// Gets the lay outing result of the page. @@ -102,17 +107,14 @@ class BeginPageLayoutArgs extends PdfCancelArgs { //Constructor /// Initialize an instance of [BeginPageLayoutArgs]. BeginPageLayoutArgs(Rect bounds, PdfPage page) { - ArgumentError.checkNotNull(page); - if (bounds != null) { - _bounds = _Rectangle.fromRect(bounds); - } + _bounds = _Rectangle.fromRect(bounds); _page = page; } //Fields /// The bounds of the lay outing on the page. - _Rectangle _bounds; - PdfPage _page; + late _Rectangle _bounds; + late PdfPage _page; //Properties /// Gets the page where the lay outing should start. @@ -123,9 +125,7 @@ class BeginPageLayoutArgs extends PdfCancelArgs { /// Sets value that indicates the lay outing bounds on the page. set bounds(Rect value) { - if (value != null) { - _bounds = _Rectangle.fromRect(value); - } + _bounds = _Rectangle.fromRect(value); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/pdf_shape_element.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/pdf_shape_element.dart index fd9bd393e..83c36e2a6 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/pdf_shape_element.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/pdf_shape_element.dart @@ -3,14 +3,14 @@ part of pdf; /// Base class for the main shapes. abstract class PdfShapeElement extends PdfLayoutElement { // fields - PdfPen _pen; - PdfBrush _brush; + PdfPen? _pen; + PdfBrush? _brush; // properties /// Gets a pen that will be used to draw the element. PdfPen get pen { _pen ??= PdfPens.black; - return _pen; + return _pen!; } /// Sets a pen that will be used to draw the element. @@ -20,16 +20,15 @@ abstract class PdfShapeElement extends PdfLayoutElement { // implementation PdfPen _obtainPen() { - return (_pen == null) ? PdfPens.black : _pen; + return (_pen == null) ? PdfPens.black : _pen!; } @override - PdfLayoutResult _layout(_PdfLayoutParams param) { - ArgumentError.checkNotNull(param, 'param'); + PdfLayoutResult? _layout(_PdfLayoutParams param) { final _ShapeLayouter layouter = _ShapeLayouter(this); - final PdfLayoutResult result = layouter._layout(param); + final PdfLayoutResult? result = layouter._layout(param); return result; } - _Rectangle _getBoundsInternal(); + _Rectangle? _getBoundsInternal(); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/shape_layouter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/shape_layouter.dart index b639e00ed..882cf6ba1 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/shape_layouter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/shape_layouter.dart @@ -7,17 +7,16 @@ class _ShapeLayouter extends _ElementLayouter { // properties /// Gets shape element. @override - PdfShapeElement get element => super.element; + PdfShapeElement? get element => super.element as PdfShapeElement?; // implementation @override - PdfLayoutResult _layoutInternal(_PdfLayoutParams param) { - ArgumentError.checkNotNull(param, 'param'); - PdfPage currentPage = param.page; - _Rectangle currentBounds = param.bounds; - _Rectangle shapeLayoutBounds = _Rectangle.empty; + PdfLayoutResult? _layoutInternal(_PdfLayoutParams param) { + PdfPage? currentPage = param.page; + _Rectangle? currentBounds = param.bounds; + _Rectangle? shapeLayoutBounds = _Rectangle.empty; if (element is PdfImage) { - shapeLayoutBounds = element._getBoundsInternal(); + shapeLayoutBounds = element!._getBoundsInternal(); } PdfLayoutResult result; _ShapeLayoutResult pageResult = _ShapeLayoutResult(); @@ -25,21 +24,21 @@ class _ShapeLayouter extends _ElementLayouter { while (true) { // ref is there so implement in proper way final Map returnedValue = - _raiseBeforePageLayout(currentPage, currentBounds.rect); + _raiseBeforePageLayout(currentPage, currentBounds!.rect); bool cancel = returnedValue['cancel']; currentBounds = _Rectangle.fromRect(returnedValue['bounds']); - EndPageLayoutArgs endArgs; + EndPageLayoutArgs? endArgs; if (!cancel) { - pageResult = - _layoutOnPage(currentPage, currentBounds, shapeLayoutBounds, param); + pageResult = _layoutOnPage( + currentPage!, currentBounds, shapeLayoutBounds!, param); endArgs = _raiseEndPageLayout(pageResult); cancel = (endArgs == null) ? false : endArgs.cancel; } if (!pageResult._end && !cancel) { currentBounds = _getPaginateBounds(param); - shapeLayoutBounds = _getNextShapeBounds(shapeLayoutBounds, pageResult); + shapeLayoutBounds = _getNextShapeBounds(shapeLayoutBounds!, pageResult); currentPage = (endArgs == null || endArgs.nextPage == null) - ? _getNextPage(currentPage) + ? _getNextPage(currentPage!) : endArgs.nextPage; } else { result = _getLayoutResult(pageResult); @@ -50,12 +49,12 @@ class _ShapeLayouter extends _ElementLayouter { } Map _raiseBeforePageLayout( - PdfPage currentPage, Rect currentBounds) { + PdfPage? currentPage, Rect currentBounds) { bool cancel = false; - if (element._raiseBeginPageLayout) { + if (element!._raiseBeginPageLayout) { final BeginPageLayoutArgs args = - BeginPageLayoutArgs(currentBounds, currentPage); - element._onBeginPageLayout(args); + BeginPageLayoutArgs(currentBounds, currentPage!); + element!._onBeginPageLayout(args); cancel = args.cancel; currentBounds = args.bounds; } @@ -67,14 +66,12 @@ class _ShapeLayouter extends _ElementLayouter { _Rectangle currentBounds, _Rectangle shapeLayoutBounds, _PdfLayoutParams param) { - ArgumentError.checkNotNull(param, 'param'); - ArgumentError.checkNotNull(currentPage, 'currentPage'); final _ShapeLayoutResult result = _ShapeLayoutResult(); currentBounds = _checkCorrectCurrentBounds( currentPage, currentBounds, shapeLayoutBounds, param); final bool fitToPage = _fitsToBounds(currentBounds, shapeLayoutBounds); final bool canDraw = - !(param.format.breakType == PdfLayoutBreakType.fitElement && + !(param.format!.breakType == PdfLayoutBreakType.fitElement && !fitToPage && currentPage == param.page); bool shapeFinished = false; @@ -88,7 +85,7 @@ class _ShapeLayouter extends _ElementLayouter { currentBounds.height.toInt() >= shapeLayoutBounds.height.toInt(); } result._end = - shapeFinished || param.format.layoutType == PdfLayoutType.onePage; + shapeFinished || param.format!.layoutType == PdfLayoutType.onePage; result._page = currentPage; return result; } @@ -98,7 +95,6 @@ class _ShapeLayouter extends _ElementLayouter { _Rectangle currentBounds, _Rectangle shapeLayoutBounds, _PdfLayoutParams param) { - ArgumentError.checkNotNull(currentPage, 'currentPage'); final Size pageSize = currentPage.graphics.clientSize; currentBounds.width = (currentBounds.width > 0) ? currentBounds.width @@ -124,11 +120,10 @@ class _ShapeLayouter extends _ElementLayouter { } void _drawShape(PdfGraphics g, Rect currentBounds, _Rectangle drawRectangle) { - ArgumentError.checkNotNull(g, 'g'); final PdfGraphicsState gState = g.save(); try { g.setClip(bounds: currentBounds); - element.draw( + element!.draw( graphics: g, bounds: Rect.fromLTWH(drawRectangle.x, drawRectangle.y, 0, 0)); } finally { @@ -147,24 +142,24 @@ class _ShapeLayouter extends _ElementLayouter { _Rectangle _getNextShapeBounds( _Rectangle shapeLayoutBounds, _ShapeLayoutResult pageResult) { - shapeLayoutBounds.y += pageResult._bounds.height; - shapeLayoutBounds.height -= pageResult._bounds.height; + shapeLayoutBounds.y += pageResult._bounds!.height; + shapeLayoutBounds.height -= pageResult._bounds!.height; return shapeLayoutBounds; } - EndPageLayoutArgs _raiseEndPageLayout(_ShapeLayoutResult pageResult) { - EndPageLayoutArgs args; - if (element._raisePageLayouted) { + EndPageLayoutArgs? _raiseEndPageLayout(_ShapeLayoutResult pageResult) { + EndPageLayoutArgs? args; + if (element!._raisePageLayouted) { final PdfLayoutResult res = _getLayoutResult(pageResult); args = EndPageLayoutArgs(res); - element._onEndPageLayout(args); + element!._onEndPageLayout(args); } return args; } PdfLayoutResult _getLayoutResult(_ShapeLayoutResult pageResult) { final PdfLayoutResult result = - PdfLayoutResult._(pageResult._page, pageResult._bounds); + PdfLayoutResult._(pageResult._page!, pageResult._bounds!); return result; } } @@ -176,7 +171,7 @@ class _ShapeLayoutResult { _end = false; } //Fields - PdfPage _page; - Rect _bounds; - bool _end; + PdfPage? _page; + Rect? _bounds; + late bool _end; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/text_layouter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/text_layouter.dart index ae1f8ca73..92d256fa0 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/text_layouter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/text_layouter.dart @@ -5,50 +5,47 @@ class _TextLayouter extends _ElementLayouter { _TextLayouter(PdfTextElement element) : super(element); //Fields - PdfStringFormat _format; + PdfStringFormat? _format; //Properties @override - PdfTextElement get element => - super.element is PdfTextElement ? super.element : null; + PdfTextElement? get element => + super.element is PdfTextElement ? super.element as PdfTextElement? : null; //Implementation _TextPageLayoutResult _layoutOnPage(String text, PdfPage currentPage, _Rectangle currentBounds, _PdfLayoutParams param) { - ArgumentError.checkNotNull(text); - ArgumentError.checkNotNull(currentPage); - ArgumentError.checkNotNull(param); final _TextPageLayoutResult result = _TextPageLayoutResult(); result.remainder = text; result.page = currentPage; currentBounds = _checkCorrectBounds(currentPage, currentBounds); if (currentBounds.height < 0) { - currentPage = _getNextPage(currentPage); + currentPage = _getNextPage(currentPage)!; result.page = currentPage; currentBounds = _Rectangle( currentBounds.x, 0, currentBounds.width, currentBounds.height); } final _PdfStringLayouter layouter = _PdfStringLayouter(); final _PdfStringLayoutResult stringResult = layouter._layout( - text, element.font, _format, + text, element!.font, _format, bounds: currentBounds, pageHeight: currentPage.getClientSize().height); final bool textFinished = - stringResult._remainder == null || stringResult._remainder.isEmpty; + stringResult._remainder == null || stringResult._remainder!.isEmpty; final bool doesntFit = - param.format.breakType == PdfLayoutBreakType.fitElement && + param.format!.breakType == PdfLayoutBreakType.fitElement && currentPage == param.page && !textFinished; final bool canDraw = !(doesntFit || stringResult._isEmpty); if (canDraw) { final PdfGraphics graphics = currentPage.graphics; - graphics._drawStringLayoutResult(stringResult, element.font, element.pen, - element._obtainBrush(), currentBounds, _format); + graphics._drawStringLayoutResult(stringResult, element!.font, + element!.pen, element!._obtainBrush(), currentBounds, _format); final _LineInfo lineInfo = - stringResult._lines[stringResult._lineCount - 1]; + stringResult._lines![stringResult._lineCount - 1]; result.lastLineBounds = graphics._getLineBounds( stringResult._lineCount - 1, stringResult, - element.font, + element!.font, currentBounds, _format); result.bounds = @@ -60,14 +57,14 @@ class _TextLayouter extends _ElementLayouter { _getTextPageBounds(currentPage, currentBounds, stringResult); } final bool stopLayouting = stringResult._isEmpty && - ((param.format.breakType != PdfLayoutBreakType.fitElement) && - (param.format.layoutType != PdfLayoutType.paginate) && + ((param.format!.breakType != PdfLayoutBreakType.fitElement) && + (param.format!.layoutType != PdfLayoutType.paginate) && canDraw) || - (param.format.breakType == PdfLayoutBreakType.fitElement && + (param.format!.breakType == PdfLayoutBreakType.fitElement && currentPage != param.page); result.end = textFinished || stopLayouting || - param.format.layoutType == PdfLayoutType.onePage; + param.format!.layoutType == PdfLayoutType.onePage; return result; } @@ -82,11 +79,9 @@ class _TextLayouter extends _ElementLayouter { Rect _getTextPageBounds(PdfPage currentPage, _Rectangle currentBounds, _PdfStringLayoutResult stringResult) { - ArgumentError.checkNotNull(currentPage); - ArgumentError.checkNotNull(stringResult); final _Size textSize = stringResult._size; - double x = currentBounds.x; - double y = currentBounds.y; + double? x = currentBounds.x; + double? y = currentBounds.y; final double width = (currentBounds.width > 0) ? currentBounds.width : textSize.width; final double height = textSize.height; @@ -108,10 +103,11 @@ class _TextLayouter extends _ElementLayouter { void _checkCorectStringFormat(_LineInfo lineInfo) { if (_format != null) { - _format._firstLineIndent = lineInfo._lineType & - _PdfStringLayouter._getLineTypeValue(_LineType.newLineBreak) > + _format!._firstLineIndent = lineInfo._lineType & + _PdfStringLayouter._getLineTypeValue( + _LineType.newLineBreak)! > 0 - ? element.stringFormat._firstLineIndent + ? element!.stringFormat!._firstLineIndent : 0; } } @@ -121,58 +117,56 @@ class _TextLayouter extends _ElementLayouter { pageResult.remainder, pageResult.lastLineBounds); } - bool _raiseBeforePageLayout(PdfPage currentPage, Rect currentBounds) { + bool _raiseBeforePageLayout(PdfPage? currentPage, Rect currentBounds) { bool cancel = false; - if (element._raiseBeginPageLayout) { + if (element!._raiseBeginPageLayout) { final BeginPageLayoutArgs args = - BeginPageLayoutArgs(currentBounds, currentPage); - element._onBeginPageLayout(args); + BeginPageLayoutArgs(currentBounds, currentPage!); + element!._onBeginPageLayout(args); cancel = args.cancel; currentBounds = args.bounds; } return cancel; } - EndTextPageLayoutArgs _raisePageLayouted(_TextPageLayoutResult pageResult) { - EndTextPageLayoutArgs args; - if (element._raisePageLayouted) { + EndTextPageLayoutArgs? _raisePageLayouted(_TextPageLayoutResult pageResult) { + EndTextPageLayoutArgs? args; + if (element!._raisePageLayouted) { args = EndTextPageLayoutArgs(_getLayoutResult(pageResult)); - element._onEndPageLayout(args); + element!._onEndPageLayout(args); } - return args; } //Override methods @override PdfLayoutResult _layoutInternal(_PdfLayoutParams param) { - ArgumentError.checkNotNull(param); if (element != null) { - _format = (element.stringFormat != null) ? element.stringFormat : null; - PdfPage currentPage = param.page; - _Rectangle currentBounds = param.bounds; - String text = element.text; + _format = (element!.stringFormat != null) ? element!.stringFormat : null; + PdfPage? currentPage = param.page; + _Rectangle? currentBounds = param.bounds; + String? text = element!.text; PdfTextLayoutResult result; _TextPageLayoutResult pageResult = _TextPageLayoutResult(); pageResult.page = currentPage; pageResult.remainder = text; while (true) { - bool cancel = _raiseBeforePageLayout(currentPage, currentBounds.rect); - EndTextPageLayoutArgs endArgs; + bool cancel = _raiseBeforePageLayout(currentPage, currentBounds!.rect); + EndTextPageLayoutArgs? endArgs; if (!cancel) { - pageResult = _layoutOnPage(text, currentPage, currentBounds, param); + pageResult = _layoutOnPage(text!, currentPage!, currentBounds, param); endArgs = _raisePageLayouted(pageResult); cancel = endArgs == null ? false : endArgs.cancel; } if (!pageResult.end && !cancel) { - if (element._isPdfTextElement) { + if (element!._isPdfTextElement) { result = _getLayoutResult(pageResult); break; } currentBounds = _getPaginateBounds(param); text = pageResult.remainder; currentPage = endArgs == null || endArgs.nextPage == null - ? _getNextPage(currentPage) + ? _getNextPage(currentPage!) : endArgs.nextPage; } else { result = _getLayoutResult(pageResult); @@ -190,23 +184,23 @@ class _TextLayouter extends _ElementLayouter { class PdfTextLayoutResult extends PdfLayoutResult { //Contructor PdfTextLayoutResult._( - PdfPage page, Rect bounds, String remainder, Rect lastLineBounds) - : super._(page, bounds) { + PdfPage? page, Rect? bounds, String? remainder, Rect? lastLineBounds) + : super._(page!, bounds!) { _remainder = remainder; _lastLineBounds = lastLineBounds; } //Fields - String _remainder; - Rect _lastLineBounds; + String? _remainder; + Rect? _lastLineBounds; //Properties /// Gets a value that contains the text that was not printed. - String get remainder => _remainder; + String? get remainder => _remainder; /// Gets a value that indicates the bounds of the last line /// that was printed on the page. - Rect get lastLineBounds => _lastLineBounds; + Rect? get lastLineBounds => _lastLineBounds; } class _TextPageLayoutResult { @@ -215,9 +209,9 @@ class _TextPageLayoutResult { end = false; lastLineBounds = const Rect.fromLTWH(0, 0, 0, 0); } - PdfPage page; - Rect bounds; - bool end; - String remainder; - Rect lastLineBounds; + PdfPage? page; + late Rect bounds; + late bool end; + String? remainder; + late Rect lastLineBounds; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_bezier_curve.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_bezier_curve.dart index 2352c2c2e..e37de5c0c 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_bezier_curve.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_bezier_curve.dart @@ -7,8 +7,10 @@ class PdfBezierCurve extends PdfShapeElement { /// with the specified [PdfPen] and [Offset] structure. PdfBezierCurve(Offset startPoint, Offset firstControlPoint, Offset secondControlPoint, Offset endPoint, - {PdfPen pen}) { - super.pen = pen; + {PdfPen? pen}) { + if (pen != null) { + super.pen = pen; + } this.startPoint = startPoint; this.firstControlPoint = firstControlPoint; this.secondControlPoint = secondControlPoint; @@ -27,9 +29,7 @@ class PdfBezierCurve extends PdfShapeElement { /// Sets the starting point of the curve set startPoint(Offset value) { - if (value != null) { - _startPoint = _Point.fromOffset(value); - } + _startPoint = _Point.fromOffset(value); } /// Gets the first control point of the curve. @@ -37,9 +37,7 @@ class PdfBezierCurve extends PdfShapeElement { /// Sets the first control point of the curve. set firstControlPoint(Offset value) { - if (value != null) { - _firstControlPoint = _Point.fromOffset(value); - } + _firstControlPoint = _Point.fromOffset(value); } /// Gets the second control point of the curve @@ -47,9 +45,7 @@ class PdfBezierCurve extends PdfShapeElement { /// Sets the second control point of the curve set secondControlPoint(Offset value) { - if (value != null) { - _secondControlPoint = _Point.fromOffset(value); - } + _secondControlPoint = _Point.fromOffset(value); } /// Gets the ending point of the curve. @@ -57,23 +53,20 @@ class PdfBezierCurve extends PdfShapeElement { /// Sets the ending point of the curve. set endPoint(Offset value) { - if (value != null) { - _endPoint = _Point.fromOffset(value); - } + _endPoint = _Point.fromOffset(value); } // implementation @override void _drawInternal(PdfGraphics graphics, _Rectangle bounds) { - ArgumentError.checkNotNull(graphics, 'graphics'); graphics.drawBezier( startPoint, firstControlPoint, secondControlPoint, endPoint, pen: _obtainPen()); } @override - _Rectangle _getBoundsInternal() { + _Rectangle? _getBoundsInternal() { return null; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_path.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_path.dart index 2538f5c6d..7a3d23327 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_path.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_path.dart @@ -7,34 +7,36 @@ class PdfPath extends PdfShapeElement { /// Initializes a new instance of the [PdfPath] class /// with pen, brush, fillMode, points and pathTypes PdfPath( - {PdfPen pen, - PdfBrush brush, - PdfFillMode fillMode, - List points, - List pathTypes}) { - super.pen = pen; - if (points != null && pathTypes != null) { + {PdfPen? pen, + PdfBrush? brush, + PdfFillMode fillMode = PdfFillMode.winding, + List points = const [], + List? pathTypes}) { + if (pen != null) { + super.pen = pen; + } + if (points.isNotEmpty && pathTypes != null) { addPath(points, pathTypes); } - _fillMode = (fillMode == null) ? _fillMode : fillMode; + _fillMode = fillMode; this.brush = brush; } // fields - final List _points = []; + List _points = []; final List<_PathPointType> _pathTypes = <_PathPointType>[]; final bool _isBeziers3 = false; bool _bStartFigure = true; - PdfFillMode _fillMode = PdfFillMode.winding; + late PdfFillMode _fillMode; // properties /// Gets the brush of the element - PdfBrush get brush { + PdfBrush? get brush { return _brush; } /// Sets the brush of the element - set brush(PdfBrush value) { + set brush(PdfBrush? value) { if (value != null) { _brush = value; } @@ -66,8 +68,6 @@ class PdfPath extends PdfShapeElement { /// Appends the path specified by the points and their types to this one. void addPath(List pathPoints, List pathTypes) { - ArgumentError.checkNotNull(pathPoints, 'pathPoints'); - ArgumentError.checkNotNull(pathTypes, 'pathTypes'); final int count = pathPoints.length; if (count != pathTypes.length) { throw ArgumentError.value( @@ -79,27 +79,23 @@ class PdfPath extends PdfShapeElement { /// Adds a line void addLine(Offset point1, Offset point2) { - final List points = []; - points.add(point1.dx); - points.add(point1.dy); - points.add(point2.dx); - points.add(point2.dy); - _addPoints(points, _PathPointType.line); + _addPoints([point1.dx, point1.dy, point2.dx, point2.dy], + _PathPointType.line); } /// Adds a rectangle void addRectangle(Rect bounds) { - final List points = []; startFigure(); - points.add(bounds.left); - points.add(bounds.top); - points.add(bounds.left + bounds.width); - points.add(bounds.top); - points.add(bounds.left + bounds.width); - points.add(bounds.top + bounds.height); - points.add(bounds.left); - points.add(bounds.top + bounds.height); - _addPoints(points, _PathPointType.line); + _addPoints([ + bounds.left, + bounds.top, + bounds.left + bounds.width, + bounds.top, + bounds.left + bounds.width, + bounds.top + bounds.height, + bounds.left, + bounds.top + bounds.height + ], _PathPointType.line); closeFigure(); } @@ -173,7 +169,6 @@ class PdfPath extends PdfShapeElement { /// Closes the last figure. void closeFigure() { if (_points.isNotEmpty) { - // _pathTypes[_points.length - 1] = _PathPointType.closeSubpath; _points.add(const Offset(0, 0)); _pathTypes.add(_PathPointType.closeSubpath); } @@ -233,7 +228,6 @@ class PdfPath extends PdfShapeElement { @override void _drawInternal(PdfGraphics graphics, _Rectangle bounds) { - ArgumentError.checkNotNull(graphics, 'graphics'); graphics.drawPath(this, pen: pen, brush: brush); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_template.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_template.dart index dea762391..af05aa386 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_template.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_template.dart @@ -1,17 +1,43 @@ part of pdf; /// Represents PDF Template object. +/// ```dart +/// Create a new PDF template and draw graphical content like text, images, and more. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawPdfTemplate( +/// PdfTemplate(100, 50) +/// ..graphics.drawString( +/// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 14), +/// brush: PdfBrushes.black, bounds: Rect.fromLTWH(5, 5, 0, 0)), +/// Offset(0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfTemplate implements _IPdfWrapper { /// Initializes a new instance of the [PdfTemplate] class. + /// ```dart + /// Create a new PDF template and draw graphical content like text, images, and more. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawPdfTemplate( + /// PdfTemplate(100, 50) + /// ..graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 14), + /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(5, 5, 0, 0)), + /// Offset(0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfTemplate(double width, double height) { - if (width != null && height != null) { - _content = _PdfStream(); - _setSize(width, height); - _content[_DictionaryProperties.type] = - _PdfName(_DictionaryProperties.xObject); - _content[_DictionaryProperties.subtype] = - _PdfName(_DictionaryProperties.form); - } + _content = _PdfStream(); + _setSize(width, height); + _content[_DictionaryProperties.type] = + _PdfName(_DictionaryProperties.xObject); + _content[_DictionaryProperties.subtype] = + _PdfName(_DictionaryProperties.form); } PdfTemplate._fromRect(Rect bounds) { @@ -24,10 +50,9 @@ class PdfTemplate implements _IPdfWrapper { } PdfTemplate._fromPdfStream(_PdfStream template) { - ArgumentError.checkNotNull(template, 'Template stream can\'t be null'); _content = template; final _IPdfPrimitive obj = - _PdfCrossTable._dereference(_content[_DictionaryProperties.bBox]); + _PdfCrossTable._dereference(_content[_DictionaryProperties.bBox])!; final _Rectangle rect = (obj as _PdfArray).toRectangle(); _size = rect.size; _isReadonly = true; @@ -37,9 +62,9 @@ class PdfTemplate implements _IPdfWrapper { _PdfDictionary resources, bool isLoadedPage) : super() { if (size == Size.zero) { - ArgumentError('The size of the new PdfTemplate can\'t be empty.'); + throw ArgumentError.value( + size, 'size', 'The size of the new PdfTemplate cannot be empty.'); } - ArgumentError.checkNotNull(stream, 'stream'); _content = _PdfStream(); if (origin.dx < 0 || origin.dy < 0) { _setSize(size.width, size.height, origin); @@ -47,37 +72,72 @@ class PdfTemplate implements _IPdfWrapper { _setSize(size.width, size.height); } _initialize(); - _content._dataStream.addAll(stream); - if (resources != null) { - _content[_DictionaryProperties.resources] = _PdfDictionary(resources); - _resources = _PdfResources(resources); - } + _content._dataStream!.addAll(stream); + _content[_DictionaryProperties.resources] = _PdfDictionary(resources); + _resources = _PdfResources(resources); _isLoadedPageTemplate = isLoadedPage; _isReadonly = true; } //Fields - _Size _size; - _PdfStream _content; - PdfGraphics _graphics; - _PdfResources _resources; - bool _writeTransformation; + late _Size _size; + late _PdfStream _content; + PdfGraphics? _graphics; + _PdfResources? _resources; + bool? _writeTransformation; bool _isReadonly = false; bool _isLoadedPageTemplate = false; //Properties /// Gets the size of the template. + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a PDF Template. + /// PdfTemplate template = PdfTemplate(100, 50); + /// //Gets the PDF template size. + /// Size size = template.size; + /// //Draw a string using the graphics of the template. + /// template.graphics.drawString( + /// 'Hello World', PdfStandardFont(PdfFontFamily.helvetica, 14), + /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(5, 5, 0, 0)); + /// //Add a new page and draw the template on the page graphics of the document. + /// document.pages.add().graphics.drawPdfTemplate(template, Offset(0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` Size get size => _size.size; /// Gets graphics context of the template. - PdfGraphics get graphics { + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a PDF Template. + /// PdfTemplate template = PdfTemplate(100, 50); + /// //Draw a rectangle on the template graphics + /// template.graphics.drawRectangle( + /// brush: PdfBrushes.burlyWood, bounds: Rect.fromLTWH(0, 0, 100, 50)); + /// //Draw a string using the graphics of the template. + /// template.graphics.drawString( + /// 'Hello World', PdfStandardFont(PdfFontFamily.helvetica, 14), + /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(5, 5, 0, 0)); + /// //Add a new page and draw the template on the page graphics of the document. + /// document.pages.add().graphics.drawPdfTemplate(template, Offset(0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfGraphics? get graphics { if (_isReadonly) { _graphics = null; } else if (_graphics == null) { _graphics = PdfGraphics._(size, _getResources, _content); _writeTransformation ??= true; - if (_writeTransformation) { - _graphics._initializeCoordinates(); + if (_writeTransformation!) { + _graphics!._initializeCoordinates(); } } return _graphics; @@ -85,9 +145,32 @@ class PdfTemplate implements _IPdfWrapper { //Public methods /// Resets an instance. - void reset([double width, double height]) { + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a PDF Template. + /// PdfTemplate template = PdfTemplate(100, 50); + /// //Draw a rectangle on the template graphics + /// template.graphics.drawRectangle( + /// brush: PdfBrushes.burlyWood, bounds: Rect.fromLTWH(0, 0, 100, 50)); + /// //Add a new page and draw the template on the page graphics of the document. + /// document.pages.add().graphics.drawPdfTemplate(template, Offset(0, 0)); + /// //Reset PDF template + /// template.reset(); + /// //Draw a string using the graphics of the template. + /// template.graphics.drawString( + /// 'Hello World', PdfStandardFont(PdfFontFamily.helvetica, 14), + /// brush: PdfBrushes.black, bounds: Rect.fromLTWH(5, 5, 0, 0)); + /// //Add a new page and draw the template on the page graphics of the document. + /// document.pages.add().graphics.drawPdfTemplate(template, Offset(0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + void reset([double? width, double? height]) { if (width != null && height != null) { - _setSize(size.width, size.height); + _setSize(width, height); reset(); } else { if (_resources != null) { @@ -95,13 +178,13 @@ class PdfTemplate implements _IPdfWrapper { _content.remove(_DictionaryProperties.resources); } if (_graphics != null) { - _graphics._reset(size); + _graphics!._reset(size); } } } //Implementation - _PdfResources _getResources() { + _PdfResources? _getResources() { if (_resources == null) { _resources = _PdfResources(); _content[_DictionaryProperties.resources] = _resources; @@ -109,7 +192,7 @@ class PdfTemplate implements _IPdfWrapper { return _resources; } - void _setSize(double width, double height, [Offset origin]) { + void _setSize(double width, double height, [Offset? origin]) { if (origin != null) { final _PdfArray array = _PdfArray([origin.dx, origin.dy, width, height]); _content[_DictionaryProperties.bBox] = array; @@ -135,13 +218,13 @@ class PdfTemplate implements _IPdfWrapper { _PdfName(_DictionaryProperties.form); } - void _cloneResources(_PdfCrossTable crossTable) { + void _cloneResources(_PdfCrossTable? crossTable) { if (_resources != null && crossTable != null) { - final List<_PdfReference> prevReference = crossTable._prevReference; + final List<_PdfReference?> prevReference = crossTable._prevReference!; crossTable._prevReference = <_PdfReference>[]; - final _PdfDictionary resourceDict = - _resources._clone(crossTable) as _PdfDictionary; - crossTable._prevReference.addAll(prevReference); + final _PdfDictionary? resourceDict = + _resources!._clone(crossTable) as _PdfDictionary?; + crossTable._prevReference!.addAll(prevReference); _resources = _PdfResources(resourceDict); _content[_DictionaryProperties.resources] = resourceDict; } @@ -149,10 +232,12 @@ class PdfTemplate implements _IPdfWrapper { //_IPdfWrapper members @override - _IPdfPrimitive get _element => _content; + _IPdfPrimitive? get _element => _content; @override //ignore: unused_element - set _element(_IPdfPrimitive value) { - _content = value; + set _element(_IPdfPrimitive? value) { + if (value != null && value is _PdfStream) { + _content = value; + } } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_text_element.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_text_element.dart index 8a76a1025..faeb7c713 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_text_element.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/pdf_text_element.dart @@ -6,40 +6,42 @@ class PdfTextElement extends PdfLayoutElement { //Constructors /// Initializes a new instance of the [PdfTextElement] class. PdfTextElement( - {String text, - PdfFont font, - PdfPen pen, - PdfBrush brush, - PdfStringFormat format}) { + {String text = '', + PdfFont? font, + PdfPen? pen, + PdfBrush? brush, + PdfStringFormat? format}) { _initialize(text, font, pen, brush, format); } //Fields /// A value indicating the text that should be printed. - String text; + late String text; /// Gets or sets a [PdfFont] that defines the text format. - PdfFont font; + late PdfFont font; /// Gets or sets a [PdfPen] that determines the color, width, /// and style of the text - PdfPen pen; + PdfPen? pen; /// Gets or sets the [PdfBrush] that will be used to draw the text with color /// and texture. - PdfBrush brush; + PdfBrush? brush; /// Gets or sets the [PdfStringFormat] that will be used to /// set the string format - PdfStringFormat stringFormat; - bool _isPdfTextElement; + PdfStringFormat? stringFormat; + late bool _isPdfTextElement; //Implementation - void _initialize(String text, PdfFont font, PdfPen pen, PdfBrush brush, - PdfStringFormat format) { - this.text = text != null && text.isNotEmpty ? text : ''; + void _initialize(String text, PdfFont? font, PdfPen? pen, PdfBrush? brush, + PdfStringFormat? format) { + this.text = text; if (font != null) { this.font = font; + } else { + this.font = PdfStandardFont(PdfFontFamily.helvetica, 8); } if (brush != null) { this.brush = brush; @@ -54,23 +56,18 @@ class PdfTextElement extends PdfLayoutElement { } PdfBrush _obtainBrush() { - return (brush == null) ? PdfSolidBrush(PdfColor(0, 0, 0)) : brush; + return (brush == null) ? PdfSolidBrush(PdfColor(0, 0, 0)) : brush!; } @override - PdfLayoutResult _layout(_PdfLayoutParams param) { - ArgumentError.checkNotNull(param); - ArgumentError.checkNotNull(font, 'Font cannot be null'); + PdfLayoutResult? _layout(_PdfLayoutParams param) { final _TextLayouter layouter = _TextLayouter(this); - final PdfLayoutResult result = layouter._layout(param); - return (result is PdfTextLayoutResult) ? result : null; + final PdfLayoutResult? result = layouter._layout(param); + return (result != null && result is PdfTextLayoutResult) ? result : null; } @override void _drawInternal(PdfGraphics graphics, _Rectangle bounds) { - ArgumentError.checkNotNull(graphics); - ArgumentError.checkNotNull(font, 'Font cannot be null'); - ArgumentError.checkNotNull(bounds); graphics.drawString(text, font, pen: pen, brush: _obtainBrush(), diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/enums.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/enums.dart index 3e1e1dbc4..544c0948d 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/enums.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/enums.dart @@ -1,6 +1,17 @@ part of pdf; /// Specifies style information applied to text. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString('Hello World!', +/// PdfStandardFont(PdfFontFamily.helvetica, 12, style: PdfFontStyle.bold)); +/// //Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` enum PdfFontStyle { ///Represents Regular text regular, @@ -19,6 +30,18 @@ enum PdfFontStyle { } /// Indicates type of standard PDF fonts. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString( +/// 'Hello world!', PdfStandardFont(PdfFontFamily.helvetica, 12), +/// brush: PdfBrushes.black); +/// //Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` enum PdfFontFamily { /// Represents the Helvetica font. helvetica, @@ -37,6 +60,20 @@ enum PdfFontFamily { } /// Specifies the type of SubSuperscript. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString( +/// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), +/// format: PdfStringFormat( +/// alignment: PdfTextAlignment.left, +/// subSuperscript: PdfSubSuperscript.subscript)); +/// //Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` enum PdfSubSuperscript { /// Specifies no subscript or superscript. none, @@ -49,6 +86,18 @@ enum PdfSubSuperscript { } /// Specifies the type of CJK font. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString( +/// 'こんにちは世界', PdfCjkStandardFont(PdfCjkFontFamily.heiseiMinchoW3, 20), +/// brush: PdfBrushes.black); +/// //Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` enum PdfCjkFontFamily { /// Represents the Hanyang Systems Gothic Medium font. hanyangSystemsGothicMedium, @@ -73,6 +122,19 @@ enum PdfCjkFontFamily { } /// Specifies the types of text wrapping. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString( +/// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), +/// format: PdfStringFormat( +/// alignment: PdfTextAlignment.left, wordWrap: PdfWordWrapType.word)); +/// //Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` enum PdfWordWrapType { /// Text wrapping between lines when formatting /// within a rectangle is disabled. diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cid_font.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cid_font.dart index b22c803d8..0c783b582 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cid_font.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cid_font.dart @@ -2,16 +2,15 @@ part of pdf; class _PdfCidFont extends _PdfDictionary { /// Initializes a new instance of the [_PdfCidFont] class. - _PdfCidFont( - PdfCjkFontFamily fontFamily, int fontStyle, _PdfFontMetrics fontMetrics) { + _PdfCidFont(PdfCjkFontFamily? fontFamily, int? fontStyle, + _PdfFontMetrics fontMetrics) { this[_DictionaryProperties.type] = _PdfName(_DictionaryProperties.font); this[_DictionaryProperties.subtype] = _PdfName(_DictionaryProperties.cidFontType2); this[_DictionaryProperties.baseFont] = _PdfName(fontMetrics.postScriptName); this[_DictionaryProperties.dw] = _PdfNumber((fontMetrics._widthTable as _CjkWidthTable).defaultWidth); - - this[_DictionaryProperties.w] = fontMetrics._widthTable.toArray(); + this[_DictionaryProperties.w] = fontMetrics._widthTable!.toArray(); this[_DictionaryProperties.fontDescriptor] = _PdfCjkFontDescryptorFactory.getFontDescryptor( fontFamily, fontStyle, fontMetrics); @@ -19,11 +18,9 @@ class _PdfCidFont extends _PdfDictionary { } /// Gets the system info. - _PdfDictionary _getSystemInfo(PdfCjkFontFamily fontFamily) { + _PdfDictionary _getSystemInfo(PdfCjkFontFamily? fontFamily) { final _PdfDictionary sysInfo = _PdfDictionary(); - sysInfo[_DictionaryProperties.registry] = _PdfString('Adobe'); - switch (fontFamily) { case PdfCjkFontFamily.hanyangSystemsGothicMedium: case PdfCjkFontFamily.hanyangSystemsShinMyeongJoMedium: @@ -44,6 +41,8 @@ class _PdfCidFont extends _PdfDictionary { sysInfo[_DictionaryProperties.ordering] = _PdfString('GB1'); sysInfo[_DictionaryProperties.supplement] = _PdfNumber(2); break; + default: + break; } return sysInfo; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_font_descryptor_factory.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_font_descryptor_factory.dart index d7b4327a4..5fb8e581a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_font_descryptor_factory.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_font_descryptor_factory.dart @@ -1,10 +1,9 @@ part of pdf; class _PdfCjkFontDescryptorFactory { - static _PdfDictionary getFontDescryptor( - PdfCjkFontFamily fontFamily, int fontStyle, _PdfFontMetrics fontMetrics) { + static _PdfDictionary getFontDescryptor(PdfCjkFontFamily? fontFamily, + int? fontStyle, _PdfFontMetrics fontMetrics) { final _PdfDictionary fontDescryptor = _PdfDictionary(); - switch (fontFamily) { case PdfCjkFontFamily.hanyangSystemsGothicMedium: fillHanyangSystemsGothicMedium(fontDescryptor, fontFamily, fontMetrics); @@ -15,7 +14,7 @@ class _PdfCjkFontDescryptorFactory { break; case PdfCjkFontFamily.heiseiKakuGothicW5: fillHeiseiKakuGothicW5( - fontDescryptor, fontStyle, fontFamily, fontMetrics); + fontDescryptor, fontStyle!, fontFamily, fontMetrics); break; case PdfCjkFontFamily.heiseiMinchoW3: fillHeiseiMinchoW3(fontDescryptor, fontFamily, fontMetrics); @@ -29,38 +28,37 @@ class _PdfCjkFontDescryptorFactory { case PdfCjkFontFamily.sinoTypeSongLight: fillSinoTypeSongLight(fontDescryptor, fontFamily, fontMetrics); break; + default: + break; } return fontDescryptor; } /// Fills the monotype sung light font descryptor. static void fillMonotypeSungLight(_PdfDictionary fontDescryptor, - PdfCjkFontFamily fontFamily, _PdfFontMetrics fontMetrics) { + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { final _Rectangle fontBBox = _Rectangle(-160, -249, 1175, 1137); - fillFontBBox(fontDescryptor, fontBBox); fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - final _PdfNumber stem = _PdfNumber(93); fontDescryptor[_DictionaryProperties.stemV] = stem; fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); fontDescryptor[_DictionaryProperties.avgWidth] = width; fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); } /// Fills the heisei kaku gothic w5 font descryptor. - static void fillHeiseiKakuGothicW5(_PdfDictionary fontDescryptor, - int fontStyle, PdfCjkFontFamily fontFamily, _PdfFontMetrics fontMetrics) { + static void fillHeiseiKakuGothicW5( + _PdfDictionary fontDescryptor, + int fontStyle, + PdfCjkFontFamily? fontFamily, + _PdfFontMetrics fontMetrics) { final _Rectangle fontBBox = _Rectangle(-92, -250, 1102, 1175); - final _Rectangle fontBBoxI = _Rectangle(-92, -250, 1102, 1932); //Italic - + final _Rectangle fontBBoxI = _Rectangle(-92, -250, 1102, 1932); if ((fontStyle & (PdfFont._getPdfFontStyle(PdfFontStyle.italic) | PdfFont._getPdfFontStyle(PdfFontStyle.bold))) != @@ -69,142 +67,109 @@ class _PdfCjkFontDescryptorFactory { } else { fillFontBBox(fontDescryptor, fontBBoxI); } - fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - final _PdfNumber stem = _PdfNumber(93); fontDescryptor[_DictionaryProperties.stemV] = stem; fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); fontDescryptor[_DictionaryProperties.avgWidth] = _PdfNumber(689); fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(718); fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(500); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); } /// Fills the hanyang systems shin myeong jo medium font descryptor. static void fillHanyangSystemsShinMyeongJoMedium( _PdfDictionary fontDescryptor, - PdfCjkFontFamily fontFamily, + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { final _Rectangle fontBBox = _Rectangle(0, -148, 1001, 1028); - fillFontBBox(fontDescryptor, fontBBox); fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - final _PdfNumber stem = _PdfNumber(93); fontDescryptor[_DictionaryProperties.stemV] = stem; fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); fontDescryptor[_DictionaryProperties.avgWidth] = width; fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); } /// Fills the heisei mincho w3 font descryptor. static void fillHeiseiMinchoW3(_PdfDictionary fontDescryptor, - PdfCjkFontFamily fontFamily, _PdfFontMetrics fontMetrics) { + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { final _Rectangle fontBBox = _Rectangle(-123, -257, 1124, 1167); - fillFontBBox(fontDescryptor, fontBBox); fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - final _PdfNumber stem = _PdfNumber(93); fontDescryptor[_DictionaryProperties.stemV] = stem; fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); fontDescryptor[_DictionaryProperties.avgWidth] = _PdfNumber(702); fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(718); fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(500); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); } /// Fills the sino type song light font descryptor. static void fillSinoTypeSongLight(_PdfDictionary fontDescryptor, - PdfCjkFontFamily fontFamily, _PdfFontMetrics fontMetrics) { + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { final _Rectangle fontBBox = _Rectangle(-25, -254, 1025, 1134); - fillFontBBox(fontDescryptor, fontBBox); fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - final _PdfNumber stem = _PdfNumber(93); fontDescryptor[_DictionaryProperties.stemV] = stem; fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); fontDescryptor[_DictionaryProperties.avgWidth] = width; fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); } /// Fills the monotype hei medium font descryptor. static void fillMonotypeHeiMedium(_PdfDictionary fontDescryptor, - PdfCjkFontFamily fontFamily, _PdfFontMetrics fontMetrics) { + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { final _Rectangle fontBBox = _Rectangle(-45, -250, 1060, 1137); - fillFontBBox(fontDescryptor, fontBBox); fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - final _PdfNumber stem = _PdfNumber(93); fontDescryptor[_DictionaryProperties.stemV] = stem; fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); fontDescryptor[_DictionaryProperties.avgWidth] = width; fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); } /// Fills the hanyang systems gothic medium font descryptor. static void fillHanyangSystemsGothicMedium(_PdfDictionary fontDescryptor, - PdfCjkFontFamily fontFamily, _PdfFontMetrics fontMetrics) { + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { final _Rectangle fontBBox = _Rectangle(-6, -145, 1009, 1025); - fillFontBBox(fontDescryptor, fontBBox); fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - fontDescryptor[_DictionaryProperties.flags] = _PdfNumber(4); - final _PdfNumber stem = _PdfNumber(93); fontDescryptor[_DictionaryProperties.stemV] = stem; fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); fontDescryptor[_DictionaryProperties.avgWidth] = width; fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); } - // - /// Fills the known info. static void fillKnownInfo(_PdfDictionary fontDescryptor, - PdfCjkFontFamily fontFamily, _PdfFontMetrics fontMetrics) { + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { fontDescryptor[_DictionaryProperties.fontName] = _PdfName(fontMetrics.postScriptName); fontDescryptor[_DictionaryProperties.type] = @@ -212,18 +177,16 @@ class _PdfCjkFontDescryptorFactory { fontDescryptor[_DictionaryProperties.italicAngle] = _PdfNumber(0); fontDescryptor[_DictionaryProperties.missingWidth] = _PdfNumber((fontMetrics._widthTable as _CjkWidthTable).defaultWidth); - fontDescryptor[_DictionaryProperties.ascent] = _PdfNumber(fontMetrics.ascent); fontDescryptor[_DictionaryProperties.descent] = _PdfNumber(fontMetrics.descent); - fillFlags(fontDescryptor, fontFamily); } /// Fills the flags. static void fillFlags( - _PdfDictionary fontDescryptor, PdfCjkFontFamily fontFamily) { + _PdfDictionary fontDescryptor, PdfCjkFontFamily? fontFamily) { switch (fontFamily) { case PdfCjkFontFamily.monotypeHeiMedium: case PdfCjkFontFamily.hanyangSystemsGothicMedium: @@ -236,6 +199,8 @@ class _PdfCjkFontDescryptorFactory { case PdfCjkFontFamily.heiseiMinchoW3: fontDescryptor[_DictionaryProperties.flags] = _PdfNumber(6); break; + default: + break; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_standard_font.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_standard_font.dart index b89e74383..b2d66174d 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_standard_font.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_standard_font.dart @@ -1,12 +1,40 @@ part of pdf; /// Represents the standard CJK fonts. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString( +/// 'こんにちは世界', +/// PdfCjkStandardFont( +/// PdfCjkFontFamily.heiseiMinchoW3, 20), +/// brush: PdfBrushes.black); +/// //Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` class PdfCjkStandardFont extends PdfFont { //Constructors /// Initializes a new instance of the [PdfCjkStandardFont] class /// with font family, size and font style. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'こんにちは世界', + /// PdfCjkStandardFont( + /// PdfCjkFontFamily.heiseiMinchoW3, 20), + /// brush: PdfBrushes.black); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` PdfCjkStandardFont(PdfCjkFontFamily fontFamily, double size, - {PdfFontStyle style, List multiStyle}) { + {PdfFontStyle? style, List? multiStyle}) { _initialize(size, style: style, multiStyle: multiStyle); _fontFamily = fontFamily; _initializeInternals(); @@ -14,8 +42,23 @@ class PdfCjkStandardFont extends PdfFont { /// Initializes a new instance of the [PdfCjkStandardFont] class /// with [PdfCjkStandardFont] as prototype, size and font style. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Draw the text. + /// document.pages.add().graphics.drawString( + /// 'Hello World!', + /// PdfStandardFont.prototype( + /// PdfStandardFont(PdfFontFamily.helvetica, 12), 12), + /// brush: PdfBrushes.black); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` PdfCjkStandardFont.protoType(PdfCjkStandardFont prototype, double size, - {PdfFontStyle style, List multiStyle}) { + {PdfFontStyle? style, List? multiStyle}) { _initialize(size, style: style, multiStyle: multiStyle); _fontFamily = prototype.fontFamily; if (style == null && (multiStyle == null || multiStyle.isEmpty)) { @@ -26,19 +69,35 @@ class PdfCjkStandardFont extends PdfFont { //Fields /// FontFamily of the font. - PdfCjkFontFamily _fontFamily; + PdfCjkFontFamily _fontFamily = PdfCjkFontFamily.heiseiKakuGothicW5; //Properties /// Gets the font family + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create PDF cjk font. + /// PdfCjkStandardFont font = + /// PdfCjkStandardFont(PdfCjkFontFamily.heiseiMinchoW3, 12); + /// //Draw the text. + /// document.pages.add().graphics.drawString( + /// '"The CJK font family name is ${font.fontFamily}', font, + /// brush: PdfBrushes.black); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` PdfCjkFontFamily get fontFamily => _fontFamily; //_IPdfWrapper elements @override - _IPdfPrimitive get _element => _fontInternals; + _IPdfPrimitive? get _element => _fontInternals; @override //ignore: unused_element - set _element(_IPdfPrimitive value) { + set _element(_IPdfPrimitive? value) { _fontInternals = value; } @@ -57,7 +116,7 @@ class PdfCjkStandardFont extends PdfFont { dictionary[_DictionaryProperties.subtype] = _PdfName(_DictionaryProperties.type0); dictionary[_DictionaryProperties.baseFont] = - _PdfName(_metrics.postScriptName); + _PdfName(_metrics!.postScriptName); dictionary[_DictionaryProperties.encoding] = _getEncoding(_fontFamily); dictionary[_DictionaryProperties.descendantFonts] = _getDescendantFont(); @@ -66,7 +125,7 @@ class PdfCjkStandardFont extends PdfFont { } /// Gets the prope CJK encoding. - static _PdfName _getEncoding(PdfCjkFontFamily fontFamily) { + static _PdfName _getEncoding(PdfCjkFontFamily? fontFamily) { String encoding = 'Unknown'; switch (fontFamily) { @@ -85,6 +144,8 @@ class PdfCjkStandardFont extends PdfFont { case PdfCjkFontFamily.sinoTypeSongLight: encoding = 'UniGB-UCS2-H'; break; + default: + break; } final _PdfName name = _PdfName(encoding); return name; @@ -93,24 +154,21 @@ class PdfCjkStandardFont extends PdfFont { /// Returns descendant font. _PdfArray _getDescendantFont() { final _PdfArray df = _PdfArray(); - final _PdfCidFont cidFont = _PdfCidFont(_fontFamily, _fontStyle, _metrics); + final _PdfCidFont cidFont = _PdfCidFont(_fontFamily, _fontStyle, _metrics!); df._add(cidFont); return df; } @override - double _getLineWidth(String line, PdfStringFormat format) { - ArgumentError.checkNotNull(line, 'line'); - + double _getLineWidth(String line, PdfStringFormat? format) { double width = 0; for (int i = 0; i < line.length; i++) { final String ch = line[i]; final double charWidth = _getCharWidthInternal(ch); - width += charWidth; } - final double size = _metrics._getSize(format); + final double size = _metrics!._getSize(format)!; width *= PdfFont._characterSizeMultiplier * size; width = _applyFormatSettings(line, format, width); @@ -121,6 +179,6 @@ class PdfCjkStandardFont extends PdfFont { double _getCharWidthInternal(String charCode) { int code = charCode.codeUnitAt(0); code = (code >= 0) ? code : 0; - return _metrics._widthTable[code].toDouble(); + return _metrics!._widthTable![code]!.toDouble(); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_standard_font_metrics_factory.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_standard_font_metrics_factory.dart index 0c82aed4c..27b7aadf1 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_standard_font_metrics_factory.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_standard_font_metrics_factory.dart @@ -10,45 +10,45 @@ class _PdfCjkStandardFontMetricsFactory { /// Returns font metrics depending on the font settings. static _PdfFontMetrics _getMetrics( - PdfCjkFontFamily fontFamily, int fontStyle, double size) { + PdfCjkFontFamily? fontFamily, int? fontStyle, double size) { _PdfFontMetrics metrics; switch (fontFamily) { case PdfCjkFontFamily.hanyangSystemsGothicMedium: metrics = - _getHanyangSystemsGothicMediumMetrix(fontFamily, fontStyle, size); + _getHanyangSystemsGothicMediumMetrix(fontFamily, fontStyle!, size); break; case PdfCjkFontFamily.hanyangSystemsShinMyeongJoMedium: metrics = _getHanyangSystemsShinMyeongJoMediumMetrix( - fontFamily, fontStyle, size); + fontFamily, fontStyle!, size); break; case PdfCjkFontFamily.heiseiKakuGothicW5: - metrics = _getHeiseiKakuGothicW5Metrix(fontFamily, fontStyle, size); + metrics = _getHeiseiKakuGothicW5Metrix(fontFamily, fontStyle!, size); break; case PdfCjkFontFamily.heiseiMinchoW3: - metrics = _getHeiseiMinchoW3(fontFamily, fontStyle, size); + metrics = _getHeiseiMinchoW3(fontFamily, fontStyle!, size); break; case PdfCjkFontFamily.monotypeHeiMedium: - metrics = _getMonotypeHeiMedium(fontFamily, fontStyle, size); + metrics = _getMonotypeHeiMedium(fontFamily, fontStyle!, size); break; case PdfCjkFontFamily.monotypeSungLight: - metrics = _getMonotypeSungLightMetrix(fontFamily, fontStyle, size); + metrics = _getMonotypeSungLightMetrix(fontFamily, fontStyle!, size); break; case PdfCjkFontFamily.sinoTypeSongLight: - metrics = _getSinoTypeSongLight(fontFamily, fontStyle, size); + metrics = _getSinoTypeSongLight(fontFamily, fontStyle!, size); break; default: throw Exception('Unsupported font family, $fontFamily'); } - metrics.name = PdfFont._standardCjkFontNames[fontFamily.index]; + metrics.name = PdfFont._standardCjkFontNames[fontFamily!.index]; metrics.subscriptSizeFactor = _subSuperscriptFactor; metrics.superscriptSizeFactor = _subSuperscriptFactor; @@ -57,7 +57,7 @@ class _PdfCjkStandardFontMetricsFactory { /// Gets the hanyang systems gothic medium font metrix. static _PdfFontMetrics _getHanyangSystemsGothicMediumMetrix( - PdfCjkFontFamily fontFamily, int fontStyle, double size) { + PdfCjkFontFamily? fontFamily, int fontStyle, double size) { final _PdfFontMetrics metrics = _PdfFontMetrics(); final _CjkWidthTable widthTable = _CjkWidthTable(1000); metrics._widthTable = widthTable; @@ -86,7 +86,7 @@ class _PdfCjkStandardFontMetricsFactory { /// Gets the monotype hei medium metrix. static _PdfFontMetrics _getMonotypeHeiMedium( - PdfCjkFontFamily fontFamily, int fontStyle, double size) { + PdfCjkFontFamily? fontFamily, int fontStyle, double size) { final _PdfFontMetrics metrics = _PdfFontMetrics(); final _CjkWidthTable widthTable = _CjkWidthTable(1000); metrics._widthTable = widthTable; @@ -115,7 +115,7 @@ class _PdfCjkStandardFontMetricsFactory { /// Gets the monotype sung light metrix. static _PdfFontMetrics _getMonotypeSungLightMetrix( - PdfCjkFontFamily fontFamily, int fontStyle, double size) { + PdfCjkFontFamily? fontFamily, int fontStyle, double size) { final _PdfFontMetrics metrics = _PdfFontMetrics(); final _CjkWidthTable widthTable = _CjkWidthTable(1000); metrics._widthTable = widthTable; @@ -144,7 +144,7 @@ class _PdfCjkStandardFontMetricsFactory { /// Gets the sino type song light font metrics. static _PdfFontMetrics _getSinoTypeSongLight( - PdfCjkFontFamily fontFamily, int fontStyle, double size) { + PdfCjkFontFamily? fontFamily, int fontStyle, double size) { final _PdfFontMetrics metrics = _PdfFontMetrics(); final _CjkWidthTable widthTable = _CjkWidthTable(1000); metrics._widthTable = widthTable; @@ -175,7 +175,7 @@ class _PdfCjkStandardFontMetricsFactory { /// Gets the heisei mincho w3. static _PdfFontMetrics _getHeiseiMinchoW3( - PdfCjkFontFamily fontFamily, int fontStyle, double size) { + PdfCjkFontFamily? fontFamily, int fontStyle, double size) { final _PdfFontMetrics metrics = _PdfFontMetrics(); final _CjkWidthTable widthTable = _CjkWidthTable(1000); metrics._widthTable = widthTable; @@ -204,7 +204,7 @@ class _PdfCjkStandardFontMetricsFactory { /// Gets the heisei kaku gothic w5 metrix. static _PdfFontMetrics _getHeiseiKakuGothicW5Metrix( - PdfCjkFontFamily fontFamily, int fontStyle, double size) { + PdfCjkFontFamily? fontFamily, int fontStyle, double size) { final _PdfFontMetrics metrics = _PdfFontMetrics(); final _CjkWidthTable widthTable = _CjkWidthTable(1000); metrics._widthTable = widthTable; @@ -233,7 +233,7 @@ class _PdfCjkStandardFontMetricsFactory { /// Gets the hanyang systems shin myeong jo medium metrix. static _PdfFontMetrics _getHanyangSystemsShinMyeongJoMediumMetrix( - PdfCjkFontFamily fontFamily, int fontStyle, double size) { + PdfCjkFontFamily? fontFamily, int fontStyle, double size) { final _PdfFontMetrics metrics = _PdfFontMetrics(); final _CjkWidthTable widthTable = _CjkWidthTable(1000); metrics._widthTable = widthTable; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_font.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_font.dart index 2c20dba21..93228e048 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_font.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_font.dart @@ -2,6 +2,18 @@ part of pdf; /// Defines a particular format for text, including font face, size, /// and style attributes. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString('Hello World!', +/// PdfStandardFont(PdfFontFamily.helvetica, 12), +/// brush: PdfBrushes.black); +/// //Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` abstract class PdfFont implements _IPdfWrapper { //Constants static const double _characterSizeMultiplier = 0.001; @@ -23,32 +35,92 @@ abstract class PdfFont implements _IPdfWrapper { ]; //Fields - /// Size of the font. - double _size; + //Size of the font. + late double _size; - /// Style of the font. - PdfFontStyle get style => _style; - - /// Metrics of the font. - _PdfFontMetrics _metrics; + //Metrics of the font. + _PdfFontMetrics? _metrics; - /// PDf primitive of the font. - _IPdfPrimitive _fontInternals; + //PDF primitive of the font. + _IPdfPrimitive? _fontInternals; - /// Number format of [PdfFontStyle] + //Number format of [PdfFontStyle]. int _fontStyle = 0; PdfFontStyle _style = PdfFontStyle.regular; //Properties - ///Gets the font name. - String get name => _metrics.name; + /// Gets style of the font. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF font instance. + /// PdfFont font = PdfStandardFont(PdfFontFamily.helvetica, 12); + /// //Draw string to PDF page. + /// document.pages.add().graphics.drawString( + /// 'Font Name: ${font.name}\nFont Size: ${font.size}\nFont Height: ${font.height}\nFont Style: ${font.style}', + /// font); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + PdfFontStyle get style => _style; + + /// Gets the font name. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF font instance. + /// PdfFont font = PdfStandardFont(PdfFontFamily.helvetica, 12); + /// //Draw string to PDF page. + /// document.pages.add().graphics.drawString( + /// 'Font Name: ${font.name}\nFont Size: ${font.size}\nFont Height: ${font.height}\nFont Style: ${font.style}', + /// font); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + String get name => _metrics!.name; - ///Gets the font size. + /// Gets the font size. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF font instance. + /// PdfFont font = PdfStandardFont(PdfFontFamily.helvetica, 12); + /// //Draw string to PDF page. + /// document.pages.add().graphics.drawString( + /// 'Font Name: ${font.name}\nFont Size: ${font.size}\nFont Height: ${font.height}\nFont Style: ${font.style}', + /// font); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` double get size => _size; /// Gets the height of the font in points. - double get height => _metrics._getHeight(null); + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF font instance. + /// PdfFont font = PdfStandardFont(PdfFontFamily.helvetica, 12); + /// //Draw string to PDF page. + /// document.pages.add().graphics.drawString( + /// 'Font Name: ${font.name}\nFont Size: ${font.size}\nFont Height: ${font.height}\nFont Style: ${font.style}', + /// font); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + double get height => _metrics!._getHeight(null); //ignore:unused_element bool get _isUnderline => @@ -81,8 +153,7 @@ abstract class PdfFont implements _IPdfWrapper { /// //Dispose the document. /// doc.dispose(); /// ``` - Size measureString(String text, {Size layoutArea, PdfStringFormat format}) { - ArgumentError.checkNotNull(text); + Size measureString(String text, {Size? layoutArea, PdfStringFormat? format}) { layoutArea ??= const Size(0, 0); final _PdfStringLayouter layouter = _PdfStringLayouter(); final _PdfStringLayoutResult result = layouter._layout(text, this, format, @@ -94,20 +165,20 @@ abstract class PdfFont implements _IPdfWrapper { /// Initializes a new instance of the [PdfFont] class /// with font size and style. void _initialize(double size, - {PdfFontStyle style, List multiStyle}) { + {PdfFontStyle? style, List? multiStyle}) { _setSize(size); _setStyle(style, multiStyle); } ///Sets the style. - void _setStyle(PdfFontStyle style, List multiStyle) { + void _setStyle(PdfFontStyle? style, List? multiStyle) { if (style != null) { _style = style; _fontStyle = _getPdfFontStyle(style); } if (multiStyle != null && multiStyle.isNotEmpty) { for (int i = 0; i < multiStyle.length; i++) { - _fontStyle |= _getPdfFontStyle(multiStyle[i]); + _fontStyle = _fontStyle | _getPdfFontStyle(multiStyle[i]); } } else if (style == null) { _style = PdfFontStyle.regular; @@ -117,7 +188,7 @@ abstract class PdfFont implements _IPdfWrapper { ///Sets the font size. void _setSize(double value) { if (_metrics != null) { - _metrics.size = value; + _metrics!.size = value; } _size = value; } @@ -125,8 +196,6 @@ abstract class PdfFont implements _IPdfWrapper { /// Gets the value for PdfFontStyle. static int _getPdfFontStyle(PdfFontStyle value) { switch (value) { - case PdfFontStyle.regular: - return 0; case PdfFontStyle.bold: return 1; case PdfFontStyle.italic: @@ -136,16 +205,16 @@ abstract class PdfFont implements _IPdfWrapper { case PdfFontStyle.strikethrough: return 8; default: - return null; + return 0; } } /// Returns width of the line. - double _getLineWidth(String line, PdfStringFormat format); + double _getLineWidth(String line, PdfStringFormat? format); /// Applies settings to the default line width. double _applyFormatSettings( - String line, PdfStringFormat format, double width) { + String line, PdfStringFormat? format, double width) { double realWidth = width; if (format != null && width > 0) { if (format.characterSpacing != 0) { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_font_metrics.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_font_metrics.dart index d1afdde5e..62e0ce8f8 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_font_metrics.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_font_metrics.dart @@ -10,10 +10,10 @@ class _PdfFontMetrics { double descent = 0; /// Gets name of the font. - String name; + String name = ''; /// Gets PostScript Name of the font. - String postScriptName; + String? postScriptName; /// Gets size of the font. double size = 0; @@ -31,28 +31,28 @@ class _PdfFontMetrics { int lineGap = 0; /// Subscript size factor. - double subscriptSizeFactor = 0; + double? subscriptSizeFactor = 0; /// Superscript size factor. - double superscriptSizeFactor; - _WidthTable _widthTable; + double? superscriptSizeFactor; + _WidthTable? _widthTable; /// Indicate whether the true type font reader font has bold style. - bool isBold; + bool? isBold; //Implementation /// Calculates size of the font depending on the subscript/superscript value. - double _getSize([PdfStringFormat format]) { - double _size = size; + double? _getSize([PdfStringFormat? format]) { + double? _size = size; if (format != null) { switch (format.subSuperscript) { case PdfSubSuperscript.subscript: - _size /= 1.5; + _size = _size / 1.5; break; case PdfSubSuperscript.superscript: - _size /= 1.5; + _size = _size / 1.5; break; - case PdfSubSuperscript.none: + default: break; } } @@ -60,31 +60,31 @@ class _PdfFontMetrics { } /// Returns height taking into consideration font's size. - double _getHeight(PdfStringFormat format) { + double _getHeight(PdfStringFormat? format) { return _getDescent(format) < 0 ? (_getAscent(format) - _getDescent(format) + _getLineGap(format)) : (_getAscent(format) + _getDescent(format) + _getLineGap(format)); } /// Returns descent taking into consideration font's size. - double _getDescent(PdfStringFormat format) { - return descent * PdfFont._characterSizeMultiplier * _getSize(format); + double _getDescent(PdfStringFormat? format) { + return descent * PdfFont._characterSizeMultiplier * _getSize(format)!; } /// Returns ascent taking into consideration font's size. - double _getAscent(PdfStringFormat format) { - return ascent * PdfFont._characterSizeMultiplier * _getSize(format); + double _getAscent(PdfStringFormat? format) { + return ascent * PdfFont._characterSizeMultiplier * _getSize(format)!; } /// Returns Line gap taking into consideration font's size. - double _getLineGap(PdfStringFormat format) { - return lineGap * PdfFont._characterSizeMultiplier * _getSize(format); + double _getLineGap(PdfStringFormat? format) { + return lineGap * PdfFont._characterSizeMultiplier * _getSize(format)!; } } /// The base class for a width table. abstract class _WidthTable { - int operator [](int index); + int? operator [](int index); /// Toes the array. _PdfArray toArray(); @@ -94,20 +94,19 @@ abstract class _WidthTable { class _StandardWidthTable extends _WidthTable { //Constructor _StandardWidthTable(List widths) : super() { - ArgumentError.checkNotNull(widths, 'widths'); _widths = widths; } // Fields - List _widths; + List? _widths; //Properties @override - int operator [](int index) => _returnValue(index); - int _returnValue(int index) { - if (index < 0 || index >= _widths.length) { + int? operator [](int index) => _returnValue(index); + int? _returnValue(int index) { + if (index < 0 || index >= _widths!.length) { throw ArgumentError.value( index, 'The character is not supported by the font.'); } - return _widths[index]; + return _widths![index]; } @override @@ -124,7 +123,7 @@ class _CjkWidthTable extends _WidthTable { } /// Local variable to store the width. - List<_CjkWidth> width; + late List<_CjkWidth> width; /// Local variable to store the default width. int defaultWidth; @@ -141,9 +140,6 @@ class _CjkWidthTable extends _WidthTable { } void add(_CjkWidth widths) { - if (widths == null) { - throw ArgumentError('widths'); - } width.add(widths); } @@ -210,18 +206,17 @@ class _CjkSameWidth extends _CjkWidth { /// Implements capabilities to control a sequent range of characters /// with different width. class _CjkDifferentWidth extends _CjkWidth { - _CjkDifferentWidth(this.from, this.width) { - if (width == null) { - throw ArgumentError('widths'); - } + _CjkDifferentWidth(from, width) { + this.from = from; + this.width = width; } /// The form @override - int from; + late int from; /// The width - List width; + late List width; /// Gets the ending character. @override diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_standard_font.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_standard_font.dart index ea80ae22b..8a4aabea2 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_standard_font.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_standard_font.dart @@ -2,12 +2,37 @@ part of pdf; /// Represents one of the 14 standard PDF fonts. /// It's used to create a standard PDF font to draw the text in to the PDF +/// +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString( +/// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), +/// brush: PdfBrushes.black); +/// //Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` class PdfStandardFont extends PdfFont { //Constructors /// Initializes a new instance of the [PdfStandardFont] class /// with font family, size and font style. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// brush: PdfBrushes.black); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` PdfStandardFont(PdfFontFamily fontFamily, double size, - {PdfFontStyle style, List multiStyle}) { + {PdfFontStyle? style, List? multiStyle}) { _initialize(size, style: style, multiStyle: multiStyle); _fontFamily = fontFamily; _checkStyle(); @@ -16,8 +41,24 @@ class PdfStandardFont extends PdfFont { /// Initializes a new instance of the [PdfStandardFont] class /// with [PdfStandardFont] as prototype, size and font style. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create PDF standard font. + /// PdfFont font = PdfStandardFont.prototype( + /// PdfStandardFont(PdfFontFamily.helvetica, 12), 12); + /// //Draw the text. + /// document.pages.add().graphics.drawString( + /// 'The font family name is ${font.fontFamily}', font, + /// brush: PdfBrushes.black); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` PdfStandardFont.prototype(PdfStandardFont prototype, double size, - {PdfFontStyle style, List multiStyle}) { + {PdfFontStyle? style, List? multiStyle}) { _initialize(size, style: style, multiStyle: multiStyle); _fontFamily = prototype.fontFamily; if (style == null && (multiStyle == null || multiStyle.isEmpty)) { @@ -29,22 +70,300 @@ class PdfStandardFont extends PdfFont { //Fields /// FontFamily of the font. - PdfFontFamily _fontFamily; + PdfFontFamily _fontFamily = PdfFontFamily.helvetica; /// First character position. static const int _charOffset = 32; //Properties /// Gets the font family. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create PDF standard font. + /// PdfStandardFont font = PdfStandardFont(PdfFontFamily.helvetica, 12); + /// //Draw the text. + /// document.pages.add().graphics.drawString( + /// 'The font family name is ${font.fontFamily}', font, + /// brush: PdfBrushes.black); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` PdfFontFamily get fontFamily => _fontFamily; + List? _windows1252MapTable; + + List? get _windowsMapTable { + _windows1252MapTable ??= [ + '\0', + '\u0001', + '\u0002', + '\u0003', + '\u0004', + '\u0005', + '\u0006', + '\a', + '\b', + '\t', + '\n', + '\v', + '\f', + '\r', + '\u000e', + '\u000f', + '\u0010', + '\u0011', + '\u0012', + '\u0013', + '\u0014', + '\u0015', + '\u0016', + '\u0017', + '\u0018', + '\u0019', + '\u001a', + '\u001b', + '\u001c', + '\u001d', + '\u001e', + '\u001f', + ' ', + '!', + '"', + '#', + '\$', + '%', + '&', + '\'', + '(', + ')', + '*', + '+', + ',', + '-', + '.', + '/', + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + ':', + ';', + '<', + '=', + '>', + '?', + '@', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + '[', + '\\', + ']', + '^', + '_', + '`', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + '{', + '|', + '}', + '~', + '\u007f', + '€', + '\u0081', + '‚', + 'ƒ', + '„', + '…', + '†', + '‡', + 'ˆ', + '‰', + 'Š', + '‹', + 'Œ', + '\u008d', + 'Ž', + '\u008f', + '\u0090', + '‘', + '’', + '“', + '”', + '•', + '–', + '—', + '˜', + '™', + 'š', + '›', + 'œ', + '\u009d', + 'ž', + 'Ÿ', + ' ', + '¡', + '¢', + '£', + '¤', + '¥', + '¦', + '§', + '¨', + '©', + 'ª', + '«', + '¬', + '­', + '®', + '¯', + '°', + '±', + '²', + '³', + '´', + 'µ', + '¶', + '·', + '¸', + '¹', + 'º', + '»', + '¼', + '½', + '¾', + '¿', + 'À', + 'Á', + 'Â', + 'Ã', + 'Ä', + 'Å', + 'Æ', + 'Ç', + 'È', + 'É', + 'Ê', + 'Ë', + 'Ì', + 'Í', + 'Î', + 'Ï', + 'Ð', + 'Ñ', + 'Ò', + 'Ó', + 'Ô', + 'Õ', + 'Ö', + '×', + 'Ø', + 'Ù', + 'Ú', + 'Û', + 'Ü', + 'Ý', + 'Þ', + 'ß', + 'à', + 'á', + 'â', + 'ã', + 'ä', + 'å', + 'æ', + 'ç', + 'è', + 'é', + 'ê', + 'ë', + 'ì', + 'í', + 'î', + 'ï', + 'ð', + 'ñ', + 'ò', + 'ó', + 'ô', + 'õ', + 'ö', + '÷', + 'ø', + 'ú', + 'û', + 'ü', + 'ý', + 'þ', + 'ÿ' + ]; + return _windows1252MapTable; + } //Implementation /// Checks font style of the font. void _checkStyle() { if (fontFamily == PdfFontFamily.symbol || fontFamily == PdfFontFamily.zapfDingbats) { - _fontStyle &= ~(PdfFont._getPdfFontStyle(PdfFontStyle.bold) | - PdfFont._getPdfFontStyle(PdfFontStyle.italic)); + _fontStyle = _fontStyle & + ~(PdfFont._getPdfFontStyle(PdfFontStyle.bold) | + PdfFont._getPdfFontStyle(PdfFontStyle.italic)); _style = PdfFontStyle.regular; } } @@ -61,11 +380,14 @@ class PdfStandardFont extends PdfFont { double _getCharWidthInternal(String charCode) { int code = 0; code = charCode.codeUnitAt(0); + if (code >= 256 && _windowsMapTable!.contains(charCode)) { + code = _windowsMapTable!.indexOf(charCode); + } if (PdfFont._standardFontNames.contains(name)) { code = code - PdfStandardFont._charOffset; } code = (code >= 0 && code != 128) ? code : 0; - return _metrics._widthTable[code].toDouble(); + return _metrics!._widthTable![code]!.toDouble(); } /// Creates font's dictionary. @@ -76,7 +398,7 @@ class PdfStandardFont extends PdfFont { dictionary[_DictionaryProperties.subtype] = _PdfName(_DictionaryProperties.type1); dictionary[_DictionaryProperties.baseFont] = - _PdfName(_metrics.postScriptName); + _PdfName(_metrics!.postScriptName); if (fontFamily != PdfFontFamily.symbol && fontFamily != PdfFontFamily.zapfDingbats) { dictionary[_DictionaryProperties.encoding] = _PdfName('WinAnsiEncoding'); @@ -86,15 +408,14 @@ class PdfStandardFont extends PdfFont { /// Returns width of the line. @override - double _getLineWidth(String line, [PdfStringFormat format]) { - ArgumentError.checkNotNull(line, 'line'); + double _getLineWidth(String line, [PdfStringFormat? format]) { double width = 0; for (int i = 0; i < line.length; i++) { final String character = line[i]; final double charWidth = _getCharWidthInternal(character); width += charWidth; } - final double size = _metrics._getSize(format); + final double size = _metrics!._getSize(format)!; width *= PdfFont._characterSizeMultiplier * size; width = _applyFormatSettings(line, format, width); return width; @@ -102,11 +423,11 @@ class PdfStandardFont extends PdfFont { //_IPdfWrapper elements @override - _IPdfPrimitive get _element => _fontInternals; + _IPdfPrimitive? get _element => _fontInternals; @override //ignore: unused_element - set _element(_IPdfPrimitive value) { + set _element(_IPdfPrimitive? value) { _fontInternals = value; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_standard_font_metrics_factory.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_standard_font_metrics_factory.dart index b83d166ed..95cac7461 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_standard_font_metrics_factory.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_standard_font_metrics_factory.dart @@ -135,17 +135,17 @@ class _PdfStandardFontMetricsFactory { /// Returns metrics of the font. static _PdfFontMetrics _getMetrics( - PdfFontFamily fontFamily, int fontStyle, double size) { + PdfFontFamily? fontFamily, int? fontStyle, double size) { _PdfFontMetrics metrics; switch (fontFamily) { case PdfFontFamily.helvetica: - metrics = _getHelveticaMetrics(fontFamily, fontStyle, size); + metrics = _getHelveticaMetrics(fontFamily, fontStyle!, size); break; case PdfFontFamily.courier: - metrics = _getCourierMetrics(fontFamily, fontStyle, size); + metrics = _getCourierMetrics(fontFamily, fontStyle!, size); break; case PdfFontFamily.timesRoman: - metrics = _getTimesMetrics(fontFamily, fontStyle, size); + metrics = _getTimesMetrics(fontFamily, fontStyle!, size); break; case PdfFontFamily.symbol: metrics = _getSymbolMetrics(fontFamily, size); @@ -155,10 +155,10 @@ class _PdfStandardFontMetricsFactory { break; default: metrics = - _getHelveticaMetrics(PdfFontFamily.helvetica, fontStyle, size); + _getHelveticaMetrics(PdfFontFamily.helvetica, fontStyle!, size); break; } - metrics.name = PdfFont._standardFontNames[fontFamily.index]; + metrics.name = PdfFont._standardFontNames[fontFamily!.index]; metrics.subscriptSizeFactor = _subSuperscriptFactor; metrics.superscriptSizeFactor = _subSuperscriptFactor; return metrics; @@ -166,7 +166,7 @@ class _PdfStandardFontMetricsFactory { /// Creates Helvetica font metrics. static _PdfFontMetrics _getHelveticaMetrics( - PdfFontFamily fontFamily, int fontStyle, double size) { + PdfFontFamily? fontFamily, int fontStyle, double size) { final _PdfFontMetrics metrics = _PdfFontMetrics(); if (fontStyle & PdfFont._getPdfFontStyle(PdfFontStyle.bold) > 0 && fontStyle & PdfFont._getPdfFontStyle(PdfFontStyle.italic) > 0) { @@ -205,7 +205,7 @@ class _PdfStandardFontMetricsFactory { /// Creates Courier font metrics. static _PdfFontMetrics _getCourierMetrics( - PdfFontFamily fontFamily, int fontStyle, double size) { + PdfFontFamily? fontFamily, int fontStyle, double size) { final _PdfFontMetrics metrics = _PdfFontMetrics(); if (fontStyle & PdfFont._getPdfFontStyle(PdfFontStyle.bold) > 0 && fontStyle & PdfFont._getPdfFontStyle(PdfFontStyle.italic) > 0) { @@ -242,7 +242,7 @@ class _PdfStandardFontMetricsFactory { /// Creates Times font metrics. static _PdfFontMetrics _getTimesMetrics( - PdfFontFamily fontFamily, int fontStyle, double size) { + PdfFontFamily? fontFamily, int fontStyle, double size) { final _PdfFontMetrics metrics = _PdfFontMetrics(); if (fontStyle & PdfFont._getPdfFontStyle(PdfFontStyle.bold) > 0 && fontStyle & PdfFont._getPdfFontStyle(PdfFontStyle.italic) > 0) { @@ -283,7 +283,7 @@ class _PdfStandardFontMetricsFactory { /// Creates Symbol font metrics. static _PdfFontMetrics _getSymbolMetrics( - PdfFontFamily fontFamily, double size) { + PdfFontFamily? fontFamily, double size) { final _PdfFontMetrics metrics = _PdfFontMetrics(); metrics.ascent = _symbolAscent; @@ -297,7 +297,7 @@ class _PdfStandardFontMetricsFactory { /// Creates ZapfDingbats font metrics. static _PdfFontMetrics _getZapfDingbatsMetrics( - PdfFontFamily fontFamily, double size) { + PdfFontFamily? fontFamily, double size) { final _PdfFontMetrics metrics = _PdfFontMetrics(); metrics.ascent = _zapfDingbatsAscent; metrics.descent = _zapfDingbatsDescent; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_string_format.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_string_format.dart index c33e0a062..64953e9cc 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_string_format.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_string_format.dart @@ -1,21 +1,67 @@ part of pdf; /// Represents the text layout information on PDF +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString( +/// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), +/// //Create a new PDF string format instance. +/// format: PdfStringFormat( +/// alignment: PdfTextAlignment.left, +/// lineAlignment: PdfVerticalAlignment.top, +/// textDirection: PdfTextDirection.leftToRight, +/// characterSpacing: 0.5, +/// wordSpacing: 0.5, +/// lineSpacing: 0.5, +/// subSuperscript: PdfSubSuperscript.superscript, +/// paragraphIndent: 10, +/// measureTrailingSpaces: true, +/// wordWrap: PdfWordWrapType.word)); +/// //Save the document. +/// List bytes = document.save(); +/// //Close the document. +/// document.dispose(); +/// ``` class PdfStringFormat { //Constructor /// Initializes a new instance of the [PdfStringFormat] class /// with horizontal alignment and vertical alignment of text. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// //Create a new PDF string format instance. + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.left, + /// lineAlignment: PdfVerticalAlignment.top, + /// textDirection: PdfTextDirection.leftToRight, + /// characterSpacing: 0.5, + /// wordSpacing: 0.5, + /// lineSpacing: 0.5, + /// subSuperscript: PdfSubSuperscript.superscript, + /// paragraphIndent: 10, + /// measureTrailingSpaces: true, + /// wordWrap: PdfWordWrapType.word)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` PdfStringFormat( - {PdfTextAlignment alignment, - PdfVerticalAlignment lineAlignment, - PdfTextDirection textDirection, - double characterSpacing, - double wordSpacing, - double lineSpacing, - PdfSubSuperscript subSuperscript, - double paragraphIndent, - bool measureTrailingSpaces, - PdfWordWrapType wordWrap}) { + {PdfTextAlignment alignment = PdfTextAlignment.left, + PdfVerticalAlignment lineAlignment = PdfVerticalAlignment.top, + PdfTextDirection textDirection = PdfTextDirection.none, + double characterSpacing = 0, + double wordSpacing = 0, + double lineSpacing = 0, + PdfSubSuperscript subSuperscript = PdfSubSuperscript.none, + double paragraphIndent = 0, + bool measureTrailingSpaces = false, + PdfWordWrapType wordWrap = PdfWordWrapType.word}) { _initialize( alignment, lineAlignment, @@ -31,56 +77,258 @@ class PdfStringFormat { //Fields /// Gets and sets Horizontal text alignment. - PdfTextAlignment alignment; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// format: PdfStringFormat(alignment: PdfTextAlignment.left)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + late PdfTextAlignment alignment; /// Gets and sets Vertical text alignment. - PdfVerticalAlignment lineAlignment; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// format: PdfStringFormat(lineAlignment: PdfVerticalAlignment.top)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + late PdfVerticalAlignment lineAlignment; /// Gets and sets text rendering direction. - PdfTextDirection textDirection; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// format: PdfStringFormat(textDirection: PdfTextAlignment.rightToLeft)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + late PdfTextDirection textDirection; /// Gets and sets Character spacing value. - double characterSpacing; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Draw the text. + /// document.pages + /// .add() + /// .graphics + /// .drawString('Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// format: PdfStringFormat(characterSpacing: 0.5)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + late double characterSpacing; /// Gets and sets Word spacing value. - double wordSpacing; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Draw the text. + /// document.pages + /// .add() + /// .graphics + /// .drawString('Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// format: PdfStringFormat(wordSpacing: 0.5)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + late double wordSpacing; /// Gets and sets Text leading or Line spacing. - double lineSpacing; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Draw the text. + /// document.pages + /// .add() + /// .graphics + /// .drawString('Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// format: PdfStringFormat(lineSpacing: 0.5)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + late double lineSpacing; /// Gets and sets if the text should be a part of the current clipping path. - bool clipPath; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// bounds: Rect.fromLTWH(0, 0, 200, 100), + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.middle, + /// characterSpacing: 1) + /// //Set the clip path. + /// ..clipPath = true); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + bool clipPath = false; /// Gets and sets whether the text is in subscript or superscript mode. - PdfSubSuperscript subSuperscript; - - /// The scaling factor of the text being drawn. - double _scalingFactor; - - /// Indent of the first line in the paragraph. - double _paragraphIndent; - - double _firstLineIndent; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// bounds: Rect.fromLTWH(0, 0, 200, 100), + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.middle, + /// characterSpacing: 1, + /// subSuperscript: PdfSubSuperscript.subscript)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + late PdfSubSuperscript subSuperscript; /// Gets and sets whether entire lines are laid out in the /// formatting rectangle only or not. - bool lineLimit; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// bounds: Rect.fromLTWH(0, 0, 200, 100), + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.middle, + /// characterSpacing: 1, + /// lineSpacing: 1.1, + /// measureTrailingSpaces: true, + /// paragraphIndent: 2.1, + /// wordSpacing: 1.5, + /// wordWrap: PdfWordWrapType.word, + /// subSuperscript: PdfSubSuperscript.subscript) + /// ..clipPath = true + /// ..noClip = true + /// //Set line limit. + /// ..lineLimit = true); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + bool lineLimit = true; /// Gets and sets whether spaces at the end of the line should be /// left or removed. - bool measureTrailingSpaces; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// format: PdfStringFormat(measureTrailingSpaces: true)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + late bool measureTrailingSpaces; /// Gets and sets whether the text region should be clipped or not. - bool noClip; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// bounds: Rect.fromLTWH(0, 0, 200, 100), + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.middle, + /// characterSpacing: 1, + /// lineSpacing: 1.1, + /// measureTrailingSpaces: true, + /// paragraphIndent: 2.1, + /// wordSpacing: 1.5, + /// wordWrap: PdfWordWrapType.word, + /// subSuperscript: PdfSubSuperscript.subscript) + /// ..clipPath = true + /// //Set no clip. + /// ..noClip = true + /// ..lineLimit = true); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + bool noClip = false; /// Gets and sets text wrapping type. - PdfWordWrapType wordWrap; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// format: PdfStringFormat(wordWrap: PdfWordWrapType.word)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + late PdfWordWrapType wordWrap; + + //The scaling factor of the text being drawn. + double _scalingFactor = 100; + + //Indent of the first line in the paragraph. + late double _paragraphIndent; + + late double _firstLineIndent; //Properties - /// Gets the indent of the first line in the paragraph. + /// Gets or sets the indent of the first line in the paragraph. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// format: PdfStringFormat(paragraphIndent: 2.1)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` double get paragraphIndent => _paragraphIndent; - - /// Sets the indent of the first line in the paragraph. set paragraphIndent(double value) { _paragraphIndent = value; _firstLineIndent = value; @@ -98,20 +346,15 @@ class PdfStringFormat { double paragraphIndent, bool measureTrailingSpaces, PdfWordWrapType wordWrap) { - alignment = textAlignment ?? PdfTextAlignment.left; - lineAlignment = verticalAlignment ?? PdfVerticalAlignment.top; - this.characterSpacing = characterSpacing ?? 0; - clipPath = false; - lineLimit = true; - this.lineSpacing = lineSpacing ?? 0; - this.measureTrailingSpaces = measureTrailingSpaces ?? false; - noClip = false; - _firstLineIndent ??= 0; - this.paragraphIndent = paragraphIndent ?? 0; - this.subSuperscript = subSuperscript ?? PdfSubSuperscript.none; - this.textDirection = textDirection ?? PdfTextDirection.none; - this.wordSpacing = wordSpacing ?? 0; - this.wordWrap = wordWrap ?? PdfWordWrapType.word; - _scalingFactor = 100; + alignment = textAlignment; + lineAlignment = verticalAlignment; + this.characterSpacing = characterSpacing; + this.lineSpacing = lineSpacing; + this.measureTrailingSpaces = measureTrailingSpaces; + this.paragraphIndent = paragraphIndent; + this.subSuperscript = subSuperscript; + this.textDirection = textDirection; + this.wordSpacing = wordSpacing; + this.wordWrap = wordWrap; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_string_layout_result.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_string_layout_result.dart index 944e16a06..585e224c9 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_string_layout_result.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_string_layout_result.dart @@ -2,19 +2,15 @@ part of pdf; class _PdfStringLayoutResult { _PdfStringLayoutResult() { - _initialize(); - } - double _lineHeight; - List<_LineInfo> _lines; - _Size _size; - //ignore:unused_field - String _remainder; - //Implementation - void _initialize() { _lineHeight = 0; _size = _Size.empty; } + late double _lineHeight; + late _Size _size; + List<_LineInfo>? _lines; + //ignore:unused_field + String? _remainder; - bool get _isEmpty => _lines == null || (_lines != null && _lines.isEmpty); - int get _lineCount => (!_isEmpty) ? _lines.length : 0; + bool get _isEmpty => _lines == null || (_lines != null && _lines!.isEmpty); + int get _lineCount => (!_isEmpty) ? _lines!.length : 0; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_string_layouter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_string_layouter.dart index e5d8bce50..3e17ac296 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_string_layouter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_string_layouter.dart @@ -10,27 +10,25 @@ class _PdfStringLayouter { //Fields //ignore: unused_field - String _text; - PdfFont _font; - PdfStringFormat _format; - _Size _size; - _Rectangle _rectangle; - double _pageHeight; - _StringTokenizer _reader; + String? _text; + PdfFont? _font; + PdfStringFormat? _format; + late _Size _size; + late _Rectangle _rectangle; + late double _pageHeight; + _StringTokenizer? _reader; int _tabOccuranceCount = 0; bool _isTabReplaced = false; //Implementation _PdfStringLayoutResult _layout( - String text, PdfFont font, PdfStringFormat format, - {double width, double height, _Rectangle bounds, double pageHeight}) { - ArgumentError.checkNotNull(text, 'text'); - ArgumentError.checkNotNull(font, 'font'); + String text, PdfFont font, PdfStringFormat? format, + {double? width, double? height, _Rectangle? bounds, double? pageHeight}) { _text = text; _font = font; _format = format; - _size = bounds == null ? _Size(width, height) : bounds.size; - _rectangle = bounds ?? _Rectangle(0, 0, width, height); + _size = bounds == null ? _Size(width!, height!) : bounds.size; + _rectangle = bounds ?? _Rectangle(0, 0, width!, height!); _pageHeight = pageHeight ?? 0; _reader = _StringTokenizer(text); final _PdfStringLayoutResult result = _doLayout(); @@ -42,23 +40,21 @@ class _PdfStringLayouter { final _PdfStringLayoutResult result = _PdfStringLayoutResult(); _PdfStringLayoutResult lineResult = _PdfStringLayoutResult(); final List<_LineInfo> lines = <_LineInfo>[]; - String line = _reader._peekLine(); - double lineIndent = _getLineIndent(true); + String? line = _reader!._peekLine(); + double? lineIndent = _getLineIndent(true); while (line != null) { - lineResult = _layoutLine(line, lineIndent); - if (lineResult != null) { - int numSymbolsInserted = 0; - final Map returnedValue = - _copyToResult(result, lineResult, lines, numSymbolsInserted); - final bool success = returnedValue['success']; - numSymbolsInserted = returnedValue['numInserted']; - if (!success) { - _reader._read(numSymbolsInserted); - break; - } + lineResult = _layoutLine(line, lineIndent!); + int? numSymbolsInserted = 0; + final Map returnedValue = + _copyToResult(result, lineResult, lines, numSymbolsInserted); + final bool success = returnedValue['success']; + numSymbolsInserted = returnedValue['numInserted']; + if (!success) { + _reader!._read(numSymbolsInserted); + break; } - _reader._readLine(); - line = _reader._peekLine(); + _reader!._readLine(); + line = _reader!._peekLine(); lineIndent = _getLineIndent(false); } _finalizeResult(result, lines); @@ -68,17 +64,17 @@ class _PdfStringLayouter { void _finalizeResult(_PdfStringLayoutResult result, List<_LineInfo> lines) { result._lines = lines.toList(); result._lineHeight = _getLineHeight(); - if (!_reader._isEndOfFile) { - result._remainder = _reader._readToEnd(); + if (!_reader!._isEndOfFile) { + result._remainder = _reader!._readToEnd(); } lines.length = 0; } - double _getLineIndent(bool firstLine) { - double lineIndent = 0; + double? _getLineIndent(bool firstLine) { + double? lineIndent = 0; if (_format != null) { lineIndent = - firstLine ? _format._firstLineIndent : _format.paragraphIndent; + firstLine ? _format!._firstLineIndent : _format!.paragraphIndent; lineIndent = (_size.width > 0) ? (_size.width <= lineIndent ? _size.width : lineIndent) : lineIndent; @@ -87,7 +83,6 @@ class _PdfStringLayouter { } _PdfStringLayoutResult _layoutLine(String line, double lineIndent) { - ArgumentError.checkNotNull(line, 'line'); if (line.contains('\t')) { _tabOccuranceCount = 0; int i = 0; @@ -113,21 +108,21 @@ class _PdfStringLayouter { lines, line, lineWidth, - _getLineTypeValue(_LineType.newLineBreak) | - _getLineTypeValue(lineType)); + _getLineTypeValue(_LineType.newLineBreak)! | + _getLineTypeValue(lineType)!); } else { String builder = ''; String curLine = ''; lineWidth = lineIndent; double curIndent = lineIndent; final _StringTokenizer reader = _StringTokenizer(line); - String word = reader._peekWord(); - if (word.length != reader._length) { + String? word = reader._peekWord(); + if (word!.length != reader._length) { if (word == ' ') { curLine = curLine + word; builder = builder + word; - reader._position += 1; - word = reader._peekWord(); + reader._position = reader._position! + 1; + word = reader._peekWord()!; } } while (word != null) { @@ -143,7 +138,7 @@ class _PdfStringLayouter { } if (curLine.length == word.length) { if (_getWrapType() == PdfWordWrapType.wordOnly) { - lineResult._remainder = line.substring(reader._position); + lineResult._remainder = line.substring(reader._position!); break; } else if (curLine.length == 1) { builder += word; @@ -163,8 +158,8 @@ class _PdfStringLayouter { lines, ln, lineWidth, - _getLineTypeValue(_LineType.layoutBreak) | - _getLineTypeValue(lineType)); + _getLineTypeValue(_LineType.layoutBreak)! | + _getLineTypeValue(lineType)!); } curLine = ''; builder = ''; @@ -172,7 +167,7 @@ class _PdfStringLayouter { curIndent = 0; curLineWidth = 0; lineType = _LineType.none; - word = readWord ? word : reader._peekWord(); + word = readWord ? word : reader._peekWord()!; readWord = true; } else { readWord = false; @@ -199,8 +194,8 @@ class _PdfStringLayouter { lines, ln, lineWidth, - _getLineTypeValue(_LineType.newLineBreak) | - _getLineTypeValue(_LineType.lastParagraphLine)); + _getLineTypeValue(_LineType.newLineBreak)! | + _getLineTypeValue(_LineType.lastParagraphLine)!); } reader._close(); } @@ -211,9 +206,6 @@ class _PdfStringLayouter { void _addToLineResult(_PdfStringLayoutResult lineResult, List<_LineInfo> lines, String line, double lineWidth, int breakType) { - ArgumentError.checkNotNull(lineResult, 'lineResult'); - ArgumentError.checkNotNull(lines, 'lines'); - ArgumentError.checkNotNull(line, 'line'); final _LineInfo info = _LineInfo(); info.text = line; info.width = lineWidth; @@ -227,72 +219,69 @@ class _PdfStringLayouter { } List<_LineType> _getLineType(int breakType) { - ArgumentError.checkNotNull(breakType); final List<_LineType> result = <_LineType>[]; - if ((breakType & _getLineTypeValue(_LineType.none)) > 0) { + if ((breakType & _getLineTypeValue(_LineType.none)!) > 0) { result.add(_LineType.none); } - if ((breakType & _getLineTypeValue(_LineType.newLineBreak)) > 0) { + if ((breakType & _getLineTypeValue(_LineType.newLineBreak)!) > 0) { result.add(_LineType.newLineBreak); } - if ((breakType & _getLineTypeValue(_LineType.layoutBreak)) > 0) { + if ((breakType & _getLineTypeValue(_LineType.layoutBreak)!) > 0) { result.add(_LineType.layoutBreak); } - if (breakType & _getLineTypeValue(_LineType.firstParagraphLine) > 0) { + if (breakType & _getLineTypeValue(_LineType.firstParagraphLine)! > 0) { result.add(_LineType.firstParagraphLine); } - if (breakType & _getLineTypeValue(_LineType.lastParagraphLine) > 0) { + if (breakType & _getLineTypeValue(_LineType.lastParagraphLine)! > 0) { result.add(_LineType.lastParagraphLine); } return result; } double _getLineHeight() { - return (_format != null && _format.lineSpacing != 0) - ? _format.lineSpacing + _font.height - : _font.height; + return (_format != null && _format!.lineSpacing != 0) + ? _format!.lineSpacing + _font!.height + : _font!.height; } double _getLineWidth(String line) { - return _font._getLineWidth(line, _format); + return _font!._getLineWidth(line, _format); } - PdfWordWrapType _getWrapType() { - return _format != null ? _format.wordWrap : PdfWordWrapType.word; + PdfWordWrapType? _getWrapType() { + return _format != null ? _format!.wordWrap : PdfWordWrapType.word; } dynamic _copyToResult( _PdfStringLayoutResult result, _PdfStringLayoutResult lineResult, List<_LineInfo> lines, - int numInserted) { - ArgumentError.checkNotNull(result, 'result'); - ArgumentError.checkNotNull(result, 'lineResult'); - ArgumentError.checkNotNull(result, 'lines'); + int? numInserted) { bool success = true; - final bool allowPartialLines = _format != null && !_format.lineLimit; - double height = result._size.height; - double maxHeight = _size.height; + final bool allowPartialLines = _format != null && !_format!.lineLimit; + double? height = result._size.height; + double? maxHeight = _size.height; if ((_pageHeight > 0) && (maxHeight + _rectangle.y > _pageHeight)) { maxHeight = _rectangle.y - _pageHeight; maxHeight = maxHeight >= -maxHeight ? maxHeight : -maxHeight; } numInserted = 0; if (lineResult._lines != null) { - for (int i = 0; i < lineResult._lines.length; i++) { - final double expHeight = height + lineResult._lineHeight; + for (int i = 0; i < lineResult._lines!.length; i++) { + final double expHeight = height! + lineResult._lineHeight; if (expHeight <= maxHeight || maxHeight <= 0 || allowPartialLines) { - _LineInfo info = lineResult._lines[i]; + _LineInfo info = lineResult._lines![i]; if (!_isTabReplaced) { - numInserted += info.text.length; + numInserted = numInserted! + info.text!.length; } else { - numInserted += info.text.length - (_tabOccuranceCount * 3); + numInserted = + numInserted! + info.text!.length - (_tabOccuranceCount * 3); _isTabReplaced = false; } info = _trimLine(info, lines.isEmpty); lines.add(info); final _Size size = result._size; - size.width = size.width >= info.width ? size.width : info.width; + size.width = (size.width >= info.width!) ? size.width : info.width!; result._size = size; height = expHeight; } else { @@ -303,7 +292,7 @@ class _PdfStringLayouter { } if (height != result._size.height) { final _Size size = result._size; - size.height = height; + size.height = height!; result._size = size; } return {'success': success, 'numInserted': numInserted}; @@ -311,20 +300,20 @@ class _PdfStringLayouter { _LineInfo _trimLine(_LineInfo info, bool firstLine) { String line = info.text.toString(); - double lineWidth = info.width; + double? lineWidth = info.width; final bool start = _format == null || - _format.textDirection != PdfTextDirection.rightToLeft; + _format!.textDirection != PdfTextDirection.rightToLeft; if (!info.lineType.contains(_LineType.firstParagraphLine)) { line = start ? line.trimLeft() : line.trimRight(); } - if (_format == null || !_format.measureTrailingSpaces) { + if (_format == null || !_format!.measureTrailingSpaces) { line = start ? line.trimRight() : line.trimLeft(); } - if (line.length != info.text.length) { + if (line.length != info.text!.length) { lineWidth = _getLineWidth(line); - if (info._lineType & _getLineTypeValue(_LineType.firstParagraphLine) > + if (info._lineType & _getLineTypeValue(_LineType.firstParagraphLine)! > 0) { - lineWidth += _getLineIndent(firstLine); + lineWidth += _getLineIndent(firstLine)!; } } info.text = line; @@ -335,13 +324,13 @@ class _PdfStringLayouter { void _clear() { _font = null; _format = null; - _reader._close(); + _reader!._close(); _reader = null; _text = null; } - static int _getLineTypeValue(_LineType type) { - int value; + static int? _getLineTypeValue(_LineType type) { + int? value; switch (type) { case _LineType.none: value = 0; @@ -365,8 +354,8 @@ class _PdfStringLayouter { /// Provides a line information class _LineInfo { - String text; - double width; + String? text; + double? width; List<_LineType> lineType = <_LineType>[]; - int _lineType; + late int _lineType; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_true_type_font.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_true_type_font.dart index eb9ed2514..2cf558085 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_true_type_font.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_true_type_font.dart @@ -44,9 +44,7 @@ class PdfTrueTypeFont extends PdfFont { /// doc.dispose(); /// ``` PdfTrueTypeFont(List fontData, double size, - {PdfFontStyle style, List multiStyle}) { - ArgumentError.checkNotNull(fontData); - ArgumentError.checkNotNull(size); + {PdfFontStyle? style, List? multiStyle}) { _initializeFont(fontData, size, style, multiStyle); } @@ -76,8 +74,7 @@ class PdfTrueTypeFont extends PdfFont { /// doc.dispose(); /// ``` PdfTrueTypeFont.fromBase64String(String fontData, double size, - {PdfFontStyle style, List multiStyle}) { - ArgumentError.checkNotNull(fontData); + {PdfFontStyle? style, List? multiStyle}) { if (fontData.isEmpty) { throw ArgumentError.value(fontData, 'fontData', 'Invalid font data'); } @@ -85,12 +82,12 @@ class PdfTrueTypeFont extends PdfFont { } //Fields - bool _unicode; - _UnicodeTrueTypeFont _fontInternal; + bool? _unicode; + late _UnicodeTrueTypeFont _fontInternal; //Implementation - void _initializeFont(List fontData, double size, PdfFontStyle style, - List multiStyle) { + void _initializeFont(List fontData, double size, PdfFontStyle? style, + List? multiStyle) { _initialize(size, style: style, multiStyle: multiStyle); _unicode = true; _createFontInternals(fontData); @@ -103,19 +100,19 @@ class PdfTrueTypeFont extends PdfFont { } void _calculateStyle(PdfFontStyle style) { - int iStyle = _fontInternal._ttfMetrics.macStyle; + int? iStyle = _fontInternal._ttfMetrics!.macStyle; if (_isUnderline) { - iStyle |= PdfFont._getPdfFontStyle(PdfFontStyle.underline); + iStyle = iStyle! | PdfFont._getPdfFontStyle(PdfFontStyle.underline); } if (_isStrikeout) { - iStyle |= PdfFont._getPdfFontStyle(PdfFontStyle.strikethrough); + iStyle = iStyle! | PdfFont._getPdfFontStyle(PdfFontStyle.strikethrough); } - _fontStyle = iStyle; + _fontStyle = iStyle!; } void _initializeInternals() { _fontInternal._createInternals(); - final _IPdfPrimitive internals = _fontInternal._fontDictionary; + final _IPdfPrimitive? internals = _fontInternal._fontDictionary; _metrics = _fontInternal._metrics; if (internals == null) { throw ArgumentError.value(internals, 'font internal cannot be null'); @@ -127,24 +124,24 @@ class PdfTrueTypeFont extends PdfFont { _fontInternal._setSymbols(text); } - double _getCharWidth(String charCode, PdfStringFormat format) { + double _getCharWidth(String charCode, PdfStringFormat? format) { double codeWidth = _fontInternal._getCharWidth(charCode); - codeWidth *= 0.001 * _metrics._getSize(format); + codeWidth *= 0.001 * _metrics!._getSize(format)!; return codeWidth; } //_IPdfWrapper elements @override - _IPdfPrimitive get _element => _fontInternals; + _IPdfPrimitive? get _element => _fontInternals; @override //ignore: unused_element - set _element(_IPdfPrimitive value) { + set _element(_IPdfPrimitive? value) { _fontInternals = value; } @override - double _getLineWidth(String line, PdfStringFormat format) { + double _getLineWidth(String line, PdfStringFormat? format) { double width = 0; String text = line; if (format != null && format.textDirection != PdfTextDirection.none) { @@ -152,7 +149,7 @@ class PdfTrueTypeFont extends PdfFont { text = renderer.shape(line.split(''), 0); } width = _fontInternal._getLineWidth(text); - final double size = _metrics._getSize(format); + final double size = _metrics!._getSize(format)!; width *= 0.001 * size; width = _applyFormatSettings(text, format, width); return width; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/rtl/arabic_shape_renderer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/rtl/arabic_shape_renderer.dart index e16588b08..d6b19072f 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/rtl/arabic_shape_renderer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/rtl/arabic_shape_renderer.dart @@ -289,7 +289,7 @@ class _ArabicShapeRenderer { String getCharacterShape(String input, int index) { final int inputCode = input == '' ? 0 : input.codeUnitAt(0); if (inputCode >= hamza.codeUnitAt(0) && inputCode <= bwhb.codeUnitAt(0)) { - final List value = arabicMapTable[input]; + final List? value = arabicMapTable[input]; if (value != null) { return value[index + 1]; } @@ -307,7 +307,7 @@ class _ArabicShapeRenderer { !((value >= fathatan.codeUnitAt(0) && value <= hamzaBelow.codeUnitAt(0)) || shape == superscriptAlef)) { - final List c = arabicMapTable[shape]; + final List? c = arabicMapTable[shape]; if (c != null) { return c.length - 1; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/rtl/bidi.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/rtl/bidi.dart index 581a04866..d0dceb7d7 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/rtl/bidi.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/rtl/bidi.dart @@ -8,14 +8,14 @@ class _Bidi { } // Fields. - List indexes; - List indexLevels; + late List indexes; + late List indexLevels; Map mirroringShapeCharacters = {}; // Implementations. String getLogicalToVisualString(String inputText, bool isRTL) { - indexLevels = List(inputText.length); - indexes = List(inputText.length); + indexLevels = List.filled(inputText.length, 0, growable: true); + indexes = List.filled(inputText.length, 0, growable: true); final _RTLCharacters rtlCharacters = _RTLCharacters(); indexLevels = rtlCharacters.getVisualOrder(inputText, isRTL); setDefaultIndexLevel(); @@ -35,7 +35,7 @@ class _Bidi { if (((indexLevels[i] & 1) == 1) && mirroringShapeCharacters.containsKey(text[i].codeUnitAt(0))) { result.write(String.fromCharCode( - mirroringShapeCharacters[text[i].codeUnitAt(0)])); + mirroringShapeCharacters[text[i].codeUnitAt(0)]!)); } else { result.write(text[i]); } @@ -445,17 +445,18 @@ class _RTLCharacters { // Constants and Fields // Specifies the character types. - List types; //sbyte + late List types; //sbyte // Specifies the text order (RTL or LTR). int textOrder = -1; //sbyte // Specifies the text length. - int length; + int? length; // Specifies the resultant types. - List result; //sbyte + late List result; //sbyte // Specifies the resultant levels. - List levels; //sbyte + late List levels; //sbyte // Specifies the RTL character types. - List rtlCharacterTypes = List(65536); //sbyte + List rtlCharacterTypes = + List.filled(65536, 0, growable: true); //sbyte // Left-to-Right (Non-European or non-Arabic digits). static const int l = 0; @@ -2234,7 +2235,8 @@ class _RTLCharacters { types = getCharacterCode(inputText); textOrder = isRTL ? lre : l; doVisualOrder(); - final List result = List(this.result.length); + final List result = + List.filled(this.result.length, 0, growable: true); for (int i = 0; i < levels.length; i++) { result[i] = levels[i].toUnsigned(8); } @@ -2242,7 +2244,8 @@ class _RTLCharacters { } List getCharacterCode(String text) { - final List characterCodes = List(text.length); + final List characterCodes = + List.filled(text.length, 0, growable: true); for (int i = 0; i < text.length; i++) { characterCodes[i] = rtlCharacterTypes[text[i].codeUnitAt(0)]; } @@ -2253,32 +2256,32 @@ class _RTLCharacters { length = types.length; //ignore:prefer_spread_collections result = []..addAll(types); - levels = List(length); + levels = List.filled(length!, 0, growable: true); setLevels(); length = getEmbeddedCharactersLength(); int preview = textOrder; int i = 0; - while (i < length) { + while (i < length!) { final int level = levels[i]; final int preType = (([preview, level].reduce(max) & 0x1) == 0) ? l : r; int lengths = i + 1; - while (lengths < length && levels[lengths] == level) { + while (lengths < length! && levels[lengths] == level) { ++lengths; } - final int success = lengths < length ? levels[lengths] : textOrder; + final int success = lengths < length! ? levels[lengths] : textOrder; final int type = (([success, level].reduce(max) & 0x1) == 0) ? l : r; - checkNSM(i, length, level, preType, type); + checkNSM(i, length!, level, preType, type); updateLevels(i, level, length); preview = level; - i = length; + i = length!; } checkEmbeddedCharacters(length); } - void updateLevels(int index, int level, int length) { + void updateLevels(int index, int level, int? length) { if ((level & 1) == 0) { - for (int i = index; i < length; ++i) { + for (int i = index; i < length!; ++i) { if (result[i] == r) { levels[i] += 1; } else if (result[i] != l) { @@ -2286,7 +2289,7 @@ class _RTLCharacters { } } } else { - for (int i = index; i < length; ++i) { + for (int i = index; i < length!; ++i) { if (result[i] != r) { levels[i] += 1; } @@ -2296,7 +2299,7 @@ class _RTLCharacters { void setLevels() { setDefaultLevels(); - for (int n = 0; n < length; ++n) { + for (int n = 0; n < length!; ++n) { int level = levels[n].toUnsigned(8); if ((level & 0x80) != 0) { level &= 0x7f; @@ -2307,14 +2310,14 @@ class _RTLCharacters { } void setDefaultLevels() { - for (int i = 0; i < length; i++) { + for (int i = 0; i < length!; i++) { levels[i] = textOrder; } } int getEmbeddedCharactersLength() { int index = 0; - for (int i = 0; i < length; ++i) { + for (int i = 0; i < length!; ++i) { if (!(types[i] == lre || types[i] == rle || types[i] == lro || @@ -2329,7 +2332,7 @@ class _RTLCharacters { return index; } - void checkEmbeddedCharacters(int length) { + void checkEmbeddedCharacters(int? length) { for (int i = types.length - 1; i >= 0; --i) { if (types[i] == lre || types[i] == rle || @@ -2340,7 +2343,7 @@ class _RTLCharacters { result[i] = types[i]; levels[i] = -1; } else { - length -= 1; + length = length! - 1; result[i] = result[length]; levels[i] = levels[length]; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/string_tokenizer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/string_tokenizer.dart index 1d50aa210..c664960cd 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/string_tokenizer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/string_tokenizer.dart @@ -4,7 +4,6 @@ part of pdf; class _StringTokenizer { //Constructor _StringTokenizer(String text) { - ArgumentError.checkNotNull(text, 'text'); _text = text; _position = 0; } @@ -15,68 +14,68 @@ class _StringTokenizer { static const List _spaces = [_whiteSpace, _tab]; //Fields - String _text; - int _position; + String? _text; + int? _position; //Properties - bool get _isEndOfFile => _position == _text.length; - int get _length => _text.length; + bool get _isEndOfFile => _position == _text!.length; + int get _length => _text!.length; //Implementation - String _peekLine() { - final int position = _position; - final String line = _readLine(); + String? _peekLine() { + final int? position = _position; + final String? line = _readLine(); _position = position; return line; } - String _readLine() { - int position = _position; + String? _readLine() { + int position = _position!; while (position < _length) { - final String character = _text[position]; + final String character = _text![position]; switch (character) { case '\r': case '\n': { - final String text = _text.substring(_position, position); + final String text = _text!.substring(_position!, position); _position = position + 1; - if (((character == '\r') && (_position < _length)) && - (_text[_position] == '\n')) { - _position++; + if (((character == '\r') && (_position! < _length)) && + (_text![_position!] == '\n')) { + _position = _position! + 1; } return text; } } position++; } - if (position > _position) { - final String result = _text.substring(_position, position); + if (position > _position!) { + final String result = _text!.substring(_position!, position); _position = position; return result; } return null; } - String _peekWord() { - final int position = _position; - final String word = _readWord(); + String? _peekWord() { + final int? position = _position; + final String? word = _readWord(); _position = position; return word; } - String _readWord() { - int position = _position; + String? _readWord() { + int position = _position!; while (position < _length) { - final String character = _text[position]; + final String character = _text![position]; switch (character) { case '\r': case '\n': { - final String text = _text.substring(_position, position); + final String text = _text!.substring(_position!, position); _position = position + 1; - if (((character == '\r') && (_position < _length)) && - (_text[_position] == '\n')) { - _position++; + if (((character == '\r') && (_position! < _length)) && + (_text![_position!] == '\n')) { + _position = _position! + 1; } return text; } @@ -86,15 +85,15 @@ class _StringTokenizer { if (position == _position) { position++; } - final String text = _text.substring(_position, position); + final String text = _text!.substring(_position!, position); _position = position; return text; } } position++; } - if (position > _position) { - final String result = _text.substring(_position, position); + if (position > _position!) { + final String result = _text!.substring(_position!, position); _position = position; return result; } @@ -102,10 +101,10 @@ class _StringTokenizer { } String _peek() { - return (!_isEndOfFile) ? _text[_position] : '\0'; + return (!_isEndOfFile) ? _text![_position!] : '\0'; } - String _read([int count]) { + String _read([int? count]) { if (count != null) { int length = 0; String builder = ''; @@ -118,8 +117,8 @@ class _StringTokenizer { } else { String character = '\0'; if (!_isEndOfFile) { - character = _text[_position]; - _position++; + character = _text![_position!]; + _position = _position! + 1; } return character; } @@ -131,8 +130,6 @@ class _StringTokenizer { } static int _getCharacterCount(String text, List symbols) { - ArgumentError.checkNotNull(text, 'text'); - ArgumentError.checkNotNull(symbols, 'symbols'); int count = 0; for (int i = 0; i < text.length; i++) { final String character = text[i]; @@ -154,9 +151,9 @@ class _StringTokenizer { return contains; } - String _readToEnd() { - final String text = - _position == 0 ? _text : _text.substring(_position, _length); + String? _readToEnd() { + final String? text = + _position == 0 ? _text : _text!.substring(_position!, _length); _position = _length; return text; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/ttf_helper.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/ttf_helper.dart index 3f29f212e..cac715da2 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/ttf_helper.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/ttf_helper.dart @@ -2,38 +2,38 @@ part of pdf; class _TtfHeadTable { /// Modified: International date (8-byte field). - int modified; + int? modified; /// Created: International date (8-byte field). - int created; + int? created; /// MagicNumber: Set to 0x5F0F3CF5. - int magicNumber; + int? magicNumber; /// CheckSumAdjustment: To compute: set it to 0, sum the entire font as ULONG, /// then store 0xB1B0AFBA - sum. - int checkSumAdjustment; + int? checkSumAdjustment; /// FontRevision: Set by font manufacturer. - double fontRevision; + double? fontRevision; /// Table version number: 0x00010000 for version 1.0. - double version; + double? version; /// Minimum x for all glyph bounding boxes. - int xMin; + late int xMin; /// Minimum y for all glyph bounding boxes. - int yMin; + int? yMin; /// Valid range is from 16 to 16384. - int unitsPerEm; + int? unitsPerEm; /// Maximum y for all glyph bounding boxes. - int yMax; + int? yMax; /// Maximum x for all glyph bounding boxes. - int xMax; + late int xMax; /// Regular: 0 /// Bold: 1 @@ -47,7 +47,7 @@ class _TtfHeadTable { /// The fsSelection bits are used over the macStyle bits in Microsoft Windows. /// The PANOSE values and 'post' table values are ignored /// for determining bold or italic fonts. - int macStyle; + int? macStyle; /// Bit 0 - baseline for font at y=0 /// Bit 1 - left SideBearing at x=0 @@ -57,10 +57,10 @@ class _TtfHeadTable { /// Bit 4 - instructions may alter advance width /// (the advance widths might not scale linearly) /// Note: All other bits must be zero. - int flags; + int? flags; /// LowestRecPPEM: Smallest readable size in pixels. - int lowestReadableSize; + int? lowestReadableSize; /// FontDirectionHint: /// 0 Fully mixed directional glyphs @@ -68,97 +68,97 @@ class _TtfHeadTable { /// 2 Like 1 but also contains neutrals /// -1 Only strongly right to left /// -2 Like -1 but also contains neutrals. - int fontDirectionHint; + int? fontDirectionHint; /// 0 for short offsets, 1 for long. - int indexToLocalFormat; + int? indexToLocalFormat; /// 0 for current format. - int glyphDataFormat; + int? glyphDataFormat; } class _TtfHorizontalHeaderTable { /// Version. - double version; + double? version; /// Typographic ascent. - int ascender; + late int ascender; /// Maximum advance width value in HTML table. - int advanceWidthMax; + int? advanceWidthMax; /// Typographic descent. - int descender; + late int descender; /// Number of hMetric entries in HTML table; /// may be smaller than the total number of glyphs in the font. - int numberOfHMetrics; + late int numberOfHMetrics; /// Typographic line gap. /// Negative LineGap values are treated as DEF_TABLE_CHECKSUM /// in Windows 3.1, System 6, and System 7. - int lineGap; + late int lineGap; /// Minimum left SideBearing value in HTML table. - int minLeftSideBearing; + int? minLeftSideBearing; /// Minimum right SideBearing value; /// calculated as Min(aw - lsb - (xMax - xMin)). - int minRightSideBearing; + int? minRightSideBearing; /// Max(lsb + (xMax - xMin)). - int xMaxExtent; + int? xMaxExtent; /// Used to calculate the slope of the cursor (rise/run); 1 for vertical. - int caretSlopeRise; + int? caretSlopeRise; /// 0 for vertical. - int caretSlopeRun; + int? caretSlopeRun; /// 0 for current format. - int metricDataFormat; + int? metricDataFormat; } /// name ttf table. class _TtfNameTable { /// Local variable to store Format Selector. - int formatSelector; + int? formatSelector; /// Local variable to store Records Count. - int recordsCount; + late int recordsCount; /// Local variable to store Offset. - int offset; + late int offset; /// Local variable to store Name Records. - List<_TtfNameRecord> nameRecords; + late List<_TtfNameRecord> nameRecords; } class _TtfNameRecord { /// The PlatformID. - int platformID; + int? platformID; /// The EncodingID. - int encodingID; + int? encodingID; /// The PlatformIDLanguageID - int languageID; + int? languageID; /// The NameID. - int nameID; + int? nameID; /// The Length. - int length; + int? length; /// The Offset. - int offset; + late int offset; /// The Name. - String name; + String? name; } class _TtfOS2Table { - int version; + late int version; /// The Average Character Width parameter specifies /// the arithmetic average of the escapement (width) @@ -167,15 +167,15 @@ class _TtfOS2Table { /// present, this parameter should equal the weighted average of all /// glyphs in the font. /// For non-UGL (platform 3, encoding 0) fonts, use the unweighted average. - int xAvgCharWidth; + int? xAvgCharWidth; /// Indicates the visual weight (degree of blackness or thickness of strokes) /// of the characters in the font. - int usWeightClass; + int? usWeightClass; /// Indicates a relative change from the normal aspect ratio (width to /// height ratio) as specified by a font designer for the glyphs in a font. - int usWidthClass; + int? usWidthClass; /// Indicates font embedding licensing rights for the font. /// Embeddable fonts may be stored in a document. @@ -185,49 +185,49 @@ class _TtfOS2Table { /// (and in some cases, permanent) use on that system by an embedding-aware /// application. /// Embedding licensing rights are granted by the vendor of the font. - int fsType; + int? fsType; /// The recommended horizontal size in font design units /// for subscripts for this font. - int ySubscriptXSize; + int? ySubscriptXSize; /// The recommended vertical size in font design units /// for subscripts for this font. - int ySubscriptYSize; + late int ySubscriptYSize; /// The recommended horizontal offset in font design units /// for subscripts for this font. - int ySubscriptXOffset; + int? ySubscriptXOffset; /// The recommended vertical offset in font design units from the baseline /// for subscripts for this font. - int ySubscriptYOffset; + int? ySubscriptYOffset; /// The recommended horizontal size in font design units /// for superscripts for this font. - int ySuperscriptXSize; + int? ySuperscriptXSize; /// The recommended vertical size in font design units /// for superscripts for this font. - int ySuperscriptYSize; + late int ySuperscriptYSize; /// The recommended horizontal offset in font design units /// for superscripts for this font. - int ySuperscriptXOffset; + int? ySuperscriptXOffset; /// The recommended vertical offset in font design units from the baseline /// for superscripts for this font. - int ySuperscriptYOffset; + int? ySuperscriptYOffset; /// Width of the strikethrough stroke in font design units. - int yStrikeoutSize; + int? yStrikeoutSize; /// The position of the strikethrough stroke relative to the baseline /// in font design units. - int yStrikeoutPosition; + int? yStrikeoutPosition; /// This parameter is a classification of font-family design. - int sFamilyClass; + int? sFamilyClass; /// This 10 byte series of numbers are used to describe the visual /// characteristics of a given typeface. @@ -236,36 +236,36 @@ class _TtfOS2Table { /// The variables for each digit are listed below. /// The specifications for each variable can be obtained in the specification /// PANOSE v2.0 Numerical Evaluation from Microsoft or Elseware Corporation. - List panose; + List? panose; - int ulUnicodeRange1; - int ulUnicodeRange2; - int ulUnicodeRange3; - int ulUnicodeRange4; + int? ulUnicodeRange1; + int? ulUnicodeRange2; + int? ulUnicodeRange3; + int? ulUnicodeRange4; /// The four character identifier for the vendor of the given type face. - List vendorIdentifier; + List? vendorIdentifier; /// Information concerning the nature of the font patterns. - int fsSelection; + int? fsSelection; /// The minimum Unicode index (character code) in this font, /// according to the cmap subtable for platform ID 3 and encoding ID 0 or 1. /// For most fonts supporting Win-ANSI or other character sets, /// this value would be 0x0020. - int usFirstCharIndex; + int? usFirstCharIndex; /// usLastCharIndex: The maximum Unicode index (character code) in this font, /// according to the cmap subtable for platform ID 3 and encoding ID 0 or 1. /// This value depends on which character sets the font supports. - int usLastCharIndex; + int? usLastCharIndex; /// The typographic ascender for this font. /// Remember that this is not the same as the Ascender value /// in the 'hhea' table, which Apple defines in a far different manner. /// DEF_TABLE_OFFSET good source for usTypoAscender is the Ascender value /// from an AFM file. - int sTypoAscender; + late int sTypoAscender; /// The typographic descender for this font. /// Remember that this is not the same as the Descender value @@ -273,12 +273,12 @@ class _TtfOS2Table { /// which Apple defines in a far different manner. /// DEF_TABLE_OFFSET good source for usTypoDescender is the Descender value /// from an AFM file. - int sTypoDescender; + late int sTypoDescender; /// The typographic line gap for this font. /// Remember that this is not the same as the LineGap value in the /// 'hhea' table, which Apple defines in a far different manner. - int sTypoLineGap; + late int sTypoLineGap; /// The ascender metric for Windows. /// This too is distinct from Apple's Ascender value and from the @@ -287,7 +287,7 @@ class _TtfOS2Table { /// usTypoAscent is used to compute the Windows font height and /// default line spacing. /// For platform 3 encoding 0 fonts, it is the same as yMax. - int usWinAscent; + int? usWinAscent; /// The descender metric for Windows. /// This too is distinct from Apple's Descender value and @@ -297,7 +297,7 @@ class _TtfOS2Table { /// usTypoAscent is used to compute the Windows font height and /// default line spacing. /// For platform 3 encoding 0 fonts, it is the same as -yMin. - int usWinDescent; + int? usWinDescent; /// This field is used to specify the code pages encompassed /// by the font file in the 'cmap' subtable for platform 3, @@ -311,7 +311,7 @@ class _TtfOS2Table { /// The determination of "functional" is left up to the font designer, /// although character set selection should attempt to be functional /// by code pages if at all possible. - int ulCodePageRange1; + int? ulCodePageRange1; /// This field is used to specify the code pages encompassed /// by the font file in the 'cmap' subtable for platform 3, @@ -325,24 +325,24 @@ class _TtfOS2Table { /// The determination of "functional" is left up to the font designer, /// although character set selection should attempt to be functional by /// code pages if at all possible. - int ulCodePageRange2; + int? ulCodePageRange2; - int sxHeight; - int sCapHeight; - int usDefaultChar; - int usBreakChar; - int usMaxContext; + int? sxHeight; + int? sCapHeight; + int? usDefaultChar; + int? usBreakChar; + int? usMaxContext; } class _TtfTableInfo { /// Gets or sets ofset from beginning of TrueType font file. - int offset; + int? offset; /// Gets or sets length of this table. - int length; + int? length; /// Gets or sets table checksum. - int checksum; + int? checksum; /// Gets a value indicating whether this [TtfTableInfo] is empty. /// true if empty, otherwise false @@ -353,37 +353,37 @@ class _TtfTableInfo { } class _TtfPostTable { - double formatType; - double italicAngle; - int underlinePosition; - int underlineThickness; - int isFixedPitch; - int minType42; - int maxType42; - int minType1; - int maxType1; + double? formatType; + double? italicAngle; + int? underlinePosition; + int? underlineThickness; + int? isFixedPitch; + int? minType42; + int? maxType42; + int? minType1; + int? maxType1; } class _TtfCmapSubTable { - int platformID; - int encodingID; - int offset; + int? platformID; + int? encodingID; + late int offset; } class _TtfCmapTable { - int version; - int tablesCount; + int? version; + late int tablesCount; } class _TtfLongHorMetric { - int advanceWidth; - int lsb; + late int advanceWidth; + int? lsb; } class _TtfAppleCmapSubTable { - int format; - int length; - int version; + int? format; + int? length; + int? version; } class _TtfGlyphInfo { @@ -403,33 +403,33 @@ class _TtfGlyphInfo { } class _TtfMicrosoftCmapSubTable { - int format; - int length; - int version; - int segCountX2; - int searchRange; - int entrySelector; - int rangeShift; - List endCount; - int reservedPad; - List startCount; - List idDelta; - List idRangeOffset; - List glyphID; + int? format; + late int length; + int? version; + late int segCountX2; + int? searchRange; + int? entrySelector; + int? rangeShift; + late List endCount; + int? reservedPad; + late List startCount; + late List idDelta; + late List idRangeOffset; + late List glyphID; } class _TtfTrimmedCmapSubTable { - int format; - int length; - int version; - int firstCode; - int entryCount; + int? format; + int? length; + int? version; + late int firstCode; + late int entryCount; } class _TtfGlyphHeader { - int numberOfContours; - int xMin; - int yMin; - int xMax; - int yMax; + late int numberOfContours; + int? xMin; + int? yMin; + int? xMax; + int? yMax; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/ttf_metrics.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/ttf_metrics.dart index f66a041bb..b2ca49160 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/ttf_metrics.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/ttf_metrics.dart @@ -1,26 +1,26 @@ part of pdf; class _TtfMetrics { - int lineGap; - bool contains; - bool isSymbol; - _Rectangle fontBox; - bool isFixedPitch; - double italicAngle; - String postScriptName; - String fontFamily; - double capHeight; - double leading; - double macAscent; - double macDescent; - double winDescent; - double winAscent; - double stemV; - List widthTable; - int macStyle; - double subscriptSizeFactor; - double superscriptSizeFactor; + int? lineGap; + bool? contains; + late bool isSymbol; + late _Rectangle fontBox; + late bool isFixedPitch; + double? italicAngle; + String? postScriptName; + String? fontFamily; + late double capHeight; + late double leading; + double macAscent = 0; + double macDescent = 0; + late double winDescent; + late double winAscent; + late double stemV; + late List widthTable; + int? macStyle; + double? subscriptSizeFactor; + double? superscriptSizeFactor; //Properties - bool get isItalic => macStyle & 2 != 0; - bool get isBold => macStyle & 1 != 0; + bool get isItalic => macStyle! & 2 != 0; + bool get isBold => macStyle! & 1 != 0; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/ttf_reader.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/ttf_reader.dart index bb950aa2d..692f6cc4b 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/ttf_reader.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/ttf_reader.dart @@ -44,38 +44,38 @@ class _TtfReader { ]; //Fields - List _fontData; - _TtfMetrics _metrics; - int _offset; - bool _isMacTtf; - bool _isTtcFont; - Map _tableDirectory; - Map _macintoshDirectory; - Map _microsoftDirectory; - Map _macintoshGlyphInfoCollection; - Map _microsoftGlyphInfoCollection; - int _lowestPosition; - bool _isLocaShort; - List _width; - int _maxMacIndex; + late List _fontData; + _TtfMetrics? _metrics; + int? _offset; + late bool _isMacTtf; + late bool _isTtcFont; + Map? _tableDirectory; + Map? _macintoshDirectory; + Map? _microsoftDirectory; + Map? _macintoshGlyphInfoCollection; + Map? _microsoftGlyphInfoCollection; + int? _lowestPosition; + late bool _isLocaShort; + late List _width; + int? _maxMacIndex; //Properties - Map get _macintosh { + Map? get _macintosh { _macintoshDirectory ??= {}; return _macintoshDirectory; } - Map get _microsoft { + Map? get _microsoft { _microsoftDirectory ??= {}; return _microsoftDirectory; } - Map get _macintoshGlyphs { + Map? get _macintoshGlyphs { _macintoshGlyphInfoCollection ??= {}; return _macintoshGlyphInfoCollection; } - Map get _microsoftGlyphs { + Map? get _microsoftGlyphs { _microsoftGlyphInfoCollection ??= {}; return _microsoftGlyphInfoCollection; } @@ -89,27 +89,27 @@ class _TtfReader { final _TtfNameTable nameTable = _readNameTable(); final _TtfHeadTable headTable = _readHeadTable(); _initializeFontName(nameTable); - _metrics.macStyle = headTable.macStyle; + _metrics!.macStyle = headTable.macStyle; } void _readFontDictionary() { _offset = 0; _checkPreambula(); - final int numTables = _readInt16(_offset); + final int numTables = _readInt16(_offset!); //searchRange - _readInt16(_offset); + _readInt16(_offset!); //entrySelector - _readInt16(_offset); + _readInt16(_offset!); //rangeShift - _readInt16(_offset); + _readInt16(_offset!); _tableDirectory ??= {}; for (int i = 0; i < numTables; ++i) { final _TtfTableInfo table = _TtfTableInfo(); final String tableKey = _readString(_int32Size); - table.checksum = _readInt32(_offset); - table.offset = _readInt32(_offset); - table.length = _readInt32(_offset); - _tableDirectory[tableKey] = table; + table.checksum = _readInt32(_offset!); + table.offset = _readInt32(_offset!); + table.length = _readInt32(_offset!); + _tableDirectory![tableKey] = table; } _lowestPosition = _offset; if (!_isTtcFont) { @@ -118,7 +118,7 @@ class _TtfReader { } void _checkPreambula() { - final int version = _readInt32(_offset); + final int version = _readInt32(_offset!); _isMacTtf = version == 0x74727565; if (version != 0x10000 && !_isMacTtf && version != 0x4f54544f) { _isTtcFont = true; @@ -126,37 +126,37 @@ class _TtfReader { if (_readString(4) != 'ttcf') { throw UnsupportedError('Can not read TTF font data'); } - _offset += 4; - if (_readInt32(_offset) < 0) { + _offset = _offset! + 4; + if (_readInt32(_offset!) < 0) { throw UnsupportedError('Can not read TTF font data'); } //Offset for version - _offset = _readInt32(_offset); + _offset = _readInt32(_offset!); //Version - _readInt32(_offset); + _readInt32(_offset!); } } void _fixOffsets() { - int minOffset = pow(2, 53); + int minOffset = pow(2, 53) as int; // Search for a smallest offset and compare it // with the lowest position found. - final List keys = _tableDirectory.keys.toList(); + final List keys = _tableDirectory!.keys.toList(); for (int i = 0; i < keys.length; i++) { - final int offset = _tableDirectory[keys[i]].offset; + final int offset = _tableDirectory![keys[i]]!.offset!; if (minOffset > offset) { minOffset = offset; - if (minOffset <= _lowestPosition) { + if (minOffset <= _lowestPosition!) { break; } } } - final int shift = minOffset - _lowestPosition; + final int shift = minOffset - _lowestPosition!; if (shift != 0) { final Map table = {}; for (int i = 0; i < keys.length; i++) { - final _TtfTableInfo value = _tableDirectory[keys[i]]; - value.offset -= shift; + final _TtfTableInfo value = _tableDirectory![keys[i]]!; + value.offset = value.offset! - shift; table[keys[i]] = value; } _tableDirectory = table; @@ -164,119 +164,119 @@ class _TtfReader { } _TtfNameTable _readNameTable() { - final _TtfTableInfo tableInfo = _getTable('name'); + final _TtfTableInfo tableInfo = _getTable('name')!; _offset = tableInfo.offset; final _TtfNameTable table = _TtfNameTable(); - table.formatSelector = _readUInt16(_offset); - table.recordsCount = _readUInt16(_offset); - table.offset = _readUInt16(_offset); + table.formatSelector = _readUInt16(_offset!); + table.recordsCount = _readUInt16(_offset!); + table.offset = _readUInt16(_offset!); table.nameRecords = <_TtfNameRecord>[]; const int recordSize = 12; - int position = _offset; + int? position = _offset; for (int i = 0; i < table.recordsCount; i++) { _offset = position; final _TtfNameRecord record = _TtfNameRecord(); - record.platformID = _readUInt16(_offset); - record.encodingID = _readUInt16(_offset); - record.languageID = _readUInt16(_offset); - record.nameID = _readUInt16(_offset); - record.length = _readUInt16(_offset); - record.offset = _readUInt16(_offset); - _offset = tableInfo.offset + table.offset + record.offset; + record.platformID = _readUInt16(_offset!); + record.encodingID = _readUInt16(_offset!); + record.languageID = _readUInt16(_offset!); + record.nameID = _readUInt16(_offset!); + record.length = _readUInt16(_offset!); + record.offset = _readUInt16(_offset!); + _offset = tableInfo.offset! + table.offset + record.offset; final bool isUnicode = record.platformID == 0 || record.platformID == 3; record.name = _readString(record.length, isUnicode); table.nameRecords.add(record); - position += recordSize; + position = position! + recordSize; } return table; } _TtfHeadTable _readHeadTable() { - final _TtfTableInfo tableInfo = _getTable('head'); + final _TtfTableInfo tableInfo = _getTable('head')!; _offset = tableInfo.offset; final _TtfHeadTable table = _TtfHeadTable(); - table.version = _readFixed(_offset); - table.fontRevision = _readFixed(_offset); - table.checkSumAdjustment = _readUInt32(_offset); - table.magicNumber = _readUInt32(_offset); - table.flags = _readUInt16(_offset); - table.unitsPerEm = _readUInt16(_offset); - table.created = _readInt64(_offset); - table.modified = _readInt64(_offset); - table.xMin = _readInt16(_offset); - table.yMin = _readInt16(_offset); - table.xMax = _readInt16(_offset); - table.yMax = _readInt16(_offset); - table.macStyle = _readUInt16(_offset); - table.lowestReadableSize = _readUInt16(_offset); - table.fontDirectionHint = _readInt16(_offset); - table.indexToLocalFormat = _readInt16(_offset); - table.glyphDataFormat = _readInt16(_offset); + table.version = _readFixed(_offset!); + table.fontRevision = _readFixed(_offset!); + table.checkSumAdjustment = _readUInt32(_offset!); + table.magicNumber = _readUInt32(_offset!); + table.flags = _readUInt16(_offset!); + table.unitsPerEm = _readUInt16(_offset!); + table.created = _readInt64(_offset!); + table.modified = _readInt64(_offset!); + table.xMin = _readInt16(_offset!); + table.yMin = _readInt16(_offset!); + table.xMax = _readInt16(_offset!); + table.yMax = _readInt16(_offset!); + table.macStyle = _readUInt16(_offset!); + table.lowestReadableSize = _readUInt16(_offset!); + table.fontDirectionHint = _readInt16(_offset!); + table.indexToLocalFormat = _readInt16(_offset!); + table.glyphDataFormat = _readInt16(_offset!); return table; } _TtfHorizontalHeaderTable _readHorizontalHeaderTable() { - final _TtfTableInfo tableInfo = _getTable('hhea'); + final _TtfTableInfo tableInfo = _getTable('hhea')!; _offset = tableInfo.offset; final _TtfHorizontalHeaderTable table = _TtfHorizontalHeaderTable(); - table.version = _readFixed(_offset); - table.ascender = _readInt16(_offset); - table.descender = _readInt16(_offset); - table.lineGap = _readInt16(_offset); - table.advanceWidthMax = _readUInt16(_offset); - table.minLeftSideBearing = _readInt16(_offset); - table.minRightSideBearing = _readInt16(_offset); - table.xMaxExtent = _readInt16(_offset); - table.caretSlopeRise = _readInt16(_offset); - table.caretSlopeRun = _readInt16(_offset); - _offset += 10; - table.metricDataFormat = _readInt16(_offset); - table.numberOfHMetrics = _readUInt16(_offset); + table.version = _readFixed(_offset!); + table.ascender = _readInt16(_offset!); + table.descender = _readInt16(_offset!); + table.lineGap = _readInt16(_offset!); + table.advanceWidthMax = _readUInt16(_offset!); + table.minLeftSideBearing = _readInt16(_offset!); + table.minRightSideBearing = _readInt16(_offset!); + table.xMaxExtent = _readInt16(_offset!); + table.caretSlopeRise = _readInt16(_offset!); + table.caretSlopeRun = _readInt16(_offset!); + _offset = _offset! + 10; + table.metricDataFormat = _readInt16(_offset!); + table.numberOfHMetrics = _readUInt16(_offset!); return table; } _TtfOS2Table _readOS2Table() { - final _TtfTableInfo tableInfo = _getTable('OS/2'); + final _TtfTableInfo tableInfo = _getTable('OS/2')!; _offset = tableInfo.offset; final _TtfOS2Table table = _TtfOS2Table(); - table.version = _readUInt16(_offset); - table.xAvgCharWidth = _readInt16(_offset); - table.usWeightClass = _readUInt16(_offset); - table.usWidthClass = _readUInt16(_offset); - table.fsType = _readInt16(_offset); - table.ySubscriptXSize = _readInt16(_offset); - table.ySubscriptYSize = _readInt16(_offset); - table.ySubscriptXOffset = _readInt16(_offset); - table.ySubscriptYOffset = _readInt16(_offset); - table.ySuperscriptXSize = _readInt16(_offset); - table.ySuperscriptYSize = _readInt16(_offset); - table.ySuperscriptXOffset = _readInt16(_offset); - table.ySuperscriptYOffset = _readInt16(_offset); - table.yStrikeoutSize = _readInt16(_offset); - table.yStrikeoutPosition = _readInt16(_offset); - table.sFamilyClass = _readInt16(_offset); + table.version = _readUInt16(_offset!); + table.xAvgCharWidth = _readInt16(_offset!); + table.usWeightClass = _readUInt16(_offset!); + table.usWidthClass = _readUInt16(_offset!); + table.fsType = _readInt16(_offset!); + table.ySubscriptXSize = _readInt16(_offset!); + table.ySubscriptYSize = _readInt16(_offset!); + table.ySubscriptXOffset = _readInt16(_offset!); + table.ySubscriptYOffset = _readInt16(_offset!); + table.ySuperscriptXSize = _readInt16(_offset!); + table.ySuperscriptYSize = _readInt16(_offset!); + table.ySuperscriptXOffset = _readInt16(_offset!); + table.ySuperscriptYOffset = _readInt16(_offset!); + table.yStrikeoutSize = _readInt16(_offset!); + table.yStrikeoutPosition = _readInt16(_offset!); + table.sFamilyClass = _readInt16(_offset!); table.panose = _readBytes(10); - table.ulUnicodeRange1 = _readUInt32(_offset); - table.ulUnicodeRange2 = _readUInt32(_offset); - table.ulUnicodeRange3 = _readUInt32(_offset); - table.ulUnicodeRange4 = _readUInt32(_offset); + table.ulUnicodeRange1 = _readUInt32(_offset!); + table.ulUnicodeRange2 = _readUInt32(_offset!); + table.ulUnicodeRange3 = _readUInt32(_offset!); + table.ulUnicodeRange4 = _readUInt32(_offset!); table.vendorIdentifier = _readBytes(4); - table.fsSelection = _readUInt16(_offset); - table.usFirstCharIndex = _readUInt16(_offset); - table.usLastCharIndex = _readUInt16(_offset); - table.sTypoAscender = _readInt16(_offset); - table.sTypoDescender = _readInt16(_offset); - table.sTypoLineGap = _readInt16(_offset); - table.usWinAscent = _readUInt16(_offset); - table.usWinDescent = _readUInt16(_offset); - table.ulCodePageRange1 = _readUInt32(_offset); - table.ulCodePageRange2 = _readUInt32(_offset); + table.fsSelection = _readUInt16(_offset!); + table.usFirstCharIndex = _readUInt16(_offset!); + table.usLastCharIndex = _readUInt16(_offset!); + table.sTypoAscender = _readInt16(_offset!); + table.sTypoDescender = _readInt16(_offset!); + table.sTypoLineGap = _readInt16(_offset!); + table.usWinAscent = _readUInt16(_offset!); + table.usWinDescent = _readUInt16(_offset!); + table.ulCodePageRange1 = _readUInt32(_offset!); + table.ulCodePageRange2 = _readUInt32(_offset!); if (table.version > 1) { - table.sxHeight = _readInt16(_offset); - table.sCapHeight = _readInt16(_offset); - table.usDefaultChar = _readUInt16(_offset); - table.usBreakChar = _readUInt16(_offset); - table.usMaxContext = _readUInt16(_offset); + table.sxHeight = _readInt16(_offset!); + table.sCapHeight = _readInt16(_offset!); + table.usDefaultChar = _readUInt16(_offset!); + table.usBreakChar = _readUInt16(_offset!); + table.usMaxContext = _readUInt16(_offset!); } else { table.sxHeight = 0; table.sCapHeight = 0; @@ -288,30 +288,30 @@ class _TtfReader { } _TtfPostTable _readPostTable() { - final _TtfTableInfo tableInfo = _getTable('post'); + final _TtfTableInfo tableInfo = _getTable('post')!; _offset = tableInfo.offset; final _TtfPostTable table = _TtfPostTable(); - table.formatType = _readFixed(_offset); - table.italicAngle = _readFixed(_offset); - table.underlinePosition = _readInt16(_offset); - table.underlineThickness = _readInt16(_offset); - table.isFixedPitch = _readUInt32(_offset); - table.minType42 = _readUInt32(_offset); - table.maxType42 = _readUInt32(_offset); - table.minType1 = _readUInt32(_offset); - table.maxType1 = _readUInt32(_offset); + table.formatType = _readFixed(_offset!); + table.italicAngle = _readFixed(_offset!); + table.underlinePosition = _readInt16(_offset!); + table.underlineThickness = _readInt16(_offset!); + table.isFixedPitch = _readUInt32(_offset!); + table.minType42 = _readUInt32(_offset!); + table.maxType42 = _readUInt32(_offset!); + table.minType1 = _readUInt32(_offset!); + table.maxType1 = _readUInt32(_offset!); return table; } - List _readWidthTable(int glyphCount, int unitsPerEm) { - final _TtfTableInfo tableInfo = _getTable('hmtx'); + List _readWidthTable(int glyphCount, int? unitsPerEm) { + final _TtfTableInfo tableInfo = _getTable('hmtx')!; _offset = tableInfo.offset; - final List width = List(glyphCount); + final List width = List.filled(glyphCount, 0, growable: true); for (int i = 0; i < glyphCount; i++) { final _TtfLongHorMetric glyph = _TtfLongHorMetric(); - glyph.advanceWidth = _readUInt16(_offset); - glyph.lsb = _readInt16(_offset); - final double glyphWidth = glyph.advanceWidth * 1000 / unitsPerEm; + glyph.advanceWidth = _readUInt16(_offset!); + glyph.lsb = _readInt16(_offset!); + final double glyphWidth = glyph.advanceWidth * 1000 / unitsPerEm!; width[i] = glyphWidth.floor(); } return width; @@ -324,7 +324,6 @@ class _TtfReader { _TtfOS2Table os2Table, _TtfPostTable postTable, List<_TtfCmapSubTable> cmapTables) { - ArgumentError.checkNotNull(cmapTables); _initializeFontName(nameTable); bool bSymbol = false; for (int i = 0; i < cmapTables.length; i++) { @@ -336,63 +335,63 @@ class _TtfReader { break; } } - _metrics.isSymbol = bSymbol; - _metrics.macStyle = headTable.macStyle; - _metrics.isFixedPitch = postTable.isFixedPitch != 0; - _metrics.italicAngle = postTable.italicAngle; - final double factor = 1000 / headTable.unitsPerEm; - _metrics.winAscent = os2Table.sTypoAscender * factor; - _metrics.macAscent = horizontalHeadTable.ascender * factor; - _metrics.capHeight = (os2Table.sCapHeight != 0) - ? os2Table.sCapHeight.toDouble() - : 0.7 * headTable.unitsPerEm * factor; - _metrics.winDescent = os2Table.sTypoDescender * factor; - _metrics.macDescent = horizontalHeadTable.descender * factor; - _metrics.leading = (os2Table.sTypoAscender - + _metrics!.isSymbol = bSymbol; + _metrics!.macStyle = headTable.macStyle; + _metrics!.isFixedPitch = postTable.isFixedPitch != 0; + _metrics!.italicAngle = postTable.italicAngle; + final double factor = 1000 / headTable.unitsPerEm!; + _metrics!.winAscent = os2Table.sTypoAscender * factor; + _metrics!.macAscent = horizontalHeadTable.ascender * factor; + _metrics!.capHeight = (os2Table.sCapHeight != 0) + ? os2Table.sCapHeight!.toDouble() + : 0.7 * headTable.unitsPerEm! * factor; + _metrics!.winDescent = os2Table.sTypoDescender * factor; + _metrics!.macDescent = horizontalHeadTable.descender * factor; + _metrics!.leading = (os2Table.sTypoAscender - os2Table.sTypoDescender + os2Table.sTypoLineGap) * factor; - _metrics.lineGap = (horizontalHeadTable.lineGap * factor).ceil(); + _metrics!.lineGap = (horizontalHeadTable.lineGap * factor).ceil(); final double left = headTable.xMin * factor; - final double top = (_metrics.macAscent + _metrics.lineGap).ceilToDouble(); + final double top = + (_metrics!.macAscent + _metrics!.lineGap!).ceilToDouble(); final double right = headTable.xMax * factor; - final double bottom = _metrics.macDescent; - _metrics.fontBox = _Rectangle(left, top, right - left, bottom - top); - _metrics.stemV = 80; - _metrics.widthTable = _updateWidth(); - _metrics.contains = _tableDirectory.containsKey('CFF'); - _metrics.subscriptSizeFactor = - headTable.unitsPerEm / os2Table.ySubscriptYSize; - _metrics.superscriptSizeFactor = - headTable.unitsPerEm / os2Table.ySuperscriptYSize; + final double bottom = _metrics!.macDescent; + _metrics!.fontBox = _Rectangle(left, top, right - left, bottom - top); + _metrics!.stemV = 80; + _metrics!.widthTable = _updateWidth(); + _metrics!.contains = _tableDirectory!.containsKey('CFF'); + _metrics!.subscriptSizeFactor = + headTable.unitsPerEm! / os2Table.ySubscriptYSize; + _metrics!.superscriptSizeFactor = + headTable.unitsPerEm! / os2Table.ySuperscriptYSize; } List<_TtfCmapSubTable> _readCmapTable() { - final _TtfTableInfo tableInfo = _getTable('cmap'); + final _TtfTableInfo tableInfo = _getTable('cmap')!; _offset = tableInfo.offset; final _TtfCmapTable table = _TtfCmapTable(); - table.version = _readUInt16(_offset); - table.tablesCount = _readUInt16(_offset); - int position = _offset; - final List<_TtfCmapSubTable> subTables = - List<_TtfCmapSubTable>(table.tablesCount); + table.version = _readUInt16(_offset!); + table.tablesCount = _readUInt16(_offset!); + int? position = _offset; + final List<_TtfCmapSubTable> subTables = <_TtfCmapSubTable>[]; for (int i = 0; i < table.tablesCount; i++) { _offset = position; final _TtfCmapSubTable subTable = _TtfCmapSubTable(); - subTable.platformID = _readUInt16(_offset); - subTable.encodingID = _readUInt16(_offset); - subTable.offset = _readUInt32(_offset); + subTable.platformID = _readUInt16(_offset!); + subTable.encodingID = _readUInt16(_offset!); + subTable.offset = _readUInt32(_offset!); position = _offset; _readCmapSubTable(subTable); - subTables[i] = subTable; + subTables.add(subTable); } return subTables; } void _readCmapSubTable(_TtfCmapSubTable subTable) { - final _TtfTableInfo tableInfo = _getTable('cmap'); - _offset = tableInfo.offset + subTable.offset; - final _TtfCmapFormat format = _getCmapFormat(_readUInt16(_offset)); + final _TtfTableInfo tableInfo = _getTable('cmap')!; + _offset = tableInfo.offset! + subTable.offset; + final _TtfCmapFormat format = _getCmapFormat(_readUInt16(_offset!)); final _TtfCmapEncoding encoding = _getCmapEncoding(subTable.platformID, subTable.encodingID); if (encoding != _TtfCmapEncoding.unknown) { @@ -412,42 +411,42 @@ class _TtfReader { void _readAppleCmapTable( _TtfCmapSubTable subTable, _TtfCmapEncoding encoding) { - final _TtfTableInfo tableInfo = _getTable('cmap'); - _offset = tableInfo.offset + subTable.offset; + final _TtfTableInfo tableInfo = _getTable('cmap')!; + _offset = tableInfo.offset! + subTable.offset; final _TtfAppleCmapSubTable table = _TtfAppleCmapSubTable(); - table.format = _readUInt16(_offset); - table.length = _readUInt16(_offset); - table.version = _readUInt16(_offset); + table.format = _readUInt16(_offset!); + table.length = _readUInt16(_offset!); + table.version = _readUInt16(_offset!); _maxMacIndex ??= 0; for (int i = 0; i < 256; ++i) { final _TtfGlyphInfo glyphInfo = _TtfGlyphInfo(); - glyphInfo.index = _fontData[_offset]; - _offset += 1; + glyphInfo.index = _fontData[_offset!]; + _offset = _offset! + 1; glyphInfo.width = _getWidth(glyphInfo.index); glyphInfo.charCode = i; - _macintosh[i] = glyphInfo; + _macintosh![i] = glyphInfo; _addGlyph(glyphInfo, encoding); - _maxMacIndex = i >= _maxMacIndex ? i : _maxMacIndex; + _maxMacIndex = i >= _maxMacIndex! ? i : _maxMacIndex; } } void _readMicrosoftCmapTable( _TtfCmapSubTable subTable, _TtfCmapEncoding encoding) { - final _TtfTableInfo tableInfo = _getTable('cmap'); - _offset = tableInfo.offset + subTable.offset; - final Map collection = + final _TtfTableInfo tableInfo = _getTable('cmap')!; + _offset = tableInfo.offset! + subTable.offset; + final Map? collection = encoding == _TtfCmapEncoding.unicode ? _microsoft : _macintosh; final _TtfMicrosoftCmapSubTable table = _TtfMicrosoftCmapSubTable(); - table.format = _readUInt16(_offset); - table.length = _readUInt16(_offset); - table.version = _readUInt16(_offset); - table.segCountX2 = _readUInt16(_offset); - table.searchRange = _readUInt16(_offset); - table.entrySelector = _readUInt16(_offset); - table.rangeShift = _readUInt16(_offset); + table.format = _readUInt16(_offset!); + table.length = _readUInt16(_offset!); + table.version = _readUInt16(_offset!); + table.segCountX2 = _readUInt16(_offset!); + table.searchRange = _readUInt16(_offset!); + table.entrySelector = _readUInt16(_offset!); + table.rangeShift = _readUInt16(_offset!); final int segCount = table.segCountX2 ~/ 2; table.endCount = _readUshortArray(segCount); - table.reservedPad = _readUInt16(_offset); + table.reservedPad = _readUInt16(_offset!); table.startCount = _readUshortArray(segCount); table.idDelta = _readUshortArray(segCount); table.idRangeOffset = _readUshortArray(segCount); @@ -481,7 +480,7 @@ class _TtfReader { ? ((k & 0xff00) == 0xf000 ? k & 0xff : k) : k; glyph.charCode = id; - collection[id] = glyph; + collection![id] = glyph; _addGlyph(glyph, encoding); } } @@ -489,30 +488,30 @@ class _TtfReader { void _readTrimmedCmapTable( _TtfCmapSubTable subTable, _TtfCmapEncoding encoding) { - final _TtfTableInfo tableInfo = _getTable('cmap'); - _offset = tableInfo.offset + subTable.offset; + final _TtfTableInfo tableInfo = _getTable('cmap')!; + _offset = tableInfo.offset! + subTable.offset; final _TtfTrimmedCmapSubTable table = _TtfTrimmedCmapSubTable(); - table.format = _readUInt16(_offset); - table.length = _readUInt16(_offset); - table.version = _readUInt16(_offset); - table.firstCode = _readUInt16(_offset); - table.entryCount = _readUInt16(_offset); + table.format = _readUInt16(_offset!); + table.length = _readUInt16(_offset!); + table.version = _readUInt16(_offset!); + table.firstCode = _readUInt16(_offset!); + table.entryCount = _readUInt16(_offset!); _maxMacIndex ??= 0; for (int i = 0; i < table.entryCount; ++i) { final _TtfGlyphInfo glyphInfo = _TtfGlyphInfo(); - glyphInfo.index = _readUInt16(_offset); + glyphInfo.index = _readUInt16(_offset!); glyphInfo.width = _getWidth(glyphInfo.index); glyphInfo.charCode = i + table.firstCode; - _macintosh[i] = glyphInfo; + _macintosh![i] = glyphInfo; _addGlyph(glyphInfo, encoding); - _maxMacIndex = i >= _maxMacIndex ? i : _maxMacIndex; + _maxMacIndex = i >= _maxMacIndex! ? i : _maxMacIndex; } } - _TtfTableInfo _getTable(String name) { - _TtfTableInfo table = _TtfTableInfo(); - if (_tableDirectory.containsKey(name)) { - table = _tableDirectory[name]; + _TtfTableInfo? _getTable(String name) { + _TtfTableInfo? table = _TtfTableInfo(); + if (_tableDirectory!.containsKey(name)) { + table = _tableDirectory![name]; } return table; } @@ -524,16 +523,17 @@ class _TtfReader { List _updateWidth() { const int count = 256; - if (_metrics.isSymbol) { - final List bytes = List(count); + if (_metrics!.isSymbol) { + final List bytes = List.filled(count, 0, growable: true); for (int i = 0; i < count; i++) { - final _TtfGlyphInfo glyphInfo = _getGlyph(char: String.fromCharCode(i)); + final _TtfGlyphInfo glyphInfo = + _getGlyph(char: String.fromCharCode(i))!; bytes[i] = (glyphInfo.empty) ? 0 : glyphInfo.width; } return bytes; } else { - final List bytes = List.filled(count, 0); - final List byteToProcess = List(1); + final List bytes = List.filled(count, 0, growable: true); + final List byteToProcess = List.filled(1, 0); final String space = String.fromCharCode(32); for (int i = 0; i < count; i++) { byteToProcess[0] = i; @@ -542,11 +542,11 @@ class _TtfReader { text += String.fromCharCode(byteToProcess[index + 0]); } final String ch = text.isNotEmpty ? text[0] : '?'; - _TtfGlyphInfo glyphInfo = _getGlyph(char: ch); + _TtfGlyphInfo glyphInfo = _getGlyph(char: ch)!; if (!glyphInfo.empty) { bytes[i] = glyphInfo.width; } else { - glyphInfo = _getGlyph(char: space); + glyphInfo = _getGlyph(char: space)!; bytes[i] = (glyphInfo.empty) ? 0 : glyphInfo.width; } } @@ -555,41 +555,41 @@ class _TtfReader { } double _getCharWidth(String code) { - _TtfGlyphInfo glyphInfo = _getGlyph(char: code); + _TtfGlyphInfo? glyphInfo = _getGlyph(char: code); glyphInfo = (glyphInfo != null && !glyphInfo.empty) ? glyphInfo : _getDefaultGlyph(); - return (!glyphInfo.empty) ? glyphInfo.width.toDouble() : 0; + return (!glyphInfo!.empty) ? glyphInfo.width.toDouble() : 0; } - _TtfGlyphInfo _getGlyph({int charCode, String char}) { + _TtfGlyphInfo? _getGlyph({int? charCode, String? char}) { if (charCode != null) { - _TtfGlyphInfo glyphInfo; - if (!_metrics.isSymbol && _microsoftGlyphs != null) { - if (_microsoftGlyphs.containsKey(charCode)) { - glyphInfo = _microsoftGlyphs[charCode]; + _TtfGlyphInfo? glyphInfo; + if (!_metrics!.isSymbol && _microsoftGlyphs != null) { + if (_microsoftGlyphs!.containsKey(charCode)) { + glyphInfo = _microsoftGlyphs![charCode]; } - } else if (_metrics.isSymbol && _macintoshGlyphs != null) { - if (_macintoshGlyphs.containsKey(charCode)) { - glyphInfo = _macintoshGlyphs[charCode]; + } else if (_metrics!.isSymbol && _macintoshGlyphs != null) { + if (_macintoshGlyphs!.containsKey(charCode)) { + glyphInfo = _macintoshGlyphs![charCode]; } } return glyphInfo ?? _getDefaultGlyph(); } else if (char != null) { - _TtfGlyphInfo glyphInfo; + _TtfGlyphInfo? glyphInfo; int code = char.codeUnitAt(0); - if (!_metrics.isSymbol && _microsoft != null) { - if (_microsoft.containsKey(code)) { - glyphInfo = _microsoft[code]; + if (!_metrics!.isSymbol && _microsoft != null) { + if (_microsoft!.containsKey(code)) { + glyphInfo = _microsoft![code]; } - } else if (_metrics.isSymbol && _macintosh != null || _isMacTtf) { + } else if (_metrics!.isSymbol && _macintosh != null || _isMacTtf) { if (_maxMacIndex != 0) { - code %= _maxMacIndex + 1; + code %= _maxMacIndex! + 1; } else { code = (code & 0xff00) == 0xf000 ? code & 0xff : code; } - if (_macintosh.containsKey(code)) { - glyphInfo = _macintosh[code]; + if (_macintosh!.containsKey(code)) { + glyphInfo = _macintosh![code]; } } if (char == ' ' && glyphInfo == null) { @@ -600,7 +600,7 @@ class _TtfReader { return null; } - _TtfGlyphInfo _getDefaultGlyph() { + _TtfGlyphInfo? _getDefaultGlyph() { return _getGlyph(char: ' '); } @@ -609,12 +609,12 @@ class _TtfReader { final _TtfNameRecord record = nameTable.nameRecords[i]; if (record.nameID == 1) { //font family - _metrics.fontFamily = record.name; + _metrics!.fontFamily = record.name; } else if (record.nameID == 6) { //post script name - _metrics.postScriptName = record.name; + _metrics!.postScriptName = record.name; } - if (_metrics.fontFamily != null && _metrics.postScriptName != null) { + if (_metrics!.fontFamily != null && _metrics!.postScriptName != null) { break; } } @@ -646,7 +646,7 @@ class _TtfReader { } } - _TtfPlatformID _getPlatformID(int platformID) { + _TtfPlatformID _getPlatformID(int? platformID) { switch (platformID) { case 1: return _TtfPlatformID.macintosh; @@ -659,7 +659,7 @@ class _TtfReader { } } - _TtfMicrosoftEncodingID _getMicrosoftEncodingID(int encodingID) { + _TtfMicrosoftEncodingID _getMicrosoftEncodingID(int? encodingID) { switch (encodingID) { case 1: return _TtfMicrosoftEncodingID.unicode; @@ -670,7 +670,7 @@ class _TtfReader { } } - _TtfMacintoshEncodingID _getMacintoshEncodingID(int encodingID) { + _TtfMacintoshEncodingID _getMacintoshEncodingID(int? encodingID) { switch (encodingID) { case 1: return _TtfMacintoshEncodingID.japanese; @@ -681,7 +681,7 @@ class _TtfReader { } } - _TtfCmapEncoding _getCmapEncoding(int platformID, int encodingID) { + _TtfCmapEncoding _getCmapEncoding(int? platformID, int? encodingID) { _TtfCmapEncoding format = _TtfCmapEncoding.unknown; if (_getPlatformID(platformID) == _TtfPlatformID.microsoft && _getMicrosoftEncodingID(encodingID) == @@ -699,7 +699,7 @@ class _TtfReader { } void _addGlyph(_TtfGlyphInfo glyph, _TtfCmapEncoding encoding) { - Map collection; + Map? collection; switch (encoding) { case _TtfCmapEncoding.unicode: collection = _microsoftGlyphs; @@ -711,16 +711,15 @@ class _TtfReader { default: break; } - collection[glyph.index] = glyph; + collection![glyph.index] = glyph; } Map _getGlyphChars(Map chars) { - ArgumentError.checkNotNull(chars); final Map dictionary = {}; final List charKeys = chars.keys.toList(); for (int i = 0; i < charKeys.length; i++) { final String ch = charKeys[i]; - final _TtfGlyphInfo glyph = _getGlyph(char: ch); + final _TtfGlyphInfo glyph = _getGlyph(char: ch)!; if (!glyph.empty) { dictionary[glyph.index] = ch.codeUnitAt(0); } @@ -728,47 +727,46 @@ class _TtfReader { return dictionary; } - List _readFontProgram(Map chars) { + List? _readFontProgram(Map chars) { final Map glyphChars = _getGlyphChars(chars); final List offsets = _readLocaTable(_isLocaShort); _updateGlyphChars(glyphChars, offsets); final Map returnedValue = _generateGlyphTable(glyphChars, offsets, null, null); - final int glyphTableSize = returnedValue['glyphTableSize']; + final int? glyphTableSize = returnedValue['glyphTableSize']; final List newLocaTable = returnedValue['newLocaTable']; final List newGlyphTable = returnedValue['newGlyphTable']; final Map result = _updateLocaTable(newLocaTable, _isLocaShort, null); - final int newLocaSize = result['newLocaSize']; + final int? newLocaSize = result['newLocaSize']; final List newLocaUpdated = result['newLocaUpdated']; - final List fontProgram = _getFontProgram( + final List? fontProgram = _getFontProgram( newLocaUpdated, newGlyphTable, glyphTableSize, newLocaSize); return fontProgram; } List _readLocaTable(bool isShort) { - final _TtfTableInfo tableInfo = _getTable('loca'); + final _TtfTableInfo tableInfo = _getTable('loca')!; _offset = tableInfo.offset; List buffer; if (isShort) { - final int len = tableInfo.length ~/ 2; - buffer = List(len); + final int len = tableInfo.length! ~/ 2; + buffer = List.filled(len, 0, growable: true); for (int i = 0; i < len; i++) { - buffer[i] = _readUInt16(_offset) * 2; + buffer[i] = _readUInt16(_offset!) * 2; } } else { - final int len = tableInfo.length ~/ 4; - buffer = List(len); + final int len = tableInfo.length! ~/ 4; + buffer = List.filled(len, 0, growable: true); for (int i = 0; i < len; i++) { - buffer[i] = _readUInt32(_offset); + buffer[i] = _readUInt32(_offset!); } } return buffer; } dynamic _updateLocaTable( - List newLocaTable, bool isLocaShort, List newLocaTableOut) { - ArgumentError.checkNotNull(newLocaTable); + List newLocaTable, bool isLocaShort, List? newLocaTableOut) { final int size = isLocaShort ? newLocaTable.length * 2 : newLocaTable.length * 4; final int count = _align(size); @@ -795,7 +793,7 @@ class _TtfReader { final List glyphCharKeys = glyphChars.keys.toList(); final Map clone = {}; for (int i = 0; i < glyphCharKeys.length; i++) { - clone[glyphCharKeys[i]] = glyphChars[glyphCharKeys[i]]; + clone[glyphCharKeys[i]] = glyphChars[glyphCharKeys[i]]!; } for (int i = 0; i < glyphCharKeys.length; i++) { _processCompositeGlyph(glyphChars, glyphCharKeys[i], offsets); @@ -807,19 +805,19 @@ class _TtfReader { if (glyph < offsets.length - 1) { final int glyphOffset = offsets[glyph]; if (glyphOffset != offsets[glyph + 1]) { - final _TtfTableInfo tableInfo = _getTable('glyf'); - _offset = tableInfo.offset + glyphOffset; + final _TtfTableInfo tableInfo = _getTable('glyf')!; + _offset = tableInfo.offset! + glyphOffset; final _TtfGlyphHeader glyphHeader = _TtfGlyphHeader(); - glyphHeader.numberOfContours = _readInt16(_offset); - glyphHeader.xMin = _readInt16(_offset); - glyphHeader.yMin = _readInt16(_offset); - glyphHeader.xMax = _readInt16(_offset); - glyphHeader.yMax = _readInt16(_offset); + glyphHeader.numberOfContours = _readInt16(_offset!); + glyphHeader.xMin = _readInt16(_offset!); + glyphHeader.yMin = _readInt16(_offset!); + glyphHeader.xMax = _readInt16(_offset!); + glyphHeader.yMax = _readInt16(_offset!); if (glyphHeader.numberOfContours < 0) { int skipBytes = 0; while (true) { - final int flags = _readUInt16(_offset); - final int glyphIndex = _readUInt16(_offset); + final int flags = _readUInt16(_offset!); + final int glyphIndex = _readUInt16(_offset!); if (!glyphChars.containsKey(glyphIndex)) { glyphChars[glyphIndex] = 0; } @@ -834,7 +832,7 @@ class _TtfReader { } else if (flags & 0x0080 != 0) { skipBytes += 2 * 4; } - _offset += skipBytes; + _offset = _offset! + skipBytes; } } } @@ -842,13 +840,13 @@ class _TtfReader { } dynamic _generateGlyphTable(Map glyphChars, List offsets, - List newLocaTable, List newGlyphTable) { + List? newLocaTable, List? newGlyphTable) { newLocaTable = []; final List activeGlyphs = glyphChars.keys.toList(); activeGlyphs.sort((int a, int b) => a - b); int glyphSize = 0; for (int i = 0; i < activeGlyphs.length; i++) { - if (offsets != null && offsets.isNotEmpty) { + if (offsets.isNotEmpty) { glyphSize += offsets[activeGlyphs[i] + 1] - offsets[activeGlyphs[i]]; } } @@ -859,7 +857,7 @@ class _TtfReader { } int nextGlyphOffset = 0; int nextGlyphIndex = 0; - final _TtfTableInfo table = _getTable('glyf'); + final _TtfTableInfo? table = _getTable('glyf'); for (int i = 0; i < offsets.length; i++) { newLocaTable.add(nextGlyphOffset); if (nextGlyphIndex < activeGlyphs.length && @@ -869,9 +867,9 @@ class _TtfReader { final int oldGlyphOffset = offsets[i]; final int oldNextGlyphOffset = offsets[i + 1] - oldGlyphOffset; if (oldNextGlyphOffset > 0) { - _offset = table.offset + oldGlyphOffset; + _offset = table!.offset! + oldGlyphOffset; final Map result = - _read(newGlyphTable, nextGlyphOffset, oldNextGlyphOffset); + _read(newGlyphTable!, nextGlyphOffset, oldNextGlyphOffset); newGlyphTable = result['buffer']; nextGlyphOffset += oldNextGlyphOffset; } @@ -888,10 +886,8 @@ class _TtfReader { return (value + 3) & (~3); } - List _getFontProgram(List newLocaTableOut, List newGlyphTable, - int glyphTableSize, int locaTableSize) { - ArgumentError.checkNotNull(newLocaTableOut); - ArgumentError.checkNotNull(newGlyphTable); + List? _getFontProgram(List newLocaTableOut, List newGlyphTable, + int? glyphTableSize, int? locaTableSize) { final dynamic result = _getFontProgramLength(newLocaTableOut, newGlyphTable, 0); final int fontProgramLength = result['fontProgramLength']; @@ -911,18 +907,16 @@ class _TtfReader { dynamic _getFontProgramLength( List newLocaTableOut, List newGlyphTable, int numTables) { - ArgumentError.checkNotNull(newLocaTableOut); - ArgumentError.checkNotNull(newGlyphTable); numTables = 2; final List tableNames = _tableNames; int fontProgramLength = 0; for (int i = 0; i < tableNames.length; i++) { final String tableName = tableNames[i]; if (tableName != 'glyf' && tableName != 'loca') { - final _TtfTableInfo table = _getTable(tableName); + final _TtfTableInfo table = _getTable(tableName)!; if (!table.empty) { ++numTables; - fontProgramLength += _align(table.length); + fontProgramLength += _align(table.length!); } } } @@ -941,17 +935,14 @@ class _TtfReader { int numTables, List newLocaTableOut, List newGlyphTable, - int glyphTableSize, - int locaTableSize) { - ArgumentError.checkNotNull(writer); - ArgumentError.checkNotNull(newLocaTableOut); - ArgumentError.checkNotNull(newGlyphTable); + int? glyphTableSize, + int? locaTableSize) { final List tableNames = _tableNames; int usedTablesSize = numTables * 16 + (3 * 4); - int nextTableSize = 0; + int? nextTableSize = 0; for (int i = 0; i < tableNames.length; i++) { final String tableName = tableNames[i]; - final _TtfTableInfo tableInfo = _getTable(tableName); + final _TtfTableInfo tableInfo = _getTable(tableName)!; if (tableInfo.empty) { continue; } @@ -965,17 +956,16 @@ class _TtfReader { writer._writeInt(checksum); nextTableSize = locaTableSize; } else { - writer._writeInt(tableInfo.checksum); + writer._writeInt(tableInfo.checksum!); nextTableSize = tableInfo.length; } writer._writeUInt(usedTablesSize); - writer._writeUInt(nextTableSize); + writer._writeUInt(nextTableSize!); usedTablesSize += _align(nextTableSize); } } int _calculateCheckSum(List bytes) { - ArgumentError.checkNotNull(bytes); int pos = 0; int byte1 = 0; int byte2 = 0; @@ -992,13 +982,10 @@ class _TtfReader { void _writeGlyphs(_BigEndianWriter writer, List newLocaTable, List newGlyphTable) { - ArgumentError.checkNotNull(writer); - ArgumentError.checkNotNull(newLocaTable); - ArgumentError.checkNotNull(newGlyphTable); final List tableNames = _tableNames; for (int i = 0; i < tableNames.length; i++) { final String tableName = tableNames[i]; - final _TtfTableInfo tableInfo = _getTable(tableName); + final _TtfTableInfo tableInfo = _getTable(tableName)!; if (tableInfo.empty) { continue; } @@ -1007,21 +994,20 @@ class _TtfReader { } else if (tableName == 'loca') { writer._writeBytes(newLocaTable); } else { - final int count = _align(tableInfo.length); + final int count = _align(tableInfo.length!); final List buff = List.filled(count, 0, growable: true); _offset = tableInfo.offset; - final dynamic result = _read(buff, 0, tableInfo.length); + final dynamic result = _read(buff, 0, tableInfo.length!); writer._writeBytes(result['buffer']); } } } String _convertString(String text) { - ArgumentError.checkNotNull(text); String glyph = ''; for (int k = 0; k < text.length; k++) { final String char = text[k]; - final _TtfGlyphInfo glyphInfo = _getGlyph(char: char); + final _TtfGlyphInfo glyphInfo = _getGlyph(char: char)!; if (!glyphInfo.empty) { glyph += String.fromCharCode(glyphInfo.index); } @@ -1032,14 +1018,14 @@ class _TtfReader { int _readInt16(int offset) { int result = (_fontData[offset] << 8) + _fontData[offset + 1]; result = (result & (1 << 15) != 0) ? result - 0x10000 : result; - _offset += 2; + _offset = _offset! + 2; return result; } int _readUInt16(int offset) { final int i1 = _fontData[offset]; final int i2 = _fontData[offset + 1]; - _offset += 2; + _offset = _offset! + 2; return (i1 << 8) | i2; } @@ -1048,7 +1034,7 @@ class _TtfReader { final int i2 = _fontData[offset + 2]; final int i3 = _fontData[offset + 1]; final int i4 = _fontData[offset]; - _offset += 4; + _offset = _offset! + 4; return i1 + (i2 << 8) + (i3 << 16) + (i4 << 24); } @@ -1057,7 +1043,7 @@ class _TtfReader { final int i2 = _fontData[offset + 2]; final int i3 = _fontData[offset + 1]; final int i4 = _fontData[offset]; - _offset += 4; + _offset = _offset! + 4; return i1 | (i2 << 8) | (i3 << 16) | (i4 << 24); } @@ -1074,22 +1060,22 @@ class _TtfReader { return _readInt16(offset) + (_readInt16(offset + 2) / 16384); } - String _readString(int length, [bool isUnicode]) { + String _readString(int? length, [bool? isUnicode]) { if (isUnicode == null) { return _readString(length, false); } else { String result = ''; if (isUnicode) { - for (int i = 0; i < length; i++) { + for (int i = 0; i < length!; i++) { if (i % 2 != 0) { - result += String.fromCharCode(_fontData[_offset]); + result += String.fromCharCode(_fontData[_offset!]); } - _offset += 1; + _offset = _offset! + 1; } } else { - for (int i = 0; i < length; i++) { - result += String.fromCharCode(_fontData[_offset]); - _offset += 1; + for (int i = 0; i < length!; i++) { + result += String.fromCharCode(_fontData[_offset!]); + _offset = _offset! + 1; } } return result; @@ -1097,34 +1083,33 @@ class _TtfReader { } List _readBytes(int length) { - final List result = List(length); + final List result = List.filled(length, 0, growable: true); for (int i = 0; i < length; i++) { - result[i] = _fontData[_offset]; - _offset += 1; + result[i] = _fontData[_offset!]; + _offset = _offset! + 1; } return result; } List _readUshortArray(int length) { - final List buffer = List(length); + final List buffer = List.filled(length, 0, growable: true); for (int i = 0; i < length; i++) { - buffer[i] = _readUInt16(_offset); + buffer[i] = _readUInt16(_offset!); } return buffer; } dynamic _read(List buffer, int index, int count) { - ArgumentError.checkNotNull(buffer); int written = 0; int read = 0; do { for (int i = 0; - i < count - written && _offset + i < _fontData.length; + i < count - written && _offset! + i < _fontData.length; i++) { - buffer[index + i] = _fontData[_offset + i]; + buffer[index + i] = _fontData[_offset! + i]; } read = count - written; - _offset += read; + _offset = _offset! + read; written += read; } while (written < count); return {'buffer': buffer, 'written': written}; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/unicode_true_type_font.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/unicode_true_type_font.dart index 8999cc42f..f18fb085f 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/unicode_true_type_font.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/unicode_true_type_font.dart @@ -29,19 +29,19 @@ class _UnicodeTrueTypeFont { _Operators.newLine; //Fields - List _fontData; - double _size; - _TtfReader _reader; - _TtfMetrics _ttfMetrics; - _PdfFontMetrics _metrics; - _PdfDictionary _fontDictionary; - _PdfStream _fontProgram; - _PdfStream _cmap; - _PdfDictionary _descendantFont; - String _subsetName; - Map _usedChars; - _PdfDictionary _fontDescriptor; - _PdfStream _cidStream; + late List _fontData; + late double _size; + late _TtfReader _reader; + _TtfMetrics? _ttfMetrics; + late _PdfFontMetrics _metrics; + _PdfDictionary? _fontDictionary; + _PdfStream? _fontProgram; + _PdfStream? _cmap; + _PdfDictionary? _descendantFont; + String? _subsetName; + Map? _usedChars; + _PdfDictionary? _fontDescriptor; + _PdfStream? _cidStream; //Implementation void _initialize(List fontData, double size) { @@ -60,7 +60,7 @@ class _UnicodeTrueTypeFont { _reader._createInternals(); _ttfMetrics = _reader._metrics; _initializeMetrics(); - _subsetName = _getFontName(_reader._metrics.postScriptName); + _subsetName = _getFontName(_reader._metrics!.postScriptName!); _createDescendantFont(); _createCmap(); _createFontDictionary(); @@ -68,16 +68,16 @@ class _UnicodeTrueTypeFont { } void _initializeMetrics() { - final _TtfMetrics ttfMetrics = _reader._metrics; + final _TtfMetrics ttfMetrics = _reader._metrics!; _metrics.ascent = ttfMetrics.macAscent; _metrics.descent = ttfMetrics.macDescent; _metrics.height = - ttfMetrics.macAscent - ttfMetrics.macDescent + ttfMetrics.lineGap; - _metrics.name = ttfMetrics.fontFamily; + ttfMetrics.macAscent - ttfMetrics.macDescent + ttfMetrics.lineGap!; + _metrics.name = ttfMetrics.fontFamily!; _metrics.postScriptName = ttfMetrics.postScriptName; _metrics.size = _size; _metrics._widthTable = _StandardWidthTable(ttfMetrics.widthTable); - _metrics.lineGap = ttfMetrics.lineGap; + _metrics.lineGap = ttfMetrics.lineGap!; _metrics.subscriptSizeFactor = ttfMetrics.subscriptSizeFactor; _metrics.superscriptSizeFactor = ttfMetrics.superscriptSizeFactor; _metrics.isBold = ttfMetrics.isBold; @@ -133,36 +133,36 @@ class _UnicodeTrueTypeFont { } void _createDescendantFont() { - _descendantFont._beginSave = _descendantFontBeginSave; - _descendantFont[_DictionaryProperties.type] = + _descendantFont!._beginSave = _descendantFontBeginSave; + _descendantFont![_DictionaryProperties.type] = _PdfName(_DictionaryProperties.font); - _descendantFont[_DictionaryProperties.subtype] = + _descendantFont![_DictionaryProperties.subtype] = _PdfName(_DictionaryProperties.cidFontType2); - _descendantFont[_DictionaryProperties.baseFont] = _PdfName(_subsetName); - _descendantFont[_DictionaryProperties.cidToGIDMap] = + _descendantFont![_DictionaryProperties.baseFont] = _PdfName(_subsetName); + _descendantFont![_DictionaryProperties.cidToGIDMap] = _PdfName(_DictionaryProperties.identity); - _descendantFont[_DictionaryProperties.dw] = _PdfNumber(1000); + _descendantFont![_DictionaryProperties.dw] = _PdfNumber(1000); _fontDescriptor = _createFontDescriptor(); - _descendantFont[_DictionaryProperties.fontDescriptor] = + _descendantFont![_DictionaryProperties.fontDescriptor] = _PdfReferenceHolder(_fontDescriptor); - _descendantFont[_DictionaryProperties.cidSystemInfo] = _createSystemInfo(); + _descendantFont![_DictionaryProperties.cidSystemInfo] = _createSystemInfo(); } _PdfDictionary _createFontDescriptor() { final _PdfDictionary descriptor = _PdfDictionary(); - final _TtfMetrics metrics = _reader._metrics; + final _TtfMetrics metrics = _reader._metrics!; descriptor[_DictionaryProperties.type] = _PdfName(_DictionaryProperties.fontDescriptor); descriptor[_DictionaryProperties.fontName] = _PdfName(_subsetName); descriptor[_DictionaryProperties.flags] = _PdfNumber(_getDescriptorFlags()); - final _Rectangle rect = _reader._metrics.fontBox; + final _Rectangle rect = _reader._metrics!.fontBox; descriptor[_DictionaryProperties.fontBBox] = _PdfArray( - [rect.x, rect.y + rect.height, rect.width, -rect.height]); + [rect.x, rect.y + rect.height, rect.width, -rect.height]); descriptor[_DictionaryProperties.missingWidth] = _PdfNumber(metrics.widthTable[32]); descriptor[_DictionaryProperties.stemV] = _PdfNumber(metrics.stemV); descriptor[_DictionaryProperties.italicAngle] = - _PdfNumber(metrics.italicAngle); + _PdfNumber(metrics.italicAngle!); descriptor[_DictionaryProperties.capHeight] = _PdfNumber(metrics.capHeight); descriptor[_DictionaryProperties.ascent] = _PdfNumber(metrics.winAscent); descriptor[_DictionaryProperties.descent] = _PdfNumber(metrics.winDescent); @@ -180,7 +180,7 @@ class _UnicodeTrueTypeFont { int _getDescriptorFlags() { int flags = 0; - final _TtfMetrics metrics = _reader._metrics; + final _TtfMetrics metrics = _reader._metrics!; if (metrics.isFixedPitch) { flags |= 1; } @@ -207,55 +207,55 @@ class _UnicodeTrueTypeFont { return systemInfo; } - void _descendantFontBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { - if (_usedChars != null && _usedChars.isNotEmpty) { - final _PdfArray width = _getDescendantWidth(); + void _descendantFontBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { + if (_usedChars != null && _usedChars!.isNotEmpty) { + final _PdfArray? width = _getDescendantWidth(); if (width != null) { - _descendantFont[_DictionaryProperties.w] = width; + _descendantFont![_DictionaryProperties.w] = width; } } } void _createCmap() { - _cmap._beginSave = _cmapBeginSave; + _cmap!._beginSave = _cmapBeginSave; } - void _cmapBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { + void _cmapBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { _generateCmap(); } void _createFontDictionary() { - _fontDictionary._beginSave = _fontDictionaryBeginSave; - _fontDictionary[_DictionaryProperties.type] = + _fontDictionary!._beginSave = _fontDictionaryBeginSave; + _fontDictionary![_DictionaryProperties.type] = _PdfName(_DictionaryProperties.font); - _fontDictionary[_DictionaryProperties.baseFont] = _PdfName(_subsetName); - _fontDictionary[_DictionaryProperties.subtype] = + _fontDictionary![_DictionaryProperties.baseFont] = _PdfName(_subsetName); + _fontDictionary![_DictionaryProperties.subtype] = _PdfName(_DictionaryProperties.type0); - _fontDictionary[_DictionaryProperties.encoding] = + _fontDictionary![_DictionaryProperties.encoding] = _PdfName(_DictionaryProperties.identityH); final _PdfArray descFonts = _PdfArray(); final _PdfReferenceHolder reference = _PdfReferenceHolder(_descendantFont); descFonts._add(reference); - _fontDictionary[_DictionaryProperties.descendantFonts] = descFonts; + _fontDictionary![_DictionaryProperties.descendantFonts] = descFonts; } - void _fontDictionaryBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { + void _fontDictionaryBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { if (_usedChars != null && - _usedChars.isNotEmpty && - !_fontDictionary.containsKey(_DictionaryProperties.toUnicode)) { - _fontDictionary[_DictionaryProperties.toUnicode] = + _usedChars!.isNotEmpty && + !_fontDictionary!.containsKey(_DictionaryProperties.toUnicode)) { + _fontDictionary![_DictionaryProperties.toUnicode] = _PdfReferenceHolder(_cmap); } } _PdfArray _getDescendantWidth() { final _PdfArray array = _PdfArray(); - if (_usedChars != null && _usedChars.isNotEmpty) { + if (_usedChars != null && _usedChars!.isNotEmpty) { final List<_TtfGlyphInfo> glyphInfo = <_TtfGlyphInfo>[]; - final List keys = _usedChars.keys.toList(); + final List keys = _usedChars!.keys.toList(); for (int i = 0; i < keys.length; i++) { final String chLen = keys[i]; - final _TtfGlyphInfo glyph = _reader._getGlyph(char: chLen); + final _TtfGlyphInfo glyph = _reader._getGlyph(char: chLen)!; if (glyph.empty) { continue; } @@ -296,8 +296,8 @@ class _UnicodeTrueTypeFont { } void _generateCmap() { - if (_usedChars != null && _usedChars.isNotEmpty) { - final Map glyphChars = _reader._getGlyphChars(_usedChars); + if (_usedChars != null && _usedChars!.isNotEmpty) { + final Map glyphChars = _reader._getGlyphChars(_usedChars!); if (glyphChars.isNotEmpty) { final List keys = glyphChars.keys.toList(); keys.sort(); @@ -325,31 +325,31 @@ class _UnicodeTrueTypeFont { final int key = keys[i]; builder += _getHexString(key, true) + _getHexString(key, true) + - _getHexString(glyphChars[key], true) + + _getHexString(glyphChars[key]!, true) + '\n'; } builder += _cmapSuffix; - _cmap._clearStream(); - _cmap._write(builder); + _cmap!._clearStream(); + _cmap!._write(builder); } } } void _createFontProgram() { - _fontProgram._beginSave = _fontProgramBeginSave; + _fontProgram!._beginSave = _fontProgramBeginSave; } - void _fontProgramBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { + void _fontProgramBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { _generateFontProgram(); } void _generateFontProgram() { - List fontProgram; + List? fontProgram; _usedChars ??= {}; _reader._offset = 0; - fontProgram = _reader._readFontProgram(_usedChars); - _fontProgram._clearStream(); - _fontProgram._write(fontProgram); + fontProgram = _reader._readFontProgram(_usedChars!); + _fontProgram!._clearStream(); + _fontProgram!._write(fontProgram); } String _getHexString(int n, bool isCaseChange) { @@ -369,10 +369,9 @@ class _UnicodeTrueTypeFont { } void _setSymbols(String text) { - ArgumentError.checkNotNull(text); _usedChars ??= {}; for (int i = 0; i < text.length; i++) { - _usedChars[text[i]] = String.fromCharCode(0); + _usedChars![text[i]] = String.fromCharCode(0); } _getDescendantWidth(); } @@ -383,20 +382,20 @@ class _UnicodeTrueTypeFont { void _initializeCidSet() { _cidStream = _PdfStream(); - _cidStream._beginSave = _cidBeginSave; - _fontDescriptor._beginSave = _fontDescriptorBeginSave; + _cidStream!._beginSave = _cidBeginSave; + _fontDescriptor!._beginSave = _fontDescriptorBeginSave; } //Runs before Cid will be saved. - void _cidBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { + void _cidBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { _generateCidSet(); } //Runs before font Dictionary will be saved. - void _fontDescriptorBeginSave(Object sender, _SavePdfPrimitiveArgs ars) { - if ((_usedChars != null && _usedChars.isNotEmpty) && - !_fontDescriptor.containsKey(_DictionaryProperties.cidSet)) { - _fontDescriptor[_DictionaryProperties.cidSet] = + void _fontDescriptorBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { + if ((_usedChars != null && _usedChars!.isNotEmpty) && + !_fontDescriptor!.containsKey(_DictionaryProperties.cidSet)) { + _fontDescriptor![_DictionaryProperties.cidSet] = _PdfReferenceHolder(_cidStream); } } @@ -413,21 +412,21 @@ class _UnicodeTrueTypeFont { 0x02, 0x01 ]; - if (_usedChars != null && _usedChars.isNotEmpty) { - final Map glyphChars = _reader._getGlyphChars(_usedChars); - List charBytes; + if (_usedChars != null && _usedChars!.isNotEmpty) { + final Map glyphChars = _reader._getGlyphChars(_usedChars!); + List? charBytes; if (glyphChars.isNotEmpty) { final List cidChars = glyphChars.keys.toList(); cidChars.sort(); final int last = cidChars[cidChars.length - 1]; - charBytes = List((last ~/ 8) + 1); + charBytes = List.filled((last ~/ 8) + 1, 0, growable: true); charBytes.fillRange(0, ((last ~/ 8) + 1), 0); for (int i = 0; i < cidChars.length; i++) { final int cid = cidChars[i]; charBytes[cid ~/ 8] |= dummyBits[cid % 8]; } } - _cidStream._write(charBytes); + _cidStream!._write(charBytes); } } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/image_decoder.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/image_decoder.dart index fdd3b5e56..09090059c 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/image_decoder.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/image_decoder.dart @@ -2,17 +2,17 @@ part of pdf; abstract class _ImageDecoder { //Fields - List imageData; - int width; - int height; - _ImageType format; - int offset; - int bitsPerComponent; - double jpegDecoderOrientationAngle; + late List imageData; + int width = 0; + int height = 0; + _ImageType? format; + late int offset; + int? bitsPerComponent; + double? jpegDecoderOrientationAngle; //Static methods - static _ImageDecoder getDecoder(List data) { - _ImageDecoder decoder; + static _ImageDecoder? getDecoder(List data) { + _ImageDecoder? decoder; if (_PdfUtils.isPng(data)) { decoder = _PngDecoder(data, _PdfUtils._pngSignature.length); } else if (_PdfUtils.isJpeg(data)) { @@ -25,7 +25,7 @@ abstract class _ImageDecoder { int _readByte() { if (offset < imageData.length) { final int value = imageData[offset]; - offset++; + offset = offset + 1; return value; } else { throw RangeError('invalid offset'); @@ -37,7 +37,7 @@ abstract class _ImageDecoder { } void _seek(int increment) { - offset += increment; + offset = offset + increment; } List _readBytes(int count) { @@ -62,7 +62,7 @@ abstract class _ImageDecoder { return i1 | (i2 << 8) | (i3 << 16) | (i4 << 24); } - String _readString(List imageData, int len) { + String _readString(List? imageData, int len) { String result = ''; for (int i = 0; i < len; i++) { result += String.fromCharCode(_readByte()); @@ -71,11 +71,11 @@ abstract class _ImageDecoder { } Map _read( - List stream, int streamOffset, List buffer, int length) { + List stream, int? streamOffset, List? buffer, int length) { int result = 0; - if (length <= stream.length && stream.length - streamOffset >= length) { + if (length <= stream.length && stream.length - streamOffset! >= length) { for (int i = 0; i < length; i++) { - buffer[i] = stream[streamOffset]; + buffer![i] = stream[streamOffset!]; streamOffset++; result++; } @@ -89,5 +89,5 @@ abstract class _ImageDecoder { //Abstract methods void readHeader(); - _PdfStream getImageDictionary(); + _PdfStream? getImageDictionary(); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/jpeg_decoder.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/jpeg_decoder.dart index c14a75672..64e218c5d 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/jpeg_decoder.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/jpeg_decoder.dart @@ -43,16 +43,16 @@ class _JpegDecoder extends _ImageDecoder { ]; //Fields - _PdfStream _imageStream; - bool _isContainsLittleEndian; - int _noOfComponents = -1; + _PdfStream? _imageStream; + late bool _isContainsLittleEndian; + int? _noOfComponents = -1; //Implementation @override void readHeader() { _reset(); bitsPerComponent = 8; - int imageOrientation = 0; + int? imageOrientation = 0; final Map returnValue = _checkForExifData(); final bool hasOrientation = returnValue['hasOrientation']; imageOrientation = returnValue['imageOrientation']; @@ -105,23 +105,24 @@ class _JpegDecoder extends _ImageDecoder { } @override - _PdfStream getImageDictionary() { + _PdfStream? getImageDictionary() { _imageStream = _PdfStream(); - _imageStream._dataStream = imageData; - _imageStream.compress = false; + _imageStream!._dataStream = imageData; + _imageStream!.compress = false; - _imageStream[_DictionaryProperties.type] = + _imageStream![_DictionaryProperties.type] = _PdfName(_DictionaryProperties.xObject); - _imageStream[_DictionaryProperties.subtype] = + _imageStream![_DictionaryProperties.subtype] = _PdfName(_DictionaryProperties.image); - _imageStream[_DictionaryProperties.width] = _PdfNumber(width); - _imageStream[_DictionaryProperties.height] = _PdfNumber(height); - _imageStream[_DictionaryProperties.bitsPerComponent] = - _PdfNumber(bitsPerComponent); - _imageStream[_DictionaryProperties.filter] = + _imageStream![_DictionaryProperties.width] = _PdfNumber(width); + _imageStream![_DictionaryProperties.height] = _PdfNumber(height); + _imageStream![_DictionaryProperties.bitsPerComponent] = + _PdfNumber(bitsPerComponent!); + _imageStream![_DictionaryProperties.filter] = _PdfName(_DictionaryProperties.dctDecode); - _imageStream[_DictionaryProperties.colorSpace] = _PdfName(_getColorSpace()); - _imageStream[_DictionaryProperties.decodeParms] = _getDecodeParams(); + _imageStream![_DictionaryProperties.colorSpace] = + _PdfName(_getColorSpace()); + _imageStream![_DictionaryProperties.decodeParms] = _getDecodeParams(); return _imageStream; } @@ -133,12 +134,12 @@ class _JpegDecoder extends _ImageDecoder { decodeParams[_DictionaryProperties.k] = _PdfNumber(-1); decodeParams[_DictionaryProperties.predictor] = _PdfNumber(15); decodeParams[_DictionaryProperties.bitsPerComponent] = - _PdfNumber(bitsPerComponent); + _PdfNumber(bitsPerComponent!); return decodeParams; } Map _checkForExifData() { - int imageOrientation = 0; + int? imageOrientation = 0; _reset(); if (_convertToUShort(_readJpegBytes(2)) != 0xFFD8) { return { @@ -146,8 +147,8 @@ class _JpegDecoder extends _ImageDecoder { 'imageOrientation': imageOrientation }; } - int jpegMarkerStart; - int jpegMarkerNum = 0; + int? jpegMarkerStart; + int? jpegMarkerNum = 0; while ((jpegMarkerStart = _readByte()) == 0xFF && (jpegMarkerNum = _readByte()) != 0xE1) { final int jpegDataLength = _convertToUShort(_readJpegBytes(2)); @@ -205,7 +206,7 @@ class _JpegDecoder extends _ImageDecoder { } _seek(6); final List orientationData = _readJpegBytes(4); - int orientationAngle = 0; + int? orientationAngle = 0; for (int i = 0; i < orientationData.length; i++) { if (orientationData[i] != 0) { orientationAngle = orientationData[i]; @@ -285,7 +286,7 @@ class _JpegDecoder extends _ImageDecoder { int _getMarker() { int skippedByte = 0; - int marker = _readByte(); + int? marker = _readByte(); while (marker != 255) { skippedByte++; marker = _readByte(); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/png_decoder.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/png_decoder.dart index 2cafddb11..7daad31f3 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/png_decoder.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/png_decoder.dart @@ -18,27 +18,27 @@ class _PngDecoder extends _ImageDecoder { } //Fields - int _currentChunkLength; - _PngHeader _header; - bool _issRGB; - bool _isDecode; - bool _shades; - bool _ideateDecode; - int _colors; - int _bitsPerPixel; - int _idatLength; - int _inputBands; - List _maskData; - List _alpha; - List _iDatStream; - List _dataStream; - int _dataStreamOffset; - _PdfArray _colorSpace; - List _decodedImageData; + late int _currentChunkLength; + late _PngHeader _header; + late bool _issRGB; + late bool _isDecode; + late bool _shades; + late bool _ideateDecode; + late int _colors; + late int _bitsPerPixel; + late int _idatLength; + late int _inputBands; + List? _maskData; + late List _alpha; + List? _iDatStream; + late List _dataStream; + int? _dataStreamOffset; + _PdfArray? _colorSpace; + List? _decodedImageData; //Implementation void _initialize() { - _PngChunkTypes header; + _PngChunkTypes? header; dynamic result = _hasValidChunkType(header); while (result['hasValidChunk'] as bool) { header = result['type']; @@ -76,18 +76,20 @@ class _PngDecoder extends _ImageDecoder { case _PngChunkTypes.unknown: _ignoreChunk(); break; + default: + break; } result = _hasValidChunkType(header); } } - Map _hasValidChunkType(_PngChunkTypes type) { + Map _hasValidChunkType(_PngChunkTypes? type) { type = _PngChunkTypes.unknown; if (offset + 8 <= imageData.length) { _currentChunkLength = _readUInt32(imageData, offset); _seek(4); final String chunk = _readString(imageData, 4); - final _PngChunkTypes header = _getChunkType(chunk); + final _PngChunkTypes? header = _getChunkType(chunk); if (header != null) { type = header; return {'type': type, 'hasValidChunk': true}; @@ -102,7 +104,7 @@ class _PngDecoder extends _ImageDecoder { } } - _PngChunkTypes _getChunkType(String chunk) { + _PngChunkTypes? _getChunkType(String chunk) { switch (chunk) { case 'IHDR': return _PngChunkTypes.iHDR; @@ -145,7 +147,7 @@ class _PngDecoder extends _ImageDecoder { } } - _PngFilterTypes _getFilterType(int type) { + _PngFilterTypes _getFilterType(int? type) { switch (type) { case 1: return _PngFilterTypes.sub; @@ -169,7 +171,7 @@ class _PngDecoder extends _ImageDecoder { void _setBitsPerPixel() { _bitsPerPixel = _header.bitDepth == 16 ? 2 : 1; if (_header.colorType == 0) { - _idatLength = ((bitsPerComponent * width + 7) / 8 * height).toInt(); + _idatLength = ((bitsPerComponent! * width + 7) / 8 * height).toInt(); _inputBands = 1; } else if (_header.colorType == 2) { _idatLength = width * height * 3; @@ -203,8 +205,8 @@ class _PngDecoder extends _ImageDecoder { if (_currentChunkLength <= imageData.length && imageData.length - offset >= _currentChunkLength) { for (int i = 0; i < _currentChunkLength; i++) { - _iDatStream.add(imageData[offset]); - offset++; + _iDatStream!.add(imageData[offset]); + offset = offset + 1; } } _seek(4); @@ -220,23 +222,23 @@ class _PngDecoder extends _ImageDecoder { final int length = width * height; _maskData = []; for (int i = 0; i < length; i++) { - _maskData.add(0); + _maskData!.add(0); } } if (_iDatStream != null) { - _dataStream = _getDeflatedData(_iDatStream); + _dataStream = _getDeflatedData(_iDatStream!); _dataStreamOffset = 0; } _decodedImageData = List.filled(_idatLength, 0, growable: true); _readDecodeData(); - if (_decodedImageData != null && _decodedImageData.isEmpty && _shades) { + if (_decodedImageData != null && _decodedImageData!.isEmpty && _shades) { _ideateDecode = false; - _decodedImageData = _iDatStream.toList(growable: true); + _decodedImageData = _iDatStream!.toList(growable: true); } } else { _ideateDecode = false; - _decodedImageData = _iDatStream.toList(growable: true); + _decodedImageData = _iDatStream!.toList(growable: true); } } @@ -244,14 +246,14 @@ class _PngDecoder extends _ImageDecoder { final List idatData = data.sublist(2, data.length - 4); final _DeflateStream deflateStream = _DeflateStream(idatData, 0, true); List buffer = List.filled(4096, 0); - int numRead = 0; + int? numRead = 0; final List outputData = []; do { final Map result = deflateStream._read(buffer, 0, buffer.length); numRead = result['count']; buffer = result['data']; - for (int i = 0; i < numRead; i++) { + for (int i = 0; i < numRead!; i++) { outputData.add(buffer[i]); } } while (numRead > 0); @@ -273,18 +275,19 @@ class _PngDecoder extends _ImageDecoder { } void _decodeData( - int xOffset, int yOffset, int xStep, int yStep, int width, int height) { + int xOffset, int yOffset, int xStep, int yStep, int? width, int? height) { if ((width == 0) || (height == 0)) { return; } else { - final int bytesPerRow = (_inputBands * width * _header.bitDepth + 7) ~/ 8; + final int bytesPerRow = + (_inputBands * width! * _header.bitDepth + 7) ~/ 8; List current = List.filled(bytesPerRow, 0); List prior = List.filled(bytesPerRow, 0); for (int sourceY = 0, destinationY = yOffset; - sourceY < height; + sourceY < height!; sourceY++, destinationY += yStep) { - final int filter = _dataStream[_dataStreamOffset]; - _dataStreamOffset++; + final int? filter = _dataStream[_dataStreamOffset!]; + _dataStreamOffset = _dataStreamOffset! + 1; _dataStreamOffset = _readStream(_dataStream, _dataStreamOffset, current, bytesPerRow); switch (_getFilterType(filter)) { @@ -313,8 +316,8 @@ class _PngDecoder extends _ImageDecoder { } } - int _readStream( - List stream, int streamOffset, List data, int count) { + int? _readStream( + List stream, int? streamOffset, List? data, int count) { final dynamic result = _read(stream, streamOffset, data, count); data = result['outputBuffer']; streamOffset = result['offset']; @@ -325,7 +328,7 @@ class _PngDecoder extends _ImageDecoder { return streamOffset; } - void _processPixels(List data, int x, int step, int y, int width) { + void _processPixels(List data, int x, int step, int y, int? width) { int sourceX, destX, size = 0; final List pixel = _getPixel(data); if (_header.colorType == 0 || @@ -335,10 +338,10 @@ class _PngDecoder extends _ImageDecoder { } else if (_header.colorType == 2 || _header.colorType == 6) { size = 3; } - if (_decodedImageData != null && _decodedImageData.isNotEmpty) { + if (_decodedImageData != null && _decodedImageData!.isNotEmpty) { destX = x; final int depth = (_header.bitDepth == 16) ? 8 : _header.bitDepth; - final int yStep = (size * width * depth + 7) ~/ 8; + final int yStep = (size * width! * depth + 7) ~/ 8; for (sourceX = 0; sourceX < width; sourceX++) { _decodedImageData = _setPixel(_decodedImageData, pixel, _inputBands * sourceX, size, destX, y, _header.bitDepth, yStep); @@ -350,23 +353,23 @@ class _PngDecoder extends _ImageDecoder { if (shades) { if ((_header.colorType & 4) != 0) { if (_header.bitDepth == 16) { - for (int i = 0; i < width; ++i) { + for (int i = 0; i < width!; ++i) { final int temp = i * _inputBands + size; pixel[temp] = ((pixel[temp]).toUnsigned(32) >> 8).toSigned(32); } } - final int yStep = width; + final int? yStep = width; destX = x; - for (sourceX = 0; sourceX < width; sourceX++) { + for (sourceX = 0; sourceX < width!; sourceX++) { _maskData = _setPixel(_maskData, pixel, _inputBands * sourceX + size, 1, destX, y, 8, yStep); destX += step; } } else { - final int yStep = width; - final List dt = List(1); + final int? yStep = width; + final List dt = List.filled(1, 0, growable: true); destX = x; - for (sourceX = 0; sourceX < width; sourceX++) { + for (sourceX = 0; sourceX < width!; sourceX++) { final int index = pixel[sourceX]; if (index < _alpha.length) { dt[0] = _alpha[index]; @@ -382,51 +385,54 @@ class _PngDecoder extends _ImageDecoder { List _getPixel(List data) { if (_header.bitDepth == 8) { - final List pixel = List(data.length); + final List pixel = List.filled(data.length, 0, growable: true); for (int i = 0; i < pixel.length; ++i) { pixel[i] = data[i] & 0xff; } return pixel; } else if (_header.bitDepth == 16) { - final List pixel = List(data.length ~/ 2); + final List pixel = + List.filled(data.length ~/ 2, 0, growable: true); for (int i = 0; i < pixel.length; ++i) { pixel[i] = ((data[i * 2] & 0xff) << 8) + (data[i * 2 + 1] & 0xff); } return pixel; } else { - final List pixel = List(data.length * 8 ~/ _header.bitDepth); + final List pixel = List.filled( + data.length * 8 ~/ _header.bitDepth, 0, + growable: true); int index = 0; final int p = 8 ~/ _header.bitDepth; final int mask = (1 << _header.bitDepth) - 1; for (int n = 0; n < data.length; ++n) { for (int i = p - 1; i >= 0; --i) { final int hb = _header.bitDepth * i; - final int d = data[n]; + final int? d = data[n]; pixel[index++] = - ((hb < 1) ? d : (d.toUnsigned(32) >> hb).toSigned(32)) & mask; + ((hb < 1) ? d : (d!.toUnsigned(32) >> hb).toSigned(32))! & mask; } } return pixel; } } - List _setPixel(List imageData, List data, int offset, int size, - int x, int y, int bitDepth, int bpr) { + List? _setPixel(List? imageData, List data, int offset, + int size, int x, int y, int? bitDepth, int? bpr) { if (bitDepth == 8) { - final int position = bpr * y + size * x; + final int position = bpr! * y + size * x; for (int i = 0; i < size; ++i) { - imageData[position + i] = (data[i + offset]).toUnsigned(8); + imageData![position + i] = data[i + offset].toUnsigned(8); } } else if (bitDepth == 16) { - final int position = bpr * y + size * x; + final int position = bpr! * y + size * x; for (int i = 0; i < size; ++i) { - imageData[position + i] = (data[i + offset] >> 8).toUnsigned(8); + imageData![position + i] = (data[i + offset] >> 8).toUnsigned(8); } } else { - final int position = bpr * y + x ~/ (8 / bitDepth); + final int position = bpr! * y + x ~/ (8 / bitDepth!); final int t = data[offset] << (8 - bitDepth * (x % (8 / bitDepth)) - bitDepth).toInt(); - imageData[position] |= t.toUnsigned(8); + imageData![position] = imageData[position] | t.toUnsigned(8); } return imageData; } @@ -492,10 +498,10 @@ class _PngDecoder extends _ImageDecoder { void _readPLTE() { if (_header.colorType == 3) { _colorSpace = _PdfArray(); - _colorSpace._add(_PdfName(_DictionaryProperties.indexed)); - _colorSpace._add(_getPngColorSpace()); - _colorSpace._add(_PdfNumber(_currentChunkLength / 3 - 1)); - _colorSpace._add(_PdfString.fromBytes(_readBytes(_currentChunkLength))); + _colorSpace!._add(_PdfName(_DictionaryProperties.indexed)); + _colorSpace!._add(_getPngColorSpace()); + _colorSpace!._add(_PdfNumber(_currentChunkLength / 3 - 1)); + _colorSpace!._add(_PdfString.fromBytes(_readBytes(_currentChunkLength))); _seek(4); } else { _ignoreChunk(); @@ -506,7 +512,7 @@ class _PngDecoder extends _ImageDecoder { if (_header.colorType == 3) { final List alpha = _readBytes(_currentChunkLength); _seek(4); - _alpha = List(alpha.length); + _alpha = List.filled(alpha.length, 0, growable: true); for (int i = 0; i < alpha.length; i++) { _alpha[i] = alpha[i]; final int sh = alpha[i] & 0xff; @@ -597,7 +603,7 @@ class _PngDecoder extends _ImageDecoder { } void _setMask(_PdfStream imageStream) { - if (_maskData != null && _maskData.isNotEmpty) { + if (_maskData != null && _maskData!.isNotEmpty) { final _PdfStream stream = _PdfStream(); stream._dataStream = _maskData; stream[_DictionaryProperties.type] = @@ -610,7 +616,7 @@ class _PngDecoder extends _ImageDecoder { stream[_DictionaryProperties.bitsPerComponent] = _PdfNumber(8); } else { stream[_DictionaryProperties.bitsPerComponent] = - _PdfNumber(bitsPerComponent); + _PdfNumber(bitsPerComponent!); } stream[_DictionaryProperties.colorSpace] = _PdfName(_DictionaryProperties.deviceGray); @@ -625,7 +631,7 @@ class _PngDecoder extends _ImageDecoder { decodeParams[_DictionaryProperties.colors] = _PdfNumber(_colors); decodeParams[_DictionaryProperties.predictor] = _PdfNumber(15); decodeParams[_DictionaryProperties.bitsPerComponent] = - _PdfNumber(bitsPerComponent); + _PdfNumber(bitsPerComponent!); return decodeParams; } @@ -666,7 +672,7 @@ class _PngDecoder extends _ImageDecoder { imageStream[_DictionaryProperties.bitsPerComponent] = _PdfNumber(8); } else { imageStream[_DictionaryProperties.bitsPerComponent] = - _PdfNumber(bitsPerComponent); + _PdfNumber(bitsPerComponent!); } if (!_isDecode || !_ideateDecode) { imageStream[_DictionaryProperties.filter] = @@ -697,11 +703,11 @@ class _PngHeader { interlace = 0; filter = _PngFilterTypes.none; } - int width; - int height; - int colorType; - int compression; - int bitDepth; - _PngFilterTypes filter; - int interlace; + late int width; + late int height; + late int colorType; + late int compression; + late int bitDepth; + late _PngFilterTypes filter; + late int interlace; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/pdf_bitmap.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/pdf_bitmap.dart index b0e019f25..31f5fe54b 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/pdf_bitmap.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/pdf_bitmap.dart @@ -52,7 +52,6 @@ class PdfBitmap extends PdfImage { /// doc.dispose(); /// ``` PdfBitmap.fromBase64String(String imageData) { - ArgumentError.checkNotNull(imageData); if (imageData.isEmpty) { ArgumentError.value(imageData, 'image data', 'image data cannot be null'); } @@ -60,13 +59,13 @@ class PdfBitmap extends PdfImage { } //Fields - _ImageDecoder _decoder; - int _height; - int _width; - double _horizontalResolution; - double _verticalResolution; + _ImageDecoder? _decoder; + late int _height; + late int _width; + double _horizontalResolution = 0; + double _verticalResolution = 0; bool _imageStatus = true; - PdfColorSpace _colorSpace; + PdfColorSpace? _colorSpace; //Properties @override @@ -159,32 +158,29 @@ class PdfBitmap extends PdfImage { //Implementation void _initialize(List imageData) { - ArgumentError.checkNotNull(imageData); if (imageData.isEmpty) { ArgumentError.value(imageData, 'image data', 'image data cannot be null'); } _colorSpace = PdfColorSpace.rgb; - final _ImageDecoder decoder = _ImageDecoder.getDecoder(imageData); + final _ImageDecoder? decoder = _ImageDecoder.getDecoder(imageData); if (decoder != null) { _decoder = decoder; - _height = _decoder.height; - _width = _decoder.width; - _jpegOrientationAngle = _decoder.jpegDecoderOrientationAngle; + _height = _decoder!.height; + _width = _decoder!.width; + _jpegOrientationAngle = _decoder!.jpegDecoderOrientationAngle; _imageStatus = false; } else { throw UnsupportedError('Invalid/Unsupported image stream'); } - _horizontalResolution ??= 0; - _verticalResolution ??= 0; } void _setColorSpace() { - final _PdfStream stream = _imageStream; - final _PdfName color = stream[_DictionaryProperties.colorSpace] as _PdfName; - if (color._name == _DictionaryProperties.deviceCMYK) { + final _PdfStream stream = _imageStream!; + final _PdfName? color = + stream[_DictionaryProperties.colorSpace] as _PdfName?; + if (color!._name == _DictionaryProperties.deviceCMYK) { _colorSpace = PdfColorSpace.cmyk; - } else if (color != null && - color._name == _DictionaryProperties.deviceGray) { + } else if (color._name == _DictionaryProperties.deviceGray) { _colorSpace = PdfColorSpace.grayScale; } if (_decoder is _PngDecoder && @@ -213,6 +209,8 @@ class PdfBitmap extends PdfImage { stream[_DictionaryProperties.colorSpace] = (_decoder as _PngDecoder)._colorSpace; break; + default: + break; } } @@ -220,9 +218,9 @@ class PdfBitmap extends PdfImage { void _save() { if (!_imageStatus) { _imageStatus = true; - _imageStream = _decoder.getImageDictionary(); - if (_decoder.format == _ImageType.png) { - final _PngDecoder decoder = _decoder as _PngDecoder; + _imageStream = _decoder!.getImageDictionary(); + if (_decoder!.format == _ImageType.png) { + final _PngDecoder? decoder = _decoder as _PngDecoder?; if (decoder != null && decoder._isDecode) { if (decoder._colorSpace != null) { _setColorSpace(); @@ -238,7 +236,6 @@ class PdfBitmap extends PdfImage { @override void _drawInternal(PdfGraphics graphics, _Rectangle bounds) { - ArgumentError.checkNotNull(graphics); graphics.drawImage( this, Rect.fromLTWH(0, 0, _width * 0.75, _height * 0.75)); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/pdf_image.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/pdf_image.dart index f7c686da7..efe1e754b 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/pdf_image.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/pdf_image.dart @@ -18,8 +18,8 @@ part of pdf; /// ``` abstract class PdfImage extends PdfShapeElement implements _IPdfWrapper { //Fields - double _jpegOrientationAngle; - _PdfStream _imageStream; + double? _jpegOrientationAngle; + _PdfStream? _imageStream; //Properties /// Width of an image @@ -127,9 +127,10 @@ abstract class PdfImage extends PdfShapeElement implements _IPdfWrapper { void _save(); @override - _IPdfPrimitive get _element => _imageStream; + _IPdfPrimitive? get _element => _imageStream; @override - set _element(_IPdfPrimitive value) { - _imageStream = value; + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + _imageStream = value as _PdfStream?; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_color.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_color.dart index 14eabedc9..5604fdda4 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_color.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_color.dart @@ -1,10 +1,36 @@ part of pdf; /// Implements structures and routines working with color. +/// +/// ```dart +/// //Creates a new PDF document. +/// PdfDocument document = PdfDocument() +/// ..pages.add().graphics.drawString( +/// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), +/// //Using PDF color set pen. +/// pen: PdfPen(PdfColor(200, 120, 80))); +/// //Saves the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfColor { /// Initializes a new instance of the [PdfColor] class /// with Red, Green, Blue and Alpha channels. - PdfColor(int red, int green, int blue, [int alpha]) { + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// //Using PDF color set pen. + /// pen: PdfPen(PdfColor(200, 120, 80))); + /// //Saves the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfColor(int red, int green, int blue, [int alpha = 255]) { _black = 0; _cyan = 0; _magenta = 0; @@ -13,13 +39,26 @@ class PdfColor { _red = red; _green = green; _blue = blue; - _alpha = alpha ?? _maxColourChannelValue.toInt(); + _alpha = alpha; _isFilled = _alpha != 0; _assignCMYK(_red, _green, _blue); } /// Initializes a new instance of the [PdfColor] class /// with Cyan, Magenta, Yellow and Black channels. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// //Using PDF color set pen. + /// pen: PdfPen(PdfColor.fromCMYK(200, 120, 80, 40))); + /// //Saves the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfColor.fromCMYK(double cyan, double magenta, double yellow, double black) { _red = 0; _cyan = cyan; @@ -86,6 +125,19 @@ class PdfColor { int get hashCode => _alpha.hashCode; /// Gets the empty(null) color. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// //Create PDF color. + /// pen: PdfPen(PdfColor.empty)); + /// //Saves the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` static PdfColor get empty { return PdfColor._empty(); } @@ -95,63 +147,96 @@ class PdfColor { final Map _rgbStrings = {}; /// Value of Red channel. - int _red; + late int _red; /// Value of Cyan channel. - double _cyan; + late double _cyan; /// Value of Green channel. - int _green; + late int _green; /// Value of Magenta channel. - double _magenta; + late double _magenta; /// Value of Blue channel. - int _blue; + late int _blue; /// Value of Yellow channel. - double _yellow; + late double _yellow; /// Value of Black channel. - double _black; + late double _black; /// Value of Gray channel. - double _gray; + late double _gray; /// Value of alpha channel. - int _alpha; + late int _alpha; /// Shows if the color is empty. - bool _isFilled; + bool _isFilled = false; /// Max value of color channel. final double _maxColourChannelValue = 255.0; //Properties - /// Gets Red channel value. + /// Gets or sets Red channel value. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// //sets the red channel value. + /// pen: PdfPen(PdfColor(0, 0, 0)..r = 255), + /// bounds: Rect.fromLTWH(10, 10, 200, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` int get r => _red; - - /// Sets Red channel value. set r(int value) { _red = value; _assignCMYK(_red, _green, _blue); _isFilled = true; } - /// Gets Green channel value. + /// Gets or sets Green channel value. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// //sets the green channel value. + /// pen: PdfPen(PdfColor(0, 0, 0)..g = 255), + /// bounds: Rect.fromLTWH(10, 10, 200, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` int get g => _green; - - /// Sets Red channel value. set g(int value) { _green = value; _assignCMYK(_red, _green, _blue); _isFilled = true; } - /// Gets Blue channel value. + /// Gets or sets Blue channel value. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// //sets the blue channel value. + /// pen: PdfPen(PdfColor(0, 0, 0)..b = 255), + /// bounds: Rect.fromLTWH(10, 10, 200, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` int get b => _blue; - - /// Sets Blue channel value. set b(int value) { _blue = value; _assignCMYK(_red, _green, _blue); @@ -159,6 +244,21 @@ class PdfColor { } /// Gets whether the PDFColor is Empty or not. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF pen instance. + /// PdfColor color = PdfColor.empty; + /// //Draw rectangle with the pen. + /// document.pages.add().graphics.drawString('Color present: ${color.isEmpty}', + /// PdfStandardFont(PdfFontFamily.helvetica, 12), + /// pen: PdfPen(color)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` bool get isEmpty => !_isFilled; //Implementation @@ -204,7 +304,7 @@ class PdfColor { } /// Converts [PdfColor] to PDF string representation. - String _toString(PdfColorSpace colorSpace, bool stroke) { + String _toString(PdfColorSpace? colorSpace, bool stroke) { if (isEmpty) { return ''; } @@ -218,14 +318,17 @@ class PdfColor { key += 1 << 24; } String colour; - Object obj; + Object? obj; if (_rgbStrings.containsKey(key)) { obj = _rgbStrings[key]; } if (obj == null) { - final double red = r / _maxColourChannelValue; - final double green = g / _maxColourChannelValue; - final double blue = b / _maxColourChannelValue; + dynamic red = r / _maxColourChannelValue; + dynamic green = g / _maxColourChannelValue; + dynamic blue = b / _maxColourChannelValue; + red = red % 1 == 0 ? red.toInt() : red; + green = green % 1 == 0 ? green.toInt() : green; + blue = blue % 1 == 0 ? blue.toInt() : blue; colour = _trimEnd(red.toString()) + ' ' + _trimEnd(green.toString()) + @@ -246,9 +349,8 @@ class PdfColor { return color.isEmpty ? '0' : color; } - _PdfArray _toArray([PdfColorSpace colorSpace]) { + _PdfArray _toArray([PdfColorSpace colorSpace = PdfColorSpace.rgb]) { final _PdfArray array = _PdfArray(); - switch (colorSpace) { case PdfColorSpace.cmyk: array._add(_PdfNumber(_cyan)); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_graphics.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_graphics.dart index 708e41b87..bc8876f6d 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_graphics.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_graphics.dart @@ -1,13 +1,25 @@ part of pdf; /// Represents a graphics context of the objects. +/// +/// ```dart +/// //Creates a new PDF document. +/// PdfDocument doc = PdfDocument() +/// ..pages +/// .add() +/// //PDF graphics for the page. +/// .graphics +/// .drawRectangle( +/// brush: PdfBrushes.red, bounds: Rect.fromLTWH(0, 0, 515, 762)); +/// //Saves the document. +/// List bytes = doc.save(); +/// //Dispose the document. +/// doc.dispose(); +/// ``` class PdfGraphics { //Constructor /// Initializes a new instance of the [PdfGraphics] class. PdfGraphics._(Size size, Function getResources, _PdfStream stream) { - ArgumentError.checkNotNull(stream); - ArgumentError.checkNotNull(getResources); - ArgumentError.checkNotNull(size); _streamWriter = _PdfStreamWriter(stream); _getResources = getResources; _canvasSize = size; @@ -15,25 +27,25 @@ class PdfGraphics { } //Fields - Function _getResources; - _PdfStreamWriter _streamWriter; - Size _canvasSize; - bool _isStateSaved; - PdfPen _currentPen; - PdfBrush _currentBrush; - PdfFont _currentFont; - _PdfTransformationMatrix _transformationMatrix; + Function? _getResources; + _PdfStreamWriter? _streamWriter; + late Size _canvasSize; + late bool _isStateSaved; + PdfPen? _currentPen; + PdfBrush? _currentBrush; + PdfFont? _currentFont; + _PdfTransformationMatrix? _transformationMatrix; final bool _hasTransparencyBrush = false; - int _previousTextRenderingMode = _TextRenderingMode.fill; - double _previousCharacterSpacing; - double _previousWordSpacing; - double _previousTextScaling; - PdfStringFormat _currentStringFormat; - _Rectangle _clipBounds; - _PdfArray _cropBox; - List _graphicsState; - double _mediaBoxUpperRightBound; - PdfPageLayer _layer; + int? _previousTextRenderingMode = _TextRenderingMode.fill; + double? _previousCharacterSpacing; + double? _previousWordSpacing; + double? _previousTextScaling; + PdfStringFormat? _currentStringFormat; + late _Rectangle _clipBounds; + _PdfArray? _cropBox; + late List _graphicsState; + double? _mediaBoxUpperRightBound; + PdfPageLayer? _layer; bool _colorSpaceChanged = false; bool _isColorSpaceInitialized = false; final Map _colorSpaces = { @@ -42,36 +54,83 @@ class PdfGraphics { PdfColorSpace.grayScale: 'GrayScale', PdfColorSpace.indexed: 'Indexed' }; - _PdfStringLayoutResult _stringLayoutResult; - _PdfAutomaticFieldInfoCollection _automaticFields; - Map<_TransparencyData, _PdfTransparency> _trasparencies; - PdfLayer _documentLayer; + _PdfStringLayoutResult? _stringLayoutResult; + _PdfAutomaticFieldInfoCollection? _automaticFields; + Map<_TransparencyData, _PdfTransparency>? _trasparencies; + PdfLayer? _documentLayer; //Properties - /// Gets or sets the current color space of the document - PdfColorSpace colorSpace; + /// Gets or sets the current color space of the document. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument(); + /// //Create PDF graphics for the page + /// doc.pages.add().graphics + /// ..colorSpace = PdfColorSpace.grayScale + /// ..drawRectangle( + /// brush: PdfBrushes.red, bounds: Rect.fromLTWH(0, 0, 515, 762)); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` + late PdfColorSpace colorSpace; /// Gets the size of the canvas. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument(); + /// //Create PDF graphics for the page. + /// PdfGraphics graphics = doc.pages.add().graphics; + /// //Draw string to PDF page graphics. + /// graphics.drawString( + /// //Get the graphics canvas size. + /// 'Canvas size: ${graphics.size}', + /// PdfStandardFont(PdfFontFamily.courier, 12)); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` Size get size => _canvasSize; /// Gets the size of the canvas reduced by margins and page templates. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument(); + /// //Create PDF graphics for the page + /// PdfGraphics graphics = doc.pages.add().graphics; + /// //Draw rectangle to PDF page graphics. + /// graphics.drawRectangle( + /// brush: PdfBrushes.red, + /// //Get the graphics client size. + /// bounds: Rect.fromLTWH( + /// 0, 0, graphics.clientSize.width, graphics.clientSize.height)); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` Size get clientSize => Size(_clipBounds.width, _clipBounds.height); _PdfTransformationMatrix get _matrix { _transformationMatrix ??= _PdfTransformationMatrix(); - return _transformationMatrix; + return _transformationMatrix!; } - PdfPage get _page { + PdfPage? get _page { if (_documentLayer != null) { - return _documentLayer._page; + return _documentLayer!._page; } else { - return _layer.page; + return _layer!.page; } } /// Gets the automatic fields. - _PdfAutomaticFieldInfoCollection get _autoFields { + _PdfAutomaticFieldInfoCollection? get _autoFields { _automaticFields ??= _PdfAutomaticFieldInfoCollection(); return _automaticFields; } @@ -80,10 +139,27 @@ class PdfGraphics { /// Changes the origin of the coordinate system /// by prepending the specified translation /// to the transformation matrix of this Graphics. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument(); + /// //Create PDF graphics for the page + /// doc.pages.add().graphics + /// ..save() + /// //Set graphics translate transform. + /// ..translateTransform(100, 100) + /// ..drawString('Hello world!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// brush: PdfBrushes.red) + /// ..restore(); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` void translateTransform(double offsetX, double offsetY) { final _PdfTransformationMatrix matrix = _PdfTransformationMatrix(); matrix._translate(offsetX, -offsetY); - _streamWriter._modifyCurrentMatrix(matrix); + _streamWriter!._modifyCurrentMatrix(matrix); _matrix._multiply(matrix); } @@ -94,13 +170,12 @@ class PdfGraphics { /// //Creates a new PDF document. /// PdfDocument doc = PdfDocument(); /// //Adds a page to the PDF document. - /// PdfPage page = doc.pages.add(); - /// PdfFont font = PdfStandardFont(PdfFontFamily.courier, 14); - /// //Set rotate transform - /// page.graphics.rotateTransform(-90); - /// //Draws the text into PDF graphics in -90 degree rotation. - /// page.graphics.drawString('Hello world.', font, - /// bounds: Rect.fromLTWH(-100, 0, 200, 50)); + /// doc.pages.add().graphics + /// //Set rotate transform + /// ..rotateTransform(-90) + /// //Draws the text into PDF graphics in -90 degree rotation. + /// ..drawString('Hello world.', PdfStandardFont(PdfFontFamily.courier, 14), + /// bounds: Rect.fromLTWH(-100, 0, 200, 50)); /// //Saves the document. /// List bytes = doc.save(); /// //Dispose the document. @@ -109,34 +184,74 @@ class PdfGraphics { void rotateTransform(double angle) { final _PdfTransformationMatrix matrix = _PdfTransformationMatrix(); matrix._rotate(-angle); - _streamWriter._modifyCurrentMatrix(matrix); + _streamWriter!._modifyCurrentMatrix(matrix); _matrix._multiply(matrix); } /// Saves the current state of this Graphics /// and identifies the saved state with a GraphicsState. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument(); + /// //Create PDF graphics for the page + /// PdfGraphics graphics = doc.pages.add().graphics; + /// //Save the graphics. + /// PdfGraphicsState state = graphics.save(); + /// //Set graphics translate transform. + /// graphics + /// ..translateTransform(100, 100) + /// ..drawString('Hello world!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// brush: PdfBrushes.red) + /// //Restore the graphics. + /// ..restore(state); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` PdfGraphicsState save() { final PdfGraphicsState state = PdfGraphicsState._(this, _matrix); state._brush = _currentBrush; state._pen = _currentPen; state._font = _currentFont; state._colorSpace = colorSpace; - state._characterSpacing = _previousCharacterSpacing; - state._wordSpacing = _previousWordSpacing; - state._textScaling = _previousTextScaling; - state._textRenderingMode = _previousTextRenderingMode; + state._characterSpacing = _previousCharacterSpacing!; + state._wordSpacing = _previousWordSpacing!; + state._textScaling = _previousTextScaling!; + state._textRenderingMode = _previousTextRenderingMode!; _graphicsState.add(state); if (_isStateSaved) { - _streamWriter._restoreGraphicsState(); + _streamWriter!._restoreGraphicsState(); _isStateSaved = false; } - _streamWriter._saveGraphicsState(); + _streamWriter!._saveGraphicsState(); return state; } /// Restores the state of this Graphics to the state represented /// by a GraphicsState. - void restore([PdfGraphicsState state]) { + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument(); + /// //Create PDF graphics for the page + /// PdfGraphics graphics = doc.pages.add().graphics; + /// //Save the graphics. + /// PdfGraphicsState state = graphics.save(); + /// //Set graphics translate transform. + /// graphics + /// ..translateTransform(100, 100) + /// ..drawString('Hello world!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// brush: PdfBrushes.red) + /// //Restore the graphics. + /// ..restore(state); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` + void restore([PdfGraphicsState? state]) { if (state == null) { if (_graphicsState.isNotEmpty) { _doRestoreState(); @@ -160,11 +275,30 @@ class PdfGraphics { } } - /// Draws the specified text string at the specified location + /// Draws the specified text string at the specified location. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument() + /// ..pages + /// .add() + /// .graphics + /// //Draw string to PDF page graphics. + /// .drawString( + /// 'Hello world!', + /// PdfStandardFont(PdfFontFamily.helvetica, 12, + /// style: PdfFontStyle.bold), + /// bounds: Rect.fromLTWH(0, 0, 200, 50), + /// brush: PdfBrushes.red, + /// pen: PdfPens.blue, + /// format: PdfStringFormat(alignment: PdfTextAlignment.left)); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` void drawString(String s, PdfFont font, - {PdfPen pen, PdfBrush brush, Rect bounds, PdfStringFormat format}) { - ArgumentError.checkNotNull(s); - ArgumentError.checkNotNull(font); + {PdfPen? pen, PdfBrush? brush, Rect? bounds, PdfStringFormat? format}) { _Rectangle layoutRectangle; if (bounds != null) { layoutRectangle = _Rectangle.fromRect(bounds); @@ -182,28 +316,55 @@ class PdfGraphics { } /// Draws a line connecting the two points specified by the coordinate pairs. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument doc = PdfDocument() + /// ..pages + /// .add() + /// .graphics + /// //Draw line. + /// .drawLine(PdfPens.black, Offset(100, 100), Offset(200, 100)); + /// // Save the document. + /// List bytes = doc.save(); + /// // Dispose the document. + /// doc.dispose(); + /// ``` void drawLine(PdfPen pen, Offset point1, Offset point2) { _beginMarkContent(); _stateControl(pen, null, null, null); - _streamWriter._beginPath(point1.dx, point1.dy); - _streamWriter._appendLineSegment(point2.dx, point2.dy); - _streamWriter._strokePath(); + _streamWriter!._beginPath(point1.dx, point1.dy); + _streamWriter!._appendLineSegment(point2.dx, point2.dy); + _streamWriter!._strokePath(); _endMarkContent(); - (_getResources() as _PdfResources) + (_getResources!() as _PdfResources) ._requireProcset(_DictionaryProperties.pdf); } /// Draws a rectangle specified by a pen, a brush and a Rect structure. - void drawRectangle({PdfPen pen, PdfBrush brush, Rect bounds}) { + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument doc = PdfDocument() + /// ..pages + /// .add() + /// .graphics + /// //Draw rectangle. + /// .drawRectangle( + /// pen: PdfPens.black, bounds: Rect.fromLTWH(0, 0, 200, 100)); + /// // Save the document. + /// List bytes = doc.save(); + /// // Dispose the document. + /// doc.dispose(); + /// ``` + void drawRectangle({PdfPen? pen, PdfBrush? brush, required Rect bounds}) { _beginMarkContent(); _stateControl(pen, brush, null, null); - bounds != null - ? _streamWriter._appendRectangle( - bounds.left, bounds.top, bounds.width, bounds.height) - : _streamWriter._appendRectangle(0, 0, 0, 0); + _streamWriter! + ._appendRectangle(bounds.left, bounds.top, bounds.width, bounds.height); _drawPath(pen, brush, PdfFillMode.winding, false); _endMarkContent(); - (_getResources() as _PdfResources) + (_getResources!() as _PdfResources) ._requireProcset(_DictionaryProperties.pdf); } @@ -212,26 +373,21 @@ class PdfGraphics { /// ```dart /// //Creates a new PDF document. /// PdfDocument doc = PdfDocument(); - /// //Adds a page to the PDF document. - /// PdfPage page = doc.pages.add(); - /// PdfFont font = PdfStandardFont(PdfFontFamily.courier, 14); - /// PdfBrush brush = PdfSolidBrush(PdfColor(255, 0, 0)); /// //Create a PDF Template. - /// PdfTemplate template = PdfTemplate(200, 100); - /// //Draws a rectangle into the graphics of the template. - /// template.graphics.drawRectangle(brush: brush, - /// bounds: Rect.fromLTWH(0, 20, 200, 50)); - /// //Draws a string into the graphics of the template. - /// template.graphics.drawString('This is PDF template.', font); + /// PdfTemplate template = PdfTemplate(200, 100) + /// ..graphics.drawRectangle( + /// brush: PdfSolidBrush(PdfColor(255, 0, 0)), + /// bounds: Rect.fromLTWH(0, 20, 200, 50)) + /// ..graphics.drawString( + /// 'This is PDF template.', PdfStandardFont(PdfFontFamily.courier, 14)); /// //Draws the template into the page graphics of the document. - /// page.graphics.drawPdfTemplate(template, Offset(100, 100)); + /// doc.pages.add().graphics.drawPdfTemplate(template, Offset(100, 100)); /// //Saves the document. /// List bytes = doc.save(); /// //Dispose the document. /// doc.dispose(); /// ``` - void drawPdfTemplate(PdfTemplate template, Offset location, [Size size]) { - ArgumentError.checkNotNull(template); + void drawPdfTemplate(PdfTemplate template, Offset location, [Size? size]) { size ??= template.size; _drawTemplate(template, location, size); } @@ -240,37 +396,259 @@ class PdfGraphics { /// /// ```dart /// //Creates a new PDF document. - /// PdfDocument doc = PdfDocument(); - /// //Draw the image. - /// doc.pages - /// .add() - /// .graphics - /// .drawImage(PdfBitmap(imageData), Rect.fromLTWH(0, 0, 100, 100)); + /// PdfDocument doc = PdfDocument() + /// ..pages + /// .add() + /// .graphics + /// //Draw image. + /// .drawImage(PdfBitmap(imageData), Rect.fromLTWH(0, 0, 100, 100)); /// //Saves the document. /// List bytes = doc.save(); /// //Dispose the document. /// doc.dispose(); /// ``` void drawImage(PdfImage image, Rect bounds) { - ArgumentError.checkNotNull(image); - ArgumentError.checkNotNull(bounds); _drawImage(image, bounds); } - /// Sets the transparency of this Graphics. - void setTransparency(double alpha, {double alphaBrush, PdfBlendMode mode}) { - if (alpha == null || alpha < 0 || alpha > 1) { + /// Sets the transparency of this graphics. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument(); + /// //Create PDF graphics for the page + /// doc.pages.add().graphics + /// //Set transparancy. + /// ..setTransparency(0.5, alphaBrush: 0.5, mode: PdfBlendMode.hardLight) + /// ..drawString('Hello world!', + /// PdfStandardFont(PdfFontFamily.helvetica, 12, style: PdfFontStyle.bold), + /// brush: PdfBrushes.red, pen: PdfPens.black); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` + void setTransparency(double alpha, + {double? alphaBrush, PdfBlendMode mode = PdfBlendMode.normal}) { + if (alpha < 0 || alpha > 1) { ArgumentError.value(alpha, 'alpha', 'invalid alpha value'); } alphaBrush ??= alpha; - mode ??= PdfBlendMode.normal; _setTransparency(alpha, alphaBrush, mode); } + /// Sets the clipping region of this Graphics to the result of the + /// specified operation combining the current clip region and the + /// rectangle specified by a RectangleF structure. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument(); + /// //Create PDF graphics for the page + /// doc.pages.add().graphics + /// //set clip. + /// ..setClip(bounds: Rect.fromLTWH(0, 0, 50, 12), mode: PdfFillMode.alternate) + /// ..drawString('Hello world!', PdfStandardFont(PdfFontFamily.helvetica, 12), + /// pen: PdfPens.red); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` + void setClip({Rect? bounds, PdfPath? path, PdfFillMode? mode}) { + if (bounds != null) { + mode ??= PdfFillMode.winding; + _streamWriter!._appendRectangle( + bounds.left, bounds.top, bounds.width, bounds.height); + _streamWriter!._clipPath(mode == PdfFillMode.alternate); + } else if (path != null) { + mode ??= path._fillMode; + _buildUpPath(path._points, path._pathTypes); + _streamWriter!._clipPath(mode == PdfFillMode.alternate); + } + } + + /// Draws a Bezier spline defined by four [Offset] structures. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument() + /// ..pages + /// .add() + /// .graphics + /// //Draw Bezier + /// .drawBezier( + /// Offset(10, 10), Offset(10, 50), Offset(50, 80), Offset(80, 10), + /// pen: PdfPens.brown); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` + void drawBezier(Offset startPoint, Offset firstControlPoint, + Offset secondControlPoint, Offset endPoint, + {PdfPen? pen}) { + _beginMarkContent(); + _stateControl(pen, null, null, null); + final _PdfStreamWriter sw = _streamWriter!; + sw._beginPath(startPoint.dx, startPoint.dy); + sw._appendBezierSegment(firstControlPoint.dx, firstControlPoint.dy, + secondControlPoint.dx, secondControlPoint.dy, endPoint.dx, endPoint.dy); + sw._strokePath(); + _endMarkContent(); + } + + /// Draws a GraphicsPath defined by a pen, a brush and path. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument() + /// ..pages + /// .add() + /// .graphics + /// //Draw Paths + /// .drawPath( + /// PdfPath() + /// ..addRectangle(Rect.fromLTWH(10, 10, 100, 100)) + /// ..addEllipse(Rect.fromLTWH(100, 100, 100, 100)), + /// pen: PdfPens.black, + /// brush: PdfBrushes.red); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` + void drawPath(PdfPath path, {PdfPen? pen, PdfBrush? brush}) { + _beginMarkContent(); + _stateControl(pen, brush, null, null); + _buildUpPath(path._points, path._pathTypes); + _drawPath(pen, brush, path._fillMode, false); + _endMarkContent(); + } + + /// Draws a pie shape defined by an ellipse specified by a Rect structure + /// uiand two radial lines. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument() + /// ..pages + /// .add() + /// .graphics + /// //Draw Pie + /// .drawPie(Rect.fromLTWH(10, 10, 100, 200), 90, 270, + /// pen: PdfPens.green, brush: PdfBrushes.red); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` + void drawPie(Rect bounds, double startAngle, double sweepAngle, + {PdfPen? pen, PdfBrush? brush}) { + if (sweepAngle != 0) { + _beginMarkContent(); + _stateControl(pen, brush, null, null); + _constructArcPath(bounds.left, bounds.top, bounds.left + bounds.width, + bounds.top + bounds.height, startAngle, sweepAngle); + _streamWriter!._appendLineSegment( + bounds.left + bounds.width / 2, bounds.top + bounds.height / 2); + _drawPath(pen, brush, PdfFillMode.winding, true); + _endMarkContent(); + } + } + + /// Draws an ellipse specified by a bounding Rect structure. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument() + /// ..pages + /// .add() + /// .graphics + /// //Draw ellipse + /// .drawEllipse(Rect.fromLTWH(10, 10, 100, 100), + /// pen: PdfPens.black, brush: PdfBrushes.red); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` + void drawEllipse(Rect bounds, {PdfPen? pen, PdfBrush? brush}) { + _beginMarkContent(); + _stateControl(pen, brush, null, null); + _constructArcPath( + bounds.left, bounds.top, bounds.right, bounds.bottom, 0, 360); + _drawPath(pen, brush, PdfFillMode.winding, true); + _endMarkContent(); + } + + /// Draws an arc representing a portion of an ellipse specified + /// by a Rect structure. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument() + /// ..pages + /// .add() + /// .graphics + /// //Draw Arc. + /// .drawArc(Rect.fromLTWH(10, 10, 100, 200), 90, 270, pen: PdfPens.red); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` + void drawArc(Rect bounds, double startAngle, double sweepAngle, + {PdfPen? pen}) { + if (sweepAngle != 0) { + _beginMarkContent(); + _stateControl(pen, null, null, null); + _constructArcPath(bounds.left, bounds.top, bounds.left + bounds.width, + bounds.top + bounds.height, startAngle, sweepAngle); + _drawPath(pen, null, PdfFillMode.winding, false); + _endMarkContent(); + } + } + + /// Draws a polygon defined by a brush, an array of [Offset] structures. + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument doc = PdfDocument() + /// ..pages.add().graphics.drawPolygon([ + /// Offset(10, 100), + /// Offset(10, 200), + /// Offset(100, 100), + /// Offset(100, 200), + /// Offset(55, 150) + /// ], pen: PdfPens.black, brush: PdfBrushes.red); + /// //Saves the document. + /// List bytes = doc.save(); + /// //Dispose the document. + /// doc.dispose(); + /// ``` + void drawPolygon(List points, {PdfPen? pen, PdfBrush? brush}) { + _beginMarkContent(); + if (points.isEmpty) { + return; + } + _stateControl(pen, brush, null, null); + _streamWriter!._beginPath(points.elementAt(0).dx, points.elementAt(0).dy); + + for (int i = 1; i < points.length; ++i) { + _streamWriter! + ._appendLineSegment(points.elementAt(i).dx, points.elementAt(i).dy); + } + _drawPath(pen, brush, PdfFillMode.winding, true); + _endMarkContent(); + } + //Implementation void _initialize() { _mediaBoxUpperRightBound = 0; _isStateSaved = false; + _isColorSpaceInitialized = false; + _currentBrush = null; colorSpace = PdfColorSpace.rgb; _previousTextRenderingMode = _TextRenderingMode.fill; _previousTextRenderingMode = -1; @@ -279,38 +657,43 @@ class PdfGraphics { _previousTextScaling = -100.0; _clipBounds = _Rectangle(0, 0, size.width, size.height); _graphicsState = []; - (_getResources() as _PdfResources) + (_getResources!() as _PdfResources) ._requireProcset(_DictionaryProperties.pdf); } void _setTransparency(double alpha, double alphaBrush, PdfBlendMode mode) { _trasparencies ??= <_TransparencyData, _PdfTransparency>{}; - _PdfTransparency transparency; + _PdfTransparency? transparency; final _TransparencyData transparencyData = _TransparencyData(alpha, alphaBrush, mode); - if (_trasparencies.containsKey(transparencyData)) { - transparency = _trasparencies[transparencyData]; + if (_trasparencies!.containsKey(transparencyData)) { + transparency = _trasparencies![transparencyData]; } if (transparency == null) { transparency = _PdfTransparency(alpha, alphaBrush, mode, conformance: _layer != null - ? _page._document._conformanceLevel == PdfConformanceLevel.a1b + ? _page!._document != null && + _page!._document!._conformanceLevel == PdfConformanceLevel.a1b : false); - _trasparencies[transparencyData] = transparency; + _trasparencies![transparencyData] = transparency; } - final _PdfResources resources = _getResources(); + final _PdfResources resources = _getResources!(); final _PdfName name = resources._getName(transparency); if (_layer != null) { - _page._setResources(resources); + _page!._setResources(resources); } - _streamWriter._setGraphicsState(name); + _streamWriter!._setGraphicsState(name); } void _drawImage(PdfImage image, Rect rectangle) { _beginMarkContent(); - final _Rectangle bounds = _Rectangle.fromRect(rectangle); - PdfGraphicsState beforeOrientation; - final int angle = image._jpegOrientationAngle.toInt(); + final _Rectangle bounds = _Rectangle.fromRect( + (rectangle.width <= 0 && rectangle.height <= 0) + ? Rect.fromLTWH(rectangle.left, rectangle.top, image.width * 0.75, + image.height * 0.75) + : rectangle); + PdfGraphicsState? beforeOrientation; + final int angle = image._jpegOrientationAngle!.toInt(); if (angle > 0) { beforeOrientation = save(); switch (angle) { @@ -352,53 +735,53 @@ class PdfGraphics { final _PdfTransformationMatrix matrix = _PdfTransformationMatrix(); matrix._translate(bounds.x, -(bounds.y + bounds.height)); matrix._scale(bounds.width, bounds.height); - _streamWriter._modifyCurrentMatrix(matrix); - final _PdfResources resources = _getResources(); + _streamWriter!._modifyCurrentMatrix(matrix); + final _PdfResources resources = _getResources!(); final _PdfName name = resources._getName(image); if (_layer != null) { - _page._setResources(resources); + _page!._setResources(resources); } - _streamWriter._executeObject(name); + _streamWriter!._executeObject(name); restore(state); if (beforeOrientation != null) { restore(beforeOrientation); } _endMarkContent(); - (_getResources() as _PdfResources) + (_getResources!() as _PdfResources) ._requireProcset(_DictionaryProperties.grayScaleImage); - (_getResources() as _PdfResources) + (_getResources!() as _PdfResources) ._requireProcset(_DictionaryProperties.colorImage); - (_getResources() as _PdfResources) + (_getResources!() as _PdfResources) ._requireProcset(_DictionaryProperties.indexedImage); - (_getResources() as _PdfResources) + (_getResources!() as _PdfResources) ._requireProcset(_DictionaryProperties.text); } void _drawPath( - PdfPen pen, PdfBrush brush, PdfFillMode fillMode, bool needClosing) { + PdfPen? pen, PdfBrush? brush, PdfFillMode fillMode, bool needClosing) { final bool isPen = pen != null && pen.color._isFilled; final bool isBrush = brush != null && (brush as PdfSolidBrush).color._isFilled; final bool isEvenOdd = fillMode == PdfFillMode.alternate; if (isPen && isBrush) { if (needClosing) { - _streamWriter._closeFillStrokePath(isEvenOdd); + _streamWriter!._closeFillStrokePath(isEvenOdd); } else { - _streamWriter._fillStrokePath(isEvenOdd); + _streamWriter!._fillStrokePath(isEvenOdd); } } else if (!isPen && !isBrush) { - _streamWriter._endPath(); + _streamWriter!._endPath(); } else if (isPen) { if (needClosing) { - _streamWriter._closeStrokePath(); + _streamWriter!._closeStrokePath(); } else { - _streamWriter._strokePath(); + _streamWriter!._strokePath(); } } else if (isBrush) { if (needClosing) { - _streamWriter._closeFillPath(isEvenOdd); + _streamWriter!._closeFillPath(isEvenOdd); } else { - _streamWriter._fillPath(isEvenOdd); + _streamWriter!._fillPath(isEvenOdd); } } else { throw UnsupportedError('Internal CLR error.'); @@ -408,26 +791,26 @@ class PdfGraphics { void _drawTemplate(PdfTemplate template, Offset location, Size size) { _beginMarkContent(); if (_layer != null && - _page._document != null && - _page._document._conformanceLevel != PdfConformanceLevel.none && - template.graphics._currentFont != null && - (template.graphics._currentFont is PdfStandardFont || - template.graphics._currentFont is PdfCjkStandardFont)) { + _page!._document != null && + _page!._document!._conformanceLevel != PdfConformanceLevel.none && + template.graphics!._currentFont != null && + (template.graphics!._currentFont is PdfStandardFont || + template.graphics!._currentFont is PdfCjkStandardFont)) { throw ArgumentError( - 'All the fonts must be embedded in ${_page._document._conformanceLevel.toString()} document.'); + 'All the fonts must be embedded in ${_page!._document!._conformanceLevel.toString()} document.'); } else if (_layer != null && - _page._document != null && - _page._document._conformanceLevel == PdfConformanceLevel.a1b && - template.graphics._currentFont != null && - template.graphics._currentFont is PdfTrueTypeFont) { - (template.graphics._currentFont as PdfTrueTypeFont) + _page!._document != null && + _page!._document!._conformanceLevel == PdfConformanceLevel.a1b && + template.graphics!._currentFont != null && + template.graphics!._currentFont is PdfTrueTypeFont) { + (template.graphics!._currentFont as PdfTrueTypeFont) ._fontInternal ._initializeCidSet(); } if ((_layer != null || _documentLayer != null) && template._isLoadedPageTemplate) { - _PdfCrossTable crossTable; - crossTable = _page._section._document._crossTable; + _PdfCrossTable? crossTable; + crossTable = _page!._section!._document!._crossTable; if ((template._isReadonly) || (template._isLoadedPageTemplate)) { template._cloneResources(crossTable); } @@ -443,27 +826,27 @@ class PdfGraphics { _page != null && template._isLoadedPageTemplate) { bool needTransformation = false; - if (_page._dictionary.containsKey(_DictionaryProperties.cropBox) && - _page._dictionary.containsKey(_DictionaryProperties.mediaBox)) { - _PdfArray cropBox; - _PdfArray mediaBox; - if (_page._dictionary[_DictionaryProperties.cropBox] + if (_page!._dictionary.containsKey(_DictionaryProperties.cropBox) && + _page!._dictionary.containsKey(_DictionaryProperties.mediaBox)) { + _PdfArray? cropBox; + _PdfArray? mediaBox; + if (_page!._dictionary[_DictionaryProperties.cropBox] is _PdfReferenceHolder) { - cropBox = (_page._dictionary[_DictionaryProperties.cropBox] + cropBox = (_page!._dictionary[_DictionaryProperties.cropBox] as _PdfReferenceHolder) - .object as _PdfArray; + .object as _PdfArray?; } else { cropBox = - _page._dictionary[_DictionaryProperties.cropBox] as _PdfArray; + _page!._dictionary[_DictionaryProperties.cropBox] as _PdfArray?; } - if (_page._dictionary[_DictionaryProperties.mediaBox] + if (_page!._dictionary[_DictionaryProperties.mediaBox] is _PdfReferenceHolder) { - mediaBox = (_page._dictionary[_DictionaryProperties.mediaBox] + mediaBox = (_page!._dictionary[_DictionaryProperties.mediaBox] as _PdfReferenceHolder) - .object as _PdfArray; + .object as _PdfArray?; } else { mediaBox = - _page._dictionary[_DictionaryProperties.mediaBox] as _PdfArray; + _page!._dictionary[_DictionaryProperties.mediaBox] as _PdfArray?; } if (cropBox != null && mediaBox != null) { if (cropBox.toRectangle() == mediaBox.toRectangle()) { @@ -471,15 +854,16 @@ class PdfGraphics { } } } - if (_page._dictionary.containsKey(_DictionaryProperties.mediaBox)) { - _PdfArray mBox; - if (_page._dictionary[_DictionaryProperties.mediaBox] + if (_page!._dictionary.containsKey(_DictionaryProperties.mediaBox)) { + _PdfArray? mBox; + if (_page!._dictionary[_DictionaryProperties.mediaBox] is _PdfReferenceHolder) { - mBox = (_page._dictionary[_DictionaryProperties.mediaBox] + mBox = (_page!._dictionary[_DictionaryProperties.mediaBox] as _PdfReferenceHolder) - .object as _PdfArray; + .object as _PdfArray?; } else { - mBox = _page._dictionary[_DictionaryProperties.mediaBox] as _PdfArray; + mBox = + _page!._dictionary[_DictionaryProperties.mediaBox] as _PdfArray?; } if (mBox != null) { if ((mBox[3] as _PdfNumber).value == 0) { @@ -487,10 +871,10 @@ class PdfGraphics { } } } - if ((_page._origin.dx >= 0 && _page._origin.dy >= 0) || + if ((_page!._origin.dx >= 0 && _page!._origin.dy >= 0) || needTransformation) { matrix._translate(location.dx, -(location.dy + size.height)); - } else if ((_page._origin.dx >= 0 && _page._origin.dy <= 0)) { + } else if ((_page!._origin.dx >= 0 && _page!._origin.dy <= 0)) { matrix._translate(location.dx, -(location.dy + size.height)); } else { matrix._translate(location.dx, -(location.dy + 0)); @@ -501,26 +885,29 @@ class PdfGraphics { if (hasScale) { matrix._scale(scaleX, scaleY); } - _streamWriter._modifyCurrentMatrix(matrix); - final _PdfResources resources = _getResources(); + _streamWriter!._modifyCurrentMatrix(matrix); + final _PdfResources resources = _getResources!(); final _PdfName name = resources._getName(template); - _streamWriter._executeObject(name); + _streamWriter!._executeObject(name); restore(state); _endMarkContent(); //Transfer automatic fields from template. - final PdfGraphics g = template.graphics; + final PdfGraphics? g = template.graphics; if (g != null) { - for (final _PdfAutomaticFieldInfo fieldInfo in g._autoFields._list) { - final _Point newLocation = _Point(fieldInfo.location.x + location.dx, - fieldInfo.location.y + location.dy); - final double scalingX = - template.size.width == 0 ? 0 : size.width / template.size.width; - final double scalingY = - template.size.height == 0 ? 0 : size.height / template.size.height; - _autoFields.add(_PdfAutomaticFieldInfo( - fieldInfo.field, newLocation, scalingX, scalingY)); - _page._dictionary.modify(); + for (final Object? fieldInfo in g._autoFields!._list) { + if (fieldInfo is _PdfAutomaticFieldInfo) { + final _Point newLocation = _Point(fieldInfo.location.x + location.dx, + fieldInfo.location.y + location.dy); + final double scalingX = + template.size.width == 0 ? 0 : size.width / template.size.width; + final double scalingY = template.size.height == 0 + ? 0 + : size.height / template.size.height; + _autoFields!.add(_PdfAutomaticFieldInfo( + fieldInfo.field, newLocation, scalingX, scalingY)); + _page!._dictionary.modify(); + } } } resources._requireProcset(_DictionaryProperties.grayScaleImage); @@ -530,10 +917,10 @@ class PdfGraphics { } void _layoutString(String s, PdfFont font, - {PdfPen pen, - PdfBrush brush, - _Rectangle layoutRectangle, - PdfStringFormat format}) { + {PdfPen? pen, + PdfBrush? brush, + required _Rectangle layoutRectangle, + PdfStringFormat? format}) { final _PdfStringLayouter layouter = _PdfStringLayouter(); _PdfStringLayoutResult result; result = layouter._layout(s, font, format, @@ -555,7 +942,7 @@ class PdfGraphics { _drawStringLayoutResult( result, font, pen, brush, layoutRectangle, format); _stringLayoutResult = result; - (_getResources() as _PdfResources) + (_getResources!() as _PdfResources) ._requireProcset(_DictionaryProperties.text); } } @@ -565,20 +952,20 @@ class PdfGraphics { _mediaBoxUpperRightBound == 0) { translateTransform(0, -size.height); } else { - translateTransform(0, -_mediaBoxUpperRightBound); + translateTransform(0, -_mediaBoxUpperRightBound!); } } void _initializeCoordinates() { - _streamWriter._writeComment('Change co-ordinate system to left/top.'); + _streamWriter!._writeComment('Change co-ordinate system to left/top.'); if (_mediaBoxUpperRightBound != -size.height) { if (_cropBox == null) { _translate(); } else { - final double cropX = (_cropBox[0] as _PdfNumber).value.toDouble(); - final double cropY = (_cropBox[1] as _PdfNumber).value.toDouble(); - final double cropW = (_cropBox[2] as _PdfNumber).value.toDouble(); - final double cropH = (_cropBox[3] as _PdfNumber).value.toDouble(); + final double cropX = (_cropBox![0] as _PdfNumber).value!.toDouble(); + final double cropY = (_cropBox![1] as _PdfNumber).value!.toDouble(); + final double cropW = (_cropBox![2] as _PdfNumber).value!.toDouble(); + final double cropH = (_cropBox![3] as _PdfNumber).value!.toDouble(); if (cropX > 0 || cropY > 0 || size.width == cropW || @@ -593,12 +980,12 @@ class PdfGraphics { void _clipTranslateMarginsWithBounds(_Rectangle clipBounds) { _clipBounds = clipBounds; - _streamWriter._writeComment('Clip margins.'); - _streamWriter._appendRectangle( + _streamWriter!._writeComment('Clip margins.'); + _streamWriter!._appendRectangle( clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height); - _streamWriter._closePath(); - _streamWriter._clipPath(false); - _streamWriter._writeComment('Translate co-ordinate system.'); + _streamWriter!._closePath(); + _streamWriter!._clipPath(false); + _streamWriter!._writeComment('Translate co-ordinate system.'); translateTransform(clipBounds.x, clipBounds.y); } @@ -607,17 +994,17 @@ class PdfGraphics { final _Rectangle clipArea = _Rectangle( left, top, size.width - left - right, size.height - top - bottom); _clipBounds = clipArea; - _streamWriter._writeComment('Clip margins.'); - _streamWriter._appendRectangle( + _streamWriter!._writeComment('Clip margins.'); + _streamWriter!._appendRectangle( _clipBounds.x, _clipBounds.y, _clipBounds.width, _clipBounds.height); - _streamWriter._closePath(); - _streamWriter._clipPath(false); - _streamWriter._writeComment('Translate co-ordinate system.'); + _streamWriter!._closePath(); + _streamWriter!._clipPath(false); + _streamWriter!._writeComment('Translate co-ordinate system.'); translateTransform(x, y); } - void _setLayer(PdfPageLayer pageLayer, [PdfLayer pdfLayer]) { - PdfPage page; + void _setLayer(PdfPageLayer? pageLayer, [PdfLayer? pdfLayer]) { + PdfPage? page; if (pageLayer != null) { _layer = pageLayer; page = pageLayer.page; @@ -628,10 +1015,11 @@ class PdfGraphics { if (page != null) { page._beginSave = () { if (_automaticFields != null) { - for (final _PdfAutomaticFieldInfo fieldInfo - in _automaticFields._list) { - fieldInfo.field._performDraw(this, fieldInfo.location, - fieldInfo.scalingX, fieldInfo.scalingY); + for (final Object? fieldInfo in _automaticFields!._list) { + if (fieldInfo is _PdfAutomaticFieldInfo) { + fieldInfo.field._performDraw(this, fieldInfo.location, + fieldInfo.scalingX, fieldInfo.scalingY); + } } } }; @@ -659,13 +1047,13 @@ class PdfGraphics { _previousWordSpacing = state._wordSpacing; _previousTextScaling = state._textScaling; _previousTextRenderingMode = state._textRenderingMode; - _streamWriter._restoreGraphicsState(); + _streamWriter!._restoreGraphicsState(); return state; } void _reset(Size size) { _canvasSize = size; - _streamWriter._clear(); + _streamWriter!._clear(); _initialize(); _initializeCoordinates(); } @@ -673,18 +1061,17 @@ class PdfGraphics { void _drawStringLayoutResult( _PdfStringLayoutResult result, PdfFont font, - PdfPen pen, - PdfBrush brush, + PdfPen? pen, + PdfBrush? brush, _Rectangle layoutRectangle, - PdfStringFormat format) { - ArgumentError.checkNotNull(result); - ArgumentError.checkNotNull(font); + PdfStringFormat? format) { if (!result._isEmpty) { _beginMarkContent(); _applyStringSettings(font, pen, brush, format, layoutRectangle); - final double textScaling = format != null ? format._scalingFactor : 100.0; + final double? textScaling = + format != null ? format._scalingFactor : 100.0; if (textScaling != _previousTextScaling) { - _streamWriter._setTextScaling(textScaling); + _streamWriter!._setTextScaling(textScaling); _previousTextScaling = textScaling; } double verticalAlignShift = _getTextVerticalAlignShift( @@ -693,11 +1080,11 @@ class PdfGraphics { matrix._translate( layoutRectangle.x, (-(layoutRectangle.y + font.height) - - (font._metrics._getDescent(format) > 0 - ? -font._metrics._getDescent(format) - : font._metrics._getDescent(format))) - + (font._metrics!._getDescent(format) > 0 + ? -font._metrics!._getDescent(format) + : font._metrics!._getDescent(format))) - verticalAlignShift); - _streamWriter._modifyTransformationMatrix(matrix); + _streamWriter!._modifyTransformationMatrix(matrix); if (layoutRectangle.height < font.size) { if ((result._size.height - layoutRectangle.height) < (font.size / 2) - 1) { @@ -706,22 +1093,22 @@ class PdfGraphics { } _drawLayoutResult(result, font, format, layoutRectangle); if (verticalAlignShift != 0) { - _streamWriter._startNextLine( - 0, -(verticalAlignShift - result._lineHeight)); + _streamWriter! + ._startNextLine(0, -(verticalAlignShift - result._lineHeight)); } - _streamWriter._endText(); + _streamWriter!._endText(); _underlineStrikeoutText( pen, brush, result, font, layoutRectangle, format); _endMarkContent(); } } - double _getLineIndent(_LineInfo lineInfo, PdfStringFormat format, + double? _getLineIndent(_LineInfo lineInfo, PdfStringFormat? format, _Rectangle layoutBounds, bool firstLine) { - double lineIndent = 0; + double? lineIndent = 0; final bool firstParagraphLine = (lineInfo._lineType & _PdfStringLayouter._getLineTypeValue( - _LineType.firstParagraphLine)) > + _LineType.firstParagraphLine)!) > 0; if (format != null && firstParagraphLine) { lineIndent = firstLine ? format._firstLineIndent : format.paragraphIndent; @@ -733,44 +1120,42 @@ class PdfGraphics { } void _drawLayoutResult(_PdfStringLayoutResult result, PdfFont font, - PdfStringFormat format, _Rectangle layoutRectangle) { - ArgumentError.checkNotNull(result); - ArgumentError.checkNotNull(font); - bool unicode = false; + PdfStringFormat? format, _Rectangle layoutRectangle) { + bool? unicode = false; if (font is PdfTrueTypeFont) { unicode = font._unicode; } - final List<_LineInfo> lines = result._lines; + final List<_LineInfo> lines = result._lines!; final double height = (format == null || format.lineSpacing == 0) ? font.height : format.lineSpacing + font.height; for (int i = 0; i < lines.length; i++) { final _LineInfo lineInfo = lines[i]; - final String line = lineInfo.text; - final double lineWidth = lineInfo.width; + final String? line = lineInfo.text; + final double? lineWidth = lineInfo.width; if (line == null || line.isEmpty) { final double verticalAlignShift = _getTextVerticalAlignShift( result._size.height, layoutRectangle.height, format); final _PdfTransformationMatrix matrix = _PdfTransformationMatrix(); double baseline = (-(layoutRectangle.y + font.height) - - font._metrics._getDescent(format)) - + font._metrics!._getDescent(format)) - verticalAlignShift; baseline -= height * (i + 1); matrix._translate(layoutRectangle.x, baseline); - _streamWriter._modifyTransformationMatrix(matrix); + _streamWriter!._modifyTransformationMatrix(matrix); } else { double horizontalAlignShift = _getHorizontalAlignShift(lineWidth, layoutRectangle.width, format); - final double lineIndent = + final double? lineIndent = _getLineIndent(lineInfo, format, layoutRectangle, i == 0); - horizontalAlignShift += (!_rightToLeft(format)) ? lineIndent : 0; + horizontalAlignShift += (!_rightToLeft(format)) ? lineIndent! : 0; if (horizontalAlignShift != 0) { - _streamWriter._startNextLine(horizontalAlignShift, 0); + _streamWriter!._startNextLine(horizontalAlignShift, 0); } if (font is PdfCjkStandardFont) { _drawCjkString(lineInfo, layoutRectangle, font, format); - } else if (unicode) { + } else if (unicode!) { _drawUnicodeLine(lineInfo, layoutRectangle, font, format); } else { _drawAsciiLine(lineInfo, layoutRectangle, font, format); @@ -781,38 +1166,37 @@ class PdfGraphics { result._size.height, layoutRectangle.height, format); final _PdfTransformationMatrix matrix = _PdfTransformationMatrix(); double baseline = (-(layoutRectangle.y + font.height) - - font._metrics._getDescent(format)) - + font._metrics!._getDescent(format)) - verticalAlignShift; baseline -= height * (i + 1); matrix._translate(layoutRectangle.x, baseline); - _streamWriter._modifyTransformationMatrix(matrix); + _streamWriter!._modifyTransformationMatrix(matrix); } } } - (_getResources() as _PdfResources) + (_getResources!() as _PdfResources) ._requireProcset(_DictionaryProperties.text); } void _drawAsciiLine(_LineInfo lineInfo, _Rectangle layoutRectangle, - PdfFont font, PdfStringFormat format) { + PdfFont font, PdfStringFormat? format) { _justifyLine(lineInfo, layoutRectangle.width, format); - final _PdfString str = _PdfString(lineInfo.text); + final _PdfString str = _PdfString(lineInfo.text!); str.isAsciiEncode = true; - _streamWriter._showNextLineText(str); + _streamWriter!._showNextLineText(str); } void _drawCjkString(_LineInfo lineInfo, _Rectangle layoutRectangle, - PdfFont font, PdfStringFormat format) { - ArgumentError.checkNotNull(font); + PdfFont font, PdfStringFormat? format) { _justifyLine(lineInfo, layoutRectangle.width, format); - final String line = lineInfo.text; + final String line = lineInfo.text!; final List str = _getCjkString(line); - _streamWriter._showNextLineText(str); + _streamWriter!._showNextLineText(str); } void _drawUnicodeLine(_LineInfo lineInfo, _Rectangle layoutRectangle, - PdfFont font, PdfStringFormat format) { - final String line = lineInfo.text; + PdfFont font, PdfStringFormat? format) { + final String? line = lineInfo.text; final bool useWordSpace = format != null && (format.wordSpacing != 0 || format.alignment == PdfTextAlignment.justify); @@ -821,7 +1205,7 @@ class PdfGraphics { _justifyLine(lineInfo, layoutRectangle.width, format); if (format != null && format.textDirection != PdfTextDirection.none) { final _ArabicShapeRenderer renderer = _ArabicShapeRenderer(); - final String txt = renderer.shape(line.split(''), 0); + final String txt = renderer.shape(line!.split(''), 0); final _Bidi bidi = _Bidi(); final String result = bidi.getLogicalToVisualString( txt, format.textDirection == PdfTextDirection.rightToLeft); @@ -842,23 +1226,20 @@ class PdfGraphics { } _drawUnicodeBlocks(blocks, words, ttfFont, format, wordSpacing); } else if (useWordSpace) { - final dynamic result = _breakUnicodeLine(line, ttfFont, null); + final dynamic result = _breakUnicodeLine(line!, ttfFont, null); final List blocks = result['tokens']; - final List words = result['words']; + final List words = result['words']!; _drawUnicodeBlocks(blocks, words, ttfFont, format, wordSpacing); } else { - final String token = _convertToUnicode(line, ttfFont); + final String token = _convertToUnicode(line!, ttfFont)!; final _PdfString value = _getUnicodeString(token); - _streamWriter._showNextLineText(value); + _streamWriter!._showNextLineText(value); } } void _drawUnicodeBlocks(List blocks, List words, - PdfTrueTypeFont font, PdfStringFormat format, double wordSpacing) { - ArgumentError.checkNotNull(blocks); - ArgumentError.checkNotNull(words); - ArgumentError.checkNotNull(font); - _streamWriter._startNextLine(); + PdfTrueTypeFont font, PdfStringFormat? format, double wordSpacing) { + _streamWriter!._startNextLine(); double x = 0; double xShift = 0; double firstLineIndent = 0; @@ -881,13 +1262,13 @@ class PdfGraphics { final String word = words[i]; double tokenWidth = 0; if (x != 0.0) { - _streamWriter._startNextLine(x, 0); + _streamWriter!._startNextLine(x, 0); } if (word.isNotEmpty) { tokenWidth += font.measureString(word, format: format).width; tokenWidth += characterSpacing; final _PdfString val = _getUnicodeString(token); - _streamWriter._showText(val); + _streamWriter!._showText(val); } if (i != blocks.length - 1) { x = tokenWidth + spaceWidth; @@ -896,7 +1277,7 @@ class PdfGraphics { } // Rolback current line position. if (xShift > 0) { - _streamWriter._startNextLine(-xShift, 0); + _streamWriter!._startNextLine(-xShift, 0); } } finally { if (format != null) { @@ -907,29 +1288,27 @@ class PdfGraphics { } dynamic _breakUnicodeLine( - String line, PdfTrueTypeFont ttfFont, List words) { - ArgumentError.checkNotNull(line); + String line, PdfTrueTypeFont ttfFont, List? words) { words = line.split(' '); final List tokens = []; for (int i = 0; i < words.length; i++) { final String word = words[i]; - final String token = _convertToUnicode(word, ttfFont); - tokens.add(token); + final String? token = _convertToUnicode(word, ttfFont); + if (token != null) { + tokens.add(token); + } } return {'tokens': tokens, 'words': words}; } _PdfString _getUnicodeString(String token) { - ArgumentError.checkNotNull(token); final _PdfString val = _PdfString(token); val.isAsciiEncode = true; return val; } - String _convertToUnicode(String text, PdfTrueTypeFont font) { - String token; - ArgumentError.checkNotNull(text); - ArgumentError.checkNotNull(font); + String? _convertToUnicode(String text, PdfTrueTypeFont font) { + String? token; if (font._fontInternal is _UnicodeTrueTypeFont) { final _TtfReader ttfReader = font._fontInternal._reader; font._setSymbols(text); @@ -941,16 +1320,15 @@ class PdfGraphics { } List _getCjkString(String line) { - ArgumentError.checkNotNull(line); List value = _PdfString.toUnicodeArray(line); value = _PdfString._escapeSymbols(value); return value; } double _justifyLine( - _LineInfo lineInfo, double boundsWidth, PdfStringFormat format) { - final String line = lineInfo.text; - double lineWidth = lineInfo.width; + _LineInfo lineInfo, double boundsWidth, PdfStringFormat? format) { + final String line = lineInfo.text!; + double? lineWidth = lineInfo.width; final bool shouldJustify = _shouldJustify(lineInfo, boundsWidth, format); final bool hasWordSpacing = format != null && format.wordSpacing != 0; final int whitespacesCount = @@ -958,37 +1336,37 @@ class PdfGraphics { double wordSpace = 0; if (shouldJustify) { if (hasWordSpacing) { - lineWidth -= whitespacesCount * format.wordSpacing; + lineWidth = lineWidth! - (whitespacesCount * format.wordSpacing); } - final double difference = boundsWidth - lineWidth; + final double difference = boundsWidth - lineWidth!; wordSpace = difference / whitespacesCount; - _streamWriter._setWordSpacing(wordSpace); + _streamWriter!._setWordSpacing(wordSpace); } else if (format != null && format.alignment == PdfTextAlignment.justify) { - _streamWriter._setWordSpacing(0); + _streamWriter!._setWordSpacing(0); } return wordSpace; } bool _shouldJustify( - _LineInfo lineInfo, double boundsWidth, PdfStringFormat format) { - final String line = lineInfo.text; - final double lineWidth = lineInfo.width; + _LineInfo lineInfo, double boundsWidth, PdfStringFormat? format) { + final String line = lineInfo.text!; + final double? lineWidth = lineInfo.width; final bool justifyStyle = format != null && format.alignment == PdfTextAlignment.justify; - final bool goodWidth = boundsWidth >= 0 && lineWidth < boundsWidth; + final bool goodWidth = boundsWidth >= 0 && lineWidth! < boundsWidth; final int whitespacesCount = _StringTokenizer._getCharacterCount(line, _StringTokenizer._spaces); final bool hasSpaces = whitespacesCount > 0 && line[0] != _StringTokenizer._whiteSpace; final bool goodLineBreakStyle = (lineInfo._lineType & - _PdfStringLayouter._getLineTypeValue(_LineType.layoutBreak)) > + _PdfStringLayouter._getLineTypeValue(_LineType.layoutBreak)!) > 0; final bool shouldJustify = justifyStyle && goodWidth && hasSpaces && goodLineBreakStyle; return shouldJustify; } - bool _rightToLeft(PdfStringFormat format) { + bool _rightToLeft(PdfStringFormat? format) { bool rtl = format != null && format.textDirection == PdfTextDirection.rightToLeft; if (format != null && format.textDirection != PdfTextDirection.none) { @@ -998,18 +1376,18 @@ class PdfGraphics { } double _getTextVerticalAlignShift( - double textHeight, double boundsHeight, PdfStringFormat format) { + double? textHeight, double boundsHeight, PdfStringFormat? format) { double shift = 0; if (boundsHeight >= 0 && format != null && format.lineAlignment != PdfVerticalAlignment.top) { switch (format.lineAlignment) { case PdfVerticalAlignment.middle: - shift = (boundsHeight - textHeight) / 2; + shift = (boundsHeight - textHeight!) / 2; break; case PdfVerticalAlignment.bottom: - shift = boundsHeight - textHeight; + shift = boundsHeight - textHeight!; break; default: break; @@ -1019,17 +1397,17 @@ class PdfGraphics { } double _getHorizontalAlignShift( - double lineWidth, double boundsWidth, PdfStringFormat format) { + double? lineWidth, double boundsWidth, PdfStringFormat? format) { double shift = 0; if (boundsWidth >= 0 && format != null && format.alignment != PdfTextAlignment.left) { switch (format.alignment) { case PdfTextAlignment.center: - shift = (boundsWidth - lineWidth) / 2; + shift = (boundsWidth - lineWidth!) / 2; break; case PdfTextAlignment.right: - shift = boundsWidth - lineWidth; + shift = boundsWidth - lineWidth!; break; default: break; @@ -1038,37 +1416,37 @@ class PdfGraphics { return shift; } - void _applyStringSettings(PdfFont font, PdfPen pen, PdfBrush brush, - PdfStringFormat format, _Rectangle bounds) { + void _applyStringSettings(PdfFont font, PdfPen? pen, PdfBrush? brush, + PdfStringFormat? format, _Rectangle bounds) { final int renderingMode = _getTextRenderingMode(pen, brush, format); - _streamWriter._writeOperator(_Operators.beginText); + _streamWriter!._writeOperator(_Operators.beginText); _stateControl(pen, brush, font, format); if (renderingMode != _previousTextRenderingMode) { - _streamWriter._setTextRenderingMode(renderingMode); + _streamWriter!._setTextRenderingMode(renderingMode); _previousTextRenderingMode = renderingMode; } - final double characterSpace = + final double? characterSpace = (format != null) ? format.characterSpacing : 0; if (characterSpace != _previousCharacterSpacing) { - _streamWriter._setCharacterSpacing(characterSpace); + _streamWriter!._setCharacterSpacing(characterSpace); _previousCharacterSpacing = characterSpace; } - final double wordSpace = (format != null) ? format.wordSpacing : 0; + final double? wordSpace = (format != null) ? format.wordSpacing : 0; if (wordSpace != _previousWordSpacing) { - _streamWriter._setWordSpacing(wordSpace); + _streamWriter!._setWordSpacing(wordSpace); _previousWordSpacing = wordSpace; } } void _stateControl( - PdfPen pen, PdfBrush brush, PdfFont font, PdfStringFormat format) { + PdfPen? pen, PdfBrush? brush, PdfFont? font, PdfStringFormat? format) { if (brush != null) { if (_layer != null) { - if (!_page._isLoadedPage && - !_page._section._pdfDocument._isLoadedDocument) { + if (!_page!._isLoadedPage && + !_page!._section!._pdfDocument!._isLoadedDocument) { if (_colorSpaceChanged == false) { if (_page != null) { - colorSpace = _page._document.colorSpace; + colorSpace = _page!._document!.colorSpace; } _colorSpaceChanged = true; } @@ -1077,9 +1455,9 @@ class PdfGraphics { _initCurrentColorSpace(colorSpace); } else if (pen != null) { if (_layer != null) { - if (!_page._isLoadedPage && - !_page._section._pdfDocument._isLoadedDocument) { - colorSpace = _page._document.colorSpace; + if (!_page!._isLoadedPage && + !_page!._section!._pdfDocument!._isLoadedDocument) { + colorSpace = _page!._document!.colorSpace; } } _initCurrentColorSpace(colorSpace); @@ -1089,27 +1467,27 @@ class PdfGraphics { _fontControl(font, format, false); } - void _initCurrentColorSpace(PdfColorSpace colorspace) { + void _initCurrentColorSpace(PdfColorSpace? colorspace) { if (!_isColorSpaceInitialized) { - _streamWriter._setColorSpace( - _PdfName('Device' + _colorSpaces[colorSpace]), true); - _streamWriter._setColorSpace( - _PdfName('Device' + _colorSpaces[colorSpace]), false); + _streamWriter! + ._setColorSpace(_PdfName('Device' + _colorSpaces[colorSpace]!), true); + _streamWriter!._setColorSpace( + _PdfName('Device' + _colorSpaces[colorSpace]!), false); _isColorSpaceInitialized = true; } } - void _penControl(PdfPen pen, bool saveState) { + void _penControl(PdfPen? pen, bool saveState) { if (pen != null) { _currentPen = pen; colorSpace = PdfColorSpace.rgb; - pen._monitorChanges(_currentPen, _streamWriter, _getResources, saveState, + pen._monitorChanges(_currentPen, _streamWriter!, _getResources, saveState, colorSpace, _matrix); _currentPen = pen; } } - void _brushControl(PdfBrush brush, bool saveState) { + void _brushControl(PdfBrush? brush, bool saveState) { if (brush != null) { brush._monitorChanges( _currentBrush, _streamWriter, _getResources, saveState, colorSpace); @@ -1118,37 +1496,37 @@ class PdfGraphics { } } - void _fontControl(PdfFont font, PdfStringFormat format, bool saveState) { + void _fontControl(PdfFont? font, PdfStringFormat? format, bool saveState) { if (font != null) { if ((font is PdfStandardFont || font is PdfCjkStandardFont) && _layer != null && - _page._document != null && - _page._document._conformanceLevel != PdfConformanceLevel.none) { + _page!._document != null && + _page!._document!._conformanceLevel != PdfConformanceLevel.none) { throw ArgumentError( - 'All the fonts must be embedded in ${_page._document._conformanceLevel.toString()} document.'); + 'All the fonts must be embedded in ${_page!._document!._conformanceLevel.toString()} document.'); } else if (font is PdfTrueTypeFont && _layer != null && - _page._document != null && - _page._document._conformanceLevel == PdfConformanceLevel.a1b) { + _page!._document != null && + _page!._document!._conformanceLevel == PdfConformanceLevel.a1b) { font._fontInternal._initializeCidSet(); } - final PdfSubSuperscript current = + final PdfSubSuperscript? current = format != null ? format.subSuperscript : PdfSubSuperscript.none; - final PdfSubSuperscript privious = _currentStringFormat != null - ? _currentStringFormat.subSuperscript + final PdfSubSuperscript? privious = _currentStringFormat != null + ? _currentStringFormat!.subSuperscript : PdfSubSuperscript.none; if (saveState || font != _currentFont || current != privious) { - final _PdfResources resources = _getResources(); + final _PdfResources resources = _getResources!(); _currentFont = font; _currentStringFormat = format; - _streamWriter._setFont( - font, resources._getName(font), font._metrics._getSize(format)); + _streamWriter!._setFont( + font, resources._getName(font), font._metrics!._getSize(format)!); } } } int _getTextRenderingMode( - PdfPen pen, PdfBrush brush, PdfStringFormat format) { + PdfPen? pen, PdfBrush? brush, PdfStringFormat? format) { int tm = _TextRenderingMode.none; if (pen != null && brush != null) { tm = _TextRenderingMode.fillStroke; @@ -1164,9 +1542,9 @@ class PdfGraphics { } _Rectangle _checkCorrectLayoutRectangle( - _Size textSize, double x, double y, PdfStringFormat format) { + _Size textSize, double? x, double? y, PdfStringFormat? format) { final _Rectangle layoutedRectangle = - _Rectangle(x, y, textSize.width, textSize.width); + _Rectangle(x!, y!, textSize.width, textSize.width); if (format != null) { switch (format.alignment) { case PdfTextAlignment.center: @@ -1193,40 +1571,40 @@ class PdfGraphics { } void _underlineStrikeoutText( - PdfPen pen, - PdfBrush brush, + PdfPen? pen, + PdfBrush? brush, _PdfStringLayoutResult result, PdfFont font, _Rectangle layoutRectangle, - PdfStringFormat format) { + PdfStringFormat? format) { if (font._isUnderline | font._isStrikeout) { - final PdfPen linePen = + final PdfPen? linePen = _createUnderlineStikeoutPen(pen, brush, font, format); if (linePen != null) { final double verticalShift = _getTextVerticalAlignShift( result._size.height, layoutRectangle.height, format); double underlineYOffset = layoutRectangle.y + verticalShift + - font._metrics._getAscent(format) + + font._metrics!._getAscent(format) + 1.5 * linePen.width; double strikeoutYOffset = layoutRectangle.y + verticalShift + - font._metrics._getHeight(format) / 2 + + font._metrics!._getHeight(format) / 2 + 1.5 * linePen.width; - final List<_LineInfo> lines = result._lines; - for (int i = 0; i < result._lines.length; i++) { - final _LineInfo lineInfo = lines[i]; - final double lineWidth = lineInfo.width; + final List<_LineInfo>? lines = result._lines; + for (int i = 0; i < result._lines!.length; i++) { + final _LineInfo lineInfo = lines![i]; + final double? lineWidth = lineInfo.width; double horizontalShift = _getHorizontalAlignShift( lineWidth, layoutRectangle.width, format); - final double lineIndent = + final double? lineIndent = _getLineIndent(lineInfo, format, layoutRectangle, i == 0); - horizontalShift += (!_rightToLeft(format)) ? lineIndent : 0; + horizontalShift += (!_rightToLeft(format)) ? lineIndent! : 0; final double x1 = layoutRectangle.x + horizontalShift; final double x2 = (!_shouldJustify(lineInfo, layoutRectangle.width, format)) - ? x1 + lineWidth - lineIndent - : x1 + layoutRectangle.width - lineIndent; + ? x1 + lineWidth! - lineIndent! + : x1 + layoutRectangle.width - lineIndent!; if (font._isUnderline) { drawLine(linePen, Offset(x1, underlineYOffset), Offset(x2, underlineYOffset)); @@ -1242,10 +1620,10 @@ class PdfGraphics { } } - PdfPen _createUnderlineStikeoutPen( - PdfPen pen, PdfBrush brush, PdfFont font, PdfStringFormat format) { - final double lineWidth = font._metrics._getSize(format) / 20; - PdfPen linePen; + PdfPen? _createUnderlineStikeoutPen( + PdfPen? pen, PdfBrush? brush, PdfFont font, PdfStringFormat? format) { + final double lineWidth = font._metrics!._getSize(format)! / 20; + PdfPen? linePen; if (pen != null) { linePen = PdfPen(pen.color, width: lineWidth); } else if (brush != null) { @@ -1255,36 +1633,32 @@ class PdfGraphics { } Rect _getLineBounds(int lineIndex, _PdfStringLayoutResult result, - PdfFont font, _Rectangle layoutRectangle, PdfStringFormat format) { - ArgumentError.checkNotNull(result); - ArgumentError.checkNotNull(font); + PdfFont font, _Rectangle layoutRectangle, PdfStringFormat? format) { _Rectangle bounds = _Rectangle.empty; if (!result._isEmpty && lineIndex < result._lineCount && lineIndex >= 0) { - final _LineInfo line = result._lines[lineIndex]; + final _LineInfo line = result._lines![lineIndex]; final double verticalShift = _getTextVerticalAlignShift( result._size.height, layoutRectangle.height, format); final double y = verticalShift + layoutRectangle.y + (result._lineHeight * lineIndex); - final double lineWidth = line.width; + final double? lineWidth = line.width; double horizontalShift = _getHorizontalAlignShift(lineWidth, layoutRectangle.width, format); - final double lineIndent = + final double? lineIndent = _getLineIndent(line, format, layoutRectangle, lineIndex == 0); - horizontalShift += (!_rightToLeft(format)) ? lineIndent : 0; + horizontalShift += (!_rightToLeft(format)) ? lineIndent! : 0; final double x = layoutRectangle.x + horizontalShift; final double width = (!_shouldJustify(line, layoutRectangle.width, format)) - ? lineWidth - lineIndent - : layoutRectangle.width - lineIndent; - final double height = result._lineHeight; - bounds = _Rectangle(x, y, width, height); + ? lineWidth! - lineIndent! + : layoutRectangle.width - lineIndent!; + final double? height = result._lineHeight; + bounds = _Rectangle(x, y, width, height!); } return bounds.rect; } - String _addChars(PdfTrueTypeFont font, String line, PdfStringFormat format) { - ArgumentError.checkNotNull(font, 'font'); - ArgumentError.checkNotNull(line, 'line'); + String _addChars(PdfTrueTypeFont font, String line, PdfStringFormat? format) { String text = line; final _UnicodeTrueTypeFont internalFont = font._fontInternal; final _TtfReader ttfReader = internalFont._reader; @@ -1300,125 +1674,33 @@ class PdfGraphics { return text; } - /// Sets the clipping region of this Graphics to the result of the - /// specified operation combining the current clip region and the - /// rectangle specified by a RectangleF structure. - void setClip({Rect bounds, PdfFillMode mode}) { - if (bounds != null) { - mode ??= PdfFillMode.winding; - _streamWriter._appendRectangle( - bounds.left, bounds.top, bounds.width, bounds.height); - _streamWriter._clipPath(mode == PdfFillMode.alternate); - } - } - - /// Draws a Bezier spline defined by four [Offset] structures. - void drawBezier(Offset startPoint, Offset firstControlPoint, - Offset secondControlPoint, Offset endPoint, - {PdfPen pen}) { - _beginMarkContent(); - _stateControl(pen, null, null, null); - final _PdfStreamWriter sw = _streamWriter; - sw._beginPath(startPoint.dx, startPoint.dy); - sw._appendBezierSegment(firstControlPoint.dx, firstControlPoint.dy, - secondControlPoint.dx, secondControlPoint.dy, endPoint.dx, endPoint.dy); - sw._strokePath(); - _endMarkContent(); - } - - /// Draws a GraphicsPath defined by a pen, a brush and path - void drawPath(PdfPath path, {PdfPen pen, PdfBrush brush}) { - _beginMarkContent(); - _stateControl(pen, brush, null, null); - _buildUpPath(path._points, path._pathTypes); - _drawPath(pen, brush, path._fillMode, false); - _endMarkContent(); - } - - /// Draws a pie shape defined by an ellipse specified by a Rect structure - /// uiand two radial lines. - void drawPie(Rect bounds, double startAngle, double sweepAngle, - {PdfPen pen, PdfBrush brush}) { - if (sweepAngle != 0) { - _beginMarkContent(); - _stateControl(pen, brush, null, null); - _constructArcPath(bounds.left, bounds.top, bounds.left + bounds.width, - bounds.top + bounds.height, startAngle, sweepAngle); - _streamWriter._appendLineSegment( - bounds.left + bounds.width / 2, bounds.top + bounds.height / 2); - _drawPath(pen, brush, PdfFillMode.winding, true); - _endMarkContent(); - } - } - - /// Draws an ellipse specified by a bounding Rect structure. - void drawEllipse(Rect bounds, {PdfPen pen, PdfBrush brush}) { - _beginMarkContent(); - _stateControl(pen, brush, null, null); - _constructArcPath( - bounds.left, bounds.top, bounds.right, bounds.bottom, 0, 360); - _drawPath(pen, brush, PdfFillMode.winding, true); - _endMarkContent(); - } - - /// Draws an arc representing a portion of an ellipse specified - /// by a Rect structure. - void drawArc(Rect bounds, double startAngle, double sweepAngle, - {PdfPen pen}) { - if (sweepAngle != 0) { - _beginMarkContent(); - _stateControl(pen, null, null, null); - _constructArcPath(bounds.left, bounds.top, bounds.left + bounds.width, - bounds.top + bounds.height, startAngle, sweepAngle); - _drawPath(pen, null, PdfFillMode.winding, false); - _endMarkContent(); - } - } - - /// Draws a polygon defined by a brush, an array of [Offset] structures. - void drawPolygon(List points, {PdfPen pen, PdfBrush brush}) { - _beginMarkContent(); - if (points.isEmpty) { - return; - } - _stateControl(pen, brush, null, null); - _streamWriter._beginPath(points.elementAt(0).dx, points.elementAt(0).dy); - - for (int i = 1; i < points.length; ++i) { - _streamWriter._appendLineSegment( - points.elementAt(i).dx, points.elementAt(i).dy); - } - _drawPath(pen, brush, PdfFillMode.winding, true); - _endMarkContent(); - } - void _buildUpPath(List points, List<_PathPointType> types) { for (int i = 0; i < points.length; ++i) { final dynamic typeValue = types[i]; final Offset point = points[i]; switch (typeValue as _PathPointType) { case _PathPointType.start: - _streamWriter._beginPath(point.dx, point.dy); + _streamWriter!._beginPath(point.dx, point.dy); break; case _PathPointType.bezier3: - Offset p2, p3; + Offset? p2, p3; final Map returnValue = _getBezierPoints(points, types, i, p2, p3); i = returnValue['i']; final List p = returnValue['points']; p2 = p.first; p3 = p.last; - _streamWriter._appendBezierSegment( + _streamWriter!._appendBezierSegment( point.dx, point.dy, p2.dx, p2.dy, p3.dx, p3.dy); break; case _PathPointType.line: - _streamWriter._appendLineSegment(point.dx, point.dy); + _streamWriter!._appendLineSegment(point.dx, point.dy); break; case _PathPointType.closeSubpath: - _streamWriter._closePath(); + _streamWriter!._closePath(); break; default: @@ -1428,7 +1710,7 @@ class PdfGraphics { } Map _getBezierPoints(List points, - List<_PathPointType> types, int i, Offset p2, Offset p3) { + List<_PathPointType> types, int i, Offset? p2, Offset? p3) { const String errorMsg = 'Malforming path.'; ++i; if (types[i] == _PathPointType.bezier3) { @@ -1456,11 +1738,11 @@ class PdfGraphics { return; } List pt = points[0]; - _streamWriter._beginPath(pt[0], pt[1]); + _streamWriter!._beginPath(pt[0], pt[1]); for (int i = 0; i < points.length; ++i) { pt = points.elementAt(i); - _streamWriter._appendBezierSegment( - pt[2], pt[3], pt[4], pt[5], pt[6], pt[7]); + _streamWriter! + ._appendBezierSegment(pt[2], pt[3], pt[4], pt[5], pt[6], pt[7]); } } @@ -1534,20 +1816,20 @@ class PdfGraphics { void _beginMarkContent() { if (_documentLayer != null) { - _documentLayer._beginLayer(this); + _documentLayer!._beginLayer(this); } } void _endMarkContent() { if (_documentLayer != null) { - if (_documentLayer._isEndState && - _documentLayer._parentLayer.isNotEmpty) { - for (int i = 0; i < _documentLayer._parentLayer.length; i++) { - _streamWriter._write('EMC\n'); + if (_documentLayer!._isEndState && + _documentLayer!._parentLayer.isNotEmpty) { + for (int i = 0; i < _documentLayer!._parentLayer.length; i++) { + _streamWriter!._write('EMC\n'); } } - if (_documentLayer._isEndState) { - _streamWriter._write('EMC\n'); + if (_documentLayer!._isEndState) { + _streamWriter!._write('EMC\n'); } } } @@ -1557,27 +1839,14 @@ class _TransparencyData { //Constructor _TransparencyData( double alphaPen, double alphaBrush, PdfBlendMode blendMode) { - if (alphaPen == null) { - ArgumentError.value(alphaPen, 'alphaPen', 'alpha value cannot be null'); - } else { - this.alphaPen = alphaPen; - } - if (alphaBrush == null) { - ArgumentError.value( - alphaBrush, 'alphaBrush', 'alpha value cannot be null'); - } else { - this.alphaBrush = alphaBrush; - } - if (blendMode == null) { - ArgumentError.value(blendMode, 'blendMode', 'blend mode cannot be null'); - } else { - this.blendMode = blendMode; - } + this.alphaPen = alphaPen; + this.alphaBrush = alphaBrush; + this.blendMode = blendMode; } //Fields - double alphaPen; - double alphaBrush; - PdfBlendMode blendMode; + double? alphaPen; + double? alphaBrush; + PdfBlendMode? blendMode; @override bool operator ==(Object other) { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_graphics_state.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_graphics_state.dart index 1ada283a6..353b0eadf 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_graphics_state.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_graphics_state.dart @@ -6,26 +6,22 @@ class PdfGraphicsState { // Constructors /// Initializes a new instance of the [PdfGraphicsState] class. PdfGraphicsState._(PdfGraphics graphics, _PdfTransformationMatrix matrix) { - if (graphics != null && matrix != null) { - _graphics = graphics; - _matrix = matrix; - } + _graphics = graphics; + _matrix = matrix; _initialize(); } //Fields - PdfGraphics _graphics; - _PdfTransformationMatrix _matrix; - double _characterSpacing; - double _wordSpacing; - double _textScaling; - PdfPen _pen; - PdfBrush _brush; - PdfFont _font; - PdfColorSpace _colorSpace; - - /// Gets or sets the text rendering mode. - int _textRenderingMode; + late PdfGraphics _graphics; + late _PdfTransformationMatrix _matrix; + late double _characterSpacing; + late double _wordSpacing; + late double _textScaling; + PdfPen? _pen; + PdfBrush? _brush; + PdfFont? _font; + late PdfColorSpace _colorSpace; + late int _textRenderingMode; //Implementation void _initialize() { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_margins.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_margins.dart index d75a10229..33064e859 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_margins.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_margins.dart @@ -1,29 +1,164 @@ part of pdf; /// A class representing PDF page margins. +/// +/// ```dart +/// //Creates a new PDF document. +/// PdfDocument document = PdfDocument() +/// //Create and set new PDF margin. +/// ..pageSettings.margins = (PdfMargins()..all = 20) +/// ..pages.add().graphics.drawString( +/// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); +/// //Saves the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfMargins { //Constructor /// Initializes a new instance of the [PdfMargins] class. - PdfMargins() { - _setMargins(0); - } + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument document = PdfDocument() + /// //Create and set new PDF margin. + /// ..pageSettings.margins = (PdfMargins()..all = 20) + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); + /// //Saves the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfMargins(); //Fields + double _left = 0; + double _top = 0; + double _right = 0; + double _bottom = 0; + bool _isPageAdded = false; + + //Properties /// Gets or sets the left margin size. - double left; + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument document = PdfDocument() + /// //Create and set new PDF margin. + /// ..pageSettings.margins = (PdfMargins() + /// ..left = 20 + /// ..right = 40 + /// ..top = 100 + /// ..bottom = 100) + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); + /// //Saves the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + double get left => _left; + set left(double value) { + if (_left != value && !_isPageAdded) { + _left = value; + } + } /// Gets or sets the top margin size. - double top; + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument document = PdfDocument() + /// //Create and set new PDF margin. + /// ..pageSettings.margins = (PdfMargins() + /// ..left = 20 + /// ..right = 40 + /// ..top = 100 + /// ..bottom = 100) + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); + /// //Saves the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + double get top => _top; + set top(double value) { + if (_top != value && !_isPageAdded) { + _top = value; + } + } /// Gets or sets the right margin size. - double right; + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument document = PdfDocument() + /// //Create and set new PDF margin. + /// ..pageSettings.margins = (PdfMargins() + /// ..left = 20 + /// ..right = 40 + /// ..top = 100 + /// ..bottom = 100) + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); + /// //Saves the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + double get right => _right; + set right(double value) { + if (_right != value && !_isPageAdded) { + _right = value; + } + } /// Gets or sets the bottom margin size. - double bottom; + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument document = PdfDocument() + /// //Create and set new PDF margin. + /// ..pageSettings.margins = (PdfMargins() + /// ..left = 20 + /// ..right = 40 + /// ..top = 100 + /// ..bottom = 100) + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); + /// //Saves the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + double get bottom => _bottom; + set bottom(double value) { + if (_bottom != value && !_isPageAdded) { + _bottom = value; + } + } - //Properties /// Sets the margins of all side. - set all(double value) => _setMargins(value); + /// + /// ```dart + /// //Creates a new PDF document. + /// PdfDocument document = PdfDocument() + /// //Create and set new PDF margin. + /// ..pageSettings.margins = (PdfMargins()..all = 20) + /// ..pages.add().graphics.drawString( + /// 'Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12)); + /// //Saves the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + set all(double value) { + if (!_isPageAdded) { + _setMargins(value); + } + } //Implementation void _setMargins(double margin) { @@ -42,6 +177,13 @@ class PdfMargins { bottom = b; } + bool _equals(PdfMargins margins) { + return _left == margins.left && + _top == margins._top && + _right == margins._right && + _bottom == margins._bottom; + } + PdfMargins _clone() { final PdfMargins result = PdfMargins(); result.left = left.toDouble(); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_pen.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_pen.dart index c64b1b32e..49a646c48 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_pen.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_pen.dart @@ -2,33 +2,41 @@ part of pdf; /// A class defining settings for drawing operations, /// that determines the color, width, and style of the drawing elements. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument doc = PdfDocument() +/// ..pages.add().graphics.drawRectangle( +/// //Create a new PDF pen instance. +/// pen: PdfPen(PdfColor(255, 0, 0)), +/// bounds: Rect.fromLTWH(0, 0, 200, 100)); +/// //Save the document. +/// List bytes = doc.save(); +/// //Close the document. +/// doc.dispose(); +/// ``` class PdfPen { //Constructor /// Initializes a new instance of the [PdfPen] class. /// /// ```dart /// //Create a new PDF document. - /// PdfDocument doc = PdfDocument(); - /// //Create a new PDF pen instance. - /// PdfPen pen = PdfPen(PdfColor(255, 0, 0)); - /// //Draw rectangle with the pen. - /// doc.pages - /// .add() - /// .graphics - /// .drawRectangle(pen: pen, bounds: Rect(0, 0, 200, 100)); + /// PdfDocument doc = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// //Create a new PDF pen instance. + /// pen: PdfPen(PdfColor(255, 0, 0)), + /// bounds: Rect.fromLTWH(0, 0, 200, 100)); /// //Save the document. /// List bytes = doc.save(); /// //Close the document. /// doc.dispose(); /// ``` PdfPen(PdfColor pdfColor, - {double width, - PdfDashStyle dashStyle, - PdfLineCap lineCap, - PdfLineJoin lineJoin}) { - if (pdfColor != null) { - _color = pdfColor; - } + {double width = 1.0, + PdfDashStyle dashStyle = PdfDashStyle.solid, + PdfLineCap lineCap = PdfLineCap.flat, + PdfLineJoin lineJoin = PdfLineJoin.miter}) { + _color = pdfColor; _initialize(width, dashStyle, lineCap, lineJoin); } @@ -36,54 +44,46 @@ class PdfPen { /// /// ```dart /// //Create a new PDF document. - /// PdfDocument doc = PdfDocument(); - /// //Create a new PDF pen instance using brush. - /// PdfPen pen = PdfPen(pdfBrush); - /// //Draw rectangle with the pen. - /// doc.pages - /// .add() - /// .graphics - /// .drawRectangle(pen: pen, bounds: Rect(0, 0, 200, 100)); + /// PdfDocument doc = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// //Create a new PDF pen instance. + /// pen: PdfPen.fromBrush(PdfBrushes.red), + /// bounds: Rect.fromLTWH(0, 0, 200, 100)); /// //Save the document. /// List bytes = doc.save(); /// //Close the document. /// doc.dispose(); /// ``` PdfPen.fromBrush(PdfBrush brush, - {double width, - PdfDashStyle dashStyle, - PdfLineCap lineCap, - PdfLineJoin lineJoin}) { - ArgumentError.checkNotNull(brush, 'brush'); + {double width = 1.0, + PdfDashStyle dashStyle = PdfDashStyle.solid, + PdfLineCap lineCap = PdfLineCap.flat, + PdfLineJoin lineJoin = PdfLineJoin.miter}) { _setBrush(brush); - if (width != null) { - _width = width; - } + _width = width; _initialize(width, dashStyle, lineCap, lineJoin); } PdfPen._immutable(PdfColor pdfColor) { - if (pdfColor != null) { - _color = pdfColor; - } + _color = pdfColor; _immutable = true; - _initialize(); + _initialize(1.0, PdfDashStyle.solid, PdfLineCap.flat, PdfLineJoin.miter); } //Fields bool _immutable = false; //ignore:unused_field - PdfColorSpace _colorSpace; - PdfDashStyle _dashStyle; - PdfColor _color; - PdfBrush _brush; - double _dashOffset; - List _dashPattern; - PdfLineCap _lineCap; - PdfLineJoin _lineJoin; + late PdfColorSpace _colorSpace; + PdfDashStyle _dashStyle = PdfDashStyle.solid; + PdfColor _color = PdfColor(0, 0, 0); + PdfBrush? _brush; + late double _dashOffset; + late List _dashPattern; + PdfLineCap _lineCap = PdfLineCap.flat; + PdfLineJoin _lineJoin = PdfLineJoin.miter; double _width = 1.0; - double _miterLimit; - bool _isSkipPatternWidth; + late double _miterLimit; + bool? _isSkipPatternWidth; //Properties @override @@ -94,93 +94,194 @@ class PdfPen { @override int get hashCode => width.hashCode; - /// Gets the color of the pen. + /// Gets or sets the color of the pen. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 0, 0)), + /// bounds: Rect.fromLTWH(10, 10, 200, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` PdfColor get color => _color; - - /// Sets the color of the pen. set color(PdfColor value) { _checkImmutability(); _color = value; } - /// Gets the brush, which specifies the pen behavior. - PdfBrush get brush => _brush; - - /// Sets the brush, which specifies the pen behavior. - set brush(PdfBrush value) { + /// Gets or sets the brush, which specifies the pen behavior. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// //Set brush + /// pen: PdfPen(PdfColor(255, 0, 0))..brush = PdfBrushes.green, + /// bounds: Rect.fromLTWH(10, 10, 200, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` + PdfBrush? get brush => _brush; + set brush(PdfBrush? value) { _checkImmutability(); _setBrush(value); } - /// Gets the dash offset of the pen. + /// Gets or sets the dash offset of the pen. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// //Set pen dash offset. + /// pen: PdfPen(PdfColor(255, 0, 0))..dashOffset = 0.5, + /// bounds: Rect.fromLTWH(10, 10, 200, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` double get dashOffset => _dashOffset; - - /// Sets the dash offset of the pen. set dashOffset(double value) { _checkImmutability(); _dashOffset = value; } - /// Gets the dash pattern of the pen. + /// Gets or sets the dash pattern of the pen. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// //Set pen dash pattern. + /// pen: PdfPen(PdfColor(255, 0, 0))..dashPattern = [4, 2, 1, 3], + /// bounds: Rect.fromLTWH(10, 10, 200, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` List get dashPattern => _dashPattern; - - /// Sets the dash pattern of the pen. set dashPattern(List value) { if (dashStyle == PdfDashStyle.solid) { - UnsupportedError('''This operation is not allowed. - Set Custom dash style to change the pattern.'''); + UnsupportedError( + 'This operation is not allowed. Set Custom dash style to change the pattern.'); } _checkImmutability(); _dashPattern = value; } - /// Gets the line cap of the pen. + /// Gets or sets the line cap of the pen. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 0, 0), + /// dashStyle: PdfDashStyle.custom, lineCap: PdfLineCap.round) + /// ..dashPattern = [4, 2, 1, 3], + /// bounds: Rect.fromLTWH(0, 0, 200, 100)); + /// /Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` PdfLineCap get lineCap => _lineCap; - - /// Sets the line cap of the pen. set lineCap(PdfLineCap value) { _checkImmutability(); _lineCap = value; } - /// Gets the line join style of the pen. + /// Gets or sets the line join style of the pen. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 0, 0), + /// dashStyle: PdfDashStyle.custom, lineJoin: PdfLineJoin.bevel) + /// ..dashPattern = [4, 2, 1, 3], + /// bounds: Rect.fromLTWH(0, 0, 200, 100)); + /// /Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` PdfLineJoin get lineJoin => _lineJoin; - - /// Sets the line join style of the pen. set lineJoin(PdfLineJoin value) { _checkImmutability(); _lineJoin = value; } - /// Gets the width of the pen. + /// Gets or sets the width of the pen. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// //Set pen width. + /// pen: PdfPen(PdfColor(255, 0, 0), width: 4), + /// bounds: Rect.fromLTWH(10, 10, 200, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` double get width => _width; - - /// Sets the width of the pen. set width(double value) { _checkImmutability(); _width = value; } - /// Gets the miter limit. + /// Gets or sets the miter limit. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 0, 0), width: 4) + /// //Set miter limit, + /// ..miterLimit = 2, + /// bounds: Rect.fromLTWH(10, 10, 200, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` double get miterLimit => _miterLimit; - - /// Sets the miter limit. set miterLimit(double value) { _checkImmutability(); _miterLimit = value; } - /// Gets the dash style of the pen. + /// Gets or sets the dash style of the pen. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument() + /// ..pages.add().graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 0, 0), + /// dashStyle: PdfDashStyle.custom, lineJoin: PdfLineJoin.bevel) + /// ..dashPattern = [4, 2, 1, 3], + /// bounds: Rect.fromLTWH(0, 0, 200, 100)); + /// /Save the document. + /// List bytes = document.save(); + /// //Close the document. + /// document.dispose(); + /// ``` PdfDashStyle get dashStyle => _dashStyle; - - /// Sets the dash style of the pen. set dashStyle(PdfDashStyle value) { _checkImmutability(); _setDashStyle(value); } //Implementation - bool _isEqual(PdfPen other) { return width == other.width && miterLimit == other.miterLimit && @@ -210,9 +311,9 @@ class PdfPen { } } - void _setDashStyle(PdfDashStyle value) { + void _setDashStyle(PdfDashStyle? value) { if (_dashStyle != value) { - _dashStyle = value; + _dashStyle = value!; switch (_dashStyle) { case PdfDashStyle.custom: break; @@ -229,6 +330,7 @@ class PdfPen { _dashPattern = [3, 1, 1, 1, 1, 1]; break; case PdfDashStyle.solid: + _dashPattern = []; break; default: _dashStyle = PdfDashStyle.solid; @@ -238,29 +340,19 @@ class PdfPen { } } - void _initialize( - [double width, - PdfDashStyle dashStyle, - PdfLineCap lineCap, - PdfLineJoin lineJoin]) { - if (width != null) { - _width = width; - } + void _initialize(double width, PdfDashStyle dashStyle, PdfLineCap lineCap, + PdfLineJoin lineJoin) { + _width = width; _colorSpace = PdfColorSpace.rgb; - if (dashStyle != null) { - this.dashStyle = dashStyle; - } else { - _dashStyle = PdfDashStyle.solid; - } - _color ??= PdfColor(0, 0, 0); _dashOffset = 0; _dashPattern = []; - _lineCap = lineCap ?? PdfLineCap.flat; - _lineJoin = lineJoin ?? PdfLineJoin.miter; + _dashStyle = PdfDashStyle.solid; + _lineCap = lineCap; + _lineJoin = lineJoin; _miterLimit = 0.0; } - void _setBrush(PdfBrush brush) { + void _setBrush(PdfBrush? brush) { if (brush is PdfSolidBrush) { _color = brush.color; _brush = brush; @@ -268,12 +360,12 @@ class PdfPen { } bool _monitorChanges( - PdfPen currentPen, + PdfPen? currentPen, _PdfStreamWriter streamWriter, - Function getResources, + Function? getResources, bool saveState, - PdfColorSpace currentColorSpace, - _PdfTransformationMatrix matrix) { + PdfColorSpace? currentColorSpace, + _PdfTransformationMatrix? matrix) { bool diff = false; saveState = true; if (currentPen == null) { @@ -292,18 +384,19 @@ class PdfPen { return diff; } - bool _dashControl(PdfPen pen, bool saveState, _PdfStreamWriter streamWriter) { + bool _dashControl( + PdfPen? pen, bool saveState, _PdfStreamWriter streamWriter) { saveState = true; - final List pattern = _getPattern(); + final List? pattern = _getPattern(); streamWriter._setLineDashPattern(pattern, dashOffset * width); return saveState; } - List _getPattern() { - final List pattern = dashPattern; + List? _getPattern() { + final List? pattern = dashPattern; _isSkipPatternWidth ??= false; - if (!_isSkipPatternWidth) { - for (int i = 0; i < pattern.length; ++i) { + if (!_isSkipPatternWidth!) { + for (int i = 0; i < pattern!.length; ++i) { pattern[i] *= width; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_pens.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_pens.dart index c3ef612c3..ab5ed4595 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_pens.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_pens.dart @@ -38,7 +38,7 @@ class PdfPens { /// ``` static PdfPen get aliceBlue { if (_pens.containsKey(_KnownColor.aliceBlue)) { - return _pens[_KnownColor.aliceBlue]; + return _pens[_KnownColor.aliceBlue]!; } else { return _getPen(_KnownColor.aliceBlue); } @@ -59,7 +59,7 @@ class PdfPens { /// ``` static PdfPen get antiqueWhite { if (_pens.containsKey(_KnownColor.antiqueWhite)) { - return _pens[_KnownColor.antiqueWhite]; + return _pens[_KnownColor.antiqueWhite]!; } else { return _getPen(_KnownColor.antiqueWhite); } @@ -83,7 +83,7 @@ class PdfPens { /// ``` static PdfPen get aqua { if (_pens.containsKey(_KnownColor.aqua)) { - return _pens[_KnownColor.aqua]; + return _pens[_KnownColor.aqua]!; } else { return _getPen(_KnownColor.aqua); } @@ -104,7 +104,7 @@ class PdfPens { /// ``` static PdfPen get aquamarine { if (_pens.containsKey(_KnownColor.aquamarine)) { - return _pens[_KnownColor.aquamarine]; + return _pens[_KnownColor.aquamarine]!; } else { return _getPen(_KnownColor.aquamarine); } @@ -128,7 +128,7 @@ class PdfPens { /// ``` static PdfPen get azure { if (_pens.containsKey(_KnownColor.azure)) { - return _pens[_KnownColor.azure]; + return _pens[_KnownColor.azure]!; } else { return _getPen(_KnownColor.azure); } @@ -152,7 +152,7 @@ class PdfPens { /// ``` static PdfPen get beige { if (_pens.containsKey(_KnownColor.beige)) { - return _pens[_KnownColor.beige]; + return _pens[_KnownColor.beige]!; } else { return _getPen(_KnownColor.beige); } @@ -176,7 +176,7 @@ class PdfPens { /// ``` static PdfPen get bisque { if (_pens.containsKey(_KnownColor.bisque)) { - return _pens[_KnownColor.bisque]; + return _pens[_KnownColor.bisque]!; } else { return _getPen(_KnownColor.bisque); } @@ -200,7 +200,7 @@ class PdfPens { /// ``` static PdfPen get black { if (_pens.containsKey(_KnownColor.black)) { - return _pens[_KnownColor.black]; + return _pens[_KnownColor.black]!; } else { return _getPen(_KnownColor.black); } @@ -221,7 +221,7 @@ class PdfPens { /// ``` static PdfPen get blanchedAlmond { if (_pens.containsKey(_KnownColor.blanchedAlmond)) { - return _pens[_KnownColor.blanchedAlmond]; + return _pens[_KnownColor.blanchedAlmond]!; } else { return _getPen(_KnownColor.blanchedAlmond); } @@ -245,7 +245,7 @@ class PdfPens { /// ``` static PdfPen get blue { if (_pens.containsKey(_KnownColor.blue)) { - return _pens[_KnownColor.blue]; + return _pens[_KnownColor.blue]!; } else { return _getPen(_KnownColor.blue); } @@ -266,7 +266,7 @@ class PdfPens { /// ``` static PdfPen get blueViolet { if (_pens.containsKey(_KnownColor.blueViolet)) { - return _pens[_KnownColor.blueViolet]; + return _pens[_KnownColor.blueViolet]!; } else { return _getPen(_KnownColor.blueViolet); } @@ -290,7 +290,7 @@ class PdfPens { /// ``` static PdfPen get brown { if (_pens.containsKey(_KnownColor.brown)) { - return _pens[_KnownColor.brown]; + return _pens[_KnownColor.brown]!; } else { return _getPen(_KnownColor.brown); } @@ -314,7 +314,7 @@ class PdfPens { /// ``` static PdfPen get burlyWood { if (_pens.containsKey(_KnownColor.burlyWood)) { - return _pens[_KnownColor.burlyWood]; + return _pens[_KnownColor.burlyWood]!; } else { return _getPen(_KnownColor.burlyWood); } @@ -338,7 +338,7 @@ class PdfPens { /// ``` static PdfPen get cadetBlue { if (_pens.containsKey(_KnownColor.cadetBlue)) { - return _pens[_KnownColor.cadetBlue]; + return _pens[_KnownColor.cadetBlue]!; } else { return _getPen(_KnownColor.cadetBlue); } @@ -359,7 +359,7 @@ class PdfPens { /// ``` static PdfPen get chartreuse { if (_pens.containsKey(_KnownColor.chartreuse)) { - return _pens[_KnownColor.chartreuse]; + return _pens[_KnownColor.chartreuse]!; } else { return _getPen(_KnownColor.chartreuse); } @@ -383,7 +383,7 @@ class PdfPens { /// ``` static PdfPen get chocolate { if (_pens.containsKey(_KnownColor.chocolate)) { - return _pens[_KnownColor.chocolate]; + return _pens[_KnownColor.chocolate]!; } else { return _getPen(_KnownColor.chocolate); } @@ -407,7 +407,7 @@ class PdfPens { /// ``` static PdfPen get coral { if (_pens.containsKey(_KnownColor.coral)) { - return _pens[_KnownColor.coral]; + return _pens[_KnownColor.coral]!; } else { return _getPen(_KnownColor.coral); } @@ -428,7 +428,7 @@ class PdfPens { /// ``` static PdfPen get cornflowerBlue { if (_pens.containsKey(_KnownColor.cornflowerBlue)) { - return _pens[_KnownColor.cornflowerBlue]; + return _pens[_KnownColor.cornflowerBlue]!; } else { return _getPen(_KnownColor.cornflowerBlue); } @@ -452,7 +452,7 @@ class PdfPens { /// ``` static PdfPen get cornsilk { if (_pens.containsKey(_KnownColor.cornsilk)) { - return _pens[_KnownColor.cornsilk]; + return _pens[_KnownColor.cornsilk]!; } else { return _getPen(_KnownColor.cornsilk); } @@ -476,7 +476,7 @@ class PdfPens { /// ``` static PdfPen get crimson { if (_pens.containsKey(_KnownColor.crimson)) { - return _pens[_KnownColor.crimson]; + return _pens[_KnownColor.crimson]!; } else { return _getPen(_KnownColor.crimson); } @@ -500,7 +500,7 @@ class PdfPens { /// ``` static PdfPen get cyan { if (_pens.containsKey(_KnownColor.cyan)) { - return _pens[_KnownColor.cyan]; + return _pens[_KnownColor.cyan]!; } else { return _getPen(_KnownColor.cyan); } @@ -524,7 +524,7 @@ class PdfPens { /// ``` static PdfPen get darkBlue { if (_pens.containsKey(_KnownColor.darkBlue)) { - return _pens[_KnownColor.darkBlue]; + return _pens[_KnownColor.darkBlue]!; } else { return _getPen(_KnownColor.darkBlue); } @@ -548,7 +548,7 @@ class PdfPens { /// ``` static PdfPen get darkCyan { if (_pens.containsKey(_KnownColor.darkCyan)) { - return _pens[_KnownColor.darkCyan]; + return _pens[_KnownColor.darkCyan]!; } else { return _getPen(_KnownColor.darkCyan); } @@ -569,7 +569,7 @@ class PdfPens { /// ``` static PdfPen get darkGoldenrod { if (_pens.containsKey(_KnownColor.darkGoldenrod)) { - return _pens[_KnownColor.darkGoldenrod]; + return _pens[_KnownColor.darkGoldenrod]!; } else { return _getPen(_KnownColor.darkGoldenrod); } @@ -593,7 +593,7 @@ class PdfPens { /// ``` static PdfPen get darkGray { if (_pens.containsKey(_KnownColor.darkGray)) { - return _pens[_KnownColor.darkGray]; + return _pens[_KnownColor.darkGray]!; } else { return _getPen(_KnownColor.darkGray); } @@ -617,7 +617,7 @@ class PdfPens { /// ``` static PdfPen get darkGreen { if (_pens.containsKey(_KnownColor.darkGreen)) { - return _pens[_KnownColor.darkGreen]; + return _pens[_KnownColor.darkGreen]!; } else { return _getPen(_KnownColor.darkGreen); } @@ -641,7 +641,7 @@ class PdfPens { /// ``` static PdfPen get darkKhaki { if (_pens.containsKey(_KnownColor.darkKhaki)) { - return _pens[_KnownColor.darkKhaki]; + return _pens[_KnownColor.darkKhaki]!; } else { return _getPen(_KnownColor.darkKhaki); } @@ -662,7 +662,7 @@ class PdfPens { /// ``` static PdfPen get darkMagenta { if (_pens.containsKey(_KnownColor.darkMagenta)) { - return _pens[_KnownColor.darkMagenta]; + return _pens[_KnownColor.darkMagenta]!; } else { return _getPen(_KnownColor.darkMagenta); } @@ -683,7 +683,7 @@ class PdfPens { /// ``` static PdfPen get darkOliveGreen { if (_pens.containsKey(_KnownColor.darkOliveGreen)) { - return _pens[_KnownColor.darkOliveGreen]; + return _pens[_KnownColor.darkOliveGreen]!; } else { return _getPen(_KnownColor.darkOliveGreen); } @@ -704,7 +704,7 @@ class PdfPens { /// ``` static PdfPen get darkOrange { if (_pens.containsKey(_KnownColor.darkOrange)) { - return _pens[_KnownColor.darkOrange]; + return _pens[_KnownColor.darkOrange]!; } else { return _getPen(_KnownColor.darkOrange); } @@ -725,7 +725,7 @@ class PdfPens { /// ``` static PdfPen get darkOrchid { if (_pens.containsKey(_KnownColor.darkOrchid)) { - return _pens[_KnownColor.darkOrchid]; + return _pens[_KnownColor.darkOrchid]!; } else { return _getPen(_KnownColor.darkOrchid); } @@ -749,7 +749,7 @@ class PdfPens { /// ``` static PdfPen get darkRed { if (_pens.containsKey(_KnownColor.darkRed)) { - return _pens[_KnownColor.darkRed]; + return _pens[_KnownColor.darkRed]!; } else { return _getPen(_KnownColor.darkRed); } @@ -770,7 +770,7 @@ class PdfPens { /// ``` static PdfPen get darkSalmon { if (_pens.containsKey(_KnownColor.darkSalmon)) { - return _pens[_KnownColor.darkSalmon]; + return _pens[_KnownColor.darkSalmon]!; } else { return _getPen(_KnownColor.darkSalmon); } @@ -791,7 +791,7 @@ class PdfPens { /// ``` static PdfPen get darkSeaGreen { if (_pens.containsKey(_KnownColor.darkSeaGreen)) { - return _pens[_KnownColor.darkSeaGreen]; + return _pens[_KnownColor.darkSeaGreen]!; } else { return _getPen(_KnownColor.darkSeaGreen); } @@ -812,7 +812,7 @@ class PdfPens { /// ``` static PdfPen get darkSlateBlue { if (_pens.containsKey(_KnownColor.darkSlateBlue)) { - return _pens[_KnownColor.darkSlateBlue]; + return _pens[_KnownColor.darkSlateBlue]!; } else { return _getPen(_KnownColor.darkSlateBlue); } @@ -833,7 +833,7 @@ class PdfPens { /// ``` static PdfPen get darkSlateGray { if (_pens.containsKey(_KnownColor.darkSlateGray)) { - return _pens[_KnownColor.darkSlateGray]; + return _pens[_KnownColor.darkSlateGray]!; } else { return _getPen(_KnownColor.darkSlateGray); } @@ -854,7 +854,7 @@ class PdfPens { /// ``` static PdfPen get darkTurquoise { if (_pens.containsKey(_KnownColor.darkTurquoise)) { - return _pens[_KnownColor.darkTurquoise]; + return _pens[_KnownColor.darkTurquoise]!; } else { return _getPen(_KnownColor.darkTurquoise); } @@ -875,7 +875,7 @@ class PdfPens { /// ``` static PdfPen get darkViolet { if (_pens.containsKey(_KnownColor.darkViolet)) { - return _pens[_KnownColor.darkViolet]; + return _pens[_KnownColor.darkViolet]!; } else { return _getPen(_KnownColor.darkViolet); } @@ -899,7 +899,7 @@ class PdfPens { /// ``` static PdfPen get deepPink { if (_pens.containsKey(_KnownColor.deepPink)) { - return _pens[_KnownColor.deepPink]; + return _pens[_KnownColor.deepPink]!; } else { return _getPen(_KnownColor.deepPink); } @@ -920,7 +920,7 @@ class PdfPens { /// ``` static PdfPen get deepSkyBlue { if (_pens.containsKey(_KnownColor.deepSkyBlue)) { - return _pens[_KnownColor.deepSkyBlue]; + return _pens[_KnownColor.deepSkyBlue]!; } else { return _getPen(_KnownColor.deepSkyBlue); } @@ -944,7 +944,7 @@ class PdfPens { /// ``` static PdfPen get dimGray { if (_pens.containsKey(_KnownColor.dimGray)) { - return _pens[_KnownColor.dimGray]; + return _pens[_KnownColor.dimGray]!; } else { return _getPen(_KnownColor.dimGray); } @@ -965,7 +965,7 @@ class PdfPens { /// ``` static PdfPen get dodgerBlue { if (_pens.containsKey(_KnownColor.dodgerBlue)) { - return _pens[_KnownColor.dodgerBlue]; + return _pens[_KnownColor.dodgerBlue]!; } else { return _getPen(_KnownColor.dodgerBlue); } @@ -989,7 +989,7 @@ class PdfPens { /// ``` static PdfPen get firebrick { if (_pens.containsKey(_KnownColor.firebrick)) { - return _pens[_KnownColor.firebrick]; + return _pens[_KnownColor.firebrick]!; } else { return _getPen(_KnownColor.firebrick); } @@ -1010,7 +1010,7 @@ class PdfPens { /// ``` static PdfPen get floralWhite { if (_pens.containsKey(_KnownColor.floralWhite)) { - return _pens[_KnownColor.floralWhite]; + return _pens[_KnownColor.floralWhite]!; } else { return _getPen(_KnownColor.floralWhite); } @@ -1031,7 +1031,7 @@ class PdfPens { /// ``` static PdfPen get forestGreen { if (_pens.containsKey(_KnownColor.forestGreen)) { - return _pens[_KnownColor.forestGreen]; + return _pens[_KnownColor.forestGreen]!; } else { return _getPen(_KnownColor.forestGreen); } @@ -1055,7 +1055,7 @@ class PdfPens { /// ``` static PdfPen get fuchsia { if (_pens.containsKey(_KnownColor.fuchsia)) { - return _pens[_KnownColor.fuchsia]; + return _pens[_KnownColor.fuchsia]!; } else { return _getPen(_KnownColor.fuchsia); } @@ -1079,7 +1079,7 @@ class PdfPens { /// ``` static PdfPen get gainsboro { if (_pens.containsKey(_KnownColor.gainsboro)) { - return _pens[_KnownColor.gainsboro]; + return _pens[_KnownColor.gainsboro]!; } else { return _getPen(_KnownColor.gainsboro); } @@ -1100,7 +1100,7 @@ class PdfPens { /// ``` static PdfPen get ghostWhite { if (_pens.containsKey(_KnownColor.ghostWhite)) { - return _pens[_KnownColor.ghostWhite]; + return _pens[_KnownColor.ghostWhite]!; } else { return _getPen(_KnownColor.ghostWhite); } @@ -1124,7 +1124,7 @@ class PdfPens { /// ``` static PdfPen get gold { if (_pens.containsKey(_KnownColor.gold)) { - return _pens[_KnownColor.gold]; + return _pens[_KnownColor.gold]!; } else { return _getPen(_KnownColor.gold); } @@ -1147,7 +1147,7 @@ class PdfPens { /// ``` static PdfPen get goldenrod { if (_pens.containsKey(_KnownColor.goldenrod)) { - return _pens[_KnownColor.goldenrod]; + return _pens[_KnownColor.goldenrod]!; } else { return _getPen(_KnownColor.goldenrod); } @@ -1171,7 +1171,7 @@ class PdfPens { /// ``` static PdfPen get gray { if (_pens.containsKey(_KnownColor.gray)) { - return _pens[_KnownColor.gray]; + return _pens[_KnownColor.gray]!; } else { return _getPen(_KnownColor.gray); } @@ -1195,7 +1195,7 @@ class PdfPens { /// ``` static PdfPen get green { if (_pens.containsKey(_KnownColor.green)) { - return _pens[_KnownColor.green]; + return _pens[_KnownColor.green]!; } else { return _getPen(_KnownColor.green); } @@ -1216,7 +1216,7 @@ class PdfPens { /// ``` static PdfPen get greenYellow { if (_pens.containsKey(_KnownColor.greenYellow)) { - return _pens[_KnownColor.greenYellow]; + return _pens[_KnownColor.greenYellow]!; } else { return _getPen(_KnownColor.greenYellow); } @@ -1240,7 +1240,7 @@ class PdfPens { /// ``` static PdfPen get honeydew { if (_pens.containsKey(_KnownColor.honeydew)) { - return _pens[_KnownColor.honeydew]; + return _pens[_KnownColor.honeydew]!; } else { return _getPen(_KnownColor.honeydew); } @@ -1264,7 +1264,7 @@ class PdfPens { /// ``` static PdfPen get hotPink { if (_pens.containsKey(_KnownColor.hotPink)) { - return _pens[_KnownColor.hotPink]; + return _pens[_KnownColor.hotPink]!; } else { return _getPen(_KnownColor.hotPink); } @@ -1288,7 +1288,7 @@ class PdfPens { /// ``` static PdfPen get indianRed { if (_pens.containsKey(_KnownColor.indianRed)) { - return _pens[_KnownColor.indianRed]; + return _pens[_KnownColor.indianRed]!; } else { return _getPen(_KnownColor.indianRed); } @@ -1311,7 +1311,7 @@ class PdfPens { /// ``` static PdfPen get indigo { if (_pens.containsKey(_KnownColor.indigo)) { - return _pens[_KnownColor.indigo]; + return _pens[_KnownColor.indigo]!; } else { return _getPen(_KnownColor.indigo); } @@ -1335,7 +1335,7 @@ class PdfPens { /// ``` static PdfPen get ivory { if (_pens.containsKey(_KnownColor.ivory)) { - return _pens[_KnownColor.ivory]; + return _pens[_KnownColor.ivory]!; } else { return _getPen(_KnownColor.ivory); } @@ -1359,7 +1359,7 @@ class PdfPens { /// ``` static PdfPen get khaki { if (_pens.containsKey(_KnownColor.khaki)) { - return _pens[_KnownColor.khaki]; + return _pens[_KnownColor.khaki]!; } else { return _getPen(_KnownColor.khaki); } @@ -1383,7 +1383,7 @@ class PdfPens { /// ``` static PdfPen get lavender { if (_pens.containsKey(_KnownColor.lavender)) { - return _pens[_KnownColor.lavender]; + return _pens[_KnownColor.lavender]!; } else { return _getPen(_KnownColor.lavender); } @@ -1404,7 +1404,7 @@ class PdfPens { /// ``` static PdfPen get lavenderBlush { if (_pens.containsKey(_KnownColor.lavenderBlush)) { - return _pens[_KnownColor.lavenderBlush]; + return _pens[_KnownColor.lavenderBlush]!; } else { return _getPen(_KnownColor.lavenderBlush); } @@ -1428,7 +1428,7 @@ class PdfPens { /// ``` static PdfPen get lawnGreen { if (_pens.containsKey(_KnownColor.lawnGreen)) { - return _pens[_KnownColor.lawnGreen]; + return _pens[_KnownColor.lawnGreen]!; } else { return _getPen(_KnownColor.lawnGreen); } @@ -1449,7 +1449,7 @@ class PdfPens { /// ``` static PdfPen get lemonChiffon { if (_pens.containsKey(_KnownColor.lemonChiffon)) { - return _pens[_KnownColor.lemonChiffon]; + return _pens[_KnownColor.lemonChiffon]!; } else { return _getPen(_KnownColor.lemonChiffon); } @@ -1473,7 +1473,7 @@ class PdfPens { /// ``` static PdfPen get lightBlue { if (_pens.containsKey(_KnownColor.lightBlue)) { - return _pens[_KnownColor.lightBlue]; + return _pens[_KnownColor.lightBlue]!; } else { return _getPen(_KnownColor.lightBlue); } @@ -1494,7 +1494,7 @@ class PdfPens { /// ``` static PdfPen get lightCoral { if (_pens.containsKey(_KnownColor.lightCoral)) { - return _pens[_KnownColor.lightCoral]; + return _pens[_KnownColor.lightCoral]!; } else { return _getPen(_KnownColor.lightCoral); } @@ -1518,7 +1518,7 @@ class PdfPens { /// ``` static PdfPen get lightCyan { if (_pens.containsKey(_KnownColor.lightCyan)) { - return _pens[_KnownColor.lightCyan]; + return _pens[_KnownColor.lightCyan]!; } else { return _getPen(_KnownColor.lightCyan); } @@ -1540,7 +1540,7 @@ class PdfPens { /// ``` static PdfPen get lightGoldenrodYellow { if (_pens.containsKey(_KnownColor.lightGoldenrodYellow)) { - return _pens[_KnownColor.lightGoldenrodYellow]; + return _pens[_KnownColor.lightGoldenrodYellow]!; } else { return _getPen(_KnownColor.lightGoldenrodYellow); } @@ -1564,7 +1564,7 @@ class PdfPens { /// ``` static PdfPen get lightGray { if (_pens.containsKey(_KnownColor.lightGray)) { - return _pens[_KnownColor.lightGray]; + return _pens[_KnownColor.lightGray]!; } else { return _getPen(_KnownColor.lightGray); } @@ -1585,7 +1585,7 @@ class PdfPens { /// ``` static PdfPen get lightGreen { if (_pens.containsKey(_KnownColor.lightGreen)) { - return _pens[_KnownColor.lightGreen]; + return _pens[_KnownColor.lightGreen]!; } else { return _getPen(_KnownColor.lightGreen); } @@ -1609,7 +1609,7 @@ class PdfPens { /// ``` static PdfPen get lightPink { if (_pens.containsKey(_KnownColor.lightPink)) { - return _pens[_KnownColor.lightPink]; + return _pens[_KnownColor.lightPink]!; } else { return _getPen(_KnownColor.lightPink); } @@ -1630,7 +1630,7 @@ class PdfPens { /// ``` static PdfPen get lightSalmon { if (_pens.containsKey(_KnownColor.lightSalmon)) { - return _pens[_KnownColor.lightSalmon]; + return _pens[_KnownColor.lightSalmon]!; } else { return _getPen(_KnownColor.lightSalmon); } @@ -1651,7 +1651,7 @@ class PdfPens { /// ``` static PdfPen get lightSeaGreen { if (_pens.containsKey(_KnownColor.lightSeaGreen)) { - return _pens[_KnownColor.lightSeaGreen]; + return _pens[_KnownColor.lightSeaGreen]!; } else { return _getPen(_KnownColor.lightSeaGreen); } @@ -1672,7 +1672,7 @@ class PdfPens { /// ``` static PdfPen get lightSkyBlue { if (_pens.containsKey(_KnownColor.lightSkyBlue)) { - return _pens[_KnownColor.lightSkyBlue]; + return _pens[_KnownColor.lightSkyBlue]!; } else { return _getPen(_KnownColor.lightSkyBlue); } @@ -1693,7 +1693,7 @@ class PdfPens { /// ``` static PdfPen get lightSlateGray { if (_pens.containsKey(_KnownColor.lightSlateGray)) { - return _pens[_KnownColor.lightSlateGray]; + return _pens[_KnownColor.lightSlateGray]!; } else { return _getPen(_KnownColor.lightSlateGray); } @@ -1714,7 +1714,7 @@ class PdfPens { /// ``` static PdfPen get lightSteelBlue { if (_pens.containsKey(_KnownColor.lightSteelBlue)) { - return _pens[_KnownColor.lightSteelBlue]; + return _pens[_KnownColor.lightSteelBlue]!; } else { return _getPen(_KnownColor.lightSteelBlue); } @@ -1735,7 +1735,7 @@ class PdfPens { /// ``` static PdfPen get lightYellow { if (_pens.containsKey(_KnownColor.lightYellow)) { - return _pens[_KnownColor.lightYellow]; + return _pens[_KnownColor.lightYellow]!; } else { return _getPen(_KnownColor.lightYellow); } @@ -1759,7 +1759,7 @@ class PdfPens { /// ``` static PdfPen get lime { if (_pens.containsKey(_KnownColor.lime)) { - return _pens[_KnownColor.lime]; + return _pens[_KnownColor.lime]!; } else { return _getPen(_KnownColor.lime); } @@ -1783,7 +1783,7 @@ class PdfPens { /// ``` static PdfPen get limeGreen { if (_pens.containsKey(_KnownColor.limeGreen)) { - return _pens[_KnownColor.limeGreen]; + return _pens[_KnownColor.limeGreen]!; } else { return _getPen(_KnownColor.limeGreen); } @@ -1807,7 +1807,7 @@ class PdfPens { /// ``` static PdfPen get linen { if (_pens.containsKey(_KnownColor.linen)) { - return _pens[_KnownColor.linen]; + return _pens[_KnownColor.linen]!; } else { return _getPen(_KnownColor.linen); } @@ -1831,7 +1831,7 @@ class PdfPens { /// ``` static PdfPen get magenta { if (_pens.containsKey(_KnownColor.magenta)) { - return _pens[_KnownColor.magenta]; + return _pens[_KnownColor.magenta]!; } else { return _getPen(_KnownColor.magenta); } @@ -1855,7 +1855,7 @@ class PdfPens { /// ``` static PdfPen get maroon { if (_pens.containsKey(_KnownColor.maroon)) { - return _pens[_KnownColor.maroon]; + return _pens[_KnownColor.maroon]!; } else { return _getPen(_KnownColor.maroon); } @@ -1876,7 +1876,7 @@ class PdfPens { /// ``` static PdfPen get mediumAquamarine { if (_pens.containsKey(_KnownColor.mediumAquamarine)) { - return _pens[_KnownColor.mediumAquamarine]; + return _pens[_KnownColor.mediumAquamarine]!; } else { return _getPen(_KnownColor.mediumAquamarine); } @@ -1897,7 +1897,7 @@ class PdfPens { /// ``` static PdfPen get mediumBlue { if (_pens.containsKey(_KnownColor.mediumBlue)) { - return _pens[_KnownColor.mediumBlue]; + return _pens[_KnownColor.mediumBlue]!; } else { return _getPen(_KnownColor.mediumBlue); } @@ -1918,7 +1918,7 @@ class PdfPens { /// ``` static PdfPen get mediumOrchid { if (_pens.containsKey(_KnownColor.mediumOrchid)) { - return _pens[_KnownColor.mediumOrchid]; + return _pens[_KnownColor.mediumOrchid]!; } else { return _getPen(_KnownColor.mediumOrchid); } @@ -1939,7 +1939,7 @@ class PdfPens { /// ``` static PdfPen get mediumPurple { if (_pens.containsKey(_KnownColor.mediumPurple)) { - return _pens[_KnownColor.mediumPurple]; + return _pens[_KnownColor.mediumPurple]!; } else { return _getPen(_KnownColor.mediumPurple); } @@ -1960,7 +1960,7 @@ class PdfPens { /// ``` static PdfPen get mediumSeaGreen { if (_pens.containsKey(_KnownColor.mediumSeaGreen)) { - return _pens[_KnownColor.mediumSeaGreen]; + return _pens[_KnownColor.mediumSeaGreen]!; } else { return _getPen(_KnownColor.mediumSeaGreen); } @@ -1981,7 +1981,7 @@ class PdfPens { /// ``` static PdfPen get mediumSlateBlue { if (_pens.containsKey(_KnownColor.mediumSlateBlue)) { - return _pens[_KnownColor.mediumSlateBlue]; + return _pens[_KnownColor.mediumSlateBlue]!; } else { return _getPen(_KnownColor.mediumSlateBlue); } @@ -2002,7 +2002,7 @@ class PdfPens { /// ``` static PdfPen get mediumSpringGreen { if (_pens.containsKey(_KnownColor.mediumSpringGreen)) { - return _pens[_KnownColor.mediumSpringGreen]; + return _pens[_KnownColor.mediumSpringGreen]!; } else { return _getPen(_KnownColor.mediumSpringGreen); } @@ -2023,7 +2023,7 @@ class PdfPens { /// ``` static PdfPen get mediumTurquoise { if (_pens.containsKey(_KnownColor.mediumTurquoise)) { - return _pens[_KnownColor.mediumTurquoise]; + return _pens[_KnownColor.mediumTurquoise]!; } else { return _getPen(_KnownColor.mediumTurquoise); } @@ -2044,7 +2044,7 @@ class PdfPens { /// ``` static PdfPen get mediumVioletRed { if (_pens.containsKey(_KnownColor.mediumVioletRed)) { - return _pens[_KnownColor.mediumVioletRed]; + return _pens[_KnownColor.mediumVioletRed]!; } else { return _getPen(_KnownColor.mediumVioletRed); } @@ -2065,7 +2065,7 @@ class PdfPens { /// ``` static PdfPen get midnightBlue { if (_pens.containsKey(_KnownColor.midnightBlue)) { - return _pens[_KnownColor.midnightBlue]; + return _pens[_KnownColor.midnightBlue]!; } else { return _getPen(_KnownColor.midnightBlue); } @@ -2089,7 +2089,7 @@ class PdfPens { /// ``` static PdfPen get mintCream { if (_pens.containsKey(_KnownColor.mintCream)) { - return _pens[_KnownColor.mintCream]; + return _pens[_KnownColor.mintCream]!; } else { return _getPen(_KnownColor.mintCream); } @@ -2113,7 +2113,7 @@ class PdfPens { /// ``` static PdfPen get mistyRose { if (_pens.containsKey(_KnownColor.mistyRose)) { - return _pens[_KnownColor.mistyRose]; + return _pens[_KnownColor.mistyRose]!; } else { return _getPen(_KnownColor.mistyRose); } @@ -2136,7 +2136,7 @@ class PdfPens { /// ``` static PdfPen get moccasin { if (_pens.containsKey(_KnownColor.moccasin)) { - return _pens[_KnownColor.moccasin]; + return _pens[_KnownColor.moccasin]!; } else { return _getPen(_KnownColor.moccasin); } @@ -2157,7 +2157,7 @@ class PdfPens { /// ``` static PdfPen get navajoWhite { if (_pens.containsKey(_KnownColor.navajoWhite)) { - return _pens[_KnownColor.navajoWhite]; + return _pens[_KnownColor.navajoWhite]!; } else { return _getPen(_KnownColor.navajoWhite); } @@ -2181,7 +2181,7 @@ class PdfPens { /// ``` static PdfPen get navy { if (_pens.containsKey(_KnownColor.navy)) { - return _pens[_KnownColor.navy]; + return _pens[_KnownColor.navy]!; } else { return _getPen(_KnownColor.navy); } @@ -2205,7 +2205,7 @@ class PdfPens { /// ``` static PdfPen get oldLace { if (_pens.containsKey(_KnownColor.oldLace)) { - return _pens[_KnownColor.oldLace]; + return _pens[_KnownColor.oldLace]!; } else { return _getPen(_KnownColor.oldLace); } @@ -2229,7 +2229,7 @@ class PdfPens { /// ``` static PdfPen get olive { if (_pens.containsKey(_KnownColor.olive)) { - return _pens[_KnownColor.olive]; + return _pens[_KnownColor.olive]!; } else { return _getPen(_KnownColor.olive); } @@ -2252,7 +2252,7 @@ class PdfPens { /// ``` static PdfPen get oliveDrab { if (_pens.containsKey(_KnownColor.oliveDrab)) { - return _pens[_KnownColor.oliveDrab]; + return _pens[_KnownColor.oliveDrab]!; } else { return _getPen(_KnownColor.oliveDrab); } @@ -2275,7 +2275,7 @@ class PdfPens { /// ``` static PdfPen get orange { if (_pens.containsKey(_KnownColor.orange)) { - return _pens[_KnownColor.orange]; + return _pens[_KnownColor.orange]!; } else { return _getPen(_KnownColor.orange); } @@ -2298,7 +2298,7 @@ class PdfPens { /// ``` static PdfPen get orangeRed { if (_pens.containsKey(_KnownColor.orangeRed)) { - return _pens[_KnownColor.orangeRed]; + return _pens[_KnownColor.orangeRed]!; } else { return _getPen(_KnownColor.orangeRed); } @@ -2322,7 +2322,7 @@ class PdfPens { /// ``` static PdfPen get orchid { if (_pens.containsKey(_KnownColor.orchid)) { - return _pens[_KnownColor.orchid]; + return _pens[_KnownColor.orchid]!; } else { return _getPen(_KnownColor.orchid); } @@ -2343,7 +2343,7 @@ class PdfPens { /// ``` static PdfPen get paleGoldenrod { if (_pens.containsKey(_KnownColor.paleGoldenrod)) { - return _pens[_KnownColor.paleGoldenrod]; + return _pens[_KnownColor.paleGoldenrod]!; } else { return _getPen(_KnownColor.paleGoldenrod); } @@ -2366,7 +2366,7 @@ class PdfPens { /// ``` static PdfPen get paleGreen { if (_pens.containsKey(_KnownColor.paleGreen)) { - return _pens[_KnownColor.paleGreen]; + return _pens[_KnownColor.paleGreen]!; } else { return _getPen(_KnownColor.paleGreen); } @@ -2387,7 +2387,7 @@ class PdfPens { /// ``` static PdfPen get paleTurquoise { if (_pens.containsKey(_KnownColor.paleTurquoise)) { - return _pens[_KnownColor.paleTurquoise]; + return _pens[_KnownColor.paleTurquoise]!; } else { return _getPen(_KnownColor.paleTurquoise); } @@ -2408,7 +2408,7 @@ class PdfPens { /// ``` static PdfPen get paleVioletRed { if (_pens.containsKey(_KnownColor.paleVioletRed)) { - return _pens[_KnownColor.paleVioletRed]; + return _pens[_KnownColor.paleVioletRed]!; } else { return _getPen(_KnownColor.paleVioletRed); } @@ -2429,7 +2429,7 @@ class PdfPens { /// ``` static PdfPen get papayaWhip { if (_pens.containsKey(_KnownColor.papayaWhip)) { - return _pens[_KnownColor.papayaWhip]; + return _pens[_KnownColor.papayaWhip]!; } else { return _getPen(_KnownColor.papayaWhip); } @@ -2452,7 +2452,7 @@ class PdfPens { /// ``` static PdfPen get peachPuff { if (_pens.containsKey(_KnownColor.peachPuff)) { - return _pens[_KnownColor.peachPuff]; + return _pens[_KnownColor.peachPuff]!; } else { return _getPen(_KnownColor.peachPuff); } @@ -2476,7 +2476,7 @@ class PdfPens { /// ``` static PdfPen get peru { if (_pens.containsKey(_KnownColor.peru)) { - return _pens[_KnownColor.peru]; + return _pens[_KnownColor.peru]!; } else { return _getPen(_KnownColor.peru); } @@ -2499,7 +2499,7 @@ class PdfPens { /// ``` static PdfPen get pink { if (_pens.containsKey(_KnownColor.pink)) { - return _pens[_KnownColor.pink]; + return _pens[_KnownColor.pink]!; } else { return _getPen(_KnownColor.pink); } @@ -2522,7 +2522,7 @@ class PdfPens { /// ``` static PdfPen get plum { if (_pens.containsKey(_KnownColor.plum)) { - return _pens[_KnownColor.plum]; + return _pens[_KnownColor.plum]!; } else { return _getPen(_KnownColor.plum); } @@ -2543,7 +2543,7 @@ class PdfPens { /// ``` static PdfPen get powderBlue { if (_pens.containsKey(_KnownColor.powderBlue)) { - return _pens[_KnownColor.powderBlue]; + return _pens[_KnownColor.powderBlue]!; } else { return _getPen(_KnownColor.powderBlue); } @@ -2566,7 +2566,7 @@ class PdfPens { /// ``` static PdfPen get purple { if (_pens.containsKey(_KnownColor.purple)) { - return _pens[_KnownColor.purple]; + return _pens[_KnownColor.purple]!; } else { return _getPen(_KnownColor.purple); } @@ -2589,7 +2589,7 @@ class PdfPens { /// ``` static PdfPen get red { if (_pens.containsKey(_KnownColor.red)) { - return _pens[_KnownColor.red]; + return _pens[_KnownColor.red]!; } else { return _getPen(_KnownColor.red); } @@ -2612,7 +2612,7 @@ class PdfPens { /// ``` static PdfPen get rosyBrown { if (_pens.containsKey(_KnownColor.rosyBrown)) { - return _pens[_KnownColor.rosyBrown]; + return _pens[_KnownColor.rosyBrown]!; } else { return _getPen(_KnownColor.rosyBrown); } @@ -2635,7 +2635,7 @@ class PdfPens { /// ``` static PdfPen get royalBlue { if (_pens.containsKey(_KnownColor.royalBlue)) { - return _pens[_KnownColor.royalBlue]; + return _pens[_KnownColor.royalBlue]!; } else { return _getPen(_KnownColor.royalBlue); } @@ -2656,7 +2656,7 @@ class PdfPens { /// ``` static PdfPen get saddleBrown { if (_pens.containsKey(_KnownColor.saddleBrown)) { - return _pens[_KnownColor.saddleBrown]; + return _pens[_KnownColor.saddleBrown]!; } else { return _getPen(_KnownColor.saddleBrown); } @@ -2679,7 +2679,7 @@ class PdfPens { /// ``` static PdfPen get salmon { if (_pens.containsKey(_KnownColor.salmon)) { - return _pens[_KnownColor.salmon]; + return _pens[_KnownColor.salmon]!; } else { return _getPen(_KnownColor.salmon); } @@ -2700,7 +2700,7 @@ class PdfPens { /// ``` static PdfPen get sandyBrown { if (_pens.containsKey(_KnownColor.sandyBrown)) { - return _pens[_KnownColor.sandyBrown]; + return _pens[_KnownColor.sandyBrown]!; } else { return _getPen(_KnownColor.sandyBrown); } @@ -2723,7 +2723,7 @@ class PdfPens { /// ``` static PdfPen get seaGreen { if (_pens.containsKey(_KnownColor.seaGreen)) { - return _pens[_KnownColor.seaGreen]; + return _pens[_KnownColor.seaGreen]!; } else { return _getPen(_KnownColor.seaGreen); } @@ -2746,7 +2746,7 @@ class PdfPens { /// ``` static PdfPen get seaShell { if (_pens.containsKey(_KnownColor.seaShell)) { - return _pens[_KnownColor.seaShell]; + return _pens[_KnownColor.seaShell]!; } else { return _getPen(_KnownColor.seaShell); } @@ -2769,7 +2769,7 @@ class PdfPens { /// ``` static PdfPen get sienna { if (_pens.containsKey(_KnownColor.sienna)) { - return _pens[_KnownColor.sienna]; + return _pens[_KnownColor.sienna]!; } else { return _getPen(_KnownColor.sienna); } @@ -2792,7 +2792,7 @@ class PdfPens { /// ``` static PdfPen get silver { if (_pens.containsKey(_KnownColor.silver)) { - return _pens[_KnownColor.silver]; + return _pens[_KnownColor.silver]!; } else { return _getPen(_KnownColor.silver); } @@ -2815,7 +2815,7 @@ class PdfPens { /// ``` static PdfPen get skyBlue { if (_pens.containsKey(_KnownColor.skyBlue)) { - return _pens[_KnownColor.skyBlue]; + return _pens[_KnownColor.skyBlue]!; } else { return _getPen(_KnownColor.skyBlue); } @@ -2838,7 +2838,7 @@ class PdfPens { /// ``` static PdfPen get slateBlue { if (_pens.containsKey(_KnownColor.slateBlue)) { - return _pens[_KnownColor.slateBlue]; + return _pens[_KnownColor.slateBlue]!; } else { return _getPen(_KnownColor.slateBlue); } @@ -2861,7 +2861,7 @@ class PdfPens { /// ``` static PdfPen get slateGray { if (_pens.containsKey(_KnownColor.slateGray)) { - return _pens[_KnownColor.slateGray]; + return _pens[_KnownColor.slateGray]!; } else { return _getPen(_KnownColor.slateGray); } @@ -2884,7 +2884,7 @@ class PdfPens { /// ``` static PdfPen get snow { if (_pens.containsKey(_KnownColor.snow)) { - return _pens[_KnownColor.snow]; + return _pens[_KnownColor.snow]!; } else { return _getPen(_KnownColor.snow); } @@ -2905,7 +2905,7 @@ class PdfPens { /// ``` static PdfPen get springGreen { if (_pens.containsKey(_KnownColor.springGreen)) { - return _pens[_KnownColor.springGreen]; + return _pens[_KnownColor.springGreen]!; } else { return _getPen(_KnownColor.springGreen); } @@ -2928,7 +2928,7 @@ class PdfPens { /// ``` static PdfPen get steelBlue { if (_pens.containsKey(_KnownColor.steelBlue)) { - return _pens[_KnownColor.steelBlue]; + return _pens[_KnownColor.steelBlue]!; } else { return _getPen(_KnownColor.steelBlue); } @@ -2951,7 +2951,7 @@ class PdfPens { /// ``` static PdfPen get tan { if (_pens.containsKey(_KnownColor.tan)) { - return _pens[_KnownColor.tan]; + return _pens[_KnownColor.tan]!; } else { return _getPen(_KnownColor.tan); } @@ -2974,7 +2974,7 @@ class PdfPens { /// ``` static PdfPen get teal { if (_pens.containsKey(_KnownColor.teal)) { - return _pens[_KnownColor.teal]; + return _pens[_KnownColor.teal]!; } else { return _getPen(_KnownColor.teal); } @@ -2997,7 +2997,7 @@ class PdfPens { /// ``` static PdfPen get thistle { if (_pens.containsKey(_KnownColor.thistle)) { - return _pens[_KnownColor.thistle]; + return _pens[_KnownColor.thistle]!; } else { return _getPen(_KnownColor.thistle); } @@ -3020,7 +3020,7 @@ class PdfPens { /// ``` static PdfPen get tomato { if (_pens.containsKey(_KnownColor.tomato)) { - return _pens[_KnownColor.tomato]; + return _pens[_KnownColor.tomato]!; } else { return _getPen(_KnownColor.tomato); } @@ -3041,7 +3041,7 @@ class PdfPens { /// ``` static PdfPen get transparent { if (_pens.containsKey(_KnownColor.transparent)) { - return _pens[_KnownColor.transparent]; + return _pens[_KnownColor.transparent]!; } else { return _getPen(_KnownColor.transparent); } @@ -3064,7 +3064,7 @@ class PdfPens { /// ``` static PdfPen get turquoise { if (_pens.containsKey(_KnownColor.turquoise)) { - return _pens[_KnownColor.turquoise]; + return _pens[_KnownColor.turquoise]!; } else { return _getPen(_KnownColor.turquoise); } @@ -3087,7 +3087,7 @@ class PdfPens { /// ``` static PdfPen get violet { if (_pens.containsKey(_KnownColor.violet)) { - return _pens[_KnownColor.violet]; + return _pens[_KnownColor.violet]!; } else { return _getPen(_KnownColor.violet); } @@ -3110,7 +3110,7 @@ class PdfPens { /// ``` static PdfPen get wheat { if (_pens.containsKey(_KnownColor.wheat)) { - return _pens[_KnownColor.wheat]; + return _pens[_KnownColor.wheat]!; } else { return _getPen(_KnownColor.wheat); } @@ -3133,7 +3133,7 @@ class PdfPens { /// ``` static PdfPen get white { if (_pens.containsKey(_KnownColor.white)) { - return _pens[_KnownColor.white]; + return _pens[_KnownColor.white]!; } else { return _getPen(_KnownColor.white); } @@ -3154,7 +3154,7 @@ class PdfPens { /// ``` static PdfPen get whiteSmoke { if (_pens.containsKey(_KnownColor.whiteSmoke)) { - return _pens[_KnownColor.whiteSmoke]; + return _pens[_KnownColor.whiteSmoke]!; } else { return _getPen(_KnownColor.whiteSmoke); } @@ -3177,7 +3177,7 @@ class PdfPens { /// ``` static PdfPen get yellow { if (_pens.containsKey(_KnownColor.yellow)) { - return _pens[_KnownColor.yellow]; + return _pens[_KnownColor.yellow]!; } else { return _getPen(_KnownColor.yellow); } @@ -3198,7 +3198,7 @@ class PdfPens { /// ``` static PdfPen get yellowGreen { if (_pens.containsKey(_KnownColor.yellowGreen)) { - return _pens[_KnownColor.yellowGreen]; + return _pens[_KnownColor.yellowGreen]!; } else { return _getPen(_KnownColor.yellowGreen); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_resources.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_resources.dart index 018a98540..ffd5cecd5 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_resources.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_resources.dart @@ -3,19 +3,29 @@ part of pdf; class _PdfResources extends _PdfDictionary { //Constructor /// Initializes a new instance of the [_PdfResources] class. - _PdfResources([_PdfDictionary baseDictionary]) : super(baseDictionary); + _PdfResources([_PdfDictionary? baseDictionary]) : super(baseDictionary); //Fields - Map<_IPdfPrimitive, _PdfName> _resourceNames; + Map<_IPdfPrimitive?, _PdfName?>? _resourceNames; final _PdfDictionary _properties = _PdfDictionary(); //Properties - Map<_IPdfPrimitive, _PdfName> get _names => _getNames(); + Map<_IPdfPrimitive?, _PdfName?>? get _names => _getNames(); //Implementation void _requireProcset(String procSetName) { - ArgumentError.checkNotNull(procSetName); - _PdfArray procSets = this[_DictionaryProperties.procSet] as _PdfArray; + _IPdfPrimitive? primitive = this[_DictionaryProperties.procSet]; + _PdfArray? procSets; + if (primitive != null) { + if (primitive is _PdfReferenceHolder) { + primitive = primitive.object; + if (primitive != null && primitive is _PdfArray) { + procSets = primitive; + } + } else if (primitive is _PdfArray) { + procSets = primitive; + } + } if (procSets == null) { procSets = _PdfArray(); this[_DictionaryProperties.procSet] = procSets; @@ -27,16 +37,15 @@ class _PdfResources extends _PdfDictionary { } _PdfName _getName(_IPdfWrapper resource) { - ArgumentError.checkNotNull(resource); - final _IPdfPrimitive primitive = resource._element; - _PdfName name; - if (_names.containsKey(primitive)) { - name = _names[primitive]; + final _IPdfPrimitive? primitive = resource._element; + _PdfName? name; + if (_names!.containsKey(primitive)) { + name = _names![primitive]; } if (name == null) { final String sName = _globallyUniqueIdentifier; name = _PdfName(sName); - _names[primitive] = name; + _names![primitive] = name; if (resource is PdfFont || resource is PdfTemplate || resource is PdfImage || @@ -47,33 +56,33 @@ class _PdfResources extends _PdfDictionary { return name; } - void _add(_IPdfWrapper resource, _PdfName name) { + void _add(_IPdfWrapper? resource, _PdfName name) { if (resource is PdfFont) { - _PdfDictionary dictionary; - final _IPdfPrimitive fonts = this[_PdfName(_DictionaryProperties.font)]; + _PdfDictionary? dictionary; + final _IPdfPrimitive? fonts = this[_PdfName(_DictionaryProperties.font)]; if (fonts != null) { if (fonts is _PdfDictionary) { dictionary = fonts; } else if (fonts is _PdfReferenceHolder) { - dictionary = _PdfCrossTable._dereference(fonts); + dictionary = _PdfCrossTable._dereference(fonts) as _PdfDictionary?; } } else { dictionary = _PdfDictionary(); this[_PdfName(_DictionaryProperties.font)] = dictionary; } - dictionary[name] = _PdfReferenceHolder(resource._element); + dictionary![name] = _PdfReferenceHolder(resource._element); } else if (resource is _PdfTransparency) { - final _IPdfPrimitive savable = resource._element; + final _IPdfPrimitive? savable = resource._element; if (savable != null) { - _PdfDictionary transDic; + _PdfDictionary? transDic; if (containsKey(_DictionaryProperties.extGState)) { - final _IPdfPrimitive primitive = + final _IPdfPrimitive? primitive = this[_DictionaryProperties.extGState]; if (primitive is _PdfDictionary) { transDic = primitive; } else if (primitive is _PdfReferenceHolder) { final _PdfReferenceHolder holder = primitive; - transDic = holder.object as _PdfDictionary; + transDic = holder.object as _PdfDictionary?; } } if (transDic == null) { @@ -83,38 +92,39 @@ class _PdfResources extends _PdfDictionary { transDic[name] = _PdfReferenceHolder(savable); } } else { - _PdfDictionary xObjects; - xObjects = this[_PdfName(_DictionaryProperties.xObject)]; + _PdfDictionary? xObjects; + xObjects = + this[_PdfName(_DictionaryProperties.xObject)] as _PdfDictionary?; if (xObjects == null) { xObjects = _PdfDictionary(); this[_PdfName(_DictionaryProperties.xObject)] = xObjects; } - xObjects[name] = _PdfReferenceHolder(resource._element); + xObjects[name] = _PdfReferenceHolder(resource!._element); } } - Map<_IPdfPrimitive, _PdfName> _getNames() { - _resourceNames ??= <_IPdfPrimitive, _PdfName>{}; - final _IPdfPrimitive fonts = this[_DictionaryProperties.font]; + Map<_IPdfPrimitive?, _PdfName?>? _getNames() { + _resourceNames ??= <_IPdfPrimitive?, _PdfName?>{}; + final _IPdfPrimitive? fonts = this[_DictionaryProperties.font]; if (fonts != null) { - _PdfDictionary dictionary; + _PdfDictionary? dictionary; if (fonts is _PdfDictionary) { dictionary = fonts; } else if (fonts is _PdfReferenceHolder) { - dictionary = _PdfCrossTable._dereference(fonts) as _PdfDictionary; + dictionary = _PdfCrossTable._dereference(fonts) as _PdfDictionary?; } if (dictionary != null) { - dictionary._items.forEach( - (_PdfName name, _IPdfPrimitive value) => _addName(name, value)); + dictionary._items!.forEach( + (_PdfName? name, _IPdfPrimitive? value) => _addName(name, value)); } } return _resourceNames; } - void _addName(_PdfName name, _IPdfPrimitive value) { - final _IPdfPrimitive primitive = _PdfCrossTable._dereference(value); - if (!_resourceNames.containsValue(name)) { - _resourceNames[primitive] = name; + void _addName(_PdfName? name, _IPdfPrimitive? value) { + final _IPdfPrimitive? primitive = _PdfCrossTable._dereference(value); + if (!_resourceNames!.containsValue(name)) { + _resourceNames![primitive] = name; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_transformation_matrix.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_transformation_matrix.dart index 041ad1954..eab5c945b 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_transformation_matrix.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_transformation_matrix.dart @@ -9,7 +9,7 @@ class _PdfTransformationMatrix { } //Fields - _Matrix _matrix; + late _Matrix _matrix; //Implementation void _translate(double offsetX, double offsetY) { @@ -55,20 +55,16 @@ class _PdfTransformationMatrix { class _Matrix { // Constructor ///Initializes a new instance of the [_Matrix] class as the - _Matrix([List elements]) { - if (elements == null) { - _elements = List(6); - } else { - _elements = elements; - } + _Matrix(List elements) { + _elements = elements; } //Fields - List _elements; + late List _elements; // Properties - double get _offsetX => _elements[4]; - double get _offsetY => _elements[5]; + double? get _offsetX => _elements[4]; + double? get _offsetY => _elements[5]; // Implementation void _translate(double offsetX, double offsetY) { @@ -80,8 +76,8 @@ class _Matrix { _Point _transform(_Point point) { final double x = point.x; final double y = point.y; - final double x2 = x * _elements[0] + y * _elements[2] + _offsetX; - final double y2 = x * _elements[1] + y * _elements[3] + _offsetY; + final double x2 = x * _elements[0] + y * _elements[2] + _offsetX!; + final double y2 = x * _elements[1] + y * _elements[3] + _offsetY!; return _Point(x2, y2); } @@ -91,7 +87,7 @@ class _Matrix { } void _multiply(_Matrix matrix) { - final List tempMatrix = List(6); + final List tempMatrix = List.filled(6, 0, growable: true); tempMatrix[0] = (_elements[0] * matrix._elements[0]) + (_elements[1] * matrix._elements[2]); tempMatrix[1] = (_elements[0] * matrix._elements[1]) + @@ -100,10 +96,10 @@ class _Matrix { (_elements[3] * matrix._elements[2]); tempMatrix[3] = (_elements[2] * matrix._elements[1]) + (_elements[3] * matrix._elements[3]); - tempMatrix[4] = (_offsetX * matrix._elements[0]) + - (_offsetY * matrix._elements[2] + matrix._offsetX); - tempMatrix[5] = (_offsetX * matrix._elements[1]) + - (_offsetY * matrix._elements[3] + matrix._offsetY); + tempMatrix[4] = (_offsetX! * matrix._elements[0]) + + (_offsetY! * matrix._elements[2] + matrix._offsetX!); + tempMatrix[5] = (_offsetX! * matrix._elements[1]) + + (_offsetY! * matrix._elements[3] + matrix._offsetY!); for (int i = 0; i < tempMatrix.length; i++) { _elements[i] = tempMatrix[i]; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_transparency.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_transparency.dart index 25ba7168c..f8dd5f938 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_transparency.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_transparency.dart @@ -20,25 +20,25 @@ class _PdfTransparency implements _IPdfWrapper { fill = 1; mode = (mode != PdfBlendMode.normal) ? PdfBlendMode.normal : mode; } - _dictionary[_DictionaryProperties.stroke] = _PdfNumber(stroke); - _dictionary[_DictionaryProperties.fill] = _PdfNumber(fill); - _dictionary[_DictionaryProperties.bm] = _PdfName(_getBlendMode(mode)); + _dictionary![_DictionaryProperties.stroke] = _PdfNumber(stroke); + _dictionary![_DictionaryProperties.fill] = _PdfNumber(fill); + _dictionary![_DictionaryProperties.bm] = _PdfName(_getBlendMode(mode)); } //Fields - _PdfDictionary _dictionary; + _PdfDictionary? _dictionary; //Properties - double get stroke => _getNumber(_DictionaryProperties.stroke); - double get fill => _getNumber(_DictionaryProperties.fill); + double? get stroke => _getNumber(_DictionaryProperties.stroke); + double? get fill => _getNumber(_DictionaryProperties.fill); //Implementation - double _getNumber(String keyName) { - double result = 0; - if (_dictionary.containsKey(keyName) && - _dictionary[keyName] is _PdfNumber) { - final _PdfNumber numb = _dictionary[keyName]; - result = numb.value; + double? _getNumber(String keyName) { + double? result = 0; + if (_dictionary!.containsKey(keyName) && + _dictionary![keyName] is _PdfNumber) { + final _PdfNumber numb = _dictionary![keyName] as _PdfNumber; + result = numb.value as double?; } return result; } @@ -82,10 +82,10 @@ class _PdfTransparency implements _IPdfWrapper { //_IPdfWrapper elements @override - _IPdfPrimitive get _element => _dictionary; + _IPdfPrimitive? get _element => _dictionary; @override // ignore: unused_element - set _element(_IPdfPrimitive value) { - _dictionary = value; + set _element(_IPdfPrimitive? value) { + _dictionary = value as _PdfDictionary?; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/big_endian_writer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/big_endian_writer.dart index a85b42b83..49282f303 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/big_endian_writer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/big_endian_writer.dart @@ -4,29 +4,29 @@ class _BigEndianWriter { //Constructor _BigEndianWriter(int capacity) { _bufferLength = capacity; - _buffer = List(capacity); + _buffer = List.filled(capacity, 0, growable: true); for (int i = 0; i < capacity; i++) { - _buffer[i] = 0; + _buffer![i] = 0; } } //Fields - int _bufferLength; - List _buffer; - int _internalPosition; + late int _bufferLength; + List? _buffer; + int? _internalPosition; //Properties - List get _data { - if (_buffer.length < _bufferLength) { - final int length = _bufferLength - _buffer.length; + List? get _data { + if (_buffer!.length < _bufferLength) { + final int length = _bufferLength - _buffer!.length; for (int i = 0; i < length; i++) { - _buffer.add(0); + _buffer!.add(0); } } return _buffer; } - int get _position { + int? get _position { _internalPosition ??= 0; return _internalPosition; } @@ -69,7 +69,6 @@ class _BigEndianWriter { } void _writeString(String value) { - ArgumentError.checkNotNull(value); final List bytes = []; for (int i = 0; i < value.length; i++) { bytes.add(value.codeUnitAt(i)); @@ -82,12 +81,11 @@ class _BigEndianWriter { } void _flush(List buff) { - ArgumentError.checkNotNull(buff); - int position = _position; + int? position = _position; for (int i = 0; i < buff.length; i++) { - _buffer[position] = buff[i]; + _buffer![position!] = buff[i]; position++; } - _internalPosition += buff.length; + _internalPosition = _internalPosition! + buff.length; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/cross_table.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/cross_table.dart index 847a59010..90d19fa20 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/cross_table.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/cross_table.dart @@ -2,53 +2,53 @@ part of pdf; class _CrossTable { //Constructor - _CrossTable(List data, _PdfCrossTable crossTable) { + _CrossTable(List? data, _PdfCrossTable crossTable) { if (data == null || data.isEmpty) { ArgumentError.value(data, 'PDF data', 'PDF data cannot be null or empty'); } - _data = data; + _data = data!; _crossTable = crossTable; _initialize(); } //Fields - List _data; - _PdfCrossTable _crossTable; - _PdfReader _reader; - _PdfParser _parser; - Map _objects; - Map<_PdfStream, _PdfParser> _readersTable; - Map _archives; + late List _data; + late _PdfCrossTable _crossTable; + _PdfReader? _reader; + _PdfParser? _parser; + late Map _objects; + late Map<_PdfStream, _PdfParser> _readersTable; + late Map _archives; int _startCrossReference = 0; bool validateSyntax = false; - _PdfDictionary _trailer; + _PdfDictionary? _trailer; bool _isStructureAltered = false; int _whiteSpace = 0; int _initialNumberOfSubsection = 0; int _initialSubsectionCount = 0; int _totalNumberOfSubsection = 0; - int _generationNumber; - Map> _allTables; - _PdfReferenceHolder _documentCatalog; - _PdfEncryptor _encryptor; + int? _generationNumber; + late Map> _allTables; + _PdfReferenceHolder? _documentCatalog; + _PdfEncryptor? _encryptor; //Properties - _ObjectInformation operator [](int key) => _returnValue(key); + _ObjectInformation? operator [](int? key) => _returnValue(key); _PdfReader get reader { _reader ??= _PdfReader(_data); - return _reader; + return _reader!; } _PdfParser get parser { _parser ??= _PdfParser(this, reader, _crossTable); - return _parser; + return _parser!; } - _PdfReferenceHolder get documentCatalog { + _PdfReferenceHolder? get documentCatalog { if (_documentCatalog == null) { - final _PdfDictionary trailer = _trailer; - final _IPdfPrimitive obj = trailer[_DictionaryProperties.root]; + final _PdfDictionary trailer = _trailer!; + final _IPdfPrimitive? obj = trailer[_DictionaryProperties.root]; if (obj is _PdfReferenceHolder) { _documentCatalog = obj; } else { @@ -58,13 +58,14 @@ class _CrossTable { return _documentCatalog; } - _PdfEncryptor get encryptor { + _PdfEncryptor? get encryptor { return _encryptor; } - set encryptor(_PdfEncryptor value) { - ArgumentError.checkNotNull(value); - _encryptor = value; + set encryptor(_PdfEncryptor? value) { + if (value != null) { + _encryptor = value; + } } //Implementation @@ -84,14 +85,14 @@ class _CrossTable { reader.position = startingOffset; reader._skipWhiteSpace(); _whiteSpace = reader.position; - int position = reader._seekEnd(); + int position = reader._seekEnd()!; checkStartXRef(); reader.position = position; final int endPosition = reader._searchBack(_Operators.endOfFileMarker); if (endPosition != -1) { if (position != endPosition + 5) { reader.position = endPosition + 5; - final String token = reader._getNextToken(); + final String token = reader._getNextToken()!; if (token.isNotEmpty && token.codeUnitAt(0) != 0 && token[0] != '0') { reader.position = 0; final List buffer = reader._readBytes(endPosition + 5); @@ -111,7 +112,7 @@ class _CrossTable { parser._setOffset(position); position = parser.startCrossReference(); _startCrossReference = position; - _parser._setOffset(position); + _parser!._setOffset(position); if (_whiteSpace != 0) { final int crossReferencePosition = reader._searchForward(_Operators.crossReference); @@ -142,8 +143,8 @@ class _CrossTable { if (!tempString.contains(_Operators.crossReference) && !tempString.contains(_Operators.obj) && !isForwardSearch) { - if (position > reader.length) { - position = reader.length; + if (position > reader.length!) { + position = reader.length!; reader.position = position; position = reader._searchBack(_Operators.startCrossReference); } @@ -157,27 +158,28 @@ class _CrossTable { try { final Map tempResult = parser._parseCrossReferenceTable(_objects, this); - _trailer = tempResult['object'] as _PdfDictionary; + _trailer = tempResult['object'] as _PdfDictionary?; _objects = tempResult['objects']; } catch (e) { throw ArgumentError.value(_trailer, 'Invalid cross reference table.'); } - _PdfDictionary trailer = _trailer; + _PdfDictionary trailer = _trailer!; while (trailer.containsKey(_DictionaryProperties.prev)) { if (_whiteSpace != 0) { - (trailer[_DictionaryProperties.prev] as _PdfNumber).value += - _whiteSpace; + final _PdfNumber number = + trailer[_DictionaryProperties.prev] as _PdfNumber; + number.value = number.value! + _whiteSpace; _isStructureAltered = true; } position = - (trailer[_DictionaryProperties.prev] as _PdfNumber).value.toInt(); - final _PdfReader tokenReader = _PdfReader(_reader._streamReader._data); + (trailer[_DictionaryProperties.prev] as _PdfNumber).value!.toInt(); + final _PdfReader tokenReader = _PdfReader(_reader!._streamReader._data); tokenReader.position = position; - String token = tokenReader._getNextToken(); + String? token = tokenReader._getNextToken(); if (token != _DictionaryProperties.crossReference) { token = tokenReader._getNextToken(); //check the coditon for valid object number - final int number = int.tryParse(token); + final int? number = int.tryParse(token!); if (number != null && number >= 0 && number <= 9) { token = tokenReader._getNextToken(); if (token == _DictionaryProperties.obj) { @@ -199,10 +201,10 @@ class _CrossTable { trailer = tempResults['object'] as _PdfDictionary; _objects = tempResults['objects']; if (trailer.containsKey(_DictionaryProperties.size) && - _trailer.containsKey(_DictionaryProperties.size)) { - if ((trailer[_DictionaryProperties.size] as _PdfNumber).value > - (_trailer[_DictionaryProperties.size] as _PdfNumber).value) { - (_trailer[_DictionaryProperties.size] as _PdfNumber).value = + _trailer!.containsKey(_DictionaryProperties.size)) { + if ((trailer[_DictionaryProperties.size] as _PdfNumber).value! > + (_trailer![_DictionaryProperties.size] as _PdfNumber).value!) { + (_trailer![_DictionaryProperties.size] as _PdfNumber).value = (trailer[_DictionaryProperties.size] as _PdfNumber).value; } } @@ -213,9 +215,9 @@ class _CrossTable { objKey = _objects.keys.toList(); for (int i = 0; i < objKey.length; i++) { final int key = objKey[i]; - final _ObjectInformation info = _objects[key]; + final _ObjectInformation info = _objects[key]!; _objects[key] = - _ObjectInformation(info._offset + _whiteSpace, null, this); + _ObjectInformation(info._offset! + _whiteSpace, null, this); } _isStructureAltered = true; } else if (_whiteSpace != 0 && _whiteSpace > 0 && !_isStructureAltered) { @@ -225,29 +227,29 @@ class _CrossTable { } } - _ObjectInformation _returnValue(int key) { - return _objects.containsKey(key) ? _objects[key] : null; + _ObjectInformation? _returnValue(int? key) { + return _objects.containsKey(key) ? _objects[key!] : null; } - _IPdfPrimitive _getObject(_IPdfPrimitive pointer) { + _IPdfPrimitive? _getObject(_IPdfPrimitive? pointer) { if (pointer == null) { throw ArgumentError.value(pointer, 'pointer'); } if (pointer is _PdfReference) { - _IPdfPrimitive obj; + _IPdfPrimitive? obj; final _PdfReference reference = pointer; - final _ObjectInformation oi = this[reference._objNum]; + final _ObjectInformation? oi = this[reference._objNum]; if (oi == null) { return _PdfNull(); } - final _PdfParser parser = oi.parser; - final int position = oi.offset; + final _PdfParser? parser = oi.parser; + final int? position = oi.offset; if (oi._obj != null) { obj = oi._obj; } else if (oi._archive == null) { - obj = parser._parseOffset(position); + obj = parser!._parseOffset(position!); } else { - obj = _getObjectFromPosition(parser, position); + obj = _getObjectFromPosition(parser!, position!); } oi._obj = obj; return obj; @@ -256,19 +258,19 @@ class _CrossTable { } } - _IPdfPrimitive _getObjectFromPosition(_PdfParser parser, int position) { + _IPdfPrimitive? _getObjectFromPosition(_PdfParser parser, int position) { parser._startFrom(position); return parser._simple(); } - Map _parseNewTable( - _PdfStream stream, Map objects) { + Map? _parseNewTable( + _PdfStream? stream, Map? objects) { if (stream == null) { throw ArgumentError.value(stream, 'Invalid format'); } stream._decompress(); final List<_SubSection> subSections = _getSections(stream); - int ssIndex = 0; + int? ssIndex = 0; for (int i = 0; i < subSections.length; i++) { final _SubSection ss = subSections[i]; final Map result = @@ -282,21 +284,19 @@ class _CrossTable { Map _parseWithHashTable( _PdfStream stream, _SubSection subsection, - Map table, - int startIndex) { - int index = startIndex; - final _IPdfPrimitive entry = _getObject(stream[_DictionaryProperties.w]); + Map? table, + int? startIndex) { + int? index = startIndex; + final _IPdfPrimitive? entry = _getObject(stream[_DictionaryProperties.w]); if (entry is _PdfArray) { final int fields = entry.count; final List format = List.filled(fields, 0, growable: true); for (int i = 0; i < fields; ++i) { final _PdfNumber formatNumber = entry[i] as _PdfNumber; - if (formatNumber != null) { - format[i] = formatNumber.value.toInt(); - } + format[i] = formatNumber.value!.toInt(); } final List reference = List.filled(fields, 0, growable: true); - final List buf = stream._dataStream; + final List? buf = stream._dataStream; for (int i = 0; i < subsection.count; ++i) { for (int j = 0; j < fields; ++j) { int field = 0; @@ -309,12 +309,13 @@ class _CrossTable { } for (int k = 0; k < format[j]; ++k) { field <<= 8; - field += buf[index++]; + field = field + buf![index!]; + index += 1; } reference[j] = field; } int offset = 0; - _ArchiveInformation ai; + _ArchiveInformation? ai; if (reference[0] == _ObjectType.normal.index) { if (_whiteSpace != 0) { offset = reference[1] + _whiteSpace; @@ -325,14 +326,14 @@ class _CrossTable { ai = _ArchiveInformation(reference[1], reference[2], _retrieveArchive); } - _ObjectInformation oi; + _ObjectInformation? oi; // NOTE: do not store removed objects. if (reference[0] != _ObjectType.free.index) { oi = _ObjectInformation(offset, ai, this); } if (oi != null) { final int objectOffset = subsection.startNumber + i; - if (!table.containsKey(objectOffset)) { + if (!table!.containsKey(objectOffset)) { table[objectOffset] = oi; } _addTables(objectOffset, oi); @@ -343,18 +344,18 @@ class _CrossTable { } _PdfStream _retrieveArchive(int archiveNumber) { - _PdfStream archive; + _PdfStream? archive; if (_archives.containsKey(archiveNumber)) { archive = _archives[archiveNumber]; } if (archive == null) { - final _ObjectInformation oi = this[archiveNumber]; - final _PdfParser parser = oi.parser; - archive = parser._parseOffset(oi._offset) as _PdfStream; - if (encryptor != null && !encryptor._encryptOnlyAttachment) { - archive.decrypt(encryptor, archiveNumber); + final _ObjectInformation oi = this[archiveNumber]!; + final _PdfParser parser = oi.parser!; + archive = parser._parseOffset(oi._offset!) as _PdfStream?; + if (encryptor != null && !encryptor!._encryptOnlyAttachment!) { + archive!.decrypt(encryptor!, archiveNumber); } - archive._decompress(); + archive!._decompress(); _archives[archiveNumber] = archive; } return archive; @@ -364,19 +365,19 @@ class _CrossTable { final List<_SubSection> subSections = <_SubSection>[]; int count = 0; if (stream.containsKey(_DictionaryProperties.size)) { - final _IPdfPrimitive primitive = stream[_DictionaryProperties.size]; + final _IPdfPrimitive? primitive = stream[_DictionaryProperties.size]; if (primitive is _PdfNumber) { - count = primitive.value.toInt(); + count = primitive.value!.toInt(); } } if (count == 0) { throw ArgumentError.value(count, 'Invalid Format'); } - final _IPdfPrimitive obj = stream[_DictionaryProperties.index]; + final _IPdfPrimitive? obj = stream[_DictionaryProperties.index]; if (obj == null) { subSections.add(_SubSection(count)); } else { - final _IPdfPrimitive primitive = _getObject(obj); + final _IPdfPrimitive? primitive = _getObject(obj); if (primitive != null && primitive is _PdfArray) { final _PdfArray indices = primitive; if ((indices.count & 1) != 0) { @@ -384,9 +385,9 @@ class _CrossTable { } for (int i = 0; i < indices.count; ++i) { int n = 0, c = 0; - n = (indices[i] as _PdfNumber).value.toInt(); + n = (indices[i] as _PdfNumber).value!.toInt(); ++i; - c = (indices[i] as _PdfNumber).value.toInt(); + c = (indices[i] as _PdfNumber).value!.toInt(); subSections.add(_SubSection(c, n)); } } @@ -394,21 +395,22 @@ class _CrossTable { return subSections; } - void _parseSubsection(_PdfParser parser, Map table) { + void _parseSubsection( + _PdfParser parser, Map? table) { // Read the initial number of the subsection. _PdfNumber integer = parser._simple() as _PdfNumber; - _initialNumberOfSubsection = integer.value.toInt(); + _initialNumberOfSubsection = integer.value!.toInt(); // Read the total number of subsection. integer = parser._simple() as _PdfNumber; - _totalNumberOfSubsection = integer.value.toInt(); + _totalNumberOfSubsection = integer.value!.toInt(); _initialSubsectionCount = _initialNumberOfSubsection; for (int i = 0; i < _totalNumberOfSubsection; ++i) { integer = parser._simple() as _PdfNumber; - final int offset = integer.value.toInt(); + final int offset = integer.value!.toInt(); integer = parser._simple() as _PdfNumber; - final int genNum = integer.value.toInt(); + final int genNum = integer.value!.toInt(); final String flag = parser._getObjectFlag(); if (flag == 'n') { final _ObjectInformation oi = _ObjectInformation(offset, null, this); @@ -418,7 +420,7 @@ class _CrossTable { } else { objectOffset = _initialSubsectionCount + i; } - if (!table.containsKey(objectOffset)) { + if (!table!.containsKey(objectOffset)) { table[objectOffset] = oi; } _addTables(objectOffset, oi); @@ -437,7 +439,7 @@ class _CrossTable { void _addTables(int objectOffset, _ObjectInformation oi) { if (_allTables.containsKey(objectOffset)) { - _allTables[objectOffset].add(oi); + _allTables[objectOffset]!.add(oi); } else { _allTables[objectOffset] = <_ObjectInformation>[oi]; } @@ -459,16 +461,16 @@ class _CrossTable { void checkStartXRef() { const int maxSize = 1024; - int pos = reader.length - maxSize; + int pos = reader.length! - maxSize; if (pos < 1) { pos = 1; } - List data = List.filled(maxSize, 0); + List? data = List.filled(maxSize, 0); while (pos > 0) { reader.position = pos; final Map result = reader._copyBytes(data, 0, maxSize); data = result['buffer']; - final String start = String.fromCharCodes(data); + final String start = String.fromCharCodes(data!); final int index = start.lastIndexOf('startxref'); if (index >= 0) { reader.position = index; @@ -478,17 +480,17 @@ class _CrossTable { } } - _PdfParser retrieveParser(_ArchiveInformation archive) { + _PdfParser? retrieveParser(_ArchiveInformation? archive) { if (archive == null) { return _parser; } else { - final _PdfStream stream = archive.archive; - _PdfParser parser; + final _PdfStream? stream = archive.archive; + _PdfParser? parser; if (_readersTable.containsKey(stream)) { parser = _readersTable[stream]; } if (parser == null) { - final _PdfReader reader = _PdfReader(stream._dataStream); + final _PdfReader reader = _PdfReader(stream!._dataStream); parser = _PdfParser(this, reader, _crossTable); _readersTable[stream] = parser; } @@ -500,59 +502,59 @@ class _CrossTable { class _ObjectInformation { //Constructor _ObjectInformation( - int offset, _ArchiveInformation arciveInfo, _CrossTable crossTable) { + int offset, _ArchiveInformation? arciveInfo, _CrossTable? crossTable) { _offset = offset; _archive = arciveInfo; _crossTable = crossTable; } //Fields - _ArchiveInformation _archive; - _PdfParser _parser; - int _offset; - _CrossTable _crossTable; - _IPdfPrimitive _obj; + _ArchiveInformation? _archive; + _PdfParser? _parser; + int? _offset; + _CrossTable? _crossTable; + _IPdfPrimitive? _obj; //Properties - _PdfParser get parser { - _parser ??= _crossTable.retrieveParser(_archive); + _PdfParser? get parser { + _parser ??= _crossTable!.retrieveParser(_archive); return _parser; } - int get offset { + int? get offset { if (_offset == 0) { - final _PdfParser parser = this.parser; + final _PdfParser parser = this.parser!; parser._startFrom(0); int pairs = 0; // Read indices. if (_archive != null) { - final _PdfNumber archieveNumber = - _archive.archive[_DictionaryProperties.n] as _PdfNumber; + final _PdfNumber? archieveNumber = + _archive!.archive[_DictionaryProperties.n] as _PdfNumber?; if (archieveNumber != null) { - pairs = archieveNumber.value.toInt(); + pairs = archieveNumber.value!.toInt(); } final List indices = List.filled(pairs * 2, 0, growable: true); for (int i = 0; i < pairs; ++i) { - _PdfNumber obj = parser._simple() as _PdfNumber; + _PdfNumber? obj = parser._simple() as _PdfNumber?; if (obj != null) { - indices[i * 2] = obj.value.toInt(); + indices[i * 2] = obj.value!.toInt(); } - obj = parser._simple() as _PdfNumber; + obj = parser._simple() as _PdfNumber?; if (obj != null) { - indices[i * 2 + 1] = obj.value.toInt(); + indices[i * 2 + 1] = obj.value!.toInt(); } } - final int index = _archive._index; + final int index = _archive!._index; if (index * 2 >= indices.length) { throw ArgumentError.value( - _archive._archiveNumber, 'Missing indexes in archive'); + _archive!._archiveNumber, 'Missing indexes in archive'); } _offset = indices[index * 2 + 1]; final int first = - (_archive.archive[_DictionaryProperties.first] as _PdfNumber) - .value + (_archive!.archive[_DictionaryProperties.first] as _PdfNumber) + .value! .toInt(); - _offset += first; + _offset = _offset! + first; } } return _offset; @@ -568,15 +570,15 @@ class _ArchiveInformation { } //Fields - int _archiveNumber; - int _index; - _PdfStream _archive; - _GetArchive _getArchive; + late int _archiveNumber; + late int _index; + _PdfStream? _archive; + late _GetArchive _getArchive; //Properties _PdfStream get archive { _archive ??= _getArchive(_archiveNumber); - return _archive; + return _archive!; } } @@ -584,13 +586,11 @@ typedef _GetArchive = _PdfStream Function(int archiveNumber); class _SubSection { //constructor - _SubSection(int count, [int start]) { - if (count != null) { - this.count = count; - } + _SubSection(int count, [int? start]) { + this.count = count; startNumber = start ?? 0; } - int startNumber; - int count; + late int startNumber; + late int count; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/decode_big_endian.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/decode_big_endian.dart index 3ed816896..109cf25f8 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/decode_big_endian.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/decode_big_endian.dart @@ -1,6 +1,6 @@ part of pdf; -String _decodeBigEndian(List bytes, [int offset = 0, int length]) { +String _decodeBigEndian(List? bytes, [int offset = 0, int? length]) { final List codeUnits = _UtfbeDecoder(bytes, offset, length).decodeRemaining(); final List result = _convertCodeUnitsToCodePoints(codeUnits); @@ -8,34 +8,35 @@ String _decodeBigEndian(List bytes, [int offset = 0, int length]) { } List _convertCodeUnitsToCodePoints(List codeUnits, - [int offset = 0, int length]) { + [int offset = 0, int? length]) { final _UtfCodeUnitDecoder decoder = _UtfCodeUnitDecoder(_ByteRange(codeUnits, offset, length)); - final List codePoints = List(decoder.byteRange.remaining); + final List codePoints = + List.filled(decoder.byteRange.remaining, 0, growable: true); int i = 0; while (decoder.moveNext) { - codePoints[i++] = decoder._current; + codePoints[i++] = decoder._current!; } if (i == codePoints.length) { return codePoints; } else { - final List cpt = List(i); + final List cpt = List.filled(i, 0, growable: true); cpt.setRange(0, i, codePoints); return cpt; } } class _UtfbeDecoder { - _UtfbeDecoder(List encodedBytes, [int offset = 0, int length]) { - length = (length ?? encodedBytes.length - offset); + _UtfbeDecoder(List? encodedBytes, [int offset = 0, int? length]) { + length = (length ?? encodedBytes!.length - offset); byteRange = _ByteRange(encodedBytes, offset, length); if (isBeBom(encodedBytes, offset, length)) { byteRange.skip(2); } } - _ByteRange byteRange; + late _ByteRange byteRange; final int rcPoint = 0xfffd; - int _current; + int? _current; int get remaining => (byteRange.remaining + 1) ~/ 2; @@ -48,62 +49,58 @@ class _UtfbeDecoder { } if (remaining == 1) { byteRange.moveNext; - if (rcPoint != null) { - _current = rcPoint; - return true; - } else { - throw ArgumentError('Invalid byte.'); - } + _current = rcPoint; + return true; } _current = decode(); return true; } List decodeRemaining() { - final List codeunits = List(remaining); + final List codeunits = List.filled(remaining, 0, growable: true); var i = 0; while (moveNext) { - codeunits[i++] = _current; + codeunits[i++] = _current!; } if (i == codeunits.length) { return codeunits; } else { - final List tcu = List(i); + final List tcu = List.filled(i, 0, growable: true); tcu.setRange(0, i, codeunits); return tcu; } } - bool isBeBom(List encodedBytes, [int offset = 0, int length]) { - final int end = length != null ? offset + length : encodedBytes.length; + bool isBeBom(List? encodedBytes, [int offset = 0, int? length]) { + final int end = length != null ? offset + length : encodedBytes!.length; return (offset + 2) <= end && - encodedBytes[offset] == 0xfe && + encodedBytes![offset] == 0xfe && encodedBytes[offset + 1] == 0xff; } int decode() { byteRange.moveNext; - final int first = byteRange.current; + final int first = byteRange.current!; byteRange.moveNext; - final int next = byteRange.current; + final int next = byteRange.current!; return (first << 8) + next; } } class _ByteRange { - _ByteRange(List source, int offset, int length) { - length = (length ?? source.length - offset); + _ByteRange(List? source, int offset, int? length) { + length = (length ?? source!.length - offset); _source = source; _offset = offset - 1; _length = length; _end = offset + _length; } - List _source; - int _offset; - int _length; - int _end; + List? _source; + late int _offset; + late int _length; + late int _end; - int get current => _source[_offset]; + int? get current => _source![_offset]; bool get moveNext => ++_offset < _end; int get remaining => _end - _offset - 1; void skip([int count = 1]) { @@ -119,24 +116,20 @@ class _UtfCodeUnitDecoder { _UtfCodeUnitDecoder(this.byteRange); final _ByteRange byteRange; final int rcPoint = 0xfffd; - int _current; + int? _current; bool get moveNext { _current = null; if (!byteRange.moveNext) { return false; } - int value = byteRange.current; + int value = byteRange.current!; if (value < 0) { - if (rcPoint != null) { - _current = rcPoint; - } else { - throw ArgumentError('Invalid data'); - } + _current = rcPoint; } else if (value < 0xd800 || (value > 0xdfff && value <= 0xffff)) { _current = value; } else if (value < 0xdc00 && byteRange.moveNext) { - final int nextValue = byteRange.current; + final int nextValue = byteRange.current!; if (nextValue >= 0xdc00 && nextValue <= 0xdfff) { value = (value - 0xd800) << 10; value += 0x10000 + (nextValue - 0xdc00); @@ -145,16 +138,10 @@ class _UtfCodeUnitDecoder { if (nextValue >= 0xd800 && nextValue < 0xdc00) { byteRange.backup(); } - if (rcPoint != null) { - _current = rcPoint; - } else { - throw ArgumentError('Invalid data'); - } + _current = rcPoint; } - } else if (rcPoint != null) { - _current = rcPoint; } else { - throw ArgumentError('Invalid data'); + _current = rcPoint; } return true; } @@ -162,7 +149,8 @@ class _UtfCodeUnitDecoder { List _encodeBigEndian(String content) { final List codeUnits = _codePointsToCodeUnits(content.codeUnits); - final List encodedBytes = List(2 * codeUnits.length); + final List encodedBytes = + List.filled(2 * codeUnits.length, 0, growable: true); int i = 0; for (final int value in codeUnits) { encodedBytes[i++] = (value & 0xff00) >> 8; @@ -183,7 +171,7 @@ List _codePointsToCodeUnits(List codePoints) { eLength++; } } - final List buffer = List(eLength); + final List buffer = List.filled(eLength, 0, growable: true); int j = 0; for (int i = 0; i < codePoints.length; i++) { final int value = codePoints[i]; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/dictionary_properties.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/dictionary_properties.dart index 57e33dc3d..8e730304c 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/dictionary_properties.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/dictionary_properties.dart @@ -215,4 +215,51 @@ class _DictionaryProperties { static const String limits = 'Limits'; static const String ocgLock = 'Locked'; static const String af = 'AF'; + static const String fs = 'FS'; + static const String eff = 'EFF'; + static const String cryptFilter = 'CryptFilter'; + static const String efOpen = 'EFOpen'; + static const String dl = 'DL'; + static const String needAppearances = 'NeedAppearances'; + static const String dr = 'DR'; + static const String q = 'Q'; + static const String bc = 'BC'; + static const String bg = 'BG'; + static const String acroForm = 'AcroForm'; + static const String sig = 'Sig'; + static const String mk = 'MK'; + static const String da = 'DA'; + static const String maxLen = 'MaxLen'; + static const String tx = 'Tx'; + static const String widget = 'Widget'; + static const String fieldFlags = 'Ff'; + static const String tabs = 'Tabs'; + static const String tm = 'TM'; + static const String tu = 'TU'; + static const String dv = 'DV'; + static const String btn = 'Btn'; + static const String yes = 'Yes'; + static const String off = 'Off'; + static const String opt = 'Opt'; + static const String ch = 'Ch'; + static const String e = 'E'; + static const String x = 'X'; + static const String fo = 'Fo'; + static const String bl = 'Bl'; + static const String js = 'JS'; + static const String javaScript = 'JavaScript'; + static const String aa = 'AA'; + static const String submitForm = 'SubmitForm'; + static const String resetForm = 'ResetForm'; + static const String sigFlags = 'SigFlags'; + static const String docMDP = 'DocMDP'; + static const String transformMethod = 'TransformMethod'; + static const String subFilter = 'SubFilter'; + static const String reason = 'Reason'; + static const String location = 'Location'; + static const String contactInfo = 'ContactInfo'; + static const String byteRange = "ByteRange"; + static const String reference = 'Reference'; + static const String sigRef = 'SigRef'; + static const String data = 'Data'; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/object_info.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/object_info.dart index 859dc0bff..eae9ebb88 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/object_info.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/object_info.dart @@ -2,7 +2,7 @@ part of pdf; class _ObjectInfo { //Constructors - _ObjectInfo(_IPdfPrimitive obj, [_PdfReference reference]) { + _ObjectInfo(_IPdfPrimitive? obj, [_PdfReference? reference]) { if (obj == null) { ArgumentError.notNull('obj'); } else { @@ -15,21 +15,20 @@ class _ObjectInfo { } //Fields - _IPdfPrimitive _object; - _PdfReference _reference; - bool _isModified; + _IPdfPrimitive? _object; + _PdfReference? _reference; + late bool _isModified; //Properties - bool get _modified { + bool? get _modified { if (_object is _IPdfChangable) { - _isModified |= (_object as _IPdfChangable).changed; + _isModified |= (_object as _IPdfChangable).changed!; } return _isModified; } //Implementation void _setReference(_PdfReference reference) { - ArgumentError.checkNotNull(reference, 'reference'); if (_reference != null) { throw ArgumentError.value( _reference, 'The object has the reference bound to it.'); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_archive_stream.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_archive_stream.dart index 6cfbfccf0..01a19b1d6 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_archive_stream.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_archive_stream.dart @@ -2,68 +2,70 @@ part of pdf; class _PdfArchiveStream extends _PdfStream { // Constructor - _PdfArchiveStream(PdfDocument document) : super() { + _PdfArchiveStream(PdfDocument? document) : super() { ArgumentError.notNull('document'); _document = document; _objects = []; _objectWriter = _PdfWriter(_objects); - _objectWriter._document = _document; - _indices = {}; + _objectWriter!._document = _document; + _indices = {}; } // Fields - PdfDocument _document; - _IPdfWriter _objectWriter; - List _objects; - Map _indices; - _StreamWriter _writer; + PdfDocument? _document; + _IPdfWriter? _objectWriter; + late List _objects; + Map? _indices; + late _StreamWriter _writer; // Properties int get _objCount { if (_indices == null) { return 0; } else { - return _indices.length; + return _indices!.length; } } - int _getIndex(int objNumber) { - return _indices.values.toList().indexOf(objNumber); + int _getIndex(int? objNumber) { + return _indices!.values.toList().indexOf(objNumber); } // Implementation @override - void save(_IPdfWriter writer) { + void save(_IPdfWriter? writer) { final List data = []; _writer = _StreamWriter(data); _saveIndices(); - this[_DictionaryProperties.first] = _PdfNumber(_writer._position); + this[_DictionaryProperties.first] = _PdfNumber(_writer._position!); _saveObjects(); _dataStream = _writer._buffer; - this[_DictionaryProperties.n] = _PdfNumber(_indices.length); + this[_DictionaryProperties.n] = _PdfNumber(_indices!.length); this[_DictionaryProperties.type] = _PdfName('ObjStm'); super.save(writer); } void _saveIndices() { - for (final int position in _indices.keys) { - _writer = _StreamWriter(_writer._buffer); - _writer._write(_indices[position]); - _writer._write(_Operators.whiteSpace); - _writer._write(position); - _writer._write(_Operators.newLine); + for (final Object? position in _indices!.keys) { + if (position is int) { + _writer = _StreamWriter(_writer._buffer!); + _writer._write(_indices![position]); + _writer._write(_Operators.whiteSpace); + _writer._write(position); + _writer._write(_Operators.newLine); + } } } void _saveObjects() { - _writer._buffer.addAll(_objects); + _writer._buffer!.addAll(_objects); } void _saveObject(_IPdfPrimitive obj, _PdfReference reference) { - final int position = _objectWriter._position; - _indices[position] = reference._objNum; + final int? position = _objectWriter!._position; + _indices![position] = reference._objNum; obj.save(_objectWriter); - _objectWriter._write(_Operators.newLine); + _objectWriter!._write(_Operators.newLine); } } @@ -76,16 +78,16 @@ class _StreamWriter extends _IPdfWriter { } //Fields - List _buffer; + List? _buffer; @override void _write(dynamic data) { if (data is List) { for (int i = 0; i < data.length; i++) { - _buffer.add(data[i]); + _buffer!.add(data[i]); } - _length = _buffer.length; - _position = _buffer.length; + _length = _buffer!.length; + _position = _buffer!.length; } else if (data is String) { _write(utf8.encode(data)); } else if (data is int) { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_cross_table.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_cross_table.dart index 2c9180ef9..d04f6e96e 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_cross_table.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_cross_table.dart @@ -4,7 +4,7 @@ part of pdf; /// savingof a PDF document. class _PdfCrossTable { //Constructor - _PdfCrossTable([PdfDocument document, List data]) { + _PdfCrossTable([PdfDocument? document, List? data]) { if (document != null) { _document = document; _objNumbers = Queue<_PdfReference>(); @@ -17,7 +17,7 @@ class _PdfCrossTable { _isColorSpace = false; } _PdfCrossTable._fromCatalog(int tableCount, - _PdfDictionary encryptionDictionary, _PdfDictionary documentCatalog) { + _PdfDictionary? encryptionDictionary, _PdfDictionary? documentCatalog) { _storedCount = tableCount; _encryptorDictionary = encryptionDictionary; _documentCatalog = documentCatalog; @@ -25,25 +25,25 @@ class _PdfCrossTable { _isColorSpace = false; } //Fields - PdfDocument _pdfDocument; + PdfDocument? _pdfDocument; int _count = 0; - _PdfMainObjectCollection _items; - _PdfDictionary _trailer; - Map _objects = {}; - List _data; - _CrossTable _crossTable; - Queue<_PdfReference> _objNumbers; - _PdfDictionary _documentCatalog; + _PdfMainObjectCollection? _items; + _PdfDictionary? _trailer; + Map? _objects = {}; + List? _data; + _CrossTable? _crossTable; + late Queue<_PdfReference> _objNumbers; + _PdfDictionary? _documentCatalog; bool _isIndexOutOfRange = false; - _PdfDictionary _encryptorDictionary; - List<_ArchiveInfo> _archives; - _PdfArchiveStream _archive; + _PdfDictionary? _encryptorDictionary; + List<_ArchiveInfo>? _archives; + _PdfArchiveStream? _archive; double _maxGenNumIndex = 0; - int _storedCount; + late int _storedCount; bool _bForceNew = false; - Map<_PdfReference, _PdfReference> _mappedReferences; - List<_PdfReference> _prevRef; - bool _isColorSpace; + Map<_PdfReference, _PdfReference>? _mappedReferences; + List<_PdfReference?>? _prevRef; + late bool _isColorSpace; //Properties int get nextObjectNumber { @@ -53,33 +53,34 @@ class _PdfCrossTable { return count++; } - _PdfEncryptor get encryptor { - return _crossTable == null ? null : _crossTable.encryptor; + _PdfEncryptor? get encryptor { + return _crossTable == null ? null : _crossTable!.encryptor; } - set encryptor(_PdfEncryptor value) { - ArgumentError.checkNotNull('Encryptor'); - _crossTable.encryptor = value._clone(); + set encryptor(_PdfEncryptor? value) { + if (value != null) { + _crossTable!.encryptor = value._clone(); + } } - _PdfDictionary get documentCatalog { + _PdfDictionary? get documentCatalog { if (_documentCatalog == null && _crossTable != null) { _documentCatalog = - _dereference(_crossTable.documentCatalog) as _PdfDictionary; + _dereference(_crossTable!.documentCatalog) as _PdfDictionary?; } return _documentCatalog; } - _PdfDictionary get trailer { - _trailer ??= _crossTable == null ? _PdfStream() : _crossTable._trailer; + _PdfDictionary? get trailer { + _trailer ??= _crossTable == null ? _PdfStream() : _crossTable!._trailer; return _trailer; } - _PdfDictionary get encryptorDictionary { + _PdfDictionary? get encryptorDictionary { if (_encryptorDictionary == null && - trailer.containsKey(_DictionaryProperties.encrypt)) { - final _IPdfPrimitive primitive = - _dereference(trailer[_DictionaryProperties.encrypt]); + trailer!.containsKey(_DictionaryProperties.encrypt)) { + final _IPdfPrimitive? primitive = + _dereference(trailer![_DictionaryProperties.encrypt]); if (primitive is _PdfDictionary) { _encryptorDictionary = primitive; } @@ -89,17 +90,17 @@ class _PdfCrossTable { int get count { if (_count == 0) { - _IPdfPrimitive obj; - _PdfNumber tempCount; + _IPdfPrimitive? obj; + _PdfNumber? tempCount; if (_crossTable != null) { - obj = _crossTable._trailer[_DictionaryProperties.size]; + obj = _crossTable!._trailer![_DictionaryProperties.size]; } if (obj != null) { - tempCount = _dereference(obj) as _PdfNumber; + tempCount = _dereference(obj) as _PdfNumber?; } else { tempCount = _PdfNumber(0); } - _count = tempCount.value.toInt(); + _count = tempCount!.value!.toInt(); } return _count; } @@ -108,20 +109,20 @@ class _PdfCrossTable { _count = value; } - _PdfMainObjectCollection get _objectCollection => _pdfDocument._objects; - PdfDocument get _document => _pdfDocument; - set _document(PdfDocument document) { + _PdfMainObjectCollection? get _objectCollection => _pdfDocument!._objects; + PdfDocument? get _document => _pdfDocument; + set _document(PdfDocument? document) { if (document == null) { throw ArgumentError('Document'); } _pdfDocument = document; - _items = _pdfDocument._objects; + _items = _pdfDocument!._objects; } - List<_PdfReference> get _prevReference => + List<_PdfReference?>? get _prevReference => (_prevRef != null) ? _prevRef : _prevRef = <_PdfReference>[]; - set _prevReference(List<_PdfReference> value) { + set _prevReference(List<_PdfReference?>? value) { if (value != null) { _prevRef = value; } @@ -133,45 +134,44 @@ class _PdfCrossTable { } void _markTrailerReferences() { - trailer._items.forEach((_PdfName name, _IPdfPrimitive element) { + trailer!._items!.forEach((_PdfName? name, _IPdfPrimitive? element) { if (element is _PdfReferenceHolder) { final _PdfReferenceHolder rh = element; - if (!_document._objects.contains(rh.object)) { - _document._objects._add(rh.object); + if (!_document!._objects.contains(rh.object!)) { + _document!._objects._add(rh.object); } } }); } void _save(_PdfWriter writer) { - ArgumentError.checkNotNull(writer, 'writer'); _saveHead(writer); - _objects.clear(); + _objects!.clear(); if (_archives != null) { - _archives.clear(); + _archives!.clear(); } _archive = null; _markTrailerReferences(); _saveObjects(writer); final int saveCount = count; - if (_document._isLoadedDocument) { + if (_document!._isLoadedDocument) { writer._position = writer._length; } if (_isCrossReferenceStream(writer._document)) { _saveArchives(writer); } _registerObject(_PdfReference(0, -1), position: 0, isFreeType: true); - final int referencePosition = writer._position; + final int? referencePosition = writer._position; final int prevXRef = - _crossTable == null ? 0 : _crossTable._startCrossReference; + _crossTable == null ? 0 : _crossTable!._startCrossReference; if (_isCrossReferenceStream(writer._document)) { - _PdfReference xRefReference; + _PdfReference? xRefReference; final Map returnedValue = _prepareXRefStream( - prevXRef.toDouble(), referencePosition.toDouble(), xRefReference); - final _PdfStream xRefStream = returnedValue['xRefStream']; - xRefReference = returnedValue['reference']; + prevXRef.toDouble(), referencePosition!.toDouble(), xRefReference); + final _PdfStream xRefStream = returnedValue['xRefStream'] as _PdfStream; + xRefReference = returnedValue['reference'] as _PdfReference?; xRefStream._blockEncryption = true; - _doSaveObject(xRefStream, xRefReference, writer); + _doSaveObject(xRefStream, xRefReference!, writer); } else { writer._write(_Operators.crossReference); writer._write(_Operators.newLine); @@ -180,15 +180,15 @@ class _PdfCrossTable { } _saveEnd(writer, referencePosition); count = saveCount; - for (int i = 0; i < _objectCollection._count; i++) { - final _ObjectInfo objectInfo = _objectCollection[i]; - objectInfo._object.isSaving = false; + for (int i = 0; i < _objectCollection!._count; i++) { + final _ObjectInfo objectInfo = _objectCollection![i]; + objectInfo._object!.isSaving = false; } } void _saveHead(_PdfWriter writer) { writer._write('%PDF-'); - final String version = _generateFileVersion(writer._document); + final String version = _generateFileVersion(writer._document!); writer._write(version); writer._write(_Operators.newLine); writer._write([0x25, 0x83, 0x92, 0xfa, 0xfe]); @@ -205,7 +205,7 @@ class _PdfCrossTable { } void _saveObjects(_PdfWriter writer) { - final _PdfMainObjectCollection objectCollection = _objectCollection; + final _PdfMainObjectCollection objectCollection = _objectCollection!; if (_bForceNew) { count = 1; _mappedReferences = null; @@ -213,14 +213,12 @@ class _PdfCrossTable { _setSecurity(); for (int i = 0; i < objectCollection._count; i++) { final _ObjectInfo objInfo = objectCollection[i]; - if (objInfo._modified || _bForceNew) { - final _IPdfPrimitive obj = objInfo._object; - final _IPdfPrimitive reference = objInfo._reference; + if (objInfo._modified! || _bForceNew) { + final _IPdfPrimitive obj = objInfo._object!; + final _IPdfPrimitive? reference = objInfo._reference; if (reference == null) { final _PdfReference ref = _getReference(obj); - if (ref != null) { - objInfo._reference = ref; - } + objInfo._reference = ref; } _saveIndirectObject(obj, writer); } @@ -228,50 +226,57 @@ class _PdfCrossTable { } void _setSecurity() { - final PdfSecurity security = _document.security; - trailer.encrypt = false; + final PdfSecurity security = _document!.security; + trailer!.encrypt = false; if (security._encryptor.encrypt) { - _PdfDictionary securityDictionary = encryptorDictionary; + _PdfDictionary? securityDictionary = encryptorDictionary; if (securityDictionary == null) { securityDictionary = _PdfDictionary(); securityDictionary.encrypt = false; - _document._objects._add(securityDictionary); + _document!._objects._add(securityDictionary); securityDictionary.position = -1; } securityDictionary = security._encryptor._saveToDictionary(securityDictionary); - trailer[_DictionaryProperties.id] = security._encryptor.fileID; - trailer[_DictionaryProperties.encrypt] = + trailer![_DictionaryProperties.id] = security._encryptor.fileID; + trailer![_DictionaryProperties.encrypt] = _PdfReferenceHolder(securityDictionary); + } else if (!security._encryptor.encryptOnlyAttachment) { + if (trailer!.containsKey(_DictionaryProperties.encrypt)) { + trailer!.remove(_DictionaryProperties.encrypt); + } + if (trailer!.containsKey(_DictionaryProperties.id) && + !_document!.fileStructure._fileID) { + trailer!.remove(_DictionaryProperties.id); + } } } void _saveIndirectObject(_IPdfPrimitive object, _PdfWriter writer) { - ArgumentError.checkNotNull(object, 'object'); - ArgumentError.checkNotNull(writer, 'writer'); final _PdfReference reference = _getReference(object); if (object is _PdfCatalog) { - trailer[_DictionaryProperties.root] = reference; + trailer![_DictionaryProperties.root] = reference; //NOTE: This is needed to get PDF/A Conformance. if (_document != null && - _document._conformanceLevel != PdfConformanceLevel.none) { - trailer[_DictionaryProperties.id] = - _document.security._encryptor.fileID; + _document!._conformanceLevel != PdfConformanceLevel.none) { + trailer![_DictionaryProperties.id] = + _document!.security._encryptor.fileID; } } - _document._currentSavingObject = reference; + _document!._currentSavingObject = reference; bool archive = false; archive = (object is _PdfDictionary) ? object._archive : true; final bool allowedType = !((object is _PdfStream) || !(archive) || (object is _PdfCatalog)); bool sigFlag = false; if (object is _PdfDictionary && - _document.fileStructure.crossReferenceType == + _document!.fileStructure.crossReferenceType == PdfCrossReferenceType.crossReferenceStream) { final _PdfDictionary dictionary = object; if (dictionary.containsKey(_DictionaryProperties.type)) { - if (dictionary[_DictionaryProperties.type] as _PdfName == - _PdfName('Sig')) { + final _PdfName? name = + dictionary[_DictionaryProperties.type] as _PdfName?; + if (name != null && name._name! == 'Sig') { sigFlag = true; } } @@ -290,32 +295,34 @@ class _PdfCrossTable { } } - _PdfReference _getReference(_IPdfPrimitive object) { + _PdfReference _getReference(_IPdfPrimitive? object) { if (object is _PdfArchiveStream) { final _PdfReference r = _findArchiveReference(object); return r; } if (object is _PdfReferenceHolder) { - object = (object as _PdfReferenceHolder).object; - object.isSaving = true; + object = object.object; + if (_document != null && !_document!._isLoadedDocument) { + object!.isSaving = true; + } } if (object is _IPdfWrapper) { object = (object as _IPdfWrapper)._element; } dynamic reference; - bool wasNew = false; - if (object.isSaving) { - if (_items._count > 0 && - object.objectCollectionIndex > 0 && - _items._count > object.objectCollectionIndex - 1) { + bool? wasNew = false; + if (object!.isSaving!) { + if (_items!._count > 0 && + object.objectCollectionIndex! > 0 && + _items!._count > object.objectCollectionIndex! - 1) { final Map result = - _document._objects._getReference(object, wasNew); + _document!._objects._getReference(object, wasNew); wasNew = result['isNew']; reference = result['reference']; } } else { final Map result = - _document._objects._getReference(object, wasNew); + _document!._objects._getReference(object, wasNew); wasNew = result['isNew']; reference = result['reference']; } @@ -331,34 +338,36 @@ class _PdfCrossTable { if (_bForceNew) { if (reference == null) { int maxObj = - (_storedCount > 0) ? _storedCount++ : _document._objects._count; + (_storedCount > 0) ? _storedCount++ : _document!._objects._count; if (maxObj <= 0) { maxObj = -1; _storedCount = 2; } - while (_document._objects._mainObjectCollection.containsKey(maxObj)) { + while (_document!._objects._mainObjectCollection!.containsKey(maxObj)) { maxObj++; } reference = _PdfReference(maxObj, 0); if (wasNew) { - _document._objects._add(object, reference); + _document!._objects._add(object, reference); } } reference = getMappedReference(reference); } if (reference == null) { int objectNumber = nextObjectNumber; - if (_crossTable != null && _crossTable._objects != null) { - while (_crossTable._objects.containsKey(objectNumber)) { + if (_crossTable != null) { + while (_crossTable!._objects.containsKey(objectNumber)) { objectNumber = nextObjectNumber; } } - if (_document._objects._mainObjectCollection.containsKey(objectNumber)) { + if (_document!._objects._mainObjectCollection! + .containsKey(objectNumber)) { reference = _PdfReference(nextObjectNumber, 0); } else { - _PdfNumber trailerCount; + _PdfNumber? trailerCount; if (_crossTable != null) { - trailerCount = _crossTable._trailer[_DictionaryProperties.size]; + trailerCount = + _crossTable!._trailer![_DictionaryProperties.size] as _PdfNumber?; } if (trailerCount != null && objectNumber == trailerCount.value) { reference = _PdfReference(nextObjectNumber, 0); @@ -370,17 +379,17 @@ class _PdfCrossTable { if (object is _IPdfChangable) { (object as _IPdfChangable).changed = true; } - _document._objects._add(object); - _document._objects._trySetReference(object, reference); - final int tempIndex = _document._objects._count - 1; - final int tempkey = - _document._objects._objectCollection[tempIndex]._reference._objNum; - final _ObjectInfo tempvalue = - _document._objects._objectCollection[_document._objects._count - 1]; - _document._objects._mainObjectCollection[tempkey] = tempvalue; + _document!._objects._add(object); + _document!._objects._trySetReference(object, reference); + final int tempIndex = _document!._objects._count - 1; + final int? tempkey = _document! + ._objects._objectCollection![tempIndex]._reference!._objNum; + final _ObjectInfo tempvalue = _document! + ._objects._objectCollection![_document!._objects._count - 1]; + _document!._objects._mainObjectCollection![tempkey] = tempvalue; object.position = -1; } else { - _document._objects._trySetReference(object, reference); + _document!._objects._trySetReference(object, reference); } object.objectCollectionIndex = reference._objNum; object.status = _ObjectStatus.none; @@ -389,36 +398,32 @@ class _PdfCrossTable { } _PdfReference getMappedReference(_PdfReference reference) { - if (reference == null) { - return null; - } _mappedReferences ??= <_PdfReference, _PdfReference>{}; - _PdfReference mref = _mappedReferences.containsKey(reference) - ? _mappedReferences[reference] + _PdfReference? mref = _mappedReferences!.containsKey(reference) + ? _mappedReferences![reference] : null; if (mref == null) { mref = _PdfReference(nextObjectNumber, 0); - _mappedReferences[reference] = mref; + _mappedReferences![reference] = mref; } return mref; } void _registerObject(_PdfReference reference, - {int position, _PdfArchiveStream archive, bool isFreeType}) { - ArgumentError.checkNotNull(reference, 'reference'); + {int? position, _PdfArchiveStream? archive, bool? isFreeType}) { if (isFreeType == null) { if (position != null) { - _objects[reference._objNum] = _RegisteredObject(position, reference); - _maxGenNumIndex = max(_maxGenNumIndex, reference._genNum.toDouble()); + _objects![reference._objNum] = _RegisteredObject(position, reference); + _maxGenNumIndex = max(_maxGenNumIndex, reference._genNum!.toDouble()); } else { - _objects[reference._objNum] = + _objects![reference._objNum] = _RegisteredObject.fromArchive(this, archive, reference); - _maxGenNumIndex = max(_maxGenNumIndex, archive.count.toDouble()); + _maxGenNumIndex = max(_maxGenNumIndex, archive!.count.toDouble()); } } else { - _objects[reference._objNum] = + _objects![reference._objNum] = _RegisteredObject(position, reference, isFreeType); - _maxGenNumIndex = max(_maxGenNumIndex, reference._genNum.toDouble()); + _maxGenNumIndex = max(_maxGenNumIndex, reference._genNum!.toDouble()); } } @@ -440,12 +445,12 @@ class _PdfCrossTable { void _saveSections(_IPdfWriter writer) { int objectNumber = 0; - int count = 0; + int? count = 0; do { final Map result = _prepareSubsection(objectNumber); count = result['count']; - objectNumber = result['objectNumber']; - _saveSubsection(writer, objectNumber, count); + objectNumber = result['objectNumber']!; + _saveSubsection(writer, objectNumber, count!); objectNumber += count; } while (count != 0); } @@ -455,23 +460,23 @@ class _PdfCrossTable { int i; int total = count; if (total <= 0) { - total = _document._objects._count + 1; + total = _document!._objects._count + 1; } - if (total < _document._objects._maximumReferenceObjectNumber) { - total = _document._objects._maximumReferenceObjectNumber; + if (total < _document!._objects._maximumReferenceObjectNumber!) { + total = _document!._objects._maximumReferenceObjectNumber!; _isIndexOutOfRange = true; } if (objectNumber >= total) { return {'count': tempCount, 'objectNumber': objectNumber}; } for (i = objectNumber; i < total; ++i) { - if (_objects.containsKey(i)) { + if (_objects!.containsKey(i)) { break; } } objectNumber = i; for (; i < total; ++i) { - if (!_objects.containsKey(i)) { + if (!_objects!.containsKey(i)) { break; } ++tempCount; @@ -479,9 +484,8 @@ class _PdfCrossTable { return {'count': tempCount, 'objectNumber': objectNumber}; } - void _saveSubsection(_IPdfWriter writer, int objectNumber, int tempCount) { - ArgumentError.checkNotNull(writer); - if (tempCount <= 0 || objectNumber >= count && !_isIndexOutOfRange) { + void _saveSubsection(_IPdfWriter writer, int? objectNumber, int tempCount) { + if (tempCount <= 0 || objectNumber! >= count && !_isIndexOutOfRange) { return; } @@ -490,18 +494,18 @@ class _PdfCrossTable { (tempCount).toString() + _Operators.newLine); for (int i = objectNumber; i < objectNumber + tempCount; ++i) { - final _RegisteredObject obj = _objects[i]; + final _RegisteredObject obj = _objects![i]!; String value = ''; if (obj._type == _ObjectType.free) { value = _getItem(obj._offset, 65535, true); } else { - value = _getItem(obj._offset, obj._generationNumber, false); + value = _getItem(obj._offset, obj._generationNumber!, false); } writer._write(value); } } - String _getItem(int offset, int generationNumber, bool isFreeType) { + String _getItem(int? offset, int generationNumber, bool isFreeType) { String result = ''; final int offsetLength = 10 - offset.toString().length; if (generationNumber <= 0) { @@ -526,12 +530,11 @@ class _PdfCrossTable { } void _saveTrailer(_IPdfWriter writer, int count, int prevCrossReference) { - ArgumentError.checkNotNull(writer, 'writer'); writer._write(_Operators.trailer); writer._write(_Operators.newLine); - _PdfDictionary trailerDictionary = trailer; + _PdfDictionary trailerDictionary = trailer!; if (prevCrossReference != 0) { - trailer[_DictionaryProperties.prev] = _PdfNumber(prevCrossReference); + trailer![_DictionaryProperties.prev] = _PdfNumber(prevCrossReference); } trailerDictionary[_DictionaryProperties.size] = _PdfNumber(_count); trailerDictionary = _PdfDictionary(trailerDictionary); @@ -539,56 +542,55 @@ class _PdfCrossTable { trailerDictionary.save(writer); } - void _saveEnd(_IPdfWriter writer, int position) { + void _saveEnd(_IPdfWriter writer, int? position) { writer._write(_Operators.newLine + _Operators.startCrossReference + _Operators.newLine); writer._write(position.toString() + _Operators.newLine); writer._write(_Operators.endOfFileMarker); + writer._write(_Operators.newLine); } - static _IPdfPrimitive _dereference(_IPdfPrimitive element) { + static _IPdfPrimitive? _dereference(_IPdfPrimitive? element) { if (element != null && element is _PdfReferenceHolder) { final _PdfReferenceHolder holder = element; - if (holder != null) { - return holder.object; - } + return holder.object; } return element; } void _dispose() { if (_items != null) { - _items._dispose(); + _items!._dispose(); _items = null; } - if (_objects != null && _objects.isNotEmpty) { - _objects.clear(); + if (_objects != null && _objects!.isNotEmpty) { + _objects!.clear(); _objects = null; } } - _IPdfPrimitive _getObject(_IPdfPrimitive pointer) { + _IPdfPrimitive? _getObject(_IPdfPrimitive? pointer) { bool isEncryptedMetadata = true; - _IPdfPrimitive result = pointer; + _IPdfPrimitive? result = pointer; if (pointer is _PdfReferenceHolder) { result = pointer.object; } else if (pointer is _PdfReference) { final _PdfReference reference = pointer; _objNumbers.addLast(pointer); - _IPdfPrimitive obj; + _IPdfPrimitive? obj; if (_crossTable != null) { - obj = _crossTable._getObject(pointer); + obj = _crossTable!._getObject(pointer); } else { - final int index = _items._getObjectIndex(reference); + final int? index = _items!._getObjectIndex(reference); if (index == 0) { - obj = _items._getObjectFromReference(reference); + obj = _items!._getObjectFromReference(reference); } } obj = _pageProceed(obj); - final _PdfMainObjectCollection goc = _items; + final _PdfMainObjectCollection? goc = _items; if (obj != null) { - if (goc._containsReference(reference)) { + if (goc!._containsReference(reference)) { goc._getObjectIndex(reference); obj = goc._getObjectFromReference(reference); } else { @@ -601,18 +603,18 @@ class _PdfCrossTable { if (obj != null && obj is _PdfDictionary) { final _PdfDictionary dictionary = obj; if (dictionary.containsKey(_DictionaryProperties.type)) { - final _IPdfPrimitive primitive = + final _IPdfPrimitive? primitive = dictionary[_DictionaryProperties.type]; if (primitive != null && primitive is _PdfName && primitive._name == _DictionaryProperties.metadata) { if (encryptor != null) { - isEncryptedMetadata = encryptor.encryptMetadata; + isEncryptedMetadata = encryptor!.encryptMetadata; } } } } - if (_document._isEncrypted && isEncryptedMetadata) { + if (_document!._isEncrypted && isEncryptedMetadata) { _decrypt(result); } } @@ -622,26 +624,26 @@ class _PdfCrossTable { return result; } - _IPdfPrimitive _pageProceed(_IPdfPrimitive obj) { + _IPdfPrimitive? _pageProceed(_IPdfPrimitive? obj) { if (obj is PdfPage) { return obj; } if (obj is _PdfDictionary) { - final _PdfDictionary dic = obj as _PdfDictionary; - if (dic != null && !(obj is PdfPage)) { + final _PdfDictionary dic = obj; + if (!(obj is PdfPage)) { if (dic.containsKey(_DictionaryProperties.type)) { - final _IPdfPrimitive objType = dic[_DictionaryProperties.type]; + final _IPdfPrimitive? objType = dic[_DictionaryProperties.type]; if (objType is _PdfName) { final _PdfName type = _getObject(objType) as _PdfName; if (type._name == 'Page') { if (!dic.containsKey(_DictionaryProperties.kids)) { - if (_pdfDocument._isLoadedDocument) { - final PdfPage lPage = _pdfDocument.pages._getPage(dic); + if (_pdfDocument!._isLoadedDocument) { + final PdfPage lPage = _pdfDocument!.pages._getPage(dic); obj = lPage._element; - final _PdfMainObjectCollection items = _pdfDocument._objects; - final int index = items._lookFor(dic); + final _PdfMainObjectCollection items = _pdfDocument!._objects; + final int index = items._lookFor(dic)!; if (index >= 0) { - items._reregisterReference(index, obj); + items._reregisterReference(index, obj!); obj.position = -1; } } @@ -654,15 +656,15 @@ class _PdfCrossTable { return obj; } - bool _isCrossReferenceStream(PdfDocument document) { + bool _isCrossReferenceStream(PdfDocument? document) { ArgumentError.notNull('document'); bool result = false; if (_crossTable != null) { - if (_crossTable._trailer is _PdfStream) { + if (_crossTable!._trailer is _PdfStream) { result = true; } } else { - result = document.fileStructure.crossReferenceType == + result = document!.fileStructure.crossReferenceType == PdfCrossReferenceType.crossReferenceStream; } return result; @@ -670,15 +672,15 @@ class _PdfCrossTable { void _saveArchives(_PdfWriter writer) { if (_archives != null) { - for (final _ArchiveInfo ai in _archives) { - _PdfReference reference = ai._reference; + for (final _ArchiveInfo ai in _archives!) { + _PdfReference? reference = ai._reference; if (reference == null) { reference = _PdfReference(nextObjectNumber, 0); ai._reference = reference; } - _document._currentSavingObject = reference; + _document!._currentSavingObject = reference; _registerObject(reference, position: writer._position); - _doSaveObject(ai._archive, reference, writer); + _doSaveObject(ai._archive!, reference, writer); } } } @@ -690,8 +692,8 @@ class _PdfCrossTable { _saveArchive(writer); } _registerObject(reference, archive: _archive); - _archive._saveObject(obj, reference); - if (_archive._objCount >= 100) { + _archive!._saveObject(obj, reference); + if (_archive!._objCount >= 100) { _archive = null; } } @@ -699,13 +701,13 @@ class _PdfCrossTable { void _saveArchive(_PdfWriter writer) { final _ArchiveInfo ai = _ArchiveInfo(null, _archive); _archives ??= <_ArchiveInfo>[]; - _archives.add(ai); + _archives!.add(ai); } Map _prepareXRefStream( - double prevXRef, double position, _PdfReference reference) { - _PdfStream xRefStream; - xRefStream = _trailer as _PdfStream; + double prevXRef, double position, _PdfReference? reference) { + _PdfStream? xRefStream; + xRefStream = _trailer as _PdfStream?; if (xRefStream == null) { xRefStream = _PdfStream(); } else { @@ -724,8 +726,8 @@ class _PdfCrossTable { final List ms = []; while (true) { final Map result = _prepareSubsection(objectNum.toInt()); - count = result['count'].toDouble(); - objectNum = result['objectNumber'].toDouble(); + count = result['count']!.toDouble(); + objectNum = result['objectNumber']!.toDouble(); if (count <= 0) { break; } else { @@ -739,17 +741,15 @@ class _PdfCrossTable { xRefStream._dataStream = ms; xRefStream[_DictionaryProperties.index] = sectionIndeces; xRefStream[_DictionaryProperties.size] = _PdfNumber(this.count); - if (prevXRef != null) { - xRefStream[_DictionaryProperties.prev] = _PdfNumber(prevXRef); - } + xRefStream[_DictionaryProperties.prev] = _PdfNumber(prevXRef); xRefStream[_DictionaryProperties.type] = _PdfName('XRef'); xRefStream[_DictionaryProperties.w] = _PdfArray(paramsFormat); if (_crossTable != null) { - final _PdfDictionary trailer = _crossTable._trailer; - trailer._items.keys.forEach((_PdfName key) { - final bool contains = xRefStream.containsKey(key); + final _PdfDictionary trailer = _crossTable!._trailer!; + trailer._items!.keys.forEach((_PdfName? key) { + final bool contains = xRefStream!.containsKey(key); if (!contains && - key._name != _DictionaryProperties.decodeParms && + key!._name != _DictionaryProperties.decodeParms && key._name != _DictionaryProperties.filter) { xRefStream[key] = trailer[key]; } @@ -790,12 +790,12 @@ class _PdfCrossTable { void _saveSubSection( List xRefStream, double objectNum, double count, List format) { - for (double i = objectNum; i < objectNum + count; ++i) { - final _RegisteredObject obj = _objects[i]; - xRefStream.add(obj._type.index.toUnsigned(8)); + for (int i = objectNum.toInt(); i < objectNum + count; ++i) { + final _RegisteredObject obj = _objects![i]!; + xRefStream.add(obj._type!.index.toUnsigned(8)); switch (obj._type) { case _ObjectType.free: - _saveLong(xRefStream, obj._objectNumber.toInt(), format[1]); + _saveLong(xRefStream, obj._objectNumber!.toInt(), format[1]); _saveLong(xRefStream, obj._generationNumber, format[2]); break; @@ -805,7 +805,7 @@ class _PdfCrossTable { break; case _ObjectType.packed: - _saveLong(xRefStream, obj._objectNumber.toInt(), format[1]); + _saveLong(xRefStream, obj._objectNumber!.toInt(), format[1]); _saveLong(xRefStream, obj.offset, format[2]); break; @@ -815,64 +815,64 @@ class _PdfCrossTable { } } - void _saveLong(List xRefStream, int number, int count) { + void _saveLong(List xRefStream, int? number, int count) { for (int i = count - 1; i >= 0; --i) { - final int b = (number >> (i << 3) & 255).toUnsigned(8); + final int b = (number! >> (i << 3) & 255).toUnsigned(8); xRefStream.add(b); } } _PdfReference _findArchiveReference(_PdfArchiveStream archive) { int i = 0; - _ArchiveInfo ai; - for (final int count = _archives.length; i < count; ++i) { - ai = _archives[i]; + late _ArchiveInfo ai; + for (final int count = _archives!.length; i < count; ++i) { + ai = _archives![i]; if (ai._archive == archive) { break; } } - _PdfReference reference = ai._reference; + _PdfReference? reference = ai._reference; reference ??= _PdfReference(nextObjectNumber, 0); ai._reference = reference; return reference; } - void _decrypt(_IPdfPrimitive obj) { + void _decrypt(_IPdfPrimitive? obj) { if (obj != null) { if (obj is _PdfDictionary || obj is _PdfStream) { final _PdfDictionary dic = obj as _PdfDictionary; - if (!dic.decrypted) { - dic._items.forEach((_PdfName key, _IPdfPrimitive element) { + if (!dic.decrypted!) { + dic._items!.forEach((_PdfName? key, _IPdfPrimitive? element) { _decrypt(element); }); if (obj is _PdfStream) { final _PdfStream stream = obj; - if (_document._isEncrypted && - stream != null && - !stream.decrypted && + if (_document!._isEncrypted && + !stream.decrypted! && _objNumbers.isNotEmpty && - encryptor != null) { - stream.decrypt(encryptor, _objNumbers.last._objNum); + encryptor != null && + !encryptor!._encryptOnlyAttachment!) { + stream.decrypt(encryptor!, _objNumbers.last._objNum); } } } } else if (obj is _PdfArray) { final _PdfArray array = obj; - array._elements.forEach((_IPdfPrimitive element) { - if (element is _PdfName) { + array._elements.forEach((_IPdfPrimitive? element) { + if (element != null && element is _PdfName) { final _PdfName name = element; if (name._name == 'Indexed') { _isColorSpace = true; } } - _decrypt(element); + _decrypt(element!); }); _isColorSpace = false; } else if (obj is _PdfString) { final _PdfString str = obj; - if (!str.decrypted && (!str._isHex || _isColorSpace)) { - if (_document._isEncrypted && _objNumbers.isNotEmpty) { - obj.decrypt(encryptor, _objNumbers.last._objNum); + if (!str.decrypted && (!str._isHex! || _isColorSpace)) { + if (_document!._isEncrypted && _objNumbers.isNotEmpty) { + obj.decrypt(encryptor!, _objNumbers.last._objNum); } } } @@ -882,51 +882,47 @@ class _PdfCrossTable { /// Represents a registered object. class _RegisteredObject { - _RegisteredObject(int offset, _PdfReference reference, [bool isFreeType]) { - if (reference == null) { - throw ArgumentError.notNull('reference'); + _RegisteredObject(int? offset, _PdfReference reference, [bool? isFreeType]) { + _offset = offset; + _generationNumber = reference._genNum; + _objNumber = reference._objNum!.toDouble(); + if (isFreeType != null) { + _type = isFreeType ? _ObjectType.free : _ObjectType.normal; } else { - _offset = offset; - _generationNumber = reference._genNum; - _objNumber = reference._objNum.toDouble(); - if (isFreeType != null) { - _type = isFreeType ? _ObjectType.free : _ObjectType.normal; - } else { - _type = _ObjectType.normal; - _objNumber = reference._objNum.toDouble(); - } + _type = _ObjectType.normal; + _objNumber = reference._objNum!.toDouble(); } } _RegisteredObject.fromArchive(_PdfCrossTable xrefTable, - _PdfArchiveStream archive, _PdfReference reference) { + _PdfArchiveStream? archive, _PdfReference reference) { _xrefTable = xrefTable; _archive = archive; _offset = reference._objNum; _type = _ObjectType.packed; } - int _offset; - int _generationNumber; - _ObjectType _type; - double _objNumber; - _PdfCrossTable _xrefTable; - _PdfArchiveStream _archive; + int? _offset; + int? _generationNumber; + _ObjectType? _type; + double? _objNumber; + late _PdfCrossTable _xrefTable; + _PdfArchiveStream? _archive; - int get offset { - int result; + int? get offset { + int? result; if (_archive != null) { - result = _archive._getIndex(_offset); + result = _archive!._getIndex(_offset); } else { result = _offset; } return result; } - double get _objectNumber { + double? get _objectNumber { _objNumber ??= 0; if (_objNumber == 0) { if (_archive != null) { - _objNumber = _xrefTable._getReference(_archive)._objNum.toDouble(); + _objNumber = _xrefTable._getReference(_archive)._objNum!.toDouble(); } } return _objNumber; @@ -935,11 +931,11 @@ class _RegisteredObject { class _ArchiveInfo { // Constructor - _ArchiveInfo(_PdfReference reference, _PdfArchiveStream archive) { + _ArchiveInfo(_PdfReference? reference, _PdfArchiveStream? archive) { _reference = reference; _archive = archive; } // Fields - _PdfReference _reference; - _PdfArchiveStream _archive; + _PdfReference? _reference; + _PdfArchiveStream? _archive; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_lexer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_lexer.dart index e503bfa6e..764f7166b 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_lexer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_lexer.dart @@ -3,7 +3,6 @@ part of pdf; class _PdfLexer { //Constructor _PdfLexer(_PdfReader reader) { - ArgumentError.checkNotNull(reader); _initialize(reader); } @@ -20,19 +19,19 @@ class _PdfLexer { final String prefix = '<<'; //Fields - _PdfReader _reader; - int bufferIndex; - int bufferRead; - int bufferStart; - int bufferEnd; - List buffer; + late _PdfReader _reader; + late int bufferIndex; + late int bufferRead; + late int bufferStart; + late int bufferEnd; + late List buffer; bool _lastWasCr = false; - int line; - bool atBool; - _State lexicalState; + late int line; + late bool atBool; + late _State lexicalState; List stateTrans = [0, 81, 83]; - List accept; - String objectName; + late List accept; + String? objectName; bool isArray = false; int paren = 0; String stringText = ''; @@ -45,11 +44,11 @@ class _PdfLexer { String get text => _text(); - List cMap; + late List cMap; - List rMap; + late List rMap; - List> next; + late List> next; //Implementation void _initialize(_PdfReader reader) { @@ -167,10 +166,8 @@ class _PdfLexer { int sequenceInteger = 0; int commaIndex; String workString; - final List> res = List>(size1); - for (int i = 0; i < size1; ++i) { - res[i] = List.filled(size2, 0); - } + final List> res = + List.generate(size1, (i) => List.generate(size2, (j) => 0)); for (int i = 0; i < size1; ++i) { for (int j = 0; j < size2; ++j) { if (sequenceLength != 0) { @@ -184,13 +181,19 @@ class _PdfLexer { colonIndex = workString.indexOf(':'); if (colonIndex == -1) { - res[i][j] = int.tryParse(workString); + res[i][j] = int.tryParse(workString)!; continue; } lengthString = workString.substring(colonIndex + 1); - sequenceLength = int.tryParse(lengthString); + final int? sl = int.tryParse(lengthString); + if (sl != null) { + sequenceLength = sl; + } workString = workString.substring(0, colonIndex); - sequenceInteger = int.tryParse(workString); + final int? si = int.tryParse(workString); + if (si != null) { + sequenceInteger = si; + } res[i][j] = sequenceInteger; --sequenceLength; } @@ -228,7 +231,7 @@ class _PdfLexer { bufferEnd = bufferIndex; } - int _advance() { + int? _advance() { if (bufferIndex < bufferRead) { return buffer[bufferIndex++]; } @@ -292,7 +295,7 @@ class _PdfLexer { List _double(List buffer) { final int length = buffer.length; - final List newBuffer = List(2 * length); + final List newBuffer = List.filled(2 * length, 0, growable: true); List.copyRange(newBuffer, 0, buffer, 0, length); return newBuffer; } @@ -348,11 +351,11 @@ class _PdfLexer { } _TokenType _getNextToken() { - int lookAhead; + int? lookAhead; int anchor = noAnchor; int state = stateTrans[lexicalState.index]; - int nextState = noState; - int lastAcceptState = noState; + int? nextState = noState; + int? lastAcceptState = noState; bool initial = true; int thisAccept; @@ -372,7 +375,7 @@ class _PdfLexer { } nextState = f; - nextState = next[rMap[state]][cMap[lookAhead]]; + nextState = next[rMap[state]][cMap[lookAhead!]]; if (eof == lookAhead && initial) { return _TokenType.eof; } @@ -388,7 +391,7 @@ class _PdfLexer { if (noState == lastAcceptState) { throw ArgumentError.value(noState, 'Lexical Error: Unmatched Input.'); } else { - anchor = accept[lastAcceptState]; + anchor = accept[lastAcceptState!]; if (0 != (end & anchor)) { _moveEnd(); } @@ -661,7 +664,6 @@ class _PdfLexer { _error(_Error.match, true); break; } - break; } case -43: break; @@ -681,7 +683,7 @@ class _PdfLexer { text += String.fromCharCode(buffer[index]); index++; } - final double value = double.tryParse(text); + final double? value = double.tryParse(text); if (value != null && buffer[bufferIndex - 1] == '.'.codeUnitAt(0) && (buffer[bufferIndex] == ' '.codeUnitAt(0) || @@ -737,11 +739,11 @@ class _PdfLexer { final String start = String.fromCharCode(buffer[bufferEnd - 2]); final int value = bufferEnd - bufferStart; if (end == ')' && (start == '\\' || start == '\u0000') && value > 3) { - int index = bufferEnd; + int? index = bufferEnd; final String text = String.fromCharCodes(buffer); index = text.indexOf(end, bufferStart) + 1; int prvIndex = 0; - while (text[index - 2] == '\\') { + while (text[index! - 2] == '\\') { index = text.indexOf(end, index) + 1; if (index > 0) { prvIndex = index; @@ -779,10 +781,10 @@ class _PdfLexer { bufferEnd = index; } } else if (end == ')' && value > 3) { - int index = bufferEnd; + int? index = bufferEnd; final String text = String.fromCharCodes(buffer); index = text.indexOf(end, bufferStart) + 1; - while (text[index - 2] == '\\') { + while (text[index! - 2] == '\\') { index = text.indexOf(end, index) + 1; } if (bufferEnd > index + 1) { @@ -811,7 +813,7 @@ class _PdfLexer { 'Fatal Error occurred at ' + position.toString() + '.\n When reading object type of ' + - objectName); + objectName!); } else { throw ArgumentError.value( code, 'Fatal Error occurred at ' + position.toString()); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_main_object_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_main_object_collection.dart index d3adc8ece..646fb0573 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_main_object_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_main_object_collection.dart @@ -7,15 +7,15 @@ class _PdfMainObjectCollection { } //Fields - int _index; - Map _mainObjectCollection; - List<_ObjectInfo> _objectCollection; - Map<_IPdfPrimitive, int> _primitiveObjectCollection; - int _maximumReferenceObjectNumber = 0; + int? _index; + Map? _mainObjectCollection; + List<_ObjectInfo>? _objectCollection; + Map<_IPdfPrimitive?, int>? _primitiveObjectCollection; + int? _maximumReferenceObjectNumber = 0; //Properties int get _count { - return _objectCollection.length; + return _objectCollection!.length; } /// Get the PdfMainObjectCollection items. @@ -24,25 +24,25 @@ class _PdfMainObjectCollection { //Implementation void _initialize() { _index = 0; - _mainObjectCollection = {}; + _mainObjectCollection = {}; _objectCollection = <_ObjectInfo>[]; - _primitiveObjectCollection = <_IPdfPrimitive, int>{}; + _primitiveObjectCollection = <_IPdfPrimitive?, int>{}; } /// Check key and return the value. _ObjectInfo _returnValue(int index) { - if (index < 0 || index > _objectCollection.length) { + if (index < 0 || index > _objectCollection!.length) { throw ArgumentError.value(index, 'index', 'index out of range'); } - return _objectCollection[index]; + return _objectCollection![index]; } bool _containsReference(_PdfReference reference) { - return _mainObjectCollection.containsKey(reference._objNum); + return _mainObjectCollection!.containsKey(reference._objNum); } /// Adds the specified element. - void _add(dynamic element, [_PdfReference reference]) { + void _add(dynamic element, [_PdfReference? reference]) { if (element == null) { throw ArgumentError.value(element, 'element', 'value cannot be null'); } @@ -51,57 +51,57 @@ class _PdfMainObjectCollection { } if (reference == null) { final _ObjectInfo info = _ObjectInfo(element); - _objectCollection.add(info); - if (!_primitiveObjectCollection.containsKey(element)) { - _primitiveObjectCollection[element] = _objectCollection.length - 1; + _objectCollection!.add(info); + if (!_primitiveObjectCollection!.containsKey(element)) { + _primitiveObjectCollection![element] = _objectCollection!.length - 1; } - element.position = _objectCollection.length - 1; - _index = _objectCollection.length - 1; + element.position = _objectCollection!.length - 1; + _index = _objectCollection!.length - 1; element.status = _ObjectStatus.registered; } else { final _ObjectInfo info = _ObjectInfo(element, reference); - if (_maximumReferenceObjectNumber < reference._objNum) { + if (_maximumReferenceObjectNumber! < reference._objNum!) { _maximumReferenceObjectNumber = reference._objNum; } - _objectCollection.add(info); - if (!_primitiveObjectCollection.containsKey(element)) { - _primitiveObjectCollection[element] = _objectCollection.length - 1; + _objectCollection!.add(info); + if (!_primitiveObjectCollection!.containsKey(element)) { + _primitiveObjectCollection![element] = _objectCollection!.length - 1; } - _mainObjectCollection[reference._objNum] = info; - element.position = _objectCollection.length - 1; - reference.position = _objectCollection.length - 1; + _mainObjectCollection![reference._objNum] = info; + element.position = _objectCollection!.length - 1; + reference.position = _objectCollection!.length - 1; } } - Map _getReference(_IPdfPrimitive object, bool isNew) { + Map _getReference(_IPdfPrimitive object, bool? isNew) { _index = _lookFor(object); - _PdfReference reference; - if (_index < 0 || _index > _count) { + _PdfReference? reference; + if (_index! < 0 || _index! > _count) { isNew = true; } else { isNew = false; - final _ObjectInfo objectInfo = _objectCollection[_index]; + final _ObjectInfo objectInfo = _objectCollection![_index!]; reference = objectInfo._reference; } return {'isNew': isNew, 'reference': reference}; } bool contains(_IPdfPrimitive element) { - return (_lookFor(element) >= 0); + return (_lookFor(element)! >= 0); } - int _lookFor(_IPdfPrimitive obj) { - int index = -1; + int? _lookFor(_IPdfPrimitive obj) { + int? index = -1; if (obj.position != -1) { return obj.position; } - if (_primitiveObjectCollection.containsKey(obj) && - _count == _primitiveObjectCollection.length) { - index = _primitiveObjectCollection[obj]; + if (_primitiveObjectCollection!.containsKey(obj) && + _count == _primitiveObjectCollection!.length) { + index = _primitiveObjectCollection![obj]; } else { for (int i = _count - 1; i >= 0; i--) { - final _ObjectInfo objectInfo = _objectCollection[i]; - final _IPdfPrimitive primitive = objectInfo._object; + final _ObjectInfo objectInfo = _objectCollection![i]; + final _IPdfPrimitive? primitive = objectInfo._object; final bool isValidType = ((primitive is _PdfName && !(obj is _PdfName)) || (!(primitive is _PdfName) && obj is _PdfName)) @@ -116,34 +116,34 @@ class _PdfMainObjectCollection { return index; } - _IPdfPrimitive _getObject(_PdfReference reference) { + _IPdfPrimitive? _getObject(_PdfReference reference) { try { - return _mainObjectCollection[reference._objNum]._object; + return _mainObjectCollection![reference._objNum]!._object; } catch (e) { return null; } } - int _getObjectIndex(_PdfReference reference) { + int? _getObjectIndex(_PdfReference reference) { if (reference.position != -1) { return reference.position; } - if (_mainObjectCollection.isEmpty) { - if (_objectCollection.isEmpty) { + if (_mainObjectCollection!.isEmpty) { + if (_objectCollection!.isEmpty) { return -1; } else { - for (int i = 0; i < _objectCollection.length - 1; i++) { - _mainObjectCollection[_objectCollection[i]._reference._objNum] = - _objectCollection[i]; + for (int i = 0; i < _objectCollection!.length - 1; i++) { + _mainObjectCollection![_objectCollection![i]._reference!._objNum] = + _objectCollection![i]; } - if (!_mainObjectCollection.containsKey(reference._objNum)) { + if (!_mainObjectCollection!.containsKey(reference._objNum)) { return -1; } else { return 0; } } } else { - if (!_mainObjectCollection.containsKey(reference._objNum)) { + if (!_mainObjectCollection!.containsKey(reference._objNum)) { return -1; } else { return 0; @@ -152,14 +152,12 @@ class _PdfMainObjectCollection { } bool _trySetReference(_IPdfPrimitive object, _PdfReference reference) { - ArgumentError.checkNotNull(object, 'object'); - ArgumentError.checkNotNull(reference, 'reference'); bool result = true; _index = _lookFor(object); - if (_index < 0 || _index >= _objectCollection.length) { + if (_index! < 0 || _index! >= _objectCollection!.length) { result = false; } else { - final _ObjectInfo objectInfo = _objectCollection[_index]; + final _ObjectInfo objectInfo = _objectCollection![_index!]; if (objectInfo._reference != null) { result = false; } else { @@ -169,23 +167,23 @@ class _PdfMainObjectCollection { return result; } - _IPdfPrimitive _getObjectFromReference(_PdfReference reference) { + _IPdfPrimitive? _getObjectFromReference(_PdfReference reference) { try { - return _mainObjectCollection[reference._objNum]._object; + return _mainObjectCollection![reference._objNum]!._object; } catch (e) { return null; } } void _reregisterReference(int oldObjIndex, _IPdfPrimitive newObj) { - ArgumentError.checkNotNull(newObj); if (oldObjIndex < 0 || oldObjIndex > _count) { - ArgumentError.value(oldObjIndex, 'index out of range'); + throw ArgumentError.value( + oldObjIndex, 'oldObjIndex', 'index out of range'); } - final _ObjectInfo oi = _objectCollection[oldObjIndex]; + final _ObjectInfo oi = _objectCollection![oldObjIndex]; if (oi._object != newObj) { - _primitiveObjectCollection.remove(oi._object); - _primitiveObjectCollection[newObj] = oldObjIndex; + _primitiveObjectCollection!.remove(oi._object); + _primitiveObjectCollection![newObj] = oldObjIndex; } oi._object = newObj; newObj.position = oldObjIndex; @@ -193,21 +191,21 @@ class _PdfMainObjectCollection { void _dispose() { if (_mainObjectCollection != null) { - _mainObjectCollection.clear(); + _mainObjectCollection!.clear(); _mainObjectCollection = null; } if (_objectCollection != null) { - _objectCollection.clear(); + _objectCollection!.clear(); _objectCollection = null; } if (_primitiveObjectCollection != null && - _primitiveObjectCollection.isNotEmpty) { - final List<_IPdfPrimitive> primitives = - _primitiveObjectCollection.keys.toList(); + _primitiveObjectCollection!.isNotEmpty) { + final List<_IPdfPrimitive?> primitives = + _primitiveObjectCollection!.keys.toList(); for (int i = 0; i < primitives.length; i++) { - primitives[i].dispose(); + primitives[i]!.dispose(); } - _primitiveObjectCollection.clear(); + _primitiveObjectCollection!.clear(); _primitiveObjectCollection = null; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_operators.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_operators.dart index 104549521..d7fb8cd52 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_operators.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_operators.dart @@ -50,4 +50,5 @@ class _Operators { static const String setText = 'Tj'; static const String setGraphicsState = 'gs'; static const String appendBezierCurve = 'c'; + static const String slash = '/'; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_parser.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_parser.dart index 77215db17..0b461d535 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_parser.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_parser.dart @@ -3,12 +3,8 @@ part of pdf; class _PdfParser { //Constructor _PdfParser(_CrossTable cTable, _PdfReader reader, _PdfCrossTable crossTable) { - ArgumentError.checkNotNull(reader, 'reader'); - ArgumentError.checkNotNull(cTable, 'cTable'); - ArgumentError.checkNotNull(crossTable, 'crossTable'); _isColorSpace = false; _isPassword = false; - _encrypted = false; _reader = reader; _cTable = cTable; _crossTable = crossTable; @@ -16,15 +12,59 @@ class _PdfParser { } //Fields - _CrossTable _cTable; - _PdfReader _reader; - _PdfLexer _lexer; - _TokenType _next; - _PdfCrossTable _crossTable; + _CrossTable? _cTable; + late _PdfReader _reader; + _PdfLexer? _lexer; + _TokenType? _next; + _PdfCrossTable? _crossTable; Queue integerQueue = Queue(); - bool _isPassword; - bool _isColorSpace; - bool _encrypted; + late bool _isPassword; + late bool _isColorSpace; + List? _windows1252MapTable; + + //Properties + List? get windows1252MapTable { + _windows1252MapTable ??= [ + '\u007f', + '€', + '\u0081', + '‚', + 'ƒ', + '„', + '…', + '†', + '‡', + 'ˆ', + '‰', + 'Š', + '‹', + 'Œ', + '\u008d', + 'Ž', + '\u008f', + '\u0090', + '‘', + '’', + '“', + '”', + '•', + '–', + '—', + '˜', + '™', + 'š', + '›', + 'œ', + '\u009d', + 'ž', + 'Ÿ', + ' ', + '¡', + '¢', + '£' + ]; + return _windows1252MapTable; + } //Implementation void _setOffset(int offset) { @@ -32,24 +72,24 @@ class _PdfParser { if (integerQueue.isNotEmpty) { integerQueue = Queue(); } - _lexer._reset(); + _lexer!._reset(); } - _PdfNumber _parseInteger() { - final double value = double.tryParse(_lexer.text); - _PdfNumber integer; + _PdfNumber? _parseInteger() { + final double? value = double.tryParse(_lexer!.text); + _PdfNumber? integer; if (value != null) { integer = _PdfNumber(value); } else { - _error(_ErrorType.badlyFormedInteger, _lexer.text); + _error(_ErrorType.badlyFormedInteger, _lexer!.text); } _advance(); return integer; } - _IPdfPrimitive _number() { - _IPdfPrimitive obj; - _PdfNumber integer; + _IPdfPrimitive? _number() { + _IPdfPrimitive? obj; + _PdfNumber? integer; if (integerQueue.isNotEmpty) { integer = _PdfNumber(integerQueue.removeFirst()); } else { @@ -58,20 +98,21 @@ class _PdfParser { } obj = integer; if (_next == _TokenType.number) { - final _PdfNumber integer2 = _parseInteger(); + final _PdfNumber? integer2 = _parseInteger(); if (_next == _TokenType.reference) { final _PdfReference reference = - _PdfReference(integer.value.toInt(), integer2.value.toInt()); + _PdfReference(integer!.value!.toInt(), integer2!.value!.toInt()); obj = _PdfReferenceHolder.fromReference(reference, _crossTable); _advance(); } else { - integerQueue.addLast(integer2.value.toInt()); + integerQueue.addLast(integer2!.value!.toInt()); } } return obj; } - void _parseOldXRef(_CrossTable cTable, Map objects) { + void _parseOldXRef( + _CrossTable cTable, Map? objects) { _advance(); while (_isSubsection()) { cTable._parseSubsection(this, objects); @@ -91,15 +132,15 @@ class _PdfParser { } Map _parseCrossReferenceTable( - Map objects, _CrossTable cTable) { - _IPdfPrimitive obj; + Map? objects, _CrossTable cTable) { + _IPdfPrimitive? obj; _advance(); if (_next == _TokenType.xRef) { _parseOldXRef(cTable, objects); obj = _trailer(); final _PdfDictionary trailerDic = obj as _PdfDictionary; if (trailerDic.containsKey('Size')) { - final int size = (trailerDic['Size'] as _PdfNumber).value.toInt(); + final int size = (trailerDic['Size'] as _PdfNumber).value!.toInt(); int initialNumber = 0; if (cTable._initialSubsectionCount == cTable._initialNumberOfSubsection) { @@ -115,9 +156,9 @@ class _PdfParser { final int difference = initialNumber + total - size; final Map newObjects = {}; - final List keys = objects.keys.toList(); + final List keys = objects!.keys.toList(); for (int i = 0; i < keys.length; i++) { - newObjects[keys[i] - difference] = objects[keys[i]]; + newObjects[keys[i] - difference] = objects[keys[i]]!; } objects = newObjects; cTable._objects = newObjects; @@ -125,7 +166,7 @@ class _PdfParser { } } else { obj = _parse(); - objects = cTable._parseNewTable(obj as _PdfStream, objects); + objects = cTable._parseNewTable(obj as _PdfStream?, objects); } return {'object': obj, 'objects': objects}; } @@ -136,33 +177,33 @@ class _PdfParser { return _dictionary(); } - _IPdfPrimitive _parseOffset(int offset) { + _IPdfPrimitive? _parseOffset(int offset) { _setOffset(offset); _advance(); return _parse(); } - _IPdfPrimitive _parse() { + _IPdfPrimitive? _parse() { _match(_next, _TokenType.number); _simple(); _simple(); _match(_next, _TokenType.objectStart); _advance(); - final _IPdfPrimitive obj = _simple(); + final _IPdfPrimitive? obj = _simple(); if (_next != _TokenType.objectEnd) { _next = _TokenType.objectEnd; } _match(_next, _TokenType.objectEnd); - if (!_lexer._skip) { + if (!_lexer!._skip) { _advance(); } else { - _lexer._skip = false; + _lexer!._skip = false; } return obj; } - _IPdfPrimitive _simple() { - _IPdfPrimitive obj; + _IPdfPrimitive? _simple() { + _IPdfPrimitive? obj; if (integerQueue.isNotEmpty) { obj = _number(); } else { @@ -206,14 +247,14 @@ class _PdfParser { return obj; } - _IPdfPrimitive _real() { + _IPdfPrimitive? _real() { _match(_next, _TokenType.real); - final double value = double.tryParse(_lexer.text); - _PdfNumber real; + final double? value = double.tryParse(_lexer!.text); + _PdfNumber? real; if (value != null) { real = _PdfNumber(value); } else { - _error(_ErrorType.badlyFormedReal, _lexer.text); + _error(_ErrorType.badlyFormedReal, _lexer!.text); } _advance(); return real; @@ -221,7 +262,7 @@ class _PdfParser { _IPdfPrimitive _readBoolean() { _match(_next, _TokenType.boolean); - final bool value = _lexer.text == 'true'; + final bool value = _lexer!.text == 'true'; final _PdfBoolean result = _PdfBoolean(value); _advance(); return result; @@ -229,7 +270,7 @@ class _PdfParser { _IPdfPrimitive _readName() { _match(_next, _TokenType.name); - final String name = _lexer.text.substring(1); + final String name = _lexer!.text.substring(1); final _PdfName result = _PdfName(name); _advance(); return result; @@ -241,15 +282,15 @@ class _PdfParser { } void _rebuildXrefTable( - Map newObjects, _CrossTable crosstable) { + Map newObjects, _CrossTable? crosstable) { final _PdfReader reader = _PdfReader(_reader._streamReader._data); reader.position = 0; newObjects.clear(); - int objNumber = 0; - int marker = 0; + int? objNumber = 0; + int? marker = 0; List previoursToken = ['\u0000']; while (true) { - if (reader.position >= reader.length - 1) { + if (reader.position >= reader.length! - 1) { break; } String str = ''; @@ -295,44 +336,30 @@ class _PdfParser { _IPdfPrimitive _readString() { _match(_next, _TokenType.string); - String text = _lexer.stringText; + String text = _lexer!.stringText; bool unicode = false; if (_isPassword) { - text = _processEscapes(text, false); + text = String.fromCharCodes(_processEscapes(text)); } else if (!_isColorSpace) { if (_checkForPreamble(text)) { - bool isSkip = false; - bool isSubstring = false; - while ( - text.contains('\\') || (isSubstring && text.contains('\u0000'))) { - if (!isSubstring) { - text = text.substring(2); - } - final String originalText = text.substring(0); - text = _processEscapes(text, true); - isSubstring = true; - isSkip = true; - if (originalText == text || - (text.contains('\\') && text.indexOf('\\') != text.length - 1)) { - break; - } - } - if (_checkForPreamble(text) && !isSkip) { - text = _processUnicodeWithPreamble(text); - unicode = true; - } + text = _processUnicodeWithPreamble(text); + unicode = true; } else { if (!_checkUnicodePreamble(text)) { - text = _processEscapes(text, false); + text = String.fromCharCodes(_processEscapes(text)); } if (_checkForPreamble(text)) { text = _processUnicodeWithPreamble(text); unicode = true; } + if (_checkUnicodePreamble(text)) { + text = text.substring(2); + text = String.fromCharCodes(_processEscapes(text)); + } } } else { if (_isColorSpace) { - text = _processEscapes(text, false); + text = String.fromCharCodes(_processEscapes(text)); } text = 'ColorFound' + text; } @@ -347,72 +374,30 @@ class _PdfParser { } String _processUnicodeWithPreamble(String text) { - List data = List.filled(text.length - 2, 0); - int j = 0; - for (int i = 2; i < text.length; i++) { - data[j++] = text[i].codeUnitAt(0).toUnsigned(8); - } - int textIndex = 0; - String tempText; - bool isNewLine = false; - for (int i = 0; i < data.length - 1; i++) { - if ((data[i] == 92 && - (data[i + 1] == 40 || - data[i + 1] == 41 || - data[i + 1] == 13 || - data[i + 1] == 0x3e || - data[i + 1] == 92)) || - data[i] == 13) { - for (int j = i; j < data.length - 1; j++) { - data[j] = data[j + 1]; - } - final List tempSub = List.filled(data.length - 1, 0); - List.copyRange(tempSub, 0, data, 0, data.length - 1); - data = tempSub; - i--; - } else if (data[i] == 92 && data[i + 1] == 114) { - final List list = []; - for (int j = 0; j < i; j++) { - list.add(data[j]); - } - list.add(13); - for (int j = i + 2; j < data.length; j++) { - list.add(data[j]); - } - data = list; - } else if (data[i] == 92 && data[i + 1] == 110) { - isNewLine = true; - final int tempTextRange = i - 1 - textIndex; - tempText += _decodeBigEndian(data, textIndex, tempTextRange); - tempText += '\r\n'; - textIndex = i + 2; - i += 2; - } - } - final int remaining = data.length - textIndex; - if (isNewLine) { - text = tempText; - text += _decodeBigEndian(data, textIndex, remaining); - } else { - text = _decodeBigEndian(data, textIndex, remaining); - } - return text; + final List data = _processEscapes(text); + return _decodeBigEndian(data, 2, data.length - 2); } - _IPdfPrimitive _readUnicodeString([String validateText]) { - final String text = validateText ?? _lexer.text; + _IPdfPrimitive _readUnicodeString([String? validateText]) { + final String text = validateText ?? _lexer!.text; String value = text.substring(0); - if (value != null && value.length > 2) { + bool unicode = false; + if (value.length > 2) { value = value.substring(1, value.length - 1); } if (_checkForPreamble(value)) { - final String actualText = value.substring(2); - if (actualText.length >= 3 && actualText[2] == '0') { - value = _processEscapes(value, false); - } + value = _processUnicodeWithPreamble(value); + unicode = true; + } else { + value = String.fromCharCodes(_processEscapes(value)); } final _PdfString str = _PdfString(value); - if (!_lexer._skip) { + if (!unicode) { + str.encode = _ForceEncoding.ascii; + } else { + str.encode = _ForceEncoding.unicode; + } + if (!_lexer!._skip) { _advance(); } else { _next = _TokenType.dictionaryEnd; @@ -421,109 +406,83 @@ class _PdfParser { } bool _checkForPreamble(String text) { - return text != null && - text.length > 1 && + return text.length > 1 && text.codeUnitAt(0) == 254 && text.codeUnitAt(1) == 255; } bool _checkUnicodePreamble(String text) { - return text != null && - text.length > 1 && + return text.length > 1 && text.codeUnitAt(0) == 255 && text.codeUnitAt(1) == 254; } - String _processEscapes(String text, bool isComplete) { - if (!isComplete) { - text = text.replaceAll('\r', ''); - } - if (_isPassword) { - text = text.replaceAll('\n', ''); - } - String sb = ''; - bool escape = false; - for (int i = 0; i < text.length; ++i) { - String c = text[i]; - - if (!escape) { - if (c == '\\') { - escape = true; - } else { - if (c != '\u0000' || - _encrypted || - _isColorSpace || - (c == '\u0000' && _isPassword)) { - if (isComplete && - c.codeUnitAt(0) < 32 && - i - 1 >= 0 && - text[i - 1] == ' ') { - final List unicodeBytes = [ - ' '.codeUnitAt(0).toUnsigned(8), - c.codeUnitAt(0).toUnsigned(8) - ]; - final String originalText = - _decodeBigEndian(unicodeBytes, 0, unicodeBytes.length); - c = originalText[0]; - sb = sb.substring(0, sb.length - 1); - } - sb += c; - } - } - } else { - switch (c[0]) { - case 'r': - sb += '\r'; + List _processEscapes(String text) { + final List data = text.codeUnits; + final List result = []; + for (int i = 0; i < data.length; i++) { + if (data[i] == 92) { + int next = data[++i]; + switch (next) { + case 110: + result.add(10); + break; + case 114: + result.add(13); break; - case 'n': - sb += '\n'; + case 116: + result.add(9); break; - case 't': - sb += '\t'; + case 98: + result.add(8); break; - case 'b': - sb += '\b'; + case 102: + result.add(12); break; - case 'f': - sb += '\f'; + case 13: + next = data[++i]; + if (next != 10) { + --i; + } break; - case '(': - case ')': - case '\\': - sb += c; + case 10: + break; + case 40: + case 41: + case 92: + result.add(next); break; default: - if (c.codeUnitAt(0) <= '7'.codeUnitAt(0) && - c.codeUnitAt(0) >= '0'.codeUnitAt(0)) { - final Map result = _processOctal(text, i); - c = result['value']; - i = result['index']; - i--; + if (next < 48 || next > 55) { + result.add(next); + break; } - final int value = c.codeUnitAt(0); - if (value < 256) { - if (isComplete && - (c != '\u0000' || - _encrypted || - _isColorSpace || - (c == '\u0000' && _isPassword))) { - if (c == '\r' && i + 1 < text.length && text[i + 1] == '\n') { - i++; - } else { - sb += c; - } - } else { - sb += c; - } + int octal = next - 48; + next = data[++i]; + if (next < 48 || next > 55) { + --i; + result.add(octal); + break; + } + octal = (octal << 3) + next - 48; + next = data[++i]; + if (next < 48 || next > 55) { + --i; + result.add(octal); + break; } + octal = (octal << 3) + next - 48; + result.add(octal & 0xff); break; } - escape = false; + } else { + result.add(data[i]); } } - return sb; + return result; } + // ignore: unused_element Map _processOctal(String text, int i) { final int length = text.length; int count = 0; @@ -538,7 +497,7 @@ class _PdfParser { ++i; ++count; } - value = double.tryParse(octalText).toInt(); + value = double.tryParse(octalText)!.toInt(); return {'value': String.fromCharCode(value), 'index': i}; } @@ -548,7 +507,7 @@ class _PdfParser { String sb = ''; bool isHex = true; while (_next != _TokenType.hexStringEnd) { - String text = _lexer.text; + String text = _lexer!.text; if (_next == _TokenType.hexStringWeird) { isHex = false; } else if (_next == _TokenType.hexStringWeirdEscape) { @@ -567,11 +526,11 @@ class _PdfParser { _IPdfPrimitive _array() { _match(_next, _TokenType.arrayStart); _advance(); - _IPdfPrimitive obj; + _IPdfPrimitive? obj; final _PdfArray array = _PdfArray(); - _lexer.isArray = true; + _lexer!.isArray = true; while ((obj = _simple()) != null) { - array._add(obj); + array._add(obj!); if (array[0] is _PdfName && (array[0] as _PdfName)._name == 'Indexed') { _isColorSpace = true; } else { @@ -583,7 +542,7 @@ class _PdfParser { } _match(_next, _TokenType.arrayEnd); _advance(); - _lexer.isArray = false; + _lexer!.isArray = false; array.freezeChanges(this); return array; } @@ -593,7 +552,7 @@ class _PdfParser { _advance(); final _PdfDictionary dic = _PdfDictionary(); _Pair pair = _readPair(); - while (pair != null && pair._name != null && pair._value != null) { + while (pair._name != null && pair._value != null) { if (pair._value != null) { dic[pair._name] = pair._value; } @@ -603,11 +562,11 @@ class _PdfParser { _next = _TokenType.dictionaryEnd; } _match(_next, _TokenType.dictionaryEnd); - if (!_lexer._skip) { + if (!_lexer!._skip) { _advance(); } else { _next = _TokenType.objectEnd; - _lexer._skip = false; + _lexer!._skip = false; } _IPdfPrimitive result; if (_next == _TokenType.streamStart) { @@ -621,12 +580,12 @@ class _PdfParser { _IPdfPrimitive _readStream(_PdfDictionary dic) { _match(_next, _TokenType.streamStart); - _lexer._skipToken(); - _lexer._skipNewLine(); + _lexer!._skipToken(); + _lexer!._skipNewLine(); - _IPdfPrimitive obj = dic[_DictionaryProperties.length]; - _PdfNumber length; - _PdfReferenceHolder reference; + _IPdfPrimitive? obj = dic[_DictionaryProperties.length]; + _PdfNumber? length; + _PdfReferenceHolder? reference; if (obj is _PdfNumber) { length = obj; } @@ -634,7 +593,7 @@ class _PdfParser { reference = obj; } if (length == null && reference == null) { - final int lexerPosition = _lexer.position; + final int lexerPosition = _lexer!.position; final int position = _reader.position; _reader.position = lexerPosition; final int end = _reader._searchForward('endstream'); @@ -645,7 +604,7 @@ class _PdfParser { streamLength = lexerPosition - end; } _reader.position = position; - final List buffer = _lexer._readBytes(streamLength); + final List buffer = _lexer!._readBytes(streamLength); final _PdfStream innerStream = _PdfStream(dic, buffer); _advance(); if (_next != _TokenType.streamEnd) { @@ -657,22 +616,22 @@ class _PdfParser { return innerStream; } else if (reference != null) { final _PdfReferenceHolder reference = obj as _PdfReferenceHolder; - final _PdfLexer lex = _lexer; + final _PdfLexer? lex = _lexer; final int position = _reader.position; _lexer = _PdfLexer(_reader); - obj = _cTable._getObject(reference.reference); - length = obj as _PdfNumber; + obj = _cTable!._getObject(reference.reference); + length = obj as _PdfNumber?; _reader.position = position; _lexer = lex; } - final int intLength = length.value.toInt(); - final bool check = _checkStreamLength(_lexer.position, intLength); + final int intLength = length!.value!.toInt(); + final bool check = _checkStreamLength(_lexer!.position, intLength); _PdfStream stream; if (check) { - final List buf = _lexer._readBytes(intLength); + final List buf = _lexer!._readBytes(intLength); stream = _PdfStream(dic, buf); } else { - final int lexerPosition = _lexer.position; + final int lexerPosition = _lexer!.position; final int position = _reader.position; _reader.position = lexerPosition; final int end = _reader._searchForward('endstream'); @@ -683,7 +642,7 @@ class _PdfParser { streamLength = lexerPosition - end; } _reader.position = position; - final List buf = _lexer._readBytes(streamLength); + final List buf = _lexer!._readBytes(streamLength); stream = _PdfStream(dic, buf); } _advance(); @@ -700,7 +659,7 @@ class _PdfParser { String _getObjectFlag() { _match(_next, _TokenType.objectType); - final String type = _lexer.text[0]; + final String type = _lexer!.text[0]; _advance(); return type; } @@ -726,7 +685,7 @@ class _PdfParser { } _Pair _readPair() { - _IPdfPrimitive obj; + _IPdfPrimitive? obj; try { obj = _simple(); } catch (e) { @@ -735,11 +694,13 @@ class _PdfParser { if (obj == null) { return _Pair.empty; } - final _PdfName name = obj as _PdfName; - if (name == null) { + _PdfName? name; + if (obj is _PdfName) { + name = obj; + } else { _error(_ErrorType.badlyFormedDictionary, 'next should be a name.'); } - if (name._name == _DictionaryProperties.u || + if (name!._name == _DictionaryProperties.u || name._name == _DictionaryProperties.o) { _isPassword = true; } @@ -752,21 +713,21 @@ class _PdfParser { _advance(); _match(_next, _TokenType.startXRef); _advance(); - final _PdfNumber number = _number() as _PdfNumber; + final _PdfNumber? number = _number() as _PdfNumber?; if (number != null) { - return number.value.toInt(); + return number.value!.toInt(); } else { return 0; } } - void _match(_TokenType token, _TokenType match) { + void _match(_TokenType? token, _TokenType match) { if (token != match) { _error(_ErrorType.unexpected, token.toString()); } } - void _error(_ErrorType error, String additional) { + void _error(_ErrorType error, String? additional) { String message; switch (error) { @@ -798,17 +759,17 @@ class _PdfParser { } if (additional != null) { - message = message + additional + ' before ' + _lexer.position.toString(); + message = message + additional + ' before ' + _lexer!.position.toString(); } throw ArgumentError.value(error, message); } void _advance() { - if (_cTable != null && _cTable.validateSyntax) { - _lexer._getNextToken(); + if (_cTable != null && _cTable!.validateSyntax) { + _lexer!._getNextToken(); } - _next = _lexer._getNextToken(); + _next = _lexer!._getNextToken(); } } @@ -824,7 +785,7 @@ enum _ErrorType { class _Pair { //constructor - _Pair(_PdfName name, _IPdfPrimitive value) { + _Pair(_PdfName? name, _IPdfPrimitive? value) { _name = name; _value = value; } @@ -832,6 +793,6 @@ class _Pair { static _Pair get empty => _Pair(null, null); //Fields - _PdfName _name; - _IPdfPrimitive _value; + _PdfName? _name; + _IPdfPrimitive? _value; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_reader.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_reader.dart index 5d54f1706..4d03480e2 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_reader.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_reader.dart @@ -2,7 +2,7 @@ part of pdf; class _PdfReader { //Constructor - _PdfReader(List data) { + _PdfReader(List? data) { _streamReader = _StreamReader(data); _peekedByte = 0; _bytePeeked = false; @@ -10,8 +10,8 @@ class _PdfReader { } //Fields - _StreamReader _streamReader; - String _delimiters; + late _StreamReader _streamReader; + late String _delimiters; final List _spaceCharacters = [ '\u0020', '\u00a0', @@ -39,8 +39,8 @@ class _PdfReader { '\u000d', '\u0085' ]; - int _peekedByte; - bool _bytePeeked; + late int _peekedByte; + late bool _bytePeeked; //Properties int get position => _streamReader.position; @@ -48,7 +48,7 @@ class _PdfReader { _streamReader.position = value; } - int get length => _streamReader.length; + int? get length => _streamReader.length; //Implementation void _skipWhiteSpace() { @@ -58,7 +58,7 @@ class _PdfReader { value = _read(); } while ( value != -1 && _spaceCharacters.contains(String.fromCharCode(value))); - position = value == -1 ? _streamReader.length : (position - 1); + position = value == -1 ? _streamReader.length! : (position - 1); } } @@ -74,17 +74,17 @@ class _PdfReader { } int _read() { - int value = 0; + int? value = 0; if (_bytePeeked) { value = _getPeeked(value); } else { value = _streamReader.readByte(); } - return value; + return value!; } List _readBytes(int length) { - final List bytes = List(length); + final List bytes = List.filled(length, 0, growable: true); for (int i = 0; i < length; i++) { bytes[i] = _read(); } @@ -105,7 +105,7 @@ class _PdfReader { if (count > 0) { final List buf = List.filled(count, 0); int tempCount = 0; - for (int j = 0; j < count && position < length; j++) { + for (int j = 0; j < count && position < length!; j++) { buf[j] = _read(); tempCount++; } @@ -139,13 +139,13 @@ class _PdfReader { return character == '\n' || character == '\r'; } - int readData(List buffer, int index, int count) { + int readData(List? buffer, int index, int count) { if (count < 0) { throw ArgumentError.value(count, 'The value can\'t be less then zero'); } int i = index; if (_bytePeeked && count > 0) { - buffer[i] = _peekedByte; + buffer![i] = _peekedByte; _bytePeeked = false; --count; ++i; @@ -154,11 +154,11 @@ class _PdfReader { if (position == length) { count = 0; } else { - final int lp = length - position; + final int lp = length! - position; count = count > lp ? lp : count; final List bytes = _readBytes(count); for (int j = 0; j < count; j++) { - buffer[i + j] = bytes[j]; + buffer![i + j] = bytes[j]; } } i += count; @@ -166,13 +166,13 @@ class _PdfReader { return i - index; } - Map _copyBytes(List buffer, int index, int count) { - if (index < 0 || index > buffer.length) { + Map _copyBytes(List? buffer, int index, int count) { + if (index < 0 || index > buffer!.length) { throw ArgumentError.value(index, 'Invalid index to read'); } final int pos = position; for (int i = pos; - i < length && i < (pos + count) && index < buffer.length; + i < length! && i < (pos + count) && index < buffer.length; i++) { buffer[index] = _read(); index++; @@ -188,7 +188,7 @@ class _PdfReader { return String.fromCharCodes(_readBytes(length)); } - int _seekEnd() { + int? _seekEnd() { return _streamReader.length; } @@ -237,12 +237,12 @@ class _PdfReader { } int _searchForward(String token) { - List buf = List.filled(token.length, 0); + List? buf = List.filled(token.length, 0); bool isStartXref = false; while (true) { int pos = position; final int character = _read(); - buf[0] = character.toUnsigned(8); + buf![0] = character.toUnsigned(8); if (buf[0] == token.codeUnitAt(0)) { if (!isStartXref) { pos = position - 1; @@ -253,7 +253,7 @@ class _PdfReader { position = pos; if (length < token.length - 1) { return -1; - } else if (token == String.fromCharCodes(buf)) { + } else if (token == String.fromCharCodes(buf!)) { return pos; } else { position += 1; @@ -264,12 +264,12 @@ class _PdfReader { pos = position - 1; position = pos; int newPosition = pos; - List buff = + List? buff = List.filled(_Operators.startCrossReference.length, 0); final Map result = _copyBytes(buff, 1, _Operators.startCrossReference.length); buff = result['buffer']; - if (_Operators.startCrossReference == String.fromCharCodes(buff)) { + if (_Operators.startCrossReference == String.fromCharCodes(buff!)) { isStartXref = true; position = ++newPosition; } @@ -279,7 +279,7 @@ class _PdfReader { } } - int _getPeeked(int byteValue) { + int _getPeeked(int? byteValue) { if (_bytePeeked) { _bytePeeked = false; byteValue = _peekedByte; @@ -289,13 +289,13 @@ class _PdfReader { return byteValue; } - String _getEqualChar(int charCode) { - return charCode == -1 ? '\uffff' : String.fromCharCode(charCode); + String _getEqualChar(int? charCode) { + return charCode == -1 ? '\uffff' : String.fromCharCode(charCode!); } - String _getNextToken() { - String token = ''; - int character = 0; + String? _getNextToken() { + String? token = ''; + int? character = 0; _skipWhiteSpace(); character = _peek(); if (_isDelimiter(_getEqualChar(character))) { @@ -315,10 +315,10 @@ class _PdfReader { return token; } - Map _appendChar(String token) { + Map _appendChar(String? token) { final int character = _read(); if (character != -1) { - token += String.fromCharCode(character); + token = token! + String.fromCharCode(character); } final Map result = {}; result['token'] = token; @@ -327,7 +327,7 @@ class _PdfReader { } int _peek() { - int value = 0; + int? value = 0; if (_bytePeeked) { value = _getPeeked(value); } else { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_stream_writer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_stream_writer.dart index 37cd40da5..41f2d63aa 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_stream_writer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_stream_writer.dart @@ -3,12 +3,12 @@ part of pdf; /// Helper class to write PDF graphic streams easily. class _PdfStreamWriter implements _IPdfWriter { //Constructor - _PdfStreamWriter(_PdfStream stream) { + _PdfStreamWriter(_PdfStream? stream) { _stream = stream; } //Fields - _PdfStream _stream; + _PdfStream? _stream; //Implementation void _saveGraphicsState() { @@ -20,19 +20,19 @@ class _PdfStreamWriter implements _IPdfWriter { } void _writeOperator(String opcode) { - _stream._write(opcode); - _stream._write(_Operators.newLine); + _stream!._write(opcode); + _stream!._write(_Operators.newLine); } void _writeComment(String comment) { _writeOperator('% ' + comment); } - void _writePoint(double x, double y) { + void _writePoint(double? x, double y) { _write(x); - _stream._write(_Operators.whiteSpace); + _stream!._write(_Operators.whiteSpace); _write(-y); - _stream._write(_Operators.whiteSpace); + _stream!._write(_Operators.whiteSpace); } void _closePath() { @@ -40,115 +40,111 @@ class _PdfStreamWriter implements _IPdfWriter { } void _clipPath(bool useEvenOddRule) { - _stream._write(_Operators.clipPath); + _stream!._write(_Operators.clipPath); if (useEvenOddRule) { - _stream._write(_Operators.evenOdd); + _stream!._write(_Operators.evenOdd); } - _stream._write(_Operators.whiteSpace); - _stream._write(_Operators.endPath); - _stream._write(_Operators.newLine); + _stream!._write(_Operators.whiteSpace); + _stream!._write(_Operators.endPath); + _stream!._write(_Operators.newLine); } - void _appendRectangle(double x, double y, double width, double height) { + void _appendRectangle(double? x, double y, double? width, double height) { _writePoint(x, y); _writePoint(width, height); _writeOperator(_Operators.appendRectangle); } void _modifyCurrentMatrix(_PdfTransformationMatrix matrix) { - ArgumentError.checkNotNull(matrix, 'matrix'); - _stream._write(matrix._toString()); + _stream!._write(matrix._toString()); _writeOperator(_Operators.currentMatrix); } void _modifyTransformationMatrix(_PdfTransformationMatrix matrix) { - ArgumentError.checkNotNull(matrix, 'matrix'); - _stream._write(matrix._toString()); + _stream!._write(matrix._toString()); _writeOperator(_Operators.transformationMatrix); } void _setLineWidth(double width) { _write(width); - _stream._write(_Operators.whiteSpace); + _stream!._write(_Operators.whiteSpace); _writeOperator(_Operators.setLineWidth); } void _setLineCap(PdfLineCap lineCapStyle) { _write(lineCapStyle.index); - _stream._write(_Operators.whiteSpace); + _stream!._write(_Operators.whiteSpace); _writeOperator(_Operators.setLineCapStyle); } void _setLineJoin(PdfLineJoin lineJoinStyle) { _write(lineJoinStyle.index); - _stream._write(_Operators.whiteSpace); + _stream!._write(_Operators.whiteSpace); _writeOperator(_Operators.setLineJoinStyle); } - void _setMiterLimit(double miterLimit) { + void _setMiterLimit(double? miterLimit) { _write(miterLimit); - _stream._write(_Operators.whiteSpace); + _stream!._write(_Operators.whiteSpace); _writeOperator(_Operators.setMiterLimit); } - void _setLineDashPattern(List pattern, double patternOffset) { + void _setLineDashPattern(List? pattern, double patternOffset) { final _PdfArray patternArray = _PdfArray(pattern); patternArray.save(this); - _stream._write(_Operators.whiteSpace); + _stream!._write(_Operators.whiteSpace); _write(patternOffset); - _stream._write(_Operators.whiteSpace); + _stream!._write(_Operators.whiteSpace); _writeOperator(_Operators.setDashPattern); } void _setColorAndSpace( - PdfColor color, PdfColorSpace colorSpace, bool forStroking) { + PdfColor color, PdfColorSpace? colorSpace, bool forStroking) { if (!color.isEmpty) { - _stream._write(color._toString(colorSpace, forStroking)); - _stream._write(_Operators.newLine); + _stream!._write(color._toString(colorSpace, forStroking)); + _stream!._write(_Operators.newLine); } } void _setColorSpace(_PdfName name, bool forStroking) { - ArgumentError.checkNotNull(name); - _stream._write(name.toString()); - _stream._write(_Operators.whiteSpace); - _stream._write(forStroking + _stream!._write(name.toString()); + _stream!._write(_Operators.whiteSpace); + _stream!._write(forStroking ? _Operators.selectColorSpaceForStroking : _Operators.selectColorSpaceForNonStroking); - _stream._write(_Operators.newLine); + _stream!._write(_Operators.newLine); } void _setFont(PdfFont font, _PdfName name, double size) { - ArgumentError.checkNotNull(font); - _stream._write(name.toString()); - _stream._write(_Operators.whiteSpace); - _stream._write(size.toStringAsFixed(2)); - _stream._write(_Operators.whiteSpace); + _stream!._write(name.toString()); + _stream!._write(_Operators.whiteSpace); + _stream!._write(size.toStringAsFixed(2)); + _stream!._write(_Operators.whiteSpace); _writeOperator(_Operators.setFont); } void _setTextRenderingMode(int renderingMode) { _write(renderingMode); - _stream._write(_Operators.whiteSpace); + _stream!._write(_Operators.whiteSpace); _writeOperator(_Operators.setRenderingMode); } - void _setTextScaling(double textScaling) { + void _setTextScaling(double? textScaling) { _write(textScaling); - _stream._write(_Operators.whiteSpace); + _stream!._write(_Operators.whiteSpace); _writeOperator(_Operators.setTextScaling); } - void _setCharacterSpacing(double charSpacing) { + void _setCharacterSpacing(double? charSpacing) { _write(charSpacing); - _stream._write(_Operators.whiteSpace); - _stream._write(_Operators.setCharacterSpace); - _stream._write(_Operators.newLine); + _stream!._write(_Operators.whiteSpace); + _stream!._write(_Operators.setCharacterSpace); + _stream!._write(_Operators.newLine); } - void _setWordSpacing(double wordSpacing) { + void _setWordSpacing(double? wordSpacing) { _write(wordSpacing); - _stream._write(_Operators.whiteSpace); + _stream!._write(_Operators.whiteSpace); _writeOperator(_Operators.setWordSpace); } @@ -160,17 +156,16 @@ class _PdfStreamWriter implements _IPdfWriter { _writeOperator(_Operators.setTextOnNewLine); } - void _startNextLine([double x, double y]) { + void _startNextLine([double? x, double? y]) { if (x == null && y == null) { _writeOperator(_Operators.goToNextLine); } else { - _writePoint(x, y); + _writePoint(x, y!); _writeOperator(_Operators.setCoords); } } void _showText(_PdfString pdfString) { - ArgumentError.checkNotNull(pdfString); _writeText(pdfString); _writeOperator(_Operators.setText); } @@ -181,22 +176,21 @@ class _PdfStreamWriter implements _IPdfWriter { void _writeText(dynamic value) { if (value is _PdfString) { - _stream._write(value._pdfEncode(null)); + _stream!._write(value._pdfEncode(null)); } else if (value is List) { - _stream._write(_PdfString.stringMark[0]); - _stream._write(value); - _stream._write(_PdfString.stringMark[1]); + _stream!._write(_PdfString.stringMark[0]); + _stream!._write(value); + _stream!._write(_PdfString.stringMark[1]); } } void _setGraphicsState(_PdfName name) { - ArgumentError.checkNotNull(name); - if (name._name.isEmpty) { + if (name._name!.isEmpty) { throw ArgumentError.value( name, 'name', 'dictionary name cannot be empty'); } - _stream._write(name.toString()); - _stream._write(_Operators.whiteSpace); + _stream!._write(name.toString()); + _stream!._write(_Operators.whiteSpace); _writeOperator(_Operators.setGraphicsState); } @@ -215,36 +209,36 @@ class _PdfStreamWriter implements _IPdfWriter { } void _closeFillStrokePath(bool useEvenOddRule) { - _stream._write(_Operators.closeFillStrokePath); + _stream!._write(_Operators.closeFillStrokePath); if (useEvenOddRule) { - _stream._write(_Operators.evenOdd); + _stream!._write(_Operators.evenOdd); } - _stream._write(_Operators.newLine); + _stream!._write(_Operators.newLine); } void _fillStrokePath(bool useEvenOddRule) { - _stream._write(_Operators.fillStroke); + _stream!._write(_Operators.fillStroke); if (useEvenOddRule) { - _stream._write(_Operators.evenOdd); + _stream!._write(_Operators.evenOdd); } - _stream._write(_Operators.newLine); + _stream!._write(_Operators.newLine); } void _fillPath(bool useEvenOddRule) { - _stream._write(_Operators.fill); + _stream!._write(_Operators.fill); if (useEvenOddRule) { - _stream._write(_Operators.evenOdd); + _stream!._write(_Operators.evenOdd); } - _stream._write(_Operators.newLine); + _stream!._write(_Operators.newLine); } void _closeFillPath(bool useEvenOddRule) { _writeOperator(_Operators.closePath); - _stream._write(_Operators.fill); + _stream!._write(_Operators.fill); if (useEvenOddRule) { - _stream._write(_Operators.evenOdd); + _stream!._write(_Operators.evenOdd); } - _stream._write(_Operators.newLine); + _stream!._write(_Operators.newLine); } void _closeStrokePath() { @@ -256,8 +250,8 @@ class _PdfStreamWriter implements _IPdfWriter { } void _executeObject(_PdfName pdfName) { - _stream._write(pdfName.toString()); - _stream._write(_Operators.whiteSpace); + _stream!._write(pdfName.toString()); + _stream!._write(_Operators.whiteSpace); _writeOperator(_Operators.paintXObject); } @@ -270,37 +264,37 @@ class _PdfStreamWriter implements _IPdfWriter { } void _clear() { - _stream._clearStream(); + _stream!._clearStream(); } //_IPdfWriter members @override - PdfDocument get _document => null; + PdfDocument? get _document => null; @override //ignore:unused_element - set _document(PdfDocument value) { + set _document(PdfDocument? value) { throw ArgumentError.value( value, 'The method or operation is not implemented'); } @override //ignore:unused_element - int get _length => _stream._dataStream.length; + int get _length => _stream!._dataStream!.length; @override //ignore:unused_element - set _length(int value) { + set _length(int? value) { throw ArgumentError.value( value, 'The method or operation is not implemented'); } @override //ignore:unused_element - int get _position => _stream.position; + int? get _position => _stream!.position; @override //ignore:unused_element - set _position(int value) { + set _position(int? value) { throw ArgumentError.value( value, 'The method or operation is not implemented'); } @@ -310,7 +304,7 @@ class _PdfStreamWriter implements _IPdfWriter { if (pdfObject is _IPdfPrimitive) { pdfObject.save(this); } else if (pdfObject is int) { - _stream._write(pdfObject.toString()); + _stream!._write(pdfObject.toString()); } else if (pdfObject is double) { pdfObject = pdfObject.toStringAsFixed(2); if (pdfObject.endsWith('.00')) { @@ -320,11 +314,11 @@ class _PdfStreamWriter implements _IPdfWriter { pdfObject = pdfObject.substring(0, pdfObject.length - 3); } } - _stream._write(pdfObject); + _stream!._write(pdfObject); } else if (pdfObject is String) { - _stream._write(pdfObject); + _stream!._write(pdfObject); } else if (pdfObject is List) { - _stream._write(pdfObject); + _stream!._write(pdfObject); } else { throw ArgumentError.value( pdfObject, 'The method or operation is not implemented'); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_writer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_writer.dart index 1e73ba892..620b4d454 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_writer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_writer.dart @@ -10,18 +10,18 @@ class _PdfWriter implements _IPdfWriter { } //Fields - List _buffer; + List? _buffer; //_IPdfWriter members @override - PdfDocument _document; + PdfDocument? _document; @override //ignore:unused_field - int _length; + int? _length; @override - int _position; + int? _position; @override - void _write(dynamic data, [int end]) { + void _write(dynamic data, [int? end]) { if (data == null) { throw ArgumentError.value(data, 'data', 'value cannot be null'); } @@ -48,12 +48,12 @@ class _PdfWriter implements _IPdfWriter { } else { length = end; } - _length += length; - _position += length; + _length = _length! + length; + _position = _position! + length; if (end == null) { - _buffer.addAll(data); + _buffer!.addAll(data); } else { - _buffer.addAll(data.take(end)); + _buffer!.addAll(data.take(end)); } } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/stream_reader.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/stream_reader.dart index 5d6d7b236..2d67d66f2 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/stream_reader.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/stream_reader.dart @@ -2,29 +2,47 @@ part of pdf; class _StreamReader { //Constructor - _StreamReader(List data) { - if (data == null || data.isEmpty) { - ArgumentError.value(data, 'data cannot be null or empty'); - } + _StreamReader([List? data]) { _data = data; - position = 0; + _position = 0; } //Fields - List _data; - int position; + List? _data; + int? _position; //Properties - int get length => _data.length; + int? get length => _data!.length; + int get position => _position!; + set position(int value) { + if (value < 0) { + throw ArgumentError.value(value, 'position', 'Invalid position'); + } + _position = value; + } //Implementation - int readByte() { - if (position != length) { - final int result = _data[position]; - position++; + int? readByte() { + if (_position != length) { + final int? result = _data![position]; + _position = _position! + 1; return result; } else { return -1; } } + + int? read(List buffer, int offset, int length) { + _position = offset; + int pos = offset; + final int end = _position! + length; + while (_position! < end) { + final int byte = readByte()!; + if (byte == -1) { + break; + } + buffer[pos++] = byte; + } + return pos - offset; + } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/enum.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/enum.dart index cb093b73e..8f5724e2c 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/enum.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/enum.dart @@ -117,3 +117,21 @@ enum PdfNumberStyle { /// Uppercase roman numerals. upperRoman } + +/// Specifies tab order types for form fields +enum PdfFormFieldsTabOrder { + /// Form fields are visited default order + none, + + /// Form fields are visited rows running horizontally across the page + row, + + /// Form fields are visited column running vertically up and down the page + column, + + /// Form fields are visited based on the structure tree + structure, + + /// Form fields are visited manual order + manual, +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer.dart index e47694a28..19fc4cb97 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer.dart @@ -11,51 +11,51 @@ class PdfLayer implements _IPdfWrapper { // Fields bool _clipPageTemplates = true; - _PdfStream _content; - PdfGraphics _graphics; - PdfLayer _layer; - String _layerId; - _PdfReferenceHolder _referenceHolder; + _PdfStream? _content; + PdfGraphics? _graphics; + PdfLayer? _layer; + String? _layerId; + _PdfReferenceHolder? _referenceHolder; final Map _graphicsMap = {}; final Map _pageGraphics = {}; bool _isEmptyLayer = false; final List _parentLayer = []; - String _name; + String? _name; bool _visible = true; - _PdfDictionary _dictionary; + _PdfDictionary? _dictionary; bool _isEndState = false; final List _pages = []; - PdfPage _page; - PdfDocument _document; - PdfLayerCollection _layerCollection; - _PdfDictionary _printOption; - _PdfDictionary _usage; - PdfLayer _parent; + PdfPage? _page; + PdfDocument? _document; + PdfLayerCollection? _layerCollection; + _PdfDictionary? _printOption; + _PdfDictionary? _usage; + PdfLayer? _parent; final List _child = []; final _PdfArray _sublayer = _PdfArray(); final List _xobject = []; bool _pageParsed = false; - bool _isPresent; + bool? _isPresent; // Properties /// Gets the name of the layer - String get name => _name; + String? get name => _name; /// Sets the name of the layer - set name(String value) { + set name(String? value) { _name = value; if (_dictionary != null && _name != null && _name != '') { - _dictionary.setProperty(_DictionaryProperties.name, _PdfString(_name)); + _dictionary!.setProperty(_DictionaryProperties.name, _PdfString(_name!)); } } /// Gets the visibility of the page layer. bool get visible { if (_dictionary != null && - _dictionary.containsKey(_DictionaryProperties.visible)) { + _dictionary!.containsKey(_DictionaryProperties.visible)) { _visible = - (_dictionary[_DictionaryProperties.visible] as _PdfBoolean).value; + (_dictionary![_DictionaryProperties.visible] as _PdfBoolean).value!; } return _visible; } @@ -64,14 +64,14 @@ class PdfLayer implements _IPdfWrapper { set visible(bool value) { _visible = value; if (_dictionary != null) { - _dictionary[_DictionaryProperties.visible] = _PdfBoolean(value); + _dictionary![_DictionaryProperties.visible] = _PdfBoolean(value); } } /// Gets the collection of child [PdfLayer] PdfLayerCollection get layers { _layerCollection ??= PdfLayerCollection._withLayer(_document, _layer); - return _layerCollection; + return _layerCollection!; } // Implementation @@ -81,25 +81,25 @@ class PdfLayer implements _IPdfWrapper { if (_graphics == null) { page._contents._add(_PdfReferenceHolder(_layer)); } - final _PdfResources resource = page._getResources(); - if (_layer._layerId == null || _layer._layerId.isEmpty) { - _layer._layerId = 'OCG_' + _PdfResources._globallyUniqueIdentifier; + final _PdfResources? resource = page._getResources(); + if (_layer!._layerId == null || _layer!._layerId!.isEmpty) { + _layer!._layerId = 'OCG_' + _PdfResources._globallyUniqueIdentifier; } if (resource != null && resource.containsKey(_DictionaryProperties.properties)) { - final _PdfDictionary propertie = - resource[_DictionaryProperties.properties] as _PdfDictionary; + final _PdfDictionary? propertie = + resource[_DictionaryProperties.properties] as _PdfDictionary?; if (propertie != null) { - if (_layer._layerId[0] == '/') { - _layer._layerId = _layer._layerId.substring(1); + if (_layer!._layerId![0] == '/') { + _layer!._layerId = _layer!._layerId!.substring(1); } - propertie[_layer._layerId] = _layer._referenceHolder; + propertie[_layer!._layerId] = _layer!._referenceHolder; } else { - resource._properties[_layer._layerId] = _layer._referenceHolder; + resource._properties[_layer!._layerId] = _layer!._referenceHolder; resource[_DictionaryProperties.properties] = resource._properties; } } else { - resource._properties[_layer._layerId] = _layer._referenceHolder; + resource!._properties[_layer!._layerId] = _layer!._referenceHolder; resource[_DictionaryProperties.properties] = resource._properties; } @@ -114,35 +114,37 @@ class PdfLayer implements _IPdfWrapper { double lly = 0; double urx = 0; double ury = 0; - final _PdfArray mediaBox = page._dictionary._getValue( - _DictionaryProperties.mediaBox, _DictionaryProperties.parent); + final _PdfArray? mediaBox = page._dictionary._getValue( + _DictionaryProperties.mediaBox, _DictionaryProperties.parent) + as _PdfArray?; if (mediaBox != null) { // Lower Left X co-ordinate Value. - llx = (mediaBox[0] as _PdfNumber).value.toDouble(); + llx = (mediaBox[0] as _PdfNumber).value!.toDouble(); // Lower Left Y co-ordinate value. - lly = (mediaBox[1] as _PdfNumber).value.toDouble(); + lly = (mediaBox[1] as _PdfNumber).value!.toDouble(); // Upper right X co-ordinate value. - urx = (mediaBox[2] as _PdfNumber).value.toDouble(); + urx = (mediaBox[2] as _PdfNumber).value!.toDouble(); // Upper right Y co-ordinate value. - ury = (mediaBox[3] as _PdfNumber).value.toDouble(); + ury = (mediaBox[3] as _PdfNumber).value!.toDouble(); } - _PdfArray cropBox; + _PdfArray? cropBox; if (page._dictionary.containsKey(_DictionaryProperties.cropBox)) { cropBox = page._dictionary._getValue( - _DictionaryProperties.cropBox, _DictionaryProperties.parent); - final double cropX = (cropBox[0] as _PdfNumber).value.toDouble(); - final double cropY = (cropBox[1] as _PdfNumber).value.toDouble(); - final double cropRX = (cropBox[2] as _PdfNumber).value.toDouble(); - final double cropRY = (cropBox[3] as _PdfNumber).value.toDouble(); + _DictionaryProperties.cropBox, _DictionaryProperties.parent) + as _PdfArray?; + final double cropX = (cropBox![0] as _PdfNumber).value!.toDouble(); + final double cropY = (cropBox[1] as _PdfNumber).value!.toDouble(); + final double cropRX = (cropBox[2] as _PdfNumber).value!.toDouble(); + final double cropRY = (cropBox[3] as _PdfNumber).value!.toDouble(); if ((cropX < 0 || cropY < 0 || cropRX < 0 || cropRY < 0) && (cropY.abs().floor() == page.size.height.abs().floor()) && (cropX.abs().floor()) == page.size.width.abs().floor()) { final Size pageSize = Size([cropX, cropRX].reduce(max), [cropY, cropRY].reduce(max)); - _graphics = PdfGraphics._(pageSize, resources, _content); + _graphics = PdfGraphics._(pageSize, resources, _content!); } else { - _graphics = PdfGraphics._(page.size, resources, _content); - _graphics._cropBox = cropBox; + _graphics = PdfGraphics._(page.size, resources, _content!); + _graphics!._cropBox = cropBox; } } else if ((llx < 0 || lly < 0 || urx < 0 || ury < 0) && (lly.abs().floor() == page.size.height.abs().floor()) && @@ -150,78 +152,78 @@ class PdfLayer implements _IPdfWrapper { final Size pageSize = Size([llx, urx].reduce(max), [lly, ury].reduce(max)); if (pageSize.width <= 0 || pageSize.height <= 0) { - _graphics = PdfGraphics._(pageSize, resources, _content); + _graphics = PdfGraphics._(pageSize, resources, _content!); } } else { - _graphics = PdfGraphics._(page.size, resources, _content); + _graphics = PdfGraphics._(page.size, resources, _content!); } if (isPageHasMediaBox) { - _graphics._mediaBoxUpperRightBound = ury; + _graphics!._mediaBoxUpperRightBound = ury; } - if (page != null && !page._isLoadedPage) { - final PdfSectionCollection sectionCollection = page._section._parent; + if (!page._isLoadedPage) { + final PdfSectionCollection? sectionCollection = page._section!._parent; if (sectionCollection != null) { - _graphics.colorSpace = sectionCollection._document.colorSpace; + _graphics!.colorSpace = sectionCollection._document!.colorSpace; } } if (!_graphicsMap.containsKey(_graphics)) { - _graphicsMap[_graphics] = _graphics; + _graphicsMap[_graphics!] = _graphics!; } if (!_pageGraphics.containsKey(page)) { - _pageGraphics[page] = _graphics; + _pageGraphics[page] = _graphics!; } - _content._beginSave = _beginSaveContent; + _content!._beginSave = _beginSaveContent; } else { if (!_pages.contains(page)) { _graphicsContent(page); } else if (_pageGraphics.containsKey(page)) { _graphics = _pageGraphics[page]; - return _graphics; + return _graphics!; } } - _graphics._streamWriter._write(_Operators.newLine); - _graphics.save(); - _graphics._initializeCoordinates(); - if (_graphics._hasTransparencyBrush) { - _graphics._setTransparencyGroup(page); + _graphics!._streamWriter!._write(_Operators.newLine); + _graphics!.save(); + _graphics!._initializeCoordinates(); + if (_graphics!._hasTransparencyBrush) { + _graphics!._setTransparencyGroup(page); } - if (page != null && - page._isLoadedPage && + if (page._isLoadedPage && (page._rotation != PdfPageRotateAngle.rotateAngle0 || page._dictionary.containsKey(_DictionaryProperties.rotate))) { - _PdfArray cropBox; + _PdfArray? cropBox; if (page._dictionary.containsKey(_DictionaryProperties.cropBox)) { cropBox = page._dictionary._getValue( - _DictionaryProperties.cropBox, _DictionaryProperties.parent); + _DictionaryProperties.cropBox, _DictionaryProperties.parent) + as _PdfArray?; } _updatePageRotation(page, _graphics, cropBox); } - if (page != null && !page._isLoadedPage) { - final _Rectangle clipRect = page._section._getActualBounds(page, true); + if (!page._isLoadedPage) { + final _Rectangle clipRect = page._section!._getActualBounds(page, true); if (_clipPageTemplates) { if (page._origin.dx >= 0 && page._origin.dy >= 0) { - _graphics._clipTranslateMarginsWithBounds(clipRect); + _graphics!._clipTranslateMarginsWithBounds(clipRect); } } else { - final PdfMargins margins = page._section.pageSettings.margins; - _graphics._clipTranslateMargins(clipRect.x, clipRect.y, margins.left, + final PdfMargins margins = page._section!.pageSettings.margins; + _graphics!._clipTranslateMargins(clipRect.x, clipRect.y, margins.left, margins.top, margins.right, margins.bottom); } } if (!_pages.contains(page)) { _pages.add(page); } - _graphics._setLayer(null, this); - return _graphics; + _graphics!._setLayer(null, this); + return _graphics!; } void _updatePageRotation( - PdfPage page, PdfGraphics graphics, _PdfArray cropBox) { - _PdfNumber rotation; + PdfPage page, PdfGraphics? graphics, _PdfArray? cropBox) { + _PdfNumber? rotation; if (page._dictionary.containsKey(_DictionaryProperties.rotate)) { - rotation = page._dictionary[_DictionaryProperties.rotate]; + rotation = page._dictionary[_DictionaryProperties.rotate] as _PdfNumber?; rotation ??= rotation = _PdfCrossTable._dereference( - page._dictionary[_DictionaryProperties.rotate]); + page._dictionary[_DictionaryProperties.rotate]) as _PdfNumber?; } else if (page._rotation != PdfPageRotateAngle.rotateAngle0) { if (page._rotation == PdfPageRotateAngle.rotateAngle90) { rotation = _PdfNumber(90); @@ -231,17 +233,19 @@ class PdfLayer implements _IPdfWrapper { rotation = _PdfNumber(270); } } - if (rotation.value == 90) { - graphics.translateTransform(0, page.size.height); + if (rotation!.value == 90) { + graphics!.translateTransform(0, page.size.height); graphics.rotateTransform(-90); if (cropBox != null) { - final double height = (cropBox[3] as _PdfNumber).value.toDouble(); + final double height = (cropBox[3] as _PdfNumber).value!.toDouble(); final Size cropBoxSize = Size( - (cropBox[2] as _PdfNumber).value.toDouble(), - height != 0 ? height : (cropBox[1] as _PdfNumber).value.toDouble()); + (cropBox[2] as _PdfNumber).value!.toDouble(), + height != 0 + ? height + : (cropBox[1] as _PdfNumber).value!.toDouble()); final Offset cropBoxOffset = Offset( - (cropBox[0] as _PdfNumber).value.toDouble(), - (cropBox[1] as _PdfNumber).value.toDouble()); + (cropBox[0] as _PdfNumber).value!.toDouble(), + (cropBox[1] as _PdfNumber).value!.toDouble()); if (page.size.height < cropBoxSize.height) { graphics._clipBounds.size = _Size(page.size.height - cropBoxOffset.dy, cropBoxSize.width - cropBoxOffset.dx); @@ -254,10 +258,10 @@ class PdfLayer implements _IPdfWrapper { graphics._clipBounds.size = _Size(page.size.height, page.size.width); } } else if (rotation.value == 180) { - graphics.translateTransform(page.size.width, page.size.height); + graphics!.translateTransform(page.size.width, page.size.height); graphics.rotateTransform(-180); } else if (rotation.value == 270) { - graphics.translateTransform(page.size.width, 0); + graphics!.translateTransform(page.size.width, 0); graphics.rotateTransform(-270); graphics._clipBounds.size = _Size(page.size.height, page.size.width); } @@ -269,108 +273,105 @@ class PdfLayer implements _IPdfWrapper { page._contents._add(_PdfReferenceHolder(stream)); stream._beginSave = _beginSaveContent; if (!_graphicsMap.containsKey(_graphics)) { - _graphicsMap[_graphics] = _graphics; + _graphicsMap[_graphics!] = _graphics!; } if (!_pageGraphics.containsKey(page)) { - _pageGraphics[page] = _graphics; + _pageGraphics[page] = _graphics!; } } - void _beginSaveContent(Object sender, _SavePdfPrimitiveArgs e) { - if (_layer._graphicsMap != null) { - bool flag = false; - PdfGraphics keyValue; - _graphicsMap.forEach((PdfGraphics key, PdfGraphics values) { - if (!flag) { - _graphics = key; - if (!_isEmptyLayer) { - _beginLayer(_graphics); - _graphics._endMarkContent(); - } - _graphics._streamWriter - ._write(_Operators.restoreState + _Operators.newLine); - keyValue = key; - flag = true; + void _beginSaveContent(Object sender, _SavePdfPrimitiveArgs? e) { + bool flag = false; + PdfGraphics? keyValue; + _graphicsMap.forEach((PdfGraphics? key, PdfGraphics? values) { + if (!flag) { + _graphics = key; + if (!_isEmptyLayer) { + _beginLayer(_graphics); + _graphics!._endMarkContent(); } - }); - if (keyValue != null) { - _graphicsMap.remove(keyValue); + _graphics!._streamWriter! + ._write(_Operators.restoreState + _Operators.newLine); + keyValue = key; + flag = true; } + }); + if (keyValue != null) { + _graphicsMap.remove(keyValue); } } - void _beginLayer(PdfGraphics currentGraphics) { - if (_graphicsMap != null) { - if (_graphicsMap.containsKey(currentGraphics)) { - _graphics = _graphicsMap[currentGraphics]; - } else { - _graphics = currentGraphics; - } + void _beginLayer(PdfGraphics? currentGraphics) { + if (_graphicsMap.containsKey(currentGraphics)) { + _graphics = _graphicsMap[currentGraphics]; + } else { + _graphics = currentGraphics; } if (_graphics != null) { if (_name != null && _name != '') { _isEmptyLayer = true; if (_parentLayer.isNotEmpty) { for (int i = 0; i < _parentLayer.length; i++) { - _graphics._streamWriter - ._write('/OC /' + _parentLayer[i]._layerId + ' BDC\n'); + _graphics!._streamWriter! + ._write('/OC /' + _parentLayer[i]._layerId! + ' BDC\n'); } } if (name != null && name != '') { - _graphics._streamWriter._write('/OC /' + _layerId + ' BDC\n'); + _graphics!._streamWriter!._write('/OC /' + _layerId! + ' BDC\n'); _isEndState = true; } else { - _content._write('/OC /' + _layerId + ' BDC\n'); + _content!._write('/OC /' + _layerId! + ' BDC\n'); } } } } void _parsingLayerPage() { - if (_document != null && _document._isLoadedDocument) { - for (int i = 0; i < _document.pages.count; i++) { - final _PdfDictionary pageDictionary = _document.pages[i]._dictionary; - final PdfPage page = _document.pages[i]; + if (_document != null && _document!._isLoadedDocument) { + for (int i = 0; i < _document!.pages.count; i++) { + final _PdfDictionary pageDictionary = _document!.pages[i]._dictionary; + final PdfPage? page = _document!.pages[i]; if (pageDictionary.containsKey(_DictionaryProperties.resources)) { - final _PdfDictionary resources = _PdfCrossTable._dereference( + final _PdfDictionary? resources = _PdfCrossTable._dereference( pageDictionary[_DictionaryProperties.resources]) - as _PdfDictionary; + as _PdfDictionary?; if (resources != null && (resources.containsKey(_DictionaryProperties.properties)) || - (resources.containsKey(_DictionaryProperties.xObject))) { - final _PdfDictionary properties = _PdfCrossTable._dereference( - resources[_DictionaryProperties.properties]) as _PdfDictionary; - final _PdfDictionary xObject = _PdfCrossTable._dereference( - resources[_DictionaryProperties.xObject]) as _PdfDictionary; + (resources!.containsKey(_DictionaryProperties.xObject))) { + final _PdfDictionary? properties = _PdfCrossTable._dereference( + resources[_DictionaryProperties.properties]) as _PdfDictionary?; + final _PdfDictionary? xObject = _PdfCrossTable._dereference( + resources[_DictionaryProperties.xObject]) as _PdfDictionary?; if (properties != null) { - properties._items.forEach((_PdfName key, _IPdfPrimitive value) { + properties._items! + .forEach((_PdfName? key, _IPdfPrimitive? value) { if (value is _PdfReferenceHolder) { - final _PdfDictionary dictionary = - value.object as _PdfDictionary; + final _PdfDictionary? dictionary = + value.object as _PdfDictionary?; _parsingDictionary(dictionary, value, page, key); } }); - if (properties._items.isEmpty) { + if (properties._items!.isEmpty) { _pageParsed = true; } } if (xObject != null) { - xObject._items.forEach((_PdfName key, _IPdfPrimitive value) { + xObject._items!.forEach((_PdfName? key, _IPdfPrimitive? value) { _PdfReferenceHolder reference = value as _PdfReferenceHolder; _PdfDictionary dictionary = reference.object as _PdfDictionary; if (dictionary.containsKey('OC')) { - final _PdfName layerID = key; + final _PdfName? layerID = key; reference = dictionary['OC'] as _PdfReferenceHolder; dictionary = _PdfCrossTable._dereference(dictionary['OC']) as _PdfDictionary; final bool isPresent = - _parsingDictionary(dictionary, reference, page, layerID); + _parsingDictionary(dictionary, reference, page, layerID)!; if (isPresent) { - _layer._xobject.add(layerID._name); + _layer!._xobject.add(layerID!._name!); } } }); - if (xObject._items.isEmpty) { + if (xObject._items!.isEmpty) { _pageParsed = true; } } @@ -380,21 +381,21 @@ class PdfLayer implements _IPdfWrapper { } } - bool _parsingDictionary(_PdfDictionary dictionary, - _PdfReferenceHolder reference, PdfPage page, _PdfName layerID) { - if (_isPresent == null || !_isPresent) { + bool? _parsingDictionary(_PdfDictionary? dictionary, + _PdfReferenceHolder? reference, PdfPage? page, _PdfName? layerID) { + if (_isPresent == null || !_isPresent!) { _isPresent = false; - if (!dictionary.containsKey(_DictionaryProperties.name) && + if (!dictionary!.containsKey(_DictionaryProperties.name) && dictionary.containsKey(_DictionaryProperties.ocg)) { if (dictionary.containsKey(_DictionaryProperties.ocg)) { - final _PdfArray pdfArray = + final _PdfArray? pdfArray = _PdfCrossTable._dereference(dictionary[_DictionaryProperties.ocg]) - as _PdfArray; + as _PdfArray?; if (pdfArray == null) { reference = - dictionary[_DictionaryProperties.ocg] as _PdfReferenceHolder; + dictionary[_DictionaryProperties.ocg] as _PdfReferenceHolder?; dictionary = _PdfCrossTable._dereference( - dictionary[_DictionaryProperties.ocg]) as _PdfDictionary; + dictionary[_DictionaryProperties.ocg]) as _PdfDictionary?; if (dictionary != null && dictionary.containsKey(_DictionaryProperties.name)) { _isPresent = _setLayerPage(reference, page, layerID); @@ -403,7 +404,7 @@ class PdfLayer implements _IPdfWrapper { for (int a = 0; a < pdfArray.count; a++) { if (pdfArray[a] is _PdfReferenceHolder) { reference = pdfArray[a] as _PdfReferenceHolder; - dictionary = reference.object as _PdfDictionary; + dictionary = reference.object as _PdfDictionary?; _isPresent = _setLayerPage(reference, page, layerID); } } @@ -419,19 +420,19 @@ class PdfLayer implements _IPdfWrapper { } bool _setLayerPage( - _PdfReferenceHolder reference, PdfPage page, _PdfName layerID) { + _PdfReferenceHolder? reference, PdfPage? page, _PdfName? layerID) { bool isPresent = false; - if (_layer._referenceHolder != null) { - if (identical(_layer._referenceHolder, reference) || - identical(_layer._referenceHolder._object, reference._object) || - _layer._referenceHolder?.reference?._objNum == - reference?.reference?._objNum) { - _layer._pageParsed = true; + if (_layer!._referenceHolder != null) { + if (identical(_layer!._referenceHolder, reference) || + identical(_layer!._referenceHolder!._object, reference!._object) || + _layer!._referenceHolder?.reference?._objNum == + reference.reference?._objNum) { + _layer!._pageParsed = true; isPresent = true; - _layer._layerId = layerID._name; - _layer._page = page; - if (!_layer._pages.contains(page)) { - _layer._pages.add(page); + _layer!._layerId = layerID!._name; + _layer!._page = page; + if (!_layer!._pages.contains(page)) { + _layer!._pages.add(page!); } } } @@ -440,10 +441,10 @@ class PdfLayer implements _IPdfWrapper { //_IPdfWrapper elements @override - _IPdfPrimitive get _element => _content; + _IPdfPrimitive? get _element => _content; @override //ignore: unused_element - set _element(_IPdfPrimitive value) { - _content = value; + set _element(_IPdfPrimitive? value) { + _content = value as _PdfStream?; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer_collection.dart index e1a6e346d..b69d82f8f 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer_collection.dart @@ -4,7 +4,6 @@ part of pdf; class PdfLayerCollection extends PdfObjectCollection { // Contructor PdfLayerCollection._(PdfDocument document) { - ArgumentError.checkNotNull(document, 'document'); _document = document; _sublayer = false; if (document._isLoadedDocument) { @@ -12,16 +11,16 @@ class PdfLayerCollection extends PdfObjectCollection { } } - PdfLayerCollection._withLayer(PdfDocument document, PdfLayer layer) { + PdfLayerCollection._withLayer(PdfDocument? document, PdfLayer? layer) { _document = document; _parent = layer; _sublayer = true; } // Fields - PdfDocument _document; - bool _sublayer; - PdfLayer _parent; + PdfDocument? _document; + bool? _sublayer; + PdfLayer? _parent; final _PdfDictionary _optionalContent = _PdfDictionary(); final Map<_PdfReferenceHolder, PdfLayer> _layerDictionary = <_PdfReferenceHolder, PdfLayer>{}; @@ -30,12 +29,15 @@ class PdfLayerCollection extends PdfObjectCollection { //Properties /// Gets [PdfLayer] by its index from [PdfLayerCollection]. PdfLayer operator [](int index) { + if (index < 0 || index >= count) { + throw RangeError('index'); + } return _list[index] as PdfLayer; } // Implementation /// Creates a new [PdfLayer] with name and adds it to the end of the collection. - PdfLayer add({String name, bool visible}) { + PdfLayer add({String? name, bool? visible}) { final PdfLayer layer = PdfLayer._(); if (name != null) { layer.name = name; @@ -52,7 +54,7 @@ class PdfLayerCollection extends PdfObjectCollection { /// Removes layer from the collection by using layer or layer name and may also remove graphical content, if isRemoveGraphicalContent is true. void remove( - {PdfLayer layer, String name, bool isRemoveGraphicalContent = false}) { + {PdfLayer? layer, String? name, bool isRemoveGraphicalContent = false}) { if (layer != null) { _removeLayer(layer, isRemoveGraphicalContent); _list.remove(layer); @@ -83,9 +85,7 @@ class PdfLayerCollection extends PdfObjectCollection { } final PdfLayer layer = this[index]; _list.removeAt(index); - if (layer != null) { - _removeLayer(layer, isRemoveGraphicalContent); - } + _removeLayer(layer, isRemoveGraphicalContent); } /// Clears layers from the [PdfLayerCollection]. @@ -98,7 +98,6 @@ class PdfLayerCollection extends PdfObjectCollection { } int _add(PdfLayer layer) { - ArgumentError.checkNotNull(layer, 'layer'); _list.add(layer); final int index = count - 1; if (_document is PdfDocument) { @@ -114,126 +113,129 @@ class PdfLayerCollection extends PdfObjectCollection { _createOptionalContentDictionary(layer); ocProperties[_DictionaryProperties.defaultView] = _createOptionalContentViews(layer); - _document._catalog[_DictionaryProperties.ocProperties] = ocProperties; - _document._catalog + _document!._catalog[_DictionaryProperties.ocProperties] = ocProperties; + _document!._catalog .setProperty(_DictionaryProperties.ocProperties, ocProperties); } - _IPdfPrimitive _createOptionalContentDictionary(PdfLayer layer) { + _IPdfPrimitive? _createOptionalContentDictionary(PdfLayer layer) { final _PdfDictionary dictionary = _PdfDictionary(); - dictionary[_DictionaryProperties.name] = _PdfString(layer.name); + dictionary[_DictionaryProperties.name] = _PdfString(layer.name!); dictionary[_DictionaryProperties.type] = _PdfName('OCG'); dictionary[_DictionaryProperties.layerID] = _PdfName(layer._layerId); dictionary[_DictionaryProperties.visible] = _PdfBoolean(layer.visible); layer._usage = _setPrintOption(layer); dictionary[_DictionaryProperties.usage] = _PdfReferenceHolder(layer._usage); - _document._printLayer._add(_PdfReferenceHolder(dictionary)); + _document!._printLayer!._add(_PdfReferenceHolder(dictionary)); final _PdfReferenceHolder reference = _PdfReferenceHolder(dictionary); - _document._primitive._add(reference); + _document!._primitive!._add(reference); layer._referenceHolder = reference; layer._dictionary = dictionary; // Order of the layers - final _PdfDictionary ocProperties = _PdfCrossTable._dereference( - _document._catalog[_DictionaryProperties.ocProperties]) - as _PdfDictionary; + final _PdfDictionary? ocProperties = _PdfCrossTable._dereference( + _document!._catalog[_DictionaryProperties.ocProperties]) + as _PdfDictionary?; _createSublayer(ocProperties, reference, layer); if (layer.visible) { - _document._on._add(reference); + _document!._on!._add(reference); } else { - _document._off._add(reference); + _document!._off!._add(reference); } - return _document._primitive; + return _document!._primitive; } _PdfDictionary _setPrintOption(PdfLayer layer) { final _PdfDictionary _usage = _PdfDictionary(); layer._printOption = _PdfDictionary(); - layer._printOption[_DictionaryProperties.subtype] = _PdfName('Print'); + layer._printOption![_DictionaryProperties.subtype] = _PdfName('Print'); _usage[_DictionaryProperties.print] = _PdfReferenceHolder(layer._printOption); return _usage; } - void _createSublayer(_PdfDictionary ocProperties, + void _createSublayer(_PdfDictionary? ocProperties, _PdfReferenceHolder reference, PdfLayer layer) { if (_sublayer == false) { if (ocProperties != null) { - _PdfArray order; - final _PdfDictionary defaultview = _PdfCrossTable._dereference( - ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + _PdfArray? order; + final _PdfDictionary? defaultview = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary?; if (defaultview != null) { order = _PdfCrossTable._dereference( - defaultview[_DictionaryProperties.ocgOrder]) as _PdfArray; + defaultview[_DictionaryProperties.ocgOrder]) as _PdfArray?; } - if (_document._order != null && order != null) { - _document._order = order; + if (_document!._order != null && order != null) { + _document!._order = order; } - _document._order._add(reference); + _document!._order!._add(reference); } else { - _document._order._add(reference); + _document!._order!._add(reference); } } else { layer._parent = _parent; if (ocProperties != null) { - _PdfArray order; - final _PdfDictionary defaultview = _PdfCrossTable._dereference( - ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + _PdfArray? order; + final _PdfDictionary? defaultview = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary?; if (defaultview != null) { order = _PdfCrossTable._dereference( - defaultview[_DictionaryProperties.ocgOrder]) as _PdfArray; + defaultview[_DictionaryProperties.ocgOrder]) as _PdfArray?; } - if (_document._order != null && order != null) { - _document._order = order; + if (_document!._order != null && order != null) { + _document!._order = order; } } - if (_parent._child.isEmpty) { - _parent._sublayer._add(reference); - } else if (_document._order._contains(_parent._referenceHolder)) { + if (_parent!._child.isEmpty) { + _parent!._sublayer._add(reference); + } else if (_document!._order!._contains(_parent!._referenceHolder!)) { final int position = - _document._order._indexOf(_parent._referenceHolder); - _document._order._removeAt(position + 1); - _parent._sublayer._add(reference); + _document!._order!._indexOf(_parent!._referenceHolder!); + _document!._order!._removeAt(position + 1); + _parent!._sublayer._add(reference); } else { - _parent._sublayer._add(reference); + _parent!._sublayer._add(reference); } - if (_document._order._contains(_parent._referenceHolder)) { + if (_document!._order!._contains(_parent!._referenceHolder!)) { final int position = - _document._order._indexOf(_parent._referenceHolder); - _document._order._insert(position + 1, _parent._sublayer); + _document!._order!._indexOf(_parent!._referenceHolder!); + _document!._order!._insert(position + 1, _parent!._sublayer); } else { - if (_parent._parent != null) { - if (_parent._parent._sublayer._contains(_parent._referenceHolder)) { - int position = - _parent._parent._sublayer._indexOf(_parent._referenceHolder); - if (_parent._sublayer.count == 1) { - _parent._parent._sublayer - ._insert(position + 1, _parent._sublayer); + if (_parent!._parent != null) { + if (_parent!._parent!._sublayer + ._contains(_parent!._referenceHolder!)) { + int position = _parent!._parent!._sublayer + ._indexOf(_parent!._referenceHolder!); + if (_parent!._sublayer.count == 1) { + _parent!._parent!._sublayer + ._insert(position + 1, _parent!._sublayer); } - if (_document._order._contains(_parent._parent._referenceHolder)) { - position = - _document._order._indexOf(_parent._parent._referenceHolder); - _document._order._removeAt(position + 1); - _document._order._insert(position + 1, _parent._parent._sublayer); + if (_document!._order! + ._contains(_parent!._parent!._referenceHolder!)) { + position = _document!._order! + ._indexOf(_parent!._parent!._referenceHolder!); + _document!._order!._removeAt(position + 1); + _document!._order! + ._insert(position + 1, _parent!._parent!._sublayer); } } } } - _parent._child.add(layer); + _parent!._child.add(layer); - if (_parent._parentLayer.isEmpty) { - layer._parentLayer.add(_parent); + if (_parent!._parentLayer.isEmpty) { + layer._parentLayer.add(_parent!); } else { - for (int i = 0; i < _parent._parentLayer.length; i++) { - if (!layer._parentLayer.contains(_parent._parentLayer[i])) { - layer._parentLayer.add(_parent._parentLayer[i]); + for (int i = 0; i < _parent!._parentLayer.length; i++) { + if (!layer._parentLayer.contains(_parent!._parentLayer[i])) { + layer._parentLayer.add(_parent!._parentLayer[i]); } } - layer._parentLayer.add(_parent); + layer._parentLayer.add(_parent!); } } } @@ -241,14 +243,14 @@ class PdfLayerCollection extends PdfObjectCollection { _IPdfPrimitive _createOptionalContentViews(PdfLayer layer) { final _PdfArray usageApplication = _PdfArray(); _optionalContent[_DictionaryProperties.name] = _PdfString('Layers'); - _optionalContent[_DictionaryProperties.ocgOrder] = _document._order; - _optionalContent[_DictionaryProperties.ocgOn] = _document._on; - _optionalContent[_DictionaryProperties.ocgOff] = _document._off; + _optionalContent[_DictionaryProperties.ocgOrder] = _document!._order; + _optionalContent[_DictionaryProperties.ocgOn] = _document!._on; + _optionalContent[_DictionaryProperties.ocgOff] = _document!._off; final _PdfArray category = _PdfArray(); category._add(_PdfName('Print')); final _PdfDictionary applicationDictionary = _PdfDictionary(); applicationDictionary[_DictionaryProperties.category] = category; - applicationDictionary[_DictionaryProperties.ocg] = _document._printLayer; + applicationDictionary[_DictionaryProperties.ocg] = _document!._printLayer; applicationDictionary[_DictionaryProperties.event] = _PdfName('Print'); usageApplication._add(_PdfReferenceHolder(applicationDictionary)); _optionalContent[_DictionaryProperties.usageApplication] = usageApplication; @@ -256,12 +258,12 @@ class PdfLayerCollection extends PdfObjectCollection { } void _getDocumentLayer(PdfDocument document) { - _PdfDictionary layerDictionary; + _PdfDictionary? layerDictionary; _PdfReferenceHolder layerReference; if (document._catalog.containsKey(_DictionaryProperties.ocProperties)) { - final _PdfDictionary ocProperties = _PdfCrossTable._dereference( + final _PdfDictionary? ocProperties = _PdfCrossTable._dereference( document._catalog[_DictionaryProperties.ocProperties]) - as _PdfDictionary; + as _PdfDictionary?; if (ocProperties != null) { if (ocProperties.containsKey(_DictionaryProperties.ocg)) { final _PdfArray ocGroup = _PdfCrossTable._dereference( @@ -269,7 +271,7 @@ class PdfLayerCollection extends PdfObjectCollection { for (int i = 0; i < ocGroup.count; i++) { if (ocGroup[i] is _PdfReferenceHolder) { layerReference = ocGroup[i] as _PdfReferenceHolder; - layerDictionary = layerReference.object as _PdfDictionary; + layerDictionary = layerReference.object as _PdfDictionary?; final PdfLayer layer = PdfLayer._(); if (layerDictionary != null && layerDictionary.containsKey(_DictionaryProperties.name)) { @@ -278,19 +280,20 @@ class PdfLayerCollection extends PdfObjectCollection { layer.name = layerName.value; layer._dictionary = layerDictionary; layer._referenceHolder = layerReference; - final _IPdfPrimitive layerId = _PdfCrossTable._dereference( + final _IPdfPrimitive? layerId = _PdfCrossTable._dereference( layerDictionary[_DictionaryProperties.layerID]); if (layerId != null) { layer._layerId = layerId.toString(); } - final _PdfDictionary usage = _PdfCrossTable._dereference( + final _PdfDictionary? usage = _PdfCrossTable._dereference( layerDictionary[_DictionaryProperties.usage]) - as _PdfDictionary; + as _PdfDictionary?; if (usage != null) { - final _PdfDictionary printOption = + final _PdfDictionary? printOption = _PdfCrossTable._dereference( - usage[_DictionaryProperties.print]) as _PdfDictionary; + usage[_DictionaryProperties.print]) + as _PdfDictionary?; if (printOption != null) { layer._printOption = printOption; @@ -314,25 +317,26 @@ class PdfLayerCollection extends PdfObjectCollection { } void _checkLayerVisible(_PdfDictionary ocProperties) { - _PdfArray visible; - if (_document._catalog.containsKey(_DictionaryProperties.ocProperties)) { - final _PdfDictionary defaultView = _PdfCrossTable._dereference( - ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + _PdfArray? visible; + if (_document!._catalog.containsKey(_DictionaryProperties.ocProperties)) { + final _PdfDictionary? defaultView = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary?; if (defaultView != null && defaultView.containsKey(_DictionaryProperties.ocgOff)) { visible = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgOff]) as _PdfArray; + defaultView[_DictionaryProperties.ocgOff]) as _PdfArray?; } if (visible != null) { for (int i = 0; i < visible.count; i++) { - final PdfLayer pdfLayer = _layerDictionary[visible[i]]; + final PdfLayer? pdfLayer = + _layerDictionary[visible[i] as _PdfReferenceHolder]; if (pdfLayer != null) { pdfLayer._visible = false; if (pdfLayer._dictionary != null && - pdfLayer._dictionary + pdfLayer._dictionary! .containsKey(_DictionaryProperties.visible)) { - pdfLayer._dictionary.setProperty( + pdfLayer._dictionary!.setProperty( _DictionaryProperties.visible, _PdfBoolean(false)); } } @@ -342,11 +346,11 @@ class PdfLayerCollection extends PdfObjectCollection { } void _checkParentLayer(_PdfDictionary ocProperties) { - final _PdfDictionary defaultView = _PdfCrossTable._dereference( - ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + final _PdfDictionary? defaultView = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary?; if (defaultView != null) { - final _PdfArray array = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgOrder]) as _PdfArray; + final _PdfArray? array = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOrder]) as _PdfArray?; if (array != null) { _parsingLayerOrder(array, _layerDictionary); } @@ -354,17 +358,18 @@ class PdfLayerCollection extends PdfObjectCollection { } void _checkLayerLock(_PdfDictionary ocProperties) { - _PdfArray locked; - final _PdfDictionary defaultView = _PdfCrossTable._dereference( - ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + _PdfArray? locked; + final _PdfDictionary? defaultView = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary?; if (defaultView != null && defaultView.containsKey(_DictionaryProperties.ocgLock)) { locked = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgLock]) as _PdfArray; + defaultView[_DictionaryProperties.ocgLock]) as _PdfArray?; } if (locked != null) { for (int i = 0; i < locked.count; i++) { - final PdfLayer pdfLayer = _layerDictionary[locked[i]]; + final PdfLayer? pdfLayer = + _layerDictionary[locked[i] as _PdfReferenceHolder]; if (pdfLayer != null) { continue; } @@ -373,41 +378,38 @@ class PdfLayerCollection extends PdfObjectCollection { } void _createLayerHierarchical(_PdfDictionary ocProperties) { - final _PdfDictionary defaultView = _PdfCrossTable._dereference( - ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; + final _PdfDictionary? defaultView = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary?; if (defaultView != null && defaultView.containsKey(_DictionaryProperties.ocgOrder)) { - if (_layerDictionary != null && _layerDictionary.isNotEmpty) { + if (_layerDictionary.isNotEmpty) { _list.clear(); _layerDictionary.forEach((_PdfReferenceHolder key, PdfLayer value) { - final PdfLayer pdflayer = value; - if (pdflayer != null) { - if (pdflayer._parent == null && !_list.contains(pdflayer)) { - _list.add(pdflayer); - } else if (pdflayer._child.isNotEmpty) { - _addChildlayer(pdflayer._parent); - } else if (pdflayer._parent != null && - pdflayer._child.isEmpty && - !pdflayer._parent.layers._list.contains(pdflayer)) { - pdflayer._parent.layers._addNestedLayer(pdflayer); - } + final PdfLayer pdfLayer = value; + if (pdfLayer._parent == null && !_list.contains(pdfLayer)) { + _list.add(pdfLayer); + } else if (pdfLayer._child.isNotEmpty) { + _addChildlayer(pdfLayer._parent!); + } else if (pdfLayer._parent != null && + pdfLayer._child.isEmpty && + !pdfLayer._parent!.layers._list.contains(pdfLayer)) { + pdfLayer._parent!.layers._addNestedLayer(pdfLayer); } }); } } } - void _addChildlayer(PdfLayer pdflayer) { - for (int i = 0; i < pdflayer._child.length; i++) { - final PdfLayer child = pdflayer._child[i]; - if (!pdflayer.layers._list.contains(child)) { - pdflayer.layers._addNestedLayer(child); + void _addChildlayer(PdfLayer pdfLayer) { + for (int i = 0; i < pdfLayer._child.length; i++) { + final PdfLayer? child = pdfLayer._child[i]; + if (!pdfLayer.layers._list.contains(child)) { + pdfLayer.layers._addNestedLayer(child!); } } } int _addNestedLayer(PdfLayer layer) { - ArgumentError.checkNotNull(layer, 'layer'); _list.add(layer); layer._layer = layer; return _list.length - 1; @@ -415,12 +417,12 @@ class PdfLayerCollection extends PdfObjectCollection { void _parsingLayerOrder( _PdfArray array, Map<_PdfReferenceHolder, PdfLayer> layerDictionary, - [PdfLayer parent]) { + [PdfLayer? parent]) { _PdfReferenceHolder reference; - PdfLayer layer; + PdfLayer? layer; for (int i = 0; i < array.count; i++) { if (array[i] is _PdfReferenceHolder) { - reference = array[i]; + reference = array[i] as _PdfReferenceHolder; layerDictionary.forEach((_PdfReferenceHolder key, PdfLayer value) { if (identical(key.object, reference.object) || identical(key.reference, reference.reference)) { @@ -429,18 +431,18 @@ class PdfLayerCollection extends PdfObjectCollection { }); if (layer != null) { if (parent != null) { - parent._child.add(layer); + parent._child.add(layer!); if (parent._parentLayer.isEmpty) { - layer._parentLayer.add(parent); - layer._parent = parent; + layer!._parentLayer.add(parent); + layer!._parent = parent; } else { for (int j = 0; j < parent._parentLayer.length; j++) { - if (!layer._parentLayer.contains(parent._parentLayer[j])) { - layer._parentLayer.add(parent._parentLayer[j]); + if (!layer!._parentLayer.contains(parent._parentLayer[j])) { + layer!._parentLayer.add(parent._parentLayer[j]); } } - layer._parentLayer.add(parent); - layer._parent = parent; + layer!._parentLayer.add(parent); + layer!._parent = parent; } } if (array.count > i + 1 && @@ -448,13 +450,13 @@ class PdfLayerCollection extends PdfObjectCollection { i++; final _PdfArray pdfArray = _PdfCrossTable._dereference(array[i]) as _PdfArray; - layer._sublayer._add(pdfArray); + layer!._sublayer._add(pdfArray); _parsingLayerOrder(pdfArray, layerDictionary, layer); } } } else if (_PdfCrossTable._dereference(array[i]) is _PdfArray) { - final _PdfArray subarray = - _PdfCrossTable._dereference(array[i]) as _PdfArray; + final _PdfArray? subarray = + _PdfCrossTable._dereference(array[i]) as _PdfArray?; if (subarray == null) { return; } @@ -471,63 +473,60 @@ class PdfLayerCollection extends PdfObjectCollection { } void _removeLayer(PdfLayer layer, bool isRemoveContent) { - ArgumentError.checkNotNull(layer, 'layer'); - _PdfDictionary dictionary; - if (layer != null || _document != null) { - if (_document != null) { - dictionary = _document._catalog; - if (dictionary.containsKey(_DictionaryProperties.ocProperties)) { - final _PdfDictionary ocPropertie = _PdfCrossTable._dereference( - dictionary[_DictionaryProperties.ocProperties]) as _PdfDictionary; - if (ocPropertie != null) { - final _PdfArray ocGroup = _PdfCrossTable._dereference( - ocPropertie[_DictionaryProperties.ocg]) as _PdfArray; - if (ocGroup != null) { - _removeOCProperties(ocGroup, layer._referenceHolder); - } - if (ocPropertie.containsKey(_DictionaryProperties.defaultView)) { - final _PdfDictionary defaultView = _PdfCrossTable._dereference( - ocPropertie[_DictionaryProperties.defaultView]) - as _PdfDictionary; - if (defaultView != null) { - _PdfArray _on, off; - if (defaultView.containsKey(_DictionaryProperties.ocgOrder)) { - final _PdfArray order = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgOrder]) as _PdfArray; - if (order != null) { - _removeOrder(layer, order, <_PdfArray>[]); - // _removeOCProperties(order, layer._referenceHolder); - } - } - if (defaultView.containsKey(_DictionaryProperties.ocgLock)) { - final _PdfArray locked = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgLock]) as _PdfArray; - if (locked != null) { - _removeOCProperties(locked, layer._referenceHolder); - } - } - if (defaultView.containsKey(_DictionaryProperties.ocgOff)) { - off = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgOff]) as _PdfArray; + _PdfDictionary? dictionary; + if (_document != null) { + dictionary = _document!._catalog; + if (dictionary.containsKey(_DictionaryProperties.ocProperties)) { + final _PdfDictionary? ocPropertie = _PdfCrossTable._dereference( + dictionary[_DictionaryProperties.ocProperties]) as _PdfDictionary?; + if (ocPropertie != null) { + final _PdfArray? ocGroup = _PdfCrossTable._dereference( + ocPropertie[_DictionaryProperties.ocg]) as _PdfArray?; + if (ocGroup != null) { + _removeOCProperties(ocGroup, layer._referenceHolder); + } + if (ocPropertie.containsKey(_DictionaryProperties.defaultView)) { + final _PdfDictionary? defaultView = _PdfCrossTable._dereference( + ocPropertie[_DictionaryProperties.defaultView]) + as _PdfDictionary?; + if (defaultView != null) { + _PdfArray? _on, off; + if (defaultView.containsKey(_DictionaryProperties.ocgOrder)) { + final _PdfArray? order = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOrder]) as _PdfArray?; + if (order != null) { + _removeOrder(layer, order, <_PdfArray>[]); + // _removeOCProperties(order, layer._referenceHolder); } - if (defaultView.containsKey(_DictionaryProperties.ocgOn)) { - _on = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgOn]) as _PdfArray; - } else if (_on == null && defaultView.containsKey('ON')) { - _on = _PdfCrossTable._dereference(defaultView['ON']) - as _PdfArray; + } + if (defaultView.containsKey(_DictionaryProperties.ocgLock)) { + final _PdfArray? locked = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgLock]) as _PdfArray?; + if (locked != null) { + _removeOCProperties(locked, layer._referenceHolder); } - if (defaultView - .containsKey(_DictionaryProperties.usageApplication)) { - final _PdfArray usage = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.usageApplication]) - as _PdfArray; - if (usage != null) { - _removeOCProperties(usage, layer._referenceHolder); - } + } + if (defaultView.containsKey(_DictionaryProperties.ocgOff)) { + off = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOff]) as _PdfArray?; + } + if (defaultView.containsKey(_DictionaryProperties.ocgOn)) { + _on = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOn]) as _PdfArray?; + } else if (_on == null && defaultView.containsKey('ON')) { + _on = _PdfCrossTable._dereference(defaultView['ON']) + as _PdfArray?; + } + if (defaultView + .containsKey(_DictionaryProperties.usageApplication)) { + final _PdfArray? usage = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.usageApplication]) + as _PdfArray?; + if (usage != null) { + _removeOCProperties(usage, layer._referenceHolder); } - _removeVisible(layer, _on, off); } + _removeVisible(layer, _on, off); } } } @@ -538,7 +537,7 @@ class PdfLayerCollection extends PdfObjectCollection { } } - void _removeVisible(PdfLayer layer, _PdfArray _on, _PdfArray off) { + void _removeVisible(PdfLayer layer, _PdfArray? _on, _PdfArray? off) { if (layer.visible) { if (_on != null) { _removeOCProperties(_on, layer._referenceHolder); @@ -553,54 +552,51 @@ class PdfLayerCollection extends PdfObjectCollection { void _removeOrder( PdfLayer layer, _PdfArray order, List<_PdfArray> arrayList) { bool isRemoveOrder = false; - if (order != null) { - for (int i = 0; i < order.count; i++) { - if (order[i] is _PdfReferenceHolder) { - final _PdfReferenceHolder holder = order[i]; - if (identical(holder.object, layer._referenceHolder.object) || - identical(holder.reference, layer._referenceHolder.reference)) { - if (i != order.count - 1) { - if (order[i + 1] is _PdfArray) { - order._removeAt(i); - order._removeAt(i); - isRemoveOrder = true; - break; - } else { - order._removeAt(i); - isRemoveOrder = true; - break; - } + for (int i = 0; i < order.count; i++) { + if (order[i] is _PdfReferenceHolder) { + final _PdfReferenceHolder holder = order[i] as _PdfReferenceHolder; + if (identical(holder.object, layer._referenceHolder!.object) || + identical(holder.reference, layer._referenceHolder!.reference)) { + if (i != order.count - 1) { + if (order[i + 1] is _PdfArray) { + order._removeAt(i); + order._removeAt(i); + isRemoveOrder = true; + break; } else { order._removeAt(i); isRemoveOrder = true; break; } + } else { + order._removeAt(i); + isRemoveOrder = true; + break; } - } else if (order[i] is _PdfArray) { - arrayList.add(order[i] as _PdfArray); } + } else if (order[i] is _PdfArray) { + arrayList.add(order[i] as _PdfArray); } } if (!isRemoveOrder) { - if (arrayList != null) { - for (int i = 0; i < arrayList.length; i++) { - order = arrayList[i]; - arrayList.removeAt(i); - i = i - 1; - _removeOrder(layer, order, arrayList); - } + for (int i = 0; i < arrayList.length; i++) { + order = arrayList[i]; + arrayList.removeAt(i); + i = i - 1; + _removeOrder(layer, order, arrayList); } } } void _removeOCProperties( - _PdfArray content, _PdfReferenceHolder referenceHolder) { + _PdfArray content, _PdfReferenceHolder? referenceHolder) { bool isChange = false; for (int i = 0; i < content.count; i++) { - if (content._elements[i] is _PdfReferenceHolder) { - final _PdfReferenceHolder holder = content._elements[i]; - if (holder.reference != null && referenceHolder.reference != null) { - if (holder.reference._objNum == referenceHolder.reference._objNum) { + final _IPdfPrimitive? primitive = content._elements[i]; + if (primitive != null && primitive is _PdfReferenceHolder) { + final _PdfReferenceHolder holder = primitive; + if (holder.reference != null && referenceHolder!.reference != null) { + if (holder.reference!._objNum == referenceHolder.reference!._objNum) { content._elements.removeAt(i); isChange = true; i--; @@ -609,13 +605,13 @@ class PdfLayerCollection extends PdfObjectCollection { content._elements.removeAt(i); isChange = true; i--; - } else if (identical(holder._object, referenceHolder._object)) { + } else if (identical(holder._object, referenceHolder!._object)) { content._elements.removeAt(i); isChange = true; i--; } - } else if (content._elements[i] is _PdfArray) { - _removeOCProperties(content._elements[i], referenceHolder); + } else if (primitive != null && primitive is _PdfArray) { + _removeOCProperties(primitive, referenceHolder); } } if (isChange) { @@ -624,9 +620,9 @@ class PdfLayerCollection extends PdfObjectCollection { } void _removeLayerContent(PdfLayer layer) { - _PdfDictionary properties; + _PdfDictionary? properties; bool isSkip = false; - _PdfDictionary xObject; + _PdfDictionary? xObject; if (!layer._pageParsed) { layer._parsingLayerPage(); _removeLayerContent(layer); @@ -634,32 +630,32 @@ class PdfLayerCollection extends PdfObjectCollection { } if (layer._pages.isNotEmpty) { for (int i = 0; i < layer._pages.length; i++) { - final _PdfDictionary resource = _PdfCrossTable._dereference( + final _PdfDictionary? resource = _PdfCrossTable._dereference( layer._pages[i]._dictionary[_DictionaryProperties.resources]) - as _PdfDictionary; + as _PdfDictionary?; if (resource != null) { properties = _PdfCrossTable._dereference( - resource[_DictionaryProperties.properties]) as _PdfDictionary; + resource[_DictionaryProperties.properties]) as _PdfDictionary?; xObject = _PdfCrossTable._dereference( - resource[_DictionaryProperties.xObject]) as _PdfDictionary; + resource[_DictionaryProperties.xObject]) as _PdfDictionary?; if (properties != null) { if (properties.containsKey(layer._layerId)) { properties.remove(layer._layerId); } } if (xObject != null && layer._xobject.isNotEmpty) { - for (final _PdfName key in xObject._items.keys) { - if (layer._xobject.contains(key._name)) { + for (final _PdfName? key in xObject._items!.keys) { + if (layer._xobject.contains(key!._name)) { xObject.remove(key); } - if (xObject._items.isEmpty) { + if (xObject._items!.isEmpty) { break; } } } final _PdfArray content = layer._pages[i]._contents; for (int m = 0; m < content.count; m++) { - List stream = []; + List? stream = []; final _PdfStream data = _PdfStream(); final _PdfStream pageContent = _PdfCrossTable._dereference(content[m]) as _PdfStream; @@ -668,11 +664,12 @@ class PdfLayerCollection extends PdfObjectCollection { } stream = pageContent._dataStream; final _ContentParser parser = _ContentParser(stream); - final _PdfRecordCollection recordCollection = parser._readContent(); + final _PdfRecordCollection recordCollection = + parser._readContent()!; for (int j = 0; j < recordCollection._recordCollection.length; j++) { - final String mOperator = + final String? mOperator = recordCollection._recordCollection[j]._operatorName; if (mOperator == 'BMC' || mOperator == 'EMC' || @@ -683,7 +680,7 @@ class PdfLayerCollection extends PdfObjectCollection { } if (mOperator == _Operators.paintXObject) { if (layer._xobject.contains( - recordCollection._recordCollection[j]._operands[0])) { + recordCollection._recordCollection[j]._operands![0])) { isSkip = true; } } @@ -709,9 +706,9 @@ class PdfLayerCollection extends PdfObjectCollection { } isSkip = false; } - if (data._dataStream.isNotEmpty) { + if (data._dataStream!.isNotEmpty) { pageContent.clear(); - pageContent._dataStream.clear(); + pageContent._dataStream!.clear(); pageContent._write(data._dataStream); } else { pageContent.clear(); @@ -722,11 +719,11 @@ class PdfLayerCollection extends PdfObjectCollection { } } - void _processBeginMarkContent(PdfLayer parser, String mOperator, - List operands, _PdfStream data) { + void _processBeginMarkContent(PdfLayer parser, String? mOperator, + List? operands, _PdfStream data) { if ('BDC' == mOperator) { - String operand; - if (operands.length > 1 && ((operands[0]) == '/OC')) { + String? operand; + if (operands!.length > 1 && ((operands[0]) == '/OC')) { operand = operands[1].substring(1); } if (_bdcCount > 0) { @@ -744,7 +741,7 @@ class PdfLayerCollection extends PdfObjectCollection { } void _streamWrite( - List operands, String mOperator, bool skip, _PdfStream data) { + List? operands, String? mOperator, bool skip, _PdfStream data) { _PdfString pdfString; if (skip && _bdcCount > 0) { return; @@ -756,7 +753,7 @@ class PdfLayerCollection extends PdfObjectCollection { data._write(_Operators.whiteSpace); } } - pdfString = _PdfString(mOperator); + pdfString = _PdfString(mOperator!); data._write(pdfString.data); data._write(_Operators.newLine); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page.dart index 77e887991..29b5e97da 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page.dart @@ -2,18 +2,41 @@ part of pdf; /// Provides methods and properties to create PDF pages /// and its elements, [PdfPage]. +/// ```dart +/// //Create a new PDF documentation +/// PdfDocument document = PdfDocument(); +/// //Create a new PDF page and draw the text +/// document.pages.add().graphics.drawString( +/// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), +/// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfPage implements _IPdfWrapper { //Constructor /// Initializes a new instance of the [PdfPage] class. + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF page + /// PdfPage page = document.pages.add(); + /// //Draw the text to the page + /// page.graphics.drawString( + /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), + /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfPage() { _initialize(); } PdfPage._fromDictionary(PdfDocument document, _PdfCrossTable crossTable, _PdfDictionary dictionary) { - ArgumentError.checkNotNull(document); - ArgumentError.checkNotNull(crossTable); - ArgumentError.checkNotNull(dictionary); _pdfDocument = document; _dictionary = dictionary; _crossTable = crossTable; @@ -24,67 +47,84 @@ class PdfPage implements _IPdfWrapper { } //Fields - bool _isNewPage; - PdfSection _section; - PdfAnnotationCollection _annotations; - _PdfDictionary _dictionary; - _PdfResources _resources; - PdfPageLayerCollection _layers; + bool? _isNewPage; + PdfSection? _section; + PdfAnnotationCollection? _annotations; + late _PdfDictionary _dictionary; + _PdfResources? _resources; + PdfPageLayerCollection? _layers; int _defaultLayerIndex = -1; bool _isLoadedPage = false; - PdfDocument _pdfDocument; - Size _size; - _PdfCrossTable _crossTable; + PdfDocument? _pdfDocument; + Size? _size; + _PdfCrossTable? _crossTable; bool _checkResources = false; final List<_PdfDictionary> _terminalAnnotation = <_PdfDictionary>[]; final _PdfArray _annotsReference = _PdfArray(); - bool _isTextExtraction; - bool _graphicStateUpdated; + late bool _isTextExtraction; + late bool _graphicStateUpdated; bool _isDefaultGraphics = false; + PdfFormFieldsTabOrder _formFieldsTabOrder = PdfFormFieldsTabOrder.none; /// Raises before the page saves. - Function _beginSave; + Function? _beginSave; //Properties /// Gets size of the PDF page- Read only + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF page and Gets the size of its page + /// Size size = document.pages.add().size; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` Size get size { if (_isLoadedPage) { - if (_size == null || (_size.width == 0 && _size.height == 0)) { + if (_size == null || (_size!.width == 0 && _size!.height == 0)) { double width = 0; double height = 0; - final _IPdfPrimitive primitive = _dictionary._getValue( + final _IPdfPrimitive? primitive = _dictionary._getValue( _DictionaryProperties.mediaBox, _DictionaryProperties.parent); if (primitive is _PdfArray) { final _PdfArray mBox = primitive; - final num m0 = (mBox[0] as _PdfNumber).value; - final num m1 = (mBox[1] as _PdfNumber).value; - final num m2 = (mBox[2] as _PdfNumber).value; - final num m3 = (mBox[3] as _PdfNumber).value; + final num m0 = (mBox[0] as _PdfNumber).value!; + final num? m1 = (mBox[1] as _PdfNumber).value; + final num m2 = (mBox[2] as _PdfNumber).value!; + final num? m3 = (mBox[3] as _PdfNumber).value; width = (m2 - m0).toDouble(); - height = m3 != 0 ? (m3 - m1).toDouble() : m1.toDouble(); + height = m3 != 0 ? (m3! - m1!).toDouble() : m1!.toDouble(); } _size = Size(width, height); } - return _size; + return _size!; } else { - return _section.pageSettings.size; + return _section!.pageSettings.size; } } Offset get _origin { if (_section != null) { - return _section.pageSettings._origin.offset; + return _section!.pageSettings._origin.offset; } else { return Offset.zero; } } - PdfDocument get _document { + PdfDocument? get _document { if (_isLoadedPage) { return _pdfDocument; } else { - if (_section != null && _section._parent != null) { - return _section._parent._document; + if (_section != null) { + if (_section!._parent != null) { + return _section!._parent!._document; + } else if (_section!._pdfDocument != null) { + return _section!._pdfDocument; + } else { + return null; + } } else { return null; } @@ -92,37 +132,89 @@ class PdfPage implements _IPdfWrapper { } /// Gets a collection of the annotations of the page- Read only. + /// ```dart + /// //Creates a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Creates a rectangle annotation + /// PdfRectangleAnnotation rectangleAnnotation = PdfRectangleAnnotation( + /// Rect.fromLTWH(0, 30, 80, 80), 'Rectangle Annotation', + /// author: 'Syncfusion', + /// color: PdfColor(255, 0, 0), + /// modifiedDate: DateTime.now()); + /// //Create a new PDF page and Adds the annotation to the PDF page + /// document.pages.add().annotations.add(rectangleAnnotation); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfAnnotationCollection get annotations { if (!_isLoadedPage) { if (_annotations == null) { _annotations = PdfAnnotationCollection(this); if (!_dictionary.containsKey(_DictionaryProperties.annots)) { _dictionary[_DictionaryProperties.annots] = - _annotations._internalAnnotations; + _annotations!._internalAnnotations; } - _annotations._internalAnnotations = - _dictionary[_DictionaryProperties.annots] as _PdfArray; + _annotations!._internalAnnotations = + _dictionary[_DictionaryProperties.annots] as _PdfArray?; } } else { if (_annotations == null) { // Create the annotations. - _createAnnotations([]); + _createAnnotations(_getWidgetReferences()); } if (_annotations == null || - (_annotations._annotations.count == 0 && _annotations.count != 0)) { + (_annotations!._annotations.count == 0 && _annotations!.count != 0)) { _annotations = PdfAnnotationCollection._(this); } } - return _annotations; + return _annotations!; } - /// Gets the graphics of the [DefaultLayer]. + /// Gets the graphics of the `defaultLayer`. + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF page and draw the text + /// document.pages.add().graphics.drawString( + /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), + /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGraphics get graphics { _isDefaultGraphics = true; return defaultLayer.graphics; } /// Gets the collection of the page's layers (Read only). + /// ```dart + /// //Creates a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Creates a new page + /// PdfPage page = document.pages.add(); + /// //Gets the layers from the page and Add the new layer. + /// PdfPageLayer layer = page.layers.add(name: 'Layer1'); + /// //Get the layer graphics. + /// PdfGraphics graphics = layer.graphics; + /// graphics.translateTransform(100, 60); + /// //Draw an Arc. + /// graphics.drawArc(Rect.fromLTWH(0, 0, 50, 50), 360, 360, + /// pen: PdfPen(PdfColor(250, 0, 0), width: 50)); + /// graphics.drawArc(Rect.fromLTWH(0, 0, 50, 50), 360, 360, + /// pen: PdfPen(PdfColor(0, 0, 250), width: 30)); + /// graphics.drawArc(Rect.fromLTWH(0, 0, 50, 50), 360, 360, + /// pen: PdfPen(PdfColor(250, 250, 0), width: 20)); + /// graphics.drawArc(Rect.fromLTWH(0, 0, 50, 50), 360, 360, + /// pen: PdfPen(PdfColor(0, 250, 0), width: 10)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfPageLayerCollection get layers { if (!_isTextExtraction && !_graphicStateUpdated) { _layers = PdfPageLayerCollection(this); @@ -130,16 +222,17 @@ class PdfPage implements _IPdfWrapper { } else { _layers ??= PdfPageLayerCollection(this); } - return _layers; + return _layers!; } /// Gets array of page's content. _PdfArray get _contents { - final _IPdfPrimitive contents = _dictionary[_DictionaryProperties.contents]; - _PdfArray elements; + final _IPdfPrimitive? contents = + _dictionary[_DictionaryProperties.contents]; + _PdfArray? elements; if (contents is _PdfReferenceHolder) { final _PdfReferenceHolder holder = contents; - final _IPdfPrimitive primitive = holder.object; + final _IPdfPrimitive? primitive = holder.object; if (primitive is _PdfArray) { elements = primitive; } else if (primitive is _PdfStream) { @@ -162,9 +255,29 @@ class PdfPage implements _IPdfWrapper { } /// Gets the default layer of the page (Read only). + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF page and gets the default layer + /// PdfPageLayer defaultLayer = document.pages.add().defaultLayer; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfPageLayer get defaultLayer => layers[defaultLayerIndex]; /// Gets or sets index of the default layer (Read only). + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF page and gets the default layer index + /// int layerIndex = document.pages.add().defaultLayerIndex; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int get defaultLayerIndex { if (layers.count == 0 || _defaultLayerIndex == -1) { final PdfPageLayer layer = layers.add(); @@ -173,6 +286,25 @@ class PdfPage implements _IPdfWrapper { return _defaultLayerIndex; } + /// Gets or sets the tab order type for form fields + PdfFormFieldsTabOrder get formFieldsTabOrder => _formFieldsTabOrder; + set formFieldsTabOrder(PdfFormFieldsTabOrder value) { + _formFieldsTabOrder = value; + if (_formFieldsTabOrder != PdfFormFieldsTabOrder.none) { + String tabs = ' '; + if (_formFieldsTabOrder == PdfFormFieldsTabOrder.row) { + tabs = _DictionaryProperties.r; + } + if (_formFieldsTabOrder == PdfFormFieldsTabOrder.column) { + tabs = _DictionaryProperties.c; + } + if (_formFieldsTabOrder == PdfFormFieldsTabOrder.structure) { + tabs = _DictionaryProperties.s; + } + _dictionary[_DictionaryProperties.tabs] = _PdfName(tabs); + } + } + PdfPageOrientation get _orientation => _obtainOrientation(); PdfPageRotateAngle get _rotation => _obtainRotation(); @@ -180,14 +312,32 @@ class PdfPage implements _IPdfWrapper { //Public methods /// Get the PDF page size reduced by page margins and /// page template dimensions. + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF page + /// PdfPage page = document.pages.add(); + /// //Gets the page client size + /// Size clientSize = page.getClientSize(); + /// //Draw the text to the page + /// page.graphics.drawString( + /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), + /// brush: PdfBrushes.darkBlue, + /// bounds: Rect.fromLTWH(400, 600, clientSize.width, clientSize.height)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` Size getClientSize() { - return _section._getActualBounds(this, true).size.size; + return _isLoadedPage + ? size + : _section!._getActualBounds(this, true).size.size; } //Implementation void _assignSection(PdfSection section) { - ArgumentError.checkNotNull(section, 'section'); _section = section; _dictionary[_DictionaryProperties.parent] = _PdfReferenceHolder(section); } @@ -204,44 +354,44 @@ class PdfPage implements _IPdfWrapper { void _drawPageTemplates(PdfDocument document) { // Draw Background templates. final bool hasBackTemplates = - _section._containsTemplates(document, this, false); + _section!._containsTemplates(document, this, false); if (hasBackTemplates) { final PdfPageLayer backLayer = PdfPageLayer._fromClipPageTemplate(this, false); final PdfPageLayerCollection _layer = PdfPageLayerCollection(this); _layers = _layer; - _layers.addLayer(backLayer); - _section._drawTemplates(this, backLayer, document, false); + _layers!.addLayer(backLayer); + _section!._drawTemplates(this, backLayer, document, false); } // Draw Foreground templates. final bool hasFrontTemplates = - _section._containsTemplates(document, this, true); + _section!._containsTemplates(document, this, true); if (hasFrontTemplates) { final PdfPageLayer frontLayer = PdfPageLayer._fromClipPageTemplate(this, false); final PdfPageLayerCollection _layer = PdfPageLayerCollection(this); _layers = _layer; - _layers.addLayer(frontLayer); - _section._drawTemplates(this, frontLayer, document, true); + _layers!.addLayer(frontLayer); + _section!._drawTemplates(this, frontLayer, document, true); } } PdfPageRotateAngle _obtainRotation() { - _PdfDictionary parent = _dictionary; - _PdfNumber angle; + _PdfDictionary? parent = _dictionary; + _PdfNumber? angle; while (parent != null && angle == null) { if (parent.containsKey(_DictionaryProperties.rotate)) { if (parent[_DictionaryProperties.rotate] is _PdfReferenceHolder) { angle = (parent[_DictionaryProperties.rotate] as _PdfReferenceHolder) - .object as _PdfNumber; + .object as _PdfNumber?; } else { - angle = parent[_DictionaryProperties.rotate] as _PdfNumber; + angle = parent[_DictionaryProperties.rotate] as _PdfNumber?; } } if (parent.containsKey(_DictionaryProperties.parent)) { - _IPdfPrimitive parentPrimitive = parent[_DictionaryProperties.parent]; + _IPdfPrimitive? parentPrimitive = parent[_DictionaryProperties.parent]; if (parentPrimitive != null) { parentPrimitive = _PdfCrossTable._dereference(parentPrimitive); parent = parentPrimitive != null && parentPrimitive is _PdfDictionary @@ -255,11 +405,11 @@ class PdfPage implements _IPdfWrapper { } } angle ??= _PdfNumber(0); - if (angle.value.toInt() < 0) { - angle.value = 360 + angle.value.toInt(); + if (angle.value!.toInt() < 0) { + angle.value = 360 + angle.value!.toInt(); } final PdfPageRotateAngle rotateAngle = - _getRotationFromAngle(angle.value ~/ 90); + _getRotationFromAngle(angle.value! ~/ 90); return rotateAngle; } @@ -281,7 +431,7 @@ class PdfPage implements _IPdfWrapper { : PdfPageOrientation.portrait; } - _PdfResources _getResources() { + _PdfResources? _getResources() { if (_resources == null) { if (!_isLoadedPage) { _resources = _PdfResources(); @@ -292,30 +442,30 @@ class PdfPage implements _IPdfWrapper { _resources = _PdfResources(); _dictionary[_DictionaryProperties.resources] = _resources; // Check for the resources in the corresponding page section. - if (_resources._getNames().isEmpty || _resources._items.isEmpty) { + if (_resources!._getNames()!.isEmpty || _resources!._items!.isEmpty) { if (_dictionary.containsKey(_DictionaryProperties.parent)) { - _IPdfPrimitive obj = _dictionary[_DictionaryProperties.parent]; - _PdfDictionary parentDic; + _IPdfPrimitive? obj = _dictionary[_DictionaryProperties.parent]; + _PdfDictionary? parentDic; if (obj is _PdfReferenceHolder) { - parentDic = obj.object as _PdfDictionary; + parentDic = obj.object as _PdfDictionary?; } else { - parentDic = obj as _PdfDictionary; + parentDic = obj as _PdfDictionary?; } - if (parentDic.containsKey(_DictionaryProperties.resources)) { + if (parentDic!.containsKey(_DictionaryProperties.resources)) { obj = parentDic[_DictionaryProperties.resources]; - if (obj is _PdfDictionary && obj._items.isNotEmpty) { + if (obj is _PdfDictionary && obj._items!.isNotEmpty) { _dictionary[_DictionaryProperties.resources] = obj; _resources = _PdfResources(obj); final _PdfDictionary xobjects = _PdfDictionary(); - if (_resources.containsKey(_DictionaryProperties.xObject)) { - final _PdfDictionary xObject = - _resources[_DictionaryProperties.xObject] - as _PdfDictionary; + if (_resources!.containsKey(_DictionaryProperties.xObject)) { + final _PdfDictionary? xObject = + _resources![_DictionaryProperties.xObject] + as _PdfDictionary?; if (xObject != null) { - final _PdfArray content = _PdfCrossTable._dereference( + final _PdfArray? content = _PdfCrossTable._dereference( _dictionary[_DictionaryProperties.contents]) - as _PdfArray; + as _PdfArray?; if (content != null) { for (int i = 0; i < content.count; i++) { @@ -331,8 +481,8 @@ class PdfPage implements _IPdfWrapper { as _PdfStream; pageContent._decompress(); } - _resources.setProperty( - _DictionaryProperties.xObject, xobjects); + _resources! + .setProperty(_DictionaryProperties.xObject, xobjects); _setResources(_resources); } } @@ -342,13 +492,13 @@ class PdfPage implements _IPdfWrapper { if (obj is _PdfReferenceHolder) { final _PdfDictionary pageSourceDictionary = obj.object as _PdfDictionary; - if (pageSourceDictionary._items.length == - _resources._items.length || - _resources._items.isEmpty) { - for (final _PdfName key in _resources._items.keys) { - if (pageSourceDictionary._items.containsKey(key)) { - if (pageSourceDictionary._items - .containsValue(_resources[key])) { + if (pageSourceDictionary._items!.length == + _resources!._items!.length || + _resources!._items!.isEmpty) { + for (final _PdfName? key in _resources!._items!.keys) { + if (pageSourceDictionary._items!.containsKey(key)) { + if (pageSourceDictionary._items! + .containsValue(_resources![key])) { isValueEqual = true; } } else { @@ -356,10 +506,10 @@ class PdfPage implements _IPdfWrapper { break; } } - if (isValueEqual || _resources._items.isEmpty) { + if (isValueEqual || _resources!._items!.isEmpty) { _dictionary[_DictionaryProperties.resources] = obj; _resources = - _PdfResources(obj.object as _PdfDictionary); + _PdfResources(obj.object as _PdfDictionary?); } _setResources(_resources); } @@ -369,24 +519,24 @@ class PdfPage implements _IPdfWrapper { } } } else { - final _IPdfPrimitive dicObj = + final _IPdfPrimitive? dicObj = _dictionary[_DictionaryProperties.resources]; - final _PdfDictionary dic = - _crossTable._getObject(dicObj) as _PdfDictionary; + final _PdfDictionary? dic = + _crossTable!._getObject(dicObj) as _PdfDictionary?; _resources = _PdfResources(dic); _dictionary[_DictionaryProperties.resources] = _resources; if (_dictionary.containsKey(_DictionaryProperties.parent)) { - final _PdfDictionary parentDic = _PdfCrossTable._dereference( - _dictionary[_DictionaryProperties.parent]) as _PdfDictionary; + final _PdfDictionary? parentDic = _PdfCrossTable._dereference( + _dictionary[_DictionaryProperties.parent]) as _PdfDictionary?; if (parentDic != null && parentDic.containsKey(_DictionaryProperties.resources)) { - final _IPdfPrimitive resource = + final _IPdfPrimitive? resource = parentDic[_DictionaryProperties.resources]; if (dicObj is _PdfReferenceHolder && resource is _PdfReferenceHolder && resource.reference == dicObj.reference) { - final _PdfDictionary resourceDict = - _PdfCrossTable._dereference(dicObj) as _PdfDictionary; + final _PdfDictionary? resourceDict = + _PdfCrossTable._dereference(dicObj) as _PdfDictionary?; if (resourceDict != null) { _resources = _PdfResources(resourceDict); } @@ -401,47 +551,113 @@ class PdfPage implements _IPdfWrapper { return _resources; } - void _setResources(_PdfResources resources) { + void _setResources(_PdfResources? resources) { _resources = resources; _dictionary[_DictionaryProperties.resources] = _resources; } - void _pageBeginSave(Object sender, _SavePdfPrimitiveArgs args) { - final PdfDocument doc = args.writer._document; + void _pageBeginSave(Object sender, _SavePdfPrimitiveArgs? args) { + final PdfDocument? doc = args!.writer!._document; if (doc != null && _document != null) { _drawPageTemplates(doc); } if (_beginSave != null) { - _beginSave(); + _beginSave!(); } } // Retrieves the terminal annotations. void _createAnnotations(List widgetReferences) { - _PdfArray annots; + _PdfArray? annots; if (_dictionary.containsKey(_DictionaryProperties.annots)) { - annots = _crossTable._getObject(_dictionary[_DictionaryProperties.annots]) - as _PdfArray; + annots = _crossTable! + ._getObject(_dictionary[_DictionaryProperties.annots]) as _PdfArray?; if (annots != null) { for (int count = 0; count < annots.count; ++count) { - final _PdfDictionary annotDicrionary = - _crossTable._getObject(annots[count]) as _PdfDictionary; + final _PdfDictionary? annotDicrionary = + _crossTable!._getObject(annots[count]) as _PdfDictionary?; final _PdfReferenceHolder annotReference = annots[count] as _PdfReferenceHolder; + if (_document != null && + _document!._crossTable.encryptor != null && + _document!._crossTable.encryptor!._encryptOnlyAttachment!) { + if (annotDicrionary != null && + annotDicrionary.containsKey(_DictionaryProperties.subtype)) { + final _IPdfPrimitive? primitive = annotDicrionary + ._items![_PdfName(_DictionaryProperties.subtype)]; + if (primitive is _PdfName && + primitive._name == 'FileAttachment' && + annotDicrionary.containsKey(_DictionaryProperties.fs)) { + final _IPdfPrimitive? file = + annotDicrionary[_DictionaryProperties.fs]; + if (file != null && file is _PdfReferenceHolder) { + final _IPdfPrimitive? streamDictionary = file.object; + if (streamDictionary != null && + streamDictionary is _PdfDictionary && + streamDictionary.containsKey(_DictionaryProperties.ef)) { + _PdfDictionary? attachmentStream; + _IPdfPrimitive? holder = + streamDictionary[_DictionaryProperties.ef]; + if (holder is _PdfReferenceHolder) { + holder = holder.object; + if (holder != null && holder is _PdfDictionary) { + attachmentStream = holder; + } + } else if (holder is _PdfDictionary) { + attachmentStream = holder; + } + if (attachmentStream != null && + attachmentStream.containsKey(_DictionaryProperties.f)) { + holder = attachmentStream[_DictionaryProperties.f]; + if (holder != null && holder is _PdfReferenceHolder) { + final _PdfReference? reference = holder.reference; + holder = holder.object; + if (holder != null && holder is _PdfStream) { + final _PdfStream encryptedObj = holder; + if (_document!._isLoadedDocument) { + if (_document!.onPdfPassword != null && + _document!._password == '') { + final PdfPasswordArgs args = PdfPasswordArgs._(); + _document!._setUserPassword(args); + _document!._password = + args.attachmentOpenPassword; + } + _document!._checkEncryption(_document! + ._crossTable.encryptor!._encryptOnlyAttachment); + encryptedObj.decrypt( + _document!._crossTable.encryptor!, + reference!._objNum); + } + } + } + } + } + } + } + } + } if (annotDicrionary != null && annotDicrionary.containsKey(_DictionaryProperties.subtype)) { - final _PdfName name = annotDicrionary - ._items[_PdfName(_DictionaryProperties.subtype)] as _PdfName; + final _PdfName? name = annotDicrionary + ._items![_PdfName(_DictionaryProperties.subtype)] as _PdfName?; if (name != null && name._name.toString() == 'Widget') { if (annotDicrionary.containsKey(_DictionaryProperties.parent)) { - final _PdfDictionary annotParentDictionary = (annotDicrionary - ._items[_PdfName(_DictionaryProperties.parent)] + final _PdfDictionary? annotParentDictionary = (annotDicrionary + ._items![_PdfName(_DictionaryProperties.parent)] as _PdfReferenceHolder) - .object as _PdfDictionary; + .object as _PdfDictionary?; if (annotParentDictionary != null) { if (!annotParentDictionary .containsKey(_DictionaryProperties.fields)) { - if (annotParentDictionary + if (annotReference.reference != null && + !widgetReferences + .contains(annotReference.reference!._objNum)) { + if (!_document!.form._terminalFields + .contains(annotParentDictionary)) { + _document!.form._terminalFields + .add(annotParentDictionary); + } + } else if (annotParentDictionary .containsKey(_DictionaryProperties.kids) && annotParentDictionary.count == 1) { annotDicrionary.remove(_DictionaryProperties.parent); @@ -451,16 +667,59 @@ class PdfPage implements _IPdfWrapper { annotDicrionary.remove(_DictionaryProperties.parent); } } + } else if (!_document!.form._terminalFields + .contains(annotDicrionary)) { + _document!.form._widgetDictionary ??= {}; + if (annotDicrionary.containsKey(_DictionaryProperties.t)) { + final String? fieldName = (annotDicrionary + ._items![_PdfName(_DictionaryProperties.t)] + as _PdfString) + .value; + if (_document!.form._widgetDictionary! + .containsKey(fieldName)) { + final List<_PdfDictionary> dict = + _document!.form._widgetDictionary![fieldName]!; + dict.add(annotDicrionary); + } else { + if (!_document!.form.fields._addedFieldNames + .contains(fieldName)) { + _document!.form._widgetDictionary![fieldName] = [ + annotDicrionary + ]; + } + } + } } } } - if (annotReference != null && annotReference.reference != null) { - if (!_annotsReference._contains(annotReference.reference)) { - _annotsReference._add(annotReference.reference); + if (annotReference.reference != null) { + if (!_annotsReference._contains(annotReference.reference!)) { + _annotsReference._add(annotReference.reference!); + } + bool skip = false; + if (_document != null && + widgetReferences.contains(annotReference.reference!._objNum)) { + final PdfFormFieldCollection collection = _document!.form.fields; + for (int i = 0; i < collection.count; i++) { + final PdfField? field = collection[i]; + if (field != null && field._isLoadedField) { + final _IPdfPrimitive? widget = field._getWidgetAnnotation( + field._dictionary, field._crossTable); + final _PdfReference widgetReference = + _crossTable!._getReference(widget!); + if (annotReference.reference!._objNum == + widgetReference._objNum && + annotReference.reference!._genNum == + widgetReference._genNum) { + skip = true; + } + } + } } if (annotDicrionary != null && annotReference.reference != null) { if (!widgetReferences - .contains(annotReference.reference._objNum)) { + .contains(annotReference.reference!._objNum) && + !skip) { if (!_terminalAnnotation.contains(annotDicrionary)) { _terminalAnnotation.add(annotDicrionary); } @@ -474,25 +733,73 @@ class PdfPage implements _IPdfWrapper { } /// Creates a template from the page content. + /// ```dart + /// //Loads an existing PDF document and create the template + /// PdfTemplate template = + /// PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()) + /// .pages[0] + /// .createTemplate(); + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Sets the page settings margin + /// document.pageSettings.setMargins(2); + /// //Create a new PDF page + /// PdfPage page = document.pages.add(); + /// //Draw the Pdf template by using created template + /// page.graphics.drawPdfTemplate( + /// template, Offset(20, 0), Size(page.size.width / 2, page.size.height)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfTemplate createTemplate() { return _getContent(); } PdfTemplate _getContent() { - final List combinedData = layers._combineContent(false); - final _PdfDictionary resources = _PdfCrossTable._dereference( - _dictionary[_DictionaryProperties.resources]) as _PdfDictionary; + final List combinedData = layers._combineContent(false)!; + final _PdfDictionary? resources = _PdfCrossTable._dereference( + _dictionary[_DictionaryProperties.resources]) as _PdfDictionary?; final PdfTemplate template = - PdfTemplate._(_origin, size, combinedData, resources, _isLoadedPage); + PdfTemplate._(_origin, size, combinedData, resources!, _isLoadedPage); return template; } + // Gets the documents widget reference collection + List _getWidgetReferences() { + final List _widgetReferences = []; + final PdfFormFieldCollection collection = _document!.form.fields; + for (int i = 0; i < collection.count; i++) { + final PdfField? field = collection[i]; + if (field != null && field._isLoadedField) { + final _IPdfPrimitive? widget = + field._getWidgetAnnotation(field._dictionary, field._crossTable); + final Map widgetReference = + _document!._objects._getReference(widget!, false); + _widgetReferences.add(((widgetReference['isNew'] as bool) + ? _crossTable!._getReference(widget)._objNum + : (widgetReference['reference'] as _PdfReference)._objNum)! + .toSigned(64)); + widgetReference.clear(); + } + } + return _widgetReferences; + } + + _PdfArray? _obtainAnnotations() { + final _IPdfPrimitive? obj = _dictionary._getValue( + _DictionaryProperties.annots, _DictionaryProperties.parent); + return (obj != null && obj is _PdfReferenceHolder ? obj.object : obj) + as _PdfArray?; + } + //_IPdfWrapper elements @override - _IPdfPrimitive get _element => _dictionary; + _IPdfPrimitive? get _element => _dictionary; @override //ignore: unused_element - set _element(_IPdfPrimitive value) { - _dictionary = value; + set _element(_IPdfPrimitive? value) { + _dictionary = value as _PdfDictionary; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_collection.dart index 457c43a27..5a281bc37 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_collection.dart @@ -4,7 +4,7 @@ part of pdf; class PdfPageCollection { //Constructor /// Initializes a new instance of the [PdfPageCollection] class. - PdfPageCollection._(PdfDocument document, [PdfSection section]) { + PdfPageCollection._(PdfDocument? document, [PdfSection? section]) { if (document == null) { throw ArgumentError.notNull('document'); } @@ -12,48 +12,46 @@ class PdfPageCollection { if (section != null) { _section = section; } - _pageCache ??= <_PdfDictionary, PdfPage>{}; + _pageCache ??= <_PdfDictionary?, PdfPage>{}; } PdfPageCollection._fromCrossTable( PdfDocument document, _PdfCrossTable crossTable) { - ArgumentError.checkNotNull(document); - ArgumentError.checkNotNull(crossTable); - _document = document ?? PdfDocument(); + _document = document; _crossTable = crossTable; - _pageCache ??= <_PdfDictionary, PdfPage>{}; + _pageCache ??= <_PdfDictionary?, PdfPage>{}; } //Fields /// Represents the method that executes on a PdfDocument /// when a new page is created. - PageAddedCallback pageAdded; - PdfDocument _document; + PageAddedCallback? pageAdded; + PdfDocument? _document; int _count = 0; final Map _pageCollectionIndex = {}; - PdfSection _section; - Map<_PdfDictionary, PdfPage> _pageCache; - _PdfCrossTable _crossTable; - _IPdfPrimitive _pageCatalog; - _PdfDictionary _nodeDictionary; + PdfSection? _section; + Map<_PdfDictionary?, PdfPage>? _pageCache; + _PdfCrossTable? _crossTable; + _IPdfPrimitive? _pageCatalog; + _PdfDictionary? _nodeDictionary; int _nodeCount = 0; - _PdfCrossTable _lastCrossTable; - _PdfArray _nodeKids; + _PdfCrossTable? _lastCrossTable; + _PdfArray? _nodeKids; int _lastPageIndex = 0; int _lastKidIndex = 0; //Properties /// Gets the page by index - PdfPage operator [](int index) => _returnValue(index); + PdfPage operator [](int index) => _returnValue(index)!; /// Gets the total number of the pages. int get count { - if (_document._isLoadedDocument) { + if (_document!._isLoadedDocument) { int tempCount = 0; - final _IPdfPrimitive obj = - _document._catalog[_DictionaryProperties.pages]; - final _PdfDictionary node = - _PdfCrossTable._dereference(obj) as _PdfDictionary; + final _IPdfPrimitive? obj = + _document!._catalog[_DictionaryProperties.pages]; + final _PdfDictionary? node = + _PdfCrossTable._dereference(obj) as _PdfDictionary?; if (node != null) { tempCount = _getNodeCount(node); } @@ -62,7 +60,7 @@ class PdfPageCollection { if (_section == null) { return _countPages(); } else { - return _section._count; + return _section!._count; } } } @@ -71,7 +69,7 @@ class PdfPageCollection { /// Adds a new page. PdfPage add() { PdfPage page; - if (_document._isLoadedDocument) { + if (_document!._isLoadedDocument) { page = insert(count); } else { page = PdfPage(); @@ -84,12 +82,11 @@ class PdfPageCollection { /// Gets the index of the page. int indexOf(PdfPage page) { - ArgumentError.checkNotNull(page, 'page'); - if (_document._isLoadedDocument) { + if (_document!._isLoadedDocument) { int index = -1; final int count = this.count; for (int i = 0; i < count; i++) { - final PdfPage p = _returnValue(i); + final PdfPage? p = _returnValue(i); if (p == page) { index = i; break; @@ -97,12 +94,12 @@ class PdfPageCollection { } return index; } else if (_section != null) { - return _section._indexOf(page); + return _section!._indexOf(page); } else { int index = -1; int numPages = 0; - for (int i = 0; i < _document._sections.count; i++) { - final PdfSection section = _document.sections[i]; + for (int i = 0; i < _document!._sections!.count; i++) { + final PdfSection section = _document!.sections![i]; index = section._indexOf(page); if (index >= 0) { index += numPages; @@ -123,10 +120,10 @@ class PdfPageCollection { /// [rotation] - The PDF page rotation angle. /// [orientation] - The PDF page orientation. PdfPage insert(int index, - [Size size, - PdfMargins margins, - PdfPageRotateAngle rotation, - PdfPageOrientation orientation]) { + [Size? size, + PdfMargins? margins, + PdfPageRotateAngle? rotation, + PdfPageOrientation? orientation]) { if (size == null || size.isEmpty) { size = PdfPageSize.a4; } @@ -145,8 +142,8 @@ class PdfPageCollection { final PdfSection sec = PdfSection._(_document, settings); sec._dropCropBox(); sec._add(page); - _PdfDictionary dic = sec._element; - int localIndex = 0; + _PdfDictionary dic = sec._element as _PdfDictionary; + int? localIndex = 0; final Map result = _getValidParent(index, localIndex, false); final _PdfDictionary parent = result['node']; @@ -155,38 +152,37 @@ class PdfPageCollection { final int rotationValue = page._rotation.index * 90; final _PdfNumber parentRotation = parent[_DictionaryProperties.rotate] as _PdfNumber; - if (parentRotation.value.toInt() != rotationValue && + if (parentRotation.value!.toInt() != rotationValue && (!dic.containsKey(_DictionaryProperties.rotate))) { page._dictionary[_DictionaryProperties.rotate] = _PdfNumber(rotationValue); } } dic[_DictionaryProperties.parent] = _PdfReferenceHolder(parent); - final _PdfArray kids = _getNodeKids(parent); - kids._insert(localIndex, _PdfReferenceHolder(dic)); + final _PdfArray kids = _getNodeKids(parent)!; + kids._insert(localIndex!, _PdfReferenceHolder(dic)); _updateCount(parent); dic = page._element as _PdfDictionary; - _pageCache[dic] = page; - page.graphics.colorSpace = _document.colorSpace; - page.graphics._layer._colorSpace = _document.colorSpace; + _pageCache![dic] = page; + page.graphics.colorSpace = _document!.colorSpace; + page.graphics._layer!._colorSpace = _document!.colorSpace; return page; } //Implementation int _getNodeCount(_PdfDictionary node) { - final _PdfNumber number = - _crossTable._getObject(node[_DictionaryProperties.count]) as _PdfNumber; - return (number == null) ? 0 : number.value.toInt(); + final _PdfNumber? number = _crossTable! + ._getObject(node[_DictionaryProperties.count]) as _PdfNumber?; + return (number == null) ? 0 : number.value!.toInt(); } void _addPage(PdfPage page) { PdfSection section = _section ?? _getLastSection(); if (_section == null) { - if (section.pageSettings.orientation != - _document.pageSettings.orientation) { - section = _document.sections.add(); - section.pageSettings.orientation = _document.pageSettings.orientation; + if (!_checkPageSettings(section._settings)) { + section = _document!.sections!.add(); + section.pageSettings = _document!.pageSettings._clone(); } if (!_pageCollectionIndex.containsKey(page)) { _pageCollectionIndex[page] = _count++; @@ -195,38 +191,48 @@ class PdfPageCollection { section._add(page); } + bool _checkPageSettings(PdfPageSettings sectionSettings) { + final PdfPageSettings docSettings = _document!.pageSettings; + return sectionSettings.size == docSettings.size && + sectionSettings.orientation == docSettings.orientation && + (sectionSettings.margins.left == docSettings.margins.left && + sectionSettings.margins.top == docSettings.margins.top && + sectionSettings.margins.right == docSettings.margins.right && + sectionSettings.margins.bottom == docSettings.margins.bottom); + } + bool _contains(PdfPage page) { if (_section != null) { - return _section._indexOf(page) != -1; + return _section!._indexOf(page) != -1; } else { bool value = false; - for (int i = 0; i < _document.sections.count; i++) { - value |= _document.sections[i].pages._contains(page); + for (int i = 0; i < _document!.sections!.count; i++) { + value |= _document!.sections![i].pages._contains(page); } return value; } } - PdfPage _returnValue(int index) { - if (_document._isLoadedDocument) { + PdfPage? _returnValue(int index) { + if (_document!._isLoadedDocument) { int localIndex = 0; final Map result = _getParent(index, localIndex); _PdfDictionary node = result['node']; - localIndex = result['index']; - final _PdfArray kids = _getNodeKids(node); + localIndex = result['index']!; + final _PdfArray? kids = _getNodeKids(node); int i = localIndex; int j = 0; while (true) { - node = _crossTable._getObject(kids[localIndex]) as _PdfDictionary; + node = _crossTable!._getObject(kids![localIndex]) as _PdfDictionary; if ((node[_DictionaryProperties.type] as _PdfName)._name == 'Pages') { i++; - node = _crossTable._getObject(kids[i]) as _PdfDictionary; - final _PdfArray innerKids = _getNodeKids(node); + node = _crossTable!._getObject(kids[i]) as _PdfDictionary; + final _PdfArray? innerKids = _getNodeKids(node); if (innerKids == null) { break; } if (innerKids.count > 0) { - node = _crossTable._getObject(innerKids[j]) as _PdfDictionary; + node = _crossTable!._getObject(innerKids[j]) as _PdfDictionary; j++; break; } @@ -237,17 +243,17 @@ class PdfPageCollection { return _getPage(node); } else { if (_section != null) { - return _section._getPageByIndex(index); + return _section!._getPageByIndex(index); } else { if (index < 0 || index >= count) { throw ArgumentError.value('index', 'out of range'); } - PdfPage page; + PdfPage? page; int sectionStartIndex = 0; int sectionCount = 0; int pageIndex = 0; - for (int i = 0; i < _document.sections.count; i++) { - final PdfSection section = _document.sections[i]; + for (int i = 0; i < _document!.sections!.count; i++) { + final PdfSection section = _document!.sections![i]; sectionCount = section._count; pageIndex = index - sectionStartIndex; if (index >= sectionStartIndex && pageIndex < sectionCount) { @@ -261,44 +267,45 @@ class PdfPageCollection { } } - void _updateCount(_PdfDictionary parent) { + void _updateCount(_PdfDictionary? parent) { while (parent != null) { final int count = _getNodeCount(parent) + 1; parent[_DictionaryProperties.count] = _PdfNumber(count); parent = _PdfCrossTable._dereference(parent[_DictionaryProperties.parent]) - as _PdfDictionary; + as _PdfDictionary?; } } Map _getValidParent( - int index, int localIndex, bool zeroValid) { + int index, int? localIndex, bool zeroValid) { if (index < 0 && index > count) { throw ArgumentError.value(index, 'page index is not within range'); } - final _IPdfPrimitive obj = _document._catalog[_DictionaryProperties.pages]; - _PdfDictionary node = _crossTable._getObject(obj) as _PdfDictionary; + final _IPdfPrimitive? obj = + _document!._catalog[_DictionaryProperties.pages]; + _PdfDictionary node = _crossTable!._getObject(obj) as _PdfDictionary; int lowIndex = 0; localIndex = _getNodeCount(node); if (index == 0 && !zeroValid) { localIndex = 0; } else if (index < count) { - _PdfArray kids = _getNodeKids(node); + _PdfArray kids = _getNodeKids(node)!; for (int i = 0; i < kids.count; i++) { - final _PdfReferenceHolder pageReferenceHolder = - kids._elements[i] as _PdfReferenceHolder; - if (pageReferenceHolder != null) { + final _IPdfPrimitive? primitive = kids._elements[i]; + if (primitive != null && primitive is _PdfReferenceHolder) { + final _PdfReferenceHolder pageReferenceHolder = primitive; final _PdfDictionary kidsCollection = pageReferenceHolder.object as _PdfDictionary; - final List<_PdfName> keys = kidsCollection._items.keys.toList(); + final List<_PdfName?> keys = kidsCollection._items!.keys.toList(); for (int keyIndex = 0; keyIndex < keys.length; keyIndex++) { - final _PdfName key = keys[keyIndex]; - final _IPdfPrimitive value = kidsCollection[key]; + final _PdfName key = keys[keyIndex]!; + final _IPdfPrimitive? value = kidsCollection[key]; if (key._name == 'Kids') { - _PdfArray kidValue; + _PdfArray? kidValue; if (value is _PdfReferenceHolder) { - kidValue = value.object as _PdfArray; + kidValue = value.object as _PdfArray?; } else { - kidValue = value as _PdfArray; + kidValue = value as _PdfArray?; } if (kidValue != null && kidValue.count == 0) { kids._removeAt(i); @@ -309,8 +316,8 @@ class PdfPageCollection { } for (int i = 0, count = kids.count; i < count; ++i) { final _PdfDictionary subNode = - _crossTable._getObject(kids[i]) as _PdfDictionary; - String pageValue = + _crossTable!._getObject(kids[i]) as _PdfDictionary; + String? pageValue = (subNode[_DictionaryProperties.type] as _PdfName)._name; if (_isNodeLeaf(subNode) && !(pageValue == _DictionaryProperties.pages)) { @@ -323,12 +330,12 @@ class PdfPageCollection { if (index < lowIndex + nodeCount + i) { lowIndex += i; node = subNode; - kids = _getNodeKids(node); + kids = _getNodeKids(node)!; i = -1; count = kids.count; if (nodeCount == count) { final _PdfDictionary kidsSubNode = - _crossTable._getObject(kids[0]) as _PdfDictionary; + _crossTable!._getObject(kids[0]) as _PdfDictionary; pageValue = (kidsSubNode[_DictionaryProperties.type] as _PdfName)._name; if (pageValue == _DictionaryProperties.pages) { @@ -345,55 +352,57 @@ class PdfPageCollection { } } } else { - localIndex = _getNodeKids(node).count; + localIndex = _getNodeKids(node)!.count; } return {'node': node, 'index': localIndex}; } - Map _getParent(int index, int localIndex) { + Map _getParent(int index, int? localIndex) { if (index < 0 && index > count) { throw ArgumentError.value(index, 'page index is not within range'); } - _pageCatalog ??= _document._catalog[_DictionaryProperties.pages]; + _pageCatalog ??= _document!._catalog[_DictionaryProperties.pages]; bool isNodeChanged = false; - _PdfDictionary node; + _PdfDictionary? node; if (_nodeDictionary == null) { - _nodeDictionary = _crossTable._getObject(_pageCatalog) as _PdfDictionary; + _nodeDictionary = + _crossTable!._getObject(_pageCatalog) as _PdfDictionary?; node = _nodeDictionary; - _nodeCount = _getNodeCount(node); + _nodeCount = _getNodeCount(node!); _lastCrossTable = _crossTable; isNodeChanged = true; } else if (_crossTable == _lastCrossTable) { node = _nodeDictionary; } else { - _nodeDictionary = _crossTable._getObject(_pageCatalog) as _PdfDictionary; + _nodeDictionary = + _crossTable!._getObject(_pageCatalog) as _PdfDictionary?; node = _nodeDictionary; - _nodeCount = _getNodeCount(node); + _nodeCount = _getNodeCount(node!); _lastCrossTable = _crossTable; isNodeChanged = true; } - localIndex = _nodeCount > 0 ? _nodeCount : _getNodeCount(node); + localIndex = _nodeCount > 0 ? _nodeCount : _getNodeCount(node!); if (index < count) { - _PdfArray kids; + _PdfArray? kids; if (_nodeKids == null || isNodeChanged) { - _nodeKids = _getNodeKids(node); + _nodeKids = _getNodeKids(node!); kids = _nodeKids; - for (int i = 0; i < kids.count; i++) { - final _PdfReferenceHolder pageReferenceHolder = - kids._elements[i] as _PdfReferenceHolder; - if (pageReferenceHolder != null) { + for (int i = 0; i < kids!.count; i++) { + final _IPdfPrimitive? primitive = kids._elements[i]; + if (primitive != null && primitive is _PdfReferenceHolder) { + final _PdfReferenceHolder pageReferenceHolder = primitive; final _PdfDictionary kidsCollection = pageReferenceHolder.object as _PdfDictionary; - final List<_PdfName> keys = kidsCollection._items.keys.toList(); + final List<_PdfName?> keys = kidsCollection._items!.keys.toList(); for (int keyIndex = 0; keyIndex < keys.length; keyIndex++) { - final _PdfName key = keys[keyIndex]; - final _IPdfPrimitive value = kidsCollection[key]; + final _PdfName key = keys[keyIndex]!; + final _IPdfPrimitive? value = kidsCollection[key]; if (key._name == 'Kids') { - _PdfArray kidValue; + _PdfArray? kidValue; if (value is _PdfReferenceHolder) { - kidValue = value.object as _PdfArray; + kidValue = value.object as _PdfArray?; } else { - kidValue = value as _PdfArray; + kidValue = value as _PdfArray?; } if (kidValue != null && kidValue.count == 0) { kids._removeAt(i); @@ -407,20 +416,20 @@ class PdfPageCollection { } int kidStartIndex = 0; if ((_lastPageIndex == index - 1 || _lastPageIndex < index) && - _lastKidIndex < kids.count) { + _lastKidIndex < kids!.count) { kidStartIndex = _lastKidIndex; } - bool isParentNodeFetched = false; - _PdfDictionary tempNode; - int tempLocalIndex = 0; + bool? isParentNodeFetched = false; + _PdfDictionary? tempNode; + int? tempLocalIndex = 0; - if (kids.count == count) { + if (kids!.count == count) { Map returnValue = _getParentNode(kidStartIndex, kids, 0, index, tempNode, tempLocalIndex, isParentNodeFetched); tempNode = returnValue['tempNode']; tempLocalIndex = returnValue['tempLocalIndex']; isParentNodeFetched = returnValue['isParentNodeFetched']; - if (!isParentNodeFetched) { + if (!isParentNodeFetched!) { returnValue = _getParentNode( 0, kids, 0, index, tempNode, tempLocalIndex, isParentNodeFetched); tempNode = returnValue['tempNode']; @@ -442,15 +451,15 @@ class PdfPageCollection { localIndex = tempLocalIndex; } } else { - localIndex = _getNodeKids(node).count; + localIndex = _getNodeKids(node!)!.count; } _lastPageIndex = index; return {'node': node, 'index': localIndex}; } - _PdfArray _getNodeKids(_PdfDictionary node) { - final _IPdfPrimitive obj = node[_DictionaryProperties.kids]; - final _PdfArray kids = _crossTable._getObject(obj) as _PdfArray; + _PdfArray? _getNodeKids(_PdfDictionary node) { + final _IPdfPrimitive? obj = node[_DictionaryProperties.kids]; + final _PdfArray? kids = _crossTable!._getObject(obj) as _PdfArray?; return kids; } @@ -459,19 +468,19 @@ class PdfPageCollection { _PdfArray kids, int lowIndex, int pageIndex, - _PdfDictionary node, - int localIndex, - bool isParentFetched) { + _PdfDictionary? node, + int? localIndex, + bool? isParentFetched) { isParentFetched = false; node = null; localIndex = -1; bool isNonLeafNode = false; int tempCount = kids.count; for (int i = kidStartIndex; i < tempCount; ++i) { - final _IPdfPrimitive primitive = _crossTable._getObject(kids[i]); + final _IPdfPrimitive? primitive = _crossTable!._getObject(kids[i]); if (primitive != null && primitive is _PdfDictionary) { final _PdfDictionary subNode = primitive; - final String pageValue = + final String? pageValue = (subNode[_DictionaryProperties.type] as _PdfName)._name; if (_isNodeLeaf(subNode) && !(pageValue == _DictionaryProperties.pages)) { @@ -490,7 +499,7 @@ class PdfPageCollection { _lastKidIndex = i; lowIndex += i; node = subNode; - kids = _getNodeKids(node); + kids = _getNodeKids(node)!; i = -1; tempCount = kids.count; continue; @@ -512,7 +521,7 @@ class PdfPageCollection { } PdfSection _getLastSection() { - final PdfSectionCollection sectionCollection = _document.sections; + final PdfSectionCollection sectionCollection = _document!.sections!; if (sectionCollection._sections.isEmpty) { sectionCollection.add(); } @@ -520,7 +529,7 @@ class PdfPageCollection { } int _countPages() { - final PdfSectionCollection sectionCollection = _document.sections; + final PdfSectionCollection sectionCollection = _document!.sections!; int count = 0; for (int i = 0; i < sectionCollection._sections.length; i++) { final PdfSection section = sectionCollection[i]; @@ -531,18 +540,17 @@ class PdfPageCollection { void _onPageAdded(PageAddedArgs args) { if (pageAdded != null) { - pageAdded(this, args); + pageAdded!(this, args); } } void _remove(PdfPage page) { - ArgumentError.checkNotNull(page); if (_section != null) { - _section._remove(page); + _section!._remove(page); } else { - for (int i = 0; i < _document.sections.count; i++) { - if (_document.sections[i].pages._contains(page)) { - _document.sections[i].pages._remove(page); + for (int i = 0; i < _document!.sections!.count; i++) { + if (_document!.sections![i].pages._contains(page)) { + _document!.sections![i].pages._remove(page); break; } } @@ -578,48 +586,49 @@ class PdfPageCollection { /// document.dispose(); /// ``` void removeAt(int index) { - ArgumentError.checkNotNull(index, 'index value cannot be null'); if (index > -1 && index < count) { - final PdfPage page = _returnValue(index); + final PdfPage? page = _returnValue(index); _removePage(page, index); } } - void _removePage(PdfPage page, int index) { - if (_document._isLoadedDocument && index > -1) { - final Map pageToBookmarkDic = - _document._createBookmarkDestinationDictionary(); + void _removePage(PdfPage? page, int index) { + if (_document!._isLoadedDocument && index > -1) { + final Map? pageToBookmarkDic = + _document!._createBookmarkDestinationDictionary(); if (pageToBookmarkDic != null) { - List bookmarks; + List? bookmarks; if (pageToBookmarkDic.containsKey(page)) { - bookmarks = pageToBookmarkDic[page] as List; + bookmarks = pageToBookmarkDic[page!] as List?; } if (bookmarks != null) { for (int i = 0; i < bookmarks.length; i++) { if (bookmarks[i] is PdfBookmarkBase) { final PdfBookmarkBase current = bookmarks[i] as PdfBookmarkBase; - if (current._dictionary.containsKey(_DictionaryProperties.a)) { - current._dictionary.remove(_DictionaryProperties.a); + if (current._dictionary!.containsKey(_DictionaryProperties.a)) { + current._dictionary!.remove(_DictionaryProperties.a); } - if (current._dictionary.containsKey(_DictionaryProperties.dest)) { - current._dictionary.remove(_DictionaryProperties.dest); + if (current._dictionary! + .containsKey(_DictionaryProperties.dest)) { + current._dictionary!.remove(_DictionaryProperties.dest); } } } } } - final _PdfDictionary dic = page._element as _PdfDictionary; - int localIndex = 0; + final _PdfDictionary dic = page!._element as _PdfDictionary; + int? localIndex = 0; final Map result = _getParent(index, localIndex); final _PdfDictionary parent = result['node']; localIndex = result['index']; dic[_DictionaryProperties.parent] = _PdfReferenceHolder(parent); - _PdfArray kids = _getNodeKids(parent); + _PdfArray? kids = _getNodeKids(parent); if (index == 0) { - final _PdfCrossTable table = _document._crossTable; + final _PdfCrossTable table = _document!._crossTable; if (table.documentCatalog != null) { - final _IPdfPrimitive primitive = table.documentCatalog['OpenAction']; - _PdfArray documentCatalog; + final _IPdfPrimitive? primitive = + table.documentCatalog!['OpenAction']; + _PdfArray? documentCatalog; if (primitive != null) { if (primitive is _PdfArray) { documentCatalog = primitive; @@ -628,10 +637,10 @@ class PdfPageCollection { if (documentCatalog != null) { documentCatalog._remove(_PdfReferenceHolder(dic)); } else if (primitive is _PdfReferenceHolder) { - final _IPdfPrimitive documentDic = primitive.object; + final _IPdfPrimitive? documentDic = primitive.object; if (documentDic != null && documentDic is _PdfDictionary) { if (documentDic.containsKey('D')) { - final _IPdfPrimitive documentObject = documentDic['D']; + final _IPdfPrimitive? documentObject = documentDic['D']; if (documentObject != null && documentObject is _PdfArray) { documentObject._remove(_PdfReferenceHolder(dic)); } @@ -640,9 +649,9 @@ class PdfPageCollection { } } } - _PdfReferenceHolder remove; - for (int i = 0; i < kids.count; i++) { - final _IPdfPrimitive holder = kids[i]; + _PdfReferenceHolder? remove; + for (int i = 0; i < kids!.count; i++) { + final _IPdfPrimitive? holder = kids[i]; if (holder != null && holder is _PdfReferenceHolder && holder._object == dic) { @@ -654,10 +663,10 @@ class PdfPageCollection { kids._remove(remove); if (kids.count == 0 && parent.containsKey(_DictionaryProperties.parent)) { - _PdfDictionary parentDic; - _IPdfPrimitive holder = parent[_DictionaryProperties.parent]; + _PdfDictionary? parentDic; + _IPdfPrimitive? holder = parent[_DictionaryProperties.parent]; if (holder is _PdfReferenceHolder) { - holder = (holder as _PdfReferenceHolder)._object; + holder = holder._object; if (holder != null && holder is _PdfDictionary) { parentDic = holder; } @@ -665,31 +674,29 @@ class PdfPageCollection { parentDic = holder; } if (parentDic != null) { - _IPdfPrimitive kidsPrimitive = + _IPdfPrimitive? kidsPrimitive = parentDic[_DictionaryProperties.kids]; if (kidsPrimitive is _PdfReferenceHolder) { - kidsPrimitive = (kidsPrimitive as _PdfReferenceHolder)._object; + kidsPrimitive = kidsPrimitive._object; if (kidsPrimitive != null && kidsPrimitive is _PdfArray) { kids = kidsPrimitive; } } else if (kidsPrimitive is _PdfArray) { kids = kidsPrimitive; } - if (kids != null) { - _PdfReferenceHolder remove; - for (int i = 0; i < kids.count; i++) { - final _IPdfPrimitive holder = kids[i]; - if (holder != null && - holder is _PdfReferenceHolder && - holder._object == parent) { - remove = holder; - break; - } - } - if (remove != null) { - kids._remove(remove); + _PdfReferenceHolder? remove; + for (int i = 0; i < kids.count; i++) { + final _IPdfPrimitive? holder = kids[i]; + if (holder != null && + holder is _PdfReferenceHolder && + holder._object == parent) { + remove = holder; + break; } } + if (remove != null) { + kids._remove(remove); + } } } } @@ -697,15 +704,15 @@ class PdfPageCollection { } } - void _updateCountDecrement(_PdfDictionary parent) { + void _updateCountDecrement(_PdfDictionary? parent) { while (parent != null) { int count = _getNodeCount(parent) - 1; if (count == 0) { final _PdfDictionary node = parent; - final _IPdfPrimitive result = + final _IPdfPrimitive? result = _PdfCrossTable._dereference(parent[_DictionaryProperties.parent]); if (result != null && result is _PdfDictionary) { - final _IPdfPrimitive kids = result[_DictionaryProperties.kids]; + final _IPdfPrimitive? kids = result[_DictionaryProperties.kids]; if (kids != null && kids is _PdfArray) { kids._remove(_PdfReferenceHolder(node)); } @@ -713,21 +720,21 @@ class PdfPageCollection { } count = _getNodeCount(parent) - 1; parent[_DictionaryProperties.count] = _PdfNumber(count); - final _IPdfPrimitive primitive = + final _IPdfPrimitive? primitive = _PdfCrossTable._dereference(parent[_DictionaryProperties.parent]); parent = (primitive != null && primitive is _PdfDictionary) ? primitive : null; } } - PdfPage _getPage(_PdfDictionary dic) { - final Map<_PdfDictionary, PdfPage> pageCahce = _pageCache; - PdfPage page; + PdfPage _getPage(_PdfDictionary? dic) { + final Map<_PdfDictionary?, PdfPage> pageCahce = _pageCache!; + PdfPage? page; if (pageCahce.containsKey(dic)) { page = pageCahce[dic]; } if (page == null) { - page = PdfPage._fromDictionary(_document, _crossTable, dic); + page = PdfPage._fromDictionary(_document!, _crossTable!, dic!); pageCahce[dic] = page; } return page; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_layer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_layer.dart index 295924577..65f9326bc 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_layer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_layer.dart @@ -12,28 +12,28 @@ class PdfPageLayer implements _IPdfWrapper { } PdfPageLayer._fromClipPageTemplate(PdfPage pdfPage, - [bool clipPageTemplates]) { + [bool? clipPageTemplates]) { _initialize(pdfPage, clipPageTemplates); } //Fields - _PdfStream _content; - PdfPage _page; - bool _clipPageTemplates; + _PdfStream? _content; + late PdfPage _page; + bool? _clipPageTemplates; - String _name; - PdfGraphics _graphics; + String? _name; + PdfGraphics? _graphics; //ignore:unused_field - PdfColorSpace _colorSpace; - PdfGraphicsState _graphicsState; + PdfColorSpace? _colorSpace; + PdfGraphicsState? _graphicsState; bool _isEndState = false; bool _isSaved = false; - _PdfDictionary _dictionary; + _PdfDictionary? _dictionary; bool _visible = true; - String _layerID; - _PdfDictionary _printOption; - _PdfDictionary _usage; - _PdfReferenceHolder _referenceHolder; + String? _layerID; + _PdfDictionary? _printOption; + _PdfDictionary? _usage; + _PdfReferenceHolder? _referenceHolder; //Properties /// Gets parent page of the layer. @@ -45,26 +45,28 @@ class PdfPageLayer implements _IPdfWrapper { if (_graphics == null || _isSaved) { _initializeGraphics(page); } - return _graphics; + return _graphics!; } /// Gets the name of the layer - String get name { + String? get name { return _name; } /// Sets the name of the layer - set name(String value) { - _name = value; - _layerID ??= 'OCG_' + _PdfResources._globallyUniqueIdentifier; + set name(String? value) { + if (value != null) { + _name = value; + _layerID ??= 'OCG_' + _PdfResources._globallyUniqueIdentifier; + } } /// Gets the visibility of the page layer. bool get visible { if (_dictionary != null && - _dictionary.containsKey(_DictionaryProperties.visible)) { + _dictionary!.containsKey(_DictionaryProperties.visible)) { _visible = - (_dictionary[_DictionaryProperties.visible] as _PdfBoolean).value; + (_dictionary![_DictionaryProperties.visible] as _PdfBoolean).value!; } return _visible; } @@ -73,13 +75,13 @@ class PdfPageLayer implements _IPdfWrapper { set visible(bool value) { _visible = value; if (_dictionary != null) { - _dictionary[_DictionaryProperties.visible] = _PdfBoolean(value); + _dictionary![_DictionaryProperties.visible] = _PdfBoolean(value); } _setVisibility(_visible); } //Implementation - void _initialize(PdfPage pdfPage, bool clipPageTemplates) { + void _initialize(PdfPage? pdfPage, bool? clipPageTemplates) { if (pdfPage != null) { _page = pdfPage; } else { @@ -90,9 +92,9 @@ class PdfPageLayer implements _IPdfWrapper { _dictionary = _PdfDictionary(); } - void _initializeGraphics(PdfPage page) { + void _initializeGraphics(PdfPage? page) { if (_graphics == null) { - final Function resources = page._getResources; + final Function resources = page!._getResources; bool isPageHasMediaBox = false; bool isInvalidSize = false; if (page._dictionary @@ -103,41 +105,43 @@ class PdfPageLayer implements _IPdfWrapper { double lly = 0; double urx = 0; double ury = 0; - final _PdfArray mediaBox = page._dictionary._getValue( - _DictionaryProperties.mediaBox, _DictionaryProperties.parent); + final _PdfArray? mediaBox = page._dictionary._getValue( + _DictionaryProperties.mediaBox, _DictionaryProperties.parent) + as _PdfArray?; final _PdfReferenceHolder referenceHolder = _PdfReferenceHolder(this); if (mediaBox != null) { // Lower Left X co-ordinate Value. - llx = (mediaBox[0] as _PdfNumber).value.toDouble(); + llx = (mediaBox[0] as _PdfNumber).value!.toDouble(); // Lower Left Y co-ordinate value. - lly = (mediaBox[1] as _PdfNumber).value.toDouble(); + lly = (mediaBox[1] as _PdfNumber).value!.toDouble(); // Upper right X co-ordinate value. - urx = (mediaBox[2] as _PdfNumber).value.toDouble(); + urx = (mediaBox[2] as _PdfNumber).value!.toDouble(); // Upper right Y co-ordinate value. - ury = (mediaBox[3] as _PdfNumber).value.toDouble(); + ury = (mediaBox[3] as _PdfNumber).value!.toDouble(); } - _PdfArray cropBox; + _PdfArray? cropBox; if (page._dictionary.containsKey(_DictionaryProperties.cropBox)) { cropBox = page._dictionary._getValue( - _DictionaryProperties.cropBox, _DictionaryProperties.parent); - final double cropX = (cropBox[0] as _PdfNumber).value.toDouble(); - final double cropY = (cropBox[1] as _PdfNumber).value.toDouble(); - final double cropRX = (cropBox[2] as _PdfNumber).value.toDouble(); - final double cropRY = (cropBox[3] as _PdfNumber).value.toDouble(); + _DictionaryProperties.cropBox, _DictionaryProperties.parent) + as _PdfArray?; + final double cropX = (cropBox![0] as _PdfNumber).value!.toDouble(); + final double cropY = (cropBox[1] as _PdfNumber).value!.toDouble(); + final double cropRX = (cropBox[2] as _PdfNumber).value!.toDouble(); + final double cropRY = (cropBox[3] as _PdfNumber).value!.toDouble(); if ((cropX < 0 || cropY < 0 || cropRX < 0 || cropRY < 0) && (cropY.abs().floor() == page.size.height.abs().floor()) && (cropX.abs().floor()) == page.size.width.abs().floor()) { final Size pageSize = Size([cropX, cropRX].reduce(max), [cropY, cropRY].reduce(max)); - _graphics = PdfGraphics._(pageSize, resources, _content); + _graphics = PdfGraphics._(pageSize, resources, _content!); if (!page._contents._contains(referenceHolder) && !page._isDefaultGraphics && !_isContainsPageContent(page._contents, referenceHolder)) { page._contents._add(referenceHolder); } } else { - _graphics = PdfGraphics._(page.size, resources, _content); - _graphics._cropBox = cropBox; + _graphics = PdfGraphics._(page.size, resources, _content!); + _graphics!._cropBox = cropBox; if (!page._contents._contains(referenceHolder) && !page._isDefaultGraphics && !_isContainsPageContent(page._contents, referenceHolder)) { @@ -161,7 +165,7 @@ class PdfPageLayer implements _IPdfWrapper { ury = -ury; } pageSize = Size([llx, urx].reduce(max), [lly, ury].reduce(max)); - _graphics = PdfGraphics._(pageSize, resources, _content); + _graphics = PdfGraphics._(pageSize, resources, _content!); if (!page._contents._contains(referenceHolder) && !page._isDefaultGraphics && !_isContainsPageContent(page._contents, referenceHolder)) { @@ -169,7 +173,7 @@ class PdfPageLayer implements _IPdfWrapper { } } } else { - _graphics = PdfGraphics._(page.size, resources, _content); + _graphics = PdfGraphics._(page.size, resources, _content!); if (!page._contents._contains(referenceHolder) && !page._isDefaultGraphics && !_isContainsPageContent(page._contents, referenceHolder)) { @@ -178,60 +182,61 @@ class PdfPageLayer implements _IPdfWrapper { } if (isPageHasMediaBox) { - _graphics._mediaBoxUpperRightBound = isInvalidSize ? -lly : ury; + _graphics!._mediaBoxUpperRightBound = isInvalidSize ? -lly : ury; } - if (page != null && !page._isLoadedPage) { - final PdfSectionCollection sectionCollection = page._section._parent; + if (!page._isLoadedPage) { + final PdfSectionCollection? sectionCollection = page._section!._parent; if (sectionCollection != null) { - _graphics.colorSpace = sectionCollection._document.colorSpace; - _colorSpace = sectionCollection._document.colorSpace; + _graphics!.colorSpace = sectionCollection._document!.colorSpace; + _colorSpace = sectionCollection._document!.colorSpace; } } - _content._beginSave = _beginSaveContent; + _content!._beginSave = _beginSaveContent; } - _graphicsState = _graphics.save(); - if (name != null && name.isNotEmpty) { - _graphics._streamWriter._write('/OC /' + _layerID + ' BDC\n'); + _graphicsState = _graphics!.save(); + if (name != null && name!.isNotEmpty) { + _graphics!._streamWriter!._write('/OC /' + _layerID! + ' BDC\n'); _isEndState = true; } - _graphics._initializeCoordinates(); - if (_graphics._hasTransparencyBrush) { - _graphics._setTransparencyGroup(page); + _graphics!._initializeCoordinates(); + if (_graphics!._hasTransparencyBrush) { + _graphics!._setTransparencyGroup(page!); } if (page != null && page._isLoadedPage && (page._rotation != PdfPageRotateAngle.rotateAngle0 || page._dictionary.containsKey(_DictionaryProperties.rotate))) { - _PdfArray cropBox; + _PdfArray? cropBox; if (page._dictionary.containsKey(_DictionaryProperties.cropBox)) { cropBox = page._dictionary._getValue( - _DictionaryProperties.cropBox, _DictionaryProperties.parent); + _DictionaryProperties.cropBox, _DictionaryProperties.parent) + as _PdfArray?; } _updatePageRotation(page, _graphics, cropBox); } if (page != null && !page._isLoadedPage) { - final _Rectangle clipRect = page._section._getActualBounds(page, true); - if (_clipPageTemplates) { + final _Rectangle clipRect = page._section!._getActualBounds(page, true); + if (_clipPageTemplates!) { if (page._origin.dx >= 0 && page._origin.dy >= 0) { - _graphics._clipTranslateMarginsWithBounds(clipRect); + _graphics!._clipTranslateMarginsWithBounds(clipRect); } } else { - final PdfMargins margins = page._section.pageSettings.margins; - _graphics._clipTranslateMargins(clipRect.x, clipRect.y, margins.left, + final PdfMargins margins = page._section!.pageSettings.margins; + _graphics!._clipTranslateMargins(clipRect.x, clipRect.y, margins.left, margins.top, margins.right, margins.bottom); } } - _graphics._setLayer(this); + _graphics!._setLayer(this); _isSaved = false; } void _updatePageRotation( - PdfPage page, PdfGraphics graphics, _PdfArray cropBox) { - _PdfNumber rotation; + PdfPage page, PdfGraphics? graphics, _PdfArray? cropBox) { + _PdfNumber? rotation; if (page._dictionary.containsKey(_DictionaryProperties.rotate)) { - rotation = page._dictionary[_DictionaryProperties.rotate]; + rotation = page._dictionary[_DictionaryProperties.rotate] as _PdfNumber?; rotation ??= rotation = _PdfCrossTable._dereference( - page._dictionary[_DictionaryProperties.rotate]); + page._dictionary[_DictionaryProperties.rotate]) as _PdfNumber?; } else if (page._rotation != PdfPageRotateAngle.rotateAngle0) { if (page._rotation == PdfPageRotateAngle.rotateAngle90) { rotation = _PdfNumber(90); @@ -241,17 +246,19 @@ class PdfPageLayer implements _IPdfWrapper { rotation = _PdfNumber(270); } } - if (rotation.value == 90) { - graphics.translateTransform(0, page.size.height); + if (rotation!.value == 90) { + graphics!.translateTransform(0, page.size.height); graphics.rotateTransform(-90); if (cropBox != null) { - final double height = (cropBox[3] as _PdfNumber).value.toDouble(); + final double height = (cropBox[3] as _PdfNumber).value!.toDouble(); final Size cropBoxSize = Size( - (cropBox[2] as _PdfNumber).value.toDouble(), - height != 0 ? height : (cropBox[1] as _PdfNumber).value.toDouble()); + (cropBox[2] as _PdfNumber).value!.toDouble(), + height != 0 + ? height + : (cropBox[1] as _PdfNumber).value!.toDouble()); final Offset cropBoxOffset = Offset( - (cropBox[0] as _PdfNumber).value.toDouble(), - (cropBox[1] as _PdfNumber).value.toDouble()); + (cropBox[0] as _PdfNumber).value!.toDouble(), + (cropBox[1] as _PdfNumber).value!.toDouble()); if (page.size.height < cropBoxSize.height) { graphics._clipBounds.size = _Size(page.size.height - cropBoxOffset.dy, cropBoxSize.width - cropBoxOffset.dx); @@ -264,19 +271,19 @@ class PdfPageLayer implements _IPdfWrapper { graphics._clipBounds.size = _Size(page.size.height, page.size.width); } } else if (rotation.value == 180) { - graphics.translateTransform(page.size.width, page.size.height); + graphics!.translateTransform(page.size.width, page.size.height); graphics.rotateTransform(-180); } else if (rotation.value == 270) { - graphics.translateTransform(page.size.width, 0); + graphics!.translateTransform(page.size.width, 0); graphics.rotateTransform(-270); graphics._clipBounds.size = _Size(page.size.height, page.size.width); } } - void _beginSaveContent(Object sender, _SavePdfPrimitiveArgs args) { + void _beginSaveContent(Object sender, _SavePdfPrimitiveArgs? args) { if (_graphicsState != null) { if (_isEndState) { - graphics._streamWriter._write('EMC\n'); + graphics._streamWriter!._write('EMC\n'); _isEndState = false; } graphics.restore(_graphicsState); @@ -285,21 +292,22 @@ class PdfPageLayer implements _IPdfWrapper { _isSaved = true; } - void _setVisibility(bool value) { - _PdfDictionary oCProperties; - if (_page._document._catalog + void _setVisibility(bool? value) { + _PdfDictionary? oCProperties; + if (_page._document!._catalog .containsKey(_DictionaryProperties.ocProperties)) { oCProperties = _PdfCrossTable._dereference( - _page._document._catalog[_DictionaryProperties.ocProperties]) - as _PdfDictionary; + _page._document!._catalog[_DictionaryProperties.ocProperties]) + as _PdfDictionary?; } if (oCProperties != null) { - final _PdfDictionary defaultView = - oCProperties[_DictionaryProperties.defaultView] as _PdfDictionary; + final _PdfDictionary? defaultView = + oCProperties[_DictionaryProperties.defaultView] as _PdfDictionary?; if (defaultView != null) { - _PdfArray ocgON = defaultView[_DictionaryProperties.ocgOn] as _PdfArray; - _PdfArray ocgOFF = - defaultView[_DictionaryProperties.ocgOff] as _PdfArray; + _PdfArray? ocgON = + defaultView[_DictionaryProperties.ocgOn] as _PdfArray?; + _PdfArray? ocgOFF = + defaultView[_DictionaryProperties.ocgOff] as _PdfArray?; if (_referenceHolder != null) { if (value == false) { if (ocgON != null) { @@ -307,19 +315,20 @@ class PdfPageLayer implements _IPdfWrapper { } if (ocgOFF == null) { ocgOFF = _PdfArray(); - defaultView._items[_PdfName(_DictionaryProperties.ocgOff)] = + defaultView._items![_PdfName(_DictionaryProperties.ocgOff)] = ocgOFF; } - ocgOFF._insert(ocgOFF.count, _referenceHolder); + ocgOFF._insert(ocgOFF.count, _referenceHolder!); } else if (value == true) { if (ocgOFF != null) { _removeContent(ocgOFF, _referenceHolder); } if (ocgON == null) { ocgON = _PdfArray(); - defaultView._items[_PdfName(_DictionaryProperties.ocgOn)] = ocgON; + defaultView._items![_PdfName(_DictionaryProperties.ocgOn)] = + ocgON; } - ocgON._insert(ocgON.count, _referenceHolder); + ocgON._insert(ocgON.count, _referenceHolder!); } } } @@ -329,10 +338,11 @@ class PdfPageLayer implements _IPdfWrapper { bool _isContainsPageContent( _PdfArray content, _PdfReferenceHolder referenceHolder) { for (int i = 0; i < content.count; i++) { - if (content._elements[i] is _PdfReferenceHolder) { - final _PdfReferenceHolder holder = content._elements[i]; + final _IPdfPrimitive? primitive = content._elements[i]; + if (primitive != null && primitive is _PdfReferenceHolder) { + final _PdfReferenceHolder holder = primitive; if (holder.reference != null && referenceHolder.reference != null) { - if (holder.reference._objNum == referenceHolder.reference._objNum) { + if (holder.reference!._objNum == referenceHolder.reference!._objNum) { return true; } } else { @@ -347,13 +357,14 @@ class PdfPageLayer implements _IPdfWrapper { return false; } - void _removeContent(_PdfArray content, _PdfReferenceHolder referenceHolder) { + void _removeContent(_PdfArray content, _PdfReferenceHolder? referenceHolder) { bool flag = false; for (int i = 0; i < content.count; i++) { - if (content._elements[i] is _PdfReferenceHolder) { - final _PdfReferenceHolder holder = content._elements[i]; - if (holder.reference != null && referenceHolder.reference != null) { - if (holder.reference._objNum == referenceHolder.reference._objNum) { + final _IPdfPrimitive? primitive = content._elements[i]; + if (primitive != null && primitive is _PdfReferenceHolder) { + final _PdfReferenceHolder holder = primitive; + if (holder.reference != null && referenceHolder!.reference != null) { + if (holder.reference!._objNum == referenceHolder.reference!._objNum) { content._elements.removeAt(i); flag = true; i--; @@ -368,10 +379,10 @@ class PdfPageLayer implements _IPdfWrapper { //_IPdfWrapper elements @override - _IPdfPrimitive get _element => _content; + _IPdfPrimitive? get _element => _content; @override //ignore: unused_element - set _element(_IPdfPrimitive value) { - _content = value; + set _element(_IPdfPrimitive? value) { + _content = value as _PdfStream?; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_layer_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_layer_collection.dart index 5b21fdecb..c8ff67cbb 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_layer_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_layer_collection.dart @@ -7,7 +7,6 @@ class PdfPageLayerCollection extends PdfObjectCollection { /// Initializes a new instance of the /// [PdfPageLayerCollection] class with PDF page. PdfPageLayerCollection(PdfPage page) : super() { - ArgumentError.checkNotNull(page, 'page'); _optionalContent = _PdfDictionary(); _subLayer = false; _page = page; @@ -15,9 +14,9 @@ class PdfPageLayerCollection extends PdfObjectCollection { } //Fields - PdfPage _page; - bool _subLayer; - _PdfDictionary _optionalContent; + late PdfPage _page; + late bool _subLayer; + _PdfDictionary? _optionalContent; int _bdcCount = 0; //Properties @@ -29,7 +28,7 @@ class PdfPageLayerCollection extends PdfObjectCollection { //Public methods /// Creates a new [PdfPageLayer] and adds it to the end of the collection. - PdfPageLayer add({String name, bool visible}) { + PdfPageLayer add({String? name, bool? visible}) { final PdfPageLayer layer = PdfPageLayer(_page); if (name != null) { layer.name = name; @@ -44,7 +43,6 @@ class PdfPageLayerCollection extends PdfObjectCollection { /// Adds [PdfPageLayer] to the collection. int addLayer(PdfPageLayer layer) { - ArgumentError.checkNotNull(layer, 'layer'); if (layer.page != _page) { ArgumentError.value(layer, 'The layer belongs to another page'); } @@ -60,7 +58,7 @@ class PdfPageLayerCollection extends PdfObjectCollection { _createOptionContentDictionary(layer); ocProperties[_DictionaryProperties.defaultView] = _createOptionalContentViews(layer); - _page._document._catalog[_DictionaryProperties.ocProperties] = + _page._document!._catalog[_DictionaryProperties.ocProperties] = ocProperties; } } @@ -70,49 +68,43 @@ class PdfPageLayerCollection extends PdfObjectCollection { /// Returns index of the [PdfPageLayer] in the collection if exists, /// -1 otherwise. int indexOf(PdfPageLayer layer) { - ArgumentError.checkNotNull(layer, 'layer'); return _list.indexOf(layer); } //Implementation void _parseLayers(PdfPage page) { - ArgumentError.checkNotNull(page, 'page'); if (!page._isTextExtraction) { final _PdfArray contents = page._contents; - - final _PdfDictionary resource = _page._getResources(); - _PdfDictionary ocProperties; - _PdfDictionary propertie; - PdfPage pdfLoaded; - final Map<_PdfReferenceHolder, PdfPageLayer> pageLayerCollection = - <_PdfReferenceHolder, PdfPageLayer>{}; + final _PdfDictionary? resource = _page._getResources(); + _PdfDictionary? ocProperties; + _PdfDictionary? propertie; + PdfPage? pdfLoaded; + final Map<_PdfReferenceHolder?, PdfPageLayer> pageLayerCollection = + <_PdfReferenceHolder?, PdfPageLayer>{}; if (page._isLoadedPage) { pdfLoaded = page; } if (pdfLoaded != null) { propertie = _PdfCrossTable._dereference( - resource[_DictionaryProperties.properties]) as _PdfDictionary; + resource![_DictionaryProperties.properties]) as _PdfDictionary?; if (pdfLoaded._document != null) { - ocProperties = _PdfCrossTable._dereference(pdfLoaded._document - ._catalog[_DictionaryProperties.ocProperties]) as _PdfDictionary; + ocProperties = _PdfCrossTable._dereference(pdfLoaded._document! + ._catalog[_DictionaryProperties.ocProperties]) as _PdfDictionary?; } } if (ocProperties != null && (propertie != null)) { - _PdfDictionary layerDictionary; - _PdfReferenceHolder layerReferenceHolder; - if (propertie != null && (propertie != null)) { - propertie._items.forEach((_PdfName key, _IPdfPrimitive value) { - layerReferenceHolder = value as _PdfReferenceHolder; - layerDictionary = - _PdfCrossTable._dereference(value) as _PdfDictionary; - if ((layerDictionary != null && layerReferenceHolder != null) || - layerDictionary.containsKey(_DictionaryProperties.ocg)) { - _addLayer(page, layerDictionary, layerReferenceHolder, key._name, - pageLayerCollection, false); - } - }); - } + propertie._items!.forEach((_PdfName? key, _IPdfPrimitive? value) { + final _PdfReferenceHolder? layerReferenceHolder = + value as _PdfReferenceHolder?; + final _PdfDictionary? layerDictionary = + _PdfCrossTable._dereference(value) as _PdfDictionary?; + if ((layerDictionary != null && layerReferenceHolder != null) || + layerDictionary!.containsKey(_DictionaryProperties.ocg)) { + _addLayer(page, layerDictionary, layerReferenceHolder, key!._name, + pageLayerCollection, false); + } + }); } if (ocProperties != null && pageLayerCollection.isNotEmpty) { _checkVisible(ocProperties, pageLayerCollection); @@ -133,10 +125,9 @@ class PdfPageLayerCollection extends PdfObjectCollection { } } - List _combineContent(bool skipSave) { - ArgumentError.checkNotNull(_page); + List? _combineContent(bool skipSave) { final bool decompress = _page._isLoadedPage; - List combinedData; + List? combinedData; final List end = [13, 10]; if (_page._isLoadedPage && (_page._contents.count != count + 2)) { combinedData = _combineProcess(_page, decompress, end, skipSave); @@ -148,26 +139,26 @@ class PdfPageLayerCollection extends PdfObjectCollection { PdfPage page, bool decompress, List end, bool isTextExtraction) { final List data = []; for (int i = 0; i < page._contents.count; i++) { - _PdfStream layerStream; - final _IPdfPrimitive contentPrimitive = page._contents[i]; - if (contentPrimitive is _PdfReferenceHolder) { - final _IPdfPrimitive primitive = contentPrimitive.object; + _PdfStream? layerStream; + final _IPdfPrimitive? contentPrimitive = page._contents[i]; + if (contentPrimitive != null && contentPrimitive is _PdfReferenceHolder) { + final _IPdfPrimitive? primitive = contentPrimitive.object; if (primitive != null && primitive is _PdfStream) { layerStream = primitive; } - } else if (contentPrimitive is _PdfStream) { + } else if (contentPrimitive != null && contentPrimitive is _PdfStream) { layerStream = contentPrimitive; } if (layerStream != null) { if (decompress) { final bool isChanged = - layerStream._isChanged != null && layerStream._isChanged + layerStream._isChanged != null && layerStream._isChanged! ? true : false; layerStream._decompress(); layerStream._isChanged = isChanged || !isTextExtraction; } - data.addAll(layerStream._dataStream); + data.addAll(layerStream._dataStream!); data.addAll(end); } } @@ -176,40 +167,39 @@ class PdfPageLayerCollection extends PdfObjectCollection { void _createLayerLoadedPage(PdfPageLayer layer) { final _PdfDictionary ocProperties = _PdfDictionary(); - final _IPdfPrimitive ocgroups = _createOptionContentDictionary(layer); + final _IPdfPrimitive? ocgroups = _createOptionContentDictionary(layer); bool isPresent = false; - if (_page != null && - _page._document != null && - _page._document._catalog != null && - _page._document._catalog + if (_page._document != null && + _page._document!._catalog .containsKey(_DictionaryProperties.ocProperties)) { - final _PdfDictionary ocDictionary = _PdfCrossTable._dereference( - _page._document._catalog[_DictionaryProperties.ocProperties]) - as _PdfDictionary; + final _PdfDictionary? ocDictionary = _PdfCrossTable._dereference( + _page._document!._catalog[_DictionaryProperties.ocProperties]) + as _PdfDictionary?; if (ocDictionary != null && ocDictionary.containsKey(_DictionaryProperties.ocg)) { - final _PdfArray ocgsList = + final _PdfArray? ocgsList = _PdfCrossTable._dereference(ocDictionary[_DictionaryProperties.ocg]) - as _PdfArray; + as _PdfArray?; if (ocgsList != null) { isPresent = true; - if (!ocgsList._contains(layer._referenceHolder)) { - ocgsList._insert(ocgsList.count, layer._referenceHolder); + if (!ocgsList._contains(layer._referenceHolder!)) { + ocgsList._insert(ocgsList.count, layer._referenceHolder!); } } if (ocDictionary.containsKey(_DictionaryProperties.defaultView)) { - final _PdfDictionary defaultView = - ocDictionary[_DictionaryProperties.defaultView] as _PdfDictionary; + final _PdfDictionary? defaultView = + ocDictionary[_DictionaryProperties.defaultView] + as _PdfDictionary?; if (defaultView != null) { - _PdfArray _on = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgOn]) as _PdfArray; - final _PdfArray order = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgOrder]) as _PdfArray; - _PdfArray off = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgOff]) as _PdfArray; - final _PdfArray usage = _PdfCrossTable._dereference( + _PdfArray? _on = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOn]) as _PdfArray?; + final _PdfArray? order = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOrder]) as _PdfArray?; + _PdfArray? off = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOff]) as _PdfArray?; + final _PdfArray? usage = _PdfCrossTable._dereference( (defaultView[_DictionaryProperties.usageApplication])) - as _PdfArray; + as _PdfArray?; if (_on == null) { _on = _PdfArray(); @@ -221,30 +211,30 @@ class PdfPageLayerCollection extends PdfObjectCollection { defaultView[_DictionaryProperties.ocgOff] = off; } - if (order != null && !order._contains(layer._referenceHolder)) { - order._insert(order.count, layer._referenceHolder); + if (order != null && !order._contains(layer._referenceHolder!)) { + order._insert(order.count, layer._referenceHolder!); } - if (layer.visible && !_on._contains(layer._referenceHolder)) { - _on._insert(_on.count, layer._referenceHolder); + if (layer.visible && !_on._contains(layer._referenceHolder!)) { + _on._insert(_on.count, layer._referenceHolder!); } if (!layer.visible && off != null && - !off._contains(layer._referenceHolder)) { - off._insert(off.count, layer._referenceHolder); + !off._contains(layer._referenceHolder!)) { + off._insert(off.count, layer._referenceHolder!); } if (usage != null && usage.count > 0) { - final _PdfDictionary asDictionary = - _PdfCrossTable._dereference(usage[0]) as _PdfDictionary; + final _PdfDictionary? asDictionary = + _PdfCrossTable._dereference(usage[0]) as _PdfDictionary?; if (asDictionary != null && asDictionary.containsKey(_DictionaryProperties.ocg)) { - final _PdfArray usageOcGroup = _PdfCrossTable._dereference( - asDictionary[_DictionaryProperties.ocg]) as _PdfArray; + final _PdfArray? usageOcGroup = _PdfCrossTable._dereference( + asDictionary[_DictionaryProperties.ocg]) as _PdfArray?; if (usageOcGroup != null && - !usageOcGroup._contains(layer._referenceHolder)) { + !usageOcGroup._contains(layer._referenceHolder!)) { usageOcGroup._insert( - usageOcGroup.count, layer._referenceHolder); + usageOcGroup.count, layer._referenceHolder!); } } } @@ -252,50 +242,47 @@ class PdfPageLayerCollection extends PdfObjectCollection { } } } - if (!isPresent && - _page != null && - _page._document != null && - _page._document._catalog != null) { + if (!isPresent && _page._document != null) { ocProperties[_DictionaryProperties.ocg] = ocgroups; ocProperties[_DictionaryProperties.defaultView] = _createOptionalContentViews(layer); - _page._document._catalog + _page._document!._catalog .setProperty(_DictionaryProperties.ocProperties, ocProperties); } } - _IPdfPrimitive _createOptionContentDictionary(PdfPageLayer layer) { + _IPdfPrimitive? _createOptionContentDictionary(PdfPageLayer layer) { final _PdfDictionary optionalContent = _PdfDictionary(); - optionalContent[_DictionaryProperties.name] = _PdfString(layer.name); + optionalContent[_DictionaryProperties.name] = _PdfString(layer.name!); optionalContent[_DictionaryProperties.type] = _PdfName('OCG'); optionalContent[_DictionaryProperties.layerID] = _PdfName(layer._layerID); optionalContent[_DictionaryProperties.visible] = _PdfBoolean(layer.visible); layer._usage = _setPrintOption(layer); optionalContent[_DictionaryProperties.usage] = _PdfReferenceHolder(layer._usage); - _page._document._printLayer._add(_PdfReferenceHolder(optionalContent)); + _page._document!._printLayer!._add(_PdfReferenceHolder(optionalContent)); final _PdfReferenceHolder reference = _PdfReferenceHolder(optionalContent); - _page._document._primitive._add(reference); + _page._document!._primitive!._add(reference); layer._dictionary = optionalContent; layer._referenceHolder = reference; if (!_subLayer) { - _page._document._order._add(reference); - _page._document._orderPosition++; + _page._document!._order!._add(reference); + _page._document!._orderPosition = _page._document!._orderPosition! + 1; } if (layer.visible) { - _page._document._on._add(reference); - _page._document._onPosition++; + _page._document!._on!._add(reference); + _page._document!._onPosition = _page._document!._onPosition! + 1; } else { - _page._document._off._add(reference); - _page._document._offPosition++; + _page._document!._off!._add(reference); + _page._document!._offPosition = _page._document!._offPosition! + 1; } - _page._document._position++; - final _PdfResources resource = _page._getResources(); + _page._document!._position = _page._document!._position! + 1; + final _PdfResources? resource = _page._getResources(); if (resource != null && resource.containsKey(_DictionaryProperties.properties) && _page._isLoadedPage) { - final _PdfDictionary dic = - resource[_DictionaryProperties.properties] as _PdfDictionary; + final _PdfDictionary? dic = + resource[_DictionaryProperties.properties] as _PdfDictionary?; if (dic != null) { dic[layer._layerID] = reference; } else { @@ -303,38 +290,38 @@ class PdfPageLayerCollection extends PdfObjectCollection { resource[_DictionaryProperties.properties] = resource._properties; } } else { - resource._properties[layer._layerID] = reference; + resource!._properties[layer._layerID] = reference; resource[_DictionaryProperties.properties] = resource._properties; } - return _page._document._primitive; + return _page._document!._primitive; } _PdfDictionary _setPrintOption(PdfPageLayer layer) { final _PdfDictionary _usage = _PdfDictionary(); layer._printOption = _PdfDictionary(); - layer._printOption[_DictionaryProperties.subtype] = _PdfName('Print'); + layer._printOption![_DictionaryProperties.subtype] = _PdfName('Print'); _usage[_DictionaryProperties.print] = _PdfReferenceHolder(layer._printOption); return _usage; } - _IPdfPrimitive _createOptionalContentViews(PdfPageLayer layer) { + _IPdfPrimitive? _createOptionalContentViews(PdfPageLayer layer) { final _PdfArray usageApplication = _PdfArray(); - _optionalContent[_DictionaryProperties.name] = _PdfString('Layers'); - _optionalContent[_DictionaryProperties.ocgOrder] = _page._document._order; - _optionalContent[_DictionaryProperties.ocgOn] = _page._document._on; - _optionalContent[_DictionaryProperties.ocgOff] = _page._document._off; + _optionalContent![_DictionaryProperties.name] = _PdfString('Layers'); + _optionalContent![_DictionaryProperties.ocgOrder] = _page._document!._order; + _optionalContent![_DictionaryProperties.ocgOn] = _page._document!._on; + _optionalContent![_DictionaryProperties.ocgOff] = _page._document!._off; final _PdfArray category = _PdfArray(); category._add(_PdfName('Print')); final _PdfDictionary applicationDictionary = _PdfDictionary(); applicationDictionary[_DictionaryProperties.category] = category; applicationDictionary[_DictionaryProperties.ocg] = - _page._document._printLayer; + _page._document!._printLayer; applicationDictionary[_DictionaryProperties.event] = _PdfName('Print'); usageApplication._add(_PdfReferenceHolder(applicationDictionary)); - if (_page._document._conformanceLevel != PdfConformanceLevel.a2b && - _page._document._conformanceLevel != PdfConformanceLevel.a3b) { - _optionalContent[_DictionaryProperties.usageApplication] = + if (_page._document!._conformanceLevel != PdfConformanceLevel.a2b && + _page._document!._conformanceLevel != PdfConformanceLevel.a3b) { + _optionalContent![_DictionaryProperties.usageApplication] = usageApplication; } return _optionalContent; @@ -343,9 +330,9 @@ class PdfPageLayerCollection extends PdfObjectCollection { void _addLayer( PdfPage page, _PdfDictionary dictionary, - _PdfReferenceHolder reference, - String key, - Map<_PdfReferenceHolder, PdfPageLayer> pageLayerCollection, + _PdfReferenceHolder? reference, + String? key, + Map<_PdfReferenceHolder?, PdfPageLayer> pageLayerCollection, bool isResourceLayer) { final PdfPageLayer layer = PdfPageLayer(page); _list.add(layer); @@ -356,9 +343,9 @@ class PdfPageLayerCollection extends PdfObjectCollection { layer._referenceHolder = reference; layer._layerID = key; if (dictionary.containsKey(_DictionaryProperties.name)) { - final _PdfString layerName = + final _PdfString? layerName = _PdfCrossTable._dereference(dictionary[_DictionaryProperties.name]) - as _PdfString; + as _PdfString?; if (layerName != null) { layer._name = layerName.value; } @@ -366,27 +353,25 @@ class PdfPageLayerCollection extends PdfObjectCollection { } void _checkVisible(_PdfDictionary ocproperties, - Map<_PdfReferenceHolder, PdfPageLayer> layerDictionary) { - if (ocproperties != null) { - final _PdfDictionary defaultView = _PdfCrossTable._dereference( - ocproperties[_DictionaryProperties.defaultView]) as _PdfDictionary; - if (defaultView != null) { - final _PdfArray visible = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgOff]) as _PdfArray; - if (visible != null && layerDictionary.isNotEmpty) { - for (int i = 0; i < visible.count; i++) { - if (layerDictionary - .containsKey(visible[i] as _PdfReferenceHolder)) { - final PdfPageLayer pdfLayer = - layerDictionary[visible[i] as _PdfReferenceHolder]; - if (pdfLayer != null) { - pdfLayer._visible = false; - if (pdfLayer._dictionary != null && - pdfLayer._dictionary - .containsKey(_DictionaryProperties.visible)) { - pdfLayer._dictionary.setProperty( - _DictionaryProperties.visible, _PdfBoolean(false)); - } + Map<_PdfReferenceHolder?, PdfPageLayer> layerDictionary) { + final _PdfDictionary? defaultView = _PdfCrossTable._dereference( + ocproperties[_DictionaryProperties.defaultView]) as _PdfDictionary?; + if (defaultView != null) { + final _PdfArray? visible = + _PdfCrossTable._dereference(defaultView[_DictionaryProperties.ocgOff]) + as _PdfArray?; + if (visible != null && layerDictionary.isNotEmpty) { + for (int i = 0; i < visible.count; i++) { + if (layerDictionary.containsKey(visible[i] as _PdfReferenceHolder)) { + final PdfPageLayer? pdfLayer = + layerDictionary[visible[i] as _PdfReferenceHolder]; + if (pdfLayer != null) { + pdfLayer._visible = false; + if (pdfLayer._dictionary != null && + pdfLayer._dictionary! + .containsKey(_DictionaryProperties.visible)) { + pdfLayer._dictionary!.setProperty( + _DictionaryProperties.visible, _PdfBoolean(false)); } } } @@ -396,7 +381,7 @@ class PdfPageLayerCollection extends PdfObjectCollection { } /// Removes layer from the collection. - void remove({PdfPageLayer layer, String name}) { + void remove({PdfPageLayer? layer, String? name}) { if (layer == null && name == null) { ArgumentError.value('layer or layerName required'); } @@ -421,7 +406,7 @@ class PdfPageLayerCollection extends PdfObjectCollection { ArgumentError.value( '$index Value can not be less 0 and greater List.Count - 1'); } - final PdfPageLayer layer = this[index]; + final PdfPageLayer? layer = this[index]; if (layer != null) { _removeLayer(layer); _list.removeAt(index); @@ -430,110 +415,109 @@ class PdfPageLayerCollection extends PdfObjectCollection { /// Clears layers from the [PdfPageLayerCollection]. void clear() { - for (final PdfPageLayer layer in _page.layers._list) { + for (int i = 0; i < _page.layers._list.length; i++) { + final PdfPageLayer layer = _page.layers._list[i] as PdfPageLayer; _removeLayer(layer); } _list.clear(); } void _removeLayer(PdfPageLayer layer) { - ArgumentError.checkNotNull(layer, 'Layer'); - _PdfDictionary ocProperties; - if (_page != null) { - _removeLayerContent(layer); - final _PdfDictionary resource = _PdfCrossTable._dereference( - _page._dictionary[_DictionaryProperties.resources]) as _PdfDictionary; - if (resource != null) { - final _PdfDictionary properties = _PdfCrossTable._dereference( - resource[_DictionaryProperties.properties]) as _PdfDictionary; - if (properties != null && - layer._layerID != null && - properties.containsKey(layer._layerID)) { - properties.remove(layer._layerID); - } + _PdfDictionary? ocProperties; + _removeLayerContent(layer); + final _PdfDictionary? resource = _PdfCrossTable._dereference( + _page._dictionary[_DictionaryProperties.resources]) as _PdfDictionary?; + if (resource != null) { + final _PdfDictionary? properties = _PdfCrossTable._dereference( + resource[_DictionaryProperties.properties]) as _PdfDictionary?; + if (properties != null && + layer._layerID != null && + properties.containsKey(layer._layerID)) { + properties.remove(layer._layerID); } - final PdfPage page = _page; - if (page != null) { - if (page._document != null && - page._document._catalog - .containsKey(_DictionaryProperties.ocProperties)) { - ocProperties = _PdfCrossTable._dereference( - page._document._catalog[_DictionaryProperties.ocProperties]) - as _PdfDictionary; - } + } + final PdfPage? page = _page; + if (page != null) { + if (page._document != null && + page._document!._catalog + .containsKey(_DictionaryProperties.ocProperties)) { + ocProperties = _PdfCrossTable._dereference( + page._document!._catalog[_DictionaryProperties.ocProperties]) + as _PdfDictionary?; } - if (ocProperties != null) { - final _PdfArray ocGroup = - _PdfCrossTable._dereference(ocProperties[_DictionaryProperties.ocg]) - as _PdfArray; - if (ocGroup != null) { - _removeContent(ocGroup, layer._referenceHolder); - } - final _PdfDictionary defaultView = _PdfCrossTable._dereference( - ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary; - if (defaultView != null) { - final _PdfArray _on = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgOn]) as _PdfArray; - final _PdfArray order = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgOrder]) as _PdfArray; - final _PdfArray off = _PdfCrossTable._dereference( - defaultView[_DictionaryProperties.ocgOff]) as _PdfArray; - final _PdfArray usage = _PdfCrossTable._dereference( - (defaultView[_DictionaryProperties.usageApplication])) - as _PdfArray; - - if (usage != null && usage.count > 0) { - for (int i = 0; i < usage.count; i++) { - final _PdfDictionary usageDictionary = - _PdfCrossTable._dereference(usage[i]) as _PdfDictionary; - if (usageDictionary != null && - usageDictionary.containsKey(_DictionaryProperties.ocg)) { - final _PdfArray usageOcGroup = - usageDictionary[_DictionaryProperties.ocg] as _PdfArray; - if (usageOcGroup != null) { - _removeContent(usageOcGroup, layer._referenceHolder); - } + } + if (ocProperties != null) { + final _PdfArray? ocGroup = + _PdfCrossTable._dereference(ocProperties[_DictionaryProperties.ocg]) + as _PdfArray?; + if (ocGroup != null) { + _removeContent(ocGroup, layer._referenceHolder); + } + final _PdfDictionary? defaultView = _PdfCrossTable._dereference( + ocProperties[_DictionaryProperties.defaultView]) as _PdfDictionary?; + if (defaultView != null) { + final _PdfArray? _on = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOn]) as _PdfArray?; + final _PdfArray? order = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOrder]) as _PdfArray?; + final _PdfArray? off = _PdfCrossTable._dereference( + defaultView[_DictionaryProperties.ocgOff]) as _PdfArray?; + final _PdfArray? usage = _PdfCrossTable._dereference( + (defaultView[_DictionaryProperties.usageApplication])) + as _PdfArray?; + + if (usage != null && usage.count > 0) { + for (int i = 0; i < usage.count; i++) { + final _PdfDictionary? usageDictionary = + _PdfCrossTable._dereference(usage[i]) as _PdfDictionary?; + if (usageDictionary != null && + usageDictionary.containsKey(_DictionaryProperties.ocg)) { + final _PdfArray? usageOcGroup = + usageDictionary[_DictionaryProperties.ocg] as _PdfArray?; + if (usageOcGroup != null) { + _removeContent(usageOcGroup, layer._referenceHolder); } } } - if (order != null) { - _removeContent(order, layer._referenceHolder); - } - if (layer.visible && _on != null) { - _removeContent(_on, layer._referenceHolder); - } else if (off != null) { - _removeContent(off, layer._referenceHolder); - } + } + if (order != null) { + _removeContent(order, layer._referenceHolder); + } + if (layer.visible && _on != null) { + _removeContent(_on, layer._referenceHolder); + } else if (off != null) { + _removeContent(off, layer._referenceHolder); } } } } - void _removeContent(_PdfArray content, _PdfReferenceHolder referenceHolder) { + void _removeContent(_PdfArray content, _PdfReferenceHolder? referenceHolder) { bool flag = false; for (int i = 0; i < content.count; i++) { - if (content._elements[i] is _PdfReferenceHolder) { - if ((content._elements[i] as _PdfReferenceHolder).reference != null && - referenceHolder.reference != null) { - if ((content._elements[i] as _PdfReferenceHolder).reference._objNum == - referenceHolder.reference._objNum) { + final _IPdfPrimitive? primitive = content._elements[i]; + if (primitive != null && primitive is _PdfReferenceHolder) { + final _PdfReferenceHolder reference = primitive; + if (reference.reference != null && referenceHolder!.reference != null) { + if (reference.reference!._objNum == + referenceHolder.reference!._objNum) { content._elements.removeAt(i); flag = true; i--; } - } else if (identical(content._elements[i], referenceHolder)) { + } else if (identical(content._elements[i]!, referenceHolder)) { content._elements.removeAt(i); flag = true; i--; } else if (identical( - (content._elements[i] as _PdfReferenceHolder)._object, - referenceHolder._object)) { + (content._elements[i]! as _PdfReferenceHolder)._object, + referenceHolder!._object)) { content._elements.removeAt(i); flag = true; i--; } - } else if (content._elements[i] is _PdfArray) { - _removeContent(content._elements[i], referenceHolder); + } else if (content._elements[i]! is _PdfArray) { + _removeContent(content._elements[i]! as _PdfArray, referenceHolder); } } if (flag) { @@ -545,12 +529,15 @@ class PdfPageLayerCollection extends PdfObjectCollection { bool isSkip = false; for (int m = 0; m < _page._contents.count; m++) { bool isNewContentStream = false; - bool removePageContent = false; - List stream = []; - final _PdfReferenceHolder pdfReference = - _page._contents[m] as _PdfReferenceHolder; - if (pdfReference != null && pdfReference.reference == null) { - isNewContentStream = true; + bool? removePageContent = false; + List? stream = []; + final _IPdfPrimitive? primitive = _page._contents[m]; + if (primitive! is _PdfReferenceHolder) { + final _PdfReferenceHolder pdfReference = + primitive as _PdfReferenceHolder; + if (pdfReference.reference == null) { + isNewContentStream = true; + } } final _PdfStream pageContent = _PdfCrossTable._dereference(_page._contents[m]) as _PdfStream; @@ -560,9 +547,9 @@ class PdfPageLayerCollection extends PdfObjectCollection { } stream = pageContent._dataStream; final _ContentParser parser = _ContentParser(stream); - final _PdfRecordCollection recordCollection = parser._readContent(); + final _PdfRecordCollection recordCollection = parser._readContent()!; for (int j = 0; j < recordCollection._recordCollection.length; j++) { - final String mOperator = + final String? mOperator = recordCollection._recordCollection[j]._operatorName; if (mOperator == 'BMC' || mOperator == 'EMC' || mOperator == 'BDC') { final Map returnedValue = _processBeginMarkContent( @@ -574,15 +561,15 @@ class PdfPageLayerCollection extends PdfObjectCollection { removePageContent); removePageContent = returnedValue['removePageContent']; isSkip = true; - if (removePageContent) { + if (removePageContent!) { break; } } - String id; - if (recordCollection._recordCollection[j]._operands.isNotEmpty && - recordCollection._recordCollection[j]._operands[0] + String? id; + if (recordCollection._recordCollection[j]._operands!.isNotEmpty && + recordCollection._recordCollection[j]._operands![0] .startsWith('/')) { - id = recordCollection._recordCollection[j]._operands[0].substring(1); + id = recordCollection._recordCollection[j]._operands![0].substring(1); } if (mOperator == _Operators.paintXObject && (id == layer._layerID)) { isSkip = true; @@ -609,17 +596,17 @@ class PdfPageLayerCollection extends PdfObjectCollection { } isSkip = false; } - if (data._dataStream.isNotEmpty && !removePageContent) { + if (data._dataStream!.isNotEmpty && !removePageContent!) { pageContent.clear(); - pageContent._dataStream.clear(); + pageContent._dataStream!.clear(); pageContent._write(data._dataStream); } else { pageContent.clear(); } - if (removePageContent) { + if (removePageContent!) { _removeContent(_page._contents, layer._referenceHolder); - if (layer._graphics != null && layer._graphics._streamWriter != null) { - final _PdfStream lcontent = layer._graphics._streamWriter._stream; + if (layer._graphics != null && layer._graphics!._streamWriter != null) { + final _PdfStream? lcontent = layer._graphics!._streamWriter!._stream; if (lcontent != null) { _removeContent(_page._contents, _PdfReferenceHolder(lcontent)); } @@ -630,15 +617,15 @@ class PdfPageLayerCollection extends PdfObjectCollection { Map _processBeginMarkContent( PdfPageLayer parser, - String mOperator, - List operands, + String? mOperator, + List? operands, _PdfStream data, bool isNewContentStream, - bool removePageContent) { + bool? removePageContent) { removePageContent = false; if ('BDC' == mOperator) { - String operand; - if (operands.length > 1 && ((operands[0]) == '/OC')) { + String? operand; + if (operands!.length > 1 && ((operands[0]) == '/OC')) { operand = operands[1].substring(1); } if (_bdcCount > 0) { @@ -664,7 +651,7 @@ class PdfPageLayerCollection extends PdfObjectCollection { } void _streamWrite( - List operands, String mOperator, bool skip, _PdfStream data) { + List? operands, String? mOperator, bool skip, _PdfStream data) { _PdfString pdfString; if (skip && _bdcCount > 0) { return; @@ -676,7 +663,7 @@ class PdfPageLayerCollection extends PdfObjectCollection { data._write(_Operators.whiteSpace); } } - pdfString = _PdfString(mOperator); + pdfString = _PdfString(mOperator!); data._write(pdfString.data); data._write(_Operators.newLine); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_settings.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_settings.dart index 0ce1b7e45..2d6ad22b5 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_settings.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_settings.dart @@ -21,7 +21,7 @@ class PdfPageSettings { /// List bytes = document.save(); /// document.dispose(); /// ``` - PdfPageSettings([Size size, PdfPageOrientation orientation]) { + PdfPageSettings([Size? size, PdfPageOrientation? orientation]) { if (size != null) { _size = _Size.fromSize(size); } @@ -30,15 +30,17 @@ class PdfPageSettings { _updateSize(orientation); } _origin = _Point(0, 0); - margins = PdfMargins(); + _margins = PdfMargins(); } //Fields + late PdfMargins _margins; PdfPageOrientation _orientation = PdfPageOrientation.portrait; _Size _size = _Size.fromSize(PdfPageSize.a4); - _Point _origin; + late _Point _origin; PdfPageRotateAngle _rotateAngle = PdfPageRotateAngle.rotateAngle0; bool _isRotation = false; + bool _isPageAdded = false; //Properties /// Gets or sets the margins of the PDF page. @@ -56,7 +58,12 @@ class PdfPageSettings { /// List bytes = document.save(); /// document.dispose(); /// ``` - PdfMargins margins; + PdfMargins get margins => _margins; + set margins(PdfMargins value) { + if (!_margins._equals(value) && !_isPageAdded) { + _margins = value; + } + } /// Gets the width of the page. /// @@ -129,7 +136,7 @@ class PdfPageSettings { /// document.dispose(); /// ``` set orientation(PdfPageOrientation value) { - if (value != _orientation) { + if (value != _orientation && !_isPageAdded) { _orientation = value; _updateSize(_orientation); } @@ -167,7 +174,11 @@ class PdfPageSettings { /// List bytes = document.save(); /// document.dispose(); /// ``` - set size(Size value) => _updateSize(_orientation, value); + set size(Size value) { + if (!_isPageAdded) { + _updateSize(_orientation, value); + } + } /// Gets the number of degrees by which the page should be rotated clockwise /// when displayed or printed. @@ -208,8 +219,10 @@ class PdfPageSettings { /// document.dispose(); /// ``` set rotate(PdfPageRotateAngle value) { - _rotateAngle = value; - _isRotation = true; + if (!_isPageAdded) { + _rotateAngle = value; + _isRotation = true; + } } //Public methods @@ -228,20 +241,22 @@ class PdfPageSettings { /// List bytes = document.save(); /// document.dispose(); /// ``` - void setMargins(double all, [double top, double right, double bottom]) { - if (top != null && right != null && bottom != null) { - margins._setMarginsAll(all, top, right, bottom); - } else if (top != null && right == null) { - margins._setMarginsLT(all, top); - } else if (top == null && bottom != null) { - margins._setMarginsLT(all, bottom); - } else { - margins._setMargins(all); + void setMargins(double all, [double? top, double? right, double? bottom]) { + if (!_isPageAdded) { + if (top != null && right != null && bottom != null) { + margins._setMarginsAll(all, top, right, bottom); + } else if (top != null && right == null) { + margins._setMarginsLT(all, top); + } else if (top == null && bottom != null) { + margins._setMarginsLT(all, bottom); + } else { + margins._setMargins(all); + } } } //Implementation - void _updateSize(PdfPageOrientation orientation, [Size size]) { + void _updateSize(PdfPageOrientation orientation, [Size? size]) { double min; double max; if (size != null) { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_template_element.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_template_element.dart index e0e0e8604..6aca55923 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_template_element.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_template_element.dart @@ -1,15 +1,70 @@ part of pdf; /// Describes a page template object that can be used as header/footer, watermark or stamp. +/// ```dart +/// //Create a new pdf document +/// PdfDocument document = PdfDocument(); +/// //Create a header and add the top of the document. +/// document.template.top = PdfPageTemplateElement(Rect.fromLTWH(0, 0, 515, 50)) +/// ..graphics.drawString( +/// 'Header', PdfStandardFont(PdfFontFamily.helvetica, 14), +/// brush: PdfBrushes.black); +/// //Create footer and add the bottom of the document. +/// document.template.bottom = +/// PdfPageTemplateElement(Rect.fromLTWH(0, 0, 515, 50)) +/// ..graphics.drawString( +/// 'Footer', PdfStandardFont(PdfFontFamily.helvetica, 11), +/// brush: PdfBrushes.black); +/// //Add the pages to the document +/// for (int i = 1; i <= 5; i++) { +/// document.pages.add().graphics.drawString( +/// 'Page $i', PdfStandardFont(PdfFontFamily.timesRoman, 11), +/// bounds: Rect.fromLTWH(250, 0, 515, 100)); +/// } +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfPageTemplateElement { //Constructor - /// Initializes a new instance of the [PdfPageTemplateElement] class - PdfPageTemplateElement(Rect bounds, [PdfPage page]) { + /// Initializes a new instance of the [PdfPageTemplateElement] class. + /// ```dart + /// Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the header with specific bounds + /// PdfPageTemplateElement header = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 300)); + /// header.graphics.drawString( + /// 'Header', PdfStandardFont(PdfFontFamily.helvetica, 14), + /// brush: PdfBrushes.black); + /// //Add the header at top of the document + /// document.template.top = header; + /// //Create the footer with specific bounds + /// PdfPageTemplateElement footer = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 50)); + /// header.graphics.drawString( + /// 'Footer', PdfStandardFont(PdfFontFamily.helvetica, 11), + /// brush: PdfBrushes.black); + /// //Add the footer at the bottom of the document + /// document.template.bottom = footer; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfPageTemplateElement(Rect bounds, [PdfPage? page]) { x = bounds.left; y = bounds.top; _pdfTemplate = PdfTemplate(bounds.width, bounds.height); if (page != null) { - graphics.colorSpace = page._document.colorSpace; + graphics.colorSpace = page._document!.colorSpace; } } @@ -18,29 +73,70 @@ class PdfPageTemplateElement { /// page layers or behind of it. If false, the page template will be located /// behind of page layer. bool foreground = false; - PdfDockStyle _dockStyle; - PdfAlignmentStyle _alignmentStyle; - PdfTemplate _pdfTemplate; + PdfDockStyle? _dockStyle; + PdfAlignmentStyle _alignmentStyle = PdfAlignmentStyle.none; + PdfTemplate? _pdfTemplate; _TemplateType _templateType = _TemplateType.none; _Point _currentLocation = _Point.empty; //Properties - /// Gets the dock style of the page template element. + /// Gets or sets the dock style of the page template element. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Set margins. + /// document.pageSettings.setMargins(25); + /// //Create the page template with specific bounds + /// final PdfPageTemplateElement custom = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, 100, 100), document.pages.add()); + /// document.template.stamps.add(custom); + /// //Gets or sets the dock style + /// custom.dock = PdfDockStyle.right; + /// //Draw template into pdf page. + /// custom.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 165, 0), width: 3), + /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), + /// bounds: Rect.fromLTWH(0, 0, 100, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfDockStyle get dock { _dockStyle ??= PdfDockStyle.none; - return _dockStyle; + return _dockStyle!; } - /// Sets the dock style of the page template element. set dock(PdfDockStyle value) { _dockStyle = value; _resetAlignment(); } - /// Gets alignment of the page template element. + /// Gets or sets the alignment of the page template element. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Set margins. + /// document.pageSettings.setMargins(25); + /// //Create the page template with specific bounds + /// final PdfPageTemplateElement custom = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, 100, 100), document.pages.add()); + /// document.template.stamps.add(custom); + /// //Gets or sets dock style. + /// custom.dock = PdfDockStyle.right; + /// Gets or sets alignment style. + /// custom.alignment = PdfAlignmentStyle.middleCenter; + /// //Draw template into pdf page. + /// custom.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 165, 0), width: 3), + /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), + /// bounds: Rect.fromLTWH(0, 0, 100, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfAlignmentStyle get alignment => _alignmentStyle; - - /// Sets alignment of the page template element. set alignment(PdfAlignmentStyle value) { if (_alignmentStyle != value) { _setAlignment(value); @@ -49,80 +145,230 @@ class PdfPageTemplateElement { /// Indicates whether the page template is located behind of the page layers /// or in front of it. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Set margins. + /// document.pageSettings.setMargins(25); + /// //Create the page template with specific bounds + /// final PdfPageTemplateElement custom = PdfPageTemplateElement( + /// const Offset(5, 5) & const Size(110, 110), document.pages.add()); + /// document.template.stamps.add(custom); + /// Gets or sets background. + /// custom.background = true; + /// //Draw template into pdf page. + /// custom.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 165, 0), width: 3), + /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), + /// bounds: Rect.fromLTWH(0, 0, 100, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` bool get background => !foreground; - - /// Indicates whether the page template is located behind of the page layers - /// or in front of it. set background(bool value) { foreground = !value; } - /// Gets location of the page template element. + /// Gets or sets location of the page template element. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Set margins. + /// document.pageSettings.setMargins(25); + /// //Create the page template with specific bounds + /// final PdfPageTemplateElement custom = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, 100, 100), document.pages.add()); + /// document.template.stamps.add(custom); + /// //Gets or sets location. + /// custom.location = Offset(5, 5); + /// //Draw template into pdf page. + /// custom.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 165, 0), width: 3), + /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), + /// bounds: Rect.fromLTWH(0, 0, 100, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` Offset get location => _currentLocation.offset; - - /// Sets location of the page template element. set location(Offset value) { - ArgumentError.checkNotNull(value); if (_templateType == _TemplateType.none) { _currentLocation = _Point.fromOffset(value); } } - /// Gets X co-ordinate of the template element on the page. - double get x => _currentLocation != null ? _currentLocation.x : 0; - - /// Sets X co-ordinate of the template element on the page. + /// Gets or sets X co-ordinate of the template element on the page. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Set margins. + /// document.pageSettings.setMargins(25); + /// //Create the page template with specific bounds + /// final PdfPageTemplateElement custom = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, 100, 100), document.pages.add()); + /// document.template.stamps.add(custom); + /// //Gets or sets X co-ordinate. + /// custom.x = 10.10; + /// //Draw template into pdf page. + /// custom.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 165, 0), width: 3), + /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), + /// bounds: Rect.fromLTWH(0, 0, 100, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + double get x => _currentLocation.x; set x(double value) { if (_type == _TemplateType.none) { _currentLocation.x = value; } } - /// Gets Y co-ordinate of the template element on the page. - double get y => _currentLocation != null ? _currentLocation.y : 0; - - /// Sets Y co-ordinate of the template element on the page. + /// Gets or sets Y co-ordinate of the template element on the page. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Set margins. + /// document.pageSettings.setMargins(25); + /// //Create the page template with specific bounds + /// final PdfPageTemplateElement custom = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, 100, 100), document.pages.add()); + /// document.template.stamps.add(custom); + /// //Gets or sets Y co-ordinate. + /// custom.y = 10.10; + /// //Draw template into pdf page. + /// custom.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 165, 0), width: 3), + /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), + /// bounds: Rect.fromLTWH(0, 0, 100, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + double get y => _currentLocation.y; set y(double value) { if (_type == _TemplateType.none) { _currentLocation.y = value; } } - /// Gets size of the page template element. - Size get size => _template.size; - - /// Sets size of the page template element. + /// Gets or sets the size of the page template element. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Set margins. + /// document.pageSettings.setMargins(25); + /// //Create the page template with specific bounds + /// final PdfPageTemplateElement custom = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, 100, 100), document.pages.add()); + /// document.template.stamps.add(custom); + /// //Gets or sets size. + /// custom.size = Size(110, 110); + /// //Draw template into pdf page. + /// custom.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 165, 0), width: 3), + /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), + /// bounds: Rect.fromLTWH(0, 0, 100, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + Size get size => _template!.size; set size(Size value) { if (_type == _TemplateType.none) { - _template.reset(value.width, value.height); + _template!.reset(value.width, value.height); } } - /// Gets width of the page template element. - double get width => _template.size.width; - - /// Sets width of the page template element. + /// Gets or sets width of the page template element. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Set margins. + /// document.pageSettings.setMargins(25); + /// //Create the page template with specific bounds + /// final PdfPageTemplateElement custom = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, 100, 100), document.pages.add()); + /// document.template.stamps.add(custom); + /// //Gets or sets width. + /// custom.width = 110; + /// //Draw template into pdf page. + /// custom.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 165, 0), width: 3), + /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), + /// bounds: Rect.fromLTWH(0, 0, 100, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + double get width => _template!.size.width; set width(double value) { - if (_template.size.width != value && _type == _TemplateType.none) { - _template.reset(value, _template.size.height); + if (_template!.size.width != value && _type == _TemplateType.none) { + _template!.reset(value, _template!.size.height); } } - /// Gets height of the page template element. - double get height => _template.size.height; - - /// Sets height of the page template element. + /// Gets or sets height of the page template element. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Set margins. + /// document.pageSettings.setMargins(25); + /// //Create the page template with specific bounds + /// final PdfPageTemplateElement custom = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, 100, 100), document.pages.add()); + /// document.template.stamps.add(custom); + /// //Gets or sets height. + /// custom.height = 110; + /// //Draw template into pdf page. + /// custom.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 165, 0), width: 3), + /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), + /// bounds: Rect.fromLTWH(0, 0, 100, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + double get height => _template!.size.height; set height(double value) { - if (_template.size.height != value && _type == _TemplateType.none) { - _template.reset(_template.size.width, value); + if (_template!.size.height != value && _type == _TemplateType.none) { + _template!.reset(_template!.size.width, value); } } - /// Gets graphics context of the page template element. - PdfGraphics get graphics => _template.graphics; + /// Gets or sets graphics context of the page template element. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Set margins. + /// document.pageSettings.setMargins(25); + /// //Create the page template with specific bounds + /// final PdfPageTemplateElement custom = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, 100, 100), document.pages.add()); + /// document.template.stamps.add(custom); + /// //Draw template into pdf page. + /// custom.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 165, 0), width: 3), + /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), + /// bounds: Rect.fromLTWH(0, 0, 100, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfGraphics get graphics => _template!.graphics!; /// Gets PDF template object. - PdfTemplate get _template => _pdfTemplate; + PdfTemplate? get _template => _pdfTemplate; /// Gets type of the usage of this page template. _TemplateType get _type => _templateType; @@ -133,10 +379,29 @@ class PdfPageTemplateElement { _templateType = value; } - /// Gets bounds of the page template element. + /// Gets or sets bounds of the page template element. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Set margins. + /// document.pageSettings.setMargins(25); + /// //Create the page template with specific bounds + /// final PdfPageTemplateElement custom = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, 100, 100), document.pages.add()); + /// document.template.stamps.add(custom); + /// //Gets or sets bounds. + /// Rect bounds = custom.bounds; + /// //Draw template into pdf page. + /// custom.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(255, 165, 0), width: 3), + /// brush: PdfSolidBrush(PdfColor(173, 255, 47)), + /// bounds: Rect.fromLTWH(0, 0, 100, 100)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` Rect get bounds => Rect.fromLTWH(x, y, width, height); - - /// Sets bounds of the page template element. set bounds(Rect value) { if (_type == _TemplateType.none) { location = Offset(value.left, value.top); @@ -146,17 +411,13 @@ class PdfPageTemplateElement { //Implementation void _draw(PdfPageLayer layer, PdfDocument document) { - ArgumentError.checkNotNull(layer, 'layer'); - ArgumentError.checkNotNull(document, 'document'); final PdfPage page = layer.page; final Rect bounds = _calculateBounds(page, document); layer._page._isDefaultGraphics = true; - layer.graphics.drawPdfTemplate(_template, bounds.topLeft, bounds.size); + layer.graphics.drawPdfTemplate(_template!, bounds.topLeft, bounds.size); } Rect _calculateBounds(PdfPage page, PdfDocument document) { - ArgumentError.checkNotNull(page, 'page'); - ArgumentError.checkNotNull(document, 'document'); Rect result = bounds; if (_alignmentStyle != PdfAlignmentStyle.none) { result = _getAlignmentBounds(page, document); @@ -167,8 +428,6 @@ class PdfPageTemplateElement { } Rect _getAlignmentBounds(PdfPage page, PdfDocument document) { - ArgumentError.checkNotNull(page, 'page'); - ArgumentError.checkNotNull(document, 'document'); Rect result = bounds; if (_type == _TemplateType.none) { result = _getSimpleAlignmentBounds(page, document); @@ -177,10 +436,8 @@ class PdfPageTemplateElement { } Rect _getSimpleAlignmentBounds(PdfPage page, PdfDocument document) { - ArgumentError.checkNotNull(page, 'page'); - ArgumentError.checkNotNull(document, 'document'); final Rect result = bounds; - final PdfSection section = page._section; + final PdfSection section = page._section!; final _Rectangle actualBounds = section._getActualBounds(page, false, document); double x = location.dx; @@ -251,14 +508,14 @@ class PdfPageTemplateElement { break; case PdfAlignmentStyle.none: break; + default: + break; } return Offset(x, y) & result.size; } Rect _getDockBounds(PdfPage page, PdfDocument document) { - ArgumentError.checkNotNull(page, 'page'); - ArgumentError.checkNotNull(document, 'document'); Rect result = bounds; if (_type == _TemplateType.none) { @@ -270,16 +527,14 @@ class PdfPageTemplateElement { } Rect _getSimpleDockBounds(PdfPage page, PdfDocument document) { - ArgumentError.checkNotNull(page, 'page'); - ArgumentError.checkNotNull(document, 'document'); Rect result = bounds; - final PdfSection section = page._section; + final PdfSection section = page._section!; final _Rectangle actualBounds = section._getActualBounds(page, false, document); double x = location.dx; double y = location.dy; - double _width = width; - double _height = height; + double? _width = width; + double? _height = height; switch (_dockStyle) { case PdfDockStyle.left: @@ -318,6 +573,8 @@ class PdfPageTemplateElement { break; case PdfDockStyle.none: break; + default: + break; } result = Rect.fromLTWH(x, y, _width, _height); @@ -326,17 +583,15 @@ class PdfPageTemplateElement { } Rect _getTemplateDockBounds(PdfPage page, PdfDocument document) { - ArgumentError.checkNotNull(page, 'page'); - ArgumentError.checkNotNull(document, 'document'); - final PdfSection section = page._section; + final PdfSection section = page._section!; final _Rectangle actualBounds = section._getActualBounds(page, false, document); final _Size actualSize = _Size.fromSize(section.pageSettings._getActualSize()); double x = location.dx; double y = location.dy; - double _width = width; - double _height = height; + double? _width = width; + double? _height = height; switch (_dockStyle) { case PdfDockStyle.left: @@ -386,6 +641,8 @@ class PdfPageTemplateElement { break; case PdfDockStyle.none: break; + default: + break; } return Rect.fromLTWH(x, y, _width, _height); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_section.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_section.dart index 7f7a0a073..49d9382bd 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_section.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_section.dart @@ -2,109 +2,174 @@ part of pdf; /// Represents a section entity. A section is a set of the pages /// with similar page settings. +/// ```dart +/// //Create a new PDF documentation +/// PdfDocument document = PdfDocument(); +/// //Create a new PDF section +/// PdfSection section = document.sections.add(); +/// //Create a new PDF page and draw the text +/// section.pages.add().graphics.drawString( +/// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), +/// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfSection implements _IPdfWrapper { //Constructor /// Initializes a new instance of the [PdfSection] class. - PdfSection._(PdfDocument document, [PdfPageSettings settings]) { + PdfSection._(PdfDocument? document, [PdfPageSettings? settings]) { _pageReferences = _PdfArray(); _section = _PdfDictionary(); - _section._beginSave = _beginSave; + _section!._beginSave = _beginSave; _pageCount = _PdfNumber(0); - _section[_DictionaryProperties.count] = _pageCount; - _section[_DictionaryProperties.type] = + _section![_DictionaryProperties.count] = _pageCount; + _section![_DictionaryProperties.type] = _PdfName(_DictionaryProperties.pages); - _section[_DictionaryProperties.kids] = _pageReferences; + _section![_DictionaryProperties.kids] = _pageReferences; _pdfDocument = document; _settings = - settings != null ? settings._clone() : document.pageSettings._clone(); + settings != null ? settings._clone() : document!.pageSettings._clone(); } //Fields - _PdfArray _pageReferences; - _PdfDictionary _section; - _PdfNumber _pageCount; - PdfDocument _pdfDocument; - PdfPageSettings _settings; - PdfSectionCollection _sectionCollection; - bool _isNewPageSection = false; + _PdfArray? _pageReferences; + _PdfDictionary? _section; + _PdfNumber? _pageCount; + PdfDocument? _pdfDocument; + late PdfPageSettings _settings; + PdfSectionCollection? _sectionCollection; + bool? _isNewPageSection = false; final List _pages = []; /// Event rises when the new page has been added - PageAddedCallback pageAdded; - PdfSectionTemplate _template; - PdfPageCollection _pageCollection; + PageAddedCallback? pageAdded; + PdfSectionTemplate? _template; + PdfPageCollection? _pageCollection; //Properties - /// Gets the [PdfSectionTemplate] for the pages in the section. + /// Gets or sets the [PdfSectionTemplate] for the pages in the section. + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF section + /// PdfSection section = document.sections.add(); + /// //Sets the page settings of the section + /// section.pageSettings = + /// PdfPageSettings(PdfPageSize.a4, PdfPageOrientation.portrait); + /// //Sets the template for the page in the section + /// section.template = PdfSectionTemplate(); + /// //Create a new PDF page and draw the text + /// section.pages.add().graphics.drawString( + /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), + /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfSectionTemplate get template { _template ??= PdfSectionTemplate(); - return _template; + return _template!; } - /// Sets [PdfSectionTemplate] for the pages in the section. set template(PdfSectionTemplate value) { _template = value; } /// Gets the collection of pages in this section. + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF section + /// PdfSection section = document.sections.add(); + /// //Create a new PDF page and draw the text + /// section.pages.add().graphics.drawString( + /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), + /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfPageCollection get pages { _pageCollection ??= PdfPageCollection._(_pdfDocument, this); - return _pageCollection; + return _pageCollection!; } - /// Gets the [PdfPageSettings] of the section. - PdfPageSettings get pageSettings => _settings; + /// Gets or sets the [PdfPageSettings] of the section. + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF section + /// PdfSection section = document.sections.add(); + /// //Sets the page settings of the section + /// section.pageSettings = + /// PdfPageSettings(PdfPageSize.a4, PdfPageOrientation.portrait); + /// //Sets the template for the page in the section + /// section.template = PdfSectionTemplate(); + /// //Create a new PDF page and draw the text + /// section.pages.add().graphics.drawString( + /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), + /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfPageSettings get pageSettings { + _settings._isPageAdded = _settings.margins._isPageAdded = _pages.isNotEmpty; + return _settings; + } - /// Sets the [PdfPageSettings] of the section. set pageSettings(PdfPageSettings settings) { - if (settings == null) { - throw ArgumentError.notNull('settings'); + if (_pages.isEmpty) { + _settings = settings; } - _settings = settings; } /// Gets the parent. - PdfSectionCollection get _parent => _sectionCollection; + PdfSectionCollection? get _parent => _sectionCollection; /// Sets the parent. - set _parent(PdfSectionCollection sections) { + set _parent(PdfSectionCollection? sections) { _sectionCollection = sections; if (sections != null) { - _section[_DictionaryProperties.parent] = _PdfReferenceHolder(sections); + _section![_DictionaryProperties.parent] = _PdfReferenceHolder(sections); } else { - _section.remove(_DictionaryProperties.parent); + _section!.remove(_DictionaryProperties.parent); } } /// Gets the document. - PdfDocument get _document => _sectionCollection._document; + PdfDocument? get _document => _sectionCollection!._document; /// Gets the count of the pages in the section. - int get _count => _pageReferences.count; + int get _count => _pageReferences!.count; //Implementation void _add(PdfPage page) { - if (!_isNewPageSection) { + if (!_isNewPageSection!) { _isNewPageSection = page._isNewPage; } final _PdfReferenceHolder holder = _PdfReferenceHolder(page); _isNewPageSection = false; _pages.add(page); - _pageReferences._add(holder); + _pageReferences!._add(holder); page._assignSection(this); _pdfPageAdded(page); } void _remove(PdfPage page) { - ArgumentError.checkNotNull(page); final _PdfReferenceHolder r = _PdfReferenceHolder(page); - if (_pageReferences._contains(r)) { - _pageReferences._elements.remove(r); + if (_pageReferences!._contains(r)) { + _pageReferences!._elements.remove(r); _pages.remove(page); } } - PdfPage _getPageByIndex(int index) { + PdfPage? _getPageByIndex(int index) { if (index < 0 || index >= _count) { throw ArgumentError.value(index, 'our of range'); } @@ -112,8 +177,7 @@ class PdfSection implements _IPdfWrapper { } void _setPageSettings(_PdfDictionary container, - [PdfPageSettings parentSettings]) { - ArgumentError.checkNotNull(container, 'container'); + [PdfPageSettings? parentSettings]) { if (parentSettings == null || pageSettings._size != parentSettings._size) { final _Rectangle bounds = _Rectangle(_settings._origin.x, _settings._origin.y, _settings.size.width, _settings.size.height); @@ -124,11 +188,11 @@ class PdfSection implements _IPdfWrapper { pageSettings.rotate != parentSettings.rotate) { int rotate = 0; if (_sectionCollection != null) { - if (pageSettings._isRotation && !_document.pageSettings._isRotation) { + if (pageSettings._isRotation && !_document!.pageSettings._isRotation) { rotate = PdfSectionCollection._rotateFactor * pageSettings.rotate.index; } else { - if (!_document.pageSettings._isRotation && + if (!_document!.pageSettings._isRotation && pageSettings.rotate != PdfPageRotateAngle.rotateAngle0) { rotate = PdfSectionCollection._rotateFactor * pageSettings.rotate.index; @@ -153,22 +217,18 @@ class PdfSection implements _IPdfWrapper { } _Rectangle _getActualBounds(PdfPage page, bool includeMargins, - [PdfDocument document]) { - ArgumentError.checkNotNull(page, 'page'); + [PdfDocument? document]) { if (document == null) { if (_parent != null) { - final PdfDocument pdfDocument = _parent._document; - ArgumentError.checkNotNull(pdfDocument, - '''The section should be added to the section collection - before this operation'''); - return _getActualBounds(page, includeMargins, pdfDocument); + final PdfDocument? pdfDocument = _parent!._document; + return _getActualBounds(page, includeMargins, pdfDocument!); } else { final _Size size = includeMargins ? _Size.fromSize(pageSettings._getActualSize()) - : pageSettings.size; - final double left = includeMargins ? pageSettings.margins.left : 0; - final double top = includeMargins ? pageSettings.margins.top : 0; - return _Rectangle(left, top, size.width, size.height); + : pageSettings.size as _Size; + final double? left = includeMargins ? pageSettings.margins.left : 0; + final double? top = includeMargins ? pageSettings.margins.top : 0; + return _Rectangle(left!, top!, size.width, size.height); } } else { final _Rectangle bounds = _Rectangle.empty; @@ -191,13 +251,11 @@ class PdfSection implements _IPdfWrapper { double _getLeftIndentWidth( PdfDocument document, PdfPage page, bool includeMargins) { - ArgumentError.checkNotNull(document); - ArgumentError.checkNotNull(page); double value = includeMargins ? pageSettings.margins.left : 0; final double templateWidth = - template._getLeft(page) != null ? template._getLeft(page).width : 0; + template._getLeft(page) != null ? template._getLeft(page)!.width : 0; final double docTemplateWidth = document.template._getLeft(page) != null - ? document.template._getLeft(page).width + ? document.template._getLeft(page)!.width : 0; value += template.leftTemplate ? (templateWidth >= docTemplateWidth ? templateWidth : docTemplateWidth) @@ -207,13 +265,11 @@ class PdfSection implements _IPdfWrapper { double _getTopIndentHeight( PdfDocument document, PdfPage page, bool includeMargins) { - ArgumentError.checkNotNull(document); - ArgumentError.checkNotNull(page); double value = includeMargins ? pageSettings.margins.top : 0; final double templateHeight = - template._getTop(page) != null ? template._getTop(page).height : 0; + template._getTop(page) != null ? template._getTop(page)!.height : 0; final double docTemplateHeight = document.template._getTop(page) != null - ? document.template._getTop(page).height + ? document.template._getTop(page)!.height : 0; value += template.topTemplate ? (templateHeight >= docTemplateHeight @@ -225,13 +281,11 @@ class PdfSection implements _IPdfWrapper { double _getRightIndentWidth( PdfDocument document, PdfPage page, bool includeMargins) { - ArgumentError.checkNotNull(document); - ArgumentError.checkNotNull(page); double value = includeMargins ? pageSettings.margins.right : 0; final double templateWidth = - template._getRight(page) != null ? template._getRight(page).width : 0; + template._getRight(page) != null ? template._getRight(page)!.width : 0; final double docTemplateWidth = document.template._getRight(page) != null - ? document.template._getRight(page).width + ? document.template._getRight(page)!.width : 0; value += template.rightTemplate ? (templateWidth >= docTemplateWidth ? templateWidth : docTemplateWidth) @@ -241,14 +295,12 @@ class PdfSection implements _IPdfWrapper { double _getBottomIndentHeight( PdfDocument document, PdfPage page, bool includeMargins) { - ArgumentError.checkNotNull(document); - ArgumentError.checkNotNull(page); double value = includeMargins ? pageSettings.margins.bottom : 0; final double templateHeight = template._getBottom(page) != null - ? template._getBottom(page).height + ? template._getBottom(page)!.height : 0; final double docTemplateHeight = document.template._getBottom(page) != null - ? document.template._getBottom(page).height + ? document.template._getBottom(page)!.height : 0; value += template.bottomTemplate ? (templateHeight >= docTemplateHeight @@ -258,29 +310,29 @@ class PdfSection implements _IPdfWrapper { return value; } - void _beginSave(Object sender, _SavePdfPrimitiveArgs args) { - _pageCount.value = _count; - final PdfDocument document = args._writer._document; + void _beginSave(Object sender, _SavePdfPrimitiveArgs? args) { + _pageCount!.value = _count; + final PdfDocument? document = args!._writer!._document; if (document == null) { - _setPageSettings(_section); + _setPageSettings(_section!); } else { - _setPageSettings(_section, document.pageSettings); + _setPageSettings(_section!, document.pageSettings); } } void _pdfPageAdded(PdfPage page) { final PageAddedArgs args = PageAddedArgs(page); _onPageAdded(args); - final PdfSectionCollection sectionCollection = _parent; + final PdfSectionCollection? sectionCollection = _parent; if (sectionCollection != null) { - sectionCollection._document.pages._onPageAdded(args); + sectionCollection._document!.pages._onPageAdded(args); } - _pageCount.value = _count; + _pageCount!.value = _count; } void _onPageAdded(PageAddedArgs args) { if (pageAdded != null) { - pageAdded(this, args); + pageAdded!(this, args); } } @@ -289,26 +341,18 @@ class PdfSection implements _IPdfWrapper { return _pages.indexOf(page); } final _PdfReferenceHolder holder = _PdfReferenceHolder(page); - return _pageReferences._indexOf(holder); + return _pageReferences!._indexOf(holder); } bool _containsTemplates(PdfDocument document, PdfPage page, bool foreground) { - if (page == null) { - ArgumentError.notNull('page'); - } - if (document == null) { - throw ArgumentError.notNull('document'); - } - - final List documentHeaders = + final List documentHeaders = _getDocumentTemplates(document, page, true, foreground); - final List documentTemplates = + final List documentTemplates = _getDocumentTemplates(document, page, false, foreground); - final List sectionHeaders = + final List sectionHeaders = _getSectionTemplates(page, true, foreground); - final List sectionTemplates = + final List sectionTemplates = _getSectionTemplates(page, false, foreground); - final bool contains = documentHeaders.isNotEmpty || documentTemplates.isNotEmpty || sectionHeaders.isNotEmpty || @@ -318,37 +362,33 @@ class PdfSection implements _IPdfWrapper { List _getDocumentTemplates( PdfDocument document, PdfPage page, bool headers, bool foreground) { - if (page == null) { - ArgumentError.notNull('page'); - } - if (document == null) { - throw ArgumentError.notNull('document'); - } final List templates = []; if (headers) { if (template.topTemplate && document.template._getTop(page) != null && - document.template._getTop(page).foreground == foreground) { - templates.add(document.template._getTop(page)); + document.template._getTop(page)!.foreground == foreground) { + templates.add(document.template._getTop(page)!); } if (template.bottomTemplate && document.template._getBottom(page) != null && - document.template._getBottom(page).foreground == foreground) { - templates.add(document.template._getBottom(page)); + document.template._getBottom(page)!.foreground == foreground) { + templates.add(document.template._getBottom(page)!); } if (template.leftTemplate && document.template._getLeft(page) != null && - document.template._getLeft(page).foreground == foreground) { - templates.add(document.template._getLeft(page)); + document.template._getLeft(page)!.foreground == foreground) { + templates.add(document.template._getLeft(page)!); } if (template.rightTemplate && document.template._getRight(page) != null && - document.template._getRight(page).foreground == foreground) { - templates.add(document.template._getRight(page)); + document.template._getRight(page)!.foreground == foreground) { + templates.add(document.template._getRight(page)!); } } else if (template.stamp) { - for (final PdfPageTemplateElement template - in document.template.stamps._list) { + final List list = document.template.stamps._list; + for (int i = 0; i < list.length; i++) { + final PdfPageTemplateElement template = + list[i] as PdfPageTemplateElement; if (template.foreground == foreground) { templates.add(template); } @@ -359,31 +399,30 @@ class PdfSection implements _IPdfWrapper { List _getSectionTemplates( PdfPage page, bool headers, bool foreground) { - if (page == null) { - ArgumentError.notNull('page'); - } final List templates = []; if (headers) { if (template._getTop(page) != null && - template._getTop(page).foreground == foreground) { - templates.add(template._getTop(page)); + template._getTop(page)!.foreground == foreground) { + templates.add(template._getTop(page)!); } if (template._getBottom(page) != null && - template._getBottom(page).foreground == foreground) { - templates.add(template._getBottom(page)); + template._getBottom(page)!.foreground == foreground) { + templates.add(template._getBottom(page)!); } if (template._getLeft(page) != null && - template._getLeft(page).foreground == foreground) { - templates.add(template._getLeft(page)); + template._getLeft(page)!.foreground == foreground) { + templates.add(template._getLeft(page)!); } if (template._getRight(page) != null && - template._getRight(page).foreground == foreground) { - templates.add(template._getRight(page)); + template._getRight(page)!.foreground == foreground) { + templates.add(template._getRight(page)!); } } else { - for (final PdfPageTemplateElement template in template.stamps._list) { - if (template.foreground == foreground) { - templates.add(template); + final List list = template.stamps._list; + for (int i = 0; i < list.length; i++) { + final PdfPageTemplateElement temp = list[i] as PdfPageTemplateElement; + if (temp.foreground == foreground) { + templates.add(temp); } } } @@ -392,13 +431,6 @@ class PdfSection implements _IPdfWrapper { void _drawTemplates( PdfPage page, PdfPageLayer layer, PdfDocument document, bool foreground) { - if (layer == null) { - ArgumentError.notNull('layer'); - } - if (document == null) { - throw ArgumentError.notNull('document'); - } - final List documentHeaders = _getDocumentTemplates(document, page, true, foreground); final List documentTemplates = @@ -407,7 +439,6 @@ class PdfSection implements _IPdfWrapper { _getSectionTemplates(page, true, foreground); final List sectionTemplates = _getSectionTemplates(page, false, foreground); - if (foreground) { _internaldrawTemplates(layer, document, sectionHeaders); _internaldrawTemplates(layer, document, sectionTemplates); @@ -423,13 +454,7 @@ class PdfSection implements _IPdfWrapper { void _internaldrawTemplates(PdfPageLayer layer, PdfDocument document, List templates) { - if (layer == null) { - ArgumentError.notNull('layer'); - } - if (document == null) { - throw ArgumentError.notNull('document'); - } - if (templates != null && templates.isNotEmpty) { + if (templates.isNotEmpty) { for (final PdfPageTemplateElement template in templates) { template._draw(layer, document); } @@ -438,26 +463,24 @@ class PdfSection implements _IPdfWrapper { _Point _pointToNativePdf(PdfPage page, _Point point) { final _Rectangle bounds = _getActualBounds(page, true); - point.x += bounds.left; point.y = pageSettings.height - (bounds.top + point.y); - return point; } void _dropCropBox() { - _setPageSettings(_section, null); - _section[_DictionaryProperties.cropBox] = - _section[_DictionaryProperties.mediaBox]; + _setPageSettings(_section!, null); + _section![_DictionaryProperties.cropBox] = + _section![_DictionaryProperties.mediaBox]; } @override - _IPdfPrimitive get _element => _section; + _IPdfPrimitive? get _element => _section; @override //ignore: unused_element - set _element(_IPdfPrimitive value) { - _section = value; + set _element(_IPdfPrimitive? value) { + _section = value as _PdfDictionary?; } } @@ -467,15 +490,15 @@ typedef PageAddedCallback = void Function(Object sender, PageAddedArgs args); /// Provides data for [PageAddedCallback] event. class PageAddedArgs { - /// Initializes a new instance of the [PageAddedEventArgs] class. + /// Initializes a new instance of the [PageAddedArgs] class. /// /// [page] - represending the page which is added in the document. - PageAddedArgs([PdfPage page]) { + PageAddedArgs([PdfPage? page]) { if (page != null) { this.page = page; } } /// Gets the newly added page. - PdfPage page; + late PdfPage page; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_section_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_section_collection.dart index bd792e79a..d15af1810 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_section_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_section_collection.dart @@ -13,11 +13,11 @@ class PdfSectionCollection implements _IPdfWrapper { static const int _rotateFactor = 90; //Fields - PdfDocument _document; - _PdfNumber _count; - _PdfDictionary _pages; + PdfDocument? _document; + _PdfNumber? _count; + _PdfDictionary? _pages; final List _sections = []; - _PdfArray _sectionCollection; + _PdfArray? _sectionCollection; //Properties /// Gets the [PdfSection] at the specified index (Read only). @@ -37,23 +37,22 @@ class PdfSectionCollection implements _IPdfWrapper { //Implementation /// Adds the specified section. void _addSection(PdfSection section) { - ArgumentError.checkNotNull(section, 'section'); final _PdfReferenceHolder holder = _PdfReferenceHolder(section); _sections.add(section); section._parent = this; - _sectionCollection._add(holder); + _sectionCollection!._add(holder); } void _initialize() { _count = _PdfNumber(0); _sectionCollection = _PdfArray(); _pages = _PdfDictionary(); - _pages._beginSave = _beginSave; - _pages[_DictionaryProperties.type] = _PdfName('Pages'); - _pages[_DictionaryProperties.kids] = _sectionCollection; - _pages[_DictionaryProperties.count] = _count; - _pages[_DictionaryProperties.resources] = _PdfDictionary(); - _setPageSettings(_pages, _document.pageSettings); + _pages!._beginSave = _beginSave; + _pages![_DictionaryProperties.type] = _PdfName('Pages'); + _pages![_DictionaryProperties.kids] = _sectionCollection; + _pages![_DictionaryProperties.count] = _count; + _pages![_DictionaryProperties.resources] = _PdfDictionary(); + _setPageSettings(_pages!, _document!.pageSettings); } /// Check key and return the value. @@ -66,9 +65,7 @@ class PdfSectionCollection implements _IPdfWrapper { void _setPageSettings( _PdfDictionary dictionary, PdfPageSettings pageSettings) { - ArgumentError.checkNotNull(dictionary, 'dictionary'); - ArgumentError.checkNotNull(pageSettings, 'pageSettings'); - final List list = [ + final List list = [ 0, 0, pageSettings._size.width, @@ -90,17 +87,17 @@ class PdfSectionCollection implements _IPdfWrapper { return count; } - void _beginSave(Object sender, _SavePdfPrimitiveArgs args) { - _count.value = _countPages(); - _setPageSettings(_pages, _document.pageSettings); + void _beginSave(Object sender, _SavePdfPrimitiveArgs? args) { + _count!.value = _countPages(); + _setPageSettings(_pages!, _document!.pageSettings); } @override - _IPdfPrimitive get _element => _pages; + _IPdfPrimitive? get _element => _pages; @override //ignore: unused_element - set _element(_IPdfPrimitive value) { - _pages = value; + set _element(_IPdfPrimitive? value) { + _pages = value as _PdfDictionary?; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_section_template.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_section_template.dart index 5fe20b0bb..223c5db24 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_section_template.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_section_template.dart @@ -1,29 +1,155 @@ part of pdf; /// Represents a page template for all the pages in the section. +/// ```dart +/// //Create a new PDF documentation +/// PdfDocument document = PdfDocument(); +/// //Create a new PDF section +/// PdfSection section = document.sections.add(); +/// //Create a section template +/// PdfSectionTemplate template = PdfSectionTemplate(); +/// //Sets the template for the page in the section +/// section.template = template; +/// //Create a new PDF page and draw the text +/// section.pages.add().graphics.drawString( +/// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), +/// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfSectionTemplate extends PdfDocumentTemplate { /// Initializes a new instance of the [PdfSectionCollection] class. + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF section + /// PdfSection section = document.sections.add(); + /// //Create a section template + /// PdfSectionTemplate template = PdfSectionTemplate(); + /// //Sets the template for the page in the section + /// section.template = template; + /// //Create a new PDF page and draw the text + /// section.pages.add().graphics.drawString( + /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), + /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfSectionTemplate() : super() { leftTemplate = topTemplate = rightTemplate = bottomTemplate = stamp = true; } /// Gets or sets value indicating whether parent Left page template /// should be used or not. - bool leftTemplate; + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF section + /// PdfSection section = document.sections.add(); + /// //Create a section template + /// PdfSectionTemplate template = PdfSectionTemplate()..leftTemplate = false; + /// //Sets the template for the page in the section + /// section.template = template; + /// //Create a new PDF page and draw the text + /// section.pages.add().graphics.drawString( + /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), + /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + late bool leftTemplate; /// Gets or sets value indicating whether parent top page template /// should be used or not. - bool topTemplate; + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF section + /// PdfSection section = document.sections.add(); + /// //Create a section template + /// PdfSectionTemplate template = PdfSectionTemplate()..topTemplate = false; + /// //Sets the template for the page in the section + /// section.template = template; + /// //Create a new PDF page and draw the text + /// section.pages.add().graphics.drawString( + /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), + /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + late bool topTemplate; /// Gets or sets value indicating whether parent right page template /// should be used or not. - bool rightTemplate; + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF section + /// PdfSection section = document.sections.add(); + /// //Create a section template + /// PdfSectionTemplate template = PdfSectionTemplate()..rightTemplate = false; + /// //Sets the template for the page in the section + /// section.template = template; + /// //Create a new PDF page and draw the text + /// section.pages.add().graphics.drawString( + /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), + /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + late bool rightTemplate; /// Gets or sets value indicating whether parent bottom page template /// should be used or not. - bool bottomTemplate; + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF section + /// PdfSection section = document.sections.add(); + /// //Create a section template + /// PdfSectionTemplate template = PdfSectionTemplate()..bottomTemplate = false; + /// //Sets the template for the page in the section + /// section.template = template; + /// //Create a new PDF page and draw the text + /// section.pages.add().graphics.drawString( + /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), + /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + late bool bottomTemplate; /// Gets or sets value indicating whether the parent stamp elements /// should be used or not. - bool stamp; + /// ```dart + /// //Create a new PDF documentation + /// PdfDocument document = PdfDocument(); + /// //Create a new PDF section + /// PdfSection section = document.sections.add(); + /// //Create a section template + /// PdfSectionTemplate template = PdfSectionTemplate()..stamp = false; + /// //Sets the template for the page in the section + /// section.template = template; + /// //Create a new PDF page and draw the text + /// section.pages.add().graphics.drawString( + /// 'Hello World!!!', PdfStandardFont(PdfFontFamily.helvetica, 27), + /// brush: PdfBrushes.darkBlue, bounds: const Rect.fromLTWH(170, 100, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + late bool stamp; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment.dart index a96a00a9a..3a6ca3cf9 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment.dart @@ -6,7 +6,7 @@ class PdfAttachment extends _PdfEmbeddedFileSpecification { /// Initializes a new instance of the [PdfAttachment] class with specified /// file name and byte data to be attached. PdfAttachment(String fileName, List data, - {String description, String mimeType}) + {String? description, String? mimeType}) : super(fileName, data) { _updateValues(description, mimeType); } @@ -14,7 +14,7 @@ class PdfAttachment extends _PdfEmbeddedFileSpecification { /// Initializes a new instance of the [PdfAttachment] class with specified /// file name and byte data as base64 String to be attached. PdfAttachment.fromBase64String(String fileName, String base64String, - {String description, String mimeType}) + {String? description, String? mimeType}) : super(fileName, base64.decode(base64String)) { _updateValues(description, mimeType); } @@ -29,11 +29,9 @@ class PdfAttachment extends _PdfEmbeddedFileSpecification { set description(String value) => super.description = value; /// Gets the file name. - @override String get fileName => _embeddedFile.fileName; /// Sets the file name. - @override set fileName(String value) => _embeddedFile.fileName = value; /// Gets the data. @@ -72,7 +70,7 @@ class PdfAttachment extends _PdfEmbeddedFileSpecification { _PdfName(_getEnumName(_relationship))); } - void _updateValues(String desc, String mime) { + void _updateValues(String? desc, String? mime) { if (mime != null) { mimeType = mime; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment_collection.dart index d061205a2..881ef8093 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment_collection.dart @@ -10,7 +10,7 @@ class PdfAttachmentCollection extends PdfObjectCollection } PdfAttachmentCollection._( - _PdfDictionary attachmentDictionary, _PdfCrossTable crossTable) { + _PdfDictionary attachmentDictionary, _PdfCrossTable? crossTable) { _dictionary = attachmentDictionary; _crossTable = crossTable; _initializeAttachmentCollection(); @@ -18,17 +18,20 @@ class PdfAttachmentCollection extends PdfObjectCollection //Fields _PdfDictionary _dictionary = _PdfDictionary(); - _PdfArray _array = _PdfArray(); + _PdfArray? _array = _PdfArray(); // ignore: prefer_final_fields bool _conformance = false; int _count = 0; final Map _dic = {}; - _PdfCrossTable _crossTable; + _PdfCrossTable? _crossTable; //Properties /// Gets the attachment by index from the collection. Read-Only. PdfAttachment operator [](int index) { - return _list[index] is PdfAttachment ? _list[index] : null; + if (index < 0 || index >= count) { + throw RangeError('index'); + } + return _list[index] as PdfAttachment; } //Public methods. @@ -36,7 +39,6 @@ class PdfAttachmentCollection extends PdfObjectCollection /// /// Returns position of the inserted attachment. int add(PdfAttachment attachment) { - ArgumentError.checkNotNull(attachment, 'attachment'); if (_conformance) { throw ArgumentError( 'Attachment is not allowed for this conformance level.'); @@ -48,7 +50,6 @@ class PdfAttachmentCollection extends PdfObjectCollection /// Removes the specified attachment from the collection. void remove(PdfAttachment attachment) { - ArgumentError.checkNotNull(attachment, 'attachment'); _doRemove(attachment: attachment); } @@ -59,7 +60,6 @@ class PdfAttachmentCollection extends PdfObjectCollection /// Search and find the index of the attachment. int indexOf(PdfAttachment attachment) { - ArgumentError.checkNotNull(attachment, 'attachment'); return _list.indexOf(attachment); } @@ -67,7 +67,6 @@ class PdfAttachmentCollection extends PdfObjectCollection /// /// Returns true if the attachment is present. bool contains(PdfAttachment attachment) { - ArgumentError.checkNotNull(attachment, 'attachment'); return _list.contains(attachment); } @@ -83,78 +82,76 @@ class PdfAttachmentCollection extends PdfObjectCollection final String converted = utf8.encode(fileName).length != fileName.length ? 'Attachment ${_count++}' : fileName; - if (_dic.isEmpty && _array.count > 0) { - for (int i = 0; i < _array.count; i += 2) { - if (!_dic.containsKey((_array[i] as _PdfString).value)) { - _dic[(_array[i] as _PdfString).value] = - _array[i + 1] as _PdfReferenceHolder; + if (_dic.isEmpty && _array!.count > 0) { + for (int i = 0; i < _array!.count; i += 2) { + if (!_dic.containsKey((_array![i] as _PdfString).value)) { + _dic[(_array![i] as _PdfString).value!] = + _array![i + 1] as _PdfReferenceHolder; } else { - final String value = (_array[i] as _PdfString).value + '_copy'; - _dic[value] = _array[i + 1] as _PdfReferenceHolder; + final String value = (_array![i] as _PdfString).value! + '_copy'; + _dic[value] = _array![i + 1] as _PdfReferenceHolder; } } } !_dic.containsKey(converted) ? _dic[converted] = _PdfReferenceHolder(attachment) : _dic[converted + '_copy'] = _PdfReferenceHolder(attachment); - final List orderList = _dic.keys.toList(); + final List orderList = _dic.keys.toList(); orderList.sort(); - _array._clear(); + _array!._clear(); for (final key in orderList) { - _array._add(_PdfString(key)); - _array._add(_dic[key]); + _array!._add(_PdfString(key!)); + _array!._add(_dic[key]!); } _list.add(attachment); return _list.length - 1; } //Removes the attachment. - void _doRemove({PdfAttachment attachment, int index}) { + void _doRemove({PdfAttachment? attachment, int? index}) { if (attachment != null) { index = _list.indexOf(attachment); } - _array._removeAt(2 * index); - _IPdfPrimitive attachmentDictionay = - _PdfCrossTable._dereference(_array[2 * index]); + _array!._removeAt(2 * index!); + _IPdfPrimitive? attachmentDictionay = + _PdfCrossTable._dereference(_array![2 * index]); if (attachmentDictionay is _PdfDictionary) { _removeAttachementObjects(attachmentDictionay); attachmentDictionay = null; } - _array._removeAt(2 * index); + _array!._removeAt(2 * index); _list.removeAt(index); } //Removing attachment dictionary and stream from main object collection. void _removeAttachementObjects(_PdfDictionary attachmentDictionary) { - _PdfMainObjectCollection _objectCollection; - if (_crossTable != null && _crossTable._document != null) { - _objectCollection = _crossTable._document._objects; + _PdfMainObjectCollection? _objectCollection; + if (_crossTable != null && _crossTable!._document != null) { + _objectCollection = _crossTable!._document!._objects; } if (_objectCollection != null) { - if (attachmentDictionary != null) { - if (attachmentDictionary.containsKey(_DictionaryProperties.ef)) { - final _IPdfPrimitive embedded = _PdfCrossTable._dereference( - attachmentDictionary[_DictionaryProperties.ef]); - if (embedded is _PdfDictionary) { - if (embedded.containsKey(_DictionaryProperties.f)) { - final _IPdfPrimitive stream = _PdfCrossTable._dereference( - embedded[_DictionaryProperties.f]); - if (stream != null) { - if (_objectCollection.contains(stream)) { - final int index = _objectCollection._lookFor(stream); - if (_objectCollection._objectCollection.length > index) { - _objectCollection._objectCollection.removeAt(index); - } + if (attachmentDictionary.containsKey(_DictionaryProperties.ef)) { + final _IPdfPrimitive? embedded = _PdfCrossTable._dereference( + attachmentDictionary[_DictionaryProperties.ef]); + if (embedded is _PdfDictionary) { + if (embedded.containsKey(_DictionaryProperties.f)) { + final _IPdfPrimitive? stream = + _PdfCrossTable._dereference(embedded[_DictionaryProperties.f]); + if (stream != null) { + if (_objectCollection.contains(stream)) { + final int index = _objectCollection._lookFor(stream)!; + if (_objectCollection._objectCollection!.length > index) { + _objectCollection._objectCollection!.removeAt(index); } } } } } - if (_objectCollection.contains(attachmentDictionary)) { - final int index = _objectCollection._lookFor(attachmentDictionary); - if (_objectCollection._objectCollection.length > index) { - _objectCollection._objectCollection.removeAt(index); - } + } + if (_objectCollection.contains(attachmentDictionary)) { + final int index = _objectCollection._lookFor(attachmentDictionary)!; + if (_objectCollection._objectCollection!.length > index) { + _objectCollection._objectCollection!.removeAt(index); } } if (_dic.isNotEmpty) { @@ -167,11 +164,11 @@ class PdfAttachmentCollection extends PdfObjectCollection void _doClear() { _list.clear(); if (_crossTable != null) { - final _PdfMainObjectCollection coll = _crossTable._document._objects; + final _PdfMainObjectCollection? coll = _crossTable!._document!._objects; if (coll != null) { - for (int i = 1; i < _array.count; i = i + 2) { - if (_array[i] is _PdfReferenceHolder) { - final _IPdfPrimitive dic = _PdfCrossTable._dereference(_array[i]); + for (int i = 1; i < _array!.count; i = i + 2) { + if (_array![i] is _PdfReferenceHolder) { + final _IPdfPrimitive? dic = _PdfCrossTable._dereference(_array![i]); if (dic is _PdfDictionary) { _removeAttachementObjects(dic); } @@ -179,40 +176,36 @@ class PdfAttachmentCollection extends PdfObjectCollection } } } - _array._clear(); + _array!._clear(); } void _initializeAttachmentCollection() { - _IPdfPrimitive embedDictionary = + _IPdfPrimitive? embedDictionary = _dictionary[_DictionaryProperties.embeddedFiles]; if (embedDictionary is _PdfReferenceHolder) { - embedDictionary = (embedDictionary as _PdfReferenceHolder).object; + embedDictionary = embedDictionary.object; } if (embedDictionary is _PdfDictionary) { - final _IPdfPrimitive obj = - (embedDictionary as _PdfDictionary)[_DictionaryProperties.names]; - final _IPdfPrimitive kid = - (embedDictionary as _PdfDictionary)[_DictionaryProperties.kids]; - if (!(obj is _PdfArray) && kid is _PdfArray) { + final _IPdfPrimitive? obj = embedDictionary[_DictionaryProperties.names]; + final _IPdfPrimitive? kid = embedDictionary[_DictionaryProperties.kids]; + if (!(obj is _PdfArray) && kid != null && kid is _PdfArray) { final _PdfArray kids = kid; - if (kids != null) { - if (kids.count != 0) { - for (int l = 0; l < kids.count; l++) { - if (kids[l] is _PdfReferenceHolder || kids[l] is _PdfDictionary) { - embedDictionary = kids[l] is _PdfDictionary - ? kids[l] as _PdfDictionary - : (kids[l] as _PdfReferenceHolder).object != null && - (kids[l] as _PdfReferenceHolder).object - is _PdfDictionary - ? (kids[l] as _PdfReferenceHolder).object - : null; - if (embedDictionary != null && - embedDictionary is _PdfDictionary) { - _array = - embedDictionary[_DictionaryProperties.names] as _PdfArray; - if (_array != null) { - _attachmentInformation(_array); - } + if (kids.count != 0) { + for (int l = 0; l < kids.count; l++) { + if (kids[l] is _PdfReferenceHolder || kids[l] is _PdfDictionary) { + embedDictionary = kids[l] is _PdfDictionary + ? kids[l] as _PdfDictionary + : (kids[l] as _PdfReferenceHolder).object != null && + (kids[l] as _PdfReferenceHolder).object + is _PdfDictionary + ? (kids[l] as _PdfReferenceHolder).object + : null; + if (embedDictionary != null && + embedDictionary is _PdfDictionary) { + _array = + embedDictionary[_DictionaryProperties.names] as _PdfArray?; + if (_array != null) { + _attachmentInformation(_array!); } } } @@ -220,7 +213,7 @@ class PdfAttachmentCollection extends PdfObjectCollection } } else if (obj is _PdfArray) { _array = obj; - _attachmentInformation(_array); + _attachmentInformation(_array!); } } } @@ -231,31 +224,39 @@ class PdfAttachmentCollection extends PdfObjectCollection int k = 1; for (int i = 0; i < (_array.count ~/ 2); i++) { if (_array[k] is _PdfReferenceHolder || _array[k] is _PdfDictionary) { - _IPdfPrimitive streamDictionary = _array[k]; + _IPdfPrimitive? streamDictionary = _array[k]; if (_array[k] is _PdfReferenceHolder) { streamDictionary = (_array[k] as _PdfReferenceHolder).object; } if (streamDictionary is _PdfDictionary) { - _PdfStream stream = _PdfStream(); - _PdfDictionary attachmentStream; + _PdfStream? stream = _PdfStream(); + _PdfDictionary? attachmentStream; if (streamDictionary.containsKey(_DictionaryProperties.ef)) { if (streamDictionary[_DictionaryProperties.ef] is _PdfDictionary) { attachmentStream = streamDictionary[_DictionaryProperties.ef] - as _PdfDictionary; + as _PdfDictionary?; } else if (streamDictionary[_DictionaryProperties.ef] is _PdfReferenceHolder) { final _PdfReferenceHolder streamHolder = streamDictionary[_DictionaryProperties.ef] as _PdfReferenceHolder; - attachmentStream = streamHolder.object as _PdfDictionary; + attachmentStream = streamHolder.object as _PdfDictionary?; } - final _PdfReferenceHolder holder1 = - attachmentStream[_DictionaryProperties.f] - as _PdfReferenceHolder; + final _PdfReferenceHolder? holder1 = + attachmentStream![_DictionaryProperties.f] + as _PdfReferenceHolder?; if (holder1 != null) { + final _IPdfPrimitive? reference = holder1.reference; if (holder1.object != null && holder1.object is _PdfStream) { - stream = holder1.object; + stream = holder1.object as _PdfStream?; + if (stream != null && + _crossTable!.encryptor != null && + _crossTable!.encryptor!._encryptOnlyAttachment! && + reference != null && + reference is _PdfReference) { + stream.decrypt(_crossTable!.encryptor!, reference._objNum); + } } } } @@ -264,14 +265,14 @@ class PdfAttachmentCollection extends PdfObjectCollection stream._decompress(); if (streamDictionary.containsKey('F')) { attachment = PdfAttachment( - (streamDictionary['F'] as _PdfString).value, - stream._dataStream); + (streamDictionary['F'] as _PdfString).value!, + stream._dataStream!); final _IPdfPrimitive fileStream = stream; if (fileStream is _PdfDictionary) { - final _IPdfPrimitive subtype = _PdfCrossTable._dereference( + final _IPdfPrimitive? subtype = _PdfCrossTable._dereference( fileStream[_DictionaryProperties.subtype]); if (subtype is _PdfName) { - attachment.mimeType = subtype._name + attachment.mimeType = subtype._name! .replaceAll('#23', '#') .replaceAll('#20', ' ') .replaceAll('#2F', '/'); @@ -279,14 +280,14 @@ class PdfAttachmentCollection extends PdfObjectCollection } if (fileStream is _PdfDictionary && fileStream.containsKey(_DictionaryProperties.params)) { - final _IPdfPrimitive mParams = _PdfCrossTable._dereference( + final _IPdfPrimitive? mParams = _PdfCrossTable._dereference( fileStream[_DictionaryProperties.params]) - as _PdfDictionary; + as _PdfDictionary?; if (mParams is _PdfDictionary) { - final _IPdfPrimitive creationDate = + final _IPdfPrimitive? creationDate = _PdfCrossTable._dereference( mParams[_DictionaryProperties.creationDate]); - final _IPdfPrimitive modifiedDate = + final _IPdfPrimitive? modifiedDate = _PdfCrossTable._dereference( mParams[_DictionaryProperties.modificationDate]); if (creationDate is _PdfString) { @@ -301,7 +302,7 @@ class PdfAttachmentCollection extends PdfObjectCollection } if (streamDictionary .containsKey(_DictionaryProperties.afRelationship)) { - final _IPdfPrimitive relationShip = + final _IPdfPrimitive? relationShip = _PdfCrossTable._dereference(streamDictionary[ _DictionaryProperties.afRelationship]); if (relationShip is _PdfName) { @@ -311,20 +312,20 @@ class PdfAttachmentCollection extends PdfObjectCollection } if (streamDictionary.containsKey('Desc')) { attachment.description = - (streamDictionary['Desc'] as _PdfString).value; + (streamDictionary['Desc'] as _PdfString).value!; } } else { attachment = PdfAttachment( - (streamDictionary['Desc'] as _PdfString).value, - stream._dataStream); + (streamDictionary['Desc'] as _PdfString).value!, + stream._dataStream!); } } else { if (streamDictionary.containsKey('Desc')) { attachment = PdfAttachment( - (streamDictionary['Desc'] as _PdfString).value, []); + (streamDictionary['Desc'] as _PdfString).value!, []); } else { attachment = PdfAttachment( - (streamDictionary['F'] as _PdfString).value, []); + (streamDictionary['F'] as _PdfString).value!, []); } } _list.add(attachment); @@ -336,7 +337,7 @@ class PdfAttachmentCollection extends PdfObjectCollection } //Obtain Attachement relation ship - PdfAttachmentRelationship _obtainRelationShip(String relation) { + PdfAttachmentRelationship _obtainRelationShip(String? relation) { PdfAttachmentRelationship relationShip = PdfAttachmentRelationship.unspecified; switch (relation) { @@ -366,7 +367,8 @@ class PdfAttachmentCollection extends PdfObjectCollection _IPdfPrimitive get _element => _dictionary; @override - set _element(_IPdfPrimitive value) { + // ignore: unused_element + set _element(_IPdfPrimitive? value) { throw ArgumentError('primitive element can\'t be set'); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field.dart index 71a3e67c9..f5018ceea 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field.dart @@ -3,60 +3,235 @@ part of pdf; /// Represents a fields which is calculated before the document saves. abstract class PdfAutomaticField { // constructor - PdfAutomaticField._(PdfFont font, {Rect bounds, PdfBrush brush}) : super() { - _font = font; + PdfAutomaticField._(PdfFont? font, {Rect? bounds, PdfBrush? brush}) + : super() { + this.font = font ?? PdfStandardFont(PdfFontFamily.helvetica, 8); if (bounds != null) { _bounds = _Rectangle.fromRect(bounds); + } else { + _bounds = _Rectangle.empty; } - _brush = brush; + this.brush = brush ?? PdfBrushes.black; } // fields - _Rectangle _bounds; + late _Rectangle _bounds; - PdfFont _font; + PdfPen? _pen; - PdfBrush _brush; + Size _templateSize = const Size(0, 0); - PdfPen _pen; + /// Gets or sets the font of the field. + ///```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the header with specific bounds + /// PdfPageTemplateElement header = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 300)); + /// //Create the date and time field + /// PdfDateTimeField dateAndTimeField = PdfDateTimeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// dateAndTimeField.date = DateTime(2020, 2, 10, 13, 13, 13, 13, 13); + /// dateAndTimeField.dateFormatString = 'E, MM.dd.yyyy'; + /// //Create the composite field. + /// PdfCompositeField compositefields = PdfCompositeField(); + /// //Gets or sets the font. + /// compositefields.font = PdfStandardFont(PdfFontFamily.timesRoman, 19); + /// //Gets or sets the brush. + /// compositefields.brush = PdfSolidBrush(PdfColor(0, 0, 0)); + /// //Gets or sets the text. + /// compositefields.text = '{0} Header'; + /// //Gets or sets the fields. + /// compositefields.fields = [dateAndTimeField]; + /// //Add composite field in header + /// compositefields.draw(header.graphics, + /// Offset(0, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 11).height)); + /// //Add the header at top of the document + /// document.template.top = header; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + ///``` + late PdfFont font; - Size _templateSize = const Size(0, 0); + /// Gets or sets the brush of the field. + ///```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the header with specific bounds + /// PdfPageTemplateElement header = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 300)); + /// //Create the date and time field + /// PdfDateTimeField dateAndTimeField = PdfDateTimeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// dateAndTimeField.date = DateTime(2020, 2, 10, 13, 13, 13, 13, 13); + /// dateAndTimeField.dateFormatString = 'E, MM.dd.yyyy'; + /// //Create the composite field. + /// PdfCompositeField compositefields = PdfCompositeField(); + /// //Gets or sets the font. + /// compositefields.font = PdfStandardFont(PdfFontFamily.timesRoman, 19); + /// //Gets or sets the brush. + /// compositefields.brush = PdfSolidBrush(PdfColor(0, 0, 0)); + /// //Gets or sets the text. + /// compositefields.text = '{0} Header'; + /// //Gets or sets the fields. + /// compositefields.fields = [dateAndTimeField]; + /// //Add composite field in header + /// compositefields.draw(header.graphics, + /// Offset(0, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 11).height)); + /// //Add the header at top of the document + /// document.template.top = header; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + ///``` + late PdfBrush brush; /// Gets or sets the stringFormat of the field. - PdfStringFormat stringFormat; + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the header with specific bounds + /// PdfPageTemplateElement header = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 300)); + /// //Create the date and time field + /// PdfDateTimeField dateAndTimeField = PdfDateTimeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// dateAndTimeField.date = DateTime(2020, 2, 10, 13, 13, 13, 13, 13); + /// dateAndTimeField.dateFormatString = 'E, MM.dd.yyyy'; + /// //Create the composite field with date field + /// PdfCompositeField compositefields = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// text: '{0} Header', + /// fields: [dateAndTimeField]); + /// compositefields.stringFormat = + /// PdfStringFormat(lineAlignment: PdfVerticalAlignment.middle); + /// //Add composite field in header + /// compositefields.draw(header.graphics, + /// Offset(0, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 11).height)); + /// //Add the header at top of the document + /// document.template.top = header; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfStringFormat? stringFormat; // properties - /// Gets the bounds of the field. + /// Gets or sets the bounds of the field. + ///```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the header with specific bounds + /// PdfPageTemplateElement header = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 300)); + /// //Create the date and time field + /// PdfDateTimeField dateAndTimeField = PdfDateTimeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// dateAndTimeField.date = DateTime(2020, 2, 10, 13, 13, 13, 13, 13); + /// dateAndTimeField.dateFormatString = 'E, MM.dd.yyyy'; + /// //Create the composite field. + /// PdfCompositeField compositefields = PdfCompositeField(); + /// //Gets or sets the bounds. + /// compositefields.bounds = Rect.fromLTWH(10, 10, 200, 200); + /// //Gets or sets the font. + /// compositefields.font = PdfStandardFont(PdfFontFamily.timesRoman, 19); + /// //Gets or sets the brush. + /// compositefields.brush = PdfSolidBrush(PdfColor(0, 0, 0)); + /// //Gets or sets the text. + /// compositefields.text = '{0} Header'; + /// //Gets or sets the fields. + /// compositefields.fields = [dateAndTimeField]; + /// //Add composite field in header + /// compositefields.draw(header.graphics, + /// Offset(0, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 11).height)); + /// //Add the header at top of the document + /// document.template.top = header; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + ///``` Rect get bounds { - _bounds ??= _Rectangle.empty; return _bounds.rect; } - /// Sets the bounds of the field. set bounds(Rect value) { _bounds = _Rectangle.fromRect(value); } - /// Gets the font of the field. - PdfFont get font => _font; - - /// Sets the font of the field. - set font(PdfFont value) { - _font = (value == null) ? throw ArgumentError.value('font') : value; - } - - /// Gets the brush of the field. - PdfBrush get brush => _brush; - - /// Sets the brush of the field. - set brush(PdfBrush value) { - _brush = (value == null) ? throw ArgumentError.value('brush') : value; - } - - /// Gets the pen of the field. - PdfPen get pen => _pen; - - /// Sets the pen of the field. - set pen(PdfPen value) { + /// Gets or sets the pen of the field. + ///```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the header with specific bounds + /// PdfPageTemplateElement header = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 300)); + /// //Create the date and time field + /// PdfDateTimeField dateAndTimeField = PdfDateTimeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// dateAndTimeField.date = DateTime(2020, 2, 10, 13, 13, 13, 13, 13); + /// dateAndTimeField.dateFormatString = 'E, MM.dd.yyyy'; + /// //Create the composite field. + /// PdfCompositeField compositefields = PdfCompositeField(); + /// //Gets or sets the font. + /// compositefields.font = PdfStandardFont(PdfFontFamily.timesRoman, 19); + /// //Gets or sets the pen. + /// compositefields.pen = PdfPen(PdfColor(0, 0, 0), width: 2); + /// //Gets or sets the text. + /// compositefields.text = '{0} Header'; + /// //Gets or sets the fields. + /// compositefields.fields = [dateAndTimeField]; + /// //Add composite field in header + /// compositefields.draw(header.graphics, + /// Offset(0, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 11).height)); + /// //Add the header at top of the document + /// document.template.top = header; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + ///``` + PdfPen? get pen => _pen; + set pen(PdfPen? value) { _pen = (value == null) ? throw ArgumentError.value('brush') : value; } @@ -65,26 +240,56 @@ abstract class PdfAutomaticField { /// Graphics context where the element should be printed. /// location has contains X co-ordinate of the element, /// Y co-ordinate of the element. - void draw(PdfGraphics graphics, [Offset location]) { + ///```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the header with specific bounds + /// PdfPageTemplateElement header = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 300)); + /// //Create the date and time field + /// PdfDateTimeField dateAndTimeField = PdfDateTimeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// dateAndTimeField.date = DateTime(2020, 2, 10, 13, 13, 13, 13, 13); + /// dateAndTimeField.dateFormatString = 'E, MM.dd.yyyy'; + /// //Create the composite field with date field + /// PdfCompositeField compositefields = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// text: '{0} Header', + /// fields: [dateAndTimeField]); + /// //Add composite field in header + /// compositefields.draw(header.graphics, + /// Offset(0, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 11).height)); + /// //Add the header at top of the document + /// document.template.top = header; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + ///``` + void draw(PdfGraphics graphics, [Offset? location]) { location ??= const Offset(0, 0); - graphics._autoFields + graphics._autoFields! .add(_PdfAutomaticFieldInfo(this, _Point.fromOffset(location))); } - String _getValue(PdfGraphics graphics) { + String? _getValue(PdfGraphics graphics) { return graphics as String; } - PdfFont _obtainFont() { - return _font ?? PdfStandardFont(PdfFontFamily.helvetica, 8); - } - - void _performDraw( - PdfGraphics graphics, _Point location, double scalingX, double scalingY) { + void _performDraw(PdfGraphics graphics, _Point? location, double scalingX, + double scalingY) { if (bounds.height == 0 || bounds.width == 0) { - final String text = _getValue(graphics); - _templateSize = _obtainFont() - .measureString(text, layoutArea: bounds.size, format: stringFormat); + final String text = _getValue(graphics)!; + _templateSize = font.measureString(text, + layoutArea: bounds.size, format: stringFormat); } } @@ -95,8 +300,4 @@ abstract class PdfAutomaticField { return bounds.size; } } - - PdfBrush _obtainBrush() { - return _brush ?? PdfBrushes.black; - } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field_info.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field_info.dart index 9e8af501a..8d138704e 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field_info.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field_info.dart @@ -3,35 +3,17 @@ part of pdf; class _PdfAutomaticFieldInfo { // constructor _PdfAutomaticFieldInfo(PdfAutomaticField field, - [_Point location, double scalingX, double scalingY]) { + [_Point? location, double scalingX = 1, double scalingY = 1]) { this.field = field; - this.location = location; + this.location = location != null ? location : _Point.empty; scalingX = scalingX; scalingY = scalingY; } - // fields - _Point _location; - PdfAutomaticField _field; double scalingX = 1; double scalingY = 1; - - // properties - PdfAutomaticField get field => _field; - set field(PdfAutomaticField value) { - (value == null) ? throw ArgumentError.notNull('field') : _field = value; - } - - _Point get location { - _location ??= _Point.empty; - return _location; - } - - set location(_Point value) { - if (value != null) { - _location = value; - } - } + late PdfAutomaticField field; + late _Point location; } class _PdfAutomaticFieldInfoCollection extends PdfObjectCollection { @@ -40,7 +22,6 @@ class _PdfAutomaticFieldInfoCollection extends PdfObjectCollection { // implementaion int add(_PdfAutomaticFieldInfo fieldInfo) { - ArgumentError.checkNotNull(fieldInfo, 'fieldInfo'); _list.add(fieldInfo); return count - 1; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_composite_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_composite_field.dart index 573bde357..f8015763a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_composite_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_composite_field.dart @@ -2,6 +2,38 @@ part of pdf; /// Represents class which can concatenate multiple /// automatic fields into single string. +/// ```dart +/// //Create a new pdf document +/// PdfDocument document = PdfDocument(); +/// //Add the pages to the document +/// for (int i = 1; i <= 5; i++) { +/// document.pages.add().graphics.drawString( +/// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), +/// bounds: Rect.fromLTWH(250, 0, 615, 100)); +/// } +/// //Create the header with specific bounds +/// PdfPageTemplateElement header = PdfPageTemplateElement( +/// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 300)); +/// //Create the date and time field +/// PdfDateTimeField dateAndTimeField = PdfDateTimeField( +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), +/// brush: PdfSolidBrush(PdfColor(0, 0, 0))); +/// //Create the composite field with date field +/// PdfCompositeField compositefields = PdfCompositeField( +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), +/// brush: PdfSolidBrush(PdfColor(0, 0, 0)), +/// text: '{0} Header', +/// fields: [dateAndTimeField]); +/// //Add composite field in header +/// compositefields.draw(header.graphics, +/// Offset(0, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 11).height)); +/// //Add the header at top of the document +/// document.template.top = header; +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfCompositeField extends _PdfMultipleValueField { // constructor /// Initializes the new instance of the [PdfCompositeField] class. @@ -10,56 +42,155 @@ class PdfCompositeField extends _PdfMultipleValueField { /// [brush] - Specifies the color and texture to the text. /// [text] - The wide-chracter string to be drawn. /// [fields] - The list of [PdfAutomaticField] objects. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the header with specific bounds + /// PdfPageTemplateElement header = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 300)); + /// //Create the date and time field + /// PdfDateTimeField dateAndTimeField = PdfDateTimeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //Create the composite field with date field + /// PdfCompositeField compositefields = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// text: '{0} Header', + /// fields: [dateAndTimeField]); + /// //Add composite field in header + /// compositefields.draw(header.graphics, + /// Offset(0, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 11).height)); + /// //Add the header at top of the document + /// document.template.top = header; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfCompositeField( - {PdfFont font, - PdfBrush brush, - String text, - List fields}) + {PdfFont? font, + PdfBrush? brush, + String? text, + List? fields}) : super(font: font, brush: brush) { this.text = (text == null) ? '' : text; - this.fields = fields; + if (fields != null) { + this.fields = fields; + } } // field /// Internal variable to store list of automatic fields. - List _fields; - String _text = ''; + List? _fields; // properties - /// Get the text for user format to display the page details + /// Get or set the text for user format to display the page details /// (eg. Input text:page {0} of {1} as dispalyed to page 1 of 5) - String get text => _text; + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the footer with specific bounds + /// PdfPageTemplateElement footer = + /// PdfPageTemplateElement(const Rect.fromLTWH(0, 0, 515, 50)); + /// //Create the page number field + /// PdfPageNumberField pageNumber = PdfPageNumberField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //Sets the number style for page number + /// pageNumber.numberStyle = PdfNumberStyle.upperRoman; + /// //Create the composite field + /// PdfCompositeField compositeField = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //Set text to composite field. + /// compositeField.text = 'Page {0}'; + /// //Add page number field to composite fields + /// compositeField.fields.add(pageNumber); + /// //Set bounds to composite field. + /// compositeField.bounds = footer.bounds; + /// //Add the composite field in footer + /// compositeField.draw(footer.graphics, + /// Offset(290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); + /// //Add the footer at the bottom of the document + /// document.template.bottom = footer; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + String text = ''; - /// Set the text for user format to display the page details - /// (eg. Input text:page {0} of {1} as dispalyed to page 1 of 5) - set text(String value) { - ArgumentError.checkNotNull(value, 'text'); - _text = value; - } - - /// Gets the automatic fields(like page number, page count and etc.,) + /// Gets or sets the automatic fields(like page number, page count and etc.,) + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the footer with specific bounds + /// PdfPageTemplateElement footer = + /// PdfPageTemplateElement(const Rect.fromLTWH(0, 0, 515, 50)); + /// //Create the page number field + /// PdfPageNumberField pageNumber = PdfPageNumberField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //Sets the number style for page number + /// pageNumber.numberStyle = PdfNumberStyle.upperRoman; + /// //Create the composite field + /// PdfCompositeField compositeField = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //Set text to composite field. + /// compositeField.text = 'Page {0}'; + /// //Sets page number field to composite fields + /// compositeField.fields = [pageNumber]; + /// //Set bounds to composite field. + /// compositeField.bounds = footer.bounds; + /// //Add the composite field in footer + /// compositeField.draw(footer.graphics, + /// Offset(290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); + /// //Add the footer at the bottom of the document + /// document.template.bottom = footer; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` List get fields { _fields ??= []; - return _fields; + return _fields!; } - /// Sets the automatic fields(like page number, page count and etc.,) set fields(List value) { - if (value != null) { - _fields = value; - } + _fields = value; } // implementation @override String _getValue(PdfGraphics graphics) { - String _copyText; - if (fields != null && fields.isNotEmpty) { - _copyText = _text; + String? copyText; + if (fields.isNotEmpty) { + copyText = text; for (int i = 0; i < fields.length; i++) { - _copyText = _copyText.replaceAll('{$i}', fields[i]._getValue(graphics)); + copyText = copyText!.replaceAll('{$i}', fields[i]._getValue(graphics)!); } } - return (_copyText == null) ? _text : _copyText; + return (copyText == null) ? text : copyText; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_date_time_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_date_time_field.dart index 31ef9f6e1..3afa2a46f 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_date_time_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_date_time_field.dart @@ -1,6 +1,41 @@ part of pdf; /// Represents date and time automated field. +/// ```dart +/// //Create a new pdf document +/// PdfDocument document = PdfDocument(); +/// //Add the pages to the document +/// for (int i = 1; i <= 5; i++) { +/// document.pages.add().graphics.drawString( +/// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), +/// bounds: Rect.fromLTWH(250, 0, 615, 100)); +/// } +/// //Create the footer with specific bounds +/// PdfPageTemplateElement footer = +/// PdfPageTemplateElement(const Rect.fromLTWH(0, 0, 515, 50)); +/// //Create the composite field +/// PdfCompositeField compositeField = PdfCompositeField( +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), +/// brush: PdfSolidBrush(PdfColor(0, 0, 0)), +/// text: 'Time:{0}'); +/// //Create the date and time field +/// PdfDateTimeField dateTimeField = PdfDateTimeField( +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), +/// brush: PdfSolidBrush(PdfColor(0, 0, 0))); +/// //Add date&time field to composite fields +/// compositeField.fields.add(dateTimeField); +/// //Set bounds to composite field. +/// compositeField.bounds = footer.bounds; +/// //Add the composite field in footer +/// compositeField.draw(footer.graphics, +/// Offset(290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); +/// //Add the footer at the bottom of the document +/// document.template.bottom = footer; +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfDateTimeField extends _PdfStaticField { // constructor /// Initializes a new instance of the [PdfDateTimeField] class. @@ -8,40 +43,168 @@ class PdfDateTimeField extends _PdfStaticField { /// [font] - Specifies the [PdfFont] to use. /// [brush] - The object that is used to fill the string. /// [bounds] - Specifies the location and size of the field. - PdfDateTimeField({PdfFont font, PdfBrush brush, Rect bounds}) + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the footer with specific bounds + /// PdfPageTemplateElement footer = + /// PdfPageTemplateElement(const Rect.fromLTWH(0, 0, 515, 50)); + /// //Create the composite field + /// PdfCompositeField compositeField = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// text: 'Time:{0}'); + /// //Create the date and time field + /// PdfDateTimeField dateTimeField = PdfDateTimeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //Add date&time field to composite fields + /// compositeField.fields.add(dateTimeField); + /// //Set bounds to composite field. + /// compositeField.bounds = footer.bounds; + /// //Add the composite field in footer + /// compositeField.draw(footer.graphics, + /// Offset(290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); + /// //Add the footer at the bottom of the document + /// document.template.bottom = footer; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfDateTimeField({PdfFont? font, PdfBrush? brush, Rect? bounds}) : super(font: font, brush: brush, bounds: bounds); - // fields - String _formatString = "dd'/'MM'/'yyyy hh':'mm':'ss"; - String _locale = 'en_US'; - /// Get the current date and set the required date. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the footer with specific bounds + /// PdfPageTemplateElement footer = + /// PdfPageTemplateElement(const Rect.fromLTWH(0, 0, 515, 50)); + /// //Create the composite field + /// PdfCompositeField compositeField = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// text: 'Time:{0}'); + /// //Create the date and time field + /// PdfDateTimeField dateTimeField = PdfDateTimeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //Gets the date and time + /// DateTime dateTime = dateTimeField.date; + /// //Add date&time field to composite fields + /// compositeField.fields.add(dateTimeField); + /// //Set bounds to composite field. + /// compositeField.bounds = footer.bounds; + /// //Add the composite field in footer + /// compositeField.draw(footer.graphics, + /// Offset(290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); + /// //Add the footer at the bottom of the document + /// document.template.bottom = footer; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` DateTime date = DateTime.now(); // properties - /// Gets the date format string. - String get dateFormatString => _formatString; - - /// Sets the date format string. - set dateFormatString(String value) { - if (value != null) { - _formatString = value; - } - } + /// Gets or sets the date format string. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the footer with specific bounds + /// PdfPageTemplateElement footer = + /// PdfPageTemplateElement(const Rect.fromLTWH(0, 0, 515, 50)); + /// //Create the composite field + /// PdfCompositeField compositeField = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// text: 'Time:{0}'); + /// //Create the date and time field + /// PdfDateTimeField dateTimeField = PdfDateTimeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //Sets the date and time format + /// dateTimeField.dateFormatString = 'hh\':\'mm\':\'ss'; + /// //Add date&time field to composite fields + /// compositeField.fields.add(dateTimeField); + /// //Set bounds to composite field. + /// compositeField.bounds = footer.bounds; + /// //Add the composite field in footer + /// compositeField.draw(footer.graphics, + /// Offset(290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); + /// //Add the footer at the bottom of the document + /// document.template.bottom = footer; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + String dateFormatString = "dd'/'MM'/'yyyy hh':'mm':'ss"; - /// Gets the locale for date and time culture. - String get locale => _locale; - - /// Sets the locale for date and time culture - set locale(String value) { - if (value != null) { - _locale = value; - } - } + /// Gets or sets the locale for date and time culture. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the footer with specific bounds + /// PdfPageTemplateElement footer = + /// PdfPageTemplateElement(const Rect.fromLTWH(0, 0, 515, 50)); + /// //Create the composite field + /// PdfCompositeField compositeField = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// text: 'Time:{0}'); + /// //Create the date and time field + /// PdfDateTimeField dateTimeField = PdfDateTimeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //Sets the date and time locale + /// dateTimeField.locale = 'en_US'; + /// //Add date&time field to composite fields + /// compositeField.fields.add(dateTimeField); + /// //Set bounds to composite field. + /// compositeField.bounds = footer.bounds; + /// //Add the composite field in footer + /// compositeField.draw(footer.graphics, + /// Offset(290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); + /// //Add the footer at the bottom of the document + /// document.template.bottom = footer; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + String locale = 'en_US'; // implementation @override - String _getValue(PdfGraphics graphics) { + String _getValue(PdfGraphics? graphics) { initializeDateFormatting(locale); final DateFormat formatter = DateFormat(dateFormatString, locale); final String value = formatter.format(date); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_destination_page_number_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_destination_page_number_field.dart index 1cf08913e..145b8b4f1 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_destination_page_number_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_destination_page_number_field.dart @@ -6,29 +6,19 @@ class PdfDestinationPageNumberField extends PdfPageNumberField { /// Initializes a new instance of the [PdfDestinationPageNumberField] class /// may include with [PdfFont], [PdfBrush] and [Rect]. PdfDestinationPageNumberField( - {PdfPage page, PdfFont font, PdfBrush brush, Rect bounds}) + {PdfPage? page, PdfFont? font, PdfBrush? brush, Rect? bounds}) : super(font: font, brush: brush, bounds: bounds) { if (page != null) { - _page = page; + this.page = page; } } - // fields - PdfPage _page; - - // properties - /// Gets the page. - PdfPage get page => _page; - - /// Sets the page. - set page(PdfPage value) { - ArgumentError.checkNotNull(value, 'page'); - _page = value; - } + /// Gets or sets the page. + PdfPage? page; // implementation @override String _getValue(PdfGraphics graphics) { - return _internalGetValue(_page); + return _internalGetValue(page); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_dynamic_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_dynamic_field.dart index cda9be126..14e6ac0c3 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_dynamic_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_dynamic_field.dart @@ -4,13 +4,13 @@ part of pdf; /// into single string. abstract class _PdfDynamicField extends PdfAutomaticField { // constructor - _PdfDynamicField({PdfFont font, Rect bounds, PdfBrush brush}) + _PdfDynamicField({PdfFont? font, Rect? bounds, PdfBrush? brush}) : super._(font, bounds: bounds, brush: brush); // public methods static PdfPage _getPageFromGraphics(PdfGraphics graphics) { - final PdfPage page = graphics._page; + final PdfPage? page = graphics._page; ArgumentError.checkNotNull(page, 'page'); - return page; + return page!; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_multiple_value_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_multiple_value_field.dart index 95e2550a7..2cfd873eb 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_multiple_value_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_multiple_value_field.dart @@ -2,7 +2,7 @@ part of pdf; abstract class _PdfMultipleValueField extends _PdfDynamicField { // constructor - _PdfMultipleValueField({PdfFont font, PdfBrush brush, Rect bounds}) + _PdfMultipleValueField({PdfFont? font, PdfBrush? brush, Rect? bounds}) : super(font: font, bounds: bounds, brush: brush); // fields @@ -11,35 +11,35 @@ abstract class _PdfMultipleValueField extends _PdfDynamicField { // implementation @override - void _performDraw(PdfGraphics graphics, _Point _location, double scalingX, + void _performDraw(PdfGraphics graphics, _Point? _location, double scalingX, double scalingY) { super._performDraw(graphics, _location, scalingX, scalingY); - final String value = _getValue(graphics); + final String? value = _getValue(graphics); if (_list.containsKey(graphics)) { - final _PdfTemplateValuePair pair = _list[graphics]; + final _PdfTemplateValuePair pair = _list[graphics]!; if (pair.value != value) { final Size size = _obtainSize(); pair.template.reset(size.width, size.height); - pair.template.graphics.drawString(value, _obtainFont(), + pair.template.graphics!.drawString(value!, font, pen: pen, - brush: _obtainBrush(), + brush: brush, bounds: Rect.fromLTWH(0, 0, size.width, size.height), format: stringFormat); } } else { final PdfTemplate template = PdfTemplate(_obtainSize().width, _obtainSize().height); - _list[graphics] = _PdfTemplateValuePair(template, value); - template.graphics.drawString(value, _obtainFont(), + _list[graphics] = _PdfTemplateValuePair(template, value!); + template.graphics!.drawString(value, font, pen: pen, - brush: _obtainBrush(), + brush: brush, bounds: Rect.fromLTWH(0, 0, _obtainSize().width, _obtainSize().height), format: stringFormat); final Offset drawLocation = - Offset(_location.x + bounds.left, _location.y + bounds.top); + Offset(_location!.x + bounds.left, _location.y + bounds.top); graphics.drawPdfTemplate( template, drawLocation, diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_number_convertor.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_number_convertor.dart index c3d761ee3..cdb2f9e53 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_number_convertor.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_number_convertor.dart @@ -10,53 +10,46 @@ class _PdfNumberConvertor { switch (numberStyle) { case PdfNumberStyle.none: return ''; - case PdfNumberStyle.numeric: return intArabic.toString(); - case PdfNumberStyle.lowerLatin: return _arabicToLetter(intArabic).toLowerCase(); - case PdfNumberStyle.lowerRoman: return _arabicToRoman(intArabic).toLowerCase(); - case PdfNumberStyle.upperLatin: return _arabicToLetter(intArabic); - case PdfNumberStyle.upperRoman: return _arabicToRoman(intArabic); } - - return ''; } static String _arabicToRoman(int intArabic) { final StringBuffer retval = StringBuffer(); List result = _generateNumber(intArabic, 1000, 'M'); retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1), 900, 'CM'); + result = _generateNumber(result.elementAt(1) as int, 900, 'CM'); retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1), 500, 'D'); + result = _generateNumber(result.elementAt(1) as int, 500, 'D'); retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1), 400, 'CD'); + result = _generateNumber(result.elementAt(1) as int, 400, 'CD'); retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1), 100, 'C'); + result = _generateNumber(result.elementAt(1) as int, 100, 'C'); retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1), 90, 'XC'); + result = _generateNumber(result.elementAt(1) as int, 90, 'XC'); retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1), 50, 'L'); + result = _generateNumber(result.elementAt(1) as int, 50, 'L'); retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1), 40, 'XL'); + result = _generateNumber(result.elementAt(1) as int, 40, 'XL'); retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1), 10, 'X'); + result = _generateNumber(result.elementAt(1) as int, 10, 'X'); retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1), 9, 'IX'); + result = _generateNumber(result.elementAt(1) as int, 9, 'IX'); retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1), 5, 'V'); + result = _generateNumber(result.elementAt(1) as int, 5, 'V'); retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1), 4, 'IV'); + result = _generateNumber(result.elementAt(1) as int, 4, 'IV'); retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1), 1, 'I'); + result = _generateNumber(result.elementAt(1) as int, 1, 'I'); retval.write(result.elementAt(0)); return retval.toString(); } @@ -108,9 +101,6 @@ class _PdfNumberConvertor { } static void _appendChar(StringBuffer result, int number) { - if (result == null) { - throw ArgumentError.notNull('result'); - } if (number <= 0 || number > 26) { throw ArgumentError.value('Value can not be less 0 and greater 26'); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_count_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_count_field.dart index d8fd6f88d..018f7d6fb 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_count_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_count_field.dart @@ -3,12 +3,86 @@ part of pdf; /// Represents total PDF document page count automatic field. /// Represents an automatic field to display total number of pages /// in section(if set isSectionPageCount as true). +/// ```dart +/// Create a new pdf document +/// PdfDocument document = PdfDocument(); +/// //Add the pages to the document +/// for (int i = 1; i <= 5; i++) { +/// document.pages.add().graphics.drawString( +/// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), +/// bounds: Rect.fromLTWH(250, 0, 615, 100)); +/// } +/// //Create the footer with specific bounds +/// PdfPageTemplateElement footer = PdfPageTemplateElement( +/// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 50)); +/// //Create the page count field +/// PdfPageCountField count = PdfPageCountField( +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), +/// brush: PdfSolidBrush(PdfColor(0, 0, 0))); +/// //set the number style for page count +/// count.numberStyle = PdfNumberStyle.upperRoman; +/// //Create the composite field with page number page count +/// PdfCompositeField compositeField = PdfCompositeField( +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), +/// brush: PdfSolidBrush(PdfColor(0, 0, 0)), +/// text: 'Page Count: {0}', +/// fields: [count]); +/// compositeField.bounds = footer.bounds; +/// //Add the composite field in footer +/// compositeField.draw( +/// footer.graphics, +/// Offset( +/// 290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); +/// //Add the footer at the bottom of the document +/// document.template.bottom = footer; +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfPageCountField extends _PdfSingleValueField { // constructor /// Initializes a new instance of the [PdfPageCountField] class /// and may also with the classes are [PdfFont], [PdfBrush] and [Rect]. + /// ```dart + /// Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the footer with specific bounds + /// PdfPageTemplateElement footer = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 50)); + /// //Create the page count field + /// PdfPageCountField count = PdfPageCountField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //set the number style for page count + /// count.numberStyle = PdfNumberStyle.upperRoman; + /// //Create the composite field with page number page count + /// PdfCompositeField compositeField = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// text: 'Page Count: {0}', + /// fields: [count]); + /// compositeField.bounds = footer.bounds; + /// //Add the composite field in footer + /// compositeField.draw( + /// footer.graphics, + /// Offset( + /// 290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); + /// //Add the footer at the bottom of the document + /// document.template.bottom = footer; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfPageCountField( - {PdfFont font, PdfBrush brush, Rect bounds, bool isSectionPageCount}) + {PdfFont? font, PdfBrush? brush, Rect? bounds, bool? isSectionPageCount}) : super(font, brush, bounds) { _isSectionPageCount = (isSectionPageCount == null) ? false : isSectionPageCount; @@ -16,6 +90,43 @@ class PdfPageCountField extends _PdfSingleValueField { // fields /// Gets or sets the specific number style. + /// ```dart + /// Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the footer with specific bounds + /// PdfPageTemplateElement footer = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 50)); + /// //Create the page count field + /// PdfPageCountField count = PdfPageCountField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //Gets or Sets the number style for page count + /// count.numberStyle = PdfNumberStyle.upperRoman; + /// //Create the composite field with page number page count + /// PdfCompositeField compositeField = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// text: 'Page Count: {0}', + /// fields: [count]); + /// compositeField.bounds = footer.bounds; + /// //Add the composite field in footer + /// compositeField.draw( + /// footer.graphics, + /// Offset( + /// 290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); + /// //Add the footer at the bottom of the document + /// document.template.bottom = footer; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfNumberStyle numberStyle = PdfNumberStyle.numeric; /// Represents an automatic field to display total number of pages in section. @@ -23,16 +134,16 @@ class PdfPageCountField extends _PdfSingleValueField { // implementation @override - String _getValue(PdfGraphics graphics) { - String result; + String? _getValue(PdfGraphics graphics) { + String? result; if (graphics._page is PdfPage) { final PdfPage page = _PdfDynamicField._getPageFromGraphics(graphics); if (_isSectionPageCount) { - final PdfSection section = page._section; + final PdfSection section = page._section!; final int count = section._count; return _PdfNumberConvertor._convert(count, numberStyle); } else { - final PdfDocument document = page._section._parent._document; + final PdfDocument document = page._section!._parent!._document!; final int number = document.pages.count; return _PdfNumberConvertor._convert(number, numberStyle); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_number_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_number_field.dart index b9b889bed..8d5304682 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_number_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_number_field.dart @@ -2,12 +2,86 @@ part of pdf; /// Represents PDF document page number field. /// Represents an automatic field to display page number within a section. +/// ```dart +/// //Create a new pdf document +/// PdfDocument document = PdfDocument(); +/// //Add the pages to the document +/// for (int i = 1; i <= 5; i++) { +/// document.pages.add().graphics.drawString( +/// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), +/// bounds: Rect.fromLTWH(250, 0, 615, 100)); +/// } +/// //Create the footer with specific bounds +/// PdfPageTemplateElement footer = PdfPageTemplateElement( +/// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 50)); +/// //Create the page number field +/// PdfPageNumberField pageNumber = PdfPageNumberField( +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), +/// brush: PdfSolidBrush(PdfColor(0, 0, 0))); +/// //Sets the number style for page number +/// pageNumber.numberStyle = PdfNumberStyle.upperRoman; +/// //Create the composite field with page number page count +/// PdfCompositeField compositeField = PdfCompositeField( +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), +/// brush: PdfSolidBrush(PdfColor(0, 0, 0)), +/// text: 'Page No: {0}', +/// fields: [pageNumber]); +/// compositeField.bounds = footer.bounds; +/// //Add the composite field in footer +/// compositeField.draw( +/// footer.graphics, +/// Offset( +/// 290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); +/// //Add the footer at the bottom of the document +/// document.template.bottom = footer; +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfPageNumberField extends _PdfMultipleValueField { // constructor /// Initializes a new instance of the [PdfPageNumberField] class /// and may also with the classes are [PdfFont], [PdfBrush] and [Rect]. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the footer with specific bounds + /// PdfPageTemplateElement footer = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 50)); + /// //Create the page number field + /// PdfPageNumberField pageNumber = PdfPageNumberField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //Sets the number style for page number + /// pageNumber.numberStyle = PdfNumberStyle.upperRoman; + /// //Create the composite field with page number page count + /// PdfCompositeField compositeField = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// text: 'Page No: {0}', + /// fields: [pageNumber]); + /// compositeField.bounds = footer.bounds; + /// //Add the composite field in footer + /// compositeField.draw( + /// footer.graphics, + /// Offset( + /// 290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); + /// //Add the footer at the bottom of the document + /// document.template.bottom = footer; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfPageNumberField( - {PdfFont font, PdfBrush brush, Rect bounds, bool isSectionPageNumber}) + {PdfFont? font, PdfBrush? brush, Rect? bounds, bool? isSectionPageNumber}) : super(font: font, brush: brush, bounds: bounds) { _isSectionPageNumber = (isSectionPageNumber == null) ? false : isSectionPageNumber; @@ -15,6 +89,43 @@ class PdfPageNumberField extends _PdfMultipleValueField { // fields /// Gets or sets the specific number style. + /// ```dart + /// //Create a new pdf document + /// PdfDocument document = PdfDocument(); + /// //Add the pages to the document + /// for (int i = 1; i <= 5; i++) { + /// document.pages.add().graphics.drawString( + /// 'page$i', PdfStandardFont(PdfFontFamily.timesRoman, 11), + /// bounds: Rect.fromLTWH(250, 0, 615, 100)); + /// } + /// //Create the footer with specific bounds + /// PdfPageTemplateElement footer = PdfPageTemplateElement( + /// Rect.fromLTWH(0, 0, document.pages[0].getClientSize().width, 50)); + /// //Create the page number field + /// PdfPageNumberField pageNumber = PdfPageNumberField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0))); + /// //Sets the number style for page number + /// pageNumber.numberStyle = PdfNumberStyle.upperRoman; + /// //Create the composite field with page number page count + /// PdfCompositeField compositeField = PdfCompositeField( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 19), + /// brush: PdfSolidBrush(PdfColor(0, 0, 0)), + /// text: 'Page No: {0}', + /// fields: [pageNumber]); + /// compositeField.bounds = footer.bounds; + /// //Add the composite field in footer + /// compositeField.draw( + /// footer.graphics, + /// Offset( + /// 290, 50 - PdfStandardFont(PdfFontFamily.timesRoman, 19).height)); + /// //Add the footer at the bottom of the document + /// document.template.bottom = footer; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfNumberStyle numberStyle = PdfNumberStyle.numeric; /// Represents an automatic field to display page number within a section. @@ -22,8 +133,8 @@ class PdfPageNumberField extends _PdfMultipleValueField { //implementation @override - String _getValue(PdfGraphics graphics) { - String result; + String? _getValue(PdfGraphics graphics) { + String? result; if (graphics._page is PdfPage) { final PdfPage page = _PdfDynamicField._getPageFromGraphics(graphics); result = _internalGetValue(page); @@ -31,13 +142,13 @@ class PdfPageNumberField extends _PdfMultipleValueField { return result; } - String _internalGetValue(PdfPage page) { + String _internalGetValue(PdfPage? page) { if (_isSectionPageNumber) { - final PdfSection section = page._section; + final PdfSection section = page!._section!; final int index = section._indexOf(page) + 1; return _PdfNumberConvertor._convert(index, numberStyle); } else { - final PdfDocument document = page._section._parent._document; + final PdfDocument document = page!._section!._parent!._document!; final int pageIndex = document.pages.indexOf(page) + 1; return _PdfNumberConvertor._convert(pageIndex, numberStyle); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_single_value_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_single_value_field.dart index daa9e220f..a84e8cfbc 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_single_value_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_single_value_field.dart @@ -2,40 +2,40 @@ part of pdf; abstract class _PdfSingleValueField extends _PdfDynamicField { // constructor - _PdfSingleValueField([PdfFont font, PdfBrush brush, Rect bounds]) + _PdfSingleValueField([PdfFont? font, PdfBrush? brush, Rect? bounds]) : super(font: font, bounds: bounds, brush: brush); // field - final Map _list = - {}; + final Map _list = + {}; final List _painterGraphics = []; // implementation @override - void _performDraw(PdfGraphics graphics, _Point _location, double scalingX, + void _performDraw(PdfGraphics graphics, _Point? _location, double scalingX, double scalingY) { if (graphics._page is PdfPage) { super._performDraw(graphics, _location, scalingX, scalingY); final PdfPage page = _PdfDynamicField._getPageFromGraphics(graphics); - final PdfDocument document = page._document; - final String value = _getValue(graphics); + final PdfDocument? document = page._document; + final String? value = _getValue(graphics); if (_list.containsKey(document)) { - final _PdfTemplateValuePair pair = _list[document]; + final _PdfTemplateValuePair pair = _list[document]!; if (pair.value != value) { final Size size = _obtainSize(); pair.template.reset(size.width, size.height); - pair.template.graphics.drawString(value, _obtainFont(), + pair.template.graphics!.drawString(value!, font, pen: pen, - brush: _obtainBrush(), + brush: brush, bounds: bounds.topLeft & size, format: stringFormat); } if (!_painterGraphics.contains(graphics)) { final Offset drawLocation = - Offset(_location.x + bounds.left, _location.y + bounds.top); + Offset(_location!.x + bounds.left, _location.y + bounds.top); graphics.drawPdfTemplate( pair.template, drawLocation, @@ -46,16 +46,16 @@ abstract class _PdfSingleValueField extends _PdfDynamicField { } else { final PdfTemplate template = PdfTemplate(_obtainSize().width, _obtainSize().height); - _list[document] = _PdfTemplateValuePair(template, value); - final _PdfTemplateValuePair pair = _list[document]; - template.graphics.drawString(value, _obtainFont(), + _list[document] = _PdfTemplateValuePair(template, value!); + final _PdfTemplateValuePair pair = _list[document]!; + template.graphics!.drawString(value, font, pen: pen, - brush: _obtainBrush(), + brush: brush, bounds: Rect.fromLTWH( bounds.left, bounds.top, bounds.width, bounds.height), format: stringFormat); final Offset drawLocation = - Offset(_location.x + bounds.left, _location.y + bounds.top); + Offset(_location!.x + bounds.left, _location.y + bounds.top); graphics.drawPdfTemplate( pair.template, drawLocation, diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_static_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_static_field.dart index 172811504..e1ab62324 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_static_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_static_field.dart @@ -4,44 +4,44 @@ abstract class _PdfStaticField extends PdfAutomaticField { // constructor /// Represents automatic field which value can be evaluated /// in the moment of creation. - _PdfStaticField({PdfFont font, PdfBrush brush, Rect bounds}) + _PdfStaticField({PdfFont? font, PdfBrush? brush, Rect? bounds}) : super._(font, brush: brush, bounds: bounds); // fields - PdfTemplate _template; + PdfTemplate? _template; final List _graphicsList = []; // implementation @override - void _performDraw(PdfGraphics graphics, _Point _location, double scalingX, + void _performDraw(PdfGraphics graphics, _Point? _location, double scalingX, double scalingY) { super._performDraw(graphics, _location, scalingX, scalingY); - final String value = _getValue(graphics); + final String? value = _getValue(graphics); final Offset drawLocation = - Offset(_location.x + bounds.left, _location.y + bounds.top); + Offset(_location!.x + bounds.left, _location.y + bounds.top); if (_template == null) { _template = PdfTemplate(_obtainSize().width, _obtainSize().height); - _template.graphics.drawString(value, _obtainFont(), + _template!.graphics!.drawString(value!, font, pen: pen, - brush: _obtainBrush(), + brush: brush, bounds: Rect.fromLTWH(0, 0, _obtainSize().width, _obtainSize().height), format: stringFormat); graphics.drawPdfTemplate( - _template, + _template!, drawLocation, - Size(_template.size.width * scalingX, - _template.size.height * scalingY)); + Size(_template!.size.width * scalingX, + _template!.size.height * scalingY)); _graphicsList.add(graphics); } else { if (!_graphicsList.contains(graphics)) { graphics.drawPdfTemplate( - _template, + _template!, drawLocation, - Size(_template.size.width * scalingX, - _template.size.height * scalingY)); + Size(_template!.size.width * scalingX, + _template!.size.height * scalingY)); _graphicsList.add(graphics); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_template_value_pair.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_template_value_pair.dart index 2c8e9a96f..81d59dd15 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_template_value_pair.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_template_value_pair.dart @@ -2,26 +2,12 @@ part of pdf; class _PdfTemplateValuePair { // constructor - _PdfTemplateValuePair([PdfTemplate template, String value]) { + _PdfTemplateValuePair(PdfTemplate template, String value) { this.template = template; this.value = value; } // field - PdfTemplate _template; - String _value; - - // properties - - PdfTemplate get template => _template; - set template(PdfTemplate value) { - ArgumentError.checkNotNull(value, 'template'); - _template = value; - } - - String get value => _value; - set value(String value) { - ArgumentError.checkNotNull(value, 'value'); - _value = value; - } + late PdfTemplate template; + late String value; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/enums.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/enums.dart index b03623c98..5b531cd8d 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/enums.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/enums.dart @@ -1,6 +1,19 @@ part of pdf; /// Allows to choose outline text style. +/// +/// ```dart +/// //Create a new document. +/// PdfDocument document = PdfDocument(); +/// //Create document bookmarks. +/// document.bookmarks.add('Page 1') +/// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)) +/// ..textStyle = [PdfTextStyle.bold]; +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` enum PdfTextStyle { /// Regular text style. regular, diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/pdf_outline.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/pdf_outline.dart index 5b21fc176..f9d878672 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/pdf_outline.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/pdf_outline.dart @@ -2,52 +2,104 @@ part of pdf; /// Each instance of this class represents an bookmark node /// in the bookmark tree. +/// +/// ```dart +/// //Create a new document. +/// PdfDocument document = PdfDocument(); +/// //Create document bookmarks. +/// PdfBookmark bookmark = document.bookmarks.add('Page 1') +/// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)) +/// ..textStyle = [PdfTextStyle.bold] +/// ..color = PdfColor(255, 0, 0); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfBookmark extends PdfBookmarkBase { + //Constructor /// Initializes a new instance of the [PdfBookmark] class. PdfBookmark._internal(String title, PdfBookmarkBase parent, - PdfBookmark previous, PdfBookmark next) + PdfBookmark? previous, PdfBookmark? next, + {bool isExpanded = false, + PdfColor? color, + PdfDestination? destination, + PdfNamedDestination? namedDestination, + PdfAction? action, + List? textStyle}) : super._internal() { - ArgumentError.checkNotNull(parent, 'parent'); - ArgumentError.checkNotNull(title, 'title'); _parent = parent; - _dictionary.setProperty( - _DictionaryProperties.parent, _PdfReferenceHolder(parent)); + _dictionary! + .setProperty(_DictionaryProperties.parent, _PdfReferenceHolder(parent)); _previous = previous; _next = next; this.title = title; + this.isExpanded = isExpanded; + if (color != null) { + this.color = color; + } + if (destination != null) { + this.destination = destination; + } + if (namedDestination != null) { + this.namedDestination = namedDestination; + } + if (action != null) { + this.action = action; + } + if (textStyle != null) { + this.textStyle = textStyle; + } } - PdfBookmark._load(_PdfDictionary dictionary, _PdfCrossTable crossTable) + PdfBookmark._load(_PdfDictionary? dictionary, _PdfCrossTable crossTable) : super._load(dictionary, crossTable); + //Fields /// Internal variable to store destination. - PdfDestination _destination; + PdfDestination? _destination; /// Internal variable to store named destination. - PdfNamedDestination _namedDestination; + PdfNamedDestination? _namedDestination; /// Internal variable to store text Style. List _textStyle = [PdfTextStyle.regular]; /// Internal variable to store previous. - PdfBookmark _previousBookmark; + PdfBookmark? _previousBookmark; /// Internal variable to store next. - PdfBookmark _nextBookmark; + PdfBookmark? _nextBookmark; /// Internal variable to store parent. - PdfBookmarkBase _parent; + PdfBookmarkBase? _parent; /// Internal variable to store color. - PdfColor _color; + PdfColor _color = PdfColor(0, 0, 0); /// Internal variable to store action. - PdfAction _action; - - /// Gets the outline destination page. - PdfDestination get destination { + PdfAction? _action; + + //Properties + /// Gets or sets the outline destination page. + /// + /// ```dart + /// //Create a new document. + /// PdfDocument document = PdfDocument(); + /// //Create document bookmarks. + /// PdfBookmark bookmark = document.bookmarks.add('Page 1') + /// //Set the destination page. + /// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)) + /// ..textStyle = [PdfTextStyle.bold] + /// ..color = PdfColor(255, 0, 0); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfDestination? get destination { if (_isLoadedBookmark) { - PdfDestination destination; + PdfDestination? destination; if (_obtainNamedDestination() == null) { destination = _obtainDestination(); } @@ -57,15 +109,34 @@ class PdfBookmark extends PdfBookmarkBase { } } - /// Sets the outline destination page. - set destination(PdfDestination value) { - ArgumentError.checkNotNull(value, 'destination'); - _destination = value; - _dictionary.setProperty(_DictionaryProperties.dest, value); + set destination(PdfDestination? value) { + if (value != null) { + _destination = value; + _dictionary!.setProperty(_DictionaryProperties.dest, value); + } } - /// Gets the named destination in outline. - PdfNamedDestination get namedDestination { + /// Gets or sets the named destination in outline. + /// + /// ```dart + /// //Create a new document. + /// PdfDocument document = PdfDocument(); + /// //Create a named destination. + /// PdfNamedDestination namedDestination = PdfNamedDestination('Page 1') + /// ..destination = PdfDestination(document.pages.add(), Offset(100, 300)); + /// //Add the named destination + /// document.namedDestinationCollection.add(namedDestination); + /// //Create document bookmarks. + /// document.bookmarks.add('Page 1') + /// //Set the named destination. + /// ..namedDestination = namedDestination + /// ..color = PdfColor(255, 0, 0); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfNamedDestination? get namedDestination { if (_isLoadedBookmark) { _namedDestination ??= _obtainNamedDestination(); return _namedDestination; @@ -74,38 +145,70 @@ class PdfBookmark extends PdfBookmarkBase { } } - /// Sets the named destination in outline. - set namedDestination(PdfNamedDestination value) { - if (_namedDestination != value) { + set namedDestination(PdfNamedDestination? value) { + if (value != null && _namedDestination != value) { _namedDestination = value; final _PdfDictionary dictionary = _PdfDictionary(); dictionary.setProperty( - _DictionaryProperties.d, _PdfString(_namedDestination.title)); + _DictionaryProperties.d, _PdfString(_namedDestination!.title)); dictionary.setProperty( _DictionaryProperties.s, _PdfName(_DictionaryProperties.goTo)); - _dictionary.setProperty( + _dictionary!.setProperty( _DictionaryProperties.a, _PdfReferenceHolder(dictionary)); } } - /// Gets the outline title. + /// Gets or sets the outline title. + /// + /// ```dart + /// //Create a new document. + /// PdfDocument document = PdfDocument(); + /// //Create document bookmarks. + /// document.bookmarks.add('Page 1') + /// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)) + /// ..textStyle = [PdfTextStyle.bold] + /// ..color = PdfColor(255, 0, 0) + /// //Set the bookmark title. + /// ..title = 'Bookmark'; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` String get title { if (_isLoadedBookmark) { return _obtainTitle(); } else { - final _PdfString title = - _dictionary[_DictionaryProperties.title] as _PdfString; - return title != null ? title.value : null; + final _PdfString? title = + _dictionary![_DictionaryProperties.title] as _PdfString?; + if (title != null && title.value != null) { + return title.value!; + } else { + return ''; + } } } - /// Sets the outline title. set title(String value) { - ArgumentError.checkNotNull(value, 'title'); - _dictionary[_DictionaryProperties.title] = _PdfString(value); + _dictionary![_DictionaryProperties.title] = _PdfString(value); } - /// Gets the color of bookmark title. + /// Gets or sets the color of bookmark title. + /// + /// ```dart + /// //Create a new document. + /// PdfDocument document = PdfDocument(); + /// //Create document bookmarks. + /// document.bookmarks.add('Page 1') + /// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)) + /// ..textStyle = [PdfTextStyle.bold] + /// ..color = PdfColor(255, 0, 0) + /// ..title = 'Bookmark'; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfColor get color { if (_isLoadedBookmark) { return _obtainColor(); @@ -114,7 +217,6 @@ class PdfBookmark extends PdfBookmarkBase { } } - /// Sets the color of bookmark title. set color(PdfColor value) { if (_isLoadedBookmark) { _assignColor(value); @@ -126,7 +228,22 @@ class PdfBookmark extends PdfBookmarkBase { } } - /// Gets the style of the outline title. + /// Gets or sets the style of the outline title. + /// + /// ```dart + /// //Create a new document. + /// PdfDocument document = PdfDocument(); + /// //Create document bookmarks. + /// document.bookmarks.add('Page 1') + /// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)) + /// ..textStyle = [PdfTextStyle.bold] + /// ..color = PdfColor(255, 0, 0) + /// ..title = 'Bookmark'; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` List get textStyle { if (_isLoadedBookmark) { return [_obtainTextStyle()]; @@ -135,12 +252,10 @@ class PdfBookmark extends PdfBookmarkBase { } } - /// Sets the style of the outline title. set textStyle(List value) { if (_isLoadedBookmark) { _assignTextStyle(value); } else { - ArgumentError.checkNotNull(value, 'textStyle'); if (_textStyle != value) { _textStyle = value; _updateTextStyle(); @@ -148,19 +263,55 @@ class PdfBookmark extends PdfBookmarkBase { } } - /// Gets the action for the outline. - PdfAction get action => _action; - - // Sets the action for the outline. - set action(PdfAction value) { - if (_action != value) { + /// Gets or sets the action for the outline. + /// + /// ```dart + /// //Create a new document. + /// PdfDocument document = PdfDocument(); + /// //Create document bookmarks. + /// document.bookmarks.add('Page 1') + /// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)) + /// ..textStyle = [PdfTextStyle.bold] + /// ..color = PdfColor(255, 0, 0) + /// //Set the bookmark action. + /// ..action = PdfUriAction('http://www.google.com'); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfAction? get action => _action; + set action(PdfAction? value) { + if (value != null && _action != value) { _action = value; - _dictionary.setProperty( - _DictionaryProperties.a, _PdfReferenceHolder(_action._dictionary)); + _dictionary!.setProperty( + _DictionaryProperties.a, _PdfReferenceHolder(_action!._dictionary)); } } - PdfBookmark get _previous { + /// Gets or sets whether to expand the node or not. + /// + /// ```dart + /// //Create a new document. + /// PdfDocument document = PdfDocument(); + /// //Create document bookmarks. + /// PdfBookmark bookmark = document.bookmarks.add('Page 1') + /// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)) + /// ..textStyle = [PdfTextStyle.bold] + /// ..color = PdfColor(255, 0, 0); + /// //Get if is expanded. + /// bool expand = bookmark.isExpanded; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + bool get isExpanded => super._isExpanded; + set isExpanded(bool value) { + super._isExpanded = value; + } + + PdfBookmark? get _previous { if (_isLoadedBookmark) { return _obtainPrevious(); } else { @@ -168,16 +319,16 @@ class PdfBookmark extends PdfBookmarkBase { } } - set _previous(PdfBookmark value) { + set _previous(PdfBookmark? value) { if (_previousBookmark != value) { _previousBookmark = value; - _dictionary.setProperty( - _DictionaryProperties.prev, _PdfReferenceHolder(value)); + _dictionary! + .setProperty(_DictionaryProperties.prev, _PdfReferenceHolder(value)); } } /// Gets the next outline object. - PdfBookmark get _next { + PdfBookmark? get _next { if (_isLoadedBookmark) { return _obtainNext(); } else { @@ -186,22 +337,15 @@ class PdfBookmark extends PdfBookmarkBase { } /// Sets the next outline object. - set _next(PdfBookmark value) { + set _next(PdfBookmark? value) { if (_nextBookmark != value) { _nextBookmark = value; - _dictionary.setProperty( - _DictionaryProperties.next, _PdfReferenceHolder(value)); + _dictionary! + .setProperty(_DictionaryProperties.next, _PdfReferenceHolder(value)); } } - /// Gets whether to expand the node or not. - bool get isExpanded => super._isExpanded; - - /// Sets whether to expand the node or not. - set isExpanded(bool value) { - super._isExpanded = value; - } - + //Implementations @override List get _list { final List list = super._list; @@ -216,24 +360,26 @@ class PdfBookmark extends PdfBookmarkBase { } void _updateColor() { - final _PdfArray array = _dictionary[_DictionaryProperties.c] as _PdfArray; + final _PdfArray? array = + _dictionary![_DictionaryProperties.c] as _PdfArray?; if (array != null && _color.isEmpty) { - _dictionary.remove(_DictionaryProperties.c); + _dictionary!.remove(_DictionaryProperties.c); } else { - _dictionary[_DictionaryProperties.c] = _color._toArray(PdfColorSpace.rgb); + _dictionary![_DictionaryProperties.c] = + _color._toArray(PdfColorSpace.rgb); } } /// Updates the outline text style. void _updateTextStyle() { if (_textStyle.length == 1 && _textStyle[0] == PdfTextStyle.regular) { - _dictionary.remove(_DictionaryProperties.f); + _dictionary!.remove(_DictionaryProperties.f); } else { int value = 0; for (int i = 0; i < _textStyle.length; i++) { value |= _textStyle[i].index; } - _dictionary[_DictionaryProperties.f] = _PdfNumber(value); + _dictionary![_DictionaryProperties.f] = _PdfNumber(value); } } @@ -243,12 +389,12 @@ class PdfBookmark extends PdfBookmarkBase { String _obtainTitle() { String title = ''; - if (_dictionary.containsKey(_DictionaryProperties.title)) { - final _PdfString str = - _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.title]) - as _PdfString; - if (str != null) { - title = str.value; + if (_dictionary!.containsKey(_DictionaryProperties.title)) { + final _PdfString? str = + _PdfCrossTable._dereference(_dictionary![_DictionaryProperties.title]) + as _PdfString?; + if (str != null && str.value != null) { + title = str.value!; } } return title; @@ -256,28 +402,28 @@ class PdfBookmark extends PdfBookmarkBase { PdfColor _obtainColor() { PdfColor color = PdfColor(0, 0, 0); - if (_dictionary.containsKey(_DictionaryProperties.c)) { - final _PdfArray colours = - _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.c]) - as _PdfArray; + if (_dictionary!.containsKey(_DictionaryProperties.c)) { + final _PdfArray? colours = + _PdfCrossTable._dereference(_dictionary![_DictionaryProperties.c]) + as _PdfArray?; if (colours != null && colours.count > 2) { - double red = 0; + double? red = 0; double green = 0; double blue = 0; - _PdfNumber colorValue = - _PdfCrossTable._dereference(colours[0]) as _PdfNumber; + _PdfNumber? colorValue = + _PdfCrossTable._dereference(colours[0]) as _PdfNumber?; if (colorValue != null) { - red = colorValue.value; + red = colorValue.value as double?; } - colorValue = _PdfCrossTable._dereference(colours[1]) as _PdfNumber; + colorValue = _PdfCrossTable._dereference(colours[1]) as _PdfNumber?; if (colorValue != null) { - green = colorValue.value.toDouble(); + green = colorValue.value!.toDouble(); } - colorValue = _PdfCrossTable._dereference(colours[2]) as _PdfNumber; + colorValue = _PdfCrossTable._dereference(colours[2]) as _PdfNumber?; if (colorValue != null) { - blue = colorValue.value.toDouble(); + blue = colorValue.value!.toDouble(); } - color = PdfColor(red.toInt(), green.toInt(), blue.toInt()); + color = PdfColor(red!.toInt(), green.toInt(), blue.toInt()); } } return color; @@ -285,47 +431,47 @@ class PdfBookmark extends PdfBookmarkBase { PdfTextStyle _obtainTextStyle() { PdfTextStyle style = PdfTextStyle.regular; - if (_dictionary.containsKey(_DictionaryProperties.f)) { - final _PdfNumber flag = - _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.f]) - as _PdfNumber; + if (_dictionary!.containsKey(_DictionaryProperties.f)) { + final _PdfNumber? flag = + _PdfCrossTable._dereference(_dictionary![_DictionaryProperties.f]) + as _PdfNumber?; int flagValue = 0; if (flag != null) { - flagValue = flag.value.toInt() - 1; + flagValue = flag.value!.toInt() - 1; } style = PdfTextStyle.values.elementAt(flagValue); } return style; } - PdfBookmark _obtainNext() { - PdfBookmark nextBookmark; - int index = _parent._list.indexOf(this); + PdfBookmark? _obtainNext() { + PdfBookmark? nextBookmark; + int index = _parent!._list.indexOf(this); ++index; - if (index < _parent._list.length) { - nextBookmark = _parent._list[index] as PdfBookmark; + if (index < _parent!._list.length) { + nextBookmark = _parent!._list[index] as PdfBookmark?; } else { - if (_dictionary.containsKey(_DictionaryProperties.next)) { - final _PdfDictionary next = - _crossTable._getObject(_dictionary[_DictionaryProperties.next]) - as _PdfDictionary; + if (_dictionary!.containsKey(_DictionaryProperties.next)) { + final _PdfDictionary? next = + _crossTable._getObject(_dictionary![_DictionaryProperties.next]) + as _PdfDictionary?; nextBookmark = PdfBookmark._load(next, _crossTable); } } return nextBookmark; } - PdfBookmark _obtainPrevious() { - PdfBookmark prevBookmark; + PdfBookmark? _obtainPrevious() { + PdfBookmark? prevBookmark; int index = _bookmarkList.indexOf(this); --index; if (index >= 0) { - prevBookmark = _bookmarkList[index] as PdfBookmark; + prevBookmark = _bookmarkList[index] as PdfBookmark?; } else { - if (_dictionary.containsKey(_DictionaryProperties.prev)) { - final _PdfDictionary prev = - _crossTable._getObject(_dictionary[_DictionaryProperties.prev]) - as _PdfDictionary; + if (_dictionary!.containsKey(_DictionaryProperties.prev)) { + final _PdfDictionary? prev = + _crossTable._getObject(_dictionary![_DictionaryProperties.prev]) + as _PdfDictionary?; prevBookmark = PdfBookmark._load(prev, _crossTable); } } @@ -339,43 +485,44 @@ class PdfBookmark extends PdfBookmarkBase { color._blue.toDouble() ]; final _PdfArray colors = _PdfArray(rgb); - _dictionary.setProperty(_DictionaryProperties.c, colors); + _dictionary!.setProperty(_DictionaryProperties.c, colors); } void _assignTextStyle(List value) { for (final PdfTextStyle v in value) { int style = PdfTextStyle.values.indexOf(_obtainTextStyle()); style |= PdfTextStyle.values.indexOf(v); - _dictionary._setNumber(_DictionaryProperties.f, style); + _dictionary!._setNumber(_DictionaryProperties.f, style); } } - PdfNamedDestination _obtainNamedDestination() { - final PdfDocument loadedDocument = _crossTable._document; - PdfNamedDestinationCollection namedCollection; + PdfNamedDestination? _obtainNamedDestination() { + final PdfDocument? loadedDocument = _crossTable._document; + PdfNamedDestinationCollection? namedCollection; if (loadedDocument != null) { namedCollection = loadedDocument.namedDestinationCollection; } - PdfNamedDestination namedDestination; - _IPdfPrimitive destination; + PdfNamedDestination? namedDestination; + _IPdfPrimitive? destination; if (namedCollection != null) { - if (_dictionary.containsKey(_DictionaryProperties.a)) { - final _PdfDictionary action = - _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.a]) - as _PdfDictionary; + if (_dictionary!.containsKey(_DictionaryProperties.a)) { + final _PdfDictionary? action = + _PdfCrossTable._dereference(_dictionary![_DictionaryProperties.a]) + as _PdfDictionary?; if (action != null && action.containsKey(_DictionaryProperties.d)) { destination = _PdfCrossTable._dereference(action[_DictionaryProperties.d]); } - } else if (_dictionary.containsKey(_DictionaryProperties.dest)) { + } else if (_dictionary!.containsKey(_DictionaryProperties.dest)) { destination = - _crossTable._getObject(_dictionary[_DictionaryProperties.dest]); + _crossTable._getObject(_dictionary![_DictionaryProperties.dest]); } if (destination != null) { - final _PdfName name = (destination is _PdfName) ? destination : null; - final _PdfString str = (destination is _PdfString) ? destination : null; - String title; + final _PdfName? name = (destination is _PdfName) ? destination : null; + final _PdfString? str = + (destination is _PdfString) ? destination : null; + String? title; if (name != null) { title = name._name; } else if (str != null) { @@ -383,7 +530,7 @@ class PdfBookmark extends PdfBookmarkBase { } if (title != null) { for (int i = 0; i < namedCollection.count; i++) { - final PdfNamedDestination nameDest = namedCollection[i]; + final PdfNamedDestination? nameDest = namedCollection[i]; if (nameDest != null) { if (nameDest.title == title) { namedDestination = nameDest; @@ -397,14 +544,14 @@ class PdfBookmark extends PdfBookmarkBase { return namedDestination; } - PdfDestination _obtainDestination() { - if (_dictionary.containsKey(_DictionaryProperties.dest) && + PdfDestination? _obtainDestination() { + if (_dictionary!.containsKey(_DictionaryProperties.dest) && (_destination == null)) { - final _IPdfPrimitive obj = - _crossTable._getObject(_dictionary[_DictionaryProperties.dest]); - _PdfArray array = obj as _PdfArray; - final _PdfName name = (obj is _PdfName) ? obj : null; - final PdfDocument ldDoc = _crossTable._document; + final _IPdfPrimitive? obj = + _crossTable._getObject(_dictionary![_DictionaryProperties.dest]); + _PdfArray? array = obj as _PdfArray?; + final _PdfName? name = (obj is _PdfName) ? obj as _PdfName? : null; + final PdfDocument? ldDoc = _crossTable._document; if (ldDoc != null) { if (name != null) { @@ -413,54 +560,55 @@ class PdfBookmark extends PdfBookmarkBase { } if (array != null) { - final _PdfReferenceHolder holder = array[0] as _PdfReferenceHolder; - PdfPage page; + final _PdfReferenceHolder? holder = array[0] as _PdfReferenceHolder?; + PdfPage? page; if (holder != null) { - final _PdfDictionary dic = - _crossTable._getObject(holder) as _PdfDictionary; + final _PdfDictionary? dic = + _crossTable._getObject(holder) as _PdfDictionary?; if (ldDoc != null && dic != null) { page = ldDoc.pages._getPage(dic); } - _PdfName mode; + _PdfName? mode; if (array.count > 1) { mode = array[1] as _PdfName; } if (mode != null) { if (mode._name == _DictionaryProperties.xyz) { - _PdfNumber left; - _PdfNumber top; + _PdfNumber? left; + _PdfNumber? top; if (array.count > 2) { left = array[2] as _PdfNumber; } if (array.count > 3) { top = array[3] as _PdfNumber; } - _PdfNumber zoom; + _PdfNumber? zoom; if (array.count > 4) { zoom = array[4] as _PdfNumber; } if (page != null) { - double topValue = - (top == null) ? 0 : page.size.height - top.value; - final double leftValue = (left == null) ? 0 : left.value; + double? topValue = + (top == null) ? 0 : page.size.height - top.value!; + final double leftValue = + (left == null) ? 0 : left.value as double; if (page._isLoadedPage && page._rotation != PdfPageRotateAngle.rotateAngle0) { topValue = _checkRotation(page, top, left); } _destination = - PdfDestination(page, Offset(leftValue, topValue)); + PdfDestination(page, Offset(leftValue, topValue!)); if (zoom != null) { - _destination.zoom = zoom.value.toDouble(); + _destination!.zoom = zoom.value!.toDouble(); } } } else { if (mode._name == _DictionaryProperties.fitR) { - _PdfNumber left; - _PdfNumber bottom; - _PdfNumber right; - _PdfNumber top; + _PdfNumber? left; + _PdfNumber? bottom; + _PdfNumber? right; + _PdfNumber? top; if (array.count > 2) { left = array[2] as _PdfNumber; } @@ -482,42 +630,45 @@ class PdfBookmark extends PdfBookmarkBase { _destination = PdfDestination._( page, - _Rectangle(left.value.toDouble(), bottom.value.toDouble(), - right.value.toDouble(), top.value.toDouble())); - _destination.mode = PdfDestinationMode.fitR; + _Rectangle( + left.value!.toDouble(), + bottom.value!.toDouble(), + right.value!.toDouble(), + top.value!.toDouble())); + _destination!.mode = PdfDestinationMode.fitR; } } else if (mode._name == _DictionaryProperties.fitBH || mode._name == _DictionaryProperties.fitH) { - _PdfNumber top; + _PdfNumber? top; if (array.count >= 3) { top = array[2] as _PdfNumber; } if (page != null) { final double topValue = - (top == null) ? 0 : page.size.height - top.value; + (top == null) ? 0 : page.size.height - top.value!; _destination = PdfDestination(page, Offset(0, topValue)); - _destination.mode = PdfDestinationMode.fitH; + _destination!.mode = PdfDestinationMode.fitH; } } else { if (page != null && mode._name == _DictionaryProperties.fit) { _destination = PdfDestination(page); - _destination.mode = PdfDestinationMode.fitToPage; + _destination!.mode = PdfDestinationMode.fitToPage; } } } } else { if (page != null) { _destination = PdfDestination(page); - _destination.mode = PdfDestinationMode.fitToPage; + _destination!.mode = PdfDestinationMode.fitToPage; } } } } - } else if (_dictionary.containsKey(_DictionaryProperties.a) && + } else if (_dictionary!.containsKey(_DictionaryProperties.a) && (_destination == null)) { - _IPdfPrimitive obj = - _crossTable._getObject(_dictionary[_DictionaryProperties.a]); - _PdfDictionary destDic; + _IPdfPrimitive? obj = + _crossTable._getObject(_dictionary![_DictionaryProperties.a]); + _PdfDictionary? destDic; if (obj is _PdfDictionary) { destDic = obj; } @@ -525,77 +676,78 @@ class PdfBookmark extends PdfBookmarkBase { obj = destDic[_DictionaryProperties.d]; } if (obj is _PdfReferenceHolder) { - obj = (obj as _PdfReferenceHolder)._object; + obj = obj._object; } - _PdfArray array = (obj is _PdfArray) ? obj : null; - final _PdfName name = (obj is _PdfName) ? obj : null; - final PdfDocument ldDoc = _crossTable._document; + _PdfArray? array = (obj is _PdfArray) ? obj : null; + final _PdfName? name = (obj is _PdfName) ? obj : null; + final PdfDocument? ldDoc = _crossTable._document; if (ldDoc != null) { if (name != null) { array = ldDoc._getNamedDestination(name); } } if (array != null) { - final _PdfReferenceHolder holder = array[0] as _PdfReferenceHolder; - PdfPage page; + final _PdfReferenceHolder? holder = array[0] as _PdfReferenceHolder?; + PdfPage? page; if (holder != null) { - final _PdfDictionary dic = - _crossTable._getObject(holder) as _PdfDictionary; + final _PdfDictionary? dic = + _crossTable._getObject(holder) as _PdfDictionary?; if (dic != null && ldDoc != null) { page = ldDoc.pages._getPage(dic); } } - _PdfName mode; + _PdfName? mode; if (array.count > 1) { mode = array[1] as _PdfName; } if (mode != null) { if (mode._name == _DictionaryProperties.fitBH || mode._name == _DictionaryProperties.fitH) { - _PdfNumber top; + _PdfNumber? top; if (array.count >= 3) { top = array[2] as _PdfNumber; } if (page != null) { final double topValue = - (top == null) ? 0 : page.size.height - top.value; + (top == null) ? 0 : page.size.height - top.value!; _destination = PdfDestination(page, Offset(0, topValue)); - _destination.mode = PdfDestinationMode.fitH; + _destination!.mode = PdfDestinationMode.fitH; } } else if (mode._name == _DictionaryProperties.xyz) { - _PdfNumber left; - _PdfNumber top; + _PdfNumber? left; + _PdfNumber? top; if (array.count > 2) { left = array[2] as _PdfNumber; } if (array.count > 3) { top = array[3] as _PdfNumber; } - _PdfNumber zoom; + _PdfNumber? zoom; if (array.count > 4 && (array[4] is _PdfNumber)) { zoom = array[4] as _PdfNumber; } if (page != null) { final double topValue = - (top == null) ? 0 : page.size.height - top.value; - final double leftValue = (left == null) ? 0 : left.value; + (top == null) ? 0 : page.size.height - top.value!; + final double leftValue = + (left == null) ? 0 : left.value as double; _destination = PdfDestination(page, Offset(leftValue, topValue)); if (zoom != null) { - _destination.zoom = zoom.value.toDouble(); + _destination!.zoom = zoom.value!.toDouble(); } } } else { if (page != null && mode._name == _DictionaryProperties.fit) { _destination = PdfDestination(page); - _destination.mode = PdfDestinationMode.fitToPage; + _destination!.mode = PdfDestinationMode.fitToPage; } } } else { if (page != null) { _destination = PdfDestination(page); - _destination.mode = PdfDestinationMode.fitToPage; + _destination!.mode = PdfDestinationMode.fitToPage; } } } @@ -603,17 +755,17 @@ class PdfBookmark extends PdfBookmarkBase { return _destination; } - double _checkRotation(PdfPage page, _PdfNumber top, _PdfNumber left) { - double topValue = 0; + double? _checkRotation(PdfPage page, _PdfNumber? top, _PdfNumber? left) { + double? topValue = 0; left = (left == null) ? _PdfNumber(0) : left; if (page._rotation == PdfPageRotateAngle.rotateAngle90) { - topValue = (top == null) ? 0 : left.value; + topValue = (top == null) ? 0 : left.value as double; } else if (page._rotation == PdfPageRotateAngle.rotateAngle180) { - topValue = (top == null) ? 0 : top.value; + topValue = (top == null) ? 0 : top.value as double; } else if (page._rotation == PdfPageRotateAngle.rotateAngle270) { - topValue = (top == null) ? 0 : page.size.width - left.value; + topValue = (top == null) ? 0 : page.size.width - left.value!; } return topValue; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/pdf_outline_base.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/pdf_outline_base.dart index 85925cffc..683b30d41 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/pdf_outline_base.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/pdf_outline_base.dart @@ -2,11 +2,28 @@ part of pdf; /// This class plays two roles: it's a base class for all bookmarks /// and it's a root of a bookmarks tree. +/// +/// ```dart +/// //Load the PDF document. +/// PdfDocument document = PdfDocument(inputBytes: inputBytes); +/// //Get the bookmark from index. +/// PdfBookmark bookmarks = document.bookmarks[0] +/// ..destination = PdfDestination(document.pages[1]) +/// ..color = PdfColor(0, 255, 0) +/// ..textStyle = [PdfTextStyle.bold] +/// ..title = 'Changed title'; +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfBookmarkBase implements _IPdfWrapper { + //Constructor /// Initializes a new instance of the [PdfBookmarkBase] class. PdfBookmarkBase._internal() : super(); - PdfBookmarkBase._load(_PdfDictionary dictionary, _PdfCrossTable crossTable) { + PdfBookmarkBase._load( + _PdfDictionary? dictionary, _PdfCrossTable? crossTable) { _isLoadedBookmark = true; _dictionary = dictionary; if (crossTable != null) { @@ -14,30 +31,43 @@ class PdfBookmarkBase implements _IPdfWrapper { } } + //Fields /// Collection of the descend outlines. final List _bookmarkList = []; /// Internal variable to store loaded bookmark. - List _bookmark; + List? _bookmark; /// Internal variable to store dictinary. - _PdfDictionary _dictionary = _PdfDictionary(); + _PdfDictionary? _dictionary = _PdfDictionary(); /// Whether the bookmark tree is expanded or not bool _expanded = false; _PdfCrossTable _crossTable = _PdfCrossTable(); - List _booklist; + List? _booklist; bool _isLoadedBookmark = false; + //Properties /// Gets number of the elements in the collection. Read-Only. + /// + /// ```dart + /// //Load the PDF document. + /// PdfDocument document = PdfDocument(inputBytes: inputBytes); + /// //get the bookmark count. + /// int count = document.bookmarks.count; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int get count { - final PdfDocument document = _crossTable._document; + final PdfDocument? document = _crossTable._document; if (document != null) { if (_booklist == null) { _booklist = []; for (int n = 0; n < _list.length; n++) { - _booklist.add(_list[n]); + _booklist!.add(_list[n]); } } return _list.length; @@ -47,7 +77,25 @@ class PdfBookmarkBase implements _IPdfWrapper { } /// Gets the bookmark at specified index. + /// + /// ```dart + /// //Load the PDF document. + /// PdfDocument document = PdfDocument(inputBytes: inputBytes); + /// //Get the bookmark from index. + /// PdfBookmark bookmarks = document.bookmarks[0] + /// ..destination = PdfDestination(document.pages[1]) + /// ..color = PdfColor(0, 255, 0) + /// ..textStyle = [PdfTextStyle.bold] + /// ..title = 'Changed title'; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfBookmark operator [](int index) { + if (index < 0 || index >= count) { + throw RangeError('index'); + } return _list[index] as PdfBookmark; } @@ -57,10 +105,10 @@ class PdfBookmarkBase implements _IPdfWrapper { /// Gets whether to expand the node or not bool get _isExpanded { - if (_dictionary.containsKey('Count')) { - return (_dictionary[_DictionaryProperties.count] as _PdfNumber).value < 0 - ? false - : true; + if (_dictionary!.containsKey('Count')) { + final _PdfNumber number = + _dictionary![_DictionaryProperties.count] as _PdfNumber; + return number.value! < 0 ? false : true; } else { return _expanded; } @@ -71,14 +119,34 @@ class PdfBookmarkBase implements _IPdfWrapper { _expanded = value; if (count > 0) { final int newCount = _expanded ? _list.length : -_list.length; - _dictionary[_DictionaryProperties.count] = _PdfNumber(newCount); + _dictionary![_DictionaryProperties.count] = _PdfNumber(newCount); } } + //Public methods /// Adds the bookmark from the document. - PdfBookmark add(String title) { - ArgumentError.checkNotNull(title, 'title'); - final PdfBookmark previous = (count < 1) ? null : this[count - 1]; + /// + /// ```dart + /// //Create a new document. + /// PdfDocument document = PdfDocument(); + /// //Add bookmarks to the document. + /// document.bookmarks.add('Page 1') + /// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)) + /// ..textStyle = [PdfTextStyle.bold] + /// ..color = PdfColor(255, 0, 0); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfBookmark add(String title, + {bool isExpanded = false, + PdfColor? color, + PdfDestination? destination, + PdfNamedDestination? namedDestination, + PdfAction? action, + List? textStyle}) { + final PdfBookmark? previous = (count < 1) ? null : this[count - 1]; final PdfBookmark outline = PdfBookmark._internal(title, this, previous, null); if (previous != null) { @@ -90,24 +158,50 @@ class PdfBookmarkBase implements _IPdfWrapper { } /// Determines whether the specified outline presents in the collection. + /// ```dart + /// //Create a new document. + /// PdfDocument document = PdfDocument(); + /// //Add bookmarks to the document. + /// PdfBookmark bookmark = document.bookmarks.add('Page 1') + /// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)); + /// //check whether the specified bookmark present in the collection + /// bool contains = document.bookmarks.contains(bookmark); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` bool contains(PdfBookmark outline) { return _list.contains(outline); } /// Remove the specified bookmark from the document. + /// + /// ```dart + /// //Load the PDF document. + /// PdfDocument document = PdfDocument(inputBytes: inputBytes); + /// //Get all the bookmarks. + /// PdfBookmarkBase bookmarks = document.bookmarks; + /// //Remove specified bookmark. + /// bookmarks.remove('bookmark'); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` void remove(String title) { - ArgumentError.checkNotNull(title, 'title'); int index = -1; - if (_bookmark == null || _bookmark.length < _list.length) { + if (_bookmark == null || _bookmark!.length < _list.length) { _bookmark = []; for (int n = 0; n < _list.length; n++) { - ArgumentError.checkNotNull(_list[n] as PdfBookmark, 'bookmark'); - _bookmark.add(_list[n] as PdfBookmark); + if (_list[n] is PdfBookmark) { + _bookmark!.add(_list[n] as PdfBookmark); + } } } - for (int c = 0; c < _bookmark.length; c++) { - if (_bookmark[c] is PdfBookmark) { - final PdfBookmark pdfbookmark = _bookmark[c]; + for (int c = 0; c < _bookmark!.length; c++) { + if (_bookmark![c] is PdfBookmark) { + final PdfBookmark pdfbookmark = _bookmark![c]; if (pdfbookmark.title == title) { index = c; break; @@ -118,64 +212,98 @@ class PdfBookmarkBase implements _IPdfWrapper { } /// Remove the bookmark from the document at the specified index. + /// + /// ```dart + /// //Load the PDF document. + /// PdfDocument document = PdfDocument(inputBytes: inputBytes); + /// //Remove specified bookmark using index. + /// document.bookmarks.removeAt(0); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` void removeAt(int index) { - if (_bookmark == null || _bookmark.length < _list.length) { + if (_bookmark == null || _bookmark!.length < _list.length) { _bookmark = []; for (int n = 0; n < _list.length; n++) { - ArgumentError.checkNotNull(_list[n] as PdfBookmark, 'bookmark'); - _bookmark.add(_list[n] as PdfBookmark); + if (_list[n] is PdfBookmark?) { + _bookmark!.add(_list[n] as PdfBookmark); + } } } - if (index < 0 || index >= _bookmark.length) { + if (index < 0 || index >= _bookmark!.length) { throw RangeError.value(index); } - if (_bookmark[index] is PdfBookmark) { - final PdfBookmark current = _bookmark[index]; + if (_bookmark![index] is PdfBookmark) { + final PdfBookmark current = _bookmark![index]; if (index == 0) { - if (current._dictionary.containsKey(_DictionaryProperties.next)) { - _dictionary.setProperty(_DictionaryProperties.first, - current._dictionary[_DictionaryProperties.next]); + if (current._dictionary!.containsKey(_DictionaryProperties.next)) { + _dictionary!.setProperty(_DictionaryProperties.first, + current._dictionary![_DictionaryProperties.next]); } else { - _dictionary.remove(_DictionaryProperties.first); - _dictionary.remove(_DictionaryProperties.last); + _dictionary!.remove(_DictionaryProperties.first); + _dictionary!.remove(_DictionaryProperties.last); } } else if ((current._parent != null) && (current._previous != null) && (current._next != null)) { - current._previous._dictionary.setProperty(_DictionaryProperties.next, - current._dictionary[_DictionaryProperties.next]); - current._next._dictionary.setProperty(_DictionaryProperties.prev, - current._dictionary[_DictionaryProperties.prev]); + current._previous!._dictionary!.setProperty(_DictionaryProperties.next, + current._dictionary![_DictionaryProperties.next]); + current._next!._dictionary!.setProperty(_DictionaryProperties.prev, + current._dictionary![_DictionaryProperties.prev]); } else if ((current._parent != null) && (current._previous != null) && (current._next == null)) { - current._previous._dictionary.remove(_DictionaryProperties.next); - current._parent._dictionary.setProperty(_DictionaryProperties.last, - current._dictionary[_DictionaryProperties.prev]); + current._previous!._dictionary!.remove(_DictionaryProperties.next); + current._parent!._dictionary!.setProperty(_DictionaryProperties.last, + current._dictionary![_DictionaryProperties.prev]); } if (current._parent != null) { - current._parent._list.remove(current); + current._parent!._list.remove(current); } } - _bookmark.clear(); + _bookmark!.clear(); _updateFields(); } /// Removes all the bookmark from the collection. + /// + /// ```dart + /// //Load the PDF document. + /// PdfDocument document = PdfDocument(inputBytes: inputBytes); + /// //Clear all the bookmarks. + /// document.bookmarks.clear(); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` void clear() { _list.clear(); if (_bookmark != null) { - _bookmark.clear(); + _bookmark!.clear(); } if (_booklist != null) { - _booklist.clear(); + _booklist!.clear(); } _updateFields(); } /// Inserts a new outline at the specified index. + /// + /// ```dart + /// //Create a new document. + /// PdfDocument document = PdfDocument(); + /// //Insert bookmark at specified index + /// document.bookmarks.insert(0, 'bookmark') + /// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfBookmark insert(int index, String title) { - ArgumentError.checkNotNull(title, 'title'); if (index < 0 || index > count) { throw RangeError.value(index); } @@ -184,7 +312,7 @@ class PdfBookmarkBase implements _IPdfWrapper { outline = add(title); } else { final PdfBookmark next = this[index]; - final PdfBookmark prevoius = (index == 0) ? null : this[index - 1]; + final PdfBookmark? prevoius = (index == 0) ? null : this[index - 1]; outline = PdfBookmark._internal(title, this, prevoius, next); _list.insert(index, outline); if (prevoius != null) { @@ -196,24 +324,25 @@ class PdfBookmarkBase implements _IPdfWrapper { return outline; } + //Implementations /// Updates all outline dictionary fields. void _updateFields() { if (count > 0) { final int newCount = _isExpanded ? _list.length : -_list.length; - _dictionary[_DictionaryProperties.count] = _PdfNumber(newCount); - _dictionary.setProperty( + _dictionary![_DictionaryProperties.count] = _PdfNumber(newCount); + _dictionary!.setProperty( _DictionaryProperties.first, _PdfReferenceHolder(this[0])); - _dictionary.setProperty( + _dictionary!.setProperty( _DictionaryProperties.last, _PdfReferenceHolder(this[count - 1])); } else { - _dictionary.clear(); + _dictionary!.clear(); } } void _reproduceTree() { - PdfBookmark currentBookmark = _getFirstBookMark(this); + PdfBookmark? currentBookmark = _getFirstBookMark(this); bool isBookmark = currentBookmark != null; - while (isBookmark && currentBookmark._dictionary != null) { + while (isBookmark && currentBookmark!._dictionary != null) { currentBookmark._setParent(this); _bookmarkList.add(currentBookmark); currentBookmark = currentBookmark._next; @@ -221,13 +350,13 @@ class PdfBookmarkBase implements _IPdfWrapper { } } - PdfBookmark _getFirstBookMark(PdfBookmarkBase bookmark) { - PdfBookmark firstBookmark; - final _PdfDictionary dictionary = bookmark._dictionary; + PdfBookmark? _getFirstBookMark(PdfBookmarkBase bookmark) { + PdfBookmark? firstBookmark; + final _PdfDictionary dictionary = bookmark._dictionary!; if (dictionary.containsKey(_DictionaryProperties.first)) { - final _PdfDictionary first = + final _PdfDictionary? first = _crossTable._getObject(dictionary[_DictionaryProperties.first]) - as _PdfDictionary; + as _PdfDictionary?; firstBookmark = PdfBookmark._load(first, _crossTable); } return firstBookmark; @@ -235,20 +364,21 @@ class PdfBookmarkBase implements _IPdfWrapper { /// Gets the element. @override - _IPdfPrimitive get _element => _dictionary; + _IPdfPrimitive? get _element => _dictionary; @override - set _element(_IPdfPrimitive value) { + // ignore: unused_element + set _element(_IPdfPrimitive? value) { throw ArgumentError(); } } class _CurrentNodeInfo { - _CurrentNodeInfo(List kids, [int index]) { + _CurrentNodeInfo(List kids, [int? index]) { this.kids = kids; this.index = index ?? 0; } //Fields - List kids; - int index; + late List kids; + late int index; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_catalog.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_catalog.dart index 6cb5a00d4..d6c9679a2 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_catalog.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_catalog.dart @@ -7,11 +7,11 @@ class _PdfCatalog extends _PdfDictionary { this[_DictionaryProperties.type] = _PdfName('Catalog'); } - _PdfCatalog.fromDocument(PdfDocument document, _PdfDictionary catalog) + _PdfCatalog.fromDocument(PdfDocument document, _PdfDictionary? catalog) : super(catalog) { _document = document; if (containsKey(_DictionaryProperties.names)) { - final _IPdfPrimitive obj = + final _IPdfPrimitive? obj = _PdfCrossTable._dereference(this[_DictionaryProperties.names]); if (obj is _PdfDictionary) { _catalogNames = _PdfCatalogNames(obj); @@ -21,30 +21,31 @@ class _PdfCatalog extends _PdfDictionary { freezeChanges(this); } - PdfSectionCollection _sections; + PdfSectionCollection? _sections; // ignore: unused_field - PdfDocument _document; - _XmpMetadata _metadata; - _PdfCatalogNames _catalogNames; + PdfDocument? _document; + _XmpMetadata? _metadata; + _PdfCatalogNames? _catalogNames; + PdfForm? _forms; // ignore: unused_element - PdfSectionCollection get _pages => _sections; - set _pages(PdfSectionCollection sections) { + PdfSectionCollection? get _pages => _sections; + set _pages(PdfSectionCollection? sections) { if (_sections != sections) { _sections = sections; this[_DictionaryProperties.pages] = _PdfReferenceHolder(sections); } } - _PdfDictionary get _destinations { - _PdfDictionary dests; + _PdfDictionary? get _destinations { + _PdfDictionary? dests; if (containsKey(_DictionaryProperties.dests)) { dests = _PdfCrossTable._dereference(this[_DictionaryProperties.dests]) - as _PdfDictionary; + as _PdfDictionary?; } return dests; } - _PdfCatalogNames get _names { + _PdfCatalogNames? get _names { if (_catalogNames == null) { _catalogNames = _PdfCatalogNames(); this[_DictionaryProperties.names] = _PdfReferenceHolder(_catalogNames); @@ -52,16 +53,27 @@ class _PdfCatalog extends _PdfDictionary { return _catalogNames; } + PdfForm? get _form => _forms; + set _form(PdfForm? value) { + if (_forms != value) { + _forms = value; + if (!_forms!._isLoadedForm) { + this[_DictionaryProperties.acroForm] = _PdfReferenceHolder(_forms); + } + } + } + //Implementation /// Reads Xmp from the document. void readMetadata() { //Read metadata if present. - final _IPdfPrimitive rhMetadata = this[_DictionaryProperties.metadata]; + final _IPdfPrimitive? rhMetadata = this[_DictionaryProperties.metadata]; if (_PdfCrossTable._dereference(rhMetadata) is _PdfStream) { - final _PdfStream xmpStream = _PdfCrossTable._dereference(rhMetadata); + final _PdfStream xmpStream = + _PdfCrossTable._dereference(rhMetadata) as _PdfStream; bool isFlateDecode = false; if (xmpStream.containsKey(_DictionaryProperties.filter)) { - _IPdfPrimitive obj = xmpStream[_DictionaryProperties.filter]; + _IPdfPrimitive? obj = xmpStream[_DictionaryProperties.filter]; if (obj is _PdfReferenceHolder) { final _PdfReferenceHolder rh = obj; obj = rh.object; @@ -75,17 +87,20 @@ class _PdfCatalog extends _PdfDictionary { } } else if (obj is _PdfArray) { final _PdfArray filter = obj; - for (final pdfFilter in filter._elements) { - final _PdfName filtername = pdfFilter as _PdfName; - if (filtername._name == _DictionaryProperties.flateDecode) { - isFlateDecode = true; + _IPdfPrimitive? pdfFilter; + for (pdfFilter in filter._elements) { + if (pdfFilter != null && pdfFilter is _PdfName) { + final _PdfName filtername = pdfFilter; + if (filtername._name == _DictionaryProperties.flateDecode) { + isFlateDecode = true; + } } } } } } - if (xmpStream.compress || isFlateDecode) { + if (xmpStream.compress! || isFlateDecode) { try { xmpStream._decompress(); } catch (e) { @@ -94,11 +109,13 @@ class _PdfCatalog extends _PdfDictionary { } XmlDocument xmp; try { - xmp = XmlDocument.parse(utf8.decode(xmpStream._dataStream)); + xmp = + XmlDocument.parse(utf8.decode(xmpStream._dataStream as List)); } catch (e) { xmpStream._decompress(); try { - xmp = XmlDocument.parse(utf8.decode(xmpStream._dataStream)); + xmp = XmlDocument.parse( + utf8.decode(xmpStream._dataStream as List)); } catch (e1) { return; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_catalog_names.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_catalog_names.dart index 8d1cd83fa..8a6d60c5e 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_catalog_names.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_catalog_names.dart @@ -1,26 +1,25 @@ part of pdf; class _PdfCatalogNames implements _IPdfWrapper { - _PdfCatalogNames([_PdfDictionary root]) { + _PdfCatalogNames([_PdfDictionary? root]) { if (root != null) { _dictionary = root; } } //Fields _PdfDictionary _dictionary = _PdfDictionary(); - PdfAttachmentCollection _attachments; + PdfAttachmentCollection? _attachments; //Properties //Gets the destinations. - _PdfDictionary get _destinations { - final _IPdfPrimitive obj = + _PdfDictionary? get _destinations { + final _IPdfPrimitive? obj = _PdfCrossTable._dereference(_dictionary[_DictionaryProperties.dests]); - final _PdfDictionary dests = obj as _PdfDictionary; + final _PdfDictionary? dests = obj as _PdfDictionary?; return dests; } set _embeddedFiles(PdfAttachmentCollection value) { - ArgumentError.checkNotNull(value, 'value'); if (_attachments != value) { _attachments = value; _dictionary.setProperty(_DictionaryProperties.embeddedFiles, @@ -30,11 +29,12 @@ class _PdfCatalogNames implements _IPdfWrapper { //Methods //Gets the named object from a tree. - _IPdfPrimitive _getNamedObjectFromTree(_PdfDictionary root, _PdfString name) { + _IPdfPrimitive? _getNamedObjectFromTree( + _PdfDictionary? root, _PdfString name) { bool found = false; - _PdfDictionary current = root; - _IPdfPrimitive obj; - while (!found && current != null && current._items.isNotEmpty) { + _PdfDictionary? current = root; + _IPdfPrimitive? obj; + while (!found && current != null && current._items!.isNotEmpty) { if (current.containsKey(_DictionaryProperties.kids)) { current = _getProperKid(current, name); } else if (current.containsKey(_DictionaryProperties.names)) { @@ -46,7 +46,7 @@ class _PdfCatalogNames implements _IPdfWrapper { } //Finds the name in the tree. - _IPdfPrimitive _findName(_PdfDictionary current, _PdfString name) { + _IPdfPrimitive? _findName(_PdfDictionary current, _PdfString name) { final _PdfArray names = _PdfCrossTable._dereference(current[_DictionaryProperties.names]) as _PdfArray; @@ -70,7 +70,7 @@ class _PdfCatalogNames implements _IPdfWrapper { break; } } - _IPdfPrimitive obj; + _IPdfPrimitive? obj; if (found) { obj = _PdfCrossTable._dereference(names[half * 2 + 1]); } @@ -78,14 +78,14 @@ class _PdfCatalogNames implements _IPdfWrapper { } //Gets the proper kid from an array. - _PdfDictionary _getProperKid(_PdfDictionary current, _PdfString name) { + _PdfDictionary? _getProperKid(_PdfDictionary current, _PdfString name) { final _PdfArray kids = _PdfCrossTable._dereference(current[_DictionaryProperties.kids]) as _PdfArray; - _PdfDictionary kid; + _PdfDictionary? kid; for (final obj in kids._elements) { - kid = _PdfCrossTable._dereference(obj) as _PdfDictionary; - if (_checkLimits(kid, name)) { + kid = _PdfCrossTable._dereference(obj) as _PdfDictionary?; + if (_checkLimits(kid!, name)) { break; } else { kid = null; @@ -96,7 +96,7 @@ class _PdfCatalogNames implements _IPdfWrapper { // Checks the limits of the named tree node. bool _checkLimits(_PdfDictionary kid, _PdfString name) { - _IPdfPrimitive obj = kid[_DictionaryProperties.limits]; + _IPdfPrimitive? obj = kid[_DictionaryProperties.limits]; bool result = false; if (obj is _PdfArray && obj.count >= 2) { final _PdfArray limits = obj; @@ -116,8 +116,8 @@ class _PdfCatalogNames implements _IPdfWrapper { } int _byteCompare(_PdfString str1, _PdfString str2) { - final List data1 = str1.data; - final List data2 = str2.data; + final List data1 = str1.data!; + final List data2 = str2.data!; final int commonSize = [data1.length, data2.length].reduce(min); int result = 0; for (int i = 0; i < commonSize; ++i) { @@ -136,9 +136,7 @@ class _PdfCatalogNames implements _IPdfWrapper { // Clear catalog names. void clear() { - if (_dictionary != null) { - _dictionary.clear(); - } + _dictionary.clear(); } //Overrides @@ -146,7 +144,8 @@ class _PdfCatalogNames implements _IPdfWrapper { _IPdfPrimitive get _element => _dictionary; @override - set _element(_IPdfPrimitive value) { + // ignore: unused_element + set _element(_IPdfPrimitive? value) { throw ArgumentError('primitive elements can\'t be set'); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document.dart index 487e5bad2..9edbfb58a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document.dart @@ -26,9 +26,9 @@ class PdfDocument { /// document.dispose(); /// ``` PdfDocument( - {List inputBytes, - PdfConformanceLevel conformanceLevel, - String password}) { + {List? inputBytes, + PdfConformanceLevel? conformanceLevel, + String? password}) { _isLoadedDocument = inputBytes != null; _password = password; _initialize(inputBytes); @@ -56,8 +56,7 @@ class PdfDocument { /// //Dispose the document. /// document.dispose(); /// ``` - PdfDocument.fromBase64String(String base64String, {String password}) { - ArgumentError.checkNotNull(base64String); + PdfDocument.fromBase64String(String base64String, {String? password}) { if (base64String.isEmpty) { ArgumentError.value(base64String, 'PDF data', 'PDF data cannot be null'); } @@ -69,42 +68,73 @@ class PdfDocument { static const double _defaultMargin = 40; //Fields - _PdfMainObjectCollection _objects; - _PdfCrossTable _crossTable; - _PdfCatalog _catalog; - PdfPageCollection _pages; - PdfPageSettings _settings; - PdfSectionCollection _sections; - PdfFileStructure _fileStructure; - PdfColorSpace _colorSpace; - PdfDocumentTemplate _template; - PdfBookmarkBase _outlines; - PdfNamedDestinationCollection _namedDestinations; + late _PdfMainObjectCollection _objects; + late _PdfCrossTable _crossTable; + late _PdfCatalog _catalog; + PdfPageCollection? _pages; + PdfPageSettings? _settings; + PdfSectionCollection? _sections; + PdfFileStructure? _fileStructure; + PdfColorSpace? _colorSpace; + PdfDocumentTemplate? _template; + PdfBookmarkBase? _outlines; + PdfNamedDestinationCollection? _namedDestinations; bool _isLoadedDocument = false; - List _data; + List? _data; bool _isStreamCopied = false; - PdfBookmarkBase _bookmark; - Map _bookmarkHashTable; - PdfDocumentInformation _documentInfo; - PdfSecurity _security; - int _position; - int _orderPosition; - int _onPosition; - int _offPosition; - _PdfArray _primitive; - _PdfArray _printLayer; - _PdfArray _on; - _PdfArray _off; - _PdfArray _order; - String _password; - bool _isEncrypted; + PdfBookmarkBase? _bookmark; + Map? _bookmarkHashTable; + PdfDocumentInformation? _documentInfo; + PdfSecurity? _security; + int? _position; + int? _orderPosition; + int? _onPosition; + int? _offPosition; + _PdfArray? _primitive; + _PdfArray? _printLayer; + _PdfArray? _on; + _PdfArray? _off; + _PdfArray? _order; + String? _password; + late bool _isEncrypted; PdfConformanceLevel _conformanceLevel = PdfConformanceLevel.none; - PdfLayerCollection _layers; - _PdfReference _currentSavingObject; - PdfAttachmentCollection _attachments; + PdfLayerCollection? _layers; + _PdfReference? _currentSavingObject; + PdfAttachmentCollection? _attachments; + bool? _isAttachOnlyEncryption; + PdfForm? _form; + + //Events + List<_DocumentSavedHandler>? _documentSavedList; /// Gets or sets the PDF document compression level. - PdfCompressionLevel compressionLevel; + PdfCompressionLevel? compressionLevel; + + /// The event raised on Pdf password. + /// + /// ```dart + /// //Load an existing PDF document + /// PdfDocument document = + /// PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()) + /// //Subsribe the onPdfPassword event + /// ..onPdfPassword = loadOnPdfPassword; + /// //Access the attachments + /// PdfAttachmentCollection attachmentCollection = document.attachments; + /// //Iterates the attachments + /// for (int i = 0; i < attachmentCollection.count; i++) { + /// //Extracts the attachment and saves it to the disk + /// File(attachmentCollection[i].fileName) + /// .writeAsBytesSync(attachmentCollection[i].data); + /// } + /// //Disposes the document + /// document.dispose(); + /// + /// void loadOnPdfPassword(PdfDocument sender, PdfPasswordArgs args) { + /// //Sets the value of PDF password. + /// args.attachmentOpenPassword = 'syncfusion'; + /// } + /// ``` + PdfPasswordCallback? onPdfPassword; //Properties /// Gets the security features of the document like encryption. @@ -114,8 +144,9 @@ class PdfDocument { /// PdfDocument document = PdfDocument(); /// //Document security /// PdfSecurity security = document.security; - /// //Set algorithm - /// security.algorithm = PdfEncryptionAlgorithm.rc4x40Bit; + /// //Set security options + /// security.keySize = PdfEncryptionKeySize.key128Bit; + /// security.algorithm = PdfEncryptionAlgorithm.rc4; /// security.userPassword = 'password'; /// security.ownerPassword = 'syncfusion'; /// //Save the document. @@ -126,9 +157,9 @@ class PdfDocument { PdfSecurity get security { _security ??= PdfSecurity._(); if (_conformanceLevel != PdfConformanceLevel.none) { - _security._conformance = true; + _security!._conformance = true; } - return _security; + return _security!; } /// Gets the collection of the sections in the document. @@ -146,7 +177,7 @@ class PdfDocument { /// List bytes = document.save(); /// document.dispose(); /// ``` - PdfSectionCollection get sections => _sections; + PdfSectionCollection? get sections => _sections; /// Gets the document's page setting. /// @@ -166,9 +197,9 @@ class PdfDocument { PdfPageSettings get pageSettings { if (_settings == null) { _settings = PdfPageSettings(); - _settings.setMargins(_defaultMargin); + _settings!.setMargins(_defaultMargin); } - return _settings; + return _settings!; } /// Sets the document's page setting. @@ -190,17 +221,13 @@ class PdfDocument { /// document.dispose(); /// ``` set pageSettings(PdfPageSettings settings) { - if (settings == null) { - throw ArgumentError.notNull('settings'); - } else { - _settings = settings; - } + _settings = settings; } /// Gets a template to all pages in the document. PdfDocumentTemplate get template { _template ??= PdfDocumentTemplate(); - return _template; + return _template!; } /// Sets a template to all pages in the document. @@ -217,7 +244,7 @@ class PdfDocument { if ((_colorSpace == PdfColorSpace.rgb) || ((_colorSpace == PdfColorSpace.cmyk) || (_colorSpace == PdfColorSpace.grayScale))) { - return _colorSpace; + return _colorSpace!; } else { return PdfColorSpace.rgb; } @@ -240,7 +267,7 @@ class PdfDocument { /// Gets the internal structure of the PDF document. PdfFileStructure get fileStructure { _fileStructure ??= PdfFileStructure(); - return _fileStructure; + return _fileStructure!; } /// Sets the internal structure of the PDF document. @@ -264,15 +291,29 @@ class PdfDocument { PdfPageCollection get pages => _getPageCollection(); /// Gets the bookmark collection of the document. + /// + /// ```dart + /// //Create a new document. + /// PdfDocument document = PdfDocument(); + /// //Create document bookmarks. + /// document.bookmarks.add('Interactive Feature') + /// ..destination = PdfDestination(document.pages.add(), Offset(20, 20)) + /// ..textStyle = [PdfTextStyle.bold] + /// ..color = PdfColor(255, 0, 0); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfBookmarkBase get bookmarks { if (_isLoadedDocument) { if (_bookmark == null) { if (_catalog.containsKey(_DictionaryProperties.outlines)) { - final _IPdfPrimitive outlines = _PdfCrossTable._dereference( + final _IPdfPrimitive? outlines = _PdfCrossTable._dereference( _catalog[_DictionaryProperties.outlines]); if (outlines != null && outlines is _PdfDictionary) { _bookmark = PdfBookmarkBase._load(outlines, _crossTable); - _bookmark._reproduceTree(); + _bookmark!._reproduceTree(); } else { _bookmark = _createBookmarkRoot(); } @@ -280,15 +321,14 @@ class PdfDocument { _bookmark = _createBookmarkRoot(); } } - // _bookmark._isLoadedBookmark = true; - return _bookmark; + return _bookmark!; } else { if (_outlines == null) { _outlines = PdfBookmarkBase._internal(); _catalog[_DictionaryProperties.outlines] = _PdfReferenceHolder(_outlines); } - return _outlines; + return _outlines!; } } @@ -298,31 +338,31 @@ class PdfDocument { if (_namedDestinations == null) { if (_catalog.containsKey(_DictionaryProperties.names) && (_namedDestinations == null)) { - final _PdfDictionary namedDestinations = + final _PdfDictionary? namedDestinations = _PdfCrossTable._dereference(_catalog[_DictionaryProperties.names]) - as _PdfDictionary; + as _PdfDictionary?; _namedDestinations = PdfNamedDestinationCollection._(namedDestinations, _crossTable); } _namedDestinations ??= _createNamedDestinations(); } - return _namedDestinations; + return _namedDestinations!; } else { if (_namedDestinations == null) { _namedDestinations = PdfNamedDestinationCollection(); _catalog[_DictionaryProperties.names] = _PdfReferenceHolder(_namedDestinations); - final _PdfReferenceHolder names = - _catalog[_DictionaryProperties.names] as _PdfReferenceHolder; + final _PdfReferenceHolder? names = + _catalog[_DictionaryProperties.names] as _PdfReferenceHolder?; if (names != null) { - final _PdfDictionary dic = names.object as _PdfDictionary; + final _PdfDictionary? dic = names.object as _PdfDictionary?; if (dic != null) { dic[_DictionaryProperties.dests] = _PdfReferenceHolder(_namedDestinations); } } } - return _namedDestinations; + return _namedDestinations!; } } @@ -330,18 +370,20 @@ class PdfDocument { PdfDocumentInformation get documentInformation { if (_documentInfo == null) { if (_isLoadedDocument) { - final _PdfDictionary trailer = _crossTable.trailer; + final _PdfDictionary trailer = _crossTable.trailer!; if (_PdfCrossTable._dereference(trailer[_DictionaryProperties.info]) is _PdfDictionary) { + final _PdfDictionary? entry = + _PdfCrossTable._dereference(trailer[_DictionaryProperties.info]) + as _PdfDictionary?; _documentInfo = PdfDocumentInformation._(_catalog, - dictionary: _PdfCrossTable._dereference( - trailer[_DictionaryProperties.info]), + dictionary: entry, isLoaded: true, conformance: _conformanceLevel); } else { _documentInfo = PdfDocumentInformation._(_catalog, conformance: _conformanceLevel); - _crossTable.trailer[_DictionaryProperties.info] = + _crossTable.trailer![_DictionaryProperties.info] = _PdfReferenceHolder(_documentInfo); } // Read document's info dictionary if present. @@ -349,17 +391,17 @@ class PdfDocument { } else { _documentInfo = PdfDocumentInformation._(_catalog, conformance: _conformanceLevel); - _crossTable.trailer[_DictionaryProperties.info] = + _crossTable.trailer![_DictionaryProperties.info] = _PdfReferenceHolder(_documentInfo); } } - return _documentInfo; + return _documentInfo!; } /// Gets the collection of PdfLayer from the PDF document. PdfLayerCollection get layers { _layers ??= PdfLayerCollection._(this); - return _layers; + return _layers!; } /// Gets the attachment collection of the document. @@ -369,24 +411,83 @@ class PdfDocument { _attachments = PdfAttachmentCollection(); if (_conformanceLevel == PdfConformanceLevel.a1b || _conformanceLevel == PdfConformanceLevel.a2b) { - _attachments._conformance = true; + _attachments!._conformance = true; } - _catalog._names._embeddedFiles = _attachments; + _catalog._names!._embeddedFiles = _attachments!; } else { - final _IPdfPrimitive attachmentDictionary = + if (onPdfPassword != null && _password == '') { + final PdfPasswordArgs args = PdfPasswordArgs._(); + onPdfPassword!(this, args); + _password = args.attachmentOpenPassword; + } + if (_isAttachOnlyEncryption!) { + _checkEncryption(_isAttachOnlyEncryption); + } + final _IPdfPrimitive? attachmentDictionary = _PdfCrossTable._dereference(_catalog[_DictionaryProperties.names]); - if (attachmentDictionary is _PdfDictionary && + if (attachmentDictionary != null && + attachmentDictionary is _PdfDictionary && attachmentDictionary .containsKey(_DictionaryProperties.embeddedFiles)) { _attachments = PdfAttachmentCollection._(attachmentDictionary, _crossTable); } else { _attachments = PdfAttachmentCollection(); - _catalog._names._embeddedFiles = _attachments; + _catalog._names!._embeddedFiles = _attachments!; } } } - return _attachments; + return _attachments!; + } + + /// Gets the interactive form of the document. + PdfForm get form { + if (_isLoadedDocument) { + if (_form == null) { + if (_catalog.containsKey(_DictionaryProperties.acroForm)) { + final _IPdfPrimitive? formDictionary = _PdfCrossTable._dereference( + _catalog[_DictionaryProperties.acroForm]); + if (formDictionary is _PdfDictionary) { + _form = PdfForm._internal(_crossTable, formDictionary); + if (_form != null && _form!.fields.count != 0) { + _catalog._form = _form; + List? widgetReference; + if (_form!._crossTable!._document != null) { + for (int i = 0; + i < _form!._crossTable!._document!.pages.count; + i++) { + final PdfPage? page = _form!._crossTable!._document!.pages[i]; + if (widgetReference == null && page != null) { + widgetReference = page._getWidgetReferences(); + } + if (page != null && + widgetReference != null && + widgetReference.isNotEmpty) { + page._createAnnotations(widgetReference); + } + } + if (widgetReference != null) widgetReference.clear(); + if (!_form!._formHasKids) { + _form!.fields + ._createFormFieldsFromWidgets(_form!.fields.count); + } + } + } + } + } else { + _form = PdfForm._internal(_crossTable); + _catalog.setProperty( + _DictionaryProperties.acroForm, _PdfReferenceHolder(_form)); + _catalog._form = _form!; + return _form!; + } + } else { + return _form!; + } + } else { + return _catalog._form ??= PdfForm(); + } + return _form!; } //Public methods @@ -408,21 +509,34 @@ class PdfDocument { final _PdfWriter writer = _PdfWriter(buffer); writer._document = this; _checkPages(); + if (security._encryptAttachments && security.userPassword == '') { + throw ArgumentError.value( + 'User password cannot be empty for encrypt only attachment.'); + } if (_isLoadedDocument) { + if (_security != null) { + if (_security!._encryptAttachments) { + fileStructure.incrementalUpdate = false; + if (_security!._encryptor.userPassword.isEmpty) { + _security!._encryptor.encryptOnlyAttachment = false; + } + } + } if (fileStructure.incrementalUpdate && (_security == null || (_security != null && - !_security._modifiedSecurity && - !_security.permissions._modifiedPermissions))) { + !_security!._modifiedSecurity && + !_security!.permissions._modifiedPermissions))) { _copyOldStream(writer); + if (_catalog.changed!) { + _readDocumentInfo(); + } } else { _crossTable = _PdfCrossTable._fromCatalog(_crossTable.count, _crossTable.encryptorDictionary, _crossTable._documentCatalog); _crossTable._document = this; - if (documentInformation != null) { - _crossTable.trailer[_DictionaryProperties.info] = - _PdfReferenceHolder(documentInformation); - } + _crossTable.trailer![_DictionaryProperties.info] = + _PdfReferenceHolder(documentInformation); } _appendDocument(writer); } else { @@ -431,9 +545,7 @@ class PdfDocument { _conformanceLevel == PdfConformanceLevel.a3b) { documentInformation._xmpMetadata; if (_conformanceLevel == PdfConformanceLevel.a3b && - _catalog != null && _catalog._names != null && - attachments != null && attachments.count > 0) { final _PdfName fileRelationShip = _PdfName(_DictionaryProperties.afRelationship); @@ -441,21 +553,23 @@ class PdfDocument { for (int i = 0; i < attachments.count; i++) { if (!attachments[i] ._dictionary - ._items + ._items! .containsKey(fileRelationShip)) { - attachments[i]._dictionary._items[fileRelationShip] = + attachments[i]._dictionary._items![fileRelationShip] = _PdfName('Alternative'); } fileAttachmentAssociationArray ._add(_PdfReferenceHolder(attachments[i]._dictionary)); } - _catalog._items[_PdfName(_DictionaryProperties.af)] = + _catalog._items![_PdfName(_DictionaryProperties.af)] = fileAttachmentAssociationArray; } } _crossTable._save(writer); + final _DocumentSavedArgs argsSaved = _DocumentSavedArgs(writer); + _onDocumentSaved(argsSaved); } - return writer._buffer; + return writer._buffer!; } void _checkPages() { @@ -482,15 +596,14 @@ class PdfDocument { void dispose() { PdfBrushes._dispose(); PdfPens._dispose(); - if (_crossTable != null) { - _crossTable._dispose(); - } + _crossTable._dispose(); _security = null; _currentSavingObject = null; } //Implementation - void _initialize(List pdfData) { + void _initialize(List? pdfData) { + _isAttachOnlyEncryption = false; _isEncrypted = false; _data = pdfData; _objects = _PdfMainObjectCollection(); @@ -498,15 +611,14 @@ class PdfDocument { _crossTable = _PdfCrossTable(this, pdfData); _isEncrypted = _checkEncryption(false); final _PdfCatalog catalog = _getCatalogValue(); - if (catalog != null && - catalog.containsKey(_DictionaryProperties.pages) && + if (catalog.containsKey(_DictionaryProperties.pages) && !catalog.containsKey(_DictionaryProperties.type)) { catalog._addItems(_DictionaryProperties.type, _PdfName(_DictionaryProperties.catalog)); } if (catalog.containsKey(_DictionaryProperties.type)) { if (!(catalog[_DictionaryProperties.type] as _PdfName) - ._name + ._name! .contains(_DictionaryProperties.catalog)) { catalog[_DictionaryProperties.type] = _PdfName(_DictionaryProperties.catalog); @@ -518,10 +630,10 @@ class PdfDocument { } bool hasVersion = false; if (catalog.containsKey(_DictionaryProperties.version)) { - final _PdfName version = - catalog[_DictionaryProperties.version] as _PdfName; + final _PdfName? version = + catalog[_DictionaryProperties.version] as _PdfName?; if (version != null) { - _setFileVersion('PDF-' + version._name); + _setFileVersion('PDF-' + version._name!); hasVersion = true; } } @@ -550,19 +662,19 @@ class PdfDocument { _printLayer = _PdfArray(); } - bool _checkEncryption(bool isAttachEncryption) { + bool _checkEncryption(bool? isAttachEncryption) { bool wasEncrypted = false; if (_crossTable.encryptorDictionary != null) { - final _PdfDictionary encryptionDict = _crossTable.encryptorDictionary; + final _PdfDictionary encryptionDict = _crossTable.encryptorDictionary!; _password ??= ''; - final _PdfDictionary trailerDict = _crossTable.trailer; - _PdfArray obj; + final _PdfDictionary trailerDict = _crossTable.trailer!; + _PdfArray? obj; if (trailerDict.containsKey(_DictionaryProperties.id)) { - _IPdfPrimitive primitive = trailerDict[_DictionaryProperties.id]; + _IPdfPrimitive? primitive = trailerDict[_DictionaryProperties.id]; if (primitive is _PdfArray) { obj = primitive; } else if (primitive is _PdfReferenceHolder) { - primitive = (primitive as _PdfReferenceHolder).object; + primitive = primitive.object; if (primitive != null && primitive is _PdfArray) { obj = primitive; } @@ -571,28 +683,40 @@ class PdfDocument { obj ??= _PdfArray().._add(_PdfString.fromBytes([])); final _PdfString key = obj[0] as _PdfString; final _PdfEncryptor encryptor = _PdfEncryptor(); - if (encryptionDict != null && - encryptionDict.containsKey(_DictionaryProperties.encryptMetadata)) { - _IPdfPrimitive primitive = + if (encryptionDict.containsKey(_DictionaryProperties.encryptMetadata)) { + _IPdfPrimitive? primitive = encryptionDict[_DictionaryProperties.encryptMetadata]; if (primitive is _PdfBoolean) { - encryptor.encryptMetadata = primitive.value; + encryptor.encryptMetadata = primitive.value!; } else if (primitive is _PdfReferenceHolder) { - primitive = (primitive as _PdfReferenceHolder)._object; + primitive = primitive._object; if (primitive != null && primitive is _PdfBoolean) { - encryptor.encryptMetadata = primitive.value; + encryptor.encryptMetadata = primitive.value!; } } } wasEncrypted = true; encryptor._readFromDictionary(encryptionDict); - if (!encryptor._checkPassword(_password, key)) { - ArgumentError.value(_password, + bool encryption = true; + if (!isAttachEncryption! && encryptor._encryptOnlyAttachment!) { + encryption = false; + } + if (!encryptor._checkPassword(_password!, key, encryption)) { + throw ArgumentError.value(_password, 'password', 'Cannot open an encrypted document. The password is invalid.'); } encryptionDict.encrypt = false; final PdfSecurity security = PdfSecurity._().._encryptor = encryptor; _security = security; + _security!._encryptOnlyAttachment = encryptor._encryptOnlyAttachment!; + _isAttachOnlyEncryption = encryptor._encryptOnlyAttachment; + if (_isAttachOnlyEncryption!) { + security._encryptor._encryptionOptions = + PdfEncryptionOptions.encryptOnlyAttachments; + } else if (!encryptor.encryptMetadata) { + security._encryptor._encryptionOptions = + PdfEncryptionOptions.encryptAllContentsExceptMetadata; + } _crossTable.encryptor = encryptor; } return wasEncrypted; @@ -600,23 +724,24 @@ class PdfDocument { void _copyOldStream(_PdfWriter writer) { writer._write(_data); - writer._write(_Operators.newLine); _isStreamCopied = true; } void _appendDocument(_PdfWriter writer) { + writer._document = this; _crossTable._save(writer); + security._encryptor.encrypt = true; + final _DocumentSavedArgs argsSaved = _DocumentSavedArgs(writer); + _onDocumentSaved(argsSaved); } void _readFileVersion() { final _PdfReader _reader = _PdfReader(_data); _reader.position = 0; - String token = _reader._getNextToken(); + String token = _reader._getNextToken()!; if (token.startsWith('%')) { - token = _reader._getNextToken(); - if (token != null) { - _setFileVersion(token); - } + token = _reader._getNextToken()!; + _setFileVersion(token); } } @@ -659,29 +784,28 @@ class PdfDocument { _PdfCatalog _getCatalogValue() { final _PdfCatalog catalog = _PdfCatalog.fromDocument(this, _crossTable.documentCatalog); - final int index = _objects._lookFor(_crossTable.documentCatalog); + final int index = _objects._lookFor(_crossTable.documentCatalog!)!; _objects._reregisterReference(index, catalog); catalog.position = -1; return catalog; } void _setCatalog(_PdfCatalog catalog) { - ArgumentError.checkNotNull(catalog); _catalog = catalog; if (_catalog.containsKey(_DictionaryProperties.outlines)) { - final _PdfReferenceHolder outlines = - _catalog[_DictionaryProperties.outlines] as _PdfReferenceHolder; - _PdfDictionary dic; + final _PdfReferenceHolder? outlines = + _catalog[_DictionaryProperties.outlines] as _PdfReferenceHolder?; + _PdfDictionary? dic; if (outlines == null) { - dic = _catalog[_DictionaryProperties.outlines] as _PdfDictionary; + dic = _catalog[_DictionaryProperties.outlines] as _PdfDictionary?; } else if (outlines.object is _PdfDictionary) { - dic = outlines.object; + dic = outlines.object as _PdfDictionary?; } if (dic != null && dic.containsKey(_DictionaryProperties.first)) { - final _PdfReferenceHolder first = - dic[_DictionaryProperties.first] as _PdfReferenceHolder; + final _PdfReferenceHolder? first = + dic[_DictionaryProperties.first] as _PdfReferenceHolder?; if (first != null) { - final _PdfDictionary firstDic = first.object as _PdfDictionary; + final _PdfDictionary? firstDic = first.object as _PdfDictionary?; if (firstDic == null) { dic.remove(_DictionaryProperties.first); } @@ -694,29 +818,29 @@ class PdfDocument { _pages ??= _isLoadedDocument ? PdfPageCollection._fromCrossTable(this, _crossTable) : PdfPageCollection._(this); - return _pages; + return _pages!; } /// Creates a bookmarks collection to the document. - PdfBookmarkBase _createBookmarkRoot() { + PdfBookmarkBase? _createBookmarkRoot() { _bookmark = PdfBookmarkBase._internal(); _catalog.setProperty( _DictionaryProperties.outlines, _PdfReferenceHolder(_bookmark)); return _bookmark; } - _PdfArray _getNamedDestination(_IPdfPrimitive obj) { - _PdfDictionary destinations; - _PdfArray destination; + _PdfArray? _getNamedDestination(_IPdfPrimitive obj) { + _PdfDictionary? destinations; + _PdfArray? destination; if (obj is _PdfName) { destinations = _catalog._destinations; - final _IPdfPrimitive name = destinations[obj]; + final _IPdfPrimitive? name = destinations![obj]; destination = _extractDestination(name); } else if (obj is _PdfString) { - final _PdfCatalogNames names = _catalog._names; + final _PdfCatalogNames? names = _catalog._names; if (names != null) { destinations = names._destinations; - final _IPdfPrimitive name = + final _IPdfPrimitive? name = names._getNamedObjectFromTree(destinations, obj); destination = _extractDestination(name); } @@ -724,37 +848,37 @@ class PdfDocument { return destination; } - _PdfArray _extractDestination(_IPdfPrimitive obj) { - _PdfDictionary dic; + _PdfArray? _extractDestination(_IPdfPrimitive? obj) { + _PdfDictionary? dic; if (obj is _PdfDictionary) { dic = obj; } else if (obj is _PdfReferenceHolder) { - final _PdfReferenceHolder holder = (obj as _PdfReferenceHolder); + final _PdfReferenceHolder holder = obj; if (holder._object is _PdfDictionary) { - dic = (holder._object as _PdfDictionary); + dic = (holder._object as _PdfDictionary?); } else if (holder._object is _PdfArray) { - obj = (holder._object as _PdfArray); + obj = (holder._object as _PdfArray?) as _PdfReferenceHolder; } } - _PdfArray destination; + _PdfArray? destination; if (obj is _PdfArray) { destination = obj; } if (dic != null) { obj = _PdfCrossTable._dereference(dic[_DictionaryProperties.d]); - destination = obj as _PdfArray; + destination = obj as _PdfArray?; } return destination; } - PdfNamedDestinationCollection _createNamedDestinations() { + PdfNamedDestinationCollection? _createNamedDestinations() { _namedDestinations = PdfNamedDestinationCollection(); - final _PdfReferenceHolder catalogReference = - _catalog[_DictionaryProperties.names] as _PdfReferenceHolder; + final _PdfReferenceHolder? catalogReference = + _catalog[_DictionaryProperties.names] as _PdfReferenceHolder?; if (catalogReference != null) { - final _PdfDictionary catalogNames = - catalogReference.object as _PdfDictionary; + final _PdfDictionary? catalogNames = + catalogReference.object as _PdfDictionary?; if (catalogNames != null) { catalogNames.setProperty(_DictionaryProperties.dests, _PdfReferenceHolder(_namedDestinations)); @@ -767,44 +891,43 @@ class PdfDocument { return _namedDestinations; } - Map _createBookmarkDestinationDictionary() { - PdfBookmarkBase current = bookmarks; - if (_bookmarkHashTable == null && current != null) { + Map? _createBookmarkDestinationDictionary() { + PdfBookmarkBase? current = bookmarks; + if (_bookmarkHashTable == null) { _bookmarkHashTable = {}; final Queue<_CurrentNodeInfo> stack = Queue<_CurrentNodeInfo>(); _CurrentNodeInfo ni = _CurrentNodeInfo(current._list); do { for (; ni.index < ni.kids.length;) { current = ni.kids[ni.index]; - final PdfNamedDestination ndest = + final PdfNamedDestination? ndest = (current as PdfBookmark).namedDestination; if (ndest != null) { if (ndest.destination != null) { - final PdfPage page = ndest.destination.page; - List list = _bookmarkHashTable.containsKey(page) - ? _bookmarkHashTable[page] as List - : null; + final PdfPage page = ndest.destination!.page; + List? list; + if (_bookmarkHashTable!.containsKey(page)) { + list = _bookmarkHashTable![page] as List?; + } if (list == null) { list = []; - _bookmarkHashTable[page] = list; + _bookmarkHashTable![page] = list; } list.add(current); } } else { - final PdfDestination dest = (current as PdfBookmark).destination; - if (dest != null) { - final PdfPage page = dest.page; - List list = _bookmarkHashTable.containsKey(page) - ? _bookmarkHashTable[page] as List - : null; - if (list == null) { - list = []; - _bookmarkHashTable[page] = list; - } - list.add(current); + final PdfDestination? dest = current.destination; + final PdfPage page = dest!.page; + List? list = _bookmarkHashTable!.containsKey(page) + ? _bookmarkHashTable![page] as List? + : null; + if (list == null) { + list = []; + _bookmarkHashTable![page] = list; } + list.add(current); } - ++ni.index; + ni.index = ni.index + 1; if (current.count > 0) { stack.addLast(ni); ni = _CurrentNodeInfo(current._list); @@ -824,22 +947,25 @@ class PdfDocument { void _readDocumentInfo() { // Read document's info if present. - final _PdfDictionary info = _PdfCrossTable._dereference( - _crossTable.trailer[_DictionaryProperties.info]) as _PdfDictionary; + final _PdfDictionary? info = _PdfCrossTable._dereference( + _crossTable.trailer![_DictionaryProperties.info]) as _PdfDictionary?; if (info != null && _documentInfo == null) { _documentInfo = PdfDocumentInformation._(_catalog, dictionary: info, isLoaded: true, conformance: _conformanceLevel); } if (info != null && - !info.changed && - _crossTable.trailer[_DictionaryProperties.info] + !info.changed! && + _crossTable.trailer![_DictionaryProperties.info] is _PdfReferenceHolder) { _documentInfo = PdfDocumentInformation._(_catalog, dictionary: info, isLoaded: true, conformance: _conformanceLevel); - if (_objects._lookFor(_documentInfo._element) > -1) { + if (_catalog.changed!) { + _documentInfo!.modificationDate = DateTime.now(); + } + if (_objects._lookFor(_documentInfo!._element!)! > -1) { _objects._reregisterReference( - _objects._lookFor(info), _documentInfo._element); - _documentInfo._element.position = -1; + _objects._lookFor(info)!, _documentInfo!._element!); + _documentInfo!._element!.position = -1; } } } @@ -873,4 +999,116 @@ class PdfDocument { _catalog['OutputIntents'] = outputIntent; } } + + //Raises DocumentSaved event. + void _onDocumentSaved(_DocumentSavedArgs args) { + if (_documentSavedList != null && _documentSavedList!.isNotEmpty) { + for (int i = 0; i < _documentSavedList!.length; i++) { + _documentSavedList![i](this, args); + } + } + } + + void _setUserPassword(PdfPasswordArgs args) { + onPdfPassword!(this, args); + } +} + +/// Delegate for handling the PDF password +/// +/// ```dart +/// //Load an existing PDF document +/// PdfDocument document = +/// PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()) +/// //Subsribe the onPdfPassword event +/// ..onPdfPassword = loadOnPdfPassword; +/// //Access the attachments +/// PdfAttachmentCollection attachmentCollection = document.attachments; +/// //Iterates the attachments +/// for (int i = 0; i < attachmentCollection.count; i++) { +/// //Extracts the attachment and saves it to the disk +/// File(attachmentCollection[i].fileName) +/// .writeAsBytesSync(attachmentCollection[i].data); +/// } +/// //Disposes the document +/// document.dispose(); +/// +/// void loadOnPdfPassword(PdfDocument sender, PdfPasswordArgs args) { +/// //Sets the value of PDF password. +/// args.attachmentOpenPassword = 'syncfusion'; +/// } +/// ``` +typedef PdfPasswordCallback = void Function( + PdfDocument sender, PdfPasswordArgs args); + +/// Arguments of Pdf Password. +/// +/// ```dart +/// //Load an existing PDF document +/// PdfDocument document = +/// PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()) +/// //Subsribe the onPdfPassword event +/// ..onPdfPassword = loadOnPdfPassword; +/// //Access the attachments +/// PdfAttachmentCollection attachmentCollection = document.attachments; +/// //Iterates the attachments +/// for (int i = 0; i < attachmentCollection.count; i++) { +/// //Extracts the attachment and saves it to the disk +/// File(attachmentCollection[i].fileName) +/// .writeAsBytesSync(attachmentCollection[i].data); +/// } +/// //Disposes the document +/// document.dispose(); +/// +/// void loadOnPdfPassword(PdfDocument sender, PdfPasswordArgs args) { +/// //Sets the value of PDF password. +/// args.attachmentOpenPassword = 'syncfusion'; +/// } +/// ``` +class PdfPasswordArgs { + PdfPasswordArgs._() { + attachmentOpenPassword = ''; + } + //Fields + /// A value of PDF password. + /// + /// ```dart + /// //Load an existing PDF document + /// PdfDocument document = + /// PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()) + /// //Subsribe the onPdfPassword event + /// ..onPdfPassword = loadOnPdfPassword; + /// //Access the attachments + /// PdfAttachmentCollection attachmentCollection = document.attachments; + /// //Iterates the attachments + /// for (int i = 0; i < attachmentCollection.count; i++) { + /// //Extracts the attachment and saves it to the disk + /// File(attachmentCollection[i].fileName) + /// .writeAsBytesSync(attachmentCollection[i].data); + /// } + /// //Disposes the document + /// document.dispose(); + /// + /// void loadOnPdfPassword(PdfDocument sender, PdfPasswordArgs args) { + /// //Sets the value of PDF password. + /// args.attachmentOpenPassword = 'syncfusion'; + /// } + /// ``` + String? attachmentOpenPassword; +} + +typedef _DocumentSavedHandler = void Function( + Object sender, _DocumentSavedArgs args); + +class _DocumentSavedArgs { + //Constructor + _DocumentSavedArgs(_IPdfWriter writer) { + _writer = writer; + } + + //Fields + _IPdfWriter? _writer; + + //Properties + _IPdfWriter? get writer => _writer; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document_information.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document_information.dart index 019fd29a4..9a5a918d8 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document_information.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document_information.dart @@ -4,10 +4,9 @@ part of pdf; class PdfDocumentInformation extends _IPdfWrapper { //Constructor PdfDocumentInformation._(_PdfCatalog catalog, - {_PdfDictionary dictionary, + {_PdfDictionary? dictionary, bool isLoaded = false, - PdfConformanceLevel conformance}) { - ArgumentError.checkNotNull(catalog, 'catalog'); + PdfConformanceLevel? conformance}) { _catalog = catalog; if (conformance != null) { _conformance = conformance; @@ -18,34 +17,34 @@ class PdfDocumentInformation extends _IPdfWrapper { } else { _dictionary = _PdfDictionary(); if (_conformance != PdfConformanceLevel.a1b) { - _dictionary._setDateTime( - _DictionaryProperties.creationDate, _creationDate); + _dictionary! + ._setDateTime(_DictionaryProperties.creationDate, _creationDate); } } } //Fields - _PdfDictionary _dictionary; - _PdfCatalog _catalog; + _PdfDictionary? _dictionary; + _PdfCatalog? _catalog; DateTime _creationDate = DateTime.now(); DateTime _modificationDate = DateTime.now(); - String _title; - String _author; - String _subject; - String _keywords; - String _creator; - String _producer; - _XmpMetadata _xmp; + String? _title; + String? _author; + String? _subject; + String? _keywords; + String? _creator; + String? _producer; + _XmpMetadata? _xmp; bool _isRemoveModifyDate = false; - PdfConformanceLevel _conformance; + PdfConformanceLevel? _conformance; //Properties /// Gets the creation date of the PDF document DateTime get creationDate { - if (_dictionary.containsKey(_DictionaryProperties.creationDate) && - _dictionary[_DictionaryProperties.creationDate] is _PdfString) { - return _creationDate = _dictionary - ._getDateTime(_dictionary[_DictionaryProperties.creationDate]); + if (_dictionary!.containsKey(_DictionaryProperties.creationDate) && + _dictionary![_DictionaryProperties.creationDate] is _PdfString) { + return _creationDate = _dictionary!._getDateTime( + _dictionary![_DictionaryProperties.creationDate] as _PdfString); } return _creationDate = DateTime.now(); } @@ -54,17 +53,17 @@ class PdfDocumentInformation extends _IPdfWrapper { set creationDate(DateTime value) { if (_creationDate != value) { _creationDate = value; - _dictionary._setDateTime( - _DictionaryProperties.creationDate, _creationDate); + _dictionary! + ._setDateTime(_DictionaryProperties.creationDate, _creationDate); } } /// Gets the modification date of the PDF document DateTime get modificationDate { - if (_dictionary.containsKey(_DictionaryProperties.modificationDate) && - _dictionary[_DictionaryProperties.modificationDate] is _PdfString) { - return _modificationDate = _dictionary - ._getDateTime(_dictionary[_DictionaryProperties.modificationDate]); + if (_dictionary!.containsKey(_DictionaryProperties.modificationDate) && + _dictionary![_DictionaryProperties.modificationDate] is _PdfString) { + return _modificationDate = _dictionary!._getDateTime( + _dictionary![_DictionaryProperties.modificationDate] as _PdfString); } return _modificationDate = DateTime.now(); } @@ -72,16 +71,16 @@ class PdfDocumentInformation extends _IPdfWrapper { /// Sets the modification date of the PDF document set modificationDate(DateTime value) { _modificationDate = value; - _dictionary._setDateTime( + _dictionary!._setDateTime( _DictionaryProperties.modificationDate, _modificationDate); } /// Gets the title. String get title { - if (_dictionary.containsKey(_DictionaryProperties.title) && - _dictionary[_DictionaryProperties.title] is _PdfString) { - return _title = (_dictionary[_DictionaryProperties.title] as _PdfString) - .value + if (_dictionary!.containsKey(_DictionaryProperties.title) && + _dictionary![_DictionaryProperties.title] is _PdfString) { + return _title = (_dictionary![_DictionaryProperties.title] as _PdfString) + .value! .replaceAll('\u0000', ''); } return _title = ''; @@ -91,16 +90,16 @@ class PdfDocumentInformation extends _IPdfWrapper { set title(String value) { if (_title != value) { _title = value; - _dictionary._setString(_DictionaryProperties.title, _title); + _dictionary!._setString(_DictionaryProperties.title, _title); } } /// Gets the author. String get author { - if (_dictionary.containsKey(_DictionaryProperties.author) && - _dictionary[_DictionaryProperties.author] is _PdfString) { + if (_dictionary!.containsKey(_DictionaryProperties.author) && + _dictionary![_DictionaryProperties.author] is _PdfString) { return _author = - (_dictionary[_DictionaryProperties.author] as _PdfString).value; + (_dictionary![_DictionaryProperties.author] as _PdfString).value!; } return _author = ''; } @@ -109,16 +108,16 @@ class PdfDocumentInformation extends _IPdfWrapper { set author(String value) { if (_author != value) { _author = value; - _dictionary._setString(_DictionaryProperties.author, _author); + _dictionary!._setString(_DictionaryProperties.author, _author); } } /// Gets the subject. String get subject { - if (_dictionary.containsKey(_DictionaryProperties.subject) && - _dictionary[_DictionaryProperties.subject] is _PdfString) { + if (_dictionary!.containsKey(_DictionaryProperties.subject) && + _dictionary![_DictionaryProperties.subject] is _PdfString) { return _subject = - (_dictionary[_DictionaryProperties.subject] as _PdfString).value; + (_dictionary![_DictionaryProperties.subject] as _PdfString).value!; } return _subject = ''; } @@ -127,16 +126,16 @@ class PdfDocumentInformation extends _IPdfWrapper { set subject(String value) { if (_subject != value) { _subject = value; - _dictionary._setString(_DictionaryProperties.subject, _subject); + _dictionary!._setString(_DictionaryProperties.subject, _subject); } } /// Gets the keywords. String get keywords { - if (_dictionary.containsKey(_DictionaryProperties.keywords) && - _dictionary[_DictionaryProperties.keywords] is _PdfString) { + if (_dictionary!.containsKey(_DictionaryProperties.keywords) && + _dictionary![_DictionaryProperties.keywords] is _PdfString) { return _keywords = - (_dictionary[_DictionaryProperties.keywords] as _PdfString).value; + (_dictionary![_DictionaryProperties.keywords] as _PdfString).value!; } return _keywords = ''; } @@ -145,19 +144,19 @@ class PdfDocumentInformation extends _IPdfWrapper { set keywords(String value) { if (_keywords != value) { _keywords = value; - _dictionary._setString(_DictionaryProperties.keywords, _keywords); + _dictionary!._setString(_DictionaryProperties.keywords, _keywords); } - if (_catalog != null && _catalog._metadata != null) { + if (_catalog != null && _catalog!._metadata != null) { _xmp = _xmpMetadata; } } /// Gets the creator. String get creator { - if (_dictionary.containsKey(_DictionaryProperties.creator) && - _dictionary[_DictionaryProperties.creator] is _PdfString) { + if (_dictionary!.containsKey(_DictionaryProperties.creator) && + _dictionary![_DictionaryProperties.creator] is _PdfString) { return _creator = - (_dictionary[_DictionaryProperties.creator] as _PdfString).value; + (_dictionary![_DictionaryProperties.creator] as _PdfString).value!; } return _creator = ''; } @@ -166,16 +165,16 @@ class PdfDocumentInformation extends _IPdfWrapper { set creator(String value) { if (_creator != value) { _creator = value; - _dictionary._setString(_DictionaryProperties.creator, _creator); + _dictionary!._setString(_DictionaryProperties.creator, _creator); } } /// Gets the producer. String get producer { - if (_dictionary.containsKey(_DictionaryProperties.producer) && - _dictionary[_DictionaryProperties.producer] is _PdfString) { + if (_dictionary!.containsKey(_DictionaryProperties.producer) && + _dictionary![_DictionaryProperties.producer] is _PdfString) { return _producer = - (_dictionary[_DictionaryProperties.producer] as _PdfString).value; + (_dictionary![_DictionaryProperties.producer] as _PdfString).value!; } return _producer = ''; } @@ -184,34 +183,34 @@ class PdfDocumentInformation extends _IPdfWrapper { set producer(String value) { if (_producer != value) { _producer = value; - _dictionary._setString(_DictionaryProperties.producer, _producer); + _dictionary!._setString(_DictionaryProperties.producer, _producer); } } /// Gets Xmp metadata of the document. /// /// Represents the document information in Xmp format. - _XmpMetadata get _xmpMetadata { + _XmpMetadata? get _xmpMetadata { if (_xmp == null) { - if (_catalog._metadata == null && _catalog._pages != null) { - _xmp = _XmpMetadata(_catalog._pages._document.documentInformation); - _catalog.setProperty( + if (_catalog!._metadata == null && _catalog!._pages != null) { + _xmp = _XmpMetadata(_catalog!._pages!._document!.documentInformation); + _catalog!.setProperty( _DictionaryProperties.metadata, _PdfReferenceHolder(_xmp)); } else { - if (_dictionary.changed && !_catalog.changed) { - _xmp = _XmpMetadata(_catalog._document.documentInformation); - _catalog.setProperty( + if (_dictionary!.changed! && !_catalog!.changed!) { + _xmp = _XmpMetadata(_catalog!._document!.documentInformation); + _catalog!.setProperty( _DictionaryProperties.metadata, _PdfReferenceHolder(_xmp)); } else { - _xmp = _catalog._metadata; - _catalog.setProperty( + _xmp = _catalog!._metadata; + _catalog!.setProperty( _DictionaryProperties.metadata, _PdfReferenceHolder(_xmp)); } } - } else if (_catalog._metadata != null && _catalog._document != null) { - if (_dictionary.changed && !_catalog.changed) { - _xmp = _XmpMetadata(_catalog._document.documentInformation); - _catalog.setProperty( + } else if (_catalog!._metadata != null && _catalog!._document != null) { + if (_dictionary!.changed! && !_catalog!.changed!) { + _xmp = _XmpMetadata(_catalog!._document!.documentInformation); + _catalog!.setProperty( _DictionaryProperties.metadata, _PdfReferenceHolder(_xmp)); } } @@ -221,14 +220,14 @@ class PdfDocumentInformation extends _IPdfWrapper { /// Remove the modification date from existing document. void removeModificationDate() { if (_dictionary != null && - _dictionary.containsKey(_DictionaryProperties.modificationDate)) { - _dictionary.remove(_DictionaryProperties.modificationDate); - if (_dictionary.changed && !_catalog.changed) { - _catalog._document.documentInformation._dictionary + _dictionary!.containsKey(_DictionaryProperties.modificationDate)) { + _dictionary!.remove(_DictionaryProperties.modificationDate); + if (_dictionary!.changed! && !_catalog!.changed!) { + _catalog!._document!.documentInformation._dictionary! .remove(_DictionaryProperties.modificationDate); - _catalog._document.documentInformation._isRemoveModifyDate = true; - _xmp = _XmpMetadata(_catalog._document.documentInformation); - _catalog.setProperty( + _catalog!._document!.documentInformation._isRemoveModifyDate = true; + _xmp = _XmpMetadata(_catalog!._document!.documentInformation); + _catalog!.setProperty( _DictionaryProperties.metadata, _PdfReferenceHolder(_xmp)); } } @@ -236,5 +235,5 @@ class PdfDocumentInformation extends _IPdfWrapper { //Overrides @override - _IPdfPrimitive get _element => _dictionary; + _IPdfPrimitive? get _element => _dictionary; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document_template.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document_template.dart index 9517e5388..c4c1b7d57 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document_template.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document_template.dart @@ -4,132 +4,201 @@ part of pdf; class PdfDocumentTemplate { //Constructors /// Initializes a new instance of the [PdfDocumentTemplate] class. - PdfDocumentTemplate(); + PdfDocumentTemplate( + {PdfPageTemplateElement? left, + PdfPageTemplateElement? top, + PdfPageTemplateElement? right, + PdfPageTemplateElement? bottom, + PdfPageTemplateElement? evenLeft, + PdfPageTemplateElement? evenTop, + PdfPageTemplateElement? evenRight, + PdfPageTemplateElement? evenBottom, + PdfPageTemplateElement? oddLeft, + PdfPageTemplateElement? oddTop, + PdfPageTemplateElement? oddRight, + PdfPageTemplateElement? oddBottom, + PdfStampCollection? stamps}) { + _intialize(left, top, right, bottom, evenLeft, evenTop, evenRight, + evenBottom, oddLeft, oddTop, oddRight, oddBottom, stamps); + } //Fields - PdfPageTemplateElement _left; - PdfPageTemplateElement _top; - PdfPageTemplateElement _right; - PdfPageTemplateElement _bottom; - PdfPageTemplateElement _evenLeft; - PdfPageTemplateElement _evenTop; - PdfPageTemplateElement _evenRight; - PdfPageTemplateElement _evenBottom; - PdfPageTemplateElement _oddLeft; - PdfPageTemplateElement _oddTop; - PdfPageTemplateElement _oddRight; - PdfPageTemplateElement _oddBottom; - PdfStampCollection _stamps; + PdfPageTemplateElement? _left; + PdfPageTemplateElement? _top; + PdfPageTemplateElement? _right; + PdfPageTemplateElement? _bottom; + PdfPageTemplateElement? _evenLeft; + PdfPageTemplateElement? _evenTop; + PdfPageTemplateElement? _evenRight; + PdfPageTemplateElement? _evenBottom; + PdfPageTemplateElement? _oddLeft; + PdfPageTemplateElement? _oddTop; + PdfPageTemplateElement? _oddRight; + PdfPageTemplateElement? _oddBottom; + PdfStampCollection? _stamps; //Properties /// Gets a left page template. - PdfPageTemplateElement get left => _left; + PdfPageTemplateElement? get left => _left; /// Sets a left page template. - set left(PdfPageTemplateElement value) { + set left(PdfPageTemplateElement? value) { _left = _checkElement(value, _TemplateType.left); } /// Gets a right page template. - PdfPageTemplateElement get right => _right; + PdfPageTemplateElement? get right => _right; /// Sets a right page template. - set right(PdfPageTemplateElement value) { + set right(PdfPageTemplateElement? value) { _right = _checkElement(value, _TemplateType.right); } /// Gets a top page template. - PdfPageTemplateElement get top => _top; + PdfPageTemplateElement? get top => _top; /// Sets a top page template. - set top(PdfPageTemplateElement value) { + set top(PdfPageTemplateElement? value) { _top = _checkElement(value, _TemplateType.top); } /// Gets a bottom page template. - PdfPageTemplateElement get bottom => _bottom; + PdfPageTemplateElement? get bottom => _bottom; /// Sets a bottom page template. - set bottom(PdfPageTemplateElement value) { + set bottom(PdfPageTemplateElement? value) { _bottom = _checkElement(value, _TemplateType.bottom); } /// Gets a even left page template. - PdfPageTemplateElement get evenLeft => _evenLeft; + PdfPageTemplateElement? get evenLeft => _evenLeft; /// Sets a even left page template. - set evenLeft(PdfPageTemplateElement value) { + set evenLeft(PdfPageTemplateElement? value) { _evenLeft = _checkElement(value, _TemplateType.left); } /// Gets a even right page template. - PdfPageTemplateElement get evenRight => _evenRight; + PdfPageTemplateElement? get evenRight => _evenRight; /// Sets a even right page template. - set evenRight(PdfPageTemplateElement value) { + set evenRight(PdfPageTemplateElement? value) { _evenRight = _checkElement(value, _TemplateType.right); } /// Gets a even top page template. - PdfPageTemplateElement get evenTop => _evenTop; + PdfPageTemplateElement? get evenTop => _evenTop; /// Sets a even top page template. - set evenTop(PdfPageTemplateElement value) { + set evenTop(PdfPageTemplateElement? value) { _evenTop = _checkElement(value, _TemplateType.top); } /// Gets a even bottom page template. - PdfPageTemplateElement get evenBottom => _evenBottom; + PdfPageTemplateElement? get evenBottom => _evenBottom; /// Sets a even bottom page template. - set evenBottom(PdfPageTemplateElement value) { + set evenBottom(PdfPageTemplateElement? value) { _evenBottom = _checkElement(value, _TemplateType.bottom); } /// Gets a odd left page template. - PdfPageTemplateElement get oddLeft => _oddLeft; + PdfPageTemplateElement? get oddLeft => _oddLeft; /// Sets a odd left page template. - set oddLeft(PdfPageTemplateElement value) { + set oddLeft(PdfPageTemplateElement? value) { _oddLeft = _checkElement(value, _TemplateType.left); } /// Gets a odd right page template. - PdfPageTemplateElement get oddRight => _oddRight; + PdfPageTemplateElement? get oddRight => _oddRight; /// Sets a odd right page template. - set oddRight(PdfPageTemplateElement value) { + set oddRight(PdfPageTemplateElement? value) { _oddRight = _checkElement(value, _TemplateType.right); } /// Gets a odd top page template. - PdfPageTemplateElement get oddTop => _oddTop; + PdfPageTemplateElement? get oddTop => _oddTop; /// Sets a odd top page template. - set oddTop(PdfPageTemplateElement value) { + set oddTop(PdfPageTemplateElement? value) { _oddTop = _checkElement(value, _TemplateType.top); } /// Gets a odd bottom page template. - PdfPageTemplateElement get oddBottom => _oddBottom; + PdfPageTemplateElement? get oddBottom => _oddBottom; /// Sets a odd bottom page template. - set oddBottom(PdfPageTemplateElement value) { + set oddBottom(PdfPageTemplateElement? value) { _oddBottom = _checkElement(value, _TemplateType.bottom); } /// Gets a collection of stamp elements. PdfStampCollection get stamps { _stamps ??= PdfStampCollection(); - return _stamps; + return _stamps!; } //Implementation - PdfPageTemplateElement _getLeft(PdfPage page) { - ArgumentError.checkNotNull(page, 'page'); - PdfPageTemplateElement template; - if (page._document.pages != null && - (evenLeft != null || oddLeft != null || left != null)) { + void _intialize( + PdfPageTemplateElement? left, + PdfPageTemplateElement? top, + PdfPageTemplateElement? right, + PdfPageTemplateElement? bottom, + PdfPageTemplateElement? evenLeft, + PdfPageTemplateElement? evenTop, + PdfPageTemplateElement? evenRight, + PdfPageTemplateElement? evenBottom, + PdfPageTemplateElement? oddLeft, + PdfPageTemplateElement? oddTop, + PdfPageTemplateElement? oddRight, + PdfPageTemplateElement? oddBottom, + PdfStampCollection? stamps) { + if (left != null) { + this.left = left; + } + if (top != null) { + this.top = top; + } + if (bottom != null) { + this.bottom = bottom; + } + if (right != null) { + this.right = right; + } + if (evenLeft != null) { + this.evenLeft = evenLeft; + } + if (evenTop != null) { + this.evenTop = evenTop; + } + if (evenRight != null) { + this.evenRight = evenRight; + } + if (evenBottom != null) { + this.evenBottom = evenBottom; + } + if (oddLeft != null) { + this.oddLeft = oddLeft; + } + if (oddTop != null) { + this.oddTop = oddTop; + } + if (oddRight != null) { + this.oddRight = oddRight; + } + if (oddBottom != null) { + this.oddBottom = oddBottom; + } + if (stamps != null) { + _stamps = stamps; + } + } + + PdfPageTemplateElement? _getLeft(PdfPage page) { + PdfPageTemplateElement? template; + if (evenLeft != null || oddLeft != null || left != null) { if (_isEven(page)) { template = (evenLeft != null) ? evenLeft : left; } else { @@ -139,11 +208,9 @@ class PdfDocumentTemplate { return template; } - PdfPageTemplateElement _getRight(PdfPage page) { - ArgumentError.checkNotNull(page, 'page'); - PdfPageTemplateElement template; - if (page._document.pages != null && - (evenRight != null || oddRight != null || right != null)) { + PdfPageTemplateElement? _getRight(PdfPage page) { + PdfPageTemplateElement? template; + if (evenRight != null || oddRight != null || right != null) { if (_isEven(page)) { template = (evenRight != null) ? evenRight : right; } else { @@ -153,11 +220,9 @@ class PdfDocumentTemplate { return template; } - PdfPageTemplateElement _getTop(PdfPage page) { - ArgumentError.checkNotNull(page, 'page'); - PdfPageTemplateElement template; - if (page._document.pages != null && - (evenTop != null || oddTop != null || top != null)) { + PdfPageTemplateElement? _getTop(PdfPage page) { + PdfPageTemplateElement? template; + if (evenTop != null || oddTop != null || top != null) { if (_isEven(page)) { template = (evenTop != null) ? evenTop : top; } else { @@ -167,11 +232,9 @@ class PdfDocumentTemplate { return template; } - PdfPageTemplateElement _getBottom(PdfPage page) { - ArgumentError.checkNotNull(page, 'page'); - PdfPageTemplateElement template; - if (page._document.pages != null && - (evenBottom != null || oddBottom != null || bottom != null)) { + PdfPageTemplateElement? _getBottom(PdfPage page) { + PdfPageTemplateElement? template; + if (evenBottom != null || oddBottom != null || bottom != null) { if (_isEven(page)) { template = (evenBottom != null) ? evenBottom : bottom; } else { @@ -182,19 +245,18 @@ class PdfDocumentTemplate { } bool _isEven(PdfPage page) { - ArgumentError.checkNotNull(page, 'page'); - final PdfPageCollection pages = page._section._document.pages; + final PdfPageCollection pages = page._section!._document!.pages; int index = 0; if (pages._pageCollectionIndex.containsKey(page)) { - index = pages._pageCollectionIndex[page] + 1; + index = pages._pageCollectionIndex[page]! + 1; } else { index = pages.indexOf(page) + 1; } return (index % 2) == 0; } - PdfPageTemplateElement _checkElement( - PdfPageTemplateElement templateElement, _TemplateType type) { + PdfPageTemplateElement? _checkElement( + PdfPageTemplateElement? templateElement, _TemplateType type) { if (templateElement != null) { if (templateElement._type != _TemplateType.none) { throw ArgumentError.value(type, @@ -214,7 +276,6 @@ class PdfStampCollection extends PdfObjectCollection { /// Adds a stamp element to the collection. /// [PdfPageTemplateElement] used here to create stamp element. int add(PdfPageTemplateElement template) { - ArgumentError.checkNotNull(template, 'template'); _list.add(template); return count - 1; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_file_structure.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_file_structure.dart index bf7b4bd63..639a9fa68 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_file_structure.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_file_structure.dart @@ -23,13 +23,15 @@ class PdfFileStructure { _version = PdfVersion.version1_7; crossReferenceType = PdfCrossReferenceType.crossReferenceTable; incrementalUpdate = true; + _fileID = false; } //Fields - PdfVersion _version; + late PdfVersion _version; + late bool _fileID; /// Gets or sets a value indicating whether incremental update. - bool incrementalUpdate; + late bool incrementalUpdate; /// The type of PDF cross-reference. /// @@ -47,7 +49,7 @@ class PdfFileStructure { /// List bytes = document.save(); /// document.dispose(); /// ``` - PdfCrossReferenceType crossReferenceType; + late PdfCrossReferenceType crossReferenceType; //Properties /// Gets the version of the PDF document. diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_array.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_array.dart index e37916e2c..d8a50b8d2 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_array.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_array.dart @@ -2,16 +2,15 @@ part of pdf; class _PdfArray implements _IPdfPrimitive, _IPdfChangable { _PdfArray([dynamic data]) { - _elements ??= <_IPdfPrimitive>[]; if (data != null) { if (data is _PdfArray) { _elements.addAll(data._elements); - } else if (data is List) { + } else if (data is List || data is List) { for (final num entry in data) { final _PdfNumber pdfNumber = _PdfNumber(entry); _elements.add(pdfNumber); } - } else if (data is List<_PdfArray>) { + } else if (data is List<_PdfArray> || data is List<_PdfArray?>) { data.forEach(_elements.add); } } @@ -22,33 +21,30 @@ class _PdfArray implements _IPdfPrimitive, _IPdfChangable { static const String endMark = ']'; //Fields - List<_IPdfPrimitive> _elements; - bool _isChanged; - bool _isSaving; - int _objectCollectionIndex; - int _position; - _ObjectStatus _status; - _PdfArray _clonedObject; - _PdfCrossTable _crossTable; + List<_IPdfPrimitive?> _elements = <_IPdfPrimitive?>[]; + bool? _isChanged; + bool? _isSaving; + int? _objectCollectionIndex; + int? _position; + _ObjectStatus? _status; + _PdfArray? _clonedObject; + _PdfCrossTable? _crossTable; //Properties - _IPdfPrimitive operator [](int index) => _getElement(index); - _IPdfPrimitive _getElement(int index) { + _IPdfPrimitive? operator [](int index) => _getElement(index); + _IPdfPrimitive? _getElement(int index) { return _elements[index]; } void _add(_IPdfPrimitive element) { - ArgumentError.checkNotNull(element, 'element'); _elements.add(element); } bool _contains(_IPdfPrimitive element) { - ArgumentError.checkNotNull(element, 'element'); return _elements.contains(element); } void _insert(int index, _IPdfPrimitive element) { - ArgumentError.checkNotNull(element, 'element'); if (index > _elements.length) { throw ArgumentError.value('index out of range $index'); } else if (index == _elements.length) { @@ -66,7 +62,6 @@ class _PdfArray implements _IPdfPrimitive, _IPdfChangable { } int _indexOf(_IPdfPrimitive element) { - ArgumentError.checkNotNull(element, 'element'); return _elements.indexOf(element); } @@ -74,7 +69,7 @@ class _PdfArray implements _IPdfPrimitive, _IPdfChangable { //Static methods static _PdfArray fromRectangle(_Rectangle rectangle) { - final List list = [ + final List list = [ rectangle.left, rectangle.top, rectangle.right, @@ -90,13 +85,13 @@ class _PdfArray implements _IPdfPrimitive, _IPdfChangable { } double x1, x2, y1, y2; _PdfNumber number = _getNumber(0); - x1 = number.value.toDouble(); + x1 = number.value!.toDouble(); number = _getNumber(1); - y1 = number.value.toDouble(); + y1 = number.value!.toDouble(); number = _getNumber(2); - x2 = number.value.toDouble(); + x2 = number.value!.toDouble(); number = _getNumber(3); - y2 = number.value.toDouble(); + y2 = number.value!.toDouble(); final double x = [x1, x2].reduce(min); final double y = [y1, y2].reduce(min); final double width = (x1 - x2).abs(); @@ -107,8 +102,8 @@ class _PdfArray implements _IPdfPrimitive, _IPdfChangable { // Gets the number from the array. _PdfNumber _getNumber(int index) { - final _PdfNumber number = - _PdfCrossTable._dereference(this[index]) as _PdfNumber; + final _PdfNumber? number = + _PdfCrossTable._dereference(this[index]) as _PdfNumber?; if (number == null) { throw ArgumentError('Can\'t convert to rectangle.'); } @@ -117,73 +112,74 @@ class _PdfArray implements _IPdfPrimitive, _IPdfChangable { //_IPdfPrimitive members @override - bool get changed { + bool? get changed { _isChanged ??= false; return _isChanged; } @override - set changed(bool value) { + set changed(bool? value) { _isChanged = value; } @override - _IPdfPrimitive clonedObject; + _IPdfPrimitive? clonedObject; @override - bool get isSaving { + bool? get isSaving { _isSaving ??= false; return _isSaving; } @override - set isSaving(bool value) { + set isSaving(bool? value) { _isSaving = value; } @override - int get objectCollectionIndex { + int? get objectCollectionIndex { _objectCollectionIndex ??= 0; return _objectCollectionIndex; } @override - set objectCollectionIndex(int value) { + set objectCollectionIndex(int? value) { _objectCollectionIndex = value; } @override - int get position { + int? get position { _position ??= -1; return _position; } @override - set position(int value) { + set position(int? value) { _position = value; } @override - _ObjectStatus get status { + _ObjectStatus? get status { _status ??= _ObjectStatus.none; return _status; } @override - set status(_ObjectStatus value) { + set status(_ObjectStatus? value) { _status = value; } @override - void save(_IPdfWriter writer) { - ArgumentError.checkNotNull(writer, 'writer'); - writer._write(startMark); - for (int i = 0; i < count; i++) { - this[i].save(writer); - if (i + 1 != count) { - writer._write(_Operators.whiteSpace); + void save(_IPdfWriter? writer) { + if (writer != null) { + writer._write(startMark); + for (int i = 0; i < count; i++) { + this[i]!.save(writer); + if (i + 1 != count) { + writer._write(_Operators.whiteSpace); + } } + writer._write(endMark); } - writer._write(endMark); } void _removeAt(int index) { @@ -192,18 +188,16 @@ class _PdfArray implements _IPdfPrimitive, _IPdfChangable { } void _remove(_IPdfPrimitive element) { - ArgumentError.checkNotNull(element, 'element cannot be null'); final bool hasRemoved = _elements.remove(element); if (_elements.isNotEmpty) { - _isChanged |= hasRemoved; + changed = changed! | hasRemoved; } } @override void dispose() { - if (_elements != null) { + if (_elements.isNotEmpty) { _elements.clear(); - _elements = null; } if (_status != null) { _status = null; @@ -212,22 +206,22 @@ class _PdfArray implements _IPdfPrimitive, _IPdfChangable { //_IPdfChangable members @override - void freezeChanges(Object freezer) { + void freezeChanges(Object? freezer) { if (freezer is _PdfParser || freezer is _PdfDictionary) { _isChanged = false; } } @override - _IPdfPrimitive _clone(_PdfCrossTable crossTable) { - if (_clonedObject != null && _clonedObject._crossTable == crossTable) { + _IPdfPrimitive? _clone(_PdfCrossTable crossTable) { + if (_clonedObject != null && _clonedObject!._crossTable == crossTable) { return _clonedObject; } else { _clonedObject = null; } final _PdfArray newArray = _PdfArray(); - for (final _IPdfPrimitive obj in _elements) { - newArray._add(obj._clone(crossTable)); + for (final _IPdfPrimitive? obj in _elements) { + newArray._add(obj!._clone(crossTable)!); } newArray._crossTable = crossTable; _clonedObject = newArray; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_boolean.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_boolean.dart index e32c2b281..c16bc4e09 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_boolean.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_boolean.dart @@ -1,70 +1,70 @@ part of pdf; class _PdfBoolean implements _IPdfPrimitive { - _PdfBoolean([bool v]) { + _PdfBoolean([bool? v]) { if (v != null) { value = v; } } //Fields - bool value = false; - bool _isSaving; - int _objectCollectionIndex; - int _position; - _ObjectStatus _status; + bool? value = false; + bool? _isSaving; + int? _objectCollectionIndex; + int? _position; + _ObjectStatus? _status; //_IPdfPrimitive members @override - _IPdfPrimitive clonedObject; + _IPdfPrimitive? clonedObject; @override - bool get isSaving { + bool? get isSaving { _isSaving ??= false; return _isSaving; } @override - set isSaving(bool value) { + set isSaving(bool? value) { _isSaving = value; } @override - int get objectCollectionIndex { + int? get objectCollectionIndex { _objectCollectionIndex ??= 0; return _objectCollectionIndex; } @override - set objectCollectionIndex(int value) { + set objectCollectionIndex(int? value) { _objectCollectionIndex = value; } @override - int get position { + int? get position { _position ??= -1; return _position; } @override - set position(int value) { + set position(int? value) { _position = value; } @override - _ObjectStatus get status { + _ObjectStatus? get status { _status ??= _ObjectStatus.none; return _status; } @override - set status(_ObjectStatus value) { + set status(_ObjectStatus? value) { _status = value; } @override - void save(_IPdfWriter writer) { - writer._write(value ? 'true' : 'false'); + void save(_IPdfWriter? writer) { + writer!._write(value! ? 'true' : 'false'); } @override diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_dictionary.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_dictionary.dart index 92ed8602a..428365eb7 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_dictionary.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_dictionary.dart @@ -2,8 +2,8 @@ part of pdf; class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { /// Constructor to create a [_PdfDictionary] object. - _PdfDictionary([_PdfDictionary dictionary]) { - _items = <_PdfName, _IPdfPrimitive>{}; + _PdfDictionary([_PdfDictionary? dictionary]) { + _items = <_PdfName?, _IPdfPrimitive?>{}; _copyDictionary(dictionary); _encrypt = true; decrypted = false; @@ -14,20 +14,20 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { static const String suffix = '>>'; //Fields - Map<_PdfName, _IPdfPrimitive> _items; - bool _isChanged; - bool _isSaving; - int _objectCollectionIndex; - int _position; - _ObjectStatus _status; - _PdfCrossTable _crossTable; + Map<_PdfName?, _IPdfPrimitive?>? _items; + bool? _isChanged; + bool? _isSaving; + int? _objectCollectionIndex; + int? _position; + _ObjectStatus? _status; + _PdfCrossTable? _crossTable; bool _archive = true; - bool _encrypt; - bool decrypted; + bool? _encrypt; + bool? decrypted; //Properties /// Get the PdfDictionary items. - _IPdfPrimitive operator [](dynamic key) => returnValue(checkName(key)); + _IPdfPrimitive? operator [](dynamic key) => returnValue(checkName(key)); /// Set the PdfDictionary items. operator []=(dynamic key, dynamic value) => _addItems(key, value); @@ -39,35 +39,35 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { if (value == null) { throw ArgumentError.value(value, 'value', 'value cannot be null'); } - _items[checkName(key)] = value; + _items![checkName(key)] = value; modify(); return value; } /// Get the length of the item. - int get count => _items.length; + int get count => _items!.length; /// Get the values of the item. - List<_IPdfPrimitive> get value => _items.values; + List<_IPdfPrimitive?> get value => _items!.values as List<_IPdfPrimitive?>; - bool get encrypt => _encrypt; + bool? get encrypt => _encrypt; - set encrypt(bool value) { + set encrypt(bool? value) { _encrypt = value; modify(); } //Implementation - void _copyDictionary(_PdfDictionary dictionary) { + void _copyDictionary(_PdfDictionary? dictionary) { if (dictionary != null) { - dictionary._items - .forEach((_PdfName k, _IPdfPrimitive v) => _addItems(k, v)); + dictionary._items! + .forEach((_PdfName? k, _IPdfPrimitive? v) => _addItems(k, v)); freezeChanges(this); } } /// Check and return the valid name. - _PdfName checkName(dynamic key) { + _PdfName? checkName(dynamic key) { if (key is _PdfName) { return key; } else if (key is String) { @@ -78,9 +78,9 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { } /// Check key and return the value. - _IPdfPrimitive returnValue(dynamic key) { - if (_items.containsKey(key)) { - return _items[key]; + _IPdfPrimitive? returnValue(dynamic key) { + if (_items!.containsKey(key)) { + return _items![key]; } else { return null; } @@ -88,9 +88,9 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { bool containsKey(dynamic key) { if (key is String) { - return _items.containsKey(_PdfName(key)); + return _items!.containsKey(_PdfName(key)); } else if (key is _PdfName) { - return _items.containsKey(key); + return _items!.containsKey(key); } return false; } @@ -100,12 +100,12 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { throw ArgumentError.value(key, 'key', 'value cannot be null'); } final _PdfName name = key is _PdfName ? key : _PdfName(key); - _items.remove(name); + _items!.remove(name); modify(); } void clear() { - _items.clear(); + _items!.clear(); modify(); } @@ -116,9 +116,9 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { void setProperty(dynamic key, dynamic value) { if (value == null) { if (key is String) { - _items.remove(_PdfName(key)); + _items!.remove(_PdfName(key)); } else if (key is _PdfName) { - _items.remove(key); + _items!.remove(key); } } else { if (value is _IPdfWrapper) { @@ -129,8 +129,8 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { modify(); } - void _setName(_PdfName key, String name) { - if (_items.containsKey(key)) { + void _setName(_PdfName key, String? name) { + if (_items!.containsKey(key)) { this[key] = _PdfName(name); modify(); } else { @@ -139,7 +139,7 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { } void _setArray(String key, List<_IPdfPrimitive> list) { - _PdfArray pdfArray = this[key] as _PdfArray; + _PdfArray? pdfArray = this[key] as _PdfArray?; if (pdfArray != null) { pdfArray._clear(); modify(); @@ -152,13 +152,13 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { } } - void _setString(String key, String str) { - final _PdfString pdfString = this[key] as _PdfString; + void _setString(String key, String? str) { + final _PdfString? pdfString = this[key] as _PdfString?; if (pdfString != null) { pdfString.value = str; modify(); } else { - this[key] = _PdfString(str); + this[key] = _PdfString(str!); } } @@ -169,13 +169,13 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { _onBeginSave(args); } if (count > 0) { - final _PdfEncryptor encryptor = writer._document.security._encryptor; + final _PdfEncryptor encryptor = writer._document!.security._encryptor; final bool state = encryptor.encrypt; - if (!_encrypt) { + if (!_encrypt!) { encryptor.encrypt = false; } _saveItems(writer); - if (!_encrypt) { + if (!_encrypt!) { encryptor.encrypt = state; } } @@ -189,20 +189,83 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { void _saveItems(_IPdfWriter writer) { writer._write(_Operators.newLine); - _items.forEach((_PdfName key, _IPdfPrimitive value) { - key.save(writer); + _items!.forEach((_PdfName? key, _IPdfPrimitive? value) { + key!.save(writer); writer._write(_Operators.whiteSpace); - value.save(writer); + final _PdfName name = key; + if (name._name == 'Fields') { + final _IPdfPrimitive? fields = value; + final List<_PdfReferenceHolder> fieldCollection = + <_PdfReferenceHolder>[]; + if (fields is _PdfArray) { + for (int k = 0; k < fields.count; k++) { + final _PdfReferenceHolder fieldReference = + fields._elements[k]! as _PdfReferenceHolder; + fieldCollection.add(fieldReference); + } + for (int i = 0; i < fields.count; i++) { + if (fields._elements[i]! is _PdfReferenceHolder) { + final _PdfReferenceHolder refHolder = + fields._elements[i]! as _PdfReferenceHolder; + final _PdfDictionary? field = + refHolder._object as _PdfDictionary?; + if (field != null) { + if (field._beginSave != null) { + final _SavePdfPrimitiveArgs args = + _SavePdfPrimitiveArgs(writer); + field._beginSave!(field, args); + } + if (!field.containsKey(_PdfName(_DictionaryProperties.kids))) { + if (field._items! + .containsKey(_PdfName(_DictionaryProperties.ft))) { + final _IPdfPrimitive? value = + field._items![_PdfName(_DictionaryProperties.ft)]; + if (value != null && + value is _PdfName && + value._name == 'Sig') { + for (int k = 0; k < fields.count; k++) { + if (k == i) { + continue; + } + final _PdfReferenceHolder fieldRef = + fields._elements[k]! as _PdfReferenceHolder; + final _PdfDictionary field1 = + fieldRef.object as _PdfDictionary; + if (field1._items!.containsKey( + _PdfName(_DictionaryProperties.t)) && + field._items!.containsKey( + _PdfName(_DictionaryProperties.t))) { + final _PdfString parentSignatureName = + field1._items![_PdfName(_DictionaryProperties.t)] + as _PdfString; + final _PdfString childName = + field._items![_PdfName(_DictionaryProperties.t)] + as _PdfString; + if (parentSignatureName.value == childName.value) { + fields._remove(refHolder); + } + } + } + } + } + } + } + } + } + value = fields; + } + } + value!.save(writer); writer._write(_Operators.newLine); }); } - _IPdfPrimitive _getValue(String key, String parentKey) { - _PdfDictionary dictionary = this; - _IPdfPrimitive element = _PdfCrossTable._dereference(dictionary[key]); + _IPdfPrimitive? _getValue(String key, String parentKey) { + _PdfDictionary? dictionary = this; + _IPdfPrimitive? element = _PdfCrossTable._dereference(dictionary[key]); while (element == null) { - dictionary = - _PdfCrossTable._dereference(dictionary[parentKey]) as _PdfDictionary; + dictionary = _PdfCrossTable._dereference(dictionary![parentKey]) + as _PdfDictionary?; if (dictionary == null) { break; } @@ -212,26 +275,26 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { } int _getInt(String propertyName) { - final _IPdfPrimitive primitive = + final _IPdfPrimitive? primitive = _PdfCrossTable._dereference(this[propertyName]); return (primitive != null && primitive is _PdfNumber) - ? primitive.value.toInt() + ? primitive.value!.toInt() : 0; } - _PdfString _getString(String propertyName) { - final _IPdfPrimitive primitive = + _PdfString? _getString(String propertyName) { + final _IPdfPrimitive? primitive = _PdfCrossTable._dereference(this[propertyName]); return (primitive != null && primitive is _PdfString) ? primitive : null; } bool _checkChanges() { bool result = false; - final List<_PdfName> keys = _items.keys.toList(); + final List<_PdfName?> keys = _items!.keys.toList(); for (int i = 0; i < keys.length; i++) { - final _IPdfPrimitive primitive = _items[keys[i]]; + final _IPdfPrimitive? primitive = _items![keys[i]]; if (primitive is _IPdfChangable && - (primitive as _IPdfChangable).changed) { + (primitive as _IPdfChangable).changed!) { result = true; break; } @@ -239,18 +302,18 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { return result; } - void _setNumber(String key, int value) { - final _PdfNumber pdfNumber = this[key] as _PdfNumber; + void _setNumber(String key, int? value) { + final _PdfNumber? pdfNumber = this[key] as _PdfNumber?; if (pdfNumber != null) { pdfNumber.value = value; modify(); } else { - this[key] = _PdfNumber(value); + this[key] = _PdfNumber(value!); } } - void _setBoolean(String key, bool value) { - final _PdfBoolean pdfBoolean = this[key] as _PdfBoolean; + void _setBoolean(String key, bool? value) { + final _PdfBoolean? pdfBoolean = this[key] as _PdfBoolean?; if (pdfBoolean != null) { pdfBoolean.value = value; modify(); @@ -260,7 +323,6 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { } void _setDateTime(String key, DateTime dateTime) { - final _PdfString pdfString = this[key] as _PdfString; final DateFormat dateFormat = DateFormat('yyyyMMddHHmmss'); final int regionMinutes = dateTime.timeZoneOffset.inMinutes ~/ 11; String offsetMinutes = regionMinutes.toString(); @@ -272,9 +334,10 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { if (regionHours >= 0 && regionHours <= 9) { offsetHours = '0' + offsetHours; } - if (pdfString != null) { - pdfString.value = - "D:${dateFormat.format(dateTime)}+$offsetHours'+$offsetMinutes'"; + final _IPdfPrimitive? primitive = this[key]; + if (primitive != null && primitive is _PdfString) { + primitive.value = + "D:${dateFormat.format(dateTime)}+$offsetHours'$offsetMinutes'"; modify(); } else { this[key] = _PdfString('D:' + @@ -289,33 +352,32 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { // Gets the date time from Pdf standard date format. DateTime _getDateTime(_PdfString dateTimeStringValue) { - ArgumentError.checkNotNull(dateTimeStringValue, 'dateTimeStringValue'); const String prefixD = 'D:'; - final _PdfString dateTimeString = _PdfString(dateTimeStringValue.value); - String value = dateTimeString.value; + final _PdfString dateTimeString = _PdfString(dateTimeStringValue.value!); + String value = dateTimeString.value!; while (value.startsWith(RegExp('[:-D-(-)]'))) { dateTimeString.value = value.replaceFirst(value[0], ''); - value = dateTimeString.value; + value = dateTimeString.value!; } while (value[value.length - 1].contains(RegExp('[:-D-(-)]'))) { dateTimeString.value = value.replaceRange(value.length - 1, value.length, ''); } - if (dateTimeString.value.startsWith('191')) { - dateTimeString.value = dateTimeString.value.replaceFirst('191', '20'); + if (dateTimeString.value!.startsWith('191')) { + dateTimeString.value = dateTimeString.value!.replaceFirst('191', '20'); } - final bool containPrefixD = dateTimeString.value.contains(prefixD); + final bool containPrefixD = dateTimeString.value!.contains(prefixD); final String dateTimeFormat = 'yyyyMMddHHmmss'; dateTimeString.value = - dateTimeString.value.padRight(dateTimeFormat.length, '0'); + dateTimeString.value!.padRight(dateTimeFormat.length, '0'); String localTime = ''.padRight(dateTimeFormat.length); - if (dateTimeString.value.isEmpty) { + if (dateTimeString.value!.isEmpty) { return DateTime.now(); } - if (dateTimeString.value.length >= localTime.length) { + if (dateTimeString.value!.length >= localTime.length) { localTime = containPrefixD - ? dateTimeString.value.substring(prefixD.length, localTime.length) - : dateTimeString.value.substring(0, localTime.length); + ? dateTimeString.value!.substring(prefixD.length, localTime.length) + : dateTimeString.value!.substring(0, localTime.length); } final String dateWithT = localTime.substring(0, 8) + 'T' + localTime.substring(8); @@ -329,16 +391,16 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { //_IPdfChangable members @override - bool get changed { + bool? get changed { _isChanged ??= false; - if (!_isChanged) { + if (!_isChanged!) { _isChanged = _checkChanges(); } return _isChanged; } @override - set changed(bool value) { + set changed(bool? value) { _isChanged = value; } @@ -351,65 +413,66 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { //_IPdfPrimitive members @override - _IPdfPrimitive clonedObject; + _IPdfPrimitive? clonedObject; @override - bool get isSaving { + bool? get isSaving { _isSaving ??= false; return _isSaving; } @override - set isSaving(bool value) { + set isSaving(bool? value) { _isSaving = value; } @override - int get objectCollectionIndex { + int? get objectCollectionIndex { _objectCollectionIndex ??= 0; return _objectCollectionIndex; } @override - set objectCollectionIndex(int value) { + set objectCollectionIndex(int? value) { _objectCollectionIndex = value; } @override - int get position { + int? get position { _position ??= -1; return _position; } @override - set position(int value) { + set position(int? value) { _position = value; } @override - _ObjectStatus get status { + _ObjectStatus? get status { _status ??= _ObjectStatus.none; return _status; } @override - set status(_ObjectStatus value) { + set status(_ObjectStatus? value) { _status = value; } @override - void save(_IPdfWriter writer) { - _saveDictionary(writer, true); + void save(_IPdfWriter? writer) { + _saveDictionary(writer!, true); } @override void dispose() { - if (_items != null && _items.isNotEmpty) { - final List<_IPdfPrimitive> primitives = _items.keys.toList(); + if (_items != null && _items!.isNotEmpty) { + final List<_IPdfPrimitive?> primitives = _items!.keys.toList(); for (int i = 0; i < primitives.length; i++) { - _items[primitives[i]].dispose(); + final _PdfName? key = primitives[i] as _PdfName?; + _items![key!]!.dispose(); } - _items.clear(); + _items!.clear(); _items = null; } if (_status != null) { @@ -418,31 +481,29 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { } //Events - _SavePdfPrimitiveCallback _beginSave; - List<_SavePdfPrimitiveCallback> _beginSaveList; - _SavePdfPrimitiveCallback _endSave; + _SavePdfPrimitiveCallback? _beginSave; + List<_SavePdfPrimitiveCallback>? _beginSaveList; + _SavePdfPrimitiveCallback? _endSave; void _onBeginSave(_SavePdfPrimitiveArgs args) { if (_beginSave != null) { - _beginSave(this, args); + _beginSave!(this, args); } if (_beginSaveList != null) { - for (int i = 0; i < _beginSaveList.length; i++) { - if (_beginSaveList[i] != null) { - _beginSaveList[i](this, args); - } + for (int i = 0; i < _beginSaveList!.length; i++) { + _beginSaveList![i](this, args); } } } void _onEndSave(_SavePdfPrimitiveArgs args) { if (_endSave != null) { - _endSave(this, args); + _endSave!(this, args); } } @override - _IPdfPrimitive _clone(_PdfCrossTable crossTable) { + _IPdfPrimitive? _clone(_PdfCrossTable crossTable) { if (!(this is _PdfStream)) { if (clonedObject != null && (clonedObject is _PdfDictionary == true) && @@ -453,10 +514,10 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { } } final _PdfDictionary newDict = _PdfDictionary(); - _items.forEach((_PdfName key, _IPdfPrimitive value) { - final _PdfName name = key; - final _IPdfPrimitive obj = value; - final _IPdfPrimitive newObj = obj._clone(crossTable); + _items!.forEach((_PdfName? key, _IPdfPrimitive? value) { + final _PdfName? name = key; + final _IPdfPrimitive obj = value!; + final _IPdfPrimitive? newObj = obj._clone(crossTable); if (!(newObj is _PdfNull)) { newDict[name] = newObj; } @@ -474,7 +535,7 @@ class _PdfDictionary implements _IPdfPrimitive, _IPdfChangable { } class _SavePdfPrimitiveArgs { - _SavePdfPrimitiveArgs(_IPdfWriter writer) { + _SavePdfPrimitiveArgs(_IPdfWriter? writer) { if (writer == null) { throw ArgumentError.notNull('writer'); } else { @@ -482,10 +543,10 @@ class _SavePdfPrimitiveArgs { } } - _IPdfWriter _writer; + _IPdfWriter? _writer; - _IPdfWriter get writer => _writer; + _IPdfWriter? get writer => _writer; } typedef _SavePdfPrimitiveCallback = void Function( - Object sender, _SavePdfPrimitiveArgs args); + Object sender, _SavePdfPrimitiveArgs? args); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_name.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_name.dart index 02d95add9..e7692edf1 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_name.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_name.dart @@ -9,15 +9,14 @@ class _PdfName implements _IPdfPrimitive { final List _replacements = [32, 9, 10, 13]; //Fields - final String _name; - bool _isSaving; - int _objectCollectionIndex; - int _position; - _ObjectStatus _status; + final String? _name; + bool? _isSaving; + int? _objectCollectionIndex; + int? _position; + _ObjectStatus? _status; //Implementation String _escapeString(String value) { - ArgumentError.checkNotNull(value, 'value'); if (value.isEmpty) { throw ArgumentError.value(value, 'empty string'); } else { @@ -38,7 +37,7 @@ class _PdfName implements _IPdfPrimitive { @override String toString() { - return stringStartMark + _escapeString(_name); + return stringStartMark + _escapeString(_name!); } //_IPdfPrimitive members @@ -51,55 +50,55 @@ class _PdfName implements _IPdfPrimitive { int get hashCode => _name.hashCode; @override - bool get isSaving { + bool? get isSaving { _isSaving ??= false; return _isSaving; } @override - set isSaving(bool value) { + set isSaving(bool? value) { _isSaving = value; } @override - int get objectCollectionIndex { + int? get objectCollectionIndex { _objectCollectionIndex ??= 0; return _objectCollectionIndex; } @override - set objectCollectionIndex(int value) { + set objectCollectionIndex(int? value) { _objectCollectionIndex = value; } @override - int get position { + int? get position { _position ??= -1; return _position; } @override - set position(int value) { + set position(int? value) { _position = value; } @override - _ObjectStatus get status { + _ObjectStatus? get status { _status ??= _ObjectStatus.none; return _status; } @override - set status(_ObjectStatus value) { + set status(_ObjectStatus? value) { _status = value; } @override - _IPdfPrimitive clonedObject; + _IPdfPrimitive? clonedObject; @override - void save(_IPdfWriter writer) { - writer._write(toString()); + void save(_IPdfWriter? writer) { + writer!._write(toString()); } @override diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_null.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_null.dart index 7f747d49a..687eb86b8 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_null.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_null.dart @@ -4,62 +4,62 @@ class _PdfNull implements _IPdfPrimitive { _PdfNull(); //Fields - bool _isSaving; - int _objectCollectionIndex; - int _position; - _ObjectStatus _status; + bool? _isSaving; + int? _objectCollectionIndex; + int? _position; + _ObjectStatus? _status; //_IPdfPrimitive members @override - bool get isSaving { + bool? get isSaving { _isSaving ??= false; return _isSaving; } @override - set isSaving(bool value) { + set isSaving(bool? value) { _isSaving = value; } @override - int get objectCollectionIndex { + int? get objectCollectionIndex { _objectCollectionIndex ??= 0; return _objectCollectionIndex; } @override - set objectCollectionIndex(int value) { + set objectCollectionIndex(int? value) { _objectCollectionIndex = value; } @override - int get position { + int? get position { _position ??= -1; return _position; } @override - set position(int value) { + set position(int? value) { _position = value; } @override - _ObjectStatus get status { + _ObjectStatus? get status { _status ??= _ObjectStatus.none; return _status; } @override - set status(_ObjectStatus value) { + set status(_ObjectStatus? value) { _status = value; } @override - _IPdfPrimitive clonedObject; + _IPdfPrimitive? clonedObject; @override - void save(_IPdfWriter writer) { - writer._write('null'); + void save(_IPdfWriter? writer) { + writer!._write('null'); } @override diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_number.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_number.dart index cff8b0752..1e5ab1b48 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_number.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_number.dart @@ -10,64 +10,64 @@ class _PdfNumber implements _IPdfPrimitive { } //Fields - num value; - bool _isSaving; - int _objectCollectionIndex; - int _position; - _ObjectStatus _status; + num? value; + bool? _isSaving; + int? _objectCollectionIndex; + int? _position; + _ObjectStatus? _status; //_IPdfPrimitive members @override - bool get isSaving { + bool? get isSaving { _isSaving ??= false; return _isSaving; } @override - set isSaving(bool value) { + set isSaving(bool? value) { _isSaving = value; } @override - int get objectCollectionIndex { + int? get objectCollectionIndex { _objectCollectionIndex ??= 0; return _objectCollectionIndex; } @override - set objectCollectionIndex(int value) { + set objectCollectionIndex(int? value) { _objectCollectionIndex = value; } @override - int get position { + int? get position { _position ??= -1; return _position; } @override - set position(int value) { + set position(int? value) { _position = value; } @override - _ObjectStatus get status { + _ObjectStatus? get status { _status ??= _ObjectStatus.none; return _status; } @override - set status(_ObjectStatus value) { + set status(_ObjectStatus? value) { _status = value; } @override - _IPdfPrimitive clonedObject; + _IPdfPrimitive? clonedObject; @override - void save(_IPdfWriter writer) { + void save(_IPdfWriter? writer) { if (value is double) { - String numberValue = value.toStringAsFixed(2); + String numberValue = value!.toStringAsFixed(2); if (numberValue.endsWith('.00')) { if (numberValue.length == 3) { numberValue = '0'; @@ -75,9 +75,9 @@ class _PdfNumber implements _IPdfPrimitive { numberValue = numberValue.substring(0, numberValue.length - 3); } } - writer._write(numberValue); + writer!._write(numberValue); } else { - writer._write(value.toString()); + writer!._write(value.toString()); } } @@ -89,5 +89,5 @@ class _PdfNumber implements _IPdfPrimitive { } @override - _IPdfPrimitive _clone(_PdfCrossTable crossTable) => _PdfNumber(value); + _IPdfPrimitive _clone(_PdfCrossTable crossTable) => _PdfNumber(value!); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference.dart index 040a6c939..ab320502a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference.dart @@ -2,8 +2,6 @@ part of pdf; class _PdfReference implements _IPdfPrimitive { _PdfReference(int objNum, int genNum) { - ArgumentError.checkNotNull(objNum, 'objNum'); - ArgumentError.checkNotNull(genNum, 'genNum'); if (objNum.isNaN) { throw ArgumentError.value(objNum, 'not a number'); } @@ -15,12 +13,12 @@ class _PdfReference implements _IPdfPrimitive { } //Fields - int _objNum; - int _genNum; - bool _isSaving; - int _objectCollectionIndex; - int _position; - _ObjectStatus _status; + int? _objNum; + int? _genNum; + bool? _isSaving; + int? _objectCollectionIndex; + int? _position; + _ObjectStatus? _status; //Implementation @override @@ -30,55 +28,55 @@ class _PdfReference implements _IPdfPrimitive { //_IPdfPrimitive members @override - bool get isSaving { + bool? get isSaving { _isSaving ??= false; return _isSaving; } @override - set isSaving(bool value) { + set isSaving(bool? value) { _isSaving = value; } @override - int get objectCollectionIndex { + int? get objectCollectionIndex { _objectCollectionIndex ??= 0; return _objectCollectionIndex; } @override - set objectCollectionIndex(int value) { + set objectCollectionIndex(int? value) { _objectCollectionIndex = value; } @override - int get position { + int? get position { _position ??= -1; return _position; } @override - set position(int value) { + set position(int? value) { _position = value; } @override - _ObjectStatus get status { + _ObjectStatus? get status { _status ??= _ObjectStatus.none; return _status; } @override - set status(_ObjectStatus value) { + set status(_ObjectStatus? value) { _status = value; } @override - _IPdfPrimitive clonedObject; + _IPdfPrimitive? clonedObject; @override - void save(_IPdfWriter writer) { - writer._write(toString()); + void save(_IPdfWriter? writer) { + writer!._write(toString()); } @override @@ -89,5 +87,5 @@ class _PdfReference implements _IPdfPrimitive { } @override - _IPdfPrimitive _clone(_PdfCrossTable crossTable) => null; + _IPdfPrimitive? _clone(_PdfCrossTable crossTable) => null; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference_holder.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference_holder.dart index ec6358a54..b66a7eb4e 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference_holder.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_reference_holder.dart @@ -16,45 +16,41 @@ class _PdfReferenceHolder implements _IPdfPrimitive { } _PdfReferenceHolder.fromReference( - _PdfReference reference, _PdfCrossTable crossTable) { + _PdfReference reference, _PdfCrossTable? crossTable) { if (crossTable != null) { this.crossTable = crossTable; } else { throw ArgumentError.value(crossTable, 'crossTable value cannot be null'); } - if (reference != null) { - this.reference = reference; - } else { - throw ArgumentError.value(reference, 'reference value cannot be null'); - } + this.reference = reference; } //Fields - _IPdfPrimitive _object; - _PdfReference reference; - bool _isSaving; - int _objectCollectionIndex; - int _position; - _ObjectStatus _status; - _PdfCrossTable crossTable; - int _objectIndex = -1; + _IPdfPrimitive? _object; + _PdfReference? reference; + bool? _isSaving; + int? _objectCollectionIndex; + int? _position; + _ObjectStatus? _status; + late _PdfCrossTable crossTable; + int? _objectIndex = -1; //_IPdfPrimitive members - _IPdfPrimitive get object { + _IPdfPrimitive? get object { if (reference != null || _object == null) { _object = _obtainObject(); } return _object; } - set object(_IPdfPrimitive value) { + set object(_IPdfPrimitive? value) { _object = value; } - int get index { - final _PdfMainObjectCollection items = crossTable._items; - _objectIndex = items._getObjectIndex(reference); - if (_objectIndex < 0) { + int? get index { + final _PdfMainObjectCollection items = crossTable._items!; + _objectIndex = items._getObjectIndex(reference!); + if (_objectIndex! < 0) { crossTable._getObject(reference); _objectIndex = items._count - 1; } @@ -62,78 +58,79 @@ class _PdfReferenceHolder implements _IPdfPrimitive { } @override - bool get isSaving { + bool? get isSaving { _isSaving ??= false; return _isSaving; } @override - set isSaving(bool value) { + set isSaving(bool? value) { _isSaving = value; } @override - int get objectCollectionIndex { + int? get objectCollectionIndex { _objectCollectionIndex ??= 0; return _objectCollectionIndex; } @override - set objectCollectionIndex(int value) { + set objectCollectionIndex(int? value) { _objectCollectionIndex = value; } @override - int get position { + int? get position { _position ??= -1; return _position; } @override - set position(int value) { + set position(int? value) { _position = value; } @override - _ObjectStatus get status { + _ObjectStatus? get status { _status ??= _ObjectStatus.none; return _status; } @override - set status(_ObjectStatus value) { + set status(_ObjectStatus? value) { _status = value; } @override - _IPdfPrimitive clonedObject; + _IPdfPrimitive? clonedObject; @override - void save(_IPdfWriter writer) { - ArgumentError.checkNotNull(writer, 'writer'); - if (!writer._document._isLoadedDocument) { - object.isSaving = true; - } - final _PdfCrossTable crossTable = writer._document._crossTable; - _PdfReference pdfReference; - if (writer._document.fileStructure.incrementalUpdate && - writer._document._isStreamCopied) { - if (reference == null) { - pdfReference = crossTable._getReference(object); + void save(_IPdfWriter? writer) { + if (writer != null) { + if (!writer._document!._isLoadedDocument) { + object!.isSaving = true; + } + final _PdfCrossTable? crossTable = writer._document!._crossTable; + _PdfReference? pdfReference; + if (writer._document!.fileStructure.incrementalUpdate && + writer._document!._isStreamCopied) { + if (reference == null) { + pdfReference = crossTable!._getReference(object); + } else { + pdfReference = reference; + } } else { - pdfReference = reference; + pdfReference = crossTable!._getReference(object); } - } else { - pdfReference = crossTable._getReference(object); + pdfReference!.save(writer); } - pdfReference.save(writer); } - _IPdfPrimitive _obtainObject() { - _IPdfPrimitive obj; + _IPdfPrimitive? _obtainObject() { + _IPdfPrimitive? obj; if (reference != null) { - if (index >= 0) { - obj = crossTable._items._getObject(reference); + if (index! >= 0) { + obj = crossTable._items!._getObject(reference!); } } else if (_object != null) { obj = _object; @@ -144,7 +141,7 @@ class _PdfReferenceHolder implements _IPdfPrimitive { @override void dispose() { if (reference != null) { - reference.dispose(); + reference!.dispose(); reference = null; } if (_status != null) { @@ -155,10 +152,10 @@ class _PdfReferenceHolder implements _IPdfPrimitive { @override _IPdfPrimitive _clone(_PdfCrossTable crossTable) { _PdfReferenceHolder refHolder; - _IPdfPrimitive temp; + _IPdfPrimitive? temp; _PdfReference reference; if (object is _PdfNumber) { - return _PdfNumber((object as _PdfNumber).value); + return _PdfNumber((object as _PdfNumber).value!); } if (object is _PdfDictionary) { @@ -166,7 +163,7 @@ class _PdfReferenceHolder implements _IPdfPrimitive { final _PdfName type = _PdfName(_DictionaryProperties.type); final _PdfDictionary dict = object as _PdfDictionary; if (dict.containsKey(type)) { - final _PdfName pageName = dict[type] as _PdfName; + final _PdfName? pageName = dict[type] as _PdfName?; if (pageName != null) { if (pageName._name == 'Page') { return _PdfNull(); @@ -180,12 +177,12 @@ class _PdfReferenceHolder implements _IPdfPrimitive { // Resolves circular references. if (crossTable._prevReference != null && - crossTable._prevReference.contains(this.reference)) { - _IPdfPrimitive obj; + crossTable._prevReference!.contains(this.reference)) { + _IPdfPrimitive? obj; if (crossTable._document != null) { obj = this.crossTable._getObject(this.reference); } else { - obj = this.crossTable._getObject(this.reference).clonedObject; + obj = this.crossTable._getObject(this.reference)!.clonedObject; } if (obj != null) { reference = crossTable._getReference(obj); @@ -195,12 +192,12 @@ class _PdfReferenceHolder implements _IPdfPrimitive { } } if (this.reference != null) { - crossTable._prevReference.add(this.reference); + crossTable._prevReference!.add(this.reference); } if (!(object is _PdfCatalog)) { - temp = object._clone(crossTable); + temp = object!._clone(crossTable); } else { - temp = crossTable._document._catalog; + temp = crossTable._document!._catalog; } reference = crossTable._getReference(temp); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_stream.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_stream.dart index 66bb5a4fa..79e6304ea 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_stream.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_stream.dart @@ -1,7 +1,7 @@ part of pdf; class _PdfStream extends _PdfDictionary { - _PdfStream([_PdfDictionary dictionary, List data]) { + _PdfStream([_PdfDictionary? dictionary, List? data]) { if (dictionary == null && data == null) { _dataStream = []; _compress = true; @@ -10,9 +10,9 @@ class _PdfStream extends _PdfDictionary { ArgumentError.checkNotNull(dictionary, 'dictionary'); _compress = false; _dataStream = []; - _dataStream.addAll(data); + _dataStream!.addAll(data!); _copyDictionary(dictionary); - this[_DictionaryProperties.length] = _PdfNumber(_dataStream.length); + this[_DictionaryProperties.length] = _PdfNumber(_dataStream!.length); } decrypted = false; _blockEncryption = false; @@ -23,19 +23,19 @@ class _PdfStream extends _PdfDictionary { static const String suffix = 'endstream'; //Fields - List _dataStream; - bool _compress; - _PdfStream _clonedObject; + List? _dataStream; + bool? _compress; + _PdfStream? _clonedObject; @override - bool _isChanged; - bool _blockEncryption; + bool? _isChanged; + late bool _blockEncryption; //Properties - bool get compress { + bool? get compress { return _compress; } - set compress(bool value) { + set compress(bool? value) { _compress = value; _modify(); } @@ -45,16 +45,16 @@ class _PdfStream extends _PdfDictionary { _isChanged = true; } - List _compressContent(_IPdfWriter writer) { - final List data = _dataStream; - if (compress && - writer._document.compressionLevel != PdfCompressionLevel.none) { + List? _compressContent(PdfDocument? document) { + final List? data = _dataStream; + if (compress! && document!.compressionLevel != PdfCompressionLevel.none) { final List outputStream = []; final _CompressedStreamWriter compressedWriter = _CompressedStreamWriter( - outputStream, false, writer._document.compressionLevel, false); - compressedWriter.write(data, 0, data.length, false); + outputStream, false, document.compressionLevel, false); + compressedWriter.write(data!, 0, data.length, false); compressedWriter._close(); _addFilter(_DictionaryProperties.flateDecode); + compress = false; return outputStream; } else { return data; @@ -62,10 +62,10 @@ class _PdfStream extends _PdfDictionary { } void _decompress() { - String filterName = ''; - _IPdfPrimitive primitive = this[_DictionaryProperties.filter]; + String? filterName = ''; + _IPdfPrimitive? primitive = this[_DictionaryProperties.filter]; if (primitive is _PdfReferenceHolder) { - final _PdfReferenceHolder holder = primitive as _PdfReferenceHolder; + final _PdfReferenceHolder holder = primitive; primitive = holder.object; } if (primitive != null) { @@ -74,20 +74,20 @@ class _PdfStream extends _PdfDictionary { if (name._name == 'ASCIIHexDecode') { _dataStream = _decode(_dataStream); } else { - _dataStream = _decompressData(_dataStream, name._name); + _dataStream = _decompressData(_dataStream!, name._name!); } _modify(); } else if (primitive is _PdfArray) { final _PdfArray filter = primitive; for (int i = 0; i < filter.count; i++) { - final _IPdfPrimitive pdfFilter = filter[i]; - if (pdfFilter is _PdfName) { + final _IPdfPrimitive? pdfFilter = filter[i]; + if (pdfFilter != null && pdfFilter is _PdfName) { filterName = pdfFilter._name; } if (filterName == 'ASCIIHexDecode') { _dataStream = _decode(_dataStream); } else { - _dataStream = _decompressData(_dataStream, filterName); + _dataStream = _decompressData(_dataStream!, filterName!); } _modify(); } @@ -99,13 +99,11 @@ class _PdfStream extends _PdfDictionary { _compress = true; } - List _decode(List data) { + List? _decode(List? data) { return data; } List _decompressData(List data, String filter) { - ArgumentError.checkNotNull(data, 'data'); - ArgumentError.checkNotNull(filter, 'filter'); if (data.isEmpty) { return data; } @@ -126,17 +124,17 @@ class _PdfStream extends _PdfDictionary { } List _postProcess(List data, String filter) { - _IPdfPrimitive obj; + _IPdfPrimitive? obj; if (filter == _DictionaryProperties.flateDecode) { obj = this[_DictionaryProperties.decodeParms]; if (obj == null) { return data; } - _PdfDictionary decodeParams; - _PdfArray decodeParamsArr; - _PdfNull pdfNull; + _PdfDictionary? decodeParams; + _PdfArray? decodeParamsArr; + _PdfNull? pdfNull; if (obj is _PdfReferenceHolder) { - final _IPdfPrimitive primitive = _PdfCrossTable._dereference(obj); + final _IPdfPrimitive? primitive = _PdfCrossTable._dereference(obj); if (primitive is _PdfDictionary) { decodeParams = primitive; } @@ -162,10 +160,10 @@ class _PdfStream extends _PdfDictionary { } } if (decodeParamsArr != null) { - final _IPdfPrimitive decode = decodeParamsArr[0]; + final _IPdfPrimitive? decode = decodeParamsArr[0]; if (decode != null && decode is _PdfDictionary) { if (decode.containsKey(_DictionaryProperties.name)) { - final _IPdfPrimitive name = decode[_DictionaryProperties.name]; + final _IPdfPrimitive? name = decode[_DictionaryProperties.name]; if (name != null && name is _PdfName && name._name == 'StdCF') { return data; } @@ -175,14 +173,14 @@ class _PdfStream extends _PdfDictionary { int predictor = 1; if (decodeParams != null) { if (decodeParams.containsKey(_DictionaryProperties.predictor)) { - final _IPdfPrimitive number = + final _IPdfPrimitive? number = decodeParams[_DictionaryProperties.predictor]; if (number is _PdfNumber) { - predictor = number.value.toInt(); + predictor = number.value!.toInt(); } } } else if (decodeParamsArr != null && decodeParamsArr.count > 0) { - final _IPdfPrimitive dictionary = decodeParamsArr[0]; + final _IPdfPrimitive? dictionary = decodeParamsArr[0]; if (dictionary != null && dictionary is _PdfDictionary && dictionary.containsKey(_DictionaryProperties.predictor)) { @@ -198,13 +196,13 @@ class _PdfStream extends _PdfDictionary { } else if (predictor < 16 && predictor > 2) { int colors = 1; int columns = 1; - obj = decodeParams[_DictionaryProperties.colors]; + obj = decodeParams![_DictionaryProperties.colors]; if (obj != null && obj is _PdfNumber) { - colors = obj.value.toInt(); + colors = obj.value!.toInt(); } obj = decodeParams[_DictionaryProperties.columns]; if (obj != null && obj is _PdfNumber) { - columns = obj.value.toInt(); + columns = obj.value!.toInt(); } data = _PdfPngFilter()._decompress(data, colors * columns); return data; @@ -216,12 +214,12 @@ class _PdfStream extends _PdfDictionary { } void _addFilter(String filterName) { - _IPdfPrimitive filter = _items[_DictionaryProperties.filter]; + _IPdfPrimitive? filter = _items![_DictionaryProperties.filter]; if (filter is _PdfReferenceHolder) { - filter = (filter as _PdfReferenceHolder)._object; + filter = filter._object; } - _PdfName name; - _PdfArray array; + late _PdfName name; + _PdfArray? array; if (filter is _PdfArray) { array = filter; } @@ -248,10 +246,10 @@ class _PdfStream extends _PdfDictionary { if (pdfObject.isEmpty) { throw ArgumentError.value(pdfObject, 'value cannot be empty'); } - if (pdfObject is String) { + if (pdfObject is String || pdfObject is String?) { _write(utf8.encode(pdfObject)); - } else if (pdfObject is List) { - _dataStream.addAll(pdfObject); + } else if (pdfObject is List || pdfObject is List) { + _dataStream!.addAll(pdfObject); _modify(); } else { throw ArgumentError.value( @@ -260,7 +258,7 @@ class _PdfStream extends _PdfDictionary { } void _clearStream() { - _dataStream.clear(); + _dataStream!.clear(); if (containsKey(_DictionaryProperties.filter)) { remove(_DictionaryProperties.filter); } @@ -270,22 +268,118 @@ class _PdfStream extends _PdfDictionary { //_IPdfPrimitive members @override - void save(_IPdfWriter writer) { + void save(_IPdfWriter? writer) { final _SavePdfPrimitiveArgs beginSaveArguments = _SavePdfPrimitiveArgs(writer); _onBeginSave(beginSaveArguments); - List data = _compressContent(writer); - final PdfSecurity security = writer._document.security; + List? data = _compressContent(writer!._document); + final PdfSecurity? security = writer._document!.security; if (security != null && - security._encryptor != null && - !security._encryptor._encryptMetadata && + security._encryptor.encrypt && + security._encryptor._encryptOnlyAttachment!) { + bool attachmentEncrypted = false; + if (containsKey(_DictionaryProperties.type)) { + final _IPdfPrimitive? primitive = _items![_DictionaryProperties.type]; + if (primitive != null && + primitive is _PdfName && + primitive._name == _DictionaryProperties.embeddedFile) { + bool? isArray; + bool? isString; + _IPdfPrimitive? filterPrimitive; + if (containsKey(_DictionaryProperties.filter)) { + filterPrimitive = _items![_DictionaryProperties.filter]; + isArray = filterPrimitive is _PdfArray; + isString = filterPrimitive is _PdfString; + } + if ((isArray == null && isString == null) || + !isArray! || + (isArray && + (filterPrimitive as _PdfArray) + ._contains(_PdfName(_DictionaryProperties.crypt)))) { + if (_compress! || + !containsKey(_DictionaryProperties.filter) || + (isArray! && + (filterPrimitive as _PdfArray)._contains( + _PdfName(_DictionaryProperties.flateDecode)) || + (isString! && + (filterPrimitive as _PdfString).value == + _DictionaryProperties.flateDecode))) { + data = _compressContent(writer._document); + } + attachmentEncrypted = true; + data = _encryptContent(data, writer); + _addFilter(_DictionaryProperties.crypt); + } + if (!containsKey(_DictionaryProperties.decodeParms)) { + final _PdfArray decode = _PdfArray(); + final _PdfDictionary decodeparms = _PdfDictionary(); + decodeparms[_DictionaryProperties.name] = + _PdfName(_DictionaryProperties.stdCF); + decode._add(decodeparms); + decode._add(_PdfNull()); + _items![_PdfName(_DictionaryProperties.decodeParms)] = decode; + } + } + } + if (!attachmentEncrypted) { + if (containsKey(_DictionaryProperties.decodeParms)) { + final _IPdfPrimitive? primitive = + _items![_DictionaryProperties.decodeParms]; + if (primitive is _PdfArray) { + final _PdfArray decodeParamArray = primitive; + if (decodeParamArray.count > 0 && + decodeParamArray[0] is _PdfDictionary) { + final _PdfDictionary decode = + decodeParamArray[0] as _PdfDictionary; + if (decode.containsKey(_DictionaryProperties.name)) { + final _IPdfPrimitive? name = decode[_DictionaryProperties.name]; + if (name is _PdfName && + name._name == _DictionaryProperties.stdCF) { + _PdfArray? filter; + if (containsKey(_DictionaryProperties.filter)) { + final _IPdfPrimitive? filterType = + _items![_DictionaryProperties.filter]; + if (filterType is _PdfArray) { + filter = filterType; + } + } + if (filter == null || + filter._contains(_PdfName(_DictionaryProperties.crypt))) { + if (_compress!) { + data = _compressContent(writer._document); + } + data = _encryptContent(data, writer); + _addFilter(_DictionaryProperties.crypt); + } + } + } + } + } + } else if (containsKey(_DictionaryProperties.dl)) { + if (_compress!) { + data = _compressContent(writer._document); + } + data = _encryptContent(data, writer); + _addFilter(_DictionaryProperties.crypt); + if (!containsKey(_DictionaryProperties.decodeParms)) { + final _PdfArray decode = _PdfArray(); + final _PdfDictionary decodeparms = _PdfDictionary(); + decodeparms[_DictionaryProperties.name] = + _PdfName(_DictionaryProperties.stdCF); + decode._add(decodeparms); + decode._add(_PdfNull()); + _items![_PdfName(_DictionaryProperties.decodeParms)] = decode; + } + } + } + } + if (security != null && + !security._encryptor._encryptMetadata! && containsKey(_DictionaryProperties.type)) { - final _IPdfPrimitive primitive = _items[_DictionaryProperties.type]; + final _IPdfPrimitive? primitive = _items![_DictionaryProperties.type]; if (primitive != null && primitive is _PdfName) { final _PdfName fileType = primitive; - if (fileType == null || - (fileType != null && - fileType._name != _DictionaryProperties.metadata)) { + if (fileType._name != _DictionaryProperties.metadata) { data = _encryptContent(data, writer); } } @@ -293,7 +387,7 @@ class _PdfStream extends _PdfDictionary { data = _encryptContent(data, writer); } - this[_DictionaryProperties.length] = _PdfNumber(data.length); + this[_DictionaryProperties.length] = _PdfNumber(data!.length); super._saveDictionary(writer, false); writer._write(prefix); writer._write(_Operators.newLine); @@ -306,27 +400,27 @@ class _PdfStream extends _PdfDictionary { final _SavePdfPrimitiveArgs endSaveArguments = _SavePdfPrimitiveArgs(writer); _onEndSave(endSaveArguments); - if (_compress) { + if (_compress!) { remove(_DictionaryProperties.filter); } } @override void dispose() { - if (_dataStream != null && _dataStream.isNotEmpty) { - _dataStream.clear(); + if (_dataStream != null && _dataStream!.isNotEmpty) { + _dataStream!.clear(); _dataStream = null; } } @override - _IPdfPrimitive _clone(_PdfCrossTable crossTable) { - if (_clonedObject != null && _clonedObject._crossTable == crossTable) { + _IPdfPrimitive? _clone(_PdfCrossTable crossTable) { + if (_clonedObject != null && _clonedObject!._crossTable == crossTable) { return _clonedObject; } else { _clonedObject = null; } - final _PdfDictionary dict = super._clone(crossTable) as _PdfDictionary; + final _PdfDictionary? dict = super._clone(crossTable) as _PdfDictionary?; final _PdfStream newStream = _PdfStream(dict, _dataStream); newStream.compress = _compress; _clonedObject = newStream; @@ -334,23 +428,23 @@ class _PdfStream extends _PdfDictionary { } @override - bool decrypted; + bool? decrypted; - void decrypt(_PdfEncryptor encryptor, int currentObjectNumber) { - if (encryptor != null && !decrypted) { + void decrypt(_PdfEncryptor encryptor, int? currentObjectNumber) { + if (!decrypted!) { decrypted = true; _dataStream = - encryptor._encryptData(currentObjectNumber, _dataStream, false); + encryptor._encryptData(currentObjectNumber, _dataStream!, false); _modify(); } } - List _encryptContent(List data, _IPdfWriter writer) { - final PdfDocument doc = writer._document; + List? _encryptContent(List? data, _IPdfWriter writer) { + final PdfDocument doc = writer._document!; final _PdfEncryptor encryptor = doc.security._encryptor; if (encryptor.encrypt && !_blockEncryption) { - data = - encryptor._encryptData(doc._currentSavingObject._objNum, data, true); + data = encryptor._encryptData( + doc._currentSavingObject!._objNum, data!, true); } return data; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_string.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_string.dart index 2aa736428..70a4145db 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_string.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_string.dart @@ -1,13 +1,21 @@ part of pdf; class _PdfString implements _IPdfPrimitive { - _PdfString(String value, [bool encrypted]) { - ArgumentError.checkNotNull(value, 'value'); + _PdfString(String value, [bool? encrypted]) { if (encrypted != null) { if (!encrypted && value.isNotEmpty) { data = _hexToBytes(value); - if (data.isNotEmpty) { - this.value = _byteToString(data); + if (data!.isNotEmpty) { + if (data![0] == 0xfe && data![1] == 0xff) { + this.value = _decodeBigEndian(data, 2, data!.length - 2); + _isHex = false; + data = []; + for (int i = 0; i < value.length; i++) { + data!.add(value.codeUnitAt(i).toUnsigned(8)); + } + } else { + this.value = _byteToString(data!); + } } } else { this.value = value; @@ -20,7 +28,7 @@ class _PdfString implements _IPdfPrimitive { this.value = value; data = []; for (int i = 0; i < value.length; i++) { - data.add(value.codeUnitAt(i).toUnsigned(8)); + data!.add(value.codeUnitAt(i).toUnsigned(8)); } } _isHex = false; @@ -30,10 +38,9 @@ class _PdfString implements _IPdfPrimitive { } _PdfString.fromBytes(List value) { - ArgumentError.checkNotNull(value, 'value'); data = value; if (value.isNotEmpty) { - this.value = String.fromCharCodes(data); + this.value = String.fromCharCodes(data as Iterable); } _isHex = true; decrypted = false; @@ -45,69 +52,71 @@ class _PdfString implements _IPdfPrimitive { static const String hexStringMark = '<>'; //Fields - List data; - String value; + List? data; + String? value; bool isAsciiEncode = false; - bool _isSaving; - int _objectCollectionIndex; - int _position; - _ObjectStatus _status; - bool _isHex; + bool? _isSaving; + int? _objectCollectionIndex; + int? _position; + _ObjectStatus? _status; + bool? _isHex; _ForceEncoding encode = _ForceEncoding.none; - _PdfCrossTable _crossTable; - _PdfString _clonedObject; - bool isParentDecrypted; + _PdfCrossTable? _crossTable; + _PdfString? _clonedObject; + late bool isParentDecrypted; //Implementations - List _pdfEncode(PdfDocument document) { + List _pdfEncode(PdfDocument? document) { final List result = []; - result.add(_isHex + result.add(_isHex! ? _PdfString.hexStringMark.codeUnitAt(0) : _PdfString.stringMark.codeUnitAt(0)); - List tempData; - bool needStartMark = false; - if (_isHex) { - tempData = _getHexBytes(data); - } else if (isAsciiEncode) { - final List data = _escapeSymbols(value); - tempData = []; - for (int i = 0; i < data.length; i++) { - tempData.add(data[i].toUnsigned(8)); + if (data != null && data!.isNotEmpty) { + List tempData; + bool needStartMark = false; + if (_isHex!) { + tempData = _getHexBytes(data!); + } else if (isAsciiEncode) { + final List data = _escapeSymbols(value); + tempData = []; + for (int i = 0; i < data.length; i++) { + tempData.add(data[i].toUnsigned(8)); + } + } else if (utf8.encode(value!).length != value!.length) { + tempData = _toUnicodeArray(value!, true); + tempData = _escapeSymbols(result); + needStartMark = true; + } else { + tempData = []; + for (int i = 0; i < value!.length; i++) { + tempData.add(value!.codeUnitAt(i).toUnsigned(8)); + } } - } else if (utf8.encode(value).length != value.length) { - tempData = _toUnicodeArray(value, true); - tempData = _escapeSymbols(result); - needStartMark = true; - } else { - tempData = []; - for (int i = 0; i < value.length; i++) { - tempData.add(value.codeUnitAt(i).toUnsigned(8)); + bool hex = false; + tempData = _encryptIfNeeded(tempData, document); + for (int i = 0; i < tempData.length; i++) { + if ((tempData[i] >= 48 && tempData[i] <= 57) || + (tempData[i] >= 65 && tempData[i] <= 70) || + (tempData[i] >= 97 && tempData[i] <= 102)) { + hex = true; + } else { + hex = false; + break; + } } - } - bool hex = false; - tempData = _encryptIfNeeded(tempData, document); - for (int i = 0; i < tempData.length; i++) { - if ((tempData[i] >= 48 && tempData[i] <= 57) || - (tempData[i] >= 65 && tempData[i] <= 70) || - (tempData[i] >= 97 && tempData[i] <= 102)) { - hex = true; - } else { - hex = false; - break; + if (_isHex! && !hex) { + tempData = _getHexBytes(tempData); + } + result.addAll(tempData); + if (needStartMark) { + result.insert( + 0, + _isHex! + ? _PdfString.hexStringMark.codeUnitAt(0) + : _PdfString.stringMark.codeUnitAt(0)); } } - if (_isHex && !hex) { - tempData = _getHexBytes(tempData); - } - result.addAll(tempData); - if (needStartMark) { - result.insert( - 0, - _isHex - ? _PdfString.hexStringMark.codeUnitAt(0) - : _PdfString.stringMark.codeUnitAt(0)); - } - result.add(_isHex + result.add(_isHex! ? _PdfString.hexStringMark.codeUnitAt(1) : _PdfString.stringMark.codeUnitAt(1)); return result; @@ -124,9 +133,17 @@ class _PdfString implements _IPdfPrimitive { return result; } + static String _bytesToHex(List data) { + String result = ''; + for (int i = 0; i < data.length; i++) { + final String radix = data[i].toRadixString(16); + result += (radix.length == 1 ? '0' + radix : radix).toUpperCase(); + } + return result; + } + /// Converts string to array of unicode symbols. static List toUnicodeArray(String value) { - ArgumentError.checkNotNull(value); final List data = []; for (int i = 0; i < value.length; i++) { final int code = value.codeUnitAt(i); @@ -172,7 +189,6 @@ class _PdfString implements _IPdfPrimitive { } static List _toUnicodeArray(String value, bool addPrefix) { - ArgumentError.checkNotNull(value); final List output = []; if (addPrefix) { output.add(254); @@ -186,8 +202,7 @@ class _PdfString implements _IPdfPrimitive { return output; } - static String _byteToString(List data, [int length]) { - ArgumentError.checkNotNull(data); + static String _byteToString(List data, [int? length]) { length ??= data.length; if (length > data.length) { ArgumentError.value(length); @@ -242,62 +257,63 @@ class _PdfString implements _IPdfPrimitive { //_IPdfPrimitive members @override - bool get isSaving { + bool? get isSaving { _isSaving ??= false; return _isSaving; } @override - set isSaving(bool value) { + set isSaving(bool? value) { _isSaving = value; } @override - int get objectCollectionIndex { + int? get objectCollectionIndex { _objectCollectionIndex ??= 0; return _objectCollectionIndex; } @override - set objectCollectionIndex(int value) { + set objectCollectionIndex(int? value) { _objectCollectionIndex = value; } @override - int get position { + int? get position { _position ??= -1; return _position; } @override - set position(int value) { + set position(int? value) { _position = value; } @override - _ObjectStatus get status { + _ObjectStatus? get status { _status ??= _ObjectStatus.none; return _status; } @override - set status(_ObjectStatus value) { + set status(_ObjectStatus? value) { _status = value; } @override - _IPdfPrimitive clonedObject; + _IPdfPrimitive? clonedObject; @override - void save(_IPdfWriter writer) { - ArgumentError.checkNotNull(writer, 'writer'); - writer._write(_pdfEncode(writer._document)); + void save(_IPdfWriter? writer) { + if (writer != null) { + writer._write(_pdfEncode(writer._document)); + } } @override void dispose() { if (data != null) { - data.clear(); + data!.clear(); data = null; } if (_status != null) { @@ -306,13 +322,13 @@ class _PdfString implements _IPdfPrimitive { } @override - _IPdfPrimitive _clone(_PdfCrossTable crossTable) { - if (_clonedObject != null && _clonedObject._crossTable == crossTable) { + _IPdfPrimitive? _clone(_PdfCrossTable crossTable) { + if (_clonedObject != null && _clonedObject!._crossTable == crossTable) { return _clonedObject; } else { _clonedObject = null; } - final _PdfString newString = _PdfString(value); + final _PdfString newString = _PdfString(value!); newString.encode = encode; newString._isHex = _isHex; newString._crossTable = crossTable; @@ -320,25 +336,28 @@ class _PdfString implements _IPdfPrimitive { return newString; } - List _encryptIfNeeded(List data, PdfDocument document) { - ArgumentError.checkNotNull(data, 'value cannor be null'); - final PdfSecurity security = (document == null) ? null : document.security; - if (security == null || !security._encryptor.encrypt) { + List _encryptIfNeeded(List data, PdfDocument? document) { + final PdfSecurity? security = (document == null) ? null : document.security; + if (security == null || + (!security._encryptor.encrypt || + security._encryptor._encryptOnlyAttachment!)) { return data; } else { data = security._encryptor - ._encryptData(document._currentSavingObject._objNum, data, true); + ._encryptData(document!._currentSavingObject!._objNum, data, true); } return _escapeSymbols(data); } - bool decrypted; - void decrypt(_PdfEncryptor encryptor, int currentObjectNumber) { - if (encryptor != null && !decrypted && !isParentDecrypted) { + late bool decrypted; + void decrypt(_PdfEncryptor encryptor, int? currentObjectNumber) { + if (!decrypted && + !isParentDecrypted && + !encryptor._encryptOnlyAttachment!) { decrypted = true; - value = _byteToString(data); + value = _byteToString(data!); final List bytes = - encryptor._encryptData(currentObjectNumber, data, false); + encryptor._encryptData(currentObjectNumber, data!, false); value = _byteToString(bytes); data = bytes; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1.dart new file mode 100644 index 000000000..ad294370d --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1.dart @@ -0,0 +1,1017 @@ +part of pdf; + +class _IAsn1 { + _Asn1? getAsn1() => null; +} + +class _IAsn1Octet implements _IAsn1 { + _StreamReader? getOctetStream() => null; + @override + _Asn1? getAsn1() => null; +} + +class _IAsn1Tag implements _IAsn1 { + int? get tagNumber => null; + _IAsn1? getParser(int tagNumber, bool isExplicit) => null; + @override + _Asn1? getAsn1() => null; +} + +class _IAsn1Collection implements _IAsn1 { + _IAsn1? readObject() => null; + + @override + _Asn1? getAsn1() => null; +} + +abstract class _Asn1 extends _Asn1Encode { + _Asn1([List<_Asn1UniversalTags>? tag]) { + if (tag != null) { + _tag = tag; + } + } + + //Fields + late List<_Asn1UniversalTags> _tag; + List? _bytes; + + //Abstract methods + void encode(_DerStream derOut); + @override + int get hashCode; + @override + bool operator ==(Object other); + + //Implementation + @override + _Asn1 getAsn1() { + return this; + } + + List? asn1Encode(List bytes) { + _bytes = []; + _bytes!.add(getTagValue(_tag)); + write(bytes.length); + _bytes!.addAll(bytes); + return _bytes; + } + + void write(int length) { + if (length > 127) { + int size = 1; + int value = length; + while ((value >>= 8) != 0) { + size++; + } + _bytes!.add((size | 0x80).toUnsigned(8)); + for (int i = (size - 1) * 8; i >= 0; i -= 8) { + _bytes!.add((length >> i).toUnsigned(8)); + } + } else { + _bytes!.add(length); + } + } + + List? getDerEncoded() { + final _DerStream stream = _DerStream([])..writeObject(this); + return stream._stream; + } + + int getAsn1Hash() { + return hashCode; + } + + int getTagValue(List<_Asn1UniversalTags> tags) { + int value = 0; + tags.forEach((tag) { + switch (tag) { + case _Asn1UniversalTags.reservedBER: + value |= 0; + break; + case _Asn1UniversalTags.boolean: + value |= 1; + break; + case _Asn1UniversalTags.integer: + value |= 2; + break; + case _Asn1UniversalTags.bitString: + value |= 3; + break; + case _Asn1UniversalTags.octetString: + value |= 4; + break; + case _Asn1UniversalTags.nullValue: + value |= 5; + break; + case _Asn1UniversalTags.objectIdentifier: + value |= 6; + break; + case _Asn1UniversalTags.objectDescriptor: + value |= 7; + break; + case _Asn1UniversalTags.externalValue: + value |= 8; + break; + case _Asn1UniversalTags.real: + value |= 9; + break; + case _Asn1UniversalTags.enumerated: + value |= 10; + break; + case _Asn1UniversalTags.embeddedPDV: + value |= 11; + break; + case _Asn1UniversalTags.utf8String: + value |= 12; + break; + case _Asn1UniversalTags.relativeOid: + value |= 13; + break; + case _Asn1UniversalTags.sequence: + value |= 16; + break; + case _Asn1UniversalTags.setValue: + value |= 17; + break; + case _Asn1UniversalTags.numericString: + value |= 18; + break; + case _Asn1UniversalTags.printableString: + value |= 19; + break; + case _Asn1UniversalTags.teletexString: + value |= 20; + break; + case _Asn1UniversalTags.videotexString: + value |= 21; + break; + case _Asn1UniversalTags.ia5String: + value |= 22; + break; + case _Asn1UniversalTags.utfTime: + value |= 23; + break; + case _Asn1UniversalTags.generalizedTime: + value |= 24; + break; + case _Asn1UniversalTags.graphicsString: + value |= 25; + break; + case _Asn1UniversalTags.visibleString: + value |= 26; + break; + case _Asn1UniversalTags.generalString: + value |= 27; + break; + case _Asn1UniversalTags.universalString: + value |= 28; + break; + case _Asn1UniversalTags.characterString: + value |= 29; + break; + case _Asn1UniversalTags.bmpString: + value |= 30; + break; + case _Asn1UniversalTags.constructed: + value |= 32; + break; + case _Asn1UniversalTags.application: + value |= 64; + break; + case _Asn1UniversalTags.tagged: + value |= 128; + break; + } + }); + return value; + } +} + +abstract class _Asn1Encode implements _IAsn1 { + _Asn1? getAsn1(); + + //Implementation + List? getEncoded([String? encoding]) { + if (encoding == null) { + return (_Asn1DerStream([])..writeObject(this))._stream; + } else { + if (encoding == _Asn1Constants.der) { + final _DerStream stream = _DerStream([])..writeObject(this); + return stream._stream; + } + return getEncoded(); + } + } + + List? getDerEncoded() { + return getEncoded(_Asn1Constants.der); + } + + @override + int get hashCode { + return getAsn1()!.getAsn1Hash(); + } + + @override + bool operator ==(Object other); +} + +class _Asn1EncodeCollection { + _Asn1EncodeCollection([List<_Asn1Encode?>? vector]) { + _encodableObjects = []; + if (vector != null) { + add(vector); + } + } + late List _encodableObjects; + + //Properties + _Asn1Encode? operator [](int index) => _encodableObjects[index]; + int get count => _encodableObjects.length; + + //Implementation + void add(List objs) { + objs.forEach((obj) { + if (obj is _Asn1Encode) { + _encodableObjects.add(obj); + } + }); + } +} + +class _Asn1Octet extends _Asn1 implements _IAsn1Octet { + _Asn1Octet(List value) + : super(<_Asn1UniversalTags>[_Asn1UniversalTags.octetString]) { + _value = value; + } + _Asn1Octet.fromObject(_Asn1Encode obj) { + _value = obj.getEncoded(_Asn1Constants.der); + } + //Fields + List? _value; + _IAsn1Octet get parser => this; + //Implementation + @override + _StreamReader getOctetStream() { + return _StreamReader(_value); + } + + List? getOctets() { + return _value; + } + + @override + int get hashCode { + return _Asn1Constants.getHashCode(getOctets()); + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _DerOctet) { + return _Asn1Constants.areEqual(getOctets(), asn1.getOctets()); + } else { + return false; + } + } + + @override + String toString() { + return _value.toString(); + } + + List? asnEncode() { + return super.asn1Encode(_value!); + } + + @override + void encode(_DerStream stream) { + throw ArgumentError.value(stream, 'stream', 'Not Implemented'); + } + + //Static methods + static _Asn1Octet? getOctetString(_Asn1Tag tag, bool isExplicit) { + final _Asn1? asn1 = tag.getObject(); + if (isExplicit || asn1 is _Asn1Octet) { + return getOctetStringFromObject(asn1); + } + return _BerOctet.getBerOctet(_Asn1Sequence.getSequence(asn1)!); + } + + static _Asn1Octet? getOctetStringFromObject(dynamic obj) { + if (obj == null || obj is _Asn1Octet) { + return obj; + } + if (obj is _Asn1Tag) { + return getOctetStringFromObject(obj.getObject()); + } + throw ArgumentError.value(obj, 'obj', 'Invalid object entry'); + } +} + +abstract class _Asn1Null extends _Asn1 { + _Asn1Null() : super(<_Asn1UniversalTags>[_Asn1UniversalTags.nullValue]) {} + //Implementation + List toArray() { + return []; + } + + List? asnEncode() { + return super.asn1Encode(toArray()); + } + + @override + String toString() { + return _Asn1Constants.nullValue; + } + + @override + void encode(_DerStream derOut) { + derOut.writeEncoded(5, toArray()); + } +} + +class _Asn1Sequence extends _Asn1 { + //Constructor + _Asn1Sequence() + : super(<_Asn1UniversalTags>[ + _Asn1UniversalTags.sequence, + _Asn1UniversalTags.constructed + ]) { + _objects = []; + } + //Fields + List? _objects; + int get count { + return _objects!.length; + } + + _IAsn1Collection get parser { + return _Asn1SequenceHelper(this); + } + + _IAsn1? operator [](int index) { + dynamic result; + if (index < _objects!.length) { + result = _objects![index]; + } else { + result = null; + } + return result; + } + + //Implementation + static _Asn1Sequence? getSequence(dynamic obj, [bool? explicitly]) { + _Asn1Sequence? result; + if (explicitly == null) { + if (obj == null || obj is _Asn1Sequence) { + result = obj; + } else if (obj is _IAsn1Collection) { + result = _Asn1Sequence.getSequence(obj.getAsn1()); + } else if (obj is List) { + result = _Asn1Sequence.getSequence( + _Asn1Stream(_StreamReader(obj)).readAsn1()); + } else if (obj is _Asn1Encode) { + final _Asn1? primitive = obj.getAsn1(); + if (primitive != null && primitive is _Asn1Sequence) { + return primitive; + } + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid entry in sequence'); + } + } else if (obj is _Asn1Tag) { + final _Asn1? inner = obj.getObject(); + if (explicitly) { + if (!obj._isExplicit!) { + throw ArgumentError.value( + explicitly, 'explicitly', 'Invalid entry in sequence'); + } + result = inner as _Asn1Sequence?; + } else if (obj._isExplicit!) { + if (obj is _DerTag) { + result = _BerSequence.fromObject(inner); + } + result = _DerSequence.fromObject(inner); + } else { + if (inner is _Asn1Sequence) { + result = inner; + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid entry in sequence'); + } + } + } + return result; + } + + @override + int get hashCode { + int hashCode = count; + _objects!.forEach((o) { + hashCode *= 17; + if (o == null) { + hashCode ^= _DerNull().getAsn1Hash(); + } else { + hashCode ^= o.hashCode; + } + }); + return hashCode; + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _Asn1Sequence) { + if (count != asn1.count) { + return false; + } + for (int i = 0; i < count; i++) { + final _Asn1? o1 = getCurrentObject(_objects![i]).getAsn1(); + final _Asn1? o2 = getCurrentObject(asn1._objects![i]).getAsn1(); + if (o1 != o2) { + return false; + } + } + } else { + return false; + } + return true; + } + + _Asn1Encode getCurrentObject(dynamic e) { + if (e != null && e is _Asn1Encode) { + return e; + } else { + return _DerNull(); + } + } + + @override + String toString([List? e]) { + if (e == null) { + return toString(_objects); + } else { + String result = '['; + for (int i = 0; i < _objects!.length; i++) { + result += _objects![i].toString(); + if (i != _objects!.length - 1) { + result += ', '; + } + } + result += ']'; + return result; + } + } + + List toArray() { + final List stream = []; + _objects!.forEach((obj) { + List? buffer; + if (obj is _Asn1Null) { + buffer = obj.asnEncode(); + } else if (obj is _Asn1Octet) { + buffer = obj.asnEncode(); + } else if (obj is _Asn1Sequence) { + buffer = obj.asnEncode(); + } else if (obj is _Algorithms) { + buffer = obj.asnEncode(); + } + if (buffer != null && buffer.isNotEmpty) { + stream.addAll(buffer); + } + }); + return stream; + } + + @override + void encode(_DerStream derOut) { + throw ArgumentError.value('Not Implemented'); + } + + List? asnEncode() { + return super.asn1Encode(toArray()); + } +} + +class _Asn1SequenceCollection extends _Asn1Encode { + _Asn1SequenceCollection(_Asn1Sequence sequence) { + _id = _DerObjectID.getID(sequence[0]); + _value = (sequence[1] as _Asn1Tag).getObject(); + if (sequence.count == 3) { + _attributes = sequence[2] as _DerSet?; + } + } + _DerObjectID? _id; + _Asn1? _value; + _Asn1Set? _attributes; + @override + _Asn1 getAsn1() { + final _Asn1EncodeCollection collection = + _Asn1EncodeCollection(<_Asn1Encode?>[_id, _DerTag(0, _value)]); + if (_attributes != null) { + collection._encodableObjects.add(_attributes); + } + return _DerSequence(collection: collection); + } +} + +class _Asn1SequenceHelper implements _IAsn1Collection { + _Asn1SequenceHelper(_Asn1Sequence sequence) { + _sequence = sequence; + _max = sequence.count; + } + //Fields + _Asn1Sequence? _sequence; + int? _max; + int? _index; + //Implementation + _IAsn1? readObject() { + if (_index == _max) { + return null; + } + final _IAsn1? obj = _sequence![_index!]; + _index = _index! + 1; + if (obj is _Asn1Sequence) { + return obj.parser; + } + if (obj is _Asn1Set) { + return obj.parser; + } + return obj; + } + + _Asn1? getAsn1() { + return _sequence; + } +} + +class _Asn1Set extends _Asn1 { + _Asn1Set([int? capacity]) { + _objects = capacity != null + ? List.generate(capacity, (i) => null) + : []; + } + //Fields + late List _objects; + + //Properties + _IAsn1SetHelper get parser { + return _Asn1SetHelper(this); + } + + _Asn1Encode? operator [](int index) => _objects[index]; + + //Implementation + @override + int get hashCode { + int hc = _objects.length; + _objects.forEach((o) { + hc *= 17; + if (o == null) { + hc ^= _DerNull.value.getAsn1Hash(); + } else { + hc ^= o.hashCode; + } + }); + return hc; + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _Asn1Set) { + if (_objects.length != asn1._objects.length) { + return false; + } + for (int i = 0; i < _objects.length; i++) { + final _Asn1? o1 = getCurrentSet(_objects[i]).getAsn1(); + final _Asn1? o2 = getCurrentSet(asn1._objects[i]).getAsn1(); + if (o1 != o2) { + return false; + } + } + } else { + return false; + } + return true; + } + + _Asn1Encode getCurrentSet(dynamic e) { + if (e is _Asn1Encode) { + return e; + } else { + return _DerNull.value; + } + } + + bool lessThanOrEqual(List a, List b) { + final int len = min(a.length, b.length); + for (int i = 0; i != len; ++i) { + if (a[i] != b[i]) { + return a[i] < b[i]; + } + } + return len == a.length; + } + + void addObject(_Asn1Encode? obj) { + _objects.add(obj); + } + + @override + String toString() { + return _objects.toString(); + } + + @override + void encode(_DerStream derOut) { + throw ArgumentError.value('Not Implemented'); + } + + void sortObjects() { + if (_objects.length > 1) { + bool swapped = true; + int lastSwap = _objects.length - 1; + while (swapped) { + int index = 0; + int swapIndex = 0; + List? a = (_objects[0] as _Asn1Encode).getEncoded(); + swapped = false; + while (index != lastSwap) { + final List b = + (_objects[index + 1] as _Asn1Encode).getEncoded()!; + if (lessThanOrEqual(a!, b)) { + a = b; + } else { + final dynamic o = _objects[index]; + _objects[index] = _objects[index + 1]; + _objects[index + 1] = o; + swapped = true; + swapIndex = index; + } + index++; + } + lastSwap = swapIndex; + } + } + } + + //static methods + static _Asn1Set? getAsn1Set(dynamic obj, [bool? isExplicit]) { + _Asn1Set? result; + if (isExplicit == null) { + if (obj == null || obj is _Asn1Set) { + result = obj; + } else if (obj is _IAsn1SetHelper) { + result = _Asn1Set.getAsn1Set(obj.getAsn1()); + } else if (obj is List) { + result = + _Asn1Set.getAsn1Set(_Asn1Stream(_StreamReader(obj)).readAsn1()); + } else if (obj is _Asn1Encode) { + final _Asn1? asn1 = obj.getAsn1(); + if (asn1 != null && asn1 is _Asn1Set) { + result = asn1; + } + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid entry in sequence'); + } + } else if (obj is _Asn1Tag) { + final _Asn1? inner = obj.getObject(); + if (isExplicit) { + if (!obj._isExplicit!) { + throw ArgumentError.value(obj, 'obj', 'Tagged object is implicit.'); + } + result = (inner is _Asn1Set) ? inner : null; + } else if (obj._isExplicit! && inner is _Asn1Encode) { + result = _DerSet(array: <_Asn1Encode?>[inner]); + } else if (inner is _Asn1Set) { + result = inner; + } else if (inner is _Asn1Sequence) { + final _Asn1EncodeCollection collection = _Asn1EncodeCollection(); + inner._objects!.forEach((entry) { + collection._encodableObjects.add(entry); + }); + result = _DerSet(collection: collection, isSort: false); + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid entry in sequence'); + } + } + return result; + } +} + +class _Asn1SetHelper implements _IAsn1SetHelper { + _Asn1SetHelper(_Asn1Set outer) { + _set = outer; + _max = outer._objects.length; + } + _Asn1Set? _set; + int? _max; + int? _index; + _IAsn1? readObject() { + if (_index == _max) { + return null; + } else { + final _Asn1Encode? obj = _set![_index!]; + _index = _index! + 1; + if (obj is _Asn1Sequence) { + return obj.parser; + } + if (obj is _Asn1Set) { + return obj.parser; + } + return obj; + } + } + + _Asn1? getAsn1() { + return _set; + } +} + +class _Asn1Tag extends _Asn1 implements _IAsn1Tag { + _Asn1Tag(int? tagNumber, _Asn1Encode? asn1Encode, [bool? isExplicit]) { + _isExplicit = isExplicit ?? true; + _tagNumber = tagNumber; + _object = asn1Encode; + } + //Fields + int? _tagNumber; + bool? _isExplicit; + _Asn1Encode? _object; + //Properties + int? get tagNumber { + return _tagNumber; + } + + //Static methods + static _Asn1Tag? getTag(dynamic obj, [bool? isExplicit]) { + if (isExplicit != null && obj is _Asn1Tag) { + if (isExplicit) { + return obj.getObject() as _Asn1Tag?; + } + throw ArgumentError.value(obj, 'obj', 'Explicit tag is not used'); + } else { + if (obj == null || obj is _Asn1Tag) { + return obj; + } + throw ArgumentError.value(obj, 'obj', 'Invalid entry in sequence'); + } + } + + //Implementation + @override + bool operator ==(Object asn1) { + if (asn1 is _Asn1Tag) { + return _tagNumber == asn1._tagNumber && + _isExplicit == asn1._isExplicit && + getObject() == asn1.getObject(); + } else { + return false; + } + } + + @override + int get hashCode { + int code = _tagNumber.hashCode; + if (_object != null) { + code ^= _object.hashCode; + } + return code; + } + + @override + String toString() { + return '[' + _tagNumber.toString() + ']' + _object.toString(); + } + + @override + void encode(_DerStream stream) { + throw ArgumentError.value(stream, 'stream', 'Not Implemented'); + } + + _Asn1? getObject() { + if (_object != null) { + return _object!.getAsn1(); + } + return null; + } + + _IAsn1? getParser(int tagNumber, bool isExplicit) { + switch (tagNumber) { + case _Asn1Tags.setTag: + return _Asn1Set.getAsn1Set(this, isExplicit)!.parser; + case _Asn1Tags.sequence: + return _Asn1Sequence.getSequence(this, isExplicit)!.parser; + case _Asn1Tags.octetString: + return _Asn1Octet.getOctetString(this, isExplicit)!.parser; + } + if (isExplicit) { + return getObject(); + } + throw ArgumentError.value( + tagNumber, 'tagNumber', 'Implicit tagging is not supported'); + } +} + +class _Asn1DerStream extends _DerStream { + _Asn1DerStream([List? stream]) { + if (stream != null) { + _stream = stream; + } else { + _stream = []; + } + } +} + +class _GeneralizedTime extends _Asn1 { + _GeneralizedTime(List bytes) { + _time = utf8.decode(bytes); + } + //Fields + late String _time; + //Implementation + DateTime? toDateTime() { + return DateTime.tryParse(_time); + } + + @override + void encode(_DerStream stream) { + stream.writeEncoded(_Asn1Tags.generalizedTime, utf8.encode(_time)); + } + + @override + bool operator ==(Object asn1Object) { + throw ArgumentError.value('Not implemented'); + } + + @override + int get hashCode { + return _time.hashCode; + } +} + +class _OctetStream extends _StreamReader { + _OctetStream(_Asn1Parser? helper) : super([]) { + _helper = helper; + _first = true; + } + _Asn1Parser? _helper; + _StreamReader? _stream; + late bool _first; + //Implementation + @override + int? read(List buffer, int offset, int count) { + if (_stream == null) { + if (!_first) { + return 0; + } + final _IAsn1? octet = _helper!.readObject(); + if (octet != null && octet is _IAsn1Octet) { + _first = false; + _stream = octet.getOctetStream(); + } else { + return 0; + } + } + int totalRead = 0; + bool isContinue = true; + int? result; + while (isContinue) { + final int numRead = + _stream!.read(buffer, offset + totalRead, count - totalRead)!; + if (numRead > 0) { + totalRead += numRead; + if (totalRead == count) { + result = totalRead; + isContinue = false; + } + } else { + final _IAsn1? octet = _helper!.readObject(); + if (octet != null && octet is _IAsn1Octet) { + _stream = octet.getOctetStream(); + } else { + _stream = null; + result = totalRead; + isContinue = false; + } + } + } + return result; + } + + @override + int? readByte() { + if (_stream == null) { + if (!_first) { + return 0; + } + final _IAsn1? octet = _helper!.readObject(); + if (octet != null && octet is _IAsn1Octet) { + _first = false; + _stream = octet.getOctetStream(); + } else { + return 0; + } + } + bool isContinue = true; + int? result; + while (isContinue) { + final int value = _stream!.readByte()!; + if (value >= 0) { + result = value; + isContinue = false; + } else { + final _IAsn1? octet = _helper!.readObject(); + if (octet != null && octet is _IAsn1Octet) { + _stream = octet.getOctetStream(); + } else { + _stream = null; + result = -1; + isContinue = false; + } + } + } + return result; + } +} + +class _Asn1Constants { + static const String nullValue = 'NULL'; + static const String der = 'DER'; + static const String desEde = 'DESede'; + static const String des = 'DES'; + static const String rsa = 'RSA'; + static const String pkcs7 = 'PKCS7'; + static int getHashCode(List? data) { + if (data == null) { + return 0; + } + int i = data.length; + int hc = i + 1; + while (--i >= 0) { + hc = (hc * 257).toSigned(32); + hc = (hc ^ data[i].toUnsigned(8)).toSigned(32); + } + return hc; + } + + static bool areEqual(List? a, List? b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + return haveSameContents(a, b); + } + + static bool haveSameContents(List a, List b) { + int i = a.length; + if (i != b.length) { + return false; + } + while (i != 0) { + --i; + if (a[i] != b[i]) { + return false; + } + } + return true; + } + + static List clone(List data) { + return List.generate(data.length, (i) => data[i]); + } + + static void uInt32ToBe(int n, List bs, int off) { + bs[off] = (n >> 24).toUnsigned(8); + bs[off + 1] = (n >> 16).toUnsigned(8); + bs[off + 2] = (n >> 8).toUnsigned(8); + bs[off + 3] = n.toUnsigned(8); + } + + static int beToUInt32(List bs, int off) { + return bs[off].toUnsigned(8) << 24 | + bs[off + 1].toUnsigned(8) << 16 | + bs[off + 2].toUnsigned(8) << 8 | + bs[off + 3].toUnsigned(8); + } +} + +class _Asn1Tags { + static const int boolean = 0x01; + static const int integer = 0x02; + static const int bitString = 0x03; + static const int octetString = 0x04; + static const int nullValue = 0x05; + static const int objectIdentifier = 0x06; + static const int enumerated = 0x0a; + static const int sequence = 0x10; + static const int setTag = 0x11; + static const int printableString = 0x13; + static const int teleText = 0x14; + static const int asciiString = 0x16; + static const int utcTime = 0x17; + static const int generalizedTime = 0x18; + static const int bmpString = 0x1e; + static const int utf8String = 0x0c; + static const int constructed = 0x20; + static const int tagged = 0x80; +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_parser.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_parser.dart new file mode 100644 index 000000000..3f0a5d58d --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_parser.dart @@ -0,0 +1,136 @@ +part of pdf; + +class _Asn1Parser { + _Asn1Parser(_StreamReader stream, [int? limit]) { + _stream = stream; + _limit = limit != null ? limit : _Asn1Stream.getLimit(stream); + _buffers = List.generate(16, (i) => []); + } + + //Fields + _StreamReader? _stream; + int? _limit; + List>? _buffers; + + //Implementation + _IAsn1 readImplicit(bool? constructed, int tagNumber) { + if (_stream is _Asn1LengthStream) { + if (!constructed!) { + throw ArgumentError.value('Invalid length specified'); + } + return readIndefinite(tagNumber); + } + if (constructed!) { + switch (tagNumber) { + case _Asn1Tags.setTag: + return _DerSetHelper(this); + case _Asn1Tags.sequence: + return _DerSequenceHelper(this); + case _Asn1Tags.octetString: + return _BerOctetHelper(this); + } + } else { + switch (tagNumber) { + case _Asn1Tags.setTag: + throw ArgumentError.value(tagNumber, 'tagNumber', + 'Constructed encoding is not used in the set'); + case _Asn1Tags.sequence: + throw ArgumentError.value(tagNumber, 'tagNumber', + 'Constructed encoding is not used in the sequence'); + case _Asn1Tags.octetString: + return _DerOctetHelper(_stream as _Asn1StreamHelper?); + } + } + throw ArgumentError.value( + tagNumber, 'tagNumber', 'Implicit tagging is not supported'); + } + + _Asn1 readTaggedObject(bool constructed, int? tagNumber) { + if (!constructed) { + final _Asn1StreamHelper stream = _stream as _Asn1StreamHelper; + return _DerTag(tagNumber, _DerOctet(stream.toArray()), false); + } + final _Asn1EncodeCollection collection = readCollection(); + if (_stream is _Asn1LengthStream) { + return collection.count == 1 + ? _DerTag(tagNumber, collection[0], true) + : _DerTag(tagNumber, _BerSequence.fromCollection(collection), false); + } + return collection.count == 1 + ? _DerTag(tagNumber, collection[0], true) + : _DerTag(tagNumber, _DerSequence.fromCollection(collection), false); + } + + _IAsn1 readIndefinite(int tagValue) { + switch (tagValue) { + case _Asn1Tags.octetString: + return _BerOctetHelper(this); + case _Asn1Tags.sequence: + return _BerSequenceHelper(this); + default: + throw ArgumentError.value( + tagValue, 'tagValue', 'Invalid entry in sequence'); + } + } + + void setEndOfFile(bool enabled) { + if (_stream is _Asn1LengthStream) { + (_stream as _Asn1LengthStream).setEndOfFileOnStart(enabled); + } + } + + _Asn1EncodeCollection readCollection() { + final _Asn1EncodeCollection collection = _Asn1EncodeCollection(); + _IAsn1? obj; + while ((obj = readObject()) != null) { + collection._encodableObjects.add(obj!.getAsn1()); + } + return collection; + } + + _IAsn1? readObject() { + final int? tag = _stream!.readByte(); + if (tag == -1) { + return null; + } + setEndOfFile(false); + final int tagNumber = _Asn1Stream.readTagNumber(_stream, tag!); + final bool isConstructed = (tag & _Asn1Tags.constructed) != 0; + final int length = _Asn1Stream.getLength(_stream!, _limit); + if (length < 0) { + if (!isConstructed) { + throw ArgumentError.value(length, 'length', 'Invalid length specified'); + } + final _Asn1LengthStream stream = _Asn1LengthStream(_stream, _limit); + final _Asn1Parser helper = _Asn1Parser(stream, _limit); + if ((tag & _Asn1Tags.tagged) != 0) + return _BerTagHelper(true, tagNumber, helper); + return helper.readIndefinite(tagNumber); + } else { + final _Asn1StreamHelper stream = _Asn1StreamHelper(_stream, length); + if ((tag & _Asn1Tags.tagged) != 0) { + return _BerTagHelper(isConstructed, tagNumber, + _Asn1Parser(stream, _Asn1Stream.getLimit(stream))); + } + if (isConstructed) { + switch (tagNumber) { + case _Asn1Tags.octetString: + return _BerOctetHelper( + _Asn1Parser(stream, _Asn1Stream.getLimit(stream))); + case _Asn1Tags.sequence: + return _DerSequenceHelper( + _Asn1Parser(stream, _Asn1Stream.getLimit(stream))); + case _Asn1Tags.setTag: + return _DerSetHelper( + _Asn1Parser(stream, _Asn1Stream.getLimit(stream))); + default: + return null; + } + } + if (tagNumber == _Asn1Tags.octetString) { + return _DerOctetHelper(stream); + } + return _Asn1Stream.getPrimitiveObject(tagNumber, stream, _buffers); + } + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_stream.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_stream.dart new file mode 100644 index 000000000..3200cda52 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_stream.dart @@ -0,0 +1,431 @@ +part of pdf; + +class _Asn1Stream { + _Asn1Stream(_StreamReader stream, [int? limit]) { + _stream = stream; + _limit = limit ?? getLimit(stream); + _buffers = List.generate(16, (i) => []); + } + int? _limit; + List>? _buffers; + _StreamReader? _stream; + + static int? getLimit(_StreamReader input) { + if (input is _Asn1BaseStream) { + return input.remaining; + } else { + return input.length! - input.position; + } + } + + static _Asn1? getPrimitiveObject( + int tagNumber, _Asn1StreamHelper stream, List>? buffers) { + switch (tagNumber) { + case _Asn1Tags.boolean: + return _DerBoolean.fromBytes(getBytes(stream, buffers!)); + case _Asn1Tags.enumerated: + return _DerCatalogue(getBytes(stream, buffers!)); + case _Asn1Tags.objectIdentifier: + return _DerObjectID.fromOctetString(getBytes(stream, buffers!)); + } + final List bytes = stream.toArray(); + switch (tagNumber) { + case _Asn1Tags.bitString: + return _DerBitString.fromAsn1Octets(bytes); + case _Asn1Tags.bmpString: + return _DerBmpString(bytes); + case _Asn1Tags.generalizedTime: + return _GeneralizedTime(bytes); + case _Asn1Tags.asciiString: + return _DerAsciiString.fromBytes(bytes); + case _Asn1Tags.integer: + return _DerInteger(bytes); + case _Asn1Tags.nullValue: + return _DerNull.value; + case _Asn1Tags.octetString: + return _DerOctet(bytes); + case _Asn1Tags.printableString: + return _DerPrintableString(utf8.decode(bytes)); + case _Asn1Tags.teleText: + return _DerTeleText(String.fromCharCodes(bytes)); + case _Asn1Tags.utcTime: + return _DerUtcTime(bytes); + case _Asn1Tags.utf8String: + return _DerUtf8String(utf8.decode(bytes)); + } + return null; + } + + static List getBytes(_Asn1StreamHelper stream, List> buffers) { + final int length = stream.remaining; + if (length >= buffers.length) { + stream.toArray(); + } + List bytes = buffers[length]; + if (bytes.isEmpty) { + bytes = buffers[length] = List.generate(length, (i) => 0); + } + stream.readAll(bytes); + return bytes; + } + + static int readTagNumber(_StreamReader? stream, int tagNumber) { + int result = tagNumber & 0x1f; + if (result == 0x1f) { + result = 0; + int b = stream!.readByte()!; + if ((b & 0x7f) == 0) throw Exception('Invalid tag number specified'); + while ((b >= 0) && ((b & 0x80) != 0)) { + result |= (b & 0x7f); + result <<= 7; + b = stream.readByte()!; + } + if (b < 0) throw new Exception('End of file detected'); + result |= (b & 0x7f); + } + return result; + } + + static int getLength(_StreamReader stream, int? limit) { + int length = stream.readByte()!; + if (length < 0) { + throw Exception('End of file detected'); + } + if (length == 0x80) { + return -1; + } + if (length > 127) { + final int size = length & 0x7f; + if (size > 4) { + throw Exception('Invalid length detected: $size'); + } + length = 0; + for (int i = 0; i < size; i++) { + final int next = stream.readByte()!; + if (next < 0) { + throw Exception('End of file detected'); + } + length = (length << 8) + next; + } + if (length < 0) { + throw Exception('Invalid length or corrupted input stream detected'); + } + if (length >= limit!) { + throw Exception('Out of bound or corrupted stream detected'); + } + } + return length; + } + + _Asn1? buildObject(int tag, int tagNumber, int length) { + final bool isConstructed = (tag & _Asn1Tags.constructed) != 0; + final _Asn1StreamHelper stream = new _Asn1StreamHelper(_stream, length); + if ((tag & _Asn1Tags.tagged) != 0) + return new _Asn1Parser(stream).readTaggedObject(isConstructed, tagNumber); + if (isConstructed) { + switch (tagNumber) { + case _Asn1Tags.octetString: + return _BerOctet(getBytesfromAsn1EncodeCollection( + getDerEncodableCollection(stream))); + case _Asn1Tags.sequence: + return createDerSequence(stream); + case _Asn1Tags.setTag: + return createDerSet(stream); + } + } + return getPrimitiveObject(tagNumber, stream, _buffers); + } + + _Asn1EncodeCollection getEncodableCollection() { + final _Asn1EncodeCollection objects = _Asn1EncodeCollection(); + _Asn1? asn1Object; + while ((asn1Object = readAsn1()) != null) { + objects._encodableObjects.add(asn1Object); + } + return objects; + } + + _Asn1? readAsn1() { + final int tag = _stream!.readByte()!; + if (tag > 0) { + final int tagNumber = readTagNumber(_stream, tag); + final bool isConstructed = (tag & _Asn1Tags.constructed) != 0; + final int length = getLength(_stream!, _limit); + if (length < 0) { + if (!isConstructed) { + throw ArgumentError.value( + length, 'length', 'Encodeing length is invalid'); + } + final _Asn1Parser sp = + _Asn1Parser(_Asn1LengthStream(_stream, _limit), _limit); + if ((tag & _Asn1Tags.tagged) != 0) { + return _BerTagHelper(true, tagNumber, sp).getAsn1(); + } + switch (tagNumber) { + case _Asn1Tags.octetString: + return _BerOctetHelper(sp).getAsn1(); + case _Asn1Tags.sequence: + return _BerSequenceHelper(sp).getAsn1(); + default: + throw ArgumentError.value( + tagNumber, 'tag', 'Invalid object in the sequence'); + } + } else { + return buildObject(tag, tagNumber, length); + } + } else if (tag < 0) { + return null; + } else if (tag == 0) { + throw ArgumentError.value(tag, 'tag', 'End of contents is invalid'); + } + return null; + } + + List getBytesfromAsn1EncodeCollection(_Asn1EncodeCollection octets) { + final List result = []; + for (int i = 0; i < octets.count; i++) { + final _DerOctet o = octets[i] as _DerOctet; + result.addAll(o.getOctets()!); + } + return result; + } + + _Asn1EncodeCollection getDerEncodableCollection(_Asn1StreamHelper stream) { + return _Asn1Stream(stream).getEncodableCollection(); + } + + _DerSequence createDerSequence(_Asn1StreamHelper stream) { + return _DerSequence(collection: getDerEncodableCollection(stream)); + } + + _DerSet createDerSet(_Asn1StreamHelper stream) { + return _DerSet( + collection: getDerEncodableCollection(stream), isSort: false); + } +} + +class _Asn1BaseStream extends _StreamReader { + _Asn1BaseStream(_StreamReader? stream, int? lm) { + input = stream; + limit = lm; + } + int? limit; + late bool closed; + _StreamReader? input; + int? get remaining => limit; + bool get canRead => !closed; + bool get canSeek => false; + bool get canWrite => false; + void close() { + closed = true; + } + + void setParentEndOfFileDetect(bool isDetect) { + if (input is _Asn1LengthStream) { + (input as _Asn1LengthStream).setEndOfFileOnStart(isDetect); + } + } + + @override + int read(List buffer, int offset, int count) { + int pos = offset; + try { + final int end = offset + count; + while (pos < end) { + final int b = input!.readByte()!; + if (b == -1) { + break; + } + buffer[pos++] = b; + } + } catch (e) { + if (pos == offset) { + throw Exception('End of stream'); + } + } + return pos - offset; + } +} + +class _Asn1StreamHelper extends _Asn1BaseStream { + _Asn1StreamHelper(_StreamReader? stream, int length) : super(stream, length) { + if (length < 0) { + throw Exception('Invalid length specified.'); + } + _remaining = length; + if (length == 0) { + setParentEndOfFileDetect(true); + } + } + + late int _remaining; + @override + int get remaining => _remaining; + + @override + int readByte() { + if (_remaining == 0) { + return -1; + } + final int result = input!.readByte()!; + if (result < 0) { + throw ArgumentError.value(result, 'result', 'Invalid length in bytes'); + } + _remaining -= 1; + if (_remaining == 0) { + setParentEndOfFileDetect(true); + } + return result; + } + + @override + int read(List bytes, int offset, int length) { + if (remaining == 0) { + return 0; + } + final int count = super.read(bytes, offset, min(length, _remaining)); + if (count < 1) { + throw ArgumentError.value(count, 'count', 'Object truncated'); + } + if ((_remaining -= count) == 0) { + setParentEndOfFileDetect(true); + } + return count; + } + + List toArray() { + if (_remaining == 0) { + return []; + } + final List bytes = List.generate(_remaining, (i) => 0); + if ((_remaining -= readData(bytes, 0, bytes.length)) != 0) { + throw ArgumentError.value(bytes, 'bytes', 'Object truncated'); + } + setParentEndOfFileDetect(true); + return bytes; + } + + void readAll(List bytes) { + if (_remaining != bytes.length) { + throw ArgumentError.value(bytes, 'bytes', 'Invalid length in bytes'); + } + if ((_remaining -= readData(bytes, 0, bytes.length)) != 0) { + throw ArgumentError.value(bytes, 'bytes', 'Object truncated'); + } + setParentEndOfFileDetect(true); + } + + int readData(List bytes, int offset, int length) { + int total = 0; + while (total < length) { + final int count = this.read(bytes, offset + total, length - total); + if (count < 1) { + break; + } + total += count; + } + return total; + } +} + +class _Asn1LengthStream extends _Asn1BaseStream { + _Asn1LengthStream(_StreamReader? stream, int? limit) : super(stream, limit) { + byte = requireByte(); + checkEndOfFile(); + } + + int? byte; + bool isEndOfFile = true; + + int requireByte() { + final int value = input!.readByte()!; + if (value < 0) { + throw ArgumentError.value(value, 'value', 'Invalid data in stream'); + } + return value; + } + + void setEndOfFileOnStart(bool isEOF) { + isEndOfFile = isEOF; + if (isEndOfFile) { + checkEndOfFile(); + } + } + + bool checkEndOfFile() { + if (byte == 0x00) { + final int extra = requireByte(); + if (extra != 0) throw Exception("Invalid content"); + byte = -1; + setParentEndOfFileDetect(true); + return true; + } + return byte! < 0; + } + + @override + int? readByte() { + if (isEndOfFile && checkEndOfFile()) { + return -1; + } + final int? result = byte; + byte = requireByte(); + return result; + } + + @override + int read(List buffer, int offset, int count) { + if (isEndOfFile || count <= 1) { + return super.read(buffer, offset, count); + } + if (byte! < 0) { + return 0; + } + final int numRead = input!.read(buffer, offset + 1, count - 1)!; + if (numRead <= 0) { + throw new Exception(); + } + buffer[offset] = byte!; + byte = requireByte(); + return numRead + 1; + } +} + +class _PushStream extends _StreamReader { + _PushStream(_StreamReader stream) : super(stream._data) { + _stream = stream; + _buffer = -1; + } + //Fields + late _StreamReader _stream; + int? _buffer; + int get position => _stream.position; + set position(int value) { + _stream.position = value; + } + + //Implementation + @override + int? readByte() { + if (_buffer != -1) { + final int? temp = _buffer; + _buffer = -1; + return temp; + } + return _stream.readByte(); + } + + @override + int? read(List buffer, int offset, int count) { + if (_buffer != -1) { + final int? temp = _buffer; + _buffer = -1; + return temp; + } + return _stream.read(buffer, offset, count); + } + + void unread(int b) { + _buffer = b & 0xFF; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/ber.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/ber.dart new file mode 100644 index 000000000..569609d08 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/ber.dart @@ -0,0 +1,175 @@ +part of pdf; + +class _BerOctet extends _DerOctet { + _BerOctet(List bytes) : super(bytes) {} + _BerOctet.fromCollection(List<_Asn1Encode> octets) + : super(_BerOctet.getBytes(octets)) { + _octets = octets; + } + + //Fields + late dynamic _octets; + + //Implementation + @override + List? getOctets() { + return _value; + } + + List<_DerOctet> generateOctets() { + final List<_DerOctet> collection = <_DerOctet>[]; + for (int i = 0; i < _value!.length; i += 1000) { + final int endIndex = min(_value!.length, i + 1000); + collection.add(_DerOctet(List.generate(endIndex - i, + (index) => ((i + index) < _value!.length) ? _value![i + index] : 0))); + } + return collection; + } + + @override + void encode(_DerStream stream) { + if (stream is _Asn1DerStream) { + stream._stream!.add(_Asn1Tags.constructed | _Asn1Tags.octetString); + stream._stream!.add(0x80); + _octets.forEach((octet) { + stream.writeObject(octet); + }); + stream._stream!.add(0x00); + stream._stream!.add(0x00); + } else { + super.encode(stream); + } + } + + static _BerOctet getBerOctet(_Asn1Sequence sequence) { + final List<_Asn1Encode> collection = <_Asn1Encode>[]; + for (int i = 0; i < sequence._objects!.length; i++) { + final dynamic entry = sequence._objects![i]; + if (entry != null && entry is _Asn1Encode) { + collection.add(entry); + } + } + return _BerOctet.fromCollection(collection); + } + + static List getBytes(List<_Asn1Encode> octets) { + final List result = []; + if (octets.isNotEmpty) { + octets.forEach((o) { + if (o is _DerOctet) { + result.addAll(o.getOctets()!); + } + }); + } + return result; + } +} + +class _BerOctetHelper implements _IAsn1Octet { + _BerOctetHelper(_Asn1Parser helper) { + _helper = helper; + } + //Fields + _Asn1Parser? _helper; + @override + _StreamReader getOctetStream() { + return _OctetStream(_helper); + } + + @override + _Asn1 getAsn1() { + return _BerOctet(readAll(getOctetStream())); + } + + List readAll(_StreamReader stream) { + final List output = []; + final List bytes = List.generate(512, (i) => 0); + int? index; + while ((index = stream.read(bytes, 0, bytes.length))! > 0) { + output.addAll(bytes.sublist(0, index)); + } + return output; + } + + @override + int get hashCode => _helper.hashCode; + + @override + bool operator ==(Object other) { + if (other is _BerOctetHelper) { + return other._helper == _helper; + } else { + return false; + } + } +} + +class _BerTagHelper implements _IAsn1Tag { + _BerTagHelper(bool isConstructed, int tagNumber, _Asn1Parser helper) { + _isConstructed = isConstructed; + _tagNumber = tagNumber; + _helper = helper; + } + //Fields + bool? _isConstructed; + int? _tagNumber; + late _Asn1Parser _helper; + //Properties + int? get tagNumber => _tagNumber; + //Implements + _IAsn1? getParser(int tagNumber, bool isExplicit) { + if (isExplicit) { + if (!_isConstructed!) { + throw ArgumentError.value( + isExplicit, 'isExplicit', 'Implicit tags identified'); + } + return _helper.readObject(); + } + return _helper.readImplicit(_isConstructed, tagNumber); + } + + _Asn1 getAsn1() { + return _helper.readTaggedObject(_isConstructed!, _tagNumber); + } +} + +class _BerSequence extends _DerSequence { + _BerSequence({List<_Asn1Encode>? array, _Asn1EncodeCollection? collection}) + : super(array: array, collection: collection) {} + _BerSequence.fromObject(_Asn1Encode? object) : super.fromObject(object) {} + static _BerSequence empty = _BerSequence(); + static _BerSequence fromCollection(_Asn1EncodeCollection collection) { + return collection.count < 1 ? empty : _BerSequence(collection: collection); + } + + @override + void encode(_DerStream stream) { + if (stream is _Asn1DerStream) { + stream._stream!.add(_Asn1Tags.sequence | _Asn1Tags.constructed); + stream._stream!.add(0x80); + _objects!.forEach((entry) { + if (entry is _Asn1Encode) { + stream.writeObject(entry); + } + }); + stream._stream!.add(0x00); + stream._stream!.add(0x00); + } else { + super.encode(stream); + } + } +} + +class _BerSequenceHelper implements _IAsn1Collection { + _BerSequenceHelper(_Asn1Parser helper) { + _helper = helper; + } + late _Asn1Parser _helper; + _IAsn1? readObject() { + return _helper.readObject(); + } + + _Asn1 getAsn1() { + return _BerSequence.fromCollection(_helper.readCollection()); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/der.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/der.dart new file mode 100644 index 000000000..195998ce5 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/der.dart @@ -0,0 +1,971 @@ +part of pdf; + +class _IAsn1String { + String? getString([List? bytes]) => null; +} + +class _IAsn1SetHelper implements _IAsn1 { + _IAsn1? readObject() => null; + _Asn1? getAsn1() => null; +} + +class _ObjectIdentityToken { + _ObjectIdentityToken(String? id) { + _id = id; + } + String? _id; + int _index = 0; + bool get hasMoreTokens => _index != -1; + String? nextToken() { + if (_index == -1) { + return null; + } + final int endIndex = _id!.indexOf('.', _index); + if (endIndex == -1) { + final String lastToken = _id!.substring(_index); + _index = -1; + return lastToken; + } + final String nextToken = _id!.substring(_index, endIndex); + _index = endIndex + 1; + return nextToken; + } +} + +abstract class _DerString extends _Asn1 implements _IAsn1String { + String? getString([List? bytes]); + + @override + String toString() { + return getString()!; + } + + @override + int get hashCode { + return getString().hashCode; + } + + @override + bool operator ==(Object other) { + if (other is _DerString) { + return getString() == other.getString(); + } else { + return false; + } + } +} + +class _DerAsciiString extends _DerString { + //Constructor + _DerAsciiString.fromBytes(List bytes) { + _value = utf8.decode(bytes); + } + + _DerAsciiString(String value, bool isValid) { + if (isValid && !isAsciiString(value)) { + throw ArgumentError.value(value, 'value', 'Invalid characters found'); + } + _value = value; + } + //Fields + String? _value; + + //Implementation + @override + String? getString([List? bytes]) { + return _value; + } + + @override + void encode(_DerStream stream) { + stream.writeEncoded(_Asn1Tags.asciiString, getOctets()); + } + + @override + int get hashCode { + return _value.hashCode; + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _DerAsciiString) { + return _value == asn1._value; + } else { + return false; + } + } + + List? asnEncode() { + return super.asn1Encode(getOctets()); + } + + List getOctets() { + return utf8.encode(_value!); + } + + //Static methods + static bool isAsciiString(String value) { + for (int i = 0; i < value.length; i++) { + if (value.codeUnitAt(i) > 0x007f) { + return false; + } + } + return true; + } +} + +class _DerBitString extends _DerString { + _DerBitString(List data, int? pad) { + _data = data; + _extra = pad ?? 0; + _table = [ + '0', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + 'A', + 'B', + 'C', + 'D', + 'E', + 'F' + ]; + } + _DerBitString.fromAsn1(_Asn1Encode asn1) { + _data = asn1.getDerEncoded(); + } + + //Fields + List? _data; + int? _extra; + late List _table; + + //Implementation + List? getBytes() { + return _data; + } + + @override + void encode(_DerStream stream) { + final List bytes = + List.generate(getBytes()!.length + 1, (i) => 0); + bytes[0] = _extra!; + List.copyRange(bytes, 1, getBytes()!, 0, bytes.length - 1); + stream.writeEncoded(_Asn1Tags.bitString, bytes); + } + + @override + int get hashCode { + return _extra.hashCode ^ _Asn1Constants.getHashCode(_data); + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _DerBitString) { + return _extra == asn1._extra && + _Asn1Constants.areEqual(_data, asn1._data); + } else { + return false; + } + } + + @override + String getString([List? bytes]) { + String result = '#'; + final List str = getDerEncoded()!; + for (int i = 0; i != str.length; i++) { + final int ubyte = str[i].toUnsigned(16); + result += _table[(ubyte >> 4) & 0xf]; + result += _table[str[i] & 0xf]; + } + return result; + } + + //Static methods + static _DerBitString fromAsn1Octets(List bytes) { + final int? pad = bytes[0]; + final List data = List.generate(bytes.length - 1, (i) => 0); + List.copyRange(data, 0, bytes, 1, data.length + 1); + return _DerBitString(data, pad); + } + + static _DerBitString? getDetBitString(dynamic obj) { + if (obj == null) { + return null; + } else if (obj is _DerBitString) { + return obj; + } + throw ArgumentError.value(obj, 'object', 'Invalid Entry'); + } + + static _DerBitString? getDerBitStringFromTag(_Asn1Tag tag, bool isExplicit) { + final _Asn1? asn1 = tag.getObject(); + if (isExplicit || asn1 is _DerBitString) { + return getDetBitString(asn1); + } + return fromAsn1Octets((asn1 as _Asn1Octet).getOctets()!); + } +} + +class _DerBmpString extends _DerString { + _DerBmpString(List bytes) { + String result = ''; + for (int i = 0; i != (bytes.length ~/ 2); i++) { + result += + String.fromCharCode((bytes[2 * i] << 8) | (bytes[2 * i + 1] & 0xff)); + } + _value = result; + } + + //Fields + String? _value; + + //Implementation + @override + String? getString([List? bytes]) { + return _value; + } + + @override + int get hashCode => _value.hashCode; + + @override + bool operator ==(Object asn1) { + if (asn1 is _DerBmpString) { + return _value == asn1._value; + } else { + return false; + } + } + + @override + void encode(_DerStream stream) { + final List bytes = List.generate((_value!.length * 2), (i) => 0); + for (int i = 0; i != _value!.length; i++) { + bytes[2 * i] = (_value!.codeUnitAt(i) >> 8).toUnsigned(8); + bytes[2 * i + 1] = _value!.codeUnitAt(i).toUnsigned(8); + } + stream.writeEncoded(_Asn1Tags.bmpString, bytes); + } +} + +class _DerPrintableString extends _DerString { + _DerPrintableString(String value) { + _value = value; + } + //Fields + String? _value; + //Implementation + @override + String? getString([List? bytes]) { + return _value; + } + + List getBytes() { + return utf8.encode(_value!); + } + + @override + void encode(_DerStream stream) { + stream.writeEncoded(_Asn1Tags.printableString, getBytes()); + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _DerPrintableString) { + return _value == asn1._value; + } else { + return false; + } + } + + @override + int get hashCode => _value.hashCode; +} + +class _DerBoolean extends _Asn1 { + _DerBoolean(bool value) { + _value = value ? 0xff : 0; + } + _DerBoolean.fromBytes(List bytes) { + if (bytes.length != 1) { + throw ArgumentError.value(bytes, 'bytes', 'Invalid length in bytes'); + } + _value = bytes[0]; + } + //Fields + late int _value; + + //Properties + bool get isTrue => _value != 0; + + //Implementation + @override + void encode(_DerStream stream) { + stream.writeEncoded(_Asn1Tags.boolean, [_value]); + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _DerBoolean) { + return isTrue == asn1.isTrue; + } else { + return false; + } + } + + @override + int get hashCode { + return isTrue.hashCode; + } + + @override + String toString() { + return isTrue ? 'TRUE' : 'FALSE'; + } +} + +class _DerInteger extends _Asn1 { + _DerInteger(List? bytes) { + _value = bytes; + } + _DerInteger.fromNumber(BigInt? value) { + if (value == null) { + throw ArgumentError.value(value, 'value', 'Invalid value'); + } + _value = _bigIntToBytes(value); + } + //Fields + List? _value; + + //Properties + BigInt get value => _bigIntFromBytes(_value); + BigInt get positiveValue => _bigIntFromBytes(_value, 1); + + //Implementation + @override + void encode(_DerStream stream) { + stream.writeEncoded(_Asn1Tags.integer, _value); + } + + @override + int get hashCode { + return _Asn1Constants.getHashCode(_value); + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _DerInteger) { + return _Asn1Constants.areEqual(_value, asn1._value); + } else { + return false; + } + } + + @override + String toString() { + return value.toString(); + } + + //Static methods + static _DerInteger? getNumber(dynamic obj) { + if (obj == null || obj is _DerInteger) { + return obj; + } + throw ArgumentError.value(obj, 'obj', 'Invalid entry'); + } + + static _DerInteger? getNumberFromTag(_Asn1Tag tag, bool isExplicit) { + final _Asn1? asn1 = tag.getObject(); + if (isExplicit || asn1 is _DerInteger) { + return getNumber(asn1); + } + return _DerInteger(_Asn1Octet.getOctetStringFromObject(asn1)!.getOctets()); + } +} + +class _DerNull extends _Asn1Null { + _DerNull() : super() { + _bytes = []; + } + //Fields + List? _bytes; + static _DerNull value = _DerNull(); + //Implementation + @override + void encode(_DerStream stream) { + stream.writeEncoded(_Asn1Tags.nullValue, _bytes); + } + + @override + bool operator ==(Object asn1) { + return asn1 is _DerNull; + } + + @override + int get hashCode { + return -1; + } +} + +class _DerObjectID extends _Asn1 { + //Contructors + _DerObjectID(String id) { + if (!isValidIdentifier(id)) { + throw ArgumentError.value(id, 'id', 'Invalid ID'); + } + _id = id; + } + _DerObjectID.fromBytes(List bytes) { + _id = getObjectID(bytes); + _bytes = _Asn1Constants.clone(bytes); + } + String? _id; + List? _bytes; + static List<_DerObjectID?> _objects = + List<_DerObjectID?>.generate(1024, (i) => null); + //Implemnetation + List? getBytes() { + _bytes ??= getOutput(); + return _bytes; + } + + List getOutput() { + List stream = []; + final _ObjectIdentityToken oidToken = _ObjectIdentityToken(_id); + String token = oidToken.nextToken()!; + final int first = int.parse(token) * 40; + token = oidToken.nextToken()!; + if (token.length <= 18) { + stream = writeField(stream, fieldValue: (first + int.parse(token))); + } else { + stream = writeField(stream, + numberValue: BigInt.parse(token) + BigInt.from(first)); + } + while (oidToken.hasMoreTokens) { + token = oidToken.nextToken()!; + if (token.length <= 18) { + stream = writeField(stream, fieldValue: int.parse(token)); + } else { + stream = writeField(stream, numberValue: BigInt.parse(token)); + } + } + return stream; + } + + List writeField(List stream, + {int? fieldValue, BigInt? numberValue}) { + if (fieldValue != null) { + final List result = []; + result.add((fieldValue & 0x7f).toUnsigned(8)); + while (fieldValue! >= 128) { + fieldValue >>= 7; + result.add(((fieldValue & 0x7f) | 0x80).toUnsigned(8)); + } + stream.addAll(result.reversed.toList()); + } else if (numberValue != null) { + final int byteCount = (numberValue.bitLength + 6) ~/ 7; + if (byteCount == 0) { + stream.add(0); + } else { + BigInt value = numberValue; + final List bytes = List.generate(byteCount, (i) => 0); + for (int i = byteCount - 1; i >= 0; i--) { + bytes[i] = ((value.toSigned(32).toInt() & 0x7f) | 0x80).toUnsigned(8); + value = value >> 7; + } + bytes[byteCount - 1] &= 0x7f; + stream.addAll(bytes); + } + } + return stream; + } + + @override + void encode(_DerStream stream) { + stream.writeEncoded(_Asn1Tags.objectIdentifier, getBytes()); + } + + @override + int get hashCode { + return _id.hashCode; + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _DerObjectID) { + return _id == asn1._id; + } else { + return false; + } + } + + @override + String toString() { + return _id!; + } + + //Static methods + static _DerObjectID? getID(dynamic obj) { + if (obj == null || obj is _DerObjectID) { + return obj; + } else if (obj is List) { + return fromOctetString(obj); + } + throw ArgumentError.value(obj, 'obj', 'Illegal object'); + } + + static bool isValidBranchID(String branchID, int start) { + bool isAllowed = false; + int position = branchID.length; + while (--position >= start) { + final String entry = branchID[position]; + if ('0'.codeUnitAt(0) <= entry.codeUnitAt(0) && + entry.codeUnitAt(0) <= '9'.codeUnitAt(0)) { + isAllowed = true; + continue; + } + if (entry == '.') { + if (!isAllowed) { + return false; + } + isAllowed = false; + continue; + } + return false; + } + return isAllowed; + } + + static bool isValidIdentifier(String id) { + if (id.length < 3 || id[1] != '.') { + return false; + } + if (id.codeUnitAt(0) < '0'.codeUnitAt(0) || + id.codeUnitAt(0) > '2'.codeUnitAt(0)) { + return false; + } + return isValidBranchID(id, 2); + } + + static String getObjectID(List bytes) { + String result = ''; + int value = 0; + BigInt? number; + bool first = true; + for (int i = 0; i != bytes.length; i++) { + final int? entry = bytes[i]; + if (value <= 72057594037927808) { + value += (entry! & 0x7f); + if ((entry & 0x80) == 0) { + if (first) { + if (value < 40) { + result += '0'; + } else if (value < 80) { + result += '1'; + value -= 40; + } else { + result += '2'; + value -= 80; + } + first = false; + } + result += '.'; + result += value.toString(); + value = 0; + } else { + value <<= 7; + } + } else { + if (number == null) { + number = BigInt.from(value); + } + number = number | BigInt.from(entry! & 0x7f); + if ((entry & 0x80) == 0) { + if (first) { + result += '2'; + number = number - BigInt.from(80); + first = false; + } + result += '.'; + result += number.toSigned(32).toInt().toString(); + number = null; + value = 0; + } else { + number = number << 7; + } + } + } + return result; + } + + static _DerObjectID? fromOctetString(List bytes) { + final int hashCode = _Asn1Constants.getHashCode(bytes); + final int first = hashCode & 1023; + final _DerObjectID? entry = _objects[first]; + if (entry != null && _Asn1Constants.areEqual(bytes, entry.getBytes())) { + return entry; + } + _objects[first] = _DerObjectID.fromBytes(bytes); + return _objects[first]; + } +} + +class _DerOctet extends _Asn1Octet { + _DerOctet(List bytes) : super(bytes) {} + _DerOctet.fromObject(_Asn1Encode asn1) : super.fromObject(asn1) {} + @override + void encode(_DerStream stream) { + stream.writeEncoded(_Asn1Tags.octetString, _value); + } +} + +class _DerSequence extends _Asn1Sequence { + _DerSequence({List<_Asn1Encode?>? array, _Asn1EncodeCollection? collection}) + : super() { + if (array != null) { + array.forEach((entry) { + _objects!.add(entry); + }); + } else if (collection != null) { + for (int i = 0; i < collection.count; i++) { + _objects!.add(collection[i]); + } + } + } + _DerSequence.fromObject(_Asn1Encode? encode) : super() { + _objects!.add(encode); + } + static _DerSequence fromCollection(_Asn1EncodeCollection collection) { + return collection.count < 1 ? empty : _DerSequence(collection: collection); + } + + static _DerSequence empty = _DerSequence(); + @override + void encode(_DerStream outputStream) { + final _DerStream stream = _DerStream([]); + _objects!.forEach((asn1) { + stream.writeObject(asn1); + }); + outputStream.writeEncoded( + _Asn1Tags.sequence | _Asn1Tags.constructed, stream._stream); + } +} + +class _DerSequenceHelper implements _IAsn1Collection { + _DerSequenceHelper(_Asn1Parser helper) { + _helper = helper; + } + //Fields + late _Asn1Parser _helper; + //Implementation + _IAsn1? readObject() { + return _helper.readObject(); + } + + _Asn1 getAsn1() { + return _DerSequence(collection: _helper.readCollection()); + } +} + +class _DerSet extends _Asn1Set { + //Constructor + _DerSet( + {List<_Asn1Encode?>? array, + _Asn1EncodeCollection? collection, + bool? isSort}) + : super() { + if (array != null) { + array.forEach((asn1) { + addObject(asn1); + }); + sortObjects(); + } else if (collection != null) { + isSort ??= true; + for (int i = 0; i < collection.count; i++) { + addObject(collection[i]); + } + if (isSort) { + sortObjects(); + } + } + } + //Implementation + @override + void encode(_DerStream outputStream) { + final _DerStream stream = _DerStream([]); + _objects.forEach((entry) { + stream.writeObject(entry); + }); + outputStream.writeEncoded( + _Asn1Tags.setTag | _Asn1Tags.constructed, stream._stream); + } +} + +class _DerSetHelper implements _IAsn1SetHelper { + _DerSetHelper(_Asn1Parser helper) { + _helper = helper; + } + //Fields + late _Asn1Parser _helper; + //Implementation + _IAsn1? readObject() { + return _helper.readObject(); + } + + _Asn1 getAsn1() { + return _DerSet(collection: _helper.readCollection(), isSort: false); + } +} + +class _DerStream { + _DerStream([List? stream]) { + if (stream != null) { + _stream = stream; + } + } + List? _stream; + //Implementation + void writeLength(int length) { + if (length > 127) { + int size = 1; + int value = length.toUnsigned(32); + while ((value >>= 8) != 0) { + size++; + } + _stream!.add((size | 0x80).toUnsigned(8)); + for (int i = (size - 1) * 8; i >= 0; i -= 8) { + _stream!.add((length >> i).toUnsigned(8)); + } + } else { + _stream!.add(length.toUnsigned(8)); + } + } + + void writeEncoded(int? tagNumber, List? bytes, [int? flag]) { + if (flag != null) { + writeTag(flag, tagNumber!); + writeLength(bytes!.length); + _stream!.addAll(bytes); + } else { + _stream!.add(tagNumber!.toUnsigned(8)); + writeLength(bytes!.length); + _stream!.addAll(bytes); + } + } + + void writeTag(int flag, int tagNumber) { + if (tagNumber < 31) { + _stream!.add((flag | tagNumber).toUnsigned(8)); + } else { + _stream!.add((flag | 0x1f).toUnsigned(8)); + if (tagNumber < 128) { + _stream!.add(tagNumber.toUnsigned(8)); + } else { + final List bytes = []; + bytes.add((tagNumber & 0x7F).toUnsigned(8)); + do { + tagNumber >>= 7; + bytes.add((tagNumber & 0x7F | 0x80).toUnsigned(8)); + } while (tagNumber > 127 && bytes.length <= 5); + _stream!.addAll(bytes.reversed.toList()); + } + } + } + + void writeObject(dynamic obj) { + if (obj == null) { + _stream!.add(_Asn1Tags.nullValue); + _stream!.add(0x00); + } else if (obj is _Asn1) { + obj.encode(this); + } else if (obj is _Asn1Encode) { + obj.getAsn1()!.encode(this); + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid object specified'); + } + } +} + +class _DerTag extends _Asn1Tag { + _DerTag(int? tagNumber, _Asn1Encode? asn1, [bool? isExplicit]) + : super(tagNumber, asn1) { + if (isExplicit != null) { + _isExplicit = isExplicit; + } + } + //Implementation + @override + void encode(_DerStream stream) { + final List? bytes = _object!.getDerEncoded(); + if (_isExplicit!) { + stream.writeEncoded( + _tagNumber, bytes, _Asn1Tags.constructed | _Asn1Tags.tagged); + } else { + final int flag = (bytes![0] & _Asn1Tags.constructed) | _Asn1Tags.tagged; + stream.writeTag(flag, _tagNumber!); + stream._stream!.addAll(bytes.sublist(1)); + } + } +} + +class _DerUtcTime extends _Asn1 { + _DerUtcTime(List bytes) { + _time = utf8.decode(bytes); + } + //Fields + String? _time; + //Properties + DateTime? get toAdjustedDateTime { + return DateTime.tryParse(adjustedTimeString); + } + + String get adjustedTimeString { + String timeString = _time!; + final String c = timeString.codeUnitAt(0) < '5'.codeUnitAt(0) ? '20' : '19'; + timeString = c + timeString; + return timeString.substring(0, 8) + 'T' + timeString.substring(8); + } + + //Implementation + @override + void encode(_DerStream stream) { + stream.writeEncoded(_Asn1Tags.utcTime, utf8.encode(_time!)); + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _DerUtcTime) { + return _time == asn1._time; + } else { + return false; + } + } + + @override + int get hashCode { + return _time.hashCode; + } + + @override + String toString() { + return _time!; + } +} + +class _DerCatalogue extends _Asn1 { + _DerCatalogue(List bytes) { + _bytes = bytes; + } + //Fields + List? _bytes; + //Implemnetation + @override + void encode(_DerStream stream) { + stream.writeEncoded(_Asn1Tags.enumerated, _bytes); + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _DerCatalogue) { + return _Asn1Constants.areEqual(_bytes, asn1._bytes); + } else { + return false; + } + } + + @override + int get hashCode { + return _Asn1Constants.getHashCode(_bytes); + } +} + +class _DerOctetHelper implements _IAsn1Octet { + _DerOctetHelper(_Asn1StreamHelper? stream) { + _stream = stream; + } + //Fields + _Asn1StreamHelper? _stream; + //Implementation + _StreamReader? getOctetStream() { + return _stream; + } + + _Asn1 getAsn1() { + return _DerOctet(_stream!.toArray()); + } +} + +class _DerUtf8String extends _DerString { + _DerUtf8String(String value) { + _value = value; + } + //Fields + String? _value; + //Implementation + @override + String? getString([List? bytes]) { + return _value; + } + + @override + void encode(_DerStream stream) { + stream.writeEncoded(_Asn1Tags.utf8String, utf8.encode(_value!)); + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _DerUtf8String) { + return _value == asn1._value; + } else { + return false; + } + } + + @override + int get hashCode => _value.hashCode; +} + +class _DerTeleText extends _DerString { + _DerTeleText(String value) { + _value = value; + } + //Fields + String? _value; + //Implementation + @override + String? getString([List? bytes]) { + return _value; + } + + @override + void encode(_DerStream stream) { + stream.writeEncoded(_Asn1Tags.teleText, toByteArray(_value!)); + } + + List toByteArray(String value) { + final List result = []; + value.codeUnits.forEach((entry) { + result.add(entry.toUnsigned(8)); + }); + return result; + } + + @override + bool operator ==(Object asn1) { + if (asn1 is _DerTeleText) { + return _value == asn1._value; + } else { + return false; + } + } + + @override + int get hashCode => _value.hashCode; +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/enum.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/enum.dart new file mode 100644 index 000000000..4a6419d90 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/enum.dart @@ -0,0 +1,36 @@ +part of pdf; + +enum _Asn1UniversalTags { + reservedBER, + boolean, + integer, + bitString, + octetString, + nullValue, + objectIdentifier, + objectDescriptor, + externalValue, + real, + enumerated, + embeddedPDV, + utf8String, + relativeOid, + sequence, + setValue, + numericString, + printableString, + teletexString, + videotexString, + ia5String, + utfTime, + generalizedTime, + graphicsString, + visibleString, + generalString, + universalString, + characterString, + bmpString, + constructed, + application, + tagged +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/aes_cipher.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/aes_cipher.dart index 70fad8eb0..d21c83e76 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/aes_cipher.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/aes_cipher.dart @@ -5,23 +5,23 @@ class _AesCipher { _AesCipher(bool isEncryption, List key, List iv) { _bp = _BufferedCipher(_CipherBlockChainingMode(_AesEngine())); final _InvalidParameter ip = _InvalidParameter(_KeyParameter(key), iv); - _bp._initialize(isEncryption, ip); + _bp.initialize(isEncryption, ip); } //Fields - _BufferedCipher _bp; + late _BufferedCipher _bp; //Implementation - List _update(List input, int inputOffset, int inputLength) { - int length = _bp._getUpdateOutputSize(inputLength); - List output; + List? _update(List input, int inputOffset, int inputLength) { + int length = _bp.getUpdateOutputSize(inputLength); + List? output; if (length > 0) { output = List.filled(length, 0, growable: true); } else { length = 0; } final Map result = - _bp._processBytes(input, inputOffset, inputLength, output, 0); + _bp.processBytes(input, inputOffset, inputLength, output, 0); output = result['output']; return output; } @@ -31,109 +31,38 @@ class _AesCipherNoPadding { //Constructor _AesCipherNoPadding(bool isEncryption, List key) { _cbc = _CipherBlockChainingMode(_AesEngine()); - _cbc._initialize(isEncryption, _KeyParameter(key)); + _cbc.initialize(isEncryption, _KeyParameter(key)); } //Fields - _CipherBlockChainingMode _cbc; + late _CipherBlockChainingMode _cbc; //Implementation - List _processBlock(List input, int offset, int length) { - if ((length % _cbc.blockSize) != 0) { + List? processBlock(List? input, int offset, int length) { + if ((length % _cbc.blockSize!) != 0) { throw ArgumentError.value('Not multiple of block: $length'); } - List output = List.filled(length, 0, growable: true); + List? output = List.filled(length, 0, growable: true); int tempOffset = 0; while (length > 0) { final Map result = - _cbc._processBlock(input, offset, output, tempOffset); + _cbc.processBlock(input, offset, output, tempOffset); output = result['output']; - length -= _cbc.blockSize; - tempOffset += _cbc.blockSize; - offset += _cbc.blockSize; + length -= _cbc.blockSize!; + tempOffset += _cbc.blockSize!; + offset += _cbc.blockSize!; } return output; } } -class _BufferedCipher { - _BufferedCipher(_CipherBlockChainingMode cipher) { - ArgumentError.checkNotNull(cipher); - _cipher = cipher; - _bytes = List.filled(cipher.blockSize, 0, growable: true); - _offset = 0; - } - - //Fields - _CipherBlockChainingMode _cipher; - List _bytes; - int _offset; - - //Properties - int get blockSize => _cipher.blockSize; - - //Implementation - void _initialize(bool isEncryption, _ICipherParameter parameter) { - _reset(); - _cipher._initialize(isEncryption, parameter); - } - - void _reset() { - _bytes = List.filled(blockSize, 0, growable: true); - _offset = 0; - _cipher._reset(); - } - - int _getUpdateOutputSize(int length) { - final int total = length + _offset; - return total - (total % _bytes.length); - } - - Map _processBytes(List input, int inOffset, int length, - List output, int outOffset) { - if (length < 1) { - return {'output': output, 'length': 0}; - } - int resultLength = 0; - final int gapLength = _bytes.length - _offset; - Map result; - if (length > gapLength) { - List.copyRange(_bytes, _offset, input, inOffset, inOffset + gapLength); - result = _cipher._processBlock(_bytes, 0, output, outOffset); - resultLength += result['length']; - output = result['output']; - _offset = 0; - length -= gapLength; - inOffset += gapLength; - while (length > _bytes.length) { - result = _cipher._processBlock( - input, inOffset, output, outOffset + resultLength); - resultLength += result['length']; - output = result['output']; - length -= blockSize; - inOffset += blockSize; - } - } - List.copyRange(_bytes, _offset, input, inOffset, inOffset + length); - _offset += length; - if (_offset == _bytes.length) { - result = - _cipher._processBlock(_bytes, 0, output, outOffset + resultLength); - resultLength += result['length']; - output = result['output']; - _offset = 0; - } - return {'output': output, 'length': resultLength}; - } -} - class _AesEncryptor { _AesEncryptor(List key, List iv, bool isEncryption) { - _initialize(); + initialize(); _aes = _Aes( key.length == _blockSize ? _KeySize.bits128 : _KeySize.bits256, key); List.copyRange(_buf, 0, iv, 0, iv.length); - List.copyRange(_cbcV, 0, iv, 0, iv.length); + List.copyRange(_cbcV!, 0, iv, 0, iv.length); if (isEncryption) { _ivOff = _buf.length; } @@ -141,25 +70,25 @@ class _AesEncryptor { } //Fields - int _blockSize; - _Aes _aes; - bool _isEncryption; - List _buf; - List _cbcV; - int _ivOff; - List _nextBlockVector; + int? _blockSize; + late _Aes _aes; + late bool _isEncryption; + late List _buf; + List? _cbcV; + int? _ivOff; + List? _nextBlockVector; //Implementation - void _initialize() { + void initialize() { _blockSize = 16; _ivOff = 0; - _buf = List.filled(_blockSize, 0, growable: true); - _cbcV = List.filled(_blockSize, 0, growable: true); - _nextBlockVector = List.filled(_blockSize, 0, growable: true); + _buf = List.filled(_blockSize!, 0, growable: true); + _cbcV = List.filled(_blockSize!, 0, growable: true); + _nextBlockVector = List.filled(_blockSize!, 0, growable: true); } int _getBlockSize(int length) { - final int total = length + _ivOff; + final int total = length + _ivOff!; final int leftOver = total % _buf.length; return total - (leftOver == 0 ? _buf.length : leftOver); } @@ -170,42 +99,41 @@ class _AesEncryptor { throw ArgumentError.value(length, 'length cannot be negative'); } int resultLen = 0; - final int bytesLeft = _buf.length - _ivOff; + final int bytesLeft = _buf.length - _ivOff!; if (length > bytesLeft) { - List.copyRange(_buf, _ivOff, input, inOff, inOff + bytesLeft); - resultLen += _processBlock(_buf, 0, output, outOff); + List.copyRange(_buf, _ivOff!, input, inOff, inOff + bytesLeft); + resultLen += processBlock(_buf, 0, output, outOff); _ivOff = 0; length -= bytesLeft; inOff += bytesLeft; while (length > _buf.length) { - resultLen += _processBlock(input, inOff, output, outOff + resultLen); - length -= _blockSize; - inOff += _blockSize; + resultLen += processBlock(input, inOff, output, outOff + resultLen); + length -= _blockSize!; + inOff += _blockSize!; } } - List.copyRange(_buf, _ivOff, input, inOff, inOff + length); - _ivOff += length; + List.copyRange(_buf, _ivOff!, input, inOff, inOff + length); + _ivOff = _ivOff! + length; } - int _processBlock( - List input, int inOff, List outBytes, int outOff) { + int processBlock(List input, int inOff, List outBytes, int outOff) { int length = 0; - if ((inOff + _blockSize) > input.length) { + if ((inOff + _blockSize!) > input.length) { throw ArgumentError.value('input buffer length is too short'); } if (_isEncryption) { - for (int i = 0; i < _blockSize; i++) { - _cbcV[i] ^= input[inOff + i]; + for (int i = 0; i < _blockSize!; i++) { + _cbcV![i] ^= input[inOff + i]; } length = _aes._cipher(_cbcV, outBytes, outOff); - List.copyRange(_cbcV, 0, outBytes, outOff, outOff + _cbcV.length); + List.copyRange(_cbcV!, 0, outBytes, outOff, outOff + _cbcV!.length); } else { - List.copyRange(_nextBlockVector, 0, input, inOff, inOff + _blockSize); + List.copyRange(_nextBlockVector!, 0, input, inOff, inOff + _blockSize!); length = _aes._invCipher(_nextBlockVector, outBytes, outOff); - for (int i = 0; i < _blockSize; i++) { - outBytes[outOff + i] ^= _cbcV[i]; + for (int i = 0; i < _blockSize!; i++) { + outBytes[outOff + i] ^= _cbcV![i]; } - final List tmp = _cbcV; + final List? tmp = _cbcV; _cbcV = _nextBlockVector; _nextBlockVector = tmp; } @@ -213,7 +141,7 @@ class _AesEncryptor { } int _calculateOutputSize() { - final int total = _ivOff; + final int total = _ivOff!; final int leftOver = total % _buf.length; return leftOver == 0 ? (_isEncryption ? (total + _buf.length) : total) @@ -225,14 +153,14 @@ class _AesEncryptor { final int outOff = 0; if (_isEncryption) { if (_ivOff == _blockSize) { - resultLen = _processBlock(_buf, 0, output, outOff); + resultLen = processBlock(_buf, 0, output, outOff); _ivOff = 0; } - _ivOff = _addPadding(_buf, _ivOff); - resultLen += _processBlock(_buf, 0, output, outOff + resultLen); + _ivOff = _addPadding(_buf, _ivOff!); + resultLen += processBlock(_buf, 0, output, outOff + resultLen); } else { if (_ivOff == _blockSize) { - resultLen = _processBlock(_buf, 0, output, 0); + resultLen = processBlock(_buf, 0, output, 0); _ivOff = 0; } resultLen -= _checkPadding(output); @@ -276,23 +204,23 @@ class _Aes { } key = List.filled(nk * 4, 0, growable: true); List.copyRange(key, 0, keyBytes, 0, key.length); - _initialize(); + initialize(); } //Fields - _KeySize _keySize; - int nb; - int nk; - int nr; - List key; - List> sBox; - List> iBox; - List> rCon; - List> keySheduleArray; - List> state; + _KeySize? _keySize; + late int nb; + late int nk; + int? nr; + late List key; + late List> sBox; + late List> iBox; + late List> rCon; + late List> keySheduleArray; + late List> state; //Implemenation - void _initialize() { + void initialize() { _buildSubstitutionBox(); _buildInverseSubBox(); _buildRoundConstants(); @@ -301,7 +229,7 @@ class _Aes { void _keyExpansion() { keySheduleArray = - List.generate((nb * (nr + 1)), (i) => List.generate(4, (j) => 0)); + List.generate((nb * (nr! + 1)), (i) => List.generate(4, (j) => 0)); for (int row = 0; row < nk; ++row) { keySheduleArray[row][0] = key[4 * row]; keySheduleArray[row][1] = key[(4 * row) + 1]; @@ -309,7 +237,7 @@ class _Aes { keySheduleArray[row][3] = key[(4 * row) + 3]; } List temp = List.filled(4, 0, growable: true); - for (int row = nk; row < nb * (nr + 1); ++row) { + for (int row = nk; row < nb * (nr! + 1); ++row) { temp[0] = keySheduleArray[row - 1][0]; temp[1] = keySheduleArray[row - 1][1]; temp[2] = keySheduleArray[row - 1][2]; @@ -360,14 +288,14 @@ class _Aes { return result; } - int _cipher(List input, List output, int outOff) { - _initialize(); + int _cipher(List? input, List output, int outOff) { + initialize(); state = List.generate(4, (i) => List.generate(nb, (j) => 0)); for (int i = 0; i < (4 * nb); ++i) { - state[i % 4][i ~/ 4] = input[i]; + state[i % 4][i ~/ 4] = input![i]; } _addRoundKey(0); - for (int round = 1; round <= (nr - 1); ++round) { + for (int round = 1; round <= (nr! - 1); ++round) { _subBytes(); _shiftRows(); _mixColumns(); @@ -382,13 +310,13 @@ class _Aes { return 16; } - int _invCipher(List input, List output, int outOff) { + int _invCipher(List? input, List output, int outOff) { state = List.generate(4, (i) => List.generate(nb, (j) => 0)); for (int i = 0; i < (4 * nb); ++i) { - state[i % 4][i ~/ 4] = input[i]; + state[i % 4][i ~/ 4] = input![i]; } _addRoundKey(nr); - for (int round = nr - 1; round >= 1; --round) { + for (int round = nr! - 1; round >= 1; --round) { _invShiftRows(); _invSubBytes(); _addRoundKey(round); @@ -551,11 +479,11 @@ class _Aes { } } - void _addRoundKey(int round) { + void _addRoundKey(int? round) { for (int r = 0; r < 4; ++r) { for (int c = 0; c < 4; ++c) { state[r][c] = ((state[r][c]).toSigned(32) ^ - (keySheduleArray[(round * 4) + c][r]).toSigned(32)) + (keySheduleArray[(round! * 4) + c][r]).toSigned(32)) .toUnsigned(8); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/aes_engine.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/aes_engine.dart index b25fb31a4..4abae28e6 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/aes_engine.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/aes_engine.dart @@ -1,48 +1,58 @@ part of pdf; -class _AesEngine { +class _AesEngine implements _ICipher { //Constructor _AesEngine() { _initializeConstants(); } //Fields - int blockSize; - List> _key; - int rounds; - bool _isEncryption; - List rcon; - List sBox; - int mix1; - int mix2; - int mix3; - int c0; - int c1; - int c2; - int c3; - int c4; - List r0; - List r1; - List r2; - List r3; - List sinv; - List rinv0; - List rinv1; - List rinv2; - List rinv3; + int? blockSize; + List>? _key; + late int rounds; + bool? _isEncryption; + late List rcon; + late List sBox; + late int mix1; + late int mix2; + late int mix3; + late int c0; + late int c1; + late int c2; + late int c3; + int? c4; + late List r0; + late List r1; + late List r2; + late List r3; + late List sinv; + late List rinv0; + late List rinv1; + late List rinv2; + late List rinv3; + + //Properties + @override + String get algorithmName => 'AES'; + + @override + bool get isBlock => false; //Initialize - void _initialize(bool isEncryption, _ICipherParameter parameter) { - ArgumentError.checkNotNull(parameter); - _key = _generateKey(parameter.keys, isEncryption); - _isEncryption = isEncryption; + @override + void initialize(bool? isEncryption, _ICipherParameter? parameter) { + if (parameter != null) { + _key = _generateKey(parameter.keys!, isEncryption!); + _isEncryption = isEncryption; + } } List> _generateKey(List keys, bool isEncryption) { final int keyLength = keys.length ~/ 4; if ((keyLength != 4 && keyLength != 6 && keyLength != 8) || keyLength * 4 != keys.length) { - throw ArgumentError.value(keyLength, 'Key length not 128/192/256 bits.'); + throw ArgumentError.value( + keyLength, 'keyLength', 'Key length not 128/192/256 bits.'); } rounds = keyLength + 6; final List> newKey = @@ -74,21 +84,28 @@ class _AesEngine { return newKey; } - Map _processBlock( - List input, int inputOffset, List output, int outputOffset) { + @override + Map processBlock( + [List? input, + int? inputOffset, + List? output, + int? outputOffset]) { ArgumentError.checkNotNull(_key); - _unPackBlock(input, inputOffset); - if (_isEncryption) { - _encryptBlock(); + _unPackBlock(input!, inputOffset!); + if (_isEncryption!) { + encryptBlock(); } else { - _decryptBlock(); + decryptBlock(); } - output = _packBlock(output, outputOffset); + output = _packBlock(output!, outputOffset!); return {'length': blockSize, 'output': output}; } - void _encryptBlock() { - final List> keys = _key; + @override + void reset() {} + + void encryptBlock() { + final List> keys = _key!; int r = 0; List kw = keys[r]; c0 ^= kw[0]; @@ -187,8 +204,8 @@ class _AesEngine { kw[3]; } - void _decryptBlock() { - final List> keys = _key; + void decryptBlock() { + final List> keys = _key!; int r = rounds; List kw = keys[r]; c0 ^= kw[0]; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/buffered_block_padding_base.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/buffered_block_padding_base.dart new file mode 100644 index 000000000..5151893e7 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/buffered_block_padding_base.dart @@ -0,0 +1,478 @@ +part of pdf; + +abstract class _BufferedBlockPaddingBase implements _IBufferedCipher { + _BufferedBlockPaddingBase() {} + //Fields + static List emptyBuffer = []; + + //Implementation + @override + String? get algorithmName => null; + @override + void initialize(bool forEncryption, _ICipherParameter? parameters); + @override + int? get blockSize => null; + @override + int? getOutputSize(int inputLen) => null; + @override + int? getUpdateOutputSize(int inputLen) => null; + @override + void reset() {} + @override + List? processByte(int intput) => null; + @override + Map? copyBytes(int input, List output, int outOff) { + final List? outBytes = processByte(input); + if (outBytes == null) { + return {'length': 0, 'output': output}; + } + if (outOff + outBytes.length > output.length) { + throw ArgumentError.value(output, 'output', 'output buffer too short'); + } + int j = 0; + for (int i = outOff; i < output.length && j < outBytes.length; i++) { + output[i] = outBytes[j]; + j++; + } + return {'length': outBytes.length, 'output': output}; + } + + @override + List? readBytesFromInput(List input) { + return readBytes(input, 0, input.length); + } + + @override + List? readBytes(List input, int inOff, int length); + @override + Map processByteFromValues( + List input, List output, int outOff) { + return processBytes(input, 0, input.length, output, outOff); + } + + @override + Map processBytes( + List input, int inOff, int length, List? output, int outOff) { + final List? outBytes = readBytes(input, inOff, length); + if (outBytes == null) { + return {'length': 0, 'output': output}; + } + if (outOff + outBytes.length > output!.length) { + throw ArgumentError.value(output, 'output', 'output buffer too short'); + } + int j = 0; + for (int i = outOff; i < output.length && j < outBytes.length; i++) { + output[i] = outBytes[j]; + j++; + } + return {'length': outBytes.length, 'output': output}; + } + + @override + List? doFinal(); + @override + List? readFinal(List? input, int inOff, int length); + @override + List? doFinalFromInput(List? input) { + return readFinal(input, 0, input!.length); + } + + @override + Map writeFinal(List output, int outOff) { + final List outBytes = doFinal()!; + if (outOff + outBytes.length > output.length) { + throw ArgumentError.value(output, 'output', 'output buffer too short'); + } + int j = 0; + for (int i = outOff; i < output.length && j < outBytes.length; i++) { + output[i] = outBytes[j]; + j++; + } + return {'length': outBytes.length, 'output': output}; + } + + @override + Map copyFinal( + List input, List output, int outOff) { + return readFinalValues(input, 0, input.length, output, outOff); + } + + @override + Map readFinalValues( + List input, int inOff, int length, List? output, int outOff) { + Map result = + processBytes(input, inOff, length, output, outOff); + int len = result['length']; + output = result['output']; + result = writeFinal(output!, outOff + len); + len += result['length']! as int; + output = result['output']; + return {'length': len, 'output': output}; + } +} + +class _BufferedCipher extends _BufferedBlockPaddingBase { + _BufferedCipher(_ICipher cipher) : super() { + _cipher = cipher; + _bytes = List.generate(cipher.blockSize!, (i) => 0); + _offset = 0; + } + //Fields + List? _bytes; + int? _offset; + late bool _isEncryption; + late _ICipher _cipher; + + //Implementation + @override + String? get algorithmName => _cipher.algorithmName; + @override + int? get blockSize => _cipher.blockSize; + @override + void initialize(bool isEncryption, _ICipherParameter? parameters) { + _isEncryption = isEncryption; + reset(); + _cipher.initialize(isEncryption, parameters); + } + + @override + int getUpdateOutputSize(int length) { + final int total = length + _offset!; + final int leftOver = total % _bytes!.length; + return total - leftOver; + } + + @override + int getOutputSize(int length) { + return length + _offset!; + } + + @override + void reset() { + _bytes = List.generate(_bytes!.length, (i) => 0); + _offset = 0; + _cipher.reset(); + } + + @override + List? processByte(int input) { + final int length = getUpdateOutputSize(1); + List? bytes = length > 0 ? List.generate(length, (i) => 0) : null; + final Map result = copyBytes(input, bytes, 0)!; + final int? position = result['length']; + bytes = result['output']; + if (length > 0 && position! < length) { + final List tempBytes = List.generate(position, (i) => 0); + List.copyRange(tempBytes, 0, bytes!, 0, position); + bytes = tempBytes; + } + return bytes; + } + + @override + Map? copyBytes(int input, List? output, int outOff) { + _bytes![_offset!] = input; + _offset = _offset! + 1; + if (_offset == _bytes!.length) { + if ((outOff + _bytes!.length) > output!.length) { + throw ArgumentError.value(output, 'output', 'output buffer too short'); + } + _offset = 0; + return _cipher.processBlock(_bytes, 0, output, outOff); + } + return {'length': 0, 'output': output}; + } + + @override + List? readBytes(List input, int inOff, int length) { + if (length < 1) { + return null; + } + final int outLength = getUpdateOutputSize(length); + List? outBytes = + outLength > 0 ? List.generate(outLength, (i) => 0) : null; + final Map result = + processBytes(input, inOff, length, outBytes, 0); + final int? position = result['length']; + outBytes = result['output']; + if (outLength > 0 && position! < outLength) { + final List tempBytes = List.generate(position, (i) => 0); + List.copyRange(tempBytes, 0, outBytes!, 0, position); + outBytes = tempBytes; + } + return outBytes; + } + + @override + Map processBytes(List? input, int inOffset, int length, + List? output, int outOffset) { + Map? result; + if (length < 1) { + return {'length': 0, 'output': output}; + } + final int? blockSize = this.blockSize; + getUpdateOutputSize(length); + int resultLength = 0; + final int gapLength = _bytes!.length - _offset!; + if (length > gapLength) { + List.copyRange(_bytes!, _offset!, input!, inOffset, inOffset + gapLength); + result = _cipher.processBlock(_bytes, 0, output, outOffset); + resultLength = resultLength + result!['length']! as int; + output = result['output']; + _offset = 0; + length -= gapLength; + inOffset += gapLength; + while (length > _bytes!.length) { + result = _cipher.processBlock( + input, inOffset, output, outOffset + resultLength); + resultLength = resultLength + result!['length']! as int; + output = result['output']; + length -= blockSize!; + inOffset += blockSize; + } + } + List.copyRange(_bytes!, _offset!, input!, inOffset, inOffset + length); + _offset = _offset! + length; + if (_offset == _bytes!.length) { + result = + _cipher.processBlock(_bytes, 0, output, outOffset + resultLength); + resultLength = resultLength + result!['length']! as int; + output = result['output']; + _offset = 0; + } + return {'output': output, 'length': resultLength}; + } + + @override + List? doFinal() { + List? bytes = _BufferedBlockPaddingBase.emptyBuffer; + final int length = getOutputSize(0); + if (length > 0) { + bytes = List.generate(length, (i) => 0); + final Map result = writeFinal(bytes, 0); + final int position = result['length']; + bytes = result['output']; + if (position < bytes!.length) { + final List tempBytes = List.generate(position, (i) => 0); + List.copyRange(tempBytes, 0, bytes, 0, position); + bytes = tempBytes; + } + } else { + reset(); + } + return bytes; + } + + @override + List? readFinal(List? input, int inOffset, int inLength) { + List? outBytes = _BufferedBlockPaddingBase.emptyBuffer; + if (input != null) { + final int length = getOutputSize(inLength); + Map result; + if (length > 0) { + outBytes = List.generate(length, (i) => 0); + int? position; + if (inLength > 0) { + result = processBytes(input, inOffset, inLength, outBytes, 0); + position = result['length']; + outBytes = result['output']; + } else { + position = 0; + } + result = writeFinal(outBytes, position); + position = position! + result['length']! as int; + outBytes = result['output']; + if (position < outBytes!.length) { + final List tempBytes = List.generate(position, (i) => 0); + List.copyRange(tempBytes, 0, outBytes, 0, position); + outBytes = tempBytes; + } + } else { + reset(); + } + } + return outBytes; + } + + @override + Map writeFinal(List? output, int? outOff) { + try { + if (_offset != 0) { + final Map result = + _cipher.processBlock(_bytes, 0, _bytes, 0)!; + _bytes = result['output']; + List.copyRange(_bytes!, 0, output!, outOff, _offset); + } + return {'length': _offset, 'output': output}; + } finally { + reset(); + } + } +} + +class _BufferedBlockPadding extends _BufferedCipher { + _BufferedBlockPadding(_ICipher cipher, [_IPadding? padding]) : super(cipher) { + _cipher = cipher; + _padding = padding != null ? padding : _Pkcs7Padding(); + _bytes = List.generate(cipher.blockSize!, (i) => 0); + _offset = 0; + } + //Fields + late _IPadding _padding; + //Implemntation + @override + void initialize(bool isEncryption, _ICipherParameter? parameters) { + _isEncryption = isEncryption; + Random? initRandom; + reset(); + _padding.initialize(initRandom); + _cipher.initialize(isEncryption, parameters); + } + + @override + int getOutputSize(int length) { + final int total = length + _offset!; + final int leftOver = total % _bytes!.length; + if (leftOver == 0) { + if (_isEncryption) { + return total + _bytes!.length; + } + return total; + } + return total - leftOver + _bytes!.length; + } + + @override + int getUpdateOutputSize(int length) { + final int total = length + _offset!; + final int leftOver = total % _bytes!.length; + if (leftOver == 0) { + return total - _bytes!.length; + } + return total - leftOver; + } + + @override + Map copyBytes(int input, List? output, int outOff) { + int? resultLen = 0; + if (_offset == _bytes!.length) { + final Map result = + _cipher.processBlock(_bytes, 0, output, outOff)!; + resultLen = result['length']; + output = result['output']; + _offset = 0; + } + _bytes![_offset!] = input; + _offset = _offset! + 1; + return {'length': resultLen, 'output': output}; + } + + @override + Map processBytes(List? input, int inOffset, int length, + List? output, int outOffset) { + if (length < 0) { + throw ArgumentError.value(length, 'length', 'Invalid length'); + } + final int? blockSize = this.blockSize; + final int outLength = getUpdateOutputSize(length); + if (outLength > 0) { + if ((outOffset + outLength) > output!.length) { + throw ArgumentError.value(length, 'length', 'Invalid buffer length'); + } + } + int resultLength = 0; + final int gapLength = _bytes!.length - _offset!; + Map? result; + if (length > gapLength) { + List.copyRange(_bytes!, _offset!, input!, inOffset, inOffset + gapLength); + result = _cipher.processBlock(_bytes, 0, output, outOffset); + resultLength += result!['length']! as int; + output = result['output']; + _offset = 0; + length -= gapLength; + inOffset += gapLength; + while (length > _bytes!.length) { + result = _cipher.processBlock( + input, inOffset, output, outOffset + resultLength); + resultLength += result!['length']! as int; + output = result['output']; + length -= blockSize!; + inOffset += blockSize; + } + } + List.copyRange(_bytes!, _offset!, input!, inOffset, inOffset + length); + _offset = _offset! + length; + return {'length': resultLength, 'output': output}; + } + + @override + Map writeFinal(List? output, int? outOff) { + final int? blockSize = _cipher.blockSize; + int resultLen = 0; + Map? result; + if (_isEncryption) { + if (_offset == blockSize) { + if ((outOff! + 2 * blockSize!) > output!.length) { + reset(); + throw ArgumentError.value( + output, 'output', 'output buffer too short'); + } + result = _cipher.processBlock(_bytes, 0, output, outOff); + resultLen = result!['length']!; + output = result['output']; + _offset = 0; + } + _padding.addPadding(_bytes, _offset); + result = _cipher.processBlock(_bytes, 0, output, outOff! + resultLen); + resultLen += result!['length']! as int; + output = result['output']; + reset(); + } else { + if (_offset == blockSize) { + result = _cipher.processBlock(_bytes, 0, _bytes, 0); + resultLen = result!['length']!; + _bytes = result['output']; + _offset = 0; + } else { + reset(); + throw ArgumentError.value(output, 'output', 'incomplete in decryption'); + } + try { + resultLen -= _padding.count(_bytes)!; + List.copyRange(output!, outOff!, _bytes!, 0, resultLen); + } finally { + reset(); + } + } + return {'length': resultLen, 'output': output}; + } +} + +class _Pkcs7Padding implements _IPadding { + _Pkcs7Padding() {} + //Properties + String get paddingName => _Asn1Constants.pkcs7; + //Implementation + void initialize(Random? random) {} + int addPadding(List? bytes, int? offset) { + final int code = (bytes!.length - offset!).toUnsigned(8); + while (offset! < bytes.length) { + bytes[offset] = code; + offset++; + } + return code; + } + + int count(List? input) { + final int count = input![input.length - 1].toSigned(32); + if (count < 1 || count > input.length) { + throw ArgumentError.value(input, 'input', 'Invalid pad'); + } + for (int i = 1; i <= count; i++) { + if (input[input.length - i] != count) { + throw ArgumentError.value(input, 'input', 'Invalid pad'); + } + } + return count; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_block_chaining_mode.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_block_chaining_mode.dart index c5caa50be..2bb1c2c85 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_block_chaining_mode.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_block_chaining_mode.dart @@ -1,30 +1,33 @@ part of pdf; -class _CipherBlockChainingMode { +class _CipherBlockChainingMode implements _ICipher { //Constructor - _CipherBlockChainingMode(_AesEngine cipher) { + _CipherBlockChainingMode(_ICipher? cipher) { _cipher = cipher; - _size = _cipher.blockSize; - _bytes = List.filled(_size, 0, growable: true); - _cbcBytes = List.filled(_size, 0, growable: true); - _cbcNextBytes = List.filled(_size, 0, growable: true); + _size = _cipher!.blockSize; + _bytes = List.filled(_size!, 0, growable: true); + _cbcBytes = List.filled(_size!, 0, growable: true); + _cbcNextBytes = List.filled(_size!, 0, growable: true); _isEncryption = false; } //Fields - _AesEngine _cipher; - int _size; - List _bytes; - List _cbcBytes; - List _cbcNextBytes; - bool _isEncryption; + _ICipher? _cipher; + int? _size; + late List _bytes; + List? _cbcBytes; + List? _cbcNextBytes; + bool? _isEncryption; //Fields - int get blockSize => _cipher.blockSize; + int? get blockSize => _cipher!.blockSize; + String get algorithmName => _cipher!.algorithmName! + '/CBC'; + bool get isBlock => false; //Implementation - void _initialize(bool isEncryption, _ICipherParameter parameters) { - final bool oldEncryption = _isEncryption; + @override + void initialize(bool? isEncryption, _ICipherParameter? parameters) { + final bool? oldEncryption = _isEncryption; _isEncryption = isEncryption; if (parameters is _InvalidParameter) { final List bytes = parameters.keys; @@ -32,59 +35,61 @@ class _CipherBlockChainingMode { throw ArgumentError.value(parameters, 'Invalid size in block'); } List.copyRange(_bytes, 0, bytes, 0, bytes.length); - parameters = (parameters as _InvalidParameter)._parameters; + parameters = parameters._parameters; } - _reset(); + reset(); if (parameters != null) { - _cipher._initialize(_isEncryption, parameters); + _cipher!.initialize(_isEncryption, parameters); } else if (oldEncryption != _isEncryption) { throw ArgumentError.value(oldEncryption, 'cannot change encrypting state without providing key.'); } } - void _reset() { + @override + void reset() { _cbcBytes = List.from(_bytes); - _cbcNextBytes = List.filled(_size, 0, growable: true); + _cbcNextBytes = List.filled(_size!, 0, growable: true); } - Map _processBlock(List inputBytes, int inputOffset, - List outputBytes, int outputOffset) { - return _isEncryption - ? _encryptBlock(inputBytes, inputOffset, outputBytes, outputOffset) - : _decryptBlock(inputBytes, inputOffset, outputBytes, outputOffset); + @override + Map processBlock(List? inputBytes, int inputOffset, + List? outputBytes, int? outputOffset) { + return _isEncryption! + ? encryptBlock(inputBytes!, inputOffset, outputBytes, outputOffset!) + : decryptBlock(inputBytes!, inputOffset, outputBytes, outputOffset); } - Map _encryptBlock(List inputBytes, int inputOffset, - List outputBytes, int outputOffset) { - if ((inputOffset + _size) > inputBytes.length) { + Map encryptBlock(List inputBytes, int inputOffset, + List? outputBytes, int outputOffset) { + if ((inputOffset + _size!) > inputBytes.length) { throw ArgumentError.value('Invalid length in input bytes'); } - for (int i = 0; i < _size; i++) { - _cbcBytes[i] ^= inputBytes[inputOffset + i]; + for (int i = 0; i < _size!; i++) { + _cbcBytes![i] ^= inputBytes[inputOffset + i]; } final Map result = - _cipher._processBlock(_cbcBytes, 0, outputBytes, outputOffset); - outputBytes = result['output'] as List; - List.copyRange(_cbcBytes, 0, outputBytes, outputOffset, - outputOffset + _cbcBytes.length); + _cipher!.processBlock(_cbcBytes, 0, outputBytes, outputOffset)!; + outputBytes = result['output'] as List?; + List.copyRange(_cbcBytes!, 0, outputBytes!, outputOffset, + outputOffset + _cbcBytes!.length); return result; } - Map _decryptBlock(List inputBytes, int inputOffset, - List outputBytes, int outputOffset) { - if ((inputOffset + _size) > inputBytes.length) { + Map decryptBlock(List inputBytes, int inputOffset, + List? outputBytes, int? outputOffset) { + if ((inputOffset + _size!) > inputBytes.length) { throw ArgumentError.value('Invalid length in input bytes'); } List.copyRange( - _cbcNextBytes, 0, inputBytes, inputOffset, inputOffset + _size); - final Map result = _cipher._processBlock( - inputBytes, inputOffset, outputBytes, outputOffset); - outputBytes = result['output'] as List; - for (int i = 0; i < _size; i++) { - outputBytes[outputOffset + i] ^= _cbcBytes[i]; + _cbcNextBytes!, 0, inputBytes, inputOffset, inputOffset + _size!); + final Map result = _cipher! + .processBlock(inputBytes, inputOffset, outputBytes, outputOffset)!; + outputBytes = result['output'] as List?; + for (int i = 0; i < _size!; i++) { + outputBytes![outputOffset! + i] ^= _cbcBytes![i]; } - final List tempBytes = _cbcBytes; + final List? tempBytes = _cbcBytes; _cbcBytes = _cbcNextBytes; _cbcNextBytes = tempBytes; return {'length': result['length'], 'output': outputBytes}; @@ -93,24 +98,24 @@ class _CipherBlockChainingMode { class _InvalidParameter implements _ICipherParameter { //Constructor - _InvalidParameter(_ICipherParameter parameter, List bytes, - [int offset, int length]) { + _InvalidParameter(_ICipherParameter? parameter, List bytes, + [int? offset, int? length]) { _parameters = parameter; length ??= bytes.length; offset ??= 0; _bytes = List.filled(length, 0, growable: true); - List.copyRange(_bytes, 0, bytes, offset, offset + length); + List.copyRange(_bytes!, 0, bytes, offset, offset + length); } //Fields - _ICipherParameter _parameters; - List _bytes; + _ICipherParameter? _parameters; + List? _bytes; //Properties @override - List get keys => List.from(_bytes); + List get keys => List.from(_bytes!); @override - set keys(List value) { + set keys(List? value) { _bytes = value; } } @@ -120,19 +125,155 @@ class _KeyParameter implements _ICipherParameter { _KeyParameter(List bytes) { _bytes = List.from(bytes); } + _KeyParameter.fromLengthValue(List bytes, int offset, int length) { + if (offset < 0 || offset > bytes.length) { + throw ArgumentError.value(offset, 'offset', 'Out of range'); + } + if (length < 0 || (offset + length) > bytes.length) { + throw ArgumentError.value(length, 'length', 'Out of range'); + } + _bytes = List.generate(length, (i) => 0); + List.copyRange(_bytes!, 0, bytes, offset, offset + length); + } //Fields - List _bytes; + List? _bytes; //Properties @override - List get keys => List.from(_bytes); + List get keys => List.from(_bytes!); @override - set keys(List value) { + set keys(List? value) { _bytes = value; } } -class _ICipherParameter { - List keys; +class _CipherParameter implements _ICipherParameter { + _CipherParameter(bool privateKey) { + _privateKey = privateKey; + } + //Fields + bool? _privateKey; + bool? get isPrivate => _privateKey; + @override + List? get keys => null; + @override + set keys(List? value) {} + @override + operator ==(Object other) { + if (other is _CipherParameter) { + return _privateKey == other._privateKey; + } else { + return false; + } + } + + @override + int get hashCode => _privateKey.hashCode; +} + +class _RsaKeyParam extends _CipherParameter { + _RsaKeyParam(bool isPrivate, BigInt? modulus, BigInt? exponent) + : super(isPrivate) { + _modulus = modulus; + _exponent = exponent; + } + //Fields + BigInt? _modulus; + BigInt? _exponent; + //Properties + BigInt? get modulus => _modulus; + BigInt? get exponent => _exponent; + @override + List? get keys => null; + @override + set keys(List? value) {} + //Implementation + @override + operator ==(Object other) { + if (other is _RsaKeyParam) { + return other.isPrivate == isPrivate && + other.modulus == _modulus && + other.exponent == _exponent; + } else { + return false; + } + } + + @override + int get hashCode => + _modulus.hashCode ^ _exponent.hashCode ^ isPrivate.hashCode; +} + +class _RsaPrivateKeyParam extends _RsaKeyParam { + _RsaPrivateKeyParam( + BigInt? modulus, + BigInt publicExponent, + BigInt? privateExponent, + BigInt p, + BigInt q, + BigInt dP, + BigInt dQ, + BigInt inverse) + : super(true, modulus, privateExponent) { + validateValue(publicExponent); + validateValue(p); + validateValue(q); + validateValue(dP); + validateValue(dQ); + validateValue(inverse); + _publicExponent = publicExponent; + _p = p; + _q = q; + _dP = dP; + _dQ = dQ; + _inverse = inverse; + } + + //Fields + BigInt? _publicExponent; + BigInt? _p; + BigInt? _q; + BigInt? _dP; + BigInt? _dQ; + BigInt? _inverse; + + //Implementation + void validateValue(BigInt number) { + if (number.sign <= 0) { + throw ArgumentError.value(number, 'number', 'Invalid RSA entry'); + } + } + + @override + List? get keys => null; + @override + set keys(List? value) {} + + @override + operator ==(Object other) { + if (other is _RsaPrivateKeyParam) { + return other._dP == _dP && + other._dQ == _dQ && + other._exponent == _exponent && + other._modulus == _modulus && + other._p == _p && + other._q == _q && + other._publicExponent == _publicExponent && + other._inverse == _inverse; + } else { + return false; + } + } + + @override + int get hashCode => + _dP.hashCode ^ + _dQ.hashCode ^ + _exponent.hashCode ^ + _modulus.hashCode ^ + _p.hashCode ^ + _q.hashCode ^ + _publicExponent.hashCode ^ + _inverse.hashCode; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_utils.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_utils.dart new file mode 100644 index 000000000..dcf40c417 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_utils.dart @@ -0,0 +1,424 @@ +part of pdf; + +class _CipherUtils { + _CipherUtils() { + _algorithms = {}; + } + //Fields + late Map _algorithms; + //Implementation + _IBufferedCipher getCipher(String algorithm) { + String? value; + if (_algorithms.length > 0) { + value = _algorithms[algorithm]; + } + if (value != null) { + algorithm = value; + } + final List parts = algorithm.split('/'); + _ICipher? blockCipher = null; + _ICipherBlock? asymBlockCipher = null; + String algorithmName = parts[0]; + if (_algorithms.length > 0) { + value = _algorithms[algorithmName]; + } + if (value != null) { + algorithmName = value; + } + final _CipherAlgorithm cipherAlgorithm = getAlgorithm(algorithmName); + switch (cipherAlgorithm) { + case _CipherAlgorithm.des: + blockCipher = _DataEncryption(); + break; + case _CipherAlgorithm.desede: + blockCipher = _DesEdeAlogorithm(); + break; + case _CipherAlgorithm.rc2: + blockCipher = _Rc2Algorithm(); + break; + case _CipherAlgorithm.rsa: + asymBlockCipher = _RsaAlgorithm(); + break; + default: + throw ArgumentError.value( + cipherAlgorithm, 'algorithm', 'Invalid cipher algorithm'); + } + bool isPadded = true; + _IPadding? padding = null; + if (parts.length > 2) { + final String paddingName = parts[2]; + _CipherPaddingType cipherPadding; + if (paddingName.isEmpty) { + cipherPadding = _CipherPaddingType.raw; + } else if (paddingName == 'X9.23PADDING') { + cipherPadding = _CipherPaddingType.x923Padding; + } else { + cipherPadding = getPaddingType(paddingName); + } + switch (cipherPadding) { + case _CipherPaddingType.noPadding: + isPadded = false; + break; + case _CipherPaddingType.raw: + case _CipherPaddingType.withCipherTextStealing: + break; + case _CipherPaddingType.pkcs1: + case _CipherPaddingType.pkcs1Padding: + asymBlockCipher = _Pkcs1Encoding(asymBlockCipher); + break; + case _CipherPaddingType.pkcs5: + case _CipherPaddingType.pkcs5Padding: + case _CipherPaddingType.pkcs7: + case _CipherPaddingType.pkcs7Padding: + padding = _Pkcs7Padding(); + break; + default: + throw ArgumentError.value(cipherPadding, 'cpiher padding algorithm', + 'Invalid cipher algorithm'); + } + } + String mode = ''; + if (parts.length > 1) { + mode = parts[1]; + int digitIngex = -1; + for (int i = 0; i < mode.length; ++i) { + if (isDigit(mode[i])) { + digitIngex = i; + break; + } + } + final String modeName = + digitIngex >= 0 ? mode.substring(0, digitIngex) : mode; + final _CipherMode cipherMode = + modeName == '' ? _CipherMode.none : getCipherMode(modeName); + switch (cipherMode) { + case _CipherMode.ecb: + case _CipherMode.none: + break; + case _CipherMode.cbc: + blockCipher = _CipherBlockChainingMode(blockCipher); + break; + case _CipherMode.cts: + blockCipher = _CipherBlockChainingMode(blockCipher); + break; + default: + throw ArgumentError.value( + cipherMode, 'CipherMode', 'Invalid cipher algorithm'); + } + } + if (blockCipher != null) { + if (padding != null) { + return _BufferedBlockPadding(blockCipher, padding); + } + if (!isPadded || blockCipher.isBlock!) { + return _BufferedCipher(blockCipher); + } + return _BufferedBlockPadding(blockCipher); + } + throw ArgumentError.value( + blockCipher, 'Cipher Algorithm', 'Invalid cipher algorithm'); + } + + _CipherAlgorithm getAlgorithm(String name) { + _CipherAlgorithm result; + switch (name.toLowerCase()) { + case 'des': + result = _CipherAlgorithm.des; + break; + case 'desede': + result = _CipherAlgorithm.desede; + break; + case 'rc2': + result = _CipherAlgorithm.rc2; + break; + case 'rsa': + result = _CipherAlgorithm.rsa; + break; + default: + throw ArgumentError.value(name, 'name', 'Invalid algorithm name'); + } + return result; + } + + _CipherMode getCipherMode(String mode) { + _CipherMode result; + switch (mode.toLowerCase()) { + case 'ecb': + result = _CipherMode.ecb; + break; + case 'none': + result = _CipherMode.none; + break; + case 'cbc': + result = _CipherMode.cbc; + break; + case 'cts': + result = _CipherMode.cts; + break; + default: + throw ArgumentError.value(mode, 'CipherMode', 'Invalid mode'); + } + return result; + } + + _CipherPaddingType getPaddingType(String type) { + _CipherPaddingType result; + switch (type.toLowerCase()) { + case 'noPadding': + result = _CipherPaddingType.noPadding; + break; + case 'raw': + result = _CipherPaddingType.raw; + break; + case 'pkcs1': + result = _CipherPaddingType.pkcs1; + break; + case 'pkcs1Padding': + result = _CipherPaddingType.pkcs1Padding; + break; + case 'pkcs5': + result = _CipherPaddingType.pkcs5; + break; + case 'pkcs5Padding': + result = _CipherPaddingType.pkcs5Padding; + break; + case 'pkcs7': + result = _CipherPaddingType.pkcs7; + break; + case 'pkcs7Padding': + result = _CipherPaddingType.pkcs7Padding; + break; + case 'withCipherTextStealing': + result = _CipherPaddingType.withCipherTextStealing; + break; + case 'x923Padding': + result = _CipherPaddingType.x923Padding; + break; + default: + throw ArgumentError.value(type, 'PaddingType', 'Invalid padding type'); + } + return result; + } + + bool isDigit(String s, [int idx = 0]) { + return (s.codeUnitAt(idx) ^ 0x30) <= 9; + } +} + +enum _CipherAlgorithm { des, desede, rc2, rsa } +enum _CipherMode { ecb, none, cbc, cts } +enum _CipherPaddingType { + noPadding, + raw, + pkcs1, + pkcs1Padding, + pkcs5, + pkcs5Padding, + pkcs7, + pkcs7Padding, + withCipherTextStealing, + x923Padding +} + +class _SignaturePrivateKey { + _SignaturePrivateKey(String hashAlgorithm, [_ICipherParameter? key]) { + _key = key; + final _MessageDigestAlgorithms alg = _MessageDigestAlgorithms(); + _hashAlgorithm = alg.getDigest(alg.getAllowedDigests(hashAlgorithm)); + if (key == null || key is _RsaKeyParam) { + _encryptionAlgorithm = 'RSA'; + } else { + throw ArgumentError.value(key, 'key', 'Invalid key'); + } + } + //Fields + _ICipherParameter? _key; + String? _hashAlgorithm; + String? _encryptionAlgorithm; + //Implementation + List? sign(List bytes) { + final String signMode = _hashAlgorithm! + 'with' + _encryptionAlgorithm!; + final _SignerUtilities util = _SignerUtilities(); + final _ISigner signer = util.getSigner(signMode); + signer.initialize(true, _key); + signer.blockUpdate(bytes, 0, bytes.length); + return signer.generateSignature(); + } + + String? getHashAlgorithm() { + return _hashAlgorithm; + } + + String? getEncryptionAlgorithm() { + return _encryptionAlgorithm; + } +} + +class _SignerUtilities { + _SignerUtilities() { + _algms['MD2WITHRSA'] = 'MD2withRSA'; + _algms['MD2WITHRSAENCRYPTION'] = 'MD2withRSA'; + _algms[_PkcsObjectId.md2WithRsaEncryption._id] = 'MD2withRSA'; + _algms[_PkcsObjectId.rsaEncryption._id] = 'RSA'; + _algms['SHA1WITHRSA'] = 'SHA-1withRSA'; + _algms['SHA1WITHRSAENCRYPTION'] = 'SHA-1withRSA'; + _algms[_PkcsObjectId.sha1WithRsaEncryption._id] = 'SHA-1withRSA'; + _algms['SHA-1WITHRSA'] = 'SHA-1withRSA'; + _algms['SHA256WITHRSA'] = 'SHA-256withRSA'; + _algms['SHA256WITHRSAENCRYPTION'] = 'SHA-256withRSA'; + _algms[_PkcsObjectId.sha256WithRsaEncryption._id] = 'SHA-256withRSA'; + _algms['SHA-256WITHRSA'] = 'SHA-256withRSA'; + _algms['SHA1WITHRSAANDMGF1'] = 'SHA-1withRSAandMGF1'; + _algms['SHA-1WITHRSAANDMGF1'] = 'SHA-1withRSAandMGF1'; + _algms['SHA1WITHRSA/PSS'] = 'SHA-1withRSAandMGF1'; + _algms['SHA-1WITHRSA/PSS'] = 'SHA-1withRSAandMGF1'; + _algms['SHA224WITHRSAANDMGF1'] = 'SHA-224withRSAandMGF1'; + _algms['SHA-224WITHRSAANDMGF1'] = 'SHA-224withRSAandMGF1'; + _algms['SHA224WITHRSA/PSS'] = 'SHA-224withRSAandMGF1'; + _algms['SHA-224WITHRSA/PSS'] = 'SHA-224withRSAandMGF1'; + _algms['SHA256WITHRSAANDMGF1'] = 'SHA-256withRSAandMGF1'; + _algms['SHA-256WITHRSAANDMGF1'] = 'SHA-256withRSAandMGF1'; + _algms['SHA256WITHRSA/PSS'] = 'SHA-256withRSAandMGF1'; + _algms['SHA-256WITHRSA/PSS'] = 'SHA-256withRSAandMGF1'; + _algms['SHA384WITHRSA'] = 'SHA-384withRSA'; + _algms['SHA512WITHRSA'] = 'SHA-512withRSA'; + _algms['SHA384WITHRSAENCRYPTION'] = 'SHA-384withRSA'; + _algms[_PkcsObjectId.sha384WithRsaEncryption._id] = 'SHA-384withRSA'; + _algms['SHA-384WITHRSA'] = 'SHA-384withRSA'; + _algms['SHA-512WITHRSA'] = 'SHA-512withRSA'; + _algms['SHA384WITHRSAANDMGF1'] = 'SHA-384withRSAandMGF1'; + _algms['SHA-384WITHRSAANDMGF1'] = 'SHA-384withRSAandMGF1'; + _algms['SHA384WITHRSA/PSS'] = 'SHA-384withRSAandMGF1'; + _algms['SHA-384WITHRSA/PSS'] = 'SHA-384withRSAandMGF1'; + _algms['SHA512WITHRSAANDMGF1'] = 'SHA-512withRSAandMGF1'; + _algms['SHA-512WITHRSAANDMGF1'] = 'SHA-512withRSAandMGF1'; + _algms['SHA512WITHRSA/PSS'] = 'SHA-512withRSAandMGF1'; + _algms['SHA-512WITHRSA/PSS'] = 'SHA-512withRSAandMGF1'; + _algms['DSAWITHSHA256'] = 'SHA-256withDSA'; + _algms['DSAWITHSHA-256'] = 'SHA-256withDSA'; + _algms['SHA256/DSA'] = 'SHA-256withDSA'; + _algms['SHA-256/DSA'] = 'SHA-256withDSA'; + _algms['SHA256WITHDSA'] = 'SHA-256withDSA'; + _algms['SHA-256WITHDSA'] = 'SHA-256withDSA'; + _algms[_NistObjectIds.dsaWithSHA256._id] = 'SHA-256withDSA'; + _algms['RIPEMD160WITHRSA'] = 'RIPEMD160withRSA'; + _algms['RIPEMD160WITHRSAENCRYPTION'] = 'RIPEMD160withRSA'; + _algms[_NistObjectIds.rsaSignatureWithRipeMD160._id] = 'RIPEMD160withRSA'; + _oids['SHA-1withRSA'] = _PkcsObjectId.sha1WithRsaEncryption; + _oids['SHA-256withRSA'] = _PkcsObjectId.sha256WithRsaEncryption; + _oids['SHA-384withRSA'] = _PkcsObjectId.sha384WithRsaEncryption; + _oids['SHA-512withRSA'] = _PkcsObjectId.sha512WithRsaEncryption; + _oids['RIPEMD160withRSA'] = _NistObjectIds.rsaSignatureWithRipeMD160; + } + //Fields + Map _algms = {}; + Map _oids = {}; + //Implementation + _ISigner getSigner(String algorithm) { + _ISigner result; + final String lower = algorithm.toLowerCase(); + String? mechanism = algorithm; + bool isContinue = true; + _algms.forEach((String? key, String value) { + if (isContinue && key!.toLowerCase() == lower) { + mechanism = _algms[key]; + isContinue = false; + } + }); + if (mechanism == 'SHA-1withRSA') { + result = _RmdSigner(_DigestAlgorithms.sha1); + } else if (mechanism == 'SHA-256withRSA') { + return _RmdSigner(_DigestAlgorithms.sha256); + } else if (mechanism == 'SHA-384withRSA') { + return _RmdSigner(_DigestAlgorithms.sha384); + } else if (mechanism == 'SHA-512withRSA') { + return _RmdSigner(_DigestAlgorithms.sha512); + } else { + throw ArgumentError.value('Signer ' + algorithm + ' not recognised.'); + } + return result; + } +} + +class _KeyIdentifier extends _Asn1Encode { + _KeyIdentifier(_Asn1Sequence sequence) { + sequence._objects!.forEach((entry) { + if (entry is _Asn1Tag) { + switch (entry.tagNumber) { + case 0: + _keyIdentifier = _Asn1Octet.getOctetStringFromObject(entry); + break; + case 1: + break; + case 2: + _serialNumber = _DerInteger.getNumberFromTag(entry, false); + break; + default: + throw ArgumentError.value( + sequence, 'sequence', 'Invalid entry in sequence'); + } + } + }); + } + //Fields + _Asn1Octet? _keyIdentifier; + _DerInteger? _serialNumber; + //Properties + List? get keyID => + _keyIdentifier == null ? null : _keyIdentifier!.getOctets(); + //Implementation + static _KeyIdentifier getKeyIdentifier(dynamic obj) { + _KeyIdentifier result; + if (obj is _KeyIdentifier) { + result = obj; + } else if (obj is _Asn1Sequence) { + result = _KeyIdentifier(obj); + } else if (obj is _X509Extension) { + result = getKeyIdentifier(_X509Extension.convertValueToObject(obj)); + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid entry'); + } + return result; + } + + @override + _Asn1 getAsn1() { + final _Asn1EncodeCollection collection = _Asn1EncodeCollection(); + if (_keyIdentifier != null) { + collection._encodableObjects.add(_DerTag(0, _keyIdentifier, false)); + } + if (_serialNumber != null) { + collection._encodableObjects.add(_DerTag(2, _serialNumber, false)); + } + return _DerSequence(collection: collection); + } + + @override + String toString() { + return 'AuthorityKeyIdentifier: KeyID(' + + String.fromCharCodes(_keyIdentifier!.getOctets() as Iterable) + + ')'; + } +} + +class _DigitalIdentifiers { + static const String pkcs7Data = '1.2.840.113549.1.7.1'; + static const String pkcs7SignedData = '1.2.840.113549.1.7.2'; + static const String rsa = '1.2.840.113549.1.1.1'; + static const String dsa = '1.2.840.10040.4.1'; + static const String ecdsa = '1.2.840.10045.2.1'; + static const String contentType = '1.2.840.113549.1.9.3'; + static const String messageDigest = '1.2.840.113549.1.9.4'; + static const String aaSigningCertificateV2 = '1.2.840.113549.1.9.16.2.47'; +} + +class _DigestAlgorithms { + static const String md5 = 'MD5'; + static const String sha1 = 'SHA-1'; + static const String sha256 = 'SHA-256'; + static const String sha384 = 'SHA-384'; + static const String sha512 = 'SHA-512'; + static const String hmacWithSha1 = 'HmacWithSHA-1'; + static const String hmacWithSha256 = 'HmacWithSHA-256'; + static const String hmacWithMd5 = 'HmacWithMD5'; +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_ede_algorithm.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_ede_algorithm.dart new file mode 100644 index 000000000..520dfc7a6 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_ede_algorithm.dart @@ -0,0 +1,70 @@ +part of pdf; + +class _DesEdeAlogorithm extends _DataEncryption { + _DesEdeAlogorithm() : super() {} + List? _key1; + List? _key2; + List? _key3; + bool? _isEncryption; + + //Properties + int? get blockSize => _blockSize; + String get algorithmName => _Asn1Constants.desEde; + //Implementation + @override + void initialize(bool? forEncryption, _ICipherParameter? parameters) { + if (!(parameters is _KeyParameter)) { + throw ArgumentError.value(parameters, 'parameters', 'Invalid parameter'); + } + final List keyMaster = parameters.keys; + if (keyMaster.length != 24 && keyMaster.length != 16) { + throw ArgumentError.value(parameters, 'parameters', + 'Invalid key size. Size must be 16 or 24 bytes.'); + } + _isEncryption = forEncryption; + final List key1 = List.generate(8, (i) => 0); + List.copyRange(key1, 0, keyMaster, 0, key1.length); + _key1 = generateWorkingKey(forEncryption, key1); + final List key2 = List.generate(8, (i) => 0); + List.copyRange(key2, 0, keyMaster, 8, 8 + key2.length); + _key2 = generateWorkingKey(!forEncryption!, key2); + if (keyMaster.length == 24) { + final List key3 = List.generate(8, (i) => 0); + List.copyRange(key3, 0, keyMaster, 16, 16 + key3.length); + _key3 = generateWorkingKey(forEncryption, key3); + } else { + _key3 = _key1; + } + } + + @override + Map processBlock( + [List? inputBytes, + int? inOffset, + List? outputBytes, + int? outOffset]) { + ArgumentError.checkNotNull(_key1); + if ((inOffset! + _blockSize!) > inputBytes!.length) { + throw ArgumentError.value( + inOffset, 'inOffset', 'Invalid length in input bytes'); + } + if ((outOffset! + _blockSize!) > outputBytes!.length) { + throw ArgumentError.value( + inOffset, 'inOffset', 'Invalid length in output bytes'); + } + final List tempBytes = List.generate(_blockSize!, (i) => 0); + if (_isEncryption!) { + encryptData(_key1, inputBytes, inOffset, tempBytes, 0); + encryptData(_key2, tempBytes, 0, tempBytes, 0); + encryptData(_key3, tempBytes, 0, outputBytes, outOffset); + } else { + encryptData(_key3, inputBytes, inOffset, tempBytes, 0); + encryptData(_key2, tempBytes, 0, tempBytes, 0); + encryptData(_key1, tempBytes, 0, outputBytes, outOffset); + } + return {'length': _blockSize, 'output': outputBytes}; + } + + @override + void reset() {} +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_encryption.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_encryption.dart new file mode 100644 index 000000000..fd78ee9d3 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_encryption.dart @@ -0,0 +1,849 @@ +part of pdf; + +class _DataEncryption implements _ICipher { + _DataEncryption() { + _blockSize = 8; + byteBit = [128, 64, 32, 16, 8, 4, 2, 1]; + bigByte = [ + 0x800000, + 0x400000, + 0x200000, + 0x100000, + 0x80000, + 0x40000, + 0x20000, + 0x10000, + 0x8000, + 0x4000, + 0x2000, + 0x1000, + 0x800, + 0x400, + 0x200, + 0x100, + 0x80, + 0x40, + 0x20, + 0x10, + 0x8, + 0x4, + 0x2, + 0x1 + ]; + pc1 = [ + 56, + 48, + 40, + 32, + 24, + 16, + 8, + 0, + 57, + 49, + 41, + 33, + 25, + 17, + 9, + 1, + 58, + 50, + 42, + 34, + 26, + 18, + 10, + 2, + 59, + 51, + 43, + 35, + 62, + 54, + 46, + 38, + 30, + 22, + 14, + 6, + 61, + 53, + 45, + 37, + 29, + 21, + 13, + 5, + 60, + 52, + 44, + 36, + 28, + 20, + 12, + 4, + 27, + 19, + 11, + 3 + ]; + toTrot = [1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28]; + pc2 = [ + 13, + 16, + 10, + 23, + 0, + 4, + 2, + 27, + 14, + 5, + 20, + 9, + 22, + 18, + 11, + 3, + 25, + 7, + 15, + 6, + 26, + 19, + 12, + 1, + 40, + 51, + 30, + 36, + 46, + 54, + 29, + 39, + 50, + 44, + 32, + 47, + 43, + 48, + 38, + 55, + 33, + 52, + 45, + 41, + 49, + 35, + 28, + 31 + ]; + sp1 = [ + 0x01010400, + 0x00000000, + 0x00010000, + 0x01010404, + 0x01010004, + 0x00010404, + 0x00000004, + 0x00010000, + 0x00000400, + 0x01010400, + 0x01010404, + 0x00000400, + 0x01000404, + 0x01010004, + 0x01000000, + 0x00000004, + 0x00000404, + 0x01000400, + 0x01000400, + 0x00010400, + 0x00010400, + 0x01010000, + 0x01010000, + 0x01000404, + 0x00010004, + 0x01000004, + 0x01000004, + 0x00010004, + 0x00000000, + 0x00000404, + 0x00010404, + 0x01000000, + 0x00010000, + 0x01010404, + 0x00000004, + 0x01010000, + 0x01010400, + 0x01000000, + 0x01000000, + 0x00000400, + 0x01010004, + 0x00010000, + 0x00010400, + 0x01000004, + 0x00000400, + 0x00000004, + 0x01000404, + 0x00010404, + 0x01010404, + 0x00010004, + 0x01010000, + 0x01000404, + 0x01000004, + 0x00000404, + 0x00010404, + 0x01010400, + 0x00000404, + 0x01000400, + 0x01000400, + 0x00000000, + 0x00010004, + 0x00010400, + 0x00000000, + 0x01010004 + ]; + sp2 = [ + 0x80108020, + 0x80008000, + 0x00008000, + 0x00108020, + 0x00100000, + 0x00000020, + 0x80100020, + 0x80008020, + 0x80000020, + 0x80108020, + 0x80108000, + 0x80000000, + 0x80008000, + 0x00100000, + 0x00000020, + 0x80100020, + 0x00108000, + 0x00100020, + 0x80008020, + 0x00000000, + 0x80000000, + 0x00008000, + 0x00108020, + 0x80100000, + 0x00100020, + 0x80000020, + 0x00000000, + 0x00108000, + 0x00008020, + 0x80108000, + 0x80100000, + 0x00008020, + 0x00000000, + 0x00108020, + 0x80100020, + 0x00100000, + 0x80008020, + 0x80100000, + 0x80108000, + 0x00008000, + 0x80100000, + 0x80008000, + 0x00000020, + 0x80108020, + 0x00108020, + 0x00000020, + 0x00008000, + 0x80000000, + 0x00008020, + 0x80108000, + 0x00100000, + 0x80000020, + 0x00100020, + 0x80008020, + 0x80000020, + 0x00100020, + 0x00108000, + 0x00000000, + 0x80008000, + 0x00008020, + 0x80000000, + 0x80100020, + 0x80108020, + 0x00108000 + ]; + sp3 = [ + 0x00000208, + 0x08020200, + 0x00000000, + 0x08020008, + 0x08000200, + 0x00000000, + 0x00020208, + 0x08000200, + 0x00020008, + 0x08000008, + 0x08000008, + 0x00020000, + 0x08020208, + 0x00020008, + 0x08020000, + 0x00000208, + 0x08000000, + 0x00000008, + 0x08020200, + 0x00000200, + 0x00020200, + 0x08020000, + 0x08020008, + 0x00020208, + 0x08000208, + 0x00020200, + 0x00020000, + 0x08000208, + 0x00000008, + 0x08020208, + 0x00000200, + 0x08000000, + 0x08020200, + 0x08000000, + 0x00020008, + 0x00000208, + 0x00020000, + 0x08020200, + 0x08000200, + 0x00000000, + 0x00000200, + 0x00020008, + 0x08020208, + 0x08000200, + 0x08000008, + 0x00000200, + 0x00000000, + 0x08020008, + 0x08000208, + 0x00020000, + 0x08000000, + 0x08020208, + 0x00000008, + 0x00020208, + 0x00020200, + 0x08000008, + 0x08020000, + 0x08000208, + 0x00000208, + 0x08020000, + 0x00020208, + 0x00000008, + 0x08020008, + 0x00020200 + ]; + sp4 = [ + 0x00802001, + 0x00002081, + 0x00002081, + 0x00000080, + 0x00802080, + 0x00800081, + 0x00800001, + 0x00002001, + 0x00000000, + 0x00802000, + 0x00802000, + 0x00802081, + 0x00000081, + 0x00000000, + 0x00800080, + 0x00800001, + 0x00000001, + 0x00002000, + 0x00800000, + 0x00802001, + 0x00000080, + 0x00800000, + 0x00002001, + 0x00002080, + 0x00800081, + 0x00000001, + 0x00002080, + 0x00800080, + 0x00002000, + 0x00802080, + 0x00802081, + 0x00000081, + 0x00800080, + 0x00800001, + 0x00802000, + 0x00802081, + 0x00000081, + 0x00000000, + 0x00000000, + 0x00802000, + 0x00002080, + 0x00800080, + 0x00800081, + 0x00000001, + 0x00802001, + 0x00002081, + 0x00002081, + 0x00000080, + 0x00802081, + 0x00000081, + 0x00000001, + 0x00002000, + 0x00800001, + 0x00002001, + 0x00802080, + 0x00800081, + 0x00002001, + 0x00002080, + 0x00800000, + 0x00802001, + 0x00000080, + 0x00800000, + 0x00002000, + 0x00802080 + ]; + sp5 = [ + 0x00000100, + 0x02080100, + 0x02080000, + 0x42000100, + 0x00080000, + 0x00000100, + 0x40000000, + 0x02080000, + 0x40080100, + 0x00080000, + 0x02000100, + 0x40080100, + 0x42000100, + 0x42080000, + 0x00080100, + 0x40000000, + 0x02000000, + 0x40080000, + 0x40080000, + 0x00000000, + 0x40000100, + 0x42080100, + 0x42080100, + 0x02000100, + 0x42080000, + 0x40000100, + 0x00000000, + 0x42000000, + 0x02080100, + 0x02000000, + 0x42000000, + 0x00080100, + 0x00080000, + 0x42000100, + 0x00000100, + 0x02000000, + 0x40000000, + 0x02080000, + 0x42000100, + 0x40080100, + 0x02000100, + 0x40000000, + 0x42080000, + 0x02080100, + 0x40080100, + 0x00000100, + 0x02000000, + 0x42080000, + 0x42080100, + 0x00080100, + 0x42000000, + 0x42080100, + 0x02080000, + 0x00000000, + 0x40080000, + 0x42000000, + 0x00080100, + 0x02000100, + 0x40000100, + 0x00080000, + 0x00000000, + 0x40080000, + 0x02080100, + 0x40000100 + ]; + sp6 = [ + 0x20000010, + 0x20400000, + 0x00004000, + 0x20404010, + 0x20400000, + 0x00000010, + 0x20404010, + 0x00400000, + 0x20004000, + 0x00404010, + 0x00400000, + 0x20000010, + 0x00400010, + 0x20004000, + 0x20000000, + 0x00004010, + 0x00000000, + 0x00400010, + 0x20004010, + 0x00004000, + 0x00404000, + 0x20004010, + 0x00000010, + 0x20400010, + 0x20400010, + 0x00000000, + 0x00404010, + 0x20404000, + 0x00004010, + 0x00404000, + 0x20404000, + 0x20000000, + 0x20004000, + 0x00000010, + 0x20400010, + 0x00404000, + 0x20404010, + 0x00400000, + 0x00004010, + 0x20000010, + 0x00400000, + 0x20004000, + 0x20000000, + 0x00004010, + 0x20000010, + 0x20404010, + 0x00404000, + 0x20400000, + 0x00404010, + 0x20404000, + 0x00000000, + 0x20400010, + 0x00000010, + 0x00004000, + 0x20400000, + 0x00404010, + 0x00004000, + 0x00400010, + 0x20004010, + 0x00000000, + 0x20404000, + 0x20000000, + 0x00400010, + 0x20004010 + ]; + sp7 = [ + 0x00200000, + 0x04200002, + 0x04000802, + 0x00000000, + 0x00000800, + 0x04000802, + 0x00200802, + 0x04200800, + 0x04200802, + 0x00200000, + 0x00000000, + 0x04000002, + 0x00000002, + 0x04000000, + 0x04200002, + 0x00000802, + 0x04000800, + 0x00200802, + 0x00200002, + 0x04000800, + 0x04000002, + 0x04200000, + 0x04200800, + 0x00200002, + 0x04200000, + 0x00000800, + 0x00000802, + 0x04200802, + 0x00200800, + 0x00000002, + 0x04000000, + 0x00200800, + 0x04000000, + 0x00200800, + 0x00200000, + 0x04000802, + 0x04000802, + 0x04200002, + 0x04200002, + 0x00000002, + 0x00200002, + 0x04000000, + 0x04000800, + 0x00200000, + 0x04200800, + 0x00000802, + 0x00200802, + 0x04200800, + 0x00000802, + 0x04000002, + 0x04200802, + 0x04200000, + 0x00200800, + 0x00000000, + 0x00000002, + 0x04200802, + 0x00000000, + 0x00200802, + 0x04200000, + 0x00000800, + 0x04000002, + 0x04000800, + 0x00000800, + 0x00200002 + ]; + sp8 = [ + 0x10001040, + 0x00001000, + 0x00040000, + 0x10041040, + 0x10000000, + 0x10001040, + 0x00000040, + 0x10000000, + 0x00040040, + 0x10040000, + 0x10041040, + 0x00041000, + 0x10041000, + 0x00041040, + 0x00001000, + 0x00000040, + 0x10040000, + 0x10000040, + 0x10001000, + 0x00001040, + 0x00041000, + 0x00040040, + 0x10040040, + 0x10041000, + 0x00001040, + 0x00000000, + 0x00000000, + 0x10040040, + 0x10000040, + 0x10001000, + 0x00041040, + 0x00040000, + 0x00041040, + 0x00040000, + 0x10041000, + 0x00001000, + 0x00000040, + 0x10040040, + 0x00001000, + 0x00041040, + 0x10001000, + 0x00000040, + 0x10000040, + 0x10040000, + 0x10040040, + 0x10000000, + 0x00040000, + 0x10001040, + 0x00000000, + 0x10041040, + 0x00040040, + 0x10000040, + 0x10040000, + 0x10001000, + 0x10001040, + 0x00000000, + 0x10041040, + 0x00041000, + 0x00041000, + 0x00001040, + 0x00001040, + 0x00040040, + 0x10000000, + 0x10041000 + ]; + } + + //Fields + int? _blockSize; + late List byteBit; + late List bigByte; + late List pc1; + late List toTrot; + late List pc2; + late List sp1; + late List sp2; + late List sp3; + late List sp4; + late List sp5; + late List sp6; + late List sp7; + late List sp8; + List? _keys; + + //Properties + List? get keys => _keys; + String get algorithmName => _Asn1Constants.des; + bool get isBlock => false; + int? get blockSize => _blockSize; + + //Implementation + void initialize(bool? isEncryption, _ICipherParameter? parameters) { + if (!(parameters is _KeyParameter)) { + throw ArgumentError.value(parameters, 'parameters', 'Invalid parameter'); + } + _keys = generateWorkingKey(isEncryption, parameters.keys); + } + + Map processBlock( + List? inBytes, int inOffset, List? outBytes, int? outOffset) { + ArgumentError.checkNotNull(_keys); + if ((inOffset + _blockSize!) > inBytes!.length) { + throw ArgumentError.value( + inOffset, 'inOffset', 'Invalid length in input bytes'); + } + if ((outOffset! + _blockSize!) > outBytes!.length) { + throw ArgumentError.value( + outOffset, 'outOffset', 'Invalid length in output bytes'); + } + encryptData(_keys, inBytes, inOffset, outBytes, outOffset); + return {'length': _blockSize, 'output': outBytes}; + } + + void reset() {} + List generateWorkingKey(bool? isEncrypt, List bytes) { + final List newKeys = List.generate(32, (i) => 0); + final List bytes1 = List.generate(56, (i) => false); + final List bytes2 = List.generate(56, (i) => false); + for (int j = 0; j < 56; j++) { + final int length = pc1[j]; + bytes1[j] = + ((bytes[length.toUnsigned(32) >> 3] & byteBit[length & 07]) != 0); + } + for (int i = 0; i < 16; i++) { + int a; + int b; + int c; + if (isEncrypt!) { + b = i << 1; + } else { + b = (15 - i) << 1; + } + c = b + 1; + newKeys[b] = newKeys[c] = 0; + for (int j = 0; j < 28; j++) { + a = j + toTrot[i]; + if (a < 28) { + bytes2[j] = bytes1[a]; + } else { + bytes2[j] = bytes1[a - 28]; + } + } + for (int j = 28; j < 56; j++) { + a = j + toTrot[i]; + if (a < 56) { + bytes2[j] = bytes1[a]; + } else { + bytes2[j] = bytes1[a - 28]; + } + } + for (int j = 0; j < 24; j++) { + if (bytes2[pc2[j]]) { + newKeys[b] |= bigByte[j]; + } + if (bytes2[pc2[j + 24]]) { + newKeys[c] |= bigByte[j]; + } + } + } + for (int i = 0; i != 32; i += 2) { + int value1, value2; + value1 = newKeys[i]; + value2 = newKeys[i + 1]; + newKeys[i] = (((value1 & 0x00fc0000) << 6).toUnsigned(32) | + ((value1 & 0x00000fc0) << 10).toUnsigned(32) | + ((value2 & 0x00fc0000).toUnsigned(32) >> 10) | + ((value2 & 0x00000fc0).toUnsigned(32) >> 6)) + .toSigned(32); + newKeys[i + 1] = (((value1 & 0x0003f000) << 12).toUnsigned(32) | + ((value1 & 0x0000003f) << 16).toUnsigned(32) | + ((value2 & 0x0003f000).toUnsigned(32) >> 4) | + (value2 & 0x0000003f).toUnsigned(32)) + .toSigned(32); + } + return newKeys; + } + + void encryptData(List? keys, List inputBytes, int inOffset, + List outBytes, int outOffset) { + int left = _Asn1Constants.beToUInt32(inputBytes, inOffset); + int right = _Asn1Constants.beToUInt32(inputBytes, inOffset + 4); + int data = (((left >> 4) ^ right) & 0x0f0f0f0f).toUnsigned(32); + right ^= data; + left ^= (data << 4); + data = ((left >> 16) ^ right) & 0x0000ffff; + right ^= data; + left ^= (data << 16); + data = ((right >> 2) ^ left) & 0x33333333; + left ^= data; + right ^= (data << 2); + data = ((right >> 8) ^ left) & 0x00ff00ff; + left ^= data; + right ^= (data << 8); + right = ((right << 1) | (right >> 31)).toUnsigned(32); + data = (left ^ right) & 0xaaaaaaaa; + left ^= data; + right ^= data; + left = ((left << 1) | (left >> 31)).toUnsigned(32); + for (int round = 0; round < 8; round++) { + data = ((right << 28) | (right >> 4)).toUnsigned(32); + data ^= (keys![round * 4 + 0]).toUnsigned(32); + int value = sp7[data & 0x3f]; + value |= sp5[(data >> 8) & 0x3f]; + value |= sp3[(data >> 16) & 0x3f]; + value |= sp1[(data >> 24) & 0x3f]; + data = right ^ (keys[round * 4 + 1]).toUnsigned(32); + value |= sp8[data & 0x3f]; + value |= sp6[(data >> 8) & 0x3f]; + value |= sp4[(data >> 16) & 0x3f]; + value |= sp2[(data >> 24) & 0x3f]; + left ^= value; + data = ((left << 28) | (left >> 4)).toUnsigned(32); + data ^= (keys[round * 4 + 2]).toUnsigned(32); + value = sp7[data & 0x3f]; + value |= sp5[(data >> 8) & 0x3f]; + value |= sp3[(data >> 16) & 0x3f]; + value |= sp1[(data >> 24) & 0x3f]; + data = left ^ (keys[round * 4 + 3]).toUnsigned(32); + value |= sp8[data & 0x3f]; + value |= sp6[(data >> 8) & 0x3f]; + value |= sp4[(data >> 16) & 0x3f]; + value |= sp2[(data >> 24) & 0x3f]; + right ^= value; + } + right = ((right << 31) | (right >> 1)).toUnsigned(32); + data = (left ^ right) & 0xaaaaaaaa; + left ^= data; + right ^= data; + left = ((left << 31) | (left >> 1)).toUnsigned(32); + data = ((left >> 8) ^ right) & 0x00ff00ff; + right ^= data; + left ^= (data << 8); + data = ((left >> 2) ^ right) & 0x33333333; + right ^= data; + left ^= (data << 2); + data = ((right >> 16) ^ left) & 0x0000ffff; + left ^= data; + right ^= (data << 16); + data = ((right >> 4) ^ left) & 0x0f0f0f0f; + left ^= data; + right ^= (data << 4); + _Asn1Constants.uInt32ToBe(right, outBytes, outOffset); + _Asn1Constants.uInt32ToBe(left, outBytes, outOffset + 4); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/ipadding.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/ipadding.dart new file mode 100644 index 000000000..320c1bb71 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/ipadding.dart @@ -0,0 +1,79 @@ +part of pdf; + +class _IPadding { + void initialize(Random? random) {} + String? get paddingName => null; + int? addPadding(List? bytes, int? offset) => null; + int? count(List? bytes) => null; +} + +class _IBufferedCipher { + String? get algorithmName => null; + void initialize(bool forEncryption, _ICipherParameter? parameters) {} + int? get blockSize => null; + int? getOutputSize(int inputLen) => null; + int? getUpdateOutputSize(int inputLen) => null; + + List? processByte(int intput) => null; + Map? copyBytes(int input, List output, int outOff) => + null; + List? readBytesFromInput(List input) => null; + List? readBytes(List input, int inOff, int length) => null; + Map? processByteFromValues( + List input, List output, int outOff) => + null; + Map? processBytes(List input, int inOff, int length, + List output, int outOff) => + null; + + List? doFinal() => null; + List? doFinalFromInput(List? input) => null; + List? readFinal(List input, int inOff, int length) => null; + Map? writeFinal(List output, int outOff) => null; + Map? copyFinal( + List input, List output, int outOff) => + null; + Map? readFinalValues(List input, int inOff, int length, + List output, int outOff) => + null; + void reset() {} +} + +class _ICipher { + String? get algorithmName => null; + void initialize(bool? isEncryption, _ICipherParameter? parameters) {} + int? get blockSize => null; + bool? get isBlock => null; + Map? processBlock(List? inBytes, int inOffset, + List? outBytes, int? outOffset) => + null; + void reset() {} +} + +class _ICipherBlock { + String? get algorithmName => null; + void initialize(bool isEncryption, _ICipherParameter? parameters) {} + int? get inputBlock => null; + int? get outputBlock => null; + List? processBlock(List bytes, int offset, int length) => null; +} + +class _ICipherParameter { + List? keys; +} + +class _ISigner { + //String get algorithmName => null; + void initialize(bool isSigning, _ICipherParameter? parameters) {} + //void update(int input) {} + void blockUpdate(List bytes, int offset, int length) {} + List? generateSignature() => null; + //bool validateSignature(List bytes) => null; + void reset() {} +} + +class _IRandom { + int? getValue(int position, [List? bytes, int? offset, int? length]) => + null; + int? get length => null; +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/pdf_cms_signer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/pdf_cms_signer.dart new file mode 100644 index 000000000..0dab6d82c --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/pdf_cms_signer.dart @@ -0,0 +1,192 @@ +part of pdf; + +class _PdfCmsSigner { + _PdfCmsSigner( + _ICipherParameter? privateKey, + List<_X509Certificate?> certChain, + String hashAlgorithm, + bool hasRSAdata) { + _digestAlgorithm = _MessageDigestAlgorithms(); + _digestAlgorithmOid = _digestAlgorithm.getAllowedDigests(hashAlgorithm); + if (_digestAlgorithmOid == null) { + throw ArgumentError.value( + hashAlgorithm, 'hashAlgorithm', 'Unknown Hash Algorithm'); + } + _version = 1; + _signerVersion = 1; + _certificates = + List<_X509Certificate?>.generate(certChain.length, (i) => certChain[i]); + _digestOid = {}; + _digestOid[_digestAlgorithmOid] = null; + _signCert = _certificates[0]; + if (privateKey != null) { + if (privateKey is _RsaKeyParam) { + _encryptionAlgorithmOid = _DigitalIdentifiers.rsa; + } else { + throw ArgumentError.value( + privateKey, 'privateKey', 'Unknown key algorithm'); + } + } + if (hasRSAdata) { + _rsaData = []; + } + } + + //Fields + late _MessageDigestAlgorithms _digestAlgorithm; + late int _version; + late int _signerVersion; + late List<_X509Certificate?> _certificates; + late Map _digestOid; + String? _digestAlgorithmOid; + _X509Certificate? _signCert; + late String _encryptionAlgorithmOid; + String? _hashAlgorithm; + List? _rsaData; + List? _signedData; + List? _signedRsaData; + List? _digest; + + //Properties + String? get hashAlgorithm { + _hashAlgorithm ??= _digestAlgorithm.getDigest(_digestAlgorithmOid); + return _hashAlgorithm; + } + + //Implementation + _DerSet getSequenceDataSet(List secondDigest, List? ocsp, + List>? crlBytes, CryptographicStandard? sigtype) { + final _Asn1EncodeCollection attribute = _Asn1EncodeCollection(); + _Asn1EncodeCollection v = _Asn1EncodeCollection(); + v._encodableObjects.add(_DerObjectID(_DigitalIdentifiers.contentType)); + v._encodableObjects.add(_DerSet( + array: <_Asn1Encode>[_DerObjectID(_DigitalIdentifiers.pkcs7Data)])); + attribute._encodableObjects.add(_DerSequence(collection: v)); + v = _Asn1EncodeCollection(); + v._encodableObjects.add(_DerObjectID(_DigitalIdentifiers.messageDigest)); + v._encodableObjects + .add(_DerSet(array: <_Asn1Encode>[_DerOctet(secondDigest)])); + attribute._encodableObjects.add(_DerSequence(collection: v)); + if (sigtype == CryptographicStandard.cades) { + v = _Asn1EncodeCollection(); + v._encodableObjects + .add(_DerObjectID(_DigitalIdentifiers.aaSigningCertificateV2)); + final _Asn1EncodeCollection aaV2 = _Asn1EncodeCollection(); + final _MessageDigestAlgorithms alg = _MessageDigestAlgorithms(); + final String? sha256Oid = + alg.getAllowedDigests(_MessageDigestAlgorithms.secureHash256); + if (sha256Oid != _digestAlgorithmOid) { + aaV2._encodableObjects + .add(_Algorithms(_DerObjectID(_digestAlgorithmOid!))); + } + final List dig = alg + .getMessageDigest(hashAlgorithm!) + .convert(_signCert!._c!.getEncoded(_Asn1Constants.der)) + .bytes; + aaV2._encodableObjects.add(_DerOctet(dig)); + v._encodableObjects.add(_DerSet(array: <_Asn1Encode>[ + _DerSequence.fromObject( + _DerSequence.fromObject(_DerSequence(collection: aaV2))) + ])); + attribute._encodableObjects.add(_DerSequence(collection: v)); + } + return _DerSet(collection: attribute); + } + + void setSignedData( + List digest, List? rsaData, String? digestEncryptionAlgorithm) { + _signedData = digest; + _signedRsaData = rsaData; + if (digestEncryptionAlgorithm != null) { + if (digestEncryptionAlgorithm == 'RSA') { + _encryptionAlgorithmOid = _DigitalIdentifiers.rsa; + } else if (digestEncryptionAlgorithm == 'DSA') { + _encryptionAlgorithmOid = _DigitalIdentifiers.dsa; + } else if (digestEncryptionAlgorithm == 'ECDSA') { + _encryptionAlgorithmOid = _DigitalIdentifiers.ecdsa; + } else { + throw ArgumentError.value( + digestEncryptionAlgorithm, 'algorithm', 'Invalid entry'); + } + } + } + + List? sign( + List secondDigest, + dynamic server, + List? timeStampResponse, + List? ocsp, + List>? crls, + CryptographicStandard? sigtype, + String? hashAlgorithm) { + if (_signedData != null) { + _digest = _signedData; + if (_rsaData != null) { + _rsaData = _signedRsaData; + } + } + final _Asn1EncodeCollection digestAlgorithms = _Asn1EncodeCollection(); + final List keys = _digestOid.keys.toList(); + keys.forEach((dal) { + final _Asn1EncodeCollection algos = _Asn1EncodeCollection(); + algos._encodableObjects.add(_DerObjectID(dal!)); + algos._encodableObjects.add(_DerNull.value); + digestAlgorithms._encodableObjects.add(_DerSequence(collection: algos)); + }); + _Asn1EncodeCollection v = _Asn1EncodeCollection(); + v._encodableObjects.add(_DerObjectID(_DigitalIdentifiers.pkcs7Data)); + if (_rsaData != null) { + v._encodableObjects.add(_DerTag(0, _DerOctet(_rsaData!))); + } + final _DerSequence contentinfo = _DerSequence(collection: v); + + v = _Asn1EncodeCollection(); + _certificates.forEach((xcert) { + v._encodableObjects.add( + _Asn1Stream(_StreamReader(xcert!._c!.getEncoded(_Asn1Constants.der))) + .readAsn1()); + }); + final _DerSet dercertificates = _DerSet(collection: v); + final _Asn1EncodeCollection signerinfo = _Asn1EncodeCollection(); + signerinfo._encodableObjects + .add(_DerInteger(_bigIntToBytes(BigInt.from(_signerVersion)))); + v = _Asn1EncodeCollection(); + v._encodableObjects.add(getIssuer( + _signCert!._c!.tbsCertificate!.getEncoded(_Asn1Constants.der))); + v._encodableObjects + .add(_DerInteger(_bigIntToBytes(_signCert!._c!.serialNumber!.value))); + signerinfo._encodableObjects.add(_DerSequence(collection: v)); + v = _Asn1EncodeCollection(); + v._encodableObjects.add(_DerObjectID(_digestAlgorithmOid!)); + v._encodableObjects.add(_DerNull.value); + signerinfo._encodableObjects.add(_DerSequence(collection: v)); + signerinfo._encodableObjects.add(_DerTag( + 0, getSequenceDataSet(secondDigest, ocsp, crls, sigtype), false)); + v = _Asn1EncodeCollection(); + v._encodableObjects.add(_DerObjectID(_encryptionAlgorithmOid)); + v._encodableObjects.add(_DerNull.value); + signerinfo._encodableObjects.add(_DerSequence(collection: v)); + signerinfo._encodableObjects.add(_DerOctet(_digest!)); + final _Asn1EncodeCollection body = _Asn1EncodeCollection(); + body._encodableObjects + .add(_DerInteger(_bigIntToBytes(BigInt.from(_version)))); + body._encodableObjects.add(_DerSet(collection: digestAlgorithms)); + body._encodableObjects.add(contentinfo); + body._encodableObjects.add(_DerTag(0, dercertificates, false)); + body._encodableObjects.add( + _DerSet(array: <_Asn1Encode>[_DerSequence(collection: signerinfo)])); + final _Asn1EncodeCollection whole = _Asn1EncodeCollection(); + whole._encodableObjects + .add(_DerObjectID(_DigitalIdentifiers.pkcs7SignedData)); + whole._encodableObjects.add(_DerTag(0, _DerSequence(collection: body))); + final _Asn1DerStream dout = _Asn1DerStream([]); + dout.writeObject(_DerSequence(collection: whole)); + return dout._stream; + } + + _Asn1? getIssuer(List? data) { + final _Asn1Sequence seq = + _Asn1Stream(_StreamReader(data)).readAsn1() as _Asn1Sequence; + return seq[seq[0] is _Asn1Tag ? 3 : 2] as _Asn1?; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/pkcs1_encoding.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/pkcs1_encoding.dart new file mode 100644 index 000000000..3e965b6cf --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/pkcs1_encoding.dart @@ -0,0 +1,91 @@ +part of pdf; + +class _Pkcs1Encoding implements _ICipherBlock { + _Pkcs1Encoding(_ICipherBlock? cipher) { + _cipher = cipher; + } + //Fields + _ICipherBlock? _cipher; + late bool _isEncryption; + bool? _isPrivateKey; + late Random _random; + //Properties + String get algorithmName => _cipher!.algorithmName! + '/PKCS1Padding'; + int? get inputBlock => + _isEncryption ? _cipher!.inputBlock! - 10 : _cipher!.inputBlock; + int? get outputBlock => + _isEncryption ? _cipher!.outputBlock : _cipher!.outputBlock! - 10; + //Implmentation + @override + void initialize(bool forEncryption, _ICipherParameter? parameters) { + _CipherParameter? kParam; + _random = Random.secure(); + kParam = parameters as _CipherParameter?; + _cipher!.initialize(forEncryption, parameters); + _isPrivateKey = kParam!.isPrivate; + _isEncryption = forEncryption; + } + + List? processBlock(List input, int inOff, int length) { + return _isEncryption + ? encodeBlock(input, inOff, length) + : decodeBlock(input, inOff, length); + } + + List? encodeBlock(List input, int inOff, int inLen) { + if (inLen > inputBlock!) { + throw ArgumentError.value(inLen, 'inLen', 'Input data too large'); + } + List block = List.generate(_cipher!.inputBlock!, (i) => 0); + if (_isPrivateKey!) { + block[0] = 0x01; + for (int i = 1; i != block.length - inLen - 1; i++) { + block[i] = (0xFF).toUnsigned(8); + } + } else { + block = + List.generate(_cipher!.inputBlock!, (i) => _random.nextInt(256)); + block[0] = 0x02; + for (int i = 1; i != block.length - inLen - 1; i++) { + while (block[i] == 0) { + block[i] = _random.nextInt(256); + } + } + } + block[block.length - inLen - 1] = 0x00; + List.copyRange(block, block.length - inLen, input, inOff, inOff + inLen); + return _cipher!.processBlock(block, 0, block.length); + } + + List decodeBlock(List input, int inOff, int inLen) { + final List block = _cipher!.processBlock(input, inOff, inLen)!; + if (block.length < outputBlock!) { + throw ArgumentError.value( + inLen, 'inLen', 'Invalid block. Block truncated'); + } + final int type = block[0]; + if (type != 1 && type != 2) { + throw ArgumentError.value(type, 'type', 'Invalid block type'); + } + if (block.length != _cipher!.outputBlock) { + throw ArgumentError.value(type, 'type', 'Invalid size'); + } + int start; + for (start = 1; start != block.length; start++) { + final int pad = block[start]; + if (pad == 0) { + break; + } + if (type == 1 && pad != (0xff).toUnsigned(8)) { + throw ArgumentError.value(type, 'type', 'Invalid block padding'); + } + } + start++; + if (start > block.length || start < 10) { + throw ArgumentError.value(start, 'start', 'no data in block'); + } + final List result = List.generate(block.length - start, (i) => 0); + List.copyRange(result, 0, block, start, start + result.length); + return result; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/random_array.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/random_array.dart new file mode 100644 index 000000000..046df7723 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/random_array.dart @@ -0,0 +1,184 @@ +part of pdf; + +class _RandomArray implements _IRandom { + _RandomArray(List array) { + _array = array; + } + //Fields + late List _array; + //Properties + @override + int get length => _array.length; + //Implementation + @override + int? getValue(int offset, [List? bytes, int? off, int? length]) { + if (bytes == null) { + if (offset >= _array.length) { + return -1; + } + return (0xff & _array[offset]); + } else { + if (offset >= _array.length) { + return -1; + } + if (offset + length! > _array.length) { + length = (_array.length - offset).toSigned(32); + } + List.copyRange(bytes, off!, _array, offset, offset + length); + return length; + } + } +} + +class _WindowRandom implements _IRandom { + _WindowRandom(_IRandom source, int offset, int length) { + _source = source; + _offset = offset; + _length = length; + } + //Fields + late _IRandom _source; + late int _offset; + int? _length; + //Properties + @override + int? get length => _length; + //Implementation + @override + int? getValue(int position, [List? bytes, int? off, int? len]) { + if (position >= _length!) { + return -1; + } + if (bytes == null) { + return _source.getValue(_offset + position); + } else { + final int toRead = min(len!, _length! - position); + return _source.getValue(_offset + position, bytes, off, toRead); + } + } +} + +class _RandomGroup implements _IRandom { + _RandomGroup(List<_IRandom?> sources) { + _sources = <_SourceEntry>[]; + int totalSize = 0; + int i = 0; + sources.forEach((ras) { + _sources.add(_SourceEntry(i, ras!, totalSize)); + ++i; + totalSize += ras.length!; + }); + _size = totalSize; + _cse = _sources[sources.length - 1]; + } + //Fields + late List<_SourceEntry> _sources; + _SourceEntry? _cse; + int? _size; + //Properties + @override + int? get length => _size; + //Implementation + @override + int getValue(int position, [List? bytes, int? off, int? len]) { + _SourceEntry? entry = getEntry(position); + if (entry == null) { + return -1; + } + int offN = entry.offsetN(position); + int? remaining = len; + bool isContinue = true; + while (isContinue && remaining! > 0) { + if (entry == null || offN > entry._source.length!) { + isContinue = false; + } else { + final int? count = entry._source.getValue(offN, bytes, off, remaining); + if (count == -1) { + isContinue = false; + } else { + off = off! + count!; + position += count; + remaining -= count; + offN = 0; + entry = getEntry(position); + } + } + } + return remaining == len ? -1 : len! - remaining!; + } + + int? getStartIndex(int offset) { + if (offset >= _cse!._startByte) { + return _cse!._index; + } + return 0; + } + + _SourceEntry? getEntry(int offset) { + if (offset >= _size!) { + return null; + } + if (offset >= _cse!._startByte && offset <= _cse!._endByte) { + return _cse; + } + final int startAt = getStartIndex(offset)!; + for (int i = startAt; i < _sources.length; i++) { + if (offset >= _sources[i]._startByte && offset <= _sources[i]._endByte) { + _cse = _sources[i]; + return _cse; + } + } + return null; + } +} + +class _SourceEntry { + _SourceEntry(int index, _IRandom source, int offset) { + _index = index; + _source = source; + _startByte = offset; + _endByte = offset + source.length! - 1; + } + //Fields + late _IRandom _source; + late int _startByte; + late int _endByte; + int? _index; + //Implementation + int offsetN(int absoluteOffset) { + return absoluteOffset - _startByte; + } +} + +class _RandomStream extends _StreamReader { + _RandomStream(_IRandom source) + : super(List.generate(source.length!, (i) => 0)) { + _random = source; + } + //Fields + late _IRandom _random; + int position = 0; + //Properties + @override + int? get length => _random.length; + + //Implementation + @override + int? read(List buffer, int offset, int length) { + final int? count = _random.getValue(position, buffer, offset, length); + if (count == -1) { + return 0; + } + position += count!; + return count; + } + + @override + int readByte() { + final int c = _random.getValue(position)!; + if (c >= 0) { + ++position; + } + return c; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rc2_algorithm.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rc2_algorithm.dart new file mode 100644 index 000000000..eaed9aa13 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rc2_algorithm.dart @@ -0,0 +1,422 @@ +part of pdf; + +class _Rc2Algorithm implements _ICipher { + _Rc2Algorithm() { + _piTable = [ + 217, + 120, + 249, + 196, + 25, + 221, + 181, + 237, + 40, + 233, + 253, + 121, + 74, + 160, + 216, + 157, + 198, + 126, + 55, + 131, + 43, + 118, + 83, + 142, + 98, + 76, + 100, + 136, + 68, + 139, + 251, + 162, + 23, + 154, + 89, + 245, + 135, + 179, + 79, + 19, + 97, + 69, + 109, + 141, + 9, + 129, + 125, + 50, + 189, + 143, + 64, + 235, + 134, + 183, + 123, + 11, + 240, + 149, + 33, + 34, + 92, + 107, + 78, + 130, + 84, + 214, + 101, + 147, + 206, + 96, + 178, + 28, + 115, + 86, + 192, + 20, + 167, + 140, + 241, + 220, + 18, + 117, + 202, + 31, + 59, + 190, + 228, + 209, + 66, + 61, + 212, + 48, + 163, + 60, + 182, + 38, + 111, + 191, + 14, + 218, + 70, + 105, + 7, + 87, + 39, + 242, + 29, + 155, + 188, + 148, + 67, + 3, + 248, + 17, + 199, + 246, + 144, + 239, + 62, + 231, + 6, + 195, + 213, + 47, + 200, + 102, + 30, + 215, + 8, + 232, + 234, + 222, + 128, + 82, + 238, + 247, + 132, + 170, + 114, + 172, + 53, + 77, + 106, + 42, + 150, + 26, + 210, + 113, + 90, + 21, + 73, + 116, + 75, + 159, + 208, + 94, + 4, + 24, + 164, + 236, + 194, + 224, + 65, + 110, + 15, + 81, + 203, + 204, + 36, + 145, + 175, + 80, + 161, + 244, + 112, + 57, + 153, + 124, + 58, + 133, + 35, + 184, + 180, + 122, + 252, + 2, + 54, + 91, + 37, + 85, + 151, + 49, + 45, + 93, + 250, + 152, + 227, + 138, + 146, + 174, + 5, + 223, + 41, + 16, + 103, + 108, + 186, + 201, + 211, + 0, + 230, + 207, + 225, + 158, + 168, + 44, + 99, + 22, + 1, + 63, + 88, + 226, + 137, + 169, + 13, + 56, + 52, + 27, + 171, + 51, + 255, + 176, + 187, + 72, + 12, + 95, + 185, + 177, + 205, + 46, + 197, + 243, + 219, + 71, + 229, + 165, + 156, + 119, + 10, + 166, + 32, + 104, + 254, + 127, + 193, + 173 + ]; + _blockSize = 8; + } + //Fields + int? _blockSize; + late List _key; + bool? _isEncrypt; + late List _piTable; + + //Properties + String get algorithmName => 'RC2'; + bool get isBlock => false; + int? get blockSize => _blockSize; + + //Implementation + List generateKey(List key, int bits) { + int x; + final List xKey = List.generate(128, (i) => 0); + for (int i = 0; i != key.length; i++) { + xKey[i] = key[i] & 0xff; + } + int len = key.length; + if (len < 128) { + int index = 0; + x = xKey[len - 1]; + do { + x = _piTable[(x + xKey[index++]) & 255] & 0xff; + xKey[len++] = x; + } while (len < 128); + } + len = (bits + 7) >> 3; + x = _piTable[xKey[128 - len] & (255 >> (7 & -bits))] & 0xff; + xKey[128 - len] = x; + for (int i = 128 - len - 1; i >= 0; i--) { + x = _piTable[x ^ xKey[i + len]] & 0xff; + xKey[i] = x; + } + final List newKey = []; + for (int i = 0; i < 64; i++) { + newKey.add((xKey[2 * i] + (xKey[2 * i + 1] << 8))); + } + return newKey; + } + + void initialize(bool? forEncryption, _ICipherParameter? parameters) { + _isEncrypt = forEncryption; + if (parameters is _KeyParameter) { + final List key = parameters.keys; + _key = generateKey(key, key.length * 8); + } + } + + void reset() {} + Map processBlock( + List? input, int inOff, List? output, int? outOff) { + if (_isEncrypt!) { + encryptBlock(input!, inOff, output!, outOff!); + } else { + decryptBlock(input!, inOff, output!, outOff!); + } + return {'length': _blockSize, 'output': output}; + } + + int rotateWordLeft(int x, int y) { + x &= 0xffff; + return (x << y) | (x >> (16 - y)); + } + + void encryptBlock( + List input, int inOff, List outBytes, int outOff) { + int x76 = ((input[inOff + 7] & 0xff) << 8) + (input[inOff + 6] & 0xff); + int x54 = ((input[inOff + 5] & 0xff) << 8) + (input[inOff + 4] & 0xff); + int x32 = ((input[inOff + 3] & 0xff) << 8) + (input[inOff + 2] & 0xff); + int x10 = ((input[inOff + 1] & 0xff) << 8) + (input[inOff + 0] & 0xff); + for (int i = 0; i <= 16; i += 4) { + x10 = rotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + _key[i], 1); + x32 = rotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + _key[i + 1], 2); + x54 = rotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + _key[i + 2], 3); + x76 = rotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + _key[i + 3], 5); + } + x10 += _key[x76 & 63]; + x32 += _key[x10 & 63]; + x54 += _key[x32 & 63]; + x76 += _key[x54 & 63]; + for (int i = 20; i <= 40; i += 4) { + x10 = rotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + _key[i], 1); + x32 = rotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + _key[i + 1], 2); + x54 = rotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + _key[i + 2], 3); + x76 = rotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + _key[i + 3], 5); + } + x10 += _key[x76 & 63]; + x32 += _key[x10 & 63]; + x54 += _key[x32 & 63]; + x76 += _key[x54 & 63]; + for (int i = 44; i < 64; i += 4) { + x10 = rotateWordLeft(x10 + (x32 & ~x76) + (x54 & x76) + _key[i], 1); + x32 = rotateWordLeft(x32 + (x54 & ~x10) + (x76 & x10) + _key[i + 1], 2); + x54 = rotateWordLeft(x54 + (x76 & ~x32) + (x10 & x32) + _key[i + 2], 3); + x76 = rotateWordLeft(x76 + (x10 & ~x54) + (x32 & x54) + _key[i + 3], 5); + } + outBytes[outOff + 0] = x10.toUnsigned(8); + outBytes[outOff + 1] = (x10 >> 8).toUnsigned(8); + outBytes[outOff + 2] = x32.toUnsigned(8); + outBytes[outOff + 3] = (x32 >> 8).toUnsigned(8); + outBytes[outOff + 4] = x54.toUnsigned(8); + outBytes[outOff + 5] = (x54 >> 8).toUnsigned(8); + outBytes[outOff + 6] = x76.toUnsigned(8); + outBytes[outOff + 7] = (x76 >> 8).toUnsigned(8); + } + + void decryptBlock( + List input, int inOff, List outBytes, int outOff) { + int x76 = ((input[inOff + 7] & 0xff) << 8) + (input[inOff + 6] & 0xff); + int x54 = ((input[inOff + 5] & 0xff) << 8) + (input[inOff + 4] & 0xff); + int x32 = ((input[inOff + 3] & 0xff) << 8) + (input[inOff + 2] & 0xff); + int x10 = ((input[inOff + 1] & 0xff) << 8) + (input[inOff + 0] & 0xff); + for (int i = 60; i >= 44; i -= 4) { + x76 = + rotateWordLeft(x76, 11) - ((x10 & ~x54) + (x32 & x54) + _key[i + 3]); + x54 = + rotateWordLeft(x54, 13) - ((x76 & ~x32) + (x10 & x32) + _key[i + 2]); + x32 = + rotateWordLeft(x32, 14) - ((x54 & ~x10) + (x76 & x10) + _key[i + 1]); + x10 = rotateWordLeft(x10, 15) - ((x32 & ~x76) + (x54 & x76) + _key[i]); + } + x76 -= _key[x54 & 63]; + x54 -= _key[x32 & 63]; + x32 -= _key[x10 & 63]; + x10 -= _key[x76 & 63]; + for (int i = 40; i >= 20; i -= 4) { + x76 = + rotateWordLeft(x76, 11) - ((x10 & ~x54) + (x32 & x54) + _key[i + 3]); + x54 = + rotateWordLeft(x54, 13) - ((x76 & ~x32) + (x10 & x32) + _key[i + 2]); + x32 = + rotateWordLeft(x32, 14) - ((x54 & ~x10) + (x76 & x10) + _key[i + 1]); + x10 = rotateWordLeft(x10, 15) - ((x32 & ~x76) + (x54 & x76) + _key[i]); + } + x76 -= _key[x54 & 63]; + x54 -= _key[x32 & 63]; + x32 -= _key[x10 & 63]; + x10 -= _key[x76 & 63]; + for (int i = 16; i >= 0; i -= 4) { + x76 = + rotateWordLeft(x76, 11) - ((x10 & ~x54) + (x32 & x54) + _key[i + 3]); + x54 = + rotateWordLeft(x54, 13) - ((x76 & ~x32) + (x10 & x32) + _key[i + 2]); + x32 = + rotateWordLeft(x32, 14) - ((x54 & ~x10) + (x76 & x10) + _key[i + 1]); + x10 = rotateWordLeft(x10, 15) - ((x32 & ~x76) + (x54 & x76) + _key[i]); + } + outBytes[outOff + 0] = x10.toUnsigned(8); + outBytes[outOff + 1] = (x10 >> 8).toUnsigned(8); + outBytes[outOff + 2] = x32.toUnsigned(8); + outBytes[outOff + 3] = (x32 >> 8).toUnsigned(8); + outBytes[outOff + 4] = x54.toUnsigned(8); + outBytes[outOff + 5] = (x54 >> 8).toUnsigned(8); + outBytes[outOff + 6] = x76.toUnsigned(8); + outBytes[outOff + 7] = (x76 >> 8).toUnsigned(8); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rmd_signer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rmd_signer.dart new file mode 100644 index 000000000..20f9e0b0b --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rmd_signer.dart @@ -0,0 +1,88 @@ +part of pdf; + +class _RmdSigner implements _ISigner { + _RmdSigner(String digest) { + _digest = getDigest(digest); + _output = AccumulatorSink(); + _input = _digest.startChunkedConversion(_output); + _rsaEngine = _Pkcs1Encoding(_RsaAlgorithm()); + _id = _Algorithms(map![digest], _DerNull.value); + } + Map? _map; + late _ICipherBlock _rsaEngine; + _Algorithms? _id; + late dynamic _digest; + late dynamic _output; + dynamic _input; + late bool _isSigning; + //Properties + Map? get map { + if (_map == null) { + _map = {}; + _map![_DigestAlgorithms.sha1] = _X509Objects.idSha1; + _map![_DigestAlgorithms.sha256] = _NistObjectIds.sha256; + _map![_DigestAlgorithms.sha384] = _NistObjectIds.sha384; + _map![_DigestAlgorithms.sha512] = _NistObjectIds.sha512; + } + return _map; + } + + dynamic getDigest(String digest) { + dynamic result; + if (digest == _DigestAlgorithms.sha1) { + result = sha1; + } else if (digest == _DigestAlgorithms.sha256) { + result = sha256; + } else if (digest == _DigestAlgorithms.sha384) { + result = sha384; + } else if (digest == _DigestAlgorithms.sha512) { + result = sha512; + } else { + throw ArgumentError.value(digest, 'digest', 'Invalid digest'); + } + return result; + } + + @override + void initialize(bool isSigning, _ICipherParameter? parameters) { + _isSigning = isSigning; + final _CipherParameter? k = parameters as _CipherParameter?; + if (isSigning && !k!.isPrivate!) { + throw ArgumentError.value('Private key required.'); + } + if (!isSigning && k!.isPrivate!) { + throw ArgumentError.value('Public key required.'); + } + reset(); + _rsaEngine.initialize(isSigning, parameters); + } + + @override + void blockUpdate(List input, int inOff, int length) { + _input.add(input.sublist(inOff, inOff + length)); + } + + @override + List? generateSignature() { + if (!_isSigning) { + throw ArgumentError.value('Invalid entry'); + } + _input.close(); + final List? hash = _output.events.single.bytes; + final List data = derEncode(hash)!; + return _rsaEngine.processBlock(data, 0, data.length); + } + + List? derEncode(List? hash) { + if (_id == null) { + return hash; + } + return _DigestInformation(_id, hash).getDerEncoded(); + } + + @override + void reset() { + _output = AccumulatorSink(); + _input = _digest.startChunkedConversion(_output); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rsa_algorithm.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rsa_algorithm.dart new file mode 100644 index 000000000..6d6cc9796 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rsa_algorithm.dart @@ -0,0 +1,153 @@ +part of pdf; + +class _RsaAlgorithm implements _ICipherBlock { + _RsaAlgorithm() { + _rsaCoreEngine = _RsaCoreAlgorithm(); + } + + //Fields + late _RsaCoreAlgorithm _rsaCoreEngine; + _RsaKeyParam? _key; + Random? _random; + + //Properties + String get algorithmName => _Asn1Constants.rsa; + int get inputBlock => _rsaCoreEngine.inputBlockSize; + int get outputBlock => _rsaCoreEngine.outputBlockSize; + //Implementation + void initialize(bool isEncryption, _ICipherParameter? parameter) { + _rsaCoreEngine.initialize(isEncryption, parameter); + _key = parameter as _RsaKeyParam?; + _random = Random.secure(); + } + + //Implementation + List processBlock(List bytes, int offset, int length) { + ArgumentError.checkNotNull(_key); + final BigInt input = _rsaCoreEngine.convertInput(bytes, offset, length); + BigInt result; + if (_key is _RsaPrivateKeyParam) { + final BigInt? e = (_key as _RsaPrivateKeyParam)._publicExponent; + if (e != null) { + final BigInt m = _key!.modulus!; + final BigInt r = + createRandomInRange(BigInt.one, (m - BigInt.one), _random); + final BigInt blindedInput = _getMod((r.modPow(e, m) * input), m); + final BigInt blindedResult = _rsaCoreEngine.processBlock(blindedInput); + final BigInt reverse = r.modInverse(m); + result = _getMod((blindedResult * reverse), m); + } else { + result = _rsaCoreEngine.processBlock(input); + } + } else { + result = _rsaCoreEngine.processBlock(input); + } + return _rsaCoreEngine.convertOutput(result); + } + + BigInt createRandomInRange(BigInt min, BigInt max, Random? random) { + final int cmp = min.compareTo(max); + if (cmp >= 0) { + if (cmp > 0) { + throw ArgumentError.value('Invalid range'); + } + return min; + } + if (min.bitLength > max.bitLength / 2) { + return createRandomInRange(BigInt.zero, max - min, random) + min; + } + for (int i = 0; i < 1000; ++i) { + final BigInt x = _bigIntFromRamdom(max.bitLength, random); + if (x.compareTo(min) >= 0 && x.compareTo(max) <= 0) { + return x; + } + } + return _bigIntFromRamdom((max - min).bitLength - 1, random) + min; + } +} + +class _RsaCoreAlgorithm { + _RsaCoreAlgorithm() {} + + //Fields + late _RsaKeyParam _key; + late bool _isEncryption; + late int _bitSize; + + //Properties + int get inputBlockSize { + if (_isEncryption) { + return (_bitSize - 1) ~/ 8; + } + return (_bitSize + 7) ~/ 8; + } + + int get outputBlockSize { + if (_isEncryption) { + return (_bitSize + 7) ~/ 8; + } + return (_bitSize - 1) ~/ 8; + } + + //Implementation + void initialize(bool isEncryption, _ICipherParameter? parameters) { + if (!(parameters is _RsaKeyParam)) { + throw ArgumentError.value(parameters, 'parameters', 'Invalid RSA key'); + } + _key = parameters; + _isEncryption = isEncryption; + _bitSize = _key.modulus!.bitLength; + } + + BigInt convertInput(List bytes, int offset, int length) { + final int maxLength = (_bitSize + 7) ~/ 8; + if (length > maxLength) { + throw ArgumentError.value(length, 'length', 'Invalid length in inputs'); + } + final BigInt input = + _bigIntFromBytes(bytes.sublist(offset, offset + length), 1); + if (input.compareTo(_key.modulus!) >= 0) { + throw ArgumentError.value(length, 'length', 'Invalid length in inputs'); + } + return input; + } + + List convertOutput(BigInt result) { + List output = _bigIntToBytes(result, false); + if (_isEncryption) { + final int outSize = outputBlockSize; + if (output.length < outSize) { + final List bytes = List.generate(outSize, (i) => 0); + int j = 0; + for (int i = (bytes.length - output.length); + j < output.length && i < bytes.length; + i++) { + bytes[i] = output[j]; + j += 1; + } + output = bytes; + } + } + return output; + } + + BigInt processBlock(BigInt input) { + if (_key is _RsaPrivateKeyParam) { + final _RsaPrivateKeyParam privateKey = _key as _RsaPrivateKeyParam; + final BigInt p = privateKey._p!; + final BigInt q = privateKey._q!; + final BigInt dP = privateKey._dP!; + final BigInt dQ = privateKey._dQ!; + final BigInt qInv = privateKey._inverse!; + final BigInt mP = (input.remainder(p)).modPow(dP, p); + final BigInt mQ = (input.remainder(q)).modPow(dQ, q); + BigInt h = mP - mQ; + h = h * qInv; + h = _getMod(h, p); + BigInt m = h * q; + m = m + mQ; + return m; + } + return input.modPow(_key._exponent!, _key._modulus!); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/signature_utilities.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/signature_utilities.dart new file mode 100644 index 000000000..3e03ce071 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/signature_utilities.dart @@ -0,0 +1,102 @@ +part of pdf; + +BigInt _bigIntFromBytes(List? data, [int? sign]) { + BigInt result; + if (sign == null) { + final bool isNegative = data!.isNotEmpty && data[0] & 0x80 == 0x80; + if (data.length == 1) { + result = BigInt.from(data[0]); + } else { + result = BigInt.zero; + for (int i = 0; i < data.length; i++) { + final int item = data[data.length - i - 1]; + result |= (BigInt.from(item) << (8 * i)); + } + } + return result != BigInt.zero + ? isNegative + ? result.toSigned(result.bitLength) + : result + : BigInt.zero; + } else { + if (sign == 0) { + return BigInt.zero; + } + if (data!.length == 1) { + result = BigInt.from(data[0]); + } else { + result = BigInt.from(0); + for (int i = 0; i < data.length; i++) { + final int item = data[data.length - i - 1]; + result |= (BigInt.from(item) << (8 * i)); + } + } + if (result != BigInt.zero) { + if (sign < 0) { + result = result.toSigned(result.bitLength); + } else { + result = result.toUnsigned(result.bitLength); + } + } + } + return result; +} + +List _bigIntToBytes(BigInt number, [bool isSigned = true]) { + List result; + final BigInt mask = BigInt.from(0xff); + final BigInt flag = BigInt.from(0x80); + if (isSigned) { + if (number == BigInt.zero) { + return [0]; + } + int paddingBytes; + int size; + if (number > BigInt.zero) { + size = (number.bitLength + 7) >> 3; + paddingBytes = ((number >> (size - 1) * 8) & flag) == flag ? 1 : 0; + } else { + paddingBytes = 0; + size = (number.bitLength + 8) >> 3; + } + final int length = size + paddingBytes; + result = List.generate(length, (i) => 0); + for (int i = 0; i < size; i++) { + result[length - i - 1] = (number & mask).toSigned(32).toInt(); + number = number >> 8; + } + } else { + if (number == BigInt.zero) { + return [0]; + } + final int length = number.bitLength + (number.isNegative ? 8 : 7) >> 3; + result = List.generate(length, (i) => 0); + for (int i = 0; i < length; i++) { + result[length - i - 1] = (number & mask).toSigned(32).toInt(); + number = number >> 8; + } + } + return result; +} + +BigInt _getMod(BigInt n, BigInt m) { + final BigInt biggie = n.remainder(m); + return (biggie.sign >= 0 ? biggie : biggie + m); +} + +BigInt _bigIntFromRamdom(int value, Random? random) { + BigInt result; + if (value < 0) { + throw ArgumentError.value(value, 'value', 'Invalid entry'); + } + if (value == 0) { + result = BigInt.from(0); + } else { + final int nBytes = (value + 8 - 1) ~/ 8; + final List b = List.generate(nBytes, (i) => random!.nextInt(256)); + final int xBits = 8 * nBytes - value; + b[0] &= (255 >> xBits).toUnsigned(8); + result = _bigIntFromBytes(b); + } + return result; +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_certificate.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_certificate.dart new file mode 100644 index 000000000..25af2e89b --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_certificate.dart @@ -0,0 +1,136 @@ +part of pdf; + +/// Represents the Certificate object. +class PdfCertificate { + /// Initializes a new instance of the [PdfCertificate] class + PdfCertificate(List certificateBytes, String password) { + _initializeCertificate(certificateBytes, password); + } + + //Fields + late _PdfPKCSCertificate _pkcsCertificate; + int _version = 0; + late List _serialNumber; + String _issuerName = ''; + String _subjectName = ''; + DateTime? _validFrom; + DateTime? _validTo; + late Map> _distinguishedNameCollection; + + //Properties + /// Gets the certificate's version number. + int get version => _version; + + /// Gets the serial number of a certificate. + List get serialNumber => _serialNumber; + + /// Gets the certificate issuer's name. + String get issuerName => _issuerName; + + /// Gets the certificate subject's name. + String get subjectName => _subjectName; + + /// Gets the date and time before which the certificate is not valid. + DateTime get validTo => _validTo!; + + /// Gets the date and time after which the certificate is not valid. + DateTime get validFrom => _validFrom!; + + //Implementation + void _initializeCertificate(List certificateBytes, String password) { + _distinguishedNameCollection = >{}; + _pkcsCertificate = _PdfPKCSCertificate(certificateBytes, password); + String certificateAlias = ''; + final List keys = _pkcsCertificate.getContentTable().keys.toList(); + bool isContinue = true; + keys.forEach((key) { + if (isContinue && + _pkcsCertificate.isKey(key) && + _pkcsCertificate.getKey(key)!._key!.isPrivate!) { + certificateAlias = key; + isContinue = false; + } + }); + final _X509Certificates entry = + _pkcsCertificate.getCertificate(certificateAlias)!; + _loadDetails(entry.certificate!); + } + + void _loadDetails(_X509Certificate certificate) { + _issuerName = + _getDistinguishedAttributes(certificate._c!.issuer.toString(), 'CN'); + _subjectName = + _getDistinguishedAttributes(certificate._c!.subject.toString(), 'CN'); + _validFrom = certificate._c!.startDate!.toDateTime(); + _validTo = certificate._c!.endDate!.toDateTime(); + _version = certificate._c!.version; + final List serialNumber = [] + ..addAll(certificate._c!.serialNumber!._value!.reversed.toList()); + _serialNumber = serialNumber; + } + + String _getDistinguishedAttributes(String name, String key) { + String x509Name = ''; + Map attributesDictionary = {}; + if (key.contains('=')) { + key = key.replaceAll('=', ''); + } + if (!_distinguishedNameCollection.containsKey(name)) { + String result = ''; + bool isInitialSeparator = true; + for (int i = 0; i < name.length; i++) { + if (isInitialSeparator) { + if (name[i] == ',' || name[i] == ';' || name[i] == '+') { + _addStringToDictionary(result, attributesDictionary); + result = ''; + } else { + result += name[i]; + if (name[i] == '\\') { + result += name[++i]; + } else if (name[i] == '"') { + isInitialSeparator = false; + } + } + } else { + result += name[i]; + if (name[i] == '\\') { + result += name[++i]; + } else if (name[i] == '"') { + isInitialSeparator = true; + } + } + } + _addStringToDictionary(result, attributesDictionary); + result = ''; + _distinguishedNameCollection[name] = attributesDictionary; + if (attributesDictionary.containsKey(key)) { + x509Name = attributesDictionary[key]!; + } + } else { + attributesDictionary = _distinguishedNameCollection[name]!; + if (attributesDictionary.containsKey(key)) { + x509Name = attributesDictionary[key]!; + } + } + return x509Name; + } + + void _addStringToDictionary(String name, Map? dictionary) { + int index = name.indexOf('='); + if (index > 0) { + final List keyNameArray = List.generate(2, (i) => null); + keyNameArray[0] = name.substring(0, index).trimLeft().trimRight(); + index++; + keyNameArray[1] = + name.substring(index, name.length).trimLeft().trimRight(); + if (keyNameArray[0] != null && + keyNameArray[0] != '' && + keyNameArray[1] != null && + keyNameArray[1] != '') { + if (!dictionary!.containsKey(keyNameArray[0])) { + dictionary[keyNameArray[0]] = keyNameArray[1]; + } + } + } + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_external_signer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_external_signer.dart new file mode 100644 index 000000000..64dd39cec --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_external_signer.dart @@ -0,0 +1,28 @@ +part of pdf; + +/// Interface for external signing to a PDF document +class IPdfExternalSigner { + //Fields + DigestAlgorithm _hashAlgorithm = DigestAlgorithm.sha256; + + //Properties + /// Get HashAlgorithm. + DigestAlgorithm get hashAlgorithm => _hashAlgorithm; + + //Public methods + /// Returns Signed Message Digest. + SignerResult? sign(List message) { + return null; + } +} + +/// External signing result +class SignerResult { + /// Initializes a new instance of the [SignerResult] class with signed data. + SignerResult(List signedData) { + this.signedData = signedData; + } + + /// Gets and sets the signed Message Digest. + late List signedData; +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_pkcs_certificate.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_pkcs_certificate.dart new file mode 100644 index 000000000..c3932e2f9 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_pkcs_certificate.dart @@ -0,0 +1,586 @@ +part of pdf; + +class _PdfPKCSCertificate { + _PdfPKCSCertificate(List certificateBytes, String password) { + _keys = _CertificateTable(); + _certificates = _CertificateTable(); + _localIdentifiers = {}; + _chainCertificates = <_CertificateIdentifier, _X509Certificates>{}; + _keyCertificates = {}; + _loadCertificate(certificateBytes, password); + } + + //Fields + late _CertificateTable _keys; + late _CertificateTable _certificates; + late Map _localIdentifiers; + late Map<_CertificateIdentifier, _X509Certificates> _chainCertificates; + late Map _keyCertificates; + + //Implementation + void _loadCertificate(List certificateBytes, String password) { + final _Asn1Sequence sequence = _Asn1Stream(_StreamReader(certificateBytes)) + .readAsn1() as _Asn1Sequence; + final _PfxData pfxData = _PfxData(sequence); + final _ContentInformation information = pfxData._contentInformation!; + bool isUnmarkedKey = false; + final bool isInvalidPassword = password.length == 0 ? true : false; + final List<_Asn1SequenceCollection> certificateChain = + <_Asn1SequenceCollection>[]; + if (information._contentType!._id == _PkcsObjectId.data._id) { + final List? octs = (information._content as _Asn1Octet).getOctets(); + final _Asn1Sequence asn1Sequence = + (_Asn1Stream(_StreamReader(octs)).readAsn1()) as _Asn1Sequence; + final List<_ContentInformation?> contentInformation = + <_ContentInformation?>[]; + for (int i = 0; i < asn1Sequence.count; i++) { + contentInformation + .add(_ContentInformation.getInformation(asn1Sequence[i])); + } + contentInformation.forEach((entry) { + final _DerObjectID type = entry!._contentType!; + if (type._id == _PkcsObjectId.data._id) { + final List? octets = (entry._content as _Asn1Octet).getOctets(); + final _Asn1Sequence asn1SubSequence = + (_Asn1Stream(_StreamReader(octets)).readAsn1()) as _Asn1Sequence; + for (int index = 0; index < asn1SubSequence.count; index++) { + final dynamic subSequence = asn1SubSequence[index]; + if (subSequence != null && subSequence is _Asn1Sequence) { + final _Asn1SequenceCollection subSequenceCollection = + _Asn1SequenceCollection(subSequence); + if (subSequenceCollection._id!._id == + _PkcsObjectId.pkcs8ShroudedKeyBag._id) { + final _EncryptedPrivateKey encryptedInformation = + _EncryptedPrivateKey.getEncryptedPrivateKeyInformation( + subSequenceCollection._value); + final _KeyInformation privateKeyInformation = + createPrivateKeyInfo( + password, isInvalidPassword, encryptedInformation)!; + _RsaPrivateKeyParam? rsaparam; + if (privateKeyInformation._algorithms!._objectID!._id == + _PkcsObjectId.rsaEncryption._id || + privateKeyInformation._algorithms!._objectID!._id == + _X509Objects.idEARsa._id) { + final _RsaKey keyStructure = _RsaKey.fromSequence( + _Asn1Sequence.getSequence( + privateKeyInformation._privateKey)!); + rsaparam = _RsaPrivateKeyParam( + keyStructure._modulus, + keyStructure._publicExponent!, + keyStructure._privateExponent, + keyStructure._prime1!, + keyStructure._prime2!, + keyStructure._exponent1!, + keyStructure._exponent2!, + keyStructure._coefficient!); + } + final _CipherParameter? privateKey = rsaparam; + final Map attributes = {}; + final _KeyEntry key = _KeyEntry(privateKey); + String? localIdentifier; + _Asn1Octet? localId; + if (subSequenceCollection._attributes != null) { + final _Asn1Set sq = subSequenceCollection._attributes!; + for (int i = 0; i < sq._objects.length; i++) { + final _Asn1Encode? entry = sq._objects[i]; + if (entry is _Asn1Sequence) { + final _DerObjectID? algorithmId = + _DerObjectID.getID(entry[0]); + final _Asn1Set attributeSet = entry[1] as _Asn1Set; + _Asn1Encode? attribute; + if (attributeSet._objects.length > 0) { + attribute = attributeSet[0]; + if (attributes.containsKey(algorithmId!._id)) { + if (attributes[algorithmId._id] != attribute) { + throw ArgumentError.value(attributes, 'attributes', + 'attempt to add existing attribute with different value'); + } + } else { + attributes[algorithmId._id] = attribute; + } + if (algorithmId._id == + _PkcsObjectId.pkcs9AtFriendlyName._id) { + localIdentifier = + (attribute as _DerBmpString).getString(); + _keys.setValue(localIdentifier!, key); + } else if (algorithmId._id == + _PkcsObjectId.pkcs9AtLocalKeyID._id) { + localId = attribute as _Asn1Octet?; + } + } + } + } + } + if (localId != null) { + final String name = + _PdfString._bytesToHex(localId.getOctets()!); + if (localIdentifier == null) { + _keys.setValue(name, key); + } else { + _localIdentifiers[localIdentifier] = name; + } + } else { + isUnmarkedKey = true; + _keys.setValue('unmarked', key); + } + } else if (subSequenceCollection._id!._id == + _PkcsObjectId.certBag._id) { + certificateChain.add(subSequenceCollection); + } + } + } + } else if (type._id == _PkcsObjectId.encryptedData._id) { + final _Asn1Sequence sequence1 = entry._content as _Asn1Sequence; + if (sequence1.count != 2) { + throw ArgumentError.value( + entry, 'sequence', 'Invalid length of the sequence'); + } + final int version = + (sequence1[0] as _DerInteger).value.toSigned(32).toInt(); + if (version != 0) { + throw ArgumentError.value( + version, 'version', 'Invalid sequence version'); + } + final _Asn1Sequence data = sequence1[1] as _Asn1Sequence; + _Asn1Octet? content; + if (data.count == 3) { + final _DerTag taggedObject = data[2] as _DerTag; + content = _Asn1Octet.getOctetString(taggedObject, false); + } + final List? octets = getCryptographicData( + false, + _Algorithms.getAlgorithms(data[1])!, + password, + isInvalidPassword, + content!.getOctets()); + final _Asn1Sequence seq = + _Asn1Stream(_StreamReader(octets)).readAsn1() as _Asn1Sequence; + seq._objects!.forEach((subSequence) { + final _Asn1SequenceCollection subSequenceCollection = + _Asn1SequenceCollection(subSequence); + if (subSequenceCollection._id!._id == _PkcsObjectId.certBag._id) { + certificateChain.add(subSequenceCollection); + } else if (subSequenceCollection._id!._id == + _PkcsObjectId.pkcs8ShroudedKeyBag._id) { + final _EncryptedPrivateKey encryptedPrivateKeyInformation = + _EncryptedPrivateKey.getEncryptedPrivateKeyInformation( + subSequenceCollection._value); + final _KeyInformation privateInformation = createPrivateKeyInfo( + password, isInvalidPassword, encryptedPrivateKeyInformation)!; + _RsaPrivateKeyParam? rsaParameter; + if (privateInformation._algorithms!._objectID!._id == + _PkcsObjectId.rsaEncryption._id || + privateInformation._algorithms!._objectID!._id == + _X509Objects.idEARsa._id) { + final _RsaKey keyStructure = _RsaKey.fromSequence( + _Asn1Sequence.getSequence(privateInformation._privateKey)!); + rsaParameter = _RsaPrivateKeyParam( + keyStructure._modulus, + keyStructure._publicExponent!, + keyStructure._privateExponent, + keyStructure._prime1!, + keyStructure._prime2!, + keyStructure._exponent1!, + keyStructure._exponent2!, + keyStructure._coefficient!); + } + final _CipherParameter? privateKey = rsaParameter; + final Map attributes = {}; + final _KeyEntry keyEntry = _KeyEntry(privateKey); + String? key; + _Asn1Octet? localIdentity; + subSequenceCollection._attributes!._objects.forEach((sq) { + final _DerObjectID? asn1Id = sq[0] as _DerObjectID?; + final _Asn1Set attributeSet = sq[1] as _Asn1Set; + _Asn1Encode? attribute; + if (attributeSet._objects.length > 0) { + attribute = attributeSet._objects[0]; + if (attributes.containsKey(asn1Id!._id)) { + if (!(attributes[asn1Id._id] == attribute)) { + throw ArgumentError.value(attributes, 'attributes', + 'attempt to add existing attribute with different value'); + } + } else { + attributes[asn1Id._id] = attribute; + } + if (asn1Id._id == _PkcsObjectId.pkcs9AtFriendlyName._id) { + key = (attribute as _DerBmpString).getString(); + _keys.setValue(key!, keyEntry); + } else if (asn1Id._id == + _PkcsObjectId.pkcs9AtLocalKeyID._id) { + localIdentity = attribute as _Asn1Octet?; + } + } + }); + final String name = + _PdfString._bytesToHex(localIdentity!.getOctets()!); + if (key == null) { + _keys.setValue(name, keyEntry); + } else { + _localIdentifiers[key] = name; + } + } else if (subSequenceCollection._id!._id == + _PkcsObjectId.keyBag._id) { + final _KeyInformation privateKeyInformation = + _KeyInformation.getInformation(subSequenceCollection._value)!; + _RsaPrivateKeyParam? rsaParameter; + if (privateKeyInformation._algorithms!._objectID!._id == + _PkcsObjectId.rsaEncryption._id || + privateKeyInformation._algorithms!._objectID!._id == + _X509Objects.idEARsa._id) { + final _RsaKey keyStructure = _RsaKey.fromSequence( + _Asn1Sequence.getSequence( + privateKeyInformation._privateKey)!); + rsaParameter = _RsaPrivateKeyParam( + keyStructure._modulus, + keyStructure._publicExponent!, + keyStructure._privateExponent, + keyStructure._prime1!, + keyStructure._prime2!, + keyStructure._exponent1!, + keyStructure._exponent2!, + keyStructure._coefficient!); + } + final _CipherParameter? privateKey = rsaParameter; + String? key; + _Asn1Octet? localId; + final Map attributes = {}; + final _KeyEntry keyEntry = _KeyEntry(privateKey); + subSequenceCollection._attributes!._objects.forEach((sq) { + final _DerObjectID? id = sq[0] as _DerObjectID?; + final _Asn1Set attributeSet = sq[1] as _Asn1Set; + _Asn1Encode? attribute; + if (attributeSet._objects.length > 0) { + attribute = attributeSet[0]; + if (attributes.containsKey(id!._id)) { + if (!attributes[id._id] == attribute) { + throw ArgumentError.value(sq, 'sequence', + 'attempt to add existing attribute with different value'); + } + } else { + attributes[id._id] = attribute; + } + if (id._id == _PkcsObjectId.pkcs9AtFriendlyName._id) { + key = (attribute as _DerBmpString).getString(); + _keys.setValue(key!, keyEntry); + } else if (id._id == _PkcsObjectId.pkcs9AtLocalKeyID._id) { + localId = attribute as _Asn1Octet?; + } + } + }); + final String name = _PdfString._bytesToHex(localId!.getOctets()!); + if (key == null) { + _keys.setValue(name, keyEntry); + } else { + _localIdentifiers[key] = name; + } + } + }); + } + }); + } + _certificates = _CertificateTable(); + _chainCertificates = <_CertificateIdentifier, _X509Certificates>{}; + _keyCertificates = {}; + certificateChain.forEach((asn1Collection) { + final _Asn1Sequence asn1Sequence = asn1Collection._value as _Asn1Sequence; + final _Asn1 certValue = _Asn1Tag.getTag(asn1Sequence[1])!.getObject()!; + final List? octets = (certValue as _Asn1Octet).getOctets(); + final _X509Certificate certificate = + _X509CertificateParser().readCertificate(_StreamReader(octets))!; + final Map attributes = {}; + _Asn1Octet? localId; + String? key; + final _Asn1Set? tempAttributes = asn1Collection._attributes; + if (tempAttributes != null) { + for (int i = 0; i < tempAttributes._objects.length; i++) { + final _Asn1Sequence sq = tempAttributes._objects[i]; + final _DerObjectID? aOid = _DerObjectID.getID(sq[0]); + final _Asn1Set attrSet = sq[1] as _Asn1Set; + if (attrSet._objects.length > 0) { + final _Asn1Encode? attr = attrSet[0]; + if (attributes.containsKey(aOid!._id)) { + if (attributes[aOid._id] != attr) { + throw ArgumentError.value(attributes, 'attributes', + 'attempt to add existing attribute with different value'); + } + } else { + attributes[aOid._id] = attr; + } + if (aOid._id == _PkcsObjectId.pkcs9AtFriendlyName._id) { + key = (attr as _DerBmpString).getString(); + } else if (aOid._id == _PkcsObjectId.pkcs9AtLocalKeyID._id) { + localId = attr as _Asn1Octet?; + } + } + } + } + final _CertificateIdentifier certId = + _CertificateIdentifier(pubKey: certificate.getPublicKey()); + final _X509Certificates certificateCollection = + _X509Certificates(certificate); + _chainCertificates[certId] = certificateCollection; + if (isUnmarkedKey) { + if (_keyCertificates.isEmpty) { + final String name = _PdfString._bytesToHex(certId._id!); + _keyCertificates[name] = certificateCollection; + final dynamic temp = _keys['unmarked']; + _keys.remove('unmarked'); + _keys.setValue('name', temp); + } + } else { + if (localId != null) { + final String name = _PdfString._bytesToHex(localId.getOctets()!); + _keyCertificates[name] = certificateCollection; + } + if (key != null) { + _certificates.setValue(key, certificateCollection); + } + } + }); + } + + static List? getCryptographicData(bool forEncryption, _Algorithms id, + String password, bool isZero, List? data) { + final _PasswordUtility utility = _PasswordUtility(); + final _IBufferedCipher? cipher = + utility.createEncoder(id._objectID) as _IBufferedCipher?; + if (cipher == null) { + throw ArgumentError.value(id, 'id', 'Invalid encryption algorithm'); + } + final _Pkcs12PasswordParameter parameter = + _Pkcs12PasswordParameter.getPbeParameter(id._parameters); + final _ICipherParameter? parameters = utility.generateCipherParameters( + id._objectID!._id!, password, isZero, parameter); + cipher.initialize(forEncryption, parameters); + return cipher.doFinalFromInput(data); + } + + _KeyInformation? createPrivateKeyInfo( + String passPhrase, bool isPkcs12empty, _EncryptedPrivateKey encInfo) { + final _Algorithms algID = encInfo._algorithms!; + final _PasswordUtility pbeU = _PasswordUtility(); + final _IBufferedCipher? cipher = + pbeU.createEncoder(algID) as _IBufferedCipher?; + if (cipher == null) { + throw ArgumentError.value( + cipher, 'cipher', 'Unknown encryption algorithm'); + } + final _ICipherParameter? cipherParameters = pbeU.generateCipherParameters( + algID._objectID!._id!, passPhrase, isPkcs12empty, algID._parameters); + cipher.initialize(false, cipherParameters); + final List? keyBytes = + cipher.doFinalFromInput(encInfo._octet!.getOctets()); + return _KeyInformation.getInformation(keyBytes); + } + + static _SubjectKeyID createSubjectKeyID(_CipherParameter publicKey) { + _SubjectKeyID result; + if (publicKey is _RsaKeyParam) { + final _PublicKeyInformation information = _PublicKeyInformation( + _Algorithms(_PkcsObjectId.rsaEncryption, _DerNull.value), + _RsaPublicKey(publicKey.modulus, publicKey.exponent).getAsn1()); + result = _SubjectKeyID(information); + } else { + throw ArgumentError.value(publicKey, 'publicKey', 'Invalid Key'); + } + return result; + } + + Map getContentTable() { + final Map result = {}; + _certificates.keys.forEach((key) { + result[key] = 'cert'; + }); + _keys.keys.forEach((key) { + if (!result.containsKey(key)) { + result[key] = 'key'; + } + }); + return result; + } + + bool isKey(String key) { + return _keys[key] != null; + } + + _KeyEntry? getKey(String key) { + return _keys[key] is _KeyEntry ? _keys[key] : null; + } + + _X509Certificates? getCertificate(String key) { + dynamic certificates = _certificates[key]; + if (certificates != null && certificates is _X509Certificates) { + return certificates; + } else { + String? id; + if (_localIdentifiers.containsKey(key)) { + id = _localIdentifiers[key]; + } + if (id != null) { + if (_keyCertificates.containsKey(id)) { + certificates = _keyCertificates[id] is _X509Certificates + ? _keyCertificates[id] + : null; + } + } else { + if (_keyCertificates.containsKey(key)) { + certificates = _keyCertificates[key] is _X509Certificates + ? _keyCertificates[key] + : null; + } + } + return certificates; + } + } + + List<_X509Certificates>? getCertificateChain(String key) { + if (!isKey(key)) { + return null; + } + _X509Certificates? certificates = getCertificate(key); + if (certificates != null) { + final List<_X509Certificates> certificateList = <_X509Certificates>[]; + bool isContinue = true; + while (certificates != null) { + final _X509Certificate x509Certificate = certificates.certificate!; + _X509Certificates? nextCertificate; + final _Asn1Octet? x509Extension = x509Certificate + .getExtension(_X509Extensions.authorityKeyIdentifier); + if (x509Extension != null) { + final _KeyIdentifier id = _KeyIdentifier.getKeyIdentifier( + _Asn1Stream(_StreamReader(x509Extension.getOctets())).readAsn1()); + if (id.keyID != null) { + if (_chainCertificates + .containsKey(_CertificateIdentifier(id: id.keyID))) { + nextCertificate = + _chainCertificates[_CertificateIdentifier(id: id.keyID)]; + } + } + } + if (nextCertificate == null) { + final _X509Name? issuer = x509Certificate._c!.issuer; + final _X509Name? subject = x509Certificate._c!.subject; + if (!(issuer == subject)) { + final List<_CertificateIdentifier> keys = + _chainCertificates.keys.toList(); + keys.forEach((certId) { + _X509Certificates? x509CertEntry; + if (_chainCertificates.containsKey(certId)) { + x509CertEntry = _chainCertificates[certId]; + } + final _X509Certificate certificate = x509CertEntry!.certificate!; + if (certificate._c!.subject == issuer) { + try { + // x509Certificate.verify(certificate.getPublicKey()); + // nextCertificate = x509CertEntry; + isContinue = false; + } catch (e) {} + } + }); + } + } + if (isContinue) { + certificateList.add(certificates); + certificates = + nextCertificate != null && nextCertificate != certificates + ? nextCertificate + : null; + } + } + return List<_X509Certificates>.generate( + certificateList.length, (i) => certificateList[i]); + } + return null; + } +} + +class _CertificateTable { + _CertificateTable() { + _orig = {}; + _keys = {}; + } + late Map _orig; + late Map _keys; + + //Implementation + void clear() { + _orig = {}; + _keys = {}; + } + + List get keys { + return _orig.keys.toList(); + } + + dynamic remove(String key) { + final String lower = key.toLowerCase(); + String? k; + _keys.forEach((String tempKey, dynamic tempValue) { + if (lower == tempKey.toLowerCase()) { + k = _keys[tempKey]; + } + }); + if (k == null) { + return null; + } + _keys.remove(lower); + final dynamic obj = _orig[k!]; + _orig.remove(k); + return obj; + } + + dynamic operator [](String key) { + final String lower = key.toLowerCase(); + String? k; + _keys.forEach((String tempKey, dynamic tempValue) { + if (lower == tempKey.toLowerCase()) { + k = tempKey; + } + }); + if (k != null) { + return _orig[_keys[k!]]; + } else { + return null; + } + } + + void setValue(String key, dynamic value) { + final String lower = key.toLowerCase(); + String? k; + _keys.forEach((String tempKey, dynamic tempValue) { + if (lower == tempKey.toLowerCase()) { + k = tempKey; + } + }); + if (k != null) { + _orig.remove(_keys[k!]); + } + _keys[lower] = key; + _orig[key] = value; + } +} + +class _CertificateIdentifier { + _CertificateIdentifier({_CipherParameter? pubKey, List? id}) { + if (pubKey != null) { + _id = _PdfPKCSCertificate.createSubjectKeyID(pubKey)._bytes; + } else { + _id = id; + } + } + //Fields + List? _id; + //Implements + @override + bool operator ==(Object other) { + if (other is _CertificateIdentifier) { + return _Asn1Constants.areEqual(_id, other._id); + } else { + return false; + } + } + + @override + int get hashCode => _Asn1Constants.getHashCode(_id); +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_signature.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_signature.dart new file mode 100644 index 000000000..7f79c3bce --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_signature.dart @@ -0,0 +1,209 @@ +part of pdf; + +/// Represents a digital signature used for signing a PDF document. +class PdfSignature { + //Constructor + /// Initializes a new instance of the [PdfSignature] class with the page and the signature name. + PdfSignature( + {String? signedName, + String? locationInfo, + String? reason, + String? contactInfo, + List? documentPermissions, + CryptographicStandard cryptographicStandard = CryptographicStandard.cms, + DigestAlgorithm digestAlgorithm = DigestAlgorithm.sha256, + PdfCertificate? certificate}) { + _init(signedName, locationInfo, reason, contactInfo, documentPermissions, + cryptographicStandard, digestAlgorithm, certificate); + } + + //Fields + PdfPage? _page; + PdfSignatureField? _field; + PdfDocument? _document; + bool _certificated = false; + _PdfSignatureDictionary? _signatureDictionary; + _PdfArray? _byteRange; + DateTime? _signedDate; + IPdfExternalSigner? _externalSigner; + List>? _externalRootCert; + List<_X509Certificate?>? _externalChain; + + //Properties + /// Gets or sets the permission for certificated document. + List documentPermissions = [ + PdfCertificationFlags.forbidChanges + ]; + + /// Gets or sets reason of signing. + String? reason; + + /// Gets or sets the physical location of the signing. + String? locationInfo; + + /// Gets or sets the signed name + String? signedName; + + /// Gets the signed date. + DateTime? get signedDate => _signedDate; + + /// Gets or sets cryptographic standard. + late CryptographicStandard cryptographicStandard; + + /// Gets or sets digestion algorithm. + late DigestAlgorithm digestAlgorithm; + + /// Gets or sets information provided by the signer to enable a recipient to contact + /// the signer to verify the signature; for example, a phone number. + String? contactInfo; + + /// Gets or sets the certificate + PdfCertificate? certificate; + + //Implementations + //To check annotation last elements have signature field + void _checkAnnotationElementsContainsSignature( + PdfPage page, String? signatureName) { + if (page._dictionary.containsKey(_DictionaryProperties.annots)) { + final _IPdfPrimitive? annotationElements = _PdfCrossTable._dereference( + page._dictionary[_DictionaryProperties.annots]); + _IPdfPrimitive? lastElement; + if (annotationElements != null && + annotationElements is _PdfArray && + annotationElements._elements.length > 0) { + lastElement = _PdfCrossTable._dereference( + annotationElements[annotationElements._elements.length - 1]); + } + if (lastElement != null && + lastElement is _PdfDictionary && + lastElement.containsKey(_DictionaryProperties.t)) { + final _IPdfPrimitive? name = + _PdfCrossTable._dereference(lastElement[_DictionaryProperties.t]); + String tempName = ''; + if (name != null && name is _PdfString) { + tempName = utf8.decode(name.data as List); + } + if (tempName == signatureName && + annotationElements != null && + annotationElements is _PdfArray && + annotationElements._elements.length > 0) { + annotationElements._elements + .removeAt(annotationElements._elements.length - 1); + } + } + } + } + + void _catalogBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { + if (_certificated) { + _IPdfPrimitive? permission = _PdfCrossTable._dereference( + _document!._catalog[_DictionaryProperties.perms]); + if (permission == null) { + permission = _PdfDictionary(); + (permission as _PdfDictionary)[_DictionaryProperties.docMDP] = + _PdfReferenceHolder(_signatureDictionary); + _document!._catalog[_DictionaryProperties.perms] = permission; + } else if (permission is _PdfDictionary && + !permission.containsKey(_DictionaryProperties.docMDP)) { + permission.setProperty(_DictionaryProperties.docMDP, + _PdfReferenceHolder(_signatureDictionary)); + } + } + } + + void _dictionaryBeginSave(Object sender, _SavePdfPrimitiveArgs? ars) { + if (_field != null) { + _field!._dictionary.encrypt = _document!.security._encryptor.encrypt; + _field!._dictionary + .setProperty(_DictionaryProperties.ap, _field!.appearance); + } + } + + void _init( + String? signedName, + String? locationInfo, + String? reason, + String? contactInfo, + List? documentPermissions, + CryptographicStandard cryptographicStandard, + DigestAlgorithm digestAlgorithm, + PdfCertificate? pdfCertificate) { + this.cryptographicStandard = cryptographicStandard; + this.digestAlgorithm = digestAlgorithm; + if (signedName != null) { + this.signedName = signedName; + } + if (locationInfo != null) { + this.locationInfo = locationInfo; + } + if (reason != null) { + this.reason = reason; + } + if (contactInfo != null) { + this.contactInfo = contactInfo; + } + if (documentPermissions != null && documentPermissions.isNotEmpty) { + this.documentPermissions = documentPermissions; + } + if (pdfCertificate != null) { + certificate = pdfCertificate; + } + } + + /// Add external signer for signature. + void addExternalSigner( + IPdfExternalSigner signer, List> publicCertificatesData) { + _externalSigner = signer; + _externalRootCert = publicCertificatesData; + if (_externalRootCert != null) { + final _X509CertificateParser parser = _X509CertificateParser(); + _externalChain = []; + _externalRootCert!.forEach((certRawData) => _externalChain! + .add(parser.readCertificate(_StreamReader(certRawData)))); + } + } + + List _getCertificateFlags(int value) { + final List result = []; + if (value & _getCertificateFlagValue(PdfCertificationFlags.forbidChanges)! > + 0) { + result.add(PdfCertificationFlags.forbidChanges); + } + if (value & _getCertificateFlagValue(PdfCertificationFlags.allowFormFill)! > + 0) { + result.add(PdfCertificationFlags.allowFormFill); + } + if (value & _getCertificateFlagValue(PdfCertificationFlags.allowComments)! > + 0) { + result.add(PdfCertificationFlags.allowComments); + } + return result; + } + + int? _getCertificateFlagValue(PdfCertificationFlags flag) { + int? result; + switch (flag) { + case PdfCertificationFlags.forbidChanges: + result = 1; + break; + case PdfCertificationFlags.allowFormFill: + result = 2; + break; + case PdfCertificationFlags.allowComments: + result = 3; + break; + } + return result; + } + + int _getCertificateFlagResult(List flags) { + int result = 0; + flags.forEach((flag) { + result |= _getCertificateFlagValue(flag)!; + }); + if (result == 0) { + result = 1; + } + return result; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_signature_dictionary.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_signature_dictionary.dart new file mode 100644 index 000000000..aa9119197 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_signature_dictionary.dart @@ -0,0 +1,525 @@ +part of pdf; + +/// Represents signature dictionary. +class _PdfSignatureDictionary implements _IPdfWrapper { + //Constructors + _PdfSignatureDictionary(PdfDocument doc, PdfSignature sig) { + _doc = doc; + _sig = sig; + doc._documentSavedList ??= []; + doc._documentSavedList!.add(_documentSaved); + _dictionary._beginSaveList ??= []; + _dictionary._beginSaveList!.add(_dictionaryBeginSave); + _cert = sig.certificate; + } + + _PdfSignatureDictionary._fromDictionary(PdfDocument doc, _PdfDictionary dic) { + _doc = doc; + _dictionary = dic; + doc._documentSavedList ??= []; + doc._documentSavedList!.add(_documentSaved); + _dictionary._beginSaveList ??= []; + _dictionary._beginSaveList!.add(_dictionaryBeginSave); + } + + //Fields + late PdfDocument _doc; + PdfSignature? _sig; + _PdfDictionary _dictionary = _PdfDictionary(); + final String _transParam = 'TransformParams'; + final String _docMdp = 'DocMDP'; + final String _cmsFilterType = 'adbe.pkcs7.detached'; + final String _cadasFilterType = 'ETSI.CAdES.detached'; + int? _firstRangeLength; + int? _secondRangeIndex; + int? _startPositionByteRange; + int _estimatedSize = 8192; + PdfCertificate? _cert; + late List _range; + List? _stream; + + //Implementations + void _dictionaryBeginSave(Object sender, _SavePdfPrimitiveArgs? args) { + final bool state = _doc.security._encryptor.encrypt; + _dictionary.encrypt = state; + if (_sig != null) { + _addRequiredItems(); + _addOptionalItems(); + } + _doc.security._encryptor.encrypt = false; + _addContents(args!.writer!); + _addRange(args.writer!); + if (_sig != null) { + if (_sig!._certificated) { + _addDigest(args.writer); + } + } + _doc.security._encryptor.encrypt = state; + } + + void _addRequiredItems() { + if (_sig!._certificated && _allowMDP()) { + _addReference(); + } + _addType(); + _addDate(); + _addFilter(); + _addSubFilter(); + } + + void _addOptionalItems() { + if (_sig != null) { + if (_sig!.reason != null) { + _dictionary.setProperty( + _DictionaryProperties.reason, _PdfString(_sig!.reason!)); + } + if (_sig!.locationInfo != null) { + _dictionary.setProperty( + _DictionaryProperties.location, _PdfString(_sig!.locationInfo!)); + } + if (_sig!.contactInfo != null) { + _dictionary.setProperty( + _DictionaryProperties.contactInfo, _PdfString(_sig!.contactInfo!)); + } + if (_sig!.signedName != null) { + _dictionary._setString(_DictionaryProperties.name, _sig!.signedName); + final _PdfDictionary dictionary = _PdfDictionary(); + final _PdfDictionary appDictionary = _PdfDictionary(); + dictionary._setName( + _PdfName(_DictionaryProperties.name), _sig!.signedName); + appDictionary.setProperty('App', _PdfReferenceHolder(dictionary)); + _dictionary.setProperty( + _PdfName('Prop_Build'), _PdfReferenceHolder(appDictionary)); + } + } + } + + bool _allowMDP() { + final _IPdfPrimitive? perms = + _PdfCrossTable._dereference(_doc._catalog[_DictionaryProperties.perms]); + if (perms != null && perms is _PdfDictionary) { + final _IPdfPrimitive? docMDP = + _PdfCrossTable._dereference(perms[_DictionaryProperties.docMDP]); + final _IPdfPrimitive dicSig = _dictionary; + return dicSig == docMDP; + } + return false; + } + + void _addReference() { + final _PdfDictionary trans = _PdfDictionary(); + final _PdfDictionary reference = _PdfDictionary(); + final _PdfArray array = _PdfArray(); + trans[_DictionaryProperties.v] = _PdfName('1.2'); + trans[_DictionaryProperties.p] = + _PdfNumber(_sig!._getCertificateFlagResult(_sig!.documentPermissions)); + trans[_DictionaryProperties.type] = _PdfName(_transParam); + reference[_DictionaryProperties.transformMethod] = _PdfName(_docMdp); + reference[_DictionaryProperties.type] = _PdfName('SigRef'); + reference[_transParam] = trans; + array._add(reference); + _dictionary.setProperty('Reference', array); + } + + void _addType() { + _dictionary._setName(_PdfName(_DictionaryProperties.type), 'Sig'); + } + + void _addDate() { + final DateTime dateTime = DateTime.now(); + final DateFormat dateFormat = DateFormat('yyyyMMddHHmmss'); + final int regionMinutes = dateTime.timeZoneOffset.inMinutes ~/ 11; + String offsetMinutes = regionMinutes.toString(); + if (regionMinutes >= 0 && regionMinutes <= 9) { + offsetMinutes = '0' + offsetMinutes; + } + final int regionHours = dateTime.timeZoneOffset.inHours; + String offsetHours = regionHours.toString(); + if (regionHours >= 0 && regionHours <= 9) { + offsetHours = '0' + offsetHours; + } + _dictionary.setProperty( + _DictionaryProperties.m, + _PdfString( + "D:${dateFormat.format(dateTime)}+$offsetHours'$offsetMinutes'")); + } + + void _addFilter() { + _dictionary._setName( + _PdfName(_DictionaryProperties.filter), 'Adobe.PPKLite'); + } + + void _addSubFilter() { + _dictionary._setName( + _PdfName(_DictionaryProperties.subFilter), + _sig!.cryptographicStandard == CryptographicStandard.cades + ? _cadasFilterType + : _cmsFilterType); + } + + void _addContents(_IPdfWriter writer) { + writer._write(_Operators.slash + + _DictionaryProperties.contents + + _Operators.whiteSpace); + _firstRangeLength = writer._position; + int length = _estimatedSize * 2; + if (_sig != null && _cert != null) { + length = _estimatedSize; + } + final List contents = + List.filled(length * 2 + 2, 0, growable: true); + writer._write(contents); + _secondRangeIndex = writer._position; + writer._write(_Operators.newLine); + } + + void _addRange(_IPdfWriter writer) { + writer._write(_Operators.slash + + _DictionaryProperties.byteRange + + _Operators.whiteSpace + + _PdfArray.startMark); + _startPositionByteRange = writer._position; + for (int i = 0; i < 32; i++) { + writer._write(_Operators.whiteSpace); + } + writer._write(_PdfArray.endMark + _Operators.newLine); + } + + void _addDigest(_IPdfWriter? writer) { + if (_allowMDP()) { + final _PdfDictionary? cat = writer!._document!._catalog; + writer._write(_PdfName(_DictionaryProperties.reference)); + writer._write(_PdfArray.startMark); + writer._write('<<'); + writer._write(_Operators.slash + _transParam); + _PdfDictionary trans = _PdfDictionary(); + trans[_DictionaryProperties.v] = _PdfName('1.2'); + trans[_DictionaryProperties.p] = _PdfNumber( + _sig!._getCertificateFlagResult(_sig!.documentPermissions)); + trans[_DictionaryProperties.type] = _PdfName(_transParam); + writer._write(trans); + writer._write(_PdfName(_DictionaryProperties.transformMethod)); + writer._write(_PdfName(_docMdp)); + writer._write(_PdfName(_DictionaryProperties.type)); + writer._write(_PdfName(_DictionaryProperties.sigRef)); + writer._write(_PdfName('DigestValue')); + int position = writer._position!; + // _docDigestPosition = position; + writer._write( + _PdfString.fromBytes(List.filled(16, 0, growable: true))); + _PdfArray digestLocation = _PdfArray(); + digestLocation._add(_PdfNumber(position)); + digestLocation._add(_PdfNumber(34)); + writer._write(_PdfName('DigestLocation')); + writer._write(digestLocation); + writer._write(_PdfName('DigestMethod')); + writer._write(_PdfName('MD5')); + writer._write(_PdfName(_DictionaryProperties.data)); + final _PdfReferenceHolder refh = _PdfReferenceHolder(cat); + writer._write(_Operators.whiteSpace); + writer._write(refh); + writer._write('>>'); + writer._write('<<'); + writer._write(_PdfName(_transParam)); + trans = _PdfDictionary(); + trans[_DictionaryProperties.v] = _PdfName('1.2'); + final _PdfArray fields = _PdfArray(); + fields._add(_PdfString(_sig!._field!.name!)); + trans[_DictionaryProperties.fields] = fields; + trans[_DictionaryProperties.type] = _PdfName(_transParam); + trans[_DictionaryProperties.action] = _PdfName('Include'); + writer._write(trans); + writer._write(_PdfName(_DictionaryProperties.transformMethod)); + writer._write(_PdfName('FieldMDP')); + writer._write(_PdfName(_DictionaryProperties.type)); + writer._write(_PdfName(_DictionaryProperties.sigRef)); + writer._write(_PdfName('DigestValue')); + position = writer._position!; + // _fieldsDigestPosition = position; + writer._write( + _PdfString.fromBytes(List.filled(16, 0, growable: true))); + digestLocation = _PdfArray(); + digestLocation._add(_PdfNumber(position)); + digestLocation._add(_PdfNumber(34)); + writer._write(_PdfName('DigestLocation')); + writer._write(digestLocation); + writer._write(_PdfName('DigestMethod')); + writer._write(_PdfName('MD5')); + writer._write(_PdfName(_DictionaryProperties.data)); + writer._write(_Operators.whiteSpace); + writer._write(_PdfReferenceHolder(cat)); + writer._write('>>'); + writer._write(_PdfArray.endMark); + writer._write(_Operators.whiteSpace); + } + } + + void _documentSaved(Object sender, _DocumentSavedArgs e) { + final bool enabled = _doc.security._encryptor.encrypt; + _doc.security._encryptor.encrypt = false; + final _PdfWriter writer = e.writer as _PdfWriter; + final int number = e.writer!._length! - _secondRangeIndex!; + final String str = '0 '; + final String str2 = _firstRangeLength.toString() + ' '; + final String str3 = _secondRangeIndex.toString() + ' '; + final String str4 = number.toString(); + int startPosition = _saveRangeItem(writer, str, _startPositionByteRange!); + startPosition = _saveRangeItem(writer, str2, startPosition); + startPosition = _saveRangeItem(writer, str3, startPosition); + _saveRangeItem(e.writer as _PdfWriter, str4, startPosition); + _range = [0, int.parse(str2), int.parse(str3), int.parse(str4)]; + _stream = writer._buffer; + final String text = _PdfString._bytesToHex(getPkcs7Content()!); + _stream!.replaceRange( + _firstRangeLength!, _firstRangeLength! + 1, utf8.encode('<')); + final int newPos = _firstRangeLength! + 1 + text.length; + _stream!.replaceRange(_firstRangeLength! + 1, newPos, utf8.encode(text)); + final int num3 = (_secondRangeIndex! - newPos) ~/ 2; + final String emptyText = + _PdfString._bytesToHex(List.generate(num3, (i) => 0)); + _stream!.replaceRange( + newPos, newPos + emptyText.length, utf8.encode(emptyText)); + _stream!.replaceRange(newPos + emptyText.length, + newPos + emptyText.length + 1, utf8.encode('>')); + _doc.security._encryptor.encrypt = enabled; + } + + List? getPkcs7Content() { + String? hasalgorithm = ''; + _SignaturePrivateKey externalSignature; + List>? crlBytes; + List? ocspByte; + List<_X509Certificate?>? chain = <_X509Certificate?>[]; + final IPdfExternalSigner? externalSigner = _sig!._externalSigner; + if (externalSigner != null && _sig!._externalChain != null) { + chain = _sig!._externalChain; + final String digest = getDigestAlgorithm(externalSigner.hashAlgorithm); + final _SignaturePrivateKey pks = _SignaturePrivateKey(digest); + hasalgorithm = pks.getHashAlgorithm(); + externalSignature = pks; + } else { + String certificateAlias = ''; + final List keys = + _cert!._pkcsCertificate.getContentTable().keys.toList(); + bool isContinue = true; + keys.forEach((key) { + if (isContinue && + _cert!._pkcsCertificate.isKey(key) && + _cert!._pkcsCertificate.getKey(key)!._key!.isPrivate!) { + certificateAlias = key; + isContinue = false; + } + }); + final _KeyEntry pk = _cert!._pkcsCertificate.getKey(certificateAlias)!; + final List<_X509Certificates> certificates = + _cert!._pkcsCertificate.getCertificateChain(certificateAlias)!; + certificates.forEach((c) { + chain!.add(c.certificate); + }); + final _RsaPrivateKeyParam? parameters = pk._key as _RsaPrivateKeyParam?; + final String digest = _sig != null + ? getDigestAlgorithm(_sig!.digestAlgorithm) + : _MessageDigestAlgorithms.secureHash256; + final _SignaturePrivateKey pks = _SignaturePrivateKey(digest, parameters); + hasalgorithm = pks.getHashAlgorithm(); + externalSignature = pks; + } + final _PdfCmsSigner pkcs7 = + _PdfCmsSigner(null, chain!, hasalgorithm!, false); + final _IRandom source = getUnderlyingSource(); + final List<_IRandom?> sources = + List<_IRandom?>.generate(_range.length ~/ 2, (i) => null); + for (int j = 0; j < _range.length; j += 2) { + sources[j ~/ 2] = _WindowRandom(source, _range[j], _range[j + 1]); + } + final _StreamReader data = _RandomStream(_RandomGroup(sources)); + final List hash = pkcs7._digestAlgorithm.digest(data, hasalgorithm)!; + final List? sh = pkcs7 + .getSequenceDataSet( + hash, ocspByte, crlBytes, _sig!.cryptographicStandard) + .getEncoded(_Asn1Constants.der); + List? extSignature; + if (externalSigner != null) { + final SignerResult? signerResult = externalSigner.sign(sh!); + if (signerResult != null && signerResult.signedData.isNotEmpty) { + extSignature = signerResult.signedData; + } + if (extSignature != null) { + pkcs7.setSignedData( + extSignature, null, externalSignature.getEncryptionAlgorithm()); + } else { + return List.filled(_estimatedSize, 0, growable: true); + } + } else { + extSignature = externalSignature.sign(sh!); + } + pkcs7.setSignedData( + extSignature!, null, externalSignature.getEncryptionAlgorithm()); + return pkcs7.sign(hash, null, null, ocspByte, crlBytes, + _sig!.cryptographicStandard, hasalgorithm); + } + + _IRandom getUnderlyingSource() { + return _RandomArray(_stream!.sublist(0)); + } + + String getDigestAlgorithm(DigestAlgorithm? digest) { + String digestAlgorithm; + switch (digest) { + case DigestAlgorithm.sha1: + digestAlgorithm = _MessageDigestAlgorithms.secureHash1; + break; + case DigestAlgorithm.sha384: + digestAlgorithm = _MessageDigestAlgorithms.secureHash384; + break; + case DigestAlgorithm.sha512: + digestAlgorithm = _MessageDigestAlgorithms.secureHash512; + break; + default: + digestAlgorithm = _MessageDigestAlgorithms.secureHash256; + break; + } + return digestAlgorithm; + } + + int _saveRangeItem(_PdfWriter writer, String str, int startPosition) { + final List date = utf8.encode(str); + writer._buffer! + .replaceRange(startPosition, startPosition + date.length, date); + return startPosition + str.length; + } + + //Overrides + @override + _IPdfPrimitive get _element => _dictionary; + + @override + // ignore: unused_element + set _element(_IPdfPrimitive? value) { + throw ArgumentError('Primitive element can\'t be set'); + } +} + +class _MessageDigestAlgorithms { + _MessageDigestAlgorithms() { + _names = {}; + _names['1.2.840.113549.2.5'] = 'MD5'; + _names['1.3.14.3.2.26'] = 'SHA1'; + _names['2.16.840.1.101.3.4.2.1'] = 'SHA256'; + _names['2.16.840.1.101.3.4.2.2'] = 'SHA384'; + _names['2.16.840.1.101.3.4.2.3'] = 'SHA512'; + _names['1.3.36.3.2.1'] = 'RIPEMD160'; + _names['1.2.840.113549.1.1.4'] = 'MD5'; + _names['1.2.840.113549.1.1.5'] = 'SHA1'; + _names['1.2.840.113549.1.1.11'] = 'SHA256'; + _names['1.2.840.113549.1.1.12'] = 'SHA384'; + _names['1.2.840.113549.1.1.13'] = 'SHA512'; + _names['1.2.840.113549.2.5'] = 'MD5'; + _names['1.2.840.10040.4.3'] = 'SHA1'; + _names['2.16.840.1.101.3.4.3.2'] = 'SHA256'; + _names['2.16.840.1.101.3.4.3.3'] = 'SHA384'; + _names['2.16.840.1.101.3.4.3.4'] = 'SHA512'; + _names['1.3.36.3.3.1.2'] = 'RIPEMD160'; + _digests = {}; + _digests['MD5'] = '1.2.840.113549.2.5'; + _digests['MD-5'] = '1.2.840.113549.2.5'; + _digests['SHA1'] = '1.3.14.3.2.26'; + _digests['SHA-1'] = '1.3.14.3.2.26'; + _digests['SHA256'] = '2.16.840.1.101.3.4.2.1'; + _digests['SHA-256'] = '2.16.840.1.101.3.4.2.1'; + _digests['SHA384'] = '2.16.840.1.101.3.4.2.2'; + _digests['SHA-384'] = '2.16.840.1.101.3.4.2.2'; + _digests['SHA512'] = '2.16.840.1.101.3.4.2.3'; + _digests['SHA-512'] = '2.16.840.1.101.3.4.2.3'; + _digests['RIPEMD160'] = '1.3.36.3.2.1'; + _digests['RIPEMD-160'] = '1.3.36.3.2.1'; + _algorithms = {}; + _algorithms['SHA1'] = 'SHA-1'; + _algorithms[_DerObjectID('1.3.14.3.2.26')._id] = 'SHA-1'; + _algorithms['SHA256'] = 'SHA-256'; + _algorithms[_NistObjectIds.sha256._id] = 'SHA-256'; + _algorithms["SHA384"] = 'SHA-384'; + _algorithms[_NistObjectIds.sha384._id] = 'SHA-384'; + _algorithms["SHA512"] = 'SHA-512'; + _algorithms[_NistObjectIds.sha512._id] = 'SHA-512'; + _algorithms["MD5"] = 'MD5'; + _algorithms[_PkcsObjectId.md5._id] = 'MD5'; + _algorithms["RIPEMD-160"] = 'RIPEMD160'; + _algorithms["RIPEMD160"] = 'RIPEMD160'; + _algorithms[_NistObjectIds.ripeMD160._id] = 'RIPEMD160'; + } + static const String secureHash1 = 'SHA-1'; + static const String secureHash256 = 'SHA-256'; + static const String secureHash384 = 'SHA-384'; + static const String secureHash512 = 'SHA-512'; + late Map _names; + late Map _digests; + late Map _algorithms; + String? getDigest(String? id) { + String? result; + if (_names.containsKey(id)) { + result = _names[id!]; + } else { + result = id; + } + return result; + } + + String? getAllowedDigests(String name) { + String? result; + final String lower = name.toLowerCase(); + _digests.forEach((String key, String value) { + if (lower == key.toLowerCase()) { + result = _digests[key]; + } + }); + return result; + } + + dynamic getMessageDigest(String hashAlgorithm) { + String lower = hashAlgorithm.toLowerCase(); + String? digest = lower; + bool isContinue = true; + _algorithms.forEach((String? key, String value) { + if (isContinue && key!.toLowerCase() == lower) { + digest = _algorithms[key]; + isContinue = false; + } + }); + dynamic result; + lower = digest!.toLowerCase(); + if (lower == 'sha1' || lower == 'sha-1' || lower == 'sha_1') { + result = sha1; + } else if (lower == 'sha256' || lower == 'sha-256' || lower == 'sha_256') { + result = sha256; + } else if (lower == 'sha384' || lower == 'sha-384' || lower == 'sha_384') { + result = sha384; + } else if (lower == 'sha512' || lower == 'sha-512' || lower == 'sha_512') { + result = sha512; + } else if (lower == 'md5' || lower == 'md-5' || lower == 'md_5') { + result = md5; + } else { + throw ArgumentError.value( + hashAlgorithm, 'hashAlgorithm', 'Invalid message digest algorithm'); + } + return result; + } + + List? digest(_StreamReader data, dynamic hashAlgorithm) { + dynamic algorithm; + if (hashAlgorithm is String) { + algorithm = getMessageDigest(hashAlgorithm); + } else { + algorithm = hashAlgorithm; + } + final dynamic output = AccumulatorSink(); + final dynamic input = algorithm.startChunkedConversion(output); + int? count; + final List bytes = List.generate(8192, (i) => 0); + while ((count = data.read(bytes, 0, bytes.length))! > 0) { + input.add(bytes.sublist(0, count)); + } + input.close(); + return output.events.single.bytes; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pkcs/password_utility.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pkcs/password_utility.dart new file mode 100644 index 000000000..f17d77ebf --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pkcs/password_utility.dart @@ -0,0 +1,787 @@ +part of pdf; + +class _PasswordUtility { + _PasswordUtility() { + _cipherUtils = _CipherUtils(); + _pkcs12 = 'Pkcs12'; + _algorithms = {}; + _type = {}; + _ids = {}; + _algorithms['PBEWITHSHAAND40BITRC4'] = 'PBEwithSHA-1and40bitRC4'; + _algorithms['PBEWITHSHA1AND40BITRC4'] = 'PBEwithSHA-1and40bitRC4'; + _algorithms['PBEWITHSHA-1AND40BITRC4'] = 'PBEwithSHA-1and40bitRC4'; + _algorithms[_PkcsObjectId.pbeWithShaAnd40BitRC4._id] = + 'PBEwithSHA-1and40bitRC4'; + _algorithms['PBEWITHSHAAND3-KEYDESEDE-CBC'] = + 'PBEwithSHA-1and3-keyDESEDE-CBC'; + _algorithms['PBEWITHSHAAND3-KEYTRIPLEDES-CBC'] = + 'PBEwithSHA-1and3-keyDESEDE-CBC'; + _algorithms['PBEWITHSHA1AND3-KEYDESEDE-CBC'] = + 'PBEwithSHA-1and3-keyDESEDE-CBC'; + _algorithms['PBEWITHSHA1AND3-KEYTRIPLEDES-CBC'] = + 'PBEwithSHA-1and3-keyDESEDE-CBC'; + _algorithms['PBEWITHSHA-1AND3-KEYDESEDE-CBC'] = + 'PBEwithSHA-1and3-keyDESEDE-CBC'; + _algorithms['PBEWITHSHA-1AND3-KEYTRIPLEDES-CBC'] = + 'PBEwithSHA-1and3-keyDESEDE-CBC'; + _algorithms[_PkcsObjectId.pbeWithShaAnd3KeyTripleDesCbc._id] = + 'PBEwithSHA-1and3-keyDESEDE-CBC'; + _algorithms['PBEWITHSHAAND40BITRC2-CBC'] = 'PBEwithSHA-1and40bitRC2-CBC'; + _algorithms['PBEWITHSHA1AND40BITRC2-CBC'] = 'PBEwithSHA-1and40bitRC2-CBC'; + _algorithms['PBEWITHSHA-1AND40BITRC2-CBC'] = 'PBEwithSHA-1and40bitRC2-CBC'; + _algorithms[_PkcsObjectId.pbewithShaAnd40BitRC2Cbc._id] = + 'PBEwithSHA-1and40bitRC2-CBC'; + _algorithms['PBEWITHSHAAND128BITAES-CBC-BC'] = + 'PBEwithSHA-1and128bitAES-CBC-BC'; + _algorithms['PBEWITHSHA1AND128BITAES-CBC-BC'] = + 'PBEwithSHA-1and128bitAES-CBC-BC'; + _algorithms['PBEWITHSHA-1AND128BITAES-CBC-BC'] = + 'PBEwithSHA-1and128bitAES-CBC-BC'; + _algorithms['PBEWITHSHAAND192BITAES-CBC-BC'] = + 'PBEwithSHA-1and192bitAES-CBC-BC'; + _algorithms['PBEWITHSHA1AND192BITAES-CBC-BC'] = + 'PBEwithSHA-1and192bitAES-CBC-BC'; + _algorithms['PBEWITHSHA-1AND192BITAES-CBC-BC'] = + 'PBEwithSHA-1and192bitAES-CBC-BC'; + _algorithms['PBEWITHSHAAND256BITAES-CBC-BC'] = + 'PBEwithSHA-1and256bitAES-CBC-BC'; + _algorithms['PBEWITHSHA1AND256BITAES-CBC-BC'] = + 'PBEwithSHA-1and256bitAES-CBC-BC'; + _algorithms['PBEWITHSHA-1AND256BITAES-CBC-BC'] = + 'PBEwithSHA-1and256bitAES-CBC-BC'; + _algorithms['PBEWITHSHA256AND128BITAES-CBC-BC'] = + 'PBEwithSHA-256and128bitAES-CBC-BC'; + _algorithms['PBEWITHSHA-256AND128BITAES-CBC-BC'] = + 'PBEwithSHA-256and128bitAES-CBC-BC'; + _algorithms['PBEWITHSHA256AND192BITAES-CBC-BC'] = + 'PBEwithSHA-256and192bitAES-CBC-BC'; + _algorithms['PBEWITHSHA-256AND192BITAES-CBC-BC'] = + 'PBEwithSHA-256and192bitAES-CBC-BC'; + _algorithms['PBEWITHSHA256AND256BITAES-CBC-BC'] = + 'PBEwithSHA-256and256bitAES-CBC-BC'; + _algorithms['PBEWITHSHA-256AND256BITAES-CBC-BC'] = + 'PBEwithSHA-256and256bitAES-CBC-BC'; + _type['Pkcs12'] = _pkcs12; + _type['PBEwithSHA-1and128bitRC4'] = _pkcs12; + _type['PBEwithSHA-1and40bitRC4'] = _pkcs12; + _type['PBEwithSHA-1and3-keyDESEDE-CBC'] = _pkcs12; + _type['PBEwithSHA-1and2-keyDESEDE-CBC'] = _pkcs12; + _type['PBEwithSHA-1and128bitRC2-CBC'] = _pkcs12; + _type['PBEwithSHA-1and40bitRC2-CBC'] = _pkcs12; + _type['PBEwithSHA-1and256bitAES-CBC-BC'] = _pkcs12; + _type['PBEwithSHA-256and128bitAES-CBC-BC'] = _pkcs12; + _type['PBEwithSHA-256and192bitAES-CBC-BC'] = _pkcs12; + _type['PBEwithSHA-256and256bitAES-CBC-BC'] = _pkcs12; + _ids['PBEwithSHA-1and128bitRC4'] = _PkcsObjectId.pbeWithShaAnd128BitRC4; + _ids['PBEwithSHA-1and40bitRC4'] = _PkcsObjectId.pbeWithShaAnd40BitRC4; + _ids['PBEwithSHA-1and3-keyDESEDE-CBC'] = + _PkcsObjectId.pbeWithShaAnd3KeyTripleDesCbc; + _ids['PBEwithSHA-1and2-keyDESEDE-CBC'] = + _PkcsObjectId.pbeWithShaAnd2KeyTripleDesCbc; + _ids['PBEwithSHA-1and128bitRC2-CBC'] = + _PkcsObjectId.pbeWithShaAnd128BitRC2Cbc; + _ids['PBEwithSHA-1and40bitRC2-CBC'] = + _PkcsObjectId.pbewithShaAnd40BitRC2Cbc; + } + + //Fields + String? _pkcs12; + late Map _algorithms; + late Map _type; + late Map _ids; + late _CipherUtils _cipherUtils; + + //Implementation + dynamic createEncoder(dynamic obj) { + dynamic result; + if (obj is _Algorithms) { + result = createEncoder(obj._objectID!._id); + } else if (obj is _DerObjectID) { + result = createEncoder(obj._id); + } else if (obj is String) { + final String lower = obj.toLowerCase(); + String? mechanism; + bool isContinue = true; + _algorithms.forEach((String? key, String value) { + if (isContinue && lower == key!.toLowerCase()) { + mechanism = _algorithms[key]; + isContinue = false; + } + }); + + if (mechanism != null && mechanism!.startsWith('PBEwithMD2') || + mechanism!.startsWith('PBEwithMD5') || + mechanism!.startsWith('PBEwithSHA-1') || + mechanism!.startsWith('PBEwithSHA-256')) { + if (mechanism!.endsWith('AES-CBC-BC') || + mechanism!.endsWith('AES-CBC-OPENSSL')) { + result = _cipherUtils.getCipher('AES/CBC'); + } else if (mechanism!.endsWith('DES-CBC')) { + result = _cipherUtils.getCipher('DES/CBC'); + } else if (mechanism!.endsWith('DESEDE-CBC')) { + result = _cipherUtils.getCipher('DESEDE/CBC'); + } else if (mechanism!.endsWith('RC2-CBC')) { + result = _cipherUtils.getCipher('RC2/CBC'); + } else if (mechanism!.endsWith('RC4')) { + result = _cipherUtils.getCipher('RC4'); + } + } + } + return result; + } + + _ICipherParameter? generateCipherParameters(String algorithm, String password, + bool isWrong, _Asn1Encode? pbeParameters) { + final String mechanism = getAlgorithmFromUpeerInvariant(algorithm)!; + late List keyBytes; + List? salt; + int iterationCount = 0; + if (isPkcs12(mechanism)) { + final _Pkcs12PasswordParameter pbeParams = + _Pkcs12PasswordParameter.getPbeParameter(pbeParameters); + salt = pbeParams._octet!.getOctets(); + iterationCount = pbeParams._iterations!.value.toSigned(32).toInt(); + keyBytes = _PasswordGenerator.toBytes(password, isWrong); + } + _ICipherParameter? parameters; + _PasswordGenerator generator; + if (mechanism.startsWith('PBEwithSHA-1')) { + generator = getEncoder(_type[mechanism], _DigestAlgorithms.sha1, keyBytes, + salt!, iterationCount, password); + if (mechanism == 'PBEwithSHA-1and128bitAES-CBC-BC') { + parameters = generator.generateParam(128, 'AES', 128); + } else if (mechanism == 'PBEwithSHA-1and192bitAES-CBC-BC') { + parameters = generator.generateParam(192, 'AES', 128); + } else if (mechanism == 'PBEwithSHA-1and256bitAES-CBC-BC') { + parameters = generator.generateParam(256, 'AES', 128); + } else if (mechanism == 'PBEwithSHA-1and128bitRC4') { + parameters = generator.generateParam(128, 'RC4'); + } else if (mechanism == 'PBEwithSHA-1and40bitRC4') { + parameters = generator.generateParam(40, 'RC4'); + } else if (mechanism == 'PBEwithSHA-1and3-keyDESEDE-CBC') { + parameters = generator.generateParam(192, 'DESEDE', 64); + } else if (mechanism == 'PBEwithSHA-1and2-keyDESEDE-CBC') { + parameters = generator.generateParam(128, 'DESEDE', 64); + } else if (mechanism == 'PBEwithSHA-1and128bitRC2-CBC') { + parameters = generator.generateParam(128, 'RC2', 64); + } else if (mechanism == 'PBEwithSHA-1and40bitRC2-CBC') { + parameters = generator.generateParam(40, 'RC2', 64); + } else if (mechanism == 'PBEwithSHA-1andDES-CBC') { + parameters = generator.generateParam(64, 'DES', 64); + } else if (mechanism == 'PBEwithSHA-1andRC2-CBC') { + parameters = generator.generateParam(64, 'RC2', 64); + } + } else if (mechanism.startsWith('PBEwithSHA-256')) { + generator = getEncoder(_type[mechanism], _DigestAlgorithms.sha256, + keyBytes, salt!, iterationCount, password); + if (mechanism == 'PBEwithSHA-256and128bitAES-CBC-BC') { + parameters = generator.generateParam(128, 'AES', 128); + } else if (mechanism == 'PBEwithSHA-256and192bitAES-CBC-BC') { + parameters = generator.generateParam(192, 'AES', 128); + } else if (mechanism == 'PBEwithSHA-256and256bitAES-CBC-BC') { + parameters = generator.generateParam(256, 'AES', 128); + } + } else if (mechanism.startsWith('PBEwithHmac')) { + final String digest = + getDigest(mechanism.substring('PBEwithHmac'.length)); + generator = getEncoder( + _type[mechanism], digest, keyBytes, salt!, iterationCount, password); + final int? bitLen = getBlockSize(digest); + parameters = generator.generateParam(bitLen); + } + keyBytes = List.generate(keyBytes.length, (i) => 0); + return fixDataEncryptionParity(mechanism, parameters); + } + + static int getByteLength(String digest) { + return (digest == _DigestAlgorithms.md5 || + digest == _DigestAlgorithms.sha1 || + digest == _DigestAlgorithms.sha256 || + digest.contains('Hmac')) + ? 64 + : 128; + } + + static int? getBlockSize(String digest) { + int? result; + if (digest == _DigestAlgorithms.md5) { + result = 16; + } else if (digest == _DigestAlgorithms.sha1) { + result = 20; + } else if (digest == _DigestAlgorithms.sha256) { + result = 32; + } else if (digest == _DigestAlgorithms.sha512) { + result = 64; + } else if (digest == _DigestAlgorithms.sha384) { + result = 48; + } else if (digest.contains('Hmac')) { + result = 20; + } + return result; + } + + _ICipherParameter? fixDataEncryptionParity( + String mechanism, _ICipherParameter? parameters) { + if (!mechanism.endsWith('DES-CBC') & !mechanism.endsWith('DESEDE-CBC')) { + return parameters; + } + if (parameters is _InvalidParameter) { + return _InvalidParameter( + fixDataEncryptionParity(mechanism, parameters._parameters), + parameters._bytes!); + } + final _KeyParameter kParam = parameters as _KeyParameter; + final List keyBytes = kParam.keys; + for (int i = 0; i < keyBytes.length; i++) { + final int value = keyBytes[i]; + keyBytes[i] = ((value & 0xfe) | + ((((value >> 1) ^ + (value >> 2) ^ + (value >> 3) ^ + (value >> 4) ^ + (value >> 5) ^ + (value >> 6) ^ + (value >> 7)) ^ + 0x01) & + 0x01)) + .toUnsigned(8); + } + return _KeyParameter(keyBytes); + } + + bool isPkcs12(String algorithm) { + final String? mechanism = getAlgorithmFromUpeerInvariant(algorithm); + return mechanism != null && + _type.containsKey(mechanism) && + _pkcs12 == _type[mechanism]; + } + + String getDigest(String algorithm) { + String? digest = getAlgorithmFromUpeerInvariant(algorithm); + if (digest == null) { + digest = algorithm; + } + if (digest.contains('sha_1') || + digest.contains('sha-1') || + digest.contains('sha1')) { + digest = _DigestAlgorithms.hmacWithSha1; + } else if (digest.contains('sha_256') || + digest.contains('sha-256') || + digest.contains('sha256')) { + digest = _DigestAlgorithms.hmacWithSha256; + } else if (digest.contains('md5') || + digest.contains('md_5') || + digest.contains('md-5')) { + digest = _DigestAlgorithms.hmacWithMd5; + } else { + throw ArgumentError.value(algorithm, 'algorithm', 'Invalid message'); + } + return digest; + } + + _PasswordGenerator getEncoder(String? type, String digest, List key, + List salt, int iterationCount, String password) { + _PasswordGenerator generator; + if (type == _pkcs12) { + generator = _Pkcs12AlgorithmGenerator(digest, password); + } else { + throw ArgumentError.value( + type, 'type', 'Invalid Password Based Encryption type'); + } + generator.init(key, salt, iterationCount); + return generator; + } + + String? getAlgorithmFromUpeerInvariant(String algorithm) { + final String temp = algorithm.toLowerCase(); + String? result; + bool isContinue = true; + _algorithms.forEach((String? key, String value) { + if (isContinue && key!.toLowerCase() == temp) { + result = value; + isContinue = false; + } + }); + return result; + } +} + +abstract class _PasswordGenerator { + List? _password; + List? _value; + int? _count; + _ICipherParameter generateParam(int? keySize, [String? algorithm, int? size]); + void init(List password, List value, int count) { + _password = _Asn1Constants.clone(password); + _value = _Asn1Constants.clone(value); + _count = count; + } + + static List toBytes(String password, bool isWrong) { + if (password.length < 1) { + return isWrong ? List.generate(2, (i) => 0) : []; + } + final List bytes = + List.generate((password.length + 1) * 2, (i) => 0); + final List tempBytes = _encodeBigEndian(password); + int i = 0; + tempBytes.forEach((tempByte) { + bytes[i] = tempBytes[i]; + i++; + }); + return bytes; + } +} + +class _Pkcs12AlgorithmGenerator extends _PasswordGenerator { + _Pkcs12AlgorithmGenerator(String digest, String password) { + _digest = getDigest(digest, password); + _size = _PasswordUtility.getBlockSize(digest); + _length = _PasswordUtility.getByteLength(digest); + _keyMaterial = 1; + _invaidMaterial = 2; + } + late dynamic _digest; + int? _size; + late int _length; + int? _keyMaterial; + int? _invaidMaterial; + //Implementes + dynamic getDigest(String digest, String password) { + dynamic result; + if (digest == _DigestAlgorithms.md5) { + result = md5; + } else if (digest == _DigestAlgorithms.sha1) { + result = sha1; + } else if (digest == _DigestAlgorithms.sha256) { + result = sha256; + } else if (digest == _DigestAlgorithms.sha384) { + result = sha384; + } else if (digest == _DigestAlgorithms.sha512) { + result = sha512; + } else if (digest == _DigestAlgorithms.hmacWithSha1) { + result = Hmac(sha1, utf8.encode(password)); + } else if (digest == _DigestAlgorithms.hmacWithSha256) { + result = Hmac(sha256, utf8.encode(password)); + } else if (digest == _DigestAlgorithms.hmacWithMd5) { + result = Hmac(md5, utf8.encode(password)); + } else { + throw ArgumentError.value(digest, 'digest', 'Invalid message digest'); + } + return result; + } + + @override + _ICipherParameter generateParam(int? keySize, + [String? algorithm, int? size]) { + if (size != null) { + size = size ~/ 8; + keySize = keySize! ~/ 8; + final List bytes = generateDerivedKey(_keyMaterial, keySize); + final _ParamUtility util = _ParamUtility(); + final _KeyParameter key = + util.createKeyParameter(algorithm!, bytes, 0, keySize); + final List iv = generateDerivedKey(_invaidMaterial, size); + return _InvalidParameter(key, iv, 0, size); + } else if (algorithm != null) { + keySize = keySize! ~/ 8; + final List bytes = generateDerivedKey(_keyMaterial, keySize); + final _ParamUtility util = _ParamUtility(); + return util.createKeyParameter(algorithm, bytes, 0, keySize); + } else { + keySize = keySize! ~/ 8; + final List bytes = generateDerivedKey(_keyMaterial, keySize); + return _KeyParameter.fromLengthValue(bytes, 0, keySize); + } + } + + List generateDerivedKey(int? id, int length) { + final List d = List.generate(_length, (index) => 0); + final List derivedKey = List.generate(length, (index) => 0); + for (int index = 0; index != d.length; index++) { + d[index] = id!.toUnsigned(8); + } + List s; + if (_value != null && _value!.length != 0) { + s = List.generate( + (_length * ((_value!.length + _length - 1) ~/ _length)), + (index) => 0); + for (int index = 0; index != s.length; index++) { + s[index] = _value![index % _value!.length]; + } + } else { + s = []; + } + List password; + if (_password != null && _password!.length != 0) { + password = List.generate( + (_length * ((_password!.length + _length - 1) ~/ _length)), + (index) => 0); + for (int index = 0; index != password.length; index++) { + password[index] = _password![index % _password!.length]; + } + } else { + password = []; + } + List tempBytes = + List.generate(s.length + password.length, (index) => 0); + List.copyRange(tempBytes, 0, s, 0, s.length); + List.copyRange(tempBytes, s.length, password, 0, password.length); + final List b = List.generate(_length, (index) => 0); + final int c = (length + _size! - 1) ~/ _size!; + List? a = List.generate(_size!, (index) => 0); + for (int i = 1; i <= c; i++) { + final dynamic output = AccumulatorSink(); + final dynamic input = sha1.startChunkedConversion(output); + input.add(d); + input.add(tempBytes); + input.close(); + a = output.events.single.bytes; + for (int j = 1; j != _count; j++) { + a = _digest.convert(a).bytes; + } + for (int j = 0; j != b.length; j++) { + b[j] = a![j % a.length]; + } + for (int j = 0; j != tempBytes.length ~/ _length; j++) { + tempBytes = adjust(tempBytes, j * _length, b); + } + if (i == c) { + List.copyRange(derivedKey, (i - 1) * _size!, a!, 0, + derivedKey.length - ((i - 1) * _size!)); + } else { + List.copyRange(derivedKey, (i - 1) * _size!, a!, 0, a.length); + } + } + return derivedKey; + } + + List adjust(List a, int offset, List b) { + int x = (b[b.length - 1] & 0xff) + (a[offset + b.length - 1] & 0xff) + 1; + a[offset + b.length - 1] = x.toUnsigned(8); + x = (x.toUnsigned(32) >> 8).toSigned(32); + for (int i = b.length - 2; i >= 0; i--) { + x += (b[i] & 0xff) + (a[offset + i] & 0xff); + a[offset + i] = x.toUnsigned(8); + x = (x.toUnsigned(32) >> 8).toSigned(32); + } + return a; + } +} + +class _Pkcs12PasswordParameter extends _Asn1Encode { + _Pkcs12PasswordParameter(_Asn1Sequence sequence) { + if (sequence.count != 2) { + throw ArgumentError.value( + sequence, 'sequence', 'Invalid length in sequence'); + } + _octet = _Asn1Octet.getOctetStringFromObject(sequence[0]); + _iterations = _DerInteger.getNumber(sequence[1]); + } + //Fields + _DerInteger? _iterations; + _Asn1Octet? _octet; + //Implementation + static _Pkcs12PasswordParameter getPbeParameter(dynamic obj) { + _Pkcs12PasswordParameter result; + if (obj is _Pkcs12PasswordParameter) { + result = obj; + } else if (obj is _Asn1Sequence) { + result = _Pkcs12PasswordParameter(obj); + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid entry'); + } + return result; + } + + @override + _Asn1 getAsn1() { + return _DerSequence(array: <_Asn1Encode?>[_octet, _iterations]); + } +} + +class _KeyEntry { + _KeyEntry(_CipherParameter? key) { + _key = key; + } + _CipherParameter? _key; + //Implementation + @override + bool operator ==(Object obj) { + if (obj is _KeyEntry) { + return _key == obj._key; + } else { + return false; + } + } + + @override + int get hashCode => _key.hashCode; +} + +class _ParamUtility { + _ParamUtility() { + _algorithms = {}; + addAlgorithm('DESEDE', [ + 'DESEDEWRAP', + 'TDEA', + _DerObjectID('1.3.14.3.2.17'), + _PkcsObjectId.idAlgCms3DesWrap + ]); + addAlgorithm('DESEDE3', [_PkcsObjectId.desEde3Cbc]); + addAlgorithm( + 'RC2', [_PkcsObjectId.rc2Cbc, _PkcsObjectId.idAlgCmsRC2Wrap]); + } + + //Fields + late Map _algorithms; + + //Implementation + void addAlgorithm(String name, List objects) { + _algorithms[name] = name; + objects.forEach((entry) { + if (entry is String) { + _algorithms[entry] = name; + } else { + _algorithms[entry.toString()] = name; + } + }); + } + + _KeyParameter createKeyParameter( + String algorithm, List bytes, int offset, int? length) { + String? name; + final String lower = algorithm.toLowerCase(); + _algorithms.forEach((String key, String value) { + if (lower == key.toLowerCase()) { + name = value; + } + }); + if (name == null) { + throw ArgumentError.value( + algorithm, 'algorithm', 'Invalid entry. Algorithm'); + } + if (name == 'DES') { + return _DataEncryptionParameter.fromLengthValue(bytes, offset, length!); + } + if (name == 'DESEDE' || name == 'DESEDE3') { + return _DesedeAlgorithmParameter(bytes, offset, length); + } + return _KeyParameter.fromLengthValue(bytes, offset, length!); + } +} + +class _DataEncryptionParameter extends _KeyParameter { + _DataEncryptionParameter(List keys) : super(keys) { + if (checkKey(keys, 0)) { + throw ArgumentError.value( + keys, 'keys', 'Invalid Data Encryption keys creation'); + } + } + _DataEncryptionParameter.fromLengthValue( + List keys, int offset, int length) + : super.fromLengthValue(keys, offset, length) { + if (checkKey(keys, 0)) { + throw ArgumentError.value( + keys, 'keys', 'Invalid Data Encryption keys creation'); + } + } + static List dataEncryptionWeekKeys = [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 31, + 31, + 31, + 31, + 14, + 14, + 14, + 14, + 224, + 224, + 224, + 224, + 241, + 241, + 241, + 241, + 254, + 254, + 254, + 254, + 254, + 254, + 254, + 254, + 1, + 254, + 1, + 254, + 1, + 254, + 1, + 254, + 31, + 224, + 31, + 224, + 14, + 241, + 14, + 241, + 1, + 224, + 1, + 224, + 1, + 241, + 1, + 241, + 31, + 254, + 31, + 254, + 14, + 254, + 14, + 254, + 1, + 31, + 1, + 31, + 1, + 14, + 1, + 14, + 224, + 254, + 224, + 254, + 241, + 254, + 241, + 254, + 254, + 1, + 254, + 1, + 254, + 1, + 254, + 1, + 224, + 31, + 224, + 31, + 241, + 14, + 241, + 14, + 224, + 1, + 224, + 1, + 241, + 1, + 241, + 1, + 254, + 31, + 254, + 31, + 254, + 14, + 254, + 14, + 31, + 1, + 31, + 1, + 14, + 1, + 14, + 1, + 254, + 224, + 254, + 224, + 254, + 241, + 254, + 241 + ]; + + static bool checkKey(List bytes, int offset) { + if (bytes.length - offset < 8) { + throw ArgumentError.value(bytes, 'bytes', 'Invalid length in bytes'); + } + for (int i = 0; i < 16; i++) { + bool isMatch = false; + for (int j = 0; j < 8; j++) { + if (bytes[j + offset] != dataEncryptionWeekKeys[i * 8 + j]) { + isMatch = true; + break; + } + } + if (!isMatch) { + return true; + } + } + return false; + } + + @override + List get keys => List.from(_bytes!); + @override + set keys(List? value) { + _bytes = value; + } +} + +class _DesedeAlgorithmParameter extends _DataEncryptionParameter { + _DesedeAlgorithmParameter(List key, int keyOffset, int? keyLength) + : super(fixKey(key, keyOffset, keyLength)) {} + //Implementation + static List fixKey(List key, int keyOffset, int? keyLength) { + final List tmp = List.generate(24, (i) => 0); + switch (keyLength) { + case 16: + List.copyRange(tmp, 0, key, keyOffset, keyOffset + 16); + List.copyRange(tmp, 16, key, keyOffset, keyOffset + 8); + break; + case 24: + List.copyRange(tmp, 0, key, keyOffset, keyOffset + 24); + break; + default: + throw ArgumentError.value( + keyLength, 'keyLen', 'Bad length for DESede key'); + } + if (checkKeyValue(tmp, 0, tmp.length)) { + throw ArgumentError.value( + key, 'key', 'Attempt to create weak DESede key'); + } + return tmp; + } + + static bool checkKeyValue(List key, int offset, int length) { + for (int i = offset; i < length; i += 8) { + if (_DataEncryptionParameter.checkKey(key, i)) { + return true; + } + } + return false; + } + + @override + List get keys => List.from(_bytes!); + @override + set keys(List? value) { + _bytes = value; + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pkcs/pfx_data.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pkcs/pfx_data.dart new file mode 100644 index 000000000..dd26752ab --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pkcs/pfx_data.dart @@ -0,0 +1,408 @@ +part of pdf; + +class _PfxData extends _Asn1Encode { + _PfxData(_Asn1Sequence sequence) { + _contentInformation = _ContentInformation.getInformation(sequence[1]); + if (sequence.count == 3) { + _macInformation = _MacInformation.getInformation(sequence[2]); + } + } + _ContentInformation? _contentInformation; + _MacInformation? _macInformation; + @override + _Asn1 getAsn1() { + final _Asn1EncodeCollection collection = + _Asn1EncodeCollection(<_Asn1Encode?>[ + _DerInteger(_bigIntToBytes(BigInt.from(3))), + _contentInformation + ]); + if (_macInformation != null) { + collection._encodableObjects.add(_macInformation); + } + return _BerSequence(collection: collection); + } +} + +class _ContentInformation extends _Asn1Encode { + _ContentInformation(_Asn1Sequence sequence) { + if (sequence.count < 1 || sequence.count > 2) { + throw ArgumentError.value( + sequence, 'sequence', 'Invalid length in sequence'); + } + _contentType = sequence[0] as _DerObjectID?; + if (sequence.count > 1) { + final _Asn1Tag tagged = sequence[1] as _Asn1Tag; + if (!tagged._isExplicit! || tagged.tagNumber != 0) { + throw ArgumentError.value(tagged, 'tagged', 'Invalid tag'); + } + _content = tagged.getObject(); + } + } + //Fields + _DerObjectID? _contentType; + _Asn1Encode? _content; + //Implementation + static _ContentInformation? getInformation(dynamic obj) { + _ContentInformation? result; + if (obj == null || obj is _ContentInformation) { + result = obj; + } else if (obj is _Asn1Sequence) { + result = _ContentInformation(obj); + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid entry'); + } + return result; + } + + @override + _Asn1 getAsn1() { + final _Asn1EncodeCollection collection = + _Asn1EncodeCollection(<_Asn1Encode?>[_contentType]); + if (_content != null) { + collection._encodableObjects.add(_DerTag(0, _content)); + } + return _BerSequence(collection: collection); + } +} + +class _MacInformation extends _Asn1Encode { + _MacInformation(_Asn1Sequence sequence) { + _digest = _DigestInformation.getDigestInformation(sequence[0]); + _value = (sequence[1] as _Asn1Octet).getOctets(); + if (sequence.count == 3) { + _count = (sequence[2] as _DerInteger).value; + } else { + _count = BigInt.one; + } + } + //Fields + _DigestInformation? _digest; + List? _value; + BigInt? _count; + //Implementation + static _MacInformation getInformation(dynamic obj) { + _MacInformation result; + if (obj is _MacInformation) { + result = obj; + } else if (obj is _Asn1Sequence) { + result = _MacInformation(obj); + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid entry'); + } + return result; + } + + @override + _Asn1 getAsn1() { + final _Asn1EncodeCollection collection = + _Asn1EncodeCollection(<_Asn1Encode?>[_digest, _DerOctet(_value!)]); + if (_count != BigInt.one) { + collection._encodableObjects.add(_DerInteger.fromNumber(_count)); + } + return _DerSequence(collection: collection); + } +} + +class _Algorithms extends _Asn1Encode { + _Algorithms(_DerObjectID? objectID, [_Asn1Encode? parameters]) { + _objectID = objectID; + if (parameters != null) { + _parameters = parameters; + _parametersDefined = true; + } + } + _Algorithms.fromSequence(_Asn1Sequence sequence) { + if (sequence.count < 1 || sequence.count > 2) { + throw ArgumentError.value('Invalid length in sequence'); + } + _objectID = _DerObjectID.getID(sequence[0]); + _parametersDefined = (sequence.count == 2); + if (_parametersDefined) { + _parameters = sequence[1] as _Asn1Encode?; + } + } + static _Algorithms? getAlgorithms(dynamic obj) { + if (obj == null || obj is _Algorithms) { + return obj; + } + if (obj is _DerObjectID) { + return _Algorithms(obj); + } + if (obj is String) { + return _Algorithms(_DerObjectID(obj)); + } + return _Algorithms.fromSequence(_Asn1Sequence.getSequence(obj)!); + } + + late _Asn1Sequence _sequence; + _DerObjectID? _objectID; + _Asn1Encode? _parameters; + bool _parametersDefined = false; + //Implementation + List? asnEncode() { + return _sequence.asnEncode(); + } + + @override + _Asn1 getAsn1() { + final _Asn1EncodeCollection collection = + _Asn1EncodeCollection(<_Asn1Encode?>[_objectID]); + if (_parametersDefined) { + collection._encodableObjects + .add(_parameters == null ? _DerNull.value : _parameters); + } + return _DerSequence(collection: collection); + } +} + +class _DigestInformation extends _Asn1Encode { + _DigestInformation(_Algorithms? algorithms, List? bytes) { + _bytes = bytes; + _algorithms = algorithms; + } + _DigestInformation.fromSequence(_Asn1Sequence sequence) { + if (sequence.count != 2) { + throw ArgumentError.value('Invalid length in sequence'); + } + _algorithms = _Algorithms.getAlgorithms(sequence[0]); + _bytes = _Asn1Octet.getOctetStringFromObject(sequence[1])!.getOctets(); + } + + //Fields + _Algorithms? _algorithms; + List? _bytes; + + static _DigestInformation getDigestInformation(dynamic obj) { + _DigestInformation result; + if (obj is _DigestInformation) { + result = obj; + } else if (obj is _Asn1Sequence) { + result = _DigestInformation.fromSequence(obj); + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid entry'); + } + return result; + } + + @override + _Asn1 getAsn1() { + return _DerSequence(array: <_Asn1Encode?>[_algorithms, _DerOctet(_bytes!)]); + } +} + +class _EncryptedPrivateKey extends _Asn1Encode { + _EncryptedPrivateKey(_Asn1Sequence sequence) { + if (sequence.count != 2) { + throw ArgumentError.value( + sequence, 'sequence', 'Invalid length in sequence'); + } + _algorithms = _Algorithms.getAlgorithms(sequence[0]); + _octet = _Asn1Octet.getOctetStringFromObject(sequence[1]); + } + + //Fields + _Algorithms? _algorithms; + _Asn1Octet? _octet; + + //Implementation + @override + _Asn1 getAsn1() { + return _DerSequence(array: <_Asn1Encode?>[_algorithms, _octet]); + } + + static _EncryptedPrivateKey getEncryptedPrivateKeyInformation(dynamic obj) { + if (obj is _EncryptedPrivateKey) { + return obj; + } + if (obj is _Asn1Sequence) { + return _EncryptedPrivateKey(obj); + } + throw ArgumentError.value(obj, 'obj', 'Invalid entry in sequence'); + } +} + +class _KeyInformation extends _Asn1Encode { + _KeyInformation(_Algorithms algorithms, _Asn1 privateKey, + [_Asn1Set? attributes]) { + _privateKey = privateKey; + _algorithms = algorithms; + if (attributes != null) { + _attributes = attributes; + } + } + _KeyInformation.fromSequence(_Asn1Sequence? sequence) { + if (sequence != null) { + final List objects = sequence._objects!; + if (objects.length >= 3) { + _algorithms = _Algorithms.getAlgorithms(objects[1]); + final dynamic privateKeyValue = objects[2]; + try { + _privateKey = _Asn1Stream(_StreamReader(privateKeyValue.getOctets())) + .readAsn1(); + } catch (e) { + throw ArgumentError.value(sequence, 'sequence', 'Invalid sequence'); + } + if (objects.length > 3) { + _attributes = _Asn1Set.getAsn1Set((objects[3] as _Asn1Tag?), false); + } + } else { + throw ArgumentError.value(sequence, 'sequence', 'Invalid sequence'); + } + } + } + + //Fields + _Asn1? _privateKey; + _Algorithms? _algorithms; + _Asn1Set? _attributes; + + //Implementation + static _KeyInformation? getInformation(dynamic obj) { + if (obj is _KeyInformation) { + return obj; + } + if (obj != null) { + return _KeyInformation.fromSequence(_Asn1Sequence.getSequence(obj)); + } + return null; + } + + @override + _Asn1 getAsn1() { + final _Asn1EncodeCollection v = _Asn1EncodeCollection(<_Asn1Encode?>[ + _DerInteger.fromNumber(BigInt.from(0)), + _algorithms, + _DerOctet.fromObject(_privateKey!) + ]); + if (_attributes != null) { + v._encodableObjects.add(_DerTag(0, _attributes, false)); + } + return _DerSequence(collection: v); + } +} + +class _RsaKey extends _Asn1Encode { + _RsaKey( + BigInt modulus, + BigInt publicExponent, + BigInt privateExponent, + BigInt prime1, + BigInt prime2, + BigInt exponent1, + BigInt exponent2, + BigInt coefficient) { + _modulus = modulus; + _publicExponent = publicExponent; + _privateExponent = privateExponent; + _prime1 = prime1; + _prime2 = prime2; + _exponent1 = exponent1; + _exponent2 = exponent2; + _coefficient = coefficient; + } + _RsaKey.fromSequence(_Asn1Sequence sequence) { + final BigInt version = (sequence[0] as _DerInteger).value; + if (version.toSigned(32).toInt() != 0) { + throw ArgumentError.value(sequence, 'sequence', 'Invalid RSA key'); + } + _modulus = (sequence[1] as _DerInteger).value; + _publicExponent = (sequence[2] as _DerInteger).value; + _privateExponent = (sequence[3] as _DerInteger).value; + _prime1 = (sequence[4] as _DerInteger).value; + _prime2 = (sequence[5] as _DerInteger).value; + _exponent1 = (sequence[6] as _DerInteger).value; + _exponent2 = (sequence[7] as _DerInteger).value; + _coefficient = (sequence[8] as _DerInteger).value; + } + BigInt? _modulus; + BigInt? _publicExponent; + BigInt? _privateExponent; + BigInt? _prime1; + BigInt? _prime2; + BigInt? _exponent1; + BigInt? _exponent2; + BigInt? _coefficient; + @override + _Asn1 getAsn1() { + return _DerSequence(array: <_Asn1Encode>[ + _DerInteger.fromNumber(BigInt.from(0)), + _DerInteger.fromNumber(_modulus), + _DerInteger.fromNumber(_publicExponent), + _DerInteger.fromNumber(_privateExponent), + _DerInteger.fromNumber(_prime1), + _DerInteger.fromNumber(_prime2), + _DerInteger.fromNumber(_exponent1), + _DerInteger.fromNumber(_exponent2), + _DerInteger.fromNumber(_coefficient) + ]); + } +} + +class _PkcsObjectId { + static const String pkcs1 = '1.2.840.113549.1.1'; + static const String encryptionAlgorithm = '1.2.840.113549.3'; + static const String pkcs7 = '1.2.840.113549.1.7'; + static const String pkcs9 = '1.2.840.113549.1.9'; + static const String pkcs12 = '1.2.840.113549.1.12'; + static const String bagTypes = pkcs12 + '.10.1'; + static const String pkcs12PbeIds = pkcs12 + '.1'; + static const String messageDigestAlgorithm = '1.2.840.113549.2'; + static _DerObjectID rsaEncryption = _DerObjectID(pkcs1 + '.1'); + static _DerObjectID md2WithRsaEncryption = _DerObjectID(pkcs1 + '.2'); + static _DerObjectID sha1WithRsaEncryption = _DerObjectID(pkcs1 + '.5'); + static _DerObjectID sha256WithRsaEncryption = _DerObjectID(pkcs1 + '.11'); + static _DerObjectID sha384WithRsaEncryption = _DerObjectID(pkcs1 + '.12'); + static _DerObjectID sha512WithRsaEncryption = _DerObjectID(pkcs1 + '.13'); + static _DerObjectID desEde3Cbc = _DerObjectID(encryptionAlgorithm + '.7'); + static _DerObjectID rc2Cbc = _DerObjectID(encryptionAlgorithm + '.2'); + static _DerObjectID data = _DerObjectID(pkcs7 + '.1'); + static _DerObjectID signedData = _DerObjectID(pkcs7 + '.2'); + static _DerObjectID encryptedData = _DerObjectID(pkcs7 + '.6'); + static _DerObjectID pkcs9AtEmailAddress = _DerObjectID(pkcs9 + '.1'); + static _DerObjectID pkcs9AtUnstructuredName = _DerObjectID(pkcs9 + '.2'); + static _DerObjectID pkcs9AtUnstructuredAddress = _DerObjectID(pkcs9 + '.8'); + static _DerObjectID pkcs9AtFriendlyName = _DerObjectID(pkcs9 + '.20'); + static _DerObjectID pkcs9AtLocalKeyID = _DerObjectID(pkcs9 + '.21'); + static _DerObjectID keyBag = _DerObjectID(bagTypes + '.1'); + static _DerObjectID pkcs8ShroudedKeyBag = _DerObjectID(bagTypes + '.2'); + static _DerObjectID certBag = _DerObjectID(bagTypes + '.3'); + static _DerObjectID pbeWithShaAnd128BitRC4 = + _DerObjectID(pkcs12PbeIds + '.1'); + static _DerObjectID pbeWithShaAnd40BitRC4 = _DerObjectID(pkcs12PbeIds + '.2'); + static _DerObjectID pbeWithShaAnd3KeyTripleDesCbc = + _DerObjectID(pkcs12PbeIds + '.3'); + static _DerObjectID pbeWithShaAnd2KeyTripleDesCbc = + _DerObjectID(pkcs12PbeIds + '.4'); + static _DerObjectID pbeWithShaAnd128BitRC2Cbc = + _DerObjectID(pkcs12PbeIds + '.5'); + static _DerObjectID pbewithShaAnd40BitRC2Cbc = + _DerObjectID(pkcs12PbeIds + '.6'); + static _DerObjectID idAlgCms3DesWrap = + _DerObjectID('1.2.840.113549.1.9.16.3.6'); + static _DerObjectID idAlgCmsRC2Wrap = + _DerObjectID('1.2.840.113549.1.9.16.3.7'); + static _DerObjectID md5 = _DerObjectID(messageDigestAlgorithm + '5'); +} + +class _NistObjectIds { + static _DerObjectID nistAlgorithm = _DerObjectID('2.16.840.1.101.3.4'); + static _DerObjectID hashAlgs = _DerObjectID(nistAlgorithm._id! + '.2'); + static _DerObjectID sha256 = _DerObjectID(hashAlgs._id! + '.1'); + static _DerObjectID sha384 = _DerObjectID(hashAlgs._id! + '.2'); + static _DerObjectID sha512 = _DerObjectID(hashAlgs._id! + '.3'); + static _DerObjectID dsaWithSHA2 = _DerObjectID(nistAlgorithm._id! + '.3'); + static _DerObjectID dsaWithSHA256 = _DerObjectID(dsaWithSHA2._id! + '.2'); + static _DerObjectID tttAlgorithm = _DerObjectID('1.3.36.3'); + static _DerObjectID ripeMD160 = _DerObjectID(tttAlgorithm._id! + '.2.1'); + static _DerObjectID tttRsaSignatureAlgorithm = + _DerObjectID(tttAlgorithm._id! + '.3.1'); + static _DerObjectID rsaSignatureWithRipeMD160 = + _DerObjectID(tttRsaSignatureAlgorithm._id! + '.2'); +} + +class _X509Objects { + static const String id = '2.5.4'; + static _DerObjectID telephoneNumberID = _DerObjectID(id + '.20'); + static _DerObjectID idSha1 = _DerObjectID('1.3.14.3.2.26'); + static _DerObjectID idEARsa = _DerObjectID('2.5.8.1.1'); +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/x509/x509_certificates.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/x509/x509_certificates.dart new file mode 100644 index 000000000..7aa29e626 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/x509/x509_certificates.dart @@ -0,0 +1,488 @@ +part of pdf; + +class _IX509Extension { + _Asn1Octet? getExtension(_DerObjectID id) => null; +} + +class _X509Certificates { + _X509Certificates(_X509Certificate certificate) { + _certificate = certificate; + } + //Fields + _X509Certificate? _certificate; + //Properties + _X509Certificate? get certificate => _certificate; + @override + int get hashCode => _certificate.hashCode; + @override + bool operator ==(Object other) { + if (other is _X509Certificates) { + return _certificate == other._certificate; + } else { + return false; + } + } +} + +abstract class _X509ExtensionBase implements _IX509Extension { + _X509Extensions? getX509Extensions(); + @override + _Asn1Octet? getExtension(_DerObjectID oid) { + final _X509Extensions? exts = getX509Extensions(); + if (exts != null) { + final _X509Extension? ext = exts.getExtension(oid); + if (ext != null) { + return ext._value; + } + } + return null; + } +} + +class _X509Extension { + _X509Extension(bool critical, _Asn1Octet? value) { + _critical = critical; + _value = value; + } + //Fields + bool? _critical; + _Asn1Octet? _value; + //Implementation + static _Asn1? convertValueToObject(_X509Extension ext) { + return _Asn1Stream(_StreamReader(ext._value!.getOctets())).readAsn1(); + } + + @override + bool operator ==(Object other) { + if (other is _X509Extension) { + return _value == other._value && _critical == other._critical; + } else { + return false; + } + } + + @override + int get hashCode => _critical! ? _value.hashCode : ~_value.hashCode; +} + +class _X509Extensions extends _Asn1Encode { + _X509Extensions(Map<_DerObjectID, _X509Extension> extensions, + [List<_DerObjectID?>? ordering]) { + _extensions = <_DerObjectID?, _X509Extension?>{}; + if (ordering == null) { + final List<_DerObjectID?> der = <_DerObjectID?>[]; + extensions.keys.forEach((col) { + der.add(col); + }); + _ordering = der; + } else { + _ordering = ordering; + } + _ordering.forEach((oid) { + _extensions[oid] = extensions[oid!]; + }); + } + _X509Extensions.fromSequence(_Asn1Sequence seq) { + _ordering = <_DerObjectID?>[]; + _extensions = <_DerObjectID?, _X509Extension?>{}; + for (int i = 0; i < seq._objects!.length; i++) { + final _Asn1Encode ae = seq._objects![i]; + final _Asn1Sequence s = _Asn1Sequence.getSequence(ae.getAsn1())!; + if (s.count < 2 || s.count > 3) { + throw ArgumentError.value( + seq, 'count', 'Bad sequence size: ' + s.count.toString()); + } + final _DerObjectID? oid = _DerObjectID.getID(s[0]!.getAsn1()); + final bool isCritical = + s.count == 3 && (s[1]!.getAsn1() as _DerBoolean).isTrue; + final _Asn1Octet? octets = + _Asn1Octet.getOctetStringFromObject(s[s.count - 1]!.getAsn1()); + _extensions[oid] = _X509Extension(isCritical, octets); + _ordering.add(oid); + } + } + static _X509Extensions? getInstance(dynamic obj, [bool? explicitly]) { + _X509Extensions? result; + if (explicitly == null) { + if (obj == null || obj is _X509Extensions) { + result = obj; + } else if (obj is _Asn1Sequence) { + result = _X509Extensions.fromSequence(obj); + } else if (obj is _Asn1Tag) { + result = getInstance(obj.getObject()); + } else { + throw ArgumentError.value(obj, 'obj', 'unknown object in factory'); + } + } else { + result = getInstance(_Asn1Sequence.getSequence(obj, explicitly)); + } + return result; + } + + //Fields + late Map<_DerObjectID?, _X509Extension?> _extensions; + late List<_DerObjectID?> _ordering; + static _DerObjectID authorityKeyIdentifier = _DerObjectID('2.5.29.35'); + //Implementation + @override + _Asn1 getAsn1() { + final _Asn1EncodeCollection vec = _Asn1EncodeCollection(); + _ordering.forEach((oid) { + final _X509Extension ext = _extensions[oid]!; + final _Asn1EncodeCollection v = + _Asn1EncodeCollection(<_Asn1Encode?>[oid]); + if (ext._critical!) { + v._encodableObjects.add(_DerBoolean(true)); + } + v._encodableObjects.add(ext._value); + vec._encodableObjects.add(_DerSequence(collection: v)); + }); + return _DerSequence(collection: vec); + } + + _X509Extension? getExtension(_DerObjectID oid) { + return (_extensions.containsKey(oid)) ? _extensions[oid] : null; + } +} + +class _X509Certificate extends _X509ExtensionBase { + _X509Certificate(_X509CertificateStructure? c) { + _c = c; + try { + final _Asn1Octet? str = getExtension(_DerObjectID('2.5.29.15')); + if (str != null) { + final _DerBitString bits = _DerBitString.getDetBitString( + _Asn1Stream(_StreamReader(str.getOctets())).readAsn1())!; + final List bytes = bits.getBytes()!; + final int length = (bytes.length * 8) - bits._extra!; + _keyUsage = + List.generate((length < 9) ? 9 : length, (i) => false); + for (int i = 0; i != length; i++) { + _keyUsage![i] = (bytes[i ~/ 8] & (0x80 >> (i % 8))) != 0; + } + } else { + _keyUsage = null; + } + } catch (e) { + throw ArgumentError.value( + e, 'ArgumentError', 'cannot construct KeyUsage'); + } + } + //Fields + _X509CertificateStructure? _c; + List? _keyUsage; + //Implementation + @override + _X509Extensions? getX509Extensions() { + return _c!.version == 3 ? _c!.tbsCertificate!.extensions : null; + } + + _CipherParameter getPublicKey() { + return createKey(_c!.subjectPublicKeyInfo!); + } + + _CipherParameter createKey(_PublicKeyInformation keyInfo) { + _CipherParameter result; + final _Algorithms algID = keyInfo.algorithm!; + final _DerObjectID algOid = algID._objectID!; + if (algOid._id == _PkcsObjectId.rsaEncryption._id || + algOid._id == _X509Objects.idEARsa._id) { + final _RsaPublicKey pubKey = + _RsaPublicKey.getPublicKey(keyInfo.getPublicKey())!; + result = _RsaKeyParam(false, pubKey.modulus, pubKey.publicExponent); + } else { + throw ArgumentError.value( + keyInfo, 'keyInfo', 'algorithm identifier in key not recognised'); + } + return result; + } +} + +class _X509CertificateStructure extends _Asn1Encode { + _X509CertificateStructure(_Asn1Sequence seq) { + _tbsCert = _SingnedCertificate.getCertificate(seq[0]); + _sigAlgID = _Algorithms.getAlgorithms(seq[1]); + _sig = _DerBitString.getDetBitString(seq[2]); + } + //Fields + _SingnedCertificate? _tbsCert; + _Algorithms? _sigAlgID; + _DerBitString? _sig; + //Properties + _SingnedCertificate? get tbsCertificate => _tbsCert; + int get version => _tbsCert!.version; + _DerInteger? get serialNumber => _tbsCert!.serialNumber; + _X509Name? get issuer => _tbsCert!.issuer; + _X509Time? get startDate => _tbsCert!.startDate; + _X509Time? get endDate => _tbsCert!.endDate; + _X509Name? get subject => _tbsCert!.subject; + _PublicKeyInformation? get subjectPublicKeyInfo => + _tbsCert!.subjectPublicKeyInfo; + _Algorithms? get signatureAlgorithm => _sigAlgID; + _DerBitString? get signature => _sig; + //Implementation + static _X509CertificateStructure? getInstance(dynamic obj) { + if (obj is _X509CertificateStructure) { + return obj; + } + if (obj != null) { + return _X509CertificateStructure(_Asn1Sequence.getSequence(obj)!); + } + return null; + } + + @override + _Asn1 getAsn1() { + return _DerSequence(array: <_Asn1Encode?>[_tbsCert, _sigAlgID, _sig]); + } +} + +class _SingnedCertificate extends _Asn1Encode { + _SingnedCertificate(_Asn1Sequence sequence) { + int seqStart = 0; + _sequence = sequence; + if (sequence[0] is _DerTag || sequence[0] is _Asn1Tag) { + _version = _DerInteger.getNumberFromTag(sequence[0] as _Asn1Tag, true); + } else { + seqStart = -1; + _version = _DerInteger(_bigIntToBytes(BigInt.from(0))); + } + _serialNumber = _DerInteger.getNumber(sequence[seqStart + 1]); + _signature = _Algorithms.getAlgorithms(sequence[seqStart + 2]); + _issuer = _X509Name.getName(sequence[seqStart + 3]); + final _Asn1Sequence dates = sequence[seqStart + 4] as _Asn1Sequence; + _startDate = _X509Time.getTime(dates[0]); + _endDate = _X509Time.getTime(dates[1]); + _subject = _X509Name.getName(sequence[seqStart + 5]); + _publicKeyInformation = + _PublicKeyInformation.getPublicKeyInformation(sequence[seqStart + 6]); + for (int extras = sequence.count - (seqStart + 6) - 1; + extras > 0; + extras--) { + final _Asn1Tag extra = sequence[seqStart + 6 + extras] as _Asn1Tag; + switch (extra.tagNumber) { + case 1: + _issuerID = _DerBitString.getDerBitStringFromTag(extra, false); + break; + case 2: + _subjectID = _DerBitString.getDerBitStringFromTag(extra, false); + break; + case 3: + _extensions = _X509Extensions.getInstance(extra); + break; + } + } + } + static _SingnedCertificate? getCertificate(dynamic obj) { + if (obj is _SingnedCertificate) { + return obj; + } + if (obj != null) { + return _SingnedCertificate(_Asn1Sequence.getSequence(obj)!); + } + return null; + } + + //Fields + _Asn1Sequence? _sequence; + _DerInteger? _version; + _DerInteger? _serialNumber; + _Algorithms? _signature; + _X509Name? _issuer; + _X509Time? _startDate; + _X509Time? _endDate; + _X509Name? _subject; + _PublicKeyInformation? _publicKeyInformation; + _DerBitString? _issuerID; + _DerBitString? _subjectID; + _X509Extensions? _extensions; + //Properties + int get version => _version!.value.toSigned(32).toInt() + 1; + _DerInteger? get serialNumber => _serialNumber; + _Algorithms? get signature => _signature; + _X509Name? get issuer => _issuer; + _X509Time? get startDate => _startDate; + _X509Time? get endDate => _endDate; + _X509Name? get subject => _subject; + _PublicKeyInformation? get subjectPublicKeyInfo => _publicKeyInformation; + _DerBitString? get issuerUniqueID => _issuerID; + _DerBitString? get subjectUniqueID => _subjectID; + _X509Extensions? get extensions => _extensions; + //Implementation + @override + _Asn1? getAsn1() { + return _sequence; + } +} + +class _PublicKeyInformation extends _Asn1Encode { + _PublicKeyInformation(_Algorithms algorithms, _Asn1Encode publicKey) { + _publicKey = _DerBitString.fromAsn1(publicKey); + _algorithms = algorithms; + } + _PublicKeyInformation.fromSequence(_Asn1Sequence sequence) { + if (sequence.count != 2) { + throw ArgumentError.value( + sequence, 'sequence', 'Invalid length in sequence'); + } + _algorithms = _Algorithms.getAlgorithms(sequence[0]); + _publicKey = _DerBitString.getDetBitString(sequence[1]); + } + static _PublicKeyInformation? getPublicKeyInformation(dynamic obj) { + if (obj is _PublicKeyInformation) { + return obj; + } + if (obj != null) { + return _PublicKeyInformation.fromSequence( + _Asn1Sequence.getSequence(obj)!); + } + return null; + } + + _Algorithms? _algorithms; + _DerBitString? _publicKey; + _Algorithms? get algorithm => _algorithms; + _DerBitString? get publicKey => _publicKey; + //Implementation + _Asn1? getPublicKey() { + return _Asn1Stream(_StreamReader(_publicKey!.getBytes())).readAsn1(); + } + + @override + _Asn1 getAsn1() { + return _DerSequence(array: <_Asn1Encode?>[_algorithms, _publicKey]); + } +} + +class _RsaPublicKey extends _Asn1Encode { + _RsaPublicKey(BigInt? modulus, BigInt? publicExponent) { + _modulus = modulus; + _publicExponent = publicExponent; + } + _RsaPublicKey.fromSequence(_Asn1Sequence sequence) { + _modulus = _DerInteger.getNumber(sequence[0])!.positiveValue; + _publicExponent = _DerInteger.getNumber(sequence[1])!.positiveValue; + } + BigInt? _modulus; + BigInt? _publicExponent; + BigInt? get modulus => _modulus; + BigInt? get publicExponent => _publicExponent; + static _RsaPublicKey? getPublicKey(dynamic obj) { + _RsaPublicKey? result; + if (obj == null || obj is _RsaPublicKey) { + result = obj; + } else if (obj is _Asn1Sequence) { + result = _RsaPublicKey.fromSequence(obj); + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid entry'); + } + return result; + } + + @override + _Asn1 getAsn1() { + return _DerSequence(array: <_Asn1Encode>[ + _DerInteger.fromNumber(modulus), + _DerInteger.fromNumber(publicExponent) + ]); + } +} + +class _SubjectKeyID extends _Asn1Encode { + _SubjectKeyID(dynamic obj) { + if (obj is _Asn1Octet) { + _bytes = obj.getOctets(); + } else if (obj is _PublicKeyInformation) { + _bytes = getDigest(obj); + } + } + + List? _bytes; + //Implementation + static List getDigest(_PublicKeyInformation publicKey) { + return sha1.convert(publicKey.publicKey!._data as List).bytes; + } + + @override + _Asn1 getAsn1() { + return _DerOctet(_bytes!); + } +} + +class _X509CertificateParser { + _X509CertificateParser() {} + //Fields + _Asn1Set? _sData; + int? _sDataObjectCount; + _StreamReader? _currentStream; + //Implementation + _X509Certificate? readCertificate(_StreamReader inStream) { + if (_currentStream == null) { + _currentStream = inStream; + _sData = null; + _sDataObjectCount = 0; + } else if (_currentStream != inStream) { + _currentStream = inStream; + _sData = null; + _sDataObjectCount = 0; + } + if (_sData != null) { + if (_sDataObjectCount != _sData!._objects.length) { + return getCertificate(); + } + _sData = null; + _sDataObjectCount = 0; + return null; + } + final _PushStream pis = _PushStream(inStream); + final int tag = pis.readByte()!; + if (tag < 0) { + return null; + } + pis.unread(tag); + return readDerCertificate(_Asn1Stream(pis)); + } + + _X509Certificate? getCertificate() { + if (_sData != null) { + while (_sDataObjectCount! < _sData!._objects.length) { + final dynamic obj = _sData![_sDataObjectCount!]; + _sDataObjectCount = _sDataObjectCount! + 1; + if (obj is _Asn1Sequence) { + return createX509Certificate( + _X509CertificateStructure.getInstance(obj)); + } + } + } + return null; + } + + _X509Certificate? readDerCertificate(_Asn1Stream dIn) { + final dynamic seq = dIn.readAsn1(); + if (seq != null && seq is _Asn1Sequence) { + if (seq.count > 1 && seq[0] is _DerObjectID) { + if ((seq[0] as _DerObjectID)._id == _PkcsObjectId.signedData._id) { + if (seq.count >= 2) { + final _Asn1Sequence signedSequence = + _Asn1Sequence.getSequence(seq[1] as _Asn1Tag?, true)!; + bool isContinue = true; + signedSequence._objects!.forEach((o) { + if (isContinue && o is _Asn1Tag) { + if (o.tagNumber == 0) { + _sData = _Asn1Set.getAsn1Set(o, false); + isContinue = false; + } + } + }); + } + return getCertificate(); + } + } + } + return createX509Certificate(_X509CertificateStructure.getInstance(seq)); + } + + _X509Certificate createX509Certificate(_X509CertificateStructure? c) { + return _X509Certificate(c); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/x509/x509_name.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/x509/x509_name.dart new file mode 100644 index 000000000..4c43fb2b7 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/x509/x509_name.dart @@ -0,0 +1,204 @@ +part of pdf; + +class _X509Name extends _Asn1Encode { + _X509Name(_Asn1Sequence sequence) { + _initialize(); + _sequence = sequence; + sequence._objects!.forEach((encode) { + final _Asn1Set asn1Set = _Asn1Set.getAsn1Set(encode.getAsn1())!; + for (int i = 0; i < asn1Set._objects.length; i++) { + final _Asn1Sequence asn1Sequence = + _Asn1Sequence.getSequence(asn1Set[i]!.getAsn1())!; + if (asn1Sequence.count != 2) { + throw ArgumentError.value( + sequence, 'sequence', 'Invalid length in sequence'); + } + _ordering.add(_DerObjectID.getID(asn1Sequence[0]!.getAsn1())); + final _Asn1? asn1 = asn1Sequence[1]!.getAsn1(); + if (asn1 is _IAsn1String) { + String value = (asn1 as _IAsn1String).getString()!; + if (value.startsWith("#")) { + value = '\\' + value; + } + _values.add(value); + } + _added.add(i != 0); + } + }); + } + static _DerObjectID c = _DerObjectID("2.5.4.6"); + static _DerObjectID o = _DerObjectID("2.5.4.10"); + static _DerObjectID ou = _DerObjectID("2.5.4.11"); + static _DerObjectID t = _DerObjectID("2.5.4.12"); + static _DerObjectID cn = _DerObjectID("2.5.4.3"); + static _DerObjectID street = _DerObjectID("2.5.4.9"); + static _DerObjectID serialNumber = _DerObjectID("2.5.4.5"); + static _DerObjectID l = _DerObjectID("2.5.4.7"); + static _DerObjectID st = _DerObjectID("2.5.4.8"); + static _DerObjectID surname = _DerObjectID("2.5.4.4"); + static _DerObjectID givenName = _DerObjectID("2.5.4.42"); + static _DerObjectID initials = _DerObjectID("2.5.4.43"); + static _DerObjectID generation = _DerObjectID("2.5.4.44"); + static _DerObjectID uniqueIdentifier = _DerObjectID("2.5.4.45"); + static _DerObjectID businessCategory = _DerObjectID("2.5.4.15"); + static _DerObjectID postalCode = _DerObjectID("2.5.4.17"); + static _DerObjectID dnQualifier = _DerObjectID("2.5.4.46"); + static _DerObjectID pseudonym = _DerObjectID("2.5.4.65"); + static _DerObjectID dateOfBirth = _DerObjectID("1.3.6.1.5.5.7.9.1"); + static _DerObjectID placeOfBirth = _DerObjectID("1.3.6.1.5.5.7.9.2"); + static _DerObjectID gender = _DerObjectID("1.3.6.1.5.5.7.9.3"); + static _DerObjectID countryOfCitizenship = _DerObjectID("1.3.6.1.5.5.7.9.4"); + static _DerObjectID countryOfResidence = _DerObjectID("1.3.6.1.5.5.7.9.5"); + static _DerObjectID nameAtBirth = _DerObjectID("1.3.36.8.3.14"); + static _DerObjectID postalAddress = _DerObjectID("2.5.4.16"); + static _DerObjectID telephoneNumber = _X509Objects.telephoneNumberID; + static _DerObjectID emailAddress = _PkcsObjectId.pkcs9AtEmailAddress; + static _DerObjectID unstructuredName = _PkcsObjectId.pkcs9AtUnstructuredName; + static _DerObjectID unstructuredAddress = + _PkcsObjectId.pkcs9AtUnstructuredAddress; + static _DerObjectID dc = _DerObjectID("0.9.2342.19200300.100.1.25"); + static _DerObjectID uid = _DerObjectID("0.9.2342.19200300.100.1.1"); + List _ordering = []; + List _values = []; + List _added = []; + Map<_DerObjectID, String> _defaultSymbols = <_DerObjectID, String>{}; + _Asn1Sequence? _sequence; + static _X509Name? getName(dynamic obj, [bool? isExplicit]) { + _X509Name? result; + if (obj is _Asn1Tag && isExplicit != null) { + result = getName(_Asn1Sequence.getSequence(obj, isExplicit)); + } else { + if (obj == null || obj is _X509Name) { + result = obj; + } else if (obj != null) { + result = _X509Name(_Asn1Sequence.getSequence(obj)!); + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid entry'); + } + } + return result; + } + + void _initialize() { + _defaultSymbols[c] = "C"; + _defaultSymbols[o] = "O"; + _defaultSymbols[t] = "T"; + _defaultSymbols[ou] = "OU"; + _defaultSymbols[cn] = "CN"; + _defaultSymbols[l] = "L"; + _defaultSymbols[st] = "ST"; + _defaultSymbols[serialNumber] = "SERIALNUMBER"; + _defaultSymbols[emailAddress] = "E"; + _defaultSymbols[dc] = "DC"; + _defaultSymbols[uid] = "UID"; + _defaultSymbols[street] = "STREET"; + _defaultSymbols[surname] = "SURNAME"; + _defaultSymbols[givenName] = "GIVENNAME"; + _defaultSymbols[initials] = "INITIALS"; + _defaultSymbols[generation] = "GENERATION"; + _defaultSymbols[unstructuredAddress] = "unstructuredAddress"; + _defaultSymbols[unstructuredName] = "unstructuredName"; + _defaultSymbols[uniqueIdentifier] = "UniqueIdentifier"; + _defaultSymbols[dnQualifier] = "DN"; + _defaultSymbols[pseudonym] = "Pseudonym"; + _defaultSymbols[postalAddress] = "PostalAddress"; + _defaultSymbols[nameAtBirth] = "NameAtBirth"; + _defaultSymbols[countryOfCitizenship] = "CountryOfCitizenship"; + _defaultSymbols[countryOfResidence] = "CountryOfResidence"; + _defaultSymbols[gender] = "Gender"; + _defaultSymbols[placeOfBirth] = "PlaceOfBirth"; + _defaultSymbols[dateOfBirth] = "DateOfBirth"; + _defaultSymbols[postalCode] = "PostalCode"; + _defaultSymbols[businessCategory] = "BusinessCategory"; + _defaultSymbols[telephoneNumber] = "TelephoneNumber"; + } + + @override + _Asn1? getAsn1() { + if (_sequence == null) { + final _Asn1EncodeCollection collection1 = _Asn1EncodeCollection(); + _Asn1EncodeCollection collection2 = _Asn1EncodeCollection(); + _DerObjectID? lstOid; + for (int i = 0; i != _ordering.length; i++) { + final _DerObjectID? oid = _ordering[i]; + if (lstOid != null && !(_added[i])) { + collection1._encodableObjects.add(_DerSet(collection: collection2)); + collection2 = _Asn1EncodeCollection(); + } + lstOid = oid; + } + collection1._encodableObjects.add(_DerSet(collection: collection2)); + _sequence = _DerSequence(collection: collection1); + } + return _sequence; + } + + String getString(bool isReverse, Map<_DerObjectID, String> symbols) { + List components = []; + String result = ''; + for (int i = 0; i < _ordering.length; i++) { + if (_added[i]) { + result = '+'; + result = appendValue( + result, symbols, _ordering[i] as _DerObjectID?, _values[i]); + } else { + result = ''; + result = appendValue( + result, symbols, _ordering[i] as _DerObjectID?, _values[i]); + components.add(result); + } + } + if (isReverse) { + components = components.reversed.toList(); + } + String buf = ''; + if (components.length > 0) { + buf += components[0].toString(); + for (int i = 1; i < components.length; ++i) { + buf += ','; + buf += components[i].toString(); + } + } + return buf; + } + + String appendValue(String builder, Map<_DerObjectID, String> symbols, + _DerObjectID? id, String value) { + final String? symbol = symbols[id!]; + if (symbol != null) { + builder += symbol; + } else { + builder += id._id!; + } + builder += '='; + int index = builder.length; + builder += value; + int end = builder.length; + if (value.startsWith('\\#')) { + index += 2; + } + while (index != end) { + if ((builder[index] == ',') || + (builder[index] == '"') || + (builder[index] == '\\') || + (builder[index] == '+') || + (builder[index] == '=') || + (builder[index] == '<') || + (builder[index] == '>') || + (builder[index] == ';')) { + builder = builder.substring(0, index) + + '\\' + + builder.substring(index, builder.length); + index++; + end++; + } + index++; + } + return builder; + } + + @override + String toString() { + return getString(false, _defaultSymbols); + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/x509/x509_time.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/x509/x509_time.dart new file mode 100644 index 000000000..2102a6d5b --- /dev/null +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/x509/x509_time.dart @@ -0,0 +1,53 @@ +part of pdf; + +class _X509Time extends _Asn1Encode { + _X509Time(_Asn1 time) { + _time = time; + } + _Asn1? _time; + static _X509Time? getTime(dynamic obj) { + _X509Time? result; + if (obj == null || obj is _X509Time) { + result = obj; + } else if (obj is _DerUtcTime) { + result = _X509Time(obj); + } else if (obj is _GeneralizedTime) { + result = _X509Time(obj); + } else { + throw ArgumentError.value(obj, 'obj', 'Invalid entry'); + } + return result; + } + + DateTime? toDateTime() { + DateTime? result; + try { + if (_time is _DerUtcTime) { + result = (_time as _DerUtcTime).toAdjustedDateTime; + } else if (_time is _GeneralizedTime) { + result = (_time as _GeneralizedTime).toDateTime(); + } else { + result = DateTime.now(); + } + } catch (e) { + throw ArgumentError.value(result, 'DateTime', 'Invalid entry'); + } + return result; + } + + @override + _Asn1? getAsn1() { + return _time; + } + + @override + String toString() { + if (_time is _DerUtcTime) { + return (_time as _DerUtcTime).adjustedTimeString; + } else if (_time is _GeneralizedTime) { + return (_time as _GeneralizedTime)._time; + } else { + return ''; + } + } +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/enum.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/enum.dart index 4c383ed35..4a74b76ab 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/enum.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/enum.dart @@ -1,5 +1,17 @@ part of pdf; +/// Specifies the encryption option. +enum PdfEncryptionOptions { + /// To encrypt all the document contents. + encryptAllContents, + + /// To encrypt all the document contents except metadata. + encryptAllContentsExceptMetadata, + + /// To encrypt atttachment files only. + encryptOnlyAttachments +} + /// Specifies the type of encryption algorithm. enum PdfEncryptionAlgorithm { /// RC4 encryption algorithm - 40-bit key. @@ -59,3 +71,39 @@ enum _KeySize { /// 256 Bit. bits256 } + +/// Specifies the available permissions on certificated document. +enum PdfCertificationFlags { + /// Restrict any changes to the document. + forbidChanges, + + /// Only allow form fill-in actions on this document. + allowFormFill, + + /// Only allow commenting and form fill-in actions on this document. + allowComments +} + +/// Specifies the cryptographic standard. +enum CryptographicStandard { + /// Cryptographic Message Syntax + cms, + + /// CMS Advanced Electronic Signatures + cades +} + +/// Specifies the digestion algorithm. +enum DigestAlgorithm { + /// SHA1 message digest algorithm + sha1, + + /// SHA256 message digest algorithm + sha256, + + /// SHA384 message digest algorithm + sha384, + + /// SHA512 message digest algorithm + sha512 +} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_encryptor.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_encryptor.dart index 251ce7a0f..27bffe190 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_encryptor.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_encryptor.dart @@ -7,81 +7,82 @@ class _PdfEncryptor { } //Fields - int _stringLength; - int _revisionNumber40Bit; - int _revisionNumber128Bit; - int _ownerLoopNum2; - int _ownerLoopNum; - List paddingBytes; - int _bytesAmount; - int _permissionSet; - int _permissionCleared; - int _permissionRevisionTwoMask; - int _revisionNumberOut; - int _versionNumberOut; - int _permissionValue; - List _randomBytes; - int _key40; - int _key128; - int _key256; - int _randomBytesAmount; - int _newKeyOffset; - - bool _encrypt; - bool _isChanged; - bool _hasComputedPasswordValues; - PdfEncryptionAlgorithm encryptionAlgorithm; - List _permissions; - int _revision; - String _userPassword; - String _ownerPassword; - List _ownerPasswordOut; - List _userPasswordOut; - List _encryptionKey; - int keyLength; - List customArray; - List _permissionFlagValues; - List _fileEncryptionKey; - List _userEncryptionKeyOut; - List _ownerEncryptionKeyOut; - List _permissionFlag; - List _userRandomBytes; - List _ownerRandomBytes; - bool _encryptMetadata; - bool _encryptOnlyAttachment; + int? _stringLength; + int? _revisionNumber40Bit; + int? _revisionNumber128Bit; + int? _ownerLoopNum2; + int? _ownerLoopNum; + List? paddingBytes; + int? _bytesAmount; + int? _permissionSet; + int? _permissionCleared; + int? _permissionRevisionTwoMask; + int? _revisionNumberOut; + int? _versionNumberOut; + int? _permissionValue; + List? _randomBytes; + int? _key40; + int? _key128; + int? _key256; + int? _randomBytesAmount; + int? _newKeyOffset; + + bool? _encrypt; + bool? _isChanged; + bool? _hasComputedPasswordValues; + PdfEncryptionAlgorithm? encryptionAlgorithm; + List? _permissions; + int? _revision; + String? _userPassword; + String? _ownerPassword; + List? _ownerPasswordOut; + List? _userPasswordOut; + List? _encryptionKey; + int? keyLength; + List? customArray; + List? _permissionFlagValues; + List? _fileEncryptionKey; + List? _userEncryptionKeyOut; + List? _ownerEncryptionKeyOut; + List? _permissionFlag; + List? _userRandomBytes; + List? _ownerRandomBytes; + bool? _encryptMetadata; + bool? _encryptOnlyAttachment; + late PdfEncryptionOptions _encryptionOptions; //Properties - int get revisionNumber { + int? get revisionNumber { return _revision == 0 ? (encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x40Bit - ? (_revisionNumberOut > 2 + ? (_revisionNumberOut! > 2 ? _revisionNumberOut : _revisionNumber40Bit) : _revisionNumber128Bit) : _revision; } - List get randomBytes { + List? get randomBytes { if (_randomBytes == null) { final Random random = Random.secure(); _randomBytes = - List.generate(_randomBytesAmount, (i) => random.nextInt(256)); + List.generate(_randomBytesAmount!, (i) => random.nextInt(256)); } return _randomBytes; } List get permissions { - return _permissions; + return _permissions!; } set permissions(List value) { _isChanged = true; _permissions = value; - _permissionValue = (_getPermissionValue(_permissions) | _permissionSet) & - _permissionCleared; - if (revisionNumber > 2) { - _permissionValue &= _permissionRevisionTwoMask; + _permissionValue = (_getPermissionValue(_permissions!) | _permissionSet!) & + _permissionCleared!; + if (revisionNumber! > 2) { + _permissionValue = _permissionValue! & _permissionRevisionTwoMask!; } _hasComputedPasswordValues = false; } @@ -90,9 +91,9 @@ class _PdfEncryptor { final List perm = permissions; final bool bEncrypt = (perm.length == 1 && !perm.contains(PdfPermissionsFlags.none)) || - _userPassword.isNotEmpty || - _ownerPassword.isNotEmpty; - return !_encrypt ? false : bEncrypt; + _userPassword!.isNotEmpty || + _ownerPassword!.isNotEmpty; + return !_encrypt! ? false : bEncrypt; } set encrypt(bool value) { @@ -100,11 +101,10 @@ class _PdfEncryptor { } String get userPassword { - return _userPassword; + return _userPassword!; } set userPassword(String value) { - ArgumentError.checkNotNull(value); if (_userPassword != value) { _isChanged = true; _userPassword = value; @@ -113,11 +113,13 @@ class _PdfEncryptor { } String get ownerPassword { - return _ownerPassword; + if (_encryptOnlyAttachment!) { + return ''; + } + return _ownerPassword!; } set ownerPassword(String value) { - ArgumentError.checkNotNull(value); if (_ownerPassword != value) { _isChanged = true; _ownerPassword = value; @@ -125,28 +127,36 @@ class _PdfEncryptor { } } + bool get encryptOnlyAttachment { + return _encryptOnlyAttachment!; + } + + set encryptOnlyAttachment(bool value) { + _encryptOnlyAttachment = value; + _hasComputedPasswordValues = false; + } + bool get encryptMetadata { - return _encryptMetadata; + return _encryptMetadata!; } set encryptMetadata(bool value) { - ArgumentError.checkNotNull(value); _hasComputedPasswordValues = false; _encryptMetadata = value; } - List get ownerPasswordOut { + List? get ownerPasswordOut { _initializeData(); return _ownerPasswordOut; } - List get userPasswordOut { + List? get userPasswordOut { _initializeData(); return _userPasswordOut; } _PdfArray get fileID { - final _PdfString str = _PdfString.fromBytes(randomBytes); + final _PdfString str = _PdfString.fromBytes(randomBytes!); final _PdfArray array = _PdfArray(); array._add(str); array._add(str); @@ -189,6 +199,7 @@ class _PdfEncryptor { 0x000800 ]; permissions = [PdfPermissionsFlags.none]; + _encryptionOptions = PdfEncryptionOptions.encryptAllContents; paddingBytes = [ 40, 191, @@ -223,7 +234,7 @@ class _PdfEncryptor { 105, 122 ]; - customArray = List.filled(_bytesAmount, 0, growable: true); + customArray = List.filled(_bytesAmount!, 0, growable: true); _isChanged = false; _hasComputedPasswordValues = false; _encryptMetadata = true; @@ -231,7 +242,7 @@ class _PdfEncryptor { } void _initializeData() { - if (!_hasComputedPasswordValues) { + if (!_hasComputedPasswordValues!) { if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit) { _userPasswordOut = _create256BitUserPassword(); _ownerPasswordOut = _create256BitOwnerPassword(); @@ -247,7 +258,7 @@ class _PdfEncryptor { _permissionFlag = _createPermissionFlag(); } else { _ownerPasswordOut = _createOwnerPassword(); - _encryptionKey = _createEncryptionKey(userPassword, _ownerPasswordOut); + _encryptionKey = _createEncryptionKey(userPassword, _ownerPasswordOut!); _userPasswordOut = _createUserPassword(); } _hasComputedPasswordValues = true; @@ -263,21 +274,21 @@ class _PdfEncryptor { List _create40BitUserPassword() { ArgumentError.checkNotNull(_encryptionKey); return _encryptDataByCustom( - List.from(paddingBytes), _encryptionKey, _encryptionKey.length); + List.from(paddingBytes!), _encryptionKey, _encryptionKey!.length); } List _create128BitUserPassword() { ArgumentError.checkNotNull(_encryptionKey); final List data = []; - data.addAll(paddingBytes); - data.addAll(randomBytes); + data.addAll(paddingBytes!); + data.addAll(randomBytes!); final List resultBytes = md5.convert(data).bytes; final List dataForCustom = - List.generate(_randomBytesAmount, (i) => resultBytes[i]); + List.generate(_randomBytesAmount!, (i) => resultBytes[i]); List dataFromCustom = _encryptDataByCustom( - dataForCustom, _encryptionKey, _encryptionKey.length); - for (int i = 1; i < _ownerLoopNum2; i++) { - final List currentKey = _getKeyWithOwnerPassword(_encryptionKey, i); + dataForCustom, _encryptionKey, _encryptionKey!.length); + for (int i = 1; i < _ownerLoopNum2!; i++) { + final List currentKey = _getKeyWithOwnerPassword(_encryptionKey!, i); dataFromCustom = _encryptDataByCustom(dataFromCustom, currentKey, currentKey.length); } @@ -287,28 +298,27 @@ class _PdfEncryptor { List _create256BitUserPassword() { final Random random = Random.secure(); _userRandomBytes = - List.generate(_randomBytesAmount, (i) => random.nextInt(256)); - final List userPasswordBytes = utf8.encode(_userPassword); + List.generate(_randomBytesAmount!, (i) => random.nextInt(256)); + final List userPasswordBytes = utf8.encode(_userPassword!); final List hash = []; hash.addAll(userPasswordBytes); - hash.addAll(List.generate(8, (i) => _userRandomBytes[i])); + hash.addAll(List.generate(8, (i) => _userRandomBytes![i])); final List userPasswordOut = []; userPasswordOut.addAll(sha256.convert(hash).bytes); - userPasswordOut.addAll(_userRandomBytes); + userPasswordOut.addAll(_userRandomBytes!); return userPasswordOut; } List _createOwnerPassword() { - final String password = ownerPassword == null || ownerPassword.isEmpty - ? userPassword - : ownerPassword; + final String password = + ownerPassword.isEmpty ? userPassword : ownerPassword; final List customKey = _getKeyFromOwnerPassword(password); final List userPasswordBytes = _padTrancateString(utf8.encode(userPassword)); List dataFromCustom = _encryptDataByCustom(userPasswordBytes, customKey, customKey.length); - if (revisionNumber > 2) { - for (int i = 1; i < _ownerLoopNum2; i++) { + if (revisionNumber! > 2) { + for (int i = 1; i < _ownerLoopNum2!; i++) { final List currentKey = _getKeyWithOwnerPassword(customKey, i); dataFromCustom = _encryptDataByCustom(dataFromCustom, currentKey, currentKey.length); @@ -320,54 +330,52 @@ class _PdfEncryptor { List _create256BitOwnerPassword() { final Random random = Random.secure(); _ownerRandomBytes = - List.generate(_randomBytesAmount, (i) => random.nextInt(256)); - final String password = ownerPassword == null || ownerPassword.isEmpty - ? userPassword - : ownerPassword; + List.generate(_randomBytesAmount!, (i) => random.nextInt(256)); + final String password = + ownerPassword.isEmpty ? userPassword : ownerPassword; final List ownerPasswordBytes = utf8.encode(password); final List hash = []; hash.addAll(ownerPasswordBytes); - hash.addAll(List.generate(8, (i) => _ownerRandomBytes[i])); - hash.addAll(_userPasswordOut); + hash.addAll(List.generate(8, (i) => _ownerRandomBytes![i])); + hash.addAll(_userPasswordOut!); final List ownerPasswordOut = []; ownerPasswordOut.addAll(sha256.convert(hash).bytes); - ownerPasswordOut.addAll(_ownerRandomBytes); + ownerPasswordOut.addAll(_ownerRandomBytes!); return ownerPasswordOut; } - List _createUserEncryptionKey() { + List? _createUserEncryptionKey() { final List hash = []; hash.addAll(utf8.encode(userPassword)); - hash.addAll(List.generate(8, (i) => _userRandomBytes[i + 8])); + hash.addAll(List.generate(8, (i) => _userRandomBytes![i + 8])); final List hashBytes = sha256.convert(hash).bytes; return _AesCipherNoPadding(true, hashBytes) - ._processBlock(_fileEncryptionKey, 0, _fileEncryptionKey.length); + .processBlock(_fileEncryptionKey, 0, _fileEncryptionKey!.length); } - List _createOwnerEncryptionKey() { - final String password = ownerPassword == null || ownerPassword.isEmpty - ? userPassword - : ownerPassword; + List? _createOwnerEncryptionKey() { + final String password = + ownerPassword.isEmpty ? userPassword : ownerPassword; final List hash = []; hash.addAll(utf8.encode(password)); - hash.addAll(List.generate(8, (i) => _ownerRandomBytes[i + 8])); - hash.addAll(_userPasswordOut); + hash.addAll(List.generate(8, (i) => _ownerRandomBytes![i + 8])); + hash.addAll(_userPasswordOut!); final List hashBytes = sha256.convert(hash).bytes; return _AesCipherNoPadding(true, hashBytes) - ._processBlock(_fileEncryptionKey, 0, _fileEncryptionKey.length); + .processBlock(_fileEncryptionKey, 0, _fileEncryptionKey!.length); } - List _createPermissionFlag() { + List? _createPermissionFlag() { final List permissionFlagBytes = [ - _permissionValue.toUnsigned(8).toInt(), - (_permissionValue >> 8).toUnsigned(8).toInt(), - (_permissionValue >> 16).toUnsigned(8).toInt(), - (_permissionValue >> 24).toUnsigned(8).toInt(), + _permissionValue!.toUnsigned(8).toInt(), + (_permissionValue! >> 8).toUnsigned(8).toInt(), + (_permissionValue! >> 16).toUnsigned(8).toInt(), + (_permissionValue! >> 24).toUnsigned(8).toInt(), 255, 255, 255, 255, - 84, + encryptMetadata ? 84 : 70, 97, 100, 98, @@ -376,12 +384,12 @@ class _PdfEncryptor { 98, 98 ]; - return _AesCipherNoPadding(true, _fileEncryptionKey) - ._processBlock(permissionFlagBytes, 0, permissionFlagBytes.length); + return _AesCipherNoPadding(true, _fileEncryptionKey!) + .processBlock(permissionFlagBytes, 0, permissionFlagBytes.length); } void _createAcrobatX256BitUserPassword() { - final List userPasswordBytes = utf8.encode(_userPassword); + final List userPasswordBytes = utf8.encode(_userPassword!); final Random random = Random.secure(); final List userValidationSalt = List.generate(8, (i) => random.nextInt(256)); @@ -392,21 +400,20 @@ class _PdfEncryptor { hash.addAll(userValidationSalt); hash = _acrobatXComputeHash(hash, userPasswordBytes, null); _userPasswordOut = []; - _userPasswordOut.addAll(hash); - _userPasswordOut.addAll(userValidationSalt); - _userPasswordOut.addAll(userKeySalt); + _userPasswordOut!.addAll(hash); + _userPasswordOut!.addAll(userValidationSalt); + _userPasswordOut!.addAll(userKeySalt); hash = []; hash.addAll(userPasswordBytes); hash.addAll(userKeySalt); hash = _acrobatXComputeHash(hash, userPasswordBytes, null); _userEncryptionKeyOut = _AesCipherNoPadding(true, hash) - ._processBlock(_fileEncryptionKey, 0, _fileEncryptionKey.length); + .processBlock(_fileEncryptionKey, 0, _fileEncryptionKey!.length); } void _createAcrobatX256BitOwnerPassword() { - final String password = ownerPassword == null || ownerPassword.isEmpty - ? userPassword - : ownerPassword; + final String password = + ownerPassword.isEmpty ? userPassword : ownerPassword; final List ownerPasswordBytes = utf8.encode(password); final Random random = Random.secure(); final List ownerValidationSalt = @@ -416,19 +423,19 @@ class _PdfEncryptor { final List owenrPasswordOut = []; owenrPasswordOut.addAll(ownerPasswordBytes); owenrPasswordOut.addAll(ownerValidationSalt); - owenrPasswordOut.addAll(_userPasswordOut); + owenrPasswordOut.addAll(_userPasswordOut!); _ownerPasswordOut = []; - _ownerPasswordOut.addAll(_acrobatXComputeHash( + _ownerPasswordOut!.addAll(_acrobatXComputeHash( owenrPasswordOut, ownerPasswordBytes, _userPasswordOut)); - _ownerPasswordOut.addAll(ownerValidationSalt); - _ownerPasswordOut.addAll(ownerKeySalt); + _ownerPasswordOut!.addAll(ownerValidationSalt); + _ownerPasswordOut!.addAll(ownerKeySalt); List hash = []; hash.addAll(ownerPasswordBytes); hash.addAll(ownerKeySalt); - hash.addAll(_userPasswordOut); + hash.addAll(_userPasswordOut!); hash = _acrobatXComputeHash(hash, ownerPasswordBytes, _userPasswordOut); _ownerEncryptionKeyOut = _AesCipherNoPadding(true, hash) - ._processBlock(_fileEncryptionKey, 0, _fileEncryptionKey.length); + .processBlock(_fileEncryptionKey, 0, _fileEncryptionKey!.length); } void _createFileEncryptionKey() { @@ -436,62 +443,63 @@ class _PdfEncryptor { _fileEncryptionKey = List.generate(32, (i) => random.nextInt(256)); } - List _encryptDataByCustom(List data, List key, int keyLength) { + List _encryptDataByCustom( + List data, List? key, int keyLength) { final List buffer = List.filled(data.length, 0, growable: true); _recreateCustomArray(key, keyLength); keyLength = data.length; int tmp1 = 0; int tmp2 = 0; for (int i = 0; i < keyLength; i++) { - tmp1 = (tmp1 + 1) % _bytesAmount; - tmp2 = (tmp2 + customArray[tmp1]) % _bytesAmount; - final int temp = customArray[tmp1]; - customArray[tmp1] = customArray[tmp2]; - customArray[tmp2] = temp; - final int byteXor = - customArray[(customArray[tmp1] + customArray[tmp2]) % _bytesAmount]; + tmp1 = (tmp1 + 1) % _bytesAmount!; + tmp2 = (tmp2 + customArray![tmp1]) % _bytesAmount!; + final int temp = customArray![tmp1]; + customArray![tmp1] = customArray![tmp2]; + customArray![tmp2] = temp; + final int byteXor = customArray![ + (customArray![tmp1] + customArray![tmp2]) % _bytesAmount!]; buffer[i] = (data[i] ^ byteXor).toUnsigned(8).toInt(); } return buffer; } - void _recreateCustomArray(List key, int keyLength) { + void _recreateCustomArray(List? key, int keyLength) { final List tempArray = - List.filled(_bytesAmount, 0, growable: true); - for (int i = 0; i < _bytesAmount; i++) { - tempArray[i] = key[i % keyLength]; - customArray[i] = i.toUnsigned(8).toInt(); + List.filled(_bytesAmount!, 0, growable: true); + for (int i = 0; i < _bytesAmount!; i++) { + tempArray[i] = key![i % keyLength]; + customArray![i] = i.toUnsigned(8).toInt(); } int temp = 0; - for (int i = 0; i < _bytesAmount; i++) { - temp = (temp + customArray[i] + tempArray[i]) % _bytesAmount; - final int tempByte = customArray[i]; - customArray[i] = customArray[temp]; - customArray[temp] = tempByte; + for (int i = 0; i < _bytesAmount!; i++) { + temp = (temp + customArray![i] + tempArray[i]) % _bytesAmount!; + final int tempByte = customArray![i]; + customArray![i] = customArray![temp]; + customArray![temp] = tempByte; } } List _getKeyFromOwnerPassword(String password) { final List passwordBytes = _padTrancateString(utf8.encode(password)); List currentHash = md5.convert(passwordBytes).bytes; - final int length = _getKeyLength(); - if (revisionNumber > 2) { - for (int i = 0; i < _ownerLoopNum; i++) { + final int? length = _getKeyLength(); + if (revisionNumber! > 2) { + for (int i = 0; i < _ownerLoopNum!; i++) { currentHash = md5 .convert(currentHash.length == length ? currentHash - : List.generate(length, (i) => currentHash[i])) + : List.generate(length!, (i) => currentHash[i])) .bytes; } } return currentHash.length == length ? currentHash - : List.generate(length, (i) => currentHash[i]); + : List.generate(length!, (i) => currentHash[i]); } - int _getKeyLength() { + int? _getKeyLength() { return keyLength != 0 - ? (keyLength ~/ 8) + ? (keyLength! ~/ 8) : (encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x40Bit ? _key40 : ((encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit || @@ -501,20 +509,18 @@ class _PdfEncryptor { } List _padTrancateString(List source) { - ArgumentError.checkNotNull(source); final List passwordBytes = []; if (source.isNotEmpty) { passwordBytes.addAll(source); } - if (source.length < _stringLength) { - passwordBytes.addAll( - paddingBytes.getRange(0, paddingBytes.length - passwordBytes.length)); + if (source.length < _stringLength!) { + passwordBytes.addAll(paddingBytes! + .getRange(0, paddingBytes!.length - passwordBytes.length)); } - return List.generate(_stringLength, (i) => passwordBytes[i]); + return List.generate(_stringLength!, (i) => passwordBytes[i]); } List _getKeyWithOwnerPassword(List originalKey, int index) { - ArgumentError.checkNotNull(originalKey); final List result = List.filled(originalKey.length, 0, growable: true); for (int i = 0; i < originalKey.length; i++) { @@ -525,43 +531,52 @@ class _PdfEncryptor { List _createEncryptionKey( String inputPassword, List ownerPasswordBytes) { - ArgumentError.checkNotNull(inputPassword); - ArgumentError.checkNotNull(ownerPasswordBytes); final List passwordBytes = _padTrancateString(utf8.encode(inputPassword)); final List encryptionKeyData = []; encryptionKeyData.addAll(passwordBytes); encryptionKeyData.addAll(ownerPasswordBytes); encryptionKeyData.addAll([ - _permissionValue.toUnsigned(8).toInt(), - (_permissionValue >> 8).toUnsigned(8).toInt(), - (_permissionValue >> 16).toUnsigned(8).toInt(), - (_permissionValue >> 24).toUnsigned(8).toInt() + _permissionValue!.toUnsigned(8).toInt(), + (_permissionValue! >> 8).toUnsigned(8).toInt(), + (_permissionValue! >> 16).toUnsigned(8).toInt(), + (_permissionValue! >> 24).toUnsigned(8).toInt() ]); - encryptionKeyData.addAll(randomBytes); - //if (revisionNumber > 3 && !EncryptMetaData) then add 4 default bytes + encryptionKeyData.addAll(randomBytes!); + int? revisionNumber; + if (_revision != 0) { + revisionNumber = this.revisionNumber; + } else { + revisionNumber = getKeyLength() + 2; + } + if (revisionNumber! > 3 && !encryptMetadata) { + encryptionKeyData.add(255); + encryptionKeyData.add(255); + encryptionKeyData.add(255); + encryptionKeyData.add(255); + } List currentHash = md5.convert(encryptionKeyData).bytes; - final int length = _getKeyLength(); - if (revisionNumber > 2) { - for (int i = 0; i < _ownerLoopNum; i++) { + final int? length = _getKeyLength(); + if (this.revisionNumber! > 2) { + for (int i = 0; i < _ownerLoopNum!; i++) { currentHash = md5 .convert(currentHash.length == length ? currentHash - : List.generate(length, (i) => currentHash[i])) + : List.generate(length!, (i) => currentHash[i])) .bytes; } } return currentHash.length == length ? currentHash - : List.generate(length, (i) => currentHash[i]); + : List.generate(length!, (i) => currentHash[i]); } List _acrobatXComputeHash( - List input, List password, List key) { + List input, List password, List? key) { List hash = sha256.convert(input).bytes; - List finalHashKey; + List? finalHashKey; for (int i = 0; - i < 64 || (finalHashKey[finalHashKey.length - 1] & 0xFF) > i - 32; + i < 64 || (finalHashKey![finalHashKey.length - 1] & 0xFF) > i - 32; i++) { final List roundHash = List.filled( (key != null && key.length >= 48) @@ -585,18 +600,18 @@ class _PdfEncryptor { final _AesCipher encrypt = _AesCipher(true, hashFirst, hashSecond); finalHashKey = encrypt._update(roundHash, 0, roundHash.length); final List finalHashKeyFirst = - List.generate(16, (i) => finalHashKey[i]); + List.generate(16, (i) => finalHashKey![i]); final BigInt finalKeyBigInteger = _readBigIntFromBytes(finalHashKeyFirst, 0, finalHashKeyFirst.length); final BigInt divisior = BigInt.parse('3'); final BigInt algorithmNumber = finalKeyBigInteger % divisior; final int algorithmIndex = algorithmNumber.toInt(); if (algorithmIndex == 0) { - hash = sha256.convert(finalHashKey).bytes; + hash = sha256.convert(finalHashKey!).bytes; } else if (algorithmIndex == 1) { - hash = sha384.convert(finalHashKey).bytes; + hash = sha384.convert(finalHashKey!).bytes; } else { - hash = sha512.convert(finalHashKey).bytes; + hash = sha512.convert(finalHashKey!).bytes; } } return (hash.length > 32) ? List.generate(32, (i) => hash[i]) : hash; @@ -617,8 +632,7 @@ class _PdfEncryptor { } void _readFromDictionary(_PdfDictionary dictionary) { - ArgumentError.checkNotNull(dictionary); - _IPdfPrimitive obj; + _IPdfPrimitive? obj; if (dictionary.containsKey(_DictionaryProperties.filter)) { obj = _PdfCrossTable._dereference(dictionary[_DictionaryProperties.filter]); @@ -630,7 +644,7 @@ class _PdfEncryptor { obj, 'Invalid Format: Unsupported security filter'); } _permissionValue = dictionary._getInt(_DictionaryProperties.p); - _updatePermissions(_permissionValue & ~_permissionSet); + _updatePermissions(_permissionValue! & ~_permissionSet!); _versionNumberOut = dictionary._getInt(_DictionaryProperties.v); _revisionNumberOut = dictionary._getInt(_DictionaryProperties.r); if (_revisionNumberOut != null) { @@ -643,26 +657,35 @@ class _PdfEncryptor { } if (keySize == 5) { _userEncryptionKeyOut = - dictionary._getString(_DictionaryProperties.ue).data; + dictionary._getString(_DictionaryProperties.ue)!.data; _ownerEncryptionKeyOut = - dictionary._getString(_DictionaryProperties.oe).data; - _permissionFlag = dictionary._getString(_DictionaryProperties.perms).data; + dictionary._getString(_DictionaryProperties.oe)!.data; + _permissionFlag = + dictionary._getString(_DictionaryProperties.perms)!.data; } - _userPasswordOut = dictionary._getString(_DictionaryProperties.u).data; - _ownerPasswordOut = dictionary._getString(_DictionaryProperties.o).data; + _userPasswordOut = dictionary._getString(_DictionaryProperties.u)!.data; + _ownerPasswordOut = dictionary._getString(_DictionaryProperties.o)!.data; keyLength = dictionary.containsKey(_DictionaryProperties.length) ? dictionary._getInt(_DictionaryProperties.length) : (keySize == 1 ? 40 : (keySize == 2 ? 128 : 256)); - if (keyLength == 128 && _revisionNumberOut < 4) { + if (keyLength == 128 && _revisionNumberOut! < 4) { keySize = 2; encryptionAlgorithm = PdfEncryptionAlgorithm.rc4x128Bit; } else if ((keyLength == 128 || keyLength == 256) && - _revisionNumberOut >= 4) { + _revisionNumberOut! >= 4) { final _PdfDictionary cryptFilter = dictionary[_DictionaryProperties.cf] as _PdfDictionary; final _PdfDictionary standardCryptFilter = cryptFilter[_DictionaryProperties.stdCF] as _PdfDictionary; - final String filterName = + if (standardCryptFilter.containsKey(_DictionaryProperties.authEvent)) { + final _IPdfPrimitive? authEventPrimitive = + standardCryptFilter[_DictionaryProperties.authEvent]; + if (authEventPrimitive is _PdfName && + authEventPrimitive._name == _DictionaryProperties.efOpen) { + encryptOnlyAttachment = true; + } + } + final String? filterName = (standardCryptFilter[_DictionaryProperties.cfm] as _PdfName)._name; if (keyLength == 128) { keySize = 2; @@ -676,10 +699,10 @@ class _PdfEncryptor { } else if (keyLength == 40) { keySize = 1; encryptionAlgorithm = PdfEncryptionAlgorithm.rc4x40Bit; - } else if (keyLength <= 128 && - keyLength > 40 && - keyLength % 8 == 0 && - _revisionNumberOut < 4) { + } else if (keyLength! <= 128 && + keyLength! > 40 && + keyLength! % 8 == 0 && + _revisionNumberOut! < 4) { encryptionAlgorithm = PdfEncryptionAlgorithm.rc4x128Bit; keySize = 2; } else { @@ -691,7 +714,7 @@ class _PdfEncryptor { keySize = 4; } if (keyLength != 0 && - keyLength % 8 != 0 && + keyLength! % 8 != 0 && (keySize == 1 || keySize == 2 || keySize == 3)) { throw ArgumentError.value( 'Invalid format: Invalid/Unsupported security dictionary.'); @@ -702,47 +725,47 @@ class _PdfEncryptor { void _updatePermissions(int value) { _permissions = []; if (value & 0x000004 > 0) { - _permissions.add(PdfPermissionsFlags.print); + _permissions!.add(PdfPermissionsFlags.print); } if (value & 0x000008 > 0) { - _permissions.add(PdfPermissionsFlags.editContent); + _permissions!.add(PdfPermissionsFlags.editContent); } if (value & 0x000010 > 0) { - _permissions.add(PdfPermissionsFlags.copyContent); + _permissions!.add(PdfPermissionsFlags.copyContent); } if (value & 0x000020 > 0) { - _permissions.add(PdfPermissionsFlags.editAnnotations); + _permissions!.add(PdfPermissionsFlags.editAnnotations); } if (value & 0x000100 > 0) { - _permissions.add(PdfPermissionsFlags.fillFields); + _permissions!.add(PdfPermissionsFlags.fillFields); } if (value & 0x000200 > 0) { - _permissions.add(PdfPermissionsFlags.accessibilityCopyContent); + _permissions!.add(PdfPermissionsFlags.accessibilityCopyContent); } if (value & 0x000400 > 0) { - _permissions.add(PdfPermissionsFlags.assembleDocument); + _permissions!.add(PdfPermissionsFlags.assembleDocument); } if (value & 0x000800 > 0) { - _permissions.add(PdfPermissionsFlags.fullQualityPrint); + _permissions!.add(PdfPermissionsFlags.fullQualityPrint); } if (permissions.isEmpty) { - _permissions.add(PdfPermissionsFlags.none); + _permissions!.add(PdfPermissionsFlags.none); } } - bool _checkPassword(String password, _PdfString key) { - ArgumentError.checkNotNull(password); - ArgumentError.checkNotNull(key); + bool _checkPassword(String password, _PdfString key, bool attachEncryption) { bool result = false; - final List fileId = - _randomBytes != null ? List.from(_randomBytes) : _randomBytes; - _randomBytes = List.from(key.data); + final List? fileId = + _randomBytes != null ? List.from(_randomBytes!) : _randomBytes; + _randomBytes = List.from(key.data!); if (_authenticateOwnerPassword(password)) { _ownerPassword = password; result = true; } else if (_authenticateUserPassword(password)) { _userPassword = password; result = true; + } else if (!attachEncryption) { + result = true; } else { _encryptionKey = null; } @@ -757,7 +780,7 @@ class _PdfEncryptor { encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { return _authenticate256BitUserPassword(password); } else { - _encryptionKey = _createEncryptionKey(password, _ownerPasswordOut); + _encryptionKey = _createEncryptionKey(password, _ownerPasswordOut!); return _compareByteArrays(_createUserPassword(), _userPasswordOut, revisionNumber == 2 ? null : 0x10); } @@ -769,20 +792,20 @@ class _PdfEncryptor { return _authenticate256BitOwnerPassword(password); } else { _encryptionKey = _getKeyFromOwnerPassword(password); - List buff = _ownerPasswordOut; + List? buff = _ownerPasswordOut; if (revisionNumber == 2) { buff = - _encryptDataByCustom(buff, _encryptionKey, _encryptionKey.length); - } else if (revisionNumber > 2) { + _encryptDataByCustom(buff!, _encryptionKey, _encryptionKey!.length); + } else if (revisionNumber! > 2) { buff = _ownerPasswordOut; - for (int i = 0; i < _ownerLoopNum2; ++i) { + for (int i = 0; i < _ownerLoopNum2!; ++i) { final List currKey = _getKeyWithOwnerPassword( - _encryptionKey, (_ownerLoopNum2 - i - 1)); - buff = _encryptDataByCustom(buff, currKey, currKey.length); + _encryptionKey!, (_ownerLoopNum2! - i - 1)); + buff = _encryptDataByCustom(buff!, currKey, currKey.length); } } _encryptionKey = null; - final String userPassword = _convertToPassword(buff); + final String userPassword = _convertToPassword(buff!); if (_authenticateUserPassword(userPassword)) { _userPassword = userPassword; _ownerPassword = password; @@ -797,8 +820,8 @@ class _PdfEncryptor { String result; int length = array.length; for (int i = 0; i < length; ++i) { - if (array[i] == paddingBytes[0]) { - if (i < length - 1 && array[i + 1] == paddingBytes[1]) { + if (array[i] == paddingBytes![0]) { + if (i < length - 1 && array[i + 1] == paddingBytes![1]) { length = i; break; } @@ -816,8 +839,8 @@ class _PdfEncryptor { List hash; final List userPassword = utf8.encode(password); if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { - List.copyRange(hashProvided, 0, _userPasswordOut, 0, 32); - List.copyRange(userValidationSalt, 0, _userPasswordOut, 32, 40); + List.copyRange(hashProvided, 0, _userPasswordOut!, 0, 32); + List.copyRange(userValidationSalt, 0, _userPasswordOut!, 32, 40); final List combinedUserpassword = List.filled( userPassword.length + userValidationSalt.length, 0, growable: true); @@ -829,14 +852,15 @@ class _PdfEncryptor { _advanceXUserFileEncryptionKey(password); return _compareByteArrays(hash, hashProvided); } else { - List.copyRange(hashProvided, 0, _userPasswordOut, 0, hashProvided.length); - List.copyRange(_userRandomBytes, 0, _userPasswordOut, 32, 48); - List.copyRange(userValidationSalt, 0, _userRandomBytes, 0, + List.copyRange( + hashProvided, 0, _userPasswordOut!, 0, hashProvided.length); + List.copyRange(_userRandomBytes!, 0, _userPasswordOut!, 32, 48); + List.copyRange(userValidationSalt, 0, _userRandomBytes!, 0, userValidationSalt.length); List.copyRange( userKeySalt, 0, - _userRandomBytes, + _userRandomBytes!, userValidationSalt.length, userKeySalt.length + userValidationSalt.length); hash = List.filled( @@ -873,10 +897,11 @@ class _PdfEncryptor { bool oEqual = false; final List ownerPassword = utf8.encode(password); if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { - List.copyRange(hashProvided, 0, _ownerPasswordOut, 0, 32); - List.copyRange(ownerValidationSalt, 0, _ownerPasswordOut, 32, 40); + List.copyRange(hashProvided, 0, _ownerPasswordOut!, 0, 32); + List.copyRange(ownerValidationSalt, 0, _ownerPasswordOut!, 32, 40); int userKeyLength = 48; - if (_userPasswordOut.length < 48) userKeyLength = _userPasswordOut.length; + if (_userPasswordOut!.length < 48) + userKeyLength = _userPasswordOut!.length; final List mixedOwnerPassword = List.filled( ownerPassword.length + ownerValidationSalt.length + userKeyLength, 0, growable: true); @@ -887,7 +912,7 @@ class _PdfEncryptor { List.copyRange( mixedOwnerPassword, ownerPassword.length + ownerValidationSalt.length, - _userPasswordOut, + _userPasswordOut!, 0, userKeyLength); hash = _acrobatXComputeHash( @@ -895,7 +920,7 @@ class _PdfEncryptor { _acrobatXOwnerFileEncryptionKey(password); oEqual = _compareByteArrays(hash, hashProvided); if (oEqual == true) { - final List ownerRandom = _fileEncryptionKey; + final List? ownerRandom = _fileEncryptionKey; final String userPassword = password; _ownerRandomBytes = null; if (_authenticateUserPassword(userPassword)) { @@ -910,13 +935,13 @@ class _PdfEncryptor { return oEqual; } else { final List userPasswordOut = List.filled(48, 0, growable: true); - List.copyRange(userPasswordOut, 0, _userPasswordOut, 0, 48); + List.copyRange(userPasswordOut, 0, _userPasswordOut!, 0, 48); List.copyRange( - hashProvided, 0, _ownerPasswordOut, 0, hashProvided.length); - List.copyRange(_ownerRandomBytes, 0, _ownerPasswordOut, 32, 48); - List.copyRange(ownerValidationSalt, 0, _ownerRandomBytes, 0, + hashProvided, 0, _ownerPasswordOut!, 0, hashProvided.length); + List.copyRange(_ownerRandomBytes!, 0, _ownerPasswordOut!, 32, 48); + List.copyRange(ownerValidationSalt, 0, _ownerRandomBytes!, 0, ownerValidationSalt.length); - List.copyRange(ownerKeySalt, 0, _ownerRandomBytes, + List.copyRange(ownerKeySalt, 0, _ownerRandomBytes!, ownerValidationSalt.length, ownerKeySalt.length); hash = List.filled( ownerPassword.length + @@ -957,17 +982,17 @@ class _PdfEncryptor { void _findFileEncryptionKey(String password) { List hash; - List hashFound; - List forDecryption; + late List hashFound; + List? forDecryption; if (_ownerRandomBytes != null) { final List ownerValidationSalt = List.filled(8, 0, growable: true); final List ownerKeySalt = List.filled(8, 0, growable: true); final List ownerPassword = utf8.encode(password); final List userPasswordOut = List.filled(48, 0, growable: true); - List.copyRange(userPasswordOut, 0, _userPasswordOut, 0, 48); - List.copyRange(ownerValidationSalt, 0, _ownerRandomBytes, 0, 8); - List.copyRange(ownerKeySalt, 0, _ownerRandomBytes, 8, 16); + List.copyRange(userPasswordOut, 0, _userPasswordOut!, 0, 48); + List.copyRange(ownerValidationSalt, 0, _ownerRandomBytes!, 0, 8); + List.copyRange(ownerKeySalt, 0, _ownerRandomBytes!, 8, 16); hash = List.filled( ownerPassword.length + ownerValidationSalt.length + @@ -986,8 +1011,8 @@ class _PdfEncryptor { List.filled(8, 0, growable: true); final List userKeySalt = List.filled(8, 0, growable: true); final List userPassword = utf8.encode(password); - List.copyRange(userValidationSalt, 0, _userRandomBytes, 0, 8); - List.copyRange(userKeySalt, 0, _userRandomBytes, 8, 16); + List.copyRange(userValidationSalt, 0, _userRandomBytes!, 0, 8); + List.copyRange(userKeySalt, 0, _userRandomBytes!, 8, 16); hash = List.filled(userPassword.length + userKeySalt.length, 0, growable: true); List.copyRange(hash, 0, userPassword, 0, userPassword.length); @@ -997,13 +1022,13 @@ class _PdfEncryptor { forDecryption = _userEncryptionKeyOut; } _fileEncryptionKey = _AesCipherNoPadding(false, hashFound) - ._processBlock(forDecryption, 0, forDecryption.length); + .processBlock(forDecryption, 0, forDecryption!.length); } - bool _compareByteArrays(List array1, List array2, [int size]) { + bool _compareByteArrays(List array1, List? array2, [int? size]) { bool result = true; if (size == null) { - if (array1 == null || array2 == null) { + if (array2 == null) { result = (array1 == array2); } else if (array1.length != array2.length) { result = false; @@ -1016,7 +1041,7 @@ class _PdfEncryptor { } } } else { - if (array1 == null || array2 == null) { + if (array2 == null) { result = (array1 == array2); } else if (array1.length < size || array2.length < size) { throw ArgumentError.value( @@ -1039,10 +1064,10 @@ class _PdfEncryptor { final List ownerValidationSalt = List.filled(8, 0, growable: true); final List ownerPassword = utf8.encode(password); - List.copyRange(ownerValidationSalt, 0, _ownerPasswordOut, 40, 48); + List.copyRange(ownerValidationSalt, 0, _ownerPasswordOut!, 40, 48); int userKeyLength = 48; - if (_userPasswordOut.length < 48) { - userKeyLength = _userPasswordOut.length; + if (_userPasswordOut!.length < 48) { + userKeyLength = _userPasswordOut!.length; } final List combinedPassword = List.filled( ownerPassword.length + ownerValidationSalt.length + userKeyLength, 0, @@ -1053,19 +1078,19 @@ class _PdfEncryptor { List.copyRange( combinedPassword, ownerPassword.length + ownerValidationSalt.length, - _userPasswordOut, + _userPasswordOut!, 0, userKeyLength); final List hash = _acrobatXComputeHash(combinedPassword, ownerPassword, _userPasswordOut); - final List fileEncryptionKey = List.from(_ownerEncryptionKeyOut); + final List fileEncryptionKey = List.from(_ownerEncryptionKeyOut!); _fileEncryptionKey = _AesCipherNoPadding(false, hash) - ._processBlock(fileEncryptionKey, 0, fileEncryptionKey.length); + .processBlock(fileEncryptionKey, 0, fileEncryptionKey.length); } void _advanceXUserFileEncryptionKey(String password) { final List userKeySalt = List.filled(8, 0, growable: true); - List.copyRange(userKeySalt, 0, _userPasswordOut, 40, 48); + List.copyRange(userKeySalt, 0, _userPasswordOut!, 40, 48); final List userpassword = utf8.encode(password); final List combinedUserPassword = List.filled( userpassword.length + userKeySalt.length, 0, @@ -1076,9 +1101,9 @@ class _PdfEncryptor { userKeySalt.length); final List hash = _acrobatXComputeHash(combinedUserPassword, userpassword, null); - final List fileEncryptionKey = List.from(_userEncryptionKeyOut); + final List fileEncryptionKey = List.from(_userEncryptionKeyOut!); _fileEncryptionKey = _AesCipherNoPadding(false, hash) - ._processBlock(fileEncryptionKey, 0, fileEncryptionKey.length); + .processBlock(fileEncryptionKey, 0, fileEncryptionKey.length); } List _generateInitVector() { @@ -1087,7 +1112,7 @@ class _PdfEncryptor { } _PdfDictionary _saveToDictionary(_PdfDictionary dictionary) { - if (_isChanged) { + if (_isChanged!) { _revisionNumberOut = 0; _versionNumberOut = 0; _revision = 0; @@ -1095,26 +1120,27 @@ class _PdfEncryptor { } dictionary[_DictionaryProperties.filter] = _PdfName(_DictionaryProperties.standard); - dictionary[_DictionaryProperties.p] = _PdfNumber(_permissionValue); - dictionary[_DictionaryProperties.u] = _PdfString.fromBytes(userPasswordOut); + dictionary[_DictionaryProperties.p] = _PdfNumber(_permissionValue!); + dictionary[_DictionaryProperties.u] = + _PdfString.fromBytes(userPasswordOut!); dictionary[_DictionaryProperties.o] = - _PdfString.fromBytes(ownerPasswordOut); + _PdfString.fromBytes(ownerPasswordOut!); if (dictionary.containsKey(_DictionaryProperties.length)) { keyLength = 0; } - dictionary[_DictionaryProperties.length] = _PdfNumber(_getKeyLength() * 8); + dictionary[_DictionaryProperties.length] = _PdfNumber(_getKeyLength()! * 8); final bool isAes4Dict = false; + if (_encryptOnlyAttachment! && + (encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x40Bit)) { + throw ArgumentError.value(encryptionAlgorithm, + 'Encrypt only attachment is supported in AES algorithm with 128, 256 and 256Revision6 encryptions only.'); + } if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit || encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { - dictionary[_DictionaryProperties.r] = _PdfNumber( - (_revisionNumberOut > 0 && isAes4Dict) - ? _revisionNumberOut - : (_getKeySize() + 3)); - dictionary[_DictionaryProperties.v] = _PdfNumber( - (_versionNumberOut > 0 && isAes4Dict) - ? _versionNumberOut - : (_getKeySize() + 3)); + dictionary[_DictionaryProperties.r] = _PdfNumber(_getKeySize() + 3); + dictionary[_DictionaryProperties.v] = _PdfNumber(_getKeySize() + 3); if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { dictionary[_DictionaryProperties.v] = _PdfNumber(5); dictionary[_DictionaryProperties.r] = _PdfNumber(6); @@ -1122,34 +1148,52 @@ class _PdfEncryptor { dictionary[_DictionaryProperties.v] = _PdfNumber(5); dictionary[_DictionaryProperties.r] = _PdfNumber(5); } - dictionary[_DictionaryProperties.stmF] = - _PdfName(_DictionaryProperties.stdCF); - dictionary[_DictionaryProperties.strF] = - _PdfName(_DictionaryProperties.stdCF); - dictionary[_DictionaryProperties.cf] = _getCryptFilterDictionary(); - if (!_encryptMetadata) { + if (_encryptOnlyAttachment!) { + dictionary[_DictionaryProperties.stmF] = + _PdfName(_DictionaryProperties.identity); + dictionary[_DictionaryProperties.strF] = + _PdfName(_DictionaryProperties.identity); + dictionary[_DictionaryProperties.eff] = + _PdfName(_DictionaryProperties.stdCF); + dictionary[_DictionaryProperties.encryptMetadata] = + _PdfBoolean(_encryptMetadata); + } else { + dictionary[_DictionaryProperties.stmF] = + _PdfName(_DictionaryProperties.stdCF); + dictionary[_DictionaryProperties.strF] = + _PdfName(_DictionaryProperties.stdCF); + if (dictionary.containsKey(_DictionaryProperties.eff)) { + dictionary.remove(_DictionaryProperties.eff); + } + } + if (!_encryptMetadata!) { if (!dictionary.containsKey(_DictionaryProperties.encryptMetadata)) { dictionary[_DictionaryProperties.encryptMetadata] = _PdfBoolean(_encryptMetadata); } + } else if (!encryptOnlyAttachment) { + if (dictionary.containsKey(_DictionaryProperties.encryptMetadata)) { + dictionary.remove(_DictionaryProperties.encryptMetadata); + } } + dictionary[_DictionaryProperties.cf] = _getCryptFilterDictionary(); if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { dictionary[_DictionaryProperties.ue] = - _PdfString.fromBytes(_userEncryptionKeyOut); + _PdfString.fromBytes(_userEncryptionKeyOut!); dictionary[_DictionaryProperties.oe] = - _PdfString.fromBytes(_ownerEncryptionKeyOut); + _PdfString.fromBytes(_ownerEncryptionKeyOut!); dictionary[_DictionaryProperties.perms] = - _PdfString.fromBytes(_permissionFlag); + _PdfString.fromBytes(_permissionFlag!); } } else { dictionary[_DictionaryProperties.r] = _PdfNumber( - (_revisionNumberOut > 0 && !isAes4Dict) - ? _revisionNumberOut + (_revisionNumberOut! > 0 && !isAes4Dict) + ? _revisionNumberOut! : (_getKeySize() + 2).toInt()); dictionary[_DictionaryProperties.v] = _PdfNumber( - (_versionNumberOut > 0 && !isAes4Dict) - ? _versionNumberOut + (_versionNumberOut! > 0 && !isAes4Dict) + ? _versionNumberOut! : (_getKeySize() + 1).toInt()); } dictionary._archive = false; @@ -1158,18 +1202,31 @@ class _PdfEncryptor { _PdfDictionary _getCryptFilterDictionary() { final _PdfDictionary standardCryptFilter = _PdfDictionary(); - standardCryptFilter[_DictionaryProperties.cfm] = _PdfName( - encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit - ? _DictionaryProperties.aesv3 - : _DictionaryProperties.aesv2); - standardCryptFilter[_DictionaryProperties.authEvent] = - _PdfName(_DictionaryProperties.docOpen); + if (!standardCryptFilter.containsKey(_DictionaryProperties.cfm)) { + if (_encryptOnlyAttachment!) { + standardCryptFilter[_DictionaryProperties.cfm] = + _PdfName(_DictionaryProperties.aesv2); + standardCryptFilter[_DictionaryProperties.type] = + _PdfName(_DictionaryProperties.cryptFilter); + } else { + standardCryptFilter[_DictionaryProperties.cfm] = _PdfName( + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit + ? _DictionaryProperties.aesv3 + : _DictionaryProperties.aesv2); + } + } + if (!standardCryptFilter.containsKey(_DictionaryProperties.authEvent)) { + standardCryptFilter[_DictionaryProperties.authEvent] = _PdfName( + _encryptOnlyAttachment! + ? _DictionaryProperties.efOpen + : _DictionaryProperties.docOpen); + } standardCryptFilter[_DictionaryProperties.length] = _PdfNumber( encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit - ? _key256 + ? _key256! : ((encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit || encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit) - ? _key128 + ? _key128! : 128)); final _PdfDictionary cryptFilterDictionary = _PdfDictionary(); cryptFilterDictionary[_DictionaryProperties.stdCF] = standardCryptFilter; @@ -1179,7 +1236,7 @@ class _PdfEncryptor { int _getPermissionValue(List permissionFlags) { int defaultValue = 0; permissionFlags.forEach((PdfPermissionsFlags flag) { - defaultValue |= _permissionFlagValues[flag.index]; + defaultValue |= _permissionFlagValues![flag.index]; }); return defaultValue; } @@ -1207,26 +1264,25 @@ class _PdfEncryptor { } List _encryptData( - int currentObjectNumber, List data, bool isEncryption) { - ArgumentError.checkNotNull(data, 'data value cannot be null'); + int? currentObjectNumber, List data, bool isEncryption) { if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256BitRevision6) { return isEncryption - ? _aesEncrypt(data, _fileEncryptionKey) + ? _aesEncrypt(data, _fileEncryptionKey!) : _aesDecrypt(data, _fileEncryptionKey); } _initializeData(); final int genNumber = 0; int keyLen = 0; List newKey; - if (_encryptionKey.length == 5) { - newKey = List.filled(_encryptionKey.length + _newKeyOffset, 0, + if (_encryptionKey!.length == 5) { + newKey = List.filled(_encryptionKey!.length + _newKeyOffset!, 0, growable: true); - for (int i = 0; i < _encryptionKey.length; ++i) { - newKey[i] = _encryptionKey[i]; + for (int i = 0; i < _encryptionKey!.length; ++i) { + newKey[i] = _encryptionKey![i]; } - int j = _encryptionKey.length - 1; - newKey[++j] = currentObjectNumber.toUnsigned(8); + int j = _encryptionKey!.length - 1; + newKey[++j] = currentObjectNumber!.toUnsigned(8); newKey[++j] = (currentObjectNumber >> 8).toUnsigned(8); newKey[++j] = (currentObjectNumber >> 16).toUnsigned(8); newKey[++j] = genNumber.toUnsigned(8); @@ -1235,15 +1291,18 @@ class _PdfEncryptor { newKey = _prepareKeyForEncryption(newKey); } else { newKey = List.filled( - _encryptionKey.length + - ((encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit) + _encryptionKey!.length + + ((encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == + PdfEncryptionAlgorithm.aesx256BitRevision6 || + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit) ? 9 : 5), 0, growable: true); - List.copyRange(newKey, 0, _encryptionKey, 0, _encryptionKey.length); - int j = _encryptionKey.length - 1; - newKey[++j] = currentObjectNumber.toUnsigned(8); + List.copyRange(newKey, 0, _encryptionKey!, 0, _encryptionKey!.length); + int j = _encryptionKey!.length - 1; + newKey[++j] = currentObjectNumber!.toUnsigned(8); newKey[++j] = (currentObjectNumber >> 8).toUnsigned(8); newKey[++j] = (currentObjectNumber >> 16).toUnsigned(8); newKey[++j] = genNumber.toUnsigned(8); @@ -1260,8 +1319,8 @@ class _PdfEncryptor { keyLen = min(keyLen, newKey.length); if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit) { return isEncryption - ? _aesEncrypt(data, newKey) - : _aesDecrypt(data, newKey); + ? _aesEncrypt(data, encryptOnlyAttachment ? _encryptionKey! : newKey) + : _aesDecrypt(data, encryptOnlyAttachment ? _encryptionKey : newKey); } return _encryptDataByCustom(data, newKey, keyLen); } @@ -1282,7 +1341,7 @@ class _PdfEncryptor { return result; } - List _aesDecrypt(List data, List key) { + List _aesDecrypt(List data, List? key) { final List result = []; final List iv = List.filled(16, 0, growable: true); int length = data.length; @@ -1292,7 +1351,7 @@ class _PdfEncryptor { length -= minBlock; ivPtr += minBlock; if (ivPtr == iv.length && length > 0) { - final _AesEncryptor decryptor = _AesEncryptor(key, iv, false); + final _AesEncryptor decryptor = _AesEncryptor(key!, iv, false); int lengthNeeded = decryptor._getBlockSize(length); final List output = List.filled(lengthNeeded, 0, growable: true); @@ -1315,13 +1374,25 @@ class _PdfEncryptor { return result; } + int getKeyLength() { + if (encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x40Bit) { + return 1; + } else if (encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit || + encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit) { + return 2; + } else if (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit) { + return 3; + } else { + return 4; + } + } + List _prepareKeyForEncryption(List originalKey) { - ArgumentError.checkNotNull(originalKey, 'Value cannot be null'); final int keyLen = originalKey.length; final List newKey = md5.convert(originalKey).bytes; - if (keyLen > _randomBytesAmount) { + if (keyLen > _randomBytesAmount!) { final int newKeyLength = - min(_getKeyLength() + _newKeyOffset, _randomBytesAmount); + min(_getKeyLength()! + _newKeyOffset!, _randomBytesAmount!); final List result = List.filled(newKeyLength, 0, growable: true); List.copyRange(result, 0, newKey, 0, newKeyLength); @@ -1350,8 +1421,8 @@ class _PdfEncryptor { .._key40 = _cloneInt(_key40) .._key128 = _cloneInt(_key128) .._key256 = _cloneInt(_key256) - .._randomBytesAmount = _cloneInt(_key256) - .._newKeyOffset = _cloneInt(_key256) + .._randomBytesAmount = _cloneInt(_randomBytesAmount) + .._newKeyOffset = _cloneInt(_newKeyOffset) .._encrypt = _cloneBool(_encrypt) .._isChanged = _cloneBool(_isChanged) .._hasComputedPasswordValues = _cloneBool(_hasComputedPasswordValues) @@ -1372,14 +1443,15 @@ class _PdfEncryptor { .._encryptOnlyAttachment = _cloneBool(_encryptOnlyAttachment) ..encryptionAlgorithm = encryptionAlgorithm .._userPassword = _userPassword - .._ownerPassword = _ownerPassword; + .._ownerPassword = _ownerPassword + .._encryptionOptions = _encryptionOptions; encryptor._permissions = _permissions != null - ? List.generate(_permissions.length, (i) => _permissions[i]) + ? List.generate(_permissions!.length, (i) => _permissions![i]) : null; return encryptor; } - bool _cloneBool(bool value) { + bool? _cloneBool(bool? value) { if (value != null) { if (value) { return true; @@ -1391,7 +1463,7 @@ class _PdfEncryptor { } } - int _cloneInt(int value) { + int? _cloneInt(int? value) { if (value != null) { return value.toInt(); } else { @@ -1399,7 +1471,7 @@ class _PdfEncryptor { } } - List _cloneList(List value) { + List? _cloneList(List? value) { if (value != null) { return List.generate(value.length, (i) => value[i], growable: true); } else { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_security.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_security.dart index 985d3580c..95f246d16 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_security.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_security.dart @@ -39,11 +39,12 @@ class PdfSecurity { } //Fields - _PdfEncryptor _encryptor; - PdfPermissions _permissions; + late _PdfEncryptor _encryptor; + PdfPermissions? _permissions; // ignore: prefer_final_fields bool _conformance = false; bool _modifiedSecurity = false; + late bool _encryptOnlyAttachment; /// Gets the type of encryption algorithm used. /// @@ -58,7 +59,7 @@ class PdfSecurity { /// document.dispose(); /// ``` PdfEncryptionAlgorithm get algorithm { - return _encryptor.encryptionAlgorithm; + return _encryptor.encryptionAlgorithm!; } /// Sets the type of encryption algorithm. @@ -78,7 +79,6 @@ class PdfSecurity { /// document.dispose(); /// ``` set algorithm(PdfEncryptionAlgorithm value) { - ArgumentError.checkNotNull(value); _encryptor.encryptionAlgorithm = value; _encryptor.encrypt = true; _encryptor._isChanged = true; @@ -102,7 +102,7 @@ class PdfSecurity { /// document.dispose(); /// ``` String get ownerPassword { - return _encryptor.ownerPassword; + return _encryptAttachments ? '' : _encryptor.ownerPassword; } /// Sets the owner password. @@ -201,11 +201,71 @@ class PdfSecurity { /// ``` PdfPermissions get permissions { _permissions ??= PdfPermissions._(_encryptor, _encryptor.permissions); - return _permissions; + return _permissions!; + } + + /// Gets or sets the type of encryption options used. + /// + /// User password is required when the PDF document is opened in a viewer. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Document security + /// PdfSecurity security = document.security; + /// //Set security options + /// security.algorithm = PdfEncryptionAlgorithm.rc4x128Bit; + /// security.userPassword = 'password'; + /// security.ownerPassword = 'syncfusion'; + /// security.encryptionOptions = PdfEncryptionOptions.encryptAllContents; + /// //Create and add attachment to the PDF document + /// document.attachments.add(PdfAttachment( + /// 'input.txt', File('input.txt').readAsBytesSync(), + /// description: 'Text File', mimeType: 'application/txt')); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfEncryptionOptions get encryptionOptions => _encryptor._encryptionOptions; + set encryptionOptions(PdfEncryptionOptions value) { + if (_conformance) { + throw ArgumentError( + 'Document encryption is not allowed with Conformance documents.'); + } + if (_encryptor._encryptionOptions != value) { + _encryptor._encryptionOptions = value; + _encryptor.encrypt = true; + _encryptor._isChanged = true; + _modifiedSecurity = true; + _encryptor._hasComputedPasswordValues = false; + if (PdfEncryptionOptions.encryptOnlyAttachments == value) { + _encryptAttachments = true; + _encryptor.encryptMetadata = false; + } else if (PdfEncryptionOptions.encryptAllContentsExceptMetadata == + value) { + _encryptAttachments = false; + _encryptor.encryptMetadata = false; + } else { + _encryptAttachments = false; + _encryptor.encryptMetadata = true; + } + } + } + + bool get _encryptAttachments { + return _encryptOnlyAttachment; + } + + set _encryptAttachments(bool value) { + _encryptOnlyAttachment = value; + _encryptor.encryptOnlyAttachment = value; + _encryptor._isChanged = true; } //Implementation void _initialize() { + _encryptOnlyAttachment = false; _encryptor = _PdfEncryptor(); } } @@ -234,15 +294,13 @@ class PdfPermissions { //constructor PdfPermissions._( _PdfEncryptor encryptor, List permissions) { - ArgumentError.checkNotNull(encryptor); - ArgumentError.checkNotNull(permissions); _encryptor = encryptor; _permissions = permissions; } //Fields - _PdfEncryptor _encryptor; - List _permissions; + late _PdfEncryptor _encryptor; + late List _permissions; bool _modifiedPermissions = false; //Implementation @@ -337,7 +395,6 @@ class PdfPermissions { /// document.dispose(); /// ``` void add(PdfPermissionsFlags permission) { - ArgumentError.checkNotNull(permission, 'value cannot be null'); if (!_permissions.contains(permission)) { _permissions.add(permission); _encryptor.permissions = _permissions; @@ -363,7 +420,6 @@ class PdfPermissions { /// document.dispose(); /// ``` void addAll(List permission) { - ArgumentError.checkNotNull(permission, 'value cannot be null'); bool isChanged = false; if (permission.isNotEmpty) { permission.forEach((PdfPermissionsFlags flag) { @@ -398,7 +454,6 @@ class PdfPermissions { /// document.dispose(); /// ``` void remove(PdfPermissionsFlags permission) { - ArgumentError.checkNotNull(permission, 'value cannot be null'); if (_permissions.contains(permission)) { _permissions.remove(permission); _encryptor.permissions = _permissions; @@ -434,7 +489,6 @@ class PdfPermissions { } PdfPermissionsFlags _returnValue(int index) { - ArgumentError.checkNotNull(index, 'Index cannot be null'); if (index < 0 || index >= count) { throw ArgumentError.value(index, 'Index out of range'); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/enums.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/enums.dart index d6103ac45..ce132dd66 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/enums.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/enums.dart @@ -2,7 +2,7 @@ part of pdf; ///Specifies the alignment type. enum PdfGridImagePosition { - /// To fit background image to the cell based on the cell with and height. + /// To fit image to the cell based on the cell width and height. fit, /// The image is rendered by center of the cell. @@ -12,7 +12,7 @@ enum PdfGridImagePosition { /// to fit the width and height of the cell. stretch, - /// The imag is rendered by tile mode. + /// The image is rendered by tile mode. tile } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/layouting/pdf_grid_layouter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/layouting/pdf_grid_layouter.dart index c3c61244e..5a22784de 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/layouting/pdf_grid_layouter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/layouting/pdf_grid_layouter.dart @@ -6,28 +6,29 @@ class _PdfGridLayouter extends _ElementLayouter { } //Fields - PdfGraphics _currentGraphics; - PdfPage _currentPage; - _Size _currentPageBounds; - _Rectangle _currentBounds; - _Point _startLocation; - double _childHeight; - PdfHorizontalOverflowType _hType; - List> _columnRanges; - int _cellEndIndex; - int _cellStartIndex; + PdfGraphics? _currentGraphics; + PdfPage? _currentPage; + late _Size _currentPageBounds; + late _Rectangle _currentBounds; + late _Point _startLocation; + late double _childHeight; + late PdfHorizontalOverflowType _hType; + List>? _columnRanges; + late int _cellEndIndex; + late int _cellStartIndex; static int _repeatRowIndex = -1; - List _parentCellIndexList; - int _currentRowIndex; - int _rowBreakPageHeightCellIndex; - bool flag; - bool _isChanged; - _Point _currentLocation; - bool _isChildGrid; - double _newheight; + late List _parentCellIndexList; + late int _currentRowIndex; + late int _currentHeaderRowIndex; + late int _rowBreakPageHeightCellIndex; + late bool flag; + late bool _isChanged; + late _Point _currentLocation; + bool? _isChildGrid; + late double _newheight; //Properties - PdfGrid get _grid => _element as PdfGrid; + PdfGrid? get _grid => _element as PdfGrid?; //Implementation void _initialize() { @@ -36,6 +37,7 @@ class _PdfGridLayouter extends _ElementLayouter { _hType = PdfHorizontalOverflowType.nextPage; _currentPageBounds = _Size.empty; _currentRowIndex = 0; + _currentHeaderRowIndex = 0; _currentLocation = _Point.empty; _currentBounds = _Rectangle.empty; _columnRanges ??= >[]; @@ -44,27 +46,25 @@ class _PdfGridLayouter extends _ElementLayouter { _cellEndIndex = 0; _isChanged = false; flag = true; - _isChildGrid = false; + _isChildGrid ??= false; _startLocation = _Point.empty; } void layout(PdfGraphics graphics, _Rectangle bounds) { - ArgumentError.checkNotNull(graphics); - ArgumentError.checkNotNull(bounds); final _PdfLayoutParams param = _PdfLayoutParams(); param.bounds = bounds; _currentGraphics = graphics; - if (_currentGraphics._layer != null && _currentGraphics._page != null) { + if (_currentGraphics!._layer != null && _currentGraphics!._page != null) { final int index = - _currentGraphics._page._section._indexOf(_currentGraphics._page); - if (!_grid._listOfNavigatePages.contains(index)) { - _grid._listOfNavigatePages.add(index); + _currentGraphics!._page!._section!._indexOf(_currentGraphics!._page!); + if (!_grid!._listOfNavigatePages.contains(index)) { + _grid!._listOfNavigatePages.add(index); } } _layoutInternal(param); } - PdfLayoutFormat _getFormat(PdfLayoutFormat format) { + PdfLayoutFormat _getFormat(PdfLayoutFormat? format) { return format != null ? PdfLayoutFormat.fromFormat(format) : PdfLayoutFormat(); @@ -75,39 +75,39 @@ class _PdfGridLayouter extends _ElementLayouter { int endColumn = 0; double cellWidths = 0; final double availableWidth = - _currentGraphics.clientSize.width - _currentBounds.x; - for (int i = 0; i < _grid.columns.count; i++) { - cellWidths += _grid.columns[i].width; + _currentGraphics!.clientSize.width - _currentBounds.x; + for (int i = 0; i < _grid!.columns.count; i++) { + cellWidths += _grid!.columns[i].width; if (cellWidths >= availableWidth) { double subWidths = 0; for (int j = startColumn; j <= i; j++) { - subWidths += _grid.columns[j].width; + subWidths += _grid!.columns[j].width; if (subWidths > availableWidth) { break; } endColumn = j; } - _columnRanges.add([startColumn, endColumn]); + _columnRanges!.add([startColumn, endColumn]); startColumn = endColumn + 1; endColumn = startColumn; - cellWidths = (endColumn <= i) ? _grid.columns[i].width : 0; + cellWidths = (endColumn <= i) ? _grid!.columns[i].width : 0; } } - if (startColumn != _grid.columns.count) { - _columnRanges.add([startColumn, _grid.columns.count - 1]); + if (startColumn != _grid!.columns.count) { + _columnRanges!.add([startColumn, _grid!.columns.count - 1]); } } - PdfLayoutResult _layoutOnPage(_PdfLayoutParams param) { + PdfLayoutResult? _layoutOnPage(_PdfLayoutParams param) { final PdfLayoutFormat format = _getFormat(param.format); - PdfGridEndPageLayoutArgs endArgs; - PdfLayoutResult result; - final Map> layoutedPages = >{}; - PdfPage startPage = param.page; - bool isParentCell = false; - final List cellBounds = []; - for (int rangeIndex = 0; rangeIndex < _columnRanges.length; rangeIndex++) { - final List range = _columnRanges[rangeIndex]; + late PdfGridEndPageLayoutArgs endArgs; + PdfLayoutResult? result; + final Map> layoutedPages = >{}; + PdfPage? startPage = param.page; + bool? isParentCell = false; + final List cellBounds = []; + for (int rangeIndex = 0; rangeIndex < _columnRanges!.length; rangeIndex++) { + final List range = _columnRanges![rangeIndex]; _cellStartIndex = range[0]; _cellEndIndex = range[1]; if (_currentPage != null) { @@ -116,31 +116,32 @@ class _PdfGridLayouter extends _ElementLayouter { _currentBounds = _Rectangle.fromRect(pageLayoutResult['currentBounds']); _currentRowIndex = pageLayoutResult['currentRow']; if (pageLayoutResult['cancel']) { - result = PdfLayoutResult._(_currentPage, _currentBounds.rect); + result = PdfLayoutResult._(_currentPage!, _currentBounds.rect); break; } } bool drawHeader; - if (_grid._isBuiltinStyle && _grid._parentCell == null) { - if (_grid._gridBuiltinStyle != PdfGridBuiltInStyle.tableGrid) { - _grid._applyBuiltinStyles(_grid._gridBuiltinStyle); + if (_grid!._isBuiltinStyle && _grid!._parentCell == null) { + if (_grid!._gridBuiltinStyle != PdfGridBuiltInStyle.tableGrid) { + _grid!._applyBuiltinStyles(_grid!._gridBuiltinStyle); } } - for (int rowIndex = 0; rowIndex < _grid.headers.count; rowIndex++) { - final PdfGridRow row = _grid.headers[rowIndex]; - final double headerHeight = _currentBounds.y; + for (int rowIndex = 0; rowIndex < _grid!.headers.count; rowIndex++) { + _currentHeaderRowIndex = rowIndex; + final PdfGridRow? row = _grid!.headers[rowIndex]; + final double? headerHeight = _currentBounds.y; if (startPage != _currentPage) { for (int k = _cellStartIndex; k <= _cellEndIndex; k++) { - if (row.cells[k]._isCellMergeContinue) { + if (row!.cells[k]._isCellMergeContinue) { row.cells[k]._isCellMergeContinue = false; row.cells[k].value = ''; } } } - final _RowLayoutResult headerResult = _drawRow(row); + final _RowLayoutResult headerResult = _drawRow(row)!; if (headerHeight == _currentBounds.y) { drawHeader = true; - _repeatRowIndex = _grid.rows._indexOf(row); + _repeatRowIndex = _grid!.rows._indexOf(row); } else { drawHeader = false; } @@ -152,38 +153,38 @@ class _PdfGridLayouter extends _ElementLayouter { _currentPage = getNextPage(format); _startLocation.y = _currentBounds.y; if (_Rectangle.fromRect(format.paginateBounds) == _Rectangle.empty) { - _currentBounds.x += _startLocation.x; + _currentBounds.x = _currentBounds.x + _startLocation.x; } _drawRow(row); } } int i = 0; - final int length = _grid.rows.count; + final int length = _grid!.rows.count; bool repeatRow; - double startingHeight = 0; + double? startingHeight = 0; bool flag = true; - if (isParentCell) { - _cellEndIndex = _cellStartIndex = _grid._parentCellIndex; + if (isParentCell!) { + _cellEndIndex = _cellStartIndex = _grid!._parentCellIndex; _parentCellIndexList = []; - _parentCellIndexList.add(_grid._parentCellIndex); - _grid._parentCell._present = true; - PdfGrid parentGrid = _grid._parentCell._row._grid; + _parentCellIndexList.add(_grid!._parentCellIndex); + _grid!._parentCell!._present = true; + PdfGrid parentGrid = _grid!._parentCell!._row!._grid; while (parentGrid._parentCell != null) { _parentCellIndexList.add(parentGrid._parentCellIndex); _cellEndIndex = parentGrid._parentCellIndex; _cellStartIndex = parentGrid._parentCellIndex; - parentGrid._parentCell._present = true; - parentGrid = parentGrid._parentCell._row._grid; + parentGrid._parentCell!._present = true; + parentGrid = parentGrid._parentCell!._row!._grid; if (parentGrid._parentCell == null) { _parentCellIndexList.removeAt(_parentCellIndexList.length - 1); } } - PdfSection section = _currentPage._section; - int index = section._indexOf(_currentPage); + PdfSection section = _currentPage!._section!; + int index = section._indexOf(_currentPage!); if ((!parentGrid._isDrawn) || (!parentGrid._listOfNavigatePages.contains(index))) { - section = _currentGraphics._page._section; - index = section._indexOf(_currentPage); + section = _currentGraphics!._page!._section!; + index = section._indexOf(_currentPage!); parentGrid._isDrawn = true; for (int rowIndex = 0; rowIndex < parentGrid.rows.count; rowIndex++) { final PdfGridRow row = parentGrid.rows[rowIndex]; @@ -191,16 +192,16 @@ class _PdfGridLayouter extends _ElementLayouter { cell.value = ''; final _Point location = _Point(_currentBounds.x, _currentBounds.y); double width = parentGrid.columns[_cellStartIndex].width; - if (width > _currentGraphics.clientSize.width) { - width = _currentGraphics.clientSize.width - 2 * location.x; + if (width > _currentGraphics!.clientSize.width) { + width = _currentGraphics!.clientSize.width - 2 * location.x; } - double height = cell.height; + double? height = cell.height; if (row.height > cell.height) { height = row.height; } cell._draw(_currentGraphics, _Rectangle(location.x, location.y, width, height), false); - _currentBounds.y += height; + _currentBounds.y = _currentBounds.y + height; } _currentBounds.y = 0; } @@ -209,23 +210,23 @@ class _PdfGridLayouter extends _ElementLayouter { _cellEndIndex = range[1]; } cellBounds.clear(); - for (int rowIndex = 0; rowIndex < _grid.rows.count; rowIndex++) { - final PdfGridRow row = _grid.rows[rowIndex]; + for (int rowIndex = 0; rowIndex < _grid!.rows.count; rowIndex++) { + final PdfGridRow row = _grid!.rows[rowIndex]; i++; _currentRowIndex = i - 1; - double originalHeight = _currentBounds.y; + double? originalHeight = _currentBounds.y; startPage = _currentPage; _repeatRowIndex = -1; - if (flag && row._grid._isChildGrid) { + if (flag && row._grid._isChildGrid!) { startingHeight = originalHeight; flag = false; } - if (row._grid._isChildGrid && - row._grid._parentCell.rowSpan > 1 && - (startingHeight + _childHeight).toInt() < + if (row._grid._isChildGrid! && + row._grid._parentCell!.rowSpan > 1 && + (startingHeight! + _childHeight).toInt() < (_currentBounds.y + row.height).toInt()) { - if (_grid.rows.count > i) { - final PdfGrid temp = row._grid._parentCell._row._grid; + if (_grid!.rows.count > i) { + final PdfGrid temp = row._grid._parentCell!._row!._grid; for (int tempRowIndex = 0; tempRowIndex < temp.rows.count; tempRowIndex++) { @@ -242,13 +243,14 @@ class _PdfGridLayouter extends _ElementLayouter { } break; } - _RowLayoutResult rowResult = _drawRow(row); + _RowLayoutResult rowResult = _drawRow(row)!; cellBounds.add(rowResult.bounds.width); if (row._isRowBreaksNextPage) { double x = 0; for (int l = 0; l < row.cells.count; l++) { bool isNestedRowBreak = false; - if (row.height == row.cells[l].height) { + if (row.height == row.cells[l].height && + row.cells[l].value is PdfGrid) { final PdfGrid grid = row.cells[l].value as PdfGrid; for (int m = grid.rows.count; 0 < m; m--) { if (grid.rows[m - 1]._rowBreakHeight > 0) { @@ -259,7 +261,8 @@ class _PdfGridLayouter extends _ElementLayouter { row._rowBreakHeight = grid.rows[m - 1]._rowBreakHeight; break; } - row._rowBreakHeight += grid.rows[m - 1].height; + row._rowBreakHeight = + row._rowBreakHeight + grid.rows[m - 1].height; } } if (isNestedRowBreak) { @@ -270,28 +273,28 @@ class _PdfGridLayouter extends _ElementLayouter { if (row.height > row.cells[j].height) { row.cells[j].value = ''; _Rectangle rect; - PdfPage page = _getNextPage(_currentPage); - final PdfSection section = _currentPage._section; - final int index = section._indexOf(page); + PdfPage? page = _getNextPage(_currentPage!); + final PdfSection section = _currentPage!._section!; + final int index = section._indexOf(page!); for (int k = 0; - k < (section._pageReferences.count - 1) - index; + k < (section._pageReferences!.count - 1) - index; k++) { rect = _Rectangle(x, 0, row._grid.columns[j].width, - page.getClientSize().height); + page!.getClientSize().height); _repeatRowIndex = -1; row.cells[j]._draw(page.graphics, rect, false); page = _getNextPage(page); } rect = _Rectangle( x, 0, row._grid.columns[j].width, row._rowBreakHeight); - row.cells[j]._draw(page.graphics, rect, false); + row.cells[j]._draw(page!.graphics, rect, false); } x += row._grid.columns[j].width; } } if (originalHeight == _currentBounds.y) { repeatRow = true; - _repeatRowIndex = _grid.rows._indexOf(row); + _repeatRowIndex = _grid!.rows._indexOf(row); } else { repeatRow = false; _repeatRowIndex = -1; @@ -299,21 +302,19 @@ class _PdfGridLayouter extends _ElementLayouter { while (!rowResult.isFinish && startPage != null) { final PdfLayoutResult tempResult = _getLayoutResult(); if (startPage != _currentPage) { - if (row._grid._isChildGrid && row._grid._parentCell != null) { + if (row._grid._isChildGrid! && row._grid._parentCell != null) { final _Rectangle bounds = _Rectangle( format.paginateBounds.left, format.paginateBounds.top, - param.bounds.width, + param.bounds!.width, tempResult.bounds.height); - bounds.x += param.bounds.x; - if (row._grid._parentCell._row._grid.style.cellPadding != null) { - bounds.y += - row._grid._parentCell._row._grid.style.cellPadding.top; - if (bounds.height > _currentPageBounds.height) { - bounds.height = _currentPageBounds.height - bounds.y; - bounds.height -= - row._grid._parentCell._row._grid.style.cellPadding.bottom; - } + bounds.x = bounds.x + param.bounds!.x; + bounds.y = bounds.y + + row._grid._parentCell!._row!._grid.style.cellPadding.top; + if (bounds.height > _currentPageBounds.height) { + bounds.height = _currentPageBounds.height - bounds.y; + bounds.height = bounds.height - + row._grid._parentCell!._row!._grid.style.cellPadding.bottom; } for (int c = 0; c < row.cells.count; c++) { final PdfGridCell cell = row.cells[c]; @@ -325,9 +326,9 @@ class _PdfGridLayouter extends _ElementLayouter { } else { cellWidth = max(cell.width, row._grid.columns[c].width); } - _currentGraphics = cell._drawCellBorders(_currentGraphics, + _currentGraphics = cell._drawCellBorders(_currentGraphics!, _Rectangle(bounds.x, bounds.y, cellWidth, bounds.height)); - bounds.x += cellWidth; + bounds.x = bounds.x + cellWidth; c += cell.columnSpan - 1; } } @@ -338,50 +339,52 @@ class _PdfGridLayouter extends _ElementLayouter { } if (repeatRow) { break; - } else if (_grid.allowRowBreakingAcrossPages) { + } else if (_grid!.allowRowBreakingAcrossPages) { _currentPage = getNextPage(format); originalHeight = _currentBounds.y; - final _Point location = _Point(_grid._defaultBorder.right.width / 2, - _grid._defaultBorder.top.width / 2); + final _Point location = _Point( + _grid!._defaultBorder.right.width / 2, + _grid!._defaultBorder.top.width / 2); if (_Rectangle.fromRect(format.paginateBounds) == _Rectangle.empty && _startLocation == location) { - _currentBounds.x += _startLocation.x; - _currentBounds.y += _startLocation.y; + _currentBounds.x = _currentBounds.x + _startLocation.x; + _currentBounds.y = _currentBounds.y + _startLocation.y; } - if (_grid._isChildGrid && row._grid._parentCell != null) { - if (_grid._parentCell._row._grid.style._cellPadding != null) { + if (_grid!._isChildGrid! && row._grid._parentCell != null) { + if (_grid!._parentCell!._row!._grid.style._cellPadding != null) { if (row._rowBreakHeight + - _grid._parentCell._row._grid.style.cellPadding.top < + _grid!._parentCell!._row!._grid.style.cellPadding.top < _currentBounds.height) { _currentBounds.y = - _grid._parentCell._row._grid.style.cellPadding.top; + _grid!._parentCell!._row!._grid.style.cellPadding.top; } } } if (row._grid._parentCell != null) { - row._grid._parentCell._row._isRowBreaksNextPage = true; - row._grid._parentCell._row._rowBreakHeight = row._rowBreakHeight + - _grid._parentCell._row._grid.style.cellPadding.top + - _grid._parentCell._row._grid.style.cellPadding.bottom; + row._grid._parentCell!._row!._isRowBreaksNextPage = true; + row._grid._parentCell!._row!._rowBreakHeight = + row._rowBreakHeight + + _grid!._parentCell!._row!._grid.style.cellPadding.top + + _grid!._parentCell!._row!._grid.style.cellPadding.bottom; } if (row._noOfPageCount > 1) { final double temp = row._rowBreakHeight; for (int j = 1; j < row._noOfPageCount; j++) { row._rowBreakHeight = 0; row.height = (row._noOfPageCount - 1) * - _currentPage.getClientSize().height; + _currentPage!.getClientSize().height; _drawRow(row); _currentPage = getNextPage(format); startPage = _currentPage; } row._rowBreakHeight = temp; row._noOfPageCount = 1; - rowResult = _drawRow(row); + rowResult = _drawRow(row)!; } else { - rowResult = _drawRow(row); + rowResult = _drawRow(row)!; } - } else if (!_grid.allowRowBreakingAcrossPages && i < length) { + } else if (!_grid!.allowRowBreakingAcrossPages && i < length) { _currentPage = getNextPage(format); break; } else if (i >= length) { @@ -394,57 +397,56 @@ class _PdfGridLayouter extends _ElementLayouter { repeatRow) { _startLocation.x = _currentBounds.x; bool isAddNextPage = false; - if (!_grid._isSingleGrid) { - for (int j = 0; j < _grid.rows.count; j++) { + if (!_grid!._isSingleGrid) { + for (int j = 0; j < _grid!.rows.count; j++) { bool isWidthGreaterthanParent = false; - for (int k = 0; k < _grid.rows[j].cells.count; k++) { - if (_grid.rows[j].cells[k].width > _currentPageBounds.width) { + for (int k = 0; k < _grid!.rows[j].cells.count; k++) { + if (_grid!.rows[j].cells[k].width > _currentPageBounds.width) { isWidthGreaterthanParent = true; } } if (isWidthGreaterthanParent && - _grid.rows[j].cells[_rowBreakPageHeightCellIndex]._pageCount > + _grid!.rows[j].cells[_rowBreakPageHeightCellIndex] + ._pageCount > 0) { isAddNextPage = true; } } } - if (!_grid._isRearranged && isAddNextPage) { - final PdfSection section = _currentPage._section; + if (!_grid!._isRearranged && isAddNextPage) { + final PdfSection section = _currentPage!._section!; final PdfPage page = PdfPage(); section._isNewPageSection = true; section._add(page); _currentPage = page; section._isNewPageSection = false; - _currentGraphics = _currentPage.graphics; - final Size clientSize = _currentPage.getClientSize(); + _currentGraphics = _currentPage!.graphics; + final Size clientSize = _currentPage!.getClientSize(); _currentBounds = _Rectangle(0, 0, clientSize.width, clientSize.height); - final int pageindex = _currentGraphics._page._section - ._indexOf(_currentGraphics._page); - if (!_grid._listOfNavigatePages.contains(pageindex)) { - _grid._listOfNavigatePages.add(pageindex); + final int pageindex = _currentGraphics!._page!._section! + ._indexOf(_currentGraphics!._page!); + if (!_grid!._listOfNavigatePages.contains(pageindex)) { + _grid!._listOfNavigatePages.add(pageindex); } } else { if (endArgs.nextPage == null) { _currentPage = getNextPage(format); } else { _currentPage = endArgs.nextPage; - _currentGraphics = endArgs.nextPage.graphics; + _currentGraphics = endArgs.nextPage!.graphics; _currentBounds = _Rectangle( 0, 0, - _currentGraphics.clientSize.width, - _currentGraphics.clientSize.height); + _currentGraphics!.clientSize.width, + _currentGraphics!.clientSize.height); } } final bool isSameSection = - _currentPage._section == param.page._section; + _currentPage!._section == param.page!._section; _currentBounds.y = format.paginateBounds.top == 0 - ? _grid._defaultBorder.top.width / 2 - : format == null - ? 0 - : format.paginateBounds.top; + ? _grid!._defaultBorder.top.width / 2 + : format.paginateBounds.top; if (_currentPage != null) { final Map pageLayoutResult = _raiseBeforePageLayout( @@ -457,25 +459,25 @@ class _PdfGridLayouter extends _ElementLayouter { } } if ((param.format != null) && - !param.format._boundsSet && + !param.format!._boundsSet && param.bounds != null && - param.bounds.height > 0 && - !_grid._isChildGrid && + param.bounds!.height > 0 && + !_grid!._isChildGrid! && isSameSection) { - _currentBounds.height = param.bounds.height; + _currentBounds.height = param.bounds!.height; } _startLocation.y = _currentBounds.y; if (_Rectangle.fromRect(format.paginateBounds) == _Rectangle.empty) { - _currentBounds.x += _startLocation.x; + _currentBounds.x = _currentBounds.x + _startLocation.x; } - if (_currentBounds.x == _grid._defaultBorder.left.width / 2) { - _currentBounds.y += _startLocation.x; + if (_currentBounds.x == _grid!._defaultBorder.left.width / 2) { + _currentBounds.y = _currentBounds.y + _startLocation.x; } - if (_grid.repeatHeader) { + if (_grid!.repeatHeader) { for (int headerIndex = 0; - headerIndex < _grid.headers.count; + headerIndex < _grid!.headers.count; headerIndex++) { - _drawRow(_grid.headers[headerIndex]); + _drawRow(_grid!.headers[headerIndex]); } } _drawRow(row); @@ -485,35 +487,35 @@ class _PdfGridLayouter extends _ElementLayouter { } } if (row._gridResult != null) { - _currentPage = row._gridResult.page; - _currentGraphics = _currentPage.graphics; + _currentPage = row._gridResult!.page; + _currentGraphics = _currentPage!.graphics; _startLocation = - _Point(row._gridResult.bounds.left, row._gridResult.bounds.top); - _currentBounds.y = row._gridResult.bounds.bottom; + _Point(row._gridResult!.bounds.left, row._gridResult!.bounds.top); + _currentBounds.y = row._gridResult!.bounds.bottom; if (startPage != _currentPage) { - final PdfSection secion = _currentPage._section; - final int startIndex = secion._indexOf(startPage) + 1; - final int endIndex = secion._indexOf(_currentPage); + final PdfSection secion = _currentPage!._section!; + final int startIndex = secion._indexOf(startPage!) + 1; + final int endIndex = secion._indexOf(_currentPage!); for (int page = startIndex; page < endIndex + 1; page++) { - PdfGraphics pageGraphics = secion._getPageByIndex(page).graphics; + PdfGraphics pageGraphics = secion._getPageByIndex(page)!.graphics; final _Point location = _Point(format.paginateBounds.left, format.paginateBounds.top); if (location == _Point.empty && _currentBounds.x > location.x && - !row._grid._isChildGrid && + !row._grid._isChildGrid! && row._grid._parentCell == null) { location.x = _currentBounds.x; } double height = page == endIndex - ? (row._gridResult.bounds.height - param.bounds.y) + ? (row._gridResult!.bounds.height - param.bounds!.y) : (_currentBounds.height - location.y); if (height <= pageGraphics.clientSize.height) { - height += param.bounds.y; + height += param.bounds!.y; } - if (row._grid._isChildGrid && row._grid._parentCell != null) { - location.x += param.bounds.x; + if (row._grid._isChildGrid! && row._grid._parentCell != null) { + location.x = location.x + param.bounds!.x; } - location.y = format == null ? 0.5 : format.paginateBounds.top; + location.y = format.paginateBounds.top; for (int c = 0; c < row.cells.count; c++) { final PdfGridCell cell = row.cells[c]; double cellWidth = 0.0; @@ -522,13 +524,13 @@ class _PdfGridLayouter extends _ElementLayouter { cellWidth += row._grid.columns[c].width; } } else { - cellWidth = _grid._isWidthSet + cellWidth = _grid!._isWidthSet ? min(cell.width, row._grid.columns[c].width) : max(cell.width, row._grid.columns[c].width); } pageGraphics = cell._drawCellBorders(pageGraphics, _Rectangle(location.x, location.y, cellWidth, height)); - location.x += cellWidth; + location.x = location.x + cellWidth; c += cell.columnSpan - 1; } } @@ -539,133 +541,138 @@ class _PdfGridLayouter extends _ElementLayouter { bool isPdfGrid = false; double maximumCellBoundsWidth = 0; if (cellBounds.isNotEmpty) { - maximumCellBoundsWidth = cellBounds[0]; + maximumCellBoundsWidth = cellBounds[0]!; } - final List> largeNavigatePage = - List>.filled(1, List.filled(2, 0)); - for (int c = 0; c < _grid.rows.count; c++) { + final List> largeNavigatePage = + List>.filled(1, List.filled(2, 0)); + for (int c = 0; c < _grid!.rows.count; c++) { if (_cellEndIndex != -1 && - _grid.rows[c].cells[_cellEndIndex].value is PdfGrid) { + _grid!.rows[c].cells[_cellEndIndex].value is PdfGrid) { final PdfGrid grid = - _grid.rows[c].cells[_cellEndIndex].value as PdfGrid; - _grid._rowLayoutBoundswidth = grid._rowLayoutBoundswidth; + _grid!.rows[c].cells[_cellEndIndex].value as PdfGrid; + _grid!._rowLayoutBoundswidth = grid._rowLayoutBoundswidth; isPdfGrid = true; - if (largeNavigatePage[0][0] < grid._listOfNavigatePages.length) { + if (largeNavigatePage[0][0]! < grid._listOfNavigatePages.length) { largeNavigatePage[0][0] = grid._listOfNavigatePages.length.toDouble(); largeNavigatePage[0][1] = cellBounds[c]; } else if ((largeNavigatePage[0][0] == grid._listOfNavigatePages.length) && - (largeNavigatePage[0][1] < cellBounds[c])) { + (largeNavigatePage[0][1]! < cellBounds[c]!)) { largeNavigatePage[0][1] = cellBounds[c]; } } } if (!isPdfGrid && cellBounds.isNotEmpty) { for (int c = 0; c < i - 1; c++) { - if (maximumCellBoundsWidth < cellBounds[c]) { - maximumCellBoundsWidth = cellBounds[c]; + if (maximumCellBoundsWidth < cellBounds[c]!) { + maximumCellBoundsWidth = cellBounds[c]!; } } - _grid._rowLayoutBoundswidth = maximumCellBoundsWidth; + _grid!._rowLayoutBoundswidth = maximumCellBoundsWidth; } else { - _grid._rowLayoutBoundswidth = largeNavigatePage[0][1]; + _grid!._rowLayoutBoundswidth = largeNavigatePage[0][1]!; } - if (_columnRanges.indexOf(range) < _columnRanges.length - 1 && + if (_columnRanges!.indexOf(range) < _columnRanges!.length - 1 && startPage != null && format.layoutType != PdfLayoutType.onePage) { - isParentCell = _grid._isChildGrid; - if (largeNavigatePage[0][0].toInt() != 0) { - final PdfSection section = _currentPage._section; - final int pageIndex = section._indexOf(_currentPage); - if (section._count > pageIndex + largeNavigatePage[0][0].toInt()) { + isParentCell = _grid!._isChildGrid; + if (largeNavigatePage[0][0]!.toInt() != 0) { + final PdfSection section = _currentPage!._section!; + final int pageIndex = section._indexOf(_currentPage!); + if (section._count > pageIndex + largeNavigatePage[0][0]!.toInt()) { _currentPage = section - ._getPageByIndex(pageIndex + largeNavigatePage[0][0].toInt()); + ._getPageByIndex(pageIndex + largeNavigatePage[0][0]!.toInt()); } else { _currentPage = PdfPage(); section._isNewPageSection = true; - section._add(_currentPage); + section._add(_currentPage!); section._isNewPageSection = false; } - _currentGraphics = _currentPage.graphics; - _currentBounds = _Rectangle(0, 0, _currentGraphics.clientSize.width, - _currentGraphics.clientSize.height); - final int pageindex = - _currentGraphics._page._section._indexOf(_currentGraphics._page); - if (!_grid._listOfNavigatePages.contains(pageindex)) { - _grid._listOfNavigatePages.add(pageindex); + _currentGraphics = _currentPage!.graphics; + _currentBounds = _Rectangle(0, 0, _currentGraphics!.clientSize.width, + _currentGraphics!.clientSize.height); + final int pageindex = _currentGraphics!._page!._section! + ._indexOf(_currentGraphics!._page!); + if (!_grid!._listOfNavigatePages.contains(pageindex)) { + _grid!._listOfNavigatePages.add(pageindex); } } else { _currentPage = getNextPage(format); } - final _Point location = _Point(_grid._defaultBorder.right.width / 2, - _grid._defaultBorder.top.width / 2); + final _Point location = _Point(_grid!._defaultBorder.right.width / 2, + _grid!._defaultBorder.top.width / 2); if (_Rectangle.fromRect(format.paginateBounds) == _Rectangle.empty && _startLocation == location) { - _currentBounds.x += _startLocation.x; - _currentBounds.y += _startLocation.y; + _currentBounds.x = _currentBounds.x + _startLocation.x; + _currentBounds.y = _currentBounds.y + _startLocation.y; } } } - result = _getLayoutResult(); - if (_currentPage != null && - _grid.style.allowHorizontalOverflow && - _grid.style.horizontalOverflowType == - PdfHorizontalOverflowType.nextPage) { - _reArrangeLayoutedPages(layoutedPages); + if (_currentPage != null) { + result = _getLayoutResult(); + if (_grid!.style.allowHorizontalOverflow && + _grid!.style.horizontalOverflowType == + PdfHorizontalOverflowType.nextPage) { + _reArrangeLayoutedPages(layoutedPages); + } + _raisePageLayouted(result); + return result; + } else { + return null; } - _raisePageLayouted(result); - return result; } bool _drawParentGridRow(PdfGrid grid) { bool present = false; grid._isDrawn = true; - double y = _currentBounds.y; + double? y = _currentBounds.y; for (int rowIndex = 0; rowIndex < grid.rows.count; rowIndex++) { final PdfGridRow row = grid.rows[rowIndex]; final PdfGridCell cell = row.cells[_cellStartIndex]; cell.value = ''; final _Point location = _Point(_currentBounds.x, _currentBounds.y); double width = grid.columns[_cellStartIndex].width; - if (width > _currentGraphics.clientSize.width) { - width = _currentGraphics.clientSize.width - 2 * location.x; + if (width > _currentGraphics!.clientSize.width) { + width = _currentGraphics!.clientSize.width - 2 * location.x; } final double height = row.height > cell.height ? row.height : cell.height; - if (_isChildGrid) { + if (_isChildGrid!) { cell._draw(_currentGraphics, _Rectangle(location.x, location.y, width, height), false); } - _currentBounds.y += height; + _currentBounds.y = _currentBounds.y + height; } for (int j = 0; j < grid.rows.count; j++) { if (grid.rows[j].cells[_cellStartIndex]._present) { present = true; if (grid.rows[j].cells[_cellStartIndex].value is PdfGrid) { - final PdfGrid childGrid = - grid.rows[j].cells[_cellStartIndex].value as PdfGrid; + final PdfGrid? childGrid = + grid.rows[j].cells[_cellStartIndex].value as PdfGrid?; grid.rows[j].cells[_cellStartIndex]._present = false; if (childGrid == _grid) { - if (!_isChildGrid) { - _currentBounds.y = y; + if (!_isChildGrid!) { + _currentBounds.y = y!; } else { if (j == 0) { - _currentBounds.y -= grid._size.height; + _currentBounds.y = _currentBounds.y - grid._size.height; } else { int k = j; while (k < grid.rows.count) { - _currentBounds.y -= grid.rows[k].height; + _currentBounds.y = _currentBounds.y - grid.rows[k].height; k++; } } } - childGrid._isDrawn = true; + childGrid!._isDrawn = true; grid.rows[j].cells[_cellStartIndex].value = childGrid; - _currentBounds.x += - grid.style.cellPadding.left + grid.style.cellPadding.right; - _currentBounds.y += - grid.style.cellPadding.top + grid.style.cellPadding.bottom; - _currentBounds.width -= 2 * _currentBounds.x; + _currentBounds.x = _currentBounds.x + + grid.style.cellPadding.left + + grid.style.cellPadding.right; + _currentBounds.y = _currentBounds.y + + grid.style.cellPadding.top + + grid.style.cellPadding.bottom; + _currentBounds.width = _currentBounds.width - 2 * _currentBounds.x; break; } else { _isChildGrid = true; @@ -674,46 +681,48 @@ class _PdfGridLayouter extends _ElementLayouter { _parentCellIndexList[_parentCellIndexList.length - 1]; _parentCellIndexList.removeAt(_parentCellIndexList.length - 1); } - _currentBounds.y = y; - _currentBounds.x += - grid.style.cellPadding.left + grid.style.cellPadding.right; - _currentBounds.y += - grid.style.cellPadding.top + grid.style.cellPadding.bottom; - final bool isPresent = _drawParentGridRow(childGrid); + _currentBounds.y = y!; + _currentBounds.x = _currentBounds.x + + grid.style.cellPadding.left + + grid.style.cellPadding.right; + _currentBounds.y = _currentBounds.y + + grid.style.cellPadding.top + + grid.style.cellPadding.bottom; + final bool isPresent = _drawParentGridRow(childGrid!); if (!isPresent) { - _currentBounds.y -= childGrid._size.height; + _currentBounds.y = _currentBounds.y - childGrid._size.height; } _isChildGrid = false; break; } } else { - y += grid.rows[j].height; + y = y! + grid.rows[j].height; } } else { - y += grid.rows[j].height; + y = y! + grid.rows[j].height; } } return present; } - _RowLayoutResult _drawRow(PdfGridRow row, - [_RowLayoutResult result, double height]) { + _RowLayoutResult? _drawRow(PdfGridRow? row, + [_RowLayoutResult? result, double? height]) { if (result == null && height == null) { _RowLayoutResult result = _RowLayoutResult(); double rowHeightWithSpan = 0; bool isHeader = false; - if (row._rowSpanExists) { - int currRowIndex = _grid.rows._indexOf(row); + if (row!._rowSpanExists) { + int currRowIndex = _grid!.rows._indexOf(row); int maxSpan = row._maximumRowSpan; if (currRowIndex == -1) { - currRowIndex = _grid.headers._indexOf(row); + currRowIndex = _grid!.headers._indexOf(row); if (currRowIndex != -1) { isHeader = true; } } for (int i = currRowIndex; i < currRowIndex + maxSpan; i++) { rowHeightWithSpan += - isHeader ? _grid.headers[i].height : _grid.rows[i].height; + isHeader ? _grid!.headers[i].height : _grid!.rows[i].height; } if ((rowHeightWithSpan > _currentBounds.height || rowHeightWithSpan + _currentBounds.y > _currentBounds.height) && @@ -725,62 +734,67 @@ class _PdfGridLayouter extends _ElementLayouter { maxSpan = cell.rowSpan; for (int i = currRowIndex; i < currRowIndex + maxSpan; i++) { rowHeightWithSpan += - isHeader ? _grid.headers[i].height : _grid.rows[i].height; + isHeader ? _grid!.headers[i].height : _grid!.rows[i].height; if ((_currentBounds.y + rowHeightWithSpan) > _currentPageBounds.height) { rowHeightWithSpan -= - isHeader ? _grid.headers[i].height : _grid.rows[i].height; - for (int j = 0; j < _grid.rows[currRowIndex].cells.count; j++) { + isHeader ? _grid!.headers[i].height : _grid!.rows[i].height; + for (int j = 0; + j < _grid!.rows[currRowIndex].cells.count; + j++) { final int newSpan = i - currRowIndex; if (!isHeader && - (_grid.rows[currRowIndex].cells[j].rowSpan == maxSpan)) { - _grid.rows[currRowIndex].cells[j].rowSpan = + (_grid!.rows[currRowIndex].cells[j].rowSpan == maxSpan)) { + _grid!.rows[currRowIndex].cells[j].rowSpan = newSpan == 0 ? 1 : newSpan; - _grid.rows[currRowIndex]._maximumRowSpan = + _grid!.rows[currRowIndex]._maximumRowSpan = newSpan == 0 ? 1 : newSpan; - _grid.rows[i].cells[j].rowSpan = maxSpan - newSpan; - final PdfGrid pdfGrid = - _grid.rows[currRowIndex].cells[j].value as PdfGrid; - _grid.rows[i].cells[j].stringFormat = - _grid.rows[currRowIndex].cells[j].stringFormat; - _grid.rows[i].cells[j].style = - _grid.rows[currRowIndex].cells[j].style; - _grid.rows[i].cells[j].style.backgroundImage = null; - _grid.rows[i].cells[j].columnSpan = - _grid.rows[currRowIndex].cells[j].columnSpan; + _grid!.rows[i].cells[j].rowSpan = maxSpan - newSpan; + PdfGrid? pdfGrid; + if (_grid!.rows[currRowIndex].cells[j].value is PdfGrid) { + pdfGrid = + _grid!.rows[currRowIndex].cells[j].value as PdfGrid?; + } + _grid!.rows[i].cells[j].stringFormat = + _grid!.rows[currRowIndex].cells[j].stringFormat; + _grid!.rows[i].cells[j].style = + _grid!.rows[currRowIndex].cells[j].style; + _grid!.rows[i].cells[j].style.backgroundImage = null; + _grid!.rows[i].cells[j].columnSpan = + _grid!.rows[currRowIndex].cells[j].columnSpan; if (pdfGrid is PdfGrid && _currentBounds.y + pdfGrid._size.height + - _grid.rows[i].height + + _grid!.rows[i].height + pdfGrid.style.cellPadding.top + pdfGrid.style.cellPadding.bottom >= _currentBounds.height) { - _grid.rows[i].cells[j].value = - _grid.rows[currRowIndex].cells[j].value; + _grid!.rows[i].cells[j].value = + _grid!.rows[currRowIndex].cells[j].value; } else if (!(pdfGrid is PdfGrid)) { - _grid.rows[i].cells[j].value = - _grid.rows[currRowIndex].cells[j].value; + _grid!.rows[i].cells[j].value = + _grid!.rows[currRowIndex].cells[j].value; } if (i > 0) { - _grid.rows[i - 1]._rowSpanExists = true; + _grid!.rows[i - 1]._rowSpanExists = true; } - _grid.rows[i].cells[j]._isRowMergeContinue = false; + _grid!.rows[i].cells[j]._isRowMergeContinue = false; } else if (isHeader && - (_grid.headers[currRowIndex].cells[j].rowSpan == + (_grid!.headers[currRowIndex].cells[j].rowSpan == maxSpan)) { - _grid.headers[currRowIndex].cells[j].rowSpan = + _grid!.headers[currRowIndex].cells[j].rowSpan = newSpan == 0 ? 1 : newSpan; - _grid.headers[i].cells[j].rowSpan = maxSpan - newSpan; - _grid.headers[i].cells[j].stringFormat = - _grid.headers[currRowIndex].cells[j].stringFormat; - _grid.headers[i].cells[j].style = - _grid.headers[currRowIndex].cells[j].style; - _grid.headers[i].cells[j].columnSpan = - _grid.headers[currRowIndex].cells[j].columnSpan; - _grid.headers[i].cells[j].value = - _grid.headers[currRowIndex].cells[j].value; - _grid.headers[i - 1]._rowSpanExists = false; - _grid.headers[i].cells[j]._isRowMergeContinue = false; + _grid!.headers[i].cells[j].rowSpan = maxSpan - newSpan; + _grid!.headers[i].cells[j].stringFormat = + _grid!.headers[currRowIndex].cells[j].stringFormat; + _grid!.headers[i].cells[j].style = + _grid!.headers[currRowIndex].cells[j].style; + _grid!.headers[i].cells[j].columnSpan = + _grid!.headers[currRowIndex].cells[j].columnSpan; + _grid!.headers[i].cells[j].value = + _grid!.headers[currRowIndex].cells[j].value; + _grid!.headers[i - 1]._rowSpanExists = false; + _grid!.headers[i].cells[j]._isRowMergeContinue = false; } } break; @@ -790,55 +804,50 @@ class _PdfGridLayouter extends _ElementLayouter { } } } - double height = + double? height = row._rowBreakHeight > 0.0 ? row._rowBreakHeight : row.height; - if (_grid._isChildGrid && _grid._parentCell != null) { + if (_grid!._isChildGrid! && _grid!._parentCell != null) { if (height + - _grid._parentCell._row._grid.style.cellPadding.bottom + - _grid._parentCell._row._grid.style.cellPadding.top > + _grid!._parentCell!._row!._grid.style.cellPadding.bottom + + _grid!._parentCell!._row!._grid.style.cellPadding.top > _currentPageBounds.height) { - if (_grid.allowRowBreakingAcrossPages) { + if (_grid!.allowRowBreakingAcrossPages) { result.isFinish = true; - if (_grid._isChildGrid && row._rowBreakHeight > 0) { - if (_grid._parentCell._row._grid.style.cellPadding != null) { - _currentBounds.y += - _grid._parentCell._row._grid.style.cellPadding.top; - } + if (_grid!._isChildGrid! && row._rowBreakHeight > 0) { + _currentBounds.y = _currentBounds.y + + _grid!._parentCell!._row!._grid.style.cellPadding.top; _currentBounds.x = _startLocation.x; } result.bounds = _currentBounds; result = _drawRowWithBreak(row, result, height); } else { - if (_grid._parentCell._row._grid.style.cellPadding != null) { - _currentBounds.y += - _grid._parentCell._row._grid.style.cellPadding.top; - height = _currentBounds.height - - _currentBounds.y - - _grid._parentCell._row._grid.style.cellPadding.bottom; - } + _currentBounds.y = _currentBounds.y + + _grid!._parentCell!._row!._grid.style.cellPadding.top; + height = _currentBounds.height - + _currentBounds.y - + _grid!._parentCell!._row!._grid.style.cellPadding.bottom; result.isFinish = false; _drawRow(row, result, height); } } else if (_currentBounds.y + - _grid._parentCell._row._grid.style.cellPadding.bottom + + _grid!._parentCell!._row!._grid.style.cellPadding.bottom + height > _currentPageBounds.height || _currentBounds.y + - _grid._parentCell._row._grid.style.cellPadding.bottom + + _grid!._parentCell!._row!._grid.style.cellPadding.bottom + height > _currentBounds.height || _currentBounds.y + - _grid._parentCell._row._grid.style.cellPadding.bottom + + _grid!._parentCell!._row!._grid.style.cellPadding.bottom + rowHeightWithSpan > _currentPageBounds.height) { if (_repeatRowIndex > -1 && _repeatRowIndex == row._index) { - if (_grid.allowRowBreakingAcrossPages) { + if (_grid!.allowRowBreakingAcrossPages) { result.isFinish = true; - if (_grid._isChildGrid && row._rowBreakHeight > 0) { - if (_grid._parentCell._row._grid.style.cellPadding != null) { - _currentBounds.y += - _grid._parentCell._row._grid.style.cellPadding.top; - } + if (_grid!._isChildGrid! && row._rowBreakHeight > 0) { + _currentBounds.y = _currentBounds.y + + _grid!._parentCell!._row!._grid.style.cellPadding.top; + _currentBounds.x = _startLocation.x; } result.bounds = _currentBounds; @@ -852,16 +861,14 @@ class _PdfGridLayouter extends _ElementLayouter { } } else { result.isFinish = true; - if (_grid._isChildGrid && row._rowBreakHeight > 0) { - if (_grid._parentCell._row._grid.style.cellPadding != null) { - height += _grid._parentCell._row._grid.style.cellPadding.bottom; - } + if (_grid!._isChildGrid! && row._rowBreakHeight > 0) { + height += _grid!._parentCell!._row!._grid.style.cellPadding.bottom; } _drawRow(row, result, height); } } else { if (height > _currentPageBounds.height) { - if (_grid.allowRowBreakingAcrossPages) { + if (_grid!.allowRowBreakingAcrossPages) { result.isFinish = true; result = _drawRowWithBreak(row, result, height); } else { @@ -872,7 +879,7 @@ class _PdfGridLayouter extends _ElementLayouter { _currentBounds.y + height > _currentBounds.height || _currentBounds.y + rowHeightWithSpan > _currentPageBounds.height) { if (_repeatRowIndex > -1 && _repeatRowIndex == row._index) { - if (_grid.allowRowBreakingAcrossPages) { + if (_grid!.allowRowBreakingAcrossPages) { result.isFinish = true; result = _drawRowWithBreak(row, result, height); } else { @@ -889,16 +896,16 @@ class _PdfGridLayouter extends _ElementLayouter { } return result; } else { - bool skipcell = false; + bool? skipcell = false; final _Point location = _currentBounds.location; - if (row._grid._isChildGrid && + if (row!._grid._isChildGrid! && row._grid.allowRowBreakingAcrossPages && _startLocation.x != _currentBounds.x && - row._getWidth() < _currentPage.getClientSize().width) { + row._getWidth() < _currentPage!.getClientSize().width) { location.x = _startLocation.x; } - result.bounds = _Rectangle(location.x, location.y, 0, 0); - height = _reCalculateHeight(row, height); + result!.bounds = _Rectangle(location.x, location.y, 0, 0); + height = _reCalculateHeight(row, height!); for (int i = _cellStartIndex; i <= _cellEndIndex; i++) { final bool cancelSpans = i > _cellEndIndex + 1 && row.cells[i].columnSpan > 1; @@ -907,41 +914,41 @@ class _PdfGridLayouter extends _ElementLayouter { row.cells[i + j]._isCellMergeContinue = true; } } - final _Size size = _Size(_grid.columns[i].width, height); - if (size.width > _currentGraphics.clientSize.width) { - size.width = _currentGraphics.clientSize.width; + final _Size size = _Size(_grid!.columns[i].width, height); + if (size.width > _currentGraphics!.clientSize.width) { + size.width = _currentGraphics!.clientSize.width; } - if (_grid._isChildGrid && _grid.style.allowHorizontalOverflow) { - if (size.width >= _currentGraphics.clientSize.width) { - size.width -= 2 * _currentBounds.x; + if (_grid!._isChildGrid! && _grid!.style.allowHorizontalOverflow) { + if (size.width >= _currentGraphics!.clientSize.width) { + size.width = size.width - 2 * _currentBounds.x; } } - if (!_checkIfDefaultFormat(_grid.columns[i].format) && + if (!_checkIfDefaultFormat(_grid!.columns[i].format) && _checkIfDefaultFormat(row.cells[i].stringFormat)) { - row.cells[i].stringFormat = _grid.columns[i].format; + row.cells[i].stringFormat = _grid!.columns[i].format; } PdfGridCellStyle cellstyle = row.cells[i].style; final Map bclResult = _raiseBeforeCellLayout( _currentGraphics, - _currentRowIndex, + row._isHeaderRow ? _currentHeaderRowIndex : _currentRowIndex, i, _Rectangle(location.x, location.y, size.width, size.height), (row.cells[i].value is String) ? row.cells[i].value.toString() : '', cellstyle, row._isHeaderRow); cellstyle = bclResult['style']; - final PdfGridBeginCellLayoutArgs gridbclArgs = bclResult['args']; + final PdfGridBeginCellLayoutArgs? gridbclArgs = bclResult['args']; row.cells[i].style = cellstyle; if (gridbclArgs != null) { skipcell = gridbclArgs.skip; } - if (!skipcell) { + if (!skipcell!) { if (row.cells[i].value is PdfGrid) { final PdfGrid grid = row.cells[i].value as PdfGrid; grid._parentCellIndex = i; } - final _PdfStringLayoutResult stringResult = row.cells[i]._draw( + final _PdfStringLayoutResult? stringResult = row.cells[i]._draw( _currentGraphics, _Rectangle(location.x, location.y, size.width, size.height), cancelSpans); @@ -985,22 +992,22 @@ class _PdfGridLayouter extends _ElementLayouter { _rowBreakPageHeightCellIndex = i; for (int k = 0; k < grid._listOfNavigatePages.length; k++) { final int pageIndex = grid._listOfNavigatePages[k]; - if (!_grid._listOfNavigatePages.contains(pageIndex)) { - _grid._listOfNavigatePages.add(pageIndex); + if (!_grid!._listOfNavigatePages.contains(pageIndex)) { + _grid!._listOfNavigatePages.add(pageIndex); } } - if (_grid.columns[i].width >= _currentGraphics.clientSize.width) { + if (_grid!.columns[i].width >= _currentGraphics!.clientSize.width) { location.x = grid._rowLayoutBoundswidth; - location.x += grid.style.cellSpacing; + location.x = location.x + grid.style.cellSpacing; } else { - location.x += _grid.columns[i].width; + location.x = location.x + _grid!.columns[i].width; } } else { - location.x += _grid.columns[i].width; + location.x = location.x + _grid!.columns[i].width; } } if (!row._rowMergeComplete || row._isRowHeightSet) { - _currentBounds.y += height; + _currentBounds.y = _currentBounds.y + height; } result.bounds = _Rectangle(result.bounds.x, result.bounds.y, location.x, location.y); @@ -1008,11 +1015,11 @@ class _PdfGridLayouter extends _ElementLayouter { } } - double _reCalculateHeight(PdfGridRow row, double height) { + double _reCalculateHeight(PdfGridRow? row, double height) { double newHeight = 0.0; for (int i = _cellStartIndex; i <= _cellEndIndex; i++) { - if (row.cells[i]._remainingString != null && - row.cells[i]._remainingString.isNotEmpty) { + if (row!.cells[i]._remainingString != null && + row.cells[i]._remainingString!.isNotEmpty) { newHeight = max(newHeight, row.cells[i]._measureHeight()); } } @@ -1020,9 +1027,9 @@ class _PdfGridLayouter extends _ElementLayouter { } _RowLayoutResult _drawRowWithBreak( - PdfGridRow row, _RowLayoutResult result, double height) { + PdfGridRow row, _RowLayoutResult result, double? height) { final _Point location = _currentBounds.location; - if (row._grid._isChildGrid && + if (row._grid._isChildGrid! && row._grid.allowRowBreakingAcrossPages && _startLocation.x != _currentBounds.x) { location.x = _startLocation.x; @@ -1038,9 +1045,9 @@ class _PdfGridLayouter extends _ElementLayouter { row._grid.style.cellPadding.bottom < _currentPageBounds.height) { row._rowBreakHeight = - _currentBounds.y + height - _currentPageBounds.height; + _currentBounds.y + height! - _currentPageBounds.height; } else { - row._rowBreakHeight = height; + row._rowBreakHeight = height!; result.isFinish = false; return result; } @@ -1065,7 +1072,7 @@ class _PdfGridLayouter extends _ElementLayouter { } } _Size size = _Size( - _grid.columns[i].width, + _grid!.columns[i].width, _newheight > 0.0 ? _newheight : _currentBounds.height < _currentPageBounds.height @@ -1074,24 +1081,24 @@ class _PdfGridLayouter extends _ElementLayouter { if (size.width == 0) { size = _Size(row.cells[i].width, size.height); } - if (!_checkIfDefaultFormat(_grid.columns[i].format) && + if (!_checkIfDefaultFormat(_grid!.columns[i].format) && _checkIfDefaultFormat(row.cells[i].stringFormat)) { - row.cells[i].stringFormat = _grid.columns[i].format; + row.cells[i].stringFormat = _grid!.columns[i].format; } PdfGridCellStyle cellstyle = row.cells[i].style; final Map cellLayoutResult = _raiseBeforeCellLayout( _currentGraphics, - _currentRowIndex, + row._isHeaderRow ? _currentHeaderRowIndex : _currentRowIndex, i, _Rectangle(location.x, location.y, size.width, size.height), row.cells[i].value is String ? row.cells[i].value.toString() : '', cellstyle, row._isHeaderRow); cellstyle = cellLayoutResult['style']; - final PdfGridBeginCellLayoutArgs bclArgs = cellLayoutResult['args']; + final PdfGridBeginCellLayoutArgs? bclArgs = cellLayoutResult['args']; row.cells[i].style = cellstyle; final bool skipcell = bclArgs != null ? bclArgs.skip : false; - _PdfStringLayoutResult stringResult; + _PdfStringLayoutResult? stringResult; if (!skipcell) { stringResult = row.cells[i]._draw( _currentGraphics, @@ -1102,7 +1109,7 @@ class _PdfGridLayouter extends _ElementLayouter { if (stringResult != null) { row.cells[i]._finished = false; row.cells[i]._remainingString = stringResult._remainder ?? ''; - if (row._grid._isChildGrid) { + if (row._grid._isChildGrid!) { row._rowBreakHeight = height - stringResult._size.height; } } else if (row.cells[i].value is PdfImage) { @@ -1127,21 +1134,22 @@ class _PdfGridLayouter extends _ElementLayouter { row.cells[i]._pageCount = grid._listOfNavigatePages.length; for (int i = 0; i < grid._listOfNavigatePages.length; i++) { final int pageIndex = grid._listOfNavigatePages[i]; - if (!_grid._listOfNavigatePages.contains(pageIndex)) { - _grid._listOfNavigatePages.add(pageIndex); + if (!_grid!._listOfNavigatePages.contains(pageIndex)) { + _grid!._listOfNavigatePages.add(pageIndex); } } - if (_grid.columns[i].width >= _currentGraphics.clientSize.width) { + if (_grid!.columns[i].width >= _currentGraphics!.clientSize.width) { location.x = grid._rowLayoutBoundswidth; - location.x += grid.style.cellSpacing; + location.x = location.x + grid.style.cellSpacing; } else { - location.x += _grid.columns[i].width; + location.x = location.x + _grid!.columns[i].width; } } else { - location.x += _grid.columns[i].width; + location.x = location.x + _grid!.columns[i].width; } } - _currentBounds.y += _newheight > 0.0 ? _newheight : height; + _currentBounds.y = + _currentBounds.y + (_newheight > 0.0 ? _newheight : height); result.bounds = _Rectangle(result.bounds.x, result.bounds.y, location.x, location.y); return result; @@ -1166,20 +1174,20 @@ class _PdfGridLayouter extends _ElementLayouter { format.wordWrap == defaultFormat.wordWrap; } - void _reArrangeLayoutedPages(Map> layoutedPages) { - final PdfDocument document = _currentPage._document; - final List pages = layoutedPages.keys.toList(); + void _reArrangeLayoutedPages(Map> layoutedPages) { + final PdfDocument? document = _currentPage!._document; + final List pages = layoutedPages.keys.toList(); for (int i = 0; i < pages.length; i++) { - final PdfPage page = pages[i]; + final PdfPage page = pages[i]!; page._section = null; - document.pages._remove(page); + document!.pages._remove(page); } for (int i = 0; i < layoutedPages.length; i++) { for (int j = i; j < layoutedPages.length; - j += layoutedPages.length ~/ _columnRanges.length) { - final PdfPage page = pages[j]; - if (document.pages.indexOf(page) == -1) { + j += layoutedPages.length ~/ _columnRanges!.length) { + final PdfPage page = pages[j]!; + if (document!.pages.indexOf(page) == -1) { document.pages._addPage(page); } } @@ -1187,28 +1195,28 @@ class _PdfGridLayouter extends _ElementLayouter { } void _reArrangePages(PdfPage page) { - final List pages = []; - int pageCount = page._document.pages.count; + final List pages = []; + int pageCount = page._document!.pages.count; int m = 0; - int n = _columnRanges.length; - if (pageCount <= _columnRanges.length) { - for (int i = 0; i < _columnRanges.length; i++) { - page._document.pages.add(); - if (page._document.pages.count > _columnRanges.length + 1) { + int n = _columnRanges!.length; + if (pageCount <= _columnRanges!.length) { + for (int i = 0; i < _columnRanges!.length; i++) { + page._document!.pages.add(); + if (page._document!.pages.count > _columnRanges!.length + 1) { break; } } } - pageCount = page._document.pages.count; + pageCount = page._document!.pages.count; for (int i = 0; i < pageCount; i++) { if (m < pageCount && pages.length != pageCount) { - final PdfPage tempPage = page._document.pages[m]; + final PdfPage? tempPage = page._document!.pages[m]; if (!pages.contains(tempPage)) { pages.add(tempPage); } } if (n < pageCount && pages.length != pageCount) { - final PdfPage tempPage = page._document.pages[n]; + final PdfPage? tempPage = page._document!.pages[n]; if (!pages.contains(tempPage)) { pages.add(tempPage); } @@ -1219,27 +1227,27 @@ class _PdfGridLayouter extends _ElementLayouter { m++; n++; } - final PdfDocument document = page._document; + final PdfDocument? document = page._document; for (int i = 0; i < pages.length; i++) { - final PdfPage tempPage = pages[i]; + final PdfPage tempPage = pages[i]!; tempPage._section = null; - document.pages._remove(tempPage); + document!.pages._remove(tempPage); } for (int i = 0; i < pages.length; i++) { - document.pages._addPage(pages[i]); + document!.pages._addPage(pages[i]!); } } - PdfPage getNextPage(PdfLayoutFormat format) { - final PdfSection section = _currentPage._section; - PdfPage nextPage; - final int index = section._indexOf(_currentPage); - if (_currentPage._document.pages.count > 1 && + PdfPage? getNextPage(PdfLayoutFormat format) { + final PdfSection section = _currentPage!._section!; + PdfPage? nextPage; + final int index = section._indexOf(_currentPage!); + if (_currentPage!._document!.pages.count > 1 && _hType == PdfHorizontalOverflowType.nextPage && flag && - _columnRanges.length > 1) { - _grid._isRearranged = true; - _reArrangePages(_currentPage); + _columnRanges!.length > 1) { + _grid!._isRearranged = true; + _reArrangePages(_currentPage!); } flag = false; if (index == section._count - 1) { @@ -1250,14 +1258,14 @@ class _PdfGridLayouter extends _ElementLayouter { } else { nextPage = section._getPageByIndex(index + 1); } - _currentGraphics = nextPage.graphics; + _currentGraphics = nextPage!.graphics; final int pageindex = - _currentGraphics._page._section._indexOf(_currentGraphics._page); - if (!_grid._listOfNavigatePages.contains(pageindex)) { - _grid._listOfNavigatePages.add(pageindex); + _currentGraphics!._page!._section!._indexOf(_currentGraphics!._page!); + if (!_grid!._listOfNavigatePages.contains(pageindex)) { + _grid!._listOfNavigatePages.add(pageindex); } - _currentBounds = _Rectangle(0, 0, _currentGraphics.clientSize.width, - _currentGraphics.clientSize.height); + _currentBounds = _Rectangle(0, 0, _currentGraphics!.clientSize.width, + _currentGraphics!.clientSize.height); if (_Rectangle.fromRect(format.paginateBounds) != _Rectangle.empty) { _currentBounds.x = format.paginateBounds.left; _currentBounds.y = format.paginateBounds.top; @@ -1267,11 +1275,11 @@ class _PdfGridLayouter extends _ElementLayouter { } PdfLayoutResult _getLayoutResult() { - if (_grid._isChildGrid && _grid.allowRowBreakingAcrossPages) { - for (int rowIndex = 0; rowIndex < _grid.rows.count; rowIndex++) { - final PdfGridRow row = _grid.rows[rowIndex]; + if (_grid!._isChildGrid! && _grid!.allowRowBreakingAcrossPages) { + for (int rowIndex = 0; rowIndex < _grid!.rows.count; rowIndex++) { + final PdfGridRow row = _grid!.rows[rowIndex]; if (row._rowBreakHeight > 0) { - _startLocation.y = _currentPage._origin.dy; + _startLocation.y = _currentPage!._origin.dy; } } } @@ -1280,22 +1288,25 @@ class _PdfGridLayouter extends _ElementLayouter { _currentBounds.width, _currentBounds.y - _currentLocation.y) : Rect.fromLTWH(_startLocation.x, _startLocation.y, _currentBounds.width, _currentBounds.y - _startLocation.y); - return PdfLayoutResult._(_currentPage, bounds); + return PdfLayoutResult._(_currentPage!, bounds); } Map _raiseBeforePageLayout( - PdfPage currentPage, Rect currentBounds, int currentRow) { + PdfPage? currentPage, Rect currentBounds, int? currentRow) { bool cancel = false; - if (_element._raiseBeginPageLayout) { + if (_element!._raiseBeginPageLayout) { final PdfGridBeginPageLayoutArgs args = - PdfGridBeginPageLayoutArgs._(currentBounds, currentPage, currentRow); - _element._onBeginPageLayout(args); + PdfGridBeginPageLayoutArgs._(currentBounds, currentPage!, currentRow); + _element!._onBeginPageLayout(args); if (_Rectangle.fromRect(currentBounds) != _Rectangle.fromRect(args.bounds)) { _isChanged = true; _currentLocation = _Point(args.bounds.left, args.bounds.top); - _grid._measureColumnsWidth(_Rectangle(args.bounds.left, args.bounds.top, - args.bounds.width + args.bounds.left, args.bounds.height)); + _grid!._measureColumnsWidth(_Rectangle( + args.bounds.left, + args.bounds.top, + args.bounds.width + args.bounds.left, + args.bounds.height)); } cancel = args.cancel; currentBounds = args.bounds; @@ -1310,135 +1321,128 @@ class _PdfGridLayouter extends _ElementLayouter { PdfGridEndPageLayoutArgs _raisePageLayouted(PdfLayoutResult result) { final PdfGridEndPageLayoutArgs args = PdfGridEndPageLayoutArgs._(result); - if (_element._raisePageLayouted) { - _element._onEndPageLayout(args); + if (_element!._raisePageLayouted) { + _element!._onEndPageLayout(args); } return args; } Map _raiseBeforeCellLayout( - PdfGraphics graphics, + PdfGraphics? graphics, int rowIndex, int cellIndex, _Rectangle bounds, String value, - PdfGridCellStyle style, + PdfGridCellStyle? style, bool isHeaderRow) { - PdfGridBeginCellLayoutArgs args; - if (_grid._raiseBeginCellLayout) { + PdfGridBeginCellLayoutArgs? args; + if (_grid!._raiseBeginCellLayout) { args = PdfGridBeginCellLayoutArgs._( - graphics, rowIndex, cellIndex, bounds, value, style, isHeaderRow); - _grid._onBeginCellLayout(args); + graphics!, rowIndex, cellIndex, bounds, value, style, isHeaderRow); + _grid!._onBeginCellLayout(args); style = args.style; } return {'args': args, 'style': style}; } void _raiseAfterCellLayout( - PdfGraphics graphics, + PdfGraphics? graphics, int rowIndex, int cellIndex, _Rectangle bounds, String value, - PdfGridCellStyle cellstyle, + PdfGridCellStyle? cellstyle, bool isHeaderRow) { PdfGridEndCellLayoutArgs args; - if (_grid._raiseEndCellLayout) { - args = PdfGridEndCellLayoutArgs._( - graphics, rowIndex, cellIndex, bounds, value, cellstyle, isHeaderRow); - _grid._onEndCellLayout(args); + if (_grid!._raiseEndCellLayout) { + args = PdfGridEndCellLayoutArgs._(graphics!, rowIndex, cellIndex, bounds, + value, cellstyle, isHeaderRow); + _grid!._onEndCellLayout(args); } } //Override methods @override - PdfLayoutResult _layoutInternal(_PdfLayoutParams param) { - ArgumentError.checkNotNull(param); + PdfLayoutResult? _layoutInternal(_PdfLayoutParams param) { final PdfLayoutFormat format = _getFormat(param.format); _currentPage = param.page; if (_currentPage != null) { - final Size size = _currentPage.getClientSize(); + final Size size = _currentPage!.getClientSize(); final double pageHeight = size.height; final double pageWidth = size.width; if (pageHeight > pageWidth || - (param.page._orientation == PdfPageOrientation.landscape && + (param.page!._orientation == PdfPageOrientation.landscape && format.breakType == PdfLayoutBreakType.fitPage)) { _currentPageBounds = _Size.fromSize(size); } else { - _currentPageBounds = _Size.fromSize(_currentPage.size); + _currentPageBounds = _Size.fromSize(_currentPage!.size); } } else { - _currentPageBounds = _Size.fromSize(_currentGraphics.clientSize); + _currentPageBounds = _Size.fromSize(_currentGraphics!.clientSize); } if (_currentPage != null) { - _currentGraphics = _currentPage.graphics; + _currentGraphics = _currentPage!.graphics; } - if (_currentGraphics._layer != null) { - final int index = _currentGraphics._page is PdfPage - ? _currentGraphics._page._section._indexOf(_currentGraphics._page) - : _currentGraphics._page._defaultLayerIndex; - if (!_grid._listOfNavigatePages.contains(index)) { - _grid._listOfNavigatePages.add(index); + if (_currentGraphics!._layer != null) { + final int index = _currentGraphics!._page is PdfPage + ? _currentGraphics!._page!._section! + ._indexOf(_currentGraphics!._page!) + : _currentGraphics!._page!._defaultLayerIndex; + if (!_grid!._listOfNavigatePages.contains(index)) { + _grid!._listOfNavigatePages.add(index); } } _currentBounds = _Rectangle( - param.bounds.x, - param.bounds.y, - format != null && - format.breakType == PdfLayoutBreakType.fitColumnsToPage - ? _grid.columns._width - : _currentGraphics.clientSize.width, - _currentGraphics.clientSize.height); - if (_grid.rows.count != 0) { - _currentBounds.width = (param.bounds.width > 0) - ? param.bounds.width - : _grid.rows[0].cells[0].style.borders != null && - _grid.rows[0].cells[0].style.borders.left != null - ? (_currentBounds.width - - _grid.rows[0].cells[0].style.borders.left.width / 2) - : _currentBounds.width; - } else if (_grid.headers.count != 0) { - _currentBounds.width = (param.bounds.width > 0) - ? param.bounds.width - : _grid.headers[0].cells[0].style.borders != null && - _grid.headers[0].cells[0].style.borders.left != null - ? (_currentBounds.width - - _grid.headers[0].cells[0].style.borders.left.width / 2) - : _currentBounds.width; + param.bounds!.x, + param.bounds!.y, + format.breakType == PdfLayoutBreakType.fitColumnsToPage + ? _grid!.columns._width + : _currentGraphics!.clientSize.width, + _currentGraphics!.clientSize.height); + if (_grid!.rows.count != 0) { + _currentBounds.width = (param.bounds!.width > 0) + ? param.bounds!.width + : (_currentBounds.width - + _grid!.rows[0].cells[0].style.borders.left.width / 2); + } else if (_grid!.headers.count != 0) { + _currentBounds.width = (param.bounds!.width > 0) + ? param.bounds!.width + : (_currentBounds.width - + _grid!.headers[0].cells[0].style.borders.left.width / 2); } - _startLocation = param.bounds.location; - if (_grid.style.allowHorizontalOverflow && - _currentBounds.width > _currentGraphics.clientSize.width) { + _startLocation = param.bounds!.location; + if (_grid!.style.allowHorizontalOverflow && + _currentBounds.width > _currentGraphics!.clientSize.width) { _currentBounds.width = - _currentGraphics.clientSize.width - _currentBounds.x; + _currentGraphics!.clientSize.width - _currentBounds.x; } - if (_grid._isChildGrid) { - _childHeight = param.bounds.height; + if (_grid!._isChildGrid!) { + _childHeight = param.bounds!.height; } - if (param.format != null && param.format._boundsSet) { - if (param.format.paginateBounds.height > 0) { - _currentBounds.height = param.format.paginateBounds.height; + if (param.format != null && param.format!._boundsSet) { + if (param.format!.paginateBounds.height > 0) { + _currentBounds.height = param.format!.paginateBounds.height; } - } else if (param.bounds.height > 0 && !_grid._isChildGrid) { - _currentBounds.height = param.bounds.height; + } else if (param.bounds!.height > 0 && !_grid!._isChildGrid!) { + _currentBounds.height = param.bounds!.height; } - if (!_grid._isChildGrid) { - _hType = _grid.style.horizontalOverflowType; + if (!_grid!._isChildGrid!) { + _hType = _grid!.style.horizontalOverflowType; } - if (!_grid.style.allowHorizontalOverflow) { - _grid._measureColumnsWidth(_currentBounds); - _columnRanges.add([0, _grid.columns.count - 1]); + if (!_grid!.style.allowHorizontalOverflow) { + _grid!._measureColumnsWidth(_currentBounds); + _columnRanges!.add([0, _grid!.columns.count - 1]); } else { - _grid._measureColumnsWidth(); + _grid!._measureColumnsWidth(); _determineColumnDrawRanges(); } - if (_grid._hasRowSpan) { - for (int i = 0; i < _grid.rows.count; i++) { - _grid.rows[i].height; - if (!_grid.rows[i]._isRowHeightSet) { - _grid.rows[i]._isRowHeightSet = true; + if (_grid!._hasRowSpan) { + for (int i = 0; i < _grid!.rows.count; i++) { + _grid!.rows[i].height; + if (!_grid!.rows[i]._isRowHeightSet) { + _grid!.rows[i]._isRowHeightSet = true; } else { - _grid.rows[i]._isRowSpanRowHeightSet = true; + _grid!.rows[i]._isRowSpanRowHeightSet = true; } } } @@ -1451,6 +1455,6 @@ class _RowLayoutResult { bounds = _Rectangle.empty; isFinish = false; } - bool isFinish; - _Rectangle bounds; + late bool isFinish; + late _Rectangle bounds; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid.dart index 7eb23fa57..51daa7549 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid.dart @@ -1,90 +1,473 @@ part of pdf; /// Represents a flexible grid that consists of columns and rows. +/// ```dart +/// //Create a new PDF document +/// PdfDocument document = PdfDocument(); +/// //Create a PdfGrid class +/// PdfGrid grid = PdfGrid(); +/// //Add the columns to the grid +/// grid.columns.add(count: 3); +/// //Add header to the grid +/// grid.headers.add(1); +/// //Add the rows to the grid +/// PdfGridRow header = grid.headers[0]; +/// header.cells[0].value = 'Employee ID'; +/// header.cells[1].value = 'Employee Name'; +/// header.cells[2].value = 'Salary'; +/// //Add rows to grid +/// PdfGridRow row = grid.rows.add(); +/// row.cells[0].value = 'E01'; +/// row.cells[1].value = 'Clay'; +/// row.cells[2].value = '\$10,000'; +/// row = grid.rows.add(); +/// row.cells[0].value = 'E02'; +/// row.cells[1].value = 'Simon'; +/// row.cells[2].value = '\$12,000'; +/// //Set the grid style +/// grid.style = PdfGridStyle( +/// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), +/// backgroundBrush: PdfBrushes.blue, +/// textBrush: PdfBrushes.white, +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 25)); +/// //Draw the grid +/// grid.draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfGrid extends PdfLayoutElement { /// Initializes a new instance of the [PdfGrid] class. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGrid() { _initialize(); } //Fields - PdfGridColumnCollection _columns; - PdfGridRowCollection _rows; - PdfGridStyle _style; - PdfGridHeaderCollection _headers; - PdfBorders _defaultBorder; + PdfGridColumnCollection? _columns; + PdfGridRowCollection? _rows; + PdfGridStyle? _style; + PdfGridHeaderCollection? _headers; + late PdfBorders _defaultBorder; /// Gets or sets a value indicating whether to repeat header. - bool repeatHeader; - _Size _gridSize; + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Sets repeatHeader + /// grid.repeatHeader = true; + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// for (int i = 0; i < 500; i++) { + /// final PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'Row - $i Cell - 1'; + /// row.cells[1].value = 'Row - $i Cell - 2'; + /// row.cells[2].value = 'Row - $i Cell - 3'; + /// } + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + late bool repeatHeader; + late _Size _gridSize; /// Gets or sets a value indicating whether to split or cut rows that overflow a page. - bool allowRowBreakingAcrossPages; - bool _isChildGrid; - PdfGridCell _parentCell; - double _initialWidth; - PdfLayoutFormat _layoutFormat; - bool _isComplete; - bool _isWidthSet; - int _parentCellIndex; - bool _isDrawn; - double _rowLayoutBoundswidth; - List _listOfNavigatePages; - bool _isRearranged; - _Rectangle _gridLocation; - bool _isPageWidth; - bool _isSingleGrid; - bool _hasColumnSpan; - bool _hasRowSpan; - PdfFont _defaultFont; + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Sets allowRowBreakingAcrossPages + /// grid.allowRowBreakingAcrossPages = true; + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(100); + /// //Add the rows to the grid + /// for (int i = 0; i < 100; i++) { + /// final PdfGridRow header = grid.headers[i]; + /// header.cells[0].value = 'Header - $i Cell - 1'; + /// final PdfTextElement element = PdfTextElement(font: PdfStandardFont(PdfFontFamily.timesRoman, 15)); + /// element.text = + /// 'Header - $i Cell - 2 Sample text for pagination. Lorem ipsum dolor sit amet,\r\n consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua'; + /// header.cells[1].value = element; + /// header.cells[2].value = 'Header - $i Cell - 3'; + /// } + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + late bool allowRowBreakingAcrossPages; + bool? _isChildGrid; + PdfGridCell? _parentCell; + late double _initialWidth; + PdfLayoutFormat? _layoutFormat; + late bool _isComplete; + late bool _isWidthSet; + late int _parentCellIndex; + late bool _isDrawn; + late double _rowLayoutBoundswidth; + late List _listOfNavigatePages; + late bool _isRearranged; + late _Rectangle _gridLocation; + late bool _isPageWidth; + late bool _isSingleGrid; + late bool _hasColumnSpan; + late bool _hasRowSpan; + late PdfFont _defaultFont; bool _headerRow = true; bool _bandedRow = true; - bool _bandedColumn; - bool _totalRow; - bool _firstColumn; - bool _lastColumn; - bool _isBuiltinStyle; - PdfGridBuiltInStyle _gridBuiltinStyle; + late bool _bandedColumn; + late bool _totalRow; + late bool _firstColumn; + late bool _lastColumn; + late bool _isBuiltinStyle; + PdfGridBuiltInStyle? _gridBuiltinStyle; + Map? _boldFontCache; + Map? _regularFontCache; + Map? _italicFontCache; //Events /// The event raised on starting cell lay outing. - PdfGridBeginCellLayoutCallback beginCellLayout; + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// // Sets the event raised on starting cell lay outing. + /// grid.beginCellLayout = (Object sender, PdfGridBeginCellLayoutArgs args) { + /// if (args.rowIndex == 1 && args.cellIndex == 1) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// if (args.isHeaderRow && args.cellIndex == 0) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// }; + /// grid.style.cellPadding = PdfPaddings(); + /// grid.style.cellPadding.all = 15; + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfGridBeginCellLayoutCallback? beginCellLayout; /// The event raised on finished cell layout. - PdfGridEndCellLayoutCallback endCellLayout; + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// // Sets the event raised on finished cell layout. + /// grid.endCellLayout = (Object sender, PdfGridEndCellLayoutArgs args) { + /// if (args.isHeaderRow && args.cellIndex == 0) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// if (args.rowIndex == 1 && args.cellIndex == 1) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// }; + /// grid.style.cellPadding = PdfPaddings(); + /// grid.style.cellPadding.all = 15; + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfGridEndCellLayoutCallback? endCellLayout; //Properties /// Gets the column collection of the PdfGrid. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Set the grid style + /// grid.style = PdfGridStyle( + /// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), + /// backgroundBrush: PdfBrushes.blue, + /// textBrush: PdfBrushes.white, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 25)); + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridColumnCollection get columns { _columns ??= PdfGridColumnCollection(this); - return _columns; + return _columns!; } /// Gets the row collection from the PdfGrid. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Set the grid style + /// grid.style = PdfGridStyle( + /// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), + /// backgroundBrush: PdfBrushes.blue, + /// textBrush: PdfBrushes.white, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 25)); + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridRowCollection get rows { _rows ??= PdfGridRowCollection(this); - return _rows; + return _rows!; } /// Gets the headers collection from the PdfGrid. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Set the grid style + /// grid.style = PdfGridStyle( + /// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), + /// backgroundBrush: PdfBrushes.blue, + /// textBrush: PdfBrushes.white, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 25)); + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridHeaderCollection get headers { _headers ??= PdfGridHeaderCollection(this); - return _headers; + return _headers!; } /// Gets the grid style. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Set the grid style + /// grid.style = PdfGridStyle( + /// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), + /// backgroundBrush: PdfBrushes.blue, + /// textBrush: PdfBrushes.white, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 25)); + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridStyle get style { _style ??= PdfGridStyle(); - return _style; + return _style!; } - /// Sets the grid style. set style(PdfGridStyle value) { _style = value; } _Size get _size { - if (_gridSize == null || _gridSize == _Size.empty) { + if (_gridSize == _Size.empty) { _gridSize = _measure(); } return _gridSize; @@ -108,7 +491,7 @@ class PdfGrid extends PdfLayoutElement { _gridLocation = _Rectangle.empty; _hasColumnSpan = false; _hasRowSpan = false; - _isChildGrid = false; + _isChildGrid ??= false; _isPageWidth = false; _isRearranged = false; _initialWidth = 0; @@ -251,10 +634,10 @@ class PdfGrid extends PdfLayoutElement { while (colSpan > 1) { currentCellIndex++; rows[currentRowIndex] - ._cells[currentCellIndex] + ._cells![currentCellIndex] ._isCellMergeContinue = true; rows[currentRowIndex] - ._cells[currentCellIndex] + ._cells![currentCellIndex] ._isRowMergeContinue = true; colSpan--; } @@ -285,9 +668,9 @@ class PdfGrid extends PdfLayoutElement { } } - void _measureColumnsWidth([_Rectangle bounds]) { + void _measureColumnsWidth([_Rectangle? bounds]) { if (bounds == null) { - List widths = List.filled(columns.count, 0); + List widths = List.filled(columns.count, 0); double cellWidth = 0; if (headers.count > 0) { for (int i = 0; i < headers[0].cells.count; i++) { @@ -320,7 +703,7 @@ class PdfGrid extends PdfLayoutElement { _gridLocation.x); } cellWidth = max( - widths[i], + widths[i]!, max( cellWidth, _initialWidth > 0.0 @@ -337,62 +720,62 @@ class PdfGrid extends PdfLayoutElement { for (int i = 0; i < rows.count; i++) { for (int j = 0; j < columns.count; j++) { if (rows[i].cells[j].columnSpan > 1) { - double totalWidth = widths[j]; + double totalWidth = widths[j]!; for (int k = 1; k < rows[i].cells[j].columnSpan; k++) { - totalWidth += widths[j + k]; + totalWidth = totalWidth + widths[j + k]!; } if (rows[i].cells[j].width > totalWidth) { double extendedWidth = rows[i].cells[j].width - totalWidth; extendedWidth = extendedWidth / rows[i].cells[j].columnSpan; for (int k = j; k < j + rows[i].cells[j].columnSpan; k++) { - widths[k] += extendedWidth; + widths[k] = widths[k]! + extendedWidth; } } } } } - if (_isChildGrid && _initialWidth != 0) { + if (_isChildGrid! && _initialWidth != 0) { widths = columns._getDefaultWidths(_initialWidth); } for (int i = 0; i < columns.count; i++) { if (columns[i].width < 0) { - columns[i]._width = widths[i]; + columns[i]._width = widths[i]!; } else if (columns[i].width > 0 && !columns[i]._isCustomWidth) { - columns[i]._width = widths[i]; + columns[i]._width = widths[i]!; } } } else { - List widths = columns._getDefaultWidths(bounds.width - bounds.x); + List widths = columns._getDefaultWidths(bounds.width - bounds.x); for (int i = 0; i < columns.count; i++) { if (columns[i].width < 0) { - columns[i]._width = widths[i]; + columns[i]._width = widths[i]!; } else if (columns[i].width > 0 && !columns[i]._isCustomWidth && - widths[i] > 0 && + widths[i]! > 0 && _isComplete) { - columns[i]._width = widths[i]; + columns[i]._width = widths[i]!; } } if (_parentCell != null && (!style.allowHorizontalOverflow) && - (!_parentCell._row._grid.style.allowHorizontalOverflow)) { + (!_parentCell!._row!._grid.style.allowHorizontalOverflow)) { double padding = 0; double columnWidth = 0; int columnCount = columns.count; - if (_parentCell.style.cellPadding != null) { - padding += _parentCell.style.cellPadding.left + - _parentCell.style.cellPadding.right; - } else if (_parentCell._row._grid.style.cellPadding != null) { - padding += _parentCell._row._grid.style.cellPadding.left + - _parentCell._row._grid.style.cellPadding.right; - } - for (int i = 0; i < _parentCell.columnSpan; i++) { + if (_parentCell!.style.cellPadding != null) { + padding += _parentCell!.style.cellPadding!.left + + _parentCell!.style.cellPadding!.right; + } else { + padding += _parentCell!._row!._grid.style.cellPadding.left + + _parentCell!._row!._grid.style.cellPadding.right; + } + for (int i = 0; i < _parentCell!.columnSpan; i++) { columnWidth += - _parentCell._row._grid.columns[_parentCellIndex + i].width; + _parentCell!._row!._grid.columns[_parentCellIndex + i].width; } for (int i = 0; i < columns.count; i++) { - if (_columns[i].width > 0 && _columns[i]._isCustomWidth) { - columnWidth -= _columns[i].width; + if (_columns![i].width > 0 && _columns![i]._isCustomWidth) { + columnWidth -= _columns![i].width; columnCount--; } } @@ -400,7 +783,7 @@ class PdfGrid extends PdfLayoutElement { final double childGridColumnWidth = (columnWidth - padding) / columnCount; if (_parentCell != null && - _parentCell.stringFormat.alignment != PdfTextAlignment.right) { + _parentCell!.stringFormat.alignment != PdfTextAlignment.right) { for (int j = 0; j < columns.count; j++) { if (!columns[j]._isCustomWidth) { columns[j]._width = childGridColumnWidth; @@ -409,11 +792,11 @@ class PdfGrid extends PdfLayoutElement { } } } - if (_parentCell != null && _parentCell._row._getWidth() > 0) { - if (_isChildGrid && _size.width > _parentCell._row._getWidth()) { + if (_parentCell != null && _parentCell!._row!._getWidth() > 0) { + if (_isChildGrid! && _size.width > _parentCell!._row!._getWidth()) { widths = columns._getDefaultWidths(bounds.width); for (int i = 0; i < columns.count; i++) { - columns[i].width = widths[i]; + columns[i].width = widths[i]!; } } } @@ -422,38 +805,69 @@ class PdfGrid extends PdfLayoutElement { void _onBeginCellLayout(PdfGridBeginCellLayoutArgs args) { if (_raiseBeginCellLayout) { - beginCellLayout(this, args); + beginCellLayout!(this, args); } } void _onEndCellLayout(PdfGridEndCellLayoutArgs args) { if (_raiseEndCellLayout) { - endCellLayout(this, args); + endCellLayout!(this, args); } } - @override - /// Draws the [PdfGrid] - PdfLayoutResult draw( - {Rect bounds, - PdfLayoutFormat format, - PdfGraphics graphics, - PdfPage page}) { - ArgumentError.checkNotNull(bounds); - _initialWidth = bounds.width == 0 + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + @override + PdfLayoutResult? draw( + {Rect? bounds, + PdfLayoutFormat? format, + PdfGraphics? graphics, + PdfPage? page}) { + final _Rectangle rectangle = + bounds != null ? _Rectangle.fromRect(bounds) : _Rectangle.empty; + _initialWidth = rectangle.width == 0 ? page != null ? page.getClientSize().width - : graphics.clientSize.width - : bounds.width; + : graphics!.clientSize.width + : rectangle.width; _isWidthSet = true; - if (graphics != null) { - _drawInternal(graphics, _Rectangle.fromRect(bounds)); - } else if (page != null) { - final PdfLayoutResult result = + if (page != null) { + final PdfLayoutResult? result = super.draw(page: page, bounds: bounds, format: format); _isComplete = true; return result; + } else if (graphics != null) { + _drawInternal(graphics, rectangle); } return null; } @@ -467,46 +881,80 @@ class PdfGrid extends PdfLayoutElement { } @override - PdfLayoutResult _layout(_PdfLayoutParams param) { - if (param.bounds.width < 0) { + PdfLayoutResult? _layout(_PdfLayoutParams param) { + if (param.bounds!.width < 0) { ArgumentError.value('width', 'width', 'out of range'); } if (rows.count != 0) { - final PdfBorders borders = rows[0].cells[0].style.borders; - if (borders != null && borders.left != null && borders.left.width != 1) { + final PdfBorders? borders = rows[0].cells[0].style.borders; + if (borders != null && borders.left.width != 1) { final double x = borders.left.width / 2; final double y = borders.top.width / 2; - if (param.bounds.x == _defaultBorder.right.width / 2 && - param.bounds.y == _defaultBorder.right.width / 2) { + if (param.bounds!.x == _defaultBorder.right.width / 2 && + param.bounds!.y == _defaultBorder.right.width / 2) { param.bounds = _Rectangle(x, y, _size.width, _size.height); } } } _setSpan(); _layoutFormat = param.format; - _gridLocation = param.bounds; + _gridLocation = param.bounds!; return _PdfGridLayouter(this)._layoutInternal(param); } /// Apply built-in table style to the table + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// final PdfGridBuiltInStyleSettings tableStyleOption = + /// PdfGridBuiltInStyleSettings(); + /// tableStyleOption.applyStyleForBandedRows = true; + /// tableStyleOption.applyStyleForHeaderRow = true; + ///Apply built-in table style + /// grid.applyBuiltInStyle(PdfGridBuiltInStyle.listTable6ColorfulAccent1, + /// settings: tableStyleOption); + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` void applyBuiltInStyle(PdfGridBuiltInStyle gridStyle, - {PdfGridBuiltInStyleSettings settings}) { + {PdfGridBuiltInStyleSettings? settings}) { _intializeBuiltInStyle(gridStyle, settings: settings); } void _intializeBuiltInStyle(PdfGridBuiltInStyle gridStyle, - {PdfGridBuiltInStyleSettings settings}) { + {PdfGridBuiltInStyleSettings? settings}) { if (settings != null) { - _headerRow = (settings.applyStyleForHeaderRow == null) - ? _headerRow - : settings.applyStyleForHeaderRow; - _totalRow = settings.applyStyleForLastRow ??= false; - _firstColumn = settings.applyStyleForFirstColumn ??= false; - _lastColumn = settings.applyStyleForLastColumn ??= false; - _bandedColumn = settings.applyStyleForBandedColumns ??= false; - _bandedRow = (settings.applyStyleForBandedRows == null) - ? _bandedRow - : settings.applyStyleForBandedRows; + _headerRow = settings.applyStyleForHeaderRow; + _totalRow = settings.applyStyleForLastRow; + _firstColumn = settings.applyStyleForFirstColumn; + _lastColumn = settings.applyStyleForLastColumn; + _bandedColumn = settings.applyStyleForBandedColumns; + _bandedRow = settings.applyStyleForBandedRows; } else { _totalRow = false; _firstColumn = false; @@ -517,7 +965,7 @@ class PdfGrid extends PdfLayoutElement { _gridBuiltinStyle = gridStyle; } - void _applyBuiltinStyles(PdfGridBuiltInStyle gridStyle) { + void _applyBuiltinStyles(PdfGridBuiltInStyle? gridStyle) { switch (gridStyle) { case PdfGridBuiltInStyle.tableGrid: _applyTableGridLight(PdfColor(0, 0, 0)); @@ -990,53 +1438,68 @@ class PdfGrid extends PdfLayoutElement { } } - PdfFont _createBoldFont(PdfFont font) { - PdfFont boldStyleFont; + PdfFont? _createBoldFont(PdfFont font) { + _boldFontCache ??= {}; if (font is PdfStandardFont) { final PdfStandardFont standardFont = font; - boldStyleFont = PdfStandardFont(standardFont.fontFamily, font.size, + return PdfStandardFont(standardFont.fontFamily, font.size, style: PdfFontStyle.bold); } else { - final PdfTrueTypeFont trueTypeFont = font as PdfTrueTypeFont; - boldStyleFont = PdfTrueTypeFont( - trueTypeFont._fontInternal._fontData, font.size, - style: PdfFontStyle.bold); + if (_boldFontCache!.containsKey(font)) { + return _boldFontCache![font as PdfTrueTypeFont]; + } else { + final PdfTrueTypeFont trueTypeFont = font as PdfTrueTypeFont; + final PdfFont boldStyleFont = PdfTrueTypeFont( + trueTypeFont._fontInternal._fontData, font.size, + style: PdfFontStyle.bold); + _boldFontCache![font] = boldStyleFont as PdfTrueTypeFont; + return boldStyleFont; + } } - return boldStyleFont; } - PdfFont _createRegularFont(PdfFont font) { - PdfFont regularStyleFont; + PdfFont? _createRegularFont(PdfFont font) { + _regularFontCache ??= {}; if (font is PdfStandardFont) { final PdfStandardFont standardFont = font; - regularStyleFont = PdfStandardFont(standardFont.fontFamily, font.size, + return PdfStandardFont(standardFont.fontFamily, font.size, style: PdfFontStyle.regular); } else { - final PdfTrueTypeFont trueTypeFont = font as PdfTrueTypeFont; - regularStyleFont = PdfTrueTypeFont( - trueTypeFont._fontInternal._fontData, font.size, - style: PdfFontStyle.regular); + if (_regularFontCache!.containsKey(font)) { + return _regularFontCache![font as PdfTrueTypeFont]; + } else { + final PdfTrueTypeFont trueTypeFont = font as PdfTrueTypeFont; + final PdfFont ttfFont = PdfTrueTypeFont( + trueTypeFont._fontInternal._fontData, font.size, + style: PdfFontStyle.regular); + _regularFontCache![font] = ttfFont as PdfTrueTypeFont; + return ttfFont; + } } - return regularStyleFont; } - PdfFont _createItalicFont(PdfFont font) { - PdfFont italicStyleFont; + PdfFont? _createItalicFont(PdfFont? font) { + _italicFontCache ??= {}; if (font is PdfStandardFont) { final PdfStandardFont standardFont = font; - italicStyleFont = PdfStandardFont(standardFont.fontFamily, font.size, + return PdfStandardFont(standardFont.fontFamily, font.size, style: PdfFontStyle.italic); } else { - final PdfTrueTypeFont trueTypeFont = font as PdfTrueTypeFont; - italicStyleFont = PdfTrueTypeFont( - trueTypeFont._fontInternal._fontData, font.size, - style: PdfFontStyle.italic); + if (_italicFontCache!.containsKey(font)) { + return _italicFontCache![font as PdfTrueTypeFont]; + } else { + final PdfTrueTypeFont trueTypeFont = font as PdfTrueTypeFont; + final PdfFont italicStyleFont = PdfTrueTypeFont( + trueTypeFont._fontInternal._fontData, font.size, + style: PdfFontStyle.italic); + _italicFontCache![font] = italicStyleFont as PdfTrueTypeFont; + return italicStyleFont; + } } - return italicStyleFont; } - PdfFont _changeFontStyle(PdfFont font) { - PdfFont pdfFont; + PdfFont? _changeFontStyle(PdfFont font) { + PdfFont? pdfFont; if (font.style == PdfFontStyle.regular) { pdfFont = _createBoldFont(font); } else if (font.style == PdfFontStyle.bold) { @@ -1045,9 +1508,9 @@ class PdfGrid extends PdfLayoutElement { return pdfFont; } - PdfBrush _applyBandedColStyle( + PdfBrush? _applyBandedColStyle( bool firstColumn, PdfColor backColor, int cellIndex) { - PdfBrush backBrush; + PdfBrush? backBrush; if (firstColumn) { if (cellIndex % 2 == 0) { backBrush = PdfSolidBrush(backColor); @@ -1060,9 +1523,9 @@ class PdfGrid extends PdfLayoutElement { return backBrush; } - PdfBrush _applyBandedRowStyle( + PdfBrush? _applyBandedRowStyle( bool headerRow, PdfColor backColor, int rowIndex) { - PdfBrush backBrush; + PdfBrush? backBrush; if (headerRow) { if (rowIndex % 2 != 0) { backBrush = PdfSolidBrush(backColor); @@ -1720,7 +2183,7 @@ class PdfGrid extends PdfLayoutElement { cell.style.borders.right = borderPen; } if (_lastColumn && j == row.cells.count) { - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -1801,7 +2264,7 @@ class PdfGrid extends PdfLayoutElement { } if (_lastColumn && j == row.cells.count) { if (!(_totalRow && i == rows.count)) { - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -1919,7 +2382,7 @@ class PdfGrid extends PdfLayoutElement { _defaultFont; cell.style.font = _changeFontStyle(font); cell.style.borders.all = PdfPen(PdfColor.empty); - if (cell._row._grid.style.cellSpacing > 0) { + if (cell._row!._grid.style.cellSpacing > 0) { cell.style.borders.bottom = headerBorder; } } else { @@ -2065,7 +2528,7 @@ class PdfGrid extends PdfLayoutElement { } } - if (_headerRow && _headers.count > 0) { + if (_headerRow && _headers!.count > 0) { if (i == 1) { cell.style.borders.top = headerBorder; } @@ -2104,7 +2567,7 @@ class PdfGrid extends PdfLayoutElement { if (_firstColumn && j == 1) { cell.style.backgroundBrush = null; cell.style.borders.all = whitePen; - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -2113,7 +2576,7 @@ class PdfGrid extends PdfLayoutElement { if (_lastColumn && j == row.cells.count) { cell.style.backgroundBrush = null; cell.style.borders.all = whitePen; - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -2159,7 +2622,7 @@ class PdfGrid extends PdfLayoutElement { if (!(_totalRow && i == rows.count)) { cell.style.backgroundBrush = null; cell.style.borders.all = whitePen; - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -2175,7 +2638,7 @@ class PdfGrid extends PdfLayoutElement { if (!(_totalRow && i == rows.count)) { cell.style.backgroundBrush = null; cell.style.borders.all = whitePen; - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -2307,7 +2770,7 @@ class PdfGrid extends PdfLayoutElement { } } - if (_headerRow && _headers.count > 0) { + if (_headerRow && _headers!.count > 0) { if (i == 1) { cell.style.borders.top = headerBackColorPen; } @@ -2590,7 +3053,7 @@ class PdfGrid extends PdfLayoutElement { if (_firstColumn && j == 1) { cell.style.backgroundBrush = null; cell.style.borders.all = whitePen; - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -2599,7 +3062,7 @@ class PdfGrid extends PdfLayoutElement { if (_lastColumn && j == row.cells.count) { cell.style.backgroundBrush = null; cell.style.borders.all = whitePen; - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -2649,7 +3112,7 @@ class PdfGrid extends PdfLayoutElement { if (i == 1 && _headerRow) { cell.style.borders.top = borderPen; } - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -2667,7 +3130,7 @@ class PdfGrid extends PdfLayoutElement { if (i == 1 && _headerRow) { cell.style.borders.top = borderPen; } - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -3600,7 +4063,7 @@ class PdfGrid extends PdfLayoutElement { if (_firstColumn && j == 1) { cell.style.backgroundBrush = null; cell.style.borders.all = emptyPen; - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -3609,7 +4072,7 @@ class PdfGrid extends PdfLayoutElement { } if (_lastColumn && j == row.cells.count) { cell.style.backgroundBrush = null; - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -3662,7 +4125,7 @@ class PdfGrid extends PdfLayoutElement { if (!(_totalRow && i == rows.count)) { cell.style.backgroundBrush = null; cell.style.borders.all = emptyPen; - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -3691,7 +4154,7 @@ class PdfGrid extends PdfLayoutElement { if (_lastColumn && j == row.cells.count) { if (!(_totalRow && i == rows.count)) { cell.style.backgroundBrush = null; - final PdfFont font = cell.style.font ?? + final PdfFont? font = cell.style.font ?? row.style.font ?? row._grid.style.font ?? _defaultFont; @@ -3725,7 +4188,7 @@ class PdfGridBeginCellLayoutArgs extends GridCellLayoutArgs { int cellInder, _Rectangle bounds, String value, - PdfGridCellStyle style, + PdfGridCellStyle? style, bool isHeaderRow) : super._(graphics, rowIndex, cellInder, bounds, value, isHeaderRow) { if (style != null) { @@ -3735,17 +4198,62 @@ class PdfGridBeginCellLayoutArgs extends GridCellLayoutArgs { } //Fields /// PDF grid cell style - PdfGridCellStyle style; + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// //Apply the cell style to specific row cells + /// row.cells[0].style = PdfGridCellStyle( + /// backgroundBrush: PdfBrushes.lightYellow, + /// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 17), + /// textBrush: PdfBrushes.white, + /// textPen: PdfPens.orange, + /// ); + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfGridCellStyle? style; /// A value that indicates whether the cell is drawn or not in the PDF document. - bool skip; + late bool skip; } /// Represents arguments of EndCellLayout Event. class PdfGridEndCellLayoutArgs extends GridCellLayoutArgs { //Constructor - PdfGridEndCellLayoutArgs._(PdfGraphics graphics, int rowIndex, int cellInder, - _Rectangle bounds, String value, PdfGridCellStyle style, bool isHeaderRow) + PdfGridEndCellLayoutArgs._( + PdfGraphics graphics, + int rowIndex, + int cellInder, + _Rectangle bounds, + String value, + PdfGridCellStyle? style, + bool isHeaderRow) : super._(graphics, rowIndex, cellInder, bounds, value, isHeaderRow) { if (style != null) { this.style = style; @@ -3753,7 +4261,7 @@ class PdfGridEndCellLayoutArgs extends GridCellLayoutArgs { } //Fields /// PDF grid cell style - PdfGridCellStyle style; + PdfGridCellStyle? style; } /// Represents the abstract class of the [GridCellLayoutArgs]. @@ -3762,7 +4270,6 @@ abstract class GridCellLayoutArgs { /// Initializes a new instance of the [StartCellLayoutArgs] class. GridCellLayoutArgs._(PdfGraphics graphics, int rowIndex, int cellIndex, _Rectangle bounds, String value, bool isHeaderRow) { - ArgumentError.checkNotNull(graphics); _rowIndex = rowIndex; _cellIndex = cellIndex; _value = value; @@ -3772,44 +4279,332 @@ abstract class GridCellLayoutArgs { } //Fields - int _rowIndex; - int _cellIndex; - String _value; - Rect _bounds; - PdfGraphics _graphics; - bool _isHeaderRow; + late int _rowIndex; + late int _cellIndex; + late String _value; + late Rect _bounds; + late PdfGraphics _graphics; + late bool _isHeaderRow; //Properties /// Gets the index of the row. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// // Sets the event raised on starting cell lay outing. + /// grid.beginCellLayout = (Object sender, PdfGridBeginCellLayoutArgs args) { + /// if (args.rowIndex == 1 && args.cellIndex == 1) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// if (args.isHeaderRow && args.cellIndex == 0) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// }; + /// grid.style.cellPadding = PdfPaddings(); + /// grid.style.cellPadding.all = 15; + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int get rowIndex => _rowIndex; /// Gets the index of the cell. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// // Sets the event raised on starting cell lay outing. + /// grid.beginCellLayout = (Object sender, PdfGridBeginCellLayoutArgs args) { + /// if (args.rowIndex == 1 && args.cellIndex == 1) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// if (args.isHeaderRow && args.cellIndex == 0) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// }; + /// grid.style.cellPadding = PdfPaddings(); + /// grid.style.cellPadding.all = 15; + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int get cellIndex => _cellIndex; /// Gets the value. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// // Sets the event raised on starting cell lay outing. + /// grid.beginCellLayout = (Object sender, PdfGridBeginCellLayoutArgs args) { + /// if (args.rowIndex == 1 && args.cellIndex == 1) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// if (args.isHeaderRow && args.cellIndex == 0) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// }; + /// grid.style.cellPadding = PdfPaddings(); + /// grid.style.cellPadding.all = 15; + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` String get value => _value; /// Gets the bounds of the cell. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// // Sets the event raised on starting cell lay outing. + /// grid.beginCellLayout = (Object sender, PdfGridBeginCellLayoutArgs args) { + /// if (args.rowIndex == 1 && args.cellIndex == 1) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// if (args.isHeaderRow && args.cellIndex == 0) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// }; + /// grid.style.cellPadding = PdfPaddings(); + /// grid.style.cellPadding.all = 15; + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` Rect get bounds => _bounds; /// Gets the graphics, on which the cell should be drawn. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// // Sets the event raised on starting cell lay outing. + /// grid.beginCellLayout = (Object sender, PdfGridBeginCellLayoutArgs args) { + /// if (args.rowIndex == 1 && args.cellIndex == 1) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// if (args.isHeaderRow && args.cellIndex == 0) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// }; + /// grid.style.cellPadding = PdfPaddings(); + /// grid.style.cellPadding.all = 15; + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGraphics get graphics => _graphics; /// Gets the type of Grid row. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// // Sets the event raised on starting cell lay outing. + /// grid.beginCellLayout = (Object sender, PdfGridBeginCellLayoutArgs args) { + /// if (args.rowIndex == 1 && args.cellIndex == 1) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// if (args.isHeaderRow && args.cellIndex == 0) { + /// args.graphics.drawRectangle( + /// pen: PdfPen(PdfColor(250, 100, 0), width: 2), + /// brush: PdfBrushes.white, + /// bounds: args.bounds); + /// } + /// }; + /// grid.style.cellPadding = PdfPaddings(); + /// grid.style.cellPadding.all = 15; + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` bool get isHeaderRow => _isHeaderRow; } /// Arguments of BeginPageLayoutEvent. class PdfGridBeginPageLayoutArgs extends BeginPageLayoutArgs { //Constructor - PdfGridBeginPageLayoutArgs._(Rect bounds, PdfPage page, int startRow) + PdfGridBeginPageLayoutArgs._(Rect bounds, PdfPage page, int? startRow) : super(bounds, page) { startRowIndex = startRow ?? 0; } //Fields /// Gets the start row index. - int startRowIndex; + late int startRowIndex; } /// Arguments of EndPageLayoutEvent. @@ -3832,20 +4627,248 @@ class PdfGridBuiltInStyleSettings { //Fields /// Gets or sets a value indicating whether to apply style bands to the columns in a table. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// final PdfGridBuiltInStyleSettings tableStyleOption = + /// PdfGridBuiltInStyleSettings(); + /// //Sets applyStyleForBandedColumns + /// tableStyleOption.applyStyleForBandedColumns = true; + ///Apply built-in table style + /// grid.applyBuiltInStyle(PdfGridBuiltInStyle.listTable6ColorfulAccent1, + /// settings: tableStyleOption); + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` bool applyStyleForBandedColumns; /// Gets or sets a value indicating whether to apply style bands to the rows in a table. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// final PdfGridBuiltInStyleSettings tableStyleOption = + /// PdfGridBuiltInStyleSettings(); + /// //Sets applyStyleForBandedRows + /// tableStyleOption.applyStyleForBandedRows = true; + ///Apply built-in table style + /// grid.applyBuiltInStyle(PdfGridBuiltInStyle.listTable6ColorfulAccent1, + /// settings: tableStyleOption); + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` bool applyStyleForBandedRows; /// Gets or sets a value indicating whether to apply first-column formatting to the first column of the specified table. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// final PdfGridBuiltInStyleSettings tableStyleOption = + /// PdfGridBuiltInStyleSettings(); + /// //Sets applyStyleForFirstColumn + /// tableStyleOption.applyStyleForFirstColumn = true; + ///Apply built-in table style + /// grid.applyBuiltInStyle(PdfGridBuiltInStyle.listTable6ColorfulAccent1, + /// settings: tableStyleOption); + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` bool applyStyleForFirstColumn; /// Gets or sets a value indicating whether to apply heading-row formatting to the first row of the table. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// final PdfGridBuiltInStyleSettings tableStyleOption = + /// PdfGridBuiltInStyleSettings(); + /// //Sets applyStyleForHeaderRow + /// tableStyleOption.applyStyleForHeaderRow = true; + ///Apply built-in table style + /// grid.applyBuiltInStyle(PdfGridBuiltInStyle.listTable6ColorfulAccent1, + /// settings: tableStyleOption); + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` bool applyStyleForHeaderRow; /// Gets or sets a value indicating whether to apply first-column formatting to the first column of the specified table. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// final PdfGridBuiltInStyleSettings tableStyleOption = + /// PdfGridBuiltInStyleSettings(); + /// //Sets applyStyleForLastColumn + /// tableStyleOption.applyStyleForLastColumn = true; + ///Apply built-in table style + /// grid.applyBuiltInStyle(PdfGridBuiltInStyle.listTable6ColorfulAccent1, + /// settings: tableStyleOption); + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` bool applyStyleForLastColumn; /// Gets or sets a value indicating whether to apply last-row formatting to the last row of the specified table. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid class + /// PdfGrid grid = PdfGrid(); + /// //Add the columns to the grid + /// grid.columns.add(count: 3); + /// //Add header to the grid + /// grid.headers.add(1); + /// //Add the rows to the grid + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row = grid.rows.add(); + /// row.cells[0].value = 'E01'; + /// row.cells[1].value = 'Clay'; + /// row.cells[2].value = '\$10,000'; + /// row = grid.rows.add(); + /// row.cells[0].value = 'E02'; + /// row.cells[1].value = 'Simon'; + /// row.cells[2].value = '\$12,000'; + /// final PdfGridBuiltInStyleSettings tableStyleOption = + /// PdfGridBuiltInStyleSettings(); + /// //Sets applyStyleForLastRow + /// tableStyleOption.applyStyleForLastRow = true; + ///Apply built-in table style + /// grid.applyBuiltInStyle(PdfGridBuiltInStyle.listTable6ColorfulAccent1, + /// settings: tableStyleOption); + /// //Draw the grid + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` bool applyStyleForLastRow; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_cell.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_cell.dart index d956167fb..de1a54001 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_cell.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_cell.dart @@ -1,50 +1,243 @@ part of pdf; /// Represents the schema of a cell in a [PdfGrid]. +/// ```dart +/// //Create a new PDF document +/// PdfDocument document = PdfDocument(); +/// //Create a PdfGrid +/// PdfGrid grid = PdfGrid(); +/// //Add columns to grid +/// grid.columns.add(count: 3); +/// //Add headers to grid +/// PdfGridRow header = grid.headers.add(1)[0]; +/// header.cells[0].value = 'Employee ID'; +/// header.cells[1].value = 'Employee Name'; +/// header.cells[2].value = 'Salary'; +/// //Add rows to grid +/// PdfGridRow row1 = grid.rows.add(); +/// row1.cells[0].value = 'E01'; +/// row1.cells[1].value = 'Clay'; +/// //Apply the cell style to specific row cells +/// row1.cells[0].style = PdfGridCellStyle( +/// backgroundBrush: PdfBrushes.lightYellow, +/// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 17), +/// textBrush: PdfBrushes.white, +/// textPen: PdfPens.orange, +/// ); +/// PdfGridCell gridCell = PdfGridCell( +/// row: row1, +/// format: PdfStringFormat( +/// alignment: PdfTextAlignment.center, +/// lineAlignment: PdfVerticalAlignment.bottom, +/// wordSpacing: 10)); +/// gridCell.value = '\$10,000'; +/// row1.cells[2].value = gridCell.value; +/// row1.cells[2].stringFormat = gridCell.stringFormat; +/// PdfGridRow row2 = grid.rows.add(); +/// row2.cells[0].value = 'E02'; +/// row2.cells[1].value = 'Simon'; +/// row2.cells[2].value = '\$12,000'; +/// //Draw the grid in PDF document page +/// grid.draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfGridCell { /// Initializes a new instance of the [PdfGridCell] class. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// PdfGridRow header = grid.headers.add(1)[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// //Apply the cell style to specific row cells + /// row1.cells[0].style = PdfGridCellStyle( + /// backgroundBrush: PdfBrushes.lightYellow, + /// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 17), + /// textBrush: PdfBrushes.white, + /// textPen: PdfPens.orange, + /// ); + /// PdfGridCell gridCell = PdfGridCell( + /// row: row1, + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom, + /// wordSpacing: 10)); + /// gridCell.value = '\$10,000'; + /// row1.cells[2].value = gridCell.value; + /// row1.cells[2].stringFormat = gridCell.stringFormat; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridCell( - {PdfGridCellStyle style, - PdfStringFormat format, - PdfGridRow row, - int rowSpan, - int columnSpan}) { + {PdfGridCellStyle? style, + PdfStringFormat? format, + PdfGridRow? row, + int? rowSpan, + int? columnSpan}) { _initialize(style, format, row, rowSpan, columnSpan); } //Fields - double _width; - double _height; - double _outerCellWidth; - int _rowSpan; - int _columnSpan; - PdfGridRow _row; + late double _width; + late double _height; + late double _outerCellWidth; + late int _rowSpan; + late int _columnSpan; + PdfGridRow? _row; dynamic _value; - PdfStringFormat _format; - bool _finished; - String _remainingString; + PdfStringFormat? _format; + late bool _finished; + String? _remainingString; bool _present = false; - PdfGridCell _parent; - double _rowSpanRemainingHeight; - double _tempRowSpanRemainingHeight; - int _pageCount; - PdfGridImagePosition _imagePosition; - _PdfGridStretchOption _pdfGridStretchOption; - PdfGridCellStyle _style; - bool _isCellMergeContinue; - bool _isRowMergeContinue; - double _maxValue; + PdfGridCell? _parent; + late double _rowSpanRemainingHeight; + late double _tempRowSpanRemainingHeight; + late int _pageCount; + late PdfGridImagePosition _imagePosition; + late _PdfGridStretchOption _pdfGridStretchOption; + PdfGridCellStyle? _style; + late bool _isCellMergeContinue; + late bool _isRowMergeContinue; + late double _maxValue; //Properties - /// Gets the width of the PdfGrid cell. + /// Gets the width of the [PdfGrid] cell. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// PdfGridRow header = grid.headers.add(1)[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add the styles to specific cell + /// header.cells[0].style.stringFormat = PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom, + /// wordSpacing: 10); + /// header.cells[1].style.textPen = PdfPens.mediumVioletRed; + /// header.cells[2].style.backgroundBrush = PdfBrushes.yellow; + /// header.cells[2].style.textBrush = PdfBrushes.darkOrange; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10000'; + /// //Sets the rowSpan + /// row1.cells[1].rowSpan = 1; + /// //Sets the colSpan + /// row1.cells[1].columnSpan = 2; + /// //Apply the cell style to specific row cells + /// row1.cells[0].style = PdfGridCellStyle( + /// backgroundBrush: PdfBrushes.lightYellow, + /// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 17), + /// textBrush: PdfBrushes.white, + /// textPen: PdfPens.orange, + /// ); + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Gets the width of the PDF grid cell + /// double width = row2.cells[2].width; + /// //Gets the height of the PDF grid cell + /// double height = row2.cells[2].height; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` double get width { - if (_width == -1 || _row._grid._isComplete) { + if (_width == -1 || _row!._grid._isComplete) { _width = _measureWidth(); } return double.parse(_width.toStringAsFixed(4)); } - /// Gets the height of the PdfGrid cell. + /// Gets the height of the [PdfGrid] cell. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// PdfGridRow header = grid.headers.add(1)[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add the styles to specific cell + /// header.cells[0].style.stringFormat = PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom, + /// wordSpacing: 10); + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10000'; + /// //Sets the rowSpan + /// row1.cells[1].rowSpan = 1; + /// //Sets the colSpan + /// row1.cells[1].columnSpan = 2; + /// //Apply the cell style to specific row cells + /// row1.cells[0].style = PdfGridCellStyle( + /// backgroundBrush: PdfBrushes.lightYellow, + /// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 17), + /// textBrush: PdfBrushes.white, + /// textPen: PdfPens.orange, + /// ); + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Gets the width of the PDF grid cell + /// double width = row2.cells[2].width; + /// //Gets the height of the PDF grid cell + /// double height = row2.cells[2].height; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` double get height { if (_height == -1) { _height = _measureHeight(); @@ -52,12 +245,54 @@ class PdfGridCell { return _height; } - /// Gets a value that indicates the total number of rows that cell spans - /// within a PdfGrid. + /// Gets or sets a value that indicates the total number of rows that cell spans + /// within a [PdfGrid]. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// PdfGridRow header = grid.headers.add(1)[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10000'; + /// //Sets the rowSpan + /// row1.cells[1].rowSpan = 1; + /// //Sets the colSpan + /// row1.cells[1].columnSpan = 2; + /// //Apply the cell style to specific row cells + /// row1.cells[0].style = PdfGridCellStyle( + /// backgroundBrush: PdfBrushes.lightYellow, + /// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 17), + /// textBrush: PdfBrushes.white, + /// textPen: PdfPens.orange, + /// ); + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Gets the width of the PDF grid cell + /// double width = row2.cells[2].width; + /// //Gets the height of the PDF grid cell + /// double height = row2.cells[2].height; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int get rowSpan => _rowSpan; - - /// Sets a value that indicates the total number of rows that cell spans - /// within a PdfGrid. set rowSpan(int value) { if (value < 1) { throw ArgumentError.value('value', 'row span', @@ -65,17 +300,67 @@ class PdfGridCell { } if (value > 1) { _rowSpan = value; - _row._rowSpanExists = true; - _row._grid._hasRowSpan = true; + _row!._rowSpanExists = true; + _row!._grid._hasRowSpan = true; } } - /// Gets a value that indicates the total number of columns that cell spans - /// within a PdfGrid. + /// Gets or sets a value that indicates the total number of columns that cell spans + /// within a [PdfGrid]. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// PdfGridRow header = grid.headers.add(1)[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add the styles to specific cell + /// header.cells[0].style.stringFormat = PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom, + /// wordSpacing: 10); + /// header.cells[1].style.textPen = PdfPens.mediumVioletRed; + /// header.cells[2].style.backgroundBrush = PdfBrushes.yellow; + /// header.cells[2].style.textBrush = PdfBrushes.darkOrange; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10000'; + /// //Sets the rowSpan + /// row1.cells[1].rowSpan = 1; + /// //Sets the colSpan + /// row1.cells[1].columnSpan = 2; + /// //Apply the cell style to specific row cells + /// row1.cells[0].style = PdfGridCellStyle( + /// backgroundBrush: PdfBrushes.lightYellow, + /// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 17), + /// textBrush: PdfBrushes.white, + /// textPen: PdfPens.orange, + /// ); + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Gets the width of the PDF grid cell + /// double width = row2.cells[2].width; + /// //Gets the height of the PDF grid cell + /// double height = row2.cells[2].height; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int get columnSpan => _columnSpan; - - /// Sets a value that indicates the total number of columns that cell spans - /// within a PdfGrid. set columnSpan(int value) { if (value < 1) { throw ArgumentError.value('value', 'column span', @@ -83,44 +368,222 @@ class PdfGridCell { } if (value > 1) { _columnSpan = value; - _row._grid._hasColumnSpan = true; + _row!._grid._hasColumnSpan = true; } } - /// Gets the cell style. + /// Gets or sets the cell style. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// PdfGridRow header = grid.headers.add(1)[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add the styles to specific cell + /// header.cells[0].style.stringFormat = PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom, + /// wordSpacing: 10); + /// header.cells[1].style.textPen = PdfPens.mediumVioletRed; + /// header.cells[2].style.backgroundBrush = PdfBrushes.yellow; + /// header.cells[2].style.textBrush = PdfBrushes.darkOrange; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// //Apply the cell style to specific row cells + /// row1.cells[0].style = PdfGridCellStyle( + /// backgroundBrush: PdfBrushes.lightYellow, + /// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 17), + /// textBrush: PdfBrushes.white, + /// textPen: PdfPens.orange, + /// ); + /// PdfGridCell gridCell = PdfGridCell( + /// row: row1, + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom, + /// wordSpacing: 10)); + /// gridCell.value = '\$10,000'; + /// row1.cells[2].value = gridCell.value; + /// row1.cells[2].stringFormat = gridCell.stringFormat; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Add the style to specific cell + /// row2.cells[2].style.borders = PdfBorders( + /// left: PdfPen(PdfColor(240, 0, 0), width: 2), + /// top: PdfPen(PdfColor(0, 240, 0), width: 3), + /// bottom: PdfPen(PdfColor(0, 0, 240), width: 4), + /// right: PdfPen(PdfColor(240, 100, 240), width: 5)); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridCellStyle get style { _style ??= PdfGridCellStyle(); - return _style; + return _style!; } - /// Sets the cell style. set style(PdfGridCellStyle value) { _style = value; } - /// Gets the value of the cell. + /// Gets or sets the value of the cell. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// PdfGridRow header = grid.headers.add(1)[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// //Apply the cell style to specific row cells + /// PdfGridCell gridCell = PdfGridCell( + /// row: row1, + /// format: PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom, + /// wordSpacing: 10)); + /// gridCell.value = '\$10,000'; + /// row1.cells[2].value = gridCell.value; + /// row1.cells[2].stringFormat = gridCell.stringFormat; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` dynamic get value => _value; - - /// Sets the value of the cell. set value(dynamic value) { _value = value; _setValue(value); } - /// Gets the string format. + /// Gets or sets the string format. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// PdfGridRow header = grid.headers.add(1)[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add the styles to specific cell + /// header.cells[0].style.stringFormat = PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom, + /// wordSpacing: 10); + /// header.cells[1].style.textPen = PdfPens.mediumVioletRed; + /// header.cells[2].style.backgroundBrush = PdfBrushes.yellow; + /// header.cells[2].style.textBrush = PdfBrushes.darkOrange; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// //Apply the cell style to specific row cells + /// row1.cells[0].style = PdfGridCellStyle( + /// backgroundBrush: PdfBrushes.lightYellow, + /// cellPadding: PdfPaddings(left: 2, right: 3, top: 4, bottom: 5), + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 17), + /// textBrush: PdfBrushes.white, + /// textPen: PdfPens.orange, + /// ); + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfStringFormat get stringFormat { _format ??= PdfStringFormat(); - return _format; + return _format!; } - /// Sets the string format. set stringFormat(PdfStringFormat value) { _format = value; } + /// Gets or sets the image alignment type of the [PdfGridCell] image. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// PdfGridRow header = grid.headers.add(1)[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// PdfGridCell cell1 = row1.cells[0]; + /// //Sets the image alignment type of the PdfGridCell image + /// cell1.imagePosition = PdfGridImagePosition.center; + /// cell1.style.backgroundImage = PdfBitmap(imageData); + /// cell1.style.cellPadding = PdfPaddings(left: 0, right: 0, top: 10, bottom: 10); + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfGridImagePosition get imagePosition => _imagePosition; + set imagePosition(PdfGridImagePosition value) { + if (_imagePosition != value) { + _imagePosition = value; + } + } + //Implementation - void _initialize(PdfGridCellStyle style, PdfStringFormat format, - PdfGridRow row, int rowSpan, int columnSpan) { + void _initialize(PdfGridCellStyle? style, PdfStringFormat? format, + PdfGridRow? row, int? rowSpan, int? columnSpan) { if (row != null) { _row = row; } @@ -160,7 +623,7 @@ class PdfGridCell { throw ArgumentError.value(value, 'value', 'value cannot be null'); } if (_value is PdfGrid) { - _row._grid._isSingleGrid = false; + _row!._grid._isSingleGrid = false; _value._parentCell = this; _value._isChildGrid = true; for (int i = 0; i < _value.rows.count; i++) { @@ -173,37 +636,37 @@ class PdfGridCell { } } - PdfFont _getTextFont() { + PdfFont? _getTextFont() { return style.font ?? - _row.style.font ?? - _row._grid.style.font ?? - _row._grid._defaultFont; + _row!.style.font ?? + _row!._grid.style.font ?? + _row!._grid._defaultFont; } - PdfBrush _getTextBrush() { + PdfBrush? _getTextBrush() { return style.textBrush ?? - _row.style.textBrush ?? - _row._grid.style.textBrush ?? + _row!.style.textBrush ?? + _row!._grid.style.textBrush ?? PdfBrushes.black; } - PdfPen _getTextPen() { - return style.textPen ?? _row.style.textPen ?? _row._grid.style.textPen; + PdfPen? _getTextPen() { + return style.textPen ?? _row!.style.textPen ?? _row!._grid.style.textPen; } - PdfBrush _getBackgroundBrush() { + PdfBrush? _getBackgroundBrush() { return style.backgroundBrush ?? - _row.style.backgroundBrush ?? - _row._grid.style.backgroundBrush; + _row!.style.backgroundBrush ?? + _row!._grid.style.backgroundBrush; } double _measureHeight() { - final double width = _calculateWidth() - + final double width = _calculateWidth()! - (style.cellPadding == null - ? (_row._grid.style.cellPadding.right + - _row._grid.style.cellPadding.left) - : (style.cellPadding.right + - style.cellPadding.left + + ? (_row!._grid.style.cellPadding.right + + _row!._grid.style.cellPadding.left) + : (style.cellPadding!.right + + style.cellPadding!.left + style.borders.left.width + style.borders.right.width)); _outerCellWidth = width; @@ -211,26 +674,27 @@ class PdfGridCell { final _PdfStringLayouter layouter = _PdfStringLayouter(); if (value is PdfTextElement) { final PdfTextElement element = value as PdfTextElement; - String temp = element.text; + String? temp = element.text; if (!_finished) { - temp = (_remainingString != null && _remainingString.isNotEmpty) + temp = (_remainingString != null && _remainingString!.isNotEmpty) ? _remainingString : value; } - final _PdfStringLayoutResult result = layouter._layout(temp, - element.font ?? _getTextFont(), element.stringFormat ?? stringFormat, + final _PdfStringLayoutResult result = layouter._layout( + temp!, element.font, element.stringFormat ?? stringFormat, width: width, height: _maxValue); height += result._size.height + ((style.borders.top.width + style.borders.bottom.width) * 2); } else if (value is String || _remainingString is String) { - String currentValue = value; + String? currentValue = value; if (!_finished) { - currentValue = (_remainingString != null && _remainingString.isNotEmpty) - ? _remainingString - : value; + currentValue = + (_remainingString != null && _remainingString!.isNotEmpty) + ? _remainingString + : value; } final _PdfStringLayoutResult result = layouter._layout( - currentValue, _getTextFont(), stringFormat, + currentValue!, _getTextFont()!, stringFormat, width: width, height: _maxValue); height += result._size.height + ((style.borders.top.width + style.borders.bottom.width) * 2); @@ -241,32 +705,32 @@ class PdfGridCell { height = img.height / (96 / 72); } height += style.cellPadding == null - ? (_row._grid.style.cellPadding.top + - _row._grid.style.cellPadding.bottom) - : (style.cellPadding.top + style.cellPadding.bottom); - height += _row._grid.style.cellSpacing; + ? (_row!._grid.style.cellPadding.top + + _row!._grid.style.cellPadding.bottom) + : (style.cellPadding!.top + style.cellPadding!.bottom); + height += _row!._grid.style.cellSpacing; return height; } - double _calculateWidth() { - final int cellIndex = _row.cells.indexOf(this); + double? _calculateWidth() { + final int cellIndex = _row!.cells.indexOf(this); final int columnSpan = this.columnSpan; double width = 0; for (int i = 0; i < columnSpan; i++) { - width += _row._grid.columns[cellIndex + i].width; + width += _row!._grid.columns[cellIndex + i].width; } if (_parent != null && - _parent._row._getWidth() > 0 && - (_row._grid._isChildGrid) && - (_row._getWidth() > _parent._row._getWidth())) { + _parent!._row!._getWidth() > 0 && + _row!._grid._isChildGrid! && + (_row!._getWidth() > _parent!._row!._getWidth())) { width = 0; - for (int j = 0; j < _parent.columnSpan; j++) { - width += _parent._row._grid.columns[j].width; + for (int j = 0; j < _parent!.columnSpan; j++) { + width += _parent!._row!._grid.columns[j].width; } - width = width / _row.cells.count; - } else if (_parent != null && _row._grid._isChildGrid && width == -1) { - width = _findGridColumnWidth(_parent); - width = width / _row.cells.count; + width = width / _row!.cells.count; + } else if (_parent != null && _row!._grid._isChildGrid! && width == -1) { + width = _findGridColumnWidth(_parent!); + width = width / _row!.cells.count; } return width; } @@ -274,8 +738,8 @@ class PdfGridCell { double _findGridColumnWidth(PdfGridCell pdfGridCell) { double width = -1; if (pdfGridCell._parent != null && pdfGridCell._outerCellWidth == -1) { - width = _findGridColumnWidth(pdfGridCell._parent); - width = width / pdfGridCell._row.cells.count; + width = _findGridColumnWidth(pdfGridCell._parent!); + width = width / pdfGridCell._row!.cells.count; } else if (pdfGridCell._parent == null && pdfGridCell._outerCellWidth > 0) { width = pdfGridCell._outerCellWidth; } @@ -283,52 +747,49 @@ class PdfGridCell { } double _measureWidth() { - double width = 0; + double? width = 0; final _PdfStringLayouter layouter = _PdfStringLayouter(); if (value is String) { - double defaultWidth = _maxValue; + double? defaultWidth = _maxValue; if (_parent != null) { defaultWidth = _getColumnWidth(); } final _PdfStringLayoutResult result = layouter._layout( - value, _getTextFont(), stringFormat, + value, _getTextFont()!, stringFormat, width: defaultWidth, height: _maxValue); width += result._size.width; - if (style.borders != null && - style.borders.left != null && - style.borders.right != null) { - width += (style.borders.left.width + style.borders.right.width) * 2; - } + width += (style.borders.left.width + style.borders.right.width) * 2; } else if (value is PdfGrid) { width = (value as PdfGrid)._gridSize.width; } else if (value is PdfTextElement) { - double defaultWidth = _maxValue; + double? defaultWidth = _maxValue; if (_parent != null) { defaultWidth = _getColumnWidth(); } final PdfTextElement element = value as PdfTextElement; - String temp = element.text; + String? temp = element.text; if (!_finished) { - temp = (_remainingString != null && _remainingString.isNotEmpty) + temp = (_remainingString != null && _remainingString!.isNotEmpty) ? _remainingString : value; } - final _PdfStringLayoutResult result = layouter._layout(temp, - element.font ?? _getTextFont(), element.stringFormat ?? stringFormat, + final _PdfStringLayoutResult result = layouter._layout( + temp!, element.font, element.stringFormat ?? stringFormat, width: defaultWidth, height: _maxValue); width += result._size.width; width += (style.borders.left.width + style.borders.right.width) * 2; } return width + - _row._grid.style.cellSpacing + + _row!._grid.style.cellSpacing + (style.cellPadding != null - ? (style.cellPadding.left + style.cellPadding.right) - : (_row._grid.style.cellPadding.left + - _row._grid.style.cellPadding.right)); + ? (style.cellPadding!.left + style.cellPadding!.right) + : (_row!._grid.style.cellPadding.left + + _row!._grid.style.cellPadding.right)); } double _getColumnWidth() { - double defaultWidth = _parent._calculateWidth() / _row._grid.columns.count; + double defaultWidth = + _parent!._calculateWidth()! / _row!._grid.columns.count; if (defaultWidth <= 0) { defaultWidth = _maxValue; } @@ -336,21 +797,21 @@ class PdfGridCell { } PdfGraphics _drawCellBorders(PdfGraphics graphics, _Rectangle bounds) { - final PdfBorders borders = style.borders; - if (_row._grid.style.borderOverlapStyle == PdfBorderOverlapStyle.inside) { - bounds.x += borders.left.width; - bounds.y += borders.top.width; - bounds.width -= borders.right.width; - bounds.height -= borders.bottom.width; + final PdfBorders? borders = style.borders; + if (_row!._grid.style.borderOverlapStyle == PdfBorderOverlapStyle.inside) { + bounds.x = bounds.x + borders!.left.width; + bounds.y = bounds.y + borders.top.width; + bounds.width = bounds.width - borders.right.width; + bounds.height = bounds.height - borders.bottom.width; } - PdfPen pen = style.borders.left; + PdfPen? pen = style.borders.left; if (style.borders._isAll) { _setTransparency(graphics, pen); graphics.drawRectangle(pen: pen, bounds: bounds.rect); } else { Offset p1 = Offset(bounds.x, bounds.y + bounds.height); Offset p2 = Offset(bounds.x, bounds.y); - if (_style.borders.left.dashStyle == PdfDashStyle.solid && + if (_style!.borders.left.dashStyle == PdfDashStyle.solid && !pen._immutable) { pen.lineCap = PdfLineCap.square; } @@ -360,13 +821,13 @@ class PdfGridCell { p1 = Offset(bounds.x + bounds.width, bounds.y); p2 = Offset(bounds.x + bounds.width, bounds.y + bounds.height); - pen = _style.borders.right; + pen = _style!.borders.right; if (bounds.x + bounds.width > graphics.clientSize.width - pen.width / 2) { p1 = Offset(graphics.clientSize.width - pen.width / 2, bounds.y); p2 = Offset(graphics.clientSize.width - pen.width / 2, bounds.y + bounds.height); } - if (_style.borders.right.dashStyle == PdfDashStyle.solid && + if (_style!.borders.right.dashStyle == PdfDashStyle.solid && !pen._immutable) { pen.lineCap = PdfLineCap.square; } @@ -375,8 +836,8 @@ class PdfGridCell { graphics.restore(); p1 = Offset(bounds.x, bounds.y); p2 = Offset(bounds.x + bounds.width, bounds.y); - pen = _style.borders.top; - if (_style.borders.top.dashStyle == PdfDashStyle.solid && + pen = _style!.borders.top; + if (_style!.borders.top.dashStyle == PdfDashStyle.solid && !pen._immutable) { pen.lineCap = PdfLineCap.square; } @@ -385,14 +846,14 @@ class PdfGridCell { graphics.restore(); p1 = Offset(bounds.x + bounds.width, bounds.y + bounds.height); p2 = Offset(bounds.x, bounds.y + bounds.height); - pen = _style.borders.bottom; + pen = _style!.borders.bottom; if (bounds.y + bounds.height > graphics.clientSize.height - pen.width / 2) { p1 = Offset(bounds.x + bounds.width, graphics.clientSize.height - pen.width / 2); p2 = Offset(bounds.x, graphics.clientSize.height - pen.width / 2); } - if (_style.borders.bottom.dashStyle == PdfDashStyle.solid && + if (_style!.borders.bottom.dashStyle == PdfDashStyle.solid && !pen._immutable) { pen.lineCap = PdfLineCap.square; } @@ -408,42 +869,42 @@ class PdfGridCell { graphics.setTransparency(pen.color._alpha / 255); } - _PdfStringLayoutResult _draw( - PdfGraphics graphics, _Rectangle bounds, bool cancelSubsequentSpans) { + _PdfStringLayoutResult? _draw( + PdfGraphics? graphics, _Rectangle bounds, bool cancelSubsequentSpans) { bool isrowbreak = false; - if (!_row._grid._isSingleGrid) { + if (!_row!._grid._isSingleGrid) { if ((_remainingString != null) || (_PdfGridLayouter._repeatRowIndex != -1)) { - _drawParentCells(graphics, bounds, true); - } else if (_row._grid.rows.count > 1) { - for (int i = 0; i < _row._grid.rows.count; i++) { - if (_row == _row._grid.rows[i]) { - if (_row._grid.rows[i]._rowBreakHeight > 0) { + _drawParentCells(graphics!, bounds, true); + } else if (_row!._grid.rows.count > 1) { + for (int i = 0; i < _row!._grid.rows.count; i++) { + if (_row == _row!._grid.rows[i]) { + if (_row!._grid.rows[i]._rowBreakHeight > 0) { isrowbreak = true; } if ((i > 0) && isrowbreak) { - _drawParentCells(graphics, bounds, false); + _drawParentCells(graphics!, bounds, false); } } } } } - _PdfStringLayoutResult result; + _PdfStringLayoutResult? result; if (cancelSubsequentSpans) { - final int currentCellIndex = _row.cells.indexOf(this); + final int currentCellIndex = _row!.cells.indexOf(this); for (int i = currentCellIndex + 1; i <= currentCellIndex + _columnSpan; i++) { - _row.cells[i]._isCellMergeContinue = false; - _row.cells[i]._isRowMergeContinue = false; + _row!.cells[i]._isCellMergeContinue = false; + _row!.cells[i]._isRowMergeContinue = false; } _columnSpan = 1; } if (_isCellMergeContinue || _isRowMergeContinue) { - if (_isCellMergeContinue && _row._grid.style.allowHorizontalOverflow) { - if ((_row._rowOverflowIndex > 0 && - (_row.cells.indexOf(this) != _row._rowOverflowIndex + 1)) || - (_row._rowOverflowIndex == 0 && _isCellMergeContinue)) { + if (_isCellMergeContinue && _row!._grid.style.allowHorizontalOverflow) { + if ((_row!._rowOverflowIndex > 0 && + (_row!.cells.indexOf(this) != _row!._rowOverflowIndex + 1)) || + (_row!._rowOverflowIndex == 0 && _isCellMergeContinue)) { return result; } } else { @@ -452,18 +913,18 @@ class PdfGridCell { } bounds = _adjustOuterLayoutArea(bounds, graphics); graphics = _drawCellBackground(graphics, bounds); - final PdfPen textPen = _getTextPen(); - final PdfBrush textBrush = _getTextBrush(); - final PdfFont font = _getTextFont(); - final PdfStringFormat strFormat = style.stringFormat ?? stringFormat; + final PdfPen? textPen = _getTextPen(); + final PdfBrush? textBrush = _getTextBrush(); + final PdfFont? font = _getTextFont(); + final PdfStringFormat? strFormat = style.stringFormat ?? stringFormat; _Rectangle innerLayoutArea = bounds._clone(); - if (innerLayoutArea.height >= graphics.clientSize.height) { - if (_row._grid.allowRowBreakingAcrossPages) { - innerLayoutArea.height -= innerLayoutArea.y; - bounds.height -= bounds.y; - if (_row._grid._isChildGrid) { - innerLayoutArea.height -= - _row._grid._parentCell._row._grid.style.cellPadding.bottom; + if (innerLayoutArea.height >= graphics!.clientSize.height) { + if (_row!._grid.allowRowBreakingAcrossPages) { + innerLayoutArea.height = innerLayoutArea.height - innerLayoutArea.y; + bounds.height = bounds.height - bounds.y; + if (_row!._grid._isChildGrid!) { + innerLayoutArea.height = innerLayoutArea.height - + _row!._grid._parentCell!._row!._grid.style.cellPadding.bottom; } } else { innerLayoutArea.height = graphics.clientSize.height; @@ -479,9 +940,9 @@ class PdfGridCell { childGrid._parentCell = this; childGrid._listOfNavigatePages = []; _PdfGridLayouter layouter = _PdfGridLayouter(childGrid); - PdfLayoutFormat format = PdfLayoutFormat(); - if (_row._grid._layoutFormat != null) { - format = _row._grid._layoutFormat; + PdfLayoutFormat? format = PdfLayoutFormat(); + if (_row!._grid._layoutFormat != null) { + format = _row!._grid._layoutFormat; } else { format.layoutType = PdfLayoutType.paginate; } @@ -491,10 +952,10 @@ class PdfGridCell { param.bounds = innerLayoutArea; param.format = format; childGrid._setSpan(); - final PdfLayoutResult childGridResult = layouter._layout(param); + final PdfLayoutResult? childGridResult = layouter._layout(param); value = childGrid; - if (param.page != childGridResult.page) { - _row._gridResult = childGridResult; + if (childGridResult != null && param.page != childGridResult.page) { + _row!._gridResult = childGridResult; bounds.height = graphics.clientSize.height - bounds.y; } } else { @@ -505,179 +966,179 @@ class PdfGridCell { graphics.restore(); } else if (value is PdfTextElement) { final PdfTextElement textelement = value as PdfTextElement; - final PdfPage page = graphics._page; + final PdfPage? page = graphics._page; textelement._isPdfTextElement = true; - final String textElementString = textelement.text; - PdfTextLayoutResult textlayoutresult; + final String? textElementString = textelement.text; + PdfTextLayoutResult? textlayoutresult; if (_finished) { textlayoutresult = textelement.draw( - page: page, bounds: innerLayoutArea.rect) as PdfTextLayoutResult; + page: page, bounds: innerLayoutArea.rect) as PdfTextLayoutResult?; } else { - textelement.text = _remainingString; + textelement.text = _remainingString!; textlayoutresult = textelement.draw( - page: page, bounds: innerLayoutArea.rect) as PdfTextLayoutResult; + page: page, bounds: innerLayoutArea.rect) as PdfTextLayoutResult?; } - if (textlayoutresult._remainder != null && - textlayoutresult._remainder.isNotEmpty) { + if (textlayoutresult!._remainder != null && + textlayoutresult._remainder!.isNotEmpty) { _remainingString = textlayoutresult._remainder; _finished = false; } else { _remainingString = null; _finished = true; } - textelement.text = textElementString; + textelement.text = textElementString!; } else if (value is String || _remainingString is String) { - String temp; + String? temp; _Rectangle layoutRectangle; - if (innerLayoutArea.height < font.height) { + if (innerLayoutArea.height < font!.height) { layoutRectangle = _Rectangle(innerLayoutArea.x, innerLayoutArea.y, innerLayoutArea.width, font.height); } else { layoutRectangle = innerLayoutArea; } if (innerLayoutArea.height < font.height && - _row._grid._isChildGrid && - _row._grid._parentCell != null) { + _row!._grid._isChildGrid! && + _row!._grid._parentCell != null) { final double height = layoutRectangle.height - - _row._grid._parentCell._row._grid.style.cellPadding.bottom - - _row._grid.style.cellPadding.bottom; + _row!._grid._parentCell!._row!._grid.style.cellPadding.bottom - + _row!._grid.style.cellPadding.bottom; if (height > 0 && height < font.height) { layoutRectangle.height = height; - } else if (height + _row._grid.style.cellPadding.bottom > 0 && - height + _row._grid.style.cellPadding.bottom < font.height) { - layoutRectangle.height = height + _row._grid.style.cellPadding.bottom; + } else if (height + _row!._grid.style.cellPadding.bottom > 0 && + height + _row!._grid.style.cellPadding.bottom < font.height) { + layoutRectangle.height = + height + _row!._grid.style.cellPadding.bottom; } else if (bounds.height < font.height) { layoutRectangle.height = bounds.height; } else if (bounds.height - - _row._grid._parentCell._row._grid.style.cellPadding.bottom < + _row!._grid._parentCell!._row!._grid.style.cellPadding.bottom < font.height) { layoutRectangle.height = bounds.height - - _row._grid._parentCell._row._grid.style.cellPadding.bottom; + _row!._grid._parentCell!._row!._grid.style.cellPadding.bottom; } } if (style.cellPadding != null && - style.cellPadding.bottom == 0 && - style.cellPadding.left == 0 && - style.cellPadding.right == 0 && - style.cellPadding.top == 0) { - layoutRectangle.width -= - style.borders.left.width + style.borders.right.width; + style.cellPadding!.bottom == 0 && + style.cellPadding!.left == 0 && + style.cellPadding!.right == 0 && + style.cellPadding!.top == 0) { + layoutRectangle.width = layoutRectangle.width - + style.borders.left.width + + style.borders.right.width; } if (_finished) { - temp = _remainingString != null && _remainingString.isEmpty + temp = _remainingString != null && _remainingString!.isEmpty ? _remainingString : value; - graphics.drawString(temp, font, + graphics.drawString(temp!, font, pen: textPen, brush: textBrush, bounds: layoutRectangle.rect, format: strFormat); } else { - graphics.drawString(_remainingString, font, + graphics.drawString(_remainingString!, font, pen: textPen, brush: textBrush, bounds: layoutRectangle.rect, format: strFormat); } result = graphics._stringLayoutResult; - if (_row._grid._isChildGrid && - _row._rowBreakHeight > 0 && + if (_row!._grid._isChildGrid! && + _row!._rowBreakHeight > 0 && result != null) { - bounds.height -= - _row._grid._parentCell._row._grid.style.cellPadding.bottom; + bounds.height = bounds.height - + _row!._grid._parentCell!._row!._grid.style.cellPadding.bottom; } } else if (_value is PdfImage) { - if (_imagePosition == PdfGridImagePosition.stretch) { - if (style.cellPadding != null && style.cellPadding != PdfPaddings()) { - final PdfPaddings padding = style.cellPadding; - bounds = _Rectangle( - bounds.x + padding.left, - bounds.y + padding.top, - bounds.width - (padding.left + padding.right), - bounds.height - (padding.top + padding.bottom)); - } else if (_row._grid.style.cellPadding != null && - _row._grid.style.cellPadding != PdfPaddings()) { - final PdfPaddings padding = _row._grid.style.cellPadding; - bounds = _Rectangle( - bounds.x + padding.left, - bounds.y + padding.top, - bounds.width - (padding.left + padding.right), - bounds.height - (padding.top + padding.bottom)); + if (style.cellPadding != null && + style.cellPadding != + PdfPaddings(left: 0, right: 0, top: 0, bottom: 0)) { + final PdfPaddings padding = style.cellPadding!; + bounds = _Rectangle( + bounds.x + padding.left, + bounds.y + padding.top, + bounds.width - (padding.left + padding.right), + bounds.height - (padding.top + padding.bottom)); + } else if (_row!._grid.style.cellPadding != + PdfPaddings(left: 0, right: 0, top: 0, bottom: 0)) { + final PdfPaddings padding = _row!._grid.style.cellPadding; + bounds = _Rectangle( + bounds.x + padding.left, + bounds.y + padding.top, + bounds.width - (padding.left + padding.right), + bounds.height - (padding.top + padding.bottom)); + } + final PdfImage img = value as PdfImage; + double? imgWidth = img.width.toDouble(); + double? imgHeight = img.height.toDouble(); + double spaceX = 0; + double spaceY = 0; + if (_pdfGridStretchOption == _PdfGridStretchOption.uniform || + _pdfGridStretchOption == _PdfGridStretchOption.uniformToFill) { + double ratio = 1; + if (imgWidth > bounds.width) { + ratio = imgWidth / bounds.width; + imgWidth = bounds.width; + imgHeight = imgHeight / ratio; + } + if (imgHeight > bounds.height) { + ratio = imgHeight / bounds.height; + imgHeight = bounds.height; + imgWidth = imgWidth / ratio; } - final PdfImage img = value as PdfImage; - double imgWidth = img.width.toDouble(); - double imgHeight = img.height.toDouble(); - double spaceX = 0; - double spaceY = 0; - if (_pdfGridStretchOption == _PdfGridStretchOption.uniform || - _pdfGridStretchOption == _PdfGridStretchOption.uniformToFill) { - double ratio = 1; - if (imgWidth > bounds.width) { + if (imgWidth < bounds.width && imgHeight < bounds.height) { + spaceX = bounds.width - imgWidth; + spaceY = bounds.height - imgHeight; + if (spaceX < spaceY) { ratio = imgWidth / bounds.width; imgWidth = bounds.width; imgHeight = imgHeight / ratio; - } - if (imgHeight > bounds.height) { + } else { ratio = imgHeight / bounds.height; imgHeight = bounds.height; imgWidth = imgWidth / ratio; } - if (imgWidth < bounds.width && imgHeight < bounds.height) { - spaceX = bounds.width - imgWidth; - spaceY = bounds.height - imgHeight; - if (spaceX < spaceY) { - ratio = imgWidth / bounds.width; - imgWidth = bounds.width; - imgHeight = imgHeight / ratio; - } else { - ratio = imgHeight / bounds.height; - imgHeight = bounds.height; - imgWidth = imgWidth / ratio; - } - } } - if (_pdfGridStretchOption == _PdfGridStretchOption.fill || - _pdfGridStretchOption == _PdfGridStretchOption.none) { - imgWidth = bounds.width; + } + if (_pdfGridStretchOption == _PdfGridStretchOption.fill || + _pdfGridStretchOption == _PdfGridStretchOption.none) { + imgWidth = bounds.width; + imgHeight = bounds.height; + } + if (_pdfGridStretchOption == _PdfGridStretchOption.uniformToFill) { + double ratio = 1; + if (imgWidth == bounds.width && imgHeight < bounds.height) { + ratio = imgHeight / bounds.height; imgHeight = bounds.height; + imgWidth = imgWidth / ratio; } - if (_pdfGridStretchOption == _PdfGridStretchOption.uniformToFill) { - double ratio = 1; - if (imgWidth == bounds.width && imgHeight < bounds.height) { - ratio = imgHeight / bounds.height; - imgHeight = bounds.height; - imgWidth = imgWidth / ratio; - } - if (imgHeight == bounds.height && imgWidth < bounds.width) { - ratio = imgWidth / bounds.width; - imgWidth = bounds.width; - imgHeight = imgHeight / ratio; - } - final PdfPage graphicsPage = graphics._page; - final PdfGraphicsState st = graphicsPage.graphics.save(); - graphicsPage.graphics - .setClip(bounds: bounds.rect, mode: PdfFillMode.winding); - graphicsPage.graphics.drawImage( - img, Rect.fromLTWH(bounds.x, bounds.y, imgWidth, imgHeight)); - graphicsPage.graphics.restore(st); - } else { - graphics.drawImage( - img, Rect.fromLTWH(bounds.x, bounds.y, imgWidth, imgHeight)); + if (imgHeight == bounds.height && imgWidth < bounds.width) { + ratio = imgWidth / bounds.width; + imgWidth = bounds.width; + imgHeight = imgHeight / ratio; } + final PdfPage graphicsPage = graphics._page!; + final PdfGraphicsState st = graphicsPage.graphics.save(); + graphicsPage.graphics + .setClip(bounds: bounds.rect, mode: PdfFillMode.winding); + graphicsPage.graphics.drawImage( + img, Rect.fromLTWH(bounds.x, bounds.y, imgWidth, imgHeight)); + graphicsPage.graphics.restore(st); + } else { + graphics = _setImagePosition( + graphics, img, _Rectangle(bounds.x, bounds.y, imgWidth, imgHeight)); } - graphics.save(); - } - if (style.borders != null && style.borders.left != null) { - graphics = _drawCellBorders(graphics, bounds); + graphics!.save(); } + graphics = _drawCellBorders(graphics, bounds); return result; } void _drawParentCells(PdfGraphics graphics, _Rectangle bounds, bool b) { - final _Point location = _Point(_row._grid._defaultBorder.right.width / 2, - _row._grid._defaultBorder.top.width / 2); + final _Point location = _Point(_row!._grid._defaultBorder.right.width / 2, + _row!._grid._defaultBorder.top.width / 2); if ((bounds.height < graphics.clientSize.height) && (b == true)) { - bounds.height += bounds.y - location.y; + bounds.height = bounds.height + bounds.y - location.y; } final _Rectangle rect = _Rectangle(location.x, location.y, bounds.width, bounds.height); @@ -685,17 +1146,21 @@ class PdfGridCell { rect.y = bounds.y; rect.height = bounds.height; } - PdfGridCell c = this; + PdfGridCell? c = this; if (_parent != null) { - if ((c._row._grid.rows.count == 1) && - (c._row._grid.rows[0].cells.count == 1)) { - c._row._grid.rows[0].cells[0]._present = true; + if ((c._row!._grid.rows.count == 1) && + (c._row!._grid.rows[0].cells.count == 1)) { + c._row!._grid.rows[0].cells[0]._present = true; } else { - for (int rowIndex = 0; rowIndex < c._row._grid.rows.count; rowIndex++) { - final PdfGridRow r = c._row._grid.rows[rowIndex]; + for (int rowIndex = 0; + rowIndex < c._row!._grid.rows.count; + rowIndex++) { + final PdfGridRow? r = c._row!._grid.rows[rowIndex]; if (r == c._row) { - for (int cellIndex = 0; cellIndex < _row.cells.count; cellIndex++) { - final PdfGridCell cell = _row.cells[cellIndex]; + for (int cellIndex = 0; + cellIndex < _row!.cells.count; + cellIndex++) { + final PdfGridCell cell = _row!.cells[cellIndex]; if (cell == c) { cell._present = true; break; @@ -704,19 +1169,19 @@ class PdfGridCell { } } } - while (c._parent != null) { + while (c!._parent != null) { c = c._parent; - c._present = true; - rect.x += c._row._grid.style.cellPadding.left; + c!._present = true; + rect.x = rect.x + c._row!._grid.style.cellPadding.left; } } if (bounds.x >= rect.x) { - rect.x -= bounds.x; + rect.x = rect.x - bounds.x; if (rect.x < 0) { rect.x = bounds.x; } } - PdfGrid pdfGrid = c._row._grid; + PdfGrid pdfGrid = c._row!._grid; for (int i = 0; i < pdfGrid.rows.count; i++) { for (int j = 0; j < pdfGrid.rows[i].cells.count; j++) { if (pdfGrid.rows[i].cells[j]._present == true) { @@ -730,7 +1195,7 @@ class PdfGridCell { } } rect.width = pdfGrid.rows[i]._getWidth() - cellwidth; - final PdfGrid grid = pdfGrid.rows[i].cells[j].value as PdfGrid; + final PdfGrid? grid = pdfGrid.rows[i].cells[j].value as PdfGrid?; if (grid != null) { for (int l = 0; l < grid.rows.count; l++) { for (int m = 0; m < grid.rows[l].cells.count; m++) { @@ -741,7 +1206,7 @@ class PdfGridCell { } } } - graphics = _drawCellBackground(graphics, rect); + graphics = _drawCellBackground(graphics, rect)!; } pdfGrid.rows[i].cells[j]._present = false; if (pdfGrid.rows[i].cells[j].style.backgroundBrush != null) { @@ -750,12 +1215,12 @@ class PdfGridCell { if (cellcount == 0) { rect.width = pdfGrid.columns[j].width; } - graphics = _drawCellBackground(graphics, rect); + graphics = _drawCellBackground(graphics, rect)!; } if (pdfGrid.rows[i].cells[j].value is PdfGrid) { - if ((pdfGrid.style != null) && (!pdfGrid.rows[i]._isrowFinish)) { + if (!pdfGrid.rows[i]._isrowFinish) { if (cellcount == 0) { - rect.x += pdfGrid.style.cellPadding.left; + rect.x = rect.x + pdfGrid.style.cellPadding.left; } } pdfGrid = pdfGrid.rows[i].cells[j].value as PdfGrid; @@ -766,7 +1231,7 @@ class PdfGridCell { rect.width = pdfGrid.columns[j].width; } } - graphics = _drawCellBackground(graphics, rect); + graphics = _drawCellBackground(graphics, rect)!; } i = -1; break; @@ -775,152 +1240,114 @@ class PdfGridCell { } } if (bounds.height < graphics.clientSize.height) { - bounds.height -= bounds.y - location.y; + bounds.height = bounds.height - bounds.y - location.y; } } - PdfGraphics _drawCellBackground(PdfGraphics graphics, _Rectangle bounds) { - final PdfBrush backgroundBrush = _getBackgroundBrush(); + PdfGraphics? _drawCellBackground(PdfGraphics? graphics, _Rectangle bounds) { + final PdfBrush? backgroundBrush = _getBackgroundBrush(); if (backgroundBrush != null) { - graphics.save(); + graphics!.save(); graphics.drawRectangle(brush: backgroundBrush, bounds: bounds.rect); graphics.restore(); } if (style.backgroundImage != null) { - final PdfImage image = style.backgroundImage; - if (style.cellPadding != null && style.cellPadding != PdfPaddings()) { - final PdfPaddings padding = style.cellPadding; + final PdfImage? image = style.backgroundImage; + if (style.cellPadding != null && + style.cellPadding != + PdfPaddings(left: 0, right: 0, top: 0, bottom: 0)) { + final PdfPaddings padding = style.cellPadding!; bounds = _Rectangle( bounds.x + padding.left, bounds.y + padding.top, bounds.width - (padding.left + padding.right), bounds.height - (padding.top + padding.bottom)); - } else if (_row._grid.style.cellPadding != null && - _row._grid.style.cellPadding != PdfPaddings()) { - final PdfPaddings padding = _row._grid.style.cellPadding; + } else if (_row!._grid.style.cellPadding != + PdfPaddings(left: 0, right: 0, top: 0, bottom: 0)) { + final PdfPaddings padding = _row!._grid.style.cellPadding; bounds = _Rectangle( bounds.x + padding.left, bounds.y + padding.top, bounds.width - (padding.left + padding.right), bounds.height - (padding.top + padding.bottom)); } - if (_imagePosition == PdfGridImagePosition.stretch) { - graphics.drawImage(image, bounds.rect); - } else if (_imagePosition == PdfGridImagePosition.center) { - double gridCentreX; - double gridCentreY; - gridCentreX = bounds.x + (bounds.width / 4); - gridCentreY = bounds.y + (bounds.height / 4); - graphics.drawImage( - image, - Rect.fromLTWH( - gridCentreX, gridCentreY, bounds.width / 2, bounds.height / 2)); - } else if (_imagePosition == PdfGridImagePosition.fit) { - final double imageWidth = image.physicalDimension.width; - final double imageHeight = image.physicalDimension.height; - double x; - double y; - if (imageHeight > imageWidth) { - y = bounds.y; - x = bounds.x + bounds.width / 4; - graphics.drawImage( - image, Rect.fromLTWH(x, y, bounds.width / 2, bounds.height)); - } else { - x = bounds.x; - - y = bounds.y + (bounds.height / 4); - graphics.drawImage( - image, Rect.fromLTWH(x, y, bounds.width, bounds.height / 2)); - } - } else if (_imagePosition == PdfGridImagePosition.tile) { - final double cellLeft = bounds.x; - final double cellTop = bounds.y; - double x = cellLeft; - double y = cellTop; - for (; y < bounds.bottom;) { - for (x = cellLeft; x < bounds.right;) { - if (x + image.physicalDimension.width < bounds.right && - y + image.physicalDimension.height < bounds.bottom) { - graphics.drawImage(image, Rect.fromLTWH(x, y, 0, 0)); - } - x += image.physicalDimension.width; - } - y += image.physicalDimension.height; - } - } + return _setImagePosition(graphics, image, bounds); } return graphics; } _Rectangle _adjustContentLayoutArea(_Rectangle bounds) { - PdfPaddings padding = style.cellPadding; + PdfPaddings? padding = style.cellPadding; if (value is PdfGrid) { - final _Size size = (value as PdfGrid)._gridSize; + final _Size? size = (value as PdfGrid)._gridSize; if (padding == null) { - padding = _row._grid.style.cellPadding; - bounds.width -= padding.right + padding.left; - bounds.height -= padding.bottom + padding.top; + padding = _row!._grid.style.cellPadding; + bounds.width = bounds.width - (padding.right + padding.left); + bounds.height = bounds.height - (padding.bottom + padding.top); if (stringFormat.alignment == PdfTextAlignment.center) { - bounds.x += padding.left + (bounds.width - size.width) / 2; - bounds.y += padding.top + (bounds.height - size.height) / 2; + bounds.x = + bounds.x + padding.left + ((bounds.width - size!.width) / 2); + bounds.y = + bounds.y + padding.top + ((bounds.height - size.height) / 2); } else if (stringFormat.alignment == PdfTextAlignment.left) { - bounds.x += padding.left; - bounds.y += padding.top; + bounds.x = bounds.x + padding.left; + bounds.y = bounds.y + padding.top; } else if (stringFormat.alignment == PdfTextAlignment.right) { - bounds.x += padding.left + (bounds.width - size.width); - bounds.y += padding.top; + bounds.x = bounds.x + padding.left + (bounds.width - size!.width); + bounds.y = bounds.y + padding.top; bounds.width = size.width; } } else { - bounds.width -= padding.right + padding.left; - bounds.height -= padding.bottom + padding.top; + bounds.width = bounds.width - (padding.right + padding.left); + bounds.height = bounds.height - (padding.bottom + padding.top); if (stringFormat.alignment == PdfTextAlignment.center) { - bounds.x += padding.left + (bounds.width - size.width) / 2; - bounds.y += padding.top + (bounds.height - size.height) / 2; + bounds.x = + bounds.x + padding.left + ((bounds.width - size!.width) / 2); + bounds.y = + bounds.y + padding.top + ((bounds.height - size.height) / 2); } else if (stringFormat.alignment == PdfTextAlignment.left) { - bounds.x += padding.left; - bounds.y += padding.top; + bounds.x = bounds.x + padding.left; + bounds.y = bounds.y + padding.top; } else if (stringFormat.alignment == PdfTextAlignment.right) { - bounds.x += padding.left + (bounds.width - size.width); - bounds.y += padding.top; + bounds.x = bounds.x + padding.left + (bounds.width - size!.width); + bounds.y = bounds.y + padding.top; bounds.width = size.width; } } } else { if (padding == null) { - padding = _row._grid.style.cellPadding; - bounds.x += padding.left; - bounds.y += padding.top; - bounds.width -= padding.right + padding.left; - bounds.height -= padding.bottom + padding.top; + padding = _row!._grid.style.cellPadding; + bounds.x = bounds.x + padding.left; + bounds.y = bounds.y + padding.top; + bounds.width = bounds.width - (padding.right + padding.left); + bounds.height = bounds.height - (padding.bottom + padding.top); } else { - bounds.x += padding.left; - bounds.y += padding.top; - bounds.width -= padding.right + padding.left; - bounds.height -= padding.bottom + padding.top; + bounds.x = bounds.x + padding.left; + bounds.y = bounds.y + padding.top; + bounds.width = bounds.width - (padding.right + padding.left); + bounds.height = bounds.height - (padding.bottom + padding.top); } } - return bounds; } - _Rectangle _adjustOuterLayoutArea(_Rectangle bounds, PdfGraphics g) { + _Rectangle _adjustOuterLayoutArea(_Rectangle bounds, PdfGraphics? g) { bool isHeader = false; - final double cellSpacing = _row._grid.style.cellSpacing; + final double cellSpacing = _row!._grid.style.cellSpacing; if (cellSpacing > 0) { bounds = _Rectangle(bounds.x + cellSpacing, bounds.y + cellSpacing, bounds.width - cellSpacing, bounds.height - cellSpacing); } - final int currentColIndex = _row.cells.indexOf(this); + final int currentColIndex = _row!.cells.indexOf(this); if (columnSpan > 1 || - (_row._rowOverflowIndex > 0 && - (currentColIndex == _row._rowOverflowIndex + 1) && + (_row!._rowOverflowIndex > 0 && + (currentColIndex == _row!._rowOverflowIndex + 1) && _isCellMergeContinue)) { int span = columnSpan; if (span == 1 && _isCellMergeContinue) { - for (int j = currentColIndex + 1; j < _row._grid.columns.count; j++) { - if (_row.cells[j]._isCellMergeContinue) { + for (int j = currentColIndex + 1; j < _row!._grid.columns.count; j++) { + if (_row!.cells[j]._isCellMergeContinue) { span++; } else { break; @@ -929,41 +1356,41 @@ class PdfGridCell { } double totalWidth = 0; for (int i = currentColIndex; i < currentColIndex + span; i++) { - if (_row._grid.style.allowHorizontalOverflow) { + if (_row!._grid.style.allowHorizontalOverflow) { double width; final double compWidth = - _row._grid._gridSize.width < g.clientSize.width - ? _row._grid._gridSize.width + _row!._grid._gridSize.width < g!.clientSize.width + ? _row!._grid._gridSize.width : g.clientSize.width; - if (_row._grid._gridSize.width > g.clientSize.width) { - width = bounds.x + totalWidth + _row._grid.columns[i].width; + if (_row!._grid._gridSize.width > g.clientSize.width) { + width = bounds.x + totalWidth + _row!._grid.columns[i].width; } else { - width = totalWidth + _row._grid.columns[i].width; + width = totalWidth + _row!._grid.columns[i].width; } if (width > compWidth) { break; } } - totalWidth += _row._grid.columns[i].width; + totalWidth += _row!._grid.columns[i].width; } - totalWidth -= _row._grid.style.cellSpacing; + totalWidth -= _row!._grid.style.cellSpacing; bounds.width = totalWidth; } - if (rowSpan > 1 || _row._rowSpanExists) { + if (rowSpan > 1 || _row!._rowSpanExists) { int span = rowSpan; - int currentRowIndex = _row._grid.rows._indexOf(_row); + int currentRowIndex = _row!._grid.rows._indexOf(_row); if (currentRowIndex == -1) { - currentRowIndex = _row._grid.headers._indexOf(_row); + currentRowIndex = _row!._grid.headers._indexOf(_row!); if (currentRowIndex != -1) { isHeader = true; } } if (span == 1 && _isCellMergeContinue) { - for (int j = currentRowIndex + 1; j < _row._grid.rows.count; j++) { + for (int j = currentRowIndex + 1; j < _row!._grid.rows.count; j++) { if (isHeader - ? _row + ? _row! ._grid.headers[j].cells[currentColIndex]._isCellMergeContinue - : _row + : _row! ._grid.rows[j].cells[currentColIndex]._isCellMergeContinue) { span++; } else { @@ -975,32 +1402,32 @@ class PdfGridCell { double max = 0; if (isHeader) { for (int i = currentRowIndex; i < currentRowIndex + span; i++) { - totalHeight += _row._grid.headers[i].height; + totalHeight += _row!._grid.headers[i].height; } - totalHeight -= _row._grid.style.cellSpacing; + totalHeight -= _row!._grid.style.cellSpacing; bounds.height = totalHeight; } else { for (int i = currentRowIndex; i < currentRowIndex + span; i++) { - if (!_row._grid.rows[i]._isRowSpanRowHeightSet) { - _row._grid.rows[i]._isRowHeightSet = false; + if (!_row!._grid.rows[i]._isRowSpanRowHeightSet) { + _row!._grid.rows[i]._isRowHeightSet = false; } totalHeight += isHeader - ? _row._grid.headers[i].height - : _row._grid.rows[i].height; - final PdfGridRow row = _row._grid.rows[i]; - final int rowIndex = _row._grid.rows._indexOf(row); + ? _row!._grid.headers[i].height + : _row!._grid.rows[i].height; + final PdfGridRow? row = _row!._grid.rows[i]; + final int rowIndex = _row!._grid.rows._indexOf(row); if (rowSpan > 1) { - for (int cellIndex = 0; cellIndex < row.cells.count; cellIndex++) { + for (int cellIndex = 0; cellIndex < row!.cells.count; cellIndex++) { final PdfGridCell cell = row.cells[cellIndex]; if (cell.rowSpan > 1) { double tempHeight = 0; for (int j = i; j < i + cell.rowSpan; j++) { - if (!_row._grid.rows[j]._isRowSpanRowHeightSet) { - _row._grid.rows[j]._isRowHeightSet = false; + if (!_row!._grid.rows[j]._isRowSpanRowHeightSet) { + _row!._grid.rows[j]._isRowHeightSet = false; } - tempHeight += _row._grid.rows[j].height; - if (!_row._grid.rows[j]._isRowSpanRowHeightSet) { - _row._grid.rows[j]._isRowHeightSet = true; + tempHeight += _row!._grid.rows[j].height; + if (!_row!._grid.rows[j]._isRowSpanRowHeightSet) { + _row!._grid.rows[j]._isRowHeightSet = true; } } if (cell.height > tempHeight) { @@ -1011,9 +1438,9 @@ class PdfGridCell { max += _tempRowSpanRemainingHeight; } final int index = row.cells.indexOf(cell); - _row._grid.rows[(rowIndex + cell.rowSpan) - 1].cells[index] + _row!._grid.rows[(rowIndex + cell.rowSpan) - 1].cells[index] ._rowSpanRemainingHeight = max; - _tempRowSpanRemainingHeight = _row + _tempRowSpanRemainingHeight = _row! ._grid .rows[(rowIndex + cell.rowSpan) - 1] .cells[index] @@ -1023,33 +1450,141 @@ class PdfGridCell { } } } - if (!_row._grid.rows[i]._isRowSpanRowHeightSet) { - _row._grid.rows[i]._isRowHeightSet = true; + if (!_row!._grid.rows[i]._isRowSpanRowHeightSet) { + _row!._grid.rows[i]._isRowHeightSet = true; } } - final int cellIndex = _row.cells.indexOf(this); - totalHeight -= _row._grid.style.cellSpacing; - if (_row.cells[cellIndex].height > totalHeight && - (!_row._grid.rows[(currentRowIndex + span) - 1]._isRowHeightSet)) { - _row._grid.rows[(currentRowIndex + span) - 1].cells[cellIndex] + final int cellIndex = _row!.cells.indexOf(this); + totalHeight -= _row!._grid.style.cellSpacing; + if (_row!.cells[cellIndex].height > totalHeight && + (!_row!._grid.rows[(currentRowIndex + span) - 1]._isRowHeightSet)) { + _row!._grid.rows[(currentRowIndex + span) - 1].cells[cellIndex] ._rowSpanRemainingHeight = - _row.cells[cellIndex].height - totalHeight; - totalHeight = _row.cells[cellIndex].height; + _row!.cells[cellIndex].height - totalHeight; + totalHeight = _row!.cells[cellIndex].height; bounds.height = totalHeight; } else { bounds.height = totalHeight; } - if (!_row._rowMergeComplete) { + if (!_row!._rowMergeComplete) { bounds.height = totalHeight; } } } return bounds; } + + PdfGraphics? _setImagePosition( + PdfGraphics? graphics, PdfImage? image, _Rectangle bounds) { + if (_imagePosition == PdfGridImagePosition.stretch) { + graphics!.drawImage(image!, bounds.rect); + } else if (_imagePosition == PdfGridImagePosition.center) { + double gridCentreX; + double gridCentreY; + gridCentreX = bounds.x + (bounds.width / 4); + gridCentreY = bounds.y + (bounds.height / 4); + graphics!.drawImage( + image!, + Rect.fromLTWH( + gridCentreX, gridCentreY, bounds.width / 2, bounds.height / 2)); + } else if (_imagePosition == PdfGridImagePosition.fit) { + final double imageWidth = image!.physicalDimension.width; + final double imageHeight = image.physicalDimension.height; + double? x; + double? y; + if (imageHeight > imageWidth) { + y = bounds.y; + x = bounds.x + bounds.width / 4; + graphics!.drawImage( + image, Rect.fromLTWH(x, y, bounds.width / 2, bounds.height)); + } else { + x = bounds.x; + y = bounds.y + (bounds.height / 4); + graphics!.drawImage( + image, Rect.fromLTWH(x, y, bounds.width, bounds.height / 2)); + } + } else if (_imagePosition == PdfGridImagePosition.tile) { + final double? cellLeft = bounds.x; + final double cellTop = bounds.y; + final pWidth = _physicalDimension(image, true); + final pHeight = _physicalDimension(image, false); + double? x = cellLeft; + double y = cellTop; + for (; y < bounds.bottom;) { + for (x = cellLeft; x! < bounds.right;) { + if (x + pWidth > bounds.right && y + pHeight > bounds.bottom) { + final PdfTemplate template = + PdfTemplate(bounds.right - x, bounds.bottom - y); + template.graphics!.drawImage(image!, Rect.fromLTWH(0, 0, 0, 0)); + graphics!.drawPdfTemplate(template, Offset(x, y)); + } else if (x + pWidth > bounds.right) { + final PdfTemplate template = PdfTemplate(bounds.right - x, pHeight); + template.graphics!.drawImage(image!, Rect.fromLTWH(0, 0, 0, 0)); + graphics!.drawPdfTemplate(template, Offset(x, y)); + } else if (y + pHeight > bounds.bottom) { + final PdfTemplate template = PdfTemplate(pWidth, bounds.bottom - y); + template.graphics!.drawImage(image!, Rect.fromLTWH(0, 0, 0, 0)); + graphics!.drawPdfTemplate(template, Offset(x, y)); + } else { + graphics!.drawImage(image!, Rect.fromLTWH(x, y, 0, 0)); + } + x += pWidth; + } + y += pHeight; + } + } + return graphics; + } + + //if horizontal/vertical resolution is not set, resolution set as default 96. + double _physicalDimension(PdfImage? image, bool isHorizontal) { + if (isHorizontal) { + return image!.width / + ((image.horizontalResolution > 0 ? image.horizontalResolution : 96) / + 72); + } else { + return image!.height / + ((image.verticalResolution > 0 ? image.verticalResolution : 96) / 72); + } + } } /// Provides access to an ordered, strongly typed collection of /// [PdfGridCell] objects. +/// ```dart +/// //Create a new PDF document +/// PdfDocument document = PdfDocument(); +/// //Create a PdfGrid +/// PdfGrid grid = PdfGrid(); +/// //Add columns to grid +/// grid.columns.add(count: 3); +/// //Add headers to grid +/// PdfGridRow header = grid.headers.add(1)[0]; +/// header.cells[0].value = 'Employee ID'; +/// header.cells[1].value = 'Employee Name'; +/// header.cells[2].value = 'Salary'; +/// //Add rows to grid +/// PdfGridRow row1 = grid.rows.add(); +/// //Gets the cell collection from the row +/// PdfGridCellCollection cellCollection = row1.cells; +/// //Gets the specific cell from the row collection +/// PdfGridCell cell1 = cellCollection[0]; +/// cell1.value = 'E01'; +/// cell1.style.cellPadding = PdfPaddings(left: 0, right: 0, top: 10, bottom: 10); +/// cellCollection[1].value = 'Clay'; +/// cellCollection[2].value = '\$10000'; +/// PdfGridRow row2 = grid.rows.add(); +/// row2.cells[0].value = 'E02'; +/// row2.cells[1].value = 'Simon'; +/// row2.cells[2].value = '\$12,000'; +/// //Draw the grid in PDF document page +/// grid.draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfGridCellCollection { //Constructors /// Initializes a new instance of the [PdfGridCellCollection] class @@ -1060,18 +1595,132 @@ class PdfGridCellCollection { } //Fields - PdfGridRow _row; - List _cells; + late PdfGridRow _row; + late List _cells; //Properties /// Gets the cells count. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// PdfGridRow header = grid.headers.add(1)[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// //Gets the cell collection from the row + /// PdfGridCellCollection cellCollection = row1.cells; + /// //Gets the specific cell from the row collection + /// PdfGridCell cell1 = cellCollection[0]; + /// cell1.value = 'E01'; + /// cell1.style.cellPadding = PdfPaddings(left: 0, right: 0, top: 10, bottom: 10); + /// cellCollection[1].value = 'Clay'; + /// cellCollection[2].value = '\$10000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Gets the cells count + /// int cellsCount = cellCollection.count; + /// //Gets the index of particular cell + /// int index = cellCollection.indexOf(cell1); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int get count => _cells.length; /// Gets the [PdfGridCell] at the specified index. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// PdfGridRow header = grid.headers.add(1)[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// //Gets the cell collection from the row + /// PdfGridCellCollection cellCollection = row1.cells; + /// //Gets the specific cell from the row collection + /// PdfGridCell cell1 = cellCollection[0]; + /// cell1.value = 'E01'; + /// cell1.style.cellPadding = PdfPaddings(left: 0, right: 0, top: 10, bottom: 10); + /// cellCollection[1].value = 'Clay'; + /// cellCollection[2].value = '\$10000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Gets the cells count + /// int cellsCount = cellCollection.count; + /// //Gets the index of particular cell + /// int index = cellCollection.indexOf(cell1); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridCell operator [](int index) => _returnValue(index); //Public methods /// Returns the index of a particular cell in the collection. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// PdfGridRow header = grid.headers.add(1)[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// //Gets the cell collection from the row + /// PdfGridCellCollection cellCollection = row1.cells; + /// //Gets the specific cell from the row collection + /// PdfGridCell cell1 = cellCollection[0]; + /// cell1.value = 'E01'; + /// cell1.style.cellPadding = PdfPaddings(left: 0, right: 0, top: 10, bottom: 10); + /// cellCollection[1].value = 'Clay'; + /// cellCollection[2].value = '\$10000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Gets the cells count + /// int cellsCount = cellCollection.count; + /// //Gets the index of particular cell + /// int index = cellCollection.indexOf(cell1); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int indexOf(PdfGridCell cell) { return _cells.indexOf(cell); } @@ -1085,7 +1734,7 @@ class PdfGridCellCollection { } void _add(PdfGridCell cell) { - cell.style ??= _row.style as PdfGridCellStyle; + // cell.style ??= _row!.style as PdfGridCellStyle; cell._row = _row; _cells.add(cell); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_column.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_column.dart index 7a2e4a857..68e94c422 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_column.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_column.dart @@ -1,10 +1,80 @@ part of pdf; /// Represents the schema of a column in a [PdfGrid]. +/// ```dart +/// //Create a new PDF document +/// PdfDocument document = PdfDocument(); +/// //Create a PdfGrid +/// PdfGrid grid = PdfGrid(); +/// //Create column to the PDF grid +/// PdfGridColumn column = PdfGridColumn(grid); +/// //Add columns to grid +/// grid.columns.add(column: column); +/// grid.columns.add(count: 2); +/// //Add headers to grid +/// grid.headers.add(1); +/// PdfGridRow header = grid.headers[0]; +/// header.cells[0].value = 'Employee ID'; +/// header.cells[1].value = 'Employee Name'; +/// header.cells[2].value = 'Salary'; +/// //Add rows to grid +/// PdfGridRow row1 = grid.rows.add(); +/// row1.cells[0].value = 'E01'; +/// row1.cells[1].value = 'Clay'; +/// row1.cells[2].value = '\$10,000'; +/// PdfGridRow row2 = grid.rows.add(); +/// row2.cells[0].value = 'E02'; +/// row2.cells[1].value = 'Simon'; +/// row2.cells[2].value = '\$12,000'; +/// //Set the width +/// grid.columns[1].width = 50; +/// //Draw the grid in PDF document page +/// grid.draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfGridColumn { //Constructors /// Initializes a new instance of the [PdfGridColumn] class /// with the parent grid. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Create column to the PDF grid + /// PdfGridColumn column = PdfGridColumn(grid); + /// //Add columns to grid + /// grid.columns.add(column: column); + /// grid.columns.add(count: 2); + /// //Add headers to grid + /// grid.headers.add(1); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the width + /// grid.columns[1].width = 50; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridColumn(PdfGrid grid) { _grid = grid; _width = -1; @@ -12,35 +82,189 @@ class PdfGridColumn { } //Fields - PdfGrid _grid; - double _width; - PdfStringFormat _format; - bool _isCustomWidth; + late PdfGrid _grid; + late double _width; + PdfStringFormat? _format; + late bool _isCustomWidth; //Properties /// Gets the width of the [PdfGridColumn]. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Create column to the PDF grid + /// PdfGridColumn column = PdfGridColumn(grid); + /// //Add columns to grid + /// grid.columns.add(column: column); + /// grid.columns.add(count: 2); + /// //Add headers to grid + /// grid.headers.add(1); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the width + /// grid.columns[1].width = 50; + /// //Set the column text format + /// grid.columns[0].format = PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` double get width => _width; - - /// Sets the width of the [PdfGridColumn]. set width(double value) { _isCustomWidth = true; _width = value; } /// Gets the information about the text formatting. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Create column to the PDF grid + /// PdfGridColumn column = PdfGridColumn(grid); + /// //Add columns to grid + /// grid.columns.add(column: column); + /// grid.columns.add(count: 2); + /// //Add headers to grid + /// grid.headers.add(1); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the width + /// grid.columns[1].width = 50; + /// //Set the column text format + /// grid.columns[0].format = PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfStringFormat get format => _format ??= PdfStringFormat(); - - /// Sets the information about the text formatting. set format(PdfStringFormat value) { _format = value; } /// Gets the parent [PdfGrid]. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Create column to the PDF grid + /// PdfGridColumn column = PdfGridColumn(grid); + /// //Add columns to grid + /// grid.columns.add(column: column); + /// grid.columns.add(count: 2); + /// //Add headers to grid + /// grid.headers.add(1); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the width + /// grid.columns[1].width = 50; + /// //Set the column text format + /// grid.columns[0].format = PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGrid get grid => _grid; } /// Provides access to an ordered, strongly typed collection of [PdfGridColumn] /// objects. +/// ```dart +/// //Create a new PDF document +/// PdfDocument document = PdfDocument(); +/// //Create a PdfGrid +/// PdfGrid grid = PdfGrid(); +/// //Create column to the PDF grid +/// PdfGridColumn column = PdfGridColumn(grid); +/// //Gets column form the PDF grid +/// PdfGridColumnCollection columns = grid.columns; +/// //Add columns to grid +/// columns.add(column: column); +/// columns.add(count: 2); +/// //Add headers to grid +/// grid.headers.add(1); +/// PdfGridRow header = grid.headers[0]; +/// header.cells[0].value = 'Employee ID'; +/// header.cells[1].value = 'Employee Name'; +/// header.cells[2].value = 'Salary'; +/// //Add rows to grid +/// PdfGridRow row1 = grid.rows.add(); +/// row1.cells[0].value = 'E01'; +/// row1.cells[1].value = 'Clay'; +/// row1.cells[2].value = '\$10,000'; +/// PdfGridRow row2 = grid.rows.add(); +/// row2.cells[0].value = 'E02'; +/// row2.cells[1].value = 'Simon'; +/// row2.cells[2].value = '\$12,000'; +/// //Set the width +/// grid.columns[1].width = 50; +/// //Set the column text format +/// grid.columns[0].format = PdfStringFormat( +/// alignment: PdfTextAlignment.center, +/// lineAlignment: PdfVerticalAlignment.bottom); +/// //Draw the grid in PDF document page +/// grid.draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfGridColumnCollection { /// Initializes a new instance of the [PdfGridColumnCollection] class /// with the parent grid. @@ -51,15 +275,99 @@ class PdfGridColumnCollection { } //Fields - PdfGrid _grid; - List _columns; - double _columnsWidth; + late PdfGrid _grid; + late List _columns; + late double _columnsWidth; //Properties /// Gets the number of columns in the [PdfGrid]. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Create column to the PDF grid + /// PdfGridColumn column = PdfGridColumn(grid); + /// //Gets column form the PDF grid + /// PdfGridColumnCollection columns = grid.columns; + /// //Add columns to grid + /// columns.add(column: column); + /// columns.add(count: 2); + /// //Gets column count + /// int columnCount = columns.count; + /// //Add headers to grid + /// grid.headers.add(1); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the width + /// grid.columns[1].width = 50; + /// //Set the column text format + /// grid.columns[0].format = PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int get count => _columns.length; /// Gets the [PdfGridColumn] at the specified index. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Create column to the PDF grid + /// PdfGridColumn column = PdfGridColumn(grid); + /// //Gets column form the PDF grid + /// PdfGridColumnCollection columns = grid.columns; + /// //Add columns to grid + /// columns.add(column: column); + /// columns.add(count: 2); + /// //Add headers to grid + /// grid.headers.add(1); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the width + /// grid.columns[1].width = 50; + /// //Set the column text format + /// grid.columns[0].format = PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridColumn operator [](int index) => _returnValue(index); double get _width { if (_columnsWidth == -1) { @@ -77,7 +385,48 @@ class PdfGridColumnCollection { //Public methods /// Adds the column into the collection. - void add({int count, PdfGridColumn column}) { + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Create column to the PDF grid + /// PdfGridColumn column = PdfGridColumn(grid); + /// //Gets column form the PDF grid + /// PdfGridColumnCollection columns = grid.columns; + /// //Add columns to grid + /// columns.add(column: column); + /// columns.add(count: 2); + /// //Add headers to grid + /// grid.headers.add(1); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the width + /// grid.columns[1].width = 50; + /// //Set the column text format + /// grid.columns[0].format = PdfStringFormat( + /// alignment: PdfTextAlignment.center, + /// lineAlignment: PdfVerticalAlignment.bottom); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + void add({int? count, PdfGridColumn? column}) { if (count == null && column == null) { final PdfGridColumn column = PdfGridColumn(_grid); _columns.add(column); @@ -116,18 +465,18 @@ class PdfGridColumnCollection { return totalWidth; } - List _getDefaultWidths(double totalWidth) { - final List widths = List.filled(count, 0); + List _getDefaultWidths(double? totalWidth) { + final List widths = List.filled(count, 0); int subFactor = count; for (int i = 0; i < count; i++) { if (_grid._isPageWidth && - totalWidth >= 0 && + totalWidth! >= 0 && !_columns[i]._isCustomWidth) { _columns[i].width = -1; } else { widths[i] = _columns[i].width; if (_columns[i].width > 0 && _columns[i]._isCustomWidth) { - totalWidth -= _columns[i].width; + totalWidth = totalWidth! - _columns[i].width; subFactor--; } else { widths[i] = -1; @@ -135,8 +484,8 @@ class PdfGridColumnCollection { } } for (int i = 0; i < count; i++) { - final double width = totalWidth / subFactor; - if (widths[i] <= 0) { + final double width = totalWidth! / subFactor; + if (widths[i]! <= 0) { widths[i] = width; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_row.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_row.dart index dc7dee251..2793d5a44 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_row.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_row.dart @@ -1,51 +1,251 @@ part of pdf; /// Provides customization of the settings for the particular row. +/// ```dart +/// //Create a new PDF document +/// PdfDocument document = PdfDocument(); +/// //Create a PdfGrid +/// PdfGrid grid = PdfGrid(); +/// //Add columns to grid +/// grid.columns.add(count: 3); +/// //Add headers to grid +/// grid.headers.add(2); +/// PdfGridRow header = grid.headers[0]; +/// header.cells[0].value = 'Employee ID'; +/// header.cells[1].value = 'Employee Name'; +/// header.cells[2].value = 'Salary'; +/// //Add rows to grid +/// PdfGridRow row1 = grid.rows.add(); +/// row1.cells[0].value = 'E01'; +/// row1.cells[1].value = 'Clay'; +/// row1.cells[2].value = '\$10,000'; +/// PdfGridRow row2 = grid.rows.add(); +/// row2.cells[0].value = 'E02'; +/// row2.cells[1].value = 'Simon'; +/// row2.cells[2].value = '\$12,000'; +/// //Set the row span +/// row1.cells[1].rowSpan = 2; +/// //Set the row height +/// row2.height = 20; +/// //Set the row style +/// row1.style = PdfGridRowStyle( +/// backgroundBrush: PdfBrushes.dimGray, +/// textPen: PdfPens.lightGoldenrodYellow, +/// textBrush: PdfBrushes.darkOrange, +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); +/// //Draw the grid in PDF document page +/// grid.draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfGridRow { /// Initializes a new instance of the [PdfGridRow] class with the parent grid. - PdfGridRow(PdfGrid grid, {PdfGridRowStyle style, double height}) { + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// grid.headers.add(2); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the row span + /// row1.cells[1].rowSpan = 2; + /// //Set the row height + /// row2.height = 20; + /// //Set the row style + /// row1.style = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.dimGray, + /// textPen: PdfPens.lightGoldenrodYellow, + /// textBrush: PdfBrushes.darkOrange, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfGridRow(PdfGrid grid, {PdfGridRowStyle? style, double? height}) { _initialize(grid, style, height); } //Fields - PdfGrid _grid; - PdfGridCellCollection _cells; - PdfGridRowStyle _style; - double _height; - double _width; - bool _rowSpanExists; - double _rowBreakHeight; - int _rowOverflowIndex; - PdfLayoutResult _gridResult; - bool _isRowBreaksNextPage; - bool _isrowFinish; - bool _rowMergeComplete; - int _noOfPageCount; - bool _isRowHeightSet; - int _maximumRowSpan; - bool _isPageBreakRowSpanApplied; - bool _isRowSpanRowHeightSet; - bool _isHeaderRow; + late PdfGrid _grid; + PdfGridCellCollection? _cells; + PdfGridRowStyle? _style; + late double _height; + late double _width; + late bool _rowSpanExists; + late double _rowBreakHeight; + late int _rowOverflowIndex; + PdfLayoutResult? _gridResult; + late bool _isRowBreaksNextPage; + late bool _isrowFinish; + late bool _rowMergeComplete; + late int _noOfPageCount; + late bool _isRowHeightSet; + late int _maximumRowSpan; + late bool _isPageBreakRowSpanApplied; + late bool _isRowSpanRowHeightSet; + late bool _isHeaderRow; //Properties /// Gets the cells from the selected row. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// grid.headers.add(2); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the row span + /// row1.cells[1].rowSpan = 2; + /// //Set the row height + /// row2.height = 20; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridCellCollection get cells { _cells ??= PdfGridCellCollection._(this); - return _cells; + return _cells!; } - /// Gets the row style. + /// Gets or sets the row style. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// grid.headers.add(2); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the row span + /// row1.cells[1].rowSpan = 2; + /// //Set the row height + /// row2.height = 20; + /// //Set the row style + /// row1.style = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.dimGray, + /// textPen: PdfPens.lightGoldenrodYellow, + /// textBrush: PdfBrushes.darkOrange, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// //Create the PDF grid row style. Assign to second row + /// PdfGridRowStyle rowStyle = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.lightGoldenrodYellow, + /// textPen: PdfPens.indianRed, + /// textBrush: PdfBrushes.lightYellow, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// row2.style = rowStyle; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridRowStyle get style { _style ??= PdfGridRowStyle(); - return _style; + return _style!; } - /// Sets the row style. - set style(PdfGridRowStyle value) { + set style(PdfGridRowStyle? value) { _style = value; } - /// Gets the height of the row. + /// Gets or sets the height of the row. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// grid.headers.add(2); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the row span + /// row1.cells[1].rowSpan = 2; + /// //Set the row height + /// row2.height = 20; + /// //Set the row style + /// row1.style = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.dimGray, + /// textPen: PdfPens.lightGoldenrodYellow, + /// textBrush: PdfBrushes.darkOrange, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` double get height { if (!_isRowHeightSet) { _height = _measureHeight(); @@ -53,7 +253,6 @@ class PdfGridRow { return _height; } - /// Sets the height of the row. set height(double value) { _isRowHeightSet = true; _height = value; @@ -62,8 +261,7 @@ class PdfGridRow { int get _index => _grid.rows._indexOf(this); //Implementation - void _initialize(PdfGrid grid, PdfGridRowStyle style, double height) { - ArgumentError.checkNotNull(grid); + void _initialize(PdfGrid grid, PdfGridRowStyle? style, double? height) { if (style != null) { _style = style; } @@ -103,8 +301,8 @@ class PdfGridRow { if (_grid.headers._indexOf(this) != -1) { isHeader = true; } - for (int i = 0; i < _cells.count; i++) { - final PdfGridCell cell = _cells[i]; + for (int i = 0; i < _cells!.count; i++) { + final PdfGridCell cell = _cells![i]; if (cell._rowSpanRemainingHeight > rowSpanRemainingHeight) { rowSpanRemainingHeight = cell._rowSpanRemainingHeight; } @@ -125,7 +323,7 @@ class PdfGridRow { if (rowHeight == 0) { rowHeight = maxHeight; } else if (rowSpanRemainingHeight > 0) { - rowHeight += rowSpanRemainingHeight; + rowHeight = rowHeight + rowSpanRemainingHeight; } if (isHeader && maxHeight != 0 && rowHeight != 0 && rowHeight < maxHeight) { rowHeight = maxHeight; @@ -144,28 +342,250 @@ class PdfGridRow { /// Provides access to an ordered, strongly typed collection of /// [PdfGridRow] objects. +/// ```dart +/// //Create a new PDF document +/// PdfDocument document = PdfDocument(); +/// //Create a PdfGrid +/// PdfGrid grid = PdfGrid(); +/// //Add columns to grid +/// grid.columns.add(count: 3); +/// //Add headers to grid +/// grid.headers.add(2); +/// PdfGridRow header = grid.headers[0]; +/// header.cells[0].value = 'Employee ID'; +/// header.cells[1].value = 'Employee Name'; +/// header.cells[2].value = 'Salary'; +/// //Add rows to grid +/// grid.rows.add(); +/// grid.rows.add(); +/// //Gets the row collection +/// PdfGridRowCollection rowCollection = grid.rows; +/// PdfGridRow row1 = rowCollection[0]; +/// row1.cells[0].value = 'E01'; +/// row1.cells[1].value = 'Clay'; +/// row1.cells[2].value = '\$10,000'; +/// PdfGridRow row2 = rowCollection[1]; +/// row2.cells[0].value = 'E02'; +/// row2.cells[1].value = 'Simon'; +/// row2.cells[2].value = '\$12,000'; +/// //Set the row span +/// row1.cells[1].rowSpan = 2; +/// //Set the row height +/// row2.height = 20; +/// //Set the row style +/// rowCollection[0].style = PdfGridRowStyle( +/// backgroundBrush: PdfBrushes.dimGray, +/// textPen: PdfPens.lightGoldenrodYellow, +/// textBrush: PdfBrushes.darkOrange, +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); +/// //Draw the grid in PDF document page +/// grid.draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfGridRowCollection { /// Initializes a new instance of the [PdfGridRowCollection] class /// with the parent grid. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// grid.headers.add(2); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// grid.rows.add(); + /// grid.rows.add(); + /// //Gets the row collection + /// PdfGridRowCollection rowCollection = grid.rows; + /// PdfGridRow row1 = rowCollection[0]; + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = rowCollection[1]; + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the row span + /// row1.cells[1].rowSpan = 2; + /// //Set the row height + /// row2.height = 20; + /// //Set the row style + /// rowCollection[0].style = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.dimGray, + /// textPen: PdfPens.lightGoldenrodYellow, + /// textBrush: PdfBrushes.darkOrange, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridRowCollection(PdfGrid grid) { _grid = grid; _rows = []; } //Fields - PdfGrid _grid; - List _rows; + late PdfGrid _grid; + late List _rows; //Properties /// Gets the rows count. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// grid.headers.add(2); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// grid.rows.add(); + /// grid.rows.add(); + /// //Gets the row count + /// int rowCount = grid.rows.count; + /// //Gets the row collection + /// PdfGridRowCollection rowCollection = grid.rows; + /// PdfGridRow row1 = rowCollection[0]; + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = rowCollection[1]; + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the row span + /// row1.cells[1].rowSpan = 2; + /// //Set the row height + /// row2.height = 20; + /// //Set the row style + /// rowCollection[0].style = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.dimGray, + /// textPen: PdfPens.lightGoldenrodYellow, + /// textBrush: PdfBrushes.darkOrange, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int get count => _rows.length; /// Gets the [PdfGridRow] at the specified index. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// grid.headers.add(2); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// grid.rows.add(); + /// grid.rows.add(); + /// //Gets the row collection + /// PdfGridRowCollection rowCollection = grid.rows; + /// PdfGridRow row1 = rowCollection[0]; + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = rowCollection[1]; + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the row span + /// row1.cells[1].rowSpan = 2; + /// //Set the row height + /// row2.height = 20; + /// //Set the row style + /// rowCollection[0].style = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.dimGray, + /// textPen: PdfPens.lightGoldenrodYellow, + /// textBrush: PdfBrushes.darkOrange, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridRow operator [](int index) => _returnValue(index); //Public methods /// Add a row to the grid. - PdfGridRow add([PdfGridRow row]) { + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// grid.headers.add(2); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// grid.rows.add(); + /// grid.rows.add(); + /// //Gets the row collection + /// PdfGridRowCollection rowCollection = grid.rows; + /// PdfGridRow row1 = rowCollection[0]; + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = rowCollection[1]; + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the row span + /// row1.cells[1].rowSpan = 2; + /// //Set the row height + /// row2.height = 20; + /// //Set the row style + /// rowCollection[0].style = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.dimGray, + /// textPen: PdfPens.lightGoldenrodYellow, + /// textBrush: PdfBrushes.darkOrange, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfGridRow add([PdfGridRow? row]) { if (row == null) { final PdfGridRow row = PdfGridRow(_grid); add(row); @@ -186,9 +606,47 @@ class PdfGridRowCollection { } /// Sets the row span and column span to a cell. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// grid.headers.add(2); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the rows span + /// grid.rows.setSpan(0, 1, 2, 1); + /// //Set the row height + /// row2.height = 20; + /// //Set the row style + /// row1.style = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.dimGray, + /// textPen: PdfPens.lightGoldenrodYellow, + /// textBrush: PdfBrushes.darkOrange, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` void setSpan(int rowIndex, int cellIndex, int rowSpan, int columnSpan) { - ArgumentError.checkNotNull(rowSpan); - ArgumentError.checkNotNull(columnSpan); if (rowIndex > _grid.rows.count) { ArgumentError.value(rowIndex, 'rowIndex', 'Index out of range'); } @@ -201,6 +659,53 @@ class PdfGridRowCollection { } /// Applies the style to all the rows in the grid. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Add headers to grid + /// grid.headers.add(2); + /// PdfGridRow header = grid.headers[0]; + /// header.cells[0].value = 'Employee ID'; + /// header.cells[1].value = 'Employee Name'; + /// header.cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Set the rows span + /// grid.rows.setSpan(0, 1, 2, 1); + /// //Set the row height + /// row2.height = 20; + /// //Set the row style + /// row1.style = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.dimGray, + /// textPen: PdfPens.lightGoldenrodYellow, + /// textBrush: PdfBrushes.darkOrange, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// //Create the PDF grid row style. Assign to whole rows + /// PdfGridRowStyle rowStyle = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.lightGoldenrodYellow, + /// textPen: PdfPens.indianRed, + /// textBrush: PdfBrushes.lightYellow, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// grid.rows.applyStyle(rowStyle); + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` void applyStyle(PdfGridStyleBase style) { if (style is PdfGridCellStyle) { for (int i = 0; i < _grid.rows.count; i++) { @@ -223,15 +728,47 @@ class PdfGridRowCollection { if (index < 0 || index >= _rows.length) { throw IndexError(index, _rows); } - return _rows[index]; + return _rows[index]!; } - int _indexOf(PdfGridRow row) { + int _indexOf(PdfGridRow? row) { return _rows.indexOf(row); } } /// Provides customization of the settings for the header. +/// ```dart +/// //Create a new PDF document +/// PdfDocument document = PdfDocument(); +/// //Create a PdfGrid +/// PdfGrid grid = PdfGrid(); +/// //Add columns to grid +/// grid.columns.add(count: 3); +/// //Add headers to grid +/// grid.headers.add(2); +/// PdfGridHeaderCollection headers = grid.headers; +/// headers[0].cells[0].value = 'Employee ID'; +/// headers[0].cells[1].value = 'Employee Name'; +/// headers[0].cells[2].value = 'Salary'; +/// //Add rows to grid +/// PdfGridRow row1 = grid.rows.add(); +/// row1.cells[0].value = 'E01'; +/// row1.cells[1].value = 'Clay'; +/// row1.cells[2].value = '\$10,000'; +/// PdfGridRow row2 = grid.rows.add(); +/// row2.cells[0].value = 'E02'; +/// row2.cells[1].value = 'Simon'; +/// row2.cells[2].value = '\$12,000'; +/// //Set the rows span +/// grid.rows.setSpan(0, 1, 2, 1); +/// //Draw the grid in PDF document page +/// grid.draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfGridHeaderCollection { /// Initializes a new instance of the [PdfGridHeaderCollection] class /// with the parent grid. @@ -241,25 +778,160 @@ class PdfGridHeaderCollection { } //Fields - PdfGrid _grid; - List _rows; + late PdfGrid _grid; + late List _rows; //Properties /// Gets the number of header in the [PdfGrid]. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Gets the headers collection from the grid + /// PdfGridHeaderCollection headers = grid.headers; + /// //Add headers to grid + /// headers.add(1); + /// Gets a header row from the headers collection + /// headers[0].cells[0].value = 'Employee ID'; + /// headers[0].cells[1].value = 'Employee Name'; + /// headers[0].cells[2].value = 'Salary'; + /// Gets the headers count + /// int headerCount = headers.count; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int get count => _rows.length; /// Gets a [PdfGridRow] object that represents the header row in a /// [PdfGridHeaderCollection] control. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Gets the headers collection from the grid + /// PdfGridHeaderCollection headers = grid.headers; + /// //Add headers to grid + /// headers.add(1); + /// Gets a header row from the headers collection + /// headers[0].cells[0].value = 'Employee ID'; + /// headers[0].cells[1].value = 'Employee Name'; + /// headers[0].cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfGridRow operator [](int index) => _returnValue(index); //Public methods /// [PdfGrid] enables you to quickly and easily add rows /// to the header at run time. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Gets the headers collection from the grid + /// PdfGridHeaderCollection headers = grid.headers; + /// //Add headers to grid + /// headers.add(1); + /// headers[0].cells[0].value = 'Employee ID'; + /// headers[0].cells[1].value = 'Employee Name'; + /// headers[0].cells[2].value = 'Salary'; + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` List add(int count) { return _addRows(count); } /// Enables you to set the appearance of the header row in a [PdfGrid]. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Gets the headers collection from the grid + /// PdfGridHeaderCollection headers = grid.headers; + /// //Add headers to grid + /// headers.add(1); + /// headers[0].cells[0].value = 'Employee ID'; + /// headers[0].cells[1].value = 'Employee Name'; + /// headers[0].cells[2].value = 'Salary'; + /// //Create the header row style. Assign to whole headers + /// PdfGridRowStyle headerStyle = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.lightGoldenrodYellow, + /// textPen: PdfPens.indianRed, + /// textBrush: PdfBrushes.lightYellow, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// headers.applyStyle(headerStyle); + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` void applyStyle(PdfGridStyleBase style) { if (style is PdfGridCellStyle) { for (int i = 0; i < _rows.length; i++) { @@ -276,6 +948,46 @@ class PdfGridHeaderCollection { } /// Removes all the header information in the [PdfGrid]. + /// ```dart + /// //Create a new PDF document + /// PdfDocument document = PdfDocument(); + /// //Create a PdfGrid + /// PdfGrid grid = PdfGrid(); + /// //Add columns to grid + /// grid.columns.add(count: 3); + /// //Gets the headers collection from the grid + /// PdfGridHeaderCollection headers = grid.headers; + /// //Add headers to grid + /// headers.add(1); + /// headers[0].cells[0].value = 'Employee ID'; + /// headers[0].cells[1].value = 'Employee Name'; + /// headers[0].cells[2].value = 'Salary'; + /// //Create the header row style. Assign to whole headers + /// PdfGridRowStyle headerStyle = PdfGridRowStyle( + /// backgroundBrush: PdfBrushes.lightGoldenrodYellow, + /// textPen: PdfPens.indianRed, + /// textBrush: PdfBrushes.lightYellow, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 12)); + /// headers.applyStyle(headerStyle); + /// //Clear the headers + /// headers.clear(); + /// //Add rows to grid + /// PdfGridRow row1 = grid.rows.add(); + /// row1.cells[0].value = 'E01'; + /// row1.cells[1].value = 'Clay'; + /// row1.cells[2].value = '\$10,000'; + /// PdfGridRow row2 = grid.rows.add(); + /// row2.cells[0].value = 'E02'; + /// row2.cells[1].value = 'Simon'; + /// row2.cells[2].value = '\$12,000'; + /// //Draw the grid in PDF document page + /// grid.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` void clear() { _rows.clear(); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/styles/pdf_borders.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/styles/pdf_borders.dart index 222a2c719..c0e378612 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/styles/pdf_borders.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/styles/pdf_borders.dart @@ -4,7 +4,7 @@ part of pdf; class PdfBorders { //Constructor /// Initialize a new instance of the [PdfBorders] class. - PdfBorders({PdfPen left, PdfPen right, PdfPen top, PdfPen bottom}) { + PdfBorders({PdfPen? left, PdfPen? right, PdfPen? top, PdfPen? bottom}) { if (left == null) { final PdfPen defaultBorderPenLeft = PdfPen(PdfColor(0, 0, 0)); defaultBorderPenLeft.dashStyle = PdfDashStyle.solid; @@ -38,23 +38,23 @@ class PdfBorders { /// Gets the default border. static PdfBorders get defaultBorder { _defaultBorder ??= PdfBorders(); - return _defaultBorder; + return _defaultBorder!; } //Fields - static PdfBorders _defaultBorder; + static PdfBorders? _defaultBorder; /// Gets or sets the pen for the left line of border. - PdfPen left; + late PdfPen left; /// Gets or sets the pen for the right line of border. - PdfPen right; + late PdfPen right; /// Gets or sets the pen for the bottom line of border. - PdfPen bottom; + late PdfPen bottom; /// Gets or sets the pen for the top line of border. - PdfPen top; + late PdfPen top; //Properties /// Sets all. @@ -69,15 +69,15 @@ class PdfBorders { class PdfPaddings { //Constructors /// Initializes a new instance of the [PdfPaddings] class. - PdfPaddings({double left, double right, double top, double bottom}) { + PdfPaddings({double? left, double? right, double? top, double? bottom}) { _initialize(left, right, top, bottom); } //Fields - double _left; - double _right; - double _bottom; - double _top; + late double _left; + late double _right; + late double _bottom; + late double _top; //Properties @override @@ -95,7 +95,6 @@ class PdfPaddings { /// Sets space value to all sides of a cell Left,Right,Top,Bottom. set all(double value) { - ArgumentError.checkNotNull(value); if (value < 0) { ArgumentError.value( value, 'all', 'value should greater than or equal to zero'); @@ -108,7 +107,6 @@ class PdfPaddings { /// Sets the left space of padding. set left(double value) { - ArgumentError.checkNotNull(value); if (value < 0) { ArgumentError.value( value, 'left', 'value should greater than or equal to zero'); @@ -121,7 +119,6 @@ class PdfPaddings { /// Sets the right space of padding. set right(double value) { - ArgumentError.checkNotNull(value); if (value < 0) { ArgumentError.value( value, 'right', 'value should greater than or equal to zero'); @@ -134,7 +131,6 @@ class PdfPaddings { /// Sets the top space of padding. set top(double value) { - ArgumentError.checkNotNull(value); if (value < 0) { ArgumentError.value( value, 'top', 'value should greater than or equal to zero'); @@ -147,7 +143,6 @@ class PdfPaddings { /// Sets the bottom space of padding. set bottom(double value) { - ArgumentError.checkNotNull(value); if (value < 0) { ArgumentError.value( value, 'bottom', 'value should greater than or equal to zero'); @@ -156,7 +151,7 @@ class PdfPaddings { } //Implementation - void _initialize(double left, double right, double top, double bottom) { + void _initialize(double? left, double? right, double? top, double? bottom) { this.left = left ?? 0.5; this.right = right ?? 0.5; this.top = top ?? 0.5; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/styles/style.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/styles/style.dart index 36ecf11d8..b811990fd 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/styles/style.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/styles/style.dart @@ -3,33 +3,33 @@ part of pdf; /// Base class for the grid style abstract class PdfGridStyleBase { /// Gets or sets the background brush. - PdfBrush backgroundBrush; + PdfBrush? backgroundBrush; /// Gets or sets the text brush. - PdfBrush textBrush; + PdfBrush? textBrush; /// Gets or sets the text pen. - PdfPen textPen; + PdfPen? textPen; /// Gets or sets the font. - PdfFont font; - PdfPaddings _gridCellPadding; + PdfFont? font; + PdfPaddings? _gridCellPadding; } /// Provides customization of the appearance for the [PdfGridRow]. class PdfGridRowStyle extends PdfGridStyleBase { /// Initializes a new instance of the [PdfGridRowStyle] class. PdfGridRowStyle( - {PdfBrush backgroundBrush, - PdfBrush textBrush, - PdfPen textPen, - PdfFont font}) { + {PdfBrush? backgroundBrush, + PdfBrush? textBrush, + PdfPen? textPen, + PdfFont? font}) { _initializeRowStyle(backgroundBrush, textBrush, textPen, font); } //Implementation - void _initializeRowStyle(PdfBrush backgroundBrush, PdfBrush textBrush, - PdfPen textPen, PdfFont font) { + void _initializeRowStyle(PdfBrush? backgroundBrush, PdfBrush? textBrush, + PdfPen? textPen, PdfFont? font) { super.backgroundBrush = backgroundBrush; super.textBrush = textBrush; super.textPen = textPen; @@ -41,14 +41,14 @@ class PdfGridRowStyle extends PdfGridStyleBase { class PdfGridCellStyle extends PdfGridRowStyle { /// Initializes a new instance of the [PdfGridCellStyle] class. PdfGridCellStyle( - {PdfBorders borders, - PdfStringFormat format, - PdfImage backgroundImage, - PdfPaddings cellPadding, - PdfBrush backgroundBrush, - PdfBrush textBrush, - PdfPen textPen, - PdfFont font}) + {PdfBorders? borders, + PdfStringFormat? format, + PdfImage? backgroundImage, + PdfPaddings? cellPadding, + PdfBrush? backgroundBrush, + PdfBrush? textBrush, + PdfPen? textPen, + PdfFont? font}) : super( backgroundBrush: backgroundBrush, textBrush: textBrush, @@ -59,34 +59,36 @@ class PdfGridCellStyle extends PdfGridRowStyle { //Fields /// Gets or sets the border of the [PdfGridCell]. - PdfBorders borders; + late PdfBorders borders; /// Gets the string format of the [PdfGridCell]. - PdfStringFormat stringFormat; - PdfPaddings _cellPadding; + PdfStringFormat? stringFormat; + PdfPaddings? _cellPadding; /// Gets or sets the background image in the [PdfGridCell]. - PdfImage backgroundImage; + PdfImage? backgroundImage; //Properties /// Gets the cell padding. - PdfPaddings get cellPadding { + PdfPaddings? get cellPadding { _cellPadding ??= _gridCellPadding; return _cellPadding; } /// Sets the cell padding. - set cellPadding(PdfPaddings value) { + set cellPadding(PdfPaddings? value) { _cellPadding = value; } //Implementation - void _initializeCellStyle(PdfBorders borders, PdfStringFormat format, - PdfImage backgroundImage, PdfPaddings cellPadding) { + void _initializeCellStyle(PdfBorders? borders, PdfStringFormat? format, + PdfImage? backgroundImage, PdfPaddings? cellPadding) { this.borders = borders ?? PdfBorders(); stringFormat = format; this.backgroundImage = backgroundImage; - this.cellPadding = cellPadding; + if (cellPadding != null) { + this.cellPadding = cellPadding; + } } } @@ -95,36 +97,36 @@ class PdfGridStyle extends PdfGridStyleBase { //Constructor /// Initializes a new instance of the [PdfGridStyle] class. PdfGridStyle( - {double cellSpacing, - PdfPaddings cellPadding, - PdfBorderOverlapStyle borderOverlapStyle, - PdfBrush backgroundBrush, - PdfBrush textBrush, - PdfPen textPen, - PdfFont font}) { + {double? cellSpacing, + PdfPaddings? cellPadding, + PdfBorderOverlapStyle? borderOverlapStyle, + PdfBrush? backgroundBrush, + PdfBrush? textBrush, + PdfPen? textPen, + PdfFont? font}) { _initializeStyle(cellSpacing, cellPadding, borderOverlapStyle, backgroundBrush, textBrush, textPen, font); } //Fields /// Gets or sets the cell spacing of the [PdfGrid]. - double cellSpacing; - PdfPaddings _cellPadding; + late double cellSpacing; + PdfPaddings? _cellPadding; /// Gets or sets the border overlap style of the [PdfGrid]. - PdfBorderOverlapStyle borderOverlapStyle; + late PdfBorderOverlapStyle borderOverlapStyle; /// Gets or sets a value indicating whether to allow horizontal overflow. - bool allowHorizontalOverflow; + late bool allowHorizontalOverflow; /// Gets or sets the type of the horizontal overflow of the [PdfGrid]. - PdfHorizontalOverflowType horizontalOverflowType; + late PdfHorizontalOverflowType horizontalOverflowType; //Properties /// Gets the cell padding. PdfPaddings get cellPadding { _cellPadding ??= PdfPaddings(); _gridCellPadding = _cellPadding; - return _cellPadding; + return _cellPadding!; } /// Sets the cell padding. @@ -135,13 +137,13 @@ class PdfGridStyle extends PdfGridStyleBase { //Implementation void _initializeStyle( - double cellSpacing, - PdfPaddings cellPadding, - PdfBorderOverlapStyle borderOverlapStyle, - PdfBrush backgroundBrush, - PdfBrush textBrush, - PdfPen textPen, - PdfFont font) { + double? cellSpacing, + PdfPaddings? cellPadding, + PdfBorderOverlapStyle? borderOverlapStyle, + PdfBrush? backgroundBrush, + PdfBrush? textBrush, + PdfPen? textPen, + PdfFont? font) { super.backgroundBrush = backgroundBrush; super.textBrush = textBrush; super.textPen = textPen; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/enums.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/enums.dart index 9adb39d57..403a8b46a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/enums.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/enums.dart @@ -1,6 +1,24 @@ part of pdf; /// Represents marker alignment. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new ordered list. +/// PdfOrderedList( +/// items: PdfListItemCollection(['Essential tools', 'Essential grid']), +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic), +/// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric) +/// ..alignment = PdfListMarkerAlignment.right) +/// ..draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// final List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` enum PdfListMarkerAlignment { /// Left alignment for marker. left, @@ -10,6 +28,23 @@ enum PdfListMarkerAlignment { } /// Specifies the marker style. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new unordered list. +/// PdfUnorderedList( +/// items: PdfListItemCollection(['Essential tools', 'Essential grid']), +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic), +/// marker: PdfUnorderedMarker(style: PdfUnorderedMarkerStyle.disk)) +/// ..draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// final List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` enum PdfUnorderedMarkerStyle { /// Marker have no style. none, diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_marker.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_marker.dart index dbf3ee457..51856404a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_marker.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_marker.dart @@ -1,22 +1,132 @@ part of pdf; /// Represents base class for markers. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new ordered list. +/// PdfOrderedList( +/// items: PdfListItemCollection(['Essential tools', 'Essential grid']), +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic)) +/// ..draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// final List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` abstract class PdfMarker { + //Fields /// Marker font. - PdfFont font; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic)) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfFont? font; /// Marker brush. - PdfBrush brush; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric) + /// ..brush = PdfBrushes.red) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfBrush? brush; /// Marker pen. - PdfPen pen; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric) + /// //Set the marker pen. + /// ..pen = PdfPens.red) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfPen? pen; /// The string format of the marker. - PdfStringFormat stringFormat; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric) + /// //Set the marker format. + /// ..stringFormat = PdfStringFormat(alignment: PdfTextAlignment.left)) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfStringFormat? stringFormat; /// Marker alignment. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric) + /// //Set the marker alignment. + /// ..alignment = PdfListMarkerAlignment.right) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfListMarkerAlignment alignment = PdfListMarkerAlignment.left; + //Properties /// Indicates is alignment right. bool get _rightToLeft => alignment == PdfListMarkerAlignment.right; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_ordered_marker.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_ordered_marker.dart index a3f18c55d..5162263f4 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_ordered_marker.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_ordered_marker.dart @@ -1,34 +1,111 @@ part of pdf; /// Represents marker for ordered list. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new ordered list. +/// PdfOrderedList( +/// items: PdfListItemCollection(['Essential tools', 'Essential grid']), +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic), +/// marker: //Create a new ordered marker. +/// PdfOrderedMarker(style: PdfNumberStyle.numeric)) +/// ..draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// final List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfOrderedMarker extends PdfMarker { + //Constructor /// Initializes a new instance of the [PdfOrderedMarker] class. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: //Create a new ordered marker. + /// PdfOrderedMarker(style: PdfNumberStyle.numeric)) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfOrderedMarker( - {this.style, PdfFont font, String suffix = '', String delimiter = ''}) { + {this.style = PdfNumberStyle.none, + PdfFont? font, + String suffix = '', + String delimiter = ''}) { _delimiter = delimiter; _suffix = suffix; this.font = font; } + //Fields /// Holds numbering style. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: //Create a new ordered marker. + /// PdfOrderedMarker(style: PdfNumberStyle.numeric)) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfNumberStyle style = PdfNumberStyle.none; /// Start number for ordered list. int _startNumber = 1; /// Delimiter for numbers. - String _delimiter; + String? _delimiter; /// Finalizer for numbers. - String _suffix; + String? _suffix; /// Current index of item. - int _currentIndex; + late int _currentIndex; - /// Gets start number for ordered list. Default value is 1. + //Properties + /// Gets or sets start number for ordered list. Default value is 1. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric) + /// //Set the start number. + /// ..startNumber = 2) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int get startNumber => _startNumber; - - /// Sets start number for ordered list. Default value is 1. set startNumber(int value) { if (value <= 0) { throw ArgumentError('Start number should be greater than 0'); @@ -36,28 +113,67 @@ class PdfOrderedMarker extends PdfMarker { _startNumber = value; } - /// Gets the delimiter. + /// Gets or sets the delimiter. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic)) + /// ..items[0].subList = PdfOrderedList( + /// items: PdfListItemCollection(['PDF', 'DocIO']), + /// marker: PdfOrderedMarker( + /// style: PdfNumberStyle.numeric, delimiter: ',', suffix: ')')) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` String get delimiter { if (_delimiter == '' || _delimiter == null) { return '.'; } - return _delimiter; + return _delimiter!; } - /// Sets the delimiter. set delimiter(String value) => _delimiter = value; - /// Gets the suffix of the marker. + /// Gets or sets the suffix of the marker. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic)) + /// ..items[0].subList = PdfOrderedList( + /// items: PdfListItemCollection(['PDF', 'DocIO']), + /// marker: PdfOrderedMarker( + /// style: PdfNumberStyle.numeric, delimiter: ',', suffix: ')')) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` String get suffix { if (_suffix == null || _suffix == '') { return '.'; } - return _suffix; + return _suffix!; } - /// Sets the suffix of the marker. set suffix(String value) => _suffix = value; + //Implementation /// Gets the marker number. String _getNumber() { return _PdfNumberConvertor._convert(_startNumber + _currentIndex, style); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_unordered_marker.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_unordered_marker.dart index fa45ea6dd..7fbe85a96 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_unordered_marker.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_unordered_marker.dart @@ -1,12 +1,54 @@ part of pdf; /// Represents bullet for the list. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new unordered list. +/// PdfUnorderedList uList = PdfUnorderedList( +/// items: PdfListItemCollection(['Essential tools', 'Essential grid']), +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic), +/// marker: //Create an unordered marker. +/// PdfUnorderedMarker(style: PdfUnorderedMarkerStyle.disk)) +/// ..draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// final List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfUnorderedMarker extends PdfMarker { + //Constructor /// Initializes a new instance of the [PdfUnorderedMarker] class /// with Pdf unordered marker style. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new unordered list. + /// PdfUnorderedList uList = PdfUnorderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: //Create an unordered marker. + /// PdfUnorderedMarker(style: PdfUnorderedMarkerStyle.disk)) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfUnorderedMarker( - {this.style, PdfFont font, String text, PdfTemplate template}) { - this.font = font; + {this.style = PdfUnorderedMarkerStyle.none, + PdfFont? font, + String? text, + PdfTemplate? template}) { + if (font != null) { + this.font = font; + } if (text != null) { this.text = text; style = PdfUnorderedMarkerStyle.customString; @@ -16,63 +58,116 @@ class PdfUnorderedMarker extends PdfMarker { } } - /// Holds the marker text. - String _text; - + //Fields /// Gets and sets the marker style. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new unordered list. + /// PdfUnorderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: PdfUnorderedMarker(style: PdfUnorderedMarkerStyle.disk)) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfUnorderedMarkerStyle style = PdfUnorderedMarkerStyle.none; + /// Holds the marker text. + String? _text; + // /// Holds the marker image. // PdfImage _image; /// Marker temlapte. - PdfTemplate _template; + PdfTemplate? _template; /// Marker size. - _Size _size; + _Size? _size; /// Font used when draws styled marker - PdfFont _unicodeFont; + late PdfFont _unicodeFont; - /// Gets template of the marker. - PdfTemplate get template => _template; - - /// Sets template of the marker. - set template(PdfTemplate value) { - ArgumentError.checkNotNull(value, 'template'); - _template = value; - style = PdfUnorderedMarkerStyle.customTemplate; + //Properties + /// Gets or sets template of the marker. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new unordered list. + /// PdfUnorderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: PdfUnorderedMarker(template: (PdfTemplate(100, 100) + /// ..graphics.drawRectangle( + /// brush: PdfBrushes.red, + /// bounds: const Rect.fromLTWH(0, 0, 100, 100))))) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfTemplate? get template => _template; + set template(PdfTemplate? value) { + if (value != null) { + _template = value; + style = PdfUnorderedMarkerStyle.customTemplate; + } } /// Gets marker text. - String get text => _text; - - ///Sets marker text. - set text(String value) { - if (value == null) { - throw ArgumentError('text'); + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new unordered list. + /// PdfUnorderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: PdfUnorderedMarker(text: 'Text')) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + String? get text => _text; + set text(String? value) { + if (value != null) { + _text = value; + style = PdfUnorderedMarkerStyle.customString; } - _text = value; - style = PdfUnorderedMarkerStyle.customString; } + //Implementation /// Draws the specified graphics. - void _draw(PdfGraphics graphics, Offset point, PdfBrush brush, PdfPen pen, - [PdfList curList]) { - final PdfTemplate templete = PdfTemplate(_size.width, _size.height); + void _draw(PdfGraphics? graphics, Offset point, PdfBrush? brush, PdfPen? pen, + [PdfList? curList]) { + final PdfTemplate templete = PdfTemplate(_size!.width, _size!.height); Offset offset = Offset(point.dx, point.dy); switch (style) { case PdfUnorderedMarkerStyle.customTemplate: - templete.graphics - .drawPdfTemplate(_template, const Offset(0, 0), _size.size); + templete.graphics! + .drawPdfTemplate(_template!, const Offset(0, 0), _size!.size); offset = Offset( point.dx, point.dy + - ((curList.font.height > font.height - ? curList.font.height - : font.height) / + ((curList!.font!.height > font!.height + ? curList.font!.height + : font!.height) / 2) - - (_size.height / 2)); + (_size!.height / 2)); break; case PdfUnorderedMarkerStyle.customImage: // templete.graphics.drawImage( @@ -81,16 +176,16 @@ class PdfUnorderedMarker extends PdfMarker { default: final _Point location = _Point.empty; if (pen != null) { - location.x += pen.width; - location.y += pen.width; + location.x = location.x + pen.width; + location.y = location.y + pen.width; } - templete.graphics.drawString(_getStyledText(), _unicodeFont, + templete.graphics!.drawString(_getStyledText(), _unicodeFont, pen: pen, brush: brush, bounds: Rect.fromLTWH(location.x, location.y, 0, 0)); break; } - graphics.drawPdfTemplate(templete, offset); + graphics!.drawPdfTemplate(templete, offset); } /// Gets the styled text. diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list.dart index 8148f6649..a28ffd217 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list.dart @@ -1,59 +1,279 @@ part of pdf; /// Represents base class for lists. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new ordered list. +/// PdfOrderedList( +/// text: 'PDF\nXlsIO\nDocIO\nPPT', +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic), +/// format: PdfStringFormat(lineSpacing: 20), +/// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), +/// style: PdfNumberStyle.numeric, +/// indent: 15, +/// textIndent: 10) +/// ..draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` abstract class PdfList extends PdfLayoutElement { // Creates an item collection. static PdfListItemCollection _createItems(String text) { - ArgumentError.checkNotNull(text, 'text'); return PdfListItemCollection(text.split('\n')); } - /// Holds collection of items. - PdfListItemCollection _items; - + //Fields /// Tabulation for items. - double indent; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), + /// style: PdfNumberStyle.numeric, + /// indent: 15, + /// textIndent: 10) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + late double indent; /// Indent between marker and text. - double textIndent; - - /// List's font. - PdfFont _font; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), + /// style: PdfNumberStyle.numeric, + /// indent: 15, + /// textIndent: 10) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + late double textIndent; /// The pen for the list. - PdfPen pen; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), + /// style: PdfNumberStyle.numeric, + /// indent: 15, + /// textIndent: 10) + /// ..pen = PdfPens.red + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfPen? pen; /// The brush for the list. - PdfBrush brush; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), + /// style: PdfNumberStyle.numeric, + /// indent: 15, + /// textIndent: 10) + /// ..brush = PdfBrushes.red + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfBrush? brush; /// The string format for the list. - PdfStringFormat stringFormat; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), + /// style: PdfNumberStyle.numeric, + /// indent: 15, + /// textIndent: 10) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfStringFormat? stringFormat; + + //Holds collection of items. + PdfListItemCollection? _items; + //List's font. + PdfFont? _font; + + //Properties /// Gets items of the list. + /// + /// ```dart + /// //Create a new instance of PdfDocument class. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList( + /// indent: 10, + /// textIndent: 10, + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 10, + /// style: PdfFontStyle.italic)) + /// ..items.add(PdfListItem(text: 'PDF')) + /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfListItemCollection get items { _items ??= PdfListItemCollection(); - return _items; + return _items!; } - /// Gets the list font. - PdfFont get font => _font; - - /// Sets the list font. - set font(PdfFont value) { - ArgumentError.checkNotNull(value, 'font'); - _font = value; + /// Gets or sets the list font. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), + /// style: PdfNumberStyle.numeric, + /// indent: 15, + /// textIndent: 10) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfFont? get font => _font; + set font(PdfFont? value) { + if (value != null) { + _font = value; + } } //Events /// Event that rises when item begin layout. - BeginItemLayoutCallback beginItemLayout; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20)) + /// //Begin item layout event. + /// ..beginItemLayout = (Object sender, BeginItemLayoutArgs args) { + /// args.item.text += '_Beginsave'; + /// } + /// //End item layout event. + /// ..endItemLayout = (Object sender, EndItemLayoutArgs args) { + /// args.page.graphics.drawRectangle( + /// brush: PdfBrushes.red, + /// bounds: const Rect.fromLTWH(400, 400, 100, 100)); + /// } + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + BeginItemLayoutCallback? beginItemLayout; /// Event that rises when item end layout. - EndItemLayoutCallback endItemLayout; + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20)) + /// //Begin item layout event. + /// ..beginItemLayout = (Object sender, BeginItemLayoutArgs args) { + /// args.item.text += '_Beginsave'; + /// } + /// //End item layout event. + /// ..endItemLayout = (Object sender, EndItemLayoutArgs args) { + /// args.page.graphics.drawRectangle( + /// brush: PdfBrushes.red, + /// bounds: const Rect.fromLTWH(400, 400, 100, 100)); + /// } + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + EndItemLayoutCallback? endItemLayout; /// Layouts on the specified Page. @override - PdfLayoutResult _layout(_PdfLayoutParams param) { + PdfLayoutResult? _layout(_PdfLayoutParams param) { final _PdfListLayouter layouter = _PdfListLayouter(this); return layouter._layout(param); } @@ -64,7 +284,7 @@ abstract class PdfList extends PdfLayoutElement { final _PdfLayoutParams param = _PdfLayoutParams(); param.bounds = bounds; param.format = PdfLayoutFormat(); - param.format.layoutType = PdfLayoutType.onePage; + param.format!.layoutType = PdfLayoutType.onePage; final _PdfListLayouter layouter = _PdfListLayouter(this); layouter.graphics = graphics; layouter._layoutInternal(param); @@ -73,58 +293,279 @@ abstract class PdfList extends PdfLayoutElement { /// Rise the BeginItemLayout event. void _onBeginItemLayout(BeginItemLayoutArgs args) { if (beginItemLayout != null) { - beginItemLayout(this, args); + beginItemLayout!(this, args); } } /// Rise the EndItemLayout event. void _onEndItemLayout(EndItemLayoutArgs args) { if (endItemLayout != null) { - endItemLayout(this, args); + endItemLayout!(this, args); } } } /// Represents begin layout event arguments. +/// +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new list. +/// PdfOrderedList( +/// text: 'PDF\nXlsIO\nDocIO\nPPT', +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic), +/// format: PdfStringFormat(lineSpacing: 20)) +/// //Begin item layout event. +/// ..beginItemLayout = (Object sender, BeginItemLayoutArgs args) { +/// args.item.text += '_Beginsave'; +/// } +/// //End item layout event. +/// ..endItemLayout = (Object sender, EndItemLayoutArgs args) { +/// args.page.graphics.drawRectangle( +/// brush: PdfBrushes.red, +/// bounds: const Rect.fromLTWH(400, 400, 100, 100)); +/// } +/// ..draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class BeginItemLayoutArgs { /// Initializes a new instance of the [BeginItemLayoutArgs] class. BeginItemLayoutArgs._internal(this._item, this._page); - /// Item that layout. + //Fields + //Item that layout. final PdfListItem _item; - /// The page in which item start layout. + //The page in which item start layout. final PdfPage _page; + //Properties /// Gets the item. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20)) + /// //Begin item layout event. + /// ..beginItemLayout = (Object sender, BeginItemLayoutArgs args) { + /// args.item.text += '_Beginsave'; + /// } + /// //End item layout event. + /// ..endItemLayout = (Object sender, EndItemLayoutArgs args) { + /// args.page.graphics.drawRectangle( + /// brush: PdfBrushes.red, + /// bounds: const Rect.fromLTWH(400, 400, 100, 100)); + /// } + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfListItem get item => _item; /// Gets the page. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20)) + /// //Begin item layout event. + /// ..beginItemLayout = (Object sender, BeginItemLayoutArgs args) { + /// args.item.text += '_Beginsave'; + /// PdfPage page = args.page; + /// } + /// //End item layout event. + /// ..endItemLayout = (Object sender, EndItemLayoutArgs args) { + /// args.page.graphics.drawRectangle( + /// brush: PdfBrushes.red, + /// bounds: const Rect.fromLTWH(400, 400, 100, 100)); + /// } + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfPage get page => _page; } /// Represents end layout event arguments. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new list. +/// PdfOrderedList oList = PdfOrderedList( +/// text: 'PDF\nXlsIO\nDocIO\nPPT', +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic), +/// format: PdfStringFormat(lineSpacing: 20)); +/// oList.beginItemLayout = (Object sender, BeginItemLayoutArgs args) { +/// args.item.text += '_Beginsave'; +/// }; +/// //End item layout event. +/// oList.endItemLayout = (Object sender, EndItemLayoutArgs args) { +/// args.page.graphics.drawRectangle( +/// brush: PdfBrushes.red, bounds: const Rect.fromLTWH(400, 400, 100, 100)); +/// }; +/// oList.draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class EndItemLayoutArgs { /// Initializes a new instance of the [EndItemLayoutArgs] class. EndItemLayoutArgs._internal(this._item, this._page); - /// Item that layouted. + //Fields + //Item that layouted. final PdfListItem _item; - /// The page in which item ended layout. + //The page in which item ended layout. final PdfPage _page; + //Properties /// Gets the item. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20)) + /// //Begin item layout event. + /// ..beginItemLayout = (Object sender, BeginItemLayoutArgs args) { + /// args.item.text += '_Beginsave'; + /// } + /// //End item layout event. + /// ..endItemLayout = (Object sender, EndItemLayoutArgs args) { + /// args.page.graphics.drawRectangle( + /// brush: PdfBrushes.red, + /// bounds: const Rect.fromLTWH(400, 400, 100, 100)); + /// PdfListItem item = args.item; + /// } + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfListItem get item => _item; /// Gets the page. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20)) + /// //Begin item layout event. + /// ..beginItemLayout = (Object sender, BeginItemLayoutArgs args) { + /// args.item.text += '_Beginsave'; + /// } + /// //End item layout event. + /// ..endItemLayout = (Object sender, EndItemLayoutArgs args) { + /// args.page.graphics.drawRectangle( + /// brush: PdfBrushes.red, + /// bounds: const Rect.fromLTWH(400, 400, 100, 100)); + /// } + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfPage get page => _page; } /// typedef for handling BeginItemLayoutEvent. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new list. +/// PdfOrderedList( +/// text: 'PDF\nXlsIO\nDocIO\nPPT', +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic), +/// format: PdfStringFormat(lineSpacing: 20)) +/// //Begin item layout event. +/// ..beginItemLayout = (Object sender, BeginItemLayoutArgs args) { +/// args.item.text += '_Beginsave'; +/// } +/// //End item layout event. +/// ..endItemLayout = (Object sender, EndItemLayoutArgs args) { +/// args.page.graphics.drawRectangle( +/// brush: PdfBrushes.red, +/// bounds: const Rect.fromLTWH(400, 400, 100, 100)); +/// } +/// ..draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` typedef BeginItemLayoutCallback = void Function( Object sender, BeginItemLayoutArgs args); /// typedef for handling EndItemLayoutEvent. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new list. +/// PdfOrderedList( +/// text: 'PDF\nXlsIO\nDocIO\nPPT', +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic), +/// format: PdfStringFormat(lineSpacing: 20)) +/// //Begin item layout event. +/// ..beginItemLayout = (Object sender, BeginItemLayoutArgs args) { +/// args.item.text += '_Beginsave'; +/// } +/// //End item layout event. +/// ..endItemLayout = (Object sender, EndItemLayoutArgs args) { +/// args.page.graphics.drawRectangle( +/// brush: PdfBrushes.red, +/// bounds: const Rect.fromLTWH(400, 400, 100, 100)); +/// } +/// ..draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` typedef EndItemLayoutCallback = void Function( Object sender, EndItemLayoutArgs args); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_item.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_item.dart index a22067bb6..eacbf1844 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_item.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_item.dart @@ -1,47 +1,198 @@ part of pdf; /// Represents list item of the list. +/// +/// ```dart +/// //Create a new instance of PdfDocument class. +/// PdfDocument document = PdfDocument(); +/// //Create a new list. +/// PdfOrderedList( +/// font: PdfStandardFont(PdfFontFamily.timesRoman, 10, +/// style: PdfFontStyle.italic)) +/// ..items.add( +/// //Create list item. +/// PdfListItem(text: 'PDF')) +/// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfListItem { + //Constructor /// Initializes a new instance of the [PdfListItem] class. + /// + /// ```dart + /// //Create a new instance of PdfDocument class. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList( + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 10, + /// style: PdfFontStyle.italic)) + /// ..items.add( + /// //Create list item. + /// PdfListItem(text: 'PDF')) + /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfListItem( {String text = '', this.font, - PdfStringFormat format, + PdfStringFormat? format, this.pen, - this.brush}) { - ArgumentError.checkNotNull(text, 'text'); - _text = text; + this.brush, + this.subList}) { + this.text = text; stringFormat = format; } + //Fields /// Holds item font. - PdfFont font; - - /// Holds item text. - String _text; + /// + /// ```dart + /// //Create a new instance of PdfDocument class. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList() + /// ..items.add(PdfListItem( + /// text: 'PDF', + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 10), + /// format: PdfStringFormat(alignment: PdfTextAlignment.left), + /// pen: PdfPens.green, + /// brush: PdfBrushes.red)) + /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfFont? font; /// Holds text format. - PdfStringFormat stringFormat; + /// + /// ```dart + /// //Create a new instance of PdfDocument class. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList() + /// ..items.add(PdfListItem( + /// text: 'PDF', + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 10), + /// format: PdfStringFormat(alignment: PdfTextAlignment.left), + /// pen: PdfPens.green, + /// brush: PdfBrushes.red)) + /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfStringFormat? stringFormat; /// Holds pen. - PdfPen pen; + /// + /// ```dart + /// //Create a new instance of PdfDocument class. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList() + /// ..items.add(PdfListItem( + /// text: 'PDF', + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 10), + /// format: PdfStringFormat(alignment: PdfTextAlignment.left), + /// pen: PdfPens.green, + /// brush: PdfBrushes.red)) + /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfPen? pen; /// Holds brush. - PdfBrush brush; + /// + /// ```dart + /// //Create a new instance of PdfDocument class. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList() + /// ..items.add(PdfListItem( + /// text: 'PDF', + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 10), + /// format: PdfStringFormat(alignment: PdfTextAlignment.left), + /// pen: PdfPens.green, + /// brush: PdfBrushes.red)) + /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfBrush? brush; - /// Sub list. - PdfList subList; + /// Adds the Sub list. + /// + /// ```dart + /// //Create a new instance of PdfDocument class. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList() + /// ..items.add(PdfListItem(text: 'Essential tools') + /// //Add sub list. + /// ..subList = PdfOrderedList(items: PdfListItemCollection(['PDF']))) + /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfList? subList; /// Text indent for current item. + /// + /// ```dart + /// //Create a new instance of PdfDocument class. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList() + /// ..items.add(PdfListItem( + /// text: 'PDF', + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 10), + /// format: PdfStringFormat(alignment: PdfTextAlignment.left), + /// pen: PdfPens.green, + /// brush: PdfBrushes.red) + /// ..textIndent = 10) + /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` double textIndent = 0; - //PdfTag m_tag; - - /// Gets item text. - String get text => _text; - /// Sets item text. - set text(String value) { - ArgumentError.checkNotNull(text, 'text'); - _text = value; - } + //Properties + /// Gets or sets item text. + /// + /// ```dart + /// //Create a new instance of PdfDocument class. + /// PdfDocument document = PdfDocument(); + /// //Create a new list. + /// PdfOrderedList() + /// ..items.add(PdfListItem( + /// text: 'PDF', + /// font: PdfStandardFont(PdfFontFamily.timesRoman, 10), + /// format: PdfStringFormat(alignment: PdfTextAlignment.left), + /// pen: PdfPens.green, + /// brush: PdfBrushes.red)) + /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(10, 10, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + String text = ''; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_item_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_item_collection.dart index ae34c5319..46e55b36c 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_item_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_item_collection.dart @@ -1,9 +1,52 @@ part of pdf; /// Represents collection of list items. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new ordered list. +/// PdfOrderedList( +/// //Create a new list item collection. +/// items: PdfListItemCollection(['PDF', 'XlsIO', 'DocIO', 'PPT']), +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic), +/// format: PdfStringFormat(lineSpacing: 20), +/// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), +/// style: PdfNumberStyle.numeric, +/// indent: 15, +/// textIndent: 10) +/// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfListItemCollection extends PdfObjectCollection { + //Constructor /// Initializes a new instance of the [PdfListItemCollection] class. - PdfListItemCollection([List items]) { + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// //Create a new list item collection. + /// items: PdfListItemCollection(['PDF', 'XlsIO', 'DocIO', 'PPT']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), + /// style: PdfNumberStyle.numeric, + /// indent: 15, + /// textIndent: 10) + /// ..draw(page: document.pages.add(), bounds: Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfListItemCollection([List? items]) { if (items != null) { for (int i = 0; i < items.length; i++) { _add(items[i]); @@ -11,39 +54,88 @@ class PdfListItemCollection extends PdfObjectCollection { } } + //Properties /// Gets the [PdfListItem] from collection at the specified index. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic)) + /// ..items[0].subList = + /// PdfOrderedList(items: PdfListItemCollection(['PDF', 'DocIO'])) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfListItem operator [](int index) { if (index < 0 || index >= count) { throw RangeError( 'The index should be less than item\'s count or more or equal to 0'); } - return _list[index]; - } - - PdfListItem _add(String text) { - ArgumentError.checkNotNull(text, 'text'); - final PdfListItem item = PdfListItem(text: text); - _list.add(item); - return item; + return _list[index] as PdfListItem; } + //Public methods /// Adds the specified item. - int add(PdfListItem item, [double itemIndent]) { + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection() + /// //Add items to list item collection + /// ..add(PdfListItem(text: 'PDF')) + /// ..add(PdfListItem(text: 'DocIO')), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic)) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + int add(PdfListItem item, [double? itemIndent]) { if (itemIndent != null) { item.textIndent = itemIndent; } - ArgumentError.checkNotNull(item, 'item'); _list.add(item); return _list.length - 1; } /// Inserts item at the specified index. - void insert(int index, PdfListItem item, [double itemIndent]) { + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection() + /// ..add(PdfListItem(text: 'PDF')) + /// //insert items to list item collection + /// ..insert(0, PdfListItem(text: 'DocIO')), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic)) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + void insert(int index, PdfListItem item, [double? itemIndent]) { if (index < 0 || index >= count) { throw ArgumentError('''The index should be less than item\'s count or more or equal to 0, $index'''); } - ArgumentError.checkNotNull(item, 'item'); if (itemIndent != null) { item.textIndent = itemIndent; } @@ -51,8 +143,28 @@ class PdfListItemCollection extends PdfObjectCollection { } /// Removes the specified item from the list. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfList list = PdfOrderedList( + /// items: PdfListItemCollection() + /// ..add(PdfListItem(text: 'PDF')) + /// ..add(PdfListItem(text: 'DocIO')), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic)); + /// //Remove a specific item + /// list + /// ..items.remove(list.items[0]) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` void remove(PdfListItem item) { - ArgumentError.checkNotNull(item, 'item'); if (!_list.contains(item)) { throw ArgumentError('The list doesn\'t contain this item, $item'); } @@ -60,6 +172,26 @@ class PdfListItemCollection extends PdfObjectCollection { } /// Removes the item at the specified index from the list. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection() + /// ..add(PdfListItem(text: 'PDF')) + /// ..insert(0, PdfListItem(text: 'DocIO')) + /// //Remove item at specific index. + /// ..removeAt(1), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic)) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` void removeAt(int index) { if (index < 0 || index >= count) { throw ArgumentError('''The index should be less than item's count @@ -69,13 +201,63 @@ class PdfListItemCollection extends PdfObjectCollection { } /// Determines the index of a specific item in the list. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a list item. + /// PdfListItem item = PdfListItem(text: 'PDF'); + /// //Create a new ordered list. + /// PdfOrderedList oList = PdfOrderedList( + /// items: PdfListItemCollection() + /// ..add(item) + /// ..add(PdfListItem(text: 'DocIO')), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic)); + /// //Get the index of specific item. + /// int index = oList.items.indexOf(item); + /// //Draw the list to the page. + /// oList.draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` int indexOf(PdfListItem item) { - ArgumentError.checkNotNull(item, 'item'); return _list.indexOf(item); } /// Clears collection + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection() + /// ..add(PdfListItem(text: 'PDF')) + /// ..add(PdfListItem(text: 'DocIO')) + /// //Clears the all items in the collection. + /// ..clear() + /// ..add(PdfListItem(text: 'DocIO')), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic)) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` void clear() { _list.clear(); } + + //Implementation + PdfListItem _add(String text) { + final PdfListItem item = PdfListItem(text: text); + _list.add(item); + return item; + } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_layouter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_layouter.dart index 50a56fafa..8fb30abd9 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_layouter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_layouter.dart @@ -6,13 +6,13 @@ class _PdfListLayouter extends _ElementLayouter { _PdfListLayouter(PdfList element) : super(element); /// Current graphics for lay outing. - PdfGraphics graphics; + PdfGraphics? graphics; /// Indicates end of lay outing. bool finish = false; /// List that layouts at the moment. - PdfList curList; + PdfList? curList; /// List that contains ListInfo. List<_ListInfo> info = <_ListInfo>[]; @@ -21,16 +21,16 @@ class _PdfListLayouter extends _ElementLayouter { int index = 0; /// The indent of current list. - double indent; + double? indent; /// Height in which it stop lay outing. - double resultHeight; + double? resultHeight; /// Lay outing bounds. - _Rectangle bounds; + late _Rectangle bounds; /// Current page for layout. - PdfPage currentPage; + PdfPage? currentPage; /// _Size for item lay outing. _Size size = _Size.empty; @@ -39,69 +39,69 @@ class _PdfListLayouter extends _ElementLayouter { bool usePaginateBounds = true; /// Current brush for lay outing. - PdfBrush currentBrush; + PdfBrush? currentBrush; /// Current pen for layout. - PdfPen currentPen; + PdfPen? currentPen; /// Current font for layout. - PdfFont currentFont; + PdfFont? currentFont; /// Current string format. - PdfStringFormat currentFormat; + PdfStringFormat? currentFormat; /// Marker maximum width. - num markerMaxWidth; + num? markerMaxWidth; @override - PdfList get element => super.element as PdfList; + PdfList? get element => super.element as PdfList?; @override - PdfLayoutResult _layoutInternal(_PdfLayoutParams param) { + PdfLayoutResult? _layoutInternal(_PdfLayoutParams param) { currentPage = param.page; - bounds = param.bounds._clone(); - if (param.bounds.height == 0 && - param.bounds.width == 0 && + bounds = param.bounds!._clone(); + if (param.bounds!.height == 0 && + param.bounds!.width == 0 && currentPage != null) { - bounds.width = currentPage.getClientSize().width; - bounds.height = currentPage.getClientSize().height; - bounds.width -= bounds.x; - bounds.height -= bounds.y; + bounds.width = currentPage!.getClientSize().width; + bounds.height = currentPage!.getClientSize().height; + bounds.width = bounds.width - bounds.x; + bounds.height = bounds.height - bounds.y; } if (currentPage != null) { - graphics = currentPage.graphics; + graphics = currentPage!.graphics; } _PageLayoutResult pageResult = _PageLayoutResult(); pageResult.broken = false; pageResult.y = bounds.y; curList = element; - indent = element.indent; + indent = element!.indent; _setCurrentParameters(element); - if (element.brush == null) { + if (element!.brush == null) { currentBrush = PdfBrushes.black; } - if (element._font == null) { + if (element!._font == null) { currentFont = PdfStandardFont(PdfFontFamily.helvetica, 8); - curList.font = currentFont; + curList!.font = currentFont!; } if (curList is PdfOrderedList) { markerMaxWidth = _getMarkerMaxWidth(curList as PdfOrderedList, info); } - final bool useOnePage = param.format.layoutType == PdfLayoutType.onePage; + final bool useOnePage = param.format!.layoutType == PdfLayoutType.onePage; while (!finish) { - bool cancel = _beforePageLayout(bounds.rect, currentPage, curList); + bool cancel = _beforePageLayout(bounds.rect, currentPage, curList!); pageResult.y = bounds.y; - ListEndPageLayoutArgs endArgs; + ListEndPageLayoutArgs? endArgs; if (!cancel) { - pageResult = _layoutOnPage(pageResult); - endArgs = _afterPageLayouted(bounds.rect, currentPage, curList); + pageResult = _layoutOnPage(pageResult)!; + endArgs = _afterPageLayouted(bounds.rect, currentPage, curList!); cancel = (endArgs == null) ? false : endArgs.cancel; } if (useOnePage || cancel) { @@ -111,63 +111,68 @@ class _PdfListLayouter extends _ElementLayouter { if ((endArgs != null) && (endArgs.nextPage != null)) { currentPage = endArgs.nextPage; } else { - currentPage = _getNextPage(currentPage); + currentPage = _getNextPage(currentPage!); } - graphics = currentPage.graphics; - if (param.bounds.width == 0 && param.bounds.height == 0) { - bounds.width = currentPage.getClientSize().width; - bounds.height = currentPage.getClientSize().height; - bounds.width -= bounds.x; - bounds.height -= bounds.y; + graphics = currentPage!.graphics; + if (param.bounds!.width == 0 && param.bounds!.height == 0) { + bounds.width = currentPage!.getClientSize().width; + bounds.height = currentPage!.getClientSize().height; + bounds.width = bounds.width - bounds.x; + bounds.height = bounds.height - bounds.y; } if ((param.format != null) && - param.format._boundsSet && + param.format!._boundsSet && usePaginateBounds) { - bounds = _Rectangle.fromRect(param.format.paginateBounds); + bounds = _Rectangle.fromRect(param.format!.paginateBounds); } } } info.clear(); final Rect finalBounds = - Rect.fromLTWH(bounds.x, pageResult.y, bounds.width, resultHeight ?? 0); - final PdfLayoutResult result = PdfLayoutResult._(currentPage, finalBounds); - return result; + Rect.fromLTWH(bounds.x, pageResult.y!, bounds.width, resultHeight ?? 0); + if (currentPage != null) { + final PdfLayoutResult result = + PdfLayoutResult._(currentPage!, finalBounds); + return result; + } else { + return null; + } } /// Layouts the on the page. - _PageLayoutResult _layoutOnPage(_PageLayoutResult pageResult) { - double height = 0; + _PageLayoutResult? _layoutOnPage(_PageLayoutResult? pageResult) { + double? height = 0; double resultantHeight = 0; - double y = bounds.y; - final double x = bounds.x; + double? y = bounds.y; + final double? x = bounds.x; size = _Size(bounds.size.width, bounds.size.height); - size.width -= indent; + size.width = size.width - indent!; while (true) { - for (; index < curList.items.count; ++index) { - final PdfListItem item = curList.items[index]; + for (; index < curList!.items.count; ++index) { + final PdfListItem item = curList!.items[index]; - if (currentPage != null && !pageResult.broken) { - _beforeItemLayout(item, currentPage); + if (currentPage != null && !pageResult!.broken) { + _beforeItemLayout(item, currentPage!); } final Map returnedValue = _drawItem( - pageResult, x, curList, index, indent, info, item, height, y); + pageResult!, x!, curList!, index, indent!, info, item, height!, y!); pageResult = returnedValue['pageResult']; height = returnedValue['height']; y = returnedValue['y']; - resultantHeight += height; - if (pageResult.broken) { + resultantHeight += height!; + if (pageResult!.broken) { return pageResult; } if (currentPage != null) { - _afterItemLayouted(item, currentPage); + _afterItemLayouted(item, currentPage!); } pageResult.markerWrote = false; - if (item.subList != null && item.subList.items.count > 0) { + if (item.subList != null && item.subList!.items.count > 0) { if (curList is PdfOrderedList) { - final PdfOrderedList oList = curList; + final PdfOrderedList oList = curList as PdfOrderedList; oList.marker._currentIndex = index; final _ListInfo listInfo = _ListInfo(curList, index, oList.marker._getNumber()); @@ -175,7 +180,7 @@ class _PdfListLayouter extends _ElementLayouter { listInfo.font = currentFont; listInfo.format = currentFormat; listInfo.pen = currentPen; - listInfo.markerWidth = markerMaxWidth; + listInfo.markerWidth = markerMaxWidth as double?; info.add(listInfo); } else { final _ListInfo listInfo = _ListInfo(curList, index); @@ -191,8 +196,8 @@ class _PdfListLayouter extends _ElementLayouter { _getMarkerMaxWidth(curList as PdfOrderedList, info); } index = -1; - indent += curList.indent; - size.width -= curList.indent; + indent = indent! + curList!.indent; + size.width = size.width - curList!.indent; _setCurrentParameters(item); _setCurrentParameters(curList); } @@ -205,8 +210,8 @@ class _PdfListLayouter extends _ElementLayouter { final _ListInfo listInfo = info.last; info.remove(listInfo); index = listInfo.index + 1; - indent -= curList.indent; - size.width += curList.indent; + indent = indent! - curList!.indent; + size.width = size.width + curList!.indent; markerMaxWidth = listInfo.markerWidth; currentBrush = listInfo.brush; currentPen = listInfo.pen; @@ -229,51 +234,51 @@ class _PdfListLayouter extends _ElementLayouter { double height, double y) { final _PdfStringLayouter layouter = _PdfStringLayouter(); - _PdfStringLayoutResult markerResult; - _PdfStringLayoutResult result; + _PdfStringLayoutResult? markerResult; + _PdfStringLayoutResult? result; bool wroteMaker = false; - final double textIndent = curList.textIndent; + final double? textIndent = curList.textIndent; final double posY = height + y; double posX = indent + x; - double itemHeight = 0; - double markerHeight = 0; + double? itemHeight = 0; + double? markerHeight = 0; _Size itemSize = _Size(size.width, size.height); - String text = item.text; - String markerText; + String? text = item.text; + String? markerText; - PdfBrush itemBrush = currentBrush; + PdfBrush? itemBrush = currentBrush; if (item.brush != null) { itemBrush = item.brush; } - PdfPen itemPen = currentPen; + PdfPen? itemPen = currentPen; if (item.pen != null) { itemPen = item.pen; } - PdfFont itemFont = currentFont; + PdfFont? itemFont = currentFont; if (item.font != null) { itemFont = item.font; } - PdfStringFormat itemFormat = currentFormat; + PdfStringFormat? itemFormat = currentFormat; if (item.stringFormat != null) { itemFormat = item.stringFormat; } - if (((size.width <= 0) || (size.width < itemFont.size)) && + if (((size.width <= 0) || (size.width < itemFont!.size)) && currentPage != null) { throw Exception('There is not enough space to layout list.'); } - size.height -= height; + size.height = size.height - height; PdfMarker marker; @@ -293,7 +298,7 @@ class _PdfListLayouter extends _ElementLayouter { if (markerText != null && ((marker is PdfUnorderedMarker) && (marker.style == PdfUnorderedMarkerStyle.customString))) { - markerResult = layouter._layout(markerText, _getMarkerFont(marker, item), + markerResult = layouter._layout(markerText, _getMarkerFont(marker, item)!, _getMarkerFormat(marker, item), width: size.width, height: size.height); posX += markerResult._size.width; @@ -305,8 +310,8 @@ class _PdfListLayouter extends _ElementLayouter { if (markerResult != null) { if (curList is PdfOrderedList) { - posX += markerMaxWidth; - pageResult.markerWidth = markerMaxWidth; + posX += markerMaxWidth!; + pageResult.markerWidth = markerMaxWidth as double?; } else { posX += markerResult._size.width; pageResult.markerWidth = markerResult._size.width; @@ -321,9 +326,9 @@ class _PdfListLayouter extends _ElementLayouter { canDrawMarker = false; } } else { - posX += (marker as PdfUnorderedMarker)._size.width; - pageResult.markerWidth = (marker as PdfUnorderedMarker)._size.width; - markerHeight = (marker as PdfUnorderedMarker)._size.height; + posX += (marker as PdfUnorderedMarker)._size!.width; + pageResult.markerWidth = marker._size!.width; + markerHeight = marker._size!.height; if (currentPage != null) { canDrawMarker = markerHeight < size.height; @@ -337,15 +342,15 @@ class _PdfListLayouter extends _ElementLayouter { if ((text != null) && canDrawMarker) { itemSize = _Size(size.width, size.height); - itemSize.width -= pageResult.markerWidth; + itemSize.width = itemSize.width - pageResult.markerWidth!; if (item.textIndent == 0) { - itemSize.width -= textIndent; + itemSize.width = itemSize.width - textIndent!; } else { - itemSize.width -= item.textIndent; + itemSize.width = itemSize.width - item.textIndent; } - if (((itemSize.width <= 0) || (itemSize.width < itemFont.size)) && + if (((itemSize.width <= 0) || (itemSize.width < itemFont!.size)) && currentPage != null) { throw Exception('''There is not enough space to layout the item text. Marker is too long or there is no enough space to draw it.'''); @@ -355,12 +360,12 @@ class _PdfListLayouter extends _ElementLayouter { if (!marker._rightToLeft) { if (item.textIndent == 0) { - itemX += textIndent; + itemX += textIndent!; } else { itemX += item.textIndent; } } else { - itemX -= pageResult.markerWidth; + itemX -= pageResult.markerWidth!; if (itemFormat != null && (itemFormat.alignment == PdfTextAlignment.right || @@ -375,34 +380,26 @@ class _PdfListLayouter extends _ElementLayouter { } } - result = layouter._layout(text, itemFont, itemFormat, + result = layouter._layout(text, itemFont!, itemFormat, width: itemSize.width, height: itemSize.height); final _Rectangle rect = _Rectangle(itemX, posY, itemSize.width, itemSize.height); - graphics._drawStringLayoutResult( + graphics!._drawStringLayoutResult( result, itemFont, itemPen, itemBrush, rect, itemFormat); y = posY; itemHeight = result._size.height; } height = (itemHeight < markerHeight) ? markerHeight : itemHeight; final bool isRemainder = - (result._remainder == null) || (result._remainder == ''); - if ((result != null) && !isRemainder || + (result!._remainder == null) || (result._remainder == ''); + if (!isRemainder || (markerResult != null) && !isRemainder || !canDrawMarker) { y = 0; height = 0; - if (result != null) { - pageResult.itemText = result._remainder; - if (result._remainder == item.text) { - canDrawMarker = false; - } - } else { - if (!canDrawMarker) { - pageResult.itemText = item.text; - } else { - pageResult.itemText = null; - } + pageResult.itemText = result._remainder; + if (result._remainder == item.text) { + canDrawMarker = false; } if (markerResult != null) { pageResult.markerText = markerResult._remainder; @@ -416,37 +413,35 @@ class _PdfListLayouter extends _ElementLayouter { pageResult.broken = false; } - if (result != null) { - pageResult.markerX = posX; - if (itemFormat != null) { - switch (itemFormat.alignment) { - case PdfTextAlignment.right: - pageResult.markerX = posX + itemSize.width - result._size.width; - break; - case PdfTextAlignment.center: - pageResult.markerX = - posX + (itemSize.width / 2) - (result._size.width / 2); - break; - case PdfTextAlignment.left: - case PdfTextAlignment.justify: - break; - } + pageResult.markerX = posX; + if (itemFormat != null) { + switch (itemFormat.alignment) { + case PdfTextAlignment.right: + pageResult.markerX = posX + itemSize.width - result._size.width; + break; + case PdfTextAlignment.center: + pageResult.markerX = + posX + (itemSize.width / 2) - (result._size.width / 2); + break; + case PdfTextAlignment.left: + case PdfTextAlignment.justify: + break; } + } - if (marker._rightToLeft) { - pageResult.markerX += result._size.width; + if (marker._rightToLeft) { + pageResult.markerX += result._size.width; - if (item.textIndent == 0) { - pageResult.markerX += textIndent; - } else { - pageResult.markerX += item.textIndent; - } + if (item.textIndent == 0) { + pageResult.markerX += textIndent!; + } else { + pageResult.markerX += item.textIndent; + } - if (itemFormat != null && - (itemFormat.alignment == PdfTextAlignment.right || - itemFormat.alignment == PdfTextAlignment.center)) { - pageResult.markerX -= indent; - } + if (itemFormat != null && + (itemFormat.alignment == PdfTextAlignment.right || + itemFormat.alignment == PdfTextAlignment.center)) { + pageResult.markerX -= indent; } } @@ -465,9 +460,9 @@ class _PdfListLayouter extends _ElementLayouter { pageResult.markerWrote = wroteMaker; if (curList is PdfOrderedList) { - pageResult.markerWidth = markerResult._size.width; + pageResult.markerWidth = markerResult!._size.width; } else { - pageResult.markerWidth = (marker as PdfUnorderedMarker)._size.width; + pageResult.markerWidth = (marker as PdfUnorderedMarker)._size!.width; } } } @@ -495,14 +490,14 @@ class _PdfListLayouter extends _ElementLayouter { } /// Gets the width of the marker max. - double _getMarkerMaxWidth(PdfOrderedList list, List<_ListInfo> info) { - double width = -1; + double? _getMarkerMaxWidth(PdfOrderedList list, List<_ListInfo> info) { + double? width = -1; for (int i = 0; i < list.items.count; i++) { final _PdfStringLayoutResult result = _createOrderedMarkerResult( list, list.items[i], i + list.marker.startNumber, info, true); - if (width < result._size.width) { + if (width! < result._size.width) { width = result._size.width; } } @@ -511,7 +506,7 @@ class _PdfListLayouter extends _ElementLayouter { /// Creates the ordered marker result. _PdfStringLayoutResult _createOrderedMarkerResult(PdfList list, - PdfListItem item, int index, List<_ListInfo> info, bool findMaxWidth) { + PdfListItem? item, int index, List<_ListInfo> info, bool findMaxWidth) { PdfOrderedList orderedList = list as PdfOrderedList; PdfOrderedMarker marker = orderedList.marker; marker._currentIndex = index; @@ -528,7 +523,7 @@ class _PdfListLayouter extends _ElementLayouter { if (listInfo.list is PdfUnorderedList) { break; } - orderedList = listInfo.list; + orderedList = listInfo.list as PdfOrderedList; if (orderedList.marker.style == PdfNumberStyle.none) { break; } @@ -540,13 +535,13 @@ class _PdfListLayouter extends _ElementLayouter { } } final _PdfStringLayouter layouter = _PdfStringLayouter(); - orderedList = list as PdfOrderedList; + orderedList = list; marker = orderedList.marker; - final PdfFont markerFont = _getMarkerFont(marker, item); - PdfStringFormat markerFormat = _getMarkerFormat(marker, item); + final PdfFont markerFont = _getMarkerFont(marker, item)!; + PdfStringFormat? markerFormat = _getMarkerFormat(marker, item); final _Size markerSize = _Size(size.width, size.height); if (!findMaxWidth) { - markerSize.width = markerMaxWidth; + markerSize.width = markerMaxWidth! as double; markerFormat = _setMarkerStringFormat(marker, markerFormat); } final _PdfStringLayoutResult result = layouter._layout( @@ -556,10 +551,10 @@ class _PdfListLayouter extends _ElementLayouter { } /// Gets the markers font. - PdfFont _getMarkerFont(PdfMarker marker, PdfListItem item) { - PdfFont markerFont = marker.font; + PdfFont? _getMarkerFont(PdfMarker marker, PdfListItem? item) { + PdfFont? markerFont = marker.font; if (marker.font == null) { - markerFont = item.font; + markerFont = item!.font; if (item.font == null) { markerFont = currentFont; } @@ -569,10 +564,10 @@ class _PdfListLayouter extends _ElementLayouter { } /// Gets the marker format. - PdfStringFormat _getMarkerFormat(PdfMarker marker, PdfListItem item) { - PdfStringFormat markerFormat = marker.stringFormat; + PdfStringFormat? _getMarkerFormat(PdfMarker marker, PdfListItem? item) { + PdfStringFormat? markerFormat = marker.stringFormat; if (marker.stringFormat == null) { - markerFormat = item.stringFormat; + markerFormat = item!.stringFormat; if (item.stringFormat == null) { markerFormat = currentFormat; } @@ -582,7 +577,7 @@ class _PdfListLayouter extends _ElementLayouter { /// Sets the marker alingment. PdfStringFormat _setMarkerStringFormat( - PdfOrderedMarker marker, PdfStringFormat markerFormat) { + PdfOrderedMarker marker, PdfStringFormat? markerFormat) { markerFormat = markerFormat == null ? PdfStringFormat() : _markerFormatClone(markerFormat); @@ -593,9 +588,7 @@ class _PdfListLayouter extends _ElementLayouter { } } if (currentPage == null) { - if (markerFormat != null) { - markerFormat.alignment = PdfTextAlignment.left; - } + markerFormat.alignment = PdfTextAlignment.left; } return markerFormat; } @@ -620,13 +613,13 @@ class _PdfListLayouter extends _ElementLayouter { /// Before the page layout. bool _beforePageLayout( - Rect currentBounds, PdfPage currentPage, PdfList list) { + Rect currentBounds, PdfPage? currentPage, PdfList list) { bool cancel = false; - if (element._raiseBeginPageLayout && currentPage != null) { + if (element!._raiseBeginPageLayout && currentPage != null) { final ListBeginPageLayoutArgs args = ListBeginPageLayoutArgs._(currentBounds, currentPage, list); - element._onBeginPageLayout(args); + element!._onBeginPageLayout(args); cancel = args.cancel; bounds = _Rectangle.fromRect(args.bounds); usePaginateBounds = false; @@ -635,13 +628,13 @@ class _PdfListLayouter extends _ElementLayouter { } /// After the page layouted. - ListEndPageLayoutArgs _afterPageLayouted( - Rect bounds, PdfPage currentPage, PdfList list) { - ListEndPageLayoutArgs args; - if (element._raisePageLayouted && currentPage != null) { + ListEndPageLayoutArgs? _afterPageLayouted( + Rect bounds, PdfPage? currentPage, PdfList list) { + ListEndPageLayoutArgs? args; + if (element!._raisePageLayouted && currentPage != null) { final PdfLayoutResult result = PdfLayoutResult._(currentPage, bounds); args = ListEndPageLayoutArgs._internal(result, list); - element._onEndPageLayout(args); + element!._onEndPageLayout(args); } return args; } @@ -649,35 +642,36 @@ class _PdfListLayouter extends _ElementLayouter { /// Before the item layout. void _beforeItemLayout(PdfListItem item, PdfPage page) { final BeginItemLayoutArgs args = BeginItemLayoutArgs._internal(item, page); - element._onBeginItemLayout(args); + element!._onBeginItemLayout(args); } /// Afters the item layouted. void _afterItemLayouted(PdfListItem item, PdfPage page) { final EndItemLayoutArgs args = EndItemLayoutArgs._internal(item, page); - element._onEndItemLayout(args); + element!._onEndItemLayout(args); } /// Draws the marker. bool _drawMarker(PdfList curList, PdfListItem item, - _PdfStringLayoutResult markerResult, double posY, double posX) { + _PdfStringLayoutResult? markerResult, double posY, double posX) { if (curList is PdfOrderedList) { - if (curList.font != null && markerResult != null) { - if (curList.font.size > markerResult._size.height) { - posY += (curList.font.size / 2) - (markerResult._size.height / 2); - markerResult._size.height += posY; + if (markerResult != null) { + if (curList.font != null && + curList.font!.size > markerResult._size.height) { + posY += (curList.font!.size / 2) - (markerResult._size.height / 2); + markerResult._size.height = markerResult._size.height + posY; } } - _drawOrderedMarker(curList, markerResult, item, posX, posY); + _drawOrderedMarker(curList, markerResult!, item, posX, posY); } else if (curList is PdfUnorderedList) { if (curList.marker.font != null && markerResult != null) { - final PdfFont font = - curList.font != null && curList.font.size > curList.marker.font.size - ? curList.font - : curList.marker.font; + final PdfFont font = curList.font != null && + curList.font!.size > curList.marker.font!.size + ? curList.font! + : curList.marker.font!; if (font.size > markerResult._size.height) { posY += (font.height / 2) - (markerResult._size.height / 2); - markerResult._size.height += posY; + markerResult._size.height = markerResult._size.height + posY; } } _drawUnorderedMarker(curList, markerResult, item, posX, posY); @@ -694,47 +688,47 @@ class _PdfListLayouter extends _ElementLayouter { double posY) { final PdfOrderedList oList = curList as PdfOrderedList; final PdfOrderedMarker marker = oList.marker; - final PdfFont markerFont = _getMarkerFont(marker, item); - PdfStringFormat markerFormat = _getMarkerFormat(marker, item); - final PdfPen markerPen = _getMarkerPen(marker, item); - final PdfBrush markerBrush = _getMarkerBrush(marker, item); - final _Rectangle rect = _Rectangle(posX - markerMaxWidth, posY, + final PdfFont markerFont = _getMarkerFont(marker, item)!; + PdfStringFormat? markerFormat = _getMarkerFormat(marker, item); + final PdfPen? markerPen = _getMarkerPen(marker, item); + final PdfBrush? markerBrush = _getMarkerBrush(marker, item); + final _Rectangle rect = _Rectangle(posX - markerMaxWidth!, posY, markerResult._size.width, markerResult._size.height); - rect.width = markerMaxWidth; + rect.width = markerMaxWidth! as double; markerFormat = _setMarkerStringFormat(marker, markerFormat); - graphics._drawStringLayoutResult( + graphics!._drawStringLayoutResult( markerResult, markerFont, markerPen, markerBrush, rect, markerFormat); return markerResult; } /// Draws the unordered marker. - _PdfStringLayoutResult _drawUnorderedMarker( + _PdfStringLayoutResult? _drawUnorderedMarker( PdfList curList, - _PdfStringLayoutResult markerResult, + _PdfStringLayoutResult? markerResult, PdfListItem item, double posX, double posY) { final PdfUnorderedList uList = curList as PdfUnorderedList; final PdfUnorderedMarker marker = uList.marker; - final PdfFont markerFont = _getMarkerFont(marker, item); - final PdfPen markerPen = _getMarkerPen(marker, item); - final PdfBrush markerBrush = _getMarkerBrush(marker, item); - final PdfStringFormat markerFormat = _getMarkerFormat(marker, item); + final PdfFont? markerFont = _getMarkerFont(marker, item); + final PdfPen? markerPen = _getMarkerPen(marker, item); + final PdfBrush? markerBrush = _getMarkerBrush(marker, item); + final PdfStringFormat? markerFormat = _getMarkerFormat(marker, item); if (markerResult != null) { final _Point location = _Point(posX - markerResult._size.width, posY); marker._size = markerResult._size; if (marker.style == PdfUnorderedMarkerStyle.customString) { final _Rectangle rect = _Rectangle(location.x, location.y, markerResult._size.width, markerResult._size.height); - graphics._drawStringLayoutResult(markerResult, markerFont, markerPen, + graphics!._drawStringLayoutResult(markerResult, markerFont!, markerPen, markerBrush, rect, markerFormat); } else { marker._unicodeFont = - PdfStandardFont(PdfFontFamily.zapfDingbats, markerFont._size); + PdfStandardFont(PdfFontFamily.zapfDingbats, markerFont!._size); marker._draw(graphics, location.offset, markerBrush, markerPen); } } else { - marker._size = _Size(markerFont._size, markerFont._size); + marker._size = _Size(markerFont!._size, markerFont._size); final _Point location = _Point(posX - markerFont._size, posY); marker._draw(graphics, location.offset, markerBrush, markerPen, curList); } @@ -742,8 +736,8 @@ class _PdfListLayouter extends _ElementLayouter { } /// Gets the marker pen. - PdfPen _getMarkerPen(PdfMarker marker, PdfListItem item) { - PdfPen markerPen = marker.pen; + PdfPen? _getMarkerPen(PdfMarker marker, PdfListItem item) { + PdfPen? markerPen = marker.pen; if (marker.pen == null) { markerPen = item.pen; if (item.pen == null) { @@ -754,8 +748,8 @@ class _PdfListLayouter extends _ElementLayouter { } /// Gets the marker brush. - PdfBrush _getMarkerBrush(PdfMarker marker, PdfListItem item) { - PdfBrush markerBrush = marker.brush; + PdfBrush? _getMarkerBrush(PdfMarker marker, PdfListItem item) { + PdfBrush? markerBrush = marker.brush; if (marker.brush == null) { markerBrush = item.brush; if (item.brush == null) { @@ -766,9 +760,9 @@ class _PdfListLayouter extends _ElementLayouter { } /// Creates the marker result. - _PdfStringLayoutResult _createMarkerResult( + _PdfStringLayoutResult? _createMarkerResult( int index, PdfList curList, List<_ListInfo> listInfo, PdfListItem item) { - _PdfStringLayoutResult markerResult; + _PdfStringLayoutResult? markerResult; if (curList is PdfOrderedList) { markerResult = _createOrderedMarkerResult(curList, item, index, info, false); @@ -780,12 +774,12 @@ class _PdfListLayouter extends _ElementLayouter { } /// Creates the unordered marker result. - _PdfStringLayoutResult _createUnorderedMarkerResult( + _PdfStringLayoutResult? _createUnorderedMarkerResult( PdfList curList, PdfListItem item, _Size markerSize) { final PdfUnorderedMarker marker = (curList as PdfUnorderedList).marker; - _PdfStringLayoutResult result; - final PdfFont markerFont = _getMarkerFont(marker, item); - final PdfStringFormat markerFormat = _getMarkerFormat(marker, item); + _PdfStringLayoutResult? result; + final PdfFont? markerFont = _getMarkerFont(marker, item); + final PdfStringFormat? markerFormat = _getMarkerFormat(marker, item); final _PdfStringLayouter layouter = _PdfStringLayouter(); switch (marker.style) { case PdfUnorderedMarkerStyle.customImage: @@ -793,22 +787,22 @@ class _PdfListLayouter extends _ElementLayouter { // marker._size = markerSize; break; case PdfUnorderedMarkerStyle.customTemplate: - markerSize = _Size(markerFont.size, markerFont.size); + markerSize = _Size(markerFont!.size, markerFont.size); marker._size = markerSize; break; case PdfUnorderedMarkerStyle.customString: - result = layouter._layout(marker.text, markerFont, markerFormat, + result = layouter._layout(marker.text!, markerFont!, markerFormat, width: size.width, height: size.height); break; default: final PdfStandardFont uFont = - PdfStandardFont(PdfFontFamily.zapfDingbats, markerFont.size); + PdfStandardFont(PdfFontFamily.zapfDingbats, markerFont!.size); result = layouter._layout(marker._getStyledText(), uFont, null, width: size.width, height: size.height); marker._size = result._size; if (marker.pen != null) { - result._size = _Size(result._size.width + 2 * marker.pen.width, - result._size.height + 2 * marker.pen.width); + result._size = _Size(result._size.width + 2 * marker.pen!.width, + result._size.height + 2 * marker.pen!.width); } break; } @@ -822,19 +816,19 @@ class _PageLayoutResult { bool broken = false; /// Y-ordinate of broken item of marker. - double y = 0; + double? y = 0; /// Text of item that was not draw. - String itemText; + String? itemText; /// Text of marker that was not draw. - String markerText; + String? markerText; /// If true marker start draw. bool markerWrote = false; /// Width of marker. - double markerWidth = 0; + double? markerWidth = 0; /// X-coordinate of marker. double markerX = 0; @@ -849,49 +843,43 @@ class _ListInfo { int index; /// Represents list. - PdfList list; + PdfList? list; /// The number of item at specified index. String number; /// Lists brush. - PdfBrush brush; + PdfBrush? brush; /// Lists pen. - PdfPen pen; + PdfPen? pen; /// Lists font. - PdfFont font; + PdfFont? font; /// Lists format. - PdfStringFormat format; + PdfStringFormat? format; /// Marker width; - double markerWidth; + double? markerWidth; } /// Represents begin page layout event arguments. class ListBeginPageLayoutArgs extends BeginPageLayoutArgs { /// Initializes a new instance of the [ListBeginPageLayoutArgs] class. - ListBeginPageLayoutArgs._(Rect bounds, PdfPage page, this._list) + ListBeginPageLayoutArgs._(Rect bounds, PdfPage page, this.list) : super(bounds, page); - /// List that ended layout. - final PdfList _list; - /// Gets the ended layout - PdfList get list => _list; + late PdfList list; } /// Represents begin page layout event arguments. class ListEndPageLayoutArgs extends EndPageLayoutArgs { /// Initializes a new instance of the [ListEndPageLayoutArgs] class. - ListEndPageLayoutArgs._internal(PdfLayoutResult result, this._list) + ListEndPageLayoutArgs._internal(PdfLayoutResult result, this.list) : super(result); - /// List that ended layout. - final PdfList _list; - /// Gets the ended layout - PdfList get list => _list; + PdfList list; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_ordered_list.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_ordered_list.dart index 8ce33570a..135d59344 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_ordered_list.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_ordered_list.dart @@ -1,20 +1,63 @@ part of pdf; /// Represents the ordered list. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new ordered list. +/// PdfOrderedList( +/// items: PdfListItemCollection(['PDF', 'XlsIO', 'DocIO', 'PPT']), +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic), +/// format: PdfStringFormat(lineSpacing: 20), +/// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), +/// style: PdfNumberStyle.numeric, +/// indent: 15, +/// textIndent: 10) +/// ..draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfOrderedList extends PdfList { + //Constructor /// Initialize a new instance of the [PdfOrderedList] class. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), + /// style: PdfNumberStyle.numeric, + /// indent: 15, + /// textIndent: 10) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfOrderedList( - {PdfOrderedMarker marker, - PdfListItemCollection items, - String text, - PdfFont font, + {PdfOrderedMarker? marker, + PdfListItemCollection? items, + String? text, + PdfFont? font, PdfNumberStyle style = PdfNumberStyle.numeric, - PdfStringFormat format, + PdfStringFormat? format, this.markerHierarchy = false, double indent = 10, double textIndent = 5}) : super() { - _marker = marker ?? _createMarker(style); + this.marker = marker ?? _createMarker(style); stringFormat = format; super.indent = indent; super.textIndent = textIndent; @@ -22,29 +65,63 @@ class PdfOrderedList extends PdfList { _font = font; } if (items != null) { - ArgumentError.checkNotNull(items, 'Items collection can\'t be null'); _items = items; } else if (text != null) { _items = PdfList._createItems(text); } } - /// Marker of the list. - PdfOrderedMarker _marker; - /// True if user want to use numbering hierarchy, otherwise false. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic)) + /// .items[0] + /// .subList = + /// PdfOrderedList( + /// items: PdfListItemCollection(['PDF', 'XlsIO', 'DocIO', 'PPT']) + /// markerHierarchy: true) + /// ..draw( + /// page: document.pages.add(), + /// bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` bool markerHierarchy; - /// Gets marker of the list items. - PdfOrderedMarker get marker => _marker; - - /// Sets marker of the list items. - set marker(PdfOrderedMarker value) { - ArgumentError.checkNotNull(value, 'marker'); - _marker = value; - } + /// Gets or sets marker of the list items. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new ordered list. + /// PdfOrderedList( + /// text: 'PDF\nXlsIO\nDocIO\nPPT', + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// format: PdfStringFormat(lineSpacing: 20), + /// marker: PdfOrderedMarker(style: PdfNumberStyle.numeric), + /// style: PdfNumberStyle.numeric, + /// indent: 15, + /// textIndent: 10) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + late PdfOrderedMarker marker; - /// Creates the marker. + //Static methods. + //Creates the marker. static PdfOrderedMarker _createMarker(PdfNumberStyle style) { return PdfOrderedMarker(style: style, font: null); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_unordered_list.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_unordered_list.dart index 0d779cea0..46d2f35c0 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_unordered_list.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_unordered_list.dart @@ -1,19 +1,60 @@ part of pdf; /// Represents the ordered list. +/// +/// ```dart +/// //Create a new PDF document. +/// PdfDocument document = PdfDocument(); +/// //Create a new unordered list. +/// PdfUnorderedList( +/// items: PdfListItemCollection(['Essential tools', 'Essential grid']), +/// font: PdfStandardFont(PdfFontFamily.helvetica, 16, +/// style: PdfFontStyle.italic), +/// marker: PdfUnorderedMarker(style: PdfUnorderedMarkerStyle.disk), +/// format: PdfStringFormat(lineSpacing: 20), +/// indent: 15, +/// textIndent: 10) +/// ..draw( +/// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); +/// //Save the document. +/// final List bytes = document.save(); +/// //Dispose the document. +/// document.dispose(); +/// ``` class PdfUnorderedList extends PdfList { + //Constructor /// Initializes a new instance of the [PdfUnorderedList] class. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new unordered list. + /// PdfUnorderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: PdfUnorderedMarker(style: PdfUnorderedMarkerStyle.disk), + /// format: PdfStringFormat(lineSpacing: 20), + /// indent: 15, + /// textIndent: 10) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` PdfUnorderedList( - {PdfUnorderedMarker marker, - PdfListItemCollection items, - String text, - PdfFont font, + {PdfUnorderedMarker? marker, + PdfListItemCollection? items, + String? text, + PdfFont? font, PdfUnorderedMarkerStyle style = PdfUnorderedMarkerStyle.disk, - PdfStringFormat format, + PdfStringFormat? format, double indent = 10, double textIndent = 5}) : super() { - _marker = marker ?? _createMarker(style); + this.marker = marker ?? _createMarker(style); stringFormat = format; super.indent = indent; super.textIndent = textIndent; @@ -21,26 +62,38 @@ class PdfUnorderedList extends PdfList { _font = font; } if (items != null) { - ArgumentError.checkNotNull(items, 'Items collection can\'t be null'); _items = items; } else if (text != null) { _items = PdfList._createItems(text); } } - /// Marker for the list. - PdfUnorderedMarker _marker; + //Properties + /// Gets or gets the marker. + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(); + /// //Create a new unordered list. + /// PdfUnorderedList( + /// items: PdfListItemCollection(['Essential tools', 'Essential grid']), + /// font: PdfStandardFont(PdfFontFamily.helvetica, 16, + /// style: PdfFontStyle.italic), + /// marker: PdfUnorderedMarker(style: PdfUnorderedMarkerStyle.disk), + /// format: PdfStringFormat(lineSpacing: 20), + /// indent: 15, + /// textIndent: 10) + /// ..draw( + /// page: document.pages.add(), bounds: const Rect.fromLTWH(20, 20, 0, 0)); + /// //Save the document. + /// final List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + late PdfUnorderedMarker marker; - /// Gets the marker. - PdfUnorderedMarker get marker => _marker; - - /// Sets the marker. - set marker(PdfUnorderedMarker value) { - ArgumentError.checkNotNull(value, 'marker'); - _marker = value; - } - - /// Creates the marker. + //Static methods + //Creates the marker. static PdfUnorderedMarker _createMarker(PdfUnorderedMarkerStyle style) { return PdfUnorderedMarker(style: style); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/xmp/xmp_metadata.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/xmp/xmp_metadata.dart index 1b9bd8686..94c3ada05 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/xmp/xmp_metadata.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/xmp/xmp_metadata.dart @@ -4,59 +4,60 @@ part of pdf; class _XmpMetadata extends _IPdfWrapper { //Constructor /// Initializes a new instance of the [XmpMetadata] class. - _XmpMetadata(PdfDocumentInformation documentInfo) { + _XmpMetadata(PdfDocumentInformation? documentInfo) { _initialize(documentInfo); } /// Initializes a new instance of the [XmpMetadata] class. _XmpMetadata.fromXmlDocument(XmlDocument xmp) { - ArgumentError.checkNotNull(xmp, 'xmpMetadata'); _stream = _PdfStream(); - _stream._beginSave = _beginSave; - _stream._endSave = _endSave; + _stream!._beginSave = _beginSave; + _stream!._endSave = _endSave; load(xmp); } //Fields - XmlDocument _xmlData; - _PdfStream _stream; - PdfDocumentInformation _documentInfo; - Map _namespaceCollection = {}; + XmlDocument? _xmlData; + _PdfStream? _stream; + PdfDocumentInformation? _documentInfo; + Map _namespaceCollection = {}; final String _rdfUri = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; final String _xap = 'http://ns.adobe.com/xap/1.0/'; final String _dublinSchema = 'http://purl.org/dc/elements/1.1/'; //Properties /// Gets XMP data in XML format. - XmlDocument get xmlData => _xmlData; + XmlDocument? get xmlData => _xmlData; //Gets RDF element of the packet. - XmlElement get _rdf { - XmlElement node; - for (final element in _xmlData.descendants) { + XmlElement? get _rdf { + XmlElement? node; + for (final element in _xmlData!.descendants) { if (element is XmlElement && element.name.local == 'RDF') { node = element; break; } } - final String elmName = _xmlData.rootElement.name.toString(); + final String elmName = _xmlData!.rootElement.name.toString(); if (node == null) { - for (final element in _xmlData.descendants) { + for (final element in _xmlData!.descendants) { if (element is XmlElement && element.name.local == elmName) { node = element; break; } } - ArgumentError.checkNotNull(node, 'node'); + if (node == null) { + throw ArgumentError.value(node, 'node', 'node cannot be null'); + } } return node; } //Gets xmpmeta element of the packet. - XmlElement get _xmpmeta { - XmlElement node; + XmlElement? get _xmpmeta { + XmlElement? node; int count = 0; - for (final element in _xmlData.descendants) { + for (final element in _xmlData!.descendants) { if (element is XmlElement && element.name.local == 'xmpmeta') { node = element; count++; @@ -66,81 +67,81 @@ class _XmpMetadata extends _IPdfWrapper { } } } - ArgumentError.checkNotNull(node, 'node'); + if (node == null) { + throw ArgumentError.value(node, 'node', 'node cannot be null'); + } return node; } //Public methods. /// Loads XMP from the XML. void load(XmlDocument xmp) { - ArgumentError.checkNotNull(xmp, 'xmp'); _reset(); _xmlData = xmp; - _importNamespaces(_xmlData); + _importNamespaces(_xmlData!); } /// Adds schema to the XMP in XML format. void add(XmlElement schema) { - ArgumentError.checkNotNull(schema, 'schema'); // Import namespaces. - _addNamespace(schema.name.prefix, schema.name.namespaceUri ?? ''); + _addNamespace(schema.name.prefix!, schema.name.namespaceUri ?? ''); // Append schema. - _rdf.children.add(schema); + _rdf!.children.add(schema); } //Implementations - void _initialize(PdfDocumentInformation info) { + void _initialize(PdfDocumentInformation? info) { _xmlData = XmlDocument(); _stream = _PdfStream(); _documentInfo = info; _initializeStream(); _createStartPacket(); _createXmpmeta(); - _createRdf(_documentInfo); + _createRdf(_documentInfo!); _createEndPacket(); } //Initialize stream. void _initializeStream() { - _stream._beginSave = _beginSave; - _stream._endSave = _endSave; - _stream[_DictionaryProperties.type] = + _stream!._beginSave = _beginSave; + _stream!._endSave = _endSave; + _stream![_DictionaryProperties.type] = _PdfName(_DictionaryProperties.metadata); - _stream[_DictionaryProperties.subtype] = + _stream![_DictionaryProperties.subtype] = _PdfName(_DictionaryProperties.xml); - _stream.compress = false; + _stream!.compress = false; } //Raises before stream saves. - void _beginSave(Object sender, _SavePdfPrimitiveArgs ars) { + void _beginSave(Object sender, _SavePdfPrimitiveArgs? ars) { //Save Xml to the stream. final _PdfStreamWriter streamWriter = _PdfStreamWriter(_stream); - streamWriter._write(_xmlData.toXmlString(pretty: true)); + streamWriter._write(_xmlData!.toXmlString(pretty: true)); } //Raises after stream saves. - void _endSave(Object sender, _SavePdfPrimitiveArgs ars) { + void _endSave(Object sender, _SavePdfPrimitiveArgs? ars) { //Reset stream data - _stream._clearStream(); + _stream!._clearStream(); } //Creates packet element. void _createStartPacket() { const String startPacket = 'begin=\"\uFEFF" id=\"W5M0MpCehiHzreSzNTczkc9d\"'; - _xmlData.children.add(XmlProcessing('xpacket', startPacket)); + _xmlData!.children.add(XmlProcessing('xpacket', startPacket)); } //Creates packet element. void _createEndPacket() { const String endPacket = 'end="r"'; - _xmlData.children.add(XmlProcessing('xpacket', endPacket)); + _xmlData!.children.add(XmlProcessing('xpacket', endPacket)); } //Creates xmpmeta element. void _createXmpmeta() { final XmlElement element = _createElement('x', 'xmpmeta', 'adobe:ns:meta/'); - _xmlData.children.add(element); + _xmlData!.children.add(element); } //Creates Resource Description Framework element. @@ -148,7 +149,7 @@ class _XmpMetadata extends _IPdfWrapper { final XmlElement rdf = _createElement('rdf', 'RDF', _rdfUri); _addNamespace('rdf', _rdfUri); if (!_isNullOrEmpty(info.producer) || !_isNullOrEmpty(info.keywords)) { - final String pdfNamespace = + final String? pdfNamespace = _addNamespace('pdf', 'http://ns.adobe.com/pdf/1.3/'); final XmlElement rdfDescription = _createElement('rdf', 'Description', _rdfUri); @@ -156,35 +157,31 @@ class _XmpMetadata extends _IPdfWrapper { if (!_isNullOrEmpty(info.producer)) { rdfDescription.children.add(XmlElement( XmlName('Producer', 'pdf'), - [XmlAttribute(XmlName('pdf', 'xmlns'), pdfNamespace)], + [XmlAttribute(XmlName('pdf', 'xmlns'), pdfNamespace!)], [XmlText(info.producer)])); } if (!_isNullOrEmpty(info.keywords)) { rdfDescription.children.add(XmlElement( XmlName('Keywords', 'pdf'), - [XmlAttribute(XmlName('pdf', 'xmlns'), pdfNamespace)], + [XmlAttribute(XmlName('pdf', 'xmlns'), pdfNamespace!)], [XmlText(info.keywords)])); } rdf.children.add(rdfDescription); } - if (!_isNullOrEmpty(info.creator) || - info._creationDate != null || - info._modificationDate != null) { + if (!_isNullOrEmpty(info.creator)) { final XmlElement xmpDescription = _createElement('rdf', 'Description', _rdfUri); xmpDescription.setAttribute('rdf:about', ' '); - final String xmpNamespace = _addNamespace('xmp', _xap); + final String? xmpNamespace = _addNamespace('xmp', _xap); xmpDescription.setAttribute('xmlns:xmp', xmpNamespace); if (!_isNullOrEmpty(info.creator)) { xmpDescription.children.add(XmlElement( XmlName('CreatorTool', 'xmp'), [], [XmlText(info.creator)])); } - if (info._creationDate != null) { - final String createDate = _getDateTime(info._creationDate); - xmpDescription.children.add(XmlElement( - XmlName('CreateDate', 'xmp'), [], [XmlText(createDate)])); - } - if (info._modificationDate != null && !info._isRemoveModifyDate) { + final String createDate = _getDateTime(info._creationDate); + xmpDescription.children.add( + XmlElement(XmlName('CreateDate', 'xmp'), [], [XmlText(createDate)])); + if (!info._isRemoveModifyDate) { final String modificationDate = _getDateTime(info._modificationDate); xmpDescription.children.add(XmlElement( XmlName('ModifyDate', 'xmp'), [], [XmlText(modificationDate)])); @@ -192,7 +189,7 @@ class _XmpMetadata extends _IPdfWrapper { rdf.children.add(xmpDescription); } //Dublin Core Schema - final String dublinNamespace = _addNamespace('dc', _dublinSchema); + final String? dublinNamespace = _addNamespace('dc', _dublinSchema); final XmlElement dublinDescription = _createElement('rdf', 'Description', _rdfUri); dublinDescription.setAttribute('rdf:about', ' '); @@ -208,16 +205,16 @@ class _XmpMetadata extends _IPdfWrapper { _createDublinCoreContainer( dublinDescription, 'creator', info.author, false, 'Seq'); rdf.children.add(dublinDescription); - if (_documentInfo._conformance == PdfConformanceLevel.a1b || - _documentInfo._conformance == PdfConformanceLevel.a2b || - _documentInfo._conformance == PdfConformanceLevel.a3b) { - final String pdfaid = + if (_documentInfo!._conformance == PdfConformanceLevel.a1b || + _documentInfo!._conformance == PdfConformanceLevel.a2b || + _documentInfo!._conformance == PdfConformanceLevel.a3b) { + final String? pdfaid = _addNamespace('pdfaid', 'http://www.aiim.org/pdfa/ns/id/'); final XmlElement pdfA = _createElement('rdf', 'Description', _rdfUri); pdfA.setAttribute('rdf:about', ' '); - if (_documentInfo._conformance == PdfConformanceLevel.a1b) { + if (_documentInfo!._conformance == PdfConformanceLevel.a1b) { pdfA.setAttribute('pdfaid:part', '1'); - } else if (_documentInfo._conformance == PdfConformanceLevel.a2b) { + } else if (_documentInfo!._conformance == PdfConformanceLevel.a2b) { pdfA.setAttribute('pdfaid:part', '2'); } else { pdfA.setAttribute('pdfaid:part', '3'); @@ -228,17 +225,15 @@ class _XmpMetadata extends _IPdfWrapper { } else { _addNamespace('pdfaid', _rdfUri); } - _xmpmeta.children.add(rdf); + _xmpmeta!.children.add(rdf); } - bool _isNullOrEmpty(String text) { + bool _isNullOrEmpty(String? text) { return text == null || text == ''; } XmlElement _createElement( String prefix, String localName, String namespaceURI) { - ArgumentError.checkNotNull(prefix, 'prefix'); - ArgumentError.checkNotNull(localName, 'localName'); XmlElement element; if (!_namespaceCollection.containsKey(prefix) && prefix != 'xml' && @@ -253,10 +248,8 @@ class _XmpMetadata extends _IPdfWrapper { return element; } - String _addNamespace(String prefix, String namespaceURI) { - ArgumentError.checkNotNull(prefix, 'prefix'); - ArgumentError.checkNotNull(namespaceURI, 'namespaceURI'); - String result = namespaceURI; + String? _addNamespace(String prefix, String namespaceURI) { + String? result = namespaceURI; if (!_namespaceCollection.containsKey(prefix) && prefix != 'xml' && prefix != 'xmlns') { @@ -288,7 +281,7 @@ class _XmpMetadata extends _IPdfWrapper { //Creates a Dublin core containers. void _createDublinCoreContainer(XmlElement dublinDesc, String containerName, - String value, bool defaultLang, String element) { + String? value, bool defaultLang, String element) { if (!_isNullOrEmpty(value)) { final XmlElement title = _createElement('dc', containerName, _dublinSchema); @@ -296,7 +289,7 @@ class _XmpMetadata extends _IPdfWrapper { final XmlElement alt = _createElement('rdf', element, _rdfUri); XmlElement li = _createElement('rdf', 'li', _rdfUri); if (containerName == 'Subject') { - final List values = value.split(','); + final List values = value!.split(','); for (int i = 0; i < values.length; i++) { if (i > 0) { li = _createElement('rdf', 'li', _rdfUri); @@ -305,7 +298,7 @@ class _XmpMetadata extends _IPdfWrapper { alt.children.add(li); } } else { - li.innerText = value; + li.innerText = value!; alt.children.add(li); } title.children.add(alt); @@ -333,5 +326,5 @@ class _XmpMetadata extends _IPdfWrapper { } @override - _IPdfPrimitive get _element => _stream; + _IPdfPrimitive? get _element => _stream; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_changable.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_changable.dart index dd40b89ab..975e5ccc5 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_changable.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_changable.dart @@ -1,6 +1,6 @@ part of pdf; class _IPdfChangable { - bool changed; + bool? changed; void freezeChanges(Object freezer) {} } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_primitive.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_primitive.dart index b2e6aefb2..2d4106267 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_primitive.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_primitive.dart @@ -4,24 +4,24 @@ part of pdf; class _IPdfPrimitive { /// Specfies the status of the IPdfPrmitive. /// Status is registered if it has a reference or else none. - _ObjectStatus status; + _ObjectStatus? status; /// Indicates whether this primitive is saving or not - bool isSaving; + bool? isSaving; /// Index value of the specified object - int objectCollectionIndex; + int? objectCollectionIndex; /// Stores the cloned object for future use. - _IPdfPrimitive clonedObject; + _IPdfPrimitive? clonedObject; /// Position of the object. - int position; + int? position; /// Saves the object using the specified writer. - void save(_IPdfWriter writer) {} + void save(_IPdfWriter? writer) {} void dispose() {} - _IPdfPrimitive _clone(_PdfCrossTable crossTable) => null; + _IPdfPrimitive? _clone(_PdfCrossTable crossTable) => null; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_wrapper.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_wrapper.dart index 9148abb3f..2f37a992a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_wrapper.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_wrapper.dart @@ -3,5 +3,5 @@ part of pdf; /// Defines the basic interface of the various Wrapper. class _IPdfWrapper { /// Get the primitive element. - _IPdfPrimitive _element; + _IPdfPrimitive? _element; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_writer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_writer.dart index 071e33e6f..ccb0851c8 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_writer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/interfaces/pdf_writer.dart @@ -2,9 +2,9 @@ part of pdf; class _IPdfWriter { //ignore:unused_field - int _position; + int? _position; //ignore:unused_field - int _length; - PdfDocument _document; + int? _length; + PdfDocument? _document; void _write(dynamic pdfObject) {} } diff --git a/packages/syncfusion_flutter_pdf/pubspec.yaml b/packages/syncfusion_flutter_pdf/pubspec.yaml index 53ae59d51..15c826563 100644 --- a/packages/syncfusion_flutter_pdf/pubspec.yaml +++ b/packages/syncfusion_flutter_pdf/pubspec.yaml @@ -1,15 +1,18 @@ name: syncfusion_flutter_pdf -description: Syncfusion Flutter PDF is a library written natively in Dart for creating PDF documents from scratch. -version: 18.1.36-beta -homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdf +description: The Flutter PDF is a library written natively in Dart for creating, reading, editing, and securing PDF files in Android, iOS, and web platforms. +version: 19.1.54-beta +homepage: https://github.com/syncfusion/flutter-examples environment: - sdk: '>=2.1.0 <3.0.0' + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: sdk: flutter - intl: ">=0.15.0 <0.17.0" - xml: ^4.5.1 + intl: ">=0.15.0 <0.20.0" + xml: ">=4.5.1 <6.0.0" syncfusion_flutter_core: path: ../syncfusion_flutter_core + crypto: ">=2.1.5 <4.0.0" + convert: ">=1.0.0 <4.0.0" + diff --git a/packages/syncfusion_flutter_pdf/test/pdf_test.dart b/packages/syncfusion_flutter_pdf/test/pdf_test.dart new file mode 100644 index 000000000..3f50f0f5d --- /dev/null +++ b/packages/syncfusion_flutter_pdf/test/pdf_test.dart @@ -0,0 +1,67 @@ +import 'package:syncfusion_flutter_pdf/pdf.dart'; + +void main() { + pageSettings(); + pdfPrimitives(); + pdfDocument(); + pdfCatalog(); + pdfPage(); + pdfStandardFont(); + pdfCjkStandardFont(); + drawStringSupport(); + drawShapeSupport(); + pdfBrushes(); + drawTextElement(); + colorWithOpacity(); + pdfSection(); + headerAndFooter(); + pdfTrueTypeFont(); + pdfList(); + pdfImage(); + pdfTransparency(); + pdfRtl(); + hyperLink(); + pdfBookmark(); + pdfGrid(); + shapes(); + sampleBrowserSamples(); + userGuideSamples(); + builtInStyle(); + contentParser(); + preservationTest(); + pdfParsing(); + crossReferenceStream(); + fontStructure(); + xObjectElementTest(); + extractText(); + pdfCoverage(); + pdfAnnotation(); + existingBookmark(); + uriAnnotation(); + saveDocument(); + shapeAnnotations(); + textWeblinkAnnotation(); + extractTextWithBounds(); + findTextFromExistingPdf(); + extractTextWithFormat(); + createPdfTemplate(); + encryptionTest(); + layers(); + pdfIssues(); + layersForParsing(); + pdfConformance(); + nestedLayers(); + removePageLayer(); + removeNestedLayers(); + pdfAttachments(); + pdfFields(); + checkBoxField(); + radioButtonField(); + buttonField(); + loadedCheckBoxField(); + loadedRadioButtonField(); + signatureField(); + digitalSignatureTest(); + externalSigner(); + fieldItemSupport(); +} diff --git a/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md b/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md index 1ce274b64..bc8cac96a 100644 --- a/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md +++ b/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md @@ -2,6 +2,29 @@ **Features** +* The Web platform support has been provided. +* Support to view the rotated PDF documents in the iOS platform has been provided. + +## [18.4.48-beta] - 03/16/2021 + +* Now, the `computeDryLayout` has been implemented and SfPdfViewer widget will be compatible in all channels of Flutter SDK. + +## [18.4.42-beta] - 02/09/2021 + +**Breaking changes** + +* Now, the text selection color and handle color can be customized using `selectionColor` and `selectionHandleColor` properties of `TextSelectionTheme` respectively. + +## [18.4.31-beta] - 12/22/2020 + +**Features** + +* Now, the highlighted search instance in the zoomed document will be navigate properly. + +## [18.4.30-beta] - 12/17/2020 + +**Features** + * Text Search - Select text presented in a PDF document. * Text Selection - Search for text and navigate to all its occurrences in a PDF document instantly. * Document Link Annotation - Navigate to the desired topic or position by tapping the document link annotation of the topics in the table of contents in a PDF document. diff --git a/packages/syncfusion_flutter_pdfviewer/README.md b/packages/syncfusion_flutter_pdfviewer/README.md index 804991c3f..76eb115fd 100644 --- a/packages/syncfusion_flutter_pdfviewer/README.md +++ b/packages/syncfusion_flutter_pdfviewer/README.md @@ -1,10 +1,10 @@ -![syncfusion_flutter_pdfviewer](https://cdn.syncfusion.com/content/images/pdfviewer-banner.png) +![syncfusion_flutter_pdfviewer](https://cdn.syncfusion.com/content/images/pdfviewer-banner.png) -# Syncfusion Flutter PDF Viewer +# Flutter PDF Viewer library -The Syncfusion Flutter PDF Viewer widget lets you view the PDF documents seamlessly and efficiently in the Android and iOS platforms. It has highly interactive and customizable features such as magnification, virtual scrolling, page navigation, and bookmark navigation. +The Flutter PDF Viewer plugin lets you view the PDF documents seamlessly and efficiently in the Android, iOS and Web platforms. It has highly interactive and customizable features such as magnification, virtual scrolling, page navigation, text selection, text search, document link navigation, and bookmark navigation. -**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or Syncfusion Community License. For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. +**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. ## Table of contents - [PDF Viewer features](#pdf-viewer-features) @@ -19,6 +19,9 @@ The Syncfusion Flutter PDF Viewer widget lets you view the PDF documents seamles - [Change the zoom level factor](#change-the-zoom-level-factor) - [Navigate to the desired pages](#navigate-to-the-desired-pages) - [Navigate to the desired bookmark topics](#navigate-to-the-desired-bookmark-topics) + - [Select and copy text](#select-and-copy-text) + - [Search text and navigate to its occurrences](#search-text-and-navigate-to-its-occurrences) + - [Enable or disable the document link annotation](#enable-or-disable-the-document-link-annotation) - [Support and feedback](#support-and-feedback) - [About Syncfusion](#about-syncfusion) @@ -31,9 +34,17 @@ The Syncfusion Flutter PDF Viewer widget lets you view the PDF documents seamles * **Page navigation** - Navigate to the desired pages instantly. ![syncfusion_flutter_pdfviewer_page_navigation](https://cdn.syncfusion.com/content/images/PDFViewer/pagination-dialog.png) +* **Text selection** - Select text presented in a PDF document. +![syncfusion_flutter_pdfviewer_text_selection](https://cdn.syncfusion.com/content/images/PDFViewer/text-selection.png) + +* **Text search** - Search for text and navigate to all its occurrences in a PDF document instantly. +![syncfusion_flutter_pdfviewer_text_search](https://cdn.syncfusion.com/content/images/PDFViewer/text-search.png) + * **Bookmark navigation** - Bookmarks saved in the document are loaded and made ready for easy navigation. This feature helps in navigation within the PDF document of the topics bookmarked already. ![syncfusion_flutter_pdfviewer_bookmark_navigation](https://cdn.syncfusion.com/content/images/PDFViewer/bookmark-navigation.png) +* **Document link annotation** - Navigate to the desired topic or position by tapping the document link annotation of the topics in the table of contents in a PDF document. + * **Themes** - Easily switch between the light and dark theme. ![syncfusion_flutter_pdfviewer_theme](https://cdn.syncfusion.com/content/images/PDFViewer/bookmark-navigation-dark.png) @@ -254,6 +265,150 @@ Widget build(BuildContext context) { } ``` +## Select and copy text + +By default, the PDF Viewer provides selection support for text in a PDF document. You can enable or disable selection using the **enableTextSelection** property. Whenever selection is changed, an **onTextSelectionChanged** callback is triggered with global selection region and selected text details. Based on these details, a context menu can be shown and a copy of the text can be performed. + +```dart +PdfViewerController _pdfViewerController; + +@override +void initState() { + _pdfViewerController = PdfViewerController(); + super.initState(); +} + +OverlayEntry _overlayEntry; +void _showContextMenu(BuildContext context,PdfTextSelectionChangedDetails details) { + final OverlayState _overlayState = Overlay.of(context); + _overlayEntry = OverlayEntry( + builder: (context) => Positioned( + top: details.globalSelectedRegion.center.dy - 55, + left: details.globalSelectedRegion.bottomLeft.dx, + child: + RaisedButton(child: Text('Copy',style: TextStyle(fontSize: 17)),onPressed: (){ + Clipboard.setData(ClipboardData(text: details.selectedText)); + _pdfViewerController.clearSelection(); + },color: Colors.white,elevation: 10,), + ), + ); + _overlayState.insert(_overlayEntry); +} +@override +Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Syncfusion Flutter PdfViewer'), + ), + body: SfPdfViewer.network( + 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', + onTextSelectionChanged: + (PdfTextSelectionChangedDetails details) { + if (details.selectedText == null && _overlayEntry != null) { + _overlayEntry.remove(); + _overlayEntry = null; + } else if (details.selectedText != null && _overlayEntry == null) { + _showContextMenu(context, details); + } + }, + controller: _pdfViewerController, + ), + ); +} +``` + +## Search text and navigate to its occurrences + +Text can be searched for in a PDF document and you can then navigate to all its occurrences. The navigation of searched text can be controlled using the **nextInstance**, **previousInstance**, and **clear** methods. + +```dart +PdfViewerController _pdfViewerController; + +@override +void initState() { + _pdfViewerController = PdfViewerController(); + super.initState(); +} +PdfTextSearchResult _searchResult; +@override +Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Syncfusion Flutter PdfViewer'), + actions: [ + IconButton( + icon: Icon( + Icons.search, + color: Colors.white, + ), + onPressed: () async { + _searchResult = await _pdfViewerController?.searchText('the', + searchOption: TextSearchOption.caseSensitive); + setState(() {}); + }, + ), + Visibility( + visible: _searchResult?.hasResult ?? false, + child: IconButton( + icon: Icon( + Icons.clear, + color: Colors.white, + ), + onPressed: () { + setState(() { + _searchResult.clear(); + }); + }, + ), + ), + Visibility( + visible: _searchResult?.hasResult ?? false, + child: IconButton( + icon: Icon( + Icons.keyboard_arrow_up, + color: Colors.white, + ), + onPressed: () { + _searchResult?.previousInstance(); + }, + ), + ), + Visibility( + visible: _searchResult?.hasResult ?? false, + child: IconButton( + icon: Icon( + Icons.keyboard_arrow_down, + color: Colors.white, + ), + onPressed: () { + _searchResult?.nextInstance(); + }, + ), + ), + ], + ), + body: SfPdfViewer.network( + 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', + controller:_pdfViewerController, + searchTextHighlightColor: Colors.yellow)); +} +``` + +## Enable or disable the document link annotation + +By default, the PDF Viewer will navigate to the document link annotation’s destination position when you tap on the document link annotation. You can enable or disable the navigation of document link annotation using the **enableDocumentLinkAnnotation** property. + +```dart +@override +Widget build(BuildContext context) { + return Scaffold( + body: Container( + child: SfPdfViewer.network( + 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', + enableDocumentLinkAnnotation: false))); +} +``` + ## Support and Feedback * For any other queries, reach our [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident) or post the queries through the [Community forums](https://www.syncfusion.com/forums) and submit a feature request or a bug through our [Feedback portal](https://www.syncfusion.com/feedback/flutter). diff --git a/packages/syncfusion_flutter_pdfviewer/android/src/main/java/com/syncfusion/flutter/pdfviewer/SyncfusionFlutterPdfViewerPlugin.java b/packages/syncfusion_flutter_pdfviewer/android/src/main/java/com/syncfusion/flutter/pdfviewer/SyncfusionFlutterPdfViewerPlugin.java index 35c6889d6..ebbb416d5 100644 --- a/packages/syncfusion_flutter_pdfviewer/android/src/main/java/com/syncfusion/flutter/pdfviewer/SyncfusionFlutterPdfViewerPlugin.java +++ b/packages/syncfusion_flutter_pdfviewer/android/src/main/java/com/syncfusion/flutter/pdfviewer/SyncfusionFlutterPdfViewerPlugin.java @@ -14,9 +14,10 @@ import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.io.OutputStream; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -40,14 +41,14 @@ public class SyncfusionFlutterPdfViewerPlugin implements FlutterPlugin, MethodCa /// Number of pages in the PDF private int pageCount; private Result resultPdf; + private File file; /// PdfRenderer instance private PdfRenderer renderer; /// Initial File descriptor ParcelFileDescriptor fileDescriptor; - /// PDF document path. - String pdfPath; /// PDF Runnable PdfRunnable bitmapRunnable; + boolean reinitializePdf=false; @Override public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "syncfusion_flutter_pdfviewer"); @@ -66,23 +67,23 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBindin @SuppressWarnings("deprecation") public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), "syncfusion_flutter_pdfviewer"); - channel.setMethodCallHandler(new SyncfusionFlutterPdfViewerPlugin()); + channel.setMethodCallHandler(new SyncfusionFlutterPdfViewerPlugin()); } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) @Override public void onMethodCall(@NonNull final MethodCall call, @NonNull final Result result) { resultPdf = result; - if (call.method.equals("getimage")) { - getImage((int) call.argument("index"), (String) call.argument("path")); - } else if (call.method.equals("initializepdfrenderer")) { - result.success(initializePdfRenderer((String) call.arguments)); - } else if (call.method.equals("getpageswidth")) { - result.success(getPagesWidth((String) call.arguments)); - } else if (call.method.equals("getpagesheight")) { - result.success(getPagesHeight((String) call.arguments)); - } else if (call.method.equals("dispose")) { - result.success(dispose()); + if (call.method.equals("getImage")) { + getImage((int) call.argument("index")); + } else if (call.method.equals("initializePdfRenderer")) { + result.success(initializePdfRenderer((byte[]) call.arguments)); + } else if (call.method.equals("getPagesWidth")) { + result.success(getPagesWidth()); + } else if (call.method.equals("getPagesHeight")) { + result.success(getPagesHeight()); + } else if (call.method.equals("closeDocument")) { + result.success(closeDocument()); } else { result.notImplemented(); } @@ -96,43 +97,47 @@ public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { // Initializes the PDF Renderer and returns the page count. @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - String initializePdfRenderer(String path) { - File file = new File(path); + String initializePdfRenderer(byte[] path) { try { + file = File.createTempFile( + ".syncfusion", ".pdf" + ); + OutputStream stream = new FileOutputStream(file); + stream.write(path); + stream.close(); fileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); renderer = new PdfRenderer(fileDescriptor); pageCount = renderer.getPageCount(); - pdfPath = path; return String.valueOf(pageCount); } catch (Exception e) { return e.toString(); } } + // Reinitialize the PDF Renderer @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - void reinitializePdfRenderer(String path) throws IOException { - if (pdfPath == null || pdfPath.compareTo(path) != 0) + void reinitializePdfRenderer() { + if(renderer == null) + { try { - File file = new File(path); - ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + reinitializePdf=true; + fileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); renderer = new PdfRenderer(fileDescriptor); - pageCount = renderer.getPageCount(); - pdfPath = path; - pageHeight = null; - pageWidth = null; } catch (Exception e) { - throw e; + e.printStackTrace(); } + } } // Returns the height collection of rendered pages. @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - double[] getPagesHeight(String path) { + double[] getPagesHeight() { + reinitializePdfRenderer(); try { - reinitializePdfRenderer(path); - pageHeight = new double[pageCount]; - pageWidth = new double[pageCount]; - for (int i = 0; i < pageCount; i++) { + int count = renderer.getPageCount(); + pageHeight = new double[count]; + pageWidth = new double[count]; + for (int i = 0; i < count; i++) { PdfRenderer.Page page = renderer.openPage(i); pageHeight[i] = page.getHeight(); pageWidth[i] = page.getWidth(); @@ -146,9 +151,9 @@ void reinitializePdfRenderer(String path) throws IOException { // Returns the width collection of rendered pages. @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - double[] getPagesWidth(String path) { + double[] getPagesWidth() { + reinitializePdfRenderer(); try { - reinitializePdfRenderer(path); if (pageWidth == null) { int count = renderer.getPageCount(); pageWidth = new double[count]; @@ -166,9 +171,9 @@ void reinitializePdfRenderer(String path) throws IOException { // Gets the specific page from PdfRenderer @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - void getImage(int pageIndex, String path) { + void getImage(int pageIndex) { + reinitializePdfRenderer(); try { - reinitializePdfRenderer(path); ExecutorService executor = Executors.newCachedThreadPool(); bitmapRunnable = new PdfRunnable(renderer, resultPdf, pageIndex); executor.submit(bitmapRunnable); @@ -178,22 +183,27 @@ void getImage(int pageIndex, String path) { } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - boolean dispose() { - if (pageWidth != null) - pageWidth = null; - if (pageHeight != null) - pageHeight = null; - if (renderer != null) { - bitmapRunnable.dispose(); - renderer.close(); - renderer = null; - } - pdfPath = null; - if (fileDescriptor != null) { - try { - fileDescriptor.close(); - } catch (IOException e) { - return false; + boolean closeDocument() { + if(!reinitializePdf) { + if (pageWidth != null) + pageWidth = null; + if (pageHeight != null) + pageHeight = null; + if (bitmapRunnable != null) { + bitmapRunnable.dispose(); + reinitializePdf=false; + bitmapRunnable = null; + } + if (renderer != null) { + renderer.close(); + renderer = null; + } + if (fileDescriptor != null) { + try { + fileDescriptor.close(); + } catch (IOException e) { + return false; + } } } return true; @@ -203,52 +213,51 @@ boolean dispose() { /// This runnable executes all the image fetch in separate thread. class PdfRunnable implements Runnable { - private List bitmaps = null; - private PdfRenderer renderer; - private Result resultPdf; - private int pageIndex; - private PdfRenderer.Page page; - - PdfRunnable(PdfRenderer renderer, Result resultPdf, int pageIndex) { - this.resultPdf = resultPdf; - this.renderer = renderer; - this.pageIndex = pageIndex; - } + private byte[] imageBytes = null; + private PdfRenderer renderer; + private Result resultPdf; + private int pageIndex; + private PdfRenderer.Page page; - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public void dispose() + PdfRunnable(PdfRenderer renderer, Result resultPdf, int pageIndex) { + this.resultPdf = resultPdf; + this.renderer = renderer; + this.pageIndex = pageIndex; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void dispose() + { + imageBytes = null; + if(page != null) { - bitmaps = null; - if(page != null) - { - page.close(); - page = null; - } + page.close(); + page = null; } + } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public void run() { - page = renderer.openPage(pageIndex - 1); - int width = (int) (page.getWidth() * 1.75); - int height = (int) (page.getHeight() * 1.75); - final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - bitmap.eraseColor(Color.WHITE); - final Rect rect = new Rect(0, 0, width, height); - page.render(bitmap, rect, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); - page.close(); - page = null; - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream); - bitmaps = new ArrayList<>(); - bitmaps.add(outStream.toByteArray()); - synchronized (this) { - notifyAll(); - } - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - resultPdf.success(bitmaps); - } - }); + page = renderer.openPage(pageIndex - 1); + int width = (int) (page.getWidth() * 1.75); + int height = (int) (page.getHeight() * 1.75); + final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.eraseColor(Color.WHITE); + final Rect rect = new Rect(0, 0, width, height); + page.render(bitmap, rect, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); + page.close(); + page = null; + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream); + imageBytes = outStream.toByteArray(); + synchronized (this) { + notifyAll(); } + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + resultPdf.success(imageBytes); + } + }); + } } \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer/example/analysis_options.yaml b/packages/syncfusion_flutter_pdfviewer/example/analysis_options.yaml new file mode 100644 index 000000000..04ff6cfa6 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:syncfusion_flutter_core/analysis_options.yaml + +analyzer: + errors: + lines_longer_than_80_chars: ignore + include_file_not_found: ignore + uri_does_not_exist: ignore \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer/example/pubspec.yaml b/packages/syncfusion_flutter_pdfviewer/example/pubspec.yaml index bfb210ad2..017eb8af6 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/pubspec.yaml +++ b/packages/syncfusion_flutter_pdfviewer/example/pubspec.yaml @@ -6,7 +6,7 @@ description: Demonstrates how to use the syncfusion_flutter_pdfviewer plugin. publish_to: 'none' # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=2.7.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: diff --git a/packages/syncfusion_flutter_pdfviewer/example/web/favicon.png b/packages/syncfusion_flutter_pdfviewer/example/web/favicon.png new file mode 100644 index 000000000..8aaa46ac1 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/example/web/favicon.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/web/icons/Icon-192.png b/packages/syncfusion_flutter_pdfviewer/example/web/icons/Icon-192.png new file mode 100644 index 000000000..b749bfef0 Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/example/web/icons/Icon-192.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/web/icons/Icon-512.png b/packages/syncfusion_flutter_pdfviewer/example/web/icons/Icon-512.png new file mode 100644 index 000000000..88cfd48df Binary files /dev/null and b/packages/syncfusion_flutter_pdfviewer/example/web/icons/Icon-512.png differ diff --git a/packages/syncfusion_flutter_pdfviewer/example/web/index.html b/packages/syncfusion_flutter_pdfviewer/example/web/index.html new file mode 100644 index 000000000..1523a4222 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/web/index.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + example + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_pdfviewer/example/web/manifest.json b/packages/syncfusion_flutter_pdfviewer/example/web/manifest.json new file mode 100644 index 000000000..0692e96ac --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "syncfusion_flutter_pdfviewer example", + "short_name": "syncfusion_flutter_pdfviewer", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "An example of the syncfusion_flutter_pdfviewer on the web.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift b/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift index 8f4c0f787..0161e5f31 100644 --- a/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift +++ b/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift @@ -3,7 +3,7 @@ import UIKit // SyncfusionFlutterPdfViewerPlugin public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { - + // Instance of CGPDFDocument var document : CGPDFDocument? // Width collection of rendered pages @@ -13,157 +13,136 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { // Number of pages in PDF document var pageCount: NSNumber // PDF path - var url: URL! - + var bytes: FlutterStandardTypedData! + // Initializes the SyncfusionFlutterPdfViewerPlugin override init(){ pagesWidth = Array() pagesHeight = Array() pageCount = NSNumber(0) document = nil - url = nil + bytes = nil } - private func dispose() + private func closeDocument() { self.document = nil self.pageCount = NSNumber(0) self.pagesHeight.removeAll() self.pagesWidth.removeAll() - self.url = nil + self.bytes = nil } - - // Registers the SyncfusionFlutterPdfViewerPlugin - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "syncfusion_flutter_pdfviewer", binaryMessenger: registrar.messenger()) - let instance = SwiftSyncfusionFlutterPdfViewerPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - // Invokes the method call operations. - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - if (call.method == "getimages") { - getImages(call:call,result:result) - } - else if(call.method == "initializepdfrenderer") - { - initializePdfRenderer(call:call,result:result); - } - else if(call.method == "getimage") - { - getImage(call:call,result:result) - } - else if(call.method == "getpageswidth") - { - getPagesWidth(call:call,result:result) - } - else if(call.method == "getpagesheight") - { - getPagesHeight(call:call,result:result) - } - else if(call.method == "dispose") - { - dispose() + + // Registers the SyncfusionFlutterPdfViewerPlugin + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "syncfusion_flutter_pdfviewer", binaryMessenger: registrar.messenger()) + let instance = SwiftSyncfusionFlutterPdfViewerPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) } - } - // Initializes the PDF Renderer and returns the page count. - private func initializePdfRenderer( call: FlutterMethodCall, result: @escaping FlutterResult) - { - self.url = URL(fileURLWithPath: call.arguments as! String) - self.document = CGPDFDocument(url as CFURL) - self.pageCount = NSNumber(value: self.document!.numberOfPages) - result(self.pageCount.stringValue); + // Invokes the method call operations. + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + if(call.method == "initializePdfRenderer") + { + initializePdfRenderer(call:call,result:result) + } + else if(call.method == "getImage") + { + getImage(call:call,result:result) + } + else if(call.method == "getPagesWidth") + { + getPagesWidth(call:call,result:result) + } + else if(call.method == "getPagesHeight") + { + getPagesHeight(call:call,result:result) + } + else if(call.method == "closeDocument") + { + closeDocument() + } } - // Reinitailize PDF Renderer if the given document URL is different from current document URL - private func reinitializePdfRenderer(path : URL) + // Initializes the PDF Renderer and returns the page count. + private func initializePdfRenderer( call: FlutterMethodCall, result: @escaping FlutterResult) { - if(self.url == nil || self.url.absoluteString != path.absoluteString) - { - self.url = path - self.document = CGPDFDocument(url as CFURL)! - self.pageCount = NSNumber(value: self.document!.numberOfPages) - } + self.bytes = call.arguments as? FlutterStandardTypedData + let byte = [UInt8](self.bytes.data) + let cfData = CFDataCreate(nil, byte, byte.count)! + let dataProvider = CGDataProvider(data: cfData)! + self.document = CGPDFDocument(dataProvider) + self.pageCount = NSNumber(value: self.document!.numberOfPages) + result(self.pageCount.stringValue); } - // Gets the pdf pages images from the specified page - private func getImages( call: FlutterMethodCall, result: @escaping FlutterResult) + // Returns the width collection of rendered pages. + private func getPagesWidth( call: FlutterMethodCall, result: @escaping FlutterResult) { - self.pagesWidth.removeAll() - self.pagesHeight.removeAll() - var bitmaps = [FlutterStandardTypedData]() - guard let argument = call.arguments else {return} - let args = argument as? [String: Any] - let path = args!["path"] as? String - reinitializePdfRenderer(path: URL(fileURLWithPath: path!)) - let startIndex = args!["firstPage"] as? Int - let endIndex = args!["lastPage"] as? Int - for index in stride(from: startIndex!,to: endIndex!+1, by: 1){ - bitmaps.append(getImageForPlugin(index: index)) + pagesWidth = Array() + for index in stride(from: 1,to: self.pageCount.intValue + 1, by: 1){ + let page = self.document!.page(at: Int(index)) + var pageRect = page!.getBoxRect(.mediaBox) + if(page!.rotationAngle > 0) + { + let angle = CGFloat(page!.rotationAngle) * CGFloat.pi/180 + pageRect = (pageRect.applying(CGAffineTransform(rotationAngle: angle))) + } + self.pagesWidth.append(Double(pageRect.width)) } - result(bitmaps); + result(pagesWidth) } - private func getImageForPlugin(index: Int) -> FlutterStandardTypedData + + // Returns the height collection of rendered pages. + private func getPagesHeight( call: FlutterMethodCall, result: @escaping FlutterResult) { - let page = self.document!.page(at: Int(index)) - let pageRect = page!.getBoxRect(.mediaBox) - self.pagesWidth.append(Double(pageRect.width)) - self.pagesHeight.append(Double(pageRect.height)) - if #available(iOS 10.0, *) { - let renderer = UIGraphicsImageRenderer(size: pageRect.size) - let img = renderer.image { ctx in - UIColor.white.set() - ctx.fill(pageRect) - - ctx.cgContext.translateBy(x: 0.0, y: pageRect.size.height) - ctx.cgContext.scaleBy(x: 1.0, y: -1.0) - - ctx.cgContext.drawPDFPage(page!) - } - return FlutterStandardTypedData(bytes: img.pngData()!) - } else { - return FlutterStandardTypedData() + pagesHeight = Array() + for index in stride(from: 1,to: self.pageCount.intValue + 1, by: 1){ + let page = self.document!.page(at: Int(index)) + var pageRect = page!.getBoxRect(.mediaBox) + if(page!.rotationAngle > 0) + { + let angle = CGFloat(page!.rotationAngle) * CGFloat.pi/180 + pageRect = (pageRect.applying(CGAffineTransform(rotationAngle: angle))) + } + self.pagesHeight.append(Double(pageRect.height)) } + result(pagesHeight) } + // Gets the pdf page image from the specified page - private func getImage( call: FlutterMethodCall, result: @escaping FlutterResult) - { - self.pagesWidth.removeAll() - self.pagesHeight.removeAll() - var bitmaps = [FlutterStandardTypedData]() + private func getImage( call: FlutterMethodCall, result: @escaping FlutterResult) + { guard let argument = call.arguments else {return} let args = argument as? [String: Any] - let path = args!["path"] as? String let index = args!["index"] as? Int - reinitializePdfRenderer(path: URL(fileURLWithPath: path!)) - bitmaps.append(getImageForPlugin(index: index!)) - result(bitmaps); - } - - // Returns the width collection of rendered pages. - private func getPagesWidth( call: FlutterMethodCall, result: @escaping FlutterResult) - { - reinitializePdfRenderer(path: URL(fileURLWithPath: call.arguments as! String)) - pagesWidth = Array() - for index in stride(from: 1,to: self.pageCount.intValue + 1, by: 1){ - let page = self.document!.page(at: Int(index)) - let pageRect = page!.getBoxRect(.mediaBox) - self.pagesWidth.append(Double(pageRect.width)) - } - result(pagesWidth) + result(getImageForPlugin(index: index!)) } - - // Returns the height collection of rendered pages. - private func getPagesHeight( call: FlutterMethodCall, result: @escaping FlutterResult) - { - reinitializePdfRenderer(path: URL(fileURLWithPath: call.arguments as! String)) - pagesHeight = Array() - for index in stride(from: 1,to: self.pageCount.intValue + 1, by: 1){ - let page = self.document!.page(at: Int(index)) - let pageRect = page!.getBoxRect(.mediaBox) - self.pagesHeight.append(Double(pageRect.height)) + + private func getImageForPlugin(index: Int) -> FlutterStandardTypedData + { + if(self.document != nil) + { + let page = self.document!.page(at: Int(index)) + var pageRect = page!.getBoxRect(.mediaBox) + if #available(iOS 10.0, *) { + let renderer = UIGraphicsImageRenderer(size: pageRect.size) + + let img = renderer.image { ctx in + + let mediaBox = page!.getBoxRect(.mediaBox) + ctx.cgContext.beginPage(mediaBox: &pageRect) + let transform = page!.getDrawingTransform(.mediaBox, rect: mediaBox, rotate: 0, preserveAspectRatio: true) + ctx.cgContext.translateBy(x: 0.0, y: mediaBox.size.height) + ctx.cgContext.scaleBy(x: 1, y: -1) + ctx.cgContext.concatenate(transform) + + ctx.cgContext.drawPDFPage(page!) + ctx.cgContext.endPage() + } + return FlutterStandardTypedData(bytes: img.pngData()!) + } } - result(pagesHeight) + return FlutterStandardTypedData() } } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/pdfviewer.dart b/packages/syncfusion_flutter_pdfviewer/lib/pdfviewer.dart index 7dd8e4eaa..88ab32027 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/pdfviewer.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/pdfviewer.dart @@ -1,4 +1,6 @@ library pdfviewer; +export 'src/control/enums.dart'; +export 'src/control/pdftextline.dart'; export 'src/control/pdfviewer_callback_details.dart'; export 'src/pdfviewer.dart'; diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_item.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_item.dart index 12fda296e..47100d079 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_item.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_item.dart @@ -1,4 +1,6 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/theme.dart'; /// Width of the back icon in the bookmark. @@ -40,17 +42,17 @@ const double _kPdfExpandIconRightPosition = 16.0; /// A material design bookmark. class BookmarkItem extends StatefulWidget { /// Creates a material design bookmark. - BookmarkItem({ - this.title = '', - this.height = 48, - this.onNavigate, - this.onExpandPressed, - this.onBackPressed, - this.textPosition = 16, - this.isBorderEnabled = false, - this.isExpandIconVisible = false, - this.isBackIconVisible = false, - }); + BookmarkItem( + {this.title = '', + this.height = 48, + required this.onNavigate, + required this.onExpandPressed, + required this.onBackPressed, + this.textPosition = 16, + this.isBorderEnabled = false, + this.isExpandIconVisible = false, + this.isBackIconVisible = false, + required this.isMobileWebView}); /// Title for the bookmark. final String title; @@ -89,19 +91,22 @@ class BookmarkItem extends StatefulWidget { /// This triggers when bookmark back button in the bookmark is tapped. final GestureTapCallback onBackPressed; + /// If true,MobileWebView is enabled.Default value is false. + final bool isMobileWebView; + @override _BookmarkItemState createState() => _BookmarkItemState(); } /// State for a [BookmarkItem] class _BookmarkItemState extends State { - Color _color; - SfPdfViewerThemeData _pdfViewerThemeData; + late Color _color; + SfPdfViewerThemeData? _pdfViewerThemeData; @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); - _color = _pdfViewerThemeData.bookmarkViewStyle.backgroundColor; + _color = _pdfViewerThemeData!.bookmarkViewStyle.backgroundColor!; super.didChangeDependencies(); } @@ -112,28 +117,30 @@ class _BookmarkItemState extends State { } void _handleBackToParent() { - _color = _pdfViewerThemeData.bookmarkViewStyle.backgroundColor; + _color = _pdfViewerThemeData!.bookmarkViewStyle.backgroundColor!; widget.onBackPressed(); } void _handleExpandBookmarkList() { - _color = _pdfViewerThemeData.bookmarkViewStyle.backgroundColor; + _color = _pdfViewerThemeData!.bookmarkViewStyle.backgroundColor!; widget.onExpandPressed(); } void _handleTap() { - if (widget.onNavigate != null) { - try { - widget.onNavigate(); - } catch (e) { - _handleCancelSelectionColor(); - } + try { + widget.onNavigate(); + } catch (e) { + _handleCancelSelectionColor(); } } void _handleTapDown(TapDownDetails details) { setState(() { - _color = _pdfViewerThemeData.bookmarkViewStyle.selectionColor; + if (kIsWeb && !widget.isMobileWebView) { + _color = Color(0xFF000000).withOpacity(0.08); + } else { + _color = _pdfViewerThemeData!.bookmarkViewStyle.selectionColor!; + } }); } @@ -143,13 +150,13 @@ class _BookmarkItemState extends State { void _handleCancelSelectionColor() { setState(() { - _color = _pdfViewerThemeData.bookmarkViewStyle.backgroundColor; + _color = _pdfViewerThemeData!.bookmarkViewStyle.backgroundColor!; }); } @override Widget build(BuildContext context) { - return GestureDetector( + final Widget bookmarkItem = GestureDetector( onTap: _handleTap, onTapDown: _handleTapDown, onLongPress: _handleCancelSelectionColor, @@ -162,8 +169,8 @@ class _BookmarkItemState extends State { ? BoxDecoration( border: Border( bottom: BorderSide( - color: _pdfViewerThemeData - .bookmarkViewStyle.titleSeparatorColor, + color: _pdfViewerThemeData! + .bookmarkViewStyle.titleSeparatorColor!, ), ), ) @@ -178,12 +185,12 @@ class _BookmarkItemState extends State { height: _kPdfBackIconHeight, width: _kPdfBackIconWidth, child: RawMaterialButton( + onPressed: _handleBackToParent, child: Icon( Icons.arrow_back, size: _kPdfBackIconSize, - color: _pdfViewerThemeData.bookmarkViewStyle.backIconColor, + color: _pdfViewerThemeData!.bookmarkViewStyle.backIconColor, ), - onPressed: _handleBackToParent, ), ), ), @@ -194,7 +201,7 @@ class _BookmarkItemState extends State { child: Text( widget.title, overflow: TextOverflow.ellipsis, - style: _pdfViewerThemeData.bookmarkViewStyle.titleTextStyle, + style: _pdfViewerThemeData!.bookmarkViewStyle.titleTextStyle, ), ), Visibility( @@ -205,13 +212,13 @@ class _BookmarkItemState extends State { height: _kPdfExpandIconHeight, width: _kPdfExpandIconWidth, child: RawMaterialButton( + onPressed: _handleExpandBookmarkList, child: Icon( Icons.arrow_forward_ios, size: _kPdfExpandIconSize, - color: _pdfViewerThemeData + color: _pdfViewerThemeData! .bookmarkViewStyle.navigationIconColor, ), - onPressed: _handleExpandBookmarkList, ), ), ) @@ -219,5 +226,22 @@ class _BookmarkItemState extends State { ), ), ); + if (kIsWeb && !widget.isMobileWebView) { + return MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (details) { + setState(() { + _color = Color(0xFF000000).withOpacity(0.04); + }); + }, + onExit: (details) { + setState(() { + _color = _pdfViewerThemeData!.bookmarkViewStyle.backgroundColor!; + }); + }, + child: bookmarkItem, + ); + } + return bookmarkItem; } } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_toolbar.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_toolbar.dart index d7a3f07be..25ee521ff 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_toolbar.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_toolbar.dart @@ -32,20 +32,21 @@ const double _kPdfCloseIconRightPosition = 16.0; /// A material design bookmark toolbar. class BookmarkToolbar extends StatefulWidget { /// Creates a material design bookmark toolbar. - BookmarkToolbar({this.onCloseButtonPressed}); + BookmarkToolbar(this.onCloseButtonPressed); /// A tap with a close button is occurred. /// /// This triggers when close button in bookmark toolbar is tapped. final GestureTapCallback onCloseButtonPressed; + @override State createState() => _BookmarkToolbarState(); } /// State for [BookmarkToolbar] class _BookmarkToolbarState extends State { - SfPdfViewerThemeData _pdfViewerThemeData; - SfLocalizations _localizations; + SfPdfViewerThemeData? _pdfViewerThemeData; + SfLocalizations? _localizations; @override void didChangeDependencies() { @@ -67,7 +68,7 @@ class _BookmarkToolbarState extends State { height: _kPdfHeaderBarHeight, margin: EdgeInsets.only(bottom: 3), decoration: BoxDecoration( - color: _pdfViewerThemeData.bookmarkViewStyle.headerBarColor, + color: _pdfViewerThemeData!.bookmarkViewStyle.headerBarColor, boxShadow: [ BoxShadow( color: Color.fromRGBO(0, 0, 0, 0.14), @@ -93,8 +94,8 @@ class _BookmarkToolbarState extends State { left: _kPdfHeaderTextLeftPosition, height: _kPdfHeaderTextHeight, child: Text( - _localizations.pdfBookmarksLabel, - style: _pdfViewerThemeData.bookmarkViewStyle.headerTextStyle, + _localizations!.pdfBookmarksLabel, + style: _pdfViewerThemeData!.bookmarkViewStyle.headerTextStyle, ), ), Positioned( @@ -103,14 +104,14 @@ class _BookmarkToolbarState extends State { height: _kPdfCloseIconHeight, width: _kPdfCloseIconWidth, child: RawMaterialButton( + onPressed: () { + widget.onCloseButtonPressed(); + }, child: Icon( Icons.close, size: _kPdfCloseIconSize, - color: _pdfViewerThemeData.bookmarkViewStyle.closeIconColor, + color: _pdfViewerThemeData!.bookmarkViewStyle.closeIconColor, ), - onPressed: () { - widget.onCloseButtonPressed(); - }, ), ), ], diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart index 1e3a081c5..42cc54397 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart @@ -1,4 +1,5 @@ import 'dart:math'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; import 'package:syncfusion_flutter_core/theme.dart'; @@ -20,13 +21,13 @@ const double _kPdfSubBookmarkTitlePosition = 55.0; /// BookmarkView of PdfViewer class BookmarkView extends StatefulWidget { /// BookmarkView Constructor. - BookmarkView({Key key, this.pdfDocument, this.controller}) : super(key: key); + BookmarkView(Key key, this.pdfDocument, this.controller) : super(key: key); /// [PdfViewerController] instance of PdfViewer. final PdfViewerController controller; /// [PdfDocument] instance of PDF library. - final PdfDocument pdfDocument; + final PdfDocument? pdfDocument; @override State createState() => BookmarkViewControllerState(); @@ -34,17 +35,17 @@ class BookmarkView extends StatefulWidget { /// State for [BookmarkView] class BookmarkViewControllerState extends State { - List _bookmarkList = []; - PdfBookmarkBase _bookmarkBase; - PdfBookmark _parentBookmark; - PdfBookmark _childBookmark; + List? _bookmarkList = []; + PdfBookmarkBase? _bookmarkBase; + PdfBookmark? _parentBookmark; + PdfBookmark? _childBookmark; bool _isExpanded = false; - double _totalWidth; - bool _isTablet; - int _listCount; - LocalHistoryEntry _historyEntry; - SfPdfViewerThemeData _pdfViewerThemeData; - SfLocalizations _localizations; + double? _totalWidth; + bool _isTablet = false; + int? _listCount; + LocalHistoryEntry? _historyEntry; + SfPdfViewerThemeData? _pdfViewerThemeData; + SfLocalizations? _localizations; /// If true, bookmark view is opened. bool showBookmark = false; @@ -58,6 +59,8 @@ class BookmarkViewControllerState extends State { @override void dispose() { + _bookmarkList!.clear(); + _bookmarkList = null; _pdfViewerThemeData = null; _localizations = null; super.dispose(); @@ -69,10 +72,10 @@ class BookmarkViewControllerState extends State { /// mobile back button. void _ensureHistoryEntry() { if (_historyEntry == null) { - final ModalRoute route = ModalRoute.of(context); + final ModalRoute? route = ModalRoute.of(context); if (route != null) { _historyEntry = LocalHistoryEntry(onRemove: _handleHistoryEntryRemoved); - route.addLocalHistoryEntry(_historyEntry); + route.addLocalHistoryEntry(_historyEntry!); } } } @@ -85,8 +88,8 @@ class BookmarkViewControllerState extends State { /// Opens the bookmark view. void open() { _ensureHistoryEntry(); - if (!showBookmark) { - _bookmarkBase = widget.pdfDocument.bookmarks; + if (!showBookmark && widget.pdfDocument != null) { + _bookmarkBase = widget.pdfDocument!.bookmarks; _bookmarkList = _populateBookmarkList(); } setState(() { @@ -113,7 +116,9 @@ class BookmarkViewControllerState extends State { final Size size = MediaQuery.of(context).size; final double diagonal = sqrt((size.width * size.width) + (size.height * size.height)); - _isTablet = diagonal > _kPdfStandardDiagonalOffset; + _isTablet = (kIsWeb && !(diagonal < _kPdfStandardDiagonalOffset)) + ? true + : diagonal > _kPdfStandardDiagonalOffset; } void _handleBackPress() { @@ -130,9 +135,9 @@ class BookmarkViewControllerState extends State { void _handleExpandPress(int index) { setState(() { - _parentBookmark = _isExpanded ? _childBookmark : _bookmarkBase[index]; + _parentBookmark = _isExpanded ? _childBookmark : _bookmarkBase![index]; _childBookmark = - _isExpanded ? _childBookmark[index] : _bookmarkBase[index]; + _isExpanded ? _childBookmark![index] : _bookmarkBase![index]; _isExpanded = true; _populateBookmarkList(); }); @@ -141,46 +146,51 @@ class BookmarkViewControllerState extends State { List _populateBookmarkList() { _bookmarkList?.clear(); if (_isExpanded) { - _bookmarkList.add(BookmarkItem( - title: _childBookmark.title, - isBackIconVisible: true, - textPosition: _kPdfSubBookmarkTitlePosition, - onBackPressed: _handleBackPress, - onNavigate: () { - widget.controller.jumpToBookmark(_childBookmark); - _handleClose(); - }, - isBorderEnabled: true)); + _bookmarkList?.add(BookmarkItem( + title: _childBookmark!.title, + isBackIconVisible: true, + textPosition: _kPdfSubBookmarkTitlePosition, + onBackPressed: _handleBackPress, + isMobileWebView: !_isTablet, + onNavigate: () { + widget.controller.jumpToBookmark(_childBookmark!); + _handleClose(); + }, + isBorderEnabled: true, + onExpandPressed: () {}, + )); } final int bookmarkListCount = - _isExpanded ? _childBookmark.count : _bookmarkBase.count; + _isExpanded ? _childBookmark!.count : _bookmarkBase!.count; for (int i = 0; i < bookmarkListCount; i++) { final BookmarkItem bookmarkItem = BookmarkItem( - title: _isExpanded ? _childBookmark[i].title : _bookmarkBase[i].title, + title: _isExpanded ? _childBookmark![i].title : _bookmarkBase![i].title, + isMobileWebView: !_isTablet, isExpandIconVisible: _isExpanded - ? _childBookmark[i].count != 0 - : _bookmarkBase[i].count != 0, + ? _childBookmark![i].count != 0 + : _bookmarkBase![i].count != 0, onNavigate: () { final PdfBookmark bookmark = - _isExpanded ? _childBookmark[i] : _bookmarkBase[i]; + _isExpanded ? _childBookmark![i] : _bookmarkBase![i]; widget.controller.jumpToBookmark(bookmark); _handleClose(); }, onExpandPressed: () { _handleExpandPress(i); }, + onBackPressed: () {}, ); - _bookmarkList.add(bookmarkItem); + _bookmarkList?.add(bookmarkItem); } - _listCount = _isExpanded ? bookmarkListCount + 1 : _bookmarkBase.count; - return _bookmarkList; + _listCount = _isExpanded ? bookmarkListCount + 1 : _bookmarkBase?.count; + return _bookmarkList!; } @override Widget build(BuildContext context) { _findDevice(context); final hasBookmark = - (widget.pdfDocument != null && widget.pdfDocument.bookmarks.count > 0) + (widget.pdfDocument != null && widget.pdfDocument!.bookmarks.count > 0) ? true : false; return Visibility( @@ -189,30 +199,31 @@ class BookmarkViewControllerState extends State { Visibility( visible: _isTablet, child: GestureDetector( - child: Container( - color: Colors.black.withOpacity(0.3), - ), - onTap: _handleClose), + onTap: _handleClose, + child: Container( + color: Colors.black.withOpacity(0.3), + ), + ), ), Align( alignment: _isTablet ? Alignment.topRight : Alignment.center, child: Container( - color: _pdfViewerThemeData.bookmarkViewStyle.backgroundColor, + color: _pdfViewerThemeData!.bookmarkViewStyle.backgroundColor, width: _isTablet ? _kPdfTabletBookmarkWidth : _totalWidth, child: Column(children: [ - BookmarkToolbar(onCloseButtonPressed: _handleClose), + BookmarkToolbar(_handleClose), Expanded( child: hasBookmark ? ListView.builder( itemCount: _listCount, itemBuilder: (BuildContext context, int index) { - return _bookmarkList[index]; + return _bookmarkList![index]; }, ) : Center( child: Text( - _localizations.pdfNoBookmarksLabel, - style: _pdfViewerThemeData + _localizations!.pdfNoBookmarksLabel, + style: _pdfViewerThemeData! .bookmarkViewStyle.titleTextStyle, ), ), diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/mobile_helper.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/mobile_helper.dart new file mode 100644 index 000000000..22f01fbf1 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/mobile_helper.dart @@ -0,0 +1,7 @@ +/// Checks whether focus node of pdf page view has primary focus. +bool hasPrimaryFocus = false; + +/// Prevents default menu. +void preventDefaultMenu() { + return null; +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_provider.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_provider.dart index 17e7d1faf..98951b218 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_provider.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_provider.dart @@ -4,9 +4,8 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:path_provider/path_provider.dart' as path_provider; -import 'package:flutter/foundation.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; +import 'package:http/http.dart' as http; /// Represents a base class of PDF document provider. /// The PDF provider can be from Asset, Memory, File and Network. @@ -15,12 +14,8 @@ abstract class PdfProvider { /// const constructors so that they can be used in const expressions. const PdfProvider(); - /// Returns the path in which PDF is saved in - /// application's temporary directory. - Future getPdfPath(BuildContext context); - - /// Returns the path defined by the user in the constructor - String getUserPath(); + /// Returns the byte information of PDF document. + Future getPdfBytes(BuildContext context); } /// Fetches the given PDF URL from the network. @@ -35,36 +30,26 @@ class NetworkPdf extends PdfProvider { /// Creates an object that fetches the PDF at the given URL. /// /// The arguments [url] must not be null. - NetworkPdf(String url) - : assert(url != null), - assert(url.isNotEmpty) { + NetworkPdf(String url) : assert(url.isNotEmpty) { _url = url; } /// The URL from which the PDF will be fetched. - String _url; + late String _url; - @override - Future getPdfPath(BuildContext context) async { - try { - final HttpClient client = HttpClient(); - final HttpClientRequest request = await client.getUrl(Uri.parse(_url)); - final HttpClientResponse response = await request.close(); - final bytes = await consolidateHttpClientResponseBytes(response); - Directory directory = await path_provider.getTemporaryDirectory(); - directory = await directory.createTemp('.syncfusion'); - final filename = _url.substring(_url.lastIndexOf('/') + 1); - File sample = File('${directory.path}/$filename'); - sample = await sample.writeAsBytes(bytes, flush: true); - return sample.path; - } on Exception catch (e) { - throw (e.toString()); - } - } + /// The document bytes + Uint8List? _documentBytes; @override - String getUserPath() { - return _url; + Future getPdfBytes(BuildContext context) async { + if (_documentBytes == null) { + try { + _documentBytes = await http.readBytes(Uri.parse(_url)); + } on Exception catch (e) { + throw (e.toString()); + } + } + return Future.value(_documentBytes); } } @@ -80,35 +65,15 @@ class MemoryPdf extends PdfProvider { /// Creates an object that decodes a [Uint8List] buffer as a PDF. /// /// The arguments must not be null. - MemoryPdf(Uint8List bytes) : assert(bytes != null) { - _bytes = bytes; + MemoryPdf(Uint8List bytes) { + _pdfBytes = bytes; } - Uint8List _bytes; - Uint8List _pdfBytes; - @override - Future getPdfPath(BuildContext context) async { - try { - Directory directory = await path_provider.getTemporaryDirectory(); - directory = await directory.createTemp('.syncfusion'); - final File sample = File('${directory.path}/sample.pdf'); - await sample.writeAsBytes(_bytes, flush: true); - _pdfBytes = _bytes; - return sample.path; - } on Exception catch (e) { - throw (e.toString()); - } - } + late Uint8List _pdfBytes; @override - String getUserPath() { - /// This variable is used to convert the bytes in collection - /// as string for comparison - String byteString; - _pdfBytes?.forEach((element) { - byteString += element.toString(); - }); - return byteString; + Future getPdfBytes(BuildContext context) async { + return Future.value(_pdfBytes); } } @@ -124,37 +89,29 @@ class AssetPdf extends PdfProvider { /// Creates an object that fetches the given PDF from an asset bundle. /// /// [assetName] must not be null. - AssetPdf(String assetName, AssetBundle bundle) - : assert(assetName != null), - assert(assetName.isNotEmpty) { + AssetPdf(String assetName, AssetBundle? bundle) + : assert(assetName.isNotEmpty) { _pdfPath = assetName; _bundle = bundle; } - String _pdfPath; - AssetBundle _bundle; + late String _pdfPath; + AssetBundle? _bundle; + Uint8List? _documentBytes; @override - Future getPdfPath(BuildContext context) async { - try { - Directory directory = await path_provider.getTemporaryDirectory(); - directory = await directory.createTemp('.syncfusion'); - final filename = _pdfPath.substring(_pdfPath.lastIndexOf('/') + 1); - final File sample = File('${directory.path}/$filename'); - final bytes = await ((_bundle != null) - ? _bundle.load(_pdfPath) - : DefaultAssetBundle.of(context).load(_pdfPath)); - final Uint8List pdfBytes = bytes.buffer.asUint8List(); - await sample.writeAsBytes(pdfBytes, flush: true); - return sample.path; - } on Exception catch (e) { - throw (e.toString()); + Future getPdfBytes(BuildContext context) async { + if (_documentBytes == null) { + try { + final bytes = await ((_bundle != null) + ? _bundle!.load(_pdfPath) + : DefaultAssetBundle.of(context).load(_pdfPath)); + _documentBytes = bytes.buffer.asUint8List(); + } on Exception catch (e) { + throw (e.toString()); + } } - } - - @override - String getUserPath() { - return _pdfPath; + return Future.value(_documentBytes); } } @@ -167,28 +124,24 @@ class FilePdf extends PdfProvider { /// Creates an object that decodes a [File] as a PDF. /// /// The [file] must not be null. - FilePdf(File file) : assert(file != null) { + FilePdf(File file) { _file = file; } - File _file; - @override - Future getPdfPath(BuildContext context) async { - try { - Directory directory = await path_provider.getTemporaryDirectory(); - directory = await directory.createTemp('.syncfusion'); - final filename = _file.path.substring(_file.path.lastIndexOf('/') + 1); - final File sample = File('${directory.path}/$filename'); - final Uint8List bytes = File(_file.path).readAsBytesSync(); - await sample.writeAsBytes(bytes, flush: true); - return sample.path; - } on Exception catch (e) { - throw (e.toString()); - } - } + late File _file; + + /// The document bytes + Uint8List? _documentBytes; @override - String getUserPath() { - return _file.path; + Future getPdfBytes(BuildContext context) async { + if (_documentBytes == null) { + try { + _documentBytes = await _file.readAsBytes(); + } on Exception catch (e) { + throw (e.toString()); + } + } + return Future.value(_documentBytes); } } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart index 1dfc83625..b61858808 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart @@ -1,48 +1,94 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/pdftextline.dart'; /// TextSelectionHelper for storing information of text selection. class TextSelectionHelper { /// It will be true,if text selection is started. bool selectionEnabled = false; - /// firstGlyphOffset of the selection. - Offset firstGlyphOffset; + /// It will be true,if mouse pointer text selection for web is started. + bool mouseSelectionEnabled = false; + + /// Glyph which is first at selected text of the selection. + TextGlyph? firstSelectedGlyph; /// Page number in which text selection is started. - int viewId; + int? viewId; + + /// Cursor page number in which mouse hover is happening. + int? cursorPageNumber; /// Represents the global region of text selection. - Rect globalSelectedRegion; + Rect? globalSelectedRegion; /// Copied text of text selection. - String copiedText; + String? copiedText; /// X position of start bubble. - double startBubbleX; + double? startBubbleX; /// Y position of start bubble. - double startBubbleY; + double? startBubbleY; /// X position of end bubble. - double endBubbleX; + double? endBubbleX; /// Y position of end bubble. - double endBubbleY; + double? endBubbleY; /// heightPercentage of pdf page - double heightPercentage; + double? heightPercentage; /// TextLine from Pdf library. - List textLines; + List? textLines; + + /// TextLine from Pdf library used while mouse cursor hovering. + List? cursorTextLines; /// Line of the start bubble. - TextLine startBubbleLine; + TextLine? startBubbleLine; /// Line of the end bubble. - TextLine endBubbleLine; + TextLine? endBubbleLine; /// Entry history of text selection - LocalHistoryEntry historyEntry; + LocalHistoryEntry? historyEntry; + + /// Checks whether the cursor is in viewport or not. + bool isCursorExit = false; + + /// Checks whether the cursor reached top of the viewport or not. + bool isCursorReachedTop = false; + + /// Initial scroll offset before scrolling while selection perform. + double initialScrollOffset = 0; + + /// Final scroll offset after scrolling while selection perform. + double finalScrollOffset = 0; + + /// Checks whether the tap selection is enabled or not. + bool enableTapSelection = false; + + /// Gets the selected text lines. + List selectedTextLines = []; +} + +/// Determines different page navigation. +enum Navigation { + /// Performs page navigation to specific page + jumpToPage, + + /// Navigates to first page + firstPage, + + /// Navigates to next page + nextPage, + + /// Navigates to last page + lastPage, + + /// Navigates to previous page + previousPage } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart index 38bcba7df..beba54742 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart @@ -1,67 +1,85 @@ import 'dart:async'; +import 'dart:typed_data'; +import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; +import 'package:syncfusion_flutter_pdfviewer_platform_interface/pdfviewer_platform_interface.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; /// Establishes communication between native(Android and iOS) code /// and flutter code using [MethodChannel] class PdfViewerPlugin { - /// Creates a instance of [PdfViewerPlugin] - PdfViewerPlugin(String path) { - _pdfPath = path; - } - - final MethodChannel _channel = MethodChannel('syncfusion_flutter_pdfviewer'); int _pageCount = 0; - List _originalHeight; - List _originalWidth; - Map _renderedPages = {}; - String _pdfPath; + int _startPageIndex = 0; + int _endPageIndex = 0; + List? _originalHeight; + List? _originalWidth; + List? _renderingPages = []; + Map? _renderedPages = {}; /// Initialize the PDF renderer. - Future initializePdfRenderer() async { - final String pageCount = - await _channel.invokeMethod('initializepdfrenderer', _pdfPath); - _pageCount = int.parse(pageCount); + Future initializePdfRenderer(Uint8List documentBytes) async { + final pageCount = + await PdfViewerPlatform.instance.initializePdfRenderer(documentBytes); + _pageCount = int.parse(pageCount!); return _pageCount; } /// Retrieves original height of PDF pages. - Future getPagesHeight() async { - _originalHeight = await _channel.invokeMethod('getpagesheight', _pdfPath); + Future getPagesHeight() async { + _originalHeight = await PdfViewerPlatform.instance.getPagesHeight(); return _originalHeight; } /// Retrieves original width of PDF pages. - Future getPagesWidth() async { - _originalWidth = await _channel.invokeMethod('getpageswidth', _pdfPath); + Future getPagesWidth() async { + _originalWidth = await PdfViewerPlatform.instance.getPagesWidth(); return _originalWidth; } /// Get the specific image of PDF - Future> _getImage(int pageIndex) async { - final List images = await _channel.invokeMethod( - 'getimage', {'index': pageIndex, 'path': _pdfPath}); - if (_renderedPages != null) { - _renderedPages[pageIndex] = images[0]; + Future?> _getImage(int pageIndex) async { + if (_renderingPages != null && !_renderingPages!.contains(pageIndex)) { + _renderingPages!.add(pageIndex); + Future imageFuture = + PdfViewerPlatform.instance.getImage(pageIndex).whenComplete(() { + _renderingPages?.remove(pageIndex); + }); + if (!kIsWeb) { + imageFuture = + imageFuture.timeout(Duration(milliseconds: 1000), onTimeout: () { + _renderingPages?.remove(pageIndex); + return null; + }); + } + final Uint8List? image = await imageFuture; + if (_renderedPages != null && image != null) { + if (kIsWeb) { + _renderedPages![pageIndex] = Uint8List.fromList(image); + } else { + _renderedPages![pageIndex] = image; + } + } } return _renderedPages; } /// Retrieves PDF pages as image collection for specified pages. - Future> getSpecificPages( + Future?> getSpecificPages( int startPageIndex, int endPageIndex) async { - imageCache.clear(); - for (int pageIndex = startPageIndex; - pageIndex <= endPageIndex; + imageCache!.clear(); + _startPageIndex = startPageIndex; + _endPageIndex = endPageIndex; + for (int pageIndex = _startPageIndex; + pageIndex <= _endPageIndex; pageIndex++) { - if (_renderedPages != null && !_renderedPages.containsKey(pageIndex)) { + if (_renderedPages != null && !_renderedPages!.containsKey(pageIndex)) { await _getImage(pageIndex); } } final pdfPage = []; _renderedPages?.forEach((key, value) { - if (!(key >= startPageIndex && key <= endPageIndex)) { + if (!(key >= _startPageIndex && key <= _endPageIndex)) { pdfPage.add(key); } }); @@ -72,12 +90,13 @@ class PdfViewerPlugin { } /// Dispose the rendered pages - void disposePages() async { - imageCache.clear(); - await _channel.invokeMethod('dispose'); - _pageCount = null; + Future closeDocument() async { + imageCache!.clear(); + await PdfViewerPlatform.instance.closeDocument(); + _pageCount = 0; _originalWidth = null; _originalHeight = null; + _renderingPages = null; if (_renderedPages != null) { _renderedPages = null; } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/web_helper.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/web_helper.dart new file mode 100644 index 000000000..86f1956a4 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/web_helper.dart @@ -0,0 +1,22 @@ +import 'dart:html' as html; + +/// Checks whether focus node of pdf page view has primary focus. +bool hasPrimaryFocus = false; + +/// Prevent default menu. +void preventDefaultMenu() { + html.window.document.onKeyDown.listen((e) => _preventSpecificDefaultMenu(e)); + html.window.document.onContextMenu.listen((e) => e.preventDefault()); +} + +/// Prevent specific default menu such as zoom panel,search. +void _preventSpecificDefaultMenu(e) { + if (e.keyCode == 114 || (e.ctrlKey && e.keyCode == 70)) { + e.preventDefault(); + } + if (hasPrimaryFocus && + e.ctrlKey && + (e.keyCode == 48 || e.keyCode == 189 || e.keyCode == 187)) { + e.preventDefault(); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/enums.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/enums.dart index 4cb5bd8fb..0a49dfd19 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/enums.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/enums.dart @@ -1,17 +1,8 @@ -/// Determines different page navigation. -enum Navigation { - /// Performs page navigation to specific page - jumpToPage, +/// Represents different pdf interaction modes. +enum PdfInteractionMode { + /// Enables the text selection mode in a desktop browser to select the text using mouse dragging. + selection, - /// Navigates to first page - firstPage, - - /// Navigates to next page - nextPage, - - /// Navigates to last page - lastPage, - - /// Navigates to previous page - previousPage + /// Enables the panning mode in a desktop browser to move or scroll through the pages using mouse dragging. + pan } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pagination.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pagination.dart index 774294c57..f146cedfa 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pagination.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pagination.dart @@ -1,12 +1,12 @@ -import 'enums.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; /// Pagination class used for page navigation. class Pagination { /// Constructor of Pagination. - Pagination({this.index, this.option}); + Pagination(this.option, {this.index}); /// Represents the page number for Pagination. - final int index; + final int? index; /// Represents the different Pagination option. final Navigation option; diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_container.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_container.dart index c77704e78..4d05506aa 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_container.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_container.dart @@ -1,8 +1,10 @@ import 'dart:math' as math; -import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/physics.dart'; +import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:syncfusion_flutter_pdfviewer/src/control/pdfviewer_callback_details.dart'; import 'package:vector_math/vector_math_64.dart' show Quad, Vector3, Matrix4; @@ -11,23 +13,22 @@ import 'package:vector_math/vector_math_64.dart' show Quad, Vector3, Matrix4; @immutable class PdfContainer extends StatefulWidget { /// Creates a instance of PdfContainer. - PdfContainer({ + PdfContainer( Key key, + this.isMobileWebView, this.initialScrollOffset, this.initialZoomLevel, - this.enableDoubleTapZooming = true, + this.enableDoubleTapZooming, this.onZoomLevelChanged, this.pdfController, - this.panEnabled = true, - this.scaleEnabled = true, - @required this.scrollController, - @required this.itemBuilder, - }) : assert(itemBuilder != null), - assert(scrollController != null), - assert(panEnabled != null), - assert(scaleEnabled != null), - assert(enableDoubleTapZooming != null), - super(key: key); + this.onTap, + this.interactionMode, + this.viewportConstraints, + this.maxPdfPageWidth, + this.panEnabled, + this.scrollController, + this.itemBuilder, + ) : super(key: key); /// Represents the initial zoom level to be applied when the [SfPdfViewer] widget is loaded. final double initialZoomLevel; @@ -35,6 +36,12 @@ class PdfContainer extends StatefulWidget { /// Represents the initial scroll offset position to be displayed when the [SfPdfViewer] widget is loaded. final Offset initialScrollOffset; + /// Represents the viewport dimension of SfPdfViewer container. + final BoxConstraints viewportConstraints; + + /// Represents the maximum page width. + final double maxPdfPageWidth; + /// An object that can be used to control the position to which this scroll /// view is scrolled. final ScrollController scrollController; @@ -45,12 +52,18 @@ class PdfContainer extends StatefulWidget { /// If true, panning is enabled. final bool panEnabled; - /// If true, scaling is enabled. - final bool scaleEnabled; - /// If true, double tap zooming is enabled. final bool enableDoubleTapZooming; + /// Triggered while tap on pdf container. + final VoidCallback onTap; + + /// Indicates interaction mode of pdfViewer. + final PdfInteractionMode interactionMode; + + /// If true,MobileWebView is enabled.Default value is false. + final bool isMobileWebView; + /// Called when the zoom level changes in [SfPdfViewer]. /// /// Called in the following scenarios where the zoom level changes @@ -63,7 +76,7 @@ class PdfContainer extends StatefulWidget { /// when the zoom level changes. /// /// See also: [PdfZoomDetails]. - final PdfZoomLevelChangedCallback onZoomLevelChanged; + final PdfZoomLevelChangedCallback? onZoomLevelChanged; /// It controls the PdfViewer final PdfViewerController pdfController; @@ -169,7 +182,7 @@ class PdfContainer extends StatefulWidget { PdfContainer.getNearestPointOnLine(point, quad.point3, quad.point0), ]; double minDistance = double.infinity; - Vector3 closestOverall; + late Vector3 closestOverall; for (final Vector3 closePoint in closestPoints) { final double distance = math.sqrt( math.pow(point.x - closePoint.x, 2) + @@ -191,27 +204,35 @@ class PdfContainer extends StatefulWidget { class PdfContainerState extends State with TickerProviderStateMixin { final bool _alignPanAxis = false; + bool _isLongPressed = false; final double _minScale = 1.0, _maxScale = 3.0; double _scale = 1.0; double _previousScale = 1.0; final EdgeInsets _boundaryMargin = EdgeInsets.zero; final GlobalKey _childKey = GlobalKey(); final GlobalKey _parentKey = GlobalKey(); - Animation _animation; - AnimationController _animationController; - PdfViewerController _pdfController; + Animation? _animation; + late AnimationController _animationController; + late PdfViewerController _pdfController; Offset _tapPosition = Offset.zero, _previousOffset = Offset.zero; - Offset _origin; - Axis _panAxis; // Used with alignPanAxis. - Offset _referenceFocalPoint; // Point where the current gesture began. - double _scaleStart; // Scale value at start of scaling gesture. - _GestureType _gestureType; + Offset? _origin; + Axis? _panAxis; // Used with alignPanAxis. + Offset? _referenceFocalPoint; // Point where the current gesture began. + double? _scaleStart; // Scale value at start of scaling gesture. + _GestureType? _gestureType; bool _isPositionHandlerAttached = false; + bool _isContentOverflowed = false; + double _overflowOrigin = 0.0; + bool _isMousePointer = false; + double _mouseDragOffsetInPanMode = 0.0; + final _scaleEnabled = true; + SystemMouseCursor _cursor = SystemMouseCursors.basic; + // Scale Matrix - Matrix4 _viewMatrix; + Matrix4? _viewMatrix; /// Gets the Scale matrix - Matrix4 get viewMatrix => _viewMatrix; + Matrix4? get viewMatrix => _viewMatrix; /// Sets the Scale matrix set viewMatrix(value) { @@ -231,7 +252,7 @@ class PdfContainerState extends State assert(_parentKey.currentContext != null); final RenderBox parentRenderBox = // ignore: avoid_as - _parentKey.currentContext.findRenderObject() as RenderBox; + _parentKey.currentContext!.findRenderObject() as RenderBox; return Offset.zero & parentRenderBox.size; } @@ -243,7 +264,7 @@ class PdfContainerState extends State } final Offset alignedTranslation = _alignPanAxis && _panAxis != null - ? _alignAxis(translation, _panAxis) + ? _alignAxis(translation, _panAxis!) : translation; final Matrix4 nextMatrix = matrix.clone() @@ -258,7 +279,7 @@ class PdfContainerState extends State final RenderBox childRenderBox = // ignore: avoid_as - _childKey.currentContext.findRenderObject() as RenderBox; + _childKey.currentContext!.findRenderObject() as RenderBox; final Size childSize = childRenderBox.size; final Rect boundaryRect = _boundaryMargin.inflateRect(Offset.zero & childSize); @@ -334,7 +355,7 @@ class PdfContainerState extends State // Don't allow a scale that results in an overall scale beyond min/max // scale. - final double currentScale = viewMatrix.getMaxScaleOnAxis(); + final double currentScale = viewMatrix!.getMaxScaleOnAxis(); final double totalScale = currentScale * scale; final double clampedTotalScale = totalScale .clamp( @@ -352,7 +373,7 @@ class PdfContainerState extends State bool _gestureIsSupported(_GestureType gestureType) { switch (gestureType) { case _GestureType.scale: - return widget.scaleEnabled; + return _scaleEnabled; case _GestureType.pan: default: @@ -365,6 +386,9 @@ class PdfContainerState extends State // Handle scale start of a gesture. void _handleScaleStart(ScaleStartDetails details) { + if (widget.interactionMode == PdfInteractionMode.pan) { + _cursor = SystemMouseCursors.grabbing; + } if (_animationController.isAnimating) { _animationController.stop(); _animationController.reset(); @@ -374,14 +398,15 @@ class PdfContainerState extends State _gestureType = null; _panAxis = null; - _scaleStart = viewMatrix.getMaxScaleOnAxis(); + _scaleStart = viewMatrix!.getMaxScaleOnAxis(); _pixels = widget.scrollController.position.pixels; _translationY = 0; + _mouseDragOffsetInPanMode = 0; if (widget.scrollController.position.maxScrollExtent > 0) { final factor = _pixels / widget.scrollController.position.maxScrollExtent; final totalSize = widget.scrollController.position.viewportDimension + widget.scrollController.position.maxScrollExtent; - final scaledSize = _scaleStart * totalSize; + final scaledSize = _scaleStart! * totalSize; final scaledMaxScrollExtent = scaledSize - widget.scrollController.position.viewportDimension; _pixels = scaledMaxScrollExtent * factor; @@ -394,7 +419,50 @@ class PdfContainerState extends State details.localFocalPoint, ); _previousOffset = - Offset(-_referenceFocalPoint.dx, -(widget.scrollController.offset)); + Offset(-_referenceFocalPoint!.dx, -(widget.scrollController.offset)); + } + + /// Jumps to the offset when document is zoomed. + /// This calculation need to be used for all jumping instance. + void jumpOnZoomedDocument( + double? xOffset, double yOffset, double viewportWidth) { + _scale = viewMatrix!.getMaxScaleOnAxis(); + var correctedY = yOffset; + if (widget.scrollController.position.maxScrollExtent > 0) { + final factor = + correctedY / widget.scrollController.position.maxScrollExtent; + final totalSize = widget.scrollController.position.viewportDimension + + widget.scrollController.position.maxScrollExtent; + final scaledSize = _scale * totalSize; + final scaledMaxScrollExtent = + scaledSize - widget.scrollController.position.viewportDimension; + correctedY = scaledMaxScrollExtent * factor; + correctedY = (correctedY / scaledSize) * totalSize; + double midOfViewport = + ((widget.scrollController.position.viewportDimension / 2) * + _scale * + factor); + if (kIsWeb && xOffset != null) { + final _heightFactor = _viewport.height / _viewport.center.dy; + if (_scale > _heightFactor) { + midOfViewport = midOfViewport / _heightFactor; + } + } + double scaledYOffset = + (toScene(Offset(0, correctedY)).dy * _scale) - midOfViewport; + if (scaledYOffset > widget.scrollController.position.maxScrollExtent) { + scaledYOffset = widget.scrollController.position.maxScrollExtent; + } + _pdfController.jumpTo(yOffset: scaledYOffset); + if (xOffset != null) { + final currentX = viewMatrix!.getTranslation().x.round() == 0 + ? 0 + : (viewMatrix!.getTranslation().x.roundToDouble().abs()) / _scale; + xOffset = currentX - xOffset; + viewMatrix = _matrixTranslate( + viewMatrix!, Offset(xOffset + ((viewportWidth / 2) / _scale), 0)); + } + } } void _setPosition(Offset translation) { @@ -404,7 +472,7 @@ class PdfContainerState extends State _translationY = _translationY + translation.dy; final totalSize = position.viewportDimension + maxScrollExtent; double factor = (_pixels - _translationY) / totalSize; - final scaledSize = totalSize * viewMatrix.getMaxScaleOnAxis(); + final scaledSize = totalSize * viewMatrix!.getMaxScaleOnAxis(); final scaledMaxScrollExtent = scaledSize - position.viewportDimension; final factorAfterScaling = (scaledSize * factor) / scaledMaxScrollExtent; double newPixels = maxScrollExtent * factorAfterScaling; @@ -421,16 +489,19 @@ class PdfContainerState extends State if (position.maxScrollExtent <= 0) { //maxScrollExtent less than zero this condition triggered _origin = Offset(0, 0); - viewMatrix = _matrixTranslate(viewMatrix, Offset(0, translation.dy)); + viewMatrix = _matrixTranslate(viewMatrix!, Offset(0, translation.dy)); } } /// Handle scale update for ongoing gesture. - void _handleScaleUpdate({ScaleUpdateDetails details, bool doubleTap}) { - Offset focalPointScene; + void _handleScaleUpdate( + {ScaleUpdateDetails? details, bool doubleTap = false}) { + _isContentOverflowed = false; + Offset? focalPointScene; //if the double details are not null doubleTap invoked ,otherwise pinch zoom invoked - if (doubleTap != null) { + if (doubleTap) { Offset _offset; + _gestureType = _GestureType.scale; if (_scale <= 1.0) { _origin = Offset.zero; final Offset normalizedOffset = @@ -439,7 +510,7 @@ class PdfContainerState extends State //this offset represent the current focus offset after double tap zoom _offset = ((_tapPosition) - normalizedOffset * _scale) / _scale; viewMatrix = _matrixScale( - viewMatrix, + viewMatrix!, _scale, ); @@ -449,13 +520,13 @@ class PdfContainerState extends State // Y axis translation will be handled by scroll controller // to maintain exact scale offset without cropping the document // in the following codes. - viewMatrix = _matrixTranslate(viewMatrix, Offset(_offset.dx, 0)); + viewMatrix = _matrixTranslate(viewMatrix!, Offset(_offset.dx, 0)); widget.scrollController.jumpTo(-(_offset.dy)); } else { final Offset normalizedOffset = ((_tapPosition) - _previousOffset * _scale) / _scale; viewMatrix = _matrixScale( - viewMatrix, + viewMatrix!, 1.0 / _scale, ); _scale = 1; @@ -468,32 +539,32 @@ class PdfContainerState extends State // Y axis translation will be handled by scroll controller // to maintain exact scale offset without cropping the document // in the following codes. - viewMatrix = _matrixTranslate(viewMatrix, Offset(_offset.dx, 0)); + viewMatrix = _matrixTranslate(viewMatrix!, Offset(_offset.dx, 0)); widget.scrollController.jumpTo(-_offset.dy); } _setPosition(_offset - _previousOffset); - if (_pdfController != null) { - _pdfController.zoomLevel = _scale; - _previousScale = _scale; - } + _pdfController.zoomLevel = _scale; + _previousScale = _scale; } else { - _scale = viewMatrix.getMaxScaleOnAxis(); + _scale = viewMatrix!.getMaxScaleOnAxis(); focalPointScene = toScene( - details.localFocalPoint, + details!.localFocalPoint, ); - _gestureType ??= - _getGestureType(!widget.scaleEnabled ? 1.0 : details.scale); + _gestureType ??= _getGestureType(!_scaleEnabled ? 1.0 : details.scale); + if (_restrictPanForWebSelectionMode()) { + return; + } if (_gestureType == _GestureType.pan) { - _panAxis ??= _getPanAxis(_referenceFocalPoint, focalPointScene); + _panAxis ??= _getPanAxis(_referenceFocalPoint!, focalPointScene); } - if (!_gestureIsSupported(_gestureType)) { + if (!_gestureIsSupported(_gestureType!)) { return; } } - switch (_gestureType) { + switch (_gestureType!) { case _GestureType.scale: - if (_scaleStart == null) { + if (_scaleStart == null || details == null) { return; } @@ -502,10 +573,10 @@ class PdfContainerState extends State // details.scale gives us the amount to change the scale as of the // start of this gesture, so calculate the amount to scale as of the // previous call to _onScaleUpdate. - final double desiredScale = _scaleStart * details.scale; + final double desiredScale = _scaleStart! * details.scale; final double scaleChange = desiredScale / _scale; viewMatrix = _matrixScale( - viewMatrix, + viewMatrix!, scaleChange, ); @@ -517,7 +588,7 @@ class PdfContainerState extends State details.localFocalPoint, ); - final translation = focalPointSceneScaled - _referenceFocalPoint; + final translation = focalPointSceneScaled - _referenceFocalPoint!; // Translating in X axis and ignored Y axis. If the Y axis is translated // then, the viewport origin will be changed to current translation @@ -525,7 +596,7 @@ class PdfContainerState extends State // Y axis translation will be handled by scroll controller // to maintain exact scale offset without cropping the document // in the following codes. - viewMatrix = _matrixTranslate(viewMatrix, Offset(translation.dx, 0.0)); + viewMatrix = _matrixTranslate(viewMatrix!, Offset(translation.dx, 0.0)); _setPosition(translation); // details.localFocalPoint should now be at the same location as the // original _referenceFocalPoint point. If it's not, that's because @@ -535,21 +606,25 @@ class PdfContainerState extends State final Offset focalPointSceneCheck = toScene( details.localFocalPoint, ); - if (_round(_referenceFocalPoint) != _round(focalPointSceneCheck)) { + if (_round(_referenceFocalPoint!) != _round(focalPointSceneCheck)) { _referenceFocalPoint = focalPointSceneCheck; } return; case _GestureType.pan: - if (_referenceFocalPoint == null || details.scale != 1.0) { + if (_restrictPanForWebSelectionMode()) { + return; + } + if (_referenceFocalPoint == null || details!.scale != 1.0) { return; } // Translate so that the same point in the scene is underneath the // focal point before and after the movement. We have ignore dy since // the Y axis is translation is done by scroll controller. - final Offset translationChange = focalPointScene - _referenceFocalPoint; + final Offset translationChange = + focalPointScene! - _referenceFocalPoint!; viewMatrix = _matrixTranslate( - viewMatrix, + viewMatrix!, Offset( translationChange.dx, widget.scrollController.position.maxScrollExtent > 0 @@ -559,21 +634,23 @@ class PdfContainerState extends State _referenceFocalPoint = toScene( details.localFocalPoint, ); + _panOnGreyArea(translationChange.dy, details.focalPoint); return; } } // Handle scale end of a gesture of _GestureType. void _handleScaleEnd(ScaleEndDetails details) { - if (_pdfController != null) { - _pdfController.zoomLevel = _scale; + if (widget.interactionMode == PdfInteractionMode.pan) { + _cursor = SystemMouseCursors.grab; } + _pdfController.zoomLevel = _scale; _previousScale = _scale; _scaleStart = null; _referenceFocalPoint = null; _animation?.removeListener(_handleAnimate); _animationController.reset(); - if (!_gestureIsSupported(_gestureType)) { + if (_gestureType != null && !_gestureIsSupported(_gestureType!)) { _panAxis = null; return; } @@ -585,7 +662,7 @@ class PdfContainerState extends State return; } - final Vector3 translationVector = viewMatrix.getTranslation(); + final Vector3 translationVector = viewMatrix!.getTranslation(); final Offset translation = Offset(translationVector.x, translationVector.y); final FrictionSimulation frictionSimulationX = FrictionSimulation( _kDrag, @@ -610,8 +687,37 @@ class PdfContainerState extends State )); _animationController.duration = Duration(milliseconds: (tFinal * 1000).round()); - _animation.addListener(_handleAnimate); + _animation!.addListener(_handleAnimate); _animationController.forward(); + if (kIsWeb && !widget.isMobileWebView && !_isMousePointer) { + _animateToOffset(frictionSimulationY.finalX, tFinal); + } + } + + // Provides panning effect on the grey area in the Web platform + void _animateToOffset(double yOffset, double velocity) { + final offset = widget.scrollController.offset - (yOffset * 2); + widget.scrollController.animateTo(offset, + curve: Curves.decelerate, + duration: Duration(milliseconds: (velocity * 2000).round())); + } + + //Identifies whether panning should be applied on grey area. + void _panOnGreyArea(double offset, Offset focalPoint) { + if (kIsWeb && + _isMousePointer && + widget.interactionMode == PdfInteractionMode.pan && + !_isContentOverflowed && + _childKey.currentContext != null) { + final RenderBox childRenderBox = + // ignore: avoid_as + _childKey.currentContext!.findRenderObject() as RenderBox; + final position = (childRenderBox.globalToLocal(focalPoint)); + if (position.dx < 0 || position.dx > childRenderBox.paintBounds.width) { + _mouseDragOffsetInPanMode += offset; + _animateToOffset(_mouseDragOffsetInPanMode, 0.5); + } + } } // Handle inertia animation. @@ -624,80 +730,140 @@ class PdfContainerState extends State return; } // Translate such that the resulting translation is _animation.value. - final Vector3 translationVector = viewMatrix.getTranslation(); + final Vector3 translationVector = viewMatrix!.getTranslation(); final Offset translation = Offset(translationVector.x, translationVector.y); final Offset translationScene = toScene( translation, ); final Offset animationScene = toScene( - _animation.value, + _animation!.value, ); final Offset translationChangeScene = animationScene - translationScene; - viewMatrix = - _matrixTranslate(viewMatrix, Offset(translationChangeScene.dx, 0.0)); + if (!_restrictPanForWebSelectionMode()) { + viewMatrix = + _matrixTranslate(viewMatrix!, Offset(translationChangeScene.dx, 0.0)); + } } /// This method use to get the tap positions void _handleTapDown(TapDownDetails details) { _tapPosition = (details.localPosition); + _isMousePointer = details.kind == PointerDeviceKind.mouse ? true : false; + } + + /// Find the Horizontal offset. + double findHorizontalOffset() { + _scale = viewMatrix!.getMaxScaleOnAxis(); + final double xPosition = viewMatrix!.getTranslation().x.round() == 0 + ? 0 + : (viewMatrix!.getTranslation().x.roundToDouble().abs()) / _scale; + return xPosition; } void _onZoomLevelChanged() { - if (_pdfController != null && - _pdfController.zoomLevel != null && - _scale != _pdfController.zoomLevel) { + _overflowOrigin = 0.0; + if (_scale != _pdfController.zoomLevel) { double scaleChangeFactor = 1.0; - //whenever user set scale greater than maximum scale percentage and less than minimum scale percentage + _isContentOverflowed = false; + // whenever user set scale greater than maximum scale percentage and less than minimum scale percentage // it will be reassigned within limit if (_pdfController.zoomLevel > _maxScale) { _pdfController.zoomLevel = _maxScale; } else if (_pdfController.zoomLevel < _minScale) { _pdfController.zoomLevel = _minScale; } - if (_scale != null) { - //previous scale value updated here before scale changed - _previousScale = _scale; - } + //previous scale value updated here before scale changed + _previousScale = _scale; _scale = _pdfController.zoomLevel; scaleChangeFactor = _scale; //Here zoom in and zoom out logic applied, // setScale value changed based on maximum zoom percentage to minimum scale percentage // and minimum percentage to maximum percentage - if (_scale != null && _previousScale > _scale) { + if (_previousScale > _scale) { scaleChangeFactor = _scale / _previousScale; + } else if (_previousScale < _scale && + (_scale - _previousScale) < 1 && + _previousScale != 1) { + viewMatrix = _matrixScale( + viewMatrix!, + 1.0 / _previousScale, + ); + viewMatrix = _matrixTranslate( + viewMatrix!, Offset(_viewport.width, _viewport.height)); } - viewMatrix = _matrixScale( - viewMatrix, + viewMatrix!, scaleChangeFactor, ); - viewMatrix = _matrixTranslate( - viewMatrix, Offset(_viewport.width, _viewport.height)); + if (!kIsWeb || (kIsWeb && widget.isMobileWebView)) { + // Existing mobile behavior + viewMatrix = _matrixTranslate( + viewMatrix!, Offset(_viewport.width, _viewport.height)); + } else { + final RenderBox childRenderBox = + // ignore: avoid_as + _childKey.currentContext!.findRenderObject() as RenderBox; + final RenderBox parentRenderBox = + // ignore: avoid_as + _parentKey.currentContext!.findRenderObject() as RenderBox; + if (widget.maxPdfPageWidth != 0 && + widget.maxPdfPageWidth * _scale > + widget.viewportConstraints.maxWidth) { + _isContentOverflowed = true; + _overflowOrigin = childRenderBox.localToGlobal(Offset.zero).dx - + parentRenderBox.localToGlobal(Offset.zero).dx; + } else { + _isContentOverflowed = false; + } + WidgetsBinding.instance?.addPostFrameCallback((timeStamp) { + if (_isContentOverflowed) { + setState(() { + _overflowOrigin += (childRenderBox.localToGlobal(Offset.zero).dx - + parentRenderBox.localToGlobal(Offset.zero).dx) / + 2; + }); + } + }); + viewMatrix = _matrixTranslate( + viewMatrix!, Offset(_viewport.width, _viewport.height)); + } } - // This invoked whenever current and previous scale percentage changed if (widget.onZoomLevelChanged != null) { final double newZoomLevel = _scale; final double oldZoomLevel = _previousScale; if (newZoomLevel != oldZoomLevel) { - widget.onZoomLevelChanged(PdfZoomDetails(newZoomLevel, oldZoomLevel)); + widget.onZoomLevelChanged!(PdfZoomDetails(newZoomLevel, oldZoomLevel)); } } } /// Call the method according to property name. - void _onControllerValueChange({String property}) { + void _onControllerValueChange({String? property}) { if (property == 'zoomLevel') { _onZoomLevelChanged(); } } + /// Identifies whether pan must be restricted for web. + bool _restrictPanForWebSelectionMode() { + if (kIsWeb && + widget.interactionMode == PdfInteractionMode.selection && + _gestureType == _GestureType.pan && + _isMousePointer) { + return true; + } + return false; + } + /// Jumps horizontally in view port. void jumpHorizontally(double value) { - viewMatrix = _matrixTranslate(viewMatrix, Offset(value, 0)); + viewMatrix = _matrixTranslate( + viewMatrix!, Offset(-_viewport.width, _pdfController.scrollOffset.dy)); + viewMatrix = _matrixTranslate(viewMatrix!, Offset(value, 0)); } - /// resets PdfContainer view matrix + /// resets PdfContainer view matrix. void reset() { viewMatrix = Matrix4.identity(); } @@ -706,45 +872,41 @@ class PdfContainerState extends State void initState() { super.initState(); _pdfController = widget.pdfController; - _pdfController?.addListener(_onControllerValueChange); + _pdfController.addListener(_onControllerValueChange); viewMatrix = Matrix4.identity(); if (widget.initialZoomLevel > 1) { if (widget.initialZoomLevel >= _minScale && widget.initialZoomLevel <= _maxScale) { viewMatrix = _matrixScale( - viewMatrix, + viewMatrix!, widget.initialZoomLevel, ); _scale = widget.initialZoomLevel; _previousScale = _scale; } } - if (_pdfController.zoomLevel != null) { - if (_pdfController.zoomLevel < _minScale || - _pdfController.zoomLevel > _maxScale) { - _pdfController.zoomLevel = _minScale; - } else { - viewMatrix = _matrixScale( - viewMatrix, - (widget.initialZoomLevel > _pdfController.zoomLevel) - ? _pdfController.zoomLevel / widget.initialZoomLevel - : _pdfController.zoomLevel, - ); + if (_pdfController.zoomLevel < _minScale || + _pdfController.zoomLevel > _maxScale) { + _pdfController.zoomLevel = _minScale; + } else { + viewMatrix = _matrixScale( + viewMatrix!, + (widget.initialZoomLevel > _pdfController.zoomLevel) + ? _pdfController.zoomLevel / widget.initialZoomLevel + : _pdfController.zoomLevel, + ); - _scale = _pdfController.zoomLevel; - _previousScale = widget.initialZoomLevel; - } + _scale = _pdfController.zoomLevel; + _previousScale = widget.initialZoomLevel; } - _animationController = AnimationController( vsync: this, ); - if (widget.initialScrollOffset != null) { - Future.delayed(Duration.zero, () async { - viewMatrix = _matrixTranslate( - viewMatrix, Offset(widget.initialScrollOffset.dx, 0)); - }); - } + Future.delayed(Duration.zero, () async { + viewMatrix = _matrixTranslate( + viewMatrix!, Offset(widget.initialScrollOffset.dx, 0)); + }); + // Added a listener for the scrolling. widget.scrollController.addListener(_updateOrigin); } @@ -752,75 +914,170 @@ class PdfContainerState extends State @override void dispose() { _animationController.dispose(); - _pdfController?.removeListener(_onControllerValueChange); + _pdfController.removeListener(_onControllerValueChange); widget.scrollController.removeListener(_updateOrigin); super.dispose(); } @override Widget build(BuildContext context) { - final Widget child = Transform( - origin: _origin, - transform: viewMatrix, - child: KeyedSubtree( - key: _childKey, - child: Center( - child: SingleChildScrollView( - controller: widget.scrollController, - child: Column( - children: List.generate(widget.pdfController.pageCount, - (index) => widget.itemBuilder(context, index)).toList())), - ), - ), + final keyedSubTree = KeyedSubtree( + key: _childKey, + child: Center( + child: Column( + children: List.generate(widget.pdfController.pageCount, + (index) => widget.itemBuilder(context, index)).toList())), ); - + Widget scrollChild; + if (kIsWeb && + !widget.isMobileWebView && + (_isMousePointer && + widget.interactionMode == PdfInteractionMode.selection)) { + scrollChild = SingleChildScrollView( + controller: widget.scrollController, + physics: NeverScrollableScrollPhysics(), + child: keyedSubTree); + } else { + scrollChild = SingleChildScrollView( + controller: widget.scrollController, child: keyedSubTree); + } + bool isAligned = false; + AlignmentGeometry alignment = (kIsWeb && !widget.isMobileWebView) + ? (_isContentOverflowed ? Alignment.topLeft : Alignment.topCenter) + : Alignment.topLeft; + if (widget.maxPdfPageWidth != 0 && + widget.maxPdfPageWidth * _scale > widget.viewportConstraints.maxWidth && + (kIsWeb && !widget.isMobileWebView)) { + alignment = Alignment + .topLeft; //In Web, the content is overflowed, the scaling must be done from topleft + isAligned = true; + } + Widget child = Transform( + origin: _isContentOverflowed ? Offset(_overflowOrigin, 0.0) : _origin, + transform: viewMatrix!, + alignment: alignment, + child: scrollChild); + + if (kIsWeb && !widget.isMobileWebView) { + child = OverflowBox( + alignment: isAligned ? alignment : Alignment.topCenter, + minWidth: 0, + minHeight: 0, + maxWidth: double.infinity, + child: child, + ); + } // A GestureDetector allows the detection of panning and zooming gestures on // the child. - return Listener( - key: _parentKey, - child: RawGestureDetector( - behavior: HitTestBehavior.opaque, - // Necessary when panning off screen. - gestures: { - PdfScaleRecognizer: - GestureRecognizerFactoryWithHandlers( - () => PdfScaleRecognizer(), - (PdfScaleRecognizer instance) { - instance.onStart = (details) => _handleScaleStart(details); - instance.onUpdate = - (details) => _handleScaleUpdate(details: details); - instance.onEnd = (details) => _handleScaleEnd(details); - }, - ), - DoubleTapGestureRecognizer: GestureRecognizerFactoryWithHandlers< - DoubleTapGestureRecognizer>( - () => DoubleTapGestureRecognizer(), - (DoubleTapGestureRecognizer instances) { - //when user disable double tap zoom it does not allowed to perform double tap zoom operations - if (widget.enableDoubleTapZooming) { - instances.onDoubleTap = - () => _handleScaleUpdate(doubleTap: true); - } else { - instances.onDoubleTap = null; - } - }, - ), - TapRecognizer: GestureRecognizerFactoryWithHandlers( - () => TapRecognizer(), - (TapRecognizer instances) { - instances.onTapDown = (int num, details) { - if (!FocusScope.of(context).hasPrimaryFocus) { - FocusScope.of(context).unfocus(); + return MouseRegion( + cursor: _cursor, + onHover: (details) { + if (widget.interactionMode == PdfInteractionMode.pan) { + _cursor = SystemMouseCursors.grab; + } else { + _cursor = SystemMouseCursors.basic; + } + }, + child: Listener( + key: _parentKey, + onPointerSignal: (pointerSignal) { + if ((kIsWeb && !widget.isMobileWebView) && + pointerSignal is PointerScrollEvent) { + _isMousePointer = true; + final offset = + widget.scrollController.offset + pointerSignal.scrollDelta.dy; + if (pointerSignal.scrollDelta.dy.isNegative) { + widget.scrollController.jumpTo(math.max(0, offset)); + } else { + widget.scrollController.jumpTo(math.min( + widget.scrollController.position.maxScrollExtent, offset)); + } + } + }, + child: RawGestureDetector( + behavior: HitTestBehavior.opaque, + // Necessary when panning off screen. + gestures: { + PdfScaleRecognizer: + GestureRecognizerFactoryWithHandlers( + () => PdfScaleRecognizer(), + (PdfScaleRecognizer instance) { + instance.onStart = (details) => _handleScaleStart(details); + instance.onUpdate = + (details) => _handleScaleUpdate(details: details); + instance.onEnd = (details) => _handleScaleEnd(details); + }, + ), + DoubleTapGestureRecognizer: GestureRecognizerFactoryWithHandlers< + DoubleTapGestureRecognizer>( + () => DoubleTapGestureRecognizer(), + (DoubleTapGestureRecognizer instances) { + //when user disable double tap zoom it does not allowed to perform double tap zoom operations + if ((!kIsWeb && widget.enableDoubleTapZooming) || + (kIsWeb && + widget.interactionMode == PdfInteractionMode.pan) || + (kIsWeb && + widget.isMobileWebView && + widget.enableDoubleTapZooming)) { + // In Web, only for Pan mode double tap must be done. + instances.onDoubleTap = + () => _handleScaleUpdate(doubleTap: true); + } else { + instances.onDoubleTap = null; + } + }, + ), + TapRecognizer: + GestureRecognizerFactoryWithHandlers( + () => TapRecognizer(), + (TapRecognizer instances) { + instances.onTapDown = (int num, details) { + if (!FocusScope.of(context).hasPrimaryFocus) { + FocusScope.of(context).unfocus(); + } + _handleTapDown(details); + }; + }, + ), + PdfTapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => PdfTapGestureRecognizer(), + (PdfTapGestureRecognizer instances) { + if (kIsWeb && !widget.isMobileWebView) { + instances.onTap = () { + if (!_isLongPressed) { + widget.onTap(); + } + _isLongPressed = false; + }; + instances.onTapDown = (details) { + _isMousePointer = details.kind == PointerDeviceKind.mouse + ? true + : false; + }; } - _handleTapDown(details); - }; - }, - ), - }, - child: ClipRect( - clipBehavior: Clip.hardEdge, - child: child, - )), + }, + ), + PdfLongPressRecognizer: + GestureRecognizerFactoryWithHandlers( + () => PdfLongPressRecognizer(), + (PdfLongPressRecognizer instances) { + if (kIsWeb && !widget.isMobileWebView) { + instances.onLongPressStart = (details) { + _isLongPressed = true; + }; + instances.onLongPressEnd = (details) { + _isLongPressed = false; + }; + } + }, + ), + }, + child: ClipRect( + clipBehavior: Clip.hardEdge, + child: child, + )), + ), ); } @@ -828,7 +1085,7 @@ class PdfContainerState extends State Offset toScene(Offset viewportPoint) { // On viewportPoint, perform the inverse transformation of the scene to get // where the point would be in the scene before the transformation. - final Matrix4 inverseMatrix = Matrix4.inverted(viewMatrix); + final Matrix4 inverseMatrix = Matrix4.inverted(viewMatrix!); final Vector3 untransformed = inverseMatrix.transform3(Vector3( viewportPoint.dx, viewportPoint.dy, @@ -876,6 +1133,26 @@ class PdfScaleRecognizer extends ScaleGestureRecognizer { } } +/// Represents the tap gesture recognizer for PdfContainer. +class PdfTapGestureRecognizer extends TapGestureRecognizer { + @override + void rejectGesture(int pointer) { + if (getKindForPointer(pointer) == PointerDeviceKind.mouse) { + /// Since list view scroll interrupts scale gesture, overridden the reject gesture behavior. This piece of code is planned to be changed in future. + acceptGesture(pointer); + } + } +} + +/// Represents the long press gesture recognizer for PdfContainer. +class PdfLongPressRecognizer extends LongPressGestureRecognizer { + @override + void rejectGesture(int pointer) { + /// Since list view scroll interrupts scale gesture, overridden the reject gesture behavior. This piece of code is planned to be changed in future. + acceptGesture(pointer); + } +} + // A classification of relevant user gestures. Each contiguous user gesture is // represented by exactly one _GestureType. enum _GestureType { @@ -1001,7 +1278,7 @@ Offset _alignAxis(Offset offset, Axis axis) { // Given two points, return the axis where the distance between the points is // greatest. If they are equal, return null. -Axis _getPanAxis(Offset point1, Offset point2) { +Axis? _getPanAxis(Offset point1, Offset point2) { if (point1 == point2) { return null; } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_page_view.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_page_view.dart index 450813953..d807e4735 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_page_view.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_page_view.dart @@ -1,19 +1,31 @@ import 'dart:core'; +import 'dart:math'; import 'dart:typed_data'; import 'dart:ui'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/pdfviewer_canvas.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/pdfviewer_canvas.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:syncfusion_flutter_pdfviewer/src/common/mobile_helper.dart' + if (dart.library.html) 'package:syncfusion_flutter_pdfviewer/src/common/web_helper.dart' + as helper; + +import 'enums.dart'; /// Wrapper class of [Image] widget which shows the PDF pages as an image class PdfPageView extends StatefulWidget { /// Constructs PdfPageView instance with the given parameters. PdfPageView( - {Key key, + Key key, this.imageStream, + this.viewportGlobalRect, + this.interactionMode, this.width, this.height, this.pageSpacing, @@ -27,13 +39,15 @@ class PdfPageView extends StatefulWidget { this.onTextSelectionChanged, this.onTextSelectionDragStarted, this.onTextSelectionDragEnded, + this.onDocumentLinkNavigationInvoked, this.searchTextHighlightColor, this.textCollection, - this.pdfTextSearchResult}) + this.isMobileWebView, + this.pdfTextSearchResult) : super(key: key); /// Image stream - final Uint8List imageStream; + final Uint8List? imageStream; /// Width of page final double width; @@ -45,7 +59,10 @@ class PdfPageView extends StatefulWidget { final double pageSpacing; /// Instance of [PdfDocument] - final PdfDocument pdfDocument; + final PdfDocument? pdfDocument; + + /// Global rect of viewport region. + final Rect? viewportGlobalRect; /// If true, document link annotation is enabled. final bool enableDocumentLinkAnnotation; @@ -59,6 +76,9 @@ class PdfPageView extends StatefulWidget { /// Instance of [ScrollController] final ScrollController scrollController; + /// Indicates interaction mode of pdfViewer. + final PdfInteractionMode interactionMode; + /// Instance of [PdfViewerController] final PdfViewerController pdfViewerController; @@ -66,7 +86,7 @@ class PdfPageView extends StatefulWidget { final bool enableTextSelection; /// Triggers when text selection is changed. - final PdfTextSelectionChangedCallback onTextSelectionChanged; + final PdfTextSelectionChangedCallback? onTextSelectionChanged; /// Triggers when text selection dragging started. final VoidCallback onTextSelectionDragStarted; @@ -74,15 +94,21 @@ class PdfPageView extends StatefulWidget { /// Triggers when text selection dragging ended. final VoidCallback onTextSelectionDragEnded; - ///Highlighting color of searched text + /// Highlighting color of searched text final Color searchTextHighlightColor; - ///searched text details - final List textCollection; + /// Searched text details + final List? textCollection; /// PdfTextSearchResult instance final PdfTextSearchResult pdfTextSearchResult; + /// Triggered while document link navigation. + final Function(double value) onDocumentLinkNavigationInvoked; + + /// If true,MobileWebView is enabled.Default value is false. + final bool isMobileWebView; + @override State createState() { return PdfPageViewState(); @@ -91,12 +117,36 @@ class PdfPageView extends StatefulWidget { /// State for [PdfPageView] class PdfPageViewState extends State { - SfPdfViewerThemeData _pdfViewerThemeData; + SfPdfViewerThemeData? _pdfViewerThemeData; final GlobalKey _canvasKey = GlobalKey(); + bool _isTouchPointer = false; + bool _isSecondaryTap = false; + final double _jumpOffset = 10.0; + SystemMouseCursor _cursor = SystemMouseCursors.basic; + int _lastTap = DateTime.now().millisecondsSinceEpoch; + int _consecutiveTaps = 1; + + /// focus node of pdf page view. + FocusNode focusNode = FocusNode(); /// CanvasRenderBox getter for accessing canvas properties. - CanvasRenderBox get canvasRenderBox => - _canvasKey?.currentContext?.findRenderObject(); + CanvasRenderBox? get canvasRenderBox => + _canvasKey.currentContext?.findRenderObject() != null + ? + // ignore: avoid_as + _canvasKey.currentContext?.findRenderObject() as CanvasRenderBox + : null; + + @override + void initState() { + if (kIsWeb && !widget.isMobileWebView) { + helper.preventDefaultMenu(); + focusNode.addListener(() { + helper.hasPrimaryFocus = focusNode.hasFocus; + }); + } + super.initState(); + } @override void didChangeDependencies() { @@ -106,82 +156,350 @@ class PdfPageViewState extends State { @override void dispose() { + PaintingBinding.instance?.imageCache?.clear(); + PaintingBinding.instance?.imageCache?.clearLiveImages(); _pdfViewerThemeData = null; super.dispose(); } + void _triggerTextSelectionCallback() { + if (widget.onTextSelectionChanged != null) { + widget + .onTextSelectionChanged!(PdfTextSelectionChangedDetails(null, null)); + } + } + + void _scroll(bool isReachedTop, bool isSelectionScroll) { + if (isSelectionScroll) { + canvasRenderBox!.getSelectionDetails().endBubbleY = + canvasRenderBox!.getSelectionDetails().endBubbleY! + + (isReachedTop ? -3 : 3); + } + + final double position = widget.scrollController.offset + + (isReachedTop ? -_jumpOffset : _jumpOffset); + widget.scrollController.animateTo(position, + duration: Duration(milliseconds: 50), curve: Curves.ease); + } + + void _scrollWhileSelection() { + if (canvasRenderBox != null && + canvasRenderBox!.getSelectionDetails().isCursorExit && + canvasRenderBox!.getSelectionDetails().mouseSelectionEnabled) { + final TextSelectionHelper details = + canvasRenderBox!.getSelectionDetails(); + final int viewId = canvasRenderBox!.getSelectionDetails().viewId!; + if (details.isCursorReachedTop && + widget.pdfViewerController.pageNumber >= viewId + 1) { + _scroll(details.isCursorReachedTop, true); + } else if (!details.isCursorReachedTop && + widget.pdfViewerController.pageNumber <= viewId + 1) { + _scroll(details.isCursorReachedTop, true); + } + if (widget.onTextSelectionChanged != null) { + widget.onTextSelectionChanged!( + PdfTextSelectionChangedDetails(null, null)); + } + } + } + + void _updateSelectionPan(DragUpdateDetails details) { + if (canvasRenderBox != null) { + final TextSelectionHelper helper = canvasRenderBox!.getSelectionDetails(); + if (widget.viewportGlobalRect != null && + !widget.viewportGlobalRect!.contains(details.globalPosition) && + details.globalPosition.dx <= widget.viewportGlobalRect!.right && + details.globalPosition.dx >= widget.viewportGlobalRect!.left) { + if (details.globalPosition.dy <= widget.viewportGlobalRect!.top) { + helper.isCursorReachedTop = true; + } else { + helper.isCursorReachedTop = false; + } + helper.isCursorExit = true; + if (helper.initialScrollOffset == 0) { + helper.initialScrollOffset = widget.scrollController.offset; + } + } else if (helper.isCursorExit) { + if (helper.isCursorReachedTop) { + helper.finalScrollOffset = + widget.scrollController.offset - _jumpOffset; + } else { + helper.finalScrollOffset = + widget.scrollController.offset + _jumpOffset; + } + helper.isCursorExit = false; + } + } + } + @override Widget build(BuildContext context) { + PaintingBinding.instance?.imageCache?.clear(); + PaintingBinding.instance?.imageCache?.clearLiveImages(); if (widget.imageStream != null) { + _scrollWhileSelection(); final Widget page = Container( height: widget.height + widget.pageSpacing, child: Column(children: [ - Image.memory(widget.imageStream, + Image.memory(widget.imageStream!, width: widget.width, height: widget.height, fit: BoxFit.fitWidth, alignment: Alignment.center), Container( height: widget.pageSpacing, - color: _pdfViewerThemeData.backgroundColor, + color: _pdfViewerThemeData!.backgroundColor, ) ])); - if (widget.textCollection != null) { - final Widget canvas = PdfViewerCanvas( - key: _canvasKey, - pdfDocument: widget.pdfDocument, - width: widget.width, + final Widget pdfPage = (kIsWeb && !widget.isMobileWebView) + ? Container( + height: widget.height + widget.pageSpacing, + width: widget.width, + child: page, + ) + : Container( + color: Colors.white, + child: page, + ); + final Widget canvasContainer = Container( height: widget.height, - pageIndex: widget.pageIndex, - pdfPages: widget.pdfPages, - scrollController: widget.scrollController, - pdfViewerController: widget.pdfViewerController, - enableDocumentLinkNavigation: widget.enableDocumentLinkAnnotation, - onTextSelectionChanged: widget.onTextSelectionChanged, - onTextSelectionDragStarted: widget.onTextSelectionDragStarted, - onTextSelectionDragEnded: widget.onTextSelectionDragEnded, - enableTextSelection: widget.enableTextSelection, - searchTextHighlightColor: widget.searchTextHighlightColor, - textCollection: widget.textCollection, - pdfTextSearchResult: widget.pdfTextSearchResult, - ); - + width: widget.width, + child: PdfViewerCanvas( + _canvasKey, + widget.height, + widget.width, + widget.pdfDocument, + widget.pageIndex, + widget.pdfPages, + widget.interactionMode, + widget.scrollController, + widget.pdfViewerController, + widget.enableDocumentLinkAnnotation, + widget.enableTextSelection, + widget.onTextSelectionChanged, + widget.onTextSelectionDragStarted, + widget.onTextSelectionDragEnded, + widget.onDocumentLinkNavigationInvoked, + widget.textCollection, + widget.searchTextHighlightColor, + widget.pdfTextSearchResult, + widget.isMobileWebView, + )); + final Widget canvas = (kIsWeb && + !widget.isMobileWebView && + canvasRenderBox != null) + ? Listener( + onPointerSignal: (details) { + if (canvasRenderBox! + .getSelectionDetails() + .mouseSelectionEnabled) { + _triggerTextSelectionCallback(); + } + }, + onPointerDown: (details) { + if (kIsWeb && !widget.isMobileWebView) { + final int now = DateTime.now().millisecondsSinceEpoch; + if (now - _lastTap <= 500) { + _consecutiveTaps++; + if (_consecutiveTaps == 2 && !_isSecondaryTap) { + canvasRenderBox!.handleDoubleTapDown(details); + } + if (_consecutiveTaps == 3 && !_isSecondaryTap) { + canvasRenderBox!.handleTripleTapDown(details); + } + } else { + _consecutiveTaps = 1; + } + _lastTap = now; + } + }, + child: RawKeyboardListener( + focusNode: focusNode, + onKey: (event) { + if ((canvasRenderBox! + .getSelectionDetails() + .mouseSelectionEnabled || + canvasRenderBox! + .getSelectionDetails() + .selectionEnabled) && + event.isControlPressed && + event.logicalKey == LogicalKeyboardKey.keyC) { + Clipboard.setData(ClipboardData( + text: + canvasRenderBox!.getSelectionDetails().copiedText ?? + '')); + } + if (event.isControlPressed && + event.logicalKey == LogicalKeyboardKey.digit0) { + widget.pdfViewerController.zoomLevel = 1.0; + } + if (event.isControlPressed && + event.logicalKey == LogicalKeyboardKey.minus) { + if (event.runtimeType.toString() == 'RawKeyDownEvent') { + double zoomLevel = widget.pdfViewerController.zoomLevel; + if (zoomLevel >= 1.0 && zoomLevel <= 1.25) { + zoomLevel = 1.0; + } else if (zoomLevel > 1.25 && zoomLevel <= 1.50) { + zoomLevel = 1.25; + } else if (zoomLevel > 1.50 && zoomLevel <= 2.0) { + zoomLevel = 1.50; + } else { + zoomLevel = 2.0; + } + widget.pdfViewerController.zoomLevel = zoomLevel; + } + } + if (event.isControlPressed && + event.logicalKey == LogicalKeyboardKey.equal) { + if (event.runtimeType.toString() == 'RawKeyDownEvent') { + double zoomLevel = widget.pdfViewerController.zoomLevel; + if (zoomLevel >= 1.0 && zoomLevel < 1.25) { + zoomLevel = 1.25; + } else if (zoomLevel >= 1.25 && zoomLevel < 1.50) { + zoomLevel = 1.50; + } else if (zoomLevel >= 1.50 && zoomLevel < 2.0) { + zoomLevel = 2.0; + } else { + zoomLevel = 3.0; + } + widget.pdfViewerController.zoomLevel = zoomLevel; + } + } + if (event.runtimeType.toString() == 'RawKeyDownEvent') { + if (event.logicalKey == LogicalKeyboardKey.home) { + widget.pdfViewerController.jumpToPage(1); + } else if (event.logicalKey == LogicalKeyboardKey.end) { + widget.pdfViewerController + .jumpToPage(widget.pdfViewerController.pageCount); + } else if (event.logicalKey == + LogicalKeyboardKey.arrowRight) { + widget.pdfViewerController.nextPage(); + } else if (event.logicalKey == + LogicalKeyboardKey.arrowLeft) { + widget.pdfViewerController.previousPage(); + } + } + if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + _scroll(true, false); + } + if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + _scroll(false, false); + } + }, + child: MouseRegion( + cursor: _cursor, + onHover: (details) { + if (canvasRenderBox != null) { + if (widget.interactionMode == + PdfInteractionMode.selection) { + final bool isText = canvasRenderBox! + .findTextWhileHover(details.localPosition) != + null; + final bool isTOC = + canvasRenderBox!.findTOC(details.localPosition); + if (isTOC) { + _cursor = SystemMouseCursors.click; + } else if (isText && !isTOC) { + _cursor = SystemMouseCursors.text; + } else { + _cursor = SystemMouseCursors.basic; + } + } else { + final bool isTOC = + canvasRenderBox!.findTOC(details.localPosition); + if (isTOC) { + _cursor = SystemMouseCursors.click; + } else if (_cursor != SystemMouseCursors.grab) { + _cursor = SystemMouseCursors.grab; + } + } + } + }, + child: GestureDetector( + onPanStart: (details) { + if (widget.interactionMode == PdfInteractionMode.pan) { + _cursor = SystemMouseCursors.grabbing; + } + if (!focusNode.hasPrimaryFocus) { + focusNode.requestFocus(); + } + _triggerTextSelectionCallback(); + canvasRenderBox?.handleDragStart(details); + }, + onPanUpdate: (details) { + _updateSelectionPan(details); + if (widget.interactionMode == PdfInteractionMode.pan) { + final newOffset = + widget.scrollController.offset - details.delta.dy; + if (details.delta.dy.isNegative) { + widget.scrollController.jumpTo(max(0, newOffset)); + } else { + widget.scrollController.jumpTo(min( + widget + .scrollController.position.maxScrollExtent, + newOffset)); + } + } + canvasRenderBox?.handleDragUpdate(details); + }, + onPanEnd: (details) { + if (canvasRenderBox != null) { + canvasRenderBox!.getSelectionDetails().isCursorExit = + false; + } + if (!focusNode.hasPrimaryFocus) { + focusNode.requestFocus(); + } + if (widget.interactionMode == PdfInteractionMode.pan) { + _cursor = SystemMouseCursors.grab; + } + canvasRenderBox!.handleDragEnd(details); + }, + onPanDown: (details) { + canvasRenderBox?.handleDragDown(details); + }, + onTapUp: (details) { + if (!focusNode.hasPrimaryFocus) { + focusNode.requestFocus(); + } + _triggerTextSelectionCallback(); + canvasRenderBox?.handleTapUp(details); + }, + onTapDown: (details) { + _isSecondaryTap = false; + _isTouchPointer = + details.kind == PointerDeviceKind.touch + ? true + : false; + canvasRenderBox?.handleTapDown(details); + }, + onLongPressStart: (details) { + _triggerTextSelectionCallback(); + if (!focusNode.hasPrimaryFocus) { + focusNode.requestFocus(); + } + if (_isTouchPointer) { + canvasRenderBox?.handleLongPressStart(details); + } + _isTouchPointer = false; + }, + onSecondaryTapDown: (details) { + _isSecondaryTap = true; + }, + child: canvasContainer), + ), + ), + ) + : canvasContainer; + if (widget.textCollection != null) { return Stack( children: [ - Container( - color: Colors.white, - child: page, - ), - Container( - height: widget.height, width: widget.width, child: canvas), + pdfPage, + canvas, ], ); } else { - return Stack(children: [ - Container( - color: Colors.white, - child: page, - ), - Container( - width: widget.width, - height: widget.height, - child: PdfViewerCanvas( - key: _canvasKey, - pdfDocument: widget.pdfDocument, - width: widget.width, - height: widget.height, - pageIndex: widget.pageIndex, - pdfPages: widget.pdfPages, - scrollController: widget.scrollController, - pdfViewerController: widget.pdfViewerController, - enableDocumentLinkNavigation: - widget.enableDocumentLinkAnnotation, - onTextSelectionChanged: widget.onTextSelectionChanged, - onTextSelectionDragStarted: widget.onTextSelectionDragStarted, - onTextSelectionDragEnded: widget.onTextSelectionDragEnded, - enableTextSelection: widget.enableTextSelection), - ), - ]); + return Stack(children: [pdfPage, canvas]); } } else { return Container( @@ -192,7 +510,7 @@ class PdfPageViewState extends State { border: Border( bottom: BorderSide( width: widget.pageSpacing, - color: _pdfViewerThemeData.backgroundColor), + color: _pdfViewerThemeData!.backgroundColor), ), ), ); diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdftextline.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdftextline.dart new file mode 100644 index 000000000..f18295cce --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdftextline.dart @@ -0,0 +1,34 @@ +import 'dart:ui'; + +/// The class PdfTextLine represents the selected text line +/// which contains the text and the bounding rectangular size relative to the page dimension +/// and page number in which text selection is happened. +class PdfTextLine { + /// Constructor of PdfTextLine. + PdfTextLine(Rect bounds, String text, int pageNumber) { + _bounds = bounds; + _text = text; + _pageNumber = pageNumber; + } + + late Rect _bounds; + + /// Gets the bounds of the selected region line relative to the page dimension. + Rect get bounds { + return _bounds; + } + + late String _text; + + /// Gets the text of the selected region line. + String get text { + return _text; + } + + late int _pageNumber; + + /// Gets the page number in which text is selected. + int get pageNumber { + return _pageNumber; + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_callback_details.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_callback_details.dart index 0381b58b6..b5ffbba8a 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_callback_details.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_callback_details.dart @@ -6,21 +6,21 @@ import 'package:syncfusion_flutter_pdf/pdf.dart'; /// such as [newPageNumber], [oldPageNumber], [isFirstPage] and [isLastPage]. class PdfPageChangedDetails { /// Creates details for [SfPdfViewer.onPageChanged] callback. - PdfPageChangedDetails({int newPage, int oldPage, bool isFirst, bool isLast}) { + PdfPageChangedDetails(int newPage, int oldPage, bool isFirst, bool isLast) { _newPageNumber = newPage; _oldPageNumber = oldPage; _isFirstPage = isFirst; _isLastPage = isLast; } - bool _isFirstPage; + late bool _isFirstPage; /// Indicates whether the new page in the [SfPdfViewer] is the first page or not. bool get isFirstPage { return _isFirstPage; } - bool _isLastPage; + late bool _isLastPage; /// Indicates whether the new page in the [SfPdfViewer] is the last page or not. bool get isLastPage { @@ -50,14 +50,15 @@ class PdfZoomDetails { _newZoomLevel = newZoomLevel; _oldZoomLevel = oldZoomLevel; } - double _newZoomLevel; + + late double _newZoomLevel; /// Current zoom level to which the document is zoomed to. double get newZoomLevel { return _newZoomLevel; } - double _oldZoomLevel; + late double _oldZoomLevel; /// Zoom level from which the zooming was initiated. double get oldZoomLevel { @@ -72,7 +73,8 @@ class PdfDocumentLoadedDetails { PdfDocumentLoadedDetails(PdfDocument document) { _document = document; } - PdfDocument _document; + + late PdfDocument _document; /// Loaded [PdfDocument] instance. PdfDocument get document { @@ -88,14 +90,15 @@ class PdfDocumentLoadFailedDetails { _error = error; _description = description; } - String _error; + + late String _error; /// Error title of the document load failed condition. String get error { return _error; } - String _description; + late String _description; /// Error description of the document load failed condition. String get description { @@ -108,22 +111,22 @@ class PdfDocumentLoadFailedDetails { class PdfTextSelectionChangedDetails { /// PdfTextSelectionChangedDetails( - String selectedText, Rect globalSelectedRegion) { + String? selectedText, Rect? globalSelectedRegion) { _selectedText = selectedText; _globalSelectedRegion = globalSelectedRegion; } - String _selectedText; + String? _selectedText; /// Selected text value. - String get selectedText { + String? get selectedText { return _selectedText; } - Rect _globalSelectedRegion; + Rect? _globalSelectedRegion; /// The global bounds information of the selected text region. - Rect get globalSelectedRegion { + Rect? get globalSelectedRegion { return _globalSelectedRegion; } } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart index 5d563ffdc..c283d404a 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -5,6 +6,10 @@ import 'package:syncfusion_flutter_pdf/pdf.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_page_view.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:syncfusion_flutter_pdfviewer/src/control/pdftextline.dart'; + +import 'enums.dart'; /// Instance of TextSelectionHelper. TextSelectionHelper _textSelectionHelper = TextSelectionHelper(); @@ -12,24 +17,27 @@ TextSelectionHelper _textSelectionHelper = TextSelectionHelper(); /// [PdfViewerCanvas] is a layer above the PDF page over which annotations, text selection, and text search UI level changes will be applied. class PdfViewerCanvas extends LeafRenderObjectWidget { /// Constructs PdfViewerCanvas instance with the given parameters. - PdfViewerCanvas({ - Key key, - this.height, - this.width, - this.pdfDocument, - this.pageIndex, - this.pdfPages, - this.scrollController, - this.pdfViewerController, - this.enableDocumentLinkNavigation, - this.enableTextSelection, - this.onTextSelectionChanged, - this.onTextSelectionDragStarted, - this.onTextSelectionDragEnded, - this.textCollection, - this.searchTextHighlightColor, - this.pdfTextSearchResult, - }) : super(key: key); + PdfViewerCanvas( + Key key, + this.height, + this.width, + this.pdfDocument, + this.pageIndex, + this.pdfPages, + this.interactionMode, + this.scrollController, + this.pdfViewerController, + this.enableDocumentLinkNavigation, + this.enableTextSelection, + this.onTextSelectionChanged, + this.onTextSelectionDragStarted, + this.onTextSelectionDragEnded, + this.onDocumentLinkNavigationInvoked, + this.textCollection, + this.searchTextHighlightColor, + this.pdfTextSearchResult, + this.isMobileWebView) + : super(key: key); /// Height of page final double height; @@ -41,7 +49,7 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { final double width; /// Instance of [PdfDocument] - final PdfDocument pdfDocument; + final PdfDocument? pdfDocument; /// Index of page final int pageIndex; @@ -65,37 +73,48 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { final VoidCallback onTextSelectionDragEnded; /// Triggers when text selection is changed. - final PdfTextSelectionChangedCallback onTextSelectionChanged; + final PdfTextSelectionChangedCallback? onTextSelectionChanged; ///Highlighting color of searched text final Color searchTextHighlightColor; ///searched text details - final List textCollection; + final List? textCollection; /// PdfTextSearchResult instance final PdfTextSearchResult pdfTextSearchResult; + /// Triggered while document link navigation. + final Function(double value) onDocumentLinkNavigationInvoked; + + /// Indicates interaction mode of pdfViewer. + final PdfInteractionMode interactionMode; + + /// If true,MobileWebView is enabled.Default value is false. + final bool isMobileWebView; + @override RenderObject createRenderObject(BuildContext context) { return CanvasRenderBox( - height: height, - width: width, - context: context, - pdfDocument: pdfDocument, - pageIndex: pageIndex, - pdfPages: pdfPages, - scrollController: scrollController, - pdfViewerController: pdfViewerController, - enableDocumentLinkNavigation: enableDocumentLinkNavigation, - enableTextSelection: enableTextSelection, - onTextSelectionChanged: onTextSelectionChanged, - onTextSelectionDragStarted: onTextSelectionDragStarted, - onTextSelectionDragEnded: onTextSelectionDragEnded, - textCollection: textCollection, - searchTextHighlightColor: searchTextHighlightColor, - pdfTextSearchResult: pdfTextSearchResult, - ); + height, + width, + context, + pdfDocument, + pdfPages, + pageIndex, + interactionMode, + scrollController, + pdfViewerController, + enableDocumentLinkNavigation, + enableTextSelection, + onTextSelectionChanged, + onTextSelectionDragStarted, + onTextSelectionDragEnded, + onDocumentLinkNavigationInvoked, + textCollection, + searchTextHighlightColor, + isMobileWebView, + pdfTextSearchResult); } @override @@ -105,6 +124,7 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { ..width = width ..pageIndex = pageIndex ..textCollection = textCollection + ..interactionMode = interactionMode ..searchTextHighlightColor = searchTextHighlightColor ..pdfTextSearchResult = pdfTextSearchResult; @@ -118,12 +138,13 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { class CanvasRenderBox extends RenderBox { /// Constructor of CanvasRenderBox. CanvasRenderBox( - {this.height, + this.height, this.width, this.context, this.pdfDocument, this.pdfPages, this.pageIndex, + this.interactionMode, this.scrollController, this.pdfViewerController, this.enableDocumentLinkNavigation, @@ -131,79 +152,91 @@ class CanvasRenderBox extends RenderBox { this.onTextSelectionChanged, this.onTextSelectionDragStarted, this.onTextSelectionDragEnded, + this.onDocumentLinkNavigationInvoked, this.textCollection, this.searchTextHighlightColor, - this.pdfTextSearchResult}) { + this.isMobileWebView, + this.pdfTextSearchResult) { final GestureArenaTeam team = GestureArenaTeam(); _tapRecognizer = TapGestureRecognizer() - ..onTapUp = _handleTapUp - ..onTapDown = _handleTapDown; + ..onTapUp = handleTapUp + ..onTapDown = handleTapDown; _longPressRecognizer = LongPressGestureRecognizer() - ..onLongPressStart = _handleLongPressStart; + ..onLongPressStart = handleLongPressStart; _dragRecognizer = PanGestureRecognizer() ..team = team - ..onStart = _handleDragStart - ..onUpdate = _handleDragUpdate - ..onEnd = _handleDragEnd - ..onDown = _handleDragDown; + ..onStart = handleDragStart + ..onUpdate = handleDragUpdate + ..onEnd = handleDragEnd + ..onDown = handleDragDown; _verticalDragRecognizer = VerticalDragGestureRecognizer() ..team = team - ..onStart = _handleDragStart - ..onUpdate = _handleDragUpdate - ..onEnd = _handleDragEnd; + ..onStart = handleDragStart + ..onUpdate = handleDragUpdate + ..onEnd = handleDragEnd; } /// Height of Page - double height; + late double height; /// Width of page - double width; + late double width; /// Index of page - int pageIndex; + late int pageIndex; /// Instance of [PdfDocument] - final PdfDocument pdfDocument; + late final PdfDocument? pdfDocument; /// BuildContext for canvas. - final BuildContext context; + late final BuildContext context; /// If false,text selection is disabled.Default value is true. - final bool enableTextSelection; + late final bool enableTextSelection; /// If true, document link annotation is enabled. - final bool enableDocumentLinkNavigation; + late final bool enableDocumentLinkNavigation; /// Information about PdfPage - final Map pdfPages; + late final Map pdfPages; /// Instance of [ScrollController] - final ScrollController scrollController; + late final ScrollController scrollController; /// PdfViewer controller. - final PdfViewerController pdfViewerController; + late final PdfViewerController pdfViewerController; /// Triggers when text selection dragging started. - final VoidCallback onTextSelectionDragStarted; + late final VoidCallback onTextSelectionDragStarted; /// Triggers when text selection dragging ended. - final VoidCallback onTextSelectionDragEnded; + late final VoidCallback onTextSelectionDragEnded; /// Triggers when text selection is changed. - final PdfTextSelectionChangedCallback onTextSelectionChanged; + late final PdfTextSelectionChangedCallback? onTextSelectionChanged; + + /// Triggered while document link navigation. + late final Function(double value) onDocumentLinkNavigationInvoked; + + /// Indicates interaction mode of pdfViewer. + late PdfInteractionMode interactionMode; ///Highlighting color of searched text - Color searchTextHighlightColor; + late Color searchTextHighlightColor; ///searched text details - List textCollection; + late List? textCollection; /// PdfTextSearchResult instance - PdfTextSearchResult pdfTextSearchResult; + late PdfTextSearchResult pdfTextSearchResult; + + /// If true,MobileWebView is enabled.Default value is false. + late final bool isMobileWebView; - int _viewId; - double _totalPageOffset; + int? _viewId; + double? _totalPageOffset; bool _isTOCTapped = false; + bool _isMousePointer = false; double _startBubbleTapX = 0; double _endBubbleTapX = 0; final double _bubbleSize = 16.0; @@ -211,19 +244,21 @@ class CanvasRenderBox extends RenderBox { bool _longPressed = false; bool _startBubbleDragging = false; bool _endBubbleDragging = false; - double _zoomPercentage; - Offset _tapDetails; - Offset _dragDetails; - Offset _dragDownDetails; - TapGestureRecognizer _tapRecognizer; - PanGestureRecognizer _dragRecognizer; - LongPressGestureRecognizer _longPressRecognizer; - VerticalDragGestureRecognizer _verticalDragRecognizer; - PdfDocumentLinkAnnotation _documentLinkAnnotation; + double _zoomPercentage = 0.0; + Offset? _tapDetails; + Offset? _dragDetails; + Offset? _dragDownDetails; + Color? _selectionColor; + Color? _selectionHandleColor; + late TapGestureRecognizer _tapRecognizer; + late PanGestureRecognizer _dragRecognizer; + late LongPressGestureRecognizer _longPressRecognizer; + late VerticalDragGestureRecognizer _verticalDragRecognizer; + late PdfDocumentLinkAnnotation? _documentLinkAnnotation; @override void handleEvent(PointerEvent event, BoxHitTestEntry entry) { - if (event is PointerDownEvent) { + if (event is PointerDownEvent && (!kIsWeb || (kIsWeb && isMobileWebView))) { _tapRecognizer.addPointer(event); _longPressRecognizer.addPointer(event); _dragRecognizer.addPointer(event); @@ -245,38 +280,50 @@ class CanvasRenderBox extends RenderBox { bool get sizedByParent => true; /// Handles the tap up event - void _handleTapUp(TapUpDetails details) { - if (textCollection == null) { + void handleTapUp(TapUpDetails details) { + if (kIsWeb && + !isMobileWebView && + !_textSelectionHelper.enableTapSelection) { + clearMouseSelection(); + } + if (textCollection == null && !_textSelectionHelper.enableTapSelection) { clearSelection(); } - if (enableDocumentLinkNavigation) { + if (_textSelectionHelper.enableTapSelection && + _textSelectionHelper.mouseSelectionEnabled) { + _triggerValueCallback(); + } + if (enableDocumentLinkNavigation && pdfDocument != null) { _viewId = pageIndex; final double heightPercentage = - pdfDocument.pages[_viewId].size.height / height; - final PdfPage page = pdfDocument.pages[pageIndex]; + pdfDocument!.pages[_viewId!].size.height / height; + final PdfPage page = pdfDocument!.pages[pageIndex]; final int length = page.annotations.count; for (int index = 0; index < length; index++) { if (page.annotations[index] is PdfDocumentLinkAnnotation) { - _documentLinkAnnotation = page.annotations[index]; - + _documentLinkAnnotation = + // ignore: avoid_as + page.annotations[index] as PdfDocumentLinkAnnotation; + assert(_documentLinkAnnotation != null); if ((details.localPosition.dy >= - (_documentLinkAnnotation.bounds.top / heightPercentage)) && + (_documentLinkAnnotation!.bounds.top / heightPercentage)) && (details.localPosition.dy <= - (_documentLinkAnnotation.bounds.bottom / heightPercentage)) && + (_documentLinkAnnotation!.bounds.bottom / + heightPercentage)) && (details.localPosition.dx >= - (_documentLinkAnnotation.bounds.left / heightPercentage)) && + (_documentLinkAnnotation!.bounds.left / heightPercentage)) && (details.localPosition.dx <= - (_documentLinkAnnotation.bounds.right / heightPercentage))) { - if (_documentLinkAnnotation?.destination?.page != null) { + (_documentLinkAnnotation!.bounds.right / heightPercentage))) { + if (_documentLinkAnnotation!.destination?.page != null) { _isTOCTapped = true; final PdfPage destinationPage = - (_documentLinkAnnotation?.destination?.page); + (_documentLinkAnnotation!.destination!.page); final int destinationPageIndex = - pdfDocument.pages.indexOf(destinationPage) + 1; + pdfDocument!.pages.indexOf(destinationPage) + 1; final double destinationPageOffset = - _documentLinkAnnotation.destination.location.dy / + _documentLinkAnnotation!.destination!.location.dy / heightPercentage; - _totalPageOffset = pdfPages[destinationPageIndex].pageOffset + + _totalPageOffset = pdfPages[destinationPageIndex]!.pageOffset + destinationPageOffset; _viewId = pageIndex; @@ -310,24 +357,36 @@ class CanvasRenderBox extends RenderBox { } /// Handles the tap down event - void _handleTapDown(TapDownDetails details) { + void handleTapDown(TapDownDetails details) { + if (_textSelectionHelper.enableTapSelection && + _textSelectionHelper.mouseSelectionEnabled) { + _triggerValueCallback(); + } _tapDetails = details.localPosition; + if (kIsWeb && + !isMobileWebView && + enableTextSelection && + interactionMode == PdfInteractionMode.selection) { + _isMousePointer = details.kind == PointerDeviceKind.mouse ? true : false; + } } - /// Handles the long press started event. - void _handleLongPressStart(LongPressStartDetails details) { - if (enableTextSelection) { - if (_textSelectionHelper.selectionEnabled) { - clearSelection(); + /// Handles the long press started event.cursorMode + void handleLongPressStart(LongPressStartDetails details) { + if (kIsWeb && !isMobileWebView && pdfDocument != null) { + clearMouseSelection(); + final bool isTOC = findTOC(details.localPosition); + if (interactionMode == PdfInteractionMode.selection && !isTOC) { + enableSelection(); } - _longPressed = true; - _textSelectionHelper.viewId = pageIndex; - markNeedsPaint(); + } else { + enableSelection(); } } /// Handles the Drag start event. - void _handleDragStart(DragStartDetails details) { + void handleDragStart(DragStartDetails details) { + _enableMouseSelection(details, 'DragStart'); if (_textSelectionHelper.selectionEnabled) { final bool isStartDragPossible = _checkStartBubblePosition(); final bool isEndDragPossible = _checkEndBubblePosition(); @@ -342,92 +401,212 @@ class CanvasRenderBox extends RenderBox { } /// Handles the drag update event. - void _handleDragUpdate(DragUpdateDetails details) { + void handleDragUpdate(DragUpdateDetails details) { + if (kIsWeb && !isMobileWebView && _isMousePointer) { + if (_textSelectionHelper.mouseSelectionEnabled && + !_textSelectionHelper.isCursorExit) { + _textSelectionHelper.endBubbleX = details.localPosition.dx; + _textSelectionHelper.endBubbleY = details.localPosition.dy + + (_textSelectionHelper.finalScrollOffset - + _textSelectionHelper.initialScrollOffset); + markNeedsPaint(); + } + } if (_textSelectionHelper.selectionEnabled) { _dragDetails = details.localPosition; if (_startBubbleDragging) { _startBubbleTapX = details.localPosition.dx; markNeedsPaint(); - if (onTextSelectionChanged != null) { - onTextSelectionChanged(PdfTextSelectionChangedDetails(null, null)); - } + _triggerNullCallback(); } else if (_endBubbleDragging) { _endBubbleTapX = details.localPosition.dx; markNeedsPaint(); if (onTextSelectionChanged != null) { - onTextSelectionChanged(PdfTextSelectionChangedDetails(null, null)); + onTextSelectionChanged!(PdfTextSelectionChangedDetails(null, null)); } + _triggerNullCallback(); } } } /// Handles the drag end event. - void _handleDragEnd(DragEndDetails details) { + void handleDragEnd(DragEndDetails details) { + if (kIsWeb && + !isMobileWebView && + _textSelectionHelper.mouseSelectionEnabled) { + onTextSelectionDragEnded(); + _triggerValueCallback(); + } if (_textSelectionHelper.selectionEnabled) { if (_startBubbleDragging) { _startBubbleDragging = false; onTextSelectionDragEnded(); - if (onTextSelectionChanged != null) { - onTextSelectionChanged(PdfTextSelectionChangedDetails( - _textSelectionHelper.copiedText, - _textSelectionHelper.globalSelectedRegion)); - } + _triggerValueCallback(); } if (_endBubbleDragging) { _endBubbleDragging = false; onTextSelectionDragEnded(); - if (onTextSelectionChanged != null) { - onTextSelectionChanged(PdfTextSelectionChangedDetails( - _textSelectionHelper.copiedText, - _textSelectionHelper.globalSelectedRegion)); - } + _triggerValueCallback(); } } } /// Handles the drag down event. - void _handleDragDown(DragDownDetails details) { + void handleDragDown(DragDownDetails details) { _dragDownDetails = details.localPosition; } + /// Handles the double tap down event. + void handleDoubleTapDown(details) { + _textSelectionHelper.enableTapSelection = true; + _enableMouseSelection(details, 'DoubleTap'); + } + + /// Handles the triple tap down event. + void handleTripleTapDown(details) { + _textSelectionHelper.enableTapSelection = true; + _enableMouseSelection(details, 'TripleTap'); + } + + /// Enable mouse selection for mouse pointer,double tap and triple tap selection. + void _enableMouseSelection(details, String gestureType) { + if (kIsWeb && + !isMobileWebView && + enableTextSelection && + interactionMode == PdfInteractionMode.selection) { + final bool isTOC = findTOC(details.localPosition); + _textSelectionHelper.initialScrollOffset = 0; + _textSelectionHelper.finalScrollOffset = 0; + if (details.kind == PointerDeviceKind.mouse && !isTOC) { + if (_textSelectionHelper.selectionEnabled) { + final bool isStartDragPossible = _checkStartBubblePosition(); + final bool isEndDragPossible = _checkEndBubblePosition(); + if (isStartDragPossible || isEndDragPossible) { + _textSelectionHelper.mouseSelectionEnabled = false; + } else { + clearSelection(); + } + } + if (gestureType == 'DragStart' && + _textSelectionHelper.mouseSelectionEnabled) { + _textSelectionHelper.endBubbleX = details.localPosition.dx; + _textSelectionHelper.endBubbleY = details.localPosition.dy; + } + if (_textSelectionHelper.textLines == null || + _textSelectionHelper.viewId != pageIndex) { + _textSelectionHelper.viewId = pageIndex; + _textSelectionHelper.textLines = PdfTextExtractor(pdfDocument!) + .extractTextLines(startPageIndex: pageIndex); + } + _textSelectionHelper.textLines!.forEach((textLine) { + textLine.wordCollection.forEach((textWord) { + textWord.glyphs.forEach((textGlyph) { + if (gestureType == 'DragStart') { + if (textGlyph.bounds.contains(details.localPosition)) { + _textSelectionHelper.firstSelectedGlyph = textGlyph; + _enableSelection(gestureType); + } + } else if (gestureType == 'DoubleTap') { + _triggerNullCallback(); + if (textWord.bounds.contains(details.localPosition)) { + _textSelectionHelper.firstSelectedGlyph = + textWord.glyphs.first; + _textSelectionHelper.endBubbleX = + textWord.glyphs.last.bounds.right; + _textSelectionHelper.endBubbleY = + textWord.glyphs.last.bounds.bottom; + _enableSelection(gestureType); + } + } else if (gestureType == 'TripleTap') { + _triggerNullCallback(); + if (textLine.bounds.contains(details.localPosition)) { + _textSelectionHelper.firstSelectedGlyph = + textLine.wordCollection.first.glyphs.first; + _textSelectionHelper.endBubbleX = + textLine.wordCollection.last.bounds.right; + _textSelectionHelper.endBubbleY = + textLine.wordCollection.last.bounds.bottom; + _enableSelection(gestureType); + } + } + }); + }); + }); + } + } + } + + /// Enable mouse text selection. + void _enableSelection(String gestureType) { + if (!_textSelectionHelper.selectionEnabled) { + if (gestureType == 'DragStart') { + clearMouseSelection(); + onTextSelectionDragStarted(); + } + _textSelectionHelper.mouseSelectionEnabled = true; + } + } + + /// Triggers null callback for text selection. + void _triggerNullCallback() { + if (onTextSelectionChanged != null) { + onTextSelectionChanged!(PdfTextSelectionChangedDetails(null, null)); + } + } + + /// Triggers value callback for text selection. + void _triggerValueCallback() { + if (onTextSelectionChanged != null) { + onTextSelectionChanged!(PdfTextSelectionChangedDetails( + _textSelectionHelper.copiedText, + _textSelectionHelper.globalSelectedRegion)); + } + } + /// Triggers when scrolling of page is started. void scrollStarted() { - if (_textSelectionHelper.selectionEnabled) { - if (onTextSelectionChanged != null) { - onTextSelectionChanged(PdfTextSelectionChangedDetails(null, null)); - } + if (_textSelectionHelper.selectionEnabled || + _textSelectionHelper.mouseSelectionEnabled) { + _triggerNullCallback(); } } /// Triggers when scrolling of page is ended. void scrollEnded() { - if (_textSelectionHelper.selectionEnabled) { - final double _heightPercentage = _textSelectionHelper.heightPercentage; + if (_textSelectionHelper.selectionEnabled || + _textSelectionHelper.mouseSelectionEnabled) { + double top; + double bottom; + if (kIsWeb && + !isMobileWebView && + _textSelectionHelper.globalSelectedRegion != null) { + top = _textSelectionHelper.globalSelectedRegion!.top; + bottom = _textSelectionHelper.globalSelectedRegion!.bottom; + } else { + final double _heightPercentage = _textSelectionHelper.heightPercentage!; + top = _textSelectionHelper.firstSelectedGlyph != null + ? _textSelectionHelper.firstSelectedGlyph!.bounds.top / + _heightPercentage + : 0; + bottom = _textSelectionHelper.endBubbleY != null + ? _textSelectionHelper.endBubbleY! / _heightPercentage + : 0; + } // addPostFrameCallback triggers after paint method is called to update the globalSelectedRegion values. // So that context menu position updating properly while changing orientation and double tap zoom. - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - if (onTextSelectionChanged != null && - ((pdfPages[_textSelectionHelper.viewId + 1].pageOffset + - _textSelectionHelper.endBubbleY / - _heightPercentage >= - scrollController.offset && - pdfPages[_textSelectionHelper.viewId + 1].pageOffset + - _textSelectionHelper.endBubbleY / - _heightPercentage <= - scrollController.offset + - scrollController.position.viewportDimension) || - (pdfPages[_textSelectionHelper.viewId + 1].pageOffset + - _textSelectionHelper.firstGlyphOffset.dy / - _heightPercentage >= - scrollController.offset && - pdfPages[_textSelectionHelper.viewId + 1].pageOffset + - _textSelectionHelper.firstGlyphOffset.dy / - _heightPercentage <= - scrollController.offset + - scrollController.position.viewportDimension))) { - onTextSelectionChanged(PdfTextSelectionChangedDetails( - _textSelectionHelper.copiedText, - _textSelectionHelper.globalSelectedRegion)); + WidgetsBinding.instance?.addPostFrameCallback((timeStamp) { + if (((pdfPages[_textSelectionHelper.viewId! + 1]!.pageOffset + bottom >= + scrollController.offset && + pdfPages[_textSelectionHelper.viewId! + 1]!.pageOffset + + bottom <= + scrollController.offset + + scrollController.position.viewportDimension) || + (pdfPages[_textSelectionHelper.viewId! + 1]!.pageOffset + top >= + scrollController.offset && + pdfPages[_textSelectionHelper.viewId! + 1]!.pageOffset + top <= + scrollController.offset + + scrollController.position.viewportDimension))) { + _triggerValueCallback(); } }); } @@ -436,15 +615,15 @@ class CanvasRenderBox extends RenderBox { /// Check the tap position same as the start bubble position. bool _checkStartBubblePosition() { if (_textSelectionHelper.selectionEnabled && _dragDownDetails != null) { - final double startBubbleX = _textSelectionHelper.startBubbleX / - _textSelectionHelper.heightPercentage; - final double startBubbleY = _textSelectionHelper.startBubbleY / - _textSelectionHelper.heightPercentage; - if (_dragDownDetails.dx >= + final double startBubbleX = _textSelectionHelper.startBubbleX! / + _textSelectionHelper.heightPercentage!; + final double startBubbleY = _textSelectionHelper.startBubbleY! / + _textSelectionHelper.heightPercentage!; + if (_dragDownDetails!.dx >= startBubbleX - (_bubbleSize * _maximumZoomLevel) && - _dragDownDetails.dx <= startBubbleX && - _dragDownDetails.dy >= startBubbleY - _bubbleSize && - _dragDownDetails.dy <= startBubbleY + _bubbleSize) { + _dragDownDetails!.dx <= startBubbleX && + _dragDownDetails!.dy >= startBubbleY - _bubbleSize && + _dragDownDetails!.dy <= startBubbleY + _bubbleSize) { return true; } } @@ -454,15 +633,15 @@ class CanvasRenderBox extends RenderBox { /// Check the tap position same as the end bubble position. bool _checkEndBubblePosition() { if (_textSelectionHelper.selectionEnabled && _dragDownDetails != null) { - final double endBubbleX = _textSelectionHelper.endBubbleX / - _textSelectionHelper.heightPercentage; - final double endBubbleY = _textSelectionHelper.endBubbleY / - _textSelectionHelper.heightPercentage; - if (_dragDownDetails.dx >= endBubbleX && - _dragDownDetails.dx <= + final double endBubbleX = _textSelectionHelper.endBubbleX! / + _textSelectionHelper.heightPercentage!; + final double endBubbleY = _textSelectionHelper.endBubbleY! / + _textSelectionHelper.heightPercentage!; + if (_dragDownDetails!.dx >= endBubbleX && + _dragDownDetails!.dx <= endBubbleX + (_bubbleSize * _maximumZoomLevel) && - _dragDownDetails.dy >= endBubbleY - _bubbleSize && - _dragDownDetails.dy <= endBubbleY + _bubbleSize) { + _dragDownDetails!.dy >= endBubbleY - _bubbleSize && + _dragDownDetails!.dy <= endBubbleY + _bubbleSize) { return true; } } @@ -470,38 +649,49 @@ class CanvasRenderBox extends RenderBox { } void _sortTextLines() { - for (int textLineIndex = 0; - textLineIndex < _textSelectionHelper.textLines.length; - textLineIndex++) { - for (int index = textLineIndex + 1; - index < _textSelectionHelper.textLines.length; - index++) { - if (_textSelectionHelper.textLines[textLineIndex].bounds.bottom > - _textSelectionHelper.textLines[index].bounds.bottom) { - final TextLine textLine = - _textSelectionHelper.textLines[textLineIndex]; - _textSelectionHelper.textLines[textLineIndex] = - _textSelectionHelper.textLines[index]; - _textSelectionHelper.textLines[index] = textLine; + if (_textSelectionHelper.textLines != null) { + for (int textLineIndex = 0; + textLineIndex < _textSelectionHelper.textLines!.length; + textLineIndex++) { + for (int index = textLineIndex + 1; + index < _textSelectionHelper.textLines!.length; + index++) { + if (_textSelectionHelper.textLines![textLineIndex].bounds.bottom > + _textSelectionHelper.textLines![index].bounds.bottom) { + final TextLine textLine = + _textSelectionHelper.textLines![textLineIndex]; + _textSelectionHelper.textLines![textLineIndex] = + _textSelectionHelper.textLines![index]; + _textSelectionHelper.textLines![index] = textLine; + } } } } } + /// Enable text selection. + void enableSelection() { + if (enableTextSelection) { + if (_textSelectionHelper.selectionEnabled) { + clearSelection(); + } + _longPressed = true; + _textSelectionHelper.viewId = pageIndex; + markNeedsPaint(); + } + } + /// Ensuring history for text selection. void _ensureHistoryEntry() { Future.delayed(Duration.zero, () async { - if (onTextSelectionChanged != null) { - onTextSelectionChanged(PdfTextSelectionChangedDetails( - _textSelectionHelper.copiedText, - _textSelectionHelper.globalSelectedRegion)); - } - if (_textSelectionHelper.historyEntry == null) { - final ModalRoute route = ModalRoute.of(context); + _triggerValueCallback(); + if ((!kIsWeb || (kIsWeb && isMobileWebView)) && + _textSelectionHelper.historyEntry == null) { + final ModalRoute? route = ModalRoute.of(context); if (route != null) { _textSelectionHelper.historyEntry = LocalHistoryEntry(onRemove: _handleHistoryEntryRemoved); - route.addLocalHistoryEntry(_textSelectionHelper.historyEntry); + route.addLocalHistoryEntry(_textSelectionHelper.historyEntry!); } } }); @@ -518,30 +708,29 @@ class CanvasRenderBox extends RenderBox { /// clears Text Selection. bool clearSelection() { + clearMouseSelection(); final bool clearTextSelection = !_textSelectionHelper.selectionEnabled; if (_textSelectionHelper.selectionEnabled) { _textSelectionHelper.selectionEnabled = false; - if (_textSelectionHelper.historyEntry != null && + if ((!kIsWeb || (kIsWeb && isMobileWebView)) && + _textSelectionHelper.historyEntry != null && Navigator.canPop(context)) { _textSelectionHelper.historyEntry = null; Navigator.of(context).maybePop(); } markNeedsPaint(); - if (onTextSelectionChanged != null) { - onTextSelectionChanged(PdfTextSelectionChangedDetails(null, null)); + _triggerNullCallback(); + if ((!kIsWeb || (kIsWeb && isMobileWebView))) { + dispose(); } - dispose(); } return clearTextSelection; } - /// Dispose the canvas. + /// Dispose the text selection. void dispose() { - _textSelectionHelper.textLines = null; - _textSelectionHelper.viewId = null; - _textSelectionHelper.copiedText = null; - _textSelectionHelper.globalSelectedRegion = null; - _textSelectionHelper.firstGlyphOffset = null; + disposeMouseSelection(); + _textSelectionHelper.firstSelectedGlyph = null; _textSelectionHelper.startBubbleX = null; _textSelectionHelper.startBubbleY = null; _textSelectionHelper.endBubbleX = null; @@ -551,6 +740,109 @@ class CanvasRenderBox extends RenderBox { _textSelectionHelper.heightPercentage = null; } + /// Find the text while hover by mouse. + TextGlyph? findTextWhileHover(details) { + if (_textSelectionHelper.cursorTextLines == null || + _textSelectionHelper.cursorPageNumber != pageIndex) { + _textSelectionHelper.cursorPageNumber = pageIndex; + _textSelectionHelper.cursorTextLines = PdfTextExtractor(pdfDocument!) + .extractTextLines(startPageIndex: pageIndex); + } + if (_textSelectionHelper.cursorTextLines != null) { + for (int textLineIndex = 0; + textLineIndex < _textSelectionHelper.cursorTextLines!.length; + textLineIndex++) { + final TextLine line = + _textSelectionHelper.cursorTextLines![textLineIndex]; + for (int wordIndex = 0; + wordIndex < line.wordCollection.length; + wordIndex++) { + final TextWord textWord = line.wordCollection[wordIndex]; + for (int glyphIndex = 0; + glyphIndex < textWord.glyphs.length; + glyphIndex++) { + if (textWord.glyphs[glyphIndex].bounds.contains(details)) { + return textWord.glyphs[glyphIndex]; + } + } + } + } + } + return null; + } + + /// Find the TOC bounds while hover by mouse. + bool findTOC(details) { + if (_textSelectionHelper.cursorPageNumber != pageIndex) { + _textSelectionHelper.cursorPageNumber = pageIndex; + } + final PdfPage page = + pdfDocument!.pages[_textSelectionHelper.cursorPageNumber!]; + for (int index = 0; index < page.annotations.count; index++) { + if (page.annotations[index] is PdfDocumentLinkAnnotation) { + _documentLinkAnnotation = + // ignore: avoid_as + page.annotations[index] as PdfDocumentLinkAnnotation; + if ((details.dy >= (_documentLinkAnnotation!.bounds.top)) && + (details.dy <= (_documentLinkAnnotation!.bounds.bottom)) && + (details.dx >= (_documentLinkAnnotation!.bounds.left)) && + (details.dx <= (_documentLinkAnnotation!.bounds.right))) { + return true; + } + } + } + return false; + } + + /// Get the selection details like copiedText,globalSelectedRegion. + TextSelectionHelper getSelectionDetails() { + return _textSelectionHelper; + } + + /// Clear the mouse pointer text selection. + void clearMouseSelection() { + if (!_textSelectionHelper.enableTapSelection && + _textSelectionHelper.mouseSelectionEnabled) { + _textSelectionHelper.mouseSelectionEnabled = false; + markNeedsPaint(); + _triggerNullCallback(); + } else { + _textSelectionHelper.enableTapSelection = false; + } + } + + /// Check the text glyph inside the selected region. + bool checkGlyphInRegion( + TextGlyph textGlyph, TextGlyph startGlyph, Offset details) { + final double glyphCenterX = textGlyph.bounds.center.dx; + final double glyphCenterY = textGlyph.bounds.center.dy; + final double top = startGlyph.bounds.top; + final double bottom = startGlyph.bounds.bottom; + if ((glyphCenterY > top && glyphCenterY < details.dy) && + (glyphCenterX > startGlyph.bounds.left || glyphCenterY > bottom) && + (textGlyph.bounds.bottom < details.dy || glyphCenterX < details.dx)) { + return true; + } + if (details.dy < top || + (details.dy < bottom && details.dx < startGlyph.bounds.left)) { + if ((glyphCenterY > details.dy && glyphCenterY < bottom) && + (glyphCenterX > details.dx || textGlyph.bounds.top > details.dy) && + (textGlyph.bounds.bottom < top || + glyphCenterX < startGlyph.bounds.left)) { + return true; + } + } + return false; + } + + /// Dispose the mouse selection. + void disposeMouseSelection() { + _textSelectionHelper.textLines = null; + _textSelectionHelper.viewId = null; + _textSelectionHelper.copiedText = null; + _textSelectionHelper.globalSelectedRegion = null; + } + /// Draw the start bubble. void _drawStartBubble( Canvas canvas, Paint bubblePaint, Offset startBubbleOffset) { @@ -588,27 +880,129 @@ class CanvasRenderBox extends RenderBox { canvas.drawRect(textRectOffset, textPaint); } + /// This replaces the old performResize method. + @override + Size computeDryLayout(BoxConstraints constraints) { + return constraints.biggest; + } + + /// Gets the selected text lines. + List? getSelectedTextLines() { + if (_textSelectionHelper.selectionEnabled || + _textSelectionHelper.mouseSelectionEnabled) { + return _textSelectionHelper.selectedTextLines; + } + return null; + } + @override void paint(PaintingContext context, Offset offset) { + if (pdfDocument == null) { + return; + } final Canvas canvas = context.canvas; + final ThemeData theme = Theme.of(this.context); + final TextSelectionThemeData selectionTheme = + TextSelectionTheme.of(this.context); + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(this.context); + switch (theme.platform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + _selectionColor ??= selectionTheme.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + _selectionHandleColor ??= + selectionTheme.selectionHandleColor ?? cupertinoTheme.primaryColor; + break; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + _selectionColor ??= selectionTheme.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + _selectionHandleColor ??= + selectionTheme.selectionHandleColor ?? theme.colorScheme.primary; + break; + } + final Paint textPaint = Paint()..color = _selectionColor!; + final Paint bubblePaint = Paint()..color = _selectionHandleColor!; + _zoomPercentage = pdfViewerController.zoomLevel > _maximumZoomLevel + ? _maximumZoomLevel + : pdfViewerController.zoomLevel; + + if (_textSelectionHelper.mouseSelectionEnabled && + _textSelectionHelper.textLines != null && + _textSelectionHelper.endBubbleX != null && + _textSelectionHelper.endBubbleY != null) { + final TextGlyph startGlyph = _textSelectionHelper.firstSelectedGlyph!; + final Offset details = Offset( + _textSelectionHelper.endBubbleX!, _textSelectionHelper.endBubbleY!); + if (_textSelectionHelper.viewId == pageIndex) { + _textSelectionHelper.copiedText = ''; + _textSelectionHelper.selectedTextLines.clear(); + _textSelectionHelper.textLines?.forEach((textLine) { + Rect? startPoint; + Rect? endPoint; + String glyphText = ''; + textLine.wordCollection.forEach((textWord) { + textWord.glyphs.forEach((textGlyph) { + final bool canSelectGlyph = + checkGlyphInRegion(textGlyph, startGlyph, details); + if (canSelectGlyph) { + startPoint ??= textGlyph.bounds; + endPoint = textGlyph.bounds; + glyphText = glyphText + textGlyph.text; + _textSelectionHelper.copiedText = + _textSelectionHelper.copiedText! + textGlyph.text; + final Rect textRectOffset = offset.translate( + textGlyph.bounds.left, textGlyph.bounds.top) & + Size(textGlyph.bounds.width, textGlyph.bounds.height); + canvas.drawRect(textRectOffset, textPaint); + } + if (startPoint != null && + endPoint != null && + textGlyph == textLine.wordCollection.last.glyphs.last) { + _textSelectionHelper.selectedTextLines.add(PdfTextLine( + Rect.fromLTRB(startPoint!.left, startPoint!.top, + endPoint!.right, endPoint!.bottom), + glyphText, + _textSelectionHelper.viewId!)); + Offset startOffset = + Offset(startGlyph.bounds.left, startGlyph.bounds.top); + final Offset endOffset = + Offset(endPoint!.right, endPoint!.bottom); + if (details.dy < startGlyph.bounds.top) { + startOffset = Offset(details.dx, details.dy); + } + _textSelectionHelper.globalSelectedRegion = Rect.fromPoints( + localToGlobal(startOffset), localToGlobal(endOffset)); + } + }); + }); + }); + } + } if (pageIndex == _viewId) { if (_isTOCTapped) { final double heightPercentage = - pdfDocument.pages[_viewId].size.height / height; + pdfDocument!.pages[_viewId!].size.height / height; final Paint wordPaint = Paint() - ..color = Color.fromRGBO(228, 238, 244, 1); + ..color = Color.fromRGBO(228, 238, 244, 0.75); canvas.drawRect( offset.translate( - _documentLinkAnnotation.bounds.left / heightPercentage, - _documentLinkAnnotation.bounds.top / heightPercentage) & - Size(_documentLinkAnnotation.bounds.width / heightPercentage, - _documentLinkAnnotation.bounds.height / heightPercentage), + _documentLinkAnnotation!.bounds.left / heightPercentage, + _documentLinkAnnotation!.bounds.top / heightPercentage) & + Size(_documentLinkAnnotation!.bounds.width / heightPercentage, + _documentLinkAnnotation!.bounds.height / heightPercentage), wordPaint); // For the ripple kind of effect so used Future.delayed Future.delayed(Duration.zero, () async { - scrollController.jumpTo(_totalPageOffset); + if (pdfViewerController.zoomLevel <= 1) { + pdfViewerController.jumpTo(yOffset: _totalPageOffset!); + } else { + onDocumentLinkNavigationInvoked(_totalPageOffset!); + } }); _isTOCTapped = false; } @@ -620,50 +1014,49 @@ class CanvasRenderBox extends RenderBox { final Paint currentInstancePaint = Paint() ..color = searchTextHighlightColor.withOpacity(0.6); - int _pageNumber; - for (int i = 0; i < textCollection.length; i++) { - final MatchedItem item = textCollection[i]; + int _pageNumber = 0; + for (int i = 0; i < textCollection!.length; i++) { + final MatchedItem item = textCollection![i]; final double _heightPercentage = - pdfDocument.pages[item.pageIndex].size.height / height; + pdfDocument!.pages[item.pageIndex].size.height / height; if (pageIndex == item.pageIndex) { canvas.drawRect( offset.translate( - textCollection[i].bounds.left / _heightPercentage, - textCollection[i].bounds.top / _heightPercentage) & - Size(textCollection[i].bounds.width / _heightPercentage, - textCollection[i].bounds.height / _heightPercentage), + textCollection![i].bounds.left / _heightPercentage, + textCollection![i].bounds.top / _heightPercentage) & + Size(textCollection![i].bounds.width / _heightPercentage, + textCollection![i].bounds.height / _heightPercentage), searchTextPaint); } - if (pdfTextSearchResult != null && - textCollection[pdfTextSearchResult.currentInstanceIndex - 1] - .pageIndex == - pageIndex) { - if (textCollection[pdfTextSearchResult.currentInstanceIndex - 1] + if (textCollection![pdfTextSearchResult.currentInstanceIndex - 1] + .pageIndex == + pageIndex) { + if (textCollection![pdfTextSearchResult.currentInstanceIndex - 1] .pageIndex != _pageNumber) { canvas.drawRect( offset.translate( - textCollection[ + textCollection![ pdfTextSearchResult.currentInstanceIndex - 1] .bounds .left / _heightPercentage, - textCollection[ + textCollection![ pdfTextSearchResult.currentInstanceIndex - 1] .bounds .top / _heightPercentage) & Size( - textCollection[ + textCollection![ pdfTextSearchResult.currentInstanceIndex - 1] .bounds .width / _heightPercentage, - textCollection[ + textCollection![ pdfTextSearchResult.currentInstanceIndex - 1] .bounds @@ -671,7 +1064,7 @@ class CanvasRenderBox extends RenderBox { _heightPercentage), currentInstancePaint); _pageNumber = - textCollection[pdfTextSearchResult.currentInstanceIndex - 1] + textCollection![pdfTextSearchResult.currentInstanceIndex - 1] .pageIndex; } } else if (item.pageIndex > pageIndex) { @@ -680,35 +1073,29 @@ class CanvasRenderBox extends RenderBox { } } - final Paint textPaint = Paint() - ..color = Theme.of(this.context).textSelectionColor.withOpacity(0.5); - final Paint bubblePaint = Paint() - ..color = Theme.of(this.context).textSelectionHandleColor; - _zoomPercentage = pdfViewerController.zoomLevel > _maximumZoomLevel - ? _maximumZoomLevel - : pdfViewerController.zoomLevel; if (_longPressed) { final double _heightPercentage = - pdfDocument.pages[_textSelectionHelper.viewId].size.height / height; + pdfDocument!.pages[_textSelectionHelper.viewId!].size.height / height; _textSelectionHelper.heightPercentage = _heightPercentage; - _textSelectionHelper.textLines = PdfTextExtractor(pdfDocument) + _textSelectionHelper.textLines = PdfTextExtractor(pdfDocument!) .extractTextLines(startPageIndex: _textSelectionHelper.viewId); for (int textLineIndex = 0; - textLineIndex < _textSelectionHelper.textLines.length; + textLineIndex < _textSelectionHelper.textLines!.length; textLineIndex++) { - final TextLine line = _textSelectionHelper.textLines[textLineIndex]; + final TextLine line = _textSelectionHelper.textLines![textLineIndex]; final List textWordCollection = line.wordCollection; for (int wordIndex = 0; wordIndex < textWordCollection.length; wordIndex++) { final TextWord textWord = textWordCollection[wordIndex]; final Rect wordBounds = textWord.bounds; - if (wordBounds.contains(_tapDetails * _heightPercentage)) { + if (_tapDetails != null && + wordBounds.contains(_tapDetails! * _heightPercentage)) { _textSelectionHelper.startBubbleLine = - _textSelectionHelper.textLines[textLineIndex]; + _textSelectionHelper.textLines![textLineIndex]; _textSelectionHelper.copiedText = textWord.text; _textSelectionHelper.endBubbleLine = - _textSelectionHelper.textLines[textLineIndex]; + _textSelectionHelper.textLines![textLineIndex]; _startBubbleTapX = textWord.bounds.bottomLeft.dx / _heightPercentage; _textSelectionHelper.startBubbleY = textWord.bounds.bottomLeft.dy; @@ -737,9 +1124,11 @@ class CanvasRenderBox extends RenderBox { localToGlobal(Offset( textWord.bounds.bottomRight.dx / _heightPercentage, textWord.bounds.bottomRight.dy / _heightPercentage))); - _textSelectionHelper.firstGlyphOffset = - Offset(textWord.bounds.topLeft.dx, textWord.bounds.topLeft.dy); + _textSelectionHelper.firstSelectedGlyph = textWord.glyphs.first; _textSelectionHelper.selectionEnabled = true; + _textSelectionHelper.selectedTextLines.clear(); + _textSelectionHelper.selectedTextLines.add(PdfTextLine( + textWord.bounds, textWord.text, _textSelectionHelper.viewId!)); _ensureHistoryEntry(); _sortTextLines(); } @@ -749,34 +1138,34 @@ class CanvasRenderBox extends RenderBox { } else if (_textSelectionHelper.selectionEnabled && pageIndex == _textSelectionHelper.viewId) { final double _heightPercentage = - pdfDocument.pages[_textSelectionHelper.viewId].size.height / height; + pdfDocument!.pages[_textSelectionHelper.viewId!].size.height / height; _textSelectionHelper.heightPercentage = _heightPercentage; if (_startBubbleDragging) { for (int textLineIndex = 0; - textLineIndex < _textSelectionHelper.textLines.length; + textLineIndex < _textSelectionHelper.textLines!.length; textLineIndex++) { - final TextLine line = _textSelectionHelper.textLines[textLineIndex]; + final TextLine line = _textSelectionHelper.textLines![textLineIndex]; if (_dragDetails != null && - _dragDetails.dy <= - _textSelectionHelper.endBubbleY / _heightPercentage && - _dragDetails.dy >= (line.bounds.top / _heightPercentage)) { + _dragDetails!.dy <= + _textSelectionHelper.endBubbleY! / _heightPercentage && + _dragDetails!.dy >= (line.bounds.top / _heightPercentage)) { _textSelectionHelper.startBubbleLine = line; _textSelectionHelper.startBubbleY = line.bounds.bottomLeft.dy; } if (_dragDetails != null && - _dragDetails.dy >= - _textSelectionHelper.endBubbleY / _heightPercentage) { + _dragDetails!.dy >= + _textSelectionHelper.endBubbleY! / _heightPercentage) { _textSelectionHelper.startBubbleLine = _textSelectionHelper.endBubbleLine; _textSelectionHelper.startBubbleY = - _textSelectionHelper.endBubbleLine.bounds.bottom; + _textSelectionHelper.endBubbleLine!.bounds.bottom; } for (int wordIndex = 0; wordIndex < - _textSelectionHelper.startBubbleLine.wordCollection.length; + _textSelectionHelper.startBubbleLine!.wordCollection.length; wordIndex++) { final TextWord textWord = - _textSelectionHelper.startBubbleLine.wordCollection[wordIndex]; + _textSelectionHelper.startBubbleLine!.wordCollection[wordIndex]; for (int glyphIndex = 0; glyphIndex < textWord.glyphs.length; glyphIndex++) { @@ -787,24 +1176,23 @@ class CanvasRenderBox extends RenderBox { (textGlyph.bounds.bottomRight.dx / _heightPercentage)) { _textSelectionHelper.startBubbleX = textGlyph.bounds.bottomLeft.dx; - _textSelectionHelper.firstGlyphOffset = - textGlyph.bounds.topLeft; + _textSelectionHelper.firstSelectedGlyph = textGlyph; } } } if (_startBubbleTapX < - (_textSelectionHelper.startBubbleLine.bounds.bottomLeft.dx / + (_textSelectionHelper.startBubbleLine!.bounds.bottomLeft.dx / _heightPercentage)) { _textSelectionHelper.startBubbleX = - (_textSelectionHelper.startBubbleLine.bounds.bottomLeft.dx); - _textSelectionHelper.firstGlyphOffset = - _textSelectionHelper.startBubbleLine.bounds.topLeft; + (_textSelectionHelper.startBubbleLine!.bounds.bottomLeft.dx); + _textSelectionHelper.firstSelectedGlyph = _textSelectionHelper + .startBubbleLine!.wordCollection.first.glyphs.first; } if (_startBubbleTapX >= - (_textSelectionHelper.startBubbleLine.bounds.bottomRight.dx / + (_textSelectionHelper.startBubbleLine!.bounds.bottomRight.dx / _heightPercentage)) { _textSelectionHelper.startBubbleX = (_textSelectionHelper - .startBubbleLine + .startBubbleLine! .wordCollection .last .glyphs @@ -812,30 +1200,29 @@ class CanvasRenderBox extends RenderBox { .bounds .bottomLeft .dx); - _textSelectionHelper.firstGlyphOffset = _textSelectionHelper - .startBubbleLine.wordCollection.last.glyphs.last.bounds.topLeft; + _textSelectionHelper.firstSelectedGlyph = _textSelectionHelper + .startBubbleLine!.wordCollection.last.glyphs.last; } - if (_textSelectionHelper.startBubbleLine.bounds.bottom / + if (_textSelectionHelper.startBubbleLine!.bounds.bottom / _heightPercentage == - _textSelectionHelper.endBubbleLine.bounds.bottom / + _textSelectionHelper.endBubbleLine!.bounds.bottom / _heightPercentage && _startBubbleTapX >= _endBubbleTapX) { for (int wordIndex = 0; wordIndex < - _textSelectionHelper.startBubbleLine.wordCollection.length; + _textSelectionHelper.startBubbleLine!.wordCollection.length; wordIndex++) { final TextWord textWord = _textSelectionHelper - .startBubbleLine.wordCollection[wordIndex]; + .startBubbleLine!.wordCollection[wordIndex]; for (int glyphIndex = 0; glyphIndex < textWord.glyphs.length; glyphIndex++) { final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; if (textGlyph.bounds.bottomRight.dx / _heightPercentage == - _textSelectionHelper.endBubbleX / _heightPercentage) { + _textSelectionHelper.endBubbleX! / _heightPercentage) { _textSelectionHelper.startBubbleX = (textGlyph.bounds.bottomLeft.dx); - _textSelectionHelper.firstGlyphOffset = - textGlyph.bounds.topLeft; + _textSelectionHelper.firstSelectedGlyph = textGlyph; break; } } @@ -844,23 +1231,24 @@ class CanvasRenderBox extends RenderBox { } } else if (_endBubbleDragging) { for (int textLineIndex = 0; - textLineIndex < _textSelectionHelper.textLines.length; + textLineIndex < _textSelectionHelper.textLines!.length; textLineIndex++) { - final TextLine line = _textSelectionHelper.textLines[textLineIndex]; + final TextLine line = _textSelectionHelper.textLines![textLineIndex]; if (_dragDetails != null && - _dragDetails.dy >= - (_textSelectionHelper.startBubbleLine.bounds.top / + _dragDetails!.dy >= + (_textSelectionHelper.startBubbleLine!.bounds.top / _heightPercentage) && - _dragDetails.dy >= (line.bounds.topLeft.dy / _heightPercentage)) { + _dragDetails!.dy >= + (line.bounds.topLeft.dy / _heightPercentage)) { _textSelectionHelper.endBubbleLine = line; _textSelectionHelper.endBubbleY = line.bounds.bottomRight.dy; } for (int wordIndex = 0; wordIndex < - _textSelectionHelper.endBubbleLine.wordCollection.length; + _textSelectionHelper.endBubbleLine!.wordCollection.length; wordIndex++) { final TextWord textWord = - _textSelectionHelper.endBubbleLine.wordCollection[wordIndex]; + _textSelectionHelper.endBubbleLine!.wordCollection[wordIndex]; for (int glyphIndex = 0; glyphIndex < textWord.glyphs.length; glyphIndex++) { @@ -875,18 +1263,18 @@ class CanvasRenderBox extends RenderBox { } } if (_endBubbleTapX.floor() > - (_textSelectionHelper.endBubbleLine.bounds.bottomRight.dx / + (_textSelectionHelper.endBubbleLine!.bounds.bottomRight.dx / _heightPercentage) .floor()) { _textSelectionHelper.endBubbleX = - (_textSelectionHelper.endBubbleLine.bounds.bottomRight.dx); + (_textSelectionHelper.endBubbleLine!.bounds.bottomRight.dx); } if (_endBubbleTapX.floor() <= - (_textSelectionHelper.endBubbleLine.bounds.bottomLeft.dx / + (_textSelectionHelper.endBubbleLine!.bounds.bottomLeft.dx / _heightPercentage) .floor()) { _textSelectionHelper.endBubbleX = (_textSelectionHelper - .endBubbleLine + .endBubbleLine! .wordCollection .first .glyphs @@ -895,23 +1283,23 @@ class CanvasRenderBox extends RenderBox { .bottomRight .dx); } - if (_textSelectionHelper.endBubbleLine.bounds.bottom / + if (_textSelectionHelper.endBubbleLine!.bounds.bottom / _heightPercentage == - _textSelectionHelper.startBubbleLine.bounds.bottom / + _textSelectionHelper.startBubbleLine!.bounds.bottom / _heightPercentage && _endBubbleTapX < _startBubbleTapX) { for (int wordIndex = 0; wordIndex < - _textSelectionHelper.endBubbleLine.wordCollection.length; + _textSelectionHelper.endBubbleLine!.wordCollection.length; wordIndex++) { final TextWord textWord = - _textSelectionHelper.endBubbleLine.wordCollection[wordIndex]; + _textSelectionHelper.endBubbleLine!.wordCollection[wordIndex]; for (int glyphIndex = 0; glyphIndex < textWord.glyphs.length; glyphIndex++) { final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; if (textGlyph.bounds.bottomLeft.dx / _heightPercentage == - _textSelectionHelper.startBubbleX / _heightPercentage) { + _textSelectionHelper.startBubbleX! / _heightPercentage) { _textSelectionHelper.endBubbleX = (textGlyph.bounds.bottomRight.dx); break; @@ -922,10 +1310,14 @@ class CanvasRenderBox extends RenderBox { } } _textSelectionHelper.copiedText = ''; + _textSelectionHelper.selectedTextLines.clear(); for (int textLineIndex = 0; - textLineIndex < _textSelectionHelper.textLines.length; + textLineIndex < _textSelectionHelper.textLines!.length; textLineIndex++) { - final TextLine line = _textSelectionHelper.textLines[textLineIndex]; + final TextLine line = _textSelectionHelper.textLines![textLineIndex]; + Rect? startPoint; + Rect? endPoint; + String glyphText = ''; final List textWordCollection = line.wordCollection; for (int wordIndex = 0; wordIndex < textWordCollection.length; @@ -936,21 +1328,24 @@ class CanvasRenderBox extends RenderBox { glyphIndex++) { final TextGlyph glyph = textWord.glyphs[glyphIndex]; if (glyph.bounds.bottom / _heightPercentage == - _textSelectionHelper.startBubbleLine.bounds.bottom / + _textSelectionHelper.startBubbleLine!.bounds.bottom / _heightPercentage) { if ((glyph.bounds.bottomCenter.dx / _heightPercentage >= - _textSelectionHelper.startBubbleX / + _textSelectionHelper.startBubbleX! / _heightPercentage && glyph.bounds.bottomCenter.dx / _heightPercentage < - _textSelectionHelper.endBubbleX / + _textSelectionHelper.endBubbleX! / _heightPercentage) || (glyph.bounds.bottomCenter.dx / _heightPercentage >= - _textSelectionHelper.startBubbleX / + _textSelectionHelper.startBubbleX! / _heightPercentage && - _textSelectionHelper.endBubbleY / _heightPercentage > + _textSelectionHelper.endBubbleY! / _heightPercentage > glyph.bounds.bottom / _heightPercentage)) { + startPoint ??= glyph.bounds; + endPoint = glyph.bounds; + glyphText = glyphText + glyph.text; _textSelectionHelper.copiedText = - _textSelectionHelper.copiedText + glyph.text; + _textSelectionHelper.copiedText! + glyph.text; final Rect textRectOffset = offset.translate( glyph.bounds.left / _heightPercentage, glyph.bounds.top / _heightPercentage) & @@ -959,18 +1354,22 @@ class CanvasRenderBox extends RenderBox { _drawTextRect(canvas, textPaint, textRectOffset); } } else if ((glyph.bounds.bottomLeft.dy / _heightPercentage >= - _textSelectionHelper.startBubbleY / _heightPercentage && - _textSelectionHelper.endBubbleX / _heightPercentage > + _textSelectionHelper.startBubbleY! / + _heightPercentage && + _textSelectionHelper.endBubbleX! / _heightPercentage > glyph.bounds.bottomCenter.dx / _heightPercentage && - _textSelectionHelper.endBubbleY / _heightPercentage > + _textSelectionHelper.endBubbleY! / _heightPercentage > glyph.bounds.top / _heightPercentage) || - (_textSelectionHelper.endBubbleY / _heightPercentage > + (_textSelectionHelper.endBubbleY! / _heightPercentage > glyph.bounds.bottom / _heightPercentage && glyph.bounds.bottomLeft.dy / _heightPercentage >= - _textSelectionHelper.startBubbleY / + _textSelectionHelper.startBubbleY! / _heightPercentage)) { + startPoint ??= glyph.bounds; + endPoint = glyph.bounds; + glyphText = glyphText + glyph.text; _textSelectionHelper.copiedText = - _textSelectionHelper.copiedText + glyph.text; + _textSelectionHelper.copiedText! + glyph.text; final Rect textRectOffset = offset.translate( glyph.bounds.left / _heightPercentage, glyph.bounds.top / _heightPercentage) & @@ -978,24 +1377,35 @@ class CanvasRenderBox extends RenderBox { glyph.bounds.height / _heightPercentage); _drawTextRect(canvas, textPaint, textRectOffset); } + if (startPoint != null && + endPoint != null && + glyph == line.wordCollection.last.glyphs.last) { + _textSelectionHelper.selectedTextLines.add(PdfTextLine( + Rect.fromLTRB(startPoint.left, startPoint.top, endPoint.right, + endPoint.bottom), + glyphText, + _textSelectionHelper.viewId!)); + } } } } final Offset startBubbleOffset = offset.translate( - _textSelectionHelper.startBubbleX / _heightPercentage, - _textSelectionHelper.startBubbleY / _heightPercentage); + _textSelectionHelper.startBubbleX! / _heightPercentage, + _textSelectionHelper.startBubbleY! / _heightPercentage); final Offset endBubbleOffset = offset.translate( - _textSelectionHelper.endBubbleX / _heightPercentage, - _textSelectionHelper.endBubbleY / _heightPercentage); + _textSelectionHelper.endBubbleX! / _heightPercentage, + _textSelectionHelper.endBubbleY! / _heightPercentage); _drawStartBubble(canvas, bubblePaint, startBubbleOffset); _drawEndBubble(canvas, bubblePaint, endBubbleOffset); _textSelectionHelper.globalSelectedRegion = Rect.fromPoints( localToGlobal(Offset( - _textSelectionHelper.firstGlyphOffset.dx / _heightPercentage, - _textSelectionHelper.firstGlyphOffset.dy / _heightPercentage)), + _textSelectionHelper.firstSelectedGlyph!.bounds.left / + _heightPercentage, + _textSelectionHelper.firstSelectedGlyph!.bounds.top / + _heightPercentage)), localToGlobal(Offset( - _textSelectionHelper.endBubbleX / _heightPercentage, - _textSelectionHelper.endBubbleY / _heightPercentage))); + _textSelectionHelper.endBubbleX! / _heightPercentage, + _textSelectionHelper.endBubbleY! / _heightPercentage))); } } } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart index 115b1ff13..c592ffca6 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:syncfusion_flutter_core/theme.dart'; @@ -13,7 +14,8 @@ const double kPdfScrollHeadHeight = 32.0; @immutable class ScrollHead extends StatefulWidget { /// Constructor for ScrollHead. - ScrollHead(this.scrollHeadOffset, this.pdfViewerController); + ScrollHead( + this.scrollHeadOffset, this.pdfViewerController, this.isMobileWebView); /// Position of the [ScrollHead] in [SfPdfViewer]. final double scrollHeadOffset; @@ -21,13 +23,16 @@ class ScrollHead extends StatefulWidget { /// PdfViewer controller of PdfViewer final PdfViewerController pdfViewerController; + /// If true,MobileWebView is enabled.Default value is false. + final bool isMobileWebView; + @override _ScrollHeadState createState() => _ScrollHeadState(); } /// State for [ScrollHead] class _ScrollHeadState extends State { - SfPdfViewerThemeData _pdfViewerThemeData; + SfPdfViewerThemeData? _pdfViewerThemeData; @override void didChangeDependencies() { @@ -43,14 +48,33 @@ class _ScrollHeadState extends State { @override Widget build(BuildContext context) { + if (kIsWeb) { + return Container( + alignment: Alignment.topRight, + margin: EdgeInsets.only(top: widget.scrollHeadOffset), + child: Material( + color: Colors.grey, + borderRadius: BorderRadius.all(Radius.circular(7.0)), + child: Container( + constraints: BoxConstraints.tight( + Size(10.0, 54.0), + ), + ), + ), + ); + } return Container( margin: EdgeInsets.only(top: widget.scrollHeadOffset), child: Stack( children: [ Material( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(kPdfScrollHeadHeight), + bottomLeft: Radius.circular(kPdfScrollHeadHeight), + ), child: Container( decoration: BoxDecoration( - color: _pdfViewerThemeData.scrollHeadStyle.backgroundColor, + color: _pdfViewerThemeData!.scrollHeadStyle.backgroundColor, borderRadius: BorderRadius.only( topLeft: Radius.circular(kPdfScrollHeadHeight), bottomLeft: Radius.circular(kPdfScrollHeadHeight), @@ -76,17 +100,13 @@ class _ScrollHeadState extends State { constraints: BoxConstraints.tightFor( width: kPdfScrollHeadHeight, height: kPdfScrollHeadHeight), ), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(kPdfScrollHeadHeight), - bottomLeft: Radius.circular(kPdfScrollHeadHeight), - ), ), Positioned.fill( child: Align( alignment: Alignment.center, child: Text( '${widget.pdfViewerController.pageNumber}', - style: _pdfViewerThemeData.scrollHeadStyle.pageNumberTextStyle, + style: _pdfViewerThemeData!.scrollHeadStyle.pageNumberTextStyle, ), ), ), diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head_overlay.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head_overlay.dart index c6a089e19..057b80918 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head_overlay.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head_overlay.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:syncfusion_flutter_pdfviewer/src/control/scroll_head.dart'; @@ -5,7 +6,10 @@ import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; /// Height of the scroll head. -const double kPdfScrollHeadHeight = 32.0; +const double _kPdfScrollHeadHeight = 32.0; + +/// Height of the scroll bar +const double _kPdfScrollBarHeight = 54.0; /// Height of the pagination text field. const double _kPdfPaginationTextFieldWidth = 328.0; @@ -15,46 +19,59 @@ const double _kPdfPaginationTextFieldWidth = 328.0; class ScrollHeadOverlay extends StatefulWidget { /// Constructor for ScrollHeadOverlay. ScrollHeadOverlay( - {this.canShowPaginationDialog, - this.scrollHeadOffset, - this.onScrollHeadDragStart, - this.onScrollHeadDragUpdate, + Key key, + this.canShowScrollHead, + this.canShowPaginationDialog, this.onScrollHeadDragEnd, - this.pdfViewerController}); - - /// Position of the [ScrollHeadOverlay] in [SfPdfViewer]. - final double scrollHeadOffset; - - /// A pointer has contacted the screen with a scroll head and has begun to - /// move vertically. - final GestureDragStartCallback onScrollHeadDragStart; - - /// A pointer that is in contact with the screen with a scroll head and - /// moving vertically has moved in the vertical direction. - final GestureDragUpdateCallback onScrollHeadDragUpdate; + this.scrollController, + this.isMobileWebView, + this.pdfViewerController) + : super(key: key); /// A pointer that was previously in contact with the screen with a scroll /// head and moving vertically is no longer in contact with the screen and /// was moving at a specific velocity when it stopped contacting the screen. - final GestureDragEndCallback onScrollHeadDragEnd; + final VoidCallback onScrollHeadDragEnd; /// Indicates whether page navigation dialog must be shown or not. final bool canShowPaginationDialog; + /// Indicates whether scroll head must be shown or not. + final bool canShowScrollHead; + + /// Scroll controller of PdfViewer + final ScrollController scrollController; + /// PdfViewer controller of PdfViewer final PdfViewerController pdfViewerController; + /// If true,MobileWebView is enabled.Default value is false. + final bool isMobileWebView; + @override - _ScrollHeadOverlayState createState() => _ScrollHeadOverlayState(); + ScrollHeadOverlayState createState() => ScrollHeadOverlayState(); } /// State for [ScrollHeadOverlay] -class _ScrollHeadOverlayState extends State { +class ScrollHeadOverlayState extends State { final TextEditingController _textFieldController = TextEditingController(); final _formKey = GlobalKey(); - final FocusScopeNode _focusScopeNode = FocusScopeNode(); - SfPdfViewerThemeData _pdfViewerThemeData; - SfLocalizations _localizations; + final FocusNode _focusNode = FocusNode(); + SfPdfViewerThemeData? _pdfViewerThemeData; + SfLocalizations? _localizations; + + /// Scroll head Offset + late double scrollHeadOffset; + + /// If true,scroll head dragging is ended. + bool isScrollHeadDragged = false; + + @override + void initState() { + super.initState(); + scrollHeadOffset = 0.0; + isScrollHeadDragged = true; + } @override void didChangeDependencies() { @@ -67,6 +84,7 @@ class _ScrollHeadOverlayState extends State { void dispose() { _pdfViewerThemeData = null; _localizations = null; + _focusNode.dispose(); super.dispose(); } @@ -75,19 +93,27 @@ class _ScrollHeadOverlayState extends State { return Align( alignment: Alignment.topRight, child: GestureDetector( - onVerticalDragStart: widget.onScrollHeadDragStart, - onVerticalDragUpdate: widget.onScrollHeadDragUpdate, - onVerticalDragEnd: widget.onScrollHeadDragEnd, + onVerticalDragStart: _handleScrollHeadDragStart, + onVerticalDragUpdate: _handleScrollHeadDragUpdate, + onVerticalDragEnd: _handleScrollHeadDragEnd, onTap: () { - _textFieldController.clear(); - if (!FocusScope.of(context).hasPrimaryFocus) { - FocusScope.of(context).unfocus(); - } - if (widget.canShowPaginationDialog) { - _showPaginationDialog(); + if (!kIsWeb || (kIsWeb && widget.isMobileWebView)) { + _textFieldController.clear(); + if (!FocusScope.of(context).hasPrimaryFocus) { + FocusScope.of(context).unfocus(); + } + if (widget.canShowPaginationDialog) { + _showPaginationDialog(); + } } }, - child: ScrollHead(widget.scrollHeadOffset, widget.pdfViewerController), + child: Visibility( + visible: (kIsWeb) + ? widget.pdfViewerController.pageCount > 1 + : widget.canShowScrollHead && + widget.pdfViewerController.pageCount > 1, + child: ScrollHead(scrollHeadOffset, widget.pdfViewerController, + widget.isMobileWebView)), ), ); } @@ -115,45 +141,45 @@ class _ScrollHeadOverlayState extends State { ? EdgeInsets.all(8) : EdgeInsets.all(4), backgroundColor: - _pdfViewerThemeData.paginationDialogStyle.backgroundColor, + _pdfViewerThemeData!.paginationDialogStyle.backgroundColor, title: Text( - _localizations.pdfGoToPageLabel, - style: _pdfViewerThemeData.paginationDialogStyle.headerTextStyle, + _localizations!.pdfGoToPageLabel, + style: _pdfViewerThemeData!.paginationDialogStyle.headerTextStyle, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(4.0))), content: SingleChildScrollView(child: _paginationTextField()), actions: [ - FlatButton( - child: Text( - _localizations.pdfPaginationDialogCancelLabel, - style: _pdfViewerThemeData - .paginationDialogStyle.cancelTextStyle.color == - null - ? _pdfViewerThemeData - .paginationDialogStyle.cancelTextStyle - .copyWith(color: Theme.of(context).primaryColor) - : _pdfViewerThemeData - .paginationDialogStyle.cancelTextStyle, - ), + TextButton( onPressed: () { _textFieldController.clear(); Navigator.of(context).pop(); }, - ), - FlatButton( child: Text( - _localizations.pdfPaginationDialogOkLabel, - style: _pdfViewerThemeData - .paginationDialogStyle.okTextStyle.color == + _localizations!.pdfPaginationDialogCancelLabel, + style: _pdfViewerThemeData! + .paginationDialogStyle.cancelTextStyle!.color == null - ? _pdfViewerThemeData.paginationDialogStyle.okTextStyle + ? _pdfViewerThemeData! + .paginationDialogStyle.cancelTextStyle! .copyWith(color: Theme.of(context).primaryColor) - : _pdfViewerThemeData.paginationDialogStyle.okTextStyle, + : _pdfViewerThemeData! + .paginationDialogStyle.cancelTextStyle, ), + ), + TextButton( onPressed: () { _handlePageNumberValidation(); }, + child: Text( + _localizations!.pdfPaginationDialogOkLabel, + style: _pdfViewerThemeData! + .paginationDialogStyle.okTextStyle!.color == + null + ? _pdfViewerThemeData!.paginationDialogStyle.okTextStyle! + .copyWith(color: Theme.of(context).primaryColor) + : _pdfViewerThemeData!.paginationDialogStyle.okTextStyle, + ), ) ], ); @@ -167,22 +193,22 @@ class _ScrollHeadOverlayState extends State { child: Container( width: _kPdfPaginationTextFieldWidth, child: TextFormField( - style: _pdfViewerThemeData.paginationDialogStyle.inputFieldTextStyle, - focusNode: _focusScopeNode, + style: _pdfViewerThemeData!.paginationDialogStyle.inputFieldTextStyle, + focusNode: _focusNode, decoration: InputDecoration( isDense: true, focusedBorder: UnderlineInputBorder( borderSide: BorderSide(color: Theme.of(context).primaryColor), ), contentPadding: const EdgeInsets.symmetric(vertical: 6), - hintText: _localizations.pdfEnterPageNumberLabel, - hintStyle: _pdfViewerThemeData.paginationDialogStyle.hintTextStyle, + hintText: _localizations!.pdfEnterPageNumberLabel, + hintStyle: _pdfViewerThemeData!.paginationDialogStyle.hintTextStyle, counterText: '${widget.pdfViewerController.pageNumber}/${widget.pdfViewerController.pageCount}', counterStyle: - _pdfViewerThemeData.paginationDialogStyle.pageInfoTextStyle, + _pdfViewerThemeData!.paginationDialogStyle.pageInfoTextStyle, errorStyle: - _pdfViewerThemeData.paginationDialogStyle.validationTextStyle, + _pdfViewerThemeData!.paginationDialogStyle.validationTextStyle, ), keyboardType: TextInputType.number, enableInteractiveSelection: false, @@ -195,14 +221,17 @@ class _ScrollHeadOverlayState extends State { // ignore: missing_return validator: (value) { try { - final int index = int.parse(value); - if (index <= 0 || index > widget.pdfViewerController.pageCount) { - _textFieldController.clear(); - return _localizations.pdfInvalidPageNumberLabel; + if (value != null) { + final int index = int.parse(value); + if (index <= 0 || + index > widget.pdfViewerController.pageCount) { + _textFieldController.clear(); + return _localizations!.pdfInvalidPageNumberLabel; + } } } on Exception { _textFieldController.clear(); - return _localizations.pdfInvalidPageNumberLabel; + return _localizations!.pdfInvalidPageNumberLabel; } }, ), @@ -212,11 +241,75 @@ class _ScrollHeadOverlayState extends State { /// Validates the page number entered in text field. void _handlePageNumberValidation() { - if (_formKey.currentState.validate()) { + if (_formKey.currentState != null && _formKey.currentState!.validate()) { final int index = int.parse(_textFieldController.text); _textFieldController.clear(); Navigator.of(context).pop(); widget.pdfViewerController.jumpToPage(index); } } + + /// Updates the scroll head position when scrolling occurs. + void updateScrollHeadPosition(double height, {double? maxScrollExtent}) { + if (widget.scrollController.hasClients) { + if (widget.scrollController.offset > 0) { + final positionRatio = (widget.scrollController.position.pixels / + (maxScrollExtent ?? + widget.scrollController.position.maxScrollExtent)); + // Calculating the scroll head position based on ratio of + // current position with ListView's MaxScrollExtent + scrollHeadOffset = (positionRatio * + (height - + ((kIsWeb) ? _kPdfScrollBarHeight : _kPdfScrollHeadHeight))); + } else { + // This conditions gets hit when scrolled to 0.0 offset + scrollHeadOffset = 0.0; + } + } + } + + /// updates UI when scroll head drag is started. + void _handleScrollHeadDragStart(DragStartDetails details) { + isScrollHeadDragged = false; + } + + /// updates UI when scroll head drag is updating. + void _handleScrollHeadDragUpdate(DragUpdateDetails details) { + final double dragOffset = details.delta.dy + scrollHeadOffset; + final double scrollHeadPosition = + widget.scrollController.position.viewportDimension - + ((kIsWeb) ? _kPdfScrollBarHeight : _kPdfScrollHeadHeight); + + // Based on the dragOffset the pdf pages must be scrolled + // and scroll position must be updated + // This if clause condition can be split into three behaviors. + // 1. Normal case - Here, based on scroll head position ratio with + // viewport height, scroll view position is changed. + // 2. End to End cases - + // There few case, where 0.0000123(at start) and 0.99999934(at end) + // factors are computed. For these case, scroll to their ends + // in scrollview. Similarly, for out of bound drag offsets. + if (dragOffset < scrollHeadPosition && dragOffset >= 0) { + widget.scrollController.jumpTo( + widget.scrollController.position.maxScrollExtent * + (dragOffset / scrollHeadPosition)); + scrollHeadOffset = dragOffset; + } else { + if (dragOffset < 0) { + widget.scrollController + .jumpTo(widget.scrollController.position.minScrollExtent); + scrollHeadOffset = 0.0; + } else { + widget.scrollController + .jumpTo(widget.scrollController.position.maxScrollExtent); + scrollHeadOffset = scrollHeadPosition; + } + } + } + + /// updates UI when scroll head drag is ended. + void _handleScrollHeadDragEnd(DragEndDetails details) { + isScrollHeadDragged = true; + widget.onScrollHeadDragEnd(); + } } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_status.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_status.dart index f2728fbc0..d219c29e8 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_status.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_status.dart @@ -21,8 +21,8 @@ class ScrollStatus extends StatefulWidget { /// State for [ScrollStatus] class _ScrollStatusState extends State { - SfPdfViewerThemeData _pdfViewerThemeData; - SfLocalizations _localizations; + SfPdfViewerThemeData? _pdfViewerThemeData; + SfLocalizations? _localizations; @override void didChangeDependencies() { @@ -54,15 +54,15 @@ class _ScrollStatusState extends State { maxWidth: MediaQuery.of(context).size.width * 0.7, ), decoration: BoxDecoration( - color: _pdfViewerThemeData.scrollStatusStyle.backgroundColor, + color: _pdfViewerThemeData!.scrollStatusStyle.backgroundColor, borderRadius: BorderRadius.all( Radius.circular(16.0), ), ), child: Text( - '${widget.pdfViewerController.pageNumber} ${_localizations.pdfScrollStatusOfLabel} ${widget.pdfViewerController.pageCount}', + '${widget.pdfViewerController.pageNumber} ${_localizations!.pdfScrollStatusOfLabel} ${widget.pdfViewerController.pageCount}', textAlign: TextAlign.center, - style: _pdfViewerThemeData.scrollStatusStyle.pageInfoTextStyle, + style: _pdfViewerThemeData!.scrollStatusStyle.pageInfoTextStyle, ), ), ], diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart index dcc63f3db..b4fb9a269 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart @@ -1,22 +1,27 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'dart:typed_data'; +import 'dart:ui'; import 'package:async/async.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:syncfusion_flutter_pdf/pdf.dart'; import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_plugin.dart'; import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_container.dart'; import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_page_view.dart'; import 'package:syncfusion_flutter_pdfviewer/src/control/scroll_status.dart'; + import 'bookmark/bookmark_view.dart'; import 'common/pdf_provider.dart'; +import 'common/pdfviewer_helper.dart'; import 'control/enums.dart'; import 'control/pagination.dart'; +import 'control/pdftextline.dart'; import 'control/pdfviewer_callback_details.dart'; import 'control/scroll_head_overlay.dart'; @@ -39,7 +44,7 @@ typedef PdfZoomLevelChangedCallback = void Function(PdfZoomDetails details); typedef PdfPageChangedCallback = void Function(PdfPageChangedDetails details); /// This callback invoked whenever listener called -typedef _PdfControllerListener = void Function({String property}); +typedef _PdfControllerListener = void Function({String? property}); /// A widget to view PDF documents. /// @@ -99,8 +104,8 @@ class SfPdfViewer extends StatefulWidget { /// } /// ``` SfPdfViewer.asset(String name, - {Key key, - AssetBundle bundle, + {Key? key, + AssetBundle? bundle, this.canShowScrollHead = true, this.pageSpacing = 4, this.controller, @@ -116,18 +121,10 @@ class SfPdfViewer extends StatefulWidget { this.canShowPaginationDialog = true, this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, + this.interactionMode = PdfInteractionMode.selection, this.searchTextHighlightColor = const Color(0xFFE56E00)}) : _provider = AssetPdf(name, bundle), - assert(canShowScrollHead != null), - assert(canShowScrollHead != null), - assert(canShowScrollStatus != null), - assert(enableDoubleTapZooming != null), - assert(canShowPaginationDialog != null), - assert(initialZoomLevel != null), - assert(initialScrollOffset != null), - assert(pageSpacing != null && pageSpacing >= 0), - assert(enableDocumentLinkAnnotation != null), - assert(searchTextHighlightColor != null), + assert(pageSpacing >= 0), super(key: key); /// Creates a widget that displays the PDF document obtained from the network. @@ -153,7 +150,7 @@ class SfPdfViewer extends StatefulWidget { /// } /// ``` SfPdfViewer.network(String src, - {Key key, + {Key? key, this.canShowScrollHead = true, this.pageSpacing = 4, this.controller, @@ -169,17 +166,10 @@ class SfPdfViewer extends StatefulWidget { this.canShowPaginationDialog = true, this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, + this.interactionMode = PdfInteractionMode.selection, this.searchTextHighlightColor = const Color(0xFFE56E00)}) : _provider = NetworkPdf(src), - assert(canShowScrollHead != null), - assert(canShowScrollStatus != null), - assert(initialZoomLevel != null), - assert(initialScrollOffset != null), - assert(enableDoubleTapZooming != null), - assert(canShowPaginationDialog != null), - assert(pageSpacing != null && pageSpacing >= 0), - assert(enableDocumentLinkAnnotation != null), - assert(searchTextHighlightColor != null), + assert(pageSpacing >= 0), super(key: key); /// Creates a widget that displays the PDF document obtained from [Uint8List]. @@ -204,7 +194,7 @@ class SfPdfViewer extends StatefulWidget { /// } /// ``` SfPdfViewer.memory(Uint8List bytes, - {Key key, + {Key? key, this.canShowScrollHead = true, this.pageSpacing = 4, this.controller, @@ -220,17 +210,10 @@ class SfPdfViewer extends StatefulWidget { this.canShowPaginationDialog = true, this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, + this.interactionMode = PdfInteractionMode.selection, this.searchTextHighlightColor = const Color(0xFFE56E00)}) : _provider = MemoryPdf(bytes), - assert(canShowScrollHead != null), - assert(canShowScrollStatus != null), - assert(initialZoomLevel != null), - assert(initialScrollOffset != null), - assert(enableDoubleTapZooming != null), - assert(canShowPaginationDialog != null), - assert(pageSpacing != null && pageSpacing >= 0), - assert(enableDocumentLinkAnnotation != null), - assert(searchTextHighlightColor != null), + assert(pageSpacing >= 0), super(key: key); /// Creates a widget that displays the PDF document obtained from [File]. @@ -260,7 +243,7 @@ class SfPdfViewer extends StatefulWidget { /// ``` SfPdfViewer.file( File file, { - Key key, + Key? key, this.canShowScrollHead = true, this.pageSpacing = 4, this.controller, @@ -276,22 +259,23 @@ class SfPdfViewer extends StatefulWidget { this.canShowPaginationDialog = true, this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, + this.interactionMode = PdfInteractionMode.selection, this.searchTextHighlightColor = const Color(0xFFE56E00), }) : _provider = FilePdf(file), - assert(canShowScrollHead != null), - assert(canShowScrollStatus != null), - assert(initialZoomLevel != null), - assert(initialScrollOffset != null), - assert(enableDoubleTapZooming != null), - assert(canShowPaginationDialog != null), - assert(pageSpacing != null && pageSpacing >= 0), - assert(enableDocumentLinkAnnotation != null), - assert(searchTextHighlightColor != null), + assert(pageSpacing >= 0), super(key: key); /// PDF file provider. final PdfProvider _provider; + /// Indicates the interaction modes of [SfPdfViewer] in a desktop browser. + /// + /// On a touch device, this will have no effect since panning is the default mode for scrolling + /// and selection is made by long pressing a word in the document. + /// + /// Defaults to `selection` mode in a desktop browser. + final PdfInteractionMode interactionMode; + /// Represents the initial zoom level to be applied when the [SfPdfViewer] widget is loaded. /// /// Defaults to 1.0 @@ -466,16 +450,17 @@ class SfPdfViewer extends StatefulWidget { /// } ///} /// ``` - final PdfViewerController controller; + final PdfViewerController? controller; /// Indicates whether the scroll head in [SfPdfViewer] can be displayed or not. /// - /// If this property is set as `false`, the scroll head in [SfPdfViewer] will not be displayed. - /// - /// Irrespective to this property, scroll head will be visible only for the document loaded - /// with 2 or more pages. + /// If this property is set as `false`, the scroll head in [SfPdfViewer] will + /// not be displayed. /// /// Defaults to `true`. + /// + /// _Note:_ On a desktop or mobile browser, this will have no effect since the scroll head + /// will not be displayed. final bool canShowScrollHead; /// Indicates whether the page scroll status in [SfPdfViewer] can be displayed or not. @@ -492,6 +477,9 @@ class SfPdfViewer extends StatefulWidget { /// will not be displayed. /// /// Defaults to `true`. + /// + /// _Note:_ On a desktop or mobile browser, this will have no effect since the pagination dialog + /// will not be displayed. final bool canShowPaginationDialog; /// Indicates whether the double tap zooming in [SfPdfViewer] can be allowed or not. @@ -500,17 +488,28 @@ class SfPdfViewer extends StatefulWidget { /// will not be allowed. /// /// Defaults to `true`. + /// + /// _Note:_ On a desktop browser, this will have no effect with mouse interaction. final bool enableDoubleTapZooming; /// Indicates whether the text selection can be performed or not. /// - /// The text selection can be performed by long pressing any text present in the document. - /// If this property is set as `false`, then the text selection will not happen. + /// On a touch device, the text selection can be performed by long pressing any text present in the + /// document. And, on a desktop browser, the text selection can be performed using mouse dragging + /// with `selection` interaction mode enabled. + /// + /// Text selection can not be performed on a desktop browser when `pan` interaction mode is enabled. + /// + /// If this property is set as `false`, then the text selection will not happen on a touch device + /// and desktop browser with `selection` interaction mode enabled. /// /// Defaults to `true`. /// - /// _Note:_ The images in the document will not be selected and also, the multiple page - /// text selection is not supported for now. + /// _Note:_ The images in the document will not be selected and also, the multiple page text + /// selection is not supported for now. Also, on a desktop browser, this will have no effect with + /// `pan` interaction mode. + /// + /// _See Also:_ `interactionMode` final bool enableTextSelection; /// The text search highlight color to be displayed on the instances found. @@ -528,7 +527,7 @@ class SfPdfViewer extends StatefulWidget { /// instance. /// /// See also: [PdfDocumentLoadedDetails]. - final PdfDocumentLoadedCallback onDocumentLoaded; + final PdfDocumentLoadedCallback? onDocumentLoaded; /// Called when the document loading fails in [SfPdfViewer]. /// @@ -542,7 +541,7 @@ class SfPdfViewer extends StatefulWidget { /// will be updated when the document loading fails. /// /// See also: [PdfDocumentLoadFailedDetails]. - final PdfDocumentLoadFailedCallback onDocumentLoadFailed; + final PdfDocumentLoadFailedCallback? onDocumentLoadFailed; /// Called when the zoom level changes in [SfPdfViewer]. /// @@ -556,7 +555,7 @@ class SfPdfViewer extends StatefulWidget { /// when the zoom level changes. /// /// See also: [PdfZoomDetails]. - final PdfZoomLevelChangedCallback onZoomLevelChanged; + final PdfZoomLevelChangedCallback? onZoomLevelChanged; /// Called when the text is selected or deselected in [SfPdfViewer]. /// @@ -615,7 +614,7 @@ class SfPdfViewer extends StatefulWidget { /// } /// } /// ``` - final PdfTextSelectionChangedCallback onTextSelectionChanged; + final PdfTextSelectionChangedCallback? onTextSelectionChanged; /// Called when the page changes in [SfPdfViewer]. /// @@ -631,7 +630,7 @@ class SfPdfViewer extends StatefulWidget { /// values in the [PdfPageChangedDetails] will be updated when the page changes. /// /// See also: [PdfPageChangedDetails]. - final PdfPageChangedCallback onPageChanged; + final PdfPageChangedCallback? onPageChanged; @override SfPdfViewerState createState() => SfPdfViewerState(); @@ -641,40 +640,41 @@ class SfPdfViewer extends StatefulWidget { /// /// Typically used to open and close the bookmark view. class SfPdfViewerState extends State with WidgetsBindingObserver { - PdfViewerPlugin _plugin; - ScrollController _scrollController; - PdfViewerController _pdfViewerController; - CancelableOperation _getPdfFileCancellableOperation, + late PdfViewerPlugin _plugin; + late ScrollController _scrollController; + late PdfViewerController _pdfViewerController; + CancelableOperation? _getPdfFileCancellableOperation, _pdfDocumentLoadCancellableOperation, _getHeightCancellableOperation, _getWidthCancellableOperation; - double _scrollHeadOffset; - List _originalHeight; - List _originalWidth; - bool _isScrollHeadDragged; - bool _isScrolled; + List? _originalHeight; + List? _originalWidth; + double _maxPdfPageWidth = 0.0; + bool _isScrolled = false; bool _isScrollControllerInitiated = false; - Orientation _deviceOrientation; - double _viewportWidth; - double _offsetBeforeOrientationChange; - BoxConstraints _viewportConstraints; - int _previousPageNumber; - PdfDocument _document; - bool _hasError; - bool _panEnabled; - bool _isTextSelectionCleared; - bool _isLoadCallbackInvoked; - double _actualViewportHeight; - double _actualViewportMaxScroll; + Orientation? _deviceOrientation; + double _viewportWidth = 0.0; + double _offsetBeforeOrientationChange = 0.0; + late BoxConstraints _viewportConstraints; + int _previousPageNumber = 0; + PdfDocument? _document; + bool _hasError = false; + bool _panEnabled = true; + bool _isMobile = false; + bool _isTextSelectionCleared = false; + bool _isLoadCallbackInvoked = false; + double _actualViewportHeight = 0.0; + double _actualViewportMaxScroll = 0.0; final Map _pdfPages = {}; final GlobalKey _bookmarkKey = GlobalKey(); final GlobalKey _pdfContainerKey = GlobalKey(); + final GlobalKey _scrollHeadOverlayKey = GlobalKey(); final Map> _pdfPagesKey = {}; - List _textCollection; - PdfTextExtractor _pdfTextExtractor; + List? _textCollection; + PdfTextExtractor? _pdfTextExtractor; /// PdfViewer theme data. - SfPdfViewerThemeData _pdfViewerThemeData; + SfPdfViewerThemeData? _pdfViewerThemeData; /// Indicates whether the built-in bookmark view in the [SfPdfViewer] is /// opened or not. @@ -686,32 +686,30 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { @override void initState() { super.initState(); + _plugin = PdfViewerPlugin(); _pdfViewerController = widget.controller ?? PdfViewerController(); _pdfViewerController.addListener(_handleControllerValueChange); - if (widget.key is PageStorageKey) { - final double offset = PageStorage.of(context).readState(context); - _scrollController = ScrollController(initialScrollOffset: offset ?? 0); + if (widget.key is PageStorageKey && PageStorage.of(context) != null) { + final double offset = PageStorage.of(context)!.readState(context); + _scrollController = ScrollController(initialScrollOffset: offset); final double zoomLevel = PageStorage.of(context)?.readState(context, identifier: 'zoomLevel_' + widget.key.toString()); - if (zoomLevel != null) { - _pdfViewerController.zoomLevel = zoomLevel; - } + _pdfViewerController.zoomLevel = zoomLevel; } else { _scrollController = ScrollController(initialScrollOffset: widget.initialScrollOffset.dy); } - _scrollHeadOffset = 0.0; _actualViewportHeight = _actualViewportMaxScroll = 0.0; _isScrolled = true; _offsetBeforeOrientationChange = 0; - _isScrollHeadDragged = true; _hasError = false; _panEnabled = true; _isTextSelectionCleared = false; _loadPdfDocument(false); _previousPageNumber = 1; _isLoadCallbackInvoked = false; - WidgetsBinding.instance.addObserver(this); + _maxPdfPageWidth = 0; + WidgetsBinding.instance?.addObserver(this); } @override @@ -730,7 +728,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (widget.controller != null) { _pdfViewerController.removeListener(_handleControllerValueChange); _pdfViewerController._reset(); - _pdfViewerController = widget.controller; + _pdfViewerController = widget.controller!; _pdfViewerController.addListener(_handleControllerValueChange); } } else { @@ -740,12 +738,20 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _pdfViewerController.addListener(_handleControllerValueChange); } else if (widget.controller != oldWidget.controller) { _pdfViewerController.removeListener(_handleControllerValueChange); - _pdfViewerController = widget.controller; + _pdfViewerController = widget.controller!; _pdfViewerController.addListener(_handleControllerValueChange); } } + _compareDocument(oldWidget._provider.getPdfBytes(context), + widget._provider.getPdfBytes(context)); + } - if (oldWidget._provider.getUserPath() != widget._provider.getUserPath()) { + // Compares the document bytes and load the PDF document if new bytes are provided. + void _compareDocument( + Future oldBytesData, Future newBytesData) async { + final oldBytes = await oldBytesData; + final newBytes = await newBytesData; + if (!listEquals(oldBytes, newBytes)) { _pdfViewerController.clearSelection(); // PDF document gets loaded only when the user changes // the input source of PDF document. @@ -761,12 +767,12 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _getWidthCancellableOperation?.cancel(); _pdfViewerThemeData = null; _scrollController.dispose(); - imageCache.clear(); - _plugin?.disposePages(); + imageCache?.clear(); + _plugin.closeDocument(); _disposeCollection(_originalHeight); _disposeCollection(_originalWidth); - _pdfPages?.clear(); - _pdfPagesKey?.clear(); + _pdfPages.clear(); + _pdfPagesKey.clear(); _document?.dispose(); _document = null; _pdfPagesKey[_pdfViewerController.pageNumber] @@ -774,14 +780,15 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ?.canvasRenderBox ?.dispose(); if (widget.onTextSelectionChanged != null) { - widget.onTextSelectionChanged(PdfTextSelectionChangedDetails(null, null)); + widget + .onTextSelectionChanged!(PdfTextSelectionChangedDetails(null, null)); } _pdfViewerController.removeListener(_handleControllerValueChange); - WidgetsBinding.instance.removeObserver(this); + WidgetsBinding.instance?.removeObserver(this); super.dispose(); } - void _disposeCollection(List list) { + void _disposeCollection(List? list) { if (list != null) { list = null; } @@ -789,58 +796,65 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Reset when PDF path is changed. void _reset() { + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.disposeMouseSelection(); + _isTextSelectionCleared = false; if (_scrollController.hasClients) { _scrollController.jumpTo(0.0); } - _scrollHeadOffset = 0.0; + _scrollHeadOverlayKey.currentState?.scrollHeadOffset = 0.0; _actualViewportHeight = _actualViewportMaxScroll = 0.0; _isScrolled = true; _offsetBeforeOrientationChange = 0; - _isScrollHeadDragged = true; + _scrollHeadOverlayKey.currentState?.isScrollHeadDragged = true; _previousPageNumber = 1; _pdfViewerController._reset(); _pdfContainerKey.currentState?.reset(); - _plugin.disposePages(); + _plugin.closeDocument(); _document?.dispose(); _document = null; - imageCache.clear(); + imageCache?.clear(); _hasError = false; _isLoadCallbackInvoked = false; - _pdfPagesKey?.clear(); + _isScrollControllerInitiated = false; + _pdfPagesKey.clear(); + _maxPdfPageWidth = 0; } /// Loads a PDF document and gets the page count from Plugin void _loadPdfDocument(bool isPdfChanged) async { try { _getPdfFileCancellableOperation = CancelableOperation.fromFuture( - widget._provider.getPdfPath(context), + widget._provider.getPdfBytes(context), ); - final String pdfPath = await _getPdfFileCancellableOperation.value; + final Uint8List pdfBytes = await _getPdfFileCancellableOperation?.value; if (isPdfChanged) { _reset(); + _plugin = PdfViewerPlugin(); } - _plugin = PdfViewerPlugin(pdfPath); _pdfDocumentLoadCancellableOperation = - CancelableOperation.fromFuture(_getPdfFile(pdfPath)); - _document = await _pdfDocumentLoadCancellableOperation.value; + CancelableOperation.fromFuture(_getPdfFile(pdfBytes)); + _document = await _pdfDocumentLoadCancellableOperation?.value; if (_document != null) { - _pdfTextExtractor = PdfTextExtractor(_document); + _pdfTextExtractor = PdfTextExtractor(_document!); } if (_document != null && widget.onDocumentLoaded != null) { - widget.onDocumentLoaded(PdfDocumentLoadedDetails(_document)); + widget.onDocumentLoaded!(PdfDocumentLoadedDetails(_document!)); } - final int pageCount = await _plugin.initializePdfRenderer(); + final int pageCount = await _plugin.initializePdfRenderer(pdfBytes); _pdfViewerController._pageCount = pageCount; if (pageCount > 0) { _pdfViewerController._pageNumber = 1; } - _pdfViewerController.zoomLevel ??= widget.initialZoomLevel; + _pdfViewerController.zoomLevel = widget.initialZoomLevel; _getHeightCancellableOperation = CancelableOperation.fromFuture(_plugin.getPagesHeight()); - _originalHeight = await _getHeightCancellableOperation.value; + _originalHeight = await _getHeightCancellableOperation?.value; _getWidthCancellableOperation = CancelableOperation.fromFuture(_plugin.getPagesWidth()); - _originalWidth = await _getWidthCancellableOperation.value; + _originalWidth = await _getWidthCancellableOperation?.value; } catch (e) { _pdfViewerController._reset(); _hasError = true; @@ -851,14 +865,14 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { errorMessage.contains( 'RangeError (end): Invalid value: Not in inclusive range')) { if (widget.onDocumentLoadFailed != null) { - widget.onDocumentLoadFailed(PdfDocumentLoadFailedDetails( + widget.onDocumentLoadFailed!(PdfDocumentLoadFailedDetails( 'Format Error', 'This document cannot be opened because it is corrupted or not a PDF.')); } } else if ((errorMessage .contains('Cannot open an encrypted document.'))) { if (widget.onDocumentLoadFailed != null) { - widget.onDocumentLoadFailed(PdfDocumentLoadFailedDetails( + widget.onDocumentLoadFailed!(PdfDocumentLoadFailedDetails( 'Encrypted PDF', 'This document cannot be opened because it is encrypted.')); } @@ -867,13 +881,13 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { else if (errorMessage.contains('Unable to load asset') || (errorMessage.contains('FileSystemException: Cannot open file'))) { if (widget.onDocumentLoadFailed != null) { - widget.onDocumentLoadFailed(PdfDocumentLoadFailedDetails( + widget.onDocumentLoadFailed!(PdfDocumentLoadFailedDetails( 'File Not Found', 'The document cannot be opened because the provided path or link is invalid.')); } } else { if (widget.onDocumentLoadFailed != null) { - widget.onDocumentLoadFailed(PdfDocumentLoadFailedDetails( + widget.onDocumentLoadFailed!(PdfDocumentLoadFailedDetails( 'Error', 'There was an error opening this document.')); } } @@ -883,13 +897,9 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } /// Get the file of the Pdf. - Future _getPdfFile(String value) async { + Future _getPdfFile(Uint8List? value) async { if (value != null) { - final File pdfFile = File(value); - final bytes = await pdfFile.readAsBytes(); - if (bytes != null) { - return PdfDocument(inputBytes: bytes); - } + return PdfDocument(inputBytes: value); } return null; } @@ -906,15 +916,10 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Invoke the [PdfViewerController] methods on document load time. void _scrollControllerInitiated() { if (!_isScrollControllerInitiated) { - if (_pdfViewerController._horizontalOffset != null || - _pdfViewerController._verticalOffset != null) { - _pdfViewerController.jumpTo( - xOffset: _pdfViewerController._horizontalOffset, - yOffset: _pdfViewerController._verticalOffset); - } - if (_scrollController.hasClients && - _pdfViewerController._pageNavigator != null && - _pdfViewerController._pageNavigator.option != null) { + _pdfViewerController.jumpTo( + xOffset: _pdfViewerController._horizontalOffset, + yOffset: _pdfViewerController._verticalOffset); + if (_scrollController.hasClients) { _pdfViewerController.notifyPropertyChangedListeners( property: 'pageNavigate'); } @@ -928,34 +933,60 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// The below calculation is done to adjust the scroll head position /// whenever keypad is raised up and down. final isKeyPadRaised = - WidgetsBinding.instance.window.viewInsets.bottom != 0.0; + WidgetsBinding.instance?.window.viewInsets.bottom != 0.0; if (isKeyPadRaised) { - _actualViewportHeight = - _scrollController?.position?.viewportDimension ?? 0.0; + _actualViewportHeight = _scrollController.position.viewportDimension; _actualViewportMaxScroll = _scrollController.position.maxScrollExtent; } else { if (_actualViewportHeight != 0.0) { - _updateScrollHeadPosition(_actualViewportHeight, + _scrollHeadOverlayKey.currentState?.updateScrollHeadPosition( + _actualViewportHeight, maxScrollExtent: _actualViewportMaxScroll); + _updateCurrentPageNumber(); } } } + /// Find whether device is mobile or Laptop. + void _findDevice(BuildContext context) { + /// Standard diagonal offset of tablet. + const double _kPdfStandardDiagonalOffset = 1100.0; + final Size size = MediaQuery.of(context).size; + final double diagonal = + sqrt((size.width * size.width) + (size.height * size.height)); + _isMobile = diagonal < _kPdfStandardDiagonalOffset; + } + + /// Get the global rect of viewport region. + Rect? _getViewportGlobalRect() { + Rect? viewportGlobalRect; + if (kIsWeb && !_isMobile && _pdfContainerKey.currentContext != null) { + final RenderBox? viewportRenderBox = + // ignore: avoid_as + _pdfContainerKey.currentContext!.findRenderObject() as RenderBox; + final Offset? position = viewportRenderBox?.localToGlobal(Offset.zero); + final Size? containerSize = viewportRenderBox?.size; + viewportGlobalRect = Rect.fromLTWH(position?.dx ?? 0, position?.dy ?? 0, + containerSize?.width ?? 0, containerSize?.height ?? 0); + } + return viewportGlobalRect; + } + @override Widget build(BuildContext context) { final emptyContainer = Container( - color: _pdfViewerThemeData.backgroundColor, + color: _pdfViewerThemeData!.backgroundColor, ); final emptyLinearProgressView = Stack( children: [ emptyContainer, LinearProgressIndicator( valueColor: AlwaysStoppedAnimation( - _pdfViewerThemeData.progressBarColor ?? + _pdfViewerThemeData!.progressBarColor ?? Theme.of(context).primaryColor), - backgroundColor: _pdfViewerThemeData.progressBarColor == null + backgroundColor: _pdfViewerThemeData!.progressBarColor == null ? Theme.of(context).primaryColor.withOpacity(0.2) - : _pdfViewerThemeData.progressBarColor.withOpacity(0.2), + : _pdfViewerThemeData!.progressBarColor!.withOpacity(0.2), ), ], ); @@ -963,21 +994,23 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { // call PdfViewerController methods after ScrollController attached. _isScrollPositionAttached(); + /// Find whether device is mobile or Laptop. + _findDevice(context); + final isPdfLoaded = (_pdfViewerController.pageCount > 0 && _originalWidth != null && _originalHeight != null); - return isPdfLoaded ? Container( - color: _pdfViewerThemeData.backgroundColor, + color: _pdfViewerThemeData!.backgroundColor, child: FutureBuilder( future: _getImages(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { final _pdfImages = snapshot.data; _viewportConstraints = context - .findRenderObject() - // ignore: avoid_as, invalid_use_of_protected_member + .findRenderObject()! + // ignore: invalid_use_of_protected_member, avoid_as .constraints as BoxConstraints; double totalHeight = 0; return NotificationListener( @@ -989,16 +1022,20 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { children: [ Center( child: PdfContainer( - key: _pdfContainerKey, - panEnabled: _panEnabled, - onZoomLevelChanged: widget.onZoomLevelChanged, - pdfController: _pdfViewerController, - scrollController: _scrollController, - enableDoubleTapZooming: - widget.enableDoubleTapZooming, - initialScrollOffset: widget.initialScrollOffset, - initialZoomLevel: widget.initialZoomLevel, - itemBuilder: (BuildContext context, int index) { + _pdfContainerKey, + _isMobile, + widget.initialScrollOffset, + widget.initialZoomLevel, + widget.enableDoubleTapZooming, + widget.onZoomLevelChanged, + _pdfViewerController, + _handleOnTap, + widget.interactionMode, + _viewportConstraints, + _maxPdfPageWidth, + _panEnabled, + _scrollController, + (BuildContext context, int index) { if (index == 0) { totalHeight = 0; } @@ -1007,47 +1044,70 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { BoxConstraints( maxWidth: _viewportConstraints.maxWidth, maxHeight: double.infinity), - _originalWidth[index], - _originalHeight[index], + _originalWidth![index], + _originalHeight![index], _viewportConstraints.maxWidth); if (!_pdfPagesKey.containsKey(pageIndex)) { _pdfPagesKey[pageIndex] = GlobalKey(); } + if (kIsWeb && + !_isMobile && + _originalWidth![index] > _maxPdfPageWidth) { + _maxPdfPageWidth = _originalWidth![index]; + } + Rect? viewportGlobalRect; + if (_isTextSelectionCleared) { + viewportGlobalRect = _getViewportGlobalRect(); + } final PdfPageView page = PdfPageView( - key: _pdfPagesKey[pageIndex], - imageStream: _pdfImages[pageIndex], - width: calculatedSize.width, - height: calculatedSize.height, - pageSpacing: widget.pageSpacing, - pdfDocument: _document, - pdfPages: _pdfPages, - scrollController: _scrollController, - pdfViewerController: _pdfViewerController, - enableDocumentLinkAnnotation: - widget.enableDocumentLinkAnnotation, - pageIndex: index, - onTextSelectionChanged: - widget.onTextSelectionChanged, - onTextSelectionDragStarted: - _handleTextSelectionDragStarted, - onTextSelectionDragEnded: - _handleTextSelectionDragEnded, - enableTextSelection: - widget.enableTextSelection, - textCollection: _textCollection, - searchTextHighlightColor: - widget.searchTextHighlightColor, - pdfTextSearchResult: - _pdfViewerController._pdfTextSearchResult, + _pdfPagesKey[pageIndex]!, + _pdfImages[pageIndex], + viewportGlobalRect, + widget.interactionMode, + (kIsWeb && !_isMobile) + ? _originalWidth![index] + : calculatedSize.width, + (kIsWeb && !_isMobile) + ? _originalHeight![index] + : calculatedSize.height, + widget.pageSpacing, + _document, + _pdfPages, + index, + _scrollController, + _pdfViewerController, + widget.enableDocumentLinkAnnotation, + widget.enableTextSelection, + widget.onTextSelectionChanged, + _handleTextSelectionDragStarted, + _handleTextSelectionDragEnded, + _handleDocumentLinkNavigationInvoked, + widget.searchTextHighlightColor, + _textCollection, + _isMobile, + _pdfViewerController._pdfTextSearchResult, ); - _pdfPages[pageIndex] = - PdfPageInfo(totalHeight, calculatedSize); - totalHeight += - calculatedSize.height + widget.pageSpacing; - _updateOffsetOnOrientationChange( - _offsetBeforeOrientationChange, - pageIndex, - totalHeight); + if (kIsWeb && !_isMobile) { + _pdfPages[pageIndex] = PdfPageInfo( + totalHeight, + Size(_originalWidth![index], + _originalHeight![index])); + totalHeight += _originalHeight![index] + + widget.pageSpacing; + _updateOffsetOnOrientationChange( + _offsetBeforeOrientationChange, + pageIndex, + totalHeight); + } else { + _pdfPages[pageIndex] = + PdfPageInfo(totalHeight, calculatedSize); + totalHeight += calculatedSize.height + + widget.pageSpacing; + _updateOffsetOnOrientationChange( + _offsetBeforeOrientationChange, + pageIndex, + totalHeight); + } if (_pdfPagesKey[ _pdfViewerController.pageNumber] ?.currentState @@ -1057,33 +1117,40 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _isTextSelectionCleared = true; Future.delayed(Duration.zero, () async { _clearSelection(); + _pdfPagesKey[ + _pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.disposeMouseSelection(); + _pdfPagesKey[ + _pdfViewerController.pageNumber] + ?.currentState + ?.focusNode + .requestFocus(); }); } return page; }, ), ), + ScrollHeadOverlay( + _scrollHeadOverlayKey, + widget.canShowScrollHead, + widget.canShowPaginationDialog, + _handleScrollHeadDragEnd, + _scrollController, + _isMobile, + _pdfViewerController), Visibility( - visible: widget.canShowScrollHead && - _pdfViewerController.pageCount > 1, - child: ScrollHeadOverlay( - canShowPaginationDialog: - widget.canShowPaginationDialog, - scrollHeadOffset: _scrollHeadOffset, - onScrollHeadDragStart: - _handleScrollHeadDragStart, - onScrollHeadDragUpdate: - _handleScrollHeadDragUpdate, - onScrollHeadDragEnd: _handleScrollHeadDragEnd, - pdfViewerController: _pdfViewerController)), - Visibility( - visible: !_isScrollHeadDragged && - widget.canShowScrollStatus, + visible: + _scrollHeadOverlayKey.currentState != null + ? (!(_scrollHeadOverlayKey.currentState! + .isScrollHeadDragged) && + widget.canShowScrollStatus) + : false, child: ScrollStatus(_pdfViewerController)), BookmarkView( - key: _bookmarkKey, - pdfDocument: _document, - controller: _pdfViewerController), + _bookmarkKey, _document, _pdfViewerController), ], ), ); @@ -1105,16 +1172,92 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// using Navigator.pop(context); void openBookmarkView() { if (widget.onTextSelectionChanged != null) { - widget.onTextSelectionChanged(PdfTextSelectionChangedDetails(null, null)); + widget + .onTextSelectionChanged!(PdfTextSelectionChangedDetails(null, null)); } _bookmarkKey.currentState?.open(); } + /// Gets the selected text lines in the [SfPdfViewer]. + /// + /// This example demonstrates how to highlight the selected text in the [SfPdfViewer]. + /// + /// ```dart + /// class MyAppState extends State{ + /// final GlobalKey _pdfViewerKey = GlobalKey(); + /// final PdfViewerController _pdfViewerController = PdfViewerController(); + /// final String _pdfPath = 'assets/sample.pdf'; + /// Uint8List _memoryBytes; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: Text('Syncfusion Flutter PDF Viewer'), + /// actions: [ + /// IconButton( + /// icon: Icon( + /// Icons.check_box_outline_blank_outlined, + /// color: Colors.white, + /// ), + /// onPressed: () async { + /// final ByteData data = await rootBundle.load(_pdfPath); + /// final PdfDocument document = PdfDocument( + /// inputBytes: data.buffer + /// .asUint8List(data.offsetInBytes, data.lengthInBytes)); + /// if (document != null) { + /// _pdfViewerKey.currentState.getSelectedTextLines().forEach((pdfTextline) { + /// final PdfPage _page = document.pages[pdfTextline.pageNumber]; + /// final PdfRectangleAnnotation rectangleAnnotation = + /// PdfRectangleAnnotation(pdfTextline.bounds, + /// 'Rectangle Annotation', + /// author: 'Syncfusion', + /// color: PdfColor.fromCMYK(0, 0, 255, 0), + /// innerColor: PdfColor.fromCMYK(0, 0, 255, 0), + /// opacity: 0.5,); + /// _page.annotations.add(rectangleAnnotation); + /// _page.annotations.flatten = true; + /// }); + /// final List bytes = document.save(); + /// _memoryBytes = Uint8List.fromList(bytes); + /// }},), + /// IconButton( + /// icon: Icon( + /// Icons.file_upload, + /// color: Colors.white, + /// ), + /// onPressed: () { + /// _pdfViewerController.clearSelection(); + /// Navigator.push( + /// context, + /// MaterialPageRoute( + /// builder: (context) => SafeArea( + /// child: SfPdfViewer.memory( + /// _memoryBytes, + /// ), + /// )),);},),],), + /// body: SfPdfViewer.asset( + /// _pdfPath, + /// key: _pdfViewerKey, + /// controller: _pdfViewerController, + /// ),); + /// } + /// } + /// ``` + List getSelectedTextLines() { + final List? selectedTextLines = + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.getSelectedTextLines(); + return selectedTextLines ?? []; + } + /// Get the rendered pages from plugin. - Future> _getImages() { - final int startPage = _pdfViewerController.pageNumber; + Future?>? _getImages() { + int startPage = _pdfViewerController.pageNumber; int endPage = _pdfViewerController.pageCount == 1 ? 1 : 2; - Future> renderedPages; + Future?>? renderedPages; double pageHeight = 0; if (_pdfPages.isNotEmpty && _scrollController.hasClients) { for (int start = _pdfViewerController.pageNumber; @@ -1122,9 +1265,12 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { start++) { if (start == _pdfViewerController.pageCount) { endPage = start; + if (startPage == endPage && endPage != 1) { + startPage = startPage - 1; + } break; } - final height = pageHeight + _pdfPages[start].pageSize.height; + final height = pageHeight + _pdfPages[start]!.pageSize.height; if (height < _scrollController.position.viewportDimension) { pageHeight += height; } else { @@ -1136,7 +1282,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } if (_isScrolled) { renderedPages = - _plugin?.getSpecificPages(startPage, endPage)?.then((value) { + _plugin.getSpecificPages(startPage, endPage).then((value) { return value; }); renderedPages.whenComplete(_checkMount); @@ -1153,26 +1299,46 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Triggers the page changed callback when current page number is changed void _pageChanged() { - if (widget.onPageChanged != null) { - if (_pdfViewerController.pageNumber != _previousPageNumber) { + if (_pdfViewerController.pageNumber != _previousPageNumber) { + if (widget.onPageChanged != null) { /// Triggering the page changed callback and pass the page changed details - widget.onPageChanged(PdfPageChangedDetails( - newPage: _pdfViewerController.pageNumber, - oldPage: _previousPageNumber, - isFirst: _pdfViewerController.pageNumber == 1, - isLast: - _pdfViewerController.pageNumber == _pdfViewerController.pageCount, + widget.onPageChanged!(PdfPageChangedDetails( + _pdfViewerController.pageNumber, + _previousPageNumber, + _pdfViewerController.pageNumber == 1, + _pdfViewerController.pageNumber == _pdfViewerController.pageCount, )); _previousPageNumber = _pdfViewerController.pageNumber; } + _isPageChanged = true; } _checkMount(); } + bool _isPageChanged = false; + + void _updateSearchInstance({isNext = true}) { + if (_textCollection != null && + _pdfViewerController._pdfTextSearchResult.hasResult && + _pdfViewerController.pageNumber != + (_textCollection![_pdfViewerController + ._pdfTextSearchResult.currentInstanceIndex - + 1] + .pageIndex + + 1) && + _isManualScrolled) { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = + _getInstanceInPage(_pdfViewerController.pageNumber, + lookForFirst: isNext); + } + } + /// Whenever orientation is changed, PDF page is changed based on viewport /// dimension so offset must be restored to avoid reading continuity loss. void _updateOffsetOnOrientationChange( double initialOffset, int pageIndex, double totalHeight) { + _pdfViewerController._scrollPositionX = + _pdfContainerKey.currentState?.findHorizontalOffset() ?? 0.0; if (_viewportWidth != _viewportConstraints.maxWidth && _deviceOrientation != MediaQuery.of(context).orientation) { if (pageIndex == 1 && _scrollController.hasClients) { @@ -1182,12 +1348,13 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _scrollController.position.maxScrollExtent * _pdfViewerController.zoomLevel); } else if (pageIndex == _pdfViewerController.pageCount) { - if (_viewportWidth != null) { + if (_viewportWidth != 0) { final targetOffset = initialOffset * totalHeight; - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + WidgetsBinding.instance?.addPostFrameCallback((timeStamp) { _scrollController.jumpTo(targetOffset); - _updateScrollHeadPosition( + _scrollHeadOverlayKey.currentState?.updateScrollHeadPosition( _scrollController.position.viewportDimension); + _updateCurrentPageNumber(); }); } _viewportWidth = _viewportConstraints.maxWidth; @@ -1211,10 +1378,13 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { Size(originalWidth, originalHeight)); } + bool _isManualScrolled = false; + /// Handles the widget based on scroll change. void _handleScrollNotification(ScrollNotification notification) { if (notification is ScrollStartNotification) { - if (_isScrollHeadDragged) { + if (_scrollHeadOverlayKey.currentState != null && + _scrollHeadOverlayKey.currentState!.isScrollHeadDragged) { _previousPageNumber = _pdfViewerController.pageNumber; } _pdfPagesKey[_pdfViewerController.pageNumber] @@ -1222,44 +1392,47 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ?.canvasRenderBox ?.scrollStarted(); _isScrolled = false; + + _isManualScrolled = notification.dragDetails != null || + PointerSignalKind.scroll != PointerSignalKind.none; } else if (notification is ScrollUpdateNotification) { - _updateScrollHeadPosition(_scrollController.position.viewportDimension); + _pdfViewerController._scrollPositionY = _scrollController.offset; + _scrollHeadOverlayKey.currentState?.updateScrollHeadPosition( + _scrollController.position.viewportDimension); + _updateCurrentPageNumber(); _isScrolled = _isLoadCallbackInvoked ? false : true; } else if (notification is ScrollEndNotification) { + if (kIsWeb && _textCollection == null && !_isMobile) { + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.focusNode + .requestFocus(); + } _pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState ?.canvasRenderBox ?.scrollEnded(); - _updateScrollHeadPosition(_scrollController.position.viewportDimension); - if (_isScrollHeadDragged) { + _scrollHeadOverlayKey.currentState?.updateScrollHeadPosition( + _scrollController.position.viewportDimension); + _updateCurrentPageNumber(); + if (_scrollHeadOverlayKey.currentState != null && + _scrollHeadOverlayKey.currentState!.isScrollHeadDragged) { _isScrolled = true; _pageChanged(); } } } - /// Updates the scroll head position when scrolling occurs. - void _updateScrollHeadPosition(double height, {double maxScrollExtent}) { - { - if (_scrollController.hasClients) { - if (_scrollController.offset > 0) { - final positionRatio = (_scrollController.position.pixels / - (maxScrollExtent ?? _scrollController.position.maxScrollExtent)); - // Calculating the scroll head position based on ratio of - // current position with ListView's MaxScrollExtent - _scrollHeadOffset = (positionRatio * (height - kPdfScrollHeadHeight)); - // As the returned values are in double. - // The ceil of the value will be applied - // ex. 1.2 means 2 will be current page number - _pdfViewerController._pageNumber = - _getPageNumber(_scrollController.offset); - } else { - // This conditions gets hit when scrolled to 0.0 offset - _scrollHeadOffset = 0.0; - _pdfViewerController._pageNumber = 1; - } - _checkMount(); + /// Updates current page number when scrolling occurs. + void _updateCurrentPageNumber() { + if (_scrollController.hasClients) { + if (_scrollController.offset > 0) { + _pdfViewerController._pageNumber = + _getPageNumber(_scrollController.offset); + } else { + _pdfViewerController._pageNumber = 1; } + _checkMount(); } } @@ -1273,111 +1446,99 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _panEnabled = true; } - /// updates UI when scroll head drag is started. - void _handleScrollHeadDragStart(DragStartDetails details) { - _isScrollHeadDragged = false; - } - - /// updates UI when scroll head drag is updating. - void _handleScrollHeadDragUpdate(DragUpdateDetails details) { - final double dragOffset = details.delta.dy + _scrollHeadOffset; - final double scrollHeadPosition = - _scrollController.position.viewportDimension - kPdfScrollHeadHeight; - - // Based on the dragOffset the pdf pages must be scrolled - // and scroll position must be updated - // This if clause condition can be split into three behaviors. - // 1. Normal case - Here, based on scroll head position ratio with - // viewport height, scroll view position is changed. - // 2. End to End cases - - // There few case, where 0.0000123(at start) and 0.99999934(at end) - // factors are computed. For these case, scroll to their ends - // in scrollview. Similarly, for out of bound drag offsets. - if (dragOffset < scrollHeadPosition && - dragOffset >= 0 && - _scrollHeadOffset != null) { - _scrollController.jumpTo(_scrollController.position.maxScrollExtent * - (dragOffset / scrollHeadPosition)); - _scrollHeadOffset = dragOffset; - } else { - if (dragOffset < 0) { - _scrollController.jumpTo(_scrollController.position.minScrollExtent); - _scrollHeadOffset = 0.0; - } else { - _scrollController.jumpTo(_scrollController.position.maxScrollExtent); - _scrollHeadOffset = scrollHeadPosition; - } - } - } - /// updates UI when scroll head drag is ended. - void _handleScrollHeadDragEnd(DragEndDetails details) { - _isScrollHeadDragged = true; + void _handleScrollHeadDragEnd() { _isScrolled = true; + _isManualScrolled = true; _pageChanged(); } + /// Handles tap event of pdf container. + void _handleOnTap() { + _clearSelection(); + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.focusNode + .requestFocus(); + } + /// Jump to desired page void _jumpToPage(int pageNumber) { - _scrollController?.position?.jumpTo(_pdfPages[pageNumber].pageOffset); + _scrollController.position.jumpTo(_pdfPages[pageNumber]!.pageOffset); + _isManualScrolled = true; } /// Jump to the bookmark location. - void _jumpToBookmark(PdfBookmark bookmark) { - _clearSelection(); - double heightPercentage; - double bookmarkOffset; - PdfPage pdfPage; - if (bookmark.namedDestination != null) { - pdfPage = bookmark.namedDestination.destination.page; - } else { - pdfPage = bookmark.destination.page; - } - final int index = _document.pages.indexOf(pdfPage) + 1; - final revealedOffset = _pdfPages[index].pageSize; - double offset = _pdfPages[index].pageOffset; - if (bookmark.namedDestination != null) { - heightPercentage = - (bookmark.namedDestination.destination.page.size.height / - revealedOffset.height); - bookmarkOffset = bookmark.namedDestination.destination.location.dy; - } else { - heightPercentage = - (bookmark.destination.page.size.height / revealedOffset.height); - bookmarkOffset = bookmark.destination.location.dy; - } - final double maxScrollEndOffset = - _scrollController.position.maxScrollExtent; - offset = (offset + (bookmarkOffset / heightPercentage)); - if (offset > maxScrollEndOffset) { - offset = maxScrollEndOffset; + void _jumpToBookmark(PdfBookmark? bookmark) { + if (bookmark != null && _document != null) { + _clearSelection(); + double heightPercentage; + double bookmarkOffset; + PdfPage pdfPage; + if (bookmark.namedDestination != null) { + pdfPage = bookmark.namedDestination!.destination!.page; + } else { + pdfPage = bookmark.destination!.page; + } + final int index = _document!.pages.indexOf(pdfPage) + 1; + final revealedOffset = _pdfPages[index]!.pageSize; + double offset = _pdfPages[index]!.pageOffset; + if (bookmark.namedDestination != null) { + heightPercentage = + bookmark.namedDestination!.destination!.page.size.height / + revealedOffset.height; + bookmarkOffset = bookmark.namedDestination!.destination!.location.dy; + } else { + heightPercentage = + bookmark.destination!.page.size.height / revealedOffset.height; + bookmarkOffset = bookmark.destination!.location.dy; + } + if (kIsWeb && !_isMobile) { + heightPercentage = 1.0; + } + final double maxScrollEndOffset = + _scrollController.position.maxScrollExtent; + offset = (offset + (bookmarkOffset / heightPercentage)); + if (offset > maxScrollEndOffset) { + offset = maxScrollEndOffset; + } + if (_pdfViewerController.zoomLevel > 1) { + _pdfContainerKey.currentState + ?.jumpOnZoomedDocument(null, offset, _viewportWidth); + } else { + _scrollController.jumpTo(offset); + } } - _scrollController.jumpTo(offset); + } + + /// trigger when document Link navigation perform on zoomed document + void _handleDocumentLinkNavigationInvoked(double offset) { + _pdfContainerKey.currentState + ?.jumpOnZoomedDocument(null, offset, _viewportWidth); } /// clears the text selection. bool _clearSelection() { return _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.canvasRenderBox - ?.clearSelection(); + ?.currentState + ?.canvasRenderBox + ?.clearSelection() ?? + false; } /// Call the method according to property name. - void _handleControllerValueChange({String property}) { + void _handleControllerValueChange({String? property}) { if (property == 'jumpToBookmark') { _jumpToBookmark(_pdfViewerController._pdfBookmark); } else if (property == 'zoomLevel') { - PageStorage.of(context).writeState( + PageStorage.of(context)?.writeState( context, _pdfViewerController.zoomLevel, identifier: 'zoomLevel_' + widget.key.toString()); } else if (property == 'clearTextSelection') { _pdfViewerController._clearTextSelection = _clearSelection(); } else if (property == 'jumpTo') { _clearSelection(); - if (_scrollController != null && - _scrollController.hasClients && - _pdfViewerController._verticalOffset != null) { + if (_scrollController.hasClients) { final yOffset = _pdfViewerController._verticalOffset; if (yOffset < _scrollController.position.minScrollExtent) { _scrollController.jumpTo(_scrollController.position.minScrollExtent); @@ -1386,22 +1547,21 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } else { _scrollController.jumpTo(yOffset); } + _isManualScrolled = true; + _scrollController.position.notifyListeners(); } - if (_pdfViewerController._horizontalOffset != null) { - _pdfContainerKey.currentState - ?.jumpHorizontally(_pdfViewerController._horizontalOffset); - } - } else if (property == 'pageNavigate') { + _pdfContainerKey.currentState + ?.jumpHorizontally(_pdfViewerController._horizontalOffset); + } else if (property == 'pageNavigate' && + _pdfViewerController._pageNavigator != null) { _clearSelection(); - if (_pdfViewerController._pageNavigator != null && - _scrollController != null && - _scrollController.hasClients) { - switch (_pdfViewerController._pageNavigator.option) { + if (_scrollController.hasClients) { + switch (_pdfViewerController._pageNavigator!.option) { case Navigation.jumpToPage: - if (_pdfViewerController._pageNavigator.index > 0 && - _pdfViewerController._pageNavigator.index <= + if (_pdfViewerController._pageNavigator!.index! > 0 && + _pdfViewerController._pageNavigator!.index! <= _pdfViewerController.pageCount) { - _jumpToPage(_pdfViewerController._pageNavigator.index); + _jumpToPage(_pdfViewerController._pageNavigator!.index!); } break; case Navigation.firstPage: @@ -1427,68 +1587,25 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } } } else if (property == 'searchText') { - if (_pdfViewerController._searchText != null) { - _pdfViewerController._pdfTextSearchResult - .removeListener(_handleTextSearch); - - _textCollection = _pdfTextExtractor.findText( - [_pdfViewerController._searchText], - searchOption: - _pdfViewerController._textSearchOption.toString().isNotEmpty || - _pdfViewerController._textSearchOption != null - ? _pdfViewerController._textSearchOption - : TextSearchOption.both, - ); - - if (_textCollection.isEmpty) { - _pdfViewerController._pdfTextSearchResult?._currentOccurrenceIndex = - 0; - - _pdfViewerController._pdfTextSearchResult?._totalSearchTextCount = 0; - _pdfViewerController._pdfTextSearchResult?._updateResult(false); - } else { - if (_pdfViewerController.pageNumber == 1) { - if (_pdfViewerController.pageNumber - 1 != - _textCollection[0].pageIndex) { - _pdfViewerController.jumpTo( - yOffset: - _pdfPages[_textCollection[0].pageIndex + 1].pageOffset); - } - _pdfViewerController._pdfTextSearchResult?._currentOccurrenceIndex = - 1; - } else { - for (int i = 0; i < _textCollection.length; i++) { - if (_textCollection[i].pageIndex == - _pdfViewerController.pageNumber - 1) { - _pdfViewerController.jumpTo( - yOffset: - _pdfPages[_textCollection[i].pageIndex + 1].pageOffset); - _pdfViewerController - ._pdfTextSearchResult?._currentOccurrenceIndex = i + 1; - break; - } else { - if (_textCollection[i].pageIndex > - _pdfViewerController.pageNumber - 1) { - if (_textCollection[i].pageIndex + 1 >= - _pdfViewerController.pageCount) { - _pdfViewerController - .jumpToPage(_pdfViewerController.pageCount); - } else { - _pdfViewerController.jumpTo( - yOffset: _pdfPages[_textCollection[i].pageIndex + 1] - .pageOffset); - } - _pdfViewerController - ._pdfTextSearchResult?._currentOccurrenceIndex = i + 1; - break; - } - } - } - } + _pdfViewerController._pdfTextSearchResult + .removeListener(_handleTextSearch); + _textCollection = _pdfTextExtractor?.findText( + [_pdfViewerController._searchText], + searchOption: _pdfViewerController._textSearchOption, + ); + if (_textCollection != null) { + if (_textCollection!.isEmpty) { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 0; - _pdfViewerController._pdfTextSearchResult?._totalSearchTextCount = - _textCollection.length; - _pdfViewerController._pdfTextSearchResult?._updateResult(true); + _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = 0; + _pdfViewerController._pdfTextSearchResult._updateResult(false); + } else { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = + _getInstanceInPage(_pdfViewerController.pageNumber); + _jumpToSearchInstance(); + _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = + _textCollection!.length; + _pdfViewerController._pdfTextSearchResult._updateResult(true); } _pdfViewerController._pdfTextSearchResult .addListener(_handleTextSearch); @@ -1498,14 +1615,14 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Retrieves the page number based on the offset of the page. int _getPageNumber(double offset) { - int pageNumber; + int pageNumber = 0; for (int i = 1; i <= _pdfViewerController.pageCount; i++) { if (i == _pdfViewerController.pageCount || offset >= _scrollController.position.maxScrollExtent) { pageNumber = _pdfViewerController.pageCount; break; - } else if (offset >= _pdfPages[i].pageOffset && - offset < _pdfPages[i + 1].pageOffset) { + } else if (offset >= _pdfPages[i]!.pageOffset && + offset < _pdfPages[i + 1]!.pageOffset) { pageNumber = i; break; } else { @@ -1515,170 +1632,107 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { return pageNumber; } - /// Call the method according to property name. - void _handleTextSearch({String property}) { - if (property == 'nextInstance') { - int _pageNumber; - bool matched = true; - if (_textCollection[_pdfViewerController - ._pdfTextSearchResult.currentInstanceIndex - - 1] - .pageIndex == - _pdfViewerController.pageNumber - 1) { - _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = - _pdfViewerController._pdfTextSearchResult.currentInstanceIndex + 1; - } else { - for (int i = 0; i < _textCollection.length; i++) { - if (_textCollection[i].pageIndex == - _pdfViewerController.pageNumber - 1) { - if (_textCollection[i].pageIndex != _pageNumber) { - _pdfViewerController - ._pdfTextSearchResult._currentOccurrenceIndex = i + 1; - _pageNumber = _textCollection[i].pageIndex; - matched = false; - } - } else if (_textCollection[i].pageIndex > - _pdfViewerController.pageNumber - 1) { - if (matched) { - _pdfViewerController - ._pdfTextSearchResult._currentOccurrenceIndex = i + 1; - } - break; - } - } - } - - if (_pdfViewerController._pdfTextSearchResult.currentInstanceIndex - 1 == - _pdfViewerController._pdfTextSearchResult.totalInstanceCount) { - _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 1; - } - final int currentInstancePageIndex = (_textCollection[_pdfViewerController - ._pdfTextSearchResult.currentInstanceIndex - - 1] - .pageIndex + - 1); + int _getInstanceInPage(int pageNumber, {bool lookForFirst = true}) { + int? instance = 0; + if (lookForFirst) { + instance = _jumpToNextInstance(pageNumber) ?? + _jumpToPreviousInstance(pageNumber); + } else { + instance = _jumpToPreviousInstance(pageNumber) ?? + _jumpToNextInstance(pageNumber); + } + return instance ?? 1; + } - final double topOffset = _textCollection[ - _pdfViewerController._pdfTextSearchResult.currentInstanceIndex - - 1] - .bounds - .top; - - final double heightPercentage = - _originalHeight[currentInstancePageIndex - 1] / - _pdfPages[currentInstancePageIndex].pageSize.height; - - if (_deviceOrientation == Orientation.landscape) { - if (_pdfPages[currentInstancePageIndex].pageOffset + - (topOffset / heightPercentage) <= - _scrollController.offset || - _pdfPages[currentInstancePageIndex].pageOffset + - (topOffset / heightPercentage) >= - _scrollController.offset + - _scrollController.position.viewportDimension) { - _pdfViewerController.jumpTo( - yOffset: (_pdfPages[currentInstancePageIndex].pageOffset + - (topOffset / heightPercentage))); - } - } else { - if (currentInstancePageIndex != _pdfViewerController.pageNumber) { - _pdfViewerController.jumpToPage(currentInstancePageIndex); - } - if (_pdfPages[currentInstancePageIndex].pageOffset + - (topOffset / heightPercentage) <= - _scrollController.offset || - _pdfPages[currentInstancePageIndex].pageOffset + - (topOffset / heightPercentage) >= - _scrollController.offset + - _scrollController.position.viewportDimension) { - _pdfViewerController.jumpTo( - yOffset: (_pdfPages[currentInstancePageIndex].pageOffset + - (topOffset / heightPercentage))); - } - } - } else if (property == 'previousInstance') { - _clearSelection(); - bool matched = true; - if (_textCollection[_pdfViewerController - ._pdfTextSearchResult.currentInstanceIndex - - 1] - .pageIndex == - _pdfViewerController.pageNumber - 1) { - _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = - _pdfViewerController._pdfTextSearchResult.currentInstanceIndex - 1; - } else { - for (int i = 0; i < _textCollection.length; i++) { - if (_textCollection[i].pageIndex == - _pdfViewerController.pageNumber - 1) { - _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = - i + 1; - matched = false; - } else if (_textCollection[i].pageIndex > - _pdfViewerController.pageNumber - 1) { - if (matched) { - _pdfViewerController._pdfTextSearchResult - ._currentOccurrenceIndex = i - 1 == 0 ? 1 : i; - } - break; - } - } + int? _jumpToNextInstance(int pageNumber) { + for (int i = 0; i < _textCollection!.length; i++) { + if (_textCollection![i].pageIndex + 1 >= pageNumber) { + return i + 1; } + } + return null; + } - if (_pdfViewerController._pdfTextSearchResult.currentInstanceIndex - 1 < - 0) { - _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = - _pdfViewerController._pdfTextSearchResult.totalInstanceCount; + int? _jumpToPreviousInstance(int pageNumber) { + for (int i = _textCollection!.length - 1; i > 0; i--) { + if (_textCollection![i].pageIndex + 1 <= pageNumber) { + return i + 1; } + } + return null; + } - final int currentInstancePageIndex = (_textCollection[_pdfViewerController + void _jumpToSearchInstance({isNext = true}) { + if (_isPageChanged) { + _updateSearchInstance(isNext: isNext); + _isPageChanged = false; + } + final int currentInstancePageIndex = (_textCollection![ + _pdfViewerController._pdfTextSearchResult.currentInstanceIndex - + 1] + .pageIndex + + 1); + + final double topOffset = _textCollection![ + _pdfViewerController._pdfTextSearchResult.currentInstanceIndex - 1] + .bounds + .top; + + final double heightPercentage = (kIsWeb && !_isMobile) + ? 1 + : (_originalHeight![currentInstancePageIndex - 1] / + _pdfPages[currentInstancePageIndex]!.pageSize.height); + final double searchOffset = + _pdfPages[currentInstancePageIndex]!.pageOffset + + (topOffset / heightPercentage); + if (_pdfViewerController.zoomLevel > 1) { + final leftOffset = (_textCollection![_pdfViewerController ._pdfTextSearchResult.currentInstanceIndex - 1] - .pageIndex + - 1); + .bounds + .left / + heightPercentage); + _pdfContainerKey.currentState + ?.jumpOnZoomedDocument(leftOffset, searchOffset, _viewportWidth); + _isManualScrolled = false; + } else if (_scrollController.offset > searchOffset || + (_scrollController.offset - searchOffset).abs() > + _scrollController.position.viewportDimension) { + _pdfViewerController.jumpTo(yOffset: searchOffset); + _isManualScrolled = false; + } + } - final double topOffset = _textCollection[ - _pdfViewerController._pdfTextSearchResult.currentInstanceIndex - - 1] - .bounds - .top; - - final double heightPercentage = - _originalHeight[currentInstancePageIndex - 1] / - _pdfPages[currentInstancePageIndex].pageSize.height; - - if (_deviceOrientation == Orientation.landscape) { - if (_pdfPages[currentInstancePageIndex].pageOffset + - (topOffset / heightPercentage) <= - _scrollController.offset || - _pdfPages[currentInstancePageIndex].pageOffset + - (topOffset / heightPercentage) >= - _scrollController.offset + - _scrollController.position.viewportDimension) { - _pdfViewerController.jumpTo( - yOffset: (_pdfPages[currentInstancePageIndex].pageOffset + - (topOffset / heightPercentage))); - } - } else { - if (currentInstancePageIndex != _pdfViewerController.pageNumber) { - _pdfViewerController.jumpToPage(currentInstancePageIndex); - } - if (_pdfPages[currentInstancePageIndex].pageOffset + - (topOffset / heightPercentage) <= - _scrollController.offset || - _pdfPages[currentInstancePageIndex].pageOffset + - (topOffset / heightPercentage) >= - _scrollController.offset + - _scrollController.position.viewportDimension) { - _pdfViewerController.jumpTo( - yOffset: (_pdfPages[currentInstancePageIndex].pageOffset + - (topOffset / heightPercentage))); - } + /// Call the method according to property name. + void _handleTextSearch({String? property}) { + if (_pdfViewerController._pdfTextSearchResult.hasResult) { + if (property == 'nextInstance') { + _pdfViewerController + ._pdfTextSearchResult._currentOccurrenceIndex = _pdfViewerController + ._pdfTextSearchResult.currentInstanceIndex < + _pdfViewerController._pdfTextSearchResult._totalInstanceCount + ? _pdfViewerController._pdfTextSearchResult.currentInstanceIndex + 1 + : 1; + _jumpToSearchInstance(isNext: true); + } else if (property == 'previousInstance') { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = + _pdfViewerController._pdfTextSearchResult.currentInstanceIndex > 1 + ? _pdfViewerController + ._pdfTextSearchResult.currentInstanceIndex - + 1 + : _pdfViewerController._pdfTextSearchResult.totalInstanceCount; + _jumpToSearchInstance(isNext: false); + } else if (property == 'clear') { + _textCollection = null; + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 0; + _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = 0; + _pdfViewerController._pdfTextSearchResult._updateResult(false); + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.focusNode + .requestFocus(); + return; } - } else if (property == 'clear') { - _textCollection = null; - _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 0; - _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = 0; - _pdfViewerController._pdfTextSearchResult._updateResult(false); } } } @@ -1742,7 +1796,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// ``` class PdfViewerController extends _ValueChangeNotifier { /// Zoom level - double _zoomLevel; + double _zoomLevel = 1; /// Current page number int _currentPageNumber = 0; @@ -1751,10 +1805,10 @@ class PdfViewerController extends _ValueChangeNotifier { int _totalPages = 0; /// Searched text value - String _searchText; + String _searchText = ''; /// option for text search - TextSearchOption _textSearchOption; + TextSearchOption? _textSearchOption; /// Sets the current page number. set _pageNumber(int num) { @@ -1769,23 +1823,32 @@ class PdfViewerController extends _ValueChangeNotifier { } /// PdfBookmark instance - PdfBookmark _pdfBookmark; + PdfBookmark? _pdfBookmark; /// PdfTextSearchResult instance final PdfTextSearchResult _pdfTextSearchResult = PdfTextSearchResult(); /// Vertical Offset - double _verticalOffset; + double _verticalOffset = 0.0; /// Horizontal Offset - double _horizontalOffset; + double _horizontalOffset = 0.0; /// Represents different page navigation option - Pagination _pageNavigator; + Pagination? _pageNavigator; /// Returns `true`, if the text selection is cleared properly. bool _clearTextSelection = false; + /// Scroll X Position + double _scrollPositionX = 0.0; + + /// Scroll Y Position + double _scrollPositionY = 0.0; + + /// The current scroll offset of the SfPdfViewer widget. + Offset get scrollOffset => Offset(_scrollPositionX, _scrollPositionY); + /// Zoom level of a document in the [SfPdfViewer]. /// /// Zoom level value can be set between 1.0 to 3.0. The maximum allowed zoom @@ -2111,9 +2174,9 @@ class PdfViewerController extends _ValueChangeNotifier { /// } ///} /// ``` - void jumpTo({double xOffset, double yOffset}) { - _horizontalOffset = xOffset ?? 0.0; - _verticalOffset = yOffset ?? 0.0; + void jumpTo({double xOffset = 0.0, double yOffset = 0.0}) { + _horizontalOffset = xOffset; + _verticalOffset = yOffset; notifyPropertyChangedListeners(property: 'jumpTo'); } @@ -2168,8 +2231,7 @@ class PdfViewerController extends _ValueChangeNotifier { ///} /// ``` void jumpToPage(int pageNumber) { - _pageNavigator = - Pagination(index: pageNumber, option: Navigation.jumpToPage); + _pageNavigator = Pagination(Navigation.jumpToPage, index: pageNumber); notifyPropertyChangedListeners(property: 'pageNavigate'); } @@ -2230,7 +2292,7 @@ class PdfViewerController extends _ValueChangeNotifier { ///} /// ``` void previousPage() { - _pageNavigator = Pagination(option: Navigation.previousPage); + _pageNavigator = Pagination(Navigation.previousPage); notifyPropertyChangedListeners(property: 'pageNavigate'); } @@ -2291,7 +2353,7 @@ class PdfViewerController extends _ValueChangeNotifier { ///} /// ``` void nextPage() { - _pageNavigator = Pagination(option: Navigation.nextPage); + _pageNavigator = Pagination(Navigation.nextPage); notifyPropertyChangedListeners(property: 'pageNavigate'); } @@ -2352,7 +2414,7 @@ class PdfViewerController extends _ValueChangeNotifier { ///} /// ``` void firstPage() { - _pageNavigator = Pagination(option: Navigation.firstPage); + _pageNavigator = Pagination(Navigation.firstPage); notifyPropertyChangedListeners(property: 'pageNavigate'); } @@ -2414,7 +2476,7 @@ class PdfViewerController extends _ValueChangeNotifier { ///} /// ``` void lastPage() { - _pageNavigator = Pagination(option: Navigation.lastPage); + _pageNavigator = Pagination(Navigation.lastPage); notifyPropertyChangedListeners(property: 'pageNavigate'); } @@ -2545,7 +2607,7 @@ class PdfViewerController extends _ValueChangeNotifier { ///} ///''' Future searchText(String searchText, - {TextSearchOption searchOption}) async { + {TextSearchOption? searchOption}) async { _searchText = searchText; _textSearchOption = searchOption; notifyPropertyChangedListeners(property: 'searchText'); @@ -2565,6 +2627,7 @@ class PdfViewerController extends _ValueChangeNotifier { _zoomLevel = 1.0; _currentPageNumber = 0; _totalPages = 0; + _pageNavigator = null; notifyPropertyChangedListeners(); } } @@ -2643,7 +2706,7 @@ class PdfTextSearchResult extends _ValueChangeNotifier { /// _ValueChangeNotifier class listener invoked whenever PdfViewerController property changed. class _ValueChangeNotifier { - _PdfControllerListener listener; + late _PdfControllerListener listener; final ObserverList<_PdfControllerListener> _listeners = ObserverList<_PdfControllerListener>(); @@ -2656,7 +2719,7 @@ class _ValueChangeNotifier { } @protected - void notifyPropertyChangedListeners({String property}) { + void notifyPropertyChangedListeners({String? property}) { for (listener in _listeners) { listener(property: property); } diff --git a/packages/syncfusion_flutter_pdfviewer/pubspec.yaml b/packages/syncfusion_flutter_pdfviewer/pubspec.yaml index 03c34ac0e..00c903f6d 100644 --- a/packages/syncfusion_flutter_pdfviewer/pubspec.yaml +++ b/packages/syncfusion_flutter_pdfviewer/pubspec.yaml @@ -1,22 +1,10 @@ name: syncfusion_flutter_pdfviewer -description: Syncfusion Flutter PDF Viewer is used to display a PDF document seamlessly and efficiently. -version: 18.3.35-beta -repository: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdfviewer +description: Flutter PDF Viewer library is used to display a PDF document seamlessly and efficiently. +version: 19.1.54-beta +homepage: https://gitlab.syncfusion.com/essential-studio/flutter-pdfviewer environment: - sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.12.0 <2.0.0" - -dependencies: - flutter: - sdk: flutter - vector_math: ">=1.1.0 <3.0.0" - async: ^2.4.2 - path_provider: ^1.6.18 - syncfusion_flutter_core: - path: ../syncfusion_flutter_core - syncfusion_flutter_pdf: - path: ../syncfusion_flutter_pdf + sdk: '>=2.12.0 <3.0.0' flutter: # This section identifies this Flutter project as a plugin project. @@ -29,4 +17,24 @@ flutter: package: com.syncfusion.flutter.pdfviewer pluginClass: SyncfusionFlutterPdfViewerPlugin ios: - pluginClass: SyncfusionFlutterPdfViewerPlugin \ No newline at end of file + pluginClass: SyncfusionFlutterPdfViewerPlugin + web: + default_package: syncfusion_flutter_pdfviewer_web + +dependencies: + flutter: + sdk: flutter + vector_math: ^2.1.0 + async: ^2.5.0 + http: ^0.13.0 + + syncfusion_flutter_pdfviewer_platform_interface: + path: ../syncfusion_flutter_pdfviewer_platform_interface + + syncfusion_flutter_pdfviewer_web: + path: ../syncfusion_flutter_pdfviewer_web + + syncfusion_flutter_core: + path: ../syncfusion_flutter_core + syncfusion_flutter_pdf: + path: ../syncfusion_flutter_pdf diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/CHANGELOG.md b/packages/syncfusion_flutter_pdfviewer_platform_interface/CHANGELOG.md new file mode 100644 index 000000000..43d0fcd40 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/CHANGELOG.md @@ -0,0 +1,3 @@ +## [unreleased] + +* Initial release. diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/LICENSE b/packages/syncfusion_flutter_pdfviewer_platform_interface/LICENSE new file mode 100644 index 000000000..f330d1667 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/LICENSE @@ -0,0 +1,12 @@ +Syncfusion License + +Syncfusion Flutter PDF Viewer package is available under the Syncfusion Essential Studio program, and can be licensed either under the Syncfusion Community License Program or the Syncfusion commercial license. + +To be qualified for the Syncfusion Community License Program you must have a gross revenue of less than one (1) million U.S. dollars ($1,000,000.00 USD) per year and have less than five (5) developers in your organization, and agree to be bound by Syncfusion’s terms and conditions. + +Customers who do not qualify for the community license can contact sales@syncfusion.com for commercial licensing options. + +Under no circumstances can you use this product without (1) either a Community License or a commercial license and (2) without agreeing and abiding by Syncfusion’s license containing all terms and conditions. + +The Syncfusion license that contains the terms and conditions can be found at +https://www.syncfusion.com/content/downloads/syncfusion_license.pdf \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/README.md b/packages/syncfusion_flutter_pdfviewer_platform_interface/README.md new file mode 100644 index 000000000..3fec19c37 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/README.md @@ -0,0 +1,15 @@ +# Flutter PDF Viewer Platform Interface library + +A common platform interface package for the [Syncfusion Flutter PDF Viewer](https://pub.dev/packages/syncfusion_flutter_pdfviewer) plugin. + +This interface allows platform-specific implementations of the `syncfusion_flutter_pdfviewer` +plugin, as well as the plugin itself, to ensure they are supporting the +same interface. + +# Usage + +To implement a new platform-specific implementation of `syncfusion_flutter_pdfviewer`, extend +`PdfViewerPlatform` with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`PdfViewerPlatform` by calling +`PdfViewerPlatform.instance = SyncfusionFlutterPdfViewerPlugin()`. \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/analysis_options.yaml b/packages/syncfusion_flutter_pdfviewer_platform_interface/analysis_options.yaml new file mode 100644 index 000000000..a8c45a74c --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/analysis_options.yaml @@ -0,0 +1,6 @@ +analyzer: + errors: + lines_longer_than_80_chars: ignore + include_file_not_found: ignore + uri_does_not_exist: ignore + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/pdfviewer_platform_interface.dart b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/pdfviewer_platform_interface.dart new file mode 100644 index 000000000..81a29938d --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/pdfviewer_platform_interface.dart @@ -0,0 +1 @@ +export 'src/pdfviewer_platform_interface.dart'; diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart new file mode 100644 index 000000000..4e75f35af --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart @@ -0,0 +1,40 @@ +import 'dart:async'; +import 'dart:typed_data'; +import 'package:flutter/services.dart'; +import 'package:syncfusion_flutter_pdfviewer_platform_interface/pdfviewer_platform_interface.dart'; + +class MethodChannelPdfViewer extends PdfViewerPlatform { + final MethodChannel _channel = MethodChannel('syncfusion_flutter_pdfviewer'); + + /// Initializes the PDF renderer instance in respective platform by loading the PDF from the provided byte information. + /// If success, returns page count else returns error message from respective platform + @override + Future initializePdfRenderer(Uint8List documentBytes) async { + return _channel.invokeMethod('initializePdfRenderer', documentBytes); + } + + /// Gets the height of all pages in the document. + @override + Future getPagesHeight() async { + return _channel.invokeMethod('getPagesHeight'); + } + + /// Gets the width of all pages in the document. + @override + Future getPagesWidth() async { + return _channel.invokeMethod('getPagesWidth'); + } + + /// Gets the image's bytes information of the specified page. + @override + Future getImage(int pageNumber) async { + return _channel.invokeMethod( + 'getImage', {'index': pageNumber}); + } + + /// Closes the PDF document. + @override + Future closeDocument() async { + return _channel.invokeMethod('closeDocument'); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart new file mode 100644 index 000000000..981095ba0 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart @@ -0,0 +1,61 @@ +library syncfusion_flutter_pdfviewer_platform_interface; + +import 'dart:async'; +import 'dart:typed_data'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'package:syncfusion_flutter_pdfviewer_platform_interface/src/method_channel_pdfviewer.dart'; + +/// The interface that implementations of syncfusion_flutter_pdfviewer must implement. +/// +/// Platform implementations should extend this class rather than implement it as `syncfusion_flutter_pdfviewer` +/// does not consider newly added methods to be breaking changes. Extending this class +/// (using `extends`) ensures that the subclass will get the default implementation, while +/// platform implementations that `implements` this interface will be broken by newly added +/// [PdfViewerPlatform] methods +abstract class PdfViewerPlatform extends PlatformInterface { + /// Constructs a PdfViewerPlatform. + PdfViewerPlatform() : super(token: _token); + static PdfViewerPlatform _instance = MethodChannelPdfViewer(); + + static final Object _token = Object(); + + /// The default instance of [PdfViewerPlatform] to use. + /// + /// Defaults to [MethodChannelPdfViewer] + static PdfViewerPlatform get instance => _instance; + + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [PdfViewerPlatform] when they register themselves. + static set instance(PdfViewerPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + /// Initializes the PDF renderer instance in respective platform by loading the PDF from the specified path. + /// + /// If success, returns page count else returns error message from respective platform + Future initializePdfRenderer(Uint8List documentBytes) async { + throw UnimplementedError( + 'initializePdfRenderer() has not been implemented.'); + } + + /// Gets the height of all pages in the document. + Future getPagesHeight() async { + throw UnimplementedError('getPagesHeight() has not been implemented.'); + } + + /// Gets the width of all pages in the document. + Future getPagesWidth() async { + throw UnimplementedError('getPagesWidth() has not been implemented.'); + } + + /// Gets the image's bytes information of the specified page. + Future getImage(int pageNumber) async { + throw UnimplementedError('getImage() has not been implemented.'); + } + + /// Closes the PDF document. + Future closeDocument() async { + throw UnimplementedError('closeDocument() has not been implemented.'); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer_platform_interface/pubspec.yaml b/packages/syncfusion_flutter_pdfviewer_platform_interface/pubspec.yaml new file mode 100644 index 000000000..e1c1d664d --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_platform_interface/pubspec.yaml @@ -0,0 +1,12 @@ +name: syncfusion_flutter_pdfviewer_platform_interface +description: A common platform interface for the Flutter PDF Viewer library that lets you view the PDF documents seamlessly and efficiently. +version: 19.1.54-beta +homepage: https://gitlab.syncfusion.com/essential-studio/flutter-pdfviewer + +environment: + sdk: '>=2.12.0 <3.0.0' + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.0 diff --git a/packages/syncfusion_flutter_pdfviewer_web/CHANGELOG.md b/packages/syncfusion_flutter_pdfviewer_web/CHANGELOG.md new file mode 100644 index 000000000..43d0fcd40 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_web/CHANGELOG.md @@ -0,0 +1,3 @@ +## [unreleased] + +* Initial release. diff --git a/packages/syncfusion_flutter_pdfviewer_web/LICENSE b/packages/syncfusion_flutter_pdfviewer_web/LICENSE new file mode 100644 index 000000000..f330d1667 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_web/LICENSE @@ -0,0 +1,12 @@ +Syncfusion License + +Syncfusion Flutter PDF Viewer package is available under the Syncfusion Essential Studio program, and can be licensed either under the Syncfusion Community License Program or the Syncfusion commercial license. + +To be qualified for the Syncfusion Community License Program you must have a gross revenue of less than one (1) million U.S. dollars ($1,000,000.00 USD) per year and have less than five (5) developers in your organization, and agree to be bound by Syncfusion’s terms and conditions. + +Customers who do not qualify for the community license can contact sales@syncfusion.com for commercial licensing options. + +Under no circumstances can you use this product without (1) either a Community License or a commercial license and (2) without agreeing and abiding by Syncfusion’s license containing all terms and conditions. + +The Syncfusion license that contains the terms and conditions can be found at +https://www.syncfusion.com/content/downloads/syncfusion_license.pdf \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer_web/README.md b/packages/syncfusion_flutter_pdfviewer_web/README.md new file mode 100644 index 000000000..92b60de78 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_web/README.md @@ -0,0 +1,31 @@ +# Flutter PDF Viewer Web library + +The web implementation of [Syncfusion Flutter PDF Viewer](https://pub.dev/packages/syncfusion_flutter_pdfviewer) plugin. + +## Usage + +### Import the package + +This package is an endorsed implementation of `syncfusion_flutter_pdfviewer` for the web platform since version `19.1.0-beta`, so it gets automatically added to your dependencies when you depend on package `syncfusion_flutter_pdfviewer`. + +```yaml +... +dependencies: + ... + syncfusion_flutter_pdfviewer: ^19.1.0-beta + ... +... +``` + +### Web integration + +We have used [PdfJs](https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.4.456/pdf.min.js) for rendering the PDF page as an image on the web platform, so the script file must be referred to in your `web/index.html` file. + +On your `web/index.html` file, add the following `script` tags, somewhere in the `body` of the document: + +```html + + +``` \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer_web/analysis_options.yaml b/packages/syncfusion_flutter_pdfviewer_web/analysis_options.yaml new file mode 100644 index 000000000..a8c45a74c --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_web/analysis_options.yaml @@ -0,0 +1,6 @@ +analyzer: + errors: + lines_longer_than_80_chars: ignore + include_file_not_found: ignore + uri_does_not_exist: ignore + invalid_dependency: ignore \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer_web/lib/pdfviewer_web.dart b/packages/syncfusion_flutter_pdfviewer_web/lib/pdfviewer_web.dart new file mode 100644 index 000000000..7436bfba1 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_web/lib/pdfviewer_web.dart @@ -0,0 +1,117 @@ +import 'dart:async'; +import 'dart:html' as html; +import 'dart:io'; +import 'dart:js_util'; +import 'dart:js' as js; +import 'dart:typed_data'; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'package:syncfusion_flutter_pdfviewer_platform_interface/pdfviewer_platform_interface.dart'; +import 'package:syncfusion_flutter_pdfviewer_web/src/pdfjs.dart'; + +class SyncfusionFlutterPdfViewerPlugin extends PdfViewerPlatform { + final Settings _settings = Settings()..scale = 1.0; + int? _pagesCount; + Int32List? _pagesHeight, _pagesWidth; + PdfJsDoc? _pdfJsDoc; + + /// Registers this class as the default instance of [PdfViewerPlatform]. + static void registerWith(Registrar registrar) { + PdfViewerPlatform.instance = SyncfusionFlutterPdfViewerPlugin(); + } + + /// Initializes the PDF renderer instance in respective platform by loading the PDF from the specified path. + @override + Future initializePdfRenderer(Uint8List documentBytes) async { + Settings documentData = Settings()..data = documentBytes; + final documentLoader = PdfJs.getDocument(documentData); + _pdfJsDoc = await promiseToFuture(documentLoader.promise); + _pagesCount = _pdfJsDoc?.numPages ?? 0; + _pagesHeight = _pagesWidth = null; + return _pagesCount.toString(); + } + + /// Gets the height of all pages in the document. + @override + Future getPagesHeight() async { + if (_pagesHeight == null) { + _pagesHeight = new Int32List(_pagesCount!); + _pagesWidth = new Int32List(_pagesCount!); + for (int pageNumber = 1; pageNumber <= _pagesCount!; pageNumber++) { + PdfJsPage page = + await promiseToFuture(_pdfJsDoc!.getPage(pageNumber)); + PdfJsViewport viewport = page.getViewport(_settings); + _pagesHeight![pageNumber - 1] = viewport.height.toInt(); + _pagesWidth![pageNumber - 1] = viewport.width.toInt(); + } + } + return _pagesHeight; + } + + /// Gets the width of all pages in the document. + @override + Future getPagesWidth() async { + if (_pagesWidth == null) { + _pagesHeight = new Int32List(_pagesCount!); + _pagesWidth = new Int32List(_pagesCount!); + for (int pageNumber = 1; pageNumber <= _pagesCount!; pageNumber++) { + PdfJsPage page = + await promiseToFuture(_pdfJsDoc!.getPage(pageNumber)); + PdfJsViewport viewport = page.getViewport(_settings); + _pagesHeight![pageNumber - 1] = viewport.height.toInt(); + _pagesWidth![pageNumber - 1] = viewport.width.toInt(); + } + } + return _pagesWidth; + } + + /// Gets the image's bytes information of the specified page. + @override + Future getImage(int pageNumber) async { + if (_pdfJsDoc != null && _pagesHeight != null && _pagesWidth != null) { + PdfJsPage page = + await promiseToFuture(_pdfJsDoc!.getPage(pageNumber)); + PdfJsViewport viewport = page.getViewport(_settings); + return renderPage(page, viewport); + } + return Uint8List.fromList([0]); + } + + /// Closes the PDF document. + @override + Future closeDocument() async { + _pdfJsDoc = null; + } + + /// Renders the page into a canvas and return image's byte information. + Future renderPage(PdfJsPage page, PdfJsViewport viewport) async { + final html.CanvasElement htmlCanvas = + js.context['document'].createElement('canvas'); + final Object? context = htmlCanvas.getContext('2d'); + final _viewport = page.getViewport(_settings); + viewport = page.getViewport( + Settings()..scale = ((viewport.width).toInt() * 2) / _viewport.width); + + htmlCanvas + ..height = viewport.height.toInt() + ..width = viewport.width.toInt(); + final renderSettings = Settings() + ..canvasContext = (context as html.CanvasRenderingContext2D) + ..viewport = viewport; + await promiseToFuture(page.render(renderSettings).promise); + + // Renders the page as a PNG image and retrieve its byte information. + final completer = Completer(); + final blob = await htmlCanvas.toBlob(); + final bytesBuilder = BytesBuilder(); + final fileReader = html.FileReader()..readAsArrayBuffer(blob); + fileReader.onLoadEnd.listen( + (html.ProgressEvent e) { + bytesBuilder.add(fileReader.result as List); + completer.complete(); + }, + ); + await completer.future; + return bytesBuilder.toBytes(); + } +} diff --git a/packages/syncfusion_flutter_pdfviewer_web/lib/src/pdfjs.dart b/packages/syncfusion_flutter_pdfviewer_web/lib/src/pdfjs.dart new file mode 100644 index 000000000..1146c451a --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_web/lib/src/pdfjs.dart @@ -0,0 +1,58 @@ +@JS() +library pdf.js; + +import 'dart:html'; +import 'dart:typed_data'; + +import 'package:js/js.dart'; + +/// Represents the classes that are equivalent to the PDFJS classes, +/// to retrieve the information from the same. +@JS('pdfjsLib') +class PdfJs { + external static PdfJsDocLoader getDocument(Settings data); +} + +@anonymous +@JS() +class Settings { + external set data(Uint8List value); + external set scale(double value); + external set canvasContext(CanvasRenderingContext2D value); + external set viewport(PdfJsViewport value); +} + +@anonymous +@JS() +class PdfJsDocLoader { + external Future get promise; +} + +@anonymous +@JS() +class PdfJsDoc { + external Future getPage(int num); + external int get numPages; +} + +@anonymous +@JS() +class PdfJsPage { + external PdfJsViewport getViewport(Settings data); + external PdfJsRender render(Settings data); + external int get pageNumber; + external List get view; +} + +@anonymous +@JS() +class PdfJsViewport { + external num get width; + external num get height; +} + +@anonymous +@JS() +class PdfJsRender { + external Future get promise; +} diff --git a/packages/syncfusion_flutter_pdfviewer_web/pubspec.yaml b/packages/syncfusion_flutter_pdfviewer_web/pubspec.yaml new file mode 100644 index 000000000..da4282fb6 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer_web/pubspec.yaml @@ -0,0 +1,23 @@ +name: syncfusion_flutter_pdfviewer_web +description: Web platform implementation of the Flutter PDF Viewer library that lets you view the PDF documents seamlessly and efficiently. +version: 19.1.54-beta +homepage: https://gitlab.syncfusion.com/essential-studio/flutter-pdfviewer + +environment: + sdk: '>=2.12.0 <3.0.0' + +dependencies: + flutter: + sdk: flutter + flutter_web_plugins: + sdk: flutter + js: ^0.6.3 + syncfusion_flutter_pdfviewer_platform_interface: + path: ../syncfusion_flutter_pdfviewer_platform_interface + +flutter: + plugin: + platforms: + web: + pluginClass: SyncfusionFlutterPdfViewerPlugin + fileName: pdfviewer_web.dart diff --git a/packages/syncfusion_flutter_signaturepad/README.md b/packages/syncfusion_flutter_signaturepad/README.md index a7d262218..ba1eab76e 100644 --- a/packages/syncfusion_flutter_signaturepad/README.md +++ b/packages/syncfusion_flutter_signaturepad/README.md @@ -8,7 +8,7 @@ Syncfusion Flutter SignaturePad library is written in Dart for capturing smooth This library is used to capture a signature through drawing gestures. You can use your finger, pen, or mouse on a tablet, touchscreen, etc., to draw your own signature in this SignaturePad widget. The widget also allows you to save a signature as an image, which can be further synchronized with your documents that need the signature. -**Disclaimer** : This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or Syncfusion Community License. For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. +**Disclaimer** : This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. **Note** : Our packages are now compatible with Flutter for web. However, this will be in beta until Flutter for web becomes stable. diff --git a/packages/syncfusion_flutter_signaturepad/analysis_options.yaml b/packages/syncfusion_flutter_signaturepad/analysis_options.yaml index 2a22b53a9..91f995863 100644 --- a/packages/syncfusion_flutter_signaturepad/analysis_options.yaml +++ b/packages/syncfusion_flutter_signaturepad/analysis_options.yaml @@ -4,3 +4,4 @@ analyzer: errors: include_file_not_found: ignore lines_longer_than_80_chars: ignore + invalid_dependency: ignore diff --git a/packages/syncfusion_flutter_signaturepad/example/analysis_options.yaml b/packages/syncfusion_flutter_signaturepad/example/analysis_options.yaml new file mode 100644 index 000000000..b2ddcf3a7 --- /dev/null +++ b/packages/syncfusion_flutter_signaturepad/example/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + errors: + invalid_dependency: ignore diff --git a/packages/syncfusion_flutter_signaturepad/example/lib/main.dart b/packages/syncfusion_flutter_signaturepad/example/lib/main.dart index 71f354482..c6f1c699a 100644 --- a/packages/syncfusion_flutter_signaturepad/example/lib/main.dart +++ b/packages/syncfusion_flutter_signaturepad/example/lib/main.dart @@ -13,21 +13,20 @@ class SignaturePadApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'SfSignaturePad Demo', - home: _MyHomePage(title: 'SfSignaturePad Demo'), + home: _MyHomePage(), ); } } class _MyHomePage extends StatefulWidget { - _MyHomePage({Key key, this.title}) : super(key: key); + _MyHomePage({Key? key}) : super(key: key); - final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<_MyHomePage> { - GlobalKey signatureGlobalKey = GlobalKey(); + final GlobalKey signatureGlobalKey = GlobalKey(); @override void initState() { @@ -35,29 +34,28 @@ class _MyHomePageState extends State<_MyHomePage> { } void _handleClearButtonPressed() { - signatureGlobalKey.currentState.clear(); + signatureGlobalKey.currentState!.clear(); } void _handleSaveButtonPressed() async { - final data = await signatureGlobalKey.currentState.toImage(pixelRatio: 3.0); + final data = + await signatureGlobalKey.currentState!.toImage(pixelRatio: 3.0); final bytes = await data.toByteData(format: ui.ImageByteFormat.png); - if (data != null) { - await Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return Scaffold( - appBar: AppBar(), - body: Center( - child: Container( - color: Colors.grey[300], - child: Image.memory(bytes.buffer.asUint8List()), - ), + await Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: Center( + child: Container( + color: Colors.grey[300], + child: Image.memory(bytes!.buffer.asUint8List()), ), - ); - }, - ), - ); - } + ), + ); + }, + ), + ); } @override @@ -78,11 +76,11 @@ class _MyHomePageState extends State<_MyHomePage> { BoxDecoration(border: Border.all(color: Colors.grey)))), SizedBox(height: 10), Row(children: [ - FlatButton( + TextButton( child: Text('ToImage'), onPressed: _handleSaveButtonPressed, ), - FlatButton( + TextButton( child: Text('Clear'), onPressed: _handleClearButtonPressed, ) diff --git a/packages/syncfusion_flutter_signaturepad/example/pubspec.yaml b/packages/syncfusion_flutter_signaturepad/example/pubspec.yaml index e6da8070f..c97e4126e 100644 --- a/packages/syncfusion_flutter_signaturepad/example/pubspec.yaml +++ b/packages/syncfusion_flutter_signaturepad/example/pubspec.yaml @@ -4,7 +4,7 @@ description: This project demonstrates how to use Syncfusion Flutter SignaturePa version: 1.0.0+1 environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: diff --git a/packages/syncfusion_flutter_signaturepad/lib/signaturepad.dart b/packages/syncfusion_flutter_signaturepad/lib/signaturepad.dart index 2064f3851..a58a1c6f8 100644 --- a/packages/syncfusion_flutter_signaturepad/lib/signaturepad.dart +++ b/packages/syncfusion_flutter_signaturepad/lib/signaturepad.dart @@ -140,17 +140,14 @@ class SfSignaturePad extends StatefulWidget { /// }); /// ``` const SfSignaturePad( - {key, + {Key? key, this.minimumStrokeWidth = _kMinimumStrokeWidth, this.maximumStrokeWidth = _kMaximumStrokeWidth, this.backgroundColor, this.strokeColor, this.onSignEnd, this.onSignStart}) - : assert(minimumStrokeWidth != null), - assert(maximumStrokeWidth != null), - assert(minimumStrokeWidth <= maximumStrokeWidth), - super(key: key); + : super(key: key); /// The minimum width of the signature stroke. /// @@ -215,7 +212,7 @@ class SfSignaturePad extends StatefulWidget { /// ); /// ``` /// - final Color backgroundColor; + final Color? backgroundColor; /// Color applied to the signature stroke. /// @@ -233,7 +230,7 @@ class SfSignaturePad extends StatefulWidget { /// ); /// ``` /// - final Color strokeColor; + final Color? strokeColor; /// Called when the user starts signing on [SfSignaturePad]. /// @@ -246,7 +243,7 @@ class SfSignaturePad extends StatefulWidget { /// }, /// ); /// ``` - final VoidCallback onSignStart; + final VoidCallback? onSignStart; /// Called when the user completes signing on [SfSignaturePad]. /// @@ -260,7 +257,7 @@ class SfSignaturePad extends StatefulWidget { /// }, /// ); /// ``` - final VoidCallback onSignEnd; + final VoidCallback? onSignEnd; @override SfSignaturePadState createState() => SfSignaturePadState(); @@ -299,11 +296,10 @@ class SfSignaturePadState extends State { /// * [renderToContext2D], renders the signature to a HTML canvas. Future toImage({double pixelRatio = 1.0}) { - if (!kIsWeb) { - final RenderSignaturePad signatureRenderBox = context.findRenderObject(); - return signatureRenderBox.toImage(pixelRatio: pixelRatio); - } - return null; + final RenderObject? signatureRenderBox = context.findRenderObject(); + // ignore: avoid_as + return (signatureRenderBox as RenderSignaturePad) + .toImage(pixelRatio: pixelRatio); } /// Clears all the signature strokes in the [SfSignaturePad]. @@ -329,8 +325,11 @@ class SfSignaturePadState extends State { /// _signaturePadKey.currentState.clear(); /// ``` void clear() { - final RenderSignaturePad signatureRenderBox = context.findRenderObject(); - signatureRenderBox.clear(); + final RenderObject? signatureRenderBox = context.findRenderObject(); + + if (signatureRenderBox is RenderSignaturePad) { + signatureRenderBox.clear(); + } } /// Renders the signature to a HTML canvas. @@ -370,16 +369,15 @@ class SfSignaturePadState extends State { /// Uint8List imageData = await completer.future; /// ``` void renderToContext2D(dynamic context2D) { - final RenderSignaturePad signatureRenderBox = context.findRenderObject(); - return signatureRenderBox.renderToContext2D(context2D); + final RenderObject? signatureRenderBox = context.findRenderObject(); + + if (signatureRenderBox is RenderSignaturePad) { + signatureRenderBox.renderToContext2D(context2D); + } } @override Widget build(BuildContext context) { - final double _minimumStrokeWidth = - widget.minimumStrokeWidth ?? _kMinimumStrokeWidth; - final double _maximumStrokeWidth = - widget.maximumStrokeWidth ?? _kMaximumStrokeWidth; final ThemeData theme = Theme.of(context); final bool isDarkTheme = theme.brightness == Brightness.dark; final Color _backgroundColor = widget.backgroundColor ?? Colors.transparent; @@ -387,23 +385,22 @@ class SfSignaturePadState extends State { widget.strokeColor ?? (isDarkTheme ? Colors.white : Colors.black); return _SfSignaturePadRenderObjectWidget( - minimumStrokeWidth: _minimumStrokeWidth, - maximumStrokeWidth: _maximumStrokeWidth, - backgroundColor: _backgroundColor, - strokeColor: _strokeColor, - onSignStart: widget.onSignStart, - onSignEnd: widget.onSignEnd, - ); + minimumStrokeWidth: widget.minimumStrokeWidth, + maximumStrokeWidth: widget.maximumStrokeWidth, + backgroundColor: _backgroundColor, + strokeColor: _strokeColor, + onSignStart: widget.onSignStart, + onSignEnd: widget.onSignEnd); } } /// A super class to create or update the RenderSignaturePad class. class _SfSignaturePadRenderObjectWidget extends LeafRenderObjectWidget { const _SfSignaturePadRenderObjectWidget({ - this.minimumStrokeWidth, - this.maximumStrokeWidth, - this.backgroundColor, - this.strokeColor, + required this.minimumStrokeWidth, + required this.maximumStrokeWidth, + required this.backgroundColor, + required this.strokeColor, this.onSignStart, this.onSignEnd, }); @@ -412,8 +409,8 @@ class _SfSignaturePadRenderObjectWidget extends LeafRenderObjectWidget { final double maximumStrokeWidth; final Color backgroundColor; final Color strokeColor; - final VoidCallback onSignStart; - final VoidCallback onSignEnd; + final VoidCallback? onSignStart; + final VoidCallback? onSignEnd; @override RenderObject createRenderObject(BuildContext context) { @@ -444,12 +441,12 @@ class _SfSignaturePadRenderObjectWidget extends LeafRenderObjectWidget { class RenderSignaturePad extends RenderBox { /// Creates a new instance of [RenderSignaturePad]. RenderSignaturePad( - {double minimumStrokeWidth, - double maximumStrokeWidth, - Color backgroundColor, - Color strokeColor, - VoidCallback onSignStart, - VoidCallback onSignEnd}) + {required double minimumStrokeWidth, + required double maximumStrokeWidth, + required Color backgroundColor, + required Color strokeColor, + VoidCallback? onSignStart, + VoidCallback? onSignEnd}) : _minimumStrokeWidth = minimumStrokeWidth, _maximumStrokeWidth = maximumStrokeWidth, _backgroundColor = backgroundColor, @@ -499,23 +496,29 @@ class RenderSignaturePad extends RenderBox { } final GestureArenaTeam _gestureArenaTeam; - TapGestureRecognizer _tapGestureRecognizer; - VerticalDragGestureRecognizer _verticalDragGestureRecognizer; - HorizontalDragGestureRecognizer _horizontalDragGestureRecognizer; - PanGestureRecognizer _panGestureRecognizer; + int _touchGestureFinder = 0; - Paint _paintStrokeStyle; - Paint _paintBackgroundStyle; - List<_TouchPoint> _lastPoints; - List> _data; - List _dotPoints; - List<_CachePoint> _bezierPoints; - double _lastVelocity; - double _lastWidth; - final double _velocityFilterWeight = 0.2; - List _pathCollection; - Path _currentPath; bool _restrictBezierPathCalculation = false; + double _velocityFilterWeight = 0.2; + + late TapGestureRecognizer _tapGestureRecognizer; + late VerticalDragGestureRecognizer _verticalDragGestureRecognizer; + late HorizontalDragGestureRecognizer _horizontalDragGestureRecognizer; + late PanGestureRecognizer _panGestureRecognizer; + + late Paint _paintStrokeStyle; + late Paint _paintBackgroundStyle; + + late List<_TouchPoint> _lastPoints; + late List> _data; + late List _dotPoints; + late List<_CachePoint> _bezierPoints; + late List _pathCollection; + + late double _lastVelocity; + late double _lastWidth; + + late Path _currentPath; double _minimumStrokeWidth; @@ -620,32 +623,32 @@ class RenderSignaturePad extends RenderBox { markNeedsPaint(); } - VoidCallback _onSignEnd; + VoidCallback? _onSignEnd; /// Gets the [onSignEnd] callback. /// /// Called when the user completes the signature in [RenderSignaturePad]. - VoidCallback get onSignEnd => _onSignEnd; + VoidCallback? get onSignEnd => _onSignEnd; /// Sets the [onSignEnd] callback. /// /// Called when the user completes the signature in [RenderSignaturePad]. - set onSignEnd(VoidCallback value) { + set onSignEnd(VoidCallback? value) { if (_onSignEnd == value) return; _onSignEnd = value; } - VoidCallback _onSignStart; + VoidCallback? _onSignStart; ///Gets the [onSignStart] callback. /// ///Called when the user starts signing on the [RenderSignaturePad]. - VoidCallback get onSignStart => _onSignStart; + VoidCallback? get onSignStart => _onSignStart; /// Sets the onSignStart callback. /// /// Called when the user starts signing on the [RenderSignaturePad] - set onSignStart(VoidCallback value) { + set onSignStart(VoidCallback? value) { if (_onSignStart == value) return; _onSignStart = value; } @@ -685,17 +688,17 @@ class RenderSignaturePad extends RenderBox { } void _handleDragEnd(DragEndDetails details) { - if (onSignEnd != null) onSignEnd(); + if (onSignEnd != null) onSignEnd!(); } void _handleTapUp(TapUpDetails details) { final _TouchPoint touchPoint = _TouchPoint(x: details.localPosition.dx, y: details.localPosition.dy); final newPointGroup = <_TouchPoint>[touchPoint]; - if (onSignStart != null) onSignStart(); + if (onSignStart != null) onSignStart!(); _data.add(newPointGroup); _drawTappedPoint(touchPoint); - if (onSignEnd != null) onSignEnd(); + if (onSignEnd != null) onSignEnd!(); } @override @@ -722,7 +725,7 @@ class RenderSignaturePad extends RenderBox { void _begin(Offset touchOffset) { final newPointGroup = <_TouchPoint>[]; - if (onSignStart != null) onSignStart(); + if (onSignStart != null) onSignStart!(); _data.add(newPointGroup); _currentPath = Path(); _pathCollection.add(_currentPath); @@ -746,7 +749,7 @@ class RenderSignaturePad extends RenderBox { : 1; if (distance > 0) { if (!_restrictBezierPathCalculation) { - final _Bezier curve = _calculateBezierPath(point); + final _Bezier? curve = _calculateBezierPath(point); if (curve != null) { _drawSignatureCurve(curve); } @@ -763,7 +766,7 @@ class RenderSignaturePad extends RenderBox { markNeedsPaint(); } - _Bezier _calculateBezierPath(_TouchPoint point) { + _Bezier? _calculateBezierPath(_TouchPoint point) { _lastPoints.add(point); if (_lastPoints.length > 2) { @@ -824,7 +827,7 @@ class RenderSignaturePad extends RenderBox { } double _velocity(_TouchPoint start, _TouchPoint end) { - double velocity = _distance(start, end) / (end.time - start.time); + double velocity = _distance(start, end) / (end.time! - start.time!); velocity = end.time == start.time ? 0 : velocity; return velocity; } @@ -837,12 +840,9 @@ class RenderSignaturePad extends RenderBox { /// /// * [renderToContext2D], renders the signature to a HTML canvas. Future toImage({double pixelRatio = 1.0}) async { - if (!kIsWeb) { - final OffsetLayer offsetLayer = layer; - return offsetLayer.toImage(Offset.zero & size, pixelRatio: pixelRatio); - } else { - return null; - } + // ignore: avoid_as + return (layer as OffsetLayer) + .toImage(Offset.zero & size, pixelRatio: pixelRatio); } /// Clears the signature strokes in [RenderSignaturePad]. @@ -879,7 +879,7 @@ class RenderSignaturePad extends RenderBox { _pathCollection.add(_currentPath); } - final _Bezier curve = _calculateBezierPath(point); + final _Bezier? curve = _calculateBezierPath(point); if (curve != null) { _drawSignatureCurve(curve); } @@ -1005,7 +1005,7 @@ class RenderSignaturePad extends RenderBox { canvas.drawPoints(ui.PointMode.points, _dotPoints, _paintStrokeStyle); } - if (_pathCollection != null && _pathCollection.isNotEmpty) { + if (_pathCollection.isNotEmpty) { _paintStrokeStyle.strokeWidth = _maximumStrokeWidth; for (int i = 0; i < _pathCollection.length; i++) { canvas.drawPath(_pathCollection[i], _paintStrokeStyle); @@ -1034,7 +1034,9 @@ class _Bezier { final double endWidth; static _Bezier fromPoints( - {List<_TouchPoint> points, double start, double end}) { + {required List<_TouchPoint> points, + required double start, + required double end}) { final _TouchPoint c2 = calculateControlPoints(points[0], points[1], points[2])[1]; final _TouchPoint c3 = @@ -1055,8 +1057,8 @@ class _Bezier { final double l1 = sqrt(dx1 * dx1 + dy1 * dy1); final double l2 = sqrt(dx2 * dx2 + dy2 * dy2); - final double dxm = m1.x - m2.x; - final double dym = m1.y - m2.y; + final double dxm = (m1.x - m2.x).toDouble(); + final double dym = (m1.y - m2.y).toDouble(); double k = l2 / (l1 + l2); if (k.isNaN) k = 0; @@ -1085,9 +1087,9 @@ class _Bezier { point(t, startPoint.y, control1.y, control2.y, endPoint.y); if (i > 0) { - final double xdiff = cx - px; - final double ydiff = cy - py; - length += sqrt(xdiff * xdiff + ydiff * ydiff); + final double xDiff = cx - px; + final double yDiff = cy - py; + length += sqrt(xDiff * xDiff + yDiff * yDiff); } px = cx; @@ -1110,7 +1112,7 @@ class _Bezier { } class _CachePoint { - const _CachePoint({this.x, this.y, this.width}); + const _CachePoint({required this.x, required this.y, required this.width}); final double x; final double y; @@ -1120,7 +1122,7 @@ class _CachePoint { ///This class holds the touch point related data. class _TouchPoint { /// Creates a new instance of [TouchPoint]. - const _TouchPoint({this.x, this.y, this.time}); + const _TouchPoint({required this.x, required this.y, this.time}); ///Touch x point. final double x; @@ -1129,5 +1131,5 @@ class _TouchPoint { final double y; ///Time in millisecondsSinceEpoch format. - final int time; + final int? time; } diff --git a/packages/syncfusion_flutter_signaturepad/pubspec.yaml b/packages/syncfusion_flutter_signaturepad/pubspec.yaml index ea822e3d1..9f70bd439 100644 --- a/packages/syncfusion_flutter_signaturepad/pubspec.yaml +++ b/packages/syncfusion_flutter_signaturepad/pubspec.yaml @@ -1,10 +1,10 @@ name: syncfusion_flutter_signaturepad -description: The Signature Pad widget allows you to capture smooth and more realistic signatures through drawn gestures and save it as an image. -version: 18.3.35-beta -homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_signaturepad +description: The Flutter Signature Pad widget allows you to capture smooth and more realistic signatures through drawn gestures and save it as an image. +version: 19.1.54-beta +homepage: https://github.com/syncfusion/flutter-examples environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: diff --git a/packages/syncfusion_flutter_sliders/CHANGELOG.md b/packages/syncfusion_flutter_sliders/CHANGELOG.md index 612e76081..cf989b08d 100644 --- a/packages/syncfusion_flutter_sliders/CHANGELOG.md +++ b/packages/syncfusion_flutter_sliders/CHANGELOG.md @@ -1,3 +1,47 @@ +## Unreleased + +## Slider + +### Features + +* Support has been provided for vertical orientation. + +## Range Slider + +### Features + +* Support has been provided for vertical orientation. + +### Breaking changes + +* Now, the `SfRangeSliderSemanticFormatterCallback` typedef has been changed into `RangeSliderSemanticFormatterCallback` and the parameter has been changed from `SfRangeValues` to `value` which denotes the value of the current thumb. A new parameter named `SfThumb` has also been added to indicate which thumb is changed currently. + +## Range Selector + +### Breaking changes + +* Now, the `SfRangeSelectorSemanticFormatterCallback` typedef has been changed into `RangeSelectorSemanticFormatterCallback` and the parameter has been changed from `SfRangeValues` to `value` which denotes the value of the current thumb. A new parameter named `SfThumb` has also been added to indicate which thumb is changed currently. + +## [18.4.30-beta] - 12/17/2020 + +## Slider + +### Breaking changes + +* The `showTooltip` property has been changed into `enableTooltip` property. + +## Range Slider + +### Breaking changes + +* The `showTooltip` property has been changed into `enableTooltip` property. + +## Range Selector + +### Breaking changes + +* The `showTooltip` property has been changed into `enableTooltip` property. + ## [18.3.35-beta] - 10/01/2020 ## Slider diff --git a/packages/syncfusion_flutter_sliders/README.md b/packages/syncfusion_flutter_sliders/README.md index c09e4c2e1..2aa6ee9d5 100644 --- a/packages/syncfusion_flutter_sliders/README.md +++ b/packages/syncfusion_flutter_sliders/README.md @@ -1,16 +1,14 @@ -![syncfusion_flutter_slider_banner](https://cdn.syncfusion.com/content/images/FTControl/Flutter/sliders/slider-banner.png) +![syncfusion_flutter_slider_banner](https://cdn.syncfusion.com/content/images/Flutter/pub_images/slider_images/slider-banner.png) -# Syncfusion Flutter Sliders +# Flutter Sliders library -Syncfusion Flutter Sliders library is written natively in Dart for creating highly interactive and UI-rich slider controls for filtering purposes in Flutter applications. +The Flutter Sliders package is written natively in Dart for creating highly interactive and UI-rich slider widgets for filtering purposes in Flutter applications. ## Overview This library is used to create three different types of sliders, namely slider, range slider, and range selector. All these sliders have a rich set of features such as support for both numeric and date values, tooltip, labels, and ticks. The range selector latter accepts any kind of child including [Charts](https://pub.dev/packages/syncfusion_flutter_charts). -**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion Commercial License or Syncfusion Community license. For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. - -**Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. +**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion Commercial License or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. ## Table of contents - [Slider features](#slider-features) @@ -52,15 +50,21 @@ This library is used to create three different types of sliders, namely slider, * **Highly customizable** - In addition to the built-in rich set of features, fully customize the control easily using the wide range of provided options. ![slider customization](https://cdn.syncfusion.com/content/images/Flutter/pub_images/slider_images/slider_customization.png) +* **Orientation** - Supports both horizontal and vertical orientations. +![slider orientation](https://cdn.syncfusion.com/content/images/Flutter/pub_images/slider_images/slider_orientation.png) + ## Range slider features Range slider supports all the above-mentioned features of the slider in addition to: +* **Orientation** - Supports both horizontal and vertical orientations. +![range slider orientation](https://cdn.syncfusion.com/content/images/Flutter/pub_images/range_slider_images/range_slider_orientation.png) + * **Interval selection** - Allows users to select a particular interval by tapping or clicking in it. Both thumbs will be moved to the current interval with animation. ## Range selector features -Range selector supports all the above-mentioned features of the range slider in addition to: +Range selector supports all the above-mentioned features(except orientation) of the range slider in addition to: * **Child support** - Add a child of any type inside the range selector. It is also possible to add Charts. With the built-in integration, range selector is smart enough to handle features like segment selection or zooming of a chart based on the selected range in the range selector. Similar to the range slider, it also supports both numeric and date values. @@ -122,6 +126,8 @@ Widget build(BuildContext context) { Add the slider elements such as ticks, labels, and tooltip to show the current position of the slider thumb. +#### Horizontal slider + ```dart double _value = 40.0; @@ -152,7 +158,39 @@ Widget build(BuildContext context) { The following screenshot illustrates the result of the above code sample. -![simple slider](https://cdn.syncfusion.com/content/images/Flutter/pub_images/slider_images/Slider_Tooltip.png) +![horizontal slider](https://cdn.syncfusion.com/content/images/Flutter/pub_images/slider_images/Slider_Tooltip.png) + +#### Vertical slider + +```dart +double _value = 40.0; + +@override +Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Syncfusion Flutter Vertical Slider'), + ), + body: SfSlider.vertical( + min: 0.0, + max: 100.0, + value: _value, + interval: 20, + showTicks: true, + showLabels: true, + enableTooltip: true, + minorTicksPerInterval: 1, + onChanged: (dynamic value) { + setState(() { + _value = value; + }); + }, + ), + ); +} +``` + +![vertical slider](https://cdn.syncfusion.com/content/images/Flutter/pub_images/slider_images/vertical-slider.png) ## Range slider getting started @@ -183,6 +221,8 @@ Widget build(BuildContext context) { Add the range slider elements such as ticks, labels, and tooltips to show the current position of the range slider thumb. +#### Horizontal range slider + ```dart SfRangeValues _values = SfRangeValues(40.0, 80.0); @@ -215,6 +255,38 @@ The following screenshot illustrates the result of the above code sample. ![simple range slider](https://cdn.syncfusion.com/content/images/FTControl/Flutter/sliders/range-slider.png) +#### Vertical range slider + +```dart +SfRangeValues _values = SfRangeValues(40.0, 80.0); + +@override +Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Syncfusion Flutter Vertical Range Slider'), + ), + body: SfRangeSlider.vertical( + min: 0.0, + max: 100.0, + values: _values, + interval: 20, + showTicks: true, + showLabels: true, + enableTooltip: true, + minorTicksPerInterval: 1, + onChanged: (SfRangeValues values) { + setState(() { + _values = values; + }); + }, + ), + ); +} +``` + +![vertical range slider](https://cdn.syncfusion.com/content/images/Flutter/pub_images/slider_images/vertical-range-slider.png) + ## Range selector getting started Import the following package. diff --git a/packages/syncfusion_flutter_sliders/example/pubspec.yaml b/packages/syncfusion_flutter_sliders/example/pubspec.yaml index a966174fa..b5985d499 100644 --- a/packages/syncfusion_flutter_sliders/example/pubspec.yaml +++ b/packages/syncfusion_flutter_sliders/example/pubspec.yaml @@ -23,8 +23,6 @@ dependencies: intl: ">=0.15.0 <0.17.0" syncfusion_flutter_sliders: path: ../ - syncfusion_flutter_charts: - path: ../../syncfusion_flutter_charts # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. diff --git a/packages/syncfusion_flutter_sliders/lib/src/common.dart b/packages/syncfusion_flutter_sliders/lib/src/common.dart index c70fd9a17..5c3dc1011 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/common.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/common.dart @@ -26,11 +26,11 @@ typedef TooltipTextFormatterCallback = String Function( /// The value will be either [double] or [DateTime] based on the `values`. typedef SfSliderSemanticFormatterCallback = String Function(dynamic value); -typedef SfRangeSliderSemanticFormatterCallback = String Function( - SfRangeValues values); +typedef RangeSliderSemanticFormatterCallback = String Function( + dynamic value, SfThumb thumb); -typedef SfRangeSelectorSemanticFormatterCallback = String Function( - SfRangeValues values); +typedef RangeSelectorSemanticFormatterCallback = String Function( + dynamic value, SfThumb thumb); /// Option to place the labels either between the major ticks /// or on the major ticks. @@ -125,6 +125,7 @@ enum SliderDragMode { /// at the same time by dragging in the area between start and end thumbs. both } +enum SliderTooltipPosition { left, right } /// Represents the current selected values of [SfRangeSlider] /// and [SfRangeSelector]. diff --git a/packages/syncfusion_flutter_sliders/lib/src/constants.dart b/packages/syncfusion_flutter_sliders/lib/src/constants.dart index 374e5defe..1f8825eb3 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/constants.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/constants.dart @@ -41,3 +41,5 @@ enum ChildElements { /// Represents the content of [SfRangeSelector]. child, } + +enum SliderType { horizontal, vertical } diff --git a/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart b/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart index 46c27bca7..c82826a6f 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart @@ -3,11 +3,7 @@ import 'dart:math' as math; import 'dart:ui'; import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart' - show - GestureArenaTeam, - TapGestureRecognizer, - HorizontalDragGestureRecognizer; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:intl/intl.dart' show DateFormat, NumberFormat; @@ -119,7 +115,7 @@ import 'slider_shapes.dart'; class SfRangeSelector extends StatefulWidget { /// Creates a [SfRangeSelector]. const SfRangeSelector( - {Key key, + {Key? key, this.min = 0.0, this.max = 1.0, this.initialValues, @@ -129,8 +125,8 @@ class SfRangeSelector extends StatefulWidget { this.interval, this.stepSize, this.stepDuration, - this.deferredUpdateDelay, - this.minorTicksPerInterval, + this.deferredUpdateDelay = 500, + this.minorTicksPerInterval = 0, this.showTicks = false, this.showLabels = false, this.showDivisors = false, @@ -140,29 +136,28 @@ class SfRangeSelector extends StatefulWidget { this.dragMode = SliderDragMode.onThumb, this.inactiveColor, this.activeColor, - this.labelPlacement, + this.labelPlacement = LabelPlacement.onTicks, this.numberFormat, this.dateFormat, this.dateIntervalType, this.labelFormatterCallback, this.tooltipTextFormatterCallback, this.semanticFormatterCallback, - this.trackShape, - this.divisorShape, - this.overlayShape, - this.thumbShape, - this.tickShape, - this.minorTickShape, - this.tooltipShape, + this.trackShape = const SfTrackShape(), + this.divisorShape = const SfDivisorShape(), + this.overlayShape = const SfOverlayShape(), + this.thumbShape = const SfThumbShape(), + this.tickShape = const SfTickShape(), + this.minorTickShape = const SfMinorTickShape(), + this.tooltipShape = const SfRectangularTooltipShape(), this.startThumbIcon, this.endThumbIcon, - @required this.child}) + required this.child}) : assert(min != null), assert(max != null), assert(min != max), assert(interval == null || interval > 0), assert(stepSize == null || stepSize > 0), - assert(child != null), assert(controller != null || initialValues != null), super(key: key); @@ -213,7 +208,7 @@ class SfRangeSelector extends StatefulWidget { /// * [RangeController](https://pub.dev/documentation/syncfusion_flutter_core/latest/core/RangeController-class.html), for coordinating between [SfRangeSelector] and the widget which listens to it. /// * [showTicks], to render major ticks at given interval. /// * [showLabels], to render labels at given interval. - final SfRangeValues initialValues; + final SfRangeValues? initialValues; /// Called when the user is selecting a new values /// for the selector by dragging. @@ -236,7 +231,7 @@ class SfRangeSelector extends StatefulWidget { /// ), /// ) /// ``` - final ValueChanged onChanged; + final ValueChanged? onChanged; /// Coordinates between [SfRangeSelector] and the widget which listens to it. /// @@ -417,7 +412,7 @@ class SfRangeSelector extends StatefulWidget { /// } /// } /// ``` - final RangeController controller; + final RangeController? controller; /// Controls the range selector’s state whether it is disabled or enabled. /// @@ -507,7 +502,7 @@ class SfRangeSelector extends StatefulWidget { /// * [showDivisors], to render divisors at given interval. /// * [showTicks], to render major ticks at given interval. /// * [showLabels], to render labels at given interval. - final double interval; + final double? interval; /// Option to select discrete values. /// @@ -535,7 +530,7 @@ class SfRangeSelector extends StatefulWidget { /// ) /// ) /// ``` - final double stepSize; + final double? stepSize; /// Option to select discrete date values. /// @@ -575,7 +570,7 @@ class SfRangeSelector extends StatefulWidget { /// * [interval], for setting the interval. /// * [dateIntervalType], for changing the interval type. /// * [dateFormat] for formatting the date labels. - final SliderStepDuration stepDuration; + final SliderStepDuration? stepDuration; /// Customize the delay duration when [enableDeferredUpdate] is `true`. /// @@ -906,7 +901,7 @@ class SfRangeSelector extends StatefulWidget { /// /// * [SfRangeSelectorThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfRangeSelectorThemeData-class.html), for customizing the individual /// inactive range selector element’s visual. - final Color inactiveColor; + final Color? inactiveColor; /// Color applied to the active track, thumb, overlay, and inactive divisors. /// @@ -935,7 +930,7 @@ class SfRangeSelector extends StatefulWidget { /// See also: /// /// * [SfRangeSelectorThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfRangeSelectorThemeData-class.html), for customizing the individual active range selector element’s visual. - final Color activeColor; + final Color? activeColor; /// Option to place the labels either between the major ticks or /// on the major ticks. @@ -990,7 +985,7 @@ class SfRangeSelector extends StatefulWidget { /// See also: /// /// * [labelFormatterCallback], for formatting the numeric and date labels. - final NumberFormat numberFormat; + final NumberFormat? numberFormat; /// Formats the date labels. It is mandatory for date [SfRangeSelector]. /// @@ -1029,7 +1024,7 @@ class SfRangeSelector extends StatefulWidget { /// * [numberFormat], for formatting the numeric labels. /// * [labelFormatterCallback], for formatting the numeric and date label. /// * [dateIntervalType], for changing the interval type. - final DateFormat dateFormat; + final DateFormat? dateFormat; /// The type of date interval. It is mandatory for date [SfRangeSelector]. /// @@ -1061,7 +1056,7 @@ class SfRangeSelector extends StatefulWidget { /// ), /// ) /// ``` - final DateIntervalType dateIntervalType; + final DateIntervalType? dateIntervalType; /// Signature for formatting or changing the whole numeric or date label text. /// @@ -1092,7 +1087,7 @@ class SfRangeSelector extends StatefulWidget { /// ), /// ) /// ``` - final LabelFormatterCallback labelFormatterCallback; + final LabelFormatterCallback? labelFormatterCallback; /// Signature for formatting or changing the whole tooltip label text. /// @@ -1128,7 +1123,7 @@ class SfRangeSelector extends StatefulWidget { /// ), /// ) /// ``` - final TooltipTextFormatterCallback tooltipTextFormatterCallback; + final TooltipTextFormatterCallback? tooltipTextFormatterCallback; /// The callback used to create a semantic value from the selector's values. /// @@ -1147,8 +1142,8 @@ class SfRangeSelector extends StatefulWidget { /// initialValues: _values, /// interval: 20, /// stepSize: 10, - /// semanticFormatterCallback: (SfRangeValues values) { - /// return '${values.start} and ${values.end}'; + /// semanticFormatterCallback: (dynamic value, SfThumb thumb) { + /// return 'The $thumb value is ${value}'; /// }, /// child: Container( /// height: 150, @@ -1156,7 +1151,7 @@ class SfRangeSelector extends StatefulWidget { /// ), /// ) /// ``` - final SfRangeSelectorSemanticFormatterCallback semanticFormatterCallback; + final RangeSelectorSemanticFormatterCallback? semanticFormatterCallback; /// Base class for [SfRangeSelector] track shapes. final SfTrackShape trackShape; @@ -1252,7 +1247,7 @@ class SfRangeSelector extends StatefulWidget { /// /// * [thumbShape], for customizing the thumb shape. /// * [SfRangeSelectorThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfRangeSelectorThemeData-class.html), for customizing the individual active range selector element’s visual. - final Widget startThumbIcon; + final Widget? startThumbIcon; /// Sets the widget inside the right thumb. /// @@ -1290,7 +1285,7 @@ class SfRangeSelector extends StatefulWidget { /// /// * [thumbShape], for customizing the thumb shape. /// * [SfRangeSelectorThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfRangeSelectorThemeData-class.html), for customizing the individual active range selector element’s visual. - final Widget endThumbIcon; + final Widget? endThumbIcon; @override _SfRangeSelectorState createState() => _SfRangeSelectorState(); @@ -1298,14 +1293,16 @@ class SfRangeSelector extends StatefulWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - properties.add( - initialValues.toDiagnosticsNode(name: 'initialValues'), - ); + if (initialValues != null) { + properties.add( + initialValues!.toDiagnosticsNode(name: 'initialValues'), + ); + } properties.add(DiagnosticsProperty('min', min)); properties.add(DiagnosticsProperty('max', max)); if (controller != null) { properties.add( - controller.toDiagnosticsNode(name: 'controller'), + controller!.toDiagnosticsNode(name: 'controller'), ); } properties.add(FlagProperty('enabled', @@ -1316,7 +1313,7 @@ class SfRangeSelector extends StatefulWidget { properties.add(DoubleProperty('interval', interval)); properties.add(DoubleProperty('stepSize', stepSize)); if (stepDuration != null) { - properties.add(stepDuration.toDiagnosticsNode(name: 'stepDuration')); + properties.add(stepDuration!.toDiagnosticsNode(name: 'stepDuration')); } properties.add(IntProperty('minorTicksPerInterval', minorTicksPerInterval)); @@ -1357,11 +1354,13 @@ class SfRangeSelector extends StatefulWidget { .add(EnumProperty('labelPlacement', labelPlacement)); properties .add(DiagnosticsProperty('numberFormat', numberFormat)); - if (initialValues.start.runtimeType == DateTime) { + if (initialValues != null && + initialValues!.start.runtimeType == DateTime && + dateFormat != null) { properties.add(StringProperty( 'dateFormat', 'Formatted value is ' + - (dateFormat.format(initialValues.start)).toString())); + (dateFormat!.format(initialValues!.start)).toString())); } properties.add(ObjectFlagProperty>.has( @@ -1372,20 +1371,22 @@ class SfRangeSelector extends StatefulWidget { 'tooltipTextFormatterCallback', tooltipTextFormatterCallback)); properties.add(ObjectFlagProperty.has( 'labelFormatterCallback', labelFormatterCallback)); - properties.add(ObjectFlagProperty>.has( - 'semanticFormatterCallback', semanticFormatterCallback)); + properties.add( + ObjectFlagProperty.has( + 'semanticFormatterCallback', semanticFormatterCallback)); } } class _SfRangeSelectorState extends State with TickerProviderStateMixin { - AnimationController overlayStartController; - AnimationController overlayEndController; - AnimationController startPositionController; - AnimationController endPositionController; - AnimationController stateController; - AnimationController tooltipAnimationController; - Timer tooltipDelayTimer; + late AnimationController overlayStartController; + late AnimationController overlayEndController; + late AnimationController startPositionController; + late AnimationController endPositionController; + late AnimationController stateController; + late AnimationController tooltipAnimationStartController; + late AnimationController tooltipAnimationEndController; + Timer? tooltipDelayTimer; final Duration duration = const Duration(milliseconds: 100); String _getFormattedLabelText(dynamic actualText, String formattedText) { @@ -1398,7 +1399,7 @@ class _SfRangeSelectorState extends State SfRangeSelectorThemeData _getRangeSelectorThemeData(ThemeData themeData) { SfRangeSelectorThemeData rangeSelectorThemeData = - SfRangeSelectorTheme.of(context); + SfRangeSelectorTheme.of(context)!; final double minTrackHeight = math.min( rangeSelectorThemeData.activeTrackHeight, rangeSelectorThemeData.inactiveTrackHeight); @@ -1414,17 +1415,17 @@ class _SfRangeSelectorState extends State labelOffset: rangeSelectorThemeData.labelOffset ?? (widget.showTicks ? const Offset(0.0, 5.0) : const Offset(0.0, 13.0)), inactiveLabelStyle: rangeSelectorThemeData.inactiveLabelStyle ?? - themeData.textTheme.bodyText1.copyWith( + themeData.textTheme.bodyText1!.copyWith( color: widget.enabled - ? themeData.textTheme.bodyText1.color.withOpacity(0.87) + ? themeData.textTheme.bodyText1!.color!.withOpacity(0.87) : themeData.colorScheme.onSurface.withOpacity(0.32)), activeLabelStyle: rangeSelectorThemeData.activeLabelStyle ?? - themeData.textTheme.bodyText1.copyWith( + themeData.textTheme.bodyText1!.copyWith( color: widget.enabled - ? themeData.textTheme.bodyText1.color.withOpacity(0.87) + ? themeData.textTheme.bodyText1!.color!.withOpacity(0.87) : themeData.colorScheme.onSurface.withOpacity(0.32)), tooltipTextStyle: rangeSelectorThemeData.tooltipTextStyle ?? - themeData.textTheme.bodyText1.copyWith( + themeData.textTheme.bodyText1!.copyWith( color: rangeSelectorThemeData.brightness == Brightness.light ? const Color.fromRGBO(255, 255, 255, 1) : const Color.fromRGBO(0, 0, 0, 1)), @@ -1512,8 +1513,8 @@ class _SfRangeSelectorState extends State super.initState(); if (widget.controller != null) { - assert(widget.controller.start != null); - assert(widget.controller.end != null); + assert(widget.controller!.start != null); + assert(widget.controller!.end != null); } overlayStartController = @@ -1524,7 +1525,9 @@ class _SfRangeSelectorState extends State AnimationController(duration: Duration.zero, vsync: this); endPositionController = AnimationController(duration: Duration.zero, vsync: this); - tooltipAnimationController = + tooltipAnimationStartController = + AnimationController(vsync: this, duration: duration); + tooltipAnimationEndController = AnimationController(vsync: this, duration: duration); stateController.value = widget.enabled && (widget.min != widget.max) ? 1.0 : 0.0; @@ -1532,12 +1535,13 @@ class _SfRangeSelectorState extends State @override void dispose() { - overlayStartController?.dispose(); - overlayEndController?.dispose(); - startPositionController?.dispose(); - endPositionController?.dispose(); - tooltipAnimationController?.dispose(); - stateController?.dispose(); + overlayStartController.dispose(); + overlayEndController.dispose(); + startPositionController.dispose(); + endPositionController.dispose(); + tooltipAnimationStartController.dispose(); + tooltipAnimationEndController.dispose(); + stateController.dispose(); super.dispose(); } @@ -1555,8 +1559,8 @@ class _SfRangeSelectorState extends State interval: widget.interval, stepSize: widget.stepSize, stepDuration: widget.stepDuration, - deferUpdateDelay: widget.deferredUpdateDelay ?? 500, - minorTicksPerInterval: widget.minorTicksPerInterval ?? 0, + deferUpdateDelay: widget.deferredUpdateDelay, + minorTicksPerInterval: widget.minorTicksPerInterval, showTicks: widget.showTicks, showLabels: widget.showLabels, showDivisors: widget.showDivisors, @@ -1567,7 +1571,7 @@ class _SfRangeSelectorState extends State inactiveColor: widget.inactiveColor ?? themeData.primaryColor.withOpacity(0.24), activeColor: widget.activeColor ?? themeData.primaryColor, - labelPlacement: widget.labelPlacement ?? LabelPlacement.onTicks, + labelPlacement: widget.labelPlacement, numberFormat: widget.numberFormat ?? NumberFormat('#.##'), dateFormat: widget.dateFormat, dateIntervalType: widget.dateIntervalType, @@ -1576,13 +1580,13 @@ class _SfRangeSelectorState extends State tooltipTextFormatterCallback: widget.tooltipTextFormatterCallback ?? _getFormattedTooltipText, semanticFormatterCallback: widget.semanticFormatterCallback, - trackShape: widget.trackShape ?? SfTrackShape(), - divisorShape: widget.divisorShape ?? SfDivisorShape(), - overlayShape: widget.overlayShape ?? SfOverlayShape(), - thumbShape: widget.thumbShape ?? SfThumbShape(), - tickShape: widget.tickShape ?? SfTickShape(), - minorTickShape: widget.minorTickShape ?? SfMinorTickShape(), - tooltipShape: widget.tooltipShape ?? SfRectangularTooltipShape(), + trackShape: widget.trackShape, + divisorShape: widget.divisorShape, + overlayShape: widget.overlayShape, + thumbShape: widget.thumbShape, + tickShape: widget.tickShape, + minorTickShape: widget.minorTickShape, + tooltipShape: widget.tooltipShape, child: widget.child, rangeSelectorThemeData: _getRangeSelectorThemeData(themeData), startThumbIcon: widget.startThumbIcon, @@ -1594,53 +1598,53 @@ class _SfRangeSelectorState extends State class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { const _RangeSelectorRenderObjectWidget( - {Key key, - this.min, - this.max, - this.values, - this.enabled, - this.interval, - this.stepSize, - this.stepDuration, - this.deferUpdateDelay, - this.minorTicksPerInterval, - this.showTicks, - this.showLabels, - this.showDivisors, - this.enableTooltip, - this.enableIntervalSelection, - this.deferUpdate, - this.dragMode, - this.inactiveColor, - this.activeColor, - this.labelPlacement, - this.numberFormat, - this.dateFormat, - this.dateIntervalType, - this.labelFormatterCallback, - this.tooltipTextFormatterCallback, - this.semanticFormatterCallback, - this.trackShape, - this.divisorShape, - this.overlayShape, - this.thumbShape, - this.tickShape, - this.minorTickShape, - this.tooltipShape, - this.child, - this.rangeSelectorThemeData, - this.startThumbIcon, - this.endThumbIcon, - this.state}) + {Key? key, + required this.min, + required this.max, + required this.values, + required this.enabled, + required this.interval, + required this.stepSize, + required this.stepDuration, + required this.deferUpdateDelay, + required this.minorTicksPerInterval, + required this.showTicks, + required this.showLabels, + required this.showDivisors, + required this.enableTooltip, + required this.enableIntervalSelection, + required this.deferUpdate, + required this.dragMode, + required this.inactiveColor, + required this.activeColor, + required this.labelPlacement, + required this.numberFormat, + required this.dateFormat, + required this.dateIntervalType, + required this.labelFormatterCallback, + required this.tooltipTextFormatterCallback, + required this.semanticFormatterCallback, + required this.trackShape, + required this.divisorShape, + required this.overlayShape, + required this.thumbShape, + required this.tickShape, + required this.minorTickShape, + required this.tooltipShape, + required this.child, + required this.rangeSelectorThemeData, + required this.startThumbIcon, + required this.endThumbIcon, + required this.state}) : super(key: key); final dynamic min; final dynamic max; - final SfRangeValues values; + final SfRangeValues? values; final bool enabled; - final double interval; - final double stepSize; - final SliderStepDuration stepDuration; + final double? interval; + final double? stepSize; + final SliderStepDuration? stepDuration; final int deferUpdateDelay; final int minorTicksPerInterval; @@ -1657,12 +1661,12 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { final LabelPlacement labelPlacement; final NumberFormat numberFormat; - final DateFormat dateFormat; - final DateIntervalType dateIntervalType; + final DateFormat? dateFormat; + final DateIntervalType? dateIntervalType; final SfRangeSelectorThemeData rangeSelectorThemeData; final LabelFormatterCallback labelFormatterCallback; final TooltipTextFormatterCallback tooltipTextFormatterCallback; - final SfRangeSelectorSemanticFormatterCallback semanticFormatterCallback; + final RangeSelectorSemanticFormatterCallback? semanticFormatterCallback; final SfTrackShape trackShape; final SfDivisorShape divisorShape; final SfOverlayShape overlayShape; @@ -1671,8 +1675,8 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { final SfTickShape minorTickShape; final SfTooltipShape tooltipShape; final Widget child; - final Widget startThumbIcon; - final Widget endThumbIcon; + final Widget? startThumbIcon; + final Widget? endThumbIcon; final _SfRangeSelectorState state; @override @@ -1739,8 +1743,6 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { ..enableIntervalSelection = enableIntervalSelection ..deferUpdate = deferUpdate ..dragMode = dragMode - ..inactiveColor = inactiveColor - ..activeColor = activeColor ..labelPlacement = labelPlacement ..numberFormat = numberFormat ..dateFormat = dateFormat @@ -1769,27 +1771,18 @@ class _RenderRangeSelectorElement extends RenderObjectElement { final Map _childToSlot = {}; @override - _RangeSelectorRenderObjectWidget get widget => super.widget; + _RangeSelectorRenderObjectWidget get widget => + // ignore: avoid_as + super.widget as _RangeSelectorRenderObjectWidget; @override - _RenderRangeSelector get renderObject => super.renderObject; - - void _mountChild(Widget newWidget, ChildElements slot) { - final Element oldChild = _slotToChild[slot]; - final Element newChild = updateChild(oldChild, newWidget, slot); - if (oldChild != null) { - _slotToChild.remove(slot); - _childToSlot.remove(oldChild); - } - if (newChild != null) { - _slotToChild[slot] = newChild; - _childToSlot[newChild] = slot; - } - } + _RenderRangeSelector get renderObject => + // ignore: avoid_as + super.renderObject as _RenderRangeSelector; - void _updateChild(Widget widget, ChildElements slot) { - final Element oldChild = _slotToChild[slot]; - final Element newChild = updateChild(oldChild, widget, slot); + void _updateChild(Widget? widget, ChildElements slot) { + final Element? oldChild = _slotToChild[slot]; + final Element? newChild = updateChild(oldChild, widget, slot); if (oldChild != null) { _childToSlot.remove(oldChild); _slotToChild.remove(slot); @@ -1800,16 +1793,19 @@ class _RenderRangeSelectorElement extends RenderObjectElement { } } - void _updateRenderObject(RenderObject child, ChildElements slot) { + void _updateRenderObject(RenderObject? child, ChildElements slot) { switch (slot) { case ChildElements.startThumbIcon: - renderObject.startThumbIcon = child; + // ignore: avoid_as + renderObject.startThumbIcon = child as RenderBox?; break; case ChildElements.endThumbIcon: - renderObject.endThumbIcon = child; + // ignore: avoid_as + renderObject.endThumbIcon = child as RenderBox?; break; case ChildElements.child: - renderObject.child = child; + // ignore: avoid_as + renderObject.child = child as RenderBox?; break; } } @@ -1820,11 +1816,11 @@ class _RenderRangeSelectorElement extends RenderObjectElement { } @override - void mount(Element parent, dynamic newSlot) { + void mount(Element? parent, dynamic newSlot) { super.mount(parent, newSlot); - _mountChild(widget.startThumbIcon, ChildElements.startThumbIcon); - _mountChild(widget.endThumbIcon, ChildElements.endThumbIcon); - _mountChild(widget.child, ChildElements.child); + _updateChild(widget.startThumbIcon, ChildElements.startThumbIcon); + _updateChild(widget.endThumbIcon, ChildElements.endThumbIcon); + _updateChild(widget.child, ChildElements.child); } @override @@ -1847,10 +1843,10 @@ class _RenderRangeSelectorElement extends RenderObjectElement { } @override - void removeRenderObjectChild(RenderObject child, dynamic slot) { + void removeRenderObjectChild(RenderObject child, ChildElements slot) { assert(child is RenderBox); assert(renderObject.childToSlot.keys.contains(child)); - _updateRenderObject(null, renderObject.childToSlot[child]); + _updateRenderObject(null, renderObject.childToSlot[child]!); assert(!renderObject.childToSlot.keys.contains(child)); assert(!renderObject.slotToChild.keys.contains(slot)); } @@ -1862,45 +1858,52 @@ class _RenderRangeSelectorElement extends RenderObjectElement { } } -class _RenderRangeSelector extends RenderBaseSlider { +class _RenderRangeSelector extends RenderBaseSlider + implements MouseTrackerAnnotation { _RenderRangeSelector({ - dynamic min, - dynamic max, - SfRangeValues values, - bool enabled, - double interval, - double stepSize, - SliderStepDuration stepDuration, - int deferUpdateDelay, - int minorTicksPerInterval, - bool showTicks, - bool showLabels, - bool showDivisors, - bool enableTooltip, - bool enableIntervalSelection, - bool deferUpdate, - SliderDragMode dragMode, - Color inactiveColor, - Color activeColor, - LabelPlacement labelPlacement, - NumberFormat numberFormat, - DateFormat dateFormat, - DateIntervalType dateIntervalType, - LabelFormatterCallback labelFormatterCallback, - TooltipTextFormatterCallback tooltipTextFormatterCallback, - SfRangeSelectorSemanticFormatterCallback semanticFormatterCallback, - SfTrackShape trackShape, - SfDivisorShape divisorShape, - SfOverlayShape overlayShape, - SfThumbShape thumbShape, - SfTickShape tickShape, - SfTickShape minorTickShape, - SfTooltipShape tooltipShape, - SfRangeSelectorThemeData rangeSelectorThemeData, - TextDirection textDirection, - MediaQueryData mediaQueryData, - @required _SfRangeSelectorState state, - }) : _state = state, + required dynamic min, + required dynamic max, + required SfRangeValues? values, + required bool enabled, + required double? interval, + required double? stepSize, + required SliderStepDuration? stepDuration, + required int deferUpdateDelay, + required int minorTicksPerInterval, + required bool showTicks, + required bool showLabels, + required bool showDivisors, + required bool enableTooltip, + required bool enableIntervalSelection, + required bool deferUpdate, + required SliderDragMode dragMode, + required Color inactiveColor, + required Color activeColor, + required LabelPlacement labelPlacement, + required NumberFormat numberFormat, + required DateFormat? dateFormat, + required DateIntervalType? dateIntervalType, + required LabelFormatterCallback labelFormatterCallback, + required TooltipTextFormatterCallback tooltipTextFormatterCallback, + required RangeSelectorSemanticFormatterCallback? semanticFormatterCallback, + required SfTrackShape trackShape, + required SfDivisorShape divisorShape, + required SfOverlayShape overlayShape, + required SfThumbShape thumbShape, + required SfTickShape tickShape, + required SfTickShape minorTickShape, + required SfTooltipShape tooltipShape, + required SfRangeSelectorThemeData rangeSelectorThemeData, + required TextDirection textDirection, + required MediaQueryData mediaQueryData, + required _SfRangeSelectorState state, + }) : _state = state, + _isEnabled = enabled, + _enableIntervalSelection = enableIntervalSelection, + _deferUpdateDelay = deferUpdateDelay, + _dragMode = dragMode, + _deferUpdate = deferUpdate, + _semanticFormatterCallback = semanticFormatterCallback, super( min: min, max: max, @@ -1928,26 +1931,18 @@ class _RenderRangeSelector extends RenderBaseSlider { sliderThemeData: rangeSelectorThemeData, textDirection: textDirection, mediaQueryData: mediaQueryData) { - _values = values; - _isEnabled = enabled; - _enableIntervalSelection = enableIntervalSelection; - _deferUpdateDelay = deferUpdateDelay; - _dragMode = dragMode; - _deferUpdate = deferUpdate; - _inactiveColor = inactiveColor; - _activeColor = activeColor; - _semanticFormatterCallback = semanticFormatterCallback; - if (_state.widget.controller != null) { - assert(_state.widget.controller.start != null); - assert(_state.widget.controller.end != null); + assert(_state.widget.controller!.start != null); + assert(_state.widget.controller!.end != null); _values = SfRangeValues( - _state.widget.controller.start, _state.widget.controller.end); + _state.widget.controller!.start, _state.widget.controller!.end); + } else { + _values = values!; } final GestureArenaTeam team = GestureArenaTeam(); - dragGestureRecognizer = HorizontalDragGestureRecognizer() + horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer() ..team = team ..onStart = _onDragStart ..onUpdate = _onDragUpdate @@ -1968,8 +1963,13 @@ class _RenderRangeSelector extends RenderBaseSlider { _stateAnimation = CurvedAnimation( parent: _state.stateController, curve: Curves.easeInOut); - _tooltipAnimation = CurvedAnimation( - parent: _state.tooltipAnimationController, curve: Curves.fastOutSlowIn); + _tooltipStartAnimation = CurvedAnimation( + parent: _state.tooltipAnimationStartController, + curve: Curves.fastOutSlowIn); + + _tooltipEndAnimation = CurvedAnimation( + parent: _state.tooltipAnimationEndController, + curve: Curves.fastOutSlowIn); if (isDateTime) { _valuesInMilliseconds = SfRangeValues( @@ -1997,19 +1997,21 @@ class _RenderRangeSelector extends RenderBaseSlider { final Map childToSlot = {}; - Animation _overlayStartAnimation; + late Animation _overlayStartAnimation; + + late Animation _overlayEndAnimation; - Animation _overlayEndAnimation; + late Animation _stateAnimation; - Animation _stateAnimation; + late Animation _tooltipStartAnimation; - Animation _tooltipAnimation; + late Animation _tooltipEndAnimation; - SfRangeValues _valuesInMilliseconds; + SfRangeValues? _valuesInMilliseconds; - Color _inactiveRegionColor; + late Color _inactiveRegionColor; - Color _activeRegionColor; + late Color _activeRegionColor; // It stores the interaction start x-position at [tapDown] and [dragStart] // method, which is used to check whether dragging is started or not. @@ -2019,7 +2021,7 @@ class _RenderRangeSelector extends RenderBaseSlider { bool _isIntervalTapped = false; - Timer _deferUpdateTimer; + Timer? _deferUpdateTimer; bool _isLocked = false; @@ -2029,7 +2031,7 @@ class _RenderRangeSelector extends RenderBaseSlider { Duration(milliseconds: 500); SfRangeValues get values => _values; - SfRangeValues _values; + late SfRangeValues _values; set values(SfRangeValues values) { if (_values == values) { @@ -2088,7 +2090,7 @@ class _RenderRangeSelector extends RenderBaseSlider { } bool get deferUpdate => _deferUpdate; - bool _deferUpdate; + late bool _deferUpdate; set deferUpdate(bool value) { if (_deferUpdate == value) { @@ -2099,6 +2101,7 @@ class _RenderRangeSelector extends RenderBaseSlider { SliderDragMode get dragMode => _dragMode; SliderDragMode _dragMode; + set dragMode(SliderDragMode value) { if (_dragMode == value) { return; @@ -2106,33 +2109,11 @@ class _RenderRangeSelector extends RenderBaseSlider { _dragMode = value; } - Color get inactiveColor => _inactiveColor; - Color _inactiveColor; - - set inactiveColor(Color value) { - if (_inactiveColor == value) { - return; - } - _inactiveColor = value; - markNeedsPaint(); - } - - Color get activeColor => _activeColor; - Color _activeColor; - - set activeColor(Color value) { - if (_activeColor == value) { - return; - } - _activeColor = value; - markNeedsPaint(); - } - - SfRangeSelectorSemanticFormatterCallback get semanticFormatterCallback => + RangeSelectorSemanticFormatterCallback? get semanticFormatterCallback => _semanticFormatterCallback; - SfRangeSelectorSemanticFormatterCallback _semanticFormatterCallback; - set semanticFormatterCallback( - SfRangeSelectorSemanticFormatterCallback value) { + RangeSelectorSemanticFormatterCallback? _semanticFormatterCallback; + + set semanticFormatterCallback(RangeSelectorSemanticFormatterCallback? value) { if (_semanticFormatterCallback == value) { return; } @@ -2155,42 +2136,42 @@ class _RenderRangeSelector extends RenderBaseSlider { markNeedsPaint(); } - RenderBox get startThumbIcon => _startThumbIcon; - RenderBox _startThumbIcon; + RenderBox? get startThumbIcon => _startThumbIcon; + RenderBox? _startThumbIcon; - set startThumbIcon(RenderBox value) { + set startThumbIcon(RenderBox? value) { _startThumbIcon = _updateChild(_startThumbIcon, value, ChildElements.startThumbIcon); } - RenderBox get endThumbIcon => _endThumbIcon; - RenderBox _endThumbIcon; + RenderBox? get endThumbIcon => _endThumbIcon; + RenderBox? _endThumbIcon; - set endThumbIcon(RenderBox value) { + set endThumbIcon(RenderBox? value) { _endThumbIcon = _updateChild(_endThumbIcon, value, ChildElements.endThumbIcon); } @override - RenderBox get child => _child; - RenderBox _child; + RenderBox? get child => _child; + RenderBox? _child; @override - set child(RenderBox value) { + set child(RenderBox? value) { _child = _updateChild(_child, value, ChildElements.child); } // The returned list is ordered for hit testing. Iterable get children sync* { if (_startThumbIcon != null) { - yield startThumbIcon; + yield startThumbIcon!; } if (_endThumbIcon != null) { - yield endThumbIcon; + yield endThumbIcon!; } if (child != null) { - yield child; + yield child!; } } @@ -2205,7 +2186,7 @@ class _RenderRangeSelector extends RenderBaseSlider { (actualMax - actualMin) * (8 / actualTrackRect.width).clamp(0.0, 1.0); SfRangeValues get actualValues => - isDateTime ? _valuesInMilliseconds : _values; + isDateTime ? _valuesInMilliseconds! : _values; // When the active track height and inactive track height are different, // a small gap is happens between min track height and child @@ -2215,10 +2196,60 @@ class _RenderRangeSelector extends RenderBaseSlider { ? sliderThemeData.activeTrackHeight - sliderThemeData.inactiveTrackHeight : sliderThemeData.inactiveTrackHeight - sliderThemeData.activeTrackHeight; + dynamic get _increasedStartValue { + return getNextSemanticValue(values.start, semanticActionUnit, + actualValue: actualValues.start); + } + + dynamic get _decreasedStartValue { + return getPrevSemanticValue(values.start, semanticActionUnit, + actualValue: actualValues.start); + } + + dynamic get _increasedEndValue { + return getNextSemanticValue(values.end, semanticActionUnit, + actualValue: actualValues.end); + } + + dynamic get _decreasedEndValue { + return getPrevSemanticValue(values.end, semanticActionUnit, + actualValue: actualValues.end); + } + + SfThumb? get activeThumb => _activeThumb; + SfThumb? _activeThumb; + + set activeThumb(SfThumb? value) { + if (_activeThumb == value && + (_state.overlayEndController.status == AnimationStatus.completed || + _state.overlayStartController.status == + AnimationStatus.completed)) { + return; + } + _activeThumb = value; + if (value == SfThumb.start) { + _state.overlayStartController.forward(); + _state.overlayEndController.reverse(); + if (enableTooltip) { + willDrawTooltip = true; + _state.tooltipAnimationStartController.forward(); + _state.tooltipAnimationEndController.reverse(); + } + } else { + _state.overlayEndController.forward(); + _state.overlayStartController.reverse(); + if (enableTooltip) { + willDrawTooltip = true; + _state.tooltipAnimationEndController.forward(); + _state.tooltipAnimationStartController.reverse(); + } + } + } + void _onTapDown(TapDownDetails details) { currentPointerType = PointerType.down; _interactionStartX = globalToLocal(details.globalPosition).dx; - currentX = _interactionStartX; + mainAxisOffset = _interactionStartX; _beginInteraction(); } @@ -2229,14 +2260,14 @@ class _RenderRangeSelector extends RenderBaseSlider { void _onDragStart(DragStartDetails details) { _isDragStart = true; _interactionStartX = globalToLocal(details.globalPosition).dx; - currentX = _interactionStartX; + mainAxisOffset = _interactionStartX; _beginInteraction(); } void _onDragUpdate(DragUpdateDetails details) { isInteractionEnd = false; currentPointerType = PointerType.move; - currentX = globalToLocal(details.globalPosition).dx; + mainAxisOffset = globalToLocal(details.globalPosition).dx; _updateRangeValues(deltaX: details.delta.dx); markNeedsPaint(); } @@ -2258,13 +2289,13 @@ class _RenderRangeSelector extends RenderBaseSlider { final double startPosition = getPositionFromValue(actualValues.start); final double endPosition = getPositionFromValue(actualValues.end); - final double leftThumbWidth = (startPosition - currentX).abs(); - final double rightThumbWidth = (endPosition - currentX).abs(); + final double leftThumbWidth = (startPosition - mainAxisOffset).abs(); + final double rightThumbWidth = (endPosition - mainAxisOffset).abs(); if ((_dragMode == SliderDragMode.both || _dragMode == SliderDragMode.betweenThumbs) && - startPosition < (currentX - minPreferredTouchWidth) && - (currentX + minPreferredTouchWidth) < endPosition) { + startPosition < (mainAxisOffset - minPreferredTouchWidth) && + (mainAxisOffset + minPreferredTouchWidth) < endPosition) { if (_isDragStart) { _isLocked = true; } else { @@ -2273,7 +2304,7 @@ class _RenderRangeSelector extends RenderBaseSlider { } else if (_dragMode == SliderDragMode.betweenThumbs) { return; } else if (rightThumbWidth == leftThumbWidth) { - switch (activeThumb) { + switch (activeThumb!) { case SfThumb.start: _state.overlayStartController.forward(); break; @@ -2296,7 +2327,8 @@ class _RenderRangeSelector extends RenderBaseSlider { void _forwardTooltipAnimation() { if (enableTooltip) { willDrawTooltip = true; - _state.tooltipAnimationController.forward(); + _state.tooltipAnimationStartController.forward(); + _state.tooltipAnimationEndController.forward(); _state.tooltipDelayTimer?.cancel(); _state.tooltipDelayTimer = Timer(const Duration(milliseconds: 500), () { _reverseTooltipAnimation(); @@ -2308,21 +2340,30 @@ class _RenderRangeSelector extends RenderBaseSlider { _state.tooltipDelayTimer = null; if (isInteractionEnd && willDrawTooltip && - _state.tooltipAnimationController.status == AnimationStatus.completed) { - _state.tooltipAnimationController.reverse(); - if (_state.tooltipAnimationController.status == - AnimationStatus.dismissed) { - willDrawTooltip = false; - } + _state.tooltipAnimationStartController.status == + AnimationStatus.completed) { + _state.tooltipAnimationStartController.reverse(); + } + if (isInteractionEnd && + willDrawTooltip && + _state.tooltipAnimationEndController.status == + AnimationStatus.completed) { + _state.tooltipAnimationEndController.reverse(); + } + if (_state.tooltipAnimationStartController.status == + AnimationStatus.dismissed && + _state.tooltipAnimationEndController.status == + AnimationStatus.dismissed) { + willDrawTooltip = false; } } - void _updateRangeValues({double deltaX}) { + void _updateRangeValues({double? deltaX}) { SfRangeValues newValues; - _isDragging = (_interactionStartX - currentX).abs() > 1; + _isDragging = (_interactionStartX - mainAxisOffset).abs() > 1; _isIntervalTapped = _enableIntervalSelection && !_isDragging; - if (_isLocked) { + if (_isLocked && deltaX != null) { newValues = _getLockRangeValues(deltaX); } else if (_dragMode == SliderDragMode.betweenThumbs) { return; @@ -2330,7 +2371,7 @@ class _RenderRangeSelector extends RenderBaseSlider { double start; double end; final double factor = getFactorFromCurrentPosition(); - final double value = lerpDouble(actualMin, actualMax, factor); + final double value = lerpDouble(actualMin, actualMax, factor)!; if (isDateTime) { start = values.start.millisecondsSinceEpoch.toDouble(); end = values.end.millisecondsSinceEpoch.toDouble(); @@ -2339,7 +2380,7 @@ class _RenderRangeSelector extends RenderBaseSlider { end = values.end; } - switch (activeThumb) { + switch (activeThumb!) { case SfThumb.start: final double startValue = math.min(value, end - minThumbGap); final dynamic actualStartValue = @@ -2386,7 +2427,7 @@ class _RenderRangeSelector extends RenderBaseSlider { if (_isIntervalTapped) { final double value = - lerpDouble(actualMin, actualMax, getFactorFromCurrentPosition()); + lerpDouble(actualMin, actualMax, getFactorFromCurrentPosition())!; _updatePositionControllerValue(_getSelectedRange(value)); } } @@ -2406,7 +2447,8 @@ class _RenderRangeSelector extends RenderBaseSlider { _state.overlayStartController.reverse(); _state.overlayEndController.reverse(); if (enableTooltip && _state.tooltipDelayTimer == null) { - _state.tooltipAnimationController.reverse(); + _state.tooltipAnimationStartController.reverse(); + _state.tooltipAnimationEndController.reverse(); } isInteractionEnd = true; @@ -2418,18 +2460,19 @@ class _RenderRangeSelector extends RenderBaseSlider { } void _updatePositionControllerValue(SfRangeValues newValues) { - DateTime startDate; - DateTime endDate; + DateTime? startDate; + DateTime? endDate; if (isDateTime) { startDate = newValues.start; endDate = newValues.end; } final double startValueFactor = getFactorFromValue(isDateTime - ? startDate.millisecondsSinceEpoch.toDouble() + ? startDate!.millisecondsSinceEpoch.toDouble() : newValues.start); - final double endValueFactor = getFactorFromValue( - isDateTime ? endDate.millisecondsSinceEpoch.toDouble() : newValues.end); + final double endValueFactor = getFactorFromValue(isDateTime + ? endDate!.millisecondsSinceEpoch.toDouble() + : newValues.end); final double startDistanceFactor = (startValueFactor - _state.startPositionController.value).abs(); @@ -2447,12 +2490,12 @@ class _RenderRangeSelector extends RenderBaseSlider { .animateTo(endValueFactor, curve: Curves.easeInOut); } - SfRangeValues _getLockRangeValues(double deltaX) { + SfRangeValues _getLockRangeValues(double? deltaX) { double startPosition = getPositionFromValue(actualValues.start); double endPosition = getPositionFromValue(actualValues.end); final double lockedRangeWidth = endPosition - startPosition; - startPosition += deltaX ?? 0; - endPosition += deltaX ?? 0; + startPosition += deltaX ?? 0.0; + endPosition += deltaX ?? 0.0; final double actualMinInPx = getPositionFromValue(actualMin); final double actualMaxInPx = getPositionFromValue(actualMax); if (startPosition < actualMinInPx) { @@ -2469,11 +2512,11 @@ class _RenderRangeSelector extends RenderBaseSlider { void _updateNewValues(SfRangeValues newValues) { if (_state.widget.onChanged != null) { - _state.widget.onChanged(newValues); + _state.widget.onChanged!(newValues); } if (_state.widget.controller != null) { - _state.widget.controller.start = newValues.start; - _state.widget.controller.end = newValues.end; + _state.widget.controller!.start = newValues.start; + _state.widget.controller!.end = newValues.end; } else if (!_deferUpdate) { values = newValues; markNeedsPaint(); @@ -2482,14 +2525,14 @@ class _RenderRangeSelector extends RenderBaseSlider { } SfRangeValues _getSelectedRange(double value) { - SfRangeValues rangeValues; + late SfRangeValues rangeValues; dynamic start; dynamic end; - for (int i = 0; i < divisions; i++) { - final double currentLabel = unformattedLabels[i]; - if (i < divisions - 1) { - final double nextLabel = unformattedLabels[i + 1]; + for (int i = 0; i < divisions!; i++) { + final double currentLabel = unformattedLabels![i]; + if (i < divisions! - 1) { + final double nextLabel = unformattedLabels![i + 1]; if (value >= currentLabel && value <= nextLabel) { if (isDateTime) { start = DateTime.fromMillisecondsSinceEpoch(currentLabel.toInt()); @@ -2505,15 +2548,15 @@ class _RenderRangeSelector extends RenderBaseSlider { start = isDateTime ? DateTime.fromMillisecondsSinceEpoch(currentLabel.toInt()) : currentLabel; - end = max; + end = this.max; rangeValues = SfRangeValues(start, end); } } return rangeValues; } - RenderBox _updateChild( - RenderBox oldChild, RenderBox newChild, ChildElements slot) { + RenderBox? _updateChild( + RenderBox? oldChild, RenderBox? newChild, ChildElements slot) { if (oldChild != null) { dropChild(oldChild); childToSlot.remove(oldChild); @@ -2530,11 +2573,11 @@ class _RenderRangeSelector extends RenderBaseSlider { void _handleRangeControllerChange() { if (_state.mounted && _state.widget.controller != null && - (_values.start != _state.widget.controller.start || - _values.end != _state.widget.controller.end)) { + (_values.start != _state.widget.controller!.start || + _values.end != _state.widget.controller!.end)) { values = SfRangeValues( - getActualValue(value: _state.widget.controller.start), - getActualValue(value: _state.widget.controller.end)); + getActualValue(value: _state.widget.controller!.start), + getActualValue(value: _state.widget.controller!.end)); markNeedsPaint(); } @@ -2554,21 +2597,24 @@ class _RenderRangeSelector extends RenderBaseSlider { : getValueFromFactor(_state.endPositionController.value); if (_state.widget.controller != null) { - _state.widget.controller.start = startValue; - _state.widget.controller.end = endValue; + _state.widget.controller!.start = startValue; + _state.widget.controller!.end = endValue; } else { values = SfRangeValues(startValue, endValue); } if (_state.widget.onChanged != null) { - _state.widget.onChanged(SfRangeValues(startValue, endValue)); + _state.widget.onChanged!(SfRangeValues(startValue, endValue)); } markNeedsPaint(); } } void _handleTooltipAnimationStatusChange(AnimationStatus status) { - if (status == AnimationStatus.dismissed) { + if (_state.tooltipAnimationStartController.status == + AnimationStatus.dismissed && + _state.tooltipAnimationEndController.status == + AnimationStatus.dismissed) { willDrawTooltip = false; } } @@ -2578,7 +2624,7 @@ class _RenderRangeSelector extends RenderBaseSlider { final Paint paint = Paint() ..isAntiAlias = true ..color = _inactiveRegionColor; - if (child != null && child.size.height > 1 && child.size.width > 1) { + if (child != null && child!.size.height > 1 && child!.size.width > 1) { final double halfActiveTrackHeight = sliderThemeData.activeTrackHeight / 2; final double halfInactiveTrackHeight = @@ -2624,7 +2670,7 @@ class _RenderRangeSelector extends RenderBaseSlider { ) { final bool isLeftThumbActive = activeThumb == SfThumb.start; Offset thumbCenter = isLeftThumbActive ? endThumbCenter : startThumbCenter; - RenderBox thumbIcon = isLeftThumbActive ? _endThumbIcon : _startThumbIcon; + RenderBox? thumbIcon = isLeftThumbActive ? _endThumbIcon : _startThumbIcon; showOverlappingThumbStroke = false; // Drawing thumb. @@ -2635,7 +2681,8 @@ class _RenderRangeSelector extends RenderBaseSlider { currentValues: _values, enableAnimation: _stateAnimation, textDirection: textDirection, - thumb: isLeftThumbActive ? SfThumb.end : SfThumb.start); + thumb: isLeftThumbActive ? SfThumb.end : SfThumb.start, + paint: null); thumbCenter = isLeftThumbActive ? startThumbCenter : endThumbCenter; thumbIcon = isLeftThumbActive ? _startThumbIcon : _endThumbIcon; @@ -2646,7 +2693,8 @@ class _RenderRangeSelector extends RenderBaseSlider { currentValues: _values, animation: isLeftThumbActive ? _overlayStartAnimation : _overlayEndAnimation, - thumb: activeThumb); + thumb: activeThumb, + paint: null); showOverlappingThumbStroke = (getFactorFromValue(actualValues.start) - getFactorFromValue(actualValues.end)) @@ -2662,7 +2710,8 @@ class _RenderRangeSelector extends RenderBaseSlider { currentValues: _values, enableAnimation: _stateAnimation, textDirection: textDirection, - thumb: activeThumb); + thumb: activeThumb, + paint: null); } void _drawTooltip( @@ -2674,12 +2723,13 @@ class _RenderRangeSelector extends RenderBaseSlider { Rect trackRect) { if (willDrawTooltip) { final Paint paint = Paint() - ..color = sliderThemeData.tooltipBackgroundColor + ..color = sliderThemeData.tooltipBackgroundColor! ..style = PaintingStyle.fill ..strokeWidth = 0; + final bool isLeftThumbActive = activeThumb == SfThumb.start; Offset thumbCenter = - activeThumb == SfThumb.start ? endThumbCenter : startThumbCenter; + isLeftThumbActive ? endThumbCenter : startThumbCenter; dynamic actualText = getValueFromPosition(thumbCenter.dx - offset.dx); String tooltipText = tooltipTextFormatterCallback( actualText, getFormattedText(actualText)); @@ -2688,7 +2738,7 @@ class _RenderRangeSelector extends RenderBaseSlider { textPainter.text = textSpan; textPainter.layout(); - Rect bottomTooltipRect; + Rect? bottomTooltipRect; if (tooltipShape is SfPaddleTooltipShape) { bottomTooltipRect = getPaddleTooltipRect( textPainter, @@ -2704,14 +2754,15 @@ class _RenderRangeSelector extends RenderBaseSlider { trackRect, sliderThemeData); } - showOverlappingTooltipStroke = false; + showOverlappingTooltipStroke = false; tooltipShape.paint(context, thumbCenter, Offset(actualTrackOffset.dx, tooltipStartY), textPainter, parentBox: this, sliderThemeData: sliderThemeData, paint: paint, - animation: _tooltipAnimation, + animation: + isLeftThumbActive ? _tooltipEndAnimation : _tooltipStartAnimation, trackRect: trackRect); thumbCenter = @@ -2724,7 +2775,7 @@ class _RenderRangeSelector extends RenderBaseSlider { textPainter.text = textSpan; textPainter.layout(); - Rect topTooltipRect; + Rect? topTooltipRect; if (tooltipShape is SfPaddleTooltipShape) { topTooltipRect = getPaddleTooltipRect( textPainter, @@ -2740,7 +2791,6 @@ class _RenderRangeSelector extends RenderBaseSlider { trackRect, sliderThemeData); } - if (bottomTooltipRect != null && topTooltipRect != null) { final Rect overlapRect = topTooltipRect.intersect(bottomTooltipRect); showOverlappingTooltipStroke = overlapRect.right > overlapRect.left; @@ -2751,15 +2801,16 @@ class _RenderRangeSelector extends RenderBaseSlider { parentBox: this, sliderThemeData: sliderThemeData, paint: paint, - animation: _tooltipAnimation, + animation: + isLeftThumbActive ? _tooltipStartAnimation : _tooltipEndAnimation, trackRect: trackRect); } } void _increaseStartAction() { if (isEnabled) { - final SfRangeValues actualNewValues = SfRangeValues( - _increaseValue(values.start, actualValues.start), values.end); + final SfRangeValues actualNewValues = + SfRangeValues(_increasedStartValue, values.end); final SfRangeValues newValues = isDateTime ? SfRangeValues( actualNewValues.start.millisecondsSinceEpoch.toDouble(), @@ -2774,22 +2825,20 @@ class _RenderRangeSelector extends RenderBaseSlider { void _decreaseStartAction() { if (isEnabled) { - _updateNewValues(SfRangeValues( - _decreaseValue(values.start, actualValues.start), values.end)); + _updateNewValues(SfRangeValues(_decreasedStartValue, values.end)); } } void _increaseEndAction() { if (isEnabled) { - _updateNewValues(SfRangeValues( - values.start, _increaseValue(values.end, actualValues.end))); + _updateNewValues(SfRangeValues(values.start, _increasedEndValue)); } } void _decreaseEndAction() { if (isEnabled) { - final SfRangeValues actualNewValues = SfRangeValues( - values.start, _decreaseValue(values.end, actualValues.end)); + final SfRangeValues actualNewValues = + SfRangeValues(values.start, _decreasedEndValue); final SfRangeValues newValues = isDateTime ? SfRangeValues( actualNewValues.start.millisecondsSinceEpoch.toDouble(), @@ -2802,29 +2851,57 @@ class _RenderRangeSelector extends RenderBaseSlider { } } - dynamic _increaseValue(dynamic value, double actualValue) { - return getNextSemanticValue(value, semanticActionUnit, - actualValue: actualValue); + void _handleExit(PointerExitEvent event) { + // Ensuring whether the thumb is drag or move + // not needed to call controller's reverse. + if (currentPointerType != PointerType.move) { + _state.overlayStartController.reverse(); + _state.overlayEndController.reverse(); + if (enableTooltip) { + _state.tooltipAnimationStartController.reverse(); + _state.tooltipAnimationEndController.reverse(); + } + } } - dynamic _decreaseValue(dynamic value, double actualValue) { - return getPrevSemanticValue(value, semanticActionUnit, - actualValue: actualValue); + void _handleHover(PointerHoverEvent details) { + double cursorPosition = 0.0; + cursorPosition = details.localPosition.dx; + final double startThumbPosition = sliderType == SliderType.vertical + ? actualTrackRect.bottom - + getFactorFromValue(actualValues.start) * actualTrackRect.height + : getFactorFromValue(actualValues.start) * actualTrackRect.width + + actualTrackRect.left; + final double endThumbPosition = sliderType == SliderType.vertical + ? actualTrackRect.bottom - + getFactorFromValue(actualValues.end) * actualTrackRect.height + : getFactorFromValue(actualValues.end) * actualTrackRect.width + + actualTrackRect.left; + final double startThumbDistance = + (cursorPosition - startThumbPosition).abs(); + final double endThumbDistance = (cursorPosition - endThumbPosition).abs(); + if (endThumbDistance > startThumbDistance) { + activeThumb = SfThumb.start; + } else { + activeThumb = SfThumb.end; + } } @override void attach(PipelineOwner owner) { super.attach(owner); - _overlayStartAnimation?.addListener(markNeedsPaint); - _overlayEndAnimation?.addListener(markNeedsPaint); - _stateAnimation?.addListener(markNeedsPaint); - _tooltipAnimation?.addListener(markNeedsPaint); - _tooltipAnimation?.addStatusListener(_handleTooltipAnimationStatusChange); - _state.startPositionController - ?.addListener(_handlePositionControllerChange); - _state.endPositionController?.addListener(_handlePositionControllerChange); + _overlayStartAnimation.addListener(markNeedsPaint); + _overlayEndAnimation.addListener(markNeedsPaint); + _stateAnimation.addListener(markNeedsPaint); + _tooltipStartAnimation.addListener(markNeedsPaint); + _tooltipStartAnimation + .addStatusListener(_handleTooltipAnimationStatusChange); + _tooltipEndAnimation.addListener(markNeedsPaint); + _tooltipEndAnimation.addStatusListener(_handleTooltipAnimationStatusChange); + _state.startPositionController.addListener(_handlePositionControllerChange); + _state.endPositionController.addListener(_handlePositionControllerChange); if (_state.widget.controller != null) { - _state.widget.controller.addListener(_handleRangeControllerChange); + _state.widget.controller!.addListener(_handleRangeControllerChange); } for (final RenderBox child in children) { child.attach(owner); @@ -2834,18 +2911,21 @@ class _RenderRangeSelector extends RenderBaseSlider { @override void detach() { _deferUpdateTimer?.cancel(); - _overlayStartAnimation?.removeListener(markNeedsPaint); - _overlayEndAnimation?.removeListener(markNeedsPaint); - _stateAnimation?.removeListener(markNeedsPaint); - _tooltipAnimation?.removeListener(markNeedsPaint); - _tooltipAnimation - ?.removeStatusListener(_handleTooltipAnimationStatusChange); + _overlayStartAnimation.removeListener(markNeedsPaint); + _overlayEndAnimation.removeListener(markNeedsPaint); + _stateAnimation.removeListener(markNeedsPaint); + _tooltipStartAnimation.removeListener(markNeedsPaint); + _tooltipStartAnimation + .removeStatusListener(_handleTooltipAnimationStatusChange); + _tooltipEndAnimation.removeListener(markNeedsPaint); + _tooltipEndAnimation + .removeStatusListener(_handleTooltipAnimationStatusChange); _state.startPositionController - ?.removeListener(_handlePositionControllerChange); + .removeListener(_handlePositionControllerChange); _state.endPositionController - ?.removeListener(_handlePositionControllerChange); + .removeListener(_handlePositionControllerChange); if (_state.widget.controller != null) { - _state.widget.controller.removeListener(_handleRangeControllerChange); + _state.widget.controller!.removeListener(_handleRangeControllerChange); } super.detach(); for (final RenderBox child in children) { @@ -2858,11 +2938,36 @@ class _RenderRangeSelector extends RenderBaseSlider { children.forEach(visitor); } + @override + MouseCursor get cursor => MouseCursor.defer; + + @override + PointerEnterEventListener? get onEnter => null; + + @override + // ignore: override_on_non_overriding_member + PointerHoverEventListener get onHover => _handleHover; + + @override + PointerExitEventListener get onExit => _handleExit; + + @override + // ignore: override_on_non_overriding_member + bool get validForMouseTracker => true; + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + if (event is PointerHoverEvent) { + _handleHover(event); + } + super.handleEvent(event, entry); + } + @override bool hitTestSelf(Offset position) => isEnabled; @override - bool hitTestChildren(BoxHitTestResult result, {Offset position}) => false; + bool hitTestChildren(BoxHitTestResult result, {Offset? position}) => false; @override void setupParentData(RenderObject child) { @@ -2875,21 +2980,44 @@ class _RenderRangeSelector extends RenderBaseSlider { void performLayout() { double childHeight = 0.0; double childWidth = 0.0; - final double bottomEdgeInset = math.max( - trackOffset.dy + maxTrackHeight / 2, - maxTrackHeight + + final double minTrackHeight = math.min( + sliderThemeData.activeTrackHeight, sliderThemeData.inactiveTrackHeight); + final Offset trackCenterLeft = trackOffset; + final double elementsHeightWithoutChild = elementsActualHeight; + + double elementsHeightAfterRenderedChild = math.max( + trackCenterLeft.dy + minTrackHeight / 2, + maxTrackHeight / 2 + + minTrackHeight / 2 + math.max(actualTickHeight, actualMinorTickHeight) + actualLabelHeight); + + if (constraints.maxHeight < elementsHeightWithoutChild) { + final double actualChildHeight = + elementsHeightWithoutChild - elementsHeightAfterRenderedChild; + final double spaceLeftInActualLayoutHeight = + (elementsHeightAfterRenderedChild - constraints.maxHeight); + // Reduce the [elementsHeightAfterRenderedChild] from the + // actual child height and remaining space in actual layout height to + // match the given constraints height. + elementsHeightAfterRenderedChild = elementsHeightAfterRenderedChild - + spaceLeftInActualLayoutHeight - + actualChildHeight; + } + if (child != null) { final double maxRadius = trackOffset.dx; final BoxConstraints childConstraints = constraints.deflate( EdgeInsets.only( - left: maxRadius, right: maxRadius, bottom: bottomEdgeInset)); - child.layout(childConstraints, parentUsesSize: true); - final BoxParentData childParentData = child.parentData; + left: maxRadius, + right: maxRadius, + bottom: elementsHeightAfterRenderedChild)); + child!.layout(childConstraints, parentUsesSize: true); + // ignore: avoid_as + final BoxParentData childParentData = child!.parentData as BoxParentData; childParentData.offset = Offset(maxRadius, 0); - childHeight = child.size.height; - childWidth = child.size.width; + childHeight = child!.size.height; + childWidth = child!.size.width; } final BoxConstraints contentConstraints = BoxConstraints.tightFor( @@ -2902,12 +3030,7 @@ class _RenderRangeSelector extends RenderBaseSlider { ? (childWidth + 2 * trackOffset.dx) : minTrackWidth + 2 * trackOffset.dx; - final double actualHeight = childHeight - - adjustTrackY / 2 + - elementsActualHeight + - maxTrackHeight / 2 - - (childHeight > trackOffset.dy ? trackOffset.dy : 0.0); - + final double actualHeight = childHeight + elementsHeightAfterRenderedChild; size = Size( constraints.hasBoundedWidth && (constraints.maxWidth < actualWidth) ? constraints.maxWidth @@ -2924,9 +3047,10 @@ class _RenderRangeSelector extends RenderBaseSlider { void paint(PaintingContext context, Offset offset) { double childHeight = 0.0; if (child != null) { - final BoxParentData childParentData = child.parentData; - context.paintChild(child, childParentData.offset + offset); - childHeight = child.size.height; + // ignore: avoid_as + final BoxParentData childParentData = child!.parentData as BoxParentData; + context.paintChild(child!, childParentData.offset + offset); + childHeight = child!.size.height; if (childHeight >= constraints.maxHeight) { childHeight -= elementsActualHeight - math.max(actualOverlaySize.height, actualThumbSize.height) / 2; @@ -2950,14 +3074,15 @@ class _RenderRangeSelector extends RenderBaseSlider { Offset(trackRect.left + thumbStartPosition, trackRect.center.dy); final Offset endThumbCenter = Offset(trackRect.left + thumbEndPosition, trackRect.center.dy); - trackShape.paint( context, actualTrackOffset, null, startThumbCenter, endThumbCenter, parentBox: this, themeData: sliderThemeData, currentValues: _values, enableAnimation: _stateAnimation, - textDirection: textDirection); + textDirection: textDirection, + activePaint: null, + inactivePaint: null); if (showLabels || showTicks || showDivisors) { drawLabelsTicksAndDivisors(context, trackRect, offset, null, @@ -2970,33 +3095,105 @@ class _RenderRangeSelector extends RenderBaseSlider { actualTrackOffset, trackRect); } + /// Describe the semantics of the start thumb. + SemanticsNode? _startSemanticsNode = SemanticsNode(); + + /// Describe the semantics of the end thumb. + SemanticsNode? _endSemanticsNode = SemanticsNode(); + + // Create the semantics configuration for a single value. + SemanticsConfiguration _createSemanticsConfiguration( + dynamic value, + dynamic increasedValue, + dynamic decreasedValue, + SfThumb thumb, + VoidCallback increaseAction, + VoidCallback decreaseAction, + ) { + final SemanticsConfiguration config = SemanticsConfiguration(); + config.isEnabled = isEnabled; + config.textDirection = textDirection; + if (isEnabled) { + config.onIncrease = increaseAction; + config.onDecrease = decreaseAction; + } + + if (semanticFormatterCallback != null) { + config.value = semanticFormatterCallback!(value, thumb); + config.increasedValue = semanticFormatterCallback!(increasedValue, thumb); + config.decreasedValue = semanticFormatterCallback!(decreasedValue, thumb); + } else { + final String thumbValue = thumb.toString().split('.').last; + config.value = 'the $thumbValue value is $value'; + config.increasedValue = 'the $thumbValue value is $increasedValue'; + config.decreasedValue = 'the $thumbValue value is $decreasedValue'; + } + return config; + } + + @override + void assembleSemanticsNode( + SemanticsNode node, + SemanticsConfiguration config, + Iterable children, + ) { + assert(children.isEmpty); + final SemanticsConfiguration startSemanticsConfiguration = + _createSemanticsConfiguration( + values.start, + _increasedStartValue, + _decreasedStartValue, + SfThumb.start, + _increaseStartAction, + _decreaseStartAction, + ); + final SemanticsConfiguration endSemanticsConfiguration = + _createSemanticsConfiguration( + values.end, + _increasedEndValue, + _decreasedEndValue, + SfThumb.end, + _increaseEndAction, + _decreaseEndAction, + ); + // Split the semantics node area between the start and end nodes. + final Rect leftRect = + Rect.fromPoints(node.rect.topLeft, node.rect.bottomCenter); + final Rect rightRect = + Rect.fromPoints(node.rect.topCenter, node.rect.bottomRight); + switch (textDirection) { + case TextDirection.ltr: + _startSemanticsNode!.rect = leftRect; + _endSemanticsNode!.rect = rightRect; + break; + case TextDirection.rtl: + _startSemanticsNode!.rect = rightRect; + _endSemanticsNode!.rect = leftRect; + break; + } + + _startSemanticsNode!.updateWith(config: startSemanticsConfiguration); + _endSemanticsNode!.updateWith(config: endSemanticsConfiguration); + + final List finalChildren = [ + _startSemanticsNode!, + _endSemanticsNode!, + ]; + + node.updateWith(config: config, childrenInInversePaintOrder: finalChildren); + } + + @override + void clearSemantics() { + super.clearSemantics(); + _startSemanticsNode = null; + _endSemanticsNode = null; + } + @override void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); - config.isSemanticBoundary = isEnabled; - if (isEnabled) { - config.textDirection = textDirection; - config.customSemanticsActions = { - const CustomSemanticsAction(label: 'Decrease start value'): - _decreaseStartAction, - const CustomSemanticsAction(label: 'Increase start value'): - _increaseStartAction, - const CustomSemanticsAction(label: 'Decrease end value'): - _decreaseEndAction, - const CustomSemanticsAction(label: 'Increase end value'): - _increaseEndAction, - }; - - assert(actualValues.start <= actualValues.end); - if (_semanticFormatterCallback != null) { - config.value = _semanticFormatterCallback(values); - } else { - config.value = - // ignore: lines_longer_than_80_chars - 'The start value is ${values.start} and the end value is ${values.end}'; - } - } } @override diff --git a/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart b/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart index fb35197af..7ebaf2dff 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart @@ -4,11 +4,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:intl/intl.dart' show DateFormat, NumberFormat; import 'package:flutter/material.dart'; -import 'package:flutter/gestures.dart' - show - GestureArenaTeam, - TapGestureRecognizer, - HorizontalDragGestureRecognizer; +import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/theme.dart'; @@ -21,7 +17,8 @@ import 'slider_shapes.dart'; /// A Material Design range slider. /// /// Used to select a range between [min] and [max]. -/// It supports both numeric and date ranges. +/// It supports horizontal and vertical orientations. +/// It also supports both numeric and date ranges. /// /// The range slider elements are: /// @@ -115,17 +112,17 @@ import 'slider_shapes.dart'; /// date labels. /// * [SfRangeSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfRangeSliderThemeData-class.html), for customizing the visual appearance of the range slider. class SfRangeSlider extends StatefulWidget { - /// Creates a [SfRangeSlider]. + /// Creates a horizontal [SfRangeSlider]. const SfRangeSlider( - {Key key, + {Key? key, this.min = 0.0, this.max = 1.0, - @required this.values, - @required this.onChanged, + required this.values, + required this.onChanged, this.interval, this.stepSize, this.stepDuration, - this.minorTicksPerInterval, + this.minorTicksPerInterval = 0, this.showTicks = false, this.showLabels = false, this.showDivisors = false, @@ -133,28 +130,120 @@ class SfRangeSlider extends StatefulWidget { this.enableIntervalSelection = false, this.inactiveColor, this.activeColor, - this.labelPlacement, + this.labelPlacement = LabelPlacement.onTicks, this.numberFormat, this.dateFormat, this.dateIntervalType, this.labelFormatterCallback, this.tooltipTextFormatterCallback, this.semanticFormatterCallback, - this.trackShape, - this.divisorShape, - this.overlayShape, - this.thumbShape, - this.tickShape, - this.minorTickShape, - this.tooltipShape, + this.trackShape = const SfTrackShape(), + this.divisorShape = const SfDivisorShape(), + this.overlayShape = const SfOverlayShape(), + this.thumbShape = const SfThumbShape(), + this.tickShape = const SfTickShape(), + this.minorTickShape = const SfMinorTickShape(), + this.tooltipShape = const SfRectangularTooltipShape(), this.startThumbIcon, this.endThumbIcon}) - : assert(min != null), + : _sliderType = SliderType.horizontal, + _tooltipPosition = null, + assert(min != null), assert(max != null), assert(min != max), assert(interval == null || interval > 0), super(key: key); + /// Creates a vertical [SfRangeSlider]. + /// + /// ## TooltipPosition + /// + /// Enables tooltip in left or right position for vertical range slider. + /// + /// ## Example + /// + /// This snippet shows how to create a vertical SfRangeSlider with + /// right side tooltip + /// + /// ```dart + /// SfRangeValues _values = SfRangeValues (30,60)); + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: Center( + /// child: SfRangeSlider.vertical( + /// min: 10.0, + /// max: 100.0, + /// values: _values, + /// enableTooltip: true, + /// tooltipPosition: SliderTooltipPosition.right, + /// onChanged: (dynamic newValues) { + /// setState(() { + /// _values = newValues; + /// }); + /// }, + /// ) + /// ) + /// ) + /// ); + /// } + /// ``` + /// + /// See also: + /// + /// * Check the default constructor for horizontal range slider. + const SfRangeSlider.vertical( + {Key? key, + this.min = 0.0, + this.max = 1.0, + required this.values, + required this.onChanged, + this.interval, + this.stepSize, + this.stepDuration, + this.minorTicksPerInterval = 0, + this.showTicks = false, + this.showLabels = false, + this.showDivisors = false, + this.enableTooltip = false, + this.enableIntervalSelection = false, + this.inactiveColor, + this.activeColor, + this.labelPlacement = LabelPlacement.onTicks, + this.numberFormat, + this.dateFormat, + this.dateIntervalType, + this.labelFormatterCallback, + this.tooltipTextFormatterCallback, + this.semanticFormatterCallback, + this.trackShape = const SfTrackShape(), + this.divisorShape = const SfDivisorShape(), + this.overlayShape = const SfOverlayShape(), + this.thumbShape = const SfThumbShape(), + this.tickShape = const SfTickShape(), + this.minorTickShape = const SfMinorTickShape(), + this.tooltipShape = const SfRectangularTooltipShape(), + this.startThumbIcon, + this.endThumbIcon, + SliderTooltipPosition tooltipPosition = SliderTooltipPosition.left}) + : _sliderType = SliderType.vertical, + _tooltipPosition = tooltipPosition, + assert(tooltipShape is! SfPaddleTooltipShape), + assert(min != null), + assert(max != null), + assert(min != max), + assert(interval == null || interval > 0), + super(key: key); + + /// This is used to determine the type of the range slider which is + /// horizontal or vertical. + final SliderType _sliderType; + + /// This is only applicable for vertical range sliders. + final SliderTooltipPosition? _tooltipPosition; + /// The minimum value that the user can select. /// /// Defaults to 0.0. Must be less than the [max]. @@ -246,7 +335,7 @@ class SfRangeSlider extends StatefulWidget { /// ) /// ``` /// - final ValueChanged onChanged; + final ValueChanged? onChanged; /// Splits the range slider into given interval. /// It is mandatory if labels, major ticks and divisors are needed. @@ -317,7 +406,7 @@ class SfRangeSlider extends StatefulWidget { /// * [showDivisors], to render divisors at given interval. /// * [showTicks], to render major ticks at given interval. /// * [showLabels], to render labels at given interval. - final double interval; + final double? interval; /// Option to select discrete values. /// @@ -345,7 +434,7 @@ class SfRangeSlider extends StatefulWidget { /// }, /// ) /// ``` - final double stepSize; + final double? stepSize; /// Option to select discrete date values. /// @@ -389,7 +478,7 @@ class SfRangeSlider extends StatefulWidget { /// * [interval], for setting the interval. /// * [dateIntervalType], for changing the interval type. /// * [dateFormat] for formatting the date labels. - final SliderStepDuration stepDuration; + final SliderStepDuration? stepDuration; /// Number of smaller ticks between two major ticks. /// @@ -631,7 +720,7 @@ class SfRangeSlider extends StatefulWidget { /// /// * [SfRangeSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfRangeSliderThemeData-class.html), for customizing the individual /// inactive range slider element’s visual. - final Color inactiveColor; + final Color? inactiveColor; /// Color applied to the active track, thumb, overlay, and inactive divisors. /// @@ -661,7 +750,7 @@ class SfRangeSlider extends StatefulWidget { /// See also: /// /// * [SfRangeSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfRangeSliderThemeData-class.html), for customizing the individual active range slider element’s visual. - final Color activeColor; + final Color? activeColor; /// Option to place the labels either between the major ticks or /// on the major ticks. @@ -719,7 +808,7 @@ class SfRangeSlider extends StatefulWidget { /// See also: /// /// * [labelFormatterCallback], for formatting the numeric and date labels. - final NumberFormat numberFormat; + final NumberFormat? numberFormat; /// Formats the date labels. It is mandatory for date [SfRangeSlider]. /// @@ -759,7 +848,7 @@ class SfRangeSlider extends StatefulWidget { /// * [numberFormat], for formatting the numeric labels. /// * [labelFormatterCallback], for formatting the numeric and date label. /// * [dateIntervalType], for changing the interval type. - final DateFormat dateFormat; + final DateFormat? dateFormat; /// The type of date interval. It is mandatory for date [SfRangeSlider]. /// @@ -792,7 +881,7 @@ class SfRangeSlider extends StatefulWidget { /// }, /// ) /// ``` - final DateIntervalType dateIntervalType; + final DateIntervalType? dateIntervalType; /// Signature for formatting or changing the whole numeric or date label text. /// @@ -823,7 +912,7 @@ class SfRangeSlider extends StatefulWidget { /// }, /// ) /// ``` - final LabelFormatterCallback labelFormatterCallback; + final LabelFormatterCallback? labelFormatterCallback; /// Signature for formatting or changing the whole tooltip label text. /// @@ -859,7 +948,7 @@ class SfRangeSlider extends StatefulWidget { /// }, /// ) /// ``` - final TooltipTextFormatterCallback tooltipTextFormatterCallback; + final TooltipTextFormatterCallback? tooltipTextFormatterCallback; /// The callback used to create a semantic value from the slider's values. /// @@ -878,8 +967,8 @@ class SfRangeSlider extends StatefulWidget { /// values: _values, /// interval: 20, /// stepSize: 10, - /// semanticFormatterCallback: (SfRangeValues values) { - /// return '${values.start} and ${values.end}'; + /// semanticFormatterCallback: (dynamic value, SfThumb thumb) { + /// return 'The $thumb value is ${value}'; /// } /// onChanged: (dynamic newValues) { /// setState(() { @@ -888,7 +977,7 @@ class SfRangeSlider extends StatefulWidget { /// }, /// ) /// ``` - final SfRangeSliderSemanticFormatterCallback semanticFormatterCallback; + final RangeSliderSemanticFormatterCallback? semanticFormatterCallback; /// Base class for [SfRangeSlider] track shapes. final SfTrackShape trackShape; @@ -971,7 +1060,7 @@ class SfRangeSlider extends StatefulWidget { /// /// * [thumbShape], for customizing the thumb shape. /// * [SfRangeSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfRangeSliderThemeData-class.html), for customizing the individual active range slider element’s visual. - final Widget startThumbIcon; + final Widget? startThumbIcon; /// Sets the widget inside the right thumb. /// @@ -1011,7 +1100,7 @@ class SfRangeSlider extends StatefulWidget { /// /// * [thumbShape], for customizing the thumb shape. /// * [SfRangeSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfRangeSliderThemeData-class.html), for customizing the individual active range slider element’s visual. - final Widget endThumbIcon; + final Widget? endThumbIcon; @override _SfRangeSliderState createState() => _SfRangeSliderState(); @@ -1030,7 +1119,7 @@ class SfRangeSlider extends StatefulWidget { properties.add(DoubleProperty('interval', interval)); properties.add(DoubleProperty('stepSize', stepSize)); if (stepDuration != null) { - properties.add(stepDuration.toDiagnosticsNode(name: 'stepDuration')); + properties.add(stepDuration!.toDiagnosticsNode(name: 'stepDuration')); } properties.add(IntProperty('minorTicksPerInterval', minorTicksPerInterval)); properties.add(FlagProperty('showTicks', @@ -1064,11 +1153,11 @@ class SfRangeSlider extends StatefulWidget { .add(EnumProperty('labelPlacement', labelPlacement)); properties .add(DiagnosticsProperty('numberFormat', numberFormat)); - if (values.start.runtimeType == DateTime) { + if (values.start.runtimeType == DateTime && dateFormat != null) { properties.add(StringProperty( 'dateFormat', 'Formatted value is ' + - (dateFormat.format(values.start)).toString())); + (dateFormat!.format(values.start)).toString())); } properties.add( EnumProperty('dateIntervalType', dateIntervalType)); @@ -1076,26 +1165,27 @@ class SfRangeSlider extends StatefulWidget { 'tooltipTextFormatterCallback', tooltipTextFormatterCallback)); properties.add(ObjectFlagProperty.has( 'labelFormatterCallback', labelFormatterCallback)); - properties.add(ObjectFlagProperty>.has( + properties.add(ObjectFlagProperty.has( 'semanticFormatterCallback', semanticFormatterCallback)); } } class _SfRangeSliderState extends State with TickerProviderStateMixin { - AnimationController overlayStartController; - AnimationController overlayEndController; - AnimationController stateController; - AnimationController startPositionController; - AnimationController endPositionController; - AnimationController tooltipAnimationController; - Timer tooltipDelayTimer; - RangeController rangeController; + late AnimationController overlayStartController; + late AnimationController overlayEndController; + late AnimationController stateController; + late AnimationController startPositionController; + late AnimationController endPositionController; + late AnimationController tooltipAnimationStartController; + late AnimationController tooltipAnimationEndController; + late RangeController rangeController; final Duration duration = const Duration(milliseconds: 100); + Timer? tooltipDelayTimer; void _onChanged(SfRangeValues values) { if (values != widget.values) { - widget.onChanged(values); + widget.onChanged!(values); } } @@ -1110,7 +1200,7 @@ class _SfRangeSliderState extends State SfRangeSliderThemeData _getRangeSliderThemeData( ThemeData themeData, bool isActive) { SfRangeSliderThemeData rangeSliderThemeData = - SfRangeSliderTheme.of(context); + SfRangeSliderTheme.of(context)!; final double minTrackHeight = math.min( rangeSliderThemeData.activeTrackHeight, rangeSliderThemeData.inactiveTrackHeight); @@ -1120,23 +1210,19 @@ class _SfRangeSliderState extends State rangeSliderThemeData = rangeSliderThemeData.copyWith( activeTrackHeight: rangeSliderThemeData.activeTrackHeight, inactiveTrackHeight: rangeSliderThemeData.inactiveTrackHeight, - tickSize: rangeSliderThemeData.tickSize, - minorTickSize: rangeSliderThemeData.minorTickSize, tickOffset: rangeSliderThemeData.tickOffset, - labelOffset: rangeSliderThemeData.labelOffset ?? - (widget.showTicks ? const Offset(0.0, 5.0) : const Offset(0.0, 13.0)), inactiveLabelStyle: rangeSliderThemeData.inactiveLabelStyle ?? - themeData.textTheme.bodyText1.copyWith( + themeData.textTheme.bodyText1!.copyWith( color: isActive - ? themeData.textTheme.bodyText1.color.withOpacity(0.87) + ? themeData.textTheme.bodyText1!.color!.withOpacity(0.87) : themeData.colorScheme.onSurface.withOpacity(0.32)), activeLabelStyle: rangeSliderThemeData.activeLabelStyle ?? - themeData.textTheme.bodyText1.copyWith( + themeData.textTheme.bodyText1!.copyWith( color: isActive - ? themeData.textTheme.bodyText1.color.withOpacity(0.87) + ? themeData.textTheme.bodyText1!.color!.withOpacity(0.87) : themeData.colorScheme.onSurface.withOpacity(0.32)), tooltipTextStyle: rangeSliderThemeData.tooltipTextStyle ?? - themeData.textTheme.bodyText1.copyWith( + themeData.textTheme.bodyText1!.copyWith( color: rangeSliderThemeData.brightness == Brightness.light ? const Color.fromRGBO(255, 255, 255, 1) : const Color.fromRGBO(0, 0, 0, 1)), @@ -1211,7 +1297,25 @@ class _SfRangeSliderState extends State rangeSliderThemeData.inactiveDivisorStrokeWidth, ); - return rangeSliderThemeData; + if (widget._sliderType == SliderType.vertical) { + return rangeSliderThemeData.copyWith( + tickSize: rangeSliderThemeData.tickSize ?? const Size(8.0, 1.0), + minorTickSize: + rangeSliderThemeData.minorTickSize ?? const Size(5.0, 1.0), + labelOffset: rangeSliderThemeData.labelOffset ?? + (widget.showTicks + ? const Offset(5.0, 0.0) + : const Offset(13.0, 0.0))); + } else { + return rangeSliderThemeData.copyWith( + tickSize: rangeSliderThemeData.tickSize ?? const Size(1.0, 8.0), + minorTickSize: + rangeSliderThemeData.minorTickSize ?? const Size(1.0, 5.0), + labelOffset: rangeSliderThemeData.labelOffset ?? + (widget.showTicks + ? const Offset(0.0, 5.0) + : const Offset(0.0, 13.0))); + } } @override @@ -1225,7 +1329,9 @@ class _SfRangeSliderState extends State AnimationController(duration: Duration.zero, vsync: this); endPositionController = AnimationController(duration: Duration.zero, vsync: this); - tooltipAnimationController = + tooltipAnimationStartController = + AnimationController(vsync: this, duration: duration); + tooltipAnimationEndController = AnimationController(vsync: this, duration: duration); stateController.value = widget.onChanged != null && (widget.min != widget.max) ? 1.0 : 0.0; @@ -1233,13 +1339,13 @@ class _SfRangeSliderState extends State @override void dispose() { - overlayStartController?.dispose(); - overlayEndController?.dispose(); - stateController?.dispose(); - startPositionController?.dispose(); - endPositionController?.dispose(); - tooltipAnimationController?.dispose(); - + overlayStartController.dispose(); + overlayEndController.dispose(); + stateController.dispose(); + startPositionController.dispose(); + endPositionController.dispose(); + tooltipAnimationStartController.dispose(); + tooltipAnimationEndController.dispose(); super.dispose(); } @@ -1258,7 +1364,7 @@ class _SfRangeSliderState extends State interval: widget.interval, stepSize: widget.stepSize, stepDuration: widget.stepDuration, - minorTicksPerInterval: widget.minorTicksPerInterval ?? 0, + minorTicksPerInterval: widget.minorTicksPerInterval, showTicks: widget.showTicks, showLabels: widget.showLabels, showDivisors: widget.showDivisors, @@ -1267,7 +1373,7 @@ class _SfRangeSliderState extends State inactiveColor: widget.inactiveColor ?? themeData.primaryColor.withOpacity(0.24), activeColor: widget.activeColor ?? themeData.primaryColor, - labelPlacement: widget.labelPlacement ?? LabelPlacement.onTicks, + labelPlacement: widget.labelPlacement, numberFormat: widget.numberFormat ?? NumberFormat('#.##'), dateIntervalType: widget.dateIntervalType, dateFormat: widget.dateFormat, @@ -1276,15 +1382,17 @@ class _SfRangeSliderState extends State tooltipTextFormatterCallback: widget.tooltipTextFormatterCallback ?? _getFormattedTooltipText, semanticFormatterCallback: widget.semanticFormatterCallback, - trackShape: widget.trackShape ?? SfTrackShape(), - divisorShape: widget.divisorShape ?? SfDivisorShape(), - overlayShape: widget.overlayShape ?? SfOverlayShape(), - thumbShape: widget.thumbShape ?? SfThumbShape(), - tickShape: widget.tickShape ?? SfTickShape(), - minorTickShape: widget.minorTickShape ?? SfMinorTickShape(), - tooltipShape: widget.tooltipShape ?? SfRectangularTooltipShape(), + trackShape: widget.trackShape, + divisorShape: widget.divisorShape, + overlayShape: widget.overlayShape, + thumbShape: widget.thumbShape, + tickShape: widget.tickShape, + minorTickShape: widget.minorTickShape, + tooltipShape: widget.tooltipShape, rangeSliderThemeData: _getRangeSliderThemeData(themeData, isActive), state: this, + sliderType: widget._sliderType, + tooltipPosition: widget._tooltipPosition, startThumbIcon: widget.startThumbIcon, endThumbIcon: widget.endThumbIcon, ); @@ -1293,49 +1401,52 @@ class _SfRangeSliderState extends State class _RangeSliderRenderObjectWidget extends RenderObjectWidget { const _RangeSliderRenderObjectWidget( - {Key key, - this.min, - this.max, - this.values, - this.onChanged, - this.interval, - this.stepSize, - this.stepDuration, - this.minorTicksPerInterval, - this.showTicks, - this.showLabels, - this.showDivisors, - this.enableTooltip, - this.enableIntervalSelection, - this.inactiveColor, - this.activeColor, - this.labelPlacement, - this.numberFormat, - this.dateFormat, - this.dateIntervalType, - this.labelFormatterCallback, - this.tooltipTextFormatterCallback, - this.semanticFormatterCallback, - this.trackShape, - this.divisorShape, - this.overlayShape, - this.thumbShape, - this.tickShape, - this.minorTickShape, - this.tooltipShape, - this.rangeSliderThemeData, - this.startThumbIcon, - this.endThumbIcon, - this.state}) + {Key? key, + required this.min, + required this.max, + required this.values, + required this.onChanged, + required this.interval, + required this.stepSize, + required this.stepDuration, + required this.minorTicksPerInterval, + required this.showTicks, + required this.showLabels, + required this.showDivisors, + required this.enableTooltip, + required this.enableIntervalSelection, + required this.inactiveColor, + required this.activeColor, + required this.labelPlacement, + required this.numberFormat, + required this.dateFormat, + required this.dateIntervalType, + required this.labelFormatterCallback, + required this.tooltipTextFormatterCallback, + required this.semanticFormatterCallback, + required this.trackShape, + required this.divisorShape, + required this.overlayShape, + required this.thumbShape, + required this.tickShape, + required this.minorTickShape, + required this.tooltipShape, + required this.rangeSliderThemeData, + required this.startThumbIcon, + required this.endThumbIcon, + required this.state, + required this.tooltipPosition, + required this.sliderType}) : super(key: key); - + final SliderType sliderType; + final SliderTooltipPosition? tooltipPosition; final dynamic min; final dynamic max; final SfRangeValues values; - final ValueChanged onChanged; - final double interval; - final double stepSize; - final SliderStepDuration stepDuration; + final ValueChanged? onChanged; + final double? interval; + final double? stepSize; + final SliderStepDuration? stepDuration; final int minorTicksPerInterval; final bool showTicks; @@ -1349,12 +1460,12 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { final LabelPlacement labelPlacement; final NumberFormat numberFormat; - final DateIntervalType dateIntervalType; - final DateFormat dateFormat; + final DateIntervalType? dateIntervalType; + final DateFormat? dateFormat; final SfRangeSliderThemeData rangeSliderThemeData; final LabelFormatterCallback labelFormatterCallback; final TooltipTextFormatterCallback tooltipTextFormatterCallback; - final SfRangeSliderSemanticFormatterCallback semanticFormatterCallback; + final RangeSliderSemanticFormatterCallback? semanticFormatterCallback; final SfDivisorShape divisorShape; final SfTrackShape trackShape; final SfTickShape tickShape; @@ -1362,8 +1473,8 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { final SfOverlayShape overlayShape; final SfThumbShape thumbShape; final SfTooltipShape tooltipShape; - final Widget startThumbIcon; - final Widget endThumbIcon; + final Widget? startThumbIcon; + final Widget? endThumbIcon; final _SfRangeSliderState state; @override @@ -1402,6 +1513,8 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { minorTickShape: minorTickShape, tooltipShape: tooltipShape, sliderThemeData: rangeSliderThemeData, + sliderType: sliderType, + tooltipPosition: tooltipPosition, textDirection: Directionality.of(context), mediaQueryData: MediaQuery.of(context), state: state, @@ -1425,8 +1538,6 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { ..showDivisors = showDivisors ..enableTooltip = enableTooltip ..enableIntervalSelection = enableIntervalSelection - ..inactiveColor = inactiveColor - ..activeColor = activeColor ..labelPlacement = labelPlacement ..numberFormat = numberFormat ..dateFormat = dateFormat @@ -1441,6 +1552,7 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { ..tickShape = tickShape ..minorTickShape = minorTickShape ..tooltipShape = tooltipShape + ..tooltipPosition = tooltipPosition ..sliderThemeData = rangeSliderThemeData ..textDirection = Directionality.of(context) ..mediaQueryData = MediaQuery.of(context); @@ -1456,27 +1568,18 @@ class _RenderRangeSliderElement extends RenderObjectElement { final Map _childToSlot = {}; @override - _RangeSliderRenderObjectWidget get widget => super.widget; + _RangeSliderRenderObjectWidget get widget => + // ignore: avoid_as + super.widget as _RangeSliderRenderObjectWidget; @override - _RenderRangeSlider get renderObject => super.renderObject; + _RenderRangeSlider get renderObject => + // ignore: avoid_as + super.renderObject as _RenderRangeSlider; - void _mountChild(Widget newWidget, ChildElements slot) { - final Element oldChild = _slotToChild[slot]; - final Element newChild = updateChild(oldChild, newWidget, slot); - if (oldChild != null) { - _slotToChild.remove(slot); - _childToSlot.remove(oldChild); - } - if (newChild != null) { - _slotToChild[slot] = newChild; - _childToSlot[newChild] = slot; - } - } - - void _updateChild(Widget widget, ChildElements slot) { - final Element oldChild = _slotToChild[slot]; - final Element newChild = updateChild(oldChild, widget, slot); + void _updateChild(Widget? widget, ChildElements slot) { + final Element? oldChild = _slotToChild[slot]; + final Element? newChild = updateChild(oldChild, widget, slot); if (oldChild != null) { _childToSlot.remove(oldChild); _slotToChild.remove(slot); @@ -1487,13 +1590,15 @@ class _RenderRangeSliderElement extends RenderObjectElement { } } - void _updateRenderObject(RenderObject child, ChildElements slot) { + void _updateRenderObject(RenderObject? child, ChildElements slot) { switch (slot) { case ChildElements.startThumbIcon: - renderObject.startThumbIcon = child; + // ignore: avoid_as + renderObject.startThumbIcon = child as RenderBox?; break; case ChildElements.endThumbIcon: - renderObject.endThumbIcon = child; + // ignore: avoid_as + renderObject.endThumbIcon = child as RenderBox?; break; case ChildElements.child: break; @@ -1506,10 +1611,10 @@ class _RenderRangeSliderElement extends RenderObjectElement { } @override - void mount(Element parent, dynamic newSlot) { + void mount(Element? parent, dynamic newSlot) { super.mount(parent, newSlot); - _mountChild(widget.startThumbIcon, ChildElements.startThumbIcon); - _mountChild(widget.endThumbIcon, ChildElements.endThumbIcon); + _updateChild(widget.startThumbIcon, ChildElements.startThumbIcon); + _updateChild(widget.endThumbIcon, ChildElements.endThumbIcon); } @override @@ -1521,7 +1626,7 @@ class _RenderRangeSliderElement extends RenderObjectElement { } @override - void insertRenderObjectChild(RenderObject child, dynamic slotValue) { + void insertRenderObjectChild(RenderObject child, ChildElements slotValue) { assert(child is RenderBox); assert(slotValue is ChildElements); final ChildElements slot = slotValue; @@ -1531,10 +1636,10 @@ class _RenderRangeSliderElement extends RenderObjectElement { } @override - void removeRenderObjectChild(RenderObject child, dynamic slot) { + void removeRenderObjectChild(RenderObject child, ChildElements slot) { assert(child is RenderBox); assert(renderObject.childToSlot.keys.contains(child)); - _updateRenderObject(null, renderObject.childToSlot[child]); + _updateRenderObject(null, renderObject.childToSlot[child]!); assert(!renderObject.childToSlot.keys.contains(child)); assert(!renderObject.slotToChild.keys.contains(slot)); } @@ -1546,42 +1651,49 @@ class _RenderRangeSliderElement extends RenderObjectElement { } } -class _RenderRangeSlider extends RenderBaseSlider { +class _RenderRangeSlider extends RenderBaseSlider + implements MouseTrackerAnnotation { _RenderRangeSlider({ - dynamic min, - dynamic max, - SfRangeValues values, - ValueChanged onChanged, - double interval, - double stepSize, - SliderStepDuration stepDuration, - int minorTicksPerInterval, - bool showTicks, - bool showLabels, - bool showDivisors, - bool enableTooltip, - bool enableIntervalSelection, - Color inactiveColor, - Color activeColor, - LabelPlacement labelPlacement, - NumberFormat numberFormat, - DateFormat dateFormat, - DateIntervalType dateIntervalType, - LabelFormatterCallback labelFormatterCallback, - TooltipTextFormatterCallback tooltipTextFormatterCallback, - SfRangeSliderSemanticFormatterCallback semanticFormatterCallback, - SfTrackShape trackShape, - SfDivisorShape divisorShape, - SfOverlayShape overlayShape, - SfThumbShape thumbShape, - SfTickShape tickShape, - SfTickShape minorTickShape, - SfTooltipShape tooltipShape, - SfRangeSliderThemeData sliderThemeData, - TextDirection textDirection, - MediaQueryData mediaQueryData, - @required _SfRangeSliderState state, - }) : _state = state, + required dynamic min, + required dynamic max, + required SfRangeValues values, + required ValueChanged? onChanged, + required double? interval, + required double? stepSize, + required SliderStepDuration? stepDuration, + required int minorTicksPerInterval, + required bool showTicks, + required bool showLabels, + required bool showDivisors, + required bool enableTooltip, + required bool enableIntervalSelection, + required Color inactiveColor, + required Color activeColor, + required LabelPlacement labelPlacement, + required NumberFormat numberFormat, + required DateFormat? dateFormat, + required DateIntervalType? dateIntervalType, + required LabelFormatterCallback labelFormatterCallback, + required TooltipTextFormatterCallback tooltipTextFormatterCallback, + required RangeSliderSemanticFormatterCallback? semanticFormatterCallback, + required SfTrackShape trackShape, + required SfDivisorShape divisorShape, + required SfOverlayShape overlayShape, + required SfThumbShape thumbShape, + required SfTickShape tickShape, + required SfTickShape minorTickShape, + required SfTooltipShape tooltipShape, + required SfRangeSliderThemeData sliderThemeData, + required SliderType sliderType, + required SliderTooltipPosition? tooltipPosition, + required TextDirection textDirection, + required MediaQueryData mediaQueryData, + required _SfRangeSliderState state, + }) : _state = state, + _values = values, + _onChanged = onChanged, + _enableIntervalSelection = enableIntervalSelection, + _semanticFormatterCallback = semanticFormatterCallback, super( min: min, max: max, @@ -1607,22 +1719,29 @@ class _RenderRangeSlider extends RenderBaseSlider { minorTickShape: minorTickShape, tooltipShape: tooltipShape, sliderThemeData: sliderThemeData, + sliderType: sliderType, + tooltipPosition: tooltipPosition, textDirection: textDirection, mediaQueryData: mediaQueryData) { - _values = values; - _onChanged = onChanged; - _enableIntervalSelection = enableIntervalSelection; - _semanticFormatterCallback = semanticFormatterCallback; - _inactiveColor = inactiveColor; - _activeColor = activeColor; final GestureArenaTeam team = GestureArenaTeam(); - dragGestureRecognizer = HorizontalDragGestureRecognizer() - ..team = team - ..onStart = _onDragStart - ..onUpdate = _onDragUpdate - ..onEnd = _onDragEnd - ..onCancel = _onDragCancel; + if (sliderType == SliderType.horizontal) { + horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer() + ..team = team + ..onStart = _onDragStart + ..onUpdate = _onDragUpdate + ..onEnd = _onDragEnd + ..onCancel = _onDragCancel; + } + + if (sliderType == SliderType.vertical) { + verticalDragGestureRecognizer = VerticalDragGestureRecognizer() + ..team = team + ..onStart = _onVerticalDragStart + ..onUpdate = _onVerticalDragUpdate + ..onEnd = _onVerticalDragEnd + ..onCancel = _onVerticalDragCancel; + } tapGestureRecognizer = TapGestureRecognizer() ..team = team @@ -1638,8 +1757,12 @@ class _RenderRangeSlider extends RenderBaseSlider { _stateAnimation = CurvedAnimation( parent: _state.stateController, curve: Curves.easeInOut); - _tooltipAnimation = CurvedAnimation( - parent: _state.tooltipAnimationController, curve: Curves.fastOutSlowIn); + _tooltipStartAnimation = CurvedAnimation( + parent: _state.tooltipAnimationStartController, + curve: Curves.fastOutSlowIn); + _tooltipEndAnimation = CurvedAnimation( + parent: _state.tooltipAnimationEndController, + curve: Curves.fastOutSlowIn); if (isDateTime) { _valuesInMilliseconds = SfRangeValues( @@ -1655,6 +1778,7 @@ class _RenderRangeSlider extends RenderBaseSlider { _state.endPositionController.value = getFactorFromValue(actualValues.end); } } + final _SfRangeSliderState _state; final Map slotToChild = @@ -1663,19 +1787,21 @@ class _RenderRangeSlider extends RenderBaseSlider { final Map childToSlot = {}; - Animation _overlayStartAnimation; + late Animation _overlayStartAnimation; - Animation _overlayEndAnimation; + late Animation _overlayEndAnimation; - Animation _stateAnimation; + late Animation _stateAnimation; - Animation _tooltipAnimation; + late Animation _tooltipStartAnimation; - SfRangeValues _valuesInMilliseconds; + late Animation _tooltipEndAnimation; - // It stores the interaction start x-position at [tapDown] and [dragStart] + SfRangeValues? _valuesInMilliseconds; + + // It stores the interaction start it's main axis at [tapDown] and [dragStart] // method, which is used to check whether dragging is started or not. - double _interactionStartX = 0.0; + double _interactionStartOffset = 0.0; bool _isDragging = false; @@ -1686,6 +1812,7 @@ class _RenderRangeSlider extends RenderBaseSlider { SfRangeValues get values => _values; SfRangeValues _values; + set values(SfRangeValues values) { if (_values == values) { return; @@ -1700,9 +1827,10 @@ class _RenderRangeSlider extends RenderBaseSlider { markNeedsPaint(); } - ValueChanged get onChanged => _onChanged; - ValueChanged _onChanged; - set onChanged(ValueChanged value) { + ValueChanged? get onChanged => _onChanged; + ValueChanged? _onChanged; + + set onChanged(ValueChanged? value) { if (value == _onChanged) { return; } @@ -1721,6 +1849,7 @@ class _RenderRangeSlider extends RenderBaseSlider { bool get enableIntervalSelection => _enableIntervalSelection; bool _enableIntervalSelection; + set enableIntervalSelection(bool value) { if (_enableIntervalSelection == value) { return; @@ -1731,30 +1860,12 @@ class _RenderRangeSlider extends RenderBaseSlider { _state.endPositionController.value = getFactorFromValue(actualValues.end); } - Color get inactiveColor => _inactiveColor; - Color _inactiveColor; - set inactiveColor(Color value) { - if (_inactiveColor == value) { - return; - } - _inactiveColor = value; - markNeedsPaint(); - } + RangeSliderSemanticFormatterCallback? get semanticFormatterCallback => + _semanticFormatterCallback; - Color get activeColor => _activeColor; - Color _activeColor; - set activeColor(Color value) { - if (_activeColor == value) { - return; - } - _activeColor = value; - markNeedsPaint(); - } + RangeSliderSemanticFormatterCallback? _semanticFormatterCallback; - SfRangeSliderSemanticFormatterCallback get semanticFormatterCallback => - _semanticFormatterCallback; - SfRangeSliderSemanticFormatterCallback _semanticFormatterCallback; - set semanticFormatterCallback(SfRangeSliderSemanticFormatterCallback value) { + set semanticFormatterCallback(RangeSliderSemanticFormatterCallback? value) { if (_semanticFormatterCallback == value) { return; } @@ -1762,42 +1873,99 @@ class _RenderRangeSlider extends RenderBaseSlider { markNeedsSemanticsUpdate(); } - RenderBox get startThumbIcon => _startThumbIcon; - RenderBox _startThumbIcon; - set startThumbIcon(RenderBox value) { + RenderBox? get startThumbIcon => _startThumbIcon; + RenderBox? _startThumbIcon; + + set startThumbIcon(RenderBox? value) { _startThumbIcon = _updateChild(_startThumbIcon, value, ChildElements.startThumbIcon); } - RenderBox get endThumbIcon => _endThumbIcon; - RenderBox _endThumbIcon; - set endThumbIcon(RenderBox value) { + RenderBox? get endThumbIcon => _endThumbIcon; + RenderBox? _endThumbIcon; + + set endThumbIcon(RenderBox? value) { _endThumbIcon = _updateChild(_endThumbIcon, value, ChildElements.endThumbIcon); } + SfThumb? get activeThumb => _activeThumb; + SfThumb? _activeThumb; + + set activeThumb(SfThumb? value) { + // Ensuring whether the animation is already completed + // and calling controller's forward again is not needed. + if (_activeThumb == value && + (_state.overlayEndController.status == AnimationStatus.completed || + _state.overlayStartController.status == + AnimationStatus.completed)) { + return; + } + _activeThumb = value; + if (value == SfThumb.start) { + _state.overlayStartController.forward(); + _state.overlayEndController.reverse(); + if (enableTooltip) { + willDrawTooltip = true; + _state.tooltipAnimationStartController.forward(); + _state.tooltipAnimationEndController.reverse(); + } + } else { + _state.overlayEndController.forward(); + _state.overlayStartController.reverse(); + if (enableTooltip) { + willDrawTooltip = true; + _state.tooltipAnimationEndController.forward(); + _state.tooltipAnimationStartController.reverse(); + } + } + } + bool get isInteractive => onChanged != null; - double get minThumbGap => - (actualMax - actualMin) * (8 / actualTrackRect.width).clamp(0.0, 1.0); + double get minThumbGap => sliderType == SliderType.vertical + ? (actualMax - actualMin) * (8 / actualTrackRect.height).clamp(0.0, 1.0) + : (actualMax - actualMin) * (8 / actualTrackRect.width).clamp(0.0, 1.0); SfRangeValues get actualValues => - isDateTime ? _valuesInMilliseconds : _values; + isDateTime ? _valuesInMilliseconds! : _values; + + dynamic get _increasedStartValue { + return getNextSemanticValue(values.start, semanticActionUnit, + actualValue: actualValues.start); + } + + dynamic get _decreasedStartValue { + return getPrevSemanticValue(values.start, semanticActionUnit, + actualValue: actualValues.start); + } + + dynamic get _increasedEndValue { + return getNextSemanticValue(values.end, semanticActionUnit, + actualValue: actualValues.end); + } + + dynamic get _decreasedEndValue { + return getPrevSemanticValue(values.end, semanticActionUnit, + actualValue: actualValues.end); + } // The returned list is ordered for hit testing. Iterable get children sync* { if (_startThumbIcon != null) { - yield startThumbIcon; + yield startThumbIcon!; } if (_endThumbIcon != null) { - yield endThumbIcon; + yield endThumbIcon!; } } void _onTapDown(TapDownDetails details) { currentPointerType = PointerType.down; - _interactionStartX = globalToLocal(details.globalPosition).dx; - currentX = _interactionStartX; + _interactionStartOffset = (sliderType == SliderType.vertical + ? globalToLocal(details.globalPosition).dy + : globalToLocal(details.globalPosition).dx); + mainAxisOffset = _interactionStartOffset; _beginInteraction(); } @@ -1806,15 +1974,15 @@ class _RenderRangeSlider extends RenderBaseSlider { } void _onDragStart(DragStartDetails details) { - _interactionStartX = globalToLocal(details.globalPosition).dx; - currentX = _interactionStartX; + _interactionStartOffset = globalToLocal(details.globalPosition).dx; + mainAxisOffset = _interactionStartOffset; _beginInteraction(); } void _onDragUpdate(DragUpdateDetails details) { isInteractionEnd = false; currentPointerType = PointerType.move; - currentX = globalToLocal(details.globalPosition).dx; + mainAxisOffset = globalToLocal(details.globalPosition).dx; _updateRangeValues(); markNeedsPaint(); } @@ -1827,22 +1995,48 @@ class _RenderRangeSlider extends RenderBaseSlider { _endInteraction(); } + void _onVerticalDragStart(DragStartDetails details) { + _interactionStartOffset = globalToLocal(details.globalPosition).dy; + mainAxisOffset = _interactionStartOffset; + _beginInteraction(); + } + + void _onVerticalDragUpdate(DragUpdateDetails details) { + isInteractionEnd = false; + currentPointerType = PointerType.move; + mainAxisOffset = globalToLocal(details.globalPosition).dy; + _updateRangeValues(); + markNeedsPaint(); + } + + void _onVerticalDragEnd(DragEndDetails details) { + _endInteraction(); + } + + void _onVerticalDragCancel() { + _endInteraction(); + } + void _beginInteraction() { // This field is used in the [paint] method to handle the // interval selection animation, so we can't reset this // field in [endInteraction] method. _isIntervalTapped = false; - final double startPosition = - getFactorFromValue(actualValues.start) * actualTrackRect.width + + final double startPosition = sliderType == SliderType.vertical + ? actualTrackRect.bottom - + getFactorFromValue(actualValues.start) * actualTrackRect.height + : getFactorFromValue(actualValues.start) * actualTrackRect.width + actualTrackRect.left; - final double endPosition = - getFactorFromValue(actualValues.end) * actualTrackRect.width + + final double endPosition = sliderType == SliderType.vertical + ? actualTrackRect.bottom - + getFactorFromValue(actualValues.end) * actualTrackRect.height + : getFactorFromValue(actualValues.end) * actualTrackRect.width + actualTrackRect.left; - final double leftThumbWidth = (startPosition - currentX).abs(); - final double rightThumbWidth = (endPosition - currentX).abs(); + final double leftThumbWidth = (startPosition - mainAxisOffset).abs(); + final double rightThumbWidth = (endPosition - mainAxisOffset).abs(); if (rightThumbWidth == leftThumbWidth) { - switch (activeThumb) { + switch (activeThumb!) { case SfThumb.start: _state.overlayStartController.forward(); break; @@ -1869,7 +2063,8 @@ class _RenderRangeSlider extends RenderBaseSlider { void _forwardTooltipAnimation() { if (enableTooltip) { willDrawTooltip = true; - _state.tooltipAnimationController.forward(); + _state.tooltipAnimationStartController.forward(); + _state.tooltipAnimationEndController.forward(); _state.tooltipDelayTimer?.cancel(); _state.tooltipDelayTimer = Timer(const Duration(milliseconds: 500), () { _reverseTooltipAnimation(); @@ -1881,12 +2076,21 @@ class _RenderRangeSlider extends RenderBaseSlider { _state.tooltipDelayTimer = null; if (isInteractionEnd && willDrawTooltip && - _state.tooltipAnimationController.status == AnimationStatus.completed) { - _state.tooltipAnimationController.reverse(); - if (_state.tooltipAnimationController.status == - AnimationStatus.dismissed) { - willDrawTooltip = false; - } + _state.tooltipAnimationStartController.status == + AnimationStatus.completed) { + _state.tooltipAnimationStartController.reverse(); + } + if (isInteractionEnd && + willDrawTooltip && + _state.tooltipAnimationEndController.status == + AnimationStatus.completed) { + _state.tooltipAnimationEndController.reverse(); + } + if (_state.tooltipAnimationStartController.status == + AnimationStatus.dismissed && + _state.tooltipAnimationEndController.status == + AnimationStatus.dismissed) { + willDrawTooltip = false; } } @@ -1903,12 +2107,12 @@ class _RenderRangeSlider extends RenderBaseSlider { end = values.end; } - final double value = lerpDouble(actualMin, actualMax, factor); - _isDragging = (_interactionStartX - currentX).abs() > 1; + final double value = lerpDouble(actualMin, actualMax, factor)!; + _isDragging = (_interactionStartOffset - mainAxisOffset).abs() > 1; _isIntervalTapped = _enableIntervalSelection && !_isDragging; if (!_isIntervalTapped) { - switch (activeThumb) { + switch (activeThumb!) { case SfThumb.start: final double startValue = math.min(value, end - minThumbGap); final dynamic actualStartValue = @@ -1922,9 +2126,8 @@ class _RenderRangeSlider extends RenderBaseSlider { newValues = values.copyWith(end: actualEndValue); break; } - if (newValues.start != _values.start || newValues.end != _values.end) { - onChanged(newValues); + onChanged!(newValues); } } } @@ -1938,11 +2141,11 @@ class _RenderRangeSlider extends RenderBaseSlider { getFactorFromValue(actualValues.end); if (_isIntervalTapped) { - final double value = + final double? value = lerpDouble(actualMin, actualMax, getFactorFromCurrentPosition()); - final SfRangeValues newValues = _getSelectedRange(value); + final SfRangeValues newValues = _getSelectedRange(value!); _updatePositionControllerValue(newValues); - onChanged(newValues); + onChanged!(newValues); } } @@ -1951,7 +2154,8 @@ class _RenderRangeSlider extends RenderBaseSlider { _state.overlayStartController.reverse(); _state.overlayEndController.reverse(); if (enableTooltip && _state.tooltipDelayTimer == null) { - _state.tooltipAnimationController.reverse(); + _state.tooltipAnimationStartController.reverse(); + _state.tooltipAnimationEndController.reverse(); } isInteractionEnd = true; @@ -1960,19 +2164,19 @@ class _RenderRangeSlider extends RenderBaseSlider { } void _updatePositionControllerValue(SfRangeValues newValues) { - DateTime startDate; - DateTime endDate; + DateTime? startDate; + DateTime? endDate; if (isDateTime) { startDate = newValues.start; endDate = newValues.end; } final double startValueFactor = getFactorFromValue(isDateTime - ? startDate.millisecondsSinceEpoch.toDouble() + ? startDate!.millisecondsSinceEpoch.toDouble() : newValues.start); - final double endValueFactor = getFactorFromValue( - isDateTime ? endDate.millisecondsSinceEpoch.toDouble() : newValues.end); - + final double endValueFactor = getFactorFromValue(isDateTime + ? endDate!.millisecondsSinceEpoch.toDouble() + : newValues.end); final double startDistanceFactor = (startValueFactor - _state.startPositionController.value).abs(); final double endDistanceFactor = @@ -1990,14 +2194,14 @@ class _RenderRangeSlider extends RenderBaseSlider { } SfRangeValues _getSelectedRange(double value) { - SfRangeValues rangeValues; + late SfRangeValues rangeValues; dynamic start; dynamic end; - for (int i = 0; i < divisions; i++) { - final double currentLabel = unformattedLabels[i]; - if (i < divisions - 1) { - final double nextLabel = unformattedLabels[i + 1]; + for (int i = 0; i < divisions!; i++) { + final double currentLabel = unformattedLabels![i]; + if (i < divisions! - 1) { + final double nextLabel = unformattedLabels![i + 1]; if (value >= currentLabel && value <= nextLabel) { if (isDateTime) { start = DateTime.fromMillisecondsSinceEpoch(currentLabel.toInt()); @@ -2013,15 +2217,15 @@ class _RenderRangeSlider extends RenderBaseSlider { start = isDateTime ? DateTime.fromMillisecondsSinceEpoch(currentLabel.toInt()) : currentLabel; - end = max; + end = this.max; rangeValues = SfRangeValues(start, end); } } return rangeValues; } - RenderBox _updateChild( - RenderBox oldChild, RenderBox newChild, ChildElements slot) { + RenderBox? _updateChild( + RenderBox? oldChild, RenderBox? newChild, ChildElements slot) { if (oldChild != null) { dropChild(oldChild); childToSlot.remove(oldChild); @@ -2036,7 +2240,10 @@ class _RenderRangeSlider extends RenderBaseSlider { } void _handleTooltipAnimationStatusChange(AnimationStatus status) { - if (status == AnimationStatus.dismissed) { + if (_state.tooltipAnimationStartController.status == + AnimationStatus.dismissed && + _state.tooltipAnimationEndController.status == + AnimationStatus.dismissed) { willDrawTooltip = false; } } @@ -2046,9 +2253,9 @@ class _RenderRangeSlider extends RenderBaseSlider { Offset endThumbCenter, Offset startThumbCenter, ) { - final bool isLeftThumbActive = activeThumb == SfThumb.start; - Offset thumbCenter = isLeftThumbActive ? endThumbCenter : startThumbCenter; - RenderBox thumbIcon = isLeftThumbActive ? _endThumbIcon : _startThumbIcon; + final bool isStartThumbActive = activeThumb == SfThumb.start; + Offset thumbCenter = isStartThumbActive ? endThumbCenter : startThumbCenter; + RenderBox? thumbIcon = isStartThumbActive ? _endThumbIcon : _startThumbIcon; // Ignore overlapping thumb stroke for bottom thumb. showOverlappingThumbStroke = false; // Drawing thumb. @@ -2059,23 +2266,29 @@ class _RenderRangeSlider extends RenderBaseSlider { currentValues: _values, enableAnimation: _stateAnimation, textDirection: textDirection, - thumb: isLeftThumbActive ? SfThumb.end : SfThumb.start); + thumb: isStartThumbActive ? SfThumb.end : SfThumb.start, + paint: null); - thumbCenter = isLeftThumbActive ? startThumbCenter : endThumbCenter; - thumbIcon = isLeftThumbActive ? _startThumbIcon : _endThumbIcon; + thumbCenter = isStartThumbActive ? startThumbCenter : endThumbCenter; + thumbIcon = isStartThumbActive ? _startThumbIcon : _endThumbIcon; // Drawing overlay. - overlayShape.paint(context, thumbCenter, - parentBox: this, - themeData: sliderThemeData, - currentValues: _values, - animation: - isLeftThumbActive ? _overlayStartAnimation : _overlayEndAnimation, - thumb: activeThumb); - + overlayShape.paint( + context, + thumbCenter, + parentBox: this, + themeData: sliderThemeData, + currentValues: _values, + animation: + isStartThumbActive ? _overlayStartAnimation : _overlayEndAnimation, + thumb: activeThumb, + paint: null, + ); showOverlappingThumbStroke = (getFactorFromValue(actualValues.start) - getFactorFromValue(actualValues.end)) .abs() * - actualTrackRect.width < + (sliderType == SliderType.vertical + ? actualTrackRect.height + : actualTrackRect.width) < actualThumbSize.width; // Drawing thumb. @@ -2086,7 +2299,8 @@ class _RenderRangeSlider extends RenderBaseSlider { currentValues: _values, enableAnimation: _stateAnimation, textDirection: textDirection, - thumb: activeThumb); + thumb: activeThumb, + paint: null); } void _drawTooltip( @@ -2098,13 +2312,17 @@ class _RenderRangeSlider extends RenderBaseSlider { Rect trackRect) { if (willDrawTooltip) { final Paint paint = Paint() - ..color = sliderThemeData.tooltipBackgroundColor + ..color = sliderThemeData.tooltipBackgroundColor! ..style = PaintingStyle.fill ..strokeWidth = 0; + final bool isStartThumbActive = activeThumb == SfThumb.start; Offset thumbCenter = - activeThumb == SfThumb.start ? endThumbCenter : startThumbCenter; - dynamic actualText = getValueFromPosition(thumbCenter.dx - offset.dx); + isStartThumbActive ? endThumbCenter : startThumbCenter; + dynamic actualText = (sliderType == SliderType.vertical) + ? getValueFromPosition(trackRect.bottom - thumbCenter.dy) + : getValueFromPosition(thumbCenter.dx - offset.dx); + String tooltipText = tooltipTextFormatterCallback( actualText, getFormattedText(actualText)); TextSpan textSpan = @@ -2112,7 +2330,7 @@ class _RenderRangeSlider extends RenderBaseSlider { textPainter.text = textSpan; textPainter.layout(); - Rect bottomTooltipRect; + Rect? bottomTooltipRect; if (tooltipShape is SfPaddleTooltipShape) { bottomTooltipRect = getPaddleTooltipRect( textPainter, @@ -2136,12 +2354,16 @@ class _RenderRangeSlider extends RenderBaseSlider { parentBox: this, sliderThemeData: sliderThemeData, paint: paint, - animation: _tooltipAnimation, + animation: isStartThumbActive + ? _tooltipEndAnimation + : _tooltipStartAnimation, trackRect: trackRect); - thumbCenter = - activeThumb == SfThumb.start ? startThumbCenter : endThumbCenter; - actualText = getValueFromPosition(thumbCenter.dx - offset.dx); + thumbCenter = isStartThumbActive ? startThumbCenter : endThumbCenter; + actualText = (sliderType == SliderType.vertical) + ? getValueFromPosition(trackRect.bottom - thumbCenter.dy) + : getValueFromPosition(thumbCenter.dx - offset.dx); + tooltipText = tooltipTextFormatterCallback( actualText, getFormattedText(actualText)); textSpan = @@ -2149,7 +2371,7 @@ class _RenderRangeSlider extends RenderBaseSlider { textPainter.text = textSpan; textPainter.layout(); - Rect topTooltipRect; + Rect? topTooltipRect; if (tooltipShape is SfPaddleTooltipShape) { topTooltipRect = getPaddleTooltipRect( textPainter, @@ -2167,22 +2389,27 @@ class _RenderRangeSlider extends RenderBaseSlider { } if (bottomTooltipRect != null && topTooltipRect != null) { final Rect overlapRect = topTooltipRect.intersect(bottomTooltipRect); - showOverlappingTooltipStroke = overlapRect.right > overlapRect.left; + showOverlappingTooltipStroke = sliderType == SliderType.vertical + ? overlapRect.top < overlapRect.bottom + : overlapRect.right > overlapRect.left; } + tooltipShape.paint(context, thumbCenter, Offset(actualTrackOffset.dx, tooltipStartY), textPainter, parentBox: this, sliderThemeData: sliderThemeData, paint: paint, - animation: _tooltipAnimation, + animation: isStartThumbActive + ? _tooltipStartAnimation + : _tooltipEndAnimation, trackRect: trackRect); } } void _increaseStartAction() { if (isInteractive) { - final SfRangeValues actualNewValues = SfRangeValues( - _increaseValue(values.start, actualValues.start), values.end); + final SfRangeValues actualNewValues = + SfRangeValues(_increasedStartValue, values.end); final SfRangeValues newValues = isDateTime ? SfRangeValues( actualNewValues.start.millisecondsSinceEpoch.toDouble(), @@ -2190,29 +2417,27 @@ class _RenderRangeSlider extends RenderBaseSlider { : actualNewValues; if (newValues.start <= newValues.end) { - onChanged(actualNewValues); + onChanged!(actualNewValues); } } } void _decreaseStartAction() { if (isInteractive) { - onChanged(SfRangeValues( - _decreaseValue(values.start, actualValues.start), values.end)); + onChanged!(SfRangeValues(_decreasedStartValue, values.end)); } } void _increaseEndAction() { if (isInteractive) { - onChanged(SfRangeValues( - values.start, _increaseValue(values.end, actualValues.end))); + onChanged!(SfRangeValues(values.start, _increasedEndValue)); } } void _decreaseEndAction() { if (isInteractive) { - final SfRangeValues actualNewValues = SfRangeValues( - values.start, _decreaseValue(values.end, actualValues.end)); + final SfRangeValues actualNewValues = + SfRangeValues(values.start, _decreasedEndValue); final SfRangeValues newValues = isDateTime ? SfRangeValues( actualNewValues.start.millisecondsSinceEpoch.toDouble(), @@ -2220,25 +2445,52 @@ class _RenderRangeSlider extends RenderBaseSlider { : actualNewValues; if (newValues.start <= newValues.end) { - onChanged(actualNewValues); + onChanged!(actualNewValues); } } } - dynamic _increaseValue(dynamic value, double actualValue) { - return getNextSemanticValue(value, semanticActionUnit, - actualValue: actualValue); + void _handleExit(PointerExitEvent event) { + // Ensuring whether the thumb is drag or move + // not needed to call controller's reverse. + if (currentPointerType != PointerType.move) { + _state.overlayStartController.reverse(); + _state.overlayEndController.reverse(); + if (enableTooltip) { + _state.tooltipAnimationStartController.reverse(); + _state.tooltipAnimationEndController.reverse(); + } + } } - dynamic _decreaseValue(dynamic value, double actualValue) { - return getPrevSemanticValue(value, semanticActionUnit, - actualValue: actualValue); + void _handleHover(PointerHoverEvent details) { + double cursorPosition = 0.0; + final double startThumbPosition = sliderType == SliderType.vertical + ? actualTrackRect.bottom - + getFactorFromValue(actualValues.start) * actualTrackRect.height + : getFactorFromValue(actualValues.start) * actualTrackRect.width + + actualTrackRect.left; + final double endThumbPosition = sliderType == SliderType.vertical + ? actualTrackRect.bottom - + getFactorFromValue(actualValues.end) * actualTrackRect.height + : getFactorFromValue(actualValues.end) * actualTrackRect.width + + actualTrackRect.left; + cursorPosition = sliderType == SliderType.vertical + ? details.localPosition.dy + : details.localPosition.dx; + final double startThumbDistance = + (cursorPosition - startThumbPosition).abs(); + final double endThumbDistance = (cursorPosition - endThumbPosition).abs(); + if (endThumbDistance > startThumbDistance) { + activeThumb = SfThumb.start; + } else { + activeThumb = SfThumb.end; + } } @override void performLayout() { super.performLayout(); - final BoxConstraints contentConstraints = BoxConstraints.tightFor( width: sliderThemeData.thumbRadius * 2, height: sliderThemeData.thumbRadius * 2); @@ -2249,13 +2501,16 @@ class _RenderRangeSlider extends RenderBaseSlider { @override void attach(PipelineOwner owner) { super.attach(owner); - _overlayStartAnimation?.addListener(markNeedsPaint); - _overlayEndAnimation?.addListener(markNeedsPaint); - _stateAnimation?.addListener(markNeedsPaint); - _state.startPositionController?.addListener(markNeedsPaint); - _state.endPositionController?.addListener(markNeedsPaint); - _tooltipAnimation?.addListener(markNeedsPaint); - _tooltipAnimation?.addStatusListener(_handleTooltipAnimationStatusChange); + _overlayStartAnimation.addListener(markNeedsPaint); + _overlayEndAnimation.addListener(markNeedsPaint); + _stateAnimation.addListener(markNeedsPaint); + _state.startPositionController.addListener(markNeedsPaint); + _state.endPositionController.addListener(markNeedsPaint); + _tooltipStartAnimation.addListener(markNeedsPaint); + _tooltipStartAnimation + .addStatusListener(_handleTooltipAnimationStatusChange); + _tooltipEndAnimation.addListener(markNeedsPaint); + _tooltipEndAnimation.addStatusListener(_handleTooltipAnimationStatusChange); for (final RenderBox child in children) { child.attach(owner); } @@ -2263,14 +2518,17 @@ class _RenderRangeSlider extends RenderBaseSlider { @override void detach() { - _overlayStartAnimation?.removeListener(markNeedsPaint); - _overlayEndAnimation?.removeListener(markNeedsPaint); - _stateAnimation?.removeListener(markNeedsPaint); - _state.startPositionController?.removeListener(markNeedsPaint); - _state.endPositionController?.removeListener(markNeedsPaint); - _tooltipAnimation?.removeListener(markNeedsPaint); - _tooltipAnimation - ?.removeStatusListener(_handleTooltipAnimationStatusChange); + _overlayStartAnimation.removeListener(markNeedsPaint); + _overlayEndAnimation.removeListener(markNeedsPaint); + _stateAnimation.removeListener(markNeedsPaint); + _state.startPositionController.removeListener(markNeedsPaint); + _state.endPositionController.removeListener(markNeedsPaint); + _tooltipStartAnimation.removeListener(markNeedsPaint); + _tooltipStartAnimation + .removeStatusListener(_handleTooltipAnimationStatusChange); + _tooltipEndAnimation.removeListener(markNeedsPaint); + _tooltipEndAnimation + .removeStatusListener(_handleTooltipAnimationStatusChange); super.detach(); for (final RenderBox child in children) { child.detach(); @@ -2282,17 +2540,49 @@ class _RenderRangeSlider extends RenderBaseSlider { children.forEach(visitor); } + @override + MouseCursor get cursor => MouseCursor.defer; + + @override + PointerEnterEventListener? get onEnter => null; + + @override + // ignore: override_on_non_overriding_member + PointerHoverEventListener get onHover => _handleHover; + + @override + PointerExitEventListener get onExit => _handleExit; + + @override + // ignore: override_on_non_overriding_member + bool get validForMouseTracker => true; + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + if (event is PointerHoverEvent) { + return onHover(event); + } + super.handleEvent(event, entry); + } + @override bool hitTestSelf(Offset position) => isInteractive; @override void paint(PaintingContext context, Offset offset) { - final Offset actualTrackOffset = Offset( - offset.dx, - offset.dy + - (size.height - actualHeight) / 2 + - trackOffset.dy - - maxTrackHeight / 2); + final Offset actualTrackOffset = sliderType == SliderType.vertical + ? Offset( + offset.dx + + (size.width - actualHeight) / 2 + + trackOffset.dy - + maxTrackHeight / 2, + offset.dy) + : Offset( + offset.dx, + offset.dy + + (size.height - actualHeight) / 2 + + trackOffset.dy - + maxTrackHeight / 2); // Drawing track. final Rect trackRect = @@ -2300,23 +2590,30 @@ class _RenderRangeSlider extends RenderBaseSlider { final double thumbStartPosition = getFactorFromValue(_isIntervalTapped ? getValueFromFactor(_state.startPositionController.value) : actualValues.start) * - trackRect.width; + (sliderType == SliderType.vertical + ? trackRect.height + : trackRect.width); final double thumbEndPosition = getFactorFromValue(_isIntervalTapped ? getValueFromFactor(_state.endPositionController.value) : actualValues.end) * - trackRect.width; - final Offset startThumbCenter = - Offset(trackRect.left + thumbStartPosition, trackRect.center.dy); - final Offset endThumbCenter = - Offset(trackRect.left + thumbEndPosition, trackRect.center.dy); - + (sliderType == SliderType.vertical + ? trackRect.height + : trackRect.width); + final Offset startThumbCenter = sliderType == SliderType.vertical + ? Offset(trackRect.center.dx, trackRect.bottom - thumbStartPosition) + : Offset(trackRect.left + thumbStartPosition, trackRect.center.dy); + final Offset endThumbCenter = sliderType == SliderType.vertical + ? Offset(trackRect.center.dx, trackRect.bottom - thumbEndPosition) + : Offset(trackRect.left + thumbEndPosition, trackRect.center.dy); trackShape.paint( context, actualTrackOffset, null, startThumbCenter, endThumbCenter, parentBox: this, themeData: sliderThemeData, currentValues: _values, enableAnimation: _stateAnimation, - textDirection: textDirection); + textDirection: textDirection, + activePaint: null, + inactivePaint: null); if (showLabels || showTicks || showDivisors) { drawLabelsTicksAndDivisors(context, trackRect, offset, null, @@ -2328,33 +2625,105 @@ class _RenderRangeSlider extends RenderBaseSlider { actualTrackOffset, trackRect); } + /// Describe the semantics of the start thumb. + SemanticsNode? _startSemanticsNode = SemanticsNode(); + + /// Describe the semantics of the end thumb. + SemanticsNode? _endSemanticsNode = SemanticsNode(); + + // Create the semantics configuration for a single value. + SemanticsConfiguration _createSemanticsConfiguration( + dynamic value, + dynamic increasedValue, + dynamic decreasedValue, + SfThumb thumb, + VoidCallback increaseAction, + VoidCallback decreaseAction, + ) { + final SemanticsConfiguration config = SemanticsConfiguration(); + config.isEnabled = isInteractive; + config.textDirection = textDirection; + if (isInteractive) { + config.onIncrease = increaseAction; + config.onDecrease = decreaseAction; + } + + if (semanticFormatterCallback != null) { + config.value = semanticFormatterCallback!(value, thumb); + config.increasedValue = semanticFormatterCallback!(increasedValue, thumb); + config.decreasedValue = semanticFormatterCallback!(decreasedValue, thumb); + } else { + final String thumbValue = thumb.toString().split('.').last; + config.value = 'the $thumbValue value is $value'; + config.increasedValue = 'the $thumbValue value is $increasedValue'; + config.decreasedValue = 'the $thumbValue value is $decreasedValue'; + } + return config; + } + + @override + void assembleSemanticsNode( + SemanticsNode node, + SemanticsConfiguration config, + Iterable children, + ) { + assert(children.isEmpty); + final SemanticsConfiguration startSemanticsConfiguration = + _createSemanticsConfiguration( + values.start, + _increasedStartValue, + _decreasedStartValue, + SfThumb.start, + _increaseStartAction, + _decreaseStartAction, + ); + final SemanticsConfiguration endSemanticsConfiguration = + _createSemanticsConfiguration( + values.end, + _increasedEndValue, + _decreasedEndValue, + SfThumb.end, + _increaseEndAction, + _decreaseEndAction, + ); + // Split the semantics node area between the start and end nodes. + final Rect startRect = sliderType == SliderType.vertical + ? Rect.fromPoints(node.rect.bottomRight, node.rect.centerLeft) + : Rect.fromPoints(node.rect.topLeft, node.rect.bottomCenter); + final Rect endRect = sliderType == SliderType.vertical + ? Rect.fromPoints(node.rect.centerLeft, node.rect.topRight) + : Rect.fromPoints(node.rect.topCenter, node.rect.bottomRight); + if (sliderType == SliderType.vertical || + textDirection == TextDirection.ltr) { + _startSemanticsNode!.rect = startRect; + _endSemanticsNode!.rect = endRect; + } else { + _startSemanticsNode!.rect = endRect; + _endSemanticsNode!.rect = startRect; + } + + _startSemanticsNode!.updateWith(config: startSemanticsConfiguration); + _endSemanticsNode!.updateWith(config: endSemanticsConfiguration); + + final List finalChildren = [ + _startSemanticsNode!, + _endSemanticsNode!, + ]; + + node.updateWith(config: config, childrenInInversePaintOrder: finalChildren); + } + + @override + void clearSemantics() { + super.clearSemantics(); + _startSemanticsNode = null; + _endSemanticsNode = null; + } + @override void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); - config.isSemanticBoundary = isInteractive; - if (isInteractive) { - config.textDirection = textDirection; - config.customSemanticsActions = { - const CustomSemanticsAction(label: 'Decrease start value'): - _decreaseStartAction, - const CustomSemanticsAction(label: 'Increase start value'): - _increaseStartAction, - const CustomSemanticsAction(label: 'Decrease end value'): - _decreaseEndAction, - const CustomSemanticsAction(label: 'Increase end value'): - _increaseEndAction, - }; - - assert(actualValues.start <= actualValues.end); - if (_semanticFormatterCallback != null) { - config.value = _semanticFormatterCallback(values); - } else { - config.value = - // ignore: lines_longer_than_80_chars - 'The start value is ${values.start} and the end value is ${values.end}'; - } - } } @override diff --git a/packages/syncfusion_flutter_sliders/lib/src/render_slider_base.dart b/packages/syncfusion_flutter_sliders/lib/src/render_slider_base.dart index d06e87ad7..0f7ff0f37 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/render_slider_base.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/render_slider_base.dart @@ -1,8 +1,7 @@ import 'dart:math' as math; import 'dart:ui'; -import 'package:flutter/gestures.dart' - show TapGestureRecognizer, HorizontalDragGestureRecognizer; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:intl/intl.dart' show DateFormat, NumberFormat; @@ -12,36 +11,40 @@ import 'common.dart'; import 'constants.dart'; import 'slider_shapes.dart'; +// ignore: public_member_api_docs class RenderBaseSlider extends RenderProxyBox with RelayoutWhenSystemFontsChangeMixin { + // ignore: public_member_api_docs RenderBaseSlider({ - dynamic min, - dynamic max, - double interval, - double stepSize, - SliderStepDuration stepDuration, - int minorTicksPerInterval, - bool showTicks, - bool showLabels, - bool showDivisors, - bool enableTooltip, - LabelPlacement labelPlacement, - NumberFormat numberFormat, - DateFormat dateFormat, - DateIntervalType dateIntervalType, - LabelFormatterCallback labelFormatterCallback, - TooltipTextFormatterCallback tooltipTextFormatterCallback, - SfTrackShape trackShape, - SfDivisorShape divisorShape, - SfOverlayShape overlayShape, - SfThumbShape thumbShape, - SfTickShape tickShape, - SfTickShape minorTickShape, - SfTooltipShape tooltipShape, - SfSliderThemeData sliderThemeData, - TextDirection textDirection, - MediaQueryData mediaQueryData, - }) : _min = min, + required dynamic min, + required dynamic max, + required double? interval, + required double? stepSize, + required SliderStepDuration? stepDuration, + required int minorTicksPerInterval, + required bool showTicks, + required bool showLabels, + required bool showDivisors, + required bool enableTooltip, + required LabelPlacement labelPlacement, + required NumberFormat numberFormat, + required DateFormat? dateFormat, + required DateIntervalType? dateIntervalType, + required LabelFormatterCallback labelFormatterCallback, + required TooltipTextFormatterCallback tooltipTextFormatterCallback, + required SfTrackShape trackShape, + required SfDivisorShape divisorShape, + required SfOverlayShape overlayShape, + required SfThumbShape thumbShape, + required SfTickShape tickShape, + required SfTickShape minorTickShape, + required SfTooltipShape tooltipShape, + required SfSliderThemeData sliderThemeData, + SliderType? sliderType, + SliderTooltipPosition? tooltipPosition, + required TextDirection textDirection, + required MediaQueryData mediaQueryData, + }) : _min = min, _max = max, _interval = interval, _stepSize = stepSize, @@ -66,7 +69,9 @@ class RenderBaseSlider extends RenderProxyBox _tooltipShape = tooltipShape, _sliderThemeData = sliderThemeData, _textDirection = textDirection, - _mediaQueryData = mediaQueryData { + _mediaQueryData = mediaQueryData, + _tooltipPosition = tooltipPosition, + sliderType = sliderType { maxTrackHeight = getMaxTrackHeight(); trackOffset = _getTrackOffset(); @@ -87,11 +92,13 @@ class RenderBaseSlider extends RenderProxyBox final TextPainter textPainter = TextPainter(); - double _minInMilliseconds; + late double _minInMilliseconds; - double _maxInMilliseconds; + late double _maxInMilliseconds; - double divisions; + final SliderType? sliderType; + + double? divisions; //ignore: prefer_final_fields bool willDrawTooltip = false; @@ -99,23 +106,25 @@ class RenderBaseSlider extends RenderProxyBox //ignore: prefer_final_fields bool isInteractionEnd = true; - List _visibleLabels; + late List _visibleLabels; + + late List _majorTickPositions; - List _majorTickPositions; + late List _minorTickPositions; - List _minorTickPositions; + List? unformattedLabels; - List unformattedLabels; + HorizontalDragGestureRecognizer? horizontalDragGestureRecognizer; - HorizontalDragGestureRecognizer dragGestureRecognizer; + VerticalDragGestureRecognizer? verticalDragGestureRecognizer; - TapGestureRecognizer tapGestureRecognizer; + late TapGestureRecognizer tapGestureRecognizer; - double actualHeight; + late double actualHeight; - Offset trackOffset; + late Offset trackOffset; - double maxTrackHeight; + late double maxTrackHeight; bool showOverlappingTooltipStroke = false; @@ -124,16 +133,17 @@ class RenderBaseSlider extends RenderProxyBox // It stores the current touch x-position, which is used in // the [endInteraction] and [dragUpdate] method. //ignore: prefer_final_fields - double currentX = 0.0; + double mainAxisOffset = 0.0; - SfThumb activeThumb; + SfThumb? activeThumb; - Tween thumbElevationTween; + late Tween thumbElevationTween; - PointerType currentPointerType; + PointerType? currentPointerType; dynamic get min => _min; dynamic _min; + set min(dynamic value) { if (_min == value) { return; @@ -148,6 +158,7 @@ class RenderBaseSlider extends RenderProxyBox dynamic get max => _max; dynamic _max; + set max(dynamic value) { if (_max == value) { return; @@ -160,10 +171,10 @@ class RenderBaseSlider extends RenderProxyBox markNeedsPaint(); } - double get interval => _interval; - double _interval; + double? get interval => _interval; + double? _interval; - set interval(double value) { + set interval(double? value) { if (_interval == value) { return; } @@ -171,10 +182,10 @@ class RenderBaseSlider extends RenderProxyBox markNeedsPaint(); } - double get stepSize => _stepSize; - double _stepSize; + double? get stepSize => _stepSize; + double? _stepSize; - set stepSize(double value) { + set stepSize(double? value) { if (_stepSize == value) { return; } @@ -182,9 +193,10 @@ class RenderBaseSlider extends RenderProxyBox markNeedsPaint(); } - SliderStepDuration get stepDuration => _stepDuration; - SliderStepDuration _stepDuration; - set stepDuration(SliderStepDuration value) { + SliderStepDuration? get stepDuration => _stepDuration; + SliderStepDuration? _stepDuration; + + set stepDuration(SliderStepDuration? value) { if (_stepDuration == value) { return; } @@ -237,6 +249,7 @@ class RenderBaseSlider extends RenderProxyBox bool get enableTooltip => _enableTooltip; bool _enableTooltip; + set enableTooltip(bool value) { if (_enableTooltip == value) { return; @@ -266,10 +279,10 @@ class RenderBaseSlider extends RenderProxyBox markNeedsPaint(); } - DateIntervalType get dateIntervalType => _dateIntervalType; - DateIntervalType _dateIntervalType; + DateIntervalType? get dateIntervalType => _dateIntervalType; + DateIntervalType? _dateIntervalType; - set dateIntervalType(DateIntervalType value) { + set dateIntervalType(DateIntervalType? value) { if (_dateIntervalType == value) { return; } @@ -277,10 +290,10 @@ class RenderBaseSlider extends RenderProxyBox markNeedsPaint(); } - DateFormat get dateFormat => _dateFormat; - DateFormat _dateFormat; + DateFormat? get dateFormat => _dateFormat; + DateFormat? _dateFormat; - set dateFormat(DateFormat value) { + set dateFormat(DateFormat? value) { if (_dateFormat == value) { return; } @@ -378,6 +391,7 @@ class RenderBaseSlider extends RenderProxyBox SfTooltipShape get tooltipShape => _tooltipShape; SfTooltipShape _tooltipShape; + set tooltipShape(SfTooltipShape value) { if (_tooltipShape == value) { return; @@ -408,6 +422,17 @@ class RenderBaseSlider extends RenderProxyBox markNeedsPaint(); } + SliderTooltipPosition? get tooltipPosition => _tooltipPosition; + SliderTooltipPosition? _tooltipPosition; + + set tooltipPosition(SliderTooltipPosition? value) { + if (_tooltipPosition == value) { + return; + } + _tooltipPosition = value; + markNeedsPaint(); + } + MediaQueryData get mediaQueryData => _mediaQueryData; MediaQueryData _mediaQueryData; @@ -428,7 +453,7 @@ class RenderBaseSlider extends RenderProxyBox double get actualMax => isDateTime ? _maxInMilliseconds : _max; bool get isDiscrete => - (_stepSize != null && _stepSize > 0) || (_stepDuration != null); + (_stepSize != null && _stepSize! > 0) || (_stepDuration != null); Size get actualDivisorSize => _divisorShape.getPreferredSize(_sliderThemeData); @@ -438,9 +463,12 @@ class RenderBaseSlider extends RenderProxyBox Size get actualMinorTickSize => _minorTickShape.getPreferredSize(_sliderThemeData); - Size get actualLabelSize => Size.fromHeight(math.max( - _sliderThemeData.inactiveLabelStyle.fontSize, - _sliderThemeData.activeLabelStyle.fontSize)); + double get maximumFontSize => math.max( + _sliderThemeData.inactiveLabelStyle!.fontSize!, + _sliderThemeData.activeLabelStyle!.fontSize!); + + // actualLabelSize is applicable only for horizontal sliders + Size get actualLabelSize => Size.fromHeight(maximumFontSize); Rect get actualTrackRect => _trackShape.getPreferredRect(this, _sliderThemeData, Offset.zero); @@ -451,26 +479,47 @@ class RenderBaseSlider extends RenderProxyBox _overlayShape.getPreferredSize(_sliderThemeData); double get actualTickHeight => _showTicks - ? _sliderThemeData.tickSize.height + + ? _sliderThemeData.tickSize!.height + + (_sliderThemeData.tickOffset != null + ? _sliderThemeData.tickOffset!.dy + : 0) + : 0; + + // actualTickWidth is applicable only for vertical sliders + double get actualTickWidth => _showTicks + ? _sliderThemeData.tickSize!.width + (_sliderThemeData.tickOffset != null - ? _sliderThemeData.tickOffset.dy + ? _sliderThemeData.tickOffset!.dx : 0) : 0; double get actualMinorTickHeight => - _minorTicksPerInterval != null ? actualMinorTickSize.height : 0; + _minorTicksPerInterval > 0 ? actualMinorTickSize.height : 0; + + // actualMinorTickWidth is applicable only for vertical sliders + double get actualMinorTickWidth => + _minorTicksPerInterval > 0 ? actualMinorTickSize.width : 0; double get actualLabelHeight => _showLabels ? actualLabelSize.height * textPainter.textScaleFactor + (_sliderThemeData.labelOffset != null - ? _sliderThemeData.labelOffset.dy + ? _sliderThemeData.labelOffset!.dy : 0) : 0; + // actualLabelOffset is applicable only for vertical sliders + double get actualLabelOffset => _showLabels + ? _sliderThemeData.labelOffset != null + ? (_sliderThemeData.labelOffset!.dx) + : 0 + : 0; + // Here 10 is a gap between tooltip nose and thumb. - double get tooltipStartY => _tooltipShape is SfPaddleTooltipShape - ? math.max(actualThumbSize.height, actualTrackRect.height) / 2 - : math.max(actualThumbSize.height, actualTrackRect.height) / 2 + 10; + double get tooltipStartY => (sliderType == SliderType.vertical) + ? math.max(actualThumbSize.width, actualTrackRect.width) / 2 + 10 + : _tooltipShape is SfPaddleTooltipShape + ? math.max(actualThumbSize.height, actualTrackRect.height) / 2 + : math.max(actualThumbSize.height, actualTrackRect.height) / 2 + 10; double get adjustmentUnit => (actualMax - actualMin) / 10; @@ -511,7 +560,9 @@ class RenderBaseSlider extends RenderProxyBox String getFormattedText(dynamic value) { if (isDateTime) { - return _dateFormat != null ? _dateFormat.format(value) : value.toString(); + return _dateFormat != null + ? _dateFormat!.format(value) + : value.toString(); } return _numberFormat.format(value); } @@ -523,11 +574,18 @@ class RenderBaseSlider extends RenderProxyBox final double factor = (value == null || actualMax <= actualMin) ? 0.0 : ((value - actualMin) / (actualMax - actualMin)); - return (_textDirection == TextDirection.rtl) ? 1.0 - factor : factor; + if (sliderType == SliderType.vertical) { + return factor; + } else { + return (_textDirection == TextDirection.rtl) ? 1.0 - factor : factor; + } } double getPositionFromValue(double value) { - return getFactorFromValue(value) * actualTrackRect.width + + return getFactorFromValue(value) * + (sliderType == SliderType.vertical + ? actualTrackRect.height + : actualTrackRect.width) + actualTrackRect.left; } @@ -535,7 +593,7 @@ class RenderBaseSlider extends RenderProxyBox _visibleLabels.clear(); unformattedLabels?.clear(); _majorTickPositions.clear(); - if (_interval != null && _interval > 0) { + if (_interval != null && _interval! > 0) { _generateLabelsAndMajorTicksBasedOnInterval(); } else if (_showTicks || _showLabels) { _generateEdgeLabelsAndMajorTicks(); @@ -545,14 +603,14 @@ class RenderBaseSlider extends RenderProxyBox void _generateLabelsAndMajorTicksBasedOnInterval() { String label; double labelPosition; - int valueInMilliseconds; + int? valueInMilliseconds; dynamic currentValue = _min; divisions = (isDateTime ? _getDateTimeDifference(_min, _max, _dateIntervalType) : _max - _min) .toDouble() / _interval; - for (int i = 0; i <= divisions; i++) { + for (int i = 0; i <= divisions!; i++) { label = _labelFormatterCallback(currentValue, getFormattedText(currentValue)); @@ -561,17 +619,21 @@ class RenderBaseSlider extends RenderProxyBox } _visibleLabels.add(label); unformattedLabels - ?.add(isDateTime ? valueInMilliseconds.toDouble() : currentValue); - - labelPosition = - getFactorFromValue(isDateTime ? valueInMilliseconds : currentValue) * - (actualTrackRect.width); + ?.add(isDateTime ? valueInMilliseconds!.toDouble() : currentValue); + if (sliderType == SliderType.vertical) { + labelPosition = getFactorFromValue( + isDateTime ? valueInMilliseconds : currentValue) * + (actualTrackRect.height); + } else { + labelPosition = getFactorFromValue( + isDateTime ? valueInMilliseconds : currentValue) * + (actualTrackRect.width); + } if (!_majorTickPositions.contains(labelPosition)) { _majorTickPositions.add(labelPosition); } - currentValue = isDateTime - ? _getNextDate(currentValue, _dateIntervalType, _interval) + ? _getNextDate(currentValue, _dateIntervalType, _interval!) : currentValue + _interval; } } @@ -589,14 +651,20 @@ class RenderBaseSlider extends RenderProxyBox unformattedLabels ?.add(isDateTime ? _max.millisecondsSinceEpoch.toDouble() : _max); - labelPosition = getFactorFromValue(actualMin) * (actualTrackRect.width); + labelPosition = getFactorFromValue(actualMin) * + (sliderType == SliderType.vertical + ? actualTrackRect.height + : actualTrackRect.width); _majorTickPositions.add(labelPosition); - labelPosition = getFactorFromValue(actualMax) * (actualTrackRect.width); + labelPosition = getFactorFromValue(actualMax) * + (sliderType == SliderType.vertical + ? actualTrackRect.height + : actualTrackRect.width); _majorTickPositions.add(labelPosition); } void generateMinorTicks() { - if (_interval != null && _interval > 0) { + if (_interval != null && _interval! > 0) { _minorTickPositions.clear(); if (_minorTicksPerInterval > 0) { if (isDateTime) { @@ -611,9 +679,9 @@ class RenderBaseSlider extends RenderProxyBox void _generateDateTimeMinorTicks() { final int majorTicksCount = _majorTickPositions.length; double minorTickPosition; - DateTime nextDate = _getNextDate(_min, _dateIntervalType, _interval); + DateTime nextDate = _getNextDate(_min, _dateIntervalType, _interval!); DateTime currentActualDate = - _getNextDate(nextDate, _dateIntervalType, -_interval); + _getNextDate(nextDate, _dateIntervalType, -_interval!); for (int i = 1; i <= majorTicksCount; i++) { // Need to divide the region based on _minorTicksPerInterval. // So, added 1 with _minorTicksPerInterval. @@ -646,7 +714,7 @@ class RenderBaseSlider extends RenderProxyBox } } currentActualDate = nextDate; - nextDate = _getNextDate(currentActualDate, _dateIntervalType, _interval); + nextDate = _getNextDate(currentActualDate, _dateIntervalType, _interval!); } } @@ -655,7 +723,9 @@ class RenderBaseSlider extends RenderProxyBox for (int i = 0; i <= majorTicksCount - 1; i++) { final double minorPositionDiff = ((i + 1 < majorTicksCount ? _majorTickPositions[i + 1] - : actualTrackRect.width) - + : (sliderType == SliderType.vertical + ? actualTrackRect.height + : actualTrackRect.width)) - _majorTickPositions[i]) / (_minorTicksPerInterval + 1); for (int j = 1; j <= _minorTicksPerInterval; j++) { @@ -668,73 +738,64 @@ class RenderBaseSlider extends RenderProxyBox /// intervalType to find the exact range. // ignore: missing_return int _getDateTimeDifference( - DateTime min, DateTime max, DateIntervalType intervalType) { + DateTime min, DateTime max, DateIntervalType? intervalType) { assert(intervalType != null); final Duration diff = max.difference(min); - switch (intervalType) { + switch (intervalType!) { case DateIntervalType.months: return ((max.year - min.year) * DateTime.monthsPerYear) + max.month - min.month; - break; case DateIntervalType.days: return diff.inDays; - break; case DateIntervalType.hours: return diff.inHours; - break; case DateIntervalType.minutes: return diff.inMinutes; - break; case DateIntervalType.seconds: return diff.inSeconds; - break; case DateIntervalType.years: return max.year - min.year; - break; } } /// Get the date time label based on the interval and intervalType. // ignore: missing_return DateTime _getNextDate( - DateTime currentDate, DateIntervalType intervalType, double interval) { + DateTime currentDate, DateIntervalType? intervalType, double interval) { assert(intervalType != null); - switch (intervalType) { + switch (intervalType!) { case DateIntervalType.months: // Make the label start date will always be 1 other than first label. return DateTime( currentDate.year, currentDate.month + interval.ceil(), 1); - break; case DateIntervalType.days: currentDate = currentDate.add(Duration(days: interval.ceil())); return DateTime(currentDate.year, currentDate.month, currentDate.day); - break; case DateIntervalType.hours: currentDate = currentDate.add(Duration(hours: interval.ceil())); return DateTime(currentDate.year, currentDate.month, currentDate.day, currentDate.hour); - break; case DateIntervalType.minutes: return currentDate.add(Duration(minutes: interval.ceil())); - break; case DateIntervalType.seconds: return currentDate.add(Duration(seconds: interval.ceil())); - break; case DateIntervalType.years: return DateTime(currentDate.year + interval.ceil(), 1, 1); - break; } } dynamic getValueFromPosition(double position) { - double valueFactor = - ((position - actualTrackRect.left) / actualTrackRect.width) + double valueFactor = sliderType == SliderType.vertical + ? (1 - (actualTrackRect.height - position) / actualTrackRect.height) + .clamp(0.0, 1.0) + : ((position - actualTrackRect.left) / actualTrackRect.width) .clamp(0.0, 1.0); - if (_textDirection == TextDirection.rtl) { - valueFactor = 1.0 - valueFactor; + if (sliderType != SliderType.vertical) { + if (_textDirection == TextDirection.rtl) { + valueFactor = 1.0 - valueFactor; + } } - final dynamic actualValue = getValueFromFactor(valueFactor); return getActualValue(valueInDouble: actualValue); } @@ -743,15 +804,15 @@ class RenderBaseSlider extends RenderProxyBox // [valueInDouble] argument holds either [double] value for // numeric range slider and date time value in milliseconds for // date range slider. - dynamic getActualValue({dynamic value, double valueInDouble}) { + dynamic getActualValue({dynamic value, double? valueInDouble}) { if (isDiscrete) { if (!isDateTime) { final double maxMinDiff = _max - _min; value = getValueFromFactor( ((getFactorFromValue(valueInDouble ?? value) * - (maxMinDiff / _stepSize)) + (maxMinDiff / _stepSize!)) .round() / - (maxMinDiff / _stepSize)) + (maxMinDiff / _stepSize!)) .clamp(0.0, 1.0)); } else { DateTime currentDate = _min; @@ -761,12 +822,12 @@ class RenderBaseSlider extends RenderProxyBox for (double i = actualMin; i < actualMax;) { nextDate = DateTime( - currentDate.year + _stepDuration.years, - currentDate.month + _stepDuration.months, - currentDate.day + _stepDuration.days, - currentDate.hour + _stepDuration.days, - currentDate.minute + _stepDuration.minutes, - currentDate.second + _stepDuration.seconds); + currentDate.year + _stepDuration!.years, + currentDate.month + _stepDuration!.months, + currentDate.day + _stepDuration!.days, + currentDate.hour + _stepDuration!.days, + currentDate.minute + _stepDuration!.minutes, + currentDate.second + _stepDuration!.seconds); final double currentDateInms = currentDate.millisecondsSinceEpoch.toDouble(); @@ -788,7 +849,7 @@ class RenderBaseSlider extends RenderProxyBox } return isDateTime - ? (value ?? DateTime.fromMillisecondsSinceEpoch(valueInDouble.toInt())) + ? (value ?? DateTime.fromMillisecondsSinceEpoch(valueInDouble!.toInt())) : value ?? valueInDouble; } @@ -797,11 +858,16 @@ class RenderBaseSlider extends RenderProxyBox } double getFactorFromCurrentPosition() { - assert(currentX != null); - final double factor = - ((currentX - actualTrackRect.left) / actualTrackRect.width) + final double factor = sliderType == SliderType.vertical + ? ((actualTrackRect.bottom - mainAxisOffset) / actualTrackRect.height) + .clamp(0.0, 1.0) + : ((mainAxisOffset - actualTrackRect.left) / actualTrackRect.width) .clamp(0.0, 1.0); - return (_textDirection == TextDirection.rtl) ? 1.0 - factor : factor; + if (sliderType == SliderType.vertical) { + return factor; + } else { + return (_textDirection == TextDirection.rtl) ? 1.0 - factor : factor; + } } Rect getRectangularTooltipRect(TextPainter textPainter, Offset offset, @@ -890,32 +956,44 @@ class RenderBaseSlider extends RenderProxyBox PaintingContext context, Rect trackRect, Offset offset, - Offset thumbCenter, - Offset startThumbCenter, - Offset endThumbCenter, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, Animation stateAnimation, dynamic value, - SfRangeValues values) { + SfRangeValues? values) { int dateTimePos = 0; - final double dx = trackRect.left; - final double dy = trackRect.top; - final double halfTrackHeight = trackRect.height / 2; + final double dx = + sliderType == SliderType.vertical ? trackRect.bottom : trackRect.left; + final double dy = + sliderType == SliderType.vertical ? trackRect.left : trackRect.top; + final double halfTrackHeight = sliderType == SliderType.vertical + ? trackRect.width / 2 + : trackRect.height / 2; + final bool isActive = startThumbCenter != null - ? offset.dx >= startThumbCenter.dx && offset.dx <= endThumbCenter.dx - : offset.dx <= thumbCenter.dx; + ? (sliderType == SliderType.vertical + ? offset.dy <= startThumbCenter.dy && + offset.dy >= endThumbCenter!.dy + : offset.dx >= startThumbCenter.dx && + offset.dx <= endThumbCenter!.dx) + : (sliderType == SliderType.vertical + ? offset.dy <= thumbCenter!.dy + : offset.dx <= thumbCenter!.dx); final double divisorRadius = _divisorShape .getPreferredSize(_sliderThemeData, isActive: isActive) .width / 2; - final double tickRadius = - _tickShape.getPreferredSize(_sliderThemeData).width / 2; + final double tickRadius = sliderType == SliderType.vertical + ? _tickShape.getPreferredSize(_sliderThemeData).height / 2 + : _tickShape.getPreferredSize(_sliderThemeData).width / 2; double textValue = isDateTime ? 0.0 : _min; int minorTickIndex = 0; final double maxRange = isDateTime ? divisions : _max; - if (divisions != null && divisions > 0) { + if (divisions != null && divisions! > 0) { while (textValue <= maxRange) { final double tickPosition = _majorTickPositions[dateTimePos]; @@ -937,7 +1015,7 @@ class RenderBaseSlider extends RenderProxyBox stateAnimation); } - if (_interval != null && _interval > 0) { + if (_interval != null && _interval! > 0) { // Drawing minor ticks. if (_minorTicksPerInterval > 0) { for (int j = 0; j < _minorTicksPerInterval; j++) { @@ -981,19 +1059,34 @@ class RenderBaseSlider extends RenderProxyBox // Drawing label. if (_showLabels) { - final double dx = trackRect.left; + final double dx = sliderType == SliderType.vertical + ? trackRect.bottom + : trackRect.left; + final bool isRTL = _textDirection == TextDirection.rtl; - double offsetX = dx + tickPosition; + double offsetX = sliderType == SliderType.vertical + ? dx - tickPosition + : dx + tickPosition; + if (_labelPlacement == LabelPlacement.betweenTicks) { - offsetX += ((dateTimePos + 1 <= divisions - ? _majorTickPositions[dateTimePos + 1] - : (isRTL ? trackRect.left : trackRect.width)) - - tickPosition) / - 2; - if (isRTL - ? offsetX <= trackRect.left - : offsetX - dx >= trackRect.width) { - break; + if (sliderType == SliderType.vertical) { + if (dateTimePos + 1 <= divisions!) { + offsetX -= + ((_majorTickPositions[dateTimePos + 1]) - tickPosition) / 2; + } else { + break; + } + } else { + offsetX += ((dateTimePos + 1 <= divisions! + ? _majorTickPositions[dateTimePos + 1] + : (isRTL ? trackRect.left : trackRect.width)) - + tickPosition) / + 2; + if (isRTL + ? offsetX <= trackRect.left + : offsetX - dx >= trackRect.width) { + break; + } } } @@ -1018,7 +1111,7 @@ class RenderBaseSlider extends RenderProxyBox // interval as _max - _min. final double intervalDiff = isDateTime ? 1 - : _interval != null && _interval > 0 + : _interval != null && _interval! > 0 ? _interval : _max - _min; textValue += intervalDiff; @@ -1035,24 +1128,41 @@ class RenderBaseSlider extends RenderProxyBox int dateTimePos, double tickRadius, PaintingContext context, - Offset thumbCenter, - Offset startThumbCenter, - Offset endThumbCenter, - value, - SfRangeValues values, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, + dynamic value, + SfRangeValues? values, Animation stateAnimation) { - Offset actualTickOffset = Offset(dx + tickPosition, dy + trackRect.height) + - (_sliderThemeData.tickOffset ?? const Offset(0, 0)); + Offset actualTickOffset = sliderType == SliderType.vertical + ? Offset(dy + trackRect.width, dx - tickPosition) + + (_sliderThemeData.tickOffset ?? Offset.zero) + : Offset(dx + tickPosition, dy + trackRect.height) + + (_sliderThemeData.tickOffset ?? Offset.zero); + if (_majorTickPositions[dateTimePos] == 0.0) { - actualTickOffset = - Offset(dx + tickPosition + tickRadius, dy + trackRect.height) + - (_sliderThemeData.tickOffset ?? const Offset(0, 0)); - } else if (_majorTickPositions[dateTimePos] == trackRect.width) { - actualTickOffset = - Offset(dx + tickPosition - tickRadius, dy + trackRect.height) + - (_sliderThemeData.tickOffset ?? const Offset(0, 0)); + actualTickOffset = sliderType == SliderType.vertical + ? Offset(dy + trackRect.width, dx - tickPosition - tickRadius) + + (_sliderThemeData.tickOffset ?? Offset.zero) + : Offset(dx + tickPosition + tickRadius, dy + trackRect.height) + + (_sliderThemeData.tickOffset ?? Offset.zero); } + // Due to floating-point operations, last [_majorTickPosition] is greater + // than [trackRect.height] or [trackRect.width]. This happens in some + // specific layouts alone. To avoid this, we limit it to 8 decimal points. + // (e.g. Expected [_majorTickPosition] = 100.0909890118 + // Current [_majorTickPosition] = 100.0909890121) + else if (_majorTickPositions[dateTimePos].toStringAsFixed(8) == + (sliderType == SliderType.vertical + ? trackRect.height.toStringAsFixed(8) + : trackRect.width.toStringAsFixed(8))) { + actualTickOffset = sliderType == SliderType.vertical + ? Offset(dy + trackRect.width, dx - tickPosition + tickRadius) + + (_sliderThemeData.tickOffset ?? Offset.zero) + : Offset(dx + tickPosition - tickRadius, dy + trackRect.height) + + (_sliderThemeData.tickOffset ?? Offset.zero); + } _tickShape.paint(context, actualTickOffset, thumbCenter, startThumbCenter, endThumbCenter, parentBox: this, @@ -1069,17 +1179,22 @@ class RenderBaseSlider extends RenderProxyBox double dx, double dy, PaintingContext context, - Offset thumbCenter, - Offset startThumbCenter, - Offset endThumbCenter, - value, - SfRangeValues values, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, + dynamic value, + SfRangeValues? values, Animation stateAnimation) { - if (currentMinorTickPosition < trackRect.width && + if (currentMinorTickPosition < + (sliderType == SliderType.vertical + ? trackRect.height + : trackRect.width) && currentMinorTickPosition > 0) { - final Offset actualTickOffset = - Offset(dx + currentMinorTickPosition, dy + trackRect.height) + - (_sliderThemeData.tickOffset ?? const Offset(0, 0)); + final Offset actualTickOffset = sliderType == SliderType.vertical + ? Offset(dy + trackRect.width, dx - currentMinorTickPosition) + + (_sliderThemeData.tickOffset ?? Offset.zero) + : Offset(dx + currentMinorTickPosition, dy + trackRect.height) + + (_sliderThemeData.tickOffset ?? Offset.zero); _minorTickShape.paint(context, actualTickOffset, thumbCenter, startThumbCenter, endThumbCenter, parentBox: this, @@ -1100,19 +1215,33 @@ class RenderBaseSlider extends RenderProxyBox double divisorRadius, Rect trackRect, PaintingContext context, - Offset thumbCenter, - Offset startThumbCenter, - Offset endThumbCenter, - value, - SfRangeValues values, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, + dynamic value, + SfRangeValues? values, Animation stateAnimation) { - Offset divisorCenter = Offset(dx + tickPosition, dy + halfTrackHeight); + Offset divisorCenter = sliderType == SliderType.vertical + ? Offset(dy + halfTrackHeight, dx - tickPosition) + : Offset(dx + tickPosition, dy + halfTrackHeight); if (_majorTickPositions[_dateTimePos] == 0.0) { - divisorCenter = - Offset(dx + tickPosition + divisorRadius, dy + halfTrackHeight); - } else if (_majorTickPositions[_dateTimePos] == trackRect.width) { - divisorCenter = - Offset(dx + tickPosition - divisorRadius, dy + halfTrackHeight); + divisorCenter = sliderType == SliderType.vertical + ? Offset(dy + halfTrackHeight, dx - tickPosition - divisorRadius) + : Offset(dx + tickPosition + divisorRadius, dy + halfTrackHeight); + } + + // Due to floating-point operations, last [_majorTickPosition] is greater + // than [trackRect.height] or [trackRect.width]. This happens in some + // specific layouts alone. To avoid this, we limit it to 8 decimal points. + // (e.g. Expected [_majorTickPosition] = 100.0909890118 + // Current [_majorTickPosition] = 100.0909890121) + else if (_majorTickPositions[_dateTimePos].toStringAsFixed(8) == + (sliderType == SliderType.vertical + ? trackRect.height.toStringAsFixed(8) + : trackRect.width.toStringAsFixed(8))) { + divisorCenter = sliderType == SliderType.vertical + ? Offset(dy + halfTrackHeight, dx - tickPosition + divisorRadius) + : Offset(dx + tickPosition - divisorRadius, dy + halfTrackHeight); } _divisorShape.paint( context, divisorCenter, thumbCenter, startThumbCenter, endThumbCenter, @@ -1121,7 +1250,8 @@ class RenderBaseSlider extends RenderProxyBox currentValue: value, currentValues: values, enableAnimation: stateAnimation, - textDirection: _textDirection); + textDirection: _textDirection, + paint: null); } void _drawLabel( @@ -1131,18 +1261,21 @@ class RenderBaseSlider extends RenderProxyBox Rect trackRect, double dy, PaintingContext context, - Offset thumbCenter, - Offset startThumbCenter, - Offset endThumbCenter, - value, - SfRangeValues values, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, + dynamic value, + SfRangeValues? values, Animation stateAnimation, double offsetX) { - final double dy = trackRect.top; + final double dy = + sliderType == SliderType.vertical ? trackRect.left : trackRect.top; final String labelText = _visibleLabels[_dateTimePos]; - final Offset actualLabelOffset = - Offset(offsetX, dy + trackRect.height + actualTickHeight) + - (_sliderThemeData.labelOffset ?? const Offset(0, 0)); + final Offset actualLabelOffset = sliderType == SliderType.vertical + ? Offset(dy + trackRect.width + actualTickWidth, offsetX) + + (_sliderThemeData.labelOffset ?? Offset.zero) + : Offset(offsetX, dy + trackRect.height + actualTickHeight) + + (_sliderThemeData.labelOffset ?? Offset.zero); _drawText(context, actualLabelOffset, thumbCenter, startThumbCenter, endThumbCenter, labelText, @@ -1155,28 +1288,40 @@ class RenderBaseSlider extends RenderProxyBox textDirection: _textDirection); } - void _drawText(PaintingContext context, Offset center, Offset thumbCenter, - Offset startThumbCenter, Offset endThumbCenter, String text, - {RenderProxyBox parentBox, - SfSliderThemeData themeData, + void _drawText(PaintingContext context, Offset center, Offset? thumbCenter, + Offset? startThumbCenter, Offset? endThumbCenter, String text, + {required RenderProxyBox parentBox, + required SfSliderThemeData themeData, dynamic currentValue, - SfRangeValues currentValues, - Animation enableAnimation, - TextPainter textPainter, - TextDirection textDirection}) { + SfRangeValues? currentValues, + required Animation enableAnimation, + required TextPainter textPainter, + required TextDirection textDirection}) { bool isInactive; switch (textDirection) { case TextDirection.ltr: // Added this condition to check whether consider single thumb or // two thumbs for finding inactive range. isInactive = startThumbCenter != null - ? center.dx < startThumbCenter.dx || center.dx > endThumbCenter.dx - : center.dx > thumbCenter.dx; + ? (sliderType == SliderType.vertical + ? center.dy > startThumbCenter.dy || + center.dy < endThumbCenter!.dy + : center.dx < startThumbCenter.dx || + center.dx > endThumbCenter!.dx) + : (sliderType == SliderType.vertical + ? center.dy < thumbCenter!.dy + : center.dx > thumbCenter!.dx); break; case TextDirection.rtl: isInactive = startThumbCenter != null - ? center.dx > startThumbCenter.dx || center.dx < endThumbCenter.dx - : center.dx < thumbCenter.dx; + ? (sliderType == SliderType.vertical + ? center.dy > startThumbCenter.dy || + center.dy < endThumbCenter!.dy + : center.dx < startThumbCenter.dx || + center.dx > endThumbCenter!.dx) + : (sliderType == SliderType.vertical + ? center.dy < thumbCenter!.dy + : center.dx < thumbCenter!.dx); break; } @@ -1188,20 +1333,23 @@ class RenderBaseSlider extends RenderProxyBox ); textPainter.text = textSpan; textPainter.layout(); - textPainter.paint( - context.canvas, Offset(center.dx - textPainter.width / 2, center.dy)); + if (sliderType == SliderType.vertical) { + textPainter.paint(context.canvas, + Offset(center.dx, center.dy - textPainter.height / 2)); + } else { + textPainter.paint( + context.canvas, Offset(center.dx - textPainter.width / 2, center.dy)); + } } dynamic getNextSemanticValue(dynamic value, dynamic semanticActionUnit, - {double actualValue}) { + {required double actualValue}) { if (isDateTime) { if (_stepDuration == null) { - if (actualValue != null) { - return DateTime.fromMillisecondsSinceEpoch( - (actualValue + semanticActionUnit) - .clamp(actualMin, actualMax) - .toInt()); - } + return DateTime.fromMillisecondsSinceEpoch( + (actualValue + semanticActionUnit) + .clamp(actualMin, actualMax) + .toInt()); } else { final DateTime nextDate = DateTime( value.year + semanticActionUnit.years, @@ -1220,15 +1368,13 @@ class RenderBaseSlider extends RenderProxyBox } dynamic getPrevSemanticValue(dynamic value, dynamic semanticActionUnit, - {double actualValue}) { + {required double actualValue}) { if (isDateTime) { if (_stepDuration == null) { - if (actualValue != null) { - return DateTime.fromMillisecondsSinceEpoch( - (actualValue - semanticActionUnit) - .clamp(actualMin, actualMax) - .toInt()); - } + return DateTime.fromMillisecondsSinceEpoch( + (actualValue - semanticActionUnit) + .clamp(actualMin, actualMax) + .toInt()); } else { final DateTime prevDate = DateTime( value.year - semanticActionUnit.years, @@ -1246,6 +1392,60 @@ class RenderBaseSlider extends RenderProxyBox } } + // This method is only applicable for vertical sliders + Size _textSize(String text, double fontSize) { + final TextPainter textPainter = TextPainter( + text: TextSpan(text: text, style: TextStyle(fontSize: fontSize)), + maxLines: 1, + textDirection: TextDirection.ltr) + ..layout(minWidth: 0, maxWidth: double.infinity); + return textPainter.size; + } + + // This method is only applicable for vertical sliders + double _maximumLabelWidth() { + double maxLabelWidth = 0.0; + if (_showLabels && _interval != null && _interval! > 0) { + String label; + dynamic currentValue = _min; + double labelLength; + divisions = (isDateTime + ? _getDateTimeDifference(_min, _max, _dateIntervalType) + : _max - _min) + .toDouble() / + _interval; + for (dynamic i = 0; i <= divisions; i++) { + label = _labelFormatterCallback( + currentValue, getFormattedText(currentValue)); + + labelLength = _textSize(label, maximumFontSize).width; + + if (maxLabelWidth < labelLength) { + maxLabelWidth = labelLength; + } + currentValue = isDateTime + ? _getNextDate(currentValue, _dateIntervalType, _interval!) + : currentValue + _interval; + } + } else if (_showLabels) { + maxLabelWidth = _edgeLabelWidth(); + } + return maxLabelWidth; + } + + // This method is only applicable for vertical sliders + double _edgeLabelWidth() { + String minLabel; + String maxLabel; + double maxLabelWidth; + minLabel = _labelFormatterCallback(_min, getFormattedText(_min)); + maxLabel = _labelFormatterCallback(_max, getFormattedText(_max)); + final double minLabelLength = _textSize(minLabel, maximumFontSize).width; + final double maxLabelLength = _textSize(maxLabel, maximumFontSize).width; + maxLabelWidth = math.max(minLabelLength, maxLabelLength); + return maxLabelWidth; + } + @override void systemFontsDidChange() { super.systemFontsDidChange(); @@ -1255,19 +1455,32 @@ class RenderBaseSlider extends RenderProxyBox @override void performLayout() { + // Here, the required width for the rendering of the + // vertical sliders is also considered as actualHeight. actualHeight = math.max( 2 * trackOffset.dy, trackOffset.dy + maxTrackHeight / 2 + - math.max(actualTickHeight, actualMinorTickHeight) + - actualLabelHeight); - - size = Size( - constraints.hasBoundedWidth - ? constraints.maxWidth - : minTrackWidth + 2 * trackOffset.dx, - constraints.hasBoundedHeight ? constraints.maxHeight : actualHeight, - ); + (sliderType == SliderType.vertical + ? math.max(actualTickWidth, actualMinorTickWidth) + + _maximumLabelWidth() + + actualLabelOffset + : math.max(actualTickHeight, actualMinorTickHeight) + + actualLabelHeight)); + + size = sliderType == SliderType.vertical + ? Size( + constraints.hasBoundedWidth ? constraints.maxWidth : actualHeight, + constraints.hasBoundedHeight + ? constraints.maxHeight + : minTrackWidth + 2 * trackOffset.dx) + : Size( + constraints.hasBoundedWidth + ? constraints.maxWidth + : minTrackWidth + 2 * trackOffset.dx, + constraints.hasBoundedHeight + ? constraints.maxHeight + : actualHeight); generateLabelsAndMajorTicks(); generateMinorTicks(); } @@ -1276,7 +1489,11 @@ class RenderBaseSlider extends RenderProxyBox void handleEvent(PointerEvent event, HitTestEntry entry) { // Checked [_isInteractionEnd] for avoiding multi touch. if (isInteractionEnd && event.down && event is PointerDownEvent) { - dragGestureRecognizer.addPointer(event); + if (sliderType == SliderType.vertical) { + verticalDragGestureRecognizer!.addPointer(event); + } else { + horizontalDragGestureRecognizer!.addPointer(event); + } tapGestureRecognizer.addPointer(event); } } diff --git a/packages/syncfusion_flutter_sliders/lib/src/slider.dart b/packages/syncfusion_flutter_sliders/lib/src/slider.dart index cc7d16580..24d06a746 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/slider.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/slider.dart @@ -4,11 +4,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:intl/intl.dart' show DateFormat, NumberFormat; import 'package:flutter/material.dart'; -import 'package:flutter/gestures.dart' - show - GestureArenaTeam, - TapGestureRecognizer, - HorizontalDragGestureRecognizer; +import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/theme.dart'; @@ -20,7 +16,8 @@ import 'slider_shapes.dart'; /// A Material Design slider. /// /// Used to select a value between [min] and [max]. -/// It supports both numeric and date values. +/// It supports horizontal and vertical orientations. +/// It also supports both numeric and date values. /// /// The slider elements are: /// @@ -109,44 +106,136 @@ import 'slider_shapes.dart'; /// date labels. /// * [SfSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfSliderThemeData-class.html), for customizing the visual appearance of the slider. class SfSlider extends StatefulWidget { - /// Creates a [SfSlider]. + /// Creates a horizontal [SfSlider]. const SfSlider( - {Key key, + {Key? key, this.min = 0.0, this.max = 1.0, - @required this.value, - @required this.onChanged, + required this.value, + required this.onChanged, this.interval, this.stepSize, this.stepDuration, - this.minorTicksPerInterval, + this.minorTicksPerInterval = 0, this.showTicks = false, this.showLabels = false, this.showDivisors = false, this.enableTooltip = false, this.activeColor, this.inactiveColor, - this.labelPlacement, + this.labelPlacement = LabelPlacement.onTicks, this.numberFormat, this.dateFormat, this.dateIntervalType, this.labelFormatterCallback, this.tooltipTextFormatterCallback, this.semanticFormatterCallback, - this.trackShape, - this.divisorShape, - this.overlayShape, - this.thumbShape, - this.tickShape, - this.minorTickShape, - this.tooltipShape, + this.trackShape = const SfTrackShape(), + this.divisorShape = const SfDivisorShape(), + this.overlayShape = const SfOverlayShape(), + this.thumbShape = const SfThumbShape(), + this.tickShape = const SfTickShape(), + this.minorTickShape = const SfMinorTickShape(), + this.tooltipShape = const SfRectangularTooltipShape(), this.thumbIcon}) - : assert(min != null), + : _sliderType = SliderType.horizontal, + _tooltipPosition = null, + assert(min != null), assert(max != null), assert(min != max), assert(interval == null || interval > 0), super(key: key); + /// Creates a vertical [SfSlider]. + /// + /// ## TooltipPosition + /// + /// Enables tooltip in left or right position for vertical slider. + /// + /// Defaults to [SliderTooltipPosition.left]. + /// + /// ## Example + /// + /// This snippet shows how to create a vertical SfSlider with right side + /// tooltip. + /// + /// ```dart + /// double _value = 4.0; + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: Center( + /// child: SfSlider.vertical( + /// min: 0.0, + /// max: 10.0, + /// value: _value, + /// enableTooltip: true, + /// tooltipPosition: SliderTooltipPosition.right, + /// onChanged: (dynamic newValue) { + /// setState(() { + /// _value = newValue; + /// }); + /// }, + /// ) + /// ) + /// ) + /// ); + /// } + /// ``` + /// + /// See also: + /// + /// * Check the default constructor for horizontal slider. + const SfSlider.vertical( + {Key? key, + this.min = 0.0, + this.max = 1.0, + required this.value, + required this.onChanged, + this.interval, + this.stepSize, + this.stepDuration, + this.minorTicksPerInterval = 0, + this.showTicks = false, + this.showLabels = false, + this.showDivisors = false, + this.enableTooltip = false, + this.activeColor, + this.inactiveColor, + this.labelPlacement = LabelPlacement.onTicks, + this.numberFormat, + this.dateFormat, + this.dateIntervalType, + this.labelFormatterCallback, + this.tooltipTextFormatterCallback, + this.semanticFormatterCallback, + this.trackShape = const SfTrackShape(), + this.divisorShape = const SfDivisorShape(), + this.overlayShape = const SfOverlayShape(), + this.thumbShape = const SfThumbShape(), + this.tickShape = const SfTickShape(), + this.minorTickShape = const SfMinorTickShape(), + this.tooltipShape = const SfRectangularTooltipShape(), + this.thumbIcon, + SliderTooltipPosition tooltipPosition = SliderTooltipPosition.left}) + : _sliderType = SliderType.vertical, + _tooltipPosition = tooltipPosition, + assert(tooltipShape is! SfPaddleTooltipShape), + assert(min != null), + assert(max != null), + assert(min != max), + assert(interval == null || interval > 0), + super(key: key); + + /// This is used to determine the type of the slider which is horizontal or + /// vertical. + final SliderType _sliderType; + + /// This is only applicable for vertical sliders. + final SliderTooltipPosition? _tooltipPosition; + /// The minimum value that the user can select. /// /// Defaults to 0.0. Must be less than the [max]. @@ -233,7 +322,7 @@ class SfSlider extends StatefulWidget { /// }, /// ) /// ``` - final ValueChanged onChanged; + final ValueChanged? onChanged; /// Splits the slider into given interval. /// It is mandatory if labels, major ticks and divisors are needed. @@ -302,7 +391,7 @@ class SfSlider extends StatefulWidget { /// * [showDivisors], to render divisors at given interval. /// * [showTicks], to render major ticks at given interval. /// * [showLabels], to render labels at given interval. - final double interval; + final double? interval; /// Option to select discrete value. /// @@ -330,7 +419,7 @@ class SfSlider extends StatefulWidget { /// }, /// ) /// ``` - final double stepSize; + final double? stepSize; /// Option to select discrete date values. /// @@ -373,7 +462,7 @@ class SfSlider extends StatefulWidget { /// * [interval], for setting the interval. /// * [dateIntervalType], for changing the interval type. /// * [dateFormat] for formatting the date labels. - final SliderStepDuration stepDuration; + final SliderStepDuration? stepDuration; /// Number of smaller ticks between two major ticks. /// @@ -577,7 +666,7 @@ class SfSlider extends StatefulWidget { /// /// * [SfSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfSliderThemeData-class.html), for customizing the individual /// inactive slider element’s visual. - final Color inactiveColor; + final Color? inactiveColor; /// Color applied to the active track, thumb, overlay, and inactive divisors. /// @@ -607,7 +696,7 @@ class SfSlider extends StatefulWidget { /// See also: /// /// * [SfSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfSliderThemeData-class.html), for customizing the individual active slider element’s visual. - final Color activeColor; + final Color? activeColor; /// Option to place the labels either between the major ticks or /// on the major ticks. @@ -664,7 +753,7 @@ class SfSlider extends StatefulWidget { /// See also: /// /// * [labelFormatterCallback], for formatting the numeric and date labels. - final NumberFormat numberFormat; + final NumberFormat? numberFormat; /// Formats the date labels. It is mandatory for date [SfSlider]. /// @@ -703,7 +792,7 @@ class SfSlider extends StatefulWidget { /// * [numberFormat], for formatting the numeric labels. /// * [labelFormatterCallback], for formatting the numeric and date label. /// * [dateIntervalType], for changing the interval type. - final DateFormat dateFormat; + final DateFormat? dateFormat; /// The type of date interval. It is mandatory for date [SfSlider]. /// @@ -735,7 +824,7 @@ class SfSlider extends StatefulWidget { /// }, /// ) /// ``` - final DateIntervalType dateIntervalType; + final DateIntervalType? dateIntervalType; /// Signature for formatting or changing the whole numeric or date label text. /// @@ -766,7 +855,7 @@ class SfSlider extends StatefulWidget { /// }, /// ) /// ``` - final LabelFormatterCallback labelFormatterCallback; + final LabelFormatterCallback? labelFormatterCallback; /// Signature for formatting or changing the whole tooltip label text. /// @@ -800,7 +889,7 @@ class SfSlider extends StatefulWidget { /// }, /// ) /// ``` - final TooltipTextFormatterCallback tooltipTextFormatterCallback; + final TooltipTextFormatterCallback? tooltipTextFormatterCallback; /// The callback used to create a semantic value from the slider's value. /// @@ -829,7 +918,7 @@ class SfSlider extends StatefulWidget { /// }, /// ) /// ``` - final SfSliderSemanticFormatterCallback semanticFormatterCallback; + final SfSliderSemanticFormatterCallback? semanticFormatterCallback; /// Base class for [SfSlider] track shapes. final SfTrackShape trackShape; @@ -912,7 +1001,7 @@ class SfSlider extends StatefulWidget { /// /// * [thumbShape], for customizing the thumb shape. /// * [SfSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfSliderThemeData-class.html), for customizing the individual active slider element’s visual. - final Widget thumbIcon; + final Widget? thumbIcon; @override _SfSliderState createState() => _SfSliderState(); @@ -929,7 +1018,7 @@ class SfSlider extends StatefulWidget { properties.add(DoubleProperty('interval', interval)); properties.add(DoubleProperty('stepSize', stepSize)); if (stepDuration != null) { - properties.add(stepDuration.toDiagnosticsNode(name: 'stepDuration')); + properties.add(stepDuration!.toDiagnosticsNode(name: 'stepDuration')); } properties.add(IntProperty('minorTicksPerInterval', minorTicksPerInterval)); properties.add(FlagProperty('showTicks', @@ -958,9 +1047,11 @@ class SfSlider extends StatefulWidget { .add(EnumProperty('labelPlacement', labelPlacement)); properties .add(DiagnosticsProperty('numberFormat', numberFormat)); - if (value.runtimeType == DateTime) { - properties.add(StringProperty('dateFormat', - 'Formatted value is ' + (dateFormat.format(value.start)).toString())); + if (value.runtimeType == DateTime && dateFormat != null) { + properties.add(StringProperty( + 'dateFormat', + 'Formatted value is ' + + (dateFormat!.format(value.start)).toString())); } properties.add( EnumProperty('dateIntervalType', dateIntervalType)); @@ -974,15 +1065,15 @@ class SfSlider extends StatefulWidget { } class _SfSliderState extends State with TickerProviderStateMixin { - AnimationController overlayController; - AnimationController stateController; - AnimationController tooltipAnimationController; - Timer tooltipDelayTimer; + late AnimationController overlayController; + late AnimationController stateController; + late AnimationController tooltipAnimationController; + Timer? tooltipDelayTimer; final Duration duration = const Duration(milliseconds: 100); void _onChanged(dynamic value) { if (value != widget.value) { - widget.onChanged(value); + widget.onChanged!(value); } } @@ -1003,23 +1094,19 @@ class _SfSliderState extends State with TickerProviderStateMixin { sliderThemeData = sliderThemeData.copyWith( activeTrackHeight: sliderThemeData.activeTrackHeight, inactiveTrackHeight: sliderThemeData.inactiveTrackHeight, - tickSize: sliderThemeData.tickSize, - minorTickSize: sliderThemeData.minorTickSize, tickOffset: sliderThemeData.tickOffset, - labelOffset: sliderThemeData.labelOffset ?? - (widget.showTicks ? const Offset(0.0, 5.0) : const Offset(0.0, 13.0)), inactiveLabelStyle: sliderThemeData.inactiveLabelStyle ?? - themeData.textTheme.bodyText1.copyWith( + themeData.textTheme.bodyText1!.copyWith( color: isActive - ? themeData.textTheme.bodyText1.color.withOpacity(0.87) + ? themeData.textTheme.bodyText1!.color!.withOpacity(0.87) : themeData.colorScheme.onSurface.withOpacity(0.32)), activeLabelStyle: sliderThemeData.activeLabelStyle ?? - themeData.textTheme.bodyText1.copyWith( + themeData.textTheme.bodyText1!.copyWith( color: isActive - ? themeData.textTheme.bodyText1.color.withOpacity(0.87) + ? themeData.textTheme.bodyText1!.color!.withOpacity(0.87) : themeData.colorScheme.onSurface.withOpacity(0.32)), tooltipTextStyle: sliderThemeData.tooltipTextStyle ?? - themeData.textTheme.bodyText1.copyWith( + themeData.textTheme.bodyText1!.copyWith( color: sliderThemeData.brightness == Brightness.light ? const Color.fromRGBO(255, 255, 255, 1) : const Color.fromRGBO(0, 0, 0, 1)), @@ -1083,8 +1170,23 @@ class _SfSliderState extends State with TickerProviderStateMixin { activeDivisorStrokeWidth: sliderThemeData.activeDivisorStrokeWidth, inactiveDivisorStrokeWidth: sliderThemeData.inactiveDivisorStrokeWidth, ); - - return sliderThemeData; + if (widget._sliderType == SliderType.vertical) { + return sliderThemeData.copyWith( + tickSize: sliderThemeData.tickSize ?? const Size(8.0, 1.0), + minorTickSize: sliderThemeData.minorTickSize ?? const Size(5.0, 1.0), + labelOffset: sliderThemeData.labelOffset ?? + (widget.showTicks + ? const Offset(5.0, 0.0) + : const Offset(13.0, 0.0))); + } else { + return sliderThemeData.copyWith( + tickSize: sliderThemeData.tickSize ?? const Size(1.0, 8.0), + minorTickSize: sliderThemeData.minorTickSize ?? const Size(1.0, 5.0), + labelOffset: sliderThemeData.labelOffset ?? + (widget.showTicks + ? const Offset(0.0, 5.0) + : const Offset(0.0, 13.0))); + } } @override @@ -1100,9 +1202,9 @@ class _SfSliderState extends State with TickerProviderStateMixin { @override void dispose() { - overlayController?.dispose(); - stateController?.dispose(); - tooltipAnimationController?.dispose(); + overlayController.dispose(); + stateController.dispose(); + tooltipAnimationController.dispose(); super.dispose(); } @@ -1122,7 +1224,7 @@ class _SfSliderState extends State with TickerProviderStateMixin { interval: widget.interval, stepSize: widget.stepSize, stepDuration: widget.stepDuration, - minorTicksPerInterval: widget.minorTicksPerInterval ?? 0, + minorTicksPerInterval: widget.minorTicksPerInterval, showTicks: widget.showTicks, showLabels: widget.showLabels, showDivisors: widget.showDivisors, @@ -1130,7 +1232,7 @@ class _SfSliderState extends State with TickerProviderStateMixin { inactiveColor: widget.inactiveColor ?? themeData.primaryColor.withOpacity(0.24), activeColor: widget.activeColor ?? themeData.primaryColor, - labelPlacement: widget.labelPlacement ?? LabelPlacement.onTicks, + labelPlacement: widget.labelPlacement, numberFormat: widget.numberFormat ?? NumberFormat('#.##'), dateIntervalType: widget.dateIntervalType, dateFormat: widget.dateFormat, @@ -1139,62 +1241,68 @@ class _SfSliderState extends State with TickerProviderStateMixin { tooltipTextFormatterCallback: widget.tooltipTextFormatterCallback ?? _getFormattedTooltipText, semanticFormatterCallback: widget.semanticFormatterCallback, - trackShape: widget.trackShape ?? SfTrackShape(), - divisorShape: widget.divisorShape ?? SfDivisorShape(), - overlayShape: widget.overlayShape ?? SfOverlayShape(), - thumbShape: widget.thumbShape ?? SfThumbShape(), - tickShape: widget.tickShape ?? SfTickShape(), - minorTickShape: widget.minorTickShape ?? SfMinorTickShape(), - tooltipShape: widget.tooltipShape ?? SfRectangularTooltipShape(), + trackShape: widget.trackShape, + divisorShape: widget.divisorShape, + overlayShape: widget.overlayShape, + thumbShape: widget.thumbShape, + tickShape: widget.tickShape, + minorTickShape: widget.minorTickShape, + tooltipShape: widget.tooltipShape, sliderThemeData: _getSliderThemeData(themeData, isActive), thumbIcon: widget.thumbIcon, + tooltipPosition: widget._tooltipPosition, + sliderType: widget._sliderType, state: this); } } class _SliderRenderObjectWidget extends RenderObjectWidget { const _SliderRenderObjectWidget( - {Key key, - this.min, - this.max, - this.value, - this.onChanged, - this.interval, - this.stepSize, - this.stepDuration, - this.minorTicksPerInterval, - this.showTicks, - this.showLabels, - this.showDivisors, - this.enableTooltip, - this.inactiveColor, - this.activeColor, - this.labelPlacement, - this.numberFormat, - this.dateFormat, - this.dateIntervalType, - this.labelFormatterCallback, - this.tooltipTextFormatterCallback, - this.semanticFormatterCallback, - this.trackShape, - this.divisorShape, - this.overlayShape, - this.thumbShape, - this.tickShape, - this.minorTickShape, - this.tooltipShape, - this.sliderThemeData, - this.thumbIcon, - this.state}) + {Key? key, + required this.min, + required this.max, + required this.value, + required this.onChanged, + required this.interval, + required this.stepSize, + required this.stepDuration, + required this.minorTicksPerInterval, + required this.showTicks, + required this.showLabels, + required this.showDivisors, + required this.enableTooltip, + required this.inactiveColor, + required this.activeColor, + required this.labelPlacement, + required this.numberFormat, + required this.dateFormat, + required this.dateIntervalType, + required this.labelFormatterCallback, + required this.tooltipTextFormatterCallback, + required this.semanticFormatterCallback, + required this.trackShape, + required this.divisorShape, + required this.overlayShape, + required this.thumbShape, + required this.tickShape, + required this.minorTickShape, + required this.tooltipShape, + required this.sliderThemeData, + required this.thumbIcon, + required this.state, + required this.sliderType, + required this.tooltipPosition}) : super(key: key); + final SliderType sliderType; + final SliderTooltipPosition? tooltipPosition; final dynamic min; final dynamic max; final dynamic value; - final ValueChanged onChanged; - final double interval; - final double stepSize; - final SliderStepDuration stepDuration; + final ValueChanged? onChanged; + final double? interval; + final double? stepSize; + final SliderStepDuration? stepDuration; final int minorTicksPerInterval; final bool showTicks; @@ -1207,12 +1315,12 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { final LabelPlacement labelPlacement; final NumberFormat numberFormat; - final DateIntervalType dateIntervalType; - final DateFormat dateFormat; + final DateIntervalType? dateIntervalType; + final DateFormat? dateFormat; final SfSliderThemeData sliderThemeData; final LabelFormatterCallback labelFormatterCallback; final TooltipTextFormatterCallback tooltipTextFormatterCallback; - final SfSliderSemanticFormatterCallback semanticFormatterCallback; + final SfSliderSemanticFormatterCallback? semanticFormatterCallback; final SfDivisorShape divisorShape; final SfTrackShape trackShape; final SfTickShape tickShape; @@ -1220,7 +1328,7 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { final SfOverlayShape overlayShape; final SfThumbShape thumbShape; final SfTooltipShape tooltipShape; - final Widget thumbIcon; + final Widget? thumbIcon; final _SfSliderState state; @override @@ -1229,39 +1337,40 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { @override RenderObject createRenderObject(BuildContext context) { return _RenderSlider( - min: min, - max: max, - value: value, - onChanged: onChanged, - minorTicksPerInterval: minorTicksPerInterval, - interval: interval, - stepSize: stepSize, - stepDuration: stepDuration, - showTicks: showTicks, - showLabels: showLabels, - showDivisors: showDivisors, - enableTooltip: enableTooltip, - inactiveColor: inactiveColor, - activeColor: activeColor, - labelPlacement: labelPlacement, - numberFormat: numberFormat, - dateFormat: dateFormat, - dateIntervalType: dateIntervalType, - labelFormatterCallback: labelFormatterCallback, - tooltipTextFormatterCallback: tooltipTextFormatterCallback, - semanticFormatterCallback: semanticFormatterCallback, - trackShape: trackShape, - divisorShape: divisorShape, - overlayShape: overlayShape, - thumbShape: thumbShape, - tickShape: tickShape, - minorTickShape: minorTickShape, - tooltipShape: tooltipShape, - sliderThemeData: sliderThemeData, - textDirection: Directionality.of(context), - mediaQueryData: MediaQuery.of(context), - state: state, - ); + min: min, + max: max, + value: value, + onChanged: onChanged, + minorTicksPerInterval: minorTicksPerInterval, + interval: interval, + stepSize: stepSize, + stepDuration: stepDuration, + showTicks: showTicks, + showLabels: showLabels, + showDivisors: showDivisors, + enableTooltip: enableTooltip, + inactiveColor: inactiveColor, + activeColor: activeColor, + labelPlacement: labelPlacement, + numberFormat: numberFormat, + dateFormat: dateFormat, + dateIntervalType: dateIntervalType, + labelFormatterCallback: labelFormatterCallback, + tooltipTextFormatterCallback: tooltipTextFormatterCallback, + semanticFormatterCallback: semanticFormatterCallback, + trackShape: trackShape, + divisorShape: divisorShape, + overlayShape: overlayShape, + thumbShape: thumbShape, + tickShape: tickShape, + minorTickShape: minorTickShape, + tooltipShape: tooltipShape, + sliderThemeData: sliderThemeData, + sliderType: sliderType, + tooltipPosition: tooltipPosition, + textDirection: Directionality.of(context), + mediaQueryData: MediaQuery.of(context), + state: state); } @override @@ -1279,8 +1388,6 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { ..showLabels = showLabels ..showDivisors = showDivisors ..enableTooltip = enableTooltip - ..inactiveColor = inactiveColor - ..activeColor = activeColor ..labelPlacement = labelPlacement ..numberFormat = numberFormat ..dateFormat = dateFormat @@ -1296,6 +1403,7 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { ..minorTickShape = minorTickShape ..tooltipShape = tooltipShape ..sliderThemeData = sliderThemeData + ..tooltipPosition = tooltipPosition ..textDirection = Directionality.of(context) ..mediaQueryData = MediaQuery.of(context); } @@ -1309,27 +1417,17 @@ class _RenderSliderElement extends RenderObjectElement { final Map _childToSlot = {}; @override - _SliderRenderObjectWidget get widget => super.widget; + _SliderRenderObjectWidget get widget => + // ignore: avoid_as + super.widget as _SliderRenderObjectWidget; @override - _RenderSlider get renderObject => super.renderObject; + // ignore: avoid_as + _RenderSlider get renderObject => super.renderObject as _RenderSlider; - void _mountChild(Widget newWidget, ChildElements slot) { - final Element oldChild = _slotToChild[slot]; - final Element newChild = updateChild(oldChild, newWidget, slot); - if (oldChild != null) { - _slotToChild.remove(slot); - _childToSlot.remove(oldChild); - } - if (newChild != null) { - _slotToChild[slot] = newChild; - _childToSlot[newChild] = slot; - } - } - - void _updateChild(Widget widget, ChildElements slot) { - final Element oldChild = _slotToChild[slot]; - final Element newChild = updateChild(oldChild, widget, slot); + void _updateChild(Widget? widget, ChildElements slot) { + final Element? oldChild = _slotToChild[slot]; + final Element? newChild = updateChild(oldChild, widget, slot); if (oldChild != null) { _childToSlot.remove(oldChild); _slotToChild.remove(slot); @@ -1340,10 +1438,11 @@ class _RenderSliderElement extends RenderObjectElement { } } - void _updateRenderObject(RenderObject child, ChildElements slot) { + void _updateRenderObject(RenderObject? child, ChildElements slot) { switch (slot) { case ChildElements.startThumbIcon: - renderObject.thumbIcon = child; + // ignore: avoid_as + renderObject.thumbIcon = child as RenderBox?; break; case ChildElements.endThumbIcon: break; @@ -1358,9 +1457,9 @@ class _RenderSliderElement extends RenderObjectElement { } @override - void mount(Element parent, dynamic newSlot) { + void mount(Element? parent, dynamic newSlot) { super.mount(parent, newSlot); - _mountChild(widget.thumbIcon, ChildElements.startThumbIcon); + _updateChild(widget.thumbIcon, ChildElements.startThumbIcon); } @override @@ -1371,7 +1470,7 @@ class _RenderSliderElement extends RenderObjectElement { } @override - void insertRenderObjectChild(RenderObject child, dynamic slotValue) { + void insertRenderObjectChild(RenderObject child, ChildElements slotValue) { assert(child is RenderBox); assert(slotValue is ChildElements); final ChildElements slot = slotValue; @@ -1381,10 +1480,10 @@ class _RenderSliderElement extends RenderObjectElement { } @override - void removeRenderObjectChild(RenderObject child, dynamic slot) { + void removeRenderObjectChild(RenderObject child, ChildElements slot) { assert(child is RenderBox); assert(renderObject.childToSlot.keys.contains(child)); - _updateRenderObject(null, renderObject.childToSlot[child]); + _updateRenderObject(null, renderObject.childToSlot[child]!); assert(!renderObject.childToSlot.keys.contains(child)); assert(!renderObject.slotToChild.keys.contains(slot)); } @@ -1396,44 +1495,50 @@ class _RenderSliderElement extends RenderObjectElement { } } -class _RenderSlider extends RenderBaseSlider { +class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { _RenderSlider({ - dynamic min, - dynamic max, - dynamic value, - ValueChanged onChanged, - double interval, - double stepSize, - SliderStepDuration stepDuration, - int minorTicksPerInterval, - bool showTicks, - bool showLabels, - bool showDivisors, - bool enableTooltip, - Color inactiveColor, - Color activeColor, - LabelPlacement labelPlacement, - NumberFormat numberFormat, - DateFormat dateFormat, - DateIntervalType dateIntervalType, - LabelFormatterCallback labelFormatterCallback, - TooltipTextFormatterCallback tooltipTextFormatterCallback, - SfSliderSemanticFormatterCallback semanticFormatterCallback, - SfTrackShape trackShape, - SfDivisorShape divisorShape, - SfOverlayShape overlayShape, - SfThumbShape thumbShape, - SfTickShape tickShape, - SfTickShape minorTickShape, - SfTooltipShape tooltipShape, - SfSliderThemeData sliderThemeData, - TextDirection textDirection, - MediaQueryData mediaQueryData, - @required _SfSliderState state, - }) : _state = state, + required dynamic min, + required dynamic max, + required dynamic value, + required ValueChanged? onChanged, + required double? interval, + required double? stepSize, + required SliderStepDuration? stepDuration, + required int minorTicksPerInterval, + required bool showTicks, + required bool showLabels, + required bool showDivisors, + required bool enableTooltip, + required Color inactiveColor, + required Color activeColor, + required LabelPlacement labelPlacement, + required NumberFormat numberFormat, + required DateFormat? dateFormat, + required DateIntervalType? dateIntervalType, + required LabelFormatterCallback labelFormatterCallback, + required TooltipTextFormatterCallback tooltipTextFormatterCallback, + required SfSliderSemanticFormatterCallback? semanticFormatterCallback, + required SfTrackShape trackShape, + required SfDivisorShape divisorShape, + required SfOverlayShape overlayShape, + required SfThumbShape thumbShape, + required SfTickShape tickShape, + required SfTickShape minorTickShape, + required SfTooltipShape tooltipShape, + required SfSliderThemeData sliderThemeData, + required SliderType sliderType, + required SliderTooltipPosition? tooltipPosition, + required TextDirection textDirection, + required MediaQueryData mediaQueryData, + required _SfSliderState state, + }) : _state = state, + _value = value, + _semanticFormatterCallback = semanticFormatterCallback, + _onChanged = onChanged, super( min: min, max: max, + sliderType: sliderType, interval: interval, stepSize: stepSize, stepDuration: stepDuration, @@ -1455,22 +1560,28 @@ class _RenderSlider extends RenderBaseSlider { tickShape: tickShape, minorTickShape: minorTickShape, tooltipShape: tooltipShape, + tooltipPosition: tooltipPosition, sliderThemeData: sliderThemeData, textDirection: textDirection, mediaQueryData: mediaQueryData) { - _value = value; - _semanticFormatterCallback = semanticFormatterCallback; - _onChanged = onChanged; - _inactiveColor = inactiveColor; - _activeColor = activeColor; final GestureArenaTeam team = GestureArenaTeam(); + if (sliderType == SliderType.horizontal) { + horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer() + ..team = team + ..onStart = _onDragStart + ..onUpdate = _onDragUpdate + ..onEnd = _onDragEnd + ..onCancel = _onDragCancel; + } - dragGestureRecognizer = HorizontalDragGestureRecognizer() - ..team = team - ..onStart = _onDragStart - ..onUpdate = _onDragUpdate - ..onEnd = _onDragEnd - ..onCancel = _onDragCancel; + if (sliderType == SliderType.vertical) { + verticalDragGestureRecognizer = VerticalDragGestureRecognizer() + ..team = team + ..onStart = _onVerticalDragStart + ..onUpdate = _onVerticalDragUpdate + ..onEnd = _onVerticalDragEnd + ..onCancel = _onVerticalDragCancel; + } tapGestureRecognizer = TapGestureRecognizer() ..team = team @@ -1495,13 +1606,13 @@ class _RenderSlider extends RenderBaseSlider { final _SfSliderState _state; - Animation _overlayAnimation; + late Animation _overlayAnimation; - Animation _stateAnimation; + late Animation _stateAnimation; - Animation _tooltipAnimation; + late Animation _tooltipAnimation; - double _valueInMilliseconds; + double? _valueInMilliseconds; final Map slotToChild = {}; @@ -1524,10 +1635,10 @@ class _RenderSlider extends RenderBaseSlider { markNeedsPaint(); } - ValueChanged get onChanged => _onChanged; - ValueChanged _onChanged; + ValueChanged? get onChanged => _onChanged; + ValueChanged? _onChanged; - set onChanged(ValueChanged value) { + set onChanged(ValueChanged? value) { if (value == _onChanged) { return; } @@ -1544,32 +1655,11 @@ class _RenderSlider extends RenderBaseSlider { } } - Color get inactiveColor => _inactiveColor; - Color _inactiveColor; - - set inactiveColor(Color value) { - if (_inactiveColor == value) { - return; - } - _inactiveColor = value; - markNeedsPaint(); - } - - Color get activeColor => _activeColor; - Color _activeColor; - - set activeColor(Color value) { - if (_activeColor == value) { - return; - } - _activeColor = value; - markNeedsPaint(); - } - - SfSliderSemanticFormatterCallback get semanticFormatterCallback => + SfSliderSemanticFormatterCallback? get semanticFormatterCallback => _semanticFormatterCallback; - SfSliderSemanticFormatterCallback _semanticFormatterCallback; - set semanticFormatterCallback(SfSliderSemanticFormatterCallback value) { + SfSliderSemanticFormatterCallback? _semanticFormatterCallback; + + set semanticFormatterCallback(SfSliderSemanticFormatterCallback? value) { if (_semanticFormatterCallback == value) { return; } @@ -1577,10 +1667,10 @@ class _RenderSlider extends RenderBaseSlider { markNeedsSemanticsUpdate(); } - RenderBox get thumbIcon => _thumbIcon; - RenderBox _thumbIcon; + RenderBox? get thumbIcon => _thumbIcon; + RenderBox? _thumbIcon; - set thumbIcon(RenderBox value) { + set thumbIcon(RenderBox? value) { _thumbIcon = _updateChild(_thumbIcon, value, ChildElements.startThumbIcon); } @@ -1591,12 +1681,22 @@ class _RenderSlider extends RenderBaseSlider { // The returned list is ordered for hit testing. Iterable get children sync* { if (_thumbIcon != null) { - yield _thumbIcon; + yield _thumbIcon!; } } - RenderBox _updateChild( - RenderBox oldChild, RenderBox newChild, ChildElements slot) { + dynamic get _increasedValue { + return getNextSemanticValue(value, semanticActionUnit, + actualValue: actualValue); + } + + dynamic get _decreasedValue { + return getPrevSemanticValue(value, semanticActionUnit, + actualValue: actualValue); + } + + RenderBox? _updateChild( + RenderBox? oldChild, RenderBox? newChild, ChildElements slot) { if (oldChild != null) { dropChild(oldChild); childToSlot.remove(oldChild); @@ -1612,7 +1712,9 @@ class _RenderSlider extends RenderBaseSlider { void _onTapDown(TapDownDetails details) { currentPointerType = PointerType.down; - currentX = globalToLocal(details.globalPosition).dx; + mainAxisOffset = (sliderType == SliderType.vertical + ? globalToLocal(details.globalPosition).dy + : globalToLocal(details.globalPosition).dx); _beginInteraction(); } @@ -1621,14 +1723,14 @@ class _RenderSlider extends RenderBaseSlider { } void _onDragStart(DragStartDetails details) { - currentX = globalToLocal(details.globalPosition).dx; + mainAxisOffset = globalToLocal(details.globalPosition).dx; _beginInteraction(); } void _onDragUpdate(DragUpdateDetails details) { isInteractionEnd = false; currentPointerType = PointerType.move; - currentX = globalToLocal(details.globalPosition).dx; + mainAxisOffset = globalToLocal(details.globalPosition).dx; _updateValue(); markNeedsPaint(); } @@ -1641,6 +1743,27 @@ class _RenderSlider extends RenderBaseSlider { _endInteraction(); } + void _onVerticalDragStart(DragStartDetails details) { + mainAxisOffset = globalToLocal(details.globalPosition).dy; + _beginInteraction(); + } + + void _onVerticalDragUpdate(DragUpdateDetails details) { + isInteractionEnd = false; + currentPointerType = PointerType.move; + mainAxisOffset = globalToLocal(details.globalPosition).dy; + _updateValue(); + markNeedsPaint(); + } + + void _onVerticalDragEnd(DragEndDetails details) { + _endInteraction(); + } + + void _onVerticalDragCancel() { + _endInteraction(); + } + void _beginInteraction() { isInteractionEnd = false; _state.overlayController.forward(); @@ -1665,11 +1788,11 @@ class _RenderSlider extends RenderBaseSlider { void _updateValue() { final double factor = getFactorFromCurrentPosition(); - final double valueFactor = lerpDouble(actualMin, actualMax, factor); + final double valueFactor = lerpDouble(actualMin, actualMax, factor)!; final dynamic newValue = getActualValue(valueInDouble: valueFactor); if (newValue != _value) { - onChanged(newValue); + onChanged!(newValue); } } @@ -1700,12 +1823,13 @@ class _RenderSlider extends RenderBaseSlider { Offset actualTrackOffset, Rect trackRect) { if (willDrawTooltip) { final Paint paint = Paint() - ..color = sliderThemeData.tooltipBackgroundColor + ..color = sliderThemeData.tooltipBackgroundColor! ..style = PaintingStyle.fill ..strokeWidth = 0; - final dynamic actualText = - getValueFromPosition(thumbCenter.dx - offset.dx); + final dynamic actualText = sliderType == SliderType.vertical + ? getValueFromPosition(trackRect.bottom - thumbCenter.dy) + : getValueFromPosition(thumbCenter.dx - offset.dx); final String tooltipText = tooltipTextFormatterCallback( actualText, getFormattedText(actualText)); final TextSpan textSpan = @@ -1725,13 +1849,32 @@ class _RenderSlider extends RenderBaseSlider { void increaseAction() { if (isInteractive) { - onChanged(getNextSemanticValue(value, semanticActionUnit)); + onChanged!(_increasedValue); } } void decreaseAction() { if (isInteractive) { - onChanged(getPrevSemanticValue(value, semanticActionUnit)); + onChanged!(_decreasedValue); + } + } + + void _handleEnter(PointerEnterEvent event) { + _state.overlayController.forward(); + if (enableTooltip) { + willDrawTooltip = true; + _state.tooltipAnimationController.forward(); + } + } + + void _handleExit(PointerExitEvent event) { + // Ensuring whether the thumb is drag or move + // not needed to call controller's reverse. + if (currentPointerType != PointerType.move) { + _state.overlayController.reverse(); + if (enableTooltip) { + _state.tooltipAnimationController.reverse(); + } } } @@ -1743,10 +1886,10 @@ class _RenderSlider extends RenderBaseSlider { @override void attach(PipelineOwner owner) { super.attach(owner); - _overlayAnimation?.addListener(markNeedsPaint); - _stateAnimation?.addListener(markNeedsPaint); - _tooltipAnimation?.addListener(markNeedsPaint); - _tooltipAnimation?.addStatusListener(_handleTooltipAnimationStatusChange); + _overlayAnimation.addListener(markNeedsPaint); + _stateAnimation.addListener(markNeedsPaint); + _tooltipAnimation.addListener(markNeedsPaint); + _tooltipAnimation.addStatusListener(_handleTooltipAnimationStatusChange); for (final RenderBox child in children) { child.attach(owner); } @@ -1754,17 +1897,29 @@ class _RenderSlider extends RenderBaseSlider { @override void detach() { - _overlayAnimation?.removeListener(markNeedsPaint); - _stateAnimation?.removeListener(markNeedsPaint); - _tooltipAnimation?.removeListener(markNeedsPaint); - _tooltipAnimation - ?.removeStatusListener(_handleTooltipAnimationStatusChange); + _overlayAnimation.removeListener(markNeedsPaint); + _stateAnimation.removeListener(markNeedsPaint); + _tooltipAnimation.removeListener(markNeedsPaint); + _tooltipAnimation.removeStatusListener(_handleTooltipAnimationStatusChange); super.detach(); for (final RenderBox child in children) { child.detach(); } } + @override + MouseCursor get cursor => MouseCursor.defer; + + @override + PointerEnterEventListener get onEnter => _handleEnter; + + @override + PointerExitEventListener get onExit => _handleExit; + + @override + // ignore: override_on_non_overriding_member + bool get validForMouseTracker => true; + @override bool hitTestSelf(Offset position) => isInteractive; @@ -1779,27 +1934,40 @@ class _RenderSlider extends RenderBaseSlider { @override void paint(PaintingContext context, Offset offset) { - final Offset actualTrackOffset = Offset( - offset.dx, - offset.dy + - (size.height - actualHeight) / 2 + - trackOffset.dy - - maxTrackHeight / 2); + final Offset actualTrackOffset = sliderType == SliderType.vertical + ? Offset( + offset.dx + + (size.width - actualHeight) / 2 + + trackOffset.dy - + maxTrackHeight / 2, + offset.dy) + : Offset( + offset.dx, + offset.dy + + (size.height - actualHeight) / 2 + + trackOffset.dy - + maxTrackHeight / 2); // Drawing track. final Rect trackRect = trackShape.getPreferredRect(this, sliderThemeData, actualTrackOffset); - final double thumbPosition = - getFactorFromValue(actualValue) * trackRect.width; - final Offset thumbCenter = - Offset(trackRect.left + thumbPosition, trackRect.center.dy); + final double thumbPosition = getFactorFromValue(actualValue) * + (sliderType == SliderType.vertical + ? trackRect.height + : trackRect.width); + final Offset thumbCenter = sliderType == SliderType.vertical + ? Offset(trackRect.center.dx, trackRect.bottom - thumbPosition) + : Offset(trackRect.left + thumbPosition, trackRect.center.dy); trackShape.paint(context, actualTrackOffset, thumbCenter, null, null, parentBox: this, currentValue: _value, + currentValues: null, themeData: sliderThemeData, enableAnimation: _stateAnimation, - textDirection: textDirection); + textDirection: textDirection, + activePaint: null, + inactivePaint: null); if (showLabels || showTicks || showDivisors) { drawLabelsTicksAndDivisors(context, trackRect, offset, thumbCenter, null, @@ -1811,7 +1979,9 @@ class _RenderSlider extends RenderBaseSlider { parentBox: this, currentValue: _value, themeData: sliderThemeData, - animation: _overlayAnimation); + animation: _overlayAnimation, + thumb: null, + paint: null); // Drawing thumb. thumbShape.paint(context, thumbCenter, @@ -1820,7 +1990,9 @@ class _RenderSlider extends RenderBaseSlider { currentValue: _value, themeData: sliderThemeData, enableAnimation: _stateAnimation, - textDirection: textDirection); + textDirection: textDirection, + thumb: null, + paint: null); _drawTooltip(context, thumbCenter, offset, actualTrackOffset, trackRect); } @@ -1828,24 +2000,19 @@ class _RenderSlider extends RenderBaseSlider { @override void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); - config.isSemanticBoundary = isInteractive; if (isInteractive) { config.textDirection = textDirection; config.onIncrease = increaseAction; config.onDecrease = decreaseAction; if (semanticFormatterCallback != null) { - config.value = semanticFormatterCallback(value); - config.increasedValue = semanticFormatterCallback( - getNextSemanticValue(value, semanticActionUnit)); - config.decreasedValue = semanticFormatterCallback( - getPrevSemanticValue(value, semanticActionUnit)); + config.value = semanticFormatterCallback!(value); + config.increasedValue = semanticFormatterCallback!(_increasedValue); + config.decreasedValue = semanticFormatterCallback!(_decreasedValue); } else { config.value = '$value'; - config.increasedValue = - '${getNextSemanticValue(value, semanticActionUnit)}'; - config.decreasedValue = - '${getPrevSemanticValue(value, semanticActionUnit)}'; + config.increasedValue = '$_increasedValue'; + config.decreasedValue = '$_decreasedValue'; } } } diff --git a/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart b/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart index b3e5a213e..a7bff2129 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart @@ -14,49 +14,68 @@ class SfTrackShape { /// Enables subclasses to provide constant constructors. const SfTrackShape(); + bool _isVertical(RenderBaseSlider parentBox) { + return parentBox.sliderType == SliderType.vertical; + } + /// Returns the size based on the values passed to it. Rect getPreferredRect( RenderBox parentBox, SfSliderThemeData themeData, Offset offset, - {bool isActive}) { + {bool? isActive}) { final double maxRadius = math.max(themeData.overlayRadius, - math.max(themeData.thumbRadius, themeData.tickSize.width / 2)); + math.max(themeData.thumbRadius, themeData.tickSize!.width / 2)); final double maxTrackHeight = math.max(themeData.activeTrackHeight, themeData.inactiveTrackHeight); - final double left = offset.dx + maxRadius; - double top = offset.dy; - if (isActive != null) { - top += isActive - ? (maxTrackHeight - themeData.activeTrackHeight) / 2 - : (maxTrackHeight - themeData.inactiveTrackHeight) / 2; + // ignore: avoid_as + if (_isVertical(parentBox as RenderBaseSlider)) { + double left = offset.dx; + if (isActive != null) { + left += isActive + ? (maxTrackHeight - themeData.activeTrackHeight) / 2 + : (maxTrackHeight - themeData.inactiveTrackHeight) / 2; + } + final double right = left + + (isActive == null + ? maxTrackHeight + : (isActive + ? themeData.activeTrackHeight + : themeData.inactiveTrackHeight)); + final double top = offset.dy + maxRadius; + final double bottom = top + parentBox.size.height - (2 * maxRadius); + return Rect.fromLTRB( + math.min(left, right), top, math.max(left, right), bottom); + } else { + final double left = offset.dx + maxRadius; + double top = offset.dy; + if (isActive != null) { + top += isActive + ? (maxTrackHeight - themeData.activeTrackHeight) / 2 + : (maxTrackHeight - themeData.inactiveTrackHeight) / 2; + } + final double right = left + parentBox.size.width - (2 * maxRadius); + final double bottom = top + + (isActive == null + ? maxTrackHeight + : (isActive + ? themeData.activeTrackHeight + : themeData.inactiveTrackHeight)); + return Rect.fromLTRB( + math.min(left, right), top, math.max(left, right), bottom); } - final double right = left + parentBox.size.width - (2 * maxRadius); - final double bottom = top + - (isActive == null - ? maxTrackHeight - : (isActive - ? themeData.activeTrackHeight - : themeData.inactiveTrackHeight)); - return Rect.fromLTRB( - math.min(left, right), top, math.max(left, right), bottom); } /// Paints the track based on the values passed to it. - void paint( - PaintingContext context, - Offset offset, - Offset thumbCenter, - Offset startThumbCenter, - Offset endThumbCenter, { - RenderBox parentBox, - SfSliderThemeData themeData, - SfRangeValues currentValues, - dynamic currentValue, - Animation enableAnimation, - Paint inactivePaint, - Paint activePaint, - TextDirection textDirection, - }) { - final Radius radius = Radius.circular(themeData.trackCornerRadius); + void paint(PaintingContext context, Offset offset, Offset? thumbCenter, + Offset? startThumbCenter, Offset? endThumbCenter, + {required RenderBox parentBox, + required SfSliderThemeData themeData, + SfRangeValues? currentValues, + dynamic currentValue, + required Animation enableAnimation, + required Paint? inactivePaint, + required Paint? activePaint, + required TextDirection textDirection}) { + final Radius radius = Radius.circular(themeData.trackCornerRadius!); final Rect inactiveTrackRect = getPreferredRect(parentBox, themeData, offset, isActive: false); final Rect activeTrackRect = @@ -67,7 +86,7 @@ class SfTrackShape { final ColorTween inactiveTrackColorTween = ColorTween( begin: themeData.disabledInactiveTrackColor, end: themeData.inactiveTrackColor); - inactivePaint.color = inactiveTrackColorTween.evaluate(enableAnimation); + inactivePaint.color = inactiveTrackColorTween.evaluate(enableAnimation)!; } if (activePaint == null) { @@ -75,7 +94,7 @@ class SfTrackShape { final ColorTween activeTrackColorTween = ColorTween( begin: themeData.disabledActiveTrackColor, end: themeData.activeTrackColor); - activePaint.color = activeTrackColorTween.evaluate(enableAnimation); + activePaint.color = activeTrackColorTween.evaluate(enableAnimation)!; } _drawTrackRect( @@ -88,28 +107,31 @@ class SfTrackShape { inactiveTrackRect, radius, context, - activeTrackRect); + activeTrackRect, + // ignore: avoid_as + isVertical: _isVertical(parentBox as RenderBaseSlider)); } void _drawTrackRect( - TextDirection textDirection, - Offset thumbCenter, - Offset startThumbCenter, - Offset endThumbCenter, + TextDirection? textDirection, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, Paint activePaint, Paint inactivePaint, Rect inactiveTrackRect, Radius radius, PaintingContext context, - Rect activeTrackRect) { - Offset leftThumbCenter; - Offset rightThumbCenter; - Paint leftTrackPaint; - Paint rightTrackPaint; - Rect leftTrackRect; - Rect rightTrackRect; - if (textDirection == TextDirection.rtl) { - if (thumbCenter == null) { + Rect activeTrackRect, + {required bool isVertical}) { + Offset? leftThumbCenter; + Offset? rightThumbCenter; + Paint? leftTrackPaint; + Paint? rightTrackPaint; + Rect? leftTrackRect; + Rect? rightTrackRect; + if (textDirection == TextDirection.rtl && !isVertical) { + if (startThumbCenter != null) { // For range slider and range selector widget. leftThumbCenter = endThumbCenter; rightThumbCenter = startThumbCenter; @@ -121,7 +143,7 @@ class SfTrackShape { rightTrackRect = activeTrackRect; } } else { - if (thumbCenter == null) { + if (startThumbCenter != null) { // For range slider and range selector widget. leftThumbCenter = startThumbCenter; rightThumbCenter = endThumbCenter; @@ -134,14 +156,16 @@ class SfTrackShape { } } - if (thumbCenter == null) { + if (leftThumbCenter != null && rightThumbCenter != null) { // Drawing range slider track. _drawRangeSliderTrack(inactiveTrackRect, leftThumbCenter, radius, context, - inactivePaint, activeTrackRect, rightThumbCenter, activePaint); + inactivePaint, activeTrackRect, rightThumbCenter, activePaint, + isVertical: isVertical); } else { // Drawing slider track. - _drawSliderTrack(leftTrackRect, thumbCenter, radius, context, - leftTrackPaint, rightTrackRect, rightTrackPaint); + _drawSliderTrack(leftTrackRect!, thumbCenter!, radius, context, + leftTrackPaint!, rightTrackRect!, rightTrackPaint!, + isVertical: isVertical); } } @@ -152,27 +176,47 @@ class SfTrackShape { PaintingContext context, Paint activePaint, Rect inactiveTrackRect, - Paint inactivePaint) { + Paint inactivePaint, + {required bool isVertical}) { RRect inactiveTrackRRect; - // Drawing active track. - Rect trackRect = Rect.fromLTRB(activeTrackRect.left, activeTrackRect.top, - thumbCenter.dx, activeTrackRect.bottom); - final RRect activeTrackRRect = RRect.fromRectAndCorners(trackRect, - topLeft: radius, bottomLeft: radius); - context.canvas.drawRRect(activeTrackRRect, activePaint); - - // Drawing inactive track. - trackRect = Rect.fromLTRB( - thumbCenter.dx, - inactiveTrackRect.top, - inactiveTrackRect.width + inactiveTrackRect.left, - inactiveTrackRect.bottom); - inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, - topLeft: Radius.zero, - topRight: radius, - bottomLeft: Radius.zero, - bottomRight: radius); - context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); + if (isVertical) { + Rect trackRect = Rect.fromLTRB(activeTrackRect.left, thumbCenter.dy, + activeTrackRect.right, activeTrackRect.bottom); + final RRect activeTrackRRect = RRect.fromRectAndCorners(trackRect, + bottomRight: radius, bottomLeft: radius); + context.canvas.drawRRect(activeTrackRRect, activePaint); + // Drawing inactive track. + trackRect = Rect.fromLTRB(inactiveTrackRect.left, inactiveTrackRect.top, + inactiveTrackRect.right, thumbCenter.dy); + inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, + topLeft: radius, + topRight: radius, + bottomLeft: Radius.zero, + bottomRight: Radius.zero); + + context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); + } else { + // Drawing active track. + Rect trackRect = Rect.fromLTRB(activeTrackRect.left, activeTrackRect.top, + thumbCenter.dx, activeTrackRect.bottom); + final RRect activeTrackRRect = RRect.fromRectAndCorners(trackRect, + topLeft: radius, bottomLeft: radius); + + context.canvas.drawRRect(activeTrackRRect, activePaint); + // Drawing inactive track. + trackRect = Rect.fromLTRB( + thumbCenter.dx, + inactiveTrackRect.top, + inactiveTrackRect.width + inactiveTrackRect.left, + inactiveTrackRect.bottom); + inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, + topLeft: Radius.zero, + topRight: radius, + bottomLeft: Radius.zero, + bottomRight: radius); + + context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); + } } void _drawRangeSliderTrack( @@ -183,32 +227,60 @@ class SfTrackShape { Paint inactivePaint, Rect activeTrackRect, Offset endThumbCenter, - Paint activePaint) { + Paint activePaint, + {bool isVertical = false}) { RRect inactiveTrackRRect; - // Drawing inactive track. - Rect trackRect = Rect.fromLTRB(inactiveTrackRect.left, - inactiveTrackRect.top, startThumbCenter.dx, inactiveTrackRect.bottom); - inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, - topLeft: radius, bottomLeft: radius); - context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); - - // Drawing active track. - final Rect activeTrackRRect = Rect.fromLTRB(startThumbCenter.dx, - activeTrackRect.top, endThumbCenter.dx, activeTrackRect.bottom); - context.canvas.drawRect(activeTrackRRect, activePaint); - - // Drawing inactive track. - trackRect = Rect.fromLTRB( - endThumbCenter.dx, - inactiveTrackRect.top, - inactiveTrackRect.width + inactiveTrackRect.left, - inactiveTrackRect.bottom); - inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, - topLeft: Radius.zero, - topRight: radius, - bottomLeft: Radius.zero, - bottomRight: radius); - context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); + if (isVertical) { + // Drawing inactive track + Rect trackRect = Rect.fromLTRB( + inactiveTrackRect.left, + startThumbCenter.dy, + inactiveTrackRect.right, + inactiveTrackRect.bottom); + inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, + bottomLeft: radius, bottomRight: radius); + context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); + + // Drawing active track. + final Rect activeTrackRRect = Rect.fromLTRB(activeTrackRect.left, + startThumbCenter.dy, activeTrackRect.right, endThumbCenter.dy); + context.canvas.drawRect(activeTrackRRect, activePaint); + + // Drawing inactive track. + trackRect = Rect.fromLTRB(inactiveTrackRect.left, inactiveTrackRect.top, + inactiveTrackRect.right, endThumbCenter.dy); + inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, + topLeft: radius, + topRight: radius, + bottomLeft: Radius.zero, + bottomRight: Radius.zero); + context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); + } else { + // Drawing inactive track. + Rect trackRect = Rect.fromLTRB(inactiveTrackRect.left, + inactiveTrackRect.top, startThumbCenter.dx, inactiveTrackRect.bottom); + inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, + topLeft: radius, bottomLeft: radius); + context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); + + // Drawing active track. + final Rect activeTrackRRect = Rect.fromLTRB(startThumbCenter.dx, + activeTrackRect.top, endThumbCenter.dx, activeTrackRect.bottom); + context.canvas.drawRect(activeTrackRRect, activePaint); + + // Drawing inactive track. + trackRect = Rect.fromLTRB( + endThumbCenter.dx, + inactiveTrackRect.top, + inactiveTrackRect.width + inactiveTrackRect.left, + inactiveTrackRect.bottom); + inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, + topLeft: Radius.zero, + topRight: radius, + bottomLeft: Radius.zero, + bottomRight: radius); + context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); + } } } @@ -229,23 +301,24 @@ class SfThumbShape { /// Paints the thumb based on the values passed to it. void paint(PaintingContext context, Offset center, - {RenderBox parentBox, - RenderBox child, - SfSliderThemeData themeData, - SfRangeValues currentValues, + {required RenderBox parentBox, + required RenderBox? child, + required SfSliderThemeData themeData, + SfRangeValues? currentValues, dynamic currentValue, - Paint paint, - Animation enableAnimation, - TextDirection textDirection, - SfThumb thumb}) { + required Paint? paint, + required Animation enableAnimation, + required TextDirection textDirection, + required SfThumb? thumb}) { final double radius = getPreferredSize(themeData).width / 2; final bool isThumbStroke = themeData.thumbStrokeColor != null && themeData.thumbStrokeColor != Colors.transparent && themeData.thumbStrokeWidth != null && - themeData.thumbStrokeWidth > 0; + themeData.thumbStrokeWidth! > 0; final bool showThumbShadow = themeData.thumbColor != Colors.transparent; - final RenderBaseSlider parentRenderBox = parentBox; + // ignore: avoid_as + final RenderBaseSlider parentRenderBox = parentBox as RenderBaseSlider; if (showThumbShadow) { final Path path = Path(); final bool isThumbActive = @@ -264,7 +337,7 @@ class SfThumbShape { if (!isThumbStroke && _isThumbOverlap(parentBox) && themeData.thumbColor != Colors.transparent) { - final Color thumbOverlappingStrokeColor = + final Color? thumbOverlappingStrokeColor = themeData is SfRangeSliderThemeData ? themeData.overlappingThumbStrokeColor : null; @@ -284,7 +357,7 @@ class SfThumbShape { paint.isAntiAlias = true; paint.color = ColorTween( begin: themeData.disabledThumbColor, end: themeData.thumbColor) - .evaluate(enableAnimation); + .evaluate(enableAnimation)!; } context.canvas @@ -298,18 +371,18 @@ class SfThumbShape { if (themeData.thumbStrokeColor != null && themeData.thumbStrokeWidth != null && - themeData.thumbStrokeWidth > 0) { + themeData.thumbStrokeWidth! > 0) { context.canvas.drawCircle( center, - themeData.thumbStrokeWidth > radius + themeData.thumbStrokeWidth! > radius ? radius / 2 - : radius - themeData.thumbStrokeWidth / 2, + : radius - themeData.thumbStrokeWidth! / 2, paint - ..color = themeData.thumbStrokeColor + ..color = themeData.thumbStrokeColor! ..style = PaintingStyle.stroke - ..strokeWidth = themeData.thumbStrokeWidth > radius + ..strokeWidth = themeData.thumbStrokeWidth! > radius ? radius - : themeData.thumbStrokeWidth); + : themeData.thumbStrokeWidth!); } } } @@ -320,62 +393,82 @@ class SfDivisorShape { /// Enables subclasses to provide constant constructors. const SfDivisorShape(); + bool _isVertical(RenderBaseSlider parentBox) { + return parentBox.sliderType == SliderType.vertical; + } + /// Returns the size based on the values passed to it. - Size getPreferredSize(SfSliderThemeData themeData, {bool isActive}) { + Size getPreferredSize(SfSliderThemeData themeData, {bool? isActive}) { return Size.fromRadius(isActive != null ? (isActive - ? themeData.activeDivisorRadius - : themeData.inactiveDivisorRadius) + ? themeData.activeDivisorRadius! + : themeData.inactiveDivisorRadius!) : 0); } /// Paints the divisors based on the values passed to it. - void paint(PaintingContext context, Offset center, Offset thumbCenter, - Offset startThumbCenter, Offset endThumbCenter, - {RenderBox parentBox, - SfSliderThemeData themeData, - SfRangeValues currentValues, + void paint(PaintingContext context, Offset center, Offset? thumbCenter, + Offset? startThumbCenter, Offset? endThumbCenter, + {required RenderBox parentBox, + required SfSliderThemeData themeData, + SfRangeValues? currentValues, dynamic currentValue, - Paint paint, - Animation enableAnimation, - TextDirection textDirection}) { - bool isActive; + required Paint? paint, + required Animation enableAnimation, + required TextDirection textDirection}) { + late bool isActive; switch (textDirection) { case TextDirection.ltr: // Added this condition to check whether consider single thumb or // two thumbs for finding active range. isActive = startThumbCenter != null - ? center.dx >= startThumbCenter.dx && center.dx <= endThumbCenter.dx - : center.dx <= thumbCenter.dx; + // ignore: avoid_as + ? (_isVertical(parentBox as RenderBaseSlider) + ? center.dy <= startThumbCenter.dy && + center.dy >= endThumbCenter!.dy + : center.dx >= startThumbCenter.dx && + center.dx <= endThumbCenter!.dx) + // ignore: avoid_as + : (_isVertical(parentBox as RenderBaseSlider) + ? center.dy >= thumbCenter!.dy + : center.dx <= thumbCenter!.dx); break; case TextDirection.rtl: isActive = startThumbCenter != null - ? center.dx >= endThumbCenter.dx && center.dx <= startThumbCenter.dx - : center.dx >= thumbCenter.dx; + // ignore: avoid_as + ? (_isVertical(parentBox as RenderBaseSlider) + ? center.dy <= startThumbCenter.dy && + center.dy >= endThumbCenter!.dy + : center.dx >= endThumbCenter!.dx && + center.dx <= startThumbCenter.dx) + // ignore: avoid_as + : (_isVertical(parentBox as RenderBaseSlider) + ? center.dy >= thumbCenter!.dy + : center.dx >= thumbCenter!.dx); break; } if (paint == null) { paint = Paint(); final Color begin = isActive - ? themeData.disabledActiveDivisorColor - : themeData.disabledInactiveDivisorColor; + ? themeData.disabledActiveDivisorColor! + : themeData.disabledInactiveDivisorColor!; final Color end = isActive - ? themeData.activeDivisorColor - : themeData.inactiveDivisorColor; + ? themeData.activeDivisorColor! + : themeData.inactiveDivisorColor!; paint.color = - ColorTween(begin: begin, end: end).evaluate(enableAnimation); + ColorTween(begin: begin, end: end).evaluate(enableAnimation)!; } final double divisorRadius = getPreferredSize(themeData, isActive: isActive).width / 2; context.canvas.drawCircle(center, divisorRadius, paint); - final double divisorStrokeWidth = isActive + final double? divisorStrokeWidth = isActive ? themeData.activeDivisorStrokeWidth : themeData.inactiveDivisorStrokeWidth; - final Color divisorStrokeColor = isActive + final Color? divisorStrokeColor = isActive ? themeData.activeDivisorStrokeColor : themeData.inactiveDivisorStrokeColor; @@ -411,19 +504,19 @@ class SfOverlayShape { /// Paints the overlay based on the values passed to it. void paint(PaintingContext context, Offset center, - {RenderBox parentBox, - SfSliderThemeData themeData, - SfRangeValues currentValues, + {required RenderBox parentBox, + required SfSliderThemeData themeData, + SfRangeValues? currentValues, dynamic currentValue, - Paint paint, - Animation animation, - SfThumb thumb}) { + required Paint? paint, + required Animation animation, + required SfThumb? thumb}) { final double radius = getPreferredSize(themeData).width / 2; final Tween tween = Tween(begin: 0.0, end: radius); if (paint == null) { paint = Paint(); - paint.color = themeData.overlayColor; + paint.color = themeData.overlayColor!; } context.canvas.drawCircle(center, tween.evaluate(animation), paint); } @@ -435,34 +528,54 @@ class SfTickShape { /// Enables subclasses to provide constant constructors. const SfTickShape(); + bool _isVertical(RenderBaseSlider parentBox) { + return parentBox.sliderType == SliderType.vertical; + } + /// Returns the size based on the values passed to it. Size getPreferredSize(SfSliderThemeData themeData) { - return Size.copy(themeData.tickSize); + return Size.copy(themeData.tickSize!); } /// Paints the major ticks based on the values passed to it. - void paint(PaintingContext context, Offset offset, Offset thumbCenter, - Offset startThumbCenter, Offset endThumbCenter, - {RenderBox parentBox, - SfSliderThemeData themeData, - SfRangeValues currentValues, + void paint(PaintingContext context, Offset offset, Offset? thumbCenter, + Offset? startThumbCenter, Offset? endThumbCenter, + {required RenderBox parentBox, + required SfSliderThemeData themeData, + SfRangeValues? currentValues, dynamic currentValue, - Animation enableAnimation, - TextDirection textDirection}) { - bool isInactive; + required Animation enableAnimation, + required TextDirection textDirection}) { + bool isInactive = false; final Size tickSize = getPreferredSize(themeData); switch (textDirection) { case TextDirection.ltr: // Added this condition to check whether consider single thumb or // two thumbs for finding inactive range. isInactive = startThumbCenter != null - ? offset.dx < startThumbCenter.dx || offset.dx > endThumbCenter.dx - : offset.dx > thumbCenter.dx; + // ignore: avoid_as + ? (_isVertical(parentBox as RenderBaseSlider) + ? offset.dy > startThumbCenter.dy || + offset.dy < endThumbCenter!.dy + : offset.dx < startThumbCenter.dx || + offset.dx > endThumbCenter!.dx) + // ignore: avoid_as + : (_isVertical(parentBox as RenderBaseSlider) + ? offset.dy < thumbCenter!.dy + : offset.dx > thumbCenter!.dx); break; case TextDirection.rtl: isInactive = startThumbCenter != null - ? offset.dx > startThumbCenter.dx || offset.dx < endThumbCenter.dx - : offset.dx < thumbCenter.dx; + // ignore: avoid_as + ? (_isVertical(parentBox as RenderBaseSlider) + ? offset.dy > startThumbCenter.dy || + offset.dy < endThumbCenter!.dy + : offset.dx > startThumbCenter.dx || + offset.dx < endThumbCenter!.dx) + // ignore: avoid_as + : (_isVertical(parentBox as RenderBaseSlider) + ? offset.dy < thumbCenter!.dy + : offset.dx < thumbCenter!.dx); break; } @@ -473,10 +586,15 @@ class SfTickShape { isInactive ? themeData.inactiveTickColor : themeData.activeTickColor; final Paint paint = Paint() ..isAntiAlias = true - ..strokeWidth = tickSize.width - ..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation); - context.canvas.drawLine( - offset, Offset(offset.dx, offset.dy + tickSize.height), paint); + ..strokeWidth = _isVertical(parentBox) ? tickSize.height : tickSize.width + ..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)!; + if (_isVertical(parentBox)) { + context.canvas.drawLine( + offset, Offset(offset.dx + tickSize.width, offset.dy), paint); + } else { + context.canvas.drawLine( + offset, Offset(offset.dx, offset.dy + tickSize.height), paint); + } } } @@ -488,40 +606,57 @@ abstract class SfTooltipShape { /// Draws the tooltip based on the values passed in the arguments. void paint(PaintingContext context, Offset thumbCenter, Offset offset, TextPainter textPainter, - {RenderBox parentBox, - SfSliderThemeData sliderThemeData, - Paint paint, - Animation animation, - Rect trackRect}); + {required RenderBox parentBox, + required SfSliderThemeData sliderThemeData, + required Paint paint, + required Animation animation, + required Rect trackRect}); } class SfMinorTickShape extends SfTickShape { + const SfMinorTickShape(); @override Size getPreferredSize(SfSliderThemeData themeData) { - return Size.copy(themeData.minorTickSize); + return Size.copy(themeData.minorTickSize!); } @override - void paint(PaintingContext context, Offset offset, Offset thumbCenter, - Offset startThumbCenter, Offset endThumbCenter, - {RenderBox parentBox, - SfRangeValues currentValues, + void paint(PaintingContext context, Offset offset, Offset? thumbCenter, + Offset? startThumbCenter, Offset? endThumbCenter, + {required RenderBox parentBox, + SfRangeValues? currentValues, dynamic currentValue, - SfSliderThemeData themeData, - Animation enableAnimation, - TextDirection textDirection}) { + required SfSliderThemeData themeData, + required Animation enableAnimation, + required TextDirection textDirection}) { bool isInactive; final Size minorTickSize = getPreferredSize(themeData); switch (textDirection) { case TextDirection.ltr: isInactive = startThumbCenter != null - ? offset.dx < startThumbCenter.dx || offset.dx > endThumbCenter.dx - : offset.dx > thumbCenter.dx; + // ignore: avoid_as + ? (_isVertical(parentBox as RenderBaseSlider) + ? offset.dy > startThumbCenter.dy || + offset.dy < endThumbCenter!.dy + : offset.dx < startThumbCenter.dx || + offset.dx > endThumbCenter!.dx) + // ignore: avoid_as + : (_isVertical(parentBox as RenderBaseSlider) + ? offset.dy < thumbCenter!.dy + : offset.dx > thumbCenter!.dx); break; case TextDirection.rtl: isInactive = startThumbCenter != null - ? offset.dx > startThumbCenter.dx || offset.dx < endThumbCenter.dx - : offset.dx < thumbCenter.dx; + // ignore: avoid_as + ? (_isVertical(parentBox as RenderBaseSlider) + ? offset.dy > startThumbCenter.dy || + offset.dy < endThumbCenter!.dy + : offset.dx > startThumbCenter.dx || + offset.dx < endThumbCenter!.dx) + // ignore: avoid_as + : (_isVertical(parentBox as RenderBaseSlider) + ? offset.dy < thumbCenter!.dy + : offset.dx < thumbCenter!.dx); break; } @@ -533,15 +668,25 @@ class SfMinorTickShape extends SfTickShape { : themeData.activeMinorTickColor; final Paint paint = Paint() ..isAntiAlias = true - ..strokeWidth = minorTickSize.width - ..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation); - context.canvas.drawLine( - offset, Offset(offset.dx, offset.dy + minorTickSize.height), paint); + ..strokeWidth = + _isVertical(parentBox) ? minorTickSize.height : minorTickSize.width + ..color = ColorTween(begin: begin, end: end).evaluate(enableAnimation)!; + if (_isVertical(parentBox)) { + context.canvas.drawLine( + offset, Offset(offset.dx + minorTickSize.width, offset.dy), paint); + } else { + context.canvas.drawLine( + offset, Offset(offset.dx, offset.dy + minorTickSize.height), paint); + } } } /// A class which is used to render paddle shape tooltip. class SfPaddleTooltipShape extends SfTooltipShape { + /// Creates a [SfPaddleTooltipShape]. + /// + /// A class which is used to render paddle shape tooltip. + const SfPaddleTooltipShape(); bool _isTooltipOverlapStroke(RenderBaseSlider parentBox) { return parentBox.showOverlappingTooltipStroke; } @@ -561,7 +706,7 @@ class SfPaddleTooltipShape extends SfTooltipShape { Rect trackRect, PaintingContext context, Animation animation, - Paint paint) { + Paint? paint) { final double paddleTopCircleRadius = textPainter.height > minPaddleTopCircleRadius ? textPainter.height @@ -662,19 +807,25 @@ class SfPaddleTooltipShape extends SfTooltipShape { context.canvas.save(); context.canvas.translate(thumbCenter.dx, thumbCenter.dy); context.canvas.scale(animation.value); - if (_isTooltipOverlapStroke(parentBox) && + final Paint strokePaint = Paint(); + // ignore: avoid_as + if (_isTooltipOverlapStroke(parentBox as RenderBaseSlider) && + sliderThemeData is SfRangeSliderThemeData && sliderThemeData.tooltipBackgroundColor != Colors.transparent) { - final Paint strokePaint = Paint() - ..color = sliderThemeData is SfRangeSliderThemeData - ? sliderThemeData.overlappingTooltipStrokeColor - : null + strokePaint + ..color = sliderThemeData.overlappingTooltipStrokeColor! ..style = PaintingStyle.stroke ..strokeWidth = 1.0; - - context.canvas.drawPath(path, strokePaint); } - - context.canvas.drawPath(path, paint); + // This loop is used to avoid the improper rendering of tooltips in + // web html rendering. + else { + strokePaint + ..color = Colors.transparent + ..style = PaintingStyle.stroke; + } + context.canvas.drawPath(path, strokePaint); + context.canvas.drawPath(path, paint!); textPainter.paint( context.canvas, Offset(-textPainter.width / 2 - shiftPaddleWidth, @@ -771,11 +922,11 @@ class SfPaddleTooltipShape extends SfTooltipShape { @override void paint(PaintingContext context, Offset thumbCenter, Offset offset, TextPainter textPainter, - {RenderBox parentBox, - SfSliderThemeData sliderThemeData, - Paint paint, - Animation animation, - Rect trackRect}) { + {required RenderBox parentBox, + required SfSliderThemeData sliderThemeData, + required Paint paint, + required Animation animation, + required Rect trackRect}) { _drawPaddleTooltip( parentBox, textPainter, @@ -797,32 +948,85 @@ class SfPaddleTooltipShape extends SfTooltipShape { /// A class which is used to render rectangular shape tooltip. class SfRectangularTooltipShape extends SfTooltipShape { + /// Creates a [SfRectangularTooltipShape]. + /// + /// A class which is used to render rectangular shape tooltip. + const SfRectangularTooltipShape(); bool _isTooltipOverlapStroke(RenderBaseSlider parentBox) { return parentBox.showOverlappingTooltipStroke; } + bool _isVertical(RenderBaseSlider parentBox) { + return parentBox.sliderType == SliderType.vertical; + } + + bool _isLeftTooltip(RenderBaseSlider parentBox) { + return parentBox.tooltipPosition == SliderTooltipPosition.left; + } + Path _updateRectangularTooltipWidth( - Size textSize, double tooltipStartY, Rect trackRect, double dx) { + Size textSize, double tooltipStartY, Rect trackRect, double dx, + {required bool isVertical, bool? isLeftTooltip}) { final double dy = tooltipStartY + tooltipTriangleHeight; final double tooltipWidth = textSize.width < minTooltipWidth ? minTooltipWidth : textSize.width; final double tooltipHeight = textSize.height < minTooltipHeight ? minTooltipHeight : textSize.height; final double halfTooltipWidth = tooltipWidth / 2; - - double rightLineWidth = dx + halfTooltipWidth > trackRect.right - ? trackRect.right - dx - : halfTooltipWidth; - final double leftLineWidth = dx - halfTooltipWidth < trackRect.left - ? dx - trackRect.left - : tooltipWidth - rightLineWidth; - rightLineWidth = leftLineWidth < halfTooltipWidth - ? halfTooltipWidth - leftLineWidth + rightLineWidth - : rightLineWidth; + final double halfTooltipHeight = tooltipHeight / 2; const double halfTooltipTriangleWidth = tooltipTriangleWidth / 2; - - return _getRectangularPath(tooltipStartY, rightLineWidth, - halfTooltipTriangleWidth, dy, tooltipHeight, leftLineWidth); + if (isVertical) { + if (isLeftTooltip!) { + double topLineHeight = dx - halfTooltipHeight < trackRect.top + ? dx - trackRect.top + : halfTooltipHeight; + final double bottomLineHeight = + dx + halfTooltipHeight > trackRect.bottom + ? trackRect.bottom - dx + : tooltipHeight - topLineHeight; + topLineHeight = bottomLineHeight < halfTooltipHeight + ? halfTooltipHeight - bottomLineHeight + topLineHeight + : topLineHeight; + return _getRectangularPath(tooltipStartY, topLineHeight, + halfTooltipTriangleWidth, dy, tooltipHeight, bottomLineHeight, + isVertical: isVertical, + toolTipWidth: tooltipWidth, + isLeftTooltip: isLeftTooltip); + } else { + double topLineHeight = dx - halfTooltipHeight < trackRect.top + ? dx - trackRect.top + : halfTooltipHeight; + final double bottomLineHeight = + dx + halfTooltipHeight > trackRect.bottom + ? trackRect.bottom - dx + : tooltipHeight - topLineHeight; + topLineHeight = bottomLineHeight < halfTooltipHeight + ? halfTooltipHeight - bottomLineHeight + topLineHeight + : topLineHeight; + return _getRectangularPath(tooltipStartY, topLineHeight, + halfTooltipTriangleWidth, dy, tooltipHeight, bottomLineHeight, + isVertical: isVertical, + toolTipWidth: tooltipWidth, + isLeftTooltip: isLeftTooltip); + } + } else { + double rightLineWidth = dx + halfTooltipWidth > trackRect.right + ? trackRect.right - dx + : halfTooltipWidth; + final double leftLineWidth = (isVertical) + ? tooltipWidth - rightLineWidth + : dx - halfTooltipWidth < trackRect.left + ? dx - trackRect.left + : tooltipWidth - rightLineWidth; + if (!isVertical) { + rightLineWidth = leftLineWidth < halfTooltipWidth + ? halfTooltipWidth - leftLineWidth + rightLineWidth + : rightLineWidth; + } + return _getRectangularPath(tooltipStartY, rightLineWidth, + halfTooltipTriangleWidth, dy, tooltipHeight, leftLineWidth, + isVertical: isVertical); + } } Path _getRectangularPath( @@ -831,47 +1035,174 @@ class SfRectangularTooltipShape extends SfTooltipShape { double halfTooltipTriangleWidth, double dy, double tooltipHeight, - double leftLineWidth) { + double leftLineWidth, + {required bool isVertical, + double? toolTipWidth, + bool? isLeftTooltip}) { final Path path = Path(); - path.moveTo(0, -tooltipStartY); - // / - final bool canAdjustTooltipNose = - rightLineWidth > halfTooltipTriangleWidth + cornerRadius / 2; - path.lineTo( - canAdjustTooltipNose ? halfTooltipTriangleWidth : rightLineWidth, - -dy - (canAdjustTooltipNose ? 0 : cornerRadius / 2)); - // ___ - // / - path.lineTo(rightLineWidth - cornerRadius, -dy); - // ___| - // / - path.quadraticBezierTo( - rightLineWidth, -dy, rightLineWidth, -dy - cornerRadius); - path.lineTo(rightLineWidth, -dy - tooltipHeight + cornerRadius); - // _______ - // ___| - // / - path.quadraticBezierTo(rightLineWidth, -dy - tooltipHeight, - rightLineWidth - cornerRadius, -dy - tooltipHeight); - path.lineTo(-leftLineWidth + cornerRadius, -dy - tooltipHeight); - // _______ - // | ___| - // / - path.quadraticBezierTo(-leftLineWidth, -dy - tooltipHeight, -leftLineWidth, - -dy - tooltipHeight + cornerRadius); - path.lineTo(-leftLineWidth, -dy - cornerRadius); - // ________ - // |___ ___| - // / - if (leftLineWidth > halfTooltipTriangleWidth) { + if (isVertical && toolTipWidth != null) { + if (isLeftTooltip!) { + path.moveTo(-tooltipStartY, 0); + // / + final bool canAdjustTooltipNose = + rightLineWidth < halfTooltipTriangleWidth; + path.lineTo(-dy, + canAdjustTooltipNose ? -rightLineWidth : -halfTooltipTriangleWidth); + // / + // | + if (!canAdjustTooltipNose) { + path.lineTo(-dy, -rightLineWidth + cornerRadius / 2); + } + path.quadraticBezierTo( + -dy, + canAdjustTooltipNose + ? -rightLineWidth + : -rightLineWidth + cornerRadius / 2, + -dy - cornerRadius / 2, + -rightLineWidth); + // / + // __________| + + path.lineTo(-dy - toolTipWidth + cornerRadius / 2, -rightLineWidth); + path.quadraticBezierTo( + -dy - toolTipWidth + cornerRadius / 2, + -rightLineWidth, + -dy - toolTipWidth, + -rightLineWidth + cornerRadius / 2); + // | + // | / + // |__________| + path.lineTo(-dy - toolTipWidth, leftLineWidth - cornerRadius / 2); + path.quadraticBezierTo( + -dy - toolTipWidth, + leftLineWidth - cornerRadius / 2, + -dy - toolTipWidth + cornerRadius / 2, + leftLineWidth, + ); + // _________ + // / + // | + // | / + // |__________| + path.lineTo(-dy - cornerRadius / 2, leftLineWidth); + // __________ + // | | + // | + // | / + // |__________| + if (leftLineWidth > halfTooltipTriangleWidth) { + path.quadraticBezierTo(-dy - cornerRadius / 2, leftLineWidth, -dy, + leftLineWidth - cornerRadius / 2); + path.lineTo(-dy, halfTooltipTriangleWidth); + } + // __________ + // | | + // | \ + // | / + // |__________| + + path.close(); + } else { + path.moveTo(tooltipStartY, 0); + // \ + final bool canAdjustTooltipNose = + rightLineWidth < halfTooltipTriangleWidth; + path.lineTo(dy, + canAdjustTooltipNose ? -rightLineWidth : -halfTooltipTriangleWidth); + // \ + // | + if (!canAdjustTooltipNose) { + path.lineTo(dy, -rightLineWidth + cornerRadius / 2); + } + // \ + // |__________ + path.quadraticBezierTo( + dy, + canAdjustTooltipNose + ? -rightLineWidth + : -rightLineWidth + cornerRadius / 2, + dy + cornerRadius / 2, + -rightLineWidth); + path.lineTo(dy + toolTipWidth - cornerRadius / 2, -rightLineWidth); + // | + // \ | + // |__________| + path.quadraticBezierTo( + dy + toolTipWidth - cornerRadius / 2, + -rightLineWidth, + dy + toolTipWidth, + -rightLineWidth + cornerRadius / 2); + path.lineTo(dy + toolTipWidth, leftLineWidth - cornerRadius / 2); + // ___________ + // | + // | + // \ | + // |__________| + path.quadraticBezierTo( + dy + toolTipWidth, + leftLineWidth - cornerRadius / 2, + dy + toolTipWidth - cornerRadius / 2, + leftLineWidth, + ); + path.lineTo(dy + cornerRadius / 2, leftLineWidth); + // ___________ + // | | + // | + // \ | + // |__________| + if (leftLineWidth > halfTooltipTriangleWidth) { + path.quadraticBezierTo(dy + cornerRadius / 2, leftLineWidth, dy, + leftLineWidth - cornerRadius / 2); + path.lineTo(dy, halfTooltipTriangleWidth); + } + // ___________ + // | | + // | | + // \ | + // |__________| + path.close(); + } + } else { + path.moveTo(0, -tooltipStartY); + // / + final bool canAdjustTooltipNose = + rightLineWidth > halfTooltipTriangleWidth + cornerRadius / 2; + path.lineTo( + canAdjustTooltipNose ? halfTooltipTriangleWidth : rightLineWidth, + -dy - (canAdjustTooltipNose ? 0 : cornerRadius / 2)); + // ___ + // / + path.lineTo(rightLineWidth - cornerRadius, -dy); + // ___| + // / path.quadraticBezierTo( - -leftLineWidth, -dy, -leftLineWidth + cornerRadius, -dy); - path.lineTo(-halfTooltipTriangleWidth, -dy); + rightLineWidth, -dy, rightLineWidth, -dy - cornerRadius); + path.lineTo(rightLineWidth, -dy - tooltipHeight + cornerRadius); + // _______ + // ___| + // / + path.quadraticBezierTo(rightLineWidth, -dy - tooltipHeight, + rightLineWidth - cornerRadius, -dy - tooltipHeight); + path.lineTo(-leftLineWidth + cornerRadius, -dy - tooltipHeight); + // _______ + // | ___| + // / + path.quadraticBezierTo(-leftLineWidth, -dy - tooltipHeight, + -leftLineWidth, -dy - tooltipHeight + cornerRadius); + path.lineTo(-leftLineWidth, -dy - cornerRadius); + // ________ + // |___ ___| + // / + if (leftLineWidth > halfTooltipTriangleWidth) { + path.quadraticBezierTo( + -leftLineWidth, -dy, -leftLineWidth + cornerRadius, -dy); + path.lineTo(-halfTooltipTriangleWidth, -dy); + } + // ________ + // |___ ___| + // \/ + path.close(); } - // ________ - // |___ ___| - // \/ - path.close(); return path; } @@ -879,59 +1210,111 @@ class SfRectangularTooltipShape extends SfTooltipShape { @override void paint(PaintingContext context, Offset thumbCenter, Offset offset, TextPainter textPainter, - {RenderBox parentBox, - SfSliderThemeData sliderThemeData, - Paint paint, - Animation animation, - Rect trackRect}) { + {required RenderBox parentBox, + required SfSliderThemeData sliderThemeData, + required Paint paint, + required Animation animation, + required Rect trackRect}) { final double leftPadding = tooltipTextPadding.dx / 2; final double minLeftX = trackRect.left; - final Path path = _updateRectangularTooltipWidth( - textPainter.size + tooltipTextPadding, - offset.dy, - trackRect, - thumbCenter.dx); + // ignore: avoid_as + final Path path = (_isVertical(parentBox as RenderBaseSlider)) + ? _updateRectangularTooltipWidth(textPainter.size + tooltipTextPadding, + offset.dy, trackRect, thumbCenter.dy, + isVertical: _isVertical(parentBox), + isLeftTooltip: _isLeftTooltip(parentBox)) + : _updateRectangularTooltipWidth(textPainter.size + tooltipTextPadding, + offset.dy, trackRect, thumbCenter.dx, + isVertical: _isVertical(parentBox)); context.canvas.save(); context.canvas.translate(thumbCenter.dx, thumbCenter.dy); context.canvas.scale(animation.value); + final Paint strokePaint = Paint(); if (_isTooltipOverlapStroke(parentBox) && sliderThemeData.tooltipBackgroundColor != Colors.transparent) { - final Paint strokePaint = Paint(); if (sliderThemeData is SfRangeSliderThemeData) { - strokePaint.color = sliderThemeData.overlappingTooltipStrokeColor; + strokePaint.color = sliderThemeData.overlappingTooltipStrokeColor!; strokePaint.style = PaintingStyle.stroke; strokePaint.strokeWidth = 1.0; } else if (sliderThemeData is SfRangeSelectorThemeData) { - strokePaint.color = sliderThemeData.overlappingTooltipStrokeColor; + strokePaint.color = sliderThemeData.overlappingTooltipStrokeColor!; strokePaint.style = PaintingStyle.stroke; strokePaint.strokeWidth = 1.0; } - context.canvas.drawPath(path, strokePaint); } - + // This loop is used to avoid the improper rendering of tooltips in + // web html rendering. + else { + strokePaint + ..color = Colors.transparent + ..style = PaintingStyle.stroke; + } + context.canvas.drawPath(path, strokePaint); context.canvas.drawPath(path, paint); final Rect pathRect = path.getBounds(); final double halfPathWidth = pathRect.width / 2; final double halfTextPainterWidth = textPainter.width / 2; final double rectLeftPosition = thumbCenter.dx - halfPathWidth; - final double dx = rectLeftPosition >= minLeftX - ? thumbCenter.dx + halfTextPainterWidth + leftPadding > trackRect.right - ? -halfTextPainterWidth - - halfPathWidth + - trackRect.right - - thumbCenter.dx - : -halfTextPainterWidth - : -halfTextPainterWidth + - halfPathWidth + - trackRect.left - - thumbCenter.dx; - final double dy = offset.dy + - tooltipTriangleHeight + - (pathRect.size.height - tooltipTriangleHeight) / 2 + - textPainter.height / 2; - textPainter.paint(context.canvas, Offset(dx, -dy)); + if (_isVertical(parentBox)) { + final double halfPathHeight = pathRect.height / 2; + final double halfTextPainterHeight = textPainter.height / 2; + final double rectTopPosition = thumbCenter.dy - halfPathHeight; + if (_isLeftTooltip(parentBox)) { + final double dx = -offset.dy - + tooltipTriangleHeight - + (pathRect.size.width - tooltipTriangleHeight) / 2 - + textPainter.width / 2; + final double dy = rectTopPosition >= trackRect.top + ? thumbCenter.dy + halfPathHeight >= trackRect.bottom + ? -halfTextPainterHeight - + halfPathHeight - + thumbCenter.dy + + trackRect.bottom + : -halfTextPainterHeight + : -halfTextPainterHeight + + halfPathHeight - + thumbCenter.dy + + trackRect.top; + textPainter.paint(context.canvas, Offset(dx, dy)); + } else { + final double dx = offset.dy + + tooltipTriangleHeight + + (pathRect.size.width - tooltipTriangleHeight) / 2 - + textPainter.width / 2; + final double dy = rectTopPosition >= trackRect.top + ? thumbCenter.dy + halfPathHeight >= trackRect.bottom + ? -halfTextPainterHeight - + halfPathHeight - + thumbCenter.dy + + trackRect.bottom + : -halfTextPainterHeight + : -halfTextPainterHeight + + halfPathHeight - + thumbCenter.dy + + trackRect.top; + textPainter.paint(context.canvas, Offset(dx, dy)); + } + } else { + final double dx = rectLeftPosition >= minLeftX + ? thumbCenter.dx + halfTextPainterWidth + leftPadding > + trackRect.right + ? -halfTextPainterWidth - + halfPathWidth + + trackRect.right - + thumbCenter.dx + : -halfTextPainterWidth + : -halfTextPainterWidth + + halfPathWidth + + trackRect.left - + thumbCenter.dx; + final double dy = offset.dy + + tooltipTriangleHeight + + (pathRect.size.height - tooltipTriangleHeight) / 2 + + textPainter.height / 2; + textPainter.paint(context.canvas, Offset(dx, -dy)); + } context.canvas.restore(); } } diff --git a/packages/syncfusion_flutter_sliders/pubspec.yaml b/packages/syncfusion_flutter_sliders/pubspec.yaml index cdbed9228..ffb6f7421 100644 --- a/packages/syncfusion_flutter_sliders/pubspec.yaml +++ b/packages/syncfusion_flutter_sliders/pubspec.yaml @@ -1,16 +1,16 @@ name: syncfusion_flutter_sliders -description: Syncfusion Flutter Sliders library is written natively in Dart for creating highly interactive and UI-rich slider controls for filtering purposes. +description: A Flutter Sliders library for creating highly customizable and UI-rich slider, range slider, and range selector widgets for filtering purposes. version: 18.3.35-beta homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_sliders environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: flutter: sdk: flutter - intl: ">=0.15.0 <0.17.0" + intl: ">=0.15.0 <0.20.0" syncfusion_flutter_core: path: ../syncfusion_flutter_core diff --git a/packages/syncfusion_flutter_treemap/CHANGELOG.md b/packages/syncfusion_flutter_treemap/CHANGELOG.md new file mode 100644 index 000000000..b8fef3f66 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/CHANGELOG.md @@ -0,0 +1,15 @@ +## Unreleased + +Initial release. + +**Features** + +Includes the treemap widget with these features: + +* Labels +* Layouts +* Hierarchical support +* Colors +* Legend +* Tooltip +* Selection diff --git a/packages/syncfusion_flutter_treemap/LICENSE b/packages/syncfusion_flutter_treemap/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/syncfusion_flutter_treemap/README.md b/packages/syncfusion_flutter_treemap/README.md new file mode 100644 index 000000000..6d273f29d --- /dev/null +++ b/packages/syncfusion_flutter_treemap/README.md @@ -0,0 +1,247 @@ +![syncfusion_flutter_treemap_banner](https://cdn.syncfusion.com/content/images/Flutter/pub_images/treemap_images/treemap-banner.png) + +# Flutter Treemap library + +A Flutter Treemap library for creating interactive treemap to visualize flat and hierarchical data as rectangles that are sized and colored based on quantitative variables using squarified, slice, and dice algorithms. + +## Overview + +Create a highly interactive and customizable Flutter Treemap that has features set like selection, legends, labels, tooltips, color mapping, and much more. + +**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. + +## Table of contents +- [Treemap features](#treemap-features) +- [Get the demo application](#get-the-demo-application) +- [Useful links](#useful-links) +- [Installation](#installation) +- [Flutter Treemap example](#flutter-treemap-example) +- [Create Treemap](#create-treemap) +- [Mapping the data source](#mapping-the-data-source) +- [Add treemap elements](#add-treemap-elements) +- [Support and Feedback](#support-and-feedback) +- [About Syncfusion](#about-syncfusion) + +## Treemap features + +**Layouts** - Use different layouts based on the algorithms such as squarified, slice, and dice to represent flat and hierarchically structured data. + +* **Squarified:** + +![Squarified layout](https://cdn.syncfusion.com/content/images/Flutter/pub_images/treemap_images/squarified.png) + +* **Slice:** + +![Slice layout](https://cdn.syncfusion.com/content/images/Flutter/pub_images/treemap_images/slice.png) + +* **Dice:** + +![Dice layout](https://cdn.syncfusion.com/content/images/Flutter/pub_images/treemap_images/dice.png) + +**Hierarchical support** - Along with the flat level, treemap supports hierarchical structure too. Each tile of the treemap is a rectangle which is filled with smaller rectangles representing sub-data. + +![Hierarchical support](https://cdn.syncfusion.com/content/images/Flutter/pub_images/treemap_images/hierarchical.png) + +**Labels** - Add any type of widgets (like text widget) to improve the readability of the individual tiles by providing brief descriptions on labels. + +**Selection** - Allows you to select the tiles to highlight it and do any specific functionalities like showing pop-up or navigate to a different page. + +![Selection support](https://cdn.syncfusion.com/content/images/Flutter/pub_images/treemap_images/selection.png) + +**Legend** - Use different legend styles to provide information on the treemap data clearly. + +![Legend support](https://cdn.syncfusion.com/content/images/Flutter/pub_images/treemap_images/legend.png) + +**Colors** - Categorize the tiles on the treemap by customizing their color based on the levels. It is possible to set the tile color for a specific value or for a range of values. + +![Color customization](https://cdn.syncfusion.com/content/images/Flutter/pub_images/treemap_images/color-mapper.png) + +**Tooltip** - Display additional information about the tile using a completely customizable tooltip on the treemap. + +![Tooltip support](https://cdn.syncfusion.com/content/images/Flutter/pub_images/treemap_images/tooltip.png) + +**Custom background widgets** - Add any type of custom widgets such as image widget as a background of the tiles to enrich the UI and easily visualize the type of data that a particular tile shows. + +![Treemap customization](https://cdn.syncfusion.com/content/images/Flutter/pub_images/treemap_images/customization.png) + +## Get the demo application + +Explore the full capability of our Flutter widgets on your device by installing our sample browser application from the following app stores. View sample codes in GitHub. + +

+ +

+ +## Useful links + +Take a look at the following to learn more about Syncfusion Flutter Treemap: + +* [Syncfusion Flutter Treemap product page](https://www.syncfusion.com/flutter-widgets/treemap) +* [User guide documentation for Treemap](https://help.syncfusion.com/flutter/treemap) +* [Knowledge base](https://www.syncfusion.com/kb) + +## Installation + +Install the latest version from [pub](https://pub.dev/packages/syncfusion_flutter_treemap#-installing-tab-). + +## Flutter Treemap example + +Import the following package. + +```dart +import 'package:syncfusion_flutter_treemap/treemap.dart'; +``` + +### Create Treemap + +After importing the package, initialize the treemap widget as a child of any widget. + +```dart +@override +Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: SfTreemap(), + ), + ); +} +``` + +### Mapping the data source + +To populate the data source, set its count to the `dataCount` property of the treemap. The data will be grouped based on the values returned from the `TreemapLevel.groupMapper` callback. You can have more than one TreemapLevel in the treemap `levels` collection to form a hierarchical treemap. The quantitative value of the underlying data has to be returned from the `weightValueMapper` callback. Based on this value, every tile (rectangle) will have its size. + +```dart +List _source; + +@override +void initState() { + _source = [ + SocialMediaUsers('India', 'Facebook', 25.4), + SocialMediaUsers('USA', 'Instagram', 19.11), + SocialMediaUsers('Japan', 'Facebook', 13.3), + SocialMediaUsers('Germany', 'Instagram', 10.65), + SocialMediaUsers('France', 'Twitter', 7.54), + SocialMediaUsers('UK', 'Instagram', 4.93), + ]; + super.initState(); +} + +@override +Widget build(BuildContext context) { + return Scaffold( + body: SfTreemap( + dataCount: _source.length, + weightValueMapper: (int index) { + return _source[index].usersInMillions; + }, + levels: [ + TreemapLevel( + groupMapper: (int index) { + return _source[index].country; + }, + ), + ], + ), + ); +} + +class SocialMediaUsers { + const SocialMediaUsers(this.country, this.socialMedia, this.usersInMillions); + + final String country; + final String socialMedia; + final double usersInMillions; +} +``` + +![Default treemap view](https://cdn.syncfusion.com/content/images/Flutter/pub_images/treemap_images/default-view.png) + +### Add treemap elements + +Add the basic treemap elements such as tooltip, labels, and legend as shown in the below code snippet. + +* **Tooltip** - You can enable tooltip for any tile in the treemap and able to return the completely customized widget using the `tooltipBuilder` property. + +* **Labels** - You can add any type of custom widgets to the tiles as labels based on the index using the `labelBuilder` property. + +* **Legend** - You can show legend by initializing the `legend` property in the `SfTreemap`. It is possible to customize the legend item's color and text using the `SfTreemap.colorMappers` property. + +```dart +List _source; + +@override +void initState() { + _source = [ + SocialMediaUsers('India', 'Facebook', 25.4), + SocialMediaUsers('USA', 'Instagram', 19.11), + SocialMediaUsers('Japan', 'Facebook', 13.3), + SocialMediaUsers('Germany', 'Instagram', 10.65), + SocialMediaUsers('France', 'Twitter', 7.54), + SocialMediaUsers('UK', 'Instagram', 4.93), + ]; + super.initState(); +} + +@override +Widget build(BuildContext context) { + return Scaffold( + body: SfTreemap( + dataCount: _source.length, + weightValueMapper: (int index) { + return _source[index].usersInMillions; + }, + levels: [ + TreemapLevel( + groupMapper: (int index) { + return _source[index].country; + }, + labelBuilder: (BuildContext context, TreemapTile tile) { + return Padding( + padding: const EdgeInsets.all(2.5), + child: Text('${tile.group}', + style: TextStyle(color: Colors.black), + ), + ); + }, + tooltipBuilder: (BuildContext context, TreemapTile tile) { + return Padding( + padding: const EdgeInsets.all(10), + child: Text('Country : ${tile.group}\nSocial media : ${tile.weight}M', + style: TextStyle(color: Colors.black)), + ); + }, + ), + ], + ), + ); +} + +class SocialMediaUsers { + const SocialMediaUsers(this.country, this.socialMedia, this.usersInMillions); + + final String country; + final String socialMedia; + final double usersInMillions; +} +``` + +The following screenshot illustrates the result of the above code sample. + +![Treemap getting started](https://cdn.syncfusion.com/content/images/Flutter/pub_images/treemap_images/getting-started.png) + +## Support and feedback + +* For questions, reach out to our [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident) or post them through the [Community forums](https://www.syncfusion.com/forums). Submit a feature request or a bug in our [Feedback portal](https://www.syncfusion.com/feedback/flutter). +* To renew your subscription, click [renew](https://www.syncfusion.com/sales/products) or contact our sales team at sales@syncfusion.com | Toll Free: 1-888-9 DOTNET. + +## About Syncfusion + +Founded in 2001 and headquartered in Research Triangle Park, N.C., Syncfusion has more than 20,000 customers and more than 1 million users, including large financial institutions, Fortune 500 companies, and global IT consultancies. + +Today we provide 1,000+ controls and frameworks for web ([ASP.NET Core](https://www.syncfusion.com/aspnet-core-ui-controls), [ASP.NET MVC](https://www.syncfusion.com/aspnet-mvc-ui-controls), [ASP.NET WebForms](https://www.syncfusion.com/jquery/aspnet-web-forms-ui-controls), [JavaScript](https://www.syncfusion.com/javascript-ui-controls), [Angular](https://www.syncfusion.com/angular-ui-components), [React](https://www.syncfusion.com/react-ui-components), [Vue](https://www.syncfusion.com/vue-ui-components), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [Flutter](https://www.syncfusion.com/flutter-widgets), [UWP](https://www.syncfusion.com/uwp-ui-controls), and [JavaScript](https://www.syncfusion.com/javascript-ui-controls)), and desktop development ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-controls)). We provide ready-to-deploy enterprise software for dashboards, reports, data integration, and big data processing. Many customers have saved millions in licensing fees by deploying our software. diff --git a/packages/syncfusion_flutter_treemap/analysis_options.yaml b/packages/syncfusion_flutter_treemap/analysis_options.yaml new file mode 100644 index 000000000..2f547af5d --- /dev/null +++ b/packages/syncfusion_flutter_treemap/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:syncfusion_flutter_core/analysis_options.yaml + +analyzer: + errors: + include_file_not_found: ignore \ No newline at end of file diff --git a/packages/syncfusion_flutter_treemap/example/README.md b/packages/syncfusion_flutter_treemap/example/README.md new file mode 100644 index 000000000..a254f6827 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/README.md @@ -0,0 +1,14 @@ +# syncfusion_flutter_treemap + +A new Flutter package project. + +## Getting Started + +This project is a starting point for a Dart +[package](https://flutter.dev/developing-packages/), +a library module containing code that can be shared easily across +multiple Flutter or Dart projects. + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. \ No newline at end of file diff --git a/packages/syncfusion_flutter_treemap/example/android/.gitignore b/packages/syncfusion_flutter_treemap/example/android/.gitignore new file mode 100644 index 000000000..0a741cb43 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/packages/syncfusion_flutter_treemap/example/android/app/build.gradle b/packages/syncfusion_flutter_treemap/example/android/app/build.gradle new file mode 100644 index 000000000..3932aa910 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/app/build.gradle @@ -0,0 +1,63 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 29 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.example" + minSdkVersion 16 + targetSdkVersion 29 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_treemap/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..c208884f3 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_treemap/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..55ca830c3 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/syncfusion_flutter_treemap/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 000000000..e793a000d --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/drawable/launch_background.xml b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..db77bb4b7 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..17987b79b Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..09d439148 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d5f1c8d34 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4d6372eeb Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..1f83a33fd --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_treemap/example/android/app/src/profile/AndroidManifest.xml b/packages/syncfusion_flutter_treemap/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..c208884f3 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/syncfusion_flutter_treemap/example/android/build.gradle b/packages/syncfusion_flutter_treemap/example/android/build.gradle new file mode 100644 index 000000000..3100ad2d5 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/syncfusion_flutter_treemap/example/android/gradle.properties b/packages/syncfusion_flutter_treemap/example/android/gradle.properties new file mode 100644 index 000000000..a6738207f --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true diff --git a/packages/syncfusion_flutter_treemap/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_treemap/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..296b146b7 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/packages/syncfusion_flutter_treemap/example/android/settings.gradle b/packages/syncfusion_flutter_treemap/example/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/syncfusion_flutter_treemap/example/ios/.gitignore b/packages/syncfusion_flutter_treemap/example/ios/.gitignore new file mode 100644 index 000000000..e96ef602b --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/syncfusion_flutter_treemap/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_treemap/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..f2872cf47 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/packages/syncfusion_flutter_treemap/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_treemap/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_treemap/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_treemap/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..1aec2aa07 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,495 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..a28140cfd --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_treemap/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000..dc9ada472 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000..28c6bf030 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000..f091b6b0b Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000..4cde12118 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000..d0ef06e7e Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000..dcdc2306c Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000..2ccbfd967 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000..c8f9ed8f5 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000..a6d6b8609 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000..75b2d164a Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000..c4df70d39 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000..6a84f41e1 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000..d0e1f5853 Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000..0bedcf2fd --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000..9da19eaca Binary files /dev/null and b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/syncfusion_flutter_treemap/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Base.lproj/Main.storyboard b/packages/syncfusion_flutter_treemap/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_treemap/example/ios/Runner/Info.plist new file mode 100644 index 000000000..a060db61e --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/syncfusion_flutter_treemap/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_treemap/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/syncfusion_flutter_treemap/example/lib/main.dart b/packages/syncfusion_flutter_treemap/example/lib/main.dart new file mode 100644 index 000000000..a0a3dfed6 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/lib/main.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_treemap/treemap.dart'; + +void main() { + return runApp(TreemapApp()); +} + +/// This widget will be the root of application. +class TreemapApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return const MaterialApp( + title: 'Treemap Demo', + home: MyHomePage(), + ); + } +} + +/// This widget is the home page of the application. +class MyHomePage extends StatefulWidget { + /// Initialize the instance of the [MyHomePage] class. + const MyHomePage({Key? key}) : super(key: key); + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + late List _source; + + @override + void initState() { + _source = [ + SocialMediaUsers('India', 'Facebook', 25.4), + SocialMediaUsers('USA', 'Instagram', 19.11), + SocialMediaUsers('Japan', 'Facebook', 13.3), + SocialMediaUsers('Germany', 'Instagram', 10.65), + SocialMediaUsers('France', 'Twitter', 7.54), + SocialMediaUsers('UK', 'Instagram', 4.93), + ]; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Treemap demo')), + body: SfTreemap( + dataCount: _source.length, + weightValueMapper: (int index) { + return _source[index].usersInMillions; + }, + levels: [ + TreemapLevel( + groupMapper: (int index) { + return _source[index].country; + }, + labelBuilder: (BuildContext context, TreemapTile tile) { + return Padding( + padding: const EdgeInsets.all(2.5), + child: Text( + '${tile.group}', + style: TextStyle(color: Colors.black), + ), + ); + }, + tooltipBuilder: (BuildContext context, TreemapTile tile) { + return Padding( + padding: const EdgeInsets.all(10), + child: Text( + '''Country : ${tile.group}\nSocial media : ${tile.weight}M''', + style: TextStyle(color: Colors.black)), + ); + }, + ), + ], + ), + ); + } +} + +class SocialMediaUsers { + const SocialMediaUsers(this.country, this.socialMedia, this.usersInMillions); + + final String country; + final String socialMedia; + final double usersInMillions; +} diff --git a/packages/syncfusion_flutter_treemap/example/pubspec.yaml b/packages/syncfusion_flutter_treemap/example/pubspec.yaml new file mode 100644 index 000000000..cf800724f --- /dev/null +++ b/packages/syncfusion_flutter_treemap/example/pubspec.yaml @@ -0,0 +1,75 @@ +name: treemap_sample +description: A new Flutter project. + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + syncfusion_flutter_treemap: + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^0.1.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/syncfusion_flutter_treemap/lib/src/layouts.dart b/packages/syncfusion_flutter_treemap/lib/src/layouts.dart new file mode 100644 index 000000000..b701295e0 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/lib/src/layouts.dart @@ -0,0 +1,1351 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../src/legend.dart'; +import '../treemap.dart'; +import 'tooltip.dart'; + +// Combines the two color values and returns a new saturated color. We have +// used black color as default mix color. +Color _getSaturatedColor(Color color, double factor, + [Color mix = Colors.black]) { + return color == Colors.transparent + ? color + : Color.fromRGBO( + ((1 - factor) * color.red + factor * mix.red).toInt(), + ((1 - factor) * color.green + factor * mix.green).toInt(), + ((1 - factor) * color.blue + factor * mix.blue).toInt(), + 1); +} + +/// Specifies the kind of pointer. +enum PointerKind { + /// Indicates the pointer kind as touch. + touch, + + /// Indicates the pointer kind as hover. + hover +} + +/// The [SfTreemap]'s layout type. +enum LayoutType { + /// Squarified layout. + squarified, + + /// Slice layout. + slice, + + /// Dice layout. + dice +} + +/// Contains information about the treemap tile. +class TreemapTile { + TreemapTile._(); + + /// Name of the tile group. This is the value returned in the + /// [TreemapLevel.groupMapper]. + String get group => _group; + late String _group; + + /// Contains the indices of the data source which forms this tile. + List get indices => _indices; + late List _indices; + + /// Weight of the respected tile. + double get weight => _weight; + late double _weight; + + /// Color of the tile. + Color get color => _color; + late Color _color; + + /// Respected tile level details. + TreemapLevel get level => _level; + late TreemapLevel _level; + + /// Area to be occupied by the squarified tile. + double? _area; + + /// TopLeft position of the squarified tile. + Offset? _offset; + + /// Size of the squarified tile. + late Size _size; + + /// Size of the respected tile's label builder which is used to position the + /// tooltip. Applicable only for hierarchical (parent) tiles. + Size? _labelBuilderSize; + + /// Surrounded padding of the respected tile. + EdgeInsetsGeometry? _padding; + + /// Child of the tile. + Map? _descendants; +} + +/// The [Treemap] is a widget for data visualization that uses +/// nested rectangles to view hierarchical data. +class Treemap extends StatefulWidget { + /// Creates [Treemap]. + const Treemap({ + Key? key, + required this.layoutType, + required this.dataCount, + required this.levels, + required this.weightValueMapper, + required this.colorMappers, + required this.legend, + required this.onSelectionChanged, + required this.selectionSettings, + required this.tooltipSettings, + }) : super(key: key); + + /// Represents the length of the given data source. + final int dataCount; + + /// Returns a value based on which index passed through it. + final IndexedDoubleValueMapper weightValueMapper; + + /// Collection of [TreemapLevel] which specifies the grouping in the + /// given data source. + final List levels; + + /// Collection of [TreemapColorMapper] which specifies treemap color based + /// on the data. + final List? colorMappers; + + /// Invoke at the time of selection is handled. + final ValueChanged? onSelectionChanged; + + /// Option to customize the selected tiles appearance. + final TreemapSelectionSettings selectionSettings; + + /// Customizes the appearance of the tooltip. + final TreemapTooltipSettings tooltipSettings; + + /// Specifies the type of the treemap. + final LayoutType layoutType; + + /// The [legend] property provides additional information about the data + /// rendered in the tree map. + final TreemapLegend? legend; + + @override + _TreemapState createState() => _TreemapState(); +} + +class _TreemapState extends State { + final Color _baseColor = Color.fromRGBO(42, 80, 160, 1); + late GlobalKey _tooltipKey; + late Map _dataSource; + + late int _levelsLength; + double _totalWeight = 0.0; + + // Set true if there is a tooltip builder for any of the TreemapLevels. We had + // checked this while the tiles were grouped. If true, we've added a treemap + // widget, and a tooltip widget to stack as children. + bool _isTooltipEnabled = false; + bool _canUpdateTileColor = true; + late Future _computeDataSource; + + _SquarifiedTreemap get _squarified => _SquarifiedTreemap( + dataCount: widget.dataCount, + dataSource: _dataSource, + totalWeight: _totalWeight, + tooltipKey: _tooltipKey, + onSelectionChanged: widget.onSelectionChanged, + selectionSettings: widget.selectionSettings, + ); + + _SliceAndDiceTreemap get _sliceAndDice => _SliceAndDiceTreemap( + dataCount: widget.dataCount, + dataSource: _dataSource, + type: widget.layoutType, + totalWeight: _totalWeight, + tooltipKey: _tooltipKey, + onSelectionChanged: widget.onSelectionChanged, + selectionSettings: widget.selectionSettings, + ); + + TreemapTooltip get _tooltip => + TreemapTooltip(key: _tooltipKey, settings: widget.tooltipSettings); + + Widget _buildTreemap(BuildContext context, bool hasData) { + Widget current = widget.layoutType == LayoutType.squarified + ? _squarified + : _sliceAndDice; + if (widget.legend != null) { + final LegendWidget legend = LegendWidget( + dataSource: widget.colorMappers ?? _dataSource, + settings: widget.legend!); + + if (widget.legend!.offset == null) { + switch (widget.legend!.position) { + case TreemapLegendPosition.top: + current = + Column(children: [legend, Expanded(child: current)]); + break; + case TreemapLegendPosition.bottom: + current = + Column(children: [Expanded(child: current), legend]); + break; + case TreemapLegendPosition.left: + current = Row(children: [legend, Expanded(child: current)]); + break; + case TreemapLegendPosition.right: + current = Row(children: [Expanded(child: current), legend]); + break; + } + } else { + current = Stack( + children: [ + current, + Align( + alignment: _getLegendAlignment(widget.legend!.position), + child: Padding(padding: _getLegendOffset(), child: legend), + ), + ], + ); + } + } + + return current; + } + + void _invalidate() { + _canUpdateTileColor = true; + _dataSource.clear(); + _obtainDataSource(); + } + + void _obtainDataSource() { + _computeDataSource = + _obtainDataSourceAndBindColorMappers().then((bool value) => value); + } + + Future _obtainDataSourceAndBindColorMappers() async { + if (_dataSource.isEmpty) { + for (int i = 0; i < widget.dataCount; i++) { + final double weight = widget.weightValueMapper(i); + assert(weight > 0); + _totalWeight += weight; + _groupTiles(weight, i); + } + } + + if (_dataSource.isNotEmpty && _canUpdateTileColor) { + _bindColorMappersIntoDataSource(_dataSource); + _canUpdateTileColor = false; + } + + return true; + } + + void _groupTiles(double weight, int dataIndex, + {int currentLevelIndex = 0, TreemapTile? ancestor}) { + final TreemapLevel currentLevel = widget.levels[currentLevelIndex]; + final String? groupKey = (currentLevel.groupMapper(dataIndex)); + final Color? color = currentLevel.color; + final EdgeInsetsGeometry? padding = currentLevel.padding; + final int nextLevelIndex = currentLevelIndex + 1; + _isTooltipEnabled |= currentLevel.tooltipBuilder != null; + + // If the groupKey is null, we have checked for next level. If we have next + // level, we will add that level as children to the current ancestor. + // Eg.: Consider we have 3 levels, [0] and [2] has group key but [1] didn't + // have group key, here we have considered the [2] as child to the [0]. + if (groupKey == null) { + if (nextLevelIndex < _levelsLength) { + _groupTiles(weight, dataIndex, + currentLevelIndex: nextLevelIndex, ancestor: ancestor); + } + return; + } + + // We used the dataSource for grouping tiles based on the group key during + // the first step, i.e.[0]. For further levels, i.e.[0+i], we used the same + // ancestor in _groupTiles method as _dataSource[i].descendants to update or + // add the tiles. + // + // Example : Lets consider a sample data with three levels, + // + // SampleData(level0: 'A', level1: 'A1', level2: 'A11', weight: 1), + // SampleData(level0: 'B', level1: 'B1', level2: 'B21', weight: 2), + // SampleData(level0: 'C', level2: 'C11', weight: 3), + // + // We have three levels, [0] is level0, [1] is level1 and [2] is level2. + // + // Let's take the 1st data and search the group key for level[0]. If the + // group key is not null, in the dataSource, we've changed it. + // Then we checked the group key for [1]. We have tested for the next + // level[1] group key. If the group key is not null, we have modified it in + // the dataSource. If it was null, we had repeated this step again. + // Similarly we have checked completely until last level for 1st data. + // + // After that, we search for the second data and again continue with the + // steps above. Checked the group key for [2]. We have modified in + // dataSource[0][0].descendants if it is not null. Likewise, We've also + // checked for the next stages. + ((ancestor?._descendants ??= {}) ?? _dataSource).update( + groupKey, + (TreemapTile tile) { + tile._weight += weight; + tile.indices.add(dataIndex); + if (nextLevelIndex < _levelsLength) { + _groupTiles(weight, dataIndex, + currentLevelIndex: nextLevelIndex, ancestor: tile); + } + return tile; + }, + ifAbsent: () { + final TreemapTile tile = TreemapTile._() + .._group = groupKey + .._level = currentLevel + .._indices = [dataIndex] + .._weight = weight + .._padding = padding; + if (color != null) { + tile._color = color; + } + + if (nextLevelIndex < _levelsLength) { + _groupTiles(weight, dataIndex, + currentLevelIndex: nextLevelIndex, ancestor: tile); + } + return tile; + }, + ); + } + + void _bindColorMappersIntoDataSource(Map? source) { + if (source != null && source.isNotEmpty) { + final double baseWeight = + source.values.map((TreemapTile tile) => tile._weight).reduce(max); + for (final TreemapTile tile in source.values) { + tile._color = _getColor(tile) ?? + _getSaturatedColor( + _baseColor, 1 - (tile._weight / baseWeight), Colors.white); + _bindColorMappersIntoDataSource(tile._descendants); + } + } + } + + Color? _getColor(TreemapTile tile) { + final List? colorMappers = widget.colorMappers; + final Object? colorValue = tile.level.colorValueMapper?.call(tile); + if (colorValue is Color) { + return colorValue; + } else if (colorMappers != null) { + // Value color mapping. + if (colorValue is String) { + for (final TreemapColorMapper mapper in colorMappers) { + assert(mapper.value != null); + if (mapper.value == colorValue) { + return mapper.color; + } + } + } + // Range color mapping. + else if (colorValue is num) { + return _getRangeColor(colorValue, colorMappers); + } + // When colorValue is null, returned the color based on the tile weight. + else if (colorValue == null) { + return tile.level.color ?? _getRangeColor(tile._weight, colorMappers); + } + } + + return tile.level.color; + } + + Color? _getRangeColor(num value, List colorMappers) { + for (final TreemapColorMapper mapper in colorMappers) { + assert(mapper.from != null && + mapper.to != null && + mapper.from! <= mapper.to!); + if ((mapper.from != null && mapper.to != null) && + (mapper.from! <= value && mapper.to! >= value)) { + return mapper.color; + } + } + + return null; + } + + // Returns the position of the legend for align widget based on the + // [TreemapLegend.position]. + AlignmentGeometry _getLegendAlignment(TreemapLegendPosition position) { + switch (position) { + case TreemapLegendPosition.top: + return Alignment.topCenter; + case TreemapLegendPosition.bottom: + return Alignment.bottomCenter; + case TreemapLegendPosition.left: + return Alignment.centerLeft; + case TreemapLegendPosition.right: + return Alignment.centerRight; + } + } + + // By default, we have aligned the legends at topCenter. Based on the + // alignments we had applied the offset values. + // In [TreemapLegend.offset].dx, for (+)ve value, the legends gets moved from + // left to right and for (-)ve value, the legends gets moved from right to + // left. + // In [TreemapLegend.offset].dy, for (+)ve value, the legends gets moved from + // top to bottom and for (-)ve value, the legends gets moved from bottom to + // top. + EdgeInsetsGeometry _getLegendOffset() { + final Offset offset = widget.legend!.offset!; + final TreemapLegendPosition legendPosition = widget.legend!.position; + switch (legendPosition) { + case TreemapLegendPosition.top: + return EdgeInsets.only( + left: offset.dx > 0 ? offset.dx * 2 : 0, + right: offset.dx < 0 ? offset.dx.abs() * 2 : 0, + top: offset.dy > 0 ? offset.dy : 0); + case TreemapLegendPosition.left: + return EdgeInsets.only( + top: offset.dy > 0 ? offset.dy * 2 : 0, + bottom: offset.dy < 0 ? offset.dy.abs() * 2 : 0, + left: offset.dx > 0 ? offset.dx : 0); + case TreemapLegendPosition.right: + return EdgeInsets.only( + top: offset.dy > 0 ? offset.dy * 2 : 0, + bottom: offset.dy < 0 ? offset.dy.abs() * 2 : 0, + right: offset.dx < 0 ? offset.dx.abs() : 0); + case TreemapLegendPosition.bottom: + return EdgeInsets.only( + left: offset.dx > 0 ? offset.dx * 2 : 0, + right: offset.dx < 0 ? offset.dx.abs() * 2 : 0, + bottom: offset.dy < 0 ? offset.dy.abs() : 0); + } + } + + @override + void initState() { + _levelsLength = widget.levels.length; + _dataSource = {}; + _tooltipKey = GlobalKey(); + _obtainDataSource(); + super.initState(); + } + + @override + void didUpdateWidget(Treemap oldWidget) { + _canUpdateTileColor = + (oldWidget.colorMappers == null && widget.colorMappers != null) || + (oldWidget.colorMappers != null && widget.colorMappers == null) || + (oldWidget.colorMappers != null && + widget.colorMappers != null && + oldWidget.colorMappers!.length != widget.colorMappers!.length); + + if (_levelsLength != widget.levels.length) { + _levelsLength = widget.levels.length; + _invalidate(); + } else if (widget.dataCount != oldWidget.dataCount) { + _invalidate(); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _dataSource.clear(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: _computeDataSource, + builder: (BuildContext context, AsyncSnapshot snapshot) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final Size size = Size( + constraints.hasBoundedWidth ? constraints.maxWidth : 300, + constraints.hasBoundedHeight ? constraints.maxHeight : 300); + return SizedBox( + width: size.width, + height: size.height, + child: snapshot.hasData + ? Stack(children: [ + _buildTreemap(context, snapshot.hasData), + if (_isTooltipEnabled) _tooltip, + ]) + : null); + }); + }, + ); + } +} + +/// Display hierarchical data using nested rectangles. +class _SquarifiedTreemap extends StatefulWidget { + const _SquarifiedTreemap({ + Key? key, + required this.dataCount, + required this.dataSource, + required this.totalWeight, + required this.tooltipKey, + required this.onSelectionChanged, + required this.selectionSettings, + }) : super(key: key); + + final int dataCount; + final Map dataSource; + final double totalWeight; + final GlobalKey tooltipKey; + final ValueChanged? onSelectionChanged; + final TreemapSelectionSettings selectionSettings; + + @override + _SquarifiedTreemapState createState() => _SquarifiedTreemapState(); +} + +class _SquarifiedTreemapState extends State<_SquarifiedTreemap> { + Size? _size; + late List _children; + late _TreemapController _treemapController; + + // Calculate the size and position of the widget list based on the aggregated + // weight and the size passed. We will get the size as widgetSize and + // aggregateWeight as the total weight of the source at first time. + // We will determine the size and offset of each tile in the source based on + // groupArea. We had passed the actual size and weight of the tile to measure + // its location and size for its children. + List _getTiles( + Map source, double aggregatedWeight, Size size, + {Offset offset = Offset.zero, int start = 0, int? end}) { + final Size widgetSize = size; + double groupArea = 0; + double referenceArea; + double? prevAspectRatio; + double? groupInitialTileArea; + final List tiles = source.values.toList(); + // Sorting the tiles in descending order. + tiles.sort((src, target) => target.weight.compareTo(src.weight)); + end ??= tiles.length; + final List children = []; + for (int i = start; i < end; i++) { + final TreemapTile tile = tiles[i]; + // Area of rectangle = length * width. + // Divide the tile weight with aggregatedWeight to get the area factor. + // Multiply it with rectangular area to get the actual area of a tile. + tile._area = widgetSize.height * + widgetSize.width * + (tile.weight / aggregatedWeight); + groupInitialTileArea ??= tile._area; + // Group start tile height or width based on the shortest side. + final double area = (groupArea + tile._area!) / size.shortestSide; + referenceArea = groupInitialTileArea! / area; + final double currentAspectRatio = max( + _getAspectRatio(referenceArea, area), + _getAspectRatio(tile._area! / area, area)); + if (prevAspectRatio == null || currentAspectRatio < prevAspectRatio) { + prevAspectRatio = currentAspectRatio; + groupArea += tile._area!; + } else { + // Aligning the tiles vertically. + if (size.width > size.height) { + children.addAll( + _getTileWidgets(tiles, Size(groupArea / size.height, size.height), + Axis.vertical, offset, start, i), + ); + offset += Offset(groupArea / size.height, 0); + size = + Size(max(0, size.width) - groupArea / size.height, size.height); + } + // Aligning the tiles horizontally. + else { + children.addAll( + _getTileWidgets(tiles, Size(size.width, groupArea / size.width), + Axis.horizontal, offset, start, i), + ); + offset += Offset(0, groupArea / size.width); + size = Size(size.width, max(0, size.height) - groupArea / size.width); + } + start = i; + groupInitialTileArea = groupArea = tile._area!; + referenceArea = + tile._area! / (groupInitialTileArea / size.shortestSide); + prevAspectRatio = + _getAspectRatio(referenceArea, (tile._area! / size.shortestSide)); + } + } + + // Calculating the size and offset of the last tile or last group area in + // the given source. + if (size.width > size.height) { + children.addAll( + _getTileWidgets(tiles, Size(groupArea / size.height, size.height), + Axis.vertical, offset, start, end), + ); + } else { + children.addAll( + _getTileWidgets(tiles, Size(groupArea / size.height, size.height), + Axis.horizontal, offset, start, end), + ); + } + return children; + } + + List _getTileWidgets( + List source, Size size, Axis axis, Offset offset, int start, int end) { + final List<_Tile> tiles = []; + for (int i = start; i < end; i++) { + final TreemapTile tileDetails = source[i]; + if (axis == Axis.vertical) { + tileDetails + .._size = Size(size.width, tileDetails._area! / size.width) + .._offset = offset; + offset += Offset(0, tileDetails._size.height); + } else { + tileDetails + .._size = Size(tileDetails._area! / size.height, size.height) + .._offset = offset; + offset += Offset(tileDetails._size.width, 0); + } + + tiles.add(_Tile( + size: tileDetails._size, + details: tileDetails, + tooltipKey: widget.tooltipKey, + treemapController: _treemapController, + child: _getDescendants(tileDetails), + onSelectionChanged: widget.onSelectionChanged, + selectionSettings: widget.selectionSettings, + )); + } + return tiles; + } + + Widget? _getDescendants(TreemapTile tile) { + Widget? child; + if (tile.level.labelBuilder != null && tile._descendants != null) { + child = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + tile.level.labelBuilder!(context, tile)!, + Expanded( + // The [TreemapLevel.padding] and [TreemapLevel.border] values has + // been previous applied by using padding widget to this column. + // So we will get constraints size with considering that. Therefore + // we haven't considered [TreemapLevel.padding] and + // [TreemapLevel.border] values while passing size to children. + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + // To get the actual size of the parent tile, we had taken the + // size of the label builder and subtracted from it. + tile._labelBuilderSize = Size( + tile._size.width - constraints.maxWidth, + tile._size.height - constraints.maxHeight); + return Stack( + children: _getTiles( + tile._descendants!, + tile.weight, + Size(tile._size.width - tile._labelBuilderSize!.width, + tile._size.height - tile._labelBuilderSize!.height)), + ); + }), + ), + ], + ); + } + // In flat or hierarchical levels[levels.length - 1] level i.e., last level + // doesn't have descendants. So, we had included the label builder + // widget directly. + // No need to consider the [TreemapLevel.padding] and [TreemapLevel.border] + // values because labelBuilder will be the child of padding widget. + else if (tile.level.labelBuilder != null) { + child = tile.level.labelBuilder!(context, tile); + } else if (tile._descendants != null) { + child = LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Stack( + children: _getTiles( + tile._descendants!, tile.weight, constraints.biggest)); + }); + } + + return child; + } + + double _getAspectRatio(double width, double height) { + return width > height ? width / height : height / width; + } + + void _reupdateTiles() { + _children.clear(); + _children = _getTiles(widget.dataSource, widget.totalWeight, _size!); + } + + @override + void initState() { + _children = []; + _treemapController = _TreemapController(); + super.initState(); + } + + @override + void didUpdateWidget(_SquarifiedTreemap oldWidget) { + if (widget.dataCount != oldWidget.dataCount) { + _reupdateTiles(); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _children.clear(); + _treemapController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final Size newSize = constraints.biggest; + if (_size != newSize) { + _size = newSize; + _reupdateTiles(); + } + + return Stack(children: _children); + }, + ); + } +} + +class _SliceAndDiceTreemap extends StatefulWidget { + const _SliceAndDiceTreemap({ + Key? key, + required this.dataCount, + required this.type, + required this.dataSource, + required this.totalWeight, + required this.tooltipKey, + required this.onSelectionChanged, + required this.selectionSettings, + }) : super(key: key); + + final int dataCount; + final LayoutType type; + final Map dataSource; + final double totalWeight; + final GlobalKey tooltipKey; + final ValueChanged? onSelectionChanged; + final TreemapSelectionSettings selectionSettings; + + @override + _SliceAndDiceTreemapState createState() => _SliceAndDiceTreemapState(); +} + +class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> { + Size? _size; + late List _children; + late _TreemapController _treemapController; + + List _getTiles( + Map source, double aggregatedWeight, Size size) { + final List children = []; + final List tiles = source.values.toList(); + // Sorting the tiles in descending order. + tiles.sort((src, target) => target.weight.compareTo(src.weight)); + for (final TreemapTile tile in tiles) { + Size tileSize; + // Finding a tile's size, based on its weight. + if (widget.type == LayoutType.slice) { + tileSize = + Size(size.width, size.height * (tile.weight / aggregatedWeight)); + } else { + tileSize = + Size(size.width * (tile.weight / aggregatedWeight), size.height); + } + + children.add(_Tile( + type: widget.type, + size: tileSize, + details: tile, + tooltipKey: widget.tooltipKey, + treemapController: _treemapController, + child: _getDescendants(tile, tileSize), + onSelectionChanged: widget.onSelectionChanged, + selectionSettings: widget.selectionSettings, + )); + } + + return children; + } + + Widget? _getDescendants(TreemapTile tile, Size tileSize) { + Widget? current; + if (tile.level.labelBuilder != null && tile._descendants != null) { + current = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + tile.level.labelBuilder!(context, tile)!, + Expanded( + // The [TreemapLevel.padding] and [TreemapLevel.border] values has + // been previous applied by using padding widget to this column. + // So we will get constraints size with considering that. Therefore + // we haven't considered [TreemapLevel.padding] and + // [TreemapLevel.border] values while passing size to children. + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + // To get the actual size of the parent tile, we had taken the + // size of the label builder and subtracted from it. + tile._labelBuilderSize = Size( + tileSize.width - constraints.maxWidth, + tileSize.height - constraints.maxHeight); + final List children = _getTiles( + tile._descendants!, + tile.weight, + Size(tileSize.width - tile._labelBuilderSize!.width, + tileSize.height - tile._labelBuilderSize!.height)); + return widget.type == LayoutType.slice + ? Column(children: children) + : Row(children: children); + }), + ), + ], + ); + } + // In flat or hierarchical levels[levels.length - 1] level i.e., last level + // doesn't have descendants. So, we had included the label builder + // widget directly. + // No need to consider the [TreemapLevel.padding] and [TreemapLevel.border] + // values because labelBuilder will be the child of padding widget. + else if (tile.level.labelBuilder != null) { + current = tile.level.labelBuilder!(context, tile); + } else if (tile._descendants != null) { + current = LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final List children = + _getTiles(tile._descendants!, tile.weight, constraints.biggest); + return widget.type == LayoutType.slice + ? Column(children: children) + : Row(children: children); + }); + } + + return current; + } + + void _reupdateTiles() { + _children.clear(); + _children = _getTiles(widget.dataSource, widget.totalWeight, _size!); + } + + @override + void initState() { + _children = []; + _treemapController = _TreemapController(); + super.initState(); + } + + @override + void didUpdateWidget(_SliceAndDiceTreemap oldWidget) { + if (widget.dataCount != oldWidget.dataCount || + widget.type != oldWidget.type) { + _reupdateTiles(); + } + + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _children.clear(); + _treemapController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final Size newSize = constraints.biggest; + if (_size != newSize) { + _size = newSize; + _reupdateTiles(); + } + + return widget.type == LayoutType.slice + ? Column(children: _children) + : Row(children: _children); + }, + ); + } +} + +/// Creates tile of the treemap. +class _Tile extends StatelessWidget { + const _Tile({ + Key? key, + this.type = LayoutType.squarified, + required this.size, + required this.details, + required this.tooltipKey, + required this.treemapController, + required this.child, + required this.onSelectionChanged, + required this.selectionSettings, + }) : super(key: key); + + final LayoutType type; + final Size size; + final TreemapTile details; + final GlobalKey tooltipKey; + final _TreemapController treemapController; + final Widget? child; + final ValueChanged? onSelectionChanged; + final TreemapSelectionSettings selectionSettings; + + @override + Widget build(BuildContext context) { + Widget current = _TileDecor( + size: size, + details: details, + tooltipKey: tooltipKey, + treemapController: treemapController, + child: child, + onSelectionChanged: onSelectionChanged, + selectionSettings: selectionSettings, + ); + + if (details._padding != null) { + current = Padding(padding: details._padding!, child: current); + } + + return type == LayoutType.squarified + ? Positioned( + width: size.width, + height: size.height, + left: details._offset!.dx, + top: details._offset!.dy, + child: current) + : SizedBox(width: size.width, height: size.height, child: current); + } +} + +class _TileDecor extends StatefulWidget { + const _TileDecor({ + Key? key, + required this.size, + required this.details, + required this.tooltipKey, + required this.treemapController, + required this.child, + required this.onSelectionChanged, + required this.selectionSettings, + }) : super(key: key); + + final Size size; + final TreemapTile details; + final GlobalKey tooltipKey; + final _TreemapController treemapController; + final Widget? child; + final ValueChanged? onSelectionChanged; + final TreemapSelectionSettings selectionSettings; + + @override + _TileDecorState createState() => _TileDecorState(); +} + +class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { + late Animation _animation; + late AnimationController _controller; + late ColorTween _colorTween; + + bool _canAddInteractableWidget = false; + bool _isSelected = false; + bool _isHover = false; + late bool _mouseIsConnected; + + late Treemap _ancestor; + + RoundedRectangleBorder? get _border => + (_isSelected && widget.details == widget.treemapController.selectedTile) + ? (widget.details.level.border != null + ? widget.details.level.border!.copyWith( + side: widget.selectionSettings.border?.side, + borderRadius: widget.selectionSettings.border?.borderRadius + .resolve(Directionality.of(context))) + : widget.selectionSettings.border) + : widget.details.level.border; + + Widget _buildTileDecor(Widget child, BuildContext context) { + Widget? current = child; + if (_canAddInteractableWidget) { + if (_mouseIsConnected) { + current = MouseRegion( + child: current, + onEnter: (PointerEnterEvent event) { + if (widget.details.level.tooltipBuilder != null) { + _showTooltip(PointerKind.hover); + } + + if (widget.onSelectionChanged != null && !_isSelected) { + widget.treemapController.hoveredTile = widget.details; + } + }, + onHover: (PointerHoverEvent event) { + // Updating the [widget.treemapController.hoveredTile] when hover + // over the same tile after deselected the tile. + if (widget.onSelectionChanged != null && !_isSelected) { + widget.treemapController.hoveredTile ??= widget.details; + } + }, + onExit: (PointerExitEvent event) { + widget.treemapController.hoveredTile = null; + if (widget.details.level.tooltipBuilder != null) { + final RenderTooltip tooltipRenderBox = + widget.tooltipKey.currentContext?.findRenderObject() + // ignore: avoid_as + as RenderTooltip; + tooltipRenderBox.hide(); + } + }, + ); + } + + current = GestureDetector( + onTapUp: (TapUpDetails details) { + if (widget.details.level.tooltipBuilder != null) { + _showTooltip(PointerKind.touch); + } + + if (widget.onSelectionChanged != null) { + widget.treemapController.selectedTile = widget.details; + // We are restricting hover action when tile is selected using the + // [_isSelected] field. Updating [_isSelected] field only in + // the [_handleSelectionChange] method, it is invoked once set + // value to the selectedTile. So we need to update the + // [widget.treemapController.selectedTile] before setting null to + // the [widget.treemapController.hoveredTile] to avoid hover action + // while selecting a tile. + widget.treemapController.hoveredTile = null; + widget.onSelectionChanged!(widget.details); + } + }, + child: current, + ); + } + + return current; + } + + void _showTooltip(PointerKind kind) { + // ignore: avoid_as + final RenderBox tileRenderBox = context.findRenderObject() as RenderBox; + // Taking the top left global position of the current tile. + final Offset globalPosition = tileRenderBox.localToGlobal(Offset.zero); + // We had used the tile size directly if the tile didn't have any + // descendants. For parent tiles, the tooltip can only be shown on the + // label builder. So, we had used the label builder height instead of the + // tile height. + final Size referenceTileSize = widget.details._descendants == null + ? widget.size + : Size(widget.size.width, widget.details._labelBuilderSize!.height); + final RenderTooltip tooltipRenderBox = + // ignore: avoid_as + widget.tooltipKey.currentContext?.findRenderObject() as RenderTooltip; + tooltipRenderBox.show( + globalPosition, widget.details, referenceTileSize, kind); + } + + void _handleMouseTrackerChange() { + _mouseIsConnected = + RendererBinding.instance?.mouseTracker.mouseIsConnected ?? false; + } + + void _handleSelectionChange() { + _isHover = false; + final TreemapTile? selectedTile = widget.treemapController.selectedTile; + final TreemapTile? previousSelectedTile = + widget.treemapController.previousSelectedTile; + if (selectedTile != null && + (selectedTile == widget.details || _isDescendant(selectedTile))) { + _isSelected = true; + _updateSelectionTweenColor(); + // We are using same controller for selection and hover. If we select the + // hovered tile, [_controller] is already forwarded to 1 for hover action. + // We need to start the controller from 0, to visible the selection + // animation for desktop platforms. + _mouseIsConnected ? _controller.forward(from: 0) : _controller.forward(); + } else if (previousSelectedTile != null && + (previousSelectedTile == widget.details || + _isDescendant(previousSelectedTile))) { + _isSelected = false; + _updateSelectionTweenColor(); + _controller.reverse(); + } + } + + void _handleHover() { + if (_isSelected) { + return; + } + _isHover = true; + final TreemapTile? hoveredTile = widget.treemapController.hoveredTile; + final TreemapTile? previousHoveredTile = + widget.treemapController.previousHoveredTile; + if (hoveredTile != null && + (hoveredTile == widget.details || _isDescendant(hoveredTile))) { + _updateHoverTweenColor(); + _controller.forward(); + } else if (previousHoveredTile != null && + (previousHoveredTile == widget.details || + _isDescendant(previousHoveredTile))) { + _updateHoverTweenColor(); + _controller.reverse(); + } + } + + void _updateSelectionTweenColor() { + final Color? selectionColor = widget.selectionSettings.color; + _colorTween.begin = (_mouseIsConnected && _isSelected) + ? _getSaturatedColor(widget.details.color, _getColorFactor()) + : widget.details.color; + _colorTween.end = selectionColor == null + ? _getSaturatedColor(widget.details.color, _getColorFactor()) + : selectionColor == Colors.transparent + ? widget.details.color + : selectionColor; + } + + bool _isDescendant(TreemapTile source) { + if (source._descendants == null) { + return false; + } + + if (widget.details.indices.length < source.indices.length) { + return widget.details.indices + .any((int index) => source.indices.contains(index)); + } else if (widget.details.indices.length == source.indices.length) { + for (final int index in widget.details.indices) { + final bool hasIndex = source.indices.contains(index); + if (hasIndex && + // Restricted the parent tile selection when both the selected tile + // and parent tile indices count, indices values are exactly same + // by checking the level index. + _ancestor.levels.indexOf(widget.details.level) >= + _ancestor.levels.indexOf(source.level)) { + return true; + } + } + } + return false; + } + + void _rebuild() { + if (mounted) { + setState(() { + // Rebuilding to update the selection color and border to the visual. + }); + } + } + + double _getColorFactor() { + return _mouseIsConnected ? (_isHover ? 0.2 : 0.4) : 0.4; + } + + void _updateHoverTweenColor() { + _colorTween.begin = widget.details.color; + _colorTween.end = + _getSaturatedColor(widget.details.color, _getColorFactor()); + } + + @override + void initState() { + _mouseIsConnected = + RendererBinding.instance?.mouseTracker.mouseIsConnected ?? false; + RendererBinding.instance?.mouseTracker + .addListener(_handleMouseTrackerChange); + + _canAddInteractableWidget = widget.onSelectionChanged != null || + widget.details.level.tooltipBuilder != null; + + _controller = AnimationController( + duration: const Duration(milliseconds: 200), vsync: this); + _animation = CurvedAnimation(parent: _controller, curve: Curves.linear); + _animation.addListener(_rebuild); + + _colorTween = + ColorTween(begin: widget.details.color, end: widget.details.color); + + widget.treemapController + ..addSelectionListener(_handleSelectionChange) + ..addHoverListener(_handleHover); + super.initState(); + } + + @override + void didChangeDependencies() { + _ancestor = context.findAncestorWidgetOfExactType()!; + super.didChangeDependencies(); + } + + @override + void dispose() { + _controller + ..removeListener(_rebuild) + ..dispose(); + + widget.treemapController + ..removeSelectionListener(_handleSelectionChange) + ..removeHoverListener(_handleHover); + + RendererBinding.instance?.mouseTracker + .removeListener(_handleMouseTrackerChange); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + Widget? current = widget.details._descendants == null ? widget.child : null; + if (widget.details.level.itemBuilder != null) { + if (current != null) { + current = Stack(children: [ + widget.details.level.itemBuilder!.call(context, widget.details)!, + current, + ]); + } else { + current = + widget.details.level.itemBuilder!.call(context, widget.details); + } + } + + // If we dynamically add a widget, the actual layout will be affected so + // _TileDecor is disposed and initialized by _Tile. so _controller was + // disposed as soon as the tile is selected so selection will not work as + // expected. To avoid this issue, we always add ColoredBox and + // _BorderPainter if selection is enabled. + current = ColoredBox( + color: _colorTween.evaluate(_animation)!, + child: current, + ); + + final EdgeInsetsGeometry padding = _border?.dimensions ?? EdgeInsets.zero; + if (_border != null || widget.onSelectionChanged != null) { + current = CustomPaint( + size: widget.size, + foregroundPainter: _BorderPainter(_border), + child: ClipPath.shape( + shape: _border ?? const RoundedRectangleBorder(), + child: Padding(padding: padding, child: current)), + ); + } + + if (widget.details._descendants == null) { + return _buildTileDecor(current, context); + } else { + // Added descendant tiles to the siblings of the parent tile instead of + // adding it as child, in order to get the respective touch pointer when + // hovered or tapped on the parent tile or descendant tiles. + return Stack( + fit: StackFit.expand, + children: [ + _buildTileDecor(current, context), + if (widget.child != null) + Padding(padding: padding, child: widget.child) + ], + ); + } + } +} + +/// Drawn a rectangular border with rounded corners. +class _BorderPainter extends CustomPainter { + const _BorderPainter(this.border); + + /// A rectangular border with rounded corners. + final RoundedRectangleBorder? border; + + @override + void paint(Canvas canvas, Size size) { + if (border != null) { + border!.paint(canvas, Offset.zero & size); + } + } + + @override + bool shouldRepaint(_BorderPainter oldDelegate) { + return oldDelegate.border != border; + } +} + +class _TreemapController { + ObserverList? _selectionListeners = + ObserverList(); + ObserverList? _hoverListeners = ObserverList(); + + void addSelectionListener(VoidCallback listener) { + _selectionListeners?.add(listener); + } + + void addHoverListener(VoidCallback listener) { + _hoverListeners?.add(listener); + } + + void removeSelectionListener(VoidCallback listener) { + _selectionListeners?.remove(listener); + } + + void removeHoverListener(VoidCallback listener) { + _hoverListeners?.remove(listener); + } + + void notifySelectionListeners() { + for (final VoidCallback listener in _selectionListeners!) { + listener(); + } + } + + void notifyHoverListeners() { + for (final VoidCallback listener in _hoverListeners!) { + listener(); + } + } + + TreemapTile? get previousSelectedTile => _previousSelectedTile; + TreemapTile? _previousSelectedTile; + + TreemapTile? get selectedTile => _selectedTile; + TreemapTile? _selectedTile; + set selectedTile(TreemapTile? value) { + _previousSelectedTile = _selectedTile; + _selectedTile = _selectedTile == value ? null : value; + notifySelectionListeners(); + } + + TreemapTile? get previousHoveredTile => _previousHoveredTile; + TreemapTile? _previousHoveredTile; + + TreemapTile? get hoveredTile => _hoveredTile; + TreemapTile? _hoveredTile; + set hoveredTile(TreemapTile? value) { + if (_hoveredTile == value) { + return; + } + _previousHoveredTile = _hoveredTile; + _hoveredTile = value; + notifyHoverListeners(); + } + + void dispose() { + _selectionListeners = null; + _hoverListeners = null; + } +} diff --git a/packages/syncfusion_flutter_treemap/lib/src/legend.dart b/packages/syncfusion_flutter_treemap/lib/src/legend.dart new file mode 100644 index 000000000..8d73f02c2 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/lib/src/legend.dart @@ -0,0 +1,2867 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../treemap.dart'; + +enum _MapLegendType { vector, bar } + +/// Positions the legend in the different directions. +enum TreemapLegendPosition { + /// Places the legend at left to the tree map area. + left, + + /// Places the legend at right to the tree map area. + right, + + /// Places the legend at top of the tree map area. + top, + + /// Places the legend at bottom of the tree map area. + bottom, +} + +/// Shapes of the legend's and marker's icon. +enum TreemapIconType { + /// Legend's and marker's icon will be drawn in the circle shape. + circle, + + /// Legend's and marker's icon will be drawn in the rectangle shape. + rectangle, + + /// Legend's and marker's icon will be drawn in the triangle shape. + triangle, + + /// Legend's and marker's icon will be drawn in the diamond shape. + diamond, +} + +/// Behavior of the legend items when it overflows. +enum TreemapLegendOverflowMode { + /// It will place all the legend items in single line and enables scrolling. + scroll, + + /// It will wrap and place the remaining legend items to next line. + wrap, +} + +/// Behavior of the labels when it overflowed from the shape. +enum TreemapLabelOverflow { + /// It hides the overflowed labels. + hide, + + /// It does not make any change even if the labels overflowed. + visible, + + /// It trims the labels based on the available space in their respective + /// legend item. + ellipsis +} + +/// Option to place the labels either between the bars or on the bar in bar +/// legend. +enum TreemapLegendLabelsPlacement { + /// [TreemapLegendLabelsPlacement.Item] places labels in the center + /// of the bar. + onItem, + + /// [TreemapLegendLabelsPlacement.betweenItems] places labels + /// in-between two bars. + betweenItems +} + +/// Placement of edge labels in the bar legend. +enum TreemapLegendEdgeLabelsPlacement { + /// Places the edge labels in inside of the legend items. + inside, + + /// Place the edge labels in the center of the starting position of the + /// legend bars. + center +} + +/// Applies gradient or solid color for the bar segments. +enum TreemapLegendPaintingStyle { + /// Applies solid color for bar segments. + solid, + + /// Applies gradient color for bar segments. + gradient +} + +/// Shows legend for the data rendered in the treemap. +/// +/// By default, legend will not be shown. +/// +/// If [SfTreemap.colorMappers] is null, then the legend items' icon color and +/// legend item's text will be applied based on the value of +/// [TreemapLevel.color] and the values returned from the +/// [TreemapLevel.groupMapper] callback of first [TreemapLevel] added in the +/// [SfTreemap.levels] collection. +/// +/// If [SfTreemap.colorMappers] is not null and [TreemapColorMapper.value] +/// constructor is used, the legend item's icon color will be applied based on +/// the [TreemapColorMapper.color] property and the legend text applied based +/// on the [TreemapColorMapper.value] property. +/// +/// And, when using [TreemapColorMapper.range] constructor, the legend item's +/// icon color will be applied based on the [TreemapColorMapper.color] +/// property and the legend text will be applied based on the +/// [TreemapColorMapper.name] property. If the +/// [TreemapColorMapper.name] property is null, then the text will +/// be applied based on the [TreemapColorMapper.from] and +/// [TreemapColorMapper.to] properties +/// +/// The below code snippet represents how to setting default legend +/// to the tree map. +/// +/// ```dart +/// List _source; +/// +/// @override +/// void initState() { +/// _source = [ +/// SocialMediaUsers('India', 'Facebook', 280), +/// SocialMediaUsers('India', 'Instagram', 88), +/// SocialMediaUsers('USA', 'Facebook', 190), +/// SocialMediaUsers('USA', 'Instagram', 120), +/// SocialMediaUsers('Japan', 'Twitter', 48), +/// SocialMediaUsers('Japan', 'Instagram', 31), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfTreemap( +/// dataCount: _source.length, +/// weightValueMapper: (int index) { +/// return _source[index].usersInMillions; +/// }, +/// levels: [ +/// TreemapLevel( +/// groupMapper: (int index) { +/// return _source[index].country; +/// }, +/// ), +/// ], +/// legend: TreemapLegend(), +/// ), +/// ); +/// } +/// +/// class SocialMediaUsers { +/// const SocialMediaUsers( +/// this.country, +/// this.socialMedia, +/// this.usersInMillions, +/// ); +/// final String country; +/// final String socialMedia; +/// final double usersInMillions; +/// } +/// ``` +/// +/// The below code snippet represents how to setting bar legend +/// to the tree map. +/// +/// ```dart +/// List _source; +/// +/// @override +/// void initState() { +/// _source = [ +/// SocialMediaUsers('India', 'Facebook', 280), +/// SocialMediaUsers('India', 'Instagram', 88), +/// SocialMediaUsers('USA', 'Facebook', 190), +/// SocialMediaUsers('USA', 'Instagram', 120), +/// SocialMediaUsers('Japan', 'Twitter', 48), +/// SocialMediaUsers('Japan', 'Instagram', 31), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfTreemap( +/// dataCount: _source.length, +/// weightValueMapper: (int index) { +/// return _source[index].usersInMillions; +/// }, +/// levels: [ +/// TreemapLevel( +/// groupMapper: (int index) { +/// return _source[index].country; +/// }, +/// ), +/// ], +/// legend: TreemapLegend.bar(), +/// ), +/// ); +/// } +/// +/// class SocialMediaUsers { +/// const SocialMediaUsers( +/// this.country, +/// this.socialMedia, +/// this.usersInMillions, +/// ); +/// final String country; +/// final String socialMedia; +/// final double usersInMillions; +/// } +/// ``` +/// +/// See also: +/// * To render bar legend, refer [TreemapLegend.bar] constructor. +@immutable +class TreemapLegend extends DiagnosticableTree { + /// Provides additional information about the data rendered in the tree map by + /// initializing the [SfTreemap.legend] property. + /// + /// Defaults to `null`. + /// + /// By default, legend will not be shown. + /// + /// If [SfTreemap.colorMappers] is null, then the legend items's icon color + /// and legend item's text applied based on the value of [TreemapLevel.color] + /// and the [TreemapLevel.groupMapper] properties of first [TreemapLevel] + /// added in the [SfTreemap.levels] collection respectively. + /// + /// If [SfTreemap.colorMappers] is not null and using + /// TreemapColorMapper.value() constructor, the legend item's icon color + /// applied based on the [TreemapColorMapper.color] property and the legend + /// text applied based on the [TreemapColorMapper.value] property. + /// + /// When using [TreemapColorMapper.range] constructor, the legend item's icon + /// color applied based on the [TreemapColorMapper.color] property and the + /// legend text applied based on the [TreemapColorMapper.name] + /// property. If the [TreemapColorMapper.name] property is null, + /// then the text applied based on the [TreemapColorMapper.from] and + /// [TreemapColorMapper.to] properties. + /// + /// The below code snippet represents how to create default legend to + /// the tree map. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend(), + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [TreemapLegend.bar] named constructor, for bar legend type. + const TreemapLegend({ + this.title, + this.position = TreemapLegendPosition.top, + this.offset, + this.overflowMode = TreemapLegendOverflowMode.wrap, + this.direction, + this.padding = const EdgeInsets.all(10.0), + this.spacing = 10.0, + this.textStyle, + TreemapIconType iconType = TreemapIconType.circle, + Size iconSize = const Size(8.0, 8.0), + }) : _legendType = _MapLegendType.vector, + _iconType = iconType, + _iconSize = iconSize, + _segmentSize = null, + _labelsPlacement = null, + _edgeLabelsPlacement = null, + _labelOverflow = null, + _segmentPaintingStyle = null, + assert(spacing >= 0); + + /// Creates a bar type legend for the tree map. + /// + /// Information provided in the legend helps to identify the data rendered in + /// the tree map. + /// + /// Defaults to `null`. + /// + /// By default, legend will not be shown. + /// + /// * labelsPlacement - Places the labels either between the bar items or on + /// the items. By default, labels placement will be + /// [TreemapLegendLabelsPlacement.betweenItems] when setting range color + /// mapping [TreemapColorMapper] without setting + /// [TreemapColorMapper.name] property. In all other cases, it will + /// be [TreemapLegendLabelsPlacement.onItem]. + /// + /// * edgeLabelsPlacement - Places the edge labels either inside or at + /// center of the edges. It doesn't work with + /// [TreemapLegendLabelsPlacement.betweenItems]. Defaults to + /// [TreemapLegendEdgeLabelsPlacement.inside]. + /// + /// * segmentPaintingStyle - Option for setting solid or gradient color for + /// the bar. To enable the gradient, set this as + /// [TreemapLegendPaintingStyle.gradient]. Defaults to + /// [TreemapLegendPaintingStyle.solid]. + /// + /// * labelOverflow - Option to trim or remove the legend item's text when + /// it is overflowed. Defaults to [TreemapLabelOverflow.hide]. + /// + /// List _source; + /// + /// ```dart + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend(), + /// colorMappers: [ + /// TreemapColorMapper.range( + /// 0, + /// 10, + /// Colors.red, + /// name: '10', + /// ), + /// TreemapColorMapper.range( + /// 11, + /// 20, + /// Colors.green, + /// name: '20', + /// ), + /// TreemapColorMapper.range( + /// 21, + /// 30, + /// Colors.blue, + /// name: '30', + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [TreemapLegend], for adding default legend type with different icon + /// styles like circle, diamond, rectangle and triangle. + const TreemapLegend.bar({ + this.title, + this.overflowMode = TreemapLegendOverflowMode.scroll, + this.padding = const EdgeInsets.all(10.0), + this.position = TreemapLegendPosition.top, + this.offset, + this.spacing = 2.0, + this.textStyle, + this.direction, + Size? segmentSize, + TreemapLegendLabelsPlacement? labelsPlacement, + TreemapLegendEdgeLabelsPlacement edgeLabelsPlacement = + TreemapLegendEdgeLabelsPlacement.inside, + TreemapLabelOverflow labelOverflow = TreemapLabelOverflow.visible, + TreemapLegendPaintingStyle segmentPaintingStyle = + TreemapLegendPaintingStyle.solid, + }) : _legendType = _MapLegendType.bar, + _labelsPlacement = labelsPlacement, + _edgeLabelsPlacement = edgeLabelsPlacement, + _labelOverflow = labelOverflow, + _segmentPaintingStyle = segmentPaintingStyle, + _segmentSize = segmentSize, + _iconType = null, + _iconSize = null, + assert(spacing >= 0); + + /// Enables a title for the legends to provide a small note about the legends. + /// + /// Defaults to `null`. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend( + /// title: const Text('Social media users'), + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + final Widget? title; + + /// Sets the padding around the legend. + /// + /// Defaults to EdgeInsets.all(10.0). + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend( + /// padding: const EdgeInsets.all(20), + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + final EdgeInsetsGeometry? padding; + + /// Arranges the legend items in either horizontal or vertical direction. + /// + /// Defaults to horizontal, if the [position] is top, bottom. + /// Defaults to vertical, if the [position] is left or right. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend( + /// direction: Axis.vertical, + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + /// + /// See also: + /// * [position], to change the position of the legend. + final Axis? direction; + + /// Positions the legend in the different directions. + /// + /// Defaults to [TreemapLegendPosition.top]. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend( + /// position: TreemapLegendPosition.bottom, + /// ), + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + /// See also: + /// * [offset], to place the legend in custom position. + final TreemapLegendPosition position; + + /// Places the legend in custom position. + /// + /// If the [offset] has been set and if the [position] is top, then the legend + /// will be placed in top but in the position additional to the + /// actual top position. Also, the legend will not take dedicated position for + /// it and will be drawn on the top of map. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend( + /// offset: Offset(150, 150), + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + /// + /// See also: + /// * [position], to set the position of the legend. + final Offset? offset; + + /// Specifies the space between the each legend items. + /// + /// Defaults to 10.0 for default legend and 2.0 for bar legend. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend( + /// spacing: 20, + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + final double spacing; + + /// Customizes the legend item's text style. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend( + /// textStyle: TextStyle( + /// color: Colors.red, + /// fontSize: 16, + /// fontStyle: FontStyle.italic, + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + final TextStyle? textStyle; + + /// Wraps or scrolls the legend items when it overflows. + /// + /// In wrap mode, overflowed items will be wrapped in a new row and will + /// be positioned from the start. + /// + /// If the legend position is left or right, scroll direction is vertical. + /// If the legend position is top or bottom, scroll direction is horizontal. + /// + /// Defaults to [TreemapLegendOverflowMode.wrap] for default legend and + /// [TreemapLegendOverflowMode.scroll] for bar legend. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend( + /// overflowMode: TreemapLegendOverflowMode.scroll, + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + /// + /// See also: + /// * [position], to set the position of the legend. + /// * [direction], to set the direction of the legend. + final TreemapLegendOverflowMode overflowMode; + + /// Specifies the shape of the legend icon. + /// + /// Defaults to [TreemapIconType.circle]. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// Population('Asia', 'Thailand', 7.54), + /// Population('Africa', 'South Africa', 25.4), + /// Population('North America', 'Canada', 13.3), + /// Population('South America', 'Chile', 19.11), + /// Population('Australia', 'New Zealand', 4.93), + /// Population('Europe', 'Czech Republic', 10.65), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Treemap legend')), + /// body: SfTreemap.dice( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].populationInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + // padding: EdgeInsets.only(left: 2.5, right: 2.5), + /// groupMapper: (int index) { + /// return _source[index].continent; + /// }, + /// color: Colors.teal, + /// ), + /// ], + /// legend: TreemapLegend( + /// iconType: TreemapIconType.diamond, + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + /// + /// See also: + /// * [iconSize], to set the size of the icon. + final TreemapIconType? _iconType; + + /// Customizes the size of the bar segments. + /// + /// Defaults to Size(80.0, 12.0). + /// + /// ```dart + /// List _source; + /// /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// colorMappers: [ + /// TreemapColorMapper.range( + /// 0, + /// 10, + /// Colors.red, + /// name: '10', + /// ), + /// TreemapColorMapper.range( + /// 11, + /// 20, + /// Colors.green, + /// name: '20', + /// ), + /// TreemapColorMapper.range( + /// 21, + /// 30, + /// Colors.blue, + /// name: '30', + /// ), + /// ], + /// legend: TreemapLegend.bar( + /// segmentSize: Size(60, 18), + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + final Size? _segmentSize; + + /// Customizes the size of the icon. + /// + /// Defaults to Size(12.0, 12.0). + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend( + /// iconSize: Size(12, 12), + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + /// + /// See also: + /// * [iconType], to set shape of the default legend icon. + final Size? _iconSize; + + /// Place the labels either between the segments or on the segments. + /// + /// By default, label placement will be + /// [TreemapLegendLabelsPlacement.betweenItems] when setting range color + /// mapper without setting color mapper text property otherwise + /// label placement will be [TreemapLegendLabelsPlacement.onItem]. + /// + /// This snippet shows how to set label placement in [SfTreemap]. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// colorMappers: [ + /// TreemapColorMapper.range( + /// 0, + /// 10, + /// Colors.red, + /// name: '10', + /// ), + /// TreemapColorMapper.range( + /// 11, + /// 20, + /// Colors.green, + /// name: '20', + /// ), + /// TreemapColorMapper.range( + /// 21, + /// 30, + /// Colors.blue, + /// name: '30', + /// ), + /// ], + /// legend: TreemapLegend.bar( + /// labelsPlacement: TreemapLegendLabelsPlacement.onItem, + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + /// + /// See also: + /// * [edgeLabelsPlacement], to place the edge labels either + /// inside or outside of the bar legend. + /// + /// * [labelOverflow], to trims or removes the legend text + /// when it is overflowed from the bar legend. + final TreemapLegendLabelsPlacement? _labelsPlacement; + + /// Place the edge labels either inside or outside of the bar legend. + /// + /// [edgeLabelsPlacement] doesn't works with + /// [TreemapLegendLabelsPlacement.betweenItems]. + /// + /// Defaults to [TreemapLegendEdgeLabelsPlacement.inside]. + /// + /// This snippet shows how to set edge label placement in [SfTreemap]. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// colorMappers: [ + /// TreemapColorMapper.range( + /// 0, + /// 10, + /// Colors.red, + /// name: '10', + /// ), + /// TreemapColorMapper.range( + /// 11, + /// 20, + /// Colors.green, + /// name: '20', + /// ), + /// TreemapColorMapper.range( + /// 21, + /// 30, + /// Colors.blue, + /// name: '30', + /// ), + /// ], + /// legend: TreemapLegend.bar( + /// edgeLabelsPlacement: TreemapLegendEdgeLabelsPlacement.center, + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + /// + /// See also: + /// * [labelsPlacement], place the labels either between the segments or + /// on the segments. + /// * [labelOverflow], to trims or removes the legend text + /// when it is overflowed from the bar legend. + final TreemapLegendEdgeLabelsPlacement? _edgeLabelsPlacement; + + /// Trims or removes the legend text when it is overflowed from the + /// bar legend. + /// + /// Defaults to [TreemapLabelOverflow.hide]. + /// + /// By default, the legend labels will render even if it overflows form the + /// bar legend. Using this property, it is possible to remove or trim the + /// legend labels based on the bar legend size. + /// + /// This snippet shows how to set the [overflowMode] for the bar legend text + /// in [SfTreemap]. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// colorMappers: [ + /// TreemapColorMapper.range( + /// 0, + /// 10, + /// Colors.red, + /// name: '10', + /// ), + /// TreemapColorMapper.range( + /// 11, + /// 20, + /// Colors.green, + /// name: '20', + /// ), + /// TreemapColorMapper.range( + /// 21, + /// 30, + /// Colors.blue, + /// name: '30', + /// ), + /// ], + /// legend: TreemapLegend.bar( + /// labelOverflow: TreemapLabelOverflow.ellipsis, + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + /// + /// See also: + /// * [labelsPlacement], place the labels either between the segments or + /// on the segments. + /// * [edgeLabelsPlacement], to place the edge labels either inside or + /// outside of the bar legend. + final TreemapLabelOverflow? _labelOverflow; + + /// Specifies the type of the legend. + final _MapLegendType _legendType; + + /// Applies gradient or solid color for the bar segments. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// colorMappers: [ + /// TreemapColorMapper.range( + /// 0, + /// 10, + /// Colors.red, + /// name: '10', + /// ), + /// TreemapColorMapper.range( + /// 11, + /// 20, + /// Colors.green, + /// name: '20', + /// ), + /// TreemapColorMapper.range( + /// 21, + /// 30, + /// Colors.blue, + /// name: '30', + /// ), + /// ], + /// legend: TreemapLegend.bar( + /// segmentPaintingStyle: TreemapLegendPaintingStyle.gradient, + /// ), + /// ), + /// ); + /// } + /// + /// class Population { + /// const Population(this.continent, this.country, + /// this.populationInMillions); + /// + /// final String country; + /// final String continent; + /// final double populationInMillions; + /// } + /// ``` + /// + /// See also: + /// * [labelsPlacement], place the labels either between the segments or + /// on the segments. + /// * [labelOverflow], to trims or removes the legend text + /// when it is overflowed from the bar legend. + /// * [edgeLabelsPlacement], to place the edge labels either inside or + /// outside of the bar legend. + final TreemapLegendPaintingStyle? _segmentPaintingStyle; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + if (other is TreemapLegend && other._legendType != _legendType) { + return false; + } + + return other is TreemapLegend && + other.padding == padding && + other.offset == offset && + other.spacing == spacing && + other.direction == direction && + other.overflowMode == overflowMode && + other.position == position && + other.textStyle == textStyle && + other.title == title; + } + + @override + int get hashCode => hashValues(padding, offset, spacing, direction, + overflowMode, position, textStyle, title); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + + properties.add(EnumProperty<_MapLegendType>('legendType', _legendType)); + properties.add(DiagnosticsProperty('padding', padding)); + properties.add(DiagnosticsProperty('offset', offset)); + properties.add(DoubleProperty('spacing', spacing)); + if (direction != null) { + properties.add(EnumProperty('direction', direction)); + } + + properties.add( + EnumProperty('overflowMode', overflowMode)); + properties.add(EnumProperty('position', position)); + if (textStyle != null) { + properties.add(textStyle!.toDiagnosticsNode(name: 'textStyle')); + } + + if (_legendType == _MapLegendType.vector) { + properties.add(DiagnosticsProperty('iconSize', _iconSize)); + properties.add(EnumProperty('iconType', _iconType)); + } else { + properties.add(DiagnosticsProperty('segmentSize', _segmentSize)); + properties.add(EnumProperty( + 'labelsPlacement', _labelsPlacement)); + properties.add(EnumProperty( + 'edgeLabelsPlacement', _edgeLabelsPlacement)); + properties.add(EnumProperty( + 'labelOverflowMode', _labelOverflow)); + properties.add(EnumProperty( + 'segmentPaintingStyle', _segmentPaintingStyle)); + } + } +} + +/// For rendering the tree map legend based on legend type. +class LegendWidget extends StatefulWidget { + /// Creates a [LegendWidget]. + LegendWidget({required this.dataSource, required this.settings}); + + /// map with the respective data in the data source. + final dynamic dataSource; + + /// Customizes the appearance of the the legend. + final TreemapLegend settings; + + @override + _LegendWidgetState createState() => _LegendWidgetState(); +} + +class _LegendWidgetState extends State { + late TextStyle _textStyle; + + @override + // ignore: missing_return + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + _textStyle = themeData.textTheme.caption! + .copyWith(color: themeData.textTheme.caption!.color!.withOpacity(0.87)) + .merge(widget.settings.textStyle); + if (widget.settings.title == null) { + switch (widget.settings.overflowMode) { + case TreemapLegendOverflowMode.scroll: + return _getScrollableWidget(); + case TreemapLegendOverflowMode.wrap: + return actualChild; + } + } else { + switch (widget.settings.overflowMode) { + case TreemapLegendOverflowMode.scroll: + if (widget.settings.position == TreemapLegendPosition.top || + widget.settings.position == TreemapLegendPosition.bottom) { + return Column( + mainAxisAlignment: + widget.settings.position == TreemapLegendPosition.bottom + ? MainAxisAlignment.end + : MainAxisAlignment.start, + children: [widget.settings.title!, _getScrollableWidget()], + ); + } else { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + widget.settings.title!, + Flexible(fit: FlexFit.loose, child: _getScrollableWidget()), + ], + ); + } + case TreemapLegendOverflowMode.wrap: + if (widget.settings.position == TreemapLegendPosition.top || + widget.settings.position == TreemapLegendPosition.bottom) { + return Column( + mainAxisAlignment: + widget.settings.position == TreemapLegendPosition.bottom + ? MainAxisAlignment.end + : MainAxisAlignment.start, + children: [widget.settings.title!, actualChild], + ); + } else { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + widget.settings.title!, + Flexible(fit: FlexFit.loose, child: actualChild) + ], + ); + } + } + } + } + + Widget _getScrollableWidget() { + return SingleChildScrollView( + scrollDirection: + widget.settings.position == TreemapLegendPosition.top || + widget.settings.position == TreemapLegendPosition.bottom + ? Axis.horizontal + : Axis.vertical, + child: actualChild); + } + + Widget get actualChild { + if (widget.settings._legendType == _MapLegendType.vector) { + final Widget child = Wrap( + direction: widget.settings.direction ?? + (widget.settings.position == TreemapLegendPosition.top || + widget.settings.position == TreemapLegendPosition.bottom + ? Axis.horizontal + : Axis.vertical), + spacing: widget.settings.spacing, + children: _getLegendItems(), + runSpacing: 6, + runAlignment: WrapAlignment.center, + alignment: WrapAlignment.start, + ); + + if (widget.settings.padding != null) { + return Padding(padding: widget.settings.padding!, child: child); + } + + return child; + } else { + if (widget.settings._segmentPaintingStyle == + TreemapLegendPaintingStyle.solid) { + return _SolidBarLegend( + dataSource: widget.dataSource, + settings: widget.settings, + textStyle: _textStyle, + ); + } else { + return _GradientBarLegend( + dataSource: widget.dataSource, + settings: widget.settings, + textStyle: _textStyle, + ); + } + } + } + + /// Returns the list of legend items based on the data source. + List _getLegendItems() { + final List legendItems = []; + if (widget.dataSource != null && widget.dataSource.isNotEmpty) { + // The legend items calculated based on color mappers. + if (widget.dataSource is List) { + final int length = widget.dataSource.length; + for (int i = 0; i < length; i++) { + final TreemapColorMapper colorMapper = widget.dataSource[i]; + final String text = colorMapper.from != null + ? colorMapper.name ?? '${colorMapper.from} - ${colorMapper.to}' + : colorMapper.value!; + legendItems.add(_getLegendItem( + text, + colorMapper.color, + )); + } + } else { + // The legend items calculated based on the first level + // [TreemapLevel.groupMapper] and [TreemapLevel.color] values. + widget.dataSource.forEach((String key, TreemapTile treeModel) { + legendItems.add(_getLegendItem( + treeModel.group, + treeModel.color, + )); + }); + } + } + + return legendItems; + } + + /// Returns the legend icon and label. + Widget _getLegendItem(String text, Color? color) { + return _LegendItem( + text: text, + iconShapeColor: color, + settings: widget.settings, + textStyle: _textStyle); + } +} + +class _LegendItem extends LeafRenderObjectWidget { + const _LegendItem({ + required this.text, + required this.iconShapeColor, + required this.settings, + required this.textStyle, + }); + + final String text; + final Color? iconShapeColor; + final TreemapLegend settings; + final TextStyle textStyle; + + @override + RenderObject createRenderObject(BuildContext context) { + return _RenderLegendItem( + text: text, + iconShapeColor: iconShapeColor, + settings: settings, + textStyle: textStyle, + mediaQueryData: MediaQuery.of(context), + ); + } + + @override + void updateRenderObject( + BuildContext context, _RenderLegendItem renderObject) { + renderObject + ..text = text + ..iconShapeColor = iconShapeColor + ..settings = settings + ..textStyle = textStyle + ..mediaQueryData = MediaQuery.of(context); + } +} + +class _RenderLegendItem extends RenderBox { + _RenderLegendItem({ + required String text, + required Color? iconShapeColor, + required TreemapLegend settings, + required TextStyle textStyle, + required MediaQueryData mediaQueryData, + }) : _text = text, + _iconShapeColor = iconShapeColor, + _settings = settings, + _textStyle = textStyle, + _mediaQueryData = mediaQueryData { + _textPainter = TextPainter(textDirection: TextDirection.ltr); + _updateTextPainter(); + } + + final int _spacing = 3; + final _TreemapIconShape _iconShape = const _TreemapIconShape(); + late TextPainter _textPainter; + + String get text => _text; + String _text; + set text(String value) { + if (_text == value) { + return; + } + _text = value; + _updateTextPainter(); + markNeedsLayout(); + } + + Color? get iconShapeColor => _iconShapeColor; + Color? _iconShapeColor; + set iconShapeColor(Color? value) { + if (_iconShapeColor == value) { + return; + } + _iconShapeColor = value; + markNeedsPaint(); + } + + TreemapLegend get settings => _settings; + TreemapLegend _settings; + set settings(TreemapLegend value) { + if (_settings == value) { + return; + } + _settings = value; + _updateTextPainter(); + markNeedsLayout(); + } + + MediaQueryData get mediaQueryData => _mediaQueryData; + MediaQueryData _mediaQueryData; + set mediaQueryData(MediaQueryData value) { + if (_mediaQueryData == value) { + return; + } + _mediaQueryData = value; + _updateTextPainter(); + markNeedsLayout(); + } + + TextStyle get textStyle => _textStyle; + TextStyle _textStyle; + set textStyle(TextStyle value) { + if (_textStyle == value) { + return; + } + _textStyle = value; + markNeedsLayout(); + } + + void _updateTextPainter() { + _textPainter.textScaleFactor = _mediaQueryData.textScaleFactor; + _textPainter.text = TextSpan(text: _text, style: textStyle); + _textPainter.layout(); + } + + @override + void performLayout() { + final double width = + _settings._iconSize!.width + _spacing + _textPainter.width; + final double height = + max(_settings._iconSize!.height, _textPainter.height) + _spacing; + size = Size(width, height); + } + + @override + void paint(PaintingContext context, Offset offset) { + Color? iconColor; + Offset actualOffset; + iconColor = _iconShapeColor; + final Size halfIconSize = + _iconShape.getPreferredSize(_settings._iconSize!) / 2; + actualOffset = + offset + Offset(0, (size.height - (halfIconSize.height * 2)) / 2); + _iconShape.paint(context, actualOffset, + parentBox: this, + iconSize: _settings._iconSize!, + color: iconColor ?? Colors.transparent, + iconType: _settings._iconType!); + + _textPainter.text = TextSpan( + style: textStyle.copyWith(color: textStyle.color), text: _text); + _textPainter.layout(); + actualOffset = offset + + Offset(_settings._iconSize!.width + _spacing, + (size.height - _textPainter.height) / 2); + _textPainter.paint(context.canvas, actualOffset); + } +} + +class _SolidBarLegend extends StatefulWidget { + const _SolidBarLegend( + {required this.dataSource, + required this.settings, + required this.textStyle}); + + final dynamic dataSource; + final TreemapLegend settings; + final TextStyle textStyle; + + @override + _SolidBarLegendState createState() => _SolidBarLegendState(); +} + +class _SolidBarLegendState extends State<_SolidBarLegend> { + late Axis _direction; + late TextDirection _textDirection; + TreemapLegendLabelsPlacement? _labelsPlacement; + late TextPainter _textPainter; + bool _isOverlapSegmentText = false; + late Size _segmentSize; + + @override + void initState() { + _textPainter = TextPainter(textDirection: TextDirection.ltr); + super.initState(); + } + + @override + Widget build(BuildContext context) { + _segmentSize = widget.settings._segmentSize ?? const Size(80.0, 12.0); + _labelsPlacement = widget.settings._labelsPlacement; + final TextDirection textDirection = Directionality.of(context); + _direction = widget.settings.direction ?? + (widget.settings.position == TreemapLegendPosition.top || + widget.settings.position == TreemapLegendPosition.bottom + ? Axis.horizontal + : Axis.vertical); + _textDirection = textDirection == TextDirection.ltr + ? textDirection + : (_direction == Axis.vertical ? TextDirection.ltr : textDirection); + _textPainter.textScaleFactor = MediaQuery.of(context).textScaleFactor; + + final Widget child = Directionality( + textDirection: _textDirection, + child: Wrap( + direction: _direction, + spacing: widget.settings.spacing, + children: _getBarSegments(), + runSpacing: 6, + runAlignment: WrapAlignment.center, + alignment: WrapAlignment.start, + ), + ); + + if (widget.settings.padding != null) { + return Padding(padding: widget.settings.padding!, child: child); + } + + return child; + } + + List _getBarSegments() { + if (widget.dataSource != null && widget.dataSource.isNotEmpty) { + if (widget.dataSource is List) { + return _getSegmentsForColorMapper(); + } else { + _labelsPlacement = widget.settings._labelsPlacement ?? + TreemapLegendLabelsPlacement.onItem; + return _getSegmentsForShapeSource(); + } + } + + return []; + } + + List _getSegmentsForColorMapper() { + final List legendItems = []; + final int length = widget.dataSource.length; + String? currentText; + for (int i = 0; i < length; i++) { + _isOverlapSegmentText = false; + final TreemapColorMapper colorMapper = widget.dataSource[i]; + _labelsPlacement = _labelsPlacement ?? + (colorMapper.from != null + ? TreemapLegendLabelsPlacement.betweenItems + : TreemapLegendLabelsPlacement.onItem); + if (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + if (i == length - 1) { + currentText = _getTrimmedText( + _getText(widget.dataSource[i]), currentText, i, length); + } else { + if (i == 0) { + final List firstSegmentLabels = + _getStartSegmentLabel(colorMapper); + currentText = (firstSegmentLabels.length > 1 + ? firstSegmentLabels[1] + : firstSegmentLabels[0]); + } else { + currentText = _getText(colorMapper); + } + + currentText = _getTrimmedText( + currentText, _getText(widget.dataSource[i + 1]), i, length); + } + } else { + currentText = _getText(colorMapper); + if (_direction == Axis.horizontal && + _labelsPlacement == TreemapLegendLabelsPlacement.onItem) { + _isOverlapSegmentText = + _getTextWidth(currentText) > _segmentSize.width; + } + } + + legendItems.add( + _getSegment(currentText, colorMapper.color, i, length, colorMapper)); + } + + return legendItems; + } + + List _getSegmentsForShapeSource() { + final List barSegments = []; + final int length = widget.dataSource.length; + // If we use as iterator, it will check first and second model and then + // check third and fourth model. But we can't check second and third item + // is overlapping or not. Since the iterator in second model . So we uses + // two iterator. If we use move next first iterator gives current model and + // second iterator gives next model. + final Iterator currentIterator = + widget.dataSource.values.iterator; + final Iterator nextIterator = + widget.dataSource.values.iterator; + String? text; + nextIterator.moveNext(); + while (currentIterator.moveNext()) { + final TreemapTile treemapModel = currentIterator.current; + if (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + if (nextIterator.moveNext()) { + text = _getTrimmedText( + treemapModel.group, nextIterator.current.group, 0, length); + } else { + text = + _getTrimmedText(currentIterator.current.group, text, 0, length); + } + } else if (_direction == Axis.horizontal && + _labelsPlacement == TreemapLegendLabelsPlacement.onItem) { + text = treemapModel.group; + _isOverlapSegmentText = _getTextWidth(text) > _segmentSize.width; + } + + barSegments.add(_getSegment(text!, treemapModel.color, 0, length)); + } + + return barSegments; + } + + String _getTrimmedText( + String currentText, String? nextText, int index, int length) { + if (widget.settings._labelOverflow == TreemapLabelOverflow.visible || + currentText.isEmpty || + (nextText != null && nextText.isEmpty) || + nextText == null) { + return currentText; + } + + final Size barSize = _segmentSize; + double refCurrentTextWidth; + double refNextTextWidth; + if (_direction == Axis.horizontal && + _labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + bool isLastInsideItem = false; + if (index == length - 1) { + isLastInsideItem = widget.settings._edgeLabelsPlacement == + TreemapLegendEdgeLabelsPlacement.inside; + refNextTextWidth = _getTextWidth(nextText) / 2; + refCurrentTextWidth = isLastInsideItem + ? _getTextWidth(currentText) + : _getTextWidth(currentText) / 2; + } else { + refCurrentTextWidth = _getTextWidth(currentText) / 2; + refNextTextWidth = index + 1 == length - 1 && + widget.settings._edgeLabelsPlacement == + TreemapLegendEdgeLabelsPlacement.inside + ? _getTextWidth(nextText) + : _getTextWidth(nextText) / 2; + } + _isOverlapSegmentText = refCurrentTextWidth + refNextTextWidth > + barSize.width + widget.settings.spacing; + if (widget.settings._labelOverflow == TreemapLabelOverflow.ellipsis) { + final double textWidth = refCurrentTextWidth + refNextTextWidth; + return _getTrimText( + currentText, + widget.textStyle, + _segmentSize.width + widget.settings.spacing / 2, + _textPainter, + textWidth, + refNextTextWidth, + isLastInsideItem); + } + } + + return currentText; + } + + String _getText(TreemapColorMapper colorMapper) { + return colorMapper.from != null + ? colorMapper.name ?? + (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems + ? colorMapper.to.toString() + : (_textDirection == TextDirection.ltr + ? '${colorMapper.from} - ${colorMapper.to}' + : '${colorMapper.to} - ${colorMapper.from}')) + : colorMapper.value!; + } + + double _getTextWidth(String text) { + _textPainter.text = TextSpan(text: text, style: widget.textStyle); + _textPainter.layout(); + return _textPainter.width; + } + + /// Returns the bar legend icon and label. + Widget _getSegment(String text, Color color, int index, int length, + [TreemapColorMapper? colorMapper]) { + final Color iconColor = color; + return _getBarWithLabel(iconColor, index, text, colorMapper, length); + } + + Widget _getBarWithLabel(Color iconColor, int index, String text, + TreemapColorMapper? colorMapper, int dataSourceLength) { + Offset textOffset = _getTextOffset(index, text, dataSourceLength); + final CrossAxisAlignment crossAxisAlignment = + _getCrossAxisAlignment(index, dataSourceLength); + if (_direction == Axis.horizontal) { + textOffset = + _textDirection == TextDirection.rtl ? -textOffset : textOffset; + return Container( + width: _segmentSize.width, + child: Column( + crossAxisAlignment: crossAxisAlignment, + children: [ + Padding( + // Gap between segment text and icon. + padding: EdgeInsets.only(bottom: 7.0), + child: Container( + height: _segmentSize.height, + color: iconColor, + ), + ), + _getTextWidget(index, text, colorMapper, textOffset), + ], + ), + ); + } else { + return _getVerticalBar( + crossAxisAlignment, iconColor, index, text, colorMapper, textOffset); + } + } + + Widget _getVerticalBar( + CrossAxisAlignment crossAxisAlignment, + Color iconColor, + int index, + String text, + TreemapColorMapper? colorMapper, + Offset textOffset) { + return Container( + height: _segmentSize.width, + child: Row( + crossAxisAlignment: crossAxisAlignment, + children: [ + Padding( + // Gap between segment text and icon. + padding: EdgeInsets.only(right: 7.0), + child: Container( + width: _segmentSize.height, + color: iconColor, + ), + ), + _getTextWidget(index, text, colorMapper, textOffset), + ], + ), + ); + } + + CrossAxisAlignment _getCrossAxisAlignment(int index, int length) { + if (_labelsPlacement == TreemapLegendLabelsPlacement.onItem && + widget.settings._labelOverflow != TreemapLabelOverflow.visible) { + return CrossAxisAlignment.center; + } else { + return CrossAxisAlignment.start; + } + } + + Widget _getTextWidget(int index, String text, TreemapColorMapper? colorMapper, + Offset legendOffset) { + if (index == 0 && + colorMapper != null && + colorMapper.from != null && + _labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + return _getStartSegmentText(colorMapper, text, legendOffset); + } else { + return _getAlignedTextWidget(legendOffset, text, _isOverlapSegmentText); + } + } + + Widget _getStartSegmentText( + TreemapColorMapper colorMapper, String text, Offset legendOffset) { + bool isStartTextOverlapping = false; + String startText; + final List firstSegmentLabels = _getStartSegmentLabel(colorMapper); + if (firstSegmentLabels.length > 1) { + startText = firstSegmentLabels[0]; + } else { + startText = colorMapper.from!.toString(); + } + + if (_direction == Axis.horizontal && + widget.settings._labelOverflow != TreemapLabelOverflow.visible && + startText.isNotEmpty && + text.isNotEmpty) { + final double refStartTextWidth = widget.settings._edgeLabelsPlacement == + TreemapLegendEdgeLabelsPlacement.inside + ? _getTextWidth(startText) + : _getTextWidth(startText) / 2; + final double refCurrentTextWidth = _getTextWidth(text) / 2; + isStartTextOverlapping = refStartTextWidth + refCurrentTextWidth > + _segmentSize.width + widget.settings.spacing; + if (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems && + widget.settings._labelOverflow == TreemapLabelOverflow.ellipsis) { + startText = _getTrimText( + startText, + widget.textStyle, + _segmentSize.width + widget.settings.spacing / 2, + _textPainter, + refStartTextWidth + refCurrentTextWidth, + refCurrentTextWidth); + } + } + + Offset startTextOffset = _getStartTextOffset(startText); + startTextOffset = + _textDirection == TextDirection.rtl && _direction == Axis.horizontal + ? -startTextOffset + : startTextOffset; + return Stack( + children: [ + _getAlignedTextWidget( + startTextOffset, startText, isStartTextOverlapping), + _getAlignedTextWidget(legendOffset, text, _isOverlapSegmentText), + ], + ); + } + + List _getStartSegmentLabel(TreemapColorMapper colorMapper) { + if (colorMapper.from != null && + colorMapper.name != null && + colorMapper.name!.isNotEmpty && + colorMapper.name![0] == '{' && + _labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + final List splitText = colorMapper.name!.split('},{'); + if (splitText.length > 1) { + splitText[0] = splitText[0].replaceAll('{', ''); + splitText[1] = splitText[1].replaceAll('}', ''); + } + + return splitText; + } else { + return [_getText(colorMapper)]; + } + } + + Widget _getAlignedTextWidget(Offset offset, String text, bool isOverlapping) { + if ((widget.settings._labelOverflow == TreemapLabelOverflow.hide && + isOverlapping) || + text.isEmpty) { + return SizedBox(width: 0.0, height: 0.0); + } + + return Directionality( + textDirection: TextDirection.ltr, + child: offset != Offset.zero + ? Transform.translate( + offset: offset, + child: Text( + text, + softWrap: false, + overflow: TextOverflow.visible, + style: widget.textStyle, + ), + ) + : Text( + text, + textAlign: TextAlign.center, + softWrap: false, + overflow: widget.settings._labelOverflow == + TreemapLabelOverflow.ellipsis && + _labelsPlacement == TreemapLegendLabelsPlacement.onItem + ? TextOverflow.ellipsis + : TextOverflow.visible, + style: widget.textStyle, + ), + ); + } + + Offset _getTextOffset(int index, String text, int dataSourceLength) { + if (_labelsPlacement == TreemapLegendLabelsPlacement.onItem && + widget.settings._labelOverflow != TreemapLabelOverflow.visible) { + return Offset.zero; + } + + if (_direction == Axis.horizontal) { + return _getHorizontalTextOffset(index, text, dataSourceLength); + } else { + return _getVerticalTextOffset(index, text, dataSourceLength); + } + } + + Offset _getVerticalTextOffset(int index, String text, int dataSourceLength) { + _textPainter.text = TextSpan(text: text, style: widget.textStyle); + _textPainter.layout(); + if (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + if (index == dataSourceLength - 1) { + if (widget.settings._edgeLabelsPlacement == + TreemapLegendEdgeLabelsPlacement.inside) { + return Offset(0.0, _segmentSize.width - _textPainter.height); + } + return Offset(0.0, _segmentSize.width - _textPainter.height / 2); + } + + return Offset( + 0.0, + _segmentSize.width - + _textPainter.height / 2 + + widget.settings.spacing / 2); + } else { + return Offset(0.0, _segmentSize.width / 2 - _textPainter.height / 2); + } + } + + Offset _getHorizontalTextOffset( + int index, String text, int dataSourceLength) { + _textPainter.text = TextSpan(text: text, style: widget.textStyle); + _textPainter.layout(); + if (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + final double width = _textDirection == TextDirection.rtl && + _segmentSize.width < _textPainter.width + ? _textPainter.width + : _segmentSize.width; + if (index == dataSourceLength - 1) { + if (widget.settings._edgeLabelsPlacement == + TreemapLegendEdgeLabelsPlacement.inside) { + return Offset(width - _textPainter.width, 0.0); + } + return Offset(width - _textPainter.width / 2, 0.0); + } + + return Offset( + width - _textPainter.width / 2 + widget.settings.spacing / 2, 0.0); + } else { + final double xPosition = _textDirection == TextDirection.rtl && + _segmentSize.width < _textPainter.width + ? _textPainter.width / 2 - _segmentSize.width / 2 + : _segmentSize.width / 2 - _textPainter.width / 2; + return Offset(xPosition, 0.0); + } + } + + Offset _getStartTextOffset(String text) { + _textPainter.text = TextSpan(text: text, style: widget.textStyle); + _textPainter.layout(); + if (widget.settings._edgeLabelsPlacement == + TreemapLegendEdgeLabelsPlacement.inside) { + return Offset(0.0, 0.0); + } + + if (_direction == Axis.horizontal) { + return Offset(-_textPainter.width / 2, 0.0); + } else { + return Offset(0.0, -_textPainter.height / 2); + } + } +} + +class _GradientBarLabel { + _GradientBarLabel(this.label, + [this.offset = Offset.zero, this.isOverlapping = false]); + + String label; + Offset offset; + bool isOverlapping; +} + +// ignore: must_be_immutable +class _GradientBarLegend extends StatelessWidget { + _GradientBarLegend( + {required this.dataSource, + required this.settings, + required this.textStyle}); + + final dynamic dataSource; + final TreemapLegend settings; + final TextStyle textStyle; + final List colors = []; + final List<_GradientBarLabel> labels = <_GradientBarLabel>[]; + + late Axis _direction; + late Size _segmentSize; + late TextPainter _textPainter; + late double _referenceArea; + bool _isRTL = false; + bool _isOverlapSegmentText = false; + TreemapLegendLabelsPlacement? _labelsPlacement; + + @override + Widget build(BuildContext context) { + _labelsPlacement = + settings._labelsPlacement ?? TreemapLegendLabelsPlacement.betweenItems; + TextDirection textDirection = Directionality.of(context); + _isRTL = textDirection == TextDirection.rtl; + _textPainter = TextPainter( + textDirection: TextDirection.ltr, + textScaleFactor: MediaQuery.of(context).textScaleFactor); + _direction = settings.direction ?? + (settings.position == TreemapLegendPosition.top || + settings.position == TreemapLegendPosition.bottom + ? Axis.horizontal + : Axis.vertical); + textDirection = _isRTL + ? (_direction == Axis.vertical ? TextDirection.ltr : textDirection) + : textDirection; + + final Widget child = Directionality( + textDirection: textDirection, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final double width = + constraints.hasBoundedWidth ? constraints.maxWidth : 300; + final double height = + constraints.hasBoundedHeight ? constraints.maxHeight : 300; + _updateSegmentSize(Size(width, height).shortestSide); + _collectLabelsAndColors(); + return _buildGradientBar(); + }), + ); + if (settings.padding != null) { + return Padding(padding: settings.padding!, child: child); + } + + return child; + } + + void _updateSegmentSize(double shortestSide) { + if (_direction == Axis.horizontal) { + final double availableWidth = settings.padding != null + ? shortestSide - settings.padding!.horizontal + : shortestSide; + _segmentSize = settings._segmentSize == null + ? Size(availableWidth, 12.0) + : Size( + settings._segmentSize!.width > availableWidth + ? availableWidth + : settings._segmentSize!.width, + settings._segmentSize!.height); + return; + } + + final double availableHeight = settings.padding != null + ? shortestSide - settings.padding!.vertical + : shortestSide; + _segmentSize = settings._segmentSize == null + ? Size(12.0, availableHeight) + : Size( + settings._segmentSize!.width, + settings._segmentSize!.height > availableHeight + ? availableHeight + : settings._segmentSize!.height); + } + + void _collectLabelsAndColors() { + final int length = dataSource.length; + _referenceArea = _direction == Axis.horizontal + ? _segmentSize.width + : _segmentSize.height; + if (dataSource != null && dataSource.isNotEmpty) { + if (dataSource is List) { + _collectColorMapperLabelsAndColors(length); + } else { + final int length = dataSource.length; + String? text; + double slab; + // If we use as iterator, it will check first and second model and + // then check third and fourth model. But we can't check second and + // third item is overlapping or not. Since the iterator in second model. + // So we uses two iterator. If we use move next first iterator gives + // current model and second iterator gives next model. + final Iterator currentIterator = + dataSource.values.iterator; + final Iterator nextIterator = dataSource.values.iterator; + int index = 0; + nextIterator.moveNext(); + while (currentIterator.moveNext()) { + final TreemapTile treemapModel = currentIterator.current; + int positionIndex; + if (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + slab = _referenceArea / (length - 1); + positionIndex = index; + } else { + slab = _referenceArea / length; + positionIndex = index + 1; + } + + if (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + if (nextIterator.moveNext()) { + text = _getTrimmedText(treemapModel.group, positionIndex, length, + slab, nextIterator.current.group); + } else { + text = _getTrimmedText( + treemapModel.group, positionIndex, length, slab, text); + } + } else { + if (_direction == Axis.horizontal) { + text = _getTrimmedText( + treemapModel.group, positionIndex, length, slab); + } + } + + labels.add(_GradientBarLabel( + text!, + _getTextOffset(text, positionIndex, length - 1, slab), + _isOverlapSegmentText)); + colors.add(treemapModel.color); + index++; + } + } + } + } + + void _collectColorMapperLabelsAndColors(int length) { + if (dataSource.isNotEmpty) { + final double slab = _referenceArea / + (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems && + dataSource[0].value != null + ? length - 1 + : length); + for (int i = 0; i < length; i++) { + _isOverlapSegmentText = false; + final TreemapColorMapper colorMapper = dataSource[i]; + String text; + if (i == 0) { + final List firstSegmentLabels = + _getStartSegmentLabel(colorMapper); + text = (firstSegmentLabels.length > 1 + ? firstSegmentLabels[1] + : firstSegmentLabels[0]); + } else { + text = _getActualText(colorMapper); + } + + if (dataSource[0].from != null) { + _collectRageColorMapperLabels(i, colorMapper, text, slab, length); + } else { + final int positionIndex = + _labelsPlacement == TreemapLegendLabelsPlacement.onItem + ? i + 1 + : i; + if (_labelsPlacement == TreemapLegendLabelsPlacement.onItem) { + text = _getTrimmedText(text, i, length, slab); + } else if (i < length - 1) { + text = _getTrimmedText( + text, i, length, slab, _getActualText(dataSource[i + 1])); + } + // For equal color mapper, slab is equals to the color mapper + // length -1. + labels.add(_GradientBarLabel( + text, + _getTextOffset(text, positionIndex, length - 1, slab), + _isOverlapSegmentText)); + } + colors.add(colorMapper.color); + } + } + } + + void _collectRageColorMapperLabels(int i, TreemapColorMapper colorMapper, + String text, double slab, int length) { + if (i == 0 && + _labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + String startText; + final List firstSegmentLabels = + _getStartSegmentLabel(colorMapper); + if (firstSegmentLabels.length > 1) { + startText = firstSegmentLabels[0]; + } else { + startText = colorMapper.from!.toString(); + } + + if (_direction == Axis.horizontal && + _labelsPlacement == TreemapLegendLabelsPlacement.betweenItems && + startText.isNotEmpty && + text.isNotEmpty) { + final double refCurrentTextWidth = settings._edgeLabelsPlacement == + TreemapLegendEdgeLabelsPlacement.inside + ? _getTextWidth(startText) + : _getTextWidth(startText) / 2; + final double refNextTextWidth = _getTextWidth(text) / 2; + _isOverlapSegmentText = refCurrentTextWidth + refNextTextWidth > slab; + if (settings._labelOverflow == TreemapLabelOverflow.ellipsis) { + if (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + final double textWidth = refCurrentTextWidth + refNextTextWidth; + startText = _getTrimText(startText, textStyle, slab, _textPainter, + textWidth, refNextTextWidth); + } + } + } + + labels.add(_GradientBarLabel(startText, + _getTextOffset(startText, i, length, slab), _isOverlapSegmentText)); + } + + if (_labelsPlacement == TreemapLegendLabelsPlacement.onItem) { + text = _getTrimmedText(text, i, length, slab); + } else if (i < length - 1) { + text = _getTrimmedText( + text, i, length, slab, _getActualText(dataSource[i + 1])); + } + + // For range color mapper, slab is equals to the color mapper + // length. So adding +1 to point out its position index. + labels.add(_GradientBarLabel(text, + _getTextOffset(text, i + 1, length, slab), _isOverlapSegmentText)); + } + + String _getTrimmedText(String currentText, int index, int length, double slab, + [String? nextText]) { + if (settings._labelOverflow == TreemapLabelOverflow.visible || + currentText.isEmpty || + (nextText != null && nextText.isEmpty) || + nextText == null) { + return currentText; + } + + if (_direction == Axis.horizontal && + _labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + double refCurrentTextWidth; + double refNextTextWidth; + bool isLastInsideItem = false; + if (index == length - 1) { + refNextTextWidth = _getTextWidth(nextText) / 2; + + if (settings._edgeLabelsPlacement == + TreemapLegendEdgeLabelsPlacement.inside) { + refCurrentTextWidth = _getTextWidth(currentText); + isLastInsideItem = true; + } else { + refCurrentTextWidth = _getTextWidth(currentText) / 2; + isLastInsideItem = false; + } + } else { + refCurrentTextWidth = _getTextWidth(currentText) / 2; + refNextTextWidth = index + 1 == length - 1 && + settings._edgeLabelsPlacement == + TreemapLegendEdgeLabelsPlacement.inside + ? _getTextWidth(nextText) + : _getTextWidth(nextText) / 2; + } + _isOverlapSegmentText = refCurrentTextWidth + refNextTextWidth > slab; + if (settings._labelOverflow == TreemapLabelOverflow.ellipsis && + _isOverlapSegmentText) { + if (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + final double textWidth = refCurrentTextWidth + refNextTextWidth; + return _getTrimText(currentText, textStyle, slab, _textPainter, + textWidth, refNextTextWidth, isLastInsideItem); + } + } + } else if (_direction == Axis.horizontal && + _labelsPlacement == TreemapLegendLabelsPlacement.onItem) { + final double textWidth = _getTextWidth(currentText); + _isOverlapSegmentText = textWidth > slab; + if (_isOverlapSegmentText) { + return _getTrimText( + currentText, textStyle, slab, _textPainter, textWidth); + } + } + + return currentText; + } + + double _getTextWidth(String text) { + _textPainter.text = TextSpan(text: text, style: textStyle); + _textPainter.layout(); + return _textPainter.width; + } + + List _getStartSegmentLabel(TreemapColorMapper colorMapper) { + if (colorMapper.from != null && + colorMapper.name != null && + colorMapper.name!.isNotEmpty && + colorMapper.name![0] == '{' && + _labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + final List splitText = colorMapper.name!.split('},{'); + if (splitText.length > 1) { + splitText[0] = splitText[0].replaceAll('{', ''); + splitText[1] = splitText[1].replaceAll('}', ''); + } + + return splitText; + } else { + return [_getActualText(colorMapper)]; + } + } + + Offset _getTextOffset( + String? text, int positionIndex, int length, double slab) { + _textPainter.text = TextSpan(text: text, style: textStyle); + _textPainter.layout(); + final bool canAdjustLabelToCenter = settings._edgeLabelsPlacement == + TreemapLegendEdgeLabelsPlacement.center && + (positionIndex == 0 || positionIndex == length) || + (positionIndex > 0 && positionIndex < length) || + settings._labelsPlacement == TreemapLegendLabelsPlacement.onItem; + if (_direction == Axis.horizontal) { + return _getHorizontalOffset( + canAdjustLabelToCenter, positionIndex, slab, length); + } else { + final double referenceTextWidth = canAdjustLabelToCenter + ? _textPainter.height / 2 + : (positionIndex == length ? _textPainter.height : 0.0); + if (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + return Offset(0.0, slab * positionIndex - referenceTextWidth); + } + + return Offset( + 0.0, (slab * positionIndex) - referenceTextWidth - slab / 2); + } + } + + Offset _getHorizontalOffset( + bool canAdjustLabelToCenter, int positionIndex, double slab, int length) { + if (_isRTL) { + final double referenceTextWidth = canAdjustLabelToCenter + ? -_textPainter.width / 2 + : (positionIndex == 0 ? -_textPainter.width : 0.0); + double dx = + _segmentSize.width - (slab * positionIndex - referenceTextWidth); + + if (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + return Offset(dx, 0.0); + } + + dx = _segmentSize.width - (slab * positionIndex); + + return Offset(dx + slab / 2 - _textPainter.width / 2, 0.0); + } + + final double referenceTextWidth = canAdjustLabelToCenter + ? _textPainter.width / 2 + : (positionIndex == length ? _textPainter.width : 0.0); + if (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems) { + return Offset(slab * positionIndex - referenceTextWidth, 0.0); + } + + return Offset( + slab * positionIndex - _textPainter.width / 2 - slab / 2, 0.0); + } + + Widget _buildGradientBar() { + return _direction == Axis.horizontal + ? Column(children: _getChildren()) + : Row(children: _getChildren()); + } + + List _getChildren() { + double? labelBoxWidth = _segmentSize.width; + double? labelBoxHeight; + Alignment startAlignment = Alignment.centerLeft; + Alignment endAlignment = Alignment.centerRight; + + if (_direction == Axis.vertical) { + labelBoxWidth = null; + labelBoxHeight = _segmentSize.height; + startAlignment = Alignment.topCenter; + endAlignment = Alignment.bottomCenter; + } + + if (_isRTL && _direction == Axis.horizontal) { + final Alignment temp = startAlignment; + startAlignment = endAlignment; + endAlignment = temp; + } + + return [ + Container( + width: _segmentSize.width, + height: _segmentSize.height, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: startAlignment, end: endAlignment, colors: colors), + ), + ), + SizedBox( + width: _direction == Axis.vertical ? 7.0 : 0.0, + height: _direction == Axis.horizontal ? 7.0 : 0.0), + Container( + width: labelBoxWidth, height: labelBoxHeight, child: _getLabels()), + ]; + } + + Widget _getLabels() { + return Stack( + textDirection: TextDirection.ltr, + children: List.generate(labels.length, (int index) { + if ((settings._labelOverflow == TreemapLabelOverflow.hide && + labels[index].isOverlapping) || + labels[index].label.isEmpty) { + return SizedBox(height: 0.0, width: 0.0); + } + + return Directionality( + textDirection: TextDirection.ltr, + child: Transform.translate( + offset: labels[index].offset, + child: Text( + labels[index].label, + style: textStyle, + softWrap: false, + ), + ), + ); + }), + ); + } + + String _getActualText(TreemapColorMapper colorMapper) { + return colorMapper.from != null + ? colorMapper.name ?? + (_labelsPlacement == TreemapLegendLabelsPlacement.betweenItems + ? colorMapper.to.toString() + : (_isRTL + ? '${colorMapper.to} - ${colorMapper.from}' + : '${colorMapper.from} - ${colorMapper.to}')) + : colorMapper.value!; + } +} + +class _TreemapIconShape { + const _TreemapIconShape(); + + /// Returns the size based on the value passed to it. + Size getPreferredSize(Size iconSize) => iconSize; + + /// Paints the shapes based on the value passed to it. + void paint( + PaintingContext context, + Offset offset, { + required RenderBox parentBox, + required Size iconSize, + required Color color, + Color? strokeColor, + double? strokeWidth, + required TreemapIconType iconType, + }) { + iconSize = getPreferredSize(iconSize); + final double halfIconWidth = iconSize.width / 2; + final double halfIconHeight = iconSize.height / 2; + final bool hasStroke = strokeWidth != null && + strokeWidth > 0 && + strokeColor != null && + strokeColor != Colors.transparent; + final Paint paint = Paint() + ..isAntiAlias = true + ..color = color; + Path path; + + switch (iconType) { + case TreemapIconType.circle: + final Rect rect = Rect.fromLTWH( + offset.dx, offset.dy, iconSize.width, iconSize.height); + context.canvas.drawOval(rect, paint); + if (hasStroke) { + paint + ..strokeWidth = strokeWidth + ..color = strokeColor + ..style = PaintingStyle.stroke; + context.canvas.drawOval(rect, paint); + } + break; + case TreemapIconType.rectangle: + final Rect rect = Rect.fromLTWH( + offset.dx, offset.dy, iconSize.width, iconSize.height); + context.canvas.drawRect(rect, paint); + if (hasStroke) { + paint + ..strokeWidth = strokeWidth + ..color = strokeColor + ..style = PaintingStyle.stroke; + context.canvas.drawRect(rect, paint); + } + break; + case TreemapIconType.triangle: + path = Path() + ..moveTo(offset.dx + halfIconWidth, offset.dy) + ..lineTo(offset.dx + iconSize.width, offset.dy + iconSize.height) + ..lineTo(offset.dx, offset.dy + iconSize.height) + ..close(); + context.canvas.drawPath(path, paint); + if (hasStroke) { + paint + ..strokeWidth = strokeWidth + ..color = strokeColor + ..style = PaintingStyle.stroke; + context.canvas.drawPath(path, paint); + } + break; + case TreemapIconType.diamond: + path = Path() + ..moveTo(offset.dx + halfIconWidth, offset.dy) + ..lineTo(offset.dx + iconSize.width, offset.dy + halfIconHeight) + ..lineTo(offset.dx + halfIconWidth, offset.dy + iconSize.height) + ..lineTo(offset.dx, offset.dy + halfIconHeight) + ..close(); + context.canvas.drawPath(path, paint); + if (hasStroke) { + paint + ..strokeWidth = strokeWidth + ..color = strokeColor + ..style = PaintingStyle.stroke; + context.canvas.drawPath(path, paint); + } + break; + } + } +} + +String _getTrimText(String text, TextStyle style, double maxWidth, + TextPainter painter, double width, + [double? nextTextHalfWidth, bool isInsideLastItem = false]) { + final int actualTextLength = text.length; + String trimmedText = text; + int trimLength = 3; // 3 dots + while (width > maxWidth) { + if (trimmedText.length <= 4) { + trimmedText = trimmedText[0] + '...'; + painter.text = TextSpan(style: style, text: trimmedText); + painter.layout(); + break; + } else { + trimmedText = text.replaceRange( + actualTextLength - trimLength, actualTextLength, '...'); + painter.text = TextSpan(style: style, text: trimmedText); + painter.layout(); + trimLength++; + } + + if (isInsideLastItem && nextTextHalfWidth != null) { + width = painter.width + nextTextHalfWidth; + } else { + width = nextTextHalfWidth != null + ? painter.width / 2 + nextTextHalfWidth + : painter.width; + } + } + + return trimmedText; +} diff --git a/packages/syncfusion_flutter_treemap/lib/src/tooltip.dart b/packages/syncfusion_flutter_treemap/lib/src/tooltip.dart new file mode 100644 index 000000000..2ee21a8c3 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/lib/src/tooltip.dart @@ -0,0 +1,497 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../treemap.dart'; +import 'layouts.dart'; + +/// The tooltip provides additional information about the treemap tiles. +class TreemapTooltip extends StatefulWidget { + /// Creates a tooltip for treemap tiles. + const TreemapTooltip({Key? key, required this.settings}) : super(key: key); + + /// Option to customizes the appearance of the tooltip. + final TreemapTooltipSettings settings; + + @override + _TreemapTooltipState createState() => _TreemapTooltipState(); +} + +class _TreemapTooltipState extends State + with SingleTickerProviderStateMixin { + Widget? _child; + late bool _isDesktop; + late AnimationController controller; + + void adoptChild(TreemapTile tile) { + if (mounted) { + setState(() { + final Widget? child = tile.level.tooltipBuilder!.call(context, tile); + _child = child != null ? IgnorePointer(child: child) : null; + }); + } + } + + @override + void initState() { + controller = AnimationController( + vsync: this, duration: const Duration(milliseconds: 200)); + super.initState(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final ThemeData themeData = Theme.of(context); + _isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows || + themeData.platform == TargetPlatform.linux; + final TreemapTooltipSettings settings = widget.settings.copyWith( + color: widget.settings.color ?? + (themeData.brightness == Brightness.light + ? const Color.fromRGBO(117, 117, 117, 1) + : const Color.fromRGBO(245, 245, 245, 1))); + return _TreemapTooltipRenderObjectWidget( + child: _child, settings: settings, state: this); + } +} + +/// A Render object widget which draws tooltip shape. +class _TreemapTooltipRenderObjectWidget extends SingleChildRenderObjectWidget { + const _TreemapTooltipRenderObjectWidget( + {Widget? child, required this.settings, required this.state}) + : super(child: child); + + final _TreemapTooltipState state; + final TreemapTooltipSettings settings; + + @override + RenderTooltip createRenderObject(BuildContext context) { + return RenderTooltip( + settings: settings, + mediaQueryData: MediaQuery.of(context), + state: state, + ); + } + + @override + void updateRenderObject(BuildContext context, RenderTooltip renderObject) { + renderObject + ..settings = settings + ..mediaQueryData = MediaQuery.of(context); + } +} + +/// Render box of [TreemapTooltip]. +class RenderTooltip extends RenderProxyBox { + /// Creates [RenderTooltip]. + RenderTooltip({ + required TreemapTooltipSettings settings, + required MediaQueryData mediaQueryData, + required _TreemapTooltipState state, + }) : _settings = settings, + _mediaQueryData = mediaQueryData, + _state = state { + _scaleAnimation = + CurvedAnimation(parent: _state.controller, curve: Curves.easeOutBack); + _textDirection = Directionality.of(_state.context); + } + + /// Tooltip nose height. + static const double tooltipTriangleHeight = 7.0; + // Pixels to be moved from the top, to position the tooltip inside the tile. + static const double _topPadding = 15.0; + final _TreemapTooltipState _state; + final _TooltipShape _tooltipShape = const _TooltipShape(); + final Duration _waitDuration = const Duration(seconds: 3); + final Duration _hideDeferDuration = const Duration(milliseconds: 500); + late Animation _scaleAnimation; + Timer? _showTimer; + Timer? _hideDeferTimer; + Size? _tileSize; + Offset? _position; + bool _preferTooltipOnTop = true; + bool _shouldCalculateTooltipPosition = false; + TreemapTile? _previousTile; + late TextDirection _textDirection; + PointerKind? _pointerKind; + + /// Customizes the appearance of the tooltip. + TreemapTooltipSettings get settings => _settings; + late TreemapTooltipSettings _settings; + set settings(TreemapTooltipSettings value) { + if (_settings == value) { + return; + } + _settings = value; + } + + /// [MediaQueryData] used to handle the orientation changes. + MediaQueryData get mediaQueryData => _mediaQueryData; + MediaQueryData _mediaQueryData; + set mediaQueryData(MediaQueryData value) { + if (_mediaQueryData.orientation == value.orientation) { + return; + } + _mediaQueryData = value; + hide(immediately: true); + } + + /// Shows the tooltip. + void show(Offset globalFocalPoint, TreemapTile tile, Size tileSize, + PointerKind kind) { + _pointerKind = kind; + if (_state._isDesktop && _previousTile == tile) { + return; + } + + _shouldCalculateTooltipPosition = true; + _state.adoptChild(tile); + _position = globalToLocal(globalFocalPoint); + _tileSize = tileSize; + _hideDeferTimer?.cancel(); + if (_state._isDesktop && _pointerKind == PointerKind.hover) { + if (_previousTile != tile) { + _previousTile = tile; + if (_state.controller.value > 0) { + _state.controller.reset(); + } + + _showTimer?.cancel(); + _showTimer = Timer(const Duration(milliseconds: 100), () { + _state.controller.forward(from: 0.0); + }); + } + } else { + _state.controller.forward(from: 0.0); + _showTimer?.cancel(); + if (_pointerKind == PointerKind.touch) { + _showTimer = Timer(_waitDuration, hide); + } + } + } + + /// Hides the tooltip. + void hide({bool immediately = false}) { + _previousTile = null; + _pointerKind = null; + _showTimer?.cancel(); + + if (immediately) { + _state.controller.reset(); + return; + } + + if (_state._isDesktop) { + _hideDeferTimer?.cancel(); + _hideDeferTimer = Timer(_hideDeferDuration, () { + _state.controller.reverse(); + }); + } else { + _state.controller.reverse(); + } + } + + @override + bool get isRepaintBoundary => true; + + @override + void performLayout() { + child?.layout(constraints); + size = Size(constraints.maxWidth, constraints.maxHeight); + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _scaleAnimation.addListener(markNeedsPaint); + } + + @override + void detach() { + _showTimer?.cancel(); + _hideDeferTimer?.cancel(); + _scaleAnimation.removeListener(markNeedsPaint); + super.detach(); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (child != null && child!.attached && _position != null) { + // We are calculating the tooltip position in paint method because of the + // child didn't get layouts while forwarding the animation controller. + if (_shouldCalculateTooltipPosition) { + _updatePositionAndDirection(); + _shouldCalculateTooltipPosition = false; + } + + _tooltipShape.paint(context, offset, _position!, _preferTooltipOnTop, + this, _scaleAnimation, _settings, _textDirection); + } + } + + void _updatePositionAndDirection() { + final double height = child!.size.height + tooltipTriangleHeight; + // If the height of the tooltip child is exactly set within the height of + // the tile, the UI looks like to indicate it to the bottom tile. Thus, we + // took the tile size of only 70%. + if ((height + _topPadding) < (_tileSize!.height * 0.7)) { + _position = + _position! + Offset(_tileSize!.width / 2, height + _topPadding); + _preferTooltipOnTop = true; + } else { + // Obtaining a tooltip position at 30% of the tile's height. + _position = + _position! + Offset(_tileSize!.width / 2, _tileSize!.height * 0.3); + _preferTooltipOnTop = + paintBounds.contains(_position! - Offset(0.0, height)); + } + } +} + +/// Base class for map tooltip shapes. +class _TooltipShape { + const _TooltipShape(); + + static const double marginSpace = 6.0; + + /// Paints the tooltip shapes based on the value passed to it. + void paint( + PaintingContext context, + Offset offset, + Offset center, + bool preferTooltipOnTop, + RenderProxyBox parentBox, + Animation tooltipAnimation, + TreemapTooltipSettings tooltipSettings, + TextDirection textDirection) { + const double tooltipTriangleWidth = 12.0; + const double halfTooltipTriangleWidth = tooltipTriangleWidth / 2; + const double elevation = 0.0; + Path path = Path(); + + BorderRadius borderRadius = + tooltipSettings.borderRadius.resolve(textDirection); + final double tooltipWidth = parentBox.child!.size.width; + double tooltipHeight = parentBox.child!.size.height; + final double halfTooltipWidth = tooltipWidth / 2; + double halfTooltipHeight = tooltipHeight / 2; + + double triangleHeight = RenderTooltip.tooltipTriangleHeight; + final double tooltipStartPoint = triangleHeight + tooltipHeight / 2; + double tooltipTriangleOffsetY = tooltipStartPoint - triangleHeight; + + final double endGlobal = parentBox.size.width - marginSpace; + double rightLineWidth = center.dx + halfTooltipWidth > endGlobal + ? endGlobal - center.dx + : halfTooltipWidth; + final double leftLineWidth = center.dx - halfTooltipWidth < marginSpace + ? center.dx - marginSpace + : tooltipWidth - rightLineWidth; + rightLineWidth = leftLineWidth < halfTooltipWidth + ? halfTooltipWidth - leftLineWidth + rightLineWidth + : rightLineWidth; + + double moveNosePoint = leftLineWidth < tooltipWidth * 0.2 + ? tooltipWidth * 0.2 - leftLineWidth + : 0.0; + moveNosePoint = rightLineWidth < tooltipWidth * 0.2 + ? -(tooltipWidth * 0.2 - rightLineWidth) + : moveNosePoint; + + double shiftText = leftLineWidth > rightLineWidth + ? -(halfTooltipWidth - rightLineWidth) + : 0.0; + shiftText = leftLineWidth < rightLineWidth + ? (halfTooltipWidth - leftLineWidth) + : shiftText; + + rightLineWidth = rightLineWidth + elevation; + if (!preferTooltipOnTop) { + // We had multiplied -1 with the below values to move its position from + // top to bottom. + // ________ + // |___ ___| to ___/\___ + // \/ |________| + triangleHeight *= -1; + halfTooltipHeight *= -1; + tooltipTriangleOffsetY *= -1; + tooltipHeight *= -1; + borderRadius = BorderRadius.only( + topRight: Radius.elliptical( + borderRadius.bottomRight.x, -borderRadius.bottomRight.y), + bottomRight: Radius.elliptical( + borderRadius.topRight.x, -borderRadius.topRight.y), + topLeft: Radius.elliptical( + borderRadius.bottomLeft.x, -borderRadius.bottomLeft.y), + bottomLeft: + Radius.elliptical(borderRadius.topLeft.x, -borderRadius.topLeft.y), + ); + } + + path = _getTooltipPath( + path, + triangleHeight, + halfTooltipHeight, + halfTooltipTriangleWidth, + tooltipTriangleOffsetY, + moveNosePoint, + rightLineWidth, + leftLineWidth, + borderRadius, + tooltipHeight); + + context.canvas.save(); + context.canvas + .translate(center.dx, center.dy - triangleHeight - halfTooltipHeight); + context.canvas.scale(tooltipAnimation.value); + // In web HTML rendering, fill color clipped half of its tooltip's size. + // To avoid this issue we are drawing stroke before fill. + final Color strokeColor = tooltipSettings.borderColor ?? Colors.transparent; + final Paint paint = Paint() + ..color = strokeColor + // We are drawing stroke before fill to avoid tooltip rendering issue in + // a web HTML rendering. Due to this, half of the stroke width only + // visible to us so that we are twice the stroke width. + ..strokeWidth = tooltipSettings.borderWidth * 2 + ..style = PaintingStyle.stroke; + // Drawing stroke. + context.canvas.drawPath(path, paint); + paint + ..style = PaintingStyle.fill + ..color = tooltipSettings.color!; + // Drawing fill color. + context.canvas.drawPath(path, paint); + + context.canvas.clipPath(path); + context.paintChild(parentBox.child!, + offset - _getShiftPosition(offset, center, parentBox)); + context.canvas.restore(); + } + + Path _getTooltipPath( + Path path, + double tooltipTriangleHeight, + double halfTooltipHeight, + double halfTooltipTriangleWidth, + double tooltipTriangleOffsetY, + double moveNosePoint, + double rightLineWidth, + double leftLineWidth, + BorderRadius borderRadius, + double tooltipHeight) { + path.reset(); + + path.moveTo(0, tooltipTriangleHeight + halfTooltipHeight); + // preferTooltipOnTop is true, + // / + + // preferTooltipOnTop is false, + // \ + path.lineTo( + halfTooltipTriangleWidth + moveNosePoint, tooltipTriangleOffsetY); + // preferTooltipOnTop is true, + // ___ + // / + + // preferTooltipOnTop is false, + // \___ + path.lineTo( + rightLineWidth - borderRadius.bottomRight.x, tooltipTriangleOffsetY); + // preferTooltipOnTop is true, + // ___| + // / + + // preferTooltipOnTop is false, + // \___ + // | + path.quadraticBezierTo(rightLineWidth, tooltipTriangleOffsetY, + rightLineWidth, tooltipTriangleOffsetY - borderRadius.bottomRight.y); + path.lineTo(rightLineWidth, + tooltipTriangleOffsetY - tooltipHeight + borderRadius.topRight.y); + // preferTooltipOnTop is true, + // _______ + // ___| + // / + + // preferTooltipOnTop is false, + // \___ + // ________| + path.quadraticBezierTo( + rightLineWidth, + tooltipTriangleOffsetY - tooltipHeight, + rightLineWidth - borderRadius.topRight.x, + tooltipTriangleOffsetY - tooltipHeight); + path.lineTo(-leftLineWidth + borderRadius.topLeft.x, + tooltipTriangleOffsetY - tooltipHeight); + // preferTooltipOnTop is true, + // _______ + // | ___| + // / + + // preferTooltipOnTop is false, + // \___ + // |________| + path.quadraticBezierTo( + -leftLineWidth, + tooltipTriangleOffsetY - tooltipHeight, + -leftLineWidth, + tooltipTriangleOffsetY - tooltipHeight + borderRadius.topLeft.y); + path.lineTo( + -leftLineWidth, tooltipTriangleOffsetY - borderRadius.bottomLeft.y); + // preferTooltipOnTop is true, + // ________ + // |___ ___| + // / + + // preferTooltipOnTop is false, + // ___ \___ + // |________| + path.quadraticBezierTo(-leftLineWidth, tooltipTriangleOffsetY, + -leftLineWidth + borderRadius.bottomLeft.x, tooltipTriangleOffsetY); + path.lineTo( + -halfTooltipTriangleWidth + moveNosePoint, tooltipTriangleOffsetY); + // preferTooltipOnTop is true, + // ________ + // |___ ___| + // \/ + + // preferTooltipOnTop is false, + // ___/\___ + // |________| + path.close(); + + return path; + } + + Offset _getShiftPosition( + Offset offset, Offset center, RenderProxyBox parentBox) { + final Size childSize = parentBox.child!.size; + final double halfChildWidth = childSize.width / 2; + final double halfChildHeight = childSize.height / 2; + + // Shifting the position of the tooltip to the left side, if its right + // edge goes out of the map's right edge. + if (center.dx + halfChildWidth + marginSpace > parentBox.size.width) { + return Offset( + childSize.width + center.dx - parentBox.size.width + marginSpace, + halfChildHeight); + } + // Shifting the position of the tooltip to the right side, if its left + // edge goes out of the map's left edge. + else if (center.dx - halfChildWidth - marginSpace < offset.dx) { + return Offset(center.dx - marginSpace, halfChildHeight); + } + + return Offset(halfChildWidth, halfChildHeight); + } +} diff --git a/packages/syncfusion_flutter_treemap/lib/treemap.dart b/packages/syncfusion_flutter_treemap/lib/treemap.dart new file mode 100644 index 000000000..cbc2f5f8d --- /dev/null +++ b/packages/syncfusion_flutter_treemap/lib/treemap.dart @@ -0,0 +1,2505 @@ +library treemap; + +import 'package:flutter/material.dart'; + +import 'src/layouts.dart'; +import 'src/legend.dart'; + +export 'src/layouts.dart' show TreemapTile; +export 'src/legend.dart' hide LegendWidget; + +/// Signature to return the string values from the data source based +/// on the index. +/// +/// See also: +/// * [IndexedDoubleValueMapper] which is similar, but it returns a +/// double value. +/// * [TreemapTileWidgetBuilder] returns a widget based on a given tile. +typedef IndexedStringValueMapper = String? Function(int index); + +/// Signature to return the double values from the data source based +/// on the index. +/// +/// See also: +/// * [IndexedStringValueMapper] which is similar, but it returns a string. +typedef IndexedDoubleValueMapper = double Function(int index); + +/// Signature to return the colors or other types from the data source based +/// on the tile based on which colors will be applied. +/// +/// See also: +/// * [IndexedDoubleValueMapper] which is similar, but it returns a double +/// value. +/// * [TreemapTileWidgetBuilder] returns a widget based on a given tile. +typedef TreemapTileColorValueMapper = dynamic Function(TreemapTile tile); + +/// Signature to return a widget based on the given tile. +/// +/// See also: +/// * [IndexedStringValueMapper] returns a string based on the given index. +/// * [TreemapTileColorValueMapper] returns a dynamic value based on the group +/// and parent. +typedef TreemapTileWidgetBuilder = Widget? Function( + BuildContext context, TreemapTile tile); + +/// The levels collection which forms either flat or hierarchal treemap. +/// +/// You can have more than one [TreemapLevel] in this collection to form a +/// hierarchal treemap. The 0th index of the [SfTreemap.levels] collection +/// forms the base level of the treemap or flat treemap. From the 1st index, +/// the values returned in the [TreemapLevel.groupMapper] callback will form as +/// inner tiles of the tile formed in the previous level for which the indices +/// match.This hierarchy will go on till the last [TreemapLevel] in the +/// [Treemap.levels] collection. +/// +/// ```dart +/// List _socialMediaUsersData; +/// +/// @override +/// void initState() { +/// _socialMediaUsersData = [ +/// SocialMediaUsers('India', 'Facebook', 280), +/// SocialMediaUsers('India', 'Instagram', 88), +/// SocialMediaUsers('USA', 'Facebook', 190), +/// SocialMediaUsers('USA', 'Instagram', 120), +/// SocialMediaUsers('Japan', 'Twitter', 48), +/// SocialMediaUsers('Japan', 'Instagram', 31), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfTreemap( +/// dataCount: _socialMediaUsersData.length, +/// weightValueMapper: (int index) { +/// return _socialMediaUsersData[index].usersInMillions; +/// }, +/// levels: [ +/// TreemapLevel( +/// color: Colors.red, +/// padding: EdgeInsets.all(2), +/// groupMapper: (int index) { +/// return _socialMediaUsersData[index].country; +/// }), +/// ], +/// ), +/// ); +/// } +/// +/// class SocialMediaUsers { +/// const SocialMediaUsers( +/// this.country, +/// this.socialMedia, +/// this.usersInMillions, +/// ); +/// final String country; +/// final String socialMedia; +/// final double usersInMillions; +/// } +/// ``` +/// +/// See also: +/// * [SfTreemap], to know how treemap render the tiles. +class TreemapLevel { + /// Creates a [TreemapLevel]. + /// + /// The levels collection which forms either flat or hierarchal treemap. + /// + /// You can have more than one [TreemapLevel] in this collection to form a + /// hierarchal treemap. The 0th index of the [TreemapLevel.levels] collection + /// forms the base level of the treemap or flat treemap. From the 1st index, + /// the values returned in the [TreemapLevel.groupMapper] callback will form + /// as inner tiles of the tile formed in the previous level for which the + /// indices match.This hierarchy will go on till the last [TreemapLevel] in + /// the [TreemapLevel.levels] collection. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// color: Colors.red, + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [SfTreemap], to know how treemap render the tiles. + const TreemapLevel({ + this.color, + this.border, + this.padding = const EdgeInsets.all(1.0), + required this.groupMapper, + this.colorValueMapper, + this.tooltipBuilder, + this.labelBuilder, + this.itemBuilder, + }); + + /// Specifies the color applied to the tiles. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// color: Colors.red, + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [border], to set the border to the tiles. + /// * [padding], to apply a space around the tiles. + final Color? color; + + /// Specifies the border of the rectangular box with rounded corners. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// color: Colors.red, + /// border: const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all(Radius.circular(15)), + /// side: BorderSide(color: Colors.black, width: 2), + /// ), + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [padding], to apply a space around the tiles. + /// * [groupMapper] denotes the group which the tile has be mapped in the + /// internal data source. + final RoundedRectangleBorder? border; + + /// Sets the padding around the tiles based on the value. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// color: Colors.red, + /// border: const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all(Radius.circular(15)), + /// side: BorderSide(color: Colors.black, width: 2), + /// ), + /// padding: EdgeInsets.all(2), + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [groupMapper] denotes the group which the tile has be mapped in the + /// internal data source. + /// * [labelBuilder], displays a basic information about the tiles. + final EdgeInsetsGeometry? padding; + + /// Returns the group to be mapped based on the data source. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// color: Colors.red, + /// border: const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all(Radius.circular(15)), + /// side: BorderSide(color: Colors.black, width: 2), + /// ), + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [labelBuilder], displays a basic information about the tiles. + /// * [tooltipBuilder], displays an additional information about the tile + /// while interacting. + final IndexedStringValueMapper groupMapper; + + /// Returns a color or value based on which tile color will be updated. + /// + /// If this returns a color, then this color will be applied to the tile + /// straightaway. + /// + /// If it returns a value other than the color, then you must set the + /// [SfTreemap.colorMappers] property. + /// + /// The value returned from the [colorValueMapper] will be used for the + /// comparison in the [TreemapColorMapper.value] or [TreemapColorMapper.from] + /// and [TreemapColorMapper.to]. Then, the [TreemapColorMapper.color] will be + /// applied to the respective tiles. + /// + /// If it returns null, we have applied color based on the tile weight. + /// + /// ```dart + /// List _socialMediaUsersData; + /// List _colorMappers; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// _colorMappers = [ + /// TreemapColorMapper.range(0, 100, Colors.green), + /// TreemapColorMapper.range(101, 200, Colors.blue), + /// TreemapColorMapper.range(201, 300, Colors.red), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// colorMappers: _colorMappers, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// colorValueMapper: (TreemapTile tile) => tile.weight, + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [color], to set the color to the tiles. + /// * [border], to set the border to the tiles. + final TreemapTileColorValueMapper? colorValueMapper; + + /// Returns a widget for the treemap label based on the tile. + /// + /// A treemap label displays additional information about the tile + /// on a treemap. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// color: Colors.red, + /// labelBuilder: (BuildContext context, TreemapTile tile) { + /// return Padding( + /// padding: EdgeInsets.all(4.0), + /// child: Text(tile.group), + /// ); + /// }), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [TreemapLevel.itemBuilder], shows icons, images and text + /// in the treemap tiles and customizes their position. + final TreemapTileWidgetBuilder? labelBuilder; + + /// Returns a widget for the treemap tile based on the index. + /// + /// A treemap item builder displays icons, images and text in the treemap + /// tiles and customizes their position. + /// + /// By default , widget will be align top left of the tile. If we need to + /// customize it, the Align widget can be wrapped and aligned. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// color: Colors.red, + /// itemBuilder: (BuildContext context, TreemapTile tile) { + /// return Icon(Icons.mobile_friendly); + /// }), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [TreemapLevel.labelBuilder], for showing the additional information + /// about the tile. + final TreemapTileWidgetBuilder? itemBuilder; + + /// Returns a widget for the tooltip based on the treemap tile. + /// + /// It displays additional information about the tiles on the treemap. + /// The returned widget will then be wrapped in the existing tooltip shape + /// which comes with the nose at the bottom. + /// + /// It is possible to customize the border appearance using the + /// [TreemapTooltipSettings.borderColor] and + /// [TreemapTooltipSettings.borderWidth]. To customize the corners, use + /// [TreemapTooltipSettings.borderRadius]. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// color: Colors.red, + /// border: const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all(Radius.circular(15)), + /// side: BorderSide(color: Colors.black, width: 2), + /// ), + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// tooltipBuilder: (BuildContext context, TreemapTile tile) { + /// return Text('Country: ${tile.group}\n Users: ${tile.weight}'); + /// }, + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// See also: + /// * [itemBuilder], displays the returned widget to the background + /// of the tiles. + final TreemapTileWidgetBuilder? tooltipBuilder; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is TreemapLevel && + other.color == color && + other.border == border && + other.padding == padding && + other.groupMapper == groupMapper && + other.colorValueMapper == colorValueMapper && + other.labelBuilder == labelBuilder && + other.itemBuilder == itemBuilder && + other.tooltipBuilder == tooltipBuilder; + } + + @override + int get hashCode => hashValues( + groupMapper, color, border, colorValueMapper, padding, tooltipBuilder); +} + +/// Customized the appearance of the tiles in selection state. +/// +/// ```dart +/// List _source; +/// +/// @override +/// void initState() { +/// _source = [ +/// SocialMediaUsers('India', 'Facebook', 280), +/// SocialMediaUsers('India', 'Instagram', 88), +/// SocialMediaUsers('USA', 'Facebook', 190), +/// SocialMediaUsers('USA', 'Instagram', 120), +/// SocialMediaUsers('Japan', 'Twitter', 48), +/// SocialMediaUsers('Japan', 'Instagram', 31), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfTreemap( +/// dataCount: _source.length, +/// weightValueMapper: (int index) { +/// return _source[index].usersInMillions; +/// }, +/// levels: [ +/// TreemapLevel( +/// groupMapper: (int index) { +/// return _source[index].country; +/// }, +/// ), +/// ], +/// onSelectionChanged: (TreemapTile tile) {}, +/// selectionSettings: const TreemapSelectionSettings(), +/// ), +/// ); +/// } +/// +/// class SocialMediaUsers { +/// const SocialMediaUsers( +/// this.country, +/// this.socialMedia, +/// this.usersInMillions, +/// ); +/// final String country; +/// final String socialMedia; +/// final double usersInMillions; +/// } +/// ``` +/// +/// See also: +/// * [TreemapSelectionSettings.color], for changing the selected tile color. +/// * [TreemapSelectionSettings.border], for applying border color, width and +/// border radius to the selected tile. +class TreemapSelectionSettings { + /// Creates [TreemapSelectionSettings]. + /// + /// Customized the appearance of the tiles in selection state. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// onSelectionChanged: (TreemapTile tile) {}, + /// selectionSettings: const TreemapSelectionSettings(), + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [TreemapSelectionSettings.color], for changing the selected tile color. + /// * [TreemapSelectionSettings.border], for applying border color, width and + /// border radius to the selected tile. + const TreemapSelectionSettings({this.color, this.border}); + + /// Customizes the color of the selected tile. + /// + /// See also: + /// * [border], for applying border color and border radius to the + /// selected tile. + final Color? color; + + /// Apply border to the selected by initializing the [border] property. + /// + /// You can change the border color and border width of the selected tile + /// using the [RoundedRectangleBorder.size] property and border radius applied + /// using the [RoundedRectangleBorder.borderRadius] property. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// onSelectionChanged: (TreemapTile tile) {}, + /// selectionSettings: TreemapSelectionSettings( + /// border: RoundedRectangleBorder( + /// side: BorderSide( + /// color: Colors.red, + /// width: 2, + /// ), + /// borderRadius: BorderRadius.circular(20), + /// ), + /// ), + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [color], for changing the selected tile color. + final RoundedRectangleBorder? border; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is TreemapSelectionSettings && + other.color == color && + other.border == border; + } + + @override + int get hashCode => hashValues(color, border); +} + +/// Customizes the appearance of the tooltip. +/// +/// ```dart +/// List _socialMediaUsersData; +/// +/// @override +/// void initState() { +/// _socialMediaUsersData = [ +/// SocialMediaUsers('India', 'Facebook', 280), +/// SocialMediaUsers('India', 'Instagram', 88), +/// SocialMediaUsers('USA', 'Facebook', 190), +/// SocialMediaUsers('USA', 'Instagram', 120), +/// SocialMediaUsers('Japan', 'Twitter', 48), +/// SocialMediaUsers('Japan', 'Instagram', 31), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfTreemap( +/// dataCount: _socialMediaUsersData.length, +/// weightValueMapper: (int index) { +/// return _socialMediaUsersData[index].usersInMillions; +/// }, +/// tooltipSettings: TreemapTooltipSettings( +/// color: Colors.blue, +/// borderWidth: 2.0, +/// borderColor: Colors.white, +/// ), +/// levels: [ +/// TreemapLevel( +/// padding: EdgeInsets.all(2), +/// groupMapper: (int index) { +/// return _socialMediaUsersData[index].country; +/// }, +/// color: Colors.red, +/// tooltipBuilder: (BuildContext context, TreemapTile tile) { +/// return Padding( +/// padding: EdgeInsets.all(10.0), +/// child: Text( +/// 'Country ${tile.group}' + '\n Users ${tile.weight}'), +/// ); +/// }), +/// ], +/// ), +/// ); +/// } +/// +/// class SocialMediaUsers { +/// const SocialMediaUsers( +/// this.country, +/// this.socialMedia, +/// this.usersInMillions, +/// ); +/// final String country; +/// final String socialMedia; +/// final double usersInMillions; +/// } +/// ``` +/// +/// See also: +/// * [TreemapLevel.tooltipBuilder], to enable the tooltip. +class TreemapTooltipSettings { + /// Creates [TreemapTooltipSettings]. + /// + /// Customizes the appearance of the tooltip. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// tooltipSettings: TreemapTooltipSettings( + /// color: Colors.blue, + /// borderWidth: 2.0, + /// borderColor: Colors.white, + /// ), + /// levels: [ + /// TreemapLevel( + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// color: Colors.red, + /// tooltipBuilder: (BuildContext context, TreemapTile tile) { + /// return Padding( + /// padding: EdgeInsets.all(10.0), + /// child: Text( + /// 'Country ${tile.group}' + '\n Users ${tile.weight}'), + /// ); + /// }), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + const TreemapTooltipSettings({ + this.color, + this.borderColor, + this.borderWidth = 1.0, + this.borderRadius = const BorderRadius.all( + Radius.circular(4.0), + ), + }); + + /// Fills the tooltip by this color. + /// + /// See also: + /// * [borderColor], to set the border color. + /// * [borderWidth], to set the border width. + /// * [borderRadius], to set the border radius. + final Color? color; + + /// Specifies the border color applies to the tooltip. + /// + /// See also: + /// * [color], to set the fill color. + /// * [borderWidth], to set the border width. + /// * [borderRadius], to set the border radius. + final Color? borderColor; + + /// Specifies the border width applies to the tooltip. + /// + /// See also: + /// * [color], to set the fill color. + /// * [borderColor], to set the border color. + /// * [borderRadius], to set the border radius. + final double borderWidth; + + /// Specifies the border radius applies to the tooltip. + /// + /// See also: + /// * [color], to set the fill color. + /// * [borderColor], to set the border color. + /// * [borderWidth], to set the border width. + final BorderRadiusGeometry borderRadius; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is TreemapTooltipSettings && + other.color == color && + other.borderColor == borderColor && + other.borderWidth == borderWidth && + other.borderRadius == borderRadius; + } + + @override + int get hashCode => hashValues(color, borderColor, borderWidth, borderRadius); + + /// Creates a copy of this class but with the given fields + /// replaced with the new values. + TreemapTooltipSettings copyWith({ + Color? color, + Color? borderColor, + double? borderWidth, + BorderRadiusGeometry? borderRadius, + }) { + return TreemapTooltipSettings( + color: color ?? this.color, + borderColor: borderColor ?? this.borderColor, + borderWidth: borderWidth ?? this.borderWidth, + borderRadius: borderRadius ?? this.borderRadius); + } +} + +/// Collection of [TreemapColorMapper] which specifies tile’s color based on +/// the data. +/// +/// The [TreemapLevel.colorValueMapper] which will be called for each tiles in +/// the respective level, needs to return a color or value based on which +/// tiles color will be updated. +/// +/// If it returns a color, then this color will be applied to the tiles +/// straightaway. If it returns a value other than the color, then this value +/// will be compared with the [TreemapColorMapper.value] for the exact value +/// or [TreemapColorMapper.from] and [TreemapColorMapper.to] for the range of +/// values. Then the respective [TreemapColorMapper.color] will be applied to +/// that tile. +/// +/// The below code snippet represents how color can be applied to the shape +/// based on the [TreemapColorMapper.value] property of [TreemapColorMapper]. +/// +/// ```dart +/// List _socialMediaUsersData; +/// List _colorMappers; +/// +/// @override +/// void initState() { +/// _socialMediaUsersData = [ +/// SocialMediaUsers('India', 'Facebook', 280), +/// SocialMediaUsers('India', 'Instagram', 88), +/// SocialMediaUsers('USA', 'Facebook', 190), +/// SocialMediaUsers('USA', 'Instagram', 120), +/// SocialMediaUsers('Japan', 'Twitter', 48), +/// SocialMediaUsers('Japan', 'Instagram', 31), +/// ]; +/// _colorMappers = [ +/// TreemapColorMapper.value('India', Colors.green), +/// TreemapColorMapper.value(USA, Colors.blue), +/// TreemapColorMapper.value(Japan, Colors.red), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfTreemap( +/// dataCount: _socialMediaUsersData.length, +/// weightValueMapper: (int index) { +/// return _socialMediaUsersData[index].usersInMillions; +/// }, +/// colorMappers: _colorMappers, +/// levels: [ +/// TreemapLevel( +/// groupMapper: (int index) { +/// return _socialMediaUsersData[index].country; +/// }, +/// colorValueMapper: (TreemapTile tile) => tile.group, +/// ), +/// ], +/// ), +/// ); +/// } +/// +/// class SocialMediaUsers { +/// const SocialMediaUsers( +/// this.country, +/// this.socialMedia, +/// this.usersInMillions, +/// ); +/// final String country; +/// final String socialMedia; +/// final double usersInMillions; +/// } +/// ``` +/// +/// The below code snippet represents how color can be applied to the shape +/// based on the range between [TreemapColorMapper.from] and +/// [TreemapColorMapper.to] properties of [TreemapColorMapper]. +/// +/// ```dart +/// List _socialMediaUsersData; +/// List _colorMappers; +/// +/// @override +/// void initState() { +/// _socialMediaUsersData = [ +/// SocialMediaUsers('India', 'Facebook', 280), +/// SocialMediaUsers('India', 'Instagram', 88), +/// SocialMediaUsers('USA', 'Facebook', 190), +/// SocialMediaUsers('USA', 'Instagram', 120), +/// SocialMediaUsers('Japan', 'Twitter', 48), +/// SocialMediaUsers('Japan', 'Instagram', 31), +/// ]; +/// _colorMappers = [ +/// TreemapColorMapper.range(0, 100, Colors.green), +/// TreemapColorMapper.range(101, 200, Colors.blue), +/// TreemapColorMapper.range(201, 300, Colors.red), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfTreemap( +/// dataCount: _socialMediaUsersData.length, +/// weightValueMapper: (int index) { +/// return _socialMediaUsersData[index].usersInMillions; +/// }, +/// colorMappers: _colorMappers, +/// levels: [ +/// TreemapLevel( +/// groupMapper: (int index) { +/// return _socialMediaUsersData[index].country; +/// }, +/// colorValueMapper: (TreemapTile tile) => tile.weight, +/// ), +/// ], +/// ), +/// ); +/// } +/// +/// class SocialMediaUsers { +/// const SocialMediaUsers( +/// this.country, +/// this.socialMedia, +/// this.usersInMillions, +/// ); +/// final String country; +/// final String socialMedia; +/// final double usersInMillions; +/// } +/// ``` +/// See also: +/// * [SfTreemap.legend], to enable and customize the legend. +class TreemapColorMapper { + /// Applies color to the tiles which lies between the + /// [TreemapColorMapper.from] and [TreemapColorMapper.to] given range. The + /// [TreemapColorMapper.from] and [TreemapColorMapper.to] must not be null. + /// + /// Applies this [TreemapColorMapper.color] value to the tiles which are in + /// the given range. The legend item color and text are based on the + /// [TreemapColorMapper.name] value. + /// + /// ```dart + /// List _socialMediaUsersData; + /// List _colorMappers; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// _colorMappers = [ + /// TreemapColorMapper.range(0, 100, Colors.green), + /// TreemapColorMapper.range(101, 200, Colors.blue), + /// TreemapColorMapper.range(201, 300, Colors.red), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// colorMappers: _colorMappers, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// colorValueMapper: (TreemapTile tile) => tile.weight, + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [SfTreemap.legend], to enable and customize the legend. + const TreemapColorMapper.range( + {required this.from, required this.to, required this.color, this.name}) + : assert(from != null && to != null && from <= to), + value = null; + + /// Applies the color to the tiles which is equal to the given + /// [TreemapColorMapper.value]. The [TreemapColorMapper.value] must not be + /// null. + /// + /// Applies this [TreemapColorMapper.color] value to the tiles which are in + /// the given [TreemapColorMapper.value]. The legend item color and text are + /// based on the [TreemapColorMapper.color] and [TreemapColorMapper.value]. + /// + /// ```dart + /// List _socialMediaUsersData; + /// List _colorMappers; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// _colorMappers = [ + /// TreemapColorMapper.value('India', Colors.green), + /// TreemapColorMapper.value(USA, Colors.blue), + /// TreemapColorMapper.value(Japan, Colors.red), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// colorMappers: _colorMappers, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// colorValueMapper: (TreemapTile tile) => tile.group, + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [SfTreemap.legend], to enable and customize the legend. + const TreemapColorMapper.value({required this.value, required this.color}) + : assert(value != null), + from = null, + to = null, + name = null; + + /// Specifies the color applies to the tile based on the value returned in + /// the [TreemapLevel.colorValueMapper]. + /// + /// See also: + /// * [name], sets the identifier text for the color mapping. + final Color color; + + /// Sets the range start for the color mapping. + /// + /// The tile will render in the specified [color] if the value + /// returned in the [TreemapLevel.colorValueMapper] falls between the [from] + /// and [to] range. + /// + /// See also: + /// * [to], sets the end range for the color mapping. + final double? from; + + /// Sets the range end for the color mapping. + /// + /// The tile will render in the specified [color] if the value + /// returned in the [TreemapLevel.colorValueMapper] falls between the [from] + /// and [to] range. + /// + /// See also: + /// * [color], maps the color to the tiles based on the value returned + /// in [TreemapLevel.colorValueMapper]. + final double? to; + + /// Sets the value for the value color mapping. + /// + /// The tile would render in the specified [color] if the value returned in + /// the [TreemapLevel.colorValueMapper] is value to this [value]. + /// + /// See also: + /// * [color], maps the color to the tiles based on the value returned + /// in [TreemapLevel.colorValueMapper]. + final String? value; + + /// Sets the identifier text to the color mapping. The same will be used + /// to the legend item text and [color] will be used as legend item color. + /// + /// See also: + /// * [from], sets the start range for the color mapping. + /// * [to], sets the end range for the color mapping. + final String? name; +} + +/// A data visualization widget that provides an effective way to visualize +/// flat and hierarchical data as rectangles that are sized and colored based +/// on quantitative variables. +/// +/// Labels - Add any type of widgets (like text widget) to improve the +/// readability of the individual tiles by providing brief descriptions. +/// +/// Layouts - Use different layouts based on the algorithms such as squarified, +/// slice, and dice to represent flat and hierarchically structured data. +/// +/// Hierarchical support - Along with the flat level, treemap supports +/// hierarchical structure too. Each tile of the treemap is a rectangle which is +/// filled with smaller rectangles representing sub-data. +/// +/// Colors - Categorize the tiles on the treemap by customizing their color +/// based on the levels. It is possible to set the tile color for a specific +/// value or for a range of values. +/// +/// Tooltip - Display additional information about the tile using a completely +/// customizable tooltip on the treemap. +/// +/// Legend - Use different legend styles to provide information on the treemap +/// data clearly. +/// +/// Selection - Allows you to select the tiles to highlight it and do any +/// specific functionalities like showing pop-up or navigate to a different +/// page. +/// +/// Custom background widgets - Add any type of custom widgets such as image +/// widget as a background of the tiles to enrich the UI and easily visualize +/// the type of data that a particular tile shows. +/// +/// To populate the treemap with the data source, set the count of the data +/// source to the [dataCount] property of the treemap. The quantitative value of +/// the underlying data has to be returned from the [weightValueMapper] +/// callback. Based on this value, every tile (rectangle) will have its size. +/// +/// The data will be grouped based on the values returned from the +/// [TreemapLevel.groupMapper] callback from the each [TreemapLevel]. Each +/// unique value returned from the callback will have its own tile and its size +/// will be based on the value returned in the [weightValueMapper] for the same +/// index. If the same values returned for the multiple indices in +/// [TreemapLevel.groupMapper] callback, it will be grouped, and its size will +/// be the sum of values returned from [weightValueMapper] for those indices. +/// +/// You can have more than one [TreemapLevel] in the [levels] collection to form +/// a hierarchal treemap. The 0th index of the [levels] collection forms the +/// base level of the treemap. From the 1st index, the values returned in the +/// [TreemapLevel.groupMapper] callback will form as inner tiles of the tile +/// formed in the previous level for which the indices match. This hierarchy +/// will go on till the last [TreemapLevel] in the [levels] collection. +/// +/// ```dart +/// List _socialMediaUsersData; +/// List _colorMappers; +/// +/// @override +/// void initState() { +/// _socialMediaUsersData = [ +/// SocialMediaUsers('India', 'Facebook', 280), +/// SocialMediaUsers('India', 'Instagram', 88), +/// SocialMediaUsers('USA', 'Facebook', 190), +/// SocialMediaUsers('USA', 'Instagram', 120), +/// SocialMediaUsers('Japan', 'Twitter', 48), +/// SocialMediaUsers('Japan', 'Instagram', 31), +/// ]; +/// _colorMappers = [ +/// TreemapColorMapper.range(0, 100, Colors.green), +/// TreemapColorMapper.range(101, 200, Colors.blue), +/// TreemapColorMapper.range(201, 300, Colors.red), +/// ]; +/// super.initState(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfTreemap( +/// dataCount: _socialMediaUsersData.length, +/// weightValueMapper: (int index) { +/// return _socialMediaUsersData[index].usersInMillions; +/// }, +/// colorMappers: _colorMappers, +/// levels: [ +/// TreemapLevel( +/// color: Colors.red, +/// border: const RoundedRectangleBorder( +/// borderRadius: BorderRadius.all(Radius.circular(15)), +/// side: BorderSide(color: Colors.black, width: 2), +/// ), +/// padding: EdgeInsets.all(2), +/// groupMapper: (int index) { +/// return _socialMediaUsersData[index].country; +/// }, +/// colorValueMapper: (TreemapTile tile) => tile.weight, +/// tooltipBuilder: (BuildContext context, TreemapTile tile) { +/// return Text('Country: ${tile.group}\n Users: ${tile.weight}'); +/// }, +/// labelBuilder: (BuildContext context, TreemapTile tile) { +/// return Text('Country : ${tile.group}'); +/// }, +/// itemBuilder: (BuildContext context, TreemapTile tile) { +/// return Center(child: Icon(Icons.home, size: 15)); +/// }), +/// ], +/// ), +/// ); +/// } +/// +/// class SocialMediaUsers { +/// const SocialMediaUsers( +/// this.country, +/// this.socialMedia, +/// this.usersInMillions, +/// ); +/// final String country; +/// final String socialMedia; +/// final double usersInMillions; +/// } +/// ``` +/// +/// See also: +/// * [SfTreemap.slice], to render the tiles horizontally. +/// * [SfTreemap.dice], to render the tiles vertically. +/// * [TreemapLevel.tooltipBuilder], to enable tooltip. +/// * [SfTreemap.legend], to enable legend. +/// * [TreemapLevel.labelBuilder], to add a label for each tile. +class SfTreemap extends StatelessWidget { + /// Creates a treemap based on the squarified algorithm. + /// + /// To populate the treemap with the data source, set the count + /// of the data source to the [SfTreemap.dataCount] property of the treemap. + /// The quantitative value of the underlying data has to be returned + /// from the [SfTreemap.weightValueMapper] callback. Based on this value, + /// every tile (rectangle) will have its size. + /// + /// The data will be grouped based on the values returned from the + /// [TreemapLevel.groupMapper] callback from the each [TreemapLevel]. + /// Each unique value returned from the callback will have its own tile + /// and its size will be based on the value returned in the + /// [SfTreemap.weightValueMapper] for the same index. If the same values + /// returned for the multiple indices in [TreemapLevel.groupMapper] callback, + /// it will be grouped, and its size will be the sum of values returned from + /// [SfTreemap.weightValueMapper] for those indices. + /// + /// You can have more than one [TreemapLevel] in the [SfTreemap.levels] + /// collection to form a hierarchal treemap. The 0th index of the + /// [SfTreemap.levels] collection forms the base level of the treemap. From + /// the 1st index, the values returned in the [TreemapLevel.groupMapper] + /// callback will form as inner tiles of the tile formed in the previous level + /// for which the indices match. This hierarchy will go on till the last + /// [TreemapLevel] in the [SfTreemap.levels] collection. + /// + /// ```dart + /// List _socialMediaUsersData; + /// List _colorMappers; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// _colorMappers = [ + /// TreemapColorMapper.range(0, 100, Colors.green), + /// TreemapColorMapper.range(101, 200, Colors.blue), + /// TreemapColorMapper.range(201, 300, Colors.red), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// colorMappers: _colorMappers, + /// levels: [ + /// TreemapLevel( + /// color: Colors.red, + /// border: const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all(Radius.circular(15)), + /// side: BorderSide(color: Colors.black, width: 2), + /// ), + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// colorValueMapper: (TreemapTile tile) => tile.weight, + /// tooltipBuilder: (BuildContext context, TreemapTile tile) { + /// return Text('Country: ${tile.group}\n Users: ${tile.weight}'); + /// }, + /// labelBuilder: (BuildContext context, TreemapTile tile) { + /// return Text('Country : ${tile.group}'); + /// }, + /// itemBuilder: (BuildContext context, TreemapTile tile) { + /// return Center(child: Icon(Icons.home, size: 15)); + /// }), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [SfTreemap.slice], to render the tiles horizontally. + /// * [SfTreemap.dice], to render the tiles vertically. + const SfTreemap({ + Key? key, + required this.dataCount, + required this.levels, + required this.weightValueMapper, + this.colorMappers, + this.legend, + this.onSelectionChanged, + this.selectionSettings = const TreemapSelectionSettings(), + this.tooltipSettings = const TreemapTooltipSettings(), + }) : assert(dataCount > 0), + assert(levels.length > 0), + assert(colorMappers == null || colorMappers.length > 0), + _layoutType = LayoutType.squarified, + super(key: key); + + /// Creates a treemap based on the slice algorithm. + /// + /// To populate the treemap with the data source, set the count of the data + /// source to the [SfTreemap.dataCount] property of the treemap. The + /// quantitative value of the underlying data has to be returned from the + /// [SfTreemap.weightValueMapper] callback. Based on this value, every tile + /// (rectangle) will have its size. + /// + /// The data will be grouped based on the values returned from the + /// [TreemapLevel.groupMapper] callback from the each [TreemapLevel]. Each + /// unique value returned from the callback will have its own tile and its + /// size will be based on the value returned in the + /// [SfTreemap.weightValueMapper] for the same index. If the same values + /// returned for the multiple indices in [TreemapLevel.groupMapper] callback, + /// it will be grouped, and its size will be the sum of values returned from + /// [SfTreemap.weightValueMapper] for those + /// indices. + /// + /// You can have more than one [TreemapLevel] in the [SfTreemap.levels] + /// collection to form a hierarchal treemap. The 0th index of the + /// [SfTreemap.levels] collection forms the base level of the treemap. From + /// the 1st index, the values returned in the [TreemapLevel.groupMapper] + /// callback will form as inner tiles of the tile formed in the previous level + /// for which the indices match. This hierarchy will go on till the last + /// [TreemapLevel] in the [SfTreemap.levels] collection. + /// + /// ```dart + /// List _socialMediaUsersData; + /// List _colorMappers; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// _colorMappers = [ + /// TreemapColorMapper.range(0, 100, Colors.green), + /// TreemapColorMapper.range(101, 200, Colors.blue), + /// TreemapColorMapper.range(201, 300, Colors.red), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap.slice( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// colorMappers: _colorMappers, + /// levels: [ + /// TreemapLevel( + /// color: Colors.red, + /// border: const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all(Radius.circular(15)), + /// side: BorderSide(color: Colors.black, width: 2), + /// ), + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// colorValueMapper: (TreemapTile tile) => tile.weight, + /// tooltipBuilder: (BuildContext context, TreemapTile tile) { + /// return Text('Country: ${tile.group}\n Users: ${tile.weight}'); + /// }, + /// labelBuilder: (BuildContext context, TreemapTile tile) { + /// return Text('Country : ${tile.group}'); + /// }, + /// itemBuilder: (BuildContext context, TreemapTile tile) { + /// return Center(child: Icon(Icons.home, size: 15)); + /// }), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [SfTreemap], to render based on squarified algorithm. + /// * [SfTreemap.dice], to render the tiles vertically. + const SfTreemap.slice({ + Key? key, + required this.dataCount, + required this.levels, + required this.weightValueMapper, + this.colorMappers, + this.legend, + this.onSelectionChanged, + this.selectionSettings = const TreemapSelectionSettings(), + this.tooltipSettings = const TreemapTooltipSettings(), + }) : assert(dataCount > 0), + assert(levels.length > 0), + assert(colorMappers == null || colorMappers.length > 0), + _layoutType = LayoutType.slice, + super(key: key); + + /// Creates a treemap based on the dice algorithm. + /// + /// To populate the treemap with the data source, set the count of the data + /// source to the [SfTreemap.dataCount] property of the treemap. The + /// quantitative value of the underlying data has to be returned from the + /// [SfTreemap.weightValueMapper] callback. Based on this value, every tile + /// (rectangle) will have its size. + /// + /// The data will be grouped based on the values returned from the + /// [TreemapLevel.groupMapper] callback from the each [TreemapLevel]. Each + /// unique value returned from the callback will have its own tile and its + /// size will be based on the value returned in the + /// [SfTreemap.weightValueMapper] for the same index. If the same values + /// returned for the multiple indices in [TreemapLevel.groupMapper] callback, + /// it will be grouped, and its size will be the sum of values returned from + /// [SfTreemap.weightValueMapper] for those indices. + /// + /// You can have more than one [TreemapLevel] in the [SfTreemap.levels] + /// collection to form a hierarchal treemap. The 0th index of the + /// [SfTreemap.levels] collection forms the base level of the treemap. From + /// the 1st index, the values returned in the [TreemapLevel.groupMapper] + /// callback will form as inner tiles of the tile formed in the previous level + /// for which the indices match. This hierarchy will go on till the last + /// [TreemapLevel] in the [SfTreemap.levels] collection. + /// + /// ```dart + /// List _socialMediaUsersData; + /// List _colorMappers; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// _colorMappers = [ + /// TreemapColorMapper.range(0, 100, Colors.green), + /// TreemapColorMapper.range(101, 200, Colors.blue), + /// TreemapColorMapper.range(201, 300, Colors.red), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap.dice( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// colorMappers: _colorMappers, + /// levels: [ + /// TreemapLevel( + /// color: Colors.red, + /// border: const RoundedRectangleBorder( + /// borderRadius: BorderRadius.all(Radius.circular(15)), + /// side: BorderSide(color: Colors.black, width: 2), + /// ), + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// colorValueMapper: (TreemapTile tile) => tile.weight, + /// tooltipBuilder: (BuildContext context, TreemapTile tile) { + /// return Text('Country: ${tile.group}\n Users: ${tile.weight}'); + /// }, + /// labelBuilder: (BuildContext context, TreemapTile tile) { + /// return Text('Country : ${tile.group}'); + /// }, + /// itemBuilder: (BuildContext context, TreemapTile tile) { + /// return Center(child: Icon(Icons.home, size: 15)); + /// }), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [SfTreemap], to render based on squarified algorithm. + /// * [SfTreemap.slice], to render the tiles horizontally. + const SfTreemap.dice({ + Key? key, + required this.dataCount, + required this.levels, + required this.weightValueMapper, + this.colorMappers, + this.legend, + this.onSelectionChanged, + this.selectionSettings = const TreemapSelectionSettings(), + this.tooltipSettings = const TreemapTooltipSettings(), + }) : assert(dataCount > 0), + assert(levels.length > 0), + assert(colorMappers == null || colorMappers.length > 0), + _layoutType = LayoutType.dice, + super(key: key); + + /// Specifies the length of the data source. + /// + /// To populate the treemap with the data source, set the count of the data + /// source to the [dataCount] property of the treemap. The [weightValueMapper] + /// and [TreemapLevel.groupMapper] will be called number of times equal to the + /// [dataCount] to determine the number of tiles and the size of the tiles. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// color: Colors.red, + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// See also: + /// * [SfTreemap], to know how treemap render the tiles. + final int dataCount; + + /// Returns the values which determines the weight of each tile. + /// + /// The quantitative value of the underlying data has to be returned from the + /// [weightValueMapper] callback. Based on this value, every tile (rectangle) + /// will have its size. + /// + /// The data will be grouped based on the values returned from the + /// [TreemapLevel.groupMapper] callback from the each [TreemapLevel]. Each + /// unique value returned from the callback will have its own tile and its + /// size will be based on the value returned in the [weightValueMapper] for + /// the same index. If the same values returned for the multiple indices in + /// [TreemapLevel.groupMapper] callback, it will be grouped, and its size will + /// be the sum of values returned from [weightValueMapper] for those indices. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// color: Colors.red, + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [SfTreemap], to know how treemap render the tiles. + final IndexedDoubleValueMapper weightValueMapper; + + /// The levels collection which forms either flat or hierarchal treemap. + /// + /// You can have more than one [TreemapLevel] in this collection to form a + /// hierarchal treemap. The 0th index of the [levels] collection forms the + /// base level of the treemap or flat treemap. From the 1st index, the values + /// returned in the [TreemapLevel.groupMapper] callback will form as inner + /// tiles of the tile formed in the previous level for which the indices + /// match.This hierarchy will go on till the last TreemapLevel in the [levels] + /// collection. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// color: Colors.red, + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [SfTreemap], to know how treemap render the tiles. + final List levels; + + /// Customizes the appearance of the tooltip. + /// + /// ```dart + /// List _socialMediaUsersData; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// tooltipSettings: TreemapTooltipSettings( + /// color: Colors.blue, + /// borderWidth: 2.0, + /// borderColor: Colors.white, + /// ), + /// levels: [ + /// TreemapLevel( + /// padding: EdgeInsets.all(2), + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// color: Colors.red, + /// tooltipBuilder: (BuildContext context, TreemapTile tile) { + /// return Padding( + /// padding: EdgeInsets.all(10.0), + /// child: Text( + /// 'Country ${tile.group}' + '\n Users ${tile.weight}'), + /// ); + /// }), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [TreemapLevel.tooltipBuilder], to enable the tooltip. + final TreemapTooltipSettings tooltipSettings; + + /// Collection of [TreemapColorMapper] which specifies tile’s color based on + /// the data. + /// + /// The [TreemapLevel.colorValueMapper] which will be called for each tiles in + /// the respective level, needs to return a color or value based on which + /// tiles color will be updated. + /// + /// If it returns a color, then this color will be applied to the tiles + /// straightaway. If it returns a value other than the color, then this value + /// will be compared with the [TreemapColorMapper.value] for the exact value + /// or [TreemapColorMapper.from] and [TreemapColorMapper.to] for the range of + /// values. Then the respective [TreemapColorMapper.color] will be applied to + /// that tile. + /// + /// The below code snippet represents how color can be applied to the shape + /// based on the [TreemapColorMapper.value] property of [TreemapColorMapper]. + /// + /// ```dart + /// List _socialMediaUsersData; + /// List _colorMappers; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// _colorMappers = [ + /// TreemapColorMapper.value('India', Colors.green), + /// TreemapColorMapper.value(USA, Colors.blue), + /// TreemapColorMapper.value(Japan, Colors.red), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// colorMappers: _colorMappers, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// colorValueMapper: (TreemapTile tile) => tile.group, + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// The below code snippet represents how color can be applied to the shape + /// based on the range between [TreemapColorMapper.from] and + /// [TreemapColorMapper.to]properties of [TreemapColorMapper]. + /// + /// ```dart + /// List _socialMediaUsersData; + /// List _colorMappers; + /// + /// @override + /// void initState() { + /// _socialMediaUsersData = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// _colorMappers = [ + /// TreemapColorMapper.range(0, 100, Colors.green), + /// TreemapColorMapper.range(101, 200, Colors.blue), + /// TreemapColorMapper.range(201, 300, Colors.red), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _socialMediaUsersData.length, + /// weightValueMapper: (int index) { + /// return _socialMediaUsersData[index].usersInMillions; + /// }, + /// colorMappers: _colorMappers, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _socialMediaUsersData[index].country; + /// }, + /// colorValueMapper: (TreemapTile tile) => tile.weight, + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// See also: + /// * [legend], to enable and customize the legend. + final List? colorMappers; + + /// Called when the user selected the tile. + /// + /// * tile - contains information about the treemap tile. + /// + /// To do any specific functionalities like showing pop-up or navigate to a + /// different page, use the [onSelectionChanged] callback. + /// + /// See also: + /// * [TreemapTile], contains information about the treemap tile. + final ValueChanged? onSelectionChanged; + + /// Customized the appearance of the tiles in selection state. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// onSelectionChanged: (TreemapTile tile) {}, + /// selectionSettings: const TreemapSelectionSettings(), + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * [TreemapSelectionSettings.color], for changing the selected tile color. + /// * [TreemapSelectionSettings.border], for applying border color, width and + /// border radius to the selected tile. + final TreemapSelectionSettings selectionSettings; + + /// Specifies the type of the treemap. + final LayoutType _layoutType; + + /// Shows legend for the data rendered in the treemap. + /// + /// Defaults to `null`. + /// + /// By default, legend will not be shown. + /// + /// If [SfTreemap.colorMappers] is null, then the legend items' icon color and + /// legend item's text will be applied based on the value of + /// [TreemapLevel.color] and the values returned from the + /// [TreemapLevel.groupMapper] callback of first [TreemapLevel] added in the + /// [levels] collection. + /// + /// If [SfTreemap.colorMappers] is not null and TreemapColorMapper.value() + /// constructor is used, the legend item's icon color will be applied based on + /// the [TreemapColorMapper.color] property and the legend text applied based + /// on the [TreemapColorMapper.value] property. + /// + /// And, when using [TreemapColorMapper.range] constructor, the legend item's + /// icon color will be applied based on the [TreemapColorMapper.color] + /// property and the legend text will be applied based on the + /// [TreemapColorMapper.name] property. If the + /// [TreemapColorMapper.name] property is null, then the text will + /// be applied based on the [TreemapColorMapper.from] and + /// [TreemapColorMapper.to] properties + /// + /// The below code snippet represents how to setting default legend + /// to the tree map. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend(), + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// The below code snippet represents how to setting bar legend + /// to the tree map. + /// + /// ```dart + /// List _source; + /// + /// @override + /// void initState() { + /// _source = [ + /// SocialMediaUsers('India', 'Facebook', 280), + /// SocialMediaUsers('India', 'Instagram', 88), + /// SocialMediaUsers('USA', 'Facebook', 190), + /// SocialMediaUsers('USA', 'Instagram', 120), + /// SocialMediaUsers('Japan', 'Twitter', 48), + /// SocialMediaUsers('Japan', 'Instagram', 31), + /// ]; + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfTreemap( + /// dataCount: _source.length, + /// weightValueMapper: (int index) { + /// return _source[index].usersInMillions; + /// }, + /// levels: [ + /// TreemapLevel( + /// groupMapper: (int index) { + /// return _source[index].country; + /// }, + /// ), + /// ], + /// legend: TreemapLegend.bar(), + /// ), + /// ); + /// } + /// + /// class SocialMediaUsers { + /// const SocialMediaUsers( + /// this.country, + /// this.socialMedia, + /// this.usersInMillions, + /// ); + /// final String country; + /// final String socialMedia; + /// final double usersInMillions; + /// } + /// ``` + /// + /// See also: + /// * To render bar legend, refer [TreemapLegend.bar] constructor. + final TreemapLegend? legend; + + @override + Widget build(BuildContext context) { + return Treemap( + layoutType: _layoutType, + dataCount: dataCount, + levels: levels, + weightValueMapper: weightValueMapper, + colorMappers: colorMappers, + legend: legend, + onSelectionChanged: onSelectionChanged, + selectionSettings: selectionSettings, + tooltipSettings: tooltipSettings, + ); + } +} diff --git a/packages/syncfusion_flutter_treemap/pubspec.yaml b/packages/syncfusion_flutter_treemap/pubspec.yaml new file mode 100644 index 000000000..5e7455f02 --- /dev/null +++ b/packages/syncfusion_flutter_treemap/pubspec.yaml @@ -0,0 +1,14 @@ +name: syncfusion_flutter_treemap +description: A Flutter Treemap library for creating interactive treemap to visualize flat and hierarchical data based on squarified, slice, and dice algorithms. +version: 19.1.54-beta +homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_treemap + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + syncfusion_flutter_core: + path: ../syncfusion_flutter_core diff --git a/packages/syncfusion_flutter_treemap/syncfusion_flutter_treemap.iml b/packages/syncfusion_flutter_treemap/syncfusion_flutter_treemap.iml new file mode 100644 index 000000000..6048a33bd --- /dev/null +++ b/packages/syncfusion_flutter_treemap/syncfusion_flutter_treemap.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/syncfusion_flutter_xlsio/README.md b/packages/syncfusion_flutter_xlsio/README.md index 53c795028..baecd4a84 100644 --- a/packages/syncfusion_flutter_xlsio/README.md +++ b/packages/syncfusion_flutter_xlsio/README.md @@ -1,4 +1,4 @@ -![syncfusion_flutter_xlsio_banner](https://cdn.syncfusion.com/content/images/FTControl/Flutter-XlsIO-Banner.png) +![syncfusion_flutter_xlsio_banner](https://cdn.syncfusion.com/content/images/FTControl/Flutter-XlsIO-Banner.png) # Syncfusion Flutter XlsIO @@ -8,7 +8,7 @@ Syncfusion Flutter XlsIO is a feature rich and high-performance non-UI Excel lib The Excel package is a non-UI and reusable Flutter library to create Excel documents programmatically with cell values, built-in styles, cell formatting, formulas, charts, and images. The creation of Excel file are in XLSX (Excel 2007 and above) format. -**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion Commercial License or Syncfusion Community license. For more details, please check the [LICENSE](LICENSE) file. +**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion Commercial License or [Free Syncfusion Community license](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. **Note:** Our packages are now compatible with Flutter for Web. However, this will be in Beta until Flutter for Web becomes stable. @@ -27,8 +27,10 @@ The Excel package is a non-UI and reusable Flutter library to create Excel docum - [Add images](#add-images) - [Add charts](#add-charts) - [Add hyperlinks](#add-hyperlinks) - - [Manipulate rows and Columns](#manipulate-rows-and-columns) - - [Protect workbook and worksheets](#Protect-workbook-and-worksheets) + - [Manipulate rows and columns](#manipulate-rows-and-columns) + - [Protect workbook and worksheets](#protect-workbook-and-worksheets) + - [Import Data](#import-data) + - [Apply conditional formatting](#apply-conditional-formatting) - [Support and feedback](#support-and-feedback) - [About Syncfusion](#about-syncfusion) @@ -44,6 +46,8 @@ The following are the key features of Syncfusion Flutter XlsIO. * Add hyperlinks to Excel worksheet * Manipulate rows and columns of Excel worksheet * Add protection to Excel document. +* Import data list to Excel Worksheet. +* Apply Excel conditional formatting. ## Get the demo application @@ -62,8 +66,9 @@ Explore the full capability of our Flutter widgets on your device by installing Take a look at the following to learn more about Syncfusion Flutter XlsIO: -* [User guide documentation]() -* [Knowledge base](https://www.syncfusion.com/kb) +* [Syncfusion Flutter Excel product page](https://www.syncfusion.com/flutter-widgets/excel-library) +* [User guide documentation](https://help.syncfusion.com/flutter/xlsio/overview) +* [Knowledge base](https://www.syncfusion.com/kb/flutter/xlslo) ## Installation @@ -88,7 +93,7 @@ final Workbook workbook = new Workbook(); //Accessing worksheet via index. workbook.worksheets[0]; // Save the document. -List bytes = workbook.saveAsStream(); +final List bytes = workbook.saveAsStream(); File('CreateExcel.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -111,7 +116,7 @@ sheet.getRangeByName('A3').setNumber(44); //Add DateTime sheet.getRangeByName('A5').setDateTime(DateTime(2020,12,12,1,10,20)); // Save the document. -List bytes = workbook.saveAsStream(); +final List bytes = workbook.saveAsStream(); File('AddingTextNumberDateTime.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -138,7 +143,7 @@ sheet.enableSheetCalculations(); sheet.getRangeByName('A3').setFormula('=A1+A2'); // Save the document. -List bytes = workbook.saveAsStream(); +final List bytes = workbook.saveAsStream(); File('AddingFormula.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -156,30 +161,62 @@ Use the following code to add and apply global style to the Excel worksheet cell ```dart // Create a new Excel document. final Workbook workbook = new Workbook(); + //Accessing worksheet via index. final Worksheet sheet = workbook.worksheets[0]; //Defining a global style with all properties. -final Style globalStyle = workbook.styles.add('style'); +Style globalStyle = workbook.styles.add('style'); +//set back color by hexa decimal. globalStyle.backColor = '#37D8E9'; +//set font name. globalStyle.fontName = 'Times New Roman'; +//set font size. globalStyle.fontSize = 20; +//set font color by hexa decimal. globalStyle.fontColor = '#C67878'; +//set font italic. globalStyle.italic = true; +//set font bold. globalStyle.bold = true; +//set font underline. globalStyle.underline = true; +//set wraper text. globalStyle.wrapText = true; +//set indent value. +globalStyle.indent = 1; +//set horizontal alignment type. globalStyle.hAlign = HAlignType.left; +//set vertical alignment type. globalStyle.vAlign = VAlignType.bottom; +//set text rotation. globalStyle.rotation = 90; -globalStyle.borders.all.lineStyle = LineStyle.Thick; +//set all border line style. +globalStyle.borders.all.lineStyle = LineStyle.thick; +//set border color by hexa decimal. globalStyle.borders.all.color = '#9954CC'; -globalStyle.setNumberFormat = '_(\$* #,##0_)';; +//set number format. +globalStyle.numberFormat = '_(\$* #,##0_)'; -//Apply GlobalStyle +//Apply GlobalStyle to 'A1'. sheet.getRangeByName('A1').cellStyle = globalStyle; + +//Defining Gloabl style. +globalStyle = workbook.styles.add('style1'); +//set back color by RGB value. +globalStyle.backColorRgb = Color.fromARGB(245, 22, 44, 144); +//set font color by RGB value. +globalStyle.fontColorRgb = Color.fromARGB(255, 244, 22, 44); +//set border line style. +globalStyle.borders.all.lineStyle = LineStyle.double; +//set border color by RGB value. +globalStyle.borders.all.colorRgb = Color.fromARGB(255, 44, 200, 44); + +//Apply GlobalStyle to 'A4'; +sheet.getRangeByName('A4').cellStyle = globalStyle; + // Save the document. -List bytes = workbook.saveAsStream(); +final List bytes = workbook.saveAsStream(); File('ApplyGlobalStyle.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -200,7 +237,7 @@ final Worksheet sheet = workbook.worksheets[0]; sheet.getRangeByName('A1').builtInStyle = BuiltInStyles.linkedCell; // Save the document. -List bytes = workbook.saveAsStream(); +final List bytes = workbook.saveAsStream(); File('ApplyBuildInStyle.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -223,14 +260,13 @@ range.setNumber(100); range.numberFormat = '\S#,##0.00'; // Save the document. -List bytes = workbook.saveAsStream(); +final List bytes = workbook.saveAsStream(); File('ApplyNumberFormat.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); ``` - ### Add images Syncfusion Flutter XlsIO supports only PNG and JPEG images. Refer to the following code to add images to Excel worksheet. @@ -246,7 +282,7 @@ final List bytes = File('image.png').readAsBytesSync(); final Picture picture = sheet.picutes.addStream(1, 1, bytes); // Save the document. -List bytes = workbook.saveAsStream(); +final List bytes = workbook.saveAsStream(); File('AddingImage.xlsx').writeAsBytes(bytes); //Dispose the workbook. workbook.dispose(); @@ -266,11 +302,11 @@ Use the following code to add charts to Excel worksheet. ```dart // Create a new Excel document. -final Workbook workbook = new Workbook(); -//Accessing worksheet via index. +final Workbook workbook = Workbook(); +// Accessing worksheet via index. final Worksheet sheet = workbook.worksheets[0]; -//Setting value in the cell. +// Setting value in the cell. sheet.getRangeByName('A1').setText('John'); sheet.getRangeByName('A2').setText('Amy'); sheet.getRangeByName('A3').setText('Jack'); @@ -283,20 +319,22 @@ sheet.getRangeByName('B4').setNumber(21); // Create an instances of chart collection. final ChartCollection charts = ChartCollection(sheet); -// Add a chart to the collection. +// Add the chart. final Chart chart = charts.add(); -//Set Chart Type. +// Set Chart Type. chart.chartType = ExcelChartType.column; -//Set data range in the worksheet. +// Set data range in the worksheet. chart.dataRange = sheet.getRangeByName('A1:B4'); -// Save the document. -List bytes = workbook.saveAsStream(); -File('ExcelCharts.xlsx').writeAsBytes(bytes); -//Dispose the workbook. +// set charts to worksheet. +sheet.charts = charts; + +// save and dispose the workbook. +final List bytes = workbook.saveAsStream(); workbook.dispose(); +File('Chart.xlsx').writeAsBytes(bytes); ``` @@ -450,9 +488,9 @@ workbook.dispose(); This section covers the various protection options in the Excel document. -**Protect Workbook** +**Protect workbook elements** -Use the following code to protect workbook of Excel document. +Use the following code to protect workbook element of Excel document. ```dart // Create a new Excel Document. @@ -478,7 +516,7 @@ workbook.dispose(); ``` -**Protect Worksheets** +**Protect worksheet** Use the following code to protect worksheets in the Excel document. @@ -505,6 +543,93 @@ final List bytes = workbook.saveAsStream(); File('WorksheetProtect.xlsx').writeAsBytes(bytes); workbook.dispose(); +``` +### Import data + +Use the following code to import list of data into Excel Worksheet. + +```dart +// Create a new Excel Document. +final Workbook workbook = Workbook(); + +// Accessing sheet via index. +final Worksheet sheet = workbook.worksheets[0]; + +//Initialize the list +final List list = [ + 'Toatal Income', + 20000, + 'On Date', + DateTime(2021, 1, 1) +]; + +//Import the Object list to Sheet +sheet.importList(list, 1, 1, true); + +// Save and dispose workbook. +final List bytes = workbook.saveAsStream(); +File('ImportDataList.xlsx').writeAsBytes(bytes); +workbook.dispose(); + +``` +### Apply conditional formatting + +Use the following code to add and apply conditional formatting to cell or range in Excel Worksheet. + +```dart +// Create a new Excel Document. +final Workbook workbook = Workbook(); + +// Accessing sheet via index. +final Worksheet sheet = workbook.worksheets[0]; + +//Applying conditional formatting to "A2". +final ConditionalFormats conditions = + sheet.getRangeByName('A2').conditionalFormats; +final ConditionalFormat condition = conditions.addCondition(); + +//Represents conditional format rule that the value in target range should be between 10 and 20 +condition.formatType = ExcelCFType.cellValue; +condition.operator = ExcelComparisonOperator.between; +condition.firstFormula = '10'; +condition.secondFormula = '20'; +sheet.getRangeByIndex(2, 1).setText('Enter a number between 10 and 20'); + +//Setting format properties to be applied when the above condition is met. +//set back color by hexa decimal. +condition.backColor = '#00FFCC'; +//set font color by RGB values. +condition.fontColorRgb = Color.fromARGB(255, 200, 20, 100); +//set font bold. +condition.isBold = true; +//set font italic. +condition.isItalic = true; +//set number format. +condition.numberFormat = '0.0'; +//set font underline. +condition.underline = true; +//set top border line style +condition.topBorderStyle = LineStyle.thick; +// set top border color by RGB values. +condition.topBorderColorRgb = Color.fromARGB(255, 200, 1, 200); +//set bottom border line style. +condition.bottomBorderStyle = LineStyle.medium; +//set bottom border color by hexa decimal. +condition.bottomBorderColor = '#FF0000'; +//set right border line style. +condition.rightBorderStyle = LineStyle.double; +// set right border color by RGB values. +condition.rightBorderColorRgb = Color.fromARGB(250, 24, 160, 200); +//set left border line style. +condition.leftBorderStyle = LineStyle.thin; +//set left border color by hexa decimal. +condition.leftBorderColor = '#AAFFAA'; + +//save and dispose. +final List bytes = workbook.saveAsStream(); +File('ConditionalFormatting.xlsx').writeAsBytes(bytes); +workbook.dispose(); + ``` ## Support and feedback diff --git a/packages/syncfusion_flutter_xlsio/example/lib/main.dart b/packages/syncfusion_flutter_xlsio/example/lib/main.dart index 2abbc4470..9b677b200 100644 --- a/packages/syncfusion_flutter_xlsio/example/lib/main.dart +++ b/packages/syncfusion_flutter_xlsio/example/lib/main.dart @@ -21,7 +21,8 @@ class CreateExcelWidget extends StatelessWidget { /// Represents the XlsIO stateful widget class. class CreateExcelStatefulWidget extends StatefulWidget { /// Initalize the instance of the [CreateExcelStatefulWidget] class. - const CreateExcelStatefulWidget({Key key, this.title}) : super(key: key); + const CreateExcelStatefulWidget({Key? key, required this.title}) + : super(key: key); /// title. final String title; @@ -40,12 +41,13 @@ class _CreateExcelState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - FlatButton( - child: const Text( - 'Generate Excel', - style: TextStyle(color: Colors.white), + TextButton( + child: const Text('Generate Excel'), + style: TextButton.styleFrom( + primary: Colors.white, + backgroundColor: Colors.lightBlue, + onSurface: Colors.grey, ), - color: Colors.blue, onPressed: generateExcel, ) ], @@ -214,7 +216,7 @@ class _CreateExcelState extends State { range9.cellStyle.vAlign = VAlignType.center; //Save and launch the excel. - final List bytes = workbook.saveAsStream(); + final List? bytes = workbook.saveAsStream(); //Dispose the document. workbook.dispose(); @@ -223,7 +225,7 @@ class _CreateExcelState extends State { await path_provider.getApplicationDocumentsDirectory(); final String path = directory.path; final File file = File('$path/output.xlsx'); - await file.writeAsBytes(bytes); + await file.writeAsBytes(bytes!); //Launch the file (used open_file package) await open_file.OpenFile.open('$path/output.xlsx'); diff --git a/packages/syncfusion_flutter_xlsio/example/pubspec.yaml b/packages/syncfusion_flutter_xlsio/example/pubspec.yaml index 60f0fce58..494339b5c 100644 --- a/packages/syncfusion_flutter_xlsio/example/pubspec.yaml +++ b/packages/syncfusion_flutter_xlsio/example/pubspec.yaml @@ -1,10 +1,13 @@ name: xlsio_example description: Demo for creating a Excel file using syncfusion_flutter_xlsio package. +environment: + sdk: ">=2.12.0 <3.0.0" + dependencies: flutter: sdk: flutter - path_provider: ^1.6.7 + path_provider: ^2.0.1 open_file: ^3.0.1 syncfusion_flutter_xlsio: path: ../ diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/calc_engine.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/calc_engine.dart index 2ebdb9750..2cfc35c7a 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/calc_engine.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/calc_engine.dart @@ -24,9 +24,9 @@ class CalcEngine { _dateTime1900Double = Range._toOADate(_dateTime1900); } - static SheetFamilyItem _defaultFamilyItem; - static Map _modelToSheetID; - static Map _sheetFamiliesList; + static SheetFamilyItem? _defaultFamilyItem; + static Map? _modelToSheetID; + static Map? _sheetFamiliesList; static int _sheetFamilyID = 0; /// A static property that gets/sets character to be recognized by the parsing code as the delimiter for arguments in a named formula's argument list. @@ -49,8 +49,8 @@ class CalcEngine { bool excelLikeComputations = false; final _currentRowNotationEnabled = true; static int _tokenCount = 0; - Worksheet _grid; - List _sortedSheetNames; + Worksheet? _grid; + List? _sortedSheetNames; static const String _sheetToken = '!'; static const int _intMaxValue = 2147483647; static const int _intMinValue = -2147483648; @@ -135,15 +135,16 @@ class CalcEngine { bool _isRangeOperand = false; bool _multiTick = false; bool _isInteriorFunction = false; - double _dateTime1900Double; + final _dateTime1900 = DateTime(1900, 1, 1, 0, 0, 0); + late double _dateTime1900Double; /// This field holds equivalent double value of 1904(DateTime). static const double _oADate1904 = 1462.0; /// Set the boolean as true static final _treat1900AsLeapYear = true; - List _tokens; + List _tokens = []; ///used to force refreshing calculations int _calcID = 0; @@ -174,7 +175,7 @@ class CalcEngine { final int _columnMaxCount = -1; // ignore: unused_field int _hitCount = 0; - Map _libraryFunctions; + Map? _libraryFunctions; final String _validFunctionNameChars = '_'; final bool _getValueFromArgPreserveLeadingZeros = false; bool _ignoreCellValue = false; @@ -183,6 +184,9 @@ class CalcEngine { bool _exteriorFormula = false; bool _isIndexInteriorFormula = false; bool _isErrorString = false; + // default value is false.This proerty set as true when the cell is empty. + // ignore: prefer_final_fields + bool _matchType = false; /// This field will be set as true, if the 1904 date system is enabled in Excel. final bool _useDate1904 = false; @@ -240,14 +244,14 @@ class CalcEngine { return _formulaChar; } - List get _sortedSheetNamesList { - final SheetFamilyItem family = _getSheetFamilyItem(_grid); + List? get _sortedSheetNamesList { + final SheetFamilyItem? family = _getSheetFamilyItem(_grid); if (_sortedSheetNames == null) { if (family != null && family._sheetNameToToken != null) { - final List names = family._sheetNameToToken.keys.toList(); + final List names = family._sheetNameToToken!.keys.toList(); _sortedSheetNames = names.map((s) => s as String).toList(); - _sortedSheetNames.sort(); + _sortedSheetNames!.sort(); } } return _sortedSheetNames; @@ -257,15 +261,15 @@ class CalcEngine { ////several sheets. If so, then dependent cells are tracked through a static member ////so that they are known across instances. bool get _isSheeted { - final SheetFamilyItem family = _getSheetFamilyItem(_grid); + final SheetFamilyItem? family = _getSheetFamilyItem(_grid); return (family == null) ? false : family._isSheeted; } /// A read-only property that gets a mapping between a formula cell and a list of cells upon which it depends. - Map get _dependentFormulaCells { + Map? get _dependentFormulaCells { if (_isSheeted) { - final SheetFamilyItem family = _getSheetFamilyItem(_grid); - family._sheetDependentFormulaCells ??= {}; + final SheetFamilyItem? family = _getSheetFamilyItem(_grid); + family!._sheetDependentFormulaCells ??= {}; return family._sheetDependentFormulaCells; } @@ -273,10 +277,10 @@ class CalcEngine { } /// A read-only property that gets the collection of FormulaInfo Objects being used by the CalcEngine. - Map get _formulaInfoTable { + Map? get _formulaInfoTable { if (_isSheeted) { - final SheetFamilyItem family = _getSheetFamilyItem(_grid); - family._sheetFormulaInfoTable ??= {}; + final SheetFamilyItem? family = _getSheetFamilyItem(_grid); + family!._sheetFormulaInfoTable ??= {}; return family._sheetFormulaInfoTable; } @@ -299,12 +303,12 @@ class CalcEngine { _modelToSheetID ??= {}; - if (_modelToSheetID[model] == null) { - _modelToSheetID[model] = sheetFamilyID; + if (_modelToSheetID![model] == null) { + _modelToSheetID![model] = sheetFamilyID; } - final SheetFamilyItem family = _getSheetFamilyItem(model); + final SheetFamilyItem? family = _getSheetFamilyItem(model); - family._isSheeted = true; + family!._isSheeted = true; final String refName1 = refName.toUpperCase(); @@ -316,24 +320,24 @@ class CalcEngine { family._parentObjectToToken ??= {}; - if (family._sheetNameToParentObject.containsKey(refName1)) { - final String token = family._sheetNameToToken[refName1] as String; + if (family._sheetNameToParentObject!.containsKey(refName1)) { + final String token = family._sheetNameToToken![refName1] as String; - family._tokenToParentObject[token] = model; - family._parentObjectToToken[model] = token; + family._tokenToParentObject![token] = model; + family._parentObjectToToken![model] = token; } else { final String token = _sheetToken + _tokenCount.toString() + _sheetToken; _tokenCount++; - family._tokenToParentObject[token] = model; - family._sheetNameToToken[refName1] = token; - family._sheetNameToParentObject[refName1] = model; - family._parentObjectToToken[model] = token; + family._tokenToParentObject![token] = model; + family._sheetNameToToken![refName1] = token; + family._sheetNameToParentObject![refName1] = model; + family._parentObjectToToken![model] = token; _sortedSheetNames = null; } } - static SheetFamilyItem _getSheetFamilyItem(Worksheet model) { + static SheetFamilyItem? _getSheetFamilyItem(Worksheet? model) { if (model == null) return null; if (_sheetFamilyID == 0) { @@ -345,20 +349,20 @@ class CalcEngine { _sheetFamiliesList ??= {}; final int i = - _modelToSheetID[model] != null ? _modelToSheetID[model] as int : 0; + _modelToSheetID![model] != null ? _modelToSheetID![model] as int : 0; - if (_sheetFamiliesList[i] == null) { - _sheetFamiliesList[i] = SheetFamilyItem(); + if (_sheetFamiliesList![i] == null) { + _sheetFamiliesList![i] = SheetFamilyItem(); } - return _sheetFamiliesList[i] as SheetFamilyItem; + return _sheetFamiliesList![i] as SheetFamilyItem; } /// A method that adds a function to the function library. bool _addFunction(String name, String func) { name = name.toUpperCase(); - if (!_libraryFunctions.containsKey(name)) { - _libraryFunctions[name] = func; + if (!_libraryFunctions!.containsKey(name)) { + _libraryFunctions![name] = func; return true; } return false; @@ -384,6 +388,15 @@ class CalcEngine { _addFunction('Concatenate', '_computeConcatenate'); _addFunction('Upper', '_computeUpper'); _addFunction('Lower', '_computeLower'); + _addFunction('AverageIFS', '_computeAverageIFS'); + _addFunction('SumIFS', '_computeSumIFS'); + _addFunction('MinIFS', '_computeMinIFS'); + _addFunction('MaxIFS', '_computeMaxIFS'); + _addFunction('CountIFS', '_computeCountIFS'); + _addFunction('VLookUp', '_computeVLoopUp'); + _addFunction('SumIf', '_computeSumIf'); + _addFunction('SumProduct', '_computeSumProduct'); + _addFunction('Product', '_computeProduct'); } /// A method that increases the calculation level of the CalcEngine. @@ -400,14 +413,14 @@ class CalcEngine { bool isUseFormulaValueChanged = false; _inAPull = true; _multiTick = false; - final Worksheet grd = _grid; + final Worksheet? grd = _grid; final String saveCell = _cell; final String s = cellRef.toUpperCase(); _updateCalcID(); - String txt; - if (!_dependentFormulaCells.containsKey(s) && - !_formulaInfoTable.containsKey(s)) { + String txt = ''; + if (!_dependentFormulaCells!.containsKey(s) && + !_formulaInfoTable!.containsKey(s)) { txt = _getValueFromParentObject(s, true); if (_useFormulaValues) { @@ -420,7 +433,7 @@ class CalcEngine { _ignoreValueChanged = true; final int row = _getRowIndex(s); final int col = _getColIndex(s); - _grid._setValueRowCol(txt, row, col); + _grid!._setValueRowCol(txt, row, col); _ignoreValueChanged = saveIVC; } @@ -438,7 +451,7 @@ class CalcEngine { } else { int i = 0; - int result; + int? result; bool bIsLetter = false; if (i < s.length && s[i] == _sheetToken) { @@ -634,13 +647,13 @@ class CalcEngine { if (name == 'AVG' && excelLikeComputations) { return _formulaErrorStrings[_badIndex]; } - if (_libraryFunctions[name] != null) { + if (_libraryFunctions![name] != null) { final int j = formula.substring(i + ii + 1).indexOf(_rightBracket); String args = formula.substring(i + ii + 2, i + ii + 2 + j - 1); try { - final String function = _libraryFunctions[name]; + final String function = _libraryFunctions![name]; final List argArray = _splitArgsPreservingQuotedCommas(args); @@ -844,7 +857,7 @@ class CalcEngine { final String s1 = _popString(_stack); final String s2 = _popString(_stack); - double d, d1; + double? d, d1; String val = ''; d = double.tryParse(s1); d1 = double.tryParse(s2); @@ -883,7 +896,7 @@ class CalcEngine { final String s1 = _popString(_stack); final String s2 = _popString(_stack); - double d, d1; + double? d, d1; String val = ''; d = double.tryParse(s1); d1 = double.tryParse(s2); @@ -924,7 +937,7 @@ class CalcEngine { final String s2 = _popString(_stack); String val = ''; - double d, d1; + double? d, d1; // ignore: prefer_contains if ((s1.startsWith(_tic) && s2.indexOf(_tic) == -1) || // ignore: prefer_contains @@ -955,7 +968,7 @@ class CalcEngine { final String s1 = _popString(_stack); final String s2 = _popString(_stack); - double d, d1; + double? d, d1; String val = ''; d = double.tryParse(s1); d1 = double.tryParse(s2); @@ -994,7 +1007,7 @@ class CalcEngine { final String s1 = _popString(_stack); final String s2 = _popString(_stack); - double d, d1; + double? d, d1; String val = ''; d = double.tryParse(s1); @@ -1034,7 +1047,7 @@ class CalcEngine { final String s1 = _popString(_stack); final String s2 = _popString(_stack); - double d, d1; + double? d, d1; String val; d = double.tryParse(s1); d1 = double.tryParse(s2); @@ -1105,7 +1118,7 @@ class CalcEngine { case _tokenOr: // exponential { final double d = _pop(_stack); - int x = int.tryParse(d.toString()); + int? x = int.tryParse(d.toString()); if (x != null && _isErrorString) { _isErrorString = false; return _errorStrings[x].toString(); @@ -1134,7 +1147,7 @@ class CalcEngine { return ''; } else { String s = ''; - double d; + double? d; int cc = _stack._count; do { { @@ -1193,69 +1206,69 @@ class CalcEngine { switch (function) { case '_computeSum': return _computeSum(args); - break; case '_computeAvg': return _computeAvg(args); - break; case '_computeMax': return _computeMax(args); - break; case '_computeMin': return _computeMin(args); - break; case '_computeCount': return _computeCount(args); - break; case '_computeIf': return _computeIf(args); - break; case '_computeIndex': return _computeIndex(args); - break; case '_computeMatch': return _computeMatch(args); - break; case '_computeAnd': return _computeAnd(args); - break; case '_computeOr': return _computeOr(args); - break; case '_computeNot': return _computeNot(args); - break; case '_computeToday': return _computeToday(args); - break; case '_computeNow': return _computeNow(args); - break; case '_computeTrim': return _computeTrim(args); - break; case '_computeConcatenate': return _computeConcatenate(args); - break; case '_computeUpper': return _computeUpper(args); - break; case '_computeLower': return _computeLower(args); - break; + case '_computeAverageIFS': + return _computeAverageIFS(args); + case '_computeSumIFS': + return _computeSumIFS(args); + case '_computeMinIFS': + return _computeMinIFS(args); + case '_computeMaxIFS': + return _computeMaxIFS(args); + case '_computeCountIFS': + return _computeCountIFS(args); + case '_computeVLoopUp': + return _computeVLoopUp(args); + case '_computeSumIf': + return _computeSumIf(args); + case '_computeSumProduct': + return _computeSumProduct(args); + case '_computeProduct': + return _computeProduct(args); default: return args; - break; } } /// Returns the sum of all values listed in the argument. String _computeSum(String range) { - double sum = 0; + double? sum = 0; String s1; - double d; + double? d; String adjustRange; final List ranges = _splitArgsPreservingQuotedCommas(range); - if (range == null || range == '') { + if (range == '') { return _formulaErrorStrings[_wrongNumberArguments]; } for (final r in ranges) { @@ -1264,7 +1277,7 @@ class CalcEngine { if (r.startsWith(_tic)) { return _errorStrings[1].toString(); } - final List cells = _getCellsFromArgs(adjustRange); + final List cells = _getCellsFromArgs(adjustRange); for (final s in cells) { try { s1 = _getValueFromArg(s); @@ -1279,7 +1292,7 @@ class CalcEngine { if (s1.isNotEmpty) { d = double.tryParse(s1); if (d != null) { - sum = sum + d; + sum = sum! + d; } } } @@ -1296,10 +1309,10 @@ class CalcEngine { if (s1.isNotEmpty) { d = double.tryParse(s1); - final double d1 = double.tryParse(s1.replaceAll(_tic, '')); + final double? d1 = double.tryParse(s1.replaceAll(_tic, '')); if ((_isCellReference(adjustRange) && d != null && !d.isNaN) || (!_isCellReference(adjustRange) && d1 != null && !d1.isNaN)) { - sum = sum + d; + sum = sum! + d!; } } } @@ -1311,10 +1324,10 @@ class CalcEngine { String _computeAvg(String range) { double sum = 0; int count = 0; - double d; + double? d; String s1; final List ranges = _splitArgsPreservingQuotedCommas(range); - if (ranges.isEmpty || range == null || range == '') { + if (ranges.isEmpty || range == '') { return _formulaErrorStrings[_invalidArguments]; } for (final r in ranges) { @@ -1373,12 +1386,10 @@ class CalcEngine { /// Returns the maximum value of all values listed in the argument. String _computeMax(String range) { double maxValue = -double.maxFinite; - double d; + double? d; String s1; final List ranges = _splitArgsPreservingQuotedCommas(range); - if (ranges.length == 1 && - !range.startsWith(_tic) && - (range == null || range == '')) { + if (ranges.length == 1 && !range.startsWith(_tic) && (range == '')) { return _formulaErrorStrings[_wrongNumberArguments]; } @@ -1439,10 +1450,10 @@ class CalcEngine { /// Returns the minimum value of all values listed in the argument. String _computeMin(String range) { double minValue = double.maxFinite; - double d; + double? d; String s1; final List ranges = _splitArgsPreservingQuotedCommas(range); - if (range == null || range == '') { + if (range == '') { return _formulaErrorStrings[_wrongNumberArguments]; } @@ -1456,9 +1467,9 @@ class CalcEngine { for (final s in _getCellsFromArgs(r)) { try { s1 = _getValueFromArg(s); - final DateTime result = DateTime.tryParse(s1.replaceAll(_tic, '')); + final DateTime? result = DateTime.tryParse(s1.replaceAll(_tic, '')); d = double.tryParse(s1); - if (s1 != null && result != null && d == null) { + if (result != null && d == null) { s1 = _getSerialDateTimeFromDate(result).toString(); } if (_errorStrings.contains(s1)) { @@ -1507,15 +1518,15 @@ class CalcEngine { String _computeCount(String range) { int count = 0; String s1 = ''; - double d; - DateTime dt; + double? d; + DateTime? dt; List array; if (_isIndexInteriorFormula) _isIndexInteriorFormula = false; final List ranges = _splitArgsPreservingQuotedCommas(range); for (final String r in ranges) { ////is a cellrange if (r.contains(':') && _isRange(r)) { - for (final String s in _getCellsFromArgs(r.replaceAll(_tic, ''))) { + for (final String? s in _getCellsFromArgs(r.replaceAll(_tic, ''))) { try { s1 = _getValueFromArg(s); } catch (e) { @@ -1574,7 +1585,7 @@ class CalcEngine { /// Conditionally computes one of two alternatives depending upon a logical expression. String _computeIf(String args) { - if (args == null || args == '') { + if (args == '') { return _formulaErrorStrings[_wrongNumberArguments]; } String s1 = ''; @@ -1586,9 +1597,8 @@ class CalcEngine { final List s = _splitArgsPreservingQuotedCommas(args); if (s.length <= 3) { try { - double d1 = 0; - final String argument1 = - (s[0] == null || s[0] == '') ? '0' : _getValueFromArg(s[0]); + double? d1 = 0; + final String argument1 = (s[0] == '') ? '0' : _getValueFromArg(s[0]); d1 = double.tryParse(argument1); if (d1 != null) { if (_errorStrings.contains(argument1)) { @@ -1605,7 +1615,7 @@ class CalcEngine { } s1 = _getValueFromArg(s[0]); - double d = 0; + double? d = 0; d = double.tryParse(s1); if (s1.replaceAll(_tic, '').toUpperCase() == (_trueValueStr) || (d != null && d != 0)) { @@ -1614,16 +1624,14 @@ class CalcEngine { _isRange(s[1]) && !s[1].contains(_tic)) { s1 = s[1]; - } else if ((s[1] == null || s[1] == '') && _treatStringsAsZero) { + } else if ((s[1] == '') && _treatStringsAsZero) { s1 = '0'; } else { s1 = _getValueFromArg(s[1]); } - if ((s1 == null || s1 == '') && - _treatStringsAsZero && - _computedValueLevel > 1) { + if ((s1 == '') && _treatStringsAsZero && _computedValueLevel > 1) { s1 = '0'; - } else if (!(s1 == null || s1 == '') && + } else if (!(s1 == '') && s1[0] == _tic[0] && !_isCellReference(s[1]) && useNoAmpersandQuotes) { @@ -1642,16 +1650,14 @@ class CalcEngine { _isRange(s[2]) && !s[2].contains(_tic)) { s1 = s[2]; - } else if ((s[2] == null || s[2] == '') && _treatStringsAsZero) { + } else if ((s[2] == '') && _treatStringsAsZero) { s1 = '0'; } else { s1 = _getValueFromArg(s[2]); } - if ((s1 == null || s1 == '') && - _treatStringsAsZero && - _computedValueLevel > 1) { + if ((s1 == '') && _treatStringsAsZero && _computedValueLevel > 1) { s1 = '0'; - } else if (!(s1 == null || s1 == '') && + } else if (!(s1 == '') && s1[0] == _tic[0] && !_isCellReference(s[2]) && useNoAmpersandQuotes) { @@ -1673,7 +1679,7 @@ class CalcEngine { /// Returns the value at a specified row and column from within a given range. String _computeIndex(String arg) { String result; - if (arg == null || arg == '') { + if (arg == '') { return _formulaErrorStrings[_invalidArguments]; } final List args = _splitArgsPreservingQuotedCommas(arg); @@ -1704,28 +1710,28 @@ class CalcEngine { i = r.indexOf(':'); int row = (argCount == 1) ? 1 - : double.tryParse(_getValueFromArg(args[1])).toInt(); + : double.tryParse(_getValueFromArg(args[1]))!.toInt(); int col = (argCount <= 2) ? 1 - : double.tryParse(_getValueFromArg(args[2])).toInt(); + : double.tryParse(_getValueFromArg(args[2]))!.toInt(); int top = _getRowIndex(r.substring(0, i)); int bottom = _getRowIndex(r.substring(i + 1)); if (!(top != -1 || bottom == -1) == (top == -1 || bottom != -1)) { return _errorStrings[5].toString(); } if (top == -1 && _grid is Worksheet) { - top = (_grid).getFirstRow(); + top = (_grid)!.getFirstRow(); } if (bottom == -1 && _grid is Worksheet) { - bottom = (_grid).getLastRow(); + bottom = (_grid)!.getLastRow(); } int left = _getColIndex(r.substring(0, i)); int right = _getColIndex(r.substring(i + 1)); if (left == -1 && _grid is Worksheet) { - left = (_grid).getFirstColumn(); + left = (_grid)!.getFirstColumn(); } if (right == -1 && _grid is Worksheet) { - right = (_grid).getLastColumn(); + right = (_grid)!.getLastColumn(); } if (argCount == 2 && row > bottom - top + 1) { col = row; @@ -1737,11 +1743,11 @@ class CalcEngine { row = _getRowIndex(r.substring(0, i)) + (row <= 0 ? row : row - 1); if (_getRowIndex(r.substring(0, i)) == -1 && _grid is Worksheet) { - row = (_grid).getFirstRow(); + row = (_grid)!.getFirstRow(); } col = _getColIndex(r.substring(0, i)) + (col <= 0 ? col : col - 1); if (_getColIndex(r.substring(0, i)) == -1 && _grid is Worksheet) { - col = (_grid).getFirstColumn(); + col = (_grid)!.getFirstColumn(); } result = _getValueFromArg( @@ -1754,7 +1760,7 @@ class CalcEngine { /// Finds the index a specified value in a lookup_range. String _computeMatch(String arg) { - if (arg == null || arg == '') { + if (arg == '') { return _formulaErrorStrings[_invalidArguments]; } final List args = _splitArgsPreservingQuotedCommas(arg); @@ -1768,7 +1774,7 @@ class CalcEngine { return checkString; } int m = 1; - List cells = []; + List cells = []; final String r = args[1].replaceAll(_tic, ' '); final int i = r.indexOf(':'); if (argCount == 3) { @@ -1779,7 +1785,7 @@ class CalcEngine { } String thirdArg = _getValueFromArg(args[2]); thirdArg = thirdArg.replaceAll(_tic, ' '); - m = double.tryParse(thirdArg).toInt(); + m = double.tryParse(thirdArg)!.toInt(); if (thirdArg.contains(_tic) && (thirdArg.contains(_trueValueStr) || thirdArg.contains(_falseValueStr))) { @@ -1792,7 +1798,7 @@ class CalcEngine { } final String searchItem = _getValueFromArg(args[0]).replaceAll(_tic, ' ').toUpperCase(); - if (searchItem == null || searchItem == '') { + if (searchItem == '') { return _errorStrings[5].toString(); } if (i > -1) { @@ -1805,33 +1811,35 @@ class CalcEngine { return _errorStrings[5].toString(); } if (row1 == -1) { - row1 = (_grid).getFirstRow(); + row1 = (_grid)!.getFirstRow(); } if (col1 == -1) { - col1 = (_grid).getFirstColumn(); + col1 = (_grid)!.getFirstColumn(); } if (row2 == -1) { - row2 = (_grid).getLastRow(); + row2 = (_grid)!.getLastRow(); } if (col2 == -1) { - col2 = (_grid).getLastColumn(); + col2 = (_grid)!.getLastColumn(); } } } - if (cells == null || (cells != null && cells.isEmpty)) { + if (cells.isEmpty) { cells = _getCellsFromArgs(_stripTics(r)); } int index = 1; int emptyValueIndex = 0; - String oldValue; - String newValue; - for (final String s in cells) { - if (_isCellReference(s.replaceAll(_tic, ' '))) { - newValue = _getValueFromArg(s).replaceAll(_tic, ' ').toUpperCase(); - } else { - newValue = s.replaceAll(_tic, '').toUpperCase(); + String oldValue = ''; + String newValue = ''; + for (final String? s in cells) { + if (s != null) { + if (_isCellReference(s.replaceAll(_tic, ' '))) { + newValue = _getValueFromArg(s).replaceAll(_tic, ' ').toUpperCase(); + } else { + newValue = s.replaceAll(_tic, '').toUpperCase(); + } } - if (oldValue != null) { + if (oldValue != '') { if (m == 1) { if (_matchCompare(newValue, oldValue) < 0 && newValue == searchItem) { index--; @@ -1853,12 +1861,12 @@ class CalcEngine { index--; break; } - if (m == 1 && newValue == null) { + if (m == 1 && newValue == '') { emptyValueIndex++; } else { index++; } - if (oldValue == null && newValue != null) { + if (oldValue == '' && newValue != '') { index = index + emptyValueIndex; emptyValueIndex = 0; } @@ -1886,10 +1894,10 @@ class CalcEngine { int _matchCompare(Object o1, Object o2) { final String s1 = o1.toString(); final String s2 = o2.toString(); - final double d1 = double.tryParse(s1); - final double d2 = double.tryParse(s2); + final double? d1 = double.tryParse(s1); + final double? d2 = double.tryParse(s2); if (s1.contains('.') || s2.contains('.')) { - return d1.compareTo(d2); + return d1!.compareTo(d2!); } else { return s1.compareTo(s2); } @@ -1898,16 +1906,16 @@ class CalcEngine { /// Returns the And of all values treated as logical values listed in the argument. String _computeAnd(String range) { bool sum = true; - if (range == null || range.isEmpty) { + if (range.isEmpty) { return _formulaErrorStrings[_wrongNumberArguments].toString(); } - String s1; - double d; + String? s1; + double? d; final List ranges = _splitArgsPreservingQuotedCommas(range); for (final String r in ranges) { if (_splitArguments(r, ':').length > 1 && _isCellReference(r.replaceAll(_tic, ''))) { - for (final String s in _getCellsFromArgs(r)) { + for (final String? s in _getCellsFromArgs(r)) { try { s1 = _getValueFromArg(s); if (_errorStrings.contains(s1)) { @@ -1933,7 +1941,7 @@ class CalcEngine { return _errorStrings[1].toString(); } if (ranges.length == 1) { - if (s1 == null && s1.isEmpty) { + if (s1.isEmpty) { return _errorStrings[1].toString(); } } @@ -1969,16 +1977,16 @@ class CalcEngine { /// Returns the And of all values treated as logical values listed in the argument. String _computeOr(String range) { bool sum = false; - if (range == null || range.isEmpty) { + if (range.isEmpty) { return _formulaErrorStrings[_wrongNumberArguments].toString(); } String s1; - double d; + double? d; final List ranges = _splitArgsPreservingQuotedCommas(range); for (final String r in ranges) { if (_splitArguments(r, ':').length > 1 && _isCellReference(r.replaceAll(_tic, ''))) { - for (final String s in _getCellsFromArgs(r)) { + for (final String? s in _getCellsFromArgs(r)) { try { s1 = _getValueFromArg(s); if (_errorStrings.contains(s1)) { @@ -2004,7 +2012,7 @@ class CalcEngine { return _errorStrings[1].toString(); } if (ranges.length == 1) { - if (s1 == null && s1.isEmpty) { + if (s1.isEmpty) { return _errorStrings[1].toString(); } } @@ -2039,7 +2047,7 @@ class CalcEngine { /// Flips the logical value represented by the argument. String _computeNot(String args) { - double d1; + double? d1; String s = args; ////parsed formula if (args.isNotEmpty && @@ -2097,12 +2105,12 @@ class CalcEngine { } /// A Virtual method to compute the value based on the argument passed in. - String _getValueFromArg(String arg) { + String _getValueFromArg(String? arg) { if (_textIsEmpty(arg)) { return ''; } - double d; - arg = arg.replaceAll('u', '-'); + double? d; + arg = arg!.replaceAll('u', '-'); arg = arg.replaceAll('~', _tic + _tic); if (!_isUpper(arg[0]) && @@ -2206,7 +2214,7 @@ class CalcEngine { } } - List _isDate(Object o, DateTime date) { + List _isDate(Object o, DateTime? date) { date = _dateTime1900; date = DateTime.tryParse(o.toString()); return [(date != null && date.difference(_dateTime1900).inDays >= 0), date]; @@ -2320,7 +2328,7 @@ class CalcEngine { ////Save Strings... final List result = _saveStrings(text); - final Map formulaStrings = result[0]; + final Map? formulaStrings = result[0]; text = result[1]; ////Make braces Strings... @@ -2336,9 +2344,9 @@ class CalcEngine { // ignore: prefer_contains if (text.indexOf(_sheetToken) > -1) { ////Replace sheet references with tokens. - final SheetFamilyItem family = _getSheetFamilyItem(_grid); - if (family._sheetNameToParentObject != null && - family._sheetNameToParentObject.isNotEmpty) { + final SheetFamilyItem? family = _getSheetFamilyItem(_grid); + if (family!._sheetNameToParentObject != null && + family._sheetNameToParentObject!.isNotEmpty) { try { if (!text.startsWith(_sheetToken.toString())) { text = _putTokensForSheets(text); @@ -2441,7 +2449,7 @@ class CalcEngine { final int len = leftParens - i - 1; if (len > 0 && - _libraryFunctions[formula.substring(i + 1, i + 1 + len)] != null) { + _libraryFunctions![formula.substring(i + 1, i + 1 + len)] != null) { if (formula.substring(i + 1, i + 1 + len) == 'AREAS') { _ignoreBracet = true; } else { @@ -2520,14 +2528,14 @@ class CalcEngine { ////TraceUtil.TraceCurrentMethodInfoIf(Switches.FormulaCell.TraceVerbose, text, this); - final String sb = text; + String sb = text; bool process = true; while (process) { - sb.replaceAll('--', '+'); - sb.replaceAll('++', '+'); + sb = sb.replaceAll('--', '+'); + sb = sb.replaceAll('++', '+'); ////Mark unary minus with u-token. - sb + sb = sb .replaceAll( parseArgumentSeparator + '-', parseArgumentSeparator + 'u') .replaceAll(_leftBracket + '-', _leftBracket + 'u') @@ -2539,7 +2547,7 @@ class CalcEngine { .replaceAll('+-', '+u') .replaceAll('^-', '^u'); ////Get rid of leading pluses. - sb + sb = sb .replaceAll( parseArgumentSeparator + ',+', parseArgumentSeparator + ',') .replaceAll(_leftBracket + '+', _leftBracket.toString()) @@ -2550,7 +2558,7 @@ class CalcEngine { .replaceAll('*+', '*') .replaceAll('^+', '^'); if (sb.isNotEmpty && sb[0] == '+') { - sb.replaceRange(0, 1, ''); + sb = sb.replaceRange(0, 1, ''); } process = text != sb.toString(); @@ -2899,8 +2907,9 @@ class CalcEngine { j = j + 1; left = text.substring(j, j + i - j); - final List leftValue = _getCellsFromArgs(left, false); - if (leftValue.isNotEmpty) left = leftValue[0]; + final List leftValue = + _getCellsFromArgs(left, false); + if (leftValue.isNotEmpty) left = leftValue[0]!; } else { //// handle normal cell reference j = j + 1; @@ -3050,8 +3059,9 @@ class CalcEngine { right = text.substring(i + 1, i + 1 + j - i); - final List rightValue = _getCellsFromArgs(right, false); - if (rightValue.isNotEmpty) right = rightValue[0]; + final List rightValue = + _getCellsFromArgs(right, false); + if (rightValue.isNotEmpty) right = rightValue[0]!; } else { //// handle normal cell reference j = j - 1; @@ -3221,6 +3231,7 @@ class CalcEngine { if (args.indexOf(':') != args.lastIndexOf(':')) { return false; } + bool result = true; args.runes.forEach((int c) { if (_isLetter(c)) { isAlpha = true; @@ -3233,9 +3244,10 @@ class CalcEngine { isAlpha = false; isNum = false; } else { - return false; + result = false; } }); + if (!result) return false; if (args.contains(':') && !args.contains(_tic)) { if (containsBoth && isAlpha && isNum) { return true; @@ -3261,29 +3273,29 @@ class CalcEngine { final int i = cell1.lastIndexOf(_sheetToken); int row = 0, col = 0; - final Worksheet grd = _grid; - final SheetFamilyItem family = _getSheetFamilyItem(_grid); - if (i > -1 && family._tokenToParentObject != null) { - _grid = family._tokenToParentObject[cell1.substring(0, i + 1)]; + final Worksheet? grd = _grid; + final SheetFamilyItem? family = _getSheetFamilyItem(_grid); + if (i > -1 && family!._tokenToParentObject != null) { + _grid = family._tokenToParentObject![cell1.substring(0, i + 1)]; row = _getRowIndex(cell1); if (row == -1 && _grid is Worksheet) { - row = (_grid).getFirstRow(); + row = (_grid)!.getFirstRow(); } col = _getColIndex(cell1); if (col == -1 && _grid is Worksheet) { - col = (_grid).getFirstColumn(); + col = (_grid)!.getFirstColumn(); } } else if (i == -1) { row = _getRowIndex(cell1); if (row == -1 && _grid is Worksheet) { - row = (_grid).getFirstRow(); + row = (_grid)!.getFirstRow(); } col = _getColIndex(cell1); if (col == -1 && _grid is Worksheet) { - col = (_grid).getFirstColumn(); + col = (_grid)!.getFirstColumn(); } - if (_isSheeted && family._parentObjectToToken != null) { - cell1 = family._parentObjectToToken[_grid] + cell1; + if (_isSheeted && family!._parentObjectToToken != null) { + cell1 = family._parentObjectToToken![_grid] + cell1; } } @@ -3292,10 +3304,10 @@ class CalcEngine { String val = ''; if (calculateFormula) { - val = _getValueComputeFormulaIfNecessary(row, col, _grid); + val = _getValueComputeFormulaIfNecessary(row, col, _grid!); } else { - final Object s = _grid._getValueRowCol(row, col); - val = s != null ? s.toString() : ''; + final Object s = _grid!._getValueRowCol(row, col); + val = s.toString(); } _grid = grd; @@ -3306,20 +3318,20 @@ class CalcEngine { String _getValueComputeFormulaIfNecessary(int row, int col, Worksheet grd) { try { bool alreadyComputed = false; - FormulaInfo formula = _formulaInfoTable[_cell] as FormulaInfo; - final Object o = grd._getValueRowCol(row, col); - String val = (o != null && o.toString() != '') + FormulaInfo? formula = _formulaInfoTable!.containsKey(_cell) + ? _formulaInfoTable![_cell] as FormulaInfo + : null; + final Object? o = grd._getValueRowCol(row, col); + String? val = (o != null && o.toString() != '') ? o.toString() : ''; ////null; //xx _grid[row, col]; - DateTime result; + DateTime? result; result = DateTime.tryParse(val); - if (val != null && - double.tryParse(val.replaceAll(_tic, '')) == null && - result != null) { - final Worksheet sheet = _grid; + if (double.tryParse(val.replaceAll(_tic, '')) == null && result != null) { + final Worksheet? sheet = _grid; if (sheet != null) { final Range range = sheet.getRangeByIndex(row, col); - if (range != null && range.dateTime != null) { + if (range.dateTime != null) { val = _getSerialDateTimeFromDate(result).toString(); } } else { @@ -3356,8 +3368,8 @@ class CalcEngine { } else { formula = FormulaInfo(); - if (!_dependentFormulaCells.containsKey(_cell)) { - _dependentFormulaCells[_cell] = {}; + if (!_dependentFormulaCells!.containsKey(_cell)) { + _dependentFormulaCells![_cell] = {}; } bool compute = true; @@ -3379,19 +3391,15 @@ class CalcEngine { _ignoreSubtotal = false; } if (compute) { - formula._formulaValue = _computeFormula(formula._parsedFormula); + formula!._formulaValue = _computeFormula(formula._parsedFormula); alreadyComputed = true; } if (formula != null) { if (!_ignoreSubtotal) formula._calcID = _calcID; - if (!_formulaInfoTable.containsKey(_cell)) { - _formulaInfoTable[_cell] = formula; - } - if (formula._formulaValue != null) { - val = formula._formulaValue; - } else { - val = ''; + if (!_formulaInfoTable!.containsKey(_cell)) { + _formulaInfoTable![_cell] = formula; } + val = formula._formulaValue; } _ignoreSubtotal = tempIgnoreSubtotal; } @@ -3399,11 +3407,7 @@ class CalcEngine { if (formula != null) { if (_useFormulaValues || (!_inAPull || alreadyComputed)) { - if (formula._formulaValue != null) { - val = formula._formulaValue; - } else { - val = ''; - } + val = formula._formulaValue; } else if (!alreadyComputed) { if (_calcID == formula._calcID) { val = formula._formulaValue; @@ -3421,15 +3425,12 @@ class CalcEngine { } } if (_treatStringsAsZero && - val == null && val == '' && _computedValueLevel > 1 && !formula._parsedFormula.startsWith(_ifMarker)) { return '0'; } } - - val ??= ''; if (val == 'NaN') val = 'Exception: #VALUE!'; return val; } finally { @@ -3453,13 +3454,13 @@ class CalcEngine { String _getCellFrom(String range) { String s = ''; - final List cells = _getCellsFromArgs(range); - if (cells.length == 1) return cells[0]; + final List cells = _getCellsFromArgs(range); + if (cells.length == 1) return cells[0]!; final int last = cells.length - 1; - final int r1 = _getRowIndex(cells[0]); - if (r1 == _getRowIndex(cells[last])) { - final int c1 = _getColIndex(cells[0]); - final int c2 = _getColIndex(cells[last]); + final int r1 = _getRowIndex(cells[0]!); + if (r1 == _getRowIndex(cells[last]!)) { + final int c1 = _getColIndex(cells[0]!); + final int c2 = _getColIndex(cells[last]!); final int c = _getColIndex(_cell); if (c >= c1 && c <= c2) { s = RangeInfo._getAlphaLabel(c) + r1.toString(); @@ -3469,7 +3470,7 @@ class CalcEngine { } /// A method that retrieves a String array of cells from the range passed in. - List _getCellsFromArgs(String args, [bool findCellsFromRange]) { + List _getCellsFromArgs(String args, [bool? findCellsFromRange]) { findCellsFromRange ??= true; args = _markColonsInQuotes(args); @@ -3502,7 +3503,7 @@ class CalcEngine { if (k1 == -1 || !_isLetter(args.codeUnitAt(k1))) { int count = (_columnMaxCount > 0) ? _columnMaxCount : 16384; if (_grid is Worksheet) { - count = (_grid).getLastColumn(); + count = (_grid)!.getLastColumn(); } args = 'A' + args.substring(0, i) + @@ -3551,16 +3552,16 @@ class CalcEngine { throw Exception(_errorStrings[5].toString()); } if (row1 == -1 && _grid is Worksheet) { - row1 = (_grid).getFirstRow(); + row1 = (_grid)!.getFirstRow(); } if (col1 == -1 && _grid is Worksheet) { - col1 = (_grid).getFirstColumn(); + col1 = (_grid)!.getFirstColumn(); } if (row2 == -1 && _grid is Worksheet) { - row2 = (_grid).getLastRow(); + row2 = (_grid)!.getLastRow(); } if (col2 == -1 && _grid is Worksheet) { - col2 = (_grid).getLastColumn(); + col2 = (_grid)!.getLastColumn(); } if (row1 > row2) { i = row2; @@ -3575,7 +3576,8 @@ class CalcEngine { } final int numCells = (row2 - row1 + 1) * (col2 - col1 + 1); - final List cells = List(numCells); + final List cells = + List.filled(numCells, null, growable: false); int k = 0; for (i = row1; i <= row2; ++i) { for (j = col1; j <= col2; ++j) { @@ -3673,7 +3675,7 @@ class CalcEngine { } List _saveStrings(String text) { - Map strings; + Map? strings; final String tICs2 = _tic + _tic; int id = 0; @@ -3718,15 +3720,15 @@ class CalcEngine { } String _putTokensForSheets(String text) { - final SheetFamilyItem family = _getSheetFamilyItem(_grid); + final SheetFamilyItem? family = _getSheetFamilyItem(_grid); if (_supportsSheetRanges) { - text = _handleSheetRanges(text, family); + text = _handleSheetRanges(text, family!); } if (_sortedSheetNamesList != null) { - for (final String name in _sortedSheetNamesList) { - String token = family._sheetNameToToken[name] as String; + for (final String name in _sortedSheetNamesList!) { + String token = family!._sheetNameToToken![name] as String; token = token.replaceAll(_sheetToken, _tempSheetPlaceHolder); String s = "'" + name.toUpperCase() + "'" + _sheetToken; @@ -3766,9 +3768,8 @@ class CalcEngine { } String _popString(Stack _stack) { - Object o = _stack._pop(); - o ??= ''; - final double d = double.tryParse(o.toString()); + final Object o = _stack._pop(); + final double? d = double.tryParse(o.toString()); if (!_getValueFromArgPreserveLeadingZeros && d != null) { return d.toString(); } @@ -3782,59 +3783,56 @@ class CalcEngine { double _pop(Stack _stack) { final Object o = _stack._pop(); - String s = ''; - if (o != null) { - if (o.toString() == _tic + _tic) { + if (o.toString() == _tic + _tic) { + return double.nan; + } else { + s = o.toString().replaceAll(_tic, ''); + } + if (s.contains('i') || + s.contains('j') || + s.contains('I') || + s.contains('J')) { + final String last = s.substring(s.length - 1, s.length - 1 + 1); + if (last == 'i' || last == 'j' || last == 'I' || last == 'J') { return double.nan; - } else { - s = o.toString().replaceAll(_tic, ''); - } - if (s.contains('i') || - s.contains('j') || - s.contains('I') || - s.contains('J')) { - final String last = s.substring(s.length - 1, s.length - 1 + 1); - if (last == 'i' || last == 'j' || last == 'I' || last == 'J') { - return double.nan; - } - } - if (_errorStrings.contains(s)) { - _isErrorString = true; - return _errorStrings.indexOf(s).toDouble(); - } - ////moved from _computedValue above as result of DT26064 - if (s.startsWith('#') || s == '') { - return 0; } + } + if (_errorStrings.contains(s)) { + _isErrorString = true; + return _errorStrings.indexOf(s).toDouble(); + } + ////moved from _computedValue above as result of DT26064 + if (s.startsWith('#') || s == '') { + return 0; + } - if (s == _trueValueStr) { - return 1; - } else if (s == _falseValueStr) { - return 0; - } + if (s == _trueValueStr) { + return 1; + } else if (s == _falseValueStr) { + return 0; + } - final double d = double.tryParse(s); - if (d != null) { - return d; - } else if (useDatesInCalculations) { - DateTime dt; - final List listResult = _isDate(o, dt); - if (listResult[0]) { - return _getSerialDateTimeFromDate(listResult[1]); - } + final double? d = double.tryParse(s); + if (d != null) { + return d; + } else if (useDatesInCalculations) { + DateTime? dt; + final List listResult = _isDate(o, dt); + if (listResult[0]) { + return _getSerialDateTimeFromDate(listResult[1]); } } - if (s == null || s == '' && _treatStringsAsZero) { + if (s == '' && _treatStringsAsZero) { return 0; - } else if (o != null && o.toString().isNotEmpty) { + } else if (o.toString().isNotEmpty) { return double.nan; } return 0; } /// Tests whether a String is NULL or empty. - static bool _textIsEmpty(String s) { + static bool _textIsEmpty(String? s) { return s == null || s == ''; } @@ -3935,10 +3933,7 @@ class CalcEngine { } s = _computedValue(s); - if (s != null && - s.isNotEmpty && - s[0] == _tic[0] && - s[s.length - 1] == _tic[0]) { + if (s.isNotEmpty && s[0] == _tic[0] && s[s.length - 1] == _tic[0]) { String newS = s.substring(1, 1 + s.length - 2); if (newS.contains(_tic)) { _multiTick = true; @@ -3996,7 +3991,7 @@ class CalcEngine { s = 'n' + s; } else { - //WPF-37458- To pass the computed result of interior functions in single cell array formula + //To pass the computed result of interior functions in single cell array formula if (!_isRange(s) && s.startsWith(_braceLeft) && s.endsWith(_braceRight)) { @@ -4032,7 +4027,7 @@ class CalcEngine { /// Returns the current date and time as a date serial number. String _computeNow(String argList) { - if (argList != null && argList.isNotEmpty) { + if (argList.isNotEmpty) { return _formulaErrorStrings[_wrongNumberArguments]; } final DateTime dt = DateTime.now(); @@ -4044,20 +4039,20 @@ class CalcEngine { /// Returns the current date as a date serial number. String _computeToday(String argList) { - if (argList != null && argList.isNotEmpty) { + if (argList.isNotEmpty) { return _formulaErrorStrings[_wrongNumberArguments]; } final DateTime dt = DateTime.now(); if (excelLikeComputations) { - final DateTime result = DateTime.tryParse(dt.year.toString() + + final DateTime? result = DateTime.tryParse(dt.year.toString() + '/' + dt.month.toString() + '/' + dt.day.toString()); if (result != null) { final String date = DateFormat( - _grid.workbook.cultureInfo.dateTimeFormat.shortDatePattern) + _grid!.workbook.cultureInfo.dateTimeFormat.shortDatePattern) .format(result); return date; } @@ -4083,7 +4078,7 @@ class CalcEngine { final List ar = _isSeparatorInTIC(range) ////range.IndexOf(TIC) > 0 ? _getStringArray(range) : _splitArgsPreservingQuotedCommas(range); - if (range == null || range.isEmpty) { + if (range.isEmpty) { return _formulaErrorStrings[_wrongNumberArguments]; } for (final String r in ar) { @@ -4264,4 +4259,1484 @@ class CalcEngine { // return _dateTime1900.add(Duration(days: days)); // } + + /// Returns the average of all the cells in a range which is statisfy the given multible criteria + String _computeAverageIFS(String argsList) { + if (argsList == '') { + return _formulaErrorStrings[_invalidArguments]; + } + final List args = _splitArgsPreservingQuotedCommas(argsList); + final int argCount = args.length; + double cellCount = 0; + final List criteriaRange = []; + final List criterias = []; + List tempList = []; + List criteriaRangeValue = []; + for (int i = 1; i < argCount; i++) { + criteriaRange.add(args[i]); + i++; + criterias.add(args[i]); + } + if (argCount < 3 && criteriaRange.length == criterias.length) { + return _formulaErrorStrings[_wrongNumberArguments]; + } + String sumRange = args[0]; + + double sum = 0; + // final List sumRangeCells = _getCellsFromArgs(args[0]); + List s2 = _getCellsFromArgs(sumRange); + bool isLastcriteria = false; + for (int v = 0; v < criterias.length; v++) { + String op; + op = _tokenEqual; + String criteria = criterias[v]; + if (criteria[0] != _tic[0] && !'=><'.contains(criteria[0])) { + ////cell reference + criteria = _getValueFromArg(criteria); + } + if (v == criteriaRange.length - 1) { + isLastcriteria = true; + } + op = _findOp(criteria, op); + criteria = _findCriteria(criteria, op); + final List s1 = _getCellsFromArgs(criteriaRange[v]); + if (s1[0] == _errorStrings[5] || s2[0] == _errorStrings[5]) { + return _errorStrings[5].toString(); + } + final int count = s1.length; + + if (count > s2.length) { + final int i = sumRange.indexOf(':'); + if (i > -1) { + int startRow = _getRowIndex(sumRange.substring(0, i)); + int row = _getRowIndex(sumRange.substring(i + 1)); + if (!(startRow != -1 || row == -1) == (startRow == -1 || row != -1)) { + return _errorStrings[5].toString(); + } + if (startRow == -1 && _grid is Worksheet) { + startRow = (_grid)!.getFirstRow(); + } + int startCol = _getColIndex(sumRange.substring(0, i)); + if (startCol == -1 && _grid is Worksheet) { + startCol = (_grid)!.getFirstColumn(); + } + if (row == -1 && _grid is Worksheet) { + row = (_grid)!.getLastRow(); + } + int col = _getColIndex(sumRange.substring(i + 1)); + if (col == -1 && _grid is Worksheet) { + col = (_grid)!.getLastColumn(); + } + if (startRow != row) { + row += count - s2.length; + } else if (startCol != col) { + col += count - s2.length; + } + + sumRange = sumRange.substring(0, i + 1) + + RangeInfo._getAlphaLabel(col) + + row.toString(); + s2 = _getCellsFromArgs(sumRange); + } + } + double? d = 0.0; + String s = ''; + double compare = -1.7976931348623157E+308; + bool isNumber = false; + if (double.tryParse(criteria) != null) { + compare = double.tryParse(criteria)!; + isNumber = true; + } + for (int index = 0; index < count; ++index) { + s = _getValueFromArg(s1[index]); //// +criteria; + final bool criteriaMatched = _checkForCriteriaMatch( + s.toUpperCase(), op, criteria.toUpperCase(), isNumber, compare); + if (criteriaMatched) { + if (isLastcriteria && criterias.length == 1) { + cellCount++; + sum += d!; + } else { + //Below code has been modified to check the index of criteria. + if (tempList.isNotEmpty && v != 0) { + final int tempCount = tempList.length; + //Below code has been used to add the values when length was same. + if (tempCount == s1.length && + _getRowIndex(tempList[index].toString()) == + _getRowIndex(s1[index]!)) { + criteriaRangeValue.add(s1[index]!); + if (isLastcriteria && s2[index] != null) { + s = _getValueFromArg(s2[index]); + d = double.tryParse(s); + final bool v = d != null; + if (v && isLastcriteria) { + sum += d; + cellCount++; + } + } + } else { + for (int i = 0; i < tempCount; i++) { + //Below code has been added to compare the old and new criteria ranges and store the values which matches. + if (_getRowIndex(tempList[i].toString()) == + _getRowIndex(s1[index]!)) { + criteriaRangeValue.add(s1[index]!); + if (isLastcriteria && s2[index] != null) { + s = _getValueFromArg(s2[index]); + d = double.tryParse(s); + final bool v = d != null; + if (v && isLastcriteria) { + sum += d; + cellCount++; + } + } + } + } + } + } else { + criteriaRangeValue.add(s1[index]!); + } + } + } + } + tempList = criteriaRangeValue; + criteriaRangeValue = []; + } + final double average = sum / cellCount; + if (_computeIsErr(average.toString()) == _trueValueStr) { + if (_rethrowExceptions) { + throw Exception(_formulaErrorStrings[_badFormula]); + } + return _errorStrings[3].toString(); + } + return average.toString(); + } + + /// Below method used to find the criteria value which is combined with tokens. + String _findCriteria(String criteria, String op1) { + final int offset = (criteria.isNotEmpty && criteria[0] == _tic[0]) ? 1 : 0; + if (criteria.substring(offset).startsWith('>=')) { + criteria = criteria.substring(offset + 2, criteria.length - 1); + op1 = _tokenGreaterEq; + } else if (criteria.substring(offset).startsWith('<=')) { + criteria = criteria.substring(offset + 2, criteria.length - 1); + op1 = _tokenLesseq; + } else if (criteria.substring(offset).startsWith('<>')) { + criteria = criteria.substring(offset + 2, criteria.length - 1); + op1 = _tokenNoEqual; + } else if (criteria.substring(offset).startsWith('<')) { + criteria = criteria.substring(offset + 1, criteria.length - 1); + op1 = _tokenLess; + } else if (criteria.substring(offset).startsWith('>')) { + criteria = criteria.substring(offset + 1, criteria.length - 1); + op1 = _tokenGreater; + } else if (criteria.substring(offset).startsWith('=')) { + criteria = criteria.substring(offset + 1, criteria.length - 1); + op1 = _tokenEqual; + } + criteria = criteria.replaceAll(_tic, ''); + return criteria; + } + + /// Below method used to find whether the criteria is matched with the Tokens "=",">",">=" or not. + bool _checkForCriteriaMatch( + String s, String op, String criteria, bool isNumber, double compare) { + final String tempcriteria = criteria; + double? d = 0.0; + //Below condition has added to match the number when ita text value.eg(s1=\"2\" and comapre="2") + s = s.replaceAll(_tic, ''); + switch (op) { + case _tokenEqual: + if (isNumber) { + d = double.tryParse(s); + final bool value = d != null; + return value && d == compare; + } + final int starindex = tempcriteria.indexOf('*'); + if (starindex != -1 && s.isNotEmpty) { + final bool isstartswith = starindex == 0; + final bool isendswith = tempcriteria.endsWith('*'); + final List tempArray = criteria.split('*'); + + //Below code has added to calculate when criteria contains multiple "*". + if (tempArray.length > 2) { + bool isMatch = false; + for (int i = 0; i < tempArray.length; i++) { + if (i == 0 && !isstartswith) { + isMatch = s.startsWith(tempArray[0]); + } else { + isMatch = s.contains(tempArray[i]); + } + if (!isMatch) { + return isMatch; + } else { + continue; + } + } + return isMatch; + } + + // Below code has been added avoid to throw argument exception when criteria length was higher than s length. + else if (!isstartswith && !isendswith) { + final List criterias = criteria.split('*'); + return s.startsWith(criterias[0]) && s.endsWith(criterias[1]); + } else if (isstartswith && isendswith) { + criteria = criteria.replaceAll('*', ''); + return s.contains(criteria); + } else if (isstartswith) { + criteria = criteria.replaceAll('*', ''); + return s.endsWith(criteria); + } else if (isendswith) { + criteria = criteria.replaceAll('*', ''); + return s.startsWith(criteria); + } + } + return s.isNotEmpty && s == criteria; + case _tokenNoEqual: + if (isNumber) { + d = double.tryParse(s); + final bool value = d != null; + return value && d != compare; + } else { + return s.isNotEmpty && s.toUpperCase() != criteria.toUpperCase(); + } + case _tokenGreaterEq: + //Below code has been added to compare the value when the criteria is string. + final int tempString = + s.toUpperCase().compareTo(criteria.toUpperCase()); + if (isNumber) { + d = double.tryParse(s); + final bool value = d != null; + return value && d >= compare; + } else { + return s.isNotEmpty && tempString >= 0; + } + case _tokenGreater: + //Below code has been added to compare the value when the criteria is string. + final int tempString = + s.toUpperCase().compareTo(criteria.toUpperCase()); + if (isNumber) { + d = double.tryParse(s); + final bool value = d != null; + return value && d > compare; + } else { + return s.isNotEmpty && tempString > 0; + } + case _tokenLess: + //Below code has been added to compare the value when the criteria is string. + final int tempString = + s.toUpperCase().compareTo(criteria.toUpperCase()); + if (isNumber) { + d = double.tryParse(s); + final bool value = d != null; + return value && d < compare; + } else { + return s.isNotEmpty && tempString < 0; + } + case _tokenLesseq: + //Below code has been added to compare the value when the criteria is string. + final int tempString = + s.toUpperCase().compareTo(criteria.toUpperCase()); + if (isNumber) { + d = double.tryParse(s); + final bool value = d != null; + return value && d <= compare; + } else { + return s.isNotEmpty && tempString <= 0; + } + } + return false; + } + + /// Returns True is the string denotes an error except #N/A. + String _computeIsErr(String range) { + if (range.isEmpty) { + if (_rethrowExceptions) { + throw Exception(_formulaErrorStrings[_wrongNumberArguments]); + } + return _formulaErrorStrings[_wrongNumberArguments]; + } + final String tempRange = range.toUpperCase(); + //Below code is modified to get the CalculatedValue when the range is cell reference. + if (range.isNotEmpty && + !range.startsWith('#') && + !tempRange.startsWith('NAN') && + !tempRange.startsWith('-NAN') && + range != double.infinity.toString() && + range != double.negativeInfinity.toString()) { + range = _getValueFromArg(range).toUpperCase().replaceAll(_tic, ''); + } else { + range = range.toUpperCase(); + } + if ((range.startsWith('NAN') || + range.startsWith('-NAN') || + range.startsWith('INFINITY') || + range.startsWith('-INFINITY') || + range.startsWith('#') || + range.startsWith('n#')) && + !range.startsWith('#N/A') || + range == double.infinity.toString() || + range == double.negativeInfinity.toString()) { + return _trueValueStr; + } else { + return _falseValueStr; + } + } + + /// Returns the sum of all the cells in a range which is statisfy the given multible criteria + String _computeSumIFS(String argList) { + return _calculateIFSFormula(argList, 'SUMIFS'); + } + + String _calculateIFSFormula(String argList, String condition) { + if (argList == '') { + return _formulaErrorStrings[_invalidArguments]; + } + final List args = _splitArgsPreservingQuotedCommas(argList); + final int argCount = args.length; + final List criteriaRange = []; + final List criterias = []; + List tempList = []; + for (int i = 1; i < argCount; i++) { + criteriaRange.add(args[i]); + i++; + criterias.add(args[i]); + } + if (argCount < 3 && criteriaRange.length == criterias.length) { + return _formulaErrorStrings[_wrongNumberArguments]; + } + String calculateRange = args[0]; + double sum = 0; + double max = -1.7976931348623157E+308; + double min = double.maxFinite; + String val = ''; + // final List _calculateRangeCells = _getCellsFromArgs(args[0]); + List s2 = _getCellsFromArgs(calculateRange); + for (int v = 0; v < criterias.length; v++) { + String op = _tokenEqual; + String criteria = criterias[v]; + if (criteria[0] != _tic[0] && !'=><'.contains(criteria[0])) { + ////cell reference + criteria = _getValueFromArg(criteria); + } + op = _findOp(criteria, op); + criteria = _findCriteria(criteria, op); + final List s1 = _getCellsFromArgs(criteriaRange[v]); + if ((s1[0] != null && s1[0] == _errorStrings[5]) || + (s2[0] != null && s2[0] == _errorStrings[5])) { + return _errorStrings[5].toString(); + } + if (s1.length != s2.length) { + return _errorStrings[1].toString(); + } + + final int count = s1.length; + if (count > s2.length) { + final int i = calculateRange.indexOf(':'); + if (i > -1) { + int startRow = _getRowIndex(calculateRange.substring(0, i)); + int row = _getRowIndex(calculateRange.substring(i + 1)); + if (!(startRow != -1 || row == -1) == (startRow == -1 || row != -1)) { + return _errorStrings[5].toString(); + } + int startCol = _getColIndex(calculateRange.substring(0, i)); + int col = _getColIndex(calculateRange.substring(i + 1)); + // Supports implenmented only for XlsIO + if (_grid is Worksheet) { + if (startRow == -1) { + startRow = (_grid)!.getFirstRow(); + } + if (startCol == -1) { + startCol = (_grid)!.getFirstColumn(); + } + if (row == -1) { + row = (_grid)!.getLastRow(); + } + if (col == -1) { + col = (_grid)!.getLastColumn(); + } + } + if (startRow != row) { + row += count - s2.length; + } else if (startCol != col) { + col += count - s2.length; + } + calculateRange = calculateRange.substring(0, i + 1) + + RangeInfo._getAlphaLabel(col) + + row.toString(); + s2 = _getCellsFromArgs(calculateRange); + } + } + // SUMIFS method is modified to handle multiplle criteria's and criteria range for operation. + String s; + final List criteriaRangeValue = []; + // int index1 = criteria.indexOf('*'); + double compare = -1.7976931348623157E+308; + bool isNumber = false; + if (double.tryParse(criteria) != null) { + compare = double.tryParse(criteria)!; + isNumber = true; + } + for (int index = 0; index < count; ++index) { + s = _getValueFromArg(s1[index]); + final bool criteriaMatched = _checkForCriteriaMatch( + s.toUpperCase(), op, criteria.toUpperCase(), isNumber, compare); + if (criteriaMatched) { + //Below code has been modified to check the index of criteria. + if (tempList.isNotEmpty && v != 0) { + final int tempCount = tempList.length; + for (int i = 0; i < tempCount; i++) { + //Below code has been added to compare the old and new criteria ranges and store the values which matches. + if (_getRowIndex(tempList[i].toString()) == + (_getRowIndex(s1[index]!))) { + criteriaRangeValue.add(s2[index]!); + } + } + } else if (s2[index] != null) { + criteriaRangeValue.add(s2[index]!); + } + } + } + //Below code has been modified to return "Zero" if any one of the criteria fails. + if (criteriaRangeValue.isEmpty) { + tempList.clear(); + break; + } else { + tempList = criteriaRangeValue; + } + } + switch (condition) { + case ('SUMIFS'): + for (int i = 0; i < tempList.length; i++) { + final String temp = _getValueFromArg(tempList[i]); + //To compute sum only for double values. + double? temp1; + temp1 = double.tryParse(temp); + sum = sum + temp1!; + } + break; + case ('MAXIFS'): + for (int i = 0; i < tempList.length; i++) { + final String temp = _getValueFromArg(tempList[i]); + //To compute sum only for double values. + double? temp1; + temp1 = double.tryParse(temp); + if (temp1! > max) { + max = temp1; + } + } + break; + case ('MINIFS'): + for (int i = 0; i < tempList.length; i++) { + final String temp = _getValueFromArg(tempList[i]); + //To compute sum only for double values. + double? temp1; + temp1 = double.tryParse(temp); + if (temp1! < min) { + min = temp1; + } + } + break; + } + //Returns the value + if (condition == 'SUMIFS') { + val = sum.toString(); + } + if (condition == 'MAXIFS') { + val = max.toString(); + } + if (condition == 'MINIFS') { + if (min == 1.7976931348623157e+308) { + min = 0.0; + } + val = min.toString(); + } + return val; + } + + /// The MINIFS function returns the minimum value among cells specified by a given set of conditions or criteria. + String _computeMinIFS(String argList) { + return _calculateIFSFormula(argList, 'MINIFS'); + } + + /// The MAXIFS function returns the maximum value among cells specified by a given set of conditions or criteria. + String _computeMaxIFS(String argList) { + return _calculateIFSFormula(argList, 'MAXIFS'); + } + + /// The COUNTIFS function applies criteria to cells across multiple ranges and counts the number of times all criteria are met. + String _computeCountIFS(String argList) { + return _computeCountIFFunctions(argList, false); + } + + /// Calculates the CountIF and CountIFS formula. + String _computeCountIFFunctions(String argList, bool isCountif) { + if (argList == '') { + return _formulaErrorStrings[_invalidArguments]; + } + final List args = _splitArgsPreservingQuotedCommas(argList); + final int argCount = args.length; + double cellCount = 0; + // final String cellCountValue = ''; + if (_isIndexInteriorFormula) _isIndexInteriorFormula = false; + bool isLastcriteria = false; + final List criteriaRange = []; + final List criterias = []; + List tempList = []; + List criteriaRangeValue = []; + for (int i = 0; i < argCount; i++) { + criteriaRange.add(args[i]); + i++; + criterias.add(args[i]); + } + final List val = _getCellsFromArgs(criteriaRange[0]); + if (argCount < 2 && + criteriaRange.length == criterias.length && + !isCountif) { + return _formulaErrorStrings[_wrongNumberArguments]; + } + if (criteriaRange.length != criterias.length) { + return _errorStrings[1].toString(); + } + if (argCount != 2 && argCount != 3 && isCountif) { + if (_rethrowExceptions) { + throw Exception(_formulaErrorStrings[_wrongNumberArguments]); + } + return _formulaErrorStrings[_wrongNumberArguments]; + } + for (int v = 0; v < criterias.length; v++) { + String op = _tokenEqual; + String criteria = criterias[v]; + if (criteria[0] != _tic[0] && !'=><'.contains(criteria[0])) { + ////cell reference + criteria = _getValueFromArg(criteria); + } + if (v == criteriaRange.length - 1 && !isCountif) { + isLastcriteria = true; + } + if (isCountif) isLastcriteria = true; + final int length = criteria.length; + if (length < 1 && isCountif) { + return '0'; + } + //Below condition has added to find the criteria for the array structure COUNTIF formula. + if (_isArrayFormula && isCountif) { + op = _findOp(criterias[0].replaceAll(_bMarker.toString(), ''), op); + criteria = + _findCriteria(criterias[0].replaceAll(_bMarker.toString(), ''), op); + } else { + op = _findOp(criteria, op); + criteria = _findCriteria(criteria, op); + } + final List s1 = _getCellsFromArgs(criteriaRange[v]); + if (s1.length != val.length) return _errorStrings[1].toString(); + if (s1[0] == _errorStrings[5]) { + if (_rethrowExceptions) { + throw Exception(_formulaErrorStrings[_badIndex]); + } + return _errorStrings[5].toString(); + } + final count = s1.length; + String s; + double compare = -1.7976931348623157E+308; + bool isNumber = false; + if (double.tryParse(criteria) != null) { + compare = double.tryParse(criteria)!; + isNumber = true; + } + + for (int index = 0; index < count; ++index) { + s = _getValueFromArg(s1[index]); //// +criteria; + //Below condition has been added to check whether the criteria is number, expression or text. For example, criteria can be expressed as 32, ">32", B4, "apples", or "32". + final bool criteriaMatched = _checkForCriteriaMatch( + s.toUpperCase(), op, criteria.toUpperCase(), isNumber, compare); + //Below code has been added to count the number of occurences of values which the criteria satisfies. + if (criteriaMatched) { + if (isCountif && isLastcriteria || + isLastcriteria && criterias.length == 1) { + cellCount++; + } else { + //Below code has been modified to check the index of criteria. + if (tempList.isNotEmpty && v != 0) { + final int tempCount = tempList.length; + for (int i = 0; i < tempCount; i++) { + //Below code has been added to compare the old and new criteria ranges and store the values which matches. + if (_getRowIndex(tempList[i].toString()) == + _getRowIndex(s1[index]!)) { + criteriaRangeValue.add(s1[index]!); + if (isLastcriteria) cellCount++; + } + } + } else { + criteriaRangeValue.add(s1[index]!); + } + } + } + } + tempList = criteriaRangeValue; + criteriaRangeValue = []; + } + return cellCount.toString(); + } + + /// Returns a vertical table look up value. + String _computeVLoopUp(String args) { + final bool cachingEnabled = false; + final List s = _splitArgsPreservingQuotedCommas(args); + String lookUp = _getValueFromArg(s[0]); + lookUp = lookUp.replaceAll(_tic, '').toUpperCase(); + DateTime? lookupDatetime; + double? lookupDoubleValue; + //Below condition has been added to return the double value while the lookup value as DateTime format. + lookupDoubleValue = double.tryParse(lookUp.replaceAll(_tic, '')); + final bool isDouble = lookupDoubleValue != null; + lookupDatetime = DateTime.tryParse(lookUp); + final bool isDateTime = lookupDatetime != null; + if (!isDouble && isDateTime) { + lookUp = _getSerialDateTimeFromDate(lookupDatetime).toString(); + } + String r = s[1].replaceAll('"', ''); + if (r == '#REF!') { + return r; + } + final String o1 = _getValueFromArg(s[2]).replaceAll('"', ''); + double? d = 0; + d = double.tryParse(o1); + final bool v = d != null; + if (_computeIsLogical(o1) == _trueValueStr) { + d = double.parse(_computeN(o1)); + } else if (!v || o1 == 'NaN') { + return '#N/A'; + } + if (d < 1) { + return _errorStrings[1].toString(); + } + final int col = d.toInt(); + bool match = true, rangeLookup = true; + if (s.length == 4) { + match = rangeLookup = (_getValueFromArg(s[3]) == _trueValueStr) || + (_getValueFromArg(s[3].replaceAll(_tic, '')) == '1'); + } + d = double.tryParse(lookUp); + final bool typeIsNumber = d != null; + int i = r.indexOf(':'); + ////single cell + if (i == -1) { + r = r + ':' + r; + i = r.indexOf(':'); + } + final int k = r.substring(0, i).lastIndexOf(_sheetToken); + final SheetFamilyItem? family = _getSheetFamilyItem(_grid); + Worksheet? dependentGrid; + if (k > -1) { + //To avoid grid resetting at run time when the grid has dependent sheets. + //To return proper value when grid type is ICalcData. + if (family!._tokenToParentObject != null && + (family._tokenToParentObject![r.substring(0, k + 1)] != null)) { + dependentGrid = + family._tokenToParentObject![r.substring(0, k + 1)] as Worksheet; + } + } + int row1 = _getRowIndex(r.substring(0, i)); + int row2 = _getRowIndex(r.substring(i + 1)); + int col1 = _getColIndex(r.substring(0, i)); + int col2 = _getColIndex(r.substring(i + 1)); + // Supports implenmented only for XlsIO + //To return proper value when grid type is ICalcData. + if (_grid is Worksheet || + (dependentGrid != null && dependentGrid is Worksheet)) { + if ((row1 != -1 || row2 == -1) != (row1 == -1 || row2 != -1)) { + return _errorStrings[5].toString(); + } + if (row1 == -1) { + //To avoid grid resetting at run time when the grid has dependent sheets. + //To return proper value when grid type is ICalcData. + if (dependentGrid != null) { + row1 = (dependentGrid).getFirstRow(); + } else { + row1 = (_grid)!.getFirstRow(); + } + } + if (col1 == -1) { + //To avoid grid resetting at run time when the grid has dependent sheets. + //To return proper value when grid type is ICalcData. + if (dependentGrid != null) { + col1 = (dependentGrid).getFirstColumn(); + } else { + col1 = (_grid)!.getFirstColumn(); + } + } + if (row2 == -1) { + //To avoid grid resetting at run time when the grid has dependent sheets. + //To return proper value when grid type is ICalcData. + if (dependentGrid != null) { + row2 = (dependentGrid).getLastRow(); + } else { + row2 = (_grid)!.getLastRow(); + } + } + if (col2 == -1) { + //To avoid grid resetting at run time when the grid has dependent sheets. + //To return proper value when grid type is ICalcData. + if (dependentGrid != null) { + col2 = (dependentGrid).getLastColumn(); + } else { + col2 = (_grid)!.getLastColumn(); + } + } + } + bool newTable = true; + String val = ''; + int lastRow = row1, matchCount = 0; + String s1 = ''; + double? d1 = 0; + bool doLastRowMark = true; + bool exactMatch = false; + final List tableValues = []; + for (int row = row1; row <= row2; ++row) { + if (!cachingEnabled) { + //To avoid grid resetting at run time when the grid has dependent sheets. + if (dependentGrid != null) { + s1 = _getValueFromParentObjectGrid(row, col1, true, dependentGrid) + .toString() + .toUpperCase() + .replaceAll('"', ''); + } else { + s1 = _getValueFromParentObjectGrid(row, col1, true, _grid) + .toString() + .toUpperCase() + .replaceAll('"', ''); + } + DateTime? matchDateTime; + double? doubleMatchValue; + doubleMatchValue = double.tryParse(s1.replaceAll(_tic, '')); + final bool isDouble = doubleMatchValue != null; + matchDateTime = DateTime.tryParse(s1); + final bool isDateTime = matchDateTime != null; + if (s1 != '' && !isDouble && isDateTime) { + s1 = _getSerialDateTimeFromDate(matchDateTime).toString(); + } + if (!tableValues.contains(s1)) { + tableValues.add(s1); + } + } + d1 = double.tryParse(s1); + final bool v = d1 != null; + if (s1 == lookUp || + (match && + (typeIsNumber + ? (v && (d1.compareTo(d) > 0)) + : s1.compareTo(lookUp) > 0))) { + if (s1.toUpperCase() == lookUp) { + if (lookUp == '' && !_matchType) { + continue; + } else { + lastRow = row; + match = true; + exactMatch = true; + matchCount++; + //Below code has been added to break the condition when the match is false.This codition break once fine the matched value. + if (s.length == 4 && s[3] == _falseValueStr) break; + } + } + if (!newTable) { + break; + } else { + doLastRowMark = false; + } + } + if (doLastRowMark) { + lastRow = row; + } + if (matchCount == 0) { + newTable = true; + } else { + newTable = false; + } + + match = true; + } + + if (match || s1 == lookUp) { + //To avoid the calculation when the lookup value is empty. + if (tableValues.isNotEmpty && lookUp != '') { + if (!cachingEnabled) { + tableValues.sort(); + } + tableValues[0] = (tableValues[0] == '') ? '0' : tableValues[0]; + } + //To return proper value when grid type is ICalcData. + if ((!exactMatch && + ((!typeIsNumber) || + (typeIsNumber && + tableValues.isNotEmpty && + double.parse(tableValues[0].toString()) > + double.parse(lookUp)))) || + (!rangeLookup && !exactMatch)) { + return '#N/A'; + } + //To return proper value when grid type is ICalcData. + if (dependentGrid != null) { + val = _getValueFromParentObjectGrid( + lastRow, col + col1 - 1, true, dependentGrid) + .toString(); + } else { + val = + _getValueFromParentObjectGrid(lastRow, col + col1 - 1, true, _grid) + .toString(); + } + if (val == '' && + !_getValueFromParentObjectGrid(lastRow, col + col1 - 1, false, _grid) + .toUpperCase() + .startsWith('=IF')) { + val = '0'; + } + if (val.isNotEmpty && val[0] == CalcEngine._formulaCharacter) { + val = _parseFormula(val); + } + d = 0; + d = double.tryParse(val); + final bool v = d != null; + if (val.isNotEmpty && val[0] != _tic[0] && !v) { + val = _tic + val + _tic; + } + } else { + val = '#N/A'; + } + return val; + } + + /// Determines whether the value is a logical value. + String _computeIsLogical(String args) { + args = _getValueFromArg(args).toUpperCase(); + + if (args == _falseValueStr || args == _trueValueStr) { + return _trueValueStr; + } + + return _falseValueStr; + } + + /// Returns a number converted from the provided value. + String _computeN(String args) { + String cellReference = ''; + double? val = 0.0; + DateTime? date; + final List arg = _splitArguments(args, parseArgumentSeparator); + final int argCount = arg.length; + if (argCount != 1) { + return _formulaErrorStrings[_requiresASingleArgument]; + } + //Below condition has been modified to calculate when provided the numeric value as string. + cellReference = _getValueFromArg(args); + val = double.tryParse(cellReference); + final bool v = val != null; + date = DateTime.tryParse(cellReference); + final bool v1 = date != null; + if (v) { + return val.toString(); + } else if (v1) { + val = _getSerialDateTimeFromDate(date); + } else if (cellReference == _trueValueStr) { + val = 1; + } else if (cellReference == _falseValueStr) { + val = 0; + } else if (_errorStrings.contains(cellReference) || + _formulaErrorStrings.contains(cellReference)) { + return cellReference; + } + return val.toString(); + } + + String _getValueFromParentObjectGrid(int row, int col, bool calculateFormula, + [Worksheet? grd]) { + final SheetFamilyItem? family = _getSheetFamilyItem(grd); + String cell1 = (family!._parentObjectToToken == null || + family._parentObjectToToken!.isEmpty) + ? '' + : family._parentObjectToToken![grd].toString(); + cell1 = cell1 + RangeInfo._getAlphaLabel(col) + row.toString(); + final Worksheet? saveGrid = _grid; + final String saveCell = _cell; + _cell = cell1; + _grid = grd; + String val = ''; + if (calculateFormula) { + val = _getValueComputeFormulaIfNecessary(row, col, grd!); + } else { + final Object s = _grid!._getValueRowCol(row, col); + val = s.toString(); + } + DateTime? tempDate; + double? doubleValue; + doubleValue = double.tryParse(val); + final bool isDouble = doubleValue != null; + tempDate = DateTime.tryParse(val); + final bool isDateTime = tempDate != null; + + if (excelLikeComputations && + useDatesInCalculations && + !isDouble && + isDateTime) { + val = Range._toOADate(tempDate).toString(); + } + _grid = saveGrid; + _cell = saveCell; + return val; + } + + /// Sums the cells specified by some criteria. + String _computeSumIf(String argList) { + final List args = _splitArgsPreservingQuotedCommas(argList); + final int argCount = args.length; + if (argCount != 2 && argCount != 3) { + if (_rethrowExceptions) { + throw Exception(_formulaErrorStrings[_wrongNumberArguments]); + } + return _formulaErrorStrings[_wrongNumberArguments]; + } + final String criteriaRange = args[0]; + String criteria = args[1]; ////.Replace(TIC, string.Empty); + if (criteria.isEmpty) { + return '0'; + } + String op = _tokenEqual; + if (criteria[0] != _tic[0] && !'=><'.contains(criteria[0])) { + ////cell reference + criteria = _getValueFromArg(criteria); + } + op = _findOp(criteria, op); + criteria = _findCriteria(criteria, op); + String sumRange = (argCount == 2) ? criteriaRange : args[2]; + + final List s1 = _getCellsFromArgs(criteriaRange); + List s2 = _getCellsFromArgs(sumRange); + if (s1[0] == _errorStrings[5] || s2[0] == _errorStrings[5]) { + return _errorStrings[5].toString(); + } + final int count = s1.length; + if (count > s2.length) { + final int i = sumRange.indexOf(':'); + final int j = criteriaRange.indexOf(':'); + int criteriaStartRow = _getRowIndex(criteriaRange.substring(0, j)); + int criteriaEndRow = _getRowIndex(criteriaRange.substring(j + 1)); + int criteriaStartCol = _getColIndex(criteriaRange.substring(0, j)); + int criteriaEndCol = _getColIndex(criteriaRange.substring(j + 1)); + if ((criteriaStartRow != -1 || criteriaEndRow == -1) != + (criteriaStartRow == -1 || criteriaEndRow != -1)) { + return _errorStrings[5].toString(); + } + //Below codtion has been added to find the row or column range when start row or start column is -1. + if (criteriaStartRow == -1) { + criteriaStartRow = (_grid)!.getFirstRow(); + } + if (criteriaStartCol == -1) { + criteriaStartCol = (_grid)!.getFirstColumn(); + } + if (criteriaEndRow == -1) { + criteriaEndRow = (_grid)!.getLastRow(); + } + if (criteriaEndCol == -1) { + criteriaEndCol = (_grid)!.getLastColumn(); + } + final int criteriaHeight = criteriaEndRow - criteriaStartRow; + final int crietriaWidth = criteriaEndCol - criteriaStartCol; + if (i > -1) { + int startRow = _getRowIndex(sumRange.substring(0, i)); + int row = _getRowIndex(sumRange.substring(i + 1)); + if ((startRow != -1 || row == -1) != (startRow == -1 || row != -1)) { + return _errorStrings[5].toString(); + } + int startCol = _getColIndex(sumRange.substring(0, i)); + int col = _getColIndex(sumRange.substring(i + 1)); + if (startRow == -1) { + startRow = (_grid)!.getFirstRow(); + } + if (startCol == -1) { + startCol = (_grid)!.getFirstColumn(); + } + if (row == -1) { + row = (_grid)!.getLastRow(); + } + if (col == -1) { + col = (_grid)!.getLastColumn(); + } + final int width = col - startCol; + final int height = row - startRow; + if (width != crietriaWidth) col = startCol + crietriaWidth; + if (height != criteriaHeight) row = startRow + criteriaHeight; + sumRange = RangeInfo._getAlphaLabel(startCol) + + sumRange.substring(1, i + 1) + + RangeInfo._getAlphaLabel(col) + + row.toString(); + } else { + int resultRow = 0, resultCol = 0; + String resultVal = ''; + resultRow = _getRowIndex(sumRange); + resultCol = _getColIndex(sumRange); + resultRow += criteriaHeight; + resultCol += crietriaWidth; + resultVal = RangeInfo._getAlphaLabel(resultCol); + sumRange = sumRange + ':' + resultVal + resultRow.toString(); + } + s2 = _getCellsFromArgs(sumRange); + } + double sum = 0; + double? d = 0.0; + String s = ''; + double compare = -1.7976931348623157E+308; + bool isNumber = false; + if (double.tryParse(criteria) != null) { + compare = double.tryParse(criteria)!; + isNumber = true; + } + for (int index = 0; index < count; ++index) { + s = _getValueFromArg(s1[index]); //// +criteria; + //Below condition is added to return Error string when s is Error string. + if (_errorStrings.contains(s)) { + if (_rethrowExceptions) { + throw Exception(_formulaErrorStrings[_invalidArguments]); + } + return s; + } + //Below code has beeb added to calculate SUMIF formula when criteria contains *. + final bool criteriaMatched = _checkForCriteriaMatch( + s.toUpperCase(), op, criteria.toUpperCase(), isNumber, compare); + if (criteriaMatched) { + s = s2[index]!; + s = _getValueFromArg(s); + d = double.tryParse(s); + final bool value = d != null; + if (value) { + sum += d; + } + } + } + return sum.toString(); + } + + /// Below method used to find the operation value which is combined with tokens. + String _findOp(String criteria, String op1) { + final int offset = (criteria.isNotEmpty && criteria[0] == _tic[0]) ? 1 : 0; + if (criteria.substring(offset).startsWith('>=')) { + op1 = _tokenGreaterEq; + } else if (criteria.substring(offset).startsWith('<=')) { + op1 = _tokenLesseq; + } else if (criteria.substring(offset).startsWith('<>')) { + op1 = _tokenNoEqual; + } else if (criteria.substring(offset).startsWith('<')) { + op1 = _tokenLess; + } else if (criteria.substring(offset).startsWith('>')) { + op1 = _tokenGreater; + } else if (criteria.substring(offset).startsWith('=')) { + op1 = _tokenEqual; + } + return op1; + } + + /// Returns the sum of the products of corresponding values. + String _computeSumProduct(String range) { + double sum = 0; + int count = 0; + double? d; + bool? indexValue = false; + List? vector; + List? ranges = null; + //Below code has been added to calculate the array structure values. + if (!range.contains(parseArgumentSeparator.toString()) && + !range.contains(_sheetToken.toString())) { + range = _adjustRangeArg(range); + } + if (range.contains(_tic) && + (range.startsWith(_tic) | range.endsWith(_tic))) { + ranges = range.split(_tic); + for (int i = 0; i < ranges!.length; ++i) { + if (ranges[i] == parseArgumentSeparator.toString()) { + final List list = ranges; + list.remove(ranges[i]); + ranges = list.toList(); + } + // Below code has been added to calculate when the ranges contain array and cell range.("1,1,0,0,0,1,1,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1",G5:G30). + else if (ranges[i].startsWith(parseArgumentSeparator.toString()) && + _isCellReference( + ranges[i].replaceAll(parseArgumentSeparator.toString(), ''))) { + ranges[i] = + ranges[i].replaceAll(parseArgumentSeparator.toString(), ''); + } + } + } else { + ranges = _splitArgsPreservingQuotedCommas(range); + } + for (final String r in ranges) { + String strArray = ''; + String errorString = ''; + //Below condition has been added to calculate when the argument conatains operators(ex:=SUMPRODUCT(--($V:$V="Monday"),($W:$W>=AB10)*($W:$W< AC10))). + if (r.contains(':') && !_isCellReference(r)) { + final String tempr = r.replaceAll(_bMarker.toString(), ''); + String finalStringValue = ''; + for (int j = 0; j <= tempr.length - 1; j++) { + String val = ''; + List? cells; + String logicalVal = ''; + String logicTest = ''; + //Below condition has been added to split the cell range. + while (j != tempr.length && + (_isDigit(tempr.codeUnitAt(j)) | + (tempr[j] == ':') | + (tempr[j] == '!') | + (j != 0 && + _isUpper(tempr[j]) && + !_isDigit(tempr.codeUnitAt(j - 1))) | + (j == 0 && _isUpper(tempr[j])))) { + val = val + tempr[j++]; + } + if (_exteriorFormula && tempr[j] == '{') { + j++; + val = tempr.substring(j, tempr.indexOf('}') - 1); + j += tempr.indexOf('}'); + cells = val.split(';'); + _exteriorFormula = false; + } + + if (j != tempr.length && (tempr[j] == '\'')) { + logicalVal = logicalVal + tempr[j++]; + while (j != tempr.length && (tempr[j] != '\'')) { + logicalVal = logicalVal + tempr[j++]; + } + logicalVal = logicalVal + tempr[j++]; + } + //Below condition has been added to split the logical value. + //Below code has been modified to sperate the logical value when there is 'n' with TRUE and numeric values. + while (j != tempr.length && + (_isUpper(tempr[j]) | + _isDigit(tempr.codeUnitAt(j)) | + (tempr[j] == 'n') || + tempr[j] == parseDecimalSeparator || + (_indexOfAny(tempr[j], ['a', 's', 'm', 'd', 'c']) > -1 && + _isDigit(tempr.codeUnitAt(j - 1))))) { + logicalVal = logicalVal + tempr[j++]; + } + + //Below condition used to add the token in logicalvalue.If the token is find break the condition/ + for (final String tempChar in _tokens) { + if (j != tempr.length && tempr[j] == tempChar) { + logicalVal = logicalVal + tempr[j]; + break; + } + } + cells ??= _getCellsFromArgs(val); + int s = 0; + if ((val == '') && (strArray != '')) { + final List args = + _splitArgsPreservingQuotedCommas(strArray); + final List tempLogicList = + args[0].replaceAll(_tic, '').split(';'); + final List tempLogicList1 = + args[1].replaceAll(_tic, '').split(';'); + { + for (s = 0; s <= tempLogicList.length - 1; s++) { + if (s + 1 != args.length) { + logicTest = _getValueFromArg(_bMarker.toString() + + tempLogicList[s] + + tempLogicList1[s] + + logicalVal + + _bMarker.toString()); + } + finalStringValue += logicTest + ';'; + } + } + //Below logic has been added to remove the previous value of straArray and to update it with newly calculated value. + strArray = ''; + } else { + for (s = 0; s <= cells.length - 1; s++) { + logicTest = _getValueFromArg(_bMarker.toString() + + cells[s]! + + logicalVal + + _bMarker.toString()); + finalStringValue += logicTest + ';'; + } + } + if (j == tempr.length - 1) { + strArray = + finalStringValue.substring(0, finalStringValue.length - 1); + } else { + finalStringValue = + finalStringValue.substring(0, finalStringValue.length - 1); + strArray += _tic + finalStringValue + _tic + parseArgumentSeparator; + } + finalStringValue = ''; + } + // perform multiplication + List result; + result = _performMultiplication( + strArray, indexValue, count, vector, errorString); + indexValue = result[0]; + count = result[1]; + vector = result[2]; + errorString = result[3]; + + if (errorString != '') return errorString; + } else if (!r.startsWith(_tic) && r.contains(':')) { + int i = r.indexOf(':'); + int row1 = _getRowIndex(r.substring(0, i)); + int row2 = _getRowIndex(r.substring(i + 1)); + if ((row1 != -1 || row2 == -1) != (row1 == -1 || row2 != -1)) { + return _errorStrings[5].ToString(); + } + int col1 = _getColIndex(r.substring(0, i)); + int col2 = _getColIndex(r.substring(i + 1)); + if (_grid is Worksheet) { + if (row1 == -1 && _grid is Worksheet) { + row1 = (_grid)!.getFirstRow(); + } + if (col1 == -1 && _grid is Worksheet) { + col1 = (_grid)!.getFirstColumn(); + } + if (row2 == -1 && _grid is Worksheet) { + row2 = (_grid)!.getLastRow(); + } + if (col2 == -1 && _grid is Worksheet) { + col2 = (_grid)!.getLastColumn(); + } + } + if (vector != null && count != (row2 - row1 + 1) * (col2 - col1 + 1)) { + if (_rethrowExceptions) { + throw Exception(_formulaErrorStrings[_badFormula]); + } + errorString = _errorStrings[1].toString(); + } else if (vector == null) { + count = (row2 - row1 + 1) * (col2 - col1 + 1); + vector = List.filled(count, 0, growable: false); + for (i = 0; i < count; ++i) { + vector[i] = 1; + } + } + final SheetFamilyItem? family = _getSheetFamilyItem(_grid); + final String s = _getSheetToken(r); + final Worksheet grd = + (s == '') ? _grid : family!._tokenToParentObject![s]; + + i = 0; + for (int row = row1; row <= row2; ++row) { + for (int col = col1; col <= col2; ++col) { + d = double.tryParse( + _getValueFromParentObjectGrid(row, col, true, grd) + .replaceAll(_tic, '')); + final String v = _getValueFromParentObjectGrid(row, col, true, grd) + .replaceAll(_tic, ''); + if (v == 'true' || v == 'false') { + indexValue = v.contains('true'); + } else { + indexValue = null; + } + + if (d != null) { + vector[i] = vector[i] * d; + } + //Below code is added to calculate the bool values eg(SUMPRODUCT({FALSE,TRUE,FALSE},{FALSE,FALSE,FALSE})). + else if (indexValue != null) { + final String val = indexValue.toString(); + double v = 0; + if (val == 'true') { + v = 1; + } + vector[i] = vector[i] * v; + } else { + vector[i] = 0; + } + + i++; + } + } + } + //Below condition has been added to calculate eg =SUMPRODUCT({0,0,1,0,1},{75,100,125,125,150}). + //Below condition has been modified to accept the range of values with Spain Culture. + else if (r.contains(parseArgumentSeparator.toString()) || + r.contains(';') || + r.contains('{')) { + String tempr = r.replaceAll(_bMarker.toString(), ''); + if (_exteriorFormula) { + tempr = tempr.replaceAll('{', '').replaceAll('}', ''); + _exteriorFormula = false; + } + // perform multiplication + List result; + result = _performMultiplication( + tempr, indexValue, count, vector, errorString); + indexValue = result[0]; + count = result[1]; + vector = result[2]; + errorString = result[3]; + + if (errorString != '') { + return errorString; + } + } else { + final String s1 = _getValueFromArg(r); + if (_errorStrings.contains(s1)) { + return s1; + } else { + if (_rethrowExceptions) { + throw Exception(_formulaErrorStrings[_badFormula]); + } + errorString = _errorStrings[1].toString(); + } + } + } + + for (int i = 0; i < count; ++i) { + sum += vector![i]; + } + + return sum.toString(); + } + + List _performMultiplication(String strArray, bool? indexValue, int count, + List? vector, String errorString) { + // perform multiplication + List tempRangs; + List temArray; + int j = 0; + double? d = 0; + if (strArray.contains(';')) { + tempRangs = strArray.split(';'); + final int listLength = tempRangs.length * + _splitArgsPreservingQuotedCommas(tempRangs[0]).length; + temArray = List.filled(listLength, '', growable: false); + } + //Below condition has been added to calculate the Sumproduct Value when parseArgument seperator is not comma(',') with Spain Culture. + else if (strArray.contains(',')) { + tempRangs = strArray.split(','); + temArray = List.filled( + tempRangs.length * + _splitArgsPreservingQuotedCommas(tempRangs[0]).length, + '', + growable: false); + } else { + tempRangs = _splitArgsPreservingQuotedCommas(strArray); + temArray = List.filled(tempRangs.length, '', growable: false); + } + + for (int i = 0; i < tempRangs.length; ++i) { + int e = 0; + if (tempRangs[i].contains(',')) { + final List arrayIndex = tempRangs[i].split(','); + while (e != arrayIndex.length) { + temArray[j] = arrayIndex[e]; + j++; + e++; + } + } else { + temArray[i] = tempRangs[i]; + } + } + tempRangs = temArray; + if (vector != null && count != tempRangs.length) { + if (_rethrowExceptions) { + throw Exception(_formulaErrorStrings[_badFormula]); + } + errorString = _errorStrings[1].toString(); + } else if (vector == null) { + count = tempRangs.length; + vector = List.filled(count, 0, growable: false); + for (int k = 0; k < count; ++k) { + vector[k] = 1; + } + } + for (int rr = 0; rr < tempRangs.length; ++rr) { + d = double.tryParse(tempRangs[rr]); + final String v = _getValueFromArg(tempRangs[rr]).toLowerCase(); + if (v == 'true' || v == 'false') { + indexValue = v.contains('true'); + } else { + indexValue = null; + } + if (d != null) { + vector[rr] = vector[rr] * d; + } + //Below code is added to calculate the bool values eg(SUMPRODUCT({FALSE,TRUE,FALSE},{FALSE,FALSE,FALSE})). + else if (indexValue != null) { + final String val = indexValue.toString(); + double v = 0; + if (val == 'true') { + v = 1; + } + vector[rr] = vector[rr] * v; + } else { + vector[rr] = 0; + } + } + return [indexValue, count, vector, errorString]; + } + + /// Returns the product of the arguments in the list. + String _computeProduct(String range) { + double prod = 1; + double? d; + String s1; + bool nohits = true; + range = _adjustRangeArg(range); + //Below condition has been modified to calculate when provided the numeric value as string. + final List ranges = + _splitArgsPreservingQuotedCommas(range.replaceAll(_tic, '')); + for (final String r in ranges) { + ////is a cellrange + if (r.contains(':')) { + for (final String? s in _getCellsFromArgs(r)) { + try { + s1 = _getValueFromArg(s); + s1 = _getValueForBool(s1); + } catch (ex) { + _exceptionThrown = true; + //Below code has been added to throw the excpetion when we enabel the RethrowLibraryComputationExceptions property. + if (_rethrowExceptions) { + rethrow; + } + return ex.toString(); + } + + if (s1.isNotEmpty) { + //Below condition has been modified to calculate when provided the numeric value as string. + d = double.tryParse(s1.replaceAll(_tic, '')); + if (d != null) { + prod = prod * d; + nohits = false; + } else if (_errorStrings.contains(s1)) { + return s1; + } + } + } + } else { + try { + s1 = _getValueFromArg(r); + s1 = _getValueForBool(s1); + } catch (ex) { + _exceptionThrown = true; + //Below code has been added to throw the excpetion when we enabel the RethrowLibraryComputationExceptions property. + if (_rethrowExceptions) { + rethrow; + } + return ex.toString(); + } + if (s1.isNotEmpty) { + //Below condition has been modified to calculate when provided the numeric value as string. + d = double.tryParse(s1.replaceAll(_tic, '')); + if (d != null) { + prod = prod * d; + nohits = false; + } else if (_errorStrings.contains(s1)) { + return s1; + } + } + } + } + return nohits ? '0' : prod.toString(); + } + + String _getValueForBool(String arg) { + if (arg == _trueValueStr || arg == 'n' + _trueValueStr) { + return '1'; + } else if (arg == _falseValueStr || arg == 'n' + _falseValueStr) { + return '0'; + } + return arg; + } } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/formula_info.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/formula_info.dart index 94c93c4cb..6734e4a3e 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/formula_info.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/formula_info.dart @@ -2,8 +2,8 @@ part of xlsio; /// FormulaInfo maintains information on a single formula object. class FormulaInfo { - String _formulaValue; - String _parsedFormula; + String _formulaValue = ''; + String _parsedFormula = ''; // ignore: prefer_final_fields int _calcID = -2147483648 + 1; } @@ -13,7 +13,7 @@ class FormulaInfo { class RangeInfo { /// GetAlphaLabel is a method that retrieves a String value for the column whose numerical index is passed in. static String _getAlphaLabel(int col) { - final List cols = List(10); + final List cols = List.filled(10, '', growable: false); int n = 0; while (col > 0 && n < 9) { col--; @@ -22,7 +22,7 @@ class RangeInfo { n++; } - final List chs = List(n); + final List chs = List.filled(n, '', growable: false); for (int i = 0; i < n; i++) { chs[n - i - 1] = cols[i]; } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/sheet_family_item.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/sheet_family_item.dart index 6a7fe4f96..d8d86277c 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/sheet_family_item.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/sheet_family_item.dart @@ -7,20 +7,20 @@ class SheetFamilyItem { bool _isSheeted = false; /// Holds mapping from parent object to sheet token. - Map _parentObjectToToken; + Map? _parentObjectToToken; /// Holds mapping for formula information table. - Map _sheetFormulaInfoTable; + Map? _sheetFormulaInfoTable; /// Holds mapping for dependent formula cells. - Map _sheetDependentFormulaCells; + Map? _sheetDependentFormulaCells; /// Holds mapping from sheet token to parent object. - Map _tokenToParentObject; + Map? _tokenToParentObject; /// Holds mapping from parent object to sheet name. - Map _sheetNameToToken; + Map? _sheetNameToToken; /// Holds mapping from sheet name to parent object. - Map _sheetNameToParentObject; + Map? _sheetNameToParentObject; } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/alignment.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/alignment.dart index 1f7bd2d47..2905e1e22 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/alignment.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/alignment.dart @@ -3,17 +3,17 @@ part of xlsio; /// Represent cell alignment class. class Alignment { /// Gets/sets horizontal alignment. - String horizontal; + late String horizontal; /// Gets/sets vertical alignment. - String vertical; + late String vertical; /// Gets/sets cell wraptext. - int wrapText; + late int wrapText; /// Gets/sets cell indent. - int indent; + late int indent; /// Gets/sets cell rotation. - int rotation; + late int rotation; } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/border.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/border.dart index 76419852e..f1d64bb39 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/border.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/border.dart @@ -3,10 +3,54 @@ part of xlsio; /// Represent cell individual border. class Border { /// Gets/sets border line style. - LineStyle lineStyle; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set borders line. + /// style.borders.all.lineStyle = LineStyle.thick; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late LineStyle lineStyle; /// Gets/sets borderline color. - String color; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set borders line color. + /// style.borders.all.lineStyle = LineStyle.thick; + /// style.borders.all.color = '#9954CC'; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late String color; + + /// Gets/sets borderline color Rgb. + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set borders line color. + /// style.borders.all.lineStyle = LineStyle.thick; + /// style.borders.all.colorRgb = Color.fromARBG(255, 255, 56, 255); + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late Color colorRgb; } /// Represent cell individual border. @@ -21,9 +65,30 @@ class CellBorder implements Border { @override LineStyle lineStyle = LineStyle.none; + String _color = '#000000'; + /// Gets/sets borderline color. @override - String color = '#000000'; + String get color => _color; + + @override + set color(String value) { + _color = value; + _colorRgb = + Color(int.parse(_color.substring(1, 7), radix: 16) + 0xFF000000); + } + + late Color _colorRgb; + + /// Gets/sets borderline color Rgb. + @override + Color get colorRgb => _colorRgb; + + @override + set colorRgb(Color value) { + _colorRgb = value; + _color = _colorRgb.value.toRadixString(16).toUpperCase(); + } /// Clone method of Cell Border. CellBorder _clone() { @@ -35,11 +100,11 @@ class CellBorder implements Border { /// Represent wrapper class for cell individual border. class CellBorderWrapper implements Border { /// Creates a instance of Border - CellBorderWrapper(List borders) { + CellBorderWrapper(List borders) { _borders = borders; } - List _borders; + late List _borders; /// Gets/sets border line style. final LineStyle _lineStyle = LineStyle.none; @@ -47,17 +112,20 @@ class CellBorderWrapper implements Border { /// Gets/sets borderline color. final String _color = '#000000'; + /// Gets/sets borderline color Rgb. + final Color _colorRgb = Color.fromARGB(255, 0, 0, 0); + /// Gets/sets border line style. @override LineStyle get lineStyle { LineStyle lineStyleStyle = _lineStyle; bool first = true; - for (final Border border in _borders) { + for (final Border? border in _borders) { if (first) { - lineStyleStyle = border.lineStyle; + lineStyleStyle = border!.lineStyle; first = false; - } else if (border.lineStyle != lineStyleStyle) { + } else if (border!.lineStyle != lineStyleStyle) { return LineStyle.none; } } @@ -66,8 +134,8 @@ class CellBorderWrapper implements Border { @override set lineStyle(LineStyle value) { - for (final Border border in _borders) { - border.lineStyle = value; + for (final Border? border in _borders) { + border!.lineStyle = value; } } @@ -77,11 +145,11 @@ class CellBorderWrapper implements Border { String colorStyle = _color; bool first = true; - for (final Border border in _borders) { + for (final Border? border in _borders) { if (first) { - colorStyle = border.color; + colorStyle = border!.color; first = false; - } else if (border.color != colorStyle) { + } else if (border!.color != colorStyle) { return '#000000'; } } @@ -90,8 +158,32 @@ class CellBorderWrapper implements Border { @override set color(String value) { - for (final Border border in _borders) { - border.color = value; + for (final Border? border in _borders) { + border!.color = value; + } + } + + /// Gets/sets borderline color Rgb. + @override + Color get colorRgb { + Color colorStyle = _colorRgb; + bool first = true; + + for (final Border? border in _borders) { + if (first) { + colorStyle = border!.colorRgb; + first = false; + } else if (border!.colorRgb != colorStyle) { + return Color.fromARGB(255, 0, 0, 0); + } + } + return colorStyle; + } + + @override + set colorRgb(Color value) { + for (final Border? border in _borders) { + border!.colorRgb = value; } } } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/borders.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/borders.dart index df51124c1..f06cced8a 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/borders.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/borders.dart @@ -3,22 +3,92 @@ part of xlsio; /// Represents cell borders class Borders { /// Represent the left border. - Border left; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set borders line style and color. + /// style.borders.left.lineStyle = LineStyle.thick; + /// style.borders.left.color = '#9954CC'; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late Border left; /// Represent the right border. - Border right; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set borders line style and color. + /// style.borders.right.lineStyle = LineStyle.thick; + /// style.borders.right.color = '#9954CC'; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late Border right; /// Represent the bottom border. - Border bottom; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set borders line style and color. + /// style.borders.bottom.lineStyle = LineStyle.thick; + /// style.borders.bottom.color = '#9954CC'; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late Border bottom; /// Represent the top border. - Border top; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set borders line style and color. + /// style.borders.all.lineStyle = LineStyle.thick; + /// style.borders.all.color = '#9954CC'; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late Border top; /// Represent the all borders. - Border all; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set borders line style and color. + /// style.borders.all.lineStyle = LineStyle.thick; + /// style.borders.all.color = '#9954CC'; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late Border all; /// Represent the workbook. - Workbook _workbook; + late Workbook _workbook; } /// Represents cell borders @@ -34,28 +104,28 @@ class BordersCollection implements Borders { } /// Represent the left border. - Border _left; + late Border _left; /// Represent the right border. - Border _right; + late Border _right; /// Represent the bottom border. - Border _bottom; + late Border _bottom; /// Represent the top border. - Border _top; + late Border _top; /// Represent the all borders. - Border _all; + late Border _all; /// Represent the workbook. @override - Workbook _workbook; + late Workbook _workbook; /// Represent the left border. @override // ignore: unnecessary_getters_setters - CellBorder get left { + Border get left { return _left; } @@ -68,7 +138,7 @@ class BordersCollection implements Borders { /// Represent the right border. @override // ignore: unnecessary_getters_setters - CellBorder get right { + Border get right { return _right; } @@ -81,7 +151,7 @@ class BordersCollection implements Borders { /// Represent the bottom border. @override // ignore: unnecessary_getters_setters - CellBorder get bottom { + Border get bottom { return _bottom; } @@ -94,7 +164,7 @@ class BordersCollection implements Borders { /// Represents the top border. @override // ignore: unnecessary_getters_setters - CellBorder get top { + Border get top { return _top; } @@ -107,7 +177,7 @@ class BordersCollection implements Borders { /// Represent the all borders. @override // ignore: unnecessary_getters_setters - CellBorder get all { + Border get all { return _all; } @@ -120,28 +190,34 @@ class BordersCollection implements Borders { /// Clone method of BordersCollecton. BordersCollection _clone() { final BordersCollection bordersCollection = BordersCollection(_workbook); - bordersCollection.all = all._clone(); - bordersCollection.left = left._clone(); - bordersCollection.right = right._clone(); - bordersCollection.top = top._clone(); - bordersCollection.bottom = bottom._clone(); + bordersCollection.all = (all as CellBorder)._clone(); + bordersCollection.left = (left as CellBorder)._clone(); + bordersCollection.right = (right as CellBorder)._clone(); + bordersCollection.top = (top as CellBorder)._clone(); + bordersCollection.bottom = (bottom as CellBorder)._clone(); return bordersCollection; } /// Compares two instances of the Cell borders. @override bool operator ==(Object toCompare) { - final BordersCollection toCompareBorders = toCompare; + // ignore: test_types_in_equals + final BordersCollection toCompareBorders = toCompare as BordersCollection; return (all.color == toCompareBorders.all.color && + all.colorRgb == toCompareBorders.all.colorRgb && all.lineStyle == toCompareBorders.all.lineStyle && left.color == toCompareBorders.left.color && + left.colorRgb == toCompareBorders.left.colorRgb && left.lineStyle == toCompareBorders.left.lineStyle && right.color == toCompareBorders.right.color && + right.colorRgb == toCompareBorders.right.colorRgb && right.lineStyle == toCompareBorders.right.lineStyle && top.color == toCompareBorders.top.color && + top.colorRgb == toCompareBorders.top.colorRgb && top.lineStyle == toCompareBorders.top.lineStyle && bottom.color == toCompareBorders.bottom.color && + bottom.colorRgb == toCompareBorders.bottom.colorRgb && bottom.lineStyle == toCompareBorders.bottom.lineStyle); } @@ -150,25 +226,25 @@ class BordersCollection implements Borders { /// Crear all the borders. void _clear() { - if (_all != null) { - _all = null; - } + // if (_all != null) { + // _all = null; + // } - if (_left != null) { - _left = null; - } + // if (_left != null) { + // _left = null; + // } - if (_right != null) { - _right = null; - } + // if (_right != null) { + // _right = null; + // } - if (_top != null) { - _top = null; - } + // if (_top != null) { + // _top = null; + // } - if (_bottom != null) { - _bottom = null; - } + // if (_bottom != null) { + // _bottom = null; + // } } } @@ -185,26 +261,26 @@ class BordersCollectionWrapper implements Borders { } /// Represent the left border. - Border _left; + Border? _left; /// Represent the right border. - Border _right; + Border? _right; /// Represent the bottom border. - Border _bottom; + Border? _bottom; /// Represent the top border. - Border _top; + Border? _top; /// Represent the all borders. - Border _all; + Border? _all; /// Represent the workbook. @override - Workbook _workbook; + late Workbook _workbook; - List _arrRanges; - List _bordersCollection; + late List _arrRanges; + late List _bordersCollection; /// Represent the left border. @override @@ -216,7 +292,7 @@ class BordersCollectionWrapper implements Borders { } _left = CellBorderWrapper(borders); } - return _left; + return _left!; } @override @@ -232,13 +308,13 @@ class BordersCollectionWrapper implements Borders { @override Border get right { if (_right == null) { - final List borders = []; + final List borders = []; for (final Borders border in _bordersCollection) { borders.add(border.right); } _right = CellBorderWrapper(borders); } - return _right; + return _right!; } @override @@ -254,13 +330,13 @@ class BordersCollectionWrapper implements Borders { @override Border get bottom { if (_bottom == null) { - final List borders = []; + final List borders = []; for (final Borders border in _bordersCollection) { borders.add(border.bottom); } _bottom = CellBorderWrapper(borders); } - return _bottom; + return _bottom!; } @override @@ -276,13 +352,13 @@ class BordersCollectionWrapper implements Borders { @override Border get top { if (_top == null) { - final List borders = []; + final List borders = []; for (final Borders border in _bordersCollection) { borders.add(border.top); } _top = CellBorderWrapper(borders); } - return _top; + return _top!; } @override @@ -298,13 +374,13 @@ class BordersCollectionWrapper implements Borders { @override Border get all { if (_all == null) { - final List borders = []; + final List borders = []; for (final Borders border in _bordersCollection) { borders.add(border.all); } _all = CellBorderWrapper(borders); } - return _all; + return _all!; } @override diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style.dart index 8596e6b99..056b199c5 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style.dart @@ -3,9 +3,9 @@ part of xlsio; /// Represent the Cell style class. class CellStyle implements Style { /// Creates an new instances of the Workbook. - CellStyle(Workbook workbook, [String name]) { + CellStyle(Workbook workbook, [String? name]) { _book = workbook; - backColor = 'none'; + backColor = '#FFFFFF'; fontName = 'Calibri'; fontSize = 11; fontColor = '#000000'; @@ -22,101 +22,118 @@ class CellStyle implements Style { borders = BordersCollection(_book); isGlobalStyle = false; locked = true; + _borders = BordersCollection(_book); if (name != null) this.name = name; } /// Represents cell style name. @override - String name; + String name = ''; /// Represents cell style index. @override - int index; + int index = -1; + + String _backColor = ''; @override /// Gets/sets back color. - String backColor; + String get backColor => _backColor; + + @override + set backColor(String value) { + _backColor = value; + _backColorRgb = + Color(int.parse(_backColor.substring(1, 7), radix: 16) + 0xFF000000); + } /// Gets/sets borders. - Borders _borders; + late BordersCollection _borders; @override /// Gets/sets font name. - String fontName; + late String fontName; @override /// Gets/sets font size. - double fontSize; + late double fontSize; + String _fontColor = ''; @override /// Gets/sets font color. - String fontColor; + String get fontColor => _fontColor; + + @override + set fontColor(String value) { + _fontColor = value; + _fontColorRgb = + Color(int.parse(_fontColor.substring(1, 7), radix: 16) + 0xFF000000); + } @override /// Gets/sets font italic. - bool italic; + late bool italic; @override /// Gets/sets font bold. - bool bold; + late bool bold; @override /// Gets/sets horizontal alignment. - HAlignType hAlign; + late HAlignType hAlign; @override /// Gets/sets cell indent. - int indent; + late int indent; @override /// Gets/sets cell rotation. - int rotation; + late int rotation; @override /// Gets/sets vertical alignment. - VAlignType vAlign; + late VAlignType vAlign; @override /// Gets/sets font underline. - bool underline; + late bool underline; @override /// Gets/sets cell wraptext. - bool wrapText; + late bool wrapText; /// Represents the global style. - bool isGlobalStyle; + bool isGlobalStyle = false; @override - int numberFormatIndex; + late int numberFormatIndex; /// Represents the workbook. - Workbook _book; + late Workbook _book; - int _builtinId; + int _builtinId = 0; @override /// Gets/Sets cell Lock - bool locked; + late bool locked; @override /// Sets borders. - BordersCollection get borders { - _borders ??= BordersCollection(_book); + Borders get borders { return _borders; } @@ -124,7 +141,7 @@ class CellStyle implements Style { /// Sets borders. set borders(Borders value) { - _borders = value; + _borders = value as BordersCollection; } /// Gets number format object. @@ -141,14 +158,14 @@ class CellStyle implements Style { @override /// Returns or sets the format code for the object. Read/write String. - String get numberFormat { + String? get numberFormat { return numberFormatObject._formatString; } @override /// Sets the number format. - set numberFormat(String value) { + set numberFormat(String? value) { numberFormatIndex = _book.innerFormats._findOrCreateFormat(value); } @@ -157,6 +174,34 @@ class CellStyle implements Style { return _book; } + Color _backColorRgb = Color.fromARGB(255, 0, 0, 0); + + Color _fontColorRgb = Color.fromARGB(255, 0, 0, 0); + + @override + + /// Gets/sets back color Rgb. + Color get backColorRgb => _backColorRgb; + + @override + set backColorRgb(Color value) { + _backColorRgb = value; + if (_backColorRgb.value.toRadixString(16).toUpperCase() != 'FFFFFFFF') { + _backColor = _backColorRgb.value.toRadixString(16).toUpperCase(); + } + } + + @override + + /// Gets/sets font color Rgb. + Color get fontColorRgb => _fontColorRgb; + + @override + set fontColorRgb(Color value) { + _fontColorRgb = value; + _fontColor = _fontColorRgb.value.toRadixString(16).toUpperCase(); + } + /// clone method of cell style CellStyle _clone() { final CellStyle _cellStyle = CellStyle(_workbook); @@ -179,7 +224,9 @@ class CellStyle implements Style { _cellStyle.numberFormatIndex = numberFormatIndex; _cellStyle.isGlobalStyle = isGlobalStyle; _cellStyle.locked = locked; - _cellStyle.borders = borders._clone(); + _cellStyle.borders = (borders as BordersCollection)._clone(); + _cellStyle.backColorRgb = backColorRgb; + _cellStyle.fontColorRgb = fontColorRgb; return _cellStyle; } @@ -187,7 +234,8 @@ class CellStyle implements Style { @override bool operator ==(Object toCompare) { final CellStyle baseStyle = this; - final CellStyle toCompareStyle = toCompare; + // ignore: test_types_in_equals + final CellStyle toCompareStyle = toCompare as CellStyle; return (baseStyle.backColor == toCompareStyle.backColor && baseStyle.bold == toCompareStyle.bold && @@ -204,37 +252,37 @@ class CellStyle implements Style { baseStyle.rotation == toCompareStyle.rotation && baseStyle.wrapText == toCompareStyle.wrapText && baseStyle.borders == toCompareStyle.borders && - baseStyle.locked == toCompareStyle.locked); + baseStyle.locked == toCompareStyle.locked && + baseStyle.backColorRgb == toCompareStyle.backColorRgb && + baseStyle.fontColorRgb == toCompareStyle.fontColorRgb); } @override int get hashCode => hashValues( - name, - backColor, - fontName, - fontSize, - fontColor, - italic, - bold, - underline, - wrapText, - hAlign, - vAlign, - indent, - rotation, - index, - _builtinId, - numberFormat, - numberFormatIndex, - isGlobalStyle, - locked, - borders); + name, + backColor, + fontName, + fontSize, + fontColor, + italic, + bold, + underline, + wrapText, + hAlign, + vAlign, + indent, + rotation, + index, + _builtinId, + numberFormat, + numberFormatIndex, + isGlobalStyle, + locked, + borders, + ); /// clear the borders void _clear() { - if (_borders != null) { - (_borders as BordersCollection)._clear(); - _borders = null; - } + _borders._clear(); } } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_wrapper.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_wrapper.dart index 0e19f50cc..80dfba686 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_wrapper.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_wrapper.dart @@ -7,21 +7,22 @@ class CellStyleWrapper implements Style { _arrRanges.addAll(range.cells); sheet = range.worksheet; workbook = sheet.workbook; + _borders = BordersCollectionWrapper(_arrRanges, workbook); } /// Gets/sets borders. - Borders _borders; + late Borders _borders; /// Represent the workbook. - Workbook workbook; + late Workbook workbook; /// Represent the sheet. - Worksheet sheet; + late Worksheet sheet; final List _arrRanges = []; @override String get name { - String nameStyle; + String nameStyle = ''; bool first = true; final int last = _arrRanges.length; @@ -32,7 +33,7 @@ class CellStyleWrapper implements Style { nameStyle = range.cellStyle.name; first = false; } else if (range.cellStyle.name != nameStyle) { - return null; + return ''; } } return nameStyle; @@ -76,7 +77,7 @@ class CellStyleWrapper implements Style { @override String get backColor { - String backColorStyle; + String backColorStyle = ''; bool first = true; final int last = _arrRanges.length; @@ -104,7 +105,6 @@ class CellStyleWrapper implements Style { @override Borders get borders { - _borders ??= BordersCollectionWrapper(_arrRanges, workbook); return _borders; } @@ -203,7 +203,7 @@ class CellStyleWrapper implements Style { @override String get fontColor { - String fontColorStyle; + String fontColorStyle = ''; bool first = true; final int last = _arrRanges.length; @@ -455,8 +455,8 @@ class CellStyleWrapper implements Style { @override //Represent numberFormat - String get numberFormat { - String numberFormatStyle; + String? get numberFormat { + String? numberFormatStyle; bool first = true; final int last = _arrRanges.length; @@ -474,7 +474,7 @@ class CellStyleWrapper implements Style { } @override - set numberFormat(String value) { + set numberFormat(String? value) { final int last = _arrRanges.length; for (int index = 0; index < last; index++) { final Range range = _arrRanges[index]; @@ -539,4 +539,60 @@ class CellStyleWrapper implements Style { (range.cellStyle as CellStyle).locked = value; } } + + @override + Color get backColorRgb { + Color backColorStyle = Color.fromARGB(255, 0, 0, 0); + bool first = true; + + final int last = _arrRanges.length; + for (int index = 0; index < last; index++) { + final Range range = _arrRanges[index]; + + if (first) { + backColorStyle = range.cellStyle.backColorRgb; + first = false; + } else if (range.cellStyle.backColorRgb != backColorStyle) { + return Color.fromARGB(255, 0, 0, 0); + } + } + return backColorStyle; + } + + @override + set backColorRgb(Color value) { + final int last = _arrRanges.length; + for (int index = 0; index < last; index++) { + final Range range = _arrRanges[index]; + range.cellStyle.backColorRgb = value; + } + } + + @override + Color get fontColorRgb { + Color fontColorStyle = Color.fromARGB(255, 0, 0, 0); + bool first = true; + + final int last = _arrRanges.length; + for (int index = 0; index < last; index++) { + final Range range = _arrRanges[index]; + + if (first) { + fontColorStyle = range.cellStyle.fontColorRgb; + first = false; + } else if (range.cellStyle.fontColorRgb != fontColorStyle) { + return Color.fromARGB(255, 0, 0, 0); + } + } + return fontColorStyle; + } + + @override + set fontColorRgb(Color value) { + final int last = _arrRanges.length; + for (int index = 0; index < last; index++) { + final Range range = _arrRanges[index]; + range.cellStyle.fontColorRgb = value; + } + } } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_xfs.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_xfs.dart index c68b47160..95268c30a 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_xfs.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_style_xfs.dart @@ -1,22 +1,22 @@ part of xlsio; /// Represents cell style xfs. -class CellStyleXfs { +class _CellStyleXfs { /// Represents number format id. - int _numberFormatId; + int _numberFormatId = 0; /// Represents font id. - int _fontId; + int _fontId = 0; /// Represents fill id. - int _fillId; + int _fillId = 0; /// Represents border id. - int _borderId; + int _borderId = 0; /// Represents alignment. - Alignment _alignment; + Alignment? _alignment; /// Represent protection. - int _locked; + int _locked = 1; } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_xfs.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_xfs.dart index 04e65468f..09fca1409 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_xfs.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/cell_xfs.dart @@ -1,7 +1,7 @@ part of xlsio; /// Represents cell xfs. -class CellXfs extends CellStyleXfs { +class _CellXfs extends _CellStyleXfs { /// Represents xf id. - int _xfId; + late int _xfId; } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/extend_compare_style.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/extend_compare_style.dart index 256e80492..ce87ba859 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/extend_compare_style.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/extend_compare_style.dart @@ -4,8 +4,8 @@ part of xlsio; class _ExtendCompareStyle { /// Represents the index of cell style. - int _index; + late int _index; /// Represents the result of the compare style. - bool _result; + late bool _result; } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/font.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/font.dart index f221373e9..3b93a0488 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/font.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/font.dart @@ -22,20 +22,20 @@ class Font { } /// Gets/sets font bold. - bool bold; + late bool bold; /// Gets/sets font italic. - bool italic; + late bool italic; /// Gets/sets font underline. - bool underline; + late bool underline; /// Gets/sets font size. - double size; + late double size; /// Gets/sets font name. - String name; + late String name; /// Gets/sets font color. - String color; + late String color; } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/global_style.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/global_style.dart index 6f5b26cba..31643d8b9 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/global_style.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/global_style.dart @@ -10,15 +10,15 @@ class _GlobalStyle { } /// Represents cell style name. - String _name; + late String _name; /// Represents xf id. - int _xfId; + late int _xfId; /// Number format. // ignore: unused_field - String _numberFormat; + String? _numberFormat; /// build in id. - int _builtinId; + late int _builtinId; } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/style.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/style.dart index c8c8fe229..9a9a82e91 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/style.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/style.dart @@ -3,56 +3,328 @@ part of xlsio; /// Represents the Style class. class Style { /// Represents cell style name. - String name; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// style.backColor = '#37D8E9'; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// // Check style name. + /// print(workbook.styles[1].name); + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late String name; /// Represents cell style index. - int index; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// style.backColor = '#37D8E9'; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// // Check index. + /// print(workbook.styles[1].index); + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late int index; /// Gets/sets back color. - String backColor; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set back color. + /// style.backColor = '#37D8E9'; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late String backColor; /// Gets/sets borders. - Borders borders; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set borders line style and color. + /// style.borders.all.lineStyle = LineStyle.thick; + /// style.borders.all.color = '#9954CC'; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late Borders borders; /// Gets/sets font name. - String fontName; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set fontName. + /// style.fontName = 'Times Roman' + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late String fontName; /// Gets/sets font size. - double fontSize; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set font size. + /// style.fontSize = 20; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late double fontSize; /// Gets/sets font color. - String fontColor; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set fontColor. + /// style.fontColor = '#C67878' + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late String fontColor; /// Gets/sets font italic. - bool italic; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set font italic. + /// style.italic = true; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late bool italic; /// Gets/sets font bold. - bool bold; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set font bold. + /// style.bold = true; + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late bool bold; /// Gets/sets horizontal alignment. - HAlignType hAlign; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // Set horizontal Alignment. + /// style.hAlign = HAlignType.left; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late HAlignType hAlign; /// Gets/sets cell indent. - int indent; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set cell indent. + /// style.indent = 1; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late int indent; - /// Gets/sets cell italic. - int rotation; + /// Gets/sets cell rotations. + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set cell rotations. + /// style.rotation = 90; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late int rotation; /// Gets/sets vertical alignment. - VAlignType vAlign; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set vertical alignment. + /// style.vAlign = VAlignType.bottom; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late VAlignType vAlign; /// Gets/sets font underline. - bool underline; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set font underline . + /// style.underline = true; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late bool underline; /// Gets/sets cell wraptext. - bool wrapText; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set wrap text. + /// style.wrapText = true; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late bool wrapText; /// Gets/sets cell numberFormat index. - int numberFormatIndex; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set number format. + /// style.numberFormat = '_(\$* #,##0_)'; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// // Check number format index. + /// print(workbook.styles[1].numberFormatIndex); + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late int numberFormatIndex; /// Gets/sets cell numberFormat. - String numberFormat; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set number format. + /// style.numberFormat = '_(\$* #,##0_)'; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + String? numberFormat; /// Gets/Sets cell Lock - bool locked; + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final Style style = workbook.styles.add('style'); + /// // set locked. + /// style.locked = true; + /// final Range range1 = sheet.getRangeByIndex(3, 4); + /// range1.number = 10; + /// range1.cellStyle = style; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late bool locked; + + /// Gets/sets back color Rgb. + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final CellStyle cellStyle1 = CellStyle(workbook); + /// cellStyle1.name = 'Style1'; + /// // set backcolor Rgb. + /// cellStyle1.backColorRgb = Color.fromARgb(255, 56, 250, 0); + /// workbook.styles.addStyle(cellStyle1); + /// final Range range1 = sheet.getRangeByIndex(1, 1); + /// range1.cellStyle = cellStyle1; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late Color backColorRgb; + + /// Gets/sets font color Rgb. + /// ```dart + /// final Workbook workbook = Workbook(); + /// final Worksheet sheet = workbook.worksheets[0]; + /// final CellStyle cellStyle1 = CellStyle(workbook); + /// cellStyle1.name = 'Style1'; + /// // set fontcolor Rgb. + /// cellStyle1.fontColorRgb = Color.fromARgb(255, 56, 250, 0); + /// workbook.styles.addStyle(cellStyle1); + /// final Range range1 = sheet.getRangeByIndex(1, 1); + /// range1.cellStyle = cellStyle1; + /// final List bytes = workbook.saveAsStream(); + /// File('CellStyle.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + late Color fontColorRgb; } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/styles_collection.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/styles_collection.dart index 3073a120a..82afd0bf8 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/styles_collection.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/cell_styles/styles_collection.dart @@ -10,13 +10,13 @@ class StylesCollection { } /// Parent workbook - Workbook _book; + late Workbook _book; /// Collection of worksheet - List